feat: run prettier correctly (#1893)

This commit is contained in:
Myriad-Dreamin 2025-07-09 19:34:57 +08:00 committed by GitHub
parent 0849d6c641
commit 9bb784e6f0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 351 additions and 566 deletions

View file

@ -8,6 +8,7 @@ target/**
dist/**
icons/
node_modules/
editors/vscode/test-dist/
editors/vscode/out/
editors/vscode/.vscode-test/**
*.toml

View file

@ -1,15 +1,15 @@
import { readFileSync } from 'fs';
import { readFileSync } from "fs";
function check() {
const cargoToml = readFileSync('../../Cargo.toml', 'utf8');
const cargoVersion = cargoToml.match(/version = "(.*?)"/)[1];
const pkgVersion = JSON.parse(readFileSync('package.json', 'utf8')).version;
const cargoToml = readFileSync("../../Cargo.toml", "utf8");
const cargoVersion = cargoToml.match(/version = "(.*?)"/)[1];
const pkgVersion = JSON.parse(readFileSync("package.json", "utf8")).version;
if (cargoVersion !== pkgVersion) {
throw new Error(`Version mismatch: ${cargoVersion} (in Cargo.toml) !== ${pkgVersion} (in package.json)`);
}
if (cargoVersion !== pkgVersion) {
throw new Error(
`Version mismatch: ${cargoVersion} (in Cargo.toml) !== ${pkgVersion} (in package.json)`,
);
}
}
check();

View file

@ -1,28 +1,24 @@
const path = require("path");
const fs = require("fs");
const rimraf = require("rimraf");
const path = require('path');
const fs = require('fs');
const rimraf = require('rimraf');
const vscodeDir = path.join(__dirname, "../");
const editorToolsDir = path.join(vscodeDir, "../../tools/editor-tools/");
const vscodeDir = path.join(__dirname, '../');
const editorToolsDir = path.join(vscodeDir, '../../tools/editor-tools/');
rimraf.sync(path.join(vscodeDir, 'out/editor-tools/'));
fs.mkdirSync(path.join(vscodeDir, 'out/editor-tools/'), { recursive: true });
rimraf.sync(path.join(vscodeDir, "out/editor-tools/"));
fs.mkdirSync(path.join(vscodeDir, "out/editor-tools/"), { recursive: true });
function copyDir(src, dest) {
fs.readdirSync(src).forEach((item) => {
const srcPath = path.join(src, item);
const destPath = path.join(dest, item);
if (fs.lstatSync(srcPath).isDirectory()) {
fs.mkdirSync(destPath,
{ recursive: true });
copyDir(srcPath, destPath);
fs.mkdirSync(destPath, { recursive: true });
copyDir(srcPath, destPath);
} else {
fs.copyFileSync(srcPath, destPath);
}
else {
fs.copyFileSync(srcPath, destPath);
}
});
});
}
copyDir(path.join(editorToolsDir, "dist"), path.join(vscodeDir, 'out/editor-tools/'));
copyDir(path.join(editorToolsDir, "dist"), path.join(vscodeDir, "out/editor-tools/"));

View file

@ -681,4 +681,4 @@ export const revealDocumentCompat = async (args: any) => {
export const ejectPreviewPanelCompat = async () => {
vscode.window.showWarningMessage("Eject is not supported in compat mode");
}
};

View file

@ -14,7 +14,7 @@
"tools/typst-preview-frontend"
],
"scripts": {
"fmt": "cargo fmt --all && npx prettier tools/**/*.{js,mjs,cjs,ts,mts,cts} editors/vscode/**/*.{js,mjs,cjs,ts,mts,cts}",
"fmt": "cargo fmt --all && npx prettier --write tools/**/*.{js,mjs,cjs,ts,mts,cts} editors/vscode/**/*.{js,mjs,cjs,ts,mts,cts}",
"fmt:check": "cargo fmt --check --all && prettier --check tools/**/*.{js,mjs,cjs,ts,mts,cts} editors/vscode/**/*.{js,mjs,cjs,ts,mts,cts}",
"build:editor-tools": "cd tools/editor-tools/ && yarn run build",
"build:preview": "cd tools/typst-preview-frontend && yarn run build && rimraf ../../crates/tinymist-assets/src/typst-preview.html && cpr ./dist/index.html ../../crates/tinymist-assets/src/typst-preview.html",

View file

@ -37,7 +37,7 @@ export function startModal(...contents: Node[]) {
{
class: "tinymist-button",
},
"Close"
"Close",
);
const keydownHandler = (e: KeyboardEvent) => {
if (e.key === "Escape" || e.key === " " || e.key === "Enter") {
@ -58,15 +58,12 @@ export function startModal(...contents: Node[]) {
{
class: "tinymist-button",
style: "margin-left: 0.5em",
title:
"Click the close button or press esc/space/enter to close this window",
title: "Click the close button or press esc/space/enter to close this window",
},
"Help"
"Help",
);
help.onclick = () => {
alert(
"Click the close button or press esc/space/enter to close this window"
);
alert("Click the close button or press esc/space/enter to close this window");
};
floatingWindow.appendChild(help);

View file

@ -22,8 +22,8 @@ export const Diagnostics = () => {
`: error occurred in this call of function \`f\``,
br(),
a({ href: "javascript:void(0)" }, `test.typ(6, 2)`),
`: error occurred in this call of function \`g\``
)
`: error occurred in this call of function \`g\``,
),
),
div(
{ class: `tinymist-card`, style: "flex: 1; width: 100%; padding: 10px" },
@ -44,7 +44,7 @@ export const Diagnostics = () => {
},
`#let f(x, y) = `,
span({ style: "text-decoration: underline" }, `x + y`),
`;`
`;`,
),
br(),
"where ",
@ -61,7 +61,7 @@ export const Diagnostics = () => {
"2nd",
" function parameter of ",
code(a({ href: "javascript:void(0)" }, `f`)),
"."
".",
),
div(
{
@ -82,7 +82,7 @@ export const Diagnostics = () => {
},
`#let g(x, y, z) = `,
span({ style: "text-decoration: underline" }, `f(x, y)`),
` + z;`
` + z;`,
),
br(),
"where ",
@ -99,7 +99,7 @@ export const Diagnostics = () => {
"2nd",
" function parameter of ",
code(a({ href: "javascript:void(0)" }, `g`)),
"."
".",
),
div(
{
@ -120,9 +120,9 @@ export const Diagnostics = () => {
},
`#`,
span({ style: "text-decoration: underline" }, `g(1, left, red)`),
`;`
)
)
)
`;`,
),
),
),
);
};

View file

@ -40,8 +40,8 @@ export const Docs = () => {
const v = parsedDocs.val;
// console.log("updated", v);
return div(MakeDoc(v));
}
)
},
),
);
};
@ -89,9 +89,7 @@ async function recoverDocsStructure(content: string) {
let match;
let lastIndex = 0;
while ((match = reg.exec(content))) {
tokenPromises.push(
Promise.resolve([TokenKind.Text, content.slice(lastIndex, match.index)])
);
tokenPromises.push(Promise.resolve([TokenKind.Text, content.slice(lastIndex, match.index)]));
tokenPromises.push(identifyCommentToken(match[1]));
lastIndex = reg.lastIndex;
}
@ -157,8 +155,7 @@ async function recoverDocsStructure(content: string) {
};
if (sym) {
current.id = `${sym.id}-param-${token[1]}`;
const renderedParams = (sym.data.renderedParams =
sym.data.renderedParams || {});
const renderedParams = (sym.data.renderedParams = sym.data.renderedParams || {});
renderedParams[current.id] = current;
}
break;
@ -240,19 +237,11 @@ async function identifyCommentToken(comment: string) {
case "end:errors":
return [TokenKind.ErrorEnd, cs[1]];
case "begin:module":
return [
TokenKind.ModuleStart,
cs[1],
JSON.parse(await base64ToUtf8(cs[2])),
];
return [TokenKind.ModuleStart, cs[1], JSON.parse(await base64ToUtf8(cs[2]))];
case "end:module":
return [TokenKind.ModuleEnd, cs[1]];
case "begin:symbol":
return [
TokenKind.SymbolStart,
cs[1],
JSON.parse(await base64ToUtf8(cs[2])),
];
return [TokenKind.SymbolStart, cs[1], JSON.parse(await base64ToUtf8(cs[2]))];
case "end:symbol":
return [TokenKind.SymbolEnd, cs[1]];
case "begin:sig":
@ -312,7 +301,7 @@ function MakeDoc(root: DocElement) {
(e) =>
e.namespace === child.data.namespace &&
e.name === child.data.name &&
e.version === child.data.version
e.version === child.data.version,
);
return;
}
@ -494,9 +483,9 @@ function MakeDoc(root: DocElement) {
style: "text-decoration: underline",
title: `It is inaccessible by paths`,
},
"Module"
"Module",
),
code(" ", knownFiles[fileLoc]?.path || v.id)
code(" ", knownFiles[fileLoc]?.path || v.id),
);
} else {
title.push(span(`Module: ${v.id}`));
@ -505,7 +494,7 @@ function MakeDoc(root: DocElement) {
return div(
{ class: "tinymist-module" },
h1({ id: v.id }, ...(fid ? [span({ id: fid }, ...title)] : title)),
ModuleBody(v)
ModuleBody(v),
);
}
@ -514,19 +503,14 @@ function MakeDoc(root: DocElement) {
return div(
h1(`@${v.data.namespace}/${v.data.name}:${v.data.version}`),
p(
span(
"This documentation is generated locally. Please submit issues to "
),
a(
{ href: "https://github.com/Myriad-Dreamin/tinymist/issues" },
"tinymist"
),
span("This documentation is generated locally. Please submit issues to "),
a({ href: "https://github.com/Myriad-Dreamin/tinymist/issues" }, "tinymist"),
span(" if you see "),
strong(i("incorrect")),
span(" information in it.")
span(" information in it."),
),
// ModuleBody(v)
...v.children.map(Item)
...v.children.map(Item),
);
}
@ -546,16 +530,16 @@ function MakeDoc(root: DocElement) {
style: "text-decoration: underline",
title: `In external package @${extPkg.namespace}/${extPkg.name}:${extPkg.version}`,
},
"external"
"external",
)
: span(
{
style: "text-decoration: underline",
title: `In local package @${extPkg.namespace}/${extPkg.name}:${extPkg.version}`,
},
"external"
"external",
),
code(" ", v.data.name)
code(" ", v.data.name),
);
} else {
const file = knownFiles[fileLoc?.[0]];
@ -567,9 +551,9 @@ function MakeDoc(root: DocElement) {
style: "text-decoration: underline",
title: `This module is inaccessible by paths`,
},
"internal"
"internal",
),
code(" ")
code(" "),
)
: code();
@ -579,8 +563,8 @@ function MakeDoc(root: DocElement) {
{
href: `#${fid}`,
},
v.data.name
)
v.data.name,
),
);
}
@ -598,8 +582,8 @@ function MakeDoc(root: DocElement) {
// <span class="sr-only">View Source</span>
// </a>
},
h3({ class: "doc-symbol-name" }, body)
)
h3({ class: "doc-symbol-name" }, body),
),
);
}
@ -618,7 +602,7 @@ function MakeDoc(root: DocElement) {
{
id: v.id,
},
code(v.data.name)
code(v.data.name),
);
let funcTitle = [...is_external, name];
if (sig) {
@ -652,10 +636,10 @@ function MakeDoc(root: DocElement) {
{
class: `detail-header doc-symbol-${v.data.kind}`,
},
h3({ class: "doc-symbol-name" }, code(...funcTitle))
h3({ class: "doc-symbol-name" }, code(...funcTitle)),
),
...SigPreview(v),
...(v.data.is_external ? ShortItemDoc(v) : [ItemDoc(v), ...SigDocs(v)])
...(v.data.is_external ? ShortItemDoc(v) : [ItemDoc(v), ...SigDocs(v)]),
);
}
@ -728,10 +712,10 @@ function MakeDoc(root: DocElement) {
{
class: "doc-param-title",
},
strong(paramTitle)
)
)
)
strong(paramTitle),
),
),
),
);
}
@ -751,7 +735,7 @@ function MakeDoc(root: DocElement) {
{
id: `param-${v.id}-${param.name}`,
},
param.name
param.name,
),
];
if (param.cano_type) {
@ -782,13 +766,13 @@ function MakeDoc(root: DocElement) {
{
class: "doc-param-title",
},
strong(code(paramTitle))
strong(code(paramTitle)),
),
div({
style: "margin-left: 0.62em",
innerHTML: docsAll ? docsAll : "<p>-</p>",
})
)
}),
),
);
}
@ -826,19 +810,13 @@ function MakeDoc(root: DocElement) {
href: v.data.external_link,
title: "this symbol is re-exported from other modules",
},
kwHl("external")
kwHl("external"),
),
code(" "),
]
: [];
const sigTitle = [
...is_external,
kwHl("let"),
code(" "),
code(fnHl(v.data.name)),
code("("),
];
const sigTitle = [...is_external, kwHl("let"), code(" "), code(fnHl(v.data.name)), code("(")];
for (let i = 0; i < paramsAll.length; i++) {
if (i > 0) {
sigTitle.push(code(", "));
@ -856,8 +834,8 @@ function MakeDoc(root: DocElement) {
{
href: `#param-${v.id}-${paramsAll[i].param.name}`,
},
...paramTitle
)
...paramTitle,
),
);
}
sigTitle.push(code(")"));
@ -877,9 +855,9 @@ function MakeDoc(root: DocElement) {
{
style: "margin: 0 1em",
},
code(...sigTitle)
)
)
code(...sigTitle),
),
),
);
return res;
@ -896,26 +874,23 @@ function MakeDoc(root: DocElement) {
},
h3(
{ class: "doc-symbol-name" },
code(`${v.data.name}`)
code(`${v.data.name}`),
// code(
// {
// style: "float: right; line-height: 1em",
// },
// `${v.data.kind}`
// )
)
),
),
ItemDoc(v)
ItemDoc(v),
);
}
return Item(root);
}
function sigTypeHighlighted(
inferred: [string, string] | undefined,
target: ChildDom[]
) {
function sigTypeHighlighted(inferred: [string, string] | undefined, target: ChildDom[]) {
// todo: determine whether it is inferred
// if (types) {
// typeHighlighted(types, target);
@ -925,24 +900,20 @@ function sigTypeHighlighted(
typeHighlighted(inferred[0], rendered, "|");
const infer = span(
{ class: "code-kw type-inferred", title: "inferred by type checker" },
"infer"
"infer",
);
target.push(
code(
{ class: "type-inferred" },
infer,
code(" "),
span({ class: "type-inferred-as", title: inferred[1] }, ...rendered)
)
span({ class: "type-inferred-as", title: inferred[1] }, ...rendered),
),
);
}
}
function typeHighlighted(
types: string,
target: ChildDom[],
by: RegExp | string = /[|,]/g
) {
function typeHighlighted(types: string, target: ChildDom[], by: RegExp | string = /[|,]/g) {
const type = types.split(by);
for (let i = 0; i < type.length; i++) {
if (i > 0) {

View file

@ -15,17 +15,13 @@ export const FontView = () => {
const FontResourcesData = `:[[preview:FontInformation]]:`;
const fontResources = van.state<FontResources>(
FontResourcesData.startsWith(":")
? DOC_MOCK
: JSON.parse(base64Decode(FontResourcesData))
FontResourcesData.startsWith(":") ? DOC_MOCK : JSON.parse(base64Decode(FontResourcesData)),
);
console.log("fontResources", fontResources);
const StyleAtCursorData = `:[[preview:StyleAtCursor]]:`;
const lastStylesAtCursor = van.state<any>(
StyleAtCursorData.startsWith(":")
? undefined
: JSON.parse(base64Decode(StyleAtCursorData))
StyleAtCursorData.startsWith(":") ? undefined : JSON.parse(base64Decode(StyleAtCursorData)),
);
console.log("styleAtCursorBase", lastStylesAtCursor);
van.derive(() => {
@ -72,12 +68,11 @@ export const FontView = () => {
icon: ChildDom,
title: string,
onclick: (this: HTMLDivElement) => void,
opts?: PropsWithKnownKeys<HTMLButtonElement> & { active?: State<boolean> }
opts?: PropsWithKnownKeys<HTMLButtonElement> & { active?: State<boolean> },
) => {
const classProp = opts?.active
? van.derive(
() =>
`tinymist-button tinymist-font-action${opts?.active?.val ? " activated" : ""}`
() => `tinymist-button tinymist-font-action${opts?.active?.val ? " activated" : ""}`,
)
: "tinymist-button tinymist-font-action";
@ -89,7 +84,7 @@ export const FontView = () => {
title,
onclick,
},
icon
icon,
);
};
@ -106,17 +101,15 @@ export const FontView = () => {
const machineTitle = `Weight ${font.weight || 400}, Stretch ${font.stretch || 1000}, at `;
const baseName = code(
font.style === "normal" || !font.style
? ""
: `${humanStyle(font.style)}, `,
font.style === "normal" || !font.style ? "" : `${humanStyle(font.style)}, `,
(_dom?: Element) => {
return span(
humanWeight(font.weight, showNumberOpt.val),
showNumber.val ? ", " : " ",
humanStretch(font.stretch, showNumberOpt.val)
humanStretch(font.stretch, showNumberOpt.val),
);
},
` (${fileName})`
` (${fileName})`,
);
let variantName;
@ -127,8 +120,7 @@ export const FontView = () => {
title = machineTitle + w.path;
variantName = a(
{
style:
"font-size: 1.2em; text-decoration: underline; cursor: pointer;",
style: "font-size: 1.2em; text-decoration: underline; cursor: pointer;",
title,
onclick() {
if (w.kind === "fs") {
@ -136,7 +128,7 @@ export const FontView = () => {
}
},
},
baseName
baseName,
);
} else {
title = machineTitle + `Embedded: ${w.name}`;
@ -145,7 +137,7 @@ export const FontView = () => {
style: "font-size: 1.2em",
title,
},
baseName
baseName,
);
}
} else {
@ -184,56 +176,44 @@ export const FontView = () => {
div(
{ style: "margin: 1.2em; margin-left: 0.5em" },
div(
FontAction(
"Copy",
"Copy to clipboard",
function (this: HTMLDivElement) {
activeMe(this);
copyToClipboard(`"${family.name || ""}"`);
}
),
FontAction("Copy", "Copy to clipboard", function (this: HTMLDivElement) {
activeMe(this);
copyToClipboard(`"${family.name || ""}"`);
}),
" | ",
FontAction(
"Paste string",
"Paste as String",
function (this: HTMLDivElement) {
activeMe(this);
const rest = name;
const markup = `#${rest}`;
requestTextEdit({
newText: {
kind: "by-mode",
markup,
rest,
},
});
}
),
FontAction("Paste string", "Paste as String", function (this: HTMLDivElement) {
activeMe(this);
const rest = name;
const markup = `#${rest}`;
requestTextEdit({
newText: {
kind: "by-mode",
markup,
rest,
},
});
}),
" ",
FontAction(
"#set",
"Paste as Set Font Rule",
function (this: HTMLDivElement) {
activeMe(this);
const rest = name;
const markup = `#set text(font: ${rest})`;
requestTextEdit({
newText: {
kind: "by-mode",
markup,
rest,
},
});
}
)
FontAction("#set", "Paste as Set Font Rule", function (this: HTMLDivElement) {
activeMe(this);
const rest = name;
const markup = `#set text(font: ${rest})`;
requestTextEdit({
newText: {
kind: "by-mode",
markup,
rest,
},
});
}),
),
span({ style: "font-size: 1.2em" }, family.name),
".",
br(),
code("Variant"),
": ",
family.infos.map(FontSlot)
)
family.infos.map(FontSlot),
),
);
};
@ -245,7 +225,7 @@ export const FontView = () => {
code(fontAtCursor),
br(),
"Checked at ",
code(fontPosition)
code(fontPosition),
);
};
@ -257,8 +237,7 @@ export const FontView = () => {
div(
{
class: "flex-col",
style:
"justify-content: center; align-items: center; gap: 10px; width: 100%;",
style: "justify-content: center; align-items: center; gap: 10px; width: 100%;",
},
div(
{
@ -270,24 +249,22 @@ export const FontView = () => {
() => {
showNumber.val = !showNumber.val;
},
{ active: showNumber }
)
{ active: showNumber },
),
),
div(
{
class: `tinymist-card`,
style: "flex: 1; width: 100%; padding: 10px; display: none",
},
(_dom?: Element) => SelectingSlot()
(_dom?: Element) => SelectingSlot(),
),
...fontResources.val.families.map(FontFamilySlot)
)
...fontResources.val.families.map(FontFamilySlot),
),
);
};
export type fontLocation = FontSource extends { kind: infer Kind }
? Kind
: never;
export type fontLocation = FontSource extends { kind: infer Kind } ? Kind : never;
interface FontInfo {
name: string;

View file

@ -21,8 +21,7 @@ import { base64Decode } from "../utils";
// };
ortEnv.wasm.numThreads = 4;
ortEnv.wasm.wasmPaths =
"https://cdn.jsdelivr.net/npm/onnxruntime-web@1.17.1/dist/";
ortEnv.wasm.wasmPaths = "https://cdn.jsdelivr.net/npm/onnxruntime-web@1.17.1/dist/";
type Point = [number, number];
type Stroke = Point[];
@ -85,7 +84,7 @@ const SYMBOL_MOCK: SymbolInformation = {
const SearchBar = (
state: State<SymbolInformation>,
symbolSelected: State<SelectedSymbolItem[] | undefined>
symbolSelected: State<SelectedSymbolItem[] | undefined>,
) => {
const def = MiniSearch.getDefault("tokenize");
const search = van.derive(() => {
@ -233,18 +232,15 @@ const CanvasPanel = (strokesState: State<Stroke[] | undefined>) => {
startModal(
p(
"The ",
span(
{ style: "font-weight: bold; text-decoration: underline" },
"offline"
),
span({ style: "font-weight: bold; text-decoration: underline" }, "offline"),
" handwritten stroke recognizer is powered by ",
a(
{
href: "https://github.com/QuarticCat/detypify",
},
"Detypify"
"Detypify",
),
". Draw a symbol to search for it."
". Draw a symbol to search for it.",
),
h4("Cannot find some symbols?"),
p(
@ -253,9 +249,9 @@ const CanvasPanel = (strokesState: State<Stroke[] | undefined>) => {
{
href: "https://github.com/QuarticCat/detypify/blob/main/assets/supported-symbols.txt",
},
"supported-symbols.txt"
"supported-symbols.txt",
),
"."
".",
),
p(
"❤️‍🔥: Click the ",
@ -267,9 +263,9 @@ const CanvasPanel = (strokesState: State<Stroke[] | undefined>) => {
{
href: "https://detypify.quarticcat.com/",
},
"Detypify"
"Detypify",
),
"."
".",
),
p(
"📝: Report the missing symbol to ",
@ -277,9 +273,9 @@ const CanvasPanel = (strokesState: State<Stroke[] | undefined>) => {
{
href: "https://github.com/QuarticCat/detypify/issues/new",
},
"GitHub Issues"
"GitHub Issues",
),
"."
".",
),
h4("Like it?"),
p(
@ -288,16 +284,16 @@ const CanvasPanel = (strokesState: State<Stroke[] | undefined>) => {
{
href: "https://github.com/QuarticCat/detypify",
},
"Detypify"
"Detypify",
),
"!"
)
"!",
),
);
},
},
HelpIcon()
HelpIcon(),
),
srcCanvas
srcCanvas,
),
button(
{
@ -305,8 +301,8 @@ const CanvasPanel = (strokesState: State<Stroke[] | undefined>) => {
title: "clear",
onclick: drawClear,
},
"Clear"
)
"Clear",
),
);
};
@ -441,16 +437,14 @@ const CATEGORY_INFO: SymbolCategory[] = [
},
];
// generate map from category value to category name
const categoryIndex = new Map(
CATEGORY_INFO.map((cat) => [cat.value, cat.name.toLowerCase()])
);
const categoryIndex = new Map(CATEGORY_INFO.map((cat) => [cat.value, cat.name.toLowerCase()]));
export const SymbolPicker = () => {
const symbolInformationData = `:[[preview:SymbolInformation]]:`;
const symInfo = van.state<SymbolInformation>(
symbolInformationData.startsWith(":")
? SYMBOL_MOCK
: JSON.parse(base64Decode(symbolInformationData))
: JSON.parse(base64Decode(symbolInformationData)),
);
console.log("symbolInformation", symInfo);
const detypifyPromise = Detypify.create();
@ -473,7 +467,7 @@ export const SymbolPicker = () => {
() =>
`<svg xmlns:xlink="http://www.w3.org/1999/xlink" width="0" height="0" viewBox="0 0 0 0" role="img" focusable="false" xmlns="http://www.w3.org/2000/svg" style="opacity: 0; position: absolute">
${symInfo.val.glyphDefs || ""}
</svg>`
</svg>`,
),
});
@ -494,10 +488,7 @@ export const SymbolPicker = () => {
};
const bboxXWidth = diff(primaryGlyph.xMin, primaryGlyph.xMax);
let xWidth = Math.max(
bboxXWidth,
primaryGlyph.xAdvance || fontSelected.unitsPerEm
);
let xWidth = Math.max(bboxXWidth, primaryGlyph.xAdvance || fontSelected.unitsPerEm);
let yReal = diff(primaryGlyph.yMin, primaryGlyph.yMax);
let yGlobal = primaryGlyph.yAdvance || fontSelected.unitsPerEm;
@ -527,7 +518,7 @@ export const SymbolPicker = () => {
// console.log(sym.typstCode, div({ innerHTML: imageData }));
maskInfo.setAttribute(
"style",
`width: ${symWidth}; height: ${symHeight}; -webkit-mask-image: url('data:image/svg+xml;utf8,${encodeURIComponent(imageData)}'); -webkit-mask-size: auto ${symHeight}; -webkit-mask-repeat: no-repeat; transition: background-color 200ms; background-color: currentColor;`
`width: ${symWidth}; height: ${symHeight}; -webkit-mask-image: url('data:image/svg+xml;utf8,${encodeURIComponent(imageData)}'); -webkit-mask-size: auto ${symHeight}; -webkit-mask-repeat: no-repeat; transition: background-color 200ms; background-color: currentColor;`,
);
}, 1);
}
@ -557,7 +548,7 @@ export const SymbolPicker = () => {
});
},
},
maskInfo
maskInfo,
);
};
@ -566,8 +557,8 @@ export const SymbolPicker = () => {
div({ style: "font-size: 14px; margin: 8px 0" }, cat.name),
div(
{ class: "flex-row", style: "flex-wrap: wrap; gap: 5px; width: 100%" },
...(cat.symbols || []).map((sym) => sym.elem)
)
...(cat.symbols || []).map((sym) => sym.elem),
),
);
};
@ -580,25 +571,21 @@ export const SymbolPicker = () => {
value,
elem: SymbolCell(value),
};
})
);
const filteredPickers = van.state<SelectedSymbolItem[] | undefined>(
undefined
}),
);
const filteredPickers = van.state<SelectedSymbolItem[] | undefined>(undefined);
function pickSymbolsBySearch(
pickers: { key: string; value: SymbolItem; elem: Element }[],
filteredPickers: SelectedSymbolItem[] | undefined
filteredPickers: SelectedSymbolItem[] | undefined,
) {
if (!filteredPickers) return pickers;
return pickers.filter((picker) =>
filteredPickers.some((f) => f.typstCode === picker.key)
);
return pickers.filter((picker) => filteredPickers.some((f) => f.typstCode === picker.key));
}
function pickSymbolsByDrawCandidates(
pickers: { key: string; value: SymbolItem; elem: Element }[],
drawCandidates: DetypifySymbol[] | undefined
drawCandidates: DetypifySymbol[] | undefined,
) {
if (drawCandidates === undefined) return pickers;
if (!drawCandidates.length) return [];
@ -623,7 +610,7 @@ export const SymbolPicker = () => {
style: "flex: 0 0 auto; gap: 5px",
},
SearchBar(symInfo, filteredPickers),
CanvasPanel(strokes)
CanvasPanel(strokes),
),
div({ style: "flex: 1;" }, (_dom?: Element) =>
div(
@ -631,27 +618,23 @@ export const SymbolPicker = () => {
CATEGORY_INFO,
pickSymbolsBySearch(
pickSymbolsByDrawCandidates(pickers.val, drawCandidates.val),
filteredPickers.val
)
filteredPickers.val,
),
)
.filter((cat) => cat.symbols?.length)
.map((info) => CategoryPicker(info))
)
)
.map((info) => CategoryPicker(info)),
),
),
);
};
function categorize(
catsRaw: SymbolCategory[],
symInfo: InstantiatedSymbolItem[]
symInfo: InstantiatedSymbolItem[],
): InstantiatedSymbolCategory[] {
let cats: InstantiatedSymbolCategory[] = [
...catsRaw.map((cat) => ({ ...cat })),
];
let cats: InstantiatedSymbolCategory[] = [...catsRaw.map((cat) => ({ ...cat }))];
// let misc
let misc: InstantiatedSymbolCategory = cats.find(
(cat) => cat.name === "Miscellaneous"
)!;
let misc: InstantiatedSymbolCategory = cats.find((cat) => cat.name === "Miscellaneous")!;
// misc.symbols = symInfo.val.symbols;
for (let sym of symInfo) {
const { key, value } = sym;

View file

@ -30,10 +30,7 @@ interface PackageMeta {
template: any;
}
const TemplateList = (
packages: State<PackageMeta[]>,
catState: FilterState
) => {
const TemplateList = (packages: State<PackageMeta[]>, catState: FilterState) => {
const AuthorItem = (author: string) => {
// split by <
const [nameStart, emailRest] = author.split("<");
@ -55,10 +52,7 @@ const TemplateList = (
const AuthorList = (authors: string[]) => {
if (authors.length <= 1) {
return span(
{ class: `tinymist-author-container` },
...authors.map(AuthorItem)
);
return span({ class: `tinymist-author-container` }, ...authors.map(AuthorItem));
}
return span(
@ -70,57 +64,51 @@ const TemplateList = (
style: "text-decoration: underline",
title: authors.slice(1).join(", "),
},
"et al."
)
"et al.",
),
);
};
const highlightMatches = (text: string, searchResults?: SearchResult[]): HTMLSpanElement => {
if (!searchResults || !text) return van.tags.span({}, text);
const searchTerms = searchResults.flatMap(result => result.queryTerms);
const regex = new RegExp(`(${searchTerms.join("|")})`, 'gi');
const searchTerms = searchResults.flatMap((result) => result.queryTerms);
const regex = new RegExp(`(${searchTerms.join("|")})`, "gi");
const parts = text.split(regex);
return van.tags.span({}, ...parts.map(part =>
regex.test(part)
? van.tags.span({ class: 'tinymist-highlight' }, part)
: part
));
}
return van.tags.span(
{},
...parts.map((part) =>
regex.test(part) ? van.tags.span({ class: "tinymist-highlight" }, part) : part,
),
);
};
const TemplateListItem = (item: PackageMeta) => {
const TemplateAction = (
icon: ChildDom,
title: string,
onclick: () => void
) =>
const TemplateAction = (icon: ChildDom, title: string, onclick: () => void) =>
button(
{
class: "tinymist-button tinymist-template-action",
title,
onclick,
},
icon
icon,
);
return Card(
"template-card",
div(
a({ href: item.repository, style: "font-size: 1.2em" },
() => {
return highlightMatches(item.name, catState.searchSelected.val);
}
),
a({ href: item.repository, style: "font-size: 1.2em" }, () => {
return highlightMatches(item.name, catState.searchSelected.val);
}),
span(" "),
span({ style: "font-size: 0.8em" }, "v" + item.version),
span(" by "),
AuthorList(item.authors)
AuthorList(item.authors),
),
div(
{
style:
"display: flex; align-items: center; gap: 0.25em; margin-top: 0.4em;",
style: "display: flex; align-items: center; gap: 0.25em; margin-top: 0.4em;",
class: "tinymist-template-actions",
},
button(
@ -134,13 +122,13 @@ const TemplateList = (
title: van.derive(() =>
catState.getIsFavorite("preview", item.name)
? "Removes from favorite"
: "Adds to favorite"
: "Adds to favorite",
),
onclick() {
catState.negIsFavorite("preview", item.name);
},
},
HeartIcon(16)
HeartIcon(16),
),
TemplateAction(AddIcon(16), "Creates project", () => {
const packageSpec = `@preview/${item.name}:${item.version}`;
@ -156,22 +144,22 @@ const TemplateList = (
}
return { value: cat };
})
.map(CategoryButton(catState))
.map(CategoryButton(catState)),
),
div({ style: "clear: both" }),
div({ style: "margin-top: 0.4em" },
div(
{ style: "margin-top: 0.4em" },
div({}, () => {
return highlightMatches(item.description, catState.searchSelected.val);
})
)
}),
),
);
};
function runFilterSearch(searchResult: SearchResult[] | undefined) {
// console.log("search", searchResult);
const searchResultMap = new Set(searchResult?.map((result) => result.id));
return (value: PackageMeta) =>
searchResult === undefined || searchResultMap.has(value.id);
return (value: PackageMeta) => searchResult === undefined || searchResultMap.has(value.id);
}
function runFilterCategory(categoryFilter: Set<string>) {
@ -197,8 +185,8 @@ const TemplateList = (
.filter(runFilterCategory(catState.categories.val))
.filter(runFilterFavorite)
.filter(runFilterSearch(catState.searchSelected.val))
.map(TemplateListItem) || []
)
.map(TemplateListItem) || [],
),
);
};
@ -280,8 +268,7 @@ const CategoryButton = (catState: FilterState) => (category: Category) => {
return button(
{
class: van.derive(() => {
const activatingCls =
category.value === catState.activating.val ? " activated" : "";
const activatingCls = category.value === catState.activating.val ? " activated" : "";
return "tinymist-button" + activatingCls;
}),
title: "Filter by category: " + category.value,
@ -291,8 +278,8 @@ const CategoryButton = (catState: FilterState) => (category: Category) => {
{
style: "height: 16px;",
},
category.display || category.value
)
category.display || category.value,
),
);
};
@ -304,15 +291,14 @@ const FilterRow = (catState: FilterState) => {
return "tinymist-button" + activatingCls;
}),
title: "Filter by favorite state",
onclick: () =>
(catState.filterFavorite.val = !catState.filterFavorite.val),
onclick: () => (catState.filterFavorite.val = !catState.filterFavorite.val),
},
HeartIcon(16)
HeartIcon(16),
);
return div(
{ class: "tinymist-category-filter" },
favButton,
...CATEGORIES.map(CategoryButton(catState))
...CATEGORIES.map(CategoryButton(catState)),
);
};
@ -322,15 +308,13 @@ export const TemplateGallery = () => {
const favoritePlaceholders = `:[[preview:FavoritePlaceholder]]:`;
const catState = new FilterState(
JSON.parse(
favoritePlaceholders.startsWith(":")
? favoriteState
: base64Decode(favoritePlaceholders)
)
favoritePlaceholders.startsWith(":") ? favoriteState : base64Decode(favoritePlaceholders),
),
);
van.derive(async () => {
const rawPackages = await fetch(
"https://packages.typst.org/preview/index.json"
).then((res) => res.json());
const rawPackages = await fetch("https://packages.typst.org/preview/index.json").then((res) =>
res.json(),
);
// collect packages by version
const packagesIndex = new Map<string, PackageMeta[]>();
@ -355,9 +339,5 @@ export const TemplateGallery = () => {
packages.val = packagesList;
});
return div(
SearchBar(packages, catState),
FilterRow(catState),
TemplateList(packages, catState)
);
return div(SearchBar(packages, catState), FilterRow(catState), TemplateList(packages, catState));
};

View file

@ -15,8 +15,4 @@ export const base64Decode = (encoded: string) =>
* @returns Base64 encoded string
*/
export const base64Encode = (utf8Str: string) =>
btoa(
Array.from(utf82bytes.encode(utf8Str), (c) => String.fromCharCode(c)).join(
""
)
);
btoa(Array.from(utf82bytes.encode(utf8Str), (c) => String.fromCharCode(c)).join(""));

View file

@ -3,15 +3,15 @@ import { viteSingleFile } from "vite-plugin-singlefile";
// /src/main.ts
const compPrefix = '--component=';
const componentArgs = process.argv.find(arg => arg.startsWith(compPrefix));
let output = 'dist/default';
const compPrefix = "--component=";
const componentArgs = process.argv.find((arg) => arg.startsWith(compPrefix));
let output = "dist/default";
if (componentArgs) {
const component = componentArgs.substring(compPrefix.length);
process.env.VITE_ENTRY = `/src/main.${component}.ts`;
output = `dist/${component}`;
} else {
process.env.VITE_ENTRY = '/src/main.ts';
process.env.VITE_ENTRY = "/src/main.ts";
}
export default defineConfig({
@ -19,13 +19,13 @@ export default defineConfig({
assetsInclude: ["**/*.onnx"],
build: {
minify: false,
outDir: output
outDir: output,
},
optimizeDeps: {
esbuildOptions: {
loader: {
".onnx": "dataurl",
},
}
}
},
},
});

View file

@ -21,5 +21,5 @@ import { TypstDocumentContext, composeDoc, provideDoc } from "./typst-doc.mjs";
* ) {}
*/
export class TypstDocument extends provideDoc(
composeDoc(TypstDocumentContext, provideCanvasDoc, provideSvgDoc)
composeDoc(TypstDocumentContext, provideCanvasDoc, provideSvgDoc),
) {}

View file

@ -11,6 +11,6 @@ export class TypstPreviewDocument extends provideDoc(
provideOutlineDoc,
provideCanvasDoc,
provideSvgDoc,
provideDebugJumpDoc
)
provideDebugJumpDoc,
),
) {}

View file

@ -4,7 +4,7 @@ export function triggerRipple(
top: number,
className: string,
animation: string,
color?: string
color?: string,
) {
const ripple = document.createElement("div");

View file

@ -35,9 +35,7 @@ export interface TypstCanvasDocument {
}
export function provideCanvasDoc<
TBase extends GConstructor<
TypstDocumentContext & Partial<TypstOutlineDocument>
>,
TBase extends GConstructor<TypstDocumentContext & Partial<TypstOutlineDocument>>,
>(Base: TBase): TBase & GConstructor<TypstCanvasDocument> {
return class CanvasDocument extends Base {
feat$canvas = true;
@ -62,40 +60,28 @@ export function provideCanvasDoc<
pageInfo.elem = document.createElement("div");
pageInfo.elem.setAttribute("class", "typst-page-canvas");
pageInfo.elem.style.transformOrigin = "0 0";
pageInfo.elem.setAttribute(
"data-page-number",
pageInfo.index.toString()
);
pageInfo.elem.setAttribute("data-page-number", pageInfo.index.toString());
const canvas = document.createElement("canvas");
pageInfo.elem.appendChild(canvas);
pageInfo.container = document.createElement("div");
// todo: reuse by key
pageInfo.container.setAttribute(
TypstPatchAttrs.Tid,
`canvas:` + pageInfo.index
);
pageInfo.container.setAttribute(TypstPatchAttrs.Tid, `canvas:` + pageInfo.index);
pageInfo.container.setAttribute("class", "typst-page canvas-mode");
pageInfo.container.setAttribute(
"data-page-number",
pageInfo.index.toString()
);
pageInfo.container.setAttribute("data-page-number", pageInfo.index.toString());
pageInfo.container.appendChild(pageInfo.elem);
// do scaling early
this.prepareCanvas(pageInfo, canvas);
rescale(
pageInfo.container,
this.isContentPreview || this.renderMode !== "canvas" || isFirst
this.isContentPreview || this.renderMode !== "canvas" || isFirst,
);
if (this.isContentPreview) {
const pageNumberIndicator = document.createElement("div");
pageNumberIndicator.setAttribute(
"class",
"typst-preview-canvas-page-number"
);
pageNumberIndicator.setAttribute("class", "typst-preview-canvas-page-number");
pageNumberIndicator.textContent = `${pageInfo.index + 1}`;
pageInfo.container.appendChild(pageNumberIndicator);
@ -146,10 +132,7 @@ export function provideCanvasDoc<
return cached;
}
async updateCanvas(
pages: CanvasPage[],
opts?: UpdateCanvasOptions
): Promise<void> {
async updateCanvas(pages: CanvasPage[], opts?: UpdateCanvasOptions): Promise<void> {
const tok = opts?.cancel || undefined;
const perf = performance.now();
console.log("updateCanvas start");
@ -181,8 +164,7 @@ export function provideCanvasDoc<
let cached = this.prepareCanvas(pageInfo, canvas);
const cacheKey =
pageInfo.elem.getAttribute("data-cache-key") || undefined;
const cacheKey = pageInfo.elem.getAttribute("data-cache-key") || undefined;
const result = await this.kModule.renderCanvas({
canvas: canvas.getContext("2d")!,
pageOffset: pageInfo.index,
@ -221,18 +203,12 @@ export function provideCanvasDoc<
if (noSpacingFromTop) {
canvasContainer.style.marginTop = `0px`;
} else {
canvasContainer.style.marginTop = `${
this.isContentPreview ? 6 : 5
}px`;
canvasContainer.style.marginTop = `${this.isContentPreview ? 6 : 5}px`;
}
let elem = canvasContainer.firstElementChild as HTMLDivElement;
const canvasWidth = Number.parseFloat(
elem.getAttribute("data-page-width")!
);
const canvasHeight = Number.parseFloat(
elem.getAttribute("data-page-height")!
);
const canvasWidth = Number.parseFloat(elem.getAttribute("data-page-width")!);
const canvasHeight = Number.parseFloat(elem.getAttribute("data-page-height")!);
this.currentRealScale =
this.previewMode === PreviewMode.Slide
@ -240,9 +216,7 @@ export function provideCanvasDoc<
: cw / canvasWidth;
const scale =
// The element in svg is already scaled by svg host
this.renderMode === "svg"
? 1
: this.currentRealScale * this.currentScaleRatio;
this.renderMode === "svg" ? 1 : this.currentRealScale * this.currentScaleRatio;
// apply scale
const appliedScale = (scale / this.pixelPerPt).toString();
@ -309,18 +283,16 @@ export function provideCanvasDoc<
async rerender$canvas() {
// console.log('toggleCanvasViewportChange!!!!!!', this.id, this.isRendering);
const pages: CanvasPage[] = this.kModule
.retrievePagesInfo()
.map((x, index) => {
return {
tag: "canvas",
index,
width: x.width,
height: x.height,
container: undefined as any as HTMLDivElement,
elem: undefined as any as HTMLDivElement,
};
});
const pages: CanvasPage[] = this.kModule.retrievePagesInfo().map((x, index) => {
return {
tag: "canvas",
index,
width: x.width,
height: x.height,
container: undefined as any as HTMLDivElement,
elem: undefined as any as HTMLDivElement,
};
});
if (!this.hookedElem.firstElementChild) {
this.hookedElem.innerHTML = `<div class="typst-doc" data-render-mode="canvas"></div>`;
@ -338,9 +310,7 @@ export function provideCanvasDoc<
checkChildren(canvasContainer);
}
if (canvasContainer.classList.contains("typst-page")) {
const pageNumber = Number.parseInt(
ch.getAttribute("data-page-number")!
);
const pageNumber = Number.parseInt(ch.getAttribute("data-page-number")!);
if (pageNumber >= pages.length) {
// todo: cache key can shifted
elem.removeChild(ch);
@ -358,9 +328,7 @@ export function provideCanvasDoc<
if (!ch.classList.contains("typst-page")) {
continue;
}
const pageNumber = Number.parseInt(
ch.getAttribute("data-page-number")!
);
const pageNumber = Number.parseInt(ch.getAttribute("data-page-number")!);
if (pageNumber >= pages.length) {
// todo: cache key shifted
docDiv.removeChild(ch);

View file

@ -14,9 +14,9 @@ interface TypstCanvasDocument {
renderCanvas(): number;
}
function provideCanvas<
TBase extends GConstructor<TypstDocument & TypstSvgDocument>
>(Base: TBase): TBase & GConstructor<TypstCanvasDocument> {
function provideCanvas<TBase extends GConstructor<TypstDocument & TypstSvgDocument>>(
Base: TBase,
): TBase & GConstructor<TypstCanvasDocument> {
return class extends Base {
canvasFeat = 10;
renderCanvas() {
@ -25,9 +25,9 @@ function provideCanvas<
};
}
function provideSvg<
TBase extends GConstructor<TypstDocument & TypstCanvasDocument>
>(Base: TBase): TBase & GConstructor<TypstSvgDocument> {
function provideSvg<TBase extends GConstructor<TypstDocument & TypstCanvasDocument>>(
Base: TBase,
): TBase & GConstructor<TypstSvgDocument> {
return class extends Base {
feat = 100;
svgProp() {
@ -42,9 +42,7 @@ function provideSvg<
describe("mixinClass", () => {
it("doMixin", () => {
const T = provideSvg(
provideCanvas(
TypstDocument as GConstructor<TypstDocument & TypstSvgDocument>
)
provideCanvas(TypstDocument as GConstructor<TypstDocument & TypstSvgDocument>),
);
const t = new T();
expect(t.renderCanvas()).toBe(51);

View file

@ -42,7 +42,7 @@ class GenElem {
constructor(
public tag: string,
public container: HTMLElement,
public additions?: Record<string, any>
public additions?: Record<string, any>,
) {}
push(child: GenNode) {
@ -171,7 +171,7 @@ class GenContext {
export function patchOutlineEntry(
prev: HTMLDivElement,
pages: CanvasPage[],
items: OutlineItemData[]
items: OutlineItemData[],
) {
const ctx = new GenContext(pages);
// the root element of the generated outline
@ -190,8 +190,7 @@ export function patchOutlineEntry(
for (const elem of ctx.allElemList) {
// apply clickable behavior to node containing children
if (elem.children.some(isDataNode)) {
const titleContentSpan = elem.additions!.title!.additions!
.content as HTMLSpanElement;
const titleContentSpan = elem.additions!.title!.additions!.content as HTMLSpanElement;
titleContentSpan.style.textDecoration = "underline";
titleContentSpan.style.cursor = "pointer";
@ -221,11 +220,7 @@ export function patchOutlineEntry(
/// Replace the `prev` element with `next` element.
/// Return true if the `prev` element is reused.
/// Return false if the `prev` element is replaced.
function reuseOrPatchOutlineElem(
ctx: GenContext,
prev: Element,
next: Element
) {
function reuseOrPatchOutlineElem(ctx: GenContext, prev: Element, next: Element) {
const canReuse = equalPatchElem(prev, next);
/// Even if the element is reused, we still need to replace its attributes.
@ -237,9 +232,7 @@ function reuseOrPatchOutlineElem(
if (canReuse) {
if (isPageElem) {
const pageNumber = Number.parseInt(
next.getAttribute("data-page-number")!
);
const pageNumber = Number.parseInt(next.getAttribute("data-page-number")!);
// console.log('reuse canvas', ctx.pages[pageNumber], prev, next);
const page = ctx.pages[pageNumber];
page.inserter = poisionCanvasMoved;
@ -264,7 +257,7 @@ function patchOutlineChildren(ctx: GenContext, prev: Element, next: Element) {
prev.children as unknown as Element[],
next.children as unknown as Element[],
// todo: accurate calculation
false
false,
);
// console.log("interpreted origin outline", targetView, toPatch);
@ -275,10 +268,7 @@ function patchOutlineChildren(ctx: GenContext, prev: Element, next: Element) {
// console.log("interpreted target outline", targetView);
const originView = changeViewPerspective(
prev.children as unknown as Element[],
targetView
);
const originView = changeViewPerspective(prev.children as unknown as Element[], targetView);
runOriginViewInstructionsOnOutline(ctx, prev, originView);
}
@ -286,7 +276,7 @@ function patchOutlineChildren(ctx: GenContext, prev: Element, next: Element) {
function runOriginViewInstructionsOnOutline(
ctx: GenContext,
prev: Element,
originView: OriginViewInstruction<Node>[]
originView: OriginViewInstruction<Node>[],
) {
// console.log("interpreted origin view", originView);
for (const [op, off, fr] of originView) {
@ -300,9 +290,7 @@ function runOriginViewInstructionsOnOutline(
break;
case "remove":
if (elem?.classList?.contains("typst-page")) {
const pageNumber = Number.parseInt(
elem.getAttribute("data-page-number")!
);
const pageNumber = Number.parseInt(elem.getAttribute("data-page-number")!);
if (pageNumber < ctx.pages.length) {
const page = ctx.pages[pageNumber];
// console.log('recover canvas', page, pageNumber);
@ -321,22 +309,14 @@ function runOriginViewInstructionsOnOutline(
}
export interface TypstOutlineDocument {
patchOutlineEntry(
prev: HTMLDivElement,
pages: CanvasPage[],
items: OutlineItemData[]
): void;
patchOutlineEntry(prev: HTMLDivElement, pages: CanvasPage[], items: OutlineItemData[]): void;
}
export function provideOutlineDoc<
TBase extends GConstructor<TypstDocumentContext>,
>(Base: TBase): TBase & GConstructor<TypstOutlineDocument> {
export function provideOutlineDoc<TBase extends GConstructor<TypstDocumentContext>>(
Base: TBase,
): TBase & GConstructor<TypstOutlineDocument> {
return class DebugJumpDocument extends Base {
patchOutlineEntry(
prev: HTMLDivElement,
pages: CanvasPage[],
items: OutlineItemData[]
) {
patchOutlineEntry(prev: HTMLDivElement, pages: CanvasPage[], items: OutlineItemData[]) {
patchOutlineEntry(prev, pages, items);
}
};

View file

@ -72,10 +72,7 @@ export function equalPatchElem(prev: ElementChildren, next: ElementChildren) {
/// To remove unused resources, An extra remove inst can remove a specify element
///
/// Example5: resource:[o1, o2] -> <reuse o1> <append t1> <remove o2> -> [o1, t1] and remove o2
export type TargetViewInstruction<T> =
| ["append", T]
| ["reuse", number]
| ["remove", number];
export type TargetViewInstruction<T> = ["append", T] | ["reuse", number] | ["remove", number];
/// The recursive patch operation must be applied to this two element.
export type PatchPair<T> = [T /* origin */, T /* target */];
@ -89,7 +86,7 @@ export function interpretTargetView<T extends ElementChildren, U extends T = T>(
targetChildren: T[],
// todo: remove this tag
isPatchingSvg: boolean = true, // patch svg or outline
tIsU = (x: T): x is U => !!x.getAttribute(TypstPatchAttrs.Tid)
tIsU = (x: T): x is U => !!x.getAttribute(TypstPatchAttrs.Tid),
): ViewTransform<U> {
const availableOwnedResource = new Map<string, [T, number[]]>();
const targetView: TargetViewInstruction<U>[] = [];
@ -187,13 +184,10 @@ export type OriginViewInstruction<T> =
/// + Finally, it inserts the extra elements.
///
/// Some better strategy would help and be implemented in future.
export function changeViewPerspective<
T extends ElementChildren,
U extends T = T
>(
export function changeViewPerspective<T extends ElementChildren, U extends T = T>(
originChildren: T[],
targetView: TargetViewInstruction<U>[],
tIsU = (_x: T): _x is U => true
tIsU = (_x: T): _x is U => true,
): OriginViewInstruction<U>[] {
const originView: OriginViewInstruction<U>[] = [];
@ -321,7 +315,7 @@ export function changeViewPerspective<
export function runOriginViewInstructions(
prev: Element,
originView: OriginViewInstruction<Node>[]
originView: OriginViewInstruction<Node>[],
) {
// console.log("interpreted origin view", originView);
for (const [op, off, fr] of originView) {

View file

@ -43,7 +43,7 @@ function patchChildren(prev: Element, next: Element) {
const originView = changeViewPerspective(
prev.children as unknown as SVGGElement[],
targetView,
isGElem
isGElem,
);
runOriginViewInstructions(prev, originView);
@ -76,10 +76,7 @@ interface FrozenReplacement {
debug?: string;
}
function preReplaceNonSVGElements(
prev: Element,
next: Element
): FrozenReplacement {
function preReplaceNonSVGElements(prev: Element, next: Element): FrozenReplacement {
const removedIndices: number[] = [];
const frozenReplacement: FrozenReplacement = {
inserts: [],
@ -118,9 +115,9 @@ function postReplaceNonSVGElements(prev: Element, frozen: FrozenReplacement) {
/// Retrieve the `<g>` elements from the `prev` element.
const gElements = Array.from(prev.children).filter(isGElem);
if (gElements.length + 1 !== frozen.inserts.length) {
throw new Error(`invalid frozen replacement: gElements.length (${gElements.length
}) + 1 !=== frozen.inserts.length (${frozen.inserts.length}) ${frozen.debug || ""
}
throw new Error(`invalid frozen replacement: gElements.length (${
gElements.length
}) + 1 !=== frozen.inserts.length (${frozen.inserts.length}) ${frozen.debug || ""}
current: ${prev.outerHTML}`);
}
@ -156,10 +153,7 @@ function initOrPatchSvgHeader(svg: SVGElement) {
}
/// Create a global resource header
const resourceHeader = document.createElementNS(
"http://www.w3.org/2000/svg",
"svg"
);
const resourceHeader = document.createElementNS("http://www.w3.org/2000/svg", "svg");
resourceHeader.id = "typst-svg-resources";
// set viewbox, width, and height
resourceHeader.setAttribute("viewBox", "0 0 0 0");
@ -195,10 +189,7 @@ function patchSvgHeader(prev: SVGElement, next: SVGElement) {
// todo: gc
prevChild.append(...nextChild.children);
}
} else if (
prevChild.tagName === "style" &&
nextChild.getAttribute("data-reuse") !== "1"
) {
} else if (prevChild.tagName === "style" && nextChild.getAttribute("data-reuse") !== "1") {
// console.log("replace extra style", prevChild, nextChild);
// todo: gc
@ -240,7 +231,7 @@ function patchSvgHeader(prev: SVGElement, next: SVGElement) {
export function patchSvgToContainer(
hookedElem: Element,
patchStr: string,
decorateSvgElement: (elem: SVGElement) => void = () => void 0
decorateSvgElement: (elem: SVGElement) => void = () => void 0,
) {
if (hookedElem.firstElementChild) {
const elem = document.createElement("div");

View file

@ -1,9 +1,5 @@
import { describe, expect, it } from "vitest";
import {
PatchPair,
interpretTargetView,
changeViewPerspective,
} from "./typst-patch.mjs";
import { PatchPair, interpretTargetView, changeViewPerspective } from "./typst-patch.mjs";
interface Attributes {
[key: string]: string | null | undefined;
@ -15,7 +11,7 @@ interface Attributes {
class MockElement {
tagName = "g";
constructor(public attrs: Attributes) { }
constructor(public attrs: Attributes) {}
getAttribute(s: string): string | null {
return this.attrs[s] ?? null;
@ -45,7 +41,7 @@ const repeatOrJust = (n: number | (number | null)[]): MockElement[] => {
(i) =>
new MockElement({
"data-tid": i !== null ? i.toString() : null,
})
}),
);
}
@ -64,7 +60,7 @@ const reuseStub = (n: number | null) =>
function toSnapshot([targetView, patchPair]: [
(MockElement | number | string)[][],
PatchPair<MockElement>[]
PatchPair<MockElement>[],
]): string[] {
const repr = (elem: unknown) => {
if (elem instanceof MockElement) {
@ -76,33 +72,24 @@ function toSnapshot([targetView, patchPair]: [
const instructions = targetView.map((i) => {
return i.map(repr).join(",");
});
const patches = patchPair.length
? [patchPair.map((i) => i.map(repr).join("->")).join(",")]
: [];
const patches = patchPair.length ? [patchPair.map((i) => i.map(repr).join("->")).join(",")] : [];
return [...instructions, ...patches];
}
const hasTid = (elem: MockElement): elem is MockElement =>
elem.getAttribute("data-tid") !== null;
const hasTid = (elem: MockElement): elem is MockElement => elem.getAttribute("data-tid") !== null;
const indexTargetView = (
init: number | (number | null)[],
rearrange: (number | null)[]
) =>
const indexTargetView = (init: number | (number | null)[], rearrange: (number | null)[]) =>
interpretTargetView<MockElement>(
injectOffsets("o", repeatOrJust(init)),
injectOffsets("t", rearrange.map(reuseStub)),
true,
hasTid
hasTid,
);
const indexOriginView = (
init: number | (number | null)[],
rearrange: (number | null)[]
) =>
const indexOriginView = (init: number | (number | null)[], rearrange: (number | null)[]) =>
changeViewPerspective<MockElement>(
injectOffsets("o", repeatOrJust(init)),
indexTargetView(init, rearrange)[0],
hasTid
hasTid,
);
describe("interpretView", () => {
@ -262,10 +249,7 @@ describe("interpretView", () => {
`);
});
it("handleReusePreserveOrder2", () => {
const result = indexTargetView(
[0, 1, 2, 1, 2, 3, 4, 3, 4],
[1, 2, 3, 4, 3, 4, 1, 2]
);
const result = indexTargetView([0, 1, 2, 1, 2, 3, 4, 3, 4], [1, 2, 3, 4, 3, 4, 1, 2]);
expect(toSnapshot(result)).toMatchInlineSnapshot(`
[
"reuse,1",
@ -282,10 +266,7 @@ describe("interpretView", () => {
`);
});
it("handleReusePreserveOrder2_origin", () => {
const result = indexOriginView(
[0, 1, 2, 1, 2, 3, 4, 3, 4],
[1, 2, 3, 4, 3, 4, 1, 2]
);
const result = indexOriginView([0, 1, 2, 1, 2, 3, 4, 3, 4], [1, 2, 3, 4, 3, 4, 1, 2]);
expect(toSnapshot([result, []])).toMatchInlineSnapshot(`
[
"remove,0",
@ -301,17 +282,8 @@ describe("interpretView", () => {
const target = injectOffsets("t", [0, null].map(reuseStub));
target[0].attrs["data-tid"] = "1";
target[1].attrs["data-tid"] = "0";
const result = interpretTargetView<MockElement>(
origin,
target,
true,
hasTid
);
const result2 = changeViewPerspective<MockElement>(
origin,
result[0],
hasTid
);
const result = interpretTargetView<MockElement>(origin, target, true, hasTid);
const result2 = changeViewPerspective<MockElement>(origin, result[0], hasTid);
expect(toSnapshot(result)).toMatchInlineSnapshot(`
[
"reuse,3",
@ -328,17 +300,8 @@ describe("interpretView", () => {
it("handleMasterproefThesisAffectedByEmptyPageAntiCase", () => {
const origin = injectOffsets("o", repeatOrJust([null, null, null, 0, 1]));
const target = injectOffsets("t", [0, 1].map(reuseStub));
const result = interpretTargetView<MockElement>(
origin,
target,
true,
hasTid
);
const result2 = changeViewPerspective<MockElement>(
origin,
result[0],
hasTid
);
const result = interpretTargetView<MockElement>(origin, target, true, hasTid);
const result2 = changeViewPerspective<MockElement>(origin, result[0], hasTid);
expect(toSnapshot(result)).toMatchInlineSnapshot(`
[
"reuse,3",
@ -354,17 +317,8 @@ describe("interpretView", () => {
it("handleReuseAppend", () => {
const origin = injectOffsets("o", repeatOrJust([null, null, null, 0, 1]));
const target = injectOffsets("t", [1, null, 0, null, 1].map(reuseStub));
const result = interpretTargetView<MockElement>(
origin,
target,
true,
hasTid
);
const result2 = changeViewPerspective<MockElement>(
origin,
result[0],
hasTid
);
const result = interpretTargetView<MockElement>(origin, target, true, hasTid);
const result2 = changeViewPerspective<MockElement>(origin, result[0], hasTid);
expect(toSnapshot(result)).toMatchInlineSnapshot(`
[
"reuse,4",

View file

@ -1,50 +1,49 @@
export function setupDrag() {
let lastPos = { x: 0, y: 0 };
let moved = false;
let containerElement: HTMLElement | null = null;
const mouseMoveHandler = function (e: MouseEvent) {
// How far the mouse has been moved
const dx = e.clientX - lastPos.x;
const dy = e.clientY - lastPos.y;
let lastPos = { x: 0, y: 0 };
let moved = false;
let containerElement: HTMLElement | null = null;
const mouseMoveHandler = function (e: MouseEvent) {
// How far the mouse has been moved
const dx = e.clientX - lastPos.x;
const dy = e.clientY - lastPos.y;
window.scrollBy(-dx, -dy);
lastPos = {
x: e.clientX,
y: e.clientY,
};
moved = true;
window.scrollBy(-dx, -dy);
lastPos = {
x: e.clientX,
y: e.clientY,
};
const mouseUpHandler = function () {
document.removeEventListener('mousemove', mouseMoveHandler);
document.removeEventListener('mouseup', mouseUpHandler);
if (!containerElement) return;
if (!moved) {
document.getSelection()?.removeAllRanges();
}
containerElement.style.cursor = 'grab';
};
const mouseDownHandler = function (e: MouseEvent) {
lastPos = {
// Get the current mouse position
x: e.clientX,
y: e.clientY,
};
if (!containerElement) return;
const elementUnderMouse = e.target as HTMLElement | null;
if (elementUnderMouse !== null && elementUnderMouse.classList.contains('tsel')) {
return;
}
e.preventDefault();
containerElement.style.cursor = 'grabbing';
moved = false;
document.addEventListener('mousemove', mouseMoveHandler);
document.addEventListener('mouseup', mouseUpHandler);
};
document.addEventListener('DOMContentLoaded', () => {
containerElement = document.getElementById('typst-container');
if (!containerElement) return;
containerElement.addEventListener('mousedown', mouseDownHandler);
moved = true;
};
const mouseUpHandler = function () {
document.removeEventListener("mousemove", mouseMoveHandler);
document.removeEventListener("mouseup", mouseUpHandler);
if (!containerElement) return;
if (!moved) {
document.getSelection()?.removeAllRanges();
}
);
containerElement.style.cursor = "grab";
};
const mouseDownHandler = function (e: MouseEvent) {
lastPos = {
// Get the current mouse position
x: e.clientX,
y: e.clientY,
};
if (!containerElement) return;
const elementUnderMouse = e.target as HTMLElement | null;
if (elementUnderMouse !== null && elementUnderMouse.classList.contains("tsel")) {
return;
}
e.preventDefault();
containerElement.style.cursor = "grabbing";
moved = false;
document.addEventListener("mousemove", mouseMoveHandler);
document.addEventListener("mouseup", mouseUpHandler);
};
document.addEventListener("DOMContentLoaded", () => {
containerElement = document.getElementById("typst-container");
if (!containerElement) return;
containerElement.addEventListener("mousedown", mouseDownHandler);
});
}