This commit is contained in:
Pacharapol Withayasakpunt committed 2019-12-25 00:11:26 +07:00
1 parent 36338ed0d7
commit 7fe8f5d154
96 files changed
+21326

No files matched your search

+57
View File
@@ -0,0 +1,57 @@
@import "./spacing.scss";
p:not([class*="title"]) {
margin-bottom: 1em;
}
pre {
padding: 0;
}
.card {
border-radius: 4px;
}
iframe {
max-height: unset;
}
.noscroll {
overflow: -moz-scrollbars-none;
-ms-overflow-style: none;
&::-webkit-scrollbar {
width: 0 !important;
}
}
.blog-post {
word-break: break-word;
}
.hanzi-img {
width: 100px;
height: 100px;
text-align: center;
font-size: 80px;
margin-bottom: 10px;
font-family: serif;
}
.speak {
background: none !important;
border: none;
padding: 0 !important;
cursor: pointer;
font-family: arial, sans-serif;
color: blue;
text-decoration: underline;
}
details {
margin-bottom: 1em;
}
.d-inline-block {
display: inline-block;
}
+51
View File
@@ -0,0 +1,51 @@
declare global {
interface Window {
speak(s: string, lang?: string): void
}
}
export function speak (s: string, lang?: string) {
lang = lang || 'zh-CN'
if (process.client) {
let trueLang = ''
const voices = speechSynthesis.getVoices()
const [la1, la2] = lang.split(/-_/)
if (la2) {
const langRegex = new RegExp(`${la1}[-_]${la2}`, 'i')
const matchedLang = voices.filter((v) => langRegex.test(v.lang))
if (matchedLang.length > 0) {
trueLang = matchedLang.sort((v1, v2) => v1.localService
? -1 : v2.localService ? 1 : 0)[0].lang
}
}
if (!trueLang) {
const langRegex = new RegExp(`^${la1}`, 'i')
const matchedLang = voices.filter((v) => langRegex.test(v.lang))
if (matchedLang.length > 0) {
trueLang = matchedLang.sort((v1, v2) => v1.localService
? -1 : v2.localService ? 1 : 0)[0].lang
}
}
if (trueLang) {
const u = new SpeechSynthesisUtterance(s)
u.lang = trueLang
speechSynthesis.speak(u)
}
}
}
if (process.client) {
window.speak = speak
document.body.addEventListener('keydown', (evt) => {
if (evt.code === 'KeyS') {
const s = getSelection()
if (s) {
speak(s.toString())
}
}
})
}
+135
View File
@@ -0,0 +1,135 @@
import qs from 'querystring'
import showdown from 'showdown'
import { IHyperPugFilters } from 'hyperpug'
import h from 'hyperscript'
import { createIndentedFilter } from 'indented-filter'
// @ts-ignore
import scopeCss from 'scope-css'
import cheerio from 'cheerio'
export default class MakeHtml {
pugExtensions: IHyperPugFilters
mdExtensions: Record<string, showdown.ShowdownExtension>
mdConverter: showdown.Converter
constructor () {
this.pugExtensions = {}
this.mdExtensions = {
simpleTable: {
type: 'lang',
filter (text) {
const rowRegex = /(?:(?:^|\r?\n)(?:\| )?(?:(?:.* \| )+.+)*(?:.* \| )+.+(?: \|)?(?:$|\r?\n))+/m
text = text.replace(rowRegex, (p0) => {
return h('table.table', p0.trim().split('\n').map((pi) => {
pi = pi.trim().replace(/^|/, '').replace(/|$/, '')
return h('tr', pi.split(' | ').map(x => x.trim()).map((qi) => {
return h('td', qi)
}))
})).outerHTML
})
return text
},
},
pug: {
type: 'lang',
filter: (s) => {
return s.replace(/\/\/ nuxt\n```pug\n([^]+?)\n```/g, (p0: string, p1: string) => {
if (process.client) {
return require('hyperpug').compile({ filters: this.pugExtensions })(p1)
} else {
return ''
}
})
},
},
css: {
type: 'lang',
filter: (s) => {
return s.replace(/\/\/ nuxt\n```css\n([^]+?)\n```/g, (p0: string, p1: string) => {
return h('style', {
attrs: {
'data-content': p1,
},
}).outerHTML
})
},
},
to: {
type: 'lang',
filter: createIndentedFilter('^^to', (s, attrs) => {
return h('a', { href: attrs.url }, s).outerHTML
}),
},
spoiler: {
type: 'lang',
filter: createIndentedFilter('^^spoiler', (s, attrs) => {
return h('details', [
...(attrs.summary ? [
h('summary', attrs.summary),
] : []),
h('div', s),
]).outerHTML
}),
},
speak: {
type: 'lang',
filter: createIndentedFilter('^^speak', (s, attrs) => {
return h('button.speak', {
attrs: {
type: 'button',
onclick: `speak('${attrs.s || s}', ${attrs.lang || ''})`,
},
}, s).outerHTML
}),
},
slide: {
type: 'lang',
filter: createIndentedFilter('^^slide', (s, attrs) => {
return h('a', {
href: `https://patarapolw.github.io/reveal-md/reveal/?${qs.stringify({
q: (() => {
let q = s
if (attrs.github) {
q = `https://raw.githubusercontent.com/${attrs.github}/master/${s}`
}
return q
})(),
})}`,
}, s).outerHTML
}),
},
}
this.mdConverter = new showdown.Converter()
this.mdConverter.setFlavor('github')
this.pugExtensions.markdown = (s) => {
return this.mdConverter.makeHtml(s)
}
Object.entries(this.mdExtensions).forEach(([k, ext]) => this.mdConverter.addExtension(ext, k))
}
parse (s: string) {
const html = this.mdConverter.makeHtml(s)
return this.finalize(html)
}
mdConvert = this.parse
pugConvert (s: string) {
const html = process.client ? require('hyperpug').compile({ filters: this.pugExtensions })(s) : ''
return this.finalize(html)
}
finalize (s: string) {
const $ = cheerio.load(s)
$('style').each((_, el) => {
const $el = $(el)
const content = $el.attr('data-content')
if (content) {
$el.html(scopeCss(content, '.make-html-scoped'))
}
})
return $.html()
}
}
export const makeHtml = new MakeHtml()
+51
View File
@@ -0,0 +1,51 @@
$sizeUnit: rem;
$marginKey: "m";
$paddingKey: "p";
$separator: "-";
$sizes: (("0", 0), ("xxs", 0.125), ("xs", 0.25), ("sm", 0.5), ("md", 1), ("lg", 2), ("xl", 4), ("xxl", 8));
$positions: (("t", "top"), ("r", "right"), ("b", "bottom"), ("l", "left"), ("x", "x"), ("y", "y"), ("a", "around"));
@function sizeValue($key, $value) {
@return if($key == "0", 0, $value + $sizeUnit);
}
@each $size in $sizes {
$sizeKey: nth($size, 1);
$sizeValue: nth($size, 2);
.#{$marginKey}#{$separator}#{$sizeKey} {
margin: sizeValue($sizeKey, $sizeValue);
}
.#{$paddingKey}#{$separator}#{$sizeKey} {
padding: sizeValue($sizeKey, $sizeValue);
}
@each $position in $positions {
$posKey: nth($position, 1);
$posValue: nth($position, 2);
.#{$marginKey}#{$posKey}#{$separator}#{$sizeKey} {
@if $posValue == "x" {
margin-left: sizeValue($sizeKey, $sizeValue);
margin-right: sizeValue($sizeKey, $sizeValue);
} @else if $posValue == "y" {
margin-top: sizeValue($sizeKey, $sizeValue);
margin-bottom: sizeValue($sizeKey, $sizeValue);
} @else if $posValue == "around" {
margin: sizeValue($sizeKey, $sizeValue);
} @else {
margin-#{$posValue}: sizeValue($sizeKey, $sizeValue);
}
}
.#{$paddingKey}#{$posKey}#{$separator}#{$sizeKey} {
@if $posValue == "x" {
padding-left: sizeValue($sizeKey, $sizeValue);
padding-right: sizeValue($sizeKey, $sizeValue);
} @else if $posValue == "y" {
padding-top: sizeValue($sizeKey, $sizeValue);
padding-bottom: sizeValue($sizeKey, $sizeValue);
} @else if $posValue == "around" {
padding: sizeValue($sizeKey, $sizeValue);
} @else {
padding-#{$posValue}: sizeValue($sizeKey, $sizeValue);
}
}
}
}
+65
View File
@@ -0,0 +1,65 @@
import QParser from 'q2filter'
export function escapeRegExp (s: string) {
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') // $& means the whole matched string
}
export function normalizeArray<T> (it: T | T[]): T | undefined {
if (Array.isArray(it)) {
return it[0]
}
return it
}
export function searchPosts (options: {
q?: any
isBlog?: boolean
current?: boolean
posts: any[]
}) {
let posts: any[] = options.posts
if (options.isBlog) {
posts = posts.filter((p) => p.path.startsWith('blog/'))
}
if (options.current) {
const now = new Date().toISOString()
posts = posts.filter((h) => h.date && h.date <= now)
}
const parser = new QParser(options.q || {}, {
isString: ['title', 'author', 'tag'],
isDate: ['date'],
filters: {
'is:reversed': (items) => {
return items.reverse()
},
},
sortBy: {
key: 'date',
desc: true,
},
})
return parser.parse(posts)
}
export async function apiHash (u: string, h: string): Promise<string> {
if (typeof location !== 'undefined') {
const url = new URL(u, location.origin)
url.searchParams.set('h', h)
return fetch(url.href, {
mode: 'no-cors',
}).then((r) => r.text())
} else {
if (/\.json$/i.test(u)) {
const req = (require as any).context('@/static', true, /\.json$/i)
return JSON.stringify(req(`.${u}`))
} else {
const req = (require as any).context('@/static', true, /\.(html?|md|txt|ya?ml)$/i)
return req(`.${u}`).default
}
}
}