js
// 功能:给编辑器增加标尺
// see https://ld246.com/article/1725849206361
(()=>{
//////////// 配置区 ////////////////////
// 标尺的间隔大小,默认50 (50代表50px高度)
const rulerGaps = 50;
//////////// 主逻辑区 ////////////////////
addRulerStyle();
whenElementExist('.layout__center').then(async element => {
// 等待笔记列表加载完毕
await sleep(40);
// 防止死循环
let locking = false;
// 监听编辑器加载事件
observeDomChange(element, async (mutation) => {
// 添加标尺本身则不监听(暂无用到)
// if(mutation.addedNodes && Array.from(mutation.addedNodes)?.some(item=>item.classList?.contains("ruler"))){
// return;
// }
if (mutation.target.classList?.contains("protyle-wysiwyg")) {
const editor = mutation.target;
if(editor && editor.closest){
// 等待编辑器加载完毕
const protyle = editor.closest(".protyle");
if(protyle.dataset.loading !== 'finished'){
await whenElementExist(()=>editor?.closest(".protyle") === 'finished');
}
// 编辑器加载完毕(包括编辑器内容被修改)
//console.log('editor loaded');
// TODO 这里写你要进行的操作
if(locking) return;
locking = true;
//console.log('runed');
//添加标尺
addRuler(editor, protyle);
setTimeout(()=>{
locking = false;
// 加载时,防止编辑器未完全加载,再执行一次
if(!document.querySelector('#ruler-'+protyle.dataset.id)) addRuler(editor, protyle);
}, 1000)
}
}
});
});
//////////// 函数辑区 ////////////////////
async function addRuler(editor, protyle) {
let ruler = document.querySelector('#ruler-'+protyle.dataset.id);
const first = !ruler;
let containner;
if(!ruler) {
containner = document.createElement('div');
containner.className = 'protyle-ruler';
ruler = document.createElement('div');
ruler.id = 'ruler-'+protyle.dataset.id;
ruler.className = 'ruler';
ruler.contentEditable = false;
}
if(first) await sleep(500);
// 根据 editor 的高度计算需要多少个刻度线
const numberOfTicks = Math.floor(editor.clientHeight / rulerGaps);
//ruler.style.height = (rulerGaps * numberOfTicks) + 'px';
if(ruler.children.length > numberOfTicks) {
while (ruler.children.length > numberOfTicks) {
ruler.removeChild(ruler.children[ruler.children.length-1]);
}
return;
}
for (let i = ruler.children.length; i <= numberOfTicks; i++) {
const tick = document.createElement('div');
tick.className = 'tick';
const tickNumber = document.createElement('span');
tickNumber.className = 'tick-number';
tickNumber.textContent = i; // 显示间隔数
const tickLine = document.createElement('div');
tickLine.className = 'tick-line';
tick.appendChild(tickNumber);
tick.appendChild(tickLine);
ruler.appendChild(tick);
}
if(first) {
//editor.prepend(ruler);
containner.appendChild(ruler);
editor.parentElement.insertBefore(containner, editor);
}
}
function addRulerStyle() {
if(document.querySelector("#rulerStyle")) return;
const style = document.createElement('style');
style.id = 'rulerStyle';
style.textContent = `
.protyle-ruler {
position:relative;
.ruler {
position: absolute;
left: 0;
top: 0;
width: 30px; /* 宽度包括数字和短横线 */
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: flex-start; /* 让内容靠左对齐 */
border-right: 0.5px solid var(--b3-border-color);
font-size:13px;
cursor: default;
user-select: none;
/*overflow:hidden;*/
}
.tick {
position: relative;
width: 100%;
height: ${rulerGaps||50}px; /* 每个刻度线的高度 */
display: flex;
justify-content: flex-end;
align-items: center;
}
.tick-line {
width: 8px;
height: 1px;
/*background-color: var(--b3-theme-on-surface-light);*/
background-color: var(--b3-border-color);
}
.tick-number {
writing-mode: vertical-rl; /* 文字垂直书写 */
transform: rotate(180deg); /* 旋转文字 */
margin-right: 1px; /* 与数字之间留点空隙 */
color: var(--b3-theme-on-surface-light);
}
}
`
document.head.appendChild(style);
}
// 观察元素被添加
function observeDomChange(selector, callback) {
// 定义一个回调函数,当DOM发生变化时调用
const onChange = function(mutationsList, observer) {
for (const mutation of mutationsList) {
if (mutation.type === 'childList') {
callback(mutation);
}
}
};
// 创建一个观察器实例,并传入回调函数
const observer = new MutationObserver(onChange);
// 配置观察选项:指定需要观察哪些变动
const config = { attributes: false, childList: true, subtree: true };
// 获取目标节点
const targetNode = typeof selector === 'string' ? document.querySelector(selector) : selector;
// 如果目标节点存在,则开始观察
if (targetNode) {
observer.observe(targetNode, config);
}
// 返回一个函数,用于停止观察
return () => {
observer.disconnect();
};
}
// 延迟执行
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// 等待元素渲染完成后执行
function whenElementExist(selector) {
return new Promise(resolve => {
const checkForElement = () => {
let element = null;
if (typeof selector === 'function') {
element = selector();
} else {
element = document.querySelector(selector);
}
if (element) {
resolve(element);
} else {
requestAnimationFrame(checkForElement);
}
};
checkForElement();
});
}
})();