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: `
",
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}
`,
}, {
filter: ["粗体", "cuti", "ct", "bold", "strong"],
value: "strong",
html: `
${window.siyuan.languages.bold}
`,
}, {
filter: ["斜体", "xieti", "xt", "italic", "em"],
value: "em",
html: `
${window.siyuan.languages.italic}
`,
}, {
filter: ["下划线", "xiahuaxian", "xhx", "underline"],
value: "u",
html: `
${window.siyuan.languages.underline}
`,
}, {
filter: ["删除线", "shanchuxian", "scx", "strike"],
value: "s",
html: `
${window.siyuan.languages.strike}
`,
}, {
filter: ["标记", "biaoji", "bj", "mark"],
value: "mark",
html: `
${window.siyuan.languages.mark}
`,
}, {
filter: ["上标", "shangbiao", "sb", "superscript"],
value: "sup",
html: `
${window.siyuan.languages.sup}
`,
}, {
filter: ["下标", "xiaobiao", "xb", "subscript"],
value: "sub",
html: `
${window.siyuan.languages.sub}
`,
}, {
filter: ["标签", "biaoqian", "bq", "tag"],
value: "tag",
html: `
${window.siyuan.languages.tag}
`,
}, {
filter: ["行内代码", "hangneidaima", "hndm", "inline code"],
value: "code",
html: `
${window.siyuan.languages["inline-code"]}
`,
}, {
filter: ["行内数学公式", "hangneishuxuegongshi", "hnsxgs", "inline math"],
value: "inline-math",
html: `
${window.siyuan.languages["inline-math"]}
`,
}, {
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);
};