diff --git a/src/components/StackedNote.vue b/src/components/StackedNote.vue index 8a76fd3..7cd0394 100644 --- a/src/components/StackedNote.vue +++ b/src/components/StackedNote.vue @@ -13,7 +13,8 @@ import { useFile } from "@/hooks/useFile.hook" import { useGitHubContent } from "@/hooks/useGitHubContent.hook" import { useImages } from "@/hooks/useImages.hook" import { useLinks } from "@/hooks/useLinks.hook" -import { runMermaid, useShikiji } from "@/hooks/useMarkdown.hook" +import { renderCodeFile, runMermaid, useShikiji } from "@/hooks/useMarkdown.hook" +import { getFileLanguage, isMarkdownPath } from "@/utils/fileLanguage" import { useNoteOverlay } from "@/hooks/useNoteOverlay.hook" import { useRouteQueryStackedNotes } from "@/hooks/useRouteQueryStackedNotes.hook" import { useTitleNotes } from "@/hooks/useTitleNotes.hook" @@ -53,6 +54,33 @@ const { getEditedSha } = useFile(sha) const initialRawContent = ref(null) +const isMarkdown = computed(() => (path.value ? isMarkdownPath(path.value) : true)) +const displayedContent = ref("") + +watch( + [rawContent, isMarkdown, path], + async ([raw, isMd, p]) => { + if (!raw) { + displayedContent.value = "" + return + } + if (isMd) { + displayedContent.value = content.value + return + } + const lang = p ? getFileLanguage(p) : null + const filename = p?.split("/").pop() + const result = await renderCodeFile(raw, lang, filename) + if (rawContent.value === raw) { + displayedContent.value = result + } + }, + { immediate: true } +) + +watch(content, (c) => { + if (isMarkdown.value) displayedContent.value = c +}) const className = computed(() => `stacked-note-${props.index}`) const { listenToClick } = useLinks(className.value, sha) const titleClassName = computed(() => `title-${className.value}`) @@ -92,7 +120,7 @@ watch([content, mode], () => { runMermaid(`.note-${sha.value} .mermaid`) } - if (rawContent.value.includes("```")) { + if (isMarkdown.value && rawContent.value.includes("```")) { useShikiji() } }) @@ -157,6 +185,7 @@ watch(mode, async (newMode) => {
-
+
-
+
diff --git a/src/hooks/useMarkdown.hook.ts b/src/hooks/useMarkdown.hook.ts index dba0716..75e98fd 100644 --- a/src/hooks/useMarkdown.hook.ts +++ b/src/hooks/useMarkdown.hook.ts @@ -11,9 +11,11 @@ import markdownItCheckbox from "markdown-it-checkbox" import MarkdownItGitHubAlerts from "markdown-it-github-alerts" import markdownItIframe from "markdown-it-iframe" import Shikiji from "markdown-it-shikiji" +import type { LanguageRegistration } from "shikiji-core" import mermaid from "mermaid" import { Ref, toValue } from "vue" +import alloyGrammar from "@/utils/alloy.tmLanguage.json" import { decodeBase64ToUTF8 } from "@/utils/decodeBase64ToUTF8" import { html5Media } from "@/utils/markdown/markdown-html5-media" import { markdownItTablerIcons } from "@/utils/markdown/markdown-it-tabler-icons" @@ -116,7 +118,12 @@ export const useShikiji = async () => { "mermaid", "html", "css", - "json" + "json", + { + ...alloyGrammar, + name: "alloy", + aliases: ["als"] + } as unknown as LanguageRegistration ] }) ) @@ -157,6 +164,19 @@ const renderMarkdown = (content: string, env?: Record) => { return env ? md.render(content, env) : md.render(content) } +export const renderCodeFile = async ( + rawContent: string, + lang: string | null, + filename?: string +): Promise => { + await useShikiji() + const heading = filename ? `# ${filename}\n\n` : "" + if (lang !== null) { + return renderMarkdown(`${heading}\`\`\`\`${lang}\n${rawContent}\n\`\`\`\``) + } + return `${renderMarkdown(heading)}
${md.utils.escapeHtml(rawContent)}
` +} + export const markdownBuilder = (defaultPrefix?: Ref | string) => { const getRawContent = (content: string) => decodeBase64ToUTF8(content) const renderFromUTF8 = (content: string, prefix?: string) => { diff --git a/src/utils/alloy.tmLanguage.json b/src/utils/alloy.tmLanguage.json new file mode 100644 index 0000000..6f6f33d --- /dev/null +++ b/src/utils/alloy.tmLanguage.json @@ -0,0 +1,244 @@ +{ + "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json", + "name": "Alloy", + "scopeName": "source.als", + "patterns": [ + { "include": "#comments" }, + { "include": "#declaration" }, + { "include": "#expression" }, + { "include": "#built-in" }, + { "include": "#keywords" }, + { "include": "#digit" } + ], + "repository": { + "comments": { + "patterns": [ + { "begin": "/\\*", "end": "\\*/", "name": "comment.block.alloy" }, + { "begin": "//", "end": "\n", "name": "comment.line.double-slash" }, + { "begin": "--", "end": "\n", "name": "comment.line.double-dash" } + ] + }, + "keywords": { + "patterns": [ + { "include": "#define" }, + { "include": "#modifier" }, + { "include": "#operator" }, + { "include": "#control" }, + { "include": "#variable" } + ], + "repository": { + "define": { + "patterns": [ + { "match": "\\b(sig)\\b", "name": "keyword.language.sig.alloy" }, + { "match": "\\b(fact)\\b", "name": "keyword.language.fact.alloy" }, + { "match": "\\b(pred)\\b", "name": "keyword.language.pred.alloy" }, + { "match": "\\b(fun)\\b", "name": "keyword.language.fun.alloy" }, + { "match": "\\b(module)\\b", "name": "keyword.language.module.alloy" }, + { "match": "\\b(extends)\\b", "name": "keyword.language.extends.alloy" }, + { "match": ":", "name": "keyword.other.colon.alloy" }, + { "match": "\\b(check)\\b", "name": "keyword.language.check.alloy" }, + { "match": "\\b(assert)\\b", "name": "keyword.language.assert.alloy" }, + { "match": "\\b(run)\\b", "name": "keyword.language.run.alloy" }, + { "match": "\\b(open)\\b", "name": "keyword.other.open.alloy" }, + { "match": "\\b(as)\\b", "name": "keyword.other.as.alloy" }, + { "match": "\\b(in)\\b", "name": "keyword.other.in.alloy" } + ] + }, + "modifier": { + "patterns": [ + { "match": "\\b(var)\\b", "name": "keyword.modifier.var.alloy" }, + { "match": "\\b(private)\\b", "name": "keyword.modifier.private.alloy" }, + { "match": "\\b(abstract)\\b", "name": "keyword.modifier.abstract.alloy" }, + { "match": "\\b(all|disj|lone|no|one|set|seq|some|sum|univ|none)\\b", "name": "keyword.modifier.set.alloy" } + ] + }, + "operator": { + "patterns": [ + { "include": "#temporal" }, + { "include": "#unary" }, + { "include": "#binary" } + ], + "repository": { + "temporal": { + "patterns": [ + { + "match": "\\b(always|eventually|after|before|historically|once|prev)\\b", + "name": "keyword.operator.temporal.unary.alloy" + }, + { + "match": "\\b(until|releases|since|triggered)\\b", + "name": "keyword.operator.temporal.binary.alloy" + } + ] + }, + "unary": { + "patterns": [ + { "match": "!|#|~|\\*|\\^|(\\b(not)\\b)", "name": "keyword.operator.unary.alloy" } + ] + }, + "binary": { + "patterns": [ + { "match": "(?:\\|\\|)|&&|<=>|=>|&|\\+|-|\\+\\+|<:|:>|\\.|=|->", "name": "keyword.operator.binary.alloy" }, + { "match": "\\b(and|or|iff|implies|else|in)\\b", "name": "keyword.operator.binary.alloy" }, + { "match": "=|<|>|=<|>=", "name": "keyword.operator.binary.alloy" }, + { "match": ",", "name": "keyword.other.comma.alloy" }, + { "match": "\\|", "name": "keyword.other.split.alloy" } + ] + } + } + }, + "variable": { + "patterns": [ + { "match": "\\b(let)\\b", "name": "keyword.language.let.alloy" }, + { "match": "\\b(this)\\b", "name": "keyword.language.this.alloy" } + ] + }, + "control": { + "patterns": [ + { "match": "\\b(for)\\b", "name": "keyword.control.for.alloy" }, + { "match": "\\b(but)\\b", "name": "keyword.control.but.alloy" }, + { "match": "\\b(exactly)\\b", "name": "keyword.control.exactly.alloy" }, + { "match": "\\b(expect)\\b", "name": "keyword.control.expect.alloy" }, + { "match": "\\b(steps)\\b", "name": "keyword.control.steps.alloy" } + ] + } + } + }, + "declaration": { + "patterns": [ + { "include": "#module" }, + { "include": "#predict" }, + { "include": "#signature" }, + { "include": "#fact" }, + { "include": "#fun" } + ], + "repository": { + "module": { + "patterns": [ + { + "match": "(module)\\b\\s*((?:\\w|'|_|\\d|/)+)", + "captures": { + "1": { "name": "keyword.language.module.alloy" }, + "2": { "name": "support.class.module.alloy" } + } + } + ] + }, + "predict": { + "patterns": [ + { + "match": "(pred)\\b\\s*((?:\\w|'|_|\\d|/)+)", + "captures": { + "1": { "name": "keyword.language.pred.alloy" }, + "2": { "name": "entity.name.function.pred.alloy" } + } + } + ] + }, + "signature": { + "patterns": [ + { + "begin": "(abstract)?\\s*(lone|some|one)?\\s*(var)?\\s*(sig)\\b\\s*", + "end": "(?=\\{)", + "beginCaptures": { + "1": { "name": "keyword.modifier.abstract.alloy" }, + "2": { "name": "keyword.modifier.set.alloy" }, + "3": { "name": "keyword.modifier.var.alloy" }, + "4": { "name": "keyword.language.sig.alloy" } + }, + "patterns": [ + { + "begin": "(extends)", + "end": "(?=\\{)", + "beginCaptures": { "1": { "name": "keyword.language.extends.alloy" } }, + "patterns": [ + { "match": "(?:\\w|'|_|\\d|/)+", "name": "entity.other.inherited-class.alloy" } + ] + }, + { + "begin": "(in)", + "end": "(?=\\{)", + "beginCaptures": { "1": { "name": "keyword.other.in.alloy" } }, + "patterns": [ + { "match": "(?:\\w|'|_|\\d|/)+", "name": "entity.other.inherited-class.alloy" }, + { "match": "\\+", "name": "keyword.operator.binary.alloy" } + ] + }, + { "match": "(?:\\w|'|_|\\d|/)+", "name": "entity.name.type.signature.alloy" }, + { "match": ",", "name": "keyword.other.comma.alloy" } + ] + } + ] + }, + "fact": { + "patterns": [ + { + "match": "(fact)\\b\\s*((?:\\w|'|_|\\d|/)+)", + "captures": { + "1": { "name": "keyword.language.fact.alloy" }, + "2": { "name": "entity.name.function.fact.alloy" } + } + } + ] + }, + "fun": { + "patterns": [ + { + "match": "(fun)\\b\\s*((?:\\w|'|_|\\d|/)+)", + "captures": { + "1": { "name": "keyword.language.fun.alloy" }, + "2": { "name": "entity.name.function.fun.alloy" } + } + } + ] + } + } + }, + "expression": { + "patterns": [ + { + "match": "(check)\\b\\s*((?:\\w|'|_|\\d|/)+)", + "captures": { + "1": { "name": "keyword.language.check.alloy" }, + "2": { "name": "entity.name.function.check.alloy" } + } + }, + { + "match": "(assert)\\b\\s*((?:\\w|'|_|\\d|/)+)", + "captures": { + "1": { "name": "keyword.language.assert.alloy" }, + "2": { "name": "entity.name.function.check.alloy" } + } + } + ] + }, + "digit": { + "patterns": [ + { "match": "\\b(\\d+)\\b", "name": "constant.numeric.alloy" } + ] + }, + "built-in": { + "patterns": [ + { + "match": "\\b(plus|minus|mul|div|rem|sum)\\[", + "captures": { "1": { "name": "support.function.numeric.alloy" } } + }, + { + "match": "\\b(open)\\b\\s*((?:\\w|'|_|\\d|/)+)\\[", + "captures": { + "1": { "name": "keyword.other.open.alloy" }, + "2": { "name": "support.class.module.alloy" } + } + }, + { + "match": "(/(?:\\w|'|_|\\d|/)+)", + "captures": { "1": { "name": "support.function.order.alloy" } } + }, + { + "match": "((?:\\w|'|_|\\d)+)\\s*\\[", + "captures": { "1": { "name": "support.function.order.alloy" } } + } + ] + } + } +} diff --git a/src/utils/fileLanguage.ts b/src/utils/fileLanguage.ts new file mode 100644 index 0000000..8d803d8 --- /dev/null +++ b/src/utils/fileLanguage.ts @@ -0,0 +1,31 @@ +const EXT_TO_LANG: Record = { + sh: "bash", + bash: "bash", + js: "javascript", + mjs: "javascript", + cjs: "javascript", + ts: "typescript", + mts: "typescript", + cts: "typescript", + md: "markdown", + mdx: "markdown", + html: "html", + htm: "html", + css: "css", + scss: "css", + json: "json", + jsonc: "json", + als: "alloy" +} + +const MARKDOWN_EXTS = new Set(["md", "mdx"]) + +export function isMarkdownPath(path: string): boolean { + const ext = path.split(".").pop()?.toLowerCase() ?? "" + return MARKDOWN_EXTS.has(ext) +} + +export function getFileLanguage(path: string): string | null { + const ext = path.split(".").pop()?.toLowerCase() ?? "" + return EXT_TO_LANG[ext] ?? null +}