Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions scripts/frontMatter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import assert from 'node:assert/strict';
import { test } from 'node:test';

import {
addFrontMatterListItem,
addFrontMatterListItems,
getMarkdownBodyWithoutFrontMatter,
getFrontMatterListItems,
Expand Down Expand Up @@ -104,8 +103,8 @@ test('getFrontMatterListItems returns string tag values for YAML lists', () => {
test('front matter tag helpers add, edit, and remove tags predictably', () => {
const original = ['logger', 'synlog'];

assert.deepEqual(addFrontMatterListItem(original, ' appconfig '), ['logger', 'synlog', 'appconfig']);
assert.deepEqual(addFrontMatterListItem(original, 'logger'), ['logger', 'synlog']);
assert.deepEqual(addFrontMatterListItems(original, [' appconfig ']), ['logger', 'synlog', 'appconfig']);
assert.deepEqual(addFrontMatterListItems(original, ['logger']), ['logger', 'synlog']);
assert.deepEqual(addFrontMatterListItems(original, [' ', 'synlog', 'codesite, onoff']), ['logger', 'synlog', 'codesite', 'onoff']);
assert.deepEqual(updateFrontMatterListItem(original, 1, ' syslog '), ['logger', 'syslog']);
assert.deepEqual(updateFrontMatterListItem(original, 1, 'logger'), ['logger', 'synlog']);
Expand Down
8 changes: 4 additions & 4 deletions scripts/keymapHarness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export type Chord = string;
*/
export const OperatingSystem = { Windows: 1, Macintosh: 2, Linux: 3 } as const;

export type OperatingSystemValue = (typeof OperatingSystem)[keyof typeof OperatingSystem];
type OperatingSystemValue = (typeof OperatingSystem)[keyof typeof OperatingSystem];

const MODIFIER_ORDER = ['Ctrl', 'Shift', 'Alt', 'Meta'] as const;

Expand All @@ -66,7 +66,7 @@ export function label(parts: {
}

/** A Monaco keybinding number, as the chord (or chord sequence) Monaco resolves it to. */
export function chordOf(binding: number, os: OperatingSystemValue): Chord {
function chordOf(binding: number, os: OperatingSystemValue): Chord {
const decoded = decodeKeybinding(binding, os);
assert.ok(decoded, `Monaco could not decode keybinding ${binding}`);
return decoded.chords.map((chord) => label(chord as KeyCodeChord)).join(' ');
Expand All @@ -80,7 +80,7 @@ export const PLATFORMS = [

// ------------------------------------------------- the editor (Monaco) layer

export type ActionDescriptor = {
type ActionDescriptor = {
id: string;
label: string;
keybindings?: number[];
Expand Down Expand Up @@ -180,7 +180,7 @@ export function editorKeymap(mac: boolean, os: OperatingSystemValue): Map<string
* `key` is the unshifted character and `code` the physical key, which is what a
* US layout reports and what every layout reports for letters and digits.
*/
export const FUZZ_KEYS: Array<{ keyCode: number; key: string; code: string }> = [
const FUZZ_KEYS: Array<{ keyCode: number; key: string; code: string }> = [
...'abcdefghijklmnopqrstuvwxyz'.split('').map((c) => ({
keyCode: KeyCode.KeyA + (c.charCodeAt(0) - 97),
key: c,
Expand Down
8 changes: 4 additions & 4 deletions scripts/renderProtocolDom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ function escapeAttribute(value: string): string {

export const NODE_ELEMENT = 1;
export const NODE_TEXT = 3;
export const NODE_COMMENT = 8;
const NODE_COMMENT = 8;

export class ShimNode {
nodeType: number;
Expand Down Expand Up @@ -213,7 +213,7 @@ export class ShimText extends ShimNode {
}
}

export class ShimComment extends ShimNode {
class ShimComment extends ShimNode {
nodeValue: string;

constructor(value: string) {
Expand Down Expand Up @@ -411,7 +411,7 @@ export class ShimElement extends ShimNode {
}
}

export class ShimDocument extends ShimNode {
class ShimDocument extends ShimNode {
documentElement: ShimElement;
body: ShimElement;

Expand Down Expand Up @@ -537,7 +537,7 @@ function parseFragment(html: string, doc: ShimDocument | null): ShimNode[] {
});
}

export class ShimDOMParser {
class ShimDOMParser {
parseFromString(html: string, _type: string): ShimDocument {
const doc = new ShimDocument();
parseInto(html, doc.body, doc);
Expand Down
4 changes: 1 addition & 3 deletions scripts/renderProtocolFixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
* here a lie.
*/

export type RenderFixture = {
type RenderFixture = {
/** Markdown handed to `convert_markdown`. */
markdown: string;
/** Exactly what `convert_markdown` returned. */
Expand Down Expand Up @@ -150,5 +150,3 @@ export const renderFixtures = {
html: "<p data-sourcepos=\"1:1-1:50\">A paragraph with <input type=\"checkbox\" /> inline.</p>\n",
},
} as const satisfies Record<string, RenderFixture>;

export type RenderFixtureName = keyof typeof renderFixtures;
2 changes: 1 addition & 1 deletion scripts/scrollSyncBlockMapping.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ function isAnnotated(element: ShimElement): boolean {
return elementChildren(element).some(isAnnotated);
}

export type Box = { top: number; height: number };
type Box = { top: number; height: number };

/**
* Assign every annotated block a top and a height, laying children out in
Expand Down
24 changes: 12 additions & 12 deletions scripts/windowTagEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ function handlerSourceIn(text: string, node: Node, name: string): string {
return soleExpression(text, attribute?.value) ?? '(() => {})';
}

export function handlerSource(node: Node, name: string): string {
function handlerSource(node: Node, name: string): string {
return handlerSourceIn(source, node, name);
}

Expand All @@ -198,7 +198,7 @@ export function handlerSource(node: Node, name: string): string {
* on its own claim ("the strip carried no scope line while a tag was set")
* rather than on a missing attribute name.
*/
export function directiveSource(node: Node, type: 'ClassDirective' | 'StyleDirective', name: string): string {
function directiveSource(node: Node, type: 'ClassDirective' | 'StyleDirective', name: string): string {
const directive = node.attributes?.find((a: Node) => a.type === type && a.name === name);
if (!directive) return type === 'ClassDirective' ? 'false' : 'undefined';
if (type === 'ClassDirective') return stripTypes(source.slice(directive.expression.start, directive.expression.end));
Expand All @@ -210,7 +210,7 @@ export function directiveSource(node: Node, type: 'ClassDirective' | 'StyleDirec
}

/** The one `<name>` element under `root`. */
export function elementByTag(root: Node, name: string): Node {
function elementByTag(root: Node, name: string): Node {
const found: Node[] = [];
collect(root, (node) => {
if (node.type === 'RegularElement' && node.name === name) found.push(node);
Expand All @@ -225,7 +225,7 @@ export function elementByTag(root: Node, name: string): Node {
* nothing when it is clicked and renders under no condition, which is what the
* stand-in models. Same reasoning as `handlerSourceIn`.
*/
export function elementByHandler(root: Node, marker: string): Node {
function elementByHandler(root: Node, marker: string): Node {
const found: Node[] = [];
collect(root, (node) => {
if (node.type !== 'RegularElement' || node === root) return;
Expand All @@ -240,7 +240,7 @@ export function elementByHandler(root: Node, marker: string): Node {
* element to be on screen at all — or `false` for an element that is not in the
* markup.
*/
export function enclosingIfTest(node: Node): string {
function enclosingIfTest(node: Node): string {
if (typeof node.start !== 'number') return 'false';
let innermost: Node | null = null;
collect(fragment, (candidate) => {
Expand All @@ -253,14 +253,14 @@ export function enclosingIfTest(node: Node): string {
}

/** The one `{…}` an element renders as its whole content, if that is all it has. */
export function textExpression(node: Node): string {
function textExpression(node: Node): string {
const tags = (node.fragment?.nodes ?? []).filter((child: Node) => child.type === 'ExpressionTag');
if (tags.length !== 1) return "''";
return stripTypes(source.slice(tags[0].expression.start, tags[0].expression.end));
}

/** The initialiser of a top-level `const`, evaluated. */
export function constantValue(name: string): unknown {
function constantValue(name: string): unknown {
const found: Node[] = [];
collect(parse(source, { modern: true, filename: TITLE_BAR }).instance, (node) => {
if (node.type === 'VariableDeclarator' && node.id?.type === 'Identifier' && node.id.name === name && node.init) {
Expand Down Expand Up @@ -295,20 +295,20 @@ const dismissEffectBody = (() => {

// ------------------------------------------------------------------- harness

export type WindowStub = {
type WindowStub = {
listeners: Map<string, () => void>;
addEventListener: (type: string, fn: () => void) => void;
removeEventListener: (type: string, fn: () => void) => void;
};

export type ClickHandler = (event: { stopPropagation: () => void }) => void;
type ClickHandler = (event: { stopPropagation: () => void }) => void;

export type MouseEventStub = {
type MouseEventStub = {
preventDefault: () => void;
stopPropagation: () => void;
};

export type InvokeCall = { cmd: string; args: any };
type InvokeCall = { cmd: string; args: any };

export type TitleBar = {
state: () => {
Expand Down Expand Up @@ -402,7 +402,7 @@ function createTitleBar(windowStub: WindowStub, invoke: (cmd: string, args: any)

export const COLORS = constantValue('tagColors') as string[];

export type SetupOptions = {
type SetupOptions = {
/** Answers `is_window_tag_taken`; every other command resolves to null. */
tagTakenElsewhere?: boolean | (() => boolean | Promise<boolean>);
/** Fails every `invoke`, to drive the "backend cannot answer" path. */
Expand Down
137 changes: 1 addition & 136 deletions src/lib/MarkdownViewer.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -697,119 +697,6 @@ import { createDocumentSession, type LoadMarkdownOptions } from './sessions/docu
}
});

function processHighlights(root: Element) {
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
acceptNode(node) {
let curr = node.parentElement;
while (curr && curr !== root) {
if (['CODE', 'PRE', 'SCRIPT', 'STYLE'].includes(curr.tagName)) return NodeFilter.FILTER_REJECT;
curr = curr.parentElement;
}
return NodeFilter.FILTER_ACCEPT;
},
});

const toReplace: { node: Text; replaced: string }[] = [];
let node: Node | null;
while ((node = walker.nextNode())) {
const text = (node as Text).nodeValue || '';
if (text.includes('==')) {
const replaced = text.replace(/==([^=\n]+)==/g, '<mark>$1</mark>');
if (replaced !== text) toReplace.push({ node: node as Text, replaced });
}
}
for (const { node, replaced } of toReplace) {
const span = root.ownerDocument!.createElement('span');
span.innerHTML = replaced;
node.parentNode?.replaceChild(span, node);
}
}

function processBlockIds(root: Element, doc: Document) {
// handle pre-emitted block-id spans from rust parser
for (const el of Array.from(root.querySelectorAll('.block-id, [data-block-id]'))) {
const rawId = el.getAttribute('data-block-id') || (el as HTMLElement).textContent?.replace(/^\^/, '').trim() || '';
if (!rawId) continue;
const anchor = doc.createElement('a');
anchor.id = rawId;
anchor.className = 'block-id-anchor';
anchor.setAttribute('data-label', rawId);
anchor.setAttribute('aria-hidden', 'true');
el.replaceWith(anchor);
}

// scan text nodes for trailing ^id pattern (text ^blockid at end of block)
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
acceptNode(node) {
const parent = node.parentElement;
if (!parent) return NodeFilter.FILTER_REJECT;
if (['CODE', 'PRE', 'SCRIPT', 'STYLE', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6'].includes(parent.tagName)) return NodeFilter.FILTER_REJECT;
return NodeFilter.FILTER_ACCEPT;
},
});

const blockIdPattern = / \^([a-zA-Z0-9_-]+)\s*$/;
const nodes: { node: Text; id: string }[] = [];
let textNode: Node | null;
while ((textNode = walker.nextNode())) {
const text = (textNode as Text).nodeValue || '';
const match = text.match(blockIdPattern);
if (match) nodes.push({ node: textNode as Text, id: match[1] });
}

for (const { node, id } of nodes) {
const text = node.nodeValue || '';
const cleanText = text.replace(blockIdPattern, '');
const anchor = doc.createElement('a');
anchor.id = id;
anchor.className = 'block-id-anchor';
anchor.setAttribute('data-label', id);
anchor.setAttribute('aria-hidden', 'true');
const parent = node.parentNode;
if (parent) {
const textBefore = doc.createTextNode(cleanText);
parent.replaceChild(anchor, node);
parent.insertBefore(textBefore, anchor);
}
}
}

function processTaskItems(root: Element) {
for (const input of Array.from(root.querySelectorAll('li input[type="checkbox"]'))) {
input.setAttribute('data-task-checkbox', '');
input.removeAttribute('disabled');
(input as HTMLInputElement).style.cursor = 'pointer';

const li = input.closest('li');
if (!li) continue;

// wrap bare text/inline nodes after checkbox in a span for CSS targeting
const nodes = Array.from(li.childNodes);
const inputIdx = nodes.indexOf(input);
const afterInput = nodes.slice(inputIdx + 1);

// we loop until we hit a block child (like a nested UL)
const inlineNodes = [];
for (const n of afterInput) {
if (n.nodeType === 1 && ['P', 'DIV', 'UL', 'OL'].includes((n as Element).tagName)) break;
inlineNodes.push(n);
}

if (inlineNodes.length > 0) {
const wrapper = root.ownerDocument!.createElement('span');
wrapper.className = 'task-text';
for (const n of inlineNodes) wrapper.appendChild(n);

// insert the newly wrapped span after the checkbox
li.insertBefore(wrapper, afterInput[inlineNodes.length] || null);
}

if ((input as HTMLInputElement).checked) {
li.classList.add('task-done');
}
}
}

// The preview and the export run the same filter in opposite orders, on
// purpose. The export sanitizes the renderer output first and processes
// afterwards, because the bytes it writes are read by another program and
Expand Down Expand Up @@ -2790,28 +2677,6 @@ import { createDocumentSession, type LoadMarkdownOptions } from './sessions/docu
window.addEventListener('pointercancel', onUp);
}

function getSplitTransition(node: Element, { isEditing, side }: { isEditing: boolean; side: 'left' | 'right' }) {
let shouldAnimate = false;
let x = 0;

if (side === 'left') {
if (!isEditing) {
shouldAnimate = true;
x = -50;
}
} else {
if (isEditing) {
shouldAnimate = true;
x = 50;
}
}

if (shouldAnimate) {
return fly(node, { x, duration: 250 });
}
return { duration: 0 };
}

onMount(() => {
loadRecentFiles();
isDisposed = false;
Expand Down Expand Up @@ -3452,7 +3317,7 @@ import { createDocumentSession, type LoadMarkdownOptions } from './sessions/docu
<input
id={frontMatterFieldId(field.key)}
type={field.kind === 'number' ? 'number' : 'text'}
value={field.editableValue}
value={field.displayValue}
onchange={(e) => handleFrontMatterEdit(field, (e.currentTarget as HTMLInputElement).value)} />
{/if}
{:else}
Expand Down
2 changes: 1 addition & 1 deletion src/lib/sessions/documentSession.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export type LoadMarkdownOptions = {
* `conflict` means the owning tab has unsaved edits, so the choice belongs
* to the user rather than to a background reload.
*/
export type ExternalChangeOutcome =
type ExternalChangeOutcome =
| { action: 'ignore' }
| { action: 'reload'; tabId: string; path: string }
| { action: 'conflict'; tabId: string; path: string };
Expand Down
2 changes: 1 addition & 1 deletion src/lib/utils/editorToolbar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export type EditorToolbarTool = {
group: EditorToolbarGroup;
};

export type EditorToolbarMove = {
type EditorToolbarMove = {
fromIndex: number;
toIndex: number;
};
Expand Down
Loading