chore: inital commit

This commit is contained in:
Jan Meyer
2026-02-27 11:13:09 +01:00
commit ca1f47efc8
32 changed files with 40344 additions and 0 deletions

View File

@@ -0,0 +1,17 @@
{
"terminal": "wezterm",
"listenOn": "127.0.0.1:2006",
"openNeovimOnLoad": false,
"supportedFileTypes": [
"txt",
"md",
"css",
"js",
"ts",
"tsx",
"jsx",
"json"
],
"pathToBinary": "",
"appname": ""
}

33192
.obsidian/plugins/edit-in-neovim/main.js vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,10 @@
{
"id": "edit-in-neovim",
"name": "Edit in Neovim",
"version": "1.4.0",
"minAppVersion": "0.15.0",
"description": "Open a Neovim buffer for the currently open file",
"author": "Theseus",
"authorUrl": "https://github.com/TheseusGrey",
"isDesktopOnly": true
}

View File

@@ -0,0 +1,68 @@
{
"commitMessage": "vault backup: {{date}}",
"autoCommitMessage": "vault backup: {{date}}",
"commitMessageScript": "",
"commitDateFormat": "YYYY-MM-DD HH:mm:ss",
"autoSaveInterval": 0,
"autoPushInterval": 0,
"autoPullInterval": 0,
"autoPullOnBoot": false,
"autoCommitOnlyStaged": false,
"disablePush": false,
"pullBeforePush": true,
"disablePopups": false,
"showErrorNotices": true,
"disablePopupsForNoChanges": false,
"listChangedFilesInMessageBody": false,
"showStatusBar": true,
"updateSubmodules": false,
"syncMethod": "merge",
"mergeStrategy": "none",
"customMessageOnAutoBackup": false,
"autoBackupAfterFileChange": false,
"treeStructure": false,
"refreshSourceControl": true,
"basePath": "",
"differentIntervalCommitAndPush": false,
"changedFilesInStatusBar": false,
"showedMobileNotice": true,
"refreshSourceControlTimer": 7000,
"showBranchStatusBar": true,
"setLastSaveToLastCommit": false,
"submoduleRecurseCheckout": false,
"gitDir": "",
"showFileMenu": true,
"authorInHistoryView": "full",
"dateInHistoryView": true,
"diffStyle": "split",
"hunks": {
"showSigns": false,
"hunkCommands": false,
"statusBar": "disabled"
},
"lineAuthor": {
"show": false,
"followMovement": "inactive",
"authorDisplay": "initials",
"showCommitHash": false,
"dateTimeFormatOptions": "date",
"dateTimeFormatCustomString": "YYYY-MM-DD HH:mm",
"dateTimeTimezone": "viewer-local",
"coloringMaxAge": "1y",
"colorNew": {
"r": 255,
"g": 150,
"b": 150
},
"colorOld": {
"r": 120,
"g": 160,
"b": 255
},
"textColorCss": "var(--text-muted)",
"ignoreWhitespace": false,
"gutterSpacingFallbackLength": 5,
"lastShownAuthorDisplay": "initials",
"lastShownDateTimeFormatOptions": "date"
}
}

452
.obsidian/plugins/obsidian-git/main.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,10 @@
{
"author": "Vinzent",
"authorUrl": "https://github.com/Vinzent03",
"id": "obsidian-git",
"name": "Git",
"description": "Integrate Git version control with automatic backup and other advanced features.",
"isDesktopOnly": false,
"fundingUrl": "https://ko-fi.com/vinzent",
"version": "2.37.1"
}

View File

@@ -0,0 +1,705 @@
@keyframes loading {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.git-signs-gutter {
.cm-gutterElement {
/* Needed to align the sign properly for different line heigts. Such as
* when having a heading or list item.
*/
padding-top: 0 !important;
}
}
.workspace-leaf-content[data-type="git-view"] .button-border {
border: 2px solid var(--interactive-accent);
border-radius: var(--radius-s);
}
.workspace-leaf-content[data-type="git-view"] .view-content {
padding-left: 0;
padding-top: 0;
padding-right: 0;
}
.workspace-leaf-content[data-type="git-history-view"] .view-content {
padding-left: 0;
padding-top: 0;
padding-right: 0;
}
.loading {
overflow: hidden;
}
.loading > svg {
animation: 2s linear infinite loading;
transform-origin: 50% 50%;
display: inline-block;
}
.obsidian-git-center {
margin: auto;
text-align: center;
width: 50%;
}
.obsidian-git-textarea {
display: block;
margin-left: auto;
margin-right: auto;
}
.obsidian-git-disabled {
opacity: 0.5;
}
.obsidian-git-center-button {
display: block;
margin: 20px auto;
}
.tooltip.mod-left {
overflow-wrap: break-word;
}
.tooltip.mod-right {
overflow-wrap: break-word;
}
/* Limits the scrollbar to the view body */
.git-view {
display: flex;
flex-direction: column;
position: relative;
height: 100%;
}
.git-tools {
display: flex;
margin-left: auto;
}
.git-tools .type {
padding-left: var(--size-2-1);
display: flex;
align-items: center;
justify-content: center;
width: 11px;
}
.git-tools .type[data-type="M"] {
color: orange;
}
.git-tools .type[data-type="D"] {
color: red;
}
.git-tools .buttons {
display: flex;
}
.git-tools .buttons > * {
padding: 0 0;
height: auto;
}
.workspace-leaf-content[data-type="git-view"] .tree-item-self,
.workspace-leaf-content[data-type="git-history-view"] .tree-item-self {
align-items: center;
}
.workspace-leaf-content[data-type="git-view"]
.tree-item-self:hover
.clickable-icon,
.workspace-leaf-content[data-type="git-history-view"]
.tree-item-self:hover
.clickable-icon {
color: var(--icon-color-hover);
}
/* Highlight an item as active if it's diff is currently opened */
.is-active .git-tools .buttons > * {
color: var(--nav-item-color-active);
}
.git-author {
color: var(--text-accent);
}
.git-date {
color: var(--text-accent);
}
.git-ref {
color: var(--text-accent);
}
/* ====== diff2html ======
The following styles are adapted from the obsidian-version-history plugin by
@kometenstaub https://github.com/kometenstaub/obsidian-version-history-diff/blob/main/src/styles.scss
which itself is adapted from the diff2html library with the following original license:
https://github.com/rtfpessoa/diff2html/blob/master/LICENSE.md
Copyright 2014-2016 Rodrigo Fernandes https://rtfpessoa.github.io/
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
.theme-dark,
.theme-light {
--git-delete-bg: #ff475040;
--git-delete-hl: #96050a75;
--git-insert-bg: #68d36840;
--git-insert-hl: #23c02350;
--git-change-bg: #ffd55840;
--git-selected: #3572b0;
--git-delete: #c33;
--git-insert: #399839;
--git-change: #d0b44c;
--git-move: #3572b0;
}
.git-diff {
.d2h-d-none {
display: none;
}
.d2h-wrapper {
text-align: left;
border-radius: 0.25em;
overflow: auto;
}
.d2h-file-header.d2h-file-header {
background-color: var(--background-secondary);
border-bottom: 1px solid var(--background-modifier-border);
font-family:
Source Sans Pro,
Helvetica Neue,
Helvetica,
Arial,
sans-serif;
height: 35px;
padding: 5px 10px;
}
.d2h-file-header,
.d2h-file-stats {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
}
.d2h-file-header {
display: none;
}
.d2h-file-stats {
font-size: 14px;
margin-left: auto;
}
.d2h-lines-added {
border: 1px solid var(--color-green);
border-radius: 5px 0 0 5px;
color: var(--color-green);
padding: 2px;
text-align: right;
vertical-align: middle;
}
.d2h-lines-deleted {
border: 1px solid var(--color-red);
border-radius: 0 5px 5px 0;
color: var(--color-red);
margin-left: 1px;
padding: 2px;
text-align: left;
vertical-align: middle;
}
.d2h-file-name-wrapper {
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
font-size: 15px;
width: 100%;
}
.d2h-file-name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: var(--text-normal);
font-size: var(--h5-size);
}
.d2h-file-wrapper {
border: 1px solid var(--background-secondary-alt);
border-radius: 3px;
margin-bottom: 1em;
max-height: 100%;
}
.d2h-file-collapse {
-webkit-box-pack: end;
-ms-flex-pack: end;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
border: 1px solid var(--background-secondary-alt);
border-radius: 3px;
cursor: pointer;
display: none;
font-size: 12px;
justify-content: flex-end;
padding: 4px 8px;
}
.d2h-file-collapse.d2h-selected {
background-color: var(--git-selected);
}
.d2h-file-collapse-input {
margin: 0 4px 0 0;
}
.d2h-diff-table {
border-collapse: collapse;
font-family: var(--font-monospace);
font-size: var(--code-size);
width: 100%;
}
.d2h-files-diff {
width: 100%;
}
.d2h-file-diff {
/*
overflow-y: scroll;
*/
border-radius: 5px;
font-size: var(--font-text-size);
line-height: var(--line-height-normal);
}
.d2h-file-side-diff {
display: inline-block;
margin-bottom: -8px;
margin-right: -4px;
overflow-x: scroll;
overflow-y: hidden;
width: 50%;
}
.d2h-code-line {
padding-left: 6em;
padding-right: 1.5em;
}
.d2h-code-line,
.d2h-code-side-line {
display: inline-block;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
white-space: nowrap;
width: 100%;
}
.d2h-code-side-line {
/* needed to be changed */
padding-left: 0.5em;
padding-right: 0.5em;
}
.d2h-code-line-ctn {
word-wrap: normal;
background: none;
display: inline-block;
padding: 0;
-webkit-user-select: text;
-moz-user-select: text;
-ms-user-select: text;
user-select: text;
vertical-align: middle;
width: 100%;
/* only works for line-by-line */
white-space: pre-wrap;
}
.d2h-code-line del,
.d2h-code-side-line del {
background-color: var(--git-delete-hl);
color: var(--text-normal);
}
.d2h-code-line del,
.d2h-code-line ins,
.d2h-code-side-line del,
.d2h-code-side-line ins {
border-radius: 0.2em;
display: inline-block;
margin-top: -1px;
text-decoration: none;
vertical-align: middle;
}
.d2h-code-line ins,
.d2h-code-side-line ins {
background-color: var(--git-insert-hl);
text-align: left;
}
.d2h-code-line-prefix {
word-wrap: normal;
background: none;
display: inline;
padding: 0;
white-space: pre;
}
.line-num1 {
float: left;
}
.line-num1,
.line-num2 {
-webkit-box-sizing: border-box;
box-sizing: border-box;
overflow: hidden;
/*
padding: 0 0.5em;
*/
text-overflow: ellipsis;
width: 2.5em;
padding-left: 0;
}
.line-num2 {
float: right;
}
.d2h-code-linenumber {
background-color: var(--background-primary);
border: solid var(--background-modifier-border);
border-width: 0 1px;
-webkit-box-sizing: border-box;
box-sizing: border-box;
color: var(--text-faint);
cursor: pointer;
display: inline-block;
position: absolute;
text-align: right;
width: 5.5em;
}
.d2h-code-linenumber:after {
content: "\200b";
}
.d2h-code-side-linenumber {
background-color: var(--background-primary);
border: solid var(--background-modifier-border);
border-width: 0 1px;
-webkit-box-sizing: border-box;
box-sizing: border-box;
color: var(--text-faint);
cursor: pointer;
overflow: hidden;
padding: 0 0.5em;
text-align: right;
text-overflow: ellipsis;
width: 4em;
/* needed to be changed */
display: table-cell;
position: relative;
}
.d2h-code-side-linenumber:after {
content: "\200b";
}
.d2h-code-side-emptyplaceholder,
.d2h-emptyplaceholder {
background-color: var(--background-primary);
border-color: var(--background-modifier-border);
}
.d2h-code-line-prefix,
.d2h-code-linenumber,
.d2h-code-side-linenumber,
.d2h-emptyplaceholder {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.d2h-code-linenumber,
.d2h-code-side-linenumber {
direction: rtl;
}
.d2h-del {
background-color: var(--git-delete-bg);
border-color: var(--git-delete-hl);
}
.d2h-ins {
background-color: var(--git-insert-bg);
border-color: var(--git-insert-hl);
}
.d2h-info {
background-color: var(--background-primary);
border-color: var(--background-modifier-border);
color: var(--text-faint);
}
.d2h-del,
.d2h-ins,
.d2h-file-diff .d2h-change {
color: var(--text-normal);
}
.d2h-file-diff .d2h-del.d2h-change {
background-color: var(--git-change-bg);
}
.d2h-file-diff .d2h-ins.d2h-change {
background-color: var(--git-insert-bg);
}
.d2h-file-list-wrapper {
a {
text-decoration: none;
cursor: default;
-webkit-user-drag: none;
}
svg {
display: none;
}
}
.d2h-file-list-header {
text-align: left;
}
.d2h-file-list-title {
display: none;
}
.d2h-file-list-line {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
text-align: left;
}
.d2h-file-list {
}
.d2h-file-list > li {
border-bottom: 1px solid var(--background-modifier-border);
margin: 0;
padding: 5px 10px;
}
.d2h-file-list > li:last-child {
border-bottom: none;
}
.d2h-file-switch {
cursor: pointer;
display: none;
font-size: 10px;
}
.d2h-icon {
fill: currentColor;
margin-right: 10px;
vertical-align: middle;
}
.d2h-deleted {
color: var(--git-delete);
}
.d2h-added {
color: var(--git-insert);
}
.d2h-changed {
color: var(--git-change);
}
.d2h-moved {
color: var(--git-move);
}
.d2h-tag {
background-color: var(--background-secondary);
display: -webkit-box;
display: -ms-flexbox;
display: flex;
font-size: 10px;
margin-left: 5px;
padding: 0 2px;
}
.d2h-deleted-tag {
border: 1px solid var(--git-delete);
}
.d2h-added-tag {
border: 1px solid var(--git-insert);
}
.d2h-changed-tag {
border: 1px solid var(--git-change);
}
.d2h-moved-tag {
border: 1px solid var(--git-move);
}
/* needed for line-by-line*/
.d2h-diff-tbody {
position: relative;
}
}
/* ====================== Line Authoring Information ====================== */
.cm-gutterElement.obs-git-blame-gutter {
/* Add background color to spacing inbetween and around the gutter for better aesthetics */
border-width: 0px 2px 0.2px 2px;
border-style: solid;
border-color: var(--background-secondary);
background-color: var(--background-secondary);
}
.cm-gutterElement.obs-git-blame-gutter > div,
.line-author-settings-preview {
/* delegate text color to settings */
color: var(--obs-git-gutter-text);
font-family: monospace;
height: 100%; /* ensure, that age-based background color occupies entire parent */
text-align: right;
padding: 0px 6px 0px 6px;
white-space: pre; /* Keep spaces and do not collapse them. */
}
@media (max-width: 800px) {
/* hide git blame gutter not to superpose text */
.cm-gutterElement.obs-git-blame-gutter {
display: none;
}
}
.git-unified-diff-view,
.git-split-diff-view .cm-deletedLine .cm-changedText {
background-color: #ee443330;
}
.git-unified-diff-view,
.git-split-diff-view .cm-insertedLine .cm-changedText {
background-color: #22bb2230;
}
.git-obscure-prompt[git-is-obscured="true"] #git-show-password:after {
-webkit-mask-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="svg-icon lucide-eye"><path d="M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0"></path><circle cx="12" cy="12" r="3"></circle></svg>');
}
.git-obscure-prompt[git-is-obscured="false"] #git-show-password:after {
-webkit-mask-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="svg-icon lucide-eye-off"><path d="M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49"></path><path d="M14.084 14.158a3 3 0 0 1-4.242-4.242"></path><path d="M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143"></path><path d="m2 2 20 20"></path></svg>');
}
/* Override styling of Codemirror merge view "collapsed lines" indicator */
.git-split-diff-view .ͼ2 .cm-collapsedLines {
background: var(--interactive-normal);
border-radius: var(--radius-m);
color: var(--text-accent);
font-size: var(--font-small);
padding: var(--size-4-1) var(--size-4-1);
}
.git-split-diff-view .ͼ2 .cm-collapsedLines:hover {
background: var(--interactive-hover);
color: var(--text-accent-hover);
}
.git-signs-gutter {
.cm-gutterElement {
display: grid;
}
}
.git-gutter-marker:hover {
border-radius: 2px;
}
.git-gutter-marker.git-add {
background-color: var(--color-green);
justify-self: center;
height: inherit;
width: 0.2rem;
}
.git-gutter-marker.git-change {
background-color: var(--color-yellow);
justify-self: center;
height: inherit;
width: 0.2rem;
}
.git-gutter-marker.git-changedelete {
color: var(--color-yellow);
font-weight: var(--font-bold);
font-size: 1rem;
justify-self: center;
height: inherit;
}
.git-gutter-marker.git-delete {
background-color: var(--color-red);
height: 0.2rem;
width: 0.8rem;
align-self: end;
}
.git-gutter-marker.git-topdelete {
background-color: var(--color-red);
height: 0.2rem;
width: 0.8rem;
align-self: start;
}
div:hover > .git-gutter-marker.git-change {
width: 0.6rem;
}
div:hover > .git-gutter-marker.git-add {
width: 0.6rem;
}
div:hover > .git-gutter-marker.git-delete {
height: 0.6rem;
}
div:hover > .git-gutter-marker.git-topdelete {
height: 0.6rem;
}
div:hover > .git-gutter-marker.git-changedelete {
font-weight: var(--font-bold);
}
.git-gutter-marker.staged {
opacity: 0.5;
}
.git-diff {
.cm-merge-revert {
width: 4em;
}
/* Ensure that merge revert markers are positioned correctly */
.cm-merge-revert > * {
position: absolute;
background-color: var(--background-secondary);
display: flex;
}
}
/* Prevent shifting of the editor when git signs gutter is the only gutter present */
.cm-gutters.cm-gutters-before:has(> .git-signs-gutter:only-child) {
margin-inline-end: 0;
.git-signs-gutter {
margin-inline-start: -1rem;
}
}
.git-changes-status-bar-colored {
.git-add {
color: var(--color-green);
}
.git-change {
color: var(--color-yellow);
}
.git-delete {
color: var(--color-red);
}
}
.git-changes-status-bar .git-add {
margin-right: 0.3em;
}
.git-changes-status-bar .git-change {
margin-right: 0.3em;
}

View File

@@ -0,0 +1,34 @@
{
"lightStyle": "minimal-light",
"darkStyle": "minimal-dark",
"lightScheme": "minimal-default-light",
"darkScheme": "minimal-flexoki-dark",
"editorFont": "",
"lineHeight": 1.5,
"lineWidth": 40,
"lineWidthWide": 50,
"maxWidth": 88,
"textNormal": 16,
"textSmall": 13,
"imgGrid": false,
"imgWidth": "img-default-width",
"tableWidth": "table-default-width",
"iframeWidth": "iframe-default-width",
"mapWidth": "map-default-width",
"chartWidth": "chart-default-width",
"colorfulHeadings": false,
"colorfulFrame": false,
"colorfulActiveStates": false,
"trimNames": true,
"labeledNav": false,
"fullWidthMedia": true,
"bordersToggle": true,
"minimalStatus": true,
"focusMode": false,
"underlineInternal": true,
"underlineExternal": true,
"folding": true,
"lineNumbers": false,
"readableLineLength": true,
"devBlockWidth": false
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,11 @@
{
"id": "obsidian-minimal-settings",
"name": "Minimal Theme Settings",
"version": "8.1.1",
"minAppVersion": "1.1.9",
"description": "Change the colors, fonts and features of Minimal Theme.",
"author": "@kepano",
"authorUrl": "https://www.twitter.com/kepano",
"fundingUrl": "https://www.buymeacoffee.com/kepano",
"isDesktopOnly": false
}

View File

@@ -0,0 +1,11 @@
{
"typstPath": "typst",
"enableInlineMath": true,
"enableDisplayMath": true,
"debugMode": false,
"showDetailedErrors": true,
"useWatchMode": false,
"enableLivePreview": false,
"forceSyncCompile": true,
"syncOnlyDuringExport": true
}

View File

@@ -0,0 +1,805 @@
/*
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
if you want to view the source, please visit the github repository of this plugin
*/
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// main.ts
var main_exports = {};
__export(main_exports, {
default: () => TypstMathPlugin
});
module.exports = __toCommonJS(main_exports);
var import_child_process = require("child_process");
var import_fs = require("fs");
var import_obsidian = require("obsidian");
var os = __toESM(require("os"));
var path = __toESM(require("path"));
var DEFAULT_SETTINGS = {
typstPath: "typst",
enableInlineMath: true,
enableDisplayMath: true,
debugMode: false,
showDetailedErrors: false,
useWatchMode: false,
enableLivePreview: true,
forceSyncCompile: false,
syncOnlyDuringExport: true
};
var TypstMathPlugin = class extends import_obsidian.Plugin {
constructor() {
super(...arguments);
// Internal flag set when a print/export flow is active
this.exportInProgress = false;
// Hold original window.print so we can restore it on unload
this._originalWindowPrint = null;
// Keep reference to matchMedia listener so we can remove it
this._printMediaListener = null;
this.watchers = /* @__PURE__ */ new Map();
// Store typst source content for blocks that are pending compilation
this.pendingTypstContent = /* @__PURE__ */ new Map();
this.renderCache = /* @__PURE__ */ new Map();
this.handleBeforePrint = () => {
this.exportInProgress = true;
if (this.settings.debugMode)
console.log("Typst plugin: export/print started");
};
this.handleAfterPrint = () => {
this.exportInProgress = false;
if (this.settings.debugMode)
console.log("Typst plugin: export/print finished");
};
}
async onload() {
var _a;
await this.loadSettings();
this.tempDir = path.join(os.tmpdir(), "obsidian-typst-math");
await this.ensureTempDir();
this.registerMarkdownCodeBlockProcessor(
"math",
this.processMathBlock.bind(this)
);
this.registerMarkdownCodeBlockProcessor(
"typst",
this.processMathBlock.bind(this)
);
await (0, import_obsidian.loadMathJax)();
if (!globalThis.MathJax) {
new import_obsidian.Notice("MathJax failed to load. Math rendering may not work.");
console.error("MathJax failed to load.");
} else {
this.originalTex2chtml = globalThis.MathJax.tex2chtml;
globalThis.MathJax.tex2chtml = (latex, options) => {
return this.renderWithTypst(latex, options);
};
const activeView = this.app.workspace.getActiveViewOfType(import_obsidian.MarkdownView);
if (activeView) {
(_a = activeView.previewMode) == null ? void 0 : _a.rerender(true);
if (this.settings.enableLivePreview) {
const editor = activeView.editor;
if (editor) {
const cursor = editor.getCursor();
editor.setCursor(cursor);
}
}
}
}
if (typeof window !== "undefined") {
window.addEventListener("beforeprint", this.handleBeforePrint);
window.addEventListener("afterprint", this.handleAfterPrint);
try {
if (typeof window.print === "function") {
this._originalWindowPrint = window.print.bind(window);
window.print = (...args) => {
this.handleBeforePrint();
try {
const res = this._originalWindowPrint(...args);
setTimeout(() => this.handleAfterPrint(), 1500);
return res;
} catch (e) {
this.handleAfterPrint();
throw e;
}
};
}
} catch (e) {
if (this.settings.debugMode)
console.warn("Failed to patch window.print", e);
}
try {
const mql = window.matchMedia && window.matchMedia("print");
if (mql) {
this._printMediaListener = (ev) => {
if (ev.matches)
this.handleBeforePrint();
else
this.handleAfterPrint();
};
if (typeof mql.addEventListener === "function") {
mql.addEventListener("change", this._printMediaListener);
} else if (typeof mql.addListener === "function") {
mql.addListener(this._printMediaListener);
}
}
} catch (e) {
if (this.settings.debugMode)
console.warn("Failed to attach matchMedia print listener", e);
}
}
this.addSettingTab(new TypstMathSettingTab(this.app, this));
this.addCommand({
id: "typst-prepare-export",
name: "Typst: Prepare export (sync compile all math)",
callback: () => {
void this.prepareExport();
}
});
console.log("Typst Math Plugin loaded");
}
async onunload() {
var _a;
if (this.originalTex2chtml && globalThis.MathJax) {
globalThis.MathJax.tex2chtml = this.originalTex2chtml;
(_a = this.app.workspace.getActiveViewOfType(import_obsidian.MarkdownView)) == null ? void 0 : _a.previewMode.rerender(true);
}
for (const [id, process] of this.watchers) {
process.kill();
}
this.watchers.clear();
try {
await import_fs.promises.rm(this.tempDir, { recursive: true, force: true });
} catch (error) {
console.error("Error cleaning up temp directory:", error);
}
try {
if (typeof window !== "undefined") {
window.removeEventListener("beforeprint", this.handleBeforePrint);
window.removeEventListener("afterprint", this.handleAfterPrint);
if (this._printMediaListener && window.matchMedia) {
const mql = window.matchMedia("print");
if (mql) {
if (typeof mql.removeEventListener === "function") {
mql.removeEventListener("change", this._printMediaListener);
} else if (typeof mql.removeListener === "function") {
mql.removeListener(this._printMediaListener);
}
}
}
if (this._originalWindowPrint) {
try {
window.print = this._originalWindowPrint;
} catch (e) {
}
}
}
} catch (e) {
if (this.settings.debugMode)
console.warn("Error restoring print handlers", e);
}
console.log("Typst Math Plugin unloaded");
}
async loadSettings() {
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
}
async saveSettings() {
await this.saveData(this.settings);
}
async ensureTempDir() {
try {
await import_fs.promises.mkdir(this.tempDir, { recursive: true });
} catch (error) {
console.error("Error creating temp directory:", error);
}
}
formatError(error, isInline = false) {
const errorMsg = typeof error === "string" ? error : error.message;
let friendlyMsg = "There was an error with your Typst code";
if (errorMsg.includes("ENOENT") || errorMsg.includes("not found")) {
friendlyMsg = "Typst CLI not found";
} else if (errorMsg.includes("syntax error") || errorMsg.includes("unexpected")) {
friendlyMsg = "Syntax error in Typst code";
} else if (errorMsg.includes("undefined")) {
friendlyMsg = "Undefined symbol or function";
} else if (errorMsg.includes("type")) {
friendlyMsg = "Type error in expression";
}
const errorClass = isInline ? "typst-error-inline" : "typst-error";
if (this.settings.showDetailedErrors) {
return `<span class="${errorClass}">${friendlyMsg}</span><span class="typst-error-details">${errorMsg}</span>`;
} else {
return `<span class="${errorClass}">${friendlyMsg}</span>`;
}
}
async processMathBlock(source, el, ctx) {
const blockId = this.generateBlockId(source, ctx.sourcePath);
if (this.renderCache.has(blockId)) {
el.empty();
const container2 = el.createDiv({ cls: "typst-math-container" });
const cached = this.renderCache.get(blockId);
container2.innerHTML = cached != null ? cached : "";
return;
}
el.empty();
const container = el.createDiv({ cls: "typst-math-container" });
container.innerHTML = '<div class="typst-loading">Rendering with Typst...</div>';
container.setAttribute("data-typst-blockid", blockId);
try {
const typstContent = this.wrapInTypstDocument(source);
const typstFile = path.join(this.tempDir, `${blockId}.typ`);
const htmlFile = path.join(this.tempDir, `${blockId}.html`);
this.pendingTypstContent.set(blockId, typstContent);
await import_fs.promises.writeFile(typstFile, typstContent, "utf-8");
if (this.settings.useWatchMode) {
await this.renderTypstWithWatch(typstFile, htmlFile, container, blockId);
} else {
await this.renderTypstToHtml(typstFile, htmlFile, container, blockId);
}
} catch (error) {
container.innerHTML = this.formatError(error, false);
if (this.settings.debugMode) {
console.error("Typst rendering error:", error);
}
}
}
renderWithTypst(latex, options) {
const isBlock = options.display || false;
if (isBlock && !this.settings.enableDisplayMath) {
return this.originalTex2chtml(latex, options);
}
if (!isBlock && !this.settings.enableInlineMath) {
return this.originalTex2chtml(latex, options);
}
if (this.hasLatexCommand(latex)) {
}
const typstContent = this.convertLatexToTypst(latex);
const blockId = this.generateBlockId(typstContent, `math-${Date.now()}`);
const container = document.createElement(isBlock ? "div" : "span");
container.className = isBlock ? "typst-math-container" : "typst-math-inline";
container.innerHTML = '<span class="typst-loading">Rendering with Typst...</span>';
try {
container.setAttribute("data-typst-blockid", blockId);
this.pendingTypstContent.set(blockId, typstContent);
} catch (e) {
}
this.renderTypstMath(typstContent, container, blockId, isBlock);
return container;
}
hasLatexCommand(expr) {
return false;
}
convertLatexToTypst(latex) {
let content = latex.trim();
content = content.replace(/^\$\$/, "").replace(/\$\$$/, "");
content = content.replace(/^\$/, "").replace(/\$$/, "");
const conversions = [
// Fractions
[/\\frac\{([^}]+)\}\{([^}]+)\}/g, "($1)/($2)"],
// Superscripts and subscripts (already mostly compatible)
// Greek letters (mostly the same, just remove backslash)
[/\\alpha\b/g, "alpha"],
[/\\beta\b/g, "beta"],
[/\\gamma\b/g, "gamma"],
[/\\delta\b/g, "delta"],
[/\\epsilon\b/g, "epsilon"],
[/\\zeta\b/g, "zeta"],
[/\\eta\b/g, "eta"],
[/\\theta\b/g, "theta"],
[/\\iota\b/g, "iota"],
[/\\kappa\b/g, "kappa"],
[/\\lambda\b/g, "lambda"],
[/\\mu\b/g, "mu"],
[/\\nu\b/g, "nu"],
[/\\xi\b/g, "xi"],
[/\\pi\b/g, "pi"],
[/\\rho\b/g, "rho"],
[/\\sigma\b/g, "sigma"],
[/\\tau\b/g, "tau"],
[/\\phi\b/g, "phi"],
[/\\chi\b/g, "chi"],
[/\\psi\b/g, "psi"],
[/\\omega\b/g, "omega"],
// Capital Greek
[/\\Gamma\b/g, "Gamma"],
[/\\Delta\b/g, "Delta"],
[/\\Theta\b/g, "Theta"],
[/\\Lambda\b/g, "Lambda"],
[/\\Xi\b/g, "Xi"],
[/\\Pi\b/g, "Pi"],
[/\\Sigma\b/g, "Sigma"],
[/\\Phi\b/g, "Phi"],
[/\\Psi\b/g, "Psi"],
[/\\Omega\b/g, "Omega"],
// Common functions
[/\\sin\b/g, "sin"],
[/\\cos\b/g, "cos"],
[/\\tan\b/g, "tan"],
[/\\log\b/g, "log"],
[/\\ln\b/g, "ln"],
[/\\exp\b/g, "exp"],
// Sums and integrals
[/\\sum/g, "sum"],
[/\\prod/g, "product"],
[/\\int/g, "integral"],
[/\\infty\b/g, "oo"],
// Arrows
[/\\rightarrow\b/g, "->"],
[/\\leftarrow\b/g, "<-"],
[/\\Rightarrow\b/g, "=>"],
[/\\Leftarrow\b/g, "<="],
// Operators
[/\\times\b/g, "times"],
[/\\cdot\b/g, "dot"],
[/\\pm\b/g, "plus.minus"],
[/\\mp\b/g, "minus.plus"],
// Special sets
[/\\mathbb\{R\}/g, "RR"],
[/\\mathbb\{N\}/g, "NN"],
[/\\mathbb\{Z\}/g, "ZZ"],
[/\\mathbb\{Q\}/g, "QQ"],
[/\\mathbb\{C\}/g, "CC"],
// Limits
[/\\lim/g, "lim"],
[/\\to\b/g, "->"],
// Parentheses (mostly the same)
[/\\left\(/g, "("],
[/\\right\)/g, ")"],
[/\\left\[/g, "["],
[/\\right\]/g, "]"],
[/\\left\{/g, "{"],
[/\\right\}/g, "}"],
// Sqrt
[/\\sqrt\{([^}]+)\}/g, "sqrt($1)"],
// Text
[/\\text\{([^}]+)\}/g, '"$1"']
];
for (const [pattern, replacement] of conversions) {
content = content.replace(pattern, replacement);
}
return content.trim();
}
async renderTypstMath(mathContent, container, blockId, isBlock) {
if (this.renderCache.has(blockId)) {
container.innerHTML = this.renderCache.get(blockId);
return;
}
try {
const wrappedContent = `$ ${mathContent} $`;
const typstContent = this.wrapInTypstDocument(wrappedContent, isBlock);
const typstFile = path.join(this.tempDir, `${blockId}.typ`);
const htmlFile = path.join(this.tempDir, `${blockId}.html`);
this.pendingTypstContent.set(blockId, typstContent);
await import_fs.promises.writeFile(typstFile, typstContent, "utf-8");
await this.renderTypstToHtml(typstFile, htmlFile, container, blockId);
} catch (error) {
container.innerHTML = this.formatError(error, true);
if (this.settings.debugMode) {
console.error("Typst rendering error:", error);
}
}
}
// Synchronously compile a single block (used by prepareExport)
async syncCompileBlock(blockId) {
try {
const typstContent = this.pendingTypstContent.get(blockId);
if (!typstContent)
return;
const typstFile = path.join(this.tempDir, `${blockId}.typ`);
const htmlFile = path.join(this.tempDir, `${blockId}.html`);
(0, import_fs.writeFileSync)(typstFile, typstContent, "utf-8");
const args = [
"compile",
typstFile,
htmlFile,
"--features",
"html",
"--format",
"html"
];
if (this.settings.debugMode) {
console.log("Typst sync compile (prepareExport):", this.settings.typstPath, args.join(" "));
}
const res = (0, import_child_process.spawnSync)(this.settings.typstPath, args, { encoding: "utf-8" });
if (res.error || res.status !== 0) {
const errStr = res.stderr || res.error && res.error.message || `Typst exited ${res.status}`;
throw new Error(errStr);
}
const html = (0, import_fs.readFileSync)(htmlFile, "utf-8");
const bodyMatch = html.match(/<body[^>]*>([\s\S]*)<\/body>/i);
const content = bodyMatch ? bodyMatch[1] : html;
this.renderCache.set(blockId, content);
try {
const el = document.querySelector(`[data-typst-blockid="${blockId}"]`);
if (el instanceof HTMLElement)
el.innerHTML = content;
} catch (e) {
}
} catch (error) {
if (this.settings.debugMode)
console.error("Error in syncCompileBlock:", error);
throw error;
}
}
// Prepare the document for export by sync-compiling all pending Typst blocks
async prepareExport() {
const pending = Array.from(this.pendingTypstContent.keys());
if (pending.length === 0) {
new import_obsidian.Notice("No pending Typst math blocks to prepare");
return;
}
new import_obsidian.Notice(`Preparing ${pending.length} Typst math blocks for export...`);
const prevForce = this.settings.forceSyncCompile;
const prevSyncOnly = this.settings.syncOnlyDuringExport;
this.settings.forceSyncCompile = true;
this.settings.syncOnlyDuringExport = false;
this.exportInProgress = true;
let success = 0;
for (const id of pending) {
try {
await this.syncCompileBlock(id);
success++;
} catch (e) {
console.error("prepareExport: failed to compile block", id, e);
}
}
this.settings.forceSyncCompile = prevForce;
this.settings.syncOnlyDuringExport = prevSyncOnly;
this.exportInProgress = false;
new import_obsidian.Notice(`Prepared ${success}/${pending.length} Typst math blocks for export`);
}
wrapInTypstDocument(mathContent, isBlock = true) {
const sizeConfig = isBlock ? "16pt" : "14pt";
return `
#set text(size: ${sizeConfig})
#show math.equation: html.frame
#show math.equation.where(block: false): box
${mathContent}
`;
}
generateBlockId(source, sourcePath) {
const hash = this.simpleHash(source + sourcePath);
return `block-${hash}`;
}
simpleHash(str) {
let hash = 0;
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = (hash << 5) - hash + char;
hash = hash & hash;
}
return Math.abs(hash).toString(36);
}
/**
* Parse the full Typst-generated HTML and return a cleaned HTML string.
* - Merges paginated pages if present
* - Removes placeholder ellipsis-only nodes
* - Strips scripts/styles that could interfere
*/
parseTypstHtml(html) {
try {
const parser = new DOMParser();
const doc = parser.parseFromString(html, "text/html");
doc.querySelectorAll("script, style").forEach((n) => n.remove());
const pageSelectors = [".typst-page", ".page", ".typst-doc", "#content", "body > div"];
let parts = [];
const typstDoc = doc.querySelector(".typst-doc");
if (typstDoc) {
const children = Array.from(typstDoc.children);
if (children.length > 1) {
for (const c of children)
parts.push(c.innerHTML);
} else {
parts.push(typstDoc.innerHTML);
}
} else {
for (const sel of pageSelectors) {
const nodes = Array.from(doc.querySelectorAll(sel));
if (nodes.length > 1) {
for (const n of nodes)
parts.push(n.innerHTML);
break;
} else if (nodes.length === 1 && parts.length === 0) {
parts.push(nodes[0].innerHTML);
}
}
}
if (parts.length === 0) {
const bodyHtml = doc.body ? doc.body.innerHTML : html;
parts = [bodyHtml];
}
parts = parts.map((p) => {
var _a;
const partDoc = parser.parseFromString(`<div>${p}</div>`, "text/html");
const walker = document.createTreeWalker(partDoc.body, NodeFilter.SHOW_ELEMENT, null);
const toRemove = [];
let node = walker.nextNode();
while (node) {
const el = node;
const txt = (_a = el.textContent) == null ? void 0 : _a.trim();
if (txt && (/^\.{3,}$/.test(txt) || /^…+$/.test(txt) || txt === "\u22EF")) {
toRemove.push(el);
}
node = walker.nextNode();
}
toRemove.forEach((e) => e.remove());
return partDoc.body.innerHTML;
});
const joined = parts.map((p, i) => `<div class="typst-merged-page">${p}</div>`).join('\n<hr class="typst-page-break">\n');
return joined;
} catch (e) {
return html;
}
}
async renderTypstToHtml(typstFile, htmlFile, container, blockId) {
const useSync = this.settings.forceSyncCompile || this.settings.syncOnlyDuringExport && this.exportInProgress;
if (useSync) {
try {
const args = [
"compile",
typstFile,
htmlFile,
"--features",
"html",
"--format",
"html"
];
if (this.settings.debugMode) {
console.log("Running Typst (sync):", this.settings.typstPath, args.join(" "));
}
const res = (0, import_child_process.spawnSync)(this.settings.typstPath, args, { encoding: "utf-8" });
if (res.error) {
const errMsg = res.error.message || String(res.error);
container.innerHTML = this.formatError(errMsg, false);
throw res.error;
}
if (res.status !== 0) {
const errStr = res.stderr || `Typst process exited with code ${res.status}`;
container.innerHTML = this.formatError(errStr, false);
throw new Error(errStr);
}
const html = (0, import_fs.readFileSync)(htmlFile, "utf-8");
const bodyMatch = html.match(/<body[^>]*>([\s\S]*)<\/body>/i);
const content = bodyMatch ? bodyMatch[1] : html;
this.renderCache.set(blockId, content);
container.innerHTML = content;
return Promise.resolve();
} catch (err) {
if (this.settings.debugMode)
console.error("Typst sync render error:", err);
return Promise.reject(err);
}
}
return new Promise((resolve, reject) => {
var _a;
if (this.watchers.has(blockId)) {
(_a = this.watchers.get(blockId)) == null ? void 0 : _a.kill();
this.watchers.delete(blockId);
}
const args = [
"compile",
typstFile,
htmlFile,
"--features",
"html",
"--format",
"html"
];
if (this.settings.debugMode) {
console.log(
"Running Typst command:",
this.settings.typstPath,
args.join(" ")
);
}
const typstProcess = (0, import_child_process.spawn)(this.settings.typstPath, args);
let stderr = "";
typstProcess.stderr.on("data", (data) => {
stderr += data.toString();
});
typstProcess.on("close", async (code) => {
if (code === 0) {
try {
const html = await import_fs.promises.readFile(htmlFile, "utf-8");
const bodyMatch = html.match(/<body[^>]*>([\s\S]*)<\/body>/i);
const content = bodyMatch ? bodyMatch[1] : html;
this.renderCache.set(blockId, content);
container.innerHTML = content;
resolve();
} catch (error) {
container.innerHTML = this.formatError(error, false);
reject(error);
}
} else {
const errorMsg = stderr || `Typst process exited with code ${code}`;
container.innerHTML = this.formatError(errorMsg, false);
reject(new Error(errorMsg));
}
});
typstProcess.on("error", (error) => {
container.innerHTML = this.formatError(error, false);
reject(error);
});
});
}
async renderTypstWithWatch(typstFile, htmlFile, container, blockId) {
return new Promise((resolve, reject) => {
var _a;
if (this.watchers.has(blockId)) {
(_a = this.watchers.get(blockId)) == null ? void 0 : _a.kill();
this.watchers.delete(blockId);
}
const args = [
"watch",
typstFile,
htmlFile,
"--features",
"html",
"--format",
"html"
];
if (this.settings.debugMode) {
console.log(
"Running Typst watch:",
this.settings.typstPath,
args.join(" ")
);
}
const typstProcess = (0, import_child_process.spawn)(this.settings.typstPath, args);
this.watchers.set(blockId, typstProcess);
let stderr = "";
let hasRendered = false;
const checkForUpdates = async () => {
try {
const html = await import_fs.promises.readFile(htmlFile, "utf-8");
const bodyMatch = html.match(/<body[^>]*>([\s\S]*)<\/body>/i);
const content = bodyMatch ? bodyMatch[1] : html;
this.renderCache.set(blockId, content);
container.innerHTML = content;
if (!hasRendered) {
hasRendered = true;
resolve();
}
} catch (error) {
if (this.settings.debugMode) {
console.log("Waiting for Typst to generate output...");
}
}
};
typstProcess.stderr.on("data", (data) => {
stderr += data.toString();
if (stderr.includes("error:")) {
container.innerHTML = this.formatError(stderr, false);
}
});
typstProcess.stdout.on("data", (data) => {
const output = data.toString();
if (this.settings.debugMode) {
console.log("Typst watch output:", output);
}
if (output.includes("written to") || output.includes("compiled")) {
setTimeout(checkForUpdates, 100);
}
});
setTimeout(checkForUpdates, 500);
typstProcess.on("close", (code) => {
this.watchers.delete(blockId);
if (code !== 0 && code !== null && !hasRendered) {
const errorMsg = stderr || `Typst watch exited with code ${code}`;
container.innerHTML = this.formatError(errorMsg, false);
reject(new Error(errorMsg));
}
});
typstProcess.on("error", (error) => {
this.watchers.delete(blockId);
container.innerHTML = this.formatError(error, false);
reject(error);
});
});
}
};
var TypstMathSettingTab = class extends import_obsidian.PluginSettingTab {
constructor(app, plugin) {
super(app, plugin);
this.plugin = plugin;
}
display() {
const { containerEl } = this;
containerEl.empty();
containerEl.createEl("h2", { text: "Typst Math Renderer Settings" });
new import_obsidian.Setting(containerEl).setName("Typst CLI path").setDesc('Path to the Typst executable (e.g., "typst" or full path)').addText(
(text) => text.setPlaceholder("typst").setValue(this.plugin.settings.typstPath).onChange(async (value) => {
this.plugin.settings.typstPath = value;
await this.plugin.saveSettings();
})
);
new import_obsidian.Setting(containerEl).setName("Enable inline math").setDesc("Process inline math blocks ($...$)").addToggle(
(toggle) => toggle.setValue(this.plugin.settings.enableInlineMath).onChange(async (value) => {
this.plugin.settings.enableInlineMath = value;
await this.plugin.saveSettings();
})
);
new import_obsidian.Setting(containerEl).setName("Enable display math").setDesc("Process display math blocks ($$...$$)").addToggle(
(toggle) => toggle.setValue(this.plugin.settings.enableDisplayMath).onChange(async (value) => {
this.plugin.settings.enableDisplayMath = value;
await this.plugin.saveSettings();
})
);
new import_obsidian.Setting(containerEl).setName("Debug mode").setDesc("Enable debug logging in the console").addToggle(
(toggle) => toggle.setValue(this.plugin.settings.debugMode).onChange(async (value) => {
this.plugin.settings.debugMode = value;
await this.plugin.saveSettings();
})
);
new import_obsidian.Setting(containerEl).setName("Show detailed errors").setDesc("Display detailed Typst error messages (useful for debugging)").addToggle(
(toggle) => toggle.setValue(this.plugin.settings.showDetailedErrors).onChange(async (value) => {
this.plugin.settings.showDetailedErrors = value;
await this.plugin.saveSettings();
})
);
new import_obsidian.Setting(containerEl).setName("Use watch mode").setDesc("Enable watch mode for code blocks (auto-recompile on changes) - experimental").addToggle(
(toggle) => toggle.setValue(this.plugin.settings.useWatchMode).onChange(async (value) => {
this.plugin.settings.useWatchMode = value;
await this.plugin.saveSettings();
})
);
new import_obsidian.Setting(containerEl).setName("Force synchronous compile (for exports)").setDesc("Use a synchronous Typst compile which can improve reliability when exporting to PDF. This will block rendering while Typst runs.").addToggle(
(toggle) => toggle.setValue(this.plugin.settings.forceSyncCompile).onChange(async (value) => {
this.plugin.settings.forceSyncCompile = value;
await this.plugin.saveSettings();
new import_obsidian.Notice("Force sync compile setting updated");
})
);
new import_obsidian.Setting(containerEl).setName("Sync only during print/export").setDesc("If enabled, synchronous compilation will only be used during a print/export flow. Disable to always use sync compilation when enabled.").addToggle(
(toggle) => toggle.setValue(this.plugin.settings.syncOnlyDuringExport).onChange(async (value) => {
this.plugin.settings.syncOnlyDuringExport = value;
await this.plugin.saveSettings();
new import_obsidian.Notice("Sync-only-during-export setting updated");
})
);
new import_obsidian.Setting(containerEl).setName("Enable live preview").setDesc("Render Typst math in live preview mode (works automatically via MathJax override)").addToggle(
(toggle) => toggle.setValue(this.plugin.settings.enableLivePreview).onChange(async (value) => {
this.plugin.settings.enableLivePreview = value;
await this.plugin.saveSettings();
new import_obsidian.Notice("Reload Obsidian for this change to take full effect");
})
);
containerEl.createEl("h3", { text: "Usage" });
containerEl.createEl("p", {
text: "Create a code block with ```math or ```typst and write your Typst math syntax inside."
});
containerEl.createEl("pre", {
text: "```math\n$ sum_(i=1)^n i = (n(n+1))/2 $\n```"
});
containerEl.createEl("h3", { text: "Installation" });
containerEl.createEl("p", {
text: "Make sure you have Typst CLI installed. Visit https://github.com/typst/typst for installation instructions."
});
}
};

View File

@@ -0,0 +1,10 @@
{
"id": "obsidian-typst-cli",
"name": "Typst CLI Math Integration",
"version": "1.3.0",
"minAppVersion": "0.15.0",
"description": "Replaces default math blocks with Typst-rendered math blocks using the Typst CLI",
"author": "Your Name",
"authorUrl": "https://yourwebsite.com",
"isDesktopOnly": true
}

View File

@@ -0,0 +1,176 @@
/* Styles for Typst Math Renderer Plugin */
.typst-math-container {
margin: 1em 0;
padding: 0.5em;
background-color: var(--background-primary);
border-radius: 4px;
color: #deb4ae;
}
.typst-math-inline {
display: inline-flex;
align-items: baseline;
margin: 0;
padding: 0 0.2em;
color: #deb4ae;
}
.typst-loading {
color: var(--text-muted);
font-style: italic;
text-align: center;
padding: 1em;
}
.typst-error {
color: #e74c3c;
background-color: transparent;
padding: 0.2em 0.4em;
border-radius: 3px;
font-family: var(--font-interface);
font-size: 0.95em;
font-weight: 500;
white-space: normal;
word-break: break-word;
}
.typst-error-inline {
display: inline;
color: #e74c3c;
font-size: 0.9em;
}
.typst-error-details {
display: block;
margin-top: 0.3em;
padding: 0.5em;
background-color: rgba(231, 76, 60, 0.1);
border-radius: 3px;
font-family: var(--font-monospace);
font-size: 0.85em;
color: var(--text-muted);
white-space: pre-wrap;
word-break: break-word;
}
/* Style the Typst output - inherit theme colors */
.typst-math-container svg,
.typst-math-inline svg,
.typst-math-container .typst-doc,
.typst-math-inline .typst-doc {
max-width: 100%;
height: auto;
display: block;
margin: 0 auto;
color: var(--text-normal);
}
.typst-math-inline svg,
.typst-math-inline .typst-doc {
display: inline-block;
vertical-align: middle;
}
/* Override Typst's default black text in SVG */
.typst-math-container svg *[fill="#000000"],
.typst-math-inline svg *[fill="#000000"],
.typst-math-container svg *[fill="black"],
.typst-math-inline svg *[fill="black"] {
fill: #deb4ae !important;
}
.typst-math-container svg *[stroke="#000000"],
.typst-math-inline svg *[stroke="#000000"],
.typst-math-container svg *[stroke="black"],
.typst-math-inline svg *[stroke="black"] {
stroke: #deb4ae !important;
}
/* Also target when SVGs are directly in code blocks */
.markdown-preview-view .cm-preview-code-block svg *[fill="#000000"],
.markdown-preview-view .cm-preview-code-block svg *[fill="black"] {
fill: #deb4ae !important;
}
.markdown-preview-view .cm-preview-code-block svg *[stroke="#000000"],
.markdown-preview-view .cm-preview-code-block svg *[stroke="black"] {
stroke: #deb4ae !important;
}
/* Ensure the typst-doc class gets color */
.typst-doc {
color: #deb4ae;
}
/* Override any remaining color specifications */
.typst-math-container *,
.typst-math-inline * {
color: inherit;
}
/* Center block math */
.typst-math-container p {
text-align: center;
margin: 0;
color: inherit;
}
/* Ensure proper spacing for math content */
.typst-math-container > * {
margin: 0;
}
/* Print-specific rules: when exporting to PDF, use document/default colors
and avoid any truncation/ellipsis or SVG recoloring that may break prints */
@media print {
/* Restore default text color so printed PDFs look like the document */
.typst-math-container,
.typst-math-inline,
.typst-doc,
.typst-math-container *,
.typst-math-inline * {
color: initial !important;
-webkit-text-fill-color: initial !important;
background: transparent !important;
}
/* Ensure SVGs keep their original fills/strokes in print (do not force plugin color) */
.typst-math-container svg *[fill="#000000"],
.typst-math-inline svg *[fill="#000000"],
.typst-math-container svg *[fill="black"],
.typst-math-inline svg *[fill="black"],
.markdown-preview-view .cm-preview-code-block svg *[fill="#000000"],
.markdown-preview-view .cm-preview-code-block svg *[fill="black"] {
fill: initial !important;
}
.typst-math-container svg *[stroke="#000000"],
.typst-math-inline svg *[stroke="#000000"],
.typst-math-container svg *[stroke="black"],
.typst-math-inline svg *[stroke="black"],
.markdown-preview-view .cm-preview-code-block svg *[stroke="#000000"],
.markdown-preview-view .cm-preview-code-block svg *[stroke="black"] {
stroke: initial !important;
}
/* Avoid text-overflow/ellipsis in printed output */
.typst-math-container,
.typst-math-inline,
.typst-math-container *,
.typst-math-inline * {
white-space: normal !important;
text-overflow: clip !important;
overflow: visible !important;
}
/* Remove any max-width/inline-block constraints that might clip content */
.typst-math-inline svg,
.typst-math-inline .typst-doc,
.typst-math-container svg,
.typst-math-container .typst-doc {
max-width: none !important;
height: auto !important;
display: inline !important;
}
}