// based on basicSetup https://github.com/codemirror/basic-setup/blob/main/src/codemirror.ts const editorSrc = `import {keymap, highlightSpecialChars, drawSelection, highlightActiveLine, dropCursor, rectangularSelection, crosshairCursor, lineNumbers, highlightActiveLineGutter} from '@codemirror/view' import {EditorState} from '@codemirror/state' import {defaultHighlightStyle, syntaxHighlighting, indentOnInput, bracketMatching, foldGutter, foldKeymap} from '@codemirror/language' import {defaultKeymap, history, historyKeymap} from '@codemirror/commands' import {searchKeymap, highlightSelectionMatches} from '@codemirror/search' import {autocompletion, completionKeymap, closeBrackets, closeBracketsKeymap} from '@codemirror/autocomplete' import {lintKeymap} from '@codemirror/lint' /* */export const basicSetup = (() => [ lineNumbers(), highlightActiveLineGutter(), highlightSpecialChars(), history(), foldGutter(), drawSelection(), dropCursor(), EditorState.allowMultipleSelections.of(true), indentOnInput(), syntaxHighlighting(defaultHighlightStyle, {fallback: true}), bracketMatching(), closeBrackets(), autocompletion(), rectangularSelection(), crosshairCursor(), highlightActiveLine(), highlightSelectionMatches(), keymap.of([ ...closeBracketsKeymap, ...defaultKeymap, ...searchKeymap, ...historyKeymap, ...foldKeymap, ...completionKeymap, ...lintKeymap ]) ])()` class Builder { jApiBaseUrl = 'https://data.jsdelivr.com/v1/' jCdnBaseUrl = 'https://cdn.jsdelivr.net/npm/' scripts = { '@rollup/browser': { version: '3.20.4', path: 'dist/rollup.browser.js', sha: 'sha256-GgOznxZmgghx1a7CH09B+VmDKtziPO5tAnC5gC+/5Kw=', }, } topLevelDeps = [ "@codemirror/autocomplete", "@codemirror/commands", "@codemirror/language", "@codemirror/lint", "@codemirror/search", "@codemirror/state", "@codemirror/view", "@codemirror/lang-javascript", ] constructor() { this.downloads = [] } checkOk(resp) { if (!resp.ok) { throw new Error(`HTTP request failed: ${resp.status}`) } } async loadDep(dep) { this.scripts[dep] = {} const dataResp = await fetch( this.jApiBaseUrl + `packages/npm/${dep}/resolved`, { headers: { 'User-Agent': 'https://codeberg.org/macchiato', }, }, ) this.checkOk(dataResp) const {version} = await dataResp.json() this.scripts[dep].version = version const pkgResp = await fetch( this.jCdnBaseUrl + `${dep}@${version}/package.json` ) this.checkOk(pkgResp) this.log(dep) const pkg = await pkgResp.json() this.scripts[dep].path = ( pkg.module ?? pkg.main ) this.downloads.push(this.getScript(dep)) const deps = Object.keys( pkg.dependencies || {} ).filter(dep => !(dep in this.scripts)) await Promise.allSettled(deps.map(dep => ( this.loadDep(dep) ))) } async sha(ab) { const hash = await crypto.subtle.digest( "SHA-256", ab ) return 'sha256-' + btoa( String.fromCharCode( ...new Uint8Array(hash) ) ) } async checkIntegrity(resp, name, script) { const blob = await resp.blob() const ab = await blob.arrayBuffer() const sha = await this.sha(ab) if (sha !== script.sha) { throw new Error( 'failed integrity check: ' + `${checkValue} !== ${script.sha}` ) } return ab } async getScript(name) { this.log('[downloading] ' + name, 'green') const script = this.scripts[name] if (script.text) { return script.text } const url = ( this.jCdnBaseUrl + `${name}@${script.version}/${script.path}` ) const resp = await fetch(url) this.checkOk(resp) if (script.sha) { const ab = await this.checkIntegrity( resp, name, script ) script.text = new TextDecoder().decode(ab) } else { script.text = await resp.text() script.sha = resp.integrity } this.log( '[downloaded] ' + url + ` [${script.text.length}]`, 'green' ) return script.text } async loadScript(name) { const text = await this.getScript(name) const s = document.createElement('script') s.text = text document.head.append(s) } get loaderPlugin() { return { name: 'loader', resolveId: async source => { if (source === 'editor.js' || source in this.scripts) { return source } }, load: async id => { if (id === 'editor.js') { this.log(`[found] editor.js`) return editorSrc } else if (id in this.scripts) { this.log(`[found] ${id}`) return this.scripts[id].text } }, } } async build() { const result = await Promise.all( this.topLevelDeps.map(dep => ( this.loadDep(dep) )) ) await Promise.all(this.downloads) await this.loadScript('@rollup/browser') const { rollup } = window.rollup const input = 'editor.js' const plugins = [this.loaderPlugin] const bundle = await rollup({input, plugins}) const {output} = await bundle.generate({format: 'es'}) this.code = output[0].code this.sha = await this.sha( new TextEncoder().encode(this.code) ) this.log(`built ${this.sha}`) } } class BuildView extends HTMLElement { constructor() { super() this.attachShadow({mode: 'open'}) this.shadowRoot.adoptedStyleSheets = [ this.constructor.styleSheet ] } log(message, cls = 'cyan') { const el = document.createElement('pre') el.classList.add(cls) el.innerText = message this.shadowRoot.append(el) ;(el.scrollIntoViewIfNeeded ?? el.scrollIntoView).call(el) } static get styleSheet() { if (this._styleSheet === undefined) { this._styleSheet = new CSSStyleSheet() this._styleSheet.replaceSync(this.css) } return this._styleSheet } static css = ` :host { display: flex; flex-direction: column; align-items: stretch; margin: 10px; gap: 10px; } pre { padding: 8px; border-radius: 5px; margin: 0; overflow-x: auto; } pre.cyan { background: cyan; } pre.green { background: lightgreen; } ` } customElements.define('m-build-view', BuildView) class App { constructor() { this.builder = new Builder() this.buildView = document.createElement('m-build-view') document.body.append(this.buildView) this.builder.log = this.buildView.log.bind(this.buildView) this.build() } async build() { try { await this.builder.build() } catch (e) { this.buildView.log(`${e}`) } } } new App()