Merge branch 'develop' into sw-notification-action
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { defineComponent, PropType } from 'vue';
|
||||
|
||||
type Captcha = {
|
||||
render(container: string | Node, options: {
|
||||
@@ -32,7 +32,7 @@ declare global {
|
||||
export default defineComponent({
|
||||
props: {
|
||||
provider: {
|
||||
type: String,
|
||||
type: String as PropType<CaptchaProvider>,
|
||||
required: true,
|
||||
},
|
||||
sitekey: {
|
||||
@@ -51,19 +51,25 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
computed: {
|
||||
loaded() {
|
||||
return !!window[this.provider as CaptchaProvider];
|
||||
variable(): string {
|
||||
switch (this.provider) {
|
||||
case 'hcaptcha': return 'hcaptcha';
|
||||
case 'recaptcha': return 'grecaptcha';
|
||||
}
|
||||
},
|
||||
src() {
|
||||
loaded(): boolean {
|
||||
return !!window[this.variable];
|
||||
},
|
||||
src(): string {
|
||||
const endpoint = ({
|
||||
hcaptcha: 'https://hcaptcha.com/1',
|
||||
recaptcha: 'https://www.recaptcha.net/recaptcha',
|
||||
} as Record<PropertyKey, unknown>)[this.provider];
|
||||
} as Record<CaptchaProvider, string>)[this.provider];
|
||||
|
||||
return `${typeof endpoint == 'string' ? endpoint : 'about:invalid'}/api.js?render=explicit`;
|
||||
return `${typeof endpoint === 'string' ? endpoint : 'about:invalid'}/api.js?render=explicit`;
|
||||
},
|
||||
captcha() {
|
||||
return window[this.provider as CaptchaProvider] || {} as unknown as Captcha;
|
||||
captcha(): Captcha {
|
||||
return window[this.variable] || {} as unknown as Captcha;
|
||||
},
|
||||
},
|
||||
|
||||
@@ -94,7 +100,7 @@ export default defineComponent({
|
||||
|
||||
methods: {
|
||||
reset() {
|
||||
this.captcha?.reset();
|
||||
if (this.captcha?.reset) this.captcha.reset();
|
||||
},
|
||||
requestRender() {
|
||||
if (this.captcha.render && this.$refs.captcha instanceof Element) {
|
||||
|
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent, h, TransitionGroup } from 'vue';
|
||||
import MkAd from '@client/components/global/ad.vue';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
@@ -22,6 +23,11 @@ export default defineComponent({
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
ad: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
@@ -58,11 +64,7 @@ export default defineComponent({
|
||||
|
||||
if (
|
||||
i != this.items.length - 1 &&
|
||||
new Date(item.createdAt).getDate() != new Date(this.items[i + 1].createdAt).getDate() &&
|
||||
!item._prId_ &&
|
||||
!this.items[i + 1]._prId_ &&
|
||||
!item._featuredId_ &&
|
||||
!this.items[i + 1]._featuredId_
|
||||
new Date(item.createdAt).getDate() != new Date(this.items[i + 1].createdAt).getDate()
|
||||
) {
|
||||
const separator = h('div', {
|
||||
class: 'separator',
|
||||
@@ -86,7 +88,15 @@ export default defineComponent({
|
||||
|
||||
return [el, separator];
|
||||
} else {
|
||||
return el;
|
||||
if (this.ad && item._shouldInsertAd_) {
|
||||
return [h(MkAd, {
|
||||
class: 'a', // advertiseの意(ブロッカー対策)
|
||||
key: item.id + ':ad',
|
||||
prefer: ['horizontal', 'horizontal-big'],
|
||||
}), el];
|
||||
} else {
|
||||
return el;
|
||||
}
|
||||
}
|
||||
}));
|
||||
},
|
||||
@@ -95,6 +105,10 @@ export default defineComponent({
|
||||
|
||||
<style lang="scss">
|
||||
.sqadhkmv {
|
||||
> *:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
> *:not(:last-child) {
|
||||
margin-bottom: var(--margin);
|
||||
}
|
||||
|
@@ -81,7 +81,7 @@ export default defineComponent({
|
||||
getMenu() {
|
||||
return [{
|
||||
text: this.$ts.rename,
|
||||
icon: faICursor,
|
||||
icon: 'fas fa-i-cursor',
|
||||
action: this.rename
|
||||
}, {
|
||||
text: this.file.isSensitive ? this.$ts.unmarkAsSensitive : this.$ts.markAsSensitive,
|
||||
|
@@ -247,7 +247,7 @@ export default defineComponent({
|
||||
}
|
||||
}, null, {
|
||||
text: this.$ts.rename,
|
||||
icon: faICursor,
|
||||
icon: 'fas fa-i-cursor',
|
||||
action: this.rename
|
||||
}, null, {
|
||||
text: this.$ts.delete,
|
||||
|
@@ -614,7 +614,7 @@ export default defineComponent({
|
||||
type: 'label'
|
||||
}, this.folder ? {
|
||||
text: this.$ts.renameFolder,
|
||||
icon: faICursor,
|
||||
icon: 'fas fa-i-cursor',
|
||||
action: () => { this.renameFolder(this.folder); }
|
||||
} : undefined, this.folder ? {
|
||||
text: this.$ts.deleteFolder,
|
||||
|
@@ -35,6 +35,7 @@
|
||||
class="_button"
|
||||
@click="chosen(emoji, $event)"
|
||||
tabindex="0"
|
||||
:key="emoji"
|
||||
>
|
||||
<MkEmoji :emoji="emoji" :normal="true"/>
|
||||
</button>
|
||||
@@ -104,7 +105,7 @@ export default defineComponent({
|
||||
return {
|
||||
emojilist: markRaw(emojilist),
|
||||
getStaticImageUrl,
|
||||
pinned: this.$store.state.reactions,
|
||||
pinned: this.$store.reactiveState.reactions,
|
||||
width: this.asReactionPicker ? this.$store.state.reactionPickerWidth : 3,
|
||||
height: this.asReactionPicker ? this.$store.state.reactionPickerHeight : 2,
|
||||
big: this.asReactionPicker ? isDeviceTouch : false,
|
||||
|
71
src/client/components/forgot-password.vue
Normal file
71
src/client/components/forgot-password.vue
Normal file
@@ -0,0 +1,71 @@
|
||||
<template>
|
||||
<XModalWindow ref="dialog"
|
||||
:width="370"
|
||||
:height="400"
|
||||
@close="$refs.dialog.close()"
|
||||
@closed="$emit('closed')"
|
||||
>
|
||||
<template #header>{{ $ts.forgotPassword }}</template>
|
||||
|
||||
<form class="_monolithic_" @submit.prevent="onSubmit" v-if="$instance.enableEmail">
|
||||
<div class="_section">
|
||||
<MkInput v-model:value="username" type="text" pattern="^[a-zA-Z0-9_]+$" spellcheck="false" autofocus required>
|
||||
<span>{{ $ts.username }}</span>
|
||||
<template #prefix>@</template>
|
||||
</MkInput>
|
||||
|
||||
<MkInput v-model:value="email" type="email" spellcheck="false" required>
|
||||
<span>{{ $ts.emailAddress }}</span>
|
||||
<template #desc>{{ $ts._forgotPassword.enterEmail }}</template>
|
||||
</MkInput>
|
||||
|
||||
<MkButton type="submit" :disabled="processing" primary style="margin: 0 auto;">{{ $ts.send }}</MkButton>
|
||||
</div>
|
||||
<div class="_section">
|
||||
<MkA to="/about" class="_link">{{ $ts._forgotPassword.ifNoEmail }}</MkA>
|
||||
</div>
|
||||
</form>
|
||||
<div v-else>
|
||||
{{ $ts._forgotPassword.contactAdmin }}
|
||||
</div>
|
||||
</XModalWindow>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import XModalWindow from '@client/components/ui/modal-window.vue';
|
||||
import MkButton from '@client/components/ui/button.vue';
|
||||
import MkInput from '@client/components/ui/input.vue';
|
||||
import * as os from '@client/os';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
XModalWindow,
|
||||
MkButton,
|
||||
MkInput,
|
||||
},
|
||||
|
||||
emits: ['done', 'closed'],
|
||||
|
||||
data() {
|
||||
return {
|
||||
username: '',
|
||||
email: '',
|
||||
processing: false,
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
async onSubmit() {
|
||||
this.processing = true;
|
||||
await os.apiWithDialog('request-reset-password', {
|
||||
username: this.username,
|
||||
email: this.email,
|
||||
});
|
||||
|
||||
this.$emit('done');
|
||||
this.$refs.dialog.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
126
src/client/components/gallery-post-preview.vue
Normal file
126
src/client/components/gallery-post-preview.vue
Normal file
@@ -0,0 +1,126 @@
|
||||
<template>
|
||||
<MkA :to="`/gallery/${post.id}`" class="ttasepnz _panel" tabindex="-1">
|
||||
<div class="thumbnail">
|
||||
<ImgWithBlurhash class="img" :src="post.files[0].thumbnailUrl" :hash="post.files[0].blurhash"/>
|
||||
</div>
|
||||
<article>
|
||||
<header>
|
||||
<MkAvatar :user="post.user" class="avatar"/>
|
||||
</header>
|
||||
<footer>
|
||||
<span class="title">{{ post.title }}</span>
|
||||
</footer>
|
||||
</article>
|
||||
</MkA>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { userName } from '@client/filters/user';
|
||||
import ImgWithBlurhash from '@client/components/img-with-blurhash.vue';
|
||||
import * as os from '@client/os';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
ImgWithBlurhash
|
||||
},
|
||||
props: {
|
||||
post: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
userName
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.ttasepnz {
|
||||
display: block;
|
||||
position: relative;
|
||||
height: 200px;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
color: var(--accent);
|
||||
|
||||
> .thumbnail {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
> article {
|
||||
> footer {
|
||||
&:before {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .thumbnail {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
transition: all 0.5s ease;
|
||||
|
||||
> .img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
> article {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
> header {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
|
||||
> .avatar {
|
||||
margin-left: auto;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
> footer {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
padding: 16px;
|
||||
box-sizing: border-box;
|
||||
color: #fff;
|
||||
text-shadow: 0 0 8px #000;
|
||||
background: linear-gradient(transparent, rgba(0, 0, 0, 0.7));
|
||||
|
||||
&:before {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(rgba(0, 0, 0, 0.4), transparent);
|
||||
opacity: 0;
|
||||
transition: opacity 0.5s ease;
|
||||
}
|
||||
|
||||
> .title {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
163
src/client/components/global/ad.vue
Normal file
163
src/client/components/global/ad.vue
Normal file
@@ -0,0 +1,163 @@
|
||||
<template>
|
||||
<div class="qiivuoyo" v-if="ad">
|
||||
<div class="main" :class="ad.place" v-if="!showMenu">
|
||||
<a :href="ad.url" target="_blank">
|
||||
<img :src="ad.imageUrl">
|
||||
<button class="_button menu" @click.prevent.stop="toggleMenu"><span class="fas fa-info-circle"></span></button>
|
||||
</a>
|
||||
</div>
|
||||
<div class="menu" v-else>
|
||||
<div class="body">
|
||||
<div>Ads by {{ host }}</div>
|
||||
<!--<MkButton>{{ $ts.stopThisAd }}</MkButton>-->
|
||||
<button class="_textButton" @click="toggleMenu">{{ $ts.close }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else></div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref } from 'vue';
|
||||
import { instance } from '@client/instance';
|
||||
import { host } from '@client/config';
|
||||
import MkButton from '@client/components/ui/button.vue';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
MkButton
|
||||
},
|
||||
|
||||
props: {
|
||||
prefer: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
specify: {
|
||||
type: Object,
|
||||
required: false
|
||||
},
|
||||
},
|
||||
|
||||
setup(props) {
|
||||
const showMenu = ref(false);
|
||||
const toggleMenu = () => {
|
||||
showMenu.value = !showMenu.value;
|
||||
};
|
||||
|
||||
let ad = null;
|
||||
|
||||
if (props.specify) {
|
||||
ad = props.specify;
|
||||
} else {
|
||||
let ads = instance.ads.filter(ad => props.prefer.includes(ad.place));
|
||||
|
||||
if (ads.length === 0) {
|
||||
ads = instance.ads.filter(ad => ad.place === 'square');
|
||||
}
|
||||
|
||||
const high = ads.filter(ad => ad.priority === 'high');
|
||||
const middle = ads.filter(ad => ad.priority === 'middle');
|
||||
const low = ads.filter(ad => ad.priority === 'low');
|
||||
|
||||
if (high.length > 0) {
|
||||
ad = high[Math.floor(Math.random() * high.length)];
|
||||
} else if (middle.length > 0) {
|
||||
ad = middle[Math.floor(Math.random() * middle.length)];
|
||||
} else if (low.length > 0) {
|
||||
ad = low[Math.floor(Math.random() * low.length)];
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
ad,
|
||||
showMenu,
|
||||
toggleMenu,
|
||||
host,
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.qiivuoyo {
|
||||
background-size: auto auto;
|
||||
background-image: repeating-linear-gradient(45deg, transparent, transparent 8px, var(--ad) 8px, var(--ad) 14px );
|
||||
|
||||
> .main {
|
||||
text-align: center;
|
||||
|
||||
> a {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
vertical-align: bottom;
|
||||
|
||||
&:hover {
|
||||
> img {
|
||||
filter: contrast(120%);
|
||||
}
|
||||
}
|
||||
|
||||
> img {
|
||||
display: block;
|
||||
object-fit: contain;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
> .menu {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
background: var(--panel);
|
||||
}
|
||||
}
|
||||
|
||||
&.square {
|
||||
> a ,
|
||||
> a > img {
|
||||
max-width: min(300px, 100%);
|
||||
max-height: 300px;
|
||||
}
|
||||
}
|
||||
|
||||
&.horizontal {
|
||||
padding: 8px;
|
||||
|
||||
> a ,
|
||||
> a > img {
|
||||
max-width: min(600px, 100%);
|
||||
max-height: 80px;
|
||||
}
|
||||
}
|
||||
|
||||
&.horizontal-big {
|
||||
padding: 8px;
|
||||
|
||||
> a ,
|
||||
> a > img {
|
||||
max-width: min(600px, 100%);
|
||||
max-height: 250px;
|
||||
}
|
||||
}
|
||||
|
||||
&.vertical {
|
||||
> a ,
|
||||
> a > img {
|
||||
max-width: min(100px, 100%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .menu {
|
||||
padding: 8px;
|
||||
text-align: center;
|
||||
|
||||
> .body {
|
||||
padding: 8px;
|
||||
margin: 0 auto;
|
||||
max-width: 400px;
|
||||
border: solid 1px var(--divider);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -113,8 +113,6 @@ export default defineComponent({
|
||||
> .icon {
|
||||
padding-left: 2px;
|
||||
font-size: .9em;
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
> .self {
|
||||
|
@@ -12,8 +12,10 @@ import url from './global/url.vue';
|
||||
import i18n from './global/i18n';
|
||||
import loading from './global/loading.vue';
|
||||
import error from './global/error.vue';
|
||||
import ad from './global/ad.vue';
|
||||
|
||||
export default function(app: App) {
|
||||
app.component('I18n', i18n);
|
||||
app.component('Mfm', mfm);
|
||||
app.component('MkA', a);
|
||||
app.component('MkAcct', acct);
|
||||
@@ -25,5 +27,5 @@ export default function(app: App) {
|
||||
app.component('MkUrl', url);
|
||||
app.component('MkLoading', loading);
|
||||
app.component('MkError', error);
|
||||
app.component('I18n', i18n);
|
||||
app.component('MkAd', ad);
|
||||
}
|
||||
|
@@ -3,12 +3,12 @@
|
||||
<div class="szkkfdyq _popup">
|
||||
<div class="main">
|
||||
<template v-for="item in items">
|
||||
<button v-if="item.action" class="_button" @click="$event => { item.action($event); close(); }">
|
||||
<button v-if="item.action" class="_button" @click="$event => { item.action($event); close(); }" v-click-anime>
|
||||
<i class="icon" :class="item.icon"></i>
|
||||
<div class="text">{{ item.text }}</div>
|
||||
<span v-if="item.indicate" class="indicator"><i class="fas fa-circle"></i></span>
|
||||
</button>
|
||||
<MkA v-else :to="item.to" @click.passive="close()">
|
||||
<MkA v-else :to="item.to" @click.passive="close()" v-click-anime>
|
||||
<i class="icon" :class="item.icon"></i>
|
||||
<div class="text">{{ item.text }}</div>
|
||||
<span v-if="item.indicate" class="indicator"><i class="fas fa-circle"></i></span>
|
||||
@@ -16,16 +16,16 @@
|
||||
</template>
|
||||
</div>
|
||||
<div class="sub">
|
||||
<MkA to="/docs" @click.passive="close()">
|
||||
<MkA to="/docs" @click.passive="close()" v-click-anime>
|
||||
<i class="fas fa-question-circle icon"></i>
|
||||
<div class="text">{{ $ts.help }}</div>
|
||||
</MkA>
|
||||
<MkA to="/about" @click.passive="close()">
|
||||
<MkA to="/about" @click.passive="close()" v-click-anime>
|
||||
<i class="fas fa-info-circle icon"></i>
|
||||
<div class="text">{{ $t('aboutX', { x: instanceName }) }}</div>
|
||||
</MkA>
|
||||
<MkA to="/about-misskey" @click.passive="close()">
|
||||
<i class="fas fa-info-circle icon"></i>
|
||||
<MkA to="/about-misskey" @click.passive="close()" v-click-anime>
|
||||
<img src="/static-assets/favicon.png" class="icon"/>
|
||||
<div class="text">{{ $ts.aboutMisskey }}</div>
|
||||
</MkA>
|
||||
</div>
|
||||
@@ -101,6 +101,7 @@ export default defineComponent({
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
vertical-align: bottom;
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
border-radius: var(--radius);
|
||||
@@ -117,6 +118,7 @@ export default defineComponent({
|
||||
|
||||
> .icon {
|
||||
font-size: 26px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
> .text {
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="yohlumlk">
|
||||
<div class="yohlumlk" v-size="{ min: [350, 500] }">
|
||||
<MkAvatar class="avatar" :user="note.user"/>
|
||||
<div class="main">
|
||||
<XNoteHeader class="header" :note="note" :mini="true"/>
|
||||
@@ -50,18 +50,19 @@ export default defineComponent({
|
||||
display: flex;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
overflow: clip;
|
||||
font-size: 0.95em;
|
||||
|
||||
> .avatar {
|
||||
|
||||
@media (min-width: 350px) {
|
||||
&.min-width_350px {
|
||||
> .avatar {
|
||||
margin: 0 10px 0 0;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 500px) {
|
||||
&.min-width_500px {
|
||||
> .avatar {
|
||||
margin: 0 12px 0 0;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
|
@@ -17,7 +17,7 @@
|
||||
</MkButton>
|
||||
</div>
|
||||
|
||||
<XList ref="notes" :items="notes" v-slot="{ item: note }" :direction="reversed ? 'up' : 'down'" :reversed="reversed" :no-gap="noGap">
|
||||
<XList ref="notes" :items="notes" v-slot="{ item: note }" :direction="reversed ? 'up' : 'down'" :reversed="reversed" :no-gap="noGap" :ad="true">
|
||||
<XNote :note="note" class="_block" @update:note="updated(note, $event)" :key="note._featuredId_ || note._prId_ || note.id"/>
|
||||
</XList>
|
||||
|
||||
|
@@ -93,7 +93,7 @@ export default defineComponent({
|
||||
if (this.menu) return;
|
||||
this.menu = os.modalMenu([{
|
||||
text: this.$ts.renameFile,
|
||||
icon: faICursor,
|
||||
icon: 'fas fa-i-cursor',
|
||||
action: () => { this.rename(file) }
|
||||
}, {
|
||||
text: file.isSensitive ? this.$ts.unmarkAsSensitive : this.$ts.markAsSensitive,
|
||||
|
@@ -11,6 +11,7 @@
|
||||
<MkInput v-model:value="password" type="password" :with-password-toggle="true" v-if="!user || user && !user.usePasswordLessLogin" required>
|
||||
<span>{{ $ts.password }}</span>
|
||||
<template #prefix><i class="fas fa-lock"></i></template>
|
||||
<template #desc><button class="_textButton" @click="resetPassword">{{ $ts.forgotPassword }}</button></template>
|
||||
</MkInput>
|
||||
<MkButton type="submit" primary :disabled="signing" style="margin: 0 auto;">{{ signing ? $ts.loggingIn : $ts.login }}</MkButton>
|
||||
</div>
|
||||
@@ -49,8 +50,8 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { toUnicode } from 'punycode/';
|
||||
import MkButton from './ui/button.vue';
|
||||
import MkInput from './ui/input.vue';
|
||||
import MkButton from '@client/components/ui/button.vue';
|
||||
import MkInput from '@client/components/ui/input.vue';
|
||||
import { apiUrl, host } from '@client/config';
|
||||
import { byteify, hexify } from '@client/scripts/2fa';
|
||||
import * as os from '@client/os';
|
||||
@@ -197,6 +198,11 @@ export default defineComponent({
|
||||
this.signing = false;
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
resetPassword() {
|
||||
os.popup(import('@client/components/forgot-password.vue'), {}, {
|
||||
}, 'closed');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@@ -12,14 +12,16 @@ export default defineComponent({
|
||||
|
||||
return withDirectives(h('div', {
|
||||
class: 'pxhvhrfw',
|
||||
}, options.map(option => h('button', {
|
||||
}, options.map(option => withDirectives(h('button', {
|
||||
class: ['_button', { active: this.value === option.props.value }],
|
||||
key: option.props.value,
|
||||
disabled: this.value === option.props.value,
|
||||
onClick: () => {
|
||||
this.$emit('update:value', option.props.value);
|
||||
}
|
||||
}, option.children))), [
|
||||
}, option.children), [
|
||||
[resolveDirective('click-anime')]
|
||||
]))), [
|
||||
[resolveDirective('size'), { max: [500] }]
|
||||
]);
|
||||
}
|
||||
|
@@ -139,7 +139,8 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
&.primary {
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
color: #fff !important;
|
||||
background: var(--accent);
|
||||
|
||||
&:not(:disabled):hover {
|
||||
@@ -200,10 +201,6 @@ export default defineComponent({
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
&.primary {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
> .ripples {
|
||||
position: absolute;
|
||||
z-index: 0;
|
||||
|
@@ -199,6 +199,7 @@ export default defineComponent({
|
||||
> .fade {
|
||||
display: block;
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
|
@@ -10,8 +10,8 @@
|
||||
|
||||
<div v-else class="cxiknjgy">
|
||||
<slot :items="items"></slot>
|
||||
<div class="more" v-show="more" key="_more_">
|
||||
<MkButton class="button" v-appear="$store.state.enableInfiniteScroll ? fetchMore : null" @click="fetchMore" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" primary>
|
||||
<div class="more _gap" v-show="more" key="_more_">
|
||||
<MkButton class="button" v-appear="($store.state.enableInfiniteScroll && !disableAutoLoad) ? fetchMore : null" @click="fetchMore" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" primary>
|
||||
<template v-if="!moreFetching">{{ $ts.loadMore }}</template>
|
||||
<template v-if="moreFetching"><MkLoading inline/></template>
|
||||
</MkButton>
|
||||
@@ -38,6 +38,12 @@ export default defineComponent({
|
||||
pagination: {
|
||||
required: true
|
||||
},
|
||||
|
||||
disableAutoLoad: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
@@ -8,33 +8,35 @@
|
||||
@closed="$emit('closed')"
|
||||
>
|
||||
<template #header>{{ $ts.selectUser }}</template>
|
||||
<div class="tbhwbxda _section">
|
||||
<div class="inputs">
|
||||
<MkInput v-model:value="username" class="input" @update:value="search" ref="username"><span>{{ $ts.username }}</span><template #prefix>@</template></MkInput>
|
||||
<MkInput v-model:value="host" class="input" @update:value="search"><span>{{ $ts.host }}</span><template #prefix>@</template></MkInput>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tbhwbxda _section result" v-if="username != '' || host != ''" :class="{ hit: users.length > 0 }">
|
||||
<div class="users" v-if="users.length > 0">
|
||||
<div class="user" v-for="user in users" :key="user.id" :class="{ selected: selected && selected.id === user.id }" @click="selected = user" @dblclick="ok()">
|
||||
<MkAvatar :user="user" class="avatar" :show-indicator="true"/>
|
||||
<div class="body">
|
||||
<MkUserName :user="user" class="name"/>
|
||||
<MkAcct :user="user" class="acct"/>
|
||||
</div>
|
||||
<div class="tbhwbxda _monolithic_">
|
||||
<div class="_section">
|
||||
<div class="inputs">
|
||||
<MkInput v-model:value="username" class="input" @update:value="search" ref="username"><span>{{ $ts.username }}</span><template #prefix>@</template></MkInput>
|
||||
<MkInput v-model:value="host" class="input" @update:value="search"><span>{{ $ts.host }}</span><template #prefix>@</template></MkInput>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="empty">
|
||||
<span>{{ $ts.noUsers }}</span>
|
||||
<div class="_section result" v-if="username != '' || host != ''" :class="{ hit: users.length > 0 }">
|
||||
<div class="users" v-if="users.length > 0">
|
||||
<div class="user" v-for="user in users" :key="user.id" :class="{ selected: selected && selected.id === user.id }" @click="selected = user" @dblclick="ok()">
|
||||
<MkAvatar :user="user" class="avatar" :show-indicator="true"/>
|
||||
<div class="body">
|
||||
<MkUserName :user="user" class="name"/>
|
||||
<MkAcct :user="user" class="acct"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="empty">
|
||||
<span>{{ $ts.noUsers }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tbhwbxda _section recent" v-if="username == '' && host == ''">
|
||||
<div class="users">
|
||||
<div class="user" v-for="user in recentUsers" :key="user.id" :class="{ selected: selected && selected.id === user.id }" @click="selected = user" @dblclick="ok()">
|
||||
<MkAvatar :user="user" class="avatar" :show-indicator="true"/>
|
||||
<div class="body">
|
||||
<MkUserName :user="user" class="name"/>
|
||||
<MkAcct :user="user" class="acct"/>
|
||||
<div class="_section recent" v-if="username == '' && host == ''">
|
||||
<div class="users">
|
||||
<div class="user" v-for="user in recentUsers" :key="user.id" :class="{ selected: selected && selected.id === user.id }" @click="selected = user" @dblclick="ok()">
|
||||
<MkAvatar :user="user" class="avatar" :show-indicator="true"/>
|
||||
<div class="body">
|
||||
<MkUserName :user="user" class="name"/>
|
||||
<MkAcct :user="user" class="acct"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -122,76 +124,78 @@ export default defineComponent({
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tbhwbxda {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: auto;
|
||||
height: 100%;
|
||||
|
||||
&.result.hit {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
&.recent {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
> .inputs {
|
||||
> .input {
|
||||
display: inline-block;
|
||||
width: 50%;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
> .users {
|
||||
flex: 1;
|
||||
> ._section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: auto;
|
||||
padding: 8px 0;
|
||||
height: 100%;
|
||||
|
||||
> .user {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px var(--root-margin);
|
||||
font-size: 14px;
|
||||
&.result.hit {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: var(--X7);
|
||||
&.recent {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
> .inputs {
|
||||
> .input {
|
||||
display: inline-block;
|
||||
width: 50%;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background: var(--accent);
|
||||
color: #fff;
|
||||
}
|
||||
> .users {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
padding: 8px 0;
|
||||
|
||||
> * {
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
> .user {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px var(--root-margin);
|
||||
font-size: 14px;
|
||||
|
||||
> .avatar {
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
}
|
||||
|
||||
> .body {
|
||||
padding: 0 8px;
|
||||
min-width: 0;
|
||||
|
||||
> .name {
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
&:hover {
|
||||
background: var(--X7);
|
||||
}
|
||||
|
||||
> .acct {
|
||||
opacity: 0.5;
|
||||
&.selected {
|
||||
background: var(--accent);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
> * {
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
> .avatar {
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
}
|
||||
|
||||
> .body {
|
||||
padding: 0 8px;
|
||||
min-width: 0;
|
||||
|
||||
> .name {
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
> .acct {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .empty {
|
||||
opacity: 0.7;
|
||||
text-align: center;
|
||||
> .empty {
|
||||
opacity: 0.7;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
Reference in New Issue
Block a user