Render all notes via Magnetar
ci/woodpecker/push/ociImagePush Pipeline was successful
Details
ci/woodpecker/push/ociImagePush Pipeline was successful
Details
This commit is contained in:
parent
76c4d0267f
commit
69cb1c5220
|
@ -11,7 +11,7 @@
|
||||||
:class="{ renote: isRenote }"
|
:class="{ renote: isRenote }"
|
||||||
:id="appearNote.id"
|
:id="appearNote.id"
|
||||||
>
|
>
|
||||||
<MkNoteSub
|
<XNoteSub
|
||||||
v-if="appearNote.parent_note && !detailedView && !collapsedReply"
|
v-if="appearNote.parent_note && !detailedView && !collapsedReply"
|
||||||
:note="appearNote.parent_note"
|
:note="appearNote.parent_note"
|
||||||
class="reply-to"
|
class="reply-to"
|
||||||
|
@ -99,7 +99,7 @@
|
||||||
<XNoteHeader class="header" :note="appearNote" />
|
<XNoteHeader class="header" :note="appearNote" />
|
||||||
</div>
|
</div>
|
||||||
<div class="body">
|
<div class="body">
|
||||||
<MkSubNoteContent
|
<XSubNoteContent
|
||||||
class="text"
|
class="text"
|
||||||
:note="appearNote"
|
:note="appearNote"
|
||||||
:detailed="true"
|
:detailed="true"
|
||||||
|
@ -108,7 +108,7 @@
|
||||||
@push="(e) => router.push(notePage(e))"
|
@push="(e) => router.push(notePage(e))"
|
||||||
@focusfooter="footerEl.focus()"
|
@focusfooter="footerEl.focus()"
|
||||||
@expanded="(e) => setPostExpanded(e)"
|
@expanded="(e) => setPostExpanded(e)"
|
||||||
></MkSubNoteContent>
|
></XSubNoteContent>
|
||||||
<div v-if="translating || translation" class="translation">
|
<div v-if="translating || translation" class="translation">
|
||||||
<MkLoading v-if="translating" mini />
|
<MkLoading v-if="translating" mini />
|
||||||
<div v-else class="translated">
|
<div v-else class="translated">
|
||||||
|
@ -229,14 +229,14 @@ import type { Ref } from "vue";
|
||||||
import { computed, inject, onMounted, ref, toRaw } from "vue";
|
import { computed, inject, onMounted, ref, toRaw } from "vue";
|
||||||
import * as mfm from "mfm-js";
|
import * as mfm from "mfm-js";
|
||||||
import type * as misskey from "calckey-js";
|
import type * as misskey from "calckey-js";
|
||||||
import MkNoteSub from "@/components/MkNoteSub.vue";
|
import XNoteSub from "@/components/MagNoteSub.vue";
|
||||||
import MkSubNoteContent from "./MkSubNoteContent.vue";
|
import XSubNoteContent from "./MagSubNoteContent.vue";
|
||||||
import XNoteHeader from "@/components/MkNoteHeader.vue";
|
import XNoteHeader from "@/components/MagNoteHeader.vue";
|
||||||
import XRenoteButton from "@/components/MkRenoteButton.vue";
|
import XRenoteButton from "@/components/MagRenoteButton.vue";
|
||||||
import XReactionsViewer from "@/components/MkReactionsViewer.vue";
|
import XReactionsViewer from "@/components/MkReactionsViewer.vue";
|
||||||
import XStarButton from "@/components/MkStarButton.vue";
|
import XStarButton from "@/components/MkStarButton.vue";
|
||||||
import XStarButtonNoEmoji from "@/components/MkStarButtonNoEmoji.vue";
|
import XStarButtonNoEmoji from "@/components/MkStarButtonNoEmoji.vue";
|
||||||
import XQuoteButton from "@/components/MkQuoteButton.vue";
|
import XQuoteButton from "@/components/MagQuoteButton.vue";
|
||||||
import MkVisibility from "@/components/MkVisibility.vue";
|
import MkVisibility from "@/components/MkVisibility.vue";
|
||||||
import copyToClipboard from "@/scripts/copy-to-clipboard";
|
import copyToClipboard from "@/scripts/copy-to-clipboard";
|
||||||
import { url } from "@/config";
|
import { url } from "@/config";
|
||||||
|
@ -298,13 +298,13 @@ if (noteViewInterruptors.length > 0) {
|
||||||
|
|
||||||
const isRenote = magIsRenote(note);
|
const isRenote = magIsRenote(note);
|
||||||
|
|
||||||
const el = ref<HTMLElement>();
|
const el = ref<HTMLElement | null>(null);
|
||||||
const footerEl = ref<HTMLElement>();
|
const footerEl = ref<HTMLElement | null>(null);
|
||||||
const menuButton = ref<HTMLElement>();
|
const menuButton = ref<HTMLElement | null>(null);
|
||||||
const starButton = ref<InstanceType<typeof XStarButton>>();
|
const starButton = ref<InstanceType<typeof XStarButton>>();
|
||||||
const renoteButton = ref<InstanceType<typeof XRenoteButton>>();
|
const renoteButton = ref<InstanceType<typeof XRenoteButton>>();
|
||||||
const renoteTime = ref<HTMLElement>();
|
const renoteTime = ref<HTMLElement | null>(null);
|
||||||
const reactButton = ref<HTMLElement>();
|
const reactButton = ref<HTMLElement | null>(null);
|
||||||
let appearNote = $computed(
|
let appearNote = $computed(
|
||||||
() => magEffectiveNote(note) as packed.PackNoteMaybeFull
|
() => magEffectiveNote(note) as packed.PackNoteMaybeFull
|
||||||
);
|
);
|
||||||
|
@ -386,7 +386,7 @@ function onContextmenu(ev: MouseEvent): void {
|
||||||
return isLink(el.parentElement);
|
return isLink(el.parentElement);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (isLink(ev.target)) return;
|
if (ev.target && isLink(ev.target as HTMLElement)) return;
|
||||||
if (window.getSelection()?.toString() !== "") return;
|
if (window.getSelection()?.toString() !== "") return;
|
||||||
|
|
||||||
if (defaultStore.state.useReactionPickerForContextMenu) {
|
if (defaultStore.state.useReactionPickerForContextMenu) {
|
||||||
|
@ -451,11 +451,11 @@ function menu(viaKeyboard = false): void {
|
||||||
note: note,
|
note: note,
|
||||||
translating,
|
translating,
|
||||||
translation,
|
translation,
|
||||||
menuButton,
|
menuButton: menuButton.value,
|
||||||
isDeleted,
|
isDeleted,
|
||||||
currentClipPage,
|
currentClipPage: currentClipPage?.value,
|
||||||
}),
|
}),
|
||||||
menuButton.value,
|
menuButton.value ?? undefined,
|
||||||
{
|
{
|
||||||
viaKeyboard,
|
viaKeyboard,
|
||||||
}
|
}
|
||||||
|
@ -478,7 +478,7 @@ function showRenoteMenu(viaKeyboard = false): void {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
renoteTime.value,
|
renoteTime.value ?? undefined,
|
||||||
{
|
{
|
||||||
viaKeyboard: viaKeyboard,
|
viaKeyboard: viaKeyboard,
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
:tabindex="!isDeleted ? '-1' : null"
|
:tabindex="!isDeleted ? '-1' : null"
|
||||||
:class="{ renote: magIsRenote(note) }"
|
:class="{ renote: magIsRenote(note) }"
|
||||||
>
|
>
|
||||||
<MkNoteSub
|
<MagNoteSub
|
||||||
v-if="conversation"
|
v-if="conversation"
|
||||||
v-for="note in conversation"
|
v-for="note in conversation"
|
||||||
:key="note.id"
|
:key="note.id"
|
||||||
|
@ -18,20 +18,20 @@
|
||||||
:detailedView="true"
|
:detailedView="true"
|
||||||
/>
|
/>
|
||||||
<MkLoading v-else-if="appearNote.parent_note_id" mini />
|
<MkLoading v-else-if="appearNote.parent_note_id" mini />
|
||||||
<MkNoteSub
|
<MagNoteSub
|
||||||
v-if="parentNote"
|
v-if="parentNote"
|
||||||
:note="parentNote"
|
:note="parentNote"
|
||||||
class="reply-to"
|
class="reply-to"
|
||||||
:detailedView="true"
|
:detailedView="true"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<MagNote
|
<XNote
|
||||||
ref="noteEl"
|
ref="noteEl"
|
||||||
@contextmenu.stop="onContextmenu"
|
@contextmenu.stop="onContextmenu"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
:note="note"
|
:note="note"
|
||||||
detailedView
|
detailedView
|
||||||
></MagNote>
|
></XNote>
|
||||||
|
|
||||||
<MkTab v-model="tab" :style="'underline'" @update:modelValue="loadTab">
|
<MkTab v-model="tab" :style="'underline'" @update:modelValue="loadTab">
|
||||||
<option value="replies">
|
<option value="replies">
|
||||||
|
@ -53,7 +53,7 @@
|
||||||
</option>
|
</option>
|
||||||
<option value="quotes" v-if="directQuotes?.length > 0">
|
<option value="quotes" v-if="directQuotes?.length > 0">
|
||||||
<!-- <i class="ph-quotes ph-bold ph-lg"></i> -->
|
<!-- <i class="ph-quotes ph-bold ph-lg"></i> -->
|
||||||
<span class="count">{{ directQuotes.length }}</span>
|
<span class="count">{{ directQuotes!.length }}</span>
|
||||||
{{ i18n.ts._notification._types.quote }}
|
{{ i18n.ts._notification._types.quote }}
|
||||||
</option>
|
</option>
|
||||||
<option value="clips" v-if="clips?.length > 0">
|
<option value="clips" v-if="clips?.length > 0">
|
||||||
|
@ -63,8 +63,8 @@
|
||||||
</option>
|
</option>
|
||||||
</MkTab>
|
</MkTab>
|
||||||
|
|
||||||
<MkNoteSub
|
<MagNoteSub
|
||||||
v-if="directReplies.length && tab === 'replies'"
|
v-if="directReplies && tab === 'replies'"
|
||||||
v-for="note in directReplies"
|
v-for="note in directReplies"
|
||||||
:key="note.id"
|
:key="note.id"
|
||||||
:note="note"
|
:note="note"
|
||||||
|
@ -75,7 +75,7 @@
|
||||||
/>
|
/>
|
||||||
<MkLoading v-else-if="tab === 'replies' && note.reply_count > 0" />
|
<MkLoading v-else-if="tab === 'replies' && note.reply_count > 0" />
|
||||||
|
|
||||||
<MkNoteSub
|
<MagNoteSub
|
||||||
v-if="directQuotes && tab === 'quotes'"
|
v-if="directQuotes && tab === 'quotes'"
|
||||||
v-for="note in directQuotes"
|
v-for="note in directQuotes"
|
||||||
:key="note.id"
|
:key="note.id"
|
||||||
|
@ -85,7 +85,7 @@
|
||||||
:detailedView="true"
|
:detailedView="true"
|
||||||
:parentId="appearNote.id"
|
:parentId="appearNote.id"
|
||||||
/>
|
/>
|
||||||
<MkLoading v-else-if="tab === 'quotes' && directQuotes.length > 0" />
|
<MkLoading v-else-if="tab === 'quotes' && directQuotes?.length > 0" />
|
||||||
|
|
||||||
<!-- <MkPagination
|
<!-- <MkPagination
|
||||||
v-if="tab === 'renotes'"
|
v-if="tab === 'renotes'"
|
||||||
|
@ -152,11 +152,12 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, onUnmounted, onUpdated, ref, toRaw, watch } from "vue";
|
import { onMounted, onUnmounted, onUpdated, Ref, ref, toRaw, watch } from "vue";
|
||||||
import * as misskey from "calckey-js";
|
import * as misskey from "calckey-js";
|
||||||
import MkTab from "@/components/MkTab.vue";
|
import MkTab from "@/components/MkTab.vue";
|
||||||
import MkNoteSub from "@/components/MkNoteSub.vue";
|
import MagNoteSub from "@/components/MagNoteSub.vue";
|
||||||
import XRenoteButton from "@/components/MkRenoteButton.vue";
|
import XNote from "@/components/MagNote.vue";
|
||||||
|
import XRenoteButton from "@/components/MagRenoteButton.vue";
|
||||||
import MkUserCardMini from "@/components/MkUserCardMini.vue";
|
import MkUserCardMini from "@/components/MkUserCardMini.vue";
|
||||||
import MkReactedUsers from "@/components/MkReactedUsers.vue";
|
import MkReactedUsers from "@/components/MkReactedUsers.vue";
|
||||||
import { pleaseLogin } from "@/scripts/please-login";
|
import { pleaseLogin } from "@/scripts/please-login";
|
||||||
|
@ -177,8 +178,9 @@ import {
|
||||||
magIsRenote,
|
magIsRenote,
|
||||||
magReactionCount,
|
magReactionCount,
|
||||||
magReactionToLegacy,
|
magReactionToLegacy,
|
||||||
|
resolveNote,
|
||||||
} from "@/scripts-mag/mag-util";
|
} from "@/scripts-mag/mag-util";
|
||||||
import MagNote from "@/components/MagNote.vue";
|
import { shallowRef } from "@vue/runtime-core";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
note: packed.PackNoteMaybeFull;
|
note: packed.PackNoteMaybeFull;
|
||||||
|
@ -210,11 +212,11 @@ if (noteViewInterruptors.length > 0) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const el = ref<HTMLElement>();
|
const el = ref<HTMLElement | null>(null);
|
||||||
const noteEl = $ref();
|
const noteEl = ref<HTMLElement | null>(null);
|
||||||
const menuButton = ref<HTMLElement>();
|
const menuButton = ref<HTMLElement | null>(null);
|
||||||
const renoteButton = ref<InstanceType<typeof XRenoteButton>>();
|
const renoteButton = ref<InstanceType<typeof XRenoteButton>>();
|
||||||
const reactButton = ref<HTMLElement>();
|
const reactButton = ref<HTMLElement | null>(null);
|
||||||
let appearNote = $computed(
|
let appearNote = $computed(
|
||||||
() => magEffectiveNote(note) as packed.PackNoteMaybeFull
|
() => magEffectiveNote(note) as packed.PackNoteMaybeFull
|
||||||
);
|
);
|
||||||
|
@ -226,12 +228,12 @@ const muted = ref(
|
||||||
);
|
);
|
||||||
const translation = ref(null);
|
const translation = ref(null);
|
||||||
const translating = ref(false);
|
const translating = ref(false);
|
||||||
let conversation = $ref<null | misskey.entities.Note[]>([]);
|
let conversation = shallowRef<packed.PackNoteMaybeFull[]>([]);
|
||||||
const replies = ref<misskey.entities.Note[]>([]);
|
const replies = shallowRef<packed.PackNoteMaybeFull[]>([]);
|
||||||
let directReplies = $ref<misskey.entities.Note[]>([]);
|
let directReplies = shallowRef<packed.PackNoteMaybeFull[] | null>(null);
|
||||||
let directQuotes = $ref<null | misskey.entities.Note[]>([]);
|
let directQuotes = shallowRef<packed.PackNoteMaybeFull[] | null>(null);
|
||||||
let clips = $ref();
|
let clips = ref<misskey.entities.Clip[]>([]);
|
||||||
let renotes = $ref();
|
let renotes = ref<misskey.entities.Note[] | null>();
|
||||||
let isScrolling;
|
let isScrolling;
|
||||||
|
|
||||||
const keymap = {
|
const keymap = {
|
||||||
|
@ -244,7 +246,7 @@ const keymap = {
|
||||||
};
|
};
|
||||||
|
|
||||||
useNoteCapture({
|
useNoteCapture({
|
||||||
rootEl: el,
|
rootEl: el as Ref<HTMLElement>,
|
||||||
note: $$(appearNote),
|
note: $$(appearNote),
|
||||||
isDeletedRef: isDeleted,
|
isDeletedRef: isDeleted,
|
||||||
});
|
});
|
||||||
|
@ -283,7 +285,7 @@ function onContextmenu(ev: MouseEvent): void {
|
||||||
return isLink(el.parentElement);
|
return isLink(el.parentElement);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (isLink(ev.target)) return;
|
if (ev.target && isLink(ev.target as HTMLElement)) return;
|
||||||
if (window.getSelection()?.toString() !== "") return;
|
if (window.getSelection()?.toString() !== "") return;
|
||||||
|
|
||||||
if (defaultStore.state.useReactionPickerForContextMenu) {
|
if (defaultStore.state.useReactionPickerForContextMenu) {
|
||||||
|
@ -295,7 +297,7 @@ function onContextmenu(ev: MouseEvent): void {
|
||||||
note: note,
|
note: note,
|
||||||
translating,
|
translating,
|
||||||
translation,
|
translation,
|
||||||
menuButton,
|
menuButton: menuButton.value,
|
||||||
isDeleted,
|
isDeleted,
|
||||||
}),
|
}),
|
||||||
ev
|
ev
|
||||||
|
@ -309,10 +311,10 @@ function menu(viaKeyboard = false): void {
|
||||||
note: note,
|
note: note,
|
||||||
translating,
|
translating,
|
||||||
translation,
|
translation,
|
||||||
menuButton,
|
menuButton: menuButton.value,
|
||||||
isDeleted,
|
isDeleted,
|
||||||
}),
|
}),
|
||||||
menuButton.value,
|
menuButton.value ?? undefined,
|
||||||
{
|
{
|
||||||
viaKeyboard,
|
viaKeyboard,
|
||||||
}
|
}
|
||||||
|
@ -320,11 +322,11 @@ function menu(viaKeyboard = false): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
function focus() {
|
function focus() {
|
||||||
noteEl.focus();
|
noteEl.value?.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
function blur() {
|
function blur() {
|
||||||
noteEl.blur();
|
noteEl.value?.blur();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateParent(note: packed.PackNoteMaybeFull) {
|
async function updateParent(note: packed.PackNoteMaybeFull) {
|
||||||
|
@ -347,7 +349,6 @@ async function updateParent(note: packed.PackNoteMaybeFull) {
|
||||||
watch(appearNote, updateParent);
|
watch(appearNote, updateParent);
|
||||||
updateParent(note);
|
updateParent(note);
|
||||||
|
|
||||||
directReplies = [];
|
|
||||||
os.api("notes/children", {
|
os.api("notes/children", {
|
||||||
noteId: appearNote.id,
|
noteId: appearNote.id,
|
||||||
limit: 30,
|
limit: 30,
|
||||||
|
@ -362,29 +363,34 @@ os.api("notes/children", {
|
||||||
},
|
},
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
replies.value = res;
|
|
||||||
directReplies = res
|
Promise.all(res.map(resolveNote)).then((a) => {
|
||||||
.filter((resNote) => resNote.replyId === appearNote.id)
|
replies.value = a;
|
||||||
|
directReplies.value = a
|
||||||
|
.filter((resNote) => resNote.parent_note_id === appearNote.id)
|
||||||
.reverse();
|
.reverse();
|
||||||
directQuotes = res.filter((resNote) => resNote.renoteId === appearNote.id);
|
directQuotes.value = a.filter(
|
||||||
|
(resNote) => resNote.renoted_note_id === appearNote.id
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
conversation = null;
|
|
||||||
if (appearNote.parent_note_id) {
|
if (appearNote.parent_note_id) {
|
||||||
os.api("notes/conversation", {
|
os.api("notes/conversation", {
|
||||||
noteId: appearNote.parent_note_id,
|
noteId: appearNote.parent_note_id,
|
||||||
limit: 30,
|
limit: 30,
|
||||||
}).then((res) => {
|
}).then(async (res) => {
|
||||||
conversation = res.reverse();
|
conversation.value = await Promise.all<packed.PackNoteMaybeFull>(
|
||||||
|
res?.reverse().map(resolveNote)
|
||||||
|
);
|
||||||
focus();
|
focus();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
clips = null;
|
|
||||||
os.api("notes/clips", {
|
os.api("notes/clips", {
|
||||||
noteId: appearNote.id,
|
noteId: appearNote.id,
|
||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
clips = res;
|
clips.value = res as misskey.entities.Clip[];
|
||||||
});
|
});
|
||||||
|
|
||||||
// const pagination = {
|
// const pagination = {
|
||||||
|
@ -395,14 +401,13 @@ os.api("notes/clips", {
|
||||||
|
|
||||||
// const pagingComponent = $ref<InstanceType<typeof MkPagination>>();
|
// const pagingComponent = $ref<InstanceType<typeof MkPagination>>();
|
||||||
|
|
||||||
renotes = null;
|
|
||||||
function loadTab() {
|
function loadTab() {
|
||||||
if (tab === "renotes" && !renotes) {
|
if (tab === "renotes" && !renotes) {
|
||||||
os.api("notes/renotes", {
|
os.api("notes/renotes", {
|
||||||
noteId: appearNote.id,
|
noteId: appearNote.id,
|
||||||
limit: 100,
|
limit: 100,
|
||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
renotes = res;
|
renotes.value = res;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -430,13 +435,23 @@ async function onNoteUpdated(noteData: NoteUpdatedEvent): Promise<void> {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "replied":
|
case "replied":
|
||||||
const { id: createdId } = body;
|
const { id: createdId } = body;
|
||||||
const replyNote = await os.api("notes/show", {
|
const replyNote = await os.magApi(
|
||||||
noteId: createdId,
|
endpoints.GetNoteById,
|
||||||
});
|
{
|
||||||
|
context: true,
|
||||||
|
attachments: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: createdId,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
replies.value.splice(found, 0, replyNote);
|
replies.value.splice(found, 0, replyNote);
|
||||||
if (found === 0) {
|
if (found === 0) {
|
||||||
directReplies.push(replyNote);
|
directReplies.value = [
|
||||||
|
...(directReplies.value ?? []),
|
||||||
|
replyNote,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -457,12 +472,12 @@ document.addEventListener("wheel", () => {
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
stream.on("noteUpdated", onNoteUpdated);
|
stream.on("noteUpdated", onNoteUpdated);
|
||||||
isScrolling = false;
|
isScrolling = false;
|
||||||
noteEl.scrollIntoView();
|
noteEl.value?.scrollIntoView();
|
||||||
});
|
});
|
||||||
|
|
||||||
onUpdated(() => {
|
onUpdated(() => {
|
||||||
if (!isScrolling) {
|
if (!isScrolling) {
|
||||||
noteEl.scrollIntoView();
|
noteEl.value?.scrollIntoView();
|
||||||
if (location.hash) {
|
if (location.hash) {
|
||||||
location.replace(location.hash); // Jump to highlighted reply
|
location.replace(location.hash); // Jump to highlighted reply
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,13 +9,7 @@
|
||||||
@click.stop
|
@click.stop
|
||||||
>
|
>
|
||||||
<MkUserName :user="note.user" class="mkusername">
|
<MkUserName :user="note.user" class="mkusername">
|
||||||
<span
|
<span v-if="note.user.is_bot" class="is-bot">bot</span>
|
||||||
v-if="
|
|
||||||
magTransProperty(note.user, 'is_bot', 'isBot')
|
|
||||||
"
|
|
||||||
class="is-bot"
|
|
||||||
>bot</span
|
|
||||||
>
|
|
||||||
</MkUserName>
|
</MkUserName>
|
||||||
</MkA>
|
</MkA>
|
||||||
<div class="username"><MkAcct :user="note.user" /></div>
|
<div class="username"><MkAcct :user="note.user" /></div>
|
||||||
|
@ -23,38 +17,16 @@
|
||||||
<div>
|
<div>
|
||||||
<div class="info">
|
<div class="info">
|
||||||
<MkA class="created-at" :to="notePage(note)">
|
<MkA class="created-at" :to="notePage(note)">
|
||||||
<MkTime
|
<MkTime :time="note.created_at" />
|
||||||
:time="
|
|
||||||
magTransProperty(
|
|
||||||
note,
|
|
||||||
'created_at',
|
|
||||||
'createdAt'
|
|
||||||
)
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
<i
|
<i
|
||||||
v-if="
|
v-if="note.updated_at"
|
||||||
magTransProperty(
|
|
||||||
note,
|
|
||||||
'updated_at',
|
|
||||||
'updatedAt'
|
|
||||||
)
|
|
||||||
"
|
|
||||||
v-tooltip.noDelay="
|
v-tooltip.noDelay="
|
||||||
i18n.t('edited', {
|
i18n.t('edited', {
|
||||||
date: new Date(
|
date: new Date(
|
||||||
magTransProperty(
|
note.updated_at
|
||||||
note,
|
|
||||||
'updated_at',
|
|
||||||
'updatedAt'
|
|
||||||
)!
|
|
||||||
).toLocaleDateString(),
|
).toLocaleDateString(),
|
||||||
time: new Date(
|
time: new Date(
|
||||||
magTransProperty(
|
note.updated_at
|
||||||
note,
|
|
||||||
'updated_at',
|
|
||||||
'updatedAt'
|
|
||||||
)!
|
|
||||||
).toLocaleTimeString(),
|
).toLocaleTimeString(),
|
||||||
})
|
})
|
||||||
"
|
"
|
||||||
|
@ -75,7 +47,6 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type * as misskey from "calckey-js";
|
|
||||||
import { defaultStore } from "@/store";
|
import { defaultStore } from "@/store";
|
||||||
import MkVisibility from "@/components/MkVisibility.vue";
|
import MkVisibility from "@/components/MkVisibility.vue";
|
||||||
import MkInstanceTicker from "@/components/MkInstanceTicker.vue";
|
import MkInstanceTicker from "@/components/MkInstanceTicker.vue";
|
||||||
|
@ -83,18 +54,15 @@ import { notePage } from "@/filters/note";
|
||||||
import { userPage } from "@/filters/user";
|
import { userPage } from "@/filters/user";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
import { packed } from "magnetar-common";
|
import { packed } from "magnetar-common";
|
||||||
import { magTransProperty } from "@/scripts-mag/mag-util";
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
note: packed.PackNoteBase | misskey.entities.Note;
|
note: packed.PackNoteBase;
|
||||||
pinned?: boolean;
|
pinned?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
let note = $ref(props.note);
|
|
||||||
|
|
||||||
const showTicker =
|
const showTicker =
|
||||||
defaultStore.state.instanceTicker === "always" ||
|
defaultStore.state.instanceTicker === "always" ||
|
||||||
(defaultStore.state.instanceTicker === "remote" && note.user.host);
|
(defaultStore.state.instanceTicker === "remote" && props.note.user.host);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
|
@ -0,0 +1,44 @@
|
||||||
|
<template>
|
||||||
|
<XNote
|
||||||
|
v-if="noteData"
|
||||||
|
:note="noteData"
|
||||||
|
:collapsed-reply="collapsedReply"
|
||||||
|
:detailed-view="detailedView"
|
||||||
|
:pinned="pinned"
|
||||||
|
>
|
||||||
|
</XNote>
|
||||||
|
<div v-else>
|
||||||
|
<i class="ph-circle-notch ph-bold ph-lg fa-pulse ph-fw ph-lg"></i>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, watch } from "vue";
|
||||||
|
import XNote from "@/components/MagNote.vue";
|
||||||
|
import { packed } from "magnetar-common";
|
||||||
|
import { resolveNote } from "@/scripts-mag/mag-util";
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
note: packed.PackNoteMaybeFull | string;
|
||||||
|
pinned?: boolean;
|
||||||
|
detailedView?: boolean;
|
||||||
|
collapsedReply?: boolean;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
let noteData = ref<packed.PackNoteMaybeFull | null>(null);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.note,
|
||||||
|
(val) => {
|
||||||
|
if (typeof val !== "string") {
|
||||||
|
noteData.value = val;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolveNote({ id: val }).then((n) => {
|
||||||
|
noteData.value = n;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
</script>
|
|
@ -4,20 +4,19 @@
|
||||||
<div class="main">
|
<div class="main">
|
||||||
<XNoteHeader class="header" :note="note" :mini="true" />
|
<XNoteHeader class="header" :note="note" :mini="true" />
|
||||||
<div class="body">
|
<div class="body">
|
||||||
<MkSubNoteContent class="text" :note="note" />
|
<MagSubNoteContent class="text" :note="note" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import * as misskey from "calckey-js";
|
import XNoteHeader from "@/components/MagNoteHeader.vue";
|
||||||
import XNoteHeader from "@/components/MkNoteHeader.vue";
|
import MagSubNoteContent from "@/components/MagSubNoteContent.vue";
|
||||||
import MkSubNoteContent from "@/components/MkSubNoteContent.vue";
|
|
||||||
import { packed } from "magnetar-common";
|
import { packed } from "magnetar-common";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
note: packed.PackNoteMaybeFull | misskey.entities.Note;
|
note: packed.PackNoteMaybeFull;
|
||||||
pinned?: boolean;
|
pinned?: boolean;
|
||||||
}>();
|
}>();
|
||||||
</script>
|
</script>
|
|
@ -29,13 +29,13 @@
|
||||||
<div class="body">
|
<div class="body">
|
||||||
<XNoteHeader class="header" :note="note" :mini="true" />
|
<XNoteHeader class="header" :note="note" :mini="true" />
|
||||||
<div class="body">
|
<div class="body">
|
||||||
<MkSubNoteContent
|
<XSubNoteContent
|
||||||
class="text"
|
class="text"
|
||||||
:note="note"
|
:note="note"
|
||||||
:parentId="parentId"
|
:parentId="parentId"
|
||||||
:conversation="conversation"
|
:conversation="conversation"
|
||||||
:detailedView="detailedView"
|
:detailedView="detailedView"
|
||||||
@focusfooter="footerEl.focus()"
|
@focusfooter="footerEl?.focus()"
|
||||||
/>
|
/>
|
||||||
<div v-if="translating || translation" class="translation">
|
<div v-if="translating || translation" class="translation">
|
||||||
<MkLoading v-if="translating" mini />
|
<MkLoading v-if="translating" mini />
|
||||||
|
@ -68,25 +68,9 @@
|
||||||
@click="reply()"
|
@click="reply()"
|
||||||
>
|
>
|
||||||
<i class="ph-arrow-u-up-left ph-bold ph-lg"></i>
|
<i class="ph-arrow-u-up-left ph-bold ph-lg"></i>
|
||||||
<template
|
<template v-if="appearNote.reply_count > 0">
|
||||||
v-if="
|
|
||||||
Number(
|
|
||||||
magTransProperty(
|
|
||||||
appearNote,
|
|
||||||
'reply_count',
|
|
||||||
'repliesCount'
|
|
||||||
)
|
|
||||||
) > 0
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<p class="count">
|
<p class="count">
|
||||||
{{
|
{{ appearNote.reply_count }}
|
||||||
magTransProperty(
|
|
||||||
appearNote,
|
|
||||||
"reply_count",
|
|
||||||
"repliesCount"
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</p>
|
</p>
|
||||||
</template>
|
</template>
|
||||||
</button>
|
</button>
|
||||||
|
@ -94,13 +78,7 @@
|
||||||
ref="renoteButton"
|
ref="renoteButton"
|
||||||
class="button"
|
class="button"
|
||||||
:note="appearNote"
|
:note="appearNote"
|
||||||
:count="
|
:count="appearNote.renote_count"
|
||||||
magTransProperty(
|
|
||||||
appearNote,
|
|
||||||
'renote_count',
|
|
||||||
'renoteCount'
|
|
||||||
)
|
|
||||||
"
|
|
||||||
/>
|
/>
|
||||||
<XStarButtonNoEmoji
|
<XStarButtonNoEmoji
|
||||||
v-if="!enableEmojiReactions"
|
v-if="!enableEmojiReactions"
|
||||||
|
@ -150,7 +128,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<template v-if="conversation">
|
<template v-if="conversation">
|
||||||
<MkNoteSub
|
<MagNoteSub
|
||||||
v-if="replyLevel < 11 && depth < 5"
|
v-if="replyLevel < 11 && depth < 5"
|
||||||
v-for="reply in replies"
|
v-for="reply in replies"
|
||||||
:key="reply.id"
|
:key="reply.id"
|
||||||
|
@ -194,13 +172,13 @@
|
||||||
import type { Ref } from "vue";
|
import type { Ref } from "vue";
|
||||||
import { inject, ref, toRaw } from "vue";
|
import { inject, ref, toRaw } from "vue";
|
||||||
import * as misskey from "calckey-js";
|
import * as misskey from "calckey-js";
|
||||||
import XNoteHeader from "@/components/MkNoteHeader.vue";
|
import XNoteHeader from "@/components/MagNoteHeader.vue";
|
||||||
import MkSubNoteContent from "@/components/MkSubNoteContent.vue";
|
import XSubNoteContent from "@/components/MagSubNoteContent.vue";
|
||||||
import XReactionsViewer from "@/components/MkReactionsViewer.vue";
|
import XReactionsViewer from "@/components/MkReactionsViewer.vue";
|
||||||
import XStarButton from "@/components/MkStarButton.vue";
|
import XStarButton from "@/components/MkStarButton.vue";
|
||||||
import XStarButtonNoEmoji from "@/components/MkStarButtonNoEmoji.vue";
|
import XStarButtonNoEmoji from "@/components/MkStarButtonNoEmoji.vue";
|
||||||
import XRenoteButton from "@/components/MkRenoteButton.vue";
|
import XRenoteButton from "@/components/MagRenoteButton.vue";
|
||||||
import XQuoteButton from "@/components/MkQuoteButton.vue";
|
import XQuoteButton from "@/components/MagQuoteButton.vue";
|
||||||
import copyToClipboard from "@/scripts/copy-to-clipboard";
|
import copyToClipboard from "@/scripts/copy-to-clipboard";
|
||||||
import { url } from "@/config";
|
import { url } from "@/config";
|
||||||
import { pleaseLogin } from "@/scripts/please-login";
|
import { pleaseLogin } from "@/scripts/please-login";
|
||||||
|
@ -217,21 +195,21 @@ import { useNoteCapture } from "@/scripts/use-note-capture";
|
||||||
import { defaultStore } from "@/store";
|
import { defaultStore } from "@/store";
|
||||||
import { packed } from "magnetar-common";
|
import { packed } from "magnetar-common";
|
||||||
import {
|
import {
|
||||||
|
magEffectiveNote,
|
||||||
magHasReacted,
|
magHasReacted,
|
||||||
magIsRenote,
|
|
||||||
magReactionCount,
|
magReactionCount,
|
||||||
|
magReactionSelf,
|
||||||
magReactionToLegacy,
|
magReactionToLegacy,
|
||||||
magTransProperty,
|
|
||||||
} from "@/scripts-mag/mag-util";
|
} from "@/scripts-mag/mag-util";
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
note: packed.PackNoteMaybeFull | misskey.entities.Note;
|
note: packed.PackNoteMaybeFull;
|
||||||
conversation?: misskey.entities.Note[];
|
conversation?: packed.PackNoteMaybeAttachments[];
|
||||||
parentId?;
|
parentId?: string;
|
||||||
detailedView?;
|
detailedView?: boolean;
|
||||||
|
|
||||||
// how many notes are in between this one and the note being viewed in detail
|
// how many notes are in between this one and the note being viewed in detail
|
||||||
depth?: number;
|
depth?: number;
|
||||||
|
@ -244,9 +222,7 @@ const props = withDefaults(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
let note = $ref<packed.PackNoteMaybeFull | misskey.entities.Note>(
|
let note = $ref<packed.PackNoteMaybeFull>(structuredClone(toRaw(props.note)));
|
||||||
structuredClone(toRaw(props.note))
|
|
||||||
);
|
|
||||||
|
|
||||||
const softMuteReasonI18nSrc = (what?: string) => {
|
const softMuteReasonI18nSrc = (what?: string) => {
|
||||||
if (what === "note") return i18n.ts.userSaysSomethingReason;
|
if (what === "note") return i18n.ts.userSaysSomethingReason;
|
||||||
|
@ -258,36 +234,32 @@ const softMuteReasonI18nSrc = (what?: string) => {
|
||||||
return i18n.ts.userSaysSomething;
|
return i18n.ts.userSaysSomething;
|
||||||
};
|
};
|
||||||
|
|
||||||
const el = ref<HTMLElement>();
|
const el = ref<HTMLElement | null>(null);
|
||||||
const footerEl = ref<HTMLElement>();
|
const footerEl = ref<HTMLElement | null>(null);
|
||||||
const menuButton = ref<HTMLElement>();
|
const menuButton = ref<HTMLElement | null>(null);
|
||||||
const starButton = ref<InstanceType<typeof XStarButton>>();
|
const starButton = ref<InstanceType<typeof XStarButton>>();
|
||||||
const renoteButton = ref<InstanceType<typeof XRenoteButton>>();
|
const renoteButton = ref<InstanceType<typeof XRenoteButton>>();
|
||||||
const reactButton = ref<HTMLElement>();
|
const reactButton = ref<HTMLElement | null>(null);
|
||||||
let appearNote = $computed(() =>
|
let appearNote = $computed(
|
||||||
magIsRenote(note)
|
() => magEffectiveNote(props.note) as packed.PackNoteMaybeFull
|
||||||
? (magTransProperty(note, "renoted_note", "renote") as
|
|
||||||
| packed.PackNoteMaybeFull
|
|
||||||
| misskey.entities.Note)
|
|
||||||
: note
|
|
||||||
);
|
);
|
||||||
const isDeleted = ref(false);
|
const isDeleted = ref(false);
|
||||||
const muted = ref(getWordSoftMute(note, $i, defaultStore.state.mutedWords));
|
const muted = ref(getWordSoftMute(note, $i, defaultStore.state.mutedWords));
|
||||||
const translation = ref(null);
|
const translation = ref(null);
|
||||||
const translating = ref(false);
|
const translating = ref(false);
|
||||||
const replies: misskey.entities.Note[] =
|
const replies: packed.PackNoteMaybeFull[] =
|
||||||
props.conversation
|
props.conversation
|
||||||
?.filter(
|
?.filter(
|
||||||
(item) =>
|
(item) =>
|
||||||
item.replyId === appearNote.id ||
|
item.parent_note_id === appearNote.id ||
|
||||||
item.renoteId === appearNote.id
|
item.renoted_note_id === appearNote.id
|
||||||
)
|
)
|
||||||
.reverse() ?? [];
|
.reverse() ?? [];
|
||||||
const enableEmojiReactions = defaultStore.state.enableEmojiReactions;
|
const enableEmojiReactions = defaultStore.state.enableEmojiReactions;
|
||||||
const expandOnNoteClick = defaultStore.state.expandOnNoteClick;
|
const expandOnNoteClick = defaultStore.state.expandOnNoteClick;
|
||||||
|
|
||||||
useNoteCapture({
|
useNoteCapture({
|
||||||
rootEl: el,
|
rootEl: el as Ref<HTMLElement>,
|
||||||
note: $$(appearNote),
|
note: $$(appearNote),
|
||||||
isDeletedRef: isDeleted,
|
isDeletedRef: isDeleted,
|
||||||
});
|
});
|
||||||
|
@ -319,8 +291,8 @@ function react(viaKeyboard = false): void {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function undoReact(note): void {
|
function undoReact(note: packed.PackNoteBase): void {
|
||||||
const oldReaction = note.myReaction;
|
const oldReaction = magReactionSelf(note);
|
||||||
if (!oldReaction) return;
|
if (!oldReaction) return;
|
||||||
os.api("notes/reactions/delete", {
|
os.api("notes/reactions/delete", {
|
||||||
noteId: note.id,
|
noteId: note.id,
|
||||||
|
@ -338,11 +310,11 @@ function menu(viaKeyboard = false): void {
|
||||||
note: note,
|
note: note,
|
||||||
translating,
|
translating,
|
||||||
translation,
|
translation,
|
||||||
menuButton,
|
menuButton: menuButton.value,
|
||||||
isDeleted,
|
isDeleted,
|
||||||
currentClipPage,
|
currentClipPage: currentClipPage?.value,
|
||||||
}),
|
}),
|
||||||
menuButton.value,
|
menuButton.value ?? undefined,
|
||||||
{
|
{
|
||||||
viaKeyboard,
|
viaKeyboard,
|
||||||
}
|
}
|
|
@ -2,49 +2,32 @@
|
||||||
<div class="tivcixzd" :class="{ done: closed || isVoted }">
|
<div class="tivcixzd" :class="{ done: closed || isVoted }">
|
||||||
<ul>
|
<ul>
|
||||||
<li
|
<li
|
||||||
v-for="(choice, i) in magTransProperty(
|
v-for="(choice, i) in note.poll.options"
|
||||||
note.poll,
|
|
||||||
'options',
|
|
||||||
'choices'
|
|
||||||
)"
|
|
||||||
:key="i"
|
:key="i"
|
||||||
:class="{ voted: magTransProperty(choice, 'voted', 'isVoted') }"
|
:class="{ voted: choice.voted }"
|
||||||
@click.stop="vote(i)"
|
@click.stop="vote(i)"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="backdrop"
|
class="backdrop"
|
||||||
:style="{
|
:style="{
|
||||||
width: `${
|
width: `${
|
||||||
showResult
|
showResult ? (choice.votes_count / total) * 100 : 0
|
||||||
? (magTransProperty(
|
|
||||||
choice,
|
|
||||||
'votes_count',
|
|
||||||
'votes'
|
|
||||||
) /
|
|
||||||
total) *
|
|
||||||
100
|
|
||||||
: 0
|
|
||||||
}%`,
|
}%`,
|
||||||
}"
|
}"
|
||||||
></div>
|
></div>
|
||||||
<span>
|
<span>
|
||||||
<template
|
<template v-if="choice.voted"
|
||||||
v-if="magTransProperty(choice, 'voted', 'isVoted')"
|
|
||||||
><i class="ph-check ph-bold ph-lg"></i
|
><i class="ph-check ph-bold ph-lg"></i
|
||||||
></template>
|
></template>
|
||||||
<Mfm
|
<Mfm
|
||||||
:text="magTransProperty(choice, 'title', 'text')"
|
:text="choice.title"
|
||||||
:plain="true"
|
:plain="true"
|
||||||
:custom-emojis="note.emojis"
|
:custom-emojis="note.emojis"
|
||||||
/>
|
/>
|
||||||
<span v-if="showResult" class="votes"
|
<span v-if="showResult" class="votes"
|
||||||
>({{
|
>({{
|
||||||
i18n.t("_poll.votesCount", {
|
i18n.t("_poll.votesCount", {
|
||||||
n: magTransProperty(
|
n: choice.votes_count,
|
||||||
choice,
|
|
||||||
"votes_count",
|
|
||||||
"votes"
|
|
||||||
),
|
|
||||||
})
|
})
|
||||||
}})</span
|
}})</span
|
||||||
>
|
>
|
||||||
|
@ -78,20 +61,15 @@
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, ref } from "vue";
|
import { computed, ref } from "vue";
|
||||||
import * as misskey from "calckey-js";
|
|
||||||
import { sum } from "@/scripts/array";
|
import { sum } from "@/scripts/array";
|
||||||
import { pleaseLogin } from "@/scripts/please-login";
|
import { pleaseLogin } from "@/scripts/please-login";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
import { useInterval } from "@/scripts/use-interval";
|
import { useInterval } from "@/scripts/use-interval";
|
||||||
import { packed } from "magnetar-common";
|
import { packed } from "magnetar-common";
|
||||||
import { magTransProperty } from "@/scripts-mag/mag-util";
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
note:
|
note: packed.PackNoteMaybeFull & { poll: {} };
|
||||||
| (packed.PackNoteMaybeFull & { poll: {} })
|
|
||||||
| (misskey.entities.Note &
|
|
||||||
Required<Pick<misskey.entities.Note, "poll">>);
|
|
||||||
readOnly?: boolean;
|
readOnly?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
@ -99,20 +77,14 @@ const pollRefreshing = ref(false);
|
||||||
const remaining = ref(-1);
|
const remaining = ref(-1);
|
||||||
|
|
||||||
const total = computed(() =>
|
const total = computed(() =>
|
||||||
sum(
|
sum(props.note.poll.options.map((x) => x.votes_count))
|
||||||
magTransProperty(props.note.poll, "options", "choices").map(
|
|
||||||
(x) => magTransProperty(x, "votes_count", "votes") as number
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
const closed = computed(() => remaining.value === 0);
|
const closed = computed(() => remaining.value === 0);
|
||||||
const isLocal = computed(() => !props.note.uri);
|
const isLocal = computed(() => !props.note.uri);
|
||||||
const isVoted = computed(
|
const isVoted = computed(
|
||||||
() =>
|
() =>
|
||||||
!magTransProperty(props.note.poll, "multiple_choice", "multiple") &&
|
!props.note.poll.multiple_choice &&
|
||||||
magTransProperty(props.note.poll, "options", "choices").some(
|
props.note.poll.options.some((c) => c.voted ?? false)
|
||||||
(c) => magTransProperty(c, "voted", "isVoted") ?? false
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
const timer = computed(() =>
|
const timer = computed(() =>
|
||||||
i18n.t(
|
i18n.t(
|
||||||
|
@ -135,17 +107,11 @@ const timer = computed(() =>
|
||||||
const showResult = ref(props.readOnly || isVoted.value);
|
const showResult = ref(props.readOnly || isVoted.value);
|
||||||
|
|
||||||
// 期限付きアンケート
|
// 期限付きアンケート
|
||||||
if (magTransProperty(props.note.poll, "expires_at", "expiresAt")) {
|
if (props.note.poll.expires_at) {
|
||||||
const tick = () => {
|
const tick = () => {
|
||||||
remaining.value = Math.floor(
|
remaining.value = Math.floor(
|
||||||
Math.max(
|
Math.max(
|
||||||
new Date(
|
new Date(props.note.poll.expires_at!).getTime() - Date.now(),
|
||||||
magTransProperty(
|
|
||||||
props.note.poll,
|
|
||||||
"expires_at",
|
|
||||||
"expiresAt"
|
|
||||||
)!
|
|
||||||
).getTime() - Date.now(),
|
|
||||||
0
|
0
|
||||||
) / 1000
|
) / 1000
|
||||||
);
|
);
|
||||||
|
@ -168,7 +134,16 @@ async function refresh() {
|
||||||
os.api("ap/show", { uri: props.note.uri })
|
os.api("ap/show", { uri: props.note.uri })
|
||||||
.then((obj) => {
|
.then((obj) => {
|
||||||
if (obj && obj.type === "Note" && obj.object.poll) {
|
if (obj && obj.type === "Note" && obj.object.poll) {
|
||||||
props.note.poll = obj.object.poll;
|
props.note.poll = {
|
||||||
|
...props.note.poll,
|
||||||
|
expires_at: obj.object.poll.expiresAt,
|
||||||
|
multiple_choice: obj.object.poll.multiple,
|
||||||
|
options: obj.object.poll.choices?.map((c) => ({
|
||||||
|
title: c.text,
|
||||||
|
votes_count: c.votes,
|
||||||
|
voted: c.isVoted,
|
||||||
|
})),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
|
@ -190,11 +165,7 @@ const vote = async (id: number) => {
|
||||||
const { canceled } = await os.confirm({
|
const { canceled } = await os.confirm({
|
||||||
type: "question",
|
type: "question",
|
||||||
text: i18n.t("voteConfirm", {
|
text: i18n.t("voteConfirm", {
|
||||||
choice: magTransProperty(
|
choice: props.note.poll.options[id].title,
|
||||||
magTransProperty(props.note.poll, "options", "choices")[id],
|
|
||||||
"title",
|
|
||||||
"text"
|
|
||||||
),
|
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
if (canceled) return;
|
if (canceled) return;
|
||||||
|
@ -203,12 +174,7 @@ const vote = async (id: number) => {
|
||||||
noteId: props.note.id,
|
noteId: props.note.id,
|
||||||
choice: id,
|
choice: id,
|
||||||
});
|
});
|
||||||
if (!showResult.value)
|
if (!showResult.value) showResult.value = !props.note.poll.multiple_choice;
|
||||||
showResult.value = !magTransProperty(
|
|
||||||
props.note.poll,
|
|
||||||
"multiple_choice",
|
|
||||||
"multiple"
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -11,7 +11,6 @@
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
import * as Misskey from "calckey-js";
|
|
||||||
import { packed } from "magnetar-common";
|
import { packed } from "magnetar-common";
|
||||||
import { pleaseLogin } from "@/scripts/please-login";
|
import { pleaseLogin } from "@/scripts/please-login";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
|
@ -19,7 +18,7 @@ import { $i } from "@/account";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
note: packed.PackNoteBase | Misskey.entities.Note;
|
note: packed.PackNoteBase;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const canRenote = computed(
|
const canRenote = computed(
|
|
@ -33,34 +33,21 @@ import { i18n } from "@/i18n";
|
||||||
import { defaultStore } from "@/store";
|
import { defaultStore } from "@/store";
|
||||||
import { MenuItem } from "@/types/menu";
|
import { MenuItem } from "@/types/menu";
|
||||||
import { packed } from "magnetar-common";
|
import { packed } from "magnetar-common";
|
||||||
import {
|
import { magLegacyVisibility } from "@/scripts-mag/mag-util";
|
||||||
magLegacyVisibility,
|
|
||||||
magTransMap,
|
|
||||||
magTransProperty,
|
|
||||||
magVisibility,
|
|
||||||
} from "@/scripts-mag/mag-util";
|
|
||||||
import * as Misskey from "calckey-js";
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
note: packed.PackNoteMaybeFull | Misskey.entities.Note;
|
note: packed.PackNoteMaybeFull;
|
||||||
count: number;
|
count: number;
|
||||||
detailedView?;
|
detailedView?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const buttonRef = ref<HTMLElement>();
|
const buttonRef = ref<HTMLElement>();
|
||||||
|
|
||||||
const hasRenotedBefore = ref<boolean>(
|
const hasRenotedBefore = ref<boolean>((props.note.self_renote_count ?? 0) > 0);
|
||||||
magTransMap(
|
|
||||||
props.note,
|
|
||||||
"self_renote_count",
|
|
||||||
"hasRenotedBefore",
|
|
||||||
(n) => (n ?? 0) > 0
|
|
||||||
) ?? false
|
|
||||||
);
|
|
||||||
|
|
||||||
const canRenote = computed(
|
const canRenote = computed(
|
||||||
() =>
|
() =>
|
||||||
["Public", "Home"].includes(magVisibility(props.note.visibility)) ||
|
["Public", "Home"].includes(props.note.visibility) ||
|
||||||
($i && props.note.user.id === $i.id)
|
($i && props.note.user.id === $i.id)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -92,7 +79,7 @@ const renote = async (viaKeyboard = false, ev?: MouseEvent) => {
|
||||||
|
|
||||||
let buttonActions: Array<MenuItem> = [];
|
let buttonActions: Array<MenuItem> = [];
|
||||||
|
|
||||||
if (magVisibility(props.note.visibility) === "Public") {
|
if (props.note.visibility === "Public") {
|
||||||
buttonActions.push({
|
buttonActions.push({
|
||||||
text: i18n.ts.renote,
|
text: i18n.ts.renote,
|
||||||
icon: "ph-repeat ph-bold ph-lg",
|
icon: "ph-repeat ph-bold ph-lg",
|
||||||
|
@ -119,7 +106,7 @@ const renote = async (viaKeyboard = false, ev?: MouseEvent) => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (["Public", "Home"].includes(magVisibility(props.note.visibility))) {
|
if (["Public", "Home"].includes(props.note.visibility)) {
|
||||||
buttonActions.push({
|
buttonActions.push({
|
||||||
text: `${i18n.ts.renote} (${i18n.ts._visibility.home})`,
|
text: `${i18n.ts.renote} (${i18n.ts._visibility.home})`,
|
||||||
icon: "ph-house ph-bold ph-lg",
|
icon: "ph-house ph-bold ph-lg",
|
||||||
|
@ -146,7 +133,7 @@ const renote = async (viaKeyboard = false, ev?: MouseEvent) => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (magVisibility(props.note.visibility) === "Direct") {
|
if (props.note.visibility === "Direct") {
|
||||||
buttonActions.push({
|
buttonActions.push({
|
||||||
text: `${i18n.ts.renote} (${i18n.ts.recipient})`,
|
text: `${i18n.ts.renote} (${i18n.ts.recipient})`,
|
||||||
icon: "ph-envelope-simple-open ph-bold ph-lg",
|
icon: "ph-envelope-simple-open ph-bold ph-lg",
|
||||||
|
@ -155,12 +142,7 @@ const renote = async (viaKeyboard = false, ev?: MouseEvent) => {
|
||||||
os.api("notes/create", {
|
os.api("notes/create", {
|
||||||
renoteId: props.note.id,
|
renoteId: props.note.id,
|
||||||
visibility: "specified",
|
visibility: "specified",
|
||||||
visibleUserIds:
|
visibleUserIds: props.note.visible_user_ids ?? [],
|
||||||
magTransProperty(
|
|
||||||
props.note,
|
|
||||||
"visible_user_ids",
|
|
||||||
"visibleUserIds"
|
|
||||||
) ?? [],
|
|
||||||
});
|
});
|
||||||
hasRenotedBefore.value = true;
|
hasRenotedBefore.value = true;
|
||||||
const el =
|
const el =
|
||||||
|
@ -212,18 +194,13 @@ const renote = async (viaKeyboard = false, ev?: MouseEvent) => {
|
||||||
action: () => {
|
action: () => {
|
||||||
os.api(
|
os.api(
|
||||||
"notes/create",
|
"notes/create",
|
||||||
magVisibility(props.note.visibility) === "Direct"
|
props.note.visibility === "Direct"
|
||||||
? {
|
? {
|
||||||
renoteId: props.note.id,
|
renoteId: props.note.id,
|
||||||
visibility: magLegacyVisibility(
|
visibility: magLegacyVisibility(
|
||||||
props.note.visibility
|
props.note.visibility
|
||||||
),
|
),
|
||||||
visibleUserIds:
|
visibleUserIds: props.note.visible_user_ids ?? [],
|
||||||
magTransProperty(
|
|
||||||
props.note,
|
|
||||||
"visible_user_ids",
|
|
||||||
"visibleUserIds"
|
|
||||||
) ?? [],
|
|
||||||
localOnly: true,
|
localOnly: true,
|
||||||
}
|
}
|
||||||
: {
|
: {
|
|
@ -1,11 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<p v-if="note.cw != null" class="cw">
|
<p v-if="note.cw != null" class="cw">
|
||||||
<MkA
|
<MkA
|
||||||
v-if="
|
v-if="conversation && note.renoted_note_id == parentId"
|
||||||
conversation &&
|
|
||||||
magTransProperty(note, 'renoted_note_id', 'renoteId') ==
|
|
||||||
parentId
|
|
||||||
"
|
|
||||||
:to="
|
:to="
|
||||||
detailedView ? `#${parentId}` : `${notePage(note)}#${parentId}`
|
detailedView ? `#${parentId}` : `${notePage(note)}#${parentId}`
|
||||||
"
|
"
|
||||||
|
@ -16,17 +12,11 @@
|
||||||
<i class="ph-quotes ph-bold ph-lg"></i>
|
<i class="ph-quotes ph-bold ph-lg"></i>
|
||||||
</MkA>
|
</MkA>
|
||||||
<MkA
|
<MkA
|
||||||
v-else-if="
|
v-else-if="!detailed && note.parent_note_id"
|
||||||
!detailed && magTransProperty(note, 'parent_note_id', 'replyId')
|
|
||||||
"
|
|
||||||
:to="
|
:to="
|
||||||
detailedView
|
detailedView
|
||||||
? `#${magTransProperty(note, 'parent_note_id', 'replyId')}`
|
? `#${note.parent_note_id}`
|
||||||
: `${notePage(note)}#${magTransProperty(
|
: `${note.parent_note_id}`
|
||||||
note,
|
|
||||||
'parent_note_id',
|
|
||||||
'replyId'
|
|
||||||
)}`
|
|
||||||
"
|
"
|
||||||
behavior="browser"
|
behavior="browser"
|
||||||
v-tooltip="i18n.ts.jumpToPrevious"
|
v-tooltip="i18n.ts.jumpToPrevious"
|
||||||
|
@ -38,7 +28,7 @@
|
||||||
<Mfm
|
<Mfm
|
||||||
v-if="note.cw != ''"
|
v-if="note.cw != ''"
|
||||||
class="text"
|
class="text"
|
||||||
:mm="magMaybeProperty(note, 'cw_mm')"
|
:mm="note.cw_mm"
|
||||||
:text="note.cw"
|
:text="note.cw"
|
||||||
:author="note.user"
|
:author="note.user"
|
||||||
:i="$i"
|
:i="$i"
|
||||||
|
@ -51,8 +41,7 @@
|
||||||
:class="{
|
:class="{
|
||||||
collapsed,
|
collapsed,
|
||||||
isLong,
|
isLong,
|
||||||
manyImages:
|
manyImages: note.attachments?.length > 4,
|
||||||
magTransProperty(note, 'attachments', 'files')?.length > 4,
|
|
||||||
showContent: note.cw && !showContent,
|
showContent: note.cw && !showContent,
|
||||||
animatedMfm: !disableMfm,
|
animatedMfm: !disableMfm,
|
||||||
}"
|
}"
|
||||||
|
@ -80,14 +69,7 @@
|
||||||
>
|
>
|
||||||
<template v-if="!note.cw">
|
<template v-if="!note.cw">
|
||||||
<MkA
|
<MkA
|
||||||
v-if="
|
v-if="conversation && note.renoted_note_id == parentId"
|
||||||
conversation &&
|
|
||||||
magTransProperty(
|
|
||||||
note,
|
|
||||||
'renoted_note_id',
|
|
||||||
'renoteId'
|
|
||||||
) == parentId
|
|
||||||
"
|
|
||||||
:to="
|
:to="
|
||||||
detailedView
|
detailedView
|
||||||
? `#${parentId}`
|
? `#${parentId}`
|
||||||
|
@ -100,22 +82,11 @@
|
||||||
<i class="ph-quotes ph-bold ph-lg"></i>
|
<i class="ph-quotes ph-bold ph-lg"></i>
|
||||||
</MkA>
|
</MkA>
|
||||||
<MkA
|
<MkA
|
||||||
v-else-if="
|
v-else-if="!detailed && note.parent_note_id"
|
||||||
!detailed &&
|
|
||||||
magTransProperty(note, 'parent_note_id', 'replyId')
|
|
||||||
"
|
|
||||||
:to="
|
:to="
|
||||||
detailedView
|
detailedView
|
||||||
? `#${magTransProperty(
|
? `#${note.parent_note_id}`
|
||||||
note,
|
: `${notePage(note)}#${note.parent_note_id}`
|
||||||
'parent_note_id',
|
|
||||||
'replyId'
|
|
||||||
)}`
|
|
||||||
: `${notePage(note)}#${magTransProperty(
|
|
||||||
note,
|
|
||||||
'parent_note_id',
|
|
||||||
'replyId'
|
|
||||||
)}`
|
|
||||||
"
|
"
|
||||||
behavior="browser"
|
behavior="browser"
|
||||||
v-tooltip="i18n.ts.jumpToPrevious"
|
v-tooltip="i18n.ts.jumpToPrevious"
|
||||||
|
@ -134,24 +105,14 @@
|
||||||
:custom-emojis="note.emojis"
|
:custom-emojis="note.emojis"
|
||||||
/>
|
/>
|
||||||
<MkA
|
<MkA
|
||||||
v-if="
|
v-if="!detailed && note.renoted_note_id"
|
||||||
!detailed &&
|
|
||||||
magTransProperty(note, 'renoted_note_id', 'renoteId')
|
|
||||||
"
|
|
||||||
class="rp"
|
class="rp"
|
||||||
:to="`/notes/${magTransProperty(
|
:to="`/notes/${note.renoted_note_id}`"
|
||||||
note,
|
|
||||||
'renoted_note_id',
|
|
||||||
'renoteId'
|
|
||||||
)}`"
|
|
||||||
>{{ i18n.ts.quoteAttached }}: ...</MkA
|
>{{ i18n.ts.quoteAttached }}: ...</MkA
|
||||||
>
|
>
|
||||||
<XMediaList
|
<XMediaList
|
||||||
v-if="
|
v-if="note.attachments?.length > 0"
|
||||||
magTransProperty(note, 'attachments', 'files')?.length >
|
:media-list="note.attachments!"
|
||||||
0
|
|
||||||
"
|
|
||||||
:media-list="magTransProperty(note, 'attachments', 'files')!"
|
|
||||||
/>
|
/>
|
||||||
<XPoll v-if="note.poll" :note="note" class="poll" />
|
<XPoll v-if="note.poll" :note="note" class="poll" />
|
||||||
<template v-if="detailed">
|
<template v-if="detailed">
|
||||||
|
@ -164,18 +125,11 @@
|
||||||
class="url-preview"
|
class="url-preview"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
v-if="magTransProperty(note, 'renoted_note', 'renote')"
|
v-if="note.renoted_note"
|
||||||
class="renote"
|
class="renote"
|
||||||
@click.stop="
|
@click.stop="emit('push', note.renoted_note)"
|
||||||
emit(
|
|
||||||
'push',
|
|
||||||
magTransProperty(note, 'renoted_note', 'renote')
|
|
||||||
)
|
|
||||||
"
|
|
||||||
>
|
>
|
||||||
<XNoteSimple
|
<XNoteSimple :note="note.renoted_note!" />
|
||||||
:note="magTransProperty(note, 'renoted_note', 'renote')!"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div
|
<div
|
||||||
|
@ -222,12 +176,11 @@
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
import * as misskey from "calckey-js";
|
|
||||||
import * as mfm from "mfm-js";
|
import * as mfm from "mfm-js";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
import XNoteSimple from "@/components/MkNoteSimple.vue";
|
import XNoteSimple from "@/components/MagNoteSimple.vue";
|
||||||
import XMediaList from "@/components/MkMediaList.vue";
|
import XMediaList from "@/components/MkMediaList.vue";
|
||||||
import XPoll from "@/components/MkPoll.vue";
|
import XPoll from "@/components/MagPoll.vue";
|
||||||
import MkUrlPreview from "@/components/MkUrlPreview.vue";
|
import MkUrlPreview from "@/components/MkUrlPreview.vue";
|
||||||
import XShowMoreButton from "@/components/MkShowMoreButton.vue";
|
import XShowMoreButton from "@/components/MkShowMoreButton.vue";
|
||||||
import XCwButton from "@/components/MkCwButton.vue";
|
import XCwButton from "@/components/MkCwButton.vue";
|
||||||
|
@ -238,13 +191,13 @@ import { extractMfmWithAnimation } from "@/scripts/extract-mfm";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
import { defaultStore } from "@/store";
|
import { defaultStore } from "@/store";
|
||||||
import { $i } from "@/account";
|
import { $i } from "@/account";
|
||||||
import { magMaybeProperty, magTransProperty } from "@/scripts-mag/mag-util";
|
import { magMaybeProperty } from "@/scripts-mag/mag-util";
|
||||||
import { packed } from "magnetar-common";
|
import { packed } from "magnetar-common";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
note: packed.PackNoteMaybeFull | misskey.entities.Note;
|
note: packed.PackNoteMaybeFull;
|
||||||
parentId?;
|
parentId?: string;
|
||||||
conversation?;
|
conversation?: packed.PackNoteMaybeAttachments[];
|
||||||
detailed?: boolean;
|
detailed?: boolean;
|
||||||
detailedView?: boolean;
|
detailedView?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
@ -264,8 +217,7 @@ const isLong =
|
||||||
((props.note.text != null &&
|
((props.note.text != null &&
|
||||||
(props.note.text.split("\n").length > 10 ||
|
(props.note.text.split("\n").length > 10 ||
|
||||||
props.note.text.length > 800)) ||
|
props.note.text.length > 800)) ||
|
||||||
(magTransProperty(props.note, "attachments", "files") ?? []).length >
|
(props.note.attachments ?? []).length > 4);
|
||||||
4);
|
|
||||||
const collapsed = $ref(props.note.cw == null && isLong);
|
const collapsed = $ref(props.note.cw == null && isLong);
|
||||||
const urls = props.note.text
|
const urls = props.note.text
|
||||||
? extractUrlFromMfm(mfm.parse(props.note.text)).slice(0, 5)
|
? extractUrlFromMfm(mfm.parse(props.note.text)).slice(0, 5)
|
|
@ -35,12 +35,13 @@ import { i18n } from "@/i18n";
|
||||||
import { useRouter } from "@/router";
|
import { useRouter } from "@/router";
|
||||||
import { stream } from "@/stream";
|
import { stream } from "@/stream";
|
||||||
import { globalEvents } from "@/events";
|
import { globalEvents } from "@/events";
|
||||||
|
import { packed } from "magnetar-common";
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const emit = defineEmits(["refresh"]);
|
const emit = defineEmits(["refresh"]);
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
user: Misskey.entities.User;
|
user: packed.PackUserBase | Misskey.entities.User;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
let state = $ref(i18n.ts.processing);
|
let state = $ref(i18n.ts.processing);
|
||||||
|
@ -48,14 +49,14 @@ const connection = stream.useChannel("main");
|
||||||
|
|
||||||
let waitIncoming = $ref(false);
|
let waitIncoming = $ref(false);
|
||||||
|
|
||||||
function accept(user: Misskey.entities.User) {
|
function accept(user: packed.PackUserBase | Misskey.entities.User) {
|
||||||
waitIncoming = true;
|
waitIncoming = true;
|
||||||
os.api("following/requests/accept", { userId: user.id }).then(() => {
|
os.api("following/requests/accept", { userId: user.id }).then(() => {
|
||||||
globalEvents.emit("followeeProcessed", user);
|
globalEvents.emit("followeeProcessed", user);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function reject(user: Misskey.entities.User) {
|
function reject(user: packed.PackUserBase | Misskey.entities.User) {
|
||||||
waitIncoming = true;
|
waitIncoming = true;
|
||||||
os.api("following/requests/reject", { userId: user.id }).then(() => {
|
os.api("following/requests/reject", { userId: user.id }).then(() => {
|
||||||
globalEvents.emit("followeeProcessed", user);
|
globalEvents.emit("followeeProcessed", user);
|
||||||
|
|
|
@ -1,941 +0,0 @@
|
||||||
<template>
|
|
||||||
<div
|
|
||||||
:aria-label="accessibleLabel"
|
|
||||||
v-if="!muted.muted"
|
|
||||||
v-show="!isDeleted"
|
|
||||||
ref="el"
|
|
||||||
v-hotkey="keymap"
|
|
||||||
v-size="{ max: [500, 350] }"
|
|
||||||
class="tkcbzcuz note-container"
|
|
||||||
:tabindex="!isDeleted ? '-1' : null"
|
|
||||||
:class="{ renote: magIsRenote(note) }"
|
|
||||||
:id="appearNote.id"
|
|
||||||
>
|
|
||||||
<MkNoteSub
|
|
||||||
v-if="appearNote.reply && !detailedView && !collapsedReply"
|
|
||||||
:note="appearNote.reply"
|
|
||||||
class="reply-to"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
v-if="!detailedView"
|
|
||||||
class="note-context"
|
|
||||||
@click="noteClick"
|
|
||||||
:class="{
|
|
||||||
collapsedReply: collapsedReply && appearNote.reply,
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<div class="line"></div>
|
|
||||||
<div v-if="appearNote._prId_" class="info">
|
|
||||||
<i class="ph-megaphone-simple-bold ph-lg"></i>
|
|
||||||
{{ i18n.ts.promotion
|
|
||||||
}}<button class="_textButton hide" @click.stop="readPromo()">
|
|
||||||
{{ i18n.ts.hideThisNote }}
|
|
||||||
<i class="ph-x ph-bold ph-lg"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div v-if="appearNote._featuredId_" class="info">
|
|
||||||
<i class="ph-lightning ph-bold ph-lg"></i>
|
|
||||||
{{ i18n.ts.featured }}
|
|
||||||
</div>
|
|
||||||
<div v-if="pinned" class="info">
|
|
||||||
<i class="ph-push-pin ph-bold ph-lg"></i
|
|
||||||
>{{ i18n.ts.pinnedNote }}
|
|
||||||
</div>
|
|
||||||
<div v-if="magIsRenote(note)" class="renote">
|
|
||||||
<i class="ph-repeat ph-bold ph-lg"></i>
|
|
||||||
<I18n :src="i18n.ts.renotedBy" tag="span">
|
|
||||||
<template #user>
|
|
||||||
<MkA
|
|
||||||
v-user-preview="note.userId"
|
|
||||||
class="name"
|
|
||||||
:to="userPage(note.user)"
|
|
||||||
@click.stop
|
|
||||||
>
|
|
||||||
<MkUserName :user="note.user" />
|
|
||||||
</MkA>
|
|
||||||
</template>
|
|
||||||
</I18n>
|
|
||||||
<div class="info">
|
|
||||||
<button
|
|
||||||
ref="renoteTime"
|
|
||||||
class="_button time"
|
|
||||||
@click.stop="showRenoteMenu()"
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
v-if="isMyRenote"
|
|
||||||
class="ph-dots-three-outline ph-bold ph-lg dropdownIcon"
|
|
||||||
></i>
|
|
||||||
<MkTime :time="note.createdAt" />
|
|
||||||
</button>
|
|
||||||
<MkVisibility :note="note" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="collapsedReply && appearNote.reply" class="info">
|
|
||||||
<MkAvatar class="avatar" :user="appearNote.reply.user" />
|
|
||||||
<MkUserName
|
|
||||||
class="username"
|
|
||||||
:user="appearNote.reply.user"
|
|
||||||
></MkUserName>
|
|
||||||
<Mfm
|
|
||||||
class="summary"
|
|
||||||
:text="getNoteSummary(appearNote.reply)"
|
|
||||||
:plain="true"
|
|
||||||
:nowrap="true"
|
|
||||||
:custom-emojis="note.emojis"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<article
|
|
||||||
class="article"
|
|
||||||
@contextmenu.stop="onContextmenu"
|
|
||||||
@click="noteClick"
|
|
||||||
:style="{
|
|
||||||
cursor: expandOnNoteClick && !detailedView ? 'pointer' : '',
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<div class="main">
|
|
||||||
<div class="header-container">
|
|
||||||
<MkAvatar class="avatar" :user="appearNote.user" />
|
|
||||||
<XNoteHeader class="header" :note="appearNote" />
|
|
||||||
</div>
|
|
||||||
<div class="body">
|
|
||||||
<MkSubNoteContent
|
|
||||||
class="text"
|
|
||||||
:note="appearNote"
|
|
||||||
:detailed="true"
|
|
||||||
:detailedView="detailedView"
|
|
||||||
:parentId="appearNote.parentId"
|
|
||||||
@push="(e) => router.push(notePage(e))"
|
|
||||||
@focusfooter="footerEl.focus()"
|
|
||||||
@expanded="(e) => setPostExpanded(e)"
|
|
||||||
></MkSubNoteContent>
|
|
||||||
<div v-if="translating || translation" class="translation">
|
|
||||||
<MkLoading v-if="translating" mini />
|
|
||||||
<div v-else class="translated">
|
|
||||||
<b
|
|
||||||
>{{
|
|
||||||
i18n.t("translatedFrom", {
|
|
||||||
x: translation.sourceLang,
|
|
||||||
})
|
|
||||||
}}:
|
|
||||||
</b>
|
|
||||||
<Mfm
|
|
||||||
:text="translation.text"
|
|
||||||
:author="appearNote.user"
|
|
||||||
:i="$i"
|
|
||||||
:custom-emojis="appearNote.emojis"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="detailedView" class="info">
|
|
||||||
<MkA class="created-at" :to="notePage(appearNote)">
|
|
||||||
<MkTime :time="appearNote.createdAt" mode="absolute" />
|
|
||||||
</MkA>
|
|
||||||
</div>
|
|
||||||
<footer ref="footerEl" class="footer" @click.stop tabindex="-1">
|
|
||||||
<XReactionsViewer
|
|
||||||
v-if="enableEmojiReactions"
|
|
||||||
ref="reactionsViewer"
|
|
||||||
:note="appearNote"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
v-tooltip.noDelay.bottom="i18n.ts.reply"
|
|
||||||
class="button _button"
|
|
||||||
@click="reply()"
|
|
||||||
>
|
|
||||||
<i class="ph-arrow-u-up-left ph-bold ph-lg"></i>
|
|
||||||
<template
|
|
||||||
v-if="appearNote.repliesCount > 0 && !detailedView"
|
|
||||||
>
|
|
||||||
<p class="count">{{ appearNote.repliesCount }}</p>
|
|
||||||
</template>
|
|
||||||
</button>
|
|
||||||
<XRenoteButton
|
|
||||||
ref="renoteButton"
|
|
||||||
class="button"
|
|
||||||
:note="appearNote"
|
|
||||||
:count="appearNote.renoteCount"
|
|
||||||
:detailedView="detailedView"
|
|
||||||
/>
|
|
||||||
<XStarButtonNoEmoji
|
|
||||||
v-if="!enableEmojiReactions"
|
|
||||||
class="button"
|
|
||||||
:note="appearNote"
|
|
||||||
:count="
|
|
||||||
Object.values(appearNote.reactions).reduce(
|
|
||||||
(partialSum, val) => partialSum + val,
|
|
||||||
0
|
|
||||||
)
|
|
||||||
"
|
|
||||||
:reacted="appearNote.myReaction != null"
|
|
||||||
/>
|
|
||||||
<XStarButton
|
|
||||||
v-if="
|
|
||||||
enableEmojiReactions &&
|
|
||||||
appearNote.myReaction == null
|
|
||||||
"
|
|
||||||
ref="starButton"
|
|
||||||
class="button"
|
|
||||||
:note="appearNote"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
v-if="
|
|
||||||
enableEmojiReactions &&
|
|
||||||
appearNote.myReaction == null
|
|
||||||
"
|
|
||||||
ref="reactButton"
|
|
||||||
v-tooltip.noDelay.bottom="i18n.ts.reaction"
|
|
||||||
class="button _button"
|
|
||||||
@click="react()"
|
|
||||||
>
|
|
||||||
<i class="ph-smiley ph-bold ph-lg"></i>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
v-if="
|
|
||||||
enableEmojiReactions &&
|
|
||||||
appearNote.myReaction != null
|
|
||||||
"
|
|
||||||
ref="reactButton"
|
|
||||||
class="button _button reacted"
|
|
||||||
@click="undoReact(appearNote)"
|
|
||||||
v-tooltip.noDelay.bottom="i18n.ts.removeReaction"
|
|
||||||
>
|
|
||||||
<i class="ph-minus ph-bold ph-lg"></i>
|
|
||||||
</button>
|
|
||||||
<XQuoteButton class="button" :note="appearNote" />
|
|
||||||
<button
|
|
||||||
ref="menuButton"
|
|
||||||
v-tooltip.noDelay.bottom="i18n.ts.more"
|
|
||||||
class="button _button"
|
|
||||||
@click="menu()"
|
|
||||||
>
|
|
||||||
<i class="ph-dots-three-outline ph-bold ph-lg"></i>
|
|
||||||
</button>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
</div>
|
|
||||||
<button v-else class="muted _button" @click="muted.muted = false">
|
|
||||||
<I18n :src="softMuteReasonI18nSrc(muted.what)" tag="small">
|
|
||||||
<template #name>
|
|
||||||
<MkA
|
|
||||||
v-user-preview="note.userId"
|
|
||||||
class="name"
|
|
||||||
:to="userPage(note.user)"
|
|
||||||
>
|
|
||||||
<MkUserName :user="note.user" />
|
|
||||||
</MkA>
|
|
||||||
</template>
|
|
||||||
<template #reason>
|
|
||||||
<b class="_blur_text">{{ muted.matched.join(", ") }}</b>
|
|
||||||
</template>
|
|
||||||
</I18n>
|
|
||||||
</button>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import type { Ref } from "vue";
|
|
||||||
import { computed, inject, onMounted, ref, toRaw } from "vue";
|
|
||||||
import * as mfm from "mfm-js";
|
|
||||||
import type * as misskey from "calckey-js";
|
|
||||||
import MkNoteSub from "@/components/MkNoteSub.vue";
|
|
||||||
import MkSubNoteContent from "./MkSubNoteContent.vue";
|
|
||||||
import XNoteHeader from "@/components/MkNoteHeader.vue";
|
|
||||||
import XRenoteButton from "@/components/MkRenoteButton.vue";
|
|
||||||
import XReactionsViewer from "@/components/MkReactionsViewer.vue";
|
|
||||||
import XStarButton from "@/components/MkStarButton.vue";
|
|
||||||
import XStarButtonNoEmoji from "@/components/MkStarButtonNoEmoji.vue";
|
|
||||||
import XQuoteButton from "@/components/MkQuoteButton.vue";
|
|
||||||
import MkVisibility from "@/components/MkVisibility.vue";
|
|
||||||
import copyToClipboard from "@/scripts/copy-to-clipboard";
|
|
||||||
import { url } from "@/config";
|
|
||||||
import { pleaseLogin } from "@/scripts/please-login";
|
|
||||||
import { focusNext, focusPrev } from "@/scripts/focus";
|
|
||||||
import { getWordSoftMute } from "@/scripts/check-word-mute";
|
|
||||||
import { useRouter } from "@/router";
|
|
||||||
import { userPage } from "@/filters/user";
|
|
||||||
import * as os from "@/os";
|
|
||||||
import { defaultStore, noteViewInterruptors } from "@/store";
|
|
||||||
import { reactionPicker } from "@/scripts/reaction-picker";
|
|
||||||
import { $i } from "@/account";
|
|
||||||
import { i18n } from "@/i18n";
|
|
||||||
import { getNoteMenu } from "@/scripts/get-note-menu";
|
|
||||||
import { useNoteCapture } from "@/scripts/use-note-capture";
|
|
||||||
import { notePage } from "@/filters/note";
|
|
||||||
import { getNoteSummary } from "@/scripts/get-note-summary";
|
|
||||||
import {
|
|
||||||
magEffectiveNote,
|
|
||||||
magIsRenote,
|
|
||||||
magReactionToLegacy,
|
|
||||||
} from "@/scripts-mag/mag-util";
|
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
note: misskey.entities.Note;
|
|
||||||
pinned?: boolean;
|
|
||||||
detailedView?: boolean;
|
|
||||||
collapsedReply?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
let note = $ref<misskey.entities.Note>(structuredClone(toRaw(props.note)));
|
|
||||||
|
|
||||||
const softMuteReasonI18nSrc = (what?: string) => {
|
|
||||||
if (what === "note") return i18n.ts.userSaysSomethingReason;
|
|
||||||
if (what === "reply") return i18n.ts.userSaysSomethingReasonReply;
|
|
||||||
if (what === "renote") return i18n.ts.userSaysSomethingReasonRenote;
|
|
||||||
if (what === "quote") return i18n.ts.userSaysSomethingReasonQuote;
|
|
||||||
|
|
||||||
// I don't think here is reachable, but just in case
|
|
||||||
return i18n.ts.userSaysSomething;
|
|
||||||
};
|
|
||||||
|
|
||||||
// plugin
|
|
||||||
if (noteViewInterruptors.length > 0) {
|
|
||||||
onMounted(async () => {
|
|
||||||
let result = structuredClone(toRaw(note));
|
|
||||||
for (const interruptor of noteViewInterruptors) {
|
|
||||||
result = await interruptor.handler(result);
|
|
||||||
}
|
|
||||||
note = result;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const el = ref<HTMLElement>();
|
|
||||||
const footerEl = ref<HTMLElement>();
|
|
||||||
const menuButton = ref<HTMLElement>();
|
|
||||||
const starButton = ref<InstanceType<typeof XStarButton>>();
|
|
||||||
const renoteButton = ref<InstanceType<typeof XRenoteButton>>();
|
|
||||||
const renoteTime = ref<HTMLElement>();
|
|
||||||
const reactButton = ref<HTMLElement>();
|
|
||||||
let appearNote = $computed(
|
|
||||||
() => magEffectiveNote(note) as misskey.entities.Note
|
|
||||||
);
|
|
||||||
const isMyRenote = $i && $i.id === note.userId;
|
|
||||||
const showContent = ref(false);
|
|
||||||
const isDeleted = ref(false);
|
|
||||||
const muted = ref(getWordSoftMute(note, $i, defaultStore.state.mutedWords));
|
|
||||||
const translation = ref(null);
|
|
||||||
const translating = ref(false);
|
|
||||||
const enableEmojiReactions = defaultStore.state.enableEmojiReactions;
|
|
||||||
const expandOnNoteClick = defaultStore.state.expandOnNoteClick;
|
|
||||||
|
|
||||||
const keymap = {
|
|
||||||
r: () => reply(true),
|
|
||||||
"e|a|plus": () => react(true),
|
|
||||||
q: () => renoteButton.value.renote(true),
|
|
||||||
"up|k": focusBefore,
|
|
||||||
"down|j": focusAfter,
|
|
||||||
esc: blur,
|
|
||||||
"m|o": () => menu(true),
|
|
||||||
s: () => showContent.value !== showContent.value,
|
|
||||||
};
|
|
||||||
|
|
||||||
useNoteCapture({
|
|
||||||
rootEl: el,
|
|
||||||
note: $$(appearNote),
|
|
||||||
isDeletedRef: isDeleted,
|
|
||||||
});
|
|
||||||
|
|
||||||
function reply(viaKeyboard = false): void {
|
|
||||||
pleaseLogin();
|
|
||||||
os.post(
|
|
||||||
{
|
|
||||||
reply: appearNote,
|
|
||||||
animation: !viaKeyboard,
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
focus();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function react(viaKeyboard = false): void {
|
|
||||||
pleaseLogin();
|
|
||||||
blur();
|
|
||||||
reactionPicker.show(
|
|
||||||
reactButton.value,
|
|
||||||
(reaction) => {
|
|
||||||
os.api("notes/reactions/create", {
|
|
||||||
noteId: appearNote.id,
|
|
||||||
reaction: magReactionToLegacy(reaction),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
focus();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function undoReact(note): void {
|
|
||||||
const oldReaction = note.myReaction;
|
|
||||||
if (!oldReaction) return;
|
|
||||||
os.api("notes/reactions/delete", {
|
|
||||||
noteId: note.id,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentClipPage = inject<Ref<misskey.entities.Clip> | null>(
|
|
||||||
"currentClipPage",
|
|
||||||
null
|
|
||||||
);
|
|
||||||
|
|
||||||
function onContextmenu(ev: MouseEvent): void {
|
|
||||||
const isLink = (el: HTMLElement) => {
|
|
||||||
if (el.tagName === "A") return true;
|
|
||||||
// The Audio element's context menu is the browser default, such as for selecting playback speed.
|
|
||||||
if (el.tagName === "AUDIO") return true;
|
|
||||||
if (el.parentElement) {
|
|
||||||
return isLink(el.parentElement);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if (isLink(ev.target)) return;
|
|
||||||
if (window.getSelection().toString() !== "") return;
|
|
||||||
|
|
||||||
if (defaultStore.state.useReactionPickerForContextMenu) {
|
|
||||||
ev.preventDefault();
|
|
||||||
react();
|
|
||||||
} else {
|
|
||||||
os.contextMenu(
|
|
||||||
[
|
|
||||||
{
|
|
||||||
type: "label",
|
|
||||||
text: notePage(appearNote),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: "ph-browser ph-bold ph-lg",
|
|
||||||
text: i18n.ts.openInWindow,
|
|
||||||
action: () => {
|
|
||||||
os.pageWindow(notePage(appearNote));
|
|
||||||
},
|
|
||||||
},
|
|
||||||
notePage(appearNote) != location.pathname
|
|
||||||
? {
|
|
||||||
icon: "ph-arrows-out-simple ph-bold ph-lg",
|
|
||||||
text: i18n.ts.showInPage,
|
|
||||||
action: () => {
|
|
||||||
router.push(notePage(appearNote), "forcePage");
|
|
||||||
},
|
|
||||||
}
|
|
||||||
: undefined,
|
|
||||||
null,
|
|
||||||
{
|
|
||||||
type: "a",
|
|
||||||
icon: "ph-arrow-square-out ph-bold ph-lg",
|
|
||||||
text: i18n.ts.openInNewTab,
|
|
||||||
href: notePage(appearNote),
|
|
||||||
target: "_blank",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: "ph-link-simple ph-bold ph-lg",
|
|
||||||
text: i18n.ts.copyLink,
|
|
||||||
action: () => {
|
|
||||||
copyToClipboard(`${url}${notePage(appearNote)}`);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
appearNote.user.host != null
|
|
||||||
? {
|
|
||||||
type: "a",
|
|
||||||
icon: "ph-arrow-square-up-right ph-bold ph-lg",
|
|
||||||
text: i18n.ts.showOnRemote,
|
|
||||||
href: appearNote.url ?? appearNote.uri ?? "",
|
|
||||||
target: "_blank",
|
|
||||||
}
|
|
||||||
: undefined,
|
|
||||||
],
|
|
||||||
ev
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function menu(viaKeyboard = false): void {
|
|
||||||
os.popupMenu(
|
|
||||||
getNoteMenu({
|
|
||||||
note: note,
|
|
||||||
translating,
|
|
||||||
translation,
|
|
||||||
menuButton,
|
|
||||||
isDeleted,
|
|
||||||
currentClipPage,
|
|
||||||
}),
|
|
||||||
menuButton.value,
|
|
||||||
{
|
|
||||||
viaKeyboard,
|
|
||||||
}
|
|
||||||
).then(focus);
|
|
||||||
}
|
|
||||||
|
|
||||||
function showRenoteMenu(viaKeyboard = false): void {
|
|
||||||
if (!isMyRenote) return;
|
|
||||||
os.popupMenu(
|
|
||||||
[
|
|
||||||
{
|
|
||||||
text: i18n.ts.unrenote,
|
|
||||||
icon: "ph-trash ph-bold ph-lg",
|
|
||||||
danger: true,
|
|
||||||
action: () => {
|
|
||||||
os.api("notes/delete", {
|
|
||||||
noteId: note.id,
|
|
||||||
});
|
|
||||||
isDeleted.value = true;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
renoteTime.value,
|
|
||||||
{
|
|
||||||
viaKeyboard: viaKeyboard,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function focus() {
|
|
||||||
el.value.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
function blur() {
|
|
||||||
el.value.blur();
|
|
||||||
}
|
|
||||||
|
|
||||||
function focusBefore() {
|
|
||||||
focusPrev(el.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
function focusAfter() {
|
|
||||||
focusNext(el.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
function scrollIntoView() {
|
|
||||||
el.value.scrollIntoView();
|
|
||||||
}
|
|
||||||
|
|
||||||
function noteClick(e) {
|
|
||||||
if (
|
|
||||||
document.getSelection().type === "Range" ||
|
|
||||||
props.detailedView ||
|
|
||||||
!expandOnNoteClick
|
|
||||||
) {
|
|
||||||
e.stopPropagation();
|
|
||||||
} else {
|
|
||||||
router.push(notePage(appearNote));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function readPromo() {
|
|
||||||
os.api("promo/read", {
|
|
||||||
noteId: appearNote.id,
|
|
||||||
});
|
|
||||||
isDeleted.value = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
let postIsExpanded = ref(false);
|
|
||||||
|
|
||||||
function setPostExpanded(val: boolean) {
|
|
||||||
postIsExpanded.value = val;
|
|
||||||
}
|
|
||||||
|
|
||||||
const accessibleLabel = computed(() => {
|
|
||||||
let label = `${appearNote.user.username}; `;
|
|
||||||
if (appearNote.renote) {
|
|
||||||
label += `${i18n.t("renoted")} ${appearNote.renote.user.username}; `;
|
|
||||||
if (appearNote.renote.cw) {
|
|
||||||
label += `${i18n.t("cw")}: ${appearNote.renote.cw}; `;
|
|
||||||
if (postIsExpanded.value) {
|
|
||||||
label += `${appearNote.renote.text}; `;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
label += `${appearNote.renote.text}; `;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (appearNote.cw) {
|
|
||||||
label += `${i18n.t("cw")}: ${appearNote.cw}; `;
|
|
||||||
if (postIsExpanded.value) {
|
|
||||||
label += `${appearNote.text}; `;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
label += `${appearNote.text}; `;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const date = new Date(appearNote.createdAt);
|
|
||||||
label += `${date.toLocaleTimeString()}`;
|
|
||||||
return label;
|
|
||||||
});
|
|
||||||
|
|
||||||
defineExpose({
|
|
||||||
focus,
|
|
||||||
blur,
|
|
||||||
scrollIntoView,
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.tkcbzcuz {
|
|
||||||
position: relative;
|
|
||||||
transition: box-shadow 0.1s ease;
|
|
||||||
font-size: 1.05em;
|
|
||||||
overflow: clip;
|
|
||||||
contain: content;
|
|
||||||
-webkit-tap-highlight-color: transparent;
|
|
||||||
|
|
||||||
// これらの指定はパフォーマンス向上には有効だが、ノートの高さは一定でないため、
|
|
||||||
// 下の方までスクロールすると上のノートの高さがここで決め打ちされたものに変化し、表示しているノートの位置が変わってしまう
|
|
||||||
// ノートがマウントされたときに自身の高さを取得し contain-intrinsic-size を設定しなおせばほぼ解決できそうだが、
|
|
||||||
// 今度はその処理自体がパフォーマンス低下の原因にならないか懸念される。また、被リアクションでも高さは変化するため、やはり多少のズレは生じる
|
|
||||||
// 一度レンダリングされた要素はブラウザがよしなにサイズを覚えておいてくれるような実装になるまで待った方が良さそう(なるのか?)
|
|
||||||
//content-visibility: auto;
|
|
||||||
//contain-intrinsic-size: 0 128px;
|
|
||||||
|
|
||||||
&:focus-visible {
|
|
||||||
outline: none;
|
|
||||||
|
|
||||||
&:after {
|
|
||||||
content: "";
|
|
||||||
pointer-events: none;
|
|
||||||
display: block;
|
|
||||||
position: absolute;
|
|
||||||
z-index: 10;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
margin: auto;
|
|
||||||
width: calc(100% - 8px);
|
|
||||||
height: calc(100% - 8px);
|
|
||||||
border: solid 1px var(--focus);
|
|
||||||
border-radius: var(--radius);
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
& > .article > .main {
|
|
||||||
&:hover,
|
|
||||||
&:focus-within {
|
|
||||||
:deep(.footer .button) {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
> .reply-to {
|
|
||||||
& + .note-context {
|
|
||||||
.line::before {
|
|
||||||
content: "";
|
|
||||||
display: block;
|
|
||||||
margin-bottom: -4px;
|
|
||||||
margin-top: 16px;
|
|
||||||
border-left: 2px solid currentColor;
|
|
||||||
margin-left: calc((var(--avatarSize) / 2) - 1px);
|
|
||||||
opacity: 0.25;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.note-context {
|
|
||||||
position: relative;
|
|
||||||
padding: 0 32px 0 32px;
|
|
||||||
display: flex;
|
|
||||||
z-index: 1;
|
|
||||||
&:first-child {
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
> :not(.line) {
|
|
||||||
width: 0;
|
|
||||||
flex-grow: 1;
|
|
||||||
position: relative;
|
|
||||||
line-height: 28px;
|
|
||||||
}
|
|
||||||
> .line {
|
|
||||||
position: relative;
|
|
||||||
z-index: 2;
|
|
||||||
width: var(--avatarSize);
|
|
||||||
display: flex;
|
|
||||||
margin-right: 14px;
|
|
||||||
margin-top: 0;
|
|
||||||
flex-grow: 0;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
> div > i {
|
|
||||||
margin-left: -0.5px;
|
|
||||||
}
|
|
||||||
> .info {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
font-size: 90%;
|
|
||||||
white-space: pre;
|
|
||||||
color: #f6c177;
|
|
||||||
|
|
||||||
> i {
|
|
||||||
margin-right: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .hide {
|
|
||||||
margin-left: auto;
|
|
||||||
color: inherit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
> .renote {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
white-space: pre;
|
|
||||||
color: var(--renote);
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
> i {
|
|
||||||
margin-right: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
> span {
|
|
||||||
overflow: hidden;
|
|
||||||
flex-shrink: 1;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
|
|
||||||
> .name {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
> .info {
|
|
||||||
margin-left: auto;
|
|
||||||
font-size: 0.9em;
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
> .time {
|
|
||||||
flex-shrink: 0;
|
|
||||||
color: inherit;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
> .dropdownIcon {
|
|
||||||
margin-right: 4px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.collapsedReply {
|
|
||||||
.line {
|
|
||||||
opacity: 0.25;
|
|
||||||
&::after {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
border-left: 2px solid currentColor;
|
|
||||||
border-top: 2px solid currentColor;
|
|
||||||
margin-left: calc(var(--avatarSize) / 2 - 1px);
|
|
||||||
width: calc(var(--avatarSize) / 2 + 14px);
|
|
||||||
border-top-left-radius: calc(var(--avatarSize) / 4);
|
|
||||||
top: calc(50% - 1px);
|
|
||||||
height: calc(50% + 5px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.info {
|
|
||||||
color: var(--fgTransparentWeak);
|
|
||||||
transition: color 0.2s;
|
|
||||||
}
|
|
||||||
.avatar {
|
|
||||||
width: 1.2em;
|
|
||||||
height: 1.2em;
|
|
||||||
border-radius: 2em;
|
|
||||||
overflow: hidden;
|
|
||||||
margin-right: 0.4em;
|
|
||||||
background: var(--panelHighlight);
|
|
||||||
}
|
|
||||||
.username {
|
|
||||||
font-weight: 700;
|
|
||||||
flex-shrink: 0;
|
|
||||||
max-width: 30%;
|
|
||||||
&::after {
|
|
||||||
content: ": ";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&:hover,
|
|
||||||
&:focus-within {
|
|
||||||
.info {
|
|
||||||
color: var(--fg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
> .article {
|
|
||||||
position: relative;
|
|
||||||
overflow: clip;
|
|
||||||
padding: 20px 32px 10px;
|
|
||||||
margin-top: -16px;
|
|
||||||
|
|
||||||
&:first-child,
|
|
||||||
&:nth-child(2) {
|
|
||||||
margin-top: -100px;
|
|
||||||
padding-top: 104px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (pointer: coarse) {
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-container {
|
|
||||||
display: flex;
|
|
||||||
position: relative;
|
|
||||||
z-index: 2;
|
|
||||||
> .avatar {
|
|
||||||
flex-shrink: 0;
|
|
||||||
display: block;
|
|
||||||
margin: 0 14px 0 0;
|
|
||||||
width: var(--avatarSize);
|
|
||||||
height: var(--avatarSize);
|
|
||||||
position: relative;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
> .header {
|
|
||||||
width: 0;
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
> .main {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
|
|
||||||
> .body {
|
|
||||||
margin-top: 0.7em;
|
|
||||||
> .translation {
|
|
||||||
border: solid 0.5px var(--divider);
|
|
||||||
border-radius: var(--radius);
|
|
||||||
padding: 12px;
|
|
||||||
margin-top: 8px;
|
|
||||||
}
|
|
||||||
> .renote {
|
|
||||||
padding-top: 8px;
|
|
||||||
> * {
|
|
||||||
padding: 16px;
|
|
||||||
border: solid 1px var(--renote);
|
|
||||||
border-radius: 8px;
|
|
||||||
transition: background 0.2s;
|
|
||||||
&:hover,
|
|
||||||
&:focus-within {
|
|
||||||
background-color: var(--panelHighlight);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
> .info {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 0.7em;
|
|
||||||
margin-top: 16px;
|
|
||||||
opacity: 0.7;
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
|
||||||
> .footer {
|
|
||||||
position: relative;
|
|
||||||
z-index: 2;
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
pointer-events: none; // Allow clicking anything w/out pointer-events: all; to open post
|
|
||||||
margin-top: 0.4em;
|
|
||||||
> :deep(.button) {
|
|
||||||
position: relative;
|
|
||||||
margin: 0;
|
|
||||||
padding: 8px;
|
|
||||||
opacity: 0.7;
|
|
||||||
flex-grow: 1;
|
|
||||||
max-width: 3.5em;
|
|
||||||
width: max-content;
|
|
||||||
min-width: max-content;
|
|
||||||
pointer-events: all;
|
|
||||||
height: auto;
|
|
||||||
transition: opacity 0.2s;
|
|
||||||
&::before {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
inset: 0;
|
|
||||||
bottom: 2px;
|
|
||||||
background: var(--panel);
|
|
||||||
z-index: -1;
|
|
||||||
transition: background 0.2s;
|
|
||||||
}
|
|
||||||
&:first-of-type {
|
|
||||||
margin-left: -0.5em;
|
|
||||||
&::before {
|
|
||||||
border-radius: 100px 0 0 100px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&:last-of-type {
|
|
||||||
&::before {
|
|
||||||
border-radius: 0 100px 100px 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&:hover {
|
|
||||||
color: var(--fgHighlighted);
|
|
||||||
}
|
|
||||||
|
|
||||||
> i {
|
|
||||||
display: inline !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .count {
|
|
||||||
display: inline;
|
|
||||||
margin: 0 0 0 8px;
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.reacted {
|
|
||||||
color: var(--accent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
> .reply {
|
|
||||||
border-top: solid 0.5px var(--divider);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.max-width_500px {
|
|
||||||
font-size: 0.975em;
|
|
||||||
--avatarSize: 46px;
|
|
||||||
padding-top: 6px;
|
|
||||||
> .note-context {
|
|
||||||
padding-inline: 16px;
|
|
||||||
margin-top: 8px;
|
|
||||||
> :not(.line) {
|
|
||||||
margin-top: 0px;
|
|
||||||
}
|
|
||||||
> .line {
|
|
||||||
margin-right: 10px;
|
|
||||||
&::before {
|
|
||||||
margin-top: 8px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
> .article {
|
|
||||||
padding: 18px 16px 8px;
|
|
||||||
&:first-child,
|
|
||||||
&:nth-child(2) {
|
|
||||||
padding-top: 104px;
|
|
||||||
}
|
|
||||||
> .main > .header-container > .avatar {
|
|
||||||
margin-right: 10px;
|
|
||||||
// top: calc(14px + var(--stickyTop, 0px));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.max-width_300px {
|
|
||||||
--avatarSize: 40px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.muted {
|
|
||||||
padding: 8px;
|
|
||||||
text-align: center;
|
|
||||||
opacity: 0.7;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
._blur_text {
|
|
||||||
pointer-events: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -23,10 +23,10 @@
|
||||||
:ad="true"
|
:ad="true"
|
||||||
class="notes"
|
class="notes"
|
||||||
>
|
>
|
||||||
<XNote
|
<XNoteResolvingProxy
|
||||||
:key="note._featuredId_ || note._prId_ || note.id"
|
:key="note._featuredId_ || note._prId_ || note.id"
|
||||||
class="qtqtichx"
|
class="qtqtichx"
|
||||||
:note="note"
|
:note="note.id"
|
||||||
/>
|
/>
|
||||||
</XList>
|
</XList>
|
||||||
</div>
|
</div>
|
||||||
|
@ -37,9 +37,9 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
import type { Paging } from "@/components/MkPagination.vue";
|
import type { Paging } from "@/components/MkPagination.vue";
|
||||||
import XNote from "@/components/MkNote.vue";
|
|
||||||
import XList from "@/components/MkDateSeparatedList.vue";
|
|
||||||
import MkPagination from "@/components/MkPagination.vue";
|
import MkPagination from "@/components/MkPagination.vue";
|
||||||
|
import XNoteResolvingProxy from "@/components/MagNoteResolvingProxy.vue";
|
||||||
|
import XList from "@/components/MkDateSeparatedList.vue";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
|
|
@ -124,12 +124,12 @@
|
||||||
v-if="notification.type === 'renote'"
|
v-if="notification.type === 'renote'"
|
||||||
class="text"
|
class="text"
|
||||||
:to="notePage(notification.note)"
|
:to="notePage(notification.note)"
|
||||||
:title="getNoteSummary(notification.note.renote)"
|
:title="getNoteSummary(notification.note.renote!)"
|
||||||
>
|
>
|
||||||
<span>{{ i18n.ts._notification.renoted }}</span>
|
<span>{{ i18n.ts._notification.renoted }}</span>
|
||||||
<i class="ph-quotes ph-fill ph-lg"></i>
|
<i class="ph-quotes ph-fill ph-lg"></i>
|
||||||
<Mfm
|
<Mfm
|
||||||
:text="getNoteSummary(notification.note.renote)"
|
:text="getNoteSummary(notification.note.renote!)"
|
||||||
:plain="true"
|
:plain="true"
|
||||||
:nowrap="!full"
|
:nowrap="!full"
|
||||||
:custom-emojis="notification.note.renote.emojis"
|
:custom-emojis="notification.note.renote.emojis"
|
||||||
|
|
|
@ -18,14 +18,14 @@
|
||||||
:items="notifications"
|
:items="notifications"
|
||||||
:no-gap="true"
|
:no-gap="true"
|
||||||
>
|
>
|
||||||
<XNote
|
<XNoteResolvingProxy
|
||||||
v-if="
|
v-if="
|
||||||
['reply', 'quote', 'mention'].includes(
|
['reply', 'quote', 'mention'].includes(
|
||||||
notification.type
|
notification.type
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
:key="notification.id"
|
:key="notification.id"
|
||||||
:note="notification.note"
|
:note="notification.note.id"
|
||||||
:collapsedReply="
|
:collapsedReply="
|
||||||
notification.type === 'reply' ||
|
notification.type === 'reply' ||
|
||||||
(notification.type === 'mention' &&
|
(notification.type === 'mention' &&
|
||||||
|
@ -46,20 +46,12 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import {
|
import { computed, onMounted, onUnmounted, ref } from "vue";
|
||||||
defineComponent,
|
|
||||||
markRaw,
|
|
||||||
onUnmounted,
|
|
||||||
onMounted,
|
|
||||||
computed,
|
|
||||||
ref,
|
|
||||||
} from "vue";
|
|
||||||
import { notificationTypes } from "calckey-js";
|
import { notificationTypes } from "calckey-js";
|
||||||
import MkPagination, { Paging } from "@/components/MkPagination.vue";
|
import MkPagination, { Paging } from "@/components/MkPagination.vue";
|
||||||
import XNotification from "@/components/MkNotification.vue";
|
import XNotification from "@/components/MkNotification.vue";
|
||||||
import XList from "@/components/MkDateSeparatedList.vue";
|
import XList from "@/components/MkDateSeparatedList.vue";
|
||||||
import XNote from "@/components/MkNote.vue";
|
import XNoteResolvingProxy from "@/components/MagNoteResolvingProxy.vue";
|
||||||
import * as os from "@/os";
|
|
||||||
import { stream } from "@/stream";
|
import { stream } from "@/stream";
|
||||||
import { $i } from "@/account";
|
import { $i } from "@/account";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
|
@ -78,7 +70,7 @@ const pagination: Paging = {
|
||||||
includeTypes: props.includeTypes ?? undefined,
|
includeTypes: props.includeTypes ?? undefined,
|
||||||
excludeTypes: props.includeTypes
|
excludeTypes: props.includeTypes
|
||||||
? undefined
|
? undefined
|
||||||
: $i.mutingNotificationTypes,
|
: $i?.mutingNotificationTypes,
|
||||||
unreadOnly: props.unreadOnly,
|
unreadOnly: props.unreadOnly,
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
|
@ -86,7 +78,7 @@ const pagination: Paging = {
|
||||||
const onNotification = (notification) => {
|
const onNotification = (notification) => {
|
||||||
const isMuted = props.includeTypes
|
const isMuted = props.includeTypes
|
||||||
? !props.includeTypes.includes(notification.type)
|
? !props.includeTypes.includes(notification.type)
|
||||||
: $i.mutingNotificationTypes.includes(notification.type);
|
: $i?.mutingNotificationTypes?.includes(notification.type);
|
||||||
if (isMuted || document.visibilityState === "visible") {
|
if (isMuted || document.visibilityState === "visible") {
|
||||||
stream.send("readNotification", {
|
stream.send("readNotification", {
|
||||||
id: notification.id,
|
id: notification.id,
|
||||||
|
|
|
@ -231,7 +231,7 @@ import insertTextAtCursor from "insert-text-at-cursor";
|
||||||
import { length } from "stringz";
|
import { length } from "stringz";
|
||||||
import { toASCII } from "punycode/";
|
import { toASCII } from "punycode/";
|
||||||
import * as Acct from "calckey-js/built/acct";
|
import * as Acct from "calckey-js/built/acct";
|
||||||
import XNoteSimple from "@/components/MkNoteSimple.vue";
|
import XNoteSimple from "@/components/MagNoteSimple.vue";
|
||||||
import XNotePreview from "@/components/MkNotePreview.vue";
|
import XNotePreview from "@/components/MkNotePreview.vue";
|
||||||
import XPostFormAttaches from "@/components/MkPostFormAttaches.vue";
|
import XPostFormAttaches from "@/components/MkPostFormAttaches.vue";
|
||||||
import XPollEditor from "@/components/MkPollEditor.vue";
|
import XPollEditor from "@/components/MkPollEditor.vue";
|
||||||
|
@ -255,18 +255,14 @@ import { uploadFile } from "@/scripts/upload";
|
||||||
import XCheatSheet from "@/components/MkCheatSheetDialog.vue";
|
import XCheatSheet from "@/components/MkCheatSheetDialog.vue";
|
||||||
import { preprocess } from "@/scripts/preprocess";
|
import { preprocess } from "@/scripts/preprocess";
|
||||||
import { packed } from "magnetar-common";
|
import { packed } from "magnetar-common";
|
||||||
import {
|
import { magLegacyVisibility } from "@/scripts-mag/mag-util";
|
||||||
magLegacyVisibility,
|
|
||||||
magTransMap,
|
|
||||||
magTransProperty,
|
|
||||||
} from "@/scripts-mag/mag-util";
|
|
||||||
|
|
||||||
const modal = inject("modal");
|
const modal = inject("modal");
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
reply?: packed.PackNoteMaybeFull | misskey.entities.Note;
|
reply?: packed.PackNoteMaybeFull;
|
||||||
renote?: packed.PackNoteMaybeFull | misskey.entities.Note;
|
renote?: packed.PackNoteMaybeFull;
|
||||||
mention?: misskey.entities.User;
|
mention?: misskey.entities.User;
|
||||||
specified?: misskey.entities.User;
|
specified?: misskey.entities.User;
|
||||||
initialText?: string;
|
initialText?: string;
|
||||||
|
@ -277,7 +273,7 @@ const props = withDefaults(
|
||||||
)[];
|
)[];
|
||||||
initialLocalOnly?: boolean;
|
initialLocalOnly?: boolean;
|
||||||
initialVisibleUsers?: misskey.entities.User[];
|
initialVisibleUsers?: misskey.entities.User[];
|
||||||
initialNote?: packed.PackNoteMaybeFull | misskey.entities.Note;
|
initialNote?: packed.PackNoteMaybeFull;
|
||||||
instant?: boolean;
|
instant?: boolean;
|
||||||
fixed?: boolean;
|
fixed?: boolean;
|
||||||
autofocus?: boolean;
|
autofocus?: boolean;
|
||||||
|
@ -332,7 +328,7 @@ if (props.initialVisibleUsers) {
|
||||||
}
|
}
|
||||||
let autocomplete = $ref(null);
|
let autocomplete = $ref(null);
|
||||||
let draghover = $ref(false);
|
let draghover = $ref(false);
|
||||||
let quoteId = $ref(null);
|
let quoteId = $ref<string | null>(null);
|
||||||
let hasNotSpecifiedMentions = $ref(false);
|
let hasNotSpecifiedMentions = $ref(false);
|
||||||
let recentHashtags = $ref(JSON.parse(localStorage.getItem("hashtags") || "[]"));
|
let recentHashtags = $ref(JSON.parse(localStorage.getItem("hashtags") || "[]"));
|
||||||
let imeText = $ref("");
|
let imeText = $ref("");
|
||||||
|
@ -483,11 +479,7 @@ if (
|
||||||
visibility = magLegacyVisibility(props.reply.visibility);
|
visibility = magLegacyVisibility(props.reply.visibility);
|
||||||
}
|
}
|
||||||
if (visibility === "specified") {
|
if (visibility === "specified") {
|
||||||
const ids = magTransProperty(
|
const ids = props.reply.visible_user_ids;
|
||||||
props.reply,
|
|
||||||
"visible_user_ids",
|
|
||||||
"visibleUserIds"
|
|
||||||
);
|
|
||||||
if (ids) {
|
if (ids) {
|
||||||
os.api("users/show", {
|
os.api("users/show", {
|
||||||
userIds: ids.filter(
|
userIds: ids.filter(
|
||||||
|
@ -730,7 +722,9 @@ async function onPaste(ev: ClipboardEvent) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
quoteId = paste.substr(url.length).match(/^\/notes\/(.+?)\/?$/)[1];
|
quoteId = paste
|
||||||
|
.substring(url.length)
|
||||||
|
.match(/^\/notes\/(.+?)\/?$/)[1];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -996,38 +990,21 @@ onMounted(() => {
|
||||||
const init = props.initialNote;
|
const init = props.initialNote;
|
||||||
text = init.text ? init.text : "";
|
text = init.text ? init.text : "";
|
||||||
|
|
||||||
files = magTransProperty(init, "attachments", "files") ?? [];
|
files = init.attachments ?? [];
|
||||||
cw = init.cw;
|
cw = init.cw;
|
||||||
useCw = init.cw != null;
|
useCw = init.cw != null;
|
||||||
if (init.poll) {
|
if (init.poll) {
|
||||||
poll = {
|
poll = {
|
||||||
choices: magTransMap(
|
choices: init.poll.options.map((x) => x.title),
|
||||||
init.poll,
|
multiple: init.poll.multiple_choice,
|
||||||
"options",
|
expiresAt: init.poll.expires_at,
|
||||||
"choices",
|
|
||||||
(a) => a.map((x) => x.title),
|
|
||||||
(b) => b.map((x) => x.text)
|
|
||||||
),
|
|
||||||
multiple: magTransProperty(
|
|
||||||
init.poll,
|
|
||||||
"multiple_choice",
|
|
||||||
"multiple"
|
|
||||||
),
|
|
||||||
expiresAt: magTransProperty(
|
|
||||||
init.poll,
|
|
||||||
"expires_at",
|
|
||||||
"expiresAt"
|
|
||||||
),
|
|
||||||
// TODO(Natty)
|
// TODO(Natty)
|
||||||
expiredAfter: null,
|
expiredAfter: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
visibility = magLegacyVisibility(init.visibility);
|
visibility = magLegacyVisibility(init.visibility);
|
||||||
localOnly =
|
localOnly = init.local_only ?? false;
|
||||||
magTransProperty(init, "local_only", "localOnly") ?? false;
|
quoteId = init.renoted_note ? init.renoted_note.id : null;
|
||||||
quoteId = magTransProperty(init, "renote", "renoted_note")
|
|
||||||
? magTransProperty(init, "renote", "renoted_note")!.id
|
|
||||||
: null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
nextTick(() => watchForDraft());
|
nextTick(() => watchForDraft());
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="tdflqwzn" :class="{ isMe }">
|
<div class="tdflqwzn" :class="{ isMe }">
|
||||||
<XReaction
|
<XReaction
|
||||||
v-for="r in reactions"
|
v-for="r in note.reactions"
|
||||||
:key="magReactionToLegacy(r[0])"
|
:key="magReactionToLegacy(r[0])"
|
||||||
:reaction="r[0]"
|
:reaction="r[0]"
|
||||||
:count="r[1]"
|
:count="r[1]"
|
||||||
|
@ -13,54 +13,18 @@
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
import * as misskey from "calckey-js";
|
|
||||||
import { $i } from "@/account";
|
import { $i } from "@/account";
|
||||||
import XReaction from "@/components/MkReactionsViewer.reaction.vue";
|
import XReaction from "@/components/MkReactionsViewer.reaction.vue";
|
||||||
import { packed, types } from "magnetar-common";
|
import { packed } from "magnetar-common";
|
||||||
import {
|
import {
|
||||||
magConvertReaction,
|
magConvertReaction,
|
||||||
magReactionEquals,
|
|
||||||
magReactionToLegacy,
|
magReactionToLegacy,
|
||||||
} from "@/scripts-mag/mag-util";
|
} from "@/scripts-mag/mag-util";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
note: packed.PackNoteMaybeFull | misskey.entities.Note;
|
note: packed.PackNoteMaybeFull;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const reactions = computed(() => {
|
|
||||||
const myReactionMk = (props.note as misskey.entities.Note).myReaction
|
|
||||||
? magConvertReaction((props.note as misskey.entities.Note).myReaction!)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
return Array.isArray(props.note.reactions)
|
|
||||||
? props.note.reactions
|
|
||||||
: Object.entries(props.note.reactions)
|
|
||||||
.filter(([e]) => e)
|
|
||||||
.map(([e, cnt]) => {
|
|
||||||
const parsed = magConvertReaction(
|
|
||||||
e,
|
|
||||||
(name, host) =>
|
|
||||||
(
|
|
||||||
props.note
|
|
||||||
.emojis as misskey.entities.Note["emojis"]
|
|
||||||
).find((e) =>
|
|
||||||
magReactionEquals(
|
|
||||||
magConvertReaction(`:${e.name}:`),
|
|
||||||
{ name, host, url: null! }
|
|
||||||
)
|
|
||||||
)?.url ?? null!
|
|
||||||
);
|
|
||||||
|
|
||||||
return [
|
|
||||||
parsed,
|
|
||||||
cnt,
|
|
||||||
myReactionMk
|
|
||||||
? magReactionEquals(myReactionMk, parsed)
|
|
||||||
: undefined,
|
|
||||||
] as types.ReactionPair;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const initialReactions = new Set(
|
const initialReactions = new Set(
|
||||||
Array.isArray(props.note.reactions)
|
Array.isArray(props.note.reactions)
|
||||||
? props.note.reactions.map((v) => magReactionToLegacy(v[0]))
|
? props.note.reactions.map((v) => magReactionToLegacy(v[0]))
|
||||||
|
|
|
@ -2,7 +2,10 @@ import { markRaw } from "vue";
|
||||||
import { locale } from "@/config";
|
import { locale } from "@/config";
|
||||||
import { I18n } from "@/scripts/i18n";
|
import { I18n } from "@/scripts/i18n";
|
||||||
|
|
||||||
export const i18n = markRaw(new I18n(locale));
|
// HACK: Type checking the locale
|
||||||
|
type LocaleMap = { [property: string]: LocaleMap } & string;
|
||||||
|
|
||||||
|
export const i18n = markRaw(new I18n<LocaleMap>(locale));
|
||||||
|
|
||||||
// このファイルに書きたくないけどここに書かないと何故かVeturが認識しない
|
// このファイルに書きたくないけどここに書かないと何故かVeturが認識しない
|
||||||
declare module "@vue/runtime-core" {
|
declare module "@vue/runtime-core" {
|
||||||
|
|
|
@ -150,7 +150,6 @@ import MkLink from "@/components/MkLink.vue";
|
||||||
import { physics } from "@/scripts/physics";
|
import { physics } from "@/scripts/physics";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
import { defaultStore } from "@/store";
|
import { defaultStore } from "@/store";
|
||||||
import * as os from "@/os";
|
|
||||||
import { definePageMetadata } from "@/scripts/page-metadata";
|
import { definePageMetadata } from "@/scripts/page-metadata";
|
||||||
|
|
||||||
let easterEggReady = false;
|
let easterEggReady = false;
|
||||||
|
@ -181,13 +180,6 @@ function gravity() {
|
||||||
easterEggEngine = physics(containerEl);
|
easterEggEngine = physics(containerEl);
|
||||||
}
|
}
|
||||||
|
|
||||||
function iLoveMisskey() {
|
|
||||||
os.post({
|
|
||||||
initialText: "I $[jelly ❤] #Calckey",
|
|
||||||
instant: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
if (easterEggEngine) {
|
if (easterEggEngine) {
|
||||||
easterEggEngine.stop();
|
easterEggEngine.stop();
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
:no-gap="false"
|
:no-gap="false"
|
||||||
:ad="false"
|
:ad="false"
|
||||||
>
|
>
|
||||||
<XNote
|
<XNoteResolvingProxy
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
:note="item.note"
|
:note="item.note"
|
||||||
:class="$style.note"
|
:class="$style.note"
|
||||||
|
@ -37,7 +37,7 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
import MkPagination from "@/components/MkPagination.vue";
|
import MkPagination from "@/components/MkPagination.vue";
|
||||||
import XNote from "@/components/MkNote.vue";
|
import XNoteResolvingProxy from "@/components/MagNoteResolvingProxy.vue";
|
||||||
import XList from "@/components/MkDateSeparatedList.vue";
|
import XList from "@/components/MkDateSeparatedList.vue";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
import { definePageMetadata } from "@/scripts/page-metadata";
|
import { definePageMetadata } from "@/scripts/page-metadata";
|
||||||
|
|
|
@ -39,7 +39,7 @@ import { ref, watch } from "vue";
|
||||||
import XContainer from "../page-editor.container.vue";
|
import XContainer from "../page-editor.container.vue";
|
||||||
import MkInput from "@/components/form/input.vue";
|
import MkInput from "@/components/form/input.vue";
|
||||||
import MkSwitch from "@/components/form/switch.vue";
|
import MkSwitch from "@/components/form/switch.vue";
|
||||||
import XNote from "@/components/MkNote.vue";
|
import XNote from "@/components/MagNote.vue";
|
||||||
import XNoteDetailed from "@/components/MagNoteDetailed.vue";
|
import XNoteDetailed from "@/components/MagNoteDetailed.vue";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
|
|
|
@ -25,11 +25,11 @@
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
import * as misskey from "calckey-js";
|
|
||||||
import MkPagination from "@/components/MkPagination.vue";
|
import MkPagination from "@/components/MkPagination.vue";
|
||||||
|
import { packed } from "magnetar-common";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
user: misskey.entities.User;
|
user: packed.PackUserBase;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const pagination = {
|
const pagination = {
|
||||||
|
|
|
@ -24,12 +24,12 @@
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
import * as misskey from "calckey-js";
|
|
||||||
import MkUserInfo from "@/components/MkUserInfo.vue";
|
import MkUserInfo from "@/components/MkUserInfo.vue";
|
||||||
import MkPagination from "@/components/MkPagination.vue";
|
import MkPagination from "@/components/MkPagination.vue";
|
||||||
|
import { packed } from "magnetar-common";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
user: misskey.entities.User;
|
user: packed.PackUserBase;
|
||||||
type: "following" | "followers";
|
type: "following" | "followers";
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
|
|
@ -16,20 +16,12 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import {
|
import { computed, watch } from "vue";
|
||||||
defineAsyncComponent,
|
|
||||||
computed,
|
|
||||||
inject,
|
|
||||||
onMounted,
|
|
||||||
onUnmounted,
|
|
||||||
watch,
|
|
||||||
} from "vue";
|
|
||||||
import * as Acct from "calckey-js/built/acct";
|
|
||||||
import * as misskey from "calckey-js";
|
|
||||||
import XFollowList from "./follow-list.vue";
|
import XFollowList from "./follow-list.vue";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
import { definePageMetadata } from "@/scripts/page-metadata";
|
import { definePageMetadata } from "@/scripts/page-metadata";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
|
import { endpoints, packed } from "magnetar-common";
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
@ -38,13 +30,14 @@ const props = withDefaults(
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
|
|
||||||
let user = $ref<null | misskey.entities.UserDetailed>(null);
|
let user = $ref<null | packed.PackUserBase>(null);
|
||||||
let error = $ref(null);
|
let error = $ref(null);
|
||||||
|
|
||||||
function fetchUser(): void {
|
function fetchUser(): void {
|
||||||
if (props.acct == null) return;
|
if (!props.acct) return;
|
||||||
user = null;
|
user = null;
|
||||||
os.api("users/show", Acct.parse(props.acct))
|
|
||||||
|
os.magApi(endpoints.GetUserByAcct, {}, { user_acct: props.acct })
|
||||||
.then((u) => {
|
.then((u) => {
|
||||||
user = u;
|
user = u;
|
||||||
})
|
})
|
||||||
|
@ -66,9 +59,7 @@ definePageMetadata(
|
||||||
user
|
user
|
||||||
? {
|
? {
|
||||||
icon: "ph-user ph-bold ph-lg",
|
icon: "ph-user ph-bold ph-lg",
|
||||||
title: user.name
|
title: `${user.display_name} (@${user.username})`,
|
||||||
? `${user.name} (@${user.username})`
|
|
||||||
: `@${user.username}`,
|
|
||||||
subtitle: i18n.ts.followers,
|
subtitle: i18n.ts.followers,
|
||||||
userName: user,
|
userName: user,
|
||||||
avatar: user,
|
avatar: user,
|
||||||
|
|
|
@ -16,20 +16,12 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import {
|
import { computed, watch } from "vue";
|
||||||
defineAsyncComponent,
|
|
||||||
computed,
|
|
||||||
inject,
|
|
||||||
onMounted,
|
|
||||||
onUnmounted,
|
|
||||||
watch,
|
|
||||||
} from "vue";
|
|
||||||
import * as Acct from "calckey-js/built/acct";
|
|
||||||
import * as misskey from "calckey-js";
|
|
||||||
import XFollowList from "./follow-list.vue";
|
import XFollowList from "./follow-list.vue";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
import { definePageMetadata } from "@/scripts/page-metadata";
|
import { definePageMetadata } from "@/scripts/page-metadata";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
|
import { endpoints, packed } from "magnetar-common";
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
@ -38,13 +30,14 @@ const props = withDefaults(
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
|
|
||||||
let user = $ref<null | misskey.entities.UserDetailed>(null);
|
let user = $ref<null | packed.PackUserBase>(null);
|
||||||
let error = $ref(null);
|
let error = $ref(null);
|
||||||
|
|
||||||
function fetchUser(): void {
|
function fetchUser(): void {
|
||||||
if (props.acct == null) return;
|
if (!props.acct) return;
|
||||||
user = null;
|
user = null;
|
||||||
os.api("users/show", Acct.parse(props.acct))
|
|
||||||
|
os.magApi(endpoints.GetUserByAcct, {}, { user_acct: props.acct })
|
||||||
.then((u) => {
|
.then((u) => {
|
||||||
user = u;
|
user = u;
|
||||||
})
|
})
|
||||||
|
@ -66,9 +59,7 @@ definePageMetadata(
|
||||||
user
|
user
|
||||||
? {
|
? {
|
||||||
icon: "ph-user ph-bold ph-lg",
|
icon: "ph-user ph-bold ph-lg",
|
||||||
title: user.name
|
title: `${user.display_name} (@${user.username})`,
|
||||||
? `${user.name} (@${user.username})`
|
|
||||||
: `@${user.username}`,
|
|
||||||
subtitle: i18n.ts.following,
|
subtitle: i18n.ts.following,
|
||||||
userName: user,
|
userName: user,
|
||||||
avatar: user,
|
avatar: user,
|
||||||
|
|
|
@ -15,13 +15,13 @@
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
import * as misskey from "calckey-js";
|
|
||||||
import MkGalleryPostPreview from "@/components/MkGalleryPostPreview.vue";
|
import MkGalleryPostPreview from "@/components/MkGalleryPostPreview.vue";
|
||||||
import MkPagination from "@/components/MkPagination.vue";
|
import MkPagination from "@/components/MkPagination.vue";
|
||||||
|
import { packed } from "magnetar-common";
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
user: misskey.entities.User;
|
user: packed.PackUserBase;
|
||||||
}>(),
|
}>(),
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
|
|
|
@ -9,13 +9,13 @@
|
||||||
<div class="main">
|
<div class="main">
|
||||||
<div class="profile">
|
<div class="profile">
|
||||||
<MkMoved
|
<MkMoved
|
||||||
v-if="user.movedToUri"
|
v-if="user.moved_to"
|
||||||
:host="user.movedToUri.host"
|
:host="user.moved_to.host"
|
||||||
:acct="user.movedToUri.username"
|
:acct="user.moved_to.username"
|
||||||
/>
|
/>
|
||||||
<MkRemoteCaution
|
<MkRemoteCaution
|
||||||
v-if="user.host != null"
|
v-if="user.host != null"
|
||||||
:href="user.url"
|
:href="user.url!"
|
||||||
class="warn"
|
class="warn"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@
|
||||||
/>
|
/>
|
||||||
<div v-if="$i?.isModerator || $i?.isAdmin">
|
<div v-if="$i?.isModerator || $i?.isAdmin">
|
||||||
<span
|
<span
|
||||||
v-if="user.isSilenced"
|
v-if="user.is_silenced"
|
||||||
style="
|
style="
|
||||||
color: var(--warn);
|
color: var(--warn);
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
|
@ -48,7 +48,7 @@
|
||||||
{{ i18n.ts.silenced }}
|
{{ i18n.ts.silenced }}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
v-if="user.isSuspended"
|
v-if="user.is_suspended"
|
||||||
style="
|
style="
|
||||||
color: var(--error);
|
color: var(--error);
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
|
@ -64,7 +64,7 @@
|
||||||
v-if="
|
v-if="
|
||||||
$i &&
|
$i &&
|
||||||
$i.id != user.id &&
|
$i.id != user.id &&
|
||||||
user.isFollowed
|
user.follows_you
|
||||||
"
|
"
|
||||||
class="followed"
|
class="followed"
|
||||||
>{{ i18n.ts.followsYou }}</span
|
>{{ i18n.ts.followsYou }}</span
|
||||||
|
@ -73,7 +73,7 @@
|
||||||
v-if="
|
v-if="
|
||||||
$i &&
|
$i &&
|
||||||
$i.id != user.id &&
|
$i.id != user.id &&
|
||||||
user.hasPendingFollowRequestToYou
|
user.they_request_follow
|
||||||
"
|
"
|
||||||
class="followed"
|
class="followed"
|
||||||
>{{ i18n.ts.followRequestYou }}:
|
>{{ i18n.ts.followRequestYou }}:
|
||||||
|
@ -89,7 +89,7 @@
|
||||||
><MkAcct :user="user" :detail="true"
|
><MkAcct :user="user" :detail="true"
|
||||||
/></span>
|
/></span>
|
||||||
<span
|
<span
|
||||||
v-if="user.isAdmin"
|
v-if="user.is_admin"
|
||||||
v-tooltip.noDelay="i18n.ts.isAdmin"
|
v-tooltip.noDelay="i18n.ts.isAdmin"
|
||||||
style="color: var(--badge)"
|
style="color: var(--badge)"
|
||||||
><i
|
><i
|
||||||
|
@ -97,7 +97,9 @@
|
||||||
></i
|
></i
|
||||||
></span>
|
></span>
|
||||||
<span
|
<span
|
||||||
v-if="!user.isAdmin && user.isModerator"
|
v-if="
|
||||||
|
!user.is_admin && user.is_moderator
|
||||||
|
"
|
||||||
v-tooltip.noDelay="i18n.ts.isModerator"
|
v-tooltip.noDelay="i18n.ts.isModerator"
|
||||||
style="color: var(--badge)"
|
style="color: var(--badge)"
|
||||||
><i
|
><i
|
||||||
|
@ -105,12 +107,12 @@
|
||||||
></i
|
></i
|
||||||
></span>
|
></span>
|
||||||
<span
|
<span
|
||||||
v-if="user.isLocked"
|
v-if="user.is_locked"
|
||||||
v-tooltip.noDelay="i18n.ts.isLocked"
|
v-tooltip.noDelay="i18n.ts.isLocked"
|
||||||
><i class="ph-lock ph-bold ph-lg"></i
|
><i class="ph-lock ph-bold ph-lg"></i
|
||||||
></span>
|
></span>
|
||||||
<span
|
<span
|
||||||
v-if="user.isBot"
|
v-if="user.is_bot"
|
||||||
v-tooltip.noDelay="i18n.ts.isBot"
|
v-tooltip.noDelay="i18n.ts.isBot"
|
||||||
><i class="ph-robot ph-bold ph-lg"></i
|
><i class="ph-robot ph-bold ph-lg"></i
|
||||||
></span>
|
></span>
|
||||||
|
@ -134,7 +136,7 @@
|
||||||
v-if="
|
v-if="
|
||||||
$i &&
|
$i &&
|
||||||
$i.id != user.id &&
|
$i.id != user.id &&
|
||||||
user.isFollowed
|
user.you_follow
|
||||||
"
|
"
|
||||||
class="followed"
|
class="followed"
|
||||||
>{{ i18n.ts.followsYou }}</span
|
>{{ i18n.ts.followsYou }}</span
|
||||||
|
@ -143,7 +145,7 @@
|
||||||
v-if="
|
v-if="
|
||||||
$i &&
|
$i &&
|
||||||
$i.id != user.id &&
|
$i.id != user.id &&
|
||||||
user.hasPendingFollowRequestToYou
|
user.they_request_follow
|
||||||
"
|
"
|
||||||
class="followed"
|
class="followed"
|
||||||
>
|
>
|
||||||
|
@ -156,14 +158,14 @@
|
||||||
</span>
|
</span>
|
||||||
<div v-if="$i?.isModerator || $i?.isAdmin">
|
<div v-if="$i?.isModerator || $i?.isAdmin">
|
||||||
<span
|
<span
|
||||||
v-if="user.isSilenced"
|
v-if="user.is_silenced"
|
||||||
style="color: var(--warn); padding: 5px"
|
style="color: var(--warn); padding: 5px"
|
||||||
>
|
>
|
||||||
<i class="ph-warning ph-bold ph-lg"></i>
|
<i class="ph-warning ph-bold ph-lg"></i>
|
||||||
{{ i18n.ts.silenced }}
|
{{ i18n.ts.silenced }}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
v-if="user.isSuspended"
|
v-if="user.is_suspended"
|
||||||
style="
|
style="
|
||||||
color: var(--error);
|
color: var(--error);
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
|
@ -179,7 +181,7 @@
|
||||||
><MkAcct :user="user" :detail="true"
|
><MkAcct :user="user" :detail="true"
|
||||||
/></span>
|
/></span>
|
||||||
<span
|
<span
|
||||||
v-if="user.isAdmin"
|
v-if="user.is_admin"
|
||||||
v-tooltip.noDelay="i18n.ts.isAdmin"
|
v-tooltip.noDelay="i18n.ts.isAdmin"
|
||||||
style="color: var(--badge)"
|
style="color: var(--badge)"
|
||||||
><i
|
><i
|
||||||
|
@ -187,7 +189,7 @@
|
||||||
></i
|
></i
|
||||||
></span>
|
></span>
|
||||||
<span
|
<span
|
||||||
v-if="!user.isAdmin && user.isModerator"
|
v-if="!user.is_admin && user.is_moderator"
|
||||||
v-tooltip.noDelay="i18n.ts.isModerator"
|
v-tooltip.noDelay="i18n.ts.isModerator"
|
||||||
style="
|
style="
|
||||||
color: var(--badge);
|
color: var(--badge);
|
||||||
|
@ -198,12 +200,12 @@
|
||||||
></i
|
></i
|
||||||
></span>
|
></span>
|
||||||
<span
|
<span
|
||||||
v-if="user.isLocked"
|
v-if="user.is_locked"
|
||||||
v-tooltip.noDelay="i18n.ts.isLocked"
|
v-tooltip.noDelay="i18n.ts.isLocked"
|
||||||
><i class="ph-lock ph-bold ph-lg"></i
|
><i class="ph-lock ph-bold ph-lg"></i
|
||||||
></span>
|
></span>
|
||||||
<span
|
<span
|
||||||
v-if="user.isBot"
|
v-if="user.is_bot"
|
||||||
v-tooltip.noDelay="i18n.ts.isBot"
|
v-tooltip.noDelay="i18n.ts.isBot"
|
||||||
><i class="ph-robot ph-bold ph-lg"></i
|
><i class="ph-robot ph-bold ph-lg"></i
|
||||||
></span>
|
></span>
|
||||||
|
@ -272,14 +274,14 @@
|
||||||
<dd class="value">
|
<dd class="value">
|
||||||
{{
|
{{
|
||||||
new Date(
|
new Date(
|
||||||
user.createdAt
|
user.created_at
|
||||||
).toLocaleString()
|
).toLocaleString()
|
||||||
}}
|
}}
|
||||||
(<MkTime :time="user.createdAt" />)
|
(<MkTime :time="user.created_at" />)
|
||||||
</dd>
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="user.fields.length > 0" class="fields">
|
<div v-if="user.fields?.length > 0" class="fields">
|
||||||
<dl
|
<dl
|
||||||
v-for="(field, i) in user.fields"
|
v-for="(field, i) in user.fields"
|
||||||
:key="i"
|
:key="i"
|
||||||
|
@ -295,6 +297,7 @@
|
||||||
</dt>
|
</dt>
|
||||||
<dd class="value">
|
<dd class="value">
|
||||||
<Mfm
|
<Mfm
|
||||||
|
:mm="field.value_mm ?? undefined"
|
||||||
:text="field.value"
|
:text="field.value"
|
||||||
:author="user"
|
:author="user"
|
||||||
:i="$i"
|
:i="$i"
|
||||||
|
@ -310,7 +313,7 @@
|
||||||
:to="userPage(user)"
|
:to="userPage(user)"
|
||||||
:class="{ active: page === 'index' }"
|
:class="{ active: page === 'index' }"
|
||||||
>
|
>
|
||||||
<b>{{ number(user.notesCount) }}</b>
|
<b>{{ number(user.note_count) }}</b>
|
||||||
<span>{{ i18n.ts.notes }}</span>
|
<span>{{ i18n.ts.notes }}</span>
|
||||||
</MkA>
|
</MkA>
|
||||||
<MkA
|
<MkA
|
||||||
|
@ -318,7 +321,7 @@
|
||||||
:to="userPage(user, 'following')"
|
:to="userPage(user, 'following')"
|
||||||
:class="{ active: page === 'following' }"
|
:class="{ active: page === 'following' }"
|
||||||
>
|
>
|
||||||
<b>{{ number(user.followingCount) }}</b>
|
<b>{{ number(user.following_count) }}</b>
|
||||||
<span>{{ i18n.ts.following }}</span>
|
<span>{{ i18n.ts.following }}</span>
|
||||||
</MkA>
|
</MkA>
|
||||||
<MkA
|
<MkA
|
||||||
|
@ -326,7 +329,7 @@
|
||||||
:to="userPage(user, 'followers')"
|
:to="userPage(user, 'followers')"
|
||||||
:class="{ active: page === 'followers' }"
|
:class="{ active: page === 'followers' }"
|
||||||
>
|
>
|
||||||
<b>{{ number(user.followersCount) }}</b>
|
<b>{{ number(user.follower_count) }}</b>
|
||||||
<span>{{ i18n.ts.followers }}</span>
|
<span>{{ i18n.ts.followers }}</span>
|
||||||
</MkA>
|
</MkA>
|
||||||
</div>
|
</div>
|
||||||
|
@ -334,9 +337,9 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="contents _gap">
|
<div class="contents _gap">
|
||||||
<div v-if="user.pinnedNotes.length > 0" class="_gap">
|
<div v-if="user.pinned_notes?.length > 0" class="_gap">
|
||||||
<XNote
|
<XNote
|
||||||
v-for="note in user.pinnedNotes"
|
v-for="note in user.pinned_notes"
|
||||||
:key="note.id"
|
:key="note.id"
|
||||||
class="note _block"
|
class="note _block"
|
||||||
:note="note"
|
:note="note"
|
||||||
|
@ -375,16 +378,14 @@
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { defineAsyncComponent, onMounted, onUnmounted } from "vue";
|
import { defineAsyncComponent, onMounted, onUnmounted } from "vue";
|
||||||
import number from "@/filters/number";
|
|
||||||
import calcAge from "s-age";
|
import calcAge from "s-age";
|
||||||
import cityTimezones from "city-timezones";
|
import cityTimezones from "city-timezones";
|
||||||
import XUserTimeline from "./index.timeline.vue";
|
import XUserTimeline from "./index.timeline.vue";
|
||||||
import type * as misskey from "calckey-js";
|
|
||||||
import XNote from "@/components/MkNote.vue";
|
|
||||||
import MkFollowButton from "@/components/MkFollowButton.vue";
|
import MkFollowButton from "@/components/MkFollowButton.vue";
|
||||||
import MkRemoteCaution from "@/components/MkRemoteCaution.vue";
|
import MkRemoteCaution from "@/components/MkRemoteCaution.vue";
|
||||||
import MkInfo from "@/components/MkInfo.vue";
|
import MkInfo from "@/components/MkInfo.vue";
|
||||||
import MkMoved from "@/components/MkMoved.vue";
|
import MkMoved from "@/components/MkMoved.vue";
|
||||||
|
import XNote from "@/components/MagNote.vue";
|
||||||
import { getScrollPosition } from "@/scripts/scroll";
|
import { getScrollPosition } from "@/scripts/scroll";
|
||||||
import { userPage } from "@/filters/user";
|
import { userPage } from "@/filters/user";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
|
@ -396,6 +397,8 @@ import Mfm from "@/components/mfm.vue";
|
||||||
import MkTime from "@/components/global/MkTime.vue";
|
import MkTime from "@/components/global/MkTime.vue";
|
||||||
import MkA from "@/components/global/MkA.vue";
|
import MkA from "@/components/global/MkA.vue";
|
||||||
import page from "@/components/page/page.vue";
|
import page from "@/components/page/page.vue";
|
||||||
|
import { packed } from "magnetar-common";
|
||||||
|
import number from "../../filters/number";
|
||||||
|
|
||||||
const XPhotos = defineAsyncComponent(() => import("./index.photos.vue"));
|
const XPhotos = defineAsyncComponent(() => import("./index.photos.vue"));
|
||||||
const XActivity = defineAsyncComponent(() => import("./index.activity.vue"));
|
const XActivity = defineAsyncComponent(() => import("./index.activity.vue"));
|
||||||
|
@ -403,7 +406,7 @@ const XActivity = defineAsyncComponent(() => import("./index.activity.vue"));
|
||||||
const emit = defineEmits(["refresh"]);
|
const emit = defineEmits(["refresh"]);
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
user: misskey.entities.UserDetailed;
|
user: packed.PackUserMaybeAll;
|
||||||
}>(),
|
}>(),
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
|
@ -414,9 +417,9 @@ let rootEl = $ref<null | HTMLElement>(null);
|
||||||
let bannerEl = $ref<null | HTMLElement>(null);
|
let bannerEl = $ref<null | HTMLElement>(null);
|
||||||
|
|
||||||
const style = $computed(() => {
|
const style = $computed(() => {
|
||||||
if (props.user.bannerUrl == null) return {};
|
if (props.user.banner_url == null) return {};
|
||||||
return {
|
return {
|
||||||
backgroundImage: `url(${props.user.bannerUrl})`,
|
backgroundImage: `url(${props.user.banner_url})`,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -29,16 +29,15 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import {} from "vue";
|
|
||||||
import * as misskey from "calckey-js";
|
|
||||||
import MkContainer from "@/components/MkContainer.vue";
|
import MkContainer from "@/components/MkContainer.vue";
|
||||||
import MkChart from "@/components/MkChart.vue";
|
import MkChart from "@/components/MkChart.vue";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
|
import { packed } from "magnetar-common";
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
user: misskey.entities.User;
|
user: packed.PackUserBase;
|
||||||
limit?: number;
|
limit?: number;
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
|
|
|
@ -37,9 +37,10 @@ import MkContainer from "@/components/MkContainer.vue";
|
||||||
import ImgWithBlurhash from "@/components/MkImgWithBlurhash.vue";
|
import ImgWithBlurhash from "@/components/MkImgWithBlurhash.vue";
|
||||||
import { defaultStore } from "@/store";
|
import { defaultStore } from "@/store";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
|
import { packed } from "magnetar-common";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
user: misskey.entities.UserDetailed;
|
user: packed.PackUserBase;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
let fetching = $ref(true);
|
let fetching = $ref(true);
|
||||||
|
|
|
@ -12,15 +12,15 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, computed } from "vue";
|
import { computed, ref } from "vue";
|
||||||
import * as misskey from "calckey-js";
|
import * as misskey from "calckey-js";
|
||||||
import XNotes from "@/components/MkNotes.vue";
|
import XNotes from "@/components/MkNotes.vue";
|
||||||
import MkTab from "@/components/MkTab.vue";
|
import MkTab from "@/components/MkTab.vue";
|
||||||
import * as os from "@/os";
|
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
|
import { packed } from "magnetar-common";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
user: misskey.entities.UserDetailed;
|
user: packed.PackUserBase | misskey.entities.UserDetailed;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const include = ref<string | null>(null);
|
const include = ref<string | null>(null);
|
||||||
|
|
|
@ -29,18 +29,14 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { defineAsyncComponent, computed, watch } from "vue";
|
import { computed, defineAsyncComponent, watch } from "vue";
|
||||||
import calcAge from "s-age";
|
|
||||||
import * as Acct from "calckey-js/built/acct";
|
|
||||||
import type * as misskey from "calckey-js";
|
|
||||||
import { getScrollPosition } from "@/scripts/scroll";
|
|
||||||
import number from "@/filters/number";
|
|
||||||
import { userPage, acct as getAcct } from "@/filters/user";
|
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
import { useRouter } from "@/router";
|
import { useRouter } from "@/router";
|
||||||
import { definePageMetadata } from "@/scripts/page-metadata";
|
import { definePageMetadata } from "@/scripts/page-metadata";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
import { $i } from "@/account";
|
import { $i } from "@/account";
|
||||||
|
import { endpoints, MagApiError, packed } from "magnetar-common";
|
||||||
|
import * as Acct from "calckey-js/built/acct";
|
||||||
|
|
||||||
const XHome = defineAsyncComponent(() => import("./home.vue"));
|
const XHome = defineAsyncComponent(() => import("./home.vue"));
|
||||||
const XReactions = defineAsyncComponent(() => import("./reactions.vue"));
|
const XReactions = defineAsyncComponent(() => import("./reactions.vue"));
|
||||||
|
@ -61,24 +57,62 @@ const props = withDefaults(
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
let tab = $ref(props.page);
|
let tab = $ref(props.page);
|
||||||
let user = $ref<null | misskey.entities.UserDetailed>(null);
|
let user = $ref<null | packed.PackUserSelfMaybeAll>(null);
|
||||||
let error = $ref(null);
|
let error = $ref<any>(null);
|
||||||
|
|
||||||
function fetchUser(): void {
|
function fetchUser(refetch: boolean = true): void {
|
||||||
if (props.acct == null) return;
|
if (!props.acct) return;
|
||||||
user = null;
|
user = null;
|
||||||
os.api("users/show", Acct.parse(props.acct))
|
|
||||||
|
os.magApi(
|
||||||
|
endpoints.GetUserByAcct,
|
||||||
|
{ relation: true, detail: true, pins: true, profile: true },
|
||||||
|
{ user_acct: props.acct }
|
||||||
|
)
|
||||||
.then((u) => {
|
.then((u) => {
|
||||||
user = u;
|
user = u;
|
||||||
|
|
||||||
|
const dayMs = 86400 * 1000;
|
||||||
|
if (
|
||||||
|
(!user.last_fetched_at ||
|
||||||
|
new Date().getTime() -
|
||||||
|
new Date(user.last_fetched_at).getTime() >
|
||||||
|
dayMs) &&
|
||||||
|
refetch
|
||||||
|
) {
|
||||||
|
os.api("users/show", Acct.parse(props.acct))
|
||||||
|
.then(() => {
|
||||||
|
fetchUser(false);
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
error = e;
|
||||||
|
});
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
error = err;
|
const magError = err as MagApiError;
|
||||||
|
if (!refetch || magError.status !== 404) {
|
||||||
|
error = magError;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
os.api("users/show", Acct.parse(props.acct))
|
||||||
|
.then(() => {
|
||||||
|
fetchUser(false);
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
error = e;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(() => props.acct, fetchUser, {
|
watch(
|
||||||
|
() => props.acct,
|
||||||
|
() => fetchUser(),
|
||||||
|
{
|
||||||
immediate: true,
|
immediate: true,
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const headerActions = $computed(() => []);
|
const headerActions = $computed(() => []);
|
||||||
|
|
||||||
|
@ -90,7 +124,7 @@ const headerTabs = $computed(() =>
|
||||||
title: i18n.ts.overview,
|
title: i18n.ts.overview,
|
||||||
icon: "ph-user ph-bold ph-lg",
|
icon: "ph-user ph-bold ph-lg",
|
||||||
},
|
},
|
||||||
...(($i && $i.id === user.id) || user.publicReactions
|
...(($i && $i.id === user.id) || user.has_public_reactions
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
key: "reactions",
|
key: "reactions",
|
||||||
|
@ -127,15 +161,15 @@ definePageMetadata(
|
||||||
user
|
user
|
||||||
? {
|
? {
|
||||||
icon: "ph-user ph-bold ph-lg",
|
icon: "ph-user ph-bold ph-lg",
|
||||||
title: user.name
|
title: user.display_name
|
||||||
? `${user.name} (@${user.username})`
|
? `${user.display_name} (@${user.username})`
|
||||||
: `@${user.username}`,
|
: `@${user.username}`,
|
||||||
subtitle: `@${getAcct(user)}`,
|
subtitle: user.acct,
|
||||||
userName: user,
|
userName: user,
|
||||||
avatar: user,
|
avatar: user,
|
||||||
path: `/@${user.username}`,
|
path: `/@${user.acct}`,
|
||||||
share: {
|
share: {
|
||||||
title: user.name,
|
title: user.display_name,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
: null
|
: null
|
||||||
|
|
|
@ -13,12 +13,12 @@
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
import * as misskey from "calckey-js";
|
|
||||||
import MkPagePreview from "@/components/MkPagePreview.vue";
|
import MkPagePreview from "@/components/MkPagePreview.vue";
|
||||||
import MkPagination from "@/components/MkPagination.vue";
|
import MkPagination from "@/components/MkPagination.vue";
|
||||||
|
import { packed } from "magnetar-common";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
user: misskey.entities.User;
|
user: packed.PackUserBase;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const pagination = {
|
const pagination = {
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
/>
|
/>
|
||||||
<MkTime :time="item.createdAt" class="createdAt" />
|
<MkTime :time="item.createdAt" class="createdAt" />
|
||||||
</div>
|
</div>
|
||||||
<MkNote :key="item.id" :note="item.note" />
|
<XNoteResolvingProxy :key="item.id" :note="item.note.id" />
|
||||||
</div>
|
</div>
|
||||||
</MkPagination>
|
</MkPagination>
|
||||||
</MkSpacer>
|
</MkSpacer>
|
||||||
|
@ -27,7 +27,7 @@
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
import * as misskey from "calckey-js";
|
import * as misskey from "calckey-js";
|
||||||
import MkPagination from "@/components/MkPagination.vue";
|
import MkPagination from "@/components/MkPagination.vue";
|
||||||
import MkNote from "@/components/MkNote.vue";
|
import XNoteResolvingProxy from "@/components/MagNoteResolvingProxy.vue";
|
||||||
import MkReactionIcon from "@/components/MkReactionIcon.vue";
|
import MkReactionIcon from "@/components/MkReactionIcon.vue";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
|
|
@ -9,9 +9,9 @@
|
||||||
<div class="content _panel" v-if="note.cw == null">
|
<div class="content _panel" v-if="note.cw == null">
|
||||||
<div class="body">
|
<div class="body">
|
||||||
<MkA
|
<MkA
|
||||||
v-if="note.replyId"
|
v-if="note.parent_note_id"
|
||||||
class="reply"
|
class="reply"
|
||||||
:to="`/notes/${note.replyId}`"
|
:to="`/notes/${note.parent_note_id}`"
|
||||||
><i class="ph-arrow-bend-up-left ph-bold ph-lg"></i
|
><i class="ph-arrow-bend-up-left ph-bold ph-lg"></i
|
||||||
></MkA>
|
></MkA>
|
||||||
<Mfm
|
<Mfm
|
||||||
|
@ -23,8 +23,11 @@
|
||||||
/>
|
/>
|
||||||
<!-- <MkA v-if="note.renoteId" class="rp" :to="`/notes/${note.renoteId}`">RN: ...</MkA> -->
|
<!-- <MkA v-if="note.renoteId" class="rp" :to="`/notes/${note.renoteId}`">RN: ...</MkA> -->
|
||||||
</div>
|
</div>
|
||||||
<div v-if="note.files.length > 0" class="richcontent">
|
<div
|
||||||
<XMediaList :media-list="note.files" />
|
v-if="note.attachments?.length > 0"
|
||||||
|
class="richcontent"
|
||||||
|
>
|
||||||
|
<XMediaList :media-list="note.attachments!" />
|
||||||
</div>
|
</div>
|
||||||
<div v-if="note.poll">
|
<div v-if="note.poll">
|
||||||
<XPoll :note="note" :readOnly="true" />
|
<XPoll :note="note" :readOnly="true" />
|
||||||
|
@ -40,10 +43,18 @@
|
||||||
import { defineComponent } from "vue";
|
import { defineComponent } from "vue";
|
||||||
import XReactionsViewer from "@/components/MkReactionsViewer.vue";
|
import XReactionsViewer from "@/components/MkReactionsViewer.vue";
|
||||||
import XMediaList from "@/components/MkMediaList.vue";
|
import XMediaList from "@/components/MkMediaList.vue";
|
||||||
import XPoll from "@/components/MkPoll.vue";
|
import XPoll from "@/components/MagPoll.vue";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
|
import { packed } from "magnetar-common";
|
||||||
|
import { resolveNote } from "@/scripts-mag/mag-util";
|
||||||
|
import { $i } from "@/account";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
|
computed: {
|
||||||
|
$i() {
|
||||||
|
return $i;
|
||||||
|
},
|
||||||
|
},
|
||||||
components: {
|
components: {
|
||||||
XReactionsViewer,
|
XReactionsViewer,
|
||||||
XMediaList,
|
XMediaList,
|
||||||
|
@ -52,14 +63,14 @@ export default defineComponent({
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
notes: [],
|
notes: [] as packed.PackNoteMaybeFull[],
|
||||||
isScrolling: false,
|
isScrolling: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
os.api("notes/featured").then((notes) => {
|
os.api("notes/featured").then(async (notes) => {
|
||||||
this.notes = notes;
|
this.notes = await Promise.all(notes.map(resolveNote));
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import * as Misskey from "calckey-js";
|
import * as Misskey from "calckey-js";
|
||||||
import { packed, types } from "magnetar-common";
|
import { endpoints, packed, types } from "magnetar-common";
|
||||||
import { UnicodeEmojiDef } from "@/scripts/emojilist";
|
import { UnicodeEmojiDef } from "@/scripts/emojilist";
|
||||||
|
import * as os from "@/os";
|
||||||
|
|
||||||
// https://stackoverflow.com/a/50375286 Evil magic
|
// https://stackoverflow.com/a/50375286 Evil magic
|
||||||
type Dist<U> = U extends any ? (k: U) => void : never;
|
type Dist<U> = U extends any ? (k: U) => void : never;
|
||||||
|
@ -331,3 +332,10 @@ export function magReactionIndex(
|
||||||
return magReactionEquals(r, reactionType);
|
return magReactionEquals(r, reactionType);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const resolveNote = async ({ id }: { id: string }) =>
|
||||||
|
os.magApi(
|
||||||
|
endpoints.GetNoteById,
|
||||||
|
{ attachments: true, context: true },
|
||||||
|
{ id }
|
||||||
|
);
|
||||||
|
|
|
@ -9,23 +9,18 @@ import { url } from "@/config";
|
||||||
import { noteActions } from "@/store";
|
import { noteActions } from "@/store";
|
||||||
import { shareAvailable } from "@/scripts/share-available";
|
import { shareAvailable } from "@/scripts/share-available";
|
||||||
import { getUserMenu } from "@/scripts/get-user-menu";
|
import { getUserMenu } from "@/scripts/get-user-menu";
|
||||||
import {
|
import { magEffectiveNote, magTransUsername } from "@/scripts-mag/mag-util";
|
||||||
magEffectiveNote,
|
|
||||||
magTransMap,
|
|
||||||
magTransProperty,
|
|
||||||
magTransUsername,
|
|
||||||
} from "@/scripts-mag/mag-util";
|
|
||||||
import { packed } from "magnetar-common";
|
import { packed } from "magnetar-common";
|
||||||
|
|
||||||
export function getNoteMenu(props: {
|
export function getNoteMenu(props: {
|
||||||
note: packed.PackNoteMaybeFull | misskey.entities.Note;
|
note: packed.PackNoteMaybeFull;
|
||||||
menuButton: Ref<HTMLElement | undefined>;
|
menuButton: HTMLElement | null;
|
||||||
translation: Ref<any>;
|
translation: Ref<any>;
|
||||||
translating: Ref<boolean>;
|
translating: Ref<boolean>;
|
||||||
isDeleted: Ref<boolean>;
|
isDeleted: Ref<boolean>;
|
||||||
currentClipPage?: Ref<misskey.entities.Clip>;
|
currentClipPage?: misskey.entities.Clip;
|
||||||
}) {
|
}) {
|
||||||
const appearNote = magEffectiveNote(props.note);
|
const appearNote = magEffectiveNote(props.note) as packed.PackNoteMaybeFull;
|
||||||
|
|
||||||
function del(): void {
|
function del(): void {
|
||||||
os.confirm({
|
os.confirm({
|
||||||
|
@ -36,7 +31,7 @@ export function getNoteMenu(props: {
|
||||||
|
|
||||||
os.api("notes/delete", {
|
os.api("notes/delete", {
|
||||||
noteId: appearNote.id,
|
noteId: appearNote.id,
|
||||||
});
|
}).then();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,23 +44,23 @@ export function getNoteMenu(props: {
|
||||||
|
|
||||||
os.api("notes/delete", {
|
os.api("notes/delete", {
|
||||||
noteId: appearNote.id,
|
noteId: appearNote.id,
|
||||||
});
|
}).then();
|
||||||
|
|
||||||
os.post({
|
os.post({
|
||||||
initialNote: appearNote,
|
initialNote: appearNote,
|
||||||
renote: magTransProperty(appearNote, "renoted_note", "renote"),
|
renote: appearNote.renoted_note,
|
||||||
reply: magTransProperty(appearNote, "parent_note", "reply"),
|
reply: appearNote.parent_note,
|
||||||
});
|
}).then();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function edit(): void {
|
function edit(): void {
|
||||||
os.post({
|
os.post({
|
||||||
initialNote: appearNote,
|
initialNote: appearNote,
|
||||||
renote: magTransProperty(appearNote, "renoted_note", "renote"),
|
renote: appearNote.renoted_note,
|
||||||
reply: magTransProperty(appearNote, "parent_note", "reply"),
|
reply: appearNote.parent_note,
|
||||||
editId: appearNote.id,
|
editId: appearNote.id,
|
||||||
});
|
}).then();
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleFavorite(favorite: boolean): void {
|
function toggleFavorite(favorite: boolean): void {
|
||||||
|
@ -74,7 +69,7 @@ export function getNoteMenu(props: {
|
||||||
{
|
{
|
||||||
noteId: appearNote.id,
|
noteId: appearNote.id,
|
||||||
}
|
}
|
||||||
);
|
).then();
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleWatch(watch: boolean): void {
|
function toggleWatch(watch: boolean): void {
|
||||||
|
@ -83,7 +78,7 @@ export function getNoteMenu(props: {
|
||||||
{
|
{
|
||||||
noteId: appearNote.id,
|
noteId: appearNote.id,
|
||||||
}
|
}
|
||||||
);
|
).then();
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleThreadMute(mute: boolean): void {
|
function toggleThreadMute(mute: boolean): void {
|
||||||
|
@ -92,22 +87,22 @@ export function getNoteMenu(props: {
|
||||||
{
|
{
|
||||||
noteId: appearNote.id,
|
noteId: appearNote.id,
|
||||||
}
|
}
|
||||||
);
|
).then();
|
||||||
}
|
}
|
||||||
|
|
||||||
function copyContent(): void {
|
function copyContent(): void {
|
||||||
copyToClipboard(appearNote.text);
|
copyToClipboard(appearNote.text);
|
||||||
os.success();
|
os.success().then();
|
||||||
}
|
}
|
||||||
|
|
||||||
function copyLink(): void {
|
function copyLink(): void {
|
||||||
copyToClipboard(`${url}/notes/${appearNote.id}`);
|
copyToClipboard(`${url}/notes/${appearNote.id}`);
|
||||||
os.success();
|
os.success().then();
|
||||||
}
|
}
|
||||||
|
|
||||||
function copyOriginal(): void {
|
function copyOriginal(): void {
|
||||||
copyToClipboard(appearNote.url ?? appearNote.uri);
|
copyToClipboard(appearNote.url ?? appearNote.uri);
|
||||||
os.success();
|
os.success().then();
|
||||||
}
|
}
|
||||||
|
|
||||||
function togglePin(pin: boolean): void {
|
function togglePin(pin: boolean): void {
|
||||||
|
@ -122,7 +117,7 @@ export function getNoteMenu(props: {
|
||||||
os.alert({
|
os.alert({
|
||||||
type: "error",
|
type: "error",
|
||||||
text: i18n.ts.pinLimitExceeded,
|
text: i18n.ts.pinLimitExceeded,
|
||||||
});
|
}).then();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -162,14 +157,15 @@ export function getNoteMenu(props: {
|
||||||
result
|
result
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (clip)
|
||||||
os.apiWithDialog("clips/add-note", {
|
os.apiWithDialog("clips/add-note", {
|
||||||
clipId: clip.id,
|
clipId: clip.id,
|
||||||
noteId: appearNote.id,
|
noteId: appearNote.id,
|
||||||
});
|
}).then();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
...clips.map((clip) => ({
|
...clips?.map((clip) => ({
|
||||||
text: clip.name,
|
text: clip.name,
|
||||||
action: () => {
|
action: () => {
|
||||||
os.promiseDialog(
|
os.promiseDialog(
|
||||||
|
@ -196,9 +192,9 @@ export function getNoteMenu(props: {
|
||||||
os.apiWithDialog("clips/remove-note", {
|
os.apiWithDialog("clips/remove-note", {
|
||||||
clipId: clip.id,
|
clipId: clip.id,
|
||||||
noteId: appearNote.id,
|
noteId: appearNote.id,
|
||||||
});
|
}).then();
|
||||||
if (
|
if (
|
||||||
props.currentClipPage?.value.id ===
|
props.currentClipPage?.id ===
|
||||||
clip.id
|
clip.id
|
||||||
)
|
)
|
||||||
props.isDeleted.value = true;
|
props.isDeleted.value = true;
|
||||||
|
@ -207,34 +203,36 @@ export function getNoteMenu(props: {
|
||||||
os.alert({
|
os.alert({
|
||||||
type: "error",
|
type: "error",
|
||||||
text: err.message + "\n" + err.id,
|
text: err.message + "\n" + err.id,
|
||||||
});
|
}).then();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
})),
|
})),
|
||||||
],
|
],
|
||||||
props.menuButton.value,
|
props.menuButton ?? undefined,
|
||||||
{}
|
{}
|
||||||
).then(focus);
|
).then(focus);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function unclip(): Promise<void> {
|
async function unclip(): Promise<void> {
|
||||||
os.apiWithDialog("clips/remove-note", {
|
os.apiWithDialog("clips/remove-note", {
|
||||||
clipId: props.currentClipPage.value.id,
|
clipId: props.currentClipPage?.id,
|
||||||
noteId: appearNote.id,
|
noteId: appearNote.id,
|
||||||
});
|
}).then();
|
||||||
props.isDeleted.value = true;
|
props.isDeleted.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function share(): void {
|
function share(): void {
|
||||||
navigator.share({
|
navigator
|
||||||
|
.share({
|
||||||
title: i18n.t("noteOf", {
|
title: i18n.t("noteOf", {
|
||||||
user: magTransUsername(appearNote.user),
|
user: magTransUsername(appearNote.user),
|
||||||
}),
|
}),
|
||||||
text: appearNote.text ?? undefined,
|
text: appearNote.text ?? undefined,
|
||||||
url: `${url}/notes/${appearNote.id}`,
|
url: `${url}/notes/${appearNote.id}`,
|
||||||
});
|
})
|
||||||
|
.then();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function translate(): Promise<void> {
|
async function translate(): Promise<void> {
|
||||||
|
@ -254,12 +252,11 @@ export function getNoteMenu(props: {
|
||||||
noteId: appearNote.id,
|
noteId: appearNote.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
const isAppearAuthor =
|
const isAppearAuthor = appearNote.user.id === $i.id;
|
||||||
magTransMap(appearNote, "user", "userId", (u) => u.id) === $i.id;
|
|
||||||
const isModerator = $i.isAdmin || $i.isModerator;
|
const isModerator = $i.isAdmin || $i.isModerator;
|
||||||
|
|
||||||
menu = [
|
menu = [
|
||||||
...(props.currentClipPage?.value.userId === $i.id
|
...(props.currentClipPage?.value?.userId === $i.id
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
icon: "ph-minus-circle ph-bold ph-lg",
|
icon: "ph-minus-circle ph-bold ph-lg",
|
||||||
|
@ -412,7 +409,7 @@ export function getNoteMenu(props: {
|
||||||
},
|
},
|
||||||
{},
|
{},
|
||||||
"closed"
|
"closed"
|
||||||
);
|
).then();
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import * as Acct from "calckey-js/built/acct";
|
|
||||||
import { defineAsyncComponent } from "vue";
|
import { defineAsyncComponent } from "vue";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
import copyToClipboard from "@/scripts/copy-to-clipboard";
|
import copyToClipboard from "@/scripts/copy-to-clipboard";
|
||||||
|
@ -8,8 +7,14 @@ import { userActions } from "@/store";
|
||||||
import { $i, iAmModerator } from "@/account";
|
import { $i, iAmModerator } from "@/account";
|
||||||
import { mainRouter } from "@/router";
|
import { mainRouter } from "@/router";
|
||||||
import { Router } from "@/nirax";
|
import { Router } from "@/nirax";
|
||||||
|
import * as Misskey from "calckey-js";
|
||||||
|
import { packed } from "magnetar-common";
|
||||||
|
import { magTransProperty } from "@/scripts-mag/mag-util";
|
||||||
|
|
||||||
export function getUserMenu(user, router: Router = mainRouter) {
|
export function getUserMenu(
|
||||||
|
user: packed.PackUserMaybeAll | Misskey.entities.UserDetailed,
|
||||||
|
router: Router = mainRouter
|
||||||
|
) {
|
||||||
const meId = $i ? $i.id : null;
|
const meId = $i ? $i.id : null;
|
||||||
|
|
||||||
async function pushList() {
|
async function pushList() {
|
||||||
|
@ -60,11 +65,12 @@ export function getUserMenu(user, router: Router = mainRouter) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function toggleMute() {
|
async function toggleMute() {
|
||||||
if (user.isMuted) {
|
if (magTransProperty(user, "mute", "isMuted")) {
|
||||||
os.apiWithDialog("mute/delete", {
|
os.apiWithDialog("mute/delete", {
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
user.isMuted = false;
|
if ("isMuted" in user) user.isMuted = false;
|
||||||
|
else if ("mute" in user) user.mute = false;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const { canceled, result: period } = await os.select({
|
const { canceled, result: period } = await os.select({
|
||||||
|
@ -112,82 +118,121 @@ export function getUserMenu(user, router: Router = mainRouter) {
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
expiresAt,
|
expiresAt,
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
user.isMuted = true;
|
if ("isMuted" in user) user.isMuted = true;
|
||||||
|
else if ("mute" in user) user.mute = true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function toggleRenoteMute(): Promise<void> {
|
async function toggleRenoteMute(): Promise<void> {
|
||||||
os.apiWithDialog(
|
os.apiWithDialog(
|
||||||
user.isRenoteMuted ? "renote-mute/delete" : "renote-mute/create",
|
magTransProperty(user, "mute_renotes", "isRenoteMuted")
|
||||||
|
? "renote-mute/delete"
|
||||||
|
: "renote-mute/create",
|
||||||
{
|
{
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
}
|
}
|
||||||
).then(() => {
|
).then(() => {
|
||||||
|
if ("isRenoteMuted" in user)
|
||||||
user.isRenoteMuted = !user.isRenoteMuted;
|
user.isRenoteMuted = !user.isRenoteMuted;
|
||||||
|
else if ("mute_renotes" in user)
|
||||||
|
user.mute_renotes = !user.mute_renotes;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function toggleBlock(): Promise<void> {
|
async function toggleBlock(): Promise<void> {
|
||||||
if (
|
if (
|
||||||
!(await getConfirmed(
|
!(await getConfirmed(
|
||||||
user.isBlocking ? i18n.ts.unblockConfirm : i18n.ts.blockConfirm
|
magTransProperty(user, "you_block", "isBlocking")
|
||||||
|
? i18n.ts.unblockConfirm
|
||||||
|
: i18n.ts.blockConfirm
|
||||||
))
|
))
|
||||||
)
|
)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
await os.apiWithDialog(
|
await os.apiWithDialog(
|
||||||
user.isBlocking ? "blocking/delete" : "blocking/create",
|
magTransProperty(user, "you_block", "isBlocking")
|
||||||
|
? "blocking/delete"
|
||||||
|
: "blocking/create",
|
||||||
{
|
{
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
user.isBlocking = !user.isBlocking;
|
|
||||||
await os.api(user.isBlocking ? "mute/create" : "mute/delete", {
|
if ("isBlocking" in user) user.isBlocking = !user.isBlocking;
|
||||||
|
else if ("you_block" in user) user.you_block = !user.you_block;
|
||||||
|
|
||||||
|
await os.api(
|
||||||
|
magTransProperty(user, "you_block", "isBlocking")
|
||||||
|
? "mute/create"
|
||||||
|
: "mute/delete",
|
||||||
|
{
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
});
|
}
|
||||||
user.isMuted = user.isBlocking;
|
);
|
||||||
if (user.isBlocking) {
|
|
||||||
|
if ("isMuted" in user) user.isMuted = user.isBlocking;
|
||||||
|
else if ("mute" in user) user.mute = !user.mute;
|
||||||
|
|
||||||
|
if (magTransProperty(user, "isBlocking", "you_block")) {
|
||||||
await os.api("following/delete", {
|
await os.api("following/delete", {
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
});
|
});
|
||||||
user.isFollowing = false;
|
|
||||||
|
if ("isFollowing" in user) user.isFollowing = false;
|
||||||
|
else if ("you_follow" in user) user.you_follow = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function toggleSilence() {
|
async function toggleSilence() {
|
||||||
if (
|
if (
|
||||||
!(await getConfirmed(
|
!(await getConfirmed(
|
||||||
i18n.t(user.isSilenced ? "unsilenceConfirm" : "silenceConfirm")
|
i18n.t(
|
||||||
|
magTransProperty(user, "is_silenced", "isSilenced")
|
||||||
|
? "unsilenceConfirm"
|
||||||
|
: "silenceConfirm"
|
||||||
|
)
|
||||||
))
|
))
|
||||||
)
|
)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
os.apiWithDialog(
|
os.apiWithDialog(
|
||||||
user.isSilenced ? "admin/unsilence-user" : "admin/silence-user",
|
magTransProperty(user, "isSilenced", "is_silenced")
|
||||||
|
? "admin/unsilence-user"
|
||||||
|
: "admin/silence-user",
|
||||||
{
|
{
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
}
|
}
|
||||||
).then(() => {
|
).then(() => {
|
||||||
user.isSilenced = !user.isSilenced;
|
if ("isSilenced" in user) user.isSilenced = !user.isSilenced;
|
||||||
|
else if ("is_silenced" in user)
|
||||||
|
user.is_silenced = !user.is_silenced;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function toggleSuspend() {
|
async function toggleSuspend() {
|
||||||
if (
|
if (
|
||||||
!(await getConfirmed(
|
!(await getConfirmed(
|
||||||
i18n.t(user.isSuspended ? "unsuspendConfirm" : "suspendConfirm")
|
i18n.t(
|
||||||
|
magTransProperty(user, "is_suspended", "isSuspended")
|
||||||
|
? "unsuspendConfirm"
|
||||||
|
: "suspendConfirm"
|
||||||
|
)
|
||||||
))
|
))
|
||||||
)
|
)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
os.apiWithDialog(
|
os.apiWithDialog(
|
||||||
user.isSuspended ? "admin/unsuspend-user" : "admin/suspend-user",
|
magTransProperty(user, "is_suspended", "isSuspended")
|
||||||
|
? "admin/unsuspend-user"
|
||||||
|
: "admin/suspend-user",
|
||||||
{
|
{
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
}
|
}
|
||||||
).then(() => {
|
).then(() => {
|
||||||
user.isSuspended = !user.isSuspended;
|
if ("isSuspended" in user) user.isSuspended = !user.isSuspended;
|
||||||
|
else if ("is_suspended" in user)
|
||||||
|
user.is_suspended = !user.is_suspended;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,7 +265,9 @@ export function getUserMenu(user, router: Router = mainRouter) {
|
||||||
os.apiWithDialog("following/invalidate", {
|
os.apiWithDialog("following/invalidate", {
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
user.isFollowed = !user.isFollowed;
|
if ("isFollowed" in user) user.isFollowed = !user.isFollowed;
|
||||||
|
else if ("follows_you" in user)
|
||||||
|
user.follows_you = !user.follows_you;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -266,10 +313,10 @@ export function getUserMenu(user, router: Router = mainRouter) {
|
||||||
: undefined,
|
: undefined,
|
||||||
null,
|
null,
|
||||||
{
|
{
|
||||||
icon: user.isRenoteMuted
|
icon: magTransProperty(user, "mute_renotes", "isRenoteMuted")
|
||||||
? "ph-eye ph-bold ph-lg"
|
? "ph-eye ph-bold ph-lg"
|
||||||
: "ph-eye-slash ph-bold ph-lg",
|
: "ph-eye-slash ph-bold ph-lg",
|
||||||
text: user.isRenoteMuted
|
text: magTransProperty(user, "mute_renotes", "isRenoteMuted")
|
||||||
? i18n.ts.renoteUnmute
|
? i18n.ts.renoteUnmute
|
||||||
: i18n.ts.renoteMute,
|
: i18n.ts.renoteMute,
|
||||||
action: toggleRenoteMute,
|
action: toggleRenoteMute,
|
||||||
|
@ -279,21 +326,26 @@ export function getUserMenu(user, router: Router = mainRouter) {
|
||||||
if ($i && meId !== user.id) {
|
if ($i && meId !== user.id) {
|
||||||
menu = menu.concat([
|
menu = menu.concat([
|
||||||
{
|
{
|
||||||
icon: user.isMuted
|
icon: magTransProperty(user, "mute", "isMuted")
|
||||||
? "ph-eye ph-bold ph-lg"
|
? "ph-eye ph-bold ph-lg"
|
||||||
: "ph-eye-slash ph-bold ph-lg",
|
: "ph-eye-slash ph-bold ph-lg",
|
||||||
text: user.isMuted ? i18n.ts.unmute : i18n.ts.mute,
|
text: magTransProperty(user, "mute", "isMuted")
|
||||||
hidden: user.isBlocking === true,
|
? i18n.ts.unmute
|
||||||
|
: i18n.ts.mute,
|
||||||
|
hidden:
|
||||||
|
magTransProperty(user, "you_block", "isBlocking") === true,
|
||||||
action: toggleMute,
|
action: toggleMute,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: "ph-prohibit ph-bold ph-lg",
|
icon: "ph-prohibit ph-bold ph-lg",
|
||||||
text: user.isBlocking ? i18n.ts.unblock : i18n.ts.block,
|
text: magTransProperty(user, "you_block", "isBlocking")
|
||||||
|
? i18n.ts.unblock
|
||||||
|
: i18n.ts.block,
|
||||||
action: toggleBlock,
|
action: toggleBlock,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (user.isFollowed) {
|
if (magTransProperty(user, "follows_you", "isFollowed")) {
|
||||||
menu = menu.concat([
|
menu = menu.concat([
|
||||||
{
|
{
|
||||||
icon: "ph-link-break ph-bold ph-lg",
|
icon: "ph-link-break ph-bold ph-lg",
|
||||||
|
@ -317,12 +369,14 @@ export function getUserMenu(user, router: Router = mainRouter) {
|
||||||
null,
|
null,
|
||||||
{
|
{
|
||||||
icon: "ph-microphone-slash ph-bold ph-lg",
|
icon: "ph-microphone-slash ph-bold ph-lg",
|
||||||
text: user.isSilenced ? i18n.ts.unsilence : i18n.ts.silence,
|
text: magTransProperty(user, "is_silenced", "isSilenced")
|
||||||
|
? i18n.ts.unsilence
|
||||||
|
: i18n.ts.silence,
|
||||||
action: toggleSilence,
|
action: toggleSilence,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: "ph-snowflake ph-bold ph-lg",
|
icon: "ph-snowflake ph-bold ph-lg",
|
||||||
text: user.isSuspended
|
text: magTransProperty(user, "is_suspended", "isSuspended")
|
||||||
? i18n.ts.unsuspend
|
? i18n.ts.unsuspend
|
||||||
: i18n.ts.suspend,
|
: i18n.ts.suspend,
|
||||||
action: toggleSuspend,
|
action: toggleSuspend,
|
||||||
|
|
|
@ -19,7 +19,7 @@ export type PageMetadata = {
|
||||||
subtitle?: string;
|
subtitle?: string;
|
||||||
icon?: string | null;
|
icon?: string | null;
|
||||||
avatar?: packed.PackUserBase | misskey.entities.User | null;
|
avatar?: packed.PackUserBase | misskey.entities.User | null;
|
||||||
userName?: misskey.entities.User | null;
|
userName?: packed.PackUserBase | misskey.entities.User | null;
|
||||||
bg?: string;
|
bg?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,7 @@ class ReactionPicker {
|
||||||
}
|
}
|
||||||
|
|
||||||
public show(
|
public show(
|
||||||
src: HTMLElement,
|
src: HTMLElement | null,
|
||||||
onChosen: ReactionPicker["onChosen"],
|
onChosen: ReactionPicker["onChosen"],
|
||||||
onClosed: ReactionPicker["onClosed"]
|
onClosed: ReactionPicker["onClosed"]
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -1,18 +1,13 @@
|
||||||
import { onUnmounted, Ref, toRaw } from "vue";
|
import { onUnmounted, Ref, toRaw } from "vue";
|
||||||
import * as misskey from "calckey-js";
|
|
||||||
import { stream } from "@/stream";
|
import { stream } from "@/stream";
|
||||||
import { $i } from "@/account";
|
import { $i } from "@/account";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
import { endpoints, packed } from "magnetar-common";
|
import { endpoints, packed } from "magnetar-common";
|
||||||
import {
|
import { magConvertReaction, magReactionIndex } from "@/scripts-mag/mag-util";
|
||||||
magConvertReaction,
|
|
||||||
magReactionIndex,
|
|
||||||
noteIsMag,
|
|
||||||
} from "@/scripts-mag/mag-util";
|
|
||||||
|
|
||||||
export function useNoteCapture(props: {
|
export function useNoteCapture(props: {
|
||||||
rootEl: Ref<HTMLElement>;
|
rootEl: Ref<HTMLElement>;
|
||||||
note: Ref<packed.PackNoteMaybeFull | misskey.entities.Note>;
|
note: Ref<packed.PackNoteMaybeFull>;
|
||||||
isDeletedRef: Ref<boolean>;
|
isDeletedRef: Ref<boolean>;
|
||||||
}) {
|
}) {
|
||||||
const note = props.note;
|
const note = props.note;
|
||||||
|
@ -23,7 +18,6 @@ export function useNoteCapture(props: {
|
||||||
|
|
||||||
if (id !== note.value.id) return;
|
if (id !== note.value.id) return;
|
||||||
|
|
||||||
if (noteIsMag(note.value)) {
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "reacted": {
|
case "reacted": {
|
||||||
const reaction = body.reaction as string;
|
const reaction = body.reaction as string;
|
||||||
|
@ -118,87 +112,6 @@ export function useNoteCapture(props: {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
switch (type) {
|
|
||||||
case "reacted": {
|
|
||||||
const reaction = body.reaction;
|
|
||||||
|
|
||||||
if (body.emoji) {
|
|
||||||
const emojis = note.value.emojis || [];
|
|
||||||
if (!emojis.includes(body.emoji)) {
|
|
||||||
note.value.emojis = [...emojis, body.emoji];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: reactionsプロパティがない場合ってあったっけ? なければ || {} は消せる
|
|
||||||
const currentCount = note.value.reactions?.[reaction] || 0;
|
|
||||||
|
|
||||||
note.value.reactions[reaction] = currentCount + 1;
|
|
||||||
|
|
||||||
if ($i && body.userId === $i.id) {
|
|
||||||
note.value.myReaction = reaction;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case "unreacted": {
|
|
||||||
const reaction = body.reaction;
|
|
||||||
|
|
||||||
// TODO: reactionsプロパティがない場合ってあったっけ? なければ || {} は消せる
|
|
||||||
const currentCount = note.value.reactions?.[reaction] || 0;
|
|
||||||
|
|
||||||
note.value.reactions[reaction] = Math.max(
|
|
||||||
0,
|
|
||||||
currentCount - 1
|
|
||||||
);
|
|
||||||
|
|
||||||
if ($i && body.userId === $i.id) {
|
|
||||||
note.value.myReaction = undefined;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case "pollVoted": {
|
|
||||||
const choice = body.choice;
|
|
||||||
|
|
||||||
if (note.value.poll) {
|
|
||||||
const choices = [...note.value.poll.choices];
|
|
||||||
choices[choice] = {
|
|
||||||
...choices[choice],
|
|
||||||
votes: choices[choice].votes + 1,
|
|
||||||
...($i && body.userId === $i.id
|
|
||||||
? {
|
|
||||||
isVoted: true,
|
|
||||||
}
|
|
||||||
: {}),
|
|
||||||
};
|
|
||||||
note.value.poll.choices = choices;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case "deleted": {
|
|
||||||
props.isDeletedRef.value = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case "updated": {
|
|
||||||
const editedNote = await os.api("notes/show", {
|
|
||||||
noteId: id,
|
|
||||||
});
|
|
||||||
|
|
||||||
const keys = new Set<string>();
|
|
||||||
Object.keys(editedNote)
|
|
||||||
.concat(Object.keys(note.value))
|
|
||||||
.forEach((key) => keys.add(key));
|
|
||||||
keys.forEach((key) => {
|
|
||||||
note.value[key] = editedNote[key];
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function capture(withHandler = false): void {
|
function capture(withHandler = false): void {
|
||||||
|
|
|
@ -41,3 +41,4 @@ export { DriveFolderBase } from "./types/DriveFolderBase";
|
||||||
export { DriveFolderParentExt } from "./types/DriveFolderParentExt";
|
export { DriveFolderParentExt } from "./types/DriveFolderParentExt";
|
||||||
export { ImageMeta } from "./types/ImageMeta";
|
export { ImageMeta } from "./types/ImageMeta";
|
||||||
export { InstanceTicker } from "./types/InstanceTicker";
|
export { InstanceTicker } from "./types/InstanceTicker";
|
||||||
|
export { MovedTo } from "./types/MovedTo";
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
|
export interface MovedTo { moved_to_uri: string, username: string, host: string, }
|
|
@ -1,5 +1,6 @@
|
||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
import type { MmXml } from "./MmXml";
|
import type { MmXml } from "./MmXml";
|
||||||
|
import type { MovedTo } from "./MovedTo";
|
||||||
import type { ProfileField } from "./ProfileField";
|
import type { ProfileField } from "./ProfileField";
|
||||||
|
|
||||||
export interface UserProfileExt { is_locked: boolean, is_silenced: boolean, is_suspended: boolean, description: string | null, description_mm: MmXml | null, location: string | null, birthday: string | null, fields: Array<ProfileField>, follower_count: number | null, following_count: number | null, note_count: number | null, url: string | null, moved_to_uri: string | null, also_known_as: string | null, banner_url: string | null, banner_blurhash: string | null, has_public_reactions: boolean, }
|
export interface UserProfileExt { is_locked: boolean, is_silenced: boolean, is_suspended: boolean, description: string | null, description_mm: MmXml | null, location: string | null, birthday: string | null, fields: Array<ProfileField>, follower_count: number | null, following_count: number | null, note_count: number | null, url: string | null, moved_to: MovedTo | null, also_known_as: string | null, banner_url: string | null, banner_blurhash: string | null, has_public_reactions: boolean, }
|
|
@ -56,6 +56,14 @@ pub struct UserBase {
|
||||||
|
|
||||||
pack!(PackUserBase, Required<Id> as id & Required<UserBase> as user);
|
pack!(PackUserBase, Required<Id> as id & Required<UserBase> as user);
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
pub struct MovedTo {
|
||||||
|
pub moved_to_uri: String,
|
||||||
|
pub username: String,
|
||||||
|
pub host: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
pub struct UserProfileExt {
|
pub struct UserProfileExt {
|
||||||
|
@ -75,7 +83,7 @@ pub struct UserProfileExt {
|
||||||
|
|
||||||
pub url: Option<String>,
|
pub url: Option<String>,
|
||||||
|
|
||||||
pub moved_to_uri: Option<String>,
|
pub moved_to: Option<MovedTo>,
|
||||||
pub also_known_as: Option<String>,
|
pub also_known_as: Option<String>,
|
||||||
|
|
||||||
pub banner_url: Option<String>,
|
pub banner_url: Option<String>,
|
||||||
|
|
|
@ -5,7 +5,7 @@ use magnetar_sdk::types::emoji::{EmojiContext, PackEmojiBase};
|
||||||
use magnetar_sdk::types::instance::InstanceTicker;
|
use magnetar_sdk::types::instance::InstanceTicker;
|
||||||
use magnetar_sdk::types::note::PackNoteMaybeFull;
|
use magnetar_sdk::types::note::PackNoteMaybeFull;
|
||||||
use magnetar_sdk::types::user::{
|
use magnetar_sdk::types::user::{
|
||||||
AvatarDecoration, PackSecurityKeyBase, ProfileField, SecurityKeyBase, SpeechTransform,
|
AvatarDecoration, MovedTo, PackSecurityKeyBase, ProfileField, SecurityKeyBase, SpeechTransform,
|
||||||
UserAuthOverviewExt, UserBase, UserDetailExt, UserProfileExt, UserProfilePinsEx,
|
UserAuthOverviewExt, UserBase, UserDetailExt, UserProfileExt, UserProfilePinsEx,
|
||||||
UserRelationExt, UserSecretsExt,
|
UserRelationExt, UserSecretsExt,
|
||||||
};
|
};
|
||||||
|
@ -80,6 +80,7 @@ pub struct UserProfileExtSource<'a> {
|
||||||
pub banner: Option<&'a PackDriveFileBase>,
|
pub banner: Option<&'a PackDriveFileBase>,
|
||||||
pub description_mm: Option<&'a MmXml>,
|
pub description_mm: Option<&'a MmXml>,
|
||||||
pub relation: Option<&'a UserRelationExt>,
|
pub relation: Option<&'a UserRelationExt>,
|
||||||
|
pub moved_to: Option<&'a MovedTo>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PackType<UserProfileExtSource<'_>> for UserProfileExt {
|
impl PackType<UserProfileExtSource<'_>> for UserProfileExt {
|
||||||
|
@ -92,6 +93,7 @@ impl PackType<UserProfileExtSource<'_>> for UserProfileExt {
|
||||||
banner,
|
banner,
|
||||||
description_mm,
|
description_mm,
|
||||||
relation,
|
relation,
|
||||||
|
moved_to,
|
||||||
}: UserProfileExtSource,
|
}: UserProfileExtSource,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let follow_visibility = match profile.ff_visibility {
|
let follow_visibility = match profile.ff_visibility {
|
||||||
|
@ -118,7 +120,7 @@ impl PackType<UserProfileExtSource<'_>> for UserProfileExt {
|
||||||
following_count: follow_visibility.then_some(user.following_count as usize),
|
following_count: follow_visibility.then_some(user.following_count as usize),
|
||||||
note_count: Some(user.notes_count as usize),
|
note_count: Some(user.notes_count as usize),
|
||||||
url: profile.url.clone(),
|
url: profile.url.clone(),
|
||||||
moved_to_uri: user.moved_to_uri.clone(),
|
moved_to: moved_to.cloned(),
|
||||||
also_known_as: user.also_known_as.clone(),
|
also_known_as: user.also_known_as.clone(),
|
||||||
banner_url: banner.and_then(|b| b.file.0.url.to_owned()),
|
banner_url: banner.and_then(|b| b.file.0.url.to_owned()),
|
||||||
banner_blurhash: banner.and_then(|b| b.file.0.blurhash.clone()),
|
banner_blurhash: banner.and_then(|b| b.file.0.blurhash.clone()),
|
||||||
|
|
|
@ -13,8 +13,8 @@ use magnetar_sdk::types::drive::PackDriveFileBase;
|
||||||
use magnetar_sdk::types::emoji::EmojiContext;
|
use magnetar_sdk::types::emoji::EmojiContext;
|
||||||
use magnetar_sdk::types::instance::InstanceTicker;
|
use magnetar_sdk::types::instance::InstanceTicker;
|
||||||
use magnetar_sdk::types::user::{
|
use magnetar_sdk::types::user::{
|
||||||
PackSecurityKeyBase, PackUserBase, PackUserMaybeAll, PackUserSelfMaybeAll, ProfileField,
|
MovedTo, PackSecurityKeyBase, PackUserBase, PackUserMaybeAll, PackUserSelfMaybeAll,
|
||||||
SecurityKeyBase, UserAuthOverviewExt, UserBase, UserDetailExt, UserProfileExt,
|
ProfileField, SecurityKeyBase, UserAuthOverviewExt, UserBase, UserDetailExt, UserProfileExt,
|
||||||
UserProfilePinsEx, UserRelationExt, UserSecretsExt,
|
UserProfilePinsEx, UserRelationExt, UserSecretsExt,
|
||||||
};
|
};
|
||||||
use magnetar_sdk::types::{Id, MmXml};
|
use magnetar_sdk::types::{Id, MmXml};
|
||||||
|
@ -153,6 +153,27 @@ impl UserModel {
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let moved = match &user.moved_to_uri {
|
||||||
|
Some(uri) => {
|
||||||
|
let moved = ctx.service.db.get_user_by_uri(uri).await?;
|
||||||
|
|
||||||
|
moved.and_then(|m| {
|
||||||
|
let Some(uri) = m.uri else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(MovedTo {
|
||||||
|
moved_to_uri: uri,
|
||||||
|
username: m.username,
|
||||||
|
host: m
|
||||||
|
.host
|
||||||
|
.unwrap_or_else(|| ctx.service.config.networking.host.to_string()),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
let description_mm = self.tokenize_description(&profile);
|
let description_mm = self.tokenize_description(&profile);
|
||||||
|
|
||||||
let fields = Vec::<ProfileFieldRaw>::deserialize(&profile.fields)?;
|
let fields = Vec::<ProfileFieldRaw>::deserialize(&profile.fields)?;
|
||||||
|
@ -192,6 +213,7 @@ impl UserModel {
|
||||||
.and_then(|desc_mm| mmm::to_xml_string(&desc_mm).map(MmXml).ok())
|
.and_then(|desc_mm| mmm::to_xml_string(&desc_mm).map(MmXml).ok())
|
||||||
.as_ref(),
|
.as_ref(),
|
||||||
relation,
|
relation,
|
||||||
|
moved_to: moved.as_ref(),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue