Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

enhance: いいねボタンの実装 #41

Merged
merged 8 commits into from
Nov 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading