diff --git a/components/Literature.vue b/components/Literature.vue
index 52d839c0..abfaa35f 100644
--- a/components/Literature.vue
+++ b/components/Literature.vue
@@ -21,7 +21,7 @@
-
${data[field]}`: data[field]}
${tsv.join('\t')}`; -} - -const loadSources = () => { - return loadTsv('sources/sources').map(s => { - if (s.author) { - s.author = s.author.replace('^', ''); - } - s.fragments = s.fragments.split('@').map(f => f.replace(/\|/g, '\n')); - return s; - }); + await db.get(SQL` + UPDATE sources + SET approved = 1, base_id = NULL + WHERE id = ${id} + `); } const router = Router(); router.get('/sources', async (req, res) => { - return res.json(loadSources()); + return res.json(await req.db.all(SQL` + SELECT s.*, u.username AS submitter FROM sources s + LEFT JOIN users u ON s.submitter_id = u.id + WHERE s.locale = ${req.config.locale} + AND s.deleted = 0 + AND s.approved >= ${req.admin ? 0 : 1} + `)); }); -router.get('/sources/:key', async (req, res) => { - return res.json([...loadSources().filter(s => s.key === req.params.key), null][0]); +router.get('/sources/:id', async (req, res) => { + return res.json(await req.db.all(SQL` + SELECT s.*, u.username AS submitter FROM sources s + LEFT JOIN users u ON s.submitter_id = u.id + WHERE s.locale = ${req.config.locale} + AND s.deleted = 0 + AND s.approved >= ${req.admin ? 0 : 1} + AND s.id = ${req.params.id} + `)); }); router.post('/sources/submit', async (req, res) => { - const emailBody = buildEmail(req.body, req.user); + console.log(req.body.fragments); + console.log(req.body.fragments.join('@')); + console.log(req.body.fragments.join('@').replace(/\n/g, '|')); - for (let admin of process.env.MAILER_ADMINS.split(',')) { - mailer(admin, `[Pronouns][${req.config.locale}] Source submission`, undefined, emailBody); + const id = ulid(); + await req.db.get(SQL` + INSERT INTO sources (id, locale, pronouns, type, author, title, extra, year, fragments, comment, link, submitter_id, base_id) + VALUES ( + ${id}, ${req.config.locale}, ${req.body.pronouns.join(';')}, + ${req.body.type}, ${req.body.author}, ${req.body.title}, ${req.body.extra}, ${req.body.year}, + ${req.body.fragments.join('@').replace(/\n/g, '|')}, ${req.body.comment}, ${req.body.link}, + ${req.user ? req.user.id : null}, ${req.body.base} + ) + `); + + if (req.admin) { + await approve(req.db, id); } - return res.json({ result: 'ok' }); + return res.json('ok'); +}); + +router.post('/sources/hide/:id', async (req, res) => { + if (!req.admin) { + res.status(401).json({error: 'Unauthorised'}); + } + + await req.db.get(SQL` + UPDATE sources + SET approved = 0 + WHERE id = ${req.params.id} + `); + + return res.json('ok'); +}); + +router.post('/sources/approve/:id', async (req, res) => { + if (!req.admin) { + res.status(401).json({error: 'Unauthorised'}); + } + + await approve(req.db, req.params.id); + + return res.json('ok'); +}); + +router.post('/sources/remove/:id', async (req, res) => { + if (!req.admin) { + res.status(401).json({error: 'Unauthorised'}); + } + + await req.db.get(SQL` + UPDATE sources + SET deleted=1 + WHERE id = ${req.params.id} + `); + + return res.json('ok'); }); export default router; diff --git a/src/buildPronoun.js b/src/buildPronoun.js index dfc525cd..4351abf0 100644 --- a/src/buildPronoun.js +++ b/src/buildPronoun.js @@ -21,6 +21,26 @@ export const getPronoun = (pronouns, id) => { return addAliasesToPronouns(pronouns)[id]; } +const buildPronounFromTemplate = (key, template) => { + return new Pronoun( + key, + template.description, + template.normative || false, + buildDict(function*(morphemes) { + for (let k in morphemes) { + if (morphemes.hasOwnProperty(k)) { + yield [k, morphemes[k].replace(/#/g, key)]; + } + } + }, template.morphemes), + [template.plural || false], + [template.pluralHonorific || false], + template.aliases || [], + template.history || '', + false, + ); +} + export const buildPronoun = (pronouns, path) => { const pronounsWithAliases = addAliasesToPronouns(pronouns); @@ -66,7 +86,6 @@ export const buildPronoun = (pronouns, path) => { [ p[p.length - 1].endsWith('selves') ], // TODO English specific, extract somewhere [ false ], [], - [], null, false, ) @@ -93,7 +112,6 @@ export const parsePronouns = (pronounsRaw) => { }), [t.plural], [t.pluralHonorific], - t.sources ? t.sources.split(',') : [], aliases.slice(1), t.history, t.pronounceable, @@ -103,23 +121,3 @@ export const parsePronouns = (pronounsRaw) => { }); } -export const buildPronounFromTemplate = (key, template) => { - return new Pronoun( - key, - template.description, - template.normative || false, - buildDict(function*(morphemes) { - for (let k in morphemes) { - if (morphemes.hasOwnProperty(k)) { - yield [k, morphemes[k].replace(/#/g, key)]; - } - } - }, template.morphemes), - [template.plural || false], - [template.pluralHonorific || false], - template.sources || [], - template.aliases || [], - template.history || null, - false, - ) -} diff --git a/src/classes.js b/src/classes.js index abe8c8e3..41f9ef3a 100644 --- a/src/classes.js +++ b/src/classes.js @@ -96,16 +96,20 @@ function clone(mainObject) { } export class Source { - constructor (type, author, title, extra, year, fragments = [], comment = null, link = null, submitter = null) { + constructor ({id, pronouns, type, author, title, extra, year, fragments = '', comment = null, link = null, submitter = null, approved, base_id = null,}) { + this.id = id; + this.pronouns = pronouns ? pronouns.split(';') : []; this.type = type; this.author = author; this.title = title; this.extra = extra; this.year = year; - this.fragments = fragments; + this.fragments = fragments ? fragments.replace(/\|/g, '\n').split('@') : []; this.comment = comment; this.link = link; this.submitter = submitter; + this.approved = approved; + this.base_id = base_id; } static get TYPES() { @@ -138,6 +142,101 @@ export class Source { } } + +export class SourceLibrary { + constructor(rawSources) { + this.sources = rawSources.map(s => new Source(s)); + this.map = {}; + const multiple = new Set(); + const pronouns = new Set(); + + for (let source of this.sources) { + if (!source.pronouns.length) { + if (this.map[''] === undefined) { this.map[''] = []; } + this.map[''].push(source); + continue; + } + for (let pronoun of source.pronouns) { + if (this.map[pronoun] === undefined) { this.map[pronoun] = []; } + this.map[pronoun].push(source); + + pronouns.add(pronoun); + if (pronoun.includes('&')) { + multiple.add(pronoun); + } + } + } + this.pronouns = [...pronouns]; + this.multiple = [...multiple]; + this.cache = {} + } + + getForPronoun(pronoun) { + if (this.cache[pronoun] === undefined) { + let sources = this.map[pronoun] || []; + + if (pronoun === '') { + for (let p of this.pronouns) { + // add pronouns that have never been requested to "other" + if (this.cache[p] === undefined) { + sources = [...sources, ...this.map[p]]; + } + } + } + + this.cache[pronoun] = sources + .map(s => this.addMetaData(s)) + .sort((a, b) => { + if (a.typePriority !== b.typePriority) { + return b.typePriority - a.typePriority; + } + + return a.sortString.localeCompare(b.sortString); + }); + } + + return this.cache[pronoun]; + } + + getForPronounExtended(pronoun) { + let sources = {}; + const s = this.getForPronoun(pronoun); + sources[pronoun] = s.length ? s : undefined; + + if (pronoun.includes('&')) { + for (let option of pronoun.split('&')) { + const s = this.getForPronoun(option); + sources[option] = s.length ? s : undefined; + } + } + + return sources; + } + + addMetaData(source) { + source.typePriority = Source.TYPES_PRIORITIES[source.type]; + + source.sortString = source.author || 'ZZZZZ' + source.title; // if no author, put on the end + if (source.sortString.includes('^')) { + const index = source.sortString.indexOf('^'); + source.sortString = source.sortString.substring(index + 1) + ' ' + source.sortString.substring(0, index); + } + + source.index = [ + (source.author || '').replace('^', ''), + source.title, + source.extra, + source.year, + //...source.fragments, + source.comment, + source.link, + ].join(' ').toLowerCase().replace(/<\/?[^>]+(>|$)/g, ''); + + return source; + } +} + + const escape = s => { if (Array.isArray(s)) { s = s.join('&'); @@ -152,7 +251,7 @@ const escape = s => { } export class Pronoun { - constructor (canonicalName, description, normative, morphemes, plural, pluralHonorific, sources = [], aliases = [], history = null, pronounceable = true) { + constructor (canonicalName, description, normative, morphemes, plural, pluralHonorific, aliases = [], history = '', pronounceable = true) { this.canonicalName = canonicalName; this.description = description; this.normative = normative; @@ -166,7 +265,6 @@ export class Pronoun { } this.plural = plural; this.pluralHonorific = pluralHonorific; - this.sources = sources; this.aliases = aliases; this.history = history; this.pronounceable = pronounceable; @@ -194,7 +292,17 @@ export class Pronoun { } clone() { - return new Pronoun(this.canonicalName, this.description, this.normative, clone(this.morphemes), [...this.plural], [...this.pluralHonorific], this.pronounceable); + return new Pronoun( + this.canonicalName, + this.description, + this.normative, + clone(this.morphemes), + [...this.plural], + [...this.pluralHonorific], + [...this.aliases], + this.history, + this.pronounceable + ); } equals(other) { @@ -214,6 +322,8 @@ export class Pronoun { }, this, other), [...this.plural, ...other.plural], [...this.pluralHonorific, ...other.pluralHonorific], + [], + '', false, ); } @@ -319,6 +429,8 @@ export class Pronoun { m, data[MORPHEMES.length].split('').map(p => parseInt(p) === 1), data[MORPHEMES.length + 1].split('').map(p => parseInt(p) === 1), + [], + null, false, ) } diff --git a/src/data.js b/src/data.js index 6acc8d59..3b256d23 100644 --- a/src/data.js +++ b/src/data.js @@ -1,7 +1,6 @@ import {Source, Example, NounTemplate, PronounGroup, PronounLibrary, Name, Person, NounDeclension} from './classes' import { buildDict, buildList } from './helpers'; -import { parsePronouns, getPronoun } from './buildPronoun'; -import sourcesForMultipleForms from '../data/sources/sourcesMultiple'; +import { parsePronouns } from './buildPronoun'; export const socialProviders = { twitter: { name: 'Twitter' }, @@ -23,55 +22,6 @@ export const examples = buildList(function* () { } }); -import sourcesRaw from '../data/sources/sources.tsv'; -export const sources = buildDict(function* () { - for (let s of sourcesRaw) { - yield [ - s.key, - new Source( - s.type, - s.author, - s.title, - s.extra, - s.year, - s.fragments ? s.fragments.replace(/\|/g, '\n').split('@') : [], - s.comment, - s.link, - ) - ]; - } -}); - -export const getSources = (selectedPronoun) => { - if (!selectedPronoun) { - return {}; - } - - let sources = {}; - for (let multiple in sourcesForMultipleForms) { - if (sourcesForMultipleForms.hasOwnProperty(multiple)) { - if (multiple === selectedPronoun.canonicalName) { - sources[multiple] = sourcesForMultipleForms[multiple]; - } - } - } - for (let option of selectedPronoun.nameOptions()) { - const pronoun = getPronoun(pronouns, option); - if (pronoun && pronoun.sources.length) { - sources[option] = pronoun.sources; - } - } - - if (Object.keys(sources).length === 0) { - const pronoun = getPronoun(pronouns, selectedPronoun.canonicalName); - if (pronoun && pronoun.sources.length) { - sources[selectedPronoun.canonicalName] = pronoun.sources; - } - } - - return sources; -} - import nounTemplatesRaw from '../data/nouns/nounTemplates.tsv'; export const nounTemplates = buildList(function* () { for (let t of nounTemplatesRaw) {