Public
Edited
Aug 7, 2023
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
editor
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
loadCodeMirrorModules = async entries => {
const repo = Object.create(null)
const loadToRepo = async key => {
const module = await esmCodeMirror(key)
repo[key] = module
}
await Promise.all(entries.map(loadToRepo))
return repo
}
Insert cell
cmImports = loadCodeMirrorModules([
'view', 'state', 'history', 'fold', 'language',
'gutter', 'commands', 'search', 'autocomplete', 'highlight',
])
Insert cell
EditorView = cmImports.view.EditorView
Insert cell
EditorState = cmImports.state.EditorState
Insert cell
Insert cell
CodeMirror = async (doc = '', config = {}) => {
const extensions = config.extensions ?? []
const keymaps = config.keymaps ?? []
const { highlightSpecialChars, keymap, drawSelection, highlightActiveLine } = cmImports.view
const { Prec } = cmImports.state
const state = EditorState.create({
doc,
extensions: [
cmReactiveViewof,
cmImports.gutter.lineNumbers(),
highlightSpecialChars(),
cmImports.history.history(),
drawSelection(),
Prec.fallback(cmImports.highlight.defaultHighlightStyle),
highlightActiveLine(),
keymap.of([
// ...closeBracketsKeymap,
...cmImports.commands.defaultKeymap,
...cmImports.search.searchKeymap,
...cmImports.history.historyKeymap,
// ...foldKeymap,
// ...commentKeymap,
// ...completionKeymap,
// ...lintKeymap,
...keymaps,
]),
...extensions,
],
})
const view = new EditorView({state})
const el = view.dom
el.value = view.state.doc.toString()
return el
}
Insert cell
Insert cell
cmReactiveViewof = {
// prevent input events coming from the contenteditable div that
// propagates and triggers a cell refresh; we shall handle it ourselves.
const blocker = cmImports.view.ViewPlugin.fromClass(class {
constructor(view) {
this.elContent = null
return true
}
update({view}) {
if (this.elContent) return;
const el = view.dom.querySelector('.cm-content')
if (el) {
this.elContent = el
this.elContent.addEventListener('input', this.handler)
}
}
destroy() {
if (!this.elContent) return;
this.elContent.removeEventListener('input', this.handler)
this.elContent = null
}
handler(evt) {
evt.stopImmediatePropagation()
}
})
const handler = EditorView.updateListener.of(update => {
const { dom } = update.view
if (!update.docChanged) return
dom.value = update.state.doc.toString()
dom.dispatchEvent(new CustomEvent('input'))
})
return [blocker, handler]
}
Insert cell
Insert cell
Insert cell
loadBasicSetup = () => esmCodeMirror('basic-setup').then(m => m.basicSetup)
Insert cell
Insert cell
cmOneDark = esmCodeMirror('theme-one-dark')
Insert cell
myDefaultTheme = EditorView.theme({
'&': {
fontFamily: 'Consolas, "Roboto Mono", monospace',
fontSize: '14px',
height: '200px',
border: '1px solid #ddd',
},
})
Insert cell
Insert cell
esmImport = pkg => import(`https://cdn.skypack.dev/${pkg}`)
Insert cell
esmCodeMirror = mod => esmImport('@codemirror/' + (mod.indexOf('@') >= 0 ? mod : `${mod}@${CODEMIRROR_VERSION}`))
Insert cell
Insert cell
MarkdownIt = require('markdown-it@12/dist/markdown-it.js')
Insert cell
mdit = MarkdownIt('commonmark').enable('strikethrough')
Insert cell
import { md as mdi } with { mdit } from '@andy0130tw/my-markdown-it'
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more