Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 22 additions & 4 deletions esbuild.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import process from "process";
import builtins from 'builtin-modules'
import fs from "fs";
import path from "path";
import sveltePlugin from 'esbuild-svelte';

const banner =
`/*
Expand Down Expand Up @@ -69,11 +70,14 @@ let copyFile = {
},
}

esbuild.build({
const options = {
banner: {
js: banner,
},
plugins: [wasmPlugin, copyFile],
plugins: [
wasmPlugin,
copyFile,
sveltePlugin()],
entryPoints: ['src/main.ts'],
bundle: true,
external: [
Expand All @@ -97,11 +101,25 @@ esbuild.build({
...builtins
],
format: 'cjs',
watch: !prod,
target: 'es2016',
logLevel: "info",
sourcemap: prod ? false : 'inline',
treeShaking: true,
outfile: path.join(pluginDir, 'main.js'),
}).catch(() => process.exit(1))
}

try {
if (prod) {
// production build
await esbuild.build(options);
} else {
// development build with watch
const ctx = await esbuild.context(options);
await ctx.watch();
console.log("👀 Watching for changes...");
}
} catch (err) {
console.error(err);
process.exit(1);
}

32 changes: 18 additions & 14 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,25 @@
"author": "",
"license": "MIT",
"devDependencies": {
"@codemirror/lang-python": "^6.0.0",
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0",
"@eslint/js": "^9.31.0",
"@types/node": "^16.11.6",
"@typescript-eslint/eslint-plugin": "^8.37.0",
"@typescript-eslint/parser": "^8.37.0",
"builtin-modules": "3.3.0",
"codemirror": "^6.0.0",
"esbuild": "^0.14.47",
"eslint": "^9.31.0",
"@codemirror/lang-python": "^6.2.1",
"@codemirror/state": "^6.5.2",
"@codemirror/view": "^6.38.1",
"@eslint/js": "^9.33.0",
"@tsconfig/svelte": "^5.0.4",
"@types/node": "^24.3.0",
"@typescript-eslint/eslint-plugin": "^8.39.1",
"@typescript-eslint/parser": "^8.39.1",
"builtin-modules": "5.0.0",
"codemirror": "^6.0.2",
"esbuild": "^0.25.9",
"esbuild-plugin-wasm": "^1.1.0",
"esbuild-svelte": "^0.9.3",
"eslint": "^9.33.0",
"globals": "^16.3.0",
"obsidian": "latest",
"tslib": "2.4.0",
"typescript": "5.8.3",
"typescript-eslint": "^8.37.0"
"svelte": "^5.38.1",
"tslib": "2.8.1",
"typescript": "5.9.2",
"typescript-eslint": "^8.39.1"
}
}
13 changes: 13 additions & 0 deletions src/components/ObsidianIcon.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<script lang="ts">
import { onMount } from 'svelte';
import { setIcon } from 'obsidian';

export let icon: string;
export let className: string = '';

let el: HTMLElement;

$: if (el && icon) setIcon(el, icon);
</script>

<span bind:this={el} class={className}></span>
131 changes: 131 additions & 0 deletions src/components/RuleEditor.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
<script lang="ts">
import { python } from '@codemirror/lang-python';
import { lineNumbers, EditorView, ViewUpdate } from '@codemirror/view';
import { Annotation, EditorState, Extension } from '@codemirror/state';
import { syntaxHighlighting, HighlightStyle } from '@codemirror/language';
import { config } from 'src/settings';
import { tags as t } from "@lezer/highlight";
import { log } from 'src/utils';
import { onMount } from 'svelte';
import ObsidianIcon from './ObsidianIcon.svelte';
import { DEFAULT_RULES } from 'src/const';

// Props
interface RuleEditorProps {
initialText: string;
checkRules: (text: string) => string[];
onValidChange: (newText: string) => void;
isValid: boolean;
}

let { initialText, checkRules, onValidChange, isValid = $bindable() } = $props();

// Editor
let editorContainer: HTMLDivElement;
let editor: EditorView;
let editorText = $state(initialText);

const obsidianTheme = EditorView.theme({
"&": {
color: config.foreground,
backgroundColor: config.background,
},
".cm-content": { caretColor: config.cursor },
"&.cm-focused .cm-cursor": { borderLeftColor: config.cursor },
"&.cm-focused .cm-selectionBackground, .cm-selectionBackground, & ::selection":
{ backgroundColor: config.selection },
".cm-activeLine": { backgroundColor: config.activeLine },
".cm-activeLineGutter": { backgroundColor: config.background },
".cm-selectionMatch": { backgroundColor: config.selection },
".cm-gutters": {
backgroundColor: config.background,
color: config.comment,
borderRight: "1px solid var(--background-modifier-border)",
},
".cm-lineNumbers, .cm-gutterElement": { color: "inherit" },
});

const obsidianHighlightStyle = HighlightStyle.define([
{
tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)],
color: config.string,
},
{
tag: [t.color, t.constant(t.name), t.standard(t.name)],
color: config.constant,
},
{ tag: t.comment, color: config.comment },
{ tag: t.invalid, color: config.invalid },
]);

const extensions: Extension[] = [
obsidianTheme,
lineNumbers(),
EditorView.lineWrapping,
python(), // it is better to write a language support for rules
syntaxHighlighting(obsidianHighlightStyle),
EditorView.updateListener.of((v) => handleEditorUpdate(v)),
];

async function handleEditorUpdate(v: ViewUpdate) {
if (v.docChanged) {
const newText = v.state.doc.toString();
editorText = newText;

validateRuleText(editorText);
}
}

export function handleEditorReset(newText: string) {
editorText = newText;
isValid = true;
validityText = "";

editor.dispatch({
changes: { from: 0, to: editor.state.doc.length, insert: editorText }
});
}

// Valiation
// isValid is defined from props
let validityText = $state("");

async function validateRuleText(newText: string) {
const errs = await checkRules(newText);
console.log("Validation errors:", errs)
if (errs.length > 0) {
validityText = errs.join('\n');
isValid = false;
} else {
validityText = "Saved";
isValid = true;
onValidChange(editorText)
}
}

onMount(() => {
const state = EditorState.create({
doc: editorText,
extensions: extensions
});

editor = new EditorView({
state,
parent: editorContainer
});
});
</script>

<div bind:this={editorContainer} class="rules-editor-wrapper"></div>

<div class="rules-footer">
<div class="rules-editor-validity">
<ObsidianIcon className="rules-editor-validity-indicator {isValid ? "valid" : "invalid" }" icon={isValid ? "checkmark" : "x"} />
<span class="rules-editor-validity-txt" >{validityText}</span>
</div>
<div class="rules-editor-buttons">
<button aria-label="Reset to default rules" onclick={() => handleEditorReset(DEFAULT_RULES)}>
<ObsidianIcon icon="repeat" className="" />
</button>
</div>
</div>
Loading