[js 片段] 发布时只显示单篇文档_release_v1.0
功能介绍
-
点击分享按钮, 复制 url 到剪切板, 黑色是分享单篇的按钮, 蓝色的是分享单篇及所有子文档的按钮
-
可以直接通过 url 访问单篇文档 或 单篇及所有子文档
- 单篇 url 示例:
http://127.0.0.1:6808/stage/build/desktop/?id=20250125151959-7as5mvq
- 单篇及所有子文档 示例:
http://127.0.0.1:6808/stage/build/desktop/?id=20250125151959-7as5mvq&sub_file=true
- 单篇 url 示例:
重要
重要! 重要! 重要!
要手动修改 cfg 下的 host_ip
host_ip 获取方法: 思源设置-> 发布-> 服务访问地址(随便挑一个)
免责说明
-
脚本核心思路是将各种元素隐藏, 来达到 好像被限制了的效果, 实际上并不能完全限制
比如: 可以通过全局搜索, 搜之后打开文档; 通过手动修改页面 css 访问 等如果介意, 请不要食用
-
测得不多, 有 bug 欢迎反馈
/*
### [js片段] 发布时只显示单篇文档_release_v1.0
#### 功能介绍
1. 点击分享按钮, 复制url到剪切板, 黑色是分享单篇的按钮, 蓝色的是分享单篇及所有子文档的按钮
2. 可以直接通过url访问单篇文档 或 单篇及所有子文档
* 单篇url 示例: `http://127.0.0.1:6808/stage/build/desktop/?id=20250125151959-7as5mvq`
* 单篇及所有子文档 示例: `http://127.0.0.1:6808/stage/build/desktop/?id=20250125151959-7as5mvq&sub_file=true`
#### 重要
**重要! 重要! 重要!**
要手动修改 cfg下的host_ip
host_ip获取方法: 思源设置->发布->服务访问地址(随便挑一个)
#### 免责说明
1. 脚本核心思路是将各种元素隐藏, 来达到 好像被限制了的效果, 实际上并不能完全限制
比如: 可以通过全局搜索, 搜之后打开文档; 通过手动修改页面css访问 等
如果介意, 请不要食用
2. 测得不多, 有bug欢迎反馈
*/
(() => {
/***************************自主配置begin**************************************/
// 修改配置后, 需要刷新页面(设置->快捷键->刷新), 否则会有问题
const cfg = {
forbidden_root_file_access: true, // 禁止访问根目录
forbidden_other_file_access: true, // 禁止访问其他文件
host_ip: "127.0.0.1:6808", //
};
/***************************自主配置end****************************************/
// 生成唯一ID用于日志标识
const SESSION_ID = '单篇发布js_' + Date.now();
function mlog(...args) {
const now = new Date();
const hours = String(now.getHours()).padStart(2, '0'); // 获取小时并补零
const minutes = String(now.getMinutes()).padStart(2, '0'); // 获取分钟并补零
const seconds = String(now.getSeconds()).padStart(2, '0'); // 获取秒数并补零
const milliseconds = String(now.getMilliseconds()).padStart(3, '0'); // 获取毫秒并补零
const timeString = `${hours}:${minutes}:${seconds}.${milliseconds}`; // 形成 hh:mm:ss.SSS 格式
console.log(`[${SESSION_ID}] [${timeString}]:`, ...args);
}
function while_exist(flag_func, func_cb, max_cnt = 0) {
let cnt = 0;
let initInterval = setInterval(() => {
cnt++;
mlog("cnt: ", cnt)
const flag = flag_func()
if (flag) {
clearInterval(initInterval);
func_cb(flag);
}
if (cnt >= max_cnt && max_cnt != 0) {
// 超次数退出
clearInterval(initInterval);
}
}, 200);
}
async function sendPushMessageUrl(url, msg) {
mlog(msg)
return fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
msg: msg,
timeout: 2000 // 如果不传 timeout,默认值为 7000
}),
})
.then((response) => {
if (response.ok) {
mlog('响应数据:' + response.text());
}
else {
throw new Error("Failed to get file content");
}
})
.catch((error) => {
mlog('请求失败:', error);
});
};
async function sendPushMessage(msg) {
sendPushMessageUrl("/api/notification/pushMsg", msg);
}
async function sendErrorPushMessage(msg) {
sendPushMessageUrl("/api/notification/pushErrMsg", msg);
}
function hide_ele(selector_str) {
const style = document.createElement('style');
style.textContent = `${selector_str} { display: none; }`;
// 将 <style> 添加到 <head> 中
document.head.appendChild(style);
}
function set_ele_display(selector_str, val) {
const ele = document.querySelector(selector_str)
if (ele) {
ele.style.display = val;
}
}
function handle_forbidden_other_file() {
// 禁止访问其他文件, 就是把书签和标签隐藏
if (!cfg.forbidden_other_file_access) return;
mlog("隐藏标签等按钮, 防止通过这些按钮访问其他文件")
const basic_selector_list = [
'[data-type="bookmark"]' , // 书签
'span[data-type="tag"]' , // 标签
'[data-type="graph"]' , // 关系图
'[data-type="globalGraph"]', // 全局关系图
'[data-type="backlink"]' , // 反链
'#barCommand' , // 命令面板
'#barSearch' , // 全局搜索
'[data-type="doc"]' , // 文档菜单
'.layout__center [data-type="wnd"]>.fn__flex', // 页签
]
basic_selector_list.forEach(selector_str => {
hide_ele(selector_str)
})
}
// 禁止访问根目录, 就是把文档树隐藏
function handle_forbidden_root_file() {
if (cfg.forbidden_root_file_access) {
mlog("隐藏文档树, 防止访问")
hide_ele('#layouts')
}
}
// 处理发布单篇文件
function handle_share_web_page_one_file() {
mlog('发布单篇文件, 隐藏文档树')
// 隐藏文档树按钮和文档树
hide_ele('[data-type="file"]')
set_ele_display('.layout__dockl', 'none')
// 监听大纲按钮
const outline_ele = document.querySelector('[data-type="outline"]')
if (!outline_ele) return;
const outline_click_cb = (event) => {
// 隐藏文档树的时候, 将这个元素隐藏了, 回导致大纲也无法显示
// 监听到大纲按钮时, 将这个元素恢复一下, 然后取消监听
set_ele_display('.layout__dockl', 'flex');
outline_ele.removeEventListener("click", outline_click_cb, true);
};
outline_ele.addEventListener("click", outline_click_cb, true);
}
// 找到当前打开的文档的li元素
function find_file_ele_li_open_now() {
document.querySelector('[data-type="focus"]')?.click();
return document.querySelector('.sy__file li.b3-list-item--focus');
}
// 处理发布单篇及子文档
function handle_share_web_page_sub_file(li_ele) {
if (!li_ele) return;
document.querySelectorAll('.sy__file>.fn__flex-1>ul li').forEach(li_node => {
// ul.remove();
if (li_node != li_ele) {
li_node.style.display = 'none';
}
})
// 隐藏定位按钮, 折叠按钮
hide_ele('[data-type="focus"]')
hide_ele('[data-type="collapse"]')
// 设置折叠按钮为关
li_ele.querySelector('span.b3-list-item__toggle--hl>svg.b3-list-item__arrow--open')?.parentElement.click()
}
// 处理单篇发布功能
function handle_share_web_page(url) {
// 禁止访问其他文件
handle_forbidden_other_file();
if (!url.includes('?id=') && !url.includes('&id=')) {
// 禁止访问根目录
handle_forbidden_root_file();
return;
}
if (url.includes('&sub_file=true')) {
mlog('发布单篇文件和子文档, 隐藏父级文档')
while_exist(find_file_ele_li_open_now, li_ele => {
mlog(li_ele)
// 发布单篇&子文档
handle_share_web_page_sub_file(li_ele);
// 监听文档树按钮, 再次执行隐藏函数
document.querySelector('[data-type="file"]')?.addEventListener("click", event => handle_share_web_page_sub_file(li_ele), true);
})
}
else {
// 发布单篇
handle_share_web_page_one_file();
}
}
// 分享按钮被点击
function share_btn_click_event(btn_type) {
// 获取当前文件id
const file_id = find_file_ele_li_open_now()?.getAttribute('data-node-id');
if (file_id == null || file_id.length == 0) {
sendErrorPushMessage('没有打开的文档, 请检查')
return;
}
// 拼接分享url
let url = `http://${cfg.host_ip}/stage/build/desktop/?id=${file_id}`
if (btn_type == "allsub") {
url = url + '&sub_file=true';
}
// 复制到剪切板
navigator.clipboard.writeText(url).then(() => {
sendPushMessage('url已经复制到剪切板');
})
}
// 增加分享按钮
function insert_and_listener_share_btn() {
while_exist(() => { return document.querySelector('#drag') }, () => {
// 定义要插入的 HTML 结构
const htmlString = `
<div id="share_web_page_one" class="toolbar__item ariaLabel" data-position="right" aria-label="分享当前文档, 点击复制url">
<svg><use xlink:href="#iconOpenWindow"></use></svg>
</div>
<div id="share_web_page_allsub" class="toolbar__item ariaLabel" data-position="right" aria-label="分享当前文档和所有子文档, 点击复制url">
<svg><use xlink:href="#iconOpenWindow" style="color: cornflowerblue;"></use></svg>
</div>
`;
// 插件按钮
const plugin_btn = document.querySelector('#drag');
// 插入 HTML
if (plugin_btn) {
plugin_btn.insertAdjacentHTML('afterend', htmlString);
// 选择刚插入的元素
const share_one = plugin_btn.nextElementSibling;
const share_allsub = share_one.nextElementSibling;
// 监听点击事件
share_one.addEventListener('click', (event) => share_btn_click_event('one'));
share_allsub.addEventListener('click', (event) => share_btn_click_event('allsub'));
}
})
}
function listener_hotkey_event() { }
// 初始化并监听点击事件
while_exist(() => { return document.querySelector('.sy__file>.fn__flex-1'); }, treeContainer => {
mlog('开始', location.pathname, siyuan.config.readonly)
if (location.pathname == '/stage/build/desktop/' || siyuan.config.readonly) {
// 网页 && 只读
handle_share_web_page(location.href);
}
else {
// 增加分享按钮
insert_and_listener_share_btn();
// 监听分享快捷键
listener_hotkey_event();
}
})
})()
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于