mirror of
https://github.com/misskey-dev/misskey.git
synced 2026-07-30 20:05:48 +00:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 39d6aecc57 | |||
| edbe30a3df | |||
| ad4751918b | |||
| bd9041bd8f | |||
| 4dcff123df | |||
| 990be44d98 | |||
| 78d65ef3dd | |||
| 0a67d6f1a0 | |||
| e312283ea0 | |||
| e8c78e12d5 |
@@ -13,6 +13,8 @@
|
||||
- Enhance: プロフィールへのリンクをユーザーポップアップのアバターに追加
|
||||
- Enhance: ユーザーのノート、フォロー、フォロワーページへのリンクをユーザーポップアップに追加
|
||||
- Enhance: プッシュ通知を行うための権限確認をより確実に行うように
|
||||
- Enhance: 投稿フォームのチュートリアルを追加
|
||||
- Enhance: 「自動でもっと見る」をほとんどの箇所で利用可能に
|
||||
- Fix: 紙吹雪エフェクトがアニメーション設定を考慮せず常に表示される問題を修正
|
||||
- Fix: ナビゲーションバーのリアルタイムモード切替ボタンの状態をよりわかりやすく表示するように
|
||||
- Fix: ページのタイトルが長いとき、はみ出る問題を修正
|
||||
|
||||
Vendored
+54
@@ -10030,6 +10030,60 @@ export interface Locale extends ILocale {
|
||||
* チャンネルに投稿...
|
||||
*/
|
||||
"channelPlaceholder": string;
|
||||
/**
|
||||
* フォームの説明を表示
|
||||
*/
|
||||
"showHowToUse": string;
|
||||
"_howToUse": {
|
||||
/**
|
||||
* 本文
|
||||
*/
|
||||
"content_title": string;
|
||||
/**
|
||||
* 投稿する内容を入力します。
|
||||
*/
|
||||
"content_description": string;
|
||||
/**
|
||||
* ツールバー
|
||||
*/
|
||||
"toolbar_title": string;
|
||||
/**
|
||||
* ファイルやアンケートの添付、注釈やハッシュタグの設定、絵文字やメンションの挿入などが行えます。
|
||||
*/
|
||||
"toolbar_description": string;
|
||||
/**
|
||||
* アカウントメニュー
|
||||
*/
|
||||
"account_title": string;
|
||||
/**
|
||||
* 投稿するアカウントを切り替えたり、アカウントに保存した下書き・予約投稿を一覧できます。
|
||||
*/
|
||||
"account_description": string;
|
||||
/**
|
||||
* 公開範囲
|
||||
*/
|
||||
"visibility_title": string;
|
||||
/**
|
||||
* ノートを公開する範囲の設定が行えます。
|
||||
*/
|
||||
"visibility_description": string;
|
||||
/**
|
||||
* メニュー
|
||||
*/
|
||||
"menu_title": string;
|
||||
/**
|
||||
* 下書きへの保存、投稿の予約、リアクションの設定など、その他のアクションが行えます。
|
||||
*/
|
||||
"menu_description": string;
|
||||
/**
|
||||
* 投稿ボタン
|
||||
*/
|
||||
"submit_title": string;
|
||||
/**
|
||||
* ノートを投稿します。Ctrl + Enter / Cmd + Enter でも投稿できます。
|
||||
*/
|
||||
"submit_description": string;
|
||||
};
|
||||
"_placeholders": {
|
||||
/**
|
||||
* いまどうしてる?
|
||||
|
||||
@@ -2641,6 +2641,20 @@ _postForm:
|
||||
replyPlaceholder: "このノートに返信..."
|
||||
quotePlaceholder: "このノートを引用..."
|
||||
channelPlaceholder: "チャンネルに投稿..."
|
||||
showHowToUse: "フォームの説明を表示"
|
||||
_howToUse:
|
||||
content_title: "本文"
|
||||
content_description: "投稿する内容を入力します。"
|
||||
toolbar_title: "ツールバー"
|
||||
toolbar_description: "ファイルやアンケートの添付、注釈やハッシュタグの設定、絵文字やメンションの挿入などが行えます。"
|
||||
account_title: "アカウントメニュー"
|
||||
account_description: "投稿するアカウントを切り替えたり、アカウントに保存した下書き・予約投稿を一覧できます。"
|
||||
visibility_title: "公開範囲"
|
||||
visibility_description: "ノートを公開する範囲の設定が行えます。"
|
||||
menu_title: "メニュー"
|
||||
menu_description: "下書きへの保存、投稿の予約、リアクションの設定など、その他のアクションが行えます。"
|
||||
submit_title: "投稿ボタン"
|
||||
submit_description: "ノートを投稿します。Ctrl + Enter / Cmd + Enter でも投稿できます。"
|
||||
_placeholders:
|
||||
a: "いまどうしてる?"
|
||||
b: "何かありましたか?"
|
||||
|
||||
+3
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "misskey",
|
||||
"version": "2025.11.0-alpha.2",
|
||||
"version": "2025.11.0-alpha.3",
|
||||
"codename": "nasubi",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -87,7 +87,8 @@
|
||||
"@aiscript-dev/aiscript-languageserver": "-"
|
||||
},
|
||||
"ignoredBuiltDependencies": [
|
||||
"@sentry-internal/node-cpu-profiler"
|
||||
"@sentry-internal/node-cpu-profiler",
|
||||
"exifreader"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,6 +111,7 @@ export class ChannelEntityService {
|
||||
description: channel.description,
|
||||
userId: channel.userId,
|
||||
bannerUrl: bannerFile ? this.driveFileEntityService.getPublicUrl(bannerFile) : null,
|
||||
bannerId: channel.bannerId,
|
||||
pinnedNoteIds: channel.pinnedNoteIds,
|
||||
color: channel.color,
|
||||
isArchived: channel.isArchived,
|
||||
|
||||
@@ -40,6 +40,11 @@ export const packedChannelSchema = {
|
||||
format: 'url',
|
||||
nullable: true, optional: false,
|
||||
},
|
||||
bannerId: {
|
||||
type: 'string',
|
||||
nullable: true, optional: false,
|
||||
format: 'id',
|
||||
},
|
||||
pinnedNoteIds: {
|
||||
type: 'array',
|
||||
nullable: false, optional: false,
|
||||
|
||||
@@ -174,6 +174,7 @@ export const packedPageSchema = {
|
||||
font: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
enum: ['serif', 'sans-serif'],
|
||||
},
|
||||
script: {
|
||||
type: 'string',
|
||||
|
||||
@@ -33,6 +33,7 @@ export function channel(id = 'somechannelid', name = 'Some Channel', bannerUrl:
|
||||
description: null,
|
||||
userId: null,
|
||||
bannerUrl,
|
||||
bannerId: null,
|
||||
pinnedNoteIds: [],
|
||||
color: '#000',
|
||||
isArchived: false,
|
||||
|
||||
@@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<MkPagination :paginator="paginator" :direction="direction" :autoLoad="autoLoad" :pullToRefresh="pullToRefresh" :withControl="withControl">
|
||||
<MkPagination :paginator="paginator" :direction="direction" :autoLoad="autoLoad" :pullToRefresh="pullToRefresh" :withControl="withControl" :forceDisableInfiniteScroll="forceDisableInfiniteScroll">
|
||||
<template #empty><MkResult type="empty" :text="i18n.ts.noNotes"/></template>
|
||||
|
||||
<template #default="{ items: notes }">
|
||||
@@ -40,26 +40,23 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
<script lang="ts" setup generic="T extends IPaginator<Misskey.entities.Note>">
|
||||
import * as Misskey from 'misskey-js';
|
||||
import type { MkPaginationOptions } from '@/components/MkPagination.vue';
|
||||
import type { IPaginator } from '@/utility/paginator.js';
|
||||
import MkNote from '@/components/MkNote.vue';
|
||||
import MkPagination from '@/components/MkPagination.vue';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { globalEvents, useGlobalEvent } from '@/events.js';
|
||||
import { useGlobalEvent } from '@/events.js';
|
||||
import { isSeparatorNeeded, getSeparatorInfo } from '@/utility/timeline-date-separate.js';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
const props = withDefaults(defineProps<MkPaginationOptions & {
|
||||
paginator: T;
|
||||
noGap?: boolean;
|
||||
|
||||
direction?: 'up' | 'down' | 'both';
|
||||
autoLoad?: boolean;
|
||||
pullToRefresh?: boolean;
|
||||
withControl?: boolean;
|
||||
}>(), {
|
||||
autoLoad: true,
|
||||
direction: 'down',
|
||||
pullToRefresh: true,
|
||||
withControl: true,
|
||||
forceDisableInfiniteScroll: false,
|
||||
});
|
||||
|
||||
useGlobalEvent('noteDeleted', (noteId) => {
|
||||
|
||||
@@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
:leaveActiveClass="prefer.s.animation ? $style.transition_fade_leaveActive : ''"
|
||||
:enterFromClass="prefer.s.animation ? $style.transition_fade_enterFrom : ''"
|
||||
:leaveToClass="prefer.s.animation ? $style.transition_fade_leaveTo : ''"
|
||||
mode="out-in"
|
||||
:mode="prefer.s.animation ? 'out-in' : undefined"
|
||||
>
|
||||
<MkLoading v-if="paginator.fetching.value"/>
|
||||
|
||||
@@ -26,14 +26,14 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
<div v-else key="_root_" class="_gaps">
|
||||
<div v-if="direction === 'up' || direction === 'both'" v-show="upButtonVisible">
|
||||
<MkButton v-if="!upButtonLoading" :class="$style.more" primary rounded @click="upButtonClick">
|
||||
<MkButton v-if="!upButtonLoading" v-appear="shouldEnableInfiniteScroll ? upButtonClick : null" :class="$style.more" primary rounded @click="upButtonClick">
|
||||
{{ i18n.ts.loadMore }}
|
||||
</MkButton>
|
||||
<MkLoading v-else/>
|
||||
</div>
|
||||
<slot :items="getValue(paginator.items)" :fetching="paginator.fetching.value || paginator.fetchingOlder.value"></slot>
|
||||
<div v-if="direction === 'down' || direction === 'both'" v-show="downButtonVisible">
|
||||
<MkButton v-if="!downButtonLoading" :class="$style.more" primary rounded @click="downButtonClick">
|
||||
<MkButton v-if="!downButtonLoading" v-appear="shouldEnableInfiniteScroll ? downButtonClick : null" :class="$style.more" primary rounded @click="downButtonClick">
|
||||
{{ i18n.ts.loadMore }}
|
||||
</MkButton>
|
||||
<MkLoading v-else/>
|
||||
@@ -44,6 +44,24 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export type MkPaginationOptions = {
|
||||
autoLoad?: boolean;
|
||||
/**
|
||||
* ページネーションを進める方向
|
||||
* - up: 上方向
|
||||
* - down: 下方向 (default)
|
||||
* - both: 双方向
|
||||
*
|
||||
* NOTE: この方向はページネーションの方向であって、アイテムの並び順ではない
|
||||
*/
|
||||
direction?: 'up' | 'down' | 'both';
|
||||
pullToRefresh?: boolean;
|
||||
withControl?: boolean;
|
||||
forceDisableInfiniteScroll?: boolean;
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup generic="T extends IPaginator">
|
||||
import { isLink } from '@@/js/is-link.js';
|
||||
import { onMounted, computed, watch, unref } from 'vue';
|
||||
@@ -56,24 +74,18 @@ import MkPullToRefresh from '@/components/MkPullToRefresh.vue';
|
||||
import MkPaginationControl from '@/components/MkPaginationControl.vue';
|
||||
import * as os from '@/os.js';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
const props = withDefaults(defineProps<MkPaginationOptions & {
|
||||
paginator: T;
|
||||
|
||||
// ページネーションを進める方向
|
||||
// up: 上方向
|
||||
// down: 下方向 (default)
|
||||
// both: 双方向
|
||||
// NOTE: この方向はページネーションの方向であって、アイテムの並び順ではない
|
||||
direction?: 'up' | 'down' | 'both';
|
||||
|
||||
autoLoad?: boolean;
|
||||
pullToRefresh?: boolean;
|
||||
withControl?: boolean;
|
||||
}>(), {
|
||||
autoLoad: true,
|
||||
direction: 'down',
|
||||
pullToRefresh: true,
|
||||
withControl: false,
|
||||
forceDisableInfiniteScroll: false,
|
||||
});
|
||||
|
||||
const shouldEnableInfiniteScroll = computed(() => {
|
||||
return prefer.r.enableInfiniteScroll.value && !props.forceDisableInfiniteScroll;
|
||||
});
|
||||
|
||||
function onContextmenu(ev: MouseEvent) {
|
||||
|
||||
@@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<header :class="$style.header">
|
||||
<div :class="$style.headerLeft">
|
||||
<button v-if="!fixed" :class="$style.cancel" class="_button" @click="cancel"><i class="ti ti-x"></i></button>
|
||||
<button v-click-anime v-tooltip="i18n.ts.account" :class="$style.account" class="_button" @click="openAccountMenu">
|
||||
<button ref="accountMenuEl" v-click-anime v-tooltip="i18n.ts.account" :class="$style.account" class="_button" @click="openAccountMenu">
|
||||
<img :class="$style.avatar" :src="(postAccount ?? $i).avatarUrl" style="border-radius: 100%;"/>
|
||||
</button>
|
||||
</div>
|
||||
@@ -37,7 +37,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<span v-else><i class="ti ti-rocket-off"></i></span>
|
||||
</button>
|
||||
<button ref="otherSettingsButton" v-tooltip="i18n.ts.other" class="_button" :class="$style.headerRightItem" @click="showOtherSettings"><i class="ti ti-dots"></i></button>
|
||||
<button v-click-anime class="_button" :class="$style.submit" :disabled="!canPost" data-cy-open-post-form-submit @click="post">
|
||||
<button ref="submitButtonEl" v-click-anime class="_button" :class="$style.submit" :disabled="!canPost" data-cy-open-post-form-submit @click="post">
|
||||
<div :class="$style.submitInner">
|
||||
<template v-if="posted"></template>
|
||||
<template v-else-if="posting"><MkEllipsis/></template>
|
||||
@@ -60,6 +60,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<button class="_buttonPrimary" style="padding: 4px; border-radius: 8px;" @click="addVisibleUser"><i class="ti ti-plus ti-fw"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
<MkInfo v-if="!store.r.tips.value.postForm" :class="$style.showHowToUse"><button class="_textButton" @click="showTour">{{ i18n.ts._postForm.showHowToUse }}</button></MkInfo>
|
||||
<MkInfo v-if="scheduledAt != null" :class="$style.scheduledAt">
|
||||
<I18n :src="i18n.ts.scheduleToPostOnX" tag="span">
|
||||
<template #x>
|
||||
@@ -89,7 +90,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<MkNotePreview v-if="showPreview" :class="$style.preview" :text="text" :files="files" :poll="poll ?? undefined" :useCw="useCw" :cw="cw" :user="postAccount ?? $i"/>
|
||||
<div v-if="showingOptions" style="padding: 8px 16px;">
|
||||
</div>
|
||||
<footer :class="$style.footer">
|
||||
<footer ref="footerEl" :class="$style.footer">
|
||||
<div :class="$style.footerLeft">
|
||||
<button v-tooltip="i18n.ts.attachFile + ' (' + i18n.ts.upload + ')'" class="_button" :class="$style.footerButton" @click="chooseFileFromPc"><i class="ti ti-photo-plus"></i></button>
|
||||
<button v-tooltip="i18n.ts.attachFile + ' (' + i18n.ts.fromDrive + ')'" class="_button" :class="$style.footerButton" @click="chooseFileFromDrive"><i class="ti ti-cloud-download"></i></button>
|
||||
@@ -153,6 +154,8 @@ import { DI } from '@/di.js';
|
||||
import { globalEvents } from '@/events.js';
|
||||
import { checkDragDataType, getDragData } from '@/drag-and-drop.js';
|
||||
import { useUploader } from '@/composables/use-uploader.js';
|
||||
import { startTour } from '@/utility/tour.js';
|
||||
import { closeTip } from '@/tips.js';
|
||||
|
||||
const $i = ensureSignin();
|
||||
|
||||
@@ -186,6 +189,9 @@ const cwInputEl = useTemplateRef('cwInputEl');
|
||||
const hashtagsInputEl = useTemplateRef('hashtagsInputEl');
|
||||
const visibilityButton = useTemplateRef('visibilityButton');
|
||||
const otherSettingsButton = useTemplateRef('otherSettingsButton');
|
||||
const accountMenuEl = useTemplateRef('accountMenuEl');
|
||||
const footerEl = useTemplateRef('footerEl');
|
||||
const submitButtonEl = useTemplateRef('submitButtonEl');
|
||||
|
||||
const posting = ref(false);
|
||||
const posted = ref(false);
|
||||
@@ -1285,6 +1291,45 @@ function cancelSchedule() {
|
||||
scheduledAt.value = null;
|
||||
}
|
||||
|
||||
function showTour() {
|
||||
if (textareaEl.value == null ||
|
||||
footerEl.value == null ||
|
||||
accountMenuEl.value == null ||
|
||||
visibilityButton.value == null ||
|
||||
otherSettingsButton.value == null ||
|
||||
submitButtonEl.value == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
startTour([{
|
||||
element: textareaEl.value,
|
||||
title: i18n.ts._postForm._howToUse.content_title,
|
||||
description: i18n.ts._postForm._howToUse.content_description,
|
||||
}, {
|
||||
element: footerEl.value,
|
||||
title: i18n.ts._postForm._howToUse.toolbar_title,
|
||||
description: i18n.ts._postForm._howToUse.toolbar_description,
|
||||
}, {
|
||||
element: accountMenuEl.value,
|
||||
title: i18n.ts._postForm._howToUse.account_title,
|
||||
description: i18n.ts._postForm._howToUse.account_description,
|
||||
}, {
|
||||
element: visibilityButton.value,
|
||||
title: i18n.ts._postForm._howToUse.visibility_title,
|
||||
description: i18n.ts._postForm._howToUse.visibility_description,
|
||||
}, {
|
||||
element: otherSettingsButton.value,
|
||||
title: i18n.ts._postForm._howToUse.menu_title,
|
||||
description: i18n.ts._postForm._howToUse.menu_description,
|
||||
}, {
|
||||
element: submitButtonEl.value,
|
||||
title: i18n.ts._postForm._howToUse.submit_title,
|
||||
description: i18n.ts._postForm._howToUse.submit_description,
|
||||
}]).then(() => {
|
||||
closeTip('postForm');
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (props.autofocus) {
|
||||
focus();
|
||||
@@ -1414,6 +1459,7 @@ defineExpose({
|
||||
}
|
||||
|
||||
.avatar {
|
||||
display: block;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
margin: auto;
|
||||
@@ -1575,6 +1621,10 @@ html[data-color-scheme=light] .preview {
|
||||
margin: 0 20px 16px 20px;
|
||||
}
|
||||
|
||||
.showHowToUse {
|
||||
margin: 0 20px 16px 20px;
|
||||
}
|
||||
|
||||
.cw,
|
||||
.hashtags,
|
||||
.text {
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div ref="rootEl" :class="$style.root" :style="{ zIndex }">
|
||||
<div :class="[$style.bg]"></div>
|
||||
<div ref="spotEl" :class="$style.spot"></div>
|
||||
<div ref="bodyEl" :class="$style.body" class="_panel _shadow">
|
||||
<div class="_gaps_s">
|
||||
<div><b>{{ title }}</b></div>
|
||||
<div>{{ description }}</div>
|
||||
<div class="_buttons">
|
||||
<MkButton v-if="hasPrev" small @click="prev"><i class="ti ti-arrow-left"></i> {{ i18n.ts.goBack }}</MkButton>
|
||||
<MkButton v-if="hasNext" small primary @click="next">{{ i18n.ts.next }} <i class="ti ti-arrow-right"></i></MkButton>
|
||||
<MkButton v-else small primary @click="next">{{ i18n.ts.done }} <i class="ti ti-check"></i></MkButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { nextTick, onMounted, onUnmounted, ref, useTemplateRef } from 'vue';
|
||||
import { calcPopupPosition } from '@/utility/popup-position.js';
|
||||
import * as os from '@/os.js';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import { i18n } from '@/i18n.js';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
title: string;
|
||||
description: string;
|
||||
anchorElement?: HTMLElement;
|
||||
x?: number;
|
||||
y?: number;
|
||||
direction?: 'top' | 'bottom' | 'right' | 'left';
|
||||
hasPrev: boolean;
|
||||
hasNext: boolean;
|
||||
}>(), {
|
||||
direction: 'top',
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(prev: 'prev'): void;
|
||||
(next: 'next'): void;
|
||||
}>();
|
||||
|
||||
function prev() {
|
||||
emit('prev');
|
||||
}
|
||||
|
||||
function next() {
|
||||
emit('next');
|
||||
}
|
||||
|
||||
const rootEl = useTemplateRef('rootEl');
|
||||
const bodyEl = useTemplateRef('bodyEl');
|
||||
const spotEl = useTemplateRef('spotEl');
|
||||
const zIndex = os.claimZIndex('high');
|
||||
const spotX = ref(0);
|
||||
const spotY = ref(0);
|
||||
const spotWidth = ref(0);
|
||||
const spotHeight = ref(0);
|
||||
|
||||
function setPosition() {
|
||||
if (spotEl.value == null) return;
|
||||
if (bodyEl.value == null) return;
|
||||
if (props.anchorElement == null) return;
|
||||
|
||||
const rect = props.anchorElement.getBoundingClientRect();
|
||||
spotX.value = rect.left;
|
||||
spotY.value = rect.top;
|
||||
spotWidth.value = rect.width;
|
||||
spotHeight.value = rect.height;
|
||||
|
||||
const data = calcPopupPosition(bodyEl.value, {
|
||||
anchorElement: props.anchorElement,
|
||||
direction: props.direction,
|
||||
align: 'center',
|
||||
innerMargin: 16,
|
||||
x: props.x,
|
||||
y: props.y,
|
||||
});
|
||||
|
||||
bodyEl.value.style.transformOrigin = data.transformOrigin;
|
||||
bodyEl.value.style.left = data.left + 'px';
|
||||
bodyEl.value.style.top = data.top + 'px';
|
||||
}
|
||||
|
||||
let loopHandler;
|
||||
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
setPosition();
|
||||
|
||||
const loop = () => {
|
||||
setPosition();
|
||||
loopHandler = window.requestAnimationFrame(loop);
|
||||
};
|
||||
|
||||
loop();
|
||||
});
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
window.cancelAnimationFrame(loopHandler);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.root {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.bg {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.spot {
|
||||
--x: v-bind("spotX + 'px'");
|
||||
--y: v-bind("spotY + 'px'");
|
||||
--width: v-bind("spotWidth + 'px'");
|
||||
--height: v-bind("spotHeight + 'px'");
|
||||
--padding: 8px;
|
||||
position: absolute;
|
||||
left: calc(var(--x) - var(--padding));
|
||||
top: calc(var(--y) - var(--padding));
|
||||
width: calc(var(--width) + var(--padding) * 2);
|
||||
height: calc(var(--height) + var(--padding) * 2);
|
||||
box-sizing: border-box;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 0 0 9999px #000a;
|
||||
transition: left 0.2s ease-out, top 0.2s ease-out, width 0.2s ease-out, height 0.2s ease-out;
|
||||
animation: blink 1s infinite;
|
||||
}
|
||||
|
||||
.body {
|
||||
position: absolute;
|
||||
padding: 16px 20px;
|
||||
box-sizing: border-box;
|
||||
width: max-content;
|
||||
max-width: min(500px, 100vw);
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
0%, 100% {
|
||||
background: color(from var(--MI_THEME-accent) srgb r g b / 0.1);
|
||||
border: 1px solid color(from var(--MI_THEME-accent) srgb r g b / 0.75);
|
||||
}
|
||||
50% {
|
||||
background: transparent;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -20,7 +20,7 @@ export type ImageCompositorFunction<PS extends ImageCompositorFunctionParams = I
|
||||
}) => void;
|
||||
};
|
||||
|
||||
export type ImageCompositorLayer<FNS extends Record<string, ImageCompositorFunction> = any> = {
|
||||
export type ImageCompositorLayer<FNS extends Record<string, ImageCompositorFunction> = Record<string, ImageCompositorFunction>> = {
|
||||
[K in keyof FNS]: {
|
||||
id: string;
|
||||
functionId: K;
|
||||
@@ -219,7 +219,7 @@ export class ImageCompositor<FNS extends Record<string, ImageCompositorFunction<
|
||||
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, resultTexture, 0);
|
||||
}
|
||||
|
||||
this.renderLayer(layer, preTexture, isLast);
|
||||
this.renderLayer(layer as ImageCompositorLayer, preTexture, isLast);
|
||||
|
||||
preTexture = resultTexture;
|
||||
}
|
||||
|
||||
@@ -198,15 +198,15 @@ async function mute() {
|
||||
const { canceled, result: period } = await os.select({
|
||||
title: i18n.ts.mutePeriod,
|
||||
items: [{
|
||||
value: 'indefinitely', text: i18n.ts.indefinitely,
|
||||
value: 'indefinitely', label: i18n.ts.indefinitely,
|
||||
}, {
|
||||
value: 'tenMinutes', text: i18n.ts.tenMinutes,
|
||||
value: 'tenMinutes', label: i18n.ts.tenMinutes,
|
||||
}, {
|
||||
value: 'oneHour', text: i18n.ts.oneHour,
|
||||
value: 'oneHour', label: i18n.ts.oneHour,
|
||||
}, {
|
||||
value: 'oneDay', text: i18n.ts.oneDay,
|
||||
value: 'oneDay', label: i18n.ts.oneDay,
|
||||
}, {
|
||||
value: 'oneWeek', text: i18n.ts.oneWeek,
|
||||
value: 'oneWeek', label: i18n.ts.oneWeek,
|
||||
}],
|
||||
default: 'indefinitely',
|
||||
});
|
||||
|
||||
@@ -39,7 +39,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<MkUserName :user="post.user" style="display: block;"/>
|
||||
<MkAcct :user="post.user"/>
|
||||
</div>
|
||||
<MkFollowButton v-if="!$i || $i.id != post.user.id" v-model:user="post.user" :inline="true" :transparent="false" :full="true" large class="koudoku"/>
|
||||
<!--<MkFollowButton v-if="!$i || $i.id != post.user.id" v-model:user="post.user" :inline="true" :transparent="false" :full="true" large class="koudoku"/>-->
|
||||
</div>
|
||||
</div>
|
||||
<MkAd :preferForms="['horizontal', 'horizontal-big']"/>
|
||||
|
||||
@@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<Transition :name="prefer.s.animation ? 'fade' : ''" mode="out-in">
|
||||
<div v-if="note">
|
||||
<div v-if="showNext" class="_margin">
|
||||
<MkNotesTimeline direction="up" :withControl="false" :pullToRefresh="false" class="" :paginator="showNext === 'channel' ? nextChannelPaginator : nextUserPaginator" :noGap="true"/>
|
||||
<MkNotesTimeline direction="up" :withControl="false" :pullToRefresh="false" class="" :paginator="showNext === 'channel' ? nextChannelPaginator : nextUserPaginator" :noGap="true" :forceDisableInfiniteScroll="true" />
|
||||
</div>
|
||||
|
||||
<div class="_margin">
|
||||
|
||||
@@ -74,7 +74,35 @@ async function add() {
|
||||
if (canceled || type == null) return;
|
||||
|
||||
const id = genId();
|
||||
children.value.push({ id, type });
|
||||
|
||||
// TODO: page-editor.vueのと共通化
|
||||
if (type === 'text') {
|
||||
children.value.push({
|
||||
id,
|
||||
type,
|
||||
text: '',
|
||||
});
|
||||
} else if (type === 'section') {
|
||||
children.value.push({
|
||||
id,
|
||||
type,
|
||||
title: '',
|
||||
children: [],
|
||||
});
|
||||
} else if (type === 'image') {
|
||||
children.value.push({
|
||||
id,
|
||||
type,
|
||||
fileId: null,
|
||||
});
|
||||
} else if (type === 'note') {
|
||||
children.value.push({
|
||||
id,
|
||||
type,
|
||||
detailed: false,
|
||||
note: null,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
@@ -60,9 +60,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<script lang="ts" setup>
|
||||
import { computed, provide, watch, ref } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { genId } from '@/utility/id.js';
|
||||
import { url } from '@@/js/config.js';
|
||||
import XBlocks from './page-editor.blocks.vue';
|
||||
import { genId } from '@/utility/id.js';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import MkSelect from '@/components/MkSelect.vue';
|
||||
import MkSwitch from '@/components/MkSwitch.vue';
|
||||
@@ -216,7 +216,35 @@ async function add() {
|
||||
if (canceled || type == null) return;
|
||||
|
||||
const id = genId();
|
||||
content.value.push({ id, type });
|
||||
|
||||
// TODO: page-editor.el.section.vueのと共通化
|
||||
if (type === 'text') {
|
||||
content.value.push({
|
||||
id,
|
||||
type,
|
||||
text: '',
|
||||
});
|
||||
} else if (type === 'section') {
|
||||
content.value.push({
|
||||
id,
|
||||
type,
|
||||
title: '',
|
||||
children: [],
|
||||
});
|
||||
} else if (type === 'image') {
|
||||
content.value.push({
|
||||
id,
|
||||
type,
|
||||
fileId: null,
|
||||
});
|
||||
} else if (type === 'note') {
|
||||
content.value.push({
|
||||
id,
|
||||
type,
|
||||
detailed: false,
|
||||
note: null,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function setEyeCatchingImage(img: Event) {
|
||||
|
||||
@@ -73,7 +73,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<MkUserName :user="page.user" :class="$style.name"/>
|
||||
<MkAcct :user="page.user" :class="$style.acct"/>
|
||||
</MkA>
|
||||
<MkFollowButton v-if="!$i || $i.id != page.user.id" :user="page.user!" :inline="true" :transparent="false" :full="true" :class="$style.follow"/>
|
||||
<!--<MkFollowButton v-if="!$i || $i.id != page.user.id" :user="page.user!" :inline="true" :transparent="false" :full="true" :class="$style.follow"/>-->
|
||||
</div>
|
||||
<div :class="$style.pageDate">
|
||||
<div><i class="ti ti-clock"></i> {{ i18n.ts.createdAt }}: <MkTime :time="page.createdAt" mode="detail"/></div>
|
||||
|
||||
@@ -11,6 +11,7 @@ export const TIPS = [
|
||||
'postFormUploader',
|
||||
'clips',
|
||||
'userLists',
|
||||
'postForm',
|
||||
'tl.home',
|
||||
'tl.local',
|
||||
'tl.social',
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import { FXS } from './fxs.js';
|
||||
import type { ImageCompositorFunction, ImageCompositorLayer } from '@/lib/ImageCompositor.js';
|
||||
import type { ImageCompositorFunction } from '@/lib/ImageCompositor.js';
|
||||
import { ImageCompositor } from '@/lib/ImageCompositor.js';
|
||||
|
||||
export type ImageEffectorRGB = [r: number, g: number, b: number];
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { computed, ref, shallowRef, watch } from 'vue';
|
||||
import * as os from '@/os.js';
|
||||
|
||||
type TourStep = {
|
||||
title: string;
|
||||
description: string;
|
||||
element: HTMLElement;
|
||||
};
|
||||
|
||||
export function startTour(steps: TourStep[]) {
|
||||
return new Promise<void>(async (resolve) => {
|
||||
const currentStepIndex = ref(0);
|
||||
const titleRef = ref(steps[0].title);
|
||||
const descriptionRef = ref(steps[0].description);
|
||||
const anchorElementRef = shallowRef<HTMLElement>(steps[0].element);
|
||||
|
||||
watch(currentStepIndex, (newIndex) => {
|
||||
const step = steps[newIndex];
|
||||
titleRef.value = step.title;
|
||||
descriptionRef.value = step.description;
|
||||
anchorElementRef.value = step.element;
|
||||
});
|
||||
|
||||
const { dispose } = os.popup(await import('@/components/MkSpot.vue').then(x => x.default), {
|
||||
title: titleRef,
|
||||
description: descriptionRef,
|
||||
anchorElement: anchorElementRef,
|
||||
hasPrev: computed(() => currentStepIndex.value > 0),
|
||||
hasNext: computed(() => currentStepIndex.value < steps.length - 1),
|
||||
}, {
|
||||
next: () => {
|
||||
if (currentStepIndex.value >= steps.length - 1) {
|
||||
dispose();
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
currentStepIndex.value++;
|
||||
},
|
||||
prev: () => {
|
||||
currentStepIndex.value--;
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -33,7 +33,7 @@ export const fn = defineImageCompositorFunction<Partial<{
|
||||
gl.uniform1i(u.noBBoxExpansion, params.noBoundingBoxExpansion ? 1 : 0);
|
||||
|
||||
// ウォーターマークテクスチャ
|
||||
const wm = textures.get(params.watermark);
|
||||
const wm = params.watermark ? textures.get(params.watermark) : null;
|
||||
if (wm) {
|
||||
gl.activeTexture(gl.TEXTURE1);
|
||||
gl.bindTexture(gl.TEXTURE_2D, wm.texture);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"type": "module",
|
||||
"name": "misskey-js",
|
||||
"version": "2025.11.0-alpha.2",
|
||||
"version": "2025.11.0-alpha.3",
|
||||
"description": "Misskey SDK for JavaScript",
|
||||
"license": "MIT",
|
||||
"main": "./built/index.js",
|
||||
|
||||
@@ -4900,7 +4900,8 @@ export type components = {
|
||||
summary: string | null;
|
||||
hideTitleWhenPinned: boolean;
|
||||
alignCenter: boolean;
|
||||
font: string;
|
||||
/** @enum {string} */
|
||||
font: 'serif' | 'sans-serif';
|
||||
script: string;
|
||||
eyeCatchingImageId: string | null;
|
||||
eyeCatchingImage: components['schemas']['DriveFile'] | null;
|
||||
@@ -4947,6 +4948,8 @@ export type components = {
|
||||
userId: string | null;
|
||||
/** Format: url */
|
||||
bannerUrl: string | null;
|
||||
/** Format: id */
|
||||
bannerId: string | null;
|
||||
pinnedNoteIds: string[];
|
||||
color: string;
|
||||
isArchived: boolean;
|
||||
|
||||
Reference in New Issue
Block a user