Skip to content

Commit

Permalink
Merge pull request #47 from team-shahu/feat/enable-remote-reaction
Browse files Browse the repository at this point in the history
feat: リモートのリアクション相乗り
  • Loading branch information
chan-mai authored Nov 15, 2024
2 parents ed8a511 + 9007757 commit b2b2880
Show file tree
Hide file tree
Showing 7 changed files with 93 additions and 39 deletions.
36 changes: 18 additions & 18 deletions locales/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10574,24 +10574,6 @@ export interface Locale extends ILocale {
*/
"loop": string;
};
"_hideReactionCount": {
/**
* 非表示にしない
*/
"none": string;
/**
* 自分のノートのみ
*/
"self": string;
/**
* 自分以外のノートのみ
*/
"others": string;
/**
* 全てのノート
*/
"all": string;
};
"_profileHiddenSettings": {
/**
* プロフィールを非表示にする機能
Expand Down Expand Up @@ -10714,6 +10696,24 @@ export interface Locale extends ILocale {
*/
"codeGeneratedDescription": string;
};
"_hideReactionCount": {
/**
* 非表示にしない
*/
"none": string;
/**
* 自分のノートのみ
*/
"self": string;
/**
* 自分以外のノートのみ
*/
"others": string;
/**
* 全てのノート
*/
"all": string;
};
/**
* 予約投稿
*/
Expand Down
12 changes: 5 additions & 7 deletions locales/ja-JP.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1342,7 +1342,6 @@ _delivery:
manuallySuspended: "手動停止中"
goneSuspended: "サーバー削除のため停止中"
autoSuspendedForNotResponding: "サーバー応答なしのため停止中"

selectReaction: "いいねボタンで使うリアクションを選択"
showLikeButton: "いいねボタンを表示する"

Expand Down Expand Up @@ -2813,12 +2812,6 @@ _mediaControls:
playbackRate: "再生速度"
loop: "ループ再生"

_hideReactionCount:
none: "非表示にしない"
self: "自分のノートのみ"
others: "自分以外のノートのみ"
all: "全てのノート"

_profileHiddenSettings:
hiddenProfile: "プロフィールを非表示にする機能"
hiddenPinnedNotes: "プロフィール上からピン留めしたノートを非表示にします"
Expand Down Expand Up @@ -2856,6 +2849,11 @@ _embedCodeGen:
generateCode: "埋め込みコードを作成"
codeGenerated: "コードが生成されました"
codeGeneratedDescription: "生成されたコードをウェブサイトに貼り付けてご利用ください。"
_hideReactionCount:
none: "非表示にしない"
self: "自分のノートのみ"
others: "自分以外のノートのみ"
all: "全てのノート"
schedulePost: "予約投稿"
schedulePostList: "予約投稿一覧"

11 changes: 10 additions & 1 deletion packages/frontend/src/components/MkNoteDetailed.vue
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div :class="$style.reactionTabs">
<button v-for="reaction in Object.keys(appearNote.reactions)" :key="reaction" :class="[$style.reactionTab, { [$style.reactionTabActive]: reactionTabType === reaction }]" class="_button" @click="reactionTabType = defaultStore.state.hideReactionUsers ? null : reaction">
<MkReactionIcon :reaction="reaction"/>
<span style="margin-left: 4px;">{{ appearNote.reactions[reaction] }}</span>
<span v-if="!hideReactionCount" style="margin-left: 4px;">{{ appearNote.reactions[reaction] }}</span>
</button>
</div>
<MkPagination v-if="reactionTabType" :key="reactionTabType" :pagination="reactionsPagination" :disableAutoLoad="true">
Expand Down Expand Up @@ -303,6 +303,15 @@ const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultS
const conversation = ref<Misskey.entities.Note[]>([]);
const replies = ref<Misskey.entities.Note[]>([]);
const canRenote = computed(() => ['public', 'home'].includes(appearNote.value.visibility) || appearNote.value.userId === $i?.id);
const hideReactionCount = computed(() => {
switch (defaultStore.state.hideReactionCount) {
case 'none': return false;
case 'all': return true;
case 'self': return props.note.userId === $i?.id;
case 'others': return props.note.userId !== $i?.id;
default: return false;
}
});

const pleaseLoginContext = computed<OpenOnRemoteOptions>(() => ({
type: 'lookup',
Expand Down
63 changes: 50 additions & 13 deletions packages/frontend/src/components/MkReactionsViewer.reaction.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
ref="buttonEl"
v-ripple="canToggle"
class="_button"
:class="[$style.root, { [$style.reacted]: note.myReaction == reaction, [$style.canToggle]: canToggle, [$style.small]: defaultStore.state.reactionsDisplaySize === 'small', [$style.large]: defaultStore.state.reactionsDisplaySize === 'large' }]"
:class="[$style.root, { [$style.reacted]: note.myReaction == reaction, [$style.canToggle]: (isLocal && canToggle), [$style.canToggleFallback]: (!isLocal && isAvailable), [$style.small]: defaultStore.state.reactionsDisplaySize === 'small', [$style.large]: defaultStore.state.reactionsDisplaySize === 'large' }]"
@click="toggleReaction()"
@contextmenu.prevent.stop="menu"
>
Expand Down Expand Up @@ -56,53 +56,73 @@ const buttonEl = shallowRef<HTMLElement>();
const emojiName = computed(() => props.reaction.replace(/:/g, '').replace(/@\./, ''));
const emoji = computed(() => customEmojisMap.get(emojiName.value) ?? getUnicodeEmoji(props.reaction));

function getReactionName(reaction: string, formated = false) {
const r = reaction.replaceAll(':', '').replace(/@.*/, '');
return formated ? `:${r}:` : r;
}

const isLocal = computed(() => !props.reaction.match(/@\w/));
const isAvailable = computed(() => isLocal.value ? true : customEmojisMap.has(getReactionName(props.reaction)));

const canToggle = computed(() => {
return !props.reaction.match(/@\w/) && $i && emoji.value && checkReactionPermissions($i, props.note, emoji.value);
return isAvailable.value && $i && emoji.value && checkReactionPermissions($i, props.note, emoji.value);
});
const canGetInfo = computed(() => !props.reaction.match(/@\w/) && props.reaction.includes(':'));

const hideReactionCount = computed(() => {
switch (defaultStore.state.hideReactionCount) {
case 'none': return false;
case 'all': return true;
case 'self': return props.note.userId === $i?.id;
case 'others': return props.note.userId !== $i?.id;
default: return false;
}
});

async function toggleReaction() {
if (!canToggle.value) return;

const oldReaction = props.note.myReaction;
const reaction = getReactionName(props.reaction, true);
const oldReaction = props.note.myReaction ? getReactionName(props.note.myReaction, true) : null;
if (oldReaction) {
const confirm = await os.confirm({
type: 'warning',
text: oldReaction !== props.reaction ? i18n.ts.changeReactionConfirm : i18n.ts.cancelReactionConfirm,
text: oldReaction !== reaction ? i18n.ts.changeReactionConfirm : i18n.ts.cancelReactionConfirm,
});
if (confirm.canceled) return;

if (oldReaction !== props.reaction) {
if (oldReaction !== reaction) {
sound.playMisskeySfx('reaction');
}

if (mock) {
emit('reactionToggled', props.reaction, (props.count - 1));
emit('reactionToggled', reaction, (props.count - 1));
return;
}

misskeyApi('notes/reactions/delete', {
noteId: props.note.id,
}).then(() => {
if (oldReaction !== props.reaction) {
if (oldReaction !== reaction) {
misskeyApi('notes/reactions/create', {
noteId: props.note.id,
reaction: props.reaction,
reaction: reaction,
});
}
});
} else {
sound.playMisskeySfx('reaction');

if (mock) {
emit('reactionToggled', props.reaction, (props.count + 1));
emit('reactionToggled', reaction, (props.count + 1));
return;
}

misskeyApi('notes/reactions/create', {
noteId: props.note.id,
reaction: props.reaction,
reaction: reaction,
});

if (props.note.text && props.note.text.length > 100 && (Date.now() - new Date(props.note.createdAt).getTime() < 1000 * 3)) {
claimAchievement('reactWithoutRead');
}
Expand Down Expand Up @@ -148,20 +168,23 @@ onMounted(() => {

if (!mock) {
useTooltip(buttonEl, async (showing) => {
const reactions = !defaultStore.state.hideReactionUsers ? await misskeyApiGet('notes/reactions', {
const useGet = !reactionChecksMuting.value;
const apiCall = useGet ? misskeyApiGet : misskeyApi;
const reactions = !defaultStore.state.hideReactionUsers ? await apiCall('notes/reactions', {
noteId: props.note.id,
type: props.reaction,
limit: 10,
_cacheKey_: props.count,
}) : [];

const users = reactions.map(x => x.user);
const count = users.length;

const { dispose } = os.popup(XDetails, {
showing,
reaction: props.reaction,
users,
count: props.count,
count,
targetElement: buttonEl.value,
}, {
closed: () => dispose(),
Expand Down Expand Up @@ -189,7 +212,21 @@ if (!mock) {
}
}

&:not(.canToggle) {
&.canToggleFallback:not(.canToggle):not(.reacted) {
box-sizing: border-box;
border: 2px dashed var(--MI_THEME-switchBg);

&.small {
border-width: 1px;
border-color: var(--MI_THEME-buttonBgSub);
}

&:hover {
background: rgba(0, 0, 0, 0.1);
}
}

&:not(.canToggle):not(.canToggleFallback) {
cursor: default;
}

Expand Down
1 change: 1 addition & 0 deletions packages/frontend/src/pages/settings/general.vue
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,7 @@ watch([
showNoteActionsOnlyHover,
showGapBetweenNotesInTimeline,
instanceTicker,
hideReactionCount,
instanceIcon,
overridedDeviceKind,
mediaListWithOneImageAppearance,
Expand Down
5 changes: 5 additions & 0 deletions packages/frontend/src/scripts/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,11 @@ function compile(theme: Theme): Record<string, string> {
props[k] = v.startsWith('"') ? v.replace(/^"\s*/, '') : genValue(getColor(v));
}

// MkReactionViewerのfallback reaction用
if ('buttonBg' in props) {
props['buttonBgSub'] = getColor(props['buttonBg'] as string).setAlpha(0.3).toString();
}

return props;
}

Expand Down
4 changes: 4 additions & 0 deletions packages/frontend/src/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,10 @@ export const defaultStore = markRaw(new Storage('base', {
where: 'account',
default: false,
},
hideReactionCount: {
where: 'account',
default: 'none' as 'none' | 'self' | 'others' | 'all',
},
limitWidthOfReaction: {
where: 'device',
default: true,
Expand Down

0 comments on commit b2b2880

Please sign in to comment.