From 1377ab51a5b48be9d27e2a24f8a2c797be27a2d3 Mon Sep 17 00:00:00 2001 From: Elijah Potter Date: Mon, 24 Nov 2025 09:45:05 -0700 Subject: [PATCH] feat: create component library + use in relevant places (#2229) --- Dockerfile | 8 +- justfile | 21 +- packages/chrome-plugin/app.css | 96 +- packages/chrome-plugin/options.html | 12 + packages/chrome-plugin/package.json | 2 +- packages/chrome-plugin/popup.html | 12 + packages/chrome-plugin/src/manifest.ts | 5 + .../chrome-plugin/src/options/Options.svelte | 89 +- packages/chrome-plugin/src/popup/Main.svelte | 9 +- .../chrome-plugin/src/popup/Onboarding.svelte | 4 +- packages/chrome-plugin/src/popup/Popup.svelte | 18 +- .../src/popup/ReportProblematicLint.svelte | 2 +- packages/components/.gitignore | 24 + packages/components/.npmrc | 1 + packages/components/package.json | 58 + packages/components/src/app.d.ts | 13 + packages/components/src/app.html | 12 + packages/components/src/lib/Button.svelte | 101 ++ packages/components/src/lib/Card.svelte | 16 + .../components/src/lib/Collapsible.svelte | 17 + packages/components/src/lib/Input.svelte | 35 + packages/components/src/lib/Link.svelte | 34 + packages/components/src/lib/Select.svelte | 45 + packages/components/src/lib/Textarea.svelte | 22 + packages/components/src/lib/index.ts | 23 + packages/components/src/lib/styles.css | 74 + packages/components/src/routes/+layout.svelte | 7 + packages/components/src/routes/+page.svelte | 7 + packages/components/src/routes/layout.css | 1 + packages/components/static/favicon.svg | 1 + packages/components/svelte.config.js | 18 + packages/components/tsconfig.json | 16 + packages/components/vite.config.ts | 7 + packages/lint-framework/package.json | 1 + packages/lint-framework/src/index.ts | 1 - .../lint-framework/src/lint/Highlights.ts | 2 +- .../lint-framework/src/lint/SuggestionBox.ts | 34 +- .../lint-framework/src/lint/lintKindColor.ts | 9 +- packages/lint-framework/src/lint/utils.ts | 13 + packages/web/package.json | 2 +- packages/web/src/app.css | 137 +- packages/web/src/app.html | 14 +- .../lib/components/DefaultNeovimConfig.svelte | 2 +- packages/web/src/lib/components/Editor.svelte | 6 +- .../web/src/lib/components/LazyEditor.svelte | 2 +- .../web/src/lib/components/LintCard.svelte | 18 +- .../web/src/lib/components/LintSidebar.svelte | 20 +- .../web/src/lib/components/Testimonial.svelte | 6 +- .../components/TestimonialCollection.svelte | 5 +- packages/web/src/routes/+layout.svelte | 15 +- packages/web/src/routes/+page.svelte | 328 ++-- .../docs/integrations/wordpress/+page.md | 2 +- .../web/src/routes/docs/rules/+page.svelte | 2 +- .../install-browser-extension/+page.svelte | 4 +- .../src/routes/languagedetection/+page.svelte | 2 +- .../web/src/routes/presentation/+page.svelte | 3 +- .../report-problematic-lint/+page.svelte | 2 +- packages/web/src/routes/stats/+page.svelte | 2 +- .../web/src/routes/titlecase/+page.svelte | 6 +- .../uninstall-browser-extension/+page.svelte | 3 +- packages/web/tailwind.config.js | 28 +- packages/web/vite.config.ts | 4 +- pnpm-lock.yaml | 1502 ++++++++++++----- pnpm-workspace.yaml | 1 + 64 files changed, 2163 insertions(+), 823 deletions(-) create mode 100644 packages/components/.gitignore create mode 100644 packages/components/.npmrc create mode 100644 packages/components/package.json create mode 100644 packages/components/src/app.d.ts create mode 100644 packages/components/src/app.html create mode 100644 packages/components/src/lib/Button.svelte create mode 100644 packages/components/src/lib/Card.svelte create mode 100644 packages/components/src/lib/Collapsible.svelte create mode 100644 packages/components/src/lib/Input.svelte create mode 100644 packages/components/src/lib/Link.svelte create mode 100644 packages/components/src/lib/Select.svelte create mode 100644 packages/components/src/lib/Textarea.svelte create mode 100644 packages/components/src/lib/index.ts create mode 100644 packages/components/src/lib/styles.css create mode 100644 packages/components/src/routes/+layout.svelte create mode 100644 packages/components/src/routes/+page.svelte create mode 100644 packages/components/src/routes/layout.css create mode 100644 packages/components/static/favicon.svg create mode 100644 packages/components/svelte.config.js create mode 100644 packages/components/tsconfig.json create mode 100644 packages/components/vite.config.ts create mode 100644 packages/lint-framework/src/lint/utils.ts diff --git a/Dockerfile b/Dockerfile index 9464e5e1..0be4ae5a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,7 +27,11 @@ WORKDIR /usr/build/ COPY . . COPY --from=wasm-build /usr/build/harper-wasm/pkg /usr/build/harper-wasm/pkg -RUN pnpm install --shamefully-hoist +RUN pnpm install --engine-strict=false --shamefully-hoist + +WORKDIR /usr/build/packages/components +RUN pnpm install --engine-strict=false --shamefully-hoist +RUN pnpm build WORKDIR /usr/build/packages/harper.js @@ -37,7 +41,7 @@ WORKDIR /usr/build/packages/lint-framework RUN pnpm build WORKDIR /usr/build/packages/web -RUN pnpm install --shamefully-hoist +RUN pnpm install --engine-strict=false --shamefully-hoist RUN pnpm build FROM node:${NODE_VERSION} diff --git a/justfile b/justfile index 52ea6ed9..318fa639 100644 --- a/justfile +++ b/justfile @@ -3,6 +3,15 @@ format: cargo fmt pnpm format +# Build the shared component library +build-components: + #!/usr/bin/env bash + set -eo pipefail + + cd "{{justfile_directory()}}/packages/components" + pnpm install --engine-strict=false + pnpm build + # Build the WebAssembly module build-wasm: #!/usr/bin/env bash @@ -80,7 +89,7 @@ build-wp: build-harperjs pnpm plugin-zip # Compile the website's dependencies and start a development server. Note that if you make changes to `harper-wasm`, you will have to re-run this command. -dev-web: build-harperjs build-lint-framework +dev-web: build-harperjs build-lint-framework build-components #!/usr/bin/env bash set -eo pipefail @@ -89,7 +98,7 @@ dev-web: build-harperjs build-lint-framework pnpm dev # Build the Harper website. -build-web: build-harperjs build-lint-framework +build-web: build-harperjs build-lint-framework build-components #!/usr/bin/env bash set -eo pipefail @@ -110,7 +119,7 @@ build-obsidian: build-harperjs zip harper-obsidian-plugin.zip manifest.json main.js # Build the Chrome extension. -build-chrome-plugin: build-harperjs build-lint-framework +build-chrome-plugin: build-harperjs build-lint-framework build-components #!/usr/bin/env bash set -eo pipefail @@ -120,7 +129,7 @@ build-chrome-plugin: build-harperjs build-lint-framework pnpm zip-for-chrome # Start a development server for the Chrome extension. -dev-chrome-plugin: build-harperjs build-lint-framework +dev-chrome-plugin: build-harperjs build-lint-framework build-components #!/usr/bin/env bash set -eo pipefail @@ -130,7 +139,7 @@ dev-chrome-plugin: build-harperjs build-lint-framework pnpm dev # Build the Firefox extension. -build-firefox-plugin: build-harperjs build-lint-framework +build-firefox-plugin: build-harperjs build-lint-framework build-components #!/usr/bin/env bash set -eo pipefail @@ -272,7 +281,7 @@ check-rust: auditdictionary # Perform format and type checking. check: check-rust check-js build-web -check-js: build-harperjs build-lint-framework +check-js: build-harperjs build-lint-framework build-components #!/usr/bin/env bash set -eo pipefail diff --git a/packages/chrome-plugin/app.css b/packages/chrome-plugin/app.css index 70e9f43a..9063eb38 100644 --- a/packages/chrome-plugin/app.css +++ b/packages/chrome-plugin/app.css @@ -1,36 +1,88 @@ @import "tailwindcss"; - -@plugin "flowbite/plugin"; +@import "components/components.css"; @custom-variant dark (&:where(.dark, .dark *)); @theme { - --color-primary-50: #fefee3; - --color-primary-100: #e3eccf; - --color-primary-200: #c9dabc; - --color-primary-300: #afc8a9; - --color-primary-400: #95b696; - --color-primary-500: #7aa482; - --color-primary-600: #60926f; - --color-primary-700: #46805c; - --color-primary-800: #2c6e49; - --color-primary-900: #23583a; + --font-sans: "Atkinson Hyperlegible", sans-serif; + --font-serif: Domine, serif; - --color-accent-peach: #ffc9b9; - --color-accent-sand: #d68c45; + --color-primary-50: #fef4e7; /* honey bronze */ + --color-primary-100: #fce9cf; + --color-primary-200: #f9d49f; + --color-primary-300: #f7be6e; + --color-primary-400: #f4a83e; + --color-primary: #f1920e; + --color-primary-600: #c1750b; + --color-primary-700: #915808; + --color-primary-800: #603b06; + --color-primary-900: #301d03; + --color-primary-950: #221402; + + --color-accent-50: #fee7e9; /* hot fuchsia */ + --color-accent-100: #fccfd3; + --color-accent-200: #f99fa6; + --color-accent-300: #f76e7a; + --color-accent-400: #f43e4d; + --color-accent: #f10e21; + --color-accent-600: #c10b1a; + --color-accent-700: #910814; + --color-accent-800: #60060d; + --color-accent-900: #300307; + --color-accent-950: #220205; + + --color-cream: #fef4e7; /* simple cream */ + --color-cream-100: #fce9cf; + --color-cream-200: #f9d49f; + --color-cream-300: #f7be6e; + --color-cream-400: #f4a83e; + --color-cream-500: #f1920e; + --color-cream-600: #c1750b; + --color-cream-700: #915808; + --color-cream-800: #603b06; + --color-cream-900: #301d03; + --color-cream-950: #221402; + + --color-champagne-mist-50: #fef4e7; + --color-champagne-mist-100: #fce9cf; + --color-champagne-mist-200: #fad49e; + --color-champagne-mist-300: #f7be6e; + --color-champagne-mist-400: #f5a83d; + --color-champagne-mist-500: #f2930d; + --color-champagne-mist-600: #c2750a; + --color-champagne-mist-700: #915808; + --color-champagne-mist-800: #613b05; + --color-champagne-mist-900: #301d03; + --color-champagne-mist-950: #221502; + + --color-white: #fffdfa; + --color-white-100: #fceacf; + --color-white-200: #fad59e; + --color-white-300: #f7c06e; + --color-white-400: #f5ab3d; + --color-white-500: #f2960d; + --color-white-600: #c2780a; + --color-white-700: #915a08; + --color-white-800: #613c05; + --color-white-900: #301e03; + --color-white-950: #221502; } -@source "./node_modules/flowbite-svelte/dist"; - code { - @apply bg-primary-100 rounded p-1; + @apply bg-primary-100 rounded p-1 dark:text-black; } -body { - @apply min-h-screen bg-white text-gray-900 transition-colors duration-150; +#app { + @apply min-h-screen bg-white text-black dark:bg-black dark:text-white transition-colors duration-150; + + font-family: + Atkinson Hyperlegible, + sans-serif; } -.dark body, -body.dark { - @apply bg-slate-950 text-slate-100; +h1, +h2, +h3, +h4 { + font-family: Domine, serif; } diff --git a/packages/chrome-plugin/options.html b/packages/chrome-plugin/options.html index 0c68e920..003305d4 100644 --- a/packages/chrome-plugin/options.html +++ b/packages/chrome-plugin/options.html @@ -6,6 +6,18 @@ Harper Settings + + + + + + diff --git a/packages/chrome-plugin/package.json b/packages/chrome-plugin/package.json index 1c92ccda..499e767f 100644 --- a/packages/chrome-plugin/package.json +++ b/packages/chrome-plugin/package.json @@ -32,7 +32,6 @@ "@types/lodash-es": "^4.17.12", "@types/node": "catalog:", "flowbite": "^3.1.2", - "flowbite-svelte": "^0.44.18", "gulp": "^5.0.0", "gulp-zip": "^6.0.0", "http-server": "^14.1.1", @@ -51,6 +50,7 @@ "dependencies": { "@fortawesome/free-solid-svg-icons": "^7.1.0", "@webcomponents/custom-elements": "^1.6.0", + "components": "workspace:*", "harper.js": "workspace:*", "lint-framework": "workspace:*", "lodash-es": "^4.17.21", diff --git a/packages/chrome-plugin/popup.html b/packages/chrome-plugin/popup.html index 0de27503..0bf6d57a 100644 --- a/packages/chrome-plugin/popup.html +++ b/packages/chrome-plugin/popup.html @@ -5,6 +5,18 @@ Harper: The Private Grammar Checker + + + + + + diff --git a/packages/chrome-plugin/src/manifest.ts b/packages/chrome-plugin/src/manifest.ts index 4c1e116c..fb81d04f 100644 --- a/packages/chrome-plugin/src/manifest.ts +++ b/packages/chrome-plugin/src/manifest.ts @@ -15,12 +15,15 @@ export function makeExtensionCSP(isDev: boolean): string { const scriptSrc = ["'self'", "'wasm-unsafe-eval'"]; // minimum, cannot add more const objectSrc = ["'self'"]; // standard const connectSrc = ["'self'"]; // WebSocket goes here + const styleSrc = ["'self'", "'unsafe-inline'", 'https://fonts.googleapis.com']; + const fontSrc = ["'self'", 'https://fonts.gstatic.com', 'data:']; if (isDev) { // `ws://` and `http://` use the same host:port → list both connectSrc.push('http://localhost:5173', 'ws://localhost:5173'); // include the 127.0.0.1 loopback in case you switch hosts connectSrc.push('http://127.0.0.1:*', 'ws://127.0.0.1:*'); + styleSrc.push('http://localhost:5173', 'http://127.0.0.1:*'); } connectSrc.push('https://writewithharper.com'); @@ -30,6 +33,8 @@ export function makeExtensionCSP(isDev: boolean): string { `script-src ${scriptSrc.join(' ')}`, `object-src ${objectSrc.join(' ')}`, `connect-src ${connectSrc.join(' ')}`, + `style-src ${styleSrc.join(' ')}`, + `font-src ${fontSrc.join(' ')}`, ].join('; ')};`; } diff --git a/packages/chrome-plugin/src/options/Options.svelte b/packages/chrome-plugin/src/options/Options.svelte index 3f0923f5..72351fc0 100644 --- a/packages/chrome-plugin/src/options/Options.svelte +++ b/packages/chrome-plugin/src/options/Options.svelte @@ -1,5 +1,5 @@ -
-
- Harper logo - Harper -
+
+
+ +
+ Harper logo +
+
+

Harper

+

Chrome Extension Settings

+
+
-
-
-

General

+ +

General

- English Dialect +

English Dialect

+
- Export Enabled Domains - Downloads JSON of domains explicitly enabled. +

Export Enabled Domains

+

Downloads JSON of domains explicitly enabled.

- +
- -
- Activation Key - If you're finding that you're accidentally triggering Harper. +

Activation Key

+

+ If you're finding that you're accidentally triggering Harper. +

+ >
-
+ -
+
-

Rules

+

Rules

- - +
@@ -256,21 +256,19 @@ async function exportEnabledDomainsCSV() { (lintDescriptions[key] ?? '').toLowerCase().includes(searchQueryLower) || key.toLowerCase().includes(searchQueryLower) ) as [key, value]} -
+
-
-

{key}

-

{@html lintDescriptions[key]}

+

{key}

+

{@html lintDescriptions[key]}

-
{/each} -
-
+ +
diff --git a/packages/chrome-plugin/src/popup/Main.svelte b/packages/chrome-plugin/src/popup/Main.svelte index 76a5d38b..b85cbe6f 100644 --- a/packages/chrome-plugin/src/popup/Main.svelte +++ b/packages/chrome-plugin/src/popup/Main.svelte @@ -1,5 +1,5 @@
-
+
Harper logo Harper
{#if popupState.page != "main"} - + }}> {/if}
@@ -48,9 +48,9 @@ function openSettings() { {/if}
diff --git a/packages/chrome-plugin/src/popup/ReportProblematicLint.svelte b/packages/chrome-plugin/src/popup/ReportProblematicLint.svelte index c6e15e57..7ff482fc 100644 --- a/packages/chrome-plugin/src/popup/ReportProblematicLint.svelte +++ b/packages/chrome-plugin/src/popup/ReportProblematicLint.svelte @@ -1,5 +1,5 @@ + +{#if href} + + + +{:else} + +{/if} diff --git a/packages/components/src/lib/Card.svelte b/packages/components/src/lib/Card.svelte new file mode 100644 index 00000000..49fcfe2e --- /dev/null +++ b/packages/components/src/lib/Card.svelte @@ -0,0 +1,16 @@ + + +
+ +
diff --git a/packages/components/src/lib/Collapsible.svelte b/packages/components/src/lib/Collapsible.svelte new file mode 100644 index 00000000..f8b5ef89 --- /dev/null +++ b/packages/components/src/lib/Collapsible.svelte @@ -0,0 +1,17 @@ + + +
+ {title} +
+ +
+
diff --git a/packages/components/src/lib/Input.svelte b/packages/components/src/lib/Input.svelte new file mode 100644 index 00000000..127254d8 --- /dev/null +++ b/packages/components/src/lib/Input.svelte @@ -0,0 +1,35 @@ + + + diff --git a/packages/components/src/lib/Link.svelte b/packages/components/src/lib/Link.svelte new file mode 100644 index 00000000..ca57911c --- /dev/null +++ b/packages/components/src/lib/Link.svelte @@ -0,0 +1,34 @@ + + + + + diff --git a/packages/components/src/lib/Select.svelte b/packages/components/src/lib/Select.svelte new file mode 100644 index 00000000..c2300d17 --- /dev/null +++ b/packages/components/src/lib/Select.svelte @@ -0,0 +1,45 @@ + + + diff --git a/packages/components/src/lib/Textarea.svelte b/packages/components/src/lib/Textarea.svelte new file mode 100644 index 00000000..c24b00b0 --- /dev/null +++ b/packages/components/src/lib/Textarea.svelte @@ -0,0 +1,22 @@ + + + diff --git a/packages/components/src/lib/index.ts b/packages/components/src/lib/index.ts new file mode 100644 index 00000000..3f90d0ed --- /dev/null +++ b/packages/components/src/lib/index.ts @@ -0,0 +1,23 @@ +export { + Badge, + Checkbox, + Fileupload, + Label, + Radio, + Spinner, + Table, + TableBody, + TableBodyCell, + TableBodyRow, + TableHead, + TableHeadCell, + Toggle, +} from 'flowbite-svelte'; + +export { default as Button } from './Button.svelte'; +export { default as Card } from './Card.svelte'; +export { default as Collapsible } from './Collapsible.svelte'; +export { default as Input } from './Input.svelte'; +export { default as Link } from './Link.svelte'; +export { default as Select } from './Select.svelte'; +export { default as Textarea } from './Textarea.svelte'; diff --git a/packages/components/src/lib/styles.css b/packages/components/src/lib/styles.css new file mode 100644 index 00000000..0b35f5a9 --- /dev/null +++ b/packages/components/src/lib/styles.css @@ -0,0 +1,74 @@ +@import "tailwindcss"; + +@plugin "flowbite/plugin"; + +@custom-variant dark (&:where(.dark, .dark *)); + +@source "./src/lib/**/*.{svelte,ts}"; +@source "./node_modules/flowbite-svelte/**/*.{svelte,ts,js}"; + +@theme { + --color-primary-50: #fef4e7; /* honey bronze */ + --color-primary-100: #fce9cf; + --color-primary-200: #f9d49f; + --color-primary-300: #f7be6e; + --color-primary-400: #f4a83e; + --color-primary: #f1920e; + --color-primary-600: #c1750b; + --color-primary-700: #915808; + --color-primary-800: #603b06; + --color-primary-900: #301d03; + --color-primary-950: #221402; + + --color-accent-50: #fee7e9; /* hot fuchsia */ + --color-accent-100: #fccfd3; + --color-accent-200: #f99fa6; + --color-accent-300: #f76e7a; + --color-accent-400: #f43e4d; + --color-accent: #f10e21; + --color-accent-600: #c10b1a; + --color-accent-700: #910814; + --color-accent-800: #60060d; + --color-accent-900: #300307; + --color-accent-950: #220205; + + --color-cream: #fef4e7; /* simple cream */ + --color-cream-100: #fce9cf; + --color-cream-200: #f9d49f; + --color-cream-300: #f7be6e; + --color-cream-400: #f4a83e; + --color-cream-500: #f1920e; + --color-cream-600: #c1750b; + --color-cream-700: #915808; + --color-cream-800: #603b06; + --color-cream-900: #301d03; + --color-cream-950: #221402; + + --color-champagne-mist-50: #fef4e7; + --color-champagne-mist-100: #fce9cf; + --color-champagne-mist-200: #fad49e; + --color-champagne-mist-300: #f7be6e; + --color-champagne-mist-400: #f5a83d; + --color-champagne-mist-500: #f2930d; + --color-champagne-mist-600: #c2750a; + --color-champagne-mist-700: #915808; + --color-champagne-mist-800: #613b05; + --color-champagne-mist-900: #301d03; + --color-champagne-mist-950: #221502; + + --color-white: #fffdfa; + --color-white-100: #fceacf; + --color-white-200: #fad59e; + --color-white-300: #f7c06e; + --color-white-400: #f5ab3d; + --color-white-500: #f2960d; + --color-white-600: #c2780a; + --color-white-700: #915a08; + --color-white-800: #613c05; + --color-white-900: #301e03; + --color-white-950: #221502; +} + +body { + @apply bg-white dark:bg-white-900 dark:text-white; +} diff --git a/packages/components/src/routes/+layout.svelte b/packages/components/src/routes/+layout.svelte new file mode 100644 index 00000000..b4059508 --- /dev/null +++ b/packages/components/src/routes/+layout.svelte @@ -0,0 +1,7 @@ + + +{@render children()} diff --git a/packages/components/src/routes/+page.svelte b/packages/components/src/routes/+page.svelte new file mode 100644 index 00000000..da127aeb --- /dev/null +++ b/packages/components/src/routes/+page.svelte @@ -0,0 +1,7 @@ + + +

Welcome to your library project

+

Create your package using @sveltejs/package and preview/showcase your work with SvelteKit

+

Visit svelte.dev/docs/kit to read the documentation

diff --git a/packages/components/src/routes/layout.css b/packages/components/src/routes/layout.css new file mode 100644 index 00000000..f1d8c73c --- /dev/null +++ b/packages/components/src/routes/layout.css @@ -0,0 +1 @@ +@import "tailwindcss"; diff --git a/packages/components/static/favicon.svg b/packages/components/static/favicon.svg new file mode 100644 index 00000000..cc5dc66a --- /dev/null +++ b/packages/components/static/favicon.svg @@ -0,0 +1 @@ +svelte-logo \ No newline at end of file diff --git a/packages/components/svelte.config.js b/packages/components/svelte.config.js new file mode 100644 index 00000000..3ad145a2 --- /dev/null +++ b/packages/components/svelte.config.js @@ -0,0 +1,18 @@ +import adapter from '@sveltejs/adapter-auto'; +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; + +/** @type {import('@sveltejs/kit').Config} */ +const config = { + // Consult https://svelte.dev/docs/kit/integrations + // for more information about preprocessors + preprocess: vitePreprocess(), + + kit: { + // adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list. + // If your environment is not supported, or you settled on a specific environment, switch out the adapter. + // See https://svelte.dev/docs/kit/adapters for more information about adapters. + adapter: adapter(), + }, +}; + +export default config; diff --git a/packages/components/tsconfig.json b/packages/components/tsconfig.json new file mode 100644 index 00000000..371777ed --- /dev/null +++ b/packages/components/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "./.svelte-kit/tsconfig.json", + "compilerOptions": { + "allowImportingTsExtensions": true, + "allowJs": true, + "checkJs": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "module": "NodeNext", + "moduleResolution": "NodeNext" + } +} diff --git a/packages/components/vite.config.ts b/packages/components/vite.config.ts new file mode 100644 index 00000000..da1f9251 --- /dev/null +++ b/packages/components/vite.config.ts @@ -0,0 +1,7 @@ +import { sveltekit } from '@sveltejs/kit/vite'; +import tailwindcss from '@tailwindcss/vite'; +import { defineConfig } from 'vite'; + +export default defineConfig({ + plugins: [tailwindcss(), sveltekit()], +}); diff --git a/packages/lint-framework/package.json b/packages/lint-framework/package.json index 64f72de1..26e7b072 100644 --- a/packages/lint-framework/package.json +++ b/packages/lint-framework/package.json @@ -24,6 +24,7 @@ "@fortawesome/fontawesome-svg-core": "^7.1.0", "@fortawesome/free-solid-svg-icons": "^7.1.0", "bowser": "^2.12.1", + "colorjs.io": "^0.5.2", "virtual-dom": "^2.1.1" }, "peerDependencies": { diff --git a/packages/lint-framework/src/index.ts b/packages/lint-framework/src/index.ts index a4f1e2b5..5356c178 100644 --- a/packages/lint-framework/src/index.ts +++ b/packages/lint-framework/src/index.ts @@ -5,7 +5,6 @@ export * from './lint/editorUtils'; export { default as Highlights } from './lint/Highlights'; export { default as LintFramework } from './lint/LintFramework'; export * from './lint/lintKindColor'; -export { default as lintKindColor } from './lint/lintKindColor'; export { default as PopupHandler } from './lint/PopupHandler'; export { default as RenderBox } from './lint/RenderBox'; export * from './lint/unpackLint'; diff --git a/packages/lint-framework/src/lint/Highlights.ts b/packages/lint-framework/src/lint/Highlights.ts index 13933d2b..e7373d8b 100644 --- a/packages/lint-framework/src/lint/Highlights.ts +++ b/packages/lint-framework/src/lint/Highlights.ts @@ -17,7 +17,7 @@ import { getSlateRoot, getTrixRoot, } from './editorUtils'; -import lintKindColor, { type LintKind } from './lintKindColor'; +import { type LintKind, lintKindColor } from './lintKindColor'; import RenderBox from './RenderBox'; import type SourceElement from './SourceElement'; import type { UnpackedLint } from './unpackLint'; diff --git a/packages/lint-framework/src/lint/SuggestionBox.ts b/packages/lint-framework/src/lint/SuggestionBox.ts index adf73ed7..e46f5013 100644 --- a/packages/lint-framework/src/lint/SuggestionBox.ts +++ b/packages/lint-framework/src/lint/SuggestionBox.ts @@ -5,7 +5,7 @@ import type { VNode } from 'virtual-dom'; import h from 'virtual-dom/h'; import bookDownSvg from '../assets/bookDownSvg'; import type { IgnorableLintBox, LintBox } from './Box'; -import lintKindColor from './lintKindColor'; +import { type LintKind, lintKindColor, lintKindTextColor } from './lintKindColor'; // Decoupled: actions passed in by framework consumer import type { UnpackedLint, UnpackedSuggestion } from './unpackLint'; @@ -190,6 +190,7 @@ function addToDictionary( } function suggestions( + lintKind: LintKind, suggestions: UnpackedSuggestion[], apply: (s: UnpackedSuggestion) => void, ): any { @@ -197,7 +198,13 @@ function suggestions( const label = s.replacement_text !== '' ? s.replacement_text : String(s.kind); const desc = `Replace with "${label}"`; const props = i === 0 ? { hook: new FocusHook() } : {}; - return button(label, { background: '#2DA44E', color: '#FFFFFF' }, () => apply(s), desc, props); + return button( + label, + { background: lintKindColor(lintKind), color: lintKindTextColor(lintKind) }, + () => apply(s), + desc, + props, + ); }); } @@ -221,10 +228,10 @@ function reportProblemButton(reportError?: () => Promise): any { ); } -function styleTag() { +function styleTag(lintKind: LintKind) { return h('style', { id: 'harper-suggestion-style' }, [ `code{ - background-color:#e3eccf; + text-decoration: underline solid ${lintKindColor(lintKind)} 2px; padding:0.125rem; border-radius:0.25rem } @@ -351,10 +358,16 @@ function styleTag() { animation: fadeIn 100ms ease-in-out forwards; } - @keyframes fadeIn { - from { opacity: 0; } - to { opacity: 1; } - } + @keyframes fadeIn { + from { + opacity: 0; + transform: scale(0.95); + } + to { + opacity: 1; + transform: scale(1); + } + } @media (prefers-color-scheme:dark){ code{background-color:#1f2d3d;color:#c9d1d9} @@ -437,6 +450,7 @@ export default function SuggestionBox( top: bottom ? '' : `${top}px`, bottom: bottom ? `${bottom}px` : '', left: `${left}px`, + transformOrigin: `${bottom ? 'bottom' : 'top'} left`, }; return h( @@ -447,7 +461,7 @@ export default function SuggestionBox( 'harper-close-on-escape': new CloseOnEscapeHook(close), }, [ - styleTag(), + styleTag(box.lint.lint_kind), header( box.lint.lint_kind_pretty, lintKindColor(box.lint.lint_kind), @@ -458,7 +472,7 @@ export default function SuggestionBox( ), body(box.lint.message_html), footer( - suggestions(box.lint.suggestions, (v) => { + suggestions(box.lint.lint_kind, box.lint.suggestions, (v) => { box.applySuggestion(v); close(); }), diff --git a/packages/lint-framework/src/lint/lintKindColor.ts b/packages/lint-framework/src/lint/lintKindColor.ts index 2c0cbda6..49c64749 100644 --- a/packages/lint-framework/src/lint/lintKindColor.ts +++ b/packages/lint-framework/src/lint/lintKindColor.ts @@ -1,3 +1,5 @@ +import { getContrastingTextColor } from './utils'; + // First, define the color map as a constant const LINT_KIND_COLORS = { Agreement: '#228B22', // Forest green @@ -29,10 +31,15 @@ export type LintKind = keyof typeof LINT_KIND_COLORS; export const LINT_KINDS = Object.keys(LINT_KIND_COLORS) as LintKind[]; // The main function that uses the map -export default function lintKindColor(lintKindKey: string): string { +export function lintKindColor(lintKindKey: string): string { const color = LINT_KIND_COLORS[lintKindKey as LintKind]; if (!color) { throw new Error(`Unexpected lint kind: ${lintKindKey}`); } return color; } + +export function lintKindTextColor(lintKindKeyOrColor: string): 'black' | 'white' { + const color = LINT_KIND_COLORS[lintKindKeyOrColor as LintKind] ?? lintKindKeyOrColor; + return getContrastingTextColor(color); +} diff --git a/packages/lint-framework/src/lint/utils.ts b/packages/lint-framework/src/lint/utils.ts new file mode 100644 index 00000000..9d73e511 --- /dev/null +++ b/packages/lint-framework/src/lint/utils.ts @@ -0,0 +1,13 @@ +import Color from 'colorjs.io'; + +/** Get the text color that best contrasts with a background of the provided color. */ +export function getContrastingTextColor(color: string): 'black' | 'white' { + const c = new Color(color); + const luminance = c.luminance; + + if (luminance > 0.5) { + return 'black'; + } else { + return 'white'; + } +} diff --git a/packages/web/package.json b/packages/web/package.json index 12fd0c4b..f8934350 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -19,7 +19,6 @@ "autoprefixer": "^10.4.21", "drizzle-kit": "^0.31.5", "flowbite": "^3.1.2", - "flowbite-svelte": "^0.44.18", "svelte": "^5.15.0", "svelte-check": "^4.1.5", "tailwindcss": "^4.1.16", @@ -35,6 +34,7 @@ "@sveltepress/theme-default": "^5.0.7", "@sveltepress/vite": "^1.1.5", "chart.js": "^4.4.8", + "components": "workspace:*", "drizzle-orm": "^0.44.6", "drizzle-zod": "^0.8.3", "harper.js": "workspace:*", diff --git a/packages/web/src/app.css b/packages/web/src/app.css index dd148a09..3370b8f0 100644 --- a/packages/web/src/app.css +++ b/packages/web/src/app.css @@ -1,53 +1,118 @@ @import "tailwindcss"; +@import "components/components.css"; -@layer base { - body { - @apply bg-white text-black dark:bg-gray-900 dark:text-white; - } +@custom-variant dark (&:where(.dark, .dark *)); - ul { - @apply list-disc pl-4; - } +@theme { + --color-primary-50: #fef4e7; /* honey bronze */ + --color-primary-100: #fce9cf; + --color-primary-200: #f9d49f; + --color-primary-300: #f7be6e; + --color-primary-400: #f4a83e; + --color-primary: #f1920e; + --color-primary-600: #c1750b; + --color-primary-700: #915808; + --color-primary-800: #603b06; + --color-primary-900: #301d03; + --color-primary-950: #221402; - ol { - @apply list-decimal pl-4; - } + --color-accent-50: #fee7e9; /* hot fuchsia */ + --color-accent-100: #fccfd3; + --color-accent-200: #f99fa6; + --color-accent-300: #f76e7a; + --color-accent-400: #f43e4d; + --color-accent: #f10e21; + --color-accent-600: #c10b1a; + --color-accent-700: #910814; + --color-accent-800: #60060d; + --color-accent-900: #300307; + --color-accent-950: #220205; - h1 { - @apply text-4xl font-extrabold tracking-tight lg:text-5xl py-4; - } + --color-cream: #fef4e7; /* simple cream */ + --color-cream-100: #fce9cf; + --color-cream-200: #f9d49f; + --color-cream-300: #f7be6e; + --color-cream-400: #f4a83e; + --color-cream-500: #f1920e; + --color-cream-600: #c1750b; + --color-cream-700: #915808; + --color-cream-800: #603b06; + --color-cream-900: #301d03; + --color-cream-950: #221402; - h2 { - @apply text-3xl font-semibold tracking-tight py-4; - } + --color-champagne-mist-50: #fef4e7; + --color-champagne-mist-100: #fce9cf; + --color-champagne-mist-200: #fad49e; + --color-champagne-mist-300: #f7be6e; + --color-champagne-mist-400: #f5a83d; + --color-champagne-mist-500: #f2930d; + --color-champagne-mist-600: #c2750a; + --color-champagne-mist-700: #915808; + --color-champagne-mist-800: #613b05; + --color-champagne-mist-900: #301d03; + --color-champagne-mist-950: #221502; - h3 { - @apply text-2xl font-semibold tracking-tight py-4; - } - - h4 { - @apply text-xl font-semibold tracking-tight; - } - - p { - @apply leading-7 [&:not(:first-child)]:mt-6; - } - - a { - @apply underline-offset-4 underline; - } - - blockquote { - @apply mt-6 border-l-2 border-gray-200 pl-6 italic dark:border-gray-700; - } + --color-white: #fffdfa; + --color-white-100: #fceacf; + --color-white-200: #fad59e; + --color-white-300: #f7c06e; + --color-white-400: #f5ab3d; + --color-white-500: #f2960d; + --color-white-600: #c2780a; + --color-white-700: #915a08; + --color-white-800: #613c05; + --color-white-900: #301e03; + --color-white-950: #221502; } -* { +body { + @apply bg-white text-black dark:bg-black dark:text-white; + font-family: Atkinson Hyperlegible, sans-serif; } +ul { + @apply list-disc pl-4; +} + +ol { + @apply list-decimal pl-4; +} + +h1 { + @apply text-4xl font-extrabold tracking-tight lg:text-5xl py-4; + font-family: Domine, serif; +} + +h2 { + @apply text-3xl font-semibold tracking-tight py-4; + font-family: Domine, serif; +} + +h3 { + @apply text-2xl font-semibold tracking-tight py-4; + font-family: Domine, serif; +} + +h4 { + @apply text-xl font-semibold tracking-tight; + font-family: Domine, serif; +} + +p { + @apply leading-7 [&:not(:first-child)]:mt-6; +} + +a { + @apply underline-offset-4 text-black dark:text-white; +} + +blockquote { + @apply mt-6 border-l-2 border-gray-200 pl-6 italic dark:border-gray-700; +} + code { font-family: "JetBrains Mono", monospace; word-break: keep-all; diff --git a/packages/web/src/app.html b/packages/web/src/app.html index 58bc9add..dcff643f 100644 --- a/packages/web/src/app.html +++ b/packages/web/src/app.html @@ -70,10 +70,22 @@ + + + + + + %sveltekit.head% - +
%sveltekit.body%
diff --git a/packages/web/src/lib/components/DefaultNeovimConfig.svelte b/packages/web/src/lib/components/DefaultNeovimConfig.svelte index 758f6105..4a59cf17 100644 --- a/packages/web/src/lib/components/DefaultNeovimConfig.svelte +++ b/packages/web/src/lib/components/DefaultNeovimConfig.svelte @@ -1,5 +1,5 @@ -
- +
+
{@html content.replace(/\n\n/g, '
')}
diff --git a/packages/web/src/lib/components/LazyEditor.svelte b/packages/web/src/lib/components/LazyEditor.svelte index 00f53cc4..c5735d29 100644 --- a/packages/web/src/lib/components/LazyEditor.svelte +++ b/packages/web/src/lib/components/LazyEditor.svelte @@ -1,5 +1,5 @@ -