diff --git a/package-lock.json b/package-lock.json index adf138e..ab8db26 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "fachwerk", - "version": "0.0.27", + "version": "0.0.28-beta.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "fachwerk", - "version": "0.0.20", + "version": "0.0.28-beta.2", "license": "MIT", "dependencies": { "d3-shape": "^3.1.0", @@ -20,6 +20,7 @@ "devDependencies": { "@babel/types": "^7.17.0", "@iconify-json/tabler": "^1.1.3", + "@slidev/parser": "^0.31.2", "@tailwindcss/typography": "^0.5.2", "@types/d3-shape": "^3.0.2", "@types/katex": "^0.11.1", @@ -478,6 +479,52 @@ "node": ">= 8.0.0" } }, + "node_modules/@slidev/parser": { + "version": "0.31.2", + "resolved": "https://registry.npmjs.org/@slidev/parser/-/parser-0.31.2.tgz", + "integrity": "sha512-NklmVD1dpUE1gqqV7SJ9DT+OeO4xL2iYREATo4TCvYrOWzAfwTHPFqFBbraqLK/m7JTwBOOWieRLj5JMIv07rA==", + "dev": true, + "dependencies": { + "@slidev/types": "0.31.2", + "js-yaml": "^4.1.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@slidev/parser/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/@slidev/parser/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@slidev/types": { + "version": "0.31.2", + "resolved": "https://registry.npmjs.org/@slidev/types/-/types-0.31.2.tgz", + "integrity": "sha512-PUmq0QyCiH0YEkOGl8iik64VATKU5PoSknCN/wUb52sOXmG/uSlGHyxeMHlarNQVJbZh3ih1T8hDoI2gj3ZIOg==", + "dev": true, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/@tailwindcss/typography": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.2.tgz", @@ -4668,6 +4715,39 @@ "picomatch": "^2.2.2" } }, + "@slidev/parser": { + "version": "0.31.2", + "resolved": "https://registry.npmjs.org/@slidev/parser/-/parser-0.31.2.tgz", + "integrity": "sha512-NklmVD1dpUE1gqqV7SJ9DT+OeO4xL2iYREATo4TCvYrOWzAfwTHPFqFBbraqLK/m7JTwBOOWieRLj5JMIv07rA==", + "dev": true, + "requires": { + "@slidev/types": "0.31.2", + "js-yaml": "^4.1.0" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + } + } + }, + "@slidev/types": { + "version": "0.31.2", + "resolved": "https://registry.npmjs.org/@slidev/types/-/types-0.31.2.tgz", + "integrity": "sha512-PUmq0QyCiH0YEkOGl8iik64VATKU5PoSknCN/wUb52sOXmG/uSlGHyxeMHlarNQVJbZh3ih1T8hDoI2gj3ZIOg==", + "dev": true + }, "@tailwindcss/typography": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.2.tgz", diff --git a/package.json b/package.json index 1c93533..a7db056 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fachwerk", - "version": "0.0.27", + "version": "0.0.28-beta.2", "engines": { "node": "^14.13.1 || >=16.0.0" }, @@ -61,6 +61,7 @@ "devDependencies": { "@babel/types": "^7.17.0", "@iconify-json/tabler": "^1.1.3", + "@slidev/parser": "^0.31.2", "@tailwindcss/typography": "^0.5.2", "@types/d3-shape": "^3.0.2", "@types/katex": "^0.11.1", diff --git a/src/internal/markdown.ts b/src/internal/markdown.ts index c1702f8..4a6a999 100644 --- a/src/internal/markdown.ts +++ b/src/internal/markdown.ts @@ -4,11 +4,12 @@ import MarkdownItExternalLinks from "markdown-it-external-links"; export function MarkdownItFachwerk(md: MarkdownIt) { md.renderer.rules.code_inline = function () { const [tokens, idx, _options, _env, _slf] = arguments; - return tokens[idx].content; + return `${md.utils.escapeHtml(tokens[idx].content)}`; }; md.renderer.rules.code_block = function () { const [tokens, idx, _options, _env, _slf] = arguments; - return tokens[idx].content; + console.log(md.utils.escapeHtml(tokens[idx].content)); + return `
${md.utils.escapeHtml(tokens[idx].content)}
`; }; return md; } diff --git a/src/slides.ts b/src/slides.ts new file mode 100644 index 0000000..dac5a97 --- /dev/null +++ b/src/slides.ts @@ -0,0 +1,291 @@ +import { + ref, + h, + computed, + onMounted, + watchEffect, + getCurrentInstance, + Ref, +} from "vue"; +import { data } from "./lib.esm"; +import { compileTemplate, compileMarkdown } from "./internal"; +import { parse } from "@slidev/parser"; +import { useStorage, useMagicKeys } from "@vueuse/core"; + +const Icon = { + props: ["id"], + setup(props: any) { + const icon = ref(""); + const [collection, name] = props.id.split(":"); + fetch(`https://unpkg.com/@iconify/json/json/${collection}.json`) + .then((res) => res.json()) + .then(({ icons }) => { + icon.value = icons[name].body; + }); + return { icon }; + }, + template: ``, +}; + +const Compiler = { + props: ["code"], + setup(props: any) { + const Output = computed(() => { + return { + setup() { + const app = getCurrentInstance()!.appContext.app; + return { ...app.config.globalProperties }; + }, + render: compileTemplate(compileMarkdown(props.code)).code, + }; + }); + return () => h(Output.value); + }, +}; + +const Info = { + inheritAttrs: false, + props: { icon: { default: "bx:info-circle" } }, + template: ` +
+ +
+
+ `, +}; + +const fahrenheit = ref(-460); +const celsius = computed(() => Math.floor((5 / 9) * (fahrenheit.value - 32))); +const resetFahrenheit = () => { + fahrenheit.value = -460; +}; + +function parseSlides(code: any) { + let global = {}; + try { + return parse(code).slides.map((s) => { + if (s.frontmatter?.data) { + Object.entries(s.frontmatter.data).forEach(([key, value]) => { + data[key] = value; + }); + } + if (s.frontmatter?.global) { + global = { ...global, ...s.frontmatter.global }; + } + s.frontmatter.global = global; + s.content = compileMarkdown(s.content); + return s; + }); + } catch (e) { + return []; + } +} + +export function useEditor() { + const input = ref(null); + onMounted(() => { + if (input.value) { + input.value.addEventListener("keydown", (e) => { + if (e.key === "Tab" && input.value) { + e.preventDefault(); + input.value.setRangeText( + Array.from({ length: 2 }).fill(" ").join(""), + input.value.selectionStart, + input.value.selectionStart, + "end" + ); + } + }); + } + }); + return input; +} + +export function useLoader(key: string, loader: () => Promise) { + const saved = useStorage(key, ""); + const current = ref(""); + const save = () => (saved.value = current.value); + const reset = () => + loader().then((original) => { + saved.value = ""; + current.value = original; + }); + loader().then((original) => { + if (saved.value && original !== saved.value) { + current.value = saved.value; + } else { + current.value = original; + } + }); + return { current, saved, save, reset }; +} + +export function useSlides(key: string, content: Ref) { + const slides = computed(() => parseSlides(content.value)); + const slideIndex = useStorage(key, 0); + watchEffect(() => { + if (slideIndex.value < 0) { + slideIndex.value = 0; + } + if (slides.value.length > 1 && slideIndex.value > slides.value.length - 1) { + slideIndex.value = slides.value.length - 1; + } + }); + const next = () => { + if (slideIndex.value) { + slideIndex.value++; + } + }; + const prev = () => { + if (slideIndex.value) { + slideIndex.value--; + } + }; + const go = (title: string) => { + const index = slides.value.findIndex((s) => s.frontmatter?.title === title); + if (index > -1) { + slideIndex.value = index; + } + }; + const { shift, left, right } = useMagicKeys(); + watchEffect(() => { + if (left.value && shift.value) prev(); + if (right.value && shift.value) next(); + }); + return { slides, slideIndex, prev, next, go }; +} + +export const App = { + components: { Compiler, Icon }, + setup() { + const app = getCurrentInstance()!.appContext.app; + + const loader = + app.config.globalProperties.loader || + (() => fetch("./slides.md").then((res) => res.text())); + + const { current, save, reset } = useLoader("slides_code", loader); + + const editor = useEditor(); + + const { slides, slideIndex, prev, next, go } = useSlides( + "slides_index", + current + ); + + const edit = useStorage("slides_edit", false); + const menu = ref(false); + app.component("Icon", Icon); + app.component("Info", Info); + + app.config.globalProperties = { + ...app.config.globalProperties, + prev, + next, + go, + fahrenheit, + celsius, + resetFahrenheit, + }; + + return { + editor, + current, + save, + reset, + slides, + slideIndex, + next, + prev, + edit, + menu, + }; + }, + template: ` +
+
+