61 | ...
62 | ```
63 |
64 | 好了,搞定!
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/_app/_posts/2021-01-20-laravel-exception-with-context.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: post
3 | title: 一种 Laravel 异常上下文解决方案
4 | excerpt: 异常时我们通常希望在用户测给一个友好的提示,但默认使用框架的异常处理方案是不 OK 的。
5 | ---
6 |
7 | 最近项目遇到一个情况,我们在遇到用户访问某个信息没有权限的时候,希望提示详细的原因,比如当访问一个团队资源时非成员访问的场景下会提示一个:`您不是 [xxxxxx] 团队的成员,暂时无法查看,可<申请加入>`,同时需要显示打码后的团队名称,以及加入按钮,可是接口方的逻辑是当没有权限时直接 `abort` 了:
8 |
9 | ```php
10 | abort_if(!$user->isMember($resouce->team), 403, '您无权访问该资源');
11 | ```
12 |
13 | 得到的响应结果如下:
14 |
15 | ```json
16 | HTTP/1.0 403 Forbidden
17 |
18 | {
19 | "message": "您无权访问该资源"
20 | }
21 | ```
22 |
23 | 我们不肯能将 message 用 html 来完成前端提示页的展示,这样耦合性太强,违背了前后端分离的原则。我们的目标是返回如下的格式即可解决:
24 |
25 | ```json
26 | HTTP/1.0 403 Forbidden
27 |
28 | {
29 | "message": "您无权访问该资源",
30 | "team": {
31 | "id": "abxT8sioa0Ms",
32 | "name": "CoDesign****"
33 | }
34 | }
35 | ```
36 |
37 | 通过携带上下文的方法传递数据,方便了前端同学自由组合。
38 |
39 | ## 开始改造
40 |
41 | 当然这并不是什么复杂的事情,直接修改原来的 `abort_if` 即可解决:
42 |
43 | ```diff
44 | - abort_if(!$user->isMember($resouce->team), 403, '您无权访问该资源');
45 | + if (!$user->isMember($resouce->team)) {
46 | + return response()->json([
47 | + 'message' => '您无权访问该资源',
48 | + 'team' => [
49 | + 'id' => $resouce->team_id,
50 | + 'name'=> $resouce->team->desensitised_name,
51 | + ]
52 | + ], 403);
53 | + }
54 | ```
55 |
56 | 这样看起来解决了问题,可是试想一下,如果是在闭包里面检测到异常想要退出,上面这种 `return` 式的写法就会比较难搞了,毕竟 `return` 只会终止最近的上下文环境,我们还是希望像 `abort` 一样能终止整个应用的执行,再进行另一番改造。
57 |
58 | ## 优化实现
59 |
60 | 看了 `abort` 源码,我发现它的第一个参数其实支持 `\Symfony\Component\HttpFoundation\Response` 实例,而上面👆我们 `return` 的结果就是它的实例,所以我们只需要改成这样就可以了:
61 |
62 | ```php
63 | if (!$user->isMember($resouce->team)) {
64 | abort(response()->json([
65 | 'message' => '您无权访问该资源',
66 | 'team' => [
67 | 'id' => $resouce->team_id,
68 | 'name'=> $resouce->team->desensitised_name,
69 | ]
70 | ], 403));
71 | }
72 | ```
73 |
74 | 新的问题来了,如果需要复用的时候还是比较尴尬,这段代码将会重复出现在各种有此权限判断的地方,这并不是我们想要的。
75 |
76 | ## 逻辑复用
77 |
78 | 为了达到逻辑复用,我认证看了 `\App\Exceptions\Handler` 的实现,发现父类的 `render` 方法还有这么一个设计:
79 |
80 | ```php
81 | public function render($request, Throwable $e)
82 | {
83 | if (method_exists($e, 'render') && $response = $e->render($request)) {
84 | return Router::toResponse($request, $response);
85 | } elseif ($e instanceof Responsable) {
86 | return $e->toResponse($request);
87 | }
88 |
89 | //...
90 | ```
91 |
92 | 所以,我们可以将这个逻辑抽离为一个独立的异常类,实现 render 方法即可:
93 |
94 | ```bash
95 | $ ./artisan make:exception NotTeamMemberException
96 | ```
97 |
98 | 代码如下:
99 |
100 | ```php
101 | team = $team;
114 | parent::__construct($message, 403);
115 | }
116 |
117 | public function render()
118 | {
119 | return response()->json(
120 | [
121 | 'message' => !empty($this->message) ? $this->message : '您无权访问该资源',
122 | 'team' => [
123 | 'id' => $this->team->id,
124 | 'name' => $this->team->desensitised_name,
125 | ],
126 | ],
127 | 403
128 | );
129 | }
130 | }
131 |
132 | ```
133 |
134 | 这样一来,我们的逻辑就变成了:
135 |
136 | ```php
137 | if (!$user->isMember($resouce->team)) {
138 | throw new NotTeamMemberException($resouce->team, '您无权访问该资源');
139 | }
140 | ```
141 |
142 | 当然也可以简化为:
143 |
144 | ```php
145 | \throw_if(!$user->isMember($resouce->team), NotTeamMemberException::class, $resouce->team, '您无权访问该资源');
146 | ```
147 |
148 | 问题到这里总算以一个比较完美的方式解决了,如果你有更好的方案欢迎评论探讨。
149 |
150 |
--------------------------------------------------------------------------------
/_app/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/overtrue/blog/c8563b36bbfb520a0b32c2260a1de38f70b80b40/_app/apple-touch-icon.png
--------------------------------------------------------------------------------
/_app/assets/_js/user.js:
--------------------------------------------------------------------------------
1 | // Custom user scripts
2 |
--------------------------------------------------------------------------------
/_app/assets/_less/user.less:
--------------------------------------------------------------------------------
1 | //
2 | // User styles
3 | // --------------------------------------------------
4 | // All other vaiables in `variables.less` can be overwritten below
5 | // NOTE: the following user styles are designed for default theme Curtana
6 |
7 | //
8 | // EXAMPLE: alternative title size
9 | //
10 | // By default all heading levels have the same font size, but many people may
11 | // not like this, so here's an alternative font size stack:
12 | //
13 | // .content {
14 | // h2 { font-size: 108%; opacity: .8; }
15 | // h3 { font-size: 96%; opacity: .7; }
16 | // h4 { font-size: 84%; opacity: .6; }
17 | // h5 { font-size: 72%; opacity: .5; }
18 | // h6 { font-size: 60%; opacity: .4; }
19 | // }
20 |
21 | //
22 | // EXAMPLE: alternative post title color
23 | // This is useful when you're using the custom color scheme.
24 | //
25 | // .content { .list h2 a, header h1, header h1 a { color: @link-color; } }
26 | // .external span { background: @link-color; }
27 |
28 | //
29 | // EXAMPLE: custom header with background image
30 | //
31 | // .content header {
32 | // color: @background-color;
33 | // background: @link-color url('/assets/img/title-background-example.jpg') center center / cover;
34 | // }
35 |
36 | //
37 | // EXAMPLE: Custom typography for Chinese Traditional
38 | //
39 | // @fontstack-sans-serif: "Helvetica Neue", "Hiragino Sans GB", Arial, sans-serif;
40 | // @fontstack-serif: Georgia, "Hiragino Mincho ProN", serif;
41 |
42 | //
43 | // EXAMPLE: amsf.github.io custom appearance
44 | //
45 | @link-color: #5477f8;
46 |
47 | body {
48 | position: relative;
49 | }
50 |
51 | .content {
52 | .list {
53 | h1 {
54 | font-size: 120%;
55 | margin-top: 5vh;
56 | margin-bottom: 5vh;
57 | }
58 | }
59 | .sub-title {
60 | font-size: 108%;
61 | opacity: 0.8;
62 | }
63 | }
64 |
65 | .content a:hover {
66 | text-decoration: none;
67 | border-bottom: solid;
68 | }
69 |
70 | img.emoji {
71 | margin: 0 0;
72 | display: inline-block;
73 | width: auto;
74 | }
75 |
76 | h2.meta {
77 | margin-top: 0;
78 | }
79 |
80 | //
81 | // Gitalk
82 | //
83 | #page-comments {
84 | margin-top: 60px;
85 | margin-left: -24vw;
86 | width: 100vw;
87 | padding: 40px 24vw;
88 | background: #fafafa;
89 |
90 | a:hover {
91 | border-bottom: none !important;
92 | }
93 |
94 | .gt-header {
95 | position: relative;
96 | display: flex;
97 | background: #fff;
98 | padding: 20px;
99 | border: 1px solid rgba(0, 0, 0, 0.09);
100 | -webkit-border-radius: 3px;
101 | border-radius: 3px;
102 | margin-top: 5px;
103 | box-shadow: 0 1px 4px rgba(0, 0, 0, 0.03);
104 | }
105 | .gt-container {
106 | .gt-header-controls-tip {
107 | color: #000;
108 | opacity: 0.5;
109 | }
110 | .gt-svg svg {
111 | fill: #000;
112 | }
113 | .gt-comment-content {
114 | background-color: transparent !important;
115 |
116 | blockquote > p {
117 | padding-left: 0;
118 | border-left: none;
119 | }
120 | }
121 |
122 | .gt-comment-content:hover {
123 | box-shadow: none;
124 | }
125 |
126 | .gt-comment {
127 | margin-top: 20px;
128 | padding: 20px;
129 | background: #fff;
130 | -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04);
131 | box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04);
132 | border: 1px solid rgba(0, 0, 0, 0.09);
133 | -webkit-border-radius: 3px;
134 | border-radius: 3px;
135 | }
136 | .gt-header-textarea {
137 | background-color: transparent;
138 | border: none;
139 | border-radius: 0;
140 | }
141 | .gt-link {
142 | border: none;
143 | }
144 | .gt-btn {
145 | background: transparent;
146 | color: #555;
147 | border: 1px solid #555;
148 | border-radius: 3px;
149 |
150 | &:hover {
151 | background-color: #fafafa;
152 | }
153 | }
154 | .gt-avatar {
155 | border-radius: 100%;
156 | overflow: hidden;
157 | img {
158 | margin: 0 auto;
159 | }
160 | }
161 | }
162 | }
163 |
164 | @media (max-width: 1080px) {
165 | #page-comments {
166 | margin-top: 30px;
167 | margin-left: -8vw;
168 | padding: 20px 8vw;
169 | }
170 | }
171 |
172 | @media (max-width: 640px) {
173 | #page-comments {
174 | margin-top: 30px;
175 | margin-left: -4vw;
176 | padding: 20px 4vw;
177 | }
178 | }
179 |
--------------------------------------------------------------------------------
/_app/assets/img/heading-background-example.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/overtrue/blog/c8563b36bbfb520a0b32c2260a1de38f70b80b40/_app/assets/img/heading-background-example.jpg
--------------------------------------------------------------------------------
/_app/assets/svg/amsf.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/_app/assets/svg/heading-image-example.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/_app/assets/themes/curtana/_js/app.js:
--------------------------------------------------------------------------------
1 | // Detect user platform
2 | if (navigator.platform.match(/(Mac|iPhone|iPod|iPad)/i)) {
3 | document.body.classList.add('js-system--apple');
4 | }
5 |
--------------------------------------------------------------------------------
/_app/assets/themes/curtana/_js/lightense.min.js:
--------------------------------------------------------------------------------
1 | /*! lightense-images v1.0.5 | © Tunghsiao Liu | MIT */
2 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.Lightense=t():e.Lightense=t()}(this,function(){return function(e){function t(r){if(n[r])return n[r].exports;var i=n[r]={i:r,l:!1,exports:{}};return e[r].call(i.exports,i,i.exports,t),i.l=!0,i.exports}var n={};return t.m=e,t.c=n,t.i=function(e){return e},t.d=function(e,n,r){t.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:r})},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="",t(t.s=0)}([function(e,t,n){"use strict";var r=Object.assign||function(e){for(var t=1;t
>16&255,t>>8&255,255&t].join(", ")+", 1)"):a(e)?e.replace(")",", 1)"):s(e)?e:(console.log("Invalid color: "+e),L.background)}function l(e){var t=c(e),n=.7,r=/([\d\.]+)\)$/g,i=r.exec(t)[1];return t.replace(r,i*n+")")}function d(e,t){var n=E.head||E.getElementsByTagName("head")[0];E.getElementById(e)&&E.getElementById(e).remove();var r=E.createElement("style");r.id=e,r.styleSheet?r.styleSheet.cssText=t:r.appendChild(E.createTextNode(t)),n.appendChild(r)}function u(){var e="\n.lightense-backdrop {\n box-sizing: border-box;\n width: 100%;\n height: 100%;\n position: fixed;\n top: 0;\n left: 0;\n overflow: hidden;\n z-index: "+(B.zIndex-1)+";\n padding: 0;\n margin: 0;\n transition: opacity "+B.time+"ms ease;\n cursor: zoom-out;\n opacity: 0;\n background-color: "+B.background+";\n visibility: hidden;\n}\n\n@supports (-webkit-backdrop-filter: blur(30px)) {\n .lightense-backdrop {\n background-color: "+l(B.background)+";\n -webkit-backdrop-filter: blur(30px);\n backdrop-filter: blur(30px);\n }\n}\n\n.lightense-wrap {\n position: relative;\n transition: transform "+B.time+"ms "+B.cubicBezier+";\n z-index: "+B.zIndex+";\n pointer-events: none;\n}\n\n.lightense-target {\n cursor: zoom-in;\n transition: transform "+B.time+"ms "+B.cubicBezier+";\n pointer-events: auto;\n}\n\n.lightense-open {\n cursor: zoom-out;\n}\n\n.lightense-transitioning {\n pointer-events: none;\n}";d("lightense-images-css",e)}function g(){B.container=E.createElement("div"),B.container.className="lightense-backdrop",E.body.appendChild(B.container)}function p(e){var t=e.width,n=e.height,r=z.pageYOffset||E.documentElement.scrollTop||0,i=z.pageXOffset||E.documentElement.scrollLeft||0,o=B.target.getBoundingClientRect(),a=t/o.width,s=z.innerWidth||E.documentElement.clientWidth||0,c=z.innerHeight||E.documentElement.clientHeight||0,l=B.target.getAttribute("data-lightense-padding")||B.target.getAttribute("data-padding")||B.padding,d=s>l?s-l:s-L.padding,u=c>l?c-l:c-L.padding,g=t/n,p=d/u;t=B.offset&&b()}function h(e){if(B.target=e,B.target.classList.contains("lightense-open"))return b();B.scrollY=z.scrollY;var t=new Image;t.onload=function(){p(this),f(),y()},t.src=B.target.src}function y(){z.addEventListener("keyup",k,!1),z.addEventListener("scroll",m,!1),B.container.addEventListener("click",b,!1)}function v(){z.removeEventListener("keyup",k,!1),z.removeEventListener("scroll",m,!1),B.container.removeEventListener("click",b,!1)}function k(e){e.preventDefault(),27===e.keyCode&&b()}function x(n){var i=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};w=e(n),B=r({},L,i),u(),g(),t(w)}var w,z=window,E=document,L={time:300,padding:40,offset:40,keyboard:!0,cubicBezier:"cubic-bezier(.2, 0, .1, 1)",background:"rgba(255, 255, 255, .98)",zIndex:2147483647},B={};return x},a=o();e.exports=a}])});
3 |
--------------------------------------------------------------------------------
/_app/assets/themes/curtana/_less/.csscomb.json:
--------------------------------------------------------------------------------
1 | {
2 | "exclude": [
3 | ".git/**",
4 | "node_modules/**"
5 | ],
6 | "always-semicolon": true,
7 | "block-indent": 2,
8 | "colon-space": true,
9 | "color-case": "lower",
10 | "color-shorthand": true,
11 | "combinator-space": true,
12 | "element-case": "lower",
13 | "eof-newline": true,
14 | "leading-zero": false,
15 | "quotes": "single",
16 | "remove-empty-rulesets": true,
17 | "rule-indent": 2,
18 | "stick-brace": true,
19 | "strip-spaces": true,
20 | "unitless-zero": true,
21 | "vendor-prefix-align": true,
22 | "sort-order": [
23 | [
24 | "font",
25 | "font-family",
26 | "font-size",
27 | "font-weight",
28 | "font-style",
29 | "font-variant",
30 | "font-size-adjust",
31 | "font-stretch",
32 | "font-effect",
33 | "font-emphasize",
34 | "font-emphasize-position",
35 | "font-emphasize-style",
36 | "font-smooth",
37 | "line-height"
38 | ],
39 | [
40 | "position",
41 | "z-index",
42 | "top",
43 | "right",
44 | "bottom",
45 | "left"
46 | ],
47 | [
48 | "display",
49 | "visibility",
50 | "float",
51 | "clear",
52 | "overflow",
53 | "overflow-x",
54 | "overflow-y",
55 | "-ms-overflow-x",
56 | "-ms-overflow-y",
57 | "clip",
58 | "zoom",
59 | "flex-direction",
60 | "flex-order",
61 | "flex-pack",
62 | "flex-align"
63 | ],
64 | [
65 | "-webkit-box-sizing",
66 | "-moz-box-sizing",
67 | "box-sizing",
68 | "width",
69 | "min-width",
70 | "max-width",
71 | "height",
72 | "min-height",
73 | "max-height",
74 | "margin",
75 | "margin-top",
76 | "margin-right",
77 | "margin-bottom",
78 | "margin-left",
79 | "padding",
80 | "padding-top",
81 | "padding-right",
82 | "padding-bottom",
83 | "padding-left"
84 | ],
85 | [
86 | "table-layout",
87 | "empty-cells",
88 | "caption-side",
89 | "border-spacing",
90 | "border-collapse",
91 | "list-style",
92 | "list-style-position",
93 | "list-style-type",
94 | "list-style-image"
95 | ],
96 | [
97 | "content",
98 | "quotes",
99 | "counter-reset",
100 | "counter-increment",
101 | "resize",
102 | "cursor",
103 | "-webkit-user-select",
104 | "-moz-user-select",
105 | "-ms-user-select",
106 | "user-select",
107 | "nav-index",
108 | "nav-up",
109 | "nav-right",
110 | "nav-down",
111 | "nav-left",
112 | "-webkit-transition",
113 | "-moz-transition",
114 | "-ms-transition",
115 | "-o-transition",
116 | "transition",
117 | "-webkit-transition-delay",
118 | "-moz-transition-delay",
119 | "-ms-transition-delay",
120 | "-o-transition-delay",
121 | "transition-delay",
122 | "-webkit-transition-timing-function",
123 | "-moz-transition-timing-function",
124 | "-ms-transition-timing-function",
125 | "-o-transition-timing-function",
126 | "transition-timing-function",
127 | "-webkit-transition-duration",
128 | "-moz-transition-duration",
129 | "-ms-transition-duration",
130 | "-o-transition-duration",
131 | "transition-duration",
132 | "-webkit-transition-property",
133 | "-moz-transition-property",
134 | "-ms-transition-property",
135 | "-o-transition-property",
136 | "transition-property",
137 | "-webkit-transform",
138 | "-moz-transform",
139 | "-ms-transform",
140 | "-o-transform",
141 | "transform",
142 | "-webkit-transform-origin",
143 | "-moz-transform-origin",
144 | "-ms-transform-origin",
145 | "-o-transform-origin",
146 | "transform-origin",
147 | "-webkit-animation",
148 | "-moz-animation",
149 | "-ms-animation",
150 | "-o-animation",
151 | "animation",
152 | "-webkit-animation-name",
153 | "-moz-animation-name",
154 | "-ms-animation-name",
155 | "-o-animation-name",
156 | "animation-name",
157 | "-webkit-animation-duration",
158 | "-moz-animation-duration",
159 | "-ms-animation-duration",
160 | "-o-animation-duration",
161 | "animation-duration",
162 | "-webkit-animation-play-state",
163 | "-moz-animation-play-state",
164 | "-ms-animation-play-state",
165 | "-o-animation-play-state",
166 | "animation-play-state",
167 | "-webkit-animation-timing-function",
168 | "-moz-animation-timing-function",
169 | "-ms-animation-timing-function",
170 | "-o-animation-timing-function",
171 | "animation-timing-function",
172 | "-webkit-animation-delay",
173 | "-moz-animation-delay",
174 | "-ms-animation-delay",
175 | "-o-animation-delay",
176 | "animation-delay",
177 | "-webkit-animation-iteration-count",
178 | "-moz-animation-iteration-count",
179 | "-ms-animation-iteration-count",
180 | "-o-animation-iteration-count",
181 | "animation-iteration-count",
182 | "-webkit-animation-direction",
183 | "-moz-animation-direction",
184 | "-ms-animation-direction",
185 | "-o-animation-direction",
186 | "animation-direction",
187 | "text-align",
188 | "-webkit-text-align-last",
189 | "-moz-text-align-last",
190 | "-ms-text-align-last",
191 | "text-align-last",
192 | "vertical-align",
193 | "white-space",
194 | "text-decoration",
195 | "text-emphasis",
196 | "text-emphasis-color",
197 | "text-emphasis-style",
198 | "text-emphasis-position",
199 | "text-indent",
200 | "-ms-text-justify",
201 | "text-justify",
202 | "letter-spacing",
203 | "word-spacing",
204 | "-ms-writing-mode",
205 | "text-outline",
206 | "text-transform",
207 | "text-wrap",
208 | "text-overflow",
209 | "-ms-text-overflow",
210 | "text-overflow-ellipsis",
211 | "text-overflow-mode",
212 | "-ms-word-wrap",
213 | "word-wrap",
214 | "word-break",
215 | "-ms-word-break",
216 | "-moz-tab-size",
217 | "-o-tab-size",
218 | "tab-size",
219 | "-webkit-hyphens",
220 | "-moz-hyphens",
221 | "hyphens",
222 | "pointer-events"
223 | ],
224 | [
225 | "opacity",
226 | "filter:progid:DXImageTransform.Microsoft.Alpha(Opacity",
227 | "-ms-filter:\\'progid:DXImageTransform.Microsoft.Alpha",
228 | "-ms-interpolation-mode",
229 | "color",
230 | "border",
231 | "border-width",
232 | "border-style",
233 | "border-color",
234 | "border-top",
235 | "border-top-width",
236 | "border-top-style",
237 | "border-top-color",
238 | "border-right",
239 | "border-right-width",
240 | "border-right-style",
241 | "border-right-color",
242 | "border-bottom",
243 | "border-bottom-width",
244 | "border-bottom-style",
245 | "border-bottom-color",
246 | "border-left",
247 | "border-left-width",
248 | "border-left-style",
249 | "border-left-color",
250 | "-webkit-border-radius",
251 | "-moz-border-radius",
252 | "border-radius",
253 | "-webkit-border-top-left-radius",
254 | "-moz-border-radius-topleft",
255 | "border-top-left-radius",
256 | "-webkit-border-top-right-radius",
257 | "-moz-border-radius-topright",
258 | "border-top-right-radius",
259 | "-webkit-border-bottom-right-radius",
260 | "-moz-border-radius-bottomright",
261 | "border-bottom-right-radius",
262 | "-webkit-border-bottom-left-radius",
263 | "-moz-border-radius-bottomleft",
264 | "border-bottom-left-radius",
265 | "-webkit-border-image",
266 | "-moz-border-image",
267 | "-o-border-image",
268 | "border-image",
269 | "-webkit-border-image-source",
270 | "-moz-border-image-source",
271 | "-o-border-image-source",
272 | "border-image-source",
273 | "-webkit-border-image-slice",
274 | "-moz-border-image-slice",
275 | "-o-border-image-slice",
276 | "border-image-slice",
277 | "-webkit-border-image-width",
278 | "-moz-border-image-width",
279 | "-o-border-image-width",
280 | "border-image-width",
281 | "-webkit-border-image-outset",
282 | "-moz-border-image-outset",
283 | "-o-border-image-outset",
284 | "border-image-outset",
285 | "-webkit-border-image-repeat",
286 | "-moz-border-image-repeat",
287 | "-o-border-image-repeat",
288 | "border-image-repeat",
289 | "outline",
290 | "outline-width",
291 | "outline-style",
292 | "outline-color",
293 | "outline-offset",
294 | "background",
295 | "filter:progid:DXImageTransform.Microsoft.AlphaImageLoader",
296 | "background-color",
297 | "background-image",
298 | "background-repeat",
299 | "background-attachment",
300 | "background-position",
301 | "background-position-x",
302 | "-ms-background-position-x",
303 | "background-position-y",
304 | "-ms-background-position-y",
305 | "-webkit-background-clip",
306 | "-moz-background-clip",
307 | "background-clip",
308 | "background-origin",
309 | "-webkit-background-size",
310 | "-moz-background-size",
311 | "-o-background-size",
312 | "background-size",
313 | "box-decoration-break",
314 | "-webkit-box-shadow",
315 | "-moz-box-shadow",
316 | "box-shadow",
317 | "filter:progid:DXImageTransform.Microsoft.gradient",
318 | "-ms-filter:\\'progid:DXImageTransform.Microsoft.gradient",
319 | "text-shadow"
320 | ]
321 | ]
322 | }
323 |
--------------------------------------------------------------------------------
/_app/assets/themes/curtana/_less/app.less:
--------------------------------------------------------------------------------
1 | //
2 | // Curtana for Almace Scaffolding | © Tunghsiao Liu | MIT
3 | //
4 |
5 | // Core components
6 | @import "variables";
7 | @import "mixins";
8 | @import "reset";
9 | @import "common";
10 | @import "plugins";
11 | @import "print";
12 |
13 | // Extra components
14 | @import "components/randomized";
15 |
16 | // User custom styles
17 | @import "../../../_less/user";
18 |
--------------------------------------------------------------------------------
/_app/assets/themes/curtana/_less/components/randomized.less:
--------------------------------------------------------------------------------
1 | //
2 | // Randomized | © Tunghsiao Liu | MIT
3 | //
4 |
5 | // Variables for standalone version
6 | // @link-color: #a212d1;
7 | // @code-color: #00cc80;
8 |
9 | // Calculate code background color based on the lightness of link color
10 | .calc-bg (@v) when (lightness(@v) >= 80%) {
11 | @calc-bg: mix(#fff, @v, 90%);
12 | }
13 |
14 | .calc-bg (@v) when (lightness(@v) < 80%) {
15 | @calc-bg: mix(#000, @v, 70%);
16 | }
17 |
18 | // Reset background
19 | //
20 | // `.highlight > pre` - Jekyll liquid code blocks
21 | // `.highlighter-rouge pre.highlight` - Rouge GFM code blocks
22 | //
23 | // Ref: https://github.com/jekyll/jekyll/pull/4053
24 | @highlighter-tint: @link-color;
25 | .highlight > pre,
26 | .highlighter-rouge pre.highlight {
27 | .calc-bg(@highlighter-tint);
28 | background: fade(@calc-bg, 2%);
29 | }
30 |
31 | // General highlights
32 | //
33 | // `.highlight code` - Rouge GFM and Jekyll liquid code blocks
34 | // `.highlighter-rouge code` - Rouge inline code
35 | .highlight,
36 | .highlighter-rouge {
37 |
38 | // Reset default `code`
39 | code { color: desaturate(@link-color, 95%); } // Normal code
40 |
41 | // Special background for syntax errors
42 | .err { background-color: fade(saturate(@highlighter-tint, 10%), 10%) } // Error
43 |
44 | .c { font-style: italic } // Comment
45 | .cm { font-style: italic } // Comment.Multiline
46 | .cp { font-weight: bold } // Comment.Preproc
47 | .c1 { font-style: italic } // Comment.Single
48 | .cs { font-weight: bold; font-style: italic } // Comment.Special
49 |
50 | .nc { font-weight: bold } // Name.Class
51 | .ne { font-weight: bold } // Name.Exception
52 | .nf { font-weight: bold } // Name.Function
53 |
54 | .o { font-weight: bold } // Operator
55 | .ow { font-weight: bold } // Operator.Word
56 |
57 | .gs { font-weight: bold } // Generic.Strong
58 | .ge { font-style: italic } // Generic.Emph
59 |
60 | .k { font-weight: bold } // Keyword
61 | .kt { font-weight: bold } // Keyword.Type
62 | .kc { font-weight: bold } // Keyword.Constant
63 | .kd { font-weight: bold } // Keyword.Declaration
64 | .kp { font-weight: bold } // Keyword.Pseudo
65 | .kr { font-weight: bold } // Keyword.Reserved
66 |
67 | @token-main:
68 | err // Error
69 | x // Other
70 |
71 | n // Name
72 | na // Name.Attribute
73 | nb // Name.Builtin
74 | bp // Name.Builtin.Pseudo
75 | nc // Name.Class
76 | no // Name.Constant
77 | nd // Name.Decorator
78 | ni // Name.Entity
79 | ne // Name.Exception
80 | nf // Name.Function
81 | nl // Name.Label
82 | nn // Name.Namespace
83 | nx // Name.Other
84 | nt // Name.Tag
85 | nv // Name.Variable
86 | vc // Name.Variable.Class
87 | vg // Name.Variable.Global
88 | vi // Name.Variable.Instance
89 |
90 | g // Generic
91 | gd // Generic.Deleted
92 | ge // Generic.Emph
93 | gr // Generic.Error
94 | gh // Generic.Heading
95 | gi // Generic.Inserted
96 | go // Generic.Output
97 | gp // Generic.Prompt
98 | gs // Generic.Strong
99 | gu // Generic.Subheading
100 | gt // Generic.Traceback
101 | gl // Generic.Lineno
102 |
103 | k // Keyword
104 | kc // Keyword.Constant
105 | kd // Keyword.Declaration
106 | kn // Keyword.Namespace
107 | kp // Keyword.Pseudo
108 | kr // Keyword.Reserved
109 | kt // Keyword.Type
110 | kv // Keyword.Variable
111 |
112 | w // Text.Whitespace
113 |
114 | l // Literal
115 |
116 | ld // Literal.Date
117 |
118 | s // Literal.String
119 | sb // Literal.String.Backtick
120 | sc // Literal.String.Char
121 | sd // Literal.String.Doc
122 | s2 // Literal.String.Double
123 | se // Literal.String.Escape
124 | sh // Literal.String.Heredoc
125 | si // Literal.String.Interpol
126 | sx // Literal.String.Other
127 | sr // Literal.String.Regex
128 | s1 // Literal.String.Single
129 | ss // Literal.String.Symbol
130 |
131 | m // Literal.Number
132 | mf // Literal.Number.Float
133 | mh // Literal.Number.Hex
134 | mi // Literal.Number.Integer
135 | il // Literal.Number.Integer.Long
136 | mo // Literal.Number.Oct
137 | mb // Literal.Number.Bin
138 | mx // Literal.Number.Other
139 |
140 | o // Operator
141 | ow // Operator.Word
142 |
143 | p // Punctuation
144 | pi // Punctuation.Indicator
145 | ;
146 |
147 | // Generate main highlights
148 | // Ref: https://github.com/less/less.js/issues/2071
149 | .loop-main (@i) when (@i < (length(@token-main) + 1)) {
150 | @token: extract(@token-main, @i);
151 |
152 | .@{token} {
153 | color: mix(spin(@code-color, (@i * 360 / length(@token-main))), @link-color, 80%);
154 | }
155 | .loop-main((@i + 1));
156 | }
157 | .loop-main(1);
158 |
159 | @token-comments:
160 | c // Comment
161 | cd // Comment.Multiline
162 | cm // Comment.Multiline
163 | cp // Comment.Preproc
164 | c1 // Comment.Single
165 | cs // Comment.Special
166 | ;
167 |
168 | // Generate highlight for comments
169 | .loop-comments (@i) when (@i < (length(@token-comments) + 1)) {
170 | @token: extract(@token-comments, @i);
171 |
172 | .@{token} {
173 | color: mix(desaturate(spin(@code-color, (@i * 360 / length(@token-comments))), 70%), @link-color, 90%);
174 | opacity: .6;
175 | }
176 | .loop-comments((@i + 1));
177 | }
178 | .loop-comments(1);
179 |
180 | // Reset code blocks appearance with line numbers
181 | table {
182 |
183 | &,
184 | th,
185 | td,
186 | td pre {
187 | padding: 0;
188 | margin: 0;
189 | border: none;
190 | background: transparent;
191 | font-size: 100%;
192 | }
193 |
194 | // Rouge generated codeblocks with `lineno` will nest `pre` inside an
195 | // outter `pre`, this could help prevent "double" scroller issue on some
196 | // platforms
197 | pre {
198 | overflow-x: visible;
199 | }
200 |
201 | .gutter {
202 |
203 | // Reset theme-specific table styles
204 | &:first-child,
205 | &:last-child {
206 | padding: 0 !important;
207 | }
208 |
209 | .lineno {
210 | color: desaturate(@link-color, 95%);
211 | opacity: .5;
212 | user-select: none;
213 | }
214 | }
215 |
216 | .code {
217 | padding-left: 1em;
218 | }
219 | }
220 | }
221 |
--------------------------------------------------------------------------------
/_app/assets/themes/curtana/_less/mixins.less:
--------------------------------------------------------------------------------
1 | //
2 | // Mixins
3 | // --------------------------------------------------
4 |
5 | // http://nicolasgallagher.com/micro-clearfix-hack/
6 | .cf {
7 |
8 | &:before, &:after {
9 | display: table;
10 | content: "";
11 | }
12 |
13 | &:after {
14 | clear: both;
15 | }
16 | }
17 |
18 | .heading() {
19 | font-size: 360%;
20 | font-weight: bold;
21 | letter-spacing: @heading-letter-spacing;
22 | }
23 |
24 | .sub-heading() {
25 | font-size: 72%;
26 | font-weight: normal;
27 | opacity: .5;
28 | }
29 |
30 | .padding-offset(@padding-left, @padding-right: @padding-left) {
31 | padding-left: @padding-left;
32 | padding-right: @padding-right;
33 | }
34 |
35 | .margin-offset(@margin-left, @margin-right: @margin-left) {
36 | margin-left: @margin-left;
37 | margin-right: @margin-right;
38 | }
39 |
40 | .spliter() {
41 | @size: 3px;
42 | @color: @text-color;
43 |
44 | &::before {
45 | display: block;
46 | content: '';
47 | width: @size;
48 | height: @size;
49 | margin: 2em auto;
50 | border-radius: 50%;
51 | background: @color;
52 | box-shadow: (@size * 8) 0 0 0 @color, (@size * -8) 0 0 0 @color;
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/_app/assets/themes/curtana/_less/plugins.less:
--------------------------------------------------------------------------------
1 | //
2 | // Plugins
3 | // --------------------------------------------------
4 |
5 | // GitHub Gist
6 | .gist {
7 | font-size: 80% !important;
8 |
9 | code, pre {
10 | padding: 0;
11 | color: inherit;
12 | background-color: transparent;
13 | }
14 |
15 | .gist-data {
16 | font-family: @fontstack-monospace !important;
17 |
18 | .file-data {
19 | font-size: 100% !important;
20 |
21 | > table {
22 | margin-bottom: 0 !important;
23 | }
24 | }
25 | }
26 |
27 | // Monospace fonts look bigger than normal fonts, so make it smaller.
28 | .highlight {
29 | font-size: 90% !important;
30 | }
31 |
32 | .markdown-body {
33 | font-size: 100% !important;
34 | padding: 1em !important;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/_app/assets/themes/curtana/_less/print.less:
--------------------------------------------------------------------------------
1 | //
2 | // Print
3 | // --------------------------------------------------
4 |
5 | // Style fixes for priting
6 |
7 | @media print {
8 |
9 | *,
10 | *:before,
11 | *:after {
12 | background: transparent !important;
13 | color: #000 !important; // Black prints faster: h5bp.com/s
14 | box-shadow: none !important;
15 | text-shadow: none !important;
16 | }
17 |
18 | body {
19 | padding: 10mm !important;
20 | margin: 0 !important;
21 | }
22 |
23 | a,
24 | a:visited {
25 | text-decoration: none;
26 | }
27 |
28 | .content header {
29 | min-height: 0;
30 | padding-bottom: 0;
31 | }
32 |
33 | .content .post-content,
34 | .content .page-content {
35 | max-width: 100% !important;
36 |
37 | a[href]:after {
38 | content: " (" attr(href) ")";
39 | font-weight: normal;
40 | }
41 |
42 | // Don't show links that are fragment identifiers,
43 | // or use the `javascript:` pseudo protocol
44 | a[href^="#"]:after,
45 | a[href^="javascript:"]:after {
46 | content: "";
47 | }
48 |
49 | abbr[title]:after {
50 | content: " (" attr(title) ")";
51 | font-weight: normal;
52 | }
53 | }
54 |
55 | pre,
56 | blockquote {
57 | page-break-inside: avoid;
58 | }
59 |
60 | thead {
61 | display: table-header-group;
62 | }
63 |
64 | tr,
65 | img {
66 | page-break-inside: avoid;
67 | }
68 |
69 | img {
70 | max-width: 100% !important;
71 | }
72 |
73 | p,
74 | h2,
75 | h3,
76 | h4,
77 | h5,
78 | h6 {
79 | orphans: 3;
80 | widows: 3;
81 | }
82 |
83 | h2,
84 | h3 {
85 | page-break-after: avoid;
86 | }
87 |
88 | .navigation,
89 | .footer {
90 | display: none;
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/_app/assets/themes/curtana/_less/reset.less:
--------------------------------------------------------------------------------
1 | //
2 | // Reset
3 | // --------------------------------------------------
4 |
5 | *,
6 | *:before,
7 | *:after {
8 | padding: 0;
9 | margin: 0;
10 | box-sizing: border-box;
11 | }
12 |
13 | html {
14 | font-size: 100%;
15 | text-size-adjust: none;
16 | text-rendering: optimizelegibility;
17 | image-rendering: optimizequality;
18 | -webkit-font-smoothing: antialiased;
19 | -moz-osx-font-smoothing: grayscale;
20 | // TODO: You have to speicify background color for `html` in order to
21 | // make `-webkit-filter` work
22 | // Link: https://bugs.chromium.org/p/chromium/issues/detail?id=591015
23 | // Date: Mar 1, 2016, 5:41 PM
24 | background: @background-color;
25 | }
26 |
27 | body {
28 | padding: 0 @space-lg;
29 | margin: 0 auto 0;
30 | font-family: @fontstack-default;
31 | font-size: @font-size;
32 | line-height: @line-height;
33 | hanging-punctuation: first allow-end;
34 | color: @text-color;
35 | background: @background-color;
36 | transition: all .2s ease;
37 |
38 | @media (max-width: @breakpoint-md) {
39 | .padding-offset(@space-md);
40 | font-size: (@font-size * 1.4);
41 | }
42 |
43 | @media (max-width: @breakpoint-sm) {
44 | .padding-offset(@space-sm);
45 | font-size: (@font-size * 2.4);
46 | }
47 |
48 | @media (max-width: @breakpoint-xs) {
49 | font-size: (@font-size * 3.2);
50 | }
51 | }
52 |
53 | // Reset fonts for relevant elements
54 | input,
55 | button,
56 | select,
57 | textarea {
58 | color: inherit;
59 | font-family: inherit;
60 | font-size: inherit;
61 | line-height: inherit;
62 | }
63 |
64 | // Reset unusual Firefox-on-Android default style.
65 | // See https://github.com/necolas/normalize.css/issues/214
66 | button,
67 | input,
68 | select[multiple],
69 | textarea {
70 | background-image: none;
71 | }
72 |
73 | // Reset common elements
74 | a, button {
75 | color: @text-color;
76 | text-decoration: none;
77 | transition: color .5s ease, border-color .5s ease, background .5s ease, opacity 1.5s ease;
78 |
79 | &:hover,
80 | &:focus {
81 | transition: color .1s ease, border-color .1s ease, background .1s ease, opacity .1s ease;
82 | text-decoration: underline;
83 | text-decoration-skip: ink;
84 | }
85 |
86 | &[disabled] {
87 | opacity: .4;
88 | cursor: not-allowed;
89 | }
90 | }
91 |
92 | button {
93 | appearance: none;
94 | cursor: pointer;
95 | background: none;
96 | border: none;
97 | outline: none;
98 | }
99 |
100 | img, hr {
101 | border: 0;
102 | }
103 |
104 | del {
105 | text-decoration: line-through;
106 | }
107 |
108 | ::placeholder {
109 | color: @text-color;
110 | opacity: .2;
111 |
112 | :focus& {
113 | color: @link-color;
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/_app/assets/themes/curtana/_less/variables.less:
--------------------------------------------------------------------------------
1 | //
2 | // Variables
3 | // --------------------------------------------------
4 |
5 | // Typography
6 | @fontstack-prefix: -apple-system, BlinkMacSystemFont;
7 | @fontstack-sans-serif: "Helvetica Neue", Arial, sans-serif;
8 | @fontstack-serif: Georgia, serif;
9 | @fontstack-monospace: Menlo, Consolas, monospace;
10 | @fontstack-default: @fontstack-prefix, @fontstack-sans-serif;
11 | @font-size: 1.6vw;
12 | @font-features: "case", "ss01", "ss02";
13 | @line-height: (20 / 14); // ~ 1.428571429
14 | @heading-letter-spacing: -.04em;
15 |
16 | // Layout
17 | @breakpoint-lg: 1600px;
18 | @breakpoint-md: 1080px;
19 | @breakpoint-sm: 640px;
20 | @breakpoint-xs: 400px;
21 | @breakpoint-default: @breakpoint-md;
22 |
23 | @space-lg: 24vw;
24 | @space-md: 8vw;
25 | @space-sm: 4vw;
26 | @space-xs: 1.2vw;
27 | @space-default: @space-md;
28 |
29 | @border-lg: 4px;
30 | @border-md: 2px;
31 | @border-sm: 1px;
32 | @border-default: @border-md;
33 |
34 | // Colors
35 | @link-color: #a212d1;
36 | @background-color: #fff;
37 | @text-color: #000;
38 | @code-color: #00cc80;
39 |
40 | // EXAMPLE: custom color schemes
41 | // Change @link-color could change the whole theme color scheme
42 |
43 | // tan #D2B48C rgb(210,180,140)
44 | // @link-color: darken(@background-color, 50%);
45 | // @background-color: tan;
46 | // @text-color: darken(@background-color, 30%);
47 |
48 | // mediumvioletred #C71585 rgb(199,21,133)
49 | // @link-color: mediumvioletred;
50 | // @background-color: #fff;
51 | // @text-color: #000;
52 |
53 | // orangered #FF4500 rgb(255,69,0)
54 | // @link-color: orangered;
55 | // @background-color: #fff;
56 | // @text-color: #000;
57 |
58 | // indigo (invert) #4B0082 rgb(75,0,130)
59 | // @link-color: #fff;
60 | // @background-color: indigo;
61 | // @text-color: lighten(@background-color, 60%);
62 |
63 | // indigo #4B0082 rgb(75,0,130)
64 | // @link-color: indigo;
65 | // @background-color: #fff;
66 | // @text-color: #000;
67 |
68 | // limegreen #32CD32 rgb(rgb(50,205,50))
69 | // @link-color: limegreen;
70 | // @background-color: #fff;
71 | // @text-color: darken(@link-color, 30%);
72 |
73 | // yellow #FFFF00 rgb(255,255,0)
74 | // @link-color: #000;
75 | // @background-color: yellow;
76 | // @text-color: darken(@background-color, 40%);
77 |
78 | // paleturquoise #AFEEEE rgb(175,238,238)
79 | // @link-color: darken(@background-color, 60%);
80 | // @background-color: paleturquoise;
81 | // @text-color: darken(@background-color, 50%);
82 |
83 | // crimson #DC143C rgb(220,20,60) × gold #FFD700 rgb(255,215,0)
84 | // @link-color: crimson;
85 | // @background-color: gold;
86 | // @text-color: #000;
87 |
--------------------------------------------------------------------------------
/_app/attachments/images/db-demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/overtrue/blog/c8563b36bbfb520a0b32c2260a1de38f70b80b40/_app/attachments/images/db-demo.png
--------------------------------------------------------------------------------
/_app/attachments/images/zsh.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/overtrue/blog/c8563b36bbfb520a0b32c2260a1de38f70b80b40/_app/attachments/images/zsh.png
--------------------------------------------------------------------------------
/_app/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/overtrue/blog/c8563b36bbfb520a0b32c2260a1de38f70b80b40/_app/favicon.png
--------------------------------------------------------------------------------
/_app/feed-atom.xml:
--------------------------------------------------------------------------------
1 | ---
2 | permalink: /feed.xml
3 | ---
4 |
5 |
6 |
7 | {% include amsf/core %}
8 |
9 |
10 | {{ site.name }}
11 | {{ site.description }}
12 | {{ site.time | date_to_xmlschema }}
13 | {{ "/" | absolute_url | xml_escape }}
14 | Almace Scaffolding
15 |
16 |
17 |
18 |
19 |
20 | {{ site.data.authors.default.name }}
21 | {{ "/" | absolute_url }}
22 | {{ site.data.authors.default.email }}
23 |
24 |
25 | {% for post in site.posts limit:300 %}
26 | {% if post.hidden != true %}
27 | {% if site.link_blog and post.link %}
28 | {% assign entry_link = post.link %}
29 | {% else %}
30 | {% assign entry_link = post.url | absolute_url %}
31 | {% endif %}
32 |
33 | {% capture entry_permalink %}
34 | {% if site.link_blog and post.link %}
35 | ◉ Permalink
36 | {% elsif site.link_blog != true and post.link %}
37 | ◉ Direct Link to Original Site
38 | {% endif %}
39 | {% endcapture %}
40 |
41 |
42 | {{ post.title | strip_html | xml_escape }}
43 | {{ post.url | absolute_url | xml_escape }}
44 |
45 | {{ post.date | date_to_xmlschema }}
46 |
47 | {% if post.last_modified_at %}
48 | {{ post.last_modified_at | date_to_xmlschema }}
49 | {% else %}
50 | {{ post.date | date_to_xmlschema }}
51 | {% endif %}
52 |
53 |
54 | {{ author.name }}
55 | {{ author.web }}
56 | {{ author.email }}
57 |
58 |
59 |
60 | {% if site.rss_excerpt %}
61 | {{ post.excerpt | markdownify | xml_escape }}
62 | {% else %}
63 | {{ post.content | markdownify | xml_escape }}
64 | {% endif %}
65 | {{ entry_permalink | xml_escape}}
66 |
67 |
68 | {% if post.excerpt and post.excerpt != empty %}
69 | {{ post.excerpt | markdownify | strip_html | strip_newlines | xml_escape }}
70 | {% endif %}
71 |
72 | {% endif %}
73 | {% endfor %}
74 |
75 |
--------------------------------------------------------------------------------
/_app/feed-json.json:
--------------------------------------------------------------------------------
1 | ---
2 | permalink: /feed.json
3 | ---
4 |
5 | [
6 |
7 | {% assign first = true %}
8 | {% include amsf/core %}
9 |
10 | {% for post in site.posts %}
11 | {% if post.hidden != true %}
12 | {% unless first %},{% endunless %}
13 | {
14 | "title": {{ post.title | jsonify }},
15 | "permalink": {{ post.url | absolute_url | jsonify }},
16 | "link": {{ post.url | absolute_url | jsonify }},
17 | "date": {{ post.date | date_to_xmlschema | jsonify }},
18 | {% if post.last_modified_at %}
19 | "modified": {{ post.last_modified_at | date_to_xmlschema | jsonify }},
20 | {% endif %}
21 | "author": {
22 | "name": {{ author.name | jsonify }},
23 | "url": {{ author.web | jsonify }},
24 | "email": {{ author.email | jsonify }}
25 | },
26 | "content": {{ post.content | markdownify | strip | jsonify }},
27 | "excerpt": {{ post.excerpt | markdownify | strip_html | strip | jsonify }},
28 | "languages": {{ post.languages | jsonify }},
29 | "categories": {{ post.categories | jsonify }},
30 | "tags": {{ post.tags | jsonify }}
31 | }
32 | {% assign first = false %}
33 | {% endif %}
34 | {% endfor %}
35 |
36 | ]
37 |
--------------------------------------------------------------------------------
/_app/manifest.json:
--------------------------------------------------------------------------------
1 | ---
2 | ---
3 |
4 | {
5 | "name": {{ site.name | jsonify }},
6 | "short_name": {{ site.name_short | jsonify }},
7 | "start_url": {{ "/" | relative_url | jsonify }},
8 | "display": "standalone",
9 | "theme_color": {{ site.colors.theme | jsonify }},
10 | "background_color": {{ site.colors.background | jsonify }},
11 | "icons": [
12 | {
13 | "src": {{ "/favicon.png" | relative_url | jsonify }},
14 | "sizes": "192x192",
15 | "type": "image/png"
16 | }
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/_app/mask-icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/_app/robots.txt:
--------------------------------------------------------------------------------
1 | ---
2 | ---
3 |
4 | {% for post in site.robots
5 | %}{{ post | strip }}
6 | {% endfor %}
7 |
8 | Sitemap: {{ "/sitemap.xml" | absolute_url }}
9 |
--------------------------------------------------------------------------------
/_app/sitemap.xml:
--------------------------------------------------------------------------------
1 | ---
2 | ---
3 |
4 |
5 |
6 | {% include amsf/core %}
7 |
8 |
9 |
10 | {{ "/" | absolute_url }}
11 | {{ site.time | date: "%F" }}
12 |
13 |
14 | {% for page in site.html_pages %}
15 | {% if page.hidden != true %}
16 |
17 | {{ page.url | absolute_url | replace: '/index.html', '/' }}
18 | {% if page.last_modified_at %}
19 | {{ page.last_modified_at | date: "%F" }}
20 | {% endif %}
21 |
22 | {% endif %}
23 | {% endfor %}
24 |
25 | {% for post in site.posts %}
26 | {% if post.hidden != true %}
27 |
28 | {{ post.url | absolute_url }}
29 | {% if post.last_modified_at %}
30 | {{ post.last_modified_at | date: "%F" }}
31 | {% else %}
32 | {{ post.date | date: "%F" }}
33 | {% endif %}
34 |
35 | {% endif %}
36 | {% endfor %}
37 |
38 |
39 |
--------------------------------------------------------------------------------
/_config.dev.yml:
--------------------------------------------------------------------------------
1 | #
2 | # Jekyll basic configurations
3 | #
4 |
5 | # Expose site to local network
6 | host: 127.0.0.1
7 |
8 | # Re-include theme example pages for development
9 | exclude: []
10 |
11 | # Disalbe custom plugins (jekyll-last-modified-at) for better performance
12 | plugins:
13 | - jekyll-last-modified-at
14 | - match_regex
15 | - jemoji
16 | - jekyll-octicons
17 | - jekyll-paginate
18 |
19 | #
20 | # Almace Scaffolding settings
21 | #
22 |
23 | # Enable development mode
24 | dev: true
25 |
26 | # Reset base to / for localhost development
27 | baseurl: ""
28 |
29 | # CDN location for development
30 | file: //
31 |
32 | # Force UTF-8 since local server doesn't send charset in their respond header
33 | force_utf_8: true
34 |
--------------------------------------------------------------------------------
/_config.init.yml:
--------------------------------------------------------------------------------
1 | #
2 | # More configuration examples at:
3 | # http://jekyllrb.com/docs/configuration/
4 | #
5 |
6 | #
7 | # Jekyll basic configurations
8 | #
9 |
10 | source: _app
11 | destination: _site
12 | timezone: America/Los_Angeles
13 | permalink: /:title.html
14 | markdown: kramdown
15 | highlighter: rouge
16 | include: ["_pages"]
17 | exclude: ["_pages/themes"]
18 |
19 | #
20 | # Jekyll serve settings
21 | #
22 |
23 | port: 4321
24 |
25 | #
26 | # Jekyll build settings
27 | #
28 |
29 | future: false
30 |
31 | #
32 | # Markdown settings
33 | #
34 |
35 | kramdown:
36 | input: GFM
37 | auto_ids: true
38 | footnote_nr: 1
39 | entity_output: as_char
40 | toc_levels: 1..6
41 | smart_quotes: lsquo,rsquo,ldquo,rdquo
42 | enable_coderay: false
43 | hard_wrap: false
44 | syntax_highlighter: rouge
45 | syntax_highlighter_opts:
46 | css_class: "highlight"
47 | transliterated_header_ids: true
48 | footnote_backlink: "↩︎"
49 |
50 | #
51 | # Jekyll plugins
52 | #
53 |
54 | plugins:
55 | - jekyll-last-modified-at
56 | - match_regex
57 |
58 | #
59 | # Almace Scaffolding settings
60 | #
61 |
62 | # Development mode
63 | #
64 | # Development mode is disabled on production build by default, we've enabled it
65 | # in `_config.dev.yml` for local development, so most of the time you don't
66 | # need to change it.
67 | #
68 | # Default: false
69 | dev: false
70 |
71 | # Site name
72 | #
73 | # Default: Almace Scaffolding
74 | name: Almace Scaffolding
75 |
76 | # Site short name
77 | #
78 | # Currently only used by Chrome Web App manifest, can be anything you like.
79 | # You can also just use your `name` for `name_short` if it is short enough
80 | #
81 | # For example:
82 | # - name: Almace Scaffolding
83 | # - name_short: AMSF
84 | #
85 | # or:
86 | # - name: Sparanoid Group
87 | # - name_short: Sparanoid
88 | #
89 | # Default: AMSF
90 | name_short: AMSF
91 |
92 | # Site description
93 | #
94 | # Will be used in the document meta and Atom feed subtitle
95 | #
96 | # Default: Almace Scaffolding - Super-fast Jekyll framework
97 | description: Almace Scaffolding - Super-fast Jekyll framework
98 |
99 | # Base URL
100 | #
101 | # This is useful when I need to build site in a subdirectory (like
102 | # GitHub Pages for Projects), it provides a specific URL prefix, for example, if
103 | # the production site URL is https://sparanoid.com/my-project/, set `baseurl` to
104 | # `/my-project`, without trailing slash.
105 | #
106 | # Default: ""
107 | baseurl: ""
108 |
109 | # CSS path used in templates
110 | #
111 | # Default: /assets
112 | assets: /assets
113 |
114 | # Feed URL
115 | #
116 | # Define your main feed URL, this will be only used in your templates
117 | #
118 | # Default: /feed.xml
119 | feed: /feed.xml
120 |
121 | # Site full URL
122 | #
123 | # Used in atom feed, sitemap, Twitter Cards, and etc. I should use absolute full
124 | # URLs for these fields
125 | #
126 | # Default: https://sparanoid.com
127 | url: https://sparanoid.com
128 |
129 | # Media assets URL
130 | #
131 | # Media assets URL are used in posts, without trailing slash. ie.
132 | # `
`. In the most cases this URL should be
133 | # powered by a CDN provider
134 | # Note: you should avoid protocol-free (ie. //cdn.mysite.tld) address here
135 | # because Twitter Cards doesn't work well with protocol-free assets when AMSF
136 | # grabs the first image of your post as post featured image.
137 | #
138 | # Default: https://d349cztnlupsuf.cloudfront.net
139 | file: https://d349cztnlupsuf.cloudfront.net
140 |
141 | # Download files URL
142 | #
143 | # Download files URL are used in posts, ie.
144 | # href="{{ site.download }}/package.zip", this should also be a CDN link just
145 | # like `site.file`. There're no notable differences in `site.file` and
146 | # `site.download`, I just use the one for post images and the other for large
147 | # download files. That's it. Remove or ignore this if you don't need it.
148 | #
149 | # Default: https://d1bmx7s6749k4k.cloudfront.net
150 | download: https://d1bmx7s6749k4k.cloudfront.net
151 |
152 | # Primary color
153 | #
154 | # Used by Safari mask-icon, Chrome Web App theme color
155 | #
156 | # Default "#ff00b4"
157 | primary_color: "#ff00b4"
158 |
159 | # Service Worker Precache
160 | service_worker:
161 | enabled: true
162 | max_size: 120000
163 | files:
164 | - "**/*.{css,html,jpg,gif,png,svg,woff,woff2}"
165 | - "**/js/**.js"
166 |
167 | # Google Analytics tracking code
168 | google_analytics:
169 | enabled: false
170 | id: UA-xxxxxxx
171 | url: https://www.googletagmanager.com/gtag/js
172 |
173 | # Site Twitter account handle
174 | twitter: sparanoid
175 |
176 | # Twitter Cards large images
177 | #
178 | # Use summary card with large image for Twitter Cards, require theme support
179 | #
180 | # Default: true
181 | tc_large_image: true
182 |
183 | # Force UTF-8 encoding
184 | #
185 | # Enable this will simply add to the head of your pages
186 | #
187 | # Default: false
188 | force_utf_8: true
189 |
190 | # Link blog
191 | #
192 | # Enable this settings to make your feed Daring Fireball-like link blog (This
193 | # basically change your value to external link you defined in your posts)
194 | #
195 | # Default: false
196 | link_blog: false
197 |
198 | # Excerpts for RSS
199 | #
200 | # Change the value to true to use post excerpt for RSS feeds
201 | #
202 | # Default: false
203 | rss_excerpt: false
204 |
205 | # Generator credits
206 | #
207 | # Show or hide generator credits, require theme support
208 | #
209 | # Default: true
210 | credits: true
211 |
212 | # Custom records in robots.txt
213 | #
214 | # For example:
215 | # robots:
216 | # - "User-agent: *"
217 | # - "Disallow: /ajax/"
218 | # - "Disallow: /@async"
219 | # - "Disallow: /log/"
220 | #
221 | # Default: "User-agent: *"
222 | robots: "User-agent: *"
223 |
--------------------------------------------------------------------------------
/_config.yml:
--------------------------------------------------------------------------------
1 | #
2 | # More configuration examples at:
3 | # http://jekyllrb.com/docs/configuration/
4 | #
5 |
6 | #
7 | # Jekyll basic configurations
8 | #
9 |
10 | source: _app
11 | destination: _site
12 | timezone: Asia/Beijing
13 | permalink: /articles/:year/:month/:title.html
14 | markdown: kramdown
15 | paginate_path: 'page/:num'
16 | paginate: 10
17 | highlighter: rouge
18 | include: ["_pages"]
19 | exclude: ["_pages/themes"]
20 |
21 | #
22 | # Jekyll serve settings
23 | #
24 |
25 | port: 4321
26 |
27 | #
28 | # Jekyll build settings
29 | #
30 |
31 | future: false
32 |
33 | #
34 | # Markdown settings
35 | #
36 |
37 | kramdown:
38 | input: GFM
39 | auto_ids: true
40 | footnote_nr: 1
41 | entity_output: as_char
42 | toc_levels: 1..6
43 | smart_quotes: lsquo,rsquo,ldquo,rdquo
44 | enable_coderay: false
45 | hard_wrap: false
46 | syntax_highlighter: rouge
47 | syntax_highlighter_opts:
48 | css_class: "highlight"
49 | transliterated_header_ids: true
50 | footnote_backlink: "↩︎"
51 |
52 | #
53 | # Jekyll plugins
54 | #
55 |
56 | plugins:
57 | - jekyll-last-modified-at
58 | - match_regex
59 | - jemoji
60 | - jekyll-octicons
61 | - jekyll-paginate
62 |
63 | #
64 | # Almace Scaffolding settings
65 | #
66 |
67 | # Development mode
68 | #
69 | # Development mode is disabled on production build by default, we've enabled it
70 | # in `_config.dev.yml` for local development, so most of the time you don't
71 | # need to change it.
72 | #
73 | # Default: false
74 | dev: false
75 |
76 | # Site name
77 | #
78 | # Default: Almace Scaffolding
79 | name: overtrue
80 |
81 | # Site short name
82 | #
83 | # Currently only used by Chrome Web App manifest, can be anything you like.
84 | # You can also just use your `name` for `name_short` if it is short enough
85 | #
86 | # For example:
87 | # - name: Almace Scaffolding
88 | # - name_short: AMSF
89 | #
90 | # or:
91 | # - name: Sparanoid Group
92 | # - name_short: Sparanoid
93 | #
94 | # Default: AMSF
95 | name_short: overtrue
96 |
97 | # Site description
98 | #
99 | # Will be used in the document meta and Atom feed subtitle
100 | #
101 | # Default: Almace Scaffolding - Super-fast Jekyll framework
102 | description: "Keep calm and coding."
103 |
104 | # Base URL
105 | #
106 | # This is useful when I need to build site in a subdirectory (like
107 | # GitHub Pages for Projects), it provides a specific URL prefix, for example, if
108 | # the production site URL is https://sparanoid.com/my-project/, set `baseurl` to
109 | # `/my-project`, without trailing slash.
110 | #
111 | # Default: ""
112 | # baseurl: ""
113 |
114 | # CSS path used in templates
115 | #
116 | # Default: /assets
117 | assets: /assets
118 |
119 | # Feed URL
120 | #
121 | # Define your main feed URL, this will be only used in your templates
122 | #
123 | # Default: /feed.xml
124 | feed: /feed.xml
125 |
126 | # Site full URL
127 | #
128 | # Used in atom feed, sitemap, Twitter Cards, and etc. I should use absolute full
129 | # URLs for these fields
130 | #
131 | # Default: https://sparanoid.com
132 | url: https://overtrue.me
133 |
134 | # Media assets URL
135 | #
136 | # Media assets URL are used in posts, without trailing slash. ie.
137 | # `
`. In the most cases this URL should be
138 | # powered by a CDN provider
139 | # Note: you should avoid protocol-free (ie. //cdn.mysite.tld) address here
140 | # because Twitter Cards doesn't work well with protocol-free assets when AMSF
141 | # grabs the first image of your post as post featured image.
142 | #
143 | # Default: https://d349cztnlupsuf.cloudfront.net
144 | # file: https://d349cztnlupsuf.cloudfront.net
145 |
146 | # Download files URL
147 | #
148 | # Download files URL are used in posts, ie.
149 | # href="{{ site.download }}/package.zip", this should also be a CDN link just
150 | # like `site.file`. There're no notable differences in `site.file` and
151 | # `site.download`, I just use the one for post images and the other for large
152 | # download files. That's it. Remove or ignore this if you don't need it.
153 | #
154 | # Default: https://d1bmx7s6749k4k.cloudfront.net
155 | # download: https://d1bmx7s6749k4k.cloudfront.net
156 |
157 | # Colors
158 | #
159 | # Used by Safari mask-icon, Chrome Web App theme color
160 | colors:
161 | theme: "#5477F8"
162 | background: "#fff"
163 |
164 | # Service Worker Precache
165 | service_worker:
166 | enabled: true
167 | max_size: 120000
168 | files:
169 | - "**/*.{css,html,jpg,gif,png,svg,woff,woff2}"
170 | - "**/js/**.js"
171 |
172 | # Google Analytics tracking code
173 | google_analytics:
174 | enabled: true
175 | id: UA-55452630-1
176 | url: https://www.googletagmanager.com/gtag/js
177 |
178 | # Site Twitter account handle
179 | twitter: overtrue666
180 |
181 | # Twitter Cards large images
182 | #
183 | # Use summary card with large image for Twitter Cards, require theme support
184 | #
185 | # Default: true
186 | tc_large_image: true
187 |
188 | # Force UTF-8 encoding
189 | #
190 | # Enable this will simply add to the head of your pages
191 | #
192 | # Default: false
193 | force_utf_8: true
194 |
195 | # Link blog
196 | #
197 | # Enable this settings to make your feed Daring Fireball-like link blog (This
198 | # basically change your value to external link you defined in your posts)
199 | #
200 | # Default: false
201 | link_blog: false
202 |
203 | # Excerpts for RSS
204 | #
205 | # Change the value to true to use post excerpt for RSS feeds
206 | #
207 | # Default: false
208 | rss_excerpt: false
209 |
210 | # Generator credits
211 | #
212 | # Show or hide generator credits, require theme support
213 | #
214 | # Default: true
215 | credits: true
216 |
217 | # Custom records in robots.txt
218 | #
219 | # For example:
220 | # robots:
221 | # - "User-agent: *"
222 | # - "Disallow: /ajax/"
223 | # - "Disallow: /@async"
224 | # - "Disallow: /log/"
225 | #
226 | # Default: "User-agent: *"
227 | robots: "User-agent: *"
228 |
--------------------------------------------------------------------------------
/_deploy.yml:
--------------------------------------------------------------------------------
1 | #
2 | # Custom deployment variables
3 | #
4 |
5 | # Usage:
6 | # $ grunt deploy:[deploy_method] --env=[deploy_environment]
7 | #
8 | # Examples:
9 | # $ grunt && grunt deploy:rsync
10 | # $ grunt && grunt deploy:rsync --env=prod
11 | # $ grunt && grunt deploy:s3_website
12 | #
13 | # You can also create your own deployment method, `default` environment is
14 | # required if no `--env` parameter passed:
15 | # ftp:
16 | # default:
17 | # user: ftp_user
18 | # port: 123
19 | # blah: blah
20 | rsync:
21 | default:
22 | user: rsync
23 | port: 22
24 | host: caladbolg.sparanoid.com
25 | dest: /srv/www/dev.sparanoid.com/public_html
26 | # Extra rsync parameters
27 | # I use `--exclude` here to prevent additional files from being deleted on
28 | # the server (rsync --delete), these files and directories are available on
29 | # the remote server but outside this git repo. So I need to ignore them.
30 | params: --exclude=lab
31 | prod:
32 | user: sparanoid
33 | port: 22
34 | host: caladbolg.sparanoid.com
35 | dest: /srv/www/sparanoid.com/public_html
36 | # Extra rsync parameters
37 | # I use `--rsync-path` here to run `rsync` with sudo permission.
38 | params: --rsync-path="sudo rsync" --exclude=lab
39 |
40 | sparanoid:
41 | default:
42 | dest: ~/Git/sparanoid.com-prod
43 |
--------------------------------------------------------------------------------
/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 |
4 | DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
5 |
6 | cd $DIR && git reset --hard && git pull && bundle install && npm i && grunt build && \
7 |
8 | curl 'https://api.github.com/users/overtrue/repos?type=owner&per_page=100' -H 'Accept: */*' -H 'Referer: https://overtrue.me/open-source/' -H 'Origin: https://overtrue.me' -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.81 Safari/537.36' --compressed > /www/overtrue.me/_site/opensource.json
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "almace-scaffolding",
3 | "version": "1.1.10",
4 | "description": "Almace Scaffolding, a super-fast Jekyll framework",
5 | "author": "Tunghsiao Liu",
6 | "homepage": "https://sparanoid.com/lab/amsf/",
7 | "main": "Gruntfile.coffee",
8 | "scripts": {
9 | "test": "grunt test"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "git://github.com/sparanoid/almace-scaffolding.git"
14 | },
15 | "bugs": {
16 | "url": "https://github.com/sparanoid/almace-scaffolding/issues"
17 | },
18 | "license": "MIT",
19 | "keywords": [
20 | "amsf",
21 | "jekyll",
22 | "blog",
23 | "less",
24 | "css"
25 | ],
26 | "readmeFilename": "README.md",
27 | "devDependencies": {
28 | "autoprefixer": "^9.8.0",
29 | "coffeelint": "^2.1.0",
30 | "grunt": "^1.0.4",
31 | "grunt-assets-inline": "^1.2.2",
32 | "grunt-bump": "^0.8.0",
33 | "grunt-cache-bust": "^1.7.0",
34 | "grunt-cleanempty": "^1.0.4",
35 | "grunt-coffeelint": "0.0.16",
36 | "grunt-concurrent": "^2.3.1",
37 | "grunt-contrib-clean": "^2.0.0",
38 | "grunt-contrib-copy": "^1.0.0",
39 | "grunt-contrib-cssmin": "^3.0.0",
40 | "grunt-contrib-htmlmin": "^3.1.0",
41 | "grunt-contrib-less": "^2.0.0",
42 | "grunt-contrib-uglify": "^4.0.1",
43 | "grunt-contrib-watch": "^1.1.0",
44 | "grunt-conventional-changelog": "^6.1.0",
45 | "grunt-csscomb": "^4.0.0",
46 | "grunt-git": "^1.0.14",
47 | "grunt-html-trim": "^0.1.2",
48 | "grunt-jekyll": "^1.0.1",
49 | "grunt-minjson": "^0.4.0",
50 | "grunt-newer": "^1.3.0",
51 | "grunt-postcss": "^0.9.0",
52 | "grunt-service-worker": "^1.0.3",
53 | "grunt-shell": "^3.0.1",
54 | "grunt-text-replace": "^0.4.0",
55 | "grunt-uncss-inline": "^1.2.6",
56 | "grunt-xmlmin": "^0.1.8",
57 | "jit-grunt": "^0.10.0",
58 | "time-grunt": "^2.0.0"
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/s3_website.example.yml:
--------------------------------------------------------------------------------
1 | # This is an example config for `s3_website`
2 | # Rename this file to `s3_website.yml` and edit it
3 | # See https://github.com/laurilehmijoki/s3_website for more info
4 | s3_id: <%= ENV['S3_ID'] %>
5 | s3_secret: <%= ENV['S3_SECRET'] %>
6 | s3_bucket: sparanoid.com
7 | site: .
8 | cache_control:
9 | "*.json": no-cache, no-store
10 | "*service-worker.js": no-cache, no-store
11 | max_age:
12 | "*.html": 1800
13 | "*.xml": 1800
14 | "*.css": 2592000
15 | "*.js": 2592000
16 | "*": 86400
17 | exclude_from_upload:
18 | - (?:\.(?:DS_Store|bak|config|sql|fla|psd|ai|sketch|ini|log|sh|inc|swp|dist))$
19 | index_document: index.html
20 | error_document: error/index.html # should match your error page permalink
21 | concurrency_level: 10
22 | treat_zero_length_objects_as_redirects: true
23 |
24 | # CloudFront settings
25 | cloudfront_distribution_id: <%= ENV['CF_DIST_ID'] %>
26 | cloudfront_invalidate_root: true
27 | cloudfront_wildcard_invalidation: true
28 | cloudfront_distribution_config:
29 | aliases:
30 | quantity: 1
31 | items:
32 | - sparanoid.com
33 |
--------------------------------------------------------------------------------