From d790e9d4a21efcbbe8d04c78748e600347eee562 Mon Sep 17 00:00:00 2001 From: Giuseppe Criscione <18699708+giuscris@users.noreply.github.com> Date: Sat, 22 Nov 2025 15:32:52 +0100 Subject: [PATCH 1/6] Add the possibility to pass asset metadata --- formwork/src/Assets/Asset.php | 12 +++++++++++- formwork/src/Assets/Assets.php | 6 ++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/formwork/src/Assets/Asset.php b/formwork/src/Assets/Asset.php index 0ad56d767..8cec561e1 100644 --- a/formwork/src/Assets/Asset.php +++ b/formwork/src/Assets/Asset.php @@ -34,9 +34,11 @@ class Asset private string $mimeType; /** + * @param array $meta Asset metadata + * * @throws AssetNotFoundException If the asset file is not found */ - public function __construct(string $path, string $uri) + public function __construct(string $path, string $uri, private array $meta = []) { $this->path = FileSystem::normalizePath($path); $this->uri = Uri::normalize($uri); @@ -105,4 +107,12 @@ public function toBase64(): string { return 'data:' . $this->mimeType() . ';base64,' . base64_encode($this->content()); } + + /** + * Get asset metadata value by key + */ + public function getMeta(string $key, mixed $default = null): mixed + { + return $this->meta[$key] ?? $default; + } } diff --git a/formwork/src/Assets/Assets.php b/formwork/src/Assets/Assets.php index ca1f37a10..3cc92c688 100644 --- a/formwork/src/Assets/Assets.php +++ b/formwork/src/Assets/Assets.php @@ -33,13 +33,15 @@ public function __construct(string $basePath, string $baseUri) /** * Add an asset to the collection + * + * @param array $meta Asset metadata */ - public function add(string $key): void + public function add(string $key, array $meta = []): void { if (!$this->collection->has($key)) { $path = FileSystem::joinPaths($this->basePath, Path::resolve($key, '/', DIRECTORY_SEPARATOR)); $uri = Path::join([$this->baseUri, Path::resolve($key, '/')]); - $this->collection->set($key, new Asset($path, $uri)); + $this->collection->set($key, new Asset($path, $uri, $meta)); } } From 1c4423608edc27a3bfd1e8e54dd2766a7ae7dec8 Mon Sep 17 00:00:00 2001 From: Giuseppe Criscione <18699708+giuscris@users.noreply.github.com> Date: Sat, 22 Nov 2025 15:33:50 +0100 Subject: [PATCH 2/6] Allow loading script as module with asset meta `module` --- panel/views/partials/scripts.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panel/views/partials/scripts.php b/panel/views/partials/scripts.php index e9cbf9fc3..eedcc80b9 100644 --- a/panel/views/partials/scripts.php +++ b/panel/views/partials/scripts.php @@ -1,5 +1,5 @@ assets()->scripts() as $script): ?> - + - \ No newline at end of file + From bd97c989a2ccbf0eef8e9f552a82579409a77b50 Mon Sep 17 00:00:00 2001 From: Giuseppe Criscione <18699708+giuscris@users.noreply.github.com> Date: Sat, 22 Nov 2025 16:00:56 +0100 Subject: [PATCH 4/6] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- panel/package.json | 2 +- panel/src/ts/components/inputs/editor/markdown/placeholder.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/panel/package.json b/panel/package.json index 3dff97d52..d3bb6bcd0 100644 --- a/panel/package.json +++ b/panel/package.json @@ -19,7 +19,7 @@ "build": "pnpm clean && pnpm build:css && pnpm build:js", "build:css": "sass ./src/scss/panel.scss:./assets/css/panel.min.css --style=compressed --no-source-map", "build:js": "tsc && node build.js", - "clean": "rm -rf ./assets/js/chunks/**.js", + "clean": "rm -rf ./assets/js/chunks/**/*.js", "watch": "pnpm watch:css & pnpm watch:js", "watch:css": "pnpm build:css --watch", "watch:js": "pnpm build:js --watch", diff --git a/panel/src/ts/components/inputs/editor/markdown/placeholder.ts b/panel/src/ts/components/inputs/editor/markdown/placeholder.ts index a9b388fb7..5a8c94ee6 100644 --- a/panel/src/ts/components/inputs/editor/markdown/placeholder.ts +++ b/panel/src/ts/components/inputs/editor/markdown/placeholder.ts @@ -1,5 +1,5 @@ import { Decoration, DecorationSet } from "prosemirror-view"; -import type { EditorState} from "prosemirror-state"; +import type { EditorState } from "prosemirror-state"; import { Plugin } from "prosemirror-state"; export function placeholderPlugin(text: string) { From 50c241c46cef177bc8a5c2ec541c840187cf4bfb Mon Sep 17 00:00:00 2001 From: Giuseppe Criscione <18699708+giuscris@users.noreply.github.com> Date: Sat, 22 Nov 2025 16:02:17 +0100 Subject: [PATCH 5/6] Load panel app only if its script is available from the assets --- formwork/src/Assets/Assets.php | 8 ++++++++ panel/views/partials/scripts.php | 10 ++++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/formwork/src/Assets/Assets.php b/formwork/src/Assets/Assets.php index 3cc92c688..06bbcc86a 100644 --- a/formwork/src/Assets/Assets.php +++ b/formwork/src/Assets/Assets.php @@ -45,6 +45,14 @@ public function add(string $key, array $meta = []): void } } + /** + * Return whether the collection has an asset with the given key + */ + public function has(string $key): bool + { + return $this->collection->has($key); + } + /** * Get an asset from the collection */ diff --git a/panel/views/partials/scripts.php b/panel/views/partials/scripts.php index d56edb2ab..e2e9fbbe7 100644 --- a/panel/views/partials/scripts.php +++ b/panel/views/partials/scripts.php @@ -2,7 +2,9 @@ - +assets()->has('js/app.min.js')): ?> + + From c8803bf96dc68761610c2bae6a379340ce5ad95f Mon Sep 17 00:00:00 2001 From: Giuseppe Criscione <18699708+giuscris@users.noreply.github.com> Date: Sat, 22 Nov 2025 16:26:11 +0100 Subject: [PATCH 6/6] Wait for editorPromise in setters --- .../src/ts/components/inputs/editor-input.ts | 34 +++++++++++++------ 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/panel/src/ts/components/inputs/editor-input.ts b/panel/src/ts/components/inputs/editor-input.ts index 7cb05ae0c..65332b291 100644 --- a/panel/src/ts/components/inputs/editor-input.ts +++ b/panel/src/ts/components/inputs/editor-input.ts @@ -40,7 +40,9 @@ export class EditorInput { private container: HTMLElement | null; - private editor: MarkdownView | CodeView; + private editor: MarkdownView | CodeView | undefined; + + private editorPromise: Promise; constructor(textarea: HTMLTextAreaElement, options: Partial = {}) { this.element = textarea; @@ -99,25 +101,27 @@ export class EditorInput { const codeSwitch = $("[data-command=toggle-markdown]", this.container) as HTMLButtonElement; if (mode === "code") { - this.switchToCode(); + this.editorPromise = this.switchToCode(); codeSwitch.classList.add("is-active"); } else { - this.switchToMarkdown(); + this.editorPromise = this.switchToMarkdown(); codeSwitch.classList.remove("is-active"); } codeSwitch.addEventListener("click", () => { if (codeSwitch.classList.toggle("is-active")) { - this.switchToCode(); + this.editorPromise = this.switchToCode(); window.localStorage.setItem(`formwork.editorMode[${key}]`, "code"); } else { - this.switchToMarkdown(); + this.editorPromise = this.switchToMarkdown(); window.localStorage.setItem(`formwork.editorMode[${key}]`, "markdown"); } - this.editor.view.focus(); + this.editorPromise.then(() => this.editor?.view.focus()); }); - $(`label[for="${textarea.id}"]`)?.addEventListener("click", () => this.editor.view.focus()); + $(`label[for="${textarea.id}"]`)?.addEventListener("click", () => { + this.editorPromise.then(() => this.editor?.view.focus()); + }); } async switchToMarkdown() { @@ -158,22 +162,30 @@ export class EditorInput { } get value(): string { - return this.editor.content; + return this.editor?.content ?? this.element.value; } get disabled(): boolean { - return !this.editor.editable; + return this.editor ? !this.editor.editable : this.element.disabled; } set disabled(value: boolean) { this.element.disabled = value; - this.editor.editable = !value; + this.editorPromise.then(() => { + if (this.editor) { + this.editor.editable = !value; + } + }); const toggleButton = $("[data-command=toggle-markdown]", this.container!) as HTMLButtonElement; toggleButton.disabled = value; } set value(value: string) { - this.editor.content = value; this.element.value = value; + this.editorPromise.then(() => { + if (this.editor) { + this.editor.content = value; + } + }); } }