UNPKG

@inkdropapp/codemirror-spell-checker

Version:
130 lines (112 loc) 3.53 kB
import fetch from 'node-fetch' import Typo from 'typo-js' import fs from 'fs' import path from 'path' const dataDir = path.join(__dirname, 'data') const baseDicUrl = 'https://raw.githubusercontent.com/titoBouzout/Dictionaries/master/' export default function SpellChecker(CodeMirror) { // Verify if ( typeof CodeMirror !== 'function' || typeof CodeMirror.defineMode !== 'function' ) { throw new Error('You must provide a class of CodeMirror') } CodeMirror.defineOption( 'spellCheckLang', undefined, async function (cm, newVal) { if (newVal) { try { CodeMirror.signal(cm, 'spell-checker:dictionary-loading', newVal) SpellChecker.typo = await initTypo(newVal) CodeMirror.signal(cm, 'spell-checker:dictionary-loaded', newVal) } catch (e) { console.error('Failed to init Typo:', e) CodeMirror.signal(cm, 'spell-checker:error', e) } } } ) CodeMirror.defineMode('spell-checker', function (config) { // Define what separates a word const rx_word = '!"#$%&()*+,-./:;<=>?@[\\]^_`{|}~ ' const nonASCIISingleCaseWordChar = /[\u00df\u0587\u0590-\u05f4\u0600-\u06ff\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af -ー]/ // Create the overlay and such const overlay = { token: function (stream) { var ch var word = '' var ignore = false var state = stream.lineOracle.state while ( (ch = stream.peek()) != null && (rx_word.includes(ch) || nonASCIISingleCaseWordChar.test(ch)) ) { ignore = true stream.next() state = stream.lineOracle.state } if (ignore) return null while ( (ch = stream.peek()) != null && !rx_word.includes(ch) && !nonASCIISingleCaseWordChar.test(ch) ) { word += ch stream.next() state = stream.lineOracle.state if ( state.base.codeblock || state.base.indentedCode || state.base.code === 1 || (typeof state.baseCur === 'string' && (state.baseCur.indexOf('url') >= 0 || state.baseCur.indexOf('string') >= 0)) ) { return null } } if ( SpellChecker.typo && word.length > 0 && !SpellChecker.typo.check(word) ) return 'spell-error' // CSS class: cm-spell-error return null } } const mode = CodeMirror.getMode(config, config.backdrop || 'text/plain') return CodeMirror.overlayMode(mode, overlay, true) }) } export async function initTypo(lang) { const data = await loadDictionary(lang) return new Typo(lang, data.aff, data.dic, { platform: 'any' }) } export async function loadDictionary(lang) { const affPath = path.join(dataDir, `${lang}.aff`) const dicPath = path.join(dataDir, `${lang}.dic`) const data = {} if (!fs.existsSync(affPath)) { const url = `${baseDicUrl}/${lang}.aff` const res = await fetch(url) data.aff = await res.text() fs.writeFileSync(affPath, data.aff) } else { data.aff = fs.readFileSync(affPath, 'utf-8') } if (!fs.existsSync(dicPath)) { const url = `${baseDicUrl}/${lang}.dic` const res = await fetch(url) data.dic = await res.text() fs.writeFileSync(dicPath, data.dic) } else { data.dic = fs.readFileSync(dicPath, 'utf-8') } return data }