import {fetchPost} from "../../util/fetch"; import {insertHTML} from "../util/insertHTML"; import {getIconByType} from "../../editor/getIcon"; import {updateHotkeyTip} from "../util/compatibility"; import {blockRender} from "../render/blockRender"; import {Constants} from "../../constants"; import {processRender} from "../util/processCode"; import {highlightRender} from "../render/highlightRender"; import {focusBlock, focusByRange, getEditorRange} from "../util/selection"; import {hasClosestBlock, hasClosestByClassName} from "../util/hasClosest"; import {getContenteditableElement, getTopAloneElement} from "../wysiwyg/getBlock"; import {replaceFileName} from "../../editor/rename"; import {transaction} from "../wysiwyg/transaction"; import {getAssetName, getDisplayName, pathPosix} from "../../util/pathName"; import {genEmptyElement} from "../../block/util"; import {updateListOrder} from "../wysiwyg/list"; import {escapeHtml} from "../../util/escape"; import {zoomOut} from "../../menus/protyle"; import {hideElements} from "../ui/hideElements"; import {genAssetHTML} from "../../asset/renderAssets"; import {unicode2Emoji} from "../../emoji"; import {avRender} from "../render/av/render"; import {isPaidUser} from "../../util/needSubscribe"; export const hintSlash = (key: string, protyle: IProtyle) => { const allList: IHintData[] = [{ filter: ["模版", "moban", "mb", "template"], value: Constants.ZWSP, html: `
${window.siyuan.languages.template}
`, }, { filter: ["挂件", "widget", "gj", "guajian"], value: Constants.ZWSP + 1, html: `
${window.siyuan.languages.widget}
`, }, { filter: ["资源", "assets", "zy", "ziyuan"], value: Constants.ZWSP + 2, html: `
${window.siyuan.languages.assets}
`, }, { filter: ["引用块", "yinyong", "yy", "block reference"], value: "((", html: `
${window.siyuan.languages.ref}((
`, }, { filter: ["嵌入块", "qianrukuai", "qrk", "embed block"], value: "{{", html: `
${window.siyuan.languages.blockEmbed}{{
`, }, { filter: ["ai chat"], value: Constants.ZWSP + 5, html: '
AI Chat
', }]; if (isPaidUser()) { allList.push({ filter: ["数据库", "属性视图", "shujuku", "shuxingshitu", "sjk", "sxst", "database", "attribute view"], value: '
', html: `
${window.siyuan.languages.database}
`, }); } [{ filter: ["文档", "子文档", "wendang", "wd", "ziwendang", "zwd", "xjwd"], value: Constants.ZWSP + 4, html: `
${window.siyuan.languages.newFile}${updateHotkeyTip(window.siyuan.config.keymap.general.newFile.custom)}
`, }, { value: "", html: "separator", }, { filter: ["yijibiaoti", "一级标题", "yjbt", "h1", "heading"], value: "# " + Lute.Caret, html: `
${window.siyuan.languages.heading1}${updateHotkeyTip(window.siyuan.config.keymap.editor.heading.heading1.custom)}
`, }, { filter: ["erjibiaoti", "二级标题", "ejbt", "h2", "heading"], value: "## " + Lute.Caret, html: `
${window.siyuan.languages.heading2}${updateHotkeyTip(window.siyuan.config.keymap.editor.heading.heading2.custom)}
`, }, { filter: ["sanjibiaoti", "三级标题", "sjbt", "h3", "heading"], value: "### " + Lute.Caret, html: `
${window.siyuan.languages.heading3}${updateHotkeyTip(window.siyuan.config.keymap.editor.heading.heading3.custom)}
`, }, { filter: ["sijibiaoti", "四级标题", "sjbt", "h4", "heading"], value: "#### " + Lute.Caret, html: `
${window.siyuan.languages.heading4}${updateHotkeyTip(window.siyuan.config.keymap.editor.heading.heading4.custom)}
`, }, { filter: ["wujibiaoti", "五级标题", "wjbt", "h5", "heading"], value: "##### " + Lute.Caret, html: `
${window.siyuan.languages.heading5}${updateHotkeyTip(window.siyuan.config.keymap.editor.heading.heading5.custom)}
`, }, { filter: ["liujibiaoti", "六级标题", "ljbt", "h6", "heading"], value: "###### " + Lute.Caret, html: `
${window.siyuan.languages.heading6}${updateHotkeyTip((window.siyuan.config.keymap.editor.heading.heading6.custom))}
`, }, { filter: ["无序列表", "wuxuliebiao", "wxlb", "unordered list"], value: "* " + Lute.Caret, html: `
${window.siyuan.languages.list}
`, }, { filter: ["有序列表", "youxuliebiao", "yxlb", "ordered list"], value: "1. " + Lute.Caret, html: `
${window.siyuan.languages["ordered-list"]}1. 
`, }, { filter: ["任务列表", "renwuliebiao", "rwlb", "task list", "todo list"], value: "* [ ] " + Lute.Caret, html: `
${window.siyuan.languages.check}${updateHotkeyTip((window.siyuan.config.keymap.editor.insert.check.custom))}
`, }, { filter: ["引述", "yinshu", "ys", "bq", "blockquote"], value: "> " + Lute.Caret, html: `
${window.siyuan.languages.quote}>
`, }, { filter: ["代码块", "daimakuai", "dmk", "code block"], value: "```", html: `
${window.siyuan.languages.code}\`\`\`Enter
`, }, { filter: ["表格", "biaoge", "bg", "table"], value: `| ${Lute.Caret} | | |\n| --- | --- | --- |\n| | | |\n| | | |`, html: `
${window.siyuan.languages.table}${updateHotkeyTip((window.siyuan.config.keymap.editor.insert.table.custom))}
`, }, { filter: ["分割线", "分隔线", "fengexian", "fgx", "divider", "thematic", "break"], value: "---", html: `
${window.siyuan.languages.line}---
`, }, { filter: ["数学公式块", "shuxuegongshikuai", "sxgsk", "math block"], value: "$$", html: `
${window.siyuan.languages.math}$$
`, }, { filter: ["html"], value: "
", html: '
HTML
', }, { value: "", html: "separator", }, { filter: ["表情", "biaoqing", "bq", "emoji"], value: "emoji", html: `
${window.siyuan.languages.emoji}:
`, }, { filter: ["链接", "lianjie", "lj", "link", "a"], value: "a", html: `
${window.siyuan.languages.link}${updateHotkeyTip((window.siyuan.config.keymap.editor.insert.link.custom))}
`, }, { filter: ["粗体", "cuti", "ct", "bold", "strong"], value: "strong", html: `
${window.siyuan.languages.bold}${updateHotkeyTip((window.siyuan.config.keymap.editor.insert.bold.custom))}
`, }, { filter: ["斜体", "xieti", "xt", "italic", "em"], value: "em", html: `
${window.siyuan.languages.italic}${updateHotkeyTip((window.siyuan.config.keymap.editor.insert.italic.custom))}
`, }, { filter: ["下划线", "xiahuaxian", "xhx", "underline"], value: "u", html: `
${window.siyuan.languages.underline}${updateHotkeyTip((window.siyuan.config.keymap.editor.insert.underline.custom))}
`, }, { filter: ["删除线", "shanchuxian", "scx", "strike"], value: "s", html: `
${window.siyuan.languages.strike}${updateHotkeyTip((window.siyuan.config.keymap.editor.insert.strike.custom))}
`, }, { filter: ["标记", "biaoji", "bj", "mark"], value: "mark", html: `
${window.siyuan.languages.mark}${updateHotkeyTip((window.siyuan.config.keymap.editor.insert.mark.custom))}
`, }, { filter: ["上标", "shangbiao", "sb", "superscript"], value: "sup", html: `
${window.siyuan.languages.sup}${updateHotkeyTip((window.siyuan.config.keymap.editor.insert.sup.custom))}
`, }, { filter: ["下标", "xiaobiao", "xb", "subscript"], value: "sub", html: `
${window.siyuan.languages.sub}${updateHotkeyTip((window.siyuan.config.keymap.editor.insert.sub.custom))}
`, }, { filter: ["标签", "biaoqian", "bq", "tag"], value: "tag", html: `
${window.siyuan.languages.tag}${updateHotkeyTip((window.siyuan.config.keymap.editor.insert.tag.custom))}
`, }, { filter: ["行内代码", "hangneidaima", "hndm", "inline code"], value: "code", html: `
${window.siyuan.languages["inline-code"]}${updateHotkeyTip((window.siyuan.config.keymap.editor.insert["inline-code"].custom))}
`, }, { filter: ["行内数学公式", "hangneishuxuegongshi", "hnsxgs", "inline math"], value: "inline-math", html: `
${window.siyuan.languages["inline-math"]}${updateHotkeyTip((window.siyuan.config.keymap.editor.insert["inline-math"].custom))}
`, }, { value: "", html: "separator", }, { filter: ["插入图片或文件", "upload", "上传", "crtphwj", "sc"], value: Constants.ZWSP + 3, html: `
${window.siyuan.languages.insertAsset}
`, }, { filter: ["iframe", "嵌入网址", "qianruwangzhan", "qrwz"], value: '', html: `
${window.siyuan.languages.insertIframeURL}
`, }, { filter: ["插入图片链接", "insert image link", "charutupianlianjie", "crtptp"], value: "![]()", html: `
${window.siyuan.languages.insertImgURL}
`, }, { filter: ["插入视频链接", "charushipinlianjie", "crsplj", "insert video url"], value: '', html: `
${window.siyuan.languages.insertVideoURL}
`, }, { filter: ["插入音频链接", "charuyinpinlianjie", "cryplj", "insert audio url"], value: '', html: `
${window.siyuan.languages.insertAudioURL}
`, }, { value: "", html: "separator", }, { filter: ["五线谱", "wuxianpu", "wxp", "staff"], value: "```abc\n```", html: `
ABC${window.siyuan.languages.staff}
`, }, { filter: ["图表", "tubiao", "tb", "chart"], value: "```echarts\n```", html: `
Chart${window.siyuan.languages.chart}
`, }, { filter: ["流程图", "liuchengtu", "lct", "flow chart"], value: "```flowchart\n```", html: '
FlowChartFlow Chart
', }, { filter: ["状态图", "zhuangtaitu", "ztt", "graph viz"], value: "```graphviz\n```", html: '
GraphvizGraph
', }, { filter: ["流程图", "时序图", "甘特图", "liuchengtu", "shixutu", "gantetu", "lct", "sxt", "gtt", "mermaid"], value: "```mermaid\n```", html: '
MermaidMermaid
', }, { filter: ["脑图", "naotu", "nt", "mind map"], value: "```mindmap\n```", html: `
Mind map${window.siyuan.languages.mindmap}
`, }, { filter: ["统一建模语言", "tongyijianmoyuyan", "tyjmyy", "plant uml"], value: "```plantuml\n```", html: '
PlantUMLUML
', }, { value: "", html: "separator", }, { filter: ["信息样式", "xinxiyangshi", "xxys", "info style"], value: `style${Constants.ZWSP}color: var(--b3-card-info-color);background-color: var(--b3-card-info-background);`, html: `
A
${window.siyuan.languages.infoStyle}
`, }, { filter: ["成功样式", "chenggongyangshi", "cgys", "success style"], value: `style${Constants.ZWSP}color: var(--b3-card-success-color);background-color: var(--b3-card-success-background);`, html: `
A
${window.siyuan.languages.successStyle}
`, }, { filter: ["警告样式", "jinggaoyangshi", "jgys", "warning style"], value: `style${Constants.ZWSP}color: var(--b3-card-warning-color);background-color: var(--b3-card-warning-background);`, html: `
A
${window.siyuan.languages.warningStyle}
`, }, { filter: ["错误样式", "cuowuyangshi", "cwys", "error style"], value: `style${Constants.ZWSP}color: var(--b3-card-error-color);background-color: var(--b3-card-error-background);`, html: `
A
${window.siyuan.languages.errorStyle}
`, }, { filter: ["移除样式", "yichuyangshi", "ycys", "remove style"], value: `style${Constants.ZWSP}`, html: `
A
${window.siyuan.languages.clearFontStyle}
`, }].forEach(item => { allList.push(item); }); allList.push({ value: "", html: "separator", }); let hasPlugin = false; protyle.app.plugins.forEach((plugin) => { plugin.protyleSlash.forEach(slash => { allList.push({ filter: slash.filter, value: `plugin${Constants.ZWSP}${plugin.name}${Constants.ZWSP}${slash.id}`, html: slash.html }); hasPlugin = true; }); }); if (!hasPlugin) { allList.pop(); } if (key === "") { return allList; } return allList.filter((item) => { if (!item.filter) { return false; } const match = item.filter.find((filter) => { if (filter.indexOf(key.toLowerCase()) > -1) { return true; } }); if (match) { return true; } else { return false; } }); }; export const hintTag = (key: string, protyle: IProtyle): IHintData[] => { protyle.hint.genLoading(protyle); fetchPost("/api/search/searchTag", { k: key, }, (response) => { const dataList: IHintData[] = []; let hasKey = false; response.data.tags.forEach((item: string) => { const value = item.replace(//g, "").replace(/<\/mark>/g, ""); dataList.push({ value: `#${value}#`, html: item, }); if (value === response.data.k) { hasKey = true; } }); if (response.data.k && !hasKey) { dataList.splice(0, 0, { value: `#${response.data.k}#`, html: `${window.siyuan.languages.new} ${escapeHtml(response.data.k)}`, }); if (dataList.length > 1) { dataList[1].focus = true; } } protyle.hint.genHTML(dataList, protyle, true, "hint"); }); return []; }; export const genHintItemHTML = (item: IBlock) => { let iconHTML; if (item.type === "NodeDocument" && item.ial.icon) { iconHTML = unicode2Emoji(item.ial.icon, "b3-list-item__graphic popover__block", true); iconHTML = iconHTML.replace('popover__block"', `popover__block" data-id="${item.id}"`); } else { iconHTML = ``; } let attrHTML = ""; if (item.name) { attrHTML += `${item.name}`; } if (item.alias) { attrHTML += `${item.alias}`; } if (item.memo) { attrHTML += `${item.memo}`; } if (attrHTML) { attrHTML = `
${attrHTML}
`; } return `${attrHTML}
${iconHTML} ${item.content}
${item.hPath}
`; }; export const hintRef = (key: string, protyle: IProtyle, source: THintSource): IHintData[] => { const nodeElement = hasClosestBlock(getEditorRange(protyle.wysiwyg.element).startContainer); protyle.hint.genLoading(protyle); fetchPost("/api/search/searchRefBlock", { k: key, id: nodeElement ? nodeElement.getAttribute("data-node-id") : protyle.block.parentID, beforeLen: Math.floor((Math.max(protyle.element.clientWidth / 2, 320) - 58) / 28.8), rootID: protyle.block.rootID, isSquareBrackets: ["[[", "【【"].includes(protyle.hint.splitChar) }, (response) => { const dataList: IHintData[] = []; if (response.data.newDoc) { const newFileName = Lute.UnEscapeHTMLStr(replaceFileName(response.data.k)); dataList.push({ value: source === "search" ? `((newFile "${newFileName}"${Constants.ZWSP}'${newFileName}${Lute.Caret}'))` : `((newFile '${newFileName}${Lute.Caret}'))`, html: `
${window.siyuan.languages.newFile} ${response.data.k}
`, }); } response.data.blocks.forEach((item: IBlock) => { let value = `${item.name || item.refText}`; if (source === "search") { value = `${key}${Constants.ZWSP}${item.name || item.refText}`; } dataList.push({ value, html: genHintItemHTML(item), }); }); if (source === "search") { protyle.hint.splitChar = "(("; protyle.hint.lastIndex = -1; } if (dataList.length === 0) { dataList.push({ value: "", html: window.siyuan.languages.emptyContent, }); } else if (response.data.newDoc && dataList.length > 1) { dataList[1].focus = true; } protyle.hint.genHTML(dataList, protyle, true, source); }); return []; }; export const hintEmbed = (key: string, protyle: IProtyle): IHintData[] => { if (key.endsWith("}}") || key.endsWith("」」")) { return []; } protyle.hint.genLoading(protyle); const nodeElement = hasClosestBlock(getEditorRange(protyle.wysiwyg.element).startContainer); fetchPost("/api/search/searchRefBlock", { k: key, beforeLen: Math.floor((Math.max(protyle.element.clientWidth / 2, 320) - 58) / 28.8), id: nodeElement ? nodeElement.getAttribute("data-node-id") : protyle.block.parentID, rootID: protyle.block.rootID, }, (response) => { const dataList: IHintData[] = []; response.data.blocks.forEach((item: IBlock) => { dataList.push({ value: `{{select * from blocks where id='${item.id}'}}`, html: genHintItemHTML(item), }); }); if (dataList.length === 0) { dataList.push({ value: "", html: window.siyuan.languages.emptyContent, }); } protyle.hint.genHTML(dataList, protyle, true, "hint"); }); return []; }; export const hintRenderTemplate = (value: string, protyle: IProtyle, nodeElement: Element) => { fetchPost("/api/template/render", { id: protyle.block.parentID, path: value }, (response) => { focusByRange(protyle.toolbar.range); const editElement = getContenteditableElement(nodeElement); if (editElement && editElement.textContent.trim() === "") { insertHTML(response.data.content, protyle, true); } else { insertHTML(response.data.content, protyle); } // https://github.com/siyuan-note/siyuan/issues/4488 protyle.wysiwyg.element.querySelectorAll('[status="temp"]').forEach(item => { item.remove(); }); blockRender(protyle, protyle.wysiwyg.element); processRender(protyle.wysiwyg.element); highlightRender(protyle.wysiwyg.element); avRender(protyle.wysiwyg.element, protyle); hideElements(["util"], protyle); }); }; export const hintRenderWidget = (value: string, protyle: IProtyle) => { focusByRange(protyle.toolbar.range); insertHTML(protyle.lute.SpinBlockDOM(``), protyle, true); hideElements(["util"], protyle); }; export const hintRenderAssets = (value: string, protyle: IProtyle) => { focusByRange(protyle.toolbar.range); const type = pathPosix().extname(value).toLowerCase(); const filename = value.startsWith("assets/") ? getAssetName(value) : value; insertHTML(genAssetHTML(type, value, filename, value.startsWith("assets/") ? filename + type : value), protyle); hideElements(["util"], protyle); }; export const hintMoveBlock = (pathString: string, sourceElements: Element[], protyle: IProtyle) => { if (pathString === "/") { return; } const parentID = getDisplayName(pathString, true, true); if (protyle.block.rootID === parentID) { return; } const doOperations: IOperation[] = []; let topSourceElement: Element; const parentElement = sourceElements[0].parentElement; let sideElement; sourceElements.forEach((item, index) => { if (index === sourceElements.length - 1 && // 动态加载过慢,导致 item 被移除 item.parentElement) { topSourceElement = getTopAloneElement(item); sideElement = topSourceElement.nextElementSibling || topSourceElement.previousElementSibling; if (topSourceElement.isSameNode(item)) { topSourceElement = undefined; } } doOperations.push({ action: "append", id: item.getAttribute("data-node-id"), parentID, }); item.remove(); }); // 删除空元素 if (topSourceElement) { doOperations.push({ action: "delete", id: topSourceElement.getAttribute("data-node-id"), }); topSourceElement.remove(); } else if (parentElement.classList.contains("list") && parentElement.getAttribute("data-subtype") === "o" && parentElement.childElementCount > 1) { updateListOrder(parentElement, 1); Array.from(parentElement.children).forEach((item) => { if (item.classList.contains("protyle-attr")) { return; } doOperations.push({ action: "update", id: item.getAttribute("data-node-id"), data: item.outerHTML }); }); } else if (protyle.block.showAll && parentElement.classList.contains("protyle-wysiwyg") && parentElement.childElementCount === 0) { setTimeout(() => { zoomOut({protyle, id: protyle.block.parent2ID, focusId: protyle.block.parent2ID}); }, Constants.TIMEOUT_INPUT * 2 + 100); } else if (parentElement.classList.contains("protyle-wysiwyg") && parentElement.innerHTML === "" && !hasClosestByClassName(parentElement, "block__edit", true) && protyle.block.id === protyle.block.rootID) { // 根文档原内容为空 const newId = Lute.NewNodeID(); const newElement = genEmptyElement(false, false, newId); doOperations.splice(0, 0, { action: "insert", id: newId, data: newElement.outerHTML, parentID: protyle.block.parentID }); parentElement.innerHTML = newElement.outerHTML; focusBlock(newElement); } else if (sideElement) { focusBlock(sideElement); } // 跨文档不支持撤销 transaction(protyle, doOperations); };