summaryrefslogtreecommitdiff
path: root/packages/client/src/components
diff options
context:
space:
mode:
authorsyuilo <Syuilotan@yahoo.co.jp>2022-08-07 00:39:21 +0900
committersyuilo <Syuilotan@yahoo.co.jp>2022-08-07 00:39:21 +0900
commit3b1669fb6b131e0809b3a4a93270b2b9e4a74247 (patch)
treed9c0397e52f722e53fb99be6c6cae7023f5f054a /packages/client/src/components
parentMerge branch 'develop' (diff)
parent12.118.0 (diff)
downloadmisskey-3b1669fb6b131e0809b3a4a93270b2b9e4a74247.tar.gz
misskey-3b1669fb6b131e0809b3a4a93270b2b9e4a74247.tar.bz2
misskey-3b1669fb6b131e0809b3a4a93270b2b9e4a74247.zip
Merge branch 'develop'
Diffstat (limited to 'packages/client/src/components')
-rw-r--r--packages/client/src/components/MkNoteSub.vue9
-rw-r--r--packages/client/src/components/abuse-report.vue13
-rw-r--r--packages/client/src/components/analog-clock.vue149
-rw-r--r--packages/client/src/components/cropper-dialog.vue3
-rw-r--r--packages/client/src/components/digital-clock.vue77
-rw-r--r--packages/client/src/components/form/checkbox.vue3
-rw-r--r--packages/client/src/components/form/input.vue3
-rw-r--r--packages/client/src/components/form/link.vue37
-rw-r--r--packages/client/src/components/form/radio.vue49
-rw-r--r--packages/client/src/components/form/select.vue5
-rw-r--r--packages/client/src/components/form/switch.vue3
-rw-r--r--packages/client/src/components/form/textarea.vue39
-rw-r--r--packages/client/src/components/global/error.vue5
-rw-r--r--packages/client/src/components/global/router-view.vue46
-rw-r--r--packages/client/src/components/global/time.vue2
-rw-r--r--packages/client/src/components/instance-stats.vue35
-rw-r--r--packages/client/src/components/key-value.vue3
-rw-r--r--packages/client/src/components/note-detailed.vue6
-rw-r--r--packages/client/src/components/note.vue28
-rw-r--r--packages/client/src/components/notes.vue6
-rw-r--r--packages/client/src/components/notification.vue8
-rw-r--r--packages/client/src/components/notifications.vue3
-rw-r--r--packages/client/src/components/page-window.vue2
-rw-r--r--packages/client/src/components/poll-editor.vue31
-rw-r--r--packages/client/src/components/poll.vue120
-rw-r--r--packages/client/src/components/post-form.vue17
-rw-r--r--packages/client/src/components/remote-caution.vue4
-rw-r--r--packages/client/src/components/renote-button.vue106
-rw-r--r--packages/client/src/components/signin-dialog.vue10
-rw-r--r--packages/client/src/components/signup-dialog.vue8
-rw-r--r--packages/client/src/components/signup.vue58
-rw-r--r--packages/client/src/components/sparkle.vue92
-rw-r--r--packages/client/src/components/sub-note-content.vue11
-rw-r--r--packages/client/src/components/tab.vue8
-rw-r--r--packages/client/src/components/tag-cloud.vue38
-rw-r--r--packages/client/src/components/ui/button.vue2
-rw-r--r--packages/client/src/components/ui/menu.vue7
-rw-r--r--packages/client/src/components/ui/pagination.vue37
-rw-r--r--packages/client/src/components/ui/window.vue1
-rw-r--r--packages/client/src/components/updated.vue9
-rw-r--r--packages/client/src/components/url-preview.vue7
-rw-r--r--packages/client/src/components/user-info.vue9
-rw-r--r--packages/client/src/components/user-list.vue6
-rw-r--r--packages/client/src/components/user-select-dialog.vue14
-rw-r--r--packages/client/src/components/visibility-picker.vue21
-rw-r--r--packages/client/src/components/widgets.vue6
46 files changed, 667 insertions, 489 deletions
diff --git a/packages/client/src/components/MkNoteSub.vue b/packages/client/src/components/MkNoteSub.vue
index 30c27e6235..9ae773bfb6 100644
--- a/packages/client/src/components/MkNoteSub.vue
+++ b/packages/client/src/components/MkNoteSub.vue
@@ -6,7 +6,7 @@
<XNoteHeader class="header" :note="note" :mini="true"/>
<div class="body">
<p v-if="note.cw != null" class="cw">
- <Mfm v-if="note.cw != ''" class="text" :text="note.cw" :author="note.user" :i="$i" :custom-emojis="note.emojis" />
+ <Mfm v-if="note.cw != ''" class="text" :text="note.cw" :author="note.user" :i="$i" :custom-emojis="note.emojis"/>
<XCwButton v-model="showContent" :note="note"/>
</p>
<div v-show="note.cw == null || showContent" class="content">
@@ -19,7 +19,7 @@
<MkNoteSub v-for="reply in replies" :key="reply.id" :note="reply" class="reply" :detail="true" :depth="depth + 1"/>
</template>
<div v-else class="more">
- <MkA class="text _link" :to="notePage(note)">{{ $ts.continueThread }} <i class="fas fa-angle-double-right"></i></MkA>
+ <MkA class="text _link" :to="notePage(note)">{{ i18n.ts.continueThread }} <i class="fas fa-angle-double-right"></i></MkA>
</div>
</div>
</template>
@@ -27,11 +27,12 @@
<script lang="ts" setup>
import { } from 'vue';
import * as misskey from 'misskey-js';
-import { notePage } from '@/filters/note';
import XNoteHeader from './note-header.vue';
import MkNoteSubNoteContent from './sub-note-content.vue';
import XCwButton from './cw-button.vue';
+import { notePage } from '@/filters/note';
import * as os from '@/os';
+import { i18n } from '@/i18n';
const props = withDefaults(defineProps<{
note: misskey.entities.Note;
@@ -49,7 +50,7 @@ let replies: misskey.entities.Note[] = $ref([]);
if (props.detail) {
os.api('notes/children', {
noteId: props.note.id,
- limit: 5
+ limit: 5,
}).then(res => {
replies = res;
});
diff --git a/packages/client/src/components/abuse-report.vue b/packages/client/src/components/abuse-report.vue
index 2b89eef85a..8c25df1107 100644
--- a/packages/client/src/components/abuse-report.vue
+++ b/packages/client/src/components/abuse-report.vue
@@ -9,7 +9,7 @@
</div>
</MkA>
<MkKeyValue class="_formBlock">
- <template #key>{{ $ts.registeredDate }}</template>
+ <template #key>{{ i18n.ts.registeredDate }}</template>
<template #value>{{ new Date(report.targetUser.createdAt).toLocaleString() }} (<MkTime :time="report.targetUser.createdAt"/>)</template>
</MkKeyValue>
</div>
@@ -18,18 +18,18 @@
<Mfm :text="report.comment"/>
</div>
<hr/>
- <div>{{ $ts.reporter }}: <MkAcct :user="report.reporter"/></div>
+ <div>{{ i18n.ts.reporter }}: <MkAcct :user="report.reporter"/></div>
<div v-if="report.assignee">
- {{ $ts.moderator }}:
+ {{ i18n.ts.moderator }}:
<MkAcct :user="report.assignee"/>
</div>
<div><MkTime :time="report.createdAt"/></div>
<div class="action">
<MkSwitch v-model="forward" :disabled="report.targetUser.host == null || report.resolved">
- {{ $ts.forwardReport }}
- <template #caption>{{ $ts.forwardReportIsAnonymous }}</template>
+ {{ i18n.ts.forwardReport }}
+ <template #caption>{{ i18n.ts.forwardReportIsAnonymous }}</template>
</MkSwitch>
- <MkButton v-if="!report.resolved" primary @click="resolve">{{ $ts.abuseMarkAsResolved }}</MkButton>
+ <MkButton v-if="!report.resolved" primary @click="resolve">{{ i18n.ts.abuseMarkAsResolved }}</MkButton>
</div>
</div>
</div>
@@ -41,6 +41,7 @@ import MkSwitch from '@/components/form/switch.vue';
import MkKeyValue from '@/components/key-value.vue';
import { acct, userPage } from '@/filters/user';
import * as os from '@/os';
+import { i18n } from '@/i18n';
const props = defineProps<{
report: any;
diff --git a/packages/client/src/components/analog-clock.vue b/packages/client/src/components/analog-clock.vue
index 18dd1e3f41..b138bfcb46 100644
--- a/packages/client/src/components/analog-clock.vue
+++ b/packages/client/src/components/analog-clock.vue
@@ -1,12 +1,30 @@
<template>
<svg class="mbcofsoe" viewBox="0 0 10 10" preserveAspectRatio="none">
- <circle v-for="(angle, i) in graduations"
- :key="i"
- :cx="5 + (Math.sin(angle) * (5 - graduationsPadding))"
- :cy="5 - (Math.cos(angle) * (5 - graduationsPadding))"
- :r="i % 5 == 0 ? 0.125 : 0.05"
- :fill="i % 5 == 0 ? majorGraduationColor : minorGraduationColor"
- />
+ <template v-if="props.graduations === 'dots'">
+ <circle
+ v-for="(angle, i) in graduationsMajor"
+ :cx="5 + (Math.sin(angle) * (5 - graduationsPadding))"
+ :cy="5 - (Math.cos(angle) * (5 - graduationsPadding))"
+ :r="0.125"
+ :fill="(props.twentyfour ? h : h % 12) === i ? nowColor : majorGraduationColor"
+ :opacity="!props.fadeGraduations || (props.twentyfour ? h : h % 12) === i ? 1 : Math.max(0, 1 - (angleDiff(hAngle, angle) / Math.PI) - numbersOpacityFactor)"
+ />
+ </template>
+ <template v-else-if="props.graduations === 'numbers'">
+ <text
+ v-for="(angle, i) in texts"
+ :x="5 + (Math.sin(angle) * (5 - textsPadding))"
+ :y="5 - (Math.cos(angle) * (5 - textsPadding))"
+ text-anchor="middle"
+ dominant-baseline="middle"
+ :font-size="(props.twentyfour ? h : h % 12) === i ? 1 : 0.7"
+ :font-weight="(props.twentyfour ? h : h % 12) === i ? 'bold' : 'normal'"
+ :fill="(props.twentyfour ? h : h % 12) === i ? nowColor : 'currentColor'"
+ :opacity="!props.fadeGraduations || (props.twentyfour ? h : h % 12) === i ? 1 : Math.max(0, 1 - (angleDiff(hAngle, angle) / Math.PI) - numbersOpacityFactor)"
+ >
+ {{ i === 0 ? (props.twentyfour ? '24' : '12') : i }}
+ </text>
+ </template>
<line
:x1="5 - (Math.sin(sAngle) * (sHandLengthRatio * handsTailLength))"
@@ -41,63 +59,116 @@
</template>
<script lang="ts" setup>
-import { ref, computed, onMounted, onBeforeUnmount } from 'vue';
+import { ref, computed, onMounted, onBeforeUnmount, shallowRef } from 'vue';
import tinycolor from 'tinycolor2';
+import { globalEvents } from '@/events.js';
+
+// https://stackoverflow.com/questions/1878907/how-can-i-find-the-difference-between-two-angles
+const angleDiff = (a: number, b: number) => {
+ const x = Math.abs(a - b);
+ return Math.abs((x + Math.PI) % (Math.PI * 2) - Math.PI);
+};
+
+const graduationsPadding = 0.5;
+const textsPadding = 0.6;
+const handsPadding = 1;
+const handsTailLength = 0.7;
+const hHandLengthRatio = 0.75;
+const mHandLengthRatio = 1;
+const sHandLengthRatio = 1;
+const numbersOpacityFactor = 0.35;
-withDefaults(defineProps<{
- thickness: number;
+const props = withDefaults(defineProps<{
+ thickness?: number;
+ offset?: number;
+ twentyfour?: boolean;
+ graduations?: 'none' | 'dots' | 'numbers';
+ fadeGraduations?: boolean;
}>(), {
+ numbers: false,
thickness: 0.1,
+ offset: 0 - new Date().getTimezoneOffset(),
+ twentyfour: false,
+ graduations: 'dots',
+ fadeGraduations: true,
});
-const now = ref(new Date());
-const enabled = ref(true);
-const graduationsPadding = ref(0.5);
-const handsPadding = ref(1);
-const handsTailLength = ref(0.7);
-const hHandLengthRatio = ref(0.75);
-const mHandLengthRatio = ref(1);
-const sHandLengthRatio = ref(1);
-const computedStyle = getComputedStyle(document.documentElement);
-
-const dark = computed(() => tinycolor(computedStyle.getPropertyValue('--bg')).isDark());
-const majorGraduationColor = computed(() => dark.value ? 'rgba(255, 255, 255, 0.3)' : 'rgba(0, 0, 0, 0.3)');
-const minorGraduationColor = computed(() => dark.value ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)');
-const sHandColor = computed(() => dark.value ? 'rgba(255, 255, 255, 0.5)' : 'rgba(0, 0, 0, 0.3)');
-const mHandColor = computed(() => tinycolor(computedStyle.getPropertyValue('--fg')).toHexString());
-const hHandColor = computed(() => tinycolor(computedStyle.getPropertyValue('--accent')).toHexString());
-const s = computed(() => now.value.getSeconds());
-const m = computed(() => now.value.getMinutes());
-const h = computed(() => now.value.getHours());
-const hAngle = computed(() => Math.PI * (h.value % 12 + (m.value + s.value / 60) / 60) / 6);
-const mAngle = computed(() => Math.PI * (m.value + s.value / 60) / 30);
-const sAngle = computed(() => Math.PI * s.value / 30);
-const graduations = computed(() => {
+const graduationsMajor = computed(() => {
const angles: number[] = [];
- for (let i = 0; i < 60; i++) {
- const angle = Math.PI * i / 30;
+ const times = props.twentyfour ? 24 : 12;
+ for (let i = 0; i < times; i++) {
+ const angle = Math.PI * i / (times / 2);
angles.push(angle);
}
-
return angles;
});
+const texts = computed(() => {
+ const angles: number[] = [];
+ const times = props.twentyfour ? 24 : 12;
+ for (let i = 0; i < times; i++) {
+ const angle = Math.PI * i / (times / 2);
+ angles.push(angle);
+ }
+ return angles;
+});
+
+let enabled = true;
+let majorGraduationColor = $ref<string>();
+//let minorGraduationColor = $ref<string>();
+let sHandColor = $ref<string>();
+let mHandColor = $ref<string>();
+let hHandColor = $ref<string>();
+let nowColor = $ref<string>();
+let h = $ref<number>(0);
+let m = $ref<number>(0);
+let s = $ref<number>(0);
+let hAngle = $ref<number>(0);
+let mAngle = $ref<number>(0);
+let sAngle = $ref<number>(0);
function tick() {
- now.value = new Date();
+ const now = new Date();
+ now.setMinutes(now.getMinutes() + (new Date().getTimezoneOffset() + props.offset));
+ s = now.getSeconds();
+ m = now.getMinutes();
+ h = now.getHours();
+ hAngle = Math.PI * (h % (props.twentyfour ? 24 : 12) + (m + s / 60) / 60) / (props.twentyfour ? 12 : 6);
+ mAngle = Math.PI * (m + s / 60) / 30;
+ sAngle = Math.PI * s / 30;
+}
+
+tick();
+
+function calcColors() {
+ const computedStyle = getComputedStyle(document.documentElement);
+ const dark = tinycolor(computedStyle.getPropertyValue('--bg')).isDark();
+ const accent = tinycolor(computedStyle.getPropertyValue('--accent')).toHexString();
+ majorGraduationColor = dark ? 'rgba(255, 255, 255, 0.3)' : 'rgba(0, 0, 0, 0.3)';
+ //minorGraduationColor = dark ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
+ sHandColor = dark ? 'rgba(255, 255, 255, 0.5)' : 'rgba(0, 0, 0, 0.3)';
+ mHandColor = tinycolor(computedStyle.getPropertyValue('--fg')).toHexString();
+ hHandColor = accent;
+ nowColor = accent;
}
+calcColors();
+
onMounted(() => {
const update = () => {
- if (enabled.value) {
+ if (enabled) {
tick();
window.setTimeout(update, 1000);
}
};
update();
+
+ globalEvents.on('themeChanged', calcColors);
});
onBeforeUnmount(() => {
- enabled.value = false;
+ enabled = false;
+
+ globalEvents.off('themeChanged', calcColors);
});
</script>
diff --git a/packages/client/src/components/cropper-dialog.vue b/packages/client/src/components/cropper-dialog.vue
index a8bde6ea05..c320b21d72 100644
--- a/packages/client/src/components/cropper-dialog.vue
+++ b/packages/client/src/components/cropper-dialog.vue
@@ -9,7 +9,7 @@
@ok="ok()"
@closed="$emit('closed')"
>
- <template #header>{{ $ts.cropImage }}</template>
+ <template #header>{{ i18n.ts.cropImage }}</template>
<template #default="{ width, height }">
<div class="mk-cropper-dialog" :style="`--vw: ${width}px; --vh: ${height}px;`">
<Transition name="fade">
@@ -36,6 +36,7 @@ import { $i } from '@/account';
import { defaultStore } from '@/store';
import { apiUrl, url } from '@/config';
import { query } from '@/scripts/url';
+import { i18n } from '@/i18n';
const emit = defineEmits<{
(ev: 'ok', cropped: misskey.entities.DriveFile): void;
diff --git a/packages/client/src/components/digital-clock.vue b/packages/client/src/components/digital-clock.vue
new file mode 100644
index 0000000000..9ed8d63d19
--- /dev/null
+++ b/packages/client/src/components/digital-clock.vue
@@ -0,0 +1,77 @@
+<template>
+<span class="zjobosdg">
+ <span v-text="hh"></span>
+ <span class="colon" :class="{ showColon }">:</span>
+ <span v-text="mm"></span>
+ <span v-if="showS" class="colon" :class="{ showColon }">:</span>
+ <span v-if="showS" v-text="ss"></span>
+ <span v-if="showMs" class="colon" :class="{ showColon }">:</span>
+ <span v-if="showMs" v-text="ms"></span>
+</span>
+</template>
+
+<script lang="ts" setup>
+import { onUnmounted, ref, watch } from 'vue';
+
+const props = withDefaults(defineProps<{
+ showS?: boolean;
+ showMs?: boolean;
+ offset?: number;
+}>(), {
+ showS: true,
+ showMs: false,
+ offset: 0 - new Date().getTimezoneOffset(),
+});
+
+let intervalId;
+const hh = ref('');
+const mm = ref('');
+const ss = ref('');
+const ms = ref('');
+const showColon = ref(false);
+let prevSec: number | null = null;
+
+watch(showColon, (v) => {
+ if (v) {
+ window.setTimeout(() => {
+ showColon.value = false;
+ }, 30);
+ }
+});
+
+const tick = () => {
+ const now = new Date();
+ now.setMinutes(now.getMinutes() + (new Date().getTimezoneOffset() + props.offset));
+ hh.value = now.getHours().toString().padStart(2, '0');
+ mm.value = now.getMinutes().toString().padStart(2, '0');
+ ss.value = now.getSeconds().toString().padStart(2, '0');
+ ms.value = Math.floor(now.getMilliseconds() / 10).toString().padStart(2, '0');
+ if (now.getSeconds() !== prevSec) showColon.value = true;
+ prevSec = now.getSeconds();
+};
+
+tick();
+
+watch(() => props.showMs, () => {
+ if (intervalId) window.clearInterval(intervalId);
+ intervalId = window.setInterval(tick, props.showMs ? 10 : 1000);
+}, { immediate: true });
+
+onUnmounted(() => {
+ window.clearInterval(intervalId);
+});
+</script>
+
+<style lang="scss" scoped>
+.zjobosdg {
+ > .colon {
+ opacity: 0;
+ transition: opacity 1s ease;
+
+ &.showColon {
+ opacity: 1;
+ transition: opacity 0s;
+ }
+ }
+}
+</style>
diff --git a/packages/client/src/components/form/checkbox.vue b/packages/client/src/components/form/checkbox.vue
index fadb770aee..fb5c82bb48 100644
--- a/packages/client/src/components/form/checkbox.vue
+++ b/packages/client/src/components/form/checkbox.vue
@@ -9,7 +9,7 @@
:disabled="disabled"
@keydown.enter="toggle"
>
- <span ref="button" v-adaptive-border v-tooltip="checked ? $ts.itsOn : $ts.itsOff" class="button" @click.prevent="toggle">
+ <span ref="button" v-adaptive-border v-tooltip="checked ? i18n.ts.itsOn : i18n.ts.itsOff" class="button" @click.prevent="toggle">
<i class="check fas fa-check"></i>
</span>
<span class="label">
@@ -24,6 +24,7 @@
import { toRefs, Ref } from 'vue';
import * as os from '@/os';
import Ripple from '@/components/ripple.vue';
+import { i18n } from '@/i18n';
const props = defineProps<{
modelValue: boolean | Ref<boolean>;
diff --git a/packages/client/src/components/form/input.vue b/packages/client/src/components/form/input.vue
index 2a03d6a5d4..1c9fee8c77 100644
--- a/packages/client/src/components/form/input.vue
+++ b/packages/client/src/components/form/input.vue
@@ -29,7 +29,7 @@
</div>
<div class="caption"><slot name="caption"></slot></div>
- <MkButton v-if="manualSave && changed" primary class="save" @click="updated"><i class="fas fa-check"></i> {{ $ts.save }}</MkButton>
+ <MkButton v-if="manualSave && changed" primary class="save" @click="updated"><i class="fas fa-check"></i> {{ i18n.ts.save }}</MkButton>
</div>
</template>
@@ -38,6 +38,7 @@ import { onMounted, onUnmounted, nextTick, ref, watch, computed, toRefs } from '
import { debounce } from 'throttle-debounce';
import MkButton from '@/components/ui/button.vue';
import { useInterval } from '@/scripts/use-interval';
+import { i18n } from '@/i18n';
const props = defineProps<{
modelValue: string | number;
diff --git a/packages/client/src/components/form/link.vue b/packages/client/src/components/form/link.vue
index b74e9bd684..34b641ffb6 100644
--- a/packages/client/src/components/form/link.vue
+++ b/packages/client/src/components/form/link.vue
@@ -19,33 +19,16 @@
</div>
</template>
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { } from 'vue';
-export default defineComponent({
- props: {
- to: {
- type: String,
- required: true
- },
- active: {
- type: Boolean,
- required: false
- },
- external: {
- type: Boolean,
- required: false
- },
- behavior: {
- type: String,
- required: false,
- },
- inline: {
- type: Boolean,
- required: false
- },
- },
-});
+const props = defineProps<{
+ to: string;
+ active?: boolean;
+ external?: boolean;
+ behavior?: null | 'window' | 'browser' | 'modalWindow';
+ inline?: boolean;
+}>();
</script>
<style lang="scss" scoped>
@@ -61,7 +44,7 @@ export default defineComponent({
align-items: center;
width: 100%;
box-sizing: border-box;
- padding: 12px 14px 12px 14px;
+ padding: 10px 14px;
background: var(--buttonBg);
border-radius: 6px;
font-size: 0.9em;
diff --git a/packages/client/src/components/form/radio.vue b/packages/client/src/components/form/radio.vue
index b4d39507e3..b36f7e9fdc 100644
--- a/packages/client/src/components/form/radio.vue
+++ b/packages/client/src/components/form/radio.vue
@@ -18,34 +18,25 @@
</div>
</template>
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { } from 'vue';
-export default defineComponent({
- props: {
- modelValue: {
- required: false,
- },
- value: {
- required: false,
- },
- disabled: {
- type: Boolean,
- default: false,
- },
- },
- computed: {
- checked(): boolean {
- return this.modelValue === this.value;
- },
- },
- methods: {
- toggle() {
- if (this.disabled) return;
- this.$emit('update:modelValue', this.value);
- },
- },
-});
+const props = defineProps<{
+ modelValue: any;
+ value: any;
+ disabled: boolean;
+}>();
+
+const emit = defineEmits<{
+ (ev: 'update:modelValue', value: any): void;
+}>();
+
+let checked = $computed(() => props.modelValue === props.value);
+
+function toggle(): void {
+ if (props.disabled) return;
+ emit('update:modelValue', props.value);
+}
</script>
<style lang="scss" scoped>
@@ -54,13 +45,13 @@ export default defineComponent({
display: inline-block;
text-align: left;
cursor: pointer;
- padding: 9px 12px;
+ padding: 8px 10px;
min-width: 60px;
background-color: var(--panel);
background-clip: padding-box !important;
border: solid 1px var(--panel);
border-radius: 6px;
- transition: all 0.3s;
+ transition: all 0.2s;
> * {
user-select: none;
diff --git a/packages/client/src/components/form/select.vue b/packages/client/src/components/form/select.vue
index 78282dfdc1..70db2dbae3 100644
--- a/packages/client/src/components/form/select.vue
+++ b/packages/client/src/components/form/select.vue
@@ -22,7 +22,7 @@
</div>
<div class="caption"><slot name="caption"></slot></div>
- <MkButton v-if="manualSave && changed" primary @click="updated"><i class="fas fa-save"></i> {{ $ts.save }}</MkButton>
+ <MkButton v-if="manualSave && changed" primary @click="updated"><i class="fas fa-save"></i> {{ i18n.ts.save }}</MkButton>
</div>
</template>
@@ -31,6 +31,7 @@ import { onMounted, onUnmounted, nextTick, ref, watch, computed, toRefs, VNode,
import MkButton from '@/components/ui/button.vue';
import * as os from '@/os';
import { useInterval } from '@/scripts/use-interval';
+import { i18n } from '@/i18n';
const props = defineProps<{
modelValue: string;
@@ -144,6 +145,8 @@ const onClick = (ev: MouseEvent) => {
} else if (Array.isArray(vnode.children)) { // 何故かフラグメントになってくることがある
const fragment = vnode;
scanOptions(fragment.children);
+ } else if (vnode.props == null) { // v-if で条件が false のときにこうなる
+ // nop?
} else {
const option = vnode;
pushOption(option);
diff --git a/packages/client/src/components/form/switch.vue b/packages/client/src/components/form/switch.vue
index fead163552..1ed00ae655 100644
--- a/packages/client/src/components/form/switch.vue
+++ b/packages/client/src/components/form/switch.vue
@@ -9,7 +9,7 @@
:disabled="disabled"
@keydown.enter="toggle"
>
- <span ref="button" v-tooltip="checked ? $ts.itsOn : $ts.itsOff" class="button" @click.prevent="toggle">
+ <span ref="button" v-tooltip="checked ? i18n.ts.itsOn : i18n.ts.itsOff" class="button" @click.prevent="toggle">
<div class="knob"></div>
</span>
<span class="label">
@@ -23,6 +23,7 @@
<script lang="ts" setup>
import { toRefs, Ref } from 'vue';
import * as os from '@/os';
+import { i18n } from '@/i18n';
const props = defineProps<{
modelValue: boolean | Ref<boolean>;
diff --git a/packages/client/src/components/form/textarea.vue b/packages/client/src/components/form/textarea.vue
index c9ba9b97a2..73633399de 100644
--- a/packages/client/src/components/form/textarea.vue
+++ b/packages/client/src/components/form/textarea.vue
@@ -2,7 +2,8 @@
<div class="adhpbeos">
<div class="label" @click="focus"><slot name="label"></slot></div>
<div class="input" :class="{ disabled, focused, tall, pre }">
- <textarea ref="inputEl"
+ <textarea
+ ref="inputEl"
v-model="v"
v-adaptive-border
:class="{ code, _monospace: code }"
@@ -21,14 +22,15 @@
</div>
<div class="caption"><slot name="caption"></slot></div>
- <MkButton v-if="manualSave && changed" primary class="save" @click="updated"><i class="fas fa-save"></i> {{ $ts.save }}</MkButton>
+ <MkButton v-if="manualSave && changed" primary class="save" @click="updated"><i class="fas fa-save"></i> {{ i18n.ts.save }}</MkButton>
</div>
</template>
<script lang="ts">
import { defineComponent, onMounted, onUnmounted, nextTick, ref, watch, computed, toRefs } from 'vue';
-import MkButton from '@/components/ui/button.vue';
import { debounce } from 'throttle-debounce';
+import MkButton from '@/components/ui/button.vue';
+import { i18n } from '@/i18n';
export default defineComponent({
components: {
@@ -37,66 +39,66 @@ export default defineComponent({
props: {
modelValue: {
- required: true
+ required: true,
},
type: {
type: String,
- required: false
+ required: false,
},
required: {
type: Boolean,
- required: false
+ required: false,
},
readonly: {
type: Boolean,
- required: false
+ required: false,
},
disabled: {
type: Boolean,
- required: false
+ required: false,
},
pattern: {
type: String,
- required: false
+ required: false,
},
placeholder: {
type: String,
- required: false
+ required: false,
},
autofocus: {
type: Boolean,
required: false,
- default: false
+ default: false,
},
autocomplete: {
- required: false
+ required: false,
},
spellcheck: {
- required: false
+ required: false,
},
code: {
type: Boolean,
- required: false
+ required: false,
},
tall: {
type: Boolean,
required: false,
- default: false
+ default: false,
},
pre: {
type: Boolean,
required: false,
- default: false
+ default: false,
},
debounce: {
type: Boolean,
required: false,
- default: false
+ default: false,
},
manualSave: {
type: Boolean,
required: false,
- default: false
+ default: false,
},
},
@@ -166,6 +168,7 @@ export default defineComponent({
onInput,
onKeydown,
updated,
+ i18n,
};
},
});
diff --git a/packages/client/src/components/global/error.vue b/packages/client/src/components/global/error.vue
index 98b96fb414..4e2ba07d30 100644
--- a/packages/client/src/components/global/error.vue
+++ b/packages/client/src/components/global/error.vue
@@ -2,14 +2,15 @@
<transition :name="$store.state.animation ? 'zoom' : ''" appear>
<div class="mjndxjcg">
<img src="https://xn--931a.moe/assets/error.jpg" class="_ghost"/>
- <p><i class="fas fa-exclamation-triangle"></i> {{ $ts.somethingHappened }}</p>
- <MkButton class="button" @click="() => $emit('retry')">{{ $ts.retry }}</MkButton>
+ <p><i class="fas fa-exclamation-triangle"></i> {{ i18n.ts.somethingHappened }}</p>
+ <MkButton class="button" @click="() => $emit('retry')">{{ i18n.ts.retry }}</MkButton>
</div>
</transition>
</template>
<script lang="ts" setup>
import MkButton from '@/components/ui/button.vue';
+import { i18n } from '@/i18n';
</script>
<style lang="scss" scoped>
diff --git a/packages/client/src/components/global/router-view.vue b/packages/client/src/components/global/router-view.vue
index fca2371f0d..1d841e050c 100644
--- a/packages/client/src/components/global/router-view.vue
+++ b/packages/client/src/components/global/router-view.vue
@@ -1,12 +1,18 @@
<template>
<KeepAlive :max="defaultStore.state.numberOfPageCache">
- <component :is="currentPageComponent" :key="key" v-bind="Object.fromEntries(currentPageProps)"/>
+ <Suspense>
+ <component :is="currentPageComponent" :key="key" v-bind="Object.fromEntries(currentPageProps)"/>
+
+ <template #fallback>
+ Loading...
+ </template>
+ </Suspense>
</KeepAlive>
</template>
<script lang="ts" setup>
-import { inject, nextTick, onMounted, onUnmounted, watch } from 'vue';
-import { Router } from '@/nirax';
+import { inject, nextTick, onBeforeUnmount, onMounted, onUnmounted, provide, watch } from 'vue';
+import { Resolved, Router } from '@/nirax';
import { defaultStore } from '@/store';
const props = defineProps<{
@@ -19,19 +25,37 @@ if (router == null) {
throw new Error('no router provided');
}
-let currentPageComponent = $shallowRef(router.getCurrentComponent());
-let currentPageProps = $ref(router.getCurrentProps());
-let key = $ref(router.getCurrentKey());
+const currentDepth = inject('routerCurrentDepth', 0);
+provide('routerCurrentDepth', currentDepth + 1);
+
+function resolveNested(current: Resolved, d = 0): Resolved | null {
+ if (d === currentDepth) {
+ return current;
+ } else {
+ if (current.child) {
+ return resolveNested(current.child, d + 1);
+ } else {
+ return null;
+ }
+ }
+}
+
+const current = resolveNested(router.current)!;
+let currentPageComponent = $shallowRef(current.route.component);
+let currentPageProps = $ref(current.props);
+let key = $ref(current.route.path + JSON.stringify(Object.fromEntries(current.props)));
-function onChange({ route, props: newProps, key: newKey }) {
- currentPageComponent = route.component;
- currentPageProps = newProps;
- key = newKey;
+function onChange({ resolved, key: newKey }) {
+ const current = resolveNested(resolved);
+ if (current == null) return;
+ currentPageComponent = current.route.component;
+ currentPageProps = current.props;
+ key = current.route.path + JSON.stringify(Object.fromEntries(current.props));
}
router.addListener('change', onChange);
-onUnmounted(() => {
+onBeforeUnmount(() => {
router.removeListener('change', onChange);
});
</script>
diff --git a/packages/client/src/components/global/time.vue b/packages/client/src/components/global/time.vue
index 801490225b..f72b153f56 100644
--- a/packages/client/src/components/global/time.vue
+++ b/packages/client/src/components/global/time.vue
@@ -20,7 +20,7 @@ const props = withDefaults(defineProps<{
const _time = typeof props.time === 'string' ? new Date(props.time) : props.time;
const absolute = _time.toLocaleString();
-let now = $ref(new Date());
+let now = $shallowRef(new Date());
const relative = $computed(() => {
const ago = (now.getTime() - _time.getTime()) / 1000/*ms*/;
return (
diff --git a/packages/client/src/components/instance-stats.vue b/packages/client/src/components/instance-stats.vue
index 1a811c2d87..65465dd9a2 100644
--- a/packages/client/src/components/instance-stats.vue
+++ b/packages/client/src/components/instance-stats.vue
@@ -4,29 +4,29 @@
<div class="body">
<div class="selects" style="display: flex;">
<MkSelect v-model="chartSrc" style="margin: 0; flex: 1;">
- <optgroup :label="$ts.federation">
- <option value="federation">{{ $ts._charts.federation }}</option>
- <option value="ap-request">{{ $ts._charts.apRequest }}</option>
+ <optgroup :label="i18n.ts.federation">
+ <option value="federation">{{ i18n.ts._charts.federation }}</option>
+ <option value="ap-request">{{ i18n.ts._charts.apRequest }}</option>
</optgroup>
- <optgroup :label="$ts.users">
- <option value="users">{{ $ts._charts.usersIncDec }}</option>
- <option value="users-total">{{ $ts._charts.usersTotal }}</option>
- <option value="active-users">{{ $ts._charts.activeUsers }}</option>
+ <optgroup :label="i18n.ts.users">
+ <option value="users">{{ i18n.ts._charts.usersIncDec }}</option>
+ <option value="users-total">{{ i18n.ts._charts.usersTotal }}</option>
+ <option value="active-users">{{ i18n.ts._charts.activeUsers }}</option>
</optgroup>
- <optgroup :label="$ts.notes">
- <option value="notes">{{ $ts._charts.notesIncDec }}</option>
- <option value="local-notes">{{ $ts._charts.localNotesIncDec }}</option>
- <option value="remote-notes">{{ $ts._charts.remoteNotesIncDec }}</option>
- <option value="notes-total">{{ $ts._charts.notesTotal }}</option>
+ <optgroup :label="i18n.ts.notes">
+ <option value="notes">{{ i18n.ts._charts.notesIncDec }}</option>
+ <option value="local-notes">{{ i18n.ts._charts.localNotesIncDec }}</option>
+ <option value="remote-notes">{{ i18n.ts._charts.remoteNotesIncDec }}</option>
+ <option value="notes-total">{{ i18n.ts._charts.notesTotal }}</option>
</optgroup>
- <optgroup :label="$ts.drive">
- <option value="drive-files">{{ $ts._charts.filesIncDec }}</option>
- <option value="drive">{{ $ts._charts.storageUsageIncDec }}</option>
+ <optgroup :label="i18n.ts.drive">
+ <option value="drive-files">{{ i18n.ts._charts.filesIncDec }}</option>
+ <option value="drive">{{ i18n.ts._charts.storageUsageIncDec }}</option>
</optgroup>
</MkSelect>
<MkSelect v-model="chartSpan" style="margin: 0 0 0 10px;">
- <option value="hour">{{ $ts.perHour }}</option>
- <option value="day">{{ $ts.perDay }}</option>
+ <option value="hour">{{ i18n.ts.perHour }}</option>
+ <option value="day">{{ i18n.ts.perDay }}</option>
</MkSelect>
</div>
<div class="chart">
@@ -71,6 +71,7 @@ import MkSelect from '@/components/form/select.vue';
import MkChart from '@/components/chart.vue';
import { useChartTooltip } from '@/scripts/use-chart-tooltip';
import * as os from '@/os';
+import { i18n } from '@/i18n';
Chart.register(
ArcElement,
diff --git a/packages/client/src/components/key-value.vue b/packages/client/src/components/key-value.vue
index 3d665e159d..586f7a3f9d 100644
--- a/packages/client/src/components/key-value.vue
+++ b/packages/client/src/components/key-value.vue
@@ -5,7 +5,7 @@
</div>
<div class="value">
<slot name="value"></slot>
- <button v-if="copy" v-tooltip="$ts.copy" class="_textButton" style="margin-left: 0.5em;" @click="copy_"><i class="far fa-copy"></i></button>
+ <button v-if="copy" v-tooltip="i18n.ts.copy" class="_textButton" style="margin-left: 0.5em;" @click="copy_"><i class="far fa-copy"></i></button>
</div>
</div>
</template>
@@ -14,6 +14,7 @@
import { } from 'vue';
import copyToClipboard from '@/scripts/copy-to-clipboard';
import * as os from '@/os';
+import { i18n } from '@/i18n';
const props = withDefaults(defineProps<{
copy?: string | null;
diff --git a/packages/client/src/components/note-detailed.vue b/packages/client/src/components/note-detailed.vue
index 85bffca4a5..1e0625b6c9 100644
--- a/packages/client/src/components/note-detailed.vue
+++ b/packages/client/src/components/note-detailed.vue
@@ -14,7 +14,7 @@
<div v-if="isRenote" class="renote">
<MkAvatar class="avatar" :user="note.user"/>
<i class="fas fa-retweet"></i>
- <I18n :src="$ts.renotedBy" tag="span">
+ <I18n :src="i18n.ts.renotedBy" tag="span">
<template #user>
<MkA v-user-preview="note.userId" class="name" :to="userPage(note.user)">
<MkUserName :user="note.user"/>
@@ -54,7 +54,7 @@
</p>
<div v-show="appearNote.cw == null || showContent" class="content">
<div class="text">
- <span v-if="appearNote.isHidden" style="opacity: 0.5">({{ $ts.private }})</span>
+ <span v-if="appearNote.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span>
<MkA v-if="appearNote.replyId" class="reply" :to="`/notes/${appearNote.replyId}`"><i class="fas fa-reply"></i></MkA>
<Mfm v-if="appearNote.text" :text="appearNote.text" :author="appearNote.user" :i="$i" :custom-emojis="appearNote.emojis"/>
<a v-if="appearNote.renote != null" class="rp">RN:</a>
@@ -103,7 +103,7 @@
<MkNoteSub v-for="note in replies" :key="note.id" :note="note" class="reply" :detail="true"/>
</div>
<div v-else class="_panel muted" @click="muted = false">
- <I18n :src="$ts.userSaysSomething" tag="small">
+ <I18n :src="i18n.ts.userSaysSomething" tag="small">
<template #name>
<MkA v-user-preview="appearNote.userId" class="name" :to="userPage(appearNote.user)">
<MkUserName :user="appearNote.user"/>
diff --git a/packages/client/src/components/note.vue b/packages/client/src/components/note.vue
index 3c9d361702..0279f014c6 100644
--- a/packages/client/src/components/note.vue
+++ b/packages/client/src/components/note.vue
@@ -41,7 +41,7 @@
<Mfm v-if="appearNote.cw != ''" class="text" :text="appearNote.cw" :author="appearNote.user" :i="$i" :custom-emojis="appearNote.emojis"/>
<XCwButton v-model="showContent" :note="appearNote"/>
</p>
- <div v-show="appearNote.cw == null || showContent" class="content" :class="{ collapsed }">
+ <div v-show="appearNote.cw == null || showContent" class="content" :class="{ collapsed, isLong }">
<div class="text">
<span v-if="appearNote.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span>
<MkA v-if="appearNote.replyId" class="reply" :to="`/notes/${appearNote.replyId}`"><i class="fas fa-reply"></i></MkA>
@@ -61,9 +61,12 @@
<XPoll v-if="appearNote.poll" ref="pollViewer" :note="appearNote" class="poll"/>
<MkUrlPreview v-for="url in urls" :key="url" :url="url" :compact="true" :detail="false" class="url-preview"/>
<div v-if="appearNote.renote" class="renote"><XNoteSimple :note="appearNote.renote"/></div>
- <button v-if="collapsed" class="fade _button" @click="collapsed = false">
+ <button v-if="isLong && collapsed" class="fade _button" @click="collapsed = false">
<span>{{ i18n.ts.showMore }}</span>
</button>
+ <button v-else-if="isLong && !collapsed" class="showLess _button" @click="collapsed = true">
+ <span>{{ i18n.ts.showLess }}</span>
+ </button>
</div>
<MkA v-if="appearNote.channel && !inChannel" class="channel" :to="`/channels/${appearNote.channel.id}`"><i class="fas fa-satellite-dish"></i> {{ appearNote.channel.name }}</MkA>
</div>
@@ -162,10 +165,11 @@ const reactButton = ref<HTMLElement>();
let appearNote = $computed(() => isRenote ? note.renote as misskey.entities.Note : note);
const isMyRenote = $i && ($i.id === note.userId);
const showContent = ref(false);
-const collapsed = ref(appearNote.cw == null && appearNote.text != null && (
+const isLong = (appearNote.cw == null && appearNote.text != null && (
(appearNote.text.split('\n').length > 9) ||
(appearNote.text.length > 500)
));
+const collapsed = ref(appearNote.cw == null && isLong);
const isDeleted = ref(false);
const muted = ref(checkWordMute(appearNote, $i, defaultStore.state.mutedWords));
const translation = ref(null);
@@ -442,6 +446,24 @@ function readPromo() {
}
> .content {
+ &.isLong {
+ > .showLess {
+ width: 100%;
+ margin-top: 1em;
+ position: sticky;
+ bottom: 1em;
+
+ > span {
+ display: inline-block;
+ background: var(--popup);
+ padding: 6px 10px;
+ font-size: 0.8em;
+ border-radius: 999px;
+ box-shadow: 0 2px 6px rgb(0 0 0 / 20%);
+ }
+ }
+ }
+
&.collapsed {
position: relative;
max-height: 9em;
diff --git a/packages/client/src/components/notes.vue b/packages/client/src/components/notes.vue
index 41bec5a579..e351a76eb5 100644
--- a/packages/client/src/components/notes.vue
+++ b/packages/client/src/components/notes.vue
@@ -3,7 +3,7 @@
<template #empty>
<div class="_fullinfo">
<img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/>
- <div>{{ $ts.noNotes }}</div>
+ <div>{{ i18n.ts.noNotes }}</div>
</div>
</template>
@@ -21,8 +21,8 @@
import { ref } from 'vue';
import XNote from '@/components/note.vue';
import XList from '@/components/date-separated-list.vue';
-import MkPagination from '@/components/ui/pagination.vue';
-import { Paging } from '@/components/ui/pagination.vue';
+import MkPagination, { Paging } from '@/components/ui/pagination.vue';
+import { i18n } from '@/i18n';
const props = defineProps<{
pagination: Paging;
diff --git a/packages/client/src/components/notification.vue b/packages/client/src/components/notification.vue
index 10cbe20902..9589970a44 100644
--- a/packages/client/src/components/notification.vue
+++ b/packages/client/src/components/notification.vue
@@ -61,10 +61,10 @@
<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full" :custom-emojis="notification.note.emojis"/>
<i class="fas fa-quote-right"></i>
</MkA>
- <span v-if="notification.type === 'follow'" class="text" style="opacity: 0.6;">{{ $ts.youGotNewFollower }}<div v-if="full"><MkFollowButton :user="notification.user" :full="true"/></div></span>
- <span v-if="notification.type === 'followRequestAccepted'" class="text" style="opacity: 0.6;">{{ $ts.followRequestAccepted }}</span>
- <span v-if="notification.type === 'receiveFollowRequest'" class="text" style="opacity: 0.6;">{{ $ts.receiveFollowRequest }}<div v-if="full && !followRequestDone"><button class="_textButton" @click="acceptFollowRequest()">{{ $ts.accept }}</button> | <button class="_textButton" @click="rejectFollowRequest()">{{ $ts.reject }}</button></div></span>
- <span v-if="notification.type === 'groupInvited'" class="text" style="opacity: 0.6;">{{ $ts.groupInvited }}: <b>{{ notification.invitation.group.name }}</b><div v-if="full && !groupInviteDone"><button class="_textButton" @click="acceptGroupInvitation()">{{ $ts.accept }}</button> | <button class="_textButton" @click="rejectGroupInvitation()">{{ $ts.reject }}</button></div></span>
+ <span v-if="notification.type === 'follow'" class="text" style="opacity: 0.6;">{{ i18n.ts.youGotNewFollower }}<div v-if="full"><MkFollowButton :user="notification.user" :full="true"/></div></span>
+ <span v-if="notification.type === 'followRequestAccepted'" class="text" style="opacity: 0.6;">{{ i18n.ts.followRequestAccepted }}</span>
+ <span v-if="notification.type === 'receiveFollowRequest'" class="text" style="opacity: 0.6;">{{ i18n.ts.receiveFollowRequest }}<div v-if="full && !followRequestDone"><button class="_textButton" @click="acceptFollowRequest()">{{ i18n.ts.accept }}</button> | <button class="_textButton" @click="rejectFollowRequest()">{{ i18n.ts.reject }}</button></div></span>
+ <span v-if="notification.type === 'groupInvited'" class="text" style="opacity: 0.6;">{{ i18n.ts.groupInvited }}: <b>{{ notification.invitation.group.name }}</b><div v-if="full && !groupInviteDone"><button class="_textButton" @click="acceptGroupInvitation()">{{ i18n.ts.accept }}</button> | <button class="_textButton" @click="rejectGroupInvitation()">{{ i18n.ts.reject }}</button></div></span>
<span v-if="notification.type === 'app'" class="text">
<Mfm :text="notification.body" :nowrap="!full"/>
</span>
diff --git a/packages/client/src/components/notifications.vue b/packages/client/src/components/notifications.vue
index eb19ad488c..baac2fdca2 100644
--- a/packages/client/src/components/notifications.vue
+++ b/packages/client/src/components/notifications.vue
@@ -3,7 +3,7 @@
<template #empty>
<div class="_fullinfo">
<img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/>
- <div>{{ $ts.noNotifications }}</div>
+ <div>{{ i18n.ts.noNotifications }}</div>
</div>
</template>
@@ -26,6 +26,7 @@ import XNote from '@/components/note.vue';
import * as os from '@/os';
import { stream } from '@/stream';
import { $i } from '@/account';
+import { i18n } from '@/i18n';
const props = defineProps<{
includeTypes?: typeof notificationTypes[number][];
diff --git a/packages/client/src/components/page-window.vue b/packages/client/src/components/page-window.vue
index 98140b95c0..43d75b0cf9 100644
--- a/packages/client/src/components/page-window.vue
+++ b/packages/client/src/components/page-window.vue
@@ -114,7 +114,7 @@ function menu(ev) {
function back() {
history.pop();
- router.change(history[history.length - 1].path, history[history.length - 1].key);
+ router.replace(history[history.length - 1].path, history[history.length - 1].key);
}
function close() {
diff --git a/packages/client/src/components/poll-editor.vue b/packages/client/src/components/poll-editor.vue
index a068aca79e..6c1a4cc89f 100644
--- a/packages/client/src/components/poll-editor.vue
+++ b/packages/client/src/components/poll-editor.vue
@@ -1,7 +1,7 @@
<template>
<div class="zmdxowus">
<p v-if="choices.length < 2" class="caution">
- <i class="fas fa-exclamation-triangle"></i>{{ $ts._poll.noOnlyOneChoice }}
+ <i class="fas fa-exclamation-triangle"></i>{{ i18n.ts._poll.noOnlyOneChoice }}
</p>
<ul>
<li v-for="(choice, i) in choices" :key="i">
@@ -12,34 +12,34 @@
</button>
</li>
</ul>
- <MkButton v-if="choices.length < 10" class="add" @click="add">{{ $ts.add }}</MkButton>
- <MkButton v-else class="add" disabled>{{ $ts._poll.noMore }}</MkButton>
- <MkSwitch v-model="multiple">{{ $ts._poll.canMultipleVote }}</MkSwitch>
+ <MkButton v-if="choices.length < 10" class="add" @click="add">{{ i18n.ts.add }}</MkButton>
+ <MkButton v-else class="add" disabled>{{ i18n.ts._poll.noMore }}</MkButton>
+ <MkSwitch v-model="multiple">{{ i18n.ts._poll.canMultipleVote }}</MkSwitch>
<section>
<div>
<MkSelect v-model="expiration" small>
- <template #label>{{ $ts._poll.expiration }}</template>
- <option value="infinite">{{ $ts._poll.infinite }}</option>
- <option value="at">{{ $ts._poll.at }}</option>
- <option value="after">{{ $ts._poll.after }}</option>
+ <template #label>{{ i18n.ts._poll.expiration }}</template>
+ <option value="infinite">{{ i18n.ts._poll.infinite }}</option>
+ <option value="at">{{ i18n.ts._poll.at }}</option>
+ <option value="after">{{ i18n.ts._poll.after }}</option>
</MkSelect>
<section v-if="expiration === 'at'">
<MkInput v-model="atDate" small type="date" class="input">
- <template #label>{{ $ts._poll.deadlineDate }}</template>
+ <template #label>{{ i18n.ts._poll.deadlineDate }}</template>
</MkInput>
<MkInput v-model="atTime" small type="time" class="input">
- <template #label>{{ $ts._poll.deadlineTime }}</template>
+ <template #label>{{ i18n.ts._poll.deadlineTime }}</template>
</MkInput>
</section>
<section v-else-if="expiration === 'after'">
<MkInput v-model="after" small type="number" class="input">
- <template #label>{{ $ts._poll.duration }}</template>
+ <template #label>{{ i18n.ts._poll.duration }}</template>
</MkInput>
<MkSelect v-model="unit" small>
- <option value="second">{{ $ts._time.second }}</option>
- <option value="minute">{{ $ts._time.minute }}</option>
- <option value="hour">{{ $ts._time.hour }}</option>
- <option value="day">{{ $ts._time.day }}</option>
+ <option value="second">{{ i18n.ts._time.second }}</option>
+ <option value="minute">{{ i18n.ts._time.minute }}</option>
+ <option value="hour">{{ i18n.ts._time.hour }}</option>
+ <option value="day">{{ i18n.ts._time.day }}</option>
</MkSelect>
</section>
</div>
@@ -55,6 +55,7 @@ import MkSwitch from './form/switch.vue';
import MkButton from './ui/button.vue';
import { formatDateTimeString } from '@/scripts/format-time-string';
import { addTime } from '@/scripts/time';
+import { i18n } from '@/i18n';
const props = defineProps<{
modelValue: {
diff --git a/packages/client/src/components/poll.vue b/packages/client/src/components/poll.vue
index 35f87325d8..d90af1cfee 100644
--- a/packages/client/src/components/poll.vue
+++ b/packages/client/src/components/poll.vue
@@ -13,97 +13,77 @@
<p v-if="!readOnly">
<span>{{ $t('_poll.totalVotes', { n: total }) }}</span>
<span> · </span>
- <a v-if="!closed && !isVoted" @click="showResult = !showResult">{{ showResult ? $ts._poll.vote : $ts._poll.showResult }}</a>
- <span v-if="isVoted">{{ $ts._poll.voted }}</span>
- <span v-else-if="closed">{{ $ts._poll.closed }}</span>
+ <a v-if="!closed && !isVoted" @click="showResult = !showResult">{{ showResult ? i18n.ts._poll.vote : i18n.ts._poll.showResult }}</a>
+ <span v-if="isVoted">{{ i18n.ts._poll.voted }}</span>
+ <span v-else-if="closed">{{ i18n.ts._poll.closed }}</span>
<span v-if="remaining > 0"> · {{ timer }}</span>
</p>
</div>
</template>
-<script lang="ts">
-import { computed, defineComponent, onUnmounted, ref, toRef } from 'vue';
+<script lang="ts" setup>
+import { computed, onUnmounted, ref, toRef } from 'vue';
+import * as misskey from 'misskey-js';
import { sum } from '@/scripts/array';
import { pleaseLogin } from '@/scripts/please-login';
import * as os from '@/os';
import { i18n } from '@/i18n';
import { useInterval } from '@/scripts/use-interval';
-export default defineComponent({
- props: {
- note: {
- type: Object,
- required: true,
- },
- readOnly: {
- type: Boolean,
- required: false,
- default: false,
- },
- },
+const props = defineProps<{
+ note: misskey.entities.Note;
+ readOnly?: boolean;
+}>();
- setup(props) {
- const remaining = ref(-1);
+const remaining = ref(-1);
- const total = computed(() => sum(props.note.poll.choices.map(x => x.votes)));
- const closed = computed(() => remaining.value === 0);
- const isVoted = computed(() => !props.note.poll.multiple && props.note.poll.choices.some(c => c.isVoted));
- const timer = computed(() => i18n.t(
- remaining.value >= 86400 ? '_poll.remainingDays' :
- remaining.value >= 3600 ? '_poll.remainingHours' :
- remaining.value >= 60 ? '_poll.remainingMinutes' : '_poll.remainingSeconds', {
- s: Math.floor(remaining.value % 60),
- m: Math.floor(remaining.value / 60) % 60,
- h: Math.floor(remaining.value / 3600) % 24,
- d: Math.floor(remaining.value / 86400),
- }));
+const total = computed(() => sum(props.note.poll.choices.map(x => x.votes)));
+const closed = computed(() => remaining.value === 0);
+const isVoted = computed(() => !props.note.poll.multiple && props.note.poll.choices.some(c => c.isVoted));
+const timer = computed(() => i18n.t(
+ remaining.value >= 86400 ? '_poll.remainingDays' :
+ remaining.value >= 3600 ? '_poll.remainingHours' :
+ remaining.value >= 60 ? '_poll.remainingMinutes' : '_poll.remainingSeconds', {
+ s: Math.floor(remaining.value % 60),
+ m: Math.floor(remaining.value / 60) % 60,
+ h: Math.floor(remaining.value / 3600) % 24,
+ d: Math.floor(remaining.value / 86400),
+ }));
- const showResult = ref(props.readOnly || isVoted.value);
+const showResult = ref(props.readOnly || isVoted.value);
- // 期限付きアンケート
- if (props.note.poll.expiresAt) {
- const tick = () => {
- remaining.value = Math.floor(Math.max(new Date(props.note.poll.expiresAt).getTime() - Date.now(), 0) / 1000);
- if (remaining.value === 0) {
- showResult.value = true;
- }
- };
-
- useInterval(tick, 3000, {
- immediate: true,
- afterMounted: false,
- });
+// 期限付きアンケート
+if (props.note.poll.expiresAt) {
+ const tick = () => {
+ remaining.value = Math.floor(Math.max(new Date(props.note.poll.expiresAt).getTime() - Date.now(), 0) / 1000);
+ if (remaining.value === 0) {
+ showResult.value = true;
}
+ };
- const vote = async (id) => {
- pleaseLogin();
+ useInterval(tick, 3000, {
+ immediate: true,
+ afterMounted: false,
+ });
+}
- if (props.readOnly || closed.value || isVoted.value) return;
+const vote = async (id) => {
+ pleaseLogin();
- const { canceled } = await os.confirm({
- type: 'question',
- text: i18n.t('voteConfirm', { choice: props.note.poll.choices[id].text }),
- });
- if (canceled) return;
+ if (props.readOnly || closed.value || isVoted.value) return;
- await os.api('notes/polls/vote', {
- noteId: props.note.id,
- choice: id,
- });
- if (!showResult.value) showResult.value = !props.note.poll.multiple;
- };
+ const { canceled } = await os.confirm({
+ type: 'question',
+ text: i18n.t('voteConfirm', { choice: props.note.poll.choices[id].text }),
+ });
+ if (canceled) return;
- return {
- remaining,
- showResult,
- total,
- isVoted,
- closed,
- timer,
- vote,
- };
- },
-});
+ await os.api('notes/polls/vote', {
+ noteId: props.note.id,
+ choice: id,
+ });
+ if (!showResult.value) showResult.value = !props.note.poll.multiple;
+};
</script>
<style lang="scss" scoped>
diff --git a/packages/client/src/components/post-form.vue b/packages/client/src/components/post-form.vue
index 77fcd79c13..6dfb2edcb8 100644
--- a/packages/client/src/components/post-form.vue
+++ b/packages/client/src/components/post-form.vue
@@ -479,7 +479,22 @@ function onDragover(ev) {
if (isFile || isDriveFile) {
ev.preventDefault();
draghover = true;
- ev.dataTransfer.dropEffect = ev.dataTransfer.effectAllowed === 'all' ? 'copy' : 'move';
+ switch (ev.dataTransfer.effectAllowed) {
+ case 'all':
+ case 'uninitialized':
+ case 'copy':
+ case 'copyLink':
+ case 'copyMove':
+ ev.dataTransfer.dropEffect = 'copy';
+ break;
+ case 'linkMove':
+ case 'move':
+ ev.dataTransfer.dropEffect = 'move';
+ break;
+ default:
+ ev.dataTransfer.dropEffect = 'none';
+ break;
+ }
}
}
diff --git a/packages/client/src/components/remote-caution.vue b/packages/client/src/components/remote-caution.vue
index 130a0249b6..e9461197ca 100644
--- a/packages/client/src/components/remote-caution.vue
+++ b/packages/client/src/components/remote-caution.vue
@@ -1,8 +1,10 @@
<template>
-<div class="jmgmzlwq _block"><i class="fas fa-exclamation-triangle" style="margin-right: 8px;"></i>{{ $ts.remoteUserCaution }}<a class="link" :href="href" rel="nofollow noopener" target="_blank">{{ $ts.showOnRemote }}</a></div>
+<div class="jmgmzlwq _block"><i class="fas fa-exclamation-triangle" style="margin-right: 8px;"></i>{{ i18n.ts.remoteUserCaution }}<a class="link" :href="href" rel="nofollow noopener" target="_blank">{{ i18n.ts.showOnRemote }}</a></div>
</template>
<script lang="ts" setup>
+import { i18n } from '@/i18n';
+
defineProps<{
href: string;
}>();
diff --git a/packages/client/src/components/renote-button.vue b/packages/client/src/components/renote-button.vue
index 3bcbe665bf..d267f30403 100644
--- a/packages/client/src/components/renote-button.vue
+++ b/packages/client/src/components/renote-button.vue
@@ -1,5 +1,6 @@
<template>
-<button v-if="canRenote"
+<button
+ v-if="canRenote"
ref="buttonRef"
class="eddddedb _button canRenote"
@click="renote()"
@@ -12,8 +13,9 @@
</button>
</template>
-<script lang="ts">
-import { computed, defineComponent, ref } from 'vue';
+<script lang="ts" setup>
+import { computed, ref } from 'vue';
+import * as misskey from 'misskey-js';
import XDetails from '@/components/users-tooltip.vue';
import { pleaseLogin } from '@/scripts/please-login';
import * as os from '@/os';
@@ -21,71 +23,55 @@ import { $i } from '@/account';
import { useTooltip } from '@/scripts/use-tooltip';
import { i18n } from '@/i18n';
-export default defineComponent({
- props: {
- count: {
- type: Number,
- required: true,
- },
- note: {
- type: Object,
- required: true,
- },
- },
+const props = defineProps<{
+ note: misskey.entities.Note;
+ count: number;
+}>();
- setup(props) {
- const buttonRef = ref<HTMLElement>();
+const buttonRef = ref<HTMLElement>();
- const canRenote = computed(() => ['public', 'home'].includes(props.note.visibility) || props.note.userId === $i.id);
+const canRenote = computed(() => ['public', 'home'].includes(props.note.visibility) || props.note.userId === $i.id);
- useTooltip(buttonRef, async (showing) => {
- const renotes = await os.api('notes/renotes', {
- noteId: props.note.id,
- limit: 11
- });
+useTooltip(buttonRef, async (showing) => {
+ const renotes = await os.api('notes/renotes', {
+ noteId: props.note.id,
+ limit: 11,
+ });
- const users = renotes.map(x => x.user);
+ const users = renotes.map(x => x.user);
- if (users.length < 1) return;
+ if (users.length < 1) return;
- os.popup(XDetails, {
- showing,
- users,
- count: props.count,
- targetElement: buttonRef.value
- }, {}, 'closed');
- });
+ os.popup(XDetails, {
+ showing,
+ users,
+ count: props.count,
+ targetElement: buttonRef.value,
+ }, {}, 'closed');
+});
- const renote = (viaKeyboard = false) => {
- pleaseLogin();
- os.popupMenu([{
- text: i18n.ts.renote,
- icon: 'fas fa-retweet',
- action: () => {
- os.api('notes/create', {
- renoteId: props.note.id
- });
- }
- }, {
- text: i18n.ts.quote,
- icon: 'fas fa-quote-right',
- action: () => {
- os.post({
- renote: props.note,
- });
- }
- }], buttonRef.value, {
- viaKeyboard
+const renote = (viaKeyboard = false) => {
+ pleaseLogin();
+ os.popupMenu([{
+ text: i18n.ts.renote,
+ icon: 'fas fa-retweet',
+ action: () => {
+ os.api('notes/create', {
+ renoteId: props.note.id,
});
- };
-
- return {
- buttonRef,
- canRenote,
- renote,
- };
- },
-});
+ },
+ }, {
+ text: i18n.ts.quote,
+ icon: 'fas fa-quote-right',
+ action: () => {
+ os.post({
+ renote: props.note,
+ });
+ },
+ }], buttonRef.value, {
+ viaKeyboard,
+ });
+};
</script>
<style lang="scss" scoped>
diff --git a/packages/client/src/components/signin-dialog.vue b/packages/client/src/components/signin-dialog.vue
index 848b11fada..ec68668a7f 100644
--- a/packages/client/src/components/signin-dialog.vue
+++ b/packages/client/src/components/signin-dialog.vue
@@ -1,11 +1,12 @@
<template>
-<XModalWindow ref="dialog"
+<XModalWindow
+ ref="dialog"
:width="370"
:height="400"
@close="onClose"
@closed="emit('closed')"
>
- <template #header>{{ $ts.login }}</template>
+ <template #header>{{ i18n.ts.login }}</template>
<MkSignin :auto-set="autoSet" :message="message" @login="onLogin"/>
</XModalWindow>
@@ -13,15 +14,16 @@
<script lang="ts" setup>
import { } from 'vue';
-import XModalWindow from '@/components/ui/modal-window.vue';
import MkSignin from './signin.vue';
+import XModalWindow from '@/components/ui/modal-window.vue';
+import { i18n } from '@/i18n';
const props = withDefaults(defineProps<{
autoSet?: boolean;
message?: string,
}>(), {
autoSet: false,
- message: ''
+ message: '',
});
const emit = defineEmits<{
diff --git a/packages/client/src/components/signup-dialog.vue b/packages/client/src/components/signup-dialog.vue
index 6dad9257a4..c5f933f6b3 100644
--- a/packages/client/src/components/signup-dialog.vue
+++ b/packages/client/src/components/signup-dialog.vue
@@ -1,11 +1,12 @@
<template>
-<XModalWindow ref="dialog"
+<XModalWindow
+ ref="dialog"
:width="366"
:height="500"
@close="dialog.close()"
@closed="$emit('closed')"
>
- <template #header>{{ $ts.signup }}</template>
+ <template #header>{{ i18n.ts.signup }}</template>
<div class="_monolithic_">
<div class="_section">
@@ -17,8 +18,9 @@
<script lang="ts" setup>
import { } from 'vue';
-import XModalWindow from '@/components/ui/modal-window.vue';
import XSignup from './signup.vue';
+import XModalWindow from '@/components/ui/modal-window.vue';
+import { i18n } from '@/i18n';
const props = withDefaults(defineProps<{
autoSet?: boolean;
diff --git a/packages/client/src/components/signup.vue b/packages/client/src/components/signup.vue
index c35d65d5de..f8e39985bc 100644
--- a/packages/client/src/components/signup.vue
+++ b/packages/client/src/components/signup.vue
@@ -1,65 +1,65 @@
<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>
- <template #label>{{ $ts.invitationCode }}</template>
+ <template #label>{{ i18n.ts.invitationCode }}</template>
<template #prefix><i class="fas fa-key"></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>{{ $ts.username }} <div v-tooltip:dialog="$ts.usernameInfo" class="_button _help"><i class="far fa-question-circle"></i></div></template>
+ <template #label>{{ i18n.ts.username }} <div v-tooltip:dialog="i18n.ts.usernameInfo" class="_button _help"><i class="far fa-question-circle"></i></div></template>
<template #prefix>@</template>
<template #suffix>@{{ host }}</template>
<template #caption>
- <span v-if="usernameState === 'wait'" style="color:#999"><i class="fas fa-spinner fa-pulse fa-fw"></i> {{ $ts.checking }}</span>
- <span v-else-if="usernameState === 'ok'" style="color: var(--success)"><i class="fas fa-check fa-fw"></i> {{ $ts.available }}</span>
- <span v-else-if="usernameState === 'unavailable'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.unavailable }}</span>
- <span v-else-if="usernameState === 'error'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.error }}</span>
- <span v-else-if="usernameState === 'invalid-format'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.usernameInvalidFormat }}</span>
- <span v-else-if="usernameState === 'min-range'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.tooShort }}</span>
- <span v-else-if="usernameState === 'max-range'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.tooLong }}</span>
+ <span v-if="usernameState === 'wait'" style="color:#999"><i class="fas fa-spinner fa-pulse fa-fw"></i> {{ i18n.ts.checking }}</span>
+ <span v-else-if="usernameState === 'ok'" style="color: var(--success)"><i class="fas fa-check fa-fw"></i> {{ i18n.ts.available }}</span>
+ <span v-else-if="usernameState === 'unavailable'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ i18n.ts.unavailable }}</span>
+ <span v-else-if="usernameState === 'error'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ i18n.ts.error }}</span>
+ <span v-else-if="usernameState === 'invalid-format'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ i18n.ts.usernameInvalidFormat }}</span>
+ <span v-else-if="usernameState === 'min-range'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ i18n.ts.tooShort }}</span>
+ <span v-else-if="usernameState === 'max-range'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></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>{{ $ts.emailAddress }} <div v-tooltip:dialog="$ts._signup.emailAddressInfo" class="_button _help"><i class="far fa-question-circle"></i></div></template>
+ <template #label>{{ i18n.ts.emailAddress }} <div v-tooltip:dialog="i18n.ts._signup.emailAddressInfo" class="_button _help"><i class="far fa-question-circle"></i></div></template>
<template #prefix><i class="fas fa-envelope"></i></template>
<template #caption>
- <span v-if="emailState === 'wait'" style="color:#999"><i class="fas fa-spinner fa-pulse fa-fw"></i> {{ $ts.checking }}</span>
- <span v-else-if="emailState === 'ok'" style="color: var(--success)"><i class="fas fa-check fa-fw"></i> {{ $ts.available }}</span>
- <span v-else-if="emailState === 'unavailable:used'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts._emailUnavailable.used }}</span>
- <span v-else-if="emailState === 'unavailable:format'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts._emailUnavailable.format }}</span>
- <span v-else-if="emailState === 'unavailable:disposable'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts._emailUnavailable.disposable }}</span>
- <span v-else-if="emailState === 'unavailable:mx'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts._emailUnavailable.mx }}</span>
- <span v-else-if="emailState === 'unavailable:smtp'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts._emailUnavailable.smtp }}</span>
- <span v-else-if="emailState === 'unavailable'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.unavailable }}</span>
- <span v-else-if="emailState === 'error'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.error }}</span>
+ <span v-if="emailState === 'wait'" style="color:#999"><i class="fas fa-spinner fa-pulse fa-fw"></i> {{ i18n.ts.checking }}</span>
+ <span v-else-if="emailState === 'ok'" style="color: var(--success)"><i class="fas fa-check fa-fw"></i> {{ i18n.ts.available }}</span>
+ <span v-else-if="emailState === 'unavailable:used'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ i18n.ts._emailUnavailable.used }}</span>
+ <span v-else-if="emailState === 'unavailable:format'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ i18n.ts._emailUnavailable.format }}</span>
+ <span v-else-if="emailState === 'unavailable:disposable'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ i18n.ts._emailUnavailable.disposable }}</span>
+ <span v-else-if="emailState === 'unavailable:mx'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ i18n.ts._emailUnavailable.mx }}</span>
+ <span v-else-if="emailState === 'unavailable:smtp'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ i18n.ts._emailUnavailable.smtp }}</span>
+ <span v-else-if="emailState === 'unavailable'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ i18n.ts.unavailable }}</span>
+ <span v-else-if="emailState === 'error'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></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>{{ $ts.password }}</template>
+ <template #label>{{ i18n.ts.password }}</template>
<template #prefix><i class="fas fa-lock"></i></template>
<template #caption>
- <span v-if="passwordStrength == 'low'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.weakPassword }}</span>
- <span v-if="passwordStrength == 'medium'" style="color: var(--warn)"><i class="fas fa-check fa-fw"></i> {{ $ts.normalPassword }}</span>
- <span v-if="passwordStrength == 'high'" style="color: var(--success)"><i class="fas fa-check fa-fw"></i> {{ $ts.strongPassword }}</span>
+ <span v-if="passwordStrength == 'low'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ i18n.ts.weakPassword }}</span>
+ <span v-if="passwordStrength == 'medium'" style="color: var(--warn)"><i class="fas fa-check fa-fw"></i> {{ i18n.ts.normalPassword }}</span>
+ <span v-if="passwordStrength == 'high'" style="color: var(--success)"><i class="fas fa-check fa-fw"></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>{{ $ts.password }} ({{ $ts.retype }})</template>
+ <template #label>{{ i18n.ts.password }} ({{ i18n.ts.retype }})</template>
<template #prefix><i class="fas fa-lock"></i></template>
<template #caption>
- <span v-if="passwordRetypeState == 'match'" style="color: var(--success)"><i class="fas fa-check fa-fw"></i> {{ $ts.passwordMatched }}</span>
- <span v-if="passwordRetypeState == 'not-match'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.passwordNotMatched }}</span>
+ <span v-if="passwordRetypeState == 'match'" style="color: var(--success)"><i class="fas fa-check fa-fw"></i> {{ i18n.ts.passwordMatched }}</span>
+ <span v-if="passwordRetypeState == 'not-match'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ i18n.ts.passwordNotMatched }}</span>
</template>
</MkInput>
<MkSwitch v-if="instance.tosUrl" v-model="ToSAgreement" class="_formBlock tou">
- <I18n :src="$ts.agreeTo">
+ <I18n :src="i18n.ts.agreeTo">
<template #0>
- <a :href="instance.tosUrl" class="_link" target="_blank">{{ $ts.tos }}</a>
+ <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>{{ $ts.start }}</MkButton>
+ <MkButton class="_formBlock" type="submit" :disabled="shouldDisableSubmitting" gradate data-cy-signup-submit>{{ i18n.ts.start }}</MkButton>
</form>
</template>
diff --git a/packages/client/src/components/sparkle.vue b/packages/client/src/components/sparkle.vue
index b52dbe31c4..cdeaf9c417 100644
--- a/packages/client/src/components/sparkle.vue
+++ b/packages/client/src/components/sparkle.vue
@@ -63,63 +63,51 @@
</span>
</template>
-<script lang="ts">
-import { defineComponent, onMounted, onUnmounted, ref } from 'vue';
-import * as os from '@/os';
+<script lang="ts" setup>
+import { onMounted, onUnmounted, ref } from 'vue';
-export default defineComponent({
- setup() {
- const particles = ref([]);
- const el = ref<HTMLElement>();
- const width = ref(0);
- const height = ref(0);
- const colors = ['#FF1493', '#00FFFF', '#FFE202', '#FFE202', '#FFE202'];
- let stop = false;
- let ro: ResizeObserver | undefined;
+const particles = ref([]);
+const el = ref<HTMLElement>();
+const width = ref(0);
+const height = ref(0);
+const colors = ['#FF1493', '#00FFFF', '#FFE202', '#FFE202', '#FFE202'];
+let stop = false;
+let ro: ResizeObserver | undefined;
- onMounted(() => {
- ro = new ResizeObserver((entries, observer) => {
- width.value = el.value?.offsetWidth + 64;
- height.value = el.value?.offsetHeight + 64;
- });
- ro.observe(el.value);
- const add = () => {
- if (stop) return;
- const x = (Math.random() * (width.value - 64));
- const y = (Math.random() * (height.value - 64));
- const sizeFactor = Math.random();
- const particle = {
- id: Math.random().toString(),
- x,
- y,
- size: 0.2 + ((sizeFactor / 10) * 3),
- dur: 1000 + (sizeFactor * 1000),
- color: colors[Math.floor(Math.random() * colors.length)],
- };
- particles.value.push(particle);
- window.setTimeout(() => {
- particles.value = particles.value.filter(x => x.id !== particle.id);
- }, particle.dur - 100);
+onMounted(() => {
+ ro = new ResizeObserver((entries, observer) => {
+ width.value = el.value?.offsetWidth + 64;
+ height.value = el.value?.offsetHeight + 64;
+ });
+ ro.observe(el.value);
+ const add = () => {
+ if (stop) return;
+ const x = (Math.random() * (width.value - 64));
+ const y = (Math.random() * (height.value - 64));
+ const sizeFactor = Math.random();
+ const particle = {
+ id: Math.random().toString(),
+ x,
+ y,
+ size: 0.2 + ((sizeFactor / 10) * 3),
+ dur: 1000 + (sizeFactor * 1000),
+ color: colors[Math.floor(Math.random() * colors.length)],
+ };
+ particles.value.push(particle);
+ window.setTimeout(() => {
+ particles.value = particles.value.filter(x => x.id !== particle.id);
+ }, particle.dur - 100);
- window.setTimeout(() => {
- add();
- }, 500 + (Math.random() * 500));
- };
+ window.setTimeout(() => {
add();
- });
-
- onUnmounted(() => {
- if (ro) ro.disconnect();
- stop = true;
- });
+ }, 500 + (Math.random() * 500));
+ };
+ add();
+});
- return {
- el,
- width,
- height,
- particles,
- };
- },
+onUnmounted(() => {
+ if (ro) ro.disconnect();
+ stop = true;
});
</script>
diff --git a/packages/client/src/components/sub-note-content.vue b/packages/client/src/components/sub-note-content.vue
index d6a37d07be..25ab883f40 100644
--- a/packages/client/src/components/sub-note-content.vue
+++ b/packages/client/src/components/sub-note-content.vue
@@ -1,8 +1,8 @@
<template>
<div class="wrmlmaau" :class="{ collapsed }">
<div class="body">
- <span v-if="note.isHidden" style="opacity: 0.5">({{ $ts.private }})</span>
- <span v-if="note.deletedAt" style="opacity: 0.5">({{ $ts.deleted }})</span>
+ <span v-if="note.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span>
+ <span v-if="note.deletedAt" style="opacity: 0.5">({{ i18n.ts.deleted }})</span>
<MkA v-if="note.replyId" class="reply" :to="`/notes/${note.replyId}`"><i class="fas fa-reply"></i></MkA>
<Mfm v-if="note.text" :text="note.text" :author="note.user" :i="$i" :custom-emojis="note.emojis"/>
<MkA v-if="note.renoteId" class="rp" :to="`/notes/${note.renoteId}`">RN: ...</MkA>
@@ -12,20 +12,21 @@
<XMediaList :media-list="note.files"/>
</details>
<details v-if="note.poll">
- <summary>{{ $ts.poll }}</summary>
+ <summary>{{ i18n.ts.poll }}</summary>
<XPoll :note="note"/>
</details>
<button v-if="collapsed" class="fade _button" @click="collapsed = false">
- <span>{{ $ts.showMore }}</span>
+ <span>{{ i18n.ts.showMore }}</span>
</button>
</div>
</template>
<script lang="ts" setup>
import { } from 'vue';
+import * as misskey from 'misskey-js';
import XPoll from './poll.vue';
import XMediaList from './media-list.vue';
-import * as misskey from 'misskey-js';
+import { i18n } from '@/i18n';
const props = defineProps<{
note: misskey.entities.Note;
diff --git a/packages/client/src/components/tab.vue b/packages/client/src/components/tab.vue
index c629727358..669e9e2e11 100644
--- a/packages/client/src/components/tab.vue
+++ b/packages/client/src/components/tab.vue
@@ -18,13 +18,13 @@ export default defineComponent({
disabled: this.modelValue === option.props.value,
onClick: () => {
this.$emit('update:modelValue', option.props.value);
- }
+ },
}, option.children), [
- [resolveDirective('click-anime')]
+ [resolveDirective('click-anime')],
]))), [
- [resolveDirective('size'), { max: [500] }]
+ [resolveDirective('size'), { max: [500] }],
]);
- }
+ },
});
</script>
diff --git a/packages/client/src/components/tag-cloud.vue b/packages/client/src/components/tag-cloud.vue
index bbebff497f..2dfd26edb0 100644
--- a/packages/client/src/components/tag-cloud.vue
+++ b/packages/client/src/components/tag-cloud.vue
@@ -25,23 +25,25 @@ let tagsEl = $ref<HTMLElement | null>(null);
let width = $ref(300);
watch($$(available), () => {
- window.TagCanvas.Start(idForCanvas, idForTags, {
- textColour: '#ffffff',
- outlineColour: tinycolor(computedStyle.getPropertyValue('--accent')).toHexString(),
- outlineRadius: 10,
- initial: [-0.030, -0.010],
- frontSelect: true,
- imageRadius: 8,
- //dragControl: true,
- dragThreshold: 3,
- wheelZoom: false,
- reverse: true,
- depth: 0.5,
- maxSpeed: 0.2,
- minSpeed: 0.003,
- stretchX: 0.8,
- stretchY: 0.8,
- });
+ try {
+ window.TagCanvas.Start(idForCanvas, idForTags, {
+ textColour: '#ffffff',
+ outlineColour: tinycolor(computedStyle.getPropertyValue('--accent')).toHexString(),
+ outlineRadius: 10,
+ initial: [-0.030, -0.010],
+ frontSelect: true,
+ imageRadius: 8,
+ //dragControl: true,
+ dragThreshold: 3,
+ wheelZoom: false,
+ reverse: true,
+ depth: 0.5,
+ maxSpeed: 0.2,
+ minSpeed: 0.003,
+ stretchX: 0.8,
+ stretchY: 0.8,
+ });
+ } catch (err) {}
});
onMounted(() => {
@@ -58,7 +60,7 @@ onMounted(() => {
});
onBeforeUnmount(() => {
- window.TagCanvas.Delete(idForCanvas);
+ if (window.TagCanvas) window.TagCanvas.Delete(idForCanvas);
});
defineExpose({
diff --git a/packages/client/src/components/ui/button.vue b/packages/client/src/components/ui/button.vue
index d3a4b5ea92..350629bf08 100644
--- a/packages/client/src/components/ui/button.vue
+++ b/packages/client/src/components/ui/button.vue
@@ -141,7 +141,7 @@ export default defineComponent({
display: block;
min-width: 100px;
width: max-content;
- padding: 8px 14px;
+ padding: 8px 16px;
text-align: center;
font-weight: normal;
font-size: 1em;
diff --git a/packages/client/src/components/ui/menu.vue b/packages/client/src/components/ui/menu.vue
index 6d1a2cc770..60b68954d6 100644
--- a/packages/client/src/components/ui/menu.vue
+++ b/packages/client/src/components/ui/menu.vue
@@ -46,7 +46,7 @@
</button>
</template>
<span v-if="items2.length === 0" class="none item">
- <span>{{ $ts.none }}</span>
+ <span>{{ i18n.ts.none }}</span>
</span>
</div>
<div v-if="childMenu" class="child">
@@ -61,6 +61,8 @@ import { focusPrev, focusNext } from '@/scripts/focus';
import FormSwitch from '@/components/form/switch.vue';
import { MenuItem, InnerMenuItem, MenuPending, MenuAction } from '@/types/menu';
import * as os from '@/os';
+import { i18n } from '@/i18n';
+
const XChild = defineAsyncComponent(() => import('./menu.child.vue'));
const props = defineProps<{
@@ -335,6 +337,9 @@ onBeforeUnmount(() => {
&.asDrawer {
padding: 12px 0 calc(env(safe-area-inset-bottom, 0px) + 12px) 0;
width: 100%;
+ border-radius: 24px;
+ border-bottom-right-radius: 0;
+ border-bottom-left-radius: 0;
> .item {
font-size: 1em;
diff --git a/packages/client/src/components/ui/pagination.vue b/packages/client/src/components/ui/pagination.vue
index a03c2b3a1d..7650c5b33a 100644
--- a/packages/client/src/components/ui/pagination.vue
+++ b/packages/client/src/components/ui/pagination.vue
@@ -8,7 +8,7 @@
<slot name="empty">
<div class="_fullinfo">
<img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/>
- <div>{{ $ts.nothing }}</div>
+ <div>{{ i18n.ts.nothing }}</div>
</div>
</slot>
</div>
@@ -16,14 +16,14 @@
<div v-else ref="rootEl">
<div v-show="pagination.reversed && more" key="_more_" class="cxiknjgy _gap">
<MkButton v-if="!moreFetching" class="button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" primary @click="fetchMoreAhead">
- {{ $ts.loadMore }}
+ {{ i18n.ts.loadMore }}
</MkButton>
<MkLoading v-else class="loading"/>
</div>
<slot :items="items"></slot>
<div v-show="!pagination.reversed && more" key="_more_" class="cxiknjgy _gap">
<MkButton v-if="!moreFetching" v-appear="($store.state.enableInfiniteScroll && !disableAutoLoad) ? fetchMore : null" class="button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" primary @click="fetchMore">
- {{ $ts.loadMore }}
+ {{ i18n.ts.loadMore }}
</MkButton>
<MkLoading v-else class="loading"/>
</div>
@@ -37,6 +37,7 @@ import * as misskey from 'misskey-js';
import * as os from '@/os';
import { onScrollTop, isTopVisible, getScrollPosition, getScrollContainer } from '@/scripts/scroll';
import MkButton from '@/components/ui/button.vue';
+import { i18n } from '@/i18n';
const SECOND_FETCH_LIMIT = 30;
@@ -196,21 +197,23 @@ const prepend = (item: Item): void => {
if (props.pagination.reversed) {
if (rootEl.value) {
const container = getScrollContainer(rootEl.value);
- if (container == null) return; // TODO?
-
- const pos = getScrollPosition(rootEl.value);
- const viewHeight = container.clientHeight;
- const height = container.scrollHeight;
- const isBottom = (pos + viewHeight > height - 32);
- if (isBottom) {
- // オーバーフローしたら古いアイテムは捨てる
- if (items.value.length >= props.displayLimit) {
- // このやり方だとVue 3.2以降アニメーションが動かなくなる
- //items.value = items.value.slice(-props.displayLimit);
- while (items.value.length >= props.displayLimit) {
- items.value.shift();
+ if (container == null) {
+ // TODO?
+ } else {
+ const pos = getScrollPosition(rootEl.value);
+ const viewHeight = container.clientHeight;
+ const height = container.scrollHeight;
+ const isBottom = (pos + viewHeight > height - 32);
+ if (isBottom) {
+ // オーバーフローしたら古いアイテムは捨てる
+ if (items.value.length >= props.displayLimit) {
+ // このやり方だとVue 3.2以降アニメーションが動かなくなる
+ //items.value = items.value.slice(-props.displayLimit);
+ while (items.value.length >= props.displayLimit) {
+ items.value.shift();
+ }
+ more.value = true;
}
- more.value = true;
}
}
}
diff --git a/packages/client/src/components/ui/window.vue b/packages/client/src/components/ui/window.vue
index 460cf7d597..758d4d47b6 100644
--- a/packages/client/src/components/ui/window.vue
+++ b/packages/client/src/components/ui/window.vue
@@ -170,6 +170,7 @@ function onHeaderMousedown(evt: MouseEvent) {
beforeClickedAt = Date.now();
const main = rootEl;
+ if (main == null) return;
if (!contains(main, document.activeElement)) main.focus();
diff --git a/packages/client/src/components/updated.vue b/packages/client/src/components/updated.vue
index 375ac0dbbb..1c1e5f4aed 100644
--- a/packages/client/src/components/updated.vue
+++ b/packages/client/src/components/updated.vue
@@ -1,10 +1,10 @@
<template>
<MkModal ref="modal" :z-priority="'middle'" @click="$refs.modal.close()" @closed="$emit('closed')">
<div class="ewlycnyt">
- <div class="title"><MkSparkle>{{ $ts.misskeyUpdated }}</MkSparkle></div>
+ <div class="title"><MkSparkle>{{ i18n.ts.misskeyUpdated }}</MkSparkle></div>
<div class="version">✨{{ version }}🚀</div>
- <MkButton full @click="whatIsNew">{{ $ts.whatIsNew }}</MkButton>
- <MkButton class="gotIt" primary full @click="$refs.modal.close()">{{ $ts.gotIt }}</MkButton>
+ <MkButton full @click="whatIsNew">{{ i18n.ts.whatIsNew }}</MkButton>
+ <MkButton class="gotIt" primary full @click="$refs.modal.close()">{{ i18n.ts.gotIt }}</MkButton>
</div>
</MkModal>
</template>
@@ -15,8 +15,9 @@ import MkModal from '@/components/ui/modal.vue';
import MkButton from '@/components/ui/button.vue';
import MkSparkle from '@/components/sparkle.vue';
import { version } from '@/config';
+import { i18n } from '@/i18n';
-const modal = ref();
+const modal = ref<InstanceType<typeof MkModal>>();
const whatIsNew = () => {
modal.value.close();
diff --git a/packages/client/src/components/url-preview.vue b/packages/client/src/components/url-preview.vue
index e15d28a382..df4b0e53b8 100644
--- a/packages/client/src/components/url-preview.vue
+++ b/packages/client/src/components/url-preview.vue
@@ -1,6 +1,6 @@
<template>
<div v-if="playerEnabled" class="player" :style="`padding: ${(player.height || 0) / (player.width || 1) * 100}% 0 0`">
- <button class="disablePlayer" :title="$ts.disablePlayer" @click="playerEnabled = false"><i class="fas fa-times"></i></button>
+ <button class="disablePlayer" :title="i18n.ts.disablePlayer" @click="playerEnabled = false"><i class="fas fa-times"></i></button>
<iframe :src="player.url + (player.url.match(/\?/) ? '&autoplay=1&auto_play=1' : '?autoplay=1&auto_play=1')" :width="player.width || '100%'" :heigth="player.height || 250" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen/>
</div>
<div v-else-if="tweetId && tweetExpanded" ref="twitter" class="twitter">
@@ -10,7 +10,7 @@
<transition :name="$store.state.animation ? 'zoom' : ''" mode="out-in">
<component :is="self ? 'MkA' : 'a'" v-if="!fetching" class="link" :class="{ compact }" :[attr]="self ? url.substr(local.length) : url" rel="nofollow noopener" :target="target" :title="url">
<div v-if="thumbnail" class="thumbnail" :style="`background-image: url('${thumbnail}')`">
- <button v-if="!playerEnabled && player.url" class="_button" :title="$ts.enablePlayer" @click.prevent="playerEnabled = true"><i class="fas fa-play-circle"></i></button>
+ <button v-if="!playerEnabled && player.url" class="_button" :title="i18n.ts.enablePlayer" @click.prevent="playerEnabled = true"><i class="fas fa-play-circle"></i></button>
</div>
<article>
<header>
@@ -26,7 +26,7 @@
</transition>
<div v-if="tweetId" class="expandTweet">
<a @click="tweetExpanded = true">
- <i class="fab fa-twitter"></i> {{ $ts.expandTweet }}
+ <i class="fab fa-twitter"></i> {{ i18n.ts.expandTweet }}
</a>
</div>
</div>
@@ -35,6 +35,7 @@
<script lang="ts" setup>
import { onMounted, onUnmounted } from 'vue';
import { url as local, lang } from '@/config';
+import { i18n } from '@/i18n';
const props = withDefaults(defineProps<{
url: string;
diff --git a/packages/client/src/components/user-info.vue b/packages/client/src/components/user-info.vue
index 6a25d412fc..1cd275a6df 100644
--- a/packages/client/src/components/user-info.vue
+++ b/packages/client/src/components/user-info.vue
@@ -10,17 +10,17 @@
<div v-if="user.description" class="mfm">
<Mfm :text="user.description" :author="user" :i="$i" :custom-emojis="user.emojis"/>
</div>
- <span v-else style="opacity: 0.7;">{{ $ts.noAccountDescription }}</span>
+ <span v-else style="opacity: 0.7;">{{ i18n.ts.noAccountDescription }}</span>
</div>
<div class="status">
<div>
- <p>{{ $ts.notes }}</p><span>{{ user.notesCount }}</span>
+ <p>{{ i18n.ts.notes }}</p><span>{{ user.notesCount }}</span>
</div>
<div>
- <p>{{ $ts.following }}</p><span>{{ user.followingCount }}</span>
+ <p>{{ i18n.ts.following }}</p><span>{{ user.followingCount }}</span>
</div>
<div>
- <p>{{ $ts.followers }}</p><span>{{ user.followersCount }}</span>
+ <p>{{ i18n.ts.followers }}</p><span>{{ user.followersCount }}</span>
</div>
</div>
<MkFollowButton v-if="$i && user.id != $i.id" class="koudoku-button" :user="user" mini/>
@@ -31,6 +31,7 @@
import * as misskey from 'misskey-js';
import MkFollowButton from './follow-button.vue';
import { userPage } from '@/filters/user';
+import { i18n } from '@/i18n';
defineProps<{
user: misskey.entities.UserDetailed;
diff --git a/packages/client/src/components/user-list.vue b/packages/client/src/components/user-list.vue
index 3e273721c7..fe30d371fe 100644
--- a/packages/client/src/components/user-list.vue
+++ b/packages/client/src/components/user-list.vue
@@ -3,7 +3,7 @@
<template #empty>
<div class="_fullinfo">
<img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/>
- <div>{{ $ts.noUsers }}</div>
+ <div>{{ i18n.ts.noUsers }}</div>
</div>
</template>
@@ -18,9 +18,9 @@
<script lang="ts" setup>
import { ref } from 'vue';
import MkUserInfo from '@/components/user-info.vue';
-import MkPagination from '@/components/ui/pagination.vue';
-import { Paging } from '@/components/ui/pagination.vue';
+import MkPagination, { Paging } from '@/components/ui/pagination.vue';
import { userPage } from '@/filters/user';
+import { i18n } from '@/i18n';
const props = defineProps<{
pagination: Paging;
diff --git a/packages/client/src/components/user-select-dialog.vue b/packages/client/src/components/user-select-dialog.vue
index 972d353486..4d8e427a72 100644
--- a/packages/client/src/components/user-select-dialog.vue
+++ b/packages/client/src/components/user-select-dialog.vue
@@ -1,5 +1,6 @@
<template>
-<XModalWindow ref="dialogEl"
+<XModalWindow
+ ref="dialogEl"
:with-ok-button="true"
:ok-button-disabled="selected == null"
@click="cancel()"
@@ -7,16 +8,16 @@
@ok="ok()"
@closed="$emit('closed')"
>
- <template #header>{{ $ts.selectUser }}</template>
+ <template #header>{{ i18n.ts.selectUser }}</template>
<div class="tbhwbxda">
<div class="form">
<FormSplit :min-width="170">
<MkInput v-model="username" :autofocus="true" @update:modelValue="search">
- <template #label>{{ $ts.username }}</template>
+ <template #label>{{ i18n.ts.username }}</template>
<template #prefix>@</template>
</MkInput>
<MkInput v-model="host" @update:modelValue="search">
- <template #label>{{ $ts.host }}</template>
+ <template #label>{{ i18n.ts.host }}</template>
<template #prefix>@</template>
</MkInput>
</FormSplit>
@@ -32,7 +33,7 @@
</div>
</div>
<div v-else class="empty">
- <span>{{ $ts.noUsers }}</span>
+ <span>{{ i18n.ts.noUsers }}</span>
</div>
</div>
<div v-if="username == '' && host == ''" class="recent">
@@ -58,6 +59,7 @@ import FormSplit from '@/components/form/split.vue';
import XModalWindow from '@/components/ui/modal-window.vue';
import * as os from '@/os';
import { defaultStore } from '@/store';
+import { i18n } from '@/i18n';
const emit = defineEmits<{
(ev: 'ok', selected: misskey.entities.UserDetailed): void;
@@ -81,7 +83,7 @@ const search = () => {
username: username,
host: host,
limit: 10,
- detail: false
+ detail: false,
}).then(_users => {
users = _users;
});
diff --git a/packages/client/src/components/visibility-picker.vue b/packages/client/src/components/visibility-picker.vue
index 7fe55858cc..f4830cd2c6 100644
--- a/packages/client/src/components/visibility-picker.vue
+++ b/packages/client/src/components/visibility-picker.vue
@@ -4,37 +4,37 @@
<button key="public" class="_button" :class="{ active: v === 'public' }" data-index="1" @click="choose('public')">
<div><i class="fas fa-globe"></i></div>
<div>
- <span>{{ $ts._visibility.public }}</span>
- <span>{{ $ts._visibility.publicDescription }}</span>
+ <span>{{ i18n.ts._visibility.public }}</span>
+ <span>{{ i18n.ts._visibility.publicDescription }}</span>
</div>
</button>
<button key="home" class="_button" :class="{ active: v === 'home' }" data-index="2" @click="choose('home')">
<div><i class="fas fa-home"></i></div>
<div>
- <span>{{ $ts._visibility.home }}</span>
- <span>{{ $ts._visibility.homeDescription }}</span>
+ <span>{{ i18n.ts._visibility.home }}</span>
+ <span>{{ i18n.ts._visibility.homeDescription }}</span>
</div>
</button>
<button key="followers" class="_button" :class="{ active: v === 'followers' }" data-index="3" @click="choose('followers')">
<div><i class="fas fa-unlock"></i></div>
<div>
- <span>{{ $ts._visibility.followers }}</span>
- <span>{{ $ts._visibility.followersDescription }}</span>
+ <span>{{ i18n.ts._visibility.followers }}</span>
+ <span>{{ i18n.ts._visibility.followersDescription }}</span>
</div>
</button>
<button key="specified" :disabled="localOnly" class="_button" :class="{ active: v === 'specified' }" data-index="4" @click="choose('specified')">
<div><i class="fas fa-envelope"></i></div>
<div>
- <span>{{ $ts._visibility.specified }}</span>
- <span>{{ $ts._visibility.specifiedDescription }}</span>
+ <span>{{ i18n.ts._visibility.specified }}</span>
+ <span>{{ i18n.ts._visibility.specifiedDescription }}</span>
</div>
</button>
<div class="divider"></div>
<button key="localOnly" class="_button localOnly" :class="{ active: localOnly }" data-index="5" @click="localOnly = !localOnly">
<div><i class="fas fa-biohazard"></i></div>
<div>
- <span>{{ $ts._visibility.localOnly }}</span>
- <span>{{ $ts._visibility.localOnlyDescription }}</span>
+ <span>{{ i18n.ts._visibility.localOnly }}</span>
+ <span>{{ i18n.ts._visibility.localOnlyDescription }}</span>
</div>
<div><i :class="localOnly ? 'fas fa-toggle-on' : 'fas fa-toggle-off'"></i></div>
</button>
@@ -46,6 +46,7 @@
import { nextTick, watch } from 'vue';
import * as misskey from 'misskey-js';
import MkModal from '@/components/ui/modal.vue';
+import { i18n } from '@/i18n';
const modal = $ref<InstanceType<typeof MkModal>>();
diff --git a/packages/client/src/components/widgets.vue b/packages/client/src/components/widgets.vue
index 0a9769e197..54d4c57af3 100644
--- a/packages/client/src/components/widgets.vue
+++ b/packages/client/src/components/widgets.vue
@@ -3,11 +3,11 @@
<template v-if="edit">
<header>
<MkSelect v-model="widgetAdderSelected" style="margin-bottom: var(--margin)" class="mk-widget-select">
- <template #label>{{ $ts.selectWidget }}</template>
+ <template #label>{{ i18n.ts.selectWidget }}</template>
<option v-for="widget in widgetDefs" :key="widget" :value="widget">{{ i18n.t(`_widgets.${widget}`) }}</option>
</MkSelect>
- <MkButton inline primary class="mk-widget-add" @click="addWidget"><i class="fas fa-plus"></i> {{ $ts.add }}</MkButton>
- <MkButton inline @click="$emit('exit')">{{ $ts.close }}</MkButton>
+ <MkButton inline primary class="mk-widget-add" @click="addWidget"><i class="fas fa-plus"></i> {{ i18n.ts.add }}</MkButton>
+ <MkButton inline @click="$emit('exit')">{{ i18n.ts.close }}</MkButton>
</header>
<XDraggable
v-model="widgets_"