--- name: 序号标签控制面板 description: 呼出或关闭序号标签(Bullet)设置面板,控制开启/关闭及样式 author: ymjr version: 1.0.0 license: MIT usage: 点击运行,屏幕弹出悬浮控制面板。可在其中设置序号样式并保存至配置。 autorun: false --- /* ```javascript */ var locales = { zh: { panel_title: "🎯 序号标签面板", btn_active: "🟢 开启中:请点击画布放置", btn_inactive: "🔴 已暂停:点击此处开启放置", lbl_index: "当前序号", lbl_fontsize: "字体大小", lbl_circlesize: "基础尺寸", lbl_colorlist: "循环背景色", lbl_fontcolor: "字体颜色", btn_save: "保存设置", btn_saved: "已保存!", notice_closed: "🔘 控制面板已关闭", notice_active: "✨ 已开启,请点击画布放置", notice_saved: "✅ 样式设置已保存", btn_add: "添加" }, en: { panel_title: "🎯 Bullet Panel", btn_active: "🟢 Active: Click canvas to place", btn_inactive: "🔴 Paused: Click to activate", lbl_index: "Current Index", lbl_fontsize: "Font Size", lbl_circlesize: "Circle Size", lbl_colorlist: "Cyclic BG Colors", lbl_fontcolor: "Font Color", btn_save: "Save Settings", btn_saved: "Saved!", notice_closed: "🔘 Panel closed", notice_active: "✨ Active: Click canvas to place", notice_saved: "✅ Settings saved", btn_add: "Add" } }; const NAMESPACE = "_ymjr_bullet"; // 确保引擎初始化了状态 if (!ExcalidrawAutomate.plugin[NAMESPACE]) { ExcalidrawAutomate.plugin[NAMESPACE] = { isActive: false, currentIndex: 1, uiElement: null, onIndexChange: null, tempSettings: null }; } const state = ExcalidrawAutomate.plugin[NAMESPACE]; if (state.uiElement) { state.uiElement.remove(); state.uiElement = null; state.isActive = false; state.tempSettings = null; // 关闭UI时清理临时设置 new Notice(t("notice_closed")); return; } // 读取系统设置并克隆到 tempSettings(即时修改不影响未保存的配置) let savedSettings = ExcalidrawAutomate.plugin.settings.scriptEngineSettings["bullet_settings"] || {}; if (savedSettings.bgColor && !savedSettings.colorList) { savedSettings.colorList = [savedSettings.bgColor]; } state.tempSettings = { fontSize: savedSettings.fontSize ?? 20, circleSize: savedSettings.circleSize ?? 36, colorList: savedSettings.colorList ? [...savedSettings.colorList] : ["#FFADAD", "#FFD6A5", "#FDFFB6", "#CAFFBF"], fontColor: savedSettings.fontColor ?? "#000000" }; const panel = document.createElement("div"); panel.id = "ymjr-bullet-ui-panel"; panel.style.cssText = ` position: fixed; top: 80px; right: 80px; width: 280px; background: var(--background-primary, #ffffff); border: 1px solid var(--background-modifier-border, #ccc); border-radius: 8px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); z-index: 99999; font-family: var(--font-interface); color: var(--text-normal, #333); overflow: hidden; user-select: none; `; panel.innerHTML = `
${t("panel_title")}
${t("lbl_colorlist")}
`; document.body.appendChild(panel); state.uiElement = panel; const header = panel.querySelector("#ymjr-bullet-header"); const closeBtn = panel.querySelector("#ymjr-bullet-close"); const toggleBtn = panel.querySelector("#ymjr-bullet-toggle"); const indexInput = panel.querySelector("#ymjr-bullet-index"); const saveBtn = panel.querySelector("#ymjr-bullet-save"); const fontSizeInput = panel.querySelector("#ymjr-bullet-fontsize"); const circleSizeInput = panel.querySelector("#ymjr-bullet-circlesize"); const fontColorInput = panel.querySelector("#ymjr-bullet-fontcolor"); const colorListContainer = panel.querySelector("#ymjr-bullet-color-list"); const addColorBtn = panel.querySelector("#ymjr-bullet-add-color"); // --- 渲染循环颜色列表 --- function renderColorList() { colorListContainer.innerHTML = ""; state.tempSettings.colorList.forEach((col, idx) => { const wrapper = document.createElement("div"); wrapper.style.cssText = "display: flex; align-items: center; gap: 2px; background: var(--background-modifier-form-field); border-radius: 4px; border: 1px solid var(--background-modifier-border); padding: 2px;"; wrapper.innerHTML = ` ${state.tempSettings.colorList.length > 1 ? `` : ''} `; colorListContainer.appendChild(wrapper); }); // 绑定颜色列表元素的即时更新事件 colorListContainer.querySelectorAll(".ymjr-col-picker").forEach(el => { el.addEventListener("input", (e) => { state.tempSettings.colorList[e.target.dataset.idx] = e.target.value; }); }); // 绑定删除颜色事件 colorListContainer.querySelectorAll(".ymjr-col-del").forEach(el => { el.addEventListener("click", (e) => { state.tempSettings.colorList.splice(e.target.dataset.idx, 1); renderColorList(); }); }); } renderColorList(); addColorBtn.addEventListener("click", () => { // 默认复制最后一个颜色 const lastColor = state.tempSettings.colorList[state.tempSettings.colorList.length - 1] || "#FFD6A5"; state.tempSettings.colorList.push(lastColor); renderColorList(); }); // --- 即时绑定区 --- fontSizeInput.addEventListener("input", (e) => state.tempSettings.fontSize = Number(e.target.value)); circleSizeInput.addEventListener("input", (e) => state.tempSettings.circleSize = Number(e.target.value)); fontColorInput.addEventListener("input", (e) => state.tempSettings.fontColor = e.target.value); // --- 核心状态控制 --- function updateToggleUI() { if (state.isActive) { toggleBtn.innerText = t("btn_active"); toggleBtn.style.background = "var(--interactive-success, #28a745)"; } else { toggleBtn.innerText = t("btn_inactive"); toggleBtn.style.background = "var(--interactive-normal, #6c757d)"; } } updateToggleUI(); toggleBtn.addEventListener("click", () => { state.isActive = !state.isActive; updateToggleUI(); if (state.isActive) { state.currentIndex = Number(indexInput.value) || 1; new Notice(t("notice_active")); } }); indexInput.addEventListener("change", (e) => state.currentIndex = Number(e.target.value)); state.onIndexChange = (newIndex) => { if (indexInput) indexInput.value = newIndex; }; // --- 持久化保存 --- saveBtn.addEventListener("click", async () => { // 将 tempSettings 深拷贝保存到磁盘 ExcalidrawAutomate.plugin.settings.scriptEngineSettings["bullet_settings"] = JSON.parse(JSON.stringify(state.tempSettings)); await ExcalidrawAutomate.plugin.saveSettings(); state.currentIndex = Number(indexInput.value); new Notice(t("notice_saved")); saveBtn.innerText = t("btn_saved"); setTimeout(() => saveBtn.innerText = t("btn_save"), 1500); }); closeBtn.addEventListener("click", () => { panel.remove(); state.uiElement = null; state.isActive = false; state.tempSettings = null; new Notice(t("notice_closed")); }); // 面板拖拽 let isDragging = false, startX, startY, initialLeft, initialTop; header.addEventListener('mousedown', (e) => { if (e.target.tagName === 'BUTTON') return; // 防止点击关闭按钮时触发拖拽 isDragging = true; startX = e.clientX; startY = e.clientY; const rect = panel.getBoundingClientRect(); initialLeft = rect.left; initialTop = rect.top; header.style.cursor = 'grabbing'; document.addEventListener('mousemove', onDrag); document.addEventListener('mouseup', onStopDrag); }); function onDrag(e) { if (!isDragging) return; panel.style.left = `${initialLeft + (e.clientX - startX)}px`; panel.style.top = `${initialTop + (e.clientY - startY)}px`; panel.style.bottom = 'auto'; panel.style.right = 'auto'; } function onStopDrag() { isDragging = false; header.style.cursor = 'grab'; document.removeEventListener('mousemove', onDrag); document.removeEventListener('mouseup', onStopDrag); }