Skip to content

Commit

Permalink
Merge pull request #41 from team-shahu/enhance/heart-reaction-button
Browse files Browse the repository at this point in the history
enhance: いいねボタンの実装
  • Loading branch information
chan-mai authored Nov 10, 2024
2 parents b301510 + 7e8fd31 commit 128726e
Show file tree
Hide file tree
Showing 14 changed files with 129 additions and 11 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
- フォローしているユーザーなら鍵ノートでもアンテナにひっかかるように https://github.com/team-shahu/misskey/pull/38
- nyaizeを無効化できるように https://github.com/team-shahu/misskey/pull/39
- 新着ノート通知があった時まとめるように https://github.com/team-shahu/misskey/pull/40
- いいねボタンの実装 https://github.com/team-shahu/misskey/pull/41

## Special Thanks
- [Misskey](https://github.com/misskey-dev/misskey)
Expand Down
1 change: 1 addition & 0 deletions locales/en-US.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1313,6 +1313,7 @@ _delivery:
manuallySuspended: "Manually suspended"
goneSuspended: "Server is suspended due to server deletion"
autoSuspendedForNotResponding: "Server is suspended due to no responding"
selectReaction: "Select reactions to use with the Like button"
scheduledNoteDelete: "Scheduled note deletion"
noteDeletationAt: "This note will be deleted at {time}."
addToEmojiPicker: "Add to Emoji Picker"
Expand Down
4 changes: 4 additions & 0 deletions locales/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5373,6 +5373,10 @@ export interface Locale extends ILocale {
"autoSuspendedForNotResponding": string;
};
};
/**
* いいねボタンで使うリアクションを選択
*/
"selectReaction": string;
"_bubbleGame": {
/**
* 遊び方
Expand Down
2 changes: 2 additions & 0 deletions locales/ja-JP.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1343,6 +1343,8 @@ _delivery:
goneSuspended: "サーバー削除のため停止中"
autoSuspendedForNotResponding: "サーバー応答なしのため停止中"

selectReaction: "いいねボタンで使うリアクションを選択"

_bubbleGame:
howToPlay: "遊び方"
hold: "ホールド"
Expand Down
1 change: 0 additions & 1 deletion packages/backend/src/models/Meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -574,7 +574,6 @@ export class MiMeta {
})
public emailWhitelist: boolean;


@Column('varchar', {
length: 1024, array: true, default: '{ "admin", "administrator", "root", "system", "maintainer", "host", "mod", "moderator", "owner", "superuser", "staff", "auth", "i", "me", "everyone", "all", "mention", "mentions", "example", "user", "users", "account", "accounts", "official", "help", "helps", "support", "supports", "info", "information", "informations", "announce", "announces", "announcement", "announcements", "notice", "notification", "notifications", "dev", "developer", "developers", "tech", "misskey" }',
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ export const paramDef = {
enableIdenticonGeneration: { type: 'boolean' },
serverRules: { type: 'array', items: { type: 'string' } },
bannedEmailDomains: { type: 'array', items: { type: 'string' } },
emailWhitelist: { type: 'boolean'},
emailWhitelist: { type: 'boolean' },
preservedUsernames: { type: 'array', items: { type: 'string' } },
manifestJsonOverride: { type: 'string' },
enableFanoutTimeline: { type: 'boolean' },
Expand Down Expand Up @@ -449,6 +449,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
}

if (ps.repositoryUrl !== undefined) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
set.repositoryUrl = URL.canParse(ps.repositoryUrl!) ? ps.repositoryUrl : null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
]);

const userIds = Array.from(userIdsWhoMeMuting ?? []).concat(Array.from(userIdsWhoBlockingMe ?? []));
if (userIds.length > 0 ){
if (userIds.length > 0 ) {
query.andWhere('reaction.userId NOT IN (:...userIds)', { userIds: Array.from(userIdsWhoMeMuting ?? []).concat(Array.from(userIdsWhoBlockingMe ?? [])) });
}
}
Expand Down
36 changes: 35 additions & 1 deletion packages/frontend/src/components/MkNote.vue
Original file line number Diff line number Diff line change
Expand Up @@ -125,11 +125,14 @@ SPDX-License-Identifier: AGPL-3.0-only
<button v-else :class="$style.footerButton" class="_button" disabled>
<i class="ti ti-ban"></i>
</button>
<button v-if="appearNote.myReaction == null" ref="heartReactButton" v-tooltip="i18n.ts.like" :class="$style.footerButton" class="_button" @mousedown="heartReact()">
<i class="ti ti-heart"></i>
</button>
<button ref="reactButton" :class="$style.footerButton" class="_button" @click="toggleReact()">
<i v-if="appearNote.reactionAcceptance === 'likeOnly' && appearNote.myReaction != null" class="ti ti-heart-filled" style="color: var(--MI_THEME-love);"></i>
<i v-else-if="appearNote.myReaction != null" class="ti ti-minus" style="color: var(--MI_THEME-accent);"></i>
<i v-else-if="appearNote.reactionAcceptance === 'likeOnly'" class="ti ti-heart"></i>
<i v-else class="ti ti-plus"></i>
<i v-else class="ti ti-mood-plus"></i>
<p v-if="(appearNote.reactionAcceptance === 'likeOnly' || defaultStore.state.showReactionsCount) && appearNote.reactionCount > 0" :class="$style.footerButtonCount">{{ number(appearNote.reactionCount) }}</p>
</button>
<button v-if="defaultStore.state.showClipButtonInNoteFooter" ref="clipButton" :class="$style.footerButton" class="_button" @mousedown.prevent="clip()">
Expand Down Expand Up @@ -258,6 +261,7 @@ const menuButton = shallowRef<HTMLElement>();
const renoteButton = shallowRef<HTMLElement>();
const renoteTime = shallowRef<HTMLElement>();
const reactButton = shallowRef<HTMLElement>();
const heartReactButton = shallowRef<HTMLElement>();
const clipButton = shallowRef<HTMLElement>();
const appearNote = computed(() => getAppearNote(note.value));
const galleryEl = shallowRef<InstanceType<typeof MkMediaList>>();
Expand Down Expand Up @@ -488,6 +492,36 @@ function react(): void {
}
}

function heartReact(): void {
pleaseLogin(undefined, pleaseLoginContext.value);
showMovedDialog();

sound.playMisskeySfx('reaction');

const selectreact = defaultStore.state.selectReaction;

if (props.mock) {
return;
}

misskeyApi('notes/reactions/create', {
noteId: appearNote.value.id,
reaction: selectreact,
});

if (appearNote.value.text && appearNote.value.text.length > 100 && (Date.now() - new Date(appearNote.value.createdAt).getTime() < 1000 * 3)) {
claimAchievement('reactWithoutRead');
}

const el = heartReactButton.value;
if (el) {
const rect = el.getBoundingClientRect();
const x = rect.left + (el.offsetWidth / 2);
const y = rect.top + (el.offsetHeight / 2);
os.popup(MkRippleEffect, { x, y }, {}, 'end');
}
}

function undoReact(targetNote: Misskey.entities.Note): void {
const oldReaction = targetNote.myReaction;
if (!oldReaction) return;
Expand Down
32 changes: 31 additions & 1 deletion packages/frontend/src/components/MkNoteDetailed.vue
Original file line number Diff line number Diff line change
Expand Up @@ -135,11 +135,14 @@ SPDX-License-Identifier: AGPL-3.0-only
<button v-else class="_button" :class="$style.noteFooterButton" disabled>
<i class="ti ti-ban"></i>
</button>
<button v-if="appearNote.myReaction == null" ref="heartReactButton" v-tooltip="i18n.ts.like" :class="$style.noteFooterButton" class="_button" @mousedown="heartReact()">
<i class="ti ti-heart"></i>
</button>
<button ref="reactButton" :class="$style.noteFooterButton" class="_button" @click="toggleReact()">
<i v-if="appearNote.reactionAcceptance === 'likeOnly' && appearNote.myReaction != null" class="ti ti-heart-filled" style="color: var(--MI_THEME-love);"></i>
<i v-else-if="appearNote.myReaction != null" class="ti ti-minus" style="color: var(--MI_THEME-accent);"></i>
<i v-else-if="appearNote.reactionAcceptance === 'likeOnly'" class="ti ti-heart"></i>
<i v-else class="ti ti-plus"></i>
<i v-else class="ti ti-mood-plus"></i>
<p v-if="(appearNote.reactionAcceptance === 'likeOnly' || defaultStore.state.showReactionsCount) && appearNote.reactionCount > 0" :class="$style.noteFooterButtonCount">{{ number(appearNote.reactionCount) }}</p>
</button>
<button v-if="defaultStore.state.showClipButtonInNoteFooter" ref="clipButton" class="_button" :class="$style.noteFooterButton" @mousedown.prevent="clip()">
Expand Down Expand Up @@ -284,6 +287,7 @@ const menuButton = shallowRef<HTMLElement>();
const renoteButton = shallowRef<HTMLElement>();
const renoteTime = shallowRef<HTMLElement>();
const reactButton = shallowRef<HTMLElement>();
const heartReactButton = shallowRef<HTMLElement>();
const clipButton = shallowRef<HTMLElement>();
const appearNote = computed(() => getAppearNote(note.value));
const galleryEl = shallowRef<InstanceType<typeof MkMediaList>>();
Expand Down Expand Up @@ -460,6 +464,32 @@ function react(): void {
}
}

function heartReact(): void {
pleaseLogin(undefined, pleaseLoginContext.value);
showMovedDialog();

sound.playMisskeySfx('reaction');

const selectreact = defaultStore.selectReaction.default;

misskeyApi('notes/reactions/create', {
noteId: appearNote.value.id,
reaction: selectreact,
});

if (appearNote.value.text && appearNote.value.text.length > 100 && (Date.now() - new Date(appearNote.value.createdAt).getTime() < 1000 * 3)) {
claimAchievement('reactWithoutRead');
}

const el = heartReactButton.value;
if (el) {
const rect = el.getBoundingClientRect();
const x = rect.left + (el.offsetWidth / 2);
const y = rect.top + (el.offsetHeight / 2);
os.popup(MkRippleEffect, { x, y }, {}, 'end');
}
}

function undoReact(targetNote: Misskey.entities.Note): void {
const oldReaction = targetNote.myReaction;
if (!oldReaction) return;
Expand Down
44 changes: 43 additions & 1 deletion packages/frontend/src/pages/settings/general.vue
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkFolder>
</div>
</FormSection>

<FormSection>
<template #label>{{ i18n.ts.displayOfNote }}</template>

Expand Down Expand Up @@ -100,6 +99,18 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkRadios>

<MkSwitch v-model="disableNoteNyaize">{{ i18n.ts.disableNoteNyaize }}<span class="_beta">{{ i18n.ts.originalFeature }}</span></MkSwitch>

<FromSlot v-model="selectReaction">
<template #label>{{ i18n.ts.selectReaction }}<span class="_beta">{{ i18n.ts.originalFeature }}</span></template>
<MkCustomEmoji v-if="selectReaction && selectReaction.startsWith(':')" style="max-height: 3em; font-size: 1.1em;" :useOriginalSize="false" :name="selectReaction" :normal="true" :noStyle="true"/>
<MkEmoji v-else-if="selectReaction && !selectReaction.startsWith(':')" :emoji="selectReaction" style="max-height: 3em; font-size: 1.1em;" :normal="true" :noStyle="true"/>
<span v-else-if="!selectReaction">{{ i18n.ts.notSet }}</span>
<div class="_buttons" style="padding-top: 8px;">
<MkButton rounded :small="true" inline @click="chooseNewReaction"><i class="ph-smiley ph-bold ph-lg"></i> Change</MkButton>
<MkButton rounded :small="true" inline @click="resetReaction"><i class="ph-arrow-clockwise ph-bold ph-lg"></i> Reset</MkButton>
</div>
</FromSlot>

</div>
</FormSection>

Expand Down Expand Up @@ -322,6 +333,9 @@ import FormSection from '@/components/form/section.vue';
import FormLink from '@/components/form/link.vue';
import MkLink from '@/components/MkLink.vue';
import MkInfo from '@/components/MkInfo.vue';
import FromSlot from '@/components/form/slot.vue';
import MkCustomEmoji from '@/components/global/MkCustomEmoji.vue';
import MkEmoji from '@/components/global/MkEmoji.vue';
import { defaultStore } from '@/store.js';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
Expand Down Expand Up @@ -396,6 +410,7 @@ const reactionChecksMuting = computed(defaultStore.makeGetterSetter('reactionChe
const hideLocalTimeLine = computed(defaultStore.makeGetterSetter('hideLocalTimeLine'));
const hideGlobalTimeLine = computed(defaultStore.makeGetterSetter('hideGlobalTimeLine'));
const hideSocialTimeLine = computed(defaultStore.makeGetterSetter('hideSocialTimeLine'));
const selectReaction = computed(defaultStore.makeGetterSetter('selectReaction'));

watch(lang, () => {
miLocalStorage.setItem('lang', lang.value as string);
Expand Down Expand Up @@ -444,10 +459,30 @@ watch([
hiddenPinnedNotes,
hiddenActivity,
hiddenFiles,
selectReaction,
], async () => {
await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true });
});

function chooseNewReaction(ev: MouseEvent) {
os.pickEmoji(getHTMLElement(ev), {
showPinned: false,
}).then(async (emoji) => {
selectReaction.value = emoji as string; // 選択された絵文字を格納
await reloadAsk(); // 必要ならリロードや更新処理
});
}

function resetReaction() {
selectReaction.value = ''; // `selectReaction` をリセット
reloadAsk(); // 必要ならリロードや更新処理
}

function getHTMLElement(ev: MouseEvent): HTMLElement {
const target = ev.currentTarget ?? ev.target;
return target as HTMLElement; // イベント発生元の HTML 要素を取得
}

const emojiIndexLangs = ['en-US', 'ja-JP', 'ja-JP_hira'] as const;

function getEmojiIndexLangName(targetLang: typeof emojiIndexLangs[number]) {
Expand Down Expand Up @@ -579,3 +614,10 @@ definePageMetadata(() => ({
icon: 'ti ti-adjustments',
}));
</script>

<!-- <style lang="scss" module>
.emojisAdd {
display: inline-block;
padding: 8px;
}
</style> -->
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ const defaultStoreSaveKeys: (keyof typeof defaultStore['state'])[] = [
'hiddenActivity',
'hiddenFiles',
'disableNoteNyaize',
'selectReaction',
];
const coldDeviceStorageSaveKeys: (keyof typeof ColdDeviceStorage.default)[] = [
'lightTheme',
Expand Down
1 change: 0 additions & 1 deletion packages/frontend/src/pages/user/home.vue
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,6 @@ import { isFollowingVisibleForMe, isFollowersVisibleForMe } from '@/scripts/isFf
import { useRouter } from '@/router/supplier.js';
import { getStaticImageUrl } from '@/scripts/media-proxy.js';
import MkSparkle from '@/components/MkSparkle.vue';
import { defaultStore } from '@/store';

function calcAge(birthdate: string): number {
const date = new Date(birthdate);
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 @@ -418,6 +418,10 @@ export const defaultStore = markRaw(new Storage('base', {
where: 'device',
default: 'medium' as 'small' | 'medium' | 'large',
},
selectReaction: {
where: 'device',
default: '🤍' as string,
},
hideReactionCount: {
where: 'account',
default: 'none' as 'none' | 'self' | 'others' | 'all',
Expand Down
8 changes: 4 additions & 4 deletions packages/misskey-js/src/autogen/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5130,7 +5130,7 @@ export type operations = {
prohibitedWords: string[];
prohibitedWordsForNameOfUser: string[];
bannedEmailDomains?: string[];
emailWhitelist?: boolean;
emailWhitelist: string | null;
preservedUsernames: string[];
hcaptchaSecretKey: string | null;
mcaptchaSecretKey: string | null;
Expand Down Expand Up @@ -9570,7 +9570,7 @@ export type operations = {
enableIdenticonGeneration?: boolean;
serverRules?: string[];
bannedEmailDomains?: string[];
emailWhitelist?: boolean;
emailWhitelist?: boolean;
preservedUsernames?: string[];
manifestJsonOverride?: string;
enableFanoutTimeline?: boolean;
Expand Down Expand Up @@ -18781,8 +18781,8 @@ export type operations = {
untilId?: string;
/** @default true */
markAsRead?: boolean;
includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'followRequestRejected' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'login' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'note:grouped' | 'pollVote' | 'groupInvited')[];
excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'followRequestRejected' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'login' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'note:grouped' | 'pollVote' | 'groupInvited')[];
includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'login' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'note:grouped' | 'pollVote' | 'groupInvited')[];
excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'login' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'note:grouped' | 'pollVote' | 'groupInvited')[];
};
};
};
Expand Down

0 comments on commit 128726e

Please sign in to comment.