Lint and so on

This commit is contained in:
2025-12-03 11:36:00 +01:00
parent e3c437c027
commit 38b0bc0437
23 changed files with 1315 additions and 185 deletions

2
.prettierignore Normal file
View File

@@ -0,0 +1,2 @@
dist
pnpm-lock.yaml

4
.prettierrc Normal file
View File

@@ -0,0 +1,4 @@
{
"useTabs": true,
"singleQuote": true
}

View File

@@ -1,3 +1,3 @@
{ {
"recommendations": ["svelte.svelte-vscode"] "recommendations": ["svelte.svelte-vscode"]
} }

View File

@@ -42,6 +42,6 @@ If you have state that's important to retain within a component, consider creati
```ts ```ts
// store.ts // store.ts
// An extremely simple external store // An extremely simple external store
import { writable } from 'svelte/store' import { writable } from 'svelte/store';
export default writable(0) export default writable(0);
``` ```

40
eslint.config.js Normal file
View File

@@ -0,0 +1,40 @@
import js from '@eslint/js';
import ts from 'typescript-eslint';
import svelte from 'eslint-plugin-svelte';
import prettier from 'eslint-config-prettier';
import globals from 'globals';
export default ts.config(
js.configs.recommended,
...ts.configs.recommended,
...svelte.configs['flat/recommended'],
prettier,
...svelte.configs['flat/prettier'],
{
languageOptions: {
globals: { ...globals.browser, ...globals.node },
},
rules: {
'@typescript-eslint/no-unused-vars': [
'error',
{ argsIgnorePattern: '^_', varsIgnorePattern: '^_' },
],
},
},
{
files: ['**/*.svelte', '**/*.svelte.ts'],
languageOptions: {
parserOptions: {
parser: ts.parser,
extraFileExtensions: ['.svelte'],
},
},
},
{
files: ['**/*.svelte.ts'],
languageOptions: {
parser: ts.parser,
},
},
{ ignores: ['dist/'] },
);

View File

@@ -1,13 +1,13 @@
<!doctype html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> <link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>buboard</title> <title>buboard</title>
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>
<script type="module" src="/src/main.ts"></script> <script type="module" src="/src/main.ts"></script>
</body> </body>
</html> </html>

View File

@@ -1,41 +1,50 @@
{ {
"name": "buboard", "name": "buboard",
"private": true, "private": true,
"version": "0.0.0", "version": "0.0.0",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "vite build", "build": "vite build",
"preview": "vite preview", "preview": "vite preview",
"check": "svelte-check --tsconfig ./tsconfig.app.json && tsc -p tsconfig.node.json" "check": "svelte-check --tsconfig ./tsconfig.app.json && tsc -p tsconfig.node.json",
}, "lint": "eslint .",
"devDependencies": { "format": "prettier --write .",
"@sveltejs/vite-plugin-svelte": "^6.2.1", "format:check": "prettier --check ."
"@tsconfig/svelte": "^5.0.6", },
"@types/node": "^24.10.1", "devDependencies": {
"svelte": "^5.43.8", "@sveltejs/vite-plugin-svelte": "^6.2.1",
"svelte-check": "^4.3.4", "@tsconfig/svelte": "^5.0.6",
"typescript": "~5.9.3", "@types/node": "^24.10.1",
"vite": "npm:rolldown-vite@7.2.5" "eslint": "^9.39.1",
}, "eslint-config-prettier": "^10.1.8",
"pnpm": { "eslint-plugin-svelte": "^3.13.0",
"overrides": { "globals": "^16.5.0",
"vite": "npm:rolldown-vite@7.2.5" "prettier": "^3.7.4",
} "svelte": "^5.43.8",
}, "svelte-check": "^4.3.4",
"dependencies": { "typescript": "~5.9.3",
"@codemirror/commands": "^6.10.0", "typescript-eslint": "^8.48.1",
"@codemirror/lang-css": "^6.3.1", "vite": "npm:rolldown-vite@7.2.5"
"@codemirror/lang-html": "^6.4.11", },
"@codemirror/language": "^6.11.3", "pnpm": {
"@codemirror/state": "^6.5.2", "overrides": {
"@codemirror/theme-one-dark": "^6.1.3", "vite": "npm:rolldown-vite@7.2.5"
"@codemirror/view": "^6.38.8", }
"@lezer/highlight": "^1.2.3", },
"@replit/codemirror-emacs": "^6.1.0", "dependencies": {
"@replit/codemirror-vim": "^6.3.0", "@codemirror/commands": "^6.10.0",
"codemirror": "^6.0.2", "@codemirror/lang-css": "^6.3.1",
"jszip": "^3.10.1", "@codemirror/lang-html": "^6.4.11",
"lucide-svelte": "^0.555.0" "@codemirror/language": "^6.11.3",
} "@codemirror/state": "^6.5.2",
"@codemirror/theme-one-dark": "^6.1.3",
"@codemirror/view": "^6.38.8",
"@lezer/highlight": "^1.2.3",
"@replit/codemirror-emacs": "^6.1.0",
"@replit/codemirror-vim": "^6.3.0",
"codemirror": "^6.0.2",
"jszip": "^3.10.1",
"lucide-svelte": "^0.555.0"
}
} }

997
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -35,6 +35,7 @@
</script> </script>
<svelte:head> <svelte:head>
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
{@html `<style>${appState.manifest.appCss}</style>`} {@html `<style>${appState.manifest.appCss}</style>`}
</svelte:head> </svelte:head>
@@ -65,7 +66,7 @@
<button class="show-ui" onclick={() => (interfaceHidden = false)} title="Show interface"> <button class="show-ui" onclick={() => (interfaceHidden = false)} title="Show interface">
<Eye size={14} /> <Eye size={14} />
</button> </button>
{#each ['1', '2', '3', '4', '5', '6', '7', '8', '9'] as key} {#each ['1', '2', '3', '4', '5', '6', '7', '8', '9'] as key (key)}
<button <button
class="flag" class="flag"
class:filled={appState.hasFlag(key)} class:filled={appState.hasFlag(key)}

View File

@@ -1,6 +1,7 @@
@font-face { @font-face {
font-family: 'Departure Mono'; font-family: 'Departure Mono';
src: url('/fonts/DepartureMono-Regular.woff2') format('woff2'), src:
url('/fonts/DepartureMono-Regular.woff2') format('woff2'),
url('/fonts/DepartureMono-Regular.woff') format('woff'); url('/fonts/DepartureMono-Regular.woff') format('woff');
font-weight: normal; font-weight: normal;
font-style: normal; font-style: normal;

View File

@@ -1,4 +1,5 @@
<script lang="ts"> <script lang="ts">
import { SvelteMap } from 'svelte/reactivity';
import type { Item } from './types'; import type { Item } from './types';
import { state as appState } from './state.svelte'; import { state as appState } from './state.svelte';
import { calculateCenterOffset, constrainToAspectRatio } from './geometry'; import { calculateCenterOffset, constrainToAspectRatio } from './geometry';
@@ -11,7 +12,7 @@
let isResizing = $state(false); let isResizing = $state(false);
let isRotating = $state(false); let isRotating = $state(false);
let dragStart = { x: 0, y: 0, itemX: 0, itemY: 0 }; let dragStart = { x: 0, y: 0, itemX: 0, itemY: 0 };
let dragStartPositions: Map<string, { x: number; y: number }> = new Map(); let dragStartPositions: SvelteMap<string, { x: number; y: number }> = new SvelteMap();
let resizeStart = { x: 0, y: 0, width: 0, height: 0, itemX: 0, itemY: 0, corner: '', aspectRatio: 1 }; let resizeStart = { x: 0, y: 0, width: 0, height: 0, itemX: 0, itemY: 0, corner: '', aspectRatio: 1 };
let rotateStart = { angle: 0, startAngle: 0 }; let rotateStart = { angle: 0, startAngle: 0 };
@@ -55,7 +56,7 @@
itemX: item.x, itemX: item.x,
itemY: item.y itemY: item.y
}; };
dragStartPositions = new Map(); dragStartPositions = new SvelteMap();
for (const id of appState.selectedIds) { for (const id of appState.selectedIds) {
const selectedItem = appState.getItem(id); const selectedItem = appState.getItem(id);
if (selectedItem) { if (selectedItem) {

View File

@@ -92,7 +92,7 @@
const x = -state.viewport.x / state.viewport.zoom + 400; const x = -state.viewport.x / state.viewport.zoom + 400;
const y = -state.viewport.y / state.viewport.zoom + 300; const y = -state.viewport.y / state.viewport.zoom + 300;
const img = new Image(); const img = document.createElement('img');
img.onload = () => { img.onload = () => {
state.addAsset(assetId, { blob: file, url, filename: file.name }); state.addAsset(assetId, { blob: file, url, filename: file.name });
state.addItem({ state.addItem({

View File

@@ -64,7 +64,7 @@
<span class="app-name">Buboard</span> <span class="app-name">Buboard</span>
<Palette /> <Palette />
<div class="flags"> <div class="flags">
{#each flagKeys as key} {#each flagKeys as key (key)}
<button <button
class="flag" class="flag"
class:filled={state.hasFlag(key)} class:filled={state.hasFlag(key)}

View File

@@ -7,7 +7,7 @@ export function calculateCenterOffset(
corner: string, corner: string,
deltaWidth: number, deltaWidth: number,
deltaHeight: number, deltaHeight: number,
rotation: number rotation: number,
): Point { ): Point {
const rad = (rotation * Math.PI) / 180; const rad = (rotation * Math.PI) / 180;
const cos = Math.cos(rad); const cos = Math.cos(rad);
@@ -24,14 +24,14 @@ export function calculateCenterOffset(
return { return {
x: localDx * cos - localDy * sin, x: localDx * cos - localDy * sin,
y: localDx * sin + localDy * cos y: localDx * sin + localDy * cos,
}; };
} }
export function constrainToAspectRatio( export function constrainToAspectRatio(
newWidth: number, newWidth: number,
newHeight: number, newHeight: number,
aspectRatio: number aspectRatio: number,
): { width: number; height: number } { ): { width: number; height: number } {
const newRatio = newWidth / newHeight; const newRatio = newWidth / newHeight;
@@ -47,13 +47,13 @@ export function detectRotationCorner(
localY: number, localY: number,
halfWidth: number, halfWidth: number,
halfHeight: number, halfHeight: number,
zoneRadius: number zoneRadius: number,
): string | null { ): string | null {
const corners: Record<string, Point> = { const corners: Record<string, Point> = {
nw: { x: -halfWidth, y: -halfHeight }, nw: { x: -halfWidth, y: -halfHeight },
ne: { x: halfWidth, y: -halfHeight }, ne: { x: halfWidth, y: -halfHeight },
sw: { x: -halfWidth, y: halfHeight }, sw: { x: -halfWidth, y: halfHeight },
se: { x: halfWidth, y: halfHeight } se: { x: halfWidth, y: halfHeight },
}; };
const isInsideBounds = const isInsideBounds =
@@ -71,8 +71,10 @@ export function detectRotationCorner(
if (dist > zoneRadius || dist < 3) continue; if (dist > zoneRadius || dist < 3) continue;
const isOutwardX = (name.includes('w') && dx < 0) || (name.includes('e') && dx > 0); const isOutwardX =
const isOutwardY = (name.includes('n') && dy < 0) || (name.includes('s') && dy > 0); (name.includes('w') && dx < 0) || (name.includes('e') && dx > 0);
const isOutwardY =
(name.includes('n') && dy < 0) || (name.includes('s') && dy > 0);
if (isOutwardX || isOutwardY) return name; if (isOutwardX || isOutwardY) return name;
} }

View File

@@ -2,7 +2,10 @@ import JSZip from 'jszip';
import type { Manifest, AssetStore } from './types'; import type { Manifest, AssetStore } from './types';
import { state } from './state.svelte'; import { state } from './state.svelte';
export async function exportBoard(): Promise<{ success: boolean; error?: string }> { export async function exportBoard(): Promise<{
success: boolean;
error?: string;
}> {
try { try {
const zip = new JSZip(); const zip = new JSZip();
const assetsFolder = zip.folder('assets'); const assetsFolder = zip.folder('assets');
@@ -12,7 +15,7 @@ export async function exportBoard(): Promise<{ success: boolean; error?: string
version: 1, version: 1,
items: state.manifest.items.map((item) => ({ ...item })), items: state.manifest.items.map((item) => ({ ...item })),
sharedCss: state.manifest.sharedCss, sharedCss: state.manifest.sharedCss,
appCss: state.manifest.appCss appCss: state.manifest.appCss,
}; };
for (const item of exportManifest.items) { for (const item of exportManifest.items) {
@@ -31,7 +34,7 @@ export async function exportBoard(): Promise<{ success: boolean; error?: string
const blob = await zip.generateAsync({ const blob = await zip.generateAsync({
type: 'blob', type: 'blob',
compression: 'DEFLATE', compression: 'DEFLATE',
compressionOptions: { level: 9 } compressionOptions: { level: 9 },
}); });
const url = URL.createObjectURL(blob); const url = URL.createObjectURL(blob);
const a = document.createElement('a'); const a = document.createElement('a');
@@ -41,28 +44,35 @@ export async function exportBoard(): Promise<{ success: boolean; error?: string
URL.revokeObjectURL(url); URL.revokeObjectURL(url);
return { success: true }; return { success: true };
} catch (e) { } catch (e) {
return { success: false, error: e instanceof Error ? e.message : 'Export failed' }; return {
success: false,
error: e instanceof Error ? e.message : 'Export failed',
};
} }
} }
export async function importBoard(file: File): Promise<{ success: boolean; error?: string }> { export async function importBoard(
file: File,
): Promise<{ success: boolean; error?: string }> {
try { try {
const zip = await JSZip.loadAsync(file); const zip = await JSZip.loadAsync(file);
const manifestFile = zip.file('manifest.json'); const manifestFile = zip.file('manifest.json');
if (!manifestFile) throw new Error('Invalid .bub file: missing manifest.json'); if (!manifestFile)
throw new Error('Invalid .bub file: missing manifest.json');
const manifestJson = await manifestFile.async('string'); const manifestJson = await manifestFile.async('string');
const raw = JSON.parse(manifestJson); const raw = JSON.parse(manifestJson);
if (raw.version !== 1) throw new Error(`Unsupported manifest version: ${raw.version}`); if (raw.version !== 1)
throw new Error(`Unsupported manifest version: ${raw.version}`);
const manifest: Manifest = { const manifest: Manifest = {
version: 1, version: 1,
items: raw.items, items: raw.items,
sharedCss: raw.sharedCss ?? '', sharedCss: raw.sharedCss ?? '',
appCss: raw.appCss ?? '', appCss: raw.appCss ?? '',
flags: raw.flags ?? {} flags: raw.flags ?? {},
}; };
const assets: AssetStore = {}; const assets: AssetStore = {};
@@ -70,7 +80,9 @@ export async function importBoard(file: File): Promise<{ success: boolean; error
for (const item of manifest.items) { for (const item of manifest.items) {
if (item.assetId) { if (item.assetId) {
const assetFiles = zip.folder('assets')?.file(new RegExp(`^${item.assetId}\\.`)); const assetFiles = zip
.folder('assets')
?.file(new RegExp(`^${item.assetId}\\.`));
if (assetFiles && assetFiles.length > 0) { if (assetFiles && assetFiles.length > 0) {
const assetFile = assetFiles[0]; const assetFile = assetFiles[0];
const blob = await assetFile.async('blob'); const blob = await assetFile.async('blob');
@@ -92,6 +104,9 @@ export async function importBoard(file: File): Promise<{ success: boolean; error
state.load(manifest, assets); state.load(manifest, assets);
return { success: true }; return { success: true };
} catch (e) { } catch (e) {
return { success: false, error: e instanceof Error ? e.message : 'Import failed' }; return {
success: false,
error: e instanceof Error ? e.message : 'Import failed',
};
} }
} }

View File

@@ -1,4 +1,5 @@
import type { Item, Manifest, Asset, AssetStore, Viewport, PositionFlag } from './types'; import { SvelteSet } from 'svelte/reactivity';
import type { Item, Manifest, Asset, AssetStore, Viewport } from './types';
const STORAGE_KEY = 'buboard'; const STORAGE_KEY = 'buboard';
@@ -68,11 +69,11 @@ function createState() {
items: [], items: [],
sharedCss: DEFAULT_SHARED_CSS, sharedCss: DEFAULT_SHARED_CSS,
appCss: DEFAULT_APP_CSS, appCss: DEFAULT_APP_CSS,
flags: {} flags: {},
}); });
let assets = $state<AssetStore>({}); let assets = $state<AssetStore>({});
let viewport = $state<Viewport>({ x: 0, y: 0, zoom: 1 }); let viewport = $state<Viewport>({ x: 0, y: 0, zoom: 1 });
let selectedIds = $state<Set<string>>(new Set()); let selectedIds = new SvelteSet<string>();
let editingId = $state<string | null>(null); let editingId = $state<string | null>(null);
let editingGlobal = $state<boolean>(false); let editingGlobal = $state<boolean>(false);
let focusedId = $state<string | null>(null); let focusedId = $state<string | null>(null);
@@ -81,8 +82,10 @@ function createState() {
let saveTimeout: ReturnType<typeof setTimeout> | null = null; let saveTimeout: ReturnType<typeof setTimeout> | null = null;
let animationId: number | null = null; let animationId: number | null = null;
let maxZIndex = $derived( const maxZIndex = $derived(
manifest.items.length > 0 ? Math.max(...manifest.items.map((i) => i.zIndex)) : 0 manifest.items.length > 0
? Math.max(...manifest.items.map((i) => i.zIndex))
: 0,
); );
async function save() { async function save() {
@@ -92,7 +95,7 @@ function createState() {
for (const [id, asset] of Object.entries(assets)) { for (const [id, asset] of Object.entries(assets)) {
storedAssets[id] = { storedAssets[id] = {
dataUrl: await blobToDataUrl(asset.blob), dataUrl: await blobToDataUrl(asset.blob),
filename: asset.filename filename: asset.filename,
}; };
} }
const stored: StoredState = { manifest, assets: storedAssets }; const stored: StoredState = { manifest, assets: storedAssets };
@@ -168,11 +171,11 @@ function createState() {
} }
function select(id: string | null) { function select(id: string | null) {
selectedIds = new Set(id ? [id] : []); selectedIds = new SvelteSet(id ? [id] : []);
} }
function toggleSelection(id: string) { function toggleSelection(id: string) {
const newSet = new Set(selectedIds); const newSet = new SvelteSet(selectedIds);
if (newSet.has(id)) { if (newSet.has(id)) {
newSet.delete(id); newSet.delete(id);
} else { } else {
@@ -182,7 +185,7 @@ function createState() {
} }
function clearSelection() { function clearSelection() {
selectedIds = new Set(); selectedIds = new SvelteSet();
} }
function edit(id: string | null) { function edit(id: string | null) {
@@ -219,19 +222,27 @@ function createState() {
function copySelected() { function copySelected() {
if (selectedIds.size === 0) return; if (selectedIds.size === 0) return;
const items = manifest.items.filter((i) => selectedIds.has(i.id)); const items = manifest.items.filter((i) => selectedIds.has(i.id));
clipboard = items.map(({ id, ...rest }) => rest); clipboard = items.map(({ id: _id, ...rest }) => rest);
} }
function pasteItems(x: number, y: number): string[] { function pasteItems(x: number, y: number): string[] {
if (clipboard.length === 0) return []; if (clipboard.length === 0) return [];
const newIds: string[] = []; const newIds: string[] = [];
const centerX = clipboard.reduce((sum, i) => sum + i.x, 0) / clipboard.length; const centerX =
const centerY = clipboard.reduce((sum, i) => sum + i.y, 0) / clipboard.length; clipboard.reduce((sum, i) => sum + i.x, 0) / clipboard.length;
const centerY =
clipboard.reduce((sum, i) => sum + i.y, 0) / clipboard.length;
for (const item of clipboard) { for (const item of clipboard) {
const id = crypto.randomUUID(); const id = crypto.randomUUID();
const offsetX = item.x - centerX; const offsetX = item.x - centerX;
const offsetY = item.y - centerY; const offsetY = item.y - centerY;
addItem({ ...item, id, x: x + offsetX, y: y + offsetY, zIndex: maxZIndex + 1 }); addItem({
...item,
id,
x: x + offsetX,
y: y + offsetY,
zIndex: maxZIndex + 1,
});
newIds.push(id); newIds.push(id);
} }
return newIds; return newIds;
@@ -316,7 +327,11 @@ function createState() {
return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2; return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
} }
function animateViewport(targetX: number, targetY: number, targetZoom: number) { function animateViewport(
targetX: number,
targetY: number,
targetZoom: number,
) {
if (animationId) cancelAnimationFrame(animationId); if (animationId) cancelAnimationFrame(animationId);
const startX = viewport.x; const startX = viewport.x;
@@ -358,11 +373,11 @@ function createState() {
items: [], items: [],
sharedCss: DEFAULT_SHARED_CSS, sharedCss: DEFAULT_SHARED_CSS,
appCss: DEFAULT_APP_CSS, appCss: DEFAULT_APP_CSS,
flags: {} flags: {},
}; };
assets = {}; assets = {};
viewport = { x: 0, y: 0, zoom: 1 }; viewport = { x: 0, y: 0, zoom: 1 };
selectedIds = new Set(); selectedIds = new SvelteSet();
editingId = null; editingId = null;
editingGlobal = false; editingGlobal = false;
focusedId = null; focusedId = null;
@@ -374,7 +389,7 @@ function createState() {
manifest = newManifest; manifest = newManifest;
assets = newAssets; assets = newAssets;
viewport = { x: 0, y: 0, zoom: 1 }; viewport = { x: 0, y: 0, zoom: 1 };
selectedIds = new Set(); selectedIds = new SvelteSet();
editingId = null; editingId = null;
editingGlobal = false; editingGlobal = false;
focusedId = null; focusedId = null;
@@ -438,7 +453,7 @@ function createState() {
clearFlag, clearFlag,
gotoFlag, gotoFlag,
reset, reset,
load load,
}; };
} }

View File

@@ -3,7 +3,9 @@ import { syntaxHighlighting, HighlightStyle } from '@codemirror/language';
import { tags } from '@lezer/highlight'; import { tags } from '@lezer/highlight';
function getVar(name: string, fallback: string): string { function getVar(name: string, fallback: string): string {
const value = getComputedStyle(document.documentElement).getPropertyValue(name).trim(); const value = getComputedStyle(document.documentElement)
.getPropertyValue(name)
.trim();
return value || fallback; return value || fallback;
} }
@@ -27,104 +29,145 @@ export function createTheme() {
'&': { '&': {
backgroundColor: surface, backgroundColor: surface,
color: '#abb2bf', color: '#abb2bf',
height: '100%' height: '100%',
}, },
'.cm-scroller': { '.cm-scroller': {
overflow: 'auto', overflow: 'auto',
fontFamily: "'Departure Mono', monospace" fontFamily: "'Departure Mono', monospace",
}, },
'.cm-content': { '.cm-content': {
caretColor: accent caretColor: accent,
}, },
'.cm-cursor, .cm-dropCursor': { '.cm-cursor, .cm-dropCursor': {
borderLeftColor: accent borderLeftColor: accent,
}, },
'&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection': '&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection':
{ {
backgroundColor: '#3E4451' backgroundColor: '#3E4451',
}, },
'.cm-panels': { '.cm-panels': {
backgroundColor: surface, backgroundColor: surface,
color: '#abb2bf' color: '#abb2bf',
}, },
'.cm-panels.cm-panels-top': { '.cm-panels.cm-panels-top': {
borderBottom: `1px solid ${border}` borderBottom: `1px solid ${border}`,
}, },
'.cm-panels.cm-panels-bottom': { '.cm-panels.cm-panels-bottom': {
borderTop: `1px solid ${border}` borderTop: `1px solid ${border}`,
}, },
'.cm-searchMatch': { '.cm-searchMatch': {
backgroundColor: '#72a1ff59', backgroundColor: '#72a1ff59',
outline: `1px solid ${border}` outline: `1px solid ${border}`,
}, },
'.cm-searchMatch.cm-searchMatch-selected': { '.cm-searchMatch.cm-searchMatch-selected': {
backgroundColor: '#6199ff2f' backgroundColor: '#6199ff2f',
}, },
'.cm-activeLine': { '.cm-activeLine': {
backgroundColor: '#2c313c50' backgroundColor: '#2c313c50',
}, },
'.cm-selectionMatch': { '.cm-selectionMatch': {
backgroundColor: '#aafe661a' backgroundColor: '#aafe661a',
}, },
'&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket': { '&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket': {
backgroundColor: '#bad0f847' backgroundColor: '#bad0f847',
}, },
'.cm-gutters': { '.cm-gutters': {
backgroundColor: surface, backgroundColor: surface,
color: textDim, color: textDim,
border: 'none' border: 'none',
}, },
'.cm-activeLineGutter': { '.cm-activeLineGutter': {
backgroundColor: '#2c313c50' backgroundColor: '#2c313c50',
}, },
'.cm-foldPlaceholder': { '.cm-foldPlaceholder': {
backgroundColor: 'transparent', backgroundColor: 'transparent',
border: 'none', border: 'none',
color: textDim color: textDim,
}, },
'.cm-tooltip': { '.cm-tooltip': {
border: 'none', border: 'none',
backgroundColor: surface backgroundColor: surface,
}, },
'.cm-tooltip .cm-tooltip-arrow:before': { '.cm-tooltip .cm-tooltip-arrow:before': {
borderTopColor: 'transparent', borderTopColor: 'transparent',
borderBottomColor: 'transparent' borderBottomColor: 'transparent',
}, },
'.cm-tooltip .cm-tooltip-arrow:after': { '.cm-tooltip .cm-tooltip-arrow:after': {
borderTopColor: surface, borderTopColor: surface,
borderBottomColor: surface borderBottomColor: surface,
}, },
'.cm-tooltip-autocomplete': { '.cm-tooltip-autocomplete': {
'& > ul > li[aria-selected]': { '& > ul > li[aria-selected]': {
backgroundColor: accent, backgroundColor: accent,
color: text color: text,
} },
} },
}, },
{ dark: true } { dark: true },
); );
const highlighting = HighlightStyle.define([ const highlighting = HighlightStyle.define([
{ tag: tags.keyword, color: keyword }, { tag: tags.keyword, color: keyword },
{ tag: [tags.name, tags.deleted, tags.character, tags.propertyName, tags.macroName], color: variable }, {
tag: [
tags.name,
tags.deleted,
tags.character,
tags.propertyName,
tags.macroName,
],
color: variable,
},
{ tag: [tags.function(tags.variableName), tags.labelName], color: func }, { tag: [tags.function(tags.variableName), tags.labelName], color: func },
{ tag: [tags.color, tags.constant(tags.name), tags.standard(tags.name)], color: number }, {
tag: [tags.color, tags.constant(tags.name), tags.standard(tags.name)],
color: number,
},
{ tag: [tags.definition(tags.name), tags.separator], color: '#abb2bf' }, { tag: [tags.definition(tags.name), tags.separator], color: '#abb2bf' },
{ tag: [tags.typeName, tags.className, tags.number, tags.changed, tags.annotation, tags.modifier, tags.self, tags.namespace], color: number }, {
{ tag: [tags.operator, tags.operatorKeyword, tags.url, tags.escape, tags.regexp, tags.link, tags.special(tags.string)], color: operator }, tag: [
tags.typeName,
tags.className,
tags.number,
tags.changed,
tags.annotation,
tags.modifier,
tags.self,
tags.namespace,
],
color: number,
},
{
tag: [
tags.operator,
tags.operatorKeyword,
tags.url,
tags.escape,
tags.regexp,
tags.link,
tags.special(tags.string),
],
color: operator,
},
{ tag: [tags.meta, tags.comment], color: comment, fontStyle: 'italic' }, { tag: [tags.meta, tags.comment], color: comment, fontStyle: 'italic' },
{ tag: tags.strong, fontWeight: 'bold' }, { tag: tags.strong, fontWeight: 'bold' },
{ tag: tags.emphasis, fontStyle: 'italic' }, { tag: tags.emphasis, fontStyle: 'italic' },
{ tag: tags.strikethrough, textDecoration: 'line-through' }, { tag: tags.strikethrough, textDecoration: 'line-through' },
{ tag: tags.link, color: comment, textDecoration: 'underline' }, { tag: tags.link, color: comment, textDecoration: 'underline' },
{ tag: tags.heading, fontWeight: 'bold', color: variable }, { tag: tags.heading, fontWeight: 'bold', color: variable },
{ tag: [tags.atom, tags.bool, tags.special(tags.variableName)], color: number }, {
{ tag: [tags.processingInstruction, tags.string, tags.inserted], color: string }, tag: [tags.atom, tags.bool, tags.special(tags.variableName)],
color: number,
},
{
tag: [tags.processingInstruction, tags.string, tags.inserted],
color: string,
},
{ tag: tags.invalid, color: '#ff0000' }, { tag: tags.invalid, color: '#ff0000' },
{ tag: tags.tagName, color: variable }, { tag: tags.tagName, color: variable },
{ tag: tags.attributeName, color: number }, { tag: tags.attributeName, color: number },
{ tag: tags.attributeValue, color: string }, { tag: tags.attributeValue, color: string },
{ tag: tags.propertyName, color: func } { tag: tags.propertyName, color: func },
]); ]);
return [theme, syntaxHighlighting(highlighting)]; return [theme, syntaxHighlighting(highlighting)];

View File

@@ -1,9 +1,9 @@
import { mount } from 'svelte' import { mount } from 'svelte';
import './app.css' import './app.css';
import App from './App.svelte' import App from './App.svelte';
const app = mount(App, { const app = mount(App, {
target: document.getElementById('app')!, target: document.getElementById('app')!,
}) });
export default app export default app;

View File

@@ -1,8 +1,8 @@
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
/** @type {import("@sveltejs/vite-plugin-svelte").SvelteConfig} */ /** @type {import("@sveltejs/vite-plugin-svelte").SvelteConfig} */
export default { export default {
// Consult https://svelte.dev/docs#compile-time-svelte-preprocess // Consult https://svelte.dev/docs#compile-time-svelte-preprocess
// for more information about preprocessors // for more information about preprocessors
preprocess: vitePreprocess(), preprocess: vitePreprocess(),
} };

View File

@@ -1,21 +1,21 @@
{ {
"extends": "@tsconfig/svelte/tsconfig.json", "extends": "@tsconfig/svelte/tsconfig.json",
"compilerOptions": { "compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "ES2022", "target": "ES2022",
"useDefineForClassFields": true, "useDefineForClassFields": true,
"module": "ESNext", "module": "ESNext",
"types": ["svelte", "vite/client"], "types": ["svelte", "vite/client"],
"noEmit": true, "noEmit": true,
/** /**
* Typecheck JS in `.svelte` and `.js` files by default. * Typecheck JS in `.svelte` and `.js` files by default.
* Disable checkJs if you'd like to use dynamic types in JS. * Disable checkJs if you'd like to use dynamic types in JS.
* Note that setting allowJs false does not prevent the use * Note that setting allowJs false does not prevent the use
* of JS in `.svelte` files. * of JS in `.svelte` files.
*/ */
"allowJs": true, "allowJs": true,
"checkJs": true, "checkJs": true,
"moduleDetection": "force" "moduleDetection": "force"
}, },
"include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"] "include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"]
} }

View File

@@ -1,7 +1,7 @@
{ {
"files": [], "files": [],
"references": [ "references": [
{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" } { "path": "./tsconfig.node.json" }
] ]
} }

View File

@@ -1,26 +1,26 @@
{ {
"compilerOptions": { "compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"target": "ES2023", "target": "ES2023",
"lib": ["ES2023"], "lib": ["ES2023"],
"module": "ESNext", "module": "ESNext",
"types": ["node"], "types": ["node"],
"skipLibCheck": true, "skipLibCheck": true,
/* Bundler mode */ /* Bundler mode */
"moduleResolution": "bundler", "moduleResolution": "bundler",
"allowImportingTsExtensions": true, "allowImportingTsExtensions": true,
"verbatimModuleSyntax": true, "verbatimModuleSyntax": true,
"moduleDetection": "force", "moduleDetection": "force",
"noEmit": true, "noEmit": true,
/* Linting */ /* Linting */
"strict": true, "strict": true,
"noUnusedLocals": true, "noUnusedLocals": true,
"noUnusedParameters": true, "noUnusedParameters": true,
"erasableSyntaxOnly": true, "erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true, "noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true "noUncheckedSideEffectImports": true
}, },
"include": ["vite.config.ts"] "include": ["vite.config.ts"]
} }

View File

@@ -1,7 +1,7 @@
import { defineConfig } from 'vite' import { defineConfig } from 'vite';
import { svelte } from '@sveltejs/vite-plugin-svelte' import { svelte } from '@sveltejs/vite-plugin-svelte';
// https://vite.dev/config/ // https://vite.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [svelte()], plugins: [svelte()],
}) });