Created
May 22, 2019 17:56
-
-
Save philippkuehn/32fa6e0a08b47ac7225d7d1c65e8cc7e to your computer and use it in GitHub Desktop.
codeblock extension for tiptap using on scrumpy.io
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<template> | |
<div class="c-code-block"> | |
<div class="c-code-block__meta" contenteditable="false" v-if="editable"> | |
<icon-btn | |
class="c-code-block__action-icon" | |
name="more" | |
:dropdown-props="{ closeOnClick: false }" | |
ref="icon" | |
> | |
<template v-slot:dropdown> | |
<actions-list> | |
<actions-group> | |
<input | |
class="c-code-block__search" | |
type="text" | |
autofocus | |
v-model="query" | |
placeholder="Search …" | |
ref="search" | |
/> | |
</actions-group> | |
<actions-group class="c-code-block__list"> | |
<actions-item | |
v-for="language in filteredLanguages" | |
:key="language.value" | |
:label="language.label" | |
@click.native="setLanguage(language.value)" | |
/> | |
</actions-group> | |
</actions-list> | |
</template> | |
</icon-btn> | |
</div> | |
<pre | |
class="c-code-block__pre" | |
:data-lang="node.attrs.language" | |
spellcheck="false" | |
><code class="c-code-block__code" ref="content"></code></pre> | |
</div> | |
</template> | |
<script> | |
import Fuse from 'fuse.js' | |
import IconBtn from 'Components/IconBtn' | |
import { ActionsList, ActionsGroup, ActionsItem } from 'Components/Actions' | |
export default { | |
components: { | |
IconBtn, | |
ActionsList, | |
ActionsGroup, | |
ActionsItem, | |
}, | |
props: ['node', 'updateAttrs', 'editable', 'options'], | |
data() { | |
return { | |
query: null, | |
} | |
}, | |
computed: { | |
filteredLanguages() { | |
if (!this.query) { | |
return this.options.languages | |
} | |
const fuse = new Fuse(this.options.languages, { | |
threshold: 0.2, | |
keys: [ | |
'value', | |
], | |
}) | |
return fuse.search(this.query) | |
}, | |
}, | |
methods: { | |
setLanguage(language) { | |
this.updateAttrs({ | |
language, | |
}) | |
this.$refs.icon.$refs.wrapper.$emit('close') | |
this.query = null | |
if (this.$refs.search) { | |
this.$refs.search.blur() | |
} | |
}, | |
}, | |
} | |
</script> | |
<style lang="scss" src="./style.scss"></style> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Plugin, PluginKey } from 'tiptap' | |
import { Decoration, DecorationSet } from 'prosemirror-view' | |
import { findBlockNodes } from 'prosemirror-utils' | |
import low from 'lowlight/lib/core' | |
function getDecorations({ doc, name }) { | |
const decorations = [] | |
const blocks = findBlockNodes(doc).filter(item => item.node.type.name === name) | |
const flatten = list => list.reduce( | |
(a, b) => a.concat(Array.isArray(b) ? flatten(b) : b), [], | |
) | |
function parseNodes(nodes, className = []) { | |
return nodes.map(node => { | |
const classes = [ | |
...className, | |
...node.properties ? node.properties.className : [], | |
] | |
if (node.children) { | |
return parseNodes(node.children, classes) | |
} | |
return { | |
text: node.value, | |
classes, | |
} | |
}) | |
} | |
blocks.forEach(block => { | |
const { language } = block.node.attrs | |
let startPos = block.pos + 1 | |
let nodes | |
try { | |
nodes = language | |
? low.highlight(language, block.node.textContent).value | |
: low.highlightAuto(block.node.textContent).value | |
} catch (error) { | |
if (error && /Unknown language/.test(error.message)) { | |
return | |
} | |
throw error | |
} | |
flatten(parseNodes(nodes)) | |
.map(node => { | |
const from = startPos | |
const to = from + node.text.length | |
startPos = to | |
return { | |
...node, | |
from, | |
to, | |
} | |
}) | |
.forEach(node => { | |
const decoration = Decoration.inline(node.from, node.to, { | |
class: node.classes.join(' '), | |
}) | |
decorations.push(decoration) | |
}) | |
}) | |
return DecorationSet.create(doc, decorations) | |
} | |
export default function HighlightPlugin({ name }) { | |
return new Plugin({ | |
name: new PluginKey('highlight'), | |
state: { | |
init: (_, { doc }) => getDecorations({ doc, name }), | |
apply: (transaction, decorationSet, oldState, state) => { | |
// TODO: find way to cache decorations | |
// see: https://discuss.prosemirror.net/t/how-to-update-multiple-inline-decorations-on-node-change/1493 | |
const nodeName = state.selection.$head.parent.type.name | |
const previousNodeName = oldState.selection.$head.parent.type.name | |
if (transaction.docChanged && [nodeName, previousNodeName].includes(name)) { | |
return getDecorations({ doc: transaction.doc, name }) | |
} | |
return decorationSet.map(transaction.mapping, transaction.doc) | |
}, | |
}, | |
props: { | |
decorations(state) { | |
return this.getState(state) | |
}, | |
}, | |
}) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import low from 'lowlight/lib/core' | |
import { Node } from 'tiptap' | |
import { toggleBlockType, setBlockType, textblockTypeInputRule } from 'tiptap-commands' | |
import languages from './languages' | |
import Component from './Component' | |
import HighlightPlugin from './HighlightPlugin' | |
languages.forEach(language => { | |
if (language.value && language.config) { | |
low.registerLanguage(language.value, language.config) | |
} | |
}) | |
export default class CodeBlockHighlight extends Node { | |
get name() { | |
return 'code_block' | |
} | |
get view() { | |
return Component | |
} | |
get defaultOptions() { | |
return { | |
languages, | |
} | |
} | |
get schema() { | |
return { | |
attrs: { | |
language: { | |
default: null, | |
}, | |
}, | |
content: 'text*', | |
marks: '', | |
group: 'block', | |
code: true, | |
defining: true, | |
draggable: false, | |
parseDOM: [ | |
{ tag: 'pre', preserveWhitespace: 'full' }, | |
], | |
toDOM: () => ['pre', ['code', 0]], | |
} | |
} | |
commands({ type, schema }) { | |
return () => toggleBlockType(type, schema.nodes.paragraph) | |
} | |
keys({ type }) { | |
return { | |
'Shift-Ctrl-\\': setBlockType(type), | |
} | |
} | |
inputRules({ type }) { | |
return [ | |
textblockTypeInputRule(/^```$/, type), | |
] | |
} | |
get plugins() { | |
return [ | |
HighlightPlugin({ name: this.name }), | |
] | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import apache from 'highlight.js/lib/languages/apache' | |
import bash from 'highlight.js/lib/languages/bash' | |
import cpp from 'highlight.js/lib/languages/cpp' | |
import cs from 'highlight.js/lib/languages/cs' | |
import css from 'highlight.js/lib/languages/css' | |
import diff from 'highlight.js/lib/languages/diff' | |
import dockerfile from 'highlight.js/lib/languages/dockerfile' | |
import http from 'highlight.js/lib/languages/http' | |
import ini from 'highlight.js/lib/languages/ini' | |
import go from 'highlight.js/lib/languages/go' | |
import less from 'highlight.js/lib/languages/less' | |
import java from 'highlight.js/lib/languages/java' | |
import javascript from 'highlight.js/lib/languages/javascript' | |
import json from 'highlight.js/lib/languages/json' | |
import makefile from 'highlight.js/lib/languages/makefile' | |
import markdown from 'highlight.js/lib/languages/markdown' | |
import nginx from 'highlight.js/lib/languages/nginx' | |
import objectivec from 'highlight.js/lib/languages/objectivec' | |
import perl from 'highlight.js/lib/languages/perl' | |
import php from 'highlight.js/lib/languages/php' | |
import plaintext from 'highlight.js/lib/languages/plaintext' | |
import properties from 'highlight.js/lib/languages/properties' | |
import python from 'highlight.js/lib/languages/python' | |
import ruby from 'highlight.js/lib/languages/ruby' | |
import scss from 'highlight.js/lib/languages/scss' | |
import shell from 'highlight.js/lib/languages/shell' | |
import sql from 'highlight.js/lib/languages/sql' | |
import typescript from 'highlight.js/lib/languages/typescript' | |
import xml from 'highlight.js/lib/languages/xml' | |
import yaml from 'highlight.js/lib/languages/yaml' | |
export default [ | |
{ | |
label: 'Auto Detection', | |
value: null, | |
config: null, | |
}, | |
{ | |
label: 'Plain Text', | |
value: 'plaintext', | |
config: plaintext, | |
}, | |
{ | |
label: 'Apache', | |
value: 'apache', | |
config: apache, | |
}, | |
{ | |
label: 'Bash', | |
value: 'bash', | |
config: bash, | |
}, | |
{ | |
label: 'C++', | |
value: 'cpp', | |
config: cpp, | |
}, | |
{ | |
label: 'C#+', | |
value: 'cs', | |
config: cs, | |
}, | |
{ | |
label: 'CSS', | |
value: 'css', | |
config: css, | |
}, | |
{ | |
label: 'Diff', | |
value: 'diff', | |
config: diff, | |
}, | |
{ | |
label: 'Dockerfile', | |
value: 'dockerfile', | |
config: dockerfile, | |
}, | |
{ | |
label: 'HTTP', | |
value: 'http', | |
config: http, | |
}, | |
{ | |
label: 'Ini, TOML', | |
value: 'ini', | |
config: ini, | |
}, | |
{ | |
label: 'Go', | |
value: 'go', | |
config: go, | |
}, | |
{ | |
label: 'Less', | |
value: 'less', | |
config: less, | |
}, | |
{ | |
label: 'Java', | |
value: 'java', | |
config: java, | |
}, | |
{ | |
label: 'JavaScript', | |
value: 'javascript', | |
config: javascript, | |
}, | |
{ | |
label: 'JSON', | |
value: 'json', | |
config: json, | |
}, | |
{ | |
label: 'Makefile', | |
value: 'makefile', | |
config: makefile, | |
}, | |
{ | |
label: 'Markdown', | |
value: 'markdown', | |
config: markdown, | |
}, | |
{ | |
label: 'Nginx', | |
value: 'nginx', | |
config: nginx, | |
}, | |
{ | |
label: 'Objective-C', | |
value: 'objectivec', | |
config: objectivec, | |
}, | |
{ | |
label: 'Perl', | |
value: 'perl', | |
config: perl, | |
}, | |
{ | |
label: 'PHP', | |
value: 'php', | |
config: php, | |
}, | |
{ | |
label: 'Properties', | |
value: 'properties', | |
config: properties, | |
}, | |
{ | |
label: 'Python', | |
value: 'python', | |
config: python, | |
}, | |
{ | |
label: 'Ruby', | |
value: 'ruby', | |
config: ruby, | |
}, | |
{ | |
label: 'SCSS', | |
value: 'scss', | |
config: scss, | |
}, | |
{ | |
label: 'Shell', | |
value: 'shell', | |
config: shell, | |
}, | |
{ | |
label: 'SQL', | |
value: 'sql', | |
config: sql, | |
}, | |
{ | |
label: 'TypeScript', | |
value: 'typescript', | |
config: typescript, | |
}, | |
{ | |
label: 'HTML, XML', | |
value: 'xml', | |
config: xml, | |
}, | |
{ | |
label: 'YAML', | |
value: 'yaml', | |
config: yaml, | |
}, | |
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@import '~settings'; | |
@import '~utilityFunctions'; | |
.c-code-block { | |
position: relative; | |
white-space: normal; | |
&__meta { | |
position: absolute; | |
top: 0.25rem; | |
right: 0; | |
} | |
&__pre { | |
} | |
&__code { | |
display: block; | |
white-space: pre; | |
} | |
.is-desktop &__action-icon { | |
opacity: 0; | |
} | |
.is-desktop &:hover &__action-icon, | |
.is-desktop .tippy-active &__action-icon { | |
opacity: 1; | |
} | |
&__search { | |
background-color: color(grey, transparent); | |
border-radius: 6px; | |
border: none; | |
padding: 0.25rem 0.5rem; | |
} | |
&__list { | |
max-height: 15rem; | |
overflow: auto; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment