From 6fa1f68a6fb9a57250af1f6dc4b4d1b91c622512 Mon Sep 17 00:00:00 2001 From: patarapolw Date: Fri, 23 Aug 2019 20:08:23 +0700 Subject: [PATCH] add post and fix bugs, also use external --- package-lock.json | 20 +- package.json | 4 +- public/posts/2019/08/千字文.md | 25 +- public/posts/2019/08/千字文1.4.pug | 1 - public/posts/2019/08/千字文2.3.pug | 100 ++++++++ src/App.vue | 7 + src/layouts/Post.vue | 2 - src/layouts/Sidebar.vue | 2 +- src/plugins/gtag.ts | 2 +- src/plugins/render.js | 4 +- src/plugins/search.spec.ts | 21 -- src/plugins/search.ts | 396 ----------------------------- src/plugins/showdown-ext.js | 17 ++ src/router.ts | 15 +- src/views/Search.vue | 25 +- src/views/Single.vue | 29 ++- tsconfig.json | 3 +- 17 files changed, 214 insertions(+), 459 deletions(-) create mode 100644 public/posts/2019/08/千字文2.3.pug delete mode 100644 src/plugins/search.spec.ts delete mode 100644 src/plugins/search.ts diff --git a/package-lock.json b/package-lock.json index d2a5518..75e1d3d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1040,8 +1040,8 @@ }, "@types/node": { "version": "12.7.2", - "resolved": "https://registry.npm.taobao.org/@types/node/download/@types/node-12.7.2.tgz", - "integrity": "sha1-xOY69eiCPOnMPws097mYwhcfDEQ=" + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.2.tgz", + "integrity": "sha512-dyYO+f6ihZEtNPDcWNR1fkoTDf3zAK3lAABDze3mz6POyIercH0lEUawUFXlG8xaQZmm1yEBON/4TsYv/laDYg==" }, "@types/normalize-package-data": { "version": "2.4.0", @@ -6306,9 +6306,9 @@ } }, "indented-filter": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/indented-filter/-/indented-filter-0.1.0.tgz", - "integrity": "sha512-usPQg1fmF5JOAjg3VKHrhd7gcXaN4PuCIeyLf88/AnxZIAhys9ruuPwB2ww40eHKcz72+mMSg7Z/GVO2V5WXHQ==" + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/indented-filter/-/indented-filter-0.1.1.tgz", + "integrity": "sha512-qOAYFXYHxMClzpXN95wgW/Myy7j332KEWNNl35boaAcU3tL1HPcLQLCLClzmTuyWG4kpmDk5Kps/Wz0u7paUNg==" }, "indexes-of": { "version": "1.0.1", @@ -9484,6 +9484,16 @@ "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", "dev": true }, + "q2filter": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/q2filter/-/q2filter-0.1.2.tgz", + "integrity": "sha512-FIYV/QBh5ECbuRowIK4NNNkjLNzHJ9kO3654dg1jxVlQ5UAy8RTqmTlioYHeJ8qeg2+fWDGbesiNO3mpYbFXHQ==", + "requires": { + "@types/uuid": "^3.4.5", + "moment": "^2.24.0", + "uuid": "^3.3.3" + } + }, "qs": { "version": "6.5.2", "resolved": "https://registry.npm.taobao.org/qs/download/qs-6.5.2.tgz", diff --git a/package.json b/package.json index ebe58a3..03220fd 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "deploy": "npm run build && gh-pages -d dist" }, "dependencies": { + "@types/node": "^12.7.2", "@types/uuid": "^3.4.5", "bootstrap-vue": "^2.0.0-rc.19", "core-js": "^2.6.5", @@ -18,9 +19,10 @@ "gray-matter": "^4.0.2", "hyperpug": "^0.1.7", "hyperscript": "^2.0.2", - "indented-filter": "^0.1.0", + "indented-filter": "^0.1.1", "js-yaml": "^3.13.1", "moment": "^2.24.0", + "q2filter": "^0.1.2", "showdown": "^1.9.0", "uuid": "^3.3.3", "vue": "^2.6.10", diff --git a/public/posts/2019/08/千字文.md b/public/posts/2019/08/千字文.md index adbb951..65d71ff 100644 --- a/public/posts/2019/08/千字文.md +++ b/public/posts/2019/08/千字文.md @@ -4,22 +4,23 @@ date: "2019-08-10" tag: - 千字文 - 汉字 + - essay --- 1 -天地玄黃,宇宙洪荒。 -日月盈昃,辰宿列張。 -寒來暑往,秋收冬藏。 -閏餘成歲,律呂調陽。 -雲騰致雨,露結為霜。 -金生麗水,玉出崑岡。 -劍號巨闕,珠稱夜光。 -果珍李柰,菜重芥薑。 -海咸河淡,鱗潛羽翔。 +^^to(url="/p" filename="public/posts/2019/08/千字文1.1.pug") 天地玄黃,宇宙洪荒。 +^^to(url="/p" filename="public/posts/2019/08/千字文1.2.pug") 日月盈昃,辰宿列張。 +^^to(url="/p" filename="public/posts/2019/08/千字文1.3.pug") 寒來暑往,秋收冬藏。 +^^to(url="/p" filename="public/posts/2019/08/千字文1.4.pug") 閏餘成歲,律呂調陽。 +^^to(url="/p" filename="public/posts/2019/08/千字文1.5.pug") 雲騰致雨,露結為霜。 +^^to(url="/p" filename="public/posts/2019/08/千字文1.6.pug") 金生麗水,玉出崑岡。 +^^to(url="/p" filename="public/posts/2019/08/千字文1.7.pug") 劍號巨闕,珠稱夜光。 +^^to(url="/p" filename="public/posts/2019/08/千字文1.8.pug") 果珍李柰,菜重芥薑。 +^^to(url="/p" filename="public/posts/2019/08/千字文1.9.pug") 海咸河淡,鱗潛羽翔。 2 -龍師火帝,鳥官人皇。 -始制文字,乃服衣裳。 -推位讓國,有虞陶唐。 +^^to(url="/p" filename="public/posts/2019/08/千字文2.1.pug") 龍師火帝,鳥官人皇。 +^^to(url="/p" filename="public/posts/2019/08/千字文2.2.pug") 始制文字,乃服衣裳。 +^^to(url="/p" filename="public/posts/2019/08/千字文2.3.pug") 推位讓國,有虞陶唐。 弔民伐罪,周發殷湯。 坐朝問道,垂拱平章。 愛育黎首,臣伏戎羌。 diff --git a/public/posts/2019/08/千字文1.4.pug b/public/posts/2019/08/千字文1.4.pug index e2f5178..23c4cc7 100644 --- a/public/posts/2019/08/千字文1.4.pug +++ b/public/posts/2019/08/千字文1.4.pug @@ -77,7 +77,6 @@ tag: 斜阳 (斜陽) | xiéyáng | setting sun 朝阳 (朝陽) | cháoyáng | to be exposed to the sun / in a position facing the sun 朝阳 (朝陽) | zhāoyáng | the morning sun -HSK 4 style. table td:first-child { width: 10em; diff --git a/public/posts/2019/08/千字文2.3.pug b/public/posts/2019/08/千字文2.3.pug new file mode 100644 index 0000000..6c589d1 --- /dev/null +++ b/public/posts/2019/08/千字文2.3.pug @@ -0,0 +1,100 @@ +--- +title: "千字文 - 2/3" +date: "2019-08-23" +tag: + - 千字文 + - 汉字 +--- + +.w-100.mt-3 + h3.text-center 推位讓國,有虞陶唐。 +.w-100 + figure.d-inline-block + img(src="https://www.mdbg.net/chinese/rsc/img/stroke_anim/25512.gif" height=100) + figcaption.text-center tui1 + figure.d-inline-block + img(src="https://www.mdbg.net/chinese/rsc/img/stroke_anim/20301.gif" height=100) + figcaption.text-center wei4 + figure.d-inline-block + img(src="https://www.mdbg.net/chinese/rsc/img/stroke_anim/35731.gif" height=100) + figcaption.text-center rang4 + figure.d-inline-block + img(src="https://www.mdbg.net/chinese/rsc/img/stroke_anim/22283.gif" height=100) + figcaption.text-center guo2 +.w-100 + figure.d-inline-block + img(src="https://www.mdbg.net/chinese/rsc/img/stroke_anim/26377.gif" height=100) + figcaption.text-center you3 + figure.d-inline-block + .hanzi-img 虞 + figcaption.text-center fu2 + figure.d-inline-block + img(src="https://www.mdbg.net/chinese/rsc/img/stroke_anim/38518.gif" height=100) + figcaption.text-center tao2 + figure.d-inline-block + img(src="https://www.mdbg.net/chinese/rsc/img/stroke_anim/21776.gif" height=100) + figcaption.text-center tang2 + +=== + +.w-100.mt-3 + :markdown + 推荐 (推薦) | tuījiàn | to recommend / recommendation + 推广 (推廣) | tuīguǎng | to extend / to spread / to popularize / generalization / promotion (of a product etc) + 推动 (推動) | tuīdòng | to push (for acceptance of a plan) / to push forward / to promote / to actuate / CL: 個|个 + 推进 (推進) | tuījìn | to impel / to carry forward / to push on / to advance / to drive forward + 推 | tuī | to push / to cut / to refuse / to reject / to decline / to shirk (responsibility) / to put off / to delay / to push forward / to nominate / to elect / massage + 推出 | tuīchū | to push out / to release / to launch / to publish / to recommend + 推理 | tuīlǐ | reasoning / inference / to infer / to deduce + 推介 | tuījiè | promotion / to promote / to introduce and recommend + 推行 | tuīxíng | to put into effect / to carry out + 单位 (單位) | dānwèi | unit (of measure) / unit (group of people as a whole) / work unit (place of employment, esp. in the PRC prior to economic reform) / CL: 個|个 + 地位 | dìwèi | position / status / place / CL: 個|个 + 位置 | wèizhi | position / place / seat / CL: 個|个 + 位 | wèi | position / location / place / seat / classifier for people (honorific) / classifier for binary bits (e.g. 十六位 16-bit or 2 bytes) / (physics) potential + 定位 | dìngwèi | to orientate / to position / to categorize (as) / to characterize (as) / positioning / position / niche + 学位 (學位) | xuéwèi | academic degree / place in school + 岗位 (崗位) | gǎngwèi | a post / a job + 位于 (位於) | wèiyú | to be located at / to be situated at / to lie + 各位 | gèwèi | everybody / all (guests, colleagues etc) / all of you + 职位 (職位) | zhíwèi | post / office / position + 方位 | fāngwèi | direction / points of the compass / bearing / position / azimuth + 让 (讓) | ràng | to yield / to permit / to let sb do sth / to have sb do sth / to make sb (feel sad etc) / by (indicates the agent in a passive clause, like 被) + 转让 (轉讓) | zhuǎnràng | to transfer (ownership, rights etc) + 让球 (讓球) | ràngqiú | to concede points (in a game) + 出让 (出讓) | chūràng | to transfer (one's property or rights to sb else) + 礼让 (禮讓) | lǐràng | to show consideration for (others) / to yield to (another vehicle etc) / courtesy / comity + 让步 (讓步) | ràngbù | to concede / to give in / to yield / a concession / (linguistics) concessive + 避让 (避讓) | bìràng | to avoid / to yield (in traffic) / to get out of the way + 国际 (國際) | guójì | international + 国家 (國家) | guójiā | country / nation / state / CL: 個|个 + 国内 (國內) | guónèi | domestic / internal (to a country) / civil + 中国 (中國) | Zhōngguó | China + 王国 (王國) | wángguó | kingdom / realm + 开国 (開國) | kāiguó | to found a state / to open a closed country + 版权所有 (版權所有) | bǎnquánsuǒyǒu | all rights reserved (copyright statement) + 有限 | yǒuxiàn | limited / finite + 有限公司 | yǒuxiàngōngsī | limited company / corporation + 拥有 (擁有) | yōngyǒu | to have / to possess + 虞 | yú | to expect / to anticipate / to worry / to cheat + 尔虞我诈 (爾虞我詐) | ěryúwǒzhà | lit. you hoodwink me and I cheat you (idiom); fig. mutual deception / each tries to outwit the other / dog eats dog and devil take the hindmost + 无虞 (無虞) | wúyú | not to be worried about / all taken care of + 安全无虞 (安全無虞) | ānquánwúyú | safe and secure + 有效 | yǒuxiào | effective / in effect / valid + 所有 | suǒyǒu | all / to have / to possess / to own + 陶瓷 | táocí | pottery and porcelain / ceramics + 陶 | táo | pottery / pleased + 陶艺 (陶藝) | táoyì | ceramic art + 陶醉 | táozuì | to be infatuated with / to be drunk with / to be enchanted with / to revel in + 熏陶 (薰陶) | xūntáo | to seep in / to influence / to nurture / influence / training + 陶器 | táoqì | pottery + 陶冶 | táoyě | lit. to fire pots and smelt metal / fig. to educate + 唐 | Táng | Tang dynasty (618-907) / surname Tang + 唐 | táng | to exaggerate / empty / in vain / old variant of 螗 + 唐诗 (唐詩) | Tángshī | Tang poetry / a Tang poem + 唐人街 | Tángrén Jiē | Chinatown / CL: 條|条, 座 + 荒唐 | huāngtáng | beyond belief / preposterous / absurd / intemperate / dissipated + style. + table td:first-child { + width: 10em; + } diff --git a/src/App.vue b/src/App.vue index 36bcaf7..d132e2e 100644 --- a/src/App.vue +++ b/src/App.vue @@ -32,3 +32,10 @@ export default class App extends Vue { private g = g; } + + diff --git a/src/layouts/Post.vue b/src/layouts/Post.vue index b273576..5c6549a 100644 --- a/src/layouts/Post.vue +++ b/src/layouts/Post.vue @@ -90,7 +90,6 @@ export default class Post extends Vue { get content() { const { content } = this.matter; const m = /^([^]+)\s+===\s+([^]+)$/.exec(content || ""); - console.log(m) if (m) { return this.parseContent(m[1] + (m[2] || "")); } @@ -99,7 +98,6 @@ export default class Post extends Vue { } public async mounted() { - console.log(this.url); this.matter = matter(await (await fetch(this.url)).text()); } diff --git a/src/layouts/Sidebar.vue b/src/layouts/Sidebar.vue index 8637ab1..98e1529 100644 --- a/src/layouts/Sidebar.vue +++ b/src/layouts/Sidebar.vue @@ -6,7 +6,7 @@ div Highlight text and press "s" to speak. a.twitter-timeline(href="https://twitter.com/ChinesePol" data-height="600") Tweets by ChinesePol - b-card + b-card.mb-3 h3 Tag cloud span.mr-3(v-for="t in tags" :key="t.name" :class="t.class") b-link(:to="'/tag/' + t.name") {{t.name}} diff --git a/src/plugins/gtag.ts b/src/plugins/gtag.ts index 69d1a5f..94a46d4 100644 --- a/src/plugins/gtag.ts +++ b/src/plugins/gtag.ts @@ -1,5 +1,5 @@ import { g } from "@/shared"; -import { dotGetter } from './search'; +import { dotGetter } from "q2filter"; declare const dataLayer: any[]; diff --git a/src/plugins/render.js b/src/plugins/render.js index caf8c21..3f9fb21 100644 --- a/src/plugins/render.js +++ b/src/plugins/render.js @@ -1,11 +1,11 @@ import showdown from "showdown"; -import { slideExt, simpleTableExt } from "./showdown-ext"; +import { slideExt, simpleTableExt, toExt } from "./showdown-ext"; export class Markdown { constructor() { this.converter = new showdown.Converter({ parseImgDimensions: true, - extensions: [simpleTableExt, slideExt] + extensions: [simpleTableExt, slideExt, toExt] }); this.converter.setFlavor("github"); } diff --git a/src/plugins/search.spec.ts b/src/plugins/search.spec.ts deleted file mode 100644 index 02a8d8b..0000000 --- a/src/plugins/search.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import SearchParser from "./search"; - -const p = new SearchParser(); - -describe("SearchParser.doParse", () => { - [ - "", - "a", - "a:b", - "a:b c", - "a:b c:d", - "a:b OR c", - "date:2018-08-12", - "date>2018-08-12", - " tag:分类词汇" - ].forEach((q) => { - it(q, () => { - console.dir(p.parseFull(q), {depth: null}); - }) - }); -}) diff --git a/src/plugins/search.ts b/src/plugins/search.ts deleted file mode 100644 index c94f706..0000000 --- a/src/plugins/search.ts +++ /dev/null @@ -1,396 +0,0 @@ -/** - * Adapted from https://github.com/patarapolw/rep2recall-web/blob/master/src/node/engine/search.ts - */ -import uuid from "uuid/v4"; -import moment from "moment"; -import { g } from '@/shared'; - -export interface ISearchParserResult { - is: Set; - sortBy: string | null; - desc: boolean | null; - cond: Record; -} - -export default class SearchParser { - public is: Set = new Set(); - public sortBy: string | null = null; - public desc: boolean | null = null; - - private readonly anyOf: Set | null = null; - private readonly isString = new Set(["title", "author", "tag"]); - private readonly isDate = new Set(["date"]); - - public filterPosts(q: string): Record[] { - const cond = this.parse(q); - - let ps = g.posts; - if (this.sortBy) { - ps = ps.sort(sorter(this.sortBy, this.desc || false)); - } else { - ps = ps.sort(sorter("date", true)); - } - - ps = ps.filter(mongoFilter(cond)); - - return ps; - } - - public parse(q: string): Record { - this.is = new Set(); - this.sortBy = null - this.desc = null; - - try { - return this._parse(q); - } catch (e) { } - - return {}; - } - - public parseFull(q: string): ISearchParserResult { - return { - cond: this.parse(q), - is: this.is, - sortBy: this.sortBy, - desc: this.desc - } - } - - private _parse(q: string): Record { - q = q.trim(); - - for (const method of [ - this.removeBrackets, - this.parseSep(" OR "), - this.parseSep(" "), - this.parseNeg, - this.parseFullExpr, - this.parsePartialExpr - ]) { - try { - return method.bind(this)(q); - } catch (e) { } - } - - throw new Error(); - } - - private removeBrackets(q: string) { - if (q[0] === "(" && q[q.length - 1] === ")") { - return this.parse(q.substr(1, q.length - 2)); - } - - throw new Error("Not bracketed"); - } - - private parseSep(sep: string) { - return (q: string) => { - const brackets: any = {}; - - q = q.replace(/\([^)]+\)/g, (p0) => { - const id = uuid(); - brackets[id] = p0; - return id; - }); - const tokens = q.split(sep); - tokens.forEach((t, i) => { - for (const k of Object.keys(brackets)) { - tokens[i] = tokens[i].replace(k, brackets[k]); - } - }); - - if (tokens.length >= 2) { - const parsedTokens = tokens.map((t) => this._parse(t)).map((t) => t); - if (parsedTokens.length > 1) { - return { [sep === " OR " ? "$or" : "$and"]: parsedTokens }; - } else { - return parsedTokens[0]; - } - } - - throw new Error(`Not separated by '${sep}'`); - } - } - - private parseNeg(q: string) { - if (q[0] === "-") { - const sb = "-sortBy:"; - if (q.startsWith(sb) && q !== sb) { - this.sortBy = q.substr(sb.length); - return {}; - } - - return { $not: this.parse(q.substr(1)) }; - } - - throw new Error("Not negative"); - } - - private parseFullExpr(q: string) { - const m = /^([\w-]+)(:|~|[><]=?|=)([\S-]+|"[^"]+")$/.exec(q); - if (m) { - let [_, k, op, v]: any[] = m; - - if (v.length > 2 && v[0] === '"' && v[v.length - 1] === '"') { - v = v.substr(1, v.length - 2); - } else { - const m1 = /^\d+(?:\.\d+)?$/.exec(v); - if (m1) { - v = parseFloat(v); - } - } - - if (k === "is") { - if (v === "due") { - k = "nextReview"; - op = "<="; - v = new Date(); - } else if (v === "leech") { - k = "srsLevel"; - op = "="; - v = 0; - } else if (v === "new") { - k = "nextReview"; - op = "=";; - v = "NULL"; - } else { - this.is.add(v); - return {}; - } - } else if (k === "sortBy") { - this.sortBy = v; - return {}; - } - - if (op === ":") { - if (k === "due" || k === "nextReview") { - k = "nextReview"; - v = "<=" - } else if (k === "created" || k === "modified") { - v = "<="; - } - } - - if (v === "NULL") { - v = null; - } - - if (this.isDate.has(k)) { - if (v === "NOW") { - v = new Date(); - } else if (typeof v === "string") { - const m1 = /^([-+]?\\d+)(\\S*)$/.exec(v); - let isMomentParsed = false; - if (m1) { - try { - v = moment().add(parseInt(m1[1]), m1[2] as any).toDate(); - isMomentParsed = true; - } catch (e) { } - } - - if (!isMomentParsed) { - try { - v = moment(v).toDate() - } catch (e) { } - } - } - } - - // if (moment.isDate(v) && op === ":") { - // op = ">"; - // } - - if (v) { - if (op === ":") { - if (typeof v === "string" || this.isString.has(k)) { - v = { $substr: v }; - } - } else if (op === "~") { - v = { $regex: v.toString() }; - } else if (op === ">=") { - v = { $gte: v }; - } else if (op === ">") { - v = { $gt: v } - } else if (op === "<=") { - v = { $lte: v }; - } else if (op === "<") { - v = { $lt: v }; - } - } - - return { [k]: v }; - } - - throw new Error("Not full expression"); - } - - - private parsePartialExpr(q: string) { - if (q && q.indexOf(":") === -1) { - const orCond: any[] = []; - - if (this.anyOf) { - for (const a of this.anyOf) { - if (this.isString.has(a)) { - orCond.push({ [a]: { $substr: q } }); - } else { - orCond.push({ [a]: q }); - } - } - } else { - return {"*": {$substr: q}} - } - - if (orCond.length > 1) { - return { $or: orCond }; - } else if (orCond.length === 1) { - return orCond[0]; - } - - return {}; - } - - throw new Error("Not partial expression"); - } -} - -export function mongoFilter(cond: any) { - return (item: Record): boolean => { - for (const k of Object.keys(cond)) { - if (k[0] === "$") { - if (k === "$and") { - return cond[k].every((x: Record) => mongoFilter(x)(item)); - } else if (k === "$or") { - return cond[k].some((x: Record) => mongoFilter(x)(item)); - } else if (k === "$not") { - return !mongoFilter(cond[k])(item); - } - } else { - let itemK = dotGetter(item, k); - - if (cond[k] && cond[k].constructor === {}.constructor - && Object.keys(cond[k]).some((k0) => k0[0] === "$")) { - return (() => { - for (const op of Object.keys(cond[k])) { - try { - if (op === "$regex") { - if (Array.isArray(itemK)) { - return itemK.some(new RegExp(cond[k][op].toString(), "i").test); - } else { - return new RegExp(cond[k][op].toString(), "i").test(itemK); - } - } else if (op === "$startswith") { - if (Array.isArray(itemK)) { - return itemK.some((el) => el.startsWith(cond[k][op])); - } else { - return itemK.startsWith(cond[k][op]); - } - } else if (op === "$substr") { - - if (Array.isArray(itemK)) { - return itemK.some((el: string) => el.includes(cond[k][op])); - } else { - return itemK.includes(cond[k][op]); - } - } else if (op === "$exists") { - return (itemK === null || itemK === undefined || itemK === "") === cond[k][op]; - } else { - let v = itemK; - let v0 = cond[k][op]; - try { - [v, v0] = [parseInt(v), parseInt(v0)]; - } catch (e) { } - - if (op === "$gte") { - return v >= v0; - } else if (op === "$gt") { - return v > v0; - } else if (op === "$lte") { - return v <= v0; - } else if (op === "$lt") { - return v < v0; - } - } - } catch (e) { } - } - return false; - })(); - } else if (Array.isArray(itemK)) { - if (!itemK.includes(cond[k])) { - return false; - } - } else if (itemK !== cond[k]) { - return false; - } - } - } - - return true; - } -} - -export function dotGetter(d: any, k: string) { - let v = d; - for (const kn of k.split(".")) { - if (v && v.constructor === {}.constructor) { - if (kn === "*") { - v = Object.values(v); - } else { - v = v[kn]; - if (v === undefined) { - v = {}; - } - } - } else if (Array.isArray(v)) { - try { - v = v[parseInt(kn)]; - if (v === undefined) { - v = null; - break; - } - } catch (e) { - v = null; - break; - } - } - } - - if (v && v.constructor === {}.constructor && Object.keys(v).length === 0) { - v = null; - } - - return v; -} - -export function sorter(sortBy?: string, desc?: boolean) { - return (a: any, b: any) => { - if (!sortBy) { - return 0; - } - - const m = a[sortBy]; - const n = b[sortBy]; - - if (typeof m === typeof n) { - if (typeof m === "string") { - return desc ? n.localeCompare(m) : m.localeCompare(n); - } else if (typeof m === "number") { - return desc ? n - m : m - n; - } else { - return 0; - } - } else { - const typeDict = { - "number": 1, - "string": 2, - "object": 3 - } as any; - - const tM = typeDict[typeof m] || -1; - const tN = typeDict[typeof n] || -1; - - return desc ? tN - tM : tM - tN; - } - } -} \ No newline at end of file diff --git a/src/plugins/showdown-ext.js b/src/plugins/showdown-ext.js index 42c09ba..ae4018b 100644 --- a/src/plugins/showdown-ext.js +++ b/src/plugins/showdown-ext.js @@ -41,4 +41,21 @@ export const slideExt = { return a.outerHTML; }) +} + +export const toExt = { + type: "lang", + filter: createIndentedFilter("^^to", (s, attrs) => { + let {url, ...params} = attrs; + let q = [] + for (const [k, v] of Object.entries(params)) { + q.push(`${k}=${encodeURIComponent(v)}`); + } + + if (q.length > 0) { + url = `${url}?${q.join("&")}`; + } + + return h("span.b-link", {attrs: {to: url}}, s).outerHTML; + }) } \ No newline at end of file diff --git a/src/router.ts b/src/router.ts index 8df5358..7af5f44 100644 --- a/src/router.ts +++ b/src/router.ts @@ -6,12 +6,25 @@ import About from "./views/About.vue"; Vue.use(Router); -export default new Router({ +const router = new Router({ mode: "history", routes: [ { path: '/', component: Search }, { path: '/tag/:tag', component: Search }, + { path: "/p", component: Single }, { path: "/p/:y/:mo/:name", component: Single }, { path: "/about", component: About } ] }); + +document.addEventListener("click", (ev) => { + const { target } = ev; + if (target instanceof HTMLSpanElement && target.matches(".b-link")) { + const to = target.getAttribute("to") + if (to) { + router.push(to); + } + } +}); + +export default router; diff --git a/src/views/Search.vue b/src/views/Search.vue index 43e5d69..c4019a8 100644 --- a/src/views/Search.vue +++ b/src/views/Search.vue @@ -15,9 +15,9 @@ import { Vue, Component, Prop, Watch } from "vue-property-decorator"; import Post from "@/layouts/Post.vue"; import Empty from "@/layouts/Empty.vue"; import First from "@/layouts/First.vue"; -import SearchParser from "@/plugins/search"; import { normalizeArray } from "@/util"; import { g } from "@/shared"; +import QParser from "q2filter"; @Component({ components: { @@ -39,12 +39,25 @@ export default class Search extends Vue { @Watch("g.q") @Watch("page") - @Watch("$route.params") + @Watch("$route.params.tag") private async updatePosts() { - const parser = new SearchParser(); - let ps = this.$route.params.tag - ? parser.filterPosts([this.g.q, `tag=${this.$route.params.tag}`].join(" ")) - : parser.filterPosts(this.g.q); + const parser = new QParser({ + isString: ["title", "author", "tag"], + isDate: ["date"], + filters: { + "is:reversed": (items) => { + return items.reverse(); + } + }, + sortBy: "date", + desc: true + }); + const q: string [] = [this.g.q]; + if (this.$route.params.tag) { + q.push(`tag=${this.$route.params.tag}`); + } + + let ps = parser.filter(POSTS, q.join(" ")); const { perPage } = g.config.posts; this.perPage = perPage; diff --git a/src/views/Single.vue b/src/views/Single.vue index ef49b19..98dda13 100644 --- a/src/views/Single.vue +++ b/src/views/Single.vue @@ -1,19 +1,19 @@ \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 3c208bd..f9cedf4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,7 +13,8 @@ "baseUrl": ".", "types": [ "webpack-env", - "mocha" + "mocha", + "node" ], "paths": { "@/*": [