email editor
This commit is contained in:
@@ -37,6 +37,7 @@ import AppTour from './elements/app_tour'
|
||||
import DashboardDropzone from './elements/dashboard_dropzone'
|
||||
import RequiredCheckboxGroup from './elements/required_checkbox_group'
|
||||
import PageContainer from './elements/page_container'
|
||||
import EmailEditor from './elements/email_editor'
|
||||
|
||||
import * as TurboInstantClick from './lib/turbo_instant_click'
|
||||
|
||||
@@ -109,6 +110,7 @@ safeRegisterElement('dashboard-dropzone', DashboardDropzone)
|
||||
safeRegisterElement('check-on-click', CheckOnClick)
|
||||
safeRegisterElement('required-checkbox-group', RequiredCheckboxGroup)
|
||||
safeRegisterElement('page-container', PageContainer)
|
||||
safeRegisterElement('email-editor', EmailEditor)
|
||||
|
||||
safeRegisterElement('template-builder', class extends HTMLElement {
|
||||
connectedCallback () {
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
import { target, targetable } from '@github/catalyst/lib/targetable'
|
||||
|
||||
let loaderPromise = null
|
||||
|
||||
function loadCodeMirror () {
|
||||
if (!loaderPromise) {
|
||||
loaderPromise = Promise.all([
|
||||
import(/* webpackChunkName: "email-editor" */ '@codemirror/view'),
|
||||
import(/* webpackChunkName: "email-editor" */ '@codemirror/commands'),
|
||||
import(/* webpackChunkName: "email-editor" */ '@codemirror/language'),
|
||||
import(/* webpackChunkName: "email-editor" */ '@codemirror/lang-html'),
|
||||
import(/* webpackChunkName: "email-editor" */ '@specious/htmlflow')
|
||||
]).then(([view, commands, language, html, htmlflow]) => {
|
||||
return {
|
||||
minimalSetup: [
|
||||
commands.history(),
|
||||
language.syntaxHighlighting(language.defaultHighlightStyle, { fallback: true }),
|
||||
view.keymap.of([...commands.defaultKeymap, ...commands.historyKeymap])
|
||||
],
|
||||
EditorView: view.EditorView,
|
||||
html: html.html,
|
||||
htmlflow: htmlflow.default || htmlflow
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return loaderPromise
|
||||
}
|
||||
|
||||
export default targetable(class extends HTMLElement {
|
||||
static [target.static] = [
|
||||
'codeViewTab',
|
||||
'previewViewTab',
|
||||
'editorContainer',
|
||||
'previewIframe'
|
||||
]
|
||||
|
||||
connectedCallback () {
|
||||
this.mount()
|
||||
|
||||
if (this.input.value) {
|
||||
this.showPreviewView()
|
||||
} else {
|
||||
this.showCodeView()
|
||||
}
|
||||
|
||||
this.previewViewTab.addEventListener('click', this.showPreviewView)
|
||||
this.codeViewTab.addEventListener('click', this.showCodeView)
|
||||
}
|
||||
|
||||
showCodeView = () => {
|
||||
this.editorView.dispatch({
|
||||
changes: { from: 0, to: this.editorView.state.doc.length, insert: this.input.value }
|
||||
})
|
||||
|
||||
this.previewViewTab.classList.remove('tab-active', 'tab-bordered')
|
||||
this.previewViewTab.classList.add('pb-[3px]')
|
||||
this.codeViewTab.classList.remove('pb-[3px]')
|
||||
this.codeViewTab.classList.add('tab-active', 'tab-bordered')
|
||||
this.editorContainer.classList.remove('hidden')
|
||||
this.previewIframe.classList.add('hidden')
|
||||
}
|
||||
|
||||
showPreviewView = () => {
|
||||
this.previewIframe.srcdoc = this.input.value
|
||||
|
||||
this.codeViewTab.classList.remove('tab-active', 'tab-bordered')
|
||||
this.codeViewTab.classList.add('pb-[3px]')
|
||||
this.previewViewTab.classList.remove('pb-[3px]')
|
||||
this.previewViewTab.classList.add('tab-active', 'tab-bordered')
|
||||
this.editorContainer.classList.add('hidden')
|
||||
this.previewIframe.classList.remove('hidden')
|
||||
}
|
||||
|
||||
async mount () {
|
||||
this.input = this.querySelector('input[type="hidden"]')
|
||||
this.input.style.display = 'none'
|
||||
|
||||
const { EditorView, minimalSetup, html, htmlflow } = await loadCodeMirror()
|
||||
|
||||
this.editorView = new EditorView({
|
||||
doc: this.input.value,
|
||||
parent: this.editorContainer,
|
||||
extensions: [
|
||||
html(),
|
||||
minimalSetup,
|
||||
EditorView.lineWrapping,
|
||||
EditorView.updateListener.of(update => {
|
||||
if (update.docChanged) this.input.value = update.state.doc.toString()
|
||||
}),
|
||||
EditorView.theme({
|
||||
'&': {
|
||||
backgroundColor: 'white',
|
||||
color: 'black',
|
||||
fontSize: '14px',
|
||||
fontFamily: 'monospace'
|
||||
},
|
||||
'&.cm-focused': {
|
||||
outline: 'none'
|
||||
},
|
||||
'&.cm-editor': {
|
||||
borderRadius: '0.375rem',
|
||||
border: 'none'
|
||||
},
|
||||
'.cm-gutters': {
|
||||
display: 'none'
|
||||
}
|
||||
})
|
||||
]
|
||||
})
|
||||
|
||||
this.previewIframe.srcdoc = this.editorView.state.doc.toString()
|
||||
|
||||
this.previewIframe.onload = () => {
|
||||
const previewIframeDoc = this.previewIframe.contentDocument
|
||||
|
||||
if (previewIframeDoc.body) {
|
||||
previewIframeDoc.body.contentEditable = true
|
||||
}
|
||||
|
||||
const contentDocument = this.previewIframe.contentDocument || this.previewIframe.contentWindow.document
|
||||
|
||||
contentDocument.body.addEventListener('input', async () => {
|
||||
const html = contentDocument.documentElement.outerHTML.replace(' contenteditable="true"', '')
|
||||
const prettifiedHtml = await htmlflow(html)
|
||||
|
||||
this.input.value = prettifiedHtml
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user