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..06bbcc86a 100644 --- a/formwork/src/Assets/Assets.php +++ b/formwork/src/Assets/Assets.php @@ -33,16 +33,26 @@ 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)); } } + /** + * 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/build.js b/panel/build.js new file mode 100644 index 000000000..8153d3722 --- /dev/null +++ b/panel/build.js @@ -0,0 +1,24 @@ +import * as esbuild from "esbuild"; +import process from "process"; + +const watch = process.argv.includes("--watch"); + +const options = { + entryPoints: { "app.min": "./src/ts/app.ts" }, + bundle: true, + format: "esm", + target: "es2020", + chunkNames: "chunks/[name]-[hash]", + minify: true, + splitting: true, + outdir: "./assets/js", + logLevel: "info", +}; + +if (watch) { + // New esbuild API for watch mode + const ctx = await esbuild.context(options); + await ctx.watch(); +} else { + await esbuild.build(options); +} diff --git a/panel/eslint.config.js b/panel/eslint.config.js index b9dc4a6e0..38555b4b5 100644 --- a/panel/eslint.config.js +++ b/panel/eslint.config.js @@ -1,17 +1,17 @@ +import { defineConfig } from "eslint/config"; import eslintConfigPrettier from "eslint-config-prettier"; import globals from "globals"; import js from "@eslint/js"; import tseslint from "typescript-eslint"; -export default [ +export default defineConfig([ js.configs.recommended, ...tseslint.configs.recommended, { languageOptions: { - ecmaVersion: 13, + ecmaVersion: 2020, globals: { ...globals.browser, - Formwork: "readonly", }, sourceType: "module", }, @@ -36,6 +36,7 @@ export default [ "prefer-arrow-callback": ["error"], "prefer-const": ["error"], "prefer-template": ["error"], + "require-await": ["error"], "sort-imports": [ "warn", { @@ -43,7 +44,12 @@ export default [ allowSeparatedGroups: true, }, ], + "@typescript-eslint/consistent-type-exports": ["error"], + "@typescript-eslint/consistent-type-imports": ["error"], "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-redundant-type-constituents": "error", + "@typescript-eslint/no-unnecessary-type-assertion": "error", + "@typescript-eslint/no-unnecessary-type-constraint": "error", "@typescript-eslint/typedef": [ "warn", { @@ -52,5 +58,14 @@ export default [ ], }, }, + { + files: ["**/*.ts"], + languageOptions: { + parserOptions: { + projectService: true, + tsconfigRootDir: import.meta.dirname, + }, + }, + }, eslintConfigPrettier, -]; +]); diff --git a/panel/package.json b/panel/package.json index ad8c3b57e..d3bb6bcd0 100644 --- a/panel/package.json +++ b/panel/package.json @@ -16,9 +16,10 @@ "directory": "panel" }, "scripts": { - "build": "pnpm build:css && pnpm build:js", + "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 && esbuild ./src/ts/app.ts --outfile=./assets/js/app.min.js --bundle --format=iife --global-name=Formwork --target=es6 --minify", + "build:js": "tsc && node build.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/dropdowns.ts b/panel/src/ts/components/dropdowns.ts index b7da4d18f..2ecf4564e 100644 --- a/panel/src/ts/components/dropdowns.ts +++ b/panel/src/ts/components/dropdowns.ts @@ -12,7 +12,7 @@ export class Dropdowns { if (button) { const dropdown = document.getElementById(button.dataset.dropdown as string) as HTMLElement; - const isVisible = getComputedStyle(dropdown as HTMLElement).display !== "none"; + const isVisible = getComputedStyle(dropdown).display !== "none"; event.preventDefault(); const resizeHandler = throttle(() => setDropdownPosition(dropdown), 100); diff --git a/panel/src/ts/components/fileslist.ts b/panel/src/ts/components/fileslist.ts index 05aaa25b7..ea38589d7 100644 --- a/panel/src/ts/components/fileslist.ts +++ b/panel/src/ts/components/fileslist.ts @@ -2,7 +2,7 @@ import { $, $$ } from "../utils/selectors"; import { escapeHtml, escapeRegExp, makeDiacriticsRegExp } from "../utils/validation"; import { app } from "../app"; import { debounce } from "../utils/events"; -import { Form } from "./form"; +import type { Form } from "./form"; import { Notification } from "./notification"; import { Request } from "../utils/request"; import { SelectInput } from "./inputs/select-input"; diff --git a/panel/src/ts/components/inputs/array-input.ts b/panel/src/ts/components/inputs/array-input.ts index 972ffa576..5f12b250c 100644 --- a/panel/src/ts/components/inputs/array-input.ts +++ b/panel/src/ts/components/inputs/array-input.ts @@ -1,6 +1,5 @@ import { $, $$ } from "../../utils/selectors"; -import { Form, HTMLInputLike } from "../form"; -import Sortable from "sortablejs"; +import type { Form, HTMLInputLike } from "../form"; export class ArrayInput { readonly element: HTMLFieldSetElement; @@ -16,6 +15,10 @@ export class ArrayInput { this.isAssociative = element.classList.contains("form-input-array-associative"); + this.init(element); + } + + private async init(element: HTMLFieldSetElement) { $$(".form-input-array-row", element).forEach((element) => this.bindItemEvents(element)); $(`label[for="${element.id}"]`)?.addEventListener("click", () => $(".form-input", element)?.focus()); @@ -24,6 +27,8 @@ export class ArrayInput { this.form.element.addEventListener("submit", () => this.handleSubmit()); } + const { default: Sortable } = await import("sortablejs"); + Sortable.create(element, { handle: ".sortable-handle", forceFallback: true, @@ -34,7 +39,7 @@ export class ArrayInput { } get name(): string { - return this.element.name as string; + return this.element.name; } set name(value: string) { diff --git a/panel/src/ts/components/inputs/duration-input.ts b/panel/src/ts/components/inputs/duration-input.ts index 16fe277d2..b98868e5e 100644 --- a/panel/src/ts/components/inputs/duration-input.ts +++ b/panel/src/ts/components/inputs/duration-input.ts @@ -77,7 +77,7 @@ export class DurationInput { Object.keys(TIME_INTERVALS).forEach((t: TimeInterval) => { if (intervalNames.includes(t)) { intervals[t] = Math.floor(seconds / TIME_INTERVALS[t]); - seconds -= (intervals[t] as number) * TIME_INTERVALS[t]; + seconds -= (intervals[t]) * TIME_INTERVALS[t]; } }); return intervals; diff --git a/panel/src/ts/components/inputs/editor-input.ts b/panel/src/ts/components/inputs/editor-input.ts index aebb28aff..65332b291 100644 --- a/panel/src/ts/components/inputs/editor-input.ts +++ b/panel/src/ts/components/inputs/editor-input.ts @@ -1,10 +1,10 @@ import { $ } from "../../utils/selectors"; import { app } from "../../app"; -import { CodeView } from "./editor/code/view"; +import { type CodeView } from "./editor/code/view"; import { debounce } from "../../utils/events"; import { escapeRegExp } from "../../utils/validation"; import { insertIcon } from "../icons"; -import { MarkdownView } from "./editor/markdown/view"; +import { type MarkdownView } from "./editor/markdown/view"; function addBaseUri(markdown: string, baseUri: string) { return markdown.replace(/(!\[.*\])\((?!https?:\/\/)([^)]+)\)/g, `$1(${baseUri}$2)`); @@ -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,32 +101,35 @@ 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()); + }); } - switchToMarkdown() { + async switchToMarkdown() { if (!this.container) { return; } this.editor?.destroy(); + const { MarkdownView } = await import("./editor/markdown/view"); this.editor = new MarkdownView(this.name, this.container, addBaseUri(this.element.value, this.options.baseUri), this.options.inputEventHandler, { editable: !(this.element.disabled || this.element.readOnly), placeholder: this.element.placeholder, @@ -135,11 +140,12 @@ export class EditorInput { this.editor.view.dom.style.height = `${this.options.height}px`; } - switchToCode() { + async switchToCode() { if (!this.container) { return; } this.editor?.destroy(); + const { CodeView } = await import("./editor/code/view"); this.editor = new CodeView(this.container, removeBaseUri(this.element.value, this.options.baseUri), this.options.inputEventHandler, { editable: !(this.element.disabled || this.element.readOnly), placeholder: this.element.placeholder, @@ -156,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; + } + }); } } diff --git a/panel/src/ts/components/inputs/editor/code/menu.ts b/panel/src/ts/components/inputs/editor/code/menu.ts index c644261dd..7e0e64bed 100644 --- a/panel/src/ts/components/inputs/editor/code/menu.ts +++ b/panel/src/ts/components/inputs/editor/code/menu.ts @@ -1,7 +1,8 @@ -import { EditorView, ViewPlugin, ViewUpdate } from "@codemirror/view"; +import type { EditorView, ViewUpdate } from "@codemirror/view"; import { redo, redoDepth, undo, undoDepth } from "@codemirror/commands"; import { app } from "../../../../app"; import { passIcon } from "../../../icons"; +import { ViewPlugin } from "@codemirror/view"; function createButton(icon: string, title: string) { const btn = document.createElement("button"); diff --git a/panel/src/ts/components/inputs/editor/markdown/commands.ts b/panel/src/ts/components/inputs/editor/markdown/commands.ts index a693a7b4d..2a1a659c9 100644 --- a/panel/src/ts/components/inputs/editor/markdown/commands.ts +++ b/panel/src/ts/components/inputs/editor/markdown/commands.ts @@ -1,6 +1,6 @@ -import { Command, EditorState } from "prosemirror-state"; -import { Mark, MarkType, Node, NodeType } from "prosemirror-model"; -import { EditorView } from "prosemirror-view"; +import type { Command, EditorState } from "prosemirror-state"; +import type { Mark, MarkType, Node, NodeType } from "prosemirror-model"; +import type { EditorView } from "prosemirror-view"; export { lift, setBlockType, toggleMark, wrapIn } from "prosemirror-commands"; export { sinkListItem, wrapInList } from "prosemirror-schema-list"; import { redo as historyRedo, undo as historyUndo, redoDepth, undoDepth } from "prosemirror-history"; diff --git a/panel/src/ts/components/inputs/editor/markdown/inputrules.ts b/panel/src/ts/components/inputs/editor/markdown/inputrules.ts index 59baecdc2..9dcd4e37b 100644 --- a/panel/src/ts/components/inputs/editor/markdown/inputrules.ts +++ b/panel/src/ts/components/inputs/editor/markdown/inputrules.ts @@ -1,5 +1,5 @@ import { ellipsis, emDash, inputRules, smartQuotes, textblockTypeInputRule, wrappingInputRule } from "prosemirror-inputrules"; -import { NodeType, Schema } from "prosemirror-model"; +import type { NodeType, Schema } from "prosemirror-model"; export function blockQuoteRule(nodeType: NodeType) { return wrappingInputRule(/^\s*>\s$/, nodeType); diff --git a/panel/src/ts/components/inputs/editor/markdown/keymap.ts b/panel/src/ts/components/inputs/editor/markdown/keymap.ts index 18c79da3c..31b37f6cb 100644 --- a/panel/src/ts/components/inputs/editor/markdown/keymap.ts +++ b/panel/src/ts/components/inputs/editor/markdown/keymap.ts @@ -1,8 +1,8 @@ import { chainCommands, exitCode, joinDown, joinUp, lift, selectParentNode, setBlockType, toggleMark, wrapIn } from "prosemirror-commands"; import { liftListItem, sinkListItem, splitListItem, wrapInList } from "prosemirror-schema-list"; import { redo, undo } from "prosemirror-history"; -import { Command } from "prosemirror-state"; -import { Schema } from "prosemirror-model"; +import type { Command } from "prosemirror-state"; +import type { Schema } from "prosemirror-model"; import { undoInputRule } from "prosemirror-inputrules"; const mac = typeof navigator !== "undefined" ? /Mac|iP(hone|[oa]d)/.test(navigator.platform) : false; diff --git a/panel/src/ts/components/inputs/editor/markdown/linktooltip.ts b/panel/src/ts/components/inputs/editor/markdown/linktooltip.ts index e592232ab..d53a395f3 100644 --- a/panel/src/ts/components/inputs/editor/markdown/linktooltip.ts +++ b/panel/src/ts/components/inputs/editor/markdown/linktooltip.ts @@ -2,9 +2,9 @@ import { debounce, throttle } from "../../../../utils/events"; import { getMarkRange, insertLink, removeLink } from "./commands"; import { $ } from "../../../../utils/selectors"; import { app } from "../../../../app"; -import { EditorView } from "prosemirror-view"; +import type { EditorView } from "prosemirror-view"; import { insertIcon } from "../../../icons"; -import { Mark } from "prosemirror-model"; +import type { Mark } from "prosemirror-model"; import { Plugin } from "prosemirror-state"; import { schema } from "prosemirror-markdown"; import { Tooltip } from "../../../tooltip"; diff --git a/panel/src/ts/components/inputs/editor/markdown/menu.ts b/panel/src/ts/components/inputs/editor/markdown/menu.ts index 70eaff88e..5fcdb59ac 100644 --- a/panel/src/ts/components/inputs/editor/markdown/menu.ts +++ b/panel/src/ts/components/inputs/editor/markdown/menu.ts @@ -1,9 +1,10 @@ -import { Command, NodeSelection, Plugin } from "prosemirror-state"; import { insertImage, insertLink, isMarkActive, lift, redo, setBlockType, sinkListItem, toggleMark, undo, wrapIn, wrapInList } from "./commands"; -import { MarkType, NodeType } from "prosemirror-model"; +import type { MarkType, NodeType } from "prosemirror-model"; +import { NodeSelection, Plugin } from "prosemirror-state"; import { $$ } from "../../../../utils/selectors"; import { app } from "../../../../app"; -import { EditorView } from "prosemirror-view"; +import type { Command } from "prosemirror-state"; +import type { EditorView } from "prosemirror-view"; import { passIcon } from "../../../icons"; import { schema } from "prosemirror-markdown"; diff --git a/panel/src/ts/components/inputs/editor/markdown/placeholder.ts b/panel/src/ts/components/inputs/editor/markdown/placeholder.ts index 9e8d728ae..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,6 @@ import { Decoration, DecorationSet } from "prosemirror-view"; -import { EditorState, Plugin } from "prosemirror-state"; +import type { EditorState } from "prosemirror-state"; +import { Plugin } from "prosemirror-state"; export function placeholderPlugin(text: string) { if (!text) { diff --git a/panel/src/ts/components/inputs/editor/markdown/view.ts b/panel/src/ts/components/inputs/editor/markdown/view.ts index 66bf24726..178b650e9 100644 --- a/panel/src/ts/components/inputs/editor/markdown/view.ts +++ b/panel/src/ts/components/inputs/editor/markdown/view.ts @@ -1,5 +1,5 @@ import { defaultMarkdownParser, defaultMarkdownSerializer, schema } from "prosemirror-markdown"; -import { EditorState, Plugin, Transaction } from "prosemirror-state"; +import { EditorState, Plugin } from "prosemirror-state"; import { app } from "../../../../app"; import { baseKeymap } from "prosemirror-commands"; import { buildInputRules } from "./inputrules"; @@ -10,6 +10,7 @@ import { keymap } from "prosemirror-keymap"; import { linkTooltip } from "./linktooltip"; import { menuPlugin } from "./menu"; import { placeholderPlugin } from "./placeholder"; +import type { Transaction } from "prosemirror-state"; export interface MarkdownViewOptions { editable?: boolean; diff --git a/panel/src/ts/components/inputs/tags-input.ts b/panel/src/ts/components/inputs/tags-input.ts index fb4bb31e6..7c600f9fe 100644 --- a/panel/src/ts/components/inputs/tags-input.ts +++ b/panel/src/ts/components/inputs/tags-input.ts @@ -2,7 +2,7 @@ import { $, $$ } from "../../utils/selectors"; import { escapeRegExp, makeDiacriticsRegExp } from "../../utils/validation"; import { debounce } from "../../utils/events"; import { insertIcon } from "../icons"; -import Sortable from "sortablejs"; +import type { SortableEvent } from "sortablejs"; interface TagsInputOptions { labels: { [key: string]: string }; @@ -69,7 +69,7 @@ export class TagsInput { this.updateDropdown(); } - private createField() { + private async createField() { if ("limit" in this.element.dataset) { this.options.limit = parseInt(this.element.dataset.limit as string); } @@ -124,6 +124,8 @@ export class TagsInput { }); if (this.options.orderable) { + const { default: Sortable } = await import("sortablejs"); + Sortable.create(this.list, { forceFallback: true, animation: 150, @@ -139,14 +141,14 @@ export class TagsInput { this.field.classList.add("is-dragging"); }, - onFilter: (event: Sortable.SortableEvent) => { + onFilter: (event: SortableEvent) => { if (event.target.matches(".tag-remove")) { this.removeTag(event.item.innerText); this.list.removeChild(event.item); } }, - onEnd: (event: Sortable.SortableEvent) => { + onEnd: (event: SortableEvent) => { this.field.classList.remove("is-dragging"); const newIndex = event.newIndex; const oldIndex = event.oldIndex; @@ -229,8 +231,8 @@ export class TagsInput { const { value, icon, thumb } = typeof list[key] === "object" ? list[key] : { value: list[key], icon: undefined, thumb: undefined }; this.addDropdownItem({ - label: value as string, - value: isAssociative ? key : (value as string), + label: value, + value: isAssociative ? key : (value), icon, thumb, }); @@ -592,7 +594,7 @@ export class TagsInput { nextItem = nextItem.nextSibling as HTMLElement; } if (nextItem) { - return this.selectDropdownItem(nextItem as HTMLElement); + return this.selectDropdownItem(nextItem); } } this.selectFirstDropdownItem(); diff --git a/panel/src/ts/components/inputs/upload-input.ts b/panel/src/ts/components/inputs/upload-input.ts index b241d4787..2b6c1e1f2 100644 --- a/panel/src/ts/components/inputs/upload-input.ts +++ b/panel/src/ts/components/inputs/upload-input.ts @@ -1,13 +1,13 @@ import { $ } from "../../utils/selectors"; import { app } from "../../app"; +import { escapeHtml } from "../../utils/validation"; import { FilesList } from "../fileslist"; -import { Form } from "../form"; +import type { Form } from "../form"; import { insertIcon } from "../icons"; import { Notification } from "../notification"; import { Request } from "../../utils/request"; import { SelectInput } from "./select-input"; import { TagsInput } from "./tags-input"; -import { escapeHtml } from "../../utils/validation"; export class UploadInput { readonly element: HTMLInputElement; diff --git a/panel/src/ts/components/modal.ts b/panel/src/ts/components/modal.ts index 1ccdcdea5..6cbe300a5 100644 --- a/panel/src/ts/components/modal.ts +++ b/panel/src/ts/components/modal.ts @@ -128,7 +128,7 @@ export class Modal { private registerEvents() { document.addEventListener("click", (event) => { - const target = (event.target as HTMLElement).closest(`[data-modal="${this.element.id}"]`) as HTMLDivElement | null; + const target = (event.target as HTMLElement).closest(`[data-modal="${this.element.id}"]`) as HTMLElement; if (target) { this.open({ action: target.dataset.modalAction, triggerElement: target }); } diff --git a/panel/src/ts/components/statistics-chart.ts b/panel/src/ts/components/statistics-chart.ts index cd5c4c658..6087eba6b 100644 --- a/panel/src/ts/components/statistics-chart.ts +++ b/panel/src/ts/components/statistics-chart.ts @@ -1,9 +1,13 @@ -import { LineChart, LineChartData } from "chartist"; +import type { LineChartData } from "chartist"; import { passIcon } from "./icons"; import { Tooltip } from "./tooltip"; export class StatisticsChart { constructor(container: HTMLElement, data: LineChartData) { + this.init(container, data); + } + + private async init(container: HTMLElement, data: LineChartData) { const spacing = 100; const options = { @@ -32,6 +36,8 @@ export class StatisticsChart { }, }; + const { LineChart } = await import("chartist"); + const chart = new LineChart(container, data, options); chart.on("draw", (event) => { diff --git a/panel/src/ts/components/views/pages.ts b/panel/src/ts/components/views/pages.ts index 0188aa897..09367756b 100644 --- a/panel/src/ts/components/views/pages.ts +++ b/panel/src/ts/components/views/pages.ts @@ -1,12 +1,12 @@ import { $, $$ } from "../../utils/selectors"; import { escapeHtml, escapeRegExp, makeDiacriticsRegExp, makeSlug } from "../../utils/validation"; +import type { MoveEvent, SortableEvent } from "sortablejs"; import { app } from "../../app"; import { debounce } from "../../utils/events"; -import { Form } from "../form"; +import type { Form } from "../form"; import { Notification } from "../notification"; import { Request } from "../../utils/request"; -import { SelectInput } from "../inputs/select-input"; -import Sortable from "sortablejs"; +import type { SelectInput } from "../inputs/select-input"; export class Pages { constructor() { @@ -185,7 +185,7 @@ export class Pages { new Request( { method: "POST", - url: action as string, + url: action, data: { "csrf-token": app.config.csrfToken as string, }, @@ -292,7 +292,9 @@ export class Pages { } } - function initSortable(element: HTMLElement) { + async function initSortable(element: HTMLElement) { + const { default: Sortable } = await import("sortablejs"); + let originalOrder: string[] = []; const sortable = Sortable.create(element, { @@ -319,13 +321,13 @@ export class Pages { element.classList.add("is-dragging"); }, - onMove(event: Sortable.MoveEvent) { + onMove(event: MoveEvent) { if (event.related.classList.contains("is-not-orderable")) { return false; } }, - onEnd(event: Sortable.SortableEvent) { + onEnd(event: SortableEvent) { element.classList.remove("is-dragging"); document.body.style.height = ""; diff --git a/panel/tsconfig.json b/panel/tsconfig.json index 84527f7e5..52f594900 100644 --- a/panel/tsconfig.json +++ b/panel/tsconfig.json @@ -5,7 +5,7 @@ "compilerOptions": { "esModuleInterop": true, "isolatedModules": true, - "lib": ["ES2017", "DOM"], + "lib": ["ES2020", "DOM"], "noEmit": true, "noImplicitAny": true, "noImplicitThis": true, diff --git a/panel/views/errors/error.php b/panel/views/errors/error.php index c18e2e21b..ff218e2c3 100644 --- a/panel/views/errors/error.php +++ b/panel/views/errors/error.php @@ -40,8 +40,8 @@ - assets()->add('js/app.min.js') ?> + assets()->add('js/app.min.js', ['module' => true]) ?> insert('partials.scripts') ?> - \ No newline at end of file + diff --git a/panel/views/layouts/login.php b/panel/views/layouts/login.php index 8ec4ded52..525b2d393 100644 --- a/panel/views/layouts/login.php +++ b/panel/views/layouts/login.php @@ -27,7 +27,7 @@ - assets()->add('js/app.min.js') ?> + assets()->add('js/app.min.js', ['module' => true]) ?> insert('partials.scripts') ?> diff --git a/panel/views/layouts/panel.php b/panel/views/layouts/panel.php index ca3e4b828..c88c9d7c3 100644 --- a/panel/views/layouts/panel.php +++ b/panel/views/layouts/panel.php @@ -36,7 +36,7 @@ modals() as $modal) : ?> insert('modals.modal', ['modal' => $modal]) ?> - assets()->add('js/app.min.js') ?> + assets()->add('js/app.min.js', ['module' => true]) ?> insert('partials.scripts') ?> diff --git a/panel/views/partials/scripts.php b/panel/views/partials/scripts.php index e9cbf9fc3..e2e9fbbe7 100644 --- a/panel/views/partials/scripts.php +++ b/panel/views/partials/scripts.php @@ -1,7 +1,10 @@ assets()->scripts() as $script): ?> - + - \ No newline at end of file +assets()->has('js/app.min.js')): ?> + +