@pantoken/scaffold 1.1.0 → 1.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.
- package/README.md +10 -3
- package/dist/cli.d.mts +25 -8
- package/dist/cli.mjs +217 -217
- package/dist/{index-CyolWk0E.d.mts → index-D2-Dhvt8.d.mts} +4 -2
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +1 -1
- package/dist/src-DxBSPAO9.mjs +903 -0
- package/package.json +18 -18
- package/dist/src-CdZxIoo2.mjs +0 -304
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pantoken/scaffold",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "npx @pantoken/scaffold <platform> — scaffold a starter project (html, react, next, angular, web-components, ...) with pantoken already installed and wired in.",
|
|
5
5
|
"homepage": "https://pantoken.app",
|
|
6
6
|
"bugs": "https://github.com/thedannywahl/pantoken/issues",
|
|
@@ -31,19 +31,19 @@
|
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@bomb.sh/tab": "^0.0.22",
|
|
33
33
|
"@clack/prompts": "^1.8.0",
|
|
34
|
-
"@pantoken/angular": "0.1.
|
|
34
|
+
"@pantoken/angular": "0.1.30",
|
|
35
35
|
"@pantoken/canvas-theme-editor": "0.2.0",
|
|
36
|
-
"@pantoken/components": "1.1.
|
|
37
|
-
"@pantoken/react": "0.1.
|
|
38
|
-
"@pantoken/scaffold-base": "0.
|
|
39
|
-
"@pantoken/svelte": "0.1.
|
|
40
|
-
"@pantoken/vue": "0.1.
|
|
41
|
-
"@pantoken/web-components": "0.6.
|
|
36
|
+
"@pantoken/components": "1.1.2",
|
|
37
|
+
"@pantoken/react": "0.1.31",
|
|
38
|
+
"@pantoken/scaffold-base": "0.3.0",
|
|
39
|
+
"@pantoken/svelte": "0.1.31",
|
|
40
|
+
"@pantoken/vue": "0.1.31",
|
|
41
|
+
"@pantoken/web-components": "0.6.2",
|
|
42
42
|
"bingo-stratum": "^0.5.13",
|
|
43
43
|
"commander": "^15.0.0"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
|
-
"@pantoken/i18n-engine": "0.
|
|
46
|
+
"@pantoken/i18n-engine": "0.3.0",
|
|
47
47
|
"@pantoken/plugin-layouts": "0.3.0",
|
|
48
48
|
"@types/node": "^26.5.0",
|
|
49
49
|
"typescript": "^7.0.2",
|
|
@@ -56,16 +56,16 @@
|
|
|
56
56
|
"scripts": {
|
|
57
57
|
"scan": "node scripts/scan-presets.ts",
|
|
58
58
|
"generate": "node scripts/generate.ts",
|
|
59
|
-
"extract": "i18n --config \"$(git rev-parse --show-toplevel)/i18n.config.json\" extract cli.scaffold",
|
|
60
|
-
"translate": "i18n --config \"$(git rev-parse --show-toplevel)/i18n.config.json\" translate cli.scaffold --provider claude",
|
|
61
|
-
"translate:agy": "i18n --config \"$(git rev-parse --show-toplevel)/i18n.config.json\" translate cli.scaffold --provider agy",
|
|
62
|
-
"translate:copilot": "i18n --config \"$(git rev-parse --show-toplevel)/i18n.config.json\" translate cli.scaffold --provider copilot",
|
|
63
|
-
"translate:force": "i18n --config \"$(git rev-parse --show-toplevel)/i18n.config.json\" translate cli.scaffold --provider claude --force",
|
|
64
|
-
"translate:force:agy": "i18n --config \"$(git rev-parse --show-toplevel)/i18n.config.json\" translate cli.scaffold --provider agy --force",
|
|
65
|
-
"translate:force:copilot": "i18n --config \"$(git rev-parse --show-toplevel)/i18n.config.json\" translate cli.scaffold --provider copilot --force",
|
|
66
|
-
"check:drift": "i18n --config \"$(git rev-parse --show-toplevel)/i18n.config.json\" check cli.scaffold",
|
|
59
|
+
"extract": "i18n --config \"$(git rev-parse --show-toplevel)/i18n.config.json\" extract cli.scaffold && i18n --config \"$(git rev-parse --show-toplevel)/i18n.config.json\" extract scaffold.readme",
|
|
60
|
+
"translate": "i18n --config \"$(git rev-parse --show-toplevel)/i18n.config.json\" translate cli.scaffold --provider claude && i18n --config \"$(git rev-parse --show-toplevel)/i18n.config.json\" translate scaffold.readme --provider claude",
|
|
61
|
+
"translate:agy": "i18n --config \"$(git rev-parse --show-toplevel)/i18n.config.json\" translate cli.scaffold --provider agy && i18n --config \"$(git rev-parse --show-toplevel)/i18n.config.json\" translate scaffold.readme --provider agy",
|
|
62
|
+
"translate:copilot": "i18n --config \"$(git rev-parse --show-toplevel)/i18n.config.json\" translate cli.scaffold --provider copilot && i18n --config \"$(git rev-parse --show-toplevel)/i18n.config.json\" translate scaffold.readme --provider copilot",
|
|
63
|
+
"translate:force": "i18n --config \"$(git rev-parse --show-toplevel)/i18n.config.json\" translate cli.scaffold --provider claude --force && i18n --config \"$(git rev-parse --show-toplevel)/i18n.config.json\" translate scaffold.readme --provider claude --force",
|
|
64
|
+
"translate:force:agy": "i18n --config \"$(git rev-parse --show-toplevel)/i18n.config.json\" translate cli.scaffold --provider agy --force && i18n --config \"$(git rev-parse --show-toplevel)/i18n.config.json\" translate scaffold.readme --provider agy --force",
|
|
65
|
+
"translate:force:copilot": "i18n --config \"$(git rev-parse --show-toplevel)/i18n.config.json\" translate cli.scaffold --provider copilot --force && i18n --config \"$(git rev-parse --show-toplevel)/i18n.config.json\" translate scaffold.readme --provider copilot --force",
|
|
66
|
+
"check:drift": "i18n --config \"$(git rev-parse --show-toplevel)/i18n.config.json\" check cli.scaffold && i18n --config \"$(git rev-parse --show-toplevel)/i18n.config.json\" check scaffold.readme",
|
|
67
67
|
"dev": "vp pack --watch",
|
|
68
68
|
"test": "vp run scan && vp run generate && vp test",
|
|
69
|
-
"check": "vp run scan && vp check &&
|
|
69
|
+
"check": "vp run scan && vp check && vp run check:drift"
|
|
70
70
|
}
|
|
71
71
|
}
|
package/dist/src-CdZxIoo2.mjs
DELETED
|
@@ -1,304 +0,0 @@
|
|
|
1
|
-
import { mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { dirname, join } from "node:path";
|
|
3
|
-
import { producePreset } from "bingo-stratum";
|
|
4
|
-
import { buildTheme } from "@pantoken/canvas-theme-editor";
|
|
5
|
-
import { presetComponents } from "@pantoken/components/scaffold-preset";
|
|
6
|
-
import { presetReact } from "@pantoken/react/scaffold-preset";
|
|
7
|
-
import { presetVue } from "@pantoken/vue/scaffold-preset";
|
|
8
|
-
import { presetWebComponents } from "@pantoken/web-components/scaffold-preset";
|
|
9
|
-
import { presetAngular } from "@pantoken/angular/scaffold-preset";
|
|
10
|
-
import { presetSvelte } from "@pantoken/svelte/scaffold-preset";
|
|
11
|
-
import { InvalidArgumentError } from "commander";
|
|
12
|
-
//#region generated/scaffolds.ts
|
|
13
|
-
const SCAFFOLDS = {
|
|
14
|
-
"angular": {
|
|
15
|
-
".gitignore": "node_modules\ndist\n",
|
|
16
|
-
"README.md": "# {{projectName}}\n\nScaffolded with `pantoken-scaffold angular` (or `pantoken-ai scaffold angular`) — a standalone\nAngular app (via Vite + `@analogjs/vite-plugin-angular`, no Angular CLI) styled with\n[`@pantoken/components`](https://www.npmjs.com/package/@pantoken/components) and\n[`@pantoken/angular`](https://www.npmjs.com/package/@pantoken/angular) (pantoken custom elements\nfor Angular).\n\n## Develop\n\n```sh\nnpm install\nnpm run dev\n```\n\n## What's here\n\n- `src/main.ts` calls `registerPantokenElements()` (from `@pantoken/angular`) once at bootstrap so\n elements like `<instui-icon>` work in templates, and imports the token/component CSS plus\n `@pantoken/plugin-layouts/layouts.css`.\n- `src/app.component.ts` is a standalone component (`CUSTOM_ELEMENTS_SCHEMA` lets the template\n compiler accept pantoken's custom element tags) whose template follows the `wrapper` app-shell\n layout from `@pantoken/plugin-layouts` (`.container` / `.header` / `.content` parts).\n- Apply classes like `class=\"instui-button\"` to markup; see the\n [component reference](https://pantoken.app/api/css) for the full class list.\n",
|
|
17
|
-
"index.html": "<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>{{projectName}}</title>\n </head>\n <body class=\"instui-display-flex\">\n <app-root></app-root>\n <script type=\"module\" src=\"/src/main.ts\"><\/script>\n </body>\n</html>\n",
|
|
18
|
-
"package.json": "{\n \"name\": \"{{projectName}}\",\n \"version\": \"0.0.0\",\n \"private\": true,\n \"type\": \"module\",\n \"scripts\": {\n \"dev\": \"vite\",\n \"build\": \"vite build\",\n \"preview\": \"vite preview\"\n },\n \"dependencies\": {\n \"@angular/common\": \"^22.1.3\",\n \"@angular/compiler\": \"^22.1.3\",\n \"@angular/core\": \"^22.1.3\",\n \"@angular/platform-browser\": \"^22.1.3\",\n \"@pantoken/angular\": \"latest\",\n \"@pantoken/plugin-layouts\": \"latest\",\n \"rxjs\": \"^7.8.2\",\n \"zone.js\": \"^0.16.2\"\n },\n \"devDependencies\": {\n \"@analogjs/vite-plugin-angular\": \"^2.7.0\",\n \"@pantoken/pantoken\": \"latest\",\n \"typescript\": \"^7.0.2\",\n \"vite\": \"^8.2.2\"\n },\n \"allowScripts\": {\n \"core-js\": true,\n \"ttf2woff2\": true\n },\n \"trustedDependencies\": [\n \"core-js\",\n \"ttf2woff2\"\n ]\n}\n",
|
|
19
|
-
"pnpm-workspace.yaml": "# core-js/ttf2woff2 arrive transitively via @pantoken/pantoken; pre-approve their install scripts\n# so `pnpm install` doesn't fail/warn under pnpm's default strictDepBuilds gate.\nallowBuilds:\n core-js: true\n ttf2woff2: true\n",
|
|
20
|
-
"tsconfig.json": "{\n \"compilerOptions\": {\n \"target\": \"ES2022\",\n \"lib\": [\"ES2022\", \"DOM\", \"DOM.Iterable\"],\n \"module\": \"ESNext\",\n \"moduleResolution\": \"Bundler\",\n \"experimentalDecorators\": true,\n \"useDefineForClassFields\": false,\n \"strict\": true,\n \"skipLibCheck\": true\n },\n \"include\": [\"src\"]\n}\n",
|
|
21
|
-
"vite.config.ts": "import angular from \"@analogjs/vite-plugin-angular\";\nimport { defineConfig } from \"vite\";\n\nexport default defineConfig({\n plugins: [angular()],\n});\n",
|
|
22
|
-
".vscode/extensions.json": "{\n \"recommendations\": [\"cssdoc.cssdoc-vscode\", \"angular.ng-template\"]\n}\n",
|
|
23
|
-
".vscode/settings.json": "{\n \"html.customData\": [\"./node_modules/@pantoken/pantoken/dist/html-custom-data.json\"],\n \"css.customData\": [\"./node_modules/@pantoken/pantoken/dist/css-custom-data.json\"]\n}\n",
|
|
24
|
-
"src/app.component.ts": "import { CUSTOM_ELEMENTS_SCHEMA, Component } from \"@angular/core\";\n\n@Component({\n selector: \"app-root\",\n standalone: true,\n schemas: [CUSTOM_ELEMENTS_SCHEMA], // accepts pantoken custom elements like <instui-icon>\n template: `\n<div class=\"container\">\n <div class=\"header\">\n <div class=\"top\">\n <!-- breadcrumb -->\n <div class=\"utilities\"> <!-- optional -->\n <button class=\"instui-button\"></button>\n </div>\n </div>\n <div class=\"info\">\n <div class=\"title\">\n {{projectName}}\n </div>\n <div class=\"description\"> <!-- optional -->\n Styled with <code>@pantoken/components</code> and laid out with the <code>wrapper</code> layout from <code>@pantoken/plugin-layouts</code>.\n </div>\n </div>\n <slot name=\"header\"></slot> <!-- optional -->\n <div class=\"actions\"> <!-- optional -->\n <button class=\"instui-button\"></button>\n </div>\n <div class=\"tabs\"> <!-- optional -->\n <!-- tabs -->\n </div>\n <div class=\"filters\"> <!-- optional -->\n <slot name=\"filters\"></slot>\n </div>\n </div>\n <div class=\"content\">\n <div class=\"list\"></div> <!-- optional -->\n <slot name=\"content\">\n <p>Get started building with pantoken.</p>\n <button class=\"instui-button -color-primary\" type=\"button\">Get started</button>\n </slot>\n <slot name=\"trailing\"></slot> <!-- optional -->\n </div>\n </div>\n `,\n})\nexport class AppComponent {}\n",
|
|
25
|
-
"src/main.ts": "import \"zone.js\";\nimport { registerPantokenElements } from \"@pantoken/angular\";\nimport { bootstrapApplication } from \"@angular/platform-browser\";\n// Defines every --instui-* custom property this app can reference.\nimport \"@pantoken/components/base.css\";\nimport \"@pantoken/components/components.css\";\nimport \"@pantoken/plugin-layouts/layouts.css\";\nimport { AppComponent } from \"./app.component.ts\";\n\nregisterPantokenElements(); // registers <instui-icon> and friends for use in templates\nbootstrapApplication(AppComponent);\n",
|
|
26
|
-
"cssdoc.json": "{\n \"$schema\": \"https://cssdoc.dev/cssdoc.schema.json\",\n \"extends\": [\n \"./node_modules/@pantoken/pantoken/cssdoc-base.json\"\n ],\n \"modifierConvention\": \"rscss\",\n \"inlineComments\": \"ignore\",\n \"providers\": [\n {\n \"path\": \"./node_modules/@pantoken/pantoken/model.json\"\n }\n ]\n}"
|
|
27
|
-
},
|
|
28
|
-
"canvas-theme-editor": {
|
|
29
|
-
".gitignore": "node_modules\ndist\n",
|
|
30
|
-
"README.md": "# {{projectName}}\n\nScaffolded with `pantoken-scaffold canvas-theme-editor` (or `pantoken-ai scaffold canvas-theme-editor`)\n— upload-ready assets for Canvas LMS's **Theme Editor**, plus a local preview for authoring Rich\nContent Editor (RCE) page templates styled with\n[`@pantoken/components`](https://www.npmjs.com/package/@pantoken/components).\n\n## What's here\n\n- `theme.css` / `theme.js` — upload these two files under **Theme Editor → Advanced** in Canvas.\n They're built for the CDN provider/theme/mode you chose when scaffolding (`--cdn`/`--theme`/\n `--theme-mode`; jsDelivr/rebrand/light by default) and include a loader for\n `@pantoken/interactions` (wires up component behaviors like modal, tooltip, and drilldown).\n- `index.html` / `src/main.ts` — a local, TinyMCE-based editor (`npm run dev`) that mirrors Canvas's\n RCE so you can author page templates against pantoken's classes before copying them into Canvas.\n The page itself is built with pantoken's `wrapper` layout: click **Edit theme** to open a tray\n with **Config** / **CSS** / **JS** tabs (CDN provider/theme/mode selects, and syntax-highlighted,\n hand-editable copies of `theme.css`/`theme.js` that feed the live preview), and the **?** button\n for full instructions. Use the **Layouts** toolbar button to start from one of pantoken's bundled\n starter page layouts (hero, callout, two-column, rubric note, testimonial), sourced from\n [`@pantoken/plugin-layouts`](https://www.npmjs.com/package/@pantoken/plugin-layouts).\n\n## Develop\n\n```sh\nnpm install\nnpm run dev\n```\n\nThis opens a Vite dev server with:\n\n- An **editor** pane (TinyMCE, matching the version/config\n [instructure/canvas-lms](https://github.com/instructure/canvas-lms) itself uses — see below).\n Use the **Layouts** button to load one of the bundled starter layouts (this replaces the whole\n document, after a confirm, rather than inserting at the cursor), and the **Source code** toggle\n to hand-edit the raw HTML in a syntax-highlighted (CodeMirror) view alongside the editor, with\n the preview pane updating live as you type — unlike TinyMCE's stock `code` plugin, which edits in\n a one-shot modal dialog with no live preview.\n- A **preview** pane showing the editor's content with the current CSS/JS tab's stylesheet/script\n applied (not just the resolved CDN URLs), wrapped to match Canvas's own content-area background\n and max width — a faithful \"what this will look like once uploaded to Canvas\" check, updated\n live as you type or edit the theme.\n- The editor and preview panes sit in a split view you can resize by dragging the divider, stack\n vertically instead of side by side, and swap the order of (drag a pane's toolbar onto the other,\n or use the **Swap panes** button). Each pane has its own fullscreen button.\n- The **Edit theme** tray's **Config** tab holds the CDN provider/theme/mode selects and\n **Download theme.css** / **Download theme.js** links; its **CSS**/**JS** tabs are editable copies\n of the generated files — hand-edit them and the preview picks up your changes directly. Changing\n a Config select re-generates the CSS/JS tabs unless you've already edited them, in which case a\n **Regenerate from config** button appears instead of silently overwriting your edit.\n\n## Workflow\n\n1. Upload `theme.css` and `theme.js` to Canvas's Theme Editor (**Advanced → CSS / JavaScript**) so\n pantoken's classes are available on every page.\n2. Run `npm run dev`, use **Layouts** to start from a bundled starter, and edit it like you\n would in Canvas's own RCE.\n3. Click **Copy HTML** and paste the result into a Canvas page's RCE (switch to the HTML editor\n view first). Canvas sanitizes pasted HTML server-side — this scaffold doesn't attempt to\n replicate that, so always verify the pasted result in Canvas (see \"Notes on Canvas's RCE\"\n below).\n\n## Notes on Canvas's RCE\n\nThe Canvas Rich Content Editor runs TinyMCE and sanitizes submitted HTML on the server against an\nallowlist (`gems/canvas_sanitize` in canvas-lms, kept in sync with TinyMCE's own\n`valid_elements`/`extended_valid_elements`). What that allowlist means for templates built here:\n\n- `class`, `id`, `style`, `title`, `role`, and `aria-*` attributes are allowed on every element —\n pantoken's `class=\"instui-*\"` styling hooks (and inline `style`, e.g. for CSS anchor positioning)\n **do** survive being pasted and saved, contrary to older guidance in this file. `<script>` tags\n are always stripped.\n- **RCE-safe pantoken components**: static/display components — alert, view, heading, text, table,\n a button-as-`<a>` (not a real `<button>`), blockquote. Stick to semantic HTML pantoken already\n supports (`div`, `p`, `span`, `a`, `img`, headings, lists, tables).\n- **RCE-unsafe**: anything from `@pantoken/interactions`'s behavior set — modal, tooltip,\n drilldown, close-button, or any other component driven by the native `popover`/`popovertarget`/\n `command`/`commandfor` attributes. Those attributes aren't in Canvas's sanitizer allowlist, so\n the markup survives but the interactive behavior silently breaks the first time the page is\n saved in Canvas, even with `theme.js`'s `@pantoken/interactions` loader present. Real form\n elements (`input`, `form`, `button`, `select`, `textarea`, `label`) and inline `<svg>` are\n stripped outright — none of the bundled starter templates use them.\n\n`theme.js` loads `@pantoken/interactions` from the CDN, which runs in Canvas's global page scope —\nuseful for interactions on pages you build directly in Canvas's RCE with raw HTML edits, but (per\nabove) any `popover`/`command`-attribute-driven markup pasted from the RCE preview here won't stay\ninteractive once saved.\n",
|
|
31
|
-
"index.html": "<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>{{projectName}} — RCE template authoring</title>\n </head>\n <body class=\"instui-display-flex\">\n <div id=\"app\"></div>\n <script type=\"module\" src=\"/src/main.ts\"><\/script>\n </body>\n</html>\n",
|
|
32
|
-
"package.json": "{\n \"name\": \"{{projectName}}\",\n \"version\": \"0.0.0\",\n \"private\": true,\n \"type\": \"module\",\n \"scripts\": {\n \"dev\": \"vite\",\n \"build\": \"vite build\",\n \"preview\": \"vite preview\"\n },\n \"dependencies\": {\n \"@pantoken/canvas-theme-editor\": \"latest\",\n \"@pantoken/cdn\": \"latest\",\n \"@pantoken/components\": \"latest\",\n \"@pantoken/css\": \"latest\",\n \"@pantoken/plugin-layouts\": \"latest\",\n \"@pantoken/tinymce\": \"latest\"\n },\n \"devDependencies\": {\n \"@codemirror/lang-css\": \"catalog:\",\n \"@codemirror/lang-html\": \"catalog:\",\n \"@codemirror/lang-javascript\": \"catalog:\",\n \"codemirror\": \"catalog:\",\n \"tinymce\": \"catalog:\",\n \"typescript\": \"^7.0.2\",\n \"vite\": \"^8.2.2\"\n },\n \"allowScripts\": {\n \"core-js\": true,\n \"ttf2woff2\": true\n },\n \"trustedDependencies\": [\n \"core-js\",\n \"ttf2woff2\"\n ]\n}\n",
|
|
33
|
-
"pnpm-workspace.yaml": "# core-js/ttf2woff2 arrive transitively via @pantoken/pantoken; pre-approve their install scripts\n# so `pnpm install` doesn't fail/warn under pnpm's default strictDepBuilds gate.\nallowBuilds:\n core-js: true\n ttf2woff2: true\n",
|
|
34
|
-
"tsconfig.json": "{\n \"compilerOptions\": {\n \"target\": \"ES2022\",\n \"lib\": [\"ES2022\", \"DOM\", \"DOM.Iterable\"],\n \"module\": \"ESNext\",\n \"moduleResolution\": \"Bundler\",\n \"types\": [\"vite/client\"],\n \"strict\": true,\n \"skipLibCheck\": true,\n \"noEmit\": true\n },\n \"include\": [\"src\"]\n}\n",
|
|
35
|
-
"vite.config.ts": "import { defineConfig } from \"vite\";\n\nexport default defineConfig({});\n",
|
|
36
|
-
"src/app.css": "/* Layout for this scaffold's own authoring UI (not part of any Canvas-pasted RCE content). */\n\n.instui-tray .list {\n display: flex;\n gap: 0.5rem;\n}\n\n.instui-tray .panel[hidden] {\n display: none;\n}\n\n.code-editor {\n border: var(--instui-component-tray-border-width, 1px) solid\n var(--instui-component-tray-border-color, #ccc);\n min-height: 20rem;\n}\n\n.split-pane {\n display: flex;\n flex-direction: row;\n gap: 0;\n min-height: 30rem;\n}\n\n.split-pane.-orientation-column {\n flex-direction: column;\n}\n\n.pane {\n flex: 1 1 50%;\n min-width: 0;\n min-height: 0;\n display: flex;\n flex-direction: column;\n}\n\n.pane:fullscreen {\n padding: 1rem;\n background: var(--instui-background-color-page, #fff);\n}\n\n.pane-toolbar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 0.5rem;\n /* Also the drag handle for reordering panes (not resizing — see .split-handle for that). */\n cursor: grab;\n}\n\n.pane-toolbar.-dragging {\n cursor: grabbing;\n opacity: 0.6;\n}\n\n.pane.-drop-target {\n outline: 2px dashed var(--instui-color-stroke-brand, #0374b5);\n}\n\n#editor,\n#css-editor,\n#js-editor,\n#preview {\n flex: 1 1 auto;\n min-height: 0;\n width: 100%;\n}\n\n#preview {\n border: 1px solid #ccc;\n}\n\n.split-handle {\n flex: 0 0 0.375rem;\n cursor: col-resize;\n background: var(--instui-color-stroke-base, #d3d3d3);\n}\n\n.split-pane.-orientation-column > .split-handle {\n cursor: row-resize;\n}\n\n.split-handle:hover,\n.split-handle.-dragging {\n background: var(--instui-color-stroke-brand, #0374b5);\n}\n",
|
|
37
|
-
"src/app.html": "<div class=\"container\">\n <div class=\"header\">\n <div class=\"info\">\n <div class=\"title\">{{pageTitle}}</div>\n <div class=\"description\">{{pageDescription}}</div>\n </div>\n <div class=\"actions\">\n <button class=\"instui-button\" type=\"button\" popovertarget=\"theme-tray\">\n {{editThemeLabel}}\n </button>\n <button\n class=\"instui-button -color-secondary\"\n type=\"button\"\n command=\"show-modal\"\n commandfor=\"help-modal\"\n aria-label=\"{{helpAriaLabel}}\"\n >\n ?\n </button>\n </div>\n </div>\n <div class=\"content\">\n <slot name=\"content\" class=\"main\">\n <p>\n <button type=\"button\" id=\"copy-html\">{{copyHtmlLabel}}</button>\n <span id=\"copy-status\" role=\"status\"></span>\n </p>\n <div class=\"split-pane\" id=\"split-pane\">\n <div class=\"pane editor-pane\" id=\"editor-pane\">\n <div class=\"pane-toolbar\" data-pane-handle=\"editor-pane\">\n <span>{{editorPaneLabel}}</span>\n <button\n type=\"button\"\n class=\"pane-fullscreen\"\n data-pane=\"editor-pane\"\n aria-label=\"{{fullscreenEditorAriaLabel}}\"\n >\n ⤢\n </button>\n </div>\n <textarea id=\"editor\"></textarea>\n </div>\n <div\n class=\"split-handle\"\n id=\"split-handle\"\n role=\"separator\"\n aria-orientation=\"vertical\"\n aria-label=\"{{resizePanesAriaLabel}}\"\n tabindex=\"0\"\n ></div>\n <div class=\"pane preview-pane\" id=\"preview-pane\">\n <div class=\"pane-toolbar\" data-pane-handle=\"preview-pane\">\n <span>{{previewPaneLabel}}</span>\n <button\n type=\"button\"\n class=\"pane-fullscreen\"\n data-pane=\"preview-pane\"\n aria-label=\"{{fullscreenPreviewAriaLabel}}\"\n >\n ⤢\n </button>\n </div>\n <iframe id=\"preview\" title=\"{{previewFrameTitle}}\"></iframe>\n </div>\n </div>\n <p class=\"split-controls\">\n <button type=\"button\" id=\"toggle-orientation\">{{stackVerticallyLabel}}</button>\n <button type=\"button\" id=\"swap-panes\">{{swapPanesLabel}}</button>\n </p>\n </slot>\n </div>\n</div>\n<div\n class=\"instui-tray -placement-end -size-md\"\n popover\n id=\"theme-tray\"\n aria-label=\"{{themeTrayAriaLabel}}\"\n>\n <div class=\"instui-tabs\">\n <div class=\"list\" role=\"tablist\" aria-label=\"{{themeTrayAriaLabel}}\">\n <button\n class=\"tab -selected\"\n role=\"tab\"\n aria-selected=\"true\"\n id=\"tab-config\"\n aria-controls=\"panel-config\"\n type=\"button\"\n >\n {{tabConfigLabel}}\n </button>\n <button\n class=\"tab\"\n role=\"tab\"\n aria-selected=\"false\"\n id=\"tab-css\"\n aria-controls=\"panel-css\"\n type=\"button\"\n >\n {{tabCssLabel}}\n </button>\n <button\n class=\"tab\"\n role=\"tab\"\n aria-selected=\"false\"\n id=\"tab-js\"\n aria-controls=\"panel-js\"\n type=\"button\"\n >\n {{tabJsLabel}}\n </button>\n </div>\n <div class=\"panel\" role=\"tabpanel\" id=\"panel-config\" aria-labelledby=\"tab-config\">\n <label>\n {{cdnProviderLabel}}\n <select id=\"cdn-provider\"></select>\n </label>\n <label>\n {{themeLabel}}\n <select id=\"theme-variant\">\n <option value=\"rebrand\">rebrand</option>\n <option value=\"canvas\">canvas</option>\n <option value=\"canvasHighContrast\">canvasHighContrast</option>\n </select>\n </label>\n <label>\n {{modeLabel}}\n <select id=\"theme-mode\">\n <option value=\"light\">light</option>\n <option value=\"adaptive\">adaptive</option>\n </select>\n </label>\n <p>\n <a id=\"download-theme-css\" download=\"theme.css\">{{downloadCssLabel}}</a>\n <a id=\"download-theme-js\" download=\"theme.js\">{{downloadJsLabel}}</a>\n </p>\n </div>\n <div class=\"panel\" role=\"tabpanel\" id=\"panel-css\" hidden aria-labelledby=\"tab-css\">\n <p><button type=\"button\" id=\"regenerate-css\" hidden>{{regenerateLabel}}</button></p>\n <div id=\"css-editor\" class=\"code-editor\"></div>\n </div>\n <div class=\"panel\" role=\"tabpanel\" id=\"panel-js\" hidden aria-labelledby=\"tab-js\">\n <p><button type=\"button\" id=\"regenerate-js\" hidden>{{regenerateLabel}}</button></p>\n <div id=\"js-editor\" class=\"code-editor\"></div>\n </div>\n </div>\n</div>\n<dialog class=\"instui-modal\" id=\"help-modal\" aria-labelledby=\"help-modal-title\">\n <div class=\"header\"><strong id=\"help-modal-title\">{{helpModalTitle}}</strong></div>\n <div class=\"body\">\n <p>{{helpModalBody1}}</p>\n <p>{{helpModalBody2}}</p>\n </div>\n <div class=\"footer\">\n <button class=\"instui-button\" type=\"button\" command=\"close\" commandfor=\"help-modal\">\n {{closeLabel}}\n </button>\n </div>\n</dialog>\n",
|
|
38
|
-
"src/main.ts": "/**\n * Authors starter page templates against pantoken's classes: TinyMCE for editing (pinned/\n * configured to match instructure/canvas-lms's own RCE — see README), a custom \"Insert template\"\n * plugin for the bundled starters, and a live styled preview pane fed by the chosen CDN\n * provider/theme/mode. The page shell itself is built with pantoken (wrapper layout, tray, modal,\n * tabs) so this authoring tool dogfoods the same library it's previewing.\n */\nimport \"@pantoken/css/style.rebrand.light.lean.css\"; // this app's own chrome always uses this theme, independent of the preview's chosen theme\nimport \"@pantoken/components/base.css\";\nimport \"@pantoken/components/components.css\";\nimport \"@pantoken/plugin-layouts/layouts.css\";\nimport \"./app.css\";\nimport appHtml from \"./app.html?raw\";\nimport { STRINGS } from \"./strings.ts\";\nimport tinymce from \"tinymce\";\nimport \"tinymce/themes/silver/theme\";\nimport \"tinymce/icons/default\";\n// canvas-lms injects its skin CSS as a raw string via a custom mechanism (RCEWrapper.tsx's\n// injectTinySkin()) — out of scope here; plain side-effect imports achieve the same look.\nimport \"tinymce/skins/ui/oxide/skin.css\";\nimport \"tinymce/skins/ui/oxide/content.css\";\nimport \"tinymce/skins/content/default/content.css\";\nimport {\n buildTheme,\n buildThemeCss,\n buildThemeJs,\n CDN_PROVIDERS,\n DEFAULT_CDN_PROVIDER_ID,\n type ThemeMode,\n type ThemeVariant,\n} from \"@pantoken/canvas-theme-editor\";\nimport type { Editor, Ui } from \"tinymce\";\nimport { basicSetup, EditorView } from \"codemirror\";\nimport { css } from \"@codemirror/lang-css\";\nimport { html } from \"@codemirror/lang-html\";\nimport { javascript } from \"@codemirror/lang-javascript\";\nimport type { CdnFile } from \"@pantoken/cdn\";\nimport {\n createLayoutsPlugin,\n createComponentsPlugin,\n createIconsPlugin,\n createLogosPlugin,\n listComponents,\n listUtilities,\n loadAllIcons,\n logos,\n products,\n pantokenHtmlLinter,\n pantokenHtmlCompletion,\n} from \"@pantoken/tinymce\";\n\n// Shared with the toolbar's source-view editor so both stay the same size.\nconst EDITOR_HEIGHT_PX = 400;\n// Canvas's content area caps out at this width — the preview mirrors that constraint.\nconst CANVAS_CONTENT_MAX_WIDTH = \"59.25rem\";\n\n/** Minimal `{{key}}` substitution — every key must exist in `strings`, values are inserted raw. */\nfunction render(template: string, strings: Record<string, string>): string {\n return template.replace(/\\{\\{(\\w+)\\}\\}/g, (match, key: string) => strings[key] ?? match);\n}\n\ndocument.querySelector<HTMLDivElement>(\"#app\")!.innerHTML = render(appHtml, STRINGS);\n\n// Tabs (Config | CSS | JS) — the .instui-tabs record is CSS-only; wiring selection is the\n// consumer's job, same as the component's own documented @example.\nconst tabButtons = document.querySelectorAll<HTMLButtonElement>('.instui-tabs [role=\"tab\"]');\nfor (const tab of tabButtons) {\n tab.addEventListener(\"click\", (): void => {\n for (const other of tabButtons) {\n const selected = other === tab;\n other.classList.toggle(\"-selected\", selected);\n other.setAttribute(\"aria-selected\", String(selected));\n const panel = document.getElementById(other.getAttribute(\"aria-controls\")!);\n if (panel) panel.hidden = !selected;\n }\n });\n}\n\n// Shared CSS assets array — tracks stylesheet URLs for dynamic injection into WYSIWYG editor.\n// Each picker plugin calls onMissingAsset to add new assets, and they're injected when an item is inserted.\nconst currentAssets: CdnFile[] = [];\nconst onMissingAsset = (asset: CdnFile) => {\n currentAssets.push(asset);\n // In a real app, you'd also trigger a download/export of the aggregated theme.css here.\n};\n\n// Register the Layouts plugin (replaces whole document on layout selection), sourced from\n// pantoken's own bundled starter page layouts (@pantoken/plugin-layouts).\nconst layoutsPlugin = createLayoutsPlugin();\ntinymce.PluginManager.add(\"pantoken_layouts\", function (editor: Editor) {\n layoutsPlugin(editor);\n // Wire the layout selection to also update the source view and refresh preview.\n const originalInsertContent = editor.insertContent;\n editor.insertContent = function (value: string) {\n originalInsertContent.call(this, value);\n replaceSourceDoc(editor.getContent());\n refreshAll();\n };\n return {};\n});\n\n/** Creates a CodeMirror editor for the given language, wired to call `onChange` on edits. */\nfunction createEditor(\n container: HTMLElement,\n lang: \"html\" | \"css\" | \"javascript\",\n value: string,\n onChange: () => void,\n): EditorView {\n const langExtension = lang === \"css\" ? css() : lang === \"javascript\" ? javascript() : html();\n const extensions: any[] = [\n basicSetup,\n langExtension,\n EditorView.lineWrapping,\n EditorView.updateListener.of((update: any): void => {\n if (update.docChanged) onChange();\n }),\n ];\n \n // Add pantoken linter + autocomplete for HTML editor only.\n if (lang === \"html\") {\n extensions.push(\n pantokenHtmlLinter({ model: [...listComponents(), ...listUtilities()] as any }),\n pantokenHtmlCompletion({ model: [...listComponents(), ...listUtilities()] as any })\n );\n }\n \n return new EditorView({\n doc: value,\n extensions,\n parent: container,\n });\n}\n\n/** Replaces an editor's full document contents. */\nfunction replaceEditorDoc(view: EditorView, content: string): void {\n view.dispatch({ changes: { from: 0, to: view.state.doc.length, insert: content } });\n}\n\n/** Lazily creates the CodeMirror instance (HTML syntax highlighting) alongside the hidden iframe. */\nfunction ensureSourceView(editor: Editor): EditorView {\n if (sourceView) return sourceView;\n const container = document.createElement(\"div\");\n container.style.cssText = `display: none; width: 100%; height: ${EDITOR_HEIGHT_PX}px; box-sizing: border-box; overflow: auto; border: 1px solid #ccc; font-size: 13px;`;\n editor.contentAreaContainer.insertAdjacentElement(\"afterend\", container);\n sourceContainer = container;\n sourceView = createEditor(container, \"html\", \"\", debouncedUpdatePreview);\n return sourceView;\n}\n\n/** Replaces the CodeMirror document's full contents, if the source view has been created. */\nfunction replaceSourceDoc(content: string): void {\n if (!sourceView) return;\n replaceEditorDoc(sourceView, content);\n}\n\n// Register the three pantoken pickers (components, icons, logos) as TinyMCE plugins.\n// Each picker has its own toolbar button and manages CSS asset tracking. tinymce.init() below\n// awaits this promise first — plugin names must be registered before init() resolves them.\nconst registerPickerPlugins = (async () => {\n const componentModel = [...listComponents(), ...listUtilities()];\n const allIcons = await loadAllIcons();\n \n const componentsPlugin = createComponentsPlugin({\n model: componentModel as any,\n currentAssets,\n onMissingAsset,\n });\n tinymce.PluginManager.add(\"pantoken_components\", componentsPlugin);\n \n const iconsPlugin = createIconsPlugin({\n icons: allIcons,\n currentAssets,\n onMissingAsset,\n });\n tinymce.PluginManager.add(\"pantoken_icons\", iconsPlugin);\n \n const logosPlugin = createLogosPlugin({\n logos,\n products,\n currentAssets,\n onMissingAsset,\n });\n tinymce.PluginManager.add(\"pantoken_logos\", logosPlugin);\n})();\n\n// Toggles between TinyMCE's WYSIWYG view and a syntax-highlighted CodeMirror source view.\n// The source view also includes pantoken linter (validation) and autocomplete (IntelliSense).\ntinymce.PluginManager.add(\"pantoken_source_toggle\", function (editor: Editor) {\n const toggle = (api: Ui.Toolbar.ToolbarToggleButtonInstanceApi) => {\n sourceMode = !sourceMode;\n api.setActive(sourceMode);\n const view = ensureSourceView(editor);\n const contentArea = editor.contentAreaContainer;\n if (sourceMode) {\n replaceSourceDoc(editor.getContent());\n contentArea.style.display = \"none\";\n sourceContainer!.style.display = \"block\";\n view.focus();\n } else {\n contentArea.style.display = \"\";\n sourceContainer!.style.display = \"none\";\n editor.setContent(view.state.doc.toString());\n }\n refreshAll();\n };\n\n editor.ui.registry.addToggleButton(\"sourcecode\", {\n icon: \"sourcecode\",\n tooltip: \"Source code\",\n onAction: toggle,\n });\n\n return {};\n});\n\nconst providerSelect = document.querySelector<HTMLSelectElement>(\"#cdn-provider\")!;\nfor (const [id, provider] of Object.entries(CDN_PROVIDERS)) {\n const option = document.createElement(\"option\");\n option.value = id;\n option.textContent = provider.label;\n providerSelect.append(option);\n}\nproviderSelect.value = DEFAULT_CDN_PROVIDER_ID;\n\nconst themeSelect = document.querySelector<HTMLSelectElement>(\"#theme-variant\")!;\nconst modeSelect = document.querySelector<HTMLSelectElement>(\"#theme-mode\")!;\nconst previewFrame = document.querySelector<HTMLIFrameElement>(\"#preview\")!;\nconst downloadCss = document.querySelector<HTMLAnchorElement>(\"#download-theme-css\")!;\nconst downloadJs = document.querySelector<HTMLAnchorElement>(\"#download-theme-js\")!;\nconst copyButton = document.querySelector<HTMLButtonElement>(\"#copy-html\")!;\nconst copyStatus = document.querySelector<HTMLElement>(\"#copy-status\")!;\nconst regenerateCssButton = document.querySelector<HTMLButtonElement>(\"#regenerate-css\")!;\nconst regenerateJsButton = document.querySelector<HTMLButtonElement>(\"#regenerate-js\")!;\n\nlet activeEditor: Editor | undefined;\nlet cssObjectUrl: string | undefined;\nlet jsObjectUrl: string | undefined;\nlet sourceView: EditorView | undefined;\nlet sourceContainer: HTMLDivElement | undefined;\nlet sourceMode = false;\n// Once the author hand-edits the CSS/JS tab, config-select changes no longer overwrite it —\n// they only re-enable \"Regenerate from config\" so the edit isn't silently discarded.\nlet cssDirty = false;\nlet jsDirty = false;\n\n/** Reads the author's current HTML from whichever view (WYSIWYG or raw source) is active. */\nfunction getEditorHtml(): string {\n if (sourceMode && sourceView) return sourceView.state.doc.toString();\n return activeEditor?.getContent() ?? \"\";\n}\n\nfunction currentSelection(): { provider: string; theme: ThemeVariant; mode: ThemeMode } {\n return {\n provider: providerSelect.value,\n theme: themeSelect.value as ThemeVariant,\n mode: modeSelect.value as ThemeMode,\n };\n}\n\n/** Debounce helper — the preview pane only needs to refresh a beat after typing settles. */\nfunction debounce<Args extends unknown[]>(fn: (...args: Args) => void, ms: number) {\n let handle: ReturnType<typeof setTimeout> | undefined;\n return (...args: Args) => {\n if (handle) clearTimeout(handle);\n handle = setTimeout(() => fn(...args), ms);\n };\n}\n\n/**\n * Renders the editor's current HTML with the CSS/JS tab's *current editable text* embedded\n * directly (not linked from the CDN) — so hand-edits in those tabs show up in the preview too.\n * The body content is wrapped to match Canvas's own content-area background and max width.\n */\nfunction updatePreview(): void {\n if (!activeEditor) return;\n const themeCss = cssEditorView.state.doc.toString();\n const themeJs = jsEditorView.state.doc.toString();\n previewFrame.srcdoc = `<!doctype html><html><head><style>${themeCss}</style></head><body>\n <div class=\"instui-view -background-primary\" style=\"max-width: ${CANVAS_CONTENT_MAX_WIDTH}; margin-inline: auto;\">\n ${getEditorHtml()}\n </div>\n <script>${themeJs}<\/script>\n </body></html>`;\n}\nconst debouncedUpdatePreview = debounce(updatePreview, 300);\n\n/** Regenerates the downloadable theme.css/theme.js blobs for the chosen provider/theme/mode. */\nfunction updateDownloads(): void {\n const { provider, theme, mode } = currentSelection();\n const { css: generatedCss, js: generatedJs } = buildTheme({ provider, theme, mode });\n if (cssObjectUrl) URL.revokeObjectURL(cssObjectUrl);\n if (jsObjectUrl) URL.revokeObjectURL(jsObjectUrl);\n cssObjectUrl = URL.createObjectURL(new Blob([generatedCss], { type: \"text/css\" }));\n jsObjectUrl = URL.createObjectURL(new Blob([generatedJs], { type: \"text/javascript\" }));\n downloadCss.href = cssObjectUrl;\n downloadJs.href = jsObjectUrl;\n}\n\n/** Re-seeds the CSS/JS editors from the current config, unless the author has hand-edited them. */\nfunction updateThemeEditors(): void {\n const { provider, theme, mode } = currentSelection();\n if (!cssDirty) replaceEditorDoc(cssEditorView, buildThemeCss({ provider, theme, mode }));\n if (!jsDirty) replaceEditorDoc(jsEditorView, buildThemeJs({ provider, theme, mode }));\n}\n\nfunction refreshAll(): void {\n updateThemeEditors();\n updatePreview();\n updateDownloads();\n}\n\n// CSS/JS editors, seeded from the initial config selection.\nconst cssEditorView = createEditor(\n document.querySelector<HTMLElement>(\"#css-editor\")!,\n \"css\",\n buildThemeCss(currentSelection()),\n () => {\n cssDirty = true;\n regenerateCssButton.hidden = false;\n debouncedUpdatePreview();\n },\n);\nconst jsEditorView = createEditor(\n document.querySelector<HTMLElement>(\"#js-editor\")!,\n \"javascript\",\n buildThemeJs(currentSelection()),\n () => {\n jsDirty = true;\n regenerateJsButton.hidden = false;\n debouncedUpdatePreview();\n },\n);\n\nregenerateCssButton.addEventListener(\"click\", () => {\n replaceEditorDoc(cssEditorView, buildThemeCss(currentSelection()));\n cssDirty = false;\n regenerateCssButton.hidden = true;\n debouncedUpdatePreview();\n});\nregenerateJsButton.addEventListener(\"click\", () => {\n replaceEditorDoc(jsEditorView, buildThemeJs(currentSelection()));\n jsDirty = false;\n regenerateJsButton.hidden = true;\n debouncedUpdatePreview();\n});\n\nproviderSelect.addEventListener(\"change\", refreshAll);\nthemeSelect.addEventListener(\"change\", refreshAll);\nmodeSelect.addEventListener(\"change\", refreshAll);\n\ncopyButton.addEventListener(\"click\", () => {\n void navigator.clipboard.writeText(getEditorHtml()).then(() => {\n copyStatus.textContent = \"Copied. Paste into the Canvas Rich Content Editor HTML view.\";\n });\n});\n\n// --- Editor/preview split-pane: orientation toggle, drag-resize, drag-to-swap, fullscreen. ---\nconst splitPane = document.querySelector<HTMLElement>(\"#split-pane\")!;\nconst splitHandle = document.querySelector<HTMLElement>(\"#split-handle\")!;\nconst editorPane = document.querySelector<HTMLElement>(\"#editor-pane\")!;\nconst previewPane = document.querySelector<HTMLElement>(\"#preview-pane\")!;\nconst orientationButton = document.querySelector<HTMLButtonElement>(\"#toggle-orientation\")!;\nconst swapButton = document.querySelector<HTMLButtonElement>(\"#swap-panes\")!;\n\nlet orientation: \"row\" | \"column\" = \"row\";\n\nfunction setPaneFlexBasis(fraction: number): void {\n const clamped = Math.min(0.85, Math.max(0.15, fraction));\n editorPane.style.flexBasis = `${clamped * 100}%`;\n previewPane.style.flexBasis = `${(1 - clamped) * 100}%`;\n}\n\norientationButton.addEventListener(\"click\", () => {\n orientation = orientation === \"row\" ? \"column\" : \"row\";\n splitPane.classList.toggle(\"-orientation-column\", orientation === \"column\");\n splitHandle.setAttribute(\"aria-orientation\", orientation === \"row\" ? \"vertical\" : \"horizontal\");\n orientationButton.textContent =\n orientation === \"row\" ? STRINGS.stackVerticallyLabel : STRINGS.stackSideBySideLabel;\n setPaneFlexBasis(0.5);\n});\n\n/** Reorders the editor/preview panes (used by both the drag gesture and the fallback button). */\nfunction swapPanes(): void {\n const parent = editorPane.parentElement!;\n const firstIsEditor = parent.firstElementChild === editorPane;\n if (firstIsEditor) parent.insertBefore(previewPane, editorPane);\n else parent.insertBefore(editorPane, previewPane);\n}\nswapButton.addEventListener(\"click\", swapPanes);\n\n// Drag-resize the split via the handle.\nsplitHandle.addEventListener(\"pointerdown\", (event) => {\n splitHandle.classList.add(\"-dragging\");\n splitHandle.setPointerCapture(event.pointerId);\n const onMove = (moveEvent: PointerEvent): void => {\n const rect = splitPane.getBoundingClientRect();\n const fraction =\n orientation === \"row\"\n ? (moveEvent.clientX - rect.left) / rect.width\n : (moveEvent.clientY - rect.top) / rect.height;\n setPaneFlexBasis(fraction);\n };\n const onUp = (): void => {\n splitHandle.classList.remove(\"-dragging\");\n splitHandle.removeEventListener(\"pointermove\", onMove);\n splitHandle.removeEventListener(\"pointerup\", onUp);\n };\n splitHandle.addEventListener(\"pointermove\", onMove);\n splitHandle.addEventListener(\"pointerup\", onUp);\n});\n\n// Drag a pane's toolbar onto the other pane to swap their order (a pointer-based reorder gesture;\n// `swapButton` above is the keyboard-accessible equivalent for anyone who can't drag).\nfor (const handle of document.querySelectorAll<HTMLElement>(\"[data-pane-handle]\")) {\n const paneId = handle.dataset.paneHandle!;\n const pane = document.getElementById(paneId)!;\n const otherPane = pane === editorPane ? previewPane : editorPane;\n\n handle.addEventListener(\"pointerdown\", (event: PointerEvent): void => {\n handle.classList.add(\"-dragging\");\n handle.setPointerCapture(event.pointerId);\n const onMove = (moveEvent: PointerEvent): void => {\n const rect = otherPane.getBoundingClientRect();\n const over =\n moveEvent.clientX >= rect.left &&\n moveEvent.clientX <= rect.right &&\n moveEvent.clientY >= rect.top &&\n moveEvent.clientY <= rect.bottom;\n otherPane.classList.toggle(\"-drop-target\", over);\n };\n const onUp = (upEvent: PointerEvent): void => {\n handle.classList.remove(\"-dragging\");\n const droppedOnOther = otherPane.classList.contains(\"-drop-target\");\n otherPane.classList.remove(\"-drop-target\");\n if (droppedOnOther) swapPanes();\n handle.removeEventListener(\"pointermove\", onMove);\n handle.removeEventListener(\"pointerup\", onUp);\n void upEvent;\n };\n handle.addEventListener(\"pointermove\", onMove);\n handle.addEventListener(\"pointerup\", onUp);\n });\n}\n\n// Per-pane fullscreen toggle.\nfor (const button of document.querySelectorAll<HTMLButtonElement>(\".pane-fullscreen\")) {\n const pane = document.getElementById(button.dataset.pane!)!;\n button.addEventListener(\"click\", (): void => {\n if (document.fullscreenElement === pane) void document.exitFullscreen();\n else void pane.requestFullscreen();\n });\n}\n\nvoid registerPickerPlugins.then(() =>\n tinymce\n .init({\n selector: \"#editor\",\n // Load all five plugin modules: layouts, source-toggle (custom), and three pickers (from @pantoken/tinymce).\n plugins: \"pantoken_layouts pantoken_source_toggle pantoken_components pantoken_icons pantoken_logos\",\n // Toolbar includes the layouts button, source-view toggle, and the three picker buttons.\n toolbar: \"pantokenLayouts sourcecode | pantokenComponents pantokenIcons pantokenLogos\",\n menubar: false,\n theme: \"silver\",\n branding: false,\n browser_spellcheck: true,\n convert_urls: false,\n toolbar_mode: \"floating\",\n height: EDITOR_HEIGHT_PX,\n skin: false,\n content_css: false,\n setup(editor: Editor): void {\n editor.on(\"input change Undo Redo SetContent\", () => debouncedUpdatePreview());\n },\n })\n .then((editors: Editor[]) => {\n activeEditor = editors[0];\n refreshAll();\n }),\n);\n",
|
|
39
|
-
"cssdoc.json": "{\n \"$schema\": \"https://cssdoc.dev/cssdoc.schema.json\",\n \"extends\": [\n \"./node_modules/@pantoken/pantoken/cssdoc-base.json\"\n ],\n \"modifierConvention\": \"rscss\",\n \"inlineComments\": \"ignore\",\n \"providers\": [\n {\n \"path\": \"./node_modules/@pantoken/pantoken/model.json\"\n }\n ]\n}",
|
|
40
|
-
"src/strings.ts": "// GENERATED by scripts/generate.ts — do not edit by hand.\n/** English UI strings, resolved at runtime by the platform's `{{key}}` substitution. */\nexport const STRINGS: Record<string, string> = {\n \"pageTitle\": \"RCE template authoring\",\n \"pageDescription\": \"Author Canvas RCE page templates styled with pantoken, with a live preview.\",\n \"editThemeLabel\": \"Edit theme\",\n \"helpAriaLabel\": \"Help\",\n \"copyHtmlLabel\": \"Copy HTML\",\n \"editorPaneLabel\": \"Editor\",\n \"previewPaneLabel\": \"Preview\",\n \"previewFrameTitle\": \"Styled preview\",\n \"fullscreenEditorAriaLabel\": \"Fullscreen editor\",\n \"fullscreenPreviewAriaLabel\": \"Fullscreen preview\",\n \"resizePanesAriaLabel\": \"Resize editor and preview\",\n \"stackVerticallyLabel\": \"Stack vertically\",\n \"stackSideBySideLabel\": \"Stack side by side\",\n \"swapPanesLabel\": \"Swap panes\",\n \"themeTrayAriaLabel\": \"Theme settings\",\n \"tabConfigLabel\": \"Config\",\n \"tabCssLabel\": \"CSS\",\n \"tabJsLabel\": \"JS\",\n \"cdnProviderLabel\": \"CDN provider\",\n \"themeLabel\": \"Theme\",\n \"modeLabel\": \"Mode\",\n \"downloadCssLabel\": \"Download theme.css\",\n \"downloadJsLabel\": \"Download theme.js\",\n \"regenerateLabel\": \"Regenerate from config\",\n \"helpModalTitle\": \"How to use this page\",\n \"helpModalBody1\": \"Pick a starter template, edit it like you would in the Canvas Rich Content Editor, then click <strong>Copy HTML</strong> and paste the result into a Canvas page (switch to the HTML editor view first). Canvas sanitizes pasted HTML server-side — this preview doesn't attempt to replicate that, so always verify the pasted result in Canvas.\",\n \"helpModalBody2\": \"Use <strong>Edit theme</strong> to change the CDN provider, theme, or mode, and to hand-edit the generated <code>theme.css</code>/<code>theme.js</code> before downloading them.\",\n \"closeLabel\": \"Close\"\n};\n"
|
|
41
|
-
},
|
|
42
|
-
"components": {
|
|
43
|
-
".gitignore": "node_modules\ndist\n",
|
|
44
|
-
"README.md": "# {{projectName}}\n\nScaffolded with `pantoken-scaffold components` (or `pantoken-ai scaffold components`) — a plain\nTypeScript app (via Vite) styled with\n[`@pantoken/components`](https://www.npmjs.com/package/@pantoken/components), Instructure's\ndesign tokens and icons.\n\n## Develop\n\n```sh\nnpm install\nnpm run dev\n```\n\n## What's here\n\n- `src/main.ts` imports `@pantoken/components/base.css` (document defaults + focus ring),\n `components.css` (`.instui-*` classes), and `@pantoken/plugin-layouts/layouts.css`, then renders\n markup into `#app`.\n- The markup follows the `wrapper` app-shell layout from `@pantoken/plugin-layouts` (`.container` /\n `.header` / `.content` parts) — see that package's README for what each part is for.\n- Apply classes like `class=\"instui-button\"` to markup; see the\n [component reference](https://pantoken.app/api/css) for the full class list.\n",
|
|
45
|
-
"index.html": "<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>{{projectName}}</title>\n </head>\n <body class=\"instui-display-flex\">\n <div id=\"app\"></div>\n <script type=\"module\" src=\"/src/main.ts\"><\/script>\n </body>\n</html>\n",
|
|
46
|
-
"package.json": "{\n \"name\": \"{{projectName}}\",\n \"version\": \"0.0.0\",\n \"private\": true,\n \"type\": \"module\",\n \"scripts\": {\n \"dev\": \"vite\",\n \"build\": \"vite build\",\n \"preview\": \"vite preview\"\n },\n \"dependencies\": {\n \"@pantoken/components\": \"latest\",\n \"@pantoken/plugin-layouts\": \"latest\"\n },\n \"devDependencies\": {\n \"@pantoken/pantoken\": \"latest\",\n \"typescript\": \"^7.0.2\",\n \"vite\": \"^8.2.2\"\n },\n \"allowScripts\": {\n \"core-js\": true,\n \"ttf2woff2\": true\n },\n \"trustedDependencies\": [\n \"core-js\",\n \"ttf2woff2\"\n ]\n}\n",
|
|
47
|
-
"pnpm-workspace.yaml": "# core-js/ttf2woff2 arrive transitively via @pantoken/pantoken; pre-approve their install scripts\n# so `pnpm install` doesn't fail/warn under pnpm's default strictDepBuilds gate.\nallowBuilds:\n core-js: true\n ttf2woff2: true\n",
|
|
48
|
-
"tsconfig.json": "{\n \"compilerOptions\": {\n \"target\": \"ES2022\",\n \"lib\": [\"ES2022\", \"DOM\", \"DOM.Iterable\"],\n \"module\": \"ESNext\",\n \"moduleResolution\": \"Bundler\",\n \"strict\": true,\n \"skipLibCheck\": true\n },\n \"include\": [\"src\"]\n}\n",
|
|
49
|
-
"vite.config.ts": "import { defineConfig } from \"vite\";\n\nexport default defineConfig({});\n",
|
|
50
|
-
".vscode/extensions.json": "{\n \"recommendations\": [\"cssdoc.cssdoc-vscode\"]\n}\n",
|
|
51
|
-
".vscode/settings.json": "{\n \"html.customData\": [\"./node_modules/@pantoken/pantoken/dist/html-custom-data.json\"],\n \"css.customData\": [\"./node_modules/@pantoken/pantoken/dist/css-custom-data.json\"]\n}\n",
|
|
52
|
-
"src/main.ts": "// Defines every --instui-* custom property this app can reference.\nimport \"@pantoken/components/base.css\";\nimport \"@pantoken/components/components.css\";\nimport \"@pantoken/plugin-layouts/layouts.css\";\n\ndocument.querySelector<HTMLDivElement>(\"#app\")!.innerHTML = `\n<div class=\"container\">\n <div class=\"header\">\n <div class=\"top\">\n <!-- breadcrumb -->\n <div class=\"utilities\"> <!-- optional -->\n <button class=\"instui-button\"></button>\n </div>\n </div>\n <div class=\"info\">\n <div class=\"title\">\n {{projectName}}\n </div>\n <div class=\"description\"> <!-- optional -->\n Styled with <code>@pantoken/components</code> and laid out with the <code>wrapper</code> layout from <code>@pantoken/plugin-layouts</code>.\n </div>\n </div>\n <slot name=\"header\"></slot> <!-- optional -->\n <div class=\"actions\"> <!-- optional -->\n <button class=\"instui-button\"></button>\n </div>\n <div class=\"tabs\"> <!-- optional -->\n <!-- tabs -->\n </div>\n <div class=\"filters\"> <!-- optional -->\n <slot name=\"filters\"></slot>\n </div>\n </div>\n <div class=\"content\">\n <div class=\"list\"></div> <!-- optional -->\n <slot name=\"content\">\n <p>Get started building with pantoken.</p>\n <button class=\"instui-button -color-primary\" type=\"button\">Get started</button>\n </slot>\n <slot name=\"trailing\"></slot> <!-- optional -->\n </div>\n </div>\n`;\n",
|
|
53
|
-
"cssdoc.json": "{\n \"$schema\": \"https://cssdoc.dev/cssdoc.schema.json\",\n \"extends\": [\n \"./node_modules/@pantoken/pantoken/cssdoc-base.json\"\n ],\n \"modifierConvention\": \"rscss\",\n \"inlineComments\": \"ignore\",\n \"providers\": [\n {\n \"path\": \"./node_modules/@pantoken/pantoken/model.json\"\n }\n ]\n}"
|
|
54
|
-
},
|
|
55
|
-
"next": {
|
|
56
|
-
".gitignore": "node_modules\n.next\n",
|
|
57
|
-
"README.md": "# {{projectName}}\n\nScaffolded with `pantoken-scaffold next` (or `pantoken-ai scaffold next`) — a Next.js (App Router) app styled with\n[`@pantoken/next`](https://www.npmjs.com/package/@pantoken/next), Instructure's design tokens and\nicons.\n\n## Develop\n\n```sh\nnpm install\nnpm run dev\n```\n\n## What's here\n\n- `next.config.mjs` wraps the config with `withPantoken(...)` (adds pantoken to\n `transpilePackages`).\n- `app/layout.tsx` imports `@pantoken/css/inject`, which defines every `--instui-*` custom\n property, at the root so it's available on every page, plus `@pantoken/plugin-layouts/layouts.css`\n and the `wrapper` layout's root class on `<body>`.\n- `app/page.tsx` renders markup following the `wrapper` app-shell layout from\n [`@pantoken/plugin-layouts`](https://www.npmjs.com/package/@pantoken/plugin-layouts)\n (`.container` / `.header` / `.content` parts) styled with `instui-*` classes.\n",
|
|
58
|
-
"next.config.mjs": "import { withPantoken } from \"@pantoken/next\";\n\n/** @type {import('next').NextConfig} */\nconst nextConfig = {};\n\nexport default withPantoken(nextConfig);\n",
|
|
59
|
-
"package.json": "{\n \"name\": \"{{projectName}}\",\n \"version\": \"0.0.0\",\n \"private\": true,\n \"type\": \"module\",\n \"scripts\": {\n \"dev\": \"next dev\",\n \"build\": \"next build\",\n \"start\": \"next start\"\n },\n \"dependencies\": {\n \"@pantoken/next\": \"latest\",\n \"@pantoken/plugin-layouts\": \"latest\",\n \"next\": \"^16.2.11\",\n \"react\": \"^19.2.8\",\n \"react-dom\": \"^19.2.8\"\n },\n \"devDependencies\": {\n \"@pantoken/pantoken\": \"latest\",\n \"typescript\": \"^7.0.2\"\n },\n \"allowScripts\": {\n \"core-js\": true,\n \"ttf2woff2\": true\n },\n \"trustedDependencies\": [\n \"core-js\",\n \"ttf2woff2\"\n ]\n}\n",
|
|
60
|
-
"pnpm-workspace.yaml": "# core-js/ttf2woff2 arrive transitively via @pantoken/pantoken; pre-approve their install scripts\n# so `pnpm install` doesn't fail/warn under pnpm's default strictDepBuilds gate.\nallowBuilds:\n core-js: true\n ttf2woff2: true\n",
|
|
61
|
-
"tsconfig.json": "{\n \"compilerOptions\": {\n \"target\": \"ES2022\",\n \"lib\": [\"ES2022\", \"DOM\", \"DOM.Iterable\"],\n \"module\": \"ESNext\",\n \"moduleResolution\": \"Bundler\",\n \"jsx\": \"preserve\",\n \"strict\": true,\n \"skipLibCheck\": true,\n \"plugins\": [{ \"name\": \"next\" }]\n },\n \"include\": [\"app\", \"next-env.d.ts\"]\n}\n",
|
|
62
|
-
".vscode/extensions.json": "{\n \"recommendations\": [\"cssdoc.cssdoc-vscode\"]\n}\n",
|
|
63
|
-
".vscode/settings.json": "{\n \"html.customData\": [\"./node_modules/@pantoken/pantoken/dist/html-custom-data.json\"],\n \"css.customData\": [\"./node_modules/@pantoken/pantoken/dist/css-custom-data.json\"]\n}\n",
|
|
64
|
-
"app/layout.tsx": "{{pantokenCssImport}}\nimport \"@pantoken/plugin-layouts/layouts.css\";\n\nexport const metadata = {\n title: \"{{projectName}}\",\n};\n\nexport default function RootLayout({ children }: { children: React.ReactNode }) {\n return (\n <html lang=\"en\">\n <body className=\"instui-display-flex\">{children}</body>\n </html>\n );\n}\n",
|
|
65
|
-
"app/page.tsx": "export default function Page() {\n return (\n<div className=\"container\">\n <div className=\"header\">\n <div className=\"top\">\n {/* breadcrumb */}\n <div className=\"utilities\"> {/* optional */}\n <button className=\"instui-button\"></button>\n </div>\n </div>\n <div className=\"info\">\n <div className=\"title\">\n {{projectName}}\n </div>\n <div className=\"description\"> {/* optional */}\n Styled with <code>@pantoken/components</code> and laid out with the <code>wrapper</code> layout from <code>@pantoken/plugin-layouts</code>.\n </div>\n </div>\n <slot name=\"header\"></slot> {/* optional */}\n <div className=\"actions\"> {/* optional */}\n <button className=\"instui-button\"></button>\n </div>\n <div className=\"tabs\"> {/* optional */}\n {/* tabs */}\n </div>\n <div className=\"filters\"> {/* optional */}\n <slot name=\"filters\"></slot>\n </div>\n </div>\n <div className=\"content\">\n <div className=\"list\"></div> {/* optional */}\n <slot name=\"content\">\n <p>Get started building with pantoken.</p>\n <button className=\"instui-button -color-primary\" type=\"button\">Get started</button>\n </slot>\n <slot name=\"trailing\"></slot> {/* optional */}\n </div>\n </div>\n );\n}\n",
|
|
66
|
-
"cssdoc.json": "{\n \"$schema\": \"https://cssdoc.dev/cssdoc.schema.json\",\n \"extends\": [\n \"./node_modules/@pantoken/pantoken/cssdoc-base.json\"\n ],\n \"modifierConvention\": \"rscss\",\n \"inlineComments\": \"ignore\",\n \"providers\": [\n {\n \"path\": \"./node_modules/@pantoken/pantoken/model.json\"\n }\n ]\n}"
|
|
67
|
-
},
|
|
68
|
-
"react": {
|
|
69
|
-
".gitignore": "node_modules\ndist\n",
|
|
70
|
-
"README.md": "# {{projectName}}\n\nScaffolded with `pantoken-scaffold react` (or `pantoken-ai scaffold react`) — a Vite + React app styled with\n[`@pantoken/react`](https://www.npmjs.com/package/@pantoken/react) and\n[`@pantoken/css`](https://www.npmjs.com/package/@pantoken/css), Instructure's design tokens and\nicons.\n\n## Develop\n\n```sh\nnpm install\nnpm run dev\n```\n\n## What's here\n\n- `src/main.tsx` imports `@pantoken/css/inject`, which defines every `--instui-*` custom property.\n- `src/App.tsx` renders markup following the `wrapper` app-shell layout from\n [`@pantoken/plugin-layouts`](https://www.npmjs.com/package/@pantoken/plugin-layouts)\n (`.container` / `.header` / `.content` parts) styled with `instui-*` classes.\n- For InstUI's full React component library (buttons, modals, etc. — including `@pantoken/react`'s\n `<Icon>`), add the specific `@instructure/ui-*` packages you need — see <https://instructure.design>.\n",
|
|
71
|
-
"index.html": "<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>{{projectName}}</title>\n </head>\n <body>\n <div id=\"root\"></div>\n <script type=\"module\" src=\"/src/main.tsx\"><\/script>\n </body>\n</html>\n",
|
|
72
|
-
"package.json": "{\n \"name\": \"{{projectName}}\",\n \"version\": \"0.0.0\",\n \"private\": true,\n \"type\": \"module\",\n \"scripts\": {\n \"dev\": \"vite\",\n \"build\": \"vite build\",\n \"preview\": \"vite preview\"\n },\n \"dependencies\": {\n \"@pantoken/css\": \"latest\",\n \"@pantoken/plugin-layouts\": \"latest\",\n \"@pantoken/react\": \"latest\",\n \"react\": \"^19.2.8\",\n \"react-dom\": \"^19.2.8\"\n },\n \"devDependencies\": {\n \"@pantoken/pantoken\": \"latest\",\n \"@types/react\": \"^19.2.18\",\n \"@types/react-dom\": \"^19.2.5\",\n \"@vitejs/plugin-react\": \"^6.1.0\",\n \"typescript\": \"^7.0.2\",\n \"vite\": \"^8.2.2\"\n },\n \"allowScripts\": {\n \"core-js\": true,\n \"ttf2woff2\": true\n },\n \"trustedDependencies\": [\n \"core-js\",\n \"ttf2woff2\"\n ]\n}\n",
|
|
73
|
-
"pnpm-workspace.yaml": "# core-js/ttf2woff2 arrive transitively via @pantoken/pantoken; pre-approve their install scripts\n# so `pnpm install` doesn't fail/warn under pnpm's default strictDepBuilds gate.\nallowBuilds:\n core-js: true\n ttf2woff2: true\n",
|
|
74
|
-
"tsconfig.json": "{\n \"compilerOptions\": {\n \"target\": \"ES2022\",\n \"lib\": [\"ES2022\", \"DOM\", \"DOM.Iterable\"],\n \"module\": \"ESNext\",\n \"moduleResolution\": \"Bundler\",\n \"jsx\": \"react-jsx\",\n \"strict\": true,\n \"skipLibCheck\": true\n },\n \"include\": [\"src\"]\n}\n",
|
|
75
|
-
"vite.config.ts": "import { defineConfig } from \"vite\";\nimport react from \"@vitejs/plugin-react\";\n\nexport default defineConfig({\n plugins: [react()],\n});\n",
|
|
76
|
-
".vscode/extensions.json": "{\n \"recommendations\": [\"cssdoc.cssdoc-vscode\"]\n}\n",
|
|
77
|
-
".vscode/settings.json": "{\n \"html.customData\": [\"./node_modules/@pantoken/pantoken/dist/html-custom-data.json\"],\n \"css.customData\": [\"./node_modules/@pantoken/pantoken/dist/css-custom-data.json\"]\n}\n",
|
|
78
|
-
"src/App.tsx": "import \"@pantoken/plugin-layouts/layouts.css\";\n\nexport function App() {\n return (\n <div className=\"instui-display-flex\">\n<div className=\"container\">\n <div className=\"header\">\n <div className=\"top\">\n {/* breadcrumb */}\n <div className=\"utilities\"> {/* optional */}\n <button className=\"instui-button\"></button>\n </div>\n </div>\n <div className=\"info\">\n <div className=\"title\">\n {{projectName}}\n </div>\n <div className=\"description\"> {/* optional */}\n Styled with <code>@pantoken/components</code> and laid out with the <code>wrapper</code> layout from <code>@pantoken/plugin-layouts</code>.\n </div>\n </div>\n <slot name=\"header\"></slot> {/* optional */}\n <div className=\"actions\"> {/* optional */}\n <button className=\"instui-button\"></button>\n </div>\n <div className=\"tabs\"> {/* optional */}\n {/* tabs */}\n </div>\n <div className=\"filters\"> {/* optional */}\n <slot name=\"filters\"></slot>\n </div>\n </div>\n <div className=\"content\">\n <div className=\"list\"></div> {/* optional */}\n <slot name=\"content\">\n <p>Get started building with pantoken.</p>\n <button className=\"instui-button -color-primary\" type=\"button\">Get started</button>\n </slot>\n <slot name=\"trailing\"></slot> {/* optional */}\n </div>\n </div>\n </div>\n );\n}\n",
|
|
79
|
-
"src/main.tsx": "import { StrictMode } from \"react\";\nimport { createRoot } from \"react-dom/client\";\n// Defines every --instui-* custom property this app can reference.\n{{pantokenCssImport}}\nimport { App } from \"./App.tsx\";\n\ncreateRoot(document.getElementById(\"root\")!).render(\n <StrictMode>\n <App />\n </StrictMode>,\n);\n",
|
|
80
|
-
"cssdoc.json": "{\n \"$schema\": \"https://cssdoc.dev/cssdoc.schema.json\",\n \"extends\": [\n \"./node_modules/@pantoken/pantoken/cssdoc-base.json\"\n ],\n \"modifierConvention\": \"rscss\",\n \"inlineComments\": \"ignore\",\n \"providers\": [\n {\n \"path\": \"./node_modules/@pantoken/pantoken/model.json\"\n }\n ]\n}"
|
|
81
|
-
},
|
|
82
|
-
"svelte": {
|
|
83
|
-
".gitignore": "node_modules\ndist\n",
|
|
84
|
-
"README.md": "# {{projectName}}\n\nScaffolded with `pantoken-scaffold svelte` (or `pantoken-ai scaffold svelte`) — a Vite + Svelte app\nstyled with [`@pantoken/components`](https://www.npmjs.com/package/@pantoken/components) and\n[`@pantoken/svelte`](https://www.npmjs.com/package/@pantoken/svelte) (icon action + token reads).\n\n## Develop\n\n```sh\nnpm install\nnpm run dev\n```\n\n## What's here\n\n- `src/main.ts` calls `register()` (from `@pantoken/svelte`) once at bootstrap so elements like\n `<instui-icon>` work in templates, and imports the token/component CSS plus\n `@pantoken/plugin-layouts/layouts.css`.\n- `src/App.svelte`'s markup follows the `wrapper` app-shell layout from\n `@pantoken/plugin-layouts` (`.container` / `.header` / `.content` parts).\n- Apply classes like `class=\"instui-button\"` to markup; see the\n [component reference](https://pantoken.app/api/css) for the full class list.\n",
|
|
85
|
-
"index.html": "<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>{{projectName}}</title>\n </head>\n <body>\n <div id=\"app\"></div>\n <script type=\"module\" src=\"/src/main.ts\"><\/script>\n </body>\n</html>\n",
|
|
86
|
-
"package.json": "{\n \"name\": \"{{projectName}}\",\n \"version\": \"0.0.0\",\n \"private\": true,\n \"type\": \"module\",\n \"scripts\": {\n \"dev\": \"vite\",\n \"build\": \"vite build\",\n \"preview\": \"vite preview\"\n },\n \"dependencies\": {\n \"@pantoken/css\": \"latest\",\n \"@pantoken/plugin-layouts\": \"latest\",\n \"@pantoken/svelte\": \"latest\"\n },\n \"devDependencies\": {\n \"@pantoken/pantoken\": \"latest\",\n \"@sveltejs/vite-plugin-svelte\": \"^7.3.0\",\n \"svelte\": \"^5.56.10\",\n \"typescript\": \"^7.0.2\",\n \"vite\": \"^8.2.2\"\n },\n \"allowScripts\": {\n \"core-js\": true,\n \"ttf2woff2\": true\n },\n \"trustedDependencies\": [\n \"core-js\",\n \"ttf2woff2\"\n ]\n}\n",
|
|
87
|
-
"pnpm-workspace.yaml": "# core-js/ttf2woff2 arrive transitively via @pantoken/pantoken; pre-approve their install scripts\n# so `pnpm install` doesn't fail/warn under pnpm's default strictDepBuilds gate.\nallowBuilds:\n core-js: true\n ttf2woff2: true\n",
|
|
88
|
-
"tsconfig.json": "{\n \"compilerOptions\": {\n \"target\": \"ES2022\",\n \"lib\": [\"ES2022\", \"DOM\", \"DOM.Iterable\"],\n \"module\": \"ESNext\",\n \"moduleResolution\": \"Bundler\",\n \"strict\": true,\n \"skipLibCheck\": true\n },\n \"include\": [\"src\"]\n}\n",
|
|
89
|
-
"vite.config.ts": "import { defineConfig } from \"vite\";\nimport { svelte } from \"@sveltejs/vite-plugin-svelte\";\n\nexport default defineConfig({\n plugins: [svelte()],\n});\n",
|
|
90
|
-
".vscode/extensions.json": "{\n \"recommendations\": [\"cssdoc.cssdoc-vscode\", \"svelte.svelte-vscode\"]\n}\n",
|
|
91
|
-
".vscode/settings.json": "{\n \"html.customData\": [\"./node_modules/@pantoken/pantoken/dist/html-custom-data.json\"],\n \"css.customData\": [\"./node_modules/@pantoken/pantoken/dist/css-custom-data.json\"]\n}\n",
|
|
92
|
-
"src/App.svelte": "<div class=\"instui-display-flex\">\n<div class=\"container\">\n <div class=\"header\">\n <div class=\"top\">\n <!-- breadcrumb -->\n <div class=\"utilities\"> <!-- optional -->\n <button class=\"instui-button\"></button>\n </div>\n </div>\n <div class=\"info\">\n <div class=\"title\">\n {{projectName}}\n </div>\n <div class=\"description\"> <!-- optional -->\n Styled with <code>@pantoken/components</code> and laid out with the <code>wrapper</code> layout from <code>@pantoken/plugin-layouts</code>.\n </div>\n </div>\n <slot name=\"header\"></slot> <!-- optional -->\n <div class=\"actions\"> <!-- optional -->\n <button class=\"instui-button\"></button>\n </div>\n <div class=\"tabs\"> <!-- optional -->\n <!-- tabs -->\n </div>\n <div class=\"filters\"> <!-- optional -->\n <slot name=\"filters\"></slot>\n </div>\n </div>\n <div class=\"content\">\n <div class=\"list\"></div> <!-- optional -->\n <slot name=\"content\">\n <p>Get started building with pantoken.</p>\n <button class=\"instui-button -color-primary\" type=\"button\">Get started</button>\n </slot>\n <slot name=\"trailing\"></slot> <!-- optional -->\n </div>\n </div>\n</div>\n",
|
|
93
|
-
"src/main.ts": "import { mount } from \"svelte\";\nimport { register } from \"@pantoken/svelte\";\n// Defines every --instui-* custom property this app can reference.\n{{pantokenCssImport}}\nimport \"@pantoken/plugin-layouts/layouts.css\";\nimport App from \"./App.svelte\";\n\nregister(); // auto-registers <instui-icon> and friends\n\nmount(App, { target: document.getElementById(\"app\")! });\n",
|
|
94
|
-
"cssdoc.json": "{\n \"$schema\": \"https://cssdoc.dev/cssdoc.schema.json\",\n \"extends\": [\n \"./node_modules/@pantoken/pantoken/cssdoc-base.json\"\n ],\n \"modifierConvention\": \"rscss\",\n \"inlineComments\": \"ignore\",\n \"providers\": [\n {\n \"path\": \"./node_modules/@pantoken/pantoken/model.json\"\n }\n ]\n}"
|
|
95
|
-
},
|
|
96
|
-
"vue": {
|
|
97
|
-
".gitignore": "node_modules\ndist\n",
|
|
98
|
-
"README.md": "# {{projectName}}\n\nScaffolded with `pantoken-scaffold vue` (or `pantoken-ai scaffold vue`) — a Vite + Vue 3 app styled\nwith [`@pantoken/components`](https://www.npmjs.com/package/@pantoken/components) and\n[`@pantoken/vue`](https://www.npmjs.com/package/@pantoken/vue) (the pantoken Vue plugin).\n\n## Develop\n\n```sh\nnpm install\nnpm run dev\n```\n\n## What's here\n\n- `src/main.ts` calls `app.use(PantokenVue)` (from `@pantoken/vue`) so elements like\n `<instui-icon>` work in templates, and imports the token/component CSS plus\n `@pantoken/plugin-layouts/layouts.css`.\n- `src/App.vue`'s template follows the `wrapper` app-shell layout from\n `@pantoken/plugin-layouts` (`.container` / `.header` / `.content` parts).\n- Apply classes like `class=\"instui-button\"` to markup; see the\n [component reference](https://pantoken.app/api/css) for the full class list.\n",
|
|
99
|
-
"index.html": "<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>{{projectName}}</title>\n </head>\n <body>\n <div id=\"app\"></div>\n <script type=\"module\" src=\"/src/main.ts\"><\/script>\n </body>\n</html>\n",
|
|
100
|
-
"package.json": "{\n \"name\": \"{{projectName}}\",\n \"version\": \"0.0.0\",\n \"private\": true,\n \"type\": \"module\",\n \"scripts\": {\n \"dev\": \"vite\",\n \"build\": \"vite build\",\n \"preview\": \"vite preview\"\n },\n \"dependencies\": {\n \"@pantoken/css\": \"latest\",\n \"@pantoken/plugin-layouts\": \"latest\",\n \"@pantoken/vue\": \"latest\",\n \"vue\": \"^3.5.41\"\n },\n \"devDependencies\": {\n \"@pantoken/pantoken\": \"latest\",\n \"@vitejs/plugin-vue\": \"^6.0.8\",\n \"typescript\": \"^7.0.2\",\n \"vite\": \"^8.2.2\",\n \"vue-tsc\": \"^3.3.11\"\n },\n \"allowScripts\": {\n \"core-js\": true,\n \"ttf2woff2\": true\n },\n \"trustedDependencies\": [\n \"core-js\",\n \"ttf2woff2\"\n ]\n}\n",
|
|
101
|
-
"pnpm-workspace.yaml": "# core-js/ttf2woff2 arrive transitively via @pantoken/pantoken; pre-approve their install scripts\n# so `pnpm install` doesn't fail/warn under pnpm's default strictDepBuilds gate.\nallowBuilds:\n core-js: true\n ttf2woff2: true\n",
|
|
102
|
-
"tsconfig.json": "{\n \"compilerOptions\": {\n \"target\": \"ES2022\",\n \"lib\": [\"ES2022\", \"DOM\", \"DOM.Iterable\"],\n \"module\": \"ESNext\",\n \"moduleResolution\": \"Bundler\",\n \"strict\": true,\n \"skipLibCheck\": true\n },\n \"include\": [\"src\"]\n}\n",
|
|
103
|
-
"vite.config.ts": "import { defineConfig } from \"vite\";\nimport vue from \"@vitejs/plugin-vue\";\n\nexport default defineConfig({\n plugins: [vue()],\n});\n",
|
|
104
|
-
".vscode/extensions.json": "{\n \"recommendations\": [\"cssdoc.cssdoc-vscode\", \"Vue.volar\"]\n}\n",
|
|
105
|
-
".vscode/settings.json": "{\n \"html.customData\": [\"./node_modules/@pantoken/pantoken/dist/html-custom-data.json\"],\n \"css.customData\": [\"./node_modules/@pantoken/pantoken/dist/css-custom-data.json\"]\n}\n",
|
|
106
|
-
"src/App.vue": "<script setup lang=\"ts\"><\/script>\n\n<template>\n <div class=\"instui-display-flex\">\n<div class=\"container\">\n <div class=\"header\">\n <div class=\"top\">\n <!-- breadcrumb -->\n <div class=\"utilities\"> <!-- optional -->\n <button class=\"instui-button\"></button>\n </div>\n </div>\n <div class=\"info\">\n <div class=\"title\">\n {{projectName}}\n </div>\n <div class=\"description\"> <!-- optional -->\n Styled with <code>@pantoken/components</code> and laid out with the <code>wrapper</code> layout from <code>@pantoken/plugin-layouts</code>.\n </div>\n </div>\n <slot name=\"header\"></slot> <!-- optional -->\n <div class=\"actions\"> <!-- optional -->\n <button class=\"instui-button\"></button>\n </div>\n <div class=\"tabs\"> <!-- optional -->\n <!-- tabs -->\n </div>\n <div class=\"filters\"> <!-- optional -->\n <slot name=\"filters\"></slot>\n </div>\n </div>\n <div class=\"content\">\n <div class=\"list\"></div> <!-- optional -->\n <slot name=\"content\">\n <p>Get started building with pantoken.</p>\n <button class=\"instui-button -color-primary\" type=\"button\">Get started</button>\n </slot>\n <slot name=\"trailing\"></slot> <!-- optional -->\n </div>\n </div>\n </div>\n</template>\n",
|
|
107
|
-
"src/main.ts": "import { createApp } from \"vue\";\nimport { PantokenVue } from \"@pantoken/vue\";\n// Defines every --instui-* custom property this app can reference.\n{{pantokenCssImport}}\nimport \"@pantoken/plugin-layouts/layouts.css\";\nimport App from \"./App.vue\";\n\ncreateApp(App).use(PantokenVue).mount(\"#app\");\n",
|
|
108
|
-
"cssdoc.json": "{\n \"$schema\": \"https://cssdoc.dev/cssdoc.schema.json\",\n \"extends\": [\n \"./node_modules/@pantoken/pantoken/cssdoc-base.json\"\n ],\n \"modifierConvention\": \"rscss\",\n \"inlineComments\": \"ignore\",\n \"providers\": [\n {\n \"path\": \"./node_modules/@pantoken/pantoken/model.json\"\n }\n ]\n}"
|
|
109
|
-
},
|
|
110
|
-
"web-components": {
|
|
111
|
-
".gitignore": "node_modules\ndist\n",
|
|
112
|
-
"README.md": "# {{projectName}}\n\nScaffolded with `pantoken-scaffold web-components` (or `pantoken-ai scaffold web-components`) — a\nplain TypeScript app (via Vite) styled with\n[`@pantoken/components`](https://www.npmjs.com/package/@pantoken/components) and using framework-agnostic\ncustom elements from [`@pantoken/web-components`](https://www.npmjs.com/package/@pantoken/web-components).\n\n## Develop\n\n```sh\nnpm install\nnpm run dev\n```\n\n## What's here\n\n- `src/main.ts` imports `@pantoken/components/base.css` + `components.css` (`.instui-*` classes),\n `@pantoken/plugin-layouts/layouts.css`, and `@pantoken/web-components` (auto-registers elements\n like `<instui-icon>`), then renders markup into `#app`.\n- The markup follows the `wrapper` app-shell layout from `@pantoken/plugin-layouts` (`.container` /\n `.header` / `.content` parts) — see that package's README for what each part is for.\n- Use custom elements directly in markup, e.g. `<instui-button>`, `<instui-alert variant=\"success\">` —\n see the [`@pantoken/web-components` README](https://www.npmjs.com/package/@pantoken/web-components)\n for the full element list.\n",
|
|
113
|
-
"index.html": "<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>{{projectName}}</title>\n </head>\n <body class=\"instui-display-flex\">\n <div id=\"app\"></div>\n <script type=\"module\" src=\"/src/main.ts\"><\/script>\n </body>\n</html>\n",
|
|
114
|
-
"package.json": "{\n \"name\": \"{{projectName}}\",\n \"version\": \"0.0.0\",\n \"private\": true,\n \"type\": \"module\",\n \"scripts\": {\n \"dev\": \"vite\",\n \"build\": \"vite build\",\n \"preview\": \"vite preview\"\n },\n \"dependencies\": {\n \"@pantoken/components\": \"latest\",\n \"@pantoken/plugin-layouts\": \"latest\",\n \"@pantoken/web-components\": \"latest\"\n },\n \"devDependencies\": {\n \"@pantoken/pantoken\": \"latest\",\n \"typescript\": \"^7.0.2\",\n \"vite\": \"^8.2.2\"\n },\n \"allowScripts\": {\n \"core-js\": true,\n \"ttf2woff2\": true\n },\n \"trustedDependencies\": [\n \"core-js\",\n \"ttf2woff2\"\n ]\n}\n",
|
|
115
|
-
"pnpm-workspace.yaml": "# core-js/ttf2woff2 arrive transitively via @pantoken/pantoken; pre-approve their install scripts\n# so `pnpm install` doesn't fail/warn under pnpm's default strictDepBuilds gate.\nallowBuilds:\n core-js: true\n ttf2woff2: true\n",
|
|
116
|
-
"tsconfig.json": "{\n \"compilerOptions\": {\n \"target\": \"ES2022\",\n \"lib\": [\"ES2022\", \"DOM\", \"DOM.Iterable\"],\n \"module\": \"ESNext\",\n \"moduleResolution\": \"Bundler\",\n \"strict\": true,\n \"skipLibCheck\": true\n },\n \"include\": [\"src\"]\n}\n",
|
|
117
|
-
"vite.config.ts": "import { defineConfig } from \"vite\";\n\nexport default defineConfig({});\n",
|
|
118
|
-
".vscode/extensions.json": "{\n \"recommendations\": [\"cssdoc.cssdoc-vscode\"]\n}\n",
|
|
119
|
-
".vscode/settings.json": "{\n \"html.customData\": [\"./node_modules/@pantoken/pantoken/dist/html-custom-data.json\"],\n \"css.customData\": [\"./node_modules/@pantoken/pantoken/dist/css-custom-data.json\"]\n}\n",
|
|
120
|
-
"src/main.ts": "// Defines every --instui-* custom property this app can reference.\nimport \"@pantoken/components/base.css\";\nimport \"@pantoken/components/components.css\";\nimport \"@pantoken/plugin-layouts/layouts.css\";\nimport \"@pantoken/web-components\"; // auto-registers <instui-icon> and friends\n\ndocument.querySelector<HTMLDivElement>(\"#app\")!.innerHTML = `\n<div class=\"container\">\n <div class=\"header\">\n <div class=\"top\">\n <!-- breadcrumb -->\n <div class=\"utilities\"> <!-- optional -->\n <button class=\"instui-button\"></button>\n </div>\n </div>\n <div class=\"info\">\n <div class=\"title\">\n {{projectName}}\n </div>\n <div class=\"description\"> <!-- optional -->\n Styled with <code>@pantoken/components</code> and laid out with the <code>wrapper</code> layout from <code>@pantoken/plugin-layouts</code>.\n </div>\n </div>\n <slot name=\"header\"></slot> <!-- optional -->\n <div class=\"actions\"> <!-- optional -->\n <button class=\"instui-button\"></button>\n </div>\n <div class=\"tabs\"> <!-- optional -->\n <!-- tabs -->\n </div>\n <div class=\"filters\"> <!-- optional -->\n <slot name=\"filters\"></slot>\n </div>\n </div>\n <div class=\"content\">\n <div class=\"list\"></div> <!-- optional -->\n <slot name=\"content\">\n <p>Get started building with pantoken.</p>\n <button class=\"instui-button -color-primary\" type=\"button\">Get started</button>\n </slot>\n <slot name=\"trailing\"></slot> <!-- optional -->\n </div>\n </div>\n`;\n\n// Framework-agnostic custom elements work anywhere — no per-framework port required.\ndocument\n .querySelector('.content slot[name=\"content\"]')\n ?.insertAdjacentHTML(\"afterbegin\", '<instui-icon name=\"check-mark\"></instui-icon>\\n');\n",
|
|
121
|
-
"cssdoc.json": "{\n \"$schema\": \"https://cssdoc.dev/cssdoc.schema.json\",\n \"extends\": [\n \"./node_modules/@pantoken/pantoken/cssdoc-base.json\"\n ],\n \"modifierConvention\": \"rscss\",\n \"inlineComments\": \"ignore\",\n \"providers\": [\n {\n \"path\": \"./node_modules/@pantoken/pantoken/model.json\"\n }\n ]\n}"
|
|
122
|
-
}
|
|
123
|
-
};
|
|
124
|
-
//#endregion
|
|
125
|
-
//#region generated/preset-ledger.ts
|
|
126
|
-
/**
|
|
127
|
-
* Registry of all available pantoken scaffold presets, keyed by platform name.
|
|
128
|
-
*/
|
|
129
|
-
const PRESET_LEDGER = {
|
|
130
|
-
"components": presetComponents,
|
|
131
|
-
"react": presetReact,
|
|
132
|
-
"vue": presetVue,
|
|
133
|
-
"web-components": presetWebComponents,
|
|
134
|
-
"angular": presetAngular,
|
|
135
|
-
"svelte": presetSvelte
|
|
136
|
-
};
|
|
137
|
-
//#endregion
|
|
138
|
-
//#region src/theme.ts
|
|
139
|
-
/**
|
|
140
|
-
* Token theme selection for scaffolded projects — a global `--theme`/`--theme-mode` flag applied
|
|
141
|
-
* across every platform, not just `canvas-theme-editor`. Mirrors
|
|
142
|
-
* `@pantoken/canvas-theme-editor`'s `src/build-theme.ts` token-sheet mapping (kept independent:
|
|
143
|
-
* this resolves an npm subpath import for a scaffolded app, not a CDN URL).
|
|
144
|
-
*
|
|
145
|
-
* @module
|
|
146
|
-
* @alpha
|
|
147
|
-
*/
|
|
148
|
-
const THEME_VARIANTS = [
|
|
149
|
-
"rebrand",
|
|
150
|
-
"canvas",
|
|
151
|
-
"canvasHighContrast"
|
|
152
|
-
];
|
|
153
|
-
const THEME_MODES = ["adaptive", "light"];
|
|
154
|
-
/** The `@pantoken/css` lean stylesheet subpath import for a theme/mode pair. */
|
|
155
|
-
function themeStylesheetImport(theme = "rebrand", mode = "light") {
|
|
156
|
-
return `import "@pantoken/css/${theme === "canvas" ? "style.canvas.lean.css" : theme === "canvasHighContrast" ? "style.canvas-high-contrast.lean.css" : mode === "light" ? "style.rebrand.light.lean.css" : "style.lean.css"}";`;
|
|
157
|
-
}
|
|
158
|
-
/**
|
|
159
|
-
* Commander Argument validator for `--theme`.
|
|
160
|
-
*
|
|
161
|
-
* @throws InvalidArgumentError if the value isn't a known theme variant
|
|
162
|
-
*/
|
|
163
|
-
function validateThemeVariant(value) {
|
|
164
|
-
if (THEME_VARIANTS.includes(value)) return value;
|
|
165
|
-
throw new InvalidArgumentError(`Theme "${value}" is not valid. Expected one of: ${THEME_VARIANTS.join(", ")}.`);
|
|
166
|
-
}
|
|
167
|
-
/**
|
|
168
|
-
* Commander Argument validator for `--theme-mode`.
|
|
169
|
-
*
|
|
170
|
-
* @throws InvalidArgumentError if the value isn't a known mode
|
|
171
|
-
*/
|
|
172
|
-
function validateThemeMode(value) {
|
|
173
|
-
if (THEME_MODES.includes(value)) return value;
|
|
174
|
-
throw new InvalidArgumentError(`Theme mode "${value}" is not valid. Expected one of: ${THEME_MODES.join(", ")}.`);
|
|
175
|
-
}
|
|
176
|
-
//#endregion
|
|
177
|
-
//#region src/index.ts
|
|
178
|
-
/**
|
|
179
|
-
* `@pantoken/scaffold` — scaffold a starter project for a platform, with pantoken already
|
|
180
|
-
* installed and wired in. Standalone; usable via `npx @pantoken/scaffold <platform>` without
|
|
181
|
-
* `@pantoken/ai`.
|
|
182
|
-
*
|
|
183
|
-
* Powered by Bingo presets: each platform exports a preset that defines its scaffold structure.
|
|
184
|
-
*
|
|
185
|
-
* @module
|
|
186
|
-
* @alpha
|
|
187
|
-
*/
|
|
188
|
-
/**
|
|
189
|
-
* Every scaffoldable platform key (discovered from available presets, plus any legacy
|
|
190
|
-
* template-only platforms not yet backed by a preset).
|
|
191
|
-
*
|
|
192
|
-
* @example List available platforms
|
|
193
|
-
* ```ts
|
|
194
|
-
* import { SCAFFOLD_PLATFORMS } from "@pantoken/scaffold";
|
|
195
|
-
*
|
|
196
|
-
* console.log(SCAFFOLD_PLATFORMS); // → ["components", "react", "vue", "web-components"]
|
|
197
|
-
* ```
|
|
198
|
-
*/
|
|
199
|
-
const SCAFFOLD_PLATFORMS = Array.from(/* @__PURE__ */ new Set([...Object.keys(PRESET_LEDGER), ...Object.keys(SCAFFOLDS)]));
|
|
200
|
-
const SCAFFOLD_PLATFORM_SET = new Set(SCAFFOLD_PLATFORMS);
|
|
201
|
-
/** Alternate platform names accepted alongside the canonical preset key (e.g. the pre-Bingo `html`). */
|
|
202
|
-
const PLATFORM_ALIASES = {
|
|
203
|
-
html: "components",
|
|
204
|
-
"theme-editor": "canvas-theme-editor"
|
|
205
|
-
};
|
|
206
|
-
/** Whether `platform` is a known {@link ScaffoldPlatform} or one of its aliases (e.g. `html`). */
|
|
207
|
-
function isScaffoldPlatform(platform) {
|
|
208
|
-
return SCAFFOLD_PLATFORM_SET.has(platform) || platform in PLATFORM_ALIASES;
|
|
209
|
-
}
|
|
210
|
-
/**
|
|
211
|
-
* Resolves `platform` to its canonical {@link ScaffoldPlatform} key, following aliases (e.g.
|
|
212
|
-
* `"theme-editor"` → `"canvas-theme-editor"`). Exported so callers (e.g. the CLI's next-steps
|
|
213
|
-
* printer) can key off the same canonical platform the scaffolder itself uses.
|
|
214
|
-
*
|
|
215
|
-
* @throws Error when `platform` isn't a known platform or alias
|
|
216
|
-
*/
|
|
217
|
-
function resolveScaffoldPlatform(platform) {
|
|
218
|
-
const resolved = PLATFORM_ALIASES[platform] ?? platform;
|
|
219
|
-
if (SCAFFOLD_PLATFORM_SET.has(resolved)) return resolved;
|
|
220
|
-
throw new Error(`Unknown platform "${platform}". Expected one of: ${SCAFFOLD_PLATFORMS.join(", ")}.`);
|
|
221
|
-
}
|
|
222
|
-
/** Writes `content` to `path`, creating parent directories as needed. */
|
|
223
|
-
function writeScaffoldFile(path, content) {
|
|
224
|
-
mkdirSync(dirname(path), { recursive: true });
|
|
225
|
-
writeFileSync(path, content);
|
|
226
|
-
}
|
|
227
|
-
/** Renders `resolvedPlatform`'s Bingo preset (if any) into `dir`; `[]` if there's no preset or it throws. */
|
|
228
|
-
function writePresetFiles(resolvedPlatform, dir, projectName) {
|
|
229
|
-
const preset = PRESET_LEDGER[resolvedPlatform];
|
|
230
|
-
if (!preset) return [];
|
|
231
|
-
try {
|
|
232
|
-
const creation = producePreset(preset, {
|
|
233
|
-
offline: true,
|
|
234
|
-
options: { name: projectName }
|
|
235
|
-
});
|
|
236
|
-
return Object.entries(creation.files ?? {}).map(([file, rawContent]) => {
|
|
237
|
-
const path = join(dir, file);
|
|
238
|
-
writeScaffoldFile(path, rawContent instanceof ArrayBuffer ? Buffer.from(rawContent) : rawContent);
|
|
239
|
-
return path;
|
|
240
|
-
});
|
|
241
|
-
} catch {
|
|
242
|
-
return [];
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
/** Writes `resolvedPlatform`'s legacy `SCAFFOLDS` templates (for platforms with no preset yet). */
|
|
246
|
-
function writeLegacyTemplateFiles(resolvedPlatform, dir, projectName, cssImport) {
|
|
247
|
-
const templates = SCAFFOLDS[resolvedPlatform];
|
|
248
|
-
if (!templates) return [];
|
|
249
|
-
return Object.entries(templates).map(([file, content]) => {
|
|
250
|
-
const substituted = content.replaceAll("{{projectName}}", projectName).replaceAll("{{pantokenCssImport}}", cssImport);
|
|
251
|
-
const path = join(dir, file);
|
|
252
|
-
writeScaffoldFile(path, substituted);
|
|
253
|
-
return path;
|
|
254
|
-
});
|
|
255
|
-
}
|
|
256
|
-
/**
|
|
257
|
-
* Builds canvas-theme-editor's `theme.css`/`theme.js` for the chosen CDN provider/theme/mode at
|
|
258
|
-
* scaffold time (rather than shipping a pre-baked jsDelivr/rebrand default); `[]` for every other
|
|
259
|
-
* platform.
|
|
260
|
-
*/
|
|
261
|
-
function writeCanvasThemeEditorAssets(resolvedPlatform, dir, options) {
|
|
262
|
-
if (resolvedPlatform !== "canvas-theme-editor") return [];
|
|
263
|
-
const { css, js } = buildTheme({
|
|
264
|
-
provider: options?.cdn,
|
|
265
|
-
theme: options?.theme,
|
|
266
|
-
mode: options?.mode
|
|
267
|
-
});
|
|
268
|
-
return ["theme.css", "theme.js"].map((file, i) => {
|
|
269
|
-
const path = join(dir, file);
|
|
270
|
-
writeScaffoldFile(path, i === 0 ? css : js);
|
|
271
|
-
return path;
|
|
272
|
-
});
|
|
273
|
-
}
|
|
274
|
-
/**
|
|
275
|
-
* Scaffold a starter project for a platform, with pantoken already installed and wired in.
|
|
276
|
-
*
|
|
277
|
-
* @param platform - A {@link ScaffoldPlatform}.
|
|
278
|
-
* @param dir - The target directory (default `"."`). Its basename (or `"pantoken-app"` for `"."`)
|
|
279
|
-
* is substituted for `{{projectName}}` in the template files.
|
|
280
|
-
* @param options - `theme`/`mode` select which `@pantoken/css` token sheet scaffolded files
|
|
281
|
-
* import (default `"rebrand"`/`"light"`), applied across every platform. `cdn` selects the CDN
|
|
282
|
-
* provider `canvas-theme-editor`'s `theme.css`/`theme.js` are built for (default jsDelivr);
|
|
283
|
-
* ignored by every other platform.
|
|
284
|
-
* @returns The paths written.
|
|
285
|
-
*
|
|
286
|
-
* @example Scaffold a React starter
|
|
287
|
-
* ```ts
|
|
288
|
-
* import { scaffoldProject } from "@pantoken/scaffold";
|
|
289
|
-
*
|
|
290
|
-
* scaffoldProject("react", "./my-app");
|
|
291
|
-
* scaffoldProject("vue", "./my-vue-app", { theme: "canvas" });
|
|
292
|
-
* ```
|
|
293
|
-
*/
|
|
294
|
-
async function scaffoldProject(platform, dir = ".", options) {
|
|
295
|
-
const resolvedPlatform = resolveScaffoldPlatform(platform);
|
|
296
|
-
const projectName = dir === "." ? "pantoken-app" : dir.split("/").pop() ?? "pantoken-app";
|
|
297
|
-
const cssImport = themeStylesheetImport(options?.theme, options?.mode);
|
|
298
|
-
const written = writePresetFiles(resolvedPlatform, dir, projectName);
|
|
299
|
-
if (written.length === 0) written.push(...writeLegacyTemplateFiles(resolvedPlatform, dir, projectName, cssImport));
|
|
300
|
-
written.push(...writeCanvasThemeEditorAssets(resolvedPlatform, dir, options));
|
|
301
|
-
return written;
|
|
302
|
-
}
|
|
303
|
-
//#endregion
|
|
304
|
-
export { themeStylesheetImport as a, scaffoldProject as i, isScaffoldPlatform as n, validateThemeMode as o, resolveScaffoldPlatform as r, validateThemeVariant as s, SCAFFOLD_PLATFORMS as t };
|