Merge branch 'linting' into 'main'

Linting

See merge request PronounsPage/PronounsPage!395
This commit is contained in:
Andrea Vos 2023-12-28 22:05:16 +00:00
commit 75653d45ce
81 changed files with 840 additions and 215 deletions

55
.eslintrc.json Normal file
View File

@ -0,0 +1,55 @@
{
"env": {
"es2021": true,
"node": true
},
"root": true,
"extends": [
"eslint:recommended",
"plugin:import/recommended",
"plugin:vue/recommended"
],
"ignorePatterns": ["data", "dist", "new", "static", "census", "keys"],
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
"rules": {
"curly": "warn",
"eqeqeq": "warn",
"no-constant-condition": "warn",
"no-empty": "warn",
"no-prototype-builtins": "warn",
"no-template-curly-in-string": "error",
"no-unused-vars": ["error", {"argsIgnorePattern": "^_", "varsIgnorePattern": "^_"}],
"no-useless-escape": "warn",
"no-useless-rename": "warn",
"object-shorthand": "warn",
"prefer-const": "warn",
"prefer-template": "warn",
"import/extensions": ["error", "always"],
"import/no-cycle": "warn",
"import/no-self-import": "error",
"import/no-useless-path-segments": "error",
"no-irregular-whitespace": "warn",
"vue/html-closing-bracket-spacing": ["warn", {"selfClosingTag": "never"}],
"vue/html-indent": ["warn", 4],
"vue/html-self-closing": ["warn", {"html": {"void": "always", "normal": "never"}}],
"vue/max-attributes-per-line": ["warn", {"singleline": 4}],
"vue/multi-word-component-names": "off",
"vue/no-mutating-props": "warn",
"vue/no-use-v-if-with-v-for": ["warn"],
"vue/require-v-for-key": "warn",
"vue/script-indent": ["warn", 4],
"vue/valid-template-root": "warn",
"vue/valid-v-for": "warn"
},
"overrides": [
{
"files": ["*.vue"],
"rules": {
"import/extensions": "off"
}
}
]
}

View File

@ -10,6 +10,11 @@ before_script:
- make switch LANG=en
- yarn install --immutable --immutable-cache --check-cache --cache-folder .yarn
lint:
stage: test
script:
- yarn lint
unit-tests:
stage: test
script:

View File

@ -14,6 +14,9 @@ install:
yarn
node server/migrate.js
lint:
yarn lint
test:
yarn test

View File

@ -209,7 +209,6 @@
<script>
import {socialProviders} from "../src/socialProviders";
import {gravatar} from "../src/helpers";
import cookieSettings from "../src/cookieSettings";
import {mapState} from "vuex";
import { usernameRegex } from '../src/username';
@ -351,7 +350,7 @@
async deleteAccount() {
await this.$confirm(this.$t('user.deleteAccountConfirm'), 'danger');
const response = await this.$post(`/user/delete`);
await this.$post(`/user/delete`);
if (this.impersonationActive) {
this.stopImpersonation();

View File

@ -186,12 +186,10 @@
</template>
<script>
import ClientOnly from 'vue-client-only'
import forbidden from "../src/forbidden";
import {sleep} from "../src/helpers";
export default {
components: { ClientOnly },
props: {
user: { required: true },
profile: {},

View File

@ -1,6 +1,6 @@
<template>
<div class="calendar">
<div v-for="i in (startingDayOfWeek - 1)"></div>
<div v-for="i in (startingDayOfWeek - 1)" :key="i"></div>
<component :is="tooltips || getDayClass(d) === 'day' ? 'div' : 'nuxt-link'"
v-for="d in iterateMonth(year.year, month)" :key="d.toString()"
:to="`/${config.calendar.route}/${d}`"

View File

@ -65,7 +65,7 @@
}
let colourIndex = 0;
new Chart(this.$el.getContext('2d'), {
new window.Chart(this.$el.getContext('2d'), {
type: this.type,
data: {
labels: this.isMultiDataset(this.data)

View File

@ -29,7 +29,6 @@
<script>
// TODO remove duplication
const escapeRegExp = s => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // https://stackoverflow.com/a/6969486/3297012
const normalise = s => decodeURIComponent(s.trim().toLowerCase());
const normaliseWithLink = s => normalise(s.replace(/^@/, '').replace(new RegExp('^https://.*?/@'), ''))

View File

@ -20,7 +20,7 @@ export default {
mounted() {
if (!this.$refs.clipboard) { return; }
const clipboard = new ClipboardJS(this.$refs.clipboard);
clipboard.on('success', (e) => {
clipboard.on('success', () => {
this.clipboardFeedback = true;
setTimeout(() => this.clipboardFeedback = false, 3000);
});

View File

@ -105,7 +105,7 @@
resolve(this.value);
}
},
cancel(event) {
cancel() {
const reject = this.reject;
this.hide();
if (reject) {

View File

@ -156,7 +156,7 @@
</template>
<script>
import { Noun } from "~/src/classes";
import { Noun } from '../src/classes';
import { buildDict } from "../src/helpers";
import hash from "../plugins/hash";

View File

@ -20,7 +20,7 @@
</template>
<script>
import { pronouns } from "~/src/data";
import { pronouns } from '../src/data';
export default {
props: {

View File

@ -6,7 +6,7 @@
</slot>
</li>
<li v-show="!allShown && hiddenCount > 0" :class="[itemClass, 'small']">
<span v-if="static">
<span v-if="isStatic">
<Icon v="plus-circle"/>
<T :params="{count: hiddenCount}">profile.expendableList.more</T>
</span>
@ -31,7 +31,7 @@ export default {
values: { required: true },
limit: { required: true },
reducedLimit: { 'default': 4 },
static: { type: Boolean },
isStatic: { type: Boolean },
expand: { type: Boolean },
itemClass: {},
},

View File

@ -193,7 +193,7 @@ export default {
data() {
return {
links: groupBy([...getContactLinks(this.config), ...getSocialLinks(this.config)], l => l.group),
supportLinks: [...getSupportLinks(this.config)],
supportLinks: [...getSupportLinks()],
versionFrontend: process.env.VERSION,
versionBackend: undefined,
adsVisible: false,

View File

@ -176,7 +176,7 @@
import { mapState } from 'vuex'
import {DateTime} from "luxon";
import forbidden from "../src/forbidden";
import NounsNav from "../data/nouns/NounsNav";
import NounsNav from '../data/nouns/NounsNav.vue';
export default {
components: { NounsNav },

View File

@ -225,7 +225,7 @@
</template>
<script>
import { InclusiveEntry } from "~/src/classes";
import { InclusiveEntry } from '../src/classes';
import { buildDict, clearUrl, clearLinkedText } from "../src/helpers";
import hash from "../plugins/hash";

View File

@ -112,7 +112,7 @@
}
},
methods: {
async submit(event) {
async submit() {
this.submitting = true;
try {
await this.$post(`/inclusive/submit`, this.form);

View File

@ -1,5 +1,5 @@
<script>
import Icon from './Icon';
import Icon from './Icon.vue';
import spelling from "../plugins/spelling";
import { escapeHtml } from '../src/helpers';
@ -15,7 +15,7 @@
if (this.escape) {
text = escapeHtml(text);
}
if (!this.text) {
if (!text) {
return h('span');
}
@ -71,7 +71,7 @@
buffer = '';
linkBuffer = '';
}
for (let c of this.text) {
for (let c of text) {
if (c === '{') {
addChild();
isLink = true;

View File

@ -81,7 +81,6 @@
<script>
import jwt from 'jsonwebtoken';
import {socialProviders} from "../src/socialProviders";
import cookieSettings from "../src/cookieSettings";
export default {
data() {

View File

@ -8,7 +8,7 @@
</template>
<script>
import {Day} from "@/src/calendar/helpers";
import { Day } from '../src/calendar/helpers';
import { calendar } from '../src/calendar/calendar';
import { ImmutableArray } from '../src/helpers';

View File

@ -34,7 +34,7 @@
this.mode = this.getMode();
this.isDark = this.detectDark();
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => this.isDark = this.detectDark());
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => this.isDark = this.detectDark());
this.$eventHub.$on('mode-changed', mode => {
if (mode !== this.mode) {

View File

@ -63,7 +63,7 @@
</template>
<script>
import {clearUrl} from '~/src/helpers';
import {clearUrl} from '../src/helpers';
import LazyHydrate from 'vue-lazy-hydration';
export default {

View File

@ -154,7 +154,7 @@
}
},
methods: {
async submit(event) {
async submit() {
this.submitting = true;
try {
await this.$post(`/nouns/submit`, this.form);

View File

@ -34,13 +34,13 @@
<T v-if="$te('profile.age')">profile.age</T><T v-else>profile.birthday</T><T>quotation.colon</T>
{{ profile.age }}
</p>
<Timezone v-if="profile.timezone" :value="profile.timezone" :static="static"/>
<Timezone v-if="profile.timezone" :value="profile.timezone" :isStatic="isStatic"/>
</div>
<div v-if="profile.flags.length || profile.customFlags.length" :class="['col-12', manyFlagsLayout ? '' : 'col-lg-6']">
<ClientOnly>
<ExpandableList :values="[...profile.flags.filter(flag => allFlags[flag]), ...profile.customFlags]"
:limit="32" :reducedLimit="8" class="list-inline" itemClass="list-inline-item p-1" :static="static" :expand="expandLinks">
:limit="32" :reducedLimit="8" class="list-inline" itemClass="list-inline-item p-1" :isStatic="isStatic" :expand="expandLinks">
<template v-slot="s">
<Flag v-if="typeof(s.el) === 'string'"
:termkey="allFlags[s.el]"
@ -73,7 +73,7 @@
<T>profile.names</T>
</h3>
<ExpandableList :values="profile.names" :limit="16" class="list-unstyled" :static="static" :expand="expandLinks">
<ExpandableList :values="profile.names" :limit="16" class="list-unstyled" :isStatic="isStatic" :expand="expandLinks">
<template v-slot="s">
<Opinion :word="convertName(s.el.value)" :opinion="s.el.opinion" :escape="false" :markdown="profile.markdown"
:link="config.locale === 'tok' && config.pronouns.enabled ? `${config.pronouns.prefix}/${s.el.value}` : null"
@ -87,7 +87,7 @@
<T>profile.pronouns</T>
</h3>
<ExpandableList :values="pronounOpinions" :limit="16" class="list-unstyled" :static="static" :expand="expandLinks">
<ExpandableList :values="pronounOpinions" :limit="16" class="list-unstyled" :isStatic="isStatic" :expand="expandLinks">
<template v-slot="s">
<Opinion :word="typeof s.el.pronoun === 'string' ? s.el.pronoun : s.el.pronoun.name(glue)" :opinion="s.el.opinion" :link="`${config.pronouns.prefix || ''}/${s.el.link}`" :customOpinions="profile.opinions"/>
</template>
@ -99,9 +99,9 @@
<T>profile.links</T>
</h3>
<ExpandableList :values="profile.links" :limit="16" class="list-unstyled" :static="static" :expand="expandLinks">
<ExpandableList :values="profile.links" :limit="16" class="list-unstyled" :isStatic="isStatic" :expand="expandLinks">
<template v-slot="s">
<ProfileLink :link="s.el" :expand="static" :verifiedLinks="profile.verifiedLinks || {}" :metadata="profile.linksMetadata[normaliseUrl(s.el)]"/>
<ProfileLink :link="s.el" :expand="isStatic" :verifiedLinks="profile.verifiedLinks || {}" :metadata="profile.linksMetadata[normaliseUrl(s.el)]"/>
</template>
</ExpandableList>
</div>
@ -119,7 +119,7 @@
<Spelling :text="column.header" :markdown="profile.markdown"/>
</h4>
<ExpandableList :values="column.values" :limit="16" class="list-unstyled" :static="static" :expand="expandLinks">
<ExpandableList :values="column.values" :limit="16" class="list-unstyled" :isStatic="isStatic" :expand="expandLinks">
<template v-slot="s">
<Opinion :word="s.el.value" :opinion="s.el.opinion" :customOpinions="profile.opinions" :markdown="profile.markdown"/>
</template>
@ -128,7 +128,7 @@
</div>
</section>
<section class="clearfix" v-if="profile.events.length + profile.customEvents.length > 0 && !static">
<section class="clearfix" v-if="profile.events.length + profile.customEvents.length > 0 && !isStatic">
<h3>
<Icon v="calendar"/>
<T>profile.calendar.header</T>
@ -137,7 +137,7 @@
<PersonalCalendar :year="year" :events="[...profile.events, ...profile.customEvents]"/>
</section>
<section class="clearfix" v-if="profile.circle.length > 0 && !static">
<section class="clearfix" v-if="profile.circle.length > 0 && !isStatic">
<h3>
<Icon v="heart-circle"/>
<T>profile.circles.header</T>
@ -177,7 +177,7 @@
user: { required: true },
profile: { required: true },
terms: { 'default': null },
static: { type: Boolean },
isStatic: { type: Boolean },
expandLinks: { type: Boolean },
},
data() {

View File

@ -65,7 +65,7 @@
await this.$confirm(this.$t('admin.user.confirmRole', {username: this.user.username, role: roles}));
const response = await this.$post(`/user/${this.user.id}/set-roles`, { roles: roles});
await this.$post(`/user/${this.user.id}/set-roles`, { roles: roles});
this.user.roles = roles;
}

View File

@ -141,7 +141,7 @@
this.page = 0;
await this.loadData();
},
async query(after, before, t) {
async query(after, before) {
if (JSON.stringify(after) === JSON.stringify(before)) { return; }
this.page = 0;
await this.loadData();

View File

@ -15,7 +15,7 @@
</template>
<script>
import {makeId} from "~/src/helpers";
import { makeId } from '../src/helpers';
export default {
props: {

View File

@ -120,7 +120,7 @@
<script>
import {pronounLibrary} from "../src/data";
import {Source} from "@/src/classes";
import { Source } from '../src/classes';
export default {
data() {

View File

@ -30,7 +30,7 @@
export default {
data() {
return {
links: [...getSupportLinks(this.config)],
links: [...getSupportLinks()],
}
},
}

View File

@ -111,7 +111,7 @@
</template>
<script>
import { TermsEntry } from "~/src/classes";
import { TermsEntry } from '../src/classes';
import { buildDict, clearUrl, clearLinkedText } from "../src/helpers";
import hash from "../plugins/hash";
import { calendar } from '../src/calendar/calendar';

View File

@ -59,7 +59,7 @@
<label class="text-nowrap"><strong>
<T>profile.flags</T>
</strong></label>
<ListInput v-model="form.flags" v-slot="s"/>
<ListInput v-model="form.flags"/>
</div>
</div>
<div class="col-12 col-lg-4">
@ -118,7 +118,7 @@
}
},
methods: {
async submit(event) {
async submit() {
this.submitting = true;
try {
await this.$post(`/terms/submit`, this.form);

View File

@ -1,6 +1,6 @@
<template>
<div v-if="timezone && dt">
<p v-if="!static">
<p v-if="!isStatic">
<Icon v="clock"/>
<T :params="{
time: dt.toLocaleString(DateTime.TIME_SIMPLE),
@ -32,7 +32,7 @@ export default {
mixins: [ timezone ],
props: {
value: {},
static: { type: Boolean },
isStatic: { type: Boolean },
},
data() {
return {

View File

@ -49,7 +49,7 @@
},
async commitChanges() {
await this.$confirm(`Do you want to commit ${this.changesCount} changes?`, 'success');
const response = await this.$post(`/translations/propose`, {
await this.$post(`/translations/propose`, {
changes: this.translationChanges,
});
this.$store.commit('translationCommit');

View File

@ -5,7 +5,7 @@
</template>
<script>
import dark from "../plugins/dark";
import dark from '../plugins/dark';
export default {
mixins: [dark],

View File

@ -38,7 +38,7 @@
import Vue from 'vue';
import dark from "../plugins/dark";
import sorter from "avris-sorter";
import {sleep} from "../src/helpers";
import { sleep } from "../src/helpers";
import md5 from 'js-md5';
// no need to be super secure, just a sign that the page is not public
@ -146,7 +146,7 @@
await this.$loadScript('gtm', 'https://www.googletagmanager.com/gtag/js?id=G-TDJEP12Q3M');
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
function gtag(){window.dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-TDJEP12Q3M');
},

View File

@ -47,10 +47,8 @@
<script>
import {NounDeclension} from "../../../src/classes";
import NounsNav from "./NounsNav";
export default {
components: { NounsNav },
data() {
return {
declensions: {

View File

@ -175,9 +175,9 @@
</template>
<script>
import {Noun, NounDeclension, SourceLibrary} from "../../../src/classes";
import {head} from "../../../src/helpers";
import NounsNav from "./NounsNav";
import { Noun, NounDeclension, SourceLibrary } from '../../../src/classes.js';
import { head } from '../../../src/helpers.js';
import NounsNav from './NounsNav.vue';
import templates from './dukatywy.tsv';
const dukajDeclension = new NounDeclension({

View File

@ -175,9 +175,9 @@
</template>
<script>
import {Noun, NounDeclension, SourceLibrary} from "../../../src/classes";
import {head} from "../../../src/helpers";
import NounsNav from "./NounsNav";
import { Noun, NounDeclension, SourceLibrary } from '../../../src/classes.js';
import { head } from '../../../src/helpers.js';
import NounsNav from './NounsNav.vue';
import templates from './iksatywy.tsv';
const xDeclension = new NounDeclension({

View File

@ -82,10 +82,10 @@
</template>
<script>
import {Noun, NounDeclension} from "../../../src/classes";
import hash from "../../../plugins/hash";
import {head} from "../../../src/helpers";
import NounsNav from "./NounsNav";
import { NounDeclension } from '../../../src/classes.js';
import hash from '../../../plugins/hash.js';
import { head } from '../../../src/helpers.js';
import NounsNav from './NounsNav.vue';
export default {
components: { NounsNav },

View File

@ -78,9 +78,9 @@
</template>
<script>
import {Noun, SourceLibrary} from "../../../src/classes";
import {head} from "../../../src/helpers";
import NounsNav from "./NounsNav";
import { Noun, SourceLibrary } from '../../../src/classes.js';
import { head } from '../../../src/helpers.js';
import NounsNav from './NounsNav.vue';
export default {
components: { NounsNav },

View File

@ -215,7 +215,7 @@ export default {
plugins: postCssPlugins,
}
},
extend (config, ctx) {
extend (config) {
config.module.rules.push({
test: /\.csv|\.tsv$/,
loader: 'csv-loader',

View File

@ -9,6 +9,7 @@
"start": "nuxt start",
"export": "nuxt export",
"serve": "nuxt serve",
"lint": "eslint --ext .js,.vue .",
"test": "node --experimental-vm-modules $(yarn bin jest)"
},
"dependencies": {
@ -23,11 +24,9 @@
"avris-generator": "^0.8.2",
"avris-sorter": "^0.0.3",
"aws-sdk": "^2.1425.0",
"body-parser": "^1.20.2",
"canvas": "^2.11.2",
"cookie-parser": "^1.4.5",
"cookie-universal-nuxt": "^2.1.4",
"csurf": "^1.11.0",
"dotenv": "^8.2.0",
"express": "^4.17.1",
"express-session": "^1.17.1",
@ -92,6 +91,10 @@
"clipboard": "^2.0.6",
"css-loader": "^5.2.7",
"csv-loader": "^3.0.3",
"eslint": "^8.55.0",
"eslint-plugin-import": "^2.29.0",
"eslint-plugin-vue": "^9.19.2",
"globals": "^13.24.0",
"jest": "^29.7.0",
"postcss": "^8.2.15",
"postcss-import": "^13.0.0",

View File

@ -1,13 +1,13 @@
<template>
<Homepage v-if="config.header"/>
<Select v-else/>
<SelectVersion v-else/>
</template>
<script>
import Homepage from '../routes/homepage';
import Select from '../routes/select';
import Homepage from '../routes/homepage.vue';
import SelectVersion from '../routes/select.vue';
export default {
components: { Homepage, Select },
components: { Homepage, SelectVersion },
}
</script>

5
plugins/.eslintrc.json Normal file
View File

@ -0,0 +1,5 @@
{
"env": {
"browser": true
}
}

View File

@ -1,6 +1,6 @@
import Vue from 'vue'
import VuejsDatePicker from 'vuejs-datepicker'
export default ({ app }) => {
export default () => {
Vue.component('datepicker', VuejsDatePicker);
}

View File

@ -48,7 +48,7 @@ export default ({ app, store }) => {
Vue.prototype.$loadScript = (name, src) => {
if (!process.client || document.querySelectorAll(`script.${name}-script`).length > 0) {
return new Promise((resolve, reject) => { resolve(); });
return new Promise((resolve) => { resolve(); });
}
let resolveFn; let rejectFn;
@ -83,7 +83,7 @@ export default ({ app, store }) => {
return decodeTime(ulid) / 1000;
}
app.router.afterEach((to, from) => {
app.router.afterEach(() => {
if (typeof window !== 'undefined' && window.fusetag) {
window.fusetag.pageInit();
}

View File

@ -1,14 +1,9 @@
function defaultHandler({plausible, to, from}) {
function defaultHandler({plausible, to}) {
console.debug("[analytics] Tracking default handler: %O", to);
plausible.trackPageview({
url: to.toString()
})
}
function eventHandler(eventName) {
return function ({plausible, to, from}) {
plausible.trackEvent(eventName, {}, {});
}
}
/**
* @param {(value: URL) => URL} redactor
* @param {(ctx) => void} base

View File

@ -43,7 +43,7 @@
<script>
import {head} from "../src/helpers";
import translator from "@/src/translator";
import translator from '../src/translator';
export default {
data() {

View File

@ -119,10 +119,8 @@
</template>
<script>
import {deepSet, head} from "../src/helpers";
import {head} from "../src/helpers";
import {socialProviders} from "../src/socialProviders";
import translator from '../src/translator';
import Suml from 'suml';
export default {
data() {
@ -179,8 +177,8 @@ export default {
const r = {};
Object.entries(this.stats)
.filter(([locale, localeStats]) => locale !== '_' && locale !== 'calculatedAt')
.sort(([aLocale, aLocaleStats], [bLocale, bLocaleStats]) => bLocaleStats.users - aLocaleStats.users)
.filter(([locale, _localeStats]) => locale !== '_' && locale !== 'calculatedAt')
.sort(([_aLocale, aLocaleStats], [_bLocale, bLocaleStats]) => bLocaleStats.users - aLocaleStats.users)
.forEach(([locale, localeStats]) => {
r[locale] = localeStats.users;
});

View File

@ -64,7 +64,7 @@
</template>
<script>
import { examples, pronouns, pronounLibrary } from "~/src/data";
import { examples, pronouns, pronounLibrary } from '../src/data';
import { head } from "../src/helpers";
export default {

View File

@ -191,7 +191,7 @@
}),
writins: buildDict(function* () {
let i = 0;
for (let question of questions) {
for (let _question of questions) {
yield [i, '']
i++;
}
@ -199,7 +199,7 @@
DateTime,
}
},
async asyncData({ app, store }) {
async asyncData({ app }) {
const finished = await app.$axios.$get(`/census/finished`);
const countResponses = await app.$axios.$get(`/census/count`);

View File

@ -89,12 +89,6 @@
<script>
import {head} from "../src/helpers";
const rgbToHex = (rgb) => {
if (!rgb.startsWith('rgb(')) { return rgb; }
const [r, g, b] = rgb.replace(/^rgb\(/, '').replace(/\)$/, '').split(',')
return `#${parseInt(r.trim()).toString(16)}${parseInt(g.trim()).toString(16)}${parseInt(b.trim()).toString(16)}`;
}
export default {
data() {
return {

View File

@ -33,7 +33,7 @@
</template>
<script>
import EnglishTable from "../data/pronouns/EnglishTable";
import EnglishTable from '../data/pronouns/EnglishTable.vue';
import { head } from "../src/helpers";
export default {

View File

@ -21,10 +21,8 @@
<script>
import { head } from "../src/helpers";
import hash from "../plugins/hash";
import NounsNav from "../data/nouns/NounsNav.vue";
export default {
components: { NounsNav },
mixins: [ hash ],
mounted() {
this.handleHash(this.config.inclusive.hashNamespace || '', filter => {

View File

@ -115,7 +115,7 @@
</template>
<script>
import {head, buildDict} from '~/src/helpers';
import {head, buildDict} from '../src/helpers';
import hash from "../plugins/hash";
import {Name} from "../src/classes";

View File

@ -40,7 +40,7 @@
</template>
<script>
import { people } from "~/src/data";
import { people } from '../src/data';
import { head } from "../src/helpers";
import {SourceLibrary} from "../src/classes";

View File

@ -29,7 +29,7 @@
<p><T>privacy.content.turnstile</T></p>
<p v-if="config.ads && config.ads.enabled">
<T>privacy.content.publift</T>
<div data-fuse-privacy-tool></div>
<span data-fuse-privacy-tool></span>
<T>privacy.content.gtm</T>
</p>
<p><T>privacy.content.logsBackups</T></p>
@ -41,7 +41,7 @@
export default {
methods: {
revokeCookieConsent() {
googlefc.callbackQueue.push(googlefc.showRevocationMessage);
window.googlefc.callbackQueue.push(window.googlefc.showRevocationMessage);
},
},
}

View File

@ -1,5 +1,5 @@
<template>
<Profile v-if="profile" :user="user" :profile="profile" class="pb-3 mt-5" static>
<Profile v-if="profile" :user="user" :profile="profile" class="pb-3 mt-5" isStatic>
<nuxt-link to="/">
<h1 class="text-nowrap h5">
<Logo style="font-size: 1.3em;"/>

View File

@ -313,7 +313,7 @@
<script>
import {head, buildList, buildDict, isValidLink} from "../src/helpers";
import { pronouns } from "~/src/data";
import { pronouns } from '../src/data';
import { buildPronoun } from "../src/buildPronoun";
import config from '../data/config.suml';
import link from '../plugins/link';

View File

@ -102,11 +102,11 @@
</template>
<script>
import { examples, pronouns, getSources, pronounLibrary } from "~/src/data";
import { examples, pronouns, pronounLibrary } from '../src/data';
import {buildPronoun} from "../src/buildPronoun";
import {head} from "../src/helpers";
import GrammarTables from "../data/pronouns/GrammarTables";
import LinkedText from "../components/LinkedText";
import GrammarTables from '../data/pronouns/GrammarTables.vue';
import LinkedText from '../components/LinkedText.vue';
import {SourceLibrary} from "../src/classes";
export default {

View File

@ -235,12 +235,12 @@
</template>
<script>
import { examples, pronouns, pronounLibrary } from "~/src/data";
import { ExamplePart } from "~/src/classes";
import { examples, pronouns, pronounLibrary } from '../src/data';
import { ExamplePart } from '../src/classes';
import Compressor from "../src/compressor";
import MORPHEMES from '../data/pronouns/morphemes';
import {mapState} from "vuex";
import Suggested from "../data/pronouns/Suggested";
import Suggested from '../data/pronouns/Suggested.vue';
export default {
components: { Suggested },

View File

@ -23,11 +23,8 @@
<script>
import { head } from "../src/helpers";
import hash from "../plugins/hash";
import NounsNav from "../data/nouns/NounsNav.vue";
export default {
components: { NounsNav },
mixins: [ hash ],
mounted() {
this.handleHash(this.config.terminology.hashNamespace || '', filter => {

View File

@ -18,7 +18,7 @@ const timer = ms => new Promise( res => setTimeout(res, ms));
}
const results = await Promise.all(chunk.map(({url}) => Promise.race([
analyser.analyse(url),
new Promise((resolve, reject) =>
new Promise((resolve) =>
setTimeout(() => resolve({url: url, error: new Error('timeout')}), 12000)
),
])));

View File

@ -11,7 +11,7 @@ const __dirname = new URL('.', import.meta.url).pathname;
const locales = buildLocaleList('_');
const publishers = {
async twitter(tweet, image, previousId, locale) {
async twitter(tweet, _image, previousId, _locale) {
const client = new Twitter({
consumer_key: process.env.TWITTER_CALENDAR_CONSUMER_KEY,
consumer_secret: process.env.TWITTER_CALENDAR_CONSUMER_SECRET,
@ -73,8 +73,6 @@ const imageTmpPath = `${tmpDir}/calendar-tmp.png`;
const lastPostId = {};
const timer = ms => new Promise( res => setTimeout(res, ms));
(async () => {
if (process.argv.length !== 4) {
console.error('Missing parameters. Usage: node server/calendarBot.js <locales> <publishers>');
@ -85,7 +83,7 @@ const timer = ms => new Promise( res => setTimeout(res, ms));
console.log(locales[locale].name);
try {
const { day, message, image } = await (await fetch(locales[locale].url + '/api/calendar/today')).json();
const { message, image } = await (await fetch(locales[locale].url + '/api/calendar/today')).json();
console.log('<<<', message, '>>>');
if (!message) { continue; }

View File

@ -79,7 +79,7 @@ const shoot = async (db, mode) => {
continue;
}
const s3putResponse = await s3.putObject({
await s3.putObject({
Key: key,
Body: buffer,
ContentType: 'image/png',

View File

@ -142,7 +142,7 @@ app.use(calendarRoute);
app.use(translationsRoute);
app.use(subscriptionRoute);
app.use(function (err, req, res, next) {
app.use(function (err, req, res) {
console.error(formatError(err, req));
res.status(500).send('Unexpected server error');
req.db.close();

View File

@ -53,7 +53,7 @@ const popularUnisex = {};
const allFirstUnisex = {};
const popularFirstUnisex = {};
_each(pesel, (counts, name) => {
_each(pesel, (counts) => {
counts['balanceBoth'] = calculateBalance(counts.K[0] + counts.K[1], counts.M[0] + counts.M[1]);
counts['balanceFirst'] = calculateBalance(counts.K[0], counts.M[0]);
counts['balanceBothPop'] = counts['balanceBoth'] * (counts.K[0] + counts.K[1] + counts.M[0] + counts.M[1]);

View File

@ -156,7 +156,7 @@ router.get('/admin/stats', handleErrorAsync(async (req, res) => {
const stats = await fetchStats(req);
for (let [locale, localeStats] of Object.entries(stats)) {
for (let locale of Object.keys(stats)) {
if (locale === '_' || locale === 'calculatedAt') { continue; }
if (!req.isGranted('panel', locale)) {

View File

@ -68,7 +68,7 @@ const buildMessage = (events, locale, day, link) => {
const eventsSummary = (day, locale) => {
const eventsRaw = calendar.getCurrentYear().eventsByDate[day.toString()];
const link = `${locale.url}/${encodeURIComponent(config.calendar.route)}/${day}`;
const link = `${locale.url}/${encodeURIComponent(global.config.calendar.route)}/${day}`;
const image = `${locale.url}/calendar/${day}.png`;
let message = null;

View File

@ -177,7 +177,7 @@ const calculateAggregate = (config, answer) => {
case 'AND':
return intersection(expected, answer).size === expected.size;
default:
throw new Exception(`Operation "${config.operation} not supported"`);
throw new Error(`Operation "${config.operation} not supported"`);
}
}
@ -187,7 +187,7 @@ router.get('/census/export', handleErrorAsync(async (req, res) => {
}
const report = [];
for (let {answers, writins, troll} of await req.db.all(SQL`
for (let {answers, writins} of await req.db.all(SQL`
SELECT answers, writins FROM census
WHERE locale = ${global.config.locale}
AND edition = ${global.config.census.edition}
@ -200,10 +200,10 @@ router.get('/census/export', handleErrorAsync(async (req, res) => {
const answer = {};
let i = 0;
for (let question of config.census.questions) {
for (let question of global.config.census.questions) {
if (question.type === 'checkbox') {
const answerForAggregate = new Set();
for (let [option, comment] of question.options) {
for (let [option, _comment] of question.options) {
const checked = (answers[i.toString()] || []).includes(option);
answer[`${i}_${option}`] = checked ? 1 : '';
if (checked) {
@ -257,7 +257,7 @@ router.post('/census/moderation/decide', handleErrorAsync(async (req, res) => {
return res.status(401).json({error: 'Unauthorised'});
}
const queue = await req.db.get(SQL`
await req.db.get(SQL`
UPDATE census SET troll = ${parseInt(req.body.decision)} WHERE id = ${req.body.id}
`);

View File

@ -190,9 +190,9 @@ router.get('/nouns/:id.png', async (req, res) => {
}
let maxItems = 0;
['masc', 'fem', 'neutr'].forEach((form, column) => {
['masc', 'fem', 'neutr'].forEach((form) => {
let items = 0;
for (let [key, symbol] of [['', '⋅'], ['Pl', '⁖']]) {
for (let key of ['', 'Pl']) {
items += noun[form + key].split('|').filter(x => x.length).length;
}
if (items > maxItems) {

View File

@ -21,7 +21,6 @@ import multer from "multer";
const __dirname = new URL('.', import.meta.url).pathname;
const escapeRegExp = s => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // https://stackoverflow.com/a/6969486/3297012
const normalise = s => decodeURIComponent(s.trim().toLowerCase());
const normaliseWithLink = s => normalise(s.replace(/^@/, '').replace(new RegExp('^https://.*?/@'), ''))
@ -349,8 +348,8 @@ const selectBestLocale = (availableLocales) => {
return availableLocales[0];
}
if (availableLocales.includes(config.locale)) {
return config.locale;
if (availableLocales.includes(global.config.locale)) {
return global.config.locale;
}
return '_';
@ -976,13 +975,13 @@ router.post('/profile/import', multer({limits: {fileSize: 10 * 1024 * 1024}}).an
return res.status(400).json({error: 'profile.backup.error.signature'});
}
const {version, profiles, images} = JSON.parse(Buffer.from(payload, 'base64').toString('utf-8'));
const {profiles, images} = JSON.parse(Buffer.from(payload, 'base64').toString('utf-8'));
const s3 = new S3(awsConfig);
for (let [id, sizes] of Object.entries(images)) {
for (let [size, content] of Object.entries(sizes)) {
try {
const data = await s3.headObject({
await s3.headObject({
Key: `images/${id}-${size}.png`,
}).promise();
continue;

View File

@ -59,7 +59,7 @@ router.get('/pronounce/:voice/:pronoun*', handleErrorAsync(async (req, res) => {
Engine: voice.engine,
}).promise();
const s3putResponse = await s3.putObject({
await s3.putObject({
Key: key,
Body: pollyResponse.AudioStream,
ContentType: pollyResponse.ContentType,

View File

@ -131,7 +131,6 @@ router.get('/translations/contributors', handleErrorAsync(async (req, res) => {
`)) {
const author_info = await req.db.get(SQL`SELECT username, roles FROM users WHERE id=${author_id}`);
if (!author_info) { continue; }
const { username, roles } = author_info;
contributors.push({
username: author_info.username,
isMember: !!author_info.roles,

View File

@ -765,7 +765,6 @@ export class NounDeclension {
decline(word, plural) {
const plurality = plural ? 'plural' : 'singular';
const rep = Object.keys(this[plurality])[0];
const base = word.substring(0, word.length - this.matches(word, plural));
const options = this[plurality];

View File

@ -176,6 +176,6 @@ export function* getSocialLinks(config) {
yield* getLink(socialLinks, 'shop');
}
export function* getSupportLinks(config) {
export function* getSupportLinks() {
yield* getLink(supportLinks, 'all');
}

View File

@ -17,7 +17,7 @@ export class LinkAnalyser {
let $document;
try {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 10000);
setTimeout(() => controller.abort(), 10000);
const htmlString = await (await fetch(url, {signal: controller.signal})).text();
$document = new JSDOM(htmlString, { virtualConsole: new VirtualConsole() });
} catch (e) {
@ -60,7 +60,7 @@ export class LinkAnalyser {
try {
const fallback = new URL('/favicon.ico', url);
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 1000);
setTimeout(() => controller.abort(), 1000);
const res = await fetch(fallback, {signal: controller.signal});
if (res.ok) {
return fallback;

View File

@ -7,38 +7,6 @@ import expectedTranslations from '../locale/expectedTranslations.js';
import fs from 'fs';
import Suml from 'suml';
// TODO all the duplication...
const buildDict = (fn, ...args) => {
const dict = {};
for (let [key, value] of fn(...args)) {
dict[key] = value;
}
return dict;
}
const zip = (list, reverse) => {
return buildDict(function* () {
for (let [k, v] of list) {
yield reverse ? [v, k] : [k, v];
}
});
}
const sortByValue = (obj, reverse = false, firstN = -1) => {
let list = [];
for (let i in obj) {
if (obj.hasOwnProperty(i)) {
list.push([parseInt(obj[i]), i]);
}
}
list = list.sort((a, b) => reverse ? b[0] - a[0] : a[0] - b[0]);
if (firstN >= 0) {
list = list.slice(0, firstN);
}
return zip(list, true);
}
const deepGet = (obj, path) => {
let value = obj;
for (let part of path.split('.')) {

View File

@ -76,7 +76,7 @@ export const mutations = {
state.translationChanges = translationChanges;
} else {
state.translationChanges = buildDict(function* (that) {
for (let k in that) {
for (const k in that) {
if (!that.hasOwnProperty(k)) { continue; }
if (k !== key) {
yield [k, that[k]];

View File

@ -1,4 +1,4 @@
import { jest } from '@jest/globals';
import { beforeEach, describe, expect, jest, test } from '@jest/globals';
// workaround to be independent of the current selected locale
jest.unstable_mockModule('../data/pronouns/morphemes.js', () => {

696
yarn.lock

File diff suppressed because it is too large Load Diff