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 +
- `${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