/* eslint-env browser */ // Modified from https://github.com/sparksuite/codemirror-spell-checker import Typo from 'typo-js' import { serverurl } from '../config' export const supportLanguages = [ { name: 'English (United States)', value: 'en_US', aff: { url: `${serverurl}/vendor/codemirror-spell-checker/en_US.aff`, cdnUrl: `${serverurl}/vendor/codemirror-spell-checker/en_US.aff` }, dic: { url: `${serverurl}/vendor/codemirror-spell-checker/en_US.dic`, cdnUrl: `${serverurl}/vendor/codemirror-spell-checker/en_US.dic` } }, { name: 'German', value: 'de', aff: { url: `${serverurl}/build/dictionary-de/index.aff`, cdnUrl: 'https://cdn.jsdelivr.net/npm/dictionary-de@2.0.3/index.aff' }, dic: { url: `${serverurl}/build/dictionary-de/index.dic`, cdnUrl: 'https://cdn.jsdelivr.net/npm/dictionary-de@2.0.3/index.dic' } }, { name: 'German (Austria)', value: 'de_AT', aff: { url: `${serverurl}/build/dictionary-de-at/index.aff`, cdnUrl: 'https://cdn.jsdelivr.net/npm/dictionary-de-at@2.0.3/index.aff' }, dic: { url: `${serverurl}/build/dictionary-de-at/index.dic`, cdnUrl: 'https://cdn.jsdelivr.net/npm/dictionary-de-at@2.0.3/index.dic' } }, { name: 'German (Switzerland)', value: 'de_CH', aff: { url: `${serverurl}/build/dictionary-de-ch/index.aff`, cdnUrl: 'https://cdn.jsdelivr.net/npm/dictionary-de-ch@2.0.3/index.aff' }, dic: { url: `${serverurl}/build/dictionary-de-ch/index.dic`, cdnUrl: 'https://cdn.jsdelivr.net/npm/dictionary-de-ch@2.0.3/index.dic' } } ] export const supportLanguageCodes = supportLanguages.map(lang => lang.value) function request (url) { return new Promise(resolve => { const req = new XMLHttpRequest() req.open('GET', url, true) req.onload = () => { if (req.readyState === 4 && req.status === 200) { resolve(req.responseText) } } req.send(null) }) } async function runSeriesP (iterables, fn) { const results = [] for (const iterable of iterables) { results.push(await fn(iterable)) } return results } function mapSeriesP (iterables, fn) { return new Promise(resolve => { resolve(runSeriesP(iterables, fn)) }) } function createTypo (lang, affData, dicData) { return new Typo(lang, affData, dicData, { platform: 'any' }) } const typoMap = new Map() let fetching = false async function findOrCreateTypoInstance (lang) { if (!lang) { return } // find existing typo instance let typo = typoMap.get(lang) if (typo) { return typo } let dict = supportLanguages.find(l => l.value === lang) if (!dict) { console.error(`Dictionary not found for "${lang}"\n Fallback to default English spellcheck`) dict = supportLanguages[0] } let affUrl let dicUrl if (window.USE_CDN) { affUrl = dict.aff.cdnUrl dicUrl = dict.dic.cdnUrl } else { affUrl = dict.aff.url dicUrl = dict.dic.url } if (fetching) { return typo } try { fetching = true const [affData, dicData] = await mapSeriesP([affUrl, dicUrl], request) typo = createTypo(lang, affData, dicData) typoMap.set(lang, typo) } catch (err) { console.error(err) } finally { fetching = false } return typo } class CodeMirrorSpellChecker { /** * @param {CodeMirror} cm * @param {string} lang */ constructor (cm, lang, editor) { // Verify if (typeof cm !== 'function' || typeof cm.defineMode !== 'function') { console.log( 'CodeMirror Spell Checker: You must provide an instance of CodeMirror via the option `codeMirrorInstance`' ) return } this.typo = undefined this.defineSpellCheckerMode(cm, lang) this.editor = editor } setDictLang (lang) { findOrCreateTypoInstance(lang).then(typo => { this.typo = typo // re-enable overlay mode to refresh spellcheck this.editor.setOption('mode', 'gfm') this.editor.setOption('mode', 'spell-checker') }) } defineSpellCheckerMode (cm, lang) { // Load AFF/DIC data async ASAP this.setDictLang(lang) cm.defineMode('spell-checker', config => { // Define what separates a word const regexWord = '!"#$%&()*+,-./:;<=>?@[\\]^_`{|}~ ' // Create the overlay and such const overlay = { token: (stream) => { let ch = stream.peek() let word = '' if (regexWord.includes(ch)) { stream.next() return null } while ((ch = stream.peek()) != null && !regexWord.includes(ch)) { word += ch stream.next() } if (this.typo && !this.typo.check(word)) { return 'spell-error' // CSS class: cm-spell-error } return null } } const mode = cm.getMode(config, config.backdrop || 'text/plain') return cm.overlayMode(mode, overlay, true) }) } } export default CodeMirrorSpellChecker