Skip to content

Commit

Permalink
Merge pull request #1 from kairlec/dev
Browse files Browse the repository at this point in the history
release v1.0.0
  • Loading branch information
kairlec authored Feb 28, 2021
2 parents 2ddff2e + 5a3c309 commit 33385e0
Show file tree
Hide file tree
Showing 12 changed files with 1,141 additions and 0 deletions.
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.DS_Store
Thumbs.db
db.json
*.log
node_modules/
public/
.deploy*/
test.js
11 changes: 11 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const { initPreConfig, isPluginDisabled, rendererFilterFunction, makeBodyEndInjectContent, makeHeadEndInjectContent } = require('./lib/index')

initPreConfig(hexo.config.kotlin_playground || {})

if (!isPluginDisabled()) {
hexo.extend.injector.register('head_end', makeHeadEndInjectContent(), 'default')

hexo.extend.injector.register('body_end', makeBodyEndInjectContent(), 'default')

hexo.extend.filter.register('marked:renderer', rendererFilterFunction)
}
43 changes: 43 additions & 0 deletions lib/SpecialKeyValue.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
const specialKeyValueMap = new Map()

class SpecialKeyValue {
constructor (targetKey, setFunction = function (value) { return value }, getFunction = function (value) { return value }) {
this.targetKey = targetKey
this.setFunction = setFunction
this.getFunction = getFunction
}
}

function registerSpecialKeyValue (srcKey, targetKey, setFunction, getFunction) {
specialKeyValueMap.set(srcKey, new SpecialKeyValue(targetKey, setFunction, getFunction))
}

function getRealKey (key) {
if (specialKeyValueMap.has(key)) {
return getRealKey(specialKeyValueMap.get(key).targetKey)
}
return key
}

function getSpecialKeyValue (key, getValueFunction, applyTarget) {
if (specialKeyValueMap.has(key)) {
const special = specialKeyValueMap.get(key)
return special.getFunction(getSpecialKeyValue(special.targetKey, getValueFunction, applyTarget))
} else {
return getValueFunction.apply(applyTarget, [key])
}
}

function setSpecialKeyValue (key, value, setValueFunction, applyTarget) {
if (specialKeyValueMap.has(key)) {
const special = specialKeyValueMap.get(key)
setSpecialKeyValue(special.targetKey, special.setFunction(value), setValueFunction, applyTarget)
} else {
setValueFunction.apply(applyTarget, [key, value])
}
}

exports.registerSpecialKeyValue = registerSpecialKeyValue
exports.getSpecialKeyValue = getSpecialKeyValue
exports.setSpecialKeyValue = setSpecialKeyValue
exports.getRealKey = getRealKey
150 changes: 150 additions & 0 deletions lib/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
const { toHorizontalLine, deepCopy } = require('./util')
const { registerSpecialKeyValue, getSpecialKeyValue, setSpecialKeyValue, getRealKey } = require('./SpecialKeyValue')

function makeSpecialConfigKey (srcKey, targetKey, setFunction, getFunction) {
if (getFunction === undefined) {
if (typeof targetKey === 'function') {
registerSpecialKeyValue(toHorizontalLine(srcKey), srcKey, targetKey, setFunction)
} else if (typeof targetKey === 'undefined') {
registerSpecialKeyValue(toHorizontalLine(srcKey), srcKey, setFunction, getFunction)
} else {
registerSpecialKeyValue(toHorizontalLine(srcKey), targetKey, setFunction, getFunction)
}
} else {
registerSpecialKeyValue(toHorizontalLine(srcKey), toHorizontalLine(targetKey), setFunction, getFunction)
}
}

function parseAttribute (str, pre) {
const config = new Config(pre)
while (true) {
// eslint-disable-next-line
let result = /([a-zA-Z_:][-a-zA-Z0-9_:.]*)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/.exec(str)
if (result === null) {
break
}
config.set(result[1], result[2] || result[3] || result[4])
str = str.slice(result.index + result[0].length)
}
return config
}

function getCodeBlockConfig (sourceCode, pre) {
// 获取第一行注释
const firstLineIndex = sourceCode.indexOf('\n')
if (firstLineIndex === -1) {
return { config: new Config(pre), code: sourceCode }
}
const firstLine = sourceCode.slice(0, firstLineIndex)
const comment = /^\s*\/{2,}@playground *(.*)$/m.exec(firstLine)
if (comment !== null && comment[1].length > 0) {
return { config: parseAttribute(comment[1], pre), code: sourceCode.slice(firstLineIndex + 1) }
} else {
return { config: new Config(pre), code: sourceCode }
}
}

function __getRealKey (key) {
return getRealKey(toHorizontalLine(key))
}

function Config (obj) {
if (obj instanceof Config) {
this.enabled = obj.enabled
this.config = deepCopy(obj.config)
} else {
this.config = {}
if (obj) {
for (const key in obj) {
this.set(__getRealKey(key), obj[key])
}
this.enabled = !!obj.enabled
} else {
this.enabled = true
}
}

this.__set = function (key, value) {
if (key === 'enabled') {
if (typeof value === 'string') {
this.enable(value.toLowerCase() !== 'false')
} else {
this.enable(value)
}
} else {
this.config[key] = value
}
}

this.__get = function (key) {
return this.config[key]
}

this.set = function (key, ...value) {
if (typeof key !== 'string') {
throw TypeError('key type must be string')
}
let realValue
for (const v of value) {
if (v !== undefined) {
realValue = v
break
}
}
setSpecialKeyValue(key, realValue, this.__set, this)
}

this.has = function (key) {
return __getRealKey(key) in this.config
}

this.remove = function (key) {
delete this.config[__getRealKey(key)]
}

this.get = function (key, defaultValue) {
if (typeof key !== 'string') {
throw TypeError('key type must be string')
}
const value = getSpecialKeyValue(key, this.__get, this)
if (value === undefined) {
return defaultValue
} else {
return value
}
}

this.enable = function (enabled) {
if (enabled === undefined) {
this.enabled = true
} else {
this.enabled = !!enabled
}
}

this.disable = function (disabled) {
if (disabled === undefined) {
this.enabled = false
} else {
this.enabled = !disabled
}
}

this.toString = function (ifNull = (item) => ` ${item}`) {
let str = ''
for (const item in this.config) {
if (this.config[item] === null) {
str += ifNull(item)
} else if (this.config[item] === undefined) {
str += ` ${item}`
} else {
str += ` ${item}="${this.config[item]}"`
}
}
return str
}
}

exports.getCodeBlockConfig = getCodeBlockConfig
exports.makeSpecialConfigKey = makeSpecialConfigKey
exports.Config = Config
147 changes: 147 additions & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
const { highlight, escapeHTML, prismHighlight } = require('hexo-util')
const stripIndent = require('strip-indent')
const fs = require('fs')
const { Config, getCodeBlockConfig, makeSpecialConfigKey } = require('./config')
const { parseHide } = require('./parse')

makeSpecialConfigKey('line-numbers', 'lines')
makeSpecialConfigKey('lines-numbers', 'lines')
makeSpecialConfigKey('line', 'lines')
makeSpecialConfigKey('gutter', 'lines')
makeSpecialConfigKey('disable-all', 'enable-all', value => !JSON.parse(value || 'true'), value => !value)

makeSpecialConfigKey('min-version', 'data-min-compiler-version')
makeSpecialConfigKey('autocomplete', 'data-autocomplete')
makeSpecialConfigKey('auto-complete', 'data-autocomplete')
makeSpecialConfigKey('readonly', 'data-highlight-only')
makeSpecialConfigKey('read-only', 'data-highlight-only')
makeSpecialConfigKey('runable', 'data-highlight-only', value => !JSON.parse(value || 'true'), value => !value)
makeSpecialConfigKey('platform', 'data-target-platform')
makeSpecialConfigKey('version', 'data-version')
makeSpecialConfigKey('enable', 'enabled')
makeSpecialConfigKey('disable', 'enabled', value => !JSON.parse(value || 'true'), value => !value)
makeSpecialConfigKey('disabled', 'enabled', value => !JSON.parse(value || 'true'), value => !value)
makeSpecialConfigKey('auto-check', 'highlight-on-fly')
makeSpecialConfigKey('brackets', 'match-brackets')

const preCodeBlockConfig = new Config()
const preConfig = new Config()
const scriptIntroduceConfig = new Config()

function initPreConfig (kotlinPlaygroundConfig) {
preConfig.set('html-tag', kotlinPlaygroundConfig.html_tag, 'code')
preConfig.set('html-tag-class', kotlinPlaygroundConfig.html_tag_class, 'kotlin-code')
preConfig.set('other-highlight', kotlinPlaygroundConfig.other_highlight, 'highlight')
preConfig.set('other-highlight-config', kotlinPlaygroundConfig.other_highlight_config, {})
preConfig.set('css', kotlinPlaygroundConfig.css, {})
preConfig.set('tab', kotlinPlaygroundConfig.tab, ' ')
preConfig.set('ext', kotlinPlaygroundConfig.extend, {})
preConfig.set('disabled-plugin', kotlinPlaygroundConfig.disable_plugin, false)
preConfig.set('data-selector', kotlinPlaygroundConfig.data_selector, '.kotlin-code')
scriptIntroduceConfig.set('src', kotlinPlaygroundConfig.src, 'https://unpkg.com/kotlin-playground@1')
scriptIntroduceConfig.set('data-server', kotlinPlaygroundConfig.data_server, null)
scriptIntroduceConfig.set('data-version', kotlinPlaygroundConfig.data_version, null)
if (kotlinPlaygroundConfig.custom_pre) {
for (const key in kotlinPlaygroundConfig.custom_pre) {
preConfig.set(key, kotlinPlaygroundConfig.custom_pre[key])
}
}

preCodeBlockConfig.set('lines', kotlinPlaygroundConfig.line_numbers, true)
preCodeBlockConfig.set('auto-indent', kotlinPlaygroundConfig.auto_indent, true)
preCodeBlockConfig.set('indent', kotlinPlaygroundConfig.indent, 4)
preCodeBlockConfig.set('theme', kotlinPlaygroundConfig.theme, 'default')
preCodeBlockConfig.enable(kotlinPlaygroundConfig.enable_all)
if (kotlinPlaygroundConfig.custom_cb_pre) {
for (const key in kotlinPlaygroundConfig.custom_cb_pre) {
preCodeBlockConfig.set(key, kotlinPlaygroundConfig.custom_cb_pre[key])
}
}
}

function isPluginDisabled () {
return preConfig.get('disabled-plugin')
}

function tryHighLightKotlin (wrapCode) {
const { config, code } = getCodeBlockConfig(wrapCode.sourceCode, preCodeBlockConfig)
if (config.enabled) {
const tag = preConfig.get('html-tag')
const tagClass = preConfig.get('html-tag-class')
const _code = parseHide(code, escapeHTML, hidden => `<textarea class="hidden-dependency">${hidden}</textarea>`)
return `<pre class="language-kotlin"><${tag} class="${tagClass}"${config.toString()}>${_code}</${tag}></pre>`
} else {
wrapCode.sourceCode = code
return null
}
}

function makeHeadEndInjectContent () {
// 统一highlight.js,playground,prismjs的样式
return `<style type="text/css">
.CodeMirror-lines,.line,.code-output,.code,.token {
font-size: ${preConfig.get('css').font_size || '16px'};
line-height: ${preConfig.get('css').line_height || '22px'};
}
</style>`
}

function makeBodyEndInjectContent () {
const dataSelector = preConfig.get('data-selector')
let jsCode = ''
const ext = preConfig.get('ext')
if (ext.js && fs.existsSync(ext.js)) {
jsCode = fs.readFileSync(ext.js, { encoding: 'utf-8' })
}

return `<script${scriptIntroduceConfig.toString(item => '')}></script>
<script>
${jsCode}
if(typeof ktpgOptions !== 'undefined'){
KotlinPlayground('${dataSelector}',ktpgOptions)
}else{
KotlinPlayground('${dataSelector}')
}
window.addEventListener('pjax:complete', event => {
if(typeof ktpgOptions !== 'undefined'){
KotlinPlayground('${dataSelector}',ktpgOptions)
}else{
KotlinPlayground('${dataSelector}')
}
});
</script>`
}

function rendererFilterFunction (renderer) {
renderer.__code = renderer.code
renderer.code = (sourceCode, language) => {
const wrapCode = { sourceCode: stripIndent(sourceCode).replace(/\t/mg, preConfig.tab) }
if (language.toLowerCase() === 'kotlin') {
const data = tryHighLightKotlin(wrapCode)
if (!data) {
return otherHighLightCode(wrapCode.sourceCode, language, () => renderer.__code(wrapCode.sourceCode, language))
}
return `<figure class="highlight kotlin">${data}</figure>`
}
return otherHighLightCode(wrapCode.sourceCode, language, () => renderer.__code(wrapCode.sourceCode, language))
}
}

function otherHighLightCode (code, _lang, els) {
const line = preCodeBlockConfig.get('lines')
const tab = preCodeBlockConfig.get('tab')
const engine = preConfig.get('other-highlight')
const engineConfig = preConfig.get('other-highlight-config')
if (engine === 'highlight') {
return highlight(code, { hljs: engineConfig.hljs, gutter: line, tab: tab, lang: _lang, ...engineConfig })
} else if (engine === 'prismjs') {
return prismHighlight(code, { lineNumber: line, tab: tab, lang: _lang, ...engineConfig })
} else {
return els()
}
}
exports.initPreConfig = initPreConfig
exports.isPluginDisabled = isPluginDisabled
exports.rendererFilterFunction = rendererFilterFunction
exports.makeBodyEndInjectContent = makeBodyEndInjectContent
exports.makeHeadEndInjectContent = makeHeadEndInjectContent
11 changes: 11 additions & 0 deletions lib/parse.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
function parseHide (code, preHandler, hiddenHandler) {
const hideStart = /[^\n]*^\/{2,}\s*@hidestart[^\n]*$\n?/mg.exec(code)
const hideEnd = /[^\n]*^\/{2,}\s*@hideend[^\n]*$\n?/mg.exec(code)
if (hideStart && hideEnd) {
return preHandler(code.substr(0, hideStart.index - 1)) + hiddenHandler(preHandler(code.slice(hideStart.index + hideStart[0].length, hideEnd.index - 1))) + preHandler(code.slice(hideEnd.index + hideEnd[0].length))
} else {
return preHandler(code)
}
}

exports.parseHide = parseHide
Loading

0 comments on commit 33385e0

Please sign in to comment.