Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion panel/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@
"lint:ts": "eslint './src/ts/**/*.ts' --fix",
"check": "pnpm check:css && pnpm check:ts",
"check:css": "stylelint './src/scss/**/*.scss'",
"check:ts": "eslint './src/ts/**/*.ts'"
"check:ts": "eslint './src/ts/**/*.ts'",
"dump:icons": "node ./src/scripts/dump-icons.js"
},
"dependencies": {
"@codemirror/commands": "^6.10.0",
Expand Down
57 changes: 57 additions & 0 deletions panel/src/scripts/dump-icons.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import fs from "fs";
import path from "path";

const ICONS_DIR = path.resolve(import.meta.dirname, "../../assets/icons/svg/");
const OUTPUT_FILE = path.resolve(import.meta.dirname, "../ts/components/icons.ts");

function sanitizeName(filename) {
return filename
.replace(/\.svg$/i, "")
.split(/[-_ ]+/)
.map((part, index) => {
if (index === 0) {
return part.toLowerCase();
}
return part.charAt(0).toUpperCase() + part.slice(1).toLowerCase();
})
.join("");
}

function readSVGFiles(dir) {
return fs.readdirSync(dir).filter((file) => file.endsWith(".svg"));
}

function generateModule(svgs) {
let content = "// This file is auto-generated. Do not edit directly.\n\n";

svgs.forEach(({ name, svg }) => {
const cleanedSVG = svg
.replace(/\r?\n|\r/g, " ")
.replace(/\s+/g, " ")
.trim();
content += `export const ${name} = \`${cleanedSVG}\\n\`;\n\n`;
});

return content;
}

function buildIcons() {
const files = readSVGFiles(ICONS_DIR);
if (!files.length) {
console.error("No SVG files found in", ICONS_DIR);
return;
}

const svgs = files.map((file) => {
const filepath = path.join(ICONS_DIR, file);
const svg = fs.readFileSync(filepath, "utf8");
const name = sanitizeName(file);
return { name, svg };
});

const moduleContent = generateModule(svgs);
fs.writeFileSync(OUTPUT_FILE, moduleContent, "utf8");
console.log(`Generated ${OUTPUT_FILE} with ${svgs.length} icons`);
}

buildIcons();
311 changes: 281 additions & 30 deletions panel/src/ts/components/icons.ts

Large diffs are not rendered by default.

16 changes: 8 additions & 8 deletions panel/src/ts/components/inputs/date-input.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { $, $$ } from "../../utils/selectors";
import { calendarClock, chevronDown, chevronLeft, chevronRight, chevronUp } from "../icons";
import { getOuterHeight, getOuterWidth } from "../../utils/dimensions";
import { insertIcon } from "../icons";
import { longClick } from "../../utils/events";
import { mod } from "../../utils/math";
import { throttle } from "../../utils/events";
Expand Down Expand Up @@ -420,17 +420,17 @@ class Calendar {
if (this.input.options.time) {
element.innerHTML += `<div class="calendar-separator"></div><table class="calendar-time"><tr><td><button type="button" class="nextHour" aria-label="${this.input.options.labels.nextHour}"></button></td><td></td><td><button type="button" class="nextMinute" aria-label="${this.input.options.labels.nextMinute}"></button></td></tr><tr><td class="calendar-hours"></td><td>:</td><td class="calendar-minutes"></td><td class="calendar-meridiem"></td></tr><tr><td><button type="button" class="prevHour" aria-label="${this.input.options.labels.prevHour}"></button></td><td></td><td><button type="button" class="prevMinute" aria-label="${this.input.options.labels.prevMinute}"></button></td></tr></table></div>`;

insertIcon("chevron-down", $(".prevHour", element) as HTMLElement);
insertIcon("chevron-up", $(".nextHour", element) as HTMLElement);
($(".prevHour", element) as HTMLElement).innerHTML = chevronDown;
($(".nextHour", element) as HTMLElement).innerHTML = chevronUp;

insertIcon("chevron-down", $(".prevMinute", element) as HTMLElement);
insertIcon("chevron-up", $(".nextMinute", element) as HTMLElement);
($(".prevMinute", element) as HTMLElement).innerHTML = chevronDown;
($(".nextMinute", element) as HTMLElement).innerHTML = chevronUp;
}

insertIcon("calendar-clock", $(".currentMonth", element) as HTMLElement);
($(".currentMonth", element) as HTMLElement).insertAdjacentHTML("afterbegin", calendarClock);

insertIcon("chevron-left", $(".prevMonth", element) as HTMLElement);
insertIcon("chevron-right", $(".nextMonth", element) as HTMLElement);
($(".prevMonth", element) as HTMLElement).innerHTML = chevronLeft;
($(".nextMonth", element) as HTMLElement).innerHTML = chevronRight;

($(".currentMonth", element) as HTMLElement).addEventListener("mousedown", (event) => {
this.now();
Expand Down
2 changes: 1 addition & 1 deletion panel/src/ts/components/inputs/duration-input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]) * TIME_INTERVALS[t];
seconds -= intervals[t] * TIME_INTERVALS[t];
}
});
return intervals;
Expand Down
4 changes: 2 additions & 2 deletions panel/src/ts/components/inputs/editor-input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { app } from "../../app";
import { type CodeView } from "./editor/code/view";
import { debounce } from "../../utils/events";
import { escapeRegExp } from "../../utils/validation";
import { insertIcon } from "../icons";
import { markdown } from "../icons";
import { type MarkdownView } from "./editor/markdown/view";

function addBaseUri(markdown: string, baseUri: string) {
Expand Down Expand Up @@ -83,7 +83,7 @@ export class EditorInput {
toggleButton.title = app.config.EditorInput.labels.toggleMarkdown;
toggleButton.ariaLabel = app.config.EditorInput.labels.toggleMarkdown;
toggleButton.disabled = this.element.disabled;
insertIcon("markdown", toggleButton);
toggleButton.innerHTML = markdown;
toolbar.appendChild(toggleButton);

this.container.appendChild(toolbar);
Expand Down
10 changes: 5 additions & 5 deletions panel/src/ts/components/inputs/editor/code/menu.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import * as icons from "../../../icons";
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) {
function createButton(icon: keyof typeof icons, title: string) {
const btn = document.createElement("button");
btn.type = "button";
btn.className = `button toolbar-button`;
btn.title = title;
btn.ariaLabel = title;
passIcon(icon, (data) => (btn.innerHTML = data));
btn.innerHTML = icons[icon] || "";
return btn;
}

Expand Down Expand Up @@ -70,8 +70,8 @@ export function MenuPlugin() {
(view) =>
new Menu(
[
{ dom: createButton("rotate-left", app.config.EditorInput.labels.undo), command: (view) => undo(view), enabler: (view) => undoDepth(view.state) > 0 },
{ dom: createButton("rotate-right", app.config.EditorInput.labels.redo), command: (view) => redo(view), enabler: (view) => redoDepth(view.state) > 0 },
{ dom: createButton("rotateLeft", app.config.EditorInput.labels.undo), command: (view) => undo(view), enabler: (view) => undoDepth(view.state) > 0 },
{ dom: createButton("rotateRight", app.config.EditorInput.labels.redo), command: (view) => redo(view), enabler: (view) => redoDepth(view.state) > 0 },
],
view,
),
Expand Down
8 changes: 4 additions & 4 deletions panel/src/ts/components/inputs/editor/markdown/linktooltip.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { debounce, throttle } from "../../../../utils/events";
import { getMarkRange, insertLink, removeLink } from "./commands";
import { link as linkIcon, pencil, trash } from "../../../icons";
import { $ } from "../../../../utils/selectors";
import { app } from "../../../../app";
import type { EditorView } from "prosemirror-view";
import { insertIcon } from "../../../icons";
import type { Mark } from "prosemirror-model";
import { Plugin } from "prosemirror-state";
import { schema } from "prosemirror-markdown";
Expand Down Expand Up @@ -82,9 +82,9 @@ class LinkTooltipView {
const tooltipEditLink = $('[data-command="edit-link"]', this.tooltip.element) as HTMLButtonElement;
const tooltipDeleteLink = $('[data-command="delete-link"]', this.tooltip.element) as HTMLButtonElement;

insertIcon("link", tooltipLink);
insertIcon("pencil", tooltipEditLink);
insertIcon("trash", tooltipDeleteLink);
tooltipLink.insertAdjacentHTML("afterbegin", linkIcon);
tooltipEditLink.insertAdjacentHTML("afterbegin", pencil);
tooltipDeleteLink.insertAdjacentHTML("afterbegin", trash);

const { state, dispatch } = this.editorView;

Expand Down
18 changes: 9 additions & 9 deletions panel/src/ts/components/inputs/editor/markdown/menu.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import * as icons from "../../../icons";
import { insertImage, insertLink, isMarkActive, lift, redo, setBlockType, sinkListItem, toggleMark, undo, wrapIn, wrapInList } from "./commands";
import type { MarkType, NodeType } from "prosemirror-model";
import { NodeSelection, Plugin } from "prosemirror-state";
import { $$ } from "../../../../utils/selectors";
import { app } from "../../../../app";
import type { Command } from "prosemirror-state";
import type { EditorView } from "prosemirror-view";
import { passIcon } from "../../../icons";
import { schema } from "prosemirror-markdown";

interface MenuItem {
Expand Down Expand Up @@ -192,12 +192,12 @@ export function menuPlugin(id: string) {
},
{
command: wrapInList(schema.nodes.bullet_list, schema.nodes.list_item),
dom: createButton("list-unordered", app.config.EditorInput.labels.bulletList),
dom: createButton("listUnordered", app.config.EditorInput.labels.bulletList),
group: "blocks",
},
{
command: wrapInList(schema.nodes.ordered_list, schema.nodes.list_item),
dom: createButton("list-ordered", app.config.EditorInput.labels.numberedList),
dom: createButton("listOrdered", app.config.EditorInput.labels.numberedList),
group: "blocks",
},
{
Expand All @@ -208,12 +208,12 @@ export function menuPlugin(id: string) {
},
{
command: sinkListItem(schema.nodes.list_item),
dom: createButton("indent-increase", app.config.EditorInput.labels.increaseIndent),
dom: createButton("indentIncrease", app.config.EditorInput.labels.increaseIndent),
group: "blocks",
},
{
command: lift,
dom: createButton("indent-decrease", app.config.EditorInput.labels.decreaseIndent),
dom: createButton("indentDecrease", app.config.EditorInput.labels.decreaseIndent),
group: "blocks",
},
{
Expand All @@ -230,11 +230,11 @@ export function menuPlugin(id: string) {
},
{
command: undo,
dom: createButton("rotate-left", app.config.EditorInput.labels.undo),
dom: createButton("rotateLeft", app.config.EditorInput.labels.undo),
},
{
command: redo,
dom: createButton("rotate-right", app.config.EditorInput.labels.redo),
dom: createButton("rotateRight", app.config.EditorInput.labels.redo),
},
];

Expand All @@ -250,13 +250,13 @@ export function menuPlugin(id: string) {
});
}

function createButton(icon: string, title: string) {
function createButton(icon: keyof typeof icons, title: string) {
const btn = document.createElement("button");
btn.type = "button";
btn.className = `button toolbar-button`;
btn.title = title;
btn.ariaLabel = title;
passIcon(icon, (data) => (btn.innerHTML = data));
btn.innerHTML = icons[icon] || "";
return btn;
}

Expand Down
6 changes: 4 additions & 2 deletions panel/src/ts/components/inputs/select-input.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as icons from "../icons";
import { $, $$ } from "../../utils/selectors";
import { escapeRegExp, makeDiacriticsRegExp } from "../../utils/validation";
import { insertIcon } from "../icons";
import { toCamelCase } from "../../utils/strings";

type SelectInputListItem = {
label: string;
Expand Down Expand Up @@ -203,7 +204,8 @@ export class SelectInput {
img.className = "dropdown-thumb";
item.insertAdjacentElement("afterbegin", img);
} else if (option.dataset.icon) {
insertIcon(option.dataset.icon, item);
const icon = toCamelCase(option.dataset.icon) as keyof typeof icons;
item.insertAdjacentHTML("afterbegin", icons[icon]);
}

for (const key in option.dataset) {
Expand Down
11 changes: 6 additions & 5 deletions panel/src/ts/components/inputs/tags-input.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import * as icons from "../icons";
import { $, $$ } from "../../utils/selectors";
import { escapeRegExp, makeDiacriticsRegExp } from "../../utils/validation";
import { debounce } from "../../utils/events";
import { insertIcon } from "../icons";
import type { SortableEvent } from "sortablejs";
import { toCamelCase } from "../../utils/strings";

interface TagsInputOptions {
labels: { [key: string]: string };
Expand All @@ -15,7 +16,7 @@ interface TagsInputOptions {
interface TagsInputDropdownItem {
label: string;
value: string;
icon?: string;
icon?: keyof typeof icons;
thumb?: string;
}

Expand Down Expand Up @@ -181,7 +182,7 @@ export class TagsInput {
img.className = "dropdown-thumb";
item.insertAdjacentElement("afterbegin", img);
} else if (option.icon) {
insertIcon(option.icon, item);
item.insertAdjacentHTML("afterbegin", icons[option.icon]);
}

item.addEventListener("click", () => {
Expand Down Expand Up @@ -232,8 +233,8 @@ export class TagsInput {

this.addDropdownItem({
label: value,
value: isAssociative ? key : (value),
icon,
value: isAssociative ? key : value,
icon: icon ? (toCamelCase(icon) as keyof typeof icons) : undefined,
thumb,
});
}
Expand Down
8 changes: 5 additions & 3 deletions panel/src/ts/components/inputs/upload-input.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import * as icons from "../icons";
import { $ } from "../../utils/selectors";
import { app } from "../../app";
import { escapeHtml } from "../../utils/validation";
import { FilesList } from "../fileslist";
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 { toCamelCase } from "../../utils/strings";

export class UploadInput {
readonly element: HTMLInputElement;
Expand Down Expand Up @@ -158,7 +159,7 @@ export class UploadInput {
label: data.name,
value: data.name,
thumb: data.thumbnail,
icon: `file-${data.type}`,
icon: toCamelCase(`file-${data.type}`) as keyof typeof icons,
});
input.sortDropdownItems();
}
Expand Down Expand Up @@ -223,7 +224,8 @@ export class UploadInput {
$(".file-thumbnail", filesItem)?.remove();
}

insertIcon(info.type ? `file-${info.type}` : "file", $(".file-icon", filesItem) as HTMLElement);
const icon = icons[toCamelCase(`file-${info.type}`) as keyof typeof icons] || icons["file"];
($(".file-icon", filesItem) as HTMLElement).innerHTML = icon;

const anchor = $(".file-name a", filesItem) as HTMLAnchorElement;
anchor.href = info.actions.info;
Expand Down
20 changes: 9 additions & 11 deletions panel/src/ts/components/notification.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import * as icons from "./icons";
import { $ } from "../utils/selectors";
import { passIcon } from "./icons";

type NotificationType = "info" | "success" | "warning" | "error";

type NotificationOptions = {
interval: number;
icon?: string;
icon?: keyof typeof icons;
newestOnTop: boolean;
fadeOutDelay: number;
mouseleaveDelay: number;
typeClass: Record<NotificationType, string>;
defaultIcons: Record<NotificationType, string>;
defaultIcons: Record<NotificationType, keyof typeof icons>;
};

export class Notification {
Expand All @@ -34,10 +34,10 @@ export class Notification {
error: "danger",
},
defaultIcons: {
info: "info-circle",
success: "check-circle",
warning: "exclamation-triangle",
error: "exclamation-octagon",
info: "infoCircle",
success: "checkCircle",
warning: "exclamationTriangle",
error: "exclamationOctagon",
},
};

Expand Down Expand Up @@ -83,10 +83,8 @@ export class Notification {
}

if (this.options.icon) {
passIcon(this.options.icon, (icon) => {
this.notificationElement = create(this.text, this.type, this.options.interval);
this.notificationElement.insertAdjacentHTML("afterbegin", icon);
});
this.notificationElement = create(this.text, this.type, this.options.interval);
this.notificationElement.insertAdjacentHTML("afterbegin", icons[this.options.icon] || "");
} else {
this.notificationElement = create(this.text, this.type, this.options.interval);
}
Expand Down
Loading