mirror of
https://iceshrimp.dev/limepotato/jormungandr-bite.git
synced 2024-11-26 03:47:40 -07:00
Merge branch 'develop' into account_migration
This commit is contained in:
commit
f6eec87c50
15 changed files with 241 additions and 91 deletions
16
CALCKEY.md
16
CALCKEY.md
|
@ -3,6 +3,9 @@
|
|||
## Planned
|
||||
|
||||
- MFM button
|
||||
- Sonic search support
|
||||
- Use Cassandra for storing notes
|
||||
- Federate with note edits
|
||||
- Classic mode make instance icon bring up new context menu
|
||||
- Exclude self from antenna
|
||||
- Backfill remote users
|
||||
|
@ -19,17 +22,14 @@
|
|||
|
||||
## Work in progress
|
||||
|
||||
- Account migration
|
||||
- Link verification
|
||||
- Better Messaging UI
|
||||
- Better API Documentation
|
||||
- Remote follow button
|
||||
- Admin custom CSS
|
||||
- Add back time machine (jump to date)
|
||||
- Improve accesibility score
|
||||
<details><summary>Current Misskey score is 57/100</summary>
|
||||
|
||||
![accesibility score](https://pool.jortage.com/voringme/misskey/8ff18aae-4dc6-4b08-9e05-a4c9d051a9e3.png)
|
||||
|
||||
</details>
|
||||
- Improve accesibility
|
||||
|
||||
## Implemented
|
||||
|
||||
|
@ -88,8 +88,9 @@
|
|||
- Page drafts
|
||||
- Patron list
|
||||
- Animations respect reduced motion
|
||||
- Obliteration of Ai-chan
|
||||
- Undo renote button inside original note
|
||||
- Custom locales
|
||||
- Obliteration of Ai-chan
|
||||
- MissV: [fix Misskey Forkbomb](https://code.vtopia.live/Vtopia/MissV/commit/40b23c070bd4adbb3188c73546c6c625138fb3c1)
|
||||
- [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)
|
||||
|
@ -131,3 +132,4 @@
|
|||
- 4c5aa9e53887cca5561fcec6ab0754e018f589a5: server: allow to like own pages
|
||||
- 923c93da1228458dd65be47483c198a1a9191bcf: use await for notes.countBy
|
||||
- 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
|
||||
|
||||
- 🐢 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)
|
||||
- 🐘 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
|
||||
|
||||
|
|
|
@ -33,7 +33,6 @@ export async function skippedInstances(hosts: Array<Instace['host']>): Array<Ins
|
|||
})
|
||||
.andWhere(new Brackets(qb => { qb
|
||||
.where('instance.isSuspended')
|
||||
.orWhere('instance.lastCommunicatedAt < :deadTime', { deadTime });
|
||||
}))
|
||||
.select('host')
|
||||
.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_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_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_pull from './endpoints/users/lists/pull.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/lists/create', ep___users_lists_create],
|
||||
['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/pull', ep___users_lists_pull],
|
||||
['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>
|
||||
</button>
|
||||
</header>
|
||||
<transition :name="$store.state.animation ? 'folder-toggle' : ''"
|
||||
<transition
|
||||
:name="$store.state.animation ? 'folder-toggle' : ''"
|
||||
@enter="enter"
|
||||
@after-enter="afterEnter"
|
||||
@leave="leave"
|
||||
|
@ -27,17 +28,18 @@ import tinycolor from 'tinycolor2';
|
|||
|
||||
const localStoragePrefix = 'ui:folder:';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default defineComponent({
|
||||
props: {
|
||||
expanded: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
default: true,
|
||||
},
|
||||
persistKey: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
|
@ -51,7 +53,7 @@ export default defineComponent({
|
|||
if (this.persistKey) {
|
||||
localStorage.setItem(localStoragePrefix + this.persistKey, this.showBody ? 't' : 'f');
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
function getParentBg(el: Element | null): string {
|
||||
|
@ -91,7 +93,7 @@ export default defineComponent({
|
|||
afterLeave(el) {
|
||||
el.style.height = null;
|
||||
},
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@ onMounted(() => {
|
|||
src: media.url,
|
||||
w: media.properties.width,
|
||||
h: media.properties.height,
|
||||
alt: media.name,
|
||||
alt: media.comment,
|
||||
};
|
||||
if (media.properties.orientation != null && media.properties.orientation >= 5) {
|
||||
[item.w, item.h] = [item.h, item.w];
|
||||
|
@ -89,9 +89,38 @@ onMounted(() => {
|
|||
[itemData.w, itemData.h] = [itemData.h, itemData.w];
|
||||
}
|
||||
itemData.msrc = file.thumbnailUrl;
|
||||
itemData.alt = file.comment;
|
||||
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();
|
||||
});
|
||||
|
||||
|
@ -193,4 +222,35 @@ const previewable = (file: misskey.entities.DriveFile): boolean => {
|
|||
//z-index: v-bind(pswpZIndex);
|
||||
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>
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
<template>
|
||||
<XModalWindow
|
||||
ref="dialog"
|
||||
:width="370"
|
||||
:height="400"
|
||||
:width="400"
|
||||
@close="onClose"
|
||||
@closed="emit('closed')"
|
||||
>
|
||||
|
|
|
@ -1,65 +1,67 @@
|
|||
<template>
|
||||
<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 #prefix><i class="ph-key-bold ph-lg"></i></template>
|
||||
</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">
|
||||
<template #label>{{ i18n.ts.username }} <div v-tooltip:dialog="i18n.ts.usernameInfo" class="_button _help"><i class="ph-question-bold"></i></div></template>
|
||||
<template #prefix>@</template>
|
||||
<template #suffix>@{{ host }}</template>
|
||||
<template #caption>
|
||||
<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>
|
||||
<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 === '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 === '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 === '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 === '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 === 'max-range'" style="color: var(--error)"><i class="ph-warning-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts.tooLong }}</span>
|
||||
</template>
|
||||
</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>
|
||||
<div v-if="!instance.disableRegistration || (instance.disableRegistration && invitationState === 'entered')">
|
||||
<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 #label>{{ i18n.ts.username }} <div v-tooltip:dialog="i18n.ts.usernameInfo" class="_button _help"><i class="ph-question-bold"></i></div></template>
|
||||
<template #prefix>@</template>
|
||||
<template #suffix>@{{ host }}</template>
|
||||
<template #caption>
|
||||
<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 === '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 === '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 === '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 === '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 === '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 === 'max-range'" style="color: var(--error)"><i class="ph-warning-bold ph-lg ph-fw ph-lg"></i> {{ i18n.ts.tooLong }}</span>
|
||||
</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>
|
||||
</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:#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>
|
||||
</template>
|
||||
|
||||
|
@ -99,6 +101,7 @@ let retypedPassword: string = $ref('');
|
|||
let invitationCode: string = $ref('');
|
||||
let email = $ref('');
|
||||
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 passwordStrength: '' | 'low' | 'medium' | 'high' = $ref('');
|
||||
let passwordRetypeState: null | 'match' | 'not-match' = $ref(null);
|
||||
|
@ -115,6 +118,14 @@ const shouldDisableSubmitting = $computed((): boolean => {
|
|||
passwordRetypeState === 'not-match';
|
||||
});
|
||||
|
||||
function onChangeInvitationCode(): void {
|
||||
if (invitationCode === '') {
|
||||
invitationState = null;
|
||||
return;
|
||||
}
|
||||
invitationState = 'entered';
|
||||
}
|
||||
|
||||
function onChangeUsername(): void {
|
||||
if (username === '') {
|
||||
usernameState = null;
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
<template>
|
||||
<XModalWindow
|
||||
ref="dialog"
|
||||
:width="366"
|
||||
:height="500"
|
||||
@close="dialog.close()"
|
||||
:width="400"
|
||||
@close="dialog!.close()"
|
||||
@closed="$emit('closed')"
|
||||
>
|
||||
<template #header>{{ i18n.ts.signup }}</template>
|
||||
|
||||
<div class="_monolithic_">
|
||||
<div class="_section">
|
||||
<XSignup :auto-set="autoSet" @signup="onSignup" @signupEmailPending="onSignupEmailPending"/>
|
||||
<XSignup :auto-set="autoSet" @signup="onSignup" @signup-email-pending="onSignupEmailPending"/>
|
||||
</div>
|
||||
</div>
|
||||
</XModalWindow>
|
||||
|
@ -37,10 +36,10 @@ const dialog = $ref<InstanceType<typeof XModalWindow>>();
|
|||
|
||||
function onSignup(res) {
|
||||
emit('done', res);
|
||||
dialog.close();
|
||||
dialog?.close();
|
||||
}
|
||||
|
||||
function onSignupEmailPending() {
|
||||
dialog.close();
|
||||
dialog?.close();
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -3,7 +3,10 @@
|
|||
<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
|
||||
<MkSpacer :content-max="700">
|
||||
<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">
|
||||
<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();
|
||||
}
|
||||
|
||||
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 headerTabs = $computed(() => []);
|
||||
|
@ -57,8 +71,13 @@ definePageMetadata({
|
|||
|
||||
<style lang="scss" scoped>
|
||||
.qkcjvfiv {
|
||||
> .add {
|
||||
margin: 0 auto var(--margin) auto;
|
||||
> .buttonWrapper {
|
||||
display: grid;
|
||||
justify-content: center;
|
||||
|
||||
> .add {
|
||||
margin: 0 auto var(--margin) auto;
|
||||
}
|
||||
}
|
||||
|
||||
> .lists {
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
<h1>{{ page.title }}</h1>
|
||||
</div>
|
||||
<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>
|
||||
<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>
|
||||
|
@ -55,7 +56,7 @@
|
|||
</div> -->
|
||||
</div>
|
||||
<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>
|
||||
<MkPagination v-slot="{items}" :pagination="otherPostsPagination">
|
||||
<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 MkPagePreview from '@/components/MkPagePreview.vue';
|
||||
import { i18n } from '@/i18n';
|
||||
import copyToClipboard from '@/scripts/copy-to-clipboard';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata';
|
||||
import { shareAvailable } from '@/scripts/share-available';
|
||||
|
||||
|
@ -113,6 +115,11 @@ function fetchPage() {
|
|||
});
|
||||
}
|
||||
|
||||
function copyUrl() {
|
||||
copyToClipboard(window.location.href);
|
||||
os.success();
|
||||
}
|
||||
|
||||
function getBgImg(): string {
|
||||
if (page.eyeCatchingImage != null) {
|
||||
return `url(${page.eyeCatchingImage.url})`;
|
||||
|
|
|
@ -144,22 +144,35 @@ function top(): void {
|
|||
|
||||
async function chooseList(ev: MouseEvent): Promise<void> {
|
||||
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,
|
||||
text: list.name,
|
||||
icon: '',
|
||||
to: `/timeline/list/${list.id}`,
|
||||
}));
|
||||
})));
|
||||
os.popupMenu(items, ev.currentTarget ?? ev.target);
|
||||
}
|
||||
|
||||
async function chooseAntenna(ev: MouseEvent): Promise<void> {
|
||||
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,
|
||||
text: antenna.name,
|
||||
icon: '',
|
||||
indicate: antenna.hasUnreadNote,
|
||||
to: `/timeline/antenna/${antenna.id}`,
|
||||
}));
|
||||
})));
|
||||
os.popupMenu(items, ev.currentTarget ?? ev.target);
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,8 @@
|
|||
<script lang="ts" setup>
|
||||
import { defineAsyncComponent } from '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 * as os from '@/os';
|
||||
import { i18n } from '@/i18n';
|
||||
|
@ -23,7 +24,7 @@ const emit = defineEmits<{
|
|||
(ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
|
||||
}>();
|
||||
|
||||
function func() {
|
||||
function func(): void {
|
||||
os.popup(defineAsyncComponent(() => import('@/components/MkNotificationSettingWindow.vue')), {
|
||||
includingTypes: props.column.includingTypes,
|
||||
}, {
|
||||
|
|
|
@ -3,7 +3,7 @@ sudo docker rmi $(docker images -q)
|
|||
sudo docker compose build
|
||||
sudo docker tag thatonecalculator/calckey:latest thatonecalculator/calckey:$(git describe --tags --exact-match)
|
||||
sudo docker images
|
||||
echo "\nPress any key to continue\n"
|
||||
echo "\nPress enter to continue\n"
|
||||
read
|
||||
sudo docker push thatonecalculator/calckey:$(git describe --tags --exact-match)
|
||||
sudo docker push thatonecalculator/calckey:latest
|
||||
|
|
Loading…
Reference in a new issue