/** * amWiki Web端 - 入口模块 * @author Tevin * @see {@link https://github.com/TevinLi/amWiki} * @license MIT - Released under the MIT license. */ $(function () { 'use strict'; /* 引入 */ //工具集 var tools = window.tools; //文档管理器 var docs = new AWDocs(); //本地存储 var storage = new AWStorage(); //全文库搜索 var search = new AWSearch(storage); //启用接口ajax测试 if (window.AWTesting) { var testing = new AWTesting(); } //是否支持history.state的API (IE9不支持) var HISTORY_STATE = 'pushState' in history; /* 页面基本 */ //页面元素 var $win = $(window), $body = $('body'), $menuIcon = $('#menuIcon'), //顶部折叠显示导航按钮 $container = $('#container'), //页面主容器 $nav = $('#nav'), //左侧导航 $menuBar = $('#menuBar'), //左侧导航内容 $filter = $('#menuFilter'), $filterClean = $filter.next('i'), $main = $('#main'), $mainInner = $main.children('.main-inner'), $mainSibling = $('#mainSibling'), //其他文章 $contents = $('#contents'); //目录 //是否为移动端 var isMobi = window.isMobi = (function () { var winW = $win.width(); var threshold = 720; var onResize = function () { winW = $win.width(); if (winW <= threshold) { $container.removeAttr('style'); } else { $container.height($win.height() - 70 - 15 - 20 * 2); } }; onResize(); $win.on('resize', onResize); return function () { return winW <= threshold; }; })(); //页面基本显示与操作 (function () { //菜单折叠 $menuBar.on('click', 'h4', function () { var $this = $(this); if (!$this.hasClass('on')) { $this.addClass('on'); $menuBar.find('a').removeClass('on'); } $menuBar.find('h5').removeClass('on').next('ul').hide(); }); $menuBar.on('click', 'h5', function () { var $this = $(this), $next = $this.next('ul'); if ($this.hasClass('on')) { $this.removeClass('on'); $next.slideUp(200, function () { $menuBar.trigger('scrollbar'); }); } else { $this.addClass('on'); $next.slideDown(200, function () { $menuBar.trigger('scrollbar'); }); } }); $menuBar.on('click', 'strong', function () { var $this = $(this), $next = $this.next('ul'); if ($this.hasClass('on')) { $this.removeClass('on'); $next.slideUp(200, function () { $menuBar.trigger('scrollbar'); }); } else { $this.addClass('on'); $next.slideDown(200, function () { $menuBar.trigger('scrollbar'); }); } }); //响应式菜单 $menuIcon.on('click', function () { var $this = $(this); if ($this.hasClass('close')) { $this.removeClass('close') .find('use').attr('xlink:href', '#icon:navStart'); $nav.removeClass('on'); } else { $this.addClass('close') .find('use').attr('xlink:href', '#icon:navClose'); $nav.addClass('on'); } }); $nav.on('navchange searchon searchoff', function (e) { $menuIcon.removeClass('close') .find('use').attr('xlink:href', '#icon:navStart'); $nav.removeClass('on'); }); //筛选操作 $filter.on('input propertychange input2', function () { var value = $filter.val().replace(/([\(\)\[\]\^\$\+])/g, '\\$1'); var valReg = new RegExp('(' + $.trim($filter.val()).split(/[ ,]/).join('|') + ')', 'ig'); if (value != '' && !/^\s$/g.test(value)) { $filterClean.removeClass('off'); $menuBar.find('h5').each(function () { filterNav('filter', valReg, $(this)); }); storage.setStates('navFilterKey', value); } else { $filterClean.addClass('off'); $menuBar.find('h5').each(function () { filterNav('open', null, $(this)); }); storage.setStates('navFilterKey'); } $menuBar.trigger('scrollbar'); }); //清空筛选 $filterClean.on('click', function () { $filter.val('').trigger('input2'); }); //显示svg图标 if (sessionStorage['AMWikiIconsSvg']) { $('#svgSymbols').append(sessionStorage['AMWikiIconsSvg']); } else { $.get('amWiki/images/icons.svg', function (svg) { sessionStorage['AMWikiIconsSvg'] = svg; $('#svgSymbols').append(svg); }, 'text').fail(function () { if (typeof AWPageMounts != 'undefined') { sessionStorage['AMWikiIconsSvg'] = AWPageMounts['icon'].content; $('#svgSymbols').append(AWPageMounts['icon'].content); } }); } //目录悬浮窗展开折叠 $contents.children('.btn').on('click', function (e) { $contents.toggleClass('on').removeClass('hover'); }); $contents.hover(function () { $contents.addClass('hover'); }, function () { $contents.removeClass('hover'); }); //开启滚动条 $('.scroller').scrollbar(); $('#backTop').on('click', function () { $mainInner.scrollTop(0); }); //图片放大 $main.imgsView(); //全局点击 $(document).on('click', function (e) { var $tag = $(e.target); //移动端 if (isMobi()) { //折叠目录悬浮窗 if ($tag.closest('#contents').length == 0) { $contents.removeClass('on').removeClass('on'); } } }); })(); /* 业务操作函数 */ /** * 向下递归进行导航筛选 * 当类型为筛选时,必定有正则 * 当文件夹匹配时,其所属链接和所有子级全部显示且显示匹配 * 当文件夹不匹配时,其所属链接和当前子级仅显示匹配,隐藏不匹配的项,下一级继续筛选 * 当类型为打开时,所属链接和子级一律全部显示不隐藏 * 如果有正则,显示当前匹配 * 如果无正则,清除匹配 * @param {String} type - 筛选类型,有 filter / open 两个值 * @param {regexp} valReg - 过滤筛选的正则 * @param {Object} $title - jquery 对象,“标题-列表”DOM结构中的标题 */ var filterNav = function (type, valReg, $title) { var $ul = $title.next('ul'); //因为一级文件夹和子级文件夹DOM结构不同,所以区分对待 // 显示上,strong 带 on 加粗显示,h5 加 off 隐藏 // 文本操作使用变量 $span,其他操作使用变量 $title var $span = $title.find('span'); //当类型为筛选时 if (type == 'filter' && valReg) { //当文件夹标题匹配时 if (valReg.test($span.text())) { $span.html($span.text().replace(valReg, '$1')); $title.addClass('on').removeClass('off'); //所属链接全部显示,且显示匹配 $ul.show().find('> li > a').each(function () { var $this = $(this); var $span2 = $this.find('span'); $span2.html($span2.text().replace(valReg, '$1')); $this.parent().removeClass('off'); }); //父级显示 showNavParents($title); //下一级筛选类型更改,文件和文件夹全部显示,且显示匹配 $ul.find('> li > strong').each(function () { filterNav('open', valReg, $(this)); }); } //当文件夹标题不匹配时 else { $span.text($span.text()); $title.removeClass('on'); //隐藏父级或隐藏h5 if ($title.is('h5')) { $title.parent().addClass('off'); } else { $title.addClass('off'); } //所属链接仅显示匹配的 $ul.hide().find('> li > a').each(function () { var $this = $(this); var $span2 = $this.find('span'); if (valReg.test($this.text())) { $span2.html($span2.text().replace(valReg, '$1')); $this.parent().removeClass('off'); //存在匹配时父级才显示 showNavParents($ul.show().prev()); } else { $span2.text($span2.text()); $this.parent().addClass('off'); } }); //下一级继续完全筛选 $ul.find('> li > strong').each(function () { filterNav('filter', valReg, $(this)); }); } } //当类型为打开,显示全部链接和文件夹 else if (type == 'open') { $title.removeClass('off'); if ($title.hasClass('on')) { $ul.show(); } else { $ul.hide(); } //当存在正则时,显示匹配 if (!!valReg) { $span.html($span.text().replace(valReg, '$1')); if (valReg.test($span.text())) { $ul.show(); //当文件夹名称命中,展开文件夹 } $ul.find('> li > a').each(function () { var $this = $(this); var $span2 = $this.find('span'); $span2.html($span2.text().replace(valReg, '$1')); if (valReg.test($this.text())) { $ul.show(); //当链接名称命中,展开文件夹 } $this.parent().removeClass('off'); }); //父级显示 showNavParents($title); //下一级继续以相同类型显示 $ul.find('> li > strong').each(function () { filterNav('open', valReg, $(this)); }); } //当正则不存在,显示所有本级和子级、清除匹配 else { $span.text($span.text()); $ul.find('> li > a').each(function () { var $span2 = $(this).find('span'); $span2.text($span2.text()); }); $ul.children('li').removeClass('off').children('strong').each(function () { filterNav('open', null, $(this)); }); } } }; //向上递归显示父级 var showNavParents = function ($title) { $title.addClass('on').removeClass('off'); //向上显示直到一级目录 if (!$title.is('h5')) { var $prev2 = $title.parent().removeClass('off').parent().show().prev(); showNavParents($prev2); } }; //改变底部上下篇目 var changeSibling = function ($item) { //如果未传导航项进来,隐藏上下篇目栏位 if (!$item) { $mainSibling.removeClass('on'); return; } //获取平级文档链接 var getDocLink = function (type, $elm) { var $other = $elm[type](); if ($other.length == 0) { return null; } if ($other.children('ul').length > 0) { return getDocLink(type, $other); } else { return $other.children('a'); } }; //设置上下篇目导航 var setSiblingNav = function (num, $other) { if ($other) { $mainSibling.find('a').eq(num) .attr('href', $other.attr('href')) .text($other.text()); } else { $mainSibling.find('a').eq(num) .removeAttr('href') .text('没有了'); } }; setSiblingNav(0, getDocLink('prev', $item)); setSiblingNav(1, getDocLink('next', $item)); if (testing && !testing.isOpen()) { $mainSibling.addClass('on'); } }; //改变导航显示 var changeNav = function (path) { if (/^home[-_].*?/.test(path) || path == '首页') { $menuBar.find('h4').addClass('on'); $menuBar.find('a').removeClass('on'); changeSibling(null); } else { var hsLink = false; $menuBar.find('a').each(function () { var $this = $(this); var path2 = $this.attr('href').split('file=')[1]; if (path2 == path) { hsLink = true; //本层加高亮 var $prev = $this.addClass('on').parent().parent().show().prev().addClass('on'); //父级高亮 showNavParents($prev); //改变上下篇切换 changeSibling($this.parent()); } else { $this.removeClass('on'); } }); if (hsLink) { $menuBar.find('h4').removeClass('on'); } } curPath = path; $menuBar.trigger('scrollbar'); }; //返回首页 var backHome = function () { docs.loadPage(homePage.path, function (state, content) { if (state == 'success') { changeNav(homePage.path); docs.renderDoc(content); storage.saveDoc(homePage.path, content); $main.trigger('scrollbar'); } }); if (HISTORY_STATE) { history.replaceState({path: homePage.path}, '', homePage.url); } }; //改变页面 var changePage = function (path, withOutPushState, callback) { //第一步,从本地缓存读取并渲染页面 var localDoc = storage.read(path); docs.renderDoc(localDoc); testing && testing.crawlContent(); $main.trigger('scrollbar'); $mainInner.scrollTop(0); //返回顶部 //更新history记录 if (!withOutPushState && HISTORY_STATE) { var path2 = path.replace(/&/g, '%26'); //对带 & 符号的地址特殊处理 history.pushState({path: path}, '', '?file=' + path2); } //第二步,加载服务器上的文档资源,如果有更新重新渲染 docs.loadPage(path, function (state, content) { //读取服务器文档失败时 if (state == 'error') { //如果本地缓存为空 if (localDoc == '') { backHome(); } //如果本地缓存不为空 else { //记录文档打开数 storage.increaseOpenedCount(path); callback && callback(); } } //读取服务器文档成功时 else if (state == 'success') { //如果服务器文档有更新,更新本地缓存、重新渲染页面、重新判断接口测试 if (content != localDoc) { docs.renderDoc(content); storage.saveDoc(path, content); testing && testing.crawlContent(); $main.trigger('scrollbar'); } //如果服务器文档与本地缓存一致,不进行任何操作 else { } //记录文档打开数 storage.increaseOpenedCount(path); callback && callback(); } }); }; //读取目录导航 var homePage = {}; var loadNav = function (callback) { var fillNav = function (data) { $menuBar.find('.scroller-content').html(marked(data)); //首页 var menuBarHome = $menuBar.find('h4'); homePage.text = menuBarHome.text(); homePage.url = menuBarHome.find('a').attr('href'); homePage.path = homePage.url.split('file=')[1]; menuBarHome.prepend(''); //列表 $menuBar.find('h5').each(function () { var $this = $(this); $this.html('' + $this.text() + '') }); $menuBar.trigger('scrollbar'); var pathList = []; //支持history api时,改变默认事件,导航不再跳转页面 $menuBar.find('a').each(function () { var $this = $(this); $this.html('' + $this.text() + ''); if (HISTORY_STATE) { var path = $this.attr('href').split('file=')[1]; pathList.push(path); $this.on('click', function () { search.displayBox('off'); //关闭搜索面板 changeNav(path); changePage(path); $this.trigger('navchange'); return false; }); } }); //上下翻页不再跳页面 $mainSibling.find('a').on('click', function () { if (HISTORY_STATE) { var $this = $(this); var href = $this.attr('href'); if (typeof href != 'undefined' && href != '') { var path = href.split('file=')[1]; changeNav(path); changePage(path); $this.trigger('navchange'); } return false; } }); $menuBar.find('strong').each(function () { var $this = $(this); $this.html('' + $this.text() + ''); }); //搜索结果不再跳转页面 $('#results').on('click', 'a', function () { if (HISTORY_STATE) { var $this = $(this); var href = $this.attr('href'); if (typeof href != 'undefined' && href != '') { var path = href.split('file=')[1]; search.displayBox('off'); //关闭搜索面板 changeNav(path); changePage(path); $this.trigger('navchange'); } return false; } }); //设置导航筛选初始值 var filterVal = storage.getStates('navFilterKey'); if (typeof filterVal != 'undefined' && filterVal != '') { $filter.val(filterVal).trigger('input2'); } //回调 callback && callback(pathList); }; $.get('library/$navigation.md?t=' + Date.now(), fillNav, 'text').fail(function () { if (typeof AWPageMounts != 'undefined') { fillNav(AWPageMounts['nav'].content); } }); }; //读取页面挂载数据文档部分 var loadPageMounts = function () { if (typeof AWPageMounts == 'undefined') { return; } //打开页面立即比较页面挂载数据时间与本地缓存更新时间 // 因为首页总是更新的,如果首页页面挂载数据时间大于本地缓存时间,则挂载数据一定已经经过重建 var homePath = AWPageMounts['home'].name.replace(/\.md$/, ''); var homeStorageTime = storage.readTime(homePath); //如果页面挂载数据经过了重建,开始更新,读取所有挂载数据插入到本地缓存,完成后删除释放资源占用 if (AWPageMounts['home'].timestamp > homeStorageTime) { //首页 storage.saveDoc(homePath, AWPageMounts['home'].content); delete AWPageMounts['home']; //其他文档 for (var lv1 in AWPageMounts) { if (!AWPageMounts.hasOwnProperty(lv1)) { continue; } if (!/^m\d+/.test(lv1)) { continue; } for (var i = 0, page; page = AWPageMounts[lv1][i]; i++) { storage.saveDoc(page.path.replace(/\.md$/, ''), page.content); } delete AWPageMounts[lv1]; } storage.saveRebuild(); } //否则直接删除页面挂载数据的文档部分,释放内存资源占用 else { delete AWPageMounts['home']; for (var name in AWPageMounts) { if (AWPageMounts.hasOwnProperty(name)) { if (/^m\d+/.test(name)) { delete AWPageMounts[name]; } } } } }; //根据hash改变滚动位置 var changeScrollByHash = function () { var hash = location.hash.split('#')[1]; //当不存在hash if (!hash || hash.length == '') { //检测是否在顶部,不在顶部滚动至顶部 if ($mainInner.scrollTop() != 0) { $mainInner.scrollTop(0); } return; } //获取hash指向的元素 var $hash = $('.anchor[name="' + hash + '"]'); if ($hash.length == 0) { return } //滚动至元素 $mainInner.scrollTop($hash.position().top + $mainInner.scrollTop() - 10); }; /* 启动应用 */ //解析地址参数 var curPath = tools.getURLParameter('file'); curPath = !curPath ? '首页' : decodeURI(curPath); curPath = curPath.replace(/%26/g, '&'); //加载导航 loadNav(function (list) { //核对本地存储 storage.checkLibChange(list); //读取页面挂载数据文档部分 loadPageMounts(); //首次打开改变导航 changeNav(curPath); //首次打开改变页面 changePage(curPath, true, changeScrollByHash); }); //history api 浏览器前进后退操作响应 if (HISTORY_STATE) { $win.on('popstate', function (e) { var path; //当有状态记录时,直接跳转 if (e.originalEvent.state) { path = e.originalEvent.state.path; path = path.replace(/%26/g, '&'); //改变导航 changeNav(path); //改变页面 changePage(path, true, changeScrollByHash); } //当没有状态记录时 else { path = tools.getURLParameter('file'); path = !path ? '首页' : decodeURI(path); path = path.replace(/%26/g, '&'); //判断 url 路径是否和当前一样,不一样才跳转 if (path != curPath) { //改变导航 changeNav(path); //改变页面 changePage(path, true, changeScrollByHash); } //相同时不跳转,根据 hash 变化改变位置 else { changeScrollByHash(); } } }); } /* 回调中转 */ //重建缓存 search.onNeedRebuildStorage = function (callback) { storage.clearLibraries(); var list = storage.getIndexList(); var count = 0; var load = function (path, i) { //为避免重建缓存频率过高,人为增设每个请求时间间隔 setTimeout(function () { docs.loadPage(path, function (state, content) { //文档读取成功时保存到内存 if (state == 'success') { storage.saveDocToDB(path, content); } //循环结束后完成重建 if (++count >= list.length) { storage.saveRebuild(); callback && callback(); } }); }, i * 100); }; for (var i = 0, item; item = list[i]; i++) { load(item, i); } }; });