#139 [terms] dictionary of queer terms
This commit is contained in:
parent
cf7b7b7b33
commit
2569b69ab9
|
@ -77,7 +77,7 @@
|
||||||
if (this.config.nouns.enabled) {
|
if (this.config.nouns.enabled) {
|
||||||
links.push({
|
links.push({
|
||||||
link: '/' + this.config.nouns.route,
|
link: '/' + this.config.nouns.route,
|
||||||
icon: 'atom-alt',
|
icon: 'book',
|
||||||
text: this.$t('nouns.header'),
|
text: this.$t('nouns.header'),
|
||||||
textLong: this.$t('nouns.headerLong'),
|
textLong: this.$t('nouns.headerLong'),
|
||||||
});
|
});
|
||||||
|
|
|
@ -178,11 +178,13 @@
|
||||||
<script>
|
<script>
|
||||||
import { InclusiveEntry } from "~/src/classes";
|
import { InclusiveEntry } from "~/src/classes";
|
||||||
import { buildDict, clearUrl } from "../src/helpers";
|
import { buildDict, clearUrl } from "../src/helpers";
|
||||||
|
import hash from "../plugins/hash";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
load: {type: Boolean}
|
load: {type: Boolean}
|
||||||
},
|
},
|
||||||
|
mixins: [ hash ],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
filter: '',
|
filter: '',
|
||||||
|
@ -274,11 +276,10 @@
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
filter() {
|
filter() {
|
||||||
if (process.client) {
|
this.setHash(this.config.nouns.inclusive.hashNamespace || '', this.filter);
|
||||||
if (this.$refs.dictionarytable) {
|
if (this.$refs.dictionarytable) {
|
||||||
this.$refs.dictionarytable.reset();
|
this.$refs.dictionarytable.reset();
|
||||||
this.$refs.dictionarytable.focus();
|
this.$refs.dictionarytable.focus();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,264 @@
|
||||||
|
<template>
|
||||||
|
<Loading :value="entriesRaw">
|
||||||
|
<section v-if="$admin()" class="px-3">
|
||||||
|
<div class="alert alert-info">
|
||||||
|
<strong>{{ entriesCountApproved() }}</strong> <T>nouns.approved</T>,
|
||||||
|
<strong>{{ entriesCountPending() }}</strong> <T>nouns.pending</T>.
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="sticky-top">
|
||||||
|
<div class="input-group mb-3 bg-white">
|
||||||
|
<div class="input-group-prepend">
|
||||||
|
<span class="input-group-text">
|
||||||
|
<Icon v="filter"/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<input class="form-control border-primary" v-model="filter" :placeholder="$t('crud.filterLong')" ref="filter"/>
|
||||||
|
<div class="input-group-append" v-if="filter">
|
||||||
|
<button class="btn btn-outline-danger" @click="filter = ''; $refs.filter.focus()">
|
||||||
|
<Icon v="times"/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="input-group-append">
|
||||||
|
<button class="btn btn-outline-success" @click="$refs.form.$el.scrollIntoView()">
|
||||||
|
<Icon v="plus-circle"/>
|
||||||
|
<T>nouns.submit.action</T>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<Table :data="visibleEntries()" columns="1" fixed :marked="(el) => !el.approved" ref="dictionarytable">
|
||||||
|
<template v-slot:header>
|
||||||
|
<th></th>
|
||||||
|
<th></th>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-slot:row="s"><template v-if="s">
|
||||||
|
<td>
|
||||||
|
<p>
|
||||||
|
<strong>{{s.el.term.join(', ')}}</strong>
|
||||||
|
<span v-if="s.el.original.length">({{s.el.original.join(', ')}})</span>
|
||||||
|
– {{s.el.definition}}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<small v-if="s.el.base && entries[s.el.base]">
|
||||||
|
<p><strong><T>nouns.edited</T>:</strong></p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<strong>{{s.el.term.join(', ')}}</strong>
|
||||||
|
<span v-if="s.el.original.length">({{s.el.original.join(', ')}})</span>
|
||||||
|
– {{s.el.definition}}
|
||||||
|
</p>
|
||||||
|
</small>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<ul class="list-unstyled list-btn-concise">
|
||||||
|
<!--
|
||||||
|
<li v-if="s.el.author" class="small">
|
||||||
|
<nuxt-link :to="`/@${s.el.author}`" class="btn btn-concise btn-outline-dark btn-sm m-1">
|
||||||
|
<Icon v="user"/>
|
||||||
|
<span class="btn-label">
|
||||||
|
<T>crud.author</T>:
|
||||||
|
@{{s.el.author}}
|
||||||
|
</span>
|
||||||
|
</nuxt-link>
|
||||||
|
</li>
|
||||||
|
-->
|
||||||
|
<template v-if="$admin()">
|
||||||
|
<li v-if="!s.el.approved">
|
||||||
|
<button class="btn btn-concise btn-success btn-sm m-1" @click="approve(s.el)">
|
||||||
|
<Icon v="check"/>
|
||||||
|
<span class="btn-label"><T>crud.approve</T></span>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li v-else @click="hide(s.el)">
|
||||||
|
<button class="btn btn-concise btn-outline-secondary btn-sm m-1">
|
||||||
|
<Icon v="times"/>
|
||||||
|
<span class="btn-label"><T>crud.hide</T></span>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<button class="btn btn-concise btn-outline-danger btn-sm m-1" @click="remove(s.el)">
|
||||||
|
<Icon v="trash"/>
|
||||||
|
<span class="btn-label"><T>crud.remove</T></span>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
|
<li>
|
||||||
|
<button class="btn btn-concise btn-outline-primary btn-sm m-1" @click="edit(s.el)">
|
||||||
|
<Icon v="pen"/>
|
||||||
|
<span class="btn-label">
|
||||||
|
<T v-if="$admin()">crud.edit</T>
|
||||||
|
<T v-else>nouns.edit</T>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</td>
|
||||||
|
</template></template>
|
||||||
|
|
||||||
|
<template v-slot:empty>
|
||||||
|
<Icon v="search"/>
|
||||||
|
<T>nouns.empty</T>
|
||||||
|
</template>
|
||||||
|
</Table>
|
||||||
|
|
||||||
|
<template v-if="config.nouns.submit">
|
||||||
|
<Separator icon="plus"/>
|
||||||
|
|
||||||
|
<div class="px-3">
|
||||||
|
<TermsSubmitForm ref="form"/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Loading>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { TermsEntry } from "~/src/classes";
|
||||||
|
import { buildDict, clearUrl } from "../src/helpers";
|
||||||
|
import hash from "../plugins/hash";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
load: {type: Boolean}
|
||||||
|
},
|
||||||
|
mixins: [ hash ],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
filter: '',
|
||||||
|
entriesRaw: undefined,
|
||||||
|
clearUrl,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
if (this.load) {
|
||||||
|
this.loadEntries();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async loadEntries() {
|
||||||
|
if (this.entriesRaw !== undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.entriesRaw = await this.$axios.$get(`/terms`);
|
||||||
|
},
|
||||||
|
async setFilter(filter) {
|
||||||
|
this.filter = filter;
|
||||||
|
await this.loadEntries();
|
||||||
|
this.focus();
|
||||||
|
},
|
||||||
|
focus() {
|
||||||
|
this.$el.focus();
|
||||||
|
this.$el.scrollIntoView();
|
||||||
|
setTimeout(_ => {
|
||||||
|
this.$el.scrollIntoView();
|
||||||
|
}, 1000);
|
||||||
|
},
|
||||||
|
edit(entry) {
|
||||||
|
this.$refs.form.edit(entry);
|
||||||
|
},
|
||||||
|
async approve(entry) {
|
||||||
|
await this.$axios.$post(`/terms/approve/${entry.id}`);
|
||||||
|
if (entry.base) {
|
||||||
|
delete this.entries[entry.base];
|
||||||
|
}
|
||||||
|
entry.approved = true;
|
||||||
|
entry.base = null;
|
||||||
|
this.$forceUpdate();
|
||||||
|
},
|
||||||
|
async hide(entry) {
|
||||||
|
await this.$axios.$post(`/terms/hide/${entry.id}`);
|
||||||
|
entry.approved = false;
|
||||||
|
this.$forceUpdate();
|
||||||
|
},
|
||||||
|
async remove(entry) {
|
||||||
|
await this.$confirm(this.$t('crud.removeConfirm'), 'danger');
|
||||||
|
|
||||||
|
await this.$axios.$post(`/terms/remove/${entry.id}`);
|
||||||
|
delete this.entries[entry.id];
|
||||||
|
this.$forceUpdate();
|
||||||
|
},
|
||||||
|
|
||||||
|
// those must be methods, not computed, because when modified, they don't get updated in the view for some reason
|
||||||
|
visibleEntries() {
|
||||||
|
return Object.values(this.entries).filter(n => n.matches(this.filter));
|
||||||
|
},
|
||||||
|
entriesCountApproved() {
|
||||||
|
return Object.values(this.entries).filter(n => n.approved).length;
|
||||||
|
},
|
||||||
|
entriesCountPending() {
|
||||||
|
return Object.values(this.entries).filter(n => !n.approved).length;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
entries() {
|
||||||
|
if (this.entriesRaw === undefined) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return buildDict(function* (that) {
|
||||||
|
const sorted = that.entriesRaw.sort((a, b) => {
|
||||||
|
if (a.approved && !b.approved) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (!a.approved && b.approved) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return a.term.toLowerCase().localeCompare(b.term.toLowerCase());
|
||||||
|
});
|
||||||
|
for (let w of sorted) {
|
||||||
|
yield [w.id, new TermsEntry(w)];
|
||||||
|
}
|
||||||
|
}, this);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
filter() {
|
||||||
|
this.setHash(this.config.nouns.terms.hashNamespace || '', this.filter);
|
||||||
|
if (this.$refs.dictionarytable) {
|
||||||
|
this.$refs.dictionarytable.reset();
|
||||||
|
this.$refs.dictionarytable.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@import "assets/variables";
|
||||||
|
|
||||||
|
tr {
|
||||||
|
.hover-show {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
&:hover .hover-show {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-concise {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
@include media-breakpoint-up('md', $grid-breakpoints) {
|
||||||
|
.list-btn-concise {
|
||||||
|
min-width: 3rem;
|
||||||
|
|
||||||
|
li {
|
||||||
|
height: 2.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.btn-concise {
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
.btn-label {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover .btn-label {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,108 @@
|
||||||
|
<template>
|
||||||
|
<section>
|
||||||
|
<div v-if="afterSubmit" class="alert alert-success text-center">
|
||||||
|
<p>
|
||||||
|
<T>nouns.submit.thanks</T>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<button class="btn btn-success" @click="afterSubmit = false">
|
||||||
|
<Icon v="plus"/>
|
||||||
|
<T>nouns.submit.another</T>
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<form v-else @submit.prevent="submit">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-borderless table-sm table-fixed-3">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="text-nowrap">
|
||||||
|
<T>nouns.terms.term</T>
|
||||||
|
</th>
|
||||||
|
<th class="text-nowrap">
|
||||||
|
<T>nouns.terms.original</T>
|
||||||
|
</th>
|
||||||
|
<th class="text-nowrap">
|
||||||
|
<T>nouns.terms.definition</T>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<NounForm v-model="form.term" required/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<NounForm v-model="form.original"/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<textarea v-model="form.definition" class="form-control form-control-sm" required rows="3"></textarea>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="alert alert-info" v-if="form.base">
|
||||||
|
<Icon v="info-circle"/>
|
||||||
|
<T>nouns.editing</T>
|
||||||
|
<button class="btn btn-sm float-right" @click="form.base = null">
|
||||||
|
<Icon v="times"/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="btn btn-primary btn-block" :disabled="submitting">
|
||||||
|
<template v-if="submitting">
|
||||||
|
<Icon v="circle-notch fa-spin"/>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<Icon v="plus"/>
|
||||||
|
<T>nouns.submit.actionLong</T>
|
||||||
|
</template>
|
||||||
|
</button>
|
||||||
|
<p class="small text-muted mt-1"><T>nouns.submit.moderation</T></p>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
form: {
|
||||||
|
term: [''],
|
||||||
|
original: [],
|
||||||
|
definition: '',
|
||||||
|
base: null,
|
||||||
|
},
|
||||||
|
submitting: false,
|
||||||
|
afterSubmit: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async submit(event) {
|
||||||
|
this.submitting = true;
|
||||||
|
await this.$axios.$post(`/terms/submit`, this.form);
|
||||||
|
|
||||||
|
this.submitting = false;
|
||||||
|
this.afterSubmit = true;
|
||||||
|
this.form = {
|
||||||
|
term: [''],
|
||||||
|
original: [],
|
||||||
|
definition: '',
|
||||||
|
base: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
edit(word) {
|
||||||
|
this.form = {
|
||||||
|
term: word.term,
|
||||||
|
original: word.original,
|
||||||
|
definition: word.definition,
|
||||||
|
base: word.id,
|
||||||
|
}
|
||||||
|
this.afterSubmit = false;
|
||||||
|
this.$el.scrollIntoView();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
|
@ -62,16 +62,19 @@ sources:
|
||||||
|
|
||||||
nouns:
|
nouns:
|
||||||
enabled: true
|
enabled: true
|
||||||
route: 'słownik'
|
route: 'słowniki'
|
||||||
collapsable: true
|
collapsable: true
|
||||||
plurals: true
|
plurals: true
|
||||||
pluralsRequired: true
|
pluralsRequired: true
|
||||||
declension: true
|
declension: true
|
||||||
submit: true
|
submit: true
|
||||||
templates: true
|
templates: true
|
||||||
|
hashNamespace: 'neutratywy'
|
||||||
inclusive:
|
inclusive:
|
||||||
categories: ['interpłciowość', 'lgbtq+', 'niepełnosprawność', 'rasa', 'trans']
|
categories: ['interpłciowość', 'lgbtq+', 'niepełnosprawność', 'rasa', 'trans']
|
||||||
hashNamespace: 'neutratywy'
|
hashNamespace: 'inkluzywny'
|
||||||
|
terms:
|
||||||
|
hashNamespace: 'terminologia'
|
||||||
|
|
||||||
names:
|
names:
|
||||||
enabled: false
|
enabled: false
|
||||||
|
@ -477,10 +480,13 @@ census:
|
||||||
- 'osobatywy'
|
- 'osobatywy'
|
||||||
|
|
||||||
redirects:
|
redirects:
|
||||||
- { from: '^/neutratywy', to: '/s%C5%82ownik' }
|
- { from: '^/neutratywy$', to: '/s%C5%82owniki' }
|
||||||
- { from: '^/rzeczowniki', to: '/s%C5%82ownik' }
|
- { from: '^/rzeczowniki$', to: '/s%C5%82owniki' }
|
||||||
- { from: '^/slownik', to: '/s%C5%82ownik' }
|
- { from: '^/slownik$', to: '/s%C5%82owniki' }
|
||||||
- { from: '^/literatura', to: '/korpus' }
|
- { from: '^/slowniki$', to: '/s%C5%82owniki' }
|
||||||
|
- { from: '^/słownik$', to: '/s%C5%82owniki' }
|
||||||
|
- { from: '^/s%C5%82ownik$', to: '/s%C5%82owniki' }
|
||||||
|
- { from: '^/literatura$', to: '/korpus' }
|
||||||
|
|
||||||
api:
|
api:
|
||||||
examples:
|
examples:
|
||||||
|
|
|
@ -239,7 +239,7 @@
|
||||||
|
|
||||||
<T>nouns.inclusive.info</T>
|
<T>nouns.inclusive.info</T>
|
||||||
|
|
||||||
<details class="border mb-3">
|
<details class="border mb-3" ref="inclusivedictionarywrapper">
|
||||||
<summary class="bg-light p-3" @click="$refs.inclusivedictionary.loadEntries()">
|
<summary class="bg-light p-3" @click="$refs.inclusivedictionary.loadEntries()">
|
||||||
<h4 class="h5 d-inline">
|
<h4 class="h5 d-inline">
|
||||||
<Icon v="book-heart"/>
|
<Icon v="book-heart"/>
|
||||||
|
@ -251,13 +251,38 @@
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
<!--
|
||||||
|
<Separator icon="atom-alt"/>
|
||||||
|
|
||||||
|
<h3 :id="$t('nouns.terms.id')">
|
||||||
|
<Icon v="flag"/>
|
||||||
|
<T>nouns.terms.headerLong</T>
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<T>nouns.terms.info</T>
|
||||||
|
|
||||||
|
<details class="border mb-3" ref="termsdictionarywrapper">
|
||||||
|
<summary class="bg-light p-3" @click="$refs.termsdictionary.loadEntries()">
|
||||||
|
<h4 class="h5 d-inline">
|
||||||
|
<Icon v="flag"/>
|
||||||
|
<T>nouns.terms.headerLong</T>
|
||||||
|
</h4>
|
||||||
|
</summary>
|
||||||
|
<div class="border-top">
|
||||||
|
<TermsDictionary ref="termsdictionary"/>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
-->
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {Noun, NounDeclension} from "../../../src/classes";
|
import {Noun, NounDeclension} from "../../../src/classes";
|
||||||
|
import hash from "../../../plugins/hash";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
mixins: [ hash ],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
personNouns: [
|
personNouns: [
|
||||||
|
@ -441,6 +466,18 @@
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
mounted() {
|
||||||
|
this.handleHash(this.config.nouns.inclusive.hashNamespace, filter => {
|
||||||
|
this.$refs.inclusivedictionarywrapper.open = true;
|
||||||
|
this.$refs.inclusivedictionarywrapper.scrollIntoView();
|
||||||
|
this.$refs.inclusivedictionary.setFilter(filter);
|
||||||
|
});
|
||||||
|
this.handleHash(this.config.nouns.terms.hashNamespace, filter => {
|
||||||
|
this.$refs.termsdictionarywrapper.open = true;
|
||||||
|
this.$refs.termsdictionarywrapper.scrollIntoView();
|
||||||
|
this.$refs.termsdictionary.setFilter(filter);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
dukajNouns: 'ghost',
|
dukajNouns: 'ghost',
|
||||||
personNouns: 'user-friends',
|
personNouns: 'user-friends',
|
||||||
// inclusive: 'book-heart',
|
// inclusive: 'book-heart',
|
||||||
|
// terms: 'flag',
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
|
@ -194,9 +194,9 @@ sources:
|
||||||
moderation: 'Propozycje będą musiały zostać zatwierdzone przed opublikowaniem.'
|
moderation: 'Propozycje będą musiały zostać zatwierdzone przed opublikowaniem.'
|
||||||
|
|
||||||
nouns:
|
nouns:
|
||||||
header: 'Słownik'
|
header: 'Słowniki'
|
||||||
headerLong: 'Słownik: neutralne rzeczowniki'
|
headerLong: 'Słowniki neutralnego języka'
|
||||||
headerLonger: 'Neutralne i niebinarne rzeczowniki'
|
headerLonger: 'Słowniki neutralnego i niebinarnego języka'
|
||||||
description: 'Feminatywy feminatywami, ale prawdziwe wyzwanie to tworzenie neutratywów! Przedstawiamy tworzony przez społeczność słownik rzeczowników z wyszczególnieniem ich formy męskiej, żeńskiej i neutralnej.'
|
description: 'Feminatywy feminatywami, ale prawdziwe wyzwanie to tworzenie neutratywów! Przedstawiamy tworzony przez społeczność słownik rzeczowników z wyszczególnieniem ich formy męskiej, żeńskiej i neutralnej.'
|
||||||
intro:
|
intro:
|
||||||
- >
|
- >
|
||||||
|
@ -340,6 +340,20 @@ nouns:
|
||||||
categories: 'Kategorie'
|
categories: 'Kategorie'
|
||||||
sources: 'Linki źródłowe'
|
sources: 'Linki źródłowe'
|
||||||
|
|
||||||
|
terms:
|
||||||
|
header: 'Terminologia'
|
||||||
|
headerLong: 'Słownik terminologii queerowej'
|
||||||
|
id: 'terminologia'
|
||||||
|
info:
|
||||||
|
- >
|
||||||
|
Większość zwrotów i wyrażeń związanych ze środowiskiem LGBTQ+
|
||||||
|
funkcjonuje w polszczyźnie jako anglojęzyczne wstawki.
|
||||||
|
Poniżej przedstawiamy słownik tłumaczący, co dane określenia oznaczają,
|
||||||
|
i jakie proponujemy dla nich polskie tłumaczenia.
|
||||||
|
term: 'Określenie'
|
||||||
|
original: 'Pochodzenie'
|
||||||
|
definition: 'Definicja'
|
||||||
|
|
||||||
names:
|
names:
|
||||||
header: 'Imiona'
|
header: 'Imiona'
|
||||||
headerLong: 'Neutralne imiona'
|
headerLong: 'Neutralne imiona'
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
-- Up
|
||||||
|
|
||||||
|
CREATE TABLE terms (
|
||||||
|
id TEXT NOT NULL PRIMARY KEY,
|
||||||
|
term TEXT NOT NULL,
|
||||||
|
original TEXT NULL,
|
||||||
|
definition TEXT NOT NULL,
|
||||||
|
locale TEXT NOT NULL,
|
||||||
|
approved INTEGER NOT NULL,
|
||||||
|
base_id TEXT,
|
||||||
|
author_id TEXT NULL REFERENCES users(id),
|
||||||
|
deleted INTEGER NOT NULL DEFAULT 0
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Down
|
||||||
|
|
||||||
|
DROP TABLE terms;
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<h2>
|
<h2>
|
||||||
<Icon v="atom-alt"/>
|
<Icon v="book"/>
|
||||||
<T>nouns.headerLonger</T>
|
<T>nouns.headerLonger</T>
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
|
|
|
@ -44,6 +44,7 @@ app.use(require('./routes/pronouns').default);
|
||||||
app.use(require('./routes/sources').default);
|
app.use(require('./routes/sources').default);
|
||||||
app.use(require('./routes/nouns').default);
|
app.use(require('./routes/nouns').default);
|
||||||
app.use(require('./routes/inclusive').default);
|
app.use(require('./routes/inclusive').default);
|
||||||
|
app.use(require('./routes/terms').default);
|
||||||
app.use(require('./routes/pronounce').default);
|
app.use(require('./routes/pronounce').default);
|
||||||
app.use(require('./routes/census').default);
|
app.use(require('./routes/census').default);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,108 @@
|
||||||
|
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 terms WHERE id=${id}`);
|
||||||
|
if (base_id) {
|
||||||
|
await db.get(SQL`
|
||||||
|
UPDATE terms
|
||||||
|
SET deleted=1
|
||||||
|
WHERE id = ${base_id}
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
await db.get(SQL`
|
||||||
|
UPDATE terms
|
||||||
|
SET approved = 1, base_id = NULL
|
||||||
|
WHERE id = ${id}
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const router = Router();
|
||||||
|
|
||||||
|
router.get('/terms', async (req, res) => {
|
||||||
|
return res.json(await req.db.all(SQL`
|
||||||
|
SELECT i.*, u.username AS author FROM terms i
|
||||||
|
LEFT JOIN users u ON i.author_id = u.id
|
||||||
|
WHERE i.locale = ${req.config.locale}
|
||||||
|
AND i.approved >= ${req.admin ? 0 : 1}
|
||||||
|
AND i.deleted = 0
|
||||||
|
ORDER BY i.term
|
||||||
|
`));
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/terms/search/:term', async (req, res) => {
|
||||||
|
const term = '%' + req.params.term + '%';
|
||||||
|
return res.json(await req.db.all(SQL`
|
||||||
|
SELECT i.*, u.username AS author FROM terms i
|
||||||
|
LEFT JOIN users u ON i.author_id = u.id
|
||||||
|
WHERE i.locale = ${req.config.locale}
|
||||||
|
AND i.approved >= ${req.admin ? 0 : 1}
|
||||||
|
AND i.deleted = 0
|
||||||
|
AND (i.term like ${term} OR i.original like ${term})
|
||||||
|
ORDER BY i.term
|
||||||
|
`));
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/terms/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 terms (id, term, original, definition, approved, base_id, locale, author_id)
|
||||||
|
VALUES (
|
||||||
|
${id},
|
||||||
|
${req.body.term.join('|')}, ${req.body.original.join('|')}, ${req.body.definition},
|
||||||
|
0, ${req.body.base}, ${req.config.locale}, ${req.user ? req.user.id : null}
|
||||||
|
)
|
||||||
|
`);
|
||||||
|
|
||||||
|
if (req.admin) {
|
||||||
|
await approve(req.db, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.json('ok');
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/terms/hide/:id', async (req, res) => {
|
||||||
|
if (!req.admin) {
|
||||||
|
res.status(401).json({error: 'Unauthorised'});
|
||||||
|
}
|
||||||
|
|
||||||
|
await req.db.get(SQL`
|
||||||
|
UPDATE terms
|
||||||
|
SET approved = 0
|
||||||
|
WHERE id = ${req.params.id}
|
||||||
|
`);
|
||||||
|
|
||||||
|
return res.json('ok');
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/terms/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('/terms/remove/:id', async (req, res) => {
|
||||||
|
if (!req.admin) {
|
||||||
|
res.status(401).json({error: 'Unauthorised'});
|
||||||
|
}
|
||||||
|
|
||||||
|
await req.db.get(SQL`
|
||||||
|
UPDATE terms
|
||||||
|
SET deleted=1
|
||||||
|
WHERE id = ${req.params.id}
|
||||||
|
`);
|
||||||
|
|
||||||
|
return res.json('ok');
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
|
@ -675,6 +675,31 @@ export class InclusiveEntry {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class TermsEntry {
|
||||||
|
constructor({id, term, original, definition, approved = true, base_id = null}) {
|
||||||
|
this.id = id;
|
||||||
|
this.term = term.split('|');
|
||||||
|
this.original = original ? original.split('|') : [];
|
||||||
|
this.definition = definition;
|
||||||
|
this.approved = !!approved;
|
||||||
|
this.base = base_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
matches(filter) {
|
||||||
|
if (!filter) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let field of ['term', 'original']) {
|
||||||
|
for (let value of this[field]) {
|
||||||
|
if (value.toLowerCase().indexOf(filter.toLowerCase()) > -1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class Name {
|
export class Name {
|
||||||
constructor(name, origin, meaning, usage, legally, pros, cons, notablePeople, count, links) {
|
constructor(name, origin, meaning, usage, legally, pros, cons, notablePeople, count, links) {
|
||||||
|
|
Reference in New Issue