Merge branch 'main' into pl-pronouns-new

This commit is contained in:
Andrea Vos 2023-02-04 12:23:22 +01:00
commit c6cf5600e0
19 changed files with 448 additions and 205 deletions

View File

@ -1,7 +1,7 @@
<template>
<span>
<span v-if="range" class="badge bg-primary">{{ event.getRange(year) }}</span>
<Flag v-if="event.flag" name="" :alt="$t('flags_alt.' + event.flag.replace(/'/g, '*').replace(/ /g, '_')) || ''" :img="`/flags/${event.flag}.png`"/>
<Flag v-if="event.flag" termkey="" name="" :alt="$t('flags_alt.' + event.flag.replace(/'/g, '*').replace(/ /g, '_')) || ''" :img="`/flags/${event.flag}.png`"/>
<Icon v-else v="arrow-circle-right"/>
<T v-if="$te(`calendar.events.${eventName}`)" :params="{param: eventParam}">calendar.events.{{eventName}}</T>
<LinkedText v-else :text="eventName"/>

View File

@ -0,0 +1,108 @@
<template>
<div class="form-group">
<draggable tag="ul" v-model="images" handle=".handle" ghostClass="ghost" @end="$emit('input', images)" class="list-unstyled">
<li v-for="(image, i) in images" class="mb-4">
<div class="input-group mb-1">
<button class="btn btn-light border handle" type="button" :aria-label="$t('table.sort')">
<Icon v="bars"/>
</button>
<div class="d-flex flex-grow-1 flex-column">
<div class="d-flex">
<ImageThumb :id="images[i].value" smallSize="flag" bigSize="flag" size="2.4em"/>
<input v-model="images[i].name" type="text" class="form-control"
@keyup="update" @change="update"
:placeholder="$t('profile.flagsCustomForm.label')"
required :maxlength="maxLength"/>
</div>
<div>
<input v-model="images[i].description" type="text" class="form-control form-control-sm"
@keyup="update" @change="update"
:placeholder="$t('profile.flagsCustomForm.description')"
maxlength="512"
/>
</div>
<div>
<input v-model="images[i].alt" type="text" class="form-control form-control-sm"
@keyup="update" @change="update"
:placeholder="$t('profile.flagsCustomForm.alt')"
maxlength="512"
/>
</div>
<div>
<input v-model="images[i].link" type="url" class="form-control form-control-sm"
@keyup="update" @change="update"
:placeholder="$t('profile.flagsCustomForm.link')"
/>
<p v-if="images[i].link && !isValidLink(images[i].link)" class="small text-danger m-1">
<Icon v="exclamation-triangle"/>
<span class="ml-1">{{$t('crud.validation.invalidLink')}}</span>
</p>
</div>
</div>
<button class="btn btn-outline-danger" type="button" @click.prevent="removeFile(image.value)" :aria-label="$t('crud.remove')">
<Icon v="times"/>
</button>
</div>
</li>
<li slot="footer">
<ImageUploader v-if="maxitems === null || value.length < maxitems" multiple :name="name" form @uploaded="addFiles" :sizes="sizes"/>
<div v-if="images.length" class="alert alert-info small mt-3 mb-0">
<p>
<Icon v="info-circle"/>
<T>profile.flagsCustomForm.altExample</T><T>quotation.colon</T>
</p>
<p class="simple">
<Flag img="/flags/Progress Pride_.png" name="" :alt="$t('flags_alt.Progress_Pride_')"/>
<T>flags_alt.Progress_Pride_</T>
</p>
</div>
</li>
<li v-if="maxitems && value.length > maxitems" class="alert alert-danger">
<Icon v="exclamation-triangle"/>
<T :params="{maxlength: maxitems}" class="ml-1">crud.validation.listMaxLength</T>
</li>
</draggable>
</div>
</template>
<script>
import draggable from 'vuedraggable'
import {curry, isValidLink} from "../src/helpers";
export default {
components: {
draggable,
},
props: {
value: {},
name: {'default': 'images'},
maxLength: {'default': 24},
sizes: {'default': 'all'},
maxitems: { 'default': null },
},
data() {
return {
images: this.value,
curry,
isValidLink,
}
},
watch: {
value() {
this.images = this.value;
}
},
methods: {
addFiles(fileIds) {
this.$emit('input', [...this.images, ...fileIds.map(id => { return {value: id, name: '', description: '', link: '', alt: ''}})]);
},
async removeFile(id) {
await this.$confirm(this.$t('crud.removeConfirm'), 'danger');
this.$emit('input', this.images.filter(i => i.value !== id));
},
update() {
this.$emit('input', this.images);
},
},
}
</script>

View File

@ -1,17 +1,17 @@
<template>
<span class="flag-wrapper">
<a v-if="link" :href="`/${config.nouns.route}/${config.terminology.route}#${link.toLowerCase()}`" :title="alt">
<img v-if="missing === false" :src="img" :alt="name" class="flag-mini rounded" @error="missing = true"/>
<a v-if="link" :href="link.startsWith('http') ? link : `/${config.nouns.route}/${config.terminology.route}#${link.toLowerCase()}`" target="_blank" rel="noopener" :title="alt">
<img v-if="missing === false" :src="img" :alt="alt" class="flag-mini rounded" @error="missing = true"/>
<LocaleLink locale="en" v-else link="/blog/missing-flags" class="text-danger"><Icon v="exclamation-circle"/></LocaleLink>
<Twemoji><Spelling escape :text="name"/><sup v-if="custom" class="text-muted"><small><Icon v="user"/></small></sup><sup v-if="asterisk" class="text-muted"><small>*</small></sup></Twemoji>
</a>
<span v-else :title="alt">
<img v-if="missing === false" :src="img" :alt="name" class="flag-mini rounded" @error="missing = true"/>
<img v-if="missing === false" :src="img" :alt="alt" class="flag-mini rounded" @error="missing = true"/>
<LocaleLink locale="en" v-else link="/blog/missing-flags" class="text-danger"><Icon v="exclamation-circle"/></LocaleLink>
<Twemoji><Spelling escape :text="name"/><sup v-if="custom" class="text-muted"><small><Icon v="user"/></small></sup><sup v-if="asterisk" class="text-muted"><small>*</small></sup></Twemoji>
</span>
<span class="flag-preview bg-white rouded p-2 border">
<img v-if="missing === false" :src="img" :alt="name" class="rounded" @error="missing = true"/>
<img v-if="missing === false" :src="img" :alt="alt" class="rounded" @error="missing = true"/>
<LocaleLink locale="en" v-else link="/blog/missing-flags" class="text-danger"><Icon v="exclamation-circle"/></LocaleLink>
<span v-if="asterisk" class="alert alert-warning small d-block-force mt-2 mb-0 p-2">
*
@ -21,6 +21,8 @@
<Icon v="user"/>
<T>profile.flagsCustomWarning</T>
</span>
<span v-if="description" class="d-block-force alert p-2"><strong>{{name}}</strong> {{description}}</span>
<span v-if="alt" class="d-block-force alert p-2 small mb-0 text-muted"><T>crud.alt</T><T>quotation.colon</T> {{alt}}</span>
</span>
</span>
</template>
@ -28,9 +30,12 @@
<script>
export default {
props: {
termkey: { required: true },
name: { required: true },
alt: { required: true },
img: { required: true },
description: { },
customlink: { },
terms: { },
custom: { type: Boolean },
asterisk: { type: Boolean },
@ -42,6 +47,10 @@
},
computed: {
link() {
if (this.customlink) {
return this.customlink;
}
if (!this.config.terminology.enabled || !(this.config.terminology.published || this.$isGranted('terms'))) {
return null;
}
@ -50,25 +59,25 @@
for (let term of this.terms || []) {
// exact match
if (term.key && term.key.toLowerCase() === this.alt.toLowerCase()) {
if (term.key && term.key.toLowerCase() === this.termkey.toLowerCase()) {
return term.key;
}
if (term.term.toLowerCase() === this.name.toLowerCase()) {
return this.name;
}
if (term.original.toLowerCase() === this.alt.toLowerCase()) {
return this.alt;
if (term.original.toLowerCase() === this.termkey.toLowerCase()) {
return this.termkey;
}
// fallback
if (term.key && term.key.toLowerCase().includes(this.alt.toLowerCase())) {
if (term.key && term.key.toLowerCase().includes(this.termkey.toLowerCase())) {
fallback = term.key;
}
if (term.term.toLowerCase().includes(this.name.toLowerCase())) {
fallback = this.name;
}
if (term.original.toLowerCase().includes(this.alt.toLowerCase())) {
fallback = this.alt;
if (term.original.toLowerCase().includes(this.termkey.toLowerCase())) {
fallback = this.termkey;
}
}

View File

@ -12,13 +12,15 @@
inverse: { type: Boolean }
},
data() {
let values = Array.isArray(this.v) ? this.v : [this.v];
values = values.filter(x => !!x);
return this.buildQueue(this.v);
},
watch: {
v(v) {
const {value, fallback} = this.buildQueue(v);
return {
value: values.shift(),
fallbacks: values,
};
this.value = value;
this.fallback = fallback;
},
},
computed: {
valueParts() {
@ -44,6 +46,19 @@
},
},
methods: {
buildQueue(v) {
let values = Array.isArray(v) ? v : [v];
values = values.filter(x => !!x);
if (!values.length) {
values = ['spacer'];
}
return {
value: values.shift(),
fallbacks: values,
};
},
fallBack() {
if (!this.fallbacks.length) { return; }

View File

@ -41,19 +41,24 @@
<ClientOnly>
<ul class="list-inline">
<li v-for="flag in profile.flags" v-if="allFlags[flag]" class="list-inline-item p-1">
<Flag :name="flag.startsWith('-') ? allFlags[flag] : $translateForPronoun(allFlags[flag], mainPronoun)"
:alt="$t('flags_alt.' + flag.replace(/'/g, '*').replace(/ /g, '_')) || allFlags[flag]"
<Flag :termkey="allFlags[flag]"
:name="flag.startsWith('-') ? allFlags[flag] : $translateForPronoun(allFlags[flag], mainPronoun)"
:alt="$t('flags_alt.' + flag.replace(/'/g, '*').replace(/ /g, '_'))"
:img="`/flags/${flag}.png`"
:terms="terms || []"
:asterisk="flagsAsterisk.includes(flag)"
/>
</li>
<li v-for="{value: flag, name} in profile.customFlags" class="list-inline-item p-1">
<Flag :name="name"
:alt="name"
<li v-for="{value: flag, name, description, alt, link} in profile.customFlags" class="list-inline-item p-1">
<Flag :termkey="name"
:name="name"
:alt="alt || ''"
:img="buildImageUrl(flag, 'flag')"
:terms="terms|| []"
custom/>
custom
:description="description"
:customlink="link"
/>
</li>
</ul>
</ClientOnly>

View File

@ -664,6 +664,12 @@ profile:
flagsCustom: 'Upload custom flags'
flagsCustomWarning: 'This flag has been uploaded by a user. The team of pronouns.page is not responsible for it.'
flagsAsterisk: 'This is not a queer identity, but we include it for people who are queer in other ways (eg. straight trans people).'
flagsCustomForm:
label: 'Label name'
description: '(optional) A short description of the label'
link: '(optional) Link to more info'
alt: '(optional) Alternative text describing the appearance of the flag (see below)'
altExample: 'An example of how the alternative text might be phrased'
links: 'Links'
linksRecommended: 'We recommend linking to'
verifiedLinks:
@ -787,6 +793,8 @@ crud:
validation:
genericForm: 'The submitted form is invalid. Correct the issues and send it again.'
listMaxLength: 'The maximum number of elements in this list is %maxlength%.'
invalidLink: 'Invalid URL format'
alt: 'Alt text'
footer:
license: >
@ -1173,6 +1181,130 @@ flags:
Two_Spirit: 'Two Spirit'
Xenogender: 'Xenogender'
flags_alt:
-de-Gay: 'A rectangular flag with six equal-width horizontal stripes: red, orange, yellow, green, blue and purple'
-Drag: 'A rectangular flag with three equal-width vertical stripes: light purple, white and blue; emblem on the white field: a pink crown with three pointy ends.'
-en-Genderdoe: ''
-pl-Dukaizmy: 'A rectangular flag with four equal-width horizontal stripes: yellow, white, purple, black; emblem: a white ghost with black eyes, open mouth, and a speech bubble saying “łu!”'
-pl-Gay: 'A rectangular flag with six equal-width horizontal stripes: red, orange, yellow, green, blue and purple'
-pl-Rodzaj_neutralny: 'A rectangular flag with four equal-width horizontal stripes: yellow, white, purple, black; emblem: a black moose holding a crowbar.'
-pl-Rodzaj_nijaki: 'A rectangular flag with four equal-width horizontal stripes: yellow, white, purple, black; emblem: a black moose holding a crowbar.'
_black-ribbon: 'A black ribbon'
_Butch: 'A rectangular flag with seven equal-width horizontal stripes, forming a gradient from dark grey blue on top, to white in the middle, to purple on the bottom'
_hrc: 'A square logo with red background and a pink equality sign “=”'
_law: 'A paragraph symbol: “§”'
_mspec_lesbians: 'A rectangular flag with five equal-width horizontal stripes: light pink, magenta, purple, blue, teal'
_red-ribbon: 'A red ribbon'
_sex-work: 'An open red umbrella'
_Unlabelled: 'A rectangular flag with four equal-width horizontal stripes: light green, white, light blue, light orange'
_yellow-ribbon: 'A yellow ribbon'
_zaimki: 'Logo of pronouns.page: two stylised letters “P”, first one rotated by 180°'
Abroromantic: 'A rectangular flag with five equal-width horizontal stripes: tealish green, peppermint green, white, pink, and pinkish red; a heart shape in the middle that makes colours on its inside more vibrant and on the outside subdued'
Abrosexual: 'A rectangular flag with five equal-width horizontal stripes: tealish green, peppermint green, white, pink, and pinkish red'
Achillean: ''
Agender: 'A rectangular flag with seven equal-width horizontal stripes: black, grey, white, lime green, white, grey, black'
Alloromantic_Asexual: ''
Ambiamorous: ''
Ambiamorous_: ''
Anarcha-Queer: 'A rectangular flag split along the positive diagonal; upper-left part pink, bottom-right part black'
Androgyne: ''
Androsexual: ''
Aporagender: ''
Archaeopronouns: ''
Aroace: 'A rectangular flag with five equal-width horizontal stripes: orange, yellow, white, cyan, dark blue'
Aromantic: 'A rectangular flag with five equal-width horizontal stripes: green, light green, white, grey, black'
Aromantic_Allosexual: ''
Asexual: 'A rectangular flag with four equal-width horizontal stripes: black, grey, white, purple'
Autigender: ''
Bear: 'A rectangular flag with seven equal-width horizontal stripes: brown, orange, yellow, bright yellow, white, grey, black; emblem in upper hoist quarter: a black footprint of a bear.'
Bicurious: ''
Bigender: ''
Bigender_: ''
Biromantic: 'A rectangular flag with the following horizontal stripes: a wide pink, a narrow purple, and a wide blue; a heart shape in the middle that makes colours on its inside more vibrant and on the outside subdued'
Bisexual: 'A rectangular flag with the following horizontal stripes: a wide pink, a narrow purple, and a wide blue'
Butch: 'A rectangular flag with seven equal-width horizontal stripes, forming a gradient from dark grey blue on top, to white in the middle, to purple on the bottom'
Ceteroromantic: ''
Ceterosexual: ''
Cis_Ally: 'A rectangular flag with five equal-width horizontal stripes: blue, pink, white, pink, blue; overlaid a black upwards-facing chevron'
Demiboy: 'A rectangular flag with seven equal-width horizontal stripes: dark grey, light grey, cyan, white, cyan, light grey, dark grey'
Demigender: ''
Demigirl: 'A rectangular flag with seven equal-width horizontal stripes: dark grey, light grey, pink, white, pink, light grey, dark grey'
Demiromantic: ''
Demisexual: ''
Diamoric: ''
Enbian: ''
Fa*afafine: ''
Femme: ''
Gay: 'A rectangular flag with six equal-width horizontal stripes: red, orange, yellow, green, blue and purple'
Gay_: 'A rectangular flag with seven equal-width horizontal stripes: dark green, greenish cyan, teal, white, cyan, purply blue, purple'
Gender_Questioning: ''
Genderfae: ''
Genderfaun: ''
Genderfluid: 'A rectangular flag with five equal-width horizontal stripes: pink, white, purple, black, blue'
Genderflux: ''
Genderqueer: ''
Greyaromantic: ''
Greyasexual: ''
Gynesexual: ''
Heteroflexible: 'A rectangular flag with three equal-width horizontal stripes: blue, white and pink; an extra vertical stripe through the middle thats coloured like a rainbow flag: red, orange, yellow, green, blue, purple'
Heteroromantic: 'A rectangular flag with three equal-width horizontal stripes: blue, white and pink; a heart shape in the middle that makes colours on its inside more vibrant and on the outside subdued'
Heterosexual: 'A rectangular flag with three equal-width horizontal stripes: blue, white and pink'
Hijra: 'A rectangular flag with following horizontal stripes, from the top: a wide pink, narrow white, crimson and white, wide light blue'
Homoflexible: 'A rectangular flag with six equal-width horizontal stripes: red, orange, yellow, green, blue and purple; an extra vertical stripe through the middle thats coloured like a heterosexual flag: blue, white and pink'
Homoromantic: 'A rectangular flag with six equal-width horizontal stripes: red, orange, yellow, green, blue and purple; a heart shape in the middle that makes colours on its inside more vibrant and on the outside subdued'
Intersex: 'A rectangular flag with a yellow background and a purple circle in the centre'
Leather_Pride: 'A rectangular flag with nine equal-width horizontal stripes: black, blue, black, blue, white,blue, black, blue, black; emblem in upper hoist quarter: a red heart symbol at an angle'
Lesbian: 'A rectangular flag with five equal-width horizontal stripes: orangy red, orange, white, light magenta, dark magenta'
Lesbian_: 'A rectangular flag with a purple background and black labret in the middle'
Lesbian__: 'A rectangular flag with six equal-width horizontal stripes: red, orange, yellow, green, blue and purple'
Lesbian___: 'A rectangular flag with four equal-width horizontal stripes: purple, pink, yellow, green'
Lesbiromantic: 'A rectangular flag with five equal-width horizontal stripes: orangy red, orange, white, light magenta, dark magenta; a heart shape in the middle that makes colours on its inside more vibrant and on the outside subdued'
LGBTQ: 'A rectangular flag with six equal-width horizontal stripes: red, orange, yellow, green, blue and purple'
Maverique: ''
Monoamorous: ''
Monogamous: ''
Muxe: ''
Nebularomantic: ''
Neopronouns: ''
Neopronouns_: ''
Neutrois: ''
Nonbinary: 'A rectangular flag with four equal-width horizontal stripes: yellow, white, purple, black'
Omniromantic: ''
Omnisexual: ''
Oriented_Aroace: ''
Pangender: ''
Panromantic: ''
Pansexual: ''
Polyamorous: 'A rectangular flag with a background split in two in the shape of a wave: top part crimson, bottom a lighter crimson-pink; emblem in the middle: an ivory white heart composed of the infinity symbol “∞” and a downwards-pointing chevron'
Polyamorous_: 'A rectangular flag with three equal-width horizontal stripes: blue, red, black; emblem in the middle: a yellow Green letter pi “π”'
Polyamorous__: 'A rectangular flag with four equal-width horizontal stripes: light green, darker green, light blue and dark blue; emblem: a white heart intertwined with the infinity symbol “∞”'
Polyamorous___: 'A rectangular flag with three equal-width horizontal stripes: blue, red, black; emblem in the middle: a yellow heart intertwined with the infinity symbol “∞”'
Polyromantic: ''
Polysexual: ''
Pomoromantic: ''
Pomosexual: ''
Progress_Pride: 'A rectangular flag with six equal-width horizontal stripes: red, orange, yellow, green, blue and purple; a chevron (triangle) on the left side composed of stripes of the following colours (from the left): white, pink, light blue, brown, black'
Progress_Pride_: 'A rectangular flag with six equal-width horizontal stripes: red, orange, yellow, green, blue and purple; a chevron (triangle) on the left side composed of stripes of the following colours (from the left): yellow with a purple circle, white, pink, light blue, brown, black'
Queer: 'A rectangular flag with six equal-width horizontal stripes: red, orange, yellow, green, blue and purple'
Queer_: 'A rectangular flag with ivory white background and two downwards-pointing chevrons: light pink and washed-out purple'
Queerian: ''
Queerplatonic: ''
Quoiromantic: ''
Sapphic: ''
Sapphic_: ''
Sexuality_Questioning: ''
Straight_Ally: 'A rectangular flag with six equal-width horizontal stripes: red, orange, yellow, green, blue and purple; overlaid a black upwards-facing chevron'
Toric: ''
Transfeminine: 'A rectangular flag with seven equal-width horizontal stripes creating a gradient from blue on top, through pink in the middle, to blue on the bottom'
Transgender: 'A rectangular flag with five equal-width horizontal stripes: blue, pink, white, pink, blue'
Transmasculine: 'A rectangular flag with seven equal-width horizontal stripes creating a gradient from pink on top, through blue in the middle, to pink on the bottom'
Transneutral: 'A rectangular flag with seven equal-width horizontal stripes creating a gradient from blue on top, through yellow in the middle, to pink on the bottom'
Trigender: ''
Trixic: ''
Two_Spirit: ''
Two_Spirit_: ''
Xenogender: ''
calendar:
header: 'Calendar'
headerLong: 'Queer Calendar'

View File

@ -805,6 +805,12 @@ profile:
flagsCustom: 'Upload custom flags'
flagsCustomWarning: 'This flag has been uploaded by a user. The team of pronouns.page is not responsible for it.'
flagsAsterisk: 'This is not a queer identity, but we include it for people who are queer in other ways (eg. straight trans people).'
flagsCustomForm:
label: 'Label name'
description: '(optional) A short description of the label'
link: '(optional) Link to more info'
alt: '(optional) Alternative text describing the appearance of the flag (see below)'
altExample: 'An example of how the alternative text might be phrased'
links: 'Links'
linksRecommended: 'We recommend linking to'
verifiedLinks:
@ -932,6 +938,7 @@ crud:
validation:
genericForm: 'The submitted form is invalid. Correct the issues and send it again.'
listMaxLength: 'The maximum number of elements in this list is %maxlength% (more info {/blog/length-validation=in this blog post}).'
invalidLink: 'Invalid URL format'
footer:
license: >
@ -1314,130 +1321,6 @@ flags:
Two_Spirit: 'Two Spirit'
Xenogender: 'Xenogender'
flags_alt:
-de-Gay: ''
-Drag: ''
-en-Genderdoe: ''
-pl-Dukaizmy: ''
-pl-Gay: ''
-pl-Rodzaj_neutralny: ''
-pl-Rodzaj_nijaki: ''
_black-ribbon: ''
_Butch: ''
_hrc: ''
_law: ''
_mspec_lesbians: ''
_red-ribbon: ''
_sex-work: ''
_Unlabelled: ''
_yellow-ribbon: ''
_zaimki: ''
Abroromantic: ''
Abrosexual: ''
Achillean: ''
Agender: ''
Alloromantic_Asexual: ''
Ambiamorous: ''
Ambiamorous_: ''
Anarcha-Queer: ''
Androgyne: ''
Androsexual: ''
Aporagender: ''
Archaeopronouns: ''
Aroace: ''
Aromantic: ''
Aromantic_Allosexual: ''
Asexual: ''
Autigender: ''
Bear: ''
Bicurious: ''
Bigender: ''
Bigender_: ''
Biromantic: ''
Bisexual: ''
Butch: ''
Ceteroromantic: ''
Ceterosexual: ''
Cis_Ally: ''
Demiboy: ''
Demigender: ''
Demigirl: ''
Demiromantic: ''
Demisexual: ''
Diamoric: ''
Enbian: ''
Fa*afafine: ''
Femme: ''
Gay: 'Rectangular flag with six equal-width horizontal stripes: red, orange, yellow, green, blue and purple'
Gay_: ''
Gender_Questioning: ''
Genderfae: ''
Genderfaun: ''
Genderfluid: ''
Genderflux: ''
Genderqueer: ''
Greyaromantic: ''
Greyasexual: ''
Gynesexual: ''
Heteroflexible: ''
Heteroromantic: ''
Heterosexual: ''
Hijra: ''
Homoflexible: ''
Homoromantic: ''
Intersex: ''
Leather_Pride: ''
Lesbian: ''
Lesbian_: ''
Lesbian__: ''
Lesbian___: ''
Lesbiromantic: ''
LGBTQ: 'Rectangular flag with six equal-width horizontal stripes: red, orange, yellow, green, blue and purple; a chevron (triangle) on the left side composed of stripes of the following colours (from the left): white, pink, light blue, brown, black'
Maverique: ''
Monoamorous: ''
Monogamous: ''
Muxe: ''
Nebularomantic: ''
Neopronouns: ''
Neopronouns_: ''
Neutrois: ''
Nonbinary: 'Rectangular flag with four equal-width horizontal stripes: yellow, white, purple, black'
Omniromantic: ''
Omnisexual: ''
Oriented_Aroace: ''
Pangender: ''
Panromantic: ''
Pansexual: ''
Polyamorous: ''
Polyamorous_: ''
Polyamorous__: ''
Polyamorous___: ''
Polyromantic: ''
Polysexual: ''
Pomoromantic: ''
Pomosexual: ''
Progress_Pride: 'Rectangular flag with six equal-width horizontal stripes: red, orange, yellow, green, blue and purple; a chevron (triangle) on the left side composed of stripes of the following colours (from the left): white, pink, light blue, brown, black'
Progress_Pride_: 'Rectangular flag with six equal-width horizontal stripes: red, orange, yellow, green, blue and purple; a chevron (triangle) on the left side composed of stripes of the following colours (from the left): yellow with a purple circle, white, pink, light blue, brown, black'
Queer: 'Rectangular flag with six equal-width horizontal stripes: red, orange, yellow, green, blue and purple; a chevron (triangle) on the left side composed of stripes of the following colours (from the left): white, pink, light blue, brown, black'
Queer_: 'Rectangular flag with ivory white background and two downwards-pointing chevrons: light pink and washed-out purple'
Queerian: ''
Queerplatonic: ''
Quoiromantic: ''
Sapphic: ''
Sapphic_: ''
Sexuality_Questioning: ''
Straight_Ally: ''
Toric: ''
Transfeminine: ''
Transgender: ''
Transmasculine: ''
Transneutral: ''
Trigender: ''
Trixic: ''
Two_Spirit: ''
Two_Spirit_: ''
Xenogender: ''
calendar:
header: 'Calendar'
headerLong: 'Queer Calendar'

View File

@ -107,4 +107,11 @@ module.exports = [
'profile.sensitive.hide',
'profile.sensitive.email.subject',
'profile.sensitive.email.content',
'crud.validation.invalidLink',
'profile.flagsCustomForm.label',
'profile.flagsCustomForm.description',
'profile.flagsCustomForm.link',
'profile.flagsCustomForm.alt',
'profile.flagsCustomForm.altExample',
'crud.alt',
];

View File

@ -6,7 +6,7 @@ home:
link: '홈페이지'
header: '대명사'
headerLong: '대명사 목록'
welcome: 'pronouns.page로 환영합니다!'
welcome: 'pronouns.page 로 환영합니다!'
intro: >
We're creating a source of information about nonbinary and gender neutral language.
why: '대명사는 왜 그렇게 중요한가요?'
@ -731,8 +731,8 @@ confirm:
yes: '예, 확신합니다'
no: '아니요, 취소해요'
ok: '네'
save: 'Save'
dismiss: 'Dismiss'
save: '변경사항 저장 을'
dismiss: '변경사항 제거'
terms:
header: '서비스 약관'
@ -790,17 +790,17 @@ terms:
queerphobia: 'queerphobia'
exclusionism: 'queer exclusionism'
sexism: 'sexism'
misogyny: 'misogyny'
harassment: 'harassment'
impersonation: 'impersonation'
misogyny: '여성 혐오'
harassment: '괴롭힘'
impersonation: '사칭'
selfHarm: 'encouraging self-harm and/or suicide' # TODO
childPornography: 'child pornography'
unlawfulConduct: 'unlawful conduct'
misinformation: 'misinformation'
misinformation: '오보'
doxxing: 'sharing of someone else''s personal data'
spam: 'spam'
trolling: 'trolling'
advertisement: 'advertisement'
spam: '스팸'
trolling: '트롤링'
advertisement: '광고'
copyright: 'copyright or trademark violations'
# TODO
violationsStrict: >
@ -908,7 +908,7 @@ captcha:
mode:
light: '라이트 모드'
automatic: 'Automatic'
automatic: '자동적 인'
dark: '다크 모드'
reducedColours: '감소된 색깔'

View File

@ -1876,7 +1876,7 @@ census:
- ['liczba mnoga, rodzaj męskoosobowy', '„byliśmy zmęczeni”']
- ['liczba mnoga, rodzaj niemęskoosobowy', '„byłyśmy zmęczone”']
- ['liczba mnoga, rodzaj neutralny', '„byłośmy zmęczone”']
- ['liczba mnoga, rodzaj postpłciowy', '„byłuśmy zmęczone”']
- ['liczba mnoga, rodzaj postpłciowy', '„byłuśmy zmęczony”']
- ['unikanie form nacechowanych płciowo', '„dopadło mnie zmęczenie”']
aggregates:
binarne:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 KiB

After

Width:  |  Height:  |  Size: 113 KiB

View File

@ -1371,6 +1371,12 @@ profile:
flagsCustom: 'Dodaj inne flagi'
flagsCustomWarning: 'Ta flaga została wgrana przez osobę użytkującą. Ekipa zaimki.pl nie jest za nią odpowiedzialna.'
flagsAsterisk: 'To nie jest queerowa tożsamość, ale uwględniamy ją tutaj przez wzgląd na osoby, które są queerowe na inne sposoby (np. hetero osoby trans).'
flagsCustomForm:
label: 'Nazwa etykietki'
description: '(nieobowiązkowe) Krótki opis etykietki'
link: '(nieobowiązkowe) Link do strony z informacjami o etykietce'
alt: '(nieobowiązkowe) Opis obrazka dla czytników ekranowych'
altExample: 'Przykładowy opis obrazka dla czytników ekranowych'
links: 'Linki'
linksRecommended: 'Polecamy dodać link do'
verifiedLinks:
@ -1563,6 +1569,8 @@ crud:
validation:
genericForm: 'Wysłany formularz jest niepoprawny. Napraw błędy i spróbuj ponownie.'
listMaxLength: 'Maksymalna liczba elementów na tej liście to %maxlength%.'
invalidLink: 'Niepoprawny format URL'
alt: 'Opis obrazka'
footer:
license: >

View File

@ -219,7 +219,7 @@ export default {
LOCALE: config.locale,
LOCALES: locales,
FLAGS: buildFlags(),
FLAGS_ASTERISK: ['Heteroromantic', 'Heterosexual', 'Monoamorous', 'Monogamous'],
FLAGS_ASTERISK: ['Heteroromantic', 'Heterosexual', 'Monoamorous', 'Monogamous', 'Cis Ally', 'Straight Ally'],
BUCKET: `https://${process.env.AWS_S3_BUCKET}.s3-${process.env.AWS_REGION}.amazonaws.com`,
CLOUDFRONT: process.env.CLOUDFRONT,
STATS_FILE: process.env.STATS_FILE,

View File

@ -94,6 +94,7 @@
icon: 'id-card',
endpoints: {
profile_get: ['GET', '/api/profile/get/{username}?version=2', undefined, ['Note that the <code>birthday</code> field will only be available when querying your own account; otherwise only the calucaled <code>age</code> might be available (if the person has filled out their birthday)']],
profile_get_by_id: ['GET', '/api/profile/get-id/{id}?version=2', undefined, ['Note that the <code>birthday</code> field will only be available when querying your own account; otherwise only the calucaled <code>age</code> might be available (if the person has filled out their birthday)']],
},
}, {
enabled: this.config.calendar.enabled,

View File

@ -158,7 +158,7 @@
<T>profile.flagsCustom</T>
</summary>
<div class="border-top">
<ImageWidgetRich v-model="customFlags" sizes="flag" :maxitems="128"/>
<CustomFlagsWidget v-model="customFlags" sizes="flag" :maxitems="128"/>
</div>
</details>
<PropagateCheckbox field="customFlags" :before="beforeChanges.customFlags" :after="customFlags" v-if="otherProfiles > 0" @change="propagateChanged"/>
@ -170,8 +170,16 @@
<T>profile.links</T>
</template>
<template v-slot:links>
<ListInput v-model="links" v-slot="s" :maxitems="128">
<input v-model="s.val" type="url" class="form-control" @keyup="s.update(s.val)" @paste="$nextTick(() => s.update(s.val))" @change="s.update(s.val)" required/>
<ListInput v-model="links" :maxitems="128">
<template v-slot="s">
<input v-model="s.val" type="url" class="form-control" @keyup="s.update(s.val)" @paste="$nextTick(() => s.update(s.val))" @change="s.update(s.val)" required/>
</template>
<template v-slot:validation="s">
<p v-if="s.val && !isValidLink(s.val)" class="small text-danger">
<Icon v="exclamation-triangle"/>
<span class="ml-1">{{$t('crud.validation.invalidLink')}}</span>
</p>
</template>
</ListInput>
<PropagateCheckbox field="links" :before="beforeChanges.links" :after="links" v-if="otherProfiles > 0" @change="propagateChanged"/>
<p class="small text-muted mb-0">
@ -278,7 +286,7 @@
</template>
<script>
import {head, buildList, buildDict} from "../src/helpers";
import {head, buildList, buildDict, isValidLink} from "../src/helpers";
import { pronouns } from "~/src/data";
import { buildPronoun } from "../src/buildPronoun";
import config from '../data/config.suml';
@ -411,6 +419,7 @@
propagate: [],
flagsAsterisk: process.env.FLAGS_ASTERISK,
defaultOpinions: opinionsToForm(opinions),
isValidLink,
};
},
async asyncData({ app, store }) {

View File

@ -60,6 +60,13 @@ const isTroll = (answers, writins) => {
return false; // no free-text provided
}
const boolToInt = (value) => {
if (value === null) { return null; }
if (value === true) { return 1; }
if (value === false) { return 0; }
throw `Invalid value ${value}`
}
const router = Router();
router.get('/census/finished', handleErrorAsync(async (req, res) => {
@ -80,8 +87,8 @@ router.post('/census/submit', handleErrorAsync(async (req, res) => {
${req.body.answers},
${req.body.writins},
${await hasFinished(req)},
${isRelevant(answers) ? 1 : 0},
${isTroll(answers, writins) ? 1 : 0}
${boolToInt(isRelevant(answers))},
${boolToInt(isTroll(answers, writins))}
)`);
return res.json(id);

View File

@ -3,7 +3,7 @@ import SQL from 'sql-template-strings';
import md5 from "js-md5";
import {ulid} from "ulid";
import avatar from "../avatar";
import {handleErrorAsync, now} from "../../src/helpers";
import {handleErrorAsync, now, isValidLink} from "../../src/helpers";
import { caches } from "../../src/cache";
import fs from 'fs';
import { minBirthdate, maxBirthdate, formatDate, parseDate } from '../../src/birthdate';
@ -228,9 +228,36 @@ const fetchCircles = async(db, profileId, userId) => {
const router = Router();
router.get('/profile/get/:username', handleErrorAsync(async (req, res) => {
const fetchProfilesRoute = async (req, res, user) => {
const isSelf = req.user && req.user.username === req.params.username;
const isAdmin = req.isGranted('users');
if (!user || (user.bannedReason !== null && !isAdmin && !isSelf)) {
return res.json({
profiles: {},
});
}
user.emailHash = md5(user.email);
delete user.email;
user.avatar = await avatar(req.db, user);
user.bannedTerms = user.bannedTerms ? user.bannedTerms.split(',') : [];
let profiles = await fetchProfiles(req.db, user.username, isSelf);
if (req.query.version !== '2') {
for (let [locale, profile] of Object.entries(profiles)) {
profiles[locale] = downgradeToV1(profile);
}
}
return res.json({
...user,
profiles,
});
}
router.get('/profile/get/:username', handleErrorAsync(async (req, res) => {
const user = await req.db.get(SQL`
SELECT
users.id,
@ -245,29 +272,25 @@ router.get('/profile/get/:username', handleErrorAsync(async (req, res) => {
WHERE users.usernameNorm = ${normalise(req.params.username)}
`);
if (!user || (user.bannedReason !== null && !isAdmin && !isSelf)) {
return res.json({
profiles: {},
});
}
return await fetchProfilesRoute(req, res, user);
}));
user.emailHash = md5(user.email);
delete user.email;
user.avatar = await avatar(req.db, user);
router.get('/profile/get-id/:id', handleErrorAsync(async (req, res) => {
const user = await req.db.get(SQL`
SELECT
users.id,
users.username,
users.email,
users.avatarSource,
users.bannedReason,
users.bannedTerms,
users.bannedBy,
users.roles != '' AS team
FROM users
WHERE users.id = ${req.params.id}
`);
user.bannedTerms = user.bannedTerms ? user.bannedTerms.split(',') : [];
let profiles = await fetchProfiles(req.db, req.params.username, isSelf);
if (req.query.version !== '2') {
for (let [locale, profile] of Object.entries(profiles)) {
profiles[locale] = downgradeToV1(profile);
}
}
return res.json({
...user,
profiles,
});
return await fetchProfilesRoute(req, res, user);
}));
router.get('/profile/versions/:username', handleErrorAsync(async (req, res) => {
@ -369,7 +392,16 @@ router.post('/profile/save', handleErrorAsync(async (req, res) => {
const pronouns = req.body.pronouns.map(p => { return {...p, value: p.value.substring(0, 192)}});
const description = req.body.description.substring(0, 256);
const birthday = cleanupBirthday(req.body.birthday || null);
const links = req.body.links.filter(x => !!x);
const links = req.body.links.filter(x => !!x && isValidLink(x));
const customFlags = req.body.customFlags.filter(x => x.name && (!x.link || isValidLink(x.link))).map(x => {
return {
value: x.value,
name: x.name.substring(0, 24),
description: x.description ? x.description.substring(0, 512) : null,
alt: x.alt ? x.alt.substring(0, 512) : null,
link: x.link ? normaliseUrl(x.link) : null,
}
});
const words = req.body.words.map(c => { return {...c, values: c.values.map(p => { return {...p, value: p.value.substring(0, 32)}})}});
const sensitive = req.body.sensitive.filter(x => !!x).map(x => x.substring(0, 64));
const timezone = req.body.timezone ? {
@ -393,7 +425,7 @@ router.post('/profile/save', handleErrorAsync(async (req, res) => {
timezone = ${JSON.stringify(timezone)},
links = ${JSON.stringify(links)},
flags = ${JSON.stringify(req.body.flags)},
customFlags = ${JSON.stringify(req.body.customFlags)},
customFlags = ${JSON.stringify(customFlags)},
words = ${JSON.stringify(words)},
sensitive = ${JSON.stringify(sensitive)},
teamName = ${req.isGranted() ? req.body.teamName || null : ''},
@ -418,7 +450,7 @@ router.post('/profile/save', handleErrorAsync(async (req, res) => {
${JSON.stringify(timezone)},
${JSON.stringify(links)},
${JSON.stringify(req.body.flags)},
${JSON.stringify(req.body.customFlags)},
${JSON.stringify(customFlags)},
${JSON.stringify(words)},
${JSON.stringify(sensitive)},
1,

View File

@ -2,22 +2,39 @@ require('../src/dotenv')();
const dbConnection = require('./db');
const SQL = require('sql-template-strings');
const {normaliseUrl} = require('../src/links');
const Suml = require('suml');
const fs = require("fs");
const loadSuml = name => new Suml().parse(fs.readFileSync(`${__dirname}/../data/${name}.suml`).toString());
const config = loadSuml('config');
const isTroll = (answers, writins) => {
if (Object.values(writins).filter(x => !!x).length) {
return null; // unknown, send to moderation
}
for (let i in config.census.questions) {
if (config.census.questions[i].type === 'textarea' && answers[i.toString()]) {
return null; // unknown, send to moderation
}
}
return false; // no free-text provided
}
const boolToInt = (value) => {
if (value === null) { return null; }
if (value === true) { return 1; }
if (value === false) { return 0; }
throw `Invalid value ${value}`
}
(async () => {
const db = await dbConnection();
const profiles = await db.all(SQL`SELECT links FROM profiles`);
let i = 0;
for (let {links} of profiles) {
if (i % 1000 === 0) {
console.log(`${i}/${profiles.length}`);
}
i++;
for (let url of JSON.parse(links)) {
url = normaliseUrl(url);
if (!url) { continue; }
await db.get(SQL`INSERT INTO links (url) VALUES (${url}) ON CONFLICT (url) DO UPDATE SET expiresAt = null`);
}
const responses = await db.all(SQL`SELECT * FROM census WHERE edition = 2023 and locale='pl'`);
for (let response of responses) {
const troll = isTroll(JSON.parse(response.answers), JSON.parse(response.writins));
await db.get(SQL`UPDATE census SET troll = ${boolToInt(troll)} WHERE id = ${response.id}`);
}
})();

View File

@ -300,3 +300,13 @@ export const findAdmins = async (db, locale, area) => {
const admins = await db.all(`SELECT username, email, roles, adminNotifications FROM users WHERE roles != ''`);
return admins.filter(admin => isGranted(admin, locale, area));
};
export const isValidLink = (url) => {
try {
url = new URL(url);
return ['http:', 'https:', 'mailto:'].includes(url.protocol);
} catch {
return false;
}
}