上次完全更新:
-文档总数:0 / 已缓存:0%
+diff --git a/.gitignore b/.gitignore
index 9c90724..1df9037 100644
--- a/.gitignore
+++ b/.gitignore
@@ -51,3 +51,6 @@ Temporary Items
*.project
+*.temp
+*.log
+*.cmd
\ No newline at end of file
diff --git a/.npmignore b/.npmignore
new file mode 100644
index 0000000..6574c45
--- /dev/null
+++ b/.npmignore
@@ -0,0 +1,21 @@
+# Windows image file caches
+Thumbs.db
+ehthumbs.db
+
+# Folder config file
+Desktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+.DS_Store
+.AppleDouble
+.LSOverride
+
+npm-debug.log
+node_modules
+
+*.project
+*.temp
+*.log
+*.cmd
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7a795f5..d43ed98 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,179 @@
-# 版本日志
+# amWiki 版本日志
+
+## [v1.2.1](https://github.com/TevinLi/amWiki/releases/tag/v1.2.1)
+_2017-08-08_
+
+- 改善 Mac 上点号开头的系统文件带来的诸多问题(例如导出报错等)([#32](https://github.com/TevinLi/amWiki/issues/32)、[#67](https://github.com/TevinLi/amWiki/issues/67))
+- 允许在 config 上重新定义 library 路径 ([#47](https://github.com/TevinLi/amWiki/issues/47))
+- 文章篇内目录显示优化 ([#33](https://github.com/TevinLi/amWiki/issues/33))
+- 文章篇内目录序号识别,已有序号不再增加序号 ([#33](https://github.com/TevinLi/amWiki/issues/33))
+- 基础 Markdown 语法扩充,实现文字飘红
+- 统一官网和文档中心网址
+- 修复几处显示细节
+
+## [v1.2.0](https://github.com/TevinLi/amWiki/releases/tag/v1.2.0)
+_2017-07-18_
+
+- 新增本地数据挂载模式,可以直接双击 index 在浏览器中访问整个文库
+ - Atom 平台和 Node-Npm 平台挂载数据操作绑定
+ - 本地页面挂载数据生成与嵌入模块
+ - 页面加载数据,页面打开时立即判断和读取挂载数据
+ - 改进搜索,网络不可用时不再用 worker 而是直接搜索
+ - disable 本地模式状态下的接口测试入口
+ - 隐藏本地模式状态下的重建缓存按钮
+- 基础 Markdown 语法扩充,支持 todo list 复选框 ([#56](https://github.com/TevinLi/amWiki/issues/56))
+- 终端本地服务器命令新增可关闭 amWiki 索引页的可选参数
+- 优化本地服务器容错性
+- 搜索面板结果列表点击不再跳转页面
+- 导航增加文档文件名转义,文档文件名允许使用语法符号`()[]'"`而不再冲突
+- 对带`&`符号的文件名在URL地址上用特殊转义替代,不会再被作为参数截断 ([#64](https://github.com/TevinLi/amWiki/issues/64))
+- 修复服务器文件系统为GBK编码时深目录无法读取的bug
+- 调整大量注释、调整一些变量名,标记私有与公有
+
+## [v1.1.3](https://github.com/TevinLi/amWiki/releases/tag/v1.1.3)
+_2017-06-25_
+
+- 增加大图预览模式第一期 ([#44](https://github.com/TevinLi/amWiki/issues/44))
+- Markdown 基础语法扩充,支持定义图片大小与对齐方式 ([#45](https://github.com/TevinLi/amWiki/issues/45))
+- 允许首页的文件名通过增加 `home-` 前缀来自定义
+- 修复图片拖拽丢失扩展名的问题 ([#58](https://github.com/TevinLi/amWiki/issues/58))
+- 更改 highlight.js 为完整版,支持 176 种语言语法高亮
+- 修复测试面板切换文档时的一个显示问题
+
+## [v1.1.2](https://github.com/TevinLi/amWiki/releases/tag/v1.1.2)
+_2017-06-03_
+
+- 允许在 config.json 上使用 imports 将额外的 css 和 js 文件嵌入页面 ([#43](https://github.com/TevinLi/amWiki/issues/43))
+- 允许本地 server 多开,端口被占用时累加端口号 ([#46](https://github.com/TevinLi/amWiki/issues/46))
+- 新增本地 server 索引页,本地根 url 不再 404
+- 修复创建时取消创建操作的一个问题
+- 调整一些文案
+
+## [v1.1.1](https://github.com/TevinLi/amWiki/releases/tag/v1.1.1)
+_2017-05-21_
+
+- 紧急修复 win 平台 npm 创建报错的问题
+
+## [v1.1.0](https://github.com/TevinLi/amWiki/releases/tag/v1.1.0)
+_2017-05-20_
+
+- 实现无限级导航 ([#5](https://github.com/TevinLi/amWiki/issues/5)、[#30](https://github.com/TevinLi/amWiki/pull/30))
+ - 实现导航水平滚动条,调和水平垂直两滚动条冲突
+ - 实现 PC、Mobi 端导航栏无限级梯度的层级显示
+ - 实现目录无限级深度树形与列表数据读取
+ - 实现无限级导航 Markdown 生成
+ - 实现无限级重复的排序 ID 检查
+ - 实现无限级图片粘贴、文件拖拽
+- 优化工作端查找 library 文件夹
+- 实现拖拽文件文件名汉字转拼音
+- 修复导航初始化时展开的一个问题
+
+---
+
+### v1.0.6
+_2017-05-05_
+
+- 修复未开启测试模块的文库 web 端无内容显示的问题
+- 修复导航未展开目录时显示不全只有一边的问题
+- 修复上一版本 npm 失误的发布造成 Linux 平台命令无效的问题
+- 移动端点击搜索按钮时关闭导航层 ([#40](https://github.com/TevinLi/amWiki/issues/40))
+
+### v1.0.5
+_2017-04-28_
+
+- 导航筛选功能增强
+ - 文件夹匹配
+ - 筛选多个词
+ - 忽略筛选词大小写
+ - 筛选条件非主动删除不消失
+ - 使用递归进行筛选,为无限级做准备 ([#5](https://github.com/TevinLi/amWiki/issues/5))
+- 修复web端在移动端点击导航导航不自动收起的问题 ([#40](https://github.com/TevinLi/amWiki/issues/40))
+- 修复web端测试面板在不关闭的条件下跳转文档后的一个显示问题
+- 菜单栏增加 `从剪切板粘贴截图` 项,调和与其他插件快捷键冲突 ([#41](https://github.com/TevinLi/amWiki/issues/41))
+
+### v1.0.4
+_2017-04-22_
+
+- 紧急修复命令行本地服务器不能启动的问题
+
+### v1.0.3
+_2017-04-22_
+
+- 基础 Markdown 语法扩展,支持 footnote 脚注 ([#37](https://github.com/TevinLi/amWiki/issues/37))
+- 修复 Windows 平台下 gitbash 执行命令结束报错的问题
+- 修复 Linux 平台下 `/usr/bin/env` 解释器调用失败的问题
+- 修复 Linux 平台下 create、version 报错的问题
+- 释放快捷键绑定 Ctrl+D、Ctrl+H ([#38](https://github.com/TevinLi/amWiki/issues/38))
+- 取消 readme 单篇长文介绍,仅保留安装方法,详细介绍引导至文档中心
+
+### v1.0.2
+_2017-04-04_
+
+- 命令行启动服务器允许使用自定义端口参数
+- 截图粘贴增强,QQ聊天、历史记录面板可复制单张图片直接粘入文档
+- 修复命令行板块创建文库操作 $navigation.md 缺失的问题
+- 修复本地 http 服务器 500 页面崩溃的问题
+- 修复 web 端打开带 hash 地址链接时,hash 无效的问题
+- 修复命令行显示版本号报错问题
+
+### v1.0.1
+_2017-03-30_
+
+- 修复 npm 渠道安装失败的问题
+- readme 添加 npm icon
+
+### v1.0.0
+_2017-03-30_
+
+- [ _祝贺 amWiki 项目一周岁_ ]
+- 构建基于 npm 的**命令行板块**,使用命令行可以同样创建维护文库
+- 重构原工作端,分离为**构建板块**与**编辑器板块**
+- 引入 ES6 异步编程,使用 Generator、yield、Thunk,将编辑器板块的 confirm 转为 confirm2,并与命令行板块的 confirm2 调用形式统一化
+- 使用 ES6 异步编程,对构建板块部分回调方法进行重构,优先使用 return 与异步编程而不是 callback
+- 使用 const/let 变量、解构赋值、箭头函数、Class 等 ES6 语法润色原工作端代码
+- 增加命令行、终端界面信息打印着色、帮助菜单、版本显示
+- 规范化部分公共方法注释
+
+---
+
+### v0.8.0
+_2016-12-30_
+
+- Web 端页面左右分栏滚动 ([#26](https://github.com/TevinLi/amWiki/issues/26))
+- 工作端解除部分判断对 F12 造成的限制 ([#27](https://github.com/TevinLi/amWiki/issues/27))
+
+---
+
+### v0.7.7
+_2016-12-19_
+
+- 全文库搜索,增加大小写忽略 ([#24](https://github.com/TevinLi/amWiki/issues/24))
+- 修复导出后,在 github wiki 页内目录无法跳转的问题 ([#20](https://github.com/TevinLi/amWiki/issues/20#issuecomment-266927563))
+- 修复导出时,html 格式的 img src 相对地址未转换问题 ([#23](https://github.com/TevinLi/amWiki/issues/23))
+- 修复导出时,链接与 a 标签相对地址未转换问题 ([#23](https://github.com/TevinLi/amWiki/issues/23))
+
+### v0.7.6
+_2016-12-11_
+
+- 增加文库导出模块,可以将文库导出为 Github Wiki 项目 (**[#20](https://github.com/TevinLi/amWiki/issues/20)**)
+- 允许 `.md` 文件文件名带空格 ([#19](https://github.com/TevinLi/amWiki/issues/19))
+- Web 移动端点击页内目录以外的区域将折叠目录 ([#22](https://github.com/TevinLi/amWiki/issues/22))
+- 修复某些情况下跳页面不返回顶部的问题 ([#21](https://github.com/TevinLi/amWiki/issues/21))
+
+### v0.7.5
+_2016-12-03_
+(none)
+
+### v0.7.4
+_2016-12-03_
+
+- 增加 Web 端右侧目录悬浮窗 ([#15](https://github.com/TevinLi/amWiki/issues/15))
+- 增加对 `[TOC]` 目录生成标签的支持 ([#15](https://github.com/TevinLi/amWiki/issues/15))
+- 修复工作端抽取页内目录含单双引号、圆括号时无法正确解析的问题 ([#16](https://github.com/TevinLi/amWiki/pull/16))
+- 修复工作端抽取页内目录无法跳转的问题 ([#15](https://github.com/TevinLi/amWiki/issues/15))
+- 修复工作端抽取页内目录时内容为空报错的问题 ([#13](https://github.com/TevinLi/amWiki/issues/13), [#14](https://github.com/TevinLi/amWiki/pull/14))
+- 修复 Web 端浏览器后退按钮报错问题
+- Atom 菜单栏添加 “关于amWiki” 按钮
### v0.7.3
_2016-10-28_
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 2fa2540..b14ba02 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,4 +1,4 @@
# amWiki 贡献指引
- 如果您是 coder,您的所有 issues 或 pull requests,请在[Github项目托管地址](https://github.com/TevinLi/amWiki)提交
-- 如果您是非技术人员,需要提问或建议,请加QQ群347125653
\ No newline at end of file
+- 如果您需要直接提问或建议,请加QQ群347125653
\ No newline at end of file
diff --git a/README.md b/README.md
index 421767c..51076ed 100644
--- a/README.md
+++ b/README.md
@@ -1,133 +1,69 @@
# amWiki 轻文库

-amWiki 是一款基于 Javascript 脚本语言、依赖 Atom 编辑器、使用 Markdown 标记语法的轻量级开源 wiki 文库系统。
-amWiki 致力于让大家可以更简单、更便捷的建设个人和团队文库系统!
+amWiki 是一款由 JS 开发、依赖 Atom 或 Nodejs-Npm 的 Markdown 轻量级前端化开源文库系统。
+amWiki 致力于让大家可以更简单、更便捷的建设个人和团队文库!
-**传送门:**
-[amWiki 项目 Github 托管地址](https://github.com/TevinLi/amWiki "amWiki项目Github托管地址")
-[ ](https://github.com/TevinLi/amWiki)
+[[fork amWiki on Github](https://github.com/TevinLi/amWiki)]
-### amWiki优势
+GitHub:
+[](https://github.com/TevinLi/amWiki "GitHub Stars") [](https://github.com/TevinLi/amWiki "GitHub Forks") [](https://github.com/TevinLi/amWiki "GitHub Open Issues") [](https://github.com/TevinLi/amWiki "GitHub Closed Issues")
+[](https://github.com/TevinLi/amWiki "GitHub Tag") [](https://atom.io/packages/amWiki "MIT License")
+APM:
+[](https://atom.io/packages/amWiki "APM Version") [](https://atom.io/packages/amWiki "APM Downloads")
+NPM:
+[](https://www.npmjs.com/package/amwiki "NPM Version") [](https://www.npmjs.com/package/amwiki "APM Downloads") [](https://www.npmjs.com/package/amwiki)
+
+
+
+## amWiki 的优势
- 文档系统采用 markdown 语法
- 不用数据库,文档使用 `.md` 格式保存本地文件
- 无需服务端开发,只需支持 http 静态访问网页空间
- 一键创建新的文库
- 自动更新文库导航目录
+- 支持多级目录
- 支持截图直接粘帖为本地 png 并插入当前 markdown
- 文档web端自适应显示,适合所有平台
- 支持接口文档自动抓取内容生成简单的ajax测试
+- 无需服务端的全文库标题内容搜索与计分排序
- ... (更多内容期待您的发现)
-### web端效果演示
-一键创建新文库将自动生成一套Web端html页面,默认生成效果一览:[https://tevinli.github.io/amWiki/](https://tevinli.github.io/amWiki/index.html)
+
-## 如何开始
+## 安装 amWiki
+amWiki 可以同时在 Atom 编辑器和 nodejs npm 的命令行两个平台工作,两个平台的工作相互独立,但所创建的文库却可以相互共用
+(PS:对这两个平台的依赖都是编辑需求而不是服务器需求,amWiki 创建的文库是纯静态 html,可以布置到任意服务器)
-1. 下载Github开源文本编辑器 [Atom 官网](https://atom.io/ "Atom官网"),并安装
+### 作为 Atom 插件安装
+1. 下载 Github 开源文本编辑器 [Atom](https://atom.io/ "Atom官网"),并安装
+2. 安装 Atom 插件 amWiki,并在完成后重启 Atom
+ - 前往 Github 的 [amWiki版本发布页](https://github.com/TevinLi/amWiki/releases) 下载最新版压缩包,解压到 C:\Users\Administrator\.atom\packages,并将文件夹名 `amWiki-1.x.x` 改为 `amWiki`
+ - 或者,Atom 菜单,File -> Setting -> Install -> 搜索 `amWiki` -> 找到 amWiki 并 Install
+ - 或者,在 cmd 或终端中命令:`apm install amWiki`(_第三字母 W 大写_)
+3. 在 Atom 菜单,File -> `Add Project Folder` 添加一个项目文件夹
+4. 在此文件夹下创建一个名为 `config.json` 的文件
+5. 在 Atom 菜单,amWiki轻文库 -> 通过“config.json”创建新文库
-2. 安装Atom插件amWiki,并重启Atom
- - Atom菜单,File -> Setting -> Install -> 搜索`amWiki`
- - 或者,运行cmd:`apm install amWiki`
- - 或者,从Github的 [amWiki版本发布](https://github.com/TevinLi/amWiki/releases) 下载zip,解压到`C:\Users\Administrator\.atom\packages`,并将文件夹名`amWiki-master`改为`amWiki`
+### 作为 nodejs 全局模块安装
+1. 下载 [nodejs](https://nodejs.org/) 并安装
+2. 执行命令: `npm install -g amwiki`(_第三字母 w 小写_)
+3. cd 到某个文件夹,通过命令 `amwiki create` 创建文库
+4. 通过命令 `amwiki help` 查看帮助
-3. (在本地服务器静态目录)创建一个文件夹
+
-4. 在Atom中 **添加项目文件夹**,并指向刚创建的文件夹
+## amWiki 教程 & 文档
+更多详细功能与使用介绍请前往: [ [amWiki 官网文档中心:amwiki.org/doc/](http://amwiki.org/doc/) ]
+(amWiki 的文档由项目自身创建与维护)
-5. 在Atom刚创建的项目下新建`config.json`文件,并输入一下内容:
- ```javascript
- {
- "name": "", //您的文库名称,设置但为空或不设置将显示默认名
- "ver": "", //文本版本号或维护者名号,设置但为空将不显示,注意诺不设置此属性将显示amWiki作者
- "logo": "", //logo的url,设置但为空或不设置将显示默认logo
- "testing": true, //是否启用接口测试,默认值false
- "colour": "" //自定义颜色,默认为蓝色
- }
- ```
-6. 点击Atom菜单:
-
- Packages -> amWiki文库 -> 通过“config.json”创建新文库
-
-7. 此时项目中自动创建了许多内容,其中 library 文件夹即为您的文档库,编辑您的文库文档
-
-8. 使用 F12 启动本地静态服务器,访问刚刚自动创建的 index.html
-
-
-## 目录结构
-自动生创建的内容目录如下
-
- index.html //http访问首页
- amWiki/ //amWiki工作文件目录
- library/ //您的markdown文库目录,所有文件必须使用.md格式
- ├ $navigation.md //amWiki文库目录导航文件,可自动/手动更新
- ├ 首页.md //内容区默认显示内容
- ├ 01-关于amWiki文库/ //markdown文件夹01
- │ ├ 001-关于amWiki //一些markdown文档,支持二级目录
- │ └ 002-...
- ├ 02-学习markdown/ //markdown文件夹02
- │ ├ 001-md入门 //一些markdown文档,支持二级目录
- │ └ 002-...
- └ 03-... //更多markdown文件夹
- (assetes/) //如果您粘帖截图,图片文件将自动创建在此处
-
-
-## 如何编辑
-1. 新建或更改文件夹和文档名,组织您自己的文档结构 (文档必须markdown文档、**使用.md扩展名**)
-
-2. 编辑markdown文档,制作您自己的文档内容
-
-3. 每个文件夹或文件使用“id-名称”来实现命名排序,请勿删除id,删除后将无法正常工作
- id仅允许整数或浮点数类型,且使用连接符或下划线与具体名称组成命名
-
-4. library文件夹下`首页.md`为打开时默认的或url参数错误时的显示内容
-
-5. `$navigation.md`导航文件无需人工维护,创建新文件夹或文件时将自动更新,也可以在菜单栏手动刷新:
-
- 菜单栏 -> amWiki文库 -> 手动更新当前文库导航文件
-
-6. 如果需要在markdown文档中插入图片,**请先截图**,然后在.md文档对应位置使用快捷键:`Ctrl + Shift + V`
-
-7. 对于较长文章,可以使用页内目录,依次进行如下操作,或使用快捷键`Ctrl + D`,即可在光标处插入页内目录。
- 菜单栏 -> amWiki文库 -> 提取h2、h3标题为页内目录
-注意:请按顺序使用h1、h2、h3,且h1仅使用一次。
-
-8. 如果启用了测试,对于文档中**同时存在“请求地址”“请求类型”“请求参数”三个h3标题**的文档,自动在右上角激活接口测试功能
- - 请求参数的表格,请按`参数名、类型、必填、描述、默认值`的顺序建立表格,否则不能正常抓取
- - 只能请求同域接口,不能跨域
- - 如果接口需要登录权限,请先登录您自己的系统
-
-
-9. 本插件升级后,您想更新`项目/amWiki/`文件夹下web端的工作文件,您只需重新打开`config.json`文件,然后在Atom菜单上选择`通过“config.json”创建新文库`即可。
-这个二次创建操作不会影响您 library 与 assetes 文件夹下的内容。
-
-10. 借助版本管理 SVN、Git、Hg,传输协议FTP/SFTP,文件同步Dropbox、百度云等等工具,便捷实现网络访问。
-
-
-## 如何使用
-
-1. 使用 http 访问项目的 index.html。
-
-2. PC端使用左侧导航栏、移动端使用右上角导航弹出菜单切换页面。
-
-3. 在导航栏顶部,可以使用筛选功能整个导航目录进行筛选。
-
-4. 如果存在页内目录,直接点击,页内目录使用hash滚动;同时您可以直接带hash分享,以方便他人快速浏览指定内容。
-
-5. 如果页面激活了接口测试功能,点击右上角“测试接口”打开测试面板,输出参数并点击“发送Ajax”,即可看到响应内容。
-
-
-## 后记
-这个项目的初始灵感来自 MDwiki,这是一个使用 html5/js 纯静态的 markdown wiki 系统。
-一开始,我也准备使用它来写一些文档,但是我很快发现,如果我新增一篇文档,需要我手动去编辑导航栏文件增加一条导航,这个体验并不友好。然后想写一套类似 [微信公众平台开发者文档](http://mp.weixin.qq.com/wiki/home/index.html) 这样较大的文档库,发现 MDwiki 由于其容量所限,并不能很好的胜任。
-于是经过一番折腾,就有了amWiki。
-个人开源不易,欢迎来github上star支持一下,如果发现问题或好的建议,请尽量在github上发issues,不胜感谢!
+
## 更新日志
### 如何获取最新版本信息与动态?
-- 欢迎添加作者微信为好友,微信号 **XF90_Net**
- _(大家加微信的时候都尽量备注一下原因吧,没有备注的不加哦!目前主要集中精力在开发,暂不接受各种调戏跟闲聊~)_
-- 其他途径: QQ群`347125653` / [微博](http://weibo.com/1706602953/profile?is_all=1) / [Twitter](https://twitter.com/tevinli2009)
+- QQ群 **347125653**
+- 如果可以,欢迎直接加我微信
+ 
### 如何查看历史版本日志?
[amWiki 版本更新日志](https://github.com/TevinLi/amWiki/blob/master/CHANGELOG.md "amWiki版本更新日志")
diff --git a/SCHEDULE.md b/SCHEDULE.md
new file mode 100644
index 0000000..2f53980
--- /dev/null
+++ b/SCHEDULE.md
@@ -0,0 +1,15 @@
+# amWiki 开发计划表
+
+## v1.2.1
+
+- 增加修改 web 端 library 目录路径的配置项 #47
+- markdown 基础语法扩展,允许使用高亮标签对文字飘红
+- 目录生成优化,识别标题是否已经带有序号 #33
+
+## v2.0.0 _(under way)_
+
+- 重新设计 Web 端架构,大幅强化可扩展性
+ - 设计可扩展的管理、通讯、调用机制
+ - 设计 Web 端插件模块机制
+ - 设计 Web 端自定义主题模块机制
+ - 分离原有功能为独立模块
diff --git a/bin/main.js b/bin/main.js
new file mode 100644
index 0000000..c23cfe2
--- /dev/null
+++ b/bin/main.js
@@ -0,0 +1,186 @@
+#!/usr/bin/env node
+
+/**
+ * 工作端 - 终端(命令行) - 入口模块
+ */
+
+const fs = require('fs');
+
+//获取命令内容
+let [nodePath, mainPath, command, ...parameters] = process.argv;
+//转换 linux 下软链接路径为真实路径
+mainPath = fs.realpathSync(mainPath);
+
+// co 模块,异步函数编程
+const co = require('../modules/co');
+
+//在全局变量上绑定 alert、confirm2、prompt2 方法,作为命令行输出与交互
+({alert: global.alert, confirm2: global.confirm2, prompt2: global.prompt2} = require('./messageBox'));
+
+// wiki 创建器
+const creator = require('../build/creator');
+//文库管理
+const mngWiki = require('../build/manageWiki');
+//文件夹管理
+const mngFolder = require('../build/manageFolder');
+//手动刷新导航文件
+const makeNav = require('../build/makeNavigation');
+//手动刷新页面挂载数据
+const makeMut = require('../build/makeMounts');
+//本地服务器模块
+const localServer = require('../build/localServer');
+//项目导出模块
+const exportGithub = require('../build/exportGithub');
+
+//格式化显示
+const printFn = require('./printf');
+
+//读取项目包配置
+const packageConf = JSON.parse(fs.readFileSync(mainPath.split(/bin[\\\/]main/)[0] + 'package.json', 'utf-8'));
+
+//项目根目录
+const root = mngFolder.isAmWiki(process.cwd());
+const wikis = {};
+
+//注册文库
+mngWiki.linkWikis(wikis);
+if (root) {
+ mngWiki.addWiki(root);
+}
+
+co(function*() {
+
+ //执行命令
+ switch (command) {
+ //依据引导创建 wiki
+ case 'init':
+ break;
+ //依据 config.json 创建 wiki
+ case 'create':
+ case '-c':
+ // config.json 文件路径
+ const configPath = process.cwd().replace(/\\/g, '/') + '/config.json';
+ //项目 files 文件夹路径
+ const filesPath = mainPath.replace(/\\/g, '/').split('bin')[0] + 'files/';
+ //开始创建
+ const root2 = yield creator.create(configPath, filesPath, packageConf);
+ if (!root2) {
+ break;
+ }
+ //更新导航
+ makeNav.refresh(root2 + 'library/');
+ makeMut.make(root, true);
+ break;
+ //更新 wiki
+ case 'update':
+ case '-u':
+ if (!root) {
+ console.error('非 amWiki 项目文件夹,无法更新导航!');
+ break;
+ }
+ const type = parameters[0];
+ //更新导航
+ if (type === 'nav') {
+ makeNav.refresh(root);
+ }
+ //更新文库嵌入数据
+ else if (type === 'mut') {
+ makeMut.make(root);
+ }
+ //更新 SEO 模块
+ else if (type === 'seo') {
+ }
+ //完整更新
+ else if (typeof type === 'undefined') {
+ makeNav.refresh(root);
+ makeMut.make(root, true);
+ }
+ break;
+ //启动本地服务器
+ case 'server':
+ case '-s':
+ if (!root) {
+ console.error('非 amWiki 项目文件夹,无法启动服务器!');
+ break;
+ }
+ const port = parameters[0];
+ const noIndex = parameters[1] || '';
+ //有输入端口
+ if (typeof port !== 'undefined') {
+ //端口合法
+ if (/^\d+$/.test(port)) {
+ yield localServer.run(wikis, port);
+ if (noIndex === 'no-index') {
+ localServer.setOffIndex();
+ }
+ }
+ //端口写成 noIndex
+ else {
+ yield localServer.run(wikis);
+ if (port === 'no-index' || noIndex === 'no-index') {
+ localServer.setOffIndex();
+ }
+ }
+ }
+ //没输入端口号
+ else {
+ yield localServer.run(wikis);
+ }
+ break;
+ //本地浏览文档
+ case 'browser':
+ case '-b':
+ if (!root) {
+ console.error('非 amWiki 项目文件夹,无法浏览文库!');
+ break;
+ }
+ const fileId = parameters[0];
+ //未给出需要浏览的文档id
+ if (typeof fileId === 'undefined') {
+ yield localServer.browser(root + 'library/', wikis);
+ }
+ //有给出需要浏览的文档id
+ else {
+ }
+ break;
+ //导出 wiki
+ case 'export':
+ case '-e':
+ if (!root) {
+ console.error('非amWiki项目文件夹,无法导出!');
+ break;
+ }
+ if (parameters[0] !== 'github-wiki') {
+ console.error('除 github-wiki 外,目前不支持其他类型导出!');
+ break;
+ }
+ let outPath = parameters[1];
+ if (typeof outPath === 'undefined') {
+ outPath = yield prompt2('请输入导出文件夹:');
+ }
+ yield exportGithub.export(root, outPath.replace(/\\/g, '/'));
+ break;
+ //显示版本号
+ case 'version':
+ case '-v':
+ printFn.ver(packageConf);
+ break;
+ //显示帮助
+ case 'help':
+ case '-h':
+ default:
+ printFn.help();
+ break;
+ }
+
+ //关闭用户输入
+ process.stdin.on('error', (e) => {
+ if (e.code !== 'EPIPE' || e.syscall !== 'shutdown') {
+ throw e;
+ }
+ });
+ process.stdin.end();
+
+}).catch((e) => {
+ console.error(e);
+});
diff --git a/bin/messageBox.js b/bin/messageBox.js
new file mode 100644
index 0000000..4056e7f
--- /dev/null
+++ b/bin/messageBox.js
@@ -0,0 +1,85 @@
+/**
+ * 工作端 - 终端(命令行) - 模拟浏览器 alert、confirm、prompt 方法进行信息交互
+ * @author Tevin
+ *
+ * @example
+ * co(function* () {
+ * const c = yield confirm2('测试confirm2');
+ * console.log(c);
+ * const p = yield prompt2('测试prompt2');
+ * console.log(p);
+ * process.stdin.end();
+ * });
+ *
+ */
+
+/**
+ * 模拟消息框
+ * @param {String} msg
+ */
+const alert = (msg) => {
+ msg = typeof msg !== 'string' ? String(msg) : msg;
+ process.stdout.write(msg + '\n')
+};
+
+/**
+ * 模拟确认框,回调传参为用户选择的布尔值
+ * @param {String} msg
+ * @returns {Promise}
+ */
+const confirm2 = (msg) => {
+ return new Promise((resolve, reject) => {
+ msg = typeof msg !== 'string' ? String(msg) : msg;
+ let readStdin = () => {
+ let chunk = process.stdin.read();
+ if (chunk !== null) {
+ chunk = chunk.replace(/[\n\r\u0004]*$/, '');
+ if (/^(y|yes|ok)$/i.test(chunk)) {
+ resolve(true);
+ } else {
+ resolve(false);
+ }
+ readStdin = null;
+ }
+ };
+ process.stdout.write(msg + '(y/n) ');
+ process.stdin.setEncoding('utf8');
+ process.stdin.on('readable', () => {
+ readStdin && readStdin();
+ });
+ });
+};
+
+/**
+ * 模拟输入框,回调传参为用户输入的字符串
+ * @param {String} msg
+ * @returns {Promise}
+ */
+const prompt2 = (msg) => {
+ return new Promise((resolve, reject) => {
+ msg = typeof msg !== 'string' ? String(msg) : msg;
+ let readStdin = () => {
+ let chunk = process.stdin.read();
+ if (chunk !== null) {
+ chunk = chunk.replace(/[\n\r\u0004]*$/, '');
+ resolve(chunk);
+ readStdin = null;
+ }
+ };
+ process.stdout.write(msg + ' ');
+ process.stdin.setEncoding('utf8');
+ process.stdin.on('readable', () => {
+ readStdin && readStdin();
+ });
+ });
+};
+
+const messageBox = (function () {
+ return {
+ alert: alert,
+ confirm2: confirm2,
+ prompt2: prompt2
+ };
+})();
+
+module.exports = messageBox;
\ No newline at end of file
diff --git a/bin/printf.js b/bin/printf.js
new file mode 100644
index 0000000..249eb4a
--- /dev/null
+++ b/bin/printf.js
@@ -0,0 +1,140 @@
+/**
+ * 工作端 - 终端(命令行) - 着色打印
+ * @author Tevin
+ */
+
+const fs = require('fs');
+
+//命令行彩色输出简化,command line color
+const clc = (colorId, text) => `\x1b[${colorId}m${text}\x1b[0m`;
+
+//文件与文件夹创建操作着色打印
+const {mkdir, mkdirSync, writeFile, writeFileSync} = fs;
+fs.mkdir = (path, mode, callback) => {
+ alert(` ${clc(36, 'create')} ${path}`);
+ return mkdir(path, mode, callback);
+};
+fs.mkdirSync = (path, mode) => {
+ alert(` ${clc(36, 'create')} ${path}`);
+ return mkdirSync(path, mode);
+};
+fs.writeFile = (path, data, options, callback) => {
+ alert(` ${clc(36, 'create')} ${path}`);
+ return writeFile(path, data, options, callback);
+};
+fs.writeFileSync = (path, data, options) => {
+ alert(` ${clc(36, 'create')} ${path}`);
+ return writeFileSync(path, data, options);
+};
+
+//文件与文件夹删除操作着色打印
+const {rmdir, rmdirSync, unlink, unlinkSync} = fs;
+fs.rmdir = (path, callback) => {
+ alert(` ${clc(31, 'delete')} ${path}`);
+ return rmdir(path, callback);
+};
+fs.rmdirSync = (path) => {
+ alert(` ${clc(31, 'delete')} ${path}`);
+ return rmdirSync(path);
+};
+fs.unlink = (path, callback) => {
+ alert(` ${clc(31, 'delete')} ${path}`);
+ return unlink(path, callback);
+};
+fs.unlinkSync = (path) => {
+ alert(` ${clc(31, 'delete')} ${path}`);
+ return unlinkSync(path);
+};
+
+//终端输出操作着色打印
+const {info, warn, error} = console;
+console.info = (...content) => {
+ return console.log(`${clc(46, 'INFO:')}`, ...content);
+};
+console.warn = (...content) => {
+ return console.log(`${clc(43, 'WARN:')}`, ...content);
+};
+console.error = (...content) => {
+ return console.log(`${clc(41, 'ERROR:')}`, ...content);
+};
+
+//帮助文本
+const helpWords = `
+用法:
+ amwiki [command] [arguments]
+
+命令列表:
+ -h, help 显示本帮助
+ -v, version 显示版本号
+ -c, create 创建 wiki
+ -u, update 更新 wiki 数据,默认根据文库配置选择适当内容更新
+ 可选参数:
+ [nav|mut]
+ 指定仅更新导航
+ 指定仅更新页面挂载数据
+ ${clc(90, '(eg: amwik update nav)')}
+ -s, server 启动本地服务器,默认本地服务器使用端口号 5171
+ 可选参数:
+ [8080] 重新指定端口号
+ [no-index] 关闭 amwiki 索引页
+ ${clc(90, '(eg: amwiki server 8080 no-index)')}
+ -b, browser 本地浏览文档,默认访问文库首页
+ 可选参数:
+ [002/001] 按目录文档的id打开指定文档(待开发)
+ ${clc(90, '(eg: amwiki browser 002/001)')}
+ -e, export amwiki 项目导出
+ 必需参数:
+ github-wiki 指定导出类型为 github 项目附属 wiki
+ d:\/your-path 指定本次导出的本地输出路径
+ ${clc(90, '(eg: amwiki export github-wiki d:\/your-path)')}
+
+更多文档:
+ http://amwiki.org/doc/
+`;
+//[seo] 仅更新SEO模块(待开发)
+
+//amWiki logo
+const vesWords = `
+ kk kk kk kk ww
+ wwwwww www www kk kk kk
+ ww ww ww ww kk kk kk kk kk kk kk
+ wwwwwww ww ww ww kk kkkk kk kk kk kk kk
+ww ww ww ww ww kk kk kk kk kk kkkk kk
+ wwwwwww ww ww ww kk kk kk kk kk kk
+
+`.replace(/w/g, `${clc(32, '$')}`).replace(/k/g, `${clc(36, '$')}`);
+
+const printf = (function () {
+ return {
+ /**
+ * 命令行彩色输出
+ * @public
+ */
+ clc: clc,
+ /**
+ * 显示帮助信息
+ * @public
+ */
+ help: function () {
+ alert(helpWords);
+ },
+ /**
+ * 显示版本信息
+ * @param {Object} packageConf
+ * @public
+ */
+ ver: function (packageConf) {
+ const text = [
+ 'Package: amWiki',
+ `Version: ${clc(0, packageConf.version)}`,
+ 'Author: Tevin Li',
+ 'HomePage: amwiki.org',
+ 'Fork: github.com/tevinli/amWiki',
+ 'License: MIT'
+ ];
+ alert(vesWords + text.join('\n') + '\n');
+ }
+ };
+})();
+
+module.exports = printf;
\ No newline at end of file
diff --git a/build/creator.js b/build/creator.js
new file mode 100644
index 0000000..0e42c3e
--- /dev/null
+++ b/build/creator.js
@@ -0,0 +1,333 @@
+/**
+ * 工作端 - 输出 web 静态资源模块
+ * @author Tevin
+ */
+
+const fs = require('fs');
+const co = require('../modules/co');
+const mngWiki = require('./manageWiki');
+const mngFolder = require('./manageFolder');
+const makeNav = require('./makeNavigation');
+
+const creator = (function () {
+ return {
+ /**
+ * 复制文件
+ * @param {String} from
+ * @param {String} to
+ * @private
+ */
+ _copyFile: function (from, to) {
+ fs.createReadStream(from).pipe(fs.createWriteStream(to));
+ },
+ /**
+ * 创建 amWiki 必要的文件夹
+ * @param {String} outputPath
+ * @returns {Boolean}
+ * @private
+ */
+ _createWikiFolder: function (outputPath) {
+ if (!fs.existsSync(outputPath + 'amWiki/')) {
+ fs.mkdirSync(outputPath + 'amWiki/', 0o777);
+ }
+ if (!fs.existsSync(outputPath + 'amWiki/js/')) {
+ fs.mkdirSync(outputPath + 'amWiki/js/', 0o777);
+ }
+ if (!fs.existsSync(outputPath + 'amWiki/css/')) {
+ fs.mkdirSync(outputPath + 'amWiki/css/', 0o777);
+ }
+ if (!fs.existsSync(outputPath + 'amWiki/images/')) {
+ fs.mkdirSync(outputPath + 'amWiki/images/', 0o777);
+ }
+ if (!fs.existsSync(outputPath + 'library/')) {
+ fs.mkdirSync(outputPath + 'library/', 0o777);
+ if (!fs.existsSync(outputPath + 'library/001-学习amWiki/')) {
+ fs.mkdirSync(outputPath + 'library/001-学习amWiki/', 0o777);
+ }
+ if (!fs.existsSync(outputPath + 'library/001-学习amWiki/05-学习markdown/')) {
+ fs.mkdirSync(outputPath + 'library/001-学习amWiki/05-学习markdown/', 0o777);
+ }
+ if (!fs.existsSync(outputPath + 'library/002-文档示范/')) {
+ fs.mkdirSync(outputPath + 'library/002-文档示范/', 0o777);
+ }
+ return false;
+ }
+ return true;
+ },
+ /**
+ * 创建着色色系
+ * (ps: 颜色变亮变暗不是 rgb 分别同时增加或减少一个值)
+ * @param {String} color
+ * @returns {{base: String, light: String, dark: String}} - 生成的浅、中、深三色
+ * @private
+ */
+ _clacWikiColour: function (color) {
+ const colours = {};
+ //修复16进制下的1位数
+ const lessThan = (str) => {
+ if (str <= 9) {
+ str = '0' + str;
+ } else {
+ const num = parseInt('0x' + str);
+ if (num >= 10 && num <= 15) {
+ str = '0' + str;
+ }
+ }
+ return str;
+ };
+ //颜色合法检查
+ const atBinary16 = /^#([0-9a-fA-F]{3}){1,2}$/.test(color);
+ const atRGB = /^[rR][gG][bB]([aA])?\(\d{1,3},\d{1,3},\d{1,3}(,.*?)?\)$/.test(color);
+ const atList = /(blue|default)/.test(color);
+ if (!atBinary16 && !atRGB && !atList) {
+ alert('请使用标准16进制颜色、标准RGB颜色、2种特定颜色名指定颜色!本次创建使用默认颜色');
+ return this._clacWikiColour('default');
+ }
+ //颜色名称转色值
+ const colorList = {
+ default: '#4296eb',
+ blue: '#4296eb'
+ };
+ if (atList) {
+ color = colorList[color];
+ }
+ //解析颜色为rgb
+ const rgb = [];
+ if (color.indexOf('#') === 0) {
+ rgb[0] = parseInt('0x' + color.substr(1, 2));
+ rgb[1] = parseInt('0x' + color.substr(3, 2));
+ rgb[2] = parseInt('0x' + color.substr(5, 2));
+ } else {
+ const rgbStr = color.split('(')[1].split(')')[0].split(',');
+ rgb[0] = parseInt(rgbStr[0]);
+ rgb[1] = parseInt(rgbStr[1]);
+ rgb[2] = parseInt(rgbStr[2]);
+ }
+ //生成主色base
+ colours.base = '#' + [
+ lessThan(rgb[0].toString(16)),
+ lessThan(rgb[1].toString(16)),
+ lessThan(rgb[2].toString(16))
+ ].join('');
+ //颜色变化量,越小的值变化越大
+ const increment = [
+ Math.round((255 - rgb[0]) / 3),
+ Math.round((255 - rgb[1]) / 3),
+ Math.round((255 - rgb[2]) / 3)
+ ];
+ //生成亮色light
+ colours.light = '#' + [
+ (rgb[0] + increment[0]).toString(16),
+ (rgb[1] + increment[1]).toString(16),
+ (rgb[2] + increment[2]).toString(16)
+ ].join('');
+ //生成暗色dark
+ let larger = 0,
+ largeIndex = -1;
+ const largerFd = (i) => {
+ if (increment[i] > larger) {
+ larger = increment[i];
+ largeIndex = i;
+ }
+ };
+ largerFd(0);
+ largerFd(1);
+ largerFd(2);
+ if (rgb[largeIndex] - larger < 0) {
+ //修复加深颜色值为负的情况,最多减到0,其他三色等比例减小
+ const reduce = (rgb[largeIndex] - larger) / larger;
+ increment[0] += Math.round(increment[0] * reduce);
+ increment[1] += Math.round(increment[1] * reduce);
+ increment[2] += Math.round(increment[2] * reduce);
+ }
+ colours.dark = '#' + [
+ lessThan((rgb[0] - increment[0]).toString(16)),
+ lessThan((rgb[1] - increment[1]).toString(16)),
+ lessThan((rgb[2] - increment[2]).toString(16))
+ ].join('');
+ return colours;
+ },
+ /**
+ * 配置检查
+ * @param {String} configPath
+ * @param {String} filesPath
+ * @returns {Promise}
+ * @private
+ */
+ _checkConfig: function (configPath, filesPath) {
+ const that = this;
+ return co(function*() {
+ const options = {};
+ //创建路径
+ options.filesPath = filesPath;
+ options.outputPath = configPath.split('config.json')[0].replace(/\\/g, '/');
+ //读取配置
+ let configStr = '';
+ let noConfig = false;
+ if (fs.existsSync(configPath)) {
+ configStr = fs.readFileSync(configPath, 'utf-8') || '';
+ } else {
+ noConfig = true;
+ }
+ if (configStr.length === 0) {
+ if (yield confirm2('没有读取到任何配置,继续创建么?')) {
+ configStr = '{}';
+ if (noConfig) {
+ fs.writeFileSync(configPath, configStr, 'utf-8');
+ }
+ } else {
+ return;
+ }
+ }
+ //解析默认配置
+ let parseOk = true,
+ config = null;
+ try {
+ config = JSON.parse(configStr);
+ } catch (e) {
+ alert('配置解析失败,请检查您的 config.json 是否有严格按 Json 格式书写!\n错误消息:' + e.message);
+ parseOk = false;
+ }
+ if (!parseOk) {
+ return;
+ }
+ return {
+ options: options,
+ config: mngWiki.parseConfig(config)
+ };
+ }).catch((e) => {
+ console.error(e);
+ });
+ },
+ /**
+ * 创建amWiki本地文件
+ * @param {String} configPath - config.json文件的路径
+ * @param {String} filesPath - 项目包files文件夹路径
+ * @returns {Promise} 项目根目录
+ * @public
+ */
+ create: function (configPath, filesPath) {
+ const that = this;
+ return co(function*() {
+ const {options, config} = yield that._checkConfig(configPath, filesPath);
+ //开始创建
+ const files = mngFolder.readFolder(options.outputPath);
+ if (files.length > 1) {
+ if (!(yield confirm2('此处已有一些文件或文件夹,是否仍然在此创建amWiki?'))) {
+ return false;
+ }
+ }
+ //创建index.html
+ let indexPage = fs.readFileSync(options.filesPath + 'amWiki.tpl', 'utf-8');
+ indexPage = indexPage.replace(/\{\{name\}\}/g, config.name)
+ .replace('{{version}}', config.version)
+ .replace('{{logo}}', config.logo);
+ //嵌入配置
+ indexPage = indexPage.replace('{{config}}', 'AWConfig=' + JSON.stringify(config));
+ //测试模块
+ if (config.testing) {
+ const testingTpl = fs.readFileSync(options.filesPath + 'amWiki.testing.tpl', 'utf-8');
+ const testingScript = '';
+ indexPage = indexPage
+ .replace('{{amWiki.testing.tpl}}', testingTpl)
+ .replace('{{amWiki.testing.js}}', testingScript);
+ } else {
+ indexPage = indexPage
+ .replace('{{amWiki.testing.tpl}}', '')
+ .replace('{{amWiki.testing.js}}', '');
+ }
+ //自定义 css、js 嵌入
+ if (config.imports) {
+ let item,
+ cssList = '',
+ jsList = '';
+ for (item of config.imports.css) {
+ cssList = ''
+ }
+ for (item of config.imports.js) {
+ jsList = ''
+ }
+ indexPage = indexPage
+ .replace('{{custom.css}}', cssList)
+ .replace('{{custom.js}}', jsList);
+ } else {
+ indexPage = indexPage
+ .replace('{{custom.css}}', '')
+ .replace('{{custom.js}}', '');
+ }
+ fs.writeFileSync(options.outputPath + 'index.html', indexPage, 'utf-8');
+ //创建文件夹
+ const hasLibrary = that._createWikiFolder(options.outputPath);
+ //创建amWiki.css
+ let wikiCss = fs.readFileSync(options.filesPath + 'amWiki.css', 'utf-8');
+ if (config.testing) {
+ wikiCss += fs.readFileSync(options.filesPath + 'amWiki.testing.css', 'utf-8');
+ }
+ wikiCss += fs.readFileSync(options.filesPath + 'amWiki.print.css', 'utf-8');
+ wikiCss += fs.readFileSync(options.filesPath + 'amWiki.search.css', 'utf-8');
+ let colors = that._clacWikiColour(config.colour);
+ wikiCss = wikiCss
+ .replace(/@colour-base/g, colors.base)
+ .replace(/@colour-light/g, colors.light)
+ .replace(/@colour-dark/g, colors.dark);
+ fs.writeFileSync(options.outputPath + 'amWiki/css/amWiki.css', wikiCss, 'utf-8');
+ //拷贝页面资源
+ const fileList = [
+ ['markdownbody.github.css', 'amWiki/css/markdownbody.github.css'],
+ ['lhjs.github-gist.css', 'amWiki/css/lhjs.github-gist.css'],
+ ['gbk.js', 'amWiki/js/gbk.js'],
+ ['pinyin.js', 'amWiki/js/pinyin.js'],
+ ['flowchart.min.js', 'amWiki/js/flowchart.min.js'],
+ ['raphael-min.js', 'amWiki/js/raphael-min.js'],
+ ['jquery-compat-3.1.0.min.js', 'amWiki/js/jquery-compat-3.1.0.min.js'],
+ ['marked.min.js', 'amWiki/js/marked.min.js'],
+ ['highlight.pack.js', 'amWiki/js/highlight.pack.js'],
+ ['amWiki.js', 'amWiki/js/amWiki.js'],
+ ['amWiki.scrollbar.js', 'amWiki/js/amWiki.scrollbar.js'],
+ ['amWiki.imgsView.js', 'amWiki/js/amWiki.imgsView.js'],
+ ['amWiki.testing.js', 'amWiki/js/amWiki.testing.js'],
+ ['amWiki.tools.js', 'amWiki/js/amWiki.tools.js'],
+ ['amWiki.storage.js', 'amWiki/js/amWiki.storage.js'],
+ ['amWiki.docs.js', 'amWiki/js/amWiki.docs.js'],
+ ['amWiki.search.js', 'amWiki/js/amWiki.search.js'],
+ ['amWiki.search.worker.js', 'amWiki/js/amWiki.search.worker.js'],
+ ['icons.svg', 'amWiki/images/icons.svg'],
+ ['logo.png', 'amWiki/images/logo.png'],
+ ['menubar_bg.png', 'amWiki/images/menubar_bg.png']
+ ];
+ for (let file of fileList) {
+ that._copyFile(options.filesPath + file[0], options.outputPath + file[1]);
+ }
+ //如果没有library则复制一套默认文档
+ if (!hasLibrary) {
+ //首页文档
+ const home = fs.readFileSync(options.filesPath + 'doc.home.md', 'utf-8').replace('{{name}}', config.name);
+ fs.writeFileSync(options.outputPath + 'library/home-首页.md', home, 'utf-8');
+ //其他页面文档
+ let fileList2 = [
+ ['doc.amwiki-introduce.md', 'library/001-学习amWiki/01-amWiki轻文库简介.md'],
+ ['doc.amwiki-mind-map.md', 'library/001-学习amWiki/02-amWiki功能导图.md'],
+ ['doc.amwiki-new.md', 'library/001-学习amWiki/03-如何开始一个新amWiki轻文库.md'],
+ ['doc.amwiki-edit.md', 'library/001-学习amWiki/04-如何编辑amWiki轻文库.md'],
+ ['doc.amwiki-testing.md', 'library/001-学习amWiki/06-使用测试模块测试接口.md'],
+ ['doc.amwiki-test-cross.md', 'library/001-学习amWiki/07-amWiki转接到任意域名进行接口测试.md'],
+ ['doc.md-start.md', 'library/001-学习amWiki/05-学习markdown/01-Markdown快速开始.md'],
+ ['doc.md-high-lighting.md', 'library/001-学习amWiki/05-学习markdown/02-amWiki与语法高亮.md'],
+ ['doc.md-flow-chart.md', 'library/001-学习amWiki/05-学习markdown/03-amWiki与流程图.md'],
+ ['doc.md-atom.md', 'library/001-学习amWiki/05-学习markdown/05-Atom对Markdown的原生支持.md'],
+ ['doc.demo-api.md', 'library/002-文档示范/001-通用API接口文档示例.md'],
+ ['doc.demo-long-article.md', 'library/002-文档示范/002-超长文档页内目录示例.md']
+ ];
+ for (let file of fileList2) {
+ that._copyFile(options.filesPath + file[0], options.outputPath + file[1]);
+ }
+ }
+ return options.outputPath;
+ }).catch((e) => {
+ console.error(e);
+ });
+ }
+ };
+})();
+
+module.exports = creator;
\ No newline at end of file
diff --git a/build/exportGithub.js b/build/exportGithub.js
new file mode 100644
index 0000000..b1d880a
--- /dev/null
+++ b/build/exportGithub.js
@@ -0,0 +1,314 @@
+/**
+ * 工作端 - 导出 github-wiki 模块
+ * @author Tevin
+ */
+
+const fs = require('fs');
+const co = require('../modules/co');
+const mngFolder = require('./manageFolder');
+const mngWiki = require('./manageWiki');
+
+const exportGithub = (function () {
+ return {
+ /**
+ * 拷贝一张图片
+ * @param {String} from
+ * @param {String} to
+ * @private
+ */
+ _copyImg: function (from, to) {
+ fs.createReadStream(from).pipe(fs.createWriteStream(to));
+ },
+ /**
+ * 拷贝一篇文档
+ * @param {String} fileName
+ * @param {String} pathTo
+ * @private
+ */
+ _copyMd: function (fileName, pathTo) {
+ let file = fs.readFileSync(fileName[0], 'utf-8');
+ //相对路径图片地址转换
+ file = file
+ .replace(/!\[(.*?)]\(assets(.*?)\)/g, '')
+ .replace(/\[(.*?)]\(assets(.*?)\)/g, '[$1](' + this._githubUrl + 'wiki/images$2)')
+ .replace(/ {
+ this._copyImg(pathFrom + '../amWiki/images/logo.png', pathTo + '/images/amWiki-logo.png');
+ return this._githubUrl + 'wiki/images/amWiki-logo.png';
+ });
+ fs.writeFileSync(pathTo + '/Home.md', file, 'utf-8');
+ },
+ /**
+ * 导出图片
+ * @param {String} pathFrom
+ * @param {String} pathTo
+ * @private
+ */
+ _exportImage: function (pathFrom, pathTo) {
+ if (!fs.existsSync(pathTo + '/images/')) {
+ fs.mkdirSync(pathTo + '/images/', 0o777);
+ }
+ if (fs.existsSync(pathFrom + '../assets/')) {
+ mngFolder.copyFolder(pathFrom + '../assets/', pathTo + '/images/');
+ }
+ },
+ /**
+ * 检查重名
+ * @param {[Array]} fileList
+ * @returns {[Array]}
+ * @private
+ */
+ _checkDuplicate: function (fileList) {
+ const dup = [];
+ for (let i = 0, item; item = fileList[i]; i++) {
+ for (let j = i + 1, item2; item2 = fileList[j]; j++) {
+ if (item[1] === item2[1]) {
+ dup.push([item, item2]);
+ }
+ }
+ }
+ return dup;
+ },
+ /**
+ * 导出重复文档
+ * @param {Array} list
+ * @param {String} pathTo
+ * @private
+ */
+ _exportDuplicate: function (list, pathTo) {
+ if (list.length === 0) {
+ return;
+ }
+ const checkExist = (fileName) => {
+ //如果已存在,增加空格
+ if (fs.existsSync(pathTo + '/' + fileName[1])) {
+ fileName[1] = fileName[1].replace(/\.md$/, ' .md');
+ checkExist(fileName);
+ }
+ //不存在正常复制
+ else {
+ this._copyMd(fileName, pathTo);
+ }
+ };
+ for (let fileName of list) {
+ checkExist(fileName);
+ }
+ },
+ /**
+ * 导出普通文档
+ * @param {Array} list
+ * @param {String} pathTo
+ * @private
+ */
+ _exportNormal: function (list, pathTo) {
+ if (list.length === 0) {
+ return;
+ }
+ for (let fileName of list) {
+ this._copyMd(fileName, pathTo);
+ }
+ },
+ /**
+ * 开始导出
+ * @param {String} pathFrom
+ * @param {String} pathTo
+ * @param {[Array]} fileList
+ * @param {[Array]} duplicates
+ * @returns {Promise}
+ * @private
+ */
+ _toExport: function (pathFrom, pathTo, fileList, duplicates) {
+ const that = this;
+ return co(function* () {
+ if (mngFolder.readFolder(pathTo).length > 0 && (yield confirm2('所选导出文件夹不为空,是否需要清空?'))) {
+ mngFolder.cleanFolder(pathTo);
+ }
+ let fileList2 = [];
+ const duplicate2 = [];
+ if (typeof duplicates !== 'undefined') {
+ for (let file of fileList) {
+ let fileDup = false;
+ for (let dup of duplicates) {
+ if (file[0] === dup) {
+ fileDup = true;
+ duplicate2.push(file);
+ break;
+ }
+ }
+ if (!fileDup) {
+ fileList2.push(file);
+ }
+ }
+ } else {
+ fileList2 = fileList;
+ }
+ //开始拷贝
+ that._exportNormal(fileList2, pathTo);
+ that._exportDuplicate(duplicate2, pathTo);
+ that._exportImage(pathFrom, pathTo);
+ that._exportHome(pathFrom, pathTo);
+ that._exportNavigation(fileList2, duplicate2, pathFrom, pathTo);
+ }).catch((e) => {
+ console.error(e);
+ });
+ },
+ /**
+ * 导出准备
+ * @param {String} pathFrom
+ * @param {String} pathTo
+ * @returns {Promise}
+ * @private
+ */
+ _toPrepare: function (pathFrom, pathTo) {
+ const that = this;
+ return co(function*() {
+ const fileList = [];
+ pathFrom += /[\\\/]$/.test(pathFrom) ? 'library/' : '/library/';
+ //读取文件夹
+ const [, , files] = mngFolder.readLibraryTree(pathFrom);
+ if (files.length === 0) {
+ return;
+ }
+ let fileName = '';
+ //提取文件名
+ for (let item of files) {
+ fileName = item.split(/[\\\/]/);
+ fileList.push([
+ item,
+ fileName[fileName.length - 1].replace(/^\d+(\.\d+)?[-_](.*?)$/, '$2')
+ ]);
+ }
+ //重名检查
+ const duplicate = that._checkDuplicate(fileList);
+ //重名文件单独处理
+ if (duplicate.length > 0) {
+ const dups = [];
+ let message = '以下文件脱离文件夹偏平化时将会重名:\n\n';
+ for (let j = 0, dup; dup = duplicate[j]; j++) {
+ message += dup[0][0].replace(/\//g, '\\') + '\n' +
+ dup[1][0].replace(/\//g, '\\') + '\n\n';
+ dups.push(dup[0][0], dup[1][0]);
+ }
+ message += '点击确认将自动处理(追加额外空格)并继续导出;\n' +
+ '点击取消将退出导出,您可以在改名后再次重新导出。';
+ if (yield confirm2(message)) {
+ return that._toExport(pathFrom, pathTo, fileList, dups);
+ }
+ } else {
+ return that._toExport(pathFrom, pathTo, fileList);
+ }
+ }).catch((e) => {
+ console.error(e);
+ });
+ },
+ /**
+ * GitHub项目地址
+ * @private
+ */
+ _githubUrl: '',
+ /**
+ * 解析 GitHub url
+ * @param {String} url
+ * @returns {Boolean|String}
+ * @private
+ */
+ _parseGithubUrl: function (url) {
+ if (url === '') {
+ alert('导出失败!\n未检测到 “github-url” 配置,请在 config.json 中配置您项目的 github-url');
+ return false;
+ }
+ if (url.indexOf('github.com') === -1) {
+ alert('导出失败!\n配置完整的 Github 项目 URL!\n例如 https://github.com/TevinLi/amWiki');
+ return false;
+ }
+ const urlArr = url.split('github.com')[1].split('/');
+ if (urlArr.length < 3) {
+ alert('导出失败!\n请配置完整 Github 项目 URL!\n例如 https://github.com/TevinLi/amWiki');
+ return false;
+ }
+ urlArr[2] = urlArr[2].split(/\?#/)[0].split(/\.git/i)[0];
+ return '/' + urlArr[1] + '/' + urlArr[2] + '/';
+ },
+ /**
+ * 导出为github-wiki
+ * @param {String} root - 文库项目根目录
+ * @param {String} outputPath - 输出路径
+ * @returns {Promise}
+ * @public
+ */
+ export: function (root, outputPath) {
+ const that = this;
+ return co(function* () {
+ const wikiConfig = mngWiki.getWikiByRoot(root).config;
+ //检测 GitHub 项目地址
+ that._githubUrl = that._parseGithubUrl(wikiConfig.githubUrl);
+ if (!that._githubUrl) {
+ return;
+ }
+ yield that._toPrepare(root, outputPath);
+ });
+ }
+ };
+})();
+
+module.exports = exportGithub;
diff --git a/build/localServer.js b/build/localServer.js
new file mode 100644
index 0000000..8f8d033
--- /dev/null
+++ b/build/localServer.js
@@ -0,0 +1,104 @@
+/**
+ * 工作端 - 本地服务器模块
+ * @author Tevin
+ */
+
+const fs = require('fs');
+const child_process = require('child_process');
+const co = require('../modules/co');
+const Sever = require('./server.class');
+const mngWiki = require('./manageWiki');
+
+const localServer = (function () {
+ return {
+ /**
+ * 本地 server 实例
+ * @private
+ */
+ _server: null,
+ /**
+ * 启动服务器
+ * @param {Object} wikis - 文库记录列表的引用
+ * @param {Number} port - 服务器端口号
+ * @public
+ */
+ run: function (wikis, port) {
+ const that = this;
+ return co(function*() {
+ if (!that._server) {
+ that._server = new Sever(wikis, port);
+ yield that._server.run();
+ }
+ });
+ },
+ /**
+ * 浏览当前文档
+ * @param {String} editPath - 当前文档的路径
+ * @param {Object} wikis - 文库记录列表的引用
+ * @returns {Promise}
+ * @public
+ */
+ browser: function (editPath, wikis) {
+ const that = this;
+ return co(function*() {
+ //判断服务器
+ if (!that._server) {
+ if (yield confirm2('本地服务器还未启动,您需要启动服务器么?')) {
+ that._server = new Sever(wikis);
+ yield that._server.run();
+ } else {
+ return;
+ }
+ }
+ //解析地址
+ const host = 'http://' + that._server.getLocalIP() + ':' + that._server.getPort();
+ const wikiId = mngWiki.createWikiId(editPath.split('library')[0]);
+ let url;
+ if (editPath.indexOf('$navigation.md') >= 0) {
+ url = host + '/wiki' + wikiId + '/index.html';
+ } else {
+ let filePath = editPath.split('library/')[1];
+ if (typeof filePath === 'undefined') {
+ url = host + '/wiki' + wikiId + '/index.html';
+ } else {
+ filePath = filePath.replace(/\\/g, '/').replace(/ /g, '%20').replace('.md', '');
+ url = host + '/wiki' + wikiId + '/index.html?file=' + filePath;
+ }
+ }
+ //呼起默认浏览器打开页面
+ let cmd;
+ //windows
+ if (process.platform === 'win32') {
+ cmd = 'start';
+ }
+ //linux
+ else if (process.platform === 'linux') {
+ cmd = 'xdg-open';
+ }
+ //mac
+ else if (process.platform === 'darwin') {
+ cmd = 'open';
+ }
+ child_process.exec(cmd + ' ' + url);
+ }).catch((e) => {
+ console.error(e);
+ });
+ },
+ /**
+ * 关闭 amWiki 索引页显示
+ * @public
+ */
+ setOffIndex: function () {
+ this._server.offIndex();
+ },
+ /**
+ * 关闭服务器
+ * @public
+ */
+ destroy: function () {
+ this._server && this._server.close();
+ }
+ };
+})();
+
+module.exports = localServer;
\ No newline at end of file
diff --git a/build/makeMounts.js b/build/makeMounts.js
new file mode 100644
index 0000000..35651cb
--- /dev/null
+++ b/build/makeMounts.js
@@ -0,0 +1,187 @@
+/**
+ * 工作端 - 创建 mounts/*.js 页面挂载数据模块
+ * @author Tevin
+ */
+
+
+const fs = require('fs');
+const mngFolder = require('./manageFolder');
+const mngWiki = require('./manageWiki');
+
+const makeMounts = (function () {
+ return {
+ /**
+ * 创建 mounts 数据
+ * @param {String} editPath
+ * @param {Boolean} [offMsg] - 是否关闭提示
+ * @public
+ */
+ make: function (editPath, offMsg) {
+ const rootPath = mngFolder.getProjectFolder(editPath);
+ if (!this._checkMountsAllow(rootPath)) {
+ if (offMsg !== true) {
+ alert('更新失败!\n配置项“page-mounts”不存在或不为 true,请在 config.json 中配置');
+ }
+ return;
+ }
+ const libPath = rootPath + 'library/';
+ const [, list,] = mngFolder.readLibraryTree(libPath);
+ const mountData = this._getMountsData(libPath, rootPath, list);
+ const mountFiles = this._exportMounts(mountData, rootPath);
+ this._updateMountLinks(mountFiles, rootPath);
+ },
+ /**
+ * 检查 mounts 创建许可
+ * @param {String} rootPath
+ * @returns {Boolean}
+ * @private
+ */
+ _checkMountsAllow: function (rootPath) {
+ const wiki = mngWiki.getWikiByRoot(rootPath);
+ if (!wiki) {
+ return false;
+ }
+ const config = wiki.config;
+ if (typeof config.pageMounts !== 'undefined') {
+ return config.pageMounts === true || typeof config.pageMounts === 'object';
+ } else {
+ return false;
+ }
+ },
+ /**
+ * 收集 mounts 数据
+ * @param {String} libPath
+ * @param {String} rootPath
+ * @param {[Object]} list
+ * @returns {{Array}}
+ * @private
+ */
+ _getMountsData: function (libPath, rootPath, list) {
+ const mountData = {};
+ const timestamp = Date.now();
+ for (let file of list) {
+ if (file.type !== 'file') {
+ continue;
+ }
+ let filePath = file.path + '/' + file.name;
+ let lv1Id = 'm' + mngFolder.getLevel1Id(filePath, libPath);
+ if (lv1Id.length === 0) {
+ continue;
+ }
+ if (typeof mountData[lv1Id] === 'undefined') {
+ mountData[lv1Id] = [];
+ }
+ mountData[lv1Id].push({
+ name: file.name,
+ path: filePath.substr(libPath.length),
+ content: fs.readFileSync(filePath, 'utf-8'),
+ timestamp: timestamp
+ });
+ }
+ mountData['home'] = this._getHomeMount(libPath);
+ mountData['nav'] = this._getNavMount(libPath);
+ mountData['icon'] = this._getIconsMount(rootPath);
+ //console.log(mountData['home'].name, mountData['nav'].name, mountData['icon'].name);
+ mountData['home'].timestamp = mountData['nav'].timestamp = mountData['icon'].timestamp = timestamp;
+ return mountData;
+ },
+ /**
+ * 获取首页挂载数据
+ * @param {String} libPath
+ * @returns {{name: String, path: String, content: String}}
+ * @private
+ */
+ _getHomeMount: function (libPath) {
+ const list = mngFolder.readFolder(libPath);
+ for (let name of list) {
+ if (/^home[-_].*?\.md$/.test(name) || name === '首页.md') {
+ const homePath = libPath + name;
+ return {
+ name: name,
+ path: homePath.substr(libPath.length),
+ content: fs.readFileSync(homePath, 'utf-8'),
+ }
+ }
+ }
+ },
+ /**
+ * 获取导航挂载数据
+ * @param {String} libPath
+ * @returns {{name: String, path: String, content: String}}
+ * @private
+ */
+ _getNavMount: function (libPath) {
+ const filePath = libPath + '$navigation.md';
+ return {
+ name: '$navigation.md',
+ path: filePath.substr(libPath.length),
+ content: fs.readFileSync(filePath, 'utf-8')
+ }
+ },
+ /**
+ * 获取图标挂载数据
+ * @param {String} rootPath
+ * @returns {{name: String, path: String, content: String}}
+ * @private
+ */
+ _getIconsMount: function (rootPath) {
+ const filePath = rootPath + 'amWiki/images/icons.svg';
+ return {
+ name: 'icons.svg',
+ path: '../' + filePath.substr(rootPath.length),
+ content: fs.readFileSync(filePath, 'utf-8')
+ }
+ },
+ /**
+ * 输出 ./mounts/*.js 文件
+ * @param {{Array}} mountData
+ * @param {String} rootPath
+ * @returns {Array}
+ * @private
+ */
+ _exportMounts: function (mountData, rootPath) {
+ const mountPath = rootPath + 'amWiki/mounts/';
+ if (!fs.existsSync(mountPath)) {
+ fs.mkdirSync(mountPath, 0o777);
+ } else {
+ mngFolder.cleanFolder(mountPath);
+ }
+ const mountTemplate = 'if(typeof AWPageMounts==\'undefined\'){AWPageMounts={}};' +
+ 'AWPageMounts[\'{{id}}\']={{content}}';
+ const mountFiles = [];
+ for (let mountId in mountData) {
+ if (mountData.hasOwnProperty(mountId)) {
+ let mountContent = JSON.stringify(mountData[mountId]).replace(/'/g, '\\\'');
+ mountContent = mountTemplate
+ .replace('{{id}}', mountId)
+ .replace('{{content}}', mountContent);
+ let mountName = mountId + '-' + mountContent.length + '.js';
+ mountFiles.push(mountName);
+ fs.writeFileSync(mountPath + mountName, mountContent, 'utf-8');
+ }
+ }
+ return mountFiles;
+ },
+ /**
+ * 更新 index.html 文件的挂载
+ * @param {Array} mountFiles
+ * @param {String} rootPath
+ * @private
+ */
+ _updateMountLinks: function (mountFiles, rootPath) {
+ let linksHtml = '';
+ for (let fileName of mountFiles) {
+ linksHtml += '';
+ }
+ let indexSrc = fs.readFileSync(rootPath + 'index.html', 'utf-8');
+ const mountReg = /
{{content}}
The requested URL ' + path + ' was not found on this server.
'); + res.end(); + } + + /** + * 500错误页面 + * @param {Object} req + * @param {Object} res + * @param {Error} error + * @static + */ + static page500(req, res, error) { + res.writeHead(500, { + 'Content-Type': 'text/html' + }); + res.write('\n'); + res.write('' + util.inspect(error) + ''); + res.end(); + } + +} + +module.exports = Server; \ No newline at end of file diff --git a/build/tools.js b/build/tools.js new file mode 100644 index 0000000..8207fa8 --- /dev/null +++ b/build/tools.js @@ -0,0 +1,21 @@ +/** + * 工作端 - 公用工具模块 + * @author Tevin + */ + + +const tools = (function () { + return { + /** + * 判断一个对象是否属于数组 + * @param {Object} obj - 需要判断的对象 + * @return {Boolean} + * @public + */ + isArray: function (obj) { + return Object.prototype.toString.call(obj) === '[object Array]'; + } + }; +})(); + +module.exports = tools; \ No newline at end of file diff --git a/files/amWiki.css b/files/amWiki.css index 09cd09f..bf3295d 100644 --- a/files/amWiki.css +++ b/files/amWiki.css @@ -14,8 +14,11 @@ .markdown-body .anchor svg{visibility:hidden;width:20px;height:20px;color:#828282;} .markdown-body h3 .anchor{position:relative;top:2px;} .markdown-body h1:hover .anchor svg,.markdown-body h2:hover .anchor svg,.markdown-body h3:hover .anchor svg{visibility:visible;} -.markdown-body :not(.anchor)>img:hover{outline:rgba(0,0,0,.05) 1px solid;box-shadow:1px 1px 1px rgba(0,0,0,.1);} +.markdown-body img:hover{outline:rgba(0,0,0,.05) 1px solid;box-shadow:1px 1px 1px rgba(0,0,0,.1);cursor:pointer;} +.markdown-body img[align="center"]{display:block;margin:0 auto;} .markdown-body tbody{word-break:initial;} +.markdown-body input[type="checkbox"]{margin-right:5px;vertical-align:middle;cursor:pointer;} +.markdown-body input[type="checkbox"]+label{vertical-align:middle;margin-right:7px;cursor:pointer;} .markdown-body pre{position:relative;} .markdown-body pre code{padding-top:0.15em;padding-bottom:0.15em;margin:0 1px;font-size:90%;color:#000;word-wrap:break-word;white-space:pre-wrap;} .markdown-body pre code.hljs{color:#333;} @@ -29,29 +32,30 @@ .markdown-body .lang-off-js-comment:hover i{color:#999;} .markdown-body .lang-off-js-comment.on{display:block;color:#333;background:#ccc;} .markdown-body .lang-off-js-comment.on i{color:#f00;} +.markdown-body mark{color:#e61e1e;background:transparent;} /* ---------- base ---------- */ -body{position:relative;background:#e7e8eb;} +html{height:100%;} +body{position:relative;height:100%;background:#e7e8eb;} .hidden{display:none !important;} /* ---------- header ---------- */ -.header{width:100%;height:80px;font-family:Microsoft YaHei,STXihei,STHeiti,Verdana,Arial,Helvetica,sans-serif;;background:#fff;border-top:@colour-dark 6px solid;border-bottom:#c3c4c6 1px solid;} -.header .header-inner{height:80px;margin:0 30px;} -.header .logo{float:left;padding-top:20px;} -.header .logo img{float:left;margin-left:5px;max-height:33px;} +.header{width:100%;height:70px;font-family:Microsoft YaHei,STXihei,STHeiti,Verdana,Arial,Helvetica,sans-serif;;background:#fff;border-top:@colour-dark 6px solid;border-bottom:#c3c4c6 1px solid;} +.header .header-inner{margin:0 30px;} +.header .logo{float:left;padding-top:15px;} +.header .logo img{float:left;margin-left:5px;max-height:32px;} .header .logo i{float:left;display:block;width:5px;height:5px;margin:16px 0 0 3px;background:#666666;border-radius:50%;} -.header .logo b{float:left;max-width:470px;height:33px;padding-left:10px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-size:24px;font-weight:normal;color:#666;} +.header .logo b{float:left;max-width:470px;height:32px;padding-left:10px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-size:24px;font-weight:normal;color:#666;} .header .logo span{float:left;padding:0 3px;margin:1px 0 0 2px;font-size:xx-small;color:#fff;background:#bbbbbb;} -.header .menu_icon{display:none;} +.header .menu-icon{display:none;} /* ---------- container ---------- */ -.container{position:relative;min-height:500px;margin:30px;overflow:hidden;background:url(../images/menubar_bg.png) repeat-y #fff;border:#c3c4c6 1px solid;border-top:@colour-base 2px solid;} +.container{position:relative;margin:20px 18px 0;background:url(../images/menubar_bg.png) repeat-y #fff;border:#c3c4c6 1px solid;border-top:@colour-base 2px solid;} /* ---------- menu ---------- */ -.container .nav{position:relative;left:auto;top:auto;width:250px;} - -/* --- filter --- */ -.menu-filter{box-sizing:content-box;position:absolute;top:0;left:0;z-index:10;width:176px;height:37px;padding-left:35px;} +.container .nav{position:absolute;left:0px;top:0px;width:250px;height:100%;} +/* filter */ +.menu-filter{box-sizing:content-box;position:absolute;top:0;left:0;z-index:10;width:176px;height:37px;padding-left:35px;border-bottom:1px solid #D9D9D9;} .menu-filter svg{position:absolute;top:12px;left:12px;width:16px;height:16px;color:@colour-base;} .menu-filter input{display:block;box-sizing:content-box;width:140px;height:22px;padding:0 25px 0 2px;margin-top:8px;line-height:20px;font-size:14px;background:none;border:none;border-bottom:#e0e0e0 1px dashed;} .menu-filter input:focus{outline:none;border-bottom:#aaa 1px solid;} @@ -61,44 +65,80 @@ body{position:relative;background:#e7e8eb;} .menu-filter :-moz-placeholder{color:#ccc;font-size:13px;} .menu-filter ::-moz-placeholder{color:#ccc;font-size:13px;} .menu-filter ::-ms-clear{display:none !important;width:0;height:0;} -.menu-filter i{display:block;box-sizing:content-box;position:absolute;top:0px;right:0px;width:23px;padding:2px 10px 0;font-size:25px;text-align:center;color:#ccc;cursor:pointer;} +.menu-filter i{display:block;box-sizing:content-box;position:absolute;top:0px;right:0px;width:23px;padding:0 10px;font-size:25px;text-align:center;color:#ccc;cursor:pointer;} .menu-filter i:hover{color:#e6660d;} .menu-filter i.off{display:none;} - -.menu-search{display:block;position:absolute;top:0;right:1px;z-index:11;width:39px;height:37px;border-left:1px solid #D9D9D9;cursor:pointer;} +/* search */ +.menu-search{display:block;position:absolute;top:0;right:1px;z-index:11;width:39px;height:38px;font-style:normal;border-left:1px solid #D9D9D9;border-bottom:1px solid #D9D9D9;cursor:pointer;} .menu-search:hover{background-color:#f4f5f9;} .menu-search svg{position:absolute;top:12px;left:12px;width:16px;height:16px;color:@colour-base;} - -/* --- menubar --- */ -.menubar{float:left;position:relative;width:250px;height:100%;padding-top:38px;font-family:Microsoft YaHei,STXihei,STHeiti,Verdana,Arial,Helvetica,sans-serif;border-right:1px solid #e7e7eb;} +/* menubar */ +.menubar{position:relative;z-index:0;width:250px;height:100%;padding-top:38px;font-family:Microsoft YaHei,STXihei,STHeiti,Verdana,Arial,Helvetica,sans-serif;border-right:1px solid #e7e7eb;} .menubar h4,.menubar h5{display:block;position:relative;height:37px;margin:-1px 0 0;padding:0 10px 0 30px;font-size:14px;font-weight:normal;color:#333;line-height:37px;text-shadow:#666 1px 0 0;border:1px solid #D9D9D9;border-width:1px 0;cursor:pointer;} +.menubar h4:hover{background-color:#f4f5f9;} +.menubar h5.off{display:none;} +.menubar h5.on{display:block;} .menubar h4 svg{position:absolute;top:12px;left:12px;width:13px;height:13px;color:#acacac;} .menubar h4.on svg{color:@colour-base;} -.menubar h4:hover{background-color:#f4f5f9;} .menubar h4 a{display:block;height:36px;color:#333;text-decoration:none;} .menubar h4.on a{color:@colour-dark;} .menubar h5 svg{position:absolute;top:12px;left:12px;width:13px;height:13px;color:#acacac;-webkit-transform:rotate(-90deg);-moz-transform:rotate(-90deg);-ms-transform:rotate(-90deg);transform:rotate(-90deg);} .menubar h5.on svg{color:@colour-base;-webkit-transform:rotate(0);-moz-transform:rotate(0);-ms-transform:rotate(0);transform:rotate(0);} .menubar ul{display:none;margin:0;padding:0;list-style:none;} -.menubar ul li{position:relative;} +.menubar ul li{padding-left:23px;} +.menubar .scroller-content > ul > li{padding-left:45px;} .menubar ul li.off{display:none;} -.menubar ul li a,.menubar ul li > strong{display:block;padding:0 20px 0 0;font-weight:normal;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;line-height:30px;font-size:12px;color:#333;text-indent:45px;text-decoration:none;cursor:pointer;-webkit-transition-duration:.3s;-moz-transition-duration:.3s;-ms-transition-duration:.3s;transition-duration:.3s;} -.menubar ul li:first-child a,.menubar ul li:first-child > strong{padding-top:2px;} -.menubar ul li:last-child a,.menubar ul li:last-child > strong{padding-bottom:2px;} -.menubar ul li a:hover,.menubar ul li > strong:hover{background:#f5f5f5;-webkit-transition-duration:0s;-moz-transition-duration:0s;-ms-transition-duration:0s;transition-duration:0s;} -.menubar ul li a.on{color:@colour-dark;background:#f5f5f5;} -.menubar ul li a.on:after{display:block;position:absolute;top:0px;right:0px;width:2px;height:30px;content:" ";background:@colour-base;} -.menubar ul li:first-child a.on:after{height:32px;} -.menubar ul li:last-child a.on:after{height:32px;} +.menubar ul li > a,.menubar ul li > strong{display:block;height:30px;padding:0 55px 0 0;font-weight:normal;white-space:nowrap;line-height:30px;font-size:12px;color:#333;text-decoration:none;cursor:pointer;} +.menubar ul li > a:after,.menubar ul li > strong:after{display:block;position:absolute;left:0;right:0;height:30px;content:" ";-webkit-transition-duration:.3s;-moz-transition-duration:.3s;-ms-transition-duration:.3s;transition-duration:.3s;} +.menubar ul li > a:hover:after,.menubar ul li > strong:hover:after{background:#f5f5f5;-webkit-transition-duration:0s;-moz-transition-duration:0s;-ms-transition-duration:0s;transition-duration:0s;} +.menubar ul li > a > span,.menubar ul li strong > span{float:left;position:relative;z-index:1;} +.menubar ul li > a.on{color:@colour-dark;} +.menubar ul li > a.on:before{display:block;position:absolute;left:0px;z-index:1;width:2px;height:30px;content:" ";background:@colour-base;} +.menubar ul li > a.on:after{background:#f5f5f5;} .menubar ul li > strong.on{text-shadow:#666 1px 0 0;} -.menubar ul li > strong:before{position:absolute;top:13px;left:32px;width:6px;height:6px;background:#ccc;border-radius:50%;content:" ";} -.menubar ul li:first-child > strong:before{top:15px;} +.menubar ul li > strong:before{position:absolute;z-index:1;width:6px;height:6px;margin:12px 0 0 -12px;background:#acacac;border-radius:50%;content:" ";} .menubar ul li > strong.on:before{background:@colour-base;} -.menubar ul li li a{text-indent:65px;} .menubar mark{color:red;background:#f5f5f5;} -.main{position:relative;padding:45px 45px 155px;margin-left:250px;} -.main-sibling{display:none;position:absolute;left:0;bottom:0;width:100%;padding:35px 45px 20px;font-size:14px;border-top:1px solid #e7e7eb;} +/* ---------- scroller ---------- */ +body[onselectstart]{cursor:pointer;-moz-user-select:none;-webkit-user-select:none;user-select:none;} +.scroller{height:100%;overflow:hidden;} +.scroller .scroller-inner{box-sizing:content-box;width:100%;height:100%;-webkit-overflow-scrolling:touch;overflow-scrolling:touch;} +.scroller .scroller-inner.on{overflow-y:scroll;} +.scroller .scrollbar{position:absolute;opacity:0;transition-duration:.5s;-moz-user-select:none;-webkit-user-select:none;user-select:none;cursor:pointer;} +.scroller:hover .scrollbar{opacity:1;transition-duration:.3s;} +.scroller .scrollbar.active{opacity:1;} +.scroller .scrollbar.off{opacity:0;} +.scroller .scrollbar div{position:relative;transition-duration:.1s;} +.scroller .scrollbar i{position:absolute;background:rgba(50,50,50,.25);border-radius:2px;transition-duration:.1s;} +.scroller .scrollbar:active{padding:0;} +.scroller .scrollbar:active div{background:rgba(50,50,50,.07);} +.scroller .scrollbar:active i{border-radius:0;} +/* scrollbar-y */ +.container .nav .scrollbar.scrollbar-y,.container .nav .scrollbar.scrollbar-y:active{padding-top:38px;padding-bottom:10px;} +.scroller .scrollbar.scrollbar-y{top:0;right:0;width:10px;height:100%;} +.scroller .scrollbar.scrollbar-y div{height:100%;} +.scroller .scrollbar.scrollbar-y i{top:0;left:4px;width:3px;height:100%;} +.scroller .scrollbar.scrollbar-y:active i{left:0;width:10px;} +/* scrollbar-x */ +.scroller[data-x-rolling="true"] .scroller-inner.on{overflow-x:scroll;} +.scroller[data-x-rolling="true"] .scroller-content{display:inline-block;position:relative;padding-bottom:10px;white-space:nowrap;min-width:250px;} +.scroller .scrollbar.scrollbar-x{bottom:0;left:0;width:100%;height:10px;padding-right:10px;} +.scroller .scrollbar.scrollbar-x div{width:100%;height:100%;} +.scroller .scrollbar.scrollbar-x i{bottom:0;top:4px;height:3px;width:100%;} +.scroller .scrollbar.scrollbar-x:active i{top:0;height:10px;} + +/* ---------- main ---------- */ +.main{position:relative;height:100%;margin-left:250px;} +.main .markdown-body{padding:45px;} +/* footnote */ +.main .footnote{position:relative;padding-top:35px;border-top:#ddd 1px solid;} +.main .footnote i{position:absolute;top:5px;left:10px;font-size:15px;font-style:normal;color:#999;} +.main .footnote li:focus{background:#EAFFEA;} +.main .footnote svg{display:inline-block;width:14px;height:14px;margin-bottom:-1px;} +.main .footnote-none{width:0;height:0;overflow:hidden;} +/* sibling */ +.main-sibling{display:none;width:100%;padding:30px 45px 20px;font-size:14px;border-top:1px solid #e7e7eb;} .main-sibling.on{display:block;} .main-sibling p{position:relative;height:22px;} .main-sibling span{position:absolute;left:0;top:0;} @@ -106,18 +146,46 @@ body{position:relative;background:#e7e8eb;} .main-sibling a:not([href]){color:#333;} .main-sibling a:not([href]):hover{text-decoration:none;} -/* ---------- footer ---------- */ -footer{position:relative;width:100%;height:15px;} -.signature{position:absolute;right:40px;bottom:17px;color:#999;} +/* ---------- imgsView ---------- */ +.imgsv{display:none;position:fixed;top:0;left:0;z-index:999999;width:100%;height:100%;} +.imgsv-background{position:absolute;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,.1);cursor:pointer;} +.imgsv-view img{position:absolute;top:48%;left:50%;opacity:0;box-shadow:rgba(0,0,0,.2) 1px 2px 5px;-webkit-transform:translate(-50%,-50%);-moz-transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%);transform:translate(-50%,-50%);-webkit-transition-duration:.5s;-moz-transition-duration:.5s;-ms-transition-duration:.5s;transition-duration:.5s;/*cursor:move;*/ +} +.imgsv.on .imgsv-view img{opacity:1;} +.imgsv-panel{position:absolute;left:50%;bottom:-50px;padding:0 10px;line-height:50px;list-style:none;white-space:nowrap;background:rgba(255,255,255,.8);border-top-left-radius:5px;border-top-right-radius:5px;box-shadow:rgba(0,0,0,.2) 0 -1px 5px;-webkit-user-select:none;-moz-user-select:none;user-select:none;-webkit-transform:translateX(-50%);-moz-transform:translateX(-50%);-ms-transform:translateX(-50%);transform:translateX(-50%);-webkit-transition-duration:.5s;-moz-transition-duration:.5s;-ms-transition-duration:.5s;transition-duration:.5s;} +.imgsv.on .imgsv-panel{bottom:0px;} +.imgsv-panel li{display:inline-block;min-width:50px;padding:0 10px;text-align:center;cursor:pointer;} +.imgsv-panel li.off{color:#999;cursor:default;} +.imgsv-panel li:hover{background:rgba(225,225,225,1);} +.imgsv-panel li.off:hover{background:none;} -/* ---------- backTop ---------- */ -.back-top{position:fixed;bottom:100px;right:7px;z-index: 9900;height:38px;width:38px;background:#f4f5f9;border:1px solid #ccc;} -.back-icon{display:block;position:relative;width:32px;margin:2px auto;} -.back-icon svg{position:absolute;top:6px;left:5px;width:22px;height:22px;color:#aaa;} -.back-text{display:none;position:absolute;top:0;left:0;z-index:99;margin:4px;font-family:Microsoft YaHei,STXihei,STHeiti,Verdana,Arial,Helvetica,sans-serif;font-size:11px;text-align:center;letter-spacing:2px;line-height:14px;background-color:#f4f5f9;cursor:pointer;} -.back-text:hover{text-decoration:none;} -.back-top:hover .back-icon{border:none;} -.back-top:hover .back-text{display:block;color:#aaa;} +/* ---------- contents ---------- */ +aside .contents{position:fixed;bottom:137px;right:7px;z-index:9900;height:38px;width:38px;background:#f4f5f9;border:1px solid #ccc;} +aside .contents .btn{cursor:pointer;} +aside .contents .btn svg{position:absolute;top:7px;left:7px;width:22px;height:22px;color:#aaa;} +aside .contents.hover .btn svg,aside .contents.on .btn svg{display:none;} +aside .contents .btn span{display:none;padding:12px 0;font-family:Microsoft YaHei,STXihei,STHeiti,Verdana,Arial,Helvetica,sans-serif;line-height:14px;text-align:center;color:#aaa;} +aside .contents.hover .btn span,aside .contents.on .btn span{display:block;} +aside .contents-list{display:none;position:absolute;right:36px;bottom:-1px;width:270px;max-height:500px;padding:18px;overflow:hidden;overflow-y:scroll;background:#f4f5f9;border:1px solid #ccc;} +aside .contents.hover .contents-list,aside .contents.on .contents-list{display:block;} +aside .contents-list p{padding-bottom:5px;margin-bottom:8px;border-bottom:#ccc 1px solid;} +blockquote.markdown-contents{padding-left:20px;} +.markdown-contents ol{padding-left:0;list-style:none;} +.markdown-contents ol ol{padding-left:2em;} +.markdown-contents ol li>i{font-style:normal;margin-right:4px;color:#4078c0;} + +/* ---------- aside-backTop ---------- */ +aside .back-top{position:fixed;bottom:100px;right:7px;z-index: 9900;height:38px;width:38px;background:#f4f5f9;border:1px solid #ccc;} +aside .back-icon{display:block;position:relative;width:32px;margin:2px auto;} +aside .back-icon svg{position:absolute;top:6px;left:5px;width:22px;height:22px;color:#aaa;} +aside .back-text{display:none;position:absolute;top:0;left:0;z-index:99;padding:4px;font-family:Microsoft YaHei,STXihei,STHeiti,Verdana,Arial,Helvetica,sans-serif;font-size:11px;text-align:center;letter-spacing:2px;line-height:14px;background-color:#f4f5f9;cursor:pointer;} +aside .back-text:hover{text-decoration:none;} +aside .back-top:hover .back-icon{border:none;} +aside .back-top:hover .back-text{display:block;color:#aaa;} + +/* ---------- footer ---------- */ +footer{position:absolute;left:0;bottom:0;width:100%;} +footer .signature{position:absolute;right:20px;bottom:8px;color:#999;} /* ---------- lowBrowser ---------- */ #lowBrowser{position:fixed;top:0;left:0;z-index:9999999;width:100%;height:100%;} @@ -142,47 +210,63 @@ footer{position:relative;width:100%;height:15px;} .header .logo i{width:3px;height:3px;margin:9px 0 0 3px;} .header .logo b{max-width:40%;height:auto;padding-left:7px;font-size:14px;background-size:4px 4px;} .header .logo span{margin:-5px 0 0 0;-webkit-transform:scale(0.8);-moz-transform:scale(0.8);-ms-transform:scale(0.8);transform:scale(0.8);} - .header .menu_icon{display:block;position:absolute;right:0;top:0;z-index:1;width:40px;height:47px;} - .header .menu_icon svg{position:absolute;top:9px;left:5px;width:28px;height:28px;color:#acacac;} - .header .menu_icon.close svg{top:14px;left:10px;width:18px;height:18px;} + .header .menu-icon{display:block;position:absolute;right:0;top:0;z-index:1;width:40px;height:47px;} + .header .menu-icon svg{position:absolute;top:9px;left:5px;width:28px;height:28px;color:#acacac;} + .header .menu-icon.close svg{top:14px;left:10px;width:18px;height:18px;} - .container{position:relative;margin:50px auto 0;overflow:hidden;background:#fff;border:none;} + .container{position:relative;margin:50px auto 0;background:#fff;border:none;} /* ---------- menu ---------- */ - .container .nav{display:none;position:fixed;left:0;top:50px;z-index:9999;width:100%;height:100%;padding-bottom:30px;background:rgba(0,0,0,0.8);overflow-y:scroll;} + .container .nav{display:none;position:fixed;left:0;top:50px;z-index:9999;width:100%;height:100%;padding-bottom:30px;background:rgba(0,0,0,0.92);overflow-y:scroll;} .container .nav.on{display:block;} .menu-filter{width:100%;padding-right:39px;background-color:none;box-sizing:border-box;} .menu-filter input{width:90%;width:-webkit-calc(100% - 39px);width:-moz-calc(100% - 39px);width:-ms-calc(100% - 39px);width:calc(100% - 39px);color:#fff;background:none;border:none;border-bottom:#999 1px dashed;} .menu-filter i{right:50px;padding:2px 0 0 10px;} + .menu-search{height:37px;} .menu-search:hover{background-color:transparent;} .menu-search:active{background-color:#f4f5f9;} + .menubar{width:100%;height:auto;padding-bottom:50px;border-right:none;} .menubar i{border-top:1px solid #999;} .menubar i:hover{background-color:transparent;} .menubar i:active{background-color:#8c8888;} .menubar h4,.menubar h5{color:#f5f5f5;border-top:1px solid #999999;border-bottom:none;cursor:pointer;} + .menubar h4,.menubar h5,.menubar ul{max-width:none;} .menubar h4:hover{background-color:#8c8888;} .menubar h4 a{color:#f5f5f5;} .menubar h4.on a{color:#fff;} .menubar ul li a{color:#f5f5f5;} - .menubar ul li a.on{color:#fff;background:#8c8888;} - .menubar ul li a:hover{color:#fff;background:#8c8888;} + .menubar ul li > a.on{color:#fff;} + .menubar ul li > a:hover{color:#fff;} + .menubar ul li > a.on:before{width:3px;} + .menubar ul li > a.on:after{background:#8c8888;} + .menubar ul li > a:hover:after,.menubar ul li > strong:hover:after{background:none;} .menubar ul li > strong{color:#fff;} .menubar ul li > strong:hover{background:none;} + .scroller .scroller-inner{overflow-x:scroll;} + .scroller[data-x-rolling="true"] .scroller-content{min-width:100%;} + .scroller .scrollbar{display:none;} - .main{width:100%;padding:25px 25px 150px;margin-left:0;box-sizing:border-box;} + .main{width:100%;height:auto;margin-left:0;overflow:visible;box-sizing:border-box;} + .main article.markdown-body{padding:25px;} + .main article iframe{border:0;} + .main .search-results{height:auto !important;} .main-sibling{padding:30px 25px 25px;} .main-sibling p{height:22px;} .main-sibling a{padding-right:20px;} + + /* ---------- contents ---------- */ + aside .contents{bottom:75px;border-radius:50%;} + aside .contents-list{right:40px;width:255px;max-height:310px;} + + /* ---------- backTop ---------- */ + aside .back-top{border-radius:50%;bottom:32px;} + aside .back-top:hover .back-text{display:none;} /* ---------- footer ---------- */ - footer{position:relative;width:100%;height:0;} - .signature{position:absolute;right:12px;bottom:6px;color:#ccc;} - .signature a{color:#aaa;} - - /* ---------- backTop ---------- */ - .back-top{border-radius:50%;bottom:32px;} - .back-top:hover .back-text{display:none;} + footer{position:relative;left:0;bottom:0;width:100%;height:0;} + footer .signature{position:absolute;right:12px;bottom:6px;color:#ccc;} + footer .signature a{color:#aaa;} } diff --git a/files/amWiki.docs.js b/files/amWiki.docs.js index 831d67c..3204010 100644 --- a/files/amWiki.docs.js +++ b/files/amWiki.docs.js @@ -1,5 +1,5 @@ /** - * @desc 文档加载渲染模块 + * amWiki Web端 - 文档加载与渲染模块 * @author Tevin */ @@ -13,23 +13,32 @@ var URL_ENCODE_NAME = 'AMWikiUrlEncode'; //记录url编码的键名 /** - * @class 创建一个文档管理对象 + * 文档管理 + * @constructor */ var Docs = function () { this.$e = { win: $(win), - //markdown文档内容容器 - view: $('#view') + //markdown 文档内容容器 + view: $('#view'), + //网页 title 标签 + title: $('title'), + //目录悬浮窗标题 + contentsTitle: $('#contentsTitle') }; this.data = { + //记录页面宽度 pageWidth: 0 }; - this.initUrlEncode(); - this.initHashEvent(); + this._initUrlEncode(); + this._initHashEvent(); }; - //初始化url编码类型标记 - Docs.prototype.initUrlEncode = function () { + /** + * 初始化url编码类型标记 + * @private + */ + Docs.prototype._initUrlEncode = function () { /* * 由于win与linux采用不同编码保存中文文件名 * 记录浏览器打开当前域名的wiki时服务器中文文件名的编码类型 @@ -43,8 +52,11 @@ delete localStorage.urlEcode }; - //修正移动端hash变化时滚动位置 - Docs.prototype.initHashEvent = function () { + /** + * 修正移动端hash变化时滚动位置 + * @private + */ + Docs.prototype._initHashEvent = function () { var that = this; this.data.pageWidth = this.$e.win.width(); this.$e.win.on('resize', function () { @@ -64,11 +76,33 @@ }); }; - //设置文档h1、h2、h3描记 - Docs.prototype.setTitlesAnchor = function () { + /** + * 转换链接文本 + * @param {String} str + * @returns {String} + * @private + */ + Docs.prototype._tramsformLinkText = function (str) { + return str + .replace(/^\s+|\s+$/g, '') //去除首尾空格 + .replace(/'/g, ''') //转义单引号 + .replace(/"/g, '"') //转义双引号,由于双引号无法正确传递给html属性,当作为hash时将删除处理 + .replace(/\(/g, '(') //转义左圆括号 + .replace(/\)/g, ')') //转义右圆括号 + .replace(/\[/g, '[') //转义左中括号 + .replace(/\]/g, ']'); //转义右中括号 + }; + + /** + * 设置文档h1、h2、h3描记 + * @returns {string} + * @private + */ + Docs.prototype._setTitlesAnchor = function () { var that = this; var $titles = null; var hash = ''; + var contentsMd = ''; //提取目录为markdown字符串 if (location.hash && location.hash.length > 1) { hash = location.hash.split('#')[1]; } @@ -77,11 +111,18 @@ $titles = that.$e.view.find('h1,h2,h3'); $titles.each(function (index, element) { var $this = $(element); - var text = $.trim($this.text()); + var text1 = that._tramsformLinkText($this.text()); + var text2 = text1.replace(/"/g, ''); //删除双引号 + //提取目录 + if ($this.is('h2')) { + contentsMd += '1. [' + text1 + '](#' + text2 + ' "' + text2 + '")\n'; + } else if ($this.is('h3')) { + contentsMd += '\t1. [' + text1 + '](#' + text2 + ' "' + text2 + '")\n'; + } //设置描记 - $this.prepend(anchorHtml.replace(/\{title\}/g, text)); + $this.prepend(anchorHtml.replace(/{title}/g, text2)); //首次打开页面滚动位置修正 - if (hash == text) { + if (hash == $this.text().replace(/"/g, '')) { if (that.data.pageWidth <= 720) { that.$e.win.scrollTop($this.offset().top - 55); } else { @@ -89,10 +130,82 @@ } } }); + if (contentsMd.indexOf('\t') == 0) { + contentsMd = '1. \n' + contentsMd; + } + return contentsMd; }; - //设置js注释隐藏 - Docs.prototype.setJSCommentDisable = function ($elm) { + /** + * 创建脚注 + * @param {String} text + * @returns {String} + * @private + */ + Docs.prototype._setFootnote = function (text) { + var footnotes = []; + var noteReg = /\[\^([ a-zA-Z\d]+)]/g; + var footReg = /\[\^([ a-zA-Z\d]+)]: ?([\S\s]+?)(?=\[\^(?:[ a-zA-Z\d]+)]|\n\n|$)/g; + var templates = $.trim($('#template\\:footnote').text()).split(/[\r\n]+\s*/g); + templates[4] += templates[5] + templates[6] + templates[7] + templates[8]; + var html = ''; + //提取脚注内容 + text = text.replace(footReg, function (match, s1, s2, index) { + var title = ''; + s2 = s2.replace(/"(.*?)"\s*$/, function (m, ss1) { + title = ss1; + return ''; + }); + footnotes.push({ + index: index, + note: s1, + content: s2, + title: title, + used: false + }); + //从页面上删除底部脚注内容 + return ''; + }); + //将脚注的标记转为序号 + text = text.replace(noteReg, function (match, s1) { + for (var i = 0, foot; foot = footnotes[i]; i++) { + if (foot.note == s1) { + foot.used = true; + return templates[0].replace(/{{index}}/g, i + 1 + '').replace('{{title}}', foot.title); + } + } + //当脚注的正文不存在,视标记文本为正文 + var length = footnotes.push({ + index: 0, + note: s1, + content: s1, + used: true + }); + return templates[0].replace(/{{index}}/g, length + ''); + }); + //生成底部脚注html + if (footnotes.length >= 1) { + for (var i = 0, foot; foot = footnotes[i]; i++) { + if (foot.used) { + html += templates[2] + .replace('{{index}}', i + 1) + .replace('{{content}}', foot.content) + .replace('{{back}}', templates[4].replace('{{index}}', i + 1)); + } else { + html += templates[3].replace('{{content}}', foot.content); + } + } + html = templates[1].replace('{{list}}', html); + } + return text + '\n
接口' + this._documents[id].api.replace(words, '' + words + '') + '
'; - this._addPorcessing(id, 'api', api); - this._addPorcessing(id, 'score', this._data.apiScore); + if (this._documents[id].api) { + var apiMatch = this._documents[id].api.match(wordsReg); + if (apiMatch && apiMatch.length > 0) { + var api = '接口' + + this._documents[id].api.replace(wordsReg, function (match) { + return '' + match + ''; + }) + '
'; + this._addPorcessing(id, 'api', api); + this._addPorcessing(id, 'score', this._data.apiScore); + } } //内容命中 - if (this._documents[id].content.indexOf(words) >= 0) { - var matches = this._documents[id].content.match(new RegExp('.{0,15}' + words + '.{0,30}', 'g')); - if (matches) { - var content = ''; - for (var i = 0, item; item = matches[i]; i++) { - if (i < 2) { - content += item.replace(words, '' + words + '') + '... '; - } + var contentMatch = this._documents[id].content.match(new RegExp('.{0,15}' + words + '.{0,30}', 'gi')); + if (contentMatch) { + var content = '
'; + for (var i = 0, item; item = contentMatch[i]; i++) { + if (i < 2) { + content += item.replace(wordsReg, function (match) { + return '' + match + ''; + }) + '... '; } - content += '
'; - this._addPorcessing(id, 'content', content); - this._addPorcessing(id, 'score', matches.length * this._data.textScore); } + content += ''; + this._addPorcessing(id, 'content', content); + this._addPorcessing(id, 'score', contentMatch.length * this._data.textScore); } } } @@ -176,21 +194,27 @@ return this._sortByScore(); }; - return new Searcher(); + //作为子进程加载时,仅子进程内有效 + //作为全局加载时,全局有效 + return this.AWSearcher = Searcher; - })(); + }).call(self); - self.onmessage = function (event) { - var data = event.data; - if (data.type == 'docs') { - searcher.initDocs(data.docs); - self.postMessage({type: 'ready'}); - } else if (data.type == 'search') { - searcher.matchWords(data.words); - self.postMessage({type: 'result', result: searcher.getResult()}); - } - }; - - self.postMessage({type: 'loaded'}); + //作为子进程工作时,通过 message 通讯工作 + if (atWorker) { + //计算器 + var searcher = new self.AWSearcher(); + self.onmessage = function (event) { + var data = event.data; + if (data.type == 'searcher:docs') { + searcher.initDocs(data.docs); + self.postMessage({type: 'searcher:ready'}); + } else if (data.type == 'searcher:search') { + searcher.matchWords(data.words); + self.postMessage({type: 'searcher:result', result: searcher.getResult()}); + } + }; + self.postMessage({type: 'searcher:loaded'}); + } })(self); \ No newline at end of file diff --git a/files/amWiki.storage.js b/files/amWiki.storage.js index 875b181..f44c494 100644 --- a/files/amWiki.storage.js +++ b/files/amWiki.storage.js @@ -1,5 +1,5 @@ /** - * @desc amWiki Web端·本地缓存模块 + * amWiki Web端 - 浏览器数据缓存模块 * @author Tevin */ @@ -11,28 +11,35 @@ var tools = win.tools; var wikiPath = tools.simString(win.location.pathname.replace('/', '').replace(/\//g, '_')).toUpperCase(); var LOCAL_STORAGE_NAME = 'AMWikiDataBase@' + wikiPath; //本地数据localStorage键名 + var LOCAL_STATES = 'AMWikiStates@' + wikiPath; //本地状态集键名 /** - * @class 创建一个本地存储管理对象 + * 本地存储管理 + * @constructor */ var Storage = function () { this._db = null; //内存中的文库缓存 - this.elm = { - $win: $(win), + this._states = null; //内存中的状态集 + this.$e = { + win: $(win), //更新全部缓存按钮 - $searchUpdate: $('#searchUpdate'), + searchUpdate: $('#searchUpdate'), //缓存状态 - $cacheState: $('#cacheState'), + cacheState: $('#cacheState'), //文档总数 - $cacheDocTotal: $('#cacheDocTotal'), + cacheDocTotal: $('#cacheDocTotal'), //上次全部缓存更新时间 - $cacheLasttime: $('#cacheLasttime') + cacheLasttime: $('#cacheLasttime') }; this._bridgeLocalStorage('read'); this._bindCtrl(); }; - //存取本地存储 + /** + * 存取本地存储 + * @param {String} type - read / save + * @private + */ Storage.prototype._bridgeLocalStorage = function (type) { if (type == 'read') { var defaultStr = '{"documents":{},"lastBuild":0}'; @@ -47,15 +54,24 @@ } }; - //绑定操作 + /** + * 绑定操作 + * @private + */ Storage.prototype._bindCtrl = function () { var that = this; - this.elm.$win.on('beforeunload', function () { + this.$e.win.on('beforeunload', function () { that._bridgeLocalStorage('save'); }); }; - //更新一篇文档,如果相同则不操作(对应内容不用重新渲染) + /** + * 更新一篇文档,如果相同则不操作(对应内容不用重新渲染) + * @param {String} uri + * @param {String} content + * @returns {Boolean} + * @public + */ Storage.prototype.update = function (uri, content) { var id = tools.simString(uri, 'short'); if (this._db.documents[id]) { @@ -72,10 +88,11 @@ }; /** - * @desc 保存一篇文档 - * @param uri {string} - 文档资源地址 - * @param content {string} - 文档内容 - * @param [id] {string} - 已经编码的文档地址,可选 + * 保存一篇文档 + * @param {String} uri - 文档资源地址 + * @param {String} content - 文档内容 + * @param {String} [id] - 已经编码的文档地址,可选 + * @public */ Storage.prototype.saveDoc = function (uri, content, id) { this.saveDocToDB(uri, content, id); @@ -83,7 +100,13 @@ this._changeSummary('sateOnly'); }; - //将文档存储到内存 + /** + * 将文档存储到内存 + * @param {String} uri + * @param {String} content + * @param {String} id + * @public + */ Storage.prototype.saveDocToDB = function (uri, content, id) { if (typeof uri != 'string' && uri == '') { throw new Error('Error, uri must be a string!'); @@ -101,9 +124,10 @@ }; /** - * @desc 读取一篇文档 - * @param uri {string} - 文档资源地址 - * @returns {string} - 文档内容 + * 读取一篇文档 + * @param {String} uri - 文档资源地址 + * @returns {String} 文档内容 + * @public */ Storage.prototype.read = function (uri) { var id = tools.simString(uri, 'short'); @@ -115,8 +139,24 @@ }; /** - * @desc 删除一篇文档 - * @param uri {string} - 文档资源地址 + * 读取一篇文档的时间 + * @param {String} uri - 文档资源地址 + * @returns {String} 文档内容 + * @public + */ + Storage.prototype.readTime = function (uri) { + var id = tools.simString(uri, 'short'); + if (this._db.documents[id]) { + return this._db.documents[id].timestamp; + } else { + return 0; + } + }; + + /** + * 删除一篇文档 + * @param {String} uri - 文档资源地址 + * @public */ Storage.prototype.remove = function (uri) { var id = tools.simString(uri, 'short'); @@ -125,15 +165,20 @@ this._changeSummary('sateOnly'); }; - //增涨文档打开数记录 + /** + * 增涨文档打开数记录 + * @param {String} uri + * @public + */ Storage.prototype.increaseOpenedCount = function (uri) { var id = tools.simString(uri, 'short'); //TODO: 待续...打开次数将一定程度影响排行 }; /** - * @desc 校对列表,清除失效文档 - * @param list {Array} - 由导航树偏平化生成的文档列表 + * 校对列表,清除失效文档 + * @param {Array} list - 由导航树偏平化生成的文档列表 + * @public */ Storage.prototype.checkLibChange = function (list) { this._indexing = list; @@ -150,7 +195,12 @@ this._changeSummary(); }; - //更新缓存摘要(位于搜素面板) + /** + * 更新缓存摘要(位于搜素面板) + * @param {String} stateOnly - 是否为只读 stateOnly / ... + * @param {String} prepare - 是否为预先 prepare / ... + * @private + */ Storage.prototype._changeSummary = function (stateOnly, prepare) { var libraryiesLong = 0; for (var p in this._db.documents) { @@ -160,51 +210,100 @@ } //如果是预先,百分数减1 if (prepare == 'prepare') { - this.elm.$cacheState.text(parseInt(libraryiesLong / this._indexing.length * 100 - 1) + '%'); + this.$e.cacheState.text(parseInt(libraryiesLong / this._indexing.length * 100 - 1) + '%'); } //非预先则正常 else { - this.elm.$cacheState.text(parseInt(libraryiesLong / this._indexing.length * 100) + '%'); + this.$e.cacheState.text(parseInt(libraryiesLong / this._indexing.length * 100) + '%'); } //如果不只是状态 if (stateOnly != 'stateOnly') { - this.elm.$cacheDocTotal.text(this._indexing.length); + this.$e.cacheDocTotal.text(this._indexing.length); if (this._db.lastBuild) { - this.elm.$cacheLasttime.text(win.tools.formatTime(this._db.lastBuild)); + this.$e.cacheLasttime.text(win.tools.formatTime(this._db.lastBuild)); } else { - this.elm.$cacheLasttime.text('0000-00-00 00:00:00'); + this.$e.cacheLasttime.text('0000-00-00 00:00:00'); } } }; - //清除内存中的库列表 + /** + * 清除内存中的库列表 + * @public + */ Storage.prototype.clearLibraries = function () { this._db.documents = {}; this._changeSummary('sateOnly'); }; - //完成本次缓存重建 + /** + * 完成本次缓存重建 + * @public + */ Storage.prototype.saveRebuild = function () { this._db.lastBuild = Date.now(); this._bridgeLocalStorage('save'); this._changeSummary(); }; - //返回导航列表 + /** + * 返回导航列表 + * @returns {Array} + * @public + */ Storage.prototype.getIndexList = function () { return this._indexing; }; - //获取当前缓存的所有文档 + /** + * 获取当前缓存的所有文档 + * @returns {{Object}} + * @public + */ Storage.prototype.getAllDocs = function () { return this._db.documents; }; - //获取缓存最后重建时间 + /** + * 获取缓存最后重建时间 + * @returns {Number} + * @public + */ Storage.prototype.getLastBuildTs = function () { return this._db.lastBuild; }; + /** + * 获取本地存储中指定名称的值 + * @param {String} name + * @returns {*} + * @public + */ + Storage.prototype.getStates = function (name) { + if (!this._states) { + this._states = JSON.parse(win.localStorage[LOCAL_STATES] || '{}'); + } + return this._states[name]; + }; + + /** + * 保持键值对到本地存储 + * @param {String} name + * @param {*} value + * @public + */ + Storage.prototype.setStates = function (name, value) { + if (!this._states) { + this._states = JSON.parse(win.localStorage[LOCAL_STATES] || '{}'); + } + if (typeof value == 'undefined') { + delete this._states[name]; + } else { + this._states[name] = value; + } + win.localStorage[LOCAL_STATES] = JSON.stringify(this._states); + }; + return win.AWStorage = Storage; })(window); \ No newline at end of file diff --git a/files/amWiki.testing.css b/files/amWiki.testing.css index a108d6c..ffdb544 100644 --- a/files/amWiki.testing.css +++ b/files/amWiki.testing.css @@ -4,9 +4,14 @@ .testing-show{display:none;position:absolute;top:0;right:0;padding:15px;cursor:pointer;} .testing-show.show{display:block;} .testing-show span{color:@colour-dark;} +.testing-show[disabled=disabled]{cursor:not-allowed;} +.testing-show[disabled=disabled] span{color:#999;} +.testing-show[disabled=disabled] i{display:none;} +.testing-show[disabled=disabled].on i{display:block;position:absolute;top:35px;right:8px;width:200px;padding:5px 7px;color:#fff;font-size:12px;line-height:18px;background:rgba(0,0,0,.75);border-radius:5px;} +.testing-show[disabled=disabled].on i:after{display:block;position:absolute;top:-4px;right:32px;width:0;height:0;border-top:8px solid rgba(0,0,0,.75);border-left:8px solid transparent;content:' ';transform:rotate(-45deg);} /* ---------- testing ---------- */ -.testing-box{display:none;position:absolute;top:0;right:0;width:100%;padding:45px;background-color:#fff;} +.testing-box{display:none;position:absolute;top:0;right:0;width:100%;padding:45px;min-height:100%;background-color:#fff;} .testing-box h2{position:relative;min-width:320px;padding:2px 10px;margin:5px 0;color:#fff;background-color:@colour-light;} .testing-box h2:after{position:absolute;top:0;right:-45px;width:45px;height:100%;content:" ";background-color:@colour-light;} .testing-box h2 small{position:absolute;top:0;right:0;font-size:14px;line-height:38px;font-weight:normal;} @@ -61,6 +66,7 @@ @media screen and (max-width:720px){ .testing-show{position:fixed;top:3px;right:40px;z-index:11000;height:46px;padding:0 10px;line-height:47px;background:#fff;} .testing-box{padding:25px;} + .testing-box h2:after{right:-25px;width:25px;} .testing-send{padding:20px 10px 35px;} .testing-send-url{width:70%;} .testing-param-val{width:45%;} diff --git a/files/amWiki.testing.js b/files/amWiki.testing.js index 3a21f52..923dc30 100644 --- a/files/amWiki.testing.js +++ b/files/amWiki.testing.js @@ -1,7 +1,8 @@ /** - * @desc amWiki Web端·简单ajax测试模块 + * amWiki Web端 - 简单 ajax 测试模块 * @author Tevin - * @summary 仅当页面存在“请求地址”、“请求类型”、“请求参数”三个h3标题时触发 + * + * @notice 仅当页面存在“请求地址”、“请求类型”、“请求参数”三个h3标题时触发,且参数列表表格顺序不能打乱 */ (function (win, $) { @@ -9,23 +10,26 @@ 'use strict'; /** - * @class 创建接口测试 + * 建接口测试 + * @constructor */ var Testing = function () { //缓存元素 - this.elm = { - $win: $(win), + this.$e = { + win: $(win), //测试面板 - $testingBox: $('#testingBox'), + testingBox: $('#testingBox'), //md文档渲染处 - $view: $('#view'), + view: $('#view'), + //上一篇下一篇切换 + sibling: $('#mainSibling'), //面板显示隐藏按钮 - $testingShow: null, + testingShow: null, //参数列表的容器 - $testingParam: $('#testingParam') + testingParam: $('#testingParam') }; //缓存数据 - this.data = { + this._data = { //全局参数列队 globalParams: [], //全局参数是否生效 @@ -34,7 +38,7 @@ paramTemplate: $('#template\\:formList').text() }; //请求数据 - this.request = { + this._request = { //请求地址 url: '', //请求类型 @@ -44,51 +48,52 @@ //全局参数 paramGlobal: [] }; - this.useGlobalParam(); - this.bindPanelCtrl(); - this.bindAjaxSend(); + this._useGlobalParam(); + this._bindPanelCtrl(); + this._bindAjaxSend(); }; /** - * @desc 抓取请求内容,抓取成功才显示按钮 + * 抓取请求内容,抓取成功才显示按钮 + * @public */ Testing.prototype.crawlContent = function () { var that = this; var testingReqState = [false, false, false]; - this.elm.$testingShow.removeClass('show'); - this.elm.$view.find('h3').each(function () { + this.$e.testingShow.removeClass('show'); + this.$e.view.find('h3').each(function () { var $this = $(this); var name = $.trim($this.text()); //抓取请求地址 if (name == '请求地址' && !testingReqState[0]) { - that.request.url = $.trim($this.next().text()); - if (that.request.url.indexOf('http') < 0) { - if (that.request.url.indexOf('/') == 0) { - that.request.url = 'http://' + location.host + that.request.url; + that._request.url = $.trim($this.next().text()); + if (that._request.url.indexOf('http') < 0) { + if (that._request.url.indexOf('/') == 0) { + that._request.url = 'http://' + location.host + that._request.url; } else { - that.request.url = 'http://' + location.host + '/' + that.request.url; + that._request.url = 'http://' + location.host + '/' + that._request.url; } } testingReqState[0] = true; } //抓取请求类型 else if (name == '请求类型' && !testingReqState[1]) { - that.request.method = $.trim($this.next().text()).toUpperCase(); + that._request.method = $.trim($this.next().text()).toUpperCase(); var methodState = false; ['GET', 'POST', 'PUT', 'DELETE'].forEach(function (value, index) { - if (that.request.method == value) { + if (that._request.method == value) { methodState = true; } }); if (!methodState) { - that.request.method = 'POST'; + that._request.method = 'POST'; } testingReqState[1] = true; } //抓取请求参数 else if (name == '请求参数' && !testingReqState[2]) { //清空参数列表 - that.request.params.length = 0; + that._request.params.length = 0; //不存在table直接无参数,存在table时开始解析 if ($this.next('table').length > 0) { $this.next('table').find('tbody').find('tr').each(function () { @@ -118,7 +123,7 @@ param.default = ''; } } - that.request.params.push(param); + that._request.params.push(param); } }); } @@ -126,154 +131,180 @@ } }); if (testingReqState[0] && testingReqState[1] && testingReqState[2]) { - this.initPanel(); + this._initPanel(); } else { - this.offPanel(); + this._offPanel(); } testingReqState = [false, false, false]; }; - //关闭测试面板 - Testing.prototype.offPanel = function () { - this.elm.$testingShow.removeClass('show'); - if (this.elm.$testingShow.hasClass('on')) { + /** + * 关闭测试面板 + * @private + */ + Testing.prototype._offPanel = function () { + this.$e.testingShow.removeClass('show'); + if (this.$e.testingShow.hasClass('on')) { this.displayBox('off'); } //清除抓取参数 - this.request.url = ''; - this.request.method = ''; - this.request.params = []; + this._request.url = ''; + this._request.method = ''; + this._request.params = []; //清空请求地址 $('#testingSendUrl').val(''); //还原请求类型 $('#testingSendType').find('option[value="POST"]').prop('selected', true); //清空参数列表 - this.elm.$testingParam.html(''); + this.$e.testingParam.html(''); //重置iframe $('#testingResponse')[0].contentWindow.location.reload(); }; - //测试面板填充数据 - Testing.prototype.initPanel = function () { - this.elm.$testingShow.addClass('show'); + /** + * 测试面板填充数据 + * @private + */ + Testing.prototype._initPanel = function () { + this.$e.testingShow.addClass('show'); //填充请求地址 - $('#testingSendUrl').val(this.request.url); + $('#testingSendUrl').val(this._request.url); //填充请求类型 - $('#testingSendType').find('option[value="' + this.request.method + '"]').prop('selected', true); + $('#testingSendType').find('option[value="' + this._request.method + '"]').prop('selected', true); //清空现有参数列表 - this.elm.$testingParam.html(''); + this.$e.testingParam.html(''); //填充参数列表 - if (this.request.params.length > 0) { + if (this._request.params.length > 0) { var paramsHTML = ''; - for (var i = 0; i < this.request.params.length; i++) { - paramsHTML += this.data.paramTemplate - .replace('{{describe}}', this.request.params[i].describe) - .replace('{{keyName}}', this.request.params[i].keyName) - .replace('{{default}}', this.request.params[i].default) - .replace('{{valueType}}', this.request.params[i].valueType) - .replace('{{required}}', this.request.params[i].required); + for (var i = 0; i < this._request.params.length; i++) { + paramsHTML += this._data.paramTemplate + .replace('{{describe}}', this._request.params[i].describe) + .replace('{{keyName}}', this._request.params[i].keyName) + .replace('{{default}}', this._request.params[i].default) + .replace('{{valueType}}', this._request.params[i].valueType) + .replace('{{required}}', this._request.params[i].required); } - this.elm.$testingParam.append(paramsHTML); + this.$e.testingParam.append(paramsHTML); } else { - this.elm.$testingParam.append('