976 lines
31 KiB
JavaScript
976 lines
31 KiB
JavaScript
const { Plugin } = require("siyuan");
|
||
const clientApi = require("siyuan");
|
||
let 核心api;
|
||
let path;
|
||
let 思源工作空间;
|
||
let importDep;
|
||
let 当前选项按钮;
|
||
let that;
|
||
let template;
|
||
|
||
class headingIndex extends Plugin {
|
||
onload() {
|
||
this.selfURL = `/plugins/${this.constructor.name}`;
|
||
this.dataPath = `/data/storage/petal/${this.constructor.name}`;
|
||
that = this;
|
||
this.设置字典 = {};
|
||
this.当前默认设置 = [];
|
||
this.已提示块 = [];
|
||
this.生成顶栏();
|
||
this.增加编辑器生成菜单();
|
||
this.添加页面();
|
||
this.初始化();
|
||
}
|
||
增加编辑器生成菜单() {
|
||
this.eventBus.on("click-editortitleicon", (e) => {
|
||
let { menu, data } = e.detail;
|
||
|
||
menu.addItem({
|
||
icon: "iconOrderedList",
|
||
label: this.i18n.设置序号生成方式,
|
||
submenu: [
|
||
{
|
||
icon: "iconOrderedList",
|
||
label: this.i18n.刷新序号,
|
||
click: () => {
|
||
生成标题序号(that.设置字典, data.id);
|
||
},
|
||
},
|
||
{
|
||
icon: "iconEdit",
|
||
label: this.i18n.写入序号,
|
||
click: async () => {
|
||
clientApi.confirm(
|
||
"⚠️",
|
||
this.i18n["生成可能需要很长时间,是否确认继续?"],
|
||
async () => {
|
||
await 生成文档内标题序号(data.id, that.设置字典, true);
|
||
}
|
||
);
|
||
},
|
||
},
|
||
],
|
||
});
|
||
|
||
let _submenu = [];
|
||
Object.getOwnPropertyNames(this.设置字典).forEach((item) => {
|
||
if (item == "当前全局配置") {
|
||
return;
|
||
}
|
||
_submenu.push({
|
||
label: this.i18n.使用序号类型 + item,
|
||
click: async () => {
|
||
await 核心api.setBlockAttrs({
|
||
id: data.id,
|
||
attrs: { "custom-index-scheme": item },
|
||
});
|
||
},
|
||
});
|
||
});
|
||
menu.addItem({
|
||
icon: "iconOrderedList",
|
||
label: this.i18n.选择序号类型,
|
||
submenu: _submenu,
|
||
});
|
||
menu.addItem({
|
||
icon: "iconRefresh",
|
||
label:(this.自动刷新标题?"结束":"开始")+this.i18n.自动刷新标题,
|
||
click:()=>{
|
||
this.自动刷新标题=!this.自动刷新标题
|
||
}
|
||
})
|
||
});
|
||
}
|
||
添加页面() {
|
||
let plugin = this;
|
||
this.customTab = this.addTab({
|
||
type: "editor",
|
||
init() {
|
||
this.data.content.forEach((标题样式, i) => {
|
||
console.log(i);
|
||
this.element.insertAdjacentHTML(
|
||
"beforeend",
|
||
`<label class="fn__flex b3-label">
|
||
<div class="fn__flex-1">
|
||
h${i + 1}
|
||
<div class="b3-label__text">${
|
||
plugin.i18n[数字转中文(i + 1) + "级标题编号样式"]
|
||
}</div>
|
||
</div>
|
||
<span class="fn__space"></span>
|
||
<input class="b3-text-field fn__flex-center" data-level="${i}" >
|
||
</label>`
|
||
);
|
||
this.element.querySelector(`[data-level="${i}"]`).value = 标题样式;
|
||
this.element
|
||
.querySelector(`[data-level="${i}"]`)
|
||
.addEventListener("change", async (e) => {
|
||
this.data.content[i] = e.target.value;
|
||
});
|
||
});
|
||
|
||
this.element.insertAdjacentHTML(
|
||
"beforeend",
|
||
`
|
||
<label class="fn__flex b3-label config__item">
|
||
<div class="fn__flex-1">
|
||
保存配置文件
|
||
<div class="b3-label__text">${this.data.name}.json</div>
|
||
</div>
|
||
<div class="fn__space"></div>
|
||
<div class="fn__size200 config__item-line fn__flex-center">
|
||
<button class="b3-button b3-button--outline fn__size200 fn__flex-center" >
|
||
确定
|
||
</button>
|
||
</div>
|
||
</label>
|
||
`
|
||
);
|
||
this.element.querySelectorAll(`button`).forEach((button) => {
|
||
button.addEventListener("click", async (e) => {
|
||
if (e.target.dataSet && e.target.dataSet.enable) {
|
||
await 思源工作空间.writeFile(
|
||
JSON.stringify(
|
||
{ name: this.data.name, content: this.data.content },
|
||
undefined,
|
||
2
|
||
),
|
||
path.join(plugin.dataPath, "lastValue.json")
|
||
);
|
||
}
|
||
await 思源工作空间.writeFile(
|
||
JSON.stringify(this.data.content, undefined, 2),
|
||
path.join(plugin.dataPath, this.data.name + ".json")
|
||
);
|
||
await plugin.初始化();
|
||
e.stopPropagation();
|
||
});
|
||
});
|
||
},
|
||
async destroy() {
|
||
await 思源工作空间.writeFile(
|
||
JSON.stringify(this.data.content, undefined, 2),
|
||
path.join(plugin.dataPath, this.data.name + ".json")
|
||
);
|
||
await plugin.初始化();
|
||
},
|
||
});
|
||
}
|
||
onunload() {
|
||
this.停止监听编辑();
|
||
this.样式元素.remove();
|
||
}
|
||
停止监听编辑() {
|
||
this.eventBus.off("ws-main", this.ws监听器);
|
||
}
|
||
async 初始化() {
|
||
path = (await import(this.selfURL + "/polyfills/path.js"))["default"];
|
||
importDep = async (moduleName) => {
|
||
return await import(path.join(this.selfURL, moduleName));
|
||
};
|
||
核心api = (await importDep("./polyfills/kernelApi.js"))["default"];
|
||
思源工作空间 = (await importDep("./polyfills/fs.js"))["default"];
|
||
await this.覆盖默认设置();
|
||
await this.获取全部设置();
|
||
document
|
||
.querySelectorAll("#headingIndexStyle")
|
||
.forEach((el) => el.remove());
|
||
this.样式元素 = document.createElement("style");
|
||
this.样式元素.setAttribute("id", "headingIndexStyle");
|
||
this.样式元素.textContent = `
|
||
.protyle-wysiwyg [data-node-id].li[data-subtype="t"] .protyle-action.protyle-action--task:before {
|
||
content:var(--custom-index) ;
|
||
}
|
||
.protyle-wysiwyg [data-type="NodeHeading"] [contenteditable]:before{
|
||
content:var(--custom-index);
|
||
}
|
||
.sy__outline [data-node-id] .b3-list-item__text:before{
|
||
content:var(--custom-index);
|
||
}
|
||
`;
|
||
let scriptEl = document.createElement("script");
|
||
scriptEl.textContent = await (
|
||
await fetch(path.join(this.selfURL, "static", "art-template-web.js"))
|
||
).text();
|
||
let iframe = document.createElement("iframe");
|
||
iframe.style.display = "none";
|
||
iframe.setAttribute("href", "about:blank");
|
||
document.body.appendChild(iframe);
|
||
iframe.contentDocument.head.appendChild(scriptEl);
|
||
template = iframe.contentWindow.template;
|
||
document.head.appendChild(this.样式元素);
|
||
console.log(this.设置字典);
|
||
生成标题序号(this.设置字典);
|
||
this.eventBus.on("ws-main", this.ws监听器);
|
||
|
||
}
|
||
生成顶栏() {
|
||
this.顶栏按钮 = this.addTopBar({
|
||
icon: "iconOrderedList",
|
||
title: this.i18n.addTopBarIcon,
|
||
position: "right",
|
||
callback: () => {
|
||
this.创建菜单();
|
||
},
|
||
});
|
||
this.顶栏按钮.addEventListener("contextmenu", async () => {
|
||
if (!window.isSecureContext) {
|
||
return;
|
||
}
|
||
try {
|
||
let 文件数组 = await window.showOpenFilePicker({
|
||
types: [
|
||
{
|
||
description: "配置文件",
|
||
accept: {
|
||
"application/javascript": [".js"],
|
||
"application/json": [".json"],
|
||
},
|
||
},
|
||
],
|
||
excludeAcceptAllOption: true,
|
||
multiple: true,
|
||
});
|
||
for await (let 文件句柄 of 文件数组) {
|
||
let name = 文件句柄.name;
|
||
let file = await 文件句柄.getFile();
|
||
await 思源工作空间.writeFile(file, path.join(this.dataPath, name));
|
||
}
|
||
await this.初始化();
|
||
} catch (e) {
|
||
console.error(e);
|
||
}
|
||
});
|
||
}
|
||
创建菜单() {
|
||
const menu = new clientApi.Menu("topBarSample", () => {});
|
||
let 配置文件名数组 = Object.getOwnPropertyNames(this.设置字典);
|
||
for (let i = 0, len = 配置文件名数组.length; i < len; i++) {
|
||
let name = 配置文件名数组[i];
|
||
try {
|
||
this.添加配置文件选择菜单项(menu, name);
|
||
} catch (e) {
|
||
console.error(e);
|
||
}
|
||
}
|
||
menu.addSeparator();
|
||
menu.addItem({
|
||
icon: "iconAdd",
|
||
label: this.i18n.添加配置文件,
|
||
click: () => {
|
||
let Dialog;
|
||
Dialog = new clientApi.Dialog({
|
||
title: "输入文件名,留空取消",
|
||
content: `<div class="fn__flex"><input class="fn__flex-1 b3-text-field b3-filter" placeholder="输文件名,留空取消"></div>`,
|
||
width: "400px",
|
||
height: "96px",
|
||
destroyCallback: async () => {
|
||
let name = Dialog.element.querySelector("input").value;
|
||
if (name) {
|
||
let reg = new RegExp('[\\\\/:*?"<>|]');
|
||
if (reg.test(name)) {
|
||
return;
|
||
}
|
||
let 新配置文件路径 = path.join(this.dataPath, name + ".json");
|
||
await 思源工作空间.writeFile(
|
||
'["","","","","",""]',
|
||
新配置文件路径
|
||
);
|
||
}
|
||
await this.初始化();
|
||
},
|
||
});
|
||
},
|
||
});
|
||
menu.addItem({
|
||
icon: "iconRefresh",
|
||
label:(this.自动刷新标题?"结束":"开始")+this.i18n.自动刷新标题,
|
||
click:()=>{
|
||
this.自动刷新标题=!this.自动刷新标题
|
||
}
|
||
})
|
||
menu.open(this.顶栏按钮.getBoundingClientRect());
|
||
}
|
||
添加配置文件选择菜单项(menu, name) {
|
||
if (name == "当前全局配置") {
|
||
return;
|
||
}
|
||
let content = this.设置字典[name];
|
||
let element = document.createElement("button");
|
||
if (this.设置字典.当前全局配置.name == name) {
|
||
element.style.backgroundColor = "var(--b3-card-success-background)";
|
||
当前选项按钮 = element;
|
||
}
|
||
element.setAttribute("class", "b3-menu__item");
|
||
element.innerHTML += `<span class="b3-menu__label">${name
|
||
.split("/")
|
||
.pop()}</span>`;
|
||
element.innerHTML +=
|
||
'<svg class="b3-menu__icon"><use xlink:href="#iconEdit"></use></svg>';
|
||
menu.menu.append(element);
|
||
element.addEventListener("click", (e) => {
|
||
if (e.target.tagName == "svg" || e.target.tagName == "use") {
|
||
this.打开编辑页面(content, name);
|
||
return;
|
||
}
|
||
this.设置字典.当前全局配置.name = name;
|
||
this.设置字典.当前全局配置.content = content;
|
||
生成标题序号(this.设置字典);
|
||
this.saveData(
|
||
"lastValues.json",
|
||
JSON.stringify({ name: name, content: content })
|
||
);
|
||
menu.close();
|
||
});
|
||
}
|
||
async 打开编辑页面(content, name) {
|
||
const tab = clientApi.openTab({
|
||
app: this.app,
|
||
custom: {
|
||
icon: "iconHeading",
|
||
title: name,
|
||
data: {
|
||
content: content,
|
||
name: name,
|
||
},
|
||
fn: this.customTab,
|
||
},
|
||
});
|
||
console.log(tab);
|
||
}
|
||
async 覆盖默认设置() {
|
||
let jsContent = await (await fetch(this.selfURL + "/实例设置1.js")).text();
|
||
let jsonContent = await (
|
||
await fetch(this.selfURL + "/实例设置2.json")
|
||
).json();
|
||
await 思源工作空间.writeFile(
|
||
jsContent,
|
||
path.join(this.dataPath, "实例设置1.js")
|
||
);
|
||
if (
|
||
!(await 思源工作空间.exists(path.join(this.dataPath, "实例设置2.json")))
|
||
) {
|
||
await 思源工作空间.writeFile(
|
||
JSON.stringify(jsonContent),
|
||
path.join(this.dataPath, "实例设置2.json")
|
||
);
|
||
}
|
||
if (
|
||
await 思源工作空间.exists(path.join(this.dataPath, "lastValues.json"))
|
||
) {
|
||
try {
|
||
this.设置字典.当前全局配置 = JSON.parse(
|
||
await 思源工作空间.readFile(
|
||
path.join(this.dataPath, "lastValues.json")
|
||
)
|
||
);
|
||
} catch (e) {
|
||
console.error(e);
|
||
}
|
||
} else {
|
||
this.设置字典.当前全局配置 = {};
|
||
}
|
||
}
|
||
async 获取全部设置() {
|
||
let 全部配置 = await 思源工作空间.readDir(this.dataPath);
|
||
for await (let 配置项 of 全部配置) {
|
||
try {
|
||
if (!(配置项.isDir || 配置项.name == "lastValues.json")) {
|
||
let 配置路径 = path.join(this.dataPath, 配置项.name);
|
||
let 配置内容 = 配置项.name.endsWith(".js")
|
||
? await 读取js配置(配置路径)
|
||
: await 读取json配置(配置路径);
|
||
if (!配置内容 instanceof Array) {
|
||
console.warn(配置项.name + "没有导出数组");
|
||
} else if (配置内容.length < 6) {
|
||
配置项.name + "没有配置全部标题序号";
|
||
}
|
||
this.设置字典[配置项.name.split(".")[0]] = 配置内容;
|
||
if (
|
||
this.设置字典.当前全局配置 &&
|
||
this.设置字典.当前全局配置.name == 配置项.name.split(".")[0]
|
||
) {
|
||
this.设置字典.当前全局配置.content = 配置内容;
|
||
}
|
||
}
|
||
} catch (e) {
|
||
console.error(`配置文件${配置项.name}加载错误`, e);
|
||
}
|
||
}
|
||
}
|
||
debounceTimer=null
|
||
async ws监听器(detail) {
|
||
if(this.自动刷新标题){
|
||
this.debounceTimer?clearTimeout(this.debounceTimer):null;
|
||
this.debounceTimer = setTimeout(async () => {
|
||
await 生成标题序号(that.设置字典);
|
||
}, 500); // 300ms为防抖时间,可以根据实际情况调整
|
||
|
||
}
|
||
}
|
||
}
|
||
module.exports = headingIndex;
|
||
async function 读取js配置(配置路径) {
|
||
let jsContent = (await 思源工作空间.readFile(配置路径)).toString();
|
||
let blob = new Blob(
|
||
[数字转中文.toString() + "\n" + jsContent + '\n//# sourceURL="' + 配置路径],
|
||
{ type: "application/javascript" }
|
||
);
|
||
let moduleURL = URL.createObjectURL(blob);
|
||
|
||
return (await import(moduleURL)).default;
|
||
}
|
||
async function 读取json配置(配置路径) {
|
||
let jsonContent = await 思源工作空间.readFile(配置路径);
|
||
return JSON.parse(jsonContent);
|
||
}
|
||
let 已提示块 = {};
|
||
async function 生成标题序号(序号设置字典, 文档id) {
|
||
if (文档id) {
|
||
await 生成文档内标题序号(文档id, 序号设置字典);
|
||
}
|
||
let 文档面包屑数组 = document.querySelectorAll(
|
||
".protyle-breadcrumb__bar span:first-child[data-node-id]"
|
||
);
|
||
文档面包屑数组.forEach(async (文档面包屑元素) => {
|
||
let 文档id = 文档面包屑元素.getAttribute("data-node-id");
|
||
try {
|
||
let 预取内容 = await 核心api.getDoc({ id: 文档id, size: 1 });
|
||
if (已提示块[文档id]) {
|
||
return;
|
||
}
|
||
if (预取内容.blockCount > 1024) {
|
||
let 文档信息 = await 核心api.getDocInfo({ id: 文档id });
|
||
核心api.pushMsg({
|
||
msg: `${文档信息.name}内块数量为${预取内容.blockCount},超过阈值,请手动生成`,
|
||
});
|
||
已提示块[文档id] = true;
|
||
return;
|
||
}
|
||
await 生成文档内标题序号(文档id, 序号设置字典);
|
||
} catch (e) {
|
||
console.warn(e);
|
||
if (当前选项按钮 && 当前选项按钮.parentElement) {
|
||
当前选项按钮.style.backgroundColor = "var(--b3-card-error-background)";
|
||
当前选项按钮.parentElement.insertAdjacentHTML(
|
||
"beforeend",
|
||
Lute.EscapeHTMLStr(e.toString())
|
||
);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
async function 生成文档内标题序号(文档id, 序号设置字典, 写入序号) {
|
||
let 文档内容 = await 核心api.getDoc({ id: 文档id, size: 102400 });
|
||
let 文档信息 = await 核心api.getDocInfo({ id: 文档id });
|
||
/*if(文档内容.content.lenth>100000){
|
||
return
|
||
}*/
|
||
let 当前序号设置 = 序号设置字典.当前全局配置.content;
|
||
if (文档信息.ial && 文档信息.ial["custom-index-scheme"] === "null") {
|
||
return;
|
||
}
|
||
if (文档信息.ial && 文档信息.ial["custom-index-scheme"]) {
|
||
当前序号设置 =
|
||
序号设置字典[文档信息.ial["custom-index-scheme"]] || 当前序号设置;
|
||
}
|
||
if (!当前序号设置) {
|
||
return;
|
||
}
|
||
let parser = new DOMParser();
|
||
let 临时元素 = parser.parseFromString(文档内容.content, "text/html").body;
|
||
//console.log(临时元素)
|
||
// 临时元素.innerHTML = 文档内容.content;
|
||
let 标题元素数组 = 临时元素.querySelectorAll(
|
||
'[data-type="NodeHeading"]:not( [data-type="NodeBlockQueryEmbed"] div)'
|
||
);
|
||
let 计数器 = [0, 0, 0, 0, 0, 0];
|
||
let 上一个标题级别 = 1;
|
||
for (let i = 0; i < 标题元素数组.length; ++i) {
|
||
let 标题元素 = 标题元素数组[i];
|
||
|
||
if (!标题元素数组[i].querySelector("[contenteditable]")) {
|
||
return;
|
||
}
|
||
if (!标题元素数组[i].querySelector("[contenteditable]").innerText) {
|
||
let 标题id = 标题元素数组[i].getAttribute("data-node-id");
|
||
document
|
||
.querySelectorAll(`.protyle-wysiwyg div[data-node-id='${标题id}']`)
|
||
.forEach((一级标题元素) => {
|
||
一级标题元素
|
||
.querySelector("[contenteditable]")
|
||
.style.removeProperty("--custom-index");
|
||
});
|
||
return;
|
||
}
|
||
let 当前标题级别 = parseInt(
|
||
标题元素数组[i].getAttribute("data-subtype").replace("h", "")
|
||
);
|
||
if (当前标题级别 <= 上一个标题级别) {
|
||
for (let j = 0; j < 计数器.length; ++j) {
|
||
if (j + 1 > 当前标题级别) {
|
||
计数器[j] = 0;
|
||
}
|
||
}
|
||
}
|
||
计数器[当前标题级别 - 1] += 1;
|
||
let 标题id = 标题元素数组[i].getAttribute("data-node-id");
|
||
if (!当前序号设置[当前标题级别 - 1]) {
|
||
document
|
||
.querySelectorAll(`.protyle-wysiwyg div[data-node-id='${标题id}']`)
|
||
.forEach(async (标题元素) => {
|
||
let 内容元素 = 标题元素.querySelector("[contenteditable]");
|
||
内容元素.setAttribute("style", `--custom-index:""`);
|
||
});
|
||
document
|
||
.querySelectorAll(`.sy__outline [data-node-id=""]`)
|
||
.forEach(async (大纲项目) => {
|
||
大纲项目.setAttribute("style", `--custom-index:""`);
|
||
});
|
||
}
|
||
if (当前序号设置[当前标题级别 - 1]) {
|
||
let 当前序号;
|
||
if (当前序号设置[当前标题级别 - 1] instanceof Function) {
|
||
当前序号 = 当前序号设置[当前标题级别 - 1](计数器[当前标题级别 - 1]);
|
||
} else {
|
||
function h(级别) {
|
||
let num = 计数器[级别 - 1];
|
||
let obj = () => {
|
||
return num;
|
||
};
|
||
|
||
obj.num = num;
|
||
obj.ch = 数字转中文(num);
|
||
obj.roman = numToRoman(num);
|
||
obj.en = numToEnglish(num);
|
||
obj.CH = 数字转中文(num, true);
|
||
obj.abc = 数字转字母(num, false);
|
||
obj.ABC = 数字转字母(num, true);
|
||
obj.enth = numToEnglish(num, false);
|
||
obj.ru = toRussian(num);
|
||
obj.toString = () => {
|
||
return Obj.num;
|
||
};
|
||
document.querySelectorAll("script").forEach((scriptEl) => {
|
||
try {
|
||
let indexFormatters;
|
||
if (
|
||
scriptEl.indexFormatters &&
|
||
scriptEl.indexFormatters instanceof Array
|
||
) {
|
||
indexFormatters = scriptEl.indexFormatters;
|
||
}
|
||
if (
|
||
scriptEl.序号格式化函数组 &&
|
||
scriptEl.序号格式化函数组 instanceof Array
|
||
) {
|
||
indexFormatters = scriptEl.序号格式化函数组;
|
||
}
|
||
if(!indexFormatters){
|
||
return
|
||
}
|
||
indexFormatters.forEach((fn) => {
|
||
if (
|
||
fn.formatter instanceof Function &&
|
||
fn.name
|
||
) {
|
||
obj[fn.name] = fn.formatter(num,obj,标题元素.dataset.nodeId);
|
||
}
|
||
if (
|
||
fn.格式化函数 instanceof Function &&
|
||
fn.名称
|
||
) {
|
||
obj[fn.name] = fn.格式化函数(num,obj,标题元素.dataset.nodeId);
|
||
}
|
||
});
|
||
} catch (e) {
|
||
console.warn("标题序号定义错误", scriptEl, e);
|
||
}
|
||
});
|
||
return obj;
|
||
}
|
||
|
||
template.defaults.rules[1].test =
|
||
/{([@#]?)[ \t]*(\/?)([\w\W]*?)[ \t]*}/;
|
||
let string = 当前序号设置[当前标题级别 - 1];
|
||
let render = template.compile(string);
|
||
当前序号 = render({
|
||
h1: h(1),
|
||
h2: h(2),
|
||
h3: h(3),
|
||
h4: h(4),
|
||
h5: h(5),
|
||
h6: h(6),
|
||
});
|
||
}
|
||
if (写入序号) {
|
||
let 旧标题序号元素 = 标题元素.querySelector(
|
||
'span[style~="--custom-index:true"]'
|
||
);
|
||
if (旧标题序号元素) {
|
||
旧标题序号元素.remove();
|
||
}
|
||
标题元素
|
||
.querySelector('[contenteditable="true"]')
|
||
.insertAdjacentHTML(
|
||
"afterBegin",
|
||
`<span style="--custom-index:true">${当前序号}</span>`
|
||
);
|
||
await 核心api.updateBlock({
|
||
dataType: "dom",
|
||
data: 标题元素.outerHTML,
|
||
id: 标题id,
|
||
});
|
||
document
|
||
.querySelectorAll(`.protyle-wysiwyg div[data-node-id='${标题id}']`)
|
||
.forEach(async (标题元素) => {
|
||
let 内容元素 = 标题元素.querySelector("[contenteditable]");
|
||
内容元素.setAttribute("style", ``);
|
||
});
|
||
document
|
||
.querySelectorAll(`.sy__outline [data-node-id="${标题id}"]`)
|
||
.forEach(async (大纲项目) => {
|
||
大纲项目.setAttribute("style", ``);
|
||
});
|
||
} else {
|
||
document
|
||
.querySelectorAll(`.protyle-wysiwyg div[data-node-id='${标题id}']`)
|
||
.forEach(async (标题元素) => {
|
||
let 内容元素 = 标题元素.querySelector("[contenteditable]");
|
||
内容元素.setAttribute("style", `--custom-index:"${当前序号}"`);
|
||
});
|
||
document
|
||
.querySelectorAll(`.sy__outline [data-node-id="${标题id}"]`)
|
||
.forEach(async (大纲项目) => {
|
||
大纲项目.setAttribute("style", `--custom-index:"${当前序号}"`);
|
||
});
|
||
}
|
||
上一个标题级别 = 当前标题级别 + 0;
|
||
}
|
||
}
|
||
}
|
||
|
||
//作者:houyhea
|
||
//链接:https://juejin.cn/post/6844903473255809038
|
||
//来源:稀土掘金
|
||
//著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
|
||
function 数字转中文(digit, 大写) {
|
||
digit = typeof digit === "number" ? String(digit) : digit;
|
||
let zh = ["零", "一", "二", "三", "四", "五", "六", "七", "八", "九"];
|
||
let unit = ["千", "百", "十", ""];
|
||
if (大写) {
|
||
zh = ["零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖"];
|
||
unit = ["仟", "佰", "拾", ""];
|
||
}
|
||
const quot = [
|
||
"万",
|
||
"亿",
|
||
"兆",
|
||
"京",
|
||
"垓",
|
||
"秭",
|
||
"穰",
|
||
"沟",
|
||
"涧",
|
||
"正",
|
||
"载",
|
||
"极",
|
||
"恒河沙",
|
||
"阿僧祗",
|
||
"那由他",
|
||
"不可思议",
|
||
"无量",
|
||
"大数",
|
||
];
|
||
|
||
let breakLen = Math.ceil(digit.length / 4);
|
||
let notBreakSegment = digit.length % 4 || 4;
|
||
let segment;
|
||
let zeroFlag = [],
|
||
allZeroFlag = [];
|
||
let result = "";
|
||
|
||
while (breakLen > 0) {
|
||
if (!result) {
|
||
// 第一次执行
|
||
segment = digit.slice(0, notBreakSegment);
|
||
let segmentLen = segment.length;
|
||
for (let i = 0; i < segmentLen; i++) {
|
||
if (segment[i] != 0) {
|
||
if (zeroFlag.length > 0) {
|
||
result += "零" + zh[segment[i]] + unit[4 - segmentLen + i];
|
||
// 判断是否需要加上 quot 单位
|
||
if (i === segmentLen - 1 && breakLen > 1) {
|
||
result += quot[breakLen - 2];
|
||
}
|
||
zeroFlag.length = 0;
|
||
} else {
|
||
result += zh[segment[i]] + unit[4 - segmentLen + i];
|
||
if (i === segmentLen - 1 && breakLen > 1) {
|
||
result += quot[breakLen - 2];
|
||
}
|
||
}
|
||
} else {
|
||
// 处理为 0 的情形
|
||
if (segmentLen == 1) {
|
||
result += zh[segment[i]];
|
||
break;
|
||
}
|
||
zeroFlag.push(segment[i]);
|
||
continue;
|
||
}
|
||
}
|
||
} else {
|
||
segment = digit.slice(notBreakSegment, notBreakSegment + 4);
|
||
notBreakSegment += 4;
|
||
|
||
for (let j = 0; j < segment.length; j++) {
|
||
if (segment[j] != 0) {
|
||
if (zeroFlag.length > 0) {
|
||
// 第一次执行zeroFlag长度不为0,说明上一个分区最后有0待处理
|
||
if (j === 0) {
|
||
result += quot[breakLen - 1] + zh[segment[j]] + unit[j];
|
||
} else {
|
||
result += "零" + zh[segment[j]] + unit[j];
|
||
}
|
||
zeroFlag.length = 0;
|
||
} else {
|
||
result += zh[segment[j]] + unit[j];
|
||
}
|
||
// 判断是否需要加上 quot 单位
|
||
if (j === segment.length - 1 && breakLen > 1) {
|
||
result += quot[breakLen - 2];
|
||
}
|
||
} else {
|
||
// 第一次执行如果zeroFlag长度不为0, 且上一划分不全为0
|
||
if (j === 0 && zeroFlag.length > 0 && allZeroFlag.length === 0) {
|
||
result += quot[breakLen - 1];
|
||
zeroFlag.length = 0;
|
||
zeroFlag.push(segment[j]);
|
||
} else if (allZeroFlag.length > 0) {
|
||
// 执行到最后
|
||
if (breakLen == 1) {
|
||
result += "";
|
||
} else {
|
||
zeroFlag.length = 0;
|
||
}
|
||
} else {
|
||
zeroFlag.push(segment[j]);
|
||
}
|
||
|
||
if (
|
||
j === segment.length - 1 &&
|
||
zeroFlag.length === 4 &&
|
||
breakLen !== 1
|
||
) {
|
||
// 如果执行到末尾
|
||
if (breakLen === 1) {
|
||
allZeroFlag.length = 0;
|
||
zeroFlag.length = 0;
|
||
result += quot[breakLen - 1];
|
||
} else {
|
||
allZeroFlag.push(segment[j]);
|
||
}
|
||
}
|
||
continue;
|
||
}
|
||
}
|
||
--breakLen;
|
||
}
|
||
return result;
|
||
}
|
||
}
|
||
|
||
//转换为罗马数字
|
||
function numToRoman(num) {
|
||
const romanNumMap = [
|
||
["I", "IV", "V", "IX"],
|
||
["X", "XL", "L", "XC"],
|
||
["C", "CD", "D", "CM"],
|
||
["M"]
|
||
];
|
||
|
||
let romanNum = "";
|
||
let digits = num.toString().split('').reverse();
|
||
for (let i = 0; i < digits.length; i++) {
|
||
let digit = parseInt(digits[i]);
|
||
if (digit <= 3) {
|
||
romanNum = romanNumMap[i][0].repeat(digit) + romanNum;
|
||
} else if (digit === 4) {
|
||
romanNum = romanNumMap[i][1] + romanNum;
|
||
} else if (digit <= 8) {
|
||
romanNum = romanNumMap[i][2] + romanNumMap[i][0].repeat(digit - 5) + romanNum;
|
||
} else if (digit === 9) {
|
||
romanNum = romanNumMap[i][3] + romanNum;
|
||
}
|
||
}
|
||
|
||
return romanNum;
|
||
}
|
||
/*function numToRoman(num) {
|
||
const romanNumMap = {
|
||
0: "",
|
||
1: "I",
|
||
2: "II",
|
||
3: "III",
|
||
4: "IV",
|
||
5: "V",
|
||
6: "VI",
|
||
7: "VII",
|
||
8: "VIII",
|
||
9: "IX",
|
||
};
|
||
// 拆分数字字符串
|
||
let numStr = num.toString().split("");
|
||
|
||
let romanNum = "";
|
||
for (let i = 0; i < numStr.length; i++) {
|
||
let digit = numStr[i];
|
||
let nextDigit = numStr[i + 1];
|
||
|
||
// 特殊情况4和9处理
|
||
if (+digit === 4 && +nextDigit === 1) {
|
||
romanNum += "IV";
|
||
i++;
|
||
} else if (+digit === 9 && +nextDigit === 1) {
|
||
romanNum += "IX";
|
||
i++;
|
||
} else {
|
||
// 如果大于5,拆分处理
|
||
if (+digit > 5) {
|
||
romanNum += romanNumMap[5];
|
||
for (let j = 1; j < +digit - 5; j++) {
|
||
romanNum += romanNumMap[1];
|
||
}
|
||
} else {
|
||
romanNum += romanNumMap[+digit];
|
||
}
|
||
}
|
||
}
|
||
|
||
return romanNum;
|
||
}*/
|
||
const englishNumMap = {
|
||
0: "zero",
|
||
1: "one",
|
||
2: "two",
|
||
3: "three",
|
||
4: "four",
|
||
5: "five",
|
||
6: "six",
|
||
7: "seven",
|
||
8: "eight",
|
||
9: "nine",
|
||
10: "ten",
|
||
11: "eleven",
|
||
12: "twelve",
|
||
13: "thirteen",
|
||
14: "fourteen",
|
||
15: "fifteen",
|
||
16: "sixteen",
|
||
17: "seventeen",
|
||
18: "eighteen",
|
||
19: "nineteen",
|
||
20: "twenty",
|
||
30: "thirty",
|
||
40: "forty",
|
||
50: "fifty",
|
||
60: "sixty",
|
||
70: "seventy",
|
||
80: "eighty",
|
||
90: "ninety",
|
||
};
|
||
|
||
function numToEnglish(num) {
|
||
let englishNum = "";
|
||
|
||
if (num === 0) return englishNumMap[0];
|
||
|
||
if (num < 20) return englishNumMap[num]; // 1-19
|
||
|
||
if (num < 100) {
|
||
// 20-99
|
||
englishNum += englishNumMap[Math.floor(num / 10) * 10];
|
||
englishNum += num % 10 === 0 ? "" : " " + numToEnglish(num % 10);
|
||
} else if (num < 1000) {
|
||
// 100-999
|
||
englishNum += englishNumMap[Math.floor(num / 100)] + " hundred";
|
||
if (num % 100 > 0) englishNum += " and " + numToEnglish(num % 100);
|
||
} else if (num < 1e6) {
|
||
// 1000-999999
|
||
englishNum += numToEnglish(Math.floor(num / 1000)) + " thousand";
|
||
if (num % 1000 > 0) englishNum += " " + numToEnglish(num % 1000);
|
||
} else if (num < 1e9) {
|
||
// 1e6-999999999
|
||
englishNum += numToEnglish(Math.floor(num / 1e6)) + " million";
|
||
if (num % 1e6 > 0) englishNum += " " + numToEnglish(num % 1e6);
|
||
}
|
||
|
||
return englishNum.trim();
|
||
}
|
||
function 数字转字母(num, upper) {
|
||
if (num <= 26) {
|
||
return upper
|
||
? String.fromCharCode(64 + num)
|
||
: String.fromCharCode(64 + num).toLowerCase();
|
||
} else {
|
||
return num;
|
||
}
|
||
}
|
||
let russianNames = [
|
||
"нуль",
|
||
"один",
|
||
"два",
|
||
"три",
|
||
"четыре",
|
||
"пять",
|
||
"шесть",
|
||
"семь",
|
||
"восемь",
|
||
"девять",
|
||
"десять",
|
||
"одиннадцать",
|
||
"двенадцать",
|
||
"тринадцать",
|
||
"четырнадцать",
|
||
"пятнадцать",
|
||
"шестнадцать",
|
||
"семнадцать",
|
||
"восемнадцать",
|
||
"девятнадцать",
|
||
"двадцать",
|
||
"двадцать один",
|
||
"двадцать два",
|
||
"двадцать три",
|
||
"двадцать четыре",
|
||
"двадцать пять",
|
||
"двадцать шесть",
|
||
"двадцать семь",
|
||
"двадцать восемь",
|
||
"двадцать девять",
|
||
"тридцать",
|
||
"сорок",
|
||
"пятьдесят",
|
||
"шестьдесят",
|
||
"семьдесят",
|
||
"восемьдесят",
|
||
"девяносто",
|
||
];
|
||
function toRussian(n) {
|
||
if (n < 0) return "минус " + toRussian(-n);
|
||
if (n < 20) return russianNames[n];
|
||
|
||
let hundreds = Math.floor(n / 100);
|
||
let tens = Math.floor((n % 100) / 10);
|
||
let ones = n % 10;
|
||
|
||
let result = "";
|
||
if (hundreds) result += russianNames[hundreds] + " сто ";
|
||
if (tens || ones) {
|
||
result += russianNames[tens * 10];
|
||
if (ones) result += " " + russianNames[ones];
|
||
}
|
||
return result;
|
||
}
|