├── template
├── theme
│ ├── robots.txt
│ ├── .gitignore
│ ├── favicon.png
│ ├── source
│ │ ├── js
│ │ │ ├── searchTpl.html
│ │ │ ├── jquery.unveil.js
│ │ │ ├── index.js
│ │ │ └── highlight.pack.js
│ │ └── css
│ │ │ ├── color.css
│ │ │ ├── global.css
│ │ │ ├── code.css
│ │ │ ├── archive-tag.css
│ │ │ ├── index.css
│ │ │ ├── article.css
│ │ │ ├── content.css
│ │ │ └── page.css
│ ├── postcss.config.js
│ ├── _footer.html
│ ├── _search.html
│ ├── _comment.html
│ ├── _header.html
│ ├── bundle
│ │ ├── index.js.LICENSE.txt
│ │ ├── searchWorker.js
│ │ ├── index.css
│ │ └── main.css
│ ├── package.json
│ ├── webpack.config.js
│ ├── tag.html
│ ├── archive.html
│ ├── _head.html
│ ├── page.html
│ ├── article.html
│ └── config.yml
├── README.md
├── source
│ ├── images
│ │ ├── avatar.png
│ │ ├── example.png
│ │ └── example-en.png
│ ├── about.me.md
│ ├── ink-blog-tool.md
│ └── ink-blog-tool-en.md
└── config.yml
├── .gitignore
├── Dockerfile
├── docker-ink-runtime
└── Dockerfile
├── .github
├── ISSUE_TEMPLATE
│ ├── feature_request.md
│ └── bug_report.md
└── workflows
│ └── release.yml
├── funcs.go
├── go.mod
├── release.sh
├── go.sum
├── util.go
├── serve.go
├── README.md
├── render.go
├── api.go
├── main.go
└── parse.go
/template/theme/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 |
--------------------------------------------------------------------------------
/template/README.md:
--------------------------------------------------------------------------------
1 | # Blog
2 | Quick start blog for ink
3 |
--------------------------------------------------------------------------------
/template/theme/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | npm-debug.log
3 | yarn.lock
--------------------------------------------------------------------------------
/template/theme/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/InkProject/ink/HEAD/template/theme/favicon.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ink
2 | ink.exe
3 | public
4 | release
5 | debian
6 | npm-debug.log
7 | editor_old
8 | .idea
9 | .DS_Store
--------------------------------------------------------------------------------
/template/source/images/avatar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/InkProject/ink/HEAD/template/source/images/avatar.png
--------------------------------------------------------------------------------
/template/source/images/example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/InkProject/ink/HEAD/template/source/images/example.png
--------------------------------------------------------------------------------
/template/source/images/example-en.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/InkProject/ink/HEAD/template/source/images/example-en.png
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1.18
2 | ADD . /code
3 | WORKDIR /code
4 | RUN go install
5 | EXPOSE 8000
6 | CMD ["ink", "preview", "template"]
--------------------------------------------------------------------------------
/template/theme/source/js/searchTpl.html:
--------------------------------------------------------------------------------
1 |
2 | {{title}}
3 |
4 |
5 |
--------------------------------------------------------------------------------
/template/source/about.me.md:
--------------------------------------------------------------------------------
1 | type: page
2 | title: "关于作者"
3 | author: me
4 |
5 | ---
6 |
7 | ## 纸小墨
8 |
9 | 构建只为纯粹书写的博客。
10 |
11 | [https://github.com/InkProject/ink](https://github.com/InkProject/ink)
--------------------------------------------------------------------------------
/template/theme/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: [
3 | require('autoprefixer')({
4 | overrideBrowserslist: ['last 5 version', '>1%', 'ios 7']
5 | }),
6 | require('precss'),
7 | require("postcss-import")
8 | ]
9 | }
--------------------------------------------------------------------------------
/template/theme/source/css/color.css:
--------------------------------------------------------------------------------
1 | $black: #293846;
2 |
3 | $grey: #7f8c8d;
4 | $greyLight: #a1b2b4;
5 | $greyLighter: #ddd;
6 |
7 | $whiteLighter: #fcfcfc;
8 | $whiteLight: #f7f7f7;
9 | $white: #eee;
10 | $whiteDark: #ddd;
11 |
12 | $greenLighter: #94ffd7;
13 | $green: #009a61;
14 | $greenDark: #004e31;
15 |
--------------------------------------------------------------------------------
/template/theme/_footer.html:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/template/theme/_search.html:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/template/theme/_comment.html:
--------------------------------------------------------------------------------
1 | {{if .Site.Comment}}
2 |
3 |
10 | {{end}}
11 |
--------------------------------------------------------------------------------
/template/theme/_header.html:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/docker-ink-runtime/Dockerfile:
--------------------------------------------------------------------------------
1 | # 本 Dockerfile 用来构建 ink-runtime 为基础镜像,以便使用.
2 |
3 | # 基于
4 | # 生成 ink 的可执行文件
5 | FROM golang:alpine AS ink-build
6 | RUN apk add --update git
7 | RUN go get -u -v github.com/taadis/ink
8 | WORKDIR /go/src/github.com/taadis/ink/
9 | RUN go install -v
10 | WORKDIR /go/bin/
11 | RUN ls -l
12 |
13 | # 基于
14 | # 生成最小化的 ink 基础镜像.
15 | # 使用过程中只需要基于镜像 taadis/ink-runtime 即可.
16 | FROM alpine:latest AS ink-runtime
17 | WORKDIR /ink/
18 | COPY --from=ink-build /go/bin/ink ./
19 | COPY --from=ink-build /go/src/github.com/taadis/ink/template/ ./template/
20 | RUN ls -l
21 | EXPOSE 8000
22 | CMD ["./ink","serve","template"]
23 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 |
5 | ---
6 |
7 | **Is your feature request related to a problem? Please describe.**
8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
9 |
10 | **Describe the solution you'd like**
11 | A clear and concise description of what you want to happen.
12 |
13 | **Describe alternatives you've considered**
14 | A clear and concise description of any alternative solutions or features you've considered.
15 |
16 | **Additional context**
17 | Add any other context or screenshots about the feature request here.
18 |
--------------------------------------------------------------------------------
/template/theme/bundle/index.js.LICENSE.txt:
--------------------------------------------------------------------------------
1 | /*!
2 | * Sizzle CSS Selector Engine v2.3.9
3 | * https://sizzlejs.com/
4 | *
5 | * Copyright JS Foundation and other contributors
6 | * Released under the MIT license
7 | * https://js.foundation/
8 | *
9 | * Date: 2022-12-19
10 | */
11 |
12 | /*!
13 | * jQuery JavaScript Library v3.6.3
14 | * https://jquery.com/
15 | *
16 | * Includes Sizzle.js
17 | * https://sizzlejs.com/
18 | *
19 | * Copyright OpenJS Foundation and other contributors
20 | * Released under the MIT license
21 | * https://jquery.org/license
22 | *
23 | * Date: 2022-12-20T21:28Z
24 | */
25 |
26 | /*! highlight.js v8.9.1 | BSD3 License | git.io/hljslicense */
27 |
--------------------------------------------------------------------------------
/funcs.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "html/template"
5 | "os"
6 | "path/filepath"
7 | )
8 |
9 | type FuncContext struct {
10 | rootPath string
11 | themePath string
12 | publicPath string
13 | currentCwd string
14 | global *GlobalConfig
15 | }
16 |
17 | func (ctx FuncContext) FuncMap() template.FuncMap {
18 | return template.FuncMap{
19 | "i18n": ctx.I18n,
20 | "readFile": ctx.ReadFile,
21 | }
22 | }
23 |
24 | func (ctx FuncContext) I18n(val string) string {
25 | return ctx.global.I18n[val]
26 | }
27 |
28 | func (ctx FuncContext) ReadFile(path string) template.HTML {
29 | bytes, _ := os.ReadFile(filepath.Join(ctx.currentCwd, path))
30 | return template.HTML(bytes)
31 | }
32 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/InkProject/ink
2 |
3 | go 1.23.0
4 |
5 | toolchain go1.24.3
6 |
7 | require (
8 | github.com/InkProject/ink.go v0.0.0-20160120061933-86de6d066e8d
9 | github.com/edwardrf/symwalk v0.1.0
10 | github.com/fsnotify/fsnotify v1.9.0
11 | github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b
12 | github.com/gorilla/feeds v1.2.0
13 | github.com/gorilla/websocket v1.5.3
14 | github.com/snabb/sitemap v1.0.4
15 | github.com/urfave/cli/v2 v2.27.7
16 | gopkg.in/yaml.v2 v2.4.0
17 | )
18 |
19 | require (
20 | github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
21 | github.com/russross/blackfriday/v2 v2.1.0 // indirect
22 | github.com/snabb/diagio v1.0.4 // indirect
23 | github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
24 | golang.org/x/sys v0.33.0 // indirect
25 | )
26 |
--------------------------------------------------------------------------------
/release.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # build binary releases for ink
4 |
5 | build () {
6 | echo "building for $1 $2..."
7 | suffix=""
8 | if [ $1 = "windows" ]
9 | then
10 | suffix=".exe"
11 | fi
12 | GOOS=$1 GOARCH=$2 go build -o release/ink$suffix
13 | cd release
14 | if [ $1 = "linux" ]
15 | then
16 | tar cvf - blog/* ink$suffix | gzip -9 - > ink_$1_$2.tar.gz
17 | else
18 | 7z a -tzip -r ink_$1_$2.zip blog ink$suffix
19 | fi
20 | rm -rf ink$suffix
21 | cd ..
22 | }
23 |
24 | rm -rf release
25 | mkdir -p release
26 |
27 | rsync -av template/* release/blog --delete --exclude public --exclude theme/node_modules
28 |
29 | build linux 386
30 | build linux amd64
31 | build linux arm
32 | build linux arm64
33 |
34 | build darwin amd64
35 | build darwin arm64
36 |
37 | build windows 386
38 | build windows amd64
39 | build windows arm
40 | build windows arm64
41 |
42 | rm -rf release/blog
43 |
--------------------------------------------------------------------------------
/template/theme/source/css/global.css:
--------------------------------------------------------------------------------
1 | @define-mixin font-main {
2 | font-family: "Helvetica Neue", Arial, "Hiragino Sans GB", "WenQuanYi Micro Hei", "Microsoft YaHei", sans-serif;
3 | }
4 |
5 | @define-mixin font-code {
6 | font-family: consolas, monaco, "Source Code Pro", hack, monospace;
7 | }
8 |
9 | html, body, input {
10 | margin: 0;
11 | @mixin font-main;
12 | }
13 |
14 | html, body {
15 | height: 100%;
16 | }
17 |
18 | ul {
19 | list-style: none;
20 | margin: 0;
21 | padding: 0;
22 | }
23 |
24 | a {
25 | text-decoration: none;
26 | &:hover {
27 | opacity: 0.9;
28 | }
29 | }
30 |
31 | *::selection {
32 | background-color: $black;
33 | color: $whiteLighter;
34 | }
35 |
36 | /*clearfix*/
37 | .clearfix:before, .clearfix:after {
38 | content: " ";
39 | display: table;
40 | }
41 |
42 | .clearfix:after {
43 | clear: both;
44 | }
45 |
46 | .clearfix {
47 | zoom: 1;
48 | }
49 |
--------------------------------------------------------------------------------
/template/config.yml:
--------------------------------------------------------------------------------
1 | site:
2 | title: "纸小墨官方博客"
3 | subtitle: "构建只为纯粹书写的博客"
4 | limit: 10
5 | theme: theme
6 | lang: zh-cn
7 | url: "https://example.io/"
8 | comment: username
9 | logo: "-/images/avatar.png"
10 | config:
11 | CustomVar: "config 下是纸小墨的自定义变量,定义时建议使用正确大小写"
12 | # link: "{category}/{year}/{month}/{day}/{title}.html"
13 | # link: "{year}{month}{day}{hour}{minute}{second}.html"
14 | # root: "/blog"
15 |
16 | authors:
17 | me:
18 | name: "纸小墨"
19 | intro: "构建只为纯粹书写的博客"
20 | avatar: "-/images/avatar.png"
21 |
22 | build:
23 | # output: "public"
24 | port: 8000
25 | # These files are copied to the public folder when 'ink build' is used
26 | copy:
27 | - "source/images"
28 | # Executed commands when 'ink publish' is used
29 | publish: |
30 | git add . -A
31 | git commit -m "update"
32 | git push origin
33 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 |
5 | ---
6 |
7 | **Describe the bug**
8 | A clear and concise description of what the bug is.
9 |
10 | **To Reproduce**
11 | Steps to reproduce the behavior:
12 | 1. Go to '...'
13 | 2. Click on '....'
14 | 3. Scroll down to '....'
15 | 4. See error
16 |
17 | **Expected behavior**
18 | A clear and concise description of what you expected to happen.
19 |
20 | **Screenshots**
21 | If applicable, add screenshots to help explain your problem.
22 |
23 | **Desktop (please complete the following information):**
24 | - OS: [e.g. iOS]
25 | - Browser [e.g. chrome, safari]
26 | - Version [e.g. 22]
27 |
28 | **Smartphone (please complete the following information):**
29 | - Device: [e.g. iPhone6]
30 | - OS: [e.g. iOS8.1]
31 | - Browser [e.g. stock browser, safari]
32 | - Version [e.g. 22]
33 |
34 | **Additional context**
35 | Add any other context about the problem here.
36 |
--------------------------------------------------------------------------------
/template/theme/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "InkPaperDefaultTheme",
3 | "version": "0.1.1",
4 | "scripts": {
5 | "start": "./node_modules/.bin/webpack --watch --mode=development",
6 | "build": "./node_modules/.bin/webpack"
7 | },
8 | "description": "InkPaper Default Theme",
9 | "devDependencies": {
10 | "autoprefixer": "^10.4.2",
11 | "css-loader": "^6.6.0",
12 | "css-minimizer-webpack-plugin": "^4.2.2",
13 | "file-loader": "^6.2.0",
14 | "mini-css-extract-plugin": "^2.6.0",
15 | "postcss": "^8.4.7",
16 | "postcss-import": "^15.1.0",
17 | "postcss-loader": "^7.0.2",
18 | "precss": "^4.0.0",
19 | "raw-loader": "^4.0.2",
20 | "source-map-loader": "^4.0.1",
21 | "style-loader": "^3.3.1",
22 | "uglifyjs-webpack-plugin": "^2.2.0",
23 | "url-loader": "^4.1.1",
24 | "webpack": "^5.69.1",
25 | "webpack-cli": "^5.0.1"
26 | },
27 | "dependencies": {
28 | "highlight.js": "^11.7.0",
29 | "jquery": "^3.6.3"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/template/theme/source/css/code.css:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Original style from softwaremaniacs.org (c) Ivan Sagalaev
4 |
5 | */
6 |
7 | .hljs {
8 | display: block;
9 | overflow-x: auto;
10 | padding: 0.5em;
11 | background: #f0f0f0;
12 | }
13 |
14 | .hljs,
15 | .hljs-subst,
16 | .hljs-tag .hljs-title {
17 | color: black;
18 | }
19 |
20 | .hljs-string,
21 | .hljs-title,
22 | .hljs-section,
23 | .hljs-attribute,
24 | .hljs-selector-id,
25 | .hljs-selector-class,
26 | .hljs-variable,
27 | .hljs-template-variable,
28 | .hljs-symbol,
29 | .hljs-deletion {
30 | color: #800;
31 | }
32 |
33 | .hljs-comment,
34 | .hljs-quote {
35 | color: #888;
36 | }
37 |
38 | .hljs-number,
39 | .hljs-regexp,
40 | .hljs-literal,
41 | .hljs-addition,
42 | .hljs-bullet,
43 | .hljs-link {
44 | color: #080;
45 | }
46 |
47 | .hljs-meta {
48 | color: #88f;
49 | }
50 |
51 | .hljs-keyword,
52 | .hljs-selector-tag,
53 | .hljs-title,
54 | .hljs-name,
55 | .hljs-section,
56 | .hljs-built_in,
57 | .hljs-doctag,
58 | .hljs-type,
59 | .hljs-strong {
60 | font-weight: bold;
61 | }
62 |
63 | .hljs-emphasis {
64 | font-style: italic;
65 | }
66 |
--------------------------------------------------------------------------------
/template/theme/webpack.config.js:
--------------------------------------------------------------------------------
1 | var MiniCssExtractPlugin = require("mini-css-extract-plugin")
2 | var UglifyJsPlugin = require('uglifyjs-webpack-plugin')
3 | var CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
4 |
5 | module.exports = {
6 | entry: './source/js/index.js',
7 | output: {
8 | path: __dirname + '/bundle/',
9 | filename: 'index.js'
10 | },
11 | module: {
12 | rules: [{
13 | test: /\.css$/i, use: [MiniCssExtractPlugin.loader, "css-loader", "postcss-loader"],
14 | }, { test: /\.woff|\.woff2|\.svg|.eot|\.ttf/, use: ['url-loader?limit=8192'] }, {
15 | test: /\.html$/i, use: [{ loader: 'raw-loader' },],
16 | },]
17 | },
18 | plugins: [
19 | new MiniCssExtractPlugin({
20 | filename: "index.css",
21 | })
22 | ],
23 | optimization: {
24 | minimizer: [new UglifyJsPlugin({
25 | parallel: true,
26 | uglifyOptions: {
27 | output: {
28 | comments: false,
29 | },
30 | },
31 | }), new CssMinimizerPlugin({
32 | parallel: true,
33 | minimizerOptions: {
34 | preset: [
35 | 'default',
36 | {
37 | discardComments: { removeAll: true },
38 | }
39 | ],
40 | },
41 | }),],
42 | },
43 | watch: false
44 | }
45 |
--------------------------------------------------------------------------------
/template/theme/tag.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{template "head" .}}
5 |
6 |
7 | {{.Site.Title}}
8 |
9 |
10 |
11 | {{template "header" .}}
12 |
13 |
14 | {{.Site.Title}}
15 | {{.Site.Subtitle}}
16 |
17 |
21 |
28 |
29 |
30 | {{template "footer" .}}
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Build artifacts & Release
2 |
3 | on:
4 | schedule:
5 | - cron: "0 4 * * *"
6 | workflow_dispatch:
7 | inputs:
8 | tag_name:
9 | description: "Tag name for release"
10 | required: false
11 | default: nightly
12 | push:
13 | tags:
14 | - v[0-9]+.[0-9]+.[0-9]+*
15 |
16 | jobs:
17 | release:
18 | permissions:
19 | contents: write
20 | runs-on: ubuntu-latest
21 | steps:
22 | - name: Checkout
23 | uses: actions/checkout@v3
24 | - name: Setup Go
25 | uses: actions/setup-go@v3
26 | with:
27 | go-version: "stable"
28 | - name: Build
29 | run: sh ./release.sh
30 | - if: github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.tag_name == 'nightly')
31 | name: Release Nightly
32 | uses: softprops/action-gh-release@v1
33 | with:
34 | files: release/ink*
35 | prerelease: true
36 | tag_name: nightly
37 | name: Nightly build
38 | generate_release_notes: true
39 | fail_on_unmatched_files: true
40 | - if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
41 | name: Release
42 | uses: softprops/action-gh-release@v1
43 | with:
44 | files: release/ink*
45 | prerelease: false
46 | name: Release ${{ github.ref }}
47 | generate_release_notes: true
48 | fail_on_unmatched_files: true
49 |
--------------------------------------------------------------------------------
/template/theme/source/js/jquery.unveil.js:
--------------------------------------------------------------------------------
1 | /**
2 | * jQuery Unveil
3 | * A very lightweight jQuery plugin to lazy load images
4 | * http://luis-almeida.github.com/unveil
5 | *
6 | * Licensed under the MIT license.
7 | * Copyright 2013 Luís Almeida
8 | * https://github.com/luis-almeida
9 | *
10 | *
11 | * Modified by @w568w to work with the lastest jQuery.
12 | */
13 | import $ from 'jquery'
14 |
15 |
16 | (function () {
17 | $.fn.unveil = function (threshold, callback) {
18 |
19 | let $w = $(window),
20 | th = threshold || 0,
21 | retina = window.devicePixelRatio > 1,
22 | attrib = retina ? "data-src-retina" : "data-src",
23 | images = this,
24 | loaded;
25 |
26 | this.one("unveil", function () {
27 | let source = this.getAttribute(attrib);
28 | source = source || this.getAttribute("data-src");
29 | if (source) {
30 | this.setAttribute("src", source);
31 | if (typeof callback === "function") callback.call(this);
32 | }
33 | });
34 |
35 | function unveil() {
36 | const inview = images.filter(function () {
37 | let $e = $(this);
38 | if ($e.is(":hidden")) return;
39 |
40 | let wt = $w.scrollTop(),
41 | wb = wt + $w.height(),
42 | et = $e.offset().top,
43 | eb = et + $e.height();
44 |
45 | return eb >= wt - th && et <= wb + th;
46 | });
47 |
48 | loaded = inview.trigger("unveil");
49 | images = images.not(loaded);
50 | }
51 |
52 | $w.on("scroll.unveil resize.unveil lookup.unveil", unveil);
53 |
54 | unveil();
55 |
56 | return this;
57 |
58 | };
59 | })();
--------------------------------------------------------------------------------
/template/theme/source/css/archive-tag.css:
--------------------------------------------------------------------------------
1 | .main {
2 | &.archive, &.tag {
3 | padding-top: 100px;
4 | padding-bottom: 100px;
5 | a {
6 | color: $green;
7 | text-decoration: none;
8 | border-bottom: 1px dashed $green;
9 | }
10 | .site {
11 | .title, .subtitle {
12 | text-align: left;
13 | @mixin font-main;
14 | }
15 | }
16 | .header {
17 | padding-top: 60px;
18 | font-size: 18px;
19 | .title {
20 | margin-top: 30px;
21 | margin-right: 5px;
22 | font-weight: bold;
23 | }
24 | .subtitle {
25 | font-style: italic;
26 | }
27 | }
28 | .archive-list {
29 | margin-top: 50px;
30 | .archive-item {
31 | margin: 40px 0;
32 | .archive-year {
33 | margin: 15px 0;
34 | color: $black;
35 | font-weight: bold;
36 | }
37 | .article-list {
38 | .article-item {
39 | margin-bottom: 10px;
40 | .date {
41 | color: $grey;
42 | margin-right: 20px;
43 | font-style: italic;
44 | }
45 | }
46 | }
47 | }
48 | }
49 | .tag-list {
50 | margin-top: 50px;
51 | .tag-item {
52 | margin-right: 10px;
53 | margin-bottom: 25px;
54 | float: left;
55 | .tag-name {
56 | padding: 5px 7px;
57 | border: 1px solid $white;
58 | border-radius: 3px;
59 | font-size: 14px;
60 | &:hover {
61 | background-color: $black;
62 | color: $whiteLighter;
63 | border: 1px solid $black;
64 | }
65 | }
66 | }
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/template/theme/archive.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{template "head" .}}
5 |
6 |
7 |
8 | {{.Site.Title}}
9 |
10 |
11 |
12 | {{template "header" .}}
13 |
14 |
15 | {{.Site.Title}}
16 | {{.Site.Subtitle}}
17 |
18 |
22 |
23 | {{range .Archive}}
24 | -
25 |
{{.Year}}
26 |
27 | {{range .Articles}}
28 | -
29 | {{.Date}}
30 | {{.Title}}
31 |
32 | {{end}}
33 |
34 |
35 | {{end}}
36 |
37 |
38 |
39 | {{template "footer" .}}
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/template/theme/bundle/searchWorker.js:
--------------------------------------------------------------------------------
1 | function load(url, callback) {
2 | var xhr
3 | if (typeof XMLHttpRequest !== 'undefined') xhr = new XMLHttpRequest()
4 | else {
5 | var versions = ["MSXML2.XmlHttp.5.0",
6 | "MSXML2.XmlHttp.4.0",
7 | "MSXML2.XmlHttp.3.0",
8 | "MSXML2.XmlHttp.2.0",
9 | "Microsoft.XmlHttp"]
10 |
11 | for(var i = 0, len = versions.length; i < len; i++) {
12 | try {
13 | xhr = new ActiveXObject(versions[i])
14 | break
15 | } catch(e) {}
16 | }
17 | }
18 | xhr.onreadystatechange = function() {
19 | if(xhr.readyState < 4) return
20 | if(xhr.status !== 200) return
21 | if(xhr.readyState === 4) callback(xhr)
22 | }
23 | xhr.open('GET', url, true)
24 | xhr.send('')
25 | }
26 |
27 | var data, lastKeyword
28 |
29 | var search = function(keyword) {
30 | var results = []
31 | var keywordStr = keyword.toLowerCase().replace(/ /g, ' ')
32 | var keywords = keywordStr.split(' ')
33 | for (var i = 0; i < data.length; i++) {
34 | var item = data[i]
35 | var title = item.title.toLowerCase()
36 | var preview = item.preview.toLowerCase()
37 | var content = item.content.toLowerCase()
38 | var isMatch = true
39 | for (var j = 0; j < keywords.length; j++) {
40 | var key = keywords[j]
41 | if (title.indexOf(key) == -1 &&
42 | preview.indexOf(key) == -1 &&
43 | content.indexOf(key) == -1) {
44 | isMatch = false
45 | break
46 | }
47 | }
48 | if (isMatch) results.push(item)
49 | }
50 | postMessage({
51 | keyword: keywordStr,
52 | keywords: keywords,
53 | results: results
54 | })
55 | }
56 |
57 |
58 | onmessage = function(event) {
59 | var action = event.data.action
60 | if (action == 'start') {
61 | load(event.data.root + '/index.json', function(xhr) {
62 | data = JSON.parse(xhr.responseText)
63 | if (lastKeyword) {
64 | search(lastKeyword)
65 | }
66 | })
67 | } else {
68 | if (data) {
69 | search(event.data.keyword)
70 | } else {
71 | lastKeyword = event.data.keyword
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/template/theme/source/css/index.css:
--------------------------------------------------------------------------------
1 | @import "color.css";
2 | @import "code.css";
3 | @import "global.css";
4 |
5 | @import "page.css";
6 | @import "article.css";
7 | @import "archive-tag.css";
8 | @import "content.css";
9 |
10 | .header-wrap {
11 | padding: 15px;
12 | position: absolute;
13 | z-index: 1;
14 | font-weight: bold;
15 | font-size: 18px;
16 | left: 0;
17 | right: 0;
18 | display: flex;
19 | justify-content: space-between;
20 | align-items: center;
21 | a {
22 | color: $greyLight;
23 | }
24 | .index {
25 | display: flex;
26 | align-items: center;
27 | .logo {
28 | width: 30px;
29 | height: 30px;
30 | border-radius: 50%;
31 | border: 1px solid $greyLighter;
32 | margin-right: 10px;
33 | }
34 | }
35 | .menu {
36 | display: flex;
37 | .menu-item {
38 | margin-right: 10px;
39 | }
40 | }
41 | }
42 | a.name {
43 | color: $black;
44 | }
45 | .main {
46 | color: $black;
47 | margin: 0 auto;
48 | max-width: 720px;
49 | padding: 0 15px;
50 | min-height: 100%;
51 | position:relative;
52 | .site {
53 | .logo {
54 | margin: 0 auto;
55 | width: 80px;
56 | height: 80px;
57 | border-radius: 50%;
58 | background-size: cover;
59 | background-repeat: no-repeat;
60 | border: 1px $greyLighter solid;
61 | }
62 | .title {
63 | margin: 0;
64 | margin-top: 30px;
65 | font-weight: bold;
66 | font-size: 32px;
67 | text-align: center;
68 | }
69 | .subtitle {
70 | @mixin font-main;
71 | font-style: italic;
72 | margin-top: 10px;
73 | font-size: 16px;
74 | color: $greyLight;
75 | text-align: center;
76 | font-weight: normal;
77 | }
78 | }
79 | &.about {
80 | .info {
81 | margin-top: 80px;
82 | text-align: center;
83 | }
84 | }
85 | }
86 | .container {
87 | height: auto;
88 | min-height: 100%;
89 | padding-bottom: 0;
90 | }
91 | .footer {
92 | width: 100%;
93 | font-size: 14px;
94 | border-top: 1px $white solid;
95 | color: $greyLight;
96 | background-color: $whiteLighter;
97 | clear: both;
98 | position: relative;
99 | height: 32px;
100 | margin-top: -36px;
101 | span {
102 | padding: 8px 15px;
103 | display: block;
104 | a {
105 | font-weight: bold;
106 | }
107 | }
108 | .copyright {
109 | float: left;
110 | }
111 | .publish {
112 | float: right;
113 | a {
114 | color: $greyLight;
115 | text-decoration: none;
116 | }
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/template/theme/source/css/article.css:
--------------------------------------------------------------------------------
1 | .main {
2 | &.article {
3 | padding-top: 120px;
4 | padding-bottom: 120px;
5 | .title {
6 | margin: 0;
7 | font-weight: bold;
8 | color: $black;
9 | font-size: 30px;
10 | line-height: 42px;
11 | }
12 | .info {
13 | margin-top: 10px;
14 | font-size: 14px;
15 | .avatar {
16 | width: 20px;
17 | height: 20px;
18 | margin-right: 10px;
19 | border-radius: 20px;
20 | background-size: cover;
21 | background-repeat: no-repeat;
22 | float: left;
23 | margin-top: -4px;
24 | border: 1px $greyLighter solid;
25 | }
26 | .name {
27 | margin-right: 5px;
28 | }
29 | .date {
30 | color: $grey;
31 | margin-right: 5px;
32 | }
33 | .tags {
34 | @mixin font-main;
35 | .tag {
36 | margin-right: 5px;
37 | color: $grey;
38 | text-decoration: none;
39 | }
40 | }
41 | }
42 | .recommend {
43 | margin-top: 50px;
44 | overflow: auto;
45 | .nav {
46 | width: 100%;
47 | .head {
48 | font-size: 12px;
49 | font-style: italic;
50 | margin-bottom: 10px;
51 | color: $grey;
52 | text-align: center;
53 | }
54 | .link {
55 | color: $black;
56 | text-align: center;
57 | display: block;
58 | }
59 | &.prev.more {
60 | width: 45%;
61 | text-align: left;
62 | float: left;
63 | .head, .link {
64 | text-align: left;
65 | }
66 | }
67 | &.next.more {
68 | width: 45%;
69 | text-align: right;
70 | float: right;
71 | .head, .link {
72 | text-align: right;
73 | }
74 | }
75 | }
76 | }
77 | .author {
78 | margin-top: 36px;
79 | padding-top: 40px;
80 | text-align: center;
81 | .avatar {
82 | margin: 0 auto;
83 | width: 70px;
84 | height: 70px;
85 | border-radius: 70px;
86 | background-size: cover;
87 | background-repeat: no-repeat;
88 | border: 20px white solid;
89 | &:before {
90 | border-top: 1px $greyLighter dashed;
91 | content: "";
92 | width: 100%;
93 | position: absolute;
94 | left: 0;
95 | right: 0;
96 | margin-top: 36px;
97 | z-index: -1;
98 | }
99 | }
100 | .name {
101 | margin-top: 20px;
102 | font-weight: bold;
103 | font-size: 16px;
104 | }
105 | .intro {
106 | @mixin font-main;
107 | font-style: italic;
108 | margin-top: 8px;
109 | font-size: 14px;
110 | color: $grey;
111 | }
112 | }
113 | #disqus_thread {
114 | margin-top: 50px;
115 | }
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/template/theme/_head.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | {{ $imgurl := printf "%s%s" .Site.Url .Site.Logo}}
11 | {{ if .Image }}
12 | {{ $imgurl = .Image }}
13 | {{ end }}
14 |
15 | {{ $subtitle := .Site.Subtitle }}
16 | {{ if .Subtitle }}
17 | {{ $subtitle = .Subtitle }}
18 | {{ end }}
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
54 |
55 |
56 |
57 |
58 | {{if .Develop}}
59 |
80 | {{end}}
81 |
--------------------------------------------------------------------------------
/template/theme/page.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{template "head" .}}
5 |
6 |
7 | {{.Site.Title}}
8 |
9 |
10 |
11 | {{template "header" .}}
12 |
13 |
25 |
26 | {{range .Articles}}
27 | -
28 | {{if .Top}}[{{i18n "top"}}]{{end}}{{.Title}}
29 | {{if .Cover}}{{end}}
30 | {{if .Preview}}{{end}}
31 |
32 | {{if .Author.Avatar}}{{end}}
33 | {{if .Author.Name}}{{.Author.Name}}{{end}}
34 | {{if .Update}}
35 |
36 | {{else}}
37 |
38 | {{end}}
39 |
40 | {{range .Tags}}{{.}}{{end}}
41 |
42 |
43 |
44 | {{end}}
45 |
46 |
47 | {{if .Prev}}{{i18n "prev_page"}}{{end}}
48 | {{if ne .Total 1}}{{.Page}} / {{.Total}}{{end}}
49 | {{if .Next}}{{i18n "next_page"}}{{end}}
50 |
51 |
52 |
53 | {{template "footer" .}}
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/InkProject/ink.go v0.0.0-20160120061933-86de6d066e8d h1:cKKHWaSZqckOTguui9bShV83raKxQKpTB9X4M7WbeaY=
2 | github.com/InkProject/ink.go v0.0.0-20160120061933-86de6d066e8d/go.mod h1:sGm8pED0mDi7pXIgjvCf7/m7LMmLSWpz3bhtB8KoKL8=
3 | github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
4 | github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
5 | github.com/edwardrf/symwalk v0.1.0 h1:NAakDFfWD1ef65UDiYREFvWiu1+RqRC2dJIDXJdpFjM=
6 | github.com/edwardrf/symwalk v0.1.0/go.mod h1:qlC1zULcOcrPbB0Mog6yENGnszBOdCM59+rLC6g607Y=
7 | github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
8 | github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
9 | github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg=
10 | github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
11 | github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b h1:EY/KpStFl60qA17CptGXhwfZ+k1sFNJIUNR8DdbcuUk=
12 | github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
13 | github.com/gorilla/feeds v1.2.0 h1:O6pBiXJ5JHhPvqy53NsjKOThq+dNFm8+DFrxBEdzSCc=
14 | github.com/gorilla/feeds v1.2.0/go.mod h1:WMib8uJP3BbY+X8Szd1rA5Pzhdfh+HCCAYT2z7Fza6Y=
15 | github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
16 | github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
17 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
18 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
19 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
20 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
21 | github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
22 | github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
23 | github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
24 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
25 | github.com/snabb/diagio v1.0.4 h1:XnlKoBarZWiAEnNBYE5t1nbvJhdaoTaW7IBzu0R4AqM=
26 | github.com/snabb/diagio v1.0.4/go.mod h1:Y+Pja4UJrskCOKaLxOfa8b8wYSVb0JWpR4YFNHuzjDI=
27 | github.com/snabb/sitemap v1.0.4 h1:BC6cPW5jXLsKWtlYQKD2s1W58CarvNzqOmdl680uQPw=
28 | github.com/snabb/sitemap v1.0.4/go.mod h1:815/fxQQ8Tt7Eqwe8Lcat4ax73zuHyPxWBZySnbaxkc=
29 | github.com/urfave/cli/v2 v2.27.7 h1:bH59vdhbjLv3LAvIu6gd0usJHgoTTPhCFib8qqOwXYU=
30 | github.com/urfave/cli/v2 v2.27.7/go.mod h1:CyNAG/xg+iAOg0N4MPGZqVmv2rCoP267496AOXUZjA4=
31 | github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
32 | github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
33 | golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
34 | golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
35 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
36 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
37 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
38 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
39 |
--------------------------------------------------------------------------------
/template/theme/source/css/content.css:
--------------------------------------------------------------------------------
1 | .main {
2 | .content {
3 | @mixin font-main;
4 | margin-top: 70px;
5 | font-size: 16px;
6 | line-height: 1.7;
7 | color: $black;
8 | h1, h2, h3, h4, h5, h6 {
9 | @mixin font-main;
10 | & + p {
11 | margin-top: 10px;
12 | }
13 | }
14 | h1 {
15 | font-size: 28px;
16 | margin-top: 40px;
17 | margin-bottom: 10px;
18 | }
19 | h2 {
20 | font-size: 24px;
21 | margin-top: 40px;
22 | margin-bottom: 10px;
23 | }
24 | h3 {
25 | font-size: 18px;
26 | margin-top: 40px;
27 | margin-bottom: 10px;
28 | }
29 | p {
30 | margin-bottom: 30px;
31 | margin-top: 30px;
32 | text-align: left;
33 | }
34 | &.preview {
35 | p {
36 | margin-bottom: 10px;
37 | margin-top: 10px;
38 | }
39 | }
40 | a {
41 | color: $green;
42 | text-decoration: none;
43 | border-bottom: 1px dashed $green;
44 | &:hover {
45 | color: $greenDark;
46 | border-bottom: 1px dashed $greenDark;
47 | }
48 | }
49 | code {
50 | @mixin font-code;
51 | font-size: 14px;
52 | padding: 1px 4px;
53 | border-radius: 3px;
54 | margin: 0px 3px;
55 | background-color: $whiteLight;
56 | color: $green;
57 | }
58 | pre {
59 | margin: 0;
60 | code {
61 | @mixin font-code;
62 | color: inherit;
63 | font-size: 14px;
64 | margin: 0;
65 | padding: 10px 15px;
66 | border-radius: 6px;
67 | border: 2px dashed $white;
68 | background-color: $whiteLighter;
69 | display: block;
70 | overflow: auto;
71 | }
72 | }
73 | blockquote {
74 | border-left: 4px $green solid;
75 | padding: 0px 10px 0px 20px;
76 | margin: 25px 0;
77 | margin-left: -23px;
78 | font-style: italic;
79 | }
80 | table {
81 | font-size: 14px;
82 | width: 100%;
83 | border-width: 1px;
84 | border-color: $whiteDark;
85 | border-collapse: collapse;
86 | th {
87 | border-width: 1px;
88 | padding: 5px;
89 | border-style: solid;
90 | border-color: $whiteDark;
91 | background-color: $white;
92 | }
93 | td {
94 | border-width: 1px;
95 | padding: 5px;
96 | border-style: solid;
97 | border-color: $whiteDark;
98 | background-color: $whiteLighter;
99 | }
100 | }
101 | ul {
102 | list-style: circle;
103 | padding-left: 40px;
104 | li {
105 | margin: 5px 0;
106 | }
107 | }
108 | ol {
109 | padding-left: 40px;
110 | li {
111 | margin: 5px 0;
112 | }
113 | }
114 | hr {
115 | margin: 25px 0;
116 | border: 0;
117 | border-top: 1px dashed $whiteDark;
118 | }
119 | img {
120 | margin: 30px auto;
121 | max-width: 100%;
122 | display: block;
123 | opacity: 0.6;
124 | transition: opacity .3s ease-in;
125 | cursor: pointer;
126 | border-radius: 5px;
127 | }
128 | .image-alt {
129 | text-align: center;
130 | color: $grey;
131 | font-style: italic;
132 | margin-top: -10px;
133 | margin-bottom: 30px;
134 | }
135 | .searched {
136 | background-color: yellow;
137 | }
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/util.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "os"
7 | "runtime"
8 | "time"
9 | )
10 |
11 | const (
12 | CLR_W = ""
13 | CLR_R = "\x1b[31;1m"
14 | CLR_G = "\x1b[32;1m"
15 | CLR_B = "\x1b[34;1m"
16 | CLR_Y = "\x1b[33;1m"
17 | )
18 |
19 | const (
20 | DATE_FORMAT = "2006-01-02 15:04:05"
21 | DATE_FORMAT_WITH_TIMEZONE = "2006-01-02 15:04:05 -0700"
22 | )
23 |
24 | var exitCode int
25 |
26 | // Print log
27 | func Log(info interface{}) {
28 | fmt.Printf("%s\n", info)
29 | }
30 |
31 | // Print warning log
32 | func Warn(info interface{}) {
33 | if runtime.GOOS == "windows" {
34 | fmt.Printf("WARNING: %s\n", info)
35 | } else {
36 | fmt.Printf("%s%s\n%s", CLR_Y, info, "\x1b[0m")
37 | }
38 | }
39 |
40 | // Print error log
41 | func Error(info interface{}) {
42 | if runtime.GOOS == "windows" {
43 | fmt.Printf("ERR: %s\n", info)
44 | } else {
45 | fmt.Printf("%s%s\n%s", CLR_R, info, "\x1b[0m")
46 | }
47 | exitCode = 1
48 | }
49 |
50 | // Print error log and exit
51 | func Fatal(info interface{}) {
52 | Error(info)
53 | os.Exit(1)
54 | }
55 |
56 | // Parse date by std date string
57 | func ParseDate(dateStr string) time.Time {
58 | date, err := time.Parse(fmt.Sprint(DATE_FORMAT_WITH_TIMEZONE), dateStr)
59 | if err != nil {
60 | date, err = time.ParseInLocation(fmt.Sprint(DATE_FORMAT), dateStr, time.Now().Location())
61 | if err != nil {
62 | Fatal(err.Error())
63 | }
64 | }
65 | return date
66 | }
67 |
68 | // Check file if exist
69 | func Exists(path string) bool {
70 | _, err := os.Stat(path)
71 | if err == nil {
72 | return true
73 | }
74 | if os.IsNotExist(err) {
75 | return false
76 | }
77 | return false
78 | }
79 |
80 | // Check file if is directory
81 | func IsDir(path string) bool {
82 | file, err := os.Stat(path)
83 | if err != nil {
84 | return false
85 | }
86 | return file.IsDir()
87 | }
88 |
89 | // Copy folder and file
90 | // Refer to https://www.socketloop.com/tutorials/golang-copy-directory-including-sub-directories-files
91 | func CopyFile(source string, dest string) {
92 | sourcefile, err := os.Open(source)
93 | if err != nil {
94 | Fatal(err.Error())
95 | }
96 | destfile, err := os.Create(dest)
97 | if err != nil {
98 | Fatal(err.Error())
99 | }
100 | defer destfile.Close()
101 | defer wg.Done()
102 | _, err = io.Copy(destfile, sourcefile)
103 | if err != nil {
104 | Fatal(err.Error())
105 | }
106 | sourceinfo, err := os.Stat(source)
107 | if err != nil {
108 | Fatal(err.Error())
109 | }
110 | err = os.Chmod(dest, sourceinfo.Mode())
111 | if err != nil {
112 | Fatal(err.Error())
113 | }
114 | sourcefile.Close()
115 | }
116 |
117 | func CopyDir(source string, dest string) {
118 | sourceinfo, err := os.Stat(source)
119 | if err != nil {
120 | Fatal(err.Error())
121 | }
122 | err = os.MkdirAll(dest, sourceinfo.Mode())
123 | if err != nil {
124 | Fatal(err.Error())
125 | }
126 | directory, _ := os.Open(source)
127 | defer directory.Close()
128 | defer wg.Done()
129 | objects, err := directory.Readdir(-1)
130 | if err != nil {
131 | Fatal(err.Error())
132 | }
133 | for _, obj := range objects {
134 | sourcefilepointer := source + "/" + obj.Name()
135 | destinationfilepointer := dest + "/" + obj.Name()
136 | if obj.IsDir() {
137 | wg.Add(1)
138 | CopyDir(sourcefilepointer, destinationfilepointer)
139 | } else {
140 | wg.Add(1)
141 | go CopyFile(sourcefilepointer, destinationfilepointer)
142 | }
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/template/theme/article.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{template "head" .}}
5 |
6 |
7 |
8 | {{.Title}}
9 |
10 |
11 |
12 | {{template "header" .}}
13 |
14 | {{.Title}}
15 |
16 | {{if .Author.Avatar}}{{end}}
17 | {{if .Author.Name}}{{.Author.Name}}{{end}}
18 | {{if .Update}}
19 |
20 | {{else}}
21 |
22 | {{end}}
23 | {{range .Tags}}{{.}}{{end}}
24 |
25 | {{.Content}}
26 |
27 | {{if .Author.Avatar}}{{end}}
28 | {{.Author.Name}}
29 | {{.Author.Intro}}
30 |
31 |
32 | {{if .Prev}}
33 |
37 | {{end}}
38 | {{if .Next}}
39 |
43 | {{end}}
44 |
45 | {{template "comment" .}}
46 |
47 |
48 | {{template "footer" .}}
49 |
50 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/template/theme/config.yml:
--------------------------------------------------------------------------------
1 | # Copied files to public folder when build
2 | copy:
3 | - bundle
4 | - favicon.png
5 | - robots.txt
6 | # I18n Support
7 | lang:
8 | archive:
9 | en: ARCHIVE
10 | zh-cn: 归档
11 | zh-tw: 歸檔
12 | ru: Архив
13 | ja: アーカイブ
14 | de: ARCHIV
15 | pt-br: Arquivo
16 | tag:
17 | en: TAG
18 | zh-cn: 标签
19 | zh-tw: 標籤
20 | ru: Теги
21 | ja: タグ
22 | de: TAG
23 | pt-br: TAG
24 | rss:
25 | en: RSS
26 | zh-cn: 订阅
27 | zh-tw: 訂閱
28 | ru: RSS
29 | ja: RSS
30 | de: RSS
31 | pt-br: RSS
32 | articles:
33 | en: Articles
34 | zh-cn: 篇文章
35 | zh-tw: 篇文章
36 | ru: Записи
37 | ja: 記事
38 | de: Beiträge
39 | pt-br: Artigos
40 | updated:
41 | en: updated
42 | zh-cn: 更新
43 | zh-tw: 更新
44 | ru: обновлено
45 | ja: 更新
46 | de: geändert
47 | pt-br: atualizado
48 | prev_page:
49 | en: Prev Page
50 | zh-cn: ◀ 上一页
51 | zh-tw: ◀ 上一頁
52 | ru: Назад
53 | ja: 前のページ
54 | de: Seite zurück
55 | pt-br: Página Anterior
56 | next_page:
57 | en: Next Page
58 | zh-cn: 下一页 ▶
59 | zh-tw: 下一頁 ▶
60 | ru: Далее
61 | ja: 次のページ
62 | de: Seite vor
63 | pt-br: Próxima Página
64 | prev_reading:
65 | en: Prev Article
66 | zh-cn: ◀ 上一篇
67 | zh-tw: ◀ 上一篇
68 | ru: Предыдущая запись
69 | ja: 新しい記事
70 | de: Vorheriger Beitrag
71 | pt-br: Artigo Anterior
72 | next_reading:
73 | en: Next Article
74 | zh-cn: 下一篇 ▶
75 | zh-tw: 下一篇 ▶
76 | ru: Следующая запись
77 | ja: 古い記事
78 | de: Nächster Beitrag
79 | pt-br: Próximo Artigo
80 | top:
81 | en: TOP
82 | zh-cn: 置顶
83 | zh-tw: 置頂
84 | ru: Наверх
85 | ja: TOP
86 | de: Angeheftet
87 | pt-br: Acima
88 | search:
89 | en: Search
90 | zh-cn: 搜索
91 | zh-tw: 搜尋
92 | ru: Поиск
93 | ja: 検索
94 | de: Suche
95 | pt-br: Busca
96 | since_year:
97 | en: " years ago"
98 | zh-cn: 年前
99 | zh-tw: 年前
100 | ru: " лет назад"
101 | ja: 年前
102 | de: " Jahre"
103 | pt-br: " anos atrás"
104 | since_month:
105 | en: " months ago"
106 | zh-cn: 个月前
107 | zh-tw: 個月前
108 | ru: " месяцев назад"
109 | ja: ヶ月前
110 | de: " Monate"
111 | pt-br: " meses atrás"
112 | since_day:
113 | en: " days ago"
114 | zh-cn: 天前
115 | zh-tw: 天前
116 | ru: " дней назад"
117 | ja: 日前
118 | de: " Tage"
119 | pt-br: " dias atrás"
120 | since_hour:
121 | en: " hours ago"
122 | zh-cn: 小时前
123 | zh-tw: 小時前
124 | ru: " часов назад"
125 | ja: 時間前
126 | de: " Stunden"
127 | pt-br: " horas atrás"
128 | since_minute:
129 | en: " minutes ago"
130 | zh-cn: 分钟前
131 | zh-tw: 分鐘前
132 | ru: " минут назад"
133 | ja: 分前
134 | de: " Minuten"
135 | pt-br: " minutos atrás"
136 | since_second:
137 | en: " seconds ago"
138 | zh-cn: 秒前
139 | zh-tw: 秒前
140 | ru: " секунд назад"
141 | ja: 秒前
142 | de: " Sekunden"
143 | pt-br: " segundos atrás"
144 |
--------------------------------------------------------------------------------
/serve.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "os"
5 | "path/filepath"
6 | "reflect"
7 |
8 | "github.com/InkProject/ink.go"
9 | "github.com/edwardrf/symwalk"
10 | "github.com/fsnotify/fsnotify"
11 | "github.com/gorilla/websocket"
12 | )
13 |
14 | var watcher *fsnotify.Watcher
15 | var conn *websocket.Conn
16 |
17 | func buildWatchList() (files []string, dirs []string) {
18 | dirs = []string{
19 | filepath.Join(rootPath, "source"),
20 | }
21 | files = []string{
22 | filepath.Join(rootPath, "config.yml"),
23 | filepath.Join(themePath),
24 | }
25 |
26 | // Add files and directories defined in theme's config.yml to watcher
27 | for _, themeCopiedPath := range themeConfig.Copy {
28 | if themeCopiedPath != "" {
29 | fullPath := filepath.Join(themePath, themeCopiedPath)
30 | s, err := os.Stat(fullPath)
31 | if s == nil || err != nil {
32 | continue
33 | }
34 |
35 | if s.IsDir() {
36 | dirs = append(dirs, fullPath)
37 | } else {
38 | files = append(files, fullPath)
39 | }
40 | }
41 | }
42 | return files, dirs
43 | }
44 |
45 | // Add files and dirs to watcher
46 | func configureWatcher(watcher *fsnotify.Watcher, files []string, dirs []string) error {
47 | for _, source := range dirs {
48 | symwalk.Walk(source, func(path string, f os.FileInfo, err error) error {
49 | if f != nil && f.IsDir() {
50 | if err := watcher.Add(path); err != nil {
51 | Warn(err.Error())
52 | }
53 | }
54 | return nil
55 | })
56 | }
57 | for _, source := range files {
58 | if err := watcher.Add(source); err != nil {
59 | Warn(err.Error())
60 | }
61 | }
62 | return nil
63 | }
64 |
65 | func Watch() {
66 | // Listen watched file change event
67 | if watcher != nil {
68 | watcher.Close()
69 | }
70 | watcher, _ = fsnotify.NewWatcher()
71 | files, dirs := buildWatchList()
72 | go func() {
73 | for {
74 | select {
75 | case event := <-watcher.Events:
76 | if event.Op == fsnotify.Write {
77 | // Handle when file change
78 | Log(event.Name)
79 | ParseGlobalConfigWrap(rootPath, true)
80 |
81 | newFiles, newDirs := buildWatchList()
82 | // If file list changed, reconfigure watcher
83 | if !reflect.DeepEqual(files, newFiles) || !reflect.DeepEqual(dirs, newDirs) {
84 | configureWatcher(watcher, newFiles, newDirs)
85 | files = newFiles
86 | dirs = newDirs
87 | }
88 |
89 | Build()
90 | if conn != nil {
91 | if err := conn.WriteMessage(websocket.TextMessage, []byte("change")); err != nil {
92 | Warn(err.Error())
93 | }
94 | }
95 | }
96 | case err := <-watcher.Errors:
97 | Warn(err.Error())
98 | }
99 | }
100 | }()
101 | configureWatcher(watcher, files, dirs)
102 | }
103 |
104 | func Websocket(ctx *ink.Context) {
105 | var upgrader = websocket.Upgrader{
106 | ReadBufferSize: 1024,
107 | WriteBufferSize: 1024,
108 | }
109 | if c, err := upgrader.Upgrade(ctx.Res, ctx.Req, nil); err != nil {
110 | Warn(err)
111 | } else {
112 | conn = c
113 | }
114 | ctx.Stop()
115 | }
116 |
117 | func Serve() {
118 | // editorWeb := ink.New()
119 | //
120 | // editorWeb.Get("/articles", ApiListArticle)
121 | // editorWeb.Get("/articles/:id", ApiGetArticle)
122 | // editorWeb.Post("/articles", ApiCreateArticle)
123 | // editorWeb.Put("/articles/:id", ApiSaveArticle)
124 | // editorWeb.Delete("/articles/:id", ApiRemoveArticle)
125 | // editorWeb.Get("/config", ApiGetConfig)
126 | // editorWeb.Put("/config", ApiSaveConfig)
127 | // editorWeb.Post("/upload", ApiUploadFile)
128 | // editorWeb.Use(ink.Cors)
129 | // editorWeb.Get("*", ink.Static(filepath.Join("editor/assets")))
130 |
131 | // Log("Access http://localhost:" + globalConfig.Build.Port + "/ to open editor")
132 | // go editorWeb.Listen(":2333")
133 |
134 | previewWeb := ink.New()
135 | previewWeb.Get("/live", Websocket)
136 | previewWeb.Get("*", ink.Static(filepath.Join(rootPath, globalConfig.Build.Output)))
137 |
138 | uri := "http://localhost:" + globalConfig.Build.Port + "/"
139 | Log("Access " + uri + " to open preview")
140 | previewWeb.Listen(":" + globalConfig.Build.Port)
141 | }
142 |
--------------------------------------------------------------------------------
/template/theme/source/css/page.css:
--------------------------------------------------------------------------------
1 | .main {
2 | &.page {
3 | padding-top: 100px;
4 | padding-bottom: 70px;
5 | .header {
6 | text-align: center;
7 | .tag {
8 | margin: 0;
9 | margin-top: 30px;
10 | font-weight: bold;
11 | font-size: 32px;
12 | }
13 | .tag-sub {
14 | margin: 0;
15 | font-weight: normal;
16 | margin-top: 10px;
17 | font-size: 16px;
18 | color: $grey;
19 | }
20 | .search-wrap {
21 | position: relative;
22 | max-width: 300px;
23 | margin: 0 auto;
24 | margin-top: 50px;
25 | .search {
26 | padding: 10px 25px;
27 | border: 1px solid $greyLighter;
28 | border-radius: 100px;
29 | width: 100%;
30 | box-sizing: border-box;
31 | opacity: 0.8;
32 | &:focus {
33 | outline: 0;
34 | opacity: 1;
35 | }
36 | }
37 | .icon {
38 | position: absolute;
39 | right: 10px;
40 | top: 5px;
41 | opacity: 0.3;
42 | }
43 | }
44 | }
45 | .article-list {
46 | margin-top: 100px;
47 | .searched {
48 | background-color: $green;
49 | color: $whiteLighter;
50 | }
51 | .article {
52 | margin-bottom: 55px;
53 | padding-bottom: 45px;
54 | border-bottom: 1px $greyLighter dashed;
55 | &:last-child {
56 | border-bottom: 0;
57 | }
58 | .title {
59 | font-size: 26px;
60 | font-weight: bold;
61 | margin-bottom: 5px;
62 | text-decoration: none;
63 | color: $black;
64 | display: block;
65 | .top {
66 | color: $grey;
67 | margin-right: 5px;
68 | }
69 | }
70 | .cover {
71 | color: $white;
72 | width: 100%;
73 | height: 280px;
74 | background-size: cover;
75 | background-position: center;
76 | background-repeat: no-repeat;
77 | border-radius: 6px;
78 | border: 1px solid $whiteDark;
79 | box-sizing: border-box;
80 | margin-top: 15px;
81 | @media (max-width: 414px) {
82 | height: 180px;
83 | }
84 | }
85 | .preview {
86 | @mixin font-main;
87 | display: block;
88 | line-height: 28px;
89 | font-size: 16px;
90 | margin-top: 10px;
91 | color: $black;
92 | }
93 | .info {
94 | margin-top: 15px;
95 | margin-bottom: 20px;
96 | color: $grey;
97 | font-size: 14px;
98 | }
99 | .avatar {
100 | width: 20px;
101 | height: 20px;
102 | margin-right: 10px;
103 | border-radius: 20px;
104 | background-size: cover;
105 | background-repeat: no-repeat;
106 | float: left;
107 | margin-top: -4px;
108 | border: 1px $greyLighter solid;
109 | }
110 | .name {
111 | margin-right: 10px;
112 | }
113 | .date {
114 | color: $grey;
115 | }
116 | .tags {
117 | @mixin font-main;
118 | margin-left: 10px;
119 | float: right;
120 | .tag {
121 | margin-right: 5px;
122 | color: $grey;
123 | text-decoration: none;
124 | }
125 | }
126 | }
127 | .empty {
128 | text-align: center;
129 | font-size: 24px;
130 | max-width: 800px;
131 | overflow: hidden;
132 | text-overflow: ellipsis;
133 | span {
134 | font-weight: bold;
135 | }
136 | }
137 | }
138 | .page-nav {
139 | position: relative;
140 | height: 50px;
141 | font-size: 14px;
142 | a {
143 | color: $grey;
144 | font-style: italic;
145 | text-decoration: none;
146 | }
147 | .nav {
148 | left: 0;
149 | right: 0;
150 | text-align: center;
151 | display: block;
152 | color: $grey;
153 | position: absolute;
154 | z-index: -1;
155 | }
156 | .prev {
157 | float: left;
158 | &:before {
159 | content: "";
160 | width: 3px;
161 | height: 3px;
162 | border: 2px $grey solid;
163 | border-radius: 3px;
164 | display: block;
165 | float: left;
166 | margin-right: 7px;
167 | margin-top: 5px;
168 | }
169 | }
170 | .next {
171 | float: right;
172 | &:after {
173 | content: "";
174 | width: 3px;
175 | height: 3px;
176 | border: 2px $grey solid;
177 | border-radius: 3px;
178 | display: block;
179 | float: right;
180 | margin-left: 7px;
181 | margin-top: 5px;
182 | }
183 | }
184 | }
185 | }
186 | }
187 |
--------------------------------------------------------------------------------
/template/source/ink-blog-tool.md:
--------------------------------------------------------------------------------
1 | title: "简洁的静态博客构建工具 —— 纸小墨(InkPaper)"
2 | date: 2015-03-01 18:00:00 +0800
3 | update: 2016-07-11 17:00:00 +0800
4 | author: me
5 | cover: "-/images/example.png"
6 | tags:
7 | - 设计
8 | - 写作
9 | preview: 纸小墨(InkPaper)是一个GO语言编写的开源静态博客构建工具,可以快速搭建博客网站。它无依赖跨平台,配置简单构建快速,注重简洁易用与更优雅的排版。
10 |
11 | ---
12 |
13 | > **⚠注意:** 这是纸小墨的旧版本文档的副本,仅用于展示渲染效果,内容不具有参考性,**请勿依赖下文的内容使用纸小墨**!请访问[我们的仓库](https://github.com/InkProject/ink/)以获取最新文档。
14 |
15 | ## 纸小墨简介
16 |
17 | 纸小墨(InkPaper)是一个GO语言编写的开源静态博客构建工具,可以快速搭建博客网站。它无依赖跨平台,配置简单构建快速,注重简洁易用与更优雅的排版。
18 |
19 | 
20 |
21 | ### 开始上手
22 |
23 | - 下载并解压 [Ink](https://github.com/InkProject/ink/releases),运行命令 `ink preview`
24 |
25 | > 注意:Linux/macOS下,使用 `./ink preview`
26 |
27 | - 使用浏览器访问 `http://localhost:8000` 预览。
28 |
29 | ### 特性介绍
30 | - YAML格式的配置
31 | - Markdown格式的文章
32 | - 无依赖跨平台
33 | - 超快的构建速度
34 | - 不断改善的主题与排版
35 | - 多文章作者支持
36 | - 归档与标签自动生成
37 | - 保存时实时预览页面
38 | - 离线的全文关键字搜索
39 | - $\LaTeX$ 风格的数学公式支持(MathJax):
40 |
41 | $$
42 | \int_{-\infty}^\infty g(x) dx = \frac{1}{2\pi i} \oint_{\gamma} \frac{f(z)}{z-g(x)} dz
43 | $$
44 |
45 | ### 配置网站
46 | 编辑`config.yml`,使用如下格式:
47 |
48 | ``` yaml
49 | site:
50 | title: 网站标题
51 | subtitle: 网站子标题
52 | limit: 每页可显示的文章数目
53 | theme: 网站主题目录
54 | comment: 评论插件变量(默认为Disqus账户名)
55 | root: 网站根路径 #可选
56 | lang: 网站语言 #支持en, zh, ru, ja,de, 可在theme/config.yml配置
57 | url: 网站链接 #用于RSS生成
58 | link: 文章链接形式 #默认为{title}.html,支持{year},{month},{day},{title}变量
59 |
60 | authors:
61 | 作者ID:
62 | name: 作者名称
63 | intro: 作者简介
64 | avatar: 作者头像路径
65 |
66 | build:
67 | output: 构建输出目录 #可选, 默认为 "public"
68 | port: 预览端口
69 | copy:
70 | - 构建时将会复制的目录/文件
71 | publish: |
72 | ink publish 命令将会执行的脚本
73 | ```
74 |
75 | ### 创建文章
76 | 在`source`目录中建立任意`.md`文件(可置于子文件夹),使用如下格式:
77 |
78 | ``` yaml
79 | title: 文章标题
80 | date: 年-月-日 时:分:秒 #创建时间,可加时区如" +0800"
81 | update: 年-月-日 时:分:秒 #更新时间,可选,可加时区如" +0800"
82 | author: 作者ID
83 | cover: 题图链接 #可选
84 | draft: false #草稿,可选
85 | top: false #置顶文章,可选
86 | preview: 文章预览,也可在正文中使用分割 #可选
87 | tags: #可选
88 | - 标签1
89 | - 标签2
90 | type: post #指定类型为文章(post)或页面(page),可选
91 | hide: false #隐藏文章,只可通过链接访问,可选
92 | toc: false #是否显示文章目录,可选
93 |
94 | ---
95 |
96 | Markdown格式的正文
97 | ```
98 |
99 | ### 发布博客
100 | - 在博客目录下运行`ink publish`命令自动构建博客并发布。
101 | - 或运行`ink build`命令将生成的`public`目录下的内容手动部署。
102 |
103 | > Tips: 在使用`ink preview`命令时,编辑保存文件后,博客会自动重新构建并刷新浏览器页面。
104 |
105 | ## 定制支持
106 |
107 | ### 修改主题
108 |
109 | 默认主题在`theme`目录下,修改源代码后在该目录下运行`npm install`与`npm run build`重新构建。
110 |
111 | 页面包含`page.html`(文章列表)及`article.html`(文章)等,所有页面均支持[GO语言HTML模板](http://golang.org/pkg/html/template/)语法,可引用变量。
112 |
113 | ### 添加页面
114 |
115 | 在`source`目录下创建的任意`.html`文件将被复制,这些文件中可引用`config.yml`中site字段下的所有变量。
116 |
117 | #### 定义自定义变量
118 | 纸小墨支持在页面中定义自定义变量,必须放置于 `config.yaml` 的 `site.config` 之下,如:
119 |
120 | ``` yaml
121 | site:
122 | config:
123 | MyVar: "Hello World"
124 | ```
125 |
126 | 在页面中可通过 `{{.Site.Config.MyVar}}` 来引用该变量。
127 |
128 | > **注意**
129 | >
130 | > 虽然 `config.yaml` 的其他部分字段名均为小写,但自定义变量的名称必须使用正确的大小写,如:
131 |
132 | > ```yaml
133 | site:
134 | config:
135 | MYVAR_aaa: "Hello World"
136 | ```
137 |
138 | > 则在页面中必须使用 `{{.Site.Config.MYVAR_aaa}}` 来引用该变量。
139 |
140 |
141 | #### 使用函数(实验性)
142 |
143 | 纸小墨定义了一个最小的函数集合,可在 HTML 页面(除 `.md` 源文件以外)中使用,如
144 |
145 | ``` yaml
146 | {{ readFile "path/to/file" }}
147 | ```
148 |
149 | 将读取 `path/to/file` 文件的内容并(不做任何处理地)包含入页面中。
150 |
151 | 对于文件相关的函数,当在 `source` 下执行时,文件路径相对于 `source` 目录;当在其他目录下执行时,文件路径相对于主题(如 `theme`)目录。
152 |
153 | 所有函数列表见源文件 `funcs.go` 文件。
154 |
155 | ### 博客迁移(Beta)
156 |
157 | 纸小墨提供简单的Jeklly/Hexo博客文章格式转换,使用命令:
158 | ``` shell
159 | ink convert /path/_posts
160 | ```
161 |
162 | ### 源码编译
163 |
164 | 本地运行
165 |
166 | 1. 配置[GO](http://golang.org/doc/install)语言环境。
167 | 2. 运行命令`git clone https://github.com/InkProject/ink && cd ink && go install`编译并安装纸小墨。
168 | 3. 运行命令`ink preview $GOPATH/src/github.com/InkProject/ink/template`,预览博客。
169 |
170 | Docker构建(示例)
171 |
172 | 1. Clone源码 `git clone git@github.com:InkProject/ink.git`。
173 | 2. 源码目录下构建镜像`docker build -t ink .`。
174 | 3. 运行容器`docker run -p 8000:80 ink`。
175 |
176 | ## 主题
177 |
178 | - Dark(Official Theme): [https://github.com/InkProject/ink-theme-dark](https://github.com/InkProject/ink-theme-dark)
179 | - simple: [https://github.com/myiq/ink-simple](https://github.com/myiq/ink-simple)
180 |
181 | ## 相关链接
182 |
183 | - [InkPaper 最佳实践](https://segmentfault.com/a/1190000009084954)
184 |
185 | ## 反馈贡献
186 |
187 | 纸小墨基于 [CC Attribution-NonCommercial License 4.0](https://creativecommons.org/licenses/by-nc/4.0/) 协议,目前为止它仍然是个非成熟的开源项目,非常欢迎任何人的任何贡献。如有问题可报告至 [https://github.com/InkProject/ink/issues](https://github.com/InkProject/ink/issues)。
188 |
189 | ## 更新计划
190 |
191 | - 排版深度优化
192 | - 纸小墨编辑器
193 |
194 | ## 正在使用
195 |
196 | - [https://imeoer.github.io/blog/](https://imeoer.github.io/blog/)
197 | - [http://blog.hyper.sh/](http://blog.hyper.sh/)
198 | - [http://wangxu.me/](http://wangxu.me/)
199 | - [http://whzecomjm.com/](http://whzecomjm.com/)
200 | - [http://www.shery.me/blog/](http://www.shery.me/blog/)
201 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Introduction
2 |
3 | InkPaper is a static blog generator developed in Golang. No dependencies, cross platform, easy to use, fast building times and an elegant theme.
4 |
5 | [](https://creativecommons.org/licenses/by-nc/4.0/)
6 |
7 | 
8 |
9 | ### Features
10 | - YAML format configuration
11 | - Markdown format articles
12 | - No dependencies, cross platform
13 | - Super fast build times
14 | - Continuously improving theme and typography
15 | - Multiple article authors support
16 | - Archive and tag generation
17 | - Real-time preview when saving
18 | - Offline full-text keyword search
19 | - $\LaTeX$ style math formula support (MathJax):
20 |
21 | $$
22 | \int_{-\infty}^\infty g(x) dx = \frac{1}{2\pi i} \oint_{\gamma} \frac{f(z)}{z-g(x)} dz
23 | $$
24 |
25 | ### Quick Start
26 | - Download & Extract [Ink](https://github.com/InkProject/ink/releases) and run `ink preview`
27 |
28 | > Tip:Linux/macOS, use `./ink preview`
29 |
30 | - Open `http://localhost:8000` in your browser to preview
31 |
32 | ### Website Configuration
33 | Edit `config.yml`, use this format:
34 |
35 | ``` yaml
36 | site:
37 | title: Website Title
38 | subtitle: Website Subtitle
39 | limit: Max Article Count Per Page
40 | theme: Website Theme Directory
41 | comment: Comment Plugin Variable (Default is disqus username)
42 | root: Website Root Path # Optional
43 | lang: Website Language # Support en, zh, ru, ja, de, pt-br, configurable in theme/lang.yml
44 | url: Website URL # For RSS generating
45 | link: Article Link Scheme # Default is {title}.html, Support {year}, {month}, {day}, {hour}, {minute}, {second}, {title} variables
46 |
47 | authors:
48 | AuthorID: # Your author ID, used in article's author field
49 | name: Author Name
50 | intro: Author Motto
51 | avatar: Author Avatar Path
52 |
53 | build:
54 | output: Build Output Directory # Optional, default is "public"
55 | port: Preview Port
56 | copy:
57 | - Copied Files When Build
58 | publish: |
59 | Excuted command when 'ink publish' is used
60 | ```
61 |
62 | ### Blog Writing
63 | Create a `.md` file in the `source` directory (Supports subdirectories). Use this format:
64 |
65 | ``` yaml
66 | title: Article Title
67 | date: Year-Month-Day Hour:Minute:Second #Created Time. Support timezone, such as " +0800"
68 | update: Year-Month-Day Hour:Minute:Second #Updated Time, optional. Support timezone, such as " +0800"
69 | author: AuthorID
70 | cover: Article Cover Path # Optional
71 | draft: false # Is draft or not, optional
72 | top: false # Place article to top or not, optional
73 | preview: Article Preview, Also use to split in body # Optional
74 | tags: # Optional
75 | - Tag1
76 | - Tag2
77 | type: post # Specify type is post or page, optional
78 | hide: false # Hide article or not. Hidden atricles still can be accessed via URL, optional
79 | toc: false # Show table of contents or not, optional
80 | ---
81 |
82 | Markdown Format's Body
83 | ```
84 |
85 | ### Publish
86 | - Run `ink publish` in the blog directory to automatically build and publish
87 | - Or run `ink build` to manually deploy generated `public` directory
88 |
89 | > **Tips**: When files changed, `ink preview` will automatically rebuild the blog. Refresh browser to update.
90 |
91 | ## Customization
92 |
93 | ### Modifying The Theme
94 |
95 | The default theme is placed in the `theme` folder, run `npm install` and `npm run build` to rebuild in this folder.
96 |
97 | page `page.html` (article list) and `article.html` (article), use variable with [Golang Template](http://golang.org/pkg/html/template/) syntax.
98 |
99 | ### New Page
100 |
101 | Created any `.html` file will be copied to `source` directory, could use all variables on `site` field in `config.yml`.
102 |
103 |
104 | #### Define Custom Variables
105 | InkPaper supports defining custom variables in pages, which must be placed under `site.config` in `config.yaml`, such as:
106 |
107 | ``` yaml
108 | site:
109 | config:
110 | MyVar: "Hello World"
111 | ```
112 |
113 | The variable can be referenced in the page by `{{.Site.Config.MyVar}}`.
114 |
115 | > **Note**
116 | >
117 | > Although the field names in other parts of `config.yaml` are all lowercase, the name of the custom variable must be used correctly. Otherwises, such a variable:
118 | >
119 | > ```yaml
120 | > site:
121 | > config:
122 | > MYVAR_aAa: "Hello World"
123 | > ```
124 | > must be referenced in the page as `{{.Site.Config.MYVAR_aAa}}`.
125 |
126 | #### Use Functions (Experimental)
127 |
128 | InkPaper defines a minimal set of functions that can be used in HTML pages (except for `.md` source files), such as
129 |
130 | ``` yaml
131 | {{ readFile "path/to/file" }}
132 | ```
133 |
134 | This will read the content of the file `path/to/file` and include it in the page without any processing.
135 |
136 | For file-related functions, when executed in the `source` directory, the file path is relative to the `source` directory; when executed in other directories, the file path is relative to the theme (such as `theme`).
137 |
138 | See the source file `funcs.go` for a list of all functions.
139 |
140 | ### Blog Migration (Beta)
141 |
142 | Supports simple Jeklly/Hexo post convertions. Usage:
143 |
144 | ``` shell
145 | ink convert /path/_posts
146 | ```
147 |
148 | ### Building from source
149 |
150 | **Local Build**
151 |
152 | 1. Install [Golang](http://golang.org/doc/install) environment
153 | 2. Run `git clone https://github.com/InkProject/ink && cd ink && go install` to compile and install ink
154 | 3. Run `ink preview $GOPATH/src/github.com/InkProject/ink/template` to preview blog
155 |
156 | **Docker Build (Example)**
157 |
158 | 1. Clone code `git clone git@github.com:InkProject/ink.git`
159 | 2. Build image `docker build -t ink .` in source directory
160 | 3. Run container `docker run -p 8888:80 ink`
161 |
162 | ## Theme
163 |
164 | - Dark (Official Theme): [https://github.com/InkProject/ink-theme-dark](https://github.com/InkProject/ink-theme-dark)
165 | - Simple: [https://github.com/myiq/ink-simple](https://github.com/myiq/ink-simple)
166 | - Story: [https://github.com/akkuman/ink-theme-story](https://github.com/akkuman/ink-theme-story)
167 | - Material2: [https://github.com/w568w/InkMaterialTheme](https://github.com/w568w/InkMaterialTheme)
168 |
169 | ## Related Tutorials
170 |
171 | - [Automatically deploy your Ink blog to GitHub pages with Travis CI](http://www.shery.me/blog/travis-ci.html)
172 |
173 | ## License
174 | [CC Attribution-NonCommercial License 4.0](https://creativecommons.org/licenses/by-nc/4.0/)
175 |
176 | ## Reporting An Issue
177 |
178 | [https://github.com/InkProject/ink/issues](https://github.com/InkProject/ink/issues)
179 |
180 | ## These blogs are driven by InkPaper
181 |
182 | - [https://imeoer.github.io/blog/](https://imeoer.github.io/blog/)
183 | - [http://blog.hyper.sh/](http://blog.hyper.sh/)
184 | - [http://wangxu.me/](http://wangxu.me/)
185 | - [http://whzecomjm.com/](http://whzecomjm.com/)
186 | - [http://www.shery.me/blog/](http://www.shery.me/blog/)
187 |
--------------------------------------------------------------------------------
/render.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "html/template"
7 | "os"
8 | "path/filepath"
9 | "strconv"
10 | "time"
11 |
12 | "github.com/gorilla/feeds"
13 | "github.com/snabb/sitemap"
14 | )
15 |
16 | type Data interface{}
17 |
18 | type RenderArticle struct {
19 | Article
20 | Next *Article
21 | Prev *Article
22 | }
23 |
24 | // Compile html template
25 | func CompileTpl(tplPath string, partialTpl string, name string, funcContext FuncContext) template.Template {
26 | // Read template data from file
27 | html, err := os.ReadFile(tplPath)
28 | if err != nil {
29 | Fatal(err.Error())
30 | }
31 | // Append partial template
32 | htmlStr := string(html) + partialTpl
33 | // Generate html content
34 | tpl, err := template.New(name).Funcs(funcContext.FuncMap()).Parse(htmlStr)
35 | if err != nil {
36 | Fatal(err.Error())
37 | }
38 | return *tpl
39 | }
40 |
41 | // Render html file by data
42 | func RenderPage(tpl template.Template, tplData interface{}, outPath string) {
43 | // Create file
44 | outFile, err := os.Create(outPath)
45 | if err != nil {
46 | Fatal(err.Error())
47 | }
48 | defer func() {
49 | outFile.Close()
50 | }()
51 | defer wg.Done()
52 | // Template render
53 | err = tpl.Execute(outFile, tplData)
54 | if err != nil {
55 | Fatal(err.Error())
56 | }
57 | }
58 |
59 | // Generate all article page
60 | func RenderArticles(tpl template.Template, articles Collections) {
61 | defer wg.Done()
62 | articleCount := len(articles)
63 | for i := range articles {
64 | currentArticle := articles[i].(Article)
65 | var renderArticle = RenderArticle{currentArticle, nil, nil}
66 | // Only show next and prev article if it is not hidden
67 | if !renderArticle.Hide {
68 | if i >= 1 {
69 | // Find prev unhidden article
70 | for j := i - 1; j >= 0; j-- {
71 | prevArticle := articles[j].(Article)
72 | if !prevArticle.Hide {
73 | renderArticle.Prev = &prevArticle
74 | break
75 | }
76 | }
77 | }
78 | if i <= articleCount-2 {
79 | // Find next unhidden article
80 | for j := i + 1; j < articleCount; j++ {
81 | nextArticle := articles[j].(Article)
82 | if !nextArticle.Hide {
83 | renderArticle.Next = &nextArticle
84 | break
85 | }
86 | }
87 | }
88 | }
89 | outPath := filepath.Join(publicPath, currentArticle.Link)
90 | wg.Add(1)
91 | go RenderPage(tpl, renderArticle, outPath)
92 | }
93 | }
94 |
95 | // Generate rss page
96 | func GenerateRSS(articles Collections) {
97 | defer wg.Done()
98 | var feedArticles Collections
99 | if len(articles) < globalConfig.Site.Limit {
100 | feedArticles = articles
101 | } else {
102 | feedArticles = articles[0:globalConfig.Site.Limit]
103 | }
104 | if globalConfig.Site.Url != "" {
105 | feed := &feeds.Feed{
106 | Title: globalConfig.Site.Title,
107 | Link: &feeds.Link{Href: globalConfig.Site.Url},
108 | Description: globalConfig.Site.Subtitle,
109 | Author: &feeds.Author{Name: globalConfig.Site.Title, Email: ""},
110 | Created: time.Now(),
111 | }
112 | feed.Items = make([]*feeds.Item, 0)
113 | for _, item := range feedArticles {
114 | article := item.(Article)
115 | feed.Items = append(feed.Items, &feeds.Item{
116 | Title: article.Title,
117 | Link: &feeds.Link{Href: globalConfig.Site.Url + "/" + article.Link},
118 | Description: string(article.Preview),
119 | Author: &feeds.Author{Name: article.Author.Name, Email: ""},
120 | Created: article.Time,
121 | Updated: article.MTime,
122 | })
123 | }
124 | if atom, err := feed.ToAtom(); err == nil {
125 | err := os.WriteFile(filepath.Join(publicPath, "atom.xml"), []byte(atom), 0644)
126 | if err != nil {
127 | Fatal(err.Error())
128 | }
129 | } else {
130 | Fatal(err.Error())
131 | }
132 | }
133 | }
134 |
135 | // Generate sitemap page
136 | func GenerateSitemap(articles Collections) {
137 | defer wg.Done()
138 |
139 | if globalConfig.Site.Url != "" {
140 | sm := sitemap.New()
141 |
142 | globalModTime := time.Now()
143 | sm.Add(&sitemap.URL{
144 | Loc: globalConfig.Site.Url,
145 | LastMod: &globalModTime,
146 | ChangeFreq: sitemap.Weekly,
147 | })
148 |
149 | for _, item := range articles {
150 | article := item.(Article)
151 |
152 | var lastModTime time.Time
153 | if article.MTime.After(article.Time) {
154 | lastModTime = article.MTime
155 | } else {
156 | lastModTime = article.Time
157 | }
158 |
159 | sm.Add(&sitemap.URL{
160 | Loc: globalConfig.Site.Url + "/" + article.Link,
161 | LastMod: &lastModTime,
162 | ChangeFreq: sitemap.Weekly,
163 | })
164 | }
165 |
166 | var sitemap bytes.Buffer
167 | sm.WriteTo(&sitemap)
168 | err := os.WriteFile(filepath.Join(publicPath, "sitemap.xml"), sitemap.Bytes(), 0644)
169 | if err != nil {
170 | Fatal(err.Error())
171 | }
172 | }
173 | }
174 |
175 | // Generate article list page
176 | func RenderArticleList(rootPath string, articles Collections, tagName string) {
177 | defer wg.Done()
178 | // Create path
179 | pagePath := filepath.Join(publicPath, rootPath)
180 | os.MkdirAll(pagePath, 0777)
181 | // Split page
182 | limit := globalConfig.Site.Limit
183 | total := len(articles)
184 | page := total / limit
185 | rest := total % limit
186 | if rest != 0 {
187 | page++
188 | }
189 | if total < limit {
190 | page = 1
191 | }
192 | for i := 0; i < page; i++ {
193 | var prev = filepath.Join(rootPath, "page"+strconv.Itoa(i)+".html")
194 | var next = filepath.Join(rootPath, "page"+strconv.Itoa(i+2)+".html")
195 | outPath := filepath.Join(pagePath, "index.html")
196 | if i != 0 {
197 | fileName := "page" + strconv.Itoa(i+1) + ".html"
198 | outPath = filepath.Join(pagePath, fileName)
199 | } else {
200 | prev = ""
201 | }
202 | if i == 1 {
203 | prev = filepath.Join(rootPath, "index.html")
204 | }
205 | first := i * limit
206 | count := first + limit
207 | if i == page-1 {
208 | if rest != 0 {
209 | count = first + rest
210 | }
211 | next = ""
212 | }
213 | var data = map[string]interface{}{
214 | "Articles": articles[first:count],
215 | "Site": globalConfig.Site,
216 | "Develop": globalConfig.Develop,
217 | "Page": i + 1,
218 | "Total": page,
219 | "Prev": template.URL(filepath.ToSlash(prev)),
220 | "Next": template.URL(filepath.ToSlash(next)),
221 | "TagName": tagName,
222 | "TagCount": len(articles),
223 | }
224 | wg.Add(1)
225 | go RenderPage(pageTpl, data, outPath)
226 | }
227 | }
228 |
229 | // Generate article list JSON
230 | func GenerateJSON(articles Collections) {
231 | defer wg.Done()
232 | datas := make([]map[string]interface{}, 0)
233 | for i := range articles {
234 | article := articles[i].(Article)
235 | var data = map[string]interface{}{
236 | "title": article.Title,
237 | "content": article.Markdown,
238 | "preview": string(article.Preview),
239 | "link": article.Link,
240 | "cover": article.Cover,
241 | }
242 | datas = append(datas, data)
243 | }
244 | str, _ := json.Marshal(datas)
245 | os.WriteFile(filepath.Join(publicPath, "index.json"), []byte(str), 0644)
246 | }
247 |
--------------------------------------------------------------------------------
/api.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "crypto/md5"
5 | "encoding/hex"
6 | "encoding/json"
7 | "io"
8 | "mime/multipart"
9 | "net/http"
10 | "os"
11 | "path/filepath"
12 | "strings"
13 | "time"
14 |
15 | "github.com/InkProject/ink.go"
16 | "github.com/edwardrf/symwalk"
17 | )
18 |
19 | type NewArticle struct {
20 | Name string
21 | Content string
22 | }
23 |
24 | type OldArticle struct {
25 | Content string
26 | }
27 |
28 | type CacheArticleInfo struct {
29 | Name string
30 | Path string
31 | Date time.Time
32 | Article *ArticleConfig
33 | }
34 |
35 | var articleCache map[string]CacheArticleInfo
36 |
37 | func hashPath(path string) string {
38 | md5Hex := md5.Sum([]byte(path))
39 | return hex.EncodeToString(md5Hex[:])
40 | }
41 |
42 | func replyJSON(ctx *ink.Context, status int, data interface{}) {
43 | jsonStr, err := json.Marshal(data)
44 | if err != nil {
45 | http.Error(ctx.Res, err.Error(), http.StatusInternalServerError)
46 | ctx.Stop()
47 | return
48 | }
49 | if status == http.StatusOK {
50 | ctx.Header().Set("Content-Type", "application/json")
51 | ctx.Header().Set("Access-Control-Allow-Origin", "*")
52 | ctx.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
53 | ctx.Res.Write(jsonStr)
54 | } else {
55 | Warn(data)
56 | http.Error(ctx.Res, data.(string), status)
57 | }
58 | ctx.Stop()
59 | }
60 |
61 | func UpdateArticleCache() {
62 | articleCache = make(map[string]CacheArticleInfo, 0)
63 | symwalk.Walk(sourcePath, func(path string, info os.FileInfo, err error) error {
64 | fileExt := strings.ToLower(filepath.Ext(path))
65 | if fileExt == ".md" {
66 | fileName := strings.TrimPrefix(strings.TrimSuffix(strings.ToLower(path), ".md"), "template/source/")
67 | config, _ := ParseArticleConfig(path)
68 | id := hashPath(path)
69 | articleCache[string(id)] = CacheArticleInfo{
70 | Name: fileName,
71 | Path: path,
72 | Date: ParseDate(config.Date),
73 | Article: config,
74 | }
75 | }
76 | return nil
77 | })
78 | }
79 |
80 | func ApiListArticle(ctx *ink.Context) {
81 | UpdateArticleCache()
82 | replyJSON(ctx, http.StatusOK, articleCache)
83 | }
84 |
85 | func ApiGetArticle(ctx *ink.Context) {
86 | UpdateArticleCache()
87 | article, ok := articleCache[ctx.Param["id"]]
88 | if !ok {
89 | replyJSON(ctx, http.StatusNotFound, "Not Found")
90 | return
91 | }
92 | filePath := article.Path
93 | data, err := os.ReadFile(filePath)
94 | if err != nil {
95 | replyJSON(ctx, http.StatusInternalServerError, err.Error())
96 | return
97 | }
98 | replyJSON(ctx, http.StatusOK, string(data))
99 | }
100 |
101 | func ApiRemoveArticle(ctx *ink.Context) {
102 | UpdateArticleCache()
103 | article, ok := articleCache[ctx.Param["id"]]
104 | if !ok {
105 | replyJSON(ctx, http.StatusNotFound, "Not Found")
106 | return
107 | }
108 | filePath := article.Path
109 | err := os.Remove(filePath)
110 | if err != nil {
111 | replyJSON(ctx, http.StatusInternalServerError, err.Error())
112 | return
113 | }
114 | replyJSON(ctx, http.StatusOK, nil)
115 | }
116 |
117 | func ApiCreateArticle(ctx *ink.Context) {
118 | decoder := json.NewDecoder(ctx.Req.Body)
119 | var article NewArticle
120 | err := decoder.Decode(&article)
121 | if err != nil {
122 | replyJSON(ctx, http.StatusBadRequest, err.Error())
123 | return
124 | }
125 | filePath := filepath.Join(sourcePath, article.Name+".md")
126 | err = os.WriteFile(filePath, []byte(article.Content), 0644)
127 | if err != nil {
128 | replyJSON(ctx, http.StatusInternalServerError, err.Error())
129 | return
130 | }
131 | replyJSON(ctx, http.StatusOK, map[string]string{
132 | "id": hashPath(filePath),
133 | })
134 | }
135 |
136 | func ApiSaveArticle(ctx *ink.Context) {
137 | UpdateArticleCache()
138 | decoder := json.NewDecoder(ctx.Req.Body)
139 | var article OldArticle
140 | err := decoder.Decode(&article)
141 | if err != nil {
142 | replyJSON(ctx, http.StatusBadRequest, err.Error())
143 | return
144 | }
145 | cacheArticle, ok := articleCache[ctx.Param["id"]]
146 | if !ok {
147 | replyJSON(ctx, http.StatusNotFound, "Not Found")
148 | return
149 | }
150 | // Write
151 | path := cacheArticle.Path
152 | err = os.WriteFile(path, []byte(article.Content), 0644)
153 | if err != nil {
154 | replyJSON(ctx, http.StatusInternalServerError, err.Error())
155 | return
156 | }
157 | replyJSON(ctx, http.StatusOK, nil)
158 | }
159 |
160 | func getFormFile(ctx *ink.Context, field string) (data []byte, handler *multipart.FileHeader, err error) {
161 | file, handler, err := ctx.Req.FormFile(field)
162 | if err != nil {
163 | replyJSON(ctx, http.StatusBadRequest, err.Error())
164 | return nil, handler, err
165 | }
166 | data, err = io.ReadAll(file)
167 | if err != nil {
168 | replyJSON(ctx, http.StatusBadRequest, err.Error())
169 | return data, handler, err
170 | }
171 | return data, handler, err
172 | }
173 |
174 | func ApiUploadFile(ctx *ink.Context) {
175 | UpdateArticleCache()
176 | fileData, handler, err := getFormFile(ctx, "file")
177 | if err != nil {
178 | replyJSON(ctx, http.StatusBadRequest, err.Error())
179 | return
180 | }
181 | articleId := ctx.Req.FormValue("article_id")
182 | article, ok := articleCache[articleId]
183 | if !ok {
184 | replyJSON(ctx, http.StatusNotFound, "Not Found")
185 | return
186 | }
187 | fileDirPath := filepath.Join(sourcePath, "images", article.Name)
188 | err = os.MkdirAll(fileDirPath, 0777)
189 | if err != nil {
190 | replyJSON(ctx, http.StatusInternalServerError, err.Error())
191 | return
192 | }
193 | if err = os.WriteFile(filepath.Join(fileDirPath, handler.Filename), fileData, 0777); err != nil {
194 | replyJSON(ctx, http.StatusInternalServerError, err.Error())
195 | return
196 | }
197 | replyJSON(ctx, http.StatusOK, map[string]string{
198 | "path": "-/" + filepath.Join("images", article.Name, handler.Filename),
199 | })
200 | }
201 |
202 | func ApiGetConfig(ctx *ink.Context) {
203 | filePath := filepath.Join(rootPath, "config.yml")
204 | data, err := os.ReadFile(filePath)
205 | if err != nil {
206 | replyJSON(ctx, http.StatusInternalServerError, err.Error())
207 | return
208 | }
209 | replyJSON(ctx, http.StatusOK, string(data))
210 | }
211 |
212 | func ApiSaveConfig(ctx *ink.Context) {
213 | content, err := io.ReadAll(ctx.Req.Body)
214 | if err != nil {
215 | replyJSON(ctx, http.StatusInternalServerError, err.Error())
216 | return
217 | }
218 | filePath := filepath.Join(rootPath, "config.yml")
219 | err = os.WriteFile(filePath, []byte(content), 0644)
220 | if err != nil {
221 | replyJSON(ctx, http.StatusInternalServerError, err.Error())
222 | return
223 | }
224 | replyJSON(ctx, http.StatusOK, nil)
225 | }
226 |
227 | // func ApiRenameArticle(ctx *ink.Context) {
228 | // // Rename
229 | // cacheArticle, ok := articleCache[ctx.Param["id"]]
230 | // if !ok {
231 | // replyJSON(ctx, http.StatusNotFound, "Not Found")
232 | // return
233 | // }
234 | // oldPath := cacheArticle.(map[string]CacheArticleInfo)["path"].(string)
235 | // newPath := filepath.Join(sourcePath, newArticle.Name+".md")
236 | // err = os.Rename(oldPath, newPath)
237 | // if err != nil {
238 | // replyJSON(ctx, http.StatusInternalServerError, err.Error())
239 | // return
240 | // }
241 | // }
242 |
--------------------------------------------------------------------------------
/template/source/ink-blog-tool-en.md:
--------------------------------------------------------------------------------
1 | title: "An Elegant Static Blog Generator —— InkPaper"
2 | date: 2015-03-01 17:00:00 +0800
3 | update: 2016-07-11 17:00:00 +0800
4 | author: me
5 | cover: "-/images/example-en.png"
6 | tags:
7 | - 设计
8 | - 写作
9 | preview: InkPaper is a static blog generator developed in Golang. No dependencies, cross platform, easy to use, fast building times and an elegant theme.
10 |
11 | ---
12 |
13 | > **⚠Note:** This is a copy of the old version of Ink documentation, only for showing rendering effect, and its content is not up to date. **DO NOT rely on the instructions below to use Ink!** Please visit [our repository](https://github.com/InkProject/ink/) to get the latest documentation.
14 |
15 | ## Introduction
16 |
17 | InkPaper is a static blog generator developed in Golang. No dependencies, cross platform, easy to use, fast building times and an elegant theme.
18 |
19 | 
20 |
21 | ### Quick Start
22 | - Download & Extract [Ink](https://github.com/InkProject/ink) and run `ink preview`
23 |
24 | > Tip:Linux/macOS, use `./ink preview`
25 |
26 | - Open `http://localhost:8000` in your browser to preview
27 |
28 | ### Features
29 | - YAML format configuration
30 | - Markdown format articles
31 | - No dependencies, cross platform
32 | - Super fast build times
33 | - Continuously improving theme and typography
34 | - Multiple article authors support
35 | - Archive and tag generation
36 | - Real-time preview when saving
37 | - Offline full-text keyword search
38 | - $\LaTeX$ style math formula support (MathJax):
39 |
40 | $$
41 | \int_{-\infty}^\infty g(x) dx = \frac{1}{2\pi i} \oint_{\gamma} \frac{f(z)}{z-g(x)} dz
42 | $$
43 | ### Website Configuration
44 | Edit `config.yml`, use this format:
45 |
46 | ``` yaml
47 | site:
48 | title: Website Title
49 | subtitle: Website Subtitle
50 | limit: Max Article Count Per Page
51 | theme: Website Theme Directory
52 | comment: Comment Plugin Variable (Default is disqus username)
53 | root: Website Root Path #Optional
54 | lang: Website Language #Support en, zh, ru, ja, de, Configurable in theme/lang.yml
55 | url: Website URL #For RSS Generating
56 | link: Article Link Scheme #Default Is {title}.html,Support {year},{month},{day},{title} Variables
57 |
58 | authors:
59 | AuthorID:
60 | name: Author Name
61 | intro: Author Motto
62 | avatar: Author Avatar Path
63 |
64 | build:
65 | output: Build Output Directory #Optional, Default is "public"
66 | port: Preview Port
67 | copy:
68 | - Copied Files When Build
69 | publish: |
70 | Excuted command when 'ink publish' is used
71 | ```
72 |
73 | ### Blog Writing
74 | Create a `.md` file in the `source` directory (Supports subdirectories). Use this format:
75 |
76 | ``` yaml
77 | title: Article Title
78 | date: Year-Month-Day Hour:Minute:Second #Created Time,Support TimeZone, such as " +0800"
79 | update: Year-Month-Day Hour:Minute:Second #Updated Time,Optional,Support TimeZone, such as " +0800"
80 | author: AuthorID
81 | cover: Article Cover Path #Optional
82 | draft: false #If Draft,Optional
83 | top: false #Place article to top, Optional
84 | preview: Article Preview,Also use to split in body #Optional
85 | tags: #Optional
86 | - Tag1
87 | - Tag2
88 | type: post #Specify type is post or page, Optional
89 | hide: false #Hide article,can be accessed via URL, Optional
90 | toc: false #Show table of contents,Optional
91 |
92 | ---
93 |
94 | Markdown Format's Body
95 | ```
96 |
97 | ### Publish
98 | - Run `ink publish` in the blog directory to automatically build and publish
99 | - Or run `ink build` to manually deploy generated `public` directory
100 |
101 | > **Tips**: When files changed,`ink preview` will automatically rebuild the blog. Refresh browser to update.
102 |
103 | ## Customization
104 |
105 | ### Modifying The Theme
106 |
107 | The default theme is placed in the `theme` folder, run `npm install` and `npm run build` to rebuild in this folder.
108 |
109 | page `page.html` (article list) and `article.html` (article), use variable with [Golang Template](http://golang.org/pkg/html/template/) syntax.
110 |
111 | ### New Page
112 |
113 | Created any `.html` file will be copied to `source` directory, could use all variables on `site` field in `config.yml`.
114 |
115 | #### Define Custom Variables
116 | InkPaper supports defining custom variables in pages, which must be placed under `site.config` in `config.yaml`, such as:
117 |
118 | ``` yaml
119 | site:
120 | config:
121 | MyVar: "Hello World"
122 | ```
123 |
124 | The variable can be referenced in the page by `{{.Site.Config.MyVar}}`.
125 |
126 | > **Note**
127 | >
128 | > Although the field names in other parts of `config.yaml` are all lowercase, the name of the custom variable must be used correctly, such as:
129 | >
130 | > ```yaml
131 | site:
132 | config:
133 | MYVAR_aaa: "Hello World"
134 | ```
135 |
136 | > then the variable must be referenced in the page by `{{.Site.Config.MYVAR_aaa}}`.
137 |
138 | #### Use Functions (Experimental)
139 |
140 | InkPaper defines a minimal set of functions that can be used in HTML pages (except for `.md` source files), such as
141 |
142 | ``` yaml
143 | {{ readFile "path/to/file" }}
144 | ```
145 |
146 | This will read the content of the file `path/to/file` and include it in the page without any processing.
147 |
148 | For file-related functions, when executed in the `source` directory, the file path is relative to the `source` directory; when executed in other directories, the file path is relative to the theme (such as `theme`).
149 |
150 | See the source file `funcs.go` for a list of all functions.
151 |
152 | ### Blog Migrate (Beta)
153 |
154 | Supports simple Jeklly/Hexo post convertions. Usage:
155 |
156 | ``` shell
157 | ink convert /path/_posts
158 | ```
159 |
160 | ### Building from source
161 |
162 | Local Build
163 |
164 | 1. Install [Golang](http://golang.org/doc/install) environment
165 | 2. Run `git clone https://github.com/InkProject/ink && cd ink && go install` to compile and install ink
166 | 3. Run `ink preview $GOPATH/src/github.com/InkProject/ink/template` to preview blog
167 |
168 | Docker Build (Example)
169 |
170 | 1. Clone code `git clone git@github.com:InkProject/ink.git`
171 | 2. Build image `docker build -t ink .` in source directory
172 | 3. Run container `docker run -p 8888:80 ink`
173 |
174 | ## Theme
175 |
176 | - Dark(Official Theme): [https://github.com/InkProject/ink-theme-dark](https://github.com/InkProject/ink-theme-dark)
177 | - simple: [https://github.com/myiq/ink-simple](https://github.com/myiq/ink-simple)
178 |
179 | ## Related Toturials
180 |
181 | - [Automatically deploy your Ink blog to GitHub pages wiht Travis CI](http://www.shery.me/blog/travis-ci.html)
182 |
183 | ## Reporting An Issue
184 |
185 | [CC Attribution-NonCommercial License 4.0](https://creativecommons.org/licenses/by-nc/4.0/)
186 |
187 | [https://github.com/InkProject/ink/issues](https://github.com/InkProject/ink/issues)
188 |
189 | ## Development Roadmap
190 |
191 | - Improve Theme
192 | - InkPaper Editor
193 |
194 | ## These blogs are driven by InkPaper
195 |
196 | - [https://imeoer.github.io/blog/](https://imeoer.github.io/blog/)
197 | - [http://blog.hyper.sh/](http://blog.hyper.sh/)
198 | - [http://wangxu.me/](http://wangxu.me/)
199 | - [http://whzecomjm.com/](http://whzecomjm.com/)
200 | - [http://www.shery.me/blog/](http://www.shery.me/blog/)
201 |
--------------------------------------------------------------------------------
/template/theme/source/js/index.js:
--------------------------------------------------------------------------------
1 | import '../css/index.css'
2 | import $ from 'jquery'
3 | import './jquery.unveil'
4 | import searchTpl from './searchTpl.html'
5 |
6 | // pick from underscore
7 | let debounce = function (func, wait, immediate) {
8 | let timeout, args, context, timestamp, result;
9 |
10 | let later = function () {
11 | let last = Date.now() - timestamp;
12 |
13 | if (last < wait && last >= 0) {
14 | timeout = setTimeout(later, wait - last);
15 | } else {
16 | timeout = null;
17 | if (!immediate) {
18 | result = func.apply(context, args);
19 | if (!timeout) context = args = null;
20 | }
21 | }
22 | };
23 |
24 | return function () {
25 | context = this;
26 | args = arguments;
27 | timestamp = Date.now();
28 | let callNow = immediate && !timeout;
29 | if (!timeout) timeout = setTimeout(later, wait);
30 | if (callNow) {
31 | result = func.apply(context, args);
32 | context = args = null;
33 | }
34 |
35 | return result;
36 | };
37 | }
38 |
39 | let timeSince = function (date) {
40 | let seconds = Math.floor((new Date() - date) / 1000)
41 | let interval = Math.floor(seconds / 31536000)
42 | if (interval > 1) return interval + timeSinceLang.year
43 |
44 | interval = Math.floor(seconds / 2592000)
45 | if (interval > 1) return interval + timeSinceLang.month
46 |
47 | interval = Math.floor(seconds / 86400)
48 | if (interval > 1) return interval + timeSinceLang.day
49 |
50 | interval = Math.floor(seconds / 3600)
51 | if (interval > 1) return interval + timeSinceLang.hour
52 |
53 | interval = Math.floor(seconds / 60)
54 | if (interval > 1) return interval + timeSinceLang.minute
55 |
56 | return Math.floor(seconds) + timeSinceLang.second
57 | }
58 |
59 | let initSearch = function () {
60 | let searchDom = $('#search')
61 | if (!searchDom.length) return
62 | let searchWorker = new Worker(root + '/bundle/searchWorker.js')
63 | let oriHtml = $('.article-list').html()
64 | let workerStarted = false
65 | let tpl = function (keywords, title, preview, link, cover) {
66 | for (let i = 0; i < keywords.length; i++) {
67 | let keyword = keywords[i]
68 | let wrap = '' + keyword + ''
69 | let reg = new RegExp(keyword, 'ig')
70 | title = title.replace(reg, wrap)
71 | preview = preview.replace(reg, wrap)
72 | }
73 | return searchTpl
74 | .replace('{{title}}', title)
75 | .replace('{{link}}', link + '?search=' + keywords) // append keywords to url
76 | .replace('{{preview}}', preview)
77 | }
78 | searchWorker.onmessage = function (event) {
79 | let results = event.data.results
80 | let keywords = event.data.keywords
81 | if (results.length) {
82 | let retHtml = ''
83 | for (let i = 0; i < results.length; i++) {
84 | let item = results[i]
85 | let itemHtml = tpl(keywords, item.title, item.preview, item.link, item.cover)
86 | retHtml += itemHtml
87 | }
88 | $('.page-nav').hide()
89 | $('.article-list').html(retHtml)
90 | } else {
91 | let keyword = event.data.keyword
92 | if (keyword) {
93 | $('.page-nav').hide()
94 | $('.article-list').html('未搜索到 "' + keyword + '"
')
95 | } else {
96 | $('.page-nav').show()
97 | $('.article-list').html(oriHtml)
98 | }
99 | }
100 | }
101 | searchDom.on('input', debounce(function () {
102 | let keyword = $(this).val().trim()
103 | if (keyword) {
104 | searchWorker.postMessage({
105 | search: 'search',
106 | keyword: keyword
107 | })
108 | } else {
109 | $('.page-nav').show()
110 | $('.article-list').html(oriHtml)
111 | }
112 | }, 500))
113 | searchDom.on('focus', function () {
114 | if (!workerStarted) {
115 | searchWorker.postMessage({
116 | action: 'start',
117 | root: root
118 | })
119 | workerStarted = true
120 | }
121 | })
122 | }
123 |
124 | $(function () {
125 | // render date
126 | $('.date').each(function (idx, item) {
127 | let $date = $(item)
128 | let timeStr = $date.data('time')
129 | if (timeStr) {
130 | let unixTime = Number(timeStr) * 1000
131 | let date = new Date(unixTime)
132 | $date.prop('title', date).text(timeSince(date))
133 | }
134 | })
135 | // render highlight
136 | import("highlight.js").then(({ default: hljs }) => {
137 | hljs.highlightAll()
138 | })
139 | // append image description
140 | $('img').each(function (_, item) {
141 | let $item = $(item)
142 | if ($item.attr('data-src')) {
143 | $item.wrap('')
144 | let imageAlt = $item.prop('alt')
145 | if (imageAlt.trim()) $item.parent('a').after('' + imageAlt + '
')
146 | }
147 | })
148 | // lazy load images
149 | if ($('img').unveil) {
150 | $('img').unveil(200, function () {
151 | $(this).on("load", function () {
152 | this.style.opacity = 1
153 | })
154 | })
155 | }
156 | // init search
157 | initSearch()
158 | })
159 |
160 | /**
161 | * Get all text nodes under an element
162 | * @param {!Element} el
163 | * @return {Array}
164 | */
165 | function getTextNodes(el) {
166 | const iterator = document.createNodeIterator(el, NodeFilter.SHOW_TEXT);
167 | const textNodes = [];
168 | let currentTextNode;
169 | while ((currentTextNode = iterator.nextNode())) {
170 | textNodes.push(currentTextNode);
171 | }
172 | return textNodes;
173 | }
174 |
175 | // get searched keywords from url
176 | let reg = new RegExp("(^|&)search=([^&]*)(&|$)")
177 | let r = window.location.search.substring(1).match(reg)
178 | if (r != null && r.toString().length > 1) {
179 | let keywords = decodeURI(r[2]).split(',')
180 | // highlight searched keywords
181 | getTextNodes(document.body).forEach(function (node) {
182 | let i;
183 | for (i = 0; i < node.parentNode.childNodes.length; i++) {
184 | if (node.parentNode.childNodes[i] === node) {
185 | break;
186 | }
187 | }
188 |
189 | let content = node.nodeValue
190 | let oldContent = content
191 | for (let i = 0; i < keywords.length; i++) {
192 | let keyword = keywords[i]
193 | let wrap = '' + keyword + ''
194 | let reg = new RegExp(keyword, 'ig')
195 | content = content.replace(reg, wrap)
196 | }
197 |
198 | // replace the text node with a span containing the highlighted text
199 | if (content != oldContent) {
200 | let newNode = document.createElement('span')
201 | newNode.innerHTML = content
202 | node.parentNode.replaceChild(newNode, node)
203 | }
204 | })
205 | // scroll to the first searched keyword
206 | let elmnt = document.getElementsByClassName("searched")
207 | elmnt[0].scrollIntoView()
208 | }
209 |
210 | // show "go top" button when needed
211 | $(function () {
212 | $("#go_top").hide();
213 | $(function () {
214 | let height = $(window).height();
215 | $(window).on("scroll", function () {
216 | if ($(window).scrollTop() > height) {
217 | $("#go_top").fadeIn(500);
218 | } else {
219 | $("#go_top").fadeOut(500);
220 | }
221 | });
222 | $("#go_top").on("click", function () {
223 | $('body,html').animate({ scrollTop: 0 }, 100);
224 | return false;
225 | });
226 | });
227 | });
--------------------------------------------------------------------------------
/template/theme/bundle/index.css:
--------------------------------------------------------------------------------
1 | .hljs{background:#f0f0f0;display:block;overflow-x:auto;padding:.5em}.hljs,.hljs-subst,.hljs-tag .hljs-title{color:#000}.hljs-attribute,.hljs-deletion,.hljs-section,.hljs-selector-class,.hljs-selector-id,.hljs-string,.hljs-symbol,.hljs-template-variable,.hljs-title,.hljs-variable{color:#800}.hljs-comment,.hljs-quote{color:#888}.hljs-addition,.hljs-bullet,.hljs-link,.hljs-literal,.hljs-number,.hljs-regexp{color:#080}.hljs-meta{color:#88f}.hljs-built_in,.hljs-doctag,.hljs-keyword,.hljs-name,.hljs-section,.hljs-selector-tag,.hljs-strong,.hljs-title,.hljs-type{font-weight:700}.hljs-emphasis{font-style:italic}@define-mixin font-main{font-family:Helvetica Neue,Arial,Hiragino Sans GB,WenQuanYi Micro Hei,Microsoft YaHei,sans-serif}@define-mixin font-code{font-family:consolas,monaco,Source Code Pro,hack,monospace}body,html,input{margin:0}body,html{height:100%}ul{list-style:none;margin:0;padding:0}a{text-decoration:none}a:hover{opacity:.9}::-moz-selection{background-color:#293846;color:#fcfcfc}::selection{background-color:#293846;color:#fcfcfc}.clearfix:after,.clearfix:before{content:" ";display:table}.clearfix:after{clear:both}.clearfix{zoom:1}.main.page{padding-bottom:70px;padding-top:100px}.main.page .header{text-align:center}.main.page .header .tag{font-size:32px;font-weight:700;margin:30px 0 0}.main.page .header .tag-sub{color:#7f8c8d;font-size:16px;font-weight:400;margin:10px 0 0}.main.page .header .search-wrap{margin:50px auto 0;max-width:300px;position:relative}.main.page .header .search-wrap .search{border:1px solid #ddd;border-radius:100px;-webkit-box-sizing:border-box;box-sizing:border-box;opacity:.8;padding:10px 25px;width:100%}.main.page .header .search-wrap .search:focus{opacity:1;outline:0}.main.page .header .search-wrap .icon{opacity:.3;position:absolute;right:10px;top:5px}.main.page .article-list{margin-top:100px}.main.page .article-list .searched{background-color:#009a61;color:#fcfcfc}.main.page .article-list .article{border-bottom:1px dashed #ddd;margin-bottom:55px;padding-bottom:45px}.main.page .article-list .article:last-child{border-bottom:0}.main.page .article-list .article .title{color:#293846;display:block;font-size:26px;font-weight:700;margin-bottom:5px;text-decoration:none}.main.page .article-list .article .title .top{color:#7f8c8d;margin-right:5px}.main.page .article-list .article .cover{background-position:50%;background-repeat:no-repeat;background-size:cover;border:1px solid #ddd;border-radius:6px;-webkit-box-sizing:border-box;box-sizing:border-box;color:#eee;height:280px;margin-top:15px;width:100%}@media (max-width:414px){.main.page .article-list .article .cover{height:180px}}.main.page .article-list .article .preview{color:#293846;display:block;font-size:16px;line-height:28px;margin-top:10px}.main.page .article-list .article .info{color:#7f8c8d;font-size:14px;margin-bottom:20px;margin-top:15px}.main.page .article-list .article .avatar{background-repeat:no-repeat;background-size:cover;border:1px solid #ddd;border-radius:20px;float:left;height:20px;margin-right:10px;margin-top:-4px;width:20px}.main.page .article-list .article .name{margin-right:10px}.main.page .article-list .article .date{color:#7f8c8d}.main.page .article-list .article .tags{float:right;margin-left:10px}.main.page .article-list .article .tags .tag{color:#7f8c8d;margin-right:5px;text-decoration:none}.main.page .article-list .empty{font-size:24px;max-width:800px;overflow:hidden;text-align:center;-o-text-overflow:ellipsis;text-overflow:ellipsis}.main.page .article-list .empty span{font-weight:700}.main.page .page-nav{font-size:14px;height:50px;position:relative}.main.page .page-nav a{color:#7f8c8d;font-style:italic;text-decoration:none}.main.page .page-nav .nav{color:#7f8c8d;display:block;left:0;position:absolute;right:0;text-align:center;z-index:-1}.main.page .page-nav .prev{float:left}.main.page .page-nav .prev:before{border:2px solid #7f8c8d;border-radius:3px;content:"";display:block;float:left;height:3px;margin-right:7px;margin-top:5px;width:3px}.main.page .page-nav .next{float:right}.main.page .page-nav .next:after{border:2px solid #7f8c8d;border-radius:3px;content:"";display:block;float:right;height:3px;margin-left:7px;margin-top:5px;width:3px}.main.article{padding-bottom:120px;padding-top:120px}.main.article .title{color:#293846;font-size:30px;font-weight:700;line-height:42px;margin:0}.main.article .info{font-size:14px;margin-top:10px}.main.article .info .avatar{background-repeat:no-repeat;background-size:cover;border:1px solid #ddd;border-radius:20px;float:left;height:20px;margin-right:10px;margin-top:-4px;width:20px}.main.article .info .name{margin-right:5px}.main.article .info .date{color:#7f8c8d;margin-right:5px}.main.article .info .tags .tag{color:#7f8c8d;margin-right:5px;text-decoration:none}.main.article .recommend{margin-top:50px;overflow:auto}.main.article .recommend .nav{width:100%}.main.article .recommend .nav .head{color:#7f8c8d;font-size:12px;font-style:italic;margin-bottom:10px;text-align:center}.main.article .recommend .nav .link{color:#293846;display:block;text-align:center}.main.article .recommend .nav.prev.more{float:left;text-align:left;width:45%}.main.article .recommend .nav.prev.more .head,.main.article .recommend .nav.prev.more .link{text-align:left}.main.article .recommend .nav.next.more{float:right;text-align:right;width:45%}.main.article .recommend .nav.next.more .head,.main.article .recommend .nav.next.more .link{text-align:right}.main.article .author{margin-top:36px;padding-top:40px;text-align:center}.main.article .author .avatar{background-repeat:no-repeat;background-size:cover;border:20px solid #fff;border-radius:70px;height:70px;margin:0 auto;width:70px}.main.article .author .avatar:before{border-top:1px dashed #ddd;content:"";left:0;margin-top:36px;position:absolute;right:0;width:100%;z-index:-1}.main.article .author .name{font-size:16px;font-weight:700;margin-top:20px}.main.article .author .intro{color:#7f8c8d;font-size:14px;font-style:italic;margin-top:8px}.main.article #disqus_thread{margin-top:50px}.main.archive,.main.tag{padding-bottom:100px;padding-top:100px}.main.archive a,.main.tag a{border-bottom:1px dashed #009a61;color:#009a61;text-decoration:none}.main.archive .site .subtitle,.main.archive .site .title,.main.tag .site .subtitle,.main.tag .site .title{text-align:left}.main.archive .header,.main.tag .header{font-size:18px;padding-top:60px}.main.archive .header .title,.main.tag .header .title{font-weight:700;margin-right:5px;margin-top:30px}.main.archive .header .subtitle,.main.tag .header .subtitle{font-style:italic}.main.archive .archive-list,.main.tag .archive-list{margin-top:50px}.main.archive .archive-list .archive-item,.main.tag .archive-list .archive-item{margin:40px 0}.main.archive .archive-list .archive-item .archive-year,.main.tag .archive-list .archive-item .archive-year{color:#293846;font-weight:700;margin:15px 0}.main.archive .archive-list .archive-item .article-list .article-item,.main.tag .archive-list .archive-item .article-list .article-item{margin-bottom:10px}.main.archive .archive-list .archive-item .article-list .article-item .date,.main.tag .archive-list .archive-item .article-list .article-item .date{color:#7f8c8d;font-style:italic;margin-right:20px}.main.archive .tag-list,.main.tag .tag-list{margin-top:50px}.main.archive .tag-list .tag-item,.main.tag .tag-list .tag-item{float:left;margin-bottom:25px;margin-right:10px}.main.archive .tag-list .tag-item .tag-name,.main.tag .tag-list .tag-item .tag-name{border:1px solid #eee;border-radius:3px;font-size:14px;padding:5px 7px}.main.archive .tag-list .tag-item .tag-name:hover,.main.tag .tag-list .tag-item .tag-name:hover{background-color:#293846;border:1px solid #293846;color:#fcfcfc}.main .content{color:#293846;font-size:16px;line-height:1.7;margin-top:70px}.main .content h1+p,.main .content h2+p,.main .content h3+p,.main .content h4+p,.main .content h5+p,.main .content h6+p{margin-top:10px}.main .content h1{font-size:28px;margin-bottom:10px;margin-top:40px}.main .content h2{font-size:24px;margin-bottom:10px;margin-top:40px}.main .content h3{font-size:18px;margin-bottom:10px;margin-top:40px}.main .content p{margin-bottom:30px;margin-top:30px;text-align:left}.main .content.preview p{margin-bottom:10px;margin-top:10px}.main .content a{border-bottom:1px dashed #009a61;color:#009a61;text-decoration:none}.main .content a:hover{border-bottom:1px dashed #004e31;color:#004e31}.main .content code{background-color:#f7f7f7;border-radius:3px;color:#009a61;font-size:14px;margin:0 3px;padding:1px 4px}.main .content pre{margin:0}.main .content pre code{background-color:#fcfcfc;border:2px dashed #eee;border-radius:6px;color:inherit;display:block;font-size:14px;margin:0;overflow:auto;padding:10px 15px}.main .content blockquote{border-left:4px solid #009a61;font-style:italic;margin:25px 0 25px -23px;padding:0 10px 0 20px}.main .content table{border-collapse:collapse;border-color:#ddd;border-width:1px;font-size:14px;width:100%}.main .content table th{background-color:#eee;border:1px solid #ddd;padding:5px}.main .content table td{background-color:#fcfcfc;border:1px solid #ddd;padding:5px}.main .content ul{list-style:circle;padding-left:40px}.main .content ul li{margin:5px 0}.main .content ol{padding-left:40px}.main .content ol li{margin:5px 0}.main .content hr{border:0;border-top:1px dashed #ddd;margin:25px 0}.main .content img{border-radius:5px;cursor:pointer;display:block;margin:30px auto;max-width:100%;opacity:.6;-webkit-transition:opacity .3s ease-in;-o-transition:opacity .3s ease-in;transition:opacity .3s ease-in}.main .content .image-alt{color:#7f8c8d;font-style:italic;margin-bottom:30px;margin-top:-10px;text-align:center}.main .content .searched{background-color:#ff0}.header-wrap{-webkit-box-pack:justify;-ms-flex-pack:justify;-webkit-box-align:center;-ms-flex-align:center;-webkit-align-items:center;align-items:center;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;font-size:18px;font-weight:700;-webkit-justify-content:space-between;justify-content:space-between;left:0;padding:15px;position:absolute;right:0;z-index:1}.header-wrap a{color:#a1b2b4}.header-wrap .index{-webkit-box-align:center;-ms-flex-align:center;-webkit-align-items:center;align-items:center;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.header-wrap .index .logo{border:1px solid #ddd;border-radius:50%;height:30px;margin-right:10px;width:30px}.header-wrap .menu{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.header-wrap .menu .menu-item{margin-right:10px}.main,a.name{color:#293846}.main{margin:0 auto;max-width:720px;min-height:100%;padding:0 15px;position:relative}.main .site .logo{background-repeat:no-repeat;background-size:cover;border:1px solid #ddd;border-radius:50%;height:80px;margin:0 auto;width:80px}.main .site .title{font-size:32px;font-weight:700;margin:30px 0 0;text-align:center}.main .site .subtitle{color:#a1b2b4;font-size:16px;font-style:italic;font-weight:400;margin-top:10px;text-align:center}.main.about .info{margin-top:80px;text-align:center}.container{height:auto;min-height:100%;padding-bottom:0}.footer{background-color:#fcfcfc;border-top:1px solid #eee;clear:both;color:#a1b2b4;font-size:14px;height:32px;margin-top:-36px;position:relative;width:100%}.footer span{display:block;padding:8px 15px}.footer span a{font-weight:700}.footer .copyright{float:left}.footer .publish{float:right}.footer .publish a{color:#a1b2b4;text-decoration:none}
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bufio"
5 | "fmt"
6 | "html/template"
7 | "os"
8 | "os/exec"
9 | "path/filepath"
10 | "runtime"
11 | "strings"
12 | "time"
13 |
14 | "github.com/edwardrf/symwalk"
15 | "github.com/urfave/cli/v2"
16 | "gopkg.in/yaml.v2"
17 | )
18 |
19 | const (
20 | VERSION = "RELEASE 2018-07-27"
21 | DEFAULT_ROOT = "blog"
22 | DATE_FORMAT_STRING = "2006-01-02 15:04:05"
23 | INDENT = " " // 2 spaces
24 | POST_TEMPLATE = `title: {{.Title}}
25 | date: {{.DateString}}
26 | author: {{.Author}}
27 | {{- if .Cover}}
28 | cover: {{.Cover}}
29 | {{- end}}
30 | draft: {{.Draft}}
31 | top: {{.Top}}
32 | {{- if .Preview}}
33 | preview: {{.Preview}}
34 | {{- end}}
35 | {{- if .Tags}}
36 | {{.Tags}}
37 | {{- end}}
38 | type: {{.Type}}
39 | hide: {{.Hide}}
40 | toc: {{.Toc}}
41 | ---
42 | `
43 | )
44 |
45 | var globalConfig *GlobalConfig
46 | var themeConfig *ThemeConfig
47 | var rootPath string
48 |
49 | func main() {
50 |
51 | app := cli.NewApp()
52 | app.Name = "ink"
53 | app.Usage = "An elegant static blog generator"
54 | app.Authors = []*cli.Author{
55 | {Name: "Harrison", Email: "harrison@lolwut.com"},
56 | {Name: "Oliver Allen", Email: "oliver@toyshop.com"},
57 | }
58 | //app.Email = "imeoer@gmail.com"
59 | app.Version = VERSION
60 | app.Commands = []*cli.Command{
61 | {
62 | Name: "build",
63 | Usage: "Generate blog to public folder",
64 | Action: func(c *cli.Context) error {
65 | ParseGlobalConfigByCli(c, false)
66 | Build()
67 | return nil
68 | },
69 | },
70 | {
71 | Name: "preview",
72 | Usage: "Run in server mode to preview blog",
73 | Action: func(c *cli.Context) error {
74 | ParseGlobalConfigByCli(c, true)
75 | Build()
76 | Watch()
77 | Serve()
78 | return nil
79 | },
80 | },
81 | {
82 | Name: "publish",
83 | Usage: "Generate blog to public folder and publish",
84 | Action: func(c *cli.Context) error {
85 | ParseGlobalConfigByCli(c, false)
86 | Build()
87 | Publish()
88 | return nil
89 | },
90 | },
91 | {
92 | Name: "serve",
93 | Usage: "Run in server mode to serve blog",
94 | Action: func(c *cli.Context) error {
95 | ParseGlobalConfigByCli(c, true)
96 | Build()
97 | Serve()
98 | return nil
99 | },
100 | },
101 | {
102 | Name: "convert",
103 | Usage: "Convert Jekyll/Hexo post format to Ink format (Beta)",
104 | Action: func(c *cli.Context) error {
105 | Convert(c)
106 | return nil
107 | },
108 | },
109 | {
110 | Name: "new",
111 | Usage: "Creates a new article",
112 | Flags: []cli.Flag{
113 | &cli.BoolFlag{
114 | Name: "hide",
115 | Usage: "Hides the article",
116 | },
117 | &cli.BoolFlag{
118 | Name: "toc",
119 | Usage: "Adds a table of contents to the article",
120 | },
121 | &cli.BoolFlag{
122 | Name: "top",
123 | Usage: "Places the article at the top",
124 | },
125 | &cli.BoolFlag{
126 | Name: "post",
127 | Usage: "The article is a post",
128 | },
129 | &cli.BoolFlag{
130 | Name: "page",
131 | Usage: "The article is a page",
132 | },
133 | &cli.BoolFlag{
134 | Name: "draft",
135 | Usage: "The article is a draft",
136 | },
137 |
138 | &cli.StringFlag{
139 | Name: "title",
140 | Usage: "Article title",
141 | },
142 | &cli.StringFlag{
143 | Name: "author",
144 | Usage: "Article author",
145 | },
146 | &cli.StringFlag{
147 | Name: "cover",
148 | Usage: "Article cover path",
149 | },
150 | &cli.StringFlag{
151 | Name: "date",
152 | Usage: "The date and time on which the article was created (2006-01-02 15:04:05)",
153 | },
154 | &cli.StringFlag{
155 | Name: "file",
156 | Usage: "The path of where the article will be stored",
157 | },
158 |
159 | &cli.StringSliceFlag{
160 | Name: "tag",
161 | Usage: "Adds a tag to the article",
162 | },
163 | },
164 | Action: func(c *cli.Context) error {
165 | New(c)
166 | return nil
167 | },
168 | },
169 | }
170 | app.Run(os.Args)
171 | os.Exit(exitCode)
172 | }
173 |
174 | func ParseGlobalConfigByCli(c *cli.Context, develop bool) {
175 | if c.Args().Len() > 0 {
176 | rootPath = c.Args().Slice()[0]
177 | } else {
178 | rootPath = "."
179 | }
180 | ParseGlobalConfigWrap(rootPath, develop)
181 | if globalConfig == nil {
182 | ParseGlobalConfigWrap(DEFAULT_ROOT, develop)
183 | if globalConfig == nil {
184 | Fatal("Parse config.yml failed, please specify a valid path")
185 | }
186 | }
187 | }
188 |
189 | func ParseGlobalConfigWrap(root string, develop bool) {
190 | rootPath = root
191 | globalConfig, themeConfig = ParseGlobalConfig(filepath.Join(rootPath, "config.yml"), develop)
192 | if globalConfig == nil || themeConfig == nil {
193 | return
194 | }
195 | }
196 |
197 | func New(c *cli.Context) {
198 | // If source folder does not exist, create
199 | if _, err := os.Stat("source/"); os.IsNotExist(err) {
200 | os.Mkdir("source", os.ModePerm)
201 | }
202 |
203 | var author, blogTitle, fileName string
204 | var tags []string
205 |
206 | // Default values
207 | draft := "false"
208 | top := "false"
209 | postType := "post"
210 | hide := "false"
211 | toc := "false"
212 | date := time.Now()
213 |
214 | // Empty string values
215 | preview := ""
216 | cover := ""
217 |
218 | // Parse args
219 | args := c.Args()
220 | if args.Len() > 0 {
221 | blogTitle = args.Slice()[0]
222 | }
223 | if blogTitle == "" {
224 | if c.String("title") != "" {
225 | blogTitle = c.String("title")
226 | } else {
227 | Fatal("Please specify the name of the blog post")
228 | }
229 | }
230 |
231 | fileName = blogTitle + ".md"
232 | if c.String("file") != "" {
233 | fileName = c.String("file")
234 | }
235 |
236 | if args.Len() > 1 {
237 | author = args.Slice()[1]
238 | }
239 | if author == "" {
240 | author = c.String("author")
241 | }
242 |
243 | if c.Bool("post") && c.Bool("page") {
244 | Fatal("The post and page arguments are mutually exclusive and cannot appear together")
245 | }
246 | if c.Bool("post") {
247 | postType = "post"
248 | }
249 | if c.Bool("page") {
250 | postType = "page"
251 | }
252 | if c.Bool("hide") {
253 | hide = "true"
254 | }
255 | if c.Bool("toc") {
256 | toc = "true"
257 | }
258 | if c.Bool("draft") {
259 | draft = "true"
260 | }
261 | if c.Bool("top") {
262 | top = "true"
263 | }
264 |
265 | if c.String("preview") != "" {
266 | preview = c.String("preview")
267 | }
268 | if c.String("cover") != "" {
269 | cover = c.String("cover")
270 | }
271 |
272 | var filePath = "source/" + fileName
273 | file, err := os.Create(filePath)
274 | if err != nil {
275 | Fatal(err)
276 | }
277 | postTemplate, err := template.New("post").Parse(POST_TEMPLATE)
278 | if err != nil {
279 | Fatal(err)
280 | }
281 |
282 | if c.StringSlice("tag") != nil {
283 | tags = c.StringSlice("tag")
284 | }
285 |
286 | var tagString string
287 | if len(tags) > 0 {
288 | tagString = "tags:"
289 | for _, tag := range tags {
290 | tagString += "\n" + INDENT + "- " + tag
291 | }
292 | }
293 |
294 | var dateString string
295 | if c.String("date") != "" {
296 | dateString = c.String("date")
297 | _, err = time.Parse(DATE_FORMAT_STRING, dateString)
298 | if err != nil {
299 | Fatal("Illegal date string")
300 | }
301 | } else {
302 | dateString = date.Format(DATE_FORMAT_STRING)
303 | }
304 | data := map[string]string{
305 | "Title": blogTitle,
306 | "DateString": dateString,
307 | "Author": author,
308 | "Draft": draft,
309 | "Top": top,
310 | "Type": postType,
311 | "Hide": hide,
312 | "Toc": toc,
313 | "Preview": preview,
314 | "Cover": cover,
315 | "Tags": tagString,
316 | }
317 | fileWriter := bufio.NewWriter(file)
318 | err = postTemplate.Execute(fileWriter, data)
319 | if err != nil {
320 | Fatal(err)
321 | }
322 | err = fileWriter.Flush()
323 | if err != nil {
324 | Fatal(err)
325 | }
326 | }
327 |
328 | func Publish() {
329 | command := globalConfig.Build.Publish
330 | // Prepare exec command
331 | var shell, flag string
332 | if runtime.GOOS == "windows" {
333 | shell = "cmd"
334 | flag = "/C"
335 | } else {
336 | shell = "/bin/sh"
337 | flag = "-c"
338 | }
339 | cmd := exec.Command(shell, flag, command)
340 | cmd.Dir = filepath.Join(rootPath, globalConfig.Build.Output)
341 | // Start print stdout and stderr of process
342 | stdout, _ := cmd.StdoutPipe()
343 | stderr, _ := cmd.StderrPipe()
344 | out := bufio.NewScanner(stdout)
345 | err := bufio.NewScanner(stderr)
346 | // Print stdout
347 | go func() {
348 | for out.Scan() {
349 | Log(out.Text())
350 | }
351 | }()
352 | // Print stdin
353 | go func() {
354 | for err.Scan() {
355 | Log(err.Text())
356 | }
357 | }()
358 | // Exec command
359 | cmd.Run()
360 | }
361 |
362 | func Convert(c *cli.Context) {
363 | // Parse arguments
364 | var sourcePath, rootPath string
365 | args := c.Args()
366 | if args.Len() > 0 {
367 | sourcePath = args.Slice()[0]
368 | } else {
369 | Fatal("Please specify the posts source path")
370 | }
371 | if args.Len() > 1 {
372 | rootPath = args.Slice()[1]
373 | } else {
374 | rootPath = "."
375 | }
376 | // Check if path exist
377 | if !Exists(sourcePath) || !Exists(rootPath) {
378 | Fatal("Please specify valid path")
379 | }
380 | // Parse Jekyll/Hexo post file
381 | count := 0
382 | symwalk.Walk(sourcePath, func(path string, f os.FileInfo, err error) error {
383 | fileExt := strings.ToLower(filepath.Ext(path))
384 | if fileExt == ".md" || fileExt == ".html" {
385 | // Read data from file
386 | data, err := os.ReadFile(path)
387 | fileName := filepath.Base(path)
388 | Log("Converting " + fileName)
389 | if err != nil {
390 | Fatal(err.Error())
391 | }
392 | // Split config and markdown
393 | var configStr, contentStr string
394 | content := strings.TrimSpace(string(data))
395 | parseAry := strings.SplitN(content, "---", 3)
396 | parseLen := len(parseAry)
397 | if parseLen == 3 { // Jekyll
398 | configStr = parseAry[1]
399 | contentStr = parseAry[2]
400 | } else if parseLen == 2 { // Hexo
401 | configStr = parseAry[0]
402 | contentStr = parseAry[1]
403 | }
404 | // Parse config
405 | var article ArticleConfig
406 | if err = yaml.Unmarshal([]byte(configStr), &article); err != nil {
407 | Fatal(err.Error())
408 | }
409 | tags := make(map[string]bool)
410 | for _, t := range article.Tags {
411 | tags[t] = true
412 | }
413 | for _, c := range article.Categories {
414 | if _, ok := tags[c]; !ok {
415 | article.Tags = append(article.Tags, c)
416 | }
417 | }
418 | if article.Author == "" {
419 | article.Author = "me"
420 | }
421 | // Convert date
422 | dateAry := strings.SplitN(article.Date, ".", 2)
423 | if len(dateAry) == 2 {
424 | article.Date = dateAry[0]
425 | }
426 | if len(article.Date) == 10 {
427 | article.Date = article.Date + " 00:00:00"
428 | }
429 | if len(article.Date) == 0 {
430 | article.Date = "1970-01-01 00:00:00"
431 | }
432 | article.Update = ""
433 | // Generate Config
434 | var inkConfig []byte
435 | if inkConfig, err = yaml.Marshal(article); err != nil {
436 | Fatal(err.Error())
437 | }
438 | inkConfigStr := string(inkConfig)
439 | markdownStr := inkConfigStr + "\n\n---\n\n" + contentStr + "\n"
440 | targetName := "source/" + fileName
441 | if fileExt != ".md" {
442 | targetName = targetName + ".md"
443 | }
444 | os.WriteFile(filepath.Join(rootPath, targetName), []byte(markdownStr), 0644)
445 | count++
446 | }
447 | return nil
448 | })
449 | fmt.Printf("\nConvert finish, total %v articles\n", count)
450 | }
451 |
--------------------------------------------------------------------------------
/parse.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "html/template"
5 | "io"
6 | "os"
7 | "path/filepath"
8 | "strings"
9 | "time"
10 |
11 | "gopkg.in/yaml.v2"
12 |
13 | gomk "github.com/gomarkdown/markdown"
14 | "github.com/gomarkdown/markdown/ast"
15 | "github.com/gomarkdown/markdown/html"
16 | "github.com/gomarkdown/markdown/parser"
17 | )
18 |
19 | type SiteConfig struct {
20 | Root string
21 | Title string
22 | Subtitle string
23 | Logo string
24 | Limit int
25 | Theme string
26 | Comment string
27 | Lang string
28 | Url string
29 | Link string
30 | Config interface{}
31 | }
32 |
33 | type AuthorConfig struct {
34 | Id string
35 | Name string
36 | Intro string
37 | Avatar string
38 | }
39 |
40 | type BuildConfig struct {
41 | Output string
42 | Port string
43 | Watch bool
44 | Copy []string
45 | Publish string
46 | }
47 |
48 | type GlobalConfig struct {
49 | I18n map[string]string
50 | Site SiteConfig
51 | Authors map[string]AuthorConfig
52 | Build BuildConfig
53 | Develop bool
54 | }
55 |
56 | type ArticleConfig struct {
57 | Title string
58 | Date string
59 | Update string
60 | Author string
61 | Tags []string
62 | Categories []string
63 | Topic string
64 | Cover string
65 | Draft bool
66 | Preview template.HTML
67 | Top bool
68 | Type string
69 | Hide bool
70 | Toc bool
71 | Image string
72 | Subtitle string
73 | Config map[string]interface{}
74 | }
75 |
76 | type Article struct {
77 | GlobalConfig
78 | ArticleConfig
79 | Time time.Time
80 | MTime time.Time
81 | Date int64
82 | Update int64
83 | Author AuthorConfig
84 | Category string
85 | Tags []string
86 | Markdown string
87 | Preview template.HTML
88 | Content template.HTML
89 | Link string
90 | Config interface{}
91 | Image string
92 | Subtitle string
93 | }
94 |
95 | type ThemeConfig struct {
96 | Copy []string
97 | Lang map[string]map[string]string
98 | }
99 |
100 | const (
101 | CONFIG_SPLIT = "---"
102 | MORE_SPLIT = ""
103 | )
104 |
105 | // Modify the rendering of the image node to use jQuery-unveil to lazy load images.
106 | //
107 | // Note: This is a hacky way to do this, but it works for now.
108 | // LazyLoadImages in markdown.html.Flags should not be used with this hook, and
109 | // adding the absolute path prefix to the image destination (which is what the original parser does)
110 | // is not implemented yet.
111 | func renderHookLazyLoadImage(w io.Writer, node ast.Node, entering bool) (ast.WalkStatus, bool) {
112 | // Skip all nodes that are not Image nodes
113 | if _, ok := node.(*ast.Image); !ok {
114 | return ast.GoToNext, false
115 | }
116 | img := node.(*ast.Image)
117 | if entering {
118 | if img.Attribute == nil {
119 | img.Attribute = &ast.Attribute{}
120 | }
121 | if img.Attrs == nil {
122 | img.Attrs = make(map[string][]byte)
123 | }
124 | img.Attrs["data-src"] = img.Destination
125 | img.Destination = []byte("data:image/gif;base64,R0lGODlhGAAYAPQAAP///wAAAM7Ozvr6+uDg4LCwsOjo6I6OjsjIyJycnNjY2KioqMDAwPLy8nd3d4aGhri4uGlpaQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkHAAAAIf4aQ3JlYXRlZCB3aXRoIGFqYXhsb2FkLmluZm8AIf8LTkVUU0NBUEUyLjADAQAAACwAAAAAGAAYAAAFriAgjiQAQWVaDgr5POSgkoTDjFE0NoQ8iw8HQZQTDQjDn4jhSABhAAOhoTqSDg7qSUQwxEaEwwFhXHhHgzOA1xshxAnfTzotGRaHglJqkJcaVEqCgyoCBQkJBQKDDXQGDYaIioyOgYSXA36XIgYMBWRzXZoKBQUMmil0lgalLSIClgBpO0g+s26nUWddXyoEDIsACq5SsTMMDIECwUdJPw0Mzsu0qHYkw72bBmozIQAh+QQJBwAAACwAAAAAGAAYAAAFsCAgjiTAMGVaDgR5HKQwqKNxIKPjjFCk0KNXC6ATKSI7oAhxWIhezwhENTCQEoeGCdWIPEgzESGxEIgGBWstEW4QCGGAIJEoxGmGt5ZkgCRQQHkGd2CESoeIIwoMBQUMP4cNeQQGDYuNj4iSb5WJnmeGng0CDGaBlIQEJziHk3sABidDAHBgagButSKvAAoyuHuUYHgCkAZqebw0AgLBQyyzNKO3byNuoSS8x8OfwIchACH5BAkHAAAALAAAAAAYABgAAAW4ICCOJIAgZVoOBJkkpDKoo5EI43GMjNPSokXCINKJCI4HcCRIQEQvqIOhGhBHhUTDhGo4diOZyFAoKEQDxra2mAEgjghOpCgz3LTBIxJ5kgwMBShACREHZ1V4Kg1rS44pBAgMDAg/Sw0GBAQGDZGTlY+YmpyPpSQDiqYiDQoCliqZBqkGAgKIS5kEjQ21VwCyp76dBHiNvz+MR74AqSOdVwbQuo+abppo10ssjdkAnc0rf8vgl8YqIQAh+QQJBwAAACwAAAAAGAAYAAAFrCAgjiQgCGVaDgZZFCQxqKNRKGOSjMjR0qLXTyciHA7AkaLACMIAiwOC1iAxCrMToHHYjWQiA4NBEA0Q1RpWxHg4cMXxNDk4OBxNUkPAQAEXDgllKgMzQA1pSYopBgonCj9JEA8REQ8QjY+RQJOVl4ugoYssBJuMpYYjDQSliwasiQOwNakALKqsqbWvIohFm7V6rQAGP6+JQLlFg7KDQLKJrLjBKbvAor3IKiEAIfkECQcAAAAsAAAAABgAGAAABbUgII4koChlmhokw5DEoI4NQ4xFMQoJO4uuhignMiQWvxGBIQC+AJBEUyUcIRiyE6CR0CllW4HABxBURTUw4nC4FcWo5CDBRpQaCoF7VjgsyCUDYDMNZ0mHdwYEBAaGMwwHDg4HDA2KjI4qkJKUiJ6faJkiA4qAKQkRB3E0i6YpAw8RERAjA4tnBoMApCMQDhFTuySKoSKMJAq6rD4GzASiJYtgi6PUcs9Kew0xh7rNJMqIhYchACH5BAkHAAAALAAAAAAYABgAAAW0ICCOJEAQZZo2JIKQxqCOjWCMDDMqxT2LAgELkBMZCoXfyCBQiFwiRsGpku0EshNgUNAtrYPT0GQVNRBWwSKBMp98P24iISgNDAS4ipGA6JUpA2WAhDR4eWM/CAkHBwkIDYcGiTOLjY+FmZkNlCN3eUoLDmwlDW+AAwcODl5bYl8wCVYMDw5UWzBtnAANEQ8kBIM0oAAGPgcREIQnVloAChEOqARjzgAQEbczg8YkWJq8nSUhACH5BAkHAAAALAAAAAAYABgAAAWtICCOJGAYZZoOpKKQqDoORDMKwkgwtiwSBBYAJ2owGL5RgxBziQQMgkwoMkhNqAEDARPSaiMDFdDIiRSFQowMXE8Z6RdpYHWnEAWGPVkajPmARVZMPUkCBQkJBQINgwaFPoeJi4GVlQ2Qc3VJBQcLV0ptfAMJBwdcIl+FYjALQgimoGNWIhAQZA4HXSpLMQ8PIgkOSHxAQhERPw7ASTSFyCMMDqBTJL8tf3y2fCEAIfkECQcAAAAsAAAAABgAGAAABa8gII4k0DRlmg6kYZCoOg5EDBDEaAi2jLO3nEkgkMEIL4BLpBAkVy3hCTAQKGAznM0AFNFGBAbj2cA9jQixcGZAGgECBu/9HnTp+FGjjezJFAwFBQwKe2Z+KoCChHmNjVMqA21nKQwJEJRlbnUFCQlFXlpeCWcGBUACCwlrdw8RKGImBwktdyMQEQciB7oACwcIeA4RVwAODiIGvHQKERAjxyMIB5QlVSTLYLZ0sW8hACH5BAkHAAAALAAAAAAYABgAAAW0ICCOJNA0ZZoOpGGQrDoOBCoSxNgQsQzgMZyIlvOJdi+AS2SoyXrK4umWPM5wNiV0UDUIBNkdoepTfMkA7thIECiyRtUAGq8fm2O4jIBgMBA1eAZ6Knx+gHaJR4QwdCMKBxEJRggFDGgQEREPjjAMBQUKIwIRDhBDC2QNDDEKoEkDoiMHDigICGkJBS2dDA6TAAnAEAkCdQ8ORQcHTAkLcQQODLPMIgIJaCWxJMIkPIoAt3EhACH5BAkHAAAALAAAAAAYABgAAAWtICCOJNA0ZZoOpGGQrDoOBCoSxNgQsQzgMZyIlvOJdi+AS2SoyXrK4umWHM5wNiV0UN3xdLiqr+mENcWpM9TIbrsBkEck8oC0DQqBQGGIz+t3eXtob0ZTPgNrIwQJDgtGAgwCWSIMDg4HiiUIDAxFAAoODwxDBWINCEGdSTQkCQcoegADBaQ6MggHjwAFBZUFCm0HB0kJCUy9bAYHCCPGIwqmRq0jySMGmj6yRiEAIfkECQcAAAAsAAAAABgAGAAABbIgII4k0DRlmg6kYZCsOg4EKhLE2BCxDOAxnIiW84l2L4BLZKipBopW8XRLDkeCiAMyMvQAA+uON4JEIo+vqukkKQ6RhLHplVGN+LyKcXA4Dgx5DWwGDXx+gIKENnqNdzIDaiMECwcFRgQCCowiCAcHCZIlCgICVgSfCEMMnA0CXaU2YSQFoQAKUQMMqjoyAglcAAyBAAIMRUYLCUkFlybDeAYJryLNk6xGNCTQXY0juHghACH5BAkHAAAALAAAAAAYABgAAAWzICCOJNA0ZVoOAmkY5KCSSgSNBDE2hDyLjohClBMNij8RJHIQvZwEVOpIekRQJyJs5AMoHA+GMbE1lnm9EcPhOHRnhpwUl3AsknHDm5RN+v8qCAkHBwkIfw1xBAYNgoSGiIqMgJQifZUjBhAJYj95ewIJCQV7KYpzBAkLLQADCHOtOpY5PgNlAAykAEUsQ1wzCgWdCIdeArczBQVbDJ0NAqyeBb64nQAGArBTt8R8mLuyPyEAOw==")
126 | } else {
127 | w.Write([]byte("\" data-src=\""))
128 | w.Write(img.Attrs["data-src"])
129 | }
130 | return ast.GoToNext, false
131 | }
132 |
133 | func ParseMarkdown(markdown string, toc bool) template.HTML {
134 | extensions := parser.CommonExtensions | parser.Footnotes
135 | parser := parser.NewWithExtensions(extensions)
136 |
137 | htmlFlags := html.CommonFlags
138 | if toc {
139 | htmlFlags |= html.TOC
140 | }
141 | opts := html.RendererOptions{Flags: htmlFlags, RenderNodeHook: renderHookLazyLoadImage}
142 | renderer := html.NewRenderer(opts)
143 |
144 | return template.HTML(gomk.ToHTML(gomk.NormalizeNewlines([]byte(markdown)), parser, renderer))
145 | }
146 |
147 | func ReplaceRootFlag(content string) string {
148 | return strings.Replace(content, "-/", globalConfig.Site.Root+"/", -1)
149 | }
150 |
151 | func ParseGlobalConfig(configPath string, develop bool) (*GlobalConfig, *ThemeConfig) {
152 | var config *GlobalConfig
153 | // Parse Global Config
154 | data, err := os.ReadFile(configPath)
155 | if err != nil {
156 | return nil, nil
157 | }
158 | if err = yaml.Unmarshal(data, &config); err != nil {
159 | Fatal(err.Error())
160 | }
161 | if config.Site.Config == nil {
162 | config.Site.Config = ""
163 | }
164 | config.Develop = develop
165 | if develop {
166 | config.Site.Root = ""
167 | }
168 | config.Site.Logo = strings.Replace(config.Site.Logo, "-/", config.Site.Root+"/", -1)
169 | if config.Site.Url != "" && strings.HasSuffix(config.Site.Url, "/") {
170 | config.Site.Url = strings.TrimSuffix(config.Site.Url, "/")
171 | }
172 | if config.Build.Output == "" {
173 | config.Build.Output = "public"
174 | }
175 | // Parse Theme Config
176 | themeConfig := ParseThemeConfig(filepath.Join(rootPath, config.Site.Theme, "config.yml"))
177 | for _, copyItem := range themeConfig.Copy {
178 | config.Build.Copy = append(config.Build.Copy, filepath.Join(config.Site.Theme, copyItem))
179 | }
180 | config.I18n = make(map[string]string)
181 | for item, langItem := range themeConfig.Lang {
182 | config.I18n[item] = langItem[config.Site.Lang]
183 | }
184 | return config, themeConfig
185 | }
186 |
187 | func ParseThemeConfig(configPath string) *ThemeConfig {
188 | // Read data from file
189 | var themeConfig *ThemeConfig
190 | data, err := os.ReadFile(configPath)
191 | if err != nil {
192 | Fatal(err.Error())
193 | }
194 | // Parse config content
195 | if err := yaml.Unmarshal(data, &themeConfig); err != nil {
196 | Fatal(err.Error())
197 | }
198 | return themeConfig
199 | }
200 |
201 | func ParseArticleConfig(markdownPath string) (config *ArticleConfig, content string) {
202 | var configStr string
203 | // Read data from file
204 | data, err := os.ReadFile(markdownPath)
205 | if err != nil {
206 | Fatal(err.Error())
207 | }
208 | // Split config and markdown
209 | contentStr := string(data)
210 | contentStr = ReplaceRootFlag(contentStr)
211 | markdownStr := strings.SplitN(contentStr, CONFIG_SPLIT, 2)
212 | contentLen := len(markdownStr)
213 | if contentLen > 0 {
214 | configStr = markdownStr[0]
215 | }
216 | if contentLen > 1 {
217 | content = markdownStr[1]
218 | }
219 | // Parse config content
220 | if err := yaml.Unmarshal([]byte(configStr), &config); err != nil {
221 | Error(err.Error())
222 | return nil, ""
223 | }
224 | if config == nil {
225 | return nil, ""
226 | }
227 | if config.Type == "" {
228 | config.Type = "post"
229 | }
230 | // Parse preview splited by MORE_SPLIT
231 | previewAry := strings.SplitN(content, MORE_SPLIT, 2)
232 | if len(config.Preview) <= 0 && len(previewAry) > 1 {
233 | config.Preview = ParseMarkdown(previewAry[0], false)
234 | content = strings.Replace(content, MORE_SPLIT, "", 1)
235 | } else {
236 | config.Preview = ParseMarkdown(string(config.Preview), false)
237 | }
238 | return config, content
239 | }
240 |
241 | func ParseArticle(markdownPath string) *Article {
242 | config, content := ParseArticleConfig(markdownPath)
243 | if config == nil {
244 | Error("Invalid format: " + markdownPath)
245 | return nil
246 | }
247 | if config.Config == nil {
248 | config.Config = make(map[string]interface{})
249 | }
250 | var article Article
251 | // Parse markdown content
252 | article.Hide = config.Hide
253 | article.Type = config.Type
254 | article.Preview = config.Preview
255 | article.Config = config.Config
256 | article.Markdown = content
257 | article.Content = ParseMarkdown(content, config.Toc)
258 | if config.Date != "" {
259 | article.Time = ParseDate(config.Date)
260 | article.Date = article.Time.Unix()
261 | }
262 | if config.Update != "" {
263 | article.MTime = ParseDate(config.Update)
264 | article.Update = article.MTime.Unix()
265 | }
266 | article.Title = config.Title
267 | article.Topic = config.Topic
268 | article.Draft = config.Draft
269 | article.Top = config.Top
270 | article.Image = config.Image
271 | article.Subtitle = config.Subtitle
272 | if author, ok := globalConfig.Authors[config.Author]; ok {
273 | author.Id = config.Author
274 | author.Avatar = ReplaceRootFlag(author.Avatar)
275 | article.Author = author
276 | }
277 | if len(config.Categories) > 0 {
278 | article.Category = config.Categories[0]
279 | } else {
280 | article.Category = "misc"
281 | }
282 | tags := map[string]bool{}
283 | article.Tags = config.Tags
284 | for _, tag := range config.Tags {
285 | tags[tag] = true
286 | }
287 | for _, cat := range config.Categories {
288 | if _, ok := tags[cat]; !ok {
289 | article.Tags = append(article.Tags, cat)
290 | }
291 | }
292 | // Support topic and cover field
293 | if config.Cover != "" {
294 | article.Cover = config.Cover
295 | } else {
296 | article.Cover = config.Topic
297 | }
298 | // Generate page name
299 | fileName := strings.TrimSuffix(strings.ToLower(filepath.Base(markdownPath)), ".md")
300 | link := fileName + ".html"
301 | // Genetate custom link
302 | if article.Type == "post" {
303 | datePrefix := article.Time.Format("2006-01-02-")
304 | fileName = strings.TrimPrefix(fileName, datePrefix)
305 | if globalConfig.Site.Link != "" {
306 | linkMap := map[string]string{
307 | "{year}": article.Time.Format("2006"),
308 | "{month}": article.Time.Format("01"),
309 | "{day}": article.Time.Format("02"),
310 | "{hour}": article.Time.Format("15"),
311 | "{minute}": article.Time.Format("04"),
312 | "{second}": article.Time.Format("05"),
313 | "{category}": article.Category,
314 | "{title}": fileName,
315 | }
316 | link = globalConfig.Site.Link
317 | for key, val := range linkMap {
318 | link = strings.Replace(link, key, val, -1)
319 | }
320 | }
321 | }
322 | article.Link = link
323 | article.GlobalConfig = *globalConfig
324 | return &article
325 | }
326 |
--------------------------------------------------------------------------------
/template/theme/bundle/main.css:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Original style from softwaremaniacs.org (c) Ivan Sagalaev
4 |
5 | */
6 |
7 | .hljs {
8 | display: block;
9 | overflow-x: auto;
10 | padding: 0.5em;
11 | background: #f0f0f0;
12 | }
13 |
14 | .hljs,
15 | .hljs-subst,
16 | .hljs-tag .hljs-title {
17 | color: black;
18 | }
19 |
20 | .hljs-string,
21 | .hljs-title,
22 | .hljs-section,
23 | .hljs-attribute,
24 | .hljs-selector-id,
25 | .hljs-selector-class,
26 | .hljs-variable,
27 | .hljs-template-variable,
28 | .hljs-symbol,
29 | .hljs-deletion {
30 | color: #800;
31 | }
32 |
33 | .hljs-comment,
34 | .hljs-quote {
35 | color: #888;
36 | }
37 |
38 | .hljs-number,
39 | .hljs-regexp,
40 | .hljs-literal,
41 | .hljs-addition,
42 | .hljs-bullet,
43 | .hljs-link {
44 | color: #080;
45 | }
46 |
47 | .hljs-meta {
48 | color: #88f;
49 | }
50 |
51 | .hljs-keyword,
52 | .hljs-selector-tag,
53 | .hljs-title,
54 | .hljs-name,
55 | .hljs-section,
56 | .hljs-built_in,
57 | .hljs-doctag,
58 | .hljs-type,
59 | .hljs-strong {
60 | font-weight: bold;
61 | }
62 |
63 | .hljs-emphasis {
64 | font-style: italic;
65 | }
66 |
67 | @define-mixin font-main {
68 | font-family: "Helvetica Neue", Arial, "Hiragino Sans GB", "WenQuanYi Micro Hei", "Microsoft YaHei", sans-serif;
69 | }
70 |
71 | @define-mixin font-code {
72 | font-family: consolas, monaco, "Source Code Pro", hack, monospace;
73 | }
74 |
75 | html, body, input {
76 | margin: 0;
77 | }
78 |
79 | html, body {
80 | height: 100%;
81 | }
82 |
83 | ul {
84 | list-style: none;
85 | margin: 0;
86 | padding: 0;
87 | }
88 |
89 | a {
90 | text-decoration: none
91 | }
92 |
93 | a:hover {
94 | opacity: 0.9;
95 | }
96 |
97 | *::-moz-selection {
98 | background-color: #293846;
99 | color: #fcfcfc;
100 | }
101 |
102 | *::selection {
103 | background-color: #293846;
104 | color: #fcfcfc;
105 | }
106 |
107 | /*clearfix*/
108 |
109 | .clearfix:before, .clearfix:after {
110 | content: " ";
111 | display: table;
112 | }
113 |
114 | .clearfix:after {
115 | clear: both;
116 | }
117 |
118 | .clearfix {
119 | zoom: 1;
120 | }
121 |
122 | .main.page {
123 | padding-top: 100px;
124 | padding-bottom: 70px;
125 | }
126 |
127 | .main.page .header {
128 | text-align: center;
129 | }
130 |
131 | .main.page .header .tag {
132 | margin: 0;
133 | margin-top: 30px;
134 | font-weight: bold;
135 | font-size: 32px;
136 | }
137 |
138 | .main.page .header .tag-sub {
139 | margin: 0;
140 | font-weight: normal;
141 | margin-top: 10px;
142 | font-size: 16px;
143 | color: #7f8c8d;
144 | }
145 |
146 | .main.page .header .search-wrap {
147 | position: relative;
148 | max-width: 300px;
149 | margin: 0 auto;
150 | margin-top: 50px;
151 | }
152 |
153 | .main.page .header .search-wrap .search {
154 | padding: 10px 25px;
155 | border: 1px solid #ddd;
156 | border-radius: 100px;
157 | width: 100%;
158 | -webkit-box-sizing: border-box;
159 | box-sizing: border-box;
160 | opacity: 0.8
161 | }
162 |
163 | .main.page .header .search-wrap .search:focus {
164 | outline: 0;
165 | opacity: 1;
166 | }
167 |
168 | .main.page .header .search-wrap .icon {
169 | position: absolute;
170 | right: 10px;
171 | top: 5px;
172 | opacity: 0.3;
173 | }
174 |
175 | .main.page .article-list {
176 | margin-top: 100px;
177 | }
178 |
179 | .main.page .article-list .searched {
180 | background-color: #009a61;
181 | color: #fcfcfc;
182 | }
183 |
184 | .main.page .article-list .article {
185 | margin-bottom: 55px;
186 | padding-bottom: 45px;
187 | border-bottom: 1px #ddd dashed
188 | }
189 |
190 | .main.page .article-list .article:last-child {
191 | border-bottom: 0;
192 | }
193 |
194 | .main.page .article-list .article .title {
195 | font-size: 26px;
196 | font-weight: bold;
197 | margin-bottom: 5px;
198 | text-decoration: none;
199 | color: #293846;
200 | display: block;
201 | }
202 |
203 | .main.page .article-list .article .title .top {
204 | color: #7f8c8d;
205 | margin-right: 5px;
206 | }
207 |
208 | .main.page .article-list .article .cover {
209 | color: #eee;
210 | width: 100%;
211 | height: 280px;
212 | background-size: cover;
213 | background-position: center;
214 | background-repeat: no-repeat;
215 | border-radius: 6px;
216 | border: 1px solid #ddd;
217 | -webkit-box-sizing: border-box;
218 | box-sizing: border-box;
219 | margin-top: 15px
220 | }
221 |
222 | @media (max-width: 414px) {
223 | .main.page .article-list .article .cover {
224 | height: 180px
225 | }
226 | }
227 |
228 | .main.page .article-list .article .preview {
229 | display: inline-block;
230 | line-height: 28px;
231 | font-size: 16px;
232 | margin-top: 10px;
233 | color: #293846;
234 | }
235 |
236 | .main.page .article-list .article .info {
237 | margin-top: 15px;
238 | margin-bottom: 20px;
239 | color: #7f8c8d;
240 | font-size: 14px;
241 | }
242 |
243 | .main.page .article-list .article .avatar {
244 | width: 20px;
245 | height: 20px;
246 | margin-right: 10px;
247 | border-radius: 20px;
248 | background-size: cover;
249 | background-repeat: no-repeat;
250 | float: left;
251 | margin-top: -4px;
252 | border: 1px #ddd solid;
253 | }
254 |
255 | .main.page .article-list .article .name {
256 | margin-right: 10px;
257 | }
258 |
259 | .main.page .article-list .article .date {
260 | color: #7f8c8d;
261 | }
262 |
263 | .main.page .article-list .article .tags {
264 | margin-left: 10px;
265 | float: right;
266 | }
267 |
268 | .main.page .article-list .article .tags .tag {
269 | margin-right: 5px;
270 | color: #7f8c8d;
271 | text-decoration: none;
272 | }
273 |
274 | .main.page .article-list .empty {
275 | text-align: center;
276 | font-size: 24px;
277 | max-width: 800px;
278 | overflow: hidden;
279 | -o-text-overflow: ellipsis;
280 | text-overflow: ellipsis;
281 | }
282 |
283 | .main.page .article-list .empty span {
284 | font-weight: bold;
285 | }
286 |
287 | .main.page .page-nav {
288 | position: relative;
289 | height: 50px;
290 | font-size: 14px;
291 | }
292 |
293 | .main.page .page-nav a {
294 | color: #7f8c8d;
295 | font-style: italic;
296 | text-decoration: none;
297 | }
298 |
299 | .main.page .page-nav .nav {
300 | left: 0;
301 | right: 0;
302 | text-align: center;
303 | display: block;
304 | color: #7f8c8d;
305 | position: absolute;
306 | z-index: -1;
307 | }
308 |
309 | .main.page .page-nav .prev {
310 | float: left
311 | }
312 |
313 | .main.page .page-nav .prev:before {
314 | content: "";
315 | width: 3px;
316 | height: 3px;
317 | border: 2px #7f8c8d solid;
318 | border-radius: 3px;
319 | display: block;
320 | float: left;
321 | margin-right: 7px;
322 | margin-top: 5px;
323 | }
324 |
325 | .main.page .page-nav .next {
326 | float: right
327 | }
328 |
329 | .main.page .page-nav .next:after {
330 | content: "";
331 | width: 3px;
332 | height: 3px;
333 | border: 2px #7f8c8d solid;
334 | border-radius: 3px;
335 | display: block;
336 | float: right;
337 | margin-left: 7px;
338 | margin-top: 5px;
339 | }
340 |
341 | .main.article {
342 | padding-top: 120px;
343 | padding-bottom: 120px;
344 | }
345 |
346 | .main.article .title {
347 | margin: 0;
348 | font-weight: bold;
349 | color: #293846;
350 | font-size: 30px;
351 | line-height: 42px;
352 | }
353 |
354 | .main.article .info {
355 | margin-top: 10px;
356 | font-size: 14px;
357 | }
358 |
359 | .main.article .info .avatar {
360 | width: 20px;
361 | height: 20px;
362 | margin-right: 10px;
363 | border-radius: 20px;
364 | background-size: cover;
365 | background-repeat: no-repeat;
366 | float: left;
367 | margin-top: -4px;
368 | border: 1px #ddd solid;
369 | }
370 |
371 | .main.article .info .name {
372 | margin-right: 5px;
373 | }
374 |
375 | .main.article .info .date {
376 | color: #7f8c8d;
377 | margin-right: 5px;
378 | }
379 |
380 | .main.article .info .tags .tag {
381 | margin-right: 5px;
382 | color: #7f8c8d;
383 | text-decoration: none;
384 | }
385 |
386 | .main.article .recommend {
387 | margin-top: 50px;
388 | overflow: auto;
389 | }
390 |
391 | .main.article .recommend .nav {
392 | width: 100%;
393 | }
394 |
395 | .main.article .recommend .nav .head {
396 | font-size: 12px;
397 | font-style: italic;
398 | margin-bottom: 10px;
399 | color: #7f8c8d;
400 | text-align: center;
401 | }
402 |
403 | .main.article .recommend .nav .link {
404 | color: #293846;
405 | text-align: center;
406 | display: block;
407 | }
408 |
409 | .main.article .recommend .nav.prev.more {
410 | width: 45%;
411 | text-align: left;
412 | float: left;
413 | }
414 |
415 | .main.article .recommend .nav.prev.more .head, .main.article .recommend .nav.prev.more .link {
416 | text-align: left;
417 | }
418 |
419 | .main.article .recommend .nav.next.more {
420 | width: 45%;
421 | text-align: right;
422 | float: right;
423 | }
424 |
425 | .main.article .recommend .nav.next.more .head, .main.article .recommend .nav.next.more .link {
426 | text-align: right;
427 | }
428 |
429 | .main.article .author {
430 | margin-top: 36px;
431 | padding-top: 40px;
432 | text-align: center;
433 | }
434 |
435 | .main.article .author .avatar {
436 | margin: 0 auto;
437 | width: 70px;
438 | height: 70px;
439 | border-radius: 70px;
440 | background-size: cover;
441 | background-repeat: no-repeat;
442 | border: 20px white solid
443 | }
444 |
445 | .main.article .author .avatar:before {
446 | border-top: 1px #ddd dashed;
447 | content: "";
448 | width: 100%;
449 | position: absolute;
450 | left: 0;
451 | right: 0;
452 | margin-top: 36px;
453 | z-index: -1;
454 | }
455 |
456 | .main.article .author .name {
457 | margin-top: 20px;
458 | font-weight: bold;
459 | font-size: 16px;
460 | }
461 |
462 | .main.article .author .intro {
463 | font-style: italic;
464 | margin-top: 8px;
465 | font-size: 14px;
466 | color: #7f8c8d;
467 | }
468 |
469 | .main.article #disqus_thread {
470 | margin-top: 50px;
471 | }
472 |
473 | .main.archive, .main.tag {
474 | padding-top: 100px;
475 | padding-bottom: 100px;
476 | }
477 |
478 | .main.archive a, .main.tag a {
479 | color: #009a61;
480 | text-decoration: none;
481 | border-bottom: 1px dashed #009a61;
482 | }
483 |
484 | .main.archive .site .title, .main.archive .site .subtitle, .main.tag .site .title, .main.tag .site .subtitle {
485 | text-align: left;
486 | }
487 |
488 | .main.archive .header, .main.tag .header {
489 | padding-top: 60px;
490 | font-size: 18px;
491 | }
492 |
493 | .main.archive .header .title, .main.tag .header .title {
494 | margin-top: 30px;
495 | margin-right: 5px;
496 | font-weight: bold;
497 | }
498 |
499 | .main.archive .header .subtitle, .main.tag .header .subtitle {
500 | font-style: italic;
501 | }
502 |
503 | .main.archive .archive-list, .main.tag .archive-list {
504 | margin-top: 50px;
505 | }
506 |
507 | .main.archive .archive-list .archive-item, .main.tag .archive-list .archive-item {
508 | margin: 40px 0;
509 | }
510 |
511 | .main.archive .archive-list .archive-item .archive-year, .main.tag .archive-list .archive-item .archive-year {
512 | margin: 15px 0;
513 | color: #293846;
514 | font-weight: bold;
515 | }
516 |
517 | .main.archive .archive-list .archive-item .article-list .article-item, .main.tag .archive-list .archive-item .article-list .article-item {
518 | margin-bottom: 10px;
519 | }
520 |
521 | .main.archive .archive-list .archive-item .article-list .article-item .date, .main.tag .archive-list .archive-item .article-list .article-item .date {
522 | color: #7f8c8d;
523 | margin-right: 20px;
524 | font-style: italic;
525 | }
526 |
527 | .main.archive .tag-list, .main.tag .tag-list {
528 | margin-top: 50px;
529 | }
530 |
531 | .main.archive .tag-list .tag-item, .main.tag .tag-list .tag-item {
532 | margin-right: 10px;
533 | margin-bottom: 25px;
534 | float: left;
535 | }
536 |
537 | .main.archive .tag-list .tag-item .tag-name, .main.tag .tag-list .tag-item .tag-name {
538 | padding: 5px 7px;
539 | border: 1px solid #eee;
540 | border-radius: 3px;
541 | font-size: 14px
542 | }
543 |
544 | .main.archive .tag-list .tag-item .tag-name:hover, .main.tag .tag-list .tag-item .tag-name:hover {
545 | background-color: #293846;
546 | color: #fcfcfc;
547 | border: 1px solid #293846;
548 | }
549 |
550 | .main .content {
551 | margin-top: 70px;
552 | font-size: 16px;
553 | line-height: 1.7;
554 | color: #293846;
555 | }
556 |
557 | .main .content h1 + p, .main .content h2 + p, .main .content h3 + p, .main .content h4 + p, .main .content h5 + p, .main .content h6 + p {
558 | margin-top: 10px;
559 | }
560 |
561 | .main .content h1 {
562 | font-size: 28px;
563 | margin-top: 40px;
564 | margin-bottom: 10px;
565 | }
566 |
567 | .main .content h2 {
568 | font-size: 24px;
569 | margin-top: 40px;
570 | margin-bottom: 10px;
571 | }
572 |
573 | .main .content h3 {
574 | font-size: 18px;
575 | margin-top: 40px;
576 | margin-bottom: 10px;
577 | }
578 |
579 | .main .content p {
580 | margin-bottom: 30px;
581 | margin-top: 30px;
582 | text-align: left;
583 | }
584 |
585 | .main .content.preview p {
586 | margin-bottom: 10px;
587 | margin-top: 10px;
588 | }
589 |
590 | .main .content a {
591 | color: #009a61;
592 | text-decoration: none;
593 | border-bottom: 1px dashed #009a61
594 | }
595 |
596 | .main .content a:hover {
597 | color: #004e31;
598 | border-bottom: 1px dashed #004e31;
599 | }
600 |
601 | .main .content code {
602 | font-size: 14px;
603 | padding: 1px 4px;
604 | border-radius: 3px;
605 | margin: 0px 3px;
606 | background-color: #f7f7f7;
607 | color: #009a61;
608 | }
609 |
610 | .main .content pre {
611 | margin: 0;
612 | }
613 |
614 | .main .content pre code {
615 | color: inherit;
616 | font-size: 14px;
617 | margin: 0;
618 | padding: 10px 15px;
619 | border-radius: 6px;
620 | border: 2px dashed #eee;
621 | background-color: #fcfcfc;
622 | display: block;
623 | overflow: auto;
624 | }
625 |
626 | .main .content blockquote {
627 | border-left: 4px #009a61 solid;
628 | padding: 0px 10px 0px 20px;
629 | margin: 25px 0;
630 | margin-left: -23px;
631 | font-style: italic;
632 | }
633 |
634 | .main .content table {
635 | font-size: 14px;
636 | width: 100%;
637 | border-width: 1px;
638 | border-color: #ddd;
639 | border-collapse: collapse;
640 | }
641 |
642 | .main .content table th {
643 | border-width: 1px;
644 | padding: 5px;
645 | border-style: solid;
646 | border-color: #ddd;
647 | background-color: #eee;
648 | }
649 |
650 | .main .content table td {
651 | border-width: 1px;
652 | padding: 5px;
653 | border-style: solid;
654 | border-color: #ddd;
655 | background-color: #fcfcfc;
656 | }
657 |
658 | .main .content ul {
659 | list-style: circle;
660 | padding-left: 40px;
661 | }
662 |
663 | .main .content ul li {
664 | margin: 5px 0;
665 | }
666 |
667 | .main .content ol {
668 | padding-left: 40px;
669 | }
670 |
671 | .main .content ol li {
672 | margin: 5px 0;
673 | }
674 |
675 | .main .content hr {
676 | margin: 25px 0;
677 | border: 0;
678 | border-top: 1px dashed #ddd;
679 | }
680 |
681 | .main .content img {
682 | margin: 30px auto;
683 | max-width: 100%;
684 | display: block;
685 | opacity: 0.6;
686 | -webkit-transition: opacity .3s ease-in;
687 | -o-transition: opacity .3s ease-in;
688 | transition: opacity .3s ease-in;
689 | cursor: pointer;
690 | border-radius: 5px;
691 | }
692 |
693 | .main .content .image-alt {
694 | text-align: center;
695 | color: #7f8c8d;
696 | font-style: italic;
697 | margin-top: -10px;
698 | margin-bottom: 30px;
699 | }
700 |
701 | .main .content .searched {
702 | background-color: yellow;
703 | }
704 |
705 | .header-wrap {
706 | padding: 15px;
707 | position: absolute;
708 | z-index: 1;
709 | font-weight: bold;
710 | font-size: 18px;
711 | left: 0;
712 | right: 0;
713 | display: -webkit-box;
714 | display: -webkit-flex;
715 | display: -ms-flexbox;
716 | display: flex;
717 | -webkit-box-pack: justify;
718 | -webkit-justify-content: space-between;
719 | -ms-flex-pack: justify;
720 | justify-content: space-between;
721 | -webkit-box-align: center;
722 | -webkit-align-items: center;
723 | -ms-flex-align: center;
724 | align-items: center;
725 | }
726 |
727 | .header-wrap a {
728 | color: #a1b2b4;
729 | }
730 |
731 | .header-wrap .index {
732 | display: -webkit-box;
733 | display: -webkit-flex;
734 | display: -ms-flexbox;
735 | display: flex;
736 | -webkit-box-align: center;
737 | -webkit-align-items: center;
738 | -ms-flex-align: center;
739 | align-items: center;
740 | }
741 |
742 | .header-wrap .index .logo {
743 | width: 30px;
744 | height: 30px;
745 | border-radius: 50%;
746 | border: 1px solid #ddd;
747 | margin-right: 10px;
748 | }
749 |
750 | .header-wrap .menu {
751 | display: -webkit-box;
752 | display: -webkit-flex;
753 | display: -ms-flexbox;
754 | display: flex;
755 | }
756 |
757 | .header-wrap .menu .menu-item {
758 | margin-right: 10px;
759 | }
760 |
761 | a.name {
762 | color: #293846;
763 | }
764 |
765 | .main {
766 | color: #293846;
767 | margin: 0 auto;
768 | max-width: 720px;
769 | padding: 0 15px;
770 | min-height: 100%;
771 | position:relative;
772 | }
773 |
774 | .main .site .logo {
775 | margin: 0 auto;
776 | width: 80px;
777 | height: 80px;
778 | border-radius: 50%;
779 | background-size: cover;
780 | background-repeat: no-repeat;
781 | border: 1px #ddd solid;
782 | }
783 |
784 | .main .site .title {
785 | margin: 0;
786 | margin-top: 30px;
787 | font-weight: bold;
788 | font-size: 32px;
789 | text-align: center;
790 | }
791 |
792 | .main .site .subtitle {
793 | font-style: italic;
794 | margin-top: 10px;
795 | font-size: 16px;
796 | color: #a1b2b4;
797 | text-align: center;
798 | font-weight: normal;
799 | }
800 |
801 | .main.about .info {
802 | margin-top: 80px;
803 | text-align: center;
804 | }
805 |
806 | .container {
807 | height: auto;
808 | min-height: 100%;
809 | padding-bottom: 0;
810 | }
811 |
812 | .footer {
813 | width: 100%;
814 | font-size: 14px;
815 | border-top: 1px #eee solid;
816 | color: #a1b2b4;
817 | background-color: #fcfcfc;
818 | clear: both;
819 | position: relative;
820 | height: 32px;
821 | margin-top: -36px;
822 | }
823 |
824 | .footer span {
825 | padding: 8px 15px;
826 | display: block;
827 | }
828 |
829 | .footer span a {
830 | font-weight: bold;
831 | }
832 |
833 | .footer .copyright {
834 | float: left;
835 | }
836 |
837 | .footer .publish {
838 | float: right;
839 | }
840 |
841 | .footer .publish a {
842 | color: #a1b2b4;
843 | text-decoration: none;
844 | }
845 |
846 |
--------------------------------------------------------------------------------
/template/theme/source/js/highlight.pack.js:
--------------------------------------------------------------------------------
1 | /*! highlight.js v8.9.1 | BSD3 License | git.io/hljslicense */
2 | !function(e){if("undefined"!=typeof exports)e(exports);else if("undefined"!=typeof window)window.hljs=e({}),"function"==typeof define&&define.amd&&define("hljs",[],function(){return window.hljs});else{if("undefined"==typeof self)throw new Error("No global object found to bind hljs variable to.");self.hljs=e({})}}(function(e){function t(e){return e.replace(/&/gm,"&").replace(//gm,">")}function r(e){return e.nodeName.toLowerCase()}function a(e,t){var r=e&&e.exec(t);return r&&0==r.index}function n(e){return/^(no-?highlight|plain|text)$/i.test(e)}function i(e){var t,r,a,i=e.className+" ";if(i+=e.parentNode?e.parentNode.className:"",r=/\blang(?:uage)?-([\w-]+)\b/i.exec(i))return y(r[1])?r[1]:"no-highlight";for(i=i.split(/\s+/),t=0,a=i.length;a>t;t++)if(y(i[t])||n(i[t]))return i[t]}function s(e,t){var r,a={};for(r in e)a[r]=e[r];if(t)for(r in t)a[r]=t[r];return a}function c(e){var t=[];return function a(e,n){for(var i=e.firstChild;i;i=i.nextSibling)3==i.nodeType?n+=i.nodeValue.length:1==i.nodeType&&(t.push({event:"start",offset:n,node:i}),n=a(i,n),r(i).match(/br|hr|img|input/)||t.push({event:"stop",offset:n,node:i}));return n}(e,0),t}function o(e,a,n){function i(){return e.length&&a.length?e[0].offset!=a[0].offset?e[0].offset"}function c(e){u+=""+r(e)+">"}function o(e){("start"==e.event?s:c)(e.node)}for(var l=0,u="",d=[];e.length||a.length;){var b=i();if(u+=t(n.substr(l,b[0].offset-l)),l=b[0].offset,b==e){d.reverse().forEach(c);do o(b.splice(0,1)[0]),b=i();while(b==e&&b.length&&b[0].offset==l);d.reverse().forEach(s)}else"start"==b[0].event?d.push(b[0].node):d.pop(),o(b.splice(0,1)[0])}return u+t(n.substr(l))}function l(e){function t(e){return e&&e.source||e}function r(r,a){return new RegExp(t(r),"m"+(e.cI?"i":"")+(a?"g":""))}function a(n,i){if(!n.compiled){if(n.compiled=!0,n.k=n.k||n.bK,n.k){var c={},o=function(t,r){e.cI&&(r=r.toLowerCase()),r.split(" ").forEach(function(e){var r=e.split("|");c[r[0]]=[t,r[1]?Number(r[1]):1]})};"string"==typeof n.k?o("keyword",n.k):Object.keys(n.k).forEach(function(e){o(e,n.k[e])}),n.k=c}n.lR=r(n.l||/\b\w+\b/,!0),i&&(n.bK&&(n.b="\\b("+n.bK.split(" ").join("|")+")\\b"),n.b||(n.b=/\B|\b/),n.bR=r(n.b),n.e||n.eW||(n.e=/\B|\b/),n.e&&(n.eR=r(n.e)),n.tE=t(n.e)||"",n.eW&&i.tE&&(n.tE+=(n.e?"|":"")+i.tE)),n.i&&(n.iR=r(n.i)),void 0===n.r&&(n.r=1),n.c||(n.c=[]);var l=[];n.c.forEach(function(e){e.v?e.v.forEach(function(t){l.push(s(e,t))}):l.push("self"==e?n:e)}),n.c=l,n.c.forEach(function(e){a(e,n)}),n.starts&&a(n.starts,i);var u=n.c.map(function(e){return e.bK?"\\.?("+e.b+")\\.?":e.b}).concat([n.tE,n.i]).map(t).filter(Boolean);n.t=u.length?r(u.join("|"),!0):{exec:function(){return null}}}}a(e)}function u(e,r,n,i){function s(e,t){for(var r=0;r";return i+=e+'">',i+t+s}function m(){if(!x.k)return t(E);var e="",r=0;x.lR.lastIndex=0;for(var a=x.lR.exec(E);a;){e+=t(E.substr(r,a.index-r));var n=b(x,a);n?(B+=n[1],e+=p(n[0],t(a[0]))):e+=t(a[0]),r=x.lR.lastIndex,a=x.lR.exec(E)}return e+t(E.substr(r))}function f(){var e="string"==typeof x.sL;if(e&&!N[x.sL])return t(E);var r=e?u(x.sL,E,!0,C[x.sL]):d(E,x.sL.length?x.sL:void 0);return x.r>0&&(B+=r.r),e&&(C[x.sL]=r.top),p(r.language,r.value,!1,!0)}function g(){return void 0!==x.sL?f():m()}function h(e,r){var a=e.cN?p(e.cN,"",!0):"";e.rB?(M+=a,E=""):e.eB?(M+=t(r)+a,E=""):(M+=a,E=r),x=Object.create(e,{parent:{value:x}})}function _(e,r){if(E+=e,void 0===r)return M+=g(),0;var a=s(r,x);if(a)return M+=g(),h(a,r),a.rB?0:r.length;var n=c(x,r);if(n){var i=x;i.rE||i.eE||(E+=r),M+=g();do x.cN&&(M+=""),B+=x.r,x=x.parent;while(x!=n.parent);return i.eE&&(M+=t(r)),E="",n.starts&&h(n.starts,""),i.rE?0:r.length}if(o(r,x))throw new Error('Illegal lexeme "'+r+'" for mode "'+(x.cN||"")+'"');return E+=r,r.length||1}var v=y(e);if(!v)throw new Error('Unknown language: "'+e+'"');l(v);var k,x=i||v,C={},M="";for(k=x;k!=v;k=k.parent)k.cN&&(M=p(k.cN,"",!0)+M);var E="",B=0;try{for(var $,z,L=0;;){if(x.t.lastIndex=L,$=x.t.exec(r),!$)break;z=_(r.substr(L,$.index-L),$[0]),L=$.index+z}for(_(r.substr(L)),k=x;k.parent;k=k.parent)k.cN&&(M+="");return{r:B,value:M,language:e,top:x}}catch(R){if(-1!=R.message.indexOf("Illegal"))return{r:0,value:t(r)};throw R}}function d(e,r){r=r||w.languages||Object.keys(N);var a={r:0,value:t(e)},n=a;return r.forEach(function(t){if(y(t)){var r=u(t,e,!1);r.language=t,r.r>n.r&&(n=r),r.r>a.r&&(n=a,a=r)}}),n.language&&(a.second_best=n),a}function b(e){return w.tabReplace&&(e=e.replace(/^((<[^>]+>|\t)+)/gm,function(e,t){return t.replace(/\t/g,w.tabReplace)})),w.useBR&&(e=e.replace(/\n/g,"
")),e}function p(e,t,r){var a=t?k[t]:r,n=[e.trim()];return e.match(/\bhljs\b/)||n.push("hljs"),-1===e.indexOf(a)&&n.push(a),n.join(" ").trim()}function m(e){var t=i(e);if(!n(t)){var r;w.useBR?(r=document.createElementNS("http://www.w3.org/1999/xhtml","div"),r.innerHTML=e.innerHTML.replace(/\n/g,"").replace(/
/g,"\n")):r=e;var a=r.textContent,s=t?u(t,a,!0):d(a),l=c(r);if(l.length){var m=document.createElementNS("http://www.w3.org/1999/xhtml","div");m.innerHTML=s.value,s.value=o(l,c(m),a)}s.value=b(s.value),e.innerHTML=s.value,e.className=p(e.className,t,s.language),e.result={language:s.language,re:s.r},s.second_best&&(e.second_best={language:s.second_best.language,re:s.second_best.r})}}function f(e){w=s(w,e)}function g(){if(!g.called){g.called=!0;var e=document.querySelectorAll("pre code");Array.prototype.forEach.call(e,m)}}function h(){addEventListener("DOMContentLoaded",g,!1),addEventListener("load",g,!1)}function _(t,r){var a=N[t]=r(e);a.aliases&&a.aliases.forEach(function(e){k[e]=t})}function v(){return Object.keys(N)}function y(e){return e=(e||"").toLowerCase(),N[e]||N[k[e]]}var w={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0},N={},k={};return e.highlight=u,e.highlightAuto=d,e.fixMarkup=b,e.highlightBlock=m,e.configure=f,e.initHighlighting=g,e.initHighlightingOnLoad=h,e.registerLanguage=_,e.listLanguages=v,e.getLanguage=y,e.inherit=s,e.IR="[a-zA-Z]\\w*",e.UIR="[a-zA-Z_]\\w*",e.NR="\\b\\d+(\\.\\d+)?",e.CNR="(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",e.BNR="\\b(0b[01]+)",e.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",e.BE={b:"\\\\[\\s\\S]",r:0},e.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[e.BE]},e.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[e.BE]},e.PWM={b:/\b(a|an|the|are|I|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|like)\b/},e.C=function(t,r,a){var n=e.inherit({cN:"comment",b:t,e:r,c:[]},a||{});return n.c.push(e.PWM),n.c.push({cN:"doctag",b:"(?:TODO|FIXME|NOTE|BUG|XXX):",r:0}),n},e.CLCM=e.C("//","$"),e.CBCM=e.C("/\\*","\\*/"),e.HCM=e.C("#","$"),e.NM={cN:"number",b:e.NR,r:0},e.CNM={cN:"number",b:e.CNR,r:0},e.BNM={cN:"number",b:e.BNR,r:0},e.CSSNM={cN:"number",b:e.NR+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",r:0},e.RM={cN:"regexp",b:/\//,e:/\/[gimuy]*/,i:/\n/,c:[e.BE,{b:/\[/,e:/\]/,r:0,c:[e.BE]}]},e.TM={cN:"title",b:e.IR,r:0},e.UTM={cN:"title",b:e.UIR,r:0},e.registerLanguage("apache",function(e){var t={cN:"number",b:"[\\$%]\\d+"};return{aliases:["apacheconf"],cI:!0,c:[e.HCM,{cN:"section",b:"?",e:">"},{cN:"attribute",b:/\w+/,r:0,k:{nomarkup:"order deny allow setenv rewriterule rewriteengine rewritecond documentroot sethandler errordocument loadmodule options header listen serverroot servername"},starts:{e:/$/,r:0,k:{literal:"on off all"},c:[{cN:"meta",b:"\\s\\[",e:"\\]$"},{cN:"variable",b:"[\\$%]\\{",e:"\\}",c:["self",t]},t,e.QSM]}}],i:/\S/}}),e.registerLanguage("bash",function(e){var t={cN:"variable",v:[{b:/\$[\w\d#@][\w\d_]*/},{b:/\$\{(.*?)}/}]},r={cN:"string",b:/"/,e:/"/,c:[e.BE,t,{cN:"variable",b:/\$\(/,e:/\)/,c:[e.BE]}]},a={cN:"string",b:/'/,e:/'/};return{aliases:["sh","zsh"],l:/-?[a-z\.]+/,k:{keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp",_:"-ne -eq -lt -gt -f -d -e -s -l -a"},c:[{cN:"meta",b:/^#![^\n]+sh\s*$/,r:10},{cN:"function",b:/\w[\w\d_]*\s*\(\s*\)\s*\{/,rB:!0,c:[e.inherit(e.TM,{b:/\w[\w\d_]*/})],r:0},e.HCM,e.NM,r,a,t]}}),e.registerLanguage("coffeescript",function(e){var t={keyword:"in if for while finally new do return else break catch instanceof throw try this switch continue typeof delete debugger super then unless until loop of by when and or is isnt not",literal:"true false null undefined yes no on off",built_in:"npm require console print module global window document"},r="[A-Za-z$_][0-9A-Za-z$_]*",a={cN:"subst",b:/#\{/,e:/}/,k:t},n=[e.BNM,e.inherit(e.CNM,{starts:{e:"(\\s*/)?",r:0}}),{cN:"string",v:[{b:/'''/,e:/'''/,c:[e.BE]},{b:/'/,e:/'/,c:[e.BE]},{b:/"""/,e:/"""/,c:[e.BE,a]},{b:/"/,e:/"/,c:[e.BE,a]}]},{cN:"regexp",v:[{b:"///",e:"///",c:[a,e.HCM]},{b:"//[gim]*",r:0},{b:/\/(?![ *])(\\\/|.)*?\/[gim]*(?=\W|$)/}]},{b:"@"+r},{b:"`",e:"`",eB:!0,eE:!0,sL:"javascript"}];a.c=n;var i=e.inherit(e.TM,{b:r}),s="(\\(.*\\))?\\s*\\B[-=]>",c={cN:"params",b:"\\([^\\(]",rB:!0,c:[{b:/\(/,e:/\)/,k:t,c:["self"].concat(n)}]};return{aliases:["coffee","cson","iced"],k:t,i:/\/\*/,c:n.concat([e.C("###","###"),e.HCM,{cN:"function",b:"^\\s*"+r+"\\s*=\\s*"+s,e:"[-=]>",rB:!0,c:[i,c]},{b:/[:\(,=]\s*/,r:0,c:[{cN:"function",b:s,e:"[-=]>",rB:!0,c:[c]}]},{cN:"class",bK:"class",e:"$",i:/[:="\[\]]/,c:[{bK:"extends",eW:!0,i:/[:="\[\]]/,c:[i]},i]},{b:r+":",e:":",rB:!0,rE:!0,r:0}])}}),e.registerLanguage("cpp",function(e){var t={cN:"keyword",b:"\\b[a-z\\d_]*_t\\b"},r={cN:"string",v:[e.inherit(e.QSM,{b:'((u8?|U)|L)?"'}),{b:'(u8?|U)?R"',e:'"',c:[e.BE]},{b:"'\\\\?.",e:"'",i:"."}]},a={cN:"number",v:[{b:"\\b(\\d+(\\.\\d*)?|\\.\\d+)(u|U|l|L|ul|UL|f|F)"},{b:e.CNR}],r:0},n={cN:"meta",b:"#",e:"$",k:"if else elif endif define undef warning error line pragma ifdef ifndef",c:[{b:/\\\n/,r:0},{bK:"include",e:"$",c:[r,{cN:"string",b:"<",e:">",i:"\\n"}]},r,a,e.CLCM,e.CBCM]},i=e.IR+"\\s*\\(",s={keyword:"int float while private char catch export virtual operator sizeof dynamic_cast|10 typedef const_cast|10 const struct for static_cast|10 union namespace unsigned long volatile static protected bool template mutable if public friend do goto auto void enum else break extern using class asm case typeid short reinterpret_cast|10 default double register explicit signed typename try this switch continue inline delete alignof constexpr decltype noexcept static_assert thread_local restrict _Bool complex _Complex _Imaginary atomic_bool atomic_char atomic_schar atomic_uchar atomic_short atomic_ushort atomic_int atomic_uint atomic_long atomic_ulong atomic_llong atomic_ullong",built_in:"std string cin cout cerr clog stdin stdout stderr stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap array shared_ptr abort abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc realloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf",literal:"true false nullptr NULL"};return{aliases:["c","cc","h","c++","h++","hpp"],k:s,i:"",c:[t,e.CLCM,e.CBCM,a,r,n,{b:"\\b(deque|list|queue|stack|vector|map|set|bitset|multiset|multimap|unordered_map|unordered_set|unordered_multiset|unordered_multimap|array)\\s*<",e:">",k:s,c:["self",t]},{b:e.IR+"::",k:s},{bK:"new throw return else",r:0},{cN:"function",b:"("+e.IR+"[\\*&\\s]+)+"+i,rB:!0,e:/[{;=]/,eE:!0,k:s,i:/[^\w\s\*&]/,c:[{b:i,rB:!0,c:[e.TM],r:0},{cN:"params",b:/\(/,e:/\)/,k:s,r:0,c:[e.CLCM,e.CBCM,r,a]},e.CLCM,e.CBCM,n]}]}}),e.registerLanguage("cs",function(e){var t="abstract as base bool break byte case catch char checked const continue decimal dynamic default delegate do double else enum event explicit extern false finally fixed float for foreach goto if implicit in int interface internal is lock long null when object operator out override params private protected public readonly ref sbyte sealed short sizeof stackalloc static string struct switch this true try typeof uint ulong unchecked unsafe ushort using virtual volatile void while async protected public private internal ascending descending from get group into join let orderby partial select set value var where yield",r=e.IR+"(<"+e.IR+">)?";return{aliases:["csharp"],k:t,i:/::/,c:[e.C("///","$",{rB:!0,c:[{cN:"doctag",v:[{b:"///",r:0},{b:""},{b:"?",e:">"}]}]}),e.CLCM,e.CBCM,{cN:"meta",b:"#",e:"$",k:"if else elif endif define undef warning error line region endregion pragma checksum"},{cN:"string",b:'@"',e:'"',c:[{b:'""'}]},e.ASM,e.QSM,e.CNM,{bK:"class interface",e:/[{;=]/,i:/[^\s:]/,c:[e.TM,e.CLCM,e.CBCM]},{bK:"namespace",e:/[{;=]/,i:/[^\s:]/,c:[e.inherit(e.TM,{b:"[a-zA-Z](\\.?\\w)*"}),e.CLCM,e.CBCM]},{bK:"new return throw await",r:0},{cN:"function",b:"("+r+"\\s+)+"+e.IR+"\\s*\\(",rB:!0,e:/[{;=]/,eE:!0,k:t,c:[{b:e.IR+"\\s*\\(",rB:!0,c:[e.TM],r:0},{cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,k:t,r:0,c:[e.ASM,e.QSM,e.CNM,e.CBCM]},e.CLCM,e.CBCM]}]}}),e.registerLanguage("css",function(e){var t="[a-zA-Z-][a-zA-Z0-9_-]*",r={b:/[A-Z\_\.\-]+\s*:/,rB:!0,e:";",eW:!0,c:[{cN:"attribute",b:/\S/,e:":",eE:!0,starts:{eW:!0,eE:!0,c:[e.CSSNM,e.QSM,e.ASM,e.CBCM,{cN:"number",b:"#[0-9A-Fa-f]+"},{cN:"meta",b:"!important"}]}}]};return{cI:!0,i:/[=\/|'\$]/,c:[e.CBCM,{cN:"selector-id",b:/#[A-Za-z0-9_-]+/},{cN:"selector-class",b:/\.[A-Za-z0-9_-]+/},{cN:"selector-attr",b:/\[/,e:/\]/,i:"$"},{b:/:(:)?[a-zA-Z0-9\_\-\+\(\)"']+/},{b:"@(font-face|page)",l:"[a-z-]+",k:"font-face page"},{b:"@",e:"[{;]",c:[{cN:"keyword",b:/\S+/},{b:/\s/,eW:!0,eE:!0,r:0,c:[e.ASM,e.QSM,e.CSSNM]}]},{cN:"selector-tag",b:t,r:0},{b:"{",e:"}",i:/\S/,c:[e.CBCM,r]}]}}),e.registerLanguage("diff",function(e){return{aliases:["patch"],c:[{cN:"meta",r:10,v:[{b:/^@@ +\-\d+,\d+ +\+\d+,\d+ +@@$/},{b:/^\*\*\* +\d+,\d+ +\*\*\*\*$/},{b:/^\-\-\- +\d+,\d+ +\-\-\-\-$/}]},{cN:"comment",v:[{b:/Index: /,e:/$/},{b:/=====/,e:/=====$/},{b:/^\-\-\-/,e:/$/},{b:/^\*{3} /,e:/$/},{b:/^\+\+\+/,e:/$/},{b:/\*{5}/,e:/\*{5}$/}]},{cN:"addition",b:"^\\+",e:"$"},{cN:"deletion",b:"^\\-",e:"$"},{cN:"addition",b:"^\\!",e:"$"}]}}),e.registerLanguage("http",function(e){var t="HTTP/[0-9\\.]+";return{aliases:["https"],i:"\\S",c:[{b:"^"+t,e:"$",c:[{cN:"number",b:"\\b\\d{3}\\b"}]},{b:"^[A-Z]+ (.*?) "+t+"$",rB:!0,e:"$",c:[{cN:"string",b:" ",e:" ",eB:!0,eE:!0},{b:t},{cN:"keyword",b:"[A-Z]+"}]},{cN:"attribute",b:"^\\w",e:": ",eE:!0,i:"\\n|\\s|=",starts:{e:"$",r:0}},{b:"\\n\\n",starts:{sL:[],eW:!0}}]}}),e.registerLanguage("ini",function(e){var t={cN:"string",c:[e.BE],v:[{b:"'''",e:"'''",r:10},{b:'"""',e:'"""',r:10},{b:'"',e:'"'},{b:"'",e:"'"}]};return{aliases:["toml"],cI:!0,i:/\S/,c:[e.C(";","$"),e.HCM,{cN:"section",b:/^\s*\[+/,e:/\]+/},{b:/^[a-z0-9\[\]_-]+\s*=\s*/,e:"$",rB:!0,c:[{cN:"attr",b:/[a-z0-9\[\]_-]+/},{b:/=/,eW:!0,r:0,c:[{cN:"literal",b:/\bon|off|true|false|yes|no\b/},{cN:"variable",v:[{b:/\$[\w\d"][\w\d_]*/},{b:/\$\{(.*?)}/}]},t,{cN:"number",b:/([\+\-]+)?[\d]+_[\d_]+/},e.NM]}]}]}}),e.registerLanguage("java",function(e){var t=e.UIR+"(<"+e.UIR+">)?",r="false synchronized int abstract float private char boolean static null if const for true while long strictfp finally protected import native final void enum else break transient catch instanceof byte super volatile case assert short package default double public try this switch continue throws protected public private",a="\\b(0[bB]([01]+[01_]+[01]+|[01]+)|0[xX]([a-fA-F0-9]+[a-fA-F0-9_]+[a-fA-F0-9]+|[a-fA-F0-9]+)|(([\\d]+[\\d_]+[\\d]+|[\\d]+)(\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))?|\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))([eE][-+]?\\d+)?)[lLfF]?",n={cN:"number",b:a,r:0};return{aliases:["jsp"],k:r,i:/<\/|#/,c:[e.C("/\\*\\*","\\*/",{r:0,c:[{b:/\w+@/,r:0},{cN:"doctag",b:"@[A-Za-z]+"}]}),e.CLCM,e.CBCM,e.ASM,e.QSM,{cN:"class",bK:"class interface",e:/[{;=]/,eE:!0,k:"class interface",i:/[:"\[\]]/,c:[{bK:"extends implements"},e.UTM]},{bK:"new throw return else",r:0},{cN:"function",b:"("+t+"\\s+)+"+e.UIR+"\\s*\\(",rB:!0,e:/[{;=]/,eE:!0,k:r,c:[{b:e.UIR+"\\s*\\(",rB:!0,r:0,c:[e.UTM]},{cN:"params",b:/\(/,e:/\)/,k:r,r:0,c:[e.ASM,e.QSM,e.CNM,e.CBCM]},e.CLCM,e.CBCM]},n,{cN:"meta",b:"@[A-Za-z]+"}]}}),e.registerLanguage("javascript",function(e){return{aliases:["js"],k:{keyword:"in of if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const export super debugger as async await import from as",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document Symbol Set Map WeakSet WeakMap Proxy Reflect Promise"},c:[{cN:"meta",r:10,b:/^\s*['"]use (strict|asm)['"]/},e.ASM,e.QSM,{cN:"string",b:"`",e:"`",c:[e.BE,{cN:"subst",b:"\\$\\{",e:"\\}"}]},e.CLCM,e.CBCM,{cN:"number",v:[{b:"\\b(0[bB][01]+)"},{b:"\\b(0[oO][0-7]+)"},{b:e.CNR}],r:0},{b:"("+e.RSR+"|\\b(case|return|throw)\\b)\\s*",k:"return throw case",c:[e.CLCM,e.CBCM,e.RM,{b:/,e:/>\s*[);\]]/,r:0,sL:"xml"}],r:0},{cN:"function",bK:"function",e:/\{/,eE:!0,c:[e.inherit(e.TM,{b:/[A-Za-z$_][0-9A-Za-z$_]*/}),{cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,c:[e.CLCM,e.CBCM]}],i:/\[|%/},{b:/\$[(.]/},{b:"\\."+e.IR,r:0},{cN:"class",bK:"class",e:/[{;=]/,eE:!0,i:/[:"\[\]]/,c:[{bK:"extends"},e.UTM]},{bK:"constructor",e:/\{/,eE:!0}],i:/#/}}),e.registerLanguage("json",function(e){var t={literal:"true false null"},r=[e.QSM,e.CNM],a={e:",",eW:!0,eE:!0,c:r,k:t},n={b:"{",e:"}",c:[{cN:"attr",b:'\\s*"',e:'"\\s*:\\s*',eB:!0,eE:!0,c:[e.BE],i:"\\n",starts:a}],i:"\\S"},i={b:"\\[",e:"\\]",c:[e.inherit(a)],i:"\\S"};return r.splice(r.length,0,n,i),{c:r,k:t,i:"\\S"}}),e.registerLanguage("makefile",function(e){var t={cN:"variable",b:/\$\(/,e:/\)/,c:[e.BE]};return{aliases:["mk","mak"],c:[e.HCM,{b:/^\w+\s*\W*=/,rB:!0,r:0,starts:{e:/\s*\W*=/,eE:!0,starts:{e:/$/,r:0,c:[t]}}},{cN:"section",b:/^[\w]+:\s*$/},{cN:"meta",b:/^\.PHONY:/,e:/$/,k:".PHONY",l:/[\.\w]+/},{b:/^\t+/,e:/$/,r:0,c:[e.QSM,t]}]}}),e.registerLanguage("xml",function(e){var t="[A-Za-z0-9\\._:-]+",r={b:/<\?(php)?(?!\w)/,e:/\?>/,sL:"php"},a={eW:!0,i:/,r:0,c:[r,{cN:"attr",b:t,r:0},{b:"=",r:0,c:[{cN:"string",c:[r],v:[{b:/"/,e:/"/},{b:/'/,e:/'/},{b:/[^\s\/>]+/}]}]}]};return{aliases:["html","xhtml","rss","atom","xsl","plist"],cI:!0,c:[{cN:"meta",b:"",r:10,c:[{b:"\\[",e:"\\]"}]},e.C("",{r:10}),{b:"<\\!\\[CDATA\\[",e:"\\]\\]>",r:10},{cN:"tag",b:"",rE:!0,sL:"css"}},{cN:"tag",b:"",rE:!0,sL:["actionscript","javascript","handlebars"]}},r,{cN:"meta",b:/<\?\w+/,e:/\?>/,r:10},{cN:"tag",b:"?",e:"/?>",c:[{cN:"name",b:/[^ \/><\n\t]+/,r:0},a]}]}}),e.registerLanguage("markdown",function(e){return{aliases:["md","mkdown","mkd"],c:[{cN:"section",v:[{b:"^#{1,6}",e:"$"},{b:"^.+?\\n[=-]{2,}$"}]},{b:"<",e:">",sL:"xml",r:0},{cN:"bullet",b:"^([*+-]|(\\d+\\.))\\s+"},{cN:"strong",b:"[*_]{2}.+?[*_]{2}"},{cN:"emphasis",v:[{b:"\\*.+?\\*"},{b:"_.+?_",r:0}]},{cN:"quote",b:"^>\\s+",e:"$"},{cN:"code",v:[{b:"`.+?`"},{b:"^( {4}| )",e:"$",r:0}]},{b:"^[-\\*]{3,}",e:"$"},{b:"\\[.+?\\][\\(\\[].*?[\\)\\]]",rB:!0,c:[{cN:"string",b:"\\[",e:"\\]",eB:!0,rE:!0,r:0},{cN:"link",b:"\\]\\(",e:"\\)",eB:!0,eE:!0},{cN:"symbol",b:"\\]\\[",e:"\\]",eB:!0,eE:!0}],r:10},{b:"^\\[.+\\]:",rB:!0,c:[{cN:"symbol",b:"\\[",e:"\\]:",eB:!0,eE:!0,starts:{cN:"link",e:"$"}}]}]}}),e.registerLanguage("nginx",function(e){var t={cN:"variable",v:[{b:/\$\d+/},{b:/\$\{/,e:/}/},{b:"[\\$\\@]"+e.UIR}]},r={eW:!0,l:"[a-z/_]+",k:{literal:"on off yes no true false none blocked debug info notice warn error crit select break last permanent redirect kqueue rtsig epoll poll /dev/poll"},r:0,i:"=>",c:[e.HCM,{cN:"string",c:[e.BE,t],v:[{b:/"/,e:/"/},{b:/'/,e:/'/}]},{b:"([a-z]+):/",e:"\\s",eW:!0,eE:!0,c:[t]},{cN:"regexp",c:[e.BE,t],v:[{b:"\\s\\^",e:"\\s|{|;",rE:!0},{b:"~\\*?\\s+",e:"\\s|{|;",rE:!0},{b:"\\*(\\.[a-z\\-]+)+"},{b:"([a-z\\-]+\\.)+\\*"}]},{cN:"number",b:"\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(:\\d{1,5})?\\b"},{cN:"number",b:"\\b\\d+[kKmMgGdshdwy]*\\b",r:0},t]};return{aliases:["nginxconf"],c:[e.HCM,{b:e.UIR+"\\s+{",rB:!0,e:"{",c:[{cN:"section",b:e.UIR}],r:0},{b:e.UIR+"\\s",e:";|{",rB:!0,c:[{cN:"attribute",b:e.UIR,starts:r}],r:0}],i:"[^\\s\\}]"}}),e.registerLanguage("objectivec",function(e){var t={cN:"built_in",b:"(AV|CA|CF|CG|CI|MK|MP|NS|UI)\\w+"},r={keyword:"int float while char export sizeof typedef const struct for union unsigned long volatile static bool mutable if do return goto void enum else break extern asm case short default double register explicit signed typename this switch continue wchar_t inline readonly assign readwrite self @synchronized id typeof nonatomic super unichar IBOutlet IBAction strong weak copy in out inout bycopy byref oneway __strong __weak __block __autoreleasing @private @protected @public @try @property @end @throw @catch @finally @autoreleasepool @synthesize @dynamic @selector @optional @required",literal:"false true FALSE TRUE nil YES NO NULL",built_in:"BOOL dispatch_once_t dispatch_queue_t dispatch_sync dispatch_async dispatch_once"},a=/[a-zA-Z@][a-zA-Z0-9_]*/,n="@interface @class @protocol @implementation";return{aliases:["mm","objc","obj-c"],k:r,l:a,i:"",c:[t,e.CLCM,e.CBCM,e.CNM,e.QSM,{cN:"string",v:[{b:'@"',e:'"',i:"\\n",c:[e.BE]},{b:"'",e:"[^\\\\]'",i:"[^\\\\][^']"}]},{cN:"meta",b:"#",e:"$",c:[{cN:"string",v:[{b:'"',e:'"'},{b:"<",e:">"}]}]},{cN:"class",b:"("+n.split(" ").join("|")+")\\b",e:"({|$)",eE:!0,k:n,l:a,c:[e.UTM]},{b:"\\."+e.UIR,r:0}]}}),e.registerLanguage("perl",function(e){var t="getpwent getservent quotemeta msgrcv scalar kill dbmclose undef lc ma syswrite tr send umask sysopen shmwrite vec qx utime local oct semctl localtime readpipe do return format read sprintf dbmopen pop getpgrp not getpwnam rewinddir qqfileno qw endprotoent wait sethostent bless s|0 opendir continue each sleep endgrent shutdown dump chomp connect getsockname die socketpair close flock exists index shmgetsub for endpwent redo lstat msgctl setpgrp abs exit select print ref gethostbyaddr unshift fcntl syscall goto getnetbyaddr join gmtime symlink semget splice x|0 getpeername recv log setsockopt cos last reverse gethostbyname getgrnam study formline endhostent times chop length gethostent getnetent pack getprotoent getservbyname rand mkdir pos chmod y|0 substr endnetent printf next open msgsnd readdir use unlink getsockopt getpriority rindex wantarray hex system getservbyport endservent int chr untie rmdir prototype tell listen fork shmread ucfirst setprotoent else sysseek link getgrgid shmctl waitpid unpack getnetbyname reset chdir grep split require caller lcfirst until warn while values shift telldir getpwuid my getprotobynumber delete and sort uc defined srand accept package seekdir getprotobyname semop our rename seek if q|0 chroot sysread setpwent no crypt getc chown sqrt write setnetent setpriority foreach tie sin msgget map stat getlogin unless elsif truncate exec keys glob tied closedirioctl socket readlink eval xor readline binmode setservent eof ord bind alarm pipe atan2 getgrent exp time push setgrent gt lt or ne m|0 break given say state when",r={cN:"subst",b:"[$@]\\{",e:"\\}",k:t},a={b:"->{",e:"}"},n={v:[{b:/\$\d/},{b:/[\$%@](\^\w\b|#\w+(::\w+)*|{\w+}|\w+(::\w*)*)/},{b:/[\$%@][^\s\w{]/,r:0}]},i=[e.BE,r,n],s=[n,e.HCM,e.C("^\\=\\w","\\=cut",{eW:!0}),a,{cN:"string",c:i,v:[{b:"q[qwxr]?\\s*\\(",e:"\\)",r:5},{b:"q[qwxr]?\\s*\\[",e:"\\]",r:5},{b:"q[qwxr]?\\s*\\{",e:"\\}",r:5},{b:"q[qwxr]?\\s*\\|",e:"\\|",r:5},{b:"q[qwxr]?\\s*\\<",e:"\\>",r:5},{b:"qw\\s+q",e:"q",r:5},{b:"'",e:"'",c:[e.BE]},{b:'"',e:'"'},{b:"`",e:"`",c:[e.BE]},{b:"{\\w+}",c:[],r:0},{b:"-?\\w+\\s*\\=\\>",c:[],r:0}]},{cN:"number",b:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",r:0},{b:"(\\/\\/|"+e.RSR+"|\\b(split|return|print|reverse|grep)\\b)\\s*",k:"split return print reverse grep",r:0,c:[e.HCM,{cN:"regexp",b:"(s|tr|y)/(\\\\.|[^/])*/(\\\\.|[^/])*/[a-z]*",r:10},{cN:"regexp",b:"(m|qr)?/",e:"/[a-z]*",c:[e.BE],r:0}]},{cN:"function",bK:"sub",e:"(\\s*\\(.*?\\))?[;{]",eE:!0,r:5,c:[e.TM]},{b:"-\\w\\b",r:0},{b:"^__DATA__$",e:"^__END__$",sL:"mojolicious",c:[{b:"^@@.*",e:"$",cN:"comment"}]}];return r.c=s,a.c=s,{aliases:["pl"],k:t,c:s}}),e.registerLanguage("php",function(e){var t={b:"\\$+[a-zA-Z_-ÿ][a-zA-Z0-9_-ÿ]*"},r={cN:"meta",b:/<\?(php)?|\?>/},a={cN:"string",c:[e.BE,r],v:[{b:'b"',e:'"'},{b:"b'",e:"'"},e.inherit(e.ASM,{i:null}),e.inherit(e.QSM,{i:null})]},n={v:[e.BNM,e.CNM]};return{aliases:["php3","php4","php5","php6"],cI:!0,k:"and include_once list abstract global private echo interface as static endswitch array null if endwhile or const for endforeach self var while isset public protected exit foreach throw elseif include __FILE__ empty require_once do xor return parent clone use __CLASS__ __LINE__ else break print eval new catch __METHOD__ case exception default die require __FUNCTION__ enddeclare final try switch continue endfor endif declare unset true false trait goto instanceof insteadof __DIR__ __NAMESPACE__ yield finally",c:[e.CLCM,e.HCM,e.C("/\\*","\\*/",{c:[{cN:"doctag",b:"@[A-Za-z]+"},r]}),e.C("__halt_compiler.+?;",!1,{eW:!0,k:"__halt_compiler",l:e.UIR}),{cN:"string",b:/<<<['"]?\w+['"]?$/,e:/^\w+;?$/,c:[e.BE,{cN:"subst",v:[{b:/\$\w+/},{b:/\{\$/,e:/\}/}]}]},r,t,{b:/(::|->)+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/},{cN:"function",bK:"function",e:/[;{]/,eE:!0,i:"\\$|\\[|%",c:[e.UTM,{cN:"params",b:"\\(",e:"\\)",c:["self",t,e.CBCM,a,n]}]},{cN:"class",bK:"class interface",e:"{",eE:!0,i:/[:\(\$"]/,c:[{bK:"extends implements"},e.UTM]},{bK:"namespace",e:";",i:/[\.']/,c:[e.UTM]},{bK:"use",e:";",c:[e.UTM]},{b:"=>"},a,n]}}),e.registerLanguage("python",function(e){var t={cN:"meta",b:/^(>>>|\.\.\.) /},r={cN:"string",c:[e.BE],v:[{b:/(u|b)?r?'''/,e:/'''/,c:[t],r:10},{b:/(u|b)?r?"""/,e:/"""/,c:[t],r:10},{b:/(u|r|ur)'/,e:/'/,r:10},{b:/(u|r|ur)"/,e:/"/,r:10},{b:/(b|br)'/,e:/'/},{b:/(b|br)"/,e:/"/},e.ASM,e.QSM]},a={cN:"number",r:0,v:[{b:e.BNR+"[lLjJ]?"},{b:"\\b(0o[0-7]+)[lLjJ]?"},{b:e.CNR+"[lLjJ]?"}]},n={cN:"params",b:/\(/,e:/\)/,c:["self",t,a,r]};return{aliases:["py","gyp"],k:{keyword:"and elif is global as in if from raise for except finally print import pass return exec else break not with class assert yield try while continue del or def lambda async await nonlocal|10 None True False",built_in:"Ellipsis NotImplemented"},i:/(<\/|->|\?)/,c:[t,a,r,e.HCM,{v:[{cN:"function",bK:"def",r:10},{cN:"class",bK:"class"}],e:/:/,i:/[${=;\n,]/,c:[e.UTM,n]},{cN:"meta",b:/^[\t ]*@/,e:/$/},{b:/\b(print|exec)\(/}]}}),e.registerLanguage("ruby",function(e){var t="[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?",r="and false then defined module in return redo if BEGIN retry end for true self when next until do begin unless END rescue nil else break undef not super class case require yield alias while ensure elsif or include attr_reader attr_writer attr_accessor",a={cN:"doctag",b:"@[A-Za-z]+"},n={b:"#<",e:">"},i=[e.C("#","$",{c:[a]}),e.C("^\\=begin","^\\=end",{c:[a],r:10}),e.C("^__END__","\\n$")],s={cN:"subst",b:"#\\{",e:"}",k:r},c={cN:"string",c:[e.BE,s],v:[{b:/'/,e:/'/},{b:/"/,e:/"/},{b:/`/,e:/`/},{b:"%[qQwWx]?\\(",e:"\\)"},{b:"%[qQwWx]?\\[",e:"\\]"},{b:"%[qQwWx]?{",e:"}"},{b:"%[qQwWx]?<",e:">"},{b:"%[qQwWx]?/",e:"/"},{b:"%[qQwWx]?%",e:"%"},{b:"%[qQwWx]?-",e:"-"},{b:"%[qQwWx]?\\|",e:"\\|"},{b:/\B\?(\\\d{1,3}|\\x[A-Fa-f0-9]{1,2}|\\u[A-Fa-f0-9]{4}|\\?\S)\b/}]},o={cN:"params",b:"\\(",e:"\\)",endsParent:!0,k:r},l=[c,n,{cN:"class",bK:"class module",e:"$|;",i:/=/,c:[e.inherit(e.TM,{b:"[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?"}),{b:"<\\s*",c:[{b:"("+e.IR+"::)?"+e.IR}]}].concat(i)},{cN:"function",bK:"def",e:"$|;",c:[e.inherit(e.TM,{b:t}),o].concat(i)},{cN:"symbol",b:e.UIR+"(\\!|\\?)?:",r:0},{cN:"symbol",b:":",c:[c,{b:t}],r:0},{cN:"number",b:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",r:0},{b:"(\\$\\W)|((\\$|\\@\\@?)(\\w+))"},{b:"("+e.RSR+")\\s*",c:[n,{cN:"regexp",c:[e.BE,s],i:/\n/,v:[{b:"/",e:"/[a-z]*"},{b:"%r{",e:"}[a-z]*"},{b:"%r\\(",e:"\\)[a-z]*"},{b:"%r!",e:"![a-z]*"},{b:"%r\\[",e:"\\][a-z]*"}]}].concat(i),r:0}].concat(i);s.c=l,o.c=l;var u="[>?]>",d="[\\w#]+\\(\\w+\\):\\d+:\\d+>",b="(\\w+-)?\\d+\\.\\d+\\.\\d(p\\d+)?[^>]+>",p=[{b:/^\s*=>/,starts:{e:"$",c:l}},{cN:"meta",b:"^("+u+"|"+d+"|"+b+")",starts:{e:"$",c:l}}];return{aliases:["rb","gemspec","podspec","thor","irb"],k:r,i:/\/\*/,c:i.concat(p).concat(l)}}),e.registerLanguage("sql",function(e){var t=e.C("--","$");return{cI:!0,i:/[<>{}*]/,c:[{bK:"begin end start commit rollback savepoint lock alter create drop rename call delete do handler insert load replace select truncate update set show pragma grant merge describe use explain help declare prepare execute deallocate release unlock purge reset change stop analyze cache flush optimize repair kill install uninstall checksum restore check backup revoke",e:/;/,eW:!0,k:{keyword:"abort abs absolute acc acce accep accept access accessed accessible account acos action activate add addtime admin administer advanced advise aes_decrypt aes_encrypt after agent aggregate ali alia alias allocate allow alter always analyze ancillary and any anydata anydataset anyschema anytype apply archive archived archivelog are as asc ascii asin assembly assertion associate asynchronous at atan atn2 attr attri attrib attribu attribut attribute attributes audit authenticated authentication authid authors auto autoallocate autodblink autoextend automatic availability avg backup badfile basicfile before begin beginning benchmark between bfile bfile_base big bigfile bin binary_double binary_float binlog bit_and bit_count bit_length bit_or bit_xor bitmap blob_base block blocksize body both bound buffer_cache buffer_pool build bulk by byte byteordermark bytes c cache caching call calling cancel capacity cascade cascaded case cast catalog category ceil ceiling chain change changed char_base char_length character_length characters characterset charindex charset charsetform charsetid check checksum checksum_agg child choose chr chunk class cleanup clear client clob clob_base clone close cluster_id cluster_probability cluster_set clustering coalesce coercibility col collate collation collect colu colum column column_value columns columns_updated comment commit compact compatibility compiled complete composite_limit compound compress compute concat concat_ws concurrent confirm conn connec connect connect_by_iscycle connect_by_isleaf connect_by_root connect_time connection consider consistent constant constraint constraints constructor container content contents context contributors controlfile conv convert convert_tz corr corr_k corr_s corresponding corruption cos cost count count_big counted covar_pop covar_samp cpu_per_call cpu_per_session crc32 create creation critical cross cube cume_dist curdate current current_date current_time current_timestamp current_user cursor curtime customdatum cycle d data database databases datafile datafiles datalength date_add date_cache date_format date_sub dateadd datediff datefromparts datename datepart datetime2fromparts day day_to_second dayname dayofmonth dayofweek dayofyear days db_role_change dbtimezone ddl deallocate declare decode decompose decrement decrypt deduplicate def defa defau defaul default defaults deferred defi defin define degrees delayed delegate delete delete_all delimited demand dense_rank depth dequeue des_decrypt des_encrypt des_key_file desc descr descri describ describe descriptor deterministic diagnostics difference dimension direct_load directory disable disable_all disallow disassociate discardfile disconnect diskgroup distinct distinctrow distribute distributed div do document domain dotnet double downgrade drop dumpfile duplicate duration e each edition editionable editions element ellipsis else elsif elt empty enable enable_all enclosed encode encoding encrypt end end-exec endian enforced engine engines enqueue enterprise entityescaping eomonth error errors escaped evalname evaluate event eventdata events except exception exceptions exchange exclude excluding execu execut execute exempt exists exit exp expire explain export export_set extended extent external external_1 external_2 externally extract f failed failed_login_attempts failover failure far fast feature_set feature_value fetch field fields file file_name_convert filesystem_like_logging final finish first first_value fixed flash_cache flashback floor flush following follows for forall force form forma format found found_rows freelist freelists freepools fresh from from_base64 from_days ftp full function g general generated get get_format get_lock getdate getutcdate global global_name globally go goto grant grants greatest group group_concat group_id grouping grouping_id groups gtid_subtract guarantee guard handler hash hashkeys having hea head headi headin heading heap help hex hierarchy high high_priority hosts hour http i id ident_current ident_incr ident_seed identified identity idle_time if ifnull ignore iif ilike ilm immediate import in include including increment index indexes indexing indextype indicator indices inet6_aton inet6_ntoa inet_aton inet_ntoa infile initial initialized initially initrans inmemory inner innodb input insert install instance instantiable instr interface interleaved intersect into invalidate invisible is is_free_lock is_ipv4 is_ipv4_compat is_not is_not_null is_used_lock isdate isnull isolation iterate java join json json_exists k keep keep_duplicates key keys kill l language large last last_day last_insert_id last_value lax lcase lead leading least leaves left len lenght length less level levels library like like2 like4 likec limit lines link list listagg little ln load load_file lob lobs local localtime localtimestamp locate locator lock locked log log10 log2 logfile logfiles logging logical logical_reads_per_call logoff logon logs long loop low low_priority lower lpad lrtrim ltrim m main make_set makedate maketime managed management manual map mapping mask master master_pos_wait match matched materialized max maxextents maximize maxinstances maxlen maxlogfiles maxloghistory maxlogmembers maxsize maxtrans md5 measures median medium member memcompress memory merge microsecond mid migration min minextents minimum mining minus minute minvalue missing mod mode model modification modify module monitoring month months mount move movement multiset mutex n name name_const names nan national native natural nav nchar nclob nested never new newline next nextval no no_write_to_binlog noarchivelog noaudit nobadfile nocheck nocompress nocopy nocycle nodelay nodiscardfile noentityescaping noguarantee nokeep nologfile nomapping nomaxvalue nominimize nominvalue nomonitoring none noneditionable nonschema noorder nopr nopro noprom nopromp noprompt norely noresetlogs noreverse normal norowdependencies noschemacheck noswitch not nothing notice notrim novalidate now nowait nth_value nullif nulls num numb numbe nvarchar nvarchar2 object ocicoll ocidate ocidatetime ociduration ociinterval ociloblocator ocinumber ociref ocirefcursor ocirowid ocistring ocitype oct octet_length of off offline offset oid oidindex old on online only opaque open operations operator optimal optimize option optionally or oracle oracle_date oradata ord ordaudio orddicom orddoc order ordimage ordinality ordvideo organization orlany orlvary out outer outfile outline output over overflow overriding p package pad parallel parallel_enable parameters parent parse partial partition partitions pascal passing password password_grace_time password_lock_time password_reuse_max password_reuse_time password_verify_function patch path patindex pctincrease pctthreshold pctused pctversion percent percent_rank percentile_cont percentile_disc performance period period_add period_diff permanent physical pi pipe pipelined pivot pluggable plugin policy position post_transaction pow power pragma prebuilt precedes preceding precision prediction prediction_cost prediction_details prediction_probability prediction_set prepare present preserve prior priority private private_sga privileges procedural procedure procedure_analyze processlist profiles project prompt protection public publishingservername purge quarter query quick quiesce quota quotename radians raise rand range rank raw read reads readsize rebuild record records recover recovery recursive recycle redo reduced ref reference referenced references referencing refresh regexp_like register regr_avgx regr_avgy regr_count regr_intercept regr_r2 regr_slope regr_sxx regr_sxy reject rekey relational relative relaylog release release_lock relies_on relocate rely rem remainder rename repair repeat replace replicate replication required reset resetlogs resize resource respect restore restricted result result_cache resumable resume retention return returning returns reuse reverse revoke right rlike role roles rollback rolling rollup round row row_count rowdependencies rowid rownum rows rtrim rules safe salt sample save savepoint sb1 sb2 sb4 scan schema schemacheck scn scope scroll sdo_georaster sdo_topo_geometry search sec_to_time second section securefile security seed segment select self sequence sequential serializable server servererror session session_user sessions_per_user set sets settings sha sha1 sha2 share shared shared_pool short show shrink shutdown si_averagecolor si_colorhistogram si_featurelist si_positionalcolor si_stillimage si_texture siblings sid sign sin size size_t sizes skip slave sleep smalldatetimefromparts smallfile snapshot some soname sort soundex source space sparse spfile split sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_small_result sql_variant_property sqlcode sqldata sqlerror sqlname sqlstate sqrt square standalone standby start starting startup statement static statistics stats_binomial_test stats_crosstab stats_ks_test stats_mode stats_mw_test stats_one_way_anova stats_t_test_ stats_t_test_indep stats_t_test_one stats_t_test_paired stats_wsr_test status std stddev stddev_pop stddev_samp stdev stop storage store stored str str_to_date straight_join strcmp strict string struct stuff style subdate subpartition subpartitions substitutable substr substring subtime subtring_index subtype success sum suspend switch switchoffset switchover sync synchronous synonym sys sys_xmlagg sysasm sysaux sysdate sysdatetimeoffset sysdba sysoper system system_user sysutcdatetime t table tables tablespace tan tdo template temporary terminated tertiary_weights test than then thread through tier ties time time_format time_zone timediff timefromparts timeout timestamp timestampadd timestampdiff timezone_abbr timezone_minute timezone_region to to_base64 to_date to_days to_seconds todatetimeoffset trace tracking transaction transactional translate translation treat trigger trigger_nestlevel triggers trim truncate try_cast try_convert try_parse type ub1 ub2 ub4 ucase unarchived unbounded uncompress under undo unhex unicode uniform uninstall union unique unix_timestamp unknown unlimited unlock unpivot unrecoverable unsafe unsigned until untrusted unusable unused update updated upgrade upped upper upsert url urowid usable usage use use_stored_outlines user user_data user_resources users using utc_date utc_timestamp uuid uuid_short validate validate_password_strength validation valist value values var var_samp varcharc vari varia variab variabl variable variables variance varp varraw varrawc varray verify version versions view virtual visible void wait wallet warning warnings week weekday weekofyear wellformed when whene whenev wheneve whenever where while whitespace with within without work wrapped xdb xml xmlagg xmlattributes xmlcast xmlcolattval xmlelement xmlexists xmlforest xmlindex xmlnamespaces xmlpi xmlquery xmlroot xmlschema xmlserialize xmltable xmltype xor year year_to_month years yearweek",
3 | literal:"true false null",built_in:"array bigint binary bit blob boolean char character date dec decimal float int int8 integer interval number numeric real record serial serial8 smallint text varchar varying void"},c:[{cN:"string",b:"'",e:"'",c:[e.BE,{b:"''"}]},{cN:"string",b:'"',e:'"',c:[e.BE,{b:'""'}]},{cN:"string",b:"`",e:"`",c:[e.BE]},e.CNM,e.CBCM,t]},e.CBCM,t]}}),e});
--------------------------------------------------------------------------------