mirror of https://github.com/status-im/codimd.git
212 lines
5.0 KiB
JavaScript
212 lines
5.0 KiB
JavaScript
/* 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
|