Skip to content

Latest commit

 

History

History
139 lines (118 loc) · 6.33 KB

Action-Format-Text-Partials.md

File metadata and controls

139 lines (118 loc) · 6.33 KB
name 文本局部格式设置
description 悬浮面板:在文本编辑模式下,快速为选中的文字添加局部加粗或变色效果。
author ymjr
version 1.0.0
license MIT
usage 运行此脚本以唤起“局部样式工具”悬浮面板。随后双击进入文本编辑模式并用光标选中一段文字,点击悬浮面板上的“局部加粗”或使用取色器选择颜色后点击“应用局部颜色”,即可为选中片段赋予独立样式。
features
构建原生悬浮控制台,并结合 `onmousedown` 事件的 `preventDefault` 技巧防止输入框失焦
自动向被选中范围注入 `boldPartial` 或 `colorPartial` 属性
dependencies
需要依赖 [Feature-Text-Render-Engine] 进行最终的 Canvas 绘制
autorun false

/*

*/
var locales = {
  zh: {
    notice_enter_edit: "请先进入文本编辑状态!",
    notice_select_text: "请选中需要设置样式的文字!",
    notice_bold_added: "已添加局部加粗",
    notice_color_added: "已添加局部变色",
    ui_title: "✨ 局部样式工具",
    ui_btn_bold: "B 局部加粗",
    ui_btn_color: "应用局部颜色"
  },
  en: {
    notice_enter_edit: "Please enter text edit mode first!",
    notice_select_text: "Please select the text to style!",
    notice_bold_added: "Partial bold added",
    notice_color_added: "Partial color added",
    ui_title: "✨ Partial Style Tool",
    ui_btn_bold: "B Partial Bold",
    ui_btn_color: "Apply Color"
  }
};

const { Notice } = ea.obsidian;

function applyPartialStyle(type, value) {
    const api = ea.getExcalidrawAPI();
    const editable = ea.targetView.contentEl.querySelector(".excalidraw-textEditorContainer textarea");
    const editingTextElement = api.getAppState().editingTextElement;
    
    if (!editable || !editingTextElement) {
        new Notice(t("notice_enter_edit"));
        return;
    }
    if (editable.selectionStart === editable.selectionEnd) {
        new Notice(t("notice_select_text"));
        return;
    }

    editingTextElement.customData = editingTextElement.customData || {};
    const startNewlineNum = editable.value.substring(0, editable.selectionStart).split("\n").length - 1;
    const endNewlineNum = editable.value.substring(0, editable.selectionEnd).split("\n").length - 1;
    
    const start = editable.selectionStart - startNewlineNum;
    const end = editable.selectionEnd - endNewlineNum;

    if (type === 'bold') {
        editingTextElement.customData.boldPartial = editingTextElement.customData.boldPartial || [];
        editingTextElement.customData.boldPartial.push({ start, end });
        new Notice(t("notice_bold_added"));
    } else if (type === 'color') {
        editingTextElement.customData.colorPartial = editingTextElement.customData.colorPartial || [];
        editingTextElement.customData.colorPartial.push({ start, end, color: value });
        new Notice(t("notice_color_added"));
    }

    ea.copyViewElementsToEAforEditing([editingTextElement]);
    ea.addElementsToView(false, false, false);
    
    // 恢复编辑框焦点
    setTimeout(() => editable.focus(), 50);
}

function createPartialStylePanel() {
    if (document.getElementById("ea-partial-style-panel")) return;

    const panel = document.createElement('div');
    panel.id = "ea-partial-style-panel";
    panel.style.cssText = `position:fixed; top:200px; right:150px; width:200px; background:var(--background-primary); border:1px solid var(--background-modifier-border); box-shadow:0 8px 24px rgba(0,0,0,0.2); border-radius:8px; z-index:9999; display:flex; flex-direction:column;`;

    const header = document.createElement('div');
    header.style.cssText = `padding:8px 12px; background:var(--background-secondary); cursor:move; border-bottom:1px solid var(--background-modifier-border); display:flex; justify-content:space-between; align-items:center; user-select:none; border-radius:8px 8px 0 0;`;
    header.innerHTML = `<b style="font-size:13px;">${t("ui_title")}</b><button id="close-btn" style="background:none; border:none; cursor:pointer;">✕</button>`;
    panel.appendChild(header);

    const content = document.createElement('div');
    content.style.cssText = `padding:12px; display:flex; flex-direction:column; gap:10px;`;

    // 局部加粗按钮 (注意 onmousedown 结合 preventDefault 防止输入框失焦)
    const btnBold = document.createElement('button');
    btnBold.innerText = t("ui_btn_bold");
    btnBold.style.cssText = "font-weight:bold; cursor:pointer; width:100%; padding:6px; border-radius:4px; background:var(--interactive-normal);";
    btnBold.onmousedown = (e) => { e.preventDefault(); applyPartialStyle('bold'); };
    content.appendChild(btnBold);

    // 局部变色区
    const colorDiv = document.createElement('div');
    colorDiv.style.cssText = "display:flex; justify-content:space-between; align-items:center; gap:8px;";
    
    const colorPicker = document.createElement('input');
    colorPicker.type = "color";
    colorPicker.value = "#ff0000";
    colorPicker.style.cssText = "width:35px; height:30px; padding:0; border:none; cursor:pointer; background:none;";
    
    const btnColor = document.createElement('button');
    btnColor.innerText = t("ui_btn_color");
    btnColor.style.cssText = "cursor:pointer; flex:1; padding:6px; border-radius:4px; background:var(--interactive-normal);";
    btnColor.onmousedown = (e) => { e.preventDefault(); applyPartialStyle('color', colorPicker.value); };

    colorDiv.appendChild(colorPicker);
    colorDiv.appendChild(btnColor);
    content.appendChild(colorDiv);

    panel.appendChild(content);
    document.body.appendChild(panel);

    header.querySelector('#close-btn').onclick = () => panel.remove();

    let isDragging = false, startX, startY, initLeft, initTop;
    header.onmousedown = (e) => {
        if(e.target.tagName === 'BUTTON') return;
        isDragging = true; startX = e.clientX; startY = e.clientY;
        const rect = panel.getBoundingClientRect(); initLeft = rect.left; initTop = rect.top;
        e.preventDefault();
        document.onmousemove = (e) => {
            if(!isDragging) return;
            panel.style.left = (initLeft + e.clientX - startX) + 'px';
            panel.style.top = (initTop + e.clientY - startY) + 'px';
        };
        document.onmouseup = () => { isDragging = false; document.onmousemove = null; document.onmouseup = null; };
    };
}

createPartialStylePanel();