Merge remote-tracking branch 'mi-dev/develop' into emoji

# Conflicts:
#	CHANGELOG.md
#	packages/misskey-js/etc/misskey-js.api.md
This commit is contained in:
mattyatea 2023-10-22 21:06:59 +09:00
commit b511ab2199
No known key found for this signature in database
GPG key ID: 068E54E2C33BEF9A
26 changed files with 323 additions and 57 deletions

View file

@ -5,14 +5,14 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<div :class="$style.root">
<MkAvatar :class="$style.avatar" :user="$i" link preview/>
<MkAvatar :class="$style.avatar" :user="user" link preview/>
<div :class="$style.main">
<div :class="$style.header">
<MkUserName :user="$i" :nowrap="true"/>
<MkUserName :user="user" :nowrap="true"/>
</div>
<div>
<div>
<Mfm :text="text.trim()" :author="$i" :i="$i"/>
<Mfm :text="text.trim()" :author="user" :i="user"/>
</div>
</div>
</div>
@ -21,10 +21,11 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { } from 'vue';
import { $i } from '@/account.js';
import * as Misskey from 'misskey-js';
const props = defineProps<{
text: string;
user: Misskey.entities.User;
}>();
</script>

View file

@ -73,7 +73,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<input v-show="withHashtags" ref="hashtagsInputEl" v-model="hashtags" :class="$style.hashtags" :placeholder="i18n.ts.hashtags" list="hashtags">
<XPostFormAttaches v-model="files" @detach="detachFile" @changeSensitive="updateFileSensitive" @changeName="updateFileName" @replaceFile="replaceFile"/>
<MkPollEditor v-if="poll" v-model="poll" @destroyed="poll = null"/>
<MkNotePreview v-if="showPreview" :class="$style.preview" :text="text"/>
<MkNotePreview v-if="showPreview" :class="$style.preview" :text="text" :user="postAccount ?? $i"/>
<div v-if="showingOptions" style="padding: 8px 16px;">
</div>
<footer :class="$style.footer">

View file

@ -34,6 +34,7 @@ const props = withDefaults(defineProps<{
textConverter?: (value: number) => string,
showTicks?: boolean;
easing?: boolean;
continuousUpdate?: boolean;
}>(), {
step: 1,
textConverter: (v) => v.toString(),
@ -123,6 +124,10 @@ const onMousedown = (ev: MouseEvent | TouchEvent) => {
const pointerX = ev.touches && ev.touches.length > 0 ? ev.touches[0].clientX : ev.clientX;
const pointerPositionOnContainer = pointerX - (containerRect.left + (thumbWidth / 2));
rawValue.value = Math.min(1, Math.max(0, pointerPositionOnContainer / (containerEl.value!.offsetWidth - thumbWidth)));
if (props.continuousUpdate) {
emit('update:modelValue', finalValue.value);
}
};
let beforeValue = finalValue.value;

View file

@ -23,7 +23,16 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</div>
</div>
<img v-if="decoration || user.avatarDecorations.length > 0" :class="[$style.decoration]" :src="decoration ?? user.avatarDecorations[0].url" alt="">
<img
v-if="showDecoration && (decoration || user.avatarDecorations.length > 0)"
:class="[$style.decoration]"
:src="decoration?.url ?? user.avatarDecorations[0].url"
:style="{
rotate: getDecorationAngle(),
scale: getDecorationScale(),
}"
alt=""
>
</component>
</template>
@ -48,18 +57,28 @@ const props = withDefaults(defineProps<{
link?: boolean;
preview?: boolean;
indicator?: boolean;
decoration?: string;
decoration?: {
url: string;
angle?: number;
flipH?: boolean;
flipV?: boolean;
};
forceShowDecoration?: boolean;
}>(), {
target: null,
link: false,
preview: false,
indicator: false,
decoration: undefined,
forceShowDecoration: false,
});
const emit = defineEmits<{
(ev: 'click', v: MouseEvent): void;
}>();
const showDecoration = props.forceShowDecoration || defaultStore.state.showAvatarDecorations;
const bound = $computed(() => props.link
? { to: userPage(props.user), target: props.target }
: {});
@ -73,6 +92,30 @@ function onClick(ev: MouseEvent): void {
emit('click', ev);
}
function getDecorationAngle() {
let angle;
if (props.decoration) {
angle = props.decoration.angle ?? 0;
} else if (props.user.avatarDecorations.length > 0) {
angle = props.user.avatarDecorations[0].angle ?? 0;
} else {
angle = 0;
}
return angle === 0 ? undefined : `${angle * 360}deg`;
}
function getDecorationScale() {
let scaleX;
if (props.decoration) {
scaleX = props.decoration.flipH ? -1 : 1;
} else if (props.user.avatarDecorations.length > 0) {
scaleX = props.user.avatarDecorations[0].flipH ? -1 : 1;
} else {
scaleX = 1;
}
return scaleX === 1 ? undefined : `${scaleX} 1`;
}
let color = $ref<string | undefined>();
watch(() => props.user.avatarBlurhash, () => {

View file

@ -294,6 +294,7 @@ const patrons = [
'美少女JKぐーちゃん',
'てば',
'たっくん',
'SHO SEKIGUCHI',
];
let thereIsTreasure = $ref($i && !claimedAchievements.includes('foundTreasure'));

View file

@ -30,8 +30,6 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkSwitch v-model="showFixedPostForm">{{ i18n.ts.showFixedPostForm }}</MkSwitch>
<MkSwitch v-model="showFixedPostFormInChannel">{{ i18n.ts.showFixedPostFormInChannel }}</MkSwitch>
<MkSwitch v-model="defaultWithReplies">{{ i18n.ts.withRepliesByDefaultForNewlyFollowed }}</MkSwitch>
<MkButton danger @click="updateRepliesAll(true)"><i class="ti ti-messages"></i> {{ i18n.ts.showRepliesToOthersInTimelineAll }}</MkButton>
<MkButton danger @click="updateRepliesAll(false)"><i class="ti ti-messages-off"></i> {{ i18n.ts.hideRepliesToOthersInTimelineAll }}</MkButton>
<MkFolder>
<template #label>{{ i18n.ts.pinnedList }}</template>
<!-- 複数ピン止め管理できるようにしたいけどめんどいので一旦ひとつのみ -->
@ -119,6 +117,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkSwitch v-model="disableShowingAnimatedImages">{{ i18n.ts.disableShowingAnimatedImages }}</MkSwitch>
<MkSwitch v-model="highlightSensitiveMedia">{{ i18n.ts.highlightSensitiveMedia }}</MkSwitch>
<MkSwitch v-model="squareAvatars">{{ i18n.ts.squareAvatars }}</MkSwitch>
<MkSwitch v-model="showAvatarDecorations">{{ i18n.ts.showAvatarDecorations }}</MkSwitch>
<MkSwitch v-model="useSystemFont">{{ i18n.ts.useSystemFont }}</MkSwitch>
<MkSwitch v-model="disableDrawer">{{ i18n.ts.disableDrawer }}</MkSwitch>
<MkSwitch v-model="forceShowAds">{{ i18n.ts.forceShowAds }}</MkSwitch>
@ -203,7 +202,7 @@ import { unisonReload } from '@/scripts/unison-reload.js';
import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
import { miLocalStorage } from '@/local-storage.js';
import { globalEvents } from '@/events';
import { globalEvents } from '@/events.js';
import { claimAchievement } from '@/scripts/achievements.js';
const lang = ref(miLocalStorage.getItem('lang'));
@ -248,6 +247,7 @@ const instanceTicker = computed(defaultStore.makeGetterSetter('instanceTicker'))
const enableInfiniteScroll = computed(defaultStore.makeGetterSetter('enableInfiniteScroll'));
const useReactionPickerForContextMenu = computed(defaultStore.makeGetterSetter('useReactionPickerForContextMenu'));
const squareAvatars = computed(defaultStore.makeGetterSetter('squareAvatars'));
const showAvatarDecorations = computed(defaultStore.makeGetterSetter('showAvatarDecorations'));
const mediaListWithOneImageAppearance = computed(defaultStore.makeGetterSetter('mediaListWithOneImageAppearance'));
const notificationPosition = computed(defaultStore.makeGetterSetter('notificationPosition'));
const notificationStackAxis = computed(defaultStore.makeGetterSetter('notificationStackAxis'));
@ -334,15 +334,6 @@ async function setPinnedList() {
defaultStore.set('pinnedUserLists', [list]);
}
async function updateRepliesAll(withReplies: boolean) {
const { canceled } = os.confirm({
type: 'warning',
text: withReplies ? i18n.ts.confirmShowRepliesAll : i18n.ts.confirmHideRepliesAll,
});
if (canceled) return;
await os.api('following/update-all', { withReplies });
}
function removePinnedList() {
defaultStore.set('pinnedUserLists', []);
}

View file

@ -73,6 +73,13 @@ SPDX-License-Identifier: AGPL-3.0-only
<FormSection>
<FormLink to="/registry"><template #icon><i class="ti ti-adjustments"></i></template>{{ i18n.ts.registry }}</FormLink>
</FormSection>
<FormSection>
<div class="_gaps_s">
<MkButton danger @click="updateRepliesAll(true)"><i class="ti ti-messages"></i> {{ i18n.ts.showRepliesToOthersInTimelineAll }}</MkButton>
<MkButton danger @click="updateRepliesAll(false)"><i class="ti ti-messages-off"></i> {{ i18n.ts.hideRepliesToOthersInTimelineAll }}</MkButton>
</div>
</FormSection>
</div>
</template>
@ -138,6 +145,15 @@ async function reloadAsk() {
unisonReload();
}
async function updateRepliesAll(withReplies: boolean) {
const { canceled } = os.confirm({
type: 'warning',
text: withReplies ? i18n.ts.confirmShowRepliesAll : i18n.ts.confirmHideRepliesAll,
});
if (canceled) return;
await os.api('following/update-all', { withReplies });
}
watch([
enableCondensedLineForAcct,
], async () => {

View file

@ -0,0 +1,114 @@
<!--
SPDX-FileCopyrightText: syuilo and other misskey contributors
SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<MkModalWindow
ref="dialog"
:width="400"
:height="450"
@close="cancel"
@closed="emit('closed')"
>
<template #header>{{ i18n.ts.avatarDecorations }}</template>
<div>
<MkSpacer :marginMin="20" :marginMax="28">
<div style="text-align: center;">
<div :class="$style.name">{{ decoration.name }}</div>
<MkAvatar style="width: 64px; height: 64px; margin-bottom: 20px;" :user="$i" :decoration="{ url: decoration.url, angle, flipH }" forceShowDecoration/>
</div>
<div class="_gaps_s">
<MkRange v-model="angle" continuousUpdate :min="-0.5" :max="0.5" :step="0.025" :textConverter="(v) => `${Math.floor(v * 360)}°`">
<template #label>{{ i18n.ts.angle }}</template>
</MkRange>
<MkSwitch v-model="flipH">
<template #label>{{ i18n.ts.flip }}</template>
</MkSwitch>
</div>
</MkSpacer>
<div :class="$style.footer" class="_buttonsCenter">
<MkButton v-if="using" primary rounded @click="attach"><i class="ti ti-check"></i> {{ i18n.ts.update }}</MkButton>
<MkButton v-if="using" rounded @click="detach"><i class="ti ti-x"></i> {{ i18n.ts.detach }}</MkButton>
<MkButton v-else primary rounded @click="attach"><i class="ti ti-check"></i> {{ i18n.ts.attach }}</MkButton>
</div>
</div>
</MkModalWindow>
</template>
<script lang="ts" setup>
import { shallowRef, ref, computed } from 'vue';
import MkButton from '@/components/MkButton.vue';
import MkModalWindow from '@/components/MkModalWindow.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import { i18n } from '@/i18n.js';
import * as os from '@/os.js';
import MkFolder from '@/components/MkFolder.vue';
import MkInfo from '@/components/MkInfo.vue';
import MkRange from '@/components/MkRange.vue';
import { $i } from '@/account.js';
const props = defineProps<{
decoration: {
id: string;
url: string;
}
}>();
const emit = defineEmits<{
(ev: 'closed'): void;
}>();
const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
const using = computed(() => $i.avatarDecorations.some(x => x.id === props.decoration.id));
const angle = ref(using.value ? $i.avatarDecorations.find(x => x.id === props.decoration.id).angle ?? 0 : 0);
const flipH = ref(using.value ? $i.avatarDecorations.find(x => x.id === props.decoration.id).flipH ?? false : false);
function cancel() {
dialog.value.close();
}
async function attach() {
const decoration = {
id: props.decoration.id,
angle: angle.value,
flipH: flipH.value,
};
await os.apiWithDialog('i/update', {
avatarDecorations: [decoration],
});
$i.avatarDecorations = [decoration];
dialog.value.close();
}
async function detach() {
await os.apiWithDialog('i/update', {
avatarDecorations: [],
});
$i.avatarDecorations = [];
dialog.value.close();
}
</script>
<style lang="scss" module>
.name {
position: relative;
z-index: 10;
font-weight: bold;
margin-bottom: 28px;
}
.footer {
position: sticky;
bottom: 0;
left: 0;
padding: 12px;
border-top: solid 0.5px var(--divider);
-webkit-backdrop-filter: var(--blur, blur(15px));
backdrop-filter: var(--blur, blur(15px));
}
</style>

View file

@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div class="_gaps_m">
<div :class="$style.avatarAndBanner" :style="{ backgroundImage: $i.bannerUrl ? `url(${ $i.bannerUrl })` : null }">
<div :class="$style.avatarContainer">
<MkAvatar :class="$style.avatar" :user="$i" @click="changeAvatar"/>
<MkAvatar :class="$style.avatar" :user="$i" forceShowDecoration @click="changeAvatar"/>
<MkButton primary rounded @click="changeAvatar">{{ i18n.ts._profile.changeAvatar }}</MkButton>
</div>
<MkButton primary rounded :class="$style.bannerEdit" @click="changeBanner">{{ i18n.ts._profile.changeBanner }}</MkButton>
@ -92,10 +92,10 @@ SPDX-License-Identifier: AGPL-3.0-only
v-for="avatarDecoration in avatarDecorations"
:key="avatarDecoration.id"
:class="[$style.avatarDecoration, { [$style.avatarDecorationActive]: $i.avatarDecorations.some(x => x.id === avatarDecoration.id) }]"
@click="toggleDecoration(avatarDecoration)"
@click="openDecoration(avatarDecoration)"
>
<div :class="$style.avatarDecorationName">{{ avatarDecoration.name }}</div>
<MkAvatar style="width: 64px; height: 64px;" :user="$i" :decoration="avatarDecoration.url"/>
<div :class="$style.avatarDecorationName"><MkCondensedLine :minScale="2 / 3">{{ avatarDecoration.name }}</MkCondensedLine></div>
<MkAvatar style="width: 60px; height: 60px;" :user="$i" :decoration="{ url: avatarDecoration.url }" forceShowDecoration/>
</div>
</div>
</MkFolder>
@ -266,18 +266,10 @@ function changeBanner(ev) {
});
}
function toggleDecoration(avatarDecoration) {
if ($i.avatarDecorations.some(x => x.id === avatarDecoration.id)) {
os.apiWithDialog('i/update', {
avatarDecorations: [],
});
$i.avatarDecorations = [];
} else {
os.apiWithDialog('i/update', {
avatarDecorations: [avatarDecoration.id],
});
$i.avatarDecorations.push(avatarDecoration);
}
function openDecoration(avatarDecoration) {
os.popup(defineAsyncComponent(() => import('./profile.avatar-decoration-dialog.vue')), {
decoration: avatarDecoration,
}, {}, 'closed');
}
const headerActions = $computed(() => []);
@ -377,13 +369,17 @@ definePageMetadata({
.avatarDecoration {
cursor: pointer;
padding: 16px 16px 24px 16px;
padding: 16px 16px 28px 16px;
border: solid 2px var(--divider);
border-radius: 8px;
text-align: center;
font-size: 90%;
overflow: clip;
contain: content;
}
.avatarDecorationActive {
background-color: var(--accentedBg);
border-color: var(--accent);
}
@ -391,6 +387,6 @@ definePageMetadata({
position: relative;
z-index: 10;
font-weight: bold;
margin-bottom: 16px;
margin-bottom: 20px;
}
</style>

View file

@ -293,6 +293,10 @@ export const defaultStore = markRaw(new Storage('base', {
where: 'device',
default: false,
},
showAvatarDecorations: {
where: 'device',
default: true,
},
postFormWithHashtags: {
where: 'device',
default: false,

View file

@ -58,9 +58,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { computed, defineAsyncComponent, ref, watch } from 'vue';
import { openInstanceMenu } from './common';
import { openInstanceMenu } from './common.js';
import * as os from '@/os.js';
import { navbarItemDef } from '@/navbar';
import { navbarItemDef } from '@/navbar.js';
import { $i, openAccountMenu as openAccountMenu_ } from '@/account.js';
import { defaultStore } from '@/store.js';
import { i18n } from '@/i18n.js';
@ -230,6 +230,7 @@ function more(ev: MouseEvent) {
text-align: left;
box-sizing: border-box;
margin-top: 16px;
overflow: clip;
}
.avatar {
@ -401,6 +402,7 @@ function more(ev: MouseEvent) {
display: block;
text-align: center;
width: 100%;
overflow: clip;
}
.avatar {