From bbfb1fce04ce112a7cfc855e20a96d67c1cbb092 Mon Sep 17 00:00:00 2001 From: jan Date: Sun, 27 Sep 2026 20:18:32 +0200 Subject: [PATCH] asdasdasdass --- .oxlintrc.json | 42 ++++++- .prettierignore | 3 + .prettierrc.json | 9 ++ bun.lock | 3 + index.html | 48 ++++---- package.json | 43 ++++--- src/code-to-element.ts | 81 +++++++------ src/dom-to-image.ts | 106 +++++----------- src/editor.ts | 33 +++-- src/main.ts | 45 +++++-- src/style.css | 268 ++++++++++++++++++++--------------------- tsconfig.json | 40 +++--- 12 files changed, 373 insertions(+), 348 deletions(-) create mode 100644 .prettierignore create mode 100644 .prettierrc.json diff --git a/.oxlintrc.json b/.oxlintrc.json index 01172ab..da33d35 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -1,4 +1,42 @@ { - "$schema": "./node_modules/oxlint/configuration_schema.json", - "ignorePatterns": ["dist/**"] + "$schema": "./node_modules/oxlint/configuration_schema.json", + "ignorePatterns": ["dist/**", "node_modules/**"], + "plugins": ["typescript", "unicorn", "oxc", "promise"], + "env": { + "browser": true, + "builtin": true, + "es2024": true + }, + "categories": { + "correctness": "error", + "suspicious": "warn", + "perf": "warn" + }, + "rules": { + "no-console": "error", + "no-debugger": "error", + "eqeqeq": ["error", "always"], + "no-var": "error", + "prefer-const": "error", + "object-shorthand": "error", + "prefer-template": "error", + "unicorn/prefer-dom-node-append": "error", + "unicorn/prefer-dom-node-remove": "error", + "unicorn/prefer-dom-node-text-content": "error", + "unicorn/prefer-query-selector": "error", + "unicorn/prefer-add-event-listener": "error", + "typescript/no-explicit-any": "error", + "typescript/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }], + "promise/always-return": "error", + "promise/catch-or-return": "error", + "oxc/no-accumulating-spread": "error" + }, + "overrides": [ + { + "files": ["**/*.config.json", ".oxlintrc.json", ".prettierrc.json"], + "rules": { + "no-console": "off" + } + } + ] } diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..81f414d --- /dev/null +++ b/.prettierignore @@ -0,0 +1,3 @@ +dist +node_modules +bun.lock diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..af69bb8 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,9 @@ +{ + "semi": false, + "singleQuote": true, + "printWidth": 100, + "tabWidth": 4, + "useTabs": false, + "trailingComma": "none", + "arrowParens": "always" +} diff --git a/bun.lock b/bun.lock index 40b3c94..c48afdd 100644 --- a/bun.lock +++ b/bun.lock @@ -10,6 +10,7 @@ }, "devDependencies": { "oxlint": "^1.85.0", + "prettier": "^3.9.9", "typescript": "~6.0.2", "vite": "^8.3.0", }, @@ -146,6 +147,8 @@ "postcss": ["postcss@8.5.28", "", { "dependencies": { "nanoid": "^3.3.18", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A=="], + "prettier": ["prettier@3.9.9", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-Z/CJHIkdujO/OtN7nXUii0Rf3VT5SRuhjBA82Xvu2XhBUgX3nhP67T0LHceBdQLex7OOFGTox+Q5Yg8Jk2Qivg=="], + "rolldown": ["rolldown@1.2.10", "", { "dependencies": { "@oxc-project/types": "=0.151.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm-eabi": "1.2.10", "@rolldown/binding-android-arm64": "1.2.10", "@rolldown/binding-darwin-arm64": "1.2.10", "@rolldown/binding-darwin-x64": "1.2.10", "@rolldown/binding-freebsd-x64": "1.2.10", "@rolldown/binding-linux-arm-gnueabihf": "1.2.10", "@rolldown/binding-linux-arm64-gnu": "1.2.10", "@rolldown/binding-linux-arm64-musl": "1.2.10", "@rolldown/binding-linux-ppc64-gnu": "1.2.10", "@rolldown/binding-linux-s390x-gnu": "1.2.10", "@rolldown/binding-linux-x64-gnu": "1.2.10", "@rolldown/binding-linux-x64-musl": "1.2.10", "@rolldown/binding-openharmony-arm64": "1.2.10", "@rolldown/binding-win32-arm64-msvc": "1.2.10", "@rolldown/binding-win32-x64-msvc": "1.2.10" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-OxkA08pSryMK7B3XiFA09B4OJ1xJMPgIYCBMY2xchzpqgBGsV1o0DetPAE+Sl3N3L4oCPiEzmHVSOj7iR04Zog=="], "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], diff --git a/index.html b/index.html index 963aa4e..f2089b2 100644 --- a/index.html +++ b/index.html @@ -1,29 +1,25 @@ + + + + + p/slides + - - - - - p/slides - - - -
- -
-
- Slides - -
-
-
-
-
-
-
-
- - - - \ No newline at end of file + +
+ +
+
+ Slides + +
+
+
+
+
+
+ + + diff --git a/package.json b/package.json index 3fcce16..541d3c7 100644 --- a/package.json +++ b/package.json @@ -1,22 +1,25 @@ { - "name": "psslides", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "tsc && vite build", - "lint": "oxlint", - "lint:fix": "oxlint --fix", - "preview": "vite preview" - }, - "devDependencies": { - "oxlint": "^1.85.0", - "typescript": "~6.0.2", - "vite": "^8.3.0" - }, - "dependencies": { - "emmet-monaco-es": "^5.7.0", - "monaco-editor": "0.55.1" - } + "name": "psslides", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "lint": "oxlint", + "lint:fix": "oxlint --fix", + "format": "prettier --write .", + "format:check": "prettier --check .", + "preview": "vite preview" + }, + "devDependencies": { + "oxlint": "^1.85.0", + "prettier": "^3.9.9", + "typescript": "~6.0.2", + "vite": "^8.3.0" + }, + "dependencies": { + "emmet-monaco-es": "^5.7.0", + "monaco-editor": "0.55.1" + } } diff --git a/src/code-to-element.ts b/src/code-to-element.ts index 8973521..55408cb 100644 --- a/src/code-to-element.ts +++ b/src/code-to-element.ts @@ -1,56 +1,57 @@ import { domToSvgDataUrl } from './dom-to-image' export interface CodeToElementOptions { - title?: string - allowScripts?: boolean - styles?: string[] + title?: string + allowScripts?: boolean + styles?: string[] } -export function codeToElement( - code: string, - options: CodeToElementOptions = {} -): HTMLIFrameElement { - const iframe = document.createElement('iframe') - const sandbox = new Set() +export function codeToElement(code: string, options: CodeToElementOptions = {}): HTMLIFrameElement { + const iframe = document.createElement('iframe') + const sandbox = new Set() - if (!options.allowScripts) { - sandbox.add('allow-same-origin') - } else { - sandbox.add('allow-scripts') - } + if (!options.allowScripts) { + sandbox.add('allow-same-origin') + } else { + sandbox.add('allow-scripts') + } - iframe.title = options.title ?? 'Rendered code' - iframe.setAttribute('sandbox', Array.from(sandbox).join(' ')) - iframe.srcdoc = `${(options.styles ?? []).join('\n')}${code}` + iframe.title = options.title ?? 'Rendered code' + iframe.setAttribute('sandbox', Array.from(sandbox).join(' ')) + iframe.srcdoc = `${(options.styles ?? []).join('\n')}${code}` - return iframe + return iframe } export async function createCodeThumbnail( - code: string, - options: CodeToElementOptions = {} + code: string, + options: CodeToElementOptions = {} ): Promise { - const iframe = codeToElement(code, { title: 'Code thumbnail', ...options, allowScripts: false }) - iframe.style.width = '640px' - iframe.style.height = '360px' - iframe.style.position = 'fixed' - iframe.style.left = '-10000px' - iframe.style.border = '0' - document.body.appendChild(iframe) + const iframe = codeToElement(code, { title: 'Code thumbnail', ...options, allowScripts: false }) + iframe.style.width = '640px' + iframe.style.height = '360px' + iframe.style.position = 'fixed' + iframe.style.left = '-10000px' + iframe.style.border = '0' + document.body.append(iframe) - try { - await new Promise((resolve, reject) => { - iframe.addEventListener('load', () => resolve(), { once: true }) - iframe.addEventListener('error', () => reject(new Error('Unable to render code thumbnail')), { once: true }) - }) + try { + await new Promise((resolve, reject) => { + iframe.addEventListener('load', () => resolve(), { once: true }) + iframe.addEventListener( + 'error', + () => reject(new Error('Unable to render code thumbnail')), + { once: true } + ) + }) - const content = iframe.contentDocument?.body - const thumbnail = document.createElement('img') - if (content) { - thumbnail.src = domToSvgDataUrl(content, { width: 640, height: 360 }) + const content = iframe.contentDocument?.body + const thumbnail = document.createElement('img') + if (content) { + thumbnail.src = domToSvgDataUrl(content, { width: 640, height: 360 }) + } + return thumbnail + } finally { + iframe.remove() } - return thumbnail - } finally { - iframe.remove() - } } diff --git a/src/dom-to-image.ts b/src/dom-to-image.ts index 7557a33..e0c1b3a 100644 --- a/src/dom-to-image.ts +++ b/src/dom-to-image.ts @@ -1,92 +1,50 @@ export interface DomExportOptions { - width?: number - height?: number - scale?: number + width?: number + height?: number } function getElementSize(element: Element) { - const rect = element.getBoundingClientRect() - return { - width: Math.max(1, Math.ceil(rect.width)), - height: Math.max(1, Math.ceil(rect.height)) - } + const rect = element.getBoundingClientRect() + return { + width: Math.max(1, Math.ceil(rect.width)), + height: Math.max(1, Math.ceil(rect.height)) + } } function copyComputedStyles(source: Element, target: Element) { - const computedStyle = window.getComputedStyle(source) - const style = Array.from(computedStyle) - .map((property) => `${property}:${computedStyle.getPropertyValue(property)}`) - .join(';') + const computedStyle = window.getComputedStyle(source) + const style = Array.from(computedStyle) + .map((property) => `${property}:${computedStyle.getPropertyValue(property)}`) + .join(';') - target.setAttribute('style', style) + target.setAttribute('style', style) } -function cloneWithStyles(source: Node, target: Node) { - if (source.nodeType === Node.ELEMENT_NODE && target.nodeType === Node.ELEMENT_NODE) { - copyComputedStyles(source as Element, target as Element) - } +function cloneWithStyles(source: Node, target: ParentNode) { + if (source.nodeType === Node.ELEMENT_NODE && target.nodeType === Node.ELEMENT_NODE) { + copyComputedStyles(source as Element, target as Element) + } - source.childNodes.forEach((child) => { - const clonedChild = child.cloneNode(false) - cloneWithStyles(child, clonedChild) - target.appendChild(clonedChild) - }) + source.childNodes.forEach((child) => { + const clonedChild = child.cloneNode(false) as ParentNode + cloneWithStyles(child, clonedChild) + target.append(clonedChild) + }) } function createSvg(element: Element, options: DomExportOptions) { - const size = getElementSize(element) - const width = options.width ?? size.width - const height = options.height ?? size.height - const clone = element.cloneNode(false) as Element - cloneWithStyles(element, clone) - const content = new XMLSerializer().serializeToString(clone) - const svg = `${content}` + const size = getElementSize(element) + const width = options.width ?? size.width + const height = options.height ?? size.height + const clone = element.cloneNode(false) as Element + cloneWithStyles(element, clone) + const content = new XMLSerializer().serializeToString(clone) + const svg = `${content}` - return { svg, width, height } + return { svg, width, height } } -export function domToSvgDataUrl( - element: Element, - options: DomExportOptions = {} -): string { - const { svg } = createSvg(element, options) - return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}` -} - -export async function domToCanvas( - element: Element, - options: DomExportOptions = {} -): Promise { - const size = getElementSize(element) - const width = options.width ?? size.width - const height = options.height ?? size.height - const scale = options.scale ?? 1 - const svgDataUrl = domToSvgDataUrl(element, options) - const image = new Image() - - await new Promise((resolve, reject) => { - image.onload = () => resolve() - image.onerror = () => reject(new Error('Unable to render DOM export as an image')) - image.src = svgDataUrl - }) - - const canvas = document.createElement('canvas') - canvas.width = Math.max(1, Math.ceil(width * scale)) - canvas.height = Math.max(1, Math.ceil(height * scale)) - const context = canvas.getContext('2d') - - if (!context) { - throw new Error('Unable to create a canvas context') - } - - context.drawImage(image, 0, 0, canvas.width, canvas.height) - return canvas -} - -export async function domToPngDataUrl( - element: Element, - options: DomExportOptions = {} -): Promise { - const canvas = await domToCanvas(element, options) - return canvas.toDataURL('image/png') +export function domToSvgDataUrl(element: Element, options: DomExportOptions = {}): string { + const { svg } = createSvg(element, options) + return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}` } diff --git a/src/editor.ts b/src/editor.ts index 62481d5..cce20ce 100644 --- a/src/editor.ts +++ b/src/editor.ts @@ -8,27 +8,24 @@ emmetHTML(monaco, ['html']) emmetCSS(monaco, ['css', 'scss', 'less']) self.MonacoEnvironment = { - getWorker(_, label) { - if (label === 'css' || label === 'scss' || label === 'less') { - return new cssWorker() + getWorker(_, label) { + if (label === 'css' || label === 'scss' || label === 'less') { + return new cssWorker() + } + if (label === 'html' || label === 'handlebars' || label === 'razor') { + return new htmlWorker() + } + return new editorWorker() } - if (label === 'html' || label === 'handlebars' || label === 'razor') { - return new htmlWorker() - } - return new editorWorker() - } } const MONACO_PARENT = '.code' export function createEditor() { - return monaco.editor.create( - document.querySelector(MONACO_PARENT)!, - { - value: 'test', - language: 'html', - theme: 'vs-dark', - automaticLayout: true - } - ) -} \ No newline at end of file + return monaco.editor.create(document.querySelector(MONACO_PARENT)!, { + value: 'test', + language: 'html', + theme: 'vs-dark', + automaticLayout: true + }) +} diff --git a/src/main.ts b/src/main.ts index 6ec9581..29e7df9 100644 --- a/src/main.ts +++ b/src/main.ts @@ -67,7 +67,7 @@ async function generateSlideThumbnail(slide: Slide, i: number) { const thumbnail = await createCodeThumbnail(slide.code) slideInList.append(slideNumber, thumbnail) - slideElement.appendChild(slideInList) + slideElement.append(slideInList) if (i !== -1) { const deleteButton = document.createElement('button') @@ -78,7 +78,7 @@ async function generateSlideThumbnail(slide: Slide, i: number) { deleteButton.addEventListener('click', () => { deleteSlide(Number(slideElement.dataset.slideId)) }) - slideElement.appendChild(deleteButton) + slideElement.append(deleteButton) } if (i === -1) { @@ -110,22 +110,45 @@ async function refreshThumbnail(index: number, slide: Slide) { updateSelection() } +const THUMBNAIL_CONCURRENCY = 6 + +async function mapWithConcurrency( + items: T[], + limit: number, + map: (item: T, index: number) => Promise +): Promise { + const results: R[] = [] + let nextIndex = 0 + + async function worker() { + while (nextIndex < items.length) { + const index = nextIndex++ + // oxlint-disable-next-line no-await-in-loop -- each worker claims one index at a time; the await is what bounds concurrency + results[index] = await map(items[index], index) + } + } + + await Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker)) + return results +} + async function setSlideList() { - const slideListSlidesList = [{ code: globalSlide }, ...slides] + const allSlides = [{ code: globalSlide }, ...slides] slideList.replaceChildren() slideElements.clear() globalSlideElement = null - for (let i = -1; i < slides.length; i++) { - slideList.appendChild(await generateSlideThumbnail(slideListSlidesList[i + 1], i)) - } + const elements = await mapWithConcurrency(allSlides, THUMBNAIL_CONCURRENCY, (slide, index) => + generateSlideThumbnail(slide, index - 1) + ) + slideList.append(...elements) updateSelection() } function updateSlideOrder() { if (globalSlideElement) { globalSlideElement.dataset.slideId = '-1' - slideList.appendChild(globalSlideElement) + slideList.append(globalSlideElement) } for (const [index, slide] of slides.entries()) { @@ -141,8 +164,10 @@ function updateSlideOrder() { if (slideNumber) { slideNumber.textContent = `${index + 1}` } - slideElement.querySelector('.delete-slide')?.setAttribute('aria-label', `Delete slide ${index + 1}`) - slideList.appendChild(slideElement) + slideElement + .querySelector('.delete-slide') + ?.setAttribute('aria-label', `Delete slide ${index + 1}`) + slideList.append(slideElement) } updateSelection() } @@ -190,7 +215,7 @@ addSlideButton.addEventListener('click', async () => { const slide = { code: '' } slides.push(slide) const slideElement = await generateSlideThumbnail(slide, slides.length - 1) - slideList.appendChild(slideElement) + slideList.append(slideElement) updateSlideOrder() setSlide(slides.length - 1) }) diff --git a/src/style.css b/src/style.css index 6837783..fbc461e 100644 --- a/src/style.css +++ b/src/style.css @@ -1,209 +1,201 @@ :root { - color-scheme: dark; + color-scheme: dark; - --bg: #181818; - --bg-raised: #202020; - --bg-sunken: #101010; + --bg: #181818; + --bg-raised: #202020; + --bg-sunken: #101010; - --text: #e8e8e8; - --text-muted: #a0a0a0; + --text: #e8e8e8; + --text-muted: #a0a0a0; - --border-color: #3f3f3f; + --border-color: #3f3f3f; - --accent: #008800; - --accent-text: #ffffff; + --accent: #008800; + --accent-text: #ffffff; - --slide-bg: #ffffff; - --slide-border: #000000; + --slide-bg: #ffffff; + --slide-border: #000000; } body { - margin: 0; - font-family: sans-serif; - background: var(--bg); - color: var(--text); + margin: 0; + font-family: sans-serif; + background: var(--bg); + color: var(--text); } .container { - display: grid; - grid-template-columns: minmax(0, 0.6fr) minmax(0, 40%) minmax(0, 1fr); - grid-template-rows: 0.03fr 1fr; - gap: 0; - grid-auto-flow: row; - grid-template-areas: - "menubar menubar menubar" - "slidelist code preview"; - width: 100vw; - height: 100vh; + display: grid; + grid-template-columns: minmax(0, 0.6fr) minmax(0, 40%) minmax(0, 1fr); + grid-template-rows: 0.03fr 1fr; + gap: 0; + grid-auto-flow: row; + grid-template-areas: + 'menubar menubar menubar' + 'slidelist code preview'; + width: 100vw; + height: 100vh; } .menubar { - grid-area: menubar; - background-color: var(--accent); - color: var(--accent-text); - display: flex; - align-items: center; - padding-left: 1rem; - padding-right: 1rem; - border-bottom: 1px solid var(--border-color); + grid-area: menubar; + background-color: var(--accent); + color: var(--accent-text); + display: flex; + align-items: center; + padding-left: 1rem; + padding-right: 1rem; + border-bottom: 1px solid var(--border-color); } .slidelist { - grid-area: slidelist; - min-width: 0; - background-color: var(--bg); - border-right: 1px solid var(--border-color); - overflow-y: auto; + grid-area: slidelist; + min-width: 0; + background-color: var(--bg); + border-right: 1px solid var(--border-color); + overflow-y: auto; } .slide-list-header { - display: flex; - align-items: center; - justify-content: space-between; - min-height: 3rem; - padding: 0 1rem; - border-bottom: 1px solid var(--border-color); - color: var(--text); - font-weight: 600; + display: flex; + align-items: center; + justify-content: space-between; + min-height: 3rem; + padding: 0 1rem; + border-bottom: 1px solid var(--border-color); + color: var(--text); + font-weight: 600; } .add-slide { - width: 1.75rem; - height: 1.75rem; - padding: 0; - border: 1px solid var(--border-color); - background: var(--bg-raised); - color: var(--text); - font-size: 1.1rem; - line-height: 1; - cursor: pointer; + width: 1.75rem; + height: 1.75rem; + padding: 0; + border: 1px solid var(--border-color); + background: var(--bg-raised); + color: var(--text); + font-size: 1.1rem; + line-height: 1; + cursor: pointer; } .add-slide:hover, .add-slide:focus-visible, .delete-slide:hover, .delete-slide:focus-visible { - border-color: var(--accent); - color: var(--accent-text); - background: var(--accent); - outline: none; + border-color: var(--accent); + color: var(--accent-text); + background: var(--accent); + outline: none; } .slide-list-items { - display: flex; - flex-direction: column; - gap: 0.75rem; - padding: 1rem; + display: flex; + flex-direction: column; + gap: 0.75rem; + padding: 1rem; } .code { - grid-area: code; - min-width: 0; - background-color: var(--bg-sunken); - border-right: 1px solid var(--border-color); + grid-area: code; + min-width: 0; + background-color: var(--bg-sunken); + border-right: 1px solid var(--border-color); } .preview { - grid-area: preview; - min-width: 0; - display: flex; - align-items: center; - justify-content: center; - height: 100%; - background-color: var(--bg); -} - -.slide { - display: block; - width: 100%; - aspect-ratio: 16 / 9; - background: var(--slide-bg); - border: 1px solid var(--slide-border); + grid-area: preview; + min-width: 0; + display: flex; + align-items: center; + justify-content: center; + height: 100%; + background-color: var(--bg); } .slide-list-item { - position: relative; - display: grid; - grid-template-columns: 1.25rem minmax(0, 1fr); - gap: 0.4rem; - align-items: start; - min-width: 0; + position: relative; + display: grid; + grid-template-columns: 1.25rem minmax(0, 1fr); + gap: 0.4rem; + align-items: start; + min-width: 0; } .slide-in-list { - grid-column: 1 / -1; - display: grid; - grid-template-columns: 1.25rem minmax(0, 1fr); - gap: 0.4rem; - align-items: start; - width: 100%; - min-width: 0; - padding: 0.35rem; - border: 1px solid var(--slide-border); - background: var(--bg-raised); - color: var(--text); - text-align: left; - cursor: pointer; + grid-column: 1 / -1; + display: grid; + grid-template-columns: 1.25rem minmax(0, 1fr); + gap: 0.4rem; + align-items: start; + width: 100%; + min-width: 0; + padding: 0.35rem; + border: 1px solid var(--slide-border); + background: var(--bg-raised); + color: var(--text); + text-align: left; + cursor: pointer; } .slide-in-list:hover, .slide-in-list:focus-visible { - border-color: var(--accent); - outline: none; + border-color: var(--accent); + outline: none; } .slide-list-item.selected .slide-in-list { - border-color: var(--accent); - box-shadow: inset 2px 0 0 var(--accent); + border-color: var(--accent); + box-shadow: inset 2px 0 0 var(--accent); } .slide-number { - display: block; - min-height: 1rem; - color: var(--text-muted); - font-size: 0.75rem; - line-height: 1rem; - text-align: center; + display: block; + min-height: 1rem; + color: var(--text-muted); + font-size: 0.75rem; + line-height: 1rem; + text-align: center; } .global-slide { - padding-left: 0.25rem; - border-left: 2px solid var(--accent); + padding-left: 0.25rem; + border-left: 2px solid var(--accent); } .global-slide .slide-number { - color: var(--accent); - font-weight: 700; + color: var(--accent); + font-weight: 700; } .slide-in-list img { - display: block; - width: 100%; - height: auto; - aspect-ratio: 16 / 9; - object-fit: contain; - background: var(--slide-bg); + display: block; + width: 100%; + height: auto; + aspect-ratio: 16 / 9; + object-fit: contain; + background: var(--slide-bg); } .delete-slide { - position: absolute; - top: 0.25rem; - right: 0.25rem; - display: grid; - place-items: center; - width: 1.4rem; - height: 1.4rem; - padding: 0; - border: 1px solid var(--border-color); - background: var(--bg-raised); - color: var(--text); - font-size: 1rem; - line-height: 1; - opacity: 0; - cursor: pointer; + position: absolute; + top: 0.25rem; + right: 0.25rem; + display: grid; + place-items: center; + width: 1.4rem; + height: 1.4rem; + padding: 0; + border: 1px solid var(--border-color); + background: var(--bg-raised); + color: var(--text); + font-size: 1rem; + line-height: 1; + opacity: 0; + cursor: pointer; } .slide-list-item:hover .delete-slide, .delete-slide:focus-visible { - opacity: 1; -} \ No newline at end of file + opacity: 1; +} diff --git a/tsconfig.json b/tsconfig.json index 206b5e0..eb46b3f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,24 +1,24 @@ { - "compilerOptions": { - "target": "es2023", - "module": "esnext", - "lib": ["ES2023", "DOM"], - "types": ["vite/client"], - "allowArbitraryExtensions": true, - "skipLibCheck": true, + "compilerOptions": { + "target": "es2023", + "module": "esnext", + "lib": ["ES2023", "DOM"], + "types": ["vite/client"], + "allowArbitraryExtensions": true, + "skipLibCheck": true, - /* Bundler mode */ - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "verbatimModuleSyntax": true, - "moduleDetection": "force", - "noEmit": true, + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, - /* Linting */ - "noUnusedLocals": true, - "noUnusedParameters": true, - "erasableSyntaxOnly": true, - "noFallthroughCasesInSwitch": true - }, - "include": ["src"] + /* Linting */ + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"] }