[
  {
    "path": ".browserslistrc",
    "content": "> 1%\nlast 2 versions\nnot ie <= 8\n"
  },
  {
    "path": ".editorconfig",
    "content": "# EditorConfig is awesome: https://EditorConfig.org\n\n# top-most EditorConfig file\nroot = true\n\n# Unix-style newlines with a newline ending every file\n[*]\nend_of_line = lf\ninsert_final_newline = true\n\n# Matches multiple files with brace expansion notation\n# Set default charset\n[*.{js,py}]\ncharset = utf-8\n\n# Tab indentation (no size specified)\n[Makefile]\nindent_style = tab\n\n# Indentation override for all JS under lib directory\n[*.{js,ts}]\nindent_style = space\nindent_size = 2\n\n# Matches the exact files either package.json or .travis.yml\n[{package.json,.travis.yml}]\nindent_style = space\nindent_size = 2"
  },
  {
    "path": ".eslintrc.js",
    "content": "module.exports = {\n  root: true,\n  env: {\n    node: true,\n  },\n  extends: ['plugin:vue/essential', '@vue/airbnb', '@vue/typescript'],\n  rules: {\n    // js 和 ts 不需要检查 import 的文件后缀\n    'import/extensions': [\n      'error',\n      'always',\n      {\n        js: 'never',\n        ts: 'never',\n      },\n    ],\n    'no-restricted-syntax': [\n      'error',\n      'WithStatement',\n      'BinaryExpression[operator=\\'in\\']',\n    ],\n    // 可以 debugger\n    'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,\n    // 不要分号\n    semi: [2, 'never'],\n    // 全部单引号\n    quotes: [2, 'single'],\n    // 对象缩写\n    'object-shorthand': 0,\n    // 可以使用 console\n    'no-console': 0,\n    // 允许使用匿名函数\n    'func-names': 0,\n    // 允许属性的 key 值加引号\n    'quote-props': 0,\n    // 允许对函数的参数赋值\n    'no-param-reassign': 0,\n    // 函数的参数可以不使用\n    'no-unused-vars': 0,\n    // 不用强制 export default\n    'import/prefer-default-export': 0,\n    // 不禁止箭头函数直接return对象\n    'arrow-body-style': 0,\n    // 允许空行\n    'no-trailing-spaces': ['error', { skipBlankLines: true }],\n    // 允许short circuit evaluations\n    'no-unused-expressions': [\n      'error',\n      { allowShortCircuit: true, allowTernary: true },\n    ],\n    // 最长字符\n    'max-len': ['error', { code: 1500 }],\n    'vue/no-parsing-error': [\n      2,\n      {\n        'invalid-first-character-of-tag-name': false,\n      },\n    ],\n    // no-plusplus\n    'no-plusplus': 0,\n    'class-methods-use-this': 0,\n    'no-irregular-whitespace': 0,\n    'consistent-return': 0,\n    'import/no-extraneous-dependencies': 0,\n    'global-require': 0,\n    'no-continue': 0,\n    'linebreak-style': 0,\n  },\n  parserOptions: {\n    parser: '@typescript-eslint/parser',\n  },\n}\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug Report\nabout: 事情不像预期的那样工作吗？\ntitle: ''\nlabels: 'bug'\nassignees: ''\n\n---\n\n<!--\n  你好！感谢你正在考虑为 Gridea 提交一个 bug。请花一点点时间尽量详细地回答以下基础问题。\n\n  如果你不确定这是一个 Gridea 的 bug，请加入我们的用户群去讨论。\n\n  谢谢！\n-->\n\n<!-- \n  请确认你已经做了下面这些事情，若 bug 还是显而易见的，尽可详细地描述你的问题。\n\n  - 我已经安装了最新版的 Gridea\n  - 我已经搜索了已有的 Issues列表\n  - 我已经阅读了 Gridea 的 FAQ：https://gridea.dev/docs/faq.html\n  - 我已经查看了上手教程（视频或文字）：https://gridea.dev/docs\n-->\n\n## 我的环境\n\n| 名称     | 值   |\n| ------- | ---- |\n| 操作系统 |      |\n| 软件版本 |      |\n| 主题名称 |      |\n\n---\n\n## 期望行为\n\n<!--\n  你期望会发生什么？\n-->\n\n## 当前行为\n\n<!--\n  描述 bug 细节，确认出现此问题的复现步骤，例如点击了哪里，发生了什么情况？\n\n  你可以粘贴截图或附件。\n-->\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature Request\nabout: 想让我们为 Gridea 增加什么功能吗？\ntitle: 'feat: '\nlabels: 'Feature Request'\nassignees: ''\n\n---\n\n<!--\n  你好！感谢你愿意考虑希望 Gridea 增加某个新功能。请花一点点时间尽量详细地回答以下基础问题。\n\n  谢谢！\n-->\n\n## 概述\n\n<!--\n  对这个新功能的一段描述\n-->\n\n## 动机\n\n<!--\n  为什么你希望在 Gridea 中使用这个功能？\n-->\n\n## 详细解释\n\n<!--\n  详细描述这个新功能。\n\n  如果这是一个小功能，你可以忽略这部分。\n-->\n\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/question.md",
    "content": "---\nname: Question\nabout: 对 Gridea 有任何问题吗？\ntitle: ''\nlabels: 'question'\nassignees: ''\n\n---\n\n<!--\n  如果你有任何问题也可以通过此渠道来向我们反馈。不过，通常我们建议你可以加入我们的群组获得更及时的解答。\n\n  谢谢！\n-->"
  },
  {
    "path": ".gitignore",
    "content": ".DS_Store\nnode_modules\n/dist\n\n# local env files\n.env.local\n.env.*.local\n\n# Log files\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# Editor directories and files\n.idea\n.vscode\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw*\n\n#Electron-builder output\n/dist_electron"
  },
  {
    "path": ".npmrc",
    "content": "registry=https://registry.npmjs.org"
  },
  {
    "path": "CHANGELOG.md",
    "content": "## v0.9.1\n\n`2020-01-01`  \n\n- 修复首次安装使用 Gridea，无法预览 BUG\n\n## v0.9.0\n\n`2019-12-28`  \n\n**注意：若你之前有手动放置文件或文件夹到构建后文件夹（output 文件夹）本次升级有非兼容升级，请看下升级说明，若没有，可直接升级啦**\n\n升级说明：\n需手动复制到博客源文件夹的 static 文件夹（若无此文件夹可新建一个，新版使用时会自动生成，有静态文件需求的可放在此文件夹，构建时会直接复制到 output 文件夹）\n\n\n## 新增\n- 新增 SFTP 部署\n- 新增文章置顶功能\n- 自定义配置支持图片类型和数组类型，增加文章数据卡片类型\n- 自定义归档路径前缀\n- 文章页与标签页支持精简 URL 与默认 URL\n- 新增菜单拖动排序\n- 支持自定义模板渲染，具体可见[Gridea 文档](https://gridea.dev/docs)\n\n\n## 修复\n- 修复更改文章 URL 大小写会删除文章的 bug\n- 修复编辑器超出一屏后回车不会滚动的 bug\n- 修复 Linux（Ubuntu）初始化配置时，检测远程链接失败的 BUG\n\n## 优化\n- 文章内图片支持懒加载（基于 chrome 的lazy loading）\n- 升级 Electron 至 7.x\n- 标签页支持渲染列表隐藏文章（break change）\n- 增加预览唤出快捷键（Ctrl + P）\n- 优化编辑器 Windows 下字体显示\n- 增加渲染过程错误日志弹窗\n- 增加应用内通知系统\n\n## v0.8.3\n\n`2019-09-23`  \n\n「全新体验，助你妙笔生花」\n## 新增\n- 全新的内置编辑器，全新的编辑体验与快捷键\n- 增加 Emoji 输入面板与文章信息统计\n- 增加 `isHomepage`字段，赋予主题开发更多可能性\n- Markdown 渲染支持 `Emoji` 与 `implict-figures`\n- 增加 GA 统计，为了更好的优化产品\n- 新上架一款主题 「Tech」\n\n<img src=\"https://user-images.githubusercontent.com/17328747/65395740-9b01ca80-ddd1-11e9-8bee-c55ae6a41ef3.png\" alt=\"Tech\" width=\"100%\" />\n\n\n## 修复\n\n- 修复文章编辑页返回未保存提示，内容不丢失\n- 修复创建文章时报 URL 冲突 BUG\n\n## 优化\n- 升级 Electron 版本到 6.0+，更快了\n- 更改本地预览为 Server 模式\n- 提升了阅读时间的准确度\n- 更多细节优化，等你发现\n\n## v0.8.2\n\n`2019-07-15`  \n\n## 新增\n- 文章编辑页增加 `Command/Ctrl + S` 快捷键，快速保存文章\n- 增加繁体中文显示 （thanks @joinmouse ）\n- Makrdown 渲染支持 `Mark`、`Sup`、`Abbr`、`Footnote`\n- 侧边栏新增直达站点按钮\n- 增加上一篇文章渲染字段支持 `post.prevPost`\n- 文章增加 Reading Time（阅读时间，例如 `9 min read`） 字段 `post.stats`\n- Markdown 渲染支持设置图片大小，例如：`![test image](https://xxx/xxx.png =100x200)`\n\n## 优化\n- 更新 Electron 版本到 5.0.6，提升应用内菜单切换流畅性，更快了\n- 优化编辑器**编辑体验**与应用内预览样式（**强烈推荐**）\n- 新增文章编辑时可新建标签\n- 提升编辑器的安全性\n- Notes 主题显示优化与增加目录显示（**需手动更新主题**）\n- 应用菜单多语言支持与优化\n\n\n## v0.8.1\n\n`2019-05-26`  \n\n### 新增\n- 增加 **Linux 版**\n- 增加 RSS 支持，并在默认主题中增加链接（⚠️**需手动更新主题**，或在原主题中添加链接为 `href=\"<%= themeConfig.domain %>/atom.xml\"`）\n- 增加 **Task lists** 渲染支持\n- 增加**编辑器粘贴剪切板中图片**功能\n- 增加 [主题市场](https://gridea.dev/themes/) 与 [主题开发样板](https://github.com/getgridea/gridea-theme-starter)，欢迎参与主题开发\n\n\n### 修复\n- 修复文章标题包含特殊字符时渲染 BUG\n- 修复 KaTeX 公式渲染 BUG（⚠️**需手动更新主题**，若不更新主题，文章内使用 KaTeX 可能会造成文章公式显示异常）\n- 修复文章详情页 `site.posts` 包含隐藏文章 BUG\n- 修复应用内预览文章，点击链接为应用内打开 BUG\n- 修复编辑文章时，更改文章标题，文章 URL 自动变化 bug\n\n\n### 优化\n- 编辑器菜单栏置顶优化\n- 优化安装包体积，减小 **25%**\n\n> **提示：**\n> 手动更新主题，需将`源文件夹/themes` 文件夹里的旧主题删除，重启应用即可\n> （源文件夹默认为：`~/Documents/Gridea`，也可在应用内：**系统 -> 源文件夹** 进行查看）\n\n\n## v0.8.0\n\n`2019-04-14`  \n\n### 新增\n\n- 全新品牌名 **Gridea**，更易读！\n- 全新 LOGO，更好记！thanks @Leohuaji\n- 全新视觉，更简约！\n- 新增主题自定义配置功能，你可以使用主题提供的社交、谷歌统计、自定义样式...等自定义配置，当然你也可以开发新的主题，提供更有趣的自定义配置，尽情发挥你的想象（⚠️**需手动更新主题**）\n- 新增一款主题 「Paper」，英文手写体风格，欢迎体验\n- 封面图支持外链\n- 文章支持 `@[toc]` 显示文章目录，同时增加 `post.toc` 字段供主题显示\n- [**KaTeX**](https://katex.org/) 公式支持\n- 全新项目主页，欢迎 [访问](https://gridea.dev)\n\n### 修复\n- 修复基本配置未填写时，点击检测远程链接导致后续操作失效 BUG\n- 修复文章 URL 中含有 `/` 导致新建文章保存失败 BUG #29 \n- 修复 Favicon 和 Avatar 图片缓存问题 #24 \n- 修复更改源文件夹，新文件夹为空时 BUG\n\n### 优化\n- 固定文章编辑页附属信息栏，长文章编辑体验增强\n- 内置主题细节优化，适配主题自定义配置功能\n\n> **提示：**\n> 若您是老用户，安装新版之后，需将 `源文件夹/themes` 文件夹里的旧主题删除，重启应用，方可使用内置主题的自定义配置功能\n> （源文件夹默认为：旧：`~/Documents/hve-notes`，新：`~/Documents/Gridea`）\n\n\n\n## v0.7.7\n\n`2019-03-01`  \n\n- 🛠 修复了应用中非第一页文章删除时 BUG\n\n\n## v0.7.6\n\n`2019-02-27`  \n\n### 新增\n- 🔥 Windows 安装支持**自定义安装路径**\n- 🔥 增加**远程连接检测**功能\n- 🔥 新增一款主题: **Simple**\n- 🔥 增加**自定义源文件夹**，可利用 OneDrive、百度网盘、iCloud、Dropbox 等进行多设备同步\n- 🧩 增加文章目录(TOC)能力（数据支持，还需主题支持）\n\n### 修复\n- 🛠 文章时间渲染错误\n- 🛠 所有文章隐藏渲染错误\n- 🛠 部署除 master 分支外分支不成功问题\n- 🛠 标签带空格使用时渲染错误\n- 🛠 文章编辑全屏时无法点击输入菜单栏的 BUG\n- 🛠 文章快捷输入链接菜单BUG\n\n### 优化\n- 🌟 摘要分隔符判断逻辑 &lt;!-- more --> 单独行为摘要渲染分割符\n- 🌟 调整应用部分 UI\n- 🌟 增加应用退出快捷键\n- 🌟 增加同步和检测远程连接时开发者工具控制台错误信息显示，更精准的定位同步问题\n\n\n## v0.7.5 - 🎈 新春快乐 🏮\u001b 🧨 🧧 \n\n`2019-01-30`  \n\n- 🌈 重写应用 UI\n- 🔥 增加**归档页**渲染支持和**每页归档数**自定义功能\n- 🔥 增加**标签页**渲染支持\n- 🔥 增加文章 Link 和标签 Link 设置**默认生成方式**和**自定义**支持\n- 🔥 增加**列表隐藏文章**功能，用于创建特殊页面使用（例如，**关于**页）\n- 🔥 增加**显示日期格式自定义**功能\n- 🌟 优化默认主题「notes、fly」，支持归档页和标签页显示（️️⚡️ 需手动更新）\n- 🌟 更新文档\n> 注：\n> - 更新主题，需将 `~/Documents/hve-notes/themes` 文件夹中主题删除，重新启动应用即可\n\n\n## v0.7.0\n\n`2019-01-20`  \n\n- 🔥 增加多语言支持：English、简体中文（默认）\n- 🔥 增加多平台部署支持：Github Pages、Coding Pages\n- 🔥 增加多评论系统支持：Gitalk、DisqusJS（不兼容更新 ⚡️）\n- 🌟 全新的文章编辑页，专注写作\n- 🌟 优化多处交互体验\n- 🌟 完善文档，更强大的主题开发能力\n- 🌟 优化默认主题「notes、fly」：多评论支持及 UI 优化\n> 注：\n> - 更新主题，需将 `~/Documents/hve-notes/themes` 文件夹中对应主题删除，重新启动应用即可\n> - ⚡️ 若之前有设置过 Gitalk 配置，此版本更新后需重新设置一下（原 Gitalk 配置信息可见`~/Documents/hve-notes/config/setting.json`）\n\n## v0.6.4\n- 新增一款主题：「fly」，优化默认主题UI：「notes」 (若想更新 notes 主题，已安装旧版本用户需将旧版本应用文件夹 `~/Documents/hve-notes` 删除，安装新版应用即可（记得备份文件哦！）)\n- 新增头像设置功能\n- 调整多处 UI\n- 完善主题开发文档\n\n## v0.6.3\n- 🐛 更改构建配置，修复了应用初始化文件夹的 BUG（使用前，需要手动删除文档（Documents）目录下的 hve-notes 文件夹，然后打开应用即可正常初始化）\n- 修复 material icons load bug，感谢 @rosuH\n- 简单优化 favicon 页面 UI\n- 添加发布前配置简单校验\n- 🙏  感谢支持\n\n## v0.6.2\n- 优化默认主题 title、link 等\n- 新增版本更新提醒\n- 新增 CNAME 配置\n- 新增 favicon 配置\n- 新增 Gitalk 配置\n- 修复 Windows 下文章更新 bug\n\n## v0.6.1\n- 增加文章删除功能\n- 更新 footer 信息\n- 更改顶部窗口控制方式\n- 更改默认打开窗口大小\n- windows版 和 mac 版同步发布 ✌\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2019 EryouHao\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README-ru.md",
    "content": "<div align=\"center\">\n  <a href=\"https://gridea.dev\">\n    <img src=\"public/app-icons/gridea.png\"  width=\"80px\" height=\"80px\">\n  </a>\n  <h1 align=\"center\">\n    Gridea\n  </h1>\n  <h3 align=\"center\">\n    Клиент для ведения блога\n  </h3>\n\n  [Загрузить](https://github.com/getgridea/gridea/releases) | [Официальная страница](https://gridea.dev/)\n\n  <a href=\"https://github.com/getgridea/gridea/releases/latest\">\n    <img src=\"https://img.shields.io/github/release/getgridea/gridea.svg?style=flat-square\" alt=\"\">\n  </a>\n\n  <a href=\"https://github.com/getgridea/gridea/blob/master/LICENSE\">\n    <img src=\"https://img.shields.io/github/license/getgridea/gridea.svg?style=flat-square\" alt=\"\">\n  </a>\n  \n  <a href=\"https://github.com/getgridea/gridea/releases/latest\">\n    <img alt=\"GitHub All Releases\" src=\"https://img.shields.io/github/downloads/getgridea/gridea/total.svg?color=%2312b886&style=flat-square\">\n  </a>\n\n</div>\n\n<div align=\"center\">\n  <img src=\"gridea-app-en.png\">\n\n</div>\n\n[English](https://github.com/getgridea/gridea/blob/master/README.md) | Русский | [简体中文](https://github.com/getgridea/gridea/blob/master/README-zh_CN.md)  | [繁體中文](https://github.com/getgridea/gridea/blob/master/README-zh_TW.md)\n\n**[CHANGELOG](https://github.com/getgridea/gridea/blob/master/CHANGELOG.md)**\n\n👏  Спасибо, что используете **Gridea**！\n\n✍️  **Gridea** это статичный клиент для ведения блога. Вы можете использовать его для записи событий своей жизни, заметок о настроении, полученых знаний, эссе и идей...\n\n\n## Возможности👇\n📝  Используйте самый крутой редактор **Markdown** для быстрого создания и редактирования материалов\n\n🌉  Вставляйте изображения и диаграммы для обложки материала или вставляйте их в любое место статьи\n\n🏷️  Создавайте рубрики и группируйте материалы \n\n📋  Редактируйте меню и добавляйте туда любые ссылки\n\n💻  Используйте статичный клиент для **Windows** или **MacOS** или даже **Linux**\n\n🌎  Используйте **Github Pages** или **Coding Pages** чтобы поделиться материалами (в следующих версиях будет поддерживаться ещё больше платформ)\n\n💬  Запросто настройте и получите доступ с системам комментирования [Gitalk](https://github.com/gitalk/gitalk) или [DisqusJS](https://github.com/SukkaW/DisqusJS)\n\n🗺️  Наш клиент мультиязычен и имеет **упрощённый Китайский**、**традиционный Китайский**、 **Английский**、 **Русский**、 **Французский**、 **Японский** языки\n\n🌁  Используйте любую тему по умолчанию прямо в клиенте или подключите любую стороннюю тему, почуствуйте свободу при настройке тем\n\n🖥  Настройте папку для хранения материалов и синхронизируйте несколько устройств с помощью OneDrive, iCloud, Dropbox и т.д.\n\n\n🌱  Конечно, **Gridea** ещё очень молода и имеет немного недостатков, но, поверьте, она продолжает двигаться вперед и развиваться 🏃\n\nНадеемся, этот клиент станет вашим неразлучным партнером в творчестве\n\nДайте полную волю своим талантам!\n\n😘  Наслаждайтесь использованием~\n\n\n## Разработка\nЕсли вы хотите внести свой вклад в код, пожалуйста, проверьте [Contribution Guide (он правда на китайском)](https://github.com/getgridea/gridea/wiki/%E8%B4%A1%E7%8C%AE%E6%8C%87%E5%8D%97) на всякий случай.\n\n``` shell\n$ # Node version > v10.0.0 is requied\n$ git clone https://github.com/getgridea/gridea.git\n$ cd gridea\n$ yarn\n$ yarn electron:serve\n$ yarn electron:build\n```\n\n\n## Контакты и обратная связь\n[Канал в Telegram](https://t.me/joinchat/AAAAAEj82_lma0Y1wmyqUQ) | [Группа в Telegram](https://t.me/joinchat/IDY0ahRqb8NPodv95BNpBg)  | QQ 1 Group: 970332209 | QQ 2 Group: 923131213 | Author Twitter: @EryouHao\n\n\n## Примеры в скриншотах\n<div align=\"center\">\n  <img src=\"./files/themes.png\">\n</div>\n\n\n## Участие в разработке\nМы приветствуем все идеи и улучшения. Вы можете отправлять любые идеи в виде [pull requests](https://github.com/getgridea/gridea/pulls) или как GitHub [issues](https://github.com/getgridea/gridea/issues).   \n\n\n## Пожертвования разработчикам\n<div>\n  <img src=\"./files/wechat.png\" width=\"240px\">\n</div>\n\n\n## Сайт переводчика на Русский язык\n[Заглянуть ко мне на сайт](https://paul.bid/) там есть и другие интересные проекты 😉\n\n\n## Лицензия\n[MIT](https://github.com/getgridea/gridea/blob/master/LICENSE). Copyright (c) 2020-2023 EryouHao\n"
  },
  {
    "path": "README-zh_CN.md",
    "content": "<div align=\"center\">\n  <a href=\"https://gridea.dev\">\n    <img src=\"public/app-icons/gridea.png\"  width=\"80px\" height=\"80px\">\n  </a>\n  <h1 align=\"center\">\n    Gridea\n  </h1>\n  <h3 align=\"center\">\n    一个静态博客写作客户端\n  </h3>\n\n  [下 载](https://github.com/getgridea/gridea/releases) | [主 页](https://gridea.dev/)\n\n  <a href=\"https://github.com/getgridea/gridea/releases/latest\">\n    <img src=\"https://img.shields.io/github/release/getgridea/gridea.svg?style=flat-square\" alt=\"\">\n  </a>\n\n  <a href=\"https://github.com/getgridea/gridea/blob/master/LICENSE\">\n    <img src=\"https://img.shields.io/github/license/getgridea/gridea.svg?style=flat-square\" alt=\"\">\n  </a>\n\n  <a href=\"https://github.com/getgridea/gridea/releases/latest\">\n    <img alt=\"GitHub All Releases\" src=\"https://img.shields.io/github/downloads/getgridea/gridea/total.svg?color=%2312b886&style=flat-square\">\n  </a>\n\n</div>\n\n<div align=\"center\">\n  <img src=\"gridea-app.png\">\n</div>\n\n[English](https://github.com/getgridea/gridea/blob/master/README.md) | 简体中文 | [繁體中文](https://github.com/getgridea/gridea/blob/master/README-zh_TW.md)\n\n**[更新日志](https://github.com/getgridea/gridea/blob/master/CHANGELOG.md)**  \n\n👏  欢迎使用 **Gridea** ！  \n\n✍️  **Gridea** 一个静态博客写作客户端。你可以用它来记录你的生活、心情、知识、笔记、创意... ... \n\n## 特性👇\n📝  你可以使用最酷的 **Markdown** 语法，进行快速创作  \n\n🌉  你可以给文章配上精美的封面图和在文章任意位置插入图片  \n\n🏷️  你可以对文章进行标签分组  \n\n📋  你可以自定义菜单，甚至可以创建外部链接菜单  \n\n💻  你可以在 **𝖶𝗂𝗇𝖽𝗈𝗐𝗌** 或 **𝖬𝖺𝖼𝖮𝖲** 或 **Linux** 设备上使用此客户端  \n\n🌎  你可以使用 **𝖦𝗂𝗍𝗁𝗎𝖻 𝖯𝖺𝗀𝖾𝗌** 或 **Coding Pages** 向世界展示，未来将支持更多平台  \n\n💬  你可以进行简单的配置，接入 [Gitalk](https://github.com/gitalk/gitalk) 或 [DisqusJS](https://github.com/SukkaW/DisqusJS) 评论系统  \n\n🇬🇧  你可以使用**中文简体**、**中文繁体**、**英语**  \n\n🌁  你可以任意使用应用内默认主题或任意第三方主题，强大的主题自定义能力  \n\n🖥  你可以自定义源文件夹，利用 OneDrive、百度网盘、iCloud、Dropbox 等进行多设备同步  \n\n🌱 当然 **Gridea** 还很年轻，有很多不足，但请相信，它会不停向前🏃\n\n未来，它一定会成为你离不开的伙伴\n\n尽情发挥你的才华吧！\n\n😘 Enjoy~\n\n## 开发\n如果你想贡献代码，请提前参阅[贡献指南](https://github.com/getgridea/gridea/wiki/%E8%B4%A1%E7%8C%AE%E6%8C%87%E5%8D%97)\n``` shell\n$ # Node version > v10.0.0 is requied\n$ git clone https://github.com/getgridea/gridea.git\n$ cd gridea\n$ yarn\n$ yarn electron:serve\n$ yarn electron:build\n```\n\n## 联系\n[Telegram 频道](https://t.me/joinchat/AAAAAEj82_lma0Y1wmyqUQ) | [Telegram 群组](https://t.me/joinchat/IDY0ahRqb8NPodv95BNpBg)  | QQ 1 群: 970332209（已满）| QQ 2 群: 923131213 | 作者推特: @EryouHao\n\n## 示例截图\n<div align=\"center\">\n  <img src=\"./files/themes.png\">\n</div>\n\n## 贡献\n我们欢迎任何形式的贡献。你可以用 [pull requests](https://github.com/getgridea/gridea/pulls) 或 [issues](https://github.com/getgridea/gridea/issues) 的方式提交任何想法。  \n\n## 支持\n<div>\n  <img src=\"./files/wechat.png\" width=\"240px\">\n</div>\n\n## License\n[MIT](https://github.com/getgridea/gridea/blob/master/LICENSE). Copyright (c) 2020 EryouHao\n"
  },
  {
    "path": "README-zh_TW.md",
    "content": "<div align=\"center\">\n  <a href=\"https://gridea.dev\">\n    <img src=\"public/app-icons/gridea.png\"  width=\"80px\" height=\"80px\">\n  </a>\n  <h1 align=\"center\">\n    Gridea\n  </h1>\n  <h3 align=\"center\">\n    一個靜態博客寫作客戶端\n  </h3>\n\n  [下 載](https://github.com/getgridea/gridea/releases) | [主 頁](https://gridea.dev/)\n\n  <a href=\"https://github.com/getgridea/gridea/releases/latest\">\n    <img src=\"https://img.shields.io/github/release/getgridea/gridea.svg?style=flat-square\" alt=\"\">\n  </a>\n\n  <a href=\"https://github.com/getgridea/gridea/blob/master/LICENSE\">\n    <img src=\"https://img.shields.io/github/license/getgridea/gridea.svg?style=flat-square\" alt=\"\">\n  </a>\n\n  <a href=\"https://github.com/getgridea/gridea/releases/latest\">\n    <img alt=\"GitHub All Releases\" src=\"https://img.shields.io/github/downloads/getgridea/gridea/total.svg?color=%2312b886&style=flat-square\">\n  </a>\n\n</div>\n\n<div align=\"center\">\n  <img src=\"gridea-app.png\">\n</div>\n\n[English](https://github.com/getgridea/gridea/blob/master/README.md) | [简体中文](https://github.com/getgridea/gridea/blob/master/README-zh_CN.md) | 繁體中文\n\n**[更新日誌](https://github.com/getgridea/gridea/blob/master/CHANGELOG.md)**  \n\n👏  歡迎使用 **Gridea** ！  \n\n✍️  **Gridea** 一個靜態博客寫作客戶端。你可以用它來記錄你的生活、心情、知識、筆記、創意... ...\n\n## 特性👇\n📝  你可以使用最酷的 **Markdown** 語法，進行快速創作\n\n🌉  你可以給文章配上精美的封面圖和在文章任意位置插入圖片\n\n🏷️  你可以對文章進行標籤分組\n\n📋  你可以自定義菜單，甚至可以創建外部鏈接菜單\n\n💻  你可以在 **𝖶𝗂𝗇𝖽𝗈𝗐𝗌** 或 **𝖬𝖺𝖼𝖮𝖲** 設備上使用此客戶端\n\n🌎  你可以使用 **𝖦𝗂𝗍𝗁𝗎𝖻 𝖯𝖺𝗀𝖾𝗌** 或 **Coding Pages** 向世界展示，未來將支持更多平台\n\n💬  你可以進行簡單的配置，接入 [Gitalk](https://github.com/gitalk/gitalk) 或 [DisqusJS](https://github.com/SukkaW/DisqusJS) 評論系統  \n\n🇬🇧  你可以使用**中文簡體**、**中文繁體**、**英語**\n\n🌁  你可以任意使用應用內默認主題或任意第三方主題，強大的主題自定義能力 \n\n🖥  你可以自定義源文件夾，利用 OneDrive、百度網盤、iCloud、Dropbox 等進行多設備同步 \n\n🌱 當然 **Gridea** 還很年輕，有很多不足，但請相信，它會不停向前🏃\n\n未來，它一定會成為你離不開的伙伴\n\n盡情發揮你的才華吧！\n\n😘 Enjoy~\n\n## 開發\n如果你想貢獻代碼，請提前參閱[貢獻指南](https://github.com/getgridea/gridea/wiki/%E8%B4%A1%E7%8C%AE%E6%8C%87%E5%8D%97)\n``` shell\n$ # Node version > v10.0.0 is requied\n$ git clone https://github.com/getgridea/gridea.git\n$ cd gridea\n$ yarn\n$ yarn electron:serve\n$ yarn electron:build\n```\n\n## 聯繫\n[Telegram 頻道](https://t.me/joinchat/AAAAAEj82_lma0Y1wmyqUQ) | [Telegram 群組](https://t.me/joinchat/IDY0ahRqb8NPodv95BNpBg)  | QQ 1 群: 970332209（已满）| QQ 2 群: 923131213 | 作者推特: @EryouHao\n\n## 示例截圖\n<div align=\"center\">\n  <img src=\"./files/themes.png\">\n</div>\n\n## 貢獻\n我們歡迎任何形式的貢獻。你可以用 [pull requests](https://github.com/getgridea/gridea/pulls) 或 [issues](https://github.com/getgridea/gridea/issues) 的方式提交任何想法。  \n\n## 支持\n<div>\n  <img src=\"./files/wechat.png\" width=\"240px\">\n</div>\n\n## License\n[MIT](https://github.com/getgridea/gridea/blob/master/LICENSE). Copyright (c) 2020 EryouHao"
  },
  {
    "path": "README.md",
    "content": "<div align=\"center\">\n  <a href=\"https://gridea.dev\">\n    <img src=\"public/app-icons/gridea.png\"  width=\"80px\" height=\"80px\">\n  </a>\n  <h1 align=\"center\">\n    Gridea\n  </h1>\n  <h3 align=\"center\">\n    A static blog writing client\n  </h3>\n\n  [Download](https://github.com/getgridea/gridea/releases) | [Homepage](https://gridea.dev/)\n\n  <a href=\"https://github.com/getgridea/gridea/releases/latest\">\n    <img src=\"https://img.shields.io/github/release/getgridea/gridea.svg?style=flat-square\" alt=\"\">\n  </a>\n\n  <a href=\"https://github.com/getgridea/gridea/blob/master/LICENSE\">\n    <img src=\"https://img.shields.io/github/license/getgridea/gridea.svg?style=flat-square\" alt=\"\">\n  </a>\n  \n  <a href=\"https://github.com/getgridea/gridea/releases/latest\">\n    <img alt=\"GitHub All Releases\" src=\"https://img.shields.io/github/downloads/getgridea/gridea/total.svg?color=%2312b886&style=flat-square\">\n  </a>\n\n</div>\n\n<div align=\"center\">\n  <img src=\"gridea-app-en.png\">\n\n</div>\n\nEnglish | [Русский](https://github.com/getgridea/gridea/blob/master/README-ru.md)  | [简体中文](https://github.com/getgridea/gridea/blob/master/README-zh_CN.md)  | [繁體中文](https://github.com/getgridea/gridea/blob/master/README-zh_TW.md)\n\n**[CHANGELOG](https://github.com/getgridea/gridea/blob/master/CHANGELOG.md)**\n\n👏  Welcome to use **Gridea**！\n\n✍️  **Gridea** A static blog writing client. You can use it to record your life, mood, knowledge, notes and ideas...\n\n\n## Features👇\n📝  Use the coolest **Markdown** editor to create quickly\n\n🌉  Insert pictures and article cover charts anywhere in the article\n\n🏷️  Label and group articles \n\n📋  Customize menus and even create external link menus\n\n💻  Use this client on **Windows** or **MacOS**  or **Linux**\n\n🌎  Use **Github Pages** or **Coding Pages** to show the world that more platforms will be supported in the future\n\n💬  Simply configure and access the [Gitalk](https://github.com/gitalk/gitalk) or [DisqusJS](https://github.com/SukkaW/DisqusJS) comment system\n\n🗺️  Use **simplified Chinese**、**traditional Chinese**、 **English**、 **Russian**、 **French**\n\n🌁  Use any default theme within the application or any third-party theme, free theme customization\n\n🖥  Customize the source folder and synchronize multiple devices using OneDrive, iCloud, Dropbox, etc.\n\n\n🌱  Of course **Gridea** is still very young and has many shortcomings, but please believe it will keep moving forward 🏃\n\nIn the future, it will surely become your inseparable partner\n\nGive full play to your talents！\n\n😘  Enjoy~\n\n\n## Development\nIf you want to contribute code, please check the [Contribution Guide](https://github.com/getgridea/gridea/wiki/%E8%B4%A1%E7%8C%AE%E6%8C%87%E5%8D%97) in advance.\n\n``` shell\n$ # Node version > v10.0.0 is requied\n$ git clone https://github.com/getgridea/gridea.git\n$ cd gridea\n$ yarn\n$ yarn electron:serve\n$ yarn electron:build\n```\n\n\n## Contact\n[Telegram Channel](https://t.me/joinchat/AAAAAEj82_lma0Y1wmyqUQ) | [Telegram Group](https://t.me/joinchat/IDY0ahRqb8NPodv95BNpBg)  | QQ 1 Group: 970332209 | QQ 2 Group: 923131213 | Author Twitter: @EryouHao\n\n\n## Example Screenshots\n<div align=\"center\">\n  <img src=\"./files/themes.png\">\n</div>\n\n\n## Contributions\nWe welcome all contributions. You can submit any ideas as [pull requests](https://github.com/getgridea/gridea/pulls) or as GitHub [issues](https://github.com/getgridea/gridea/issues).   \n\n\n## Donation\n<div>\n  <img src=\"./files/wechat.png\" width=\"240px\">\n</div>\n\n\n## License\n[MIT](https://github.com/getgridea/gridea/blob/master/LICENSE). Copyright (c) 2020-2023 EryouHao\n"
  },
  {
    "path": "babel.config.js",
    "content": "module.exports = {\n  presets: [\n    '@vue/app',\n  ],\n  plugins: [\n    ['prismjs', {\n      'languages': ['javascript', 'css', 'markup', 'json', 'bash', 'sass', 'python', 'typescript', 'java', 'less', 'php', 'pug', 'jsx', 'c', 'ruby', 'rust', 'dart', 'stylus', 'swift', 'yaml', 'sql'],\n      'plugins': ['line-numbers'],\n      'theme': 'default',\n      'css': true,\n    }],\n  ],\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"gridea\",\n  \"version\": \"0.9.3\",\n  \"private\": true,\n  \"description\": \"A static blog writing client. You can use it to record your life, mood, knowledge, notes and ideas...\",\n  \"keywords\": [\n    \"gridea\",\n    \"static-site\",\n    \"static-site-generator\"\n  ],\n  \"homepage\": \"https://gridea.dev\",\n  \"license\": \"MIT\",\n  \"author\": {\n    \"name\": \"EryouHao\",\n    \"email\": \"eryouhao@gmail.com\"\n  },\n  \"repository\": \"https://github.com/getgridea/gridea\",\n  \"scripts\": {\n    \"electron:build\": \"vue-cli-service electron:build\",\n    \"electron:serve\": \"vue-cli-service electron:serve\",\n    \"postinstall\": \"electron-builder install-app-deps\",\n    \"lint\": \"vue-cli-service lint\"\n  },\n  \"dependencies\": {\n    \"@fontsource/noto-serif\": \"^4.5.9\",\n    \"@iktakahiro/markdown-it-katex\": \"^3.1.0\",\n    \"@sentry/electron\": \"^1.2.0\",\n    \"ant-design-vue\": \"^1.3.5\",\n    \"axios\": \"^0.27.2\",\n    \"bluebird\": \"^3.5.3\",\n    \"ejs\": \"^2.6.1\",\n    \"electron-google-analytics\": \"^0.1.0\",\n    \"electron-updater\": \"^4.2.0\",\n    \"feed\": \"^2.0.4\",\n    \"fs-extra\": \"^7.0.1\",\n    \"gray-matter\": \"^4.0.1\",\n    \"hpagent\": \"^1.0.0\",\n    \"isomorphic-git\": \"1.17.1\",\n    \"junk\": \"^3.1.0\",\n    \"katex\": \"0.12.0\",\n    \"less\": \"^3.9.0\",\n    \"lowdb\": \"^1.0.0\",\n    \"macaddress\": \"^0.2.9\",\n    \"markdown-it\": \"^13.0.1\",\n    \"markdown-it-abbr\": \"^1.0.4\",\n    \"markdown-it-emoji\": \"^2.0.2\",\n    \"markdown-it-footnote\": \"^3.0.3\",\n    \"markdown-it-image-lazy-loading\": \"^1.2.0\",\n    \"markdown-it-implicit-figures\": \"^0.9.0\",\n    \"markdown-it-imsize\": \"^2.0.1\",\n    \"markdown-it-mark\": \"^2.0.0\",\n    \"markdown-it-sub\": \"^1.0.0\",\n    \"markdown-it-sup\": \"^1.0.0\",\n    \"markdown-it-toc-and-anchor\": \"^4.2.0\",\n    \"moment\": \"^2.24.0\",\n    \"monaco-markdown\": \"^0.0.6\",\n    \"node-ssh\": \"^6.0.0\",\n    \"normalize-path\": \"^3.0.0\",\n    \"prismjs\": \"^1.16.0\",\n    \"remixicon\": \"2.3.0\",\n    \"shortid\": \"^2.2.14\",\n    \"simple-get\": \"^4.0.1\",\n    \"slug\": \"^0.9.3\",\n    \"ssh2-sftp-client\": \"^4.2.4\",\n    \"striptags\": \"^3.1.1\",\n    \"transliteration\": \"^1.6.6\",\n    \"url-join\": \"^4.0.1\",\n    \"uuid\": \"^3.3.3\",\n    \"v-emoji-picker\": \"^1.1.6\",\n    \"vee-validate\": \"^3.1.3\",\n    \"vue\": \"^2.6.10\",\n    \"vue-class-component\": \"^6.0.0\",\n    \"vue-i18n\": \"^8.7.0\",\n    \"vue-property-decorator\": \"^7.3.0\",\n    \"vue-router\": \"^3.0.1\",\n    \"vue-shortkey\": \"^3.1.7\",\n    \"vue2-transitions\": \"^0.2.3\",\n    \"vuedraggable\": \"^2.23.2\",\n    \"vuex\": \"^3.0.1\",\n    \"vuex-class\": \"^0.3.1\"\n  },\n  \"devDependencies\": {\n    \"@types/bluebird\": \"^3.5.26\",\n    \"@types/ejs\": \"^2.6.3\",\n    \"@types/express\": \"^4.17.0\",\n    \"@types/fs-extra\": \"^5.0.5\",\n    \"@types/less\": \"^3.0.0\",\n    \"@types/lowdb\": \"^1.0.7\",\n    \"@types/markdown-it\": \"0.0.7\",\n    \"@types/marked\": \"^0.6.5\",\n    \"@types/normalize-path\": \"^3.0.0\",\n    \"@types/prismjs\": \"^1.16.0\",\n    \"@types/shortid\": \"0.0.29\",\n    \"@types/slug\": \"^0.9.1\",\n    \"@types/ssh2-sftp-client\": \"^4.1.1\",\n    \"@types/url-join\": \"^4.0.0\",\n    \"@types/uuid\": \"^3.4.5\",\n    \"@vue/cli-plugin-babel\": \"^3.6.0\",\n    \"@vue/cli-plugin-eslint\": \"^3.6.0\",\n    \"@vue/cli-plugin-typescript\": \"^3.6.0\",\n    \"@vue/cli-service\": \"^3.6.0\",\n    \"@vue/eslint-config-airbnb\": \"^4.0.0\",\n    \"@vue/eslint-config-typescript\": \"^4.0.0\",\n    \"babel-eslint\": \"^10.0.1\",\n    \"babel-plugin-prismjs\": \"^1.0.2\",\n    \"electron\": \"^7.3.3\",\n    \"eslint\": \"^5.16.0\",\n    \"eslint-plugin-vue\": \"^5.2.2\",\n    \"husky\": \"^1.3.1\",\n    \"less-loader\": \"^4.1.0\",\n    \"lint-staged\": \"^8.1.5\",\n    \"markdown-it-task-lists\": \"^2.1.1\",\n    \"postcss-import\": \"^12.0.1\",\n    \"tailwindcss\": \"^1.1.4\",\n    \"typescript\": \"^3.0.0\",\n    \"vue-cli-plugin-electron-builder\": \"2.0.0-beta.2\",\n    \"vue-template-compiler\": \"^2.6.10\"\n  },\n  \"main\": \"background.js\",\n  \"engines\": {\n    \"node\": \">=10.0.0\"\n  },\n  \"husky\": {\n    \"hooks\": {\n      \"pre-commit\": \"lint-staged\"\n    }\n  },\n  \"lint-staged\": {\n    \"*.{js,ts,tsx,vue}\": [\n      \"vue-cli-service lint\",\n      \"git add\"\n    ]\n  },\n  \"__npminstall_done\": false\n}\n"
  },
  {
    "path": "postcss.config.js",
    "content": "module.exports = {\n  plugins: [\n    require('postcss-import'),\n    require('tailwindcss'),\n    require('autoprefixer'),\n  ],\n}\n"
  },
  {
    "path": "public/default-files/config/posts.json",
    "content": "{\n  \"posts\": [],\n  \"tags\": [],\n  \"menus\": [\n    {\n      \"link\": \"/\",\n      \"name\": \"首页\",\n      \"openType\": \"Internal\"\n    },\n    {\n      \"link\": \"/archives\",\n      \"name\": \"归档\",\n      \"openType\": \"Internal\"\n    },\n    {\n      \"link\": \"/tags\",\n      \"name\": \"标签\",\n      \"openType\": \"Internal\"\n    },\n    {\n      \"link\": \"/post/about\",\n      \"name\": \"关于\",\n      \"openType\": \"Internal\"\n    }\n  ]\n}\n"
  },
  {
    "path": "public/default-files/config/setting.json",
    "content": "{\n  \"config\": {\n    \"platform\": \"github\",\n    \"domain\": \"\",\n    \"repository\": \"\",\n    \"branch\": \"\",\n    \"username\": \"\",\n    \"email\": \"\",\n    \"token\": \"\"\n  },\n  \"comment\": {\n    \"commentPlatform\": \"gitalk\",\n    \"disqusSetting\": {\n      \"api\": \"\",\n      \"apikey\": \"\",\n      \"shortname\": \"\"\n    },\n    \"gitalkSetting\": {\n      \"clientId\": \"\",\n      \"clientSecret\": \"\",\n      \"owner\": \"\",\n      \"repository\": \"\"\n    },\n    \"showComment\": false\n  }\n}\n"
  },
  {
    "path": "public/default-files/config/theme.json",
    "content": "{\n  \"config\": {\n    \"footerInfo\": \"Powered by <a href=\\\"https://github.com/getgridea/gridea\\\" target=\\\"_blank\\\">Gridea</a>\",\n    \"postPageSize\": 10,\n    \"archivesPageSize\": 50,\n    \"showFeatureImage\": true,\n    \"siteDescription\": \"温故而知新\",\n    \"siteName\": \"Gridea\",\n    \"themeName\": \"notes\",\n    \"dateFormat\": \"YYYY-MM-DD\",\n    \"postUrlFormat\": \"SLUG\",\n    \"tagUrlFormat\": \"SHORT_ID\",\n    \"feedCount\": 10,\n    \"feedFullText\": true\n  },\n  \"customConfig\": {}\n}\n"
  },
  {
    "path": "public/default-files/post-images/.gitkeep",
    "content": ""
  },
  {
    "path": "public/default-files/posts/about.md",
    "content": "---\ntitle: 关于\ndate: 2019-01-25 19:09:48\ntags: \npublished: true\nhideInList: true\nfeature: \n---\n> 欢迎来到我的小站呀，很高兴遇见你！🤝\n\n## 🏠 关于本站\n\n## 👨‍💻 博主是谁\n\n## ⛹ 兴趣爱好\n\n## 📬 联系我呀\n"
  },
  {
    "path": "public/default-files/posts/hello-gridea.md",
    "content": "---\ntitle: Hello Gridea\ndate: 2018-12-12\ntags: [Gridea]\npublished: true\nhideInList: false\nfeature: /post-images/hello-gridea.png\n---\n👏  欢迎使用 **Gridea** ！  \n✍️  **Gridea** 一个静态博客写作客户端。你可以用它来记录你的生活、心情、知识、笔记、创意... ... \n\n<!-- more -->\n\n[Github](https://github.com/getgridea/gridea)  \n[Gridea 主页](https://gridea.dev/)  \n[示例网站](https://fehey.com/)\n\n## 特性👇\n📝  你可以使用最酷的 **Markdown** 语法，进行快速创作  \n\n🌉  你可以给文章配上精美的封面图和在文章任意位置插入图片  \n\n🏷️  你可以对文章进行标签分组  \n\n📋  你可以自定义菜单，甚至可以创建外部链接菜单  \n\n💻  你可以在 **Windows**，**MacOS** 或 **Linux** 设备上使用此客户端  \n\n🌎  你可以使用 **𝖦𝗂𝗍𝗁𝗎𝖻 𝖯𝖺𝗀𝖾𝗌** 或 **Coding Pages** 向世界展示，未来将支持更多平台  \n\n💬  你可以进行简单的配置，接入 [Gitalk](https://github.com/gitalk/gitalk) 或 [DisqusJS](https://github.com/SukkaW/DisqusJS) 评论系统  \n\n🇬🇧  你可以使用**中文简体**或**英语**  \n\n🌁  你可以任意使用应用内默认主题或任意第三方主题，强大的主题自定义能力  \n\n🖥  你可以自定义源文件夹，利用 OneDrive、百度网盘、iCloud、Dropbox 等进行多设备同步  \n\n🌱 当然 **Gridea** 还很年轻，有很多不足，但请相信，它会不停向前 🏃\n\n未来，它一定会成为你离不开的伙伴\n\n尽情发挥你的才华吧！\n\n😘 Enjoy~\n"
  },
  {
    "path": "public/default-files/static/404.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n  <title>Page Not Found</title>\n  <style>\n    * {\n      margin: 0;\n      padding: 0;\n    }\n    body {\n      display: flex;\n      flex-direction: column;\n      height: 100vh;\n      text-align: center;\n    }\n    .box {\n      flex: 1;\n      display: flex;\n      justify-content: center;\n      flex-direction: column;\n    }\n    .number {\n      font-size: 80px;\n      color: #666;\n      font-weight: bold;\n    }\n    .text {\n      font-size: 14px;\n      margin: 24px;\n      color: #333;\n    }\n    .btn-container {\n      display: flex;\n      justify-content: center;\n    }\n    .btn {\n      padding: 8px 24px;\n      display: inline-block;\n      text-decoration: none;\n      background: #fff;\n      border: 2px solid #efefef;\n      color: #333;\n      margin: 24px;\n      border-radius: 20px;\n      cursor: pointer;\n      display: flex;\n      align-items: center;\n    }\n    .footer {\n      padding: 16px;\n      border-top: 1px solid #efefef;\n      color: #777;\n      font-weight: lighter;\n    }\n    .footer a {\n      text-decoration: none;\n      font-weight: bold;\n      color: #000;\n    }\n  </style>\n</head>\n<body>\n  <div class=\"box\">\n    <div class=\"number\">4 0 4</div>\n    <div class=\"text\">\n      Page not found\n    </div>\n    <div class=\"btn-container\">\n      <a class=\"btn\" id=\"back\">\n        <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" style=\"margin-right: 8px;\">\n          <path fill=\"none\" d=\"M0 0h24v24H0z\"/>\n          <path d=\"M5.828 7l2.536 2.536L6.95 10.95 2 6l4.95-4.95 1.414 1.414L5.828 5H13a8 8 0 1 1 0 16H4v-2h9a6 6 0 1 0 0-12H5.828z\"/>\n        </svg>\n        Back\n      </a>\n    </div>\n  </div>\n  <footer class=\"footer\">\n    Powered by <a href=\"https://gridea.dev\" target=\"_blank\">Gridea</a>\n  </footer>\n  <script>\n    var back = document.getElementById('back')\n    back.onclick = function() { console.log('run...'); history.back() }\n  </script>\n</body>\n</html>"
  },
  {
    "path": "public/default-files/themes/fly/assets/styles/_blocks/archives.less",
    "content": ".archives-container {\n  padding: 32px 0;\n  .year {\n    font-size: 24px;\n    margin: 24px 0;\n    color: var(--c-base-purple);\n  }\n  .post {\n    margin-bottom: 32px;\n    .post-title {\n      font-size: 18px;\n      font-weight: normal;\n      position: relative;\n      transition: all 0.382s;\n      &:before {\n        content: '';\n        display: block;\n        width: 6px;\n        height: 6px;\n        border-radius: 50%;\n        background: var(--c-base-purple);\n        position: absolute;\n        left: 0px;\n        top: 50%;\n        transform: translateY(-50%);\n        opacity: 0;\n        transition: all 0.382s;\n      }\n      &:hover {\n        transform: translateX(8px);\n        &:before {\n          left: -8px;\n          opacity: 1;\n        }\n      }\n    }\n  }\n}"
  },
  {
    "path": "public/default-files/themes/fly/assets/styles/_blocks/fonts.less",
    "content": "@font-face {\n  font-family: 'icomoon';\n  src:  url('../media/fonts/icomoon.eot?pjaj8c');\n  src:  url('../media/fonts/icomoon.eot?pjaj8c#iefix') format('embedded-opentype'),\n    url('../media/fonts/icomoon.ttf?pjaj8c') format('truetype'),\n    url('../media/fonts/icomoon.woff?pjaj8c') format('woff'),\n    url('../media/fonts/icomoon.svg?pjaj8c#icomoon') format('svg');\n  font-weight: normal;\n  font-style: normal;\n}\n\n[class^=\"icon-\"], [class*=\" icon-\"] {\n  /* use !important to prevent issues with browser extensions that change fonts */\n  font-family: 'icomoon' !important;\n  speak: none;\n  font-style: normal;\n  font-weight: normal;\n  font-variant: normal;\n  text-transform: none;\n  line-height: 1;\n\n  /* Better Font Rendering =========== */\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n}\n\n.icon-activity-outline:before {\n  content: \"\\e900\";\n}\n.icon-alert-circle-outline:before {\n  content: \"\\e901\";\n}\n.icon-alert-triangle-outline:before {\n  content: \"\\e902\";\n}\n.icon-archive-outline:before {\n  content: \"\\e903\";\n}\n.icon-arrow-back-outline:before {\n  content: \"\\e904\";\n}\n.icon-arrow-circle-down-outline:before {\n  content: \"\\e905\";\n}\n.icon-arrow-circle-left-outline:before {\n  content: \"\\e906\";\n}\n.icon-arrow-circle-right-outline:before {\n  content: \"\\e907\";\n}\n.icon-arrow-circle-up-outline:before {\n  content: \"\\e908\";\n}\n.icon-arrow-down-outline:before {\n  content: \"\\e909\";\n}\n.icon-arrow-downward-outline:before {\n  content: \"\\e90a\";\n}\n.icon-arrow-forward-outline:before {\n  content: \"\\e90b\";\n}\n.icon-arrow-ios-back-outline:before {\n  content: \"\\e90c\";\n}\n.icon-arrow-ios-downward-outline:before {\n  content: \"\\e90d\";\n}\n.icon-arrow-ios-forward-outline:before {\n  content: \"\\e90e\";\n}\n.icon-arrow-ios-upward-outline:before {\n  content: \"\\e90f\";\n}\n.icon-arrow-left-outline:before {\n  content: \"\\e910\";\n}\n.icon-arrow-right-outline:before {\n  content: \"\\e911\";\n}\n.icon-arrow-up-outline:before {\n  content: \"\\e912\";\n}\n.icon-arrow-upward-outline:before {\n  content: \"\\e913\";\n}\n.icon-arrowhead-down-outline:before {\n  content: \"\\e914\";\n}\n.icon-arrowhead-left-outline:before {\n  content: \"\\e915\";\n}\n.icon-arrowhead-right-outline:before {\n  content: \"\\e916\";\n}\n.icon-arrowhead-up-outline:before {\n  content: \"\\e917\";\n}\n.icon-at-outline:before {\n  content: \"\\e918\";\n}\n.icon-attach-2-outline:before {\n  content: \"\\e919\";\n}\n.icon-attach-outline:before {\n  content: \"\\e91a\";\n}\n.icon-award-outline:before {\n  content: \"\\e91b\";\n}\n.icon-backspace-outline:before {\n  content: \"\\e91c\";\n}\n.icon-bar-chart-2-outline:before {\n  content: \"\\e91d\";\n}\n.icon-bar-chart-outline:before {\n  content: \"\\e91e\";\n}\n.icon-battery-outline:before {\n  content: \"\\e91f\";\n}\n.icon-behance-outline:before {\n  content: \"\\e920\";\n}\n.icon-bell-off-outline:before {\n  content: \"\\e921\";\n}\n.icon-bell-outline:before {\n  content: \"\\e922\";\n}\n.icon-bluetooth-outline:before {\n  content: \"\\e923\";\n}\n.icon-book-open-outline:before {\n  content: \"\\e924\";\n}\n.icon-book-outline:before {\n  content: \"\\e925\";\n}\n.icon-bookmark-outline:before {\n  content: \"\\e926\";\n}\n.icon-briefcase-outline:before {\n  content: \"\\e927\";\n}\n.icon-browser-outline:before {\n  content: \"\\e928\";\n}\n.icon-brush-outline:before {\n  content: \"\\e929\";\n}\n.icon-bulb-outline:before {\n  content: \"\\e92a\";\n}\n.icon-calendar-outline:before {\n  content: \"\\e92b\";\n}\n.icon-camera-outline:before {\n  content: \"\\e92c\";\n}\n.icon-car-outline:before {\n  content: \"\\e92d\";\n}\n.icon-cast-outline:before {\n  content: \"\\e92e\";\n}\n.icon-charging-outline:before {\n  content: \"\\e92f\";\n}\n.icon-checkmark-circle-2-outline:before {\n  content: \"\\e930\";\n}\n.icon-checkmark-circle-outline:before {\n  content: \"\\e931\";\n}\n.icon-checkmark-outline:before {\n  content: \"\\e932\";\n}\n.icon-checkmark-square-2-outline:before {\n  content: \"\\e933\";\n}\n.icon-checkmark-square-outline:before {\n  content: \"\\e934\";\n}\n.icon-chevron-down-outline:before {\n  content: \"\\e935\";\n}\n.icon-chevron-left-outline:before {\n  content: \"\\e936\";\n}\n.icon-chevron-right-outline:before {\n  content: \"\\e937\";\n}\n.icon-chevron-up-outline:before {\n  content: \"\\e938\";\n}\n.icon-clipboard-outline:before {\n  content: \"\\e939\";\n}\n.icon-clock-outline:before {\n  content: \"\\e93a\";\n}\n.icon-close-circle-outline:before {\n  content: \"\\e93b\";\n}\n.icon-close-outline:before {\n  content: \"\\e93c\";\n}\n.icon-close-square-outline:before {\n  content: \"\\e93d\";\n}\n.icon-cloud-download-outline:before {\n  content: \"\\e93e\";\n}\n.icon-cloud-upload-outline:before {\n  content: \"\\e93f\";\n}\n.icon-code-download-outline:before {\n  content: \"\\e940\";\n}\n.icon-code-outline:before {\n  content: \"\\e941\";\n}\n.icon-collapse-outline:before {\n  content: \"\\e942\";\n}\n.icon-color-palette-outline:before {\n  content: \"\\e943\";\n}\n.icon-color-picker-outline:before {\n  content: \"\\e944\";\n}\n.icon-compass-outline:before {\n  content: \"\\e945\";\n}\n.icon-copy-outline:before {\n  content: \"\\e946\";\n}\n.icon-corner-down-left-outline:before {\n  content: \"\\e947\";\n}\n.icon-corner-down-right-outline:before {\n  content: \"\\e948\";\n}\n.icon-corner-left-down-outline:before {\n  content: \"\\e949\";\n}\n.icon-corner-left-up-outline:before {\n  content: \"\\e94a\";\n}\n.icon-corner-right-down-outline:before {\n  content: \"\\e94b\";\n}\n.icon-corner-right-up-outline:before {\n  content: \"\\e94c\";\n}\n.icon-corner-up-left-outline:before {\n  content: \"\\e94d\";\n}\n.icon-corner-up-right-outline:before {\n  content: \"\\e94e\";\n}\n.icon-credit-card-outline:before {\n  content: \"\\e94f\";\n}\n.icon-crop-outline:before {\n  content: \"\\e950\";\n}\n.icon-cube-outline:before {\n  content: \"\\e951\";\n}\n.icon-diagonal-arrow-left-down-outline:before {\n  content: \"\\e952\";\n}\n.icon-diagonal-arrow-left-up-outline:before {\n  content: \"\\e953\";\n}\n.icon-diagonal-arrow-right-down-outline:before {\n  content: \"\\e954\";\n}\n.icon-diagonal-arrow-right-up-outline:before {\n  content: \"\\e955\";\n}\n.icon-done-all-outline:before {\n  content: \"\\e956\";\n}\n.icon-download-outline:before {\n  content: \"\\e957\";\n}\n.icon-droplet-off-outline:before {\n  content: \"\\e958\";\n}\n.icon-droplet-outline:before {\n  content: \"\\e959\";\n}\n.icon-edit-2-outline:before {\n  content: \"\\e95a\";\n}\n.icon-edit-outline:before {\n  content: \"\\e95b\";\n}\n.icon-email-outline:before {\n  content: \"\\e95c\";\n}\n.icon-expand-outline:before {\n  content: \"\\e95d\";\n}\n.icon-external-link-outline:before {\n  content: \"\\e95e\";\n}\n.icon-eye-off-2-outline:before {\n  content: \"\\e95f\";\n}\n.icon-eye-off-outline:before {\n  content: \"\\e960\";\n}\n.icon-eye-outline:before {\n  content: \"\\e961\";\n}\n.icon-facebook-outline:before {\n  content: \"\\e962\";\n}\n.icon-file-add-outline:before {\n  content: \"\\e963\";\n}\n.icon-file-outline:before {\n  content: \"\\e964\";\n}\n.icon-file-remove-outline:before {\n  content: \"\\e965\";\n}\n.icon-file-text-outline:before {\n  content: \"\\e966\";\n}\n.icon-film-outline:before {\n  content: \"\\e967\";\n}\n.icon-flag-outline:before {\n  content: \"\\e968\";\n}\n.icon-flash-off-outline:before {\n  content: \"\\e969\";\n}\n.icon-flash-outline:before {\n  content: \"\\e96a\";\n}\n.icon-flip-2-outline:before {\n  content: \"\\e96b\";\n}\n.icon-flip-outline:before {\n  content: \"\\e96c\";\n}\n.icon-folder-add-outline:before {\n  content: \"\\e96d\";\n}\n.icon-folder-outline:before {\n  content: \"\\e96e\";\n}\n.icon-folder-remove-outline:before {\n  content: \"\\e96f\";\n}\n.icon-funnel-outline:before {\n  content: \"\\e970\";\n}\n.icon-gift-outline:before {\n  content: \"\\e971\";\n}\n.icon-github-outline:before {\n  content: \"\\e972\";\n}\n.icon-globe-2-outline:before {\n  content: \"\\e973\";\n}\n.icon-globe-outline:before {\n  content: \"\\e974\";\n}\n.icon-google-outline:before {\n  content: \"\\e975\";\n}\n.icon-grid-outline:before {\n  content: \"\\e976\";\n}\n.icon-hard-drive-outline:before {\n  content: \"\\e977\";\n}\n.icon-hash-outline:before {\n  content: \"\\e978\";\n}\n.icon-headphones-outline:before {\n  content: \"\\e979\";\n}\n.icon-heart-outline:before {\n  content: \"\\e97a\";\n}\n.icon-home-outline:before {\n  content: \"\\e97b\";\n}\n.icon-image-outline:before {\n  content: \"\\e97c\";\n}\n.icon-inbox-outline:before {\n  content: \"\\e97d\";\n}\n.icon-info-outline:before {\n  content: \"\\e97e\";\n}\n.icon-keypad-outline:before {\n  content: \"\\e97f\";\n}\n.icon-layers-outline:before {\n  content: \"\\e980\";\n}\n.icon-layout-outline:before {\n  content: \"\\e981\";\n}\n.icon-link-2-outline:before {\n  content: \"\\e982\";\n}\n.icon-link-outline:before {\n  content: \"\\e983\";\n}\n.icon-linkedin-outline:before {\n  content: \"\\e984\";\n}\n.icon-list-outline:before {\n  content: \"\\e985\";\n}\n.icon-loader-outline:before {\n  content: \"\\e986\";\n}\n.icon-lock-outline:before {\n  content: \"\\e987\";\n}\n.icon-log-in-outline:before {\n  content: \"\\e988\";\n}\n.icon-log-out-outline:before {\n  content: \"\\e989\";\n}\n.icon-map-outline:before {\n  content: \"\\e98a\";\n}\n.icon-maximize-outline:before {\n  content: \"\\e98b\";\n}\n.icon-menu-2-outline:before {\n  content: \"\\e98c\";\n}\n.icon-menu-arrow-outline:before {\n  content: \"\\e98d\";\n}\n.icon-menu-outline:before {\n  content: \"\\e98e\";\n}\n.icon-message-circle-outline:before {\n  content: \"\\e98f\";\n}\n.icon-message-square-outline:before {\n  content: \"\\e990\";\n}\n.icon-mic-off-outline:before {\n  content: \"\\e991\";\n}\n.icon-mic-outline:before {\n  content: \"\\e992\";\n}\n.icon-minimize-outline:before {\n  content: \"\\e993\";\n}\n.icon-minus-circle-outline:before {\n  content: \"\\e994\";\n}\n.icon-minus-outline:before {\n  content: \"\\e995\";\n}\n.icon-minus-square-outline:before {\n  content: \"\\e996\";\n}\n.icon-monitor-outline:before {\n  content: \"\\e997\";\n}\n.icon-moon-outline:before {\n  content: \"\\e998\";\n}\n.icon-more-horizontal-outline:before {\n  content: \"\\e999\";\n}\n.icon-more-vertical-outline:before {\n  content: \"\\e99a\";\n}\n.icon-move-outline:before {\n  content: \"\\e99b\";\n}\n.icon-music-outline:before {\n  content: \"\\e99c\";\n}\n.icon-navigation-2-outline:before {\n  content: \"\\e99d\";\n}\n.icon-navigation-outline:before {\n  content: \"\\e99e\";\n}\n.icon-npm-outline:before {\n  content: \"\\e99f\";\n}\n.icon-options-2-outline:before {\n  content: \"\\e9a0\";\n}\n.icon-options-outline:before {\n  content: \"\\e9a1\";\n}\n.icon-pantone-outline:before {\n  content: \"\\e9a2\";\n}\n.icon-paper-plane-outline:before {\n  content: \"\\e9a3\";\n}\n.icon-pause-circle-outline:before {\n  content: \"\\e9a4\";\n}\n.icon-people-outline:before {\n  content: \"\\e9a5\";\n}\n.icon-percent-outline:before {\n  content: \"\\e9a6\";\n}\n.icon-person-add-outline:before {\n  content: \"\\e9a7\";\n}\n.icon-person-delete-outline:before {\n  content: \"\\e9a8\";\n}\n.icon-person-done-outline:before {\n  content: \"\\e9a9\";\n}\n.icon-person-outline:before {\n  content: \"\\e9aa\";\n}\n.icon-person-remove-outline:before {\n  content: \"\\e9ab\";\n}\n.icon-phone-call-outline:before {\n  content: \"\\e9ac\";\n}\n.icon-phone-missed-outline:before {\n  content: \"\\e9ad\";\n}\n.icon-phone-off-outline:before {\n  content: \"\\e9ae\";\n}\n.icon-phone-outline:before {\n  content: \"\\e9af\";\n}\n.icon-pie-chart-outline:before {\n  content: \"\\e9b0\";\n}\n.icon-pin-outline:before {\n  content: \"\\e9b1\";\n}\n.icon-play-circle-outline:before {\n  content: \"\\e9b2\";\n}\n.icon-plus-circle-outline:before {\n  content: \"\\e9b3\";\n}\n.icon-plus-outline:before {\n  content: \"\\e9b4\";\n}\n.icon-plus-square-outline:before {\n  content: \"\\e9b5\";\n}\n.icon-power-outline:before {\n  content: \"\\e9b6\";\n}\n.icon-pricetags-outline:before {\n  content: \"\\e9b7\";\n}\n.icon-printer-outline:before {\n  content: \"\\e9b8\";\n}\n.icon-question-mark-circle-outline:before {\n  content: \"\\e9b9\";\n}\n.icon-question-mark-outline:before {\n  content: \"\\e9ba\";\n}\n.icon-radio-button-off-outline:before {\n  content: \"\\e9bb\";\n}\n.icon-radio-button-on-outline:before {\n  content: \"\\e9bc\";\n}\n.icon-radio-outline:before {\n  content: \"\\e9bd\";\n}\n.icon-recording-outline:before {\n  content: \"\\e9be\";\n}\n.icon-refresh-outline:before {\n  content: \"\\e9bf\";\n}\n.icon-repeat-outline:before {\n  content: \"\\e9c0\";\n}\n.icon-rewind-left-outline:before {\n  content: \"\\e9c1\";\n}\n.icon-rewind-right-outline:before {\n  content: \"\\e9c2\";\n}\n.icon-save-outline:before {\n  content: \"\\e9c3\";\n}\n.icon-scissors-outline:before {\n  content: \"\\e9c4\";\n}\n.icon-search-outline:before {\n  content: \"\\e9c5\";\n}\n.icon-settings-2-outline:before {\n  content: \"\\e9c6\";\n}\n.icon-settings-outline:before {\n  content: \"\\e9c7\";\n}\n.icon-shake-outline:before {\n  content: \"\\e9c8\";\n}\n.icon-share-outline:before {\n  content: \"\\e9c9\";\n}\n.icon-shield-off-outline:before {\n  content: \"\\e9ca\";\n}\n.icon-shield-outline:before {\n  content: \"\\e9cb\";\n}\n.icon-shopping-bag-outline:before {\n  content: \"\\e9cc\";\n}\n.icon-shopping-cart-outline:before {\n  content: \"\\e9cd\";\n}\n.icon-shuffle-2-outline:before {\n  content: \"\\e9ce\";\n}\n.icon-shuffle-outline:before {\n  content: \"\\e9cf\";\n}\n.icon-skip-back-outline:before {\n  content: \"\\e9d0\";\n}\n.icon-skip-forward-outline:before {\n  content: \"\\e9d1\";\n}\n.icon-slash-outline:before {\n  content: \"\\e9d2\";\n}\n.icon-smartphone-outline:before {\n  content: \"\\e9d3\";\n}\n.icon-speaker-outline:before {\n  content: \"\\e9d4\";\n}\n.icon-square-outline:before {\n  content: \"\\e9d5\";\n}\n.icon-star-outline:before {\n  content: \"\\e9d6\";\n}\n.icon-stop-circle-outline:before {\n  content: \"\\e9d7\";\n}\n.icon-sun-outline:before {\n  content: \"\\e9d8\";\n}\n.icon-swap-outline:before {\n  content: \"\\e9d9\";\n}\n.icon-sync-outline:before {\n  content: \"\\e9da\";\n}\n.icon-text-outline:before {\n  content: \"\\e9db\";\n}\n.icon-thermometer-minus-outline:before {\n  content: \"\\e9dc\";\n}\n.icon-thermometer-outline:before {\n  content: \"\\e9dd\";\n}\n.icon-thermometer-plus-outline:before {\n  content: \"\\e9de\";\n}\n.icon-toggle-left-outline:before {\n  content: \"\\e9df\";\n}\n.icon-toggle-right-outline:before {\n  content: \"\\e9e0\";\n}\n.icon-trash-2-outline:before {\n  content: \"\\e9e1\";\n}\n.icon-trash-outline:before {\n  content: \"\\e9e2\";\n}\n.icon-trending-down-outline:before {\n  content: \"\\e9e3\";\n}\n.icon-trending-up-outline:before {\n  content: \"\\e9e4\";\n}\n.icon-tv-outline:before {\n  content: \"\\e9e5\";\n}\n.icon-twitter-outline:before {\n  content: \"\\e9e6\";\n}\n.icon-umbrella-outline:before {\n  content: \"\\e9e7\";\n}\n.icon-undo-outline:before {\n  content: \"\\e9e8\";\n}\n.icon-unlock-outline:before {\n  content: \"\\e9e9\";\n}\n.icon-upload-outline:before {\n  content: \"\\e9ea\";\n}\n.icon-video-off-outline:before {\n  content: \"\\e9eb\";\n}\n.icon-video-outline:before {\n  content: \"\\e9ec\";\n}\n.icon-volume-down-outline:before {\n  content: \"\\e9ed\";\n}\n.icon-volume-mute-outline:before {\n  content: \"\\e9ee\";\n}\n.icon-volume-off-outline:before {\n  content: \"\\e9ef\";\n}\n.icon-volume-up-outline:before {\n  content: \"\\e9f0\";\n}\n.icon-wifi-off-outline:before {\n  content: \"\\e9f1\";\n}\n.icon-wifi-outline:before {\n  content: \"\\e9f2\";\n}\n"
  },
  {
    "path": "public/default-files/themes/fly/assets/styles/_blocks/footer.less",
    "content": ".site-footer {\n  text-align: center;\n  font-size: 12px;\n  padding: 32px 16px;\n  .slogan {\n    margin-bottom: 24px;\n  }\n  .social-container {\n    margin-bottom: 24px;\n    font-size: 16px;\n    a {\n      margin: 0 8px;\n    }\n  }\n}\n"
  },
  {
    "path": "public/default-files/themes/fly/assets/styles/_blocks/header.less",
    "content": ".site-header-container {\n  position: fixed;\n  right: 0;\n  left: 0;\n  z-index: 1024;\n  background: #fff;\n  height: 56px;\n}\n.site-header {\n  height: 56px;\n  padding: 0 16px;\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  max-width: 768px;\n  margin: 0 auto;\n  box-shadow: inset 0px -1px 0px #fafafa;\n\n  .left {\n    display: flex;\n    .avatar {\n      border-radius: 50%;\n    }\n    .site-title {\n      margin: 0 16px;\n      font-size: 24px;\n      line-height: 32px;\n    }\n  }\n  .right {\n    .icon {\n      font-size: 24px;\n      cursor: pointer;\n    }\n  }\n}\n\n.menu-container {\n  display: none;\n  position: fixed;\n  top: 56px;\n  left: 0;\n  right: 0;\n  bottom: 0;\n  background: #fff;\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n  z-index: 1024;\n  .menu-list {\n    display: inline-flex;\n    flex-direction: column;\n    align-items: center;\n    justify-content: center;\n    margin-top: -40px;\n  }\n  .menu {\n    display: inline-block;\n    padding: 2px 8px;\n    font-size: 22px;\n    margin: 16px 0;\n    font-weight: bold;\n  }\n}\n\n.fade-enter-active, .fade-leave-active {\n  transition: opacity .5s;\n}\n.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {\n  opacity: 0;\n}\n"
  },
  {
    "path": "public/default-files/themes/fly/assets/styles/_blocks/link.less",
    "content": "\na,\n.link {\n  color: var(--c-base-black);\n  text-decoration: none;\n  cursor: pointer;\n  background: transparent 0 0;\n\n  &:hover,\n  &:focus {\n    // text-decoration: underline;\n  }\n\n  &:hover,\n  &:focus,\n  &:active {\n    outline: 0;\n  }\n}\n\na.purple-link {\n  position: relative;\n  background: linear-gradient(180deg,transparent 70%,rgba(101,125,225,.4) 0);\n}\n"
  },
  {
    "path": "public/default-files/themes/fly/assets/styles/_blocks/list.less",
    "content": ".main {\n  max-width: 768px;\n  margin: 0 auto;\n  display: flex;\n  min-height: 100vh;\n  flex-direction: column;\n}\n\n.content-container {\n  flex: 1;\n  padding: 48px 16px 32px;\n}\n\n.post-item {\n  display: flex;\n  justify-content: space-between;\n  padding: 32px 0;\n  max-height: 200px;\n  .content {\n    flex: 1;\n    // display: flex;\n    // flex-direction: column;\n    .post-title {\n      font-size: 22px;\n      color: #0C1E29;\n      display: inline-block;\n    }\n    .post-abstract {\n      font-size: 14px;\n      margin: 16px 0 8px;\n      flex: 1;\n      color: #4E616C;\n      overflow:hidden;\n      line-height: 24px;\n      position: relative;\n      display: -webkit-box;\n      overflow: hidden;\n      -webkit-line-clamp: 3;\n      -webkit-box-orient: vertical;\n\n      // &:after {\n      //   content: '';\n      //   position: absolute; \n      //   bottom: 0; \n      //   right: 0;\n      //   width: 120px; \n      //   height: 32px;\n        \n      //   /* \"transparent\" only works here because == rgba(0,0,0,0) */\n      //   background-image: linear-gradient(to right, transparent, white);\n      // }\n\n      strong {\n        font-weight: bolder;\n      }\n\n      a {\n        font-weight: 600;\n        background: linear-gradient(180deg,transparent 70%,rgba(101,125,225,.4) 0);\n      }\n\n      code {\n        padding: 0 3px;\n        margin: 0 2px;\n        background: rgba(195,195,195,0.41);\n        font-size: 0.9em;\n        border-radius: 2px;\n      }\n\n    }\n  }\n\n  .feature-container {\n    margin-left: 16px;\n    flex-shrink: 0;\n    width: 240px;\n    height: 135px;\n    position: relative;\n    overflow: hidden;\n    background-size: cover;\n    background-position: center;\n  }\n}\n\n.post-info {\n  color: var(--c-base-purple);\n  font-size: 12px;\n  span {\n    margin-right: 16px;\n  }\n  .icon {\n    font-size: 12px;\n    margin-right: 4px;\n  }\n  a {\n    color: var(--c-base-purple);\n  }\n}\n\n@media (max-width: 600px) {\n  .post-item {\n    flex-direction: column;\n    height: auto;\n    padding: 24px 0;\n    max-height: none;\n    .content {\n      order: 2;\n      display: block;\n      .post-title {\n        margin-top: 16px;\n        font-size: 18px;\n      }\n    }\n    .feature-container {\n      margin-left: 0;\n      order: 1;\n      height: 0;\n      width: 100%;\n      padding-top: 56%;\n      position: relative;\n      background-size: cover;\n      background-position: center;\n      // .feature {\n      //   width: 100%;\n      //   height: 100%;\n      //   position: absolute;\n      //   top: 0;\n      //   left: 0;\n      //   right: 0;\n      //   bottom: 0;\n      // }\n    }\n  }\n}\n"
  },
  {
    "path": "public/default-files/themes/fly/assets/styles/_blocks/pagination.less",
    "content": ".pagination-container {\n  padding: 24px 0;\n  overflow: hidden;\n  .prev, .next {\n    display: flex;\n    align-items: center;\n    padding: 2px 8px;\n    background: linear-gradient(180deg,transparent 70%,rgba(101,125,225,.4) 0);\n  }\n  .prev {\n    float: left;\n  }\n  .next {\n    float: right;\n  }\n}\n"
  },
  {
    "path": "public/default-files/themes/fly/assets/styles/_blocks/post.less",
    "content": ".post-detail {\n  padding: 32px 0;\n  .post-title {\n    font-size: 32px;\n    margin: 24px 0;\n    color: var(--c-base-black);\n  }\n  .post-detail-info {\n    padding: 0 0 32px;\n  }\n  .feature-container {\n    padding-top: 56.25%;\n    background-size: cover;\n    background-position: center;\n    border-radius: 2px;\n    box-shadow: 0 0 30px #eee;\n  }\n}\n\n.next-post {\n  text-align: center;\n  .post-title {\n    display: inline-block;\n    font-size: 22px;\n  }\n}\n\n.post-content {\n  h1, h2, h3, h4, h5, h6 {\n    padding: 16px 0;\n    color: #0C1E29;\n  }\n\n  a {\n    font-weight: 600;\n    background: linear-gradient(180deg,transparent 70%,rgba(101,125,225,.4) 0);\n  }\n  img {\n    display: block;\n    box-shadow: 0 0 30px #eee;\n    max-width: 100%;\n    border-radius: 2px;\n    margin: 16px auto;\n  }\n\n  p {\n    line-height: 1.725;\n    margin-bottom: 16px;\n    color: var(--c-base-blacklight);\n\n    code {\n      padding: 0 3px;\n      margin: 0 2px;\n      background: rgba(195,195,195,0.41);\n      font-size: 0.9em;\n      border-radius: 2px;\n      display: inline-block;\n    }\n  }\n\n  code {\n    color: #476582;\n    padding: .25rem .5rem;\n    margin: 0;\n    font-size: .85em;\n    background-color: rgba(27,31,35,.05);\n    border-radius: 3px;\n    display: inline-block;\n  }\n\n  blockquote {\n    background: #f3f5f7;\n    padding: 16px;\n    border-left: 2px solid var(--c-base-purple);\n    margin-bottom: 16px;\n    p {\n      margin-bottom: 0;\n    }\n  }\n\n  pre {\n    margin-bottom: 16px;\n    code {\n      font-size: 14px;\n      font-family: 'Source Code Pro', Consolas, Menlo, Monaco, 'Courier New', monospace;\n      padding: 2em 1em 1em;\n      border-radius: 5px;\n      line-height: 1.375;\n      position: relative;\n      background: #fafafa;\n      display: block;\n      &:after {\n        content: 'CODE';\n        display: block;\n        position: absolute;\n        left: 8px;\n        top: 4px;\n        font-size: 14px;\n        font-weight: bold;\n        color: #ccc;\n      }\n    }\n  }\n\n  table {\n    border-collapse: collapse;\n    margin: 1rem 0;\n    display: block;\n    overflow-x: auto;\n  }\n  tr {\n    border-top: 1px solid #dfe2e5;\n  }\n  td, th {\n    border: 1px solid #dfe2e5;\n    padding: .6em 1em;\n  }\n\n  ul, ol {\n    color: var(--c-base-blacklight);\n    padding-left: 24px;\n    line-height: 1.725;\n    margin-bottom: 16px;\n  }\n\n  strong {\n    font-weight: bolder;\n  }\n\n}\n"
  },
  {
    "path": "public/default-files/themes/fly/assets/styles/_blocks/tag.less",
    "content": ".current-tag-container {\n  padding-top: 32px;\n  color: var(--c-base-purple);\n}\n"
  },
  {
    "path": "public/default-files/themes/fly/assets/styles/_blocks/tags.less",
    "content": ".tags-container {\n  padding: 32px 0;\n  .tag {\n    color: var(--c-base-purple);\n    padding: 4px 8px;\n    display: inline-block;\n    border: 2px solid var(--c-base-purple);\n    margin: 0 16px 16px 0;\n    border-radius: 16px;\n  }\n}"
  },
  {
    "path": "public/default-files/themes/fly/assets/styles/_core/base.less",
    "content": "/*! modern-normalize | MIT License | https://github.com/sindresorhus/modern-normalize */\n\n/* Document\n   ========================================================================== */\n\n/**\n * Use a better box model (opinionated).\n */\n\n html {\n\tbox-sizing: border-box;\n}\n\n*,\n*::before,\n*::after {\n  box-sizing: inherit;\n  margin: 0;\n  padding: 0;\n}\n\n/**\n * Use a more readable tab size (opinionated).\n */\n\n:root {\n\t-moz-tab-size: 4;\n\ttab-size: 4;\n}\n\n/**\n * 1. Correct the line height in all browsers.\n * 2. Prevent adjustments of font size after orientation changes in iOS.\n */\n\nhtml {\n\tline-height: 1.15; /* 1 */\n\t-webkit-text-size-adjust: 100%; /* 2 */\n}\n\n/* Sections\n   ========================================================================== */\n\n/**\n * Remove the margin in all browsers.\n */\n\nbody {\n  margin: 0;\n  font-size: 16px;\n}\n\n/**\n * Improve consistency of default fonts in all browsers. (https://github.com/sindresorhus/modern-normalize/issues/3)\n */\n\nbody {\n\tfont-family:\n\t\t-apple-system,\n\t\tBlinkMacSystemFont,\n\t\t'Segoe UI',\n\t\tRoboto,\n\t\t\"PingFang SC\",\n\t\t\"Hiragino Sans GB\",\n\t\t\"Microsoft YaHei\",\n\t\tHelvetica,\n\t\tArial,\n\t\tsans-serif,\n\t\t'Apple Color Emoji',\n\t\t'Segoe UI Emoji',\n\t\t'Segoe UI Symbol';\n}\n\n/* Grouping content\n   ========================================================================== */\n\n/**\n * Add the correct height in Firefox.\n */\n\nhr {\n\theight: 0;\n}\n\n/* Text-level semantics\n   ========================================================================== */\n\n/**\n * Add the correct text decoration in Chrome, Edge, and Safari.\n */\n\nabbr[title] {\n\ttext-decoration: underline dotted;\n}\n\n/**\n * Add the correct font weight in Chrome, Edge, and Safari.\n */\n\nb,\nstrong {\n\tfont-weight: bolder;\n}\n\n/**\n * 1. Improve consistency of default fonts in all browsers. (https://github.com/sindresorhus/modern-normalize/issues/3)\n * 2. Correct the odd `em` font sizing in all browsers.\n */\n\ncode,\nkbd,\nsamp,\npre {\n\tfont-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace; /* 1 */\n\tfont-size: 1em; /* 2 */\n}\n\n/**\n * Add the correct font size in all browsers.\n */\n\nsmall {\n\tfont-size: 80%;\n}\n\n/**\n * Prevent `sub` and `sup` elements from affecting the line height in all browsers.\n */\n\nsub,\nsup {\n\tfont-size: 75%;\n\tline-height: 0;\n\tposition: relative;\n\tvertical-align: baseline;\n}\n\nsub {\n\tbottom: -0.25em;\n}\n\nsup {\n\ttop: -0.5em;\n}\n\n/* Forms\n   ========================================================================== */\n\n/**\n * 1. Change the font styles in all browsers.\n * 2. Remove the margin in Firefox and Safari.\n */\n\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n\tfont-family: inherit; /* 1 */\n\tfont-size: 100%; /* 1 */\n\tline-height: 1.15; /* 1 */\n\tmargin: 0; /* 2 */\n}\n\n/**\n * Remove the inheritance of text transform in Edge and Firefox.\n * 1. Remove the inheritance of text transform in Firefox.\n */\n\nbutton,\nselect { /* 1 */\n\ttext-transform: none;\n}\n\n/**\n * Correct the inability to style clickable types in iOS and Safari.\n */\n\nbutton,\n[type='button'],\n[type='reset'],\n[type='submit'] {\n\t-webkit-appearance: button;\n}\n\n/**\n * Remove the inner border and padding in Firefox.\n */\n\nbutton::-moz-focus-inner,\n[type='button']::-moz-focus-inner,\n[type='reset']::-moz-focus-inner,\n[type='submit']::-moz-focus-inner {\n\tborder-style: none;\n\tpadding: 0;\n}\n\n/**\n * Restore the focus styles unset by the previous rule.\n */\n\nbutton:-moz-focusring,\n[type='button']:-moz-focusring,\n[type='reset']:-moz-focusring,\n[type='submit']:-moz-focusring {\n\toutline: 1px dotted ButtonText;\n}\n\n/**\n * Correct the padding in Firefox.\n */\n\nfieldset {\n\tpadding: 0.35em 0.75em 0.625em;\n}\n\n/**\n * Remove the padding so developers are not caught out when they zero out `fieldset` elements in all browsers.\n */\n\nlegend {\n\tpadding: 0;\n}\n\n/**\n * Add the correct vertical alignment in Chrome and Firefox.\n */\n\nprogress {\n\tvertical-align: baseline;\n}\n\n/**\n * Correct the cursor style of increment and decrement buttons in Safari.\n */\n\n[type='number']::-webkit-inner-spin-button,\n[type='number']::-webkit-outer-spin-button {\n\theight: auto;\n}\n\n/**\n * 1. Correct the odd appearance in Chrome and Safari.\n * 2. Correct the outline style in Safari.\n */\n\n[type='search'] {\n\t-webkit-appearance: textfield; /* 1 */\n\toutline-offset: -2px; /* 2 */\n}\n\n/**\n * Remove the inner padding in Chrome and Safari on macOS.\n */\n\n[type='search']::-webkit-search-decoration {\n\t-webkit-appearance: none;\n}\n\n/**\n * 1. Correct the inability to style clickable types in iOS and Safari.\n * 2. Change font properties to `inherit` in Safari.\n */\n\n::-webkit-file-upload-button {\n\t-webkit-appearance: button; /* 1 */\n\tfont: inherit; /* 2 */\n}\n\n/* Interactive\n   ========================================================================== */\n\n/*\n * Add the correct display in Chrome and Safari.\n */\n\nsummary {\n\tdisplay: list-item;\n}\n"
  },
  {
    "path": "public/default-files/themes/fly/assets/styles/_core/colors.less",
    "content": "\n:root {\n  --c-base-black: #0C1E29;\n  --c-base-blacklight: #4E616C;\n  --c-base-purple: #5978F3;\n  --c-base-purplelight: #C2CCF2;\n  --c-base-bluelight: hwb(199, 1%, 4%);\n  --c-base-blue: hwb(209, 0%, 35%);\n  --c-base-green: hwb(123, 40%, 27%);\n  --c-base-graylight: hwb(0, 80%, 20%);\n  --c-base-gray: hwb(0, 60%, 40%);\n  --c-base-orange: hwb(22, 21%, 5%);\n  --c-base-red: hwb(5, 24%, 9%);\n  --c-base-white: hwb(0, 95%, 5%);\n  --c-base-yellow: hwb(48, 6%, 5%);\n}\n"
  },
  {
    "path": "public/default-files/themes/fly/assets/styles/_core/github.less",
    "content": ".hljs {\n  display: block;\n  overflow-x: auto;\n  padding: 0.5em;\n  color: #333;\n  background: #f8f8f8;\n}\n\n.hljs-comment,\n.hljs-quote {\n  color: #998;\n  font-style: italic;\n}\n\n.hljs-keyword,\n.hljs-selector-tag,\n.hljs-subst {\n  color: #333;\n  font-weight: bold;\n}\n\n.hljs-number,\n.hljs-literal,\n.hljs-variable,\n.hljs-template-variable,\n.hljs-tag .hljs-attr {\n  color: #008080;\n}\n\n.hljs-string,\n.hljs-doctag {\n  color: #d14;\n}\n\n.hljs-title,\n.hljs-section,\n.hljs-selector-id {\n  color: #900;\n  font-weight: bold;\n}\n\n.hljs-subst {\n  font-weight: normal;\n}\n\n.hljs-type,\n.hljs-class .hljs-title {\n  color: #458;\n  font-weight: bold;\n}\n\n.hljs-tag,\n.hljs-name,\n.hljs-attribute {\n  color: #000080;\n  font-weight: normal;\n}\n\n.hljs-regexp,\n.hljs-link {\n  color: #009926;\n}\n\n.hljs-symbol,\n.hljs-bullet {\n  color: #990073;\n}\n\n.hljs-built_in,\n.hljs-builtin-name {\n  color: #0086b3;\n}\n\n.hljs-meta {\n  color: #999;\n  font-weight: bold;\n}\n\n.hljs-deletion {\n  background: #fdd;\n}\n\n.hljs-addition {\n  background: #dfd;\n}\n\n.hljs-emphasis {\n  font-style: italic;\n}\n\n.hljs-strong {\n  font-weight: bold;\n}\n"
  },
  {
    "path": "public/default-files/themes/fly/assets/styles/main.less",
    "content": "@import \"_core/colors\";\n@import \"_core/base\";\n@import \"_core/github\";\n\n@import \"_blocks/fonts\";\n@import \"_blocks/footer\";\n@import \"_blocks/header\";\n@import \"_blocks/link\";\n@import \"_blocks/list\";\n@import \"_blocks/pagination\";\n@import \"_blocks/post\";\n@import \"_blocks/tag\";\n@import \"_blocks/archives\";\n@import \"_blocks/tags\";\n"
  },
  {
    "path": "public/default-files/themes/fly/config.json",
    "content": "{\n  \"name\": \"Fly\",\n  \"version\": \"1.0.0\",\n  \"author\": \"EryouHao\",\n  \"repository\": \"https://github.com/getgridea/gridea-theme-fly\",\n  \"customConfig\": [\n    {\n      \"name\": \"skin\",\n      \"label\": \"皮肤\",\n      \"group\": \"皮肤\",\n      \"value\": \"white\",\n      \"type\": \"select\",\n      \"options\": [\n        {\n          \"label\": \"简约白\",\n          \"value\": \"white\"\n        },\n        {\n          \"label\": \"低调黑\",\n          \"value\": \"black\"\n        }\n      ]\n    },\n    {\n      \"name\": \"contentMaxWidth\",\n      \"label\": \"内容区最大宽度\",\n      \"group\": \"布局\",\n      \"value\": \"800px\",\n      \"type\": \"input\",\n      \"note\": \"可填像素类型（如：320px）或百分比类型（如：38.2%）\"\n    },\n    {\n      \"name\": \"textSize\",\n      \"label\": \"正文内容文字大小\",\n      \"group\": \"布局\",\n      \"value\": \"16px\",\n      \"type\": \"input\",\n      \"note\": \"px 或 rem（如 16px 或 1rem）\"\n    },\n    {\n      \"name\": \"github\",\n      \"label\": \"Github\",\n      \"group\": \"社交\",\n      \"value\": \"\",\n      \"type\": \"input\",\n      \"note\": \"链接地址\"\n    },\n    {\n      \"name\": \"twitter\",\n      \"label\": \"Twitter\",\n      \"group\": \"社交\",\n      \"value\": \"\",\n      \"type\": \"input\",\n      \"note\": \"链接地址\"\n    },\n    {\n      \"name\": \"weibo\",\n      \"label\": \"微博\",\n      \"group\": \"社交\",\n      \"value\": \"\",\n      \"type\": \"input\",\n      \"note\": \"链接地址\"\n    },\n    {\n      \"name\": \"zhihu\",\n      \"label\": \"知乎\",\n      \"group\": \"社交\",\n      \"value\": \"\",\n      \"type\": \"input\",\n      \"note\": \"链接地址\"\n    },\n    {\n      \"name\": \"facebook\",\n      \"label\": \"Facebook\",\n      \"group\": \"社交\",\n      \"value\": \"\",\n      \"type\": \"input\",\n      \"note\": \"链接地址\"\n    },\n    {\n      \"name\": \"customCss\",\n      \"label\": \"自定义CSS\",\n      \"group\": \"自定义样式\",\n      \"value\": \"\",\n      \"type\": \"textarea\",\n      \"note\": \"\"\n    },\n    {\n      \"name\": \"ga\",\n      \"label\": \"跟踪 ID\",\n      \"group\": \"谷歌统计\",\n      \"value\": \"\",\n      \"type\": \"input\",\n      \"note\": \"UA-xxxxxxxxx-x\"\n    }\n  ]\n}\n"
  },
  {
    "path": "public/default-files/themes/fly/style-override.js",
    "content": "const generateOverride = (params = {}) => {\n  let result = ''\n\n  // 暗黑皮肤\n  if (params.skin && params.skin !== 'white') {\n    result += `\n      body {\n        color: #dee2e6;\n      }\n      body, .site-header-container, .menu-container {\n        background: #212529;\n      }\n      a, .link {\n        color: #e9ecef;\n      }\n      .site-header {\n        box-shadow: inset 0px -1px 0px #495057;\n      }\n      .post-item .content .post-title {\n        color: #e9ecef;\n      }\n      .post-item .content .post-abstract {\n        color: #868e96;\n      }\n      .post-info {\n        color: #495057;\n      }\n      .post-info a {\n        color: #5978f3;\n      }\n      a.purple-link, .post-content a {\n        background: linear-gradient(180deg, transparent 70%, #5978f3 0);\n      }\n      .post-detail .post-title {\n        color: #e9ecef;\n      }\n      .post-content p, .post-content table, .post-content ul, .post-content ol {\n        color: #dee2e6;\n      }\n      .post-content h1, .post-content h2, .post-content h3, .post-content h4, .post-content h5, .post-content h6 {\n        color: #e9ecef;\n      }\n      .post-detail .feature-container, .post-content img {\n        box-shadow: none;\n      }\n      .post-content blockquote {\n        background: #343a40;\n        border-left: 2px solid #5978f3;\n      }\n      .post-content code {\n        color: #e9ecef;\n      }\n      .post-content p code {\n        background: #495057;\n      }\n      .post-content pre code {\n        background: #000000;\n      }\n      .post-content pre code:after {\n        color: #495057;\n      }\n      .hljs-keyword, .hljs-selector-tag, .hljs-subst {\n        color: #d4bdbd;\n      }\n      .post-content tr {\n        border-top: 1px solid #495057;\n      }\n      .post-content td, .post-content th {\n        border: 1px solid #495057;\n      }\n    `\n  }\n\n  // 内容区最大宽度 - contentMaxWidth\n  if (params.contentMaxWidth && params.contentMaxWidth !== '800px') {\n    result += `\n      .main {\n        max-width: ${params.contentMaxWidth};\n      }\n    `\n  }\n\n  // 正文内容文字大小 - textSize\n  if (params.textSize && params.textSize !== '16px') {\n    result += `\n      body {\n        font-size: ${params.textSize};\n      }\n    `\n  }\n\n  if (params.customCss) {\n    result += `\n      ${params.customCss}\n    `\n  }\n\n  console.log('result', result)\n\n  return result\n}\n\nmodule.exports = generateOverride\n"
  },
  {
    "path": "public/default-files/themes/fly/templates/archives.ejs",
    "content": "<html>\n  <head>\n    <%- include('./includes/head', { siteTitle: themeConfig.siteName }) %>\n  </head>\n  <body>\n    <div id=\"app\" class=\"main\">\n      <%- include('./includes/header') %>\n\n        <div class=\"content-container\">\n          \n          <%- include('./includes/post-list-archives') %>\n\n        </div>\n\n        <%- include('./includes/footer') %>\n\n      </div>\n      <%- include('./includes/scripts') %>\n    </div>\n  </body>\n</html>\n"
  },
  {
    "path": "public/default-files/themes/fly/templates/includes/footer.ejs",
    "content": "<div class=\"site-footer\">\n  <div class=\"slogan\"><%- themeConfig.siteDescription %></div>\n  <div class=\"social-container\">\n    <% ['github', 'twitter', 'weibo', 'zhihu', 'facebook'].forEach((item) => { %>\n      <% if (site.customConfig[item]) { %>\n        <a href=\"<%= site.customConfig[item] %>\" target=\"_blank\">\n          <i class=\"fab fa-<%= item %>\"></i>\n        </a>\n      <% } %>\n    <% }) %>\n  </div>\n  <%- themeConfig.footerInfo %> | <a class=\"rss\" href=\"<%= themeConfig.domain %>/atom.xml\" target=\"_blank\">RSS</a>\n</div>\n"
  },
  {
    "path": "public/default-files/themes/fly/templates/includes/head.ejs",
    "content": "\r\n<meta charset=\"utf-8\" >\r\n\r\n<title><%= siteTitle %></title>\r\n<meta name=\"description\" content=\"<%= themeConfig.siteDescription %>\">\r\n\r\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no\">\r\n<link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.7.0/animate.min.css\">\r\n\r\n<link rel=\"stylesheet\" href=\"https://use.fontawesome.com/releases/v5.7.2/css/all.css\" integrity=\"sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr\" crossorigin=\"anonymous\">\r\n<link rel=\"shortcut icon\" href=\"<%= themeConfig.domain %>/favicon.ico?v=<%= site.utils.now %>\">\r\n<link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.10.0/katex.min.css\">\r\n<link rel=\"stylesheet\" href=\"<%= themeConfig.domain %>/styles/main.css\">\r\n\r\n<% if (typeof commentSetting !== 'undefined' && commentSetting.showComment) { %>\r\n  <% if (commentSetting.commentPlatform === 'gitalk') { %>\r\n    <link rel=\"stylesheet\" href=\"https://unpkg.com/gitalk/dist/gitalk.css\" />\r\n  <% } %>\r\n\r\n  <% if (commentSetting.commentPlatform === 'disqus') { %>\r\n    <link rel=\"stylesheet\" href=\"https://unpkg.com/disqusjs@1.1/dist/disqusjs.css\" />\r\n  <% } %>\r\n<% } %>\r\n\r\n<script src=\"https://cdn.jsdelivr.net/npm/vue/dist/vue.js\"></script>\r\n<script src=\"//cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.5.1/build/highlight.min.js\"></script>\r\n\r\n<% if (site.customConfig.ga) { %>\r\n<script async src=\"https://www.googletagmanager.com/gtag/js?id=<%= site.customConfig.ga %>\"></script>\r\n<script>\r\n  window.dataLayer = window.dataLayer || [];\r\n  function gtag(){dataLayer.push(arguments);}\r\n  gtag('js', new Date());\r\n\r\n  gtag('config', '<%= site.customConfig.ga %>');\r\n</script>\r\n<% } %>\r\n"
  },
  {
    "path": "public/default-files/themes/fly/templates/includes/header.ejs",
    "content": "<div class=\"site-header-container\">\n  <div class=\"site-header\">\n    <div class=\"left\">\n      <a href=\"<%= themeConfig.domain %>\">\n        <img class=\"avatar\" src=\"<%= themeConfig.domain %>/images/avatar.png?v=<%= site.utils.now %>\" alt=\"\" width=\"32px\" height=\"32px\">\n      </a>\n      <a href=\"<%= themeConfig.domain %>\">\n        <h1 class=\"site-title\"><%= themeConfig.siteName %></h1>\n      </a>\n    </div>\n    <div class=\"right\">\n      <transition name=\"fade\">\n        <i class=\"icon\" :class=\"{ 'icon-close-outline': menuVisible, 'icon-menu-outline': !menuVisible }\" @click=\"menuVisible = !menuVisible\"></i>\n      </transition>\n    </div>\n  </div>\n</div>\n\n<transition name=\"fade\">\n  <div class=\"menu-container\" style=\"display: none;\" v-show=\"menuVisible\">\n    <div class=\"menu-list\">\n      <% menus.forEach(function(menu) { %>\n        <% if (menu.openType === 'External') { %>\n          <a class=\"menu purple-link\" href=\"<%= menu.link %>\" class=\"menu\" target=\"_blank\">\n            <%= menu.name %>\n          </a>\n        <% } else { %>\n          <a href=\"<%= menu.link %>\" class=\"menu purple-link\">\n            <%= menu.name %>\n          </a>\n        <% } %>\n      <% }); %>\n    </div>\n  </div>\n</transition>\n"
  },
  {
    "path": "public/default-files/themes/fly/templates/includes/post-list-archives.ejs",
    "content": "<% let years = []; posts.forEach((item) => { const year = item.date.substring(0, 4); if (!years.includes(year)) { years.push(year); } }); %>\n\n<div class=\"archives-container\">\n  <% years.forEach(function(year) { %>\n    <h2 class=\"year\"><%- year %></h2>\n    <% posts.forEach(function(post) { %>\n      <%if (post.date.indexOf(year) !== -1) { %>\n        <article class=\"post\">\n          <a href=\"<%= post.link %>\">\n            <h2 class=\"post-title\">\n              <%= post.title %>\n              <small><%= post.dateFormat %></small>\n            </h2>\n          </a>\n        </article>\n      <% } %>\n    <% }); %>\n  <% }); %>\n</div>\n"
  },
  {
    "path": "public/default-files/themes/fly/templates/includes/post-list.ejs",
    "content": "<% posts.forEach(function(post) { %>\n<section class=\"post-item\">\n    <div class=\"content\">\n      <a href=\"<%= post.link %>\">\n        <h2 class=\"post-title\"><%= post.title %></h2>\n      </a>\n      <div class=\"post-abstract\">\n        <%- post.abstract %>\n      </div>\n      <div class=\"post-info\">\n        <span><i class=\"icon icon-calendar-outline\"></i> <%= post.dateFormat %></span>\n        <% if (post.tags.length > 0) { %>\n          <span>\n            <i class=\"icon icon-pricetags-outline\"></i>\n            <% post.tags.forEach(function(tag, index) { %>\n              <a href=\"<%= tag.link %>\">\n                <%= tag.name %>\n                <% if (index !== post.tags.length - 1) { %>\n                  ，\n                <% } %>\n              </a>\n            <% }); %>\n          </span>\n        <% } %>\n      </div>\n    </div>\n    <% if (themeConfig.showFeatureImage && post.feature) { %>\n      <a href=\"<%= post.link %>\">\n        <div class=\"feature-container\" style=\"background-image: url('<%= post.feature %>')\">\n        </div>\n      </a>\n    <% } %>\n  </section>\n<% }); %>\n\n<div class=\"pagination-container\">\n  <% if (pagination.prev) { %>\n    <a href=\"<%= pagination.prev %>\" class=\"prev\"><i class=\"icon-arrow-ios-back-outline\"></i> 上一页</a>\n  <% } %>\n  <% if (pagination.next) { %>\n    <a href=\"<%= pagination.next %>\" class=\"next\">下一页 <i class=\"icon-arrow-ios-forward-outline\"></i></a>\n  <% } %>\n</div>\n"
  },
  {
    "path": "public/default-files/themes/fly/templates/includes/scripts.ejs",
    "content": "<script type=\"application/javascript\">\n\nhljs.initHighlightingOnLoad()\n\nvar app = new Vue({\n  el: '#app',\n  data: {\n    menuVisible: false,\n  },\n})\n\n</script>\n\n<% if (typeof commentSetting !== 'undefined' && commentSetting.showComment) { %>\n\n  <% if (commentSetting.commentPlatform === 'gitalk') { %>\n    <script src=\"https://unpkg.com/gitalk/dist/gitalk.min.js\"></script>\n    <script>\n\n      var gitalk = new Gitalk({\n        clientID: '<%= commentSetting.gitalkSetting.clientId %>',\n        clientSecret: '<%= commentSetting.gitalkSetting.clientSecret %>',\n        repo: '<%= commentSetting.gitalkSetting.repository %>',\n        owner: '<%= commentSetting.gitalkSetting.owner %>',\n        admin: ['<%= commentSetting.gitalkSetting.owner %>'],\n        id: (location.pathname).substring(0, 49),      // Ensure uniqueness and length less than 50\n        distractionFreeMode: false  // Facebook-like distraction free mode\n      })\n\n      gitalk.render('gitalk-container')\n\n    </script>\n  <% } %>\n\n  <% if (commentSetting.commentPlatform === 'disqus') { %>\n    <script src=\"https://unpkg.com/disqusjs@1.1/dist/disqus.js\"></script>\n    <script>\n\n    var options = {\n      shortname: '<%= commentSetting.disqusSetting.shortname %>',\n      apikey: '<%= commentSetting.disqusSetting.apikey %>',\n    }\n    if ('<%= commentSetting.disqusSetting.api %>') {\n      options.api = '<%= commentSetting.disqusSetting.api %>'\n    }\n    var dsqjs = new DisqusJS(options)\n\n    </script>\n  <% } %>\n\n<% } %>\n\n"
  },
  {
    "path": "public/default-files/themes/fly/templates/index.ejs",
    "content": "\n<html>\n  <head>\n    <%- include('./includes/head', { siteTitle: themeConfig.siteName }) %>\n  </head>\n  <body>\n    <div id=\"app\" class=\"main\">\n      <%- include('./includes/header') %>\n\n      <div class=\"content-container\">\n        \n        <%- include('./includes/post-list') %>\n\n      </div>\n\n      <%- include('./includes/footer') %>\n\n    </div>\n    <%- include('./includes/scripts') %>\n  </body>\n</html>\n"
  },
  {
    "path": "public/default-files/themes/fly/templates/post.ejs",
    "content": "\n<!DOCTYPE html>\n<html>\n  <head>\n    <%- include('./includes/head', { siteTitle: `${post.title} | ${themeConfig.siteName}` }) %>\n  </head>\n  <body>\n    <div id=\"app\" class=\"main\">\n      <%- include('./includes/header') %>\n\n      <div class=\"content-container\">\n        <div class=\"post-detail\">\n          <% if (themeConfig.showFeatureImage && post.feature) { %>\n            <div class=\"feature-container\" style=\"background-image: url('<%= post.feature %>')\">\n            </div>\n          <% } %>\n          <h2 class=\"post-title\"><%= post.title %></h2>\n          <div class=\"post-info post-detail-info\">\n            <span><i class=\"icon-calendar-outline\"></i> <%= post.dateFormat %></span>\n            <% if (post.tags.length > 0) { %>\n              <span>\n                <i class=\"icon-pricetags-outline\"></i>\n                <% post.tags.forEach(function(tag, index) { %>\n                  <a href=\"<%= tag.link %>\">\n                    <%= tag.name %>\n                    <% if (index !== post.tags.length - 1) { %>\n                      ，\n                    <% } %>\n                  </a>\n                <% }); %>\n              </span>\n            <% } %>\n          </div>\n          <div class=\"post-content\" v-pre>\n            <%- post.content %>\n          </div>\n        </div>\n\n        <% if (post.nextPost && !post.hideInList) { %>\n          <div class=\"next-post\">\n            <a class=\"purple-link\" href=\"<%= post.nextPost.link %>\">\n              <h3 class=\"post-title\">\n                下一篇：<%= post.nextPost.title %>\n              </h3>\n            </a>\n          </div>\n          <% } %>\n      </div>\n\n      <% if (typeof commentSetting !== 'undefined' && commentSetting.showComment) { %>\n        <% if (commentSetting.commentPlatform === 'gitalk') { %>\n          <div id=\"gitalk-container\"></div>\n        <% } %>\n\n        <% if (commentSetting.commentPlatform === 'disqus') { %>\n          <div id=\"disqus_thread\"></div>\n        <% } %>\n      <% } %>\n\n      <%- include('./includes/footer') %>\n\n    </div>\n    <%- include('./includes/scripts') %>\n  </body>\n</html>\n"
  },
  {
    "path": "public/default-files/themes/fly/templates/tag.ejs",
    "content": "\n<!DOCTYPE html>\n<html>\n  <head>\n    <%- include('./includes/head', { siteTitle: `${tag.name} | ${themeConfig.siteName}` }) %>\n  </head>\n  <body>\n    <div id=\"app\" class=\"main\">\n      <%- include('./includes/header') %>\n\n      <div class=\"content-container\">\n\n        <div class=\"current-tag-container\">\n          <h2 class=\"title\">\n            <i class=\"icon icon-pricetags-outline\"></i> <%= tag.name %>\n          </h2> \n        </div>\n\n        <%- include('./includes/post-list') %>\n\n      </div>\n\n      <%- include('./includes/footer') %>\n\n    </div>\n    <%- include('./includes/scripts') %>\n  </body>\n</html>\n"
  },
  {
    "path": "public/default-files/themes/fly/templates/tags.ejs",
    "content": "<html>\n  <head>\n    <%- include('./includes/head', { siteTitle: themeConfig.siteName }) %>\n  </head>\n  <body>\n    <div id=\"app\" class=\"main\">\n      <%- include('./includes/header') %>\n\n        <div class=\"content-container\">\n          \n          <div class=\"tags-container\">\n            <% tags.forEach((tag) => { %>\n              <a class=\"tag\" href=\"<%= tag.link %>\"><%= tag.name %></a>\n            <% }); %>\n          </div>\n\n        </div>\n\n        <%- include('./includes/footer') %>\n\n      </div>\n      <%- include('./includes/scripts') %>\n    </div>\n  </body>\n</html>\n"
  },
  {
    "path": "public/default-files/themes/notes/assets/styles/abstracts/varibles.less",
    "content": "@darker-main-color: #004cb3;\n@dark-main-color: #0061e6;\n@main-color: #006CFF;\n@light-main-color: #006cff21;\n\n@black: #000000;\n@white: #FFFFFF;\n\n@dark-gray: #5E5E5E;\n@default-gray: #CCCCCC;\n@light-gray: #E6E6E6;\n@lighter-gray: #F3F3F3;\n\n\n@darker-red: #af0900;\n@dark-red: #e20c00;\n@default-red: #FB0D00;\n\n\n@default-green: #11B711;\n@default-yellow: #FAAD14;\n@default-green-blue: #788F9A;\n\n@dark-text-color: #2b2b2b;\n@default-text-color: #444444;\n@light-text-color: #6a6a6a;\n"
  },
  {
    "path": "public/default-files/themes/notes/assets/styles/components/about.less",
    "content": ".about-page {\n  padding: 24px 32px;\n}\n"
  },
  {
    "path": "public/default-files/themes/notes/assets/styles/components/archives.less",
    "content": ".archives-container {\n  padding: 32px;\n  flex: 1;\n  .year {\n    font-size: 1.375rem;\n    font-weight: bold;\n    margin: 24px 0 16px;\n    color: @oc-gray-6;\n    padding: 0 24px;\n  }\n  .post {\n    padding: 16px 24px;\n    display: block;\n    .post-title {\n      font-size: 16px;\n      font-weight: 900;\n      letter-spacing: .02em;\n    }\n    .time {\n      font-size: 0.75rem;\n      margin-top: 8px;\n      color: @oc-gray-4;\n    }\n  }\n}\n\n@media (max-width: 600px) {\n  .archives-container {\n    padding: 16px;\n  }\n}"
  },
  {
    "path": "public/default-files/themes/notes/assets/styles/components/footer.less",
    "content": ".site-footer {\n  font-size: 12px;\n  text-align: center;\n  padding: 40px 24px;\n  color: @oc-gray-6;\n  display: flex;\n  justify-content: center;\n  align-items: center;\n}\n\n.rss {\n  display: inline-flex;\n  align-items: center;\n  margin-left: 24px;\n}"
  },
  {
    "path": "public/default-files/themes/notes/assets/styles/components/header.less",
    "content": ".site-header {\n  padding: 48px 0;\n  text-align: center;\n\n  .site-title {\n    font-size: 32px;\n    font-weight: bold;\n  }\n\n  .site-description {\n    font-size: 16px;\n    padding: 24px;\n    color: @oc-gray-7;\n    font-weight: lighter;\n  }\n\n  .menu-container {\n    display: flex;\n    justify-content: center;\n    flex-wrap: wrap;\n    a.menu {\n      font-size: 16px;\n      padding: 8px 16px;\n      flex-shrink: 0;\n      font-weight: 600;\n    }\n  }\n\n  .avatar {\n    margin-bottom: 24px;\n    border-radius: 50%;\n    width: 120px;\n    height: 120px;\n  }\n\n  .social-container {\n    padding: 16px;\n    font-size: 18px;\n    a {\n      margin: 4px 8px;\n      color: #868e96;\n    }\n  }\n\n}\n\n@media (max-width: 600px) {\n  .site-header {\n    padding: 24px 0 0;\n\n    .avatar {\n      width: 80px;\n      height: 80px;\n    }\n  }\n}\n"
  },
  {
    "path": "public/default-files/themes/notes/assets/styles/components/home.less",
    "content": ".post-container {\n  flex: 1;\n  .post {\n    padding-bottom: 32px;\n\n    .post-title {\n      font-size: 28px;\n      text-align: center;\n      padding: 24px 0;\n      font-weight: 900;\n      letter-spacing: .02em;\n    }\n\n    .post-info {\n      text-align: center;\n      font-size: 12px;\n      padding-bottom: 24px;\n      > span {\n        color: @dark-gray;\n\n        &:not(:first-child) {\n          &:before {\n            content: \"/ \";\n            font-size: 10px;\n            color: rgba(0,0,0,.1);\n            margin: 0 4px;\n          }\n        }\n      }\n      .post-tag {\n        padding: 8px 8px;\n      }\n    }\n    .post-feature-image {\n      display: block;\n      width: 100%;\n      padding-top: 32.6%;\n      border-radius: 2px;\n      overflow: hidden;\n      background-size: cover;\n      background-position: center;\n      transition: all 0.3s;\n      img {\n        width: 100%;\n        // height: 100%\n      }\n      &:hover {\n        transform: scale(1.0082);\n      }\n    }\n    .post-abstract {\n      padding: 24px 0;\n      line-height: 1.5;\n      font-size: 16px;\n      strong {\n        font-weight: bolder;\n      }\n      a {\n        color: @main-color;\n        transition: all 0.3s;\n        &:hover {\n          color: @dark-main-color;\n          border-bottom: 1px dotted @dark-main-color;\n        }\n      }\n      code {\n        font-family: monospace;\n        font-size: inherit;\n        background-color: rgba(0,0,0,.06);\n        padding: 0 2px;\n        border: 1px solid rgba(0,0,0,.08);\n        border-radius: 2px 2px;\n        line-height: initial;\n        word-wrap: break-word;\n        text-indent: 0;\n      }\n    }\n  }\n}\n\n\n.pagination-container {\n  padding: 32px 16px;\n  overflow: hidden;\n  .prev-page {\n    float: left;\n  }\n  .next-page {\n    float: right;\n  }\n  .prev-page,\n  .next-page {\n    padding: 6px 12px;\n    font-weight: bold;\n    border-bottom: 2px solid transparent;\n    &:hover {\n      border-bottom: 2px solid;\n    }\n  }\n}\n\n@media (max-width: 600px) {\n  .post-container {\n    .post {\n      padding: 16px 16px;\n      .post-title {\n        padding: 16px 0;\n        font-size: 24px;\n      }\n      .post-abstract {\n        padding: 16px 0;\n      }\n      .post-feature-image {\n        padding-top: 56.25%;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "public/default-files/themes/notes/assets/styles/components/post.less",
    "content": ".post-detail {\n  flex: 1;\n  .post {\n    padding: 24px 32px;\n    .post-feature-image {\n      width: 100%;\n      height: auto;\n      margin-bottom: 24px;\n      border-radius: 2px;\n    }\n\n    .post-title {\n      font-size: 32px;\n      text-align: center;\n      padding: 24px 0;\n      font-weight: 900;\n      letter-spacing: 0.02em;\n    }\n\n    .post-info {\n      text-align: center;\n      font-size: 12px;\n      padding-bottom: 24px;\n\n      > span {\n        color: @dark-gray;\n\n        &:not(:first-child) {\n          &:before {\n            content: \"/ \";\n            font-size: 10px;\n            color: rgba(0,0,0,.1);\n            margin: 0 4px;\n          }\n        }\n      }\n\n      .post-tag {\n        padding: 8px 8px;\n      }\n    }\n    \n    .post-content-wrapper {\n      display: flex;\n    }\n\n    .post-content {\n      width: 100%;\n      flex-shrink: 0;\n      font-family: \"Noto Serif\",\"PingFang SC\",\"Hiragino Sans GB\",\"Droid Sans Fallback\",\"Microsoft YaHei\",sans-serif;\n\n      a {\n        color: rgba(0,0,0,.98);\n        word-wrap: break-word;\n        text-decoration: none;\n        border-bottom: 1px solid rgba(0,0,0,.26);\n        &:hover {\n          color: @dark-main-color;\n          border-bottom: 1px solid @dark-main-color;\n        }\n      }\n      img {\n        display: block;\n        box-shadow: 0 0 30px #eee;\n        max-width: 100%;\n        border-radius: 2px;\n        margin: 24px auto;\n      }\n\n      p {\n        line-height: 1.62;\n        margin-bottom: 1.12em;\n        font-size: 16px;\n        letter-spacing: .05em;\n        hyphens: auto;\n      }\n\n      p, li {\n        code {\n          font-family: 'Source Code Pro', Consolas, Menlo, Monaco, 'Courier New', monospace;\n          line-height: initial;\n          word-wrap: break-word;\n          border-radius: 0;\n          background-color: #fff5f5;\n          color: #c53030;\n          padding: .2em .33333333em;\n          margin-left: .125em;\n          margin-right: .125em;\n        }\n      }\n\n      pre {\n        margin-bottom: 1.5rem;\n        padding: 0;\n        position: relative;\n        code {\n          font-size: 0.96em;\n          font-family: 'Source Code Pro', Consolas, Menlo, Monaco, 'Courier New', monospace;\n          padding: 1em;\n          border-radius: 5px;\n          line-height: 1.5;\n        }\n      }\n\n      blockquote {\n        color: #9a9a9a;\n        position: relative;\n        padding: .4em 0 0 2.2em;\n        font-size: .96em;\n        &:before {\n          position: absolute;\n          top: -4px;\n          left: 0;\n          content: \"\\201c\";\n          font: 700 62px/1 serif;\n          color: rgba(0,0,0,.1);\n        }\n      }\n\n      table {\n        border-collapse: collapse;\n        margin: 1rem 0;\n        display: block;\n        overflow-x: auto;\n      }\n      tr {\n        border-top: 1px solid #dfe2e5;\n      }\n      td, th {\n        border: 1px solid #dfe2e5;\n        padding: .6em 1em;\n      }\n\n      ul, ol {\n        padding-left: 35px;\n        line-height: 1.725;\n        margin-bottom: 16px;\n      }\n\n      ul {\n        list-style-type: square;\n      }\n\n      h1, h2, h3, h4, h5, h6 {\n        margin: 16px 0;\n        font-weight: 700;\n        padding-top: 16px;\n      }\n      h1 {\n        font-size: 1.8em;\n      }\n      h2 {\n        font-size: 1.42em;\n      }\n      h3 {\n        font-size: 1.17em;\n      }\n      h4 {\n        font-size: 1em;\n      }\n      h5 {\n        font-size: 1em;\n      }\n      h6 {\n        font-size: 1em;\n        font-weight: 500;\n      }\n      hr {\n        display: block;\n        border: 0;\n        margin: 2.24em auto 2.86em;\n        &:before {\n          color: rgba(0,0,0,.2);\n          font-size: 1.1em;\n          display: block;\n          content: \"* * *\";\n          text-align: center;\n        }\n      }\n\n      mark {\n        background: #faf089;\n        color: #744210;\n        padding: .2em;\n      }\n\n      .footnotes {\n        margin-left: auto;\n        margin-right: auto;\n        max-width: 760px;\n        padding-left: 18px;\n        padding-right: 18px;\n        &:before {\n          content: \"\";\n          display: block;\n          border-top: 4px solid rgba(0,0,0,.1);\n          width: 50%;\n          max-width: 100px;\n          margin: 40px 0 20px;\n        }\n      }\n\n      .contains-task-list {\n        list-style-type: none;\n        padding-left: 30px;\n      }\n      .task-list-item {\n        position: relative;\n      }\n      .task-list-item-checkbox {\n        position: absolute;\n        cursor: pointer;\n        width: 16px;\n        height: 16px;\n        margin: 4px 0 0;\n        top: -1px;\n        left: -22px;\n        transform-origin: center;\n        transform: rotate(-90deg);\n        transition: all .2s ease;\n        &:checked {\n          transform: rotate(0);\n          &:before {\n            border: transparent;\n            background-color: @oc-green-5;\n          }\n          &:after {\n            transform: rotate(-45deg) scale(1);\n          }\n          + .task-list-item-label {\n            color: #a0a0a0;\n            text-decoration: line-through;\n          }\n        }\n        &:before {\n          content: \"\";\n          width: 16px;\n          height: 16px;\n          box-sizing: border-box;\n          display: inline-block;\n          border: 1px solid #9ae6b4;\n          border-radius: 2px;\n          background-color: #fff;\n          position: absolute;\n          top: 0;\n          left: 0;\n          transition: all .2s ease;\n        }\n        &:after {\n          content: \"\";\n          transform: rotate(-45deg) scale(0);\n          width: 9px;\n          height: 5px;\n          border: 1px solid #fff;\n          border-top: none;\n          border-right: none;\n          position: absolute;\n          display: inline-block;\n          top: 4px;\n          left: 4px;\n          transition: all .2s ease;\n        }\n      }\n    }\n  }\n}\n\n\n.next-post {\n  text-align: center;\n  padding: 24px 32px;\n  .next {\n    margin-bottom: 24px;\n    color: @oc-gray-8;\n    font-weight: lighter;\n  }\n  .post-title {\n    font-size: 20px;\n    font-weight: bold;\n    letter-spacing: .02em;\n  }\n}\n\n#gitalk-container, #disqus_thread {\n  padding: 24px 32px;\n}\n\n.toc-container {\n  .markdownIt-TOC {\n    position: sticky;\n    top: 32px;\n    width: 200px;\n    font-size: 12px;\n    list-style: none;\n    padding-left: 0;\n    padding: 16px 8px;\n    &:before {\n      content: \"\";\n      position: absolute;\n      top: 0;\n      left: 8px;\n      bottom: 0;\n      width: 1px;\n      background-color: #ebedef;\n      opacity: .5;\n    }\n  }\n  ul {\n    list-style: none;\n  }\n  li {\n    padding-left: 16px;\n    a {\n      color: @oc-gray-6;\n      padding: 4px;\n      display: block;\n      transition: all 0.3s;\n      &:hover {\n        background: #fafafa;\n      }\n      &.current {\n        color: @main-color;\n        background: #fafafa;\n      }\n    }\n  }\n}\n\n@media (max-width: 600px) {\n  .post-detail {\n    .post {\n      padding: 16px;\n      .post-title {\n        font-size: 24px;\n        padding: 16px 0;\n      }\n    }\n  }\n}\n\n@media (max-width: 1150px) {\n  .toc-container {\n    display: none;\n  }\n}\n"
  },
  {
    "path": "public/default-files/themes/notes/assets/styles/components/tag.less",
    "content": ".current-tag-container {\n  .title {\n    text-align: center;\n    font-size: 18px;\n    margin-bottom: 24px;\n  }\n}\n\n"
  },
  {
    "path": "public/default-files/themes/notes/assets/styles/components/tags.less",
    "content": ".tags-container {\n  padding: 32px 32px;\n  flex: 1;\n  text-align: center;\n  .tag {\n    display: inline-block;\n    padding: 8px 16px;\n    margin: 8px;\n    background: @oc-gray-0;\n    color: @oc-gray-7;\n    border-radius: 2px;\n    font-size: 14px;\n    &:hover {\n      background: @oc-gray-2;\n      color: @oc-gray-9;\n    }\n  }\n}\n"
  },
  {
    "path": "public/default-files/themes/notes/assets/styles/lib/colors.less",
    "content": "//\n//\n//  𝗖 𝗢 𝗟 𝗢 𝗥\n//  v 1.6.3\n//\n//  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n\n//  General\n//  ───────────────────────────────────\n\n@oc-white:         #ffffff;\n@oc-black:         #000000;\n\n\n//  Gray\n//  ───────────────────────────────────\n\n@oc-gray-list: #f8f9fa, #f1f3f5, #e9ecef, #dee2e6, #ced4da, #adb5bd, #868e96, #495057, #343a40, #212529;\n\n@oc-gray-0: extract(@oc-gray-list, 1);\n@oc-gray-1: extract(@oc-gray-list, 2);\n@oc-gray-2: extract(@oc-gray-list, 3);\n@oc-gray-3: extract(@oc-gray-list, 4);\n@oc-gray-4: extract(@oc-gray-list, 5);\n@oc-gray-5: extract(@oc-gray-list, 6);\n@oc-gray-6: extract(@oc-gray-list, 7);\n@oc-gray-7: extract(@oc-gray-list, 8);\n@oc-gray-8: extract(@oc-gray-list, 9);\n@oc-gray-9: extract(@oc-gray-list, 10);\n\n\n//  Red\n//  ───────────────────────────────────\n\n@oc-red-list: #fff5f5, #ffe3e3, #ffc9c9, #ffa8a8, #ff8787, #ff6b6b, #fa5252, #f03e3e, #e03131, #c92a2a;\n\n@oc-red-0: extract(@oc-red-list, 1);\n@oc-red-1: extract(@oc-red-list, 2);\n@oc-red-2: extract(@oc-red-list, 3);\n@oc-red-3: extract(@oc-red-list, 4);\n@oc-red-4: extract(@oc-red-list, 5);\n@oc-red-5: extract(@oc-red-list, 6);\n@oc-red-6: extract(@oc-red-list, 7);\n@oc-red-7: extract(@oc-red-list, 8);\n@oc-red-8: extract(@oc-red-list, 9);\n@oc-red-9: extract(@oc-red-list, 10);\n\n\n//  Pink\n//  ───────────────────────────────────\n\n@oc-pink-list: #fff0f6, #ffdeeb, #fcc2d7, #faa2c1, #f783ac, #f06595, #e64980, #d6336c, #c2255c, #a61e4d;\n\n@oc-pink-0: extract(@oc-pink-list, 1);\n@oc-pink-1: extract(@oc-pink-list, 2);\n@oc-pink-2: extract(@oc-pink-list, 3);\n@oc-pink-3: extract(@oc-pink-list, 4);\n@oc-pink-4: extract(@oc-pink-list, 5);\n@oc-pink-5: extract(@oc-pink-list, 6);\n@oc-pink-6: extract(@oc-pink-list, 7);\n@oc-pink-7: extract(@oc-pink-list, 8);\n@oc-pink-8: extract(@oc-pink-list, 9);\n@oc-pink-9: extract(@oc-pink-list, 10);\n\n\n//  Grape\n//  ───────────────────────────────────\n\n@oc-grape-list: #f8f0fc, #f3d9fa, #eebefa, #e599f7, #da77f2, #cc5de8, #be4bdb, #ae3ec9, #9c36b5, #862e9c;\n\n@oc-grape-0: extract(@oc-grape-list, 1);\n@oc-grape-1: extract(@oc-grape-list, 2);\n@oc-grape-2: extract(@oc-grape-list, 3);\n@oc-grape-3: extract(@oc-grape-list, 4);\n@oc-grape-4: extract(@oc-grape-list, 5);\n@oc-grape-5: extract(@oc-grape-list, 6);\n@oc-grape-6: extract(@oc-grape-list, 7);\n@oc-grape-7: extract(@oc-grape-list, 8);\n@oc-grape-8: extract(@oc-grape-list, 9);\n@oc-grape-9: extract(@oc-grape-list, 10);\n\n\n//  Violet\n//  ───────────────────────────────────\n\n@oc-violet-list: #f3f0ff, #e5dbff, #d0bfff, #b197fc, #9775fa, #845ef7, #7950f2, #7048e8, #6741d9, #5f3dc4;\n\n@oc-violet-0: extract(@oc-violet-list, 1);\n@oc-violet-1: extract(@oc-violet-list, 2);\n@oc-violet-2: extract(@oc-violet-list, 3);\n@oc-violet-3: extract(@oc-violet-list, 4);\n@oc-violet-4: extract(@oc-violet-list, 5);\n@oc-violet-5: extract(@oc-violet-list, 6);\n@oc-violet-6: extract(@oc-violet-list, 7);\n@oc-violet-7: extract(@oc-violet-list, 8);\n@oc-violet-8: extract(@oc-violet-list, 9);\n@oc-violet-9: extract(@oc-violet-list, 10);\n\n\n//  Indigo\n//  ───────────────────────────────────\n\n@oc-indigo-list: #edf2ff, #dbe4ff, #bac8ff, #91a7ff, #748ffc, #5c7cfa, #4c6ef5, #4263eb, #3b5bdb, #364fc7;\n\n@oc-indigo-0: extract(@oc-indigo-list, 1);\n@oc-indigo-1: extract(@oc-indigo-list, 2);\n@oc-indigo-2: extract(@oc-indigo-list, 3);\n@oc-indigo-3: extract(@oc-indigo-list, 4);\n@oc-indigo-4: extract(@oc-indigo-list, 5);\n@oc-indigo-5: extract(@oc-indigo-list, 6);\n@oc-indigo-6: extract(@oc-indigo-list, 7);\n@oc-indigo-7: extract(@oc-indigo-list, 8);\n@oc-indigo-8: extract(@oc-indigo-list, 9);\n@oc-indigo-9: extract(@oc-indigo-list, 10);\n\n\n//  Blue\n//  ───────────────────────────────────\n\n@oc-blue-list: #e7f5ff, #d0ebff, #a5d8ff, #74c0fc, #4dabf7, #339af0, #228be6, #1c7ed6, #1971c2, #1864ab;\n\n@oc-blue-0: extract(@oc-blue-list, 1);\n@oc-blue-1: extract(@oc-blue-list, 2);\n@oc-blue-2: extract(@oc-blue-list, 3);\n@oc-blue-3: extract(@oc-blue-list, 4);\n@oc-blue-4: extract(@oc-blue-list, 5);\n@oc-blue-5: extract(@oc-blue-list, 6);\n@oc-blue-6: extract(@oc-blue-list, 7);\n@oc-blue-7: extract(@oc-blue-list, 8);\n@oc-blue-8: extract(@oc-blue-list, 9);\n@oc-blue-9: extract(@oc-blue-list, 10);\n\n\n//  Cyan\n//  ───────────────────────────────────\n\n@oc-cyan-list: #e3fafc, #c5f6fa, #99e9f2, #66d9e8, #3bc9db, #22b8cf, #15aabf, #1098ad, #0c8599, #0b7285;\n\n@oc-cyan-0: extract(@oc-cyan-list, 1);\n@oc-cyan-1: extract(@oc-cyan-list, 2);\n@oc-cyan-2: extract(@oc-cyan-list, 3);\n@oc-cyan-3: extract(@oc-cyan-list, 4);\n@oc-cyan-4: extract(@oc-cyan-list, 5);\n@oc-cyan-5: extract(@oc-cyan-list, 6);\n@oc-cyan-6: extract(@oc-cyan-list, 7);\n@oc-cyan-7: extract(@oc-cyan-list, 8);\n@oc-cyan-8: extract(@oc-cyan-list, 9);\n@oc-cyan-9: extract(@oc-cyan-list, 10);\n\n\n//  Teal\n//  ───────────────────────────────────\n\n@oc-teal-list: #e6fcf5, #c3fae8, #96f2d7, #63e6be, #38d9a9, #20c997, #12b886, #0ca678, #099268, #087f5b;\n\n@oc-teal-0: extract(@oc-teal-list, 1);\n@oc-teal-1: extract(@oc-teal-list, 2);\n@oc-teal-2: extract(@oc-teal-list, 3);\n@oc-teal-3: extract(@oc-teal-list, 4);\n@oc-teal-4: extract(@oc-teal-list, 5);\n@oc-teal-5: extract(@oc-teal-list, 6);\n@oc-teal-6: extract(@oc-teal-list, 7);\n@oc-teal-7: extract(@oc-teal-list, 8);\n@oc-teal-8: extract(@oc-teal-list, 9);\n@oc-teal-9: extract(@oc-teal-list, 10);\n\n\n//  Green\n//  ───────────────────────────────────\n\n@oc-green-list: #ebfbee, #d3f9d8, #b2f2bb, #8ce99a, #69db7c, #51cf66, #40c057, #37b24d, #2f9e44, #2b8a3e;\n\n@oc-green-0: extract(@oc-green-list, 1);\n@oc-green-1: extract(@oc-green-list, 2);\n@oc-green-2: extract(@oc-green-list, 3);\n@oc-green-3: extract(@oc-green-list, 4);\n@oc-green-4: extract(@oc-green-list, 5);\n@oc-green-5: extract(@oc-green-list, 6);\n@oc-green-6: extract(@oc-green-list, 7);\n@oc-green-7: extract(@oc-green-list, 8);\n@oc-green-8: extract(@oc-green-list, 9);\n@oc-green-9: extract(@oc-green-list, 10);\n\n\n//  Lime\n//  ───────────────────────────────────\n\n@oc-lime-list: #f4fce3, #e9fac8, #d8f5a2, #c0eb75, #a9e34b, #94d82d, #82c91e, #74b816, #66a80f, #5c940d;\n\n@oc-lime-0: extract(@oc-lime-list, 1);\n@oc-lime-1: extract(@oc-lime-list, 2);\n@oc-lime-2: extract(@oc-lime-list, 3);\n@oc-lime-3: extract(@oc-lime-list, 4);\n@oc-lime-4: extract(@oc-lime-list, 5);\n@oc-lime-5: extract(@oc-lime-list, 6);\n@oc-lime-6: extract(@oc-lime-list, 7);\n@oc-lime-7: extract(@oc-lime-list, 8);\n@oc-lime-8: extract(@oc-lime-list, 9);\n@oc-lime-9: extract(@oc-lime-list, 10);\n\n\n//  Yellow\n//  ───────────────────────────────────\n\n@oc-yellow-list: #fff9db, #fff3bf, #ffec99, #ffe066, #ffd43b, #fcc419, #fab005, #f59f00, #f08c00, #e67700;\n\n@oc-yellow-0: extract(@oc-yellow-list, 1);\n@oc-yellow-1: extract(@oc-yellow-list, 2);\n@oc-yellow-2: extract(@oc-yellow-list, 3);\n@oc-yellow-3: extract(@oc-yellow-list, 4);\n@oc-yellow-4: extract(@oc-yellow-list, 5);\n@oc-yellow-5: extract(@oc-yellow-list, 6);\n@oc-yellow-6: extract(@oc-yellow-list, 7);\n@oc-yellow-7: extract(@oc-yellow-list, 8);\n@oc-yellow-8: extract(@oc-yellow-list, 9);\n@oc-yellow-9: extract(@oc-yellow-list, 10);\n\n\n//  Orange\n//  ───────────────────────────────────\n\n@oc-orange-list: #fff4e6, #ffe8cc, #ffd8a8, #ffc078, #ffa94d, #ff922b, #fd7e14, #f76707, #e8590c, #d9480f;\n\n@oc-orange-0: extract(@oc-orange-list, 1);\n@oc-orange-1: extract(@oc-orange-list, 2);\n@oc-orange-2: extract(@oc-orange-list, 3);\n@oc-orange-3: extract(@oc-orange-list, 4);\n@oc-orange-4: extract(@oc-orange-list, 5);\n@oc-orange-5: extract(@oc-orange-list, 6);\n@oc-orange-6: extract(@oc-orange-list, 7);\n@oc-orange-7: extract(@oc-orange-list, 8);\n@oc-orange-8: extract(@oc-orange-list, 9);\n@oc-orange-9: extract(@oc-orange-list, 10);\n"
  },
  {
    "path": "public/default-files/themes/notes/assets/styles/lib/github.less",
    "content": ".hljs {\n  display: block;\n  overflow-x: auto;\n  padding: 0.5em;\n  color: #333;\n  background: #f9f7f3;\n}\n\n.hljs-comment,\n.hljs-quote {\n  color: #998;\n  font-style: italic;\n}\n\n.hljs-keyword,\n.hljs-selector-tag,\n.hljs-subst {\n  color: #333;\n  font-weight: bold;\n}\n\n.hljs-number,\n.hljs-literal,\n.hljs-variable,\n.hljs-template-variable,\n.hljs-tag .hljs-attr {\n  color: #008080;\n}\n\n.hljs-string,\n.hljs-doctag {\n  color: #d14;\n}\n\n.hljs-title,\n.hljs-section,\n.hljs-selector-id {\n  color: #900;\n  font-weight: bold;\n}\n\n.hljs-subst {\n  font-weight: normal;\n}\n\n.hljs-type,\n.hljs-class .hljs-title {\n  color: #458;\n  font-weight: bold;\n}\n\n.hljs-tag,\n.hljs-name,\n.hljs-attribute {\n  color: #000080;\n  font-weight: normal;\n}\n\n.hljs-regexp,\n.hljs-link {\n  color: #009926;\n}\n\n.hljs-symbol,\n.hljs-bullet {\n  color: #990073;\n}\n\n.hljs-built_in,\n.hljs-builtin-name {\n  color: #0086b3;\n}\n\n.hljs-meta {\n  color: #999;\n  font-weight: bold;\n}\n\n.hljs-deletion {\n  background: #fdd;\n}\n\n.hljs-addition {\n  background: #dfd;\n}\n\n.hljs-emphasis {\n  font-style: italic;\n}\n\n.hljs-strong {\n  font-weight: bold;\n}\n"
  },
  {
    "path": "public/default-files/themes/notes/assets/styles/lib/modern-normalize.less",
    "content": "/*! modern-normalize | MIT License | https://github.com/sindresorhus/modern-normalize */\n\n/* Document\n   ========================================================================== */\n\n/**\n * Use a better box model (opinionated).\n */\n\n html {\n\tbox-sizing: border-box;\n}\n\n*,\n*::before,\n*::after {\n\tbox-sizing: inherit;\n}\n\n/**\n * Use a more readable tab size (opinionated).\n */\n\n:root {\n\t-moz-tab-size: 4;\n\ttab-size: 4;\n}\n\n/**\n * 1. Correct the line height in all browsers.\n * 2. Prevent adjustments of font size after orientation changes in iOS.\n */\n\nhtml {\n\tline-height: 1.15; /* 1 */\n\t-webkit-text-size-adjust: 100%; /* 2 */\n}\n\n/* Sections\n   ========================================================================== */\n\n/**\n * Remove the margin in all browsers.\n */\n\nbody {\n\tmargin: 0;\n}\n\n/**\n * Improve consistency of default fonts in all browsers. (https://github.com/sindresorhus/modern-normalize/issues/3)\n */\n\nbody {\n\tfont-family:\n\t\t-apple-system,\n\t\tBlinkMacSystemFont,\n\t\t'Segoe UI',\n\t\tRoboto,\n\t\tHelvetica,\n\t\tArial,\n\t\tsans-serif,\n\t\t'Apple Color Emoji',\n\t\t'Segoe UI Emoji',\n\t\t'Segoe UI Symbol';\n}\n\n/* Grouping content\n   ========================================================================== */\n\n\n/* Text-level semantics\n   ========================================================================== */\n\n/**\n * Add the correct text decoration in Chrome, Edge, and Safari.\n */\n\nabbr[title] {\n\ttext-decoration: underline dotted;\n}\n\n/**\n * Add the correct font weight in Chrome, Edge, and Safari.\n */\n\nb,\nstrong {\n\tfont-weight: bolder;\n}\n\n/**\n * 1. Improve consistency of default fonts in all browsers. (https://github.com/sindresorhus/modern-normalize/issues/3)\n * 2. Correct the odd `em` font sizing in all browsers.\n */\n\ncode,\nkbd,\nsamp,\npre {\n\tfont-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace; /* 1 */\n\tfont-size: 1em; /* 2 */\n}\n\n/**\n * Add the correct font size in all browsers.\n */\n\nsmall {\n\tfont-size: 80%;\n}\n\n/**\n * Prevent `sub` and `sup` elements from affecting the line height in all browsers.\n */\n\nsub,\nsup {\n\tfont-size: 75%;\n\tline-height: 0;\n\tposition: relative;\n\tvertical-align: baseline;\n}\n\nsub {\n\tbottom: -0.25em;\n}\n\nsup {\n\ttop: -0.5em;\n}\n\n/* Forms\n   ========================================================================== */\n\n/**\n * 1. Change the font styles in all browsers.\n * 2. Remove the margin in Firefox and Safari.\n */\n\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n\tfont-family: inherit; /* 1 */\n\tfont-size: 100%; /* 1 */\n\tline-height: 1.15; /* 1 */\n\tmargin: 0; /* 2 */\n}\n\n/**\n * Remove the inheritance of text transform in Edge and Firefox.\n * 1. Remove the inheritance of text transform in Firefox.\n */\n\nbutton,\nselect { /* 1 */\n\ttext-transform: none;\n}\n\n/**\n * Correct the inability to style clickable types in iOS and Safari.\n */\n\nbutton,\n[type='button'],\n[type='reset'],\n[type='submit'] {\n\t-webkit-appearance: button;\n}\n\n/**\n * Remove the inner border and padding in Firefox.\n */\n\nbutton::-moz-focus-inner,\n[type='button']::-moz-focus-inner,\n[type='reset']::-moz-focus-inner,\n[type='submit']::-moz-focus-inner {\n\tborder-style: none;\n\tpadding: 0;\n}\n\n/**\n * Restore the focus styles unset by the previous rule.\n */\n\nbutton:-moz-focusring,\n[type='button']:-moz-focusring,\n[type='reset']:-moz-focusring,\n[type='submit']:-moz-focusring {\n\toutline: 1px dotted ButtonText;\n}\n\n/**\n * Correct the padding in Firefox.\n */\n\nfieldset {\n\tpadding: 0.35em 0.75em 0.625em;\n}\n\n/**\n * Remove the padding so developers are not caught out when they zero out `fieldset` elements in all browsers.\n */\n\nlegend {\n\tpadding: 0;\n}\n\n/**\n * Add the correct vertical alignment in Chrome and Firefox.\n */\n\nprogress {\n\tvertical-align: baseline;\n}\n\n/**\n * Correct the cursor style of increment and decrement buttons in Safari.\n */\n\n[type='number']::-webkit-inner-spin-button,\n[type='number']::-webkit-outer-spin-button {\n\theight: auto;\n}\n\n/**\n * 1. Correct the odd appearance in Chrome and Safari.\n * 2. Correct the outline style in Safari.\n */\n\n[type='search'] {\n\t-webkit-appearance: textfield; /* 1 */\n\toutline-offset: -2px; /* 2 */\n}\n\n/**\n * Remove the inner padding in Chrome and Safari on macOS.\n */\n\n[type='search']::-webkit-search-decoration {\n\t-webkit-appearance: none;\n}\n\n/**\n * 1. Correct the inability to style clickable types in iOS and Safari.\n * 2. Change font properties to `inherit` in Safari.\n */\n\n::-webkit-file-upload-button {\n\t-webkit-appearance: button; /* 1 */\n\tfont: inherit; /* 2 */\n}\n\n/* Interactive\n   ========================================================================== */\n\n/*\n * Add the correct display in Chrome and Safari.\n */\n\nsummary {\n\tdisplay: list-item;\n}\n"
  },
  {
    "path": "public/default-files/themes/notes/assets/styles/main.less",
    "content": "@import './lib/modern-normalize';\n@import './lib/colors';\n@import './abstracts/varibles';\n\n*,\n*:before,\n*:after {\n  margin: 0;\n  padding: 0;\n}\nhtml, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video {\n  border: 0;\n  vertical-align: baseline;\n}\nhtml {\n  font-size: 58%;\n}\nbody {\n  color: rgba(0,0,0,.86);\n  font: 400 16px/1.42 -apple-system,BlinkMacSystemFont,\"Helvetica Neue\",\"PingFang SC\",\"Hiragino Sans GB\",\"Droid Sans Fallback\",\"Microsoft YaHei\",sans-serif;\n  letter-spacing: .05em;\n}\na {\n  color: rgba(0,0,0,.98);\n  text-decoration: none;\n  transition: all 0.3s;\n  &:hover {\n    color: @main-color;\n  }\n}\nbody, div, a, p, ul, li, ol, h1, h2, h3, h4, h5, h6, table, tr, td {\n  box-sizing: border-box;\n  margin: 0;\n  padding: 0;\n}\n\n.main {\n  max-width: 800px;\n  min-height: 100vh;\n  margin: 0 auto;\n  background: #fff;\n  .main-content {\n    flex: 1;\n    display: flex;\n    min-height: 100vh;\n    flex-direction: column;\n    padding: 0 24px;\n  }\n}\n\n\n\n@import \"./components/header.less\";\n@import \"./components/home.less\";\n@import \"./components/post.less\";\n@import \"./components/archives.less\";\n@import \"./components/tags.less\";\n@import \"./components/tag.less\";\n@import \"./components/about.less\";\n@import \"./components/footer.less\";\n@import \"./lib/github.less\";\n"
  },
  {
    "path": "public/default-files/themes/notes/config.json",
    "content": "{\n  \"name\": \"Notes\",\n  \"version\": \"1.2.0\",\n  \"author\": \"EryouHao\",\n  \"repository\": \"https://github.com/getgridea/gridea-theme-notes\",\n  \"customConfig\": [\n    {\n      \"name\": \"contentMaxWidth\",\n      \"label\": \"内容区最大宽度\",\n      \"group\": \"布局\",\n      \"value\": \"800px\",\n      \"type\": \"input\",\n      \"note\": \"可填像素类型（如：320px）或百分比类型（如：38.2%）\"\n    },\n    {\n      \"name\": \"textSize\",\n      \"label\": \"正文内容文字大小\",\n      \"group\": \"布局\",\n      \"value\": \"16px\",\n      \"type\": \"input\",\n      \"note\": \"px 或 rem（如 16px 或 1rem）\"\n    },\n    {\n      \"name\": \"titleAlign\",\n      \"label\": \"标题对齐\",\n      \"group\": \"布局\",\n      \"value\": \"center\",\n      \"type\": \"radio\",\n      \"options\": [\n        {\n          \"label\": \"居中对齐\",\n          \"value\": \"center\"\n        },\n        {\n          \"label\": \"左对齐\",\n          \"value\": \"left\"\n        },\n        {\n          \"label\": \"右对齐\",\n          \"value\": \"right\"\n        }\n      ],\n      \"note\": \"包含标题及日期、标签等信息部分\"\n    },\n    {\n      \"name\": \"siteFont\",\n      \"label\": \"网站字体\",\n      \"group\": \"布局\",\n      \"value\": \"-apple-system,BlinkMacSystemFont,'Helvetica Neue','PingFang SC','Hiragino Sans GB','Droid Sans Fallback','Microsoft YaHei',sans-serif\",\n      \"type\": \"select\",\n      \"options\": [\n        {\n          \"label\": \"-apple-system,BlinkMacSystemFont,'Helvetica Neue','PingFang SC','Hiragino Sans GB','Droid Sans Fallback','Microsoft YaHei',sans-serif\",\n          \"value\": \"-apple-system,BlinkMacSystemFont,'Helvetica Neue','PingFang SC','Hiragino Sans GB','Droid Sans Fallback','Microsoft YaHei',sans-serif\"\n        },\n        {\n          \"label\": \"Georgia, serif\",\n          \"value\": \"Georgia, serif\"\n        }\n      ],\n      \"note\": \"\"\n    },\n    {\n      \"name\": \"openPostToc\",\n      \"label\": \"是否显示文章目录\",\n      \"group\": \"布局\",\n      \"value\": true,\n      \"type\": \"switch\",\n      \"note\": \"仅在宽屏时生效\"\n    },\n    {\n      \"name\": \"contentBgColor\",\n      \"label\": \"内容区背景色\",\n      \"group\": \"颜色\",\n      \"value\": \"#ffffff\",\n      \"type\": \"input\",\n      \"card\": \"color\",\n      \"note\": \"颜色字符串:（如：#EEEEEE、rgba(255, 255, 255, 0.9)）\"\n    },\n    {\n      \"name\": \"pageBgColor\",\n      \"label\": \"网页背景色\",\n      \"group\": \"颜色\",\n      \"value\": \"#ffffff\",\n      \"type\": \"input\",\n      \"card\": \"color\",\n      \"note\": \"颜色字符串:（如：#EEEEEE、rgba(255, 255, 255, 0.9)）\"\n    },\n    {\n      \"name\": \"textColor\",\n      \"label\": \"文字颜色\",\n      \"group\": \"颜色\",\n      \"value\": \"rgba(0, 0, 0, 0.86)\",\n      \"type\": \"input\",\n      \"card\": \"color\",\n      \"note\": \"颜色字符串:（如：#EEEEEE、rgba(255, 255, 255, 0.9)）\"\n    },\n    {\n      \"name\": \"linkColor\",\n      \"label\": \"链接颜色\",\n      \"group\": \"颜色\",\n      \"value\": \"rgba(0,0,0,.98)\",\n      \"type\": \"input\",\n      \"card\": \"color\",\n      \"note\": \"颜色字符串:（如：#EEEEEE、rgba(255, 255, 255, 0.9)）\"\n    },\n    {\n      \"name\": \"linkHoverColor\",\n      \"label\": \"链接 Hover 颜色\",\n      \"group\": \"颜色\",\n      \"value\": \"#006CFF\",\n      \"type\": \"input\",\n      \"card\": \"color\",\n      \"note\": \"颜色字符串:（如：#EEEEEE、rgba(255, 255, 255, 0.9)）\"\n    },\n    {\n      \"name\": \"github\",\n      \"label\": \"Github\",\n      \"group\": \"社交\",\n      \"value\": \"\",\n      \"type\": \"input\",\n      \"note\": \"链接地址\"\n    },\n    {\n      \"name\": \"twitter\",\n      \"label\": \"Twitter\",\n      \"group\": \"社交\",\n      \"value\": \"\",\n      \"type\": \"input\",\n      \"note\": \"链接地址\"\n    },\n    {\n      \"name\": \"weibo\",\n      \"label\": \"微博\",\n      \"group\": \"社交\",\n      \"value\": \"\",\n      \"type\": \"input\",\n      \"note\": \"链接地址\"\n    },\n    {\n      \"name\": \"zhihu\",\n      \"label\": \"知乎\",\n      \"group\": \"社交\",\n      \"value\": \"\",\n      \"type\": \"input\",\n      \"note\": \"链接地址\"\n    },\n    {\n      \"name\": \"facebook\",\n      \"label\": \"Facebook\",\n      \"group\": \"社交\",\n      \"value\": \"\",\n      \"type\": \"input\",\n      \"note\": \"链接地址\"\n    },\n    {\n      \"name\": \"customCss\",\n      \"label\": \"自定义CSS\",\n      \"group\": \"自定义样式\",\n      \"value\": \"\",\n      \"type\": \"textarea\",\n      \"note\": \"\"\n    },\n    {\n      \"name\": \"ga\",\n      \"label\": \"跟踪 ID\",\n      \"group\": \"谷歌统计\",\n      \"value\": \"\",\n      \"type\": \"input\",\n      \"note\": \"UA-xxxxxxxxx-x\"\n    },\n    {\n      \"name\": \"metaDescription\",\n      \"label\": \"Meta Description\",\n      \"group\": \"SEO\",\n      \"value\": \"\",\n      \"type\": \"input\"\n    }\n  ]\n}\n"
  },
  {
    "path": "public/default-files/themes/notes/style-override.js",
    "content": "const generateOverride = (params = {}) => {\n  let result = ''\n\n  // 内容区最大宽度 - contentMaxWidth\n  if (params.contentMaxWidth && params.contentMaxWidth !== '800px') {\n    result += `\n      .main {\n        max-width: ${params.contentMaxWidth};\n      }\n    `\n  }\n\n  // 正文内容文字大小 - textSize\n  if (params.textSize && params.textSize !== '16px') {\n    result += `\n      .post-detail .post .post-content p {\n        font-size: ${params.textSize};\n      }\n    `\n  }\n\n  // 标题对齐 - titleAlign: center(默认)、left、right\n  if (params.titleAlign) {\n    result += `\n      .post-container .post .post-title {\n        text-align: ${params.titleAlign};\n      }\n      .post-container .post .post-info {\n        text-align: ${params.titleAlign};\n      }\n      .post-detail .post .post-title {\n        text-align: ${params.titleAlign};\n      }\n      .post-detail .post .post-info {\n        text-align: ${params.titleAlign};\n      }\n    `\n  }\n\n  // 网站字体\n  if (params.siteFont) {\n    result += `\n      body {\n        font-family: ${params.siteFont};\n      }\n    `\n  }\n\n  // 是否显示文章目录\n  if (typeof params.openPostToc !== 'undefined' && !params.openPostToc) {\n    result += `\n      .toc-container {\n        display: none;\n      }\n    `\n  }\n\n  // 内容区背景色 - contentBgColor\n  if (params.contentBgColor && params.contentBgColor !== '#ffffff') {\n    result += `\n      .main {\n        background: ${params.contentBgColor};\n      }\n    `\n  }\n\n  // 网页背景色 - pageBgColor\n  if (params.pageBgColor && params.pageBgColor !== '#ffffff') {\n    result += `\n      body {\n        background: ${params.pageBgColor};\n      }\n    `\n  }\n\n  // 文字颜色 - textColor\n  if (params.textColor && params.textColor !== 'rgba(0, 0, 0, 0.86)') {\n    result += `\n      body {\n        color: ${params.textColor};\n      }\n    `\n  }\n  \n  // 链接颜色 - linkColor\n  if (params.linkColor && params.linkColor !== 'rgba(0,0,0,.98)') {\n    result += `\n      a {\n        color: ${params.linkColor};\n      }\n    `\n  }\n  \n  // 链接 Hover 颜色 - linkHoverColor\n  if (params.linkHoverColor && params.linkHoverColor !== '#006CFF') {\n    result += `\n      a:hover {\n        color: ${params.linkHoverColor};\n      }\n    `\n  }\n\n  if (params.customCss) {\n    result += `\n      ${params.customCss}\n    `\n  }\n\n\n  console.log('result', result)\n\n  return result\n}\n\nmodule.exports = generateOverride\n"
  },
  {
    "path": "public/default-files/themes/notes/templates/archives.ejs",
    "content": "<html>\n  <head>\n    <%- include('./includes/head', { siteTitle: themeConfig.siteName }) %>\n    <meta name=\"description\" content=\"<%- site.customConfig.metaDescription || themeConfig.siteDescription %>\" />\n  </head>\n  <body>\n    <div class=\"main\">\n      <div class=\"main-content\">\n        <%- include('./includes/header') %>\n    \n        <%- include('./includes/post-list-archives') %>\n    \n        <%- include('./includes/pagination') %>\n    \n        <%- include('./includes/footer') %>\n      </div>\n    </div>\n  </body>\n</html>\n"
  },
  {
    "path": "public/default-files/themes/notes/templates/includes/disqus.ejs",
    "content": "<link rel=\"stylesheet\" href=\"https://unpkg.com/disqusjs@1.1/dist/disqusjs.css\">\n<script src=\"https://unpkg.com/disqusjs@1.1/dist/disqus.js\"></script>\n\n<div id=\"disqus_thread\"></div>\n\n<script>\n\nvar options = {\n  shortname: '<%= commentSetting.disqusSetting.shortname %>',\n  apikey: '<%= commentSetting.disqusSetting.apikey %>',\n}\nif ('<%= commentSetting.disqusSetting.api %>') {\n  options.api = '<%= commentSetting.disqusSetting.api %>'\n}\nvar dsqjs = new DisqusJS(options)\n\n</script>\n"
  },
  {
    "path": "public/default-files/themes/notes/templates/includes/footer.ejs",
    "content": "<div class=\"site-footer\">\n  <%- themeConfig.footerInfo %>\n  <a class=\"rss\" href=\"<%= themeConfig.domain %>/atom.xml\" target=\"_blank\">\n    <i class=\"ri-rss-line\"></i> RSS\n  </a>\n</div>\n"
  },
  {
    "path": "public/default-files/themes/notes/templates/includes/gitalk.ejs",
    "content": "<link rel=\"stylesheet\" href=\"https://unpkg.com/gitalk/dist/gitalk.css\">\n<script src=\"https://unpkg.com/gitalk/dist/gitalk.min.js\"></script>\n\n<div id=\"gitalk-container\"></div>\n\n<script>\n\n  var gitalk = new Gitalk({\n    clientID: '<%= commentSetting.gitalkSetting.clientId %>',\n    clientSecret: '<%= commentSetting.gitalkSetting.clientSecret %>',\n    repo: '<%= commentSetting.gitalkSetting.repository %>',\n    owner: '<%= commentSetting.gitalkSetting.owner %>',\n    admin: ['<%= commentSetting.gitalkSetting.owner %>'],\n    id: (location.pathname).substring(0, 49),      // Ensure uniqueness and length less than 50\n    distractionFreeMode: false  // Facebook-like distraction free mode\n  })\n\n  gitalk.render('gitalk-container')\n\n</script>\n"
  },
  {
    "path": "public/default-files/themes/notes/templates/includes/head.ejs",
    "content": "<meta charset=\"utf-8\" />\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n<title><%= siteTitle %></title>\n<link rel=\"shortcut icon\" href=\"<%= themeConfig.domain %>/favicon.ico?v=<%= site.utils.now %>\">\n<link href=\"https://cdn.jsdelivr.net/npm/remixicon@2.3.0/fonts/remixicon.css\" rel=\"stylesheet\">\n<link rel=\"stylesheet\" href=\"<%= themeConfig.domain %>/styles/main.css\">\n<link rel=\"alternate\" type=\"application/atom+xml\" title=\"<%= siteTitle %> - Atom Feed\" href=\"<%= themeConfig.domain %>/atom.xml\">\n<link rel=\"stylesheet\" href=\"https://fonts.googleapis.com/css?family=Droid+Serif:400,700\">\n\n<% if (site.customConfig.ga) { %>\n<script async src=\"https://www.googletagmanager.com/gtag/js?id=<%= site.customConfig.ga %>\"></script>\n<script>\n  window.dataLayer = window.dataLayer || [];\n  function gtag(){dataLayer.push(arguments);}\n  gtag('js', new Date());\n\n  gtag('config', '<%= site.customConfig.ga %>');\n</script>\n<% } %>\n"
  },
  {
    "path": "public/default-files/themes/notes/templates/includes/header.ejs",
    "content": "<div class=\"site-header\">\n  <a href=\"<%= themeConfig.domain %>\">\n  <img class=\"avatar\" src=\"<%= themeConfig.domain %>/images/avatar.png?v=<%= site.utils.now %>\" alt=\"\">\n  </a>\n  <h1 class=\"site-title\">\n    <%= themeConfig.siteName %>\n  </h1>\n  <p class=\"site-description\">\n    <%- themeConfig.siteDescription %>\n  </p>\n  <div class=\"menu-container\">\n    <% menus.forEach(function(menu) { %>\n      <% if (menu.openType === 'External') { %>\n        <a href=\"<%= menu.link %>\" class=\"menu\" target=\"_blank\">\n          <%= menu.name %>\n        </a>\n      <% } else { %>\n        <a href=\"<%= menu.link %>\" class=\"menu\">\n          <%= menu.name %>\n        </a>\n      <% } %>\n    <% }); %>\n  </div>\n  <div class=\"social-container\">\n    <% ['github', 'twitter', 'weibo', 'zhihu', 'facebook'].forEach((item) => { %>\n      <% if (site.customConfig[item]) { %>\n        <a href=\"<%= site.customConfig[item] %>\" target=\"_blank\">\n          <i class=\"ri-<%= item %>-line\"></i>\n        </a>\n      <% } %>\n    <% }) %>\n  </div>\n</div>\n"
  },
  {
    "path": "public/default-files/themes/notes/templates/includes/pagination.ejs",
    "content": "<div class=\"pagination-container\">\n  <% if (pagination.prev) { %>\n    <a href=\"<%= pagination.prev %>\" class=\"prev-page\">上一页</a>\n  <% } %>\n  <% if (pagination.next) { %>\n    <a href=\"<%= pagination.next %>\" class=\"next-page\">下一页</a>\n  <% } %>\n</div>\n"
  },
  {
    "path": "public/default-files/themes/notes/templates/includes/post-list-archives.ejs",
    "content": "<% let years = []; posts.forEach((item) => { const year = item.date.substring(0, 4); if (!years.includes(year)) { years.push(year); } }); %>\n\n<div class=\"archives-container\">\n  <% years.forEach(function(year) { %>\n    <h2 class=\"year\"><%- year %></h2>\n    <% posts.forEach(function(post) { %>\n      <%if (post.date.indexOf(year) !== -1) { %>\n        <a href=\"<%= post.link %>\" class=\"post\">\n          <h2 class=\"post-title\">\n            <%= post.title %>\n          </h2>\n          <div class=\"time\"><%= post.dateFormat %></div>\n        </a>\n      <% } %>\n    <% }); %>\n  <% }); %>\n</div>\n"
  },
  {
    "path": "public/default-files/themes/notes/templates/includes/post-list.ejs",
    "content": "<div class=\"post-container\">\n  <% posts.forEach(function(post) { %>\n    <article class=\"post\">\n      <a href=\"<%= post.link %>\">\n        <h2 class=\"post-title\"><%= post.title %></h2>\n      </a>\n      <div class=\"post-info\">\n        <span>\n          <%= post.dateFormat %>\n        </span>\n        <span>\n          <%= post.stats.text %>\n        </span>\n        <% post.tags.forEach(function(tag) { %>\n          <a href=\"<%= tag.link %>\" class=\"post-tag\">\n            # <%= tag.name %>\n          </a>\n        <% }); %>\n      </div>\n      <% if (themeConfig.showFeatureImage && post.feature) { %>\n        <a href=\"<%= post.link %>\" class=\"post-feature-image\" style=\"background-image: url('<%= post.feature %>')\">\n        </a>\n      <% } %>\n      <div class=\"post-abstract\">\n        <%- post.abstract %>\n      </div>\n    </article>\n  <% }); %>\n</div>\n"
  },
  {
    "path": "public/default-files/themes/notes/templates/index.ejs",
    "content": "<html>\n  <head>\n    <%- include('./includes/head', { siteTitle: themeConfig.siteName }) %>\n    <meta name=\"description\" content=\"<%- site.customConfig.metaDescription || themeConfig.siteDescription %>\" />\n  </head>\n  <body>\n    <div class=\"main\">\n      <div class=\"main-content\">\n        <%- include('./includes/header') %>\n    \n        <%- include('./includes/post-list') %>\n    \n        <%- include('./includes/pagination') %>\n    \n        <%- include('./includes/footer') %>\n      </div>\n    </div>\n  </body>\n</html>\n"
  },
  {
    "path": "public/default-files/themes/notes/templates/post.ejs",
    "content": "<html>\n  <head>\n    <%- include('./includes/head', { siteTitle: `${post.title} | ${themeConfig.siteName}` }) %>\n    <meta name=\"description\" content=\"<%- post.description %>\" />\n    <meta name=\"keywords\" content=\"<%- post.tags.map(tag => tag.name).join(',') %>\" />\n    <link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.10.0/katex.min.css\">\n    <script src=\"//cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.5.1/build/highlight.min.js\"></script>\n  </head>\n  <body>\n    <div class=\"main\">\n      <div class=\"main-content\">\n        <%- include('./includes/header') %>\n        <div class=\"post-detail\">\n          <article class=\"post\">\n            <h2 class=\"post-title\">\n              <%= post.title %>\n            </h2>\n            <div class=\"post-info\">\n              <span>\n                <%= post.dateFormat %>\n              </span>\n              <span>\n                <%= post.stats.text %>\n              </span>\n              <% post.tags.forEach(function(tag) { %>\n                <a href=\"<%= tag.link %>\" class=\"post-tag\">\n                  # <%= tag.name %>\n                </a>\n              <% }); %>\n            </div>\n            <% if (themeConfig.showFeatureImage && post.feature) { %>\n              <img class=\"post-feature-image\" src=\"<%= post.feature %>\" alt=\"\">\n            <% } %>\n            <div class=\"post-content-wrapper\">\n              <div class=\"post-content\" v-pre>\n                <%- post.content %>\n              </div>\n              <div class=\"toc-container\">\n                <%- post.toc %>\n              </div>\n            </div>\n          </article>\n        </div>\n\n        <% if (post.nextPost && !post.hideInList) { %>\n          <div class=\"next-post\">\n            <div class=\"next\">下一篇</div>\n            <a href=\"<%= post.nextPost.link %>\">\n              <h3 class=\"post-title\">\n                <%= post.nextPost.title %>\n              </h3>\n            </a>\n          </div>\n        <% } %>\n\n        <% if (typeof commentSetting !== 'undefined' && commentSetting.showComment) { %>\n          <% if (commentSetting.commentPlatform === 'gitalk') { %>\n            <%- include('./includes/gitalk') %>\n          <% } %>\n\n          <% if (commentSetting.commentPlatform === 'disqus') { %>\n            <%- include('./includes/disqus') %>\n          <% } %>\n        <% } %>\n\n        <%- include('./includes/footer') %>\n      </div>\n    </div>\n\n    <script>\n      hljs.initHighlightingOnLoad()\n\n      let mainNavLinks = document.querySelectorAll(\".markdownIt-TOC a\");\n\n      // This should probably be throttled.\n      // Especially because it triggers during smooth scrolling.\n      // https://lodash.com/docs/4.17.10#throttle\n      // You could do like...\n      // window.addEventListener(\"scroll\", () => {\n      //    _.throttle(doThatStuff, 100);\n      // });\n      // Only not doing it here to keep this Pen dependency-free.\n\n      window.addEventListener(\"scroll\", event => {\n        let fromTop = window.scrollY;\n\n        mainNavLinks.forEach((link, index) => {\n          let section = document.getElementById(decodeURI(link.hash).substring(1));\n          let nextSection = null\n          if (mainNavLinks[index + 1]) {\n            nextSection = document.getElementById(decodeURI(mainNavLinks[index + 1].hash).substring(1));\n          }\n          if (section.offsetTop <= fromTop) {\n            if (nextSection) {\n              if (nextSection.offsetTop > fromTop) {\n                link.classList.add(\"current\");\n              } else {\n                link.classList.remove(\"current\");    \n              }\n            } else {\n              link.classList.add(\"current\");\n            }\n          } else {\n            link.classList.remove(\"current\");\n          }\n        });\n      });\n\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "public/default-files/themes/notes/templates/tag.ejs",
    "content": "<html>\n  <head>\n  <%- include('./includes/head', { siteTitle: `${tag.name} | ${themeConfig.siteName}` }) %>\n  <meta name=\"description\" content=\"<%- site.customConfig.metaDescription || themeConfig.siteDescription %>\" />\n  </head>\n  <body>\n    <div class=\"main\">\n      <div class=\"main-content\">\n        <%- include('./includes/header') %>\n        <div class=\"current-tag-container\">\n            <h2 class=\"title\">\n                标签：# <%= tag.name %>\n            </h2> \n        </div>\n        <%- include('./includes/post-list') %>\n        <%- include('./includes/pagination') %>\n        <%- include('./includes/footer') %>\n      </div>\n    </div>\n  </body>\n</html>\n"
  },
  {
    "path": "public/default-files/themes/notes/templates/tags.ejs",
    "content": "<html>\n  <head>\n  <%- include('./includes/head', { siteTitle: themeConfig.siteName }) %>\n  <meta name=\"description\" content=\"<%- site.customConfig.metaDescription || themeConfig.siteDescription %>\" />\n  </head>\n  <body>\n    <div class=\"main\">\n      <div class=\"main-content\">\n        <%- include('./includes/header') %>\n\n        <div class=\"tags-container\">\n          <% tags.forEach((tag) => { %>\n            <a class=\"tag\" href=\"<%= tag.link %>\"><%= tag.name %></a>\n          <% }); %>\n        </div>\n\n        <%- include('./includes/footer') %>\n      </div>\n    </div>\n  </body>\n</html>\n"
  },
  {
    "path": "public/default-files/themes/paper/assets/styles/_core/base.less",
    "content": "/*! modern-normalize | MIT License | https://github.com/sindresorhus/modern-normalize */\n\n/* Document\n   ========================================================================== */\n\n/**\n * Use a better box model (opinionated).\n */\n\n html {\n\tbox-sizing: border-box;\n}\n\n*,\n*::before,\n*::after {\n  box-sizing: inherit;\n  margin: 0;\n  padding: 0;\n}\n\n/**\n * Use a more readable tab size (opinionated).\n */\n\n:root {\n\t-moz-tab-size: 4;\n\ttab-size: 4;\n}\n\n/**\n * 1. Correct the line height in all browsers.\n * 2. Prevent adjustments of font size after orientation changes in iOS.\n */\n\nhtml {\n\tline-height: 1.15; /* 1 */\n\t-webkit-text-size-adjust: 100%; /* 2 */\n}\n\n/* Sections\n   ========================================================================== */\n\n/**\n * Remove the margin in all browsers.\n */\n\nbody {\n  margin: 0;\n  font-size: 14px;\n}\n\n/**\n * Improve consistency of default fonts in all browsers. (https://github.com/sindresorhus/modern-normalize/issues/3)\n */\n\nbody {\n\tfont-family:\n\t\t-apple-system,\n\t\tBlinkMacSystemFont,\n\t\t'Segoe UI',\n\t\tRoboto,\n\t\t\"PingFang SC\",\n\t\t\"Hiragino Sans GB\",\n\t\t\"Microsoft YaHei\",\n\t\tHelvetica,\n\t\tArial,\n\t\tsans-serif,\n\t\t'Apple Color Emoji',\n\t\t'Segoe UI Emoji',\n\t\t'Segoe UI Symbol';\n}\n\n/* Grouping content\n   ========================================================================== */\n\n/**\n * Add the correct height in Firefox.\n */\n\nhr {\n\theight: 0;\n}\n\n/* Text-level semantics\n   ========================================================================== */\n\n/**\n * Add the correct text decoration in Chrome, Edge, and Safari.\n */\n\nabbr[title] {\n\ttext-decoration: underline dotted;\n}\n\n/**\n * Add the correct font weight in Chrome, Edge, and Safari.\n */\n\nb,\nstrong {\n\tfont-weight: bolder;\n}\n\n/**\n * 1. Improve consistency of default fonts in all browsers. (https://github.com/sindresorhus/modern-normalize/issues/3)\n * 2. Correct the odd `em` font sizing in all browsers.\n */\n\ncode,\nkbd,\nsamp,\npre {\n\tfont-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace; /* 1 */\n\tfont-size: 1em; /* 2 */\n}\n\n/**\n * Add the correct font size in all browsers.\n */\n\nsmall {\n\tfont-size: 80%;\n}\n\n/**\n * Prevent `sub` and `sup` elements from affecting the line height in all browsers.\n */\n\nsub,\nsup {\n\tfont-size: 75%;\n\tline-height: 0;\n\tposition: relative;\n\tvertical-align: baseline;\n}\n\nsub {\n\tbottom: -0.25em;\n}\n\nsup {\n\ttop: -0.5em;\n}\n\n/* Forms\n   ========================================================================== */\n\n/**\n * 1. Change the font styles in all browsers.\n * 2. Remove the margin in Firefox and Safari.\n */\n\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n\tfont-family: inherit; /* 1 */\n\tfont-size: 100%; /* 1 */\n\tline-height: 1.15; /* 1 */\n\tmargin: 0; /* 2 */\n}\n\n/**\n * Remove the inheritance of text transform in Edge and Firefox.\n * 1. Remove the inheritance of text transform in Firefox.\n */\n\nbutton,\nselect { /* 1 */\n\ttext-transform: none;\n}\n\n/**\n * Correct the inability to style clickable types in iOS and Safari.\n */\n\nbutton,\n[type='button'],\n[type='reset'],\n[type='submit'] {\n\t-webkit-appearance: button;\n}\n\n/**\n * Remove the inner border and padding in Firefox.\n */\n\nbutton::-moz-focus-inner,\n[type='button']::-moz-focus-inner,\n[type='reset']::-moz-focus-inner,\n[type='submit']::-moz-focus-inner {\n\tborder-style: none;\n\tpadding: 0;\n}\n\n/**\n * Restore the focus styles unset by the previous rule.\n */\n\nbutton:-moz-focusring,\n[type='button']:-moz-focusring,\n[type='reset']:-moz-focusring,\n[type='submit']:-moz-focusring {\n\toutline: 1px dotted ButtonText;\n}\n\n/**\n * Correct the padding in Firefox.\n */\n\nfieldset {\n\tpadding: 0.35em 0.75em 0.625em;\n}\n\n/**\n * Remove the padding so developers are not caught out when they zero out `fieldset` elements in all browsers.\n */\n\nlegend {\n\tpadding: 0;\n}\n\n/**\n * Add the correct vertical alignment in Chrome and Firefox.\n */\n\nprogress {\n\tvertical-align: baseline;\n}\n\n/**\n * Correct the cursor style of increment and decrement buttons in Safari.\n */\n\n[type='number']::-webkit-inner-spin-button,\n[type='number']::-webkit-outer-spin-button {\n\theight: auto;\n}\n\n/**\n * 1. Correct the odd appearance in Chrome and Safari.\n * 2. Correct the outline style in Safari.\n */\n\n[type='search'] {\n\t-webkit-appearance: textfield; /* 1 */\n\toutline-offset: -2px; /* 2 */\n}\n\n/**\n * Remove the inner padding in Chrome and Safari on macOS.\n */\n\n[type='search']::-webkit-search-decoration {\n\t-webkit-appearance: none;\n}\n\n/**\n * 1. Correct the inability to style clickable types in iOS and Safari.\n * 2. Change font properties to `inherit` in Safari.\n */\n\n::-webkit-file-upload-button {\n\t-webkit-appearance: button; /* 1 */\n\tfont: inherit; /* 2 */\n}\n\n/* Interactive\n   ========================================================================== */\n\n/*\n * Add the correct display in Chrome and Safari.\n */\n\nsummary {\n\tdisplay: list-item;\n}\n"
  },
  {
    "path": "public/default-files/themes/paper/assets/styles/_core/colors.less",
    "content": "//\n//\n//  𝗖 𝗢 𝗟 𝗢 𝗥\n//  v 1.6.3\n//\n//  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n\n//  General\n//  ───────────────────────────────────\n\n@oc-white:         #ffffff;\n@oc-black:         #000000;\n\n\n//  Gray\n//  ───────────────────────────────────\n\n@oc-gray-list: #f8f9fa, #f1f3f5, #e9ecef, #dee2e6, #ced4da, #adb5bd, #868e96, #495057, #343a40, #212529;\n\n@oc-gray-0: extract(@oc-gray-list, 1);\n@oc-gray-1: extract(@oc-gray-list, 2);\n@oc-gray-2: extract(@oc-gray-list, 3);\n@oc-gray-3: extract(@oc-gray-list, 4);\n@oc-gray-4: extract(@oc-gray-list, 5);\n@oc-gray-5: extract(@oc-gray-list, 6);\n@oc-gray-6: extract(@oc-gray-list, 7);\n@oc-gray-7: extract(@oc-gray-list, 8);\n@oc-gray-8: extract(@oc-gray-list, 9);\n@oc-gray-9: extract(@oc-gray-list, 10);\n\n\n//  Red\n//  ───────────────────────────────────\n\n@oc-red-list: #fff5f5, #ffe3e3, #ffc9c9, #ffa8a8, #ff8787, #ff6b6b, #fa5252, #f03e3e, #e03131, #c92a2a;\n\n@oc-red-0: extract(@oc-red-list, 1);\n@oc-red-1: extract(@oc-red-list, 2);\n@oc-red-2: extract(@oc-red-list, 3);\n@oc-red-3: extract(@oc-red-list, 4);\n@oc-red-4: extract(@oc-red-list, 5);\n@oc-red-5: extract(@oc-red-list, 6);\n@oc-red-6: extract(@oc-red-list, 7);\n@oc-red-7: extract(@oc-red-list, 8);\n@oc-red-8: extract(@oc-red-list, 9);\n@oc-red-9: extract(@oc-red-list, 10);\n\n\n//  Pink\n//  ───────────────────────────────────\n\n@oc-pink-list: #fff0f6, #ffdeeb, #fcc2d7, #faa2c1, #f783ac, #f06595, #e64980, #d6336c, #c2255c, #a61e4d;\n\n@oc-pink-0: extract(@oc-pink-list, 1);\n@oc-pink-1: extract(@oc-pink-list, 2);\n@oc-pink-2: extract(@oc-pink-list, 3);\n@oc-pink-3: extract(@oc-pink-list, 4);\n@oc-pink-4: extract(@oc-pink-list, 5);\n@oc-pink-5: extract(@oc-pink-list, 6);\n@oc-pink-6: extract(@oc-pink-list, 7);\n@oc-pink-7: extract(@oc-pink-list, 8);\n@oc-pink-8: extract(@oc-pink-list, 9);\n@oc-pink-9: extract(@oc-pink-list, 10);\n\n\n//  Grape\n//  ───────────────────────────────────\n\n@oc-grape-list: #f8f0fc, #f3d9fa, #eebefa, #e599f7, #da77f2, #cc5de8, #be4bdb, #ae3ec9, #9c36b5, #862e9c;\n\n@oc-grape-0: extract(@oc-grape-list, 1);\n@oc-grape-1: extract(@oc-grape-list, 2);\n@oc-grape-2: extract(@oc-grape-list, 3);\n@oc-grape-3: extract(@oc-grape-list, 4);\n@oc-grape-4: extract(@oc-grape-list, 5);\n@oc-grape-5: extract(@oc-grape-list, 6);\n@oc-grape-6: extract(@oc-grape-list, 7);\n@oc-grape-7: extract(@oc-grape-list, 8);\n@oc-grape-8: extract(@oc-grape-list, 9);\n@oc-grape-9: extract(@oc-grape-list, 10);\n\n\n//  Violet\n//  ───────────────────────────────────\n\n@oc-violet-list: #f3f0ff, #e5dbff, #d0bfff, #b197fc, #9775fa, #845ef7, #7950f2, #7048e8, #6741d9, #5f3dc4;\n\n@oc-violet-0: extract(@oc-violet-list, 1);\n@oc-violet-1: extract(@oc-violet-list, 2);\n@oc-violet-2: extract(@oc-violet-list, 3);\n@oc-violet-3: extract(@oc-violet-list, 4);\n@oc-violet-4: extract(@oc-violet-list, 5);\n@oc-violet-5: extract(@oc-violet-list, 6);\n@oc-violet-6: extract(@oc-violet-list, 7);\n@oc-violet-7: extract(@oc-violet-list, 8);\n@oc-violet-8: extract(@oc-violet-list, 9);\n@oc-violet-9: extract(@oc-violet-list, 10);\n\n\n//  Indigo\n//  ───────────────────────────────────\n\n@oc-indigo-list: #edf2ff, #dbe4ff, #bac8ff, #91a7ff, #748ffc, #5c7cfa, #4c6ef5, #4263eb, #3b5bdb, #364fc7;\n\n@oc-indigo-0: extract(@oc-indigo-list, 1);\n@oc-indigo-1: extract(@oc-indigo-list, 2);\n@oc-indigo-2: extract(@oc-indigo-list, 3);\n@oc-indigo-3: extract(@oc-indigo-list, 4);\n@oc-indigo-4: extract(@oc-indigo-list, 5);\n@oc-indigo-5: extract(@oc-indigo-list, 6);\n@oc-indigo-6: extract(@oc-indigo-list, 7);\n@oc-indigo-7: extract(@oc-indigo-list, 8);\n@oc-indigo-8: extract(@oc-indigo-list, 9);\n@oc-indigo-9: extract(@oc-indigo-list, 10);\n\n\n//  Blue\n//  ───────────────────────────────────\n\n@oc-blue-list: #e7f5ff, #d0ebff, #a5d8ff, #74c0fc, #4dabf7, #339af0, #228be6, #1c7ed6, #1971c2, #1864ab;\n\n@oc-blue-0: extract(@oc-blue-list, 1);\n@oc-blue-1: extract(@oc-blue-list, 2);\n@oc-blue-2: extract(@oc-blue-list, 3);\n@oc-blue-3: extract(@oc-blue-list, 4);\n@oc-blue-4: extract(@oc-blue-list, 5);\n@oc-blue-5: extract(@oc-blue-list, 6);\n@oc-blue-6: extract(@oc-blue-list, 7);\n@oc-blue-7: extract(@oc-blue-list, 8);\n@oc-blue-8: extract(@oc-blue-list, 9);\n@oc-blue-9: extract(@oc-blue-list, 10);\n\n\n//  Cyan\n//  ───────────────────────────────────\n\n@oc-cyan-list: #e3fafc, #c5f6fa, #99e9f2, #66d9e8, #3bc9db, #22b8cf, #15aabf, #1098ad, #0c8599, #0b7285;\n\n@oc-cyan-0: extract(@oc-cyan-list, 1);\n@oc-cyan-1: extract(@oc-cyan-list, 2);\n@oc-cyan-2: extract(@oc-cyan-list, 3);\n@oc-cyan-3: extract(@oc-cyan-list, 4);\n@oc-cyan-4: extract(@oc-cyan-list, 5);\n@oc-cyan-5: extract(@oc-cyan-list, 6);\n@oc-cyan-6: extract(@oc-cyan-list, 7);\n@oc-cyan-7: extract(@oc-cyan-list, 8);\n@oc-cyan-8: extract(@oc-cyan-list, 9);\n@oc-cyan-9: extract(@oc-cyan-list, 10);\n\n\n//  Teal\n//  ───────────────────────────────────\n\n@oc-teal-list: #e6fcf5, #c3fae8, #96f2d7, #63e6be, #38d9a9, #20c997, #12b886, #0ca678, #099268, #087f5b;\n\n@oc-teal-0: extract(@oc-teal-list, 1);\n@oc-teal-1: extract(@oc-teal-list, 2);\n@oc-teal-2: extract(@oc-teal-list, 3);\n@oc-teal-3: extract(@oc-teal-list, 4);\n@oc-teal-4: extract(@oc-teal-list, 5);\n@oc-teal-5: extract(@oc-teal-list, 6);\n@oc-teal-6: extract(@oc-teal-list, 7);\n@oc-teal-7: extract(@oc-teal-list, 8);\n@oc-teal-8: extract(@oc-teal-list, 9);\n@oc-teal-9: extract(@oc-teal-list, 10);\n\n\n//  Green\n//  ───────────────────────────────────\n\n@oc-green-list: #ebfbee, #d3f9d8, #b2f2bb, #8ce99a, #69db7c, #51cf66, #40c057, #37b24d, #2f9e44, #2b8a3e;\n\n@oc-green-0: extract(@oc-green-list, 1);\n@oc-green-1: extract(@oc-green-list, 2);\n@oc-green-2: extract(@oc-green-list, 3);\n@oc-green-3: extract(@oc-green-list, 4);\n@oc-green-4: extract(@oc-green-list, 5);\n@oc-green-5: extract(@oc-green-list, 6);\n@oc-green-6: extract(@oc-green-list, 7);\n@oc-green-7: extract(@oc-green-list, 8);\n@oc-green-8: extract(@oc-green-list, 9);\n@oc-green-9: extract(@oc-green-list, 10);\n\n\n//  Lime\n//  ───────────────────────────────────\n\n@oc-lime-list: #f4fce3, #e9fac8, #d8f5a2, #c0eb75, #a9e34b, #94d82d, #82c91e, #74b816, #66a80f, #5c940d;\n\n@oc-lime-0: extract(@oc-lime-list, 1);\n@oc-lime-1: extract(@oc-lime-list, 2);\n@oc-lime-2: extract(@oc-lime-list, 3);\n@oc-lime-3: extract(@oc-lime-list, 4);\n@oc-lime-4: extract(@oc-lime-list, 5);\n@oc-lime-5: extract(@oc-lime-list, 6);\n@oc-lime-6: extract(@oc-lime-list, 7);\n@oc-lime-7: extract(@oc-lime-list, 8);\n@oc-lime-8: extract(@oc-lime-list, 9);\n@oc-lime-9: extract(@oc-lime-list, 10);\n\n\n//  Yellow\n//  ───────────────────────────────────\n\n@oc-yellow-list: #fff9db, #fff3bf, #ffec99, #ffe066, #ffd43b, #fcc419, #fab005, #f59f00, #f08c00, #e67700;\n\n@oc-yellow-0: extract(@oc-yellow-list, 1);\n@oc-yellow-1: extract(@oc-yellow-list, 2);\n@oc-yellow-2: extract(@oc-yellow-list, 3);\n@oc-yellow-3: extract(@oc-yellow-list, 4);\n@oc-yellow-4: extract(@oc-yellow-list, 5);\n@oc-yellow-5: extract(@oc-yellow-list, 6);\n@oc-yellow-6: extract(@oc-yellow-list, 7);\n@oc-yellow-7: extract(@oc-yellow-list, 8);\n@oc-yellow-8: extract(@oc-yellow-list, 9);\n@oc-yellow-9: extract(@oc-yellow-list, 10);\n\n\n//  Orange\n//  ───────────────────────────────────\n\n@oc-orange-list: #fff4e6, #ffe8cc, #ffd8a8, #ffc078, #ffa94d, #ff922b, #fd7e14, #f76707, #e8590c, #d9480f;\n\n@oc-orange-0: extract(@oc-orange-list, 1);\n@oc-orange-1: extract(@oc-orange-list, 2);\n@oc-orange-2: extract(@oc-orange-list, 3);\n@oc-orange-3: extract(@oc-orange-list, 4);\n@oc-orange-4: extract(@oc-orange-list, 5);\n@oc-orange-5: extract(@oc-orange-list, 6);\n@oc-orange-6: extract(@oc-orange-list, 7);\n@oc-orange-7: extract(@oc-orange-list, 8);\n@oc-orange-8: extract(@oc-orange-list, 9);\n@oc-orange-9: extract(@oc-orange-list, 10);\n"
  },
  {
    "path": "public/default-files/themes/paper/assets/styles/_core/github.less",
    "content": ".hljs {\n  display: block;\n  overflow-x: auto;\n  padding: 0.5em;\n  color: #333;\n  background: #f8f8f8;\n}\n\n.hljs-comment,\n.hljs-quote {\n  color: #998;\n  font-style: italic;\n}\n\n.hljs-keyword,\n.hljs-selector-tag,\n.hljs-subst {\n  color: #333;\n  font-weight: bold;\n}\n\n.hljs-number,\n.hljs-literal,\n.hljs-variable,\n.hljs-template-variable,\n.hljs-tag .hljs-attr {\n  color: #008080;\n}\n\n.hljs-string,\n.hljs-doctag {\n  color: #d14;\n}\n\n.hljs-title,\n.hljs-section,\n.hljs-selector-id {\n  color: #900;\n  font-weight: bold;\n}\n\n.hljs-subst {\n  font-weight: normal;\n}\n\n.hljs-type,\n.hljs-class .hljs-title {\n  color: #458;\n  font-weight: bold;\n}\n\n.hljs-tag,\n.hljs-name,\n.hljs-attribute {\n  color: #000080;\n  font-weight: normal;\n}\n\n.hljs-regexp,\n.hljs-link {\n  color: #009926;\n}\n\n.hljs-symbol,\n.hljs-bullet {\n  color: #990073;\n}\n\n.hljs-built_in,\n.hljs-builtin-name {\n  color: #0086b3;\n}\n\n.hljs-meta {\n  color: #999;\n  font-weight: bold;\n}\n\n.hljs-deletion {\n  background: #fdd;\n}\n\n.hljs-addition {\n  background: #dfd;\n}\n\n.hljs-emphasis {\n  font-style: italic;\n}\n\n.hljs-strong {\n  font-weight: bold;\n}\n"
  },
  {
    "path": "public/default-files/themes/paper/assets/styles/main.less",
    "content": "@import \"_core/colors\";\n@import \"_core/github\";\n\nbody {\n  background-image: url('../media/images/geometry2.png');\n}\n\n#top {\n  max-width: 1440px;\n  margin-top: 3rem;\n}\n\narticle .article-meta a {\n  color: #ffffff;\n}\n.sidebar {\n  .sidebar-title {\n    margin-bottom: 24px;\n  }\n  ul {\n    padding-left: 0px;\n  }\n  li {\n    margin-bottom: 16px;\n  }\n  a.badge {\n    background-image: none;\n    margin: 4px;\n    color: #ffffff;\n  }\n}\n\n.info-container {\n  text-align: center;\n  .avatar {\n    width: 120px;\n    height: 120px;\n    border-top-left-radius: 185px 160px;\n    border-top-right-radius: 200px 195px;\n    border-bottom-right-radius: 160px 195px;\n    border-bottom-left-radius: 185px 190px;\n    margin-bottom: 24px;\n  }\n}\n\n.social-container {\n  margin-top: 24px;\n  a {\n    background-image: none;\n    margin: 4px;\n  }\n}\n\n.tags-container {\n  a {\n    margin: 8px;\n    background-image: none;\n    color: #ffffff;\n  }\n}\n\n.readmore-link {\n  background-image: none;\n}\n\nh1 {\n  font-size: 3.5rem;\n}\n\narticle .article-title, h2 {\n  font-size: 2.5rem;\n}\n\nnav ul.inline li a {\n  display: block;\n}\n"
  },
  {
    "path": "public/default-files/themes/paper/config.json",
    "content": "{\n  \"name\": \"Paper\",\n  \"version\": \"1.0.0\",\n  \"author\": \"EryouHao\",\n  \"repository\": \"https://github.com/getgridea/gridea-theme-paper\",\n  \"customConfig\": [\n    {\n      \"name\": \"readMoreText\",\n      \"label\": \"Read More\",\n      \"group\": \"文案\",\n      \"value\": \"Read More\",\n      \"type\": \"input\",\n      \"note\": \"Read More\"\n    },\n    {\n      \"name\": \"nextArticleText\",\n      \"label\": \"下一篇\",\n      \"group\": \"文案\",\n      \"value\": \"下一篇\",\n      \"type\": \"input\",\n      \"note\": \"下一篇\"\n    },\n    {\n      \"name\": \"prevPageText\",\n      \"label\": \"上一页\",\n      \"group\": \"文案\",\n      \"value\": \"上一页\",\n      \"type\": \"input\",\n      \"note\": \"上一页\"\n    },\n    {\n      \"name\": \"nextPageText\",\n      \"label\": \"下一页\",\n      \"group\": \"文案\",\n      \"value\": \"下一页\",\n      \"type\": \"input\",\n      \"note\": \"下一页\"\n    },\n    {\n      \"name\": \"latestArticleText\",\n      \"label\": \"最新文章\",\n      \"group\": \"文案\",\n      \"value\": \"最新文章\",\n      \"type\": \"input\",\n      \"note\": \"最新文章\"\n    },\n    {\n      \"name\": \"tagListText\",\n      \"label\": \"标签列表\",\n      \"group\": \"文案\",\n      \"value\": \"标签列表\",\n      \"type\": \"input\",\n      \"note\": \"标签列表\"\n    },\n    {\n      \"name\": \"contentBgColor\",\n      \"label\": \"内容区背景色\",\n      \"group\": \"颜色\",\n      \"value\": \"#ffffff\",\n      \"type\": \"input\",\n      \"card\": \"color\",\n      \"note\": \"颜色字符串:（如：#EEEEEE、rgba(255, 255, 255, 0.9)）\"\n    },\n    {\n      \"name\": \"navBgColor\",\n      \"label\": \"导航栏背景色\",\n      \"group\": \"颜色\",\n      \"value\": \"#ffffff\",\n      \"type\": \"input\",\n      \"card\": \"color\",\n      \"note\": \"颜色字符串:（如：#EEEEEE、rgba(255, 255, 255, 0.9)）\"\n    },\n    {\n      \"name\": \"github\",\n      \"label\": \"Github\",\n      \"group\": \"社交\",\n      \"value\": \"\",\n      \"type\": \"input\",\n      \"note\": \"链接地址\"\n    },\n    {\n      \"name\": \"twitter\",\n      \"label\": \"Twitter\",\n      \"group\": \"社交\",\n      \"value\": \"\",\n      \"type\": \"input\",\n      \"note\": \"链接地址\"\n    },\n    {\n      \"name\": \"weibo\",\n      \"label\": \"微博\",\n      \"group\": \"社交\",\n      \"value\": \"\",\n      \"type\": \"input\",\n      \"note\": \"链接地址\"\n    },\n    {\n      \"name\": \"zhihu\",\n      \"label\": \"知乎\",\n      \"group\": \"社交\",\n      \"value\": \"\",\n      \"type\": \"input\",\n      \"note\": \"链接地址\"\n    },\n    {\n      \"name\": \"facebook\",\n      \"label\": \"Facebook\",\n      \"group\": \"社交\",\n      \"value\": \"\",\n      \"type\": \"input\",\n      \"note\": \"链接地址\"\n    },\n    {\n      \"name\": \"customCss\",\n      \"label\": \"自定义CSS\",\n      \"group\": \"自定义样式\",\n      \"value\": \"\",\n      \"type\": \"textarea\",\n      \"note\": \"\"\n    },\n    {\n      \"name\": \"ga\",\n      \"label\": \"跟踪 ID\",\n      \"group\": \"谷歌统计\",\n      \"value\": \"\",\n      \"type\": \"input\",\n      \"note\": \"UA-xxxxxxxxx-x\"\n    }\n  ]\n}\n"
  },
  {
    "path": "public/default-files/themes/paper/style-override.js",
    "content": "const generateOverride = (params = {}) => {\n  let result = ''\n\n  // 内容区背景色 - contentBgColor\n  if (params.contentBgColor && params.contentBgColor !== '#ffffff') {\n    result += `\n      .paper {\n        background: ${params.contentBgColor};\n      }\n    `\n  }\n\n  // 导航栏背景色 - contentBgColor\n  if (params.navBgColor && params.navBgColor !== '#ffffff') {\n    result += `\n      .navbar {\n        background: ${params.navBgColor};\n      }\n    `\n  }\n\n  if (params.customCss) {\n    result += `\n      ${params.customCss}\n    `\n  }\n\n\n  console.log('result', result)\n\n  return result\n}\n\nmodule.exports = generateOverride\n"
  },
  {
    "path": "public/default-files/themes/paper/templates/_blocks/head.ejs",
    "content": "<meta charset=\"utf-8\" >\r\n\r\n<title><%= siteTitle %></title>\r\n<meta name=\"description\" content=\"<%= themeConfig.siteDescription %>\">\r\n\r\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no\">\r\n<link rel=\"shortcut icon\" href=\"<%= themeConfig.domain %>/favicon.ico?v=<%= site.utils.now %>\">\r\n\r\n<link rel=\"stylesheet\" href=\"https://use.fontawesome.com/releases/v5.7.2/css/all.css\" integrity=\"sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr\" crossorigin=\"anonymous\">\r\n<link rel=\"stylesheet\" href=\"https://unpkg.com/papercss@1.6.1/dist/paper.min.css\" />\r\n<link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.10.0/katex.min.css\">\r\n<link rel=\"stylesheet\" href=\"<%= themeConfig.domain %>/styles/main.css\">\r\n\r\n<% if (typeof commentSetting !== 'undefined' && commentSetting.showComment) { %>\r\n  <% if (commentSetting.commentPlatform === 'gitalk') { %>\r\n    <link rel=\"stylesheet\" href=\"https://unpkg.com/gitalk/dist/gitalk.css\" />\r\n  <% } %>\r\n\r\n  <% if (commentSetting.commentPlatform === 'disqus') { %>\r\n    <link rel=\"stylesheet\" href=\"https://unpkg.com/disqusjs@1.1/dist/disqusjs.css\" />\r\n  <% } %>\r\n<% } %>\r\n<script src=\"//cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.5.1/build/highlight.min.js\"></script>\r\n\r\n\r\n<link rel=\"stylesheet\" href=\"https://unpkg.com/aos@next/dist/aos.css\" />\r\n<% if (site.customConfig.ga) { %>\r\n<script async src=\"https://www.googletagmanager.com/gtag/js?id=<%= site.customConfig.ga %>\"></script>\r\n<script>\r\n  window.dataLayer = window.dataLayer || [];\r\n  function gtag(){dataLayer.push(arguments);}\r\n  gtag('js', new Date());\r\n\r\n  gtag('config', '<%= site.customConfig.ga %>');\r\n</script>\r\n<% } %>\r\n"
  },
  {
    "path": "public/default-files/themes/paper/templates/_blocks/header.ejs",
    "content": "<nav class=\"navbar border fixed split-nav\">\n  <div class=\"nav-brand\">\n    <h3><a href=\"<%= themeConfig.domain %>\"><%= themeConfig.siteName %></a></h3>\n  </div>\n  <div class=\"collapsible\">\n    <input id=\"collapsible1\" type=\"checkbox\" name=\"collapsible1\">\n    <button>\n      <label for=\"collapsible1\">\n        <div class=\"bar1\"></div>\n        <div class=\"bar2\"></div>\n        <div class=\"bar3\"></div>\n      </label>\n    </button>\n    <div class=\"collapsible-body\">\n      <ul class=\"inline\">\n        <% menus.forEach(function(menu) { %>\n          <li>\n            <% if (menu.openType === 'External') { %>\n              <a href=\"<%= menu.link %>\" class=\"menu\" target=\"_blank\">\n                <%= menu.name %>\n              </a>\n            <% } else { %>\n              <a href=\"<%= menu.link %>\" class=\"menu\">\n                <%= menu.name %>\n              </a>\n            <% } %>\n          </li>\n        <% }); %>\n      </ul>\n    </div>\n  </div>\n</nav>\n"
  },
  {
    "path": "public/default-files/themes/paper/templates/_blocks/pagination.ejs",
    "content": "<div class=\"row flex-edges\">\n  <% if (pagination.prev) { %>\n    <a href=\"<%= pagination.prev %>\" class=\"paper-btn\">\n      <%= site.customConfig.prevPageText || '上一页' %>\n    </a>\n  <% } %>\n  <% if (pagination.next) { %>\n    <a href=\"<%= pagination.next %>\" class=\"paper-btn\">\n      <%= site.customConfig.nextPageText || '下一页' %>\n    </a>\n  <% } %>\n</div>\n"
  },
  {
    "path": "public/default-files/themes/paper/templates/_blocks/post-list.ejs",
    "content": "<% posts.forEach(function(post) { %>\n  <article class=\"article\">\n    <h2 class=\"article-title\">\n      <a href=\"<%= post.link %>\"><%= post.title %></a>\n    </h2>\n    <p class=\"article-meta\">\n      <%= post.dateFormat %>\n      <% post.tags.forEach(function(tag, tagIndex) { %>\n        <a href=\"<%= tag.link %>\" class=\"badge <%= ['', 'secondary', 'success', 'warning', 'secondary'][Math.floor(Math.random()*5)] %>\">\n          <%= tag.name %>\n        </a>\n      <% }); %>\n    </p>\n    <p class=\"text-lead\">\n      <%- post.abstract %>\n    </p>\n    <div class=\"row\">\n      <a href=\"<%= post.link %>\" class=\"readmore-link\">\n        <button>\n          <%= site.customConfig.readMoreText || 'Read More' %>\n        </button>\n      </a>\n    </div>\n  </article>\n<% }); %>\n"
  },
  {
    "path": "public/default-files/themes/paper/templates/_blocks/scripts.ejs",
    "content": "<script src=\"https://unpkg.com/aos@next/dist/aos.js\"></script>\n\n<script type=\"application/javascript\">\n\nAOS.init();\n\nhljs.initHighlightingOnLoad()\n\n</script>\n\n<% if (typeof commentSetting !== 'undefined' && commentSetting.showComment) { %>\n\n  <% if (commentSetting.commentPlatform === 'gitalk') { %>\n    <script src=\"https://unpkg.com/gitalk/dist/gitalk.min.js\"></script>\n    <script>\n\n      var gitalk = new Gitalk({\n        clientID: '<%= commentSetting.gitalkSetting.clientId %>',\n        clientSecret: '<%= commentSetting.gitalkSetting.clientSecret %>',\n        repo: '<%= commentSetting.gitalkSetting.repository %>',\n        owner: '<%= commentSetting.gitalkSetting.owner %>',\n        admin: ['<%= commentSetting.gitalkSetting.owner %>'],\n        id: (location.pathname).substring(0, 49),      // Ensure uniqueness and length less than 50\n        distractionFreeMode: false  // Facebook-like distraction free mode\n      })\n\n      gitalk.render('gitalk-container')\n\n    </script>\n  <% } %>\n\n  <% if (commentSetting.commentPlatform === 'disqus') { %>\n    <script src=\"https://unpkg.com/disqusjs@1.1/dist/disqus.js\"></script>\n    <script>\n\n    var options = {\n      shortname: '<%= commentSetting.disqusSetting.shortname %>',\n      apikey: '<%= commentSetting.disqusSetting.apikey %>',\n    }\n    if ('<%= commentSetting.disqusSetting.api %>') {\n      options.api = '<%= commentSetting.disqusSetting.api %>'\n    }\n    var dsqjs = new DisqusJS(options)\n\n    </script>\n  <% } %>\n\n<% } %>\n\n"
  },
  {
    "path": "public/default-files/themes/paper/templates/_blocks/sidebar.ejs",
    "content": "<div class=\"sm-12 md-4 col sidebar\">\n  <div class=\"paper info-container\">\n    <img src=\"<%= themeConfig.domain %>/images/avatar.png?v=<%= site.utils.now %>\" class=\"no-responsive avatar\">\n    <div class=\"text-muted\"><%- themeConfig.siteDescription %></div>\n    <div class=\"social-container\">\n      <% ['github', 'twitter', 'weibo', 'zhihu', 'facebook'].forEach((item) => { %>\n        <% if (site.customConfig[item]) { %>\n          <a href=\"<%= site.customConfig[item] %>\" target=\"_blank\">\n            <i class=\"fab fa-<%= item %>\"></i>\n          </a>\n        <% } %>\n      <% }) %>\n    </div>\n  </div>\n  <div class=\"paper\">\n    <div class=\"sidebar-title\">\n      <%= site.customConfig.latestArticleText || '最新文章' %>\n    </div>\n    <div class=\"row\">\n      <ul>\n        <% site.posts.forEach(function(post, index) { %>\n          <% if (index < 10) { %>\n            <li>\n              <a href=\"<%= post.link %>\"><%= post.title %></a>\n            </li>\n          <% } %>\n        <% }); %>\n      </ul>\n    </div>\n  </div>\n  <div class=\"paper\">\n    <div class=\"sidebar-title\">\n      <%= site.customConfig.tagListText || '标签列表' %>\n    </div>\n    <div class=\"row\">\n      <% site.tags.forEach(function(tag, tagIndex) { %>\n        <a href=\"<%= tag.link %>\" class=\"badge <%= ['', 'secondary', 'success', 'warning', 'secondary'][Math.floor(Math.random()*5)] %>\">\n          <%= tag.name %>\n        </a>\n      <% }); %>\n    </div>\n  </div>\n  <div class=\"paper\">\n    <%- themeConfig.footerInfo %> | <a class=\"rss\" href=\"<%= themeConfig.domain %>/atom.xml\" target=\"_blank\">RSS</a>\n  </div>\n</div>\n"
  },
  {
    "path": "public/default-files/themes/paper/templates/archives.ejs",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <%- include('./_blocks/head', { siteTitle: `文章归档 | ${themeConfig.siteName}` }) %>\n  </head>\n  <body>\n    <%- include('./_blocks/header') %>\n    \n    <div id=\"top\" class=\"row site\">\n      <div class=\"sm-12 md-8 col\">\n        <div class=\"paper\">\n          <% let years = []; posts.forEach((item) => { const year = item.date.substring(0, 4); if (!years.includes(year)) { years.push(year); } }); %>\n\n          <h2 class=\"archives-title\">文章归档</h2>\n          <div class=\"archives-container\">\n            <% years.forEach(function(year) { %>\n              <h3 class=\"year\" data-aos-delay=\"500\"><%- year %></h3>\n              <% posts.forEach(function(post) { %>\n                <%if (post.date.indexOf(year) !== -1) { %>\n                  <article class=\"post\">\n                    <a href=\"<%= post.link %>\">\n                      <h4 class=\"post-title\">\n                        <%= post.title %>\n                      </h4>\n                    </a>\n                  </article>\n                <% } %>\n              <% }); %>\n            <% }); %>\n          </div>\n        </div>\n        <%- include('./_blocks/pagination') %>\n      </div>\n      <%- include('./_blocks/sidebar') %>\n\n    <%- include('./_blocks/scripts') %>\n  </body>\n</html>\n"
  },
  {
    "path": "public/default-files/themes/paper/templates/index.ejs",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <%- include('./_blocks/head', { siteTitle: themeConfig.siteName }) %>\n  </head>\n  <body>\n    <%- include('./_blocks/header') %>\n\n    <div id=\"top\" class=\"row site\">\n      <div class=\"sm-12 md-8 col\">\n        <div class=\"paper\">\n          <%- include('./_blocks/post-list') %>\n        </div>\n        \n        <%- include('./_blocks/pagination') %>\n      </div>\n      \n      <%- include('./_blocks/sidebar') %>\n    </div>\n    <%- include('./_blocks/scripts') %>\n  </body>\n</html>\n"
  },
  {
    "path": "public/default-files/themes/paper/templates/post.ejs",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <%- include('./_blocks/head', { siteTitle: `${post.title} | ${themeConfig.siteName}` }) %>\n  </head>\n  <body>\n  \n    <%- include('./_blocks/header') %>\n    <div id=\"top\" class=\"row site\">\n      <div class=\"sm-12 md-8 col\">\n        <div class=\"paper\">\n          <article class=\"article\">\n            <h1><%= post.title %></h1>\n            <p class=\"article-meta\">\n              <%= post.dateFormat %>\n              <% post.tags.forEach(function(tag, tagIndex) { %>\n                <a href=\"<%= tag.link %>\" class=\"badge <%= ['', 'secondary', 'success', 'warning', 'secondary'][Math.floor(Math.random()*5)] %>\">\n                  <%= tag.name %>\n                </a>\n              <% }); %>\n            </p>\n            <% if (post.feature) { %>\n              <img src=\"<%= post.feature %>\" alt=\"<%= post.title %>\">\n            <% } %>\n            <div class=\"post-content\" v-pre>\n              <%- post.content %>\n            </div>\n          </article>\n        </div>\n        <div class=\"paper\" data-aos=\"fade-in\">\n          <% if (post.nextPost && !post.hideInList) { %>\n            <div class=\"next-post\">\n              <div class=\"next\">\n                <%= site.customConfig.nextArticleText || '下一篇' %>\n              </div>\n              <a href=\"<%= post.nextPost.link %>\">\n                <h3 class=\"post-title\">\n                  <%= post.nextPost.title %>\n                </h3>\n              </a>\n            </div>\n          <% } %>\n        </div>\n        <% if (typeof commentSetting !== 'undefined' && commentSetting.showComment) { %>\n          <% if (commentSetting.commentPlatform === 'gitalk') { %>\n            <div class=\"paper\" data-aos=\"fade-in\">\n              <div id=\"gitalk-container\"></div>\n            </div>\n          <% } %>\n\n          <% if (commentSetting.commentPlatform === 'disqus') { %>\n            <div class=\"paper\" data-aos=\"fade-in\">\n              <div id=\"disqus_thread\"></div>\n            </div>\n          <% } %>\n        <% } %>\n      </div>\n\n      <%- include('./_blocks/sidebar') %>\n\n    </div>\n\n    <%- include('./_blocks/scripts') %>\n  </body>\n</html>\n"
  },
  {
    "path": "public/default-files/themes/paper/templates/tag.ejs",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <%- include('./_blocks/head', { siteTitle: `${tag.name} | ${themeConfig.siteName}` }) %>\n  </head>\n  <body>\n    <%- include('./_blocks/header') %>\n    <div id=\"top\" class=\"row site\">\n      <div class=\"sm-12 md-8 col\">\n        <div class=\"paper\">\n          <h2 class=\"current-tag\">标签: <%= tag.name %></h2>\n          <%- include('./_blocks/post-list') %>\n        </div>\n        <%- include('./_blocks/pagination') %>\n      </div>\n      <%- include('./_blocks/sidebar') %>\n    </div>\n\n    <%- include('./_blocks/scripts') %>\n  </body>\n</html>\n"
  },
  {
    "path": "public/default-files/themes/paper/templates/tags.ejs",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <%- include('./_blocks/head', { siteTitle: `标签列表 | ${themeConfig.siteName}` }) %>\n  </head>\n  <body>\n    <%- include('./_blocks/header') %>\n    <div id=\"top\" class=\"row site\">\n      <div class=\"sm-12 md-8 col\">\n        <div class=\"paper\">\n          <h2 class=\"tag-list-title\">标签列表</h2>\n          <div class=\"row tags-container\">\n            <% tags.forEach(function(tag, tagIndex) { %>\n              <a href=\"<%= tag.link %>\" class=\"badge <%= ['', 'secondary', 'success', 'warning', 'secondary'][Math.floor(Math.random()*5)] %>\">\n                <%= tag.name %>\n              </a>\n            <% }); %>\n          </div>\n        </div>\n      </div>\n      <%- include('./_blocks/sidebar') %>  \n    </div>\n\n    <%- include('./_blocks/scripts') %>\n  </body>\n</html>\n"
  },
  {
    "path": "public/default-files/themes/simple/assets/styles/_core/a11y-dark.less",
    "content": "/* a11y-dark theme */\n/* Based on the Tomorrow Night Eighties theme: https://github.com/isagalaev/highlight.js/blob/master/src/styles/tomorrow-night-eighties.css */\n/* @author: ericwbailey */\n\n/* Comment */\n.hljs-comment,\n.hljs-quote {\n  color: #d4d0ab;\n}\n\n/* Red */\n.hljs-variable,\n.hljs-template-variable,\n.hljs-tag,\n.hljs-name,\n.hljs-selector-id,\n.hljs-selector-class,\n.hljs-regexp,\n.hljs-deletion {\n  color: #ffa07a;\n}\n\n/* Orange */\n.hljs-number,\n.hljs-built_in,\n.hljs-builtin-name,\n.hljs-literal,\n.hljs-type,\n.hljs-params,\n.hljs-meta,\n.hljs-link {\n  color: #f5ab35;\n}\n\n/* Yellow */\n.hljs-attribute {\n  color: #ffd700;\n}\n\n/* Green */\n.hljs-string,\n.hljs-symbol,\n.hljs-bullet,\n.hljs-addition {\n  color: #abe338;\n}\n\n/* Blue */\n.hljs-title,\n.hljs-section {\n  color: #00e0e0;\n}\n\n/* Purple */\n.hljs-keyword,\n.hljs-selector-tag {\n  color: #dcc6e0;\n}\n\n.hljs {\n  display: block;\n  overflow-x: auto;\n  background: #2b2b2b;\n  color: #f8f8f2;\n  padding: 0.5em;\n}\n\n.hljs-emphasis {\n  font-style: italic;\n}\n\n.hljs-strong {\n  font-weight: bold;\n}\n\n@media screen and (-ms-high-contrast: active) {\n  .hljs-addition,\n  .hljs-attribute,\n  .hljs-built_in,\n  .hljs-builtin-name,\n  .hljs-bullet,\n  .hljs-comment,\n  .hljs-link,\n  .hljs-literal,\n  .hljs-meta,\n  .hljs-number,\n  .hljs-params,\n  .hljs-string,\n  .hljs-symbol,\n  .hljs-type,\n  .hljs-quote {\n        color: highlight;\n    }\n\n    .hljs-keyword,\n    .hljs-selector-tag {\n        font-weight: bold;\n    }\n}\n"
  },
  {
    "path": "public/default-files/themes/simple/assets/styles/_core/atom-one-dark.less",
    "content": "/*\n\nAtom One Dark by Daniel Gamage\nOriginal One Dark Syntax theme from https://github.com/atom/one-dark-syntax\n\nbase:    #282c34\nmono-1:  #abb2bf\nmono-2:  #818896\nmono-3:  #5c6370\nhue-1:   #56b6c2\nhue-2:   #61aeee\nhue-3:   #c678dd\nhue-4:   #98c379\nhue-5:   #e06c75\nhue-5-2: #be5046\nhue-6:   #d19a66\nhue-6-2: #e6c07b\n\n*/\n\n.hljs {\n  display: block;\n  overflow-x: auto;\n  padding: 0.5em;\n  color: #abb2bf;\n  background: #282c34;\n}\n\n.hljs-comment,\n.hljs-quote {\n  color: #5c6370;\n  font-style: italic;\n}\n\n.hljs-doctag,\n.hljs-keyword,\n.hljs-formula {\n  color: #c678dd;\n}\n\n.hljs-section,\n.hljs-name,\n.hljs-selector-tag,\n.hljs-deletion,\n.hljs-subst {\n  color: #e06c75;\n}\n\n.hljs-literal {\n  color: #56b6c2;\n}\n\n.hljs-string,\n.hljs-regexp,\n.hljs-addition,\n.hljs-attribute,\n.hljs-meta-string {\n  color: #98c379;\n}\n\n.hljs-built_in,\n.hljs-class .hljs-title {\n  color: #e6c07b;\n}\n\n.hljs-attr,\n.hljs-variable,\n.hljs-template-variable,\n.hljs-type,\n.hljs-selector-class,\n.hljs-selector-attr,\n.hljs-selector-pseudo,\n.hljs-number {\n  color: #d19a66;\n}\n\n.hljs-symbol,\n.hljs-bullet,\n.hljs-link,\n.hljs-meta,\n.hljs-selector-id,\n.hljs-title {\n  color: #61aeee;\n}\n\n.hljs-emphasis {\n  font-style: italic;\n}\n\n.hljs-strong {\n  font-weight: bold;\n}\n\n.hljs-link {\n  text-decoration: underline;\n}\n"
  },
  {
    "path": "public/default-files/themes/simple/assets/styles/_core/base.less",
    "content": "/*! modern-normalize | MIT License | https://github.com/sindresorhus/modern-normalize */\n\n/* Document\n   ========================================================================== */\n\n/**\n * Use a better box model (opinionated).\n */\n\n html {\n\tbox-sizing: border-box;\n}\n\n*,\n*::before,\n*::after {\n  box-sizing: inherit;\n  margin: 0;\n  padding: 0;\n}\n\n/**\n * Use a more readable tab size (opinionated).\n */\n\n:root {\n\t-moz-tab-size: 4;\n\ttab-size: 4;\n}\n\n/**\n * 1. Correct the line height in all browsers.\n * 2. Prevent adjustments of font size after orientation changes in iOS.\n */\n\nhtml {\n\tline-height: 1.15; /* 1 */\n\t-webkit-text-size-adjust: 100%; /* 2 */\n}\n\n/* Sections\n   ========================================================================== */\n\n/**\n * Remove the margin in all browsers.\n */\n\nbody {\n  margin: 0;\n  font-size: 14px;\n}\n\n/**\n * Improve consistency of default fonts in all browsers. (https://github.com/sindresorhus/modern-normalize/issues/3)\n */\n\nbody {\n\tfont-family:\n\t\t-apple-system,\n\t\tBlinkMacSystemFont,\n\t\t'Segoe UI',\n\t\tRoboto,\n\t\t\"PingFang SC\",\n\t\t\"Hiragino Sans GB\",\n\t\t\"Microsoft YaHei\",\n\t\tHelvetica,\n\t\tArial,\n\t\tsans-serif,\n\t\t'Apple Color Emoji',\n\t\t'Segoe UI Emoji',\n\t\t'Segoe UI Symbol';\n}\n\n/* Grouping content\n   ========================================================================== */\n\n/**\n * Add the correct height in Firefox.\n */\n\nhr {\n\theight: 0;\n}\n\n/* Text-level semantics\n   ========================================================================== */\n\n/**\n * Add the correct text decoration in Chrome, Edge, and Safari.\n */\n\nabbr[title] {\n\ttext-decoration: underline dotted;\n}\n\n/**\n * Add the correct font weight in Chrome, Edge, and Safari.\n */\n\nb,\nstrong {\n\tfont-weight: bolder;\n}\n\n/**\n * 1. Improve consistency of default fonts in all browsers. (https://github.com/sindresorhus/modern-normalize/issues/3)\n * 2. Correct the odd `em` font sizing in all browsers.\n */\n\ncode,\nkbd,\nsamp,\npre {\n\tfont-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace; /* 1 */\n\tfont-size: 1em; /* 2 */\n}\n\n/**\n * Add the correct font size in all browsers.\n */\n\nsmall {\n\tfont-size: 80%;\n}\n\n/**\n * Prevent `sub` and `sup` elements from affecting the line height in all browsers.\n */\n\nsub,\nsup {\n\tfont-size: 75%;\n\tline-height: 0;\n\tposition: relative;\n\tvertical-align: baseline;\n}\n\nsub {\n\tbottom: -0.25em;\n}\n\nsup {\n\ttop: -0.5em;\n}\n\n/* Forms\n   ========================================================================== */\n\n/**\n * 1. Change the font styles in all browsers.\n * 2. Remove the margin in Firefox and Safari.\n */\n\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n\tfont-family: inherit; /* 1 */\n\tfont-size: 100%; /* 1 */\n\tline-height: 1.15; /* 1 */\n\tmargin: 0; /* 2 */\n}\n\n/**\n * Remove the inheritance of text transform in Edge and Firefox.\n * 1. Remove the inheritance of text transform in Firefox.\n */\n\nbutton,\nselect { /* 1 */\n\ttext-transform: none;\n}\n\n/**\n * Correct the inability to style clickable types in iOS and Safari.\n */\n\nbutton,\n[type='button'],\n[type='reset'],\n[type='submit'] {\n\t-webkit-appearance: button;\n}\n\n/**\n * Remove the inner border and padding in Firefox.\n */\n\nbutton::-moz-focus-inner,\n[type='button']::-moz-focus-inner,\n[type='reset']::-moz-focus-inner,\n[type='submit']::-moz-focus-inner {\n\tborder-style: none;\n\tpadding: 0;\n}\n\n/**\n * Restore the focus styles unset by the previous rule.\n */\n\nbutton:-moz-focusring,\n[type='button']:-moz-focusring,\n[type='reset']:-moz-focusring,\n[type='submit']:-moz-focusring {\n\toutline: 1px dotted ButtonText;\n}\n\n/**\n * Correct the padding in Firefox.\n */\n\nfieldset {\n\tpadding: 0.35em 0.75em 0.625em;\n}\n\n/**\n * Remove the padding so developers are not caught out when they zero out `fieldset` elements in all browsers.\n */\n\nlegend {\n\tpadding: 0;\n}\n\n/**\n * Add the correct vertical alignment in Chrome and Firefox.\n */\n\nprogress {\n\tvertical-align: baseline;\n}\n\n/**\n * Correct the cursor style of increment and decrement buttons in Safari.\n */\n\n[type='number']::-webkit-inner-spin-button,\n[type='number']::-webkit-outer-spin-button {\n\theight: auto;\n}\n\n/**\n * 1. Correct the odd appearance in Chrome and Safari.\n * 2. Correct the outline style in Safari.\n */\n\n[type='search'] {\n\t-webkit-appearance: textfield; /* 1 */\n\toutline-offset: -2px; /* 2 */\n}\n\n/**\n * Remove the inner padding in Chrome and Safari on macOS.\n */\n\n[type='search']::-webkit-search-decoration {\n\t-webkit-appearance: none;\n}\n\n/**\n * 1. Correct the inability to style clickable types in iOS and Safari.\n * 2. Change font properties to `inherit` in Safari.\n */\n\n::-webkit-file-upload-button {\n\t-webkit-appearance: button; /* 1 */\n\tfont: inherit; /* 2 */\n}\n\n/* Interactive\n   ========================================================================== */\n\n/*\n * Add the correct display in Chrome and Safari.\n */\n\nsummary {\n\tdisplay: list-item;\n}\n"
  },
  {
    "path": "public/default-files/themes/simple/assets/styles/_core/colors.less",
    "content": "//\n//\n//  𝗖 𝗢 𝗟 𝗢 𝗥\n//  v 1.6.3\n//\n//  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n\n//  General\n//  ───────────────────────────────────\n\n@oc-white:         #ffffff;\n@oc-black:         #000000;\n\n\n//  Gray\n//  ───────────────────────────────────\n\n@oc-gray-list: #f8f9fa, #f1f3f5, #e9ecef, #dee2e6, #ced4da, #adb5bd, #868e96, #495057, #343a40, #212529;\n\n@oc-gray-0: extract(@oc-gray-list, 1);\n@oc-gray-1: extract(@oc-gray-list, 2);\n@oc-gray-2: extract(@oc-gray-list, 3);\n@oc-gray-3: extract(@oc-gray-list, 4);\n@oc-gray-4: extract(@oc-gray-list, 5);\n@oc-gray-5: extract(@oc-gray-list, 6);\n@oc-gray-6: extract(@oc-gray-list, 7);\n@oc-gray-7: extract(@oc-gray-list, 8);\n@oc-gray-8: extract(@oc-gray-list, 9);\n@oc-gray-9: extract(@oc-gray-list, 10);\n\n\n//  Red\n//  ───────────────────────────────────\n\n@oc-red-list: #fff5f5, #ffe3e3, #ffc9c9, #ffa8a8, #ff8787, #ff6b6b, #fa5252, #f03e3e, #e03131, #c92a2a;\n\n@oc-red-0: extract(@oc-red-list, 1);\n@oc-red-1: extract(@oc-red-list, 2);\n@oc-red-2: extract(@oc-red-list, 3);\n@oc-red-3: extract(@oc-red-list, 4);\n@oc-red-4: extract(@oc-red-list, 5);\n@oc-red-5: extract(@oc-red-list, 6);\n@oc-red-6: extract(@oc-red-list, 7);\n@oc-red-7: extract(@oc-red-list, 8);\n@oc-red-8: extract(@oc-red-list, 9);\n@oc-red-9: extract(@oc-red-list, 10);\n\n\n//  Pink\n//  ───────────────────────────────────\n\n@oc-pink-list: #fff0f6, #ffdeeb, #fcc2d7, #faa2c1, #f783ac, #f06595, #e64980, #d6336c, #c2255c, #a61e4d;\n\n@oc-pink-0: extract(@oc-pink-list, 1);\n@oc-pink-1: extract(@oc-pink-list, 2);\n@oc-pink-2: extract(@oc-pink-list, 3);\n@oc-pink-3: extract(@oc-pink-list, 4);\n@oc-pink-4: extract(@oc-pink-list, 5);\n@oc-pink-5: extract(@oc-pink-list, 6);\n@oc-pink-6: extract(@oc-pink-list, 7);\n@oc-pink-7: extract(@oc-pink-list, 8);\n@oc-pink-8: extract(@oc-pink-list, 9);\n@oc-pink-9: extract(@oc-pink-list, 10);\n\n\n//  Grape\n//  ───────────────────────────────────\n\n@oc-grape-list: #f8f0fc, #f3d9fa, #eebefa, #e599f7, #da77f2, #cc5de8, #be4bdb, #ae3ec9, #9c36b5, #862e9c;\n\n@oc-grape-0: extract(@oc-grape-list, 1);\n@oc-grape-1: extract(@oc-grape-list, 2);\n@oc-grape-2: extract(@oc-grape-list, 3);\n@oc-grape-3: extract(@oc-grape-list, 4);\n@oc-grape-4: extract(@oc-grape-list, 5);\n@oc-grape-5: extract(@oc-grape-list, 6);\n@oc-grape-6: extract(@oc-grape-list, 7);\n@oc-grape-7: extract(@oc-grape-list, 8);\n@oc-grape-8: extract(@oc-grape-list, 9);\n@oc-grape-9: extract(@oc-grape-list, 10);\n\n\n//  Violet\n//  ───────────────────────────────────\n\n@oc-violet-list: #f3f0ff, #e5dbff, #d0bfff, #b197fc, #9775fa, #845ef7, #7950f2, #7048e8, #6741d9, #5f3dc4;\n\n@oc-violet-0: extract(@oc-violet-list, 1);\n@oc-violet-1: extract(@oc-violet-list, 2);\n@oc-violet-2: extract(@oc-violet-list, 3);\n@oc-violet-3: extract(@oc-violet-list, 4);\n@oc-violet-4: extract(@oc-violet-list, 5);\n@oc-violet-5: extract(@oc-violet-list, 6);\n@oc-violet-6: extract(@oc-violet-list, 7);\n@oc-violet-7: extract(@oc-violet-list, 8);\n@oc-violet-8: extract(@oc-violet-list, 9);\n@oc-violet-9: extract(@oc-violet-list, 10);\n\n\n//  Indigo\n//  ───────────────────────────────────\n\n@oc-indigo-list: #edf2ff, #dbe4ff, #bac8ff, #91a7ff, #748ffc, #5c7cfa, #4c6ef5, #4263eb, #3b5bdb, #364fc7;\n\n@oc-indigo-0: extract(@oc-indigo-list, 1);\n@oc-indigo-1: extract(@oc-indigo-list, 2);\n@oc-indigo-2: extract(@oc-indigo-list, 3);\n@oc-indigo-3: extract(@oc-indigo-list, 4);\n@oc-indigo-4: extract(@oc-indigo-list, 5);\n@oc-indigo-5: extract(@oc-indigo-list, 6);\n@oc-indigo-6: extract(@oc-indigo-list, 7);\n@oc-indigo-7: extract(@oc-indigo-list, 8);\n@oc-indigo-8: extract(@oc-indigo-list, 9);\n@oc-indigo-9: extract(@oc-indigo-list, 10);\n\n\n//  Blue\n//  ───────────────────────────────────\n\n@oc-blue-list: #e7f5ff, #d0ebff, #a5d8ff, #74c0fc, #4dabf7, #339af0, #228be6, #1c7ed6, #1971c2, #1864ab;\n\n@oc-blue-0: extract(@oc-blue-list, 1);\n@oc-blue-1: extract(@oc-blue-list, 2);\n@oc-blue-2: extract(@oc-blue-list, 3);\n@oc-blue-3: extract(@oc-blue-list, 4);\n@oc-blue-4: extract(@oc-blue-list, 5);\n@oc-blue-5: extract(@oc-blue-list, 6);\n@oc-blue-6: extract(@oc-blue-list, 7);\n@oc-blue-7: extract(@oc-blue-list, 8);\n@oc-blue-8: extract(@oc-blue-list, 9);\n@oc-blue-9: extract(@oc-blue-list, 10);\n\n\n//  Cyan\n//  ───────────────────────────────────\n\n@oc-cyan-list: #e3fafc, #c5f6fa, #99e9f2, #66d9e8, #3bc9db, #22b8cf, #15aabf, #1098ad, #0c8599, #0b7285;\n\n@oc-cyan-0: extract(@oc-cyan-list, 1);\n@oc-cyan-1: extract(@oc-cyan-list, 2);\n@oc-cyan-2: extract(@oc-cyan-list, 3);\n@oc-cyan-3: extract(@oc-cyan-list, 4);\n@oc-cyan-4: extract(@oc-cyan-list, 5);\n@oc-cyan-5: extract(@oc-cyan-list, 6);\n@oc-cyan-6: extract(@oc-cyan-list, 7);\n@oc-cyan-7: extract(@oc-cyan-list, 8);\n@oc-cyan-8: extract(@oc-cyan-list, 9);\n@oc-cyan-9: extract(@oc-cyan-list, 10);\n\n\n//  Teal\n//  ───────────────────────────────────\n\n@oc-teal-list: #e6fcf5, #c3fae8, #96f2d7, #63e6be, #38d9a9, #20c997, #12b886, #0ca678, #099268, #087f5b;\n\n@oc-teal-0: extract(@oc-teal-list, 1);\n@oc-teal-1: extract(@oc-teal-list, 2);\n@oc-teal-2: extract(@oc-teal-list, 3);\n@oc-teal-3: extract(@oc-teal-list, 4);\n@oc-teal-4: extract(@oc-teal-list, 5);\n@oc-teal-5: extract(@oc-teal-list, 6);\n@oc-teal-6: extract(@oc-teal-list, 7);\n@oc-teal-7: extract(@oc-teal-list, 8);\n@oc-teal-8: extract(@oc-teal-list, 9);\n@oc-teal-9: extract(@oc-teal-list, 10);\n\n\n//  Green\n//  ───────────────────────────────────\n\n@oc-green-list: #ebfbee, #d3f9d8, #b2f2bb, #8ce99a, #69db7c, #51cf66, #40c057, #37b24d, #2f9e44, #2b8a3e;\n\n@oc-green-0: extract(@oc-green-list, 1);\n@oc-green-1: extract(@oc-green-list, 2);\n@oc-green-2: extract(@oc-green-list, 3);\n@oc-green-3: extract(@oc-green-list, 4);\n@oc-green-4: extract(@oc-green-list, 5);\n@oc-green-5: extract(@oc-green-list, 6);\n@oc-green-6: extract(@oc-green-list, 7);\n@oc-green-7: extract(@oc-green-list, 8);\n@oc-green-8: extract(@oc-green-list, 9);\n@oc-green-9: extract(@oc-green-list, 10);\n\n\n//  Lime\n//  ───────────────────────────────────\n\n@oc-lime-list: #f4fce3, #e9fac8, #d8f5a2, #c0eb75, #a9e34b, #94d82d, #82c91e, #74b816, #66a80f, #5c940d;\n\n@oc-lime-0: extract(@oc-lime-list, 1);\n@oc-lime-1: extract(@oc-lime-list, 2);\n@oc-lime-2: extract(@oc-lime-list, 3);\n@oc-lime-3: extract(@oc-lime-list, 4);\n@oc-lime-4: extract(@oc-lime-list, 5);\n@oc-lime-5: extract(@oc-lime-list, 6);\n@oc-lime-6: extract(@oc-lime-list, 7);\n@oc-lime-7: extract(@oc-lime-list, 8);\n@oc-lime-8: extract(@oc-lime-list, 9);\n@oc-lime-9: extract(@oc-lime-list, 10);\n\n\n//  Yellow\n//  ───────────────────────────────────\n\n@oc-yellow-list: #fff9db, #fff3bf, #ffec99, #ffe066, #ffd43b, #fcc419, #fab005, #f59f00, #f08c00, #e67700;\n\n@oc-yellow-0: extract(@oc-yellow-list, 1);\n@oc-yellow-1: extract(@oc-yellow-list, 2);\n@oc-yellow-2: extract(@oc-yellow-list, 3);\n@oc-yellow-3: extract(@oc-yellow-list, 4);\n@oc-yellow-4: extract(@oc-yellow-list, 5);\n@oc-yellow-5: extract(@oc-yellow-list, 6);\n@oc-yellow-6: extract(@oc-yellow-list, 7);\n@oc-yellow-7: extract(@oc-yellow-list, 8);\n@oc-yellow-8: extract(@oc-yellow-list, 9);\n@oc-yellow-9: extract(@oc-yellow-list, 10);\n\n\n//  Orange\n//  ───────────────────────────────────\n\n@oc-orange-list: #fff4e6, #ffe8cc, #ffd8a8, #ffc078, #ffa94d, #ff922b, #fd7e14, #f76707, #e8590c, #d9480f;\n\n@oc-orange-0: extract(@oc-orange-list, 1);\n@oc-orange-1: extract(@oc-orange-list, 2);\n@oc-orange-2: extract(@oc-orange-list, 3);\n@oc-orange-3: extract(@oc-orange-list, 4);\n@oc-orange-4: extract(@oc-orange-list, 5);\n@oc-orange-5: extract(@oc-orange-list, 6);\n@oc-orange-6: extract(@oc-orange-list, 7);\n@oc-orange-7: extract(@oc-orange-list, 8);\n@oc-orange-8: extract(@oc-orange-list, 9);\n@oc-orange-9: extract(@oc-orange-list, 10);\n"
  },
  {
    "path": "public/default-files/themes/simple/assets/styles/_core/github.less",
    "content": ".hljs {\n  display: block;\n  overflow-x: auto;\n  padding: 0.5em;\n  color: #333;\n  background: #f8f8f8;\n}\n\n.hljs-comment,\n.hljs-quote {\n  color: #998;\n  font-style: italic;\n}\n\n.hljs-keyword,\n.hljs-selector-tag,\n.hljs-subst {\n  color: #333;\n  font-weight: bold;\n}\n\n.hljs-number,\n.hljs-literal,\n.hljs-variable,\n.hljs-template-variable,\n.hljs-tag .hljs-attr {\n  color: #008080;\n}\n\n.hljs-string,\n.hljs-doctag {\n  color: #d14;\n}\n\n.hljs-title,\n.hljs-section,\n.hljs-selector-id {\n  color: #900;\n  font-weight: bold;\n}\n\n.hljs-subst {\n  font-weight: normal;\n}\n\n.hljs-type,\n.hljs-class .hljs-title {\n  color: #458;\n  font-weight: bold;\n}\n\n.hljs-tag,\n.hljs-name,\n.hljs-attribute {\n  color: #000080;\n  font-weight: normal;\n}\n\n.hljs-regexp,\n.hljs-link {\n  color: #009926;\n}\n\n.hljs-symbol,\n.hljs-bullet {\n  color: #990073;\n}\n\n.hljs-built_in,\n.hljs-builtin-name {\n  color: #0086b3;\n}\n\n.hljs-meta {\n  color: #999;\n  font-weight: bold;\n}\n\n.hljs-deletion {\n  background: #fdd;\n}\n\n.hljs-addition {\n  background: #dfd;\n}\n\n.hljs-emphasis {\n  font-style: italic;\n}\n\n.hljs-strong {\n  font-weight: bold;\n}\n"
  },
  {
    "path": "public/default-files/themes/simple/assets/styles/main.less",
    "content": "@import \"_core/colors\";\n@import \"_core/base\";\n@import \"_core/a11y-dark\";\n\nbody {\n  background: @oc-gray-0;\n  color: @oc-gray-7;\n  font-size: 16px;\n}\na {\n  text-decoration: none;\n  color: @oc-gray-7;\n}\n.sidebar {\n  width: 320px;\n  position: fixed;\n  left: 0;\n  top: 0;\n  bottom: 0;\n  background-color: #7c8280;\n  background-size: cover;\n  background-position: center;\n  background-image: url('../media/images/sidebar-bg.jpg');\n  display: flex;\n  flex-direction: column;\n  overflow-y: scroll;\n  .menu-btn {\n    display: none;\n  }\n  .top-container {\n    text-align: center;\n    padding: 48px 16px;\n    flex: 1;\n    .site-logo {\n      width: 80px;\n      height: 80px;\n      border-radius: 50%;\n      border: 2px solid #F1F3F5;\n      box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16);\n    }\n    .site-title {\n      font-size: 24px;\n      padding: 32px 0;\n      color: @oc-gray-3;\n    }\n    .site-nav {\n      display: block;\n      padding: 8px 16px;\n      margin: 16px 0;\n      color: @oc-gray-3;\n      transition: all 0.3s;\n      &:hover {\n        color: @oc-gray-0;\n      }\n    }\n  }\n  .bottom-container {\n    padding: 24px 16px;\n    color: @oc-gray-3;\n    font-size: 12px;\n    .site-description {\n      padding: 16px 0;\n    }\n    a {\n      color: #fff;\n    }\n  }\n}\n.main-container {\n  margin-left: 320px;\n}\n.content-container {\n  max-width: 1064px;\n  margin: 0 auto;\n  padding: 48px 32px;\n}\n\n.post-item {\n  display: flex;\n  padding-bottom: 32px;\n  margin-bottom: 32px;\n  border-bottom: 1px solid @oc-gray-4;\n  &:last-of-type {\n   border-bottom: none;\n  }\n  .left {\n    flex: 1;\n    .post-title {\n      font-size: 24px;\n      transition: all 0.3s;\n      &:hover {\n        color: @oc-gray-9;\n      }\n    }\n    .post-date {\n      font-size: 18px;\n      padding: 24px 0;\n      color: @oc-gray-5;\n    }\n    .post-abstract {\n      line-height: 24px;\n      font-size: 18px;\n      color: @oc-gray-6;\n    }\n  }\n  .right {\n    flex-shrink: 0;\n    margin-left: 24px;\n    width: 38.2%;\n    .feature-container {\n      padding-top: 56.25%;\n      background-size: cover;\n      background-position: center;\n      border-radius: 3px;\n      box-shadow: 0 2px 5px rgba(0,0,25,0.1), 0 5px 75px 1px rgba(0,0,50,0.2);\n    }\n  }\n}\n\n.pagination-container {\n  display: flex;\n  justify-content: space-between;\n  .prev, .next {\n    display: inline-block;\n    padding: 8px 16px;\n    background: #fff;\n    border: 1px solid @oc-gray-1;\n    border-radius: 2px;\n    transition: all 0.3s;\n    &:hover {\n      transform: translateY(-3px);\n      border: 1px solid @oc-gray-3;\n    }\n  }\n}\n\n.post-detail {\n  max-width: 720px;\n  margin: 0 auto;\n  .feature-container {\n    padding-top: 56.25%;\n    background-size: cover;\n    background-position: center;\n    border-radius: 3px;\n    box-shadow: 0 2px 5px rgba(0,0,25,0.1), 0 5px 75px 1px rgba(0,0,50,0.2);\n    margin-bottom: 24px;\n  }\n  .post-title {\n    font-size: 40px;\n  }\n  .post-date {\n    font-size: 18px;\n    padding: 24px 0;\n    color: @oc-gray-5;\n  }\n  .post-content {\n    h1, h2, h3, h4, h5, h6 {\n      margin: 16px 0;\n      color: @oc-gray-8;\n    }\n\n    a {\n      color: @oc-indigo-6;\n      border-bottom: 1px dotted @oc-indigo-6;\n      transition: all 0.3s;\n      &:hover {\n        color: @oc-indigo-8;\n        border-bottom: 1px dotted @oc-indigo-8;\n      }\n    }\n    img {\n      display: block;\n      box-shadow: 0 2px 5px rgba(0,0,25,0.1), 0 5px 75px 1px rgba(0,0,50,0.2);\n      max-width: 100%;\n      border-radius: 2px;\n      margin: 24px auto;\n    }\n\n    p {\n      line-height: 1.725;\n      margin-bottom: 24px;\n      font-size: 18px;\n      color: @oc-gray-7;\n    }\n\n    p, ul, ol {\n      code {\n        padding: 0 3px;\n        margin: 0 2px;\n        // background: rgba(195,195,195,0.41);\n        background: @oc-gray-2;\n        font-size: 0.9em;\n        border-radius: 2px;\n        border: 1px solid @oc-gray-3;\n        display: inline-block;\n        line-height: 1.5;\n        color: @oc-gray-6;\n      }\n    }\n\n    blockquote {\n      background: @oc-gray-2;\n      padding: 16px;\n      border-left: 3px solid @oc-violet-7;\n      border-radius: 2px;\n      margin-bottom: 16px;\n      p {\n        color: @oc-gray-6;\n        margin-bottom: 0;\n      }\n    }\n\n    pre {\n      margin-bottom: 16px;\n      code {\n        font-size: 14px;\n        font-family: 'Source Code Pro', Consolas, Menlo, Monaco, 'Courier New', monospace;\n        padding: 2em 1em 1em;\n        border-radius: 5px;\n        line-height: 1.375;\n        position: relative;\n        background: @oc-gray-8;\n        color: @oc-gray-1;\n        display: block;\n        &:after {\n          content: 'CODE';\n          display: block;\n          position: absolute;\n          left: 8px;\n          top: 4px;\n          font-size: 14px;\n          font-weight: bold;\n          color: @oc-gray-7;\n        }\n      }\n    }\n\n    table {\n      border-collapse: collapse;\n      margin: 1rem 0;\n      display: block;\n      overflow-x: auto;\n    }\n    tr {\n      border-top: 1px solid #dfe2e5;\n    }\n    td, th {\n      border: 1px solid #dfe2e5;\n      padding: .6em 1em;\n    }\n\n    ul, ol {\n      color: var(--c-base-blacklight);\n      padding-left: 24px;\n      line-height: 1.725;\n      margin-bottom: 16px;\n    }\n\n    strong {\n      font-weight: bolder;\n    }\n\n    em {\n      color: @oc-gray-6;\n    }\n\n    hr {\n      height: 0;\n      border: 2px solid #efefef;\n      margin-bottom: 24px;\n    }\n  }\n}\n\n.tag {\n  display: inline-block;\n  font-size: 14px;\n  padding: 8px 16px;\n  border-radius: 16px;\n  background: @oc-gray-2;\n  color: @oc-gray-6;\n  margin: 16px 16px 16px 0;\n  transition: all 0.3s;\n  &:hover {\n    background: @oc-gray-3;\n    color: @oc-gray-7;\n    transform: translateY(-3px);\n  }\n}\n\n.next-post {\n  border-top: 1px solid @oc-gray-4;\n  border-bottom: 1px solid @oc-gray-4;\n  padding: 24px 0;\n  margin: 32px 0;\n  .post-title {\n    font-size: 24px;\n  }\n  .next {\n    color: @oc-gray-4;\n    margin-bottom: 16px;\n  }\n}\n\n.archives-title, .tag-list-title, .current-tag {\n  color: @oc-gray-7;\n  padding-bottom: 48px;\n  font-size: 32px;\n}\n\n.archives-container {\n  padding-bottom: 32px;\n  .year {\n    font-size: 16px;\n    padding-bottom: 16px;\n    border-bottom: 1px solid @oc-gray-4;\n    margin: 16px 0;\n    color: @oc-gray-6;\n  }\n  .post {\n    padding-bottom: 16px;\n    .post-title {\n      font-size: 18px;\n      transition: all 0.3s;\n      &:hover {\n        color: @oc-gray-9;\n      }\n    }\n  }\n}\n\n// Mobile ----------------------- //\n\n@media (max-width: 800px) {\n  .sidebar {\n    position: relative;\n    width: 100% !important;\n    height: 80px;\n    overflow: hidden;\n    transition: height 0.382s ease-in-out;\n    &.full-height {\n      height: 100vh;\n    }\n    .sidebar-content {\n      position: absolute;\n      top: 0;\n      bottom: 0;\n      left: 0;\n      right: 0;\n    }\n    .top-header-container {\n      display: flex;\n      justify-content: space-between;\n      margin-top: 16px;\n      .menu-btn {\n        display: block;\n        position: relative;\n        width: 48px;\n        height: 48px;\n        .line {\n          width: 32px;\n          height: 2px;\n          background: @oc-gray-2;\n          border-radius: 2px;\n          position: absolute;\n          right: 0;\n          top: 23px;\n        }\n        &:before, &:after {\n          content: '';\n          display: block;\n          width: 32px;\n          height: 2px;\n          background: @oc-gray-2;\n          border-radius: 2px;\n          position: absolute;\n          right: 0;\n        }\n        &:before {\n          top: 12px;\n        }\n        &:after {\n          bottom: 12px;\n        }\n      }\n    }\n    .top-container {\n      text-align: left;\n      padding: 0 16px;\n      .site-title-container {\n        display: flex;\n        align-items: center;\n      }\n      .site-logo {\n        width: 48px;\n        height: 48px;\n      }\n      .site-title {\n        display: inline;\n        padding: 0 8px;\n        font-size: 18px;\n      }\n    }\n  }\n\n  .main-container {\n    margin-left: 0 !important;;\n  }\n  .content-container {\n    padding: 32px 16px;\n  }\n\n  .post-item {\n    flex-direction: column-reverse;\n    padding-bottom: 16px;\n    margin-bottom: 16x;\n    .right {\n      width: 100%;\n      margin-left: 0;\n      margin-bottom: 16px;\n    }\n    .left {\n      .post-date {\n        font-size: 16px;\n        padding: 16px 0;\n      }\n      .post-abstract {\n        font-size: 16px;\n      }\n    }\n  }\n\n  .pagination-container {\n    .prev, .next {\n      &:hover {\n        transform: translateY(0px);\n      }\n    }\n  }\n\n  .post-detail {\n    .post-title {\n      font-size: 28px;\n    }\n    .post-date {\n      font-size: 16px;\n      padding: 16px 0;\n    }\n    .feature-container {\n      margin-bottom: 16px;\n    }\n    .post-content {\n      p {\n        font-size: 16px;\n      }\n    }\n  }\n  .next-post {\n    margin: 24px 0;\n    padding: 16px 0;\n  }\n\n  .archives-title, .tag-list-title, .current-tag {\n    font-size: 28px;\n    padding-bottom: 32px;\n  }\n\n  .tag {\n    margin: 8px 8px 8px 0;\n    &:hover {\n      transform: translateY(0px);\n    }\n  }\n}\n\n.social-container {\n  .social-link {\n    color: #dee2e6;\n    font-size: 16px;\n    margin: 4px 8px;\n  }\n}\n"
  },
  {
    "path": "public/default-files/themes/simple/config.json",
    "content": "{\n  \"name\": \"Simple\",\n  \"version\": \"1.1.0\",\n  \"author\": \"EryouHao\",\n  \"customConfig\": [\n    {\n      \"name\": \"sidebarWidth\",\n      \"label\": \"菜单栏宽度\",\n      \"group\": \"布局\",\n      \"value\": \"320px\",\n      \"type\": \"input\",\n      \"note\": \"可填像素类型（如：320px）或百分比类型（如：38.2%）\"\n    },\n    {\n      \"name\": \"featureBorderRadius\",\n      \"label\": \"封面图圆角\",\n      \"group\": \"布局\",\n      \"value\": \"3px\",\n      \"type\": \"input\",\n      \"note\": \"像素类型（如：3px）\"\n    },\n    {\n      \"name\": \"menuColor\",\n      \"label\": \"菜单颜色\",\n      \"group\": \"颜色\",\n      \"value\": \"#dee2e6\",\n      \"type\": \"input\",\n      \"card\": \"color\",\n      \"note\": \"颜色字符串:（如：#EEEEEE、rgba(255, 255, 255, 0.9)）\"\n    },\n    {\n      \"name\": \"contentBgColor\",\n      \"label\": \"内容区背景色\",\n      \"group\": \"颜色\",\n      \"value\": \"#f8f9fa\",\n      \"type\": \"input\",\n      \"card\": \"color\",\n      \"note\": \"颜色字符串:（如：#EEEEEE、rgba(255, 255, 255, 0.9)）\"\n    },\n    {\n      \"name\": \"renderKaTeX\",\n      \"label\": \"是否渲染 KaTeX 公式\",\n      \"group\": \"渲染\",\n      \"value\": false,\n      \"type\": \"switch\"\n    },\n    {\n      \"name\": \"renderCode\",\n      \"label\": \"是否渲染代码高亮\",\n      \"group\": \"渲染\",\n      \"value\": false,\n      \"type\": \"switch\"\n    },\n    {\n      \"name\": \"github\",\n      \"label\": \"Github\",\n      \"group\": \"社交\",\n      \"value\": \"\",\n      \"type\": \"input\",\n      \"note\": \"链接地址\"\n    },\n    {\n      \"name\": \"twitter\",\n      \"label\": \"Twitter\",\n      \"group\": \"社交\",\n      \"value\": \"\",\n      \"type\": \"input\",\n      \"note\": \"链接地址\"\n    },\n    {\n      \"name\": \"weibo\",\n      \"label\": \"微博\",\n      \"group\": \"社交\",\n      \"value\": \"\",\n      \"type\": \"input\",\n      \"note\": \"链接地址\"\n    },\n    {\n      \"name\": \"zhihu\",\n      \"label\": \"知乎\",\n      \"group\": \"社交\",\n      \"value\": \"\",\n      \"type\": \"input\",\n      \"note\": \"链接地址\"\n    },\n    {\n      \"name\": \"facebook\",\n      \"label\": \"Facebook\",\n      \"group\": \"社交\",\n      \"value\": \"\",\n      \"type\": \"input\",\n      \"note\": \"链接地址\"\n    },\n    {\n      \"name\": \"customCss\",\n      \"label\": \"自定义CSS\",\n      \"group\": \"自定义样式\",\n      \"value\": \"\",\n      \"type\": \"textarea\",\n      \"note\": \"\"\n    },\n    {\n      \"name\": \"ga\",\n      \"label\": \"跟踪 ID\",\n      \"group\": \"谷歌统计\",\n      \"value\": \"\",\n      \"type\": \"input\",\n      \"note\": \"UA-xxxxxxxxx-x\"\n    }\n  ]\n}\n"
  },
  {
    "path": "public/default-files/themes/simple/style-override.js",
    "content": "const generateOverride = (params = {}) => {\n  let result = ''\n\n  // 侧边栏宽度 - sidebarWidth\n  if (params.sidebarWidth && params.sidebarWidth !== '320px') {\n    result += `\n      .sidebar {\n        width: ${params.sidebarWidth};\n      }\n      .main-container {\n        margin-left: ${params.sidebarWidth};\n      }\n    `\n  }\n\n  // 菜单颜色 - menuColor\n  if (params.menuColor && params.menuColor !== '#dee2e6') {\n    result += `\n      .sidebar .top-container .site-nav {\n        color: ${params.menuColor};\n      }\n    `\n  }\n\n  // 封面图圆角 - featureBorderRadius\n  if (params.featureBorderRadius && params.featureBorderRadius !== '3px') {\n    result += `\n      .post-item .right .feature-container {\n        border-radius: ${params.featureBorderRadius};\n      }\n    `\n  }\n\n  // 内容区背景色 - contentBgColor\n  if (params.contentBgColor && params.contentBgColor !== '#f8f9fa') {\n    result += `\n      body {\n        background: ${params.contentBgColor};\n      }\n    `\n  }\n\n  if (params.customCss) {\n    result += `\n      ${params.customCss}\n    `\n  }\n\n\n  console.log('result', result)\n\n  return result\n}\n\nmodule.exports = generateOverride\n"
  },
  {
    "path": "public/default-files/themes/simple/templates/_blocks/head.ejs",
    "content": "<meta charset=\"utf-8\" >\n\n<title><%= siteTitle %></title>\n\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no\">\n\n<link rel=\"stylesheet\" href=\"https://use.fontawesome.com/releases/v5.7.2/css/all.css\" integrity=\"sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr\" crossorigin=\"anonymous\">\n<link rel=\"shortcut icon\" href=\"<%= themeConfig.domain %>/favicon.ico?v=<%= site.utils.now %>\">\n<link rel=\"stylesheet\" href=\"<%= themeConfig.domain %>/styles/main.css\">\n\n<% if (typeof commentSetting !== 'undefined' && commentSetting.showComment) { %>\n  <% if (commentSetting.commentPlatform === 'gitalk') { %>\n    <link rel=\"stylesheet\" href=\"https://unpkg.com/gitalk/dist/gitalk.css\" />\n  <% } %>\n\n  <% if (commentSetting.commentPlatform === 'disqus') { %>\n    <link rel=\"stylesheet\" href=\"https://unpkg.com/disqusjs@1.1/dist/disqusjs.css\" />\n  <% } %>\n<% } %>\n\n<link rel=\"stylesheet\" href=\"https://unpkg.com/aos@next/dist/aos.css\" />\n<script src=\"https://cdn.jsdelivr.net/npm/vue/dist/vue.js\"></script>\n\n<% if (site.customConfig.ga) { %>\n<script async src=\"https://www.googletagmanager.com/gtag/js?id=<%= site.customConfig.ga %>\"></script>\n<script>\n  window.dataLayer = window.dataLayer || [];\n  function gtag(){dataLayer.push(arguments);}\n  gtag('js', new Date());\n\n  gtag('config', '<%= site.customConfig.ga %>');\n</script>\n<% } %>\n"
  },
  {
    "path": "public/default-files/themes/simple/templates/_blocks/pagination.ejs",
    "content": "<div class=\"pagination-container\">\n  <% if (pagination.prev) { %>\n    <a href=\"<%= pagination.prev %>\" class=\"prev\"><i class=\"icon-arrow-ios-back-outline\"></i> 上一页</a>\n  <% } %>\n  <% if (pagination.next) { %>\n    <a href=\"<%= pagination.next %>\" class=\"next\">下一页 <i class=\"icon-arrow-ios-forward-outline\"></i></a>\n  <% } %>\n</div>\n"
  },
  {
    "path": "public/default-files/themes/simple/templates/_blocks/scripts.ejs",
    "content": "<script src=\"https://unpkg.com/aos@next/dist/aos.js\"></script>\n<script type=\"application/javascript\">\n\nAOS.init();\n\nvar app = new Vue({\n  el: '#app',\n  data: {\n    menuVisible: false,\n  },\n})\n\n</script>\n\n<% if (site.customConfig.renderCode) { %>\n  <script src=\"//cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.5.1/build/highlight.min.js\"></script>\n  <script>\n    hljs.initHighlightingOnLoad()\n  </script>\n<% } %>\n\n<% if (typeof commentSetting !== 'undefined' && commentSetting.showComment) { %>\n\n  <% if (commentSetting.commentPlatform === 'gitalk') { %>\n    <script src=\"https://unpkg.com/gitalk/dist/gitalk.min.js\"></script>\n    <script>\n\n      var gitalk = new Gitalk({\n        clientID: '<%= commentSetting.gitalkSetting.clientId %>',\n        clientSecret: '<%= commentSetting.gitalkSetting.clientSecret %>',\n        repo: '<%= commentSetting.gitalkSetting.repository %>',\n        owner: '<%= commentSetting.gitalkSetting.owner %>',\n        admin: ['<%= commentSetting.gitalkSetting.owner %>'],\n        id: (location.pathname).substring(0, 49),      // Ensure uniqueness and length less than 50\n        distractionFreeMode: false  // Facebook-like distraction free mode\n      })\n\n      gitalk.render('gitalk-container')\n\n    </script>\n  <% } %>\n\n  <% if (commentSetting.commentPlatform === 'disqus') { %>\n    <script src=\"https://unpkg.com/disqusjs@1.1/dist/disqus.js\"></script>\n    <script>\n\n    var options = {\n      shortname: '<%= commentSetting.disqusSetting.shortname %>',\n      apikey: '<%= commentSetting.disqusSetting.apikey %>',\n    }\n    if ('<%= commentSetting.disqusSetting.api %>') {\n      options.api = '<%= commentSetting.disqusSetting.api %>'\n    }\n    var dsqjs = new DisqusJS(options)\n\n    </script>\n  <% } %>\n\n<% } %>\n\n"
  },
  {
    "path": "public/default-files/themes/simple/templates/_blocks/sidebar.ejs",
    "content": "<div class=\"sidebar\" :class=\"{ 'full-height': menuVisible }\">\n  <div class=\"top-container\" data-aos=\"fade-right\">\n    <div class=\"top-header-container\">\n      <a class=\"site-title-container\" href=\"<%= themeConfig.domain %>\">\n        <img src=\"<%= themeConfig.domain %>/images/avatar.png?v=<%= site.utils.now %>\" class=\"site-logo\">\n        <h1 class=\"site-title\"><%= themeConfig.siteName %></h1>\n      </a>\n      <div class=\"menu-btn\" @click=\"menuVisible = !menuVisible\">\n        <div class=\"line\"></div>\n      </div>\n    </div>\n    <div>\n      <% menus.forEach(function(menu) { %>\n        <% if (menu.openType === 'External') { %>\n          <a href=\"<%= menu.link %>\" class=\"site-nav\" target=\"_blank\">\n            <%= menu.name %>\n          </a>\n        <% } else { %>\n          <a href=\"<%= menu.link %>\" class=\"site-nav\">\n            <%= menu.name %>\n          </a>\n        <% } %>\n      <% }); %>\n    </div>\n  </div>\n  <div class=\"bottom-container\" data-aos=\"flip-up\" data-aos-offset=\"0\">\n    <div class=\"social-container\">\n      <% ['github', 'twitter', 'weibo', 'zhihu', 'facebook'].forEach((item) => { %>\n        <% if (site.customConfig[item]) { %>\n          <a class=\"social-link\" href=\"<%= site.customConfig[item] %>\" target=\"_blank\">\n            <i class=\"fab fa-<%= item %>\"></i>\n          </a>\n        <% } %>\n      <% }) %>\n    </div>\n    <div class=\"site-description\">\n      <%- themeConfig.siteDescription %>\n    </div>\n    <div class=\"site-footer\">\n      <%- themeConfig.footerInfo %> | <a class=\"rss\" href=\"<%= themeConfig.domain %>/atom.xml\" target=\"_blank\">RSS</a>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "public/default-files/themes/simple/templates/archives.ejs",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <%- include('./_blocks/head', { siteTitle: `文章归档 | ${themeConfig.siteName}` }) %>\n    <meta name=\"description\" content=\"<%= themeConfig.siteDescription %>\">\n  </head>\n  <body>\n    <div id=\"app\" class=\"main\">\n\n      <%- include('./_blocks/sidebar') %>\n\n      <div class=\"main-container\">\n        <div class=\"content-container\" data-aos=\"fade-up\">\n          <% let years = []; posts.forEach((item) => { const year = item.date.substring(0, 4); if (!years.includes(year)) { years.push(year); } }); %>\n\n          <h2 class=\"archives-title\">文章归档</h2>\n          <div class=\"archives-container\">\n            <% years.forEach(function(year) { %>\n              <h2 class=\"year\" data-aos=\"fade-in\" data-aos-delay=\"500\"><%- year %></h2>\n              <% posts.forEach(function(post) { %>\n                <%if (post.date.indexOf(year) !== -1) { %>\n                  <article class=\"post\">\n                    <a href=\"<%= post.link %>\">\n                      <h2 class=\"post-title\">\n                        <%= post.title %>\n                      </h2>\n                    </a>\n                  </article>\n                <% } %>\n              <% }); %>\n            <% }); %>\n          </div>\n\n          <%- include('./_blocks/pagination') %>\n        </div>\n      </div>\n    </div>\n\n    <%- include('./_blocks/scripts') %>\n  </body>\n</html>\n"
  },
  {
    "path": "public/default-files/themes/simple/templates/index.ejs",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <%- include('./_blocks/head', { siteTitle: themeConfig.siteName }) %>\n    <meta name=\"description\" content=\"<%= themeConfig.siteDescription %>\">\n    <% if (site.customConfig.renderKaTeX) { %>\n      <link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.10.0/katex.min.css\">\n    <% } %>\n  </head>\n  <body>\n    <div id=\"app\" class=\"main\">\n\n      <%- include('./_blocks/sidebar') %>\n\n      <div class=\"main-container\">\n        <div class=\"content-container\" data-aos=\"fade-up\">\n          <% posts.forEach(function(post) { %>\n            <article class=\"post-item\">\n              <div class=\"left\">\n                <a href=\"<%= post.link %>\">\n                  <h2 class=\"post-title\"><%= post.title %></h2>\n                </a>\n                <div class=\"post-date\">\n                  <%= post.dateFormat %>\n                </div>\n                <div class=\"post-abstract\">\n                  <%- post.abstract %>\n                </div>\n              </div>\n              <% if (themeConfig.showFeatureImage && post.feature) { %>\n                <a class=\"right\" href=\"<%= post.link %>\">\n                  <div class=\"feature-container\" style=\"background-image: url('<%= post.feature %>')\">\n                  </div>\n                </a>\n              <% } %>\n            </article>\n          <% }); %>\n\n          <%- include('./_blocks/pagination') %>\n        </div>\n      </div>\n    </div>\n\n    <%- include('./_blocks/scripts') %>\n  </body>\n</html>\n"
  },
  {
    "path": "public/default-files/themes/simple/templates/post.ejs",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <%- include('./_blocks/head', { siteTitle: `${post.title} | ${themeConfig.siteName}` }) %>\n    <meta name=\"description\" content=\"<%- post.description %>\" />\n    <meta name=\"keywords\" content=\"<%- post.tags.map(tag => tag.name).join(',') %>\" />\n  </head>\n  <body>\n    <div id=\"app\" class=\"main\">\n\n      <%- include('./_blocks/sidebar') %>\n\n      <div class=\"main-container\">\n        <div class=\"content-container\" data-aos=\"fade-up\">\n          <div class=\"post-detail\">\n            <h2 class=\"post-title\"><%= post.title %></h2>\n            <div class=\"post-date\"><%= post.dateFormat %></div>\n            <% if (post.feature) { %>\n              <div class=\"feature-container\" style=\"background-image: url('<%= post.feature %>')\">\n              </div>\n            <% } %>\n            <div class=\"post-content\" v-pre>\n              <%- post.content %>\n            </div>\n            <% if (post.tags.length > 0) { %>\n              <div class=\"tag-container\">\n                <% post.tags.forEach(function(tag, index) { %>\n                  <a href=\"<%= tag.link %>\" class=\"tag\">\n                    <%= tag.name %>\n                  </a>\n                <% }); %>\n              </div>\n            <% } %>\n            <% if (post.nextPost && !post.hideInList) { %>\n              <div class=\"next-post\">\n                <div class=\"next\">下一篇</div>\n                <a href=\"<%= post.nextPost.link %>\">\n                  <h3 class=\"post-title\">\n                    <%= post.nextPost.title %>\n                  </h3>\n                </a>\n              </div>\n            <% } %>\n\n            <% if (typeof commentSetting !== 'undefined' && commentSetting.showComment) { %>\n              <% if (commentSetting.commentPlatform === 'gitalk') { %>\n                <div id=\"gitalk-container\" data-aos=\"fade-in\"></div>\n              <% } %>\n\n              <% if (commentSetting.commentPlatform === 'disqus') { %>\n                <div id=\"disqus_thread\" data-aos=\"fade-in\"></div>\n              <% } %>\n            <% } %>\n\n          </div>\n\n        </div>\n      </div>\n    </div>\n\n    <%- include('./_blocks/scripts') %>\n  </body>\n</html>\n"
  },
  {
    "path": "public/default-files/themes/simple/templates/tag.ejs",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <%- include('./_blocks/head', { siteTitle: `${tag.name} | ${themeConfig.siteName}` }) %>\n    <meta name=\"description\" content=\"<%= themeConfig.siteDescription %>\">\n  </head>\n  <body>\n    <div id=\"app\" class=\"main\">\n\n      <%- include('./_blocks/sidebar') %>\n\n      <div class=\"main-container\">\n        <div class=\"content-container\" data-aos=\"fade-up\">\n          <h2 class=\"current-tag\">标签: <%= tag.name %></h2>\n          <% posts.forEach(function(post) { %>\n            <article class=\"post-item\">\n              <div class=\"left\">\n                <a href=\"<%= post.link %>\">\n                  <h2 class=\"post-title\"><%= post.title %></h2>\n                </a>\n                <div class=\"post-date\">\n                  <%= post.dateFormat %>\n                </div>\n                <div class=\"post-abstract\">\n                  <%- post.abstract %>\n                </div>\n              </div>\n              <% if (themeConfig.showFeatureImage && post.feature) { %>\n                <a class=\"right\" href=\"<%= post.link %>\">\n                  <div class=\"feature-container\" style=\"background-image: url('<%= post.feature %>')\">\n                  </div>\n                </a>\n              <% } %>\n            </article>\n          <% }); %>\n\n          <%- include('./_blocks/pagination') %>\n        </div>\n      </div>\n    </div>\n\n    <%- include('./_blocks/scripts') %>\n  </body>\n</html>\n"
  },
  {
    "path": "public/default-files/themes/simple/templates/tags.ejs",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <%- include('./_blocks/head', { siteTitle: `标签列表 | ${themeConfig.siteName}` }) %>\n    <meta name=\"description\" content=\"<%= themeConfig.siteDescription %>\">\n  </head>\n  <body>\n    <div id=\"app\" class=\"main\">\n\n      <%- include('./_blocks/sidebar') %>\n\n      <div class=\"main-container\">\n        <div class=\"content-container\" data-aos=\"fade-up\">\n          <h2 class=\"tag-list-title\">标签列表</h2>\n          <div class=\"tags-container\">\n            <% tags.forEach((tag) => { %>\n              <a class=\"tag\" href=\"<%= tag.link %>\"><%= tag.name %></a>\n            <% }); %>\n          </div>\n        </div>\n      </div>\n    </div>\n\n    <%- include('./_blocks/scripts') %>\n  </body>\n</html>\n"
  },
  {
    "path": "public/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n    <meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n    <link rel=\"icon\" href=\"<%= BASE_URL %>favicon.ico\">\n    <title>Gridea</title>\n    <style>\n      .landing {\n        position: fixed;\n        top: 0;\n        left: 0;\n        right: 0;\n        bottom: 0;\n        display: flex;\n        flex-direction: column;\n        justify-content: center;\n        align-items: center;\n        background: #fff;\n      }\n\n      .loading-inner--fadeout {\n          -webkit-animation: .2s ease 0s normal forwards 1 loading-inner--fadeout;\n          animation: .2s ease 0s normal forwards 1 loading-inner--fadeout;\n          opacity: 1\n      }\n\n      @-webkit-keyframes loading-inner--fadeout {\n          0% {\n              opacity: 1\n          }\n\n          100% {\n              opacity: 0\n          }\n      }\n\n      @keyframes loading-inner--fadeout {\n          0% {\n              opacity: 1\n          }\n\n          100% {\n              opacity: 0\n          }\n      }\n\n      .loading-inner--loading {\n          -webkit-animation: .2s ease .8s normal forwards 1 loading-inner--loading;\n          animation: .2s ease .8s normal forwards 1 loading-inner--loading;\n          opacity: 0\n      }\n\n      @-webkit-keyframes loading-inner--loading {\n          0% {\n              opacity: 0\n          }\n\n          100% {\n              opacity: 1\n          }\n      }\n\n      @keyframes loading-inner--loading {\n          0% {\n              opacity: 0\n          }\n\n          100% {\n              opacity: 1\n          }\n      }\n\n      .loading-inner--fadein {\n          -webkit-animation: .2s ease 0s normal forwards 1 loading-inner--fadein;\n          animation: .2s ease 0s normal forwards 1 loading-inner--fadein;\n          opacity: 1;\n      }\n\n      @-webkit-keyframes loading-inner--fadein {\n          0% {\n              opacity: 0\n          }\n\n          100% {\n              opacity: 1\n          }\n      }\n\n      @keyframes loading-inner--fadein {\n          0% {\n              opacity: 0\n          }\n\n          100% {\n              opacity: 1\n          }\n      }\n\n      .pulse-spinner {\n          position: relative;\n          width: 24px;\n          height: 24px;\n          margin: 100px auto;\n      }\n\n      .pulse-spinner__nib {\n          position: absolute;\n          left: 50%;\n          top: 0;\n          margin-left: -1.5px;\n          width: 2px;\n          height: 6px;\n          border-radius: 1.5px;\n          -webkit-transform-origin: center 12px;\n          transform-origin: center 12px;\n          -webkit-animation-name: nib;\n          animation-name: nib;\n          animation-direction: reverse;\n          -webkit-animation-duration: 1s;\n          animation-duration: 1s;\n          -webkit-animation-iteration-count: infinite;\n          animation-iteration-count: infinite;\n          -webkit-animation-timing-function: cubic-bezier(-webkit-calc(1 / 3),0,-webkit-calc(2 / 3),-webkit-calc(1 / 3));\n          animation-timing-function: cubic-bezier(calc(1 / 3),0,calc(2 / 3),calc(1 / 3));\n          background: #000\n      }\n\n      .pulse-spinner__nib-0 {\n          -webkit-animation-delay: -1s;\n          animation-delay: -1s;\n          -webkit-transform: rotate(0);\n          transform: rotate(0)\n      }\n\n      .pulse-spinner__nib-1 {\n          -webkit-animation-delay: -.91667s;\n          animation-delay: -.91667s;\n          -webkit-transform: rotate(30deg);\n          transform: rotate(30deg)\n      }\n\n      .pulse-spinner__nib-2 {\n          -webkit-animation-delay: -.83333s;\n          animation-delay: -.83333s;\n          -webkit-transform: rotate(60deg);\n          transform: rotate(60deg)\n      }\n\n      .pulse-spinner__nib-3 {\n          -webkit-animation-delay: -.75s;\n          animation-delay: -.75s;\n          -webkit-transform: rotate(90deg);\n          transform: rotate(90deg)\n      }\n\n      .pulse-spinner__nib-4 {\n          -webkit-animation-delay: -.66667s;\n          animation-delay: -.66667s;\n          -webkit-transform: rotate(120deg);\n          transform: rotate(120deg)\n      }\n\n      .pulse-spinner__nib-5 {\n          -webkit-animation-delay: -.58333s;\n          animation-delay: -.58333s;\n          -webkit-transform: rotate(150deg);\n          transform: rotate(150deg)\n      }\n\n      .pulse-spinner__nib-6 {\n          -webkit-animation-delay: -.5s;\n          animation-delay: -.5s;\n          -webkit-transform: rotate(180deg);\n          transform: rotate(180deg)\n      }\n\n      .pulse-spinner__nib-7 {\n          -webkit-animation-delay: -.41667s;\n          animation-delay: -.41667s;\n          -webkit-transform: rotate(210deg);\n          transform: rotate(210deg)\n      }\n\n      .pulse-spinner__nib-8 {\n          -webkit-animation-delay: -.33333s;\n          animation-delay: -.33333s;\n          -webkit-transform: rotate(240deg);\n          transform: rotate(240deg)\n      }\n\n      .pulse-spinner__nib-9 {\n          -webkit-animation-delay: -.25s;\n          animation-delay: -.25s;\n          -webkit-transform: rotate(270deg);\n          transform: rotate(270deg)\n      }\n\n      .pulse-spinner__nib-10 {\n          -webkit-animation-delay: -.16667s;\n          animation-delay: -.16667s;\n          -webkit-transform: rotate(300deg);\n          transform: rotate(300deg)\n      }\n\n      .pulse-spinner__nib-11 {\n          -webkit-animation-delay: -83.33ms;\n          animation-delay: -83.33ms;\n          -webkit-transform: rotate(330deg);\n          transform: rotate(330deg)\n      }\n\n      @-webkit-keyframes nib {\n          0% {\n              opacity: .16863\n          }\n\n          100% {\n              opacity: 1\n          }\n      }\n\n      @keyframes nib {\n          0% {\n              opacity: .16863\n          }\n\n          100% {\n              opacity: 1\n          }\n      }\n\n    </style>\n    <script src=\"https://cdn.headwayapp.co/widget.js\"></script>\n  </head>\n  <body>\n    <noscript>\n      <strong>We're sorry but Gridea doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>\n    </noscript>\n    <div id=\"app\">\n      <div id=\"landing\" class=\"landing\">\n        <div class=\"pulse-spinner\">\n          <div class=\"pulse-spinner__nib pulse-spinner__nib-0\"></div>\n          <div class=\"pulse-spinner__nib pulse-spinner__nib-1\"></div>\n          <div class=\"pulse-spinner__nib pulse-spinner__nib-2\"></div>\n          <div class=\"pulse-spinner__nib pulse-spinner__nib-3\"></div>\n          <div class=\"pulse-spinner__nib pulse-spinner__nib-4\"></div>\n          <div class=\"pulse-spinner__nib pulse-spinner__nib-5\"></div>\n          <div class=\"pulse-spinner__nib pulse-spinner__nib-6\"></div>\n          <div class=\"pulse-spinner__nib pulse-spinner__nib-7\"></div>\n          <div class=\"pulse-spinner__nib pulse-spinner__nib-8\"></div>\n          <div class=\"pulse-spinner__nib pulse-spinner__nib-9\"></div>\n          <div class=\"pulse-spinner__nib pulse-spinner__nib-10\"></div>\n          <div class=\"pulse-spinner__nib pulse-spinner__nib-11\"></div>\n        </div>\n      </div>\n    </div>\n    <!-- built files will be auto injected -->\n  </body>\n</html>\n"
  },
  {
    "path": "src/App.vue",
    "content": "<template>\n  <div id=\"app\">\n    <router-view />\n  </div>\n</template>\n\n<script lang=\"ts\">\nimport { shell } from 'electron'\nimport Vue from 'vue'\nimport Component from 'vue-class-component'\n\n@Component\nexport default class App extends Vue {\n  mounted() {\n    document.addEventListener('click', (event: any) => {\n      if (event.target.tagName === 'A' && event.target.href.startsWith('http')) {\n        event.preventDefault()\n        shell.openExternal(event.target.href)\n      }\n    })\n  }\n}\n</script>\n\n<style lang=\"less\" scoped>\n\n</style>\n\n\n<style>\n\n  /* Global CSS */\n  body {\n    background: #fff;\n    color: #555;\n  }\n\n  ::-webkit-scrollbar{\n    width: 4px;\n    height: 4px;\n    border-radius: 4px;\n    background-color: #fff;\n\n  }\n    /*滚动条两端的箭头*/\n  ::-webkit-scrollbar-button{\n    display: none;\n  }\n  ::-webkit-scroll-track{\n    display: none;\n  }\n  ::-webkit-scrollbar-track-piece {\n    display: none;\n  }\n\n  ::-webkit-scrollbar-thumb{\n    background-color: #eee;\n    opacity: 0.7;\n    border-radius: 4px;\n  }\n\n  ::-webkit-scrollbar-corner {\n    display: none;\n  }\n  ::-webkit-resizer{\n    display: none;\n  }\n\n  .github {\n    font-size: 16px;\n    margin-left: 16px;\n    cursor: pointer;\n  }\n\n  .logo {\n    user-select: none;\n  }\n\n  .application {\n    font-family: PingFang SC,-apple-system,SF UI Text,Lucida Grande,STheiti,Microsoft YaHei,sans-serif !important;\n  }\n</style>\n"
  },
  {
    "path": "src/assets/locales-menu.ts",
    "content": "const messages: any = {\n  'zh-CN': {\n    edit: '编辑',\n    help: '帮助',\n    save: '保存',\n    undo: '撤销',\n    redo: '重做',\n    cut: '剪切',\n    copy: '复制',\n    paste: '粘贴',\n    delete: '删除',\n    selectall: '全选',\n    toggledevtools: '开发者工具',\n    close: '关闭',\n    quit: '退出',\n  },\n  'zh-TW': {\n    edit: '编辑',\n    help: '帮助',\n    save: '保存',\n    undo: '撤銷',\n    redo: '重做',\n    cut: '剪切',\n    copy: '複製',\n    paste: '粘貼',\n    delete: '刪除',\n    selectall: '全選',\n    toggledevtools: '開發者工具',\n    close: '關閉',\n    quit: '退出',\n  },\n  'en': {\n    edit: 'Edit',\n    help: 'Help',\n    save: 'Save',\n    undo: 'Undo',\n    redo: 'Redo',\n    cut: 'Cut',\n    copy: 'Copy',\n    paste: 'Paste',\n    delete: 'Delete',\n    selectall: 'Select All',\n    toggledevtools: 'Toogle Developer Tools',\n    close: 'Close Window',\n    quit: 'Quit',\n  },\n  'fr-FR': {\n    edit: 'Éditer',\n    help: 'Aide',\n    save: 'Enregistrer',\n    undo: 'Annuler',\n    redo: 'Refaire',\n    cut: 'Couper',\n    copy: 'Copier',\n    paste: 'Coller',\n    delete: 'Supprimer',\n    selectall: 'Tout sélectionner',\n    toggledevtools: 'Toogle Developer Tools',\n    close: 'Fermer',\n    quit: 'Quitter',\n  },\n  'ru': {\n    edit: 'Редактировать',\n    help: 'Помощь',\n    save: 'Сохранить',\n    undo: 'Отменить',\n    redo: 'Повторить',\n    cut: 'Вырезать',\n    copy: 'Копировать',\n    paste: 'Вставить',\n    delete: 'Удалить',\n    selectall: 'Выделить всё',\n    toggledevtools: 'Инструменты разработчика',\n    close: 'Закрыть окно',\n    quit: 'Выход',\n  },\n  'ja-JP': {\n    edit: '編集',\n    help: 'ヘルプ',\n    save: 'セーブ',\n    undo: '取り消す',\n    redo: 'やり直す',\n    cut: 'カット',\n    copy: 'コピー',\n    paste: 'ペースト',\n    delete: '削除',\n    selectall: '全て選択',\n    toggledevtools: '開発者ツール',\n    close: '閉じる',\n    quit: '終了',\n  }\n}\nexport default messages\n"
  },
  {
    "path": "src/assets/locales.ts",
    "content": "const message = {\n  zhHans: {\n    preview: '预 览',\n    syncSite: '同 步',\n    newVersion: '有新版本',\n    article: '文 章',\n    menu: '菜 单',\n    tag: '标 签',\n    theme: '主 题',\n    remote: '远 程',\n    system: '系 统',\n    renderSuccess: '渲染完毕，快去预览吧！',\n    renderError: '渲染失败，请检查 hosts 文件中 127.0.0.1 是否指向 localhost。确认配置正确后，尝试重启应用。',\n    syncWarning: '必须完成配置才能同步哦！',\n    syncSuccess: '同步成功啦！',\n    syncError1: '同步遇到了错误，请查阅',\n    syncError2: '来寻找解决方案',\n    newVersionTips: '有新版本发布，快去下载新版本吧！',\n    newArticle: '新文章',\n    publish: '发布',\n    published: '已发布',\n    draft: '草稿',\n    title: '标题',\n    status: '状态',\n    createAt: '创建时间',\n    actions: '操作',\n    deleteWarning: '删除后不可撤销，你确定要删除吗？',\n    warning: '警告',\n    articleDelete: '文章已删除',\n    cancel: '取 消',\n    select: '选 择',\n    featureImage: '封面图',\n    saveDraft: '存草稿',\n    save: '保 存',\n    newMenu: '新菜单',\n    name: '名称',\n    openType: '打开方式',\n    link: 'Link',\n    menuSuccess: '菜单已保存',\n    menuDelete: '菜单已删除',\n    draftSuccess: '已存为草稿',\n    saveSuccess: '已保存',\n    newTag: '新标签',\n    tagName: '标签名',\n    selectTheme: '选择主题',\n    siteName: '网站名称',\n    siteDescription: '网站描述',\n    footerInfo: '底部信息',\n    isShowFeatureImage: '显示封面图',\n    articlesPerPage: '每页文章数',\n    archivesPerPage: '每页归档数',\n    basicSetting: '基础配置',\n    commentSetting: '评论配置',\n    faviconSetting: '网页图标',\n    avatarSetting: '头像配置',\n    domain: '域 名',\n    repository: '仓库名称',\n    branch: '分 支',\n    username: '仓库用户名',\n    email: '邮 箱',\n    isShowComment: '是否显示评论',\n    domainShouldStartsWithWarn: '域名应以 \\'https://\\' 或 \\'http://\\' 开头',\n    basicSettingSuccess: '基础配置已保存',\n    commentSettingSuccess: '评论配置已保存',\n    faviconSettingSuccess: '网页图标已保存',\n    avatarSettingSuccess: '头像配置已保存',\n    saved: '已保存',\n    syncing: '同步中，请耐心等待...',\n    articleDefault: '文章 URL 默认格式',\n    tagDefault: '标签 URL 默认格式',\n    hideInList: '列表中隐藏',\n    dateFormat: '日期格式',\n    htmlSupport: '支持 HTML',\n    change: '更 换',\n    editorTip: '你可以插入单独行的 <!-- more --> 为摘要分隔标识（此行之前内容为摘要）',\n    saveError: '保存失败',\n    privateKeyTip: '请填写绝对路径，例如：/home/username/.ssh/id_rsa',\n    remotePathTip: '请填写绝对路径，例如：/home/username/www/',\n    testConnection: '检测远程连接',\n    connectSuccess: '远程连接成功',\n    connectFailed: '远程连接失败，请检查仓库、用户名和令牌设置',\n    sourceFolder: '站点源文件路径',\n    language: '语 言',\n    inConfig: '配置中',\n    searchArticle: '搜索文章',\n    deleteSelected: '已选',\n    inputContent: '输入内容',\n    postUrlRepeatTip: '文章的 URL 与其他文章重复',\n    postUrlIncludeTip: 'URL 不可包含 /',\n    onlyPicDrag: '仅支持图片拖拽',\n    themeConfigSaved: '主题配置已保存',\n    reset: '重置',\n    reseted: '已重置',\n    noCustomConfigTip: '当前主题暂无自定义配置',\n    customConfig: '自定义配置',\n    moreThemes: '更多主题',\n    postSettings: '文章设置',\n    back: '返回',\n    savedIn: '保存于',\n    or: '或',\n    starSupport: 'Star 支持作者！',\n    showAllPost: '显示全文',\n    showAbstract: '仅显示摘要',\n    unsavedWarning: '你将丢失所有的未保存的更改，是否继续？',\n    noSaveAndBack: '继续',\n    insertImage: '插入图片',\n    insertMore: '插入摘要分隔符',\n    writingIn: '写作于',\n    words: '字 数',\n    readingTime: '阅读时间',\n    version: '版本',\n    token: '令 牌',\n    tokenUsername: '令牌用户名',\n    platform: '平 台',\n    topArticles: '置顶文章',\n    default: '默认',\n    external: '外链',\n    pathContainHttps: '路径必须包含 http 或 https',\n    articleUrlPath: '文章 URL 路径',\n    concise: '精简',\n    tagUrlPath: '标签 URL 路径',\n    archivePathPrefix: '归档路径前缀',\n    showFullText: '显示全文',\n    showAbstractOnly: '仅显示摘要',\n    numberArticlesRSS: 'RSS/Feed 文章数量',\n    Proxy: 'HTTP代理',\n    ProxyAddress: '地址',\n    ProxyPort: '端口',\n  },\n  zh_TW: {\n    preview: '預 覽',\n    syncSite: '同 步',\n    newVersion: '有新版本',\n    article: '文 章',\n    menu: '菜 單',\n    tag: '標 簽',\n    theme: '主 題',\n    remote: '遠 程',\n    system: '系 統',\n    renderSuccess: '渲染完毕，快去预览吧！',\n    renderError: '渲染失敗，請檢查 hosts 文件中 127.0.0.1 是否指向 localhost。確認配置正確後，嘗試重啟應用。',\n    syncWarning: '必須完成配置才能同步哦！',\n    syncSuccess: '同步成功啦！',\n    syncError1: '同步遇到了错误，请查閱',\n    syncError2: '來尋找解決方案',\n    newVersionTips: '有新版本發布，快去下載新版本吧！',\n    newArticle: '新文章',\n    publish: '發布',\n    published: '已發布',\n    draft: '草稿',\n    title: '標題',\n    status: '狀態',\n    createAt: '創建時間',\n    actions: '操作',\n    deleteWarning: '刪除後不可撤銷，你確定要刪除嗎？',\n    warning: '警告',\n    articleDelete: '文章已刪除',\n    cancel: '取 消',\n    select: '選 擇',\n    featureImage: '封面圖',\n    saveDraft: '存草稿',\n    save: '保 存',\n    newMenu: '新菜單',\n    name: '名稱',\n    openType: '打開方式',\n    link: 'Link',\n    menuSuccess: '菜單已保存',\n    menuDelete: '菜單已刪除',\n    draftSuccess: '已存為草稿',\n    saveSuccess: '已保存',\n    newTag: '新標籤',\n    tagName: '標籤名',\n    selectTheme: '選擇主題',\n    siteName: '網站名稱',\n    siteDescription: '網站描述',\n    footerInfo: '底部信息',\n    isShowFeatureImage: '顯示封面圖',\n    articlesPerPage: '每頁文章數',\n    archivesPerPage: '每頁歸檔數',\n    basicSetting: '基礎配置',\n    commentSetting: '評論配置',\n    faviconSetting: '網頁圖標',\n    avatarSetting: '頭像配置',\n    domain: '域 名',\n    repository: '倉庫名稱',\n    branch: '分 支',\n    username: '倉庫用戶名',\n    email: '郵 箱',\n    isShowComment: '是否顯示評論',\n    domainShouldStartsWithWarn: '域名應以 \\'https://\\' 或 \\'http://\\' 開頭',\n    basicSettingSuccess: '基礎配置已保存',\n    commentSettingSuccess: '評論配置已保存',\n    faviconSettingSuccess: '網頁圖標配置已保存',\n    avatarSettingSuccess: '頭像配置已保存',\n    saved: '已保存',\n    syncing: '同步中，請耐心等待...',\n    articleDefault: '文章 URL 默認格式',\n    tagDefault: '標籤 URL 默認格式',\n    hideInList: '列表中隱藏',\n    dateFormat: '日期格式',\n    htmlSupport: '支持 HTML',\n    change: '更 換',\n    editorTip: '你可以插入單獨行的 <!-- more --> 為摘要分隔標識（此行之前內容為摘要）',\n    saveError: '保存失敗',\n    privateKeyTip: '請填寫絕對路徑，例如：/home/username/.ssh/id_rsa',\n    remotePathTip: '請填寫絕對路徑，例如：/home/username/www/',\n    testConnection: '檢測遠程連接',\n    connectSuccess: '遠程連接成功',\n    connectFailed: '遠程連接失敗，請檢查倉庫、用戶名和令牌設置',\n    sourceFolder: '站点源文件路径',\n    language: '語 言',\n    inConfig: '配置中',\n    searchArticle: '搜索文章',\n    deleteSelected: '已選',\n    inputContent: '輸入內容',\n    postUrlRepeatTip: '文章的 URL 與其他文章重複',\n    postUrlIncludeTip: 'URL 不可包含 /',\n    onlyPicDrag: '僅支持圖片拖拽',\n    themeConfigSaved: '主題配置已保存',\n    reset: '重置',\n    reseted: '已重置',\n    noCustomConfigTip: '當前主題暫無自定義配置',\n    customConfig: '自定義配置',\n    moreThemes: '更多主題',\n    postSettings: '文章設置',\n    back: '返回',\n    savedIn: '保存於',\n    or: '或',\n    starSupport: 'Star 支持作者！',\n    showAllPost: '顯示全文',\n    showAbstract: '僅顯示摘要',\n    unsavedWarning: '你將丟失所有的未保存的更改，是否繼續？',\n    noSaveAndBack: '繼續',\n    insertImage: '插入圖片',\n    insertMore: '插入摘要分隔符',\n    writingIn: '寫作於',\n    words: '字 數',\n    readingTime: '閱讀時間',\n    version: '版本',\n    token: '令 牌',\n    tokenUsername: '令牌用户名',\n    platform: '平 臺',\n    topArticles: '置顶文章',\n    default: '默认',\n    external: '外链',\n    pathContainHttps: '路径必须包含 http 或 https',\n    articleUrlPath: '文章 URL 路径',\n    concise: '精简',\n    tagUrlPath: '标签 URL 路径',\n    archivePathPrefix: '归档路径前缀',\n    showFullText: '显示全文',\n    showAbstractOnly: '仅显示摘要',\n    numberArticlesRSS: 'RSS/Feed 文章数量',\n    Proxy: 'HTTP代理',\n    ProxyAddress: '地址',\n    ProxyPort: '端口',\n  },\n  en: {\n    preview: 'Preview',\n    syncSite: 'Sync Website',\n    newVersion: 'New version',\n    article: 'Article',\n    menu: 'Menu',\n    tag: 'Tag',\n    theme: 'Theme',\n    remote: 'Server',\n    system: 'System',\n    renderSuccess: 'Congratulations, the rendering is complete, go ahead and preview.',\n    renderError: 'Rendering failed, please check whether 127.0.0.1 in the hosts file points to localhost. After confirming that the configuration is correct, try to restart the application.',\n    syncWarning: 'You must complete the configuration to synchronize!',\n    syncSuccess: 'Synchronization succeeded!',\n    syncError1: 'Sorry, synchronization encountered an error, please refer to',\n    syncError2: 'to find a solution',\n    newVersionTips: 'There is a new version released, go and download the new version!',\n    newArticle: 'New',\n    publish: 'Publish',\n    published: 'Published',\n    draft: 'Draft',\n    title: 'Title',\n    status: 'Status',\n    createAt: 'Create Time',\n    actions: 'Actions',\n    deleteWarning: 'After deletion, it cannot be revoked. Are you sure you want to delete it?',\n    warning: 'Warning',\n    articleDelete: 'Article deleted',\n    cancel: 'Cancel',\n    select: 'Select',\n    featureImage: 'Feature Image',\n    saveDraft: 'Save as Draft',\n    save: 'Save',\n    newMenu: 'New',\n    name: 'Name',\n    openType: 'Open Type',\n    link: 'Link',\n    menuSuccess: 'Menu saved',\n    menuDelete: 'Menu deleted',\n    draftSuccess: 'Saved as draft',\n    saveSuccess: 'Saved successfully',\n    newTag: 'New',\n    tagName: 'Tag Name',\n    selectTheme: 'Select Theme',\n    siteName: 'Site Name',\n    siteDescription: 'Site Description',\n    footerInfo: 'Footer Information',\n    isShowFeatureImage: 'Feature image',\n    articlesPerPage: 'Articles per page',\n    archivesPerPage: 'Archives per page',\n    basicSetting: 'Basic settings',\n    commentSetting: 'Comment settings',\n    faviconSetting: 'Favicon setting',\n    avatarSetting: 'Avatar setting',\n    domain: 'Domain',\n    repository: 'Repository Name',\n    branch: 'Branch',\n    username: 'Branch Username',\n    email: 'Email',\n    isShowComment: 'Show comments',\n    domainShouldStartsWithWarn: 'Domain should starts with \\'https://\\' or \\'http://\\' ',\n    basicSettingSuccess: 'The basic setting saved',\n    commentSettingSuccess: 'The comment setting saved',\n    faviconSettingSuccess: 'The favicon setting saved',\n    avatarSettingSuccess: 'The avatar setting saved',\n    saved: 'Saved',\n    syncing: 'Synchronizing, please wait...',\n    articleDefault: 'Article URL Default',\n    tagDefault: 'Tag URL Default',\n    hideInList: 'Hide in list',\n    dateFormat: 'Date format',\n    htmlSupport: 'HTML is supported in this field',\n    change: 'Change',\n    editorTip: 'You can insert a separate line <!-- more --> is the abstract separator identifier ( the content before this line is the abstract)',\n    saveError: 'Save failed',\n    privateKeyTip: 'Please fill in the absolute path, for example: /home/username/.ssh/id_rsa',\n    remotePathTip: 'Please fill in the absolute path, for example:：/home/username/www/',\n    testConnection: 'Test Connection',\n    connectSuccess: 'Remote connection succeeded',\n    connectFailed: 'Remote connection failed, please check repository, username and token settings',\n    sourceFolder: 'Site source file path',\n    language: 'Language',\n    inConfig: 'In configuration',\n    searchArticle: 'Articles search',\n    deleteSelected: 'Selected',\n    inputContent: 'Input content',\n    postUrlRepeatTip: 'The URL of the article is duplicated with other articles.',\n    postUrlIncludeTip: 'URL cannot contain /',\n    onlyPicDrag: 'Only picture dragging is supported',\n    themeConfigSaved: 'The theme configuration has been saved',\n    reset: 'Reset',\n    reseted: 'Reset',\n    noCustomConfigTip: 'There is no custom configuration for the theme',\n    customConfig: 'Custom configuration',\n    moreThemes: 'More Themes',\n    postSettings: 'Post Settings',\n    back: 'Back',\n    savedIn: 'Saved in',\n    or: 'or',\n    starSupport: 'Give us a star!',\n    showAllPost: 'Show full content',\n    showAbstract: 'Show abstract only',\n    unsavedWarning: 'You will lose all unsaved changes, do you want to continue?',\n    noSaveAndBack: 'Continue',\n    insertImage: 'Insert image',\n    insertMore: 'Insert summary separator',\n    writingIn: 'Writing in',\n    words: 'Words',\n    readingTime: 'Reading time',\n    version: 'Version',\n    token: 'Token',\n    tokenUsername: 'Token Username',\n    platform: 'Platform',\n    topArticles: 'Top articles',\n    default: 'Default',\n    external: 'External',\n    pathContainHttps: 'The path must contain either http or https',\n    articleUrlPath: 'Article URL path',\n    concise: 'concise',\n    tagUrlPath: 'Tag URL path',\n    archivePathPrefix: 'Archive path prefix',\n    showFullText: 'Show full text',\n    showAbstractOnly: 'Show abstract only',\n    numberArticlesRSS: 'Number articles RSS/Feed',\n    Proxy: 'HTTP Proxy',\n    ProxyAddress: 'Proxy Address',\n    ProxyPort: 'Proxy Port',\n  },\n  fr_FR: {\n    preview: 'Aperçu',\n    syncSite: 'Synchroniser',\n    newVersion: 'Nouvelle version',\n    article: 'Article',\n    menu: 'Menu',\n    tag: 'Tag',\n    theme: 'Thème',\n    remote: 'Serveur',\n    system: 'Système',\n    renderSuccess: 'Félicitations, le rendu est terminé et regardez en avant-première.',\n    renderError: 'Le rendu a échoué, veuillez vérifier si 127.0.0.1 dans le fichier hosts pointe vers localhost. Après avoir vérifié que la configuration est correcte, essayez de redémarrer l\\'application.',\n    syncWarning: 'Vous devez compléter la configuration pour synchroniser !',\n    syncSuccess: 'La synchronisation a réussi !',\n    syncError1: 'Désolé, la synchronisation a rencontré une erreur, veuillez vous référer à',\n    syncError2: 'pour trouver une solution',\n    newVersionTips: 'Une nouvelle version est disponible, téléchargez la nouvelle version!',\n    newArticle: 'Nouveau',\n    publish: 'Publier',\n    published: 'Publié',\n    draft: 'Brouillon',\n    title: 'Titre',\n    status: 'Status',\n    createAt: 'Heure de création',\n    actions: 'Actions',\n    deleteWarning: 'Après la suppression, celui-ci ne peut plus être révoqué. Êtes-vous sûr de vouloir supprimer ?',\n    warning: 'Attention',\n    articleDelete: 'Article supprimé',\n    cancel: 'Annuler',\n    select: 'Selectionner',\n    featureImage: 'Image de fond',\n    saveDraft: 'Enregristrer en brouillon',\n    save: 'Enregistrer',\n    newMenu: 'Nouveau',\n    name: 'Nom',\n    openType: 'type d\\'ouverture',\n    link: 'Lien',\n    menuSuccess: 'Menu enregistré',\n    menuDelete: 'Menu supprimé',\n    draftSuccess: 'Enregistré en brouillon',\n    saveSuccess: 'Sauvegardé avec succès',\n    newTag: 'Nouveau tag',\n    tagName: 'Nom du tag',\n    selectTheme: 'Sélectionnez un thème',\n    siteName: 'Nom du site',\n    siteDescription: 'Description du site',\n    footerInfo: 'Informations sur le Footer',\n    isShowFeatureImage: 'Image de fond',\n    articlesPerPage: 'Articles par page',\n    archivesPerPage: 'Archives par page',\n    basicSetting: 'Paramètres de base',\n    commentSetting: 'Paramétrage des commentaires',\n    faviconSetting: 'Paramètres du Favicon',\n    avatarSetting: 'Paramètres de l\\'avatar',\n    domain: 'Domaine',\n    repository: 'Nom du repository',\n    branch: 'Branche',\n    username: 'Nom d\\'utilisateur',\n    email: 'Email',\n    isShowComment: 'Afficher les commentaires',\n    domainShouldStartsWithWarn: 'Le domaine doit commencer par \\'https://\\' or \\'http://\\' ',\n    basicSettingSuccess: 'Les réglages de base sont enregistrés',\n    commentSettingSuccess: 'Les réglages des commentaires sont enregistrés',\n    faviconSettingSuccess: 'Les réglages du favicon sont enregistrés',\n    avatarSettingSuccess: 'Les réglages de l\\'avatar sont enregistrés',\n    saved: 'Enregistré',\n    syncing: 'Synchronisation, veuillez patienter...',\n    articleDefault: 'URL de l\\'article par défaut',\n    tagDefault: 'URL de tag par défault',\n    hideInList: 'Cacher dans la liste',\n    dateFormat: 'Format de la date',\n    htmlSupport: 'Gestion du HTML',\n    change: 'Changer',\n    editorTip: 'Vous pouvez insérer une ligne séparée <!-- more --> c\\'est un identifiant pour séparer le résumé (le contenu avant cette ligne est le résumé)',\n    saveError: 'Enregistrement échoué',\n    privateKeyTip: 'Veuillez indiquer le chemin absolu, par exemple: /home/username/.ssh/id_rsa',\n    remotePathTip: 'Veuillez indiquer le chemin absolu, par exemple: /home/username/www/',\n    testConnection: 'Test de connexion',\n    connectSuccess: 'Connexion à distance a réussi',\n    connectFailed: 'La connexion à distance a échoué, veuillez vérifier les paramètres du référentiel, du nom d\\'utilisateur et du token',\n    sourceFolder: 'Chemin d\\'accès au fichier source du site',\n    language: 'Langue',\n    inConfig: 'En configuration',\n    searchArticle: 'Rechercher des articles',\n    deleteSelected: 'Sélectionné',\n    inputContent: 'Saisie du contenu',\n    postUrlRepeatTip: 'L\\'URL de l\\'article est dupliquée avec d\\'autres articles.',\n    postUrlIncludeTip: 'L\\'URL ne peut pas contenir /',\n    onlyPicDrag: 'Seul le dragage d\\'images est autorisé',\n    themeConfigSaved: 'La configuration du thème a été sauvegardée',\n    reset: 'Réinitialiser',\n    reseted: 'Réinitialiser',\n    noCustomConfigTip: 'Il n\\'y a pas de configuration personnalisée pour le thème',\n    customConfig: 'Configuration personnalisée',\n    moreThemes: 'Autres thèmes',\n    postSettings: 'Paramètres des postes',\n    back: 'Retour',\n    savedIn: 'Sauvegardé dans',\n    or: 'ou',\n    starSupport: 'Donnez-nous une étoile !',\n    showAllPost: 'Afficher tout les postes',\n    showAbstract: 'Afficher uniquement le résumé',\n    unsavedWarning: 'Vous allez perdre tous les changements non sauvegardés, voulez-vous continuer ?',\n    noSaveAndBack: 'Continuer',\n    insertImage: 'Insérer une image',\n    insertMore: 'Insérer un séparateur de résumé',\n    writingIn: 'Ecrire en',\n    words: 'Mots',\n    readingTime: 'Temps de lecture',\n    version: 'Version',\n    token: 'Token',\n    tokenUsername: 'Token Username',\n    platform: 'Plate-forme',\n    topArticles: 'Articles en tête',\n    default: 'Par défaut',\n    external: 'Externe',\n    pathContainHttps: 'L\\'URL doit contenir soit \\'http\\' ou \\'https\\'',\n    articleUrlPath: 'URL des articles',\n    concise: 'simplifié',\n    tagUrlPath: 'URL des tags',\n    archivePathPrefix: 'Préfix du chemin des Archives',\n    showFullText: 'Tout afficher',\n    showAbstractOnly: 'Afficher seulement le résumé',\n    numberArticlesRSS: 'Nombre d\\'articles RSS/Feed',\n    Proxy: 'HTTP Proxy',\n    ProxyAddress: 'Proxy Address',\n    ProxyPort: 'Proxy Port',\n  },\n  ru: {\n    preview: 'Предпросмотр',\n    syncSite: 'Синхронизировать сайт',\n    newVersion: 'Новая версия',\n    article: 'Запись',\n    menu: 'Меню',\n    tag: 'Тег',\n    theme: 'Тема',\n    remote: 'Сервер',\n    system: 'Система',\n    renderSuccess: 'Поздравляем, рендеринг завершен, переходите к предпросмотру.',\n    renderError: 'Не удалось выполнить рендеринг, пожалуйста, проверьте, указывает ли 127.0.0.1 в файле hosts на localhost. После подтверждения правильности настроек попробуйте перезапустить приложение.',\n    syncWarning: 'Для синхронизации следует завершить настройку!',\n    syncSuccess: 'Синхронизация выполнена успешно!',\n    syncError1: 'Извините, при синхронизации произошла ошибка, пожалуйста, обратитесь к',\n    syncError2: 'чтобы найти решение',\n    newVersionTips: 'Выпущена новая версия, пожалуйста, зайдите и скачайте новую версию!',\n    newArticle: 'Новая запись',\n    publish: 'Опубликовать',\n    published: 'Опубликовано',\n    draft: 'Черновик',\n    title: 'Заголовок',\n    status: 'Статус',\n    createAt: 'Дата и время создания',\n    actions: 'Действия',\n    deleteWarning: 'После удаления вернуть всё назад не получится. Вы уверены, что хотите удалить?',\n    warning: 'Предупреждение',\n    articleDelete: 'Запись удалена',\n    cancel: 'Отмена',\n    select: 'Выбрать',\n    featureImage: 'Изображение записи',\n    saveDraft: 'Сохранить как Черновик',\n    save: 'Сохранить',\n    newMenu: 'Добавить',\n    name: 'Название',\n    openType: 'Тип ссылки',\n    link: 'Ссылка',\n    menuSuccess: 'Меню сохранено',\n    menuDelete: 'Меню удалено',\n    draftSuccess: 'Сохранено как Черновик',\n    saveSuccess: 'Успешно сохранено',\n    newTag: 'Добавить тег',\n    tagName: 'Название тега',\n    selectTheme: 'Выберите Тему',\n    siteName: 'Название сайта',\n    siteDescription: 'Описание сайта',\n    footerInfo: 'Информация в подвале сайта',\n    isShowFeatureImage: 'Изображения записей',\n    articlesPerPage: 'Записей на странице',\n    archivesPerPage: 'Архивных записей на странице',\n    basicSetting: 'Основные настройки',\n    commentSetting: 'Настройки комментариев',\n    faviconSetting: 'Настройки иконки сайта',\n    avatarSetting: 'Настройки аватара',\n    domain: 'Домен',\n    repository: 'Название репозитория',\n    branch: 'Ветка',\n    username: 'Имя пользователя ветки',\n    email: 'Email',\n    isShowComment: 'Показывать комментарии',\n    domainShouldStartsWithWarn: 'Домен должен начинаться с \\'https://\\' или \\'http://\\' ',\n    basicSettingSuccess: 'Основные настройки сохранены',\n    commentSettingSuccess: 'Настройки комментариев сохранены',\n    faviconSettingSuccess: 'Новая иконка сайта сохранена',\n    avatarSettingSuccess: 'Новый аватар сохранён',\n    saved: 'Сохранено',\n    syncing: 'Идёт синхронизация, пожалуйста подождите...',\n    articleDefault: 'URL записи по умолчанию',\n    tagDefault: 'URL тега по умолчанию',\n    hideInList: 'Скрыть из списка записей',\n    dateFormat: 'Формат даты',\n    htmlSupport: 'В этом поле поддерживается HTML',\n    change: 'Изменить',\n    editorTip: 'Вы можете вставить отдельную строку с тегом <!-- more --> — это тег разделителя (содержимое перед этой строкой будет отображаться на странице со списком записей)',\n    saveError: 'Ошибка сохранения',\n    privateKeyTip: 'Пожалуйста, введите абсолютный путь, например: /home/username/.ssh/id_rsa',\n    remotePathTip: 'Пожалуйста, введите абсолютный путь, например: /home/username/www/',\n    testConnection: 'Проверка соединения',\n    connectSuccess: 'Удалённое подключение выполнено успешно',\n    connectFailed: 'Ошибка при удалённом подключении. Пожалуйста, проверьте настройки репозитория, имени пользователя и токена.',\n    sourceFolder: 'Путь к исходным файлам сайта',\n    language: 'Язык',\n    inConfig: 'Применение настроек',\n    searchArticle: 'Поиск записей',\n    deleteSelected: 'Выбранное',\n    inputContent: 'Введите содержимое',\n    postUrlRepeatTip: 'URL-адрес записи в точности повторяет URL-адрес других записей.',\n    postUrlIncludeTip: 'URL-адрес не может содержать /',\n    onlyPicDrag: 'Поддерживается только перетаскивание изображений',\n    themeConfigSaved: 'Настройки темы были сохранены',\n    reset: 'Сбросить',\n    reseted: 'Сброшено',\n    noCustomConfigTip: 'Пользовательских настроек для этой темы не предусмотрено',\n    customConfig: 'Пользовательские настройки',\n    moreThemes: 'Ещё больше тем',\n    postSettings: 'Настройки записи',\n    back: 'Назад',\n    savedIn: 'Сохранено в',\n    or: 'или',\n    starSupport: 'Оцените нас!',\n    showAllPost: 'Показывать содержимое записей полностью',\n    showAbstract: 'Показывать краткое описание записей',\n    unsavedWarning: 'Вы потеряете все несохраненные изменения, хотите продолжить?',\n    noSaveAndBack: 'Продолжить',\n    insertImage: 'Вставить изображение',\n    insertMore: 'Вставить разделитель',\n    writingIn: 'Создано с помощью',\n    words: 'Слов(а)',\n    readingTime: 'Время чтения',\n    version: 'Версия',\n    token: 'Токен',\n    tokenUsername: 'Имя пользователя токена',\n    platform: 'Платформа',\n    topArticles: 'Закрепить запись',\n    default: 'По умолчанию',\n    external: 'Ссылка на изображение',\n    pathContainHttps: 'Ссылка должна содержать либо http, либо https',\n    articleUrlPath: 'URL-адрес записи',\n    concise: 'Компактный вид',\n    tagUrlPath: 'URL-адрес тега',\n    archivePathPrefix: 'Префикс для архива',\n    showFullText: 'Отображать содержимое полностью',\n    showAbstractOnly: 'Отображать краткое содержимое',\n    numberArticlesRSS: 'Количество записей в RSS/Feed',\n    Proxy: 'HTTP Прокси',\n    ProxyAddress: 'Прокси адрес',\n    ProxyPort: 'Прокси порт',\n  },\n  ja_JP: {\n    preview: 'プレビュー',\n    syncSite: 'サイトを同期化',\n    newVersion: '新しいバージョン',\n    article: '文書',\n    menu: 'メニュー',\n    tag: 'タグ',\n    theme: 'テーマ',\n    remote: 'リモート',\n    system: 'システム',\n    renderSuccess: 'レンダリングは完成しました、プレビューしてください。',\n    renderError: 'レンダリングに失敗しました。hostsファイルの127.0.0.1がlocalhostを指しているかどうかを確認してください。構成が正しいことを確認した後、アプリケーションを再起動してみてください。',\n    syncWarning: 'サイトを同期化する前に、システムの配置を完成してください！',\n    syncSuccess: 'サイトの同期化は完成しました！',\n    syncError1: '同期化の途中でエラーが発生しました、ご確認ください。',\n    syncError2: '解決策を見つかりましょう',\n    newVersionTips: '新しいバージョンがリリースしました、アップデートしましょう！',\n    newArticle: '新規文書',\n    publish: '公開',\n    published: '公開済み',\n    draft: '下書き',\n    title: 'タイトル',\n    status: 'ステータス',\n    createAt: '作成日',\n    actions: '操作',\n    deleteWarning: '完全に削除してもよろしいですか？',\n    warning: 'ウォーニング',\n    articleDelete: '文書を削除しました。',\n    cancel: '取り消す',\n    select: '選択',\n    featureImage: '表紙イメージ',\n    saveDraft: '下書き保存',\n    save: 'セーブ',\n    newMenu: '新規メニュー',\n    name: '名前',\n    openType: '開く方式',\n    link: 'リンク',\n    menuSuccess: 'メニューを保存しました',\n    menuDelete: 'メニューを削除しました',\n    draftSuccess: '下書きを保存しました',\n    saveSuccess: 'セーブしました',\n    newTag: '新規タグ',\n    tagName: 'タグ名',\n    selectTheme: 'テーマ選択',\n    siteName: 'サイト名',\n    siteDescription: 'サイト紹介',\n    footerInfo: 'フッター',\n    isShowFeatureImage: '表紙イメージを表示します',\n    articlesPerPage: '毎ページの文書数',\n    archivesPerPage: '毎ページのアーカイブ数',\n    basicSetting: 'システム設定',\n    commentSetting: 'コメント設定',\n    faviconSetting: 'タブアイコン',\n    avatarSetting: 'ユーザーアイコン',\n    domain: 'ドメイン名',\n    repository: 'レポジトリ名',\n    branch: 'ブランチ',\n    username: 'ユーザー名',\n    email: 'メールアドレス',\n    isShowComment: 'コメント表示',\n    domainShouldStartsWithWarn: 'ドメイン名は必ず　\\'https://\\' 或 \\'http://\\'　で始まります',\n    basicSettingSuccess: 'システム設定を保存しました',\n    commentSettingSuccess: 'コメント設定を保存しました',\n    faviconSettingSuccess: 'タブアイコンを保存しました',\n    avatarSettingSuccess: 'ユーザーアイコンを保存しました',\n    saved: '保存しました',\n    syncing: '同期中、しばらくお待ちください…',\n    articleDefault: '文書URLのデフォルトフォーマット',\n    tagDefault: 'タグURLのデフォルトフォーマット',\n    hideInList: 'リストに隠す',\n    dateFormat: '日付フォーマット',\n    htmlSupport: 'HTMLサポート',\n    change: '更新',\n    editorTip: '文書の後ろに一行の <!-- more --> を追加したら、この部分を摘要としてを表示できます。',\n    saveError: '保存失敗しました',\n    privateKeyTip: '絶対パスを記入してください、例えば：「/home/username/.ssh/id_rsa」',\n    remotePathTip: '絶対パスを記入してください、例えば：「/home/username/www/」',\n    testConnection: '接続をテストしています',\n    connectSuccess: '接続は成功しました',\n    connectFailed: '接続が失敗しました。リポジトリ名、ユーザー名とトークンを確認してください。',\n    sourceFolder: 'サイトのソースファイルのパス',\n    language: '言語',\n    inConfig: '配置中',\n    searchArticle: '文書検索',\n    deleteSelected: '選択済み',\n    inputContent: '内容を入力してください',\n    postUrlRepeatTip: '文書のURLは他の文書と重複しました',\n    postUrlIncludeTip: 'URLの中には　/　を含めません',\n    onlyPicDrag: 'イメージをドラッグのみです',\n    themeConfigSaved: 'テームの設定を保存しました',\n    reset: 'リセット',\n    reseted: 'リセット完成しました',\n    noCustomConfigTip: '現在のテーマはカスタム設定はありません',\n    customConfig: 'カスタム設定',\n    moreThemes: 'テーマストアー',\n    postSettings: '文書設定',\n    back: '戻る',\n    savedIn: '上書き保存',\n    or: 'または',\n    starSupport: 'Starで開発者をサポートします',\n    showAllPost: '全ての文書を表示します',\n    showAbstract: '摘要のみを表示します',\n    unsavedWarning: '未保存の変更内容を全て削除します、よろしいですか？',\n    noSaveAndBack: 'つづく',\n    insertImage: 'イマージを挿入します',\n    insertMore: '摘要標記を挿入します',\n    writingIn: '作成地',\n    words: '字数',\n    readingTime: '読む時間',\n    version: 'バージョン',\n    token: 'トークン',\n    tokenUsername: 'トークンのユーザー名',\n    platform: 'プラットホーム',\n    topArticles: '固定文書',\n    default: 'デフォルト',\n    external: '外部リンク',\n    pathContainHttps: 'パスは必ずhttpまたはhttpsを含めます',\n    articleUrlPath: '文書のURLのパス',\n    concise: '簡素化',\n    tagUrlPath: 'タグURLのパス',\n    archivePathPrefix: 'アーカイブのパスの接頭辞',\n    showFullText: '全文を表示します',\n    showAbstractOnly: '摘要のみを表示します',\n    numberArticlesRSS: 'RSS/Feed 文書数量',\n    Proxy: 'HTTPプロキシ',\n    ProxyAddress: 'プロキシアドレス',\n    ProxyPort: 'プロキシポート',\n  }\n}\nexport default message\n"
  },
  {
    "path": "src/assets/styles/custom.less",
    "content": ".ant-table {\n  background: #fff;\n  color: #555;\n}\n.ant-table-tbody {\n  .ant-table-row:nth-of-type(2n) {\n    background: #fafafa;\n  }\n}\n.ant-table-tbody > tr > td {\n  border-bottom: 0;\n}\n\n.ant-table-thead > tr > th {\n  background: #ffffff;\n  color: #1b1b18b0;\n  font-weight: normal;\n}\n\n.ant-table-thead > tr.ant-table-row-hover:not(.ant-table-expanded-row) > td, .ant-table-tbody > tr.ant-table-row-hover:not(.ant-table-expanded-row) > td, .ant-table-thead > tr:hover:not(.ant-table-expanded-row) > td, .ant-table-tbody > tr:hover:not(.ant-table-expanded-row) > td {\n  background: transparent;\n}\n\n.ant-table-thead > tr > th, .ant-table-tbody > tr > td {\n  padding: 12px 12px;\n}\n\n.ant-table-tbody > tr.ant-table-row-selected td {\n  background: transparent;\n}\n.tool-container {\n  padding: 0px 16px 7px;\n  margin-bottom: 16px;\n  position: fixed;\n  top: 8px;\n  left: 200px;\n  right: 0px;\n  z-index: 1;\n  background: #fff;\n  border-bottom: 1px solid #e8e8e88a;\n  -webkit-app-region: drag;\n  .btn {\n    margin-left: 16px;\n    -webkit-app-region: no-drag;\n  }\n  .op-btn {\n    height: 30px;\n    line-height: 30px;\n    padding: 0 16px;\n    font-size: 18px;\n    border-radius: 20px;\n    margin-left: 8px;\n    outline: none;\n    transition: all 0.3s;\n    display: flex;\n    justify-content: center;\n    align-items: center;\n    i {\n      font-weight: bold;\n    }\n    &:hover {\n      background: #efefef;\n      color: #515457;\n    }\n    &:focus {\n      background: #efefef;\n    }\n    &.save-btn {\n      color: #38A169;\n      &:hover {\n        background: #9AE6B4;\n        color: #22543D;\n      }\n      &:focus {\n        background: #68D391;\n      }\n    }\n  }\n}\n.content-container {\n  margin-top: 48px;\n}\n\n.ant-input, .ant-input-group-addon, .ant-select-selection, .ant-input-number-input, .ant-input-number {\n  background: #FAFAFA;\n  border-color: #EAEAEA;\n  &:focus {\n    box-shadow: none;\n    background: #fff;\n    border-color: #999;\n  }\n}\n\n.ant-input-number-focused {\n  box-shadow: none;\n}\n\n.ant-select-dropdown-menu-item-active, .ant-select-dropdown-menu-item:hover {\n  background: #f7f7f7;\n}\n\n.ant-btn {\n  font-size: 13px;\n  box-shadow: none;\n  [class^='zwicon-']:not(:last-child) {\n    font-size: 16px;\n    margin-right: 8px;\n  }\n}\n\n.ant-btn-primary {\n  box-shadow: none;\n  text-shadow: none;\n}\n\n.ant-menu-inline > .ant-menu-item {\n  height: 36px;\n  line-height: 36px;\n}\n\n.ant-menu-item, .ant-menu-submenu-title {\n  transition: none;\n}\n\n.ant-menu-item:active, .ant-menu-submenu-title:active {\n  background: inherit;\n}\n\n.ant-pagination-prev .ant-pagination-item-link, .ant-pagination-next .ant-pagination-item-link, .ant-pagination-item {\n  border: none;\n  &:hover {\n    background: #fafafa;\n  }\n}\n\n.ant-pagination-item-active {\n  &, &:hover {\n    background: #eaeaea;\n  }\n}\n\n.ant-menu.ant-menu-dark .ant-menu-item-selected, .ant-menu-submenu-popup.ant-menu-dark .ant-menu-item-selected {\n  position: relative;\n}\n\n.ant-checkbox-inner:after {\n  left: 3.571429px;\n}\n\n.ant-checkbox-checked .ant-checkbox-inner {\n  background-color: #fff;\n  &:after {\n    border-color: @primary-color;\n  }\n}\n\n.ant-tabs-nav {\n  .ant-tabs-tab {\n    padding: 4px 8px;\n    margin-bottom: 8px;\n    color: #b1b1b1;\n    border-radius: 6px;\n    transition: all 0.3s;\n    &:hover {\n      background: #efefef;\n    }\n  }\n  .ant-tabs-tab-active {\n    color: #1b1b18;\n  }\n}\n\n.ant-modal-confirm .ant-modal-body {\n  padding: 24px 16px 16px;\n}\n\n.tip-text {\n  background: #fafafa;\n  padding: 4px 8px;\n  border-radius: 2px;\n  line-height: 1.25;\n  font-size: 12px;\n  color: #6669;\n}\n\n.ant-tabs-ink-bar {\n  height: 0;\n}\n\n.ant-message {\n  z-index: 3010;\n  top: 8px;\n  font-size: 12px;\n}\n\n.ant-message-notice-content {\n  padding: 8px 16px;\n  border-radius: 16px;\n  box-shadow: 0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px 0 rgba(0,0,0,.06)!important;\n  background: #000;\n  color: #fafafa;\n}\n\n.ant-drawer-close-x {\n  width: 32px;\n  height: 32px;\n  line-height: 32px;\n  border-radius: 50%;\n  background: #f3f7f9;\n  margin: 12px;\n  font-size: 14px;\n  display: inline-flex;\n  justify-content: center;\n  align-items: center;\n}\n\n.ant-modal-close {\n  &:focus {\n    outline: none;\n  }\n}\n\n.anticon {\n  vertical-align: unset;\n}\n.ant-drawer-header {\n  border-bottom: none;\n}\n\n.ant-form-item-control {\n  padding-right: 2px;\n}\n\n.ant-menu-inline .ant-menu-item, .ant-menu-inline .ant-menu-submenu-title {\n  width: 90%;\n  margin-left: 5%;\n  border-radius: 6px;\n  color: #666;\n}\n\n.menu-tab {\n  .ant-tabs-top-bar {\n    position: fixed;\n    top: 8px;\n    left: 200px;\n    right: 0;\n    background: #fff;\n    z-index: 100;\n    padding-left: 24px;\n  }\n  .ant-tabs-content {\n    padding-top: 44px;\n  }\n}\n\n.ant-tabs-bar {\n  border-bottom: 1px solid #e8e8e88a;\n}\n\n.ant-tag {\n  border: none;\n  line-height: 22px;\n}\n\n.ant-notification-notice {\n  padding: 8px 12px;\n  box-shadow: 0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -2px rgba(0,0,0,.05)!important;\n}\n\n.ant-notification-notice-description {\n  font-size: 12px;\n  line-height: 16px;\n}\n\n.ant-notification-notice-with-icon {\n  .ant-notification-notice-message,\n  .ant-notification-notice-description {\n    margin-left: 40px;\n  }\n}\n.ant-notification-notice-close {\n  right: 4px;\n  top: 2px;\n}\n\n.ant-modal-content {\n  border-radius: 10px;\n  overflow: hidden;\n  box-shadow: 0px 20px 100px 0px rgba(0,0,0,0.15);\n}\n\n.ant-modal-header {\n  border-bottom-color: #fafafa;\n}\n\n.ant-form-explain, .ant-form-extra {\n  font-size: 12px;\n}\n\n.ant-tooltip {\n  font-size: 12px;\n}\n\n.ant-tooltip-arrow,\n.ant-popover-arrow {\n  display: none;\n}\n\n.ant-tooltip-inner {\n  padding: 4px 8px;\n  min-height: auto;\n}\n\n.ant-drawer-mask, .ant-modal-mask {\n  backdrop-filter: saturate(180%) blur(20px);\n  background-color: rgba(242,242,242,0.5);\n}\n\n.ant-drawer.ant-drawer-open .ant-drawer-mask {\n  opacity: 1;\n}\n\n.ant-drawer-right.ant-drawer-open .ant-drawer-content-wrapper {\n  box-shadow: 0px 20px 100px 0px rgba(0,0,0,0.15);\n}\n\n.ant-popover-inner {\n  border: 1px solid #eee;\n  box-shadow: 0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -1px rgba(0,0,0,.06)!important;\n}\n\n::selection {\n  background: #bed6fb;\n  color: @primary-color;\n}\n\n.ant-radio-inner:after {\n  width: 14px;\n  height: 14px;\n  left: 0;\n  top: 0;\n}\n\n.ant-radio-wrapper {\n  padding: 2px 0 2px 8px;\n  border-radius: 2px;\n  &:hover {\n    transition: all 0.3s;\n    background: #efefef;\n  }\n}\n\n.ant-slider-rail, .ant-slider-track, .ant-slider-step {\n  height: 2px;\n  top: 5px;\n}\n\n.ant-drawer-close:focus {\n  outline: none;\n}\n"
  },
  {
    "path": "src/assets/styles/main.less",
    "content": "// @import \"./custom.less\";\n@import \"~ant-design-vue/dist/antd.less\";\n@import \"./var.less\";\n@import \"./custom.less\";\n@import \"./zwicon.less\";\n"
  },
  {
    "path": "src/assets/styles/tailwind.css",
    "content": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n.transition {\n  transition: all .1s ease-in !important;\n}\n\n@variants responsive, hover {\n  .translate-r-2px {\n    transform: translateX(2px) !important;\n  }\n  .transition-fast {\n    transition: all .2s ease !important;\n  }\n}\n"
  },
  {
    "path": "src/assets/styles/var.less",
    "content": "@primary-color: #1b1b18;\r\n@primary-bg: #f7f6f6;\r\n@danger-color: #fa5252;\r\n\r\n@border-radius-base : 6px;\r\n@link-color: #1a5ccf;\r\n@border-color: #e8e8e88a;\r\n\r\n@btn-default-border: #eaeaea;\r\n\r\n@label-color: #1b1b18;\r\n\r\n@font-family: PingFang SC,-apple-system,SF UI Text,Lucida Grande,STheiti,Microsoft YaHei,sans-serif;\r\n\r\n@input-hover-border-color: #999;"
  },
  {
    "path": "src/assets/styles/zwicon.less",
    "content": "@font-face {\n  font-family: 'zwicon';\n  src:  url('../fonts/zwicon.eot?k483k8');\n  src:  url('../fonts/zwicon.eot?k483k8#iefix') format('embedded-opentype'),\n    url('../fonts/zwicon.ttf?k483k8') format('truetype'),\n    url('../fonts/zwicon.woff?k483k8') format('woff'),\n    url('../fonts/zwicon.svg?k483k8#zwicon') format('svg');\n  font-weight: normal;\n  font-style: normal;\n}\n\ni {\n  /* use !important to prevent issues with browser extensions that change fonts */\n  font-family: 'zwicon' !important;\n  speak: none;\n  font-style: normal;\n  font-weight: normal;\n  font-variant: normal;\n  text-transform: none;\n  line-height: 1;\n\n  /* Better Font Rendering =========== */\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n}\n\n.zwicon-align-bottom:before {\n  content: \"\\e900\";\n}\n.zwicon-align-h:before {\n  content: \"\\e901\";\n}\n.zwicon-align-left:before {\n  content: \"\\e902\";\n}\n.zwicon-align-right:before {\n  content: \"\\e903\";\n}\n.zwicon-align-top:before {\n  content: \"\\e904\";\n}\n.zwicon-align-v:before {\n  content: \"\\e905\";\n}\n.zwicon-distribute-h:before {\n  content: \"\\e906\";\n}\n.zwicon-distribute-v:before {\n  content: \"\\e907\";\n}\n.zwicon-arrow-bottom-left:before {\n  content: \"\\e908\";\n}\n.zwicon-arrow-bottom-right:before {\n  content: \"\\e909\";\n}\n.zwicon-arrow-circle-down:before {\n  content: \"\\e90a\";\n}\n.zwicon-arrow-circle-left:before {\n  content: \"\\e90b\";\n}\n.zwicon-arrow-circle-right:before {\n  content: \"\\e90c\";\n}\n.zwicon-arrow-circle-up:before {\n  content: \"\\e90d\";\n}\n.zwicon-arrow-down:before {\n  content: \"\\e90e\";\n}\n.zwicon-arrow-left:before {\n  content: \"\\e90f\";\n}\n.zwicon-arrow-right:before {\n  content: \"\\e910\";\n}\n.zwicon-arrow-square-down:before {\n  content: \"\\e911\";\n}\n.zwicon-arrow-square-left:before {\n  content: \"\\e912\";\n}\n.zwicon-arrow-square-right:before {\n  content: \"\\e913\";\n}\n.zwicon-arrow-square-up:before {\n  content: \"\\e914\";\n}\n.zwicon-arrow-to-top:before {\n  content: \"\\e915\";\n}\n.zwicon-arrow-top-left:before {\n  content: \"\\e916\";\n}\n.zwicon-arrow-top-right:before {\n  content: \"\\e917\";\n}\n.zwicon-arrow-up:before {\n  content: \"\\e918\";\n}\n.zwicon-back:before {\n  content: \"\\e919\";\n}\n.zwicon-chevron-double-down:before {\n  content: \"\\e91a\";\n}\n.zwicon-chevron-double-left:before {\n  content: \"\\e91b\";\n}\n.zwicon-chevron-double-right:before {\n  content: \"\\e91c\";\n}\n.zwicon-chevron-double-up:before {\n  content: \"\\e91d\";\n}\n.zwicon-chevron-down:before {\n  content: \"\\e91e\";\n}\n.zwicon-chevron-left:before {\n  content: \"\\e91f\";\n}\n.zwicon-chevron-right:before {\n  content: \"\\e920\";\n}\n.zwicon-chevron-up:before {\n  content: \"\\e921\";\n}\n.zwicon-collapse-alt:before {\n  content: \"\\e922\";\n}\n.zwicon-collapse-alt2:before {\n  content: \"\\e923\";\n}\n.zwicon-collapse-down:before {\n  content: \"\\e924\";\n}\n.zwicon-collapse-left:before {\n  content: \"\\e925\";\n}\n.zwicon-collapse-right:before {\n  content: \"\\e926\";\n}\n.zwicon-collapse-up:before {\n  content: \"\\e927\";\n}\n.zwicon-collapse:before {\n  content: \"\\e928\";\n}\n.zwicon-continue:before {\n  content: \"\\e929\";\n}\n.zwicon-expand-alt:before {\n  content: \"\\e92a\";\n}\n.zwicon-expand-alt2:before {\n  content: \"\\e92b\";\n}\n.zwicon-expand-down:before {\n  content: \"\\e92c\";\n}\n.zwicon-expand-h:before {\n  content: \"\\e92d\";\n}\n.zwicon-expand-left:before {\n  content: \"\\e92e\";\n}\n.zwicon-expand-right:before {\n  content: \"\\e92f\";\n}\n.zwicon-expand-up:before {\n  content: \"\\e930\";\n}\n.zwicon-expand-v:before {\n  content: \"\\e931\";\n}\n.zwicon-expand:before {\n  content: \"\\e932\";\n}\n.zwicon-loop:before {\n  content: \"\\e933\";\n}\n.zwicon-prioritize-down:before {\n  content: \"\\e934\";\n}\n.zwicon-prioritize-up:before {\n  content: \"\\e935\";\n}\n.zwicon-redo:before {\n  content: \"\\e936\";\n}\n.zwicon-refresh-double:before {\n  content: \"\\e937\";\n}\n.zwicon-refresh-left:before {\n  content: \"\\e938\";\n}\n.zwicon-refresh-right:before {\n  content: \"\\e939\";\n}\n.zwicon-restart:before {\n  content: \"\\e93a\";\n}\n.zwicon-split-h:before {\n  content: \"\\e93b\";\n}\n.zwicon-split-v:before {\n  content: \"\\e93c\";\n}\n.zwicon-undo:before {\n  content: \"\\e93d\";\n}\n.zwicon-cell-border-bottom:before {\n  content: \"\\e93e\";\n}\n.zwicon-cell-border-full:before {\n  content: \"\\e93f\";\n}\n.zwicon-cell-border-left:before {\n  content: \"\\e940\";\n}\n.zwicon-cell-border-right:before {\n  content: \"\\e941\";\n}\n.zwicon-cell-border-top:before {\n  content: \"\\e942\";\n}\n.zwicon-cell-empty:before {\n  content: \"\\e943\";\n}\n.zwicon-cell-full:before {\n  content: \"\\e944\";\n}\n.zwicon-cell-split-h:before {\n  content: \"\\e945\";\n}\n.zwicon-cell-split-v:before {\n  content: \"\\e946\";\n}\n.zwicon-cell-split:before {\n  content: \"\\e947\";\n}\n.zwicon-archive:before {\n  content: \"\\e948\";\n}\n.zwicon-document:before {\n  content: \"\\e949\";\n}\n.zwicon-folder-delete:before {\n  content: \"\\e94a\";\n}\n.zwicon-folder-minus:before {\n  content: \"\\e94b\";\n}\n.zwicon-folder-open:before {\n  content: \"\\e94c\";\n}\n.zwicon-folder-plus:before {\n  content: \"\\e94d\";\n}\n.zwicon-folder:before {\n  content: \"\\e94e\";\n}\n.zwicon-note:before {\n  content: \"\\e94f\";\n}\n.zwicon-notebook:before {\n  content: \"\\e950\";\n}\n.zwicon-script:before {\n  content: \"\\e951\";\n}\n.zwicon-sticker:before {\n  content: \"\\e952\";\n}\n.zwicon-sticky-notes:before {\n  content: \"\\e953\";\n}\n.zwicon-tray-delete:before {\n  content: \"\\e954\";\n}\n.zwicon-tray-empty:before {\n  content: \"\\e955\";\n}\n.zwicon-tray-export:before {\n  content: \"\\e956\";\n}\n.zwicon-tray-import:before {\n  content: \"\\e957\";\n}\n.zwicon-tray-minus:before {\n  content: \"\\e958\";\n}\n.zwicon-tray-plus:before {\n  content: \"\\e959\";\n}\n.zwicon-tray-stack:before {\n  content: \"\\e95a\";\n}\n.zwicon-tray:before {\n  content: \"\\e95b\";\n}\n.zwicon-artboard:before {\n  content: \"\\e95c\";\n}\n.zwicon-brush:before {\n  content: \"\\e95d\";\n}\n.zwicon-clipboard:before {\n  content: \"\\e95e\";\n}\n.zwicon-copy-alt:before {\n  content: \"\\e95f\";\n}\n.zwicon-copy:before {\n  content: \"\\e960\";\n}\n.zwicon-crop:before {\n  content: \"\\e961\";\n}\n.zwicon-cut-alt:before {\n  content: \"\\e962\";\n}\n.zwicon-cut:before {\n  content: \"\\e963\";\n}\n.zwicon-drafting-compass:before {\n  content: \"\\e964\";\n}\n.zwicon-duplicate-alt:before {\n  content: \"\\e965\";\n}\n.zwicon-duplicate:before {\n  content: \"\\e966\";\n}\n.zwicon-eraser:before {\n  content: \"\\e967\";\n}\n.zwicon-eye-dropper:before {\n  content: \"\\e968\";\n}\n.zwicon-group:before {\n  content: \"\\e969\";\n}\n.zwicon-layer-stack:before {\n  content: \"\\e96a\";\n}\n.zwicon-magic-wand:before {\n  content: \"\\e96b\";\n}\n.zwicon-marker:before {\n  content: \"\\e96c\";\n}\n.zwicon-paint-bucket:before {\n  content: \"\\e96d\";\n}\n.zwicon-paint-roller:before {\n  content: \"\\e96e\";\n}\n.zwicon-palette:before {\n  content: \"\\e96f\";\n}\n.zwicon-paste-alt:before {\n  content: \"\\e970\";\n}\n.zwicon-paste:before {\n  content: \"\\e971\";\n}\n.zwicon-pen-circle:before {\n  content: \"\\e972\";\n}\n.zwicon-pen:before {\n  content: \"\\e973\";\n}\n.zwicon-pencil-circle:before {\n  content: \"\\e974\";\n}\n.zwicon-pencil:before {\n  content: \"\\e975\";\n}\n.zwicon-ruler-combined:before {\n  content: \"\\e976\";\n}\n.zwicon-ruler-h:before {\n  content: \"\\e977\";\n}\n.zwicon-ruler-v:before {\n  content: \"\\e978\";\n}\n.zwicon-stamp:before {\n  content: \"\\e979\";\n}\n.zwicon-table:before {\n  content: \"\\e97a\";\n}\n.zwicon-activity:before {\n  content: \"\\e97b\";\n}\n.zwicon-android:before {\n  content: \"\\e97c\";\n}\n.zwicon-apple:before {\n  content: \"\\e97d\";\n}\n.zwicon-bolt:before {\n  content: \"\\e97e\";\n}\n.zwicon-cloud-download:before {\n  content: \"\\e97f\";\n}\n.zwicon-cloud-minus:before {\n  content: \"\\e980\";\n}\n.zwicon-cloud-plus:before {\n  content: \"\\e981\";\n}\n.zwicon-cloud-upload:before {\n  content: \"\\e982\";\n}\n.zwicon-code:before {\n  content: \"\\e983\";\n}\n.zwicon-command:before {\n  content: \"\\e984\";\n}\n.zwicon-database:before {\n  content: \"\\e985\";\n}\n.zwicon-deploy:before {\n  content: \"\\e986\";\n}\n.zwicon-git-branch:before {\n  content: \"\\e987\";\n}\n.zwicon-git-commit:before {\n  content: \"\\e988\";\n}\n.zwicon-git-fork:before {\n  content: \"\\e989\";\n}\n.zwicon-git-merge:before {\n  content: \"\\e98a\";\n}\n.zwicon-git-pull:before {\n  content: \"\\e98b\";\n}\n.zwicon-ios:before {\n  content: \"\\e98c\";\n}\n.zwicon-lan-connection:before {\n  content: \"\\e98d\";\n}\n.zwicon-lan-error:before {\n  content: \"\\e98e\";\n}\n.zwicon-lan:before {\n  content: \"\\e98f\";\n}\n.zwicon-osx:before {\n  content: \"\\e990\";\n}\n.zwicon-repository:before {\n  content: \"\\e991\";\n}\n.zwicon-web:before {\n  content: \"\\e992\";\n}\n.zwicon-window-delete:before {\n  content: \"\\e993\";\n}\n.zwicon-window-minus:before {\n  content: \"\\e994\";\n}\n.zwicon-window-plus:before {\n  content: \"\\e995\";\n}\n.zwicon-window:before {\n  content: \"\\e996\";\n}\n.zwicon-windows:before {\n  content: \"\\e997\";\n}\n.zwicon-airpods-alt:before {\n  content: \"\\e998\";\n}\n.zwicon-airpods:before {\n  content: \"\\e999\";\n}\n.zwicon-apple-watch-smile:before {\n  content: \"\\e99a\";\n}\n.zwicon-apple-watch-time:before {\n  content: \"\\e99b\";\n}\n.zwicon-apple-watch:before {\n  content: \"\\e99c\";\n}\n.zwicon-cable-hdmi:before {\n  content: \"\\e99d\";\n}\n.zwicon-cable-jack:before {\n  content: \"\\e99e\";\n}\n.zwicon-cable-lan:before {\n  content: \"\\e99f\";\n}\n.zwicon-cable-lightning:before {\n  content: \"\\e9a0\";\n}\n.zwicon-cable-magsafe:before {\n  content: \"\\e9a1\";\n}\n.zwicon-cable-usb:before {\n  content: \"\\e9a2\";\n}\n.zwicon-cardboard-vr:before {\n  content: \"\\e9a3\";\n}\n.zwicon-controller-alt:before {\n  content: \"\\e9a4\";\n}\n.zwicon-controller:before {\n  content: \"\\e9a5\";\n}\n.zwicon-desktop:before {\n  content: \"\\e9a6\";\n}\n.zwicon-devices:before {\n  content: \"\\e9a7\";\n}\n.zwicon-floppy:before {\n  content: \"\\e9a8\";\n}\n.zwicon-gameboy:before {\n  content: \"\\e9a9\";\n}\n.zwicon-hard-drive:before {\n  content: \"\\e9aa\";\n}\n.zwicon-headphone:before {\n  content: \"\\e9ab\";\n}\n.zwicon-imac:before {\n  content: \"\\e9ac\";\n}\n.zwicon-ipad-h:before {\n  content: \"\\e9ad\";\n}\n.zwicon-ipad:before {\n  content: \"\\e9ae\";\n}\n.zwicon-iphone-h:before {\n  content: \"\\e9af\";\n}\n.zwicon-iphone-x-h:before {\n  content: \"\\e9b0\";\n}\n.zwicon-iphone-x:before {\n  content: \"\\e9b1\";\n}\n.zwicon-iphone:before {\n  content: \"\\e9b2\";\n}\n.zwicon-keyboard:before {\n  content: \"\\e9b3\";\n}\n.zwicon-laptop:before {\n  content: \"\\e9b4\";\n}\n.zwicon-mac-pro:before {\n  content: \"\\e9b5\";\n}\n.zwicon-macbook-pro:before {\n  content: \"\\e9b6\";\n}\n.zwicon-memory-card:before {\n  content: \"\\e9b7\";\n}\n.zwicon-mouse:before {\n  content: \"\\e9b8\";\n}\n.zwicon-phone-andorid-h:before {\n  content: \"\\e9b9\";\n}\n.zwicon-phone-andorid:before {\n  content: \"\\e9ba\";\n}\n.zwicon-phone-holding-double:before {\n  content: \"\\e9bb\";\n}\n.zwicon-phone-holding:before {\n  content: \"\\e9bc\";\n}\n.zwicon-plug:before {\n  content: \"\\e9bd\";\n}\n.zwicon-printer:before {\n  content: \"\\e9be\";\n}\n.zwicon-server-stack:before {\n  content: \"\\e9bf\";\n}\n.zwicon-smart-glasses:before {\n  content: \"\\e9c0\";\n}\n.zwicon-smart-tv:before {\n  content: \"\\e9c1\";\n}\n.zwicon-smart-watch-time:before {\n  content: \"\\e9c2\";\n}\n.zwicon-smart-watch:before {\n  content: \"\\e9c3\";\n}\n.zwicon-tablet-h:before {\n  content: \"\\e9c4\";\n}\n.zwicon-tablet:before {\n  content: \"\\e9c5\";\n}\n.zwicon-terminal:before {\n  content: \"\\e9c6\";\n}\n.zwicon-virtual-reality:before {\n  content: \"\\e9c7\";\n}\n.zwicon-voice-assistant:before {\n  content: \"\\e9c8\";\n}\n.zwicon-edit-circle:before {\n  content: \"\\e9c9\";\n}\n.zwicon-edit-pencil:before {\n  content: \"\\e9ca\";\n}\n.zwicon-edit-square-feather:before {\n  content: \"\\e9cb\";\n}\n.zwicon-edit-square:before {\n  content: \"\\e9cc\";\n}\n.zwicon-file-archive:before {\n  content: \"\\e9cd\";\n}\n.zwicon-file-audio:before {\n  content: \"\\e9ce\";\n}\n.zwicon-file-cloud:before {\n  content: \"\\e9cf\";\n}\n.zwicon-file-download:before {\n  content: \"\\e9d0\";\n}\n.zwicon-file-empty:before {\n  content: \"\\e9d1\";\n}\n.zwicon-file-export:before {\n  content: \"\\e9d2\";\n}\n.zwicon-file-font:before {\n  content: \"\\e9d3\";\n}\n.zwicon-file-graphic:before {\n  content: \"\\e9d4\";\n}\n.zwicon-file-image:before {\n  content: \"\\e9d5\";\n}\n.zwicon-file-import:before {\n  content: \"\\e9d6\";\n}\n.zwicon-file-pdf:before {\n  content: \"\\e9d7\";\n}\n.zwicon-file-search:before {\n  content: \"\\e9d8\";\n}\n.zwicon-file-sketch:before {\n  content: \"\\e9d9\";\n}\n.zwicon-file-table:before {\n  content: \"\\e9da\";\n}\n.zwicon-file-upload:before {\n  content: \"\\e9db\";\n}\n.zwicon-file-vector:before {\n  content: \"\\e9dc\";\n}\n.zwicon-file-video:before {\n  content: \"\\e9dd\";\n}\n.zwicon-filter-alt:before {\n  content: \"\\e9de\";\n}\n.zwicon-filter:before {\n  content: \"\\e9df\";\n}\n.zwicon-slider-circle-h:before {\n  content: \"\\e9e0\";\n}\n.zwicon-slider-circle-v:before {\n  content: \"\\e9e1\";\n}\n.zwicon-slider-rectangle-h:before {\n  content: \"\\e9e2\";\n}\n.zwicon-slider-rectangle-v:before {\n  content: \"\\e9e3\";\n}\n.zwicon-sort-alphabetic-down:before {\n  content: \"\\e9e4\";\n}\n.zwicon-sort-alphabetic-up:before {\n  content: \"\\e9e5\";\n}\n.zwicon-sort-amount-down:before {\n  content: \"\\e9e6\";\n}\n.zwicon-sort-amount-up:before {\n  content: \"\\e9e7\";\n}\n.zwicon-sort-numeric-down:before {\n  content: \"\\e9e8\";\n}\n.zwicon-sort-numeric-up:before {\n  content: \"\\e9e9\";\n}\n.zwicon-toggle-switch:before {\n  content: \"\\e9ea\";\n}\n.zwicon-bar-code-scan:before {\n  content: \"\\e9eb\";\n}\n.zwicon-bar-code:before {\n  content: \"\\e9ec\";\n}\n.zwicon-bid:before {\n  content: \"\\e9ed\";\n}\n.zwicon-bill:before {\n  content: \"\\e9ee\";\n}\n.zwicon-bitcoin-sign:before {\n  content: \"\\e9ef\";\n}\n.zwicon-bull-horn:before {\n  content: \"\\e9f0\";\n}\n.zwicon-coin:before {\n  content: \"\\e9f1\";\n}\n.zwicon-credit-card:before {\n  content: \"\\e9f2\";\n}\n.zwicon-diamond:before {\n  content: \"\\e9f3\";\n}\n.zwicon-dollar-sign:before {\n  content: \"\\e9f4\";\n}\n.zwicon-euro-sign:before {\n  content: \"\\e9f5\";\n}\n.zwicon-hammer:before {\n  content: \"\\e9f6\";\n}\n.zwicon-line-chart:before {\n  content: \"\\e9f7\";\n}\n.zwicon-lira-sign:before {\n  content: \"\\e9f8\";\n}\n.zwicon-money-bill:before {\n  content: \"\\e9f9\";\n}\n.zwicon-money-stack:before {\n  content: \"\\e9fa\";\n}\n.zwicon-package:before {\n  content: \"\\e9fb\";\n}\n.zwicon-piggy-bank:before {\n  content: \"\\e9fc\";\n}\n.zwicon-pound-sign:before {\n  content: \"\\e9fd\";\n}\n.zwicon-price-tag:before {\n  content: \"\\e9fe\";\n}\n.zwicon-qr-code-scan:before {\n  content: \"\\e9ff\";\n}\n.zwicon-qr-code:before {\n  content: \"\\ea00\";\n}\n.zwicon-receipt:before {\n  content: \"\\ea01\";\n}\n.zwicon-rubel-sign:before {\n  content: \"\\ea02\";\n}\n.zwicon-rupee-sign:before {\n  content: \"\\ea03\";\n}\n.zwicon-sale-badge:before {\n  content: \"\\ea04\";\n}\n.zwicon-shopping-bag-alt:before {\n  content: \"\\ea05\";\n}\n.zwicon-shopping-bag:before {\n  content: \"\\ea06\";\n}\n.zwicon-shopping-cart:before {\n  content: \"\\ea07\";\n}\n.zwicon-store:before {\n  content: \"\\ea08\";\n}\n.zwicon-wallet:before {\n  content: \"\\ea09\";\n}\n.zwicon-won-sign:before {\n  content: \"\\ea0a\";\n}\n.zwicon-yen-sign:before {\n  content: \"\\ea0b\";\n}\n.zwicon-flip-left-alt:before {\n  content: \"\\ea0c\";\n}\n.zwicon-flip-left:before {\n  content: \"\\ea0d\";\n}\n.zwicon-flip-right-alt:before {\n  content: \"\\ea0e\";\n}\n.zwicon-flip-right:before {\n  content: \"\\ea0f\";\n}\n.zwicon-double-tap-two:before {\n  content: \"\\ea10\";\n}\n.zwicon-double-tap:before {\n  content: \"\\ea11\";\n}\n.zwicon-drag:before {\n  content: \"\\ea12\";\n}\n.zwicon-flick-left-two:before {\n  content: \"\\ea13\";\n}\n.zwicon-flick-left:before {\n  content: \"\\ea14\";\n}\n.zwicon-flick-right-two:before {\n  content: \"\\ea15\";\n}\n.zwicon-flick-right:before {\n  content: \"\\ea16\";\n}\n.zwicon-horns:before {\n  content: \"\\ea17\";\n}\n.zwicon-pinch:before {\n  content: \"\\ea18\";\n}\n.zwicon-point:before {\n  content: \"\\ea19\";\n}\n.zwicon-press:before {\n  content: \"\\ea1a\";\n}\n.zwicon-scroll-down-two:before {\n  content: \"\\ea1b\";\n}\n.zwicon-scroll-down:before {\n  content: \"\\ea1c\";\n}\n.zwicon-scroll-h-two:before {\n  content: \"\\ea1d\";\n}\n.zwicon-scroll-h:before {\n  content: \"\\ea1e\";\n}\n.zwicon-scroll-up-two:before {\n  content: \"\\ea1f\";\n}\n.zwicon-scroll-up:before {\n  content: \"\\ea20\";\n}\n.zwicon-scroll-v-two:before {\n  content: \"\\ea21\";\n}\n.zwicon-scroll-v:before {\n  content: \"\\ea22\";\n}\n.zwicon-shaka:before {\n  content: \"\\ea23\";\n}\n.zwicon-spread:before {\n  content: \"\\ea24\";\n}\n.zwicon-tap-two:before {\n  content: \"\\ea25\";\n}\n.zwicon-tap:before {\n  content: \"\\ea26\";\n}\n.zwicon-two-drag:before {\n  content: \"\\ea27\";\n}\n.zwicon-add-item-alt:before {\n  content: \"\\ea28\";\n}\n.zwicon-add-item:before {\n  content: \"\\ea29\";\n}\n.zwicon-add-note:before {\n  content: \"\\ea2a\";\n}\n.zwicon-add-to-list:before {\n  content: \"\\ea2b\";\n}\n.zwicon-at:before {\n  content: \"\\ea2c\";\n}\n.zwicon-attach-document:before {\n  content: \"\\ea2d\";\n}\n.zwicon-paperclip:before {\n  content: \"\\ea2e\";\n}\n.zwicon-battery-full:before {\n  content: \"\\ea30\";\n}\n.zwicon-battery-low:before {\n  content: \"\\ea31\";\n}\n.zwicon-battery-mid:before {\n  content: \"\\ea32\";\n}\n.zwicon-battery-v:before {\n  content: \"\\ea33\";\n}\n.zwicon-bell-alt-ring:before {\n  content: \"\\ea34\";\n}\n.zwicon-bell-alt:before {\n  content: \"\\ea35\";\n}\n.zwicon-bell-slash:before {\n  content: \"\\ea36\";\n}\n.zwicon-bell-snooze:before {\n  content: \"\\ea37\";\n}\n.zwicon-bell:before {\n  content: \"\\ea38\";\n}\n.zwicon-block:before {\n  content: \"\\ea39\";\n}\n.zwicon-book-alt:before {\n  content: \"\\ea3a\";\n}\n.zwicon-book:before {\n  content: \"\\ea3b\";\n}\n.zwicon-bookmark:before {\n  content: \"\\ea3c\";\n}\n.zwicon-briefcase:before {\n  content: \"\\ea3d\";\n}\n.zwicon-calendar-day:before {\n  content: \"\\ea3e\";\n}\n.zwicon-calendar-month:before {\n  content: \"\\ea3f\";\n}\n.zwicon-calendar-never:before {\n  content: \"\\ea40\";\n}\n.zwicon-calendar-week:before {\n  content: \"\\ea41\";\n}\n.zwicon-calendar:before {\n  content: \"\\ea42\";\n}\n.zwicon-call-in:before {\n  content: \"\\ea43\";\n}\n.zwicon-call-out:before {\n  content: \"\\ea44\";\n}\n.zwicon-chat:before {\n  content: \"\\ea45\";\n}\n.zwicon-checkmark-circle:before {\n  content: \"\\ea46\";\n}\n.zwicon-checkmark-square:before {\n  content: \"\\ea47\";\n}\n.zwicon-checkmark:before {\n  content: \"\\ea48\";\n}\n.zwicon-clock:before {\n  content: \"\\ea49\";\n}\n.zwicon-close-circle:before {\n  content: \"\\ea4a\";\n}\n.zwicon-close-square:before {\n  content: \"\\ea4b\";\n}\n.zwicon-close:before {\n  content: \"\\ea4c\";\n}\n.zwicon-cog:before {\n  content: \"\\ea4d\";\n}\n.zwicon-comment:before {\n  content: \"\\ea4e\";\n}\n.zwicon-compass:before {\n  content: \"\\ea4f\";\n}\n.zwicon-delete:before {\n  content: \"\\ea50\";\n}\n.zwicon-download:before {\n  content: \"\\ea51\";\n}\n.zwicon-earth-alt:before {\n  content: \"\\ea52\";\n}\n.zwicon-earth:before {\n  content: \"\\ea53\";\n}\n.zwicon-exclamation-triangle:before {\n  content: \"\\ea54\";\n}\n.zwicon-exclamation-mark:before {\n  content: \"\\ea2f\";\n}\n.zwicon-export:before {\n  content: \"\\ea55\";\n}\n.zwicon-eye-slash:before {\n  content: \"\\ea56\";\n}\n.zwicon-eye:before {\n  content: \"\\ea57\";\n}\n.zwicon-face-id:before {\n  content: \"\\ea58\";\n}\n.zwicon-flag:before {\n  content: \"\\ea59\";\n}\n.zwicon-grid:before {\n  content: \"\\ea5a\";\n}\n.zwicon-hamburger-menu:before {\n  content: \"\\ea5b\";\n}\n.zwicon-heart:before {\n  content: \"\\ea5c\";\n}\n.zwicon-home:before {\n  content: \"\\ea5d\";\n}\n.zwicon-import:before {\n  content: \"\\ea5e\";\n}\n.zwicon-info-circle:before {\n  content: \"\\ea5f\";\n}\n.zwicon-lifebelt:before {\n  content: \"\\ea60\";\n}\n.zwicon-link:before {\n  content: \"\\ea61\";\n}\n.zwicon-lock-alt:before {\n  content: \"\\ea62\";\n}\n.zwicon-lock:before {\n  content: \"\\ea63\";\n}\n.zwicon-mail:before {\n  content: \"\\ea64\";\n}\n.zwicon-map-marker:before {\n  content: \"\\ea65\";\n}\n.zwicon-minus-circle:before {\n  content: \"\\ea66\";\n}\n.zwicon-minus-square:before {\n  content: \"\\ea67\";\n}\n.zwicon-minus:before {\n  content: \"\\ea68\";\n}\n.zwicon-more-h:before {\n  content: \"\\ea69\";\n}\n.zwicon-more-v:before {\n  content: \"\\ea6a\";\n}\n.zwicon-my-location:before {\n  content: \"\\ea6b\";\n}\n.zwicon-password:before {\n  content: \"\\ea6c\";\n}\n.zwicon-phone:before {\n  content: \"\\ea6d\";\n}\n.zwicon-pin:before {\n  content: \"\\ea6e\";\n}\n.zwicon-plus-circle:before {\n  content: \"\\ea6f\";\n}\n.zwicon-plus-square:before {\n  content: \"\\ea70\";\n}\n.zwicon-plus:before {\n  content: \"\\ea71\";\n}\n.zwicon-search:before {\n  content: \"\\ea72\";\n}\n.zwicon-send:before {\n  content: \"\\ea73\";\n}\n.zwicon-share:before {\n  content: \"\\ea74\";\n}\n.zwicon-shortcut:before {\n  content: \"\\ea75\";\n}\n.zwicon-sign-in:before {\n  content: \"\\ea76\";\n}\n.zwicon-sign-out:before {\n  content: \"\\ea77\";\n}\n.zwicon-thumbs-down:before {\n  content: \"\\ea78\";\n}\n.zwicon-thumbs-up:before {\n  content: \"\\ea79\";\n}\n.zwicon-trash:before {\n  content: \"\\ea7a\";\n}\n.zwicon-unlink:before {\n  content: \"\\ea7b\";\n}\n.zwicon-upload:before {\n  content: \"\\ea7c\";\n}\n.zwicon-user-circle:before {\n  content: \"\\ea7d\";\n}\n.zwicon-user-delete:before {\n  content: \"\\ea7e\";\n}\n.zwicon-user-follow:before {\n  content: \"\\ea7f\";\n}\n.zwicon-user-minus:before {\n  content: \"\\ea80\";\n}\n.zwicon-user-plus:before {\n  content: \"\\ea81\";\n}\n.zwicon-user:before {\n  content: \"\\ea82\";\n}\n.zwicon-users:before {\n  content: \"\\ea83\";\n}\n.zwicon-history:before {\n  content: \"\\ea84\";\n}\n.zwicon-task:before {\n  content: \"\\ea85\";\n}\n.zwicon-bottom-bar:before {\n  content: \"\\ea86\";\n}\n.zwicon-content-left:before {\n  content: \"\\ea87\";\n}\n.zwicon-content-right:before {\n  content: \"\\ea88\";\n}\n.zwicon-desktop-1:before {\n  content: \"\\ea89\";\n}\n.zwicon-desktop-2:before {\n  content: \"\\ea8a\";\n}\n.zwicon-desktop-3:before {\n  content: \"\\ea8b\";\n}\n.zwicon-half-h:before {\n  content: \"\\ea8c\";\n}\n.zwicon-half-v:before {\n  content: \"\\ea8d\";\n}\n.zwicon-layout-1:before {\n  content: \"\\ea8e\";\n}\n.zwicon-layout-2:before {\n  content: \"\\ea8f\";\n}\n.zwicon-layout-3:before {\n  content: \"\\ea90\";\n}\n.zwicon-layout-4:before {\n  content: \"\\ea91\";\n}\n.zwicon-layout-5:before {\n  content: \"\\ea92\";\n}\n.zwicon-left-bar:before {\n  content: \"\\ea93\";\n}\n.zwicon-margin:before {\n  content: \"\\ea94\";\n}\n.zwicon-right-bar:before {\n  content: \"\\ea95\";\n}\n.zwicon-sidebar:before {\n  content: \"\\ea96\";\n}\n.zwicon-three-h:before {\n  content: \"\\ea97\";\n}\n.zwicon-three-v:before {\n  content: \"\\ea98\";\n}\n.zwicon-to-bottom:before {\n  content: \"\\ea99\";\n}\n.zwicon-to-left:before {\n  content: \"\\ea9a\";\n}\n.zwicon-to-right:before {\n  content: \"\\ea9b\";\n}\n.zwicon-to-top:before {\n  content: \"\\ea9c\";\n}\n.zwicon-top-bar:before {\n  content: \"\\ea9d\";\n}\n.zwicon-airplay:before {\n  content: \"\\ea9e\";\n}\n.zwicon-broadcast:before {\n  content: \"\\ea9f\";\n}\n.zwicon-camera-alt:before {\n  content: \"\\eaa0\";\n}\n.zwicon-camera-alt2:before {\n  content: \"\\eaa1\";\n}\n.zwicon-camera:before {\n  content: \"\\eaa2\";\n}\n.zwicon-cast:before {\n  content: \"\\eaa3\";\n}\n.zwicon-collapse-wide:before {\n  content: \"\\eaa4\";\n}\n.zwicon-collapse1:before {\n  content: \"\\eaa5\";\n}\n.zwicon-disk:before {\n  content: \"\\eaa6\";\n}\n.zwicon-expand-wide:before {\n  content: \"\\eaa7\";\n}\n.zwicon-expand1:before {\n  content: \"\\eaa8\";\n}\n.zwicon-film-alt:before {\n  content: \"\\eaa9\";\n}\n.zwicon-film-play:before {\n  content: \"\\eaaa\";\n}\n.zwicon-film:before {\n  content: \"\\eaab\";\n}\n.zwicon-image-circle:before {\n  content: \"\\eaac\";\n}\n.zwicon-image-gallery:before {\n  content: \"\\eaad\";\n}\n.zwicon-image-wide:before {\n  content: \"\\eaae\";\n}\n.zwicon-image:before {\n  content: \"\\eaaf\";\n}\n.zwicon-microphone-mute:before {\n  content: \"\\eab0\";\n}\n.zwicon-microphone:before {\n  content: \"\\eab1\";\n}\n.zwicon-next-alt:before {\n  content: \"\\eab2\";\n}\n.zwicon-next:before {\n  content: \"\\eab3\";\n}\n.zwicon-panorama-h:before {\n  content: \"\\eab4\";\n}\n.zwicon-pause-alt:before {\n  content: \"\\eab5\";\n}\n.zwicon-pause:before {\n  content: \"\\eab6\";\n}\n.zwicon-play-alt:before {\n  content: \"\\eab7\";\n}\n.zwicon-play:before {\n  content: \"\\eab8\";\n}\n.zwicon-previous-alt:before {\n  content: \"\\eab9\";\n}\n.zwicon-previous:before {\n  content: \"\\eaba\";\n}\n.zwicon-shuffle:before {\n  content: \"\\eabb\";\n}\n.zwicon-video-alt:before {\n  content: \"\\eabc\";\n}\n.zwicon-video-camera:before {\n  content: \"\\eabd\";\n}\n.zwicon-video:before {\n  content: \"\\eabe\";\n}\n.zwicon-volume-low:before {\n  content: \"\\eabf\";\n}\n.zwicon-volume-max:before {\n  content: \"\\eac0\";\n}\n.zwicon-volume-mid:before {\n  content: \"\\eac1\";\n}\n.zwicon-volume-min:before {\n  content: \"\\eac2\";\n}\n.zwicon-wide-angle:before {\n  content: \"\\eac3\";\n}\n.zwicon-exclude:before {\n  content: \"\\eac4\";\n}\n.zwicon-flatten:before {\n  content: \"\\eac5\";\n}\n.zwicon-intersect:before {\n  content: \"\\eac6\";\n}\n.zwicon-substract-back:before {\n  content: \"\\eac7\";\n}\n.zwicon-substract-front:before {\n  content: \"\\eac8\";\n}\n.zwicon-unite:before {\n  content: \"\\eac9\";\n}\n.zwicon-height:before {\n  content: \"\\eaca\";\n}\n.zwicon-resize-alt:before {\n  content: \"\\eacb\";\n}\n.zwicon-resize:before {\n  content: \"\\eacc\";\n}\n.zwicon-scale-down:before {\n  content: \"\\eacd\";\n}\n.zwicon-scale-up:before {\n  content: \"\\eace\";\n}\n.zwicon-scale:before {\n  content: \"\\eacf\";\n}\n.zwicon-width:before {\n  content: \"\\ead0\";\n}\n.zwicon-rotate-axis-x:before {\n  content: \"\\ead1\";\n}\n.zwicon-rotate-axis-xy:before {\n  content: \"\\ead2\";\n}\n.zwicon-rotate-axis-y:before {\n  content: \"\\ead3\";\n}\n.zwicon-rotate-left:before {\n  content: \"\\ead4\";\n}\n.zwicon-rotate-right:before {\n  content: \"\\ead5\";\n}\n.zwicon-rotate-shape:before {\n  content: \"\\ead6\";\n}\n.zwicon-cursor-square:before {\n  content: \"\\ead7\";\n}\n.zwicon-cursor:before {\n  content: \"\\ead8\";\n}\n.zwicon-select-cursor:before {\n  content: \"\\ead9\";\n}\n.zwicon-select:before {\n  content: \"\\eada\";\n}\n.zwicon-shape-circle:before {\n  content: \"\\eadb\";\n}\n.zwicon-shape-cone:before {\n  content: \"\\eadc\";\n}\n.zwicon-shape-cube:before {\n  content: \"\\eadd\";\n}\n.zwicon-shape-cylinder:before {\n  content: \"\\eade\";\n}\n.zwicon-shape-octagonal:before {\n  content: \"\\eadf\";\n}\n.zwicon-shape-polygon:before {\n  content: \"\\eae0\";\n}\n.zwicon-shape-sphere:before {\n  content: \"\\eae1\";\n}\n.zwicon-shape-square:before {\n  content: \"\\eae2\";\n}\n.zwicon-laugh:before {\n  content: \"\\eae3\";\n}\n.zwicon-neutral:before {\n  content: \"\\eae4\";\n}\n.zwicon-sad:before {\n  content: \"\\eae5\";\n}\n.zwicon-smile:before {\n  content: \"\\eae6\";\n}\n.zwicon-bold:before {\n  content: \"\\eae7\";\n}\n.zwicon-draw-text-field:before {\n  content: \"\\eae8\";\n}\n.zwicon-font-height:before {\n  content: \"\\eae9\";\n}\n.zwicon-font-size:before {\n  content: \"\\eaea\";\n}\n.zwicon-font-width:before {\n  content: \"\\eaeb\";\n}\n.zwicon-font:before {\n  content: \"\\eaec\";\n}\n.zwicon-heading:before {\n  content: \"\\eaed\";\n}\n.zwicon-indent-left-alt:before {\n  content: \"\\eaee\";\n}\n.zwicon-indent-left:before {\n  content: \"\\eaef\";\n}\n.zwicon-indent-right-alt:before {\n  content: \"\\eaf0\";\n}\n.zwicon-indent-right:before {\n  content: \"\\eaf1\";\n}\n.zwicon-italic:before {\n  content: \"\\eaf2\";\n}\n.zwicon-list-bullet:before {\n  content: \"\\eaf3\";\n}\n.zwicon-list-number:before {\n  content: \"\\eaf4\";\n}\n.zwicon-outdent-left:before {\n  content: \"\\eaf5\";\n}\n.zwicon-outdent-right:before {\n  content: \"\\eaf6\";\n}\n.zwicon-paragraph:before {\n  content: \"\\eaf7\";\n}\n.zwicon-text-align-center:before {\n  content: \"\\eaf8\";\n}\n.zwicon-text-align-justify:before {\n  content: \"\\eaf9\";\n}\n.zwicon-text-align-left:before {\n  content: \"\\eafa\";\n}\n.zwicon-text-align-right:before {\n  content: \"\\eafb\";\n}\n.zwicon-text-cursor:before {\n  content: \"\\eafc\";\n}\n.zwicon-text-decoration:before {\n  content: \"\\eafd\";\n}\n.zwicon-text-field:before {\n  content: \"\\eafe\";\n}\n.zwicon-text:before {\n  content: \"\\eaff\";\n}\n.zwicon-underline:before {\n  content: \"\\eb00\";\n}\n.zwicon-wrap-img-left:before {\n  content: \"\\eb01\";\n}\n.zwicon-wrap-img-right:before {\n  content: \"\\eb02\";\n}\n.zwicon-wrap-left:before {\n  content: \"\\eb03\";\n}\n.zwicon-wrap-right:before {\n  content: \"\\eb04\";\n}\n.zwicon-transform-left:before {\n  content: \"\\eb05\";\n}\n.zwicon-transform-right:before {\n  content: \"\\eb06\";\n}\n.zwicon-ab-testing:before {\n  content: \"\\eb07\";\n}\n.zwicon-agile:before {\n  content: \"\\eb08\";\n}\n.zwicon-backlog:before {\n  content: \"\\eb09\";\n}\n.zwicon-design-studio:before {\n  content: \"\\eb0a\";\n}\n.zwicon-design-validation:before {\n  content: \"\\eb0b\";\n}\n.zwicon-information-architecture:before {\n  content: \"\\eb0c\";\n}\n.zwicon-interview:before {\n  content: \"\\eb0d\";\n}\n.zwicon-kanban-board:before {\n  content: \"\\eb0e\";\n}\n.zwicon-lego-serious-play:before {\n  content: \"\\eb0f\";\n}\n.zwicon-paper-prototype:before {\n  content: \"\\eb10\";\n}\n.zwicon-persona:before {\n  content: \"\\eb11\";\n}\n.zwicon-prototype-mobile:before {\n  content: \"\\eb12\";\n}\n.zwicon-prototype:before {\n  content: \"\\eb13\";\n}\n.zwicon-responsive:before {\n  content: \"\\eb14\";\n}\n.zwicon-screen-flow:before {\n  content: \"\\eb15\";\n}\n.zwicon-stand-up:before {\n  content: \"\\eb16\";\n}\n.zwicon-sticky-notes1:before {\n  content: \"\\eb17\";\n}\n.zwicon-usability:before {\n  content: \"\\eb18\";\n}\n.zwicon-user-flow:before {\n  content: \"\\eb19\";\n}\n.zwicon-user-interview:before {\n  content: \"\\eb1a\";\n}\n.zwicon-user-journey:before {\n  content: \"\\eb1b\";\n}\n.zwicon-cloud:before {\n  content: \"\\eb1c\";\n}\n.zwicon-cloudy-day:before {\n  content: \"\\eb1d\";\n}\n.zwicon-cloudy-night:before {\n  content: \"\\eb1e\";\n}\n.zwicon-heavy-rain-day:before {\n  content: \"\\eb1f\";\n}\n.zwicon-heavy-rain-night:before {\n  content: \"\\eb20\";\n}\n.zwicon-heavy-rain:before {\n  content: \"\\eb21\";\n}\n.zwicon-heavy-wind:before {\n  content: \"\\eb22\";\n}\n.zwicon-mild-rain-day:before {\n  content: \"\\eb23\";\n}\n.zwicon-mild-rain-night:before {\n  content: \"\\eb24\";\n}\n.zwicon-mild-rain:before {\n  content: \"\\eb25\";\n}\n.zwicon-moon:before {\n  content: \"\\eb26\";\n}\n.zwicon-rain-day:before {\n  content: \"\\eb27\";\n}\n.zwicon-rain-night:before {\n  content: \"\\eb28\";\n}\n.zwicon-rain:before {\n  content: \"\\eb29\";\n}\n.zwicon-snow-day:before {\n  content: \"\\eb2a\";\n}\n.zwicon-snow-night:before {\n  content: \"\\eb2b\";\n}\n.zwicon-snow:before {\n  content: \"\\eb2c\";\n}\n.zwicon-storm-day:before {\n  content: \"\\eb2d\";\n}\n.zwicon-storm-night:before {\n  content: \"\\eb2e\";\n}\n.zwicon-storm:before {\n  content: \"\\eb2f\";\n}\n.zwicon-sun:before {\n  content: \"\\eb30\";\n}\n.zwicon-temperature:before {\n  content: \"\\eb31\";\n}\n.zwicon-wind-alt:before {\n  content: \"\\eb32\";\n}\n.zwicon-wind-cloudy-day:before {\n  content: \"\\eb33\";\n}\n.zwicon-wind-cloudy-night:before {\n  content: \"\\eb34\";\n}\n.zwicon-wind-cloudy:before {\n  content: \"\\eb35\";\n}\n.zwicon-wind:before {\n  content: \"\\eb36\";\n}\n"
  },
  {
    "path": "src/background.ts",
    "content": "import {\n  app, protocol, BrowserWindow, Menu, shell,\n} from 'electron'\nimport {\n  createProtocol,\n} from 'vue-cli-plugin-electron-builder/lib'\nimport { autoUpdater } from 'electron-updater'\nimport { init } from '@sentry/electron/dist/main'\nimport App from './server/app'\nimport messages from './assets/locales-menu'\nimport initServer from './server'\n\ninit({ dsn: 'https://6a6dacc57a6a4e27a88eb31596c152f8@sentry.io/1887150' })\n\nconst isDevelopment = process.env.NODE_ENV !== 'production'\n\n// Keep a global reference of the window object, if you don't, the window will\n// be closed automatically when the JavaScript object is garbage collected.\nlet win: any\nlet menu: Menu\nlet httpServer: any\n\n// Standard scheme must be registered before the app is ready\nprotocol.registerSchemesAsPrivileged([{ scheme: 'app', privileges: { secure: true, standard: true } }])\nfunction createWindow() {\n  // Create the browser window.\n  const winOption: any = {\n    width: 1200,\n    height: 800,\n    minHeight: 642,\n    minWidth: 1000,\n    webPreferences: {\n      webSecurity: false, // FIXED: Not allowed to load local resource\n      nodeIntegration: true,\n      enableRemoteModule: true, // FIXED: 兼容 electron@11.0.1\n    },\n    // frame: false, // 去除默认窗口栏\n    titleBarStyle: 'hiddenInset' as ('hidden' | 'default' | 'hiddenInset' | 'customButtonsOnHover' | undefined),\n  }\n\n  if (process.platform !== 'darwin') {\n    winOption.icon = `${__dirname}/app-icons/gridea.png`\n  }\n\n  win = new BrowserWindow(winOption)\n  win.setTitle('Gridea')\n\n  if (process.env.WEBPACK_DEV_SERVER_URL) {\n    // Load the url of the dev server if in development mode\n    win.loadURL(process.env.WEBPACK_DEV_SERVER_URL as string)\n    if (!process.env.IS_TEST) { win.webContents.openDevTools() }\n  } else {\n    createProtocol('app')\n    // Load the index.html when not in development\n    win.loadURL('app://./index.html')\n    autoUpdater.checkForUpdatesAndNotify()\n  }\n\n  win.on('closed', () => {\n    win = null\n  })\n\n  const locale: string = app.getLocale() || 'zh-CN'\n  const menuLabels = messages[locale] || messages['zh-CN']\n  // menu\n  const template: any = [\n    {\n      label: menuLabels.edit,\n      submenu: [\n        {\n          label: menuLabels.save,\n          accelerator: 'CmdOrCtrl+S',\n          click: () => {\n            win.webContents.send('click-menu-save')\n          },\n        },\n        { type: 'separator' },\n        { role: 'undo', label: menuLabels.undo },\n        { role: 'redo', label: menuLabels.redo },\n        { type: 'separator' },\n        { role: 'cut', label: menuLabels.cut },\n        { role: 'copy', label: menuLabels.copy },\n        { role: 'paste', label: menuLabels.paste },\n        { role: 'delete', label: menuLabels.delete },\n        { role: 'selectall', label: menuLabels.selectall },\n        { role: 'toggledevtools', label: menuLabels.toggledevtools },\n        { type: 'separator' },\n        { role: 'close', label: menuLabels.close },\n        { role: 'quit', label: menuLabels.quit },\n      ],\n    },\n    {\n      role: 'windowMenu',\n    },\n    {\n      role: menuLabels.help,\n      submenu: [\n        {\n          label: 'Learn More',\n          click() { shell.openExternal('https://github.com/getgridea/gridea') },\n        },\n      ],\n    },\n  ]\n\n  menu = Menu.buildFromTemplate(template)\n  Menu.setApplicationMenu(menu)\n\n  const s = initServer()\n  httpServer = s.server\n\n  const setting = {\n    mainWindow: win,\n    app,\n    baseDir: __dirname,\n    previewServer: s.app,\n  }\n\n  // Init app\n  const appInstance = new App(setting)\n  console.log('Main process runing...', appInstance.appDir) // DELETE ME\n}\n\n// Quit when all windows are closed.\napp.on('window-all-closed', () => {\n  httpServer && httpServer.close()\n  // On macOS it is common for applications and their menu bar\n  // to stay active until the user quits explicitly with Cmd + Q\n  if (process.platform !== 'darwin') {\n    app.quit()\n  }\n})\n\napp.on('activate', () => {\n  // On macOS it's common to re-create a window in the app when the\n  // dock icon is clicked and there are no other windows open.\n  if (win === null) {\n    createWindow()\n  }\n})\n\n// This method will be called when Electron has finished\n// initialization and is ready to create browser windows.\n// Some APIs can only be used after this event occurs.\napp.on('ready', async () => {\n  // if (isDevelopment && !process.env.IS_TEST) {\n  //   // Install Vue Devtools\n  //   await installVueDevtools()\n  // }\n  createWindow()\n})\n\n// Exit cleanly on request from parent process in development mode.\nif (isDevelopment) {\n  if (process.platform === 'win32') {\n    process.on('message', (data) => {\n      if (data === 'graceful-exit') {\n        app.quit()\n      }\n    })\n  } else {\n    process.on('SIGTERM', () => {\n      app.quit()\n    })\n  }\n}\n\n// ipcMain.on('min-window', () => {\n//   if (win) {\n//     win.minimize()\n//   }\n// })\n\n// ipcMain.on('max-window', () => {\n//   if (win) {\n//     if (win.isMaximized()) {\n//       win.unmaximize()\n//     } else {\n//       win.maximize()\n//     }\n//   }\n// })\n\n// ipcMain.on('close-window', () => {\n//   if (win) {\n//     win.close()\n//   }\n// })\n\n/**\n * Auto Updater\n *\n * Uncomment the following code below and install `electron-updater` to\n * support auto updating. Code Signing with a valid certificate is required.\n * https://simulatedgreg.gitbooks.io/electron-vue/content/en/using-electron-builder.html#auto-updating\n */\n\n/*\nimport { autoUpdater } from 'electron-updater'\n\nautoUpdater.on('update-downloaded', () => {\n  autoUpdater.quitAndInstall()\n})\n\napp.on('ready', () => {\n  if (process.env.NODE_ENV === 'production') autoUpdater.checkForUpdates()\n})\n */\n"
  },
  {
    "path": "src/components/AppSystem/Index.vue",
    "content": "<template>\n  <div>\n    <language-setting></language-setting>\n    <source-folder-setting></source-folder-setting>\n    <version></version>\n  </div>\n</template>\n\n<script lang=\"ts\">\nimport { Vue, Component } from 'vue-property-decorator'\nimport LanguageSetting from './includes/LanguageSetting.vue'\nimport SourceFolderSetting from './includes/SourceFolderSetting.vue'\nimport Version from './includes/Version.vue'\n\n@Component({\n  components: {\n    LanguageSetting,\n    SourceFolderSetting,\n    Version,\n  },\n})\nexport default class System extends Vue {\n\n}\n</script>\n\n<style lang=\"less\" scoped>\n</style>\n"
  },
  {
    "path": "src/components/AppSystem/includes/LanguageSetting.vue",
    "content": "<template>\n  <div class=\"mb-4 py-4 border-b border-gray-200\">\n    <div class=\"text-base font-medium mb-4\">{{ $t('language') }}</div>\n    <a-form>\n      <a-form-item :labelCol=\"formLayout.label\" :wrapperCol=\"formLayout.wrapper\" :colon=\"false\">\n        <a-radio-group name=\"currentLanguage\" v-model=\"currentLanguage\">\n          <a-radio value=\"zhHans\">简体中文</a-radio>\n          <a-radio value=\"en\">English</a-radio>\n          <a-radio value=\"zh_TW\">繁體中文</a-radio>\n          <a-radio value=\"fr_FR\">Français</a-radio>\n          <a-radio value=\"ja_JP\">日本語</a-radio>\n        </a-radio-group>\n      </a-form-item>\n      <a-form-item :labelCol=\"formLayout.label\" :wrapperCol=\"formLayout.wrapper\" :colon=\"false\">\n        <a-button type=\"primary\" @click=\"saveLanguage\">{{ $t('save') }}</a-button>\n      </a-form-item>\n    </a-form>\n  </div>\n</template>\n\n<script lang=\"ts\">\nimport { Vue, Component } from 'vue-property-decorator'\n\n@Component\nexport default class System extends Vue {\n  formLayout = {\n    label: { span: 5 },\n    wrapper: { span: 12 },\n  }\n\n  currentLanguage = 'zhHans'\n\n  mounted() {\n    console.log(this.$root)\n    this.currentLanguage = localStorage.getItem('language') || 'zhHans'\n  }\n\n  saveLanguage() {\n    localStorage.setItem('language', this.currentLanguage)\n    this.$root.$i18n.locale = this.currentLanguage\n  }\n}\n</script>\n\n<style lang=\"less\" scoped>\n</style>\n"
  },
  {
    "path": "src/components/AppSystem/includes/SourceFolderSetting.vue",
    "content": "<template>\n  <div class=\"mb-4 py-4 border-b border-gray-200\">\n    <div class=\"text-base font-medium mb-4\">{{ $t('sourceFolder') }}</div>\n    <a-form>\n      <a-form-item :labelCol=\"formLayout.label\" :wrapperCol=\"formLayout.wrapper\" :colon=\"false\">\n        <a-input v-model=\"currentFolderPath\" read-only>\n          <i slot=\"addonAfter\" class=\"zwicon-folder-open px-2\" @click=\"handleFolderSelect\"></i>\n        </a-input>\n      </a-form-item>\n      <a-form-item :labelCol=\"formLayout.label\" :wrapperCol=\"formLayout.wrapper\" :colon=\"false\">\n        <a-button type=\"primary\" @click=\"save\">{{ $t('save') }}</a-button>\n      </a-form-item>\n    </a-form>\n  </div>\n</template>\n\n<script lang=\"ts\">\nimport {\n  ipcRenderer, IpcRendererEvent, remote,\n} from 'electron'\nimport { Vue, Component } from 'vue-property-decorator'\nimport { State } from 'vuex-class'\n\n@Component\nexport default class System extends Vue {\n  @State('site') site!: any\n\n  formLayout = {\n    label: { span: 5 },\n    wrapper: { span: 12 },\n  }\n\n  currentFolderPath = '-'\n\n  mounted() {\n    this.currentFolderPath = this.site.appDir\n  }\n\n  save() {\n    ipcRenderer.send('app-source-folder-setting', this.currentFolderPath)\n    ipcRenderer.once('app-source-folder-set', (event: IpcRendererEvent, data: any) => {\n      if (data) {\n        this.$message.success(this.$t('saved'))\n        this.$bus.$emit('site-reload')\n        remote.app.relaunch()\n        remote.app.quit()\n      } else {\n        this.$message.error(this.$t('saveError'))\n      }\n    })\n  }\n\n  async handleFolderSelect() {\n    const res = await remote.dialog.showOpenDialog({\n      properties: ['openDirectory', 'createDirectory'],\n    })\n    if (res.filePaths.length > 0) {\n      this.currentFolderPath = res.filePaths[0].replace(/\\\\/g, '/')\n    }\n  }\n}\n</script>\n\n<style lang=\"less\" scoped>\n/deep/ .ant-input-group-addon {\n  padding: 0;\n}\n.folder-btn {\n  padding: 0 8px;\n}\n</style>\n"
  },
  {
    "path": "src/components/AppSystem/includes/Version.vue",
    "content": "<template>\n  <div class=\"mb-4 py-4 border-b border-gray-200\">\n    <div class=\"text-base font-medium mb-4\">{{ $t('version') }}</div>\n    <div>{{ version }}</div>\n    <a href=\"https://gridea.dev\" target=\"_blank\"> Gridea</a>\n  </div>\n</template>\n\n<script lang=\"ts\">\nimport { Vue, Component } from 'vue-property-decorator'\nimport * as pkg from '../../../../package.json'\n\n@Component\nexport default class System extends Vue {\n  formLayout = {\n    label: { span: 5 },\n    wrapper: { span: 12 },\n  }\n\n  version = (pkg as any).version\n\n  currentLanguage = 'zhHans'\n\n  mounted() {\n    console.log(this.$root)\n    this.currentLanguage = localStorage.getItem('language') || 'zhHans'\n  }\n\n  saveLanguage() {\n    localStorage.setItem('language', this.currentLanguage)\n    this.$root.$i18n.locale = this.currentLanguage\n  }\n}\n</script>\n\n<style lang=\"less\" scoped>\n</style>\n"
  },
  {
    "path": "src/components/ColorCard/Index.vue",
    "content": "<template>\n  <div class=\"color-card\">\n    <div class=\"item-container\" v-for=\"(item, index) in colorList\" :key=\"index\">\n      <div class=\"item\" v-for=\"(color, index1) in item\" :key=\"index1\" @click=\"handleColorSelect(color)\" :style=\"{ 'background-color': color }\"></div>\n    </div>\n  </div>\n</template>\n\n<script lang=\"ts\">\nimport { Vue, Component } from 'vue-property-decorator'\n@Component\nexport default class ColorCard extends Vue {\n  colorList = [\n    ['#f8f9fa', '#f1f3f5', '#e9ecef', '#dee2e6', '#ced4da', '#adb5bd', '#868e96', '#495057', '#343a40', '#212529'],\n    ['#fff5f5', '#ffe3e3', '#ffc9c9', '#ffa8a8', '#ff8787', '#ff6b6b', '#fa5252', '#f03e3e', '#e03131', '#c92a2a'],\n    ['#fff0f6', '#ffdeeb', '#fcc2d7', '#faa2c1', '#f783ac', '#f06595', '#e64980', '#d6336c', '#c2255c', '#a61e4d'],\n    ['#f8f0fc', '#f3d9fa', '#eebefa', '#e599f7', '#da77f2', '#cc5de8', '#be4bdb', '#ae3ec9', '#9c36b5', '#862e9c'],\n    ['#f3f0ff', '#e5dbff', '#d0bfff', '#b197fc', '#9775fa', '#845ef7', '#7950f2', '#7048e8', '#6741d9', '#5f3dc4'],\n    ['#edf2ff', '#dbe4ff', '#bac8ff', '#91a7ff', '#748ffc', '#5c7cfa', '#4c6ef5', '#4263eb', '#3b5bdb', '#364fc7'],\n    ['#e7f5ff', '#d0ebff', '#a5d8ff', '#74c0fc', '#4dabf7', '#339af0', '#228be6', '#1c7ed6', '#1971c2', '#1864ab'],\n    ['#e3fafc', '#c5f6fa', '#99e9f2', '#66d9e8', '#3bc9db', '#22b8cf', '#15aabf', '#1098ad', '#0c8599', '#0b7285'],\n    ['#e6fcf5', '#c3fae8', '#96f2d7', '#63e6be', '#38d9a9', '#20c997', '#12b886', '#0ca678', '#099268', '#087f5b'],\n    ['#ebfbee', '#d3f9d8', '#b2f2bb', '#8ce99a', '#69db7c', '#51cf66', '#40c057', '#37b24d', '#2f9e44', '#2b8a3e'],\n    ['#f4fce3', '#e9fac8', '#d8f5a2', '#c0eb75', '#a9e34b', '#94d82d', '#82c91e', '#74b816', '#66a80f', '#5c940d'],\n    ['#fff9db', '#fff3bf', '#ffec99', '#ffe066', '#ffd43b', '#fcc419', '#fab005', '#f59f00', '#f08c00', '#e67700'],\n    ['#fff4e6', '#ffe8cc', '#ffd8a8', '#ffc078', '#ffa94d', '#ff922b', '#fd7e14', '#f76707', '#e8590c', '#d9480f'],\n  ]\n\n  handleColorSelect(color: string) {\n    this.$emit('change', color)\n  }\n}\n</script>\n\n<style lang=\"less\" scoped>\n.color-card {\n  max-height: 160px;\n  overflow: scroll;\n  margin: -15px;\n}\n.item-container {\n  display: flex;\n  .item {\n    width: 16px;\n    height: 16px;\n    margin: 2px;\n    cursor: pointer;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/components/EmojiCard/Index.vue",
    "content": "<template>\n  <div class=\"emoji-card\">\n    <VEmojiPicker :pack=\"pack\" :showSearch=\"false\" :emojisByRow=\"8\" @select=\"handleSelect\" />\n  </div>\n</template>\n\n<script lang=\"ts\">\nimport {\n  Vue, Component, Prop, Watch, Model,\n} from 'vue-property-decorator'\nimport VEmojiPicker from 'v-emoji-picker'\nimport packData from 'v-emoji-picker/data/emojis.json'\n\n@Component({\n  components: {\n    VEmojiPicker,\n  },\n})\nexport default class EmojiCard extends Vue {\n  pack = packData\n\n  handleSelect(emojiData: any) {\n    this.$emit('select', emojiData.emoji)\n  }\n}\n</script>\n\n<style lang=\"less\" scoped>\n.emoji-card {\n  margin: -14px -17px;\n}\n/deep/ #EmojiPicker {\n  background: #fff;\n  color: #000;\n  #Categories {\n    background: #fff;\n    border-bottom: 1px solid #efefef;\n  }\n  .category {\n    transition: all 0.3s ease;\n    cursor: default;\n    &:hover {\n      background: #efefef;\n    }\n  }\n  .category.active {\n    border-bottom: 2px solid #000;\n    padding-bottom: 3px;\n  }\n\n  ::-webkit-scrollbar {\n    background-color: #fff;\n  }\n\n  ::-webkit-scrollbar-thumb {\n    background-color: #eee;\n    opacity: 0.7;\n  }\n  .emoji {\n    cursor: default;\n    width: 40px;\n    height: 40px;\n    line-height: 44px;\n    padding: 0;\n    border-radius: 8px;\n    transition: all 0.3s ease;\n    max-height: none;\n    &:hover {\n      background: #efefef;\n    }\n  }\n  #VSvg {\n    line-height: 16px;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/components/FooterBox/Index.vue",
    "content": "<template>\n  <div class=\"footer-box bg-white py-2 px-4\">\n    <slot />\n  </div>\n</template>\n\n<script lang=\"ts\">\nimport {\n  Vue, Component, Prop, Watch, Model,\n} from 'vue-property-decorator'\n\n@Component\nexport default class EmojiCard extends Vue {\n}\n</script>\n\n<style lang=\"less\" scoped>\n.footer-box {\n  border-top: 1px solid #e8e8e88a;\n  position: fixed;\n  left: 200px;\n  right: 0;\n  bottom: 0;\n  z-index: 10;\n}\n</style>\n"
  },
  {
    "path": "src/components/Main.vue",
    "content": "<template>\n  <a-layout>\n    <a-layout-sider\n      class=\"sider\"\n      :style=\"{ overflow: 'auto', height: '100vh', position: 'fixed', left: 0 }\"\n    >\n      <div class=\"top-container\">\n        <div class=\"logo\">\n          <img class=\"img\" src=\"@/assets/logo.png\">\n        </div>\n        <a-menu mode=\"inline\" :defaultSelectedKeys=\"['/articles']\" @click=\"clickMenu\">\n          <a-menu-item :key=\"menu.router\" v-for=\"menu in sideMenus\">\n            <div class=\"menu-item\">\n              <div class=\"flex items-center\">\n                <i\n                  class=\"mr-2 text-base\"\n                  :class=\"{ [menu.icon]: true }\"\n                  :style=\"{ color: currentRouter === menu.router ? '#f9d757' : 'inherit' }\"\n                ></i>\n                <span class=\"nav-text\">{{ menu.text }}</span>\n              </div>\n              <span class=\"number\">{{ menu.count }}</span>\n            </div>\n          </a-menu-item>\n        </a-menu>\n      </div>\n      <div class=\"bottom-container\">\n        <a-button class=\"preview-btn\" block @click=\"preview\">\n          <i class=\"zwicon-eye\"></i>\n          {{ $t('preview') }}\n        </a-button>\n        <a-button class=\"sync-btn\" block type=\"primary\" :loading=\"publishLoading\" @click=\"publish\">\n          <template v-if=\"!publishLoading\">\n            <i class=\"zwicon-deploy\"></i>\n            {{ $t('syncSite') }}\n          </template>\n        </a-button>\n        <div class=\"version-container\" :class=\"{ 'version-dot': hasUpdate }\">\n          <i class=\"ri-equalizer-line text-base\" @click=\"systemModalVisible = true\"></i>\n          <i class=\"ri-earth-line web-btn\" @click=\"goWeb\" v-if=\"site.setting.domain\"></i>\n          <a-tooltip :title=\"`🌟 ${$t('starSupport')}`\">\n            <i class=\"ri-github-line text-base\" @click=\"handleGithubClick\"></i>\n          </a-tooltip>\n        </div>\n      </div>\n    </a-layout-sider>\n    <a-layout class=\"right-container\">\n      <div class=\"content\">\n        <keep-alive exclude=\"Loading,Theme\">\n          <router-view></router-view>\n        </keep-alive>\n      </div>\n    </a-layout>\n\n    <a-modal :visible=\"syncErrorModalVisible\" :footer=\"null\" @cancel=\"syncErrorModalVisible = false\" :maskClosable=\"false\">\n      🙁 {{ $t('syncError1') }} <a @click=\"openInBrowser('https://gridea.dev/')\">FAQ</a> {{ $t('or') }} <a @click=\"openInBrowser('https://github.com/getgridea/gridea/issues')\">Issues</a> {{ $t('syncError2') }}\n    </a-modal>\n\n    <a-modal title=\"🔥 New Version\" :visible=\"updateModalVisible\" :footer=\"null\" @cancel=\"updateModalVisible = false\" :maskClosable=\"false\">\n      <div class=\"download-container\">\n        👉 <a href=\"https://gridea.dev\">Gridea Homepage</a> | <a href=\"https://github.com/getgridea/gridea/releases\">Github Releases</a> 👈\n      </div>\n      <h2>{{ newVersion }}</h2>\n      <div class=\"version-info\" v-html=\"updateContent\"></div>\n    </a-modal>\n\n    <!-- <a-modal :width=\"900\" :visible=\"systemModalVisible\" :footer=\"null\" @cancel=\"systemModalVisible = false\">\n      <app-system />\n    </a-modal> -->\n\n    <a-modal :width=\"900\" :visible=\"logModalVisible\" :footer=\"null\" @cancel=\"logModalVisible = false\">\n      <h2>{{ log.type }}</h2>\n      <pre>\n        {{ log.message }}\n      </pre>\n    </a-modal>\n\n    <a-drawer\n      title=\"\"\n      placement=\"bottom\"\n      height=\"100%\"\n      @close=\"systemModalVisible = false\"\n      :visible=\"systemModalVisible\"\n    >\n      <app-system />\n    </a-drawer>\n\n  </a-layout>\n</template>\n\n<script lang=\"ts\">\nimport { VNodeChildren } from 'vue'\nimport { ipcRenderer, IpcRendererEvent, shell } from 'electron'\nimport { Vue, Component } from 'vue-property-decorator'\nimport axios from 'axios'\nimport { State, Action } from 'vuex-class'\nimport AppSystem from './AppSystem/Index.vue'\nimport ISnackbar from '../interfaces/snackbar'\nimport { Site } from '../store/modules/site'\nimport * as pkg from '../../package.json'\nimport markdown from '../server/plugins/markdown'\nimport ga from '../helpers/analytics'\n\n@Component({\n  components: {\n    AppSystem,\n  },\n})\nexport default class App extends Vue {\n  @State('site') site!: Site\n\n  @Action('site/updateSite') updateSite!: (siteData: Site) => void\n\n  ipcRenderer = ipcRenderer\n\n  version = (pkg as any).version\n\n  drawer = true\n\n  publishLoading = false\n\n  hasUpdate = false\n\n  newVersion = ''\n\n  syncErrorModalVisible = false\n\n  updateModalVisible = false\n\n  systemModalVisible = false\n\n  updateContent = ''\n\n  logModalVisible = false\n\n  log: any = {}\n\n  get currentRouter() {\n    return this.$route.path\n  }\n\n  get sideMenus() {\n    return [\n      {\n        icon: 'ri-article-line',\n        text: this.$t('article'),\n        count: this.site.posts.length,\n        router: '/articles',\n      },\n      {\n        icon: 'ri-menu-2-line',\n        text: this.$t('menu'),\n        count: this.site.menus.length,\n        router: '/menu',\n      },\n      {\n        icon: 'ri-price-tag-3-line',\n        text: this.$t('tag'),\n        count: this.site.tags.length,\n        router: '/tags',\n      },\n      {\n        icon: 'ri-t-shirt-line',\n        text: this.$t('theme'),\n        router: '/theme',\n      },\n      {\n        icon: 'ri-server-line',\n        text: this.$t('remote'),\n        router: '/setting',\n      },\n    ]\n  }\n\n  created() {\n    this.$bus.$on('site-reload', () => {\n      this.reloadSite()\n    })\n    this.checkUpdate()\n\n    ipcRenderer.on(('log-error'), (event: any, result: any) => {\n      this.log = result\n      this.logModalVisible = true\n    })\n  }\n\n  mounted() {\n    // @see https://docs.headwayapp.co/widget for more configuration options.\n    const config = {\n      selector: '.version-container',\n      account: 'xbrnVx',\n      translations: {\n        title: 'Gridea News',\n        readMore: 'Read more',\n        labels: {\n          'new': 'News',\n          'improvement': 'Updates',\n          'fix': 'Fixes',\n        },\n        footer: 'Read more 👉',\n      },\n    }\n    // @ts-ignore\n    if (window.Headway) {\n      // @ts-ignore\n      Headway.init(config)\n    }\n  }\n\n  clickMenu(e: any) {\n    this.$router.push(e.key)\n  }\n\n  public reloadSite() {\n    const siteFolder = localStorage.getItem('sourceFolder') || ''\n\n    ipcRenderer.send('app-site-reload', { siteFolder })\n    ipcRenderer.once('app-site-loaded', (event: IpcRendererEvent, result: Site) => {\n      console.log(result)\n      this.updateSite(result)\n    })\n  }\n\n  public preview() {\n    ipcRenderer.send('html-render')\n\n    ga.event('Preview', 'Preview - start', { evLabel: this.site.setting.domain })\n\n    ipcRenderer.once('html-rendered', (event: IpcRendererEvent, result: any) => {\n      // this.$message.success(`🎉  ${this.$t('renderSuccess')}`)\n\n      ga.event('Preview', 'Preview - success', { evLabel: this.site.setting.domain })\n\n      ipcRenderer.send('app-preview-server-port-get')\n      ipcRenderer.once(\n        'app-preview-server-port-got',\n        (portGotEvent: IpcRendererEvent, port: number | string | null) => {\n          if (!port && typeof port !== 'number') {\n            this.$message.config({\n              top: '36px',\n            })\n            this.$message.open({\n              content: (h: Vue.CreateElement) => {\n                const errStyle = {\n                  display: 'inline-block',\n                  maxWidth: '800px',\n                }\n                return h('span', {\n                  style: errStyle,\n                }, `❌ ${this.$t('renderError')}` as any as VNodeChildren)\n              },\n              icon: '',\n            })\n          } else {\n            this.$message.success(`🎉  ${this.$t('renderSuccess')}`)\n            this.openInBrowser(`http://localhost:${port}`)\n          }\n        },\n      )\n    })\n  }\n\n  public publish() {\n    const { setting } = this.site\n    if (setting.platform === 'netlify' && !setting.netlifyAccessToken && !setting.netlifySiteId) {\n      this.$message.error(`🙁  ${this.$t('syncWarning')}`)\n      return false\n    }\n\n    if (!setting.branch && !setting.domain && !setting.token && !setting.repository) {\n      this.$message.error(`🙁  ${this.$t('syncWarning')}`)\n      return false\n    }\n\n    ipcRenderer.send('site-publish')\n    this.publishLoading = true\n\n    ga.event('Publish', 'Publish - start', { evLabel: this.site.setting.domain })\n\n    ipcRenderer.once('site-published', (event: IpcRendererEvent, result: any) => {\n      console.log(result)\n      if (result.success) {\n        this.$message.success(`🎉  ${this.$t('syncSuccess')}`)\n\n        ga.event('Publish', 'Publish - success', { evLabel: this.site.setting.domain })\n      } else {\n        this.syncErrorModalVisible = true\n\n        ga.event('Publish', 'Publish - failed', { evLabel: this.site.setting.domain })\n      }\n      this.publishLoading = false\n    })\n  }\n\n  openInBrowser(url: string) {\n    shell.openExternal(url)\n  }\n\n  goWeb() {\n    if (this.site.setting.domain) {\n      ga.event('Client', 'Client - open-web', { evLabel: this.site.setting.domain })\n\n      shell.openExternal(this.site.setting.domain)\n    }\n  }\n\n  handleGithubClick() {\n    ga.event('Client', 'Client - open-github', {})\n\n    this.openInBrowser('https://github.com/getgridea/gridea')\n  }\n\n  public async checkUpdate() {\n    const res = await axios.get('https://api.github.com/repos/getgridea/gridea/releases/latest')\n    if (res.status === 200) {\n      this.newVersion = res.data.name\n      const latestVersion = res.data.name.substring(1).split('.').map((item: string) => parseInt(item, 10))\n      const currentVersion = this.version.split('.').map((item: string) => parseInt(item, 10))\n      this.updateContent = markdown.render(res.data.body)\n\n      for (let i = 0; i < currentVersion.length; i += 1) {\n        if (currentVersion[i] > latestVersion[i]) {\n          this.hasUpdate = false\n          break\n        }\n        if (currentVersion[i] < latestVersion[i]) {\n          this.hasUpdate = true\n          break\n        }\n      }\n\n      if (this.hasUpdate) {\n        // this.$message.success(`🔥  ${this.$t('newVersionTips')}`, 8)\n        this.updateModalVisible = true\n      }\n    }\n  }\n}\n</script>\n\n<style lang=\"less\" scoped>\n@import '~@/assets/styles/var.less';\n\n.logo {\n  min-height: 64px;\n  -webkit-app-region: drag;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  flex-direction: column;\n  padding: 32px 0 16px;\n  h3 {\n    color: @primary-color;\n    font-size: 18px;\n    transform: translateY(8px);\n    // background-image: -webkit-gradient(linear, 0 0, 0 bottom, from(rgba(250, 250, 250, 1)), to(rgba(255, 255, 255, 0.4)));\n    // -webkit-background-clip: text;\n    // -webkit-text-fill-color: transparent;\n  }\n  .img {\n    width: 64px;\n    height: 64px;\n    border-radius: 16px;\n  }\n}\n.sider {\n  background: @primary-bg;\n  // background: linear-gradient(to bottom, #434343, #000000);\n  &::-webkit-scrollbar {\n    width: 0;\n  }\n}\n\n/deep/ .ant-menu {\n  background: @primary-bg;\n  @apply text-gray-500;\n}\n\n/deep/ .ant-menu-item {\n  padding-left: 16px !important;\n  transition: all 0.3s;\n  cursor: default;\n\n  &:hover {\n    background-color: #fff;\n  }\n}\n\n/deep/ .ant-menu-vertical .ant-menu-item:after, .ant-menu-vertical-left .ant-menu-item:after, .ant-menu-vertical-right .ant-menu-item:after, .ant-menu-inline .ant-menu-item:after {\n  border-right: none;\n}\n\n/deep/ .ant-menu:not(.ant-menu-horizontal) .ant-menu-item-selected {\n  background-color: #fff;\n  // @apply shadow;\n  color: #000;\n}\n\n/deep/ .ant-menu-inline, .ant-menu-vertical, .ant-menu-vertical-left {\n  border-right: none;\n}\n\n/deep/ .ant-layout-sider-children {\n  display: flex;\n  flex-direction: column;\n  justify-content: space-between;\n}\n\n.bottom-container {\n  padding: 24px 32px 8px;\n  button {\n    margin: 8px 0;\n  }\n}\n\n.right-container {\n  background: #fff;\n  margin-left: 8px 8px 8px 208px;\n  padding: 8px 16px 8px 8px;\n  position: absolute;\n  top: 8px;\n  bottom: 8px;\n  left: 208px;\n  right: 0px;\n}\n.version-container {\n  display: flex;\n  justify-content: space-between;\n  margin-top: 8px;\n  align-items: center;\n  position:relative;\n  font-size: 12px;\n  &.version-dot {\n    &:before {\n      content: '';\n      display: block;\n      width: 4px;\n      height: 4px;\n      background: red;\n      border-radius: 2px;\n      position: absolute;\n      top: 51%;\n      left: -12px;\n      transform: translateY(-50%);\n    }\n  }\n}\n\n.preview-btn {\n  border-radius: 20px;\n  background: #fff;\n  transition: all 0.3s;\n  &:hover {\n    background: #fafafa;\n  }\n}\n\n.sync-btn {\n  border-radius: 20px;\n  background: linear-gradient(124deg, rgba(65,70,75,1) 0%, rgba(0,0,0,1) 100%);\n  color: #bababa;\n  border: none;\n  transition: all 0.3s;\n  &:hover {\n    background: linear-gradient(124deg, rgba(0,0,0,1) 0%, rgba(0,0,0,1) 100%);\n    border: none;\n  }\n}\n\n.web-btn {\n  font-size: 16px;\n  cursor: pointer;\n  &:hover {\n    color: @link-color;\n  }\n}\n\n.nav-text {\n  font-weight: normal;\n}\n\n.menu-item {\n  display: flex;\n  justify-content: space-between;\n  .number {\n    font-weight: lighter;\n  }\n}\n\n.menu-icon {\n  font-size: 17px;\n  margin-right: 8px;\n  font-weight: 400;\n}\n\n.version-info {\n  /deep/ code {\n    background-color: rgba(27,31,35,.05);\n    border-radius: 3px;\n    font-size: 85%;\n    margin: 0;\n    padding: .2em .4em;\n  }\n  /deep/ blockquote {\n    border-left: .25em solid #dfe2e5;\n    color: #6a737d;\n    padding: 0 1em;\n  }\n  /deep/ ul, ol {\n    padding: 0;\n    list-style-type: none;\n    font-size: 14px;\n    margin: 30px 20px;\n\n    ul,\n    ol {\n      margin: 20px 20px 10px;\n    }\n  }\n\n  /deep/ li {\n      line-height: 1.2;\n    }\n\n  /deep/ ul > li {\n      display: table-row;\n\n      &:before {\n        content:'\\25CF';\n        color: #fad849;\n        padding-right: 10px;\n        display: table-cell;\n      }\n\n      + li:before {\n        padding-top: 10px;\n      }\n    }\n\n  /deep/ ol {\n      counter-reset: ordered-counter;\n      > li {\n        counter-increment: ordered-counter;\n        display: table-row;\n\n        &:before {\n          content: counter(ordered-counter);\n          color: #fad849;\n          padding-right: 10px;\n          display: table-cell;\n          text-align: right;\n        }\n\n        + li:before {\n          padding-top: 10px;\n        }\n      }\n    }\n}\n\n.download-container {\n  text-align: center;\n  padding: 16px 0;\n  background: #fafafa;\n}\n</style>\n"
  },
  {
    "path": "src/components/MonacoMarkdownEditor/Index.vue",
    "content": "<template>\n  <div id=\"monaco-markdown-editor\" style=\"max-width: 728px; min-height: calc(100vh - 176px); margin: 0 auto;\" :style=\"{\n    width: isPostPage ? '728px' : 'auto'\n  }\">\n  </div>\n</template>\n\n<script lang=\"ts\">\nimport {\n  Vue, Component, Prop, Watch, Model,\n} from 'vue-property-decorator'\nimport * as monaco from 'monaco-editor'\nimport * as MonacoMarkdown from 'monaco-markdown'\nimport theme from './theme'\n\n@Component\nexport default class MonacoMarkdownEditor extends Vue {\n  @Prop({ type: Boolean }) readonly isPostPage!: boolean\n\n  @Model('change', { type: String }) readonly value!: string\n\n\n  editor: any = null\n\n  prevLineCount: number = -1\n\n  mounted() {\n    monaco.editor.defineTheme('GrideaLight', theme as any)\n\n    this.editor = monaco.editor.create(document.getElementById('monaco-markdown-editor') as any, {\n      language: 'markdown-math',\n      value: this.value,\n      fontSize: 15,\n      theme: 'GrideaLight',\n      lineNumbers: 'off',\n      minimap: {\n        enabled: false,\n      },\n      wordWrap: 'on',\n      cursorWidth: 2,\n      cursorSmoothCaretAnimation: true,\n      cursorBlinking: 'smooth',\n      colorDecorators: true,\n      extraEditorClassName: 'gridea-editor',\n      folding: false,\n      highlightActiveIndentGuide: false,\n      renderIndentGuides: false,\n      renderLineHighlight: 'none',\n      scrollbar: {\n        vertical: 'auto',\n        horizontal: 'hidden',\n        verticalScrollbarSize: 4,\n      },\n      lineHeight: 26.25,\n      scrollBeyondLastLine: false,\n      wordBasedSuggestions: false,\n      snippetSuggestions: 'none',\n      lineDecorationsWidth: 0,\n      occurrencesHighlight: false,\n      automaticLayout: true,\n      fontFamily: 'PingFang SC,-apple-system,SF UI Text,Lucida Grande,STheiti,Microsoft YaHei,sans-serif',\n    })\n\n    const extension = new MonacoMarkdown.MonacoMarkdownExtension()\n    extension.activate(this.editor)\n\n    setTimeout(this.setEditorHeight, 0)\n\n    this.editor.onDidChangeModelContent(() => {\n      setTimeout(this.setEditorHeight, 0)\n      const value = this.editor.getValue()\n      if (this.value !== value) {\n        this.$emit('change', value)\n      }\n    })\n    this.editor.onKeyDown(() => {\n      this.$emit('keydown')\n    })\n  }\n\n  setEditorHeight() {\n    // const { editor } = this\n    // if (!editor) return\n\n    // const editorDomNode = editor.getDomNode()\n    // if (!editorDomNode) return\n\n    // const LINE_HEIGHT = 26.25\n\n    // const container = editorDomNode.getElementsByClassName('view-lines')[0] as HTMLElement\n    // const containerHeight = container.offsetHeight\n    // const lineHeight = container.firstChild\n    //   ? (container.firstChild as HTMLElement).offsetHeight\n    //   : LINE_HEIGHT\n    \n    // if (!containerHeight) {\n    //   setTimeout(this.setEditorHeight, 0)\n    // } else {\n    //   const currLineCount = container.childElementCount\n    //   const nextHeight = (this.prevLineCount > currLineCount)\n    //     ? currLineCount * lineHeight\n    //     : containerHeight\n      \n    //   editorDomNode.style.height = `${nextHeight}px`\n    //   editor.layout()\n\n    //   if (container.childElementCount !== currLineCount) {\n    //     this.setEditorHeight()\n    //   } else {\n    //     this.prevLineCount = currLineCount\n    //   }\n    // }\n\n    const lines = document.querySelectorAll('.view-line') as any\n    if (lines) {\n      if (lines.length === 1 && !lines[0].innerText.trim()) {\n        lines[0].classList.add('input-holder')\n      } else if (lines[0].classList.contains('input-holder')) {\n        lines[0].classList.remove('input-holder')\n      }\n    }\n  }\n\n  @Watch('value')\n  onValueChanged(newValue: any) {\n    if (this.editor && newValue !== this.editor.getValue()) {\n      this.editor.setValue(newValue)\n    }\n  }\n}\n</script>\n\n<style lang=\"less\" scoped>\n/deep/.context-view .monaco-scrollable-element {\n  box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .1), 0 1px 2px 0 rgba(0, 0, 0, .06) !important;\n  border-radius: 4px;\n}\n\n/deep/ .monaco-menu .monaco-action-bar.vertical .action-item {\n  border: none;\n}\n\n/deep/ .action-menu-item {\n  color: #718096 !important;\n  &:hover {\n    color: #744210 !important;\n    background: #FFFFF0 !important;\n  }\n}\n\n/deep/ .decorationsOverviewRuler {\n  display: none !important;\n}\n\n/deep/ .monaco-menu .monaco-action-bar.vertical .action-label.separator {\n  border-bottom-color: #E2E8F0 !important;\n}\n/deep/ .input-holder {\n  &:before {\n    content: '开始写作...';\n    color: rgba(208, 211, 217, 0.6);\n  }\n}\n\n/deep/ .monaco-editor {\n  .scrollbar {\n    .slider {\n      background: #eee;\n    }\n  }\n  .scroll-decoration {\n    box-shadow: #efefef 0 2px 2px -2px inset;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/components/MonacoMarkdownEditor/theme.js",
    "content": "export default {\n  'base': 'vs',\n  'inherit': true,\n  'rules': [\n    {\n      'foreground': '999999',\n      'token': 'comment',\n    },\n    {\n      'foreground': 'e88501',\n      'token': 'string',\n    },\n    {\n      'foreground': '999999',\n      'token': 'string.link',\n    },\n    {\n      'foreground': '999999',\n      'token': 'variable.source',\n    },\n    {\n      'foreground': '4C51BF',\n      'token': 'variable',\n    },\n    {\n      'foreground': '2B6CB0',\n      'token': 'markup.list',\n    },\n    {\n      'foreground': '2B6CB0',\n      'token': 'markup.underline.link',\n    },\n    {\n      'foreground': '46a609',\n      'token': 'constant.numeric',\n    },\n    {\n      'foreground': '39946a',\n      'token': 'constant.language',\n    },\n    {\n      'foreground': 'b7791f',\n      'token': 'keyword',\n    },\n    {\n      'fontStyle': 'bold',\n      'token': 'markup.heading',\n    },\n    {\n      'fontStyle': 'bold',\n      'token': 'markup.bold',\n    },\n    {\n      'fontStyle': 'italic',\n      'token': 'markup.italic',\n    },\n    // ie bold/italic/heading/list marks\n    {\n      'foreground': '999999',\n      'token': 'punctuation.definition.constant.markdown',\n    },\n    {\n      'foreground': '999999',\n      'token': 'punctuation.definition.bold.markdown',\n    },\n    {\n      'foreground': '999999',\n      'token': 'punctuation.definition.italic.markdown',\n    },\n    {\n      'foreground': '999999',\n      'token': 'punctuation.definition.heading.markdown',\n    },\n    {\n      'foreground': '999999',\n      'token': 'punctuation.definition.heading.begin.markdown',\n    },\n    {\n      'foreground': '999999',\n      'token': 'punctuation.definition.heading.end.markdown',\n    },\n    {\n      'foreground': '999999',\n      'token': 'punctuation.definition.heading.setext.markdown',\n    },\n    {\n      'foreground': '999999',\n      'token': 'punctuation.definition.list_item.markdown',\n    },\n    {\n      'foreground': '999999',\n      'token': 'markup.list.numbered.bullet.markdown',\n    },\n    {\n      'foreground': '999999',\n      'token': 'punctuation.definition.bold.begin.markdown',\n    },\n    {\n      'foreground': '999999',\n      'token': 'punctuation.definition.bold.end.markdown',\n    },\n    {\n      'foreground': '999999',\n      'token': 'punctuation.definition.italic.begin.markdown',\n    },\n    {\n      'foreground': '999999',\n      'token': 'punctuation.definition.italic.end.markdown',\n    },\n    {\n      'foreground': '999999',\n      'token': 'punctuation.definition.variable.begin.markdown',\n    },\n    {\n      'foreground': '999999',\n      'token': 'punctuation.definition.variable.end.markdown',\n    },\n    {\n      'foreground': '999999',\n      'token': 'punctuation.definition.link.begin.markdown',\n    },\n    {\n      'foreground': '999999',\n      'token': 'punctuation.definition.link.end.markdown',\n    },\n\n    {\n      'foreground': 'b7791f',\n      'token': 'support.constant.property-value',\n    },\n    {\n      'foreground': 'b7791f',\n      'token': 'constant.other.color',\n    },\n    {\n      'foreground': '96dc5f',\n      'token': 'keyword.other.unit',\n    },\n    {\n      'foreground': '484848',\n      'token': 'keyword.operator',\n    },\n    {\n      'foreground': 'c52727',\n      'token': 'storage',\n    },\n    {\n      'foreground': '858585',\n      'token': 'entity.other.inherited-class',\n    },\n    {\n      'foreground': '606060',\n      'token': 'entity.name.tag',\n    },\n    {\n      'foreground': 'bf78cc',\n      'token': 'constant.character.entity',\n    },\n    {\n      'foreground': 'bf78cc',\n      'token': 'support.class.js',\n    },\n    {\n      'foreground': '606060',\n      'token': 'entity.other.attribute-name',\n    },\n    {\n      'foreground': 'c52727',\n      'token': 'meta.selector.css',\n    },\n    {\n      'foreground': 'c52727',\n      'token': 'entity.name.tag.css',\n    },\n    {\n      'foreground': 'c52727',\n      'token': 'entity.other.attribute-name.id.css',\n    },\n    {\n      'foreground': 'c52727',\n      'token': 'entity.other.attribute-name.class.css',\n    },\n    {\n      'foreground': '484848',\n      'token': 'meta.property-name.css',\n    },\n    {\n      'foreground': 'c52727',\n      'token': 'support.function',\n    },\n    {\n      'background': 'ff002a',\n      'token': 'invalid',\n    },\n    {\n      'foreground': 'c52727',\n      'token': 'punctuation.section.embedded',\n    },\n    {\n      'foreground': '606060',\n      'token': 'punctuation.definition.tag',\n    },\n    {\n      'foreground': 'bf78cc',\n      'token': 'constant.other.color.rgb-value.css',\n    },\n    {\n      'foreground': 'bf78cc',\n      'token': 'support.constant.property-value.css',\n    },\n  ],\n  'colors': {\n    'editor.foreground': '#333333',\n    'editor.background': '#FFFFFF',\n    'editor.selectionBackground': '#BDD5FC',\n    'editor.lineHighlightBackground': '#FFFBD1',\n    'editorCursor.foreground': '#000000',\n    'editorWhitespace.foreground': '#BFBFBF',\n    'textLink.foreground': '#666',\n  },\n}\n"
  },
  {
    "path": "src/components/PostsCard/Index.vue",
    "content": "<template>\r\n  <div class=\"post-card\" v-if=\"posts && posts.length > 0\">\r\n    <div class=\"item-container\" v-for=\"(post, index) in posts\" :key=\"index\">\r\n      <div @click=\"handleClick(post)\">{{ post.data.title }}</div>\r\n    </div>\r\n  </div>\r\n</template>\r\n\r\n<script lang=\"ts\">\r\nimport { Vue, Component, Prop } from 'vue-property-decorator'\r\n\r\n@Component\r\nexport default class PostsCard extends Vue {\r\n  @Prop(Array) posts!: any[]\r\n\r\n  handleClick(post: any) {\r\n    this.$emit('select', post.link)\r\n  }\r\n}\r\n</script>\r\n\r\n<style lang=\"less\" scoped>\r\n.post-card {\r\n  max-height: 480px;\r\n  overflow: scroll;\r\n  margin: -15px;\r\n}\r\n.item-container {\r\n  margin: 4px;\r\n  padding: 2px;\r\n  border-radius: 6px;\r\n  font-weight: bold;\r\n  cursor: pointer;\r\n  &:hover {\r\n    background: #fafafa;\r\n  }\r\n}\r\n</style>\r\n"
  },
  {
    "path": "src/helpers/analytics.ts",
    "content": "import GA from 'electron-google-analytics'\nimport macaddress from 'macaddress'\nimport * as pkg from '../../package.json'\n\nconst isDevelopment = process.env.NODE_ENV !== 'production'\n\ninterface EvOptions {\n  evLabel?: any\n  evValue?: any\n}\n\nconst hostname = 'http://client.gridea.dev'\n\nclass Analytics {\n  private readonly ga: any\n\n  private clientId: any\n\n  constructor() {\n    this.ga = new GA('UA-113307620-4', { debug: isDevelopment })\n    \n    this.ga.set('version', (pkg as any).version)\n  }\n\n  public getClientId(callback: any) {\n    if (this.clientId) {\n      callback(this.clientId)\n    }\n    macaddress.one((err: any, mac: string) => {\n      this.clientId = mac\n      callback(mac)\n    })\n  }\n\n  public async pageView(url: string, title?: string) {\n    this.getClientId(async (clientId: any) => {\n      try {\n        await this.ga.pageview(hostname, url, title, 1, clientId)\n      } catch (e) {\n        console.error(e)\n      }\n    })\n  }\n\n  public async event(evCategory: string, evAction: string, options: EvOptions) {\n    this.getClientId(async (clientId: any) => {\n      try {\n        await this.ga.event(evCategory, evAction, {\n          ...options,\n          clientID: clientId,\n        })\n      } catch (e) {\n        console.error(e)\n      }\n    })\n  }\n\n  public async exception(exDesc: string, exFatal: any) {\n    this.getClientId(async (clientId: any) => {\n      try {\n        await this.ga.exception(exDesc, exFatal)\n      } catch (e) {\n        console.error(e)\n      }\n    })\n  }\n}\n\nexport default new Analytics()\n"
  },
  {
    "path": "src/helpers/constants.ts",
    "content": "export const UrlFormats = [\n  {\n    text: 'Slug',\n    value: 'SLUG',\n  },\n  {\n    text: 'Short ID',\n    value: 'SHORT_ID',\n  },\n]\n\nexport const DEFAULT_POST_PAGE_SIZE = 10\nexport const DEFAULT_ARCHIVES_PAGE_SIZE = 50\nexport const DEFAULT_FEED_COUNT = 10\nexport const DEFAULT_ARCHIVES_PATH = 'archives'\nexport const DEFAULT_POST_PATH = 'post'\nexport const DEFAULT_TAG_PATH = 'tag'\n"
  },
  {
    "path": "src/helpers/content-helper.ts",
    "content": "export default class ContentHelper {\n  localReg: RegExp\n\n  domainReg: RegExp\n\n  featureDomainReg: RegExp\n\n  featureLocalReg: RegExp\n\n  constructor() {\n    this.localReg = /\\(file.*\\/post-images\\//g\n    this.domainReg = /\\(.*\\/post-images\\//g\n    this.featureDomainReg = /\\.*\\/post-images\\//g\n    this.featureLocalReg = /file.*\\/post-images\\//g\n  }\n\n  /**\n   * 将文章中本地图片路径，变更为线上路径\n   * @param content 内容\n   * @param domainPath 线上路径\n   */\n  changeImageUrlLocalToDomain(content: string, domainPath: string) {\n    domainPath = domainPath.replace(/\\\\/g, '/')\n    return content.replace(this.localReg, `(${domainPath}/post-images/`)\n  }\n\n  /**\n   * 将文章中线上图片路径，变更为本地路径\n   * @param content 内容\n   * @param localPath 本地路径\n   */\n  changeImageUrlDomainToLocal(content: string, localPath: string) {\n    localPath = localPath.replace(/\\\\/g, '/')\n    return content.replace(this.domainReg, `(file://${localPath}/post-images/`)\n  }\n\n  /**\n   * 将 feature 图片路径，变更为本地路径\n   */\n  changeFeatureImageUrlDomainToLocal(content: string, localPath: string) {\n    return content.replace(this.featureDomainReg, `file://${localPath}/post-images/`)\n  }\n\n  /**\n   * 将 feature 本地图片路径，变更为线上路径\n   */\n  changeFeatureImageUrlLocalToDomain(content: string, domainPath: string) {\n    let url = content.replace(this.featureLocalReg, `${domainPath}/post-images/`)\n    url = url.replace(/\\\\/g, '/')\n    return url\n  }\n}\n"
  },
  {
    "path": "src/helpers/enums.ts",
    "content": "export enum MenuTypes {\n  Internal = 'Internal',\n  External = 'External',\n}\n\nexport enum UrlFormats {\n  Slug = 'SLUG',\n  ShortId = 'SHORT_ID',\n}\n"
  },
  {
    "path": "src/helpers/shortcut-keys.ts",
    "content": "export default [\n  {\n    name: '编辑',\n    list: [\n      {\n        title: '保存文章',\n        keyboard: ['⌘', 'S'],\n      },\n      {\n        title: '剪切',\n        keyboard: ['⌘', 'X'],\n      },\n      {\n        title: '复制',\n        keyboard: ['⌘', 'C'],\n      },\n      {\n        title: '粘贴',\n        keyboard: ['⌘', 'V'],\n      },\n    ],\n  },\n  {\n    name: 'Markdown',\n    list: [\n      {\n        title: '标题降级 (# -)',\n        keyboard: ['⌃', '⇧', '['],\n      },\n      {\n        title: '标题升级 (# +)',\n        keyboard: ['⌃', '⇧', ']'],\n      },\n      {\n        title: '加粗 (**)',\n        keyboard: ['⌘', 'B'],\n      },\n      {\n        title: '行内 Code(`)',\n        keyboard: ['⌘', '`'],\n      },\n      {\n        title: '斜体 (*)',\n        keyboard: ['⌘', 'I'],\n      },\n      {\n        title: '列表 (-)',\n        keyboard: ['⌘', 'L'],\n      },\n      {\n        title: 'LaTeX ($)',\n        keyboard: ['⌘', 'M'],\n      },\n      {\n        title: 'LaTeX ($$)',\n        keyboard: ['⇧', '⌘', 'M'],\n      },\n      {\n        title: '删除线 (~~)',\n        keyboard: ['⌥', 'S'],\n      },\n    ],\n  },\n  {\n    name: '其他',\n    list: [\n      {\n        title: '格式化文档',\n        keyboard: ['⇧', '⌥', 'F'],\n      },\n    ],\n  },\n]\n"
  },
  {
    "path": "src/helpers/slug.ts",
    "content": "/* tslint:disable */\nconst { transliterate } = require('transliteration')\nconst slug = require('slug')\n\n/*\n * Custom mode of rfc3986 without unicode symbols\n */\nslug.defaults.modes['rfc3986-non-unicode'] = {\n  replacement: '-', // replace spaces with replacement\n  symbols: false, // replace unicode symbols or not\n  remove: /[.]/g, // (optional) regex to remove characters\n  lower: true, // result in lower case\n  charmap: slug.charmap, // replace special characters\n  multicharmap: slug.multicharmap, // replace multi-characters\n}\n\nslug.defaults.modes['rfc3986-non-unicode-with-dots'] = {\n  replacement: '-', // replace spaces with replacement\n  symbols: false, // replace unicode symbols or not\n  lower: true, // result in lower case\n  charmap: slug.charmap, // replace special characters\n  multicharmap: slug.multicharmap, // replace multi-characters\n}\n\nslug.defaults.modes['rfc3986-non-unicode-with-dots-no-lower'] = {\n  replacement: '-', // replace spaces with replacement\n  symbols: false, // replace unicode symbols or not\n  lower: false, // result in lower case\n  charmap: slug.charmap, // replace special characters\n  multicharmap: slug.multicharmap, // replace multi-characters\n}\n\nslug.defaults.mode = 'rfc3986-non-unicode'\n\n/**\n * Slugify 文本\n * @param textToSlugify 待 slugify 的文本\n * @param filenameMode\n * @param saveLowerChars\n */\nfunction createSlug(textToSlugify: any, filenameMode = false, saveLowerChars = false) {\n  textToSlugify = transliterate(textToSlugify)\n\n  if (!filenameMode) {\n    if (saveLowerChars) {\n      slug.defaults.mode = 'rfc3986-non-unicode-with-dots-no-lower'\n    }\n\n    textToSlugify = slug(textToSlugify)\n    slug.defaults.mode = 'rfc3986-non-unicode'\n  } else {\n    slug.defaults.mode = 'rfc3986-non-unicode-with-dots'\n    textToSlugify = slug(textToSlugify)\n    slug.defaults.mode = 'rfc3986-non-unicode'\n  }\n\n  return textToSlugify\n}\n\nexport default createSlug\n"
  },
  {
    "path": "src/helpers/utils.ts",
    "content": "import markdown from '../server/plugins/markdown'\n\n/**\n * Add single-quoted to string type field, in order to be compatible with many special characters\n * eg. true, false, 1, [, ], {, }, ,, #, <, >, @,\n */\nexport function formatYamlString(string: any) {\n  return string.replace(/'/g, '\\'\\'')\n}\n\n\nexport const formatThemeCustomConfigToRender = (config: any, currentThemeConfig: any) => {\n  for (const configItem of currentThemeConfig) {\n    const configValue = config[configItem.name]\n    if (configItem.type === 'markdown') {\n      if (!configValue) continue\n\n      config[configItem.name] = markdown.render(configValue)\n    } else if (configItem.type === 'array' && configValue) {\n      for (let arrItemIndex = 0; arrItemIndex < configValue.length; arrItemIndex += 1) {\n        const foundConfigItem = currentThemeConfig.find((i: any) => i.name === configItem.name)\n        const arrayItemKeys = Object.keys(configValue[arrItemIndex])\n\n        for (let keyIndex = 0; keyIndex < arrayItemKeys.length; keyIndex += 1) {\n          const key = arrayItemKeys[keyIndex]\n          const foundMarkdownField = foundConfigItem.arrayItems.find((i: any) => i.name === key && i.type === 'markdown')\n\n          if (foundMarkdownField) {\n            const fieldValue = configValue[arrItemIndex][key]\n            if (!fieldValue) continue\n\n            configValue[arrItemIndex][key] = markdown.render(fieldValue)\n          }\n        }\n      }\n    }\n  }\n\n  return config\n}\n"
  },
  {
    "path": "src/helpers/vee-validate.ts",
    "content": "import { required } from 'vee-validate/dist/rules'\nimport { extend } from 'vee-validate'\n\nextend('required', {\n  ...required,\n  message: '此项是必填项',\n})\n"
  },
  {
    "path": "src/helpers/words-count.ts",
    "content": "import striptags from 'striptags'\n\nconst CN_PATTERN = /[\\u4E00-\\u9FA5]/g\nconst EN_PATTERN = /[a-zA-Z0-9_\\u0392-\\u03c9\\u0400-\\u04FF]+|[\\u4E00-\\u9FFF\\u3400-\\u4dbf\\uf900-\\ufaff\\u3040-\\u309f\\uac00-\\ud7af\\u0400-\\u04FF]+|[\\u00E4\\u00C4\\u00E5\\u00C5\\u00F6\\u00D6]+|\\w+/g\n\nfunction countContent(content: any): [number, number] {\n  if (typeof content !== 'string') {\n    throw new Error('[word-counter] content must be string type')\n  }\n  let cn = 0\n  let en = 0\n  if (content.length > 0) {\n    content = striptags(content)\n    cn = (content.match(CN_PATTERN) || []).length\n    en = (content.replace(CN_PATTERN, '').match(EN_PATTERN) || []).length\n  }\n  return [cn, en]\n}\n\nexport function wordCount(content?: any, transformFn?: (count: number) => any): any {\n  const [cn, en] = countContent(content)\n  const count = cn + en\n  if (typeof transformFn === 'function') {\n    return transformFn(count)\n  }\n  return count\n}\n\ninterface TimeConfig {\n  cn?: number\n  en?: number\n}\n\nexport function timeCalc(content?: any, { cn = 300, en = 160 }: TimeConfig = {}): {\n  minius: number,\n  second: number,\n} {\n  const [cnCount, enCount] = countContent(content)\n  const minius = cnCount / cn + enCount / en\n\n  return {\n    minius: minius === 0 ? 0 : Math.ceil(minius),\n    second: Math.floor(minius * 60),\n  }\n}\n"
  },
  {
    "path": "src/interfaces/menu.ts",
    "content": "export interface IMenu {\n  name: string\n  openType: string\n  link: string\n}\n"
  },
  {
    "path": "src/interfaces/post.ts",
    "content": "export interface IPostData {\n  title: string\n  date: string\n  published: boolean\n  hideInList: boolean\n  tags?: []\n  feature: string\n  isTop: boolean\n}\n\nexport interface IPost {\n  content: string\n\n  data: IPostData\n\n  fileName: string\n}\n"
  },
  {
    "path": "src/interfaces/setting.ts",
    "content": "export interface ISetting {\n  platform: 'github' | 'coding' | 'sftp' | 'gitee' | 'netlify'\n  domain: string\n  repository: string\n  branch: string\n  username: string\n  email: string\n  tokenUsername: string\n  token: string\n  cname: string\n  port: string\n  server: string\n  password: string\n  privateKey: string\n  remotePath: string\n  proxyPath: string\n  proxyPort: string\n  enabledProxy: 'direct' | 'proxy'\n  netlifyAccessToken: string\n  netlifySiteId: string\n  [index: string]: string\n}\n\nexport interface IDisqusSetting {\n  api: string\n  apikey: string\n  shortname: string\n}\nexport interface IGitalkSetting {\n  clientId: string\n  clientSecret: string\n  repository: string\n  owner: string\n}\n\nexport interface ICommentSetting {\n  commentPlatform: string\n  showComment: boolean\n  disqusSetting: IDisqusSetting\n  gitalkSetting: IGitalkSetting\n}\n"
  },
  {
    "path": "src/interfaces/snackbar.ts",
    "content": "export default interface ISnackbar {\n  /**\n   * 通知颜色：success, info, error 或其他颜色\n   * default: success\n   */\n  color?: string\n  snackbar?: boolean\n  message?: string\n  bottom?: boolean\n}\n"
  },
  {
    "path": "src/interfaces/tag.ts",
    "content": "export interface ITag {\n  name: string\n  used: boolean\n  slug?: string\n}\n"
  },
  {
    "path": "src/interfaces/theme.ts",
    "content": "export interface ITheme {\n  themeName: string\n  postPageSize: number\n  archivesPageSize: number\n  siteName: string\n  siteDescription: string\n  footerInfo: string\n  showFeatureImage: boolean\n  postUrlFormat: string\n  tagUrlFormat: string\n  dateFormat: string\n  feedFullText: boolean\n  feedCount: number\n  archivesPath: string\n  postPath: string\n  tagPath: string\n}\n"
  },
  {
    "path": "src/main.ts",
    "content": "import Vue from 'vue'\nimport moment from 'moment'\nimport Antd from 'ant-design-vue'\nimport 'remixicon/fonts/remixicon.css'\nimport 'katex/dist/katex.min.css'\nimport '@fontsource/noto-serif/index.css'\nimport '@/assets/styles/tailwind.css'\nimport '@/assets/styles/main.less'\nimport VueI18n from 'vue-i18n'\nimport Prism from 'prismjs'\nimport VueShortkey from 'vue-shortkey'\nimport { remote } from 'electron'\nimport * as Sentry from '@sentry/electron'\nimport locale from './assets/locales'\nimport App from './App.vue'\nimport router from './router'\nimport store from './store/index'\nimport VueBus from './vue-bus'\nimport ga from './helpers/analytics'\nimport './helpers/vee-validate'\n\nga.event('Client', 'show', {\n  evLabel: 'startup',\n})\n\nSentry.init({ dsn: 'https://6a6dacc57a6a4e27a88eb31596c152f8@sentry.io/1887150' })\n\nconst defaultLocale = ({\n  'zh-CN': 'zhHans',\n  'zh-TW': 'zh_TW',\n  'en-US': 'en',\n} as any)[remote.app.getLocale() || 'zh-CN']\n\nVue.use(VueI18n)\nconst i18n = new VueI18n({\n  locale: localStorage.getItem('language') || defaultLocale,\n  messages: locale as any,\n  silentTranslationWarn: true,\n})\n\nPrism.highlightAll()\n\nVue.use(Antd)\nVue.use(VueBus)\nVue.use(VueShortkey)\nVue.prototype.$moment = moment\n\nVue.config.productionTip = false\n\nnew Vue({\n  router,\n  store,\n  i18n,\n  render: h => h(App),\n  mounted() {\n    router.push('/')\n  },\n}).$mount('#app')\n"
  },
  {
    "path": "src/router.ts",
    "content": "import Vue from 'vue'\nimport Router from 'vue-router'\nimport macaddress from 'macaddress'\nimport Main from './components/Main.vue'\n// import ArticleUpdate from './views/article/ArticleUpdate.vue'\nimport Articles from './views/article/Articles.vue'\nimport Menu from './views/menu/Index.vue'\nimport Tags from './views/tags/Index.vue'\nimport Theme from './views/theme/Index.vue'\nimport Setting from './views/setting/Index.vue'\nimport Loading from './views/loading/Index.vue'\nimport ga from './helpers/analytics'\n\nVue.use(Router)\n\nconst router = new Router({\n  base: process.env.BASE_URL,\n  routes: [\n    {\n      path: '/',\n      name: 'main',\n      component: Main,\n      children: [\n        {\n          path: '/articles',\n          name: 'articles',\n          component: Articles,\n        },\n        {\n          path: '/menu',\n          name: 'menu',\n          component: Menu,\n        },\n        {\n          path: '/tags',\n          name: 'tags',\n          component: Tags,\n        },\n        {\n          path: '/theme',\n          name: 'theme',\n          component: Theme,\n        },\n        {\n          path: '/setting',\n          name: 'setting',\n          component: Setting,\n        },\n        {\n          path: '/loading',\n          name: 'loading',\n          component: Loading,\n        },\n        {\n          path: '*',\n          redirect: '/articles',\n        },\n      ],\n    },\n  ],\n})\n\nrouter.afterEach((to, from) => {\n  ga.pageView(to.fullPath, to.name)\n})\n\nexport default router\n"
  },
  {
    "path": "src/server/app.ts",
    "content": "import { BrowserWindow, app } from 'electron'\nimport * as fse from 'fs-extra'\nimport * as path from 'path'\nimport express from 'express'\nimport EventClasses from './events/index'\nimport Posts from './posts'\nimport Tags from './tags'\nimport Menus from './menus'\nimport Theme from './theme'\nimport Renderer from './renderer'\nimport Setting from './setting'\nimport Deploy from './deploy'\n\nimport { IApplicationDb, IApplicationSetting } from './interfaces/application'\nimport {\n  DEFAULT_POST_PAGE_SIZE, DEFAULT_ARCHIVES_PAGE_SIZE, DEFAULT_FEED_COUNT, DEFAULT_ARCHIVES_PATH, DEFAULT_POST_PATH, DEFAULT_TAG_PATH,\n} from '../helpers/constants'\n// eslint-disable-next-line\ndeclare const __static: string\n\nexport default class App {\n  mainWindow: BrowserWindow\n\n  app: any\n\n  baseDir: string\n\n  appDir: string\n\n  previewServer: any\n\n  db: IApplicationDb\n\n  buildDir: string\n\n  constructor(setting: IApplicationSetting) {\n    this.mainWindow = setting.mainWindow\n    this.app = setting.app\n    this.baseDir = setting.baseDir\n    this.appDir = path.join(this.app.getPath('documents'), 'gridea')\n    this.previewServer = setting.previewServer\n    this.buildDir = path.join(this.app.getPath('home'), '.gridea', 'output')\n\n    this.db = {\n      posts: [],\n      tags: [],\n      menus: [],\n      themeConfig: {\n        themeName: '',\n        postPageSize: DEFAULT_POST_PAGE_SIZE,\n        archivesPageSize: DEFAULT_ARCHIVES_PAGE_SIZE,\n        siteName: '',\n        siteDescription: '',\n        footerInfo: 'Powered by Gridea',\n        showFeatureImage: true,\n        domain: '',\n        postUrlFormat: 'SLUG',\n        tagUrlFormat: 'SLUG',\n        dateFormat: 'YYYY-MM-DD',\n        feedFullText: true,\n        feedCount: DEFAULT_FEED_COUNT,\n        archivesPath: DEFAULT_ARCHIVES_PATH,\n        postPath: DEFAULT_POST_PATH,\n        tagPath: DEFAULT_TAG_PATH,\n      },\n      themeCustomConfig: {},\n      currentThemeConfig: [],\n      themes: [],\n      setting: {\n        platform: 'github',\n        domain: '',\n        repository: '',\n        branch: '',\n        username: '',\n        email: '',\n        tokenUsername: '',\n        token: '',\n        cname: '',\n        port: '22',\n        server: '',\n        password: '',\n        privateKey: '',\n        remotePath: '',\n        proxyPath: '',\n        proxyPort: '',\n        enabledProxy: 'direct',\n        netlifySiteId: '',\n        netlifyAccessToken: '',\n      },\n      commentSetting: {\n        showComment: false,\n        commentPlatform: 'gitalk',\n        gitalkSetting: {\n          clientId: '',\n          clientSecret: '',\n          repository: '',\n          owner: '',\n        },\n        disqusSetting: {\n          api: '',\n          apikey: '',\n          shortname: '',\n        },\n      },\n    }\n\n    this.checkDir()\n  }\n\n  /**\n   *  Load site config and data\n   */\n  public async loadSite() {\n    const postsInstance = new Posts(this)\n    const posts = await postsInstance.list()\n\n    const tagsInstance = new Tags(this)\n    const tags = await tagsInstance.list()\n\n    const menusInstance = new Menus(this)\n    const menus = await menusInstance.list()\n\n    const themeInstance = new Theme(this)\n    const themeConfig = await themeInstance.getThemeConfig()\n    const themes = await themeInstance.getThemeList()\n    const themeCustomConfig = await themeInstance.getThemeCustomConfig()\n    const currentThemeConfig = await themeInstance.getCurrentThemeCustomConfig()\n\n    const settingInstance = new Setting(this)\n    const setting = await settingInstance.getSetting()\n    const commentSetting = await settingInstance.getCommentSetting()\n\n    const deployInstance = new Deploy(this)\n\n    this.db = {\n      posts,\n      tags,\n      menus,\n      themeConfig: Object.assign(this.db.themeConfig, themeConfig),\n      themeCustomConfig,\n      currentThemeConfig,\n      themes,\n      setting,\n      commentSetting: commentSetting || this.db.commentSetting,\n    }\n    this.updateStaticServer()\n    this.initEvents()\n    return {\n      ...this.db,\n      currentThemeConfig,\n      appDir: this.appDir,\n      mainWindow: this.mainWindow,\n    }\n  }\n\n  public renderHtml() {\n    const renderer = new Renderer(this)\n    console.log(renderer)\n    // renderer.renderPostList()\n  }\n\n  public async saveSourceFolderSetting(sourceFolderPath: string = '') {\n    try {\n      const appConfigFolder = path.join(this.app.getPath('home'), '.gridea')\n      const appConfigPath = path.join(appConfigFolder, 'config.json')\n      const jsonString = `{\"sourceFolder\": \"${sourceFolderPath || this.appDir}\"}`\n\n      fse.writeFileSync(appConfigPath, jsonString)\n      const appConfig = fse.readJsonSync(appConfigPath)\n      this.appDir = appConfig.sourceFolder\n      this.updateStaticServer()\n\n      this.checkDir()\n\n      return true\n    } catch (e) {\n      console.log(e)\n      return false\n    }\n  }\n\n  /**\n   * Check if the hve-next folder exists, if it does not exist, it is initialized\n   */\n  private async checkDir() {\n    // Check if there is a .hve-notes folder, if it exists, load it, otherwise use the default configuration.\n    const appConfigFolderOld = path.join(this.app.getPath('home'), '.hve-notes') // < 0.7.7\n\n    const appConfigFolder = path.join(this.app.getPath('home'), '.gridea')\n    const appConfigPath = path.join(appConfigFolder, 'config.json')\n    let defaultAppDir = path.join(this.app.getPath('documents'), 'Gridea')\n    defaultAppDir = defaultAppDir.replace(/\\\\/g, '/')\n\n    try {\n      // if exist `.hve-notes` config folder, change folder name to `.gridea`\n      try {\n        if (!fse.pathExistsSync(appConfigFolder) && fse.pathExistsSync(appConfigFolderOld)) {\n          fse.renameSync(appConfigFolderOld, appConfigFolder)\n        }\n      } catch (e) {\n        console.log('Rename Error: ', e)\n      }\n\n      if (!fse.pathExistsSync(appConfigFolder)) {\n        fse.mkdirSync(appConfigFolder)\n        const jsonString = `{\"sourceFolder\": \"${defaultAppDir}\"}`\n        fse.writeFileSync(appConfigPath, jsonString)\n      }\n\n      const buildDir = path.join(appConfigFolder, 'output')\n      if (!fse.pathExistsSync(buildDir)) {\n        fse.mkdirSync(buildDir)\n      }\n\n      const appConfig = fse.readJsonSync(appConfigPath)\n      this.appDir = appConfig.sourceFolder\n\n      // Site folder exists\n      if (fse.pathExistsSync(this.appDir)) {\n        // Check if the `images`, `config`, 'output', `post-images`, 'posts', 'themes', 'static' folder exists, if it does not exist, copy it from default-files\n        ['images', 'config', 'post-images', 'posts', 'themes', 'static'].forEach((folder: string) => {\n          const folderPath = path.join(this.appDir, folder)\n          if (!fse.pathExistsSync(folderPath)) {\n            fse.copySync(\n              path.join(__static, 'default-files', folder),\n              folderPath,\n            )\n          }\n        })\n\n        // Check default theme folder if includes [notes、fly、simple、paper] themes\n        this.checkTheme('notes')\n        this.checkTheme('fly')\n        this.checkTheme('simple')\n        this.checkTheme('paper')\n\n        // move output/favicon.ico to Gridea/favicon.ico\n        const outputFavicon = path.join(this.buildDir, 'favicon.ico')\n        const sourceFavicon = path.join(this.appDir, 'favicon.ico')\n        const existFaviconOutput = fse.pathExistsSync(outputFavicon)\n        const existFaviconSource = fse.pathExistsSync(sourceFavicon)\n        if (existFaviconOutput && !existFaviconSource) {\n          fse.moveSync(outputFavicon, sourceFavicon)\n        }\n\n        return\n      }\n\n      // Site folder not exists\n      this.appDir = defaultAppDir\n      const jsonString = `{\"sourceFolder\": \"${defaultAppDir}\"}`\n      fse.writeFileSync(appConfigPath, jsonString)\n      fse.mkdirSync(this.appDir)\n\n      fse.copySync(\n        path.join(__static, 'default-files'),\n        path.join(this.appDir),\n      )\n    } catch (e) {\n      console.log('Error', e)\n    } finally {\n      this.initEvents()\n    }\n  }\n\n  /**\n   * Check whether the theme is included, and if not, initialize one copy of the theme within the application.\n   */\n  private checkTheme(themeName: string): void {\n    const themePath = path.join(this.appDir, 'themes', themeName)\n    if (!fse.pathExistsSync(themePath)) {\n      fse.copySync(\n        path.join(__static, 'default-files', 'themes', themeName),\n        themePath,\n      )\n    }\n  }\n\n  private updateStaticServer(): void {\n    function removeMiddleware(route: any, i: number, routes: any) {\n      if (route.handle.name === 'serveStatic') {\n        routes.splice(i, 1)\n        console.log('Preview server: Removed old static route')\n      }\n    }\n    const routers = this.previewServer._router // eslint-disable-line no-underscore-dangle\n    if (routers) {\n      const routesStack = routers.stack\n      routesStack.forEach(removeMiddleware)\n    }\n    this.previewServer.use(express.static(`${this.buildDir}`))\n    console.log(`Preview server: Static dir change to ${this.buildDir}`)\n  }\n\n  private initEvents(): void {\n    const {\n      SiteEvents,\n      PostEvents,\n      TagEvents,\n      MenuEvents,\n      ThemeEvents,\n      RendererEvents,\n      SettingEvents,\n      DeployEvents,\n    } = EventClasses\n\n    const site = new SiteEvents(this)\n    const post = new PostEvents(this)\n    const tag = new TagEvents(this)\n    const menu = new MenuEvents(this)\n    const theme = new ThemeEvents(this)\n    const renderer = new RendererEvents(this)\n    const setting = new SettingEvents(this)\n    const deploy = new DeployEvents(this)\n  }\n}\n"
  },
  {
    "path": "src/server/deploy.ts",
    "content": "import fs from 'fs'\nimport moment from 'moment'\n// @ts-ignore\nimport Model from './model'\nimport GitProxy from './plugins/deploys/gitproxy'\n\nconst git = require('isomorphic-git')\n\nexport default class Deploy extends Model {\n  outputDir: string = this.buildDir\n\n  remoteUrl = ''\n\n  platformAddress = ''\n\n  http = new GitProxy(this)\n\n  constructor(appInstance: any) {\n    super(appInstance)\n    const { setting } = this.db\n    this.platformAddress = ({\n      github: 'github.com',\n      coding: 'e.coding.net',\n      gitee: 'gitee.com',\n    } as any)[setting.platform || 'github']\n\n    const preUrl = ({\n      github: `${setting.username}:${setting.token}`,\n      coding: `${setting.tokenUsername}:${setting.token}`,\n      gitee: `${setting.username}:${setting.token}`,\n    } as any)[setting.platform || 'github']\n\n    this.remoteUrl = `https://${preUrl}@${this.platformAddress}/${setting.username}/${setting.repository}.git`\n  }\n\n  /**\n   * Check whether the remote connection is normal\n   */\n  async remoteDetect() {\n    const result = {\n      success: true,\n      message: [''],\n    }\n    try {\n      const { setting } = this.db\n      let isRepo = false\n      try {\n        await git.currentBranch({ fs, dir: this.outputDir })\n        isRepo = true\n      } catch (e) {\n        console.log('Not a repo', e.message)\n      }\n\n      if (!setting.username || !setting.repository || !setting.token) {\n        return {\n          success: false,\n          message: 'Username、repository、token is required',\n        }\n      }\n      if (!isRepo) {\n        await git.init({ fs, dir: this.outputDir })\n        await git.setConfig({\n          fs,\n          dir: this.outputDir,\n          path: 'user.name',\n          value: setting.username,\n        })\n        await git.setConfig({\n          fs,\n          dir: this.outputDir,\n          path: 'user.email',\n          value: setting.email,\n        })\n      }\n\n      await git.addRemote({\n        fs, dir: this.outputDir, remote: 'origin', url: this.remoteUrl, force: true,\n      })\n      const info = await git.getRemoteInfo({\n        http: this.http,\n        url: this.remoteUrl,\n      })\n      console.log('info', info)\n      result.message = info.capabilities\n    } catch (e) {\n      console.log('Test Remote Error: ', e)\n      result.success = false\n      result.message = e.message\n    }\n    return result\n  }\n\n  async publish() {\n    await this.remoteDetect()\n    this.db.themeConfig.domain = this.db.setting.domain\n    let result = {\n      success: true,\n      message: '',\n      localBranchs: {},\n    }\n    let isRepo = false\n    try {\n      await git.currentBranch({ fs, dir: this.outputDir })\n      isRepo = true\n    } catch (e) {\n      console.log('Not a repo', e.message)\n    }\n    if (isRepo) {\n      result = await this.commonPush()\n    } else {\n      // result = await this.firstPush()\n    }\n    return result\n  }\n\n  async commonPush() {\n    console.log('common push')\n    const { setting } = this.db\n    const localBranchs = {}\n    try {\n      const statusSummary = await git.status({ fs, dir: this.outputDir, filepath: '.' })\n      console.log('statusSummary', statusSummary)\n      await git.addRemote({\n        fs, dir: this.outputDir, remote: 'origin', url: this.remoteUrl, force: true,\n      })\n\n      if (statusSummary !== 'unmodified') {\n        await git.add({ fs, dir: this.outputDir, filepath: '.' })\n        await git.commit({\n          fs,\n          dir: this.outputDir,\n          message: `update from gridea: ${moment().format('YYYY-MM-DD HH:mm:ss')}`,\n        })\n      }\n\n      await this.checkCurrentBranch()\n\n      const pushRes = await git.push({\n        fs,\n        http: this.http,\n        dir: this.outputDir,\n        remote: 'origin',\n        ref: setting.branch,\n        force: true,\n      })\n      console.log('pushRes', pushRes)\n      return {\n        success: true,\n        data: pushRes,\n        message: '',\n        localBranchs,\n      }\n    } catch (e) {\n      console.log(e)\n      return {\n        success: false,\n        message: e.message,\n        data: localBranchs,\n        localBranchs,\n      }\n    }\n  }\n\n  /**\n   * Check whether the branch needs to be switched,\n   * FIXME: if branch is change, then the fist push is not work. so need to push again.\n   */\n  async checkCurrentBranch() {\n    const { setting } = this.db\n    const currentBranch = await git.currentBranch({ fs, dir: this.outputDir, fullname: false })\n    const localBranches = await git.listBranches({ fs, dir: this.outputDir })\n\n    if (currentBranch !== setting.branch) {\n      if (!localBranches.includes(setting.branch)) {\n        await git.branch({ fs, dir: this.outputDir, ref: setting.branch })\n      }\n\n      await git.checkout({ fs, dir: this.outputDir, ref: setting.branch })\n    }\n  }\n}\n"
  },
  {
    "path": "src/server/events/deploy.ts",
    "content": "import { ipcMain, IpcMainEvent } from 'electron'\nimport Deploy from '../deploy'\nimport Renderer from '../renderer'\nimport SftpDeploy from '../plugins/deploys/sftp'\nimport NetlifyDeploy from '../plugins/deploys/netlify'\n\nexport default class DeployEvents {\n  constructor(appInstance: any) {\n    const { platform } = appInstance.db.setting\n    \n    const deploy = new Deploy(appInstance)\n    const sftp = new SftpDeploy(appInstance)\n    const renderer = new Renderer(appInstance)\n    const netlify = new NetlifyDeploy(appInstance)\n\n    ipcMain.removeAllListeners('site-publish')\n    ipcMain.removeAllListeners('site-published')\n    ipcMain.removeAllListeners('remote-detect')\n    ipcMain.removeAllListeners('remote-detected')\n\n    ipcMain.on('site-publish', async (event: IpcMainEvent, params: any) => {\n      console.log(platform)\n      const client = ({\n        'github': deploy,\n        'coding': deploy,\n        'gitee': deploy,\n        'sftp': sftp,\n        'netlify': netlify,\n      } as any)[platform]\n      \n      // render\n      renderer.db.themeConfig.domain = renderer.db.setting.domain\n      await renderer.renderAll()\n      \n      // publish\n      const result = await client.publish()\n      event.sender.send('site-published', result)\n    })\n\n    ipcMain.on('remote-detect', async (event: IpcMainEvent, params: any) => {\n      const client = ({\n        'github': deploy,\n        'coding': deploy,\n        'gitee': deploy,\n        'sftp': sftp,\n        'netlify': netlify,\n      } as any)[platform]\n      \n      const result = await client.remoteDetect()\n      event.sender.send('remote-detected', result)\n    })\n  }\n}\n"
  },
  {
    "path": "src/server/events/index.ts",
    "content": "import SiteEvents from './site'\nimport PostEvents from './post'\nimport TagEvents from './tag'\nimport MenuEvents from './menu'\nimport ThemeEvents from './theme'\nimport RendererEvents from './renderer'\nimport SettingEvents from './setting'\nimport DeployEvents from './deploy'\n\nexport default {\n  SiteEvents,\n  PostEvents,\n  TagEvents,\n  MenuEvents,\n  ThemeEvents,\n  RendererEvents,\n  SettingEvents,\n  DeployEvents,\n}\n"
  },
  {
    "path": "src/server/events/menu.ts",
    "content": "import { ipcMain, IpcMainEvent } from 'electron'\nimport Menus from '../menus'\nimport { IMenu } from '../interfaces/menu'\n\nexport default class MenuEvents {\n  constructor(appInstance: any) {\n    const menus = new Menus(appInstance)\n\n    ipcMain.removeAllListeners('menu-delete')\n    ipcMain.removeAllListeners('menu-deleted')\n    ipcMain.removeAllListeners('menu-save')\n    ipcMain.removeAllListeners('menu-saved')\n    ipcMain.removeAllListeners('menu-sort')\n    ipcMain.removeAllListeners('menu-sorted')\n\n    ipcMain.on('menu-delete', async (event: IpcMainEvent, menuName: string) => {\n      const data = await menus.deleteMenu(menuName)\n      event.sender.send('menu-deleted', data)\n    })\n\n    ipcMain.on('menu-save', async (event: IpcMainEvent, menu: IMenu) => {\n      const data = await menus.saveMenu(menu)\n      event.sender.send('menu-saved', data)\n    })\n\n    ipcMain.on('menu-sort', async (event: IpcMainEvent, menuList: IMenu[]) => {\n      const data = await menus.saveMenus(menuList)\n      event.sender.send('menu-sorted', data)\n    })\n  }\n}\n"
  },
  {
    "path": "src/server/events/post.ts",
    "content": "import { ipcMain, IpcMainEvent } from 'electron'\nimport { IPost, IPostDb } from '../interfaces/post'\nimport Posts from '../posts'\n\nexport default class PostEvents {\n  constructor(appInstance: any) {\n    ipcMain.removeAllListeners('app-post-create')\n    ipcMain.removeAllListeners('app-post-created')\n    ipcMain.removeAllListeners('app-post-delete')\n    ipcMain.removeAllListeners('app-post-deleted')\n    ipcMain.removeAllListeners('app-post-list-delete')\n    ipcMain.removeAllListeners('app-post-list-deleted')\n    ipcMain.removeAllListeners('image-upload')\n    ipcMain.removeAllListeners('image-uploaded')\n\n    const posts = new Posts(appInstance)\n\n    ipcMain.on('app-post-create', async (event: IpcMainEvent, post: IPost) => {\n      const data = await posts.savePostToFile(post)\n      event.sender.send('app-post-created', data)\n    })\n\n    ipcMain.on('app-post-delete', async (event: IpcMainEvent, post: IPostDb) => {\n      const data = await posts.deletePost(post)\n      event.sender.send('app-post-deleted', data)\n    })\n\n    ipcMain.on('app-post-list-delete', async (event: IpcMainEvent, postList: IPostDb[]) => {\n      let data: any = false\n      for (const post of postList) {\n        data = posts.deletePost(post)\n      }\n\n      event.sender.send('app-post-list-deleted', data)\n    })\n\n    ipcMain.on('image-upload', async (event: IpcMainEvent, files: any[]) => {\n      console.log('执行了上传图片', files)\n      const data = await posts.uploadImages(files)\n      event.sender.send('image-uploaded', data)\n    })\n  }\n}\n"
  },
  {
    "path": "src/server/events/renderer.ts",
    "content": "import { ipcMain, IpcMainEvent } from 'electron'\nimport Renderer from '../renderer'\n\nexport default class RendererEvents {\n  constructor(appInstance: any) {\n    const renderer = new Renderer(appInstance)\n\n    ipcMain.removeAllListeners('html-render')\n    ipcMain.removeAllListeners('html-rendered')\n\n    ipcMain.on('html-render', async (event: IpcMainEvent, params: any) => {\n      if (renderer.db.themeConfig.themeName) {\n        await renderer.preview()\n      }\n      event.sender.send('html-rendered', null)\n    })\n  }\n}\n"
  },
  {
    "path": "src/server/events/setting.ts",
    "content": "import { ipcMain, IpcMainEvent } from 'electron'\nimport Setting from '../setting'\nimport { ICommentSetting } from '../interfaces/setting'\nimport { ISetting } from '../../interfaces/setting'\n\nexport default class SettingEvents {\n  constructor(appInstance: any) {\n    const settingInstance = new Setting(appInstance)\n\n    ipcMain.removeAllListeners('setting-save')\n    ipcMain.removeAllListeners('setting-saved')\n    ipcMain.removeAllListeners('comment-setting-save')\n    ipcMain.removeAllListeners('comment-setting-saved')\n    ipcMain.removeAllListeners('favicon-upload')\n    ipcMain.removeAllListeners('favicon-uploaded')\n    ipcMain.removeAllListeners('avatar-upload')\n    ipcMain.removeAllListeners('avatar-uploaded')\n\n    ipcMain.on('setting-save', async (event: IpcMainEvent, setting: ISetting) => {\n      const data = await settingInstance.saveSetting(setting)\n      event.sender.send('setting-saved', data)\n    })\n\n    ipcMain.on('comment-setting-save', async (event: IpcMainEvent, setting: ICommentSetting) => {\n      const data = await settingInstance.saveCommentSetting(setting)\n      event.sender.send('comment-setting-saved', data)\n    })\n\n    ipcMain.on('favicon-upload', async (event: IpcMainEvent, filePath: string) => {\n      console.log('执行了上传图片', filePath)\n      const data = await settingInstance.uploadFavicon(filePath)\n      event.sender.send('favicon-uploaded', data)\n    })\n\n    ipcMain.on('avatar-upload', async (event: IpcMainEvent, filePath: string) => {\n      console.log('执行了上传头像', filePath)\n      const data = await settingInstance.uploadAvatar(filePath)\n      event.sender.send('avatar-uploaded', data)\n    })\n  }\n}\n"
  },
  {
    "path": "src/server/events/site.ts",
    "content": "import { ipcMain, IpcMainEvent } from 'electron'\n\nexport default class SiteEvents {\n  constructor(appInstance: any) {\n    /**\n     * load site config and data\n     */\n    ipcMain.removeAllListeners('app-site-reload')\n    ipcMain.removeAllListeners('app-site-loaded')\n    ipcMain.removeAllListeners('app-source-folder-setting')\n    ipcMain.removeAllListeners('app-source-folder-set')\n    ipcMain.removeAllListeners('app-preview-server-port-get')\n    ipcMain.removeAllListeners('app-preview-server-port-got')\n\n    ipcMain.on('app-site-reload', async (event: IpcMainEvent, params: any) => {\n      const result = await appInstance.loadSite()\n      event.sender.send('app-site-loaded', result)\n    })\n\n    ipcMain.on('app-source-folder-setting', async (event: IpcMainEvent, params: string) => {\n      const result = await appInstance.saveSourceFolderSetting(params)\n      event.sender.send('app-source-folder-set', result)\n    })\n\n    ipcMain.on('app-preview-server-port-get', async (event: IpcMainEvent, params: string) => {\n      const port = await appInstance.previewServer.get('port')\n      event.sender.send('app-preview-server-port-got', port)\n    })\n  }\n}\n"
  },
  {
    "path": "src/server/events/tag.ts",
    "content": "import { ipcMain, IpcMainEvent } from 'electron'\nimport Tags from '../tags'\nimport { ITag } from '../interfaces/tag'\n\nexport default class TagEvents {\n  constructor(appInstance: any) {\n    const tags = new Tags(appInstance)\n\n    ipcMain.removeAllListeners('tag-delete')\n    ipcMain.removeAllListeners('tag-deleted')\n    ipcMain.removeAllListeners('tag-save')\n    ipcMain.removeAllListeners('tag-saved')\n\n    ipcMain.on('tag-delete', async (event: IpcMainEvent, tagName: string) => {\n      const data = await tags.deleteTag(tagName)\n      event.sender.send('tag-deleted', data)\n    })\n\n    ipcMain.on('tag-save', async (event: IpcMainEvent, tag: ITag) => {\n      const data = await tags.saveTag(tag)\n      event.sender.send('tag-saved', data)\n    })\n  }\n}\n"
  },
  {
    "path": "src/server/events/theme.ts",
    "content": "import { ipcMain, IpcMainEvent } from 'electron'\nimport { ITheme } from '../interfaces/theme'\nimport Theme from '../theme'\n\nexport default class ThemeEvents {\n  constructor(appInstance: any) {\n    const theme = new Theme(appInstance)\n\n    ipcMain.removeAllListeners('theme-save')\n    ipcMain.removeAllListeners('theme-saved')\n    ipcMain.removeAllListeners('theme-custom-config-save')\n    ipcMain.removeAllListeners('theme-custom-config-saved')\n\n    ipcMain.on('theme-save', async (event: IpcMainEvent, themeConfig: ITheme) => {\n      const config = await theme.saveThemeConfig(themeConfig)\n      event.sender.send('theme-saved', config)\n    })\n\n    ipcMain.on('theme-custom-config-save', async (event: IpcMainEvent, config: any) => {\n      const result = await theme.saveThemeCustomConfig(config)\n      event.sender.send('theme-custom-config-saved', result)\n    })\n  }\n}\n"
  },
  {
    "path": "src/server/interfaces/application.ts",
    "content": "import { BrowserWindow } from 'electron'\nimport { IPostDb } from './post'\nimport { ITag } from './tag'\nimport { ITheme } from './theme'\nimport { IMenu } from './menu'\nimport { ICommentSetting } from './setting'\nimport { ISetting } from '../../interfaces/setting'\n\nexport interface IApplicationSetting {\n  mainWindow: BrowserWindow\n  app: any\n  baseDir: string\n  previewServer: any\n}\n\nexport interface IApplicationDb {\n  posts: IPostDb[]\n  tags: ITag[]\n  menus: IMenu[]\n  themeConfig: ITheme\n  themeCustomConfig: any\n  themes: any[]\n  setting: ISetting\n  commentSetting: ICommentSetting\n  currentThemeConfig: any[]\n}\n\nexport interface IApplication {\n  mainWindow: BrowserWindow\n  app: any\n  baseDir: string\n  appDir: string\n  buildDir: string\n  db: IApplicationDb\n}\n"
  },
  {
    "path": "src/server/interfaces/menu.ts",
    "content": "export interface IMenu {\n  name: string\n  index?: number\n  openType: string\n  link: string\n}\n"
  },
  {
    "path": "src/server/interfaces/post.ts",
    "content": "import { ITag } from './tag'\n\nexport interface IPost {\n  title: string\n  fileName: string\n  tags: string[]\n  date: string\n  content: string\n  published: boolean\n  hideInList: boolean\n  isTop: boolean\n  featureImage: {\n    name?: string,\n    path?: string,\n    type?: string,\n  }\n  /** 外链封面图 */\n  featureImagePath: string\n  deleteFileName?: string\n}\n\nexport interface IPostData {\n  title: string\n  date: string\n  published: boolean\n  hideInList: boolean\n  isTop: boolean\n  tags?: []\n  feature: string\n}\n\nexport interface IPostDb {\n  content: string\n\n  abstract: string,\n\n  data: IPostData\n\n  fileName: string\n}\n\nexport interface ITagRenderData extends ITag {\n  link: string\n}\n\nexport interface ISiteTagsData extends ITagRenderData {\n  count: number\n}\n\nexport interface IStats {\n  text: string\n  minutes: number\n  time: number\n  words: number\n}\n\nexport interface IPostRenderData {\n  content: string\n  fileName: string\n  abstract: string\n  title: string\n  tags: ITagRenderData[]\n  date: string\n  dateFormat: string\n  feature: string\n  link: string\n  hideInList: boolean\n  isTop: boolean\n  toc?: any\n  stats: IStats\n  nextPost?: IPostRenderData\n  prevPost?: IPostRenderData\n  description: string\n}\n"
  },
  {
    "path": "src/server/interfaces/renderer.ts",
    "content": "export interface IPagination {\n  prev: string,\n  next: string,\n}\n"
  },
  {
    "path": "src/server/interfaces/setting.ts",
    "content": "export interface IDisqusSetting {\n  api: string\n  apikey: string\n  shortname: string\n}\nexport interface IGitalkSetting {\n  clientId: string\n  clientSecret: string\n  repository: string\n  owner: string\n}\n\nexport interface ICommentSetting {\n  commentPlatform: string\n  showComment: boolean\n  disqusSetting: IDisqusSetting\n  gitalkSetting: IGitalkSetting\n}\n"
  },
  {
    "path": "src/server/interfaces/tag.ts",
    "content": "export interface ITag {\n  name: string\n  used: boolean\n  slug?: string\n  index?: number\n}\n"
  },
  {
    "path": "src/server/interfaces/theme.ts",
    "content": "export interface ITheme {\n  themeName: string\n  postPageSize: number\n  archivesPageSize: number\n  siteName: string\n  siteDescription: string\n  footerInfo: string\n  showFeatureImage: boolean\n  domain: string\n  postUrlFormat: string\n  tagUrlFormat: string\n  dateFormat: string\n  feedFullText: boolean\n  feedCount: number\n  archivesPath: string\n  postPath: string\n  tagPath: string\n}\n"
  },
  {
    "path": "src/server/menus.ts",
    "content": "import Model from './model'\nimport { IMenu } from './interfaces/menu'\n\nexport default class Menus extends Model {\n  list() {\n    const menus = this.$posts.get('menus').value()\n    return menus\n  }\n\n  public async saveMenu(menu: IMenu) {\n    const menus = await this.$posts.get('menus').value()\n    if (typeof menu.index === 'number') {\n      const { index } = menu\n      delete menu.index\n      menus[index] = menu\n    } else {\n      delete menu.index\n      menus.push(menu)\n    }\n\n    await this.$posts.set('menus', menus).write()\n    return menus\n  }\n\n  public async saveMenus(menus: IMenu[]) {\n    await this.$posts.set('menus', menus).write()\n    return menus\n  }\n\n  public async deleteMenu(menuValue: string) {\n    const menu = await this.$posts.get('menus').remove({ name: menuValue }).write()\n    return menu\n  }\n}\n"
  },
  {
    "path": "src/server/model.ts",
    "content": "import path from 'path'\nimport low from 'lowdb'\nimport FileSync from 'lowdb/adapters/FileSync'\nimport { BrowserWindow } from 'electron'\nimport { IApplicationDb, IApplication } from './interfaces/application'\n\nexport default class Model {\n  appDir: string\n\n  buildDir: string\n\n  $setting: any\n\n  $posts: any\n\n  $theme: any\n\n  db: IApplicationDb\n\n  mainWindow: BrowserWindow\n\n  constructor(appInstance: IApplication) {\n    this.appDir = appInstance.appDir\n    this.buildDir = appInstance.buildDir\n    this.db = appInstance.db\n    this.mainWindow = appInstance.mainWindow\n\n    this.initDataStore()\n  }\n\n  private initDataStore(): void {\n    const settingAdapter = new FileSync(path.join(this.appDir, 'config/setting.json'))\n    const setting = low(settingAdapter)\n    this.$setting = setting\n\n    const postsAdapter = new FileSync(path.join(this.appDir, 'config/posts.json'))\n    const posts = low(postsAdapter)\n    this.$posts = posts\n\n    const themeAdapter = new FileSync(path.join(this.appDir, 'config/theme.json'))\n    const theme = low(themeAdapter)\n    this.$theme = theme\n  }\n}\n"
  },
  {
    "path": "src/server/plugins/deploys/gitproxy.ts",
    "content": "import { IApplicationDb } from '../../interfaces/application'\n\nconst { HttpProxyAgent, HttpsProxyAgent } = require('hpagent')\nconst get = require('simple-get')\n\nexport default class GitProxy {\n  db: IApplicationDb\n\n  constructor(appInstance: any) {\n    this.db = appInstance.db\n    // console.log('instance git proxy',this.db.setting)\n  }\n\n  public async request({\n    url,\n    method,\n    headers,\n    body,\n  }: {\n    url: any;\n    method: any;\n    headers: any;\n    body: any;\n  }) {\n    const { setting } = this.db\n    body = await this.mergeBuffers(body)\n    const proxy = url.startsWith('https:')\n      ? { Agent: HttpsProxyAgent }\n      : { Agent: HttpProxyAgent }\n    const agent = setting.enabledProxy === 'proxy'\n      ? new proxy.Agent({\n        proxy: `http://${setting.proxyPath}:${setting.proxyPort}`,\n      })\n      : undefined\n    // const agent = new proxy.Agent({ proxy: 'http://127.0.0.1:1081' })\n    return new Promise((resolve, reject) => get(\n      {\n        url,\n        method,\n        agent,\n        headers,\n        body,\n      },\n      (err: any, res: any) => (err ? reject(err) : resolve(this.transformResponse(res))),\n    ))\n  }\n\n  private async mergeBuffers(data: any[] | Uint8Array) {\n    if (!Array.isArray(data)) return data\n    if (data.length === 1 && data[0] instanceof Buffer) return data[0]\n    const buffers = []\n    let offset = 0\n    let size = 0\n    for await (const chunk of data) {\n      buffers.push(chunk)\n      size += chunk.byteLength\n    }\n    data = new Uint8Array(size)\n    for (const buffer of buffers) {\n      data.set(buffer, offset)\n      offset += buffer.byteLength\n    }\n    return Buffer.from(data.buffer)\n  }\n\n  private async transformResponse(res: {\n    url: any;\n    method: any;\n    statusCode: any;\n    statusMessage: any;\n    headers: any;\n  }) {\n    const {\n      url, method, statusCode, statusMessage, headers,\n    } = res\n    return {\n      url,\n      method,\n      statusCode,\n      statusMessage,\n      headers,\n      body: res,\n    }\n  }\n}\n"
  },
  {
    "path": "src/server/plugins/deploys/netlify.ts",
    "content": "import fs from 'fs'\nimport path from 'path'\nimport axios from 'axios'\nimport normalizePath from 'normalize-path'\nimport crypto from 'crypto'\nimport util from 'util'\nimport Model from '../../model'\nimport { IApplication } from '../../interfaces/application'\n\nconst asyncReadFile = util.promisify(fs.readFile)\n\nexport default class NetlifyApi extends Model {\n  private apiUrl: string\n\n  private accessToken: string\n\n  private siteId: string\n\n  private inputDir: string\n\n  constructor(appInstance: IApplication) {\n    super(appInstance)\n    this.apiUrl = 'https://api.netlify.com/api/v1/'\n    this.accessToken = appInstance.db.setting.netlifyAccessToken\n    this.siteId = appInstance.db.setting.netlifySiteId\n    this.inputDir = appInstance.buildDir\n  }\n\n  async request(method: 'GET' | 'PUT' | 'POST', endpoint: string, data?: any) {\n    const endpointUrl = this.apiUrl + endpoint.replace(':site_id', this.siteId)\n    const { setting } = this.db\n    const proxy = setting.enabledProxy === 'proxy' ? {\n      host: setting.proxyPath,\n      port: Number(setting.proxyPort),\n    } : undefined\n\n    return axios(\n      endpointUrl,\n      {\n        method,\n        headers: {\n          'User-Agent': 'Gridea',\n          'Authorization': `Bearer ${this.accessToken}`,\n        },\n        data,\n        proxy,\n      },\n    )\n  }\n\n  async remoteDetect() {\n    try {\n      const res = await this.request('GET', 'sites/:site_id/')\n      if (res.status === 200) {\n        return {\n          success: true,\n          message: res.data,\n        }\n      }\n\n      return {\n        success: false,\n        message: res.data,\n      }\n    } catch (e) {\n      return {\n        success: false,\n        message: e,\n      }\n    }\n  }\n\n  async publish() {\n    const result = {\n      success: true,\n      message: '同步成功',\n      data: null,\n    }\n\n    try {\n      const localFilesList = await this.prepareLocalFilesList()\n      const deployData = await this.request('POST', 'sites/:site_id/deploys', localFilesList)\n      const deployId = deployData.data.id\n      const hashOfFilesToUpload = deployData.data.required\n      const filesToUpload = this.getFilesToUpload(localFilesList, hashOfFilesToUpload)\n\n      for (let i = 0; i < filesToUpload.length; i += 1) {\n        const filePath = filesToUpload[i]\n\n        try {\n          // eslint-disable-next-line no-await-in-loop\n          const res = await this.uploadFile(filePath, deployId)\n\n          if (res.status === 422) {\n            return Promise.reject(res)\n          }\n        } catch (e) {\n          try {\n            // eslint-disable-next-line no-await-in-loop\n            const res = await this.uploadFile(filePath, deployId)\n\n            if (res.status === 422) {\n              return Promise.reject(res)\n            }\n          } catch (error) {\n            return Promise.reject(error)\n          }\n        }\n      }\n\n      return result\n    } catch (e) {\n      result.success = false\n      result.message = `[Server] 同步失败： ${e.message}`\n    }\n  }\n\n  async prepareLocalFilesList() {\n    const tempFileList: any = this.readDirRecursiveSync(this.inputDir)\n    const fileList: any = {}\n\n    for (const filePath of tempFileList) {\n      if (fs.lstatSync(path.join(this.inputDir, filePath)).isDirectory()) {\n        continue\n      }\n\n      // eslint-disable-next-line no-await-in-loop\n      const fileHash = await this.getFileHash(path.join(this.inputDir, filePath))\n      const fileKey = `/${filePath}`.replace(/\\/\\//gmi, '/')\n      fileList[fileKey] = fileHash\n    }\n\n    return Promise.resolve({ files: fileList })\n  }\n\n  readDirRecursiveSync(dir: string, fileList?: any) {\n    const files = fs.readdirSync(dir)\n    fileList = fileList || []\n\n    files.forEach((file) => {\n      if (this.fileIsDirectory(dir, file)) {\n        fileList = this.readDirRecursiveSync(path.join(dir, file), fileList)\n        return\n      }\n\n      if (this.fileIsNotExcluded(file)) {\n        fileList.push(this.getFilePath(dir, file))\n      }\n    })\n\n    return fileList\n  }\n\n  fileIsDirectory(dir: string, file: string) {\n    return fs.statSync(path.join(dir, file)).isDirectory()\n  }\n\n  fileIsNotExcluded(file: string) {\n    return file.indexOf('.') !== 0 || file === '.htaccess' || file === '_redirects'\n  }\n\n  getFilePath(dir: string, file: string, includeInputDir = false) {\n    if (!includeInputDir) {\n      dir = dir.replace(this.inputDir, '')\n    }\n\n    return normalizePath(path.join(dir, file))\n  }\n\n  getFileHash(fileName: string) {\n    return new Promise((resolve, reject) => {\n      const shaSumCalculator = crypto.createHash('sha1')\n\n      try {\n        const fileStream = fs.createReadStream(fileName)\n        fileStream.on('data', fileContentChunk => shaSumCalculator.update(fileContentChunk))\n        fileStream.on('end', () => resolve(shaSumCalculator.digest('hex')))\n      } catch (e) {\n        return reject(e)\n      }\n    })\n  }\n\n  getFilesToUpload(filesList: any, hashesToUpload: any) {\n    const filePaths = Object.keys(filesList.files)\n    const filesToUpload = []\n    const foundedHashes = []\n\n    for (let i = 0; i < filePaths.length; i++) {\n      const filePath = filePaths[i]\n\n      if (hashesToUpload.indexOf(filesList.files[filePath]) > -1) {\n        filesToUpload.push(filePath.replace(/\\/\\//gmi, '/'))\n        foundedHashes.push(filesList.files[filePath])\n      }\n    }\n\n    return filesToUpload\n  }\n\n  async uploadFile(filePath: any, deployID: any) {\n    const endpointUrl = `${this.apiUrl}deploys/${deployID}/files${filePath}`\n    const fullFilePath = this.getFilePath(this.inputDir, filePath, true)\n    const fileContent = await asyncReadFile(fullFilePath)\n\n    return axios(endpointUrl, {\n      method: 'PUT',\n      headers: {\n        'User-Agent': 'Gridea',\n        'Content-Type': 'application/octet-stream',\n        'Authorization': `Bearer ${this.accessToken}`,\n      },\n      data: fileContent,\n    })\n  }\n}\n"
  },
  {
    "path": "src/server/plugins/deploys/sftp.ts",
    "content": "import * as fse from 'fs-extra'\n// import * as fs from 'fs'\nimport path from 'path'\nimport SftpClient from 'ssh2-sftp-client'\nimport NodeSsh from 'node-ssh'\nimport normalizePath from 'normalize-path'\nimport Model from '../../model'\n\ntype sftpConnectConfig = {\n  host: string;\n  port: number;\n  type?: string;\n  username: string;\n  password?: string;\n  privateKey?: string | Buffer;\n}\n\nexport default class SftpDeploy extends Model {\n  // connect: SftpClient\n  constructor(appInstance: any) {\n    super(appInstance)\n    // this.connect = new SftpClient()\n    console.log('instance sftp deploy')\n  }\n\n  async remoteDetect() {\n    const result = {\n      success: true,\n      message: '',\n    }\n\n    const client = new SftpClient()\n\n    const { setting } = this.db\n\n    const connectConfig: sftpConnectConfig = {\n      host: setting.server,\n      port: Number(setting.port),\n      username: setting.username,\n    }\n\n    if (setting.privateKey) {\n      try {\n        connectConfig.privateKey = fse.readFileSync(setting.privateKey)\n      } catch (e) {\n        console.error('SFTP Test Remote Error: ', e.message)\n        result.success = false\n        result.message = e.message\n        return result\n      }\n    } else {\n      connectConfig.password = setting.password\n    }\n\n    const testFilename = 'gridea.txt'\n    const localTestFilePath = normalizePath(path.join(this.appDir, testFilename))\n    const remoteTestFilePath = normalizePath(path.join(setting.remotePath, testFilename))\n\n    try {\n      await client.connect(connectConfig)\n      await client.list('/')\n\n      try {\n        fse.writeFileSync(localTestFilePath, 'This is gridea test file. you can delete it.')\n\n        await client.put(localTestFilePath, remoteTestFilePath)\n        await client.delete(remoteTestFilePath)\n      } catch (e) {\n        console.error('SFTP Test Remote Error: ', e.message)\n        result.success = false\n        result.message = e.message\n      } finally {\n        if (fse.existsSync(localTestFilePath)) {\n          fse.unlinkSync(localTestFilePath)\n        }\n      }\n    } catch (e) {\n      console.error('SFTP Test Remote Error: ', e.message)\n      result.success = false\n      result.message = e.message\n    } finally {\n      await client.end()\n    }\n\n    return result\n  }\n\n  async publish() {\n    const result = {\n      success: true,\n      message: '',\n    }\n\n    const client = new NodeSsh()\n\n    const { setting } = this.db\n\n    const connectConfig: sftpConnectConfig = {\n      host: setting.server,\n      port: Number(setting.port),\n      type: 'sftp',\n      username: setting.username,\n    }\n\n    // node-ssh: privateKey is path string.\n    if (setting.privateKey) {\n      connectConfig.privateKey = setting.privateKey\n    } else {\n      connectConfig.password = setting.password\n    }\n\n    const localPath = normalizePath(path.join(this.buildDir))\n    const remotePath = normalizePath(path.join(setting.remotePath))\n\n    try {\n      await client.connect(connectConfig)\n      try {\n        await client.exec(`rm -rf ${remotePath}`)\n        await client.mkdir(remotePath)\n        const res = await client.putDirectory(localPath, remotePath, {\n          recursive: true,\n          concurrency: 1, // 解决同步丢失js、css、图片文件问题\n          validate: function (itemPath: string) {\n            const baseName = path.basename(itemPath)\n            return baseName.substr(0, 1) !== '.' // do not allow dot files\n                   && baseName !== 'node_modules' // do not allow node_modules\n          },\n        })\n      } catch (e) {\n        console.error('SFTP Publish Error: ', e.message)\n        result.success = false\n        result.message = e.message\n      }\n    } catch (e) {\n      console.error('SFTP Publish Error: ', e.message)\n      result.success = false\n      result.message = e.message\n    } finally {\n      await client.dispose()\n    }\n\n    return result\n  }\n}\n"
  },
  {
    "path": "src/server/plugins/markdown.ts",
    "content": "import MarkdownIt from 'markdown-it'\nimport MarkdownItKatex from '@iktakahiro/markdown-it-katex'\nimport markdownItTocAndAnchor from 'markdown-it-toc-and-anchor'\nimport MarkdownItTaskLists from 'markdown-it-task-lists'\nimport MarkdownItMark from 'markdown-it-mark'\nimport MarkdownItSup from 'markdown-it-sup'\nimport MarkdownItSub from 'markdown-it-sub'\nimport MarkdownItAbbr from 'markdown-it-abbr'\nimport MarkdownItFootnote from 'markdown-it-footnote'\nimport MarkdownItImsize from 'markdown-it-imsize'\nimport MarkdownItEmoji from 'markdown-it-emoji'\nimport MarkdownItImplicitFigures from 'markdown-it-implicit-figures'\nimport MarkdownItImageLazyLoading from 'markdown-it-image-lazy-loading'\n\nconst markdownIt = new MarkdownIt({\n  html: true,\n  breaks: true,\n})\n\nconst BAD_PROTO_RE = /^(vbscript|javascript|data):/\nconst GOOD_DATA_RE = /^data:image\\/(gif|png|jpeg|webp);/\n\nmarkdownIt.validateLink = function (url) {\n  url = url.trim().toLowerCase()\n\n  return BAD_PROTO_RE.test(url) ? (!!GOOD_DATA_RE.test(url)) : true\n}\n\nmarkdownIt.use(MarkdownItKatex)\nmarkdownIt.use(markdownItTocAndAnchor, {\n  anchorLink: false,\n})\nmarkdownIt.use(MarkdownItTaskLists, {\n  label: true,\n  labelAfter: true,\n})\nmarkdownIt.use(MarkdownItMark)\nmarkdownIt.use(MarkdownItSup)\nmarkdownIt.use(MarkdownItSub)\nmarkdownIt.use(MarkdownItAbbr)\nmarkdownIt.use(MarkdownItFootnote)\nmarkdownIt.use(MarkdownItImsize)\nmarkdownIt.use(MarkdownItEmoji)\nmarkdownIt.use(MarkdownItImplicitFigures, {\n  dataType: true, // <figure data-type=\"image\">, default: false\n  figcaption: false, // <figcaption>alternative text</figcaption>, default: false\n  tabindex: true, // <figure tabindex=\"1+n\">..., default: false\n  link: false, // <a href=\"img.png\"><img src=\"img.png\"></a>, default: false\n})\nmarkdownIt.use(MarkdownItImageLazyLoading)\n\nexport default markdownIt\n"
  },
  {
    "path": "src/server/posts.ts",
    "content": "import * as fs from 'fs'\nimport * as fse from 'fs-extra'\nimport * as path from 'path'\nimport matter from 'gray-matter'\nimport moment from 'moment'\nimport Bluebird from 'bluebird'\nimport junk from 'junk'\nimport Model from './model'\nimport { IPost, IPostDb } from './interfaces/post'\nimport ContentHelper from '../helpers/content-helper'\nimport { formatYamlString } from '../helpers/utils'\n\nBluebird.promisifyAll(fs)\n\nexport default class Posts extends Model {\n  postDir: string\n\n  postImageDir: string\n\n  constructor(appInstance: any) {\n    super(appInstance)\n    this.postDir = path.join(this.appDir, 'posts')\n    this.postImageDir = `${this.appDir}/post-images`\n  }\n\n  public async savePosts() {\n    const resultList: any = []\n    const requestList: any = []\n    let files = await fse.readdir(this.postDir)\n\n    files = files.filter(junk.not)\n    files.forEach((item) => {\n      requestList.push(fs.readFileSync(path.join(this.postDir, item), 'utf8'))\n    })\n    const results = await Bluebird.all(requestList)\n    const fixedResults = JSON.parse(JSON.stringify(results))\n    /**\n     * The format of the correction `tag` is changed from a string to an array, and the article source file is updated. from v0.7.6\n     */\n    await Promise.all(results.map(async (result: any, index: any) => {\n      const postMatter = matter(result)\n      const data = (postMatter.data as any)\n\n      data.title = formatYamlString(data.title)\n\n      if (data && data.date) {\n        if (typeof data.date === 'string') {\n          data.date = moment(data.date).format('YYYY-MM-DD HH:mm:ss')\n        } else {\n          data.date = moment(data.date).subtract(8, 'hours').format('YYYY-MM-DD HH:mm:ss')\n        }\n      }\n\n      // If there is a `tag` and it is of string type, it is corrected to array type.\n      if (data && typeof data.tags === 'string') {\n        const tagReg = /tags: [^\\s[]/i\n        const newTagString = data.tags.split(' ').toString()\n\n        if (tagReg.test(result)) {\n          const mdStr = `---\ntitle: '${data.title}'\ndate: ${data.date}\ntags: [${newTagString}]\npublished: ${data.published || false}\nhideInList: ${data.hideInList || false}\nfeature: ${data.feature || ''}\nisTop: ${data.isTop || false}\n---\n${postMatter.content}`\n\n          fixedResults[index] = mdStr\n          fse.writeFileSync(`${this.postDir}/${files[index]}`, mdStr)\n        }\n      }\n    }))\n\n    fixedResults.forEach((result: any, index: any) => {\n      const postMatter = matter(result)\n\n      const data = (postMatter.data as any)\n\n      // Remove useless `'` in formatYamlString generate\n      if (data && data.title) {\n        data.title = String(data.title).replace(/''/g, '\\'')\n      }\n      \n      // Fix matter's formatted `date` problem\n      if (data && data.date) {\n        if (typeof data.date === 'string') {\n          data.date = moment(data.date).format('YYYY-MM-DD HH:mm:ss')\n        } else {\n          data.date = moment(data.date).subtract(8, 'hours').format('YYYY-MM-DD HH:mm:ss')\n        }\n      }\n\n      delete postMatter.orig // Remove orig <Buffer>\n      const post = {\n        ...postMatter,\n        abstract: '',\n        fileName: '',\n      }\n\n      const moreReg = /\\n\\s*<!--\\s*more\\s*-->\\s*\\n/i\n      const matchMore = moreReg.exec(post.content)\n      if (matchMore) {\n        post.abstract = (post.content).substring(0, matchMore.index) // Abstract\n      }\n\n      post.fileName = files[index].substring(0, files[index].length - 3) // To be optimized!\n      resultList.push(post)\n    })\n\n    const list: any = []\n    resultList.forEach((item: any) => {\n      // Articles migrated from hexo or other platforms do not have a `published` field\n      if (item.data.published === undefined) {\n        item.data.published = false\n      }\n\n      // Articles migrated from other platforms or old articles do not have `hideInList` fields\n      if (item.data.hideInList === undefined) {\n        item.data.hideInList = false\n      }\n\n      // Articles migrated from other platforms or old articles do not have `isTop` fields\n      if (item.data.isTop === undefined) {\n        item.data.isTop = false\n      }\n\n      list.push(item)\n    })\n\n    list.sort((a: any, b: any) => moment(b.data.date).unix() - moment(a.data.date).unix())\n\n    this.$posts.set('posts', list).write()\n    return true\n  }\n\n  async list() {\n    await this.savePosts()\n    const posts = await this.$posts.get('posts').value()\n    const helper = new ContentHelper()\n\n    const list = posts.map((post: IPostDb) => {\n      const item = JSON.parse(JSON.stringify(post))\n      item.content = helper.changeImageUrlDomainToLocal(item.content, this.appDir)\n      item.data.feature = item.data.feature && !item.data.feature.includes('http')\n        ? helper.changeFeatureImageUrlDomainToLocal(item.data.feature, this.appDir)\n        : item.data.feature\n      return item\n    })\n\n    return list\n  }\n\n  /**\n   * Save Post to file\n   * @param post\n   */\n  async savePostToFile(post: IPost): Promise<IPost | null> {\n    const helper = new ContentHelper()\n    const content = helper.changeImageUrlLocalToDomain(post.content, this.db.setting.domain)\n    const extendName = (post.featureImage.name || 'jpg').split('.').pop()\n\n    post.title = formatYamlString(post.title)\n\n    const mdStr = `---\ntitle: '${post.title}'\ndate: ${post.date}\ntags: [${post.tags.join(',')}]\npublished: ${post.published}\nhideInList: ${post.hideInList}\nfeature: ${post.featureImage.name ? `/post-images/${post.fileName}.${extendName}` : post.featureImagePath}\nisTop: ${post.isTop}\n---\n${content}`\n\n    try {\n      // If exist feature image\n      if (post.featureImage.path) {\n        const filePath = `${this.postImageDir}/${post.fileName}.${extendName}`\n\n        if (post.featureImage.path !== filePath) {\n          fse.copySync(post.featureImage.path, filePath)\n\n          // Clean the old file\n          if (post.featureImage.path.includes(this.postImageDir)) {\n            fse.removeSync(post.featureImage.path)\n          }\n        }\n      }\n\n      // Write file must use fse, beause fs.writeFile need callback\n      await fse.writeFile(`${this.postDir}/${post.fileName}.md`, mdStr)\n\n      // Clean the old file\n      if (post.deleteFileName) {\n        fse.removeSync(`${this.postDir}/${post.deleteFileName}.md`)\n      }\n    } catch (e) {\n      console.error('ERROR: ', e)\n    }\n    return post\n  }\n\n  async deletePost(post: IPostDb) {\n    try {\n      const postUrl = `${this.postDir}/${post.fileName}.md`\n      fse.removeSync(postUrl)\n\n      // Clean feature image\n      if (post.data.feature) {\n        fse.removeSync(post.data.feature.replace('file://', ''))\n      }\n\n      // Clean post content image\n      const imageReg = /(!\\[.*?\\]\\()(.+?)(\\))/g\n      const imageList = post.content.match(imageReg)\n      if (imageList) {\n        const postImagePaths = imageList.map((item: string) => {\n          const index = item.indexOf('(')\n          return item.substring(index + 1, item.length - 1)\n        })\n        postImagePaths.forEach(async (filePath: string) => {\n          fse.removeSync(filePath.replace('file://', ''))\n        })\n      }\n      return true\n    } catch (e) {\n      console.error('Delete Error', e)\n      return false\n    }\n  }\n\n  async uploadImages(files: any[]) {\n    await fse.ensureDir(this.postImageDir)\n    const results = []\n    for (const file of files) {\n      const extendName = file.name.split('.').pop()\n      const newFileName = new Date().getTime()\n      const filePath = `${this.postImageDir}/${newFileName}.${extendName}`\n      fse.copySync(file.path, filePath)\n      results.push(filePath)\n    }\n    return results\n  }\n}\n"
  },
  {
    "path": "src/server/renderer.ts",
    "content": "import * as fs from 'fs'\nimport urlJoin from 'url-join'\nimport Bluebird from 'bluebird'\nimport * as fse from 'fs-extra'\nimport ejs from 'ejs'\nimport moment from 'moment'\nimport less from 'less'\nimport { Feed } from 'feed'\nimport junk from 'junk'\nimport { wordCount, timeCalc } from '../helpers/words-count'\nimport Model from './model'\nimport ContentHelper from '../helpers/content-helper'\nimport { formatThemeCustomConfigToRender } from '../helpers/utils'\nimport {\n  IPostDb, IPostRenderData, ITagRenderData, ISiteTagsData,\n} from './interfaces/post'\nimport { ITag } from './interfaces/tag'\nimport { DEFAULT_POST_PAGE_SIZE, DEFAULT_ARCHIVES_PAGE_SIZE } from '../helpers/constants'\nimport markdown from './plugins/markdown'\nimport { IMenu } from './interfaces/menu'\n\nBluebird.promisifyAll(fs)\nconst helper = new ContentHelper()\n\nexport default class Renderer extends Model {\n  outputDir: string = this.buildDir\n\n  themePath: string = ''\n\n  postsData: IPostRenderData[] = []\n\n  tagsData: ISiteTagsData[] = []\n\n  menuData: IMenu[] = []\n\n  siteData: any = {}\n\n  previewPort: number\n\n  utils: any = {}\n\n  constructor(appInstance: any) {\n    super(appInstance)\n    this.previewPort = appInstance.previewServer.get('port')\n    this.loadConfig()\n\n    this.utils.now = Date.now()\n    this.utils.moment = moment\n  }\n\n  async preview() {\n    this.db.themeConfig.domain = `http://localhost:${this.previewPort}`\n    await this.renderAll()\n  }\n\n  async renderAll() {\n    await this.clearOutputFolder()\n    await this.formatDataForRender()\n    await this.buildCss()\n\n    // Render post list page\n    await this.renderPostList('')\n\n    // Render archives page\n    await this.renderPostList(urlJoin('/', this.db.themeConfig.archivesPath))\n    // Render tag list page\n    await this.renderTags()\n    await this.renderPostDetail()\n    await this.renderTagDetail()\n\n    // Need before `renderCustomPage`, because maybe theme custom page include a `404 page`\n    await this.copyFiles()\n\n    // Render custom page\n    await this.renderCustomPage()\n\n    await this.buildCname()\n\n    await this.buildFeed()\n  }\n\n  /**\n   * Load Config\n   */\n  async loadConfig() {\n    this.themePath = urlJoin(this.appDir, 'themes', this.db.themeConfig.themeName)\n\n    fse.ensureDirSync(urlJoin(this.outputDir))\n  }\n\n  /**\n   * Format data for rendering pages\n   */\n  public formatDataForRender(): any {\n    const { themeConfig } = this.db\n\n    this.postsData = this.db.posts.filter((item: IPostDb) => item.data.published)\n      .map((item: IPostDb) => {\n        const currentTags = item.data.tags || []\n        let toc = ''\n        const content = markdown.render(helper.changeImageUrlLocalToDomain(item.content, themeConfig.domain), {\n          tocCallback(tocMarkdown: any, tocArray: any, tocHtml: any) {\n            toc = tocHtml\n          },\n        })\n\n        let words = 0\n        wordCount(content, (count: number) => {\n          words = count\n        })\n\n        const reading = timeCalc(content)\n\n        const stats = {\n          text: `${reading.minius} min read`,\n          time: reading.second * 1000, // ms\n          words,\n          minutes: reading.minius,\n        }\n\n        const result: IPostRenderData = {\n          content,\n          fileName: item.fileName,\n          abstract: markdown.render(helper.changeImageUrlLocalToDomain(item.abstract, themeConfig.domain)),\n          title: item.data.title,\n          tags: this.db.tags\n            .filter((tag: ITag) => currentTags.find(i => i === tag.name))\n            .map((tag: ITag) => ({ ...tag, link: urlJoin(themeConfig.domain, themeConfig.tagPath, `${tag.slug}`, '/') })),\n          date: item.data.date,\n          dateFormat: (themeConfig.dateFormat && moment(item.data.date).format(themeConfig.dateFormat)) || item.data.date,\n          feature: item.data.feature && !item.data.feature.includes('http')\n            ? `${helper.changeFeatureImageUrlLocalToDomain(item.data.feature, themeConfig.domain)}`\n            : item.data.feature || '',\n          link: urlJoin(themeConfig.domain, themeConfig.postPath, item.fileName, '/'),\n          hideInList: !!item.data.hideInList,\n          isTop: !!item.data.isTop,\n          stats,\n          description: `${content.replace(/<[^>]*>/g, '').substring(0, 120)}${content[121] ? '...' : ''}`,\n        }\n\n        result.toc = toc\n        return result\n      })\n      .sort((a: IPostRenderData, b: IPostRenderData) => moment(b.date).unix() - moment(a.date).unix())\n\n\n    this.tagsData = []\n    this.postsData.forEach((item: IPostRenderData) => {\n      if (!item.hideInList) {\n        item.tags.forEach((tag: ITagRenderData) => {\n          const foundTag = this.tagsData.find((t: ITagRenderData) => t.link === tag.link)\n          if (!foundTag) {\n            this.tagsData.push({\n              ...tag,\n              count: 1,\n            })\n          } else {\n            foundTag.count += 1\n          }\n        })\n      }\n    })\n\n    this.menuData = this.db.menus.map((menu: IMenu) => {\n      let link = menu.link.replace(this.db.setting.domain, this.db.themeConfig.domain)\n\n      const isSiteLink = menu.link.includes(this.db.setting.domain)\n      if (isSiteLink) {\n        link = `${link}`\n      }\n\n      return {\n        ...menu,\n        link,\n      }\n    })\n\n    this.siteData = {\n      posts: this.postsData,\n      tags: this.tagsData,\n      menus: this.menuData,\n      themeConfig: this.db.themeConfig,\n      customConfig: formatThemeCustomConfigToRender(this.db.themeCustomConfig, this.db.currentThemeConfig),\n      utils: this.utils,\n      isHomepage: false,\n    }\n  }\n\n  /**\n   * Render the article list, excluding hidden articles.\n   * if extraPath exist, render archive template, if not render index template\n   */\n  public async renderPostList(archivePath: string) {\n    const { postPageSize, archivesPageSize, domain } = this.db.themeConfig\n\n    // Compatible: < v0.7.0\n    const pageSize = archivePath\n      ? archivesPageSize || DEFAULT_ARCHIVES_PAGE_SIZE\n      : postPageSize || DEFAULT_POST_PAGE_SIZE\n\n    let excludeHidePostsData = this.postsData.filter((item: IPostRenderData) => !item.hideInList)\n\n    const renderTemplatePath = urlJoin(this.themePath, 'templates', `${archivePath ? 'archives.ejs' : 'index.ejs'}`)\n\n    // If it is not archives, sort by `isTop` then to render\n    if (!archivePath) {\n      const isTopPosts = excludeHidePostsData.filter((item: IPostRenderData) => item.isTop)\n      const notTopPosts = excludeHidePostsData.filter((item: IPostRenderData) => !item.isTop)\n      excludeHidePostsData = isTopPosts.concat(notTopPosts)\n    }\n\n    const renderData: any = {\n      menus: this.menuData,\n      posts: [],\n      pagination: {\n        prev: '',\n        next: '',\n      },\n      themeConfig: this.db.themeConfig,\n      site: this.siteData,\n    }\n\n    let html = ''\n    const outputFolder = urlJoin(this.outputDir, archivePath)\n    let renderPath = urlJoin(outputFolder, 'index.html')\n\n    const renderFile = async (path: string, data: any) => {\n      await ejs.renderFile(path, data, {}, async (err: any, str) => {\n        if (err) {\n          console.error('❌ Render post list error')\n          this.mainWindow.webContents.send('log-error', {\n            type: 'Render post list error',\n            message: err.message,\n          })\n        }\n        if (str) {\n          html = str\n        }\n      })\n    }\n\n    // If there is no article to render\n    if (!excludeHidePostsData.length) {\n      renderData.site.isHomepage = !archivePath\n\n      fse.ensureDirSync(outputFolder)\n      renderFile(renderTemplatePath, renderData)\n      await fs.writeFileSync(renderPath, html)\n      return\n    }\n\n    for (let i = 0; i * pageSize < excludeHidePostsData.length; i += 1) {\n      renderData.posts = excludeHidePostsData.slice(i * pageSize, (i + 1) * pageSize)\n      renderData.site.isHomepage = !archivePath && !i\n\n      if (i === 0 && excludeHidePostsData.length > pageSize) {\n        fse.ensureDirSync(urlJoin(this.outputDir, archivePath, 'page'))\n\n        renderData.pagination.next = urlJoin(domain, archivePath, 'page', '2')\n      } else if (i > 0 && excludeHidePostsData.length > pageSize) {\n        fse.ensureDirSync(urlJoin(this.outputDir, archivePath, 'page', `${i + 1}`))\n\n        renderPath = urlJoin(this.outputDir, archivePath, 'page', `${i + 1}`, 'index.html')\n\n        renderData.pagination.prev = i === 1\n          ? urlJoin(domain, archivePath, '/')\n          : urlJoin(domain, archivePath, 'page', `${i}/`)\n\n        renderData.pagination.next = (i + 1) * pageSize < excludeHidePostsData.length\n          ? urlJoin(domain, archivePath, 'page', `${i + 2}/`)\n          : ''\n      } else {\n        fse.ensureDirSync(urlJoin(this.outputDir, archivePath))\n      }\n\n      renderFile(renderTemplatePath, renderData)\n\n      console.log('👏  PostList Page:', renderPath)\n      fs.writeFileSync(renderPath, html)\n    }\n  }\n\n  /**\n   * Render the article details page, including hidden articles.\n   */\n  async renderPostDetail() {\n    for (let i = 0; i < this.postsData.length; i += 1) {\n      const post: IPostRenderData = { ...this.postsData[i] }\n\n      if (!post.hideInList) {\n        if (i < this.postsData.length - 1) {\n          const nexPost = this.postsData.slice(i + 1, this.postsData.length).find((item: IPostRenderData) => !item.hideInList)\n          if (nexPost) {\n            post.nextPost = nexPost\n          }\n        }\n        if (i > 0) {\n          const prevPost = this.postsData.slice(0, i).reverse().find((item: IPostRenderData) => !item.hideInList)\n          if (prevPost) {\n            post.prevPost = prevPost\n          }\n        }\n      }\n\n\n      const renderData = {\n        menus: this.menuData,\n        post,\n        themeConfig: this.db.themeConfig,\n        commentSetting: this.db.commentSetting,\n        site: this.siteData,\n      }\n      let html = ''\n      ejs.renderFile(urlJoin(this.themePath, 'templates', 'post.ejs'), renderData, {}, async (err: any, str) => {\n        if (err) {\n          console.error('❌ Render post detail error')\n          this.mainWindow.webContents.send('log-error', {\n            type: 'Render post detail error',\n            message: err.message,\n          })\n        }\n        if (str) {\n          html = str\n        }\n      })\n\n      const renderFolerPath = urlJoin(this.outputDir, `${this.db.themeConfig.postPath}`, post.fileName)\n      fse.ensureDirSync(renderFolerPath)\n      fs.writeFileSync(urlJoin(renderFolerPath, 'index.html'), html)\n    }\n  }\n\n  /**\n   * Render tags page\n   */\n  async renderTags() {\n    const tagsFolder = urlJoin(this.outputDir, 'tags')\n    const renderPath = urlJoin(tagsFolder, 'index.html')\n    const renderData = {\n      tags: this.tagsData,\n      menus: this.menuData,\n      themeConfig: this.db.themeConfig,\n      site: this.siteData,\n    }\n    let html = ''\n\n    fse.ensureDirSync(tagsFolder)\n    await ejs.renderFile(urlJoin(this.themePath, 'templates', 'tags.ejs'), renderData, {}, async (err: any, str) => {\n      if (err) {\n        console.log('❌ Render tags page error', err)\n        this.mainWindow.webContents.send('log-error', {\n          type: 'Render tags page error',\n          message: err.message,\n        })\n      }\n      if (str) {\n        html = str\n      }\n    })\n    console.log('👏  Tags Page:', renderPath)\n    fs.writeFileSync(renderPath, html)\n  }\n\n  /**\n   * Render tag detail page\n   */\n  async renderTagDetail() {\n    const usedTags = this.db.tags.filter((tag: ITag) => tag.used)\n    const { postPageSize, domain, tagPath } = this.db.themeConfig\n\n    // Compatible: < v0.7.0\n    const pageSize = postPageSize || DEFAULT_POST_PAGE_SIZE\n\n    for (const usedTag of usedTags) {\n      const posts = this.postsData.filter((post: IPostRenderData) => {\n        return post.tags.find((tag: ITagRenderData) => tag.slug === usedTag.slug)\n      })\n\n      const currentTag = usedTag\n\n      const tagFolderPath = urlJoin(this.outputDir, tagPath, `${currentTag.slug}`)\n      const tagDomainPath = urlJoin(domain, tagPath, `${currentTag.slug}`)\n      fse.ensureDirSync(tagFolderPath)\n\n      for (let i = 0; i * pageSize < posts.length; i += 1) {\n        const renderData = {\n          tag: currentTag,\n          menus: this.menuData,\n          posts: posts.slice(i * pageSize, (i + 1) * pageSize),\n          pagination: {\n            prev: '',\n            next: '',\n          },\n          themeConfig: this.db.themeConfig,\n          site: this.siteData,\n        }\n\n        // Paging\n        let renderPath = urlJoin(tagFolderPath, 'index.html')\n\n        if (i === 0 && posts.length > pageSize) {\n          fse.ensureDirSync(urlJoin(tagFolderPath, 'page'))\n\n          renderData.pagination.next = urlJoin(tagDomainPath, 'page', '2')\n        } else if (i > 0 && posts.length > pageSize) {\n          fse.ensureDirSync(urlJoin(tagFolderPath, 'page', `${i + 1}`))\n\n          renderPath = urlJoin(tagFolderPath, 'page', `${i + 1}`, 'index.html')\n\n          renderData.pagination.prev = i === 1\n            ? tagDomainPath\n            : urlJoin(tagDomainPath, 'page', `${i}/`)\n\n          renderData.pagination.next = (i + 1) * pageSize < posts.length\n            ? urlJoin(tagDomainPath, 'page', `${i + 2}/`)\n            : ''\n        }\n\n        let html = ''\n        ejs.renderFile(urlJoin(this.themePath, 'templates', 'tag.ejs'), renderData, {}, async (err: any, str) => {\n          if (err) {\n            console.log('❌ Render tag detail error', err)\n            this.mainWindow.webContents.send('log-error', {\n              type: 'Render tag detail error',\n              message: err.message,\n            })\n          }\n          if (str) {\n            html = str\n          }\n        })\n        console.log('👏  Tag Page:', renderPath)\n        fs.writeFileSync(renderPath, html)\n      }\n    }\n  }\n\n  /**\n   * Render custom page, eg. friends.ejs, about.ejs, home.ejs, projects.ejs...\n   */\n  async renderCustomPage() {\n    const files = fse.readdirSync(urlJoin(this.themePath, 'templates'), { withFileTypes: true })\n    const customTemplates = files\n      .filter(item => !item.isDirectory())\n      .map(item => item.name)\n      .filter(junk.not)\n      .filter((name: string) => {\n        return ![\n          'index.ejs',\n          'post.ejs',\n          'tag.ejs',\n          'tags.ejs',\n          'archives.ejs',\n          // 👇 Gridea protected word, because these filename is gridea folder's name\n          'images.ejs',\n          'media.ejs',\n          'post-images.ejs',\n          'styles.ejs',\n          'tag.ejs',\n          'tags.ejs',\n        ].includes(name)\n      })\n    \n    const renderData = {\n      menus: this.menuData,\n      themeConfig: this.db.themeConfig,\n      commentSetting: this.db.commentSetting,\n      site: this.siteData,\n    }\n\n    customTemplates.forEach(async (name: string) => {\n      let renderFolder = urlJoin(this.outputDir, name.substring(0, name.length - 4))\n      let renderPath = urlJoin(renderFolder, 'index.html')\n      let html = ''\n\n      if (name === '404.ejs') {\n        renderFolder = this.outputDir\n        renderPath = urlJoin(renderFolder, '404.html')\n      }\n\n      fse.ensureDirSync(renderFolder)\n      await ejs.renderFile(urlJoin(this.themePath, 'templates', name), renderData, async (err: any, str) => {\n        if (err) {\n          console.error('❌ Render custom page error', err)\n          this.mainWindow.webContents.send('log-error', {\n            type: 'Render custom page error',\n            message: err.message,\n          })\n        }\n        if (str) {\n          html = str\n        }\n      })\n      fse.writeFileSync(renderPath, html)\n      console.log('✅ Render custom page success', renderPath)\n    })\n  }\n\n  /**\n   * Build CSS and write file\n   */\n  async buildCss() {\n    const lessFilePath = urlJoin(this.themePath, 'assets', 'styles', 'main.less')\n    const cssFolderPath = urlJoin(this.outputDir, 'styles')\n\n    fse.ensureDirSync(cssFolderPath)\n\n    const lessString = fs.readFileSync(lessFilePath, 'utf8')\n    return new Promise((resolve, reject) => {\n      less.render(lessString, { filename: lessFilePath }, async (err: any, cssString: Less.RenderOutput) => {\n        if (err) {\n          console.log(err)\n          reject(err)\n        }\n        let { css } = cssString\n  \n        // if have override\n        const customConfig = this.db.themeCustomConfig\n        const currentThemePath = urlJoin(this.appDir, 'themes', this.db.themeConfig.themeName)\n  \n        const styleOverridePath = urlJoin(currentThemePath, 'style-override.js')\n        const existOverrideFile = await fse.pathExists(styleOverridePath)\n        if (existOverrideFile) {\n          // clean cache\n          delete __non_webpack_require__.cache[__non_webpack_require__.resolve(styleOverridePath)]\n  \n          const generateOverride = __non_webpack_require__(styleOverridePath)\n          const customCss = generateOverride(customConfig)\n          css += customCss\n        }\n  \n        fs.writeFileSync(urlJoin(cssFolderPath, 'main.css'), css)\n        resolve(true)\n      })\n    })\n  }\n\n  /**\n   * Create CNAME file\n   */\n  async buildCname() {\n    const cnamePath = urlJoin(this.outputDir, 'CNAME')\n\n    if (this.db.setting.cname) {\n      fs.writeFileSync(cnamePath, this.db.setting.cname)\n    } else {\n      fse.removeSync(cnamePath)\n    }\n  }\n\n  /**\n   * Build Feed\n   */\n  async buildFeed() {\n    const DEFAULT_FEED_COUNT = 10\n    const feedFilename = 'atom.xml'\n    const { themeConfig } = this.db\n\n    const feed = new Feed({\n      title: themeConfig.siteName,\n      description: themeConfig.siteDescription,\n      id: themeConfig.domain,\n      link: themeConfig.domain,\n      image: urlJoin(themeConfig.domain, 'images', 'avatar.png'),\n      favicon: urlJoin(themeConfig.domain, 'favicon.ico'),\n      copyright: `All rights reserved ${(new Date()).getFullYear()}, ${themeConfig.siteName}`,\n      feedLinks: {\n        atom: urlJoin(themeConfig.domain, feedFilename),\n      },\n    })\n\n    const postsData = this.postsData\n      .filter((item: IPostRenderData) => !item.hideInList)\n      .slice(0, themeConfig.feedCount || DEFAULT_FEED_COUNT)\n\n    const feedFullText = (typeof themeConfig.feedFullText) === 'undefined' ? true : themeConfig.feedFullText\n\n    postsData.forEach((post: IPostRenderData) => {\n      feed.addItem({\n        title: post.title,\n        id: post.link,\n        link: post.link,\n        description: post.abstract,\n        content: feedFullText ? post.content : post.abstract,\n        image: post.feature,\n        date: new Date(post.date),\n      })\n    })\n\n    fs.writeFileSync(urlJoin(this.outputDir, feedFilename), feed.atom1())\n  }\n\n  /**\n   * Copy file to output folder\n   */\n  async copyFiles() {\n    const postImageInputPath = urlJoin(this.appDir, 'post-images')\n    const postImageOutputPath = urlJoin(this.outputDir, 'post-images')\n\n    fse.ensureDirSync(postImageOutputPath)\n    fse.copySync(postImageInputPath, postImageOutputPath)\n\n    const imagesInputPath = urlJoin(this.appDir, 'images')\n    const imagesOutputPath = urlJoin(this.outputDir, 'images')\n\n    fse.ensureDirSync(imagesOutputPath)\n    fse.copySync(imagesInputPath, imagesOutputPath)\n\n    const mediaInputPath = urlJoin(this.themePath, 'assets', 'media')\n    const mediaOutputPath = urlJoin(this.outputDir, 'media')\n\n    fse.ensureDirSync(mediaInputPath)\n    fse.copySync(mediaInputPath, mediaOutputPath)\n\n    // Copy /static\n    const staticFilesPath = urlJoin(this.appDir, 'static')\n    if (fse.existsSync(staticFilesPath)) {\n      fse.copySync(staticFilesPath, this.outputDir)\n    }\n\n    // Copy favicon.ico\n    const faviconInputPath = urlJoin(this.appDir, 'favicon.ico')\n    if (fse.existsSync(faviconInputPath)) {\n      fse.copyFileSync(faviconInputPath, urlJoin(this.outputDir, 'favicon.ico'))\n    }\n  }\n\n  async clearOutputFolder() {\n    try {\n      fse.emptyDirSync(this.outputDir)\n    } catch (e) {\n      console.log('Delete file error', e)\n    }\n  }\n}\n"
  },
  {
    "path": "src/server/setting.ts",
    "content": "import * as fse from 'fs-extra'\nimport * as path from 'path'\nimport Model from './model'\nimport { ICommentSetting } from './interfaces/setting'\nimport { ISetting } from '../interfaces/setting'\n\nexport default class Setting extends Model {\n  getSetting() {\n    const setting = this.$setting.get('config').value()\n    return setting\n  }\n\n  getGitalkSetting() {\n    const setting = this.$setting.get('gitalk').value()\n    return setting\n  }\n\n  getCommentSetting() {\n    const setting = this.$setting.get('comment').value()\n    return setting\n  }\n\n  public async saveSetting(setting: ISetting) {\n    await this.$setting.set('config', setting).write()\n    return true\n  }\n\n  public async saveCommentSetting(setting: ICommentSetting) {\n    await this.$setting.set('comment', setting).write()\n    return true\n  }\n\n  async uploadFavicon(filePath: string) {\n    const faviconPath = path.join(this.appDir, 'favicon.ico')\n    fse.copySync(filePath, faviconPath)\n  }\n\n  async uploadAvatar(filePath: string) {\n    const avatarPath = path.join(this.appDir, 'images/avatar.png')\n    fse.copySync(filePath, avatarPath)\n  }\n}\n"
  },
  {
    "path": "src/server/tags.ts",
    "content": "import shortid from 'shortid'\nimport Model from './model'\nimport { ITag } from './interfaces/tag'\nimport slug from '../helpers/slug'\nimport { UrlFormats } from '../helpers/enums'\n\nexport default class Tags extends Model {\n  public async saveTags() {\n    const posts = this.$posts.get('posts').value()\n    let list: any = []\n    posts.forEach((post: any) => {\n      if (Array.isArray(post.data.tags)) {\n        list = list.concat(post.data.tags)\n      }\n    })\n    list = Array.from(new Set([...list]))\n\n    const themeConfig = await this.$theme.get('config').value()\n    const tagUrlFormat = themeConfig.tagUrlFormat || UrlFormats.Slug\n\n    let existUsedTags = this.$posts.get('tags').filter({ used: true }).value()\n\n    // If you delete an article after using a tag, there may be a tag unused state.\n    existUsedTags = existUsedTags.map((tag: ITag) => {\n      return {\n        ...tag,\n        used: list.includes(tag.name),\n      }\n    })\n\n    const unusedTags = this.$posts.get('tags').filter({ used: false }).value()\n\n    // The tag of the imported article is newly used\n    const newUsedTags = list\n      .filter((item: any) => !existUsedTags.find((tag: ITag) => tag.name === item))\n      .map((item: any) => {\n        const foundItem = unusedTags.find((tag: ITag) => tag.name === item)\n        if (foundItem) {\n          // remove from unusedTags\n          const foundItemIndex = unusedTags.indexOf(foundItem)\n          unusedTags.splice(foundItemIndex, 1)\n\n          return {\n            ...foundItem,\n            used: true,\n          }\n        }\n\n        return {\n          name: item,\n          slug: tagUrlFormat === UrlFormats.Slug ? slug(item) : shortid.generate(),\n          used: true,\n        }\n      })\n\n    const tags = [...newUsedTags, ...existUsedTags, ...unusedTags]\n\n    this.$posts.set('tags', tags).write()\n  }\n\n  async list() {\n    await this.saveTags()\n    const tags = await this.$posts.get('tags').value()\n    return tags\n  }\n\n  public async saveTag(tag: ITag) {\n    const tags = await this.$posts.get('tags').value()\n    if (typeof tag.index === 'number' && tag.index >= 0) {\n      tags[tag.index] = tag\n    } else {\n      tags.push(tag)\n    }\n    await this.$posts.set('tags', tags).write()\n    return tags\n  }\n\n  public async deleteTag(tagValue: string) {\n    const tag = await this.$posts.get('tags').remove({ name: tagValue }).write()\n    return tag\n  }\n}\n"
  },
  {
    "path": "src/server/theme.ts",
    "content": "import * as path from 'path'\nimport * as fse from 'fs-extra'\nimport junk from 'junk'\nimport Model from './model'\nimport { ITheme } from './interfaces/theme'\n\nexport default class Theme extends Model {\n  themeDir: string\n\n  themeList: string[]\n\n  themeConfig: any\n\n  currentThemePath = ''\n\n  constructor(appInstance: any) {\n    super(appInstance)\n    this.themeDir = path.join(this.appDir, 'themes')\n    this.themeConfig = {}\n    this.themeList = []\n  }\n\n  /**\n   * Get the theme list\n   */\n  async getThemeList() {\n    let themes = await fse.readdir(this.themeDir)\n    themes = themes.filter(junk.not)\n    const result = await Promise.all(themes.map(async (item: string) => {\n      const data = {\n        folder: item,\n        name: item,\n        version: '',\n        author: '',\n        repository: '',\n      }\n      const themeConfigPath = path.join(this.themeDir, item, 'config.json')\n      if (fse.existsSync(themeConfigPath)) {\n        const config = fse.readJSONSync(themeConfigPath)\n        data.name = config.name\n        data.version = config.version\n        data.author = config.repository\n        data.repository = config.repository\n      }\n      return data\n    }))\n\n    return result\n  }\n\n  /**\n   * Get the theme configuration\n   */\n  async getThemeConfig() {\n    this.themeConfig = await this.$theme.get('config').value()\n    this.currentThemePath = path.join(this.appDir, 'themes', this.themeConfig.themeName)\n    return this.themeConfig\n  }\n\n  /**\n   * Save the theme configuration\n   */\n  public async saveThemeConfig(themeConfig: ITheme) {\n    await this.$theme.set('config', themeConfig).write()\n\n    // If there is a backup of the custom configuration, copy the backup to the custom configuration\n    const themeConfigBackupPath = path.join(this.appDir, 'config', `theme.${themeConfig.themeName}.config.json`)\n    const existThemeConfigBackupFile = await fse.pathExists(themeConfigBackupPath)\n    if (existThemeConfigBackupFile) {\n      const config = fse.readJSONSync(themeConfigBackupPath)\n      await this.$theme.set('customConfig', config).write()\n    } else {\n      await this.$theme.set('customConfig', {}).write()\n    }\n    return themeConfig\n  }\n\n  /**\n   * Save the theme custom configuration\n   */\n  public async saveThemeCustomConfig(config: any) {\n    if (Object.keys(config).length > 0) {\n      // Save the picture type configuration\n      const toPath = path.join(this.appDir, 'themes', this.db.themeConfig.themeName, 'assets', 'media', 'images')\n      const includedArrayTypeImages: string[] = []\n  \n      for (const configItem of this.db.currentThemeConfig) {\n        const configValue = config[configItem.name]\n  \n        // Picture upload config type data need to upload image to folder\n        if (configItem.type === 'picture-upload') {\n          if (\n            typeof configValue === 'string'\n            && configValue !== configItem.value\n            && !configValue.startsWith('/media/')\n          ) {\n            const extendName = configValue.split('.').pop()\n            const fileName = `custom-${configItem.name}.${extendName}`\n  \n            fse.ensureDirSync(toPath)\n            fse.copySync(configValue, path.join(toPath, fileName))\n  \n            // Change value to finally value\n            config[configItem.name] = path.join('/', 'media', 'images', fileName)\n          } else if (typeof configValue === 'undefined' || configValue === configItem.value) {\n            const currentConfigValue = this.db.themeCustomConfig[configItem.name]\n            if (currentConfigValue && currentConfigValue !== configItem.value) {\n              const extendName = this.db.themeCustomConfig[configItem.name].split('.').pop()\n              const fileName = `custom-${configItem.name}.${extendName}`\n    \n              fse.removeSync(path.join(toPath, fileName))\n            }\n          }\n        }\n  \n        // Array config type data need to find image config to upload folder\n        if (configItem.type === 'array') {\n          for (let arrItemIndex = 0; arrItemIndex < configValue.length; arrItemIndex += 1) {\n            const foundConfigItem = this.db.currentThemeConfig.find((i: any) => i.name === configItem.name)\n            const arrayItemKeys = Object.keys(configValue[arrItemIndex])\n  \n            for (let keyIndex = 0; keyIndex < arrayItemKeys.length; keyIndex += 1) {\n              const key = arrayItemKeys[keyIndex]\n              const foundPictureTypeField = foundConfigItem.arrayItems.find((i: any) => i.name === key && i.type === 'picture-upload')\n  \n              if (foundPictureTypeField) {\n                const fieldValue = configValue[arrItemIndex][key]\n    \n                if (\n                  typeof fieldValue === 'string'\n                  && fieldValue !== foundPictureTypeField.value\n                  && !fieldValue.startsWith('/media/')\n                ) {\n                  const extendName = fieldValue.split('.').pop()\n                  const fileName = `custom-array-${configItem.name}-${new Date().getTime()}-${key}.${extendName}`\n    \n                  fse.ensureDirSync(toPath)\n                  fse.copySync(fieldValue, path.join(toPath, fileName))\n    \n                  // Change value to finally value\n                  configValue[arrItemIndex][key] = path.join('/', 'media', 'images', fileName)\n                  includedArrayTypeImages.push(configValue[arrItemIndex][key])\n                } else if (typeof fieldValue === 'undefined' || fieldValue === foundPictureTypeField.value) {\n                  console.log('run...')\n                } else {\n                  includedArrayTypeImages.push(fieldValue)\n                }\n              }\n            }\n          }\n        }\n      }\n  \n      // Remove unused array type config images\n      const assetsFolderPath = path.join(this.appDir, 'themes', this.db.themeConfig.themeName, 'assets')\n      const imagesFolderPath = path.join(assetsFolderPath, 'media', 'images')\n      if (fse.existsSync(imagesFolderPath)) {\n        const files = await fse.readdirSync(imagesFolderPath, { withFileTypes: true })\n        const arrayTypeImages = files\n          .filter(item => !item.isDirectory())\n          .map(item => path.join('/', 'media', 'images', item.name))\n          .filter(item => item.includes('custom-array'))\n    \n        arrayTypeImages.forEach((name: string) => {\n          if (!includedArrayTypeImages.includes(name)) {\n            fse.removeSync(path.join(assetsFolderPath, name))\n          }\n        })\n      }\n    }\n\n\n    await this.$theme.set('customConfig', config).write()\n\n    // Backup theme custom config\n    const themeConfigBackupPath = path.join(this.appDir, 'config', `theme.${this.db.themeConfig.themeName}.config.json`)\n    fse.writeJSONSync(themeConfigBackupPath, config)\n    return config\n  }\n\n  /**\n   * Get the theme custom configuration\n   */\n\n  public async getThemeCustomConfig() {\n    const config = await this.$theme.get('customConfig').value()\n    return config\n  }\n\n  /**\n   * Get current theme custom configuration\n   */\n  public async getCurrentThemeCustomConfig() {\n    const themeConfigPath = path.join(this.currentThemePath, 'config.json')\n    const existThemeConfigFile = await fse.pathExists(themeConfigPath)\n    if (existThemeConfigFile) {\n      const themeConfig = fse.readJSONSync(themeConfigPath)\n      if (themeConfig && themeConfig.customConfig) {\n        return themeConfig.customConfig\n      }\n    }\n\n    return []\n  }\n}\n"
  },
  {
    "path": "src/server.ts",
    "content": "import express from 'express'\n\nexport default function initServer() {\n  const app = express()\n  let server: any = null\n\n  function listen(port: number) {\n    server = app.listen(port, 'localhost').on('error', (err: NodeJS.ErrnoException) => {\n      if (err) {\n        if (err.message === 'getaddrinfo ENOTFOUND localhost') {\n          // Fixed: 修复渲染服务在找不到 localhost 时崩溃问题\n          console.log('\\x1B[31m%s\\x1B[0m', 'Localhost is not found so that the preview server is not working. Please check your hosts file and then restart the application.')\n        } else {\n          console.log(`Preview server port ${port} is busy, trying with port ${port + 1}`)\n          listen(port + 1)\n        }\n      }\n    }).on('listening', () => {\n      app.set('port', port)\n      console.log(`Preview server is running on port : ${port}`)\n    })\n  }\n\n  listen(4000)\n\n  return {\n    server,\n    app,\n  }\n}\n"
  },
  {
    "path": "src/shims-tsx.d.ts",
    "content": "import Vue, { VNode } from 'vue'\n\ndeclare global {\n  namespace JSX {\n    // tslint:disable no-empty-interface\n    interface Element extends VNode {}\n    // tslint:disable no-empty-interface\n    interface ElementClass extends Vue {}\n    interface IntrinsicElements {\n      [elem: string]: any\n    }\n  }\n}\n"
  },
  {
    "path": "src/shims-vue.d.ts",
    "content": "declare module '*.vue' {\n  import { ComponentOptions } from 'vue'\n\n  export default Vue\n}\ndeclare module '*.json' {\n  const data: any\n  export default data\n}\n\ndeclare module 'vue2-transitions'\ndeclare module '@iktakahiro/markdown-it-katex'\ndeclare module 'markdown-it-toc-and-anchor'\ndeclare module 'markdown-it-task-lists'\ndeclare module 'markdown-it-abbr'\ndeclare module 'markdown-it-footnote'\ndeclare module 'markdown-it-mark'\ndeclare module 'markdown-it-sub'\ndeclare module 'markdown-it-sup'\ndeclare module 'markdown-it-imsize'\ndeclare module 'markdown-it-emoji'\ndeclare module 'markdown-it-implicit-figures'\ndeclare module 'markdown-it-image-lazy-loading'\ndeclare module 'electron-google-analytics'\ndeclare module 'macaddress'\ndeclare module 'v-emoji-picker'\ndeclare module 'vue-shortkey'\n\ndeclare module 'easy-ftp'\ndeclare module 'node-ssh'\n\ndeclare module 'vuedraggable' {\n  \n\n  export interface DraggedContext<T> {\n    index: number;\n    futureIndex: number;\n    element: T;\n  }\n\n  export interface DropContext<T> {\n    index: number;\n    component: Vue;\n    element: T;\n  }\n\n  export interface Rectangle {\n    top: number;\n    right: number;\n    bottom: number;\n    left: number;\n    width: number;\n    height: number;\n  }\n\n  export interface MoveEvent<T> {\n    originalEvent: DragEvent;\n    dragged: Element;\n    draggedContext: DraggedContext<T>;\n    draggedRect: Rectangle;\n    related: Element;\n    relatedContext: DropContext<T>;\n    relatedRect: Rectangle;\n    from: Element;\n    to: Element;\n    willInsertAfter: boolean;\n    isTrusted: boolean;\n  }\n\n  const draggableComponent: ComponentOptions<Vue>\n\n  export default draggableComponent\n}\n"
  },
  {
    "path": "src/shims.d.ts",
    "content": "import { AllElectron } from 'electron'\n\ndeclare module 'vue/types/vue' {\n  interface Vue {\n    readonly $electron: AllElectron,\n    $bus: any,\n  }\n}\n"
  },
  {
    "path": "src/store/index.ts",
    "content": "import Vue from 'vue'\nimport Vuex from 'vuex'\nimport site from './modules/site'\n\nVue.use(Vuex)\n\nexport default new Vuex.Store({\n  modules: {\n    site,\n  },\n  strict: process.env.NODE_ENV !== 'production',\n})\n"
  },
  {
    "path": "src/store/modules/site.ts",
    "content": "import { ActionTree, Module, MutationTree } from 'vuex'\nimport { IPost } from '../../interfaces/post'\nimport { ITag } from '../../interfaces/tag'\nimport { ITheme } from '../../interfaces/theme'\nimport { IMenu } from '../../interfaces/menu'\nimport { ISetting, ICommentSetting } from '../../interfaces/setting'\nimport {\n  DEFAULT_POST_PAGE_SIZE, DEFAULT_ARCHIVES_PAGE_SIZE, DEFAULT_FEED_COUNT, DEFAULT_ARCHIVES_PATH, DEFAULT_POST_PATH, DEFAULT_TAG_PATH,\n} from '../../helpers/constants'\n\nexport interface Site {\n  appDir: string\n  config: any\n  posts: IPost[]\n  tags: ITag[]\n  menus: IMenu[]\n  themeConfig: ITheme\n  themeCustomConfig: any\n  currentThemeConfig: any\n  themes: string[]\n  setting: ISetting\n  commentSetting: ICommentSetting\n}\nconst siteState: Site = {\n  appDir: '',\n  config: {},\n  posts: [],\n  tags: [],\n  menus: [],\n  themeConfig: {\n    themeName: '',\n    postPageSize: DEFAULT_POST_PAGE_SIZE,\n    archivesPageSize: DEFAULT_ARCHIVES_PAGE_SIZE,\n    siteName: '',\n    siteDescription: '',\n    footerInfo: 'Powered by Gridea',\n    showFeatureImage: true,\n    postUrlFormat: 'SLUG',\n    tagUrlFormat: 'SLUG',\n    dateFormat: 'YYYY-MM-DD',\n    feedCount: DEFAULT_FEED_COUNT,\n    feedFullText: true,\n    archivesPath: DEFAULT_ARCHIVES_PATH,\n    postPath: DEFAULT_POST_PATH,\n    tagPath: DEFAULT_TAG_PATH,\n  },\n  themeCustomConfig: {},\n  currentThemeConfig: {},\n  themes: [],\n  setting: {\n    platform: 'github',\n    domain: '',\n    repository: '',\n    branch: '',\n    username: '',\n    email: '',\n    tokenUsername: '',\n    token: '',\n    cname: '',\n    port: '22',\n    server: '',\n    password: '',\n    privateKey: '',\n    remotePath: '',\n    proxyPath: '',\n    proxyPort: '',\n    enabledProxy: 'direct',\n    netlifySiteId: '',\n    netlifyAccessToken: '',\n  },\n  commentSetting: {\n    showComment: false,\n    commentPlatform: 'gitalk',\n    gitalkSetting: {\n      clientId: '',\n      clientSecret: '',\n      repository: '',\n      owner: '',\n    },\n    disqusSetting: {\n      api: '',\n      apikey: '',\n      shortname: '',\n    },\n  },\n}\n\nconst mutations: MutationTree<Site> = {\n  updateSite(state, siteData: Site) {\n    console.log('data', siteData)\n    state.appDir = siteData.appDir\n    state.posts = siteData.posts\n    state.tags = siteData.tags\n    state.menus = siteData.menus\n    state.config = siteData.config\n    state.themeConfig = siteData.themeConfig\n    state.themeConfig.postUrlFormat = siteData.themeConfig.postUrlFormat || 'SLUG'\n    state.themeConfig.tagUrlFormat = siteData.themeConfig.tagUrlFormat || 'SLUG'\n    state.themeConfig.dateFormat = siteData.themeConfig.dateFormat || 'YYYY-MM-DD'\n    state.themeConfig.postPageSize = siteData.themeConfig.postPageSize || DEFAULT_POST_PAGE_SIZE\n    state.themeConfig.archivesPageSize = siteData.themeConfig.archivesPageSize || DEFAULT_ARCHIVES_PAGE_SIZE\n    state.themeConfig.feedCount = siteData.themeConfig.feedCount || DEFAULT_FEED_COUNT\n    state.themeConfig.feedFullText = (typeof siteData.themeConfig.feedFullText) === 'undefined' ? true : siteData.themeConfig.feedFullText // from > 0.8.0\n    state.themes = siteData.themes\n    state.setting = siteData.setting\n    state.commentSetting = siteData.commentSetting\n    state.themeCustomConfig = siteData.themeCustomConfig\n    state.currentThemeConfig = siteData.currentThemeConfig\n  },\n  updatePosts(state, posts: IPost[]) {\n    state.posts = posts\n  },\n}\n\nconst actions: ActionTree<Site, any> = {\n  updatePosts({ commit }, posts: IPost[]) {\n    commit('updatePosts', posts)\n  },\n  updateSite({ commit }, siteData: Site) {\n    console.log('siteData:', siteData)\n    commit('updateSite', siteData)\n  },\n}\n\nconst module: Module<Site, any> = {\n  namespaced: true,\n  state: siteState,\n  mutations,\n  actions,\n}\n\nexport default module\n"
  },
  {
    "path": "src/views/article/ArticleUpdate.vue",
    "content": "<template>\n  <div class=\"article-update-page\" :class=\"{ 'is-entering': entering }\" v-if=\"visible\" @mousemove=\"handlePageMousemove\">\n    <div class=\"page-title\" ref=\"pageTitle\">\n      <a-row type=\"flex\" justify=\"end\">\n        <a-tooltip placement=\"bottom\" :title=\"$t('back')\">\n          <div class=\"op-btn\" tabindex=\"0\" @click=\"close\">\n            <i class=\"zwicon-arrow-left\"></i>\n          </div>\n        </a-tooltip>\n        <a-tooltip placement=\"bottom\" :title=\"$t('saveDraft')\">\n          <div class=\"op-btn\" tabindex=\"0\" :class=\"{ 'disabled': !canSubmit }\" @click=\"saveDraft\">\n            <i class=\"zwicon-checkmark\"></i>\n          </div>\n        </a-tooltip>\n        <a-tooltip placement=\"bottom\" :title=\"$t('save')\">\n          <div class=\"op-btn save-btn\" tabindex=\"0\" :class=\"{ 'disabled': !canSubmit }\" @click=\"savePost\">\n            <i class=\"zwicon-checkmark\"></i>\n          </div>\n        </a-tooltip>\n      </a-row>\n    </div>\n    <div class=\"page-content\">\n      <div class=\"editor-wrapper\">\n        <a-input class=\"post-title\" size=\"large\" :placeholder=\"$t('title')\" v-model=\"form.title\" @change=\"handleTitleChange\" @keydown=\"handleInputKeydown\"></a-input>\n        <monaco-markdown-editor\n          ref=\"monacoMarkdownEditor\"\n          v-model=\"form.content\"\n          @keydown=\"handleInputKeydown\"\n          :isPostPage=\"true\"\n          class=\"post-editor\"\n        ></monaco-markdown-editor>\n        <div class=\"footer-info\">\n          {{ $t('writingIn') }} <a @click.prevent=\"openPage('https://gridea.dev')\" class=\"link\">Gridea</a>\n        </div>\n\n        <div class=\"right-tool-container\">\n          <a-popover placement=\"left\" trigger=\"click\">\n            <template slot=\"content\">\n              <div class=\"post-stats\">\n                <div class=\"item\">\n                  <h4>{{ $t('words') }}</h4>\n                  <div class=\"number\">{{ postStats.wordsNumber }}</div>\n                </div>\n                <div class=\"item\">\n                  <h4>{{ $t('readingTime') }}</h4>\n                  <div class=\"number\">{{ postStats.formatTime }}</div>\n                </div>\n              </div>\n            </template>\n            <div class=\"op-btn\" @click=\"handleInfoClick\">\n              <i class=\"zwicon-info-circle\"></i>\n            </div>\n          </a-popover>\n          <a-popover placement=\"left\" trigger=\"click\">\n            <template slot=\"content\">\n              <EmojiCard @select=\"handleEmojiSelect\" />\n            </template>\n            <div class=\"op-btn\" @click=\"handleEmojiClick\">\n              <i class=\"zwicon-smile\"></i>\n            </div>\n          </a-popover>\n          <a-tooltip placement=\"left\" :title=\"$t('insertImage')\">\n            <div class=\"op-btn\" @click=\"insertImage\">\n              <i class=\"zwicon-image\"></i>\n            </div>\n          </a-tooltip>\n          <a-tooltip placement=\"left\" :title=\"$t('insertMore')\">\n            <div class=\"op-btn\" @click=\"insertMore\">\n              <i class=\"zwicon-more-v\"></i>\n            </div>\n          </a-tooltip>\n          <a-tooltip placement=\"left\" :title=\"$t('postSettings')\">\n            <div class=\"op-btn\" @click=\"handlePostSettingClick\">\n              <i class=\"zwicon-cog\"></i>\n            </div>\n          </a-tooltip>\n          <a-tooltip placement=\"left\" :title=\"`${$t('preview')} [Ctrl + P]`\">\n            <div class=\"op-btn\" v-shortkey=\"['ctrl', 'p']\" @shortkey=\"shortPreviewPost\" @click=\"previewPost\">\n              <i class=\"zwicon-eye\"></i>\n            </div>\n          </a-tooltip>\n        </div>\n        <div class=\"right-bottom-tool-container\">\n          <a-popover placement=\"leftBottom\" trigger=\"click\">\n            <template slot=\"content\">\n              <div class=\"keyboard-tip\">\n                💁‍♂️ 编辑区域右键能弹出快捷菜单哦\n              </div>\n              <div class=\"keyboard-container\">\n                <div class=\"item\" v-for=\"(item, index) in shortcutKeys\" :key=\"index\">\n                  <a-divider class=\"keyboard-group-title\" orientation=\"left\">{{ item.name }}</a-divider>\n                  <div class=\"list\">\n                    <div class=\"list-item\" v-for=\"(listItem, listIndex) in item.list\" :key=\"listIndex\">\n                      <div class=\"list-item-title\">{{ listItem.title }}</div>\n                      <div>\n                        <span v-for=\"(keyCode, keyIndex) in listItem.keyboard\" :key=\"keyIndex\">\n                          <code>{{keyCode }}</code> <span v-if=\"keyIndex !== listItem.keyboard.length - 1\"> + </span>\n                        </span>\n                      </div>\n                    </div>\n                  </div>\n                </div>\n              </div>\n            </template>\n            <div class=\"op-btn\">\n              <i class=\"zwicon-keyboard\"></i>\n            </div>\n          </a-popover>\n        </div>\n      </div>\n\n      <a-drawer\n        :visible=\"previewVisible\"\n        @close=\"previewVisible = false\"\n        width=\"800\"\n        :wrapStyle=\"{\n          height: 'calc(100% - 108px)',\n          overflow: 'auto',\n          paddingBottom: '108px',\n          zIndex: 1025,\n        }\"\n        title=\" \"\n      >\n        <img class=\"preview-feature-image\" v-if=\"featureType === 'DEFAULT' && form.featureImage.path\" :src=\"form.featureImage.path\" alt=\"\">\n        <img class=\"preview-feature-image\" v-if=\"featureType === 'EXTERNAL' && form.featureImagePath\" :src=\"form.featureImagePath\" alt=\"\">\n        <h1 class=\"preview-title\">{{ form.title }}</h1>\n        <div class=\"preview-date\">{{ form.date.format(site.themeConfig.dateFormat) }}</div>\n        <div class=\"preview-tags\">\n          <span class=\"tag\" v-for=\"(tag, index) in form.tags\" :key=\"index\">\n            {{ tag }}\n          </span>\n        </div>\n        <div class=\"preview-container\" ref=\"previewContainer\"></div>\n      </a-drawer>\n\n      <a-drawer\n        :title=\"$t('postSettings')\"\n        :visible=\"postSettingsVisible\"\n        @close=\"postSettingsVisible = false\"\n        width=\"400\"\n        :wrapStyle=\"{height: 'calc(100% - 108px)',overflow: 'auto',paddingBottom: '108px', zIndex: 1025}\"\n      >\n        <a-collapse v-model=\"activeKey\" class=\"post-settings\" :bordered=\"false\">\n          <a-collapse-panel header=\"URL\" key=\"1\">\n            <a-input v-model=\"form.fileName\" @change=\"handleFileNameChange\"></a-input>\n          </a-collapse-panel>\n          <a-collapse-panel :header=\"$t('tag')\" key=\"2\">\n            <div>\n              <a-select mode=\"tags\" style=\"width: 100%\" v-model=\"form.tags\">\n                <a-select-option v-for=\"tag in tags\" :key=\"tag\" :value=\"tag\">{{ tag }}</a-select-option>\n              </a-select>\n            </div>\n          </a-collapse-panel>\n          <a-collapse-panel :header=\"$t('createAt')\" key=\"3\">\n            <a-date-picker\n              showTime\n              format=\"YYYY-MM-DD HH:mm:ss\"\n              v-model=\"form.date\"\n              style=\"width: 100%\"\n            />\n          </a-collapse-panel>\n\n          <a-collapse-panel :header=\"$t('featureImage')\" key=\"4\">\n            <a-radio-group style=\"margin-bottom: 16px;\" defaultValue=\"a\" buttonStyle=\"solid\" v-model=\"featureType\" size=\"small\">\n              <a-radio-button value=\"DEFAULT\">{{$t('default')}}</a-radio-button>\n              <a-radio-button value=\"EXTERNAL\">{{$t('external')}}</a-radio-button>\n            </a-radio-group>\n            <div v-if=\"featureType === 'DEFAULT'\">\n              <a-upload\n                action=\"\"\n                listType=\"picture-card\"\n                class=\"feature-uploader\"\n                :showUploadList=\"false\"\n                :beforeUpload=\"beforeFeatureUpload\"\n              >\n                <div v-if=\"form.featureImage.path\">\n                  <img class=\"feature-image\" :src=\"`file://${form.featureImage.path}`\" height=\"150\" />\n                </div>\n                <div v-else>\n                  <img src=\"@/assets/images/image_upload.svg\" class=\"upload-img\">\n                  <i class=\"zwicon-upload upload-icon\"></i>\n                </div>\n              </a-upload>\n              <a-button v-if=\"form.featureImage.path\" type=\"danger\" block icon=\"delete\" @click=\"form.featureImage = {}\" />\n            </div>\n            <div v-if=\"featureType === 'EXTERNAL'\">\n              <a-input v-model=\"form.featureImagePath\"></a-input>\n              <div class=\"tip-text\">{{$t('pathContainHttps')}}</div>\n              <div class=\"feature-image-container\" v-if=\"form.featureImagePath\">\n                <img class=\"feature-image\" :src=\"form.featureImagePath\" height=\"150\">\n              </div>\n            </div>\n          </a-collapse-panel>\n          <a-collapse-panel :header=\"$t('hideInList')\" key=\"5\">\n            <a-switch v-model=\"form.hideInList\"></a-switch>\n          </a-collapse-panel>\n          <a-collapse-panel :header=\"$t('topArticles')\" key=\"6\">\n            <a-switch v-model=\"form.isTop\"></a-switch>\n          </a-collapse-panel>\n        </a-collapse>\n      </a-drawer>\n\n      <!-- 编辑器点击图片上传用 -->\n      <input ref=\"uploadInput\" class=\"upload-input\" type=\"file\" accept=\"image/*\" @change=\"fileChangeHandler\">\n\n      <span class=\"save-tip\">{{ postStatusTip }}</span>\n    </div>\n  </div>\n</template>\n\n<script lang=\"ts\">\nimport {\n  ipcRenderer, IpcRendererEvent, shell, clipboard, remote,\n} from 'electron'\nimport {\n  Vue, Component, Prop, Watch,\n} from 'vue-property-decorator'\nimport { State } from 'vuex-class'\nimport shortid from 'shortid'\nimport moment from 'moment'\nimport * as fse from 'fs-extra'\nimport * as monaco from 'monaco-editor'\nimport Prism from 'prismjs'\nimport { wordCount, timeCalc } from '../../helpers/words-count'\nimport markdown from '../../server/plugins/markdown'\nimport MonacoMarkdownEditor from '../../components/MonacoMarkdownEditor/Index.vue'\nimport EmojiCard from '../../components/EmojiCard/Index.vue'\nimport slug from '../../helpers/slug'\nimport { IPost } from '../../interfaces/post'\nimport { Site } from '../../store/modules/site'\nimport { UrlFormats } from '../../helpers/enums'\nimport shortcutKeys from '../../helpers/shortcut-keys'\nimport ga from '../../helpers/analytics'\n\n@Component({\n  components: {\n    MonacoMarkdownEditor,\n    EmojiCard,\n  },\n})\nexport default class ArticleUpdate extends Vue {\n  @Prop(Boolean) visible!: boolean\n\n  @Prop(String) articleFileName!: string\n\n  postSettingsVisible = false\n\n  previewVisible = false\n\n  previewPostHTML = ''\n\n  changedAfterLastSave = false\n\n  entering = false\n\n  shortcutKeys = shortcutKeys\n\n  $refs!: {\n    uploadInput: any,\n    image: any,\n    articlePage: HTMLElement,\n    monacoMarkdownEditor: any,\n    previewContainer: HTMLElement,\n    pageTitle: HTMLElement,\n  }\n\n  @State('site') site!: Site\n\n  form = {\n    title: '',\n    fileName: '',\n    tags: [] as string[],\n    date: moment(new Date()),\n    content: '',\n    published: false,\n    hideInList: false,\n    isTop: false,\n    featureImage: {\n      path: '',\n      name: '',\n      type: '',\n    },\n    featureImagePath: '',\n    deleteFileName: '',\n  }\n\n  featureType: 'DEFAULT' | 'EXTERNAL' = 'DEFAULT'\n\n  activeKey = ['1']\n\n  postStatusTip = ''\n\n  get dateLocale() {\n    return this.$root.$i18n.locale === 'zhHans' ? 'zh-cn' : 'en-us'\n  }\n\n  // 编辑文章时，当前文章的索引\n  currentPostIndex = -1\n\n  originalFileName = ''\n\n  fileNameChanged = false\n\n  get canSubmit() {\n    return this.form.title && this.form.content\n  }\n\n  get tags() {\n    return this.site.tags.map((tag: any) => tag.name)\n  }\n\n  get postStats() {\n    const reading = timeCalc(this.form.content)\n    const second = Number((reading.second - (reading.minius - 1) * 60).toFixed(2))\n    const formatTime = `${Math.floor(reading.second / 60)}m ${second < 60 ? second : ''}${second < 60 ? 's' : ''}`\n\n    let wordsNumber = 0\n    wordCount(this.form.content, (count: number) => {\n      wordsNumber = count\n    })\n\n    return {\n      formatTime: formatTime,\n      wordsNumber: Array.isArray(wordsNumber) ? 0 : wordsNumber,\n    }\n  }\n\n  mounted() {\n    this.buildCurrentForm()\n    ipcRenderer.removeAllListeners('click-menu-save')\n    ipcRenderer.on('click-menu-save', (event: IpcRendererEvent, data: any) => {\n      this.normalSavePost()\n    })\n\n    this.$watch('form', () => {\n      this.changedAfterLastSave = true\n    }, { deep: true })\n  }\n\n  buildCurrentForm() {\n    const { articleFileName } = this\n    if (articleFileName) {\n      this.fileNameChanged = true // 编辑文章标题时 URL 不跟随其变化\n      this.currentPostIndex = this.site.posts.findIndex((item: IPost) => item.fileName === articleFileName)\n      const currentPost = this.site.posts[this.currentPostIndex]\n      this.originalFileName = currentPost.fileName\n\n      if (currentPost) {\n        this.form.title = currentPost.data.title\n        this.form.fileName = currentPost.fileName\n        this.form.tags = currentPost.data.tags || []\n        this.form.date = moment(currentPost.data.date).isValid() ? moment(currentPost.data.date) : moment()\n        this.form.content = currentPost.content\n        this.form.published = currentPost.data.published\n        this.form.hideInList = currentPost.data.hideInList\n        this.form.isTop = currentPost.data.isTop\n\n        if (currentPost.data.feature && currentPost.data.feature.includes('http')) {\n          this.form.featureImagePath = currentPost.data.feature\n          this.featureType = 'EXTERNAL'\n        } else {\n          this.form.featureImage.path = (currentPost.data.feature && currentPost.data.feature.substring(7)) || ''\n          this.form.featureImage.name = this.form.featureImage.path.replace(/^.*[\\\\/]/, '')\n        }\n      }\n    } else if (this.site.themeConfig.postUrlFormat === UrlFormats.ShortId) {\n      this.form.fileName = shortid.generate()\n    }\n  }\n\n  beforeFeatureUpload(file: any) {\n    if (!file) {\n      return\n    }\n    const isImage = file.type.indexOf('image') !== -1\n    if (!isImage) {\n      return\n    }\n    if (file && isImage) {\n      this.form.featureImage = {\n        name: file.name,\n        path: file.path,\n        type: file.type,\n      }\n    }\n\n    ga.event('Post', 'Post - set-local-feature-image', {})\n    return false\n  }\n\n  close() {\n    if (this.changedAfterLastSave) {\n      this.$confirm({\n        title: `${this.$t('warning')}`,\n        content: `${this.$t('unsavedWarning')}`,\n        okText: `${this.$t('noSaveAndBack')}`,\n        okType: 'danger',\n        cancelText: `${this.$t('cancel')}`,\n        zIndex: 2000,\n        onOk: () => {\n          this.$emit('close')\n        },\n      })\n      return\n    }\n    this.$emit('close')\n  }\n\n  updatePostSavedStatus() {\n    this.postStatusTip = `${this.$t('savedIn')} ${moment().format('HH:mm:ss')}`\n    this.changedAfterLastSave = false\n  }\n\n  handleTitleChange(val: string) {\n    if (!this.fileNameChanged && this.site.themeConfig.postUrlFormat === UrlFormats.Slug) {\n      this.form.fileName = slug(this.form.title)\n    }\n  }\n\n  handleFileNameChange(val: string) {\n    this.fileNameChanged = !!val\n  }\n\n  preventDefault(event: any) {\n    if (event.target.tagName === 'A') {\n      const href = event.target.getAttribute('href')\n      if (href && !href.startsWith('#')) {\n        // ignore anchor link.\n        event.preventDefault()\n        shell.openExternal(href)\n      }\n    }\n  }\n\n  buildFileName() {\n    if (this.form.fileName !== '') {\n      return\n    }\n\n    this.form.fileName = this.site.themeConfig.postUrlFormat === UrlFormats.Slug\n      ? slug(this.form.title)\n      : shortid.generate()\n  }\n\n  checkArticleUrlValid() {\n    const restPosts = JSON.parse(JSON.stringify(this.site.posts))\n    const foundPostIndex = restPosts.findIndex((post: IPost) => post.fileName === this.form.fileName)\n\n    if (foundPostIndex !== -1) {\n      if (this.currentPostIndex === -1) {\n        // 新增文章时文件名和其他文章文件名冲突\n        return false\n      }\n      restPosts.splice(this.currentPostIndex, 1)\n      const index = restPosts.findIndex((post: IPost) => post.fileName === this.form.fileName)\n      if (index !== -1) {\n        return false\n      }\n    }\n\n    this.currentPostIndex = this.currentPostIndex === -1 ? 0 : this.currentPostIndex\n\n    return true\n  }\n\n  formatForm(published?: boolean) {\n    this.buildFileName()\n    const valid = this.checkArticleUrlValid()\n    if (!valid) {\n      this.$message.error(this.$t('postUrlRepeatTip'))\n      return\n    }\n\n    if (this.form.fileName.includes('/')) {\n      this.$message.error(this.$t('postUrlIncludeTip'))\n      return\n    }\n\n    // 文件名改变之后，删除原来文件\n    if (this.form.fileName.toLowerCase() !== this.originalFileName.toLowerCase()) {\n      this.form.deleteFileName = this.originalFileName\n    }\n\n    const form = {\n      ...this.form,\n      date: this.form.date.format('YYYY-MM-DD HH:mm:ss'),\n    }\n    if (this.featureType !== 'EXTERNAL') {\n      form.featureImagePath = ''\n    }\n    if (this.featureType !== 'DEFAULT') {\n      form.featureImage = {\n        path: '',\n        name: '',\n        type: '',\n      }\n    }\n    form.published = typeof published === 'boolean' ? published : form.published\n\n    return form\n  }\n\n  saveDraft() {\n    if (!this.canSubmit) return\n    const form = this.formatForm(false)\n\n    ipcRenderer.send('app-post-create', form)\n    ipcRenderer.once('app-post-created', (event: IpcRendererEvent, data: any) => {\n      this.updatePostSavedStatus()\n      this.$message.success(`🎉  ${this.$t('draftSuccess')}`)\n      this.$emit('fetchData')\n    })\n\n    ga.event('Post', 'Post - click-save-draft', {})\n  }\n\n  savePost() {\n    if (!this.canSubmit) return\n    const form = this.formatForm(true)\n\n    ipcRenderer.send('app-post-create', form)\n    ipcRenderer.once('app-post-created', (event: IpcRendererEvent, data: any) => {\n      this.updatePostSavedStatus()\n      this.$message.success(`🎉  ${this.$t('saveSuccess')}`)\n      this.$emit('fetchData')\n    })\n\n    ga.event('Post', 'Post - click-save-post', {})\n  }\n\n  normalSavePost() {\n    if (!this.canSubmit) return\n    const form = this.formatForm()\n\n    ipcRenderer.send('app-post-create', form)\n    ipcRenderer.once('app-post-created', (event: IpcRendererEvent, data: any) => {\n      this.updatePostSavedStatus()\n      this.$emit('fetchData')\n    })\n  }\n\n  insertImage() {\n    this.$refs.uploadInput.click()\n    ga.event('Post', 'Post - click-insert-image', {})\n  }\n\n  handlePostSettingClick() {\n    this.postSettingsVisible = true\n\n    ga.event('Post', 'Post - click-post-setting', {})\n  }\n\n  handleInfoClick() {\n    ga.event('Post', 'Post - click-post-info', {})\n  }\n\n  handleEmojiClick() {\n    ga.event('Post', 'Post - click-emoji-card', {})\n  }\n\n  uploadImageFiles(files: any[]) {\n    ipcRenderer.send('image-upload', files)\n    ipcRenderer.once('image-uploaded', (event: IpcRendererEvent, data: any) => {\n      for (const path of data) {\n        let url = `![](file://${path})`\n        url = url.replace(/\\\\/g, '/')\n\n        this.$refs.monacoMarkdownEditor.editor.getModel().applyEdits([{\n          range: monaco.Range.fromPositions(this.$refs.monacoMarkdownEditor.editor.getPosition()),\n          text: url,\n        }])\n      }\n    })\n  }\n\n  insertMore() {\n    this.$refs.monacoMarkdownEditor.editor.getModel().applyEdits([{\n      range: monaco.Range.fromPositions(this.$refs.monacoMarkdownEditor.editor.getPosition()),\n      text: '\\n<!-- more -->\\n',\n    }])\n\n    ga.event('Post', 'Post - click-add-more', {})\n  }\n\n  handleEmojiSelect(emoji: any) {\n    this.$refs.monacoMarkdownEditor.editor.getModel().applyEdits([{\n      range: monaco.Range.fromPositions(this.$refs.monacoMarkdownEditor.editor.getPosition()),\n      text: emoji,\n    }])\n  }\n\n  previewPost() {\n    this.previewVisible = true\n    setTimeout(() => {\n      this.$refs.previewContainer.innerHTML = markdown.render(this.form.content)\n      Prism.highlightAll()\n    }, 1)\n\n    ga.event('Post', 'Post - click-preview-post', {})\n  }\n\n  shortPreviewPost(event: any) {\n    if (this.previewVisible) {\n      this.previewVisible = false\n      return\n    }\n    this.previewPost()\n  }\n\n  /**\n   * 单张图片上传\n   */\n  fileChangeHandler(e: any) {\n    const file = (e.target.files || e.dataTransfer)[0]\n    if (!file) {\n      return\n    }\n    const isImage = file.type.indexOf('image') !== -1\n    if (!isImage) {\n      return\n    }\n    if (file && isImage) {\n      this.uploadImageFiles([\n        {\n          name: file.name,\n          path: file.path,\n          type: file.type,\n        },\n      ])\n    }\n  }\n\n  handleInputKeydown() {\n    this.entering = true\n  }\n\n  handlePageMousemove() {\n    this.entering = false\n  }\n\n  openPage(url: string) {\n    shell.openExternal(url)\n  }\n}\n</script>\n\n<style lang=\"less\" scoped>\n.upload-input {\n  display: none;\n}\n\n.btn {\n  margin-left: 16px;\n}\n.feature-image-container {\n  text-align: center;\n  padding: 16px;\n}\n\n.feature-image {\n  max-width: 100%\n}\n/deep/ .ant-upload.ant-upload-select-picture-card {\n  width: 100%\n}\n\n/deep/ .ant-modal-content {\n  height: 100%;\n}\n\n/deep/ .ant-collapse {\n  background: #F7F6F3;\n}\n.article-update-page {\n  position: fixed;\n  top: 0;\n  left: 0;\n  right: 0;\n  bottom: 0;\n  z-index: 1025;\n  background: #fff;\n  display: flex;\n  flex-direction: column;\n  .page-title {\n    padding: 8px 16px;\n    z-index: 1026;\n    background: #fff;\n    transition: opacity 700ms ease;\n    border-bottom: 1px solid #e8e8e88a;\n\n    .op-btn {\n      height: 30px;\n      line-height: 30px;\n      padding: 0 16px;\n      font-size: 18px;\n      border-radius: 20px;\n      margin-left: 8px;\n      outline: none;\n      display: flex;\n      justify-content: center;\n      align-items: center;\n      transition: all 0.3s;\n      i {\n        font-weight: bold;\n      }\n      &:hover {\n        background: #efefef;\n        color: #515457;\n      }\n      &:focus {\n        background: #efefef;\n      }\n      &.save-btn:not(.disabled) {\n        color: #38A169;\n        &:hover {\n          background: #9AE6B4;\n          color: #22543D;\n        }\n        &:focus {\n          color: #22543D;\n          background: #68D391;\n        }\n      }\n      &.disabled {\n        cursor: default;\n        color: #ccc;\n        // background: #fafafa;\n        &:hover, &:focus {\n          background: #fff;\n        }\n      }\n    }\n  }\n  .page-content {\n    background: #fff;\n    flex: 1;\n    display: flex;\n    overflow: scroll;\n  }\n\n  &.is-entering {\n    .page-title,\n    .right-tool-container,\n    .right-bottom-tool-container {\n      opacity: 0;\n    }\n  }\n}\n\n.tip-text {\n  margin: 8px 0;\n}\n\n.post-title {\n  font-weight: 400;\n  background: #fff;\n  padding: 8px 0;\n  font-size: 24px;\n  color: #000;\n  border: none;\n  // border-bottom: 1px solid #e8e8e8;\n  display: block;\n  width: 728px;\n  margin: 12px auto 12px;\n  &:focus {\n    box-shadow: none;\n  }\n}\n\n#markdown-editor {\n  /deep/ .editor-toolbar {\n    position: fixed;\n    top: 0px;\n    z-index: 3000;\n    &:before {\n      margin-bottom: 7px;\n    }\n  }\n}\n.editor-container {\n  padding: 32px 89px 16px 24px;\n  border: 1px solid #e8e8e8;\n  background: #ffffff;\n  box-shadow: 0 2px 8px rgba(115, 115, 115, 0.08);\n}\n.ant-drawer {\n  z-index: 1025;\n}\n\n.upload-img {\n  width: 80px;\n}\n.upload-icon {\n  font-size: 18px;\n  margin-top: 8px;\n  display: block;\n}\n\n.post-settings {\n  /deep/ .ant-collapse-item {\n    border-bottom: 1px solid #e8e8e88a;\n    > .ant-collapse-header {\n      padding-left: 16px;\n      background: #fbfbfb;\n      .arrow {\n        left: auto;\n        right: 16px;\n      }\n    }\n    &.ant-collapse-item-active {\n      > .ant-collapse-header {\n        background: #fff;\n      }\n    }\n  }\n}\n.footer-info {\n  text-align: center;\n  color: #666;\n  font-size: 12px;\n  font-weight: lighter;\n  -webkit-font-smoothing: antialiased;\n  padding-top: 6px;\n  margin-top: 8px;\n  border-top: 1px solid #e8e8e88a;\n  .link {\n    color: #666;\n    &:hover {\n      color: #101010;\n    }\n  }\n}\n\n.editor-wrapper {\n  width: 100%;\n  margin: 0 auto;\n  position: relative;\n  display: flex;\n  flex-direction: column;\n\n  .post-editor {\n    flex: 1;\n\n    /deep/ .monaco-markdown-editor {\n      width: 728px;\n    }\n\n    /deep/ .monaco-editor {\n      .scrollbar {\n        position: fixed !important;\n        top: 110px !important;\n      }\n    }\n  }\n}\n.right-tool-container,\n.right-bottom-tool-container {\n  position: fixed;\n  right: 12px;\n  display: flex;\n  flex-direction: column;\n  color: #A0AEC0;\n  transition: color 0.3s ease;\n  transition: opacity 700ms ease;\n  &:hover {\n    color: #4A5568;\n  }\n  .op-btn {\n    font-size: 18px;\n    margin-top: 8px;\n    padding: 4px;\n    border-radius: 4px;\n    line-height: 1;\n    transition: all 0.3s;\n    &:hover {\n      background: #efefef;\n      color: #515457;\n    }\n  }\n}\n\n.right-tool-container {\n  bottom: 50%;\n  transform: translateY(50%);\n}\n\n.right-bottom-tool-container {\n  bottom: 2px;\n}\n\n.post-title-container {\n  margin-left: 65px;\n}\n\n.save-tip {\n  padding: 4px 10px;\n  line-height: 22px;\n  font-size: 12px;\n  color: #b7b7b7;\n  position: fixed;\n  left: 0;\n  bottom: 0;\n}\n\n.preview-container {\n  width: 100%;\n  flex-shrink: 0;\n  font-family: \"Noto Serif\",\"PingFang SC\",\"Hiragino Sans GB\",\"Droid Sans Fallback\",\"Microsoft YaHei\",sans-serif;\n  font-size: 15px;\n\n  /deep/ a {\n    color: rgba(0,0,0,.98);\n    word-wrap: break-word;\n    text-decoration: none;\n    border-bottom: 1px solid rgba(0,0,0,.26);\n    &:hover {\n      color: #000;\n      border-bottom: 1px solid #000;\n    }\n  }\n  /deep/ img {\n    display: block;\n    max-width: 100%;\n    border-radius: 2px;\n    margin: 24px auto;\n  }\n\n  /deep/ p {\n    line-height: 1.62;\n    margin-bottom: 1.12em;\n    font-size: 15px;\n    letter-spacing: .05em;\n    hyphens: auto;\n  }\n\n  /deep/ p, /deep/ li {\n    line-height: 1.62;\n    code {\n      font-family: 'Source Code Pro', Consolas, Menlo, Monaco, 'Courier New', monospace;\n      line-height: initial;\n      word-wrap: break-word;\n      border-radius: 0;\n      background-color: #FFF5F5;\n      color: #C53030;\n      padding: .2em .33333333em;\n      font-size: .875rem;\n      margin-left: .125em;\n      margin-right: .125em;\n    }\n  }\n\n  /deep/ pre {\n    background: #f7f6f3;\n    padding: 16px;\n    border-radius: 2px;\n    code {\n      color: #000;\n      font-family: 'Source Code Pro', Consolas, Menlo, Monaco, 'Courier New', monospace;\n    }\n  }\n\n  /deep/ blockquote {\n    color: #9a9a9a;\n    position: relative;\n    padding: .4em 0 0 2.2em;\n    font-size: .96em;\n    &:before {\n      position: absolute;\n      top: -4px;\n      left: 0;\n      content: \"\\201c\";\n      font: 700 62px/1 serif;\n      color: rgba(0,0,0,.1);\n    }\n  }\n\n  /deep/ table {\n    border-collapse: collapse;\n    margin: 1rem 0;\n    width: 100%;\n    tr {\n      border-top: 1px solid #dfe2e5;\n      &:nth-child(2n) {\n        background-color: #f6f8fa;\n      }\n    }\n    td, th {\n      border: 1px solid #dfe2e5;\n      padding: .6em 1em;\n    }\n  }\n\n  /deep/ ul, /deep/ ol {\n    padding-left: 35px;\n    line-height: 1.62;\n    margin-bottom: 16px;\n  }\n\n  /deep/ ol {\n    list-style: decimal !important;\n  }\n\n  /deep/ ul {\n    list-style-type: square !important;\n  }\n\n  /deep/ h1, h2, h3, h4, h5, h6 {\n    margin: 16px 0;\n    font-weight: 700;\n    padding-top: 16px;\n  }\n  /deep/ h1 {\n    font-size: 1.8em;\n  }\n  /deep/ h2 {\n    font-size: 1.42em;\n  }\n  /deep/ h3 {\n    font-size: 1.17em;\n  }\n  /deep/ h4 {\n    font-size: 1em;\n  }\n  /deep/ h5 {\n    font-size: 1em;\n  }\n  /deep/ h6 {\n    font-size: 1em;\n    font-weight: 500;\n  }\n  /deep/ hr {\n    display: block;\n    border: 0;\n    margin: 2.24em auto 2.86em;\n    &:before {\n      color: rgba(0,0,0,.2);\n      font-size: 1.1em;\n      display: block;\n      content: \"* * *\";\n      text-align: center;\n    }\n  }\n\n  /deep/ .footnotes {\n    margin-left: auto;\n    margin-right: auto;\n    max-width: 760px;\n    padding-left: 18px;\n    padding-right: 18px;\n    &:before {\n      content: \"\";\n      display: block;\n      border-top: 4px solid rgba(0,0,0,.1);\n      width: 50%;\n      max-width: 100px;\n      margin: 40px 0 20px;\n    }\n  }\n\n  /deep/ .contains-task-list {\n    list-style-type: none;\n    padding-left: 30px;\n  }\n  /deep/ .task-list-item {\n    position: relative;\n  }\n  /deep/ .task-list-item-checkbox {\n    position: absolute;\n    cursor: pointer;\n    width: 16px;\n    height: 16px;\n    margin: 4px 0 0;\n    top: -1px;\n    left: -22px;\n    transform-origin: center;\n    transform: rotate(-90deg);\n    transition: all .2s ease;\n    &:checked {\n      transform: rotate(0);\n      &:before {\n        border: transparent;\n        background-color: #9AE6B4;\n      }\n      &:after {\n        transform: rotate(-45deg) scale(1);\n      }\n      + .task-list-item-label {\n        color: #999;\n        text-decoration: line-through;\n      }\n    }\n    &:before {\n      content: \"\";\n      width: 16px;\n      height: 16px;\n      box-sizing: border-box;\n      display: inline-block;\n      border: 1px solid #9AE6B4;\n      border-radius: 2px;\n      background-color: #fff;\n      position: absolute;\n      top: 0;\n      left: 0;\n      transition: all .2s ease;\n    }\n    &:after {\n      content: \"\";\n      transform: rotate(-45deg) scale(0);\n      width: 9px;\n      height: 5px;\n      border: 1px solid #22543D;\n      border-top: none;\n      border-right: none;\n      position: absolute;\n      display: inline-block;\n      top: 4px;\n      left: 4px;\n      transition: all .2s ease;\n    }\n  }\n\n  /deep/ .markdownIt-TOC {\n    list-style: none;\n    background: #f7fafc;\n    padding: 1.5rem;\n    border-radius: 0.5rem;\n    color: #4a5568;\n  }\n\n  /deep/ .markdownIt-TOC ul {\n    list-style: none;\n    padding-left: 16px;\n  }\n\n  /deep/ mark {\n    background: #FAF089;\n    color: #744210;\n  }\n}\n\n.preview-title {\n  font-size: 24px;\n  font-weight: bold;\n  font-family: \"Noto Serif\",\"PingFang SC\",\"Hiragino Sans GB\",\"Droid Sans Fallback\",\"Microsoft YaHei\",sans-serif;\n}\n\n.preview-date {\n  font-size: 13px;\n  color: #718096;\n  margin-bottom: 16px;\n}\n\n.preview-tags {\n  font-size: 12px;\n  margin-bottom: 16px;\n  .tag {\n    display: inline-block;\n    margin: 0 8px 8px 0;\n    padding: 4px 8px;\n    background: #F7FAFC;\n    color: #4A5568;\n    border-radius: 20px;\n  }\n}\n\n.preview-feature-image {\n  max-width: 100%;\n  margin-bottom: 16px;\n  border-radius: 2px;\n}\n\n.keyboard-container {\n  width: 200px;\n  .keyboard-group-title {\n    margin: 8px 0;\n    font-size: 12px;\n  }\n  .list {\n    .list-item {\n      display: flex;\n      justify-content: space-between;\n      font-size: 12px;\n      padding: 4px;\n      border-radius: 2px;\n      &:not(:last-child) {\n        border-bottom: 1px solid #fafafa;\n      }\n      &:hover {\n        background: #FFFFF0;\n        color: #B7791F;\n      }\n\n      code {\n        padding: 0px 4px;\n        border-radius: 2px;\n        background: #EDF2F7;\n      }\n    }\n  }\n}\n\n.post-stats {\n  display: flex;\n  .item {\n    width: 50%;\n    min-width: 80px;\n    h4 {\n      color: #718096;\n      font-size: 12px;\n      font-weight: normal;\n    }\n    .number {\n      font-size: 18px;\n      font-family: 'Noto Serif';\n    }\n  }\n}\n\n.keyboard-tip {\n  font-size: 12px;\n  color: #909090;\n}\n</style>\n"
  },
  {
    "path": "src/views/article/Articles.vue",
    "content": "<template>\r\n  <div class=\"articles-page\">\r\n    <div class=\"flex justify-between tool-container\">\r\n      <div class=\"flex items-center\">\r\n        <div v-if=\"selectedPost.length > 0\" @click=\"deleteSelectedPosts\" class=\"flex items-center py-1 px-2 bg-gray-100 transition cursor-default hover:bg-gray-200 rounded-sm\">\r\n          <i class=\"ri-delete-bin-3-line mr-2\"></i><span class=\"text-sm\">{{ $t('deleteSelected') }} {{ selectedPost.length }}</span>\r\n        </div>\r\n      </div>\r\n      <div class=\"flex\">\r\n        <a-input-search\r\n          class=\"search-input\"\r\n          :placeholder=\"$t('searchArticle')\"\r\n          style=\"width: 200px\"\r\n          @search=\"onSearch\"\r\n          v-model=\"keyword\"\r\n          @blur=\"handleSearchInputBlur\"\r\n          v-if=\"searchInputVisible\"\r\n        />\r\n        <a-tooltip placement=\"bottom\" :title=\"$t('searchArticle')\">\r\n          <div class=\"op-btn\" @click=\"searchInputVisible = true\" v-if=\"!keyword && !searchInputVisible\">\r\n            <i class=\"zwicon-search\"></i>\r\n          </div>\r\n        </a-tooltip>\r\n        <a-tooltip placement=\"bottom\" :title=\"$t('newArticle')\">\r\n          <div class=\"op-btn\" tabindex=\"0\" @click=\"newArticle\">\r\n            <i class=\"zwicon-plus\"></i>\r\n          </div>\r\n        </a-tooltip>\r\n      </div>\r\n    </div>\r\n    <div class=\"content-container\">\r\n      <div class=\"pb-12\">\r\n        <div\r\n          class=\"post-container flex mb-2 relative cursor-pointer transition-fast hover:bg-gray-100 overflow-hidden\"\r\n          v-for=\"post in currentPostList\" :key=\"post.fileName\"\r\n          @click=\"editPost(post)\"\r\n        >\r\n          <div class=\"p-4 flex-1 flex\">\r\n            <div class=\"flex flex-shrink-0 items-center pr-4\">\r\n              <a-checkbox @click.stop=\"() => {}\" :checked=\"selectedPost.includes(post)\" @change=\"onSelectChange(post)\"></a-checkbox>\r\n            </div>\r\n            <div class=\"flex-1\">\r\n              <a class=\"post-title block text-base text-gray-700 mb-2\">{{ post.data.title }}</a>\r\n              <div class=\"text-xs flex items-center text-gray-300\">\r\n                <div class=\"text-xs flex items-center mr-2\">\r\n                  <div class=\"rounded-full w-1 h-1 mr-1\" :class=\"{ 'bg-green-500': post.data.published, 'bg-gray-500': !post.data.published }\"></div>\r\n                  {{ post.data.published ? $t('published') : $t('draft') }}\r\n                </div>\r\n                <div class=\"flex items-center\">\r\n                  <i class=\"ri-calendar-line mr-1\"></i> {{ $moment(post.data.date).format('YYYY-MM-DD') }}\r\n                </div>\r\n                <div class=\"flex-1 flex flex-wrap items-center ml-2\" v-if=\"(post.data.tags || []).length > 0\">\r\n                  <i class=\"ri-price-tag-3-line\"></i>\r\n                  <div v-for=\"(tag, index) in post.data.tags\" :key=\"index\">\r\n                    <div class=\"text-xs ml-1 flex\" v-if=\"index < 2\">\r\n                      {{ tag }}\r\n                    </div>\r\n                  </div>\r\n                  <div v-if=\"(post.data.tags || []).length > 2\" class=\"ml-1\">\r\n                    ...\r\n                  </div>\r\n                </div>\r\n              </div>\r\n            </div>\r\n          </div>\r\n\r\n          <img v-if=\"post.data.feature\" :src=\"post.data.feature\" class=\"feature-img\" />\r\n\r\n          <div class=\"absolute right-0 top-0 -mt-px -mr-px flex\">\r\n            <div v-if=\"post.data.hideInList\" class=\"text-xs flex items-center px-2 rounded-b rounded-br-none bg-gray-800 text-white\">\r\n              HIDE\r\n            </div>\r\n            <div v-if=\"post.data.isTop\" class=\"ml-2 text-xs flex items-center px-2 rounded-b rounded-br-none bg-yellow-400 text-gray-900\">\r\n              TOP\r\n            </div>\r\n          </div>\r\n        </div>\r\n      </div>\r\n      <div class=\"pagination-container py-2\" v-if=\"postList.length > pageSize\">\r\n        <a-pagination\r\n          v-model=\"currentPage\"\r\n          :pageSize=\"pageSize\"\r\n          :total=\"postList.length\"\r\n          @change=\"handlePageChanged\"\r\n        />\r\n      </div>\r\n    </div>\r\n\r\n    <fade-transition :duration=\"100\">\r\n      <article-update\r\n        v-if=\"articleUpdateVisible\"\r\n        :visible=\"articleUpdateVisible\"\r\n        :articleFileName=\"currentArticleFileName\"\r\n        @close=\"close\"\r\n        @fetchData=\"$bus.$emit('site-reload')\"\r\n      ></article-update>\r\n    </fade-transition>\r\n  </div>\r\n</template>\r\n\r\n<script lang=\"ts\">\r\nimport { ipcRenderer, IpcRendererEvent } from 'electron'\r\nimport { Vue, Component, Watch } from 'vue-property-decorator'\r\nimport { State } from 'vuex-class'\r\nimport { FadeTransition } from 'vue2-transitions'\r\nimport { IPost } from '../../interfaces/post'\r\nimport ArticleUpdate from './ArticleUpdate.vue'\r\nimport ga from '../../helpers/analytics'\r\n\r\n@Component({\r\n  components: {\r\n    ArticleUpdate,\r\n    FadeTransition,\r\n  },\r\n})\r\nexport default class Articles extends Vue {\r\n  @State('site') site!: any\r\n\r\n  articleUpdateVisible = false\r\n\r\n  currentArticleFileName = ''\r\n\r\n  selectedPost: any = []\r\n\r\n  keyword = ''\r\n\r\n  searchInputVisible = false\r\n\r\n  currentPage = 1\r\n\r\n  pageSize = 20\r\n\r\n  handleSearchInputBlur() {\r\n    if (!this.keyword) {\r\n      this.searchInputVisible = false\r\n    }\r\n  }\r\n\r\n  get postList() {\r\n    return this.site.posts.filter((item: IPost) => item.data.title.toLowerCase().includes(this.keyword.toLowerCase()))\r\n  }\r\n\r\n  get currentPostList() {\r\n    return this.postList.slice((this.currentPage - 1) * this.pageSize, this.currentPage * this.pageSize)\r\n  }\r\n\r\n  onSelectChange(post: any) {\r\n    const foundIndex = this.selectedPost.findIndex((item: any) => item === post)\r\n    if (foundIndex !== -1) {\r\n      this.selectedPost.splice(foundIndex, 1)\r\n    } else {\r\n      this.selectedPost.push(post)\r\n    }\r\n  }\r\n\r\n  handlePageChanged(page: number) {\r\n    this.currentPage = page\r\n  }\r\n\r\n  mounted() {\r\n    this.$bus.$emit('site-reload')\r\n  }\r\n\r\n  close() {\r\n    this.articleUpdateVisible = false\r\n    this.currentArticleFileName = ''\r\n  }\r\n\r\n  newArticle() {\r\n    this.articleUpdateVisible = true\r\n    this.currentArticleFileName = ''\r\n\r\n    ga.event('Post', 'Post - new', { evLabel: this.site.setting.domain })\r\n  }\r\n\r\n  editPost(post: IPost) {\r\n    this.articleUpdateVisible = true\r\n    this.currentArticleFileName = post.fileName\r\n  }\r\n\r\n  async deletePost(post: IPost) {\r\n    this.$confirm({\r\n      title: `${this.$t('warning')}`,\r\n      content: `${this.$t('deleteWarning')}`,\r\n      okText: 'Yes',\r\n      okType: 'danger',\r\n      cancelText: 'No',\r\n      onOk: () => {\r\n        ipcRenderer.send('app-post-delete', post)\r\n        ipcRenderer.once('app-post-deleted', (event: IpcRendererEvent, data: any) => {\r\n          if (data) {\r\n            this.$message.success(this.$t('articleDelete'))\r\n            this.$bus.$emit('site-reload')\r\n          }\r\n        })\r\n      },\r\n    })\r\n  }\r\n\r\n  async deleteSelectedPosts() {\r\n    this.$confirm({\r\n      title: `${this.$t('warning')}`,\r\n      content: `${this.$t('deleteWarning')}`,\r\n      okText: 'Yes',\r\n      okType: 'danger',\r\n      cancelText: 'No',\r\n      onOk: () => {\r\n        ipcRenderer.send('app-post-list-delete', this.selectedPost)\r\n        ipcRenderer.once('app-post-list-deleted', (event: IpcRendererEvent, data: any) => {\r\n          console.log(data)\r\n          if (data) {\r\n            this.$bus.$emit('snackbar-display', this.$t('articleDelete'))\r\n            this.$bus.$emit('site-reload')\r\n\r\n            ga.event('Post', 'Post - delete', { evLabel: this.site.setting.domain, evValue: this.selectedPost.length })\r\n\r\n            this.selectedPost = []\r\n          }\r\n        })\r\n      },\r\n    })\r\n  }\r\n\r\n  onSearch(val: string) {\r\n    this.keyword = val\r\n  }\r\n\r\n  @Watch('keyword')\r\n  handleKeywordChange(val: string) {\r\n    this.currentPage = 1\r\n  }\r\n}\r\n</script>\r\n\r\n<style lang=\"less\" scoped>\r\n@import '~@/assets/styles/var.less';\r\n\r\n.articles-page {\r\n  position: relative;\r\n}\r\n\r\n.post-container {\r\n  box-shadow: inset 0 0 0 1px #eaeaea;\r\n  border-radius: 6px;\r\n  &:hover {\r\n    .post-title {\r\n      color: @link-color;\r\n    }\r\n  }\r\n}\r\n\r\n.feature-img {\r\n  height: 84px;\r\n  width: 176px;\r\n  object-fit: cover;\r\n}\r\n\r\n.pagination-container {\r\n  display: flex;\r\n  justify-content: center;\r\n  position: fixed;\r\n  bottom: 0;\r\n  right: 0;\r\n  left: 200px;\r\n  background: #fff;\r\n  border-top: 1px solid #e8e8e88a;\r\n}\r\n</style>\r\n"
  },
  {
    "path": "src/views/loading/Index.vue",
    "content": "<template>\n  <div class=\"loading-page\">\n    <img class=\"img\" src=\"@/assets/images/developing_code.svg\" width=\"160px\">\n    {{ $t('inConfig') }}...\n  </div>\n</template>\n\n<script>\nimport { Vue, Component } from 'vue-property-decorator'\n\n@Component({\n  name: 'Loading',\n})\nexport default class Loading extends Vue {\n  mounted() {\n    setTimeout(() => {\n      const { redirect } = this.$route.query\n      this.$router.push(redirect)\n    }, 1000)\n  }\n}\n</script>\n\n<style lang=\"less\" scoped>\n.loading-page {\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  flex-direction: column;\n  min-height: 100vh;\n  .img {\n    margin-bottom: 16px;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/views/menu/Index.vue",
    "content": "<template>\n  <div class=\"\">\n    <a-row type=\"flex\" justify=\"end\" class=\"tool-container\">\n      <a-tooltip placement=\"bottom\" :title=\"$t('newMenu')\">\n        <div class=\"op-btn\" tabindex=\"0\" @click=\"newMenu\">\n          <i class=\"zwicon-plus\"></i>\n        </div>\n      </a-tooltip>\n    </a-row>\n    <div class=\"content-container\">\n      <draggable v-model=\"menuList\" handle=\".handle\" @change=\"handleMenuSort\">\n        <div\n          class=\"menu-container flex mb-2 rounded-sm relative cursor-pointer transition-fast hover:bg-gray-100\"\n          v-for=\"(menu, index) in menuList\"\n          :key=\"index\"\n          @click=\"editMenu(menu, index)\"\n        >\n          <div class=\"flex items-center pl-4 handle cursor-move\">\n            <i class=\"ri-drag-move-line\"></i>\n          </div>\n          <div class=\"p-4 flex-1\">\n            <div class=\"text-base text-gray-700 mb-2\">\n              {{ menu.name }}\n            </div>\n            <div class=\"text-xs flex items-center\">\n              <div class=\"text-xs flex items-center px-2 rounded border bg-gray-100 border-gray-200 text-gray-500 mr-4\">\n                {{ menu.openType }}\n                <i class=\"ri-external-link-line ml-2\" v-if=\"menu.openType === 'External'\"></i>\n              </div>\n              <div class=\"text-gray-300\">\n                {{ menu.link }}\n              </div>\n            </div>\n          </div>\n          <div class=\"flex items-center px-4\">\n            <i class=\"ri-delete-bin-4-line hover:text-red-700\" @click.stop=\"deleteMenu(menu.name)\"></i>\n          </div>\n        </div>\n      </draggable>\n    </div>\n    <a-drawer\n      title=\"Menu\"\n      width=\"400\"\n      :visible=\"visible\"\n      @close=\"close\"\n      :wrapStyle=\"{height: 'calc(100% - 108px)',overflow: 'auto',paddingBottom: '108px'}\"\n    >\n      <a-form :form=\"form\" layout=\"vertical\">\n        <a-form-item :label=\"$t('name')\">\n          <a-input v-model=\"form.name\" />\n        </a-form-item>\n        <a-form-item label=\" \">\n          <a-radio-group defaultValue=\"a\" buttonStyle=\"solid\" v-model=\"form.openType\">\n            <a-radio-button v-for=\"item in menuTypes\" :key=\"item\" :value=\"item\">{{ item }}</a-radio-button>\n          </a-radio-group>\n        </a-form-item>\n        <a-form-item label=\"Link\">\n          <a-input v-model=\"form.link\" class=\"link-input\" placeholder=\"输入或从下面选择\"></a-input>\n          <a-select v-model=\"form.link\">\n            <a-select-option v-for=\"item in menuLinks\" :key=\"item.value\" :value=\"item.value\">{{ item.text }}</a-select-option>\n          </a-select>\n        </a-form-item>\n      </a-form>\n      <div\n        :style=\"{\n          position: 'absolute',\n          left: 0,\n          bottom: 0,\n          width: '100%',\n          padding: '10px 16px',\n          background: '#fff',\n          textAlign: 'right',\n        }\"\n      >\n        <a-button\n          :style=\"{marginRight: '8px'}\"\n          @click=\"close\"\n        >\n          {{ $t('cancel') }}\n        </a-button>\n        <a-button type=\"primary\" :disabled=\"!canSubmit\" @click=\"saveMenu\">{{ $t('save') }}</a-button>\n      </div>\n    </a-drawer>\n  </div>\n</template>\n\n<script lang=\"ts\">\nimport { ipcRenderer, IpcRendererEvent } from 'electron'\nimport { Vue, Component } from 'vue-property-decorator'\nimport { State } from 'vuex-class'\nimport urlJoin from 'url-join'\nimport Draggable from 'vuedraggable'\nimport { MenuTypes } from '../../helpers/enums'\nimport { IMenu } from '../../interfaces/menu'\nimport { IPost } from '../../interfaces/post'\nimport ga from '../../helpers/analytics'\n\ninterface IForm {\n  name: any\n  index: any\n  openType: string\n  link: string\n}\n\n@Component({\n  components: {\n    Draggable,\n  },\n})\nexport default class Menu extends Vue {\n  @State('site') site!: any\n\n  form: IForm = {\n    name: '',\n    index: '',\n    openType: MenuTypes.Internal,\n    link: '',\n  }\n\n  menuList: any = []\n\n  draggleList: any = []\n\n  visible = false\n\n  menuTypes = MenuTypes\n\n  get menuLinks() {\n    const { setting, themeConfig } = this.site\n    const posts = this.site.posts.map((item: IPost) => {\n      return {\n        text: `📄 ${item.data.title}`,\n        value: urlJoin(setting.domain, themeConfig.postPath, item.fileName),\n      }\n    })\n    return [\n      {\n        text: '🏠 Homepage',\n        value: setting.domain,\n      },\n      {\n        text: '📚 Archives',\n        value: urlJoin(setting.domain, themeConfig.archivesPath),\n      },\n      {\n        text: '🏷️ Tags',\n        value: urlJoin(setting.domain, 'tags'),\n      },\n      ...posts,\n    ]\n  }\n\n  get canSubmit() {\n    return this.form.name && this.form.link\n  }\n\n  mounted() {\n    this.menuList = [...this.site.menus]\n  }\n\n  newMenu() {\n    this.form.name = null\n    this.form.index = null\n    this.form.openType = MenuTypes.Internal\n    this.form.link = ''\n    this.visible = true\n\n    ga.event('Menu', 'Menu - new', { evLabel: this.site.setting.domain })\n  }\n\n  close() {\n    this.visible = false\n  }\n\n  editMenu(menu: IMenu, index: number) {\n    this.visible = true\n    this.form.index = index\n    this.form.name = menu.name\n    this.form.openType = menu.openType\n    this.form.link = menu.link\n  }\n\n  saveMenu() {\n    ipcRenderer.send('menu-save', { ...this.form })\n    ipcRenderer.once('menu-saved', (event: IpcRendererEvent, result: any) => {\n      if (typeof this.form.index !== 'number') {\n        this.menuList.push({ ...this.form })\n      } else {\n        this.menuList[this.form.index] = { ...this.form }\n      }\n      this.$bus.$emit('site-reload')\n      this.$message.success(this.$t('menuSuccess'))\n      this.visible = false\n\n      ga.event('Menu', 'Menu - save', { evLabel: this.form.name })\n    })\n  }\n\n  async deleteMenu(menuValue: string) {\n    this.$confirm({\n      title: `${this.$t('warning')}`,\n      content: `${this.$t('deleteWarning')}`,\n      okText: 'Yes',\n      okType: 'danger',\n      cancelText: 'No',\n      onOk: () => {\n        ipcRenderer.send('menu-delete', menuValue)\n        ipcRenderer.once('menu-deleted', (event: IpcRendererEvent, result: any) => {\n          const foundIndex = this.menuList.findIndex((item: IMenu) => item.name === menuValue)\n          this.menuList.splice(foundIndex, 1)\n\n          this.$bus.$emit('site-reload')\n          this.$message.success(this.$t('menuDelete'))\n          this.visible = false\n        })\n      },\n    })\n  }\n\n  async handleMenuSort() {\n    ipcRenderer.send('menu-sort', this.menuList)\n    ipcRenderer.once('menu-sorted', (event: IpcRendererEvent, result: any) => {\n      this.$bus.$emit('site-reload')\n      this.$message.success(this.$t('menuSuccess'))\n      ga.event('Menu', 'Menu - sort', { evLabel: '' })\n    })\n  }\n}\n</script>\n\n<style lang=\"less\" scoped>\n.link-input {\n  margin-bottom: 8px;\n}\n.menu-icon {\n  font-size: 18px;\n}\n.menu-container {\n  box-shadow: inset 0 0 0 1px #eaeaea;\n}\n.menu-title {\n  color: #373530;\n  &:hover {\n    color: #3687eb;\n  }\n}\n.delete-icon {\n  padding: 4px 8px;\n  &:hover {\n    color: #fa5252;\n    cursor: pointer;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/views/setting/Index.vue",
    "content": "<template>\n  <div class=\"\">\n    <a-tabs class=\"menu-tab\" defaultActiveKey=\"1\" :animated=\"false\">\n      <a-tab-pane :tab=\"$t('basicSetting')\" key=\"1\">\n        <basic-setting></basic-setting>\n      </a-tab-pane>\n      <a-tab-pane :tab=\"$t('commentSetting')\" key=\"2\">\n        <comment-setting></comment-setting>\n      </a-tab-pane>\n    </a-tabs>\n  </div>\n</template>\n\n<script lang=\"ts\">\nimport { ipcRenderer, IpcRendererEvent } from 'electron'\nimport { Vue, Component } from 'vue-property-decorator'\nimport { State } from 'vuex-class'\nimport BasicSetting from './includes/BasicSetting.vue'\nimport CommentSetting from './includes/CommentSetting.vue'\n\n@Component({\n  components: {\n    BasicSetting,\n    CommentSetting,\n  },\n})\nexport default class Setting extends Vue {\n\n}\n</script>\n\n<style lang=\"less\" scoped>\n</style>\n"
  },
  {
    "path": "src/views/setting/includes/BasicSetting.vue",
    "content": "<template>\n  <div>\n    <a-form :form=\"form\" style=\"padding-bottom: 48px;\">\n      <a-form-item :label=\"$t('platform')\" :labelCol=\"formLayout.label\" :wrapperCol=\"formLayout.wrapper\" :colon=\"false\">\n        <a-radio-group name=\"platform\" v-model=\"form.platform\">\n          <a-radio value=\"github\">Github Pages</a-radio>\n          <a-radio value=\"netlify\">Netlify</a-radio>\n          <a-radio value=\"coding\">Coding Pages</a-radio>\n          <a-radio value=\"gitee\">Gitee Pages</a-radio>\n          <a-radio value=\"sftp\">SFTP</a-radio>\n        </a-radio-group>\n      </a-form-item>\n      <a-form-item :label=\"$t('domain')\" :labelCol=\"formLayout.label\" :wrapperCol=\"formLayout.wrapper\" :colon=\"false\">\n        <a-input-group compact>\n          <a-select v-model=\"protocol\" style=\"width: 96px\">\n            <a-select-option value=\"https://\">https://</a-select-option>\n            <a-select-option value=\"http://\">http://</a-select-option>\n          </a-select>\n          <a-input v-model=\"form.domain\" placeholder=\"mydomain.com\" style=\"width: calc(100% - 96px);\" />\n        </a-input-group>\n      </a-form-item>\n      <template v-if=\"['netlify'].includes(form.platform)\">\n        <a-form-item label=\"Site ID\" :labelCol=\"formLayout.label\" :wrapperCol=\"formLayout.wrapper\" :colon=\"false\">\n          <a-input v-model=\"form.netlifySiteId\" />\n        </a-form-item>\n        <a-form-item label=\"Access Token\" :labelCol=\"formLayout.label\" :wrapperCol=\"formLayout.wrapper\" :colon=\"false\" v-if=\"remoteType === 'password'\">\n          <a-input v-model=\"form.netlifyAccessToken\" :type=\"passVisible ? '' : 'password'\">\n            <a-icon class=\"icon\" slot=\"addonAfter\" :type=\"passVisible ? 'eye-invisible' : 'eye'\" @click=\"passVisible = !passVisible\" />\n          </a-input>\n        </a-form-item>\n        <a-form-item label=\" \" :labelCol=\"formLayout.label\" :wrapperCol=\"formLayout.wrapper\" :colon=\"false\">\n          <a href=\"https://gridea.dev/netlify\" target=\"_blank\">如何配置？</a>\n        </a-form-item>\n      </template>\n      <template v-if=\"['github', 'coding', 'gitee'].includes(form.platform)\">\n        <a-form-item :label=\"$t('repository')\" :labelCol=\"formLayout.label\" :wrapperCol=\"formLayout.wrapper\" :colon=\"false\">\n          <a-input v-model=\"form.repository\" />\n        </a-form-item>\n        <a-form-item :label=\"$t('branch')\" :labelCol=\"formLayout.label\" :wrapperCol=\"formLayout.wrapper\" :colon=\"false\">\n          <a-input v-model=\"form.branch\" placeholder=\"master\" />\n        </a-form-item>\n        <a-form-item :label=\"$t('username')\" :labelCol=\"formLayout.label\" :wrapperCol=\"formLayout.wrapper\" :colon=\"false\">\n          <a-input v-model=\"form.username\" />\n        </a-form-item>\n        <a-form-item :label=\"$t('email')\" :labelCol=\"formLayout.label\" :wrapperCol=\"formLayout.wrapper\" :colon=\"false\">\n          <a-input v-model=\"form.email\" />\n        </a-form-item>\n        <a-form-item v-if=\"form.platform === 'coding'\" :label=\"$t('tokenUsername')\" :labelCol=\"formLayout.label\" :wrapperCol=\"formLayout.wrapper\" :colon=\"false\">\n          <a-input v-model=\"form.tokenUsername\" />\n        </a-form-item>\n        <a-form-item :label=\"$t('token')\" :labelCol=\"formLayout.label\" :wrapperCol=\"formLayout.wrapper\" :colon=\"false\">\n          <a-input :type=\"passVisible ? '' : 'password'\" v-model=\"form.token\">\n            <a-icon class=\"icon\" slot=\"addonAfter\" :type=\"passVisible ? 'eye-invisible' : 'eye'\" @click=\"passVisible = !passVisible\" />\n          </a-input>\n        </a-form-item>\n        <a-form-item label=\"CNAME\" :labelCol=\"formLayout.label\" :wrapperCol=\"formLayout.wrapper\" :colon=\"false\">\n          <a-input v-model=\"form.cname\" placeholder=\"mydomain.com\" />\n        </a-form-item>\n      </template>\n      <template v-if=\"['sftp'].includes(form.platform)\">\n        <a-form-item label=\"Port\" :labelCol=\"formLayout.label\" :wrapperCol=\"formLayout.wrapper\" :colon=\"false\">\n          <a-input-number v-model=\"form.port\" />\n        </a-form-item>\n        <a-form-item label=\"Server\" :labelCol=\"formLayout.label\" :wrapperCol=\"formLayout.wrapper\" :colon=\"false\">\n          <a-input v-model=\"form.server\" />\n        </a-form-item>\n        <a-form-item label=\"Username\" :labelCol=\"formLayout.label\" :wrapperCol=\"formLayout.wrapper\" :colon=\"false\">\n          <a-input v-model=\"form.username\" />\n        </a-form-item>\n        <a-form-item label=\"Connect Type\" :labelCol=\"formLayout.label\" :wrapperCol=\"formLayout.wrapper\" :colon=\"false\">\n          <a-radio-group name=\"remoteType\" v-model=\"remoteType\">\n            <a-radio value=\"password\">Password</a-radio>\n            <a-radio value=\"key\">SSH Key</a-radio>\n          </a-radio-group>\n        </a-form-item>\n        <a-form-item label=\"Password\" :labelCol=\"formLayout.label\" :wrapperCol=\"formLayout.wrapper\" :colon=\"false\" v-if=\"remoteType === 'password'\">\n          <a-input v-model=\"form.password\" :type=\"passVisible ? '' : 'password'\">\n            <a-icon class=\"icon\" slot=\"addonAfter\" :type=\"passVisible ? 'eye-invisible' : 'eye'\" @click=\"passVisible = !passVisible\" />\n          </a-input>\n        </a-form-item>\n        <a-form-item label=\"Private Key Path\" :labelCol=\"formLayout.label\" :wrapperCol=\"formLayout.wrapper\" :colon=\"false\" :help=\"$t('privateKeyTip')\" v-else>\n          <a-input v-model=\"form.privateKey\" />\n        </a-form-item>\n        <a-form-item label=\"Remote Path\" :labelCol=\"formLayout.label\" :wrapperCol=\"formLayout.wrapper\" :colon=\"false\" :help=\"$t('remotePathTip')\">\n          <a-input v-model=\"form.remotePath\" />\n        </a-form-item>\n      </template>\n      <a-form-item v-if=\"form.platform !== 'sftp'\" :label=\"$t('Proxy')\" :labelCol=\"formLayout.label\" :wrapperCol=\"formLayout.wrapper\" :colon=\"false\">\n        <a-radio-group name=\"Proxy\" v-model=\"form.enabledProxy\">\n          <a-radio value=\"direct\">Direct</a-radio>\n          <a-radio value=\"proxy\">Proxy</a-radio>\n        </a-radio-group>\n      </a-form-item>\n      <template v-if=\"['proxy'].includes(form.enabledProxy) && form.platform !== 'sftp'\">\n        <a-form-item :label=\"$t('ProxyAddress')\" :labelCol=\"formLayout.label\" :wrapperCol=\"formLayout.wrapper\" :colon=\"false\">\n          <a-input-group compact>\n            <a-input v-model=\"form.proxyPath\" />\n          </a-input-group>\n        </a-form-item>\n        <a-form-item :label=\"$t('ProxyPort')\" :labelCol=\"formLayout.label\" :wrapperCol=\"formLayout.wrapper\" :colon=\"false\">\n          <a-input v-model=\"form.proxyPort\" />\n        </a-form-item>\n      </template>\n      <footer-box>\n        <div class=\"flex justify-between\">\n          <a-button :disabled=\"!canSubmit\" :loading=\"detectLoading\" @click=\"remoteDetect\" style=\"margin-right: 16px;\">{{ $t('testConnection') }}</a-button>\n          <a-button :disabled=\"!canSubmit\" @click=\"submit\" type=\"primary\">{{ $t('save') }}</a-button>\n        </div>\n      </footer-box>\n    </a-form>\n  </div>\n</template>\n\n<script lang=\"ts\">\nimport { ipcRenderer, IpcRendererEvent } from 'electron'\nimport { Vue, Component, Watch } from 'vue-property-decorator'\nimport { State } from 'vuex-class'\nimport FooterBox from '../../../components/FooterBox/Index.vue'\nimport ga from '../../../helpers/analytics'\nimport { ISetting } from '../../../interfaces/setting'\n\n@Component({\n  components: {\n    FooterBox,\n  },\n})\nexport default class BasicSetting extends Vue {\n  @State('site') site!: any\n\n  passVisible = false\n\n  detectLoading = false\n\n  formLayout = {\n    label: { span: 6 },\n    wrapper: { span: 12 },\n  }\n\n  protocol = 'https://'\n\n  form: ISetting = {\n    platform: 'github',\n    domain: '',\n    repository: '',\n    branch: '',\n    username: '',\n    email: '',\n    tokenUsername: '',\n    token: '',\n    cname: '',\n    port: '22',\n    server: '',\n    password: '',\n    privateKey: '',\n    remotePath: '',\n    proxyPath: '',\n    proxyPort: '',\n    enabledProxy: 'direct',\n    netlifyAccessToken: '',\n    netlifySiteId: '',\n  }\n\n  remoteType = 'password'\n\n  get canSubmit() {\n    const { form } = this\n    const baseValid = form.domain\n      && form.repository\n      && form.branch\n      && form.username\n      && form.token\n    const pagesPlatfomValid = baseValid && (form.platform === 'gitee' || form.platform === 'github' || (form.platform === 'coding' && form.tokenUsername))\n\n    const sftpPlatformValid = ['sftp'].includes(form.platform)\n      && form.port\n      && form.server\n      && form.username\n      && form.remotePath\n      && (form.password || form.privateKey)\n\n    const netlifyPlatformValid = ['netlify'].includes(form.platform)\n      && form.netlifyAccessToken\n      && form.netlifySiteId\n\n    return pagesPlatfomValid || sftpPlatformValid || netlifyPlatformValid\n  }\n\n  mounted() {\n    const { form, site: { setting } } = this\n    console.log('setting', setting)\n    Object.keys(form).forEach((key: string) => {\n      if (key === 'domain') {\n        const protocolEndIndex = setting[key].indexOf('://')\n        if (protocolEndIndex !== -1) {\n          form[key] = setting[key].substring(protocolEndIndex + 3)\n          this.protocol = setting[key].substring(0, protocolEndIndex + 3)\n        }\n      } else {\n        form[key] = setting[key]\n      }\n    })\n\n    if (form.privateKey) {\n      this.remoteType = 'key'\n    }\n  }\n\n  // /**\n  //  * check form validate\n  //  * @returns {boolean}\n  //  */\n  // checkFormValid() {\n  //   // if (!['https://', 'http://'].some(d => this.form.domain.startsWith(d))) {\n  //   //   this.$message.warn(this.$t('domainShouldStartsWithWarn'))\n  //   //   return false\n  //   // }\n  //   return true\n  // }\n\n  submit() {\n    // const formValid = this.checkFormValid()\n    // if (!formValid) { return false }\n\n    const form = {\n      ...this.form,\n      domain: `${this.protocol}${this.form.domain}`,\n    }\n\n    if (this.remoteType === 'password') {\n      form.privateKey = ''\n    } else {\n      form.password = ''\n    }\n\n    ipcRenderer.send('setting-save', form)\n    ipcRenderer.once('setting-saved', (event: IpcRendererEvent, result: any) => {\n      this.$bus.$emit('site-reload')\n      this.$message.success(this.$t('basicSettingSuccess'))\n\n      ga.event('Setting', 'Setting - save', { evLabel: this.form.platform })\n    })\n  }\n\n  async remoteDetect() {\n    const form = {\n      ...this.form,\n      domain: `${this.protocol}${this.form.domain}`,\n    }\n\n    ipcRenderer.send('setting-save', form)\n    ipcRenderer.once('setting-saved', () => {\n      ipcRenderer.send('app-site-reload')\n      ipcRenderer.once('app-site-loaded', () => {\n        this.detectLoading = true\n        ipcRenderer.send('remote-detect')\n\n        ga.event('Setting', 'Setting - detect', { evLabel: this.form.platform })\n        ipcRenderer.once('remote-detected', (event: IpcRendererEvent, result: any) => {\n          console.log('检测结果', result)\n          this.detectLoading = false\n          if (result.success) {\n            this.$message.success(this.$t('connectSuccess'))\n\n            ga.event('Setting', 'Setting - detect-success', { evLabel: this.form.platform })\n          } else {\n            this.$message.error(this.$t('connectFailed'))\n\n            ga.event('Setting', 'Setting - detect-failed', { evLabel: this.form.platform })\n          }\n        })\n      })\n    })\n  }\n\n  @Watch('form.token')\n  onTokenChanged(val: string) {\n    this.form.token = this.form.token.trim()\n  }\n}\n</script>\n\n\n<style lang=\"less\" scoped>\n.icon {\n  cursor: pointer;\n}\n</style>\n"
  },
  {
    "path": "src/views/setting/includes/CommentSetting.vue",
    "content": "<template>\n  <div>\n    <a-form :form=\"form\" style=\"padding-bottom: 48px;\">\n      <a-form-item :label=\"$t('platform')\" :labelCol=\"formLayout.label\" :wrapperCol=\"formLayout.wrapper\" :colon=\"false\">\n        <a-radio-group name=\"commentPlatform\" v-model=\"form.commentPlatform\">\n          <a-radio value=\"gitalk\">Gitalk</a-radio>\n          <a-radio value=\"disqus\">Disqus</a-radio>\n        </a-radio-group>\n      </a-form-item>\n      <a-form-item :label=\"$t('isShowComment')\" :labelCol=\"formLayout.label\" :wrapperCol=\"formLayout.wrapper\" :colon=\"false\">\n        <a-switch v-model=\"form.showComment\" />\n      </a-form-item>\n      <gitalk-setting ref=\"gitalkSetting\" v-show=\"form.commentPlatform === 'gitalk'\"></gitalk-setting>\n      <disqus-setting ref=\"disqusSetting\" v-show=\"form.commentPlatform === 'disqus'\"></disqus-setting>\n      <footer-box>\n        <div class=\"flex justify-end\">\n          <a-button @click=\"submit\" type=\"primary\">{{ $t('save') }}</a-button>\n        </div>\n      </footer-box>\n    </a-form>\n  </div>\n</template>\n\n<script lang=\"ts\">\nimport { ipcRenderer, IpcRendererEvent } from 'electron'\nimport { Vue, Component } from 'vue-property-decorator'\nimport { State } from 'vuex-class'\nimport GitalkSetting from './GitalkSetting.vue'\nimport DisqusSetting from './DisqusSetting.vue'\nimport FooterBox from '../../../components/FooterBox/Index.vue'\nimport ga from '../../../helpers/analytics'\n\n@Component({\n  components: {\n    GitalkSetting,\n    DisqusSetting,\n    FooterBox,\n  },\n})\nexport default class CommentSetting extends Vue {\n  @State('site') site!: any\n\n  $refs!: {\n    gitalkSetting: HTMLFormElement,\n    disqusSetting: HTMLFormElement,\n  }\n\n  formLayout = {\n    label: { span: 6 },\n    wrapper: { span: 12 },\n  }\n\n  form = {\n    commentPlatform: 'gitalk',\n    showComment: false,\n  }\n\n  mounted() {\n    const { commentSetting } = this.site\n    this.form.commentPlatform = commentSetting.commentPlatform\n    this.form.showComment = commentSetting.showComment\n  }\n\n  submit() {\n    const form = {\n      ...this.form,\n      gitalkSetting: this.$refs.gitalkSetting.form,\n      disqusSetting: this.$refs.disqusSetting.form,\n    }\n    console.log('click comment setting save', form)\n    ipcRenderer.send('comment-setting-save', form)\n    ipcRenderer.once('comment-setting-saved', (event: IpcRendererEvent, result: any) => {\n      this.$bus.$emit('site-reload')\n      this.$message.success(this.$t('commentSettingSuccess'))\n\n      ga.event('Setting', 'Setting - comment-save', { evLabel: this.form.commentPlatform })\n    })\n  }\n}\n</script>\n\n\n<style lang=\"less\" scoped>\n</style>\n"
  },
  {
    "path": "src/views/setting/includes/DisqusSetting.vue",
    "content": "<template>\n  <div>\n    <a-form-item label=\" \" :labelCol=\"formLayout.label\" :wrapperCol=\"formLayout.wrapper\" :colon=\"false\">\n      <a @click.prevent=\"openPage('https://github.com/SukkaW/DisqusJS')\">DisqusJS Document</a>\n    </a-form-item>\n    <a-form-item label=\"shortname\" :labelCol=\"formLayout.label\" :wrapperCol=\"formLayout.wrapper\" :colon=\"false\">\n      <a-input v-model=\"form.shortname\"></a-input>\n    </a-form-item>\n    <a-form-item label=\"apikey\" :labelCol=\"formLayout.label\" :wrapperCol=\"formLayout.wrapper\" :colon=\"false\">\n      <a-input v-model=\"form.apikey\"></a-input>\n    </a-form-item>\n    <a-form-item label=\"api\" :labelCol=\"formLayout.label\" :wrapperCol=\"formLayout.wrapper\" :colon=\"false\">\n      <a-input v-model=\"form.api\" placeholder=\"default: https://disqus.skk.moe/disqus/\"></a-input>\n    </a-form-item>\n  </div>\n</template>\n\n<script lang=\"ts\">\nimport { ipcRenderer, IpcRendererEvent, shell } from 'electron'\nimport Vue from 'vue'\nimport Component from 'vue-class-component'\nimport { State } from 'vuex-class'\n\n@Component\nexport default class DisqusSetting extends Vue {\n  @State('site') site!: any\n\n  formLayout = {\n    label: { span: 6 },\n    wrapper: { span: 12 },\n  }\n\n  form = {\n    shortname: '',\n    api: '',\n    apikey: '',\n  }\n\n  mounted() {\n    const { disqusSetting } = this.site.commentSetting\n\n    this.form.shortname = disqusSetting.shortname\n    this.form.api = disqusSetting.api\n    this.form.apikey = disqusSetting.apikey\n  }\n\n  openPage(url: string) {\n    shell.openExternal(url)\n  }\n}\n</script>\n\n\n<style lang=\"less\" scoped>\n</style>\n"
  },
  {
    "path": "src/views/setting/includes/GitalkSetting.vue",
    "content": "<template>\n  <div>\n    <a-form-item label=\" \" :labelCol=\"formLayout.label\" :wrapperCol=\"formLayout.wrapper\" :colon=\"false\">\n      <a @click.prevent=\"openPage('https://github.com/gitalk/gitalk')\">Gitalk Document</a>\n    </a-form-item>\n    <a-form-item label=\"Client ID\" :labelCol=\"formLayout.label\" :wrapperCol=\"formLayout.wrapper\" :colon=\"false\">\n      <a-input v-model=\"form.clientId\"></a-input>\n    </a-form-item>\n    <a-form-item label=\"Client Secret\" :labelCol=\"formLayout.label\" :wrapperCol=\"formLayout.wrapper\" :colon=\"false\">\n      <a-input v-model=\"form.clientSecret\"></a-input>\n    </a-form-item>\n    <a-form-item :label=\"$t('repository')\" :labelCol=\"formLayout.label\" :wrapperCol=\"formLayout.wrapper\" :colon=\"false\">\n      <a-input v-model=\"form.repository\"></a-input>\n    </a-form-item>\n    <a-form-item label=\"Owner\" :labelCol=\"formLayout.label\" :wrapperCol=\"formLayout.wrapper\" :colon=\"false\">\n      <a-input v-model=\"form.owner\"></a-input>\n    </a-form-item>\n  </div>\n</template>\n\n<script lang=\"ts\">\nimport { ipcRenderer, IpcRendererEvent, shell } from 'electron'\nimport { Vue, Component } from 'vue-property-decorator'\nimport { State } from 'vuex-class'\n\n@Component\nexport default class GitalkSetting extends Vue {\n  @State('site') site!: any\n\n  formLayout = {\n    label: { span: 6 },\n    wrapper: { span: 12 },\n  }\n\n  form = {\n    clientId: '',\n    clientSecret: '',\n    repository: '',\n    owner: '',\n  }\n\n  mounted() {\n    const { gitalkSetting } = this.site.commentSetting\n\n    this.form.clientId = gitalkSetting.clientId\n    this.form.clientSecret = gitalkSetting.clientSecret\n    this.form.repository = gitalkSetting.repository\n    this.form.owner = gitalkSetting.owner\n  }\n\n  openPage(url: string) {\n    shell.openExternal(url)\n  }\n}\n</script>\n\n\n<style lang=\"less\" scoped>\n</style>\n"
  },
  {
    "path": "src/views/tags/Index.vue",
    "content": "<template>\n  <div class=\"\">\n    <a-row type=\"flex\" justify=\"end\" class=\"tool-container\">\n      <a-tooltip placement=\"bottom\" :title=\"$t('newTag')\">\n        <div class=\"op-btn\" tabindex=\"0\" @click=\"newTag\">\n          <i class=\"zwicon-plus\"></i>\n        </div>\n      </a-tooltip>\n    </a-row>\n    <div class=\"content-container\">\n      <div v-for=\"(tag, index) in site.tags\" :key=\"tag.name\" class=\"tag-wrapper\">\n        <div class=\"tag\" @click=\"tag.used ? null : updateTag(tag, index)\"><i class=\"zwicon-price-tag text-base mr-1\"></i> {{ tag.name }}</div>\n        <i class=\"zwicon-trash delete-icon\" v-if=\"!tag.used\" @click=\"handleDelete(tag.name)\"></i>\n      </div>\n    </div>\n    <a-drawer\n      :title=\"$t('tag')\"\n      width=\"400\"\n      :visible=\"visible\"\n      @close=\"close\"\n      :wrapStyle=\"{height: 'calc(100% - 108px)',overflow: 'auto',paddingBottom: '108px'}\"\n    >\n      <a-form :form=\"form\" layout=\"vertical\">\n        <a-form-item :label=\"$t('tagName')\">\n          <a-input v-model=\"form.name\" @input=\"handleNameChange\" />\n        </a-form-item>\n        <a-form-item label=\"标签 URL\">\n          <a-input v-model=\"form.slug\" @input=\"handleSlugChange\" />\n        </a-form-item>\n      </a-form>\n      <div\n        :style=\"{\n          position: 'absolute',\n          left: 0,\n          bottom: 0,\n          width: '100%',\n          padding: '10px 16px',\n          background: '#fff',\n          textAlign: 'right',\n        }\"\n      >\n        <a-button\n          :style=\"{marginRight: '8px'}\"\n          @click=\"close\"\n        >\n          {{ $t('cancel') }}\n        </a-button>\n        <a-button type=\"primary\" :disabled=\"!canSubmit\" @click=\"saveTag\">{{ $t('save') }}</a-button>\n      </div>\n    </a-drawer>\n  </div>\n</template>\n\n<script lang=\"ts\">\nimport { ipcRenderer, IpcRendererEvent } from 'electron'\nimport { Vue, Component } from 'vue-property-decorator'\nimport { State } from 'vuex-class'\nimport shortid from 'shortid'\nimport slug from '../../helpers/slug'\nimport { Site } from '../../store/modules/site'\nimport { UrlFormats } from '../../helpers/enums'\nimport { ITag } from '../../interfaces/tag'\nimport ga from '../../helpers/analytics'\n\n@Component\nexport default class Tags extends Vue {\n  @State('site') site!: Site\n\n  visible = false\n\n  isUpdate = false\n\n  form = {\n    name: null,\n    slug: '',\n    index: -1,\n  }\n\n  slugChanged = false\n\n  get canSubmit() {\n    return this.form.name\n  }\n\n  handleNameChange(val: string) {\n    if (!this.slugChanged && this.site.themeConfig.tagUrlFormat === UrlFormats.Slug) {\n      this.form.slug = slug(this.form.name)\n    }\n  }\n\n  handleSlugChange(val: string) {\n    this.slugChanged = !!val\n  }\n\n  close() {\n    this.visible = false\n  }\n\n  newTag() {\n    this.form.name = null\n    this.form.index = -1\n    this.form.slug = ''\n    this.visible = true\n    this.isUpdate = false\n    if (this.site.themeConfig.tagUrlFormat === UrlFormats.ShortId) {\n      this.form.slug = shortid.generate()\n    }\n\n    ga.event('Tags', 'Tags - new', { evLabel: this.site.setting.domain })\n  }\n\n  buildSlug() {\n    if (this.form.slug === '') {\n      if (this.site.themeConfig.tagUrlFormat === UrlFormats.Slug) {\n        this.form.slug = slug(this.form.name)\n      }\n      if (this.site.themeConfig.tagUrlFormat === UrlFormats.ShortId) {\n        this.form.slug = shortid.generate()\n      }\n    }\n  }\n\n  updateTag(tag: any, index: number) {\n    console.log(tag)\n    this.visible = true\n    this.isUpdate = true\n    this.form.name = tag.name\n    this.form.slug = tag.slug\n    this.form.index = index\n  }\n\n  /**\n   * 检查标签合法性\n   * 若是新增，则 slug 和 name 都不允许和已有的重复\n   * 若是编辑，则 slug 和 name 都不允许和已有的其他标签重复\n   */\n  checkTagValid() {\n    const siteTags = this.site.tags\n\n    const tags = JSON.parse(JSON.stringify(siteTags))\n    if (this.isUpdate) {\n      tags.splice(this.form.index, 1)\n    }\n\n\n    const foundTagIndex = tags.findIndex((tag: ITag) => tag.name === this.form.name || tag.slug === this.form.slug)\n    if (foundTagIndex !== -1) {\n      return false\n    }\n\n    return true\n  }\n\n  saveTag() {\n    this.buildSlug()\n\n    const valid = this.checkTagValid()\n    if (!valid) {\n      this.$message.error('标签的名称或 URL 与其他标签重复')\n      return\n    }\n\n    ipcRenderer.send('tag-save', { ...this.form, used: false })\n    ipcRenderer.once('tag-saved', (event: IpcRendererEvent, result: any) => {\n      this.$bus.$emit('site-reload')\n      this.$message.success('标签已保存')\n      this.visible = false\n\n      ga.event('Tags', 'Tags - save', { evLabel: this.form.name })\n    })\n  }\n\n  async handleDelete(tagValue: string) {\n    this.$confirm({\n      title: `${this.$t('warning')}`,\n      content: `${this.$t('deleteWarning')}`,\n      okText: 'Yes',\n      okType: 'danger',\n      cancelText: 'No',\n      onOk: () => {\n        ipcRenderer.send('tag-delete', tagValue)\n        ipcRenderer.once('tag-deleted', (event: IpcRendererEvent, result: any) => {\n          this.$bus.$emit('site-reload')\n          this.$message.success('标签已删除')\n          this.visible = false\n        })\n      },\n    })\n  }\n}\n</script>\n\n<style lang=\"less\" scoped>\n.content-container {\n  background: transparent;\n}\n\n.tag-wrapper {\n  display: inline-flex;\n  margin-right: 24px;\n  margin-bottom: 16px;\n  align-items: center;\n  border: 1px solid #e8e8e8;\n  border-radius: 20px;\n  transition: all 0.3s;\n  &:hover {\n    // box-shadow: 0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -1px rgba(0,0,0,.06)!important;\n    @apply shadow-lg;\n  }\n  .tag {\n    font-size: 12px;\n    margin-right: 0px;\n    border-radius: 0;\n    padding: 6px 16px 6px 12px;\n    cursor: default;\n    &:not(:last-child) {\n      cursor: pointer;\n      border-right: 1px solid #e8e8e8;\n    }\n  }\n}\n.delete-icon {\n  padding: 4px 8px;\n  &:hover {\n    color: #fa5252;\n    cursor: pointer;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/views/theme/Index.vue",
    "content": "<template>\n  <div class=\"\">\n    <a-tabs class=\"menu-tab\" defaultActiveKey=\"basic\" v-model=\"currentTab\" forceRender :animated=\"false\">\n      <a-tab-pane :tab=\"$t('basicSetting')\" key=\"basic\">\n        <basic-setting></basic-setting>\n      </a-tab-pane>\n      <a-tab-pane :tab=\"$t('customConfig')\" key=\"custom\">\n        <custom-setting></custom-setting>\n      </a-tab-pane>\n      <a-tab-pane :tab=\"$t('faviconSetting')\" key=\"favicon\">\n        <favicon-setting></favicon-setting>\n      </a-tab-pane>\n      <a-tab-pane :tab=\"$t('avatarSetting')\" key=\"avatar\">\n        <avatar-setting></avatar-setting>\n      </a-tab-pane>\n    </a-tabs>\n  </div>\n</template>\n\n<script lang=\"ts\">\nimport { Vue, Component } from 'vue-property-decorator'\nimport BasicSetting from './includes/BasicSetting.vue'\nimport CustomSetting from './includes/CustomSetting.vue'\nimport FaviconSetting from './includes/FaviconSetting.vue'\nimport AvatarSetting from './includes/AvatarSetting.vue'\n\n@Component({\n  name: 'Theme',\n  components: {\n    BasicSetting,\n    CustomSetting,\n    FaviconSetting,\n    AvatarSetting,\n  },\n})\nexport default class Theme extends Vue {\n  currentTab = 'basic'\n\n  mounted() {\n    const { tab } = this.$route.query\n    if (tab && typeof tab === 'string') {\n      this.currentTab = tab\n      console.log(this.currentTab)\n    }\n  }\n}\n</script>\n\n<style lang=\"less\" scoped>\n</style>\n"
  },
  {
    "path": "src/views/theme/includes/AvatarSetting.vue",
    "content": "<template>\n  <div>\n    <a-upload\n      action=\"\"\n      listType=\"picture-card\"\n      class=\"feature-uploader\"\n      :showUploadList=\"false\"\n      :beforeUpload=\"beforeUpload\"\n    >\n      <div v-if=\"avatarPath\">\n        <img class=\"favicon-image\" :src=\"avatarPath\" width=\"88px\" height=\"88px\" />\n      </div>\n      <div v-else>\n        <a-icon type=\"plus\" />\n        <div class=\"ant-upload-text\">Upload</div>\n      </div>\n    </a-upload>\n    <div class=\"tip-text\" v-if=\"file\">\n      {{ file.path }}\n    </div>\n    <footer-box>\n      <div class=\"flex justify-end\">\n        <a-button type=\"primary\" @click=\"submit\">{{ $t('save') }}</a-button>\n      </div>\n    </footer-box>\n  </div>\n</template>\n\n<script lang=\"ts\">\nimport { ipcRenderer, IpcRendererEvent } from 'electron'\nimport { Vue, Component } from 'vue-property-decorator'\nimport { State } from 'vuex-class'\nimport * as path from 'path'\nimport FooterBox from '../../../components/FooterBox/Index.vue'\n\n@Component({\n  components: {\n    FooterBox,\n  },\n})\nexport default class AvatarSetting extends Vue {\n  @State('site') site!: any\n\n  file: any = null\n\n  avatarPath = ''\n\n  mounted() {\n    this.avatarPath = path.join('file://', this.site.appDir, 'images', `avatar.png?a=${Math.random()}`)\n  }\n\n  beforeUpload(file: any) {\n    if (!file) {\n      return\n    }\n    const isImage = file.type.indexOf('image') !== -1\n    if (!isImage) {\n      return\n    }\n    if (file && isImage) {\n      this.file = file\n    }\n    return false\n  }\n\n  submit() {\n    if (!this.file) {\n      return\n    }\n    console.log('click avatar upload', this.file)\n    ipcRenderer.send('avatar-upload', this.file.path)\n    ipcRenderer.once('avatar-uploaded', (event: IpcRendererEvent, result: any) => {\n      this.file = null\n      this.$bus.$emit('site-reload')\n      this.avatarPath = path.join('file://', this.site.appDir, 'images', `avatar.png?a=${Math.random()}`)\n      this.$message.success(this.$t('avatarSettingSuccess'))\n    })\n  }\n}\n</script>\n\n\n<style lang=\"less\" scoped>\n.file-name {\n  margin-bottom: 8px;\n}\n</style>\n"
  },
  {
    "path": "src/views/theme/includes/BasicSetting.vue",
    "content": "<template>\n  <div>\n    <ValidationObserver ref=\"observer\" v-slot=\"{ invalid }\">\n      <a-form :form=\"form\" style=\"padding-bottom: 48px;\">\n        <a-form-item :label=\"$t('selectTheme')\" :labelCol=\"formLayout.label\" :wrapperCol=\"formLayout.wrapper\" :colon=\"false\">\n          <a-select v-model=\"form.themeName\" optionLabelProp=\"name\">\n            <a-select-option v-for=\"item in site.themes\" :key=\"item.folder\" :name=\"item.name\" :value=\"item.folder\">\n              <div class=\"theme-option\">\n                <div class=\"left\">\n                  <div class=\"theme-name\">{{ item.name }}</div>\n                  <div class=\"theme-version\" v-if=\"item.version\">{{ item.version }}</div>\n                </div>\n                <div class=\"extra\" v-if=\"item.repository\">\n                  <a-button @click.stop=\"openPage(item.repository)\" type=\"dashed\" shape=\"circle\" size=\"small\" icon=\"github\"></a-button>\n                </div>\n              </div>\n            </a-select-option>\n            <div slot=\"dropdownRender\" slot-scope=\"menu\">\n              <v-nodes :vnodes=\"menu\"/>\n              <a-divider style=\"margin: 4px 0;\" />\n              <div class=\"p-2 flex items-center cursor-pointer\" @click=\"openPage('https://gridea.dev/themes/')\">\n                <i class=\"ri-t-shirt-line mr-2\"></i> {{ $t('moreThemes') }}\n              </div>\n            </div>\n          </a-select>\n        </a-form-item>\n\n        <a-form-item :label=\"$t('siteName')\" :labelCol=\"formLayout.label\" :wrapperCol=\"formLayout.wrapper\" :colon=\"false\">\n          <a-input v-model=\"form.siteName\" />\n        </a-form-item>\n\n        <a-form-item :label=\"$t('siteDescription')\" :labelCol=\"formLayout.label\" :wrapperCol=\"formLayout.wrapper\" :colon=\"false\">\n          <a-input type=\"textarea\" v-model=\"form.siteDescription\" />\n          <div class=\"tip-text\">{{ $t('htmlSupport') }}</div>\n        </a-form-item>\n        <a-form-item :label=\"$t('footerInfo')\" :labelCol=\"formLayout.label\" :wrapperCol=\"formLayout.wrapper\" :colon=\"false\">\n          <a-input type=\"textarea\" v-model=\"form.footerInfo\" />\n          <div class=\"tip-text\">{{ $t('htmlSupport') }}</div>\n        </a-form-item>\n        <a-form-item :label=\"$t('isShowFeatureImage')\" :labelCol=\"formLayout.label\" :wrapperCol=\"formLayout.wrapper\" :colon=\"false\">\n          <a-switch v-model=\"form.showFeatureImage\" />\n        </a-form-item>\n        <a-form-item :label=\"$t('articlesPerPage')\" :labelCol=\"formLayout.label\" :wrapperCol=\"formLayout.wrapper\" :colon=\"false\">\n          <a-slider v-model=\"form.postPageSize\" :min=\"0\" :max=\"50\" />\n        </a-form-item>\n        <a-form-item :label=\"$t('archivesPerPage')\" :labelCol=\"formLayout.label\" :wrapperCol=\"formLayout.wrapper\" :colon=\"false\">\n          <a-slider v-model=\"form.archivesPageSize\" :min=\"0\" :max=\"100\" />\n        </a-form-item>\n        <a-form-item :label=\"$t('articleDefault')\" :labelCol=\"formLayout.label\" :wrapperCol=\"formLayout.wrapper\" :colon=\"false\">\n          <a-radio-group name=\"postUrlFormat\" v-model=\"form.postUrlFormat\">\n            <a-radio v-for=\"item in urlFormats\" :key=\"item.value\" :value=\"item.value\">{{ item.text }}</a-radio>\n          </a-radio-group>\n        </a-form-item>\n        <a-form-item :label=\"$t('tagDefault')\" :labelCol=\"formLayout.label\" :wrapperCol=\"formLayout.wrapper\" :colon=\"false\">\n          <a-radio-group name=\"tagUrlFormat\" v-model=\"form.tagUrlFormat\">\n            <a-radio v-for=\"item in urlFormats\" :key=\"item.value\" :value=\"item.value\">{{ item.text }}</a-radio>\n          </a-radio-group>\n        </a-form-item>\n\n\n        <a-form-item :label=\"$t('articleUrlPath')\" :labelCol=\"formLayout.label\" :wrapperCol=\"formLayout.wrapper\" :colon=\"false\">\n          <a-radio-group name=\"postPath\" v-model=\"form.postPath\">\n            <a-tooltip placement=\"bottom\" title=\"example.com/post/xxx\">\n              <a-radio value=\"post\">{{$t('default')}}</a-radio>\n            </a-tooltip>\n            <a-tooltip placement=\"bottom\" title=\"example.com/xxx\">\n              <a-radio value=\"\">{{$t('concise')}}</a-radio>\n            </a-tooltip>\n          </a-radio-group>\n        </a-form-item>\n\n        <a-form-item :label=\"$t('tagUrlPath')\" :labelCol=\"formLayout.label\" :wrapperCol=\"formLayout.wrapper\" :colon=\"false\">\n          <a-radio-group name=\"tagPath\" v-model=\"form.tagPath\">\n            <a-tooltip placement=\"bottom\" title=\"example.com/tag/xxx\">\n              <a-radio value=\"tag\">{{$t('default')}}</a-radio>\n            </a-tooltip>\n            <a-tooltip placement=\"bottom\" title=\"example.com/xxx\">\n              <a-radio value=\"\">{{$t('concise')}}</a-radio>\n            </a-tooltip>\n          </a-radio-group>\n        </a-form-item>\n\n        <ValidationProvider name=\"archivesPath\" rules=\"required\" v-slot=\"slotProps\">\n          <a-form-item\n            :label=\"$t('archivePathPrefix')\"\n            :labelCol=\"formLayout.label\"\n            :wrapperCol=\"formLayout.wrapper\"\n            :colon=\"false\"\n            :validateStatus=\"resolveState(slotProps)\"\n            :help=\"slotProps.errors[0]\"\n          >\n            <a-input v-model=\"form.archivesPath\" />\n          </a-form-item>\n        </ValidationProvider>\n\n        <a-form-item :label=\"$t('dateFormat')\" :labelCol=\"formLayout.label\" :wrapperCol=\"formLayout.wrapper\" :colon=\"false\">\n          <a-input v-model=\"form.dateFormat\" />\n          <div><a @click.prevent=\"openPage('http://momentjs.cn/docs/#/displaying/format/')\">Momentjs Format</a></div>\n        </a-form-item>\n        <a-form-item label=\"RSS/Feed\" :labelCol=\"formLayout.label\" :wrapperCol=\"formLayout.wrapper\" :colon=\"false\">\n          <a-radio-group name=\"tagUrlFormat\" v-model=\"form.feedFullText\">\n            <a-radio :value=\"true\">{{$t('showFullText')}}</a-radio>\n            <a-radio :value=\"false\">{{$t('showAbstractOnly')}}</a-radio>\n          </a-radio-group>\n        </a-form-item>\n        <a-form-item :label=\"$t('numberArticlesRSS')\" :labelCol=\"formLayout.label\" :wrapperCol=\"formLayout.wrapper\" :colon=\"false\">\n          <a-input-number :min=\"0\" :max=\"10000\" v-model=\"form.feedCount\" />\n        </a-form-item>\n        <footer-box>\n          <div class=\"flex justify-end\">\n            <a-button class=\"btn\" type=\"primary\" :disabled=\"invalid\" @click=\"saveTheme\">{{ $t('save') }}</a-button>\n          </div>\n        </footer-box>\n      </a-form>\n    </ValidationObserver>\n  </div>\n</template>\n\n<script lang=\"ts\">\nimport { ipcRenderer, IpcRendererEvent, shell } from 'electron'\nimport { Vue, Component } from 'vue-property-decorator'\nimport { ValidationProvider, ValidationObserver } from 'vee-validate'\nimport { State } from 'vuex-class'\nimport FooterBox from '../../../components/FooterBox/Index.vue'\nimport { Site } from '../../../store/modules/site'\nimport {\n  UrlFormats,\n  DEFAULT_POST_PAGE_SIZE,\n  DEFAULT_ARCHIVES_PAGE_SIZE,\n  DEFAULT_FEED_COUNT,\n  DEFAULT_ARCHIVES_PATH,\n  DEFAULT_POST_PATH,\n  DEFAULT_TAG_PATH,\n} from '../../../helpers/constants'\nimport ga from '../../../helpers/analytics'\n\n@Component({\n  name: 'ThemeBasicSetting',\n  components: {\n    VNodes: {\n      functional: true,\n      render: (h: any, ctx: any) => ctx.props.vnodes,\n    },\n    ValidationProvider,\n    ValidationObserver,\n    FooterBox,\n  },\n})\nexport default class ThemeBasicSetting extends Vue {\n  @State('site') site!: Site\n\n  formLayout = {\n    label: { span: 6 },\n    wrapper: { span: 12 },\n  }\n\n  form = {\n    themeName: '',\n    postPageSize: DEFAULT_POST_PAGE_SIZE,\n    archivesPageSize: DEFAULT_ARCHIVES_PAGE_SIZE,\n    siteName: '',\n    siteDescription: '',\n    footerInfo: '',\n    showFeatureImage: true,\n    postUrlFormat: 'SLUG',\n    tagUrlFormat: 'SLUG',\n    dateFormat: 'YYYY-MM-DD',\n    feedFullText: true,\n    feedCount: DEFAULT_FEED_COUNT,\n    archivesPath: DEFAULT_ARCHIVES_PATH,\n    postPath: DEFAULT_POST_PATH,\n    tagPath: DEFAULT_TAG_PATH,\n  }\n\n  lCol = { span: 5 }\n\n  wCol = { span: 12 }\n\n  urlFormats = UrlFormats\n\n  saveTheme() {\n    ipcRenderer.send('theme-save', this.form)\n    ipcRenderer.once('theme-saved', async (event: IpcRendererEvent, result: any) => {\n      await this.$bus.$emit('site-reload')\n      this.$router.push({ name: 'loading', query: { redirect: 'theme?tab=basic' } })\n      this.$message.success(this.$t('themeConfigSaved'))\n\n      ga.event('Theme', 'Theme - save', { evLabel: this.form.themeName })\n    })\n  }\n\n  mounted() {\n    const config = this.site.themeConfig\n\n    this.form.themeName = config.themeName\n    this.form.postPageSize = config.postPageSize\n    this.form.archivesPageSize = config.archivesPageSize\n    this.form.siteName = config.siteName\n    this.form.siteDescription = config.siteDescription\n    this.form.footerInfo = config.footerInfo\n    this.form.showFeatureImage = config.showFeatureImage\n    this.form.postUrlFormat = config.postUrlFormat\n    this.form.tagUrlFormat = config.tagUrlFormat\n    this.form.dateFormat = config.dateFormat\n    this.form.feedFullText = config.feedFullText\n    this.form.feedCount = config.feedCount\n    this.form.archivesPath = config.archivesPath\n    this.form.postPath = config.postPath\n    this.form.tagPath = config.tagPath\n  }\n\n  openPage(url: string) {\n    shell.openExternal(url)\n  }\n\n  resolveState({ errors }: { errors: any }) {\n    if (errors[0]) {\n      return 'error'\n    }\n\n    return ''\n  }\n}\n</script>\n\n<style lang=\"less\" scoped>\n/deep/ .ant-slider-rail {\n  background: #e1e1e1;\n}\n.theme-option {\n  display: flex;\n  justify-content: space-between;\n  .theme-name {\n    padding-bottom: 8px;\n  }\n  .theme-version {\n    font-size: 12px;\n    color: #ced4da;\n    font-weight: lighter !important;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/views/theme/includes/CustomSetting.vue",
    "content": "<template>\n  <div>\n    <div v-if=\"currentThemeConfig.length > 0\" style=\"padding-bottom: 48px;\">\n      <a-tabs tabPosition=\"left\" defaultActiveKey=\"1\" v-model=\"activeKey\">\n        <a-tab-pane :tab=\"group\" v-for=\"(group, index) in groups\" :key=\"index + 1\">\n          <div v-for=\"(item, index1) in currentThemeConfig\" :key=\"index1\">\n            <a-form-item v-if=\"item.group === group\" :label=\"item.label\" :colon=\"false\" :help=\"item.note\">\n\n              <!-- 普通输入 -->\n              <a-input v-if=\"item.type === 'input' && !item.card\" :placeholder=\"item.note\" v-model=\"form[item.name]\" />\n\n              <!-- 带颜色卡片输入 -->\n              <a-popover\n                title=\"Color\"\n                trigger=\"click\"\n                placement=\"bottomLeft\"\n              >\n                <color-card slot=\"content\" @change=\"handleColorChange($event, item.name)\"></color-card>\n                <a-input :ref=\"`color${index1}`\" v-if=\"item.type === 'input' && item.card === 'color'\" :placeholder=\"item.note\" v-model=\"form[item.name]\">\n                  <div class=\"w-4 h-4 rounded-full\" slot=\"prefix\" v-if=\"form[item.name]\" :style=\"{ backgroundColor: form[item.name] }\"></div>\n                </a-input>\n              </a-popover>\n              \n              <!-- 带文章卡片输入 -->\n              <a-popover\n                title=\"文章\"\n                trigger=\"click\"\n                placement=\"bottomLeft\"\n              >\n                <posts-card slot=\"content\" :posts=\"postsWithLink\" @select=\"handlePostSelected($event, item.name)\"></posts-card>\n                <a-input :ref=\"`color${index1}`\" v-if=\"item.type === 'input' && item.card === 'post'\" :placeholder=\"item.note\" v-model=\"form[item.name]\" />\n              </a-popover>\n              <div class=\"tip-text mb-1\" v-if=\"item.type === 'input' && item.card === 'post' && form[item.name]\">{{ getPostTitleByLink(form[item.name]) }}</div>\n\n              <!-- 下拉选择 -->\n              <a-select v-if=\"item.type === 'select'\" v-model=\"form[item.name]\" style=\"width: 100%;\">\n                <a-select-option v-for=\"(option, index2) in item.options\" :key=\"index2\" :value=\"option.value\">{{ option.label }}</a-select-option>\n              </a-select>\n\n              <!-- 单选组合 -->\n              <a-radio-group v-if=\"item.type === 'radio'\" v-model=\"form[item.name]\">\n                <a-radio v-for=\"(option, index2) in item.options\" :key=\"index2\" :value=\"option.value\">{{ option.label }}</a-radio>\n              </a-radio-group>\n\n              <!-- switch 类型 -->\n              <a-switch v-if=\"item.type === 'switch'\" v-model=\"form[item.name]\"/>\n\n              <!-- textarea 类型 -->\n              <a-textarea v-if=\"item.type === 'textarea'\" v-model=\"form[item.name]\" :placeholder=\"item.note\" :autosize=\"{ minRows: 2, maxRows: 32 }\" />\n\n              <!-- picture-upload 类型 -->\n              <div v-if=\"item.type === 'picture-upload'\" style=\"display: flex;\">\n                <a-upload\n                  action=\"\"\n                  listType=\"picture-card\"\n                  class=\"feature-uploader\"\n                  :showUploadList=\"false\"\n                  :beforeUpload=\"file => beforeImageUpload(file, item.name)\"\n                >\n                  <div v-if=\"form[item.name] && form[item.name].startsWith('/media/')\">\n                    <img class=\"picture\" :src=\"`file://${site.appDir}/themes/${site.themeConfig.themeName}/assets${form[item.name]}`\" height=\"150\" />\n                  </div>\n                  <div v-else-if=\"form[item.name] === ''\">\n                    <img src=\"@/assets/images/image_upload.svg\" class=\"picture\">\n                    <i class=\"zwicon-upload upload-icon\"></i>\n                  </div>\n                  <div v-else>\n                    <img class=\"picture\" :src=\"`file://${form[item.name]}`\" alt=\"\">\n                  </div>\n                </a-upload>\n                <a-tooltip placement=\"left\" title=\"Reset\">\n                  <a-button style=\"margin-left: 8px;\" v-if=\"form[item.name]\" shape=\"circle\" site=\"small\" @click=\"resetFormItem(item.name)\">\n                    <i class=\"zwicon-undo\"></i>\n                  </a-button>\n                </a-tooltip>\n              </div>\n\n              <!-- Markdown 类型 -->\n              <div v-if=\"item.type === 'markdown'\" class=\"shadow p-4\">\n                <monaco-markdown-editor ref=\"monacoMarkdownEditor\" v-model=\"form[item.name]\"></monaco-markdown-editor>\n              </div>\n\n              <!-- array 类型 -->\n              <div v-if=\"item.type === 'array'\">\n                <div class=\"item-card\" v-for=\"(configItem, configItemIndex) in form[item.name]\" :key=\"configItemIndex\">\n                  <div v-for=\"(field, fieldIndex) in item.arrayItems\" :key=\"fieldIndex\">\n                    <a-form-item :label=\"field.label\" :colon=\"false\" :help=\"field.note\" :labelCol=\"formLayout.label\" :wrapperCol=\"formLayout.wrapper\">\n                      <!-- 普通输入 -->\n                      <a-input v-if=\"field.type === 'input' && !field.card\" :placeholder=\"field.note\" v-model=\"configItem[field.name]\" />\n\n                      <!-- 带颜色卡片输入 -->\n                      <a-popover\n                        title=\"Color\"\n                        trigger=\"click\"\n                        placement=\"bottomLeft\"\n                      >\n                        <color-card slot=\"content\" @change=\"handleColorChange($event, item.name, configItemIndex, field.name)\"></color-card>\n                        <a-input :ref=\"`color-${configItemIndex}-${fieldIndex}`\" v-if=\"field.type === 'input' && field.card === 'color'\" :placeholder=\"field.note\" v-model=\"configItem[field.name]\">\n                          <div class=\"w-4 h-4 rounded-full\" slot=\"prefix\" v-if=\"configItem[field.name]\" :style=\"{ backgroundColor: configItem[field.name] }\"></div>\n                        </a-input>\n                      </a-popover>\n\n                      <!-- 带文章卡片输入 -->\n                      <a-popover\n                        title=\"文章\"\n                        trigger=\"click\"\n                        placement=\"bottomLeft\"\n                      >\n                        <posts-card slot=\"content\" :posts=\"postsWithLink\" @select=\"handlePostSelected($event, item.name, configItemIndex, field.name)\"></posts-card>\n                        <a-input :ref=\"`color-${configItemIndex}-${fieldIndex}`\" v-if=\"field.type === 'input' && field.card === 'post'\" :placeholder=\"field.note\" v-model=\"configItem[field.name]\" />\n                      </a-popover>\n                      <div class=\"tip-text mb-1\" v-if=\"field.type === 'input' && field.card === 'post' && configItem[field.name]\">{{ getPostTitleByLink(configItem[field.name]) }}</div>\n\n                      <!-- 下拉选择 -->\n                      <a-select v-if=\"field.type === 'select'\" v-model=\"configItem[field.name]\" style=\"width: 100%;\">\n                        <a-select-option v-for=\"(option, index2) in field.options\" :key=\"index2\" :value=\"option.value\">{{ option.label }}</a-select-option>\n                      </a-select>\n\n                      <!-- 单选组合 -->\n                      <a-radio-group v-if=\"field.type === 'radio'\" v-model=\"configItem[field.name]\">\n                        <a-radio v-for=\"(option, index2) in field.options\" :key=\"index2\" :value=\"option.value\">{{ option.label }}</a-radio>\n                      </a-radio-group>\n\n                      <!-- switch 类型 -->\n                      <a-switch v-if=\"field.type === 'switch'\" v-model=\"configItem[field.name]\"/>\n\n                      <!-- textarea 类型 -->\n                      <a-textarea v-if=\"field.type === 'textarea'\" v-model=\"configItem[field.name]\" :placeholder=\"field.note\" :autosize=\"{ minRows: 2, maxRows: 32 }\" />\n\n                      <!-- picture-upload 类型 -->\n                      <div v-if=\"field.type === 'picture-upload'\" style=\"display: flex;\">\n                        <a-upload\n                          action=\"\"\n                          listType=\"picture-card\"\n                          class=\"feature-uploader\"\n                          :showUploadList=\"false\"\n                          :beforeUpload=\"file => beforeImageUpload(file, item.name, field.name, configItemIndex)\"\n                        >\n                          <div v-if=\"configItem[field.name] && configItem[field.name].startsWith('/media/')\">\n                            <img class=\"picture\" :src=\"`file://${site.appDir}/themes/${site.themeConfig.themeName}/assets${configItem[field.name]}`\" height=\"150\" />\n                          </div>\n                          <div v-else-if=\"configItem[field.name] === ''\">\n                            <img src=\"@/assets/images/image_upload.svg\" class=\"picture\">\n                            <i class=\"zwicon-upload upload-icon\"></i>\n                          </div>\n                          <div v-else>\n                            <img class=\"picture\" :src=\"`file://${configItem[field.name]}`\" alt=\"\">\n                          </div>\n                        </a-upload>\n                        <a-tooltip placement=\"left\" title=\"Reset\">\n                          <a-button style=\"margin-left: 8px;\" v-if=\"configItem[field.name]\" shape=\"circle\" site=\"small\" @click=\"resetFormItem(item.name, field.name, configItemIndex)\">\n                            <i class=\"zwicon-undo\"></i>\n                          </a-button>\n                        </a-tooltip>\n                      </div>\n\n                      <!-- Markdown 类型 -->\n                      <div v-if=\"field.type === 'markdown'\" class=\"shadow p-4\">\n                        <monaco-markdown-editor ref=\"monacoMarkdownEditor\" v-model=\"configItem[field.name]\"></monaco-markdown-editor>\n                      </div>\n                    </a-form-item>\n                  </div>\n\n                  <a-button shape=\"circle\" @click=\"addConfigItem(form[item.name], configItemIndex, item.arrayItems)\" style=\"margin-right: 8px;\"><i class=\"zwicon-plus\"></i></a-button>\n                  <a-button shape=\"circle\" @click=\"deleteConfigItem(form[item.name], configItemIndex)\"><i class=\"zwicon-minus\"></i></a-button>\n                </div>\n\n                <a-button v-if=\"!form[item.name] || form[item.name].length === 0\" block @click=\"addConfigItem(form[item.name], 0, item.arrayItems)\"><i class=\"zwicon-plus\"></i></a-button>\n              </div>\n\n            </a-form-item>\n          </div>\n        </a-tab-pane>\n      </a-tabs>\n      <footer-box>\n        <div class=\"flex justify-between\">\n          <a-button @click=\"resetThemeCustomConfig\">\n            <i class=\"zwicon-undo\"></i>\n          </a-button>\n          <a-button @click=\"saveThemeCustomConfig\" type=\"primary\">{{ $t('save') }}</a-button>\n        </div>\n      </footer-box>\n    </div>\n    <div class=\"empty-container\" v-else>\n      <img class=\"icon\" src=\"@/assets/images/graphic-empty-box.svg\" alt=\"\">\n      <div class=\"description\">{{ $t('noCustomConfigTip') }}</div>\n    </div>\n  </div>\n</template>\n\n<script lang=\"ts\">\nimport { ipcRenderer, IpcRendererEvent, shell } from 'electron'\nimport { Vue, Component } from 'vue-property-decorator'\nimport { State } from 'vuex-class'\nimport urlJoin from 'url-join'\nimport { Site } from '../../../store/modules/site'\nimport MonacoMarkdownEditor from '../../../components/MonacoMarkdownEditor/Index.vue'\nimport FooterBox from '../../../components/FooterBox/Index.vue'\nimport ColorCard from '../../../components/ColorCard/Index.vue'\nimport PostsCard from '../../../components/PostsCard/Index.vue'\n\n@Component({\n  name: 'ThemeCustomSetting',\n  components: {\n    MonacoMarkdownEditor,\n    FooterBox,\n    ColorCard,\n    PostsCard,\n  },\n})\nexport default class ThemeCustomSetting extends Vue {\n  @State('site') site!: Site\n\n  formLayout = {\n    label: { span: 6 },\n    wrapper: { span: 12 },\n  }\n\n  form: any = {}\n\n  get groups() {\n    let list = this.site.currentThemeConfig.map((item: any) => item.group)\n    list = [...new Set(list)]\n    return list\n  }\n\n  get currentThemeConfig() {\n    return this.site.currentThemeConfig || []\n  }\n\n  get postsWithLink() {\n    const list = this.site.posts.map((post: any) => {\n      return {\n        ...post,\n        link: urlJoin(this.site.setting.domain, this.site.themeConfig.postPath, post.fileName, '/'),\n      }\n    }).filter((post: any) => post.data.published)\n    \n    return list\n  }\n\n  activeKey = 1\n\n  mounted() {\n    this.loadCustomConfig()\n  }\n\n  activated() {\n    this.loadCustomConfig()\n  }\n\n  getPostTitleByLink(link: string) {\n    const foundPost = this.postsWithLink.find((post: any) => post.link === link)\n    return (foundPost && foundPost.data.title) || ''\n  }\n\n  loadCustomConfig() {\n    const keys = Object.keys(this.site.themeCustomConfig || {})\n    keys.forEach((key: string) => {\n      this.$set(this.form, key, this.site.themeCustomConfig[key])\n    })\n    this.currentThemeConfig.forEach((item: any) => {\n      if (this.form[item.name] === undefined) {\n        this.$set(this.form, item.name, item.value)\n      }\n    })\n  }\n\n  openPage(url: string) {\n    shell.openExternal(url)\n  }\n\n  saveThemeCustomConfig() {\n    console.log('this.form', this.form)\n    ipcRenderer.send('theme-custom-config-save', this.form)\n    ipcRenderer.once('theme-custom-config-saved', (event: IpcRendererEvent, result: any) => {\n      this.$bus.$emit('site-reload')\n      this.$message.success(this.$t('saved'))\n    })\n  }\n\n  resetThemeCustomConfig() {\n    this.$confirm({\n      title: '重置',\n      content: '此操作将会使该主题配置恢复到初始状态，确认重置吗？',\n      okText: '确认',\n      cancelText: '取消',\n      onOk: () => {\n        ipcRenderer.send('theme-custom-config-save', {})\n        ipcRenderer.once('theme-custom-config-saved', async (event: IpcRendererEvent, result: any) => {\n          await this.$bus.$emit('site-reload')\n          this.$router.push({ name: 'loading', query: { redirect: 'theme?tab=custom' } })\n          this.$message.success(this.$t('reseted'))\n        })\n      },\n    })\n  }\n\n  handleColorChange(color: string, name: string, arrayIndex?: number, fieldName?: string) {\n    if (arrayIndex === undefined) {\n      this.form[name] = color\n    } else if (arrayIndex !== undefined && fieldName !== undefined) {\n      this.form[name][arrayIndex][fieldName] = color\n    }\n  }\n\n  handlePostSelected(postUrl: string, name: string, arrayIndex?: number, fieldName?: string) {\n    console.log('postUrl', postUrl)\n    if (arrayIndex === undefined) {\n      this.form[name] = postUrl\n    } else if (arrayIndex !== undefined && fieldName !== undefined) {\n      this.form[name][arrayIndex][fieldName] = postUrl\n    }\n  }\n\n  beforeImageUpload(file: any, formItemName: string, arrayFieldItemName?: string, configItemIndex?: number) {\n    if (!file) {\n      return\n    }\n\n    if (arrayFieldItemName && typeof configItemIndex === 'number') {\n      this.form[formItemName][configItemIndex][arrayFieldItemName] = file.path\n      return false\n    }\n\n    this.form[formItemName] = file.path\n    return false\n  }\n\n  resetFormItem(formItemName: string, arrayFieldItemName?: string, configItemIndex?: number) {\n    const originalItem = this.currentThemeConfig.find((item: any) => item.name === formItemName)\n    if (arrayFieldItemName && typeof configItemIndex === 'number') {\n      const foundItem = originalItem.arrayItems.find((item: any) => item.name === arrayFieldItemName)\n      this.form[formItemName][configItemIndex][arrayFieldItemName] = foundItem.value\n    } else {\n      this.form[formItemName] = originalItem.value\n    }\n  }\n\n  deleteConfigItem(formItem: any[], index: number) {\n    console.log('run...', formItem, index)\n    formItem.splice(index, 1)\n  }\n\n  addConfigItem(formItem: any[], index: number, arrayItems: any) {\n    const newValue = arrayItems.reduce((o: any, c: any) => {\n      o[c.name] = c.value\n      return o\n    }, {})\n    formItem.splice(index + 1, 0, newValue)\n  }\n}\n</script>\n\n<style lang=\"less\" scoped>\n.top-container {\n  display: flex;\n  justify-content: space-between;\n  padding-bottom: 24px;\n}\n.empty-container {\n  text-align: center;\n  padding: 40px 0;\n  .icon {\n    width: 48px;\n  }\n  .description {\n    font-size: 16px;\n    padding: 24px;\n    color: #8c8c8c;\n  }\n}\n\n.picture {\n  height: 150px;\n}\n\n.item-card {\n  margin-bottom: 24px;\n  padding: 24px;\n  box-shadow: 0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px 0 rgba(0,0,0,.06);\n  border-radius: 6px;\n}\n\n/deep/ .ant-slider-rail {\n  background: #e1e1e1;\n}\n\n/deep/ .ant-card {\n  background: transparent;\n}\n/deep/ .ant-card-head {\n  border-bottom: none;\n}\n\n/deep/ .ant-tabs-nav .ant-tabs-tab-active {\n  color: #1b1b18;\n  border-radius: 4px 0 0 4px;\n  &::after {\n    display: none;\n  }\n}\n\n/deep/ .ant-tabs .ant-tabs-left-bar .ant-tabs-ink-bar, .ant-tabs .ant-tabs-right-bar .ant-tabs-ink-bar {\n  width: 1px;\n}\n</style>\n"
  },
  {
    "path": "src/views/theme/includes/FaviconSetting.vue",
    "content": "<template>\n  <div>\n     <a-upload\n      action=\"\"\n      listType=\"picture-card\"\n      class=\"feature-uploader\"\n      :showUploadList=\"false\"\n      :beforeUpload=\"beforeUpload\"\n    >\n      <div v-if=\"faviconPath\">\n        <img class=\"favicon-image\" :src=\"faviconPath\" width=\"88px\" height=\"88px\" />\n      </div>\n      <div v-else>\n        <a-icon type=\"plus\" />\n        <div class=\"ant-upload-text\">Upload</div>\n      </div>\n    </a-upload>\n    <div class=\"tip-text\" v-if=\"file\">\n      {{ file.path }}\n    </div>\n    <footer-box>\n      <div class=\"flex justify-end\">\n        <a-button type=\"primary\" @click=\"submit\">{{ $t('save') }}</a-button>\n      </div>\n    </footer-box>\n  </div>\n</template>\n\n<script lang=\"ts\">\nimport { ipcRenderer, IpcRendererEvent } from 'electron'\nimport { Vue, Component } from 'vue-property-decorator'\nimport { State } from 'vuex-class'\nimport * as path from 'path'\nimport FooterBox from '../../../components/FooterBox/Index.vue'\n\n@Component({\n  components: {\n    FooterBox,\n  },\n})\nexport default class FaviconSetting extends Vue {\n  @State('site') site!: any\n\n  file: any = null\n\n  faviconPath = ''\n\n  mounted() {\n    this.faviconPath = path.join('file://', this.site.appDir, `favicon.ico?a=${Math.random()}`)\n  }\n\n  beforeUpload(file: any) {\n    if (!file) {\n      return\n    }\n    const isImage = file.type.indexOf('image') !== -1\n    if (!isImage) {\n      return\n    }\n    if (file && isImage) {\n      this.file = file\n    }\n    return false\n  }\n\n  submit() {\n    if (!this.file) {\n      return\n    }\n    console.log('click favicon upload', this.file)\n    ipcRenderer.send('favicon-upload', this.file.path)\n    ipcRenderer.once('favicon-uploaded', (event: IpcRendererEvent, result: any) => {\n      this.file = null\n      this.$bus.$emit('site-reload')\n      this.faviconPath = path.join('file://', this.site.appDir, `favicon.ico?a=${Math.random()}`)\n      this.$message.success(this.$t('faviconSettingSuccess'))\n    })\n  }\n}\n</script>\n\n\n<style lang=\"less\" scoped>\n.file-name {\n  margin-bottom: 8px;\n}\n</style>\n"
  },
  {
    "path": "src/vue-bus.ts",
    "content": "import _Vue from 'vue'\n\ndeclare module 'vue/types/vue' {\n    interface Vue {\n        $bus: any\n    }\n}\nclass VueBus {\n  static install(Vue: any, options: any) {\n    const bus = new Vue()\n    Vue.bus = bus\n    Vue.prototype.$bus = bus\n  }\n}\n// eslint-disable-next-line\nif ('Vue' in window) {\n  _Vue.use(VueBus)\n}\nexport default VueBus\n"
  },
  {
    "path": "tailwind.config.js",
    "content": "module.exports = {\n  prefix: '',\n  important: false,\n  separator: ':',\n  theme: {\n    screens: {\n      sm: '640px',\n      md: '768px',\n      lg: '1024px',\n      xl: '1280px',\n    },\n    colors: {\n      transparent: 'transparent',\n\n      black: '#000',\n      white: '#fff',\n\n      gray: {\n        100: '#FAFAFA',\n        200: '#EAEAEA',\n        300: '#999',\n        400: '#888',\n        500: '#666',\n        600: '#444',\n        700: '#333',\n        800: '#111',\n        900: '#000',\n      },\n      red: {\n        100: '#fff5f5',\n        200: '#fed7d7',\n        300: '#feb2b2',\n        400: '#fc8181',\n        500: '#f56565',\n        600: '#e53e3e',\n        700: '#c53030',\n        800: '#9b2c2c',\n        900: '#742a2a',\n      },\n      orange: {\n        100: '#fffaf0',\n        200: '#feebc8',\n        300: '#fbd38d',\n        400: '#f6ad55',\n        500: '#ed8936',\n        600: '#dd6b20',\n        700: '#c05621',\n        800: '#9c4221',\n        900: '#7b341e',\n      },\n      yellow: {\n        100: '#fffff0',\n        200: '#fefcbf',\n        300: '#faf089',\n        400: '#f6e05e',\n        500: '#ecc94b',\n        600: '#d69e2e',\n        700: '#b7791f',\n        800: '#975a16',\n        900: '#744210',\n      },\n      green: {\n        100: '#f0fff4',\n        200: '#c6f6d5',\n        300: '#9ae6b4',\n        400: '#68d391',\n        500: '#48bb78',\n        600: '#38a169',\n        700: '#2f855a',\n        800: '#276749',\n        900: '#22543d',\n      },\n      teal: {\n        100: '#e6fffa',\n        200: '#b2f5ea',\n        300: '#81e6d9',\n        400: '#4fd1c5',\n        500: '#38b2ac',\n        600: '#319795',\n        700: '#2c7a7b',\n        800: '#285e61',\n        900: '#234e52',\n      },\n      blue: {\n        100: '#ebf8ff',\n        200: '#bee3f8',\n        300: '#90cdf4',\n        400: '#63b3ed',\n        500: '#4299e1',\n        600: '#3182ce',\n        700: '#2b6cb0',\n        800: '#2c5282',\n        900: '#2a4365',\n      },\n      indigo: {\n        100: '#ebf4ff',\n        200: '#c3dafe',\n        300: '#a3bffa',\n        400: '#7f9cf5',\n        500: '#667eea',\n        600: '#5a67d8',\n        700: '#4c51bf',\n        800: '#434190',\n        900: '#3c366b',\n      },\n      purple: {\n        100: '#faf5ff',\n        200: '#e9d8fd',\n        300: '#d6bcfa',\n        400: '#b794f4',\n        500: '#9f7aea',\n        600: '#805ad5',\n        700: '#6b46c1',\n        800: '#553c9a',\n        900: '#44337a',\n      },\n      pink: {\n        100: '#fff5f7',\n        200: '#fed7e2',\n        300: '#fbb6ce',\n        400: '#f687b3',\n        500: '#ed64a6',\n        600: '#d53f8c',\n        700: '#b83280',\n        800: '#97266d',\n        900: '#702459',\n      },\n    },\n    spacing: {\n      px: '1px',\n      '0': '0',\n      '1': '0.25rem',\n      '2': '0.5rem',\n      '3': '0.75rem',\n      '4': '1rem',\n      '5': '1.25rem',\n      '6': '1.5rem',\n      '8': '2rem',\n      '10': '2.5rem',\n      '12': '3rem',\n      '16': '4rem',\n      '20': '5rem',\n      '24': '6rem',\n      '32': '8rem',\n      '40': '10rem',\n      '48': '12rem',\n      '56': '14rem',\n      '64': '16rem',\n    },\n    backgroundColor: theme => theme('colors'),\n    backgroundPosition: {\n      bottom: 'bottom',\n      center: 'center',\n      left: 'left',\n      'left-bottom': 'left bottom',\n      'left-top': 'left top',\n      right: 'right',\n      'right-bottom': 'right bottom',\n      'right-top': 'right top',\n      top: 'top',\n    },\n    backgroundSize: {\n      auto: 'auto',\n      cover: 'cover',\n      contain: 'contain',\n    },\n    borderColor: theme => ({\n      ...theme('colors'),\n      default: theme('colors.gray.300', 'currentColor'),\n    }),\n    borderRadius: {\n      none: '0',\n      sm: '0.125rem',\n      default: '0.25rem',\n      lg: '0.5rem',\n      full: '9999px',\n    },\n    borderWidth: {\n      default: '1px',\n      '0': '0',\n      '2': '2px',\n      '4': '4px',\n      '8': '8px',\n    },\n    boxShadow: {\n      default: '0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)',\n      md: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)',\n      lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)',\n      xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)',\n      '2xl': '0 25px 50px -12px rgba(0, 0, 0, 0.25)',\n      inner: 'inset 0 2px 4px 0 rgba(0, 0, 0, 0.06)',\n      outline: '0 0 0 3px rgba(66, 153, 225, 0.5)',\n      none: 'none',\n    },\n    container: {},\n    cursor: {\n      auto: 'auto',\n      default: 'default',\n      pointer: 'pointer',\n      wait: 'wait',\n      text: 'text',\n      move: 'move',\n      'not-allowed': 'not-allowed',\n    },\n    fill: {\n      current: 'currentColor',\n    },\n    flex: {\n      '1': '1 1 0%',\n      auto: '1 1 auto',\n      initial: '0 1 auto',\n      none: 'none',\n    },\n    flexGrow: {\n      '0': '0',\n      default: '1',\n    },\n    flexShrink: {\n      '0': '0',\n      default: '1',\n    },\n    fontFamily: {\n      sans: [\n        '-apple-system',\n        'BlinkMacSystemFont',\n        '\"Segoe UI\"',\n        'Roboto',\n        '\"Helvetica Neue\"',\n        'Arial',\n        '\"Noto Sans\"',\n        'sans-serif',\n        '\"Apple Color Emoji\"',\n        '\"Segoe UI Emoji\"',\n        '\"Segoe UI Symbol\"',\n        '\"Noto Color Emoji\"',\n      ],\n      serif: [\n        'Georgia',\n        'Cambria',\n        '\"Times New Roman\"',\n        'Times',\n        'serif',\n      ],\n      mono: [\n        'Menlo',\n        'Monaco',\n        'Consolas',\n        '\"Liberation Mono\"',\n        '\"Courier New\"',\n        'monospace',\n      ],\n    },\n    fontSize: {\n      xs: '0.75rem',\n      sm: '0.875rem',\n      base: '1rem',\n      lg: '1.125rem',\n      xl: '1.25rem',\n      '2xl': '1.5rem',\n      '3xl': '1.875rem',\n      '4xl': '2.25rem',\n      '5xl': '3rem',\n      '6xl': '4rem',\n    },\n    fontWeight: {\n      hairline: '100',\n      thin: '200',\n      light: '300',\n      normal: '400',\n      medium: '500',\n      semibold: '600',\n      bold: '700',\n      extrabold: '800',\n      black: '900',\n    },\n    height: theme => ({\n      auto: 'auto',\n      ...theme('spacing'),\n      full: '100%',\n      screen: '100vh',\n    }),\n    inset: {\n      '0': '0',\n      auto: 'auto',\n    },\n    letterSpacing: {\n      tighter: '-0.05em',\n      tight: '-0.025em',\n      normal: '0',\n      wide: '0.025em',\n      wider: '0.05em',\n      widest: '0.1em',\n    },\n    lineHeight: {\n      none: '1',\n      tight: '1.25',\n      snug: '1.375',\n      normal: '1.5',\n      relaxed: '1.625',\n      loose: '2',\n    },\n    listStyleType: {\n      none: 'none',\n      disc: 'disc',\n      decimal: 'decimal',\n    },\n    margin: (theme, { negative }) => ({\n      auto: 'auto',\n      ...theme('spacing'),\n      ...negative(theme('spacing')),\n    }),\n    maxHeight: {\n      full: '100%',\n      screen: '100vh',\n    },\n    maxWidth: {\n      xs: '20rem',\n      sm: '24rem',\n      md: '28rem',\n      lg: '32rem',\n      xl: '36rem',\n      '2xl': '42rem',\n      '3xl': '48rem',\n      '4xl': '56rem',\n      '5xl': '64rem',\n      '6xl': '72rem',\n      full: '100%',\n    },\n    minHeight: {\n      '0': '0',\n      full: '100%',\n      screen: '100vh',\n    },\n    minWidth: {\n      '0': '0',\n      full: '100%',\n    },\n    objectPosition: {\n      bottom: 'bottom',\n      center: 'center',\n      left: 'left',\n      'left-bottom': 'left bottom',\n      'left-top': 'left top',\n      right: 'right',\n      'right-bottom': 'right bottom',\n      'right-top': 'right top',\n      top: 'top',\n    },\n    opacity: {\n      '0': '0',\n      '25': '0.25',\n      '50': '0.5',\n      '75': '0.75',\n      '100': '1',\n    },\n    order: {\n      first: '-9999',\n      last: '9999',\n      none: '0',\n      '1': '1',\n      '2': '2',\n      '3': '3',\n      '4': '4',\n      '5': '5',\n      '6': '6',\n      '7': '7',\n      '8': '8',\n      '9': '9',\n      '10': '10',\n      '11': '11',\n      '12': '12',\n    },\n    padding: theme => theme('spacing'),\n    placeholderColor: theme => theme('colors'),\n    stroke: {\n      current: 'currentColor',\n    },\n    textColor: theme => theme('colors'),\n    width: theme => ({\n      auto: 'auto',\n      ...theme('spacing'),\n      '1/2': '50%',\n      '1/3': '33.333333%',\n      '2/3': '66.666667%',\n      '1/4': '25%',\n      '2/4': '50%',\n      '3/4': '75%',\n      '1/5': '20%',\n      '2/5': '40%',\n      '3/5': '60%',\n      '4/5': '80%',\n      '1/6': '16.666667%',\n      '2/6': '33.333333%',\n      '3/6': '50%',\n      '4/6': '66.666667%',\n      '5/6': '83.333333%',\n      '1/12': '8.333333%',\n      '2/12': '16.666667%',\n      '3/12': '25%',\n      '4/12': '33.333333%',\n      '5/12': '41.666667%',\n      '6/12': '50%',\n      '7/12': '58.333333%',\n      '8/12': '66.666667%',\n      '9/12': '75%',\n      '10/12': '83.333333%',\n      '11/12': '91.666667%',\n      full: '100%',\n      screen: '100vw',\n    }),\n    zIndex: {\n      auto: 'auto',\n      '0': '0',\n      '10': '10',\n      '20': '20',\n      '30': '30',\n      '40': '40',\n      '50': '50',\n    },\n  },\n  variants: {\n    accessibility: ['responsive', 'focus'],\n    alignContent: ['responsive'],\n    alignItems: ['responsive'],\n    alignSelf: ['responsive'],\n    appearance: ['responsive'],\n    backgroundAttachment: ['responsive'],\n    backgroundColor: ['responsive', 'hover', 'focus'],\n    backgroundPosition: ['responsive'],\n    backgroundRepeat: ['responsive'],\n    backgroundSize: ['responsive'],\n    borderCollapse: ['responsive'],\n    borderColor: ['responsive', 'hover', 'focus'],\n    borderRadius: ['responsive'],\n    borderStyle: ['responsive'],\n    borderWidth: ['responsive'],\n    boxShadow: ['responsive', 'hover', 'focus'],\n    cursor: ['responsive'],\n    display: ['responsive'],\n    fill: ['responsive'],\n    flex: ['responsive'],\n    flexDirection: ['responsive'],\n    flexGrow: ['responsive'],\n    flexShrink: ['responsive'],\n    flexWrap: ['responsive'],\n    float: ['responsive'],\n    fontFamily: ['responsive'],\n    fontSize: ['responsive'],\n    fontSmoothing: ['responsive'],\n    fontStyle: ['responsive'],\n    fontWeight: ['responsive', 'hover', 'focus'],\n    height: ['responsive'],\n    inset: ['responsive'],\n    justifyContent: ['responsive'],\n    letterSpacing: ['responsive'],\n    lineHeight: ['responsive'],\n    listStylePosition: ['responsive'],\n    listStyleType: ['responsive'],\n    margin: ['responsive'],\n    maxHeight: ['responsive'],\n    maxWidth: ['responsive'],\n    minHeight: ['responsive'],\n    minWidth: ['responsive'],\n    objectFit: ['responsive'],\n    objectPosition: ['responsive'],\n    opacity: ['responsive', 'hover', 'focus'],\n    order: ['responsive'],\n    outline: ['responsive', 'focus'],\n    overflow: ['responsive'],\n    padding: ['responsive'],\n    placeholderColor: ['responsive', 'focus'],\n    pointerEvents: ['responsive'],\n    position: ['responsive'],\n    resize: ['responsive'],\n    stroke: ['responsive'],\n    tableLayout: ['responsive'],\n    textAlign: ['responsive'],\n    textColor: ['responsive', 'hover', 'focus'],\n    textDecoration: ['responsive', 'hover', 'focus'],\n    textTransform: ['responsive'],\n    userSelect: ['responsive'],\n    verticalAlign: ['responsive'],\n    visibility: ['responsive'],\n    whitespace: ['responsive'],\n    width: ['responsive'],\n    wordBreak: ['responsive'],\n    zIndex: ['responsive'],\n  },\n  corePlugins: {},\n  plugins: [],\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"esnext\",\n    \"module\": \"esnext\",\n    \"strict\": true,\n    \"jsx\": \"preserve\",\n    \"importHelpers\": true,\n    \"moduleResolution\": \"node\",\n    \"experimentalDecorators\": true,\n    \"esModuleInterop\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"sourceMap\": true,\n    \"baseUrl\": \".\",\n    \"skipLibCheck\": true,\n    \"allowJs\": true,\n    \"types\": [\n      \"webpack-env\"\n    ],\n    \"paths\": {\n      \"@/*\": [\n        \"src/*\"\n      ],\n      \"*\": [\"node_modules/*\"]\n    },\n    \"lib\": [\n      \"esnext\",\n      \"dom\",\n      \"dom.iterable\",\n      \"scripthost\"\n    ]\n  },\n  \"include\": [\n    \"src/**/*.ts\",\n    \"src/**/*.tsx\",\n    \"src/**/*.vue\",\n    \"tests/**/*.ts\",\n    \"tests/**/*.tsx\"\n  ],\n  \"exclude\": [\n    \"node_modules\",\n    \"node_modules/gray-matter/gray-matter.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "vue.config.js",
    "content": "const path = require('path')\n\nfunction resolve(dir) {\n  return path.join(__dirname, dir)\n}\n\nmodule.exports = {\n  css: {\n    loaderOptions: {\n      less: {\n        import: [\n          resolve('src/assets/styles/var.less'),\n        ],\n        modifyVars: {\n          'btn-height-base': '30px',\n          'input-height-base': '30px',\n        },\n        javascriptEnabled: true,\n      },\n    },\n  },\n  pluginOptions: {\n    electronBuilder: {\n      nodeIntegration: true,\n      builderOptions: {\n        productName: 'Gridea',\n        win: {\n          icon: './public/app-icons/gridea.ico',\n          // target: [\n          //   {\n          //     target: 'nsis',\n          //     arch: [\n          //       'ia32',\n          //       'x64',\n          //     ],\n          //   },\n          // ],\n        },\n        mac: {\n          icon: './public/app-icons/gridea.icns',\n        },\n        linux: {\n          icon: './public/app-icons/gridea.png',\n          target: [\n            {\n              target: 'AppImage',\n            },\n            {\n              target: 'deb',\n            },\n            {\n              target: 'snap',\n            },\n          ],\n        },\n        asar: false,\n        nsis: {\n          oneClick: false, // 是否一键安装\n          allowElevation: true, // 允许请求提升。 如果为false，则用户必须使用提升的权限重新启动安装程序。\n          allowToChangeInstallationDirectory: true, // 允许修改安装目录\n          createDesktopShortcut: true, // 创建桌面图标\n          createStartMenuShortcut: true, // 创建开始菜单图标\n          shortcutName: 'Gridea', // 图标名称\n        },\n        publish: ['github'],\n      },\n      // mainProcessWatch: [\n      //   'src/server/**/*',\n      // ],\n    },\n  },\n}\n"
  }
]