Browse Source

Support rich formatting in editor preview

master
buttercat1791 1 year ago
parent
commit
dd27f6d529
  1. 2
      package.json
  2. 17
      pnpm-lock.yaml
  3. 12
      src/app.css
  4. 36
      src/lib/articleParser.ts
  5. 16
      src/lib/components/Preview.svelte
  6. 20
      src/lib/parser.ts
  7. 29
      src/routes/new/edit/+page.svelte

2
package.json

@ -20,6 +20,7 @@ @@ -20,6 +20,7 @@
"@tailwindcss/forms": "^0.5.7",
"@tailwindcss/typography": "^0.5.10",
"asciidoctor": "^3.0.4",
"he": "^1.2.0",
"markdown-it": "^14.0.0",
"markdown-it-plain-text": "^0.3.0",
"marked": "^11.1.1",
@ -31,6 +32,7 @@ @@ -31,6 +32,7 @@
"devDependencies": {
"@sveltejs/adapter-auto": "^3.1.1",
"@sveltejs/kit": "^2.4.3",
"@types/he": "^1.2.3",
"@types/markdown-it": "^13.0.7",
"@types/node": "^22.5.4",
"@types/showdown": "^2.0.6",

17
pnpm-lock.yaml

@ -29,6 +29,9 @@ importers: @@ -29,6 +29,9 @@ importers:
asciidoctor:
specifier: ^3.0.4
version: 3.0.4(chokidar@3.6.0)
he:
specifier: ^1.2.0
version: 1.2.0
markdown-it:
specifier: ^14.0.0
version: 14.1.0
@ -57,6 +60,9 @@ importers: @@ -57,6 +60,9 @@ importers:
'@sveltejs/kit':
specifier: ^2.4.3
version: 2.5.25(@sveltejs/vite-plugin-svelte@3.1.2(svelte@4.2.19)(vite@5.4.2(@types/node@22.5.4)))(svelte@4.2.19)(vite@5.4.2(@types/node@22.5.4))
'@types/he':
specifier: ^1.2.3
version: 1.2.3
'@types/markdown-it':
specifier: ^13.0.7
version: 13.0.9
@ -572,6 +578,9 @@ packages: @@ -572,6 +578,9 @@ packages:
'@types/estree@1.0.5':
resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
'@types/he@1.2.3':
resolution: {integrity: sha512-q67/qwlxblDzEDvzHhVkwc1gzVWxaNxeyHUBF4xElrvjL11O+Ytze+1fGpBHlr/H9myiBUaUXNnNPmBHxxfAcA==}
'@types/linkify-it@3.0.5':
resolution: {integrity: sha512-yg6E+u0/+Zjva+buc3EIb+29XEg4wltq7cSmd4Uc2EE/1nUVmxyzpX6gUXD0V8jIrG0r7YeOGVIbYRkxeooCtw==}
@ -1208,6 +1217,10 @@ packages: @@ -1208,6 +1217,10 @@ packages:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'}
he@1.2.0:
resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
hasBin: true
htmlparser2@9.1.0:
resolution: {integrity: sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==}
@ -2614,6 +2627,8 @@ snapshots: @@ -2614,6 +2627,8 @@ snapshots:
'@types/estree@1.0.5': {}
'@types/he@1.2.3': {}
'@types/linkify-it@3.0.5': {}
'@types/markdown-it@13.0.9':
@ -3329,6 +3344,8 @@ snapshots: @@ -3329,6 +3344,8 @@ snapshots:
dependencies:
function-bind: 1.1.2
he@1.2.0: {}
htmlparser2@9.1.0:
dependencies:
domelementtype: 2.3.0

12
src/app.css

@ -48,8 +48,16 @@ @@ -48,8 +48,16 @@
}
/* Content */
div.note-leather {
@apply bg-primary-0 dark:bg-primary-1000 text-gray-800 dark:text-gray-300 hover:bg-primary-100 dark:hover:bg-primary-800 p-2 rounded;
div.note-leather,
p.note-leather,
section.note-leather {
@apply bg-primary-0 dark:bg-primary-1000 text-gray-800 dark:text-gray-300 p-2 rounded;
}
div.note-leather:hover:not(:has(.note-leather:hover)),
p.note-leather:hover:not(:has(.note-leather:hover)),
section.note-leather:hover:not(:has(.note-leather:hover)) {
@apply hover:bg-primary-100 dark:hover:bg-primary-800 ;
}
/* Heading */

36
src/lib/articleParser.ts

@ -1,36 +0,0 @@ @@ -1,36 +0,0 @@
import MarkdownIt from 'markdown-it';
import LinkToArticle from '$components/LinkToArticle.svelte';
import plainText from 'markdown-it-plain-text';
const md = new MarkdownIt();
const mdTxt = new MarkdownIt().use(plainText);
export function parse(markdown: string) {
let parsedMarkdown = md.render(markdown);
parsedMarkdown = parsedMarkdown.replace(/\[\[(.*?)\]\]/g, (match: any, content: any) => {
const container = document.createElement('span');
const linkToArticle = new LinkToArticle({
target: container,
props: {
content: content
}
});
return container.outerHTML;
});
return parsedMarkdown;
}
export function parsePlainText(markdown: string) {
mdTxt.render(markdown);
/* @ts-ignore */ // markdown-it-plain-text doesnt have typescript support??
let parsedText = mdTxt.plainText.replace(/\[\[(.*?)\]\]/g, (match: any, content: any) => {
return content;
});
return parsedText;
}

16
src/lib/components/Preview.svelte

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
<script lang="ts">
import Pharos from "$lib/parser";
import { Heading } from "flowbite-svelte";
import { Heading, P } from "flowbite-svelte";
export let parser: Pharos;
export let rootIndexId: string;
@ -27,17 +27,21 @@ @@ -27,17 +27,21 @@
};
</script>
<div>
<section class='note-leather flex flex-col space-y-2'>
{#if depth < 4}
<Heading tag={getHeadingTag(depth)}>{title}</Heading>
{#each orderedChildren as id}
{#each orderedChildren as id, index}
{#if childIndices.includes(id)}
<svelte:self {parser} rootIndexId={id} depth={depth + 1} />
{:else if (childZettels.includes(id))}
{@html parser.getHtmlContent(id)}
<P class='note-leather' firstupper={index === 0}>
{@html parser.getContent(id)}
</P>
{/if}
{/each}
{:else}
{@html parser.getHtmlContent(rootIndexId)}
<P class='note-leather' firstupper>
{@html parser.getContent(rootIndexId)}
</P>
{/if}
</div>
</section>

20
src/lib/parser.ts

@ -9,6 +9,7 @@ import asciidoctor, { @@ -9,6 +9,7 @@ import asciidoctor, {
Section,
type ProcessorOptions
} from 'asciidoctor';
import he from 'he';
interface IndexMetadata {
authors?: string[];
@ -146,7 +147,8 @@ export default class Pharos { @@ -146,7 +147,8 @@ export default class Pharos {
*/
getIndexTitle(id: string): string | undefined {
const section = this.nodes.get(id) as Section;
return section.getTitle();
const title = section.getTitle() ?? '';
return he.decode(title);
}
/**
@ -173,10 +175,20 @@ export default class Pharos { @@ -173,10 +175,20 @@ export default class Pharos {
}
/**
* @returns The converted content of the node with the given ID.
* @returns The content of the node with the given ID. The presentation of the returned content
* varies by the node's context.
* @remarks By default, the content is returned as HTML produced by the
* Asciidoctor converter. However, other formats are returned for specific contexts:
* - Paragraph: The content is returned as a plain string.
*/
getHtmlContent(id: string): string {
const block = this.nodes.get(id) as Block;
getContent(id: string): string {
const block = this.nodes.get(id) as AbstractBlock;
switch (block.getContext()) {
case 'paragraph':
return block.getContent() ?? '';
}
return block.convert();
}

29
src/routes/new/edit/+page.svelte

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
<script lang="ts">
import { Input, Label, Textarea, Toolbar, ToolbarButton } from "flowbite-svelte";
import { Heading, Textarea, Toolbar, ToolbarButton } from "flowbite-svelte";
import { CodeOutline, EyeSolid } from "flowbite-svelte-icons";
import { editorText } from "$lib/stores";
import Preview from "$lib/components/Preview.svelte";
@ -25,14 +25,7 @@ @@ -25,14 +25,7 @@
<main class='w-full flex justify-center'>
<form class='max-w-2xl w-full'>
<div class='flex flex-col space-y-4'>
<div>
<Label for='article-title' class='mb-2'>Article Title</Label>
<Input type='text' id='article-title' placeholder='Title' required />
</div>
<div>
<Label for='article-author' class='mb-2'>Author Name</Label>
<Input type='text' id='article-author' placeholder='Author' required />
</div>
<Heading tag='h1' class='mb-2'>New Article</Heading>
{#if isEditing}
<Textarea
id='article-content'
@ -47,16 +40,14 @@ @@ -47,16 +40,14 @@
</Toolbar>
</Textarea>
{:else}
<div>
<Toolbar>
<ToolbarButton name='Edit' on:click={hidePreview}>
<CodeOutline class='w-6 h-6' />
</ToolbarButton>
</Toolbar>
{#if rootIndexId}
<Preview {parser} {rootIndexId} />
{/if}
</div>
<Toolbar>
<ToolbarButton name='Edit' on:click={hidePreview}>
<CodeOutline class='w-6 h-6' />
</ToolbarButton>
</Toolbar>
{#if rootIndexId}
<Preview {parser} {rootIndexId} />
{/if}
{/if}
</div>
</form>

Loading…
Cancel
Save