create-quicklook 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/LICENSE +23 -0
  2. package/README.markdown +19 -0
  3. package/dist/index.mjs +82 -0
  4. package/files/biome.template.jsonc +28 -0
  5. package/files/ci.yml +27 -0
  6. package/files/gitignore +14 -0
  7. package/files/hk.pkl +27 -0
  8. package/files/mise.toml +21 -0
  9. package/files/release.yml +52 -0
  10. package/package.json +45 -3
  11. package/templates/analog/index.html +18 -0
  12. package/templates/analog/package.json +29 -0
  13. package/templates/analog/quicklook.config.ts +15 -0
  14. package/templates/analog/src/app/app.ts +45 -0
  15. package/templates/analog/src/app/file-viewer.ts +24 -0
  16. package/templates/analog/src/main.ts +4 -0
  17. package/templates/analog/tsconfig.json +16 -0
  18. package/templates/analog/vite.config.ts +15 -0
  19. package/templates/astro/astro.config.ts +6 -0
  20. package/templates/astro/package.json +21 -0
  21. package/templates/astro/quicklook.config.ts +15 -0
  22. package/templates/astro/src/components/FileViewer.astro +39 -0
  23. package/templates/astro/src/env.d.ts +1 -0
  24. package/templates/astro/src/pages/index.astro +73 -0
  25. package/templates/astro/tsconfig.json +5 -0
  26. package/templates/nuxt/app/app.vue +46 -0
  27. package/templates/nuxt/app/components/FileViewer.vue +24 -0
  28. package/templates/nuxt/nuxt.config.ts +16 -0
  29. package/templates/nuxt/package.json +23 -0
  30. package/templates/nuxt/quicklook.config.ts +15 -0
  31. package/templates/nuxt/tsconfig.json +9 -0
  32. package/templates/solid-start/package.json +24 -0
  33. package/templates/solid-start/quicklook.config.ts +15 -0
  34. package/templates/solid-start/src/app.css +12 -0
  35. package/templates/solid-start/src/app.tsx +12 -0
  36. package/templates/solid-start/src/components/FileViewer.css +8 -0
  37. package/templates/solid-start/src/components/FileViewer.tsx +8 -0
  38. package/templates/solid-start/src/entry-client.tsx +7 -0
  39. package/templates/solid-start/src/entry-server.tsx +22 -0
  40. package/templates/solid-start/src/routes/index.tsx +35 -0
  41. package/templates/solid-start/tsconfig.json +16 -0
  42. package/templates/solid-start/vite.config.ts +13 -0
  43. package/templates/sveltekit/package.json +25 -0
  44. package/templates/sveltekit/quicklook.config.ts +15 -0
  45. package/templates/sveltekit/src/app.d.ts +1 -0
  46. package/templates/sveltekit/src/app.html +12 -0
  47. package/templates/sveltekit/src/lib/FileViewer.svelte +28 -0
  48. package/templates/sveltekit/src/routes/+page.svelte +42 -0
  49. package/templates/sveltekit/svelte.config.js +11 -0
  50. package/templates/sveltekit/tsconfig.json +11 -0
  51. package/templates/sveltekit/vite.config.ts +7 -0
  52. package/README.md +0 -3
package/LICENSE ADDED
@@ -0,0 +1,23 @@
1
+ Permission is hereby granted, free of charge, to any
2
+ person obtaining a copy of this software and associated
3
+ documentation files (the "Software"), to deal in the
4
+ Software without restriction, including without
5
+ limitation the rights to use, copy, modify, merge,
6
+ publish, distribute, sublicense, and/or sell copies of
7
+ the Software, and to permit persons to whom the Software
8
+ is furnished to do so, subject to the following
9
+ conditions:
10
+
11
+ The above copyright notice and this permission notice
12
+ shall be included in all copies or substantial portions
13
+ of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
16
+ ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
17
+ TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
18
+ PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
19
+ SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
22
+ IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
23
+ DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,19 @@
1
+ # create-quicklook
2
+
3
+ ![License](https://img.shields.io/npm/l/quicklook-kit?style=for-the-badge)
4
+ [![Version](https://img.shields.io/npm/v/quicklook-kit?style=for-the-badge)](https://www.npmjs.org/package/quicklook-kit)
5
+ [![Build](https://img.shields.io/github/actions/workflow/status/idleberg/quicklook-kit/ci.yml?style=for-the-badge)](https://github.com/idleberg/quicklook-kit/actions)
6
+
7
+ > Create a macOS QuickLook preview with any Vite-based web framework
8
+
9
+ ```sh
10
+ pnpm create quicklook my-preview
11
+ ```
12
+
13
+ Asks for a template (SvelteKit, Astro, SolidStart, Nuxt or Analog) and whether to add a GitHub release workflow. Skip the questions with `--template <name>` and `--release` or `--no-release`.
14
+
15
+ The project uses [quicklook-kit](https://www.npmjs.com/package/quicklook-kit); see its readme for what to do next.
16
+
17
+ ## License
18
+
19
+ This work is licensed under [The MIT License](https://opensource.org/licenses/MIT).
package/dist/index.mjs ADDED
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env node
2
+ import { execFileSync } from "node:child_process";
3
+ import { existsSync } from "node:fs";
4
+ import { cp, readFile, readdir, writeFile } from "node:fs/promises";
5
+ import { basename, relative, resolve } from "node:path";
6
+ import process from "node:process";
7
+ import { fileURLToPath } from "node:url";
8
+ import { parseArgs } from "node:util";
9
+ import { cancel, confirm, intro, isCancel, note, outro, select, text } from "@clack/prompts";
10
+ //#region src/index.ts
11
+ const pkgRoot = fileURLToPath(new URL("..", import.meta.url));
12
+ const templates = (await readdir(`${pkgRoot}/templates`)).toSorted();
13
+ const { values, positionals } = parseArgs({
14
+ allowPositionals: true,
15
+ allowNegative: true,
16
+ options: {
17
+ template: {
18
+ type: "string",
19
+ short: "t"
20
+ },
21
+ release: { type: "boolean" }
22
+ }
23
+ });
24
+ /** Exits quietly when a prompt is cancelled with Ctrl+C or Esc. */
25
+ function answer(value) {
26
+ if (isCancel(value)) {
27
+ cancel("Cancelled, nothing created.");
28
+ process.exit(1);
29
+ }
30
+ return value;
31
+ }
32
+ intro("create-quicklook");
33
+ const dir = resolve(positionals[0] ?? answer(await text({
34
+ message: "Project folder",
35
+ placeholder: "quicklook-preview",
36
+ defaultValue: "quicklook-preview"
37
+ })));
38
+ if (existsSync(dir) && (await readdir(dir)).length > 0) {
39
+ cancel(`${dir} is not empty.`);
40
+ process.exit(1);
41
+ }
42
+ const template = values.template && templates.includes(values.template) ? values.template : answer(await select({
43
+ message: "Template",
44
+ options: templates.map((value) => ({ value }))
45
+ }));
46
+ const release = values.release ?? answer(await confirm({
47
+ message: "Add a GitHub release workflow?",
48
+ initialValue: false
49
+ }));
50
+ await cp(`${pkgRoot}/templates/${template}`, dir, { recursive: true });
51
+ for (const file of ["mise.toml", "hk.pkl"]) await cp(`${pkgRoot}/files/${file}`, `${dir}/${file}`);
52
+ await cp(`${pkgRoot}/files/biome.template.jsonc`, `${dir}/biome.jsonc`);
53
+ await cp(`${pkgRoot}/files/ci.yml`, `${dir}/.github/workflows/ci.yml`);
54
+ await cp(`${pkgRoot}/files/gitignore`, `${dir}/.gitignore`);
55
+ if (release) await cp(`${pkgRoot}/files/release.yml`, `${dir}/.github/workflows/release.yml`);
56
+ const pkg = JSON.parse(await readFile(`${dir}/package.json`, "utf8"));
57
+ pkg.name = basename(dir);
58
+ const pnpm = process.env.npm_config_user_agent?.match(/^pnpm\/(\S+)/)?.[1];
59
+ if (pnpm) pkg.packageManager = `pnpm@${pnpm}`;
60
+ await writeFile(`${dir}/package.json`, `${JSON.stringify(pkg, null, " ")}\n`);
61
+ function git(...args) {
62
+ execFileSync("git", args, {
63
+ cwd: dir,
64
+ stdio: "ignore"
65
+ });
66
+ }
67
+ try {
68
+ git("rev-parse", "--is-inside-work-tree");
69
+ } catch {
70
+ try {
71
+ git("init", "--quiet");
72
+ } catch {}
73
+ }
74
+ note([
75
+ `cd ${relative(process.cwd(), dir)}`,
76
+ "mise install # or: pnpm install, with Xcode and XcodeGen installed",
77
+ "pnpm dev # drop a file on the page to preview it",
78
+ "pnpm register # build the app and register it with QuickLook"
79
+ ].join("\n"), "Next steps");
80
+ outro(`Created from the ${template} template.`);
81
+ //#endregion
82
+ export {};
@@ -0,0 +1,28 @@
1
+ {
2
+ "$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
3
+ "vcs": {
4
+ "enabled": true,
5
+ "clientKind": "git",
6
+ "useIgnoreFile": true
7
+ },
8
+ "formatter": {
9
+ "indentStyle": "tab",
10
+ "lineWidth": 100
11
+ },
12
+ "javascript": {
13
+ "formatter": {
14
+ "quoteStyle": "single",
15
+ "trailingCommas": "all"
16
+ }
17
+ },
18
+ "html": {
19
+ "experimentalFullSupportEnabled": true
20
+ },
21
+ "assist": {
22
+ "actions": {
23
+ "source": {
24
+ "organizeImports": "on"
25
+ }
26
+ }
27
+ }
28
+ }
package/files/ci.yml ADDED
@@ -0,0 +1,27 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ pull_request:
8
+
9
+ jobs:
10
+ check:
11
+ # Same runner as the release workflow, XcodeGen from mise.toml needs macOS.
12
+ runs-on: macos-15
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+
16
+ # pnpm's default store on macOS.
17
+ - uses: actions/cache@v6
18
+ with:
19
+ path: ~/Library/pnpm/store
20
+ key: pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
21
+ restore-keys: pnpm-
22
+
23
+ - uses: jdx/mise-action@v2
24
+
25
+ - run: pnpm run lint
26
+
27
+ - run: pnpm run check
@@ -0,0 +1,14 @@
1
+ node_modules/
2
+ .quicklook/
3
+ .DS_Store
4
+
5
+ # Framework output and caches
6
+ .svelte-kit/
7
+ build/
8
+ .astro/
9
+ dist
10
+ .output/
11
+ .nitro/
12
+ .vinxi/
13
+ .nuxt/
14
+ .angular/
package/files/hk.pkl ADDED
@@ -0,0 +1,27 @@
1
+ amends "package://github.com/jdx/hk/releases/download/v2.5.0/hk@2.5.0#/Config.pkl"
2
+ import "package://github.com/jdx/hk/releases/download/v2.5.0/hk@2.5.0#/Builtins.pkl"
3
+
4
+ local linters = new Mapping<String, Step> {
5
+ // pnpm exec runs the project's own Biome, whatever else is on PATH.
6
+ ["biome"] = (Builtins.biome) {
7
+ // The builtin only matches JS, TS and JSON.
8
+ glob = List("**/*.{js,jsx,ts,tsx,json,jsonc,css,html,svelte,astro,vue}")
9
+ check = "pnpm exec biome check --no-errors-on-unmatched --files-ignore-unknown=true {{ files }}"
10
+ fix = "pnpm exec biome check --write --no-errors-on-unmatched --files-ignore-unknown=true {{ files }}"
11
+ }
12
+ }
13
+
14
+ hooks {
15
+ ["pre-commit"] {
16
+ fix = true
17
+ stash = "git"
18
+ steps = linters
19
+ }
20
+ ["fix"] {
21
+ fix = true
22
+ steps = linters
23
+ }
24
+ ["check"] {
25
+ steps = linters
26
+ }
27
+ }
@@ -0,0 +1,21 @@
1
+ [tools]
2
+ node = "24"
3
+ xcodegen = "latest"
4
+ hk = "latest"
5
+ pkl = "latest"
6
+
7
+ [settings]
8
+ # Node ships pnpm through corepack, pinned by packageManager in package.json
9
+ node.corepack = true
10
+
11
+ [env]
12
+ HK_MISE = 1
13
+ COREPACK_ENABLE_DOWNLOAD_PROMPT = 0
14
+ _.path = ["{{config_root}}/node_modules/.bin"]
15
+
16
+ # The pre-commit hook needs a git repository; run `hk install --mise` after `git init`.
17
+ [hooks]
18
+ postinstall = """
19
+ pnpm install
20
+ if git rev-parse --git-dir >/dev/null 2>&1; then hk install --mise; fi
21
+ """
@@ -0,0 +1,52 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - 'v*'
7
+
8
+ permissions:
9
+ contents: write
10
+
11
+ env:
12
+ # The folder holding quicklook.config.*, relative to the repository root.
13
+ PROJECT: .
14
+
15
+ jobs:
16
+ build:
17
+ runs-on: macos-15
18
+ steps:
19
+ - uses: actions/checkout@v4
20
+
21
+ - uses: actions/cache@v6
22
+ with:
23
+ path: ~/Library/pnpm/store
24
+ key: pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
25
+ restore-keys: pnpm-
26
+
27
+ - uses: jdx/mise-action@v2
28
+
29
+ # Release builds are universal (arm64 + x86_64) by default.
30
+ - run: pnpm install --frozen-lockfile && pnpm run build
31
+ working-directory: ${{ env.PROJECT }}
32
+ env:
33
+ VERSION: ${{ github.ref_name }}
34
+
35
+ - name: Package app
36
+ id: package
37
+ run: |
38
+ cd "$PROJECT/.quicklook/build"
39
+ app=$(ls -d *.app)
40
+ zip="${app%.app}"
41
+ zip="${zip// /-}-${GITHUB_REF_NAME}.zip"
42
+ zip -r -9 -y "$GITHUB_WORKSPACE/$zip" "$app"
43
+ cd "$GITHUB_WORKSPACE"
44
+ shasum -a 256 "$zip" > SHA256SUMS
45
+ echo "zip=$zip" >> "$GITHUB_OUTPUT"
46
+
47
+ - uses: softprops/action-gh-release@v2
48
+ with:
49
+ files: |
50
+ ${{ steps.package.outputs.zip }}
51
+ SHA256SUMS
52
+ generate_release_notes: true
package/package.json CHANGED
@@ -1,6 +1,48 @@
1
1
  {
2
2
  "name": "create-quicklook",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.2.0",
4
+ "description": "Create a macOS QuickLook preview with any Vite-based web framework",
5
+ "license": "MIT",
6
+ "keywords": [
7
+ "quicklook",
8
+ "quicklook-plugin",
9
+ "create",
10
+ "template"
11
+ ],
12
+ "type": "module",
13
+ "bin": {
14
+ "create-quicklook": "./dist/index.mjs"
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "files",
19
+ "templates",
20
+ "!templates/*/node_modules",
21
+ "!templates/*/.quicklook",
22
+ "!templates/*/.DS_Store",
23
+ "!templates/*/.svelte-kit",
24
+ "!templates/*/build",
25
+ "!templates/*/.astro",
26
+ "!templates/*/dist",
27
+ "!templates/*/.output",
28
+ "!templates/*/.nitro",
29
+ "!templates/*/.vinxi",
30
+ "!templates/*/.nuxt",
31
+ "!templates/*/.angular"
32
+ ],
33
+ "engines": {
34
+ "node": ">=22.17"
35
+ },
36
+ "devDependencies": {
37
+ "@types/node": "24.19.2",
38
+ "tsdown": "0.23.0",
39
+ "typescript": "6.0.3"
40
+ },
41
+ "dependencies": {
42
+ "@clack/prompts": "1.8.1"
43
+ },
44
+ "scripts": {
45
+ "build": "tsdown",
46
+ "check": "tsc --noEmit"
47
+ }
6
48
  }
@@ -0,0 +1,18 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <meta name="color-scheme" content="light dark" />
7
+ <title>Preview</title>
8
+ <style>
9
+ body {
10
+ margin: 0;
11
+ }
12
+ </style>
13
+ </head>
14
+ <body>
15
+ <app-root></app-root>
16
+ <script type="module" src="/src/main.ts"></script>
17
+ </body>
18
+ </html>
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "quicklook-analog",
3
+ "private": true,
4
+ "type": "module",
5
+ "scripts": {
6
+ "dev": "quicklook dev",
7
+ "build": "quicklook build",
8
+ "register": "quicklook build && quicklook register",
9
+ "unregister": "quicklook unregister",
10
+ "check": "ngc -p tsconfig.json --noEmit",
11
+ "lint": "biome check",
12
+ "format": "biome check --write"
13
+ },
14
+ "devDependencies": {
15
+ "@analogjs/vite-plugin-angular": "2.7.5",
16
+ "@angular/build": "22.2.0",
17
+ "@angular/common": "22.2.0",
18
+ "@angular/compiler": "22.2.0",
19
+ "@angular/compiler-cli": "22.2.0",
20
+ "@angular/core": "22.2.0",
21
+ "@angular/platform-browser": "22.2.0",
22
+ "@biomejs/biome": "2.5.14",
23
+ "quicklook-kit": "^0.3.0",
24
+ "rxjs": "7.8.2",
25
+ "tslib": "2.8.1",
26
+ "typescript": "6.0.3",
27
+ "vite": "8.3.0"
28
+ }
29
+ }
@@ -0,0 +1,15 @@
1
+ import { defineConfig } from 'quicklook-kit';
2
+
3
+ export default defineConfig({
4
+ name: 'QuickLook Example',
5
+ bundleId: 'com.example.QuickLookExample',
6
+ types: [
7
+ {
8
+ id: 'com.example.QuickLookExample.example',
9
+ description: 'Example File',
10
+ extensions: ['example'],
11
+ conformsTo: ['public.plain-text'],
12
+ },
13
+ ],
14
+ framework: 'analog',
15
+ });
@@ -0,0 +1,45 @@
1
+ import { browserFile, nativeFile, type QuickLookFile } from 'virtual:quicklook';
2
+ import { Component, signal } from '@angular/core';
3
+ import { FileViewer } from './file-viewer';
4
+
5
+ @Component({
6
+ selector: 'app-root',
7
+ imports: [FileViewer],
8
+ host: { '(window:dragover)': '$event.preventDefault()', '(window:drop)': 'drop($event)' },
9
+ template: `
10
+ @if (file(); as file) {
11
+ <app-file-viewer [file]="file" />
12
+ } @else {
13
+ <!-- Only reachable in the browser: QuickLook always injects a file. -->
14
+ <label class="drop">
15
+ Drop a file here, or click to pick one
16
+ <input type="file" hidden (change)="open($any($event.target).files)" />
17
+ </label>
18
+ }
19
+ `,
20
+ styles: `
21
+ .drop {
22
+ display: grid;
23
+ place-items: center;
24
+ height: 100vh;
25
+ font: 14px system-ui;
26
+ color: GrayText;
27
+ cursor: pointer;
28
+ }
29
+ `,
30
+ })
31
+ export class App {
32
+ protected readonly file = signal<QuickLookFile | undefined>(nativeFile());
33
+
34
+ protected async open(files: FileList | null | undefined) {
35
+ const picked = files?.[0];
36
+ if (picked) {
37
+ this.file.set(await browserFile(picked));
38
+ }
39
+ }
40
+
41
+ protected drop(event: DragEvent) {
42
+ event.preventDefault();
43
+ this.open(event.dataTransfer?.files);
44
+ }
45
+ }
@@ -0,0 +1,24 @@
1
+ // Your preview goes here. `file.bytes` holds the raw file; decode it however
2
+ // the format needs. This example shows it as UTF-8 text.
3
+
4
+ import type { QuickLookFile } from 'virtual:quicklook';
5
+ import { Component, computed, input } from '@angular/core';
6
+
7
+ @Component({
8
+ selector: 'app-file-viewer',
9
+ template: '<pre>{{ text() }}</pre>',
10
+ styles: `
11
+ pre {
12
+ margin: 0;
13
+ padding: 1rem;
14
+ font:
15
+ 12px ui-monospace,
16
+ monospace;
17
+ white-space: pre-wrap;
18
+ }
19
+ `,
20
+ })
21
+ export class FileViewer {
22
+ readonly file = input.required<QuickLookFile>();
23
+ protected readonly text = computed(() => new TextDecoder().decode(this.file().bytes));
24
+ }
@@ -0,0 +1,4 @@
1
+ import { bootstrapApplication } from '@angular/platform-browser';
2
+ import { App } from './app/app';
3
+
4
+ bootstrapApplication(App);
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es2023",
4
+ "module": "esnext",
5
+ "moduleResolution": "bundler",
6
+ "lib": ["es2023", "dom"],
7
+ "types": ["vite/client", "quicklook-kit/virtual"],
8
+ "strict": true,
9
+ "allowImportingTsExtensions": true,
10
+ "skipLibCheck": true,
11
+ "isolatedModules": true,
12
+ "useDefineForClassFields": false
13
+ },
14
+ "angularCompilerOptions": { "strictTemplates": true },
15
+ "include": ["src"]
16
+ }
@@ -0,0 +1,15 @@
1
+ import angular from '@analogjs/vite-plugin-angular';
2
+ import { quicklook } from 'quicklook-kit/vite';
3
+ import { defineConfig } from 'vite';
4
+
5
+ export default defineConfig({
6
+ plugins: [
7
+ angular({
8
+ tsconfig: 'tsconfig.json',
9
+ // Only this app's components go through the Angular compiler; it would
10
+ // empty out a linked package's TypeScript, which isn't in the tsconfig.
11
+ transformFilter: (_code, id) => id.startsWith(`${import.meta.dirname}/src/`),
12
+ }),
13
+ quicklook(),
14
+ ],
15
+ });
@@ -0,0 +1,6 @@
1
+ import { defineConfig } from 'astro/config';
2
+ import { quicklook } from 'quicklook-kit/vite';
3
+
4
+ export default defineConfig({
5
+ vite: { plugins: [quicklook()] },
6
+ });
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "quicklook-astro",
3
+ "private": true,
4
+ "type": "module",
5
+ "scripts": {
6
+ "dev": "quicklook dev",
7
+ "build": "quicklook build",
8
+ "register": "quicklook build && quicklook register",
9
+ "unregister": "quicklook unregister",
10
+ "check": "astro check",
11
+ "lint": "biome check",
12
+ "format": "biome check --write"
13
+ },
14
+ "devDependencies": {
15
+ "@astrojs/check": "0.9.10",
16
+ "@biomejs/biome": "2.5.14",
17
+ "astro": "7.3.5",
18
+ "quicklook-kit": "^0.3.0",
19
+ "typescript": "6.0.3"
20
+ }
21
+ }
@@ -0,0 +1,15 @@
1
+ import { defineConfig } from 'quicklook-kit';
2
+
3
+ export default defineConfig({
4
+ name: 'QuickLook Example',
5
+ bundleId: 'com.example.QuickLookExample',
6
+ types: [
7
+ {
8
+ id: 'com.example.QuickLookExample.example',
9
+ description: 'Example File',
10
+ extensions: ['example'],
11
+ conformsTo: ['public.plain-text'],
12
+ },
13
+ ],
14
+ framework: 'astro',
15
+ });
@@ -0,0 +1,39 @@
1
+ <!--
2
+ Your preview goes here. `file.bytes` holds the raw file; decode it however
3
+ the format needs. This example shows it as UTF-8 text. Astro components only
4
+ render on the server, so the viewer is a custom element the page calls.
5
+ -->
6
+ <file-viewer hidden><pre></pre></file-viewer>
7
+
8
+ <script>
9
+ import type { QuickLookFile } from 'virtual:quicklook';
10
+
11
+ class FileViewer extends HTMLElement {
12
+ show(file: QuickLookFile) {
13
+ const pre = this.querySelector('pre');
14
+ if (pre) {
15
+ pre.textContent = new TextDecoder().decode(file.bytes);
16
+ }
17
+ this.hidden = false;
18
+ }
19
+ }
20
+
21
+ declare global {
22
+ interface HTMLElementTagNameMap {
23
+ 'file-viewer': FileViewer;
24
+ }
25
+ }
26
+
27
+ customElements.define('file-viewer', FileViewer);
28
+ </script>
29
+
30
+ <style>
31
+ pre {
32
+ margin: 0;
33
+ padding: 1rem;
34
+ font:
35
+ 12px ui-monospace,
36
+ monospace;
37
+ white-space: pre-wrap;
38
+ }
39
+ </style>
@@ -0,0 +1 @@
1
+ /// <reference types="quicklook-kit/virtual" />
@@ -0,0 +1,73 @@
1
+ ---
2
+ import FileViewer from '../components/FileViewer.astro';
3
+ ---
4
+
5
+ <html lang="en">
6
+ <head>
7
+ <meta charset="utf-8" />
8
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
9
+ <meta name="color-scheme" content="light dark" />
10
+ <title>Preview</title>
11
+ </head>
12
+ <body>
13
+ <FileViewer />
14
+ <!-- Only reachable in the browser: QuickLook always injects a file. -->
15
+ <label id="drop">
16
+ Drop a file here, or click to pick one
17
+ <input type="file" hidden />
18
+ </label>
19
+
20
+ <script>
21
+ import { browserFile, nativeFile, type QuickLookFile } from 'virtual:quicklook';
22
+
23
+ const drop = document.querySelector<HTMLLabelElement>('#drop');
24
+
25
+ async function show(file: QuickLookFile) {
26
+ await customElements.whenDefined('file-viewer');
27
+ document.querySelector('file-viewer')?.show(file);
28
+ if (drop) {
29
+ drop.hidden = true;
30
+ }
31
+ }
32
+
33
+ async function open(files: FileList | null | undefined) {
34
+ const picked = files?.[0];
35
+ if (picked) {
36
+ await show(await browserFile(picked));
37
+ }
38
+ }
39
+
40
+ const injected = nativeFile();
41
+ if (injected) {
42
+ show(injected);
43
+ }
44
+ window.addEventListener('dragover', (event) => event.preventDefault());
45
+ window.addEventListener('drop', (event) => {
46
+ event.preventDefault();
47
+ open(event.dataTransfer?.files);
48
+ });
49
+ drop?.querySelector('input')?.addEventListener('change', (event) => {
50
+ open((event.currentTarget as HTMLInputElement).files);
51
+ });
52
+ </script>
53
+ </body>
54
+ </html>
55
+
56
+ <style is:global>
57
+ body {
58
+ margin: 0;
59
+ }
60
+
61
+ #drop {
62
+ display: grid;
63
+ place-items: center;
64
+ height: 100vh;
65
+ font: 14px system-ui;
66
+ color: GrayText;
67
+ cursor: pointer;
68
+ }
69
+
70
+ #drop[hidden] {
71
+ display: none;
72
+ }
73
+ </style>
@@ -0,0 +1,5 @@
1
+ {
2
+ "extends": "astro/tsconfigs/strict",
3
+ "include": [".astro/types.d.ts", "**/*"],
4
+ "exclude": ["dist", ".quicklook"]
5
+ }
@@ -0,0 +1,46 @@
1
+ <script setup lang="ts">
2
+ import { browserFile, nativeFile, type QuickLookFile } from 'virtual:quicklook';
3
+ import { onMounted, shallowRef } from 'vue';
4
+ import FileViewer from '~/components/FileViewer.vue';
5
+
6
+ const file = shallowRef<QuickLookFile | undefined>(nativeFile());
7
+
8
+ async function open(files: FileList | null | undefined) {
9
+ const picked = files?.[0];
10
+ if (picked) {
11
+ file.value = await browserFile(picked);
12
+ }
13
+ }
14
+
15
+ onMounted(() => {
16
+ window.addEventListener('dragover', (event) => event.preventDefault());
17
+ window.addEventListener('drop', (event) => {
18
+ event.preventDefault();
19
+ open(event.dataTransfer?.files);
20
+ });
21
+ });
22
+ </script>
23
+
24
+ <template>
25
+ <FileViewer v-if="file" :file="file" />
26
+ <!-- Only reachable in the browser: QuickLook always injects a file. -->
27
+ <label v-else class="drop">
28
+ Drop a file here, or click to pick one
29
+ <input type="file" hidden @change="open(($event.target as HTMLInputElement).files)" />
30
+ </label>
31
+ </template>
32
+
33
+ <style>
34
+ body {
35
+ margin: 0;
36
+ }
37
+
38
+ .drop {
39
+ display: grid;
40
+ place-items: center;
41
+ height: 100vh;
42
+ font: 14px system-ui;
43
+ color: GrayText;
44
+ cursor: pointer;
45
+ }
46
+ </style>
@@ -0,0 +1,24 @@
1
+ <!-- Your preview goes here. `file.bytes` holds the raw file; decode it however
2
+ the format needs. This example shows it as UTF-8 text. -->
3
+ <script setup lang="ts">
4
+ import type { QuickLookFile } from 'virtual:quicklook';
5
+ import { computed } from 'vue';
6
+
7
+ const props = defineProps<{ file: QuickLookFile }>();
8
+ const text = computed(() => new TextDecoder().decode(props.file.bytes));
9
+ </script>
10
+
11
+ <template>
12
+ <pre v-text="text" />
13
+ </template>
14
+
15
+ <style scoped>
16
+ pre {
17
+ margin: 0;
18
+ padding: 1rem;
19
+ font:
20
+ 12px ui-monospace,
21
+ monospace;
22
+ white-space: pre-wrap;
23
+ }
24
+ </style>
@@ -0,0 +1,16 @@
1
+ import { defineNuxtConfig } from 'nuxt/config';
2
+ import { quicklook } from 'quicklook-kit/vite';
3
+
4
+ export default defineNuxtConfig({
5
+ compatibilityDate: '2026-07-01',
6
+ ssr: false,
7
+ app: { head: { meta: [{ name: 'color-scheme', content: 'light dark' }] } },
8
+ // The build manifest is fetched at runtime, which the extension's CSP blocks.
9
+ experimental: { appManifest: false },
10
+ vite: { plugins: [quicklook()] },
11
+ typescript: {
12
+ tsConfig: {
13
+ compilerOptions: { allowImportingTsExtensions: true, types: ['quicklook-kit/virtual'] },
14
+ },
15
+ },
16
+ });
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "quicklook-nuxt",
3
+ "private": true,
4
+ "type": "module",
5
+ "scripts": {
6
+ "postinstall": "nuxt prepare",
7
+ "dev": "quicklook dev",
8
+ "build": "quicklook build",
9
+ "register": "quicklook build && quicklook register",
10
+ "unregister": "quicklook unregister",
11
+ "check": "nuxt typecheck",
12
+ "lint": "biome check",
13
+ "format": "biome check --write"
14
+ },
15
+ "devDependencies": {
16
+ "@biomejs/biome": "2.5.14",
17
+ "nuxt": "4.5.2",
18
+ "quicklook-kit": "^0.3.0",
19
+ "typescript": "6.0.3",
20
+ "vue": "3.5.43",
21
+ "vue-tsc": "3.3.11"
22
+ }
23
+ }
@@ -0,0 +1,15 @@
1
+ import { defineConfig } from 'quicklook-kit';
2
+
3
+ export default defineConfig({
4
+ name: 'QuickLook Example',
5
+ bundleId: 'com.example.QuickLookExample',
6
+ types: [
7
+ {
8
+ id: 'com.example.QuickLookExample.example',
9
+ description: 'Example File',
10
+ extensions: ['example'],
11
+ conformsTo: ['public.plain-text'],
12
+ },
13
+ ],
14
+ framework: 'nuxt',
15
+ });
@@ -0,0 +1,9 @@
1
+ {
2
+ "files": [],
3
+ "references": [
4
+ { "path": "./.nuxt/tsconfig.app.json" },
5
+ { "path": "./.nuxt/tsconfig.server.json" },
6
+ { "path": "./.nuxt/tsconfig.shared.json" },
7
+ { "path": "./.nuxt/tsconfig.node.json" }
8
+ ]
9
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "quicklook-solid-start",
3
+ "private": true,
4
+ "type": "module",
5
+ "scripts": {
6
+ "dev": "quicklook dev",
7
+ "build": "quicklook build",
8
+ "register": "quicklook build && quicklook register",
9
+ "unregister": "quicklook unregister",
10
+ "check": "tsc --noEmit",
11
+ "lint": "biome check",
12
+ "format": "biome check --write"
13
+ },
14
+ "devDependencies": {
15
+ "@biomejs/biome": "2.5.14",
16
+ "@solidjs/router": "1.0.0",
17
+ "@solidjs/start": "2.0.6",
18
+ "nitro": "3.0.260903-beta",
19
+ "quicklook-kit": "^0.3.0",
20
+ "solid-js": "1.9.17",
21
+ "typescript": "6.0.3",
22
+ "vite": "8.3.0"
23
+ }
24
+ }
@@ -0,0 +1,15 @@
1
+ import { defineConfig } from 'quicklook-kit';
2
+
3
+ export default defineConfig({
4
+ name: 'QuickLook Example',
5
+ bundleId: 'com.example.QuickLookExample',
6
+ types: [
7
+ {
8
+ id: 'com.example.QuickLookExample.example',
9
+ description: 'Example File',
10
+ extensions: ['example'],
11
+ conformsTo: ['public.plain-text'],
12
+ },
13
+ ],
14
+ framework: 'solid-start',
15
+ });
@@ -0,0 +1,12 @@
1
+ body {
2
+ margin: 0;
3
+ }
4
+
5
+ .drop {
6
+ display: grid;
7
+ place-items: center;
8
+ height: 100vh;
9
+ font: 14px system-ui;
10
+ color: GrayText;
11
+ cursor: pointer;
12
+ }
@@ -0,0 +1,12 @@
1
+ import { Router } from '@solidjs/router';
2
+ import { FileRoutes } from '@solidjs/start/router';
3
+ import { Suspense } from 'solid-js';
4
+ import './app.css';
5
+
6
+ export default function App() {
7
+ return (
8
+ <Router root={(props) => <Suspense>{props.children}</Suspense>}>
9
+ <FileRoutes />
10
+ </Router>
11
+ );
12
+ }
@@ -0,0 +1,8 @@
1
+ pre {
2
+ margin: 0;
3
+ padding: 1rem;
4
+ font:
5
+ 12px ui-monospace,
6
+ monospace;
7
+ white-space: pre-wrap;
8
+ }
@@ -0,0 +1,8 @@
1
+ // Your preview goes here. `file.bytes` holds the raw file; decode it however
2
+ // the format needs. This example shows it as UTF-8 text.
3
+ import type { QuickLookFile } from 'virtual:quicklook';
4
+ import './FileViewer.css';
5
+
6
+ export default function FileViewer(props: { file: QuickLookFile }) {
7
+ return <pre>{new TextDecoder().decode(props.file.bytes)}</pre>;
8
+ }
@@ -0,0 +1,7 @@
1
+ // @refresh reload
2
+ import { mount, StartClient } from '@solidjs/start/client';
3
+
4
+ const app = document.getElementById('app');
5
+ if (app) {
6
+ mount(() => <StartClient />, app);
7
+ }
@@ -0,0 +1,22 @@
1
+ // @refresh reload
2
+ import { createHandler, StartServer } from '@solidjs/start/server';
3
+
4
+ export default createHandler(() => (
5
+ <StartServer
6
+ document={({ assets, children, scripts }) => (
7
+ <html lang="en">
8
+ <head>
9
+ <meta charset="utf-8" />
10
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
11
+ <meta name="color-scheme" content="light dark" />
12
+ <title>Preview</title>
13
+ {assets}
14
+ </head>
15
+ <body>
16
+ <div id="app">{children}</div>
17
+ {scripts}
18
+ </body>
19
+ </html>
20
+ )}
21
+ />
22
+ ));
@@ -0,0 +1,35 @@
1
+ import { browserFile, nativeFile, type QuickLookFile } from 'virtual:quicklook';
2
+ import { createSignal, Show } from 'solid-js';
3
+ import FileViewer from '~/components/FileViewer';
4
+
5
+ export default function Home() {
6
+ const [file, setFile] = createSignal<QuickLookFile | undefined>(nativeFile());
7
+
8
+ async function open(files: FileList | null | undefined) {
9
+ const picked = files?.[0];
10
+ if (picked) {
11
+ setFile(await browserFile(picked));
12
+ }
13
+ }
14
+
15
+ window.addEventListener('dragover', (event) => event.preventDefault());
16
+ window.addEventListener('drop', (event) => {
17
+ event.preventDefault();
18
+ open(event.dataTransfer?.files);
19
+ });
20
+
21
+ return (
22
+ <Show
23
+ when={file()}
24
+ fallback={
25
+ // Only reachable in the browser: QuickLook always injects a file.
26
+ <label class="drop">
27
+ Drop a file here, or click to pick one
28
+ <input type="file" hidden={true} onChange={(event) => open(event.currentTarget.files)} />
29
+ </label>
30
+ }
31
+ >
32
+ {(current) => <FileViewer file={current()} />}
33
+ </Show>
34
+ );
35
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es2023",
4
+ "module": "esnext",
5
+ "moduleResolution": "bundler",
6
+ "jsx": "preserve",
7
+ "jsxImportSource": "solid-js",
8
+ "types": ["vite/client", "quicklook-kit/virtual"],
9
+ "strict": true,
10
+ "noEmit": true,
11
+ "allowImportingTsExtensions": true,
12
+ "skipLibCheck": true,
13
+ "isolatedModules": true,
14
+ "paths": { "~/*": ["./src/*"] }
15
+ }
16
+ }
@@ -0,0 +1,13 @@
1
+ import { solidStart } from '@solidjs/start/config';
2
+ import { nitro } from 'nitro/vite';
3
+ import { quicklook } from 'quicklook-kit/vite';
4
+ import { defineConfig } from 'vite';
5
+
6
+ export default defineConfig({
7
+ plugins: [
8
+ // Client-only, prerendered to a static index.html in .output/public.
9
+ solidStart({ ssr: false }),
10
+ nitro({ preset: 'static', prerender: { routes: ['/'] } }),
11
+ quicklook(),
12
+ ],
13
+ });
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "quicklook-sveltekit",
3
+ "private": true,
4
+ "type": "module",
5
+ "scripts": {
6
+ "dev": "quicklook dev",
7
+ "build": "quicklook build",
8
+ "register": "quicklook build && quicklook register",
9
+ "unregister": "quicklook unregister",
10
+ "check": "svelte-kit sync && svelte-check",
11
+ "lint": "biome check",
12
+ "format": "biome check --write"
13
+ },
14
+ "devDependencies": {
15
+ "@biomejs/biome": "2.5.14",
16
+ "@sveltejs/adapter-static": "3.0.10",
17
+ "@sveltejs/kit": "2.70.3",
18
+ "@sveltejs/vite-plugin-svelte": "7.3.1",
19
+ "quicklook-kit": "^0.3.0",
20
+ "svelte": "5.57.1",
21
+ "svelte-check": "4.7.6",
22
+ "typescript": "6.0.3",
23
+ "vite": "8.3.0"
24
+ }
25
+ }
@@ -0,0 +1,15 @@
1
+ import { defineConfig } from 'quicklook-kit';
2
+
3
+ export default defineConfig({
4
+ name: 'QuickLook Example',
5
+ bundleId: 'com.example.QuickLookExample',
6
+ types: [
7
+ {
8
+ id: 'com.example.QuickLookExample.example',
9
+ description: 'Example File',
10
+ extensions: ['example'],
11
+ conformsTo: ['public.plain-text'],
12
+ },
13
+ ],
14
+ framework: 'sveltekit',
15
+ });
@@ -0,0 +1 @@
1
+ /// <reference types="quicklook-kit/virtual" />
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <meta name="color-scheme" content="light dark" />
7
+ %sveltekit.head%
8
+ </head>
9
+ <body>
10
+ <div style="display: contents">%sveltekit.body%</div>
11
+ </body>
12
+ </html>
@@ -0,0 +1,28 @@
1
+ <!--
2
+ Your preview goes here. `file.bytes` holds the raw file; decode it however
3
+ the format needs. This example shows it as UTF-8 text.
4
+ -->
5
+ <script lang="ts">
6
+ import type { QuickLookFile } from 'virtual:quicklook';
7
+
8
+ let { file }: { file: QuickLookFile } = $props();
9
+
10
+ const text = $derived(new TextDecoder().decode(file.bytes));
11
+ </script>
12
+
13
+ <pre>{text}</pre>
14
+
15
+ <style>
16
+ :global(body) {
17
+ margin: 0;
18
+ }
19
+
20
+ pre {
21
+ margin: 0;
22
+ padding: 1rem;
23
+ font:
24
+ 12px ui-monospace,
25
+ monospace;
26
+ white-space: pre-wrap;
27
+ }
28
+ </style>
@@ -0,0 +1,42 @@
1
+ <script lang="ts">
2
+ import { browserFile, nativeFile, type QuickLookFile } from 'virtual:quicklook';
3
+ import FileViewer from '$lib/FileViewer.svelte';
4
+
5
+ let file = $state<QuickLookFile | undefined>(nativeFile());
6
+
7
+ async function open(files: FileList | null | undefined) {
8
+ const picked = files?.[0];
9
+ if (picked) {
10
+ file = await browserFile(picked);
11
+ }
12
+ }
13
+ </script>
14
+
15
+ <!-- Only reachable in the browser: QuickLook always injects a file. -->
16
+ <svelte:window
17
+ ondragover={(e) => e.preventDefault()}
18
+ ondrop={(e) => {
19
+ e.preventDefault();
20
+ open(e.dataTransfer?.files);
21
+ }}
22
+ />
23
+
24
+ {#if file}
25
+ <FileViewer {file} />
26
+ {:else}
27
+ <label class="drop">
28
+ Drop a file here, or click to pick one
29
+ <input type="file" hidden onchange={(e) => open(e.currentTarget.files)} />
30
+ </label>
31
+ {/if}
32
+
33
+ <style>
34
+ .drop {
35
+ display: grid;
36
+ place-items: center;
37
+ height: 100vh;
38
+ font: 14px system-ui;
39
+ color: GrayText;
40
+ cursor: pointer;
41
+ }
42
+ </style>
@@ -0,0 +1,11 @@
1
+ import adapter from '@sveltejs/adapter-static';
2
+
3
+ /** @type {import('@sveltejs/kit').Config} */
4
+ export default {
5
+ kit: {
6
+ // A static build; the extension serves it and sets the CSP from quicklook.config.ts.
7
+ adapter: adapter(),
8
+ // Builds a client-only app shell, which is all the preview needs.
9
+ router: { type: 'hash' },
10
+ },
11
+ };
@@ -0,0 +1,11 @@
1
+ {
2
+ "extends": "./.svelte-kit/tsconfig.json",
3
+ "compilerOptions": {
4
+ "strict": true,
5
+ "allowImportingTsExtensions": true,
6
+ "allowJs": true,
7
+ "checkJs": true,
8
+ "skipLibCheck": true,
9
+ "moduleResolution": "bundler"
10
+ }
11
+ }
@@ -0,0 +1,7 @@
1
+ import { sveltekit } from '@sveltejs/kit/vite';
2
+ import { quicklook } from 'quicklook-kit/vite';
3
+ import { defineConfig } from 'vite';
4
+
5
+ export default defineConfig({
6
+ plugins: [sveltekit(), quicklook()],
7
+ });
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.