You've already forked obsidian-visualiser
Update DB schema to include an ID and split overview and content. Progressing on ContentEditor with the ID fixing many issues. Adding modal and sync features.
This commit is contained in:
@@ -29,12 +29,6 @@ type EdgeEditor = InstanceType<typeof CanvasEdgeEditor>;
|
||||
|
||||
const cancelEvent = (e: Event) => e.preventDefault();
|
||||
const stopPropagation = (e: Event) => e.stopImmediatePropagation();
|
||||
function getID(length: number)
|
||||
{
|
||||
for (var id = [], i = 0; i < length; i++)
|
||||
id.push((16 * Math.random() | 0).toString(16));
|
||||
return id.join("");
|
||||
}
|
||||
function center(touches: TouchList): Position
|
||||
{
|
||||
const pos = { x: 0, y: 0 };
|
||||
@@ -58,7 +52,7 @@ function distance(touches: TouchList): number
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Icon } from '@iconify/vue/dist/iconify.js';
|
||||
import { clamp } from '#shared/general.util';
|
||||
import { clamp, getID, ID_SIZE } from '#shared/general.util';
|
||||
import type { CanvasContent, CanvasEdge, CanvasNode } from '~/types/canvas';
|
||||
|
||||
const canvas = defineModel<CanvasContent>({ required: true });
|
||||
@@ -342,11 +336,10 @@ function edit(element: Element)
|
||||
}
|
||||
function createNode(e: MouseEvent)
|
||||
{
|
||||
const centerX = (viewportSize.right.value - viewportSize.left.value) / 2 + viewportSize.left.value, centerY = (viewportSize.bottom.value - viewportSize.top.value) / 2 + viewportSize.top.value;
|
||||
const width = 250, height = 100;
|
||||
const x = e.layerX / zoom.value - dispX.value - width / 2;
|
||||
const y = e.layerY / zoom.value - dispY.value - height / 2;
|
||||
const node: CanvasNode = { id: getID(16), x, y, width, height, type: 'text' };
|
||||
const node: CanvasNode = { id: getID(ID_SIZE), x, y, width, height, type: 'text' };
|
||||
|
||||
if(!canvas.value.nodes)
|
||||
canvas.value.nodes = [node];
|
||||
@@ -412,7 +405,7 @@ function dragEndEdgeTo(e: MouseEvent): void
|
||||
if(fakeEdge.value.snapped)
|
||||
{
|
||||
const node = canvas.value.nodes!.find(e => e.id === fakeEdge.value.drag!.id)!;
|
||||
const edge: CanvasEdge = { fromNode: fakeEdge.value.drag!.id, fromSide: fakeEdge.value.fromSide!, toNode: fakeEdge.value.snapped.node, toSide: fakeEdge.value.snapped.side, id: getID(16), color: node.color };
|
||||
const edge: CanvasEdge = { fromNode: fakeEdge.value.drag!.id, fromSide: fakeEdge.value.fromSide!, toNode: fakeEdge.value.snapped.node, toSide: fakeEdge.value.snapped.side, id: getID(ID_SIZE), color: node.color };
|
||||
canvas.value.edges?.push(edge);
|
||||
|
||||
addAction('create', [{ from: undefined, to: edge, element: { id: edge.id, type: 'edge' } }]);
|
||||
|
||||
@@ -1,276 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { crosshairCursor, Decoration, dropCursor, EditorView, keymap, ViewPlugin, ViewUpdate, WidgetType, type DecorationSet } from '@codemirror/view';
|
||||
import { Annotation, EditorState, RangeValue, SelectionRange, type Range } from '@codemirror/state';
|
||||
import { defaultKeymap, history, historyKeymap } from '@codemirror/commands';
|
||||
import { bracketMatching, defaultHighlightStyle, foldKeymap, HighlightStyle, indentOnInput, syntaxHighlighting, syntaxTree } from '@codemirror/language';
|
||||
import { search, searchKeymap } from '@codemirror/search';
|
||||
import { closeBrackets, closeBracketsKeymap, completionKeymap } from '@codemirror/autocomplete';
|
||||
import { lintKeymap } from '@codemirror/lint';
|
||||
import { markdown, markdownLanguage } from '@codemirror/lang-markdown';
|
||||
import { IterMode, Tree } from '@lezer/common';
|
||||
import { tags } from '@lezer/highlight';
|
||||
const External = Annotation.define<boolean>();
|
||||
const Hidden = Decoration.mark({ class: 'hidden' });
|
||||
const Bullet = Decoration.mark({ class: '*:hidden before:absolute before:top-2 before:left-0 before:inline-block before:w-2 before:h-2 before:rounded before:bg-light-40 dark:before:bg-dark-40 relative ps-4' });
|
||||
const Blockquote = Decoration.line({ class: '*:hidden before:block !ps-4 relative before:absolute before:top-0 before:bottom-0 before:left-0 before:w-1 before:bg-none before:bg-light-30 dark:before:bg-dark-30' });
|
||||
|
||||
const TagTag = tags.special(tags.content);
|
||||
|
||||
const intersects = (a: {
|
||||
from: number;
|
||||
to: number;
|
||||
}, b: {
|
||||
from: number;
|
||||
to: number;
|
||||
}) => !(a.to < b.from || b.to < a.from);
|
||||
|
||||
const highlight = HighlightStyle.define([
|
||||
{ tag: tags.heading1, class: 'text-5xl pt-4 pb-2 after:hidden' },
|
||||
{ tag: tags.heading2, class: 'text-4xl pt-4 pb-2 ps-1 leading-loose after:hidden' },
|
||||
{ tag: tags.heading3, class: 'text-2xl font-bold pt-1 after:hidden' },
|
||||
{ tag: tags.heading4, class: 'text-xl font-semibold pt-1 after:hidden variant-cap' },
|
||||
{ tag: tags.meta, color: "#404740" },
|
||||
{ tag: tags.link, textDecoration: "underline" },
|
||||
{ tag: tags.heading, textDecoration: "underline", fontWeight: "bold" },
|
||||
{ tag: tags.emphasis, fontStyle: "italic" },
|
||||
{ tag: tags.strong, fontWeight: "bold" },
|
||||
{ tag: tags.strikethrough, textDecoration: "line-through" },
|
||||
{ tag: tags.keyword, color: "#708" },
|
||||
{ tag: TagTag, class: 'cursor-default bg-accent-blue bg-opacity-10 hover:bg-opacity-20 text-accent-blue text-sm px-1 ms-1 pb-0.5 rounded-full rounded-se-none border border-accent-blue border-opacity-30' }
|
||||
]);
|
||||
|
||||
class Decorator
|
||||
{
|
||||
static hiddenNodes: string[] = [
|
||||
'HardBreak',
|
||||
'LinkMark',
|
||||
'EmphasisMark',
|
||||
'CodeMark',
|
||||
'CodeInfo',
|
||||
'URL',
|
||||
]
|
||||
decorations: DecorationSet;
|
||||
constructor(view: EditorView)
|
||||
{
|
||||
this.decorations = Decoration.set(this.iterate(syntaxTree(view.state), view.visibleRanges, []), true);
|
||||
}
|
||||
update(update: ViewUpdate)
|
||||
{
|
||||
if(!update.docChanged && !update.viewportChanged && !update.selectionSet)
|
||||
return;
|
||||
|
||||
this.decorations = this.decorations.update({
|
||||
filter: (f, t, v) => false,
|
||||
add: this.iterate(syntaxTree(update.state), update.view.visibleRanges, update.state.selection.ranges),
|
||||
sort: true,
|
||||
});
|
||||
}
|
||||
iterate(tree: Tree, visible: readonly {
|
||||
from: number;
|
||||
to: number;
|
||||
}[], selection: readonly SelectionRange[]): Range<Decoration>[]
|
||||
{
|
||||
const decorations: Range<Decoration>[] = [];
|
||||
|
||||
for (let { from, to } of visible) {
|
||||
tree.iterate({
|
||||
from, to, mode: IterMode.IgnoreMounts,
|
||||
enter: node => {
|
||||
if(node.node.parent && selection.some(e => intersects(e, node.node.parent!)))
|
||||
return true;
|
||||
|
||||
else if(node.name === 'HeaderMark')
|
||||
decorations.push(Hidden.range(node.from, node.to + 1));
|
||||
|
||||
else if(Decorator.hiddenNodes.includes(node.name))
|
||||
decorations.push(Hidden.range(node.from, node.to));
|
||||
|
||||
else if(node.matchContext(['BulletList', 'ListItem']) && node.name === 'ListMark')
|
||||
decorations.push(Bullet.range(node.from, node.to + 1));
|
||||
|
||||
else if(node.matchContext(['Blockquote']))
|
||||
decorations.push(Blockquote.range(node.from, node.from));
|
||||
|
||||
return true;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return decorations;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { autofocus = false } = defineProps<{
|
||||
placeholder?: string
|
||||
autofocus?: boolean
|
||||
}>();
|
||||
const model = defineModel<string>();
|
||||
|
||||
const editor = useTemplateRef('editor');
|
||||
const view = ref<EditorView>();
|
||||
|
||||
onMounted(() => {
|
||||
if(editor.value)
|
||||
{
|
||||
view.value = new EditorView({
|
||||
doc: model.value,
|
||||
parent: editor.value,
|
||||
extensions: [
|
||||
markdown({
|
||||
base: markdownLanguage,
|
||||
extensions: {
|
||||
defineNodes: [
|
||||
{ name: "Tag", style: TagTag },
|
||||
{ name: "TagMark", style: tags.processingInstruction }
|
||||
],
|
||||
parseInline: [{
|
||||
name: "Tag",
|
||||
parse(cx, next, pos) {
|
||||
if (next != 35 || cx.char(pos + 1) == 35) return -1;
|
||||
let elts = [cx.elt("TagMark", pos, pos + 1)];
|
||||
for (let i = pos + 1; i < cx.end; i++) {
|
||||
let next = cx.char(i);
|
||||
if (next == 35)
|
||||
return cx.addElement(cx.elt("Tag", pos, i + 1, elts.concat(cx.elt("TagMark", i, i + 1))));
|
||||
if (next == 92)
|
||||
elts.push(cx.elt("Escape", i, i++ + 2));
|
||||
if (next == 32 || next == 9 || next == 10 || next == 13) break;
|
||||
}
|
||||
return -1
|
||||
}
|
||||
}],
|
||||
}
|
||||
}),
|
||||
history(),
|
||||
search(),
|
||||
dropCursor(),
|
||||
EditorState.allowMultipleSelections.of(true),
|
||||
indentOnInput(),
|
||||
syntaxHighlighting(highlight),
|
||||
bracketMatching(),
|
||||
closeBrackets(),
|
||||
crosshairCursor(),
|
||||
EditorView.lineWrapping,
|
||||
keymap.of([
|
||||
...closeBracketsKeymap,
|
||||
...defaultKeymap,
|
||||
...searchKeymap,
|
||||
...historyKeymap,
|
||||
...foldKeymap,
|
||||
...completionKeymap,
|
||||
...lintKeymap
|
||||
]),
|
||||
EditorView.updateListener.of((viewUpdate: ViewUpdate) => {
|
||||
if (viewUpdate.docChanged && !viewUpdate.transactions.some(tr => tr.annotation(External)))
|
||||
{
|
||||
model.value = viewUpdate.state.doc.toString();
|
||||
}
|
||||
}),
|
||||
EditorView.contentAttributes.of({spellcheck: "true"}),
|
||||
ViewPlugin.fromClass(Decorator, {
|
||||
decorations: e => e.decorations,
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
if(autofocus)
|
||||
{
|
||||
view.value.focus();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (view.value)
|
||||
{
|
||||
view.value?.destroy();
|
||||
view.value = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
if (model.value === void 0) {
|
||||
return;
|
||||
}
|
||||
const currentValue = view.value ? view.value.state.doc.toString() : "";
|
||||
if (view.value && model.value !== currentValue) {
|
||||
view.value.dispatch({
|
||||
changes: { from: 0, to: currentValue.length, insert: model.value || "" },
|
||||
annotations: [External.of(true)],
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
defineExpose({ focus: () => editor.value?.focus() });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="editor" class="flex flex-1 w-full justify-stretch items-stretch py-2 px-1.5 font-sans text-base"></div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.CodeMirror
|
||||
{
|
||||
@apply bg-transparent;
|
||||
@apply flex-1 h-full;
|
||||
@apply font-sans;
|
||||
|
||||
@apply text-light-100 dark:text-dark-100;
|
||||
}
|
||||
.cancel-gutters .CodeMirror-gutters
|
||||
{
|
||||
@apply hidden;
|
||||
}
|
||||
.CodeMirror-sizer
|
||||
{
|
||||
@apply !px-3;
|
||||
}
|
||||
.cancel-gutters .CodeMirror-sizer
|
||||
{
|
||||
@apply ms-2;
|
||||
}
|
||||
.CodeMirror-gutters
|
||||
{
|
||||
@apply bg-transparent;
|
||||
@apply border-transparent;
|
||||
}
|
||||
.CodeMirror-gutter-wrapper
|
||||
{
|
||||
@apply absolute top-0 bottom-0;
|
||||
@apply flex justify-center items-center;
|
||||
}
|
||||
.CodeMirror-foldmarker
|
||||
{
|
||||
@apply text-light-100;
|
||||
@apply dark:text-dark-100;
|
||||
@apply ps-3;
|
||||
text-shadow: none;
|
||||
}
|
||||
.hmd-inactive-line .cm-formatting-header, .hmd-inactive-line .cm-formatting-link, .hmd-inactive-line .cm-link-has-alias, .hmd-inactive-line .cm-link-alias-pipe
|
||||
{
|
||||
@apply hidden;
|
||||
}
|
||||
.CodeMirror-line
|
||||
{
|
||||
@apply text-base;
|
||||
}
|
||||
.CodeMirror-cursor
|
||||
{
|
||||
@apply border-light-100 dark:border-dark-100;
|
||||
}
|
||||
.CodeMirror-selected
|
||||
{
|
||||
@apply bg-light-35 dark:bg-dark-35;
|
||||
}
|
||||
.HyperMD-list-line-1 {
|
||||
@apply !ps-0;
|
||||
}
|
||||
.HyperMD-list-line-2 {
|
||||
@apply !ps-6;
|
||||
}
|
||||
.HyperMD-list-line-3 {
|
||||
@apply !ps-12;
|
||||
}
|
||||
</style>
|
||||
@@ -1,40 +0,0 @@
|
||||
<template>
|
||||
<Editor ref="editor" v-model="model" autofocus :gutters="false" />
|
||||
<iframe ref="iframe" class="w-full h-full border-0" sandbox="allow-same-origin allow-scripts"></iframe>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const model = defineModel<string>();
|
||||
const editor = useTemplateRef('editor'), iframe = useTemplateRef('iframe');
|
||||
|
||||
onMounted(() => {
|
||||
if(iframe.value && iframe.value.contentDocument && editor.value)
|
||||
{
|
||||
editor.value.$el.remove();
|
||||
|
||||
iframe.value.contentDocument.documentElement.setAttribute('class', document.documentElement.getAttribute('class') ?? '');
|
||||
iframe.value.contentDocument.documentElement.setAttribute('style', document.documentElement.getAttribute('style') ?? '');
|
||||
|
||||
const base = iframe.value.contentDocument.head.appendChild(iframe.value.contentDocument.createElement('base'));
|
||||
base.setAttribute('href', window.location.href);
|
||||
|
||||
for(let element of document.getElementsByTagName('link'))
|
||||
{
|
||||
if(element.getAttribute('rel') === 'stylesheet')
|
||||
iframe.value.contentDocument.head.appendChild(element.cloneNode(true));
|
||||
}
|
||||
|
||||
for(let element of document.getElementsByTagName('style'))
|
||||
{
|
||||
iframe.value.contentDocument.head.appendChild(element.cloneNode(true));
|
||||
}
|
||||
|
||||
iframe.value.contentDocument.body.setAttribute('class', document.body.getAttribute('class') ?? '');
|
||||
iframe.value.contentDocument.body.setAttribute('style', document.body.getAttribute('style') ?? '');
|
||||
|
||||
iframe.value.contentDocument.body.appendChild(editor.value.$el);
|
||||
|
||||
editor.value.focus();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -1,80 +0,0 @@
|
||||
<template>
|
||||
<div ref="element"></div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { heading } from 'hast-util-heading';
|
||||
import { headingRank } from 'hast-util-heading-rank';
|
||||
import { parseId } from '~/shared/general.util';
|
||||
import type { Root } from 'hast';
|
||||
import { renderMarkdown } from '~/shared/markdown.util';
|
||||
import { tag, a, blockquote, h1, h2, h3, h4, h5, hr, li, small, table, td, th, type Prose } from '~/shared/proses';
|
||||
import { callout } from '../shared/proses';
|
||||
|
||||
const { content, proses, filter } = defineProps<{
|
||||
content: string
|
||||
proses?: Record<string, Prose>
|
||||
filter?: string
|
||||
}>();
|
||||
const element = useTemplateRef('element');
|
||||
|
||||
const parser = useMarkdown(), data = ref<Root>();
|
||||
const node = computed(() => content ? parser.parseSync(content) : undefined);
|
||||
watch([node], () => {
|
||||
if(!node.value)
|
||||
{
|
||||
data.value = undefined;
|
||||
return;
|
||||
}
|
||||
else if(!filter)
|
||||
{
|
||||
data.value = node.value;
|
||||
}
|
||||
else
|
||||
{
|
||||
const start = node.value?.children.findIndex(e => heading(e) && parseId(e.properties.id as string | undefined) === filter) ?? -1;
|
||||
|
||||
if(start === -1)
|
||||
data.value = node.value;
|
||||
else
|
||||
{
|
||||
let end = start;
|
||||
const rank = headingRank(node.value.children[start])!;
|
||||
while(end < node.value.children.length)
|
||||
{
|
||||
end++;
|
||||
if(heading(node.value.children[end]) && headingRank(node.value.children[end])! <= rank)
|
||||
break;
|
||||
}
|
||||
data.value = { ...node.value, children: node.value.children.slice(start, end) };
|
||||
}
|
||||
}
|
||||
|
||||
mount();
|
||||
}, { immediate: true, });
|
||||
|
||||
onMounted(() => {
|
||||
mount();
|
||||
})
|
||||
|
||||
function mount()
|
||||
{
|
||||
if(!element.value || !data.value)
|
||||
return;
|
||||
|
||||
const el = element.value!;
|
||||
|
||||
for(let i = el.childElementCount - 1; i >= 0; i--)
|
||||
{
|
||||
el.removeChild(el.children[i]);
|
||||
}
|
||||
|
||||
el.appendChild(renderMarkdown(data.value, { tag, a, blockquote, callout, h1, h2, h3, h4, h5, hr, li, small, table, td, th, ...proses }));
|
||||
|
||||
const hash = useRouter().currentRoute.value.hash;
|
||||
if(hash.length > 0)
|
||||
{
|
||||
document.getElementById(parseId(hash.substring(1))!)?.scrollIntoView({ behavior: 'instant' })
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,115 +0,0 @@
|
||||
<script lang="ts">
|
||||
import type { RootContent, Root } from 'hast';
|
||||
import { Text, Comment } from 'vue';
|
||||
|
||||
import ProseP from '~/components/prose/ProseP.vue';
|
||||
import ProseA from '~/components/prose/ProseA.vue';
|
||||
import ProseBlockquote from '~/components/prose/ProseBlockquote.vue';
|
||||
import ProseCallout from './prose/ProseCallout.vue';
|
||||
import ProseCode from '~/components/prose/ProseCode.vue';
|
||||
import ProsePre from '~/components/prose/ProsePre.vue';
|
||||
import ProseEm from '~/components/prose/ProseEm.vue';
|
||||
import ProseH1 from '~/components/prose/ProseH1.vue';
|
||||
import ProseH2 from '~/components/prose/ProseH2.vue';
|
||||
import ProseH3 from '~/components/prose/ProseH3.vue';
|
||||
import ProseH4 from '~/components/prose/ProseH4.vue';
|
||||
import ProseH5 from '~/components/prose/ProseH5.vue';
|
||||
import ProseH6 from '~/components/prose/ProseH6.vue';
|
||||
import ProseHr from '~/components/prose/ProseHr.vue';
|
||||
import ProseImg from '~/components/prose/ProseImg.vue';
|
||||
import ProseUl from '~/components/prose/ProseUl.vue';
|
||||
import ProseOl from '~/components/prose/ProseOl.vue';
|
||||
import ProseLi from '~/components/prose/ProseLi.vue';
|
||||
import ProseSmall from './prose/ProseSmall.vue';
|
||||
import ProseStrong from '~/components/prose/ProseStrong.vue';
|
||||
import ProseTable from '~/components/prose/ProseTable.vue';
|
||||
import ProseTag from '~/components/prose/ProseTag.vue';
|
||||
import ProseThead from '~/components/prose/ProseThead.vue';
|
||||
import ProseTbody from '~/components/prose/ProseTbody.vue';
|
||||
import ProseTd from '~/components/prose/ProseTd.vue';
|
||||
import ProseTh from '~/components/prose/ProseTh.vue';
|
||||
import ProseTr from '~/components/prose/ProseTr.vue';
|
||||
import ProseScript from '~/components/prose/ProseScript.vue';
|
||||
|
||||
const proseList = {
|
||||
"p": ProseP,
|
||||
"a": ProseA,
|
||||
"blockquote": ProseBlockquote,
|
||||
"callout": ProseCallout,
|
||||
"code": ProseCode,
|
||||
"pre": ProsePre,
|
||||
"em": ProseEm,
|
||||
"h1": ProseH1,
|
||||
"h2": ProseH2,
|
||||
"h3": ProseH3,
|
||||
"h4": ProseH4,
|
||||
"h5": ProseH5,
|
||||
"h6": ProseH6,
|
||||
"hr": ProseHr,
|
||||
"img": ProseImg,
|
||||
"ul": ProseUl,
|
||||
"ol": ProseOl,
|
||||
"li": ProseLi,
|
||||
"small": ProseSmall,
|
||||
"strong": ProseStrong,
|
||||
"table": ProseTable,
|
||||
"tag": ProseTag,
|
||||
"thead": ProseThead,
|
||||
"tbody": ProseTbody,
|
||||
"td": ProseTd,
|
||||
"th": ProseTh,
|
||||
"tr": ProseTr,
|
||||
"script": ProseScript
|
||||
};
|
||||
|
||||
export default defineComponent({
|
||||
name: 'MarkdownRenderer',
|
||||
props: {
|
||||
node: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
proses: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
async setup(props) {
|
||||
if(props.proses)
|
||||
{
|
||||
for(const prose of Object.keys(props.proses))
|
||||
{
|
||||
if(typeof props.proses[prose] === 'string')
|
||||
props.proses[prose] = await resolveComponent(props.proses[prose]);
|
||||
}
|
||||
}
|
||||
return { tags: Object.assign({}, proseList, props.proses) };
|
||||
},
|
||||
render(ctx: any) {
|
||||
const { node, tags } = ctx;
|
||||
|
||||
if(!node)
|
||||
return null;
|
||||
|
||||
return h('div', null, {default: () => (node as Root).children.map(e => renderNode(e, tags)).filter(e => !!e)});
|
||||
}
|
||||
});
|
||||
|
||||
function renderNode(node: RootContent, tags: Record<string, any>): VNode | undefined
|
||||
{
|
||||
if(node.type === 'text' && node.value.length > 0 && node.value !== '\n')
|
||||
{
|
||||
return h(Text, node.value);
|
||||
}
|
||||
else if(node.type === 'comment' && node.value.length > 0 && node.value !== '\n')
|
||||
{
|
||||
return h(Comment, node.value);
|
||||
}
|
||||
else if(node.type === 'element')
|
||||
{
|
||||
return h(tags[node.tagName] ?? node.tagName, { ...node.properties, class: node.properties.className }, { default: () => node.children.map(e => renderNode(e, tags)).filter(e => !!e) });
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user