Merge tag '12.118.1' into develop
This commit is contained in:
@ -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;
|
||||
});
|
||||
|
@ -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;
|
||||
|
@ -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';
|
||||
|
||||
withDefaults(defineProps<{
|
||||
thickness: number;
|
||||
// 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;
|
||||
|
||||
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>
|
||||
|
||||
|
@ -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;
|
||||
|
77
packages/client/src/components/digital-clock.vue
Normal file
77
packages/client/src/components/digital-clock.vue
Normal file
@ -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>
|
@ -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>;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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>;
|
||||
|
@ -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,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
@ -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>
|
||||
|
@ -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 onChange({ route, props: newProps, key: newKey }) {
|
||||
currentPageComponent = route.component;
|
||||
currentPageProps = newProps;
|
||||
key = newKey;
|
||||
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({ 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>
|
||||
|
@ -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 (
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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"/>
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
|
@ -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][];
|
||||
|
@ -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() {
|
||||
|
@ -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: {
|
||||
|
@ -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>
|
||||
|
@ -487,7 +487,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}>();
|
||||
|
@ -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');
|
||||
});
|
||||
|
||||
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
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
buttonRef,
|
||||
canRenote,
|
||||
renote,
|
||||
};
|
||||
},
|
||||
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,
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -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<{
|
||||
|
@ -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;
|
||||
|
@ -2,65 +2,65 @@
|
||||
<form class="qlvuhzng _formRoot" autocomplete="new-password" @submit.prevent="onSubmit">
|
||||
<div class="_formBlock">過去にこのインスタンスを訪れたことがある場合は、<a href="/flush" target="_blank" class="_link">Local Storageを削除</a>してください</div>
|
||||
<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>
|
||||
|
||||
|
@ -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);
|
||||
|
||||
window.setTimeout(() => {
|
||||
add();
|
||||
}, 500 + (Math.random() * 500));
|
||||
};
|
||||
add();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (ro) ro.disconnect();
|
||||
stop = true;
|
||||
});
|
||||
|
||||
return {
|
||||
el,
|
||||
width,
|
||||
height,
|
||||
particles,
|
||||
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));
|
||||
};
|
||||
add();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (ro) ro.disconnect();
|
||||
stop = true;
|
||||
});
|
||||
</script>
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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({
|
||||
|
@ -149,7 +149,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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
});
|
||||
|
@ -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>>();
|
||||
|
||||
|
@ -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_"
|
||||
|
Reference in New Issue
Block a user