Skip to content

Commit

Permalink
Add ctzn-code to rich editor
Browse files Browse the repository at this point in the history
  • Loading branch information
pfrazee committed Apr 13, 2021
1 parent 61ade1a commit eb8f57d
Show file tree
Hide file tree
Showing 4 changed files with 197 additions and 28 deletions.
12 changes: 10 additions & 2 deletions static/js/com/rich-editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ import { LitElement, html } from '../../vendor/lit-element/lit-element.js'
import * as postView from '../ctzn-tags-editor/post-view.js'
import * as commentView from '../ctzn-tags-editor/comment-view.js'
import * as iframe from '../ctzn-tags-editor/iframe.js'
import * as code from '../ctzn-tags-editor/code.js'

const POST_TAGS = [
postView,
commentView,
iframe
iframe,
code
]

export class RichEditor extends LitElement {
Expand Down Expand Up @@ -38,10 +40,11 @@ export class RichEditor extends LitElement {
],
toolbar: 'undo redo | post-embeds | formatselect | ' +
'bold italic underline strikethrough | link | bullist numlist outdent indent | ' +
'table tabledelete | removeformat',
'ctzn-code | table tabledelete | removeformat',

custom_elements: POST_TAGS.map(t => t.name).join(','),
extended_valid_elements: POST_TAGS.map(t => t.validElements).filter(Boolean).join(','),
valid_children: 'ctzn-code[pre,#text]',

setup: (editor) => {
editor.on('PreInit', () => {
Expand All @@ -53,6 +56,11 @@ export class RichEditor extends LitElement {
editor.serializer.addNodeFilter(tag.name, contentEditableFilter)
}
})
editor.ui.registry.addButton('ctzn-code', {
icon: 'code-sample',
tooltip: 'Code snippet',
onAction: () => code.insert(editor)
})
editor.ui.registry.addMenuButton('post-embeds', {
icon: 'image',
tooltip: 'Insert media',
Expand Down
25 changes: 16 additions & 9 deletions static/js/ctzn-tags-editor/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,17 @@ export function createBaseClass (win, doc, editor) {
this.setAttribute('contenteditable', false)
this.attachShadow({mode: 'open'})

for (let attrName of this.constructor.observedAttributes) {
Object.defineProperty(this, attrName, {
get: () => {
return this.getAttribute(attrName)
},
set: (v) => {
this.setAttribute(attrName, v)
}
})
if (this.constructor.observedAttributes) {
for (let attrName of this.constructor.observedAttributes) {
Object.defineProperty(this, attrName, {
get: () => {
return this.getAttribute(attrName)
},
set: (v) => {
this.setAttribute(attrName, v)
}
})
}
}

this.updateDom()
Expand All @@ -27,6 +29,7 @@ export function createBaseClass (win, doc, editor) {

updateDom () {
render(this.render(), this.shadowRoot)
this.updated()
}

render () {
Expand All @@ -35,6 +38,10 @@ export function createBaseClass (win, doc, editor) {
`
}

updated () {
// optional override
}

attributeChangedCallback (name, oldValue, newValue) {
this.updateDom()
}
Expand Down
153 changes: 153 additions & 0 deletions static/js/ctzn-tags-editor/code.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import { html } from '../../vendor/lit-element/lit-html/lit-html.js'
import { repeat } from '../../vendor/lit-element/lit-html/directives/repeat.js'
import { createBaseClass } from './base.js'
import { makeSafe } from '../lib/strings.js'

const penSvg = html`<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="pen" class="svg-inline--fa fa-pen fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M290.74 93.24l128.02 128.02-277.99 277.99-114.14 12.6C11.35 513.54-1.56 500.62.14 485.34l12.7-114.22 277.9-277.88zm207.2-19.06l-60.11-60.11c-18.75-18.75-49.16-18.75-67.91 0l-56.55 56.55 128.02 128.02 56.55-56.55c18.75-18.76 18.75-49.16 0-67.91z"></path></svg>`

// exported api
// =

export const name = 'ctzn-code'

export function setup (win, doc, editor) {
class CtznCode extends createBaseClass(win) {
get codeLines () {
const nodes = this.shadowRoot.querySelector('slot')?.assignedNodes() || []
return nodes.map(n => n.textContent).join('\n').trim().split('\n')
}

render () {
return html`
<style>
:host {
display: block;
background: #fff;
border-radius: 6px;
border: 1px solid #ccc;
padding: 1rem;
box-shadow: 0 2px 4px #0001;
}
.btn {
display: inline-block;
cursor: pointer;
border-radius: 4px;
padding: 0 8px;
}
.btn:hover {
background: #eee;
}
.btn svg {
width: 12px;
color: #666;
}
slot {
display: none;
}
header {
padding-bottom: 8px;
}
.rendered-code {
overflow-x: scroll;
background-color: #F3F4F6; /*bg-gray-100*/
padding: 0.8rem;
border: 1px solid #D1D5DB /*border-gray-300*/;
border-radius: 5px;
counter-reset: line;
}
.rendered-code > :last-child {
margin-bottom: 0 !important;
}
.rendered-code > code {
display: block;
white-space: pre;
color: #1F2937 /*text-gray-800*/;
background-color: transparent;
}
.rendered-code > code::before {
display: inline-block;
text-align: right;
width: 2ch;
margin-right: 1ch;
color: #9CA3AF /*text-gray-400*/;
counter-increment: line;
content: counter(line);
}
</style>
<header>
<strong>Code snippet</strong>
<span class="btn" @click=${e => this.onClickEdit(e)}>${penSvg}</span>
</header>
<div class="rendered-code">
${repeat(this.codeLines, line => html`<code>${line}</code>`)}
</div>
<slot></slot>
`
}

onClickEdit (e) {
doPropertiesDialog(this, editor)
}
}
win.customElements.define('ctzn-code', CtznCode)
}

export function insert (editor) {
doPropertiesDialog(null, editor)
}

// internal methods
// =

function doPropertiesDialog (el, editor) {
editor.windowManager.open({
title: 'Code snippet',
size: 'large',
body: {
type: 'panel',
items: [
{
type: 'textarea',
name: 'code',
placeholder: 'Insert your code snippet here'
}
]
},
buttons: [
{
type: 'cancel',
name: 'closeButton',
text: 'Cancel'
},
{
type: 'submit',
name: 'submitButton',
text: 'Save',
primary: true
}
],
initialData: {
code: el ? el.textContent : ''
},
onSubmit: (dialog) => {
var data = dialog.getData()

if (!el) {
editor.insertContent(`<ctzn-code id="__new"><pre></pre></ctzn-code>`)
let newEl = editor.$('#__new')
newEl[0].querySelector('pre').textContent = data.code
newEl[0].updateDom()
editor.selection.select(newEl[0])
newEl.removeAttr('id')
}
else {
editor.undoManager.transact(() => {
el.querySelector('pre').textContent = data.code
})
el.updateDom()
editor.nodeChanged()
}
dialog.close()
}
})
}
35 changes: 18 additions & 17 deletions static/js/ctzn-tags/code.js
Original file line number Diff line number Diff line change
@@ -1,44 +1,44 @@
import { LitElement, html } from '../../vendor/lit-element/lit-element.js'
import { repeat } from '../../vendor/lit-element/lit-html/directives/repeat.js'

export class Code extends LitElement {
constructor () {
super()
this.setAttribute('ctzn-elem', '1')
this.classList.add('mb-1')
}

firstUpdated () {
this.style.counterReset = 'line'
this.classList.add('mb-1')
const rawLines = this.textContent.trim().split('\n')
this.innerHTML = ''
for (const rawLine of rawLines) {
const codeLine = document.createElement('code')
codeLine.textContent = rawLine
this.appendChild(codeLine)
}
get codeLines () {
return this.textContent.trim().split('\n')
}

render () {
return html`
<style>
div {
:host {
display: block;
overflow-x: scroll;
background-color: #F3F4F6; /*bg-gray-100*/
padding: 0.8rem;
border: 1px solid #D1D5DB /*border-gray-300*/;
border-radius: 5px;
}
::slotted(:last-child) {
slot {
display: none;
}
.rendered-code {
counter-reset: line;
}
.rendered-code > :last-child {
margin-bottom: 0 !important;
}
::slotted(code) {
.rendered-code > code {
display: block;
white-space: pre;
color: #1F2937 /*text-gray-800*/;
background-color: transparent;
font-size: 90% !important;
}
::slotted(code)::before {
.rendered-code > code::before {
display: inline-block;
text-align: right;
width: 2ch;
Expand All @@ -48,9 +48,10 @@ export class Code extends LitElement {
content: counter(line);
}
</style>
<div>
<slot></slot>
<div class="rendered-code">
${repeat(this.codeLines, line => html`<code>${line}</code>`)}
</div>
<slot></slot>
`
}
}
Expand Down

0 comments on commit eb8f57d

Please sign in to comment.