From 3874163d6338953208022ce4f8c4b4ad7c5f7133 Mon Sep 17 00:00:00 2001 From: Avris Date: Tue, 17 Nov 2020 19:21:49 +0100 Subject: [PATCH] =?UTF-8?q?#103=20[incl][pl]=20s=C5=82ownik=20j=C4=99zyka?= =?UTF-8?q?=20inkluzywnego=20-=20kod?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/InclusiveDictionary.vue | 256 +++++++++++++++++++++++++++++ components/InclusiveSubmitForm.vue | 109 ++++++++++++ components/NounSubmitForm.vue | 1 - locale/pl/nouns/NounsExtra.vue | 24 +++ locale/pl/nouns/NounsNav.vue | 41 +++-- locale/pl/translations.suml | 17 ++ migrations/006-inclusive.sql | 15 ++ server/index.js | 1 + server/routes/inclusive.js | 102 ++++++++++++ server/routes/nouns.js | 5 +- src/classes.js | 27 +++ src/helpers.js | 4 + 12 files changed, 584 insertions(+), 18 deletions(-) create mode 100644 components/InclusiveDictionary.vue create mode 100644 components/InclusiveSubmitForm.vue create mode 100644 migrations/006-inclusive.sql create mode 100644 server/routes/inclusive.js diff --git a/components/InclusiveDictionary.vue b/components/InclusiveDictionary.vue new file mode 100644 index 00000000..40e0523c --- /dev/null +++ b/components/InclusiveDictionary.vue @@ -0,0 +1,256 @@ + + + + + diff --git a/components/InclusiveSubmitForm.vue b/components/InclusiveSubmitForm.vue new file mode 100644 index 00000000..59deb604 --- /dev/null +++ b/components/InclusiveSubmitForm.vue @@ -0,0 +1,109 @@ + + + diff --git a/components/NounSubmitForm.vue b/components/NounSubmitForm.vue index 0a40394b..1bee2379 100644 --- a/components/NounSubmitForm.vue +++ b/components/NounSubmitForm.vue @@ -32,7 +32,6 @@ nouns.neuter nouns.neuterShort - diff --git a/locale/pl/nouns/NounsExtra.vue b/locale/pl/nouns/NounsExtra.vue index 4cc421fb..6e7934f0 100644 --- a/locale/pl/nouns/NounsExtra.vue +++ b/locale/pl/nouns/NounsExtra.vue @@ -3,6 +3,7 @@

+ nouns.neuterNouns.header

@@ -66,6 +67,7 @@

+ nouns.dukajNouns.header

@@ -150,6 +152,7 @@

+ nouns.personNouns.header

@@ -209,6 +212,27 @@ + + + +

+ + nouns.inclusive.headerLong +

+ + nouns.inclusive.info + +
+ +

+ + nouns.inclusive.headerLong +

+
+
+ +
+
diff --git a/locale/pl/nouns/NounsNav.vue b/locale/pl/nouns/NounsNav.vue index fa18d508..5f08ceea 100644 --- a/locale/pl/nouns/NounsNav.vue +++ b/locale/pl/nouns/NounsNav.vue @@ -1,16 +1,31 @@ + + diff --git a/locale/pl/translations.suml b/locale/pl/translations.suml index 3ae86346..1aa95ebe 100644 --- a/locale/pl/translations.suml +++ b/locale/pl/translations.suml @@ -225,6 +225,23 @@ nouns: plural: 'liczba mnoga' pluralShort: 'l. mn.' + inclusive: + header: 'Inkluzywność' + headerLong: 'Słownik inkluzywnego języka' + id: 'inkluzywnosc' + insteadOf: 'Zamiast' + say: 'Lepiej mów' + because: 'Ponieważ' + info: + - > + Język jest nośnikiem myśli, nośnikiem kultury, podstawą komunikacji. Wpływa na to, co robimy i jak myślimy. + Jeśli chcemy tworzyć społeczeństwo otwarte na różnorodność i akceptujące odmienność, + to nasz język też musi być włączający. + - > + Inkluzywny język to nie tylko rzeczowniki i nie tylko kwestie płciowości. + Poniżej przedstawiamy słownik, w którym zbieramy sugestie, + jakich konstrukcji lepiej unikać i dlatego, oraz czym je zastępować. + names: header: 'Imiona' headerLong: 'Neutralne imiona' diff --git a/migrations/006-inclusive.sql b/migrations/006-inclusive.sql new file mode 100644 index 00000000..3119aa9e --- /dev/null +++ b/migrations/006-inclusive.sql @@ -0,0 +1,15 @@ +-- Up + +CREATE TABLE inclusive ( + id TEXT NOT NULL PRIMARY KEY, + insteadOf TEXT NOT NULL, + say TEXT NOT NULL, + because TEXT NOT NULL, + locale TEXT NOT NULL, + approved INTEGER NOT NULL, + base_id TEXT +); + +-- Down + +DROP TABLE inclusive; diff --git a/server/index.js b/server/index.js index f67126e0..06321565 100644 --- a/server/index.js +++ b/server/index.js @@ -39,6 +39,7 @@ app.use(require('./routes/admin').default); app.use(require('./routes/pronouns').default); app.use(require('./routes/sources').default); app.use(require('./routes/nouns').default); +app.use(require('./routes/inclusive').default); export default { path: '/api', diff --git a/server/routes/inclusive.js b/server/routes/inclusive.js new file mode 100644 index 00000000..5bf3e938 --- /dev/null +++ b/server/routes/inclusive.js @@ -0,0 +1,102 @@ +import { Router } from 'express'; +import SQL from 'sql-template-strings'; +import {ulid} from "ulid"; +import {isTroll} from "../../src/helpers"; + +const approve = async (db, id) => { + const { base_id } = await db.get(SQL`SELECT base_id FROM inclusive WHERE id=${id}`); + if (base_id) { + await db.get(SQL` + DELETE FROM inclusive + WHERE id = ${base_id} + `); + } + await db.get(SQL` + UPDATE inclusive + SET approved = 1, base_id = NULL + WHERE id = ${id} + `); +} + +const router = Router(); + +router.get('/inclusive', async (req, res) => { + return res.json(await req.db.all(SQL` + SELECT * FROM inclusive + WHERE locale = ${req.config.locale} + AND approved >= ${req.admin ? 0 : 1} + ORDER BY approved, insteadOf + `)); +}); + +router.get('/inclusive/search/:term', async (req, res) => { + const term = '%' + req.params.term + '%'; + return res.json(await req.db.all(SQL` + SELECT * FROM inclusive + WHERE locale = ${req.config.locale} + AND approved >= ${req.admin ? 0 : 1} + AND (insteadOf like ${term} OR say like ${term}) + ORDER BY approved, insteadOf + `)); +}); + +router.post('/inclusive/submit', async (req, res) => { + if (!(req.user && req.user.admin) && isTroll(JSON.stringify(req.body))) { + return res.json('ok'); + } + + const id = ulid(); + await req.db.get(SQL` + INSERT INTO inclusive (id, insteadOf, say, because, approved, base_id, locale) + VALUES ( + ${id}, + ${req.body.insteadOf.join('|')}, ${req.body.say.join('|')}, ${req.body.because}, + 0, ${req.body.base}, ${req.config.locale} + ) + `); + + if (req.admin) { + await approve(req.db, id); + } + + return res.json('ok'); +}); + +router.post('/inclusive/hide/:id', async (req, res) => { + if (!req.admin) { + res.status(401).json({error: 'Unauthorised'}); + } + + await req.db.get(SQL` + UPDATE inclusive + SET approved = 0 + WHERE id = ${req.params.id} + `); + + return res.json('ok'); +}); + +router.post('/inclusive/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('/inclusive/remove/:id', async (req, res) => { + if (!req.admin) { + res.status(401).json({error: 'Unauthorised'}); + } + + await req.db.get(SQL` + DELETE FROM inclusive + WHERE id = ${req.params.id} + `); + + return res.json('ok'); +}); + +export default router; diff --git a/server/routes/nouns.js b/server/routes/nouns.js index 442500e1..8dd1cd58 100644 --- a/server/routes/nouns.js +++ b/server/routes/nouns.js @@ -1,10 +1,7 @@ import { Router } from 'express'; import SQL from 'sql-template-strings'; import {ulid} from "ulid"; - -const isTroll = (body) => { - return ['cipeusz', 'feminazi', 'bruksela', 'zboczeń'].some(t => body.indexOf(t) > -1); -} +import {isTroll} from "../../src/helpers"; const approve = async (db, id) => { const { base_id } = await db.get(SQL`SELECT base_id FROM nouns WHERE id=${id}`); diff --git a/src/classes.js b/src/classes.js index f7805970..9dbabde5 100644 --- a/src/classes.js +++ b/src/classes.js @@ -433,6 +433,33 @@ export class NounDeclension { } +export class InclusiveEntry { + constructor({id, insteadOf, say, because, approved = true, base_id = null}) { + this.id = id; + this.insteadOf = insteadOf.split('|'); + this.say = say.split('|'); + this.because = because; + this.approved = !!approved; + this.base = base_id; + } + + matches(filter) { + if (!filter) { + return true; + } + + for (let field of ['insteadOf', 'say']) { + for (let value of this[field]) { + if (value.toLowerCase().indexOf(filter.toLowerCase()) > -1) { + return true; + } + } + } + return false; + } +} + + export class Name { constructor(name, origin, meaning, usage, legally, pros, cons, notablePeople, count, links) { this.name = name; diff --git a/src/helpers.js b/src/helpers.js index e1820dac..1fdbb4fa 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -131,3 +131,7 @@ export const now = function () { export const isEmoji = char => { return !!char.match(/^(?:[\u2700-\u27bf]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff]|[\u0023-\u0039]\ufe0f?\u20e3|\u3299|\u3297|\u303d|\u3030|\u24c2|\ud83c[\udd70-\udd71]|\ud83c[\udd7e-\udd7f]|\ud83c\udd8e|\ud83c[\udd91-\udd9a]|\ud83c[\udde6-\uddff]|\ud83c[\ude01-\ude02]|\ud83c\ude1a|\ud83c\ude2f|\ud83c[\ude32-\ude3a]|\ud83c[\ude50-\ude51]|\u203c|\u2049|[\u25aa-\u25ab]|\u25b6|\u25c0|[\u25fb-\u25fe]|\u00a9|\u00ae|\u2122|\u2139|\ud83c\udc04|[\u2600-\u26FF]|\u2b05|\u2b06|\u2b07|\u2b1b|\u2b1c|\u2b50|\u2b55|\u231a|\u231b|\u2328|\u23cf|[\u23e9-\u23f3]|[\u23f8-\u23fa]|\ud83c\udccf|\u2934|\u2935|[\u2190-\u21ff])$/) } + +export const isTroll = (body) => { + return ['cipeusz', 'feminazi', 'bruksela', 'zboczeń'].some(t => body.indexOf(t) > -1); +}