#220 [profile] card images
This commit is contained in:
parent
ba5df7e400
commit
9eaf3f8046
|
@ -13,6 +13,7 @@
|
|||
/stats.json
|
||||
|
||||
/cache
|
||||
/static/card
|
||||
|
||||
# Created by .ignore support plugin (hsz.mobi)
|
||||
### Node template
|
||||
|
|
|
@ -61,15 +61,6 @@ body {
|
|||
max-width: min(90vw, 920px);
|
||||
}
|
||||
|
||||
@include media-breakpoint-up('lg', $grid-breakpoints) {
|
||||
body {
|
||||
margin-top: $header-margin;
|
||||
}
|
||||
.sticky-top {
|
||||
top: $header-height - 1px;
|
||||
}
|
||||
}
|
||||
|
||||
section {
|
||||
margin: 2*$spacer 0;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,182 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="mb-3 d-flex justify-content-between flex-column flex-md-row">
|
||||
<h2 class="text-nowrap">
|
||||
<Avatar :user="profile"/>
|
||||
@{{profile.username}}
|
||||
</h2>
|
||||
<div class="flex-grow-1 text-lg-end">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section v-if="profile.age || profile.description.trim().length || profile.team">
|
||||
<p v-if="profile.team" class="mb-2">
|
||||
<nuxt-link :to="`/${config.contact.team.route}`" class="badge bg-primary text-white">
|
||||
<Icon v="collective-logo.svg" class="inverted"/>
|
||||
<T>contact.team.member</T>
|
||||
</nuxt-link>
|
||||
</p>
|
||||
<p v-for="line in profile.description.split('\n')" class="mb-1">
|
||||
<Spelling escape :text="line"/>
|
||||
</p>
|
||||
<p v-if="profile.age">
|
||||
<Icon v="birthday-cake"/>
|
||||
{{ profile.age }}
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section v-if="profile.flags.length || Object.keys(profile.customFlags).length">
|
||||
<ul class="list-inline">
|
||||
<li v-for="flag in profile.flags" v-if="allFlags[flag]" class="list-inline-item pr-2">
|
||||
<Flag :name="flag.startsWith('-') ? allFlags[flag] : $translateForPronoun(allFlags[flag], mainPronoun)"
|
||||
:alt="allFlags[flag]"
|
||||
:img="`/flags/${flag}.png`"
|
||||
:terms="terms || []"/>
|
||||
</li>
|
||||
<li v-for="(desc, flag) in profile.customFlags" class="list-inline-item pr-2">
|
||||
<Flag :name="desc"
|
||||
:alt="desc"
|
||||
:img="buildImageUrl(flag, 'flag')"
|
||||
:terms="terms|| []"
|
||||
custom/>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section v-if="profile.links.length">
|
||||
<ul class="list-inline">
|
||||
<li v-for="link in profile.links" class="list-inline-item pr-2">
|
||||
<ProfileLink :link="link"/>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="d-flex">
|
||||
<div class="w-50" v-if="Object.keys(profile.names).length">
|
||||
<h3>
|
||||
<Icon v="signature"/>
|
||||
<T>profile.names</T>
|
||||
</h3>
|
||||
|
||||
<ul class="list-unstyled">
|
||||
<li v-for="(opinion, name) in profile.names"><Opinion :word="name" :opinion="opinion"/></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="w-50" v-if="Object.keys(profile.pronouns).length">
|
||||
<h3>
|
||||
<Icon v="tags"/>
|
||||
<T>profile.pronouns</T>
|
||||
</h3>
|
||||
|
||||
<ul class="list-unstyled">
|
||||
<li v-for="{link, pronoun, opinion} in pronounOpinions">
|
||||
<Opinion :word="typeof pronoun === 'string' ? pronoun : (pronoun.name(glue) + (pronoun.smallForm ? '/' + pronoun.morphemes[pronoun.smallForm] : ''))" :opinion="opinion" :link="`/${link}`"/>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="clearfix">
|
||||
<h3>
|
||||
<Icon v="scroll-old"/>
|
||||
<T>profile.words</T>
|
||||
</h3>
|
||||
|
||||
<div>
|
||||
<div v-for="group in profile.words" v-if="Object.keys(profile.words).length" class="float-start w-50 w-md-25">
|
||||
<ul class="list-unstyled">
|
||||
<li v-for="(opinion, word) in group"><Opinion :word="word" :opinion="opinion"/></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<OpinionLegend/>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { pronouns } from "~/src/data";
|
||||
import { buildPronoun } from "../src/buildPronoun";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
profile: { required: true },
|
||||
terms: { 'default': null },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
allFlags: process.env.FLAGS,
|
||||
glue: ' ' + this.$t('pronouns.or') + ' ',
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
pronounOpinions() {
|
||||
const pronounOpinions = [];
|
||||
for (let pronoun in this.profile.pronouns) {
|
||||
if (!this.profile.pronouns.hasOwnProperty(pronoun)) { continue; }
|
||||
|
||||
let link = decodeURIComponent(
|
||||
pronoun
|
||||
.trim()
|
||||
.replace(new RegExp('^' + this.$base), '')
|
||||
.replace(new RegExp('^' + this.$base.replace(/^https?:\/\//, '')), '')
|
||||
.replace(new RegExp('^/'), '')
|
||||
);
|
||||
if (!link.startsWith(':')) {
|
||||
link = link.toLowerCase();
|
||||
}
|
||||
|
||||
if (link === this.config.pronouns.any || link === this.config.pronouns.avoiding) {
|
||||
pronounOpinions.push({
|
||||
link,
|
||||
pronoun: link,
|
||||
opinion: this.profile.pronouns[pronoun],
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
const pronounEntity = buildPronoun(pronouns, link);
|
||||
|
||||
if (pronounEntity) {
|
||||
pronounOpinions.push({
|
||||
link,
|
||||
pronoun: pronounEntity,
|
||||
opinion: this.profile.pronouns[pronoun],
|
||||
});
|
||||
}
|
||||
}
|
||||
return pronounOpinions;
|
||||
},
|
||||
mainPronoun() {
|
||||
let mainPronoun = buildPronoun(pronouns, this.config.profile.flags.defaultPronoun);
|
||||
let mainOpinion = -1;
|
||||
for (let {pronoun, opinion} of this.pronounOpinions) {
|
||||
if (typeof pronoun === 'string') {
|
||||
continue;
|
||||
}
|
||||
if (opinion === 2) {
|
||||
opinion = 0.5;
|
||||
}
|
||||
if (opinion > mainOpinion) {
|
||||
mainPronoun = pronoun;
|
||||
mainOpinion = opinion;
|
||||
}
|
||||
}
|
||||
|
||||
return mainPronoun;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.avatar {
|
||||
width: 100%;
|
||||
max-width: 5rem;
|
||||
max-height: 5rem;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,20 @@
|
|||
<template>
|
||||
<main class="container vh-100">
|
||||
<Nuxt/>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import dark from "../plugins/dark";
|
||||
|
||||
export default {
|
||||
mixins: [dark],
|
||||
mounted() {
|
||||
this.setMode(this.detectDark());
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "assets/style";
|
||||
</style>
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="d-flex flex-column vh-100">
|
||||
<div class="d-flex flex-column vh-100 body">
|
||||
<div class="flex-grow-1">
|
||||
<Header/>
|
||||
<main class="container">
|
||||
|
@ -56,4 +56,13 @@
|
|||
<style lang="scss">
|
||||
@import "assets/style";
|
||||
@import "~avris-sorter/dist/Sorter.min.css";
|
||||
|
||||
@include media-breakpoint-up('lg', $grid-breakpoints) {
|
||||
.body {
|
||||
margin-top: $header-margin;
|
||||
}
|
||||
.sticky-top {
|
||||
top: $header-height - 1px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -414,6 +414,10 @@ profile:
|
|||
# Then you can link to it in your bio or email footer.
|
||||
# Just create an account {/account=here}.
|
||||
# bannerButton: 'Create a card'
|
||||
# TODO
|
||||
card:
|
||||
link: 'Card picture'
|
||||
generating: 'Generation in progress…'
|
||||
|
||||
share: 'Teilen'
|
||||
|
||||
|
|
|
@ -497,6 +497,9 @@ profile:
|
|||
Then you can link to it in your bio or email footer.
|
||||
Just create an account {/account=here}.
|
||||
bannerButton: 'Create a card'
|
||||
card:
|
||||
link: 'Card picture'
|
||||
generating: 'Generation in progress…'
|
||||
|
||||
share: 'Share'
|
||||
|
||||
|
|
|
@ -423,6 +423,10 @@ profile:
|
|||
# Then you can link to it in your bio or email footer.
|
||||
# Just create an account {/account=here}.
|
||||
# bannerButton: 'Create a card'
|
||||
# TODO
|
||||
card:
|
||||
link: 'Card picture'
|
||||
generating: 'Generation in progress…'
|
||||
|
||||
share: 'Compartir'
|
||||
|
||||
|
|
|
@ -417,6 +417,10 @@ profile:
|
|||
# Then you can link to it in your bio or email footer.
|
||||
# Just create an account {/account=here}.
|
||||
# bannerButton: 'Create a card'
|
||||
# TODO
|
||||
card:
|
||||
link: 'Card picture'
|
||||
generating: 'Generation in progress…'
|
||||
|
||||
share: 'Deel'
|
||||
|
||||
|
|
|
@ -977,6 +977,9 @@ profile:
|
|||
Potem możesz do niej podlinkować w swoim bio czy stopce maila.
|
||||
Wystarczy, że założysz konto {/konto=tutaj}.
|
||||
bannerButton: 'Stwórz wizytówkę'
|
||||
card:
|
||||
link: 'Obrazek'
|
||||
generating: 'Trwa generowanie obrazka…'
|
||||
|
||||
census:
|
||||
header: 'Spis'
|
||||
|
|
|
@ -421,6 +421,10 @@ profile:
|
|||
# Then you can link to it in your bio or email footer.
|
||||
# Just create an account {/account=here}.
|
||||
# bannerButton: 'Create a card'
|
||||
# TODO
|
||||
card:
|
||||
link: 'Card picture'
|
||||
generating: 'Generation in progress…'
|
||||
|
||||
share: 'Compartilhar'
|
||||
|
||||
|
|
|
@ -956,6 +956,10 @@ profile:
|
|||
# Then you can link to it in your bio or email footer.
|
||||
# Just create an account {/account=here}.
|
||||
# bannerButton: 'Create a card'
|
||||
# TODO
|
||||
card:
|
||||
link: 'Card picture'
|
||||
generating: 'Generation in progress…'
|
||||
|
||||
census:
|
||||
header: 'Spis'
|
||||
|
|
|
@ -435,6 +435,10 @@ profile:
|
|||
# Then you can link to it in your bio or email footer.
|
||||
# Just create an account {/account=here}.
|
||||
# bannerButton: 'Create a card'
|
||||
# TODO
|
||||
card:
|
||||
link: 'Card picture'
|
||||
generating: 'Generation in progress…'
|
||||
|
||||
share: 'Share'
|
||||
|
||||
|
|
|
@ -402,6 +402,10 @@ profile:
|
|||
# Then you can link to it in your bio or email footer.
|
||||
# Just create an account {/account=here}.
|
||||
# bannerButton: 'Create a card'
|
||||
# TODO
|
||||
card:
|
||||
link: 'Card picture'
|
||||
generating: 'Generation in progress…'
|
||||
|
||||
share: '這裡'
|
||||
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
-- Up
|
||||
|
||||
ALTER TABLE profiles ADD COLUMN card TEXT NULL;
|
||||
|
||||
-- Down
|
|
@ -230,6 +230,7 @@ export default {
|
|||
|
||||
if (config.profile.enabled) {
|
||||
routes.push({path: '/@*', component: resolve(__dirname, 'routes/profile.vue')});
|
||||
routes.push({path: '/card/@*', component: resolve(__dirname, 'routes/profileCard.vue')});
|
||||
if (config.profile.editorEnabled) {
|
||||
routes.push({path: '/editor', component: resolve(__dirname, 'routes/profileEditor.vue')});
|
||||
}
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
"markdown-loader": "^6.0.0",
|
||||
"multer": "^1.4.2",
|
||||
"nuxt": "^2.15.2",
|
||||
"pageres": "^6.2.3",
|
||||
"rtlcss": "^3.1.2",
|
||||
"sha1": "^1.1.1",
|
||||
"sql-template-strings": "^2.2.2",
|
||||
|
|
|
@ -1,40 +1,5 @@
|
|||
<template>
|
||||
<div v-if="profile">
|
||||
<ClientOnly>
|
||||
<div slot="placeholder" class="my-5 text-center">
|
||||
<Spinner size="5rem"/>
|
||||
</div>
|
||||
|
||||
<div class="mb-3 d-flex justify-content-between flex-column flex-md-row">
|
||||
<h2 class="text-nowrap">
|
||||
<Avatar :user="profile"/>
|
||||
@{{username}}
|
||||
</h2>
|
||||
<div class="flex-grow-1 text-lg-end">
|
||||
<div>
|
||||
<nuxt-link v-if="$user() && $user().username === username" to="/editor"
|
||||
class="btn btn-outline-primary btn-sm mb-2 mx-1"
|
||||
>
|
||||
<Icon v="edit"/>
|
||||
<T>profile.edit</T>
|
||||
</nuxt-link>
|
||||
<a :href="`https://pronouns.page/@${username}`" v-if="Object.keys(profiles).length > 1 && $user() && $user().username === username"
|
||||
class="btn btn-outline-secondary btn-sm mb-2 mx-1"
|
||||
>
|
||||
<Icon v="external-link"/>
|
||||
pronouns.page/@{{username}}
|
||||
</a>
|
||||
</div>
|
||||
<div v-if="Object.keys(profiles).length > 1">
|
||||
<LocaleLink v-for="(options, locale) in locales" :key="locale" v-if="profiles[locale] !== undefined"
|
||||
:locale="locale" :link="`/@${username}`"
|
||||
:class="['btn', locale === config.locale ? 'btn-primary disabled' : 'btn-outline-primary', 'btn-sm', 'mb-2 mx-1']">
|
||||
{{options.name}}
|
||||
</LocaleLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section v-if="$isGranted('users') && profile.bannedReason">
|
||||
<div class="alert alert-warning">
|
||||
<p class="h4">
|
||||
|
@ -45,91 +10,48 @@
|
|||
</div>
|
||||
</section>
|
||||
|
||||
<section v-if="profile.age || profile.description.trim().length || profile.team">
|
||||
<p v-if="profile.team" class="mb-2">
|
||||
<nuxt-link :to="`/${config.contact.team.route}`" class="badge bg-primary text-white">
|
||||
<Icon v="collective-logo.svg" class="inverted"/>
|
||||
<T>contact.team.member</T>
|
||||
<Profile :profile="profile" :terms="terms">
|
||||
<div v-if="Object.keys(profiles).length > 1">
|
||||
<LocaleLink v-for="(options, locale) in locales" :key="locale" v-if="profiles[locale] !== undefined"
|
||||
:locale="locale" :link="`/@${profile.username}`"
|
||||
:class="['btn', locale === config.locale ? 'btn-primary disabled' : 'btn-outline-primary', 'btn-sm', 'mb-2 mx-1']">
|
||||
{{options.name}}
|
||||
</LocaleLink>
|
||||
</div>
|
||||
<div v-if="$user() && $user().username === profile.username">
|
||||
<nuxt-link to="/editor"
|
||||
class="btn btn-primary btn-sm mb-2 mx-1"
|
||||
>
|
||||
<Icon v="edit"/>
|
||||
<T>profile.edit</T>
|
||||
</nuxt-link>
|
||||
</p>
|
||||
<p v-for="line in profile.description.split('\n')" class="mb-1">
|
||||
<Spelling escape :text="line"/>
|
||||
</p>
|
||||
<p v-if="profile.age">
|
||||
<Icon v="birthday-cake"/>
|
||||
{{ profile.age }}
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section v-if="profile.flags.length || Object.keys(profile.customFlags).length">
|
||||
<ul class="list-inline">
|
||||
<li v-for="flag in profile.flags" v-if="allFlags[flag]" class="list-inline-item pr-2">
|
||||
<Flag :name="flag.startsWith('-') ? allFlags[flag] : $translateForPronoun(allFlags[flag], mainPronoun)"
|
||||
:alt="allFlags[flag]"
|
||||
:img="`/flags/${flag}.png`"
|
||||
:terms="terms"/>
|
||||
</li>
|
||||
<li v-for="(desc, flag) in profile.customFlags" class="list-inline-item pr-2">
|
||||
<Flag :name="desc"
|
||||
:alt="desc"
|
||||
:img="buildImageUrl(flag, 'flag')"
|
||||
:terms="terms"
|
||||
custom/>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section v-if="profile.links.length">
|
||||
<ul class="list-inline">
|
||||
<li v-for="link in profile.links" class="list-inline-item pr-2">
|
||||
<ProfileLink :link="link"/>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="d-flex">
|
||||
<div class="w-50" v-if="Object.keys(profile.names).length">
|
||||
<h3>
|
||||
<Icon v="signature"/>
|
||||
<T>profile.names</T>
|
||||
</h3>
|
||||
|
||||
<ul class="list-unstyled">
|
||||
<li v-for="(opinion, name) in profile.names"><Opinion :word="name" :opinion="opinion"/></li>
|
||||
</ul>
|
||||
<a :href="`https://pronouns.page/@${profile.username}`" v-if="Object.keys(profiles).length > 1"
|
||||
class="btn btn-outline-secondary btn-sm mb-2 mx-1"
|
||||
>
|
||||
<Icon v="external-link"/>
|
||||
pronouns.page/@{{profile.username}}
|
||||
</a>
|
||||
</div>
|
||||
<div class="w-50" v-if="Object.keys(profile.pronouns).length">
|
||||
<h3>
|
||||
<Icon v="tags"/>
|
||||
<T>profile.pronouns</T>
|
||||
</h3>
|
||||
|
||||
<ul class="list-unstyled">
|
||||
<li v-for="{link, pronoun, opinion} in pronounOpinions">
|
||||
<Opinion :word="typeof pronoun === 'string' ? pronoun : (pronoun.name(glue) + (pronoun.smallForm ? '/' + pronoun.morphemes[pronoun.smallForm] : ''))" :opinion="opinion" :link="`/${link}`"/>
|
||||
</li>
|
||||
</ul>
|
||||
<div v-if="$user() && $user().username === profile.username">
|
||||
<small>
|
||||
<Icon v="id-card"/>
|
||||
<T>profile.card.link</T>:
|
||||
</small>
|
||||
<template v-if="profile.card">
|
||||
<a :href="profile.card" target="_blank" rel="noopener"
|
||||
class="btn btn-outline-success btn-sm mb-2 mx-1">
|
||||
<Icon v="sun"/>
|
||||
<T>mode.light</T>
|
||||
</a>
|
||||
<a :href="profile.card.replace('.png', '-dark.png')" target="_blank" rel="noopener"
|
||||
class="btn btn-outline-success btn-sm mb-2 mx-1">
|
||||
<Icon v="moon"/>
|
||||
<T>mode.dark</T>
|
||||
</a>
|
||||
</template>
|
||||
<small v-else><T>profile.card.generating</T></small>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="clearfix">
|
||||
<h3>
|
||||
<Icon v="scroll-old"/>
|
||||
<T>profile.words</T>
|
||||
</h3>
|
||||
|
||||
<div>
|
||||
<div v-for="group in profile.words" v-if="Object.keys(profile.words).length" class="float-start w-50 w-md-25">
|
||||
<ul class="list-unstyled">
|
||||
<li v-for="(opinion, word) in group"><Opinion :word="word" :opinion="opinion"/></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<OpinionLegend/>
|
||||
</section>
|
||||
</Profile>
|
||||
|
||||
<client-only>
|
||||
<section v-if="$isGranted('users')">
|
||||
|
@ -148,7 +70,6 @@
|
|||
<section>
|
||||
<Share/>
|
||||
</section>
|
||||
</ClientOnly>
|
||||
</div>
|
||||
<div v-else-if="Object.keys(profiles).length">
|
||||
<h2 class="text-nowrap mb-3">
|
||||
|
@ -170,18 +91,13 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import {head, listToDict} from "../src/helpers";
|
||||
import { pronouns } from "~/src/data";
|
||||
import { buildPronoun } from "../src/buildPronoun";
|
||||
import { head } from "../src/helpers";
|
||||
import ClientOnly from 'vue-client-only'
|
||||
|
||||
export default {
|
||||
components: { ClientOnly },
|
||||
data() {
|
||||
return {
|
||||
profiles: {},
|
||||
glue: ' ' + this.$t('pronouns.or') + ' ',
|
||||
allFlags: process.env.FLAGS,
|
||||
saving: false,
|
||||
terms: [],
|
||||
}
|
||||
|
@ -197,6 +113,15 @@
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
profile() {
|
||||
for (let locale in this.profiles) {
|
||||
if (locale === this.config.locale) {
|
||||
return this.profiles[locale];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
username() {
|
||||
const base = this.$route.params.pathMatch;
|
||||
|
||||
|
@ -214,70 +139,6 @@
|
|||
|
||||
return this.profile.username;
|
||||
},
|
||||
profile() {
|
||||
for (let locale in this.profiles) {
|
||||
if (locale === this.config.locale) {
|
||||
return this.profiles[locale];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
pronounOpinions() {
|
||||
const pronounOpinions = [];
|
||||
for (let pronoun in this.profile.pronouns) {
|
||||
if (!this.profile.pronouns.hasOwnProperty(pronoun)) { continue; }
|
||||
|
||||
let link = decodeURIComponent(
|
||||
pronoun
|
||||
.trim()
|
||||
.replace(new RegExp('^' + this.$base), '')
|
||||
.replace(new RegExp('^' + this.$base.replace(/^https?:\/\//, '')), '')
|
||||
.replace(new RegExp('^/'), '')
|
||||
);
|
||||
if (!link.startsWith(':')) {
|
||||
link = link.toLowerCase();
|
||||
}
|
||||
|
||||
if (link === this.config.pronouns.any || link === this.config.pronouns.avoiding) {
|
||||
pronounOpinions.push({
|
||||
link,
|
||||
pronoun: link,
|
||||
opinion: this.profile.pronouns[pronoun],
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
const pronounEntity = buildPronoun(pronouns, link);
|
||||
|
||||
if (pronounEntity) {
|
||||
pronounOpinions.push({
|
||||
link,
|
||||
pronoun: pronounEntity,
|
||||
opinion: this.profile.pronouns[pronoun],
|
||||
});
|
||||
}
|
||||
}
|
||||
return pronounOpinions;
|
||||
},
|
||||
mainPronoun() {
|
||||
let mainPronoun = buildPronoun(pronouns, this.config.profile.flags.defaultPronoun);
|
||||
let mainOpinion = -1;
|
||||
for (let {pronoun, opinion} of this.pronounOpinions) {
|
||||
if (typeof pronoun === 'string') {
|
||||
continue;
|
||||
}
|
||||
if (opinion === 2) {
|
||||
opinion = 0.5;
|
||||
}
|
||||
if (opinion > mainOpinion) {
|
||||
mainPronoun = pronoun;
|
||||
mainOpinion = opinion;
|
||||
}
|
||||
}
|
||||
|
||||
return mainPronoun;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async ban() {
|
||||
|
@ -305,12 +166,6 @@
|
|||
<style lang="scss" scoped>
|
||||
@import "assets/variables";
|
||||
|
||||
.avatar {
|
||||
width: 100%;
|
||||
max-width: 5rem;
|
||||
max-height: 5rem;
|
||||
}
|
||||
|
||||
.list-group-item-hoverable {
|
||||
&:hover {
|
||||
color: $primary;
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
<template>
|
||||
<Profile v-if="profile" :profile="profile" class="pb-3">
|
||||
<nuxt-link to="/">
|
||||
<h1 class="text-nowrap h5">
|
||||
<Icon v="tags"/>
|
||||
<T>title</T>
|
||||
</h1>
|
||||
</nuxt-link>
|
||||
</Profile>
|
||||
<NotFound v-else/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { head } from "../src/helpers";
|
||||
|
||||
export default {
|
||||
layout: 'basic',
|
||||
async asyncData({ app, route }) {
|
||||
return {
|
||||
profiles: await app.$axios.$get(`/profile/get/${encodeURIComponent(route.params.pathMatch)}`),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
profile() {
|
||||
for (let locale in this.profiles) {
|
||||
if (locale === this.config.locale) {
|
||||
return this.profiles[locale];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
},
|
||||
head() {
|
||||
return head({
|
||||
title: `@${this.$route.params.pathMatch}`,
|
||||
banner: `api/banner/@${this.$route.params.pathMatch}.png`,
|
||||
});
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -1,4 +1,4 @@
|
|||
export default {
|
||||
module.exports = {
|
||||
region: process.env.AWS_REGION,
|
||||
credentials: {
|
||||
accessKeyId: process.env.AWS_KEY,
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
require('dotenv').config({ path:__dirname + '/../.env' });
|
||||
const Pageres = require('pageres');
|
||||
const isHighLoadTime = require('./overload');
|
||||
const dbConnection = require('./db');
|
||||
const locales = require('../src/locales');
|
||||
const { ulid } = require('ulid');
|
||||
|
||||
const awsConfig = require('./aws');
|
||||
const S3 = require('aws-sdk/clients/s3');
|
||||
const s3 = new S3(awsConfig);
|
||||
|
||||
const urlBases = {}
|
||||
for (let [code, , url, ] of locales) {
|
||||
urlBases[code] = url + '/card/@'; // 'http://localhost:3000/card/@'
|
||||
}
|
||||
|
||||
(async () => {
|
||||
const db = await dbConnection();
|
||||
for (let {id, locale, username} of await db.all('SELECT profiles.id, profiles.locale, users.username FROM profiles LEFT JOIN users on profiles.userId = users.id WHERE profiles.card IS NULL')) {
|
||||
if (isHighLoadTime(locale)) {
|
||||
continue;
|
||||
}
|
||||
// if (locale !== 'pl' || username !== 'andrea') { continue; }
|
||||
|
||||
const cardId = ulid();
|
||||
const key = `card/${locale}/${username}-${cardId}.png`;
|
||||
|
||||
console.log(locale, username, cardId);
|
||||
|
||||
for (let dark of [false, true]) {
|
||||
const [ buffer ] = await new Pageres({ darkMode: dark })
|
||||
.src(urlBases[locale] + username, ['1024x300'])
|
||||
.run();
|
||||
|
||||
const s3putResponse = await s3.putObject({
|
||||
Key: dark ? key.replace('.png', '-dark.png') : key,
|
||||
Body: buffer,
|
||||
ContentType: 'image/png',
|
||||
ACL: 'public-read',
|
||||
}).promise();
|
||||
}
|
||||
|
||||
await db.get(`UPDATE profiles SET card='https://${awsConfig.params.Bucket}.s3.${awsConfig.region}.amazonaws.com/${key}' WHERE id='${id}'`)
|
||||
// '/card/@${username}.png'
|
||||
}
|
||||
})();
|
|
@ -1,3 +1,5 @@
|
|||
import isHighLoadTime from './overload';
|
||||
|
||||
const USER_AGENT_BOTS = /bot|crawler|baiduspider|80legs|ia_archiver|voyager|curl|wget|yahoo! slurp|mediapartners-google|facebookexternalhit|twitterbot|whatsapp|php|python/;
|
||||
const USER_AGENT_BROWSERS = /mozilla|msie|gecko|firefox|edge|opera|safari|netscape|konqueror|android/;
|
||||
|
||||
|
@ -11,27 +13,9 @@ const isBrowser = (userAgent) => {
|
|||
return isProbablyBrowser || !isProbablyBot;
|
||||
}
|
||||
|
||||
const overload = {
|
||||
en: [[17, 23]],
|
||||
};
|
||||
|
||||
const isHighLoad = (timestamp, overloadPeriods) => {
|
||||
if (overloadPeriods === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let [periodStart, periodEnd] of overloadPeriods) {
|
||||
if (timestamp.getUTCHours() >= periodStart && timestamp.getUTCHours() < periodEnd) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export default function(req, res, next) {
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
res.spa = isBrowser(req.headers['user-agent']) || isHighLoad(new Date, overload[process.env.LOCALE]);
|
||||
if (process.env.NODE_ENV === 'production' && !req.url.startsWith('/card/@')) {
|
||||
res.spa = isBrowser(req.headers['user-agent']) || isHighLoadTime(process.env.LOCALE);
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
const overloadPeriods = {
|
||||
en: [[17, 23]],
|
||||
};
|
||||
|
||||
module.exports = (locale, timestamp = new Date) => {
|
||||
if (overloadPeriods[locale] === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let [periodStart, periodEnd] of overloadPeriods[locale]) {
|
||||
if (timestamp.getUTCHours() >= periodStart && timestamp.getUTCHours() < periodEnd) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
|
@ -58,6 +58,7 @@ const fetchProfiles = async (db, username, self, isAdmin) => {
|
|||
footerAreas: profile.footerAreas ? profile.footerAreas.split(',') : [],
|
||||
bannedReason: profile.bannedReason,
|
||||
team: !!profile.roles,
|
||||
card: profile.card,
|
||||
};
|
||||
}
|
||||
return p;
|
||||
|
@ -89,7 +90,8 @@ router.post('/profile/save', handleErrorAsync(async (req, res) => {
|
|||
words = ${JSON.stringify(req.body.words)},
|
||||
teamName = ${req.isGranted('users') ? req.body.teamName || null : ''},
|
||||
footerName = ${req.isGranted('users') ? req.body.footerName || null : ''},
|
||||
footerAreas = ${req.isGranted('users') ? req.body.footerAreas.join(',').toLowerCase() || null : ''}
|
||||
footerAreas = ${req.isGranted('users') ? req.body.footerAreas.join(',').toLowerCase() || null : ''},
|
||||
card = NULL
|
||||
WHERE id = ${ids[0]}
|
||||
`);
|
||||
} else {
|
||||
|
|
Reference in New Issue