一、代码实现
啊 这次这个可能有一定的风险.
siyuan-noob 升级了一下之后,现在可以注册新的菜单类型了.
import { 注册自定义菜单类型 } from "siyuan-noob/customMenu/index.js";
import { getSelectionPosition } from "siyuan-noob/utilFront/range.js";
import {
hasClosestBlock,
hasClosestByTag,
} from "siyuan-noob/utilFront/hasClosest.js";
import { 注册表 } from "siyuan-noob/commonStruct";
import 核心api from "siyuan-noob/utilKernel/kernelApi";
这里注册一个新的菜单类型,就叫行内唤起菜单吧.@style{}
let 旧目标节点
let 行内唤起菜单;
行内唤起菜单 = 注册自定义菜单类型(
//决定了菜单的标题
"快速插入",
//决定了菜单监听的事件类型有哪些
["input","beforeinput", "keydown" ,"keypress"],
//判定函数,决定了菜单是否显示
(event) => {
if (getSelection().rangeCount>0&&getSelection().getRangeAt(0).commonAncestorContainer.textContent) {
行内唤起菜单.菜单状态.当前块元素 = hasClosestBlock(
getSelection().getRangeAt(0).commonAncestorContainer
);
if (行内唤起菜单.菜单状态.当前块元素) {
行内唤起菜单.菜单状态.当前块id =
行内唤起菜单.菜单状态.当前块元素.getAttribute("data-node-id");
行内唤起菜单.菜单状态.当前节点 =
getSelection().getRangeAt(0).commonAncestorContainer;
行内唤起菜单.菜单状态.rangePosition = getSelectionPosition(event.target);
行内唤起菜单.菜单状态.range = getSelection().getRangeAt(0).cloneRange();
行内唤起菜单.菜单状态.当前行内元素 = hasClosestByTag(
getSelection().getRangeAt(0).commonAncestorContainer,
"SPAN"
);
}
if(event.code&&(event.code==="ArrowLeft"||event.code==="ArrowRight")
&&旧目标节点 === 行内唤起菜单.菜单状态.当前节点
&&!window.siyuan.menus.menu.element.querySelector('.b3-menu__submenu')
){
window.siyuan.menus.menu.remove()
return
}
旧目标节点 = 行内唤起菜单.菜单状态.当前节点
return (
行内唤起菜单.菜单状态.当前块元素 &&
行内唤起菜单.待渲染菜单项目数组 &&
行内唤起菜单.待渲染菜单项目数组[0]
);
} else {
return false;
}
},
//坐标计算函数,决定了菜单浮现的位置
(event) => {
return {
x: 行内唤起菜单.菜单状态.rangePosition.left + 18,
y: 行内唤起菜单.菜单状态.rangePosition.top + 18,
};
},
//监听对象,决定了对哪个元素进行监听
document,
//事件配置,决定了事件监听器的options选项
true
);
//筛选函数,这个函数决定了单个选项会不会渲染
行内唤起菜单.筛选函数 = (item) => {
return item.行内关键词判断函数(行内唤起菜单.菜单状态);
};
//这里使得这个菜单的项目可以在外部注册
let 行内唤起菜单注册表 = new 注册表("自定义关键词菜单");
行内唤起菜单.菜单注册表 = 行内唤起菜单注册表.items;
//这里说是其实是通过render函数批量注册了一堆菜单项目
先来一个插入笔记内关键词的看看
let 词表 = [];
//这样在其他地方也可以通过同一个名字的注册表来增加关键词
let 关键词注册表 = new 注册表("关键词注册表");
行内唤起菜单注册表.注册({
id: "快捷关键词输入",
点击回调函数: async (e) => {
let 当前块id = 行内唤起菜单.菜单状态.当前块id;
},
行内关键词判断函数: (菜单状态) => {
if (
行内唤起菜单.菜单状态.当前节点
//下面这两行代码的意思是只有输入@@的时候才会显示出那个输入框,我这里先注释掉了,需要的可以加上
//&&
//行内唤起菜单.菜单状态.当前节点.textContent.indexOf("@@") >= 0
) {
let textContent = 行内唤起菜单.菜单状态.当前节点.textContent;
if (!textContent.trim()) {
return;
}
词表 = [];
//这个函数会返回关键词注册表里面的所有项目
let 已注册关键词表 = 关键词注册表.list();
已注册关键词表.forEach((注册项) => {
let flag;
if (注册项.唤起词列表) {
//如果唤起词列表里面有的话
注册项.唤起词列表.forEach((唤起词) => {
if (唤起词 === textContent.trim()) {
flag = true;
}
if (唤起词.indexOf(textContent.split("@@").pop()) > -1) {
flag = true;
}
});
}
//如果id包含的话
if (注册项.id.indexOf(textContent.trim()) > -1) {
flag = true;
}
if (注册项.id.indexOf(textContent.split("@@").pop()) > -1) {
flag = true;
}
//如果内容命中了的话
if (注册项.内容.indexOf(textContent.trim()) > -1) {
flag = true;
}
if (注册项.内容.indexOf(textContent.split("@@").pop()) > -1) {
flag = true;
}
flag ? 词表.push(注册项.内容) : null;
});
//默认把当前窗口所有行内元素全都加上去
let list = document.querySelectorAll(
".protyle-wysiwyg div[data-node-id] span:not(.hljs span)"
);
list.forEach((el) => {
let text = el.innerText.trim();
if (
textContent.trim() !== "" &&
text &&
text.indexOf(textContent.split("@@").pop()) > -1 &&
el.className !== "hljs-string" &&
text !== textContent.split("@@").pop()
) {
词表.push(text);
}
});
词表 = Array.from(new Set(词表));
return textContent.trim().length >= 2 && 词表.length;
}
},
render: () => {
let html = "";
let div = document.createElement("div");
词表.forEach((spanText) => {
html += `
<button style="width: calc(100% - 16px)"
class="b3-menu__item">
<span class="b3-menu__label">${Lute.EscapeHTMLStr(spanText.trim())}</span>
</button>
`;
});
词表 = [];
div.innerHTML = html;
div.querySelectorAll("button").forEach((button) => {
button.addEventListener("click", (e) => {
let value = hasClosestByTag(e.target, "BUTTON").querySelector(
"span"
).innerText;
if (行内唤起菜单.菜单状态.当前节点.textContent.indexOf("@@") > -1) {
行内唤起菜单.菜单状态.当前节点.textContent =
行内唤起菜单.菜单状态.当前节点.textContent.slice(
0,
行内唤起菜单.菜单状态.当前节点.textContent.lastIndexOf("@@")
) + value;
} else {
行内唤起菜单.菜单状态.当前节点.textContent = value;
}
var range = document.createRange();
range.selectNodeContents(行内唤起菜单.菜单状态.当前节点);
range.collapse(false);
const selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
window.siyuan.menus.menu.remove();
});
});
setTimeout(() => {
div.querySelectorAll("button").forEach((button) => {
window.siyuan.menus.menu.append(button);
if (
window.siyuan.menus.menu.element.getBoundingClientRect().bottom >=
window.innerHeight
) {
window.siyuan.menus.menu.element.style.top =
window.siyuan.menus.menu.element.getBoundingClientRect().top -
window.siyuan.menus.menu.element.getBoundingClientRect().bottom +
window.innerHeight +
"px";
}
});
div.remove();
});
return div;
},
});
然后我们来试试增加一些关键词,先把所有文档以及带有命名和别名的块加进去,限制 1000 个,反正你自己改吧.
//这个你其实可以自己改掉
关键词注册表.注册({
id: "思源笔记",
唤起词: ["siyuan", "sy"],
内容: "思源笔记还可以啊",
});
//这里是通过sql将所有带有命名和别名的块以及文档块都弄进去了
核心api.sql(
{
stmt: `select * from blocks where name or alias or type = 'd' limit 1000`,
},
"",
(data) => {
data.forEach((block) => {
let 唤起词 = [];
if (block.alias) {
唤起词 = 唤起词.concat(block.alias.split(","));
}
if (block.name) {
唤起词.push(block.name);
}
if (block.title) {
唤起词.push(block.title);
}
关键词注册表.注册({
id: block.id,
唤起词: 唤起词,
内容: block.content,
});
});
}
);
为了测试它的表现,我们再弄一个新的菜单项,这个菜单项的作用就是在你输入 @style 之后,如果再输入一段内容就可以设置当前块的样式
//注册块样式
let styleData;
行内唤起菜单注册表.注册({
id: "快捷块样式",
行内关键词判断函数: (菜单状态) => {
if (
菜单状态.当前节点 &&
(菜单状态.当前节点.wholeText||菜单状态.当前节点.textContent).indexOf("@style") >-1
) {
let wholeText = 菜单状态.当前节点.wholeText;
try {
styleData = new Function(`"use strict";return (${wholeText.split("@style").pop()})`)();
return true;
} catch (e) {
return false;
}
}
},
文字: "设置为块样式",
图标: "#iconFormat",
点击回调函数: () => {
let element =行内唤起菜单.菜单状态.当前节点.parentElement
Object.getOwnPropertyNames(styleData).forEach((ruler) => {
行内唤起菜单.菜单状态.当前块元素.style[ruler] = styleData[ruler];
行内唤起菜单.菜单状态.当前节点.parentElement.innerText=行内唤起菜单.菜单状态.当前节点.wholeText.slice(
0,
行内唤起菜单.菜单状态.当前节点.wholeText.lastIndexOf("@style")
);
element.insertAdjacentHTML("beforeend", "<wbr>");
});
var range = document.createRange();
let wbr=element.querySelector('wbr')
range.selectNodeContents(element.querySelector('wbr'));
range.collapse(false);
const selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
wbr.remove()
window.siyuan.menus.menu.remove();
},
});
比如说 @style{color:'red'}就可以当前块的样式设置成红色字体.
或者这种形式也可以 @style(()={return {color:'red'}})()
安装方式
这还是一个可运行代码片段,可以使用这个代码片段来进行安装,话说有老哥应该已经弄了一个运行代码的插件来着
思源笔记折腾记录 - 运行你的笔记 - 链滴 (ld246.com)
直接剪藏这篇笔记之后,用运行到代码片段功能安装到代码片段就可以了。
使用效果
来我来打个样
如果这玩意对你有用可以去爱发电给我买杯咖啡
leolee9086 正在创作一些简单的技术教程和小工具,以及设计方面内容 | 爱发电 (afdian.net)
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于