[js] 思源笔记可移动的导航大纲 js 代码片段

本贴最后更新于 298 天前,其中的信息可能已经时异事殊

导航目录大体功能:(使用方法:把代码放进 js 代码片段,粘贴完注意第 8 条)

  1. 随着笔记标签页的切换,导航内容也能相应切换
  2. 目录字体大小可调
  3. 可折叠起来
  4. 导航目录过长,可滚动
  5. 鼠标点击目录,可以导航到正文相应位置
  6. 鼠标悬停在目录上,可弹出浮窗
  7. 鼠标单击块,浮动大纲中对应的标题内容颜色会改变。再次点击会找上一级标题
  8. image.png

看下图,应该能明白。更新时间 20250227,修复了一些 bug

screenshots.gif

// ========== 工具函数 ==========
// 使用原生DOMParser解析HTML并提取纯文本内容
const parseHtmlToText = (html) => {
    return html.replace(/<[^>]+>/g, '').replace(/ /g, ' '); // 注意,使用时要在 后面加一个分号;
};

// 通用高亮函数,返回是否匹配成功
function highlightOutlineElements(outlineContent, nodeId, textContent) {
    const outlineElements = outlineContent.querySelectorAll("[data-href]");
    let isMatched = false; // 用于标记是否找到匹配项
    outlineElements.forEach((element) => {
        const href = element.getAttribute('data-href');
        const hrefId = href.split('/').pop();
        const isMatch = nodeId ? hrefId === nodeId : element.textContent.trim() === textContent;

        // 只在必要时修改样式
        if (isMatch && element.style.backgroundColor !== 'green') {
            element.style.backgroundColor = 'green'; // 高亮背景颜色
            isMatched = true; // 标记匹配成功
        } else if (!isMatch && element.style.backgroundColor === 'green') {
            element.style.backgroundColor = ''; // 恢复默认背景颜色
        }
    });
    return isMatched; // 返回是否匹配成功
}

// ========== 数据获取函数 ==========
// 获取文档信息,包括 rootID
async function getRootID({ id }) {
    const response = await fetch(`/api/block/getDocInfo`, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
        },
        body: JSON.stringify({ id }),
    });
    const data = await response.json();
    return data.data.rootID; // 直接返回 rootID
}

// 获取文档大纲的函数
const getDocOutline = async (docId) => {
    try {
        const response = await fetch(`/api/outline/getDocOutline`, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({ id: docId }),
        });
        if (!response.ok) {
            throw new Error(`网络请求失败:${response.status}`);
        }
        const data = await response.json();
        if (data.code === 0) {
            return data.data;
        } else {
            console.error('获取文档目录结构失败:', data.msg);
            return null;
        }
    } catch (error) {
        console.error('获取文档目录结构失败:', error.message);
        return null;
    }
};

// ========== 大纲处理函数 ==========
// 收集大纲标题的函数
const collectTitles = (data) => {
    let titles = [];
    for (let item of data) {
        let text = (item.name || item.content).trim();

        // 解析HTML为纯文本
        const parsedText = parseHtmlToText(text);
        titles.push({
            text,
            parsedText,
            id: item.id,
            depth: item.depth,
            needParse: text !== parsedText,
        });

        // 递归处理子标题
        if (item.count > 0) {
            titles = titles.concat(collectTitles(item.blocks ?? item.children));
        }
    }
    return titles;
};

// 生成大纲内容的函数
const generateOutline = async (nodeId, outlineContent) => {
    console.log("开始生成大纲,节点 ID:", nodeId); // 调试日志
    try {
        if (!nodeId) {
            outlineContent.innerHTML = "<li>未找到有效节点,请点击文档内容。</li>";
            return;
        }

        // 检查是否需要重新生成大纲
        const currentOutlineNodeId = outlineContent.getAttribute('data-current-node-id');
        if (currentOutlineNodeId === nodeId) {
            console.log("大纲已存在,无需重新生成"); // 调试日志
            return;
        }

        const docOutline = await getDocOutline(nodeId); // 获取文档大纲
        if (!docOutline) {
            outlineContent.innerHTML = "<li>无法获取文档目录结构。</li>";
            return;
        }

        const titles = collectTitles(docOutline); // 收集大纲标题
        const fragment = document.createDocumentFragment(); // 创建文档片段

        // 遍历标题并生成列表项
        titles.forEach(title => {
            const listItem = document.createElement("li");
            const link = document.createElement("span");
            link.setAttribute("data-type", "a");
            link.setAttribute("data-href", `siyuan://blocks/${title.id}`);

            // 使用 textContent 设置内容,避免HTML被解析
            link.textContent = title.parsedText;

            // 设置链接样式
            Object.assign(link.style, {
                color: "#000",
                whiteSpace: "nowrap",
                overflow: "hidden",
                textOverflow: "ellipsis",
                display: "inline-block",
                maxWidth: "100%",
            });

            // 添加链接点击事件
            link.addEventListener("mousedown", (e) => {
                if (e.button === 0) {
                    e.preventDefault();
                    const selection = window.getSelection();
                    const range = document.createRange();
                    range.selectNodeContents(link);
                    selection.removeAllRanges();
                    selection.addRange(range);
                }
            });

            link.addEventListener('click', function (event) {
                event.preventDefault();
                const href = this.getAttribute("data-href");
                window.open(href, '_blank');
            });

            // 设置列表项的缩进
            listItem.style.paddingLeft = `${title.depth * 15}px`;
            listItem.appendChild(link);
            fragment.appendChild(listItem);
        });

        // 清空并更新大纲内容
        outlineContent.innerHTML = "";
        outlineContent.appendChild(fragment);

        // 更新当前大纲的节点 ID
        outlineContent.setAttribute('data-current-node-id', nodeId);
        console.log("大纲生成完成"); // 调试日志
    } catch (error) {
        outlineContent.innerHTML = `<li>生成大纲失败:${error.message}</li>`;
    }
};

// ========== 页面交互函数 ==========
// 修改函数:根据 rootId 查找并返回所有兄弟元素的 title 文本数组
function getLastSiblingTitle(rootId) {
    const allBreadcrumbItems = document.querySelectorAll('.protyle-breadcrumb__item');
    for (const item of allBreadcrumbItems) {
        if (item.getAttribute('data-node-id') === rootId && item.classList.contains('protyle-breadcrumb__item--active')) {
            const parent = item.parentElement;
            const siblings = Array.from(parent.children).filter(sibling => sibling !== item);

            const titles = [];
            siblings.forEach(sibling => {
                const titleElement = sibling.querySelector('.protyle-breadcrumb__text');
                if (titleElement && titleElement.getAttribute('title')) {
                    titles.push(titleElement.getAttribute('title'));
                }
            });
            return titles; // 返回包含所有兄弟节点标题的数组
        }
    }
    return []; // 如果未找到符合条件的元素,返回空数组
}

// 处理 NodeHeading 类型
function handleNodeHeading(outlineContent, nodeId) {
    console.log("处理 NodeHeading 类型,节点 ID:", nodeId); // 调试日志
    highlightOutlineElements(outlineContent, nodeId, null);
}

// 处理非 NodeHeading 类型
function handleNonNodeHeading(outlineContent, textContentArray) {
    console.log("处理非 NodeHeading 类型,兄弟节点标题数组:", textContentArray); // 调试日志
    if (Array.isArray(textContentArray) && textContentArray.length > 0) {
        // 倒序遍历数组,依次尝试匹配
        for (let i = textContentArray.length - 1; i >= 0; i--) {
            const currentLine = textContentArray[i];
            if (highlightOutlineElements(outlineContent, null, currentLine)) {
                return; // 匹配成功后退出循环
            }
        }
    }
}

// 全局点击事件处理函数
const handleClick = async (e, outlineContent) => {
    console.log("点击事件触发,目标元素:", e.target); // 调试日志
    let target = e.target;
    while (target && target !== document && !target.hasAttribute('data-node-id')) {
        target = target.parentNode;
    }

    if (target && target.hasAttribute('data-node-id')) {
        const nodeId = target.getAttribute('data-node-id');
        try {
            // 获取 rootID
            const rootID = await getRootID({ id: nodeId });
            if (rootID) {
                console.log("开始生成大纲,节点 ID:", nodeId); // 调试日志
                await generateOutline(nodeId, outlineContent);
                if (target.getAttribute('data-type') === 'NodeHeading') {
                    handleNodeHeading(outlineContent, nodeId);
                } else {
                    const siblingTitles = getLastSiblingTitle(rootID);
                    if (siblingTitles.length > 0) {
                        handleNonNodeHeading(outlineContent, siblingTitles);
                    }
                }
            }
        } catch (error) {
            console.error('获取 rootID 失败:', error.message);
        }
    } else {
        outlineContent.innerHTML = "<li>未找到有效节点,请点击文档内容。</li>";
    }
};

// ========== UI 创建函数 ==========
// 创建大纲面板的函数
const createOutlinePanel = () => {
    const outlinePanel = document.createElement("div");
    Object.assign(outlinePanel.style, {
        position: "fixed",
        top: "100px",
        left: "1000px",
        width: "200px",
        height: "30px",
        background: "#f1f1f1",
        border: "1px solid #ccc",
        borderRadius: "5px",
        padding: "0",
        boxShadow: "0 0 10px rgba(0, 0, 0, 0.1)",
        zIndex: "1000",
        overflow: "hidden",
    });

    const topButtonsContainer = createTopButtonsContainer();
    outlinePanel.appendChild(topButtonsContainer);

    const outlineTitle = document.createElement("h3");
    outlineTitle.textContent = "大纲";
    Object.assign(outlineTitle.style, {
        margin: "0 0 10px",
    });
    outlinePanel.appendChild(outlineTitle);

    const outlineContent = document.createElement("ul");
    outlineContent.id = "outline-list";
    Object.assign(outlineContent.style, {
        listStyle: "none",
        padding: "0",
        margin: "0",
        fontSize: "14px",
        marginTop: "20px",
        overflowY: "auto",
        maxHeight: "340px",
    });
    outlineContent.setAttribute("draggable", "false");
    outlineContent.addEventListener("dragstart", (e) => e.preventDefault());
    outlinePanel.appendChild(outlineContent);

    const toggleButtonInstance = topButtonsContainer.querySelector('button:nth-child(2)');
    toggleButtonInstance.textContent = "展开";
    let isExpanded = false;
    toggleButtonInstance.addEventListener("click", () => {
        isExpanded = !isExpanded;
        outlinePanel.style.height = isExpanded ? "400px" : `${topButtonsContainer.offsetHeight}px`;
        toggleButtonInstance.textContent = isExpanded ? "折叠" : "展开";
    });

    enableDragging(outlinePanel, topButtonsContainer);

    return { outlinePanel, outlineContent };
};

// 创建顶部按钮容器的函数
const createTopButtonsContainer = () => {
    const topButtonsContainer = document.createElement("div");
    Object.assign(topButtonsContainer.style, {
        position: "absolute",
        top: "0",
        left: "0",
        right: "0",
        height: "20px",
        backgroundColor: "#f1f1f1",
        display: "flex",
        justifyContent: "space-between",
        alignItems: "center",
        padding: "5px",
    });

    const showOutlineButton = document.createElement("button");
    showOutlineButton.textContent = "思源大纲";
    Object.assign(showOutlineButton.style, {
        padding: "5px",
        background: "#007bff",
        color: "#fff",
        border: "none",
        borderRadius: "5px",
        cursor: "pointer",
    });

    const toggleButtonElement = document.createElement("button");
    toggleButtonElement.textContent = "展开";
    Object.assign(toggleButtonElement.style, {
        padding: "5px",
        background: "#ccc",
        border: "none",
        borderRadius: "5px",
        cursor: "pointer",
    });

    topButtonsContainer.appendChild(showOutlineButton);
    topButtonsContainer.appendChild(toggleButtonElement);
    return topButtonsContainer;
};

// 创建字体大小调整按钮的函数
const createFontButtonsContainer = (outlineContent) => {
    const fontButtonsContainer = document.createElement("div");
    Object.assign(fontButtonsContainer.style, {
        display: "flex",
        gap: "5px",
    });

    const decreaseFontSizeButton = document.createElement("button");
    decreaseFontSizeButton.textContent = "-";
    Object.assign(decreaseFontSizeButton.style, {
        width: "20px",
        height: "20px",
        fontSize: "16px",
        border: "none",
        background: "#ccc",
        borderRadius: "50%",
    });
    decreaseFontSizeButton.addEventListener("click", () => {
        const currentSize = parseFloat(outlineContent.style.fontSize);
        outlineContent.style.fontSize = `${Math.max(currentSize - 1, 10)}px`;
    });

    const increaseFontSizeButton = document.createElement("button");
    increaseFontSizeButton.textContent = "+";
    Object.assign(increaseFontSizeButton.style, {
        width: "20px",
        height: "20px",
        fontSize: "16px",
        border: "none",
        background: "#ccc",
        borderRadius: "50%",
    });
    increaseFontSizeButton.addEventListener("click", () => {
        const currentSize = parseFloat(outlineContent.style.fontSize);
        outlineContent.style.fontSize = `${Math.min(currentSize + 1, 24)}px`;
    });

    fontButtonsContainer.appendChild(decreaseFontSizeButton);
    fontButtonsContainer.appendChild(increaseFontSizeButton);

    return fontButtonsContainer;
};

// 实现面板拖动功能的函数
const enableDragging = (outlinePanel, topButtonsContainer) => {
    let isDragging = false;
    let offsetX, offsetY;

    topButtonsContainer.style.cursor = "move";

    topButtonsContainer.addEventListener("mousedown", (e) => {
        if (isDragging) return;
        isDragging = true;
        offsetX = e.clientX - Number(outlinePanel.style.left.replace('px', ''));
        offsetY = e.clientY - Number(outlinePanel.style.top.replace('px', ''));

        const onMouseMove = (e) => {
            if (!isDragging) return;
            let newX = e.clientX - offsetX;
            let newY = e.clientY - offsetY;

            const minX = 20;
            const minY = 20;
            const maxX = window.innerWidth - outlinePanel.offsetWidth - 20;
            const maxY = window.innerHeight - outlinePanel.offsetHeight - 20;

            newX = Math.max(minX, Math.min(newX, maxX));
            newY = Math.max(minY, Math.min(newY, maxY));

            outlinePanel.style.left = `${newX}px`;
            outlinePanel.style.top = `${newY}px`;
        };

        const onMouseUp = () => {
            isDragging = false;
            document.removeEventListener("mousemove", onMouseMove);
            document.removeEventListener("mouseup", onMouseUp);
        };

        document.addEventListener("mousemove", onMouseMove);
        document.addEventListener("mouseup", onMouseUp);
    });
};

// ========== 主函数 ==========
const main = () => {
    const { outlinePanel, outlineContent } = createOutlinePanel();
    document.body.appendChild(outlinePanel);

    const fontButtonsContainer = createFontButtonsContainer(outlineContent);
    const topButtonsContainer = outlinePanel.querySelector('div');
    topButtonsContainer.appendChild(fontButtonsContainer);

    document.addEventListener('click', (e) => handleClick(e, outlineContent));
};

main();
  • 思源笔记

    思源笔记是一款隐私优先的个人知识管理系统,支持完全离线使用,同时也支持端到端加密同步。

    融合块、大纲和双向链接,重构你的思维。

    28446 引用 • 119768 回帖
  • 代码片段

    代码片段分为 CSS 与 JS 两种代码,添加在 [设置 - 外观 - 代码片段] 中,这些代码会在思源笔记加载时自动执行,用于改善笔记的样式或功能。

    用户在该标签下分享代码片段时需在帖子标题前添加 [css] [js] 用于区分代码片段类型。

    285 引用 • 1985 回帖
2 操作
cxg318 在 2025-02-27 01:25:05 更新了该帖
cxg318 在 2025-02-24 00:11:08 更新了该帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...
  • zzshang

    鸡肋

  • 其他回帖
  • att88

    js 代码用了好像没起作用?上面 docker 栏中也没找到相应图标,是不是我哪里使用姿势不对

    1 回复
  • Floria233

    这个很棒啊,感谢大大。就我的理解,这个设计是不是和 ob 的 floating index(好像是这个)插件相似?就是可以使大纲悬浮起来——就动图与说明看起来,似乎是这个样子?

    (我就说嘛,ob 能做到的东西,思源大差不差也都能做到类似的,就是这边开发者人手缺乏罢了,扼腕……)

    这个我所能想到的第一个优点就是节省面板位置,因为插件那么多,而软件本体除了编辑器之外的其他部分又那么小,可谓寸土寸金,参考我的话,左边是自带文档树 + 书签两个部件,右边是二级文档树这个部件,这三个是长期固定,常用的浮动面板是 knote 和小记,偶尔需要查看大纲,就得按快捷键切换了(因为不是每个文档都有大纲,需要用的时候才会固定,不用的时候就不希望它占用位置)

    因为文档跳转频繁,文档树与其浮动还是贴边吧,而浮动的大纲面板可以按照需要跳出来,最能辅助正文查看又能灵活调取(不至于总是放在某个地方),唯有它浮动起来是最合适的 😁

    我的体验是,它是一个有则最好,无则稍憾(思源本体可以凑合)而绝非鸡肋的功能设计,ob,flowus(notion 不知道)还有有道云(印象不知道)这种都有特别给出这个设计啊,所以它绝对是有必要的

    目前我所想到的优化方向,1 设计上,应该是这个面板先跟主题风格和谐一致,除此之外,2 这个 UI 的信息界面,大大要不要参考动图右边那个大纲插件设计?给每个大标题旁边都用数字标注其下小标题的数量之类的?

    另,有道云的悬浮大纲,在不用时会变成一道道长短不一的横线,这个看起来很简洁,但可以的话,请大大千万别做成这样,因为我用过几次,每次都需要鼠标浮过去它才会显现,不够直观还增加了操作步骤。

    也不希望完全参照 ob 的那个插件——只能在一个固定位置悬浮(这个当大纲标题太长时,偶尔会和正文重合起来),就是动图中这种自由拖动我感觉挺好的 👍(不过它是只能在编辑区拖动?还是可以在整个界面拖动呢?后者感觉更好一点)

    其实这个面板,只要存在了就是它最大的意义,其他都是锦上添花(来自小白的看法)

    1 回复
  • att88 2 评论

    我个人可太需要这个了好嘛!之前论坛找了好几次是否有悬浮大纲悬浮文档树,结果都是没有。思源大纲虽说可以四处移动,但是高宽还是受到主页面限制,有时候大纲没几行内容还占了一大片,老按快捷键开关也很麻烦。就很希望能像 ps 那样工具面板都能页面外悬浮,能专注页面本身不被视觉中心外的东西干扰。感谢作者!希望尽快上架

    文档树原来就是可以悬浮的,但悬浮大纲这个是真没有
    Floria233
    请问文档树怎么悬浮?
    att88
  • 查看全部回帖

推荐标签 标签

  • 单点登录

    单点登录(Single Sign On)是目前比较流行的企业业务整合的解决方案之一。SSO 的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。

    9 引用 • 25 回帖 • 8 关注
  • 快应用

    快应用 是基于手机硬件平台的新型应用形态;标准是由主流手机厂商组成的快应用联盟联合制定;快应用标准的诞生将在研发接口、能力接入、开发者服务等层面建设标准平台;以平台化的生态模式对个人开发者和企业开发者全品类开放。

    15 引用 • 127 回帖
  • NetBeans

    NetBeans 是一个始于 1997 年的 Xelfi 计划,本身是捷克布拉格查理大学的数学及物理学院的学生计划。此计划延伸而成立了一家公司进而发展这个商用版本的 NetBeans IDE,直到 1999 年 Sun 买下此公司。Sun 于次年(2000 年)六月将 NetBeans IDE 开源,直到现在 NetBeans 的社群依然持续增长。

    78 引用 • 102 回帖 • 724 关注
  • BAE

    百度应用引擎(Baidu App Engine)提供了 PHP、Java、Python 的执行环境,以及云存储、消息服务、云数据库等全面的云服务。它可以让开发者实现自动地部署和管理应用,并且提供动态扩容和负载均衡的运行环境,让开发者不用考虑高成本的运维工作,只需专注于业务逻辑,大大降低了开发者学习和迁移的成本。

    19 引用 • 75 回帖 • 702 关注
  • 星云链

    星云链是一个开源公链,业内简单的将其称为区块链上的谷歌。其实它不仅仅是区块链搜索引擎,一个公链的所有功能,它基本都有,比如你可以用它来开发部署你的去中心化的 APP,你可以在上面编写智能合约,发送交易等等。3 分钟快速接入星云链 (NAS) 测试网

    3 引用 • 16 回帖
  • 30Seconds

    📙 前端知识精选集,包含 HTML、CSS、JavaScript、React、Node、安全等方面,每天仅需 30 秒。

    • 精选常见面试题,帮助您准备下一次面试
    • 精选常见交互,帮助您拥有简洁酷炫的站点
    • 精选有用的 React 片段,帮助你获取最佳实践
    • 精选常见代码集,帮助您提高打码效率
    • 整理前端界的最新资讯,邀您一同探索新世界
    488 引用 • 384 回帖
  • OpenStack

    OpenStack 是一个云操作系统,通过数据中心可控制大型的计算、存储、网络等资源池。所有的管理通过前端界面管理员就可以完成,同样也可以通过 Web 接口让最终用户部署资源。

    10 引用 • 8 关注
  • frp

    frp 是一个可用于内网穿透的高性能的反向代理应用,支持 TCP、UDP、 HTTP 和 HTTPS 协议。

    17 引用 • 7 回帖 • 1 关注
  • Python

    Python 是一种面向对象、直译式电脑编程语言,具有近二十年的发展历史,成熟且稳定。它包含了一组完善而且容易理解的标准库,能够轻松完成很多常见的任务。它的语法简捷和清晰,尽量使用无异义的英语单词,与其它大多数程序设计语言使用大括号不一样,它使用缩进来定义语句块。

    561 引用 • 677 回帖 • 1 关注
  • jsDelivr

    jsDelivr 是一个开源的 CDN 服务,可为 npm 包、GitHub 仓库提供免费、快速并且可靠的全球 CDN 加速服务。

    5 引用 • 31 回帖 • 120 关注
  • 深度学习

    深度学习(Deep Learning)是机器学习的分支,是一种试图使用包含复杂结构或由多重非线性变换构成的多个处理层对数据进行高层抽象的算法。

    45 引用 • 44 回帖 • 2 关注
  • V2Ray
    1 引用 • 15 回帖 • 4 关注
  • Sublime

    Sublime Text 是一款可以用来写代码、写文章的文本编辑器。支持代码高亮、自动完成,还支持通过插件进行扩展。

    10 引用 • 5 回帖 • 1 关注
  • TensorFlow

    TensorFlow 是一个采用数据流图(data flow graphs),用于数值计算的开源软件库。节点(Nodes)在图中表示数学操作,图中的线(edges)则表示在节点间相互联系的多维数据数组,即张量(tensor)。

    20 引用 • 19 回帖
  • SEO

    发布对别人有帮助的原创内容是最好的 SEO 方式。

    36 引用 • 200 回帖 • 54 关注
  • 职场

    找到自己的位置,萌新烦恼少。

    127 引用 • 1708 回帖 • 1 关注
  • Wide

    Wide 是一款基于 Web 的 Go 语言 IDE。通过浏览器就可以进行 Go 开发,并有代码自动完成、查看表达式、编译反馈、Lint、实时结果输出等功能。

    欢迎访问我们运维的实例: https://wide.b3log.org

    30 引用 • 218 回帖 • 663 关注
  • Eclipse

    Eclipse 是一个开放源代码的、基于 Java 的可扩展开发平台。就其本身而言,它只是一个框架和一组服务,用于通过插件组件构建开发环境。

    76 引用 • 258 回帖 • 641 关注
  • 工具

    子曰:“工欲善其事,必先利其器。”

    308 引用 • 773 回帖
  • 浅吟主题

    Jeffrey Chen 制作的思源笔记主题,项目仓库:https://github.com/TCOTC/Whisper

    2 引用 • 34 回帖 • 1 关注
  • iOS

    iOS 是由苹果公司开发的移动操作系统,最早于 2007 年 1 月 9 日的 Macworld 大会上公布这个系统,最初是设计给 iPhone 使用的,后来陆续套用到 iPod touch、iPad 以及 Apple TV 等产品上。iOS 与苹果的 Mac OS X 操作系统一样,属于类 Unix 的商业操作系统。

    89 引用 • 150 回帖 • 1 关注
  • abitmean

    有点意思就行了

    44 关注
  • ZooKeeper

    ZooKeeper 是一个分布式的,开放源码的分布式应用程序协调服务,是 Google 的 Chubby 一个开源的实现,是 Hadoop 和 HBase 的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。

    61 引用 • 29 回帖 • 14 关注
  • Windows

    Microsoft Windows 是美国微软公司研发的一套操作系统,它问世于 1985 年,起初仅仅是 Microsoft-DOS 模拟环境,后续的系统版本由于微软不断的更新升级,不但易用,也慢慢的成为家家户户人们最喜爱的操作系统。

    232 引用 • 484 回帖 • 1 关注
  • Scala

    Scala 是一门多范式的编程语言,集成面向对象编程和函数式编程的各种特性。

    13 引用 • 11 回帖 • 180 关注
  • ZeroNet

    ZeroNet 是一个基于比特币加密技术和 BT 网络技术的去中心化的、开放开源的网络和交流系统。

    1 引用 • 21 回帖 • 667 关注
  • Kubernetes

    Kubernetes 是 Google 开源的一个容器编排引擎,它支持自动化部署、大规模可伸缩、应用容器化管理。

    119 引用 • 54 回帖