├── .eslintignore ├── .eslintrc.js ├── LICENSE ├── README.md └── example ├── assets ├── media │ ├── gridea-search │ │ ├── ejs.min.js │ │ ├── fuse.basic.min.js │ │ ├── gridea-search.js │ │ └── result-template.ejs │ └── scripts │ │ └── index.js └── styles │ ├── abstracts │ └── varibles.less │ ├── components │ ├── about.less │ ├── archives.less │ ├── footer.less │ ├── header.less │ ├── home.less │ ├── post.less │ ├── tag.less │ └── tags.less │ ├── lib │ ├── colors.less │ ├── github.less │ ├── katex.less │ └── modern-normalize.less │ └── main.less ├── config.json ├── style-override.js └── templates ├── api.ejs ├── archives.ejs ├── includes ├── disqus.ejs ├── footer.ejs ├── gitalk.ejs ├── head.ejs ├── header.ejs ├── pagination.ejs ├── post-list-archives.ejs └── post-list.ejs ├── index.ejs ├── post.ejs ├── search.ejs ├── tag.ejs └── tags.ejs /.eslintignore: -------------------------------------------------------------------------------- 1 | *.js 2 | !example/assets/media/gridea-search/gridea-search.js -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parserOptions: { 4 | parser: 'babel-eslint', 5 | sourceType: 'module' 6 | }, 7 | env: { 8 | browser: true, 9 | node: true, 10 | es6: true 11 | }, 12 | extends: ['eslint:recommended'], 13 | 14 | // add your custom rules here 15 | rules: { 16 | 'accessor-pairs': 2, 17 | 'arrow-spacing': [2, { 18 | 'before': true, 19 | 'after': true 20 | }], 21 | 'block-spacing': [2, 'always'], 22 | 'brace-style': [2, '1tbs', { 23 | 'allowSingleLine': true 24 | }], 25 | 'camelcase': [0, { 26 | 'properties': 'always' 27 | }], 28 | 'comma-dangle': [2, 'never'], 29 | 'comma-spacing': [2, { 30 | 'before': false, 31 | 'after': true 32 | }], 33 | 'comma-style': [2, 'last'], 34 | 'constructor-super': 2, 35 | 'curly': [2, 'multi-line'], 36 | 'dot-location': [2, 'property'], 37 | 'eol-last': 2, 38 | 'eqeqeq': ['error', 'always', { 'null': 'ignore' }], 39 | 'generator-star-spacing': [2, { 40 | 'before': true, 41 | 'after': true 42 | }], 43 | 'handle-callback-err': [2, '^(err|error)$'], 44 | 'indent': [2, 2, { 45 | 'SwitchCase': 1 46 | }], 47 | 'jsx-quotes': [2, 'prefer-single'], 48 | 'key-spacing': [2, { 49 | 'beforeColon': false, 50 | 'afterColon': true 51 | }], 52 | 'keyword-spacing': [2, { 53 | 'before': true, 54 | 'after': true 55 | }], 56 | 'new-cap': [2, { 57 | 'newIsCap': true, 58 | 'capIsNew': false 59 | }], 60 | 'new-parens': 2, 61 | 'no-array-constructor': 2, 62 | 'no-caller': 2, 63 | 'no-console': 'off', 64 | 'no-class-assign': 2, 65 | 'no-cond-assign': 2, 66 | 'no-const-assign': 2, 67 | 'no-control-regex': 0, 68 | 'no-delete-var': 2, 69 | 'no-dupe-args': 2, 70 | 'no-dupe-class-members': 2, 71 | 'no-dupe-keys': 2, 72 | 'no-duplicate-case': 2, 73 | 'no-empty-character-class': 2, 74 | 'no-empty-pattern': 2, 75 | 'no-eval': 2, 76 | 'no-ex-assign': 2, 77 | 'no-extend-native': 2, 78 | 'no-extra-bind': 2, 79 | 'no-extra-boolean-cast': 2, 80 | 'no-extra-parens': [2, 'functions'], 81 | 'no-fallthrough': 2, 82 | 'no-floating-decimal': 2, 83 | 'no-func-assign': 2, 84 | 'no-implied-eval': 2, 85 | 'no-inner-declarations': [2, 'functions'], 86 | 'no-invalid-regexp': 2, 87 | 'no-irregular-whitespace': 2, 88 | 'no-iterator': 2, 89 | 'no-label-var': 2, 90 | 'no-labels': [2, { 91 | 'allowLoop': false, 92 | 'allowSwitch': false 93 | }], 94 | 'no-lone-blocks': 2, 95 | 'no-mixed-spaces-and-tabs': 2, 96 | 'no-multi-spaces': 2, 97 | 'no-multi-str': 2, 98 | 'no-multiple-empty-lines': [2, { 99 | 'max': 1 100 | }], 101 | 'no-native-reassign': 2, 102 | 'no-negated-in-lhs': 2, 103 | 'no-new-object': 2, 104 | 'no-new-require': 2, 105 | 'no-new-symbol': 2, 106 | 'no-new-wrappers': 2, 107 | 'no-obj-calls': 2, 108 | 'no-octal': 2, 109 | 'no-octal-escape': 2, 110 | 'no-path-concat': 2, 111 | 'no-proto': 2, 112 | 'no-redeclare': 2, 113 | 'no-regex-spaces': 2, 114 | 'no-return-assign': [2, 'except-parens'], 115 | 'no-self-assign': 2, 116 | 'no-self-compare': 2, 117 | 'no-sequences': 2, 118 | 'no-shadow-restricted-names': 2, 119 | 'no-spaced-func': 2, 120 | 'no-sparse-arrays': 2, 121 | 'no-this-before-super': 2, 122 | 'no-throw-literal': 2, 123 | 'no-trailing-spaces': 2, 124 | 'no-undef': 2, 125 | 'no-undef-init': 2, 126 | 'no-unexpected-multiline': 2, 127 | 'no-unmodified-loop-condition': 2, 128 | 'no-unneeded-ternary': [2, { 129 | 'defaultAssignment': false 130 | }], 131 | 'no-unreachable': 2, 132 | 'no-unsafe-finally': 2, 133 | 'no-unused-vars': [2, { 134 | 'vars': 'all', 135 | 'args': 'none' 136 | }], 137 | 'no-useless-call': 2, 138 | 'no-useless-computed-key': 2, 139 | 'no-useless-constructor': 2, 140 | 'no-useless-escape': 0, 141 | 'no-whitespace-before-property': 2, 142 | 'no-with': 2, 143 | 'one-var': [2, { 144 | 'initialized': 'never' 145 | }], 146 | 'operator-linebreak': [2, 'after', { 147 | 'overrides': { 148 | '?': 'before', 149 | ':': 'before' 150 | } 151 | }], 152 | 'padded-blocks': [2, 'never'], 153 | 'quotes': [2, 'single', { 154 | 'avoidEscape': true, 155 | 'allowTemplateLiterals': true 156 | }], 157 | 'semi': [2, 'never'], 158 | 'semi-spacing': [2, { 159 | 'before': false, 160 | 'after': true 161 | }], 162 | 'space-before-blocks': [2, 'always'], 163 | 'space-before-function-paren': [2, 'never'], 164 | 'space-in-parens': [2, 'never'], 165 | 'space-infix-ops': 2, 166 | 'space-unary-ops': [2, { 167 | 'words': true, 168 | 'nonwords': false 169 | }], 170 | 'spaced-comment': [2, 'always', { 171 | 'markers': ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ','] 172 | }], 173 | 'template-curly-spacing': [2, 'never'], 174 | 'use-isnan': 2, 175 | 'valid-typeof': 2, 176 | 'wrap-iife': [2, 'any'], 177 | 'yield-star-spacing': [2, 'both'], 178 | 'yoda': [2, 'never'], 179 | 'prefer-const': 2, 180 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0, 181 | 'object-curly-spacing': [2, 'always', { 182 | objectsInObjects: false 183 | }], 184 | 'array-bracket-spacing': [2, 'never'] 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 tangkaichuan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [Gridea](https://github.com/getgridea) 静态站点客户端-文章搜索主题插件 2 | 3 | **预览:,或者下载此项目将 `example/` 目录置于 Gridea 的 themes 路径,自行运行 Gridea。** 4 | 5 | **⚠ 仅适合有前端编程基础的 Gridea 主题开发者使用;可在此查看[使用 gridea-search 的主题](https://github.com/kytrun/gridea-search/discussions/10)**。 6 | 7 | ## 特点: 8 | 9 | * 独立搜索结果页面和网页链接 10 | * 主题开发者可高度复用已有模板 11 | * 延用官方 API,无附加学习成本 12 | * 仅使用搜索时加载所需资源 13 | * 支持样式自定义 14 | * 良好的兼容性 15 | 16 | ## 快速开始: 17 | 18 | ### 1. 以官方 [主题开发样板](https://github.com/getgridea/gridea-theme-starter) 为例,在其基础上新增文件: 19 | 20 | ``` 21 | ├── assets 22 | │ └── media 23 | │ └── gridea-search 24 | │ └── result-template.ejs - 搜索结果列表模板 25 | │ └── ejs.min.js - 模板渲染引擎 26 | │ └── fuse.basic.min.js - 模糊搜索 27 | │ └── gridea-search.js - 功能入口 28 | └── templates 29 | ├── api.ejs - 输出整站 API 30 | └── search.ejs - 搜索页面 31 | ``` 32 | ### 2. 修改以下文件 33 | 34 | #### (1) ./templates/api.ejs 35 | 36 | 输出 JSON 格式 API 的模板,为了尽量缩短网络传输时间,初始注释了部分属性,可根据需要取消相应注释。 37 | 38 | #### (2) ./templates/includes/header.ejs 39 | 40 | 公共模板,在适当位置添加搜索框供其他页面引用: 41 | 42 | ```html 43 |
44 | 45 |
46 | ``` 47 | 48 | 现有部分不可修改,可以添加 class 或 style 等其他属性。 49 | 50 | #### (3) ./templates/search.ejs 51 | 52 | 搜索页面,可基于其他页面修改,然后添加搜索结果渲染节点及依赖脚本。 53 | 54 | * **依赖的脚本 ` 3 | 4 |
5 | 6 | 18 | -------------------------------------------------------------------------------- /example/templates/includes/footer.ejs: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | -------------------------------------------------------------------------------- /example/templates/includes/gitalk.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | 21 | -------------------------------------------------------------------------------- /example/templates/includes/head.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= siteTitle %> 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | <% if (site.customConfig.ga) { %> 16 | 17 | 24 | <% } %> 25 | -------------------------------------------------------------------------------- /example/templates/includes/header.ejs: -------------------------------------------------------------------------------- 1 | 37 | -------------------------------------------------------------------------------- /example/templates/includes/pagination.ejs: -------------------------------------------------------------------------------- 1 |
2 | <% if (pagination.prev) { %> 3 | 上一页 4 | <% } %> 5 | <% if (pagination.next) { %> 6 | 下一页 7 | <% } %> 8 |
9 | -------------------------------------------------------------------------------- /example/templates/includes/post-list-archives.ejs: -------------------------------------------------------------------------------- 1 | <% let years = []; posts.forEach((item) => { const year = item.date.substring(0, 4); if (!years.includes(year)) { years.push(year); } }); %> 2 | 3 |
4 | <% years.forEach(function(year) { %> 5 |

<%- year %>

6 | <% posts.forEach(function(post) { %> 7 | <%if (post.date.indexOf(year) !== -1) { %> 8 | 16 | <% } %> 17 | <% }); %> 18 | <% }); %> 19 |
20 | -------------------------------------------------------------------------------- /example/templates/includes/post-list.ejs: -------------------------------------------------------------------------------- 1 |
2 | <% posts.forEach(function(post) { %> 3 | 25 | <% }); %> 26 |
27 | -------------------------------------------------------------------------------- /example/templates/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%- include('./includes/head', { siteTitle: themeConfig.siteName }) %> 4 | 5 | 6 |
7 |
8 | <%- include('./includes/header') %> 9 | 10 | <%- include('./includes/post-list') %> 11 | 12 | <%- include('./includes/pagination') %> 13 | 14 | <%- include('./includes/footer') %> 15 |
16 |
17 | 18 | 19 | -------------------------------------------------------------------------------- /example/templates/post.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%- include('./includes/head', { siteTitle: `${post.title} | ${themeConfig.siteName}` }) %> 4 | 5 | 6 |
7 |
8 | <%- include('./includes/header') %> 9 | 10 |
11 |
12 |

13 | <%= post.title %> 14 |

15 | 25 | <% if (themeConfig.showFeatureImage && post.feature) { %> 26 |
27 |
28 | <% } %> 29 |
30 | <%- post.content %> 31 |
32 |
33 |
34 | 35 | <% if (post.nextPost && !post.hideInList) { %> 36 | 44 | <% } %> 45 | 46 | <% if (typeof commentSetting !== 'undefined' && commentSetting.showComment) { %> 47 | <% if (commentSetting.commentPlatform === 'gitalk') { %> 48 | <%- include('./includes/gitalk') %> 49 | <% } %> 50 | 51 | <% if (commentSetting.commentPlatform === 'disqus') { %> 52 | <%- include('./includes/disqus') %> 53 | <% } %> 54 | <% } %> 55 | 56 | <%- include('./includes/footer') %> 57 |
58 |
59 | 60 | 61 | -------------------------------------------------------------------------------- /example/templates/search.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <%- include('./includes/head', { siteTitle: themeConfig.siteName }) %> 6 | 7 | 8 | 9 | 14 |
15 | <%- include('./includes/header') %> 16 |
17 |
搜索中......
18 | 19 |
20 |
21 | <%- include('./includes/footer') %> 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /example/templates/tag.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%- include('./includes/head', { siteTitle: `${tag.name} | ${themeConfig.siteName}` }) %> 4 | 5 | 6 |
7 |
8 | <%- include('./includes/header') %> 9 |
10 |

11 | 标签:#<%= tag.name %> 12 |

13 |
14 | <%- include('./includes/post-list') %> 15 | <%- include('./includes/pagination') %> 16 | <%- include('./includes/footer') %> 17 |
18 |
19 | 20 | 21 | -------------------------------------------------------------------------------- /example/templates/tags.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%- include('./includes/head', { siteTitle: themeConfig.siteName }) %> 4 | 5 | 6 |
7 |
8 | <%- include('./includes/header') %> 9 | 10 |
11 | <% tags.forEach((tag) => { %> 12 | <%= tag.name %> 13 | <% }); %> 14 |
15 | 16 | <%- include('./includes/footer') %> 17 |
18 |
19 | 20 | 21 | --------------------------------------------------------------------------------