Merge branch 'develop' into account_migration
This commit is contained in:
commit
7e3f8e8166
16
CALCKEY.md
16
CALCKEY.md
|
@ -3,6 +3,9 @@
|
||||||
## Planned
|
## Planned
|
||||||
|
|
||||||
- MFM button
|
- MFM button
|
||||||
|
- Sonic search support
|
||||||
|
- Use Cassandra for storing notes
|
||||||
|
- Federate with note edits
|
||||||
- Classic mode make instance icon bring up new context menu
|
- Classic mode make instance icon bring up new context menu
|
||||||
- Exclude self from antenna
|
- Exclude self from antenna
|
||||||
- Backfill remote users
|
- Backfill remote users
|
||||||
|
@ -19,17 +22,14 @@
|
||||||
|
|
||||||
## Work in progress
|
## Work in progress
|
||||||
|
|
||||||
|
- Account migration
|
||||||
|
- Link verification
|
||||||
- Better Messaging UI
|
- Better Messaging UI
|
||||||
- Better API Documentation
|
- Better API Documentation
|
||||||
- Remote follow button
|
- Remote follow button
|
||||||
- Admin custom CSS
|
- Admin custom CSS
|
||||||
- Add back time machine (jump to date)
|
- Add back time machine (jump to date)
|
||||||
- Improve accesibility score
|
- Improve accesibility
|
||||||
<details><summary>Current Misskey score is 57/100</summary>
|
|
||||||
|
|
||||||
![accesibility score](https://pool.jortage.com/voringme/misskey/8ff18aae-4dc6-4b08-9e05-a4c9d051a9e3.png)
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
## Implemented
|
## Implemented
|
||||||
|
|
||||||
|
@ -88,8 +88,9 @@
|
||||||
- Page drafts
|
- Page drafts
|
||||||
- Patron list
|
- Patron list
|
||||||
- Animations respect reduced motion
|
- Animations respect reduced motion
|
||||||
- Obliteration of Ai-chan
|
|
||||||
- Undo renote button inside original note
|
- Undo renote button inside original note
|
||||||
|
- Custom locales
|
||||||
|
- Obliteration of Ai-chan
|
||||||
- MissV: [fix Misskey Forkbomb](https://code.vtopia.live/Vtopia/MissV/commit/40b23c070bd4adbb3188c73546c6c625138fb3c1)
|
- MissV: [fix Misskey Forkbomb](https://code.vtopia.live/Vtopia/MissV/commit/40b23c070bd4adbb3188c73546c6c625138fb3c1)
|
||||||
- [Make showing ads optional](https://github.com/misskey-dev/misskey/pull/8996)
|
- [Make showing ads optional](https://github.com/misskey-dev/misskey/pull/8996)
|
||||||
- [Tapping avatar in mobile opens account modal](https://github.com/misskey-dev/misskey/pull/9056)
|
- [Tapping avatar in mobile opens account modal](https://github.com/misskey-dev/misskey/pull/9056)
|
||||||
|
@ -131,3 +132,4 @@
|
||||||
- 4c5aa9e53887cca5561fcec6ab0754e018f589a5: server: allow to like own pages
|
- 4c5aa9e53887cca5561fcec6ab0754e018f589a5: server: allow to like own pages
|
||||||
- 923c93da1228458dd65be47483c198a1a9191bcf: use await for notes.countBy
|
- 923c93da1228458dd65be47483c198a1a9191bcf: use await for notes.countBy
|
||||||
- ca90cedba0a0704b503c2778694230f5a7dfbace: server: reduce dead instance detection to 7 days
|
- ca90cedba0a0704b503c2778694230f5a7dfbace: server: reduce dead instance detection to 7 days
|
||||||
|
- e9ab42c10afb4e27516c2d2b5e3e06630efe9edd: Alt text in image viewer
|
||||||
|
|
|
@ -49,10 +49,10 @@ This guide will work for both **starting from scratch** and **migrating from Mis
|
||||||
|
|
||||||
## 📦 Dependencies
|
## 📦 Dependencies
|
||||||
|
|
||||||
- 🐢 At least [NodeJS](https://nodejs.org/en/) v18.12.1 (v19.1.0 recommended)
|
- 🐢 At least [NodeJS](https://nodejs.org/en/) v18.12.1 (v19 recommended)
|
||||||
- Install with [nvm](https://github.com/nvm-sh/nvm)
|
- Install with [nvm](https://github.com/nvm-sh/nvm)
|
||||||
- 🐘 At least [PostgreSQL](https://www.postgresql.org/) v12
|
- 🐘 At least [PostgreSQL](https://www.postgresql.org/) v12
|
||||||
- 🍱 At least [Redis](https://redis.io/) v6 (v7 recommended)
|
- 🍱 At least [Redis](https://redis.io/) v6 (v7 recommended) or [Titan](https://github.com/distributedio/titan)
|
||||||
|
|
||||||
### 😗 Optional dependencies
|
### 😗 Optional dependencies
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,6 @@ export async function skippedInstances(hosts: Array<Instace['host']>): Array<Ins
|
||||||
})
|
})
|
||||||
.andWhere(new Brackets(qb => { qb
|
.andWhere(new Brackets(qb => { qb
|
||||||
.where('instance.isSuspended')
|
.where('instance.isSuspended')
|
||||||
.orWhere('instance.lastCommunicatedAt < :deadTime', { deadTime });
|
|
||||||
}))
|
}))
|
||||||
.select('host')
|
.select('host')
|
||||||
.getRawMany()
|
.getRawMany()
|
||||||
|
|
|
@ -306,6 +306,7 @@ import * as ep___users_groups_transfer from './endpoints/users/groups/transfer.j
|
||||||
import * as ep___users_groups_update from './endpoints/users/groups/update.js';
|
import * as ep___users_groups_update from './endpoints/users/groups/update.js';
|
||||||
import * as ep___users_lists_create from './endpoints/users/lists/create.js';
|
import * as ep___users_lists_create from './endpoints/users/lists/create.js';
|
||||||
import * as ep___users_lists_delete from './endpoints/users/lists/delete.js';
|
import * as ep___users_lists_delete from './endpoints/users/lists/delete.js';
|
||||||
|
import * as ep___users_lists_delete_all from './endpoints/users/lists/delete-all.js';
|
||||||
import * as ep___users_lists_list from './endpoints/users/lists/list.js';
|
import * as ep___users_lists_list from './endpoints/users/lists/list.js';
|
||||||
import * as ep___users_lists_pull from './endpoints/users/lists/pull.js';
|
import * as ep___users_lists_pull from './endpoints/users/lists/pull.js';
|
||||||
import * as ep___users_lists_push from './endpoints/users/lists/push.js';
|
import * as ep___users_lists_push from './endpoints/users/lists/push.js';
|
||||||
|
@ -631,6 +632,7 @@ const eps = [
|
||||||
['users/groups/update', ep___users_groups_update],
|
['users/groups/update', ep___users_groups_update],
|
||||||
['users/lists/create', ep___users_lists_create],
|
['users/lists/create', ep___users_lists_create],
|
||||||
['users/lists/delete', ep___users_lists_delete],
|
['users/lists/delete', ep___users_lists_delete],
|
||||||
|
['users/lists/delete-all', ep___users_lists_delete_all],
|
||||||
['users/lists/list', ep___users_lists_list],
|
['users/lists/list', ep___users_lists_list],
|
||||||
['users/lists/pull', ep___users_lists_pull],
|
['users/lists/pull', ep___users_lists_pull],
|
||||||
['users/lists/push', ep___users_lists_push],
|
['users/lists/push', ep___users_lists_push],
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
import { UserLists } from '@/models/index.js';
|
||||||
|
import define from '../../../define.js';
|
||||||
|
import { ApiError } from '../../../error.js';
|
||||||
|
|
||||||
|
export const meta = {
|
||||||
|
tags: ['lists'],
|
||||||
|
|
||||||
|
requireCredential: true,
|
||||||
|
|
||||||
|
kind: 'write:account',
|
||||||
|
|
||||||
|
description: 'Delete all lists of users.',
|
||||||
|
|
||||||
|
errors: {
|
||||||
|
noSuchList: {
|
||||||
|
message: 'No such list.',
|
||||||
|
code: 'NO_SUCH_LIST',
|
||||||
|
id: '78436795-db79-42f5-b1e2-55ea2cf19166',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const paramDef = {
|
||||||
|
type: 'object',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/no-default-export
|
||||||
|
export default define(meta, paramDef, async (ps, user) => {
|
||||||
|
while (await UserLists.findOneBy({ userId: user.id }) != null) {
|
||||||
|
const userList = await UserLists.findOneBy({ userId: user.id });
|
||||||
|
if (userList == null) {
|
||||||
|
throw new ApiError(meta.errors.noSuchList);
|
||||||
|
}
|
||||||
|
await UserLists.delete(userList.id);
|
||||||
|
}
|
||||||
|
});
|
|
@ -8,7 +8,8 @@
|
||||||
<template v-else><i class="ph-caret-down-bold ph-lg"></i></template>
|
<template v-else><i class="ph-caret-down-bold ph-lg"></i></template>
|
||||||
</button>
|
</button>
|
||||||
</header>
|
</header>
|
||||||
<transition :name="$store.state.animation ? 'folder-toggle' : ''"
|
<transition
|
||||||
|
:name="$store.state.animation ? 'folder-toggle' : ''"
|
||||||
@enter="enter"
|
@enter="enter"
|
||||||
@after-enter="afterEnter"
|
@after-enter="afterEnter"
|
||||||
@leave="leave"
|
@leave="leave"
|
||||||
|
@ -27,17 +28,18 @@ import tinycolor from 'tinycolor2';
|
||||||
|
|
||||||
const localStoragePrefix = 'ui:folder:';
|
const localStoragePrefix = 'ui:folder:';
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/no-default-export
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
expanded: {
|
expanded: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
default: true
|
default: true,
|
||||||
},
|
},
|
||||||
persistKey: {
|
persistKey: {
|
||||||
type: String,
|
type: String,
|
||||||
required: false,
|
required: false,
|
||||||
default: null
|
default: null,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
|
@ -51,7 +53,7 @@ export default defineComponent({
|
||||||
if (this.persistKey) {
|
if (this.persistKey) {
|
||||||
localStorage.setItem(localStoragePrefix + this.persistKey, this.showBody ? 't' : 'f');
|
localStorage.setItem(localStoragePrefix + this.persistKey, this.showBody ? 't' : 'f');
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
function getParentBg(el: Element | null): string {
|
function getParentBg(el: Element | null): string {
|
||||||
|
@ -91,7 +93,7 @@ export default defineComponent({
|
||||||
afterLeave(el) {
|
afterLeave(el) {
|
||||||
el.style.height = null;
|
el.style.height = null;
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -46,7 +46,7 @@ onMounted(() => {
|
||||||
src: media.url,
|
src: media.url,
|
||||||
w: media.properties.width,
|
w: media.properties.width,
|
||||||
h: media.properties.height,
|
h: media.properties.height,
|
||||||
alt: media.name,
|
alt: media.comment,
|
||||||
};
|
};
|
||||||
if (media.properties.orientation != null && media.properties.orientation >= 5) {
|
if (media.properties.orientation != null && media.properties.orientation >= 5) {
|
||||||
[item.w, item.h] = [item.h, item.w];
|
[item.w, item.h] = [item.h, item.w];
|
||||||
|
@ -89,9 +89,38 @@ onMounted(() => {
|
||||||
[itemData.w, itemData.h] = [itemData.h, itemData.w];
|
[itemData.w, itemData.h] = [itemData.h, itemData.w];
|
||||||
}
|
}
|
||||||
itemData.msrc = file.thumbnailUrl;
|
itemData.msrc = file.thumbnailUrl;
|
||||||
|
itemData.alt = file.comment;
|
||||||
itemData.thumbCropped = true;
|
itemData.thumbCropped = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
lightbox.on('uiRegister', () => {
|
||||||
|
lightbox.pswp.ui.registerElement({
|
||||||
|
name: 'altText',
|
||||||
|
className: 'pwsp__alt-text-container',
|
||||||
|
appendTo: 'wrapper',
|
||||||
|
onInit: (el, pwsp) => {
|
||||||
|
let textBox = document.createElement('p');
|
||||||
|
textBox.className = 'pwsp__alt-text';
|
||||||
|
el.appendChild(textBox);
|
||||||
|
|
||||||
|
let preventProp = function(ev: Event): void {
|
||||||
|
ev.stopPropagation();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Allow scrolling/text selection
|
||||||
|
el.onwheel = preventProp;
|
||||||
|
el.onclick = preventProp;
|
||||||
|
el.onpointerdown = preventProp;
|
||||||
|
el.onpointercancel = preventProp;
|
||||||
|
el.onpointermove = preventProp;
|
||||||
|
|
||||||
|
pwsp.on('change', () => {
|
||||||
|
textBox.textContent = pwsp.currSlide.data.alt?.trim();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
lightbox.init();
|
lightbox.init();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -193,4 +222,35 @@ const previewable = (file: misskey.entities.DriveFile): boolean => {
|
||||||
//z-index: v-bind(pswpZIndex);
|
//z-index: v-bind(pswpZIndex);
|
||||||
z-index: 2000000;
|
z-index: 2000000;
|
||||||
}
|
}
|
||||||
|
.pwsp__alt-text-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
bottom: 30px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
|
||||||
|
width: 75%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pwsp__alt-text {
|
||||||
|
color: white;
|
||||||
|
margin: 0 auto;
|
||||||
|
text-align: center;
|
||||||
|
padding: 10px;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
border-radius: 5px;
|
||||||
|
|
||||||
|
max-height: 10vh;
|
||||||
|
overflow-x: clip;
|
||||||
|
overflow-y: auto;
|
||||||
|
overscroll-behavior: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pwsp__alt-text:empty {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<XModalWindow
|
<XModalWindow
|
||||||
ref="dialog"
|
ref="dialog"
|
||||||
:width="370"
|
:width="400"
|
||||||
:height="400"
|
|
||||||
@close="onClose"
|
@close="onClose"
|
||||||
@closed="emit('closed')"
|
@closed="emit('closed')"
|
||||||
>
|
>
|
||||||
|
|
|
@ -1,65 +1,67 @@
|
||||||
<template>
|
<template>
|
||||||
<form class="qlvuhzng _formRoot" autocomplete="new-password" @submit.prevent="onSubmit">
|
<form class="qlvuhzng _formRoot" autocomplete="new-password" @submit.prevent="onSubmit">
|
||||||
<MkInput v-if="instance.disableRegistration" v-model="invitationCode" class="_formBlock" type="text" :spellcheck="false" required>
|
<MkInput v-if="instance.disableRegistration" v-model="invitationCode" class="_formBlock" type="text" :spellcheck="false" required data-cy-signup-invitation-code @update:modelValue="onChangeInvitationCode">
|
||||||
<template #label>{{ i18n.ts.invitationCode }}</template>
|
<template #label>{{ i18n.ts.invitationCode }}</template>
|
||||||
<template #prefix><i class="ph-key-bold ph-lg"></i></template>
|
<template #prefix><i class="ph-key-bold ph-lg"></i></template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
<MkInput v-model="username" class="_formBlock" type="text" pattern="^[a-zA-Z0-9_]{1,20}$" :spellcheck="false" required data-cy-signup-username @update:modelValue="onChangeUsername">
|
<div v-if="!instance.disableRegistration || (instance.disableRegistration && invitationState === 'entered')">
|
||||||
<template #label>{{ i18n.ts.username }} <div v-tooltip:dialog="i18n.ts.usernameInfo" class="_button _help"><i class="ph-question-bold"></i></div></template>
|
<MkInput v-model="username" class="_formBlock" type="text" pattern="^[a-zA-Z0-9_]{1,20}$" :spellcheck="false" required data-cy-signup-username @update:modelValue="onChangeUsername">
|
||||||
<template #prefix>@</template>
|
<template #label>{{ i18n.ts.username }} <div v-tooltip:dialog="i18n.ts.usernameInfo" class="_button _help"><i class="ph-question-bold"></i></div></template>
|
||||||
<template #suffix>@{{ host }}</template>
|
<template #prefix>@</template>
|
||||||
<template #caption>
|
<template #suffix>@{{ host }}</template>
|
||||||
<span v-if="usernameState === 'wait'" style="color:#999"><i class="ph-circle-notch-bold ph-lg fa-pulse ph-fw ph-lg"></i> {{ i18n.ts.checking }}</span>
|
<template #caption>
|
||||||
<span v-else-if="usernameState === 'ok'" style="color: var(--success)"><i class="ph-check-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts.available }}</span>
|
<span v-if="usernameState === 'wait'" style="color:#6e6a86"><i class="ph-circle-notch-bold ph-lg fa-pulse ph-fw ph-lg"></i> {{ i18n.ts.checking }}</span>
|
||||||
<span v-else-if="usernameState === 'unavailable'" style="color: var(--error)"><i class="ph-warning-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts.unavailable }}</span>
|
<span v-else-if="usernameState === 'ok'" style="color: var(--success)"><i class="ph-check-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts.available }}</span>
|
||||||
<span v-else-if="usernameState === 'error'" style="color: var(--error)"><i class="ph-warning-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts.error }}</span>
|
<span v-else-if="usernameState === 'unavailable'" style="color: var(--error)"><i class="ph-warning-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts.unavailable }}</span>
|
||||||
<span v-else-if="usernameState === 'invalid-format'" style="color: var(--error)"><i class="ph-warning-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts.usernameInvalidFormat }}</span>
|
<span v-else-if="usernameState === 'error'" style="color: var(--error)"><i class="ph-warning-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts.error }}</span>
|
||||||
<span v-else-if="usernameState === 'min-range'" style="color: var(--error)"><i class="ph-warning-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts.tooShort }}</span>
|
<span v-else-if="usernameState === 'invalid-format'" style="color: var(--error)"><i class="ph-warning-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts.usernameInvalidFormat }}</span>
|
||||||
<span v-else-if="usernameState === 'max-range'" style="color: var(--error)"><i class="ph-warning-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts.tooLong }}</span>
|
<span v-else-if="usernameState === 'min-range'" style="color: var(--error)"><i class="ph-warning-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts.tooShort }}</span>
|
||||||
</template>
|
<span v-else-if="usernameState === 'max-range'" style="color: var(--error)"><i class="ph-warning-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts.tooLong }}</span>
|
||||||
</MkInput>
|
|
||||||
<MkInput v-if="instance.emailRequiredForSignup" v-model="email" class="_formBlock" :debounce="true" type="email" :spellcheck="false" required data-cy-signup-email @update:modelValue="onChangeEmail">
|
|
||||||
<template #label>{{ i18n.ts.emailAddress }} <div v-tooltip:dialog="i18n.ts._signup.emailAddressInfo" class="_button _help"><i class="ph-question-bold"></i></div></template>
|
|
||||||
<template #prefix><i class="ph-envelope-simple-open-bold ph-lg"></i></template>
|
|
||||||
<template #caption>
|
|
||||||
<span v-if="emailState === 'wait'" style="color:#999"><i class="ph-circle-notch-bold ph-lg fa-pulse ph-fw ph-lg"></i> {{ i18n.ts.checking }}</span>
|
|
||||||
<span v-else-if="emailState === 'ok'" style="color: var(--success)"><i class="ph-check-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts.available }}</span>
|
|
||||||
<span v-else-if="emailState === 'unavailable:used'" style="color: var(--error)"><i class="ph-warning-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts._emailUnavailable.used }}</span>
|
|
||||||
<span v-else-if="emailState === 'unavailable:format'" style="color: var(--error)"><i class="ph-warning-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts._emailUnavailable.format }}</span>
|
|
||||||
<span v-else-if="emailState === 'unavailable:disposable'" style="color: var(--error)"><i class="ph-warning-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts._emailUnavailable.disposable }}</span>
|
|
||||||
<span v-else-if="emailState === 'unavailable:mx'" style="color: var(--error)"><i class="ph-warning-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts._emailUnavailable.mx }}</span>
|
|
||||||
<span v-else-if="emailState === 'unavailable:smtp'" style="color: var(--error)"><i class="ph-warning-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts._emailUnavailable.smtp }}</span>
|
|
||||||
<span v-else-if="emailState === 'unavailable'" style="color: var(--error)"><i class="ph-warning-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts.unavailable }}</span>
|
|
||||||
<span v-else-if="emailState === 'error'" style="color: var(--error)"><i class="ph-warning-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts.error }}</span>
|
|
||||||
</template>
|
|
||||||
</MkInput>
|
|
||||||
<MkInput v-model="password" class="_formBlock" type="password" autocomplete="new-password" required data-cy-signup-password @update:modelValue="onChangePassword">
|
|
||||||
<template #label>{{ i18n.ts.password }}</template>
|
|
||||||
<template #prefix><i class="ph-lock-bold ph-lg"></i></template>
|
|
||||||
<template #caption>
|
|
||||||
<span v-if="passwordStrength == 'low'" style="color: var(--error)"><i class="ph-warning-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts.weakPassword }}</span>
|
|
||||||
<span v-if="passwordStrength == 'medium'" style="color: var(--warn)"><i class="ph-check-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts.normalPassword }}</span>
|
|
||||||
<span v-if="passwordStrength == 'high'" style="color: var(--success)"><i class="ph-check-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts.strongPassword }}</span>
|
|
||||||
</template>
|
|
||||||
</MkInput>
|
|
||||||
<MkInput v-model="retypedPassword" class="_formBlock" type="password" autocomplete="new-password" required data-cy-signup-password-retype @update:modelValue="onChangePasswordRetype">
|
|
||||||
<template #label>{{ i18n.ts.password }} ({{ i18n.ts.retype }})</template>
|
|
||||||
<template #prefix><i class="ph-lock-bold ph-lg"></i></template>
|
|
||||||
<template #caption>
|
|
||||||
<span v-if="passwordRetypeState == 'match'" style="color: var(--success)"><i class="ph-check-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts.passwordMatched }}</span>
|
|
||||||
<span v-if="passwordRetypeState == 'not-match'" style="color: var(--error)"><i class="ph-warning-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts.passwordNotMatched }}</span>
|
|
||||||
</template>
|
|
||||||
</MkInput>
|
|
||||||
<MkSwitch v-if="instance.tosUrl" v-model="ToSAgreement" class="_formBlock tou">
|
|
||||||
<I18n :src="i18n.ts.agreeTo">
|
|
||||||
<template #0>
|
|
||||||
<a :href="instance.tosUrl" class="_link" target="_blank">{{ i18n.ts.tos }}</a>
|
|
||||||
</template>
|
</template>
|
||||||
</I18n>
|
</MkInput>
|
||||||
</MkSwitch>
|
<MkInput v-if="instance.emailRequiredForSignup" v-model="email" class="_formBlock" :debounce="true" type="email" :spellcheck="false" required data-cy-signup-email @update:modelValue="onChangeEmail">
|
||||||
<MkCaptcha v-if="instance.enableHcaptcha" ref="hcaptcha" v-model="hCaptchaResponse" class="_formBlock captcha" provider="hcaptcha" :sitekey="instance.hcaptchaSiteKey"/>
|
<template #label>{{ i18n.ts.emailAddress }} <div v-tooltip:dialog="i18n.ts._signup.emailAddressInfo" class="_button _help"><i class="ph-question-bold"></i></div></template>
|
||||||
<MkCaptcha v-if="instance.enableRecaptcha" ref="recaptcha" v-model="reCaptchaResponse" class="_formBlock captcha" provider="recaptcha" :sitekey="instance.recaptchaSiteKey"/>
|
<template #prefix><i class="ph-envelope-simple-open-bold ph-lg"></i></template>
|
||||||
<MkButton class="_formBlock" type="submit" :disabled="shouldDisableSubmitting" gradate data-cy-signup-submit>{{ i18n.ts.start }}</MkButton>
|
<template #caption>
|
||||||
|
<span v-if="emailState === 'wait'" style="color:#6e6a86"><i class="ph-circle-notch-bold ph-lg fa-pulse ph-fw ph-lg"></i> {{ i18n.ts.checking }}</span>
|
||||||
|
<span v-else-if="emailState === 'ok'" style="color: var(--success)"><i class="ph-check-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts.available }}</span>
|
||||||
|
<span v-else-if="emailState === 'unavailable:used'" style="color: var(--error)"><i class="ph-warning-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts._emailUnavailable.used }}</span>
|
||||||
|
<span v-else-if="emailState === 'unavailable:format'" style="color: var(--error)"><i class="ph-warning-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts._emailUnavailable.format }}</span>
|
||||||
|
<span v-else-if="emailState === 'unavailable:disposable'" style="color: var(--error)"><i class="ph-warning-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts._emailUnavailable.disposable }}</span>
|
||||||
|
<span v-else-if="emailState === 'unavailable:mx'" style="color: var(--error)"><i class="ph-warning-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts._emailUnavailable.mx }}</span>
|
||||||
|
<span v-else-if="emailState === 'unavailable:smtp'" style="color: var(--error)"><i class="ph-warning-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts._emailUnavailable.smtp }}</span>
|
||||||
|
<span v-else-if="emailState === 'unavailable'" style="color: var(--error)"><i class="ph-warning-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts.unavailable }}</span>
|
||||||
|
<span v-else-if="emailState === 'error'" style="color: var(--error)"><i class="ph-warning-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts.error }}</span>
|
||||||
|
</template>
|
||||||
|
</MkInput>
|
||||||
|
<MkInput v-model="password" class="_formBlock" type="password" autocomplete="new-password" required data-cy-signup-password @update:modelValue="onChangePassword">
|
||||||
|
<template #label>{{ i18n.ts.password }}</template>
|
||||||
|
<template #prefix><i class="ph-lock-bold ph-lg"></i></template>
|
||||||
|
<template #caption>
|
||||||
|
<span v-if="passwordStrength == 'low'" style="color: var(--error)"><i class="ph-warning-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts.weakPassword }}</span>
|
||||||
|
<span v-if="passwordStrength == 'medium'" style="color: var(--warn)"><i class="ph-check-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts.normalPassword }}</span>
|
||||||
|
<span v-if="passwordStrength == 'high'" style="color: var(--success)"><i class="ph-check-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts.strongPassword }}</span>
|
||||||
|
</template>
|
||||||
|
</MkInput>
|
||||||
|
<MkInput v-model="retypedPassword" class="_formBlock" type="password" autocomplete="new-password" required data-cy-signup-password-retype @update:modelValue="onChangePasswordRetype">
|
||||||
|
<template #label>{{ i18n.ts.password }} ({{ i18n.ts.retype }})</template>
|
||||||
|
<template #prefix><i class="ph-lock-bold ph-lg"></i></template>
|
||||||
|
<template #caption>
|
||||||
|
<span v-if="passwordRetypeState == 'match'" style="color: var(--success)"><i class="ph-check-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts.passwordMatched }}</span>
|
||||||
|
<span v-if="passwordRetypeState == 'not-match'" style="color: var(--error)"><i class="ph-warning-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts.passwordNotMatched }}</span>
|
||||||
|
</template>
|
||||||
|
</MkInput>
|
||||||
|
<MkSwitch v-if="instance.tosUrl" v-model="ToSAgreement" class="_formBlock tou">
|
||||||
|
<I18n :src="i18n.ts.agreeTo">
|
||||||
|
<template #0>
|
||||||
|
<a :href="instance.tosUrl" class="_link" target="_blank">{{ i18n.ts.tos }}</a>
|
||||||
|
</template>
|
||||||
|
</I18n>
|
||||||
|
</MkSwitch>
|
||||||
|
<MkCaptcha v-if="instance.enableHcaptcha" ref="hcaptcha" v-model="hCaptchaResponse" class="_formBlock captcha" provider="hcaptcha" :sitekey="instance.hcaptchaSiteKey"/>
|
||||||
|
<MkCaptcha v-if="instance.enableRecaptcha" ref="recaptcha" v-model="reCaptchaResponse" class="_formBlock captcha" provider="recaptcha" :sitekey="instance.recaptchaSiteKey"/>
|
||||||
|
<MkButton class="_formBlock" type="submit" :disabled="shouldDisableSubmitting" gradate data-cy-signup-submit>{{ i18n.ts.start }}</MkButton>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -99,6 +101,7 @@ let retypedPassword: string = $ref('');
|
||||||
let invitationCode: string = $ref('');
|
let invitationCode: string = $ref('');
|
||||||
let email = $ref('');
|
let email = $ref('');
|
||||||
let usernameState: null | 'wait' | 'ok' | 'unavailable' | 'error' | 'invalid-format' | 'min-range' | 'max-range' = $ref(null);
|
let usernameState: null | 'wait' | 'ok' | 'unavailable' | 'error' | 'invalid-format' | 'min-range' | 'max-range' = $ref(null);
|
||||||
|
let invitationState: null | 'entered' = $ref(null);
|
||||||
let emailState: null | 'wait' | 'ok' | 'unavailable:used' | 'unavailable:format' | 'unavailable:disposable' | 'unavailable:mx' | 'unavailable:smtp' | 'unavailable' | 'error' = $ref(null);
|
let emailState: null | 'wait' | 'ok' | 'unavailable:used' | 'unavailable:format' | 'unavailable:disposable' | 'unavailable:mx' | 'unavailable:smtp' | 'unavailable' | 'error' = $ref(null);
|
||||||
let passwordStrength: '' | 'low' | 'medium' | 'high' = $ref('');
|
let passwordStrength: '' | 'low' | 'medium' | 'high' = $ref('');
|
||||||
let passwordRetypeState: null | 'match' | 'not-match' = $ref(null);
|
let passwordRetypeState: null | 'match' | 'not-match' = $ref(null);
|
||||||
|
@ -115,6 +118,14 @@ const shouldDisableSubmitting = $computed((): boolean => {
|
||||||
passwordRetypeState === 'not-match';
|
passwordRetypeState === 'not-match';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function onChangeInvitationCode(): void {
|
||||||
|
if (invitationCode === '') {
|
||||||
|
invitationState = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
invitationState = 'entered';
|
||||||
|
}
|
||||||
|
|
||||||
function onChangeUsername(): void {
|
function onChangeUsername(): void {
|
||||||
if (username === '') {
|
if (username === '') {
|
||||||
usernameState = null;
|
usernameState = null;
|
||||||
|
|
|
@ -1,16 +1,15 @@
|
||||||
<template>
|
<template>
|
||||||
<XModalWindow
|
<XModalWindow
|
||||||
ref="dialog"
|
ref="dialog"
|
||||||
:width="366"
|
:width="400"
|
||||||
:height="500"
|
@close="dialog!.close()"
|
||||||
@close="dialog.close()"
|
|
||||||
@closed="$emit('closed')"
|
@closed="$emit('closed')"
|
||||||
>
|
>
|
||||||
<template #header>{{ i18n.ts.signup }}</template>
|
<template #header>{{ i18n.ts.signup }}</template>
|
||||||
|
|
||||||
<div class="_monolithic_">
|
<div class="_monolithic_">
|
||||||
<div class="_section">
|
<div class="_section">
|
||||||
<XSignup :auto-set="autoSet" @signup="onSignup" @signupEmailPending="onSignupEmailPending"/>
|
<XSignup :auto-set="autoSet" @signup="onSignup" @signup-email-pending="onSignupEmailPending"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</XModalWindow>
|
</XModalWindow>
|
||||||
|
@ -37,10 +36,10 @@ const dialog = $ref<InstanceType<typeof XModalWindow>>();
|
||||||
|
|
||||||
function onSignup(res) {
|
function onSignup(res) {
|
||||||
emit('done', res);
|
emit('done', res);
|
||||||
dialog.close();
|
dialog?.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onSignupEmailPending() {
|
function onSignupEmailPending() {
|
||||||
dialog.close();
|
dialog?.close();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -3,7 +3,10 @@
|
||||||
<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
|
<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
|
||||||
<MkSpacer :content-max="700">
|
<MkSpacer :content-max="700">
|
||||||
<div class="qkcjvfiv">
|
<div class="qkcjvfiv">
|
||||||
<MkButton primary class="add" @click="create"><i class="ph-plus-bold ph-lg"></i> {{ i18n.ts.createList }}</MkButton>
|
<div class="buttonWrapper">
|
||||||
|
<MkButton primary class="add" @click="create"><i class="ph-plus-bold ph-lg"></i> {{ i18n.ts.createList }}</MkButton>
|
||||||
|
<MkButton @click="deleteAll"><i class="ph-trash-bold ph-lg"></i> {{ i18n.ts.deleteAll }}</MkButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
<MkPagination v-slot="{items}" ref="pagingComponent" :pagination="pagination" class="lists _content">
|
<MkPagination v-slot="{items}" ref="pagingComponent" :pagination="pagination" class="lists _content">
|
||||||
<MkA v-for="list in items" :key="list.id" class="list _panel" :to="`/my/lists/${ list.id }`">
|
<MkA v-for="list in items" :key="list.id" class="list _panel" :to="`/my/lists/${ list.id }`">
|
||||||
|
@ -41,6 +44,17 @@ async function create() {
|
||||||
pagingComponent.reload();
|
pagingComponent.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function deleteAll() {
|
||||||
|
const { canceled } = await os.confirm({
|
||||||
|
type: 'warning',
|
||||||
|
text: i18n.t('removeAreYouSure', { x: 'all lists' }),
|
||||||
|
});
|
||||||
|
if (canceled) return;
|
||||||
|
|
||||||
|
await os.api('users/lists/delete-all');
|
||||||
|
os.success();
|
||||||
|
}
|
||||||
|
|
||||||
const headerActions = $computed(() => []);
|
const headerActions = $computed(() => []);
|
||||||
|
|
||||||
const headerTabs = $computed(() => []);
|
const headerTabs = $computed(() => []);
|
||||||
|
@ -57,8 +71,13 @@ definePageMetadata({
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.qkcjvfiv {
|
.qkcjvfiv {
|
||||||
> .add {
|
> .buttonWrapper {
|
||||||
margin: 0 auto var(--margin) auto;
|
display: grid;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
> .add {
|
||||||
|
margin: 0 auto var(--margin) auto;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> .lists {
|
> .lists {
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
<h1>{{ page.title }}</h1>
|
<h1>{{ page.title }}</h1>
|
||||||
</div>
|
</div>
|
||||||
<div class="menu-actions">
|
<div class="menu-actions">
|
||||||
|
<button v-tooltip="i18n.ts.copyUrl" @click="copyUrl" class="menu _button"><i class="ph-link-simple-bold ph-lg"/></button>
|
||||||
<MkA v-tooltip="i18n.ts._pages.viewSource" :to="`/@${username}/pages/${pageName}/view-source`" class="menu _button"><i class="ph-code-bold ph-lg"/></MkA>
|
<MkA v-tooltip="i18n.ts._pages.viewSource" :to="`/@${username}/pages/${pageName}/view-source`" class="menu _button"><i class="ph-code-bold ph-lg"/></MkA>
|
||||||
<template v-if="$i && $i.id === page.userId">
|
<template v-if="$i && $i.id === page.userId">
|
||||||
<MkA v-tooltip="i18n.ts._pages.editPage" class="menu _button" :to="`/pages/edit/${page.id}`"><i class="ph-pencil-bold ph-lg"/></MkA>
|
<MkA v-tooltip="i18n.ts._pages.editPage" class="menu _button" :to="`/pages/edit/${page.id}`"><i class="ph-pencil-bold ph-lg"/></MkA>
|
||||||
|
@ -55,7 +56,7 @@
|
||||||
</div> -->
|
</div> -->
|
||||||
</div>
|
</div>
|
||||||
<MkAd :prefer="['horizontal', 'horizontal-big']"/>
|
<MkAd :prefer="['horizontal', 'horizontal-big']"/>
|
||||||
<MkContainer :max-height="300" :foldable="true" class="other">
|
<MkContainer :max-height="300" :foldable="true" :expanded="false" class="other">
|
||||||
<template #header><i class="ph-clock-bold ph-lg"></i> {{ i18n.ts.recentPosts }}</template>
|
<template #header><i class="ph-clock-bold ph-lg"></i> {{ i18n.ts.recentPosts }}</template>
|
||||||
<MkPagination v-slot="{items}" :pagination="otherPostsPagination">
|
<MkPagination v-slot="{items}" :pagination="otherPostsPagination">
|
||||||
<MkPagePreview v-for="page in items" :key="page.id" :page="page" class="_gap"/>
|
<MkPagePreview v-for="page in items" :key="page.id" :page="page" class="_gap"/>
|
||||||
|
@ -80,6 +81,7 @@ import MkContainer from '@/components/MkContainer.vue';
|
||||||
import MkPagination from '@/components/MkPagination.vue';
|
import MkPagination from '@/components/MkPagination.vue';
|
||||||
import MkPagePreview from '@/components/MkPagePreview.vue';
|
import MkPagePreview from '@/components/MkPagePreview.vue';
|
||||||
import { i18n } from '@/i18n';
|
import { i18n } from '@/i18n';
|
||||||
|
import copyToClipboard from '@/scripts/copy-to-clipboard';
|
||||||
import { definePageMetadata } from '@/scripts/page-metadata';
|
import { definePageMetadata } from '@/scripts/page-metadata';
|
||||||
import { shareAvailable } from '@/scripts/share-available';
|
import { shareAvailable } from '@/scripts/share-available';
|
||||||
|
|
||||||
|
@ -113,6 +115,11 @@ function fetchPage() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function copyUrl() {
|
||||||
|
copyToClipboard(window.location.href);
|
||||||
|
os.success();
|
||||||
|
}
|
||||||
|
|
||||||
function getBgImg(): string {
|
function getBgImg(): string {
|
||||||
if (page.eyeCatchingImage != null) {
|
if (page.eyeCatchingImage != null) {
|
||||||
return `url(${page.eyeCatchingImage.url})`;
|
return `url(${page.eyeCatchingImage.url})`;
|
||||||
|
|
|
@ -144,22 +144,35 @@ function top(): void {
|
||||||
|
|
||||||
async function chooseList(ev: MouseEvent): Promise<void> {
|
async function chooseList(ev: MouseEvent): Promise<void> {
|
||||||
const lists = await os.api('users/lists/list');
|
const lists = await os.api('users/lists/list');
|
||||||
const items = lists.map((list) => ({
|
const items = [{
|
||||||
|
type: 'link' as const,
|
||||||
|
text: i18n.ts.manageLists,
|
||||||
|
icon: 'ph-faders-horizontal-bold ph-lg',
|
||||||
|
to: '/my/lists',
|
||||||
|
}].concat(lists.map((list) => ({
|
||||||
type: 'link' as const,
|
type: 'link' as const,
|
||||||
text: list.name,
|
text: list.name,
|
||||||
|
icon: '',
|
||||||
to: `/timeline/list/${list.id}`,
|
to: `/timeline/list/${list.id}`,
|
||||||
}));
|
})));
|
||||||
os.popupMenu(items, ev.currentTarget ?? ev.target);
|
os.popupMenu(items, ev.currentTarget ?? ev.target);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function chooseAntenna(ev: MouseEvent): Promise<void> {
|
async function chooseAntenna(ev: MouseEvent): Promise<void> {
|
||||||
const antennas = await os.api('antennas/list');
|
const antennas = await os.api('antennas/list');
|
||||||
const items = antennas.map((antenna) => ({
|
const items = [{
|
||||||
|
type: 'link' as const,
|
||||||
|
indicate: false,
|
||||||
|
text: i18n.ts.manageAntennas,
|
||||||
|
icon: 'ph-faders-horizontal-bold ph-lg',
|
||||||
|
to: '/my/antennas',
|
||||||
|
}].concat(antennas.map((antenna) => ({
|
||||||
type: 'link' as const,
|
type: 'link' as const,
|
||||||
text: antenna.name,
|
text: antenna.name,
|
||||||
|
icon: '',
|
||||||
indicate: antenna.hasUnreadNote,
|
indicate: antenna.hasUnreadNote,
|
||||||
to: `/timeline/antenna/${antenna.id}`,
|
to: `/timeline/antenna/${antenna.id}`,
|
||||||
}));
|
})));
|
||||||
os.popupMenu(items, ev.currentTarget ?? ev.target);
|
os.popupMenu(items, ev.currentTarget ?? ev.target);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,8 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { defineAsyncComponent } from 'vue';
|
import { defineAsyncComponent } from 'vue';
|
||||||
import XColumn from './column.vue';
|
import XColumn from './column.vue';
|
||||||
import { updateColumn , Column } from './deck-store';
|
import { updateColumn } from './deck-store';
|
||||||
|
import type { Column } from './deck-store';
|
||||||
import XNotifications from '@/components/MkNotifications.vue';
|
import XNotifications from '@/components/MkNotifications.vue';
|
||||||
import * as os from '@/os';
|
import * as os from '@/os';
|
||||||
import { i18n } from '@/i18n';
|
import { i18n } from '@/i18n';
|
||||||
|
@ -23,7 +24,7 @@ const emit = defineEmits<{
|
||||||
(ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
|
(ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
function func() {
|
function func(): void {
|
||||||
os.popup(defineAsyncComponent(() => import('@/components/MkNotificationSettingWindow.vue')), {
|
os.popup(defineAsyncComponent(() => import('@/components/MkNotificationSettingWindow.vue')), {
|
||||||
includingTypes: props.column.includingTypes,
|
includingTypes: props.column.includingTypes,
|
||||||
}, {
|
}, {
|
||||||
|
|
|
@ -3,7 +3,7 @@ sudo docker rmi $(docker images -q)
|
||||||
sudo docker compose build
|
sudo docker compose build
|
||||||
sudo docker tag thatonecalculator/calckey:latest thatonecalculator/calckey:$(git describe --tags --exact-match)
|
sudo docker tag thatonecalculator/calckey:latest thatonecalculator/calckey:$(git describe --tags --exact-match)
|
||||||
sudo docker images
|
sudo docker images
|
||||||
echo "\nPress any key to continue\n"
|
echo "\nPress enter to continue\n"
|
||||||
read
|
read
|
||||||
sudo docker push thatonecalculator/calckey:$(git describe --tags --exact-match)
|
sudo docker push thatonecalculator/calckey:$(git describe --tags --exact-match)
|
||||||
sudo docker push thatonecalculator/calckey:latest
|
sudo docker push thatonecalculator/calckey:latest
|
||||||
|
|
Loading…
Reference in New Issue