diff --git a/.vscode/extensions.json b/.vscode/extensions.json index a797c99f..5e3345ff 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -2,6 +2,7 @@ // See http://go.microsoft.com/fwlink/?LinkId=827846 // for the documentation about the extensions.json format "recommendations": [ + "biomejs.biome", "tamasfe.even-better-toml", "rust-lang.rust-analyzer", "dbaeumer.vscode-eslint", diff --git a/biome.json b/biome.json new file mode 100644 index 00000000..21cacd93 --- /dev/null +++ b/biome.json @@ -0,0 +1,60 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.8.3/schema.json", + "vcs": { + "enabled": true, + "clientKind": "git", + "useIgnoreFile": true + }, + "organizeImports": { "enabled": true }, + "formatter": { + "enabled": true, + "formatWithErrors": false, + "indentStyle": "space", + "indentWidth": 2, + "lineEnding": "lf", + "lineWidth": 120, + "attributePosition": "auto" + }, + "javascript": { + "formatter": { + "jsxQuoteStyle": "double", + "quoteProperties": "asNeeded", + "trailingCommas": "all", + "semicolons": "always", + "arrowParentheses": "always", + "bracketSpacing": true, + "bracketSameLine": false, + "quoteStyle": "single", + "attributePosition": "auto" + } + }, + "overrides": [{ "include": ["*.md"], "formatter": { "lineWidth": 100 } }], + "linter": { + "enabled": true, + "rules": { + "recommended": false, + "complexity": { + "noBannedTypes": "error", + "noUselessTypeConstraint": "error" + }, + "correctness": { + "noPrecisionLoss": "error", + "noUnusedVariables": "off", + "useArrayLiterals": "off" + }, + "style": { + "noNamespace": "error", + "noNonNullAssertion": "off", + "useAsConstAssertion": "error", + "useBlockStatements": "off" + }, + "suspicious": { + "noExplicitAny": "off", + "noExtraNonNullAssertion": "error", + "noFallthroughSwitchClause": "error", + "noMisleadingInstantiator": "error", + "noUnsafeDeclarationMerging": "error" + } + } + } +} diff --git a/package.json b/package.json index ed77968a..256d967c 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "prepare": "husky install && pnpm prepare:build && pnpm prepare:link" }, "devDependencies": { + "@biomejs/biome": "1.8.3", "@types/node": "^20.10.0", "@typescript-eslint/eslint-plugin": "^6.13.1", "@typescript-eslint/parser": "^6.13.1", diff --git a/packages/gem-book/src/element/elements/pre.ts b/packages/gem-book/src/element/elements/pre.ts index 0816fda2..802401e3 100644 --- a/packages/gem-book/src/element/elements/pre.ts +++ b/packages/gem-book/src/element/elements/pre.ts @@ -249,7 +249,8 @@ const styles = createCSSSheet(css` display: none; } :host { - display: block; + display: flex; + flex-direction: column; font-family: ${theme.codeFont}; background: rgb(from ${theme.textColor} r g b / 0.05); border-radius: ${theme.normalRound}; @@ -266,7 +267,7 @@ const styles = createCSSSheet(css` color: rgb(from ${theme.textColor} r g b / 0.5); } .container { - height: 100%; + flex-grow: 1; overflow-y: auto; scrollbar-width: thin; position: relative; @@ -455,9 +456,9 @@ export class Pre extends GemElement { this.#ranges = getRanges(this.range, lines); this.#highlightLineSet = new Set( this.highlight - ? getRanges(this.highlight, lines) - .map(([start, end]) => Array.from({ length: end - start + 1 }, (_, i) => start + i)) - .flat() + ? getRanges(this.highlight, lines).flatMap(([start, end]) => + Array.from({ length: end - start + 1 }, (_, i) => start + i), + ) : [], ); }; @@ -487,19 +488,31 @@ export class Pre extends GemElement { this.#onInputHandle(); }; + // https://stackoverflow.com/questions/62054839/shadowroot-getselection + #chrome = 'getSelection' in this.shadowRoot!; + #offset = 0; #onInputHandle = () => { - const selection = - 'getSelection' in this.shadowRoot! ? (this.shadowRoot as unknown as Window).getSelection() : getSelection(); - if (!selection || selection.focusNode?.nodeType !== Node.TEXT_NODE) return; + let focusNode: Node | null; + let focusOffset = 0; + if (this.#chrome) { + const selection: Selection = (this.shadowRoot as any).getSelection(); + focusNode = selection?.focusNode; + focusOffset = selection?.focusOffset; + } else { + const range: Range = (getSelection() as any)?.getComposedRanges(this.shadowRoot)?.at(0); + focusNode = range?.startContainer; + focusOffset = range?.startOffset; + } + if (focusNode?.nodeType !== Node.TEXT_NODE) return; this.#offset = 0; const textNodeIterator = document.createNodeIterator(this.#codeRef.element!, NodeFilter.SHOW_TEXT); while (true) { const textNode = textNodeIterator.nextNode(); - if (textNode && textNode !== selection?.focusNode) { + if (textNode && textNode !== focusNode) { this.#offset += textNode.nodeValue!.length; } else { - this.#offset += selection.focusOffset; + this.#offset += focusOffset; break; } } @@ -522,6 +535,7 @@ export class Pre extends GemElement { const range = document.createRange(); range.setStart(textNode, offset); range.collapse(false); + // TODO: safari not work sel.removeAllRanges(); sel.addRange(range); break; @@ -556,11 +570,9 @@ export class Pre extends GemElement { const { parts, lineNumbersParts } = this.#getParts(htmlStr); this.#codeRef.element.innerHTML = parts.reduce( (p, c, i) => - p + - ` @@ ${lineNumbersParts[i - 1].at(-1)! + 1}-${ + `${p} @@ ${lineNumbersParts[i - 1].at(-1)! + 1}-${ lineNumbersParts[i].at(0)! - 1 - } @@` + - c, + } @@${c}`, ); this.#setOffset(); }; @@ -570,10 +582,10 @@ export class Pre extends GemElement { const ob = new MutationObserver(() => this.update()); ob.observe(this, { childList: true, characterData: true, subtree: true }); const io = new IntersectionObserver((entries) => { - entries.forEach(({ intersectionRatio }) => { + for (const { intersectionRatio } of entries) { this.#isVisble = intersectionRatio > 0; this.update(); - }); + } }); io.observe(this); return () => { diff --git a/packages/gem-book/src/plugins/sandpack.ts b/packages/gem-book/src/plugins/sandpack.ts index 392b6852..b1224029 100644 --- a/packages/gem-book/src/plugins/sandpack.ts +++ b/packages/gem-book/src/plugins/sandpack.ts @@ -350,14 +350,21 @@ class _GbpSandpackElement extends GemBookPluginElement { const msg = (await formatMessages(e.errors, { kind: 'error', color: false, terminalWidth: 100 })).join('\n'); code = `console.error(\`${msg.replaceAll('\\', '\\\\').replaceAll('`', '\\`')}\`)`; } + const loadEventName = '_load_'; const htmlCode = ` ${data.files[Object.keys(data.files).find((e) => e.toLowerCase() === 'index.html')!]?.code} ${this.#getErudaResources() .map((src) => ``) .join('')} - + `; - if (document.head?.firstElementChild?.textContent?.includes('_html_')) { + addEventListener('message', (evt) => { + if (evt.data === loadEventName) this.#state({ status: 'done' }); + }); + if (!document.head?.firstElementChild?.textContent?.includes('_html_')) { const url = new URL(`./?${new URLSearchParams({ _html_: encodeURIComponent(htmlCode) })}`, location.href); iframe.src = url.href; } else { @@ -366,7 +373,6 @@ class _GbpSandpackElement extends GemBookPluginElement { } }; compile(); - this.#state({ status: 'done' }); return { listen: (..._rest) => {}, updateSandbox: (sandboxSetup?: SandboxSetup) => { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 43eb10ea..57d23ef1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,9 @@ importers: .: devDependencies: + '@biomejs/biome': + specifier: 1.8.3 + version: 1.8.3 '@types/node': specifier: ^20.10.0 version: 20.16.5 @@ -962,6 +965,59 @@ packages: '@bcoe/v8-coverage@0.2.3': resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + '@biomejs/biome@1.8.3': + resolution: {integrity: sha512-/uUV3MV+vyAczO+vKrPdOW0Iaet7UnJMU4bNMinggGJTAnBPjCoLEYcyYtYHNnUNYlv4xZMH6hVIQCAozq8d5w==} + engines: {node: '>=14.21.3'} + hasBin: true + + '@biomejs/cli-darwin-arm64@1.8.3': + resolution: {integrity: sha512-9DYOjclFpKrH/m1Oz75SSExR8VKvNSSsLnVIqdnKexj6NwmiMlKk94Wa1kZEdv6MCOHGHgyyoV57Cw8WzL5n3A==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [darwin] + + '@biomejs/cli-darwin-x64@1.8.3': + resolution: {integrity: sha512-UeW44L/AtbmOF7KXLCoM+9PSgPo0IDcyEUfIoOXYeANaNXXf9mLUwV1GeF2OWjyic5zj6CnAJ9uzk2LT3v/wAw==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [darwin] + + '@biomejs/cli-linux-arm64-musl@1.8.3': + resolution: {integrity: sha512-9yjUfOFN7wrYsXt/T/gEWfvVxKlnh3yBpnScw98IF+oOeCYb5/b/+K7YNqKROV2i1DlMjg9g/EcN9wvj+NkMuQ==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + + '@biomejs/cli-linux-arm64@1.8.3': + resolution: {integrity: sha512-fed2ji8s+I/m8upWpTJGanqiJ0rnlHOK3DdxsyVLZQ8ClY6qLuPc9uehCREBifRJLl/iJyQpHIRufLDeotsPtw==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + + '@biomejs/cli-linux-x64-musl@1.8.3': + resolution: {integrity: sha512-UHrGJX7PrKMKzPGoEsooKC9jXJMa28TUSMjcIlbDnIO4EAavCoVmNQaIuUSH0Ls2mpGMwUIf+aZJv657zfWWjA==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + + '@biomejs/cli-linux-x64@1.8.3': + resolution: {integrity: sha512-I8G2QmuE1teISyT8ie1HXsjFRz9L1m5n83U1O6m30Kw+kPMPSKjag6QGUn+sXT8V+XWIZxFFBoTDEDZW2KPDDw==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + + '@biomejs/cli-win32-arm64@1.8.3': + resolution: {integrity: sha512-J+Hu9WvrBevfy06eU1Na0lpc7uR9tibm9maHynLIoAjLZpQU3IW+OKHUtyL8p6/3pT2Ju5t5emReeIS2SAxhkQ==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [win32] + + '@biomejs/cli-win32-x64@1.8.3': + resolution: {integrity: sha512-/PJ59vA1pnQeKahemaQf4Nyj7IKUvGQSc3Ze1uIGi+Wvr1xF7rGobSrAAG01T/gUDG21vkDsZYM03NAmPiVkqg==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [win32] + '@codesandbox/nodebox@0.1.8': resolution: {integrity: sha512-2VRS6JDSk+M+pg56GA6CryyUSGPjBEe8Pnae0QL3jJF1mJZJVMDKr93gJRtBbLkfZN6LD/DwMtf+2L0bpWrjqg==} @@ -8653,6 +8709,41 @@ snapshots: '@bcoe/v8-coverage@0.2.3': {} + '@biomejs/biome@1.8.3': + optionalDependencies: + '@biomejs/cli-darwin-arm64': 1.8.3 + '@biomejs/cli-darwin-x64': 1.8.3 + '@biomejs/cli-linux-arm64': 1.8.3 + '@biomejs/cli-linux-arm64-musl': 1.8.3 + '@biomejs/cli-linux-x64': 1.8.3 + '@biomejs/cli-linux-x64-musl': 1.8.3 + '@biomejs/cli-win32-arm64': 1.8.3 + '@biomejs/cli-win32-x64': 1.8.3 + + '@biomejs/cli-darwin-arm64@1.8.3': + optional: true + + '@biomejs/cli-darwin-x64@1.8.3': + optional: true + + '@biomejs/cli-linux-arm64-musl@1.8.3': + optional: true + + '@biomejs/cli-linux-arm64@1.8.3': + optional: true + + '@biomejs/cli-linux-x64-musl@1.8.3': + optional: true + + '@biomejs/cli-linux-x64@1.8.3': + optional: true + + '@biomejs/cli-win32-arm64@1.8.3': + optional: true + + '@biomejs/cli-win32-x64@1.8.3': + optional: true + '@codesandbox/nodebox@0.1.8': dependencies: outvariant: 1.4.0