Wilson@思源

目 录

实现可手动修改数据库主键引用块的标题

前言
众所周知,思源数据库主键引用块或文档的标题是和块紧密关联的,无法自定义修改。
看到小伙伴们有自定义标题的需求,于是根据此回复帖子的原理,实现了该功能。
该功能可在不同的数据库下自定义不同的标题。
效果演示
代码
js
// 功能:修改数据库主键引用块的标题 // 版本:0.0.2 // 更新记录 // 0.0.1 初始版本,实现了可手动修改数据库主键引用块的标题 // 0.0.2 修复多个数据库切换焦点失效问题 // 问题反馈:https://ld246.com/article/1731215224093 // 使用方法:把代码放到思源js代码片段即可,修改方式和普通字段一样,点击编辑即可。 // 删除自定义标题方法:在输入框输入空值即可删除,或进入引用块属性面板,删除dbid-开头的自定义属性即可,可参考下面的原理帖子。 // 原理:https://ld246.com/article/1730621536283/comment/1731085168853?r=wilsons#comments (()=>{ let documentListened = false; observeElementCreation( document.body, '.av__row:not(.av__row--header) [data-dtype="block"]:has([data-type="block-ref"])', async cell => { // 获取数据库id const av = cell.closest('[data-av-type="table"][data-av-id]'); const avId = av?.dataset.avId; const text = cell.querySelector('.av__celltext--ref'); if(text) text.dataset.text = text.textContent; let blockId; if(avId) { // 获取块属性 const block = cell.querySelector('[data-type="block-ref"]'); blockId = block?.dataset.id; if(blockId){ let blockAttrs = {}; try { blockAttrs = await fetchSyncPost('/api/attr/getBlockAttrs', {"id": blockId}); } catch(e) { console.log(e); } if(blockAttrs.code === 0 && blockAttrs.data) { blockAttrs = blockAttrs.data; const customText = blockAttrs['custom-dbid-' + avId]; if(customText){ // 修改文本名称 if(text) text.textContent = customText; } } } } // 单元格单击事件,动态添加输入框 cell.addEventListener('click', (event) => { // 如果引用块标题则返回 if(event.target.tagName === 'SPAN' && event.target.classList.contains('av__celltext--ref')) { return; } event.stopPropagation(); // 移除多余的激活单元格样式 const selectCells = document.querySelectorAll('.av__cell.av__cell--active.av__cell--select'); selectCells.forEach(item => { if(item !== cell) { item.classList.remove('av__cell--active'); item.classList.remove('av__cell--select'); } }); // 添加输入框并获取焦点 addAvMask(cell); const inputElement = document.querySelector('.av__mask--rename .b3-text-field'); inputElement.select(); inputElement.focus(); // 输入框点击事件 inputElement.addEventListener('click', (event) => { event.stopPropagation(); }); // 输入框按键事件 inputElement.addEventListener('keydown', (event) => { if (event.isComposing) { return; } if(event.key === "Escape"|| event.key === "Tab" || (event.key === "Enter" && !event.shiftKey && isNotCtrl(event))){ event.preventDefault(); event.stopPropagation(); updateCellValue(blockId, avId, inputElement, text, cell, av, event); } }); // 输入框关闭事件 inputElement.parentElement.addEventListener('click', (event) => { updateCellValue(blockId, avId, inputElement, text, cell, av); }); }); // 更新按钮阻止冒泡 cell.querySelector('[data-type="block-more"]')?.addEventListener('click', (event) => { event.stopPropagation(); }); // 监听document按键事件(仅在第一个单元格被添加时创建一次) if(!documentListened) { documentListened = true; document.addEventListener('keydown', (event) => { if (event.isComposing) { return; } // 当按回车时打开输入框 if (event.key === "Enter" && !event.shiftKey && isNotCtrl(event)) { const inputElement = document.querySelector('.av__mask--rename .b3-text-field'); if(!inputElement) { const selectCell = document.querySelector('.av__cell.av__cell--active.av__cell--select[data-dtype="block"]:has([data-type="block-ref"])'); if(selectCell) selectCell.click(); } event.preventDefault(); event.stopPropagation(); } }); } } ); // 更新单元格 async function updateCellValue(blockId, avId, inputElement, text, cell, av, event) { if(blockId && avId && inputElement) { let value = inputElement.value.trim(); // 修改块属性,空值删除,注意这里的key不能用custom-av开头 await fetchSyncPost('/api/attr/setBlockAttrs', { "id": blockId, "attrs": { ["custom-dbid-" + avId]: value } }); // 如果value为空,尝试获取块的内容 text = text || cell.querySelector('.av__celltext--ref'); if(!value && text) { value = text.dataset.text || ''; } // 更新文本值 if(text) text.textContent = value; // 删除输入框 inputElement.parentElement.remove(); // 修复av__cell--active样式丢失问题 cell.classList.add('av__cell--active'); // 解决多个表格切换焦点失效问题 av?.querySelector('.av__header .av__title')?.focus(); // 解决tab,方向键无法改变焦点问题 pressKeyboard({key: 'Escape', keyCode: 27}, document); // 模拟触发tab键 if(event?.key === "Tab"){ pressKeyboard({key: 'Tab', keyCode: 9}); } } } // 添加输入框 function addAvMask(cell) { // see https://github.com/siyuan-note/siyuan/blob/487c48427ae8e3b840d209047cc316273c7a3931/app/src/protyle/render/av/cell.ts#L363 let html = ""; let cellRect = cell.getBoundingClientRect(); let height = cellRect.height; const contentElement = cell.closest('.protyle-content'); if (contentElement) { const contentRect = contentElement.getBoundingClientRect(); if (cellRect.bottom > contentRect.bottom) { height = contentRect.bottom - cellRect.top; } } const style = `style="padding-top: 6.5px;position:absolute;left: ${cellRect.left}px;top: ${cellRect.top}px;width:${Math.max(cellRect.width, 25)}px;height: ${height}px"`; html = ``; document.body.insertAdjacentHTML("beforeend", `
${html}
`); } // 是否非ctrl键true是,false不是 function isNotCtrl(event) { if (!event.metaKey && !event.ctrlKey) { return true; } return false; } // 模拟按键 function pressKeyboard(keyInit, element) { element = element || getProtyleEl(); keyInit["bubbles"] = true; let keydownEvent = new KeyboardEvent('keydown', keyInit); element?.dispatchEvent(keydownEvent); let keyUpEvent = new KeyboardEvent('keyup', keyInit); element?.dispatchEvent(keyUpEvent); } // 获取当前文档编辑器元素 function getProtyleEl() { return document.querySelector('[data-type="wnd"].layout__wnd--active .protyle:not(.fn__none) .protyle-wysiwyg.protyle-wysiwyg--attr'); } // 请求api async function fetchSyncPost(url, data, returnType = 'json') { const init = { method: "POST", }; if (data) { if (data instanceof FormData) { init.body = data; } else { init.body = JSON.stringify(data); } } try { const res = await fetch(url, init); const res2 = returnType === 'json' ? await res.json() : await res.text(); return res2; } catch(e) { console.log(e); return returnType === 'json' ? {code:e.code||1, msg: e.message||"", data: null} : ""; } } // 监听元素被创建 function observeElementCreation(parentNode, selector, onElementCreated) { // 配置观察器选项 const config = { childList: true, // 观察直接子节点的添加和移除 subtree: true // 观察所有后代节点 }; // 当检测到变动时执行的回调函数 const callback = function(mutationsList, observer) { for (let mutation of mutationsList) { if (mutation.type === 'childList') { mutation.addedNodes.forEach(node => { if (node.nodeType === Node.ELEMENT_NODE) { // 使用 querySelectorAll 查找所有符合条件的新元素 const elements = node.querySelectorAll(selector); elements.forEach(element => { onElementCreated(element); // 调用外部提供的回调函数 }); } }); } } }; // 创建一个观察器实例并传入回调函数 const observer = new MutationObserver(callback); // 开始观察目标节点 observer.observe(parentNode, config); // 返回一个函数来停止观察 return () => observer.disconnect(); } // 统计代码 (function tongji(enable) { if(!enable) return; !function(p){"use strict";!function(t){var s=window,e=document,i=p,c="".concat("https:"===e.location.protocol?"https://":"http://","sdk.51.la/js-sdk-pro.min.js"),n=e.createElement("script"),r=e.getElementsByTagName("script")[0];n.type="text/javascript",n.setAttribute("charset","UTF-8"),n.async=!0,n.src=c,n.id="LA_COLLECT",i.d=n;var o=function(){s.LA.ids.push(i)};s.LA?s.LA.ids&&o():(s.LA=p,s.LA.ids=[],o()),r.parentNode.insertBefore(n,r)}()}({id:"KctUAV1SBR5tEp7H",ck:"KctUAV1SBR5tEp7H"}); })(1); })()
备份代码地址:点这里查看
使用方法
把上面的代码放到思源 js 代码片段即可。
数据库主键修改方式和普通字段一样,点击编辑即可。
删除自定义标题方法
在输入框输入空值即可删除或进入引用块属性面板,删除 dbid-开头的自定义属性即可,可参考这个原理帖子

免责声明

请严格测试后谨慎使用,由此造成的任何后果均与脚本与作者无关。