Browse Source

Merge pull request #3 from buttercat1791/article-editor

[WIP] Edit and Compose Views
master
Michael J 1 year ago committed by GitHub
parent
commit
cf6c0cf228
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 5385
      package-lock.json
  2. 290
      src/lib/components/Preview.svelte
  3. 254
      src/lib/parser.ts
  4. 4
      src/lib/stores.ts
  5. 11
      src/routes/new/compose/+page.svelte
  6. 32
      src/routes/new/edit/+page.svelte

5385
package-lock.json generated

File diff suppressed because it is too large Load Diff

290
src/lib/components/Preview.svelte

@ -1,20 +1,75 @@ @@ -1,20 +1,75 @@
<script lang="ts">
import { parser } from "$lib/parser";
import { hoverTargetId } from "$lib/stores";
import { Button, Heading, P, Textarea, Tooltip } from "flowbite-svelte";
import { parser, SiblingSearchDirection } from "$lib/parser";
import { Button, ButtonGroup, CloseButton, Heading, Input, P, Textarea, Tooltip } from "flowbite-svelte";
import { CaretDownSolid, CaretUpSolid, EditOutline } from "flowbite-svelte-icons";
import { createEventDispatcher } from "svelte";
// TODO: Fix move between parents.
export let sectionClass: string = '';
export let isSectionStart: boolean = false;
export let rootId: string;
export let parentId: string | null | undefined = null;
export let depth: number = 0;
export let allowEditing: boolean = false;
export let needsUpdate: boolean = false;
const dispatch = createEventDispatcher();
let currentContent: string = $parser.getContent(rootId);
let title: string | undefined = $parser.getIndexTitle(rootId);
let orderedChildren: string[] = $parser.getOrderedChildIds(rootId);
let isEditing: boolean = false;
let currentContent: string;
let hasCursor: boolean = false;
let childHasCursor: boolean;
let hasPreviousSibling: boolean = false;
let hasNextSibling: boolean = false;
let subtreeNeedsUpdate: boolean = false;
let updateCount: number = 0;
let subtreeUpdateCount: number = 0;
const title = $parser.getIndexTitle(rootId);
const orderedChildren = $parser.getOrderedChildIds(rootId);
$: buttonsVisible = hasCursor && !childHasCursor;
$: {
if (needsUpdate) {
updateCount++;
needsUpdate = false;
title = $parser.getIndexTitle(rootId);
currentContent = $parser.getContent(rootId);
}
if (subtreeNeedsUpdate) {
subtreeUpdateCount++;
subtreeNeedsUpdate = false;
const prevChildCount = orderedChildren.length;
orderedChildren = $parser.getOrderedChildIds(rootId);
const newChildCount = orderedChildren.length;
// If the number of children has changed, a child has been added or removed, and a child may
// have been moved into a different subtree. Due to the `needsUpdate` binding in the
// component's recursion, setting `needsUpdate` to true will force the parent to rerender its
// subtree.
if (newChildCount !== prevChildCount) {
needsUpdate = true;
}
}
}
$: {
if (parentId && allowEditing) {
// Check for previous/next siblings on load
const previousSibling = $parser.getNearestSibling(rootId, depth - 1, SiblingSearchDirection.Previous);
const nextSibling = $parser.getNearestSibling(rootId, depth - 1, SiblingSearchDirection.Next);
// Hide arrows if no siblings exist
hasPreviousSibling = !!previousSibling[0];
hasNextSibling = !!nextSibling[0];
}
}
const getHeadingTag = (depth: number) => {
switch (depth) {
@ -31,68 +86,152 @@ @@ -31,68 +86,152 @@
}
};
const handleFocus = (e: Event) => {
const target = e.target as HTMLElement;
if (target.id === rootId) {
$hoverTargetId = rootId;
e.stopPropagation();
}
const handleMouseEnter = (e: MouseEvent) => {
hasCursor = true;
dispatch('cursorcapture', e);
};
const handleBlur = (e: Event) => {
const target = e.target as HTMLElement;
if (target.id === rootId) {
$hoverTargetId = '';
e.stopPropagation();
}
const handleMouseLeave = (e: MouseEvent) => {
hasCursor = false;
dispatch('cursorrelease', e);
};
const handleChildCursorCaptured = (e: MouseEvent) => {
childHasCursor = true;
dispatch('cursorrelase', e);
};
// TODO: Trigger rerender when editing state changes.
const handleChildCursorReleased = (e: MouseEvent) => {
childHasCursor = false;
}
const toggleEditing = (id: string, shouldSave: boolean = true) => {
const editing = isEditing;
currentContent = $parser.getContent(id);
if (editing && shouldSave) {
// TODO: Save updated content.
if (orderedChildren.length > 0) {
}
$parser.updateEventContent(id, currentContent);
}
isEditing = !editing;
};
const moveUp = (rootId: string, parentId: string) => {
// Get the previous sibling and its index
const [prevSiblingId, prevIndex] = $parser.getNearestSibling(rootId, depth - 1, SiblingSearchDirection.Previous);
if (!prevSiblingId || prevIndex == null) {
return;
}
// Move the current event before the previous sibling.
$parser.moveEvent(rootId, prevSiblingId, false);
needsUpdate = true;
};
const moveDown = (rootId: string, parentId: string) => {
// Get the next sibling and its index
const [nextSiblingId, nextIndex] = $parser.getNearestSibling(rootId, depth - 1, SiblingSearchDirection.Next);
if (!nextSiblingId || nextIndex == null) {
return;
}
// Move the current event after the next sibling
$parser.moveEvent(rootId, nextSiblingId, true);
needsUpdate = true;
};
</script>
<!-- This component is recursively structured. The base case is single block of content. -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<section
id={rootId}
class={`note-leather w-full flex space-x-2 ${sectionClass}`}
on:mouseover={handleFocus}
on:focus={handleFocus}
class={`note-leather flex space-x-2 justify-between ${sectionClass}`}
on:mouseenter={handleMouseEnter}
on:mouseleave={handleMouseLeave}
>
<!-- Zettel base case -->
{#if orderedChildren.length === 0 || depth >= 4}
<P firstupper={isSectionStart}>
{@html $parser.getContent(rootId)}
</P>
{#key updateCount}
{#if isEditing}
<form class='w-full'>
<Textarea class='textarea-leather w-full' bind:value={currentContent}>
<div slot='footer' class='flex space-x-2 justify-end'>
<Button
type='reset'
class='btn-leather min-w-fit'
size='sm'
outline
on:click={() => toggleEditing(rootId, false)}
>
Cancel
</Button>
<Button
type='submit'
class='btn-leather min-w-fit'
size='sm'
solid
on:click={() => toggleEditing(rootId, true)}
>
Save
</Button>
</div>
</Textarea>
</form>
{:else}
<P firstupper={isSectionStart}>
{@html currentContent}
</P>
{/if}
{/key}
{:else}
<div class='flex flex-col space-y-2'>
<Heading tag={getHeadingTag(depth)} class='h-leather'>
{title}
</Heading>
{#if isEditing}
<ButtonGroup class='w-full'>
<Input type='text' class='input-leather' size='lg' bind:value={title}>
<CloseButton slot='right' on:click={() => toggleEditing(rootId, false)} />
</Input>
<Button class='btn-leather' color='primary' size='lg' on:click={() => toggleEditing(rootId, true)}>
Save
</Button>
</ButtonGroup>
{:else}
<Heading tag={getHeadingTag(depth)} class='h-leather'>
{title}
</Heading>
{/if}
<!-- Recurse on child indices and zettels -->
{#each orderedChildren as id, index}
<svelte:self rootId={id} depth={depth + 1} {allowEditing} isSectionStart={index === 0} />
{/each}
{#key subtreeUpdateCount}
{#each orderedChildren as id, index}
<svelte:self
rootId={id}
parentId={rootId}
depth={depth + 1}
{allowEditing}
isSectionStart={index === 0}
bind:needsUpdate={subtreeNeedsUpdate}
on:cursorcapture={handleChildCursorCaptured}
on:cursorrelease={handleChildCursorReleased}
/>
{/each}
{/key}
</div>
{/if}
{#if allowEditing}
<div class={`flex flex-col space-y-2 justify-start ${$hoverTargetId === rootId ? 'visible' : 'invisible'}`}>
<Button class='btn-leather' size='sm' outline>
<CaretUpSolid />
</Button>
<Button class='btn-leather' size='sm' outline>
<CaretDownSolid />
</Button>
<Button class='btn-leather' size='sm' outline>
{#if allowEditing && depth > 0}
<div class={`flex flex-col space-y-2 justify-start ${buttonsVisible ? 'visible' : 'invisible'}`}>
{#if hasPreviousSibling && parentId}
<Button class='btn-leather' size='sm' outline on:click={() => moveUp(rootId, parentId)}>
<CaretUpSolid />
</Button>
{/if}
{#if hasNextSibling && parentId}
<Button class='btn-leather' size='sm' outline on:click={() => moveDown(rootId, parentId)}>
<CaretDownSolid />
</Button>
{/if}
<Button class='btn-leather' size='sm' outline on:click={() => toggleEditing(rootId)}>
<EditOutline />
</Button>
<Tooltip class='tooltip-leather' type='auto' placement='top'>
@ -101,70 +240,3 @@ @@ -101,70 +240,3 @@
</div>
{/if}
</section>
<!-- <section class={`note-leather grid grid-cols-[1fr_auto] gap-2 ${sectionClass}`}>
<div class={`flex flex-col space-y-2 ${depth > 0 ? 'border-l-gray-500 border-l pl-2' : ''}`}>
{#if depth < 4}
<Heading tag={getHeadingTag(depth)} class='h-leather'>{title}</Heading>
{#each orderedChildren as id, index}
{#if childIndices.includes(id)}
<svelte:self rootIndexId={id} depth={depth + 1} {allowEditing} />
{:else if (childZettels.includes(id))}
<div class='note-leather grid grid-cols-[1fr_auto] gap-2'>
{#if isEditing.get(id)}
<form>
<Textarea class='textarea-leather' rows={5} bind:value={editorContent[id]}>
<div slot='footer' class='flex justify-end'>
<Button class='btn-leather' size='sm' outline on:click={() => toggleEditing(id, false)}>
Cancel
</Button>
<Button class='btn-leather' size='sm' on:click={() => toggleEditing(id)}>
Save
</Button>
</div>
</Textarea>
</form>
{:else}
<P class='border-l-gray-500 border-l pl-2' firstupper={index === 0}>
{@html $parser.getContent(id)}
</P>
{/if}
{#if allowEditing}
<div class='col-start-2 flex flex-col space-y-2 justify-start'>
<Button class='btn-leather' size='sm' outline>
<CaretUpSolid />
</Button>
<Button class='btn-leather' size='sm' outline>
<CaretDownSolid />
</Button>
<Button class='btn-leather' size='sm' outline on:click={() => toggleEditing(id)}>
<EditOutline />
</Button>
<Tooltip class='tooltip-leather' type='auto' placement='top'>
Edit
</Tooltip>
</div>
{/if}
</div>
{/if}
{/each}
{:else}
<P class='note-leather' firstupper>
{@html $parser.getContent(rootIndexId)}
</P>
{/if}
</div>
{#if allowEditing}
<div class='col-start-2 flex flex-col space-y-2 justify-start'>
<Button class='btn-leather' size='sm' outline>
<CaretUpSolid />
</Button>
<Button class='btn-leather' size='sm' outline>
<CaretDownSolid />
</Button>
<Button class='btn-leather' size='sm' outline>
<EditOutline />
</Button>
</div>
{/if}
</section> -->

254
src/lib/parser.ts

@ -24,6 +24,16 @@ interface IndexMetadata { @@ -24,6 +24,16 @@ interface IndexMetadata {
coverImage?: string;
}
export enum SiblingSearchDirection {
Previous,
Next
}
export enum InsertLocation {
Before,
After
}
/**
* @classdesc Pharos is an extension of the Asciidoctor class that adds Nostr Knowledge Base (NKB)
* features to core Asciidoctor functionality. Asciidoctor is used to parse an AsciiDoc document
@ -84,6 +94,12 @@ export default class Pharos { @@ -84,6 +94,12 @@ export default class Pharos {
*/
private events: Map<string, NDKEvent> = new Map<string, NDKEvent>();
/**
* A map of event d tags to the context name assigned to each event's originating node by the
* Asciidoctor parser.
*/
private eventToContextMap: Map<string, string> = new Map<string, string>();
/**
* A map of node IDs to the integer event kind that will be used to represent the node.
*/
@ -99,6 +115,11 @@ export default class Pharos { @@ -99,6 +115,11 @@ export default class Pharos {
*/
private eventIds: Map<string, string> = new Map<string, string>();
/**
* A map of the levels of the event tree to a list of event IDs at each level.
*/
private eventsByLevelMap: Map<number, string[]> = new Map<number, string[]>();
/**
* When `true`, `getEvents()` should regenerate the event tree to propagate updates.
*/
@ -125,7 +146,12 @@ export default class Pharos { @@ -125,7 +146,12 @@ export default class Pharos {
}
parse(content: string, options?: ProcessorOptions | undefined): void {
this.html = this.asciidoctor.convert(content, options) as string | Document | undefined;
try {
this.html = this.asciidoctor.convert(content, options) as string | Document | undefined;
} catch (error) {
console.error(error);
throw new Error('Failed to parse AsciiDoc document.');
}
}
/**
@ -235,50 +261,150 @@ export default class Pharos { @@ -235,50 +261,150 @@ export default class Pharos {
throw new Error(`No event found for #d:${dTag}.`);
}
event.content = content;
event.id = event.getEventHash();
this.events.set(dTag, event);
this.eventIds.set(dTag, event.id);
this.shouldUpdateEventTree = true;
this.updateEventByContext(dTag, content, this.eventToContextMap.get(dTag)!);
return event;
}
/**
* Finds the nearest sibling of the event with the given d tag.
* @param targetDTag The d tag of the target event.
* @param parentDTag The d tag of the target event's parent.
* @param depth The depth of the target event within the parser tree.
* @param direction The direction in which to search for a sibling.
* @returns A tuple containing the d tag of the nearest sibling and the d tag of the nearest
* sibling's parent.
*/
getNearestSibling(
targetDTag: string,
depth: number,
direction: SiblingSearchDirection
): [string | null, string | null] {
const eventsAtLevel = this.eventsByLevelMap.get(depth);
if (!eventsAtLevel) {
throw new Error(`No events found at level ${depth}.`);
}
const targetIndex = eventsAtLevel.indexOf(targetDTag);
if (targetIndex === -1) {
throw new Error(`The event indicated by #d:${targetDTag} does not exist at level ${depth} of the event tree.`);
}
const parentDTag = this.getParent(targetDTag);
if (!parentDTag) {
throw new Error(`The event indicated by #d:${targetDTag} does not have a parent.`);
}
const grandparentDTag = this.getParent(parentDTag);
// If the target is the first node at its level and we're searching for a previous sibling,
// look among the siblings of the target's parent at the previous level.
if (targetIndex === 0 && direction === SiblingSearchDirection.Previous) {
// * Base case: The target is at the first level of the tree and has no previous sibling.
if (!grandparentDTag) {
return [null, null];
}
return this.getNearestSibling(parentDTag, depth - 1, direction);
}
// If the target is the last node at its level and we're searching for a next sibling,
// look among the siblings of the target's parent at the previous level.
if (targetIndex === eventsAtLevel.length - 1 && direction === SiblingSearchDirection.Next) {
// * Base case: The target is at the last level of the tree and has no subsequent sibling.
if (!grandparentDTag) {
return [null, null];
}
return this.getNearestSibling(parentDTag, depth - 1, direction);
}
// * Base case: There is an adjacent sibling at the same depth as the target.
switch (direction) {
case SiblingSearchDirection.Previous:
return [eventsAtLevel[targetIndex - 1], parentDTag];
case SiblingSearchDirection.Next:
return [eventsAtLevel[targetIndex + 1], parentDTag];
}
return [null, null];
}
/**
* Gets the d tag of the parent of the event with the given d tag.
* @param dTag The d tag of the target event.
* @returns The d tag of the parent event, or null if the target event does not have a parent.
* @throws An error if the target event does not exist in the parser tree.
*/
getParent(dTag: string): string | null {
// Check if the event exists in the parser tree.
if (!this.eventIds.has(dTag)) {
throw new Error(`The event indicated by #d:${dTag} does not exist in the parser tree.`);
}
// Iterate through all the index to child mappings.
// This may be expensive on large trees.
for (const [indexId, childIds] of this.indexToChildEventsMap) {
// If this parent contains our target as a child, we found the parent
if (childIds.has(dTag)) {
return indexId;
}
}
return null;
}
/**
* Moves an event within the event tree.
* @param dTag The d tag of the event to be moved.
* @param oldParentDTag The d tag of the moved event's current parent.
* @param newParentDTag The d tag of the moved event's new parent.
* @param targetDTag The d tag of the event to be moved.
* @param destinationDTag The d tag another event, next to which the target will be placed.
* @param insertAfter If true, the target will be placed after the destination event, otherwise,
* it will be placed before the destination event.
* @throws Throws an error if the parameters specify an invalid move.
* @remarks Both the old and new parent events must be kind 30040 index events. Moving the event
* within the tree changes the hash of several events, so the event tree will be regenerated when
* the consumer next invokes `getEvents()`.
* @remarks Moving the target event within the tree changes the hash of several events, so the
* event tree will be regenerated when the consumer next invokes `getEvents()`.
*/
moveEvent(dTag: string, oldParentDTag: string, newParentDTag: string): void {
const event = this.events.get(dTag);
if (!event) {
throw new Error(`No event found for #d:${dTag}.`);
moveEvent(targetDTag: string, destinationDTag: string, insertAfter: boolean = false): void {
const targetEvent = this.events.get(targetDTag);
const destinationEvent = this.events.get(destinationDTag);
const targetParent = this.getParent(targetDTag);
const destinationParent = this.getParent(destinationDTag);
if (!targetEvent) {
throw new Error(`No event found for #d:${targetDTag}.`);
}
if (this.eventToKindMap.get(oldParentDTag) !== 30040) {
throw new Error(`Old parent event #d:${oldParentDTag} is not an index event.`);
if (!destinationEvent) {
throw new Error(`No event found for #d:${destinationDTag}.`);
}
if (this.eventToKindMap.get(newParentDTag) !== 30040) {
throw new Error(`New parent event #d:${newParentDTag} is not an index event.`);
if (!targetParent) {
throw new Error(`The event indicated by #d:${targetDTag} does not have a parent.`);
}
const oldParentMap = this.indexToChildEventsMap.get(oldParentDTag);
const newParentMap = this.indexToChildEventsMap.get(newParentDTag);
if (!oldParentMap?.has(dTag)) {
throw new Error(`Event #d:${dTag} is not a child of parent #d:${oldParentDTag}.`);
if (!destinationParent) {
throw new Error(`The event indicated by #d:${destinationDTag} does not have a parent.`);
}
// Perform the move.
oldParentMap?.delete(dTag);
newParentMap?.add(dTag);
// Remove the target from among the children of its current parent.
this.indexToChildEventsMap.get(targetParent)?.delete(targetDTag);
// If necessary, remove the target event from among the children of its destination parent.
this.indexToChildEventsMap.get(destinationParent)?.delete(targetDTag);
// Get the index of the destination event among the children of its parent.
const destinationIndex = Array.from(this.indexToChildEventsMap.get(destinationParent) ?? [])
.indexOf(destinationDTag);
// Insert next to the index of the destination event, either before or after as specified by
// the insertAfter flag.
const destinationChildren = Array.from(this.indexToChildEventsMap.get(destinationParent) ?? []);
insertAfter
? destinationChildren.splice(destinationIndex + 1, 0, targetDTag)
: destinationChildren.splice(destinationIndex, 0, targetDTag);
this.indexToChildEventsMap.set(destinationParent, new Set(destinationChildren));
this.shouldUpdateEventTree = true;
}
@ -294,6 +420,7 @@ export default class Pharos { @@ -294,6 +420,7 @@ export default class Pharos {
this.nodes.clear();
this.eventToKindMap.clear();
this.indexToChildEventsMap.clear();
this.eventsByLevelMap.clear();
this.eventIds.clear();
}
@ -330,6 +457,8 @@ export default class Pharos { @@ -330,6 +457,8 @@ export default class Pharos {
this.processBlock(block as Block);
}
}
this.buildEventsByLevelMap(this.rootNodeId!, 0);
}
/**
@ -402,6 +531,35 @@ export default class Pharos { @@ -402,6 +531,35 @@ export default class Pharos {
//#endregion
// #region Event Tree Operations
/**
* Recursively walks the event tree and builds a map of the events at each level.
* @param parentNodeId The ID of the parent node.
* @param depth The depth of the parent node.
*/
private buildEventsByLevelMap(parentNodeId: string, depth: number): void {
// If we're at the root level, clear the map so it can be freshly rebuilt.
if (depth === 0) {
this.eventsByLevelMap.clear();
}
const children = this.indexToChildEventsMap.get(parentNodeId);
if (!children) {
return;
}
const eventsAtLevel = this.eventsByLevelMap.get(depth) ?? [];
eventsAtLevel.push(...children);
this.eventsByLevelMap.set(depth, eventsAtLevel);
for (const child of children) {
this.buildEventsByLevelMap(child, depth + 1);
}
}
// #endregion
// #region NDKEvent Generation
/**
@ -563,6 +721,10 @@ export default class Pharos { @@ -563,6 +721,10 @@ export default class Pharos {
// #region Utility Functions
/**
* Generates an ID for the given block that is unique within the document, and adds a mapping of
* the generated ID to the block's context, as determined by the Asciidoctor parser.
*/
private generateNodeId(block: AbstractBlock): string {
let blockId: string | null = this.normalizeId(block.getId());
@ -752,6 +914,8 @@ export default class Pharos { @@ -752,6 +914,8 @@ export default class Pharos {
}
block.setId(blockId);
this.eventToContextMap.set(blockId, context);
return blockId;
}
@ -768,6 +932,38 @@ export default class Pharos { @@ -768,6 +932,38 @@ export default class Pharos {
.replace(/[^a-z0-9\-]/g, ''); // Remove non-alphanumeric characters except dashes.
}
private updateEventByContext(dTag: string, value: string, context: string) {
switch (context) {
case 'document':
case 'section':
this.updateEventTitle(dTag, value);
break;
default:
this.updateEventBody(dTag, value);
break;
}
}
private updateEventTitle(dTag: string, value: string) {
const event = this.events.get(dTag);
this.events.delete(dTag);
this.events.set(value, event!);
this.rehashEvent(dTag, event!);
}
private updateEventBody(dTag: string, value: string) {
const event = this.events.get(dTag);
event!.content = value;
this.rehashEvent(dTag, event!);
}
private rehashEvent(dTag: string, event: NDKEvent) {
event.id = event.getEventHash();
this.eventIds.set(dTag, event.id);
this.shouldUpdateEventTree = true;
}
private extractAndNormalizeWikilinks(content: string): string[][] {
const wikilinkPattern = /\[\[([^\]]+)\]\]/g;
const wikilinks: string[][] = [];

4
src/lib/stores.ts

@ -6,7 +6,3 @@ export let idList = writable<string[]>([]); @@ -6,7 +6,3 @@ export let idList = writable<string[]>([]);
export let alexandriaKinds = readable<number[]>([30040, 30041]);
export let feedType = writable<FeedType>(FeedType.Relays);
export let editorText = writable<string>('');
export let hoverTargetId = writable<string | null>(null);

11
src/routes/new/compose/+page.svelte

@ -3,12 +3,21 @@ @@ -3,12 +3,21 @@
import { parser } from "$lib/parser";
import { Heading } from "flowbite-svelte";
let treeNeedsUpdate: boolean = false;
let treeUpdateCount: number = 0;
$: {
if (treeNeedsUpdate) {
treeUpdateCount++;
}
}
</script>
<div class='w-full flex justify-center'>
<main class='main-leather flex flex-col space-y-4 max-w-2xl w-full mt-4 mb-4'>
<Heading tag='h1' class='h-leather mb-2'>Compose</Heading>
<Preview rootId={$parser.getRootIndexId()} allowEditing={true} />
{#key treeUpdateCount}
<Preview rootId={$parser.getRootIndexId()} allowEditing={true} bind:needsUpdate={treeNeedsUpdate} />
{/key}
</main>
</div>

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

@ -1,7 +1,6 @@ @@ -1,7 +1,6 @@
<script lang="ts">
import { Heading, Textarea, Toolbar, ToolbarButton, Tooltip } from "flowbite-svelte";
import { CodeOutline, EyeSolid, PaperPlaneOutline } from "flowbite-svelte-icons";
import { editorText } from "$lib/stores";
import Preview from "$lib/components/Preview.svelte";
import Pharos, { parser } from "$lib/parser";
import { ndk } from "$lib/ndk";
@ -12,10 +11,19 @@ @@ -12,10 +11,19 @@
let isEditing: boolean = true;
let rootIndexId: string;
let editorText: string;
const showPreview = () => {
$parser ??= new Pharos($ndk);
$parser.reset();
$parser.parse($editorText);
try {
$parser ??= new Pharos($ndk);
$parser.reset();
$parser.parse(editorText);
} catch (e) {
console.error(e);
// TODO: Indicate error in UI.
return;
}
rootIndexId = $parser.getRootIndexId();
isEditing = false;
};
@ -25,8 +33,16 @@ @@ -25,8 +33,16 @@
};
const prepareReview = () => {
$parser.reset();
$parser.parse($editorText);
try {
$parser.reset();
$parser.parse(editorText);
} catch (e) {
console.error(e);
// TODO: Indicate error in UI.
return;
}
$parser.generate($ndk.activeUser?.pubkey!);
goto('/new/compose');
}
</script>
@ -41,7 +57,7 @@ @@ -41,7 +57,7 @@
class='textarea-leather'
rows=8
placeholder='Write AsciiDoc content'
bind:value={$editorText}
bind:value={editorText}
>
<Toolbar slot='header' embedded>
<ToolbarButton name='Preview' on:click={showPreview}>
@ -64,7 +80,7 @@ @@ -64,7 +80,7 @@
</ToolbarButton>
</Toolbar>
{#if rootIndexId}
<Preview sectionClass='m-2' {rootIndexId} />
<Preview sectionClass='m-2' rootId={rootIndexId} />
{/if}
</form>
{/if}

Loading…
Cancel
Save