取自官方 https://cn.vuejs.org (Version:2.5)
持续更新
用法
Mac 下用户下载 Dash 使用文档 (下载 .docset 后缀文件, 双击导入即可)
Windows 和 Linux 用户可下载 Zeal 使用本文档 (应该类似吧, 我没用过, 自行搜一下吧, 溜了溜了)
制作 docSet 文档
Dash 所需的文档都是. docSet 后缀的文件, 其实 docSet 文件就是一个文件夹而已, 里头包含最终的 html 文档, 以及根据 HTML 建立的索引 (索引放在 SQLite 数据库中).
生成文档的方法有很多种, 如 Python,Ruby,Objective-C https://github.com/Kapeli/javadocset ,Node.JS https://github.com/exlee/d3-dash-gen ,PHP https://github.com/akirk/dash-phpunit
可以选择 镜像时处理, 也可以镜像后处理. 只需要结果中包含 HTML, 以及 SQLite 就 OJBK.
这里我选择 镜像后用 Node.JS 处理
需要用到的库为:
fs 做一些文件的读写操作
path 路径处理
sync-exec 执行一些 cmd 命令
SQLite-sync 做一些 SQLite 操作
cheerio 服务器版的 jQuery
主要步骤:
根据官网提供的官方文档 https://kapeli.com/docsets#dashDocset , 整个转换主要有以下 5 个步骤:
1. 创建 Docset 目录结构 (Create the Docset Folder);
2. 复制 HTML 文件 (Copy the HTML Documentation);
3. 创建 Info.plist 文件 (Create the Info.plist File);
4. 创建 SQLite 数据库文件 (Create the SQLite Index);
5. 写入 SQLite 数据索引 (Populate the SQLite Index);
1. 镜像站点
镜像工具有很多, 这里只推荐我尝试过的几款, 并且用着还不错:
名称 | 平台 | 地址 |
---|---|---|
HTTrack | OS X / Windows/Linux/Unix/BSD | http://www.httrack.com |
SiteSucker | OS X | http://sitesucker.us/home.html |
Cyotek webCopy | Windows | https://www.cyotek.com/cyotek-webcopy |
实际上这几种都不是很完美, 或多或少会漏一些外部站点的资源文件, 目前还没找到解决办法,
如果你们有解决办法, 麻烦 **@** 我一下.
我这里以 SiteSucker 为例, 镜像 https://cn.vuejs.org , 一级目录结构如下:
// cn.vuejs.org
├── _00.txt
├── _downloads.HTML
├── coc
├── CSS
├── fonts
├── guide
├── images
├── index.HTML
├── JS
├── manifest.JSON
├── support-vuejs
└── v2
重点关注对象为以下, 提取其中的内容, 生成索引
- cn.vuejs.org/v2/API/index.HTML
- (API 列表)
- cn.vuejs.org/v2/guide/*
- (官网教程列表)
- cn.vuejs.org/v2/style-guide/index.HTML
- (风格指南)
参考 Dash 的 Vue 文档, 一层层拨开后, 发现官方用的 HTTrack 做的镜像, 并且资源文件比我自己用 HTTrack 镜像下来的资源齐全, 一番折腾下来, 也没成功.
对比后选择用官方的外部资源, 文档内容则用自己镜像的
左侧为 Dash 官方文档中的资源 , 中间为合并后的资源 (后面要用到), 右侧为自己镜像的资源
2. 创建 Docset 目录结构 (Create the Docset Folder)
本例中我们创建的文档叫 VueJS, 所以按下列结构创建目录:
mkdir -p VueJS.docset/Contents/Resources/Documents/
3. 复制 HTML 文件 (Copy the HTML Documentation)
把所有的 HTML 文档拷贝到 Documents 文件夹中, Dash 默认 Documents 为文档根目录
为了省事, 我把需要的资源文件放在了当前项目下.
cp -r ./Documents VueJS.docset/Contents/Resources/
4. 创建 Info.plist 文件 (Create the Info.plist File)
在 VueJS.docset/Contents/ 中创建 Info.plist 文件, 注意文件名的大小写, 文件内容如下:
<?xml version="1.0" encoding="UTF-8"?>
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
- <plist version="1.0">
- <dict>
- <key>CFBundleIdentifier</key>
- <string>VueJS</string>
- <key>CFBundleName</key>
- <string>VueJS-CN</string>
- <key>DocSetPlatformFamily</key>
- <string>VueJS</string>
- <key>isDashDocset</key>
- <true/>
- <key>DashDocSetFamily</key>
- <string>dashtoc3</string>
- <key>dashIndexFilePath</key>
- <string>cn.vuejs.org/index.HTML</string>
- </dict>
- </plist>
一个 xml 文件, 里面都是成对的 key-string 配置项
dashIndexFilePath 表示在 Dash 中点击你的文档后, 默认的主页是什么
CFBundleName 为在 Dash 中的文档名称
DashDocSetFamily 左下角显示索引列表 (这里我有配置, 但是没生效, 后续再研究)
其他部分为一些关键字
5. 创建 SQLite 数据库文件 (Create the SQLite Index)
创建 SQLite 索引.
索引文件的位置是: VueJS.docset/Contents/Resources/docSet.dsidx ,(Mac 电脑已经预装了 SQLite)
所以直接从命令行进入 Resources 文件夹, 在命令行中敲:
sqlite3 docSet.dsidx
这样就进入了 SQLite 数据库, 接下来, 创建数据表
CREATE TABLE searchIndex(id INTEGER PRIMARY KEY, name TEXT, type TEXT, path TEXT)
6. 写入 SQLite 数据索引 (Populate the SQLite Index);
再往后就是, 从 HTML 文件中提取内容, 插入索引. 这是最重要的一点, 这里没弄好, 整个都没啥用.
INSERT OR IGNORE INTO searchIndex(name, type, path) VALUES ('name', 'type', 'path');
其中
name 为关键字, 比如你想在 Dash 中输入一个 select 就可以查询, 那这个 select 就是关键字;
type 为关键字的类型, 官方支持的有很多, 如 Class,Function,Guide,Method 等;
path 为文档的锚点地址, 点击目录跳转
官方原文为:
name is the name of the entry. For example, if you are adding a class, it would be the name of the class. This is the column that Dash searches.
type is the type of the entry. For example, if you are adding a class, it would be "Class". For a list of types that Dash recognises, see below.
path is the relative path towards the documentation file you want Dash to display for this entry. It can contain an anchor (#). Alternatively, Dash also supports http:// URL entries.
以下为我的部分代码, 完整的代码在 build-vue.js
- // build-vue.JS
- /**
- * 根据各个标题处理相应的锚点 添加索引
- *
- * @param $ dom 对象
- * @param relativePath 相对路径
- * @param dir 文件夹名称
- */
- function handleTitles($,relativePath,dir) {
- // 教程模块 以 h2 为索引, 需要添加一个 h1 的索引
- let h1Title = '';
- if(dir === 'guide'){
- $('h1').each(function (i,h) {
- h1Title = Array.from(h.childNodes).map((node) => node.data ).join('');
- db.run(`INSERT INTO searchIndex (name, type, path) VALUES ('${h1Title}', '${type['guide']}', '${relativePath}')`,function(res){
- if(res.error) throw res.error;
- console.log(res);
- });
- });
- }
- $('h2').each(function (i,h) {
- if(!h.attribs.id) return;
- let h2s = extractText(h); // 提取标题中的 ID, 文本内容
- let h3s = [];
- if(dir === 'api'){
- h3s = collectH3s(h);
- if(h3s.length<1) return
- }
- let entryType = type[h2s.id] || type['h2']; // 默认 Section
- console.log(h2s);
- let h2Num = dir === 'api' ? 1 : 0;
- let h2Type = type['h2']; // h2 归类为 Section
- addDashAnchor(h,h2s.id,h2Type,h2Num);
- let inTitle = `${h2s.text} ${dir === 'guide' ? '-'+ h1Title : ''}`;
- let iniType = dir ==='api' ? type['guide'] : h2Type;
- db.run(`INSERT INTO searchIndex (name, type, path) VALUES ('${inTitle}', '${iniType}', '${relativePath}#${encodeURIComponent(h2s.id)}')`,function(res){
- if(res.error) throw res.error;
- console.log(res);
- });
- // API 下 需要处理 h3 标题, 生成相应的索引
- if(dir === 'api'){
- h3s.forEach(function (titleNode,index) {
- let id = titleNode.attribs.id;
- let text = [].slice.call(titleNode.childNodes).map( (node) => node.data).join('');
- // 需要处理括号
- if(text.match(/^([^(]+)\(/)) text= text.match(/^([^(]+)\(/)[1];
- console.log(id,text,entryType);
- addDashAnchor(titleNode,id,entryType,0);
- db.run(`INSERT INTO searchIndex (name, type, path) VALUES ('${text}', '${entryType}', '${relativePath}#${encodeURIComponent(id)}')`,function(res){
- if(res.error) throw res.error;
- console.log(res);
- });
- });
- }
- });
- /**
- * 提取标题中的 ID, 文本内容
- * @param h node
- * @returns {{id: *, text: *}} id 用来生成锚点, text 当做标题
- */
- function extractText (h) {
- let title = [].slice.call(h.childNodes).map( (node) => node.tagName === 'a' ? node.attribs.title : '').join('');
- let id = h.attribs.id;
- return {
- id: id,
- text: title ? htmlEscape(title) : id // 如果没有就用 ID 代替
- }
- }
- // 字符转义
- function htmlEscape (text) {
- return text
- .replace(/&/g, '&')
- .replace(/"/g, '"')
- .replace(/'/g, `'`)
- .replace(/</g, '<')
- .replace(/>/g, '>')
- }
- // 提取 h2 附近的 h3 标题列表
- function collectH3s (h) {
- let h3s = [];
- let next = h.nextSibling;
- while (next && next.tagName !== 'h2') {
- if (next.tagName === 'h3') {
- next.childNodes = removeTagA(next);
- h3s.push(next)
- }
- next = next.nextSibling
- }
- return h3s
- }
- // 移除 A 标签
- function removeTagA(h) {
- return [].slice.call(h.childNodes).filter(function (node) {
- return node.tagName !== 'a'
- })
- }
- // 添加 Dash 规定格式的 锚点
- function addDashAnchor(h,name,types,num) {
- let nameStr = (`//dash_ref_${name}/${types}/${encodeURIComponent(name)}/${num}`); // 需要对 URL 进行 URL 编码 (百分比转义)
- let dashAnchor = `<a class="dashAnchor" name="${nameStr}"/>`;
- h.childNodes = removeTagA(h); // 取完 title 之后移除原有的锚点, 添加 Dash 规定格式的锚点
- $(h).before(dashAnchor).HTML();
- }
- }
7. 导入文档
把所有的索引数据都插入到 searchIndex 以后, docSet 文档就制作好了, 直接双击 VueJS.docSet 就可以导入 Dash 了.
截图
以上示例代码均可在 Gayhub 查看
参考
https://kapeli.com/docsets
Dash 制作 docSet 文档
来源: https://juejin.im/post/5c34a8fb6fb9a049f23cef68