Merge branch 'develop' into pag-back
This commit is contained in:
commit
b330ede502
92 changed files with 3176 additions and 1027 deletions
|
|
@ -356,9 +356,7 @@ onMounted(() => {
|
|||
|
||||
props.textarea.addEventListener('keydown', onKeydown);
|
||||
|
||||
for (const el of Array.from(document.querySelectorAll('body *'))) {
|
||||
el.addEventListener('mousedown', onMousedown);
|
||||
}
|
||||
document.body.addEventListener('mousedown', onMousedown);
|
||||
|
||||
nextTick(() => {
|
||||
exec();
|
||||
|
|
@ -374,9 +372,7 @@ onMounted(() => {
|
|||
onBeforeUnmount(() => {
|
||||
props.textarea.removeEventListener('keydown', onKeydown);
|
||||
|
||||
for (const el of Array.from(document.querySelectorAll('body *'))) {
|
||||
el.removeEventListener('mousedown', onMousedown);
|
||||
}
|
||||
document.body.removeEventListener('mousedown', onMousedown);
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -61,15 +61,11 @@ onMounted(() => {
|
|||
rootEl.style.top = `${top}px`;
|
||||
rootEl.style.left = `${left}px`;
|
||||
|
||||
for (const el of Array.from(document.querySelectorAll('body *'))) {
|
||||
el.addEventListener('mousedown', onMousedown);
|
||||
}
|
||||
document.body.addEventListener('mousedown', onMousedown);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
for (const el of Array.from(document.querySelectorAll('body *'))) {
|
||||
el.removeEventListener('mousedown', onMousedown);
|
||||
}
|
||||
document.body.removeEventListener('mousedown', onMousedown);
|
||||
});
|
||||
|
||||
function onMousedown(evt: Event) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,60 @@
|
|||
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
||||
import { StoryObj } from '@storybook/vue3';
|
||||
import { rest } from 'msw';
|
||||
import { userDetailed, inviteCode } from '../../.storybook/fakes';
|
||||
import { commonHandlers } from '../../.storybook/mocks';
|
||||
import MkInviteCode from './MkInviteCode.vue';
|
||||
|
||||
export const Default = {
|
||||
render(args) {
|
||||
return {
|
||||
components: {
|
||||
MkInviteCode,
|
||||
},
|
||||
setup() {
|
||||
return {
|
||||
args,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
props() {
|
||||
return {
|
||||
...this.args,
|
||||
};
|
||||
},
|
||||
},
|
||||
template: '<MkInviteCode v-bind="props" />',
|
||||
};
|
||||
},
|
||||
args: {
|
||||
invite: inviteCode() as any,
|
||||
},
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
msw: {
|
||||
handlers: [
|
||||
...commonHandlers,
|
||||
rest.post('/api/users/show', (req, res, ctx) => {
|
||||
return res(ctx.json(userDetailed(req.params.userId as string)));
|
||||
}),
|
||||
],
|
||||
},
|
||||
},
|
||||
decorators: [() => ({
|
||||
template: '<div style="width:100cqmin"><story/></div>',
|
||||
})],
|
||||
} satisfies StoryObj<typeof MkInviteCode>;
|
||||
|
||||
export const Used = {
|
||||
...Default,
|
||||
args: {
|
||||
invite: inviteCode(true) as any
|
||||
},
|
||||
} satisfies StoryObj<typeof MkInviteCode>;
|
||||
|
||||
export const Expired = {
|
||||
...Default,
|
||||
args: {
|
||||
invite: inviteCode(false, true, true) as any
|
||||
},
|
||||
} satisfies StoryObj<typeof MkInviteCode>;
|
||||
124
packages/frontend/src/components/MkInviteCode.vue
Normal file
124
packages/frontend/src/components/MkInviteCode.vue
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
<template>
|
||||
<MkFolder>
|
||||
<template #label>{{ invite.code }}</template>
|
||||
<template #suffix>
|
||||
<span v-if="invite.used">{{ i18n.ts.used }}</span>
|
||||
<span v-else-if="isExpired" style="color: var(--error)">{{ i18n.ts.expired }}</span>
|
||||
<span v-else style="color: var(--success)">{{ i18n.ts.unused }}</span>
|
||||
</template>
|
||||
|
||||
<div class="_gaps_s" :class="$style.root">
|
||||
<div :class="$style.items">
|
||||
<div>
|
||||
<div :class="$style.label">{{ i18n.ts.invitationCode }}</div>
|
||||
<div>{{ invite.code }}</div>
|
||||
</div>
|
||||
<div v-if="moderator">
|
||||
<div :class="$style.label">{{ i18n.ts.inviteCodeCreator }}</div>
|
||||
<div v-if="invite.createdBy" :class="$style.user">
|
||||
<MkAvatar :user="invite.createdBy" :class="$style.avatar" link preview/>
|
||||
<MkUserName :user="invite.createdBy" :nowrap="false"/>
|
||||
<div v-if="moderator">({{ invite.createdBy.id }})</div>
|
||||
</div>
|
||||
<div v-else>system</div>
|
||||
</div>
|
||||
<div v-if="invite.used">
|
||||
<div :class="$style.label">{{ i18n.ts.registeredUserUsingInviteCode }}</div>
|
||||
<div v-if="invite.usedBy" :class="$style.user">
|
||||
<MkAvatar :user="invite.usedBy" :class="$style.avatar" link preview/>
|
||||
<MkUserName :user="invite.usedBy" :nowrap="false"/>
|
||||
<div v-if="moderator">({{ invite.usedBy.id }})</div>
|
||||
</div>
|
||||
<div v-else>{{ i18n.ts.unknown }} ({{ i18n.ts.waitingForMailAuth }})</div>
|
||||
</div>
|
||||
<div v-if="invite.expiresAt && !invite.used">
|
||||
<div :class="$style.label">{{ i18n.ts.expirationDate }}</div>
|
||||
<div><MkTime :time="invite.expiresAt" mode="absolute"/></div>
|
||||
</div>
|
||||
<div v-if="invite.usedAt">
|
||||
<div :class="$style.label">{{ i18n.ts.inviteCodeUsedAt }}</div>
|
||||
<div><MkTime :time="invite.usedAt" mode="absolute"/></div>
|
||||
</div>
|
||||
<div v-if="moderator">
|
||||
<div :class="$style.label">{{ i18n.ts.createdAt }}</div>
|
||||
<div><MkTime :time="invite.createdAt" mode="absolute"/></div>
|
||||
</div>
|
||||
</div>
|
||||
<div :class="$style.buttons">
|
||||
<MkButton v-if="!invite.used && !isExpired" primary rounded @click="copyInviteCode()"><i class="ti ti-copy"></i> {{ i18n.ts.copy }}</MkButton>
|
||||
<MkButton v-if="!invite.used || moderator" danger rounded @click="deleteCode()"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
|
||||
</div>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
import * as misskey from 'misskey-js';
|
||||
import MkFolder from '@/components/MkFolder.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import copyToClipboard from '@/scripts/copy-to-clipboard';
|
||||
import { i18n } from '@/i18n';
|
||||
import * as os from '@/os';
|
||||
|
||||
const props = defineProps<{
|
||||
invite: misskey.entities.Invite;
|
||||
moderator?: boolean;
|
||||
}>();
|
||||
|
||||
const emits = defineEmits<{
|
||||
(event: 'deleted', value: string): void;
|
||||
}>();
|
||||
|
||||
const isExpired = computed(() => {
|
||||
return props.invite.expiresAt && new Date(props.invite.expiresAt) < new Date();
|
||||
});
|
||||
|
||||
function deleteCode() {
|
||||
os.apiWithDialog('invite/delete', {
|
||||
inviteId: props.invite.id,
|
||||
});
|
||||
emits('deleted', props.invite.id);
|
||||
}
|
||||
|
||||
function copyInviteCode() {
|
||||
copyToClipboard(props.invite.code);
|
||||
os.success();
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.root {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.items {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
||||
grid-gap: 12px;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 0.85em;
|
||||
padding: 0 0 8px 0;
|
||||
user-select: none;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.user {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
--height: 24px;
|
||||
width: var(--height);
|
||||
height: var(--height);
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -256,7 +256,7 @@ export default function(props: {
|
|||
case 'mention': {
|
||||
return [h(MkMention, {
|
||||
key: Math.random(),
|
||||
host: (token.props.host == null && props.author && props.author.host != null ? props.author.host : token.props.host) || host,
|
||||
host: (token.props.host == null && props.author && props.author.host != null ? props.author.host : token.props.host) ?? host,
|
||||
username: token.props.username,
|
||||
})];
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue