feat: Enhance Obsidian Halo Plugin with comprehensive design system and component styles
- Updated styles.css to implement a three-layer design token architecture: Primitive, Semantic, and Component. - Introduced new CSS variables for colors, spacing, typography, and shadows. - Developed styles for buttons, cards, inputs, textareas, selects, badges, tables, modals, forms, color pickers, empty states, and loading spinners. - Improved sync status view with responsive design and enhanced UI elements. - Added skeleton loading animations for better user experience. chore: Add configuration files for development environment - Created .claude/settings.json to enable commit commands plugin. - Added .vscode/c_cpp_properties.json for C/C++ IntelliSense configuration. - Introduced .vscode/launch.json for debugging C/C++ applications with GDB.
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"enabledPlugins": {
|
||||||
|
"commit-commands@claude-plugins-official": true
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+123
-23
File diff suppressed because one or more lines are too long
Vendored
+1221
-4
File diff suppressed because it is too large
Load Diff
Vendored
+1
-2
@@ -156,13 +156,12 @@
|
|||||||
"state": {
|
"state": {
|
||||||
"type": "outline",
|
"type": "outline",
|
||||||
"state": {
|
"state": {
|
||||||
"file": "博客/Git团队协作指南(精简版).md",
|
|
||||||
"followCursor": true,
|
"followCursor": true,
|
||||||
"showSearch": true,
|
"showSearch": true,
|
||||||
"searchQuery": ""
|
"searchQuery": ""
|
||||||
},
|
},
|
||||||
"icon": "lucide-list",
|
"icon": "lucide-list",
|
||||||
"title": "Git团队协作指南(精简版) 的大纲"
|
"title": "大纲"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
Vendored
+18
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "windows-gcc-x64",
|
||||||
|
"includePath": [
|
||||||
|
"${workspaceFolder}/**"
|
||||||
|
],
|
||||||
|
"compilerPath": "D:/settings/Language/C/mingw64/bin/gcc.exe",
|
||||||
|
"cStandard": "c17",
|
||||||
|
"cppStandard": "c++17",
|
||||||
|
"intelliSenseMode": "windows-gcc-x64",
|
||||||
|
"compilerArgs": [
|
||||||
|
""
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version": 4
|
||||||
|
}
|
||||||
Vendored
+24
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "C/C++ Runner: Debug Session",
|
||||||
|
"type": "cppdbg",
|
||||||
|
"request": "launch",
|
||||||
|
"args": [],
|
||||||
|
"stopAtEntry": false,
|
||||||
|
"externalConsole": true,
|
||||||
|
"cwd": "d:/Code/Obsidian/.obsidian/plugins/halo",
|
||||||
|
"program": "d:/Code/Obsidian/.obsidian/plugins/halo/build/Debug/outDebug",
|
||||||
|
"MIMode": "gdb",
|
||||||
|
"miDebuggerPath": "gdb",
|
||||||
|
"setupCommands": [
|
||||||
|
{
|
||||||
|
"description": "Enable pretty-printing for gdb",
|
||||||
|
"text": "-enable-pretty-printing",
|
||||||
|
"ignoreFailures": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Vendored
+58
-1
@@ -1,3 +1,60 @@
|
|||||||
{
|
{
|
||||||
"git.ignoreLimitWarning": true
|
"git.ignoreLimitWarning": true,
|
||||||
|
"C_Cpp_Runner.cCompilerPath": "gcc",
|
||||||
|
"C_Cpp_Runner.cppCompilerPath": "g++",
|
||||||
|
"C_Cpp_Runner.debuggerPath": "gdb",
|
||||||
|
"C_Cpp_Runner.cStandard": "c17",
|
||||||
|
"C_Cpp_Runner.cppStandard": "c++17",
|
||||||
|
"C_Cpp_Runner.msvcBatchPath": "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\VC\\Auxiliary\\Build\\vcvarsall.bat",
|
||||||
|
"C_Cpp_Runner.useMsvc": false,
|
||||||
|
"C_Cpp_Runner.warnings": [
|
||||||
|
"-Wall",
|
||||||
|
"-Wextra",
|
||||||
|
"-Wpedantic",
|
||||||
|
"-Wshadow",
|
||||||
|
"-Wformat=2",
|
||||||
|
"-Wcast-align",
|
||||||
|
"-Wconversion",
|
||||||
|
"-Wsign-conversion",
|
||||||
|
"-Wnull-dereference"
|
||||||
|
],
|
||||||
|
"C_Cpp_Runner.msvcWarnings": [
|
||||||
|
"/W4",
|
||||||
|
"/permissive-",
|
||||||
|
"/w14242",
|
||||||
|
"/w14287",
|
||||||
|
"/w14296",
|
||||||
|
"/w14311",
|
||||||
|
"/w14826",
|
||||||
|
"/w44062",
|
||||||
|
"/w44242",
|
||||||
|
"/w14905",
|
||||||
|
"/w14906",
|
||||||
|
"/w14263",
|
||||||
|
"/w44265",
|
||||||
|
"/w14928"
|
||||||
|
],
|
||||||
|
"C_Cpp_Runner.enableWarnings": true,
|
||||||
|
"C_Cpp_Runner.warningsAsError": false,
|
||||||
|
"C_Cpp_Runner.compilerArgs": [],
|
||||||
|
"C_Cpp_Runner.linkerArgs": [],
|
||||||
|
"C_Cpp_Runner.includePaths": [],
|
||||||
|
"C_Cpp_Runner.includeSearch": [
|
||||||
|
"*",
|
||||||
|
"**/*"
|
||||||
|
],
|
||||||
|
"C_Cpp_Runner.excludeSearch": [
|
||||||
|
"**/build",
|
||||||
|
"**/build/**",
|
||||||
|
"**/.*",
|
||||||
|
"**/.*/**",
|
||||||
|
"**/.vscode",
|
||||||
|
"**/.vscode/**"
|
||||||
|
],
|
||||||
|
"C_Cpp_Runner.useAddressSanitizer": false,
|
||||||
|
"C_Cpp_Runner.useUndefinedSanitizer": false,
|
||||||
|
"C_Cpp_Runner.useLeakSanitizer": false,
|
||||||
|
"C_Cpp_Runner.showCompilationTime": false,
|
||||||
|
"C_Cpp_Runner.useLinkTimeOptimization": false,
|
||||||
|
"C_Cpp_Runner.msvcSecureNoWarnings": false
|
||||||
}
|
}
|
||||||
@@ -196,7 +196,11 @@
|
|||||||
},
|
},
|
||||||
"tag_manager_modal": {
|
"tag_manager_modal": {
|
||||||
"title": "Tag Manager",
|
"title": "Tag Manager",
|
||||||
"button_create": "Create Tag",
|
"title_create": "Create Tag",
|
||||||
|
"title_edit": "Edit Tag",
|
||||||
|
"button_create": "Create",
|
||||||
|
"button_save": "Save",
|
||||||
|
"button_cancel": "Cancel",
|
||||||
"loading": "Loading...",
|
"loading": "Loading...",
|
||||||
"empty": "No tags",
|
"empty": "No tags",
|
||||||
"column_name": "Name",
|
"column_name": "Name",
|
||||||
@@ -218,7 +222,11 @@
|
|||||||
},
|
},
|
||||||
"category_manager_modal": {
|
"category_manager_modal": {
|
||||||
"title": "Category Manager",
|
"title": "Category Manager",
|
||||||
"button_create": "Create Category",
|
"title_create": "Create Category",
|
||||||
|
"title_edit": "Edit Category",
|
||||||
|
"button_create": "Create",
|
||||||
|
"button_save": "Save",
|
||||||
|
"button_cancel": "Cancel",
|
||||||
"loading": "Loading...",
|
"loading": "Loading...",
|
||||||
"empty": "No categories",
|
"empty": "No categories",
|
||||||
"column_name": "Name",
|
"column_name": "Name",
|
||||||
|
|||||||
@@ -196,7 +196,11 @@
|
|||||||
},
|
},
|
||||||
"tag_manager_modal": {
|
"tag_manager_modal": {
|
||||||
"title": "标签管理",
|
"title": "标签管理",
|
||||||
"button_create": "创建标签",
|
"title_create": "创建标签",
|
||||||
|
"title_edit": "编辑标签",
|
||||||
|
"button_create": "创建",
|
||||||
|
"button_save": "保存",
|
||||||
|
"button_cancel": "取消",
|
||||||
"loading": "加载中...",
|
"loading": "加载中...",
|
||||||
"empty": "暂无标签",
|
"empty": "暂无标签",
|
||||||
"column_name": "名称",
|
"column_name": "名称",
|
||||||
@@ -218,7 +222,11 @@
|
|||||||
},
|
},
|
||||||
"category_manager_modal": {
|
"category_manager_modal": {
|
||||||
"title": "分类管理",
|
"title": "分类管理",
|
||||||
"button_create": "创建分类",
|
"title_create": "创建分类",
|
||||||
|
"title_edit": "编辑分类",
|
||||||
|
"button_create": "创建",
|
||||||
|
"button_save": "保存",
|
||||||
|
"button_cancel": "取消",
|
||||||
"loading": "加载中...",
|
"loading": "加载中...",
|
||||||
"empty": "暂无分类",
|
"empty": "暂无分类",
|
||||||
"column_name": "名称",
|
"column_name": "名称",
|
||||||
|
|||||||
@@ -196,7 +196,11 @@
|
|||||||
},
|
},
|
||||||
"tag_manager_modal": {
|
"tag_manager_modal": {
|
||||||
"title": "標籤管理",
|
"title": "標籤管理",
|
||||||
"button_create": "創建標籤",
|
"title_create": "創建標籤",
|
||||||
|
"title_edit": "編輯標籤",
|
||||||
|
"button_create": "創建",
|
||||||
|
"button_save": "保存",
|
||||||
|
"button_cancel": "取消",
|
||||||
"loading": "加載中...",
|
"loading": "加載中...",
|
||||||
"empty": "暫無標籤",
|
"empty": "暫無標籤",
|
||||||
"column_name": "名稱",
|
"column_name": "名稱",
|
||||||
@@ -218,7 +222,11 @@
|
|||||||
},
|
},
|
||||||
"category_manager_modal": {
|
"category_manager_modal": {
|
||||||
"title": "分類管理",
|
"title": "分類管理",
|
||||||
"button_create": "創建分類",
|
"title_create": "創建分類",
|
||||||
|
"title_edit": "編輯分類",
|
||||||
|
"button_create": "創建",
|
||||||
|
"button_save": "保存",
|
||||||
|
"button_cancel": "取消",
|
||||||
"loading": "加載中...",
|
"loading": "加載中...",
|
||||||
"empty": "暫無分類",
|
"empty": "暫無分類",
|
||||||
"column_name": "名稱",
|
"column_name": "名稱",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import { Modal, Notice, Setting } from "obsidian";
|
import { Modal, Notice } from "obsidian";
|
||||||
import type HaloPlugin from "../main";
|
import type HaloPlugin from "../main";
|
||||||
import type { HaloSite } from "../settings";
|
import type { HaloSite } from "../settings";
|
||||||
import HaloService from "../service";
|
import HaloService from "../service";
|
||||||
@@ -9,8 +9,145 @@ export function openCategoryManagerModal(plugin: HaloPlugin, site: HaloSite): vo
|
|||||||
modal.open();
|
modal.open();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface CategoryFormData {
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
priority: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
class CategoryFormModal extends Modal {
|
||||||
|
private category: { metadata: { name: string }; spec: { displayName: string; slug: string; priority: number } } | null = null;
|
||||||
|
private isEdit = false;
|
||||||
|
private onSubmit: (data: CategoryFormData) => void;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private plugin: HaloPlugin,
|
||||||
|
private site: HaloSite,
|
||||||
|
category: CategoryFormModal["category"],
|
||||||
|
private onSubmitFn: (data: CategoryFormData) => void
|
||||||
|
) {
|
||||||
|
super(app);
|
||||||
|
this.category = category;
|
||||||
|
this.isEdit = !!category;
|
||||||
|
this.onSubmit = onSubmitFn;
|
||||||
|
}
|
||||||
|
|
||||||
|
onOpen() {
|
||||||
|
const { contentEl } = this;
|
||||||
|
contentEl.empty();
|
||||||
|
|
||||||
|
const modalEl = contentEl.createDiv("halo-modal");
|
||||||
|
modalEl.style.cssText = `
|
||||||
|
position: fixed; inset: 0; z-index: 1000;
|
||||||
|
display: flex; align-items: center; justify-content: center;
|
||||||
|
background: rgba(0,0,0,0.5); -webkit-backdrop-filter: blur(4px);
|
||||||
|
`;
|
||||||
|
|
||||||
|
const content = modalEl.createDiv("halo-card");
|
||||||
|
content.style.cssText = `
|
||||||
|
width: 100%; max-width: 440px; max-height: 90vh; overflow-y: auto;
|
||||||
|
padding: var(--space-6);
|
||||||
|
`;
|
||||||
|
|
||||||
|
const header = content.createDiv("halo-modal-header");
|
||||||
|
header.style.cssText = "margin-bottom: var(--space-4);";
|
||||||
|
|
||||||
|
const title = header.createEl("h3");
|
||||||
|
title.textContent = this.isEdit
|
||||||
|
? i18next.t("category_manager_modal.title_edit")
|
||||||
|
: i18next.t("category_manager_modal.title_create");
|
||||||
|
title.style.cssText = "font-size: 18px; font-weight: 600; margin: 0;";
|
||||||
|
|
||||||
|
const closeBtn = header.createEl("button", { text: "✕" });
|
||||||
|
closeBtn.className = "halo-modal-close";
|
||||||
|
closeBtn.style.cssText = `
|
||||||
|
display: flex; align-items: center; justify-content: center;
|
||||||
|
width: 32px; height: 32px; background: transparent; border: none;
|
||||||
|
border-radius: 8px; color: var(--text-muted); cursor: pointer; font-size: 16px;
|
||||||
|
`;
|
||||||
|
closeBtn.addEventListener("click", () => this.close());
|
||||||
|
|
||||||
|
const form = content.createDiv("halo-modal-body");
|
||||||
|
|
||||||
|
// Name field
|
||||||
|
const nameGroup = form.createDiv("halo-form-group");
|
||||||
|
const nameLabel = nameGroup.createEl("label", { text: i18next.t("category_manager_modal.column_name") });
|
||||||
|
nameLabel.className = "halo-form-label";
|
||||||
|
const nameInput = nameGroup.createEl("input", { type: "text" }) as HTMLInputElement;
|
||||||
|
nameInput.className = "halo-input";
|
||||||
|
nameInput.style.cssText = "width: 100%;";
|
||||||
|
nameInput.value = this.category?.spec.displayName || "";
|
||||||
|
nameInput.placeholder = i18next.t("category_manager_modal.prompt_name");
|
||||||
|
|
||||||
|
// Slug field
|
||||||
|
const slugGroup = form.createDiv("halo-form-group");
|
||||||
|
const slugLabel = slugGroup.createEl("label", { text: i18next.t("category_manager_modal.column_slug") });
|
||||||
|
slugLabel.className = "halo-form-label";
|
||||||
|
const slugInput = slugGroup.createEl("input", { type: "text" }) as HTMLInputElement;
|
||||||
|
slugInput.className = "halo-input";
|
||||||
|
slugInput.style.cssText = "width: 100%;";
|
||||||
|
slugInput.value = this.category?.spec.slug || "";
|
||||||
|
slugInput.placeholder = i18next.t("category_manager_modal.prompt_slug");
|
||||||
|
|
||||||
|
// Priority field
|
||||||
|
const priorityGroup = form.createDiv("halo-form-group");
|
||||||
|
const priorityLabel = priorityGroup.createEl("label", { text: i18next.t("category_manager_modal.column_priority") });
|
||||||
|
priorityLabel.className = "halo-form-label";
|
||||||
|
const priorityInput = priorityGroup.createEl("input", { type: "number" }) as HTMLInputElement;
|
||||||
|
priorityInput.className = "halo-input";
|
||||||
|
priorityInput.style.cssText = "width: 100%;";
|
||||||
|
priorityInput.value = String(this.category?.spec.priority ?? 0);
|
||||||
|
priorityInput.placeholder = i18next.t("category_manager_modal.prompt_priority");
|
||||||
|
|
||||||
|
// Footer buttons
|
||||||
|
const footer = content.createDiv("halo-modal-footer");
|
||||||
|
footer.style.cssText = "margin-top: var(--space-4);";
|
||||||
|
|
||||||
|
const cancelBtn = footer.createEl("button", { text: i18next.t("common.button_cancel") });
|
||||||
|
cancelBtn.className = "halo-btn halo-btn-secondary";
|
||||||
|
cancelBtn.addEventListener("click", () => this.close());
|
||||||
|
|
||||||
|
const submitBtn = footer.createEl("button", { text: this.isEdit ? i18next.t("common.button_save") : i18next.t("category_manager_modal.button_create") });
|
||||||
|
submitBtn.className = "halo-btn halo-btn-primary";
|
||||||
|
|
||||||
|
submitBtn.addEventListener("click", () => {
|
||||||
|
const name = nameInput.value.trim();
|
||||||
|
const slug = slugInput.value.trim() || this.generateSlug(name);
|
||||||
|
const priority = parseInt(priorityInput.value) || 0;
|
||||||
|
|
||||||
|
if (!name) {
|
||||||
|
nameInput.classList.add("error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.onSubmit({ name, slug, priority });
|
||||||
|
this.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Auto-generate slug on name change
|
||||||
|
nameInput.addEventListener("input", () => {
|
||||||
|
if (!this.isEdit || !this.category) {
|
||||||
|
slugInput.value = this.generateSlug(nameInput.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
modalEl.addEventListener("click", (e) => {
|
||||||
|
if (e.target === modalEl) this.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
nameInput.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
private generateSlug(name: string): string {
|
||||||
|
return name
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/[^a-z0-9一-龥]+/g, "-")
|
||||||
|
.replace(/^-|-$/g, "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class CategoryManagerModal extends Modal {
|
class CategoryManagerModal extends Modal {
|
||||||
private categories: any[] = [];
|
private categories: Array<{ metadata: { name: string }; spec: { displayName: string; slug: string; priority: number } }> = [];
|
||||||
private loading = true;
|
private loading = true;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@@ -26,34 +163,37 @@ class CategoryManagerModal extends Modal {
|
|||||||
|
|
||||||
contentEl.createEl("h2", {
|
contentEl.createEl("h2", {
|
||||||
text: i18next.t("category_manager_modal.title"),
|
text: i18next.t("category_manager_modal.title"),
|
||||||
|
cls: "modal-title"
|
||||||
});
|
});
|
||||||
|
|
||||||
const actionsEl = contentEl.createDiv("category-manager-actions");
|
const actionsEl = contentEl.createDiv("category-manager-actions");
|
||||||
|
actionsEl.style.cssText = "display: flex; justify-content: space-between; align-items: center; padding: var(--space-4);";
|
||||||
new Setting(actionsEl)
|
|
||||||
.addButton((button) => {
|
const createBtn = actionsEl.createEl("button", {
|
||||||
button
|
text: i18next.t("category_manager_modal.button_create"),
|
||||||
.setButtonText(i18next.t("category_manager_modal.button_create"))
|
cls: "halo-btn halo-btn-primary"
|
||||||
.setCta()
|
});
|
||||||
.onClick(() => {
|
createBtn.addEventListener("click", () => this.showCreateCategoryForm());
|
||||||
this.showCreateCategoryDialog();
|
|
||||||
});
|
const closeBtn = actionsEl.createEl("button", {
|
||||||
})
|
text: i18next.t("common.button_close"),
|
||||||
.addButton((button) => {
|
cls: "halo-btn halo-btn-secondary"
|
||||||
button
|
});
|
||||||
.setButtonText(i18next.t("common.button_close"))
|
closeBtn.addEventListener("click", () => this.close());
|
||||||
.onClick(() => this.close());
|
|
||||||
});
|
|
||||||
|
|
||||||
const listEl = contentEl.createDiv("category-list");
|
const listEl = contentEl.createDiv("category-list");
|
||||||
listEl.createEl("p", { text: i18next.t("category_manager_modal.loading") });
|
listEl.style.cssText = "padding: 0 var(--space-4) var(--space-4);";
|
||||||
|
|
||||||
|
const loadingEl = listEl.createDiv("halo-empty");
|
||||||
|
loadingEl.style.cssText = "padding: var(--space-6);";
|
||||||
|
loadingEl.innerHTML = `<div class="halo-spinner"></div><p style="margin-top: var(--space-3); color: var(--text-muted);">${i18next.t("category_manager_modal.loading")}</p>`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const service = new HaloService(this.plugin.app, this.plugin.settings, this.site);
|
const service = new HaloService(this.plugin.app, this.plugin.settings, this.site);
|
||||||
this.categories = await service.getCategories();
|
this.categories = await service.getCategories();
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
this.renderCategoryList();
|
this.renderCategoryList();
|
||||||
} catch (error) {
|
} catch {
|
||||||
new Notice(i18next.t("common.error_connection_failed"));
|
new Notice(i18next.t("common.error_connection_failed"));
|
||||||
this.close();
|
this.close();
|
||||||
}
|
}
|
||||||
@@ -62,113 +202,140 @@ class CategoryManagerModal extends Modal {
|
|||||||
private renderCategoryList() {
|
private renderCategoryList() {
|
||||||
const { contentEl } = this;
|
const { contentEl } = this;
|
||||||
const listEl = contentEl.querySelector(".category-list") as HTMLElement;
|
const listEl = contentEl.querySelector(".category-list") as HTMLElement;
|
||||||
|
|
||||||
if (!listEl) return;
|
if (!listEl) return;
|
||||||
|
|
||||||
listEl.empty();
|
listEl.empty();
|
||||||
|
|
||||||
if (this.categories.length === 0) {
|
if (this.categories.length === 0) {
|
||||||
listEl.createEl("p", { text: i18next.t("category_manager_modal.empty") });
|
const emptyEl = listEl.createDiv("halo-empty");
|
||||||
|
emptyEl.innerHTML = `
|
||||||
|
<div style="font-size: 48px; opacity: 0.5;">📁</div>
|
||||||
|
<p style="margin-top: var(--space-3); font-weight: 500;">${i18next.t("category_manager_modal.empty")}</p>
|
||||||
|
`;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const table = listEl.createEl("table", { cls: "category-table" });
|
const table = listEl.createEl("table", { cls: "halo-table" });
|
||||||
const header = table.createEl("tr");
|
const header = table.createEl("thead").createEl("tr");
|
||||||
header.createEl("th", { text: i18next.t("category_manager_modal.column_name") });
|
header.createEl("th", { text: i18next.t("category_manager_modal.column_name") });
|
||||||
header.createEl("th", { text: i18next.t("category_manager_modal.column_slug") });
|
header.createEl("th", { text: i18next.t("category_manager_modal.column_slug") });
|
||||||
header.createEl("th", { text: i18next.t("category_manager_modal.column_priority") });
|
header.createEl("th", { text: i18next.t("category_manager_modal.column_priority") });
|
||||||
header.createEl("th", { text: i18next.t("category_manager_modal.column_actions") });
|
header.createEl("th", { text: i18next.t("category_manager_modal.column_actions"), cls: "text-right" });
|
||||||
|
|
||||||
|
const tbody = table.createEl("tbody");
|
||||||
|
|
||||||
for (const category of this.categories) {
|
for (const category of this.categories) {
|
||||||
const row = table.createEl("tr");
|
const row = tbody.createEl("tr");
|
||||||
|
|
||||||
row.createEl("td", { text: category.spec.displayName });
|
const nameCell = row.createEl("td");
|
||||||
|
nameCell.style.cssText = "font-weight: 500;";
|
||||||
|
nameCell.textContent = category.spec.displayName;
|
||||||
|
|
||||||
row.createEl("td", { text: category.spec.slug });
|
row.createEl("td", { text: category.spec.slug });
|
||||||
row.createEl("td", { text: String(category.spec.priority || 0) });
|
|
||||||
|
const priorityCell = row.createEl("td");
|
||||||
|
const priorityBadge = priorityCell.createEl("span", { text: String(category.spec.priority || 0) });
|
||||||
|
priorityBadge.style.cssText = `
|
||||||
|
display: inline-flex; align-items: center; justify-content: center;
|
||||||
|
min-width: 28px; height: 22px; padding: 0 8px;
|
||||||
|
background: var(--background-secondary); border-radius: var(--radius-full);
|
||||||
|
font-size: var(--font-size-xs); font-weight: 500;
|
||||||
|
`;
|
||||||
|
|
||||||
const actionsCell = row.createEl("td");
|
const actionsCell = row.createEl("td");
|
||||||
actionsCell.createEl("button", {
|
actionsCell.style.cssText = "text-align: right;";
|
||||||
|
|
||||||
|
const editBtn = actionsCell.createEl("button", {
|
||||||
text: i18next.t("category_manager_modal.button_edit"),
|
text: i18next.t("category_manager_modal.button_edit"),
|
||||||
cls: "category-action-btn",
|
cls: "halo-btn halo-btn-sm halo-btn-ghost"
|
||||||
}).addEventListener("click", () => {
|
|
||||||
this.showEditCategoryDialog(category);
|
|
||||||
});
|
});
|
||||||
|
editBtn.addEventListener("click", () => this.showEditCategoryForm(category));
|
||||||
actionsCell.createEl("button", {
|
|
||||||
|
const deleteBtn = actionsCell.createEl("button", {
|
||||||
text: i18next.t("category_manager_modal.button_delete"),
|
text: i18next.t("category_manager_modal.button_delete"),
|
||||||
cls: "category-action-btn danger",
|
cls: "halo-btn halo-btn-sm halo-btn-ghost"
|
||||||
}).addEventListener("click", () => {
|
|
||||||
this.showDeleteCategoryDialog(category);
|
|
||||||
});
|
});
|
||||||
|
deleteBtn.style.cssText += "color: var(--halo-danger);";
|
||||||
|
deleteBtn.addEventListener("click", () => this.showDeleteCategoryDialog(category));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async showCreateCategoryDialog() {
|
private showCreateCategoryForm() {
|
||||||
const name = prompt(i18next.t("category_manager_modal.prompt_name"));
|
new CategoryFormModal(this.plugin, this.site, null, async (data) => {
|
||||||
if (!name) return;
|
try {
|
||||||
|
const service = new HaloService(this.plugin.app, this.plugin.settings, this.site);
|
||||||
const slug = prompt(i18next.t("category_manager_modal.prompt_slug"), this.generateSlug(name));
|
await service.createCategory(data.name, data.slug, data.priority);
|
||||||
if (!slug) return;
|
new Notice(i18next.t("category_manager_modal.notice_create_success"));
|
||||||
|
this.categories = await service.getCategories();
|
||||||
const priority = prompt(i18next.t("category_manager_modal.prompt_priority"), "0");
|
this.renderCategoryList();
|
||||||
if (priority === null) return;
|
} catch {
|
||||||
|
new Notice(i18next.t("category_manager_modal.error_create_failed"));
|
||||||
try {
|
}
|
||||||
const service = new HaloService(this.plugin.app, this.plugin.settings, this.site);
|
});
|
||||||
await service.createCategory(name, slug, parseInt(priority) || 0);
|
|
||||||
new Notice(i18next.t("category_manager_modal.notice_create_success"));
|
|
||||||
|
|
||||||
this.categories = await service.getCategories();
|
|
||||||
this.renderCategoryList();
|
|
||||||
} catch (error) {
|
|
||||||
new Notice(i18next.t("category_manager_modal.error_create_failed"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async showEditCategoryDialog(category: any) {
|
private showEditCategoryForm(category: typeof this.categories[0]) {
|
||||||
const name = prompt(i18next.t("category_manager_modal.prompt_name"), category.spec.displayName);
|
new CategoryFormModal(this.plugin, this.site, category, async (data) => {
|
||||||
if (name === null) return;
|
try {
|
||||||
|
const service = new HaloService(this.plugin.app, this.plugin.settings, this.site);
|
||||||
const slug = prompt(i18next.t("category_manager_modal.prompt_slug"), category.spec.slug);
|
await service.updateCategory(category.metadata.name, data.name, data.slug, data.priority);
|
||||||
if (slug === null) return;
|
new Notice(i18next.t("category_manager_modal.notice_update_success"));
|
||||||
|
this.categories = await service.getCategories();
|
||||||
const priority = prompt(i18next.t("category_manager_modal.prompt_priority"), String(category.spec.priority || 0));
|
this.renderCategoryList();
|
||||||
if (priority === null) return;
|
} catch {
|
||||||
|
new Notice(i18next.t("category_manager_modal.error_update_failed"));
|
||||||
try {
|
}
|
||||||
const service = new HaloService(this.plugin.app, this.plugin.settings, this.site);
|
});
|
||||||
await service.updateCategory(category.metadata.name, name, slug, parseInt(priority) || 0);
|
|
||||||
new Notice(i18next.t("category_manager_modal.notice_update_success"));
|
|
||||||
|
|
||||||
this.categories = await service.getCategories();
|
|
||||||
this.renderCategoryList();
|
|
||||||
} catch (error) {
|
|
||||||
new Notice(i18next.t("category_manager_modal.error_update_failed"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async showDeleteCategoryDialog(category: any) {
|
private showDeleteCategoryDialog(category: typeof this.categories[0]) {
|
||||||
if (!confirm(i18next.t("category_manager_modal.confirm_delete", { name: category.spec.displayName }))) {
|
const modalEl = document.createElement("div");
|
||||||
return;
|
modalEl.style.cssText = `
|
||||||
}
|
position: fixed; inset: 0; z-index: 1000;
|
||||||
|
display: flex; align-items: center; justify-content: center;
|
||||||
|
background: rgba(0,0,0,0.5); -webkit-backdrop-filter: blur(4px);
|
||||||
|
`;
|
||||||
|
|
||||||
try {
|
const content = modalEl.createDiv("halo-card");
|
||||||
const service = new HaloService(this.plugin.app, this.plugin.settings, this.site);
|
content.style.cssText = "width: 100%; max-width: 400px; padding: var(--space-6);";
|
||||||
await service.deleteCategory(category.metadata.name);
|
|
||||||
new Notice(i18next.t("category_manager_modal.notice_delete_success"));
|
|
||||||
|
|
||||||
this.categories = await service.getCategories();
|
|
||||||
this.renderCategoryList();
|
|
||||||
} catch (error) {
|
|
||||||
new Notice(i18next.t("category_manager_modal.error_delete_failed"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private generateSlug(name: string): string {
|
const title = content.createEl("h3", {
|
||||||
return name
|
text: i18next.t("category_manager_modal.confirm_delete", { name: category.spec.displayName })
|
||||||
.toLowerCase()
|
});
|
||||||
.replace(/[^a-z0-9\u4e00-\u9fa5]+/g, "-")
|
title.style.cssText = "font-size: 16px; font-weight: 600; margin: 0 0 var(--space-4) 0;";
|
||||||
.replace(/^-|-$/g, "");
|
|
||||||
|
const actions = content.createDiv("halo-modal-footer");
|
||||||
|
actions.style.cssText = "margin-top: var(--space-4);";
|
||||||
|
|
||||||
|
const cancelBtn = actions.createEl("button", {
|
||||||
|
text: i18next.t("common.button_cancel"),
|
||||||
|
cls: "halo-btn halo-btn-secondary"
|
||||||
|
});
|
||||||
|
cancelBtn.addEventListener("click", () => modalEl.remove());
|
||||||
|
|
||||||
|
const deleteBtn = actions.createEl("button", {
|
||||||
|
text: i18next.t("common.button_delete"),
|
||||||
|
cls: "halo-btn halo-btn-danger"
|
||||||
|
});
|
||||||
|
deleteBtn.addEventListener("click", async () => {
|
||||||
|
modalEl.remove();
|
||||||
|
try {
|
||||||
|
const service = new HaloService(this.plugin.app, this.plugin.settings, this.site);
|
||||||
|
await service.deleteCategory(category.metadata.name);
|
||||||
|
new Notice(i18next.t("category_manager_modal.notice_delete_success"));
|
||||||
|
this.categories = await service.getCategories();
|
||||||
|
this.renderCategoryList();
|
||||||
|
} catch {
|
||||||
|
new Notice(i18next.t("category_manager_modal.error_delete_failed"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
modalEl.addEventListener("click", (e) => {
|
||||||
|
if (e.target === modalEl) modalEl.remove();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.body.appendChild(modalEl);
|
||||||
}
|
}
|
||||||
|
|
||||||
onClose() {
|
onClose() {
|
||||||
|
|||||||
@@ -9,8 +9,191 @@ export function openTagManagerModal(plugin: HaloPlugin, site: HaloSite): void {
|
|||||||
modal.open();
|
modal.open();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface TagFormData {
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
color: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
class TagFormModal extends Modal {
|
||||||
|
private tag: { metadata: { name: string }; spec: { displayName: string; slug: string; color: string } } | null = null;
|
||||||
|
private isEdit = false;
|
||||||
|
private onSubmit: (data: TagFormData) => void;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private plugin: HaloPlugin,
|
||||||
|
private site: HaloSite,
|
||||||
|
tag: TagFormModal["tag"],
|
||||||
|
private onSubmitFn: (data: TagFormData) => void
|
||||||
|
) {
|
||||||
|
super(app);
|
||||||
|
this.tag = tag;
|
||||||
|
this.isEdit = !!tag;
|
||||||
|
this.onSubmit = onSubmitFn;
|
||||||
|
}
|
||||||
|
|
||||||
|
onOpen() {
|
||||||
|
const { contentEl } = this;
|
||||||
|
contentEl.empty();
|
||||||
|
|
||||||
|
const modalEl = contentEl.createDiv("halo-modal");
|
||||||
|
modalEl.style.cssText = `
|
||||||
|
position: fixed; inset: 0; z-index: 1000;
|
||||||
|
display: flex; align-items: center; justify-content: center;
|
||||||
|
background: rgba(0,0,0,0.5); -webkit-backdrop-filter: blur(4px);
|
||||||
|
`;
|
||||||
|
|
||||||
|
const content = modalEl.createDiv("halo-card");
|
||||||
|
content.style.cssText = `
|
||||||
|
width: 100%; max-width: 440px; max-height: 90vh; overflow-y: auto;
|
||||||
|
padding: var(--space-6);
|
||||||
|
`;
|
||||||
|
|
||||||
|
const header = content.createDiv("halo-modal-header");
|
||||||
|
header.style.cssText = "margin-bottom: var(--space-4);";
|
||||||
|
|
||||||
|
const title = header.createEl("h3");
|
||||||
|
title.textContent = this.isEdit
|
||||||
|
? i18next.t("tag_manager_modal.title_edit")
|
||||||
|
: i18next.t("tag_manager_modal.title_create");
|
||||||
|
title.style.cssText = "font-size: 18px; font-weight: 600; margin: 0;";
|
||||||
|
|
||||||
|
const closeBtn = header.createEl("button", { text: "✕" });
|
||||||
|
closeBtn.className = "halo-modal-close";
|
||||||
|
closeBtn.style.cssText = `
|
||||||
|
display: flex; align-items: center; justify-content: center;
|
||||||
|
width: 32px; height: 32px; background: transparent; border: none;
|
||||||
|
border-radius: 8px; color: var(--text-muted); cursor: pointer; font-size: 16px;
|
||||||
|
`;
|
||||||
|
closeBtn.addEventListener("click", () => this.close());
|
||||||
|
|
||||||
|
const form = content.createDiv("halo-modal-body");
|
||||||
|
|
||||||
|
// Name field
|
||||||
|
const nameGroup = form.createDiv("halo-form-group");
|
||||||
|
const nameLabel = nameGroup.createEl("label", { text: i18next.t("tag_manager_modal.column_name") });
|
||||||
|
nameLabel.className = "halo-form-label";
|
||||||
|
const nameInput = nameGroup.createEl("input", { type: "text" }) as HTMLInputElement;
|
||||||
|
nameInput.className = "halo-input";
|
||||||
|
nameInput.style.cssText = "width: 100%;";
|
||||||
|
nameInput.value = this.tag?.spec.displayName || "";
|
||||||
|
nameInput.placeholder = i18next.t("tag_manager_modal.prompt_name");
|
||||||
|
|
||||||
|
// Slug field
|
||||||
|
const slugGroup = form.createDiv("halo-form-group");
|
||||||
|
const slugLabel = slugGroup.createEl("label", { text: i18next.t("tag_manager_modal.column_slug") });
|
||||||
|
slugLabel.className = "halo-form-label";
|
||||||
|
const slugInput = slugGroup.createEl("input", { type: "text" }) as HTMLInputElement;
|
||||||
|
slugInput.className = "halo-input";
|
||||||
|
slugInput.style.cssText = "width: 100%;";
|
||||||
|
slugInput.value = this.tag?.spec.slug || "";
|
||||||
|
slugInput.placeholder = i18next.t("tag_manager_modal.prompt_slug");
|
||||||
|
|
||||||
|
// Color field
|
||||||
|
const colorGroup = form.createDiv("halo-form-group");
|
||||||
|
const colorLabel = colorGroup.createEl("label", { text: i18next.t("tag_manager_modal.column_color") });
|
||||||
|
colorLabel.className = "halo-form-label";
|
||||||
|
|
||||||
|
const colorPicker = colorGroup.createDiv("halo-color-picker");
|
||||||
|
colorPicker.style.cssText = "display: flex; align-items: center; gap: var(--space-3);";
|
||||||
|
|
||||||
|
const colorPreview = colorPicker.createEl("input", { type: "color" }) as HTMLInputElement;
|
||||||
|
colorPreview.style.cssText = `
|
||||||
|
width: 40px; height: 36px; padding: 2px; border: 1px solid var(--border-color);
|
||||||
|
border-radius: var(--radius-md); cursor: pointer; background: transparent;
|
||||||
|
`;
|
||||||
|
colorPreview.value = this.tag?.spec.color || "#4A90E2";
|
||||||
|
|
||||||
|
const colorInput = colorPicker.createEl("input", { type: "text" }) as HTMLInputElement;
|
||||||
|
colorInput.className = "halo-input halo-color-input";
|
||||||
|
colorInput.style.cssText = "flex: 1; font-family: monospace;";
|
||||||
|
colorInput.value = this.tag?.spec.color || "#4A90E2";
|
||||||
|
|
||||||
|
// Color preview update
|
||||||
|
colorPreview.addEventListener("input", () => {
|
||||||
|
colorInput.value = colorPreview.value;
|
||||||
|
});
|
||||||
|
colorInput.addEventListener("input", () => {
|
||||||
|
if (/^#[0-9A-Fa-f]{6}$/.test(colorInput.value)) {
|
||||||
|
colorPreview.value = colorInput.value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Preset colors
|
||||||
|
const presets = colorGroup.createDiv("halo-color-presets");
|
||||||
|
presets.style.cssText = "display: flex; gap: var(--space-2); margin-top: var(--space-2); flex-wrap: wrap;";
|
||||||
|
const presetColors = ["#4A90E2", "#67C23A", "#E6A23C", "#F56C6C", "#909399", "#8E44AD", "#3498DB", "#1ABC9C"];
|
||||||
|
|
||||||
|
for (const color of presetColors) {
|
||||||
|
const preset = presets.createEl("button");
|
||||||
|
preset.style.cssText = `
|
||||||
|
width: 24px; height: 24px; border-radius: var(--radius-sm);
|
||||||
|
background: ${color}; border: 2px solid transparent;
|
||||||
|
cursor: pointer; transition: all var(--transition-fast);
|
||||||
|
`;
|
||||||
|
preset.addEventListener("click", () => {
|
||||||
|
colorPreview.value = color;
|
||||||
|
colorInput.value = color;
|
||||||
|
});
|
||||||
|
preset.addEventListener("mouseenter", () => {
|
||||||
|
preset.style.transform = "scale(1.15)";
|
||||||
|
preset.style.borderColor = "var(--interactive-accent)";
|
||||||
|
});
|
||||||
|
preset.addEventListener("mouseleave", () => {
|
||||||
|
preset.style.transform = "scale(1)";
|
||||||
|
preset.style.borderColor = "transparent";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Footer buttons
|
||||||
|
const footer = content.createDiv("halo-modal-footer");
|
||||||
|
footer.style.cssText = "margin-top: var(--space-4);";
|
||||||
|
|
||||||
|
const cancelBtn = footer.createEl("button", { text: i18next.t("common.button_cancel") });
|
||||||
|
cancelBtn.className = "halo-btn halo-btn-secondary";
|
||||||
|
cancelBtn.addEventListener("click", () => this.close());
|
||||||
|
|
||||||
|
const submitBtn = footer.createEl("button", { text: this.isEdit ? i18next.t("common.button_save") : i18next.t("tag_manager_modal.button_create") });
|
||||||
|
submitBtn.className = "halo-btn halo-btn-primary";
|
||||||
|
|
||||||
|
submitBtn.addEventListener("click", () => {
|
||||||
|
const name = nameInput.value.trim();
|
||||||
|
const slug = slugInput.value.trim() || this.generateSlug(name);
|
||||||
|
const color = colorInput.value.trim() || "#4A90E2";
|
||||||
|
|
||||||
|
if (!name) {
|
||||||
|
nameInput.classList.add("error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.onSubmit({ name, slug, color });
|
||||||
|
this.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Auto-generate slug on name change
|
||||||
|
nameInput.addEventListener("input", () => {
|
||||||
|
if (!this.isEdit || !this.tag) {
|
||||||
|
slugInput.value = this.generateSlug(nameInput.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
modalEl.addEventListener("click", (e) => {
|
||||||
|
if (e.target === modalEl) this.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
nameInput.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
private generateSlug(name: string): string {
|
||||||
|
return name
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/[^a-z0-9一-龥]+/g, "-")
|
||||||
|
.replace(/^-|-$/g, "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class TagManagerModal extends Modal {
|
class TagManagerModal extends Modal {
|
||||||
private tags: any[] = [];
|
private tags: Array<{ metadata: { name: string }; spec: { displayName: string; slug: string; color: string } }> = [];
|
||||||
private loading = true;
|
private loading = true;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@@ -26,34 +209,37 @@ class TagManagerModal extends Modal {
|
|||||||
|
|
||||||
contentEl.createEl("h2", {
|
contentEl.createEl("h2", {
|
||||||
text: i18next.t("tag_manager_modal.title"),
|
text: i18next.t("tag_manager_modal.title"),
|
||||||
|
cls: "modal-title"
|
||||||
});
|
});
|
||||||
|
|
||||||
const actionsEl = contentEl.createDiv("tag-manager-actions");
|
const actionsEl = contentEl.createDiv("tag-manager-actions");
|
||||||
|
actionsEl.style.cssText = "display: flex; justify-content: space-between; align-items: center; padding: var(--space-4);";
|
||||||
new Setting(actionsEl)
|
|
||||||
.addButton((button) => {
|
const createBtn = actionsEl.createEl("button", {
|
||||||
button
|
text: i18next.t("tag_manager_modal.button_create"),
|
||||||
.setButtonText(i18next.t("tag_manager_modal.button_create"))
|
cls: "halo-btn halo-btn-primary"
|
||||||
.setCta()
|
});
|
||||||
.onClick(() => {
|
createBtn.addEventListener("click", () => this.showCreateTagForm());
|
||||||
this.showCreateTagDialog();
|
|
||||||
});
|
const closeBtn = actionsEl.createEl("button", {
|
||||||
})
|
text: i18next.t("common.button_close"),
|
||||||
.addButton((button) => {
|
cls: "halo-btn halo-btn-secondary"
|
||||||
button
|
});
|
||||||
.setButtonText(i18next.t("common.button_close"))
|
closeBtn.addEventListener("click", () => this.close());
|
||||||
.onClick(() => this.close());
|
|
||||||
});
|
|
||||||
|
|
||||||
const listEl = contentEl.createDiv("tag-list");
|
const listEl = contentEl.createDiv("tag-list");
|
||||||
listEl.createEl("p", { text: i18next.t("tag_manager_modal.loading") });
|
listEl.style.cssText = "padding: 0 var(--space-4) var(--space-4);";
|
||||||
|
|
||||||
|
const loadingEl = listEl.createDiv("halo-empty");
|
||||||
|
loadingEl.style.cssText = "padding: var(--space-6);";
|
||||||
|
loadingEl.innerHTML = `<div class="halo-spinner"></div><p style="margin-top: var(--space-3); color: var(--text-muted);">${i18next.t("tag_manager_modal.loading")}</p>`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const service = new HaloService(this.plugin.app, this.plugin.settings, this.site);
|
const service = new HaloService(this.plugin.app, this.plugin.settings, this.site);
|
||||||
this.tags = await service.getTags();
|
this.tags = await service.getTags();
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
this.renderTagList();
|
this.renderTagList();
|
||||||
} catch (error) {
|
} catch {
|
||||||
new Notice(i18next.t("common.error_connection_failed"));
|
new Notice(i18next.t("common.error_connection_failed"));
|
||||||
this.close();
|
this.close();
|
||||||
}
|
}
|
||||||
@@ -62,119 +248,140 @@ class TagManagerModal extends Modal {
|
|||||||
private renderTagList() {
|
private renderTagList() {
|
||||||
const { contentEl } = this;
|
const { contentEl } = this;
|
||||||
const listEl = contentEl.querySelector(".tag-list") as HTMLElement;
|
const listEl = contentEl.querySelector(".tag-list") as HTMLElement;
|
||||||
|
|
||||||
if (!listEl) return;
|
if (!listEl) return;
|
||||||
|
|
||||||
listEl.empty();
|
listEl.empty();
|
||||||
|
|
||||||
if (this.tags.length === 0) {
|
if (this.tags.length === 0) {
|
||||||
listEl.createEl("p", { text: i18next.t("tag_manager_modal.empty") });
|
const emptyEl = listEl.createDiv("halo-empty");
|
||||||
|
emptyEl.innerHTML = `
|
||||||
|
<div style="font-size: 48px; opacity: 0.5;">🏷️</div>
|
||||||
|
<p style="margin-top: var(--space-3); font-weight: 500;">${i18next.t("tag_manager_modal.empty")}</p>
|
||||||
|
`;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const table = listEl.createEl("table", { cls: "tag-table" });
|
const table = listEl.createEl("table", { cls: "halo-table" });
|
||||||
const header = table.createEl("tr");
|
const header = table.createEl("thead").createEl("tr");
|
||||||
header.createEl("th", { text: i18next.t("tag_manager_modal.column_name") });
|
header.createEl("th", { text: i18next.t("tag_manager_modal.column_name") });
|
||||||
header.createEl("th", { text: i18next.t("tag_manager_modal.column_slug") });
|
header.createEl("th", { text: i18next.t("tag_manager_modal.column_slug") });
|
||||||
header.createEl("th", { text: i18next.t("tag_manager_modal.column_color") });
|
header.createEl("th", { text: i18next.t("tag_manager_modal.column_color") });
|
||||||
header.createEl("th", { text: i18next.t("tag_manager_modal.column_actions") });
|
header.createEl("th", { text: i18next.t("tag_manager_modal.column_actions"), cls: "text-right" });
|
||||||
|
|
||||||
|
const tbody = table.createEl("tbody");
|
||||||
|
|
||||||
for (const tag of this.tags) {
|
for (const tag of this.tags) {
|
||||||
const row = table.createEl("tr");
|
const row = tbody.createEl("tr");
|
||||||
|
|
||||||
row.createEl("td", { text: tag.spec.displayName });
|
const nameCell = row.createEl("td");
|
||||||
|
nameCell.style.cssText = "font-weight: 500;";
|
||||||
|
nameCell.textContent = tag.spec.displayName;
|
||||||
|
|
||||||
row.createEl("td", { text: tag.spec.slug });
|
row.createEl("td", { text: tag.spec.slug });
|
||||||
|
|
||||||
const colorCell = row.createEl("td");
|
const colorCell = row.createEl("td");
|
||||||
colorCell.createEl("span", {
|
const colorBadge = colorCell.createEl("span", { text: " " });
|
||||||
text: tag.spec.color || "#ffffff",
|
colorBadge.style.cssText = `
|
||||||
cls: "tag-color-preview",
|
display: inline-block; width: 16px; height: 16px;
|
||||||
attr: { style: `background-color: ${tag.spec.color || "#ffffff"}` }
|
background: ${tag.spec.color || "#ffffff"}; border-radius: var(--radius-sm);
|
||||||
});
|
border: 1px solid var(--border-color); vertical-align: middle;
|
||||||
|
`;
|
||||||
|
colorCell.createSpan({ text: ` ${tag.spec.color || "#ffffff"}` });
|
||||||
|
|
||||||
const actionsCell = row.createEl("td");
|
const actionsCell = row.createEl("td");
|
||||||
actionsCell.createEl("button", {
|
actionsCell.style.cssText = "text-align: right;";
|
||||||
|
|
||||||
|
const editBtn = actionsCell.createEl("button", {
|
||||||
text: i18next.t("tag_manager_modal.button_edit"),
|
text: i18next.t("tag_manager_modal.button_edit"),
|
||||||
cls: "tag-action-btn",
|
cls: "halo-btn halo-btn-sm halo-btn-ghost"
|
||||||
}).addEventListener("click", () => {
|
|
||||||
this.showEditTagDialog(tag);
|
|
||||||
});
|
});
|
||||||
|
editBtn.addEventListener("click", () => this.showEditTagForm(tag));
|
||||||
actionsCell.createEl("button", {
|
|
||||||
|
const deleteBtn = actionsCell.createEl("button", {
|
||||||
text: i18next.t("tag_manager_modal.button_delete"),
|
text: i18next.t("tag_manager_modal.button_delete"),
|
||||||
cls: "tag-action-btn danger",
|
cls: "halo-btn halo-btn-sm halo-btn-ghost"
|
||||||
}).addEventListener("click", () => {
|
|
||||||
this.showDeleteTagDialog(tag);
|
|
||||||
});
|
});
|
||||||
|
deleteBtn.style.cssText += "color: var(--halo-danger);";
|
||||||
|
deleteBtn.addEventListener("click", () => this.showDeleteTagDialog(tag));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async showCreateTagDialog() {
|
private showCreateTagForm() {
|
||||||
const name = prompt(i18next.t("tag_manager_modal.prompt_name"));
|
new TagFormModal(this.plugin, this.site, null, async (data) => {
|
||||||
if (!name) return;
|
try {
|
||||||
|
const service = new HaloService(this.plugin.app, this.plugin.settings, this.site);
|
||||||
const slug = prompt(i18next.t("tag_manager_modal.prompt_slug"), this.generateSlug(name));
|
await service.createTag(data.name, data.slug, data.color);
|
||||||
if (!slug) return;
|
new Notice(i18next.t("tag_manager_modal.notice_create_success"));
|
||||||
|
this.tags = await service.getTags();
|
||||||
const color = prompt(i18next.t("tag_manager_modal.prompt_color"), "#4A90E2");
|
this.renderTagList();
|
||||||
if (!color) return;
|
} catch {
|
||||||
|
new Notice(i18next.t("tag_manager_modal.error_create_failed"));
|
||||||
try {
|
}
|
||||||
const service = new HaloService(this.plugin.app, this.plugin.settings, this.site);
|
});
|
||||||
await service.createTag(name, slug, color);
|
|
||||||
new Notice(i18next.t("tag_manager_modal.notice_create_success"));
|
|
||||||
|
|
||||||
this.tags = await service.getTags();
|
|
||||||
this.renderTagList();
|
|
||||||
} catch (error) {
|
|
||||||
new Notice(i18next.t("tag_manager_modal.error_create_failed"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async showEditTagDialog(tag: any) {
|
private showEditTagForm(tag: typeof this.tags[0]) {
|
||||||
const name = prompt(i18next.t("tag_manager_modal.prompt_name"), tag.spec.displayName);
|
new TagFormModal(this.plugin, this.site, tag, async (data) => {
|
||||||
if (name === null) return;
|
try {
|
||||||
|
const service = new HaloService(this.plugin.app, this.plugin.settings, this.site);
|
||||||
const slug = prompt(i18next.t("tag_manager_modal.prompt_slug"), tag.spec.slug);
|
await service.updateTag(tag.metadata.name, data.name, data.slug, data.color);
|
||||||
if (slug === null) return;
|
new Notice(i18next.t("tag_manager_modal.notice_update_success"));
|
||||||
|
this.tags = await service.getTags();
|
||||||
const color = prompt(i18next.t("tag_manager_modal.prompt_color"), tag.spec.color || "#4A90E2");
|
this.renderTagList();
|
||||||
if (color === null) return;
|
} catch {
|
||||||
|
new Notice(i18next.t("tag_manager_modal.error_update_failed"));
|
||||||
try {
|
}
|
||||||
const service = new HaloService(this.plugin.app, this.plugin.settings, this.site);
|
});
|
||||||
await service.updateTag(tag.metadata.name, name, slug, color);
|
|
||||||
new Notice(i18next.t("tag_manager_modal.notice_update_success"));
|
|
||||||
|
|
||||||
this.tags = await service.getTags();
|
|
||||||
this.renderTagList();
|
|
||||||
} catch (error) {
|
|
||||||
new Notice(i18next.t("tag_manager_modal.error_update_failed"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async showDeleteTagDialog(tag: any) {
|
private showDeleteTagDialog(tag: typeof this.tags[0]) {
|
||||||
if (!confirm(i18next.t("tag_manager_modal.confirm_delete", { name: tag.spec.displayName }))) {
|
const modalEl = document.createElement("div");
|
||||||
return;
|
modalEl.style.cssText = `
|
||||||
}
|
position: fixed; inset: 0; z-index: 1000;
|
||||||
|
display: flex; align-items: center; justify-content: center;
|
||||||
|
background: rgba(0,0,0,0.5); -webkit-backdrop-filter: blur(4px);
|
||||||
|
`;
|
||||||
|
|
||||||
try {
|
const content = modalEl.createDiv("halo-card");
|
||||||
const service = new HaloService(this.plugin.app, this.plugin.settings, this.site);
|
content.style.cssText = "width: 100%; max-width: 400px; padding: var(--space-6);";
|
||||||
await service.deleteTag(tag.metadata.name);
|
|
||||||
new Notice(i18next.t("tag_manager_modal.notice_delete_success"));
|
|
||||||
|
|
||||||
this.tags = await service.getTags();
|
|
||||||
this.renderTagList();
|
|
||||||
} catch (error) {
|
|
||||||
new Notice(i18next.t("tag_manager_modal.error_delete_failed"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private generateSlug(name: string): string {
|
const title = content.createEl("h3", {
|
||||||
return name
|
text: i18next.t("tag_manager_modal.confirm_delete", { name: tag.spec.displayName })
|
||||||
.toLowerCase()
|
});
|
||||||
.replace(/[^a-z0-9\u4e00-\u9fa5]+/g, "-")
|
title.style.cssText = "font-size: 16px; font-weight: 600; margin: 0 0 var(--space-4) 0;";
|
||||||
.replace(/^-|-$/g, "");
|
|
||||||
|
const actions = content.createDiv("halo-modal-footer");
|
||||||
|
actions.style.cssText = "margin-top: var(--space-4);";
|
||||||
|
|
||||||
|
const cancelBtn = actions.createEl("button", {
|
||||||
|
text: i18next.t("common.button_cancel"),
|
||||||
|
cls: "halo-btn halo-btn-secondary"
|
||||||
|
});
|
||||||
|
cancelBtn.addEventListener("click", () => modalEl.remove());
|
||||||
|
|
||||||
|
const deleteBtn = actions.createEl("button", {
|
||||||
|
text: i18next.t("common.button_delete"),
|
||||||
|
cls: "halo-btn halo-btn-danger"
|
||||||
|
});
|
||||||
|
deleteBtn.addEventListener("click", async () => {
|
||||||
|
modalEl.remove();
|
||||||
|
try {
|
||||||
|
const service = new HaloService(this.plugin.app, this.plugin.settings, this.site);
|
||||||
|
await service.deleteTag(tag.metadata.name);
|
||||||
|
new Notice(i18next.t("tag_manager_modal.notice_delete_success"));
|
||||||
|
this.tags = await service.getTags();
|
||||||
|
this.renderTagList();
|
||||||
|
} catch {
|
||||||
|
new Notice(i18next.t("tag_manager_modal.error_delete_failed"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
modalEl.addEventListener("click", (e) => {
|
||||||
|
if (e.target === modalEl) modalEl.remove();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.body.appendChild(modalEl);
|
||||||
}
|
}
|
||||||
|
|
||||||
onClose() {
|
onClose() {
|
||||||
|
|||||||
+1075
-117
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user