当前效果
写在前面(重要!重要!重要!)
- 简单的实现, 效果不是很好, 但是没想到更好的交互, 暂时就这样, 如果你有更好的效果, 请联系我, 我会酌情考虑实现
- 我只简单测了一下, 肯定有 bug, 欢迎反馈 pc 端 的 bug, 备注: 其他设备如果有问题, 请自行排查(我没需求), 手动狗头
- 实现原理比较简单粗暴, 如果文档比较多, 会有性能问题, 如果是性能问题, 请自行优化(我没需求)
- 默认打印日志, 如果日志影响你了, 请自行将
is_debug
设置为false
当前功能
- 单击文档树节点, 如果有子节点, 增加二级文档树页面显示子节点, 如果没有子节点, 会隐藏二级文档树页面
- 返回上级按钮
影响
- 二级文档树页面是直接复制的一级文档树元素, 大概率会对样式有影响
- 与单击节点展开效果上有冲突, 但不影响使用
实现原理:
以后再补吧(言外之意: 不会补了, 自己看代码吧)
代码
css 代码
css 代码也要添加, 不然鼠标悬浮不会置灰
.new_sy__file li:hover {
background-color: var(--b3-list-hover);
border-radius: var(--b3-border-radius);
}
js 代码
/*******************************简介********************************************
# 二级文档树_release_v1.0
## 写在前面(重要!重要!重要!)
1. 简单的实现, 效果不是很好, 但是没想到更好的交互, 暂时就这样, 如果你有更好的效果, 请联系我, 我会酌情考虑实现
2. 我只简单测了一下, 肯定有bug, 欢迎反馈 **pc端** 的bug, 备注: 其他设备如果有问题, 请自行排查(我没需求), 手动狗头
3. 实现原理比较简单粗暴, 如果文档比较多, 会有性能问题, 如果是性能问题, 请自行优化(我没需求)
4. 默认打印日志, 如果日志影响你了, 请自行将 `is_debug` 设置为 `false`
## 当前功能
1. 单击文档树节点, 如果有子节点, 增加二级文档树页面显示子节点, 如果没有子节点, 会隐藏二级文档树页面
2. 返回上级按钮
## 影响
1. 二级文档树页面是直接复制的一级文档树元素, 大概率会对样式有影响
2. 与单击节点展开效果上有冲突, 但不影响使用
## 实现原理:
以后再补吧(言外之意: 不会补了, 自己看代码吧)
*******************************************************************************/
(async () => {
let sub_tree_dock = null;
let sub_resize = null;
let li_template = null;
let sub_tree_parent = null;
let sub_tree_back = null;
let sub_tree = null;
let is_show = false;
let click_book_path = null;
let click_parent_path = null;
let is_debug = true;
const sbin_id = Date.now()
function my_log(...args) {
if (is_debug) {
console.log(`[${sbin_id}]:`, ...args)
}
}
// 延迟执行
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function when_element_exist(selector) {
// 返回一个 Promise 对象
return new Promise(resolve => {
// 定义一个内部函数 checkForElement 来检测是否存在特定元素
const checkForElement = () => {
// 初始化 isExist 变量为 false
let isExist = false;
// 如果传入的选择器是函数
if (typeof selector === 'function') {
// 执行选择器函数,获取返回值
isExist = selector();
} else {
// 如果选择器不是函数,则使用 document.querySelector 获取元素
isExist = document.querySelector(selector);
}
// 如果元素存在
if (isExist) {
// 解决 Promise,表示元素存在
resolve(true);
} else {
// 如果元素不存在,通过 requestAnimationFrame 在下一个动画帧继续检查
requestAnimationFrame(checkForElement);
}
};
// 第一次调用 checkForElement 函数开始检查元素是否存在
checkForElement();
});
}
function get_all_book_ele() {
return Array.from(document.querySelectorAll("ul.b3-list[data-url]"));
}
function monitor_tree_node_click_event(real_func) {
if (real_func == null) {
return;
}
get_all_book_ele()?.forEach(book => {
// 监听点击事件
//todo 新建笔记本无法被监听, 也就无法使用
my_log("init addEventListener click event:", book);
const clickHandler = (function(book) {
async function clickHandlerReal(event) {
my_log("user click event:", book)
real_func(event, book, clickHandlerReal);
}
return clickHandlerReal;
})(book);
book.addEventListener("click", clickHandler);
});
}
const tree_click_e = {
invalid: 0, // 无效的点击
node : 1, // 点击节点
arrow : 2, // 点击折叠按钮
}
function get_tag_name_from_btn(btn) {
return btn?.tagName?.toLowerCase() || "";
}
function get_click_btn_type(click_btn) {
if (click_btn == null) {
return tree_click_e.invalid;
}
let tagName = get_tag_name_from_btn(click_btn);
if (tagName == "use") {
click_btn = click_btn.parentElement;
tagName = get_tag_name_from_btn(click_btn);
}
if (tagName == "svg") {
click_btn = click_btn.parentElement;
tagName = get_tag_name_from_btn(click_btn);
}
if (tagName == "ul") {
return tree_click_e.invalid;
}
else if (tagName == "li" && click_btn.getAttribute("data-path") == "/") {
return tree_click_e.arrow;
}
else if (tagName == "li") {
return tree_click_e.node;
}
else if (tagName == "span") {
if (click_btn.classList.contains("b3-list-item__toggle")) {
return tree_click_e.arrow;
}
else if (click_btn.classList.contains("b3-list-item__text") && click_btn.parentElement.getAttribute("data-path") == "/") {
return tree_click_e.arrow;
}
else if (click_btn.classList.contains("b3-list-item__text")) {
return tree_click_e.node;
}
else {
return tree_click_e.invalid;
}
}
return tree_click_e.invalid;
}
function is_click_tree_node(click_btn)
{
return (get_click_btn_type(click_btn) == tree_click_e.node);
}
function is_click_invalid_node(click_btn)
{
return (get_click_btn_type(click_btn) == tree_click_e.invalid);
}
function is_click_valid_node(click_btn)
{
return (!is_click_invalid_node(click_btn));
}
const tree_node_deep_e = {
invalid: 0, //
li : 1, //
span : 2, //
svg : 3, //
use : 4, //
}
function get_deep_level_from_name(name) {
return tree_node_deep_e[name] || tree_node_deep_e.invalid;
}
function get_deep_level_from_btn(btn) {
let tagName = get_tag_name_from_btn(btn);
return get_deep_level_from_name(tagName);
}
function get_ele_from_click_btn(click_btn, type_name) {
if (click_btn == null) {
return null;
}
let click_deep = get_deep_level_from_btn(click_btn);
let ret_deep = get_deep_level_from_name(type_name);
while (click_deep < ret_deep) {
click_btn = click_btn?.firstElementChild;
click_deep++;
}
while (click_deep > ret_deep) {
click_btn = click_btn?.parentElement;
click_deep--;
}
if (click_deep == ret_deep) {
return click_btn;
}
return null;
}
async function get_tree_li_from_path(book_url, el_path) {
if (book_url == null || book_url == "" || el_path == null || el_path == "") {
return null;
}
const selector_str = `ul.b3-list[data-url='${book_url}'] li[data-path='${el_path}']`;
await when_element_exist(selector_str);
return document.querySelector(selector_str);
}
// 折叠按钮是否是展开的
function is_open_arrow_btn(span_btn_arrow) {
return span_btn_arrow?.firstElementChild?.classList?.contains("b3-list-item__arrow--open")
}
// 折叠按钮是否是折叠的
function is_close_arrow_btn(span_btn_arrow) {
return !is_open_arrow_btn(span_btn_arrow)
}
// 通过路径, 找到折叠按钮
async function get_arrow_btn_from_path(book_url, el_path) {
if (book_url == null || book_url == "" || el_path == null || el_path == "") {
return null;
}
const selector_str = `ul.b3-list[data-url='${book_url}'] li[data-path='${el_path}']>span.b3-list-item__toggle`;
await when_element_exist(selector_str);
return document.querySelector(selector_str);
}
// 通过路径, 展开折叠按钮
async function open_arrow_btn_from_path(book_url, el_path) {
let arrow_span = await get_arrow_btn_from_path(book_url, el_path);
if (is_close_arrow_btn(arrow_span)) {
my_log("需要展开");
// 之前是折叠状态, 要展开
arrow_span.click();
}
await when_element_exist(() => {
// 监听, 直到找到 折叠元素的父级的兄弟节点 是ul
// 监听, 直到找到 这个文档树节点已经展开
return arrow_span.closest("li").nextElementSibling?.tagName === 'UL';
});
}
// 获取子节点内容
async function get_sub_tree(notebook, path) {
return fetch("/api/filetree/listDocsByPath", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
notebook,
path,
}),
})
.then((response) => {
if (response.ok) {
return response.text();
}
else {
throw new Error("Failed to get file content");
}
})
.catch((error) => {
console.error(error);
});
}
// 获取子节点列表
async function get_sub_tree_node_list_from_api(notebook, file_path) {
if (notebook == null || file_path == null) {
return null;
}
// 获取json配置里面的内容
let data = await get_sub_tree(notebook, file_path)
// 转换成json
if (data) {
data = JSON.parse(data);
}
if(data.code == 0) {
return data.data;
}
return null;
}
// 新增二级文档树页面
async function page_init() {
// 找到文档树页面
await when_element_exist('div.layout__dockl')
let src_tree_dock = document.querySelector('div.layout__dockl')
let src_resize = src_tree_dock.nextElementSibling
let src_li_ele = document.querySelector(".sy__file ul>li");
// 复制并插入元素
sub_tree_dock = src_tree_dock.cloneNode(true)
sub_resize = src_resize.cloneNode(true)
li_template = src_li_ele.cloneNode(true)
src_tree_dock.insertAdjacentElement("afterend", sub_resize)
src_resize.insertAdjacentElement("beforebegin", sub_tree_dock)
// 有一些样式是根据.sy__file搜索的, 所以要换掉
sub_tree_parent = sub_tree_dock.querySelector(".sy__file")
// sub_tree_parent.classList.remove('sy__file');
sub_tree_parent.classList.add('new_sy__file');
// 删掉一些没用的元素
Array.from(sub_tree_parent.children).forEach(child => {
if (child.classList.contains('block__icons')) {
// 处理头部
Array.from(child.children).forEach(sub_child => {
if (sub_child.classList.contains('block__logo')) {
sub_child.childNodes[2].textContent = '二级树'
// 插入返回按钮
let newElementType = "sub_tree_back_btn"
let displayContent = "返回上一级"
sub_child.insertAdjacentHTML("afterend",
`<span data-type="${newElementType}" class="block__icon b3-tooltips b3-tooltips__sw" aria-label="${displayContent}">
<svg><use xlink:href="#iconBack" style="opacity: 1;"></use></svg>
</span>`);
// 事件监听
let back_ele = child.querySelector(`[data-type="${newElementType}"]`);
back_ele?.addEventListener("click", sub_tree_back_click_handler);
}
else {
sub_child.remove()
}
})
}
else if (child.classList.contains('fn__flex-1')) {
// 处理树
sub_tree_back = child.cloneNode(true);
child.remove()
Array.from(sub_tree_back.children).forEach(sub_child => {
sub_child.remove()
})
}
else {
child.remove()
}
})
sub_tree_parent.addEventListener("click", sub_tree_node_click_handler);
my_log(sub_tree_parent)
// 处理二级文档树节点, 删掉没用的内容
Array.from(li_template.children).forEach(child_ele => {
if (child_ele.classList.contains("b3-list-item__toggle")) {
child_ele.firstElementChild?.classList.remove("b3-list-item__arrow--open")
}
if (!(child_ele.classList.contains("b3-list-item__toggle") ||
child_ele.classList.contains("b3-list-item__text"))) {
child_ele.remove();
}
})
is_show = true;
}
function hide_sub_tree_node() {
if (!is_show) {
return; // 原本就是隐藏的话, 直接退出
}
sub_tree_dock.remove();
sub_tree_dock = null;
sub_resize.remove();
sub_resize = null;
li_template = null;
sub_tree_parent = null;
sub_tree_back = null;
sub_tree = null;
is_show = false;
click_book_path = null;
click_parent_path = null;
}
function clear_sub_tree_node() {
sub_tree?.remove()
sub_tree = sub_tree_back.cloneNode(true)
sub_tree_parent.appendChild(sub_tree);
}
async function show_sub_tree_node(notebook, parent_path, node_list) {
if (!is_show) {
// 之前是隐藏的, 需要初始化
await page_init();
}
// 清空文档树
clear_sub_tree_node();
click_book_path = notebook;
click_parent_path = parent_path;
// 遍历子节点, 并在二级文档树里面显示
node_list.forEach(node => {
let new_li = li_template.cloneNode(true);
sub_tree.appendChild(new_li)
new_li.lastElementChild.textContent = node.name?.slice(0, -3);
new_li.setAttribute('data-path', node.path);
if (node.subFileCount == 0) {
new_li.firstElementChild.classList.add('fn__hidden');
}
})
}
// 点击一级文档树
async function src_tree_node_click_handler(event, book, clickHandler){
let click_btn = event.target;
if (is_click_invalid_node(click_btn)) {
// 不是点击叶子节点的跳过
return;
}
my_log("点击一级文档树")
// 找到路径
let notebook = book?.getAttribute("data-url");
let parent_path = get_ele_from_click_btn(click_btn, "li")?.getAttribute("data-path");
// 获取子节点列表
let node_list_data = await get_sub_tree_node_list_from_api(notebook, parent_path)
let node_list = node_list_data?.files;
// 没有子节点, 隐藏二级文档树
if (node_list == null || node_list.length == 0) {
hide_sub_tree_node()
return;
}
// 有子节点, 有显示二级文档树
show_sub_tree_node(notebook, parent_path, node_list)
}
// 点击二级文档树
async function sub_tree_node_click_handler(event) {
let click_btn = event.target;
if (is_click_invalid_node(click_btn)) {
// 不是点击叶子节点的跳过
return;
}
my_log("点击二级文档树")
// 展开父节点
open_arrow_btn_from_path(click_book_path, click_parent_path);
// 找到子节点并点击
let click_node_path = get_ele_from_click_btn(click_btn, "li")?.getAttribute("data-path");
let click_node_src = await get_tree_li_from_path(click_book_path, click_node_path);
click_node_src?.click();
// 定位当前打开的文档
await sleep(40);
document.querySelector(".layout-tab-container .block__icons span[data-type=focus]").click();
}
// 点击返回上级按钮
async function sub_tree_back_click_handler(event) {
my_log("点击返回上级按钮")
// 找到父节点
let click_node_src = await get_tree_li_from_path(click_book_path, click_parent_path);
click_node_src?.parentElement?.previousElementSibling?.click();
// 定位当前打开的文档
await sleep(40);
document.querySelector(".layout-tab-container .block__icons span[data-type=focus]")?.click();
}
// 主流程
(async () => {
// 监听一级文档树点击事件
await when_element_exist("ul.b3-list[data-url]")
monitor_tree_node_click_event(src_tree_node_click_handler);
})()
})()
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于