Merge branch 'develop' into mkjs-n

This commit is contained in:
tamaina 2023-05-25 14:49:51 +00:00
commit d7fe783c46
27 changed files with 3254 additions and 1002 deletions

72
locales/generateDTS.js Normal file
View file

@ -0,0 +1,72 @@
const fs = require('fs');
const yaml = require('js-yaml');
const ts = require('typescript');
function createMembers(record) {
return Object.entries(record)
.map(([k, v]) => ts.factory.createPropertySignature(
undefined,
ts.factory.createStringLiteral(k),
undefined,
typeof v === 'string'
? ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword)
: ts.factory.createTypeLiteralNode(createMembers(v)),
));
}
module.exports = function generateDTS() {
const locale = yaml.load(fs.readFileSync(`${__dirname}/ja-JP.yml`, 'utf-8'));
const members = createMembers(locale);
const elements = [
ts.factory.createInterfaceDeclaration(
[ts.factory.createToken(ts.SyntaxKind.ExportKeyword)],
ts.factory.createIdentifier('Locale'),
undefined,
undefined,
members,
),
ts.factory.createVariableStatement(
[ts.factory.createToken(ts.SyntaxKind.DeclareKeyword)],
ts.factory.createVariableDeclarationList(
[ts.factory.createVariableDeclaration(
ts.factory.createIdentifier('locales'),
undefined,
ts.factory.createTypeLiteralNode([ts.factory.createIndexSignature(
undefined,
[ts.factory.createParameterDeclaration(
undefined,
undefined,
ts.factory.createIdentifier('lang'),
undefined,
ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
undefined,
)],
ts.factory.createTypeReferenceNode(
ts.factory.createIdentifier('Locale'),
undefined,
),
)]),
undefined,
)],
ts.NodeFlags.Const | ts.NodeFlags.Ambient | ts.NodeFlags.ContextFlags,
),
),
ts.factory.createExportAssignment(
undefined,
true,
ts.factory.createIdentifier('locales'),
),
];
const printed = ts.createPrinter({
newLine: ts.NewLineKind.LineFeed,
}).printList(
ts.ListFormat.MultiLine,
ts.factory.createNodeArray(elements),
ts.createSourceFile('index.d.ts', '', ts.ScriptTarget.ESNext, true, ts.ScriptKind.TS),
);
fs.writeFileSync(`${__dirname}/index.d.ts`, `/* eslint-disable */
// This file is generated by locales/generateDTS.js
// Do not edit this file directly.
${printed}`, 'utf-8');
}

2145
locales/index.d.ts vendored

File diff suppressed because it is too large Load diff

View file

@ -59,7 +59,7 @@
"@typescript-eslint/eslint-plugin": "5.59.5",
"@typescript-eslint/parser": "5.59.5",
"cross-env": "7.0.3",
"cypress": "12.12.0",
"cypress": "12.13.0",
"eslint": "8.40.0",
"start-server-and-test": "2.0.0"
},

View file

@ -63,9 +63,9 @@
"@fastify/multipart": "7.6.0",
"@fastify/static": "6.10.1",
"@fastify/view": "7.4.1",
"@nestjs/common": "9.4.1",
"@nestjs/core": "9.4.1",
"@nestjs/testing": "9.4.1",
"@nestjs/common": "9.4.2",
"@nestjs/core": "9.4.2",
"@nestjs/testing": "9.4.2",
"@peertube/http-signature": "1.7.0",
"@sinonjs/fake-timers": "10.0.2",
"@swc/cli": "0.1.62",
@ -179,11 +179,11 @@
"@types/jsonld": "1.5.8",
"@types/jsrsasign": "10.5.8",
"@types/mime-types": "2.1.1",
"@types/node": "20.2.1",
"@types/node": "20.2.3",
"@types/node-fetch": "3.0.3",
"@types/nodemailer": "6.4.8",
"@types/oauth": "0.9.1",
"@types/pg": "8.6.6",
"@types/pg": "8.10.1",
"@types/pug": "2.0.6",
"@types/punycode": "2.1.0",
"@types/qrcode": "1.5.0",
@ -197,7 +197,7 @@
"@types/sinonjs__fake-timers": "8.1.2",
"@types/tinycolor2": "1.4.3",
"@types/tmp": "0.2.3",
"@types/unzipper": "0.10.5",
"@types/unzipper": "0.10.6",
"@types/uuid": "9.0.1",
"@types/vary": "1.1.0",
"@types/web-push": "3.3.2",

View file

@ -4,7 +4,7 @@ import * as Redis from 'ioredis';
import { DataSource } from 'typeorm';
import { MeiliSearch } from 'meilisearch';
import { DI } from './di-symbols.js';
import { loadConfig } from './config.js';
import { Config, loadConfig } from './config.js';
import { createPostgresDataSource } from './postgres.js';
import { RepositoryModule } from './models/RepositoryModule.js';
import type { Provider, OnApplicationShutdown } from '@nestjs/common';
@ -25,7 +25,7 @@ const $db: Provider = {
const $meilisearch: Provider = {
provide: DI.meilisearch,
useFactory: (config) => {
useFactory: (config: Config) => {
if (config.meilisearch) {
return new MeiliSearch({
host: `${config.meilisearch.ssl ? 'https' : 'http' }://${config.meilisearch.host}:${config.meilisearch.port}`,
@ -40,7 +40,7 @@ const $meilisearch: Provider = {
const $redis: Provider = {
provide: DI.redis,
useFactory: (config) => {
useFactory: (config: Config) => {
return new Redis.Redis({
port: config.redis.port,
host: config.redis.host,
@ -55,7 +55,7 @@ const $redis: Provider = {
const $redisForPub: Provider = {
provide: DI.redisForPub,
useFactory: (config) => {
useFactory: (config: Config) => {
const redis = new Redis.Redis({
port: config.redisForPubsub.port,
host: config.redisForPubsub.host,
@ -71,7 +71,7 @@ const $redisForPub: Provider = {
const $redisForSub: Provider = {
provide: DI.redisForSub,
useFactory: (config) => {
useFactory: (config: Config) => {
const redis = new Redis.Redis({
port: config.redisForPubsub.port,
host: config.redisForPubsub.host,

View file

@ -160,14 +160,16 @@
<path d="M12 9v2m0 4v.01"></path>
<path d="M5 19h14a2 2 0 0 0 1.84 -2.75l-7.1 -12.25a2 2 0 0 0 -3.5 0l-7.1 12.25a2 2 0 0 0 1.75 2.75"></path>
</svg>
<h1>An error has occurred!</h1>
<button class="button-big" onclick="location.reload();">
<span class="button-label-big">Refresh</span>
<h1>Failed to load<br>読み込みに失敗しました</h1>
<button class="button-big" onclick="location.reload(true);">
<span class="button-label-big">Reload / リロード</span>
</button>
<p class="dont-worry">Don't worry, it's (probably) not your fault.</p>
<p>If the problem persists after refreshing, please contact your instance's administrator.<br>You may also try the following options:</p>
<p>Update your os and browser.</p>
<p>Disable an adblocker.</p>
<p><b>The following actions may solve the problem. / 以下を行うと解決する可能性があります</b></p>
<p>Clear the browser cache / ブラウザのキャッシュをクリアする</p>
<p>Update your os and browser / ブラウザおよびOSを最新バージョンに更新する</p>
<p>Disable an adblocker / アドブロッカーを無効にする</p>
<details style="color: #86b300;">
<summary>Other options / その他のオプション</summary>
<a href="/flush">
<button class="button-small">
<span class="button-label-small">Clear preferences and cache</span>
@ -185,12 +187,14 @@
<span class="button-label-small">Start the repair tool</span>
</button>
</a>
</details>
<br>
<div id="errors"></div>
`;
errorsElement = document.getElementById('errors');
}
const detailsElement = document.createElement('details');
detailsElement.id = 'errorInfo';
detailsElement.innerHTML = `
<br>
<summary>
@ -247,7 +251,7 @@
.button-label-big {
color: #222;
font-weight: bold;
font-size: 20px;
font-size: 1.2em;
padding: 12px;
}
@ -267,11 +271,6 @@
font-size: 16px;
}
.dont-worry,
#msg {
font-size: 18px;
}
.icon-warning {
color: #dec340;
height: 4rem;
@ -279,14 +278,15 @@
}
h1 {
font-size: 32px;
font-size: 1.5em;
margin: 1em;
}
code {
font-family: Fira, FiraCode, monospace;
}
details {
#errorInfo {
background: #333;
margin-bottom: 2rem;
padding: 0.5rem 1rem;
@ -296,16 +296,16 @@
margin: auto;
}
summary {
#errorInfo summary {
cursor: pointer;
}
summary > * {
#errorInfo summary > * {
display: inline;
}
@media screen and (max-width: 500px) {
details {
#errorInfo {
width: 50%;
}
`)

View file

@ -22,7 +22,7 @@
"@syuilo/aiscript": "0.13.3",
"@tabler/icons-webfont": "2.17.0",
"@vitejs/plugin-vue": "4.2.3",
"@vue-macros/reactivity-transform": "0.3.7",
"@vue-macros/reactivity-transform": "0.3.8",
"@vue/compiler-sfc": "3.3.4",
"autosize": "6.0.1",
"broadcast-channel": "4.20.2",
@ -53,7 +53,7 @@
"punycode": "2.3.0",
"querystring": "0.2.1",
"rndstr": "1.0.0",
"rollup": "3.22.0",
"rollup": "3.23.0",
"s-age": "1.1.2",
"sanitize-html": "2.10.0",
"sass": "1.62.1",
@ -77,37 +77,37 @@
"vuedraggable": "next"
},
"devDependencies": {
"@storybook/addon-actions": "7.0.12",
"@storybook/addon-essentials": "7.0.12",
"@storybook/addon-interactions": "7.0.12",
"@storybook/addon-links": "7.0.12",
"@storybook/addon-storysource": "7.0.12",
"@storybook/addons": "7.0.12",
"@storybook/blocks": "7.0.12",
"@storybook/core-events": "7.0.12",
"@storybook/addon-actions": "7.0.15",
"@storybook/addon-essentials": "7.0.15",
"@storybook/addon-interactions": "7.0.15",
"@storybook/addon-links": "7.0.15",
"@storybook/addon-storysource": "7.0.15",
"@storybook/addons": "7.0.15",
"@storybook/blocks": "7.0.15",
"@storybook/core-events": "7.0.15",
"@storybook/jest": "0.1.0",
"@storybook/manager-api": "7.0.12",
"@storybook/preview-api": "7.0.12",
"@storybook/react": "7.0.12",
"@storybook/react-vite": "7.0.12",
"@storybook/manager-api": "7.0.15",
"@storybook/preview-api": "7.0.15",
"@storybook/react": "7.0.15",
"@storybook/react-vite": "7.0.15",
"@storybook/testing-library": "0.1.0",
"@storybook/theming": "7.0.12",
"@storybook/types": "7.0.12",
"@storybook/vue3": "7.0.12",
"@storybook/vue3-vite": "7.0.12",
"@storybook/theming": "7.0.15",
"@storybook/types": "7.0.15",
"@storybook/vue3": "7.0.15",
"@storybook/vue3-vite": "7.0.15",
"@testing-library/jest-dom": "5.16.5",
"@testing-library/vue": "7.0.0",
"@types/escape-regexp": "0.0.1",
"@types/estree": "1.0.1",
"@types/gulp": "4.0.10",
"@types/gulp-rename": "2.0.2",
"@types/matter-js": "0.18.3",
"@types/matter-js": "0.18.4",
"@types/micromatch": "4.0.2",
"@types/node": "20.2.1",
"@types/node": "20.2.3",
"@types/punycode": "2.1.0",
"@types/sanitize-html": "2.9.0",
"@types/seedrandom": "3.0.5",
"@types/testing-library__jest-dom": "^5.14.5",
"@types/testing-library__jest-dom": "^5.14.6",
"@types/throttle-debounce": "5.0.0",
"@types/tinycolor2": "1.4.3",
"@types/uuid": "9.0.1",
@ -117,13 +117,13 @@
"@typescript-eslint/parser": "5.59.5",
"@vitest/coverage-c8": "0.31.1",
"@vue/runtime-core": "3.3.4",
"astring": "1.8.4",
"astring": "1.8.5",
"chokidar-cli": "3.0.0",
"cross-env": "7.0.3",
"cypress": "12.12.0",
"cypress": "12.13.0",
"eslint": "8.40.0",
"eslint-plugin-import": "2.27.5",
"eslint-plugin-vue": "9.13.0",
"eslint-plugin-vue": "9.14.0",
"fast-glob": "3.2.12",
"happy-dom": "9.19.2",
"micromatch": "3.1.10",
@ -133,7 +133,7 @@
"react": "18.2.0",
"react-dom": "18.2.0",
"start-server-and-test": "2.0.0",
"storybook": "7.0.12",
"storybook": "7.0.15",
"storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme",
"summaly": "github:misskey-dev/summaly",
"vite-plugin-turbosnap": "1.0.2",

View file

@ -5,7 +5,9 @@ import '@/style.scss';
import { mainBoot } from './boot/main-boot';
import { subBoot } from './boot/sub-boot';
if (['/share', '/auth', '/miauth'].includes(location.pathname)) {
const subBootPaths = ['/share', '/auth', '/miauth', '/signup-complete'];
if (subBootPaths.some(i => location.pathname === i || location.pathname.startsWith(i + '/'))) {
subBoot();
} else {
mainBoot();

View file

@ -8,8 +8,9 @@
>
<template #header>{{ i18n.ts.forgotPassword }}</template>
<form v-if="instance.enableEmail" class="bafeceda" @submit.prevent="onSubmit">
<div class="main _gaps_m">
<MkSpacer :marginMin="20" :marginMax="28">
<form v-if="instance.enableEmail" @submit.prevent="onSubmit">
<div class="_gaps_m">
<MkInput v-model="username" type="text" pattern="^[a-zA-Z0-9_]+$" :spellcheck="false" autofocus required>
<template #label>{{ i18n.ts.username }}</template>
<template #prefix>@</template>
@ -20,15 +21,15 @@
<template #caption>{{ i18n.ts._forgotPassword.enterEmail }}</template>
</MkInput>
<MkButton type="submit" :disabled="processing" primary style="margin: 0 auto;">{{ i18n.ts.send }}</MkButton>
</div>
<div class="sub">
<MkA to="/about" class="_link">{{ i18n.ts._forgotPassword.ifNoEmail }}</MkA>
<MkButton type="submit" rounded :disabled="processing" primary style="margin: 0 auto;">{{ i18n.ts.send }}</MkButton>
<MkInfo>{{ i18n.ts._forgotPassword.ifNoEmail }}</MkInfo>
</div>
</form>
<div v-else class="bafecedb">
<div v-else>
{{ i18n.ts._forgotPassword.contactAdmin }}
</div>
</MkSpacer>
</MkModalWindow>
</template>
@ -37,6 +38,7 @@ import { } from 'vue';
import MkModalWindow from '@/components/MkModalWindow.vue';
import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/MkInput.vue';
import MkInfo from '@/components/MkInfo.vue';
import * as os from '@/os';
import { instance } from '@/instance';
import { i18n } from '@/i18n';
@ -62,20 +64,3 @@ async function onSubmit() {
dialog.close();
}
</script>
<style lang="scss" scoped>
.bafeceda {
> .main {
padding: 24px;
}
> .sub {
border-top: solid 0.5px var(--divider);
padding: 24px;
}
}
.bafecedb {
padding: 24px;
}
</style>

View file

@ -1,16 +1,16 @@
<template>
<div v-show="props.modelValue.length != 0" class="skeikyzd">
<Sortable :modelValue="props.modelValue" class="files" itemKey="id" :animation="150" :delay="100" :delayOnTouchOnly="true" @update:modelValue="v => emit('update:modelValue', v)">
<div v-show="props.modelValue.length != 0" :class="$style.root">
<Sortable :modelValue="props.modelValue" :class="$style.files" itemKey="id" :animation="150" :delay="100" :delayOnTouchOnly="true" @update:modelValue="v => emit('update:modelValue', v)">
<template #item="{element}">
<div class="file" @click="showFileMenu(element, $event)" @contextmenu.prevent="showFileMenu(element, $event)">
<MkDriveFileThumbnail :data-id="element.id" class="thumbnail" :file="element" fit="cover"/>
<div v-if="element.isSensitive" class="sensitive">
<i class="ti ti-alert-triangle icon"></i>
<div :class="$style.file" @click="showFileMenu(element, $event)" @contextmenu.prevent="showFileMenu(element, $event)">
<MkDriveFileThumbnail :data-id="element.id" :class="$style.thumbnail" :file="element" fit="cover"/>
<div v-if="element.isSensitive" :class="$style.sensitive">
<i class="ti ti-alert-triangle" style="margin: auto;"></i>
</div>
</div>
</template>
</Sortable>
<p class="remain">{{ 16 - props.modelValue.length }}/16</p>
<p :class="$style.remain">{{ 16 - props.modelValue.length }}/16</p>
</div>
</template>
@ -108,16 +108,18 @@ function showFileMenu(file, ev: MouseEvent) {
}
</script>
<style lang="scss" scoped>
.skeikyzd {
<style lang="scss" module>
.root {
padding: 8px 16px;
position: relative;
}
> .files {
.files {
display: flex;
flex-wrap: wrap;
}
> .file {
.file {
position: relative;
width: 64px;
height: 64px;
@ -125,19 +127,16 @@ function showFileMenu(file, ev: MouseEvent) {
border-radius: 4px;
overflow: hidden;
cursor: move;
&:hover > .remove {
display: block;
}
> .thumbnail {
.thumbnail {
width: 100%;
height: 100%;
z-index: 1;
color: var(--fg);
}
> .sensitive {
.sensitive {
display: flex;
position: absolute;
width: 64px;
@ -147,21 +146,15 @@ function showFileMenu(file, ev: MouseEvent) {
z-index: 2;
background: rgba(17, 17, 17, .7);
color: #fff;
> .icon {
margin: auto;
}
}
}
}
> .remain {
.remain {
display: block;
position: absolute;
top: 8px;
right: 8px;
margin: 0;
padding: 0;
}
font-size: 90%;
}
</style>

View file

@ -1,7 +1,7 @@
<template>
<div ref="rootEl" class="meijqfqm">
<canvas :id="idForCanvas" ref="canvasEl" class="canvas" :width="width" height="300" @contextmenu.prevent="() => {}"></canvas>
<div :id="idForTags" ref="tagsEl" class="tags">
<div ref="rootEl" :class="$style.root">
<canvas :id="idForCanvas" ref="canvasEl" style="display: block;" :width="width" height="300" @contextmenu.prevent="() => {}"></canvas>
<div :id="idForTags" ref="tagsEl" :class="$style.tags">
<ul>
<slot></slot>
</ul>
@ -70,21 +70,17 @@ defineExpose({
});
</script>
<style lang="scss" scoped>
.meijqfqm {
<style lang="scss" module>
.root {
position: relative;
overflow: clip;
display: grid;
place-items: center;
> .canvas {
display: block;
}
> .tags {
.tags {
position: absolute;
top: 999px;
left: 999px;
}
}
</style>

View file

@ -46,12 +46,6 @@ const onUserRemoved = () => {
tlComponent.pagingComponent?.reload();
};
const onChangeFollowing = () => {
if (!tlComponent.pagingComponent?.backed) {
tlComponent.pagingComponent?.reload();
}
};
let endpoint;
let query;
let connection;
@ -79,8 +73,6 @@ if (props.src === 'antenna') {
connection.on('note', prepend);
connection2 = stream.useChannel('main');
connection2.on('follow', onChangeFollowing);
connection2.on('unfollow', onChangeFollowing);
} else if (props.src === 'local') {
endpoint = 'notes/local-timeline';
query = {

View file

@ -1,10 +1,10 @@
<template>
<div class="adhpbeou">
<div class="label" @click="focus"><slot name="label"></slot></div>
<div class="content">
<div>
<div :class="$style.label" @click="focus"><slot name="label"></slot></div>
<div :class="$style.content">
<slot></slot>
</div>
<div class="caption"><slot name="caption"></slot></div>
<div :class="$style.caption"><slot name="caption"></slot></div>
</div>
</template>
@ -16,9 +16,8 @@ function focus() {
}
</script>
<style lang="scss" scoped>
.adhpbeou {
> .label {
<style lang="scss" module>
.label {
font-size: 0.85em;
padding: 0 0 8px 0;
user-select: none;
@ -28,7 +27,7 @@ function focus() {
}
}
> .caption {
.caption {
font-size: 0.85em;
padding: 8px 0 0 0;
color: var(--fgTransparentWeak);
@ -37,5 +36,4 @@ function focus() {
display: none;
}
}
}
</style>

View file

@ -1,8 +1,9 @@
import { markRaw } from 'vue';
import type { Locale } from '../../../locales';
import { locale } from '@/config';
import { I18n } from '@/scripts/i18n';
export const i18n = markRaw(new I18n(locale));
export const i18n = markRaw(new I18n<Locale>(locale));
export function updateI18n(newLocale) {
i18n.ts = newLocale;

View file

@ -2,22 +2,28 @@
<MkStickyContainer>
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
<MkSpacer :contentMax="700">
<div v-if="tab === 'featured'" class="rknalgpo">
<div v-if="tab === 'featured'">
<MkPagination v-slot="{items}" :pagination="featuredPagesPagination">
<MkPagePreview v-for="page in items" :key="page.id" class="ckltabjg" :page="page"/>
<div class="_gaps">
<MkPagePreview v-for="page in items" :key="page.id" :page="page"/>
</div>
</MkPagination>
</div>
<div v-else-if="tab === 'my'" class="rknalgpo my">
<div v-else-if="tab === 'my'" class="_gaps">
<MkButton class="new" @click="create()"><i class="ti ti-plus"></i></MkButton>
<MkPagination v-slot="{items}" :pagination="myPagesPagination">
<MkPagePreview v-for="page in items" :key="page.id" class="ckltabjg" :page="page"/>
<div class="_gaps">
<MkPagePreview v-for="page in items" :key="page.id" :page="page"/>
</div>
</MkPagination>
</div>
<div v-else-if="tab === 'liked'" class="rknalgpo">
<div v-else-if="tab === 'liked'">
<MkPagination v-slot="{items}" :pagination="likedPagesPagination">
<MkPagePreview v-for="like in items" :key="like.page.id" class="ckltabjg" :page="like.page"/>
<div class="_gaps">
<MkPagePreview v-for="like in items" :key="like.page.id" :page="like.page"/>
</div>
</MkPagination>
</div>
</MkSpacer>
@ -79,21 +85,3 @@ definePageMetadata(computed(() => ({
icon: 'ti ti-note',
})));
</script>
<style lang="scss" scoped>
.rknalgpo {
&.my .ckltabjg:first-child {
margin-top: 16px;
}
.ckltabjg:not(:last-child) {
margin-bottom: 8px;
}
@media (min-width: 500px) {
.ckltabjg:not(:last-child) {
margin-bottom: 16px;
}
}
}
</style>

View file

@ -9,11 +9,11 @@
</template>
<template #default="{items}">
<div class="_gaps">
<div v-for="token in items" :key="token.id" class="_panel bfomjevm">
<img v-if="token.iconUrl" class="icon" :src="token.iconUrl" alt=""/>
<div class="body">
<div class="name">{{ token.name }}</div>
<div class="description">{{ token.description }}</div>
<div v-for="token in items" :key="token.id" class="_panel" :class="$style.app">
<img v-if="token.iconUrl" :class="$style.appIcon" :src="token.iconUrl" alt=""/>
<div :class="$style.appBody">
<div :class="$style.appName">{{ token.name }}</div>
<div>{{ token.description }}</div>
<MkKeyValue oneline>
<template #key>{{ i18n.ts.installedDate }}</template>
<template #value><MkTime :time="token.createdAt"/></template>
@ -28,7 +28,7 @@
<li v-for="p in token.permission" :key="p">{{ i18n.t(`_permissions.${p}`) }}</li>
</ul>
</details>
<div class="actions">
<div>
<MkButton inline danger @click="revoke(token)"><i class="ti ti-trash"></i></MkButton>
</div>
</div>
@ -75,12 +75,13 @@ definePageMetadata({
});
</script>
<style lang="scss" scoped>
.bfomjevm {
<style lang="scss" module>
.app {
display: flex;
padding: 16px;
}
> .icon {
.appIcon {
display: block;
flex-shrink: 0;
margin: 0 12px 0 0;
@ -89,13 +90,12 @@ definePageMetadata({
border-radius: 8px;
}
> .body {
.appBody {
width: calc(100% - 62px);
position: relative;
}
> .name {
.appName {
font-weight: bold;
}
}
}
</style>

View file

@ -1,37 +1,83 @@
<template>
<div :class="$style.root">
<MkAnimBg style="position: fixed; top: 0;"/>
<div :class="$style.formContainer">
<form :class="$style.form" class="_panel" @submit.prevent="submit()">
<div :class="$style.banner">
<i class="ti ti-user-check"></i>
</div>
<div class="_gaps_m" style="padding: 32px;">
<div>{{ i18n.t('clickToFinishEmailVerification', { ok: i18n.ts.gotIt }) }}</div>
<div>
{{ i18n.ts.processing }}
<MkButton gradate large rounded type="submit" :disabled="submitting" data-cy-admin-ok style="margin: 0 auto;">
{{ submitting ? i18n.ts.processing : i18n.ts.gotIt }}<MkEllipsis v-if="submitting"/>
</MkButton>
</div>
</div>
</form>
</div>
</div>
</template>
<script lang="ts" setup>
import { onMounted } from 'vue';
import * as os from '@/os';
import { } from 'vue';
import MkButton from '@/components/MkButton.vue';
import MkAnimBg from '@/components/MkAnimBg.vue';
import { login } from '@/account';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
import * as os from '@/os';
let submitting = $ref(false);
const props = defineProps<{
code: string;
}>();
onMounted(async () => {
await os.alert({
type: 'info',
text: i18n.t('clickToFinishEmailVerification', { ok: i18n.ts.gotIt }),
});
const res = await os.apiWithDialog('signup-pending', {
function submit() {
if (submitting) return;
submitting = true;
os.api('signup-pending', {
code: props.code,
});
login(res.i, '/');
});
}).then(res => {
return login(res.i, '/');
}).catch(() => {
submitting = false;
const headerActions = $computed(() => []);
const headerTabs = $computed(() => []);
definePageMetadata({
title: i18n.ts.signup,
icon: 'ti ti-user',
os.alert({
type: 'error',
text: i18n.ts.somethingHappened,
});
});
}
</script>
<style lang="scss" module>
.root {
}
.formContainer {
min-height: 100svh;
padding: 32px 32px 64px 32px;
box-sizing: border-box;
display: grid;
place-content: center;
}
.form {
position: relative;
z-index: 10;
border-radius: var(--radius);
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
overflow: clip;
max-width: 500px;
}
.banner {
padding: 16px;
text-align: center;
font-size: 26px;
background-color: var(--accentedBg);
color: var(--accent);
}
</style>

View file

@ -34,7 +34,3 @@ const props = defineProps<{
}>();
</script>
<style lang="scss" scoped>
</style>

View file

@ -56,6 +56,3 @@ definePageMetadata(computed(() => user ? {
avatar: user,
} : null));
</script>
<style lang="scss" scoped>
</style>

View file

@ -56,6 +56,3 @@ definePageMetadata(computed(() => user ? {
avatar: user,
} : null));
</script>
<style lang="scss" scoped>
</style>

View file

@ -2,7 +2,6 @@
<MkStickyContainer>
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
<div>
<Transition name="fade" mode="out-in">
<div v-if="user">
<XHome v-if="tab === 'home'" :user="user"/>
<XTimeline v-else-if="tab === 'notes'" :user="user"/>
@ -16,7 +15,6 @@
</div>
<MkError v-else-if="error" @retry="fetchUser()"/>
<MkLoading v-else/>
</Transition>
</div>
</MkStickyContainer>
</template>
@ -118,14 +116,3 @@ definePageMetadata(computed(() => user ? {
},
} : null));
</script>
<style lang="scss" scoped>
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.125s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>

View file

@ -1,9 +1,9 @@
<template>
<MkContainer :naked="widgetProps.transparent" :showHeader="false" data-cy-mkw-clock class="mkw-clock">
<div class="vubelbmv" :class="widgetProps.size">
<div v-if="widgetProps.label === 'tz' || widgetProps.label === 'timeAndTz'" class="_monospace label a abbrev">{{ tzAbbrev }}</div>
<MkContainer :naked="widgetProps.transparent" :showHeader="false" data-cy-mkw-clock>
<div :class="[$style.root, $style[widgetProps.size]]">
<div v-if="widgetProps.label === 'tz' || widgetProps.label === 'timeAndTz'" class="_monospace" :class="[$style.label, $style.a]">{{ tzAbbrev }}</div>
<MkAnalogClock
class="clock"
:class="$style.clock"
:thickness="widgetProps.thickness"
:offset="tzOffset"
:graduations="widgetProps.graduations"
@ -11,8 +11,8 @@
:twentyfour="widgetProps.twentyFour"
:sAnimation="widgetProps.sAnimation"
/>
<MkDigitalClock v-if="widgetProps.label === 'time' || widgetProps.label === 'timeAndTz'" class="_monospace label c time" :showS="false" :offset="tzOffset"/>
<div v-if="widgetProps.label === 'tz' || widgetProps.label === 'timeAndTz'" class="_monospace label d offset">{{ tzOffsetLabel }}</div>
<MkDigitalClock v-if="widgetProps.label === 'time' || widgetProps.label === 'timeAndTz'" :class="[$style.label, $style.c]" class="_monospace" :showS="false" :offset="tzOffset"/>
<div v-if="widgetProps.label === 'tz' || widgetProps.label === 'timeAndTz'" class="_monospace" :class="[$style.label, $style.d]">{{ tzOffsetLabel }}</div>
</div>
</MkContainer>
</template>
@ -140,39 +140,10 @@ defineExpose<WidgetComponentExpose>({
});
</script>
<style lang="scss" scoped>
.vubelbmv {
<style lang="scss" module>
.root {
position: relative;
> .label {
position: absolute;
opacity: 0.7;
&.a {
top: 14px;
left: 14px;
}
&.b {
top: 14px;
right: 14px;
}
&.c {
bottom: 14px;
left: 14px;
}
&.d {
bottom: 14px;
right: 14px;
}
}
> .clock {
margin: auto;
}
&.small {
padding: 12px;
@ -197,4 +168,33 @@ defineExpose<WidgetComponentExpose>({
}
}
}
.label {
position: absolute;
opacity: 0.7;
&.a {
top: 14px;
left: 14px;
}
&.b {
top: 14px;
right: 14px;
}
&.c {
bottom: 14px;
left: 14px;
}
&.d {
bottom: 14px;
right: 14px;
}
}
.clock {
margin: auto;
}
</style>

View file

@ -72,7 +72,3 @@ defineExpose<WidgetComponentExpose>({
id: props.widget ? props.widget.id : null,
});
</script>
<style lang="scss" scoped>
</style>

View file

@ -1,8 +1,10 @@
<template>
<div data-cy-mkw-onlineUsers class="mkw-onlineUsers" :class="{ _panel: !widgetProps.transparent, pad: !widgetProps.transparent }">
<I18n v-if="onlineUsersCount" :src="i18n.ts.onlineUsersCount" textTag="span" class="text">
<template #n><b>{{ number(onlineUsersCount) }}</b></template>
<div data-cy-mkw-onlineUsers :class="[$style.root, { _panel: !widgetProps.transparent, [$style.pad]: !widgetProps.transparent }]">
<span :class="$style.text">
<I18n v-if="onlineUsersCount" :src="i18n.ts.onlineUsersCount" textTag="span">
<template #n><b style="color: #41b781;">{{ number(onlineUsersCount) }}</b></template>
</I18n>
</span>
</div>
</template>
@ -55,22 +57,16 @@ defineExpose<WidgetComponentExpose>({
});
</script>
<style lang="scss" scoped>
.mkw-onlineUsers {
<style lang="scss" module>
.root {
text-align: center;
&.pad {
padding: 16px 0;
}
> .text {
::v-deep(b) {
color: #41b781;
}
::v-deep(span) {
opacity: 0.7;
}
}
.text {
color: var(--fgTransparentWeak);
}
</style>

View file

@ -1,11 +1,12 @@
<template>
<svg class="hsalcinq" viewBox="0 0 1 1" preserveAspectRatio="none">
<svg :class="$style.root" viewBox="0 0 1 1" preserveAspectRatio="none">
<circle
:r="r"
cx="50%" cy="50%"
fill="none"
stroke-width="0.1"
stroke="rgba(0, 0, 0, 0.05)"
:class="$style.circle"
/>
<circle
:r="r"
@ -16,7 +17,7 @@
stroke-width="0.1"
:stroke="color"
/>
<text x="50%" y="50%" dy="0.05" text-anchor="middle">{{ (value * 100).toFixed(0) }}%</text>
<text x="50%" y="50%" dy="0.05" text-anchor="middle" :class="$style.text">{{ (value * 100).toFixed(0) }}%</text>
</svg>
</template>
@ -33,20 +34,20 @@ const color = $computed(() => `hsl(${180 - (props.value * 180)}, 80%, 70%)`);
const strokeDashoffset = $computed(() => (1 - props.value) * (Math.PI * (r * 2)));
</script>
<style lang="scss" scoped>
.hsalcinq {
<style lang="scss" module>
.root {
display: block;
height: 100%;
}
> circle {
.circle {
transform-origin: center;
transform: rotate(-90deg);
transition: stroke-dashoffset 0.5s ease;
}
> text {
.text {
font-size: 0.15px;
fill: currentColor;
}
}
</style>

View file

@ -6,6 +6,7 @@ import { type UserConfig, defineConfig } from 'vite';
import ReactivityTransform from '@vue-macros/reactivity-transform/vite';
import locales from '../../locales';
import generateDTS from '../../locales/generateDTS';
import meta from '../../package.json';
import pluginJson5 from './vite.json5';
@ -64,6 +65,10 @@ export function getConfig(): UserConfig {
}),
]
: [],
{
name: 'locale:generateDTS',
buildStart: generateDTS,
},
],
resolve: {

File diff suppressed because it is too large Load diff