diff --git a/.config/example.yml b/.config/example.yml index df423c2c83..49f45efb7e 100644 --- a/.config/example.yml +++ b/.config/example.yml @@ -118,7 +118,7 @@ redis: # ┌───────────────────────────┐ #───┘ MeiliSearch configuration └───────────────────────────── -# You can set scope to local (default value) or global +# You can set scope to local (default value) or global # (include notes from remote). #meilisearch: @@ -180,7 +180,9 @@ id: 'aidx' #outgoingAddressFamily: ipv4 # Proxy for HTTP/HTTPS -#proxy: http://127.0.0.1:3128 +# + + proxyBypassHosts: - api.deepl.com @@ -214,7 +216,7 @@ proxyRemoteFiles: true signToActivityPubGet: true # For security reasons, uploading attachments from the intranet is prohibited, -# but exceptions can be made from the following settings. Default value is "undefined". +# but exceptions can be made from the following settings. Default value is "undefined". # Read changelog to learn more (Improvements of 12.90.0 (2021/09/04)). #allowedPrivateNetworks: [ # '127.0.0.1/32' diff --git a/locales/en-US.yml b/locales/en-US.yml index dc988ccee6..34b17f9abc 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -9,6 +9,8 @@ notifications: "Notifications" username: "Username" password: "Password" forgotPassword: "Forgot password" +setDefaultProfileConfirm: "Do you want to make this profile the default?" +emojiPickerProfile: "Emoji picker profile" fetchingAsApObject: "Fetching from the Fediverse..." ok: "OK" gotIt: "Got it!" diff --git a/locales/index.d.ts b/locales/index.d.ts index 42c658403f..b807edcd9e 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -14,6 +14,8 @@ export interface Locale { "forgotPassword": string; "fetchingAsApObject": string; "ok": string; + "setDefaultProfileConfirm": string; + "emojiPickerProfile": string; "notificationIndicator": string; "hanntenn": string; "hanntennInfo": string; @@ -1767,6 +1769,7 @@ export interface Locale { }; "_options": { "gtlAvailable": string; + "emojiPickerProfileLimit": string; "ltlAvailable": string; "canPublicNote": string; "canEditNote": string; diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 508d4e655a..78d90976cc 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -11,6 +11,8 @@ password: "パスワード" forgotPassword: "パスワードを忘れた" fetchingAsApObject: "連合に照会中" ok: "OK" +setDefaultProfileConfirm: "このプロファイルをデフォルトにしますか?" +emojiPickerProfile: "絵文字ピッカーのプロファイル" notificationIndicator: "通知のインジケーターの数字を表示する" hanntenn: "アイコンとバナーを反転させる" hanntennInfo: "ダークだったらライトのアイコンに、ライトだったらダークのアイコンに。" @@ -1673,6 +1675,7 @@ _role: high: "高" _options: gtlAvailable: "グローバルタイムラインの閲覧" + emojiPickerProfileLimit: "絵文字ピッカーのプロファイルの上限数(最大5)" ltlAvailable: "ローカルタイムラインの閲覧" canPublicNote: "パブリック投稿の許可" canEditNote: "ノートの編集" diff --git a/package.json b/package.json index a05afa02d7..4d669abd12 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "misskey", - "version": "2023.12.2-PrisMisskey.4", + "version": "2023.12.2-PrisMisskey.5", "codename": "nasubi", "repository": { "type": "git", diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts index 64cb12d4f5..de214debeb 100644 --- a/packages/backend/src/core/RoleService.ts +++ b/packages/backend/src/core/RoleService.ts @@ -59,6 +59,7 @@ export type RolePolicies = { userEachUserListsLimit: number; rateLimitFactor: number; avatarDecorationLimit: number; + emojiPickerProfileLimit: number; }; export const DEFAULT_POLICIES: RolePolicies = { @@ -74,7 +75,6 @@ export const DEFAULT_POLICIES: RolePolicies = { canManageCustomEmojis: false, canRequestCustomEmojis: false, canManageAvatarDecorations: false, - canRequestCustomEmojis: false, canSearchNotes: false, canUseTranslator: true, canHideAds: false, @@ -90,6 +90,7 @@ export const DEFAULT_POLICIES: RolePolicies = { userEachUserListsLimit: 50, rateLimitFactor: 1, avatarDecorationLimit: 1, + emojiPickerProfileLimit: 2, }; @Injectable() @@ -355,6 +356,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit { userEachUserListsLimit: calc('userEachUserListsLimit', vs => Math.max(...vs)), rateLimitFactor: calc('rateLimitFactor', vs => Math.max(...vs)), avatarDecorationLimit: calc('avatarDecorationLimit', vs => Math.max(...vs)), + emojiPickerProfileLimit: calc('emojiPickerProfileLimit', vs => Math.max(...vs)), }; } diff --git a/packages/frontend/src/components/MkEmojiPicker.vue b/packages/frontend/src/components/MkEmojiPicker.vue index 43ce3ed7b9..a58c51e0b0 100644 --- a/packages/frontend/src/components/MkEmojiPicker.vue +++ b/packages/frontend/src/components/MkEmojiPicker.vue @@ -4,98 +4,103 @@ SPDX-License-Identifier: AGPL-3.0-only --> - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - - - - - - + + + + + {{ defaultStore.state[`pickerProfileName${a > 1 ? a - 1 : ''}`] }} + + + + + + + + + - - {{ i18n.ts.recentUsed }} - - - - - - - - - - {{ i18n.ts.customEmojis }} - - {{ child.value || i18n.ts.other }} - - - - {{ i18n.ts.emoji }} - {{ category }} - - - - - - - - - + + {{ i18n.ts.recentUsed }} + + + + + + + + + + {{ i18n.ts.customEmojis }} + + {{ child.value || i18n.ts.other }} + + + + {{ i18n.ts.emoji }} + {{ category }} + + + + + + + + + @@ -685,4 +699,24 @@ left: 0;*/ } } } +.sllfktkhgl{ + display: inline-block; + padding: 0 4px; + font-size: 12px; + line-height: 32px; + text-align: center; + color: var(--fg); + cursor: pointer; + width: 100%; + transition: transform 0.3s ease; + box-shadow: 0 1.5px 0 var(--divider); + height: 32px; + overflow: hidden; + &:hover { + transform: translateY(1.5px); + } + &.active { + transform: translateY(5px); + } +} diff --git a/packages/frontend/src/components/MkUserSelectDialog.vue b/packages/frontend/src/components/MkUserSelectDialog.vue index 90f2194a97..76350e5716 100644 --- a/packages/frontend/src/components/MkUserSelectDialog.vue +++ b/packages/frontend/src/components/MkUserSelectDialog.vue @@ -68,8 +68,6 @@ import { defaultStore } from '@/store.js'; import { i18n } from '@/i18n.js'; import { $i } from '@/account.js'; import { hostname } from '@/config.js'; -import { multipleSelectUser } from '@/os.js'; - const emit = defineEmits<{ (ev: 'ok', selected: Misskey.entities.UserDetailed): void; (ev: 'cancel'): void; diff --git a/packages/frontend/src/const.ts b/packages/frontend/src/const.ts index b2b6c663d5..ddb7bfa030 100644 --- a/packages/frontend/src/const.ts +++ b/packages/frontend/src/const.ts @@ -99,6 +99,7 @@ export const ROLE_POLICIES = [ 'userEachUserListsLimit', 'rateLimitFactor', 'avatarDecorationLimit', + 'emojiPickerProfileLimit' ] as const; // なんか動かない diff --git a/packages/frontend/src/pages/admin/roles.vue b/packages/frontend/src/pages/admin/roles.vue index 25958213bd..dc0d43f7b7 100644 --- a/packages/frontend/src/pages/admin/roles.vue +++ b/packages/frontend/src/pages/admin/roles.vue @@ -72,6 +72,13 @@ SPDX-License-Identifier: AGPL-3.0-only + + {{ i18n.ts._role._options.emojiPickerProfileLimit }} + {{ policies.emojiPickerProfileLimit }} + + + + {{ i18n.ts._role._options.inviteLimit }} {{ policies.inviteLimit }} diff --git a/packages/frontend/src/pages/settings/emoji-picker.vue b/packages/frontend/src/pages/settings/emoji-picker.vue index 61f3332122..07c6e2c61d 100644 --- a/packages/frontend/src/pages/settings/emoji-picker.vue +++ b/packages/frontend/src/pages/settings/emoji-picker.vue @@ -5,6 +5,13 @@ SPDX-License-Identifier: AGPL-3.0-only + + {{ i18n.ts.emojiPickerProfile }} + {{ a }}. {{ defaultStore.state[`pickerProfileName${a > 1 ? a - 1 : ''}`] }} + + + {{ i18n.ts.name }} + {{ i18n.ts.pinned }} ({{ i18n.ts.reaction }}) @@ -84,7 +91,7 @@ SPDX-License-Identifier: AGPL-3.0-only - + {{ i18n.ts.default }} {{ i18n.ts.emojiPickerDisplay }} @@ -139,6 +146,9 @@ import { emojiPicker } from '@/scripts/emoji-picker.js'; import MkCustomEmoji from '@/components/global/MkCustomEmoji.vue'; import MkEmoji from '@/components/global/MkEmoji.vue'; import MkFolder from '@/components/MkFolder.vue'; +import MkSelect from '@/components/MkSelect.vue'; +import { signinRequired } from '@/account.js'; +import MkInput from '@/components/MkInput.vue'; const pinnedEmojisForReaction: Ref = ref(deepClone(defaultStore.state.reactions)); const pinnedEmojis: Ref = ref(deepClone(defaultStore.state.pinnedEmojis)); @@ -155,6 +165,20 @@ const setDefaultReaction = () => setDefault(pinnedEmojisForReaction); const removeEmoji = (reaction: string, ev: MouseEvent) => remove(pinnedEmojis, reaction, ev); const chooseEmoji = (ev: MouseEvent) => pickEmoji(pinnedEmojis, ev); const setDefaultEmoji = () => setDefault(pinnedEmojis); +const nowProfileId = ref(defaultStore.state.pickerProfileDefault); + +const $i = signinRequired(); +const profileMax = $i.policies.emojiPickerProfileLimit; +const profileName = ref(defaultStore.state[`pickerProfileName${nowProfileId.value > 1 ? nowProfileId.value - 1 : ''}`]); +pinnedEmojisForReaction.value = deepClone(defaultStore.state[`reactions${nowProfileId.value > 1 ? nowProfileId.value - 1 : ''}`]); +pinnedEmojis.value = deepClone(defaultStore.state[`pinnedEmojis${nowProfileId.value > 1 ? nowProfileId.value - 1 : ''}`]); +profileName.value = deepClone(defaultStore.state[`pickerProfileName${nowProfileId.value > 1 ? nowProfileId.value - 1 : ''}`]); + +watch(nowProfileId, () => { + pinnedEmojisForReaction.value = deepClone(defaultStore.state[`reactions${nowProfileId.value > 1 ? nowProfileId.value - 1 : ''}`]); + pinnedEmojis.value = deepClone(defaultStore.state[`pinnedEmojis${nowProfileId.value > 1 ? nowProfileId.value - 1 : ''}`]); + profileName.value = deepClone(defaultStore.state[`pickerProfileName${nowProfileId.value > 1 ? nowProfileId.value - 1 : ''}`]); +}); function previewReaction(ev: MouseEvent) { reactionPicker.show(getHTMLElement(ev)); @@ -226,17 +250,32 @@ function getHTMLElement(ev: MouseEvent): HTMLElement { } watch(pinnedEmojisForReaction, () => { - defaultStore.set('reactions', pinnedEmojisForReaction.value); + defaultStore.set(`reactions${nowProfileId.value > 1 ? nowProfileId.value - 1 : ''}`, pinnedEmojisForReaction.value); }, { deep: true, }); watch(pinnedEmojis, () => { - defaultStore.set('pinnedEmojis', pinnedEmojis.value); + defaultStore.set( `pinnedEmojis${nowProfileId.value > 1 ? nowProfileId.value - 1 : ''}`, pinnedEmojis.value); }, { deep: true, }); +watch(profileName, () => { + defaultStore.set(`pickerProfileName${nowProfileId.value > 1 ? nowProfileId.value - 1 : ''}`, profileName.value); +}, { + deep: true, +}); + +async function setDefaultProfile() { + const { canceled } = await os.confirm({ + type: 'info', + text: i18n.ts.setDefaultProfileConfirm, + }); + if (canceled) return; + await defaultStore.set('pickerProfileDefault', nowProfileId.value); +} + definePageMetadata({ title: i18n.ts.emojiPicker, icon: 'ti ti-mood-happy', diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts index 8ce4963581..d4181d21d8 100644 --- a/packages/frontend/src/store.ts +++ b/packages/frontend/src/store.ts @@ -58,11 +58,10 @@ export const noteActions: NoteAction[] = []; export const noteViewInterruptors: NoteViewInterruptor[] = []; export const notePostInterruptors: NotePostInterruptor[] = []; export const pageViewInterruptors: PageViewInterruptor[] = []; -export const bannerDark='https://files.prismisskey.space/misskey/e088c6d1-b07f-4312-8d41-fee2f64071e9.png' -export const bannerLight ='https://files.prismisskey.space/misskey/85500d2f-41a9-48ff-a737-65d6fdf74604.png' -export const iconDark='https://files.prismisskey.space/misskey/484efc68-de41-4786-b2b6-e5085c31c2c4.webp' -export const iconLight='https://files.prismisskey.space/misskey/c3d722fe-379f-4c85-9414-90c232d53237.webp' - +export const bannerDark = 'https://files.prismisskey.space/misskey/e088c6d1-b07f-4312-8d41-fee2f64071e9.png'; +export const bannerLight = 'https://files.prismisskey.space/misskey/85500d2f-41a9-48ff-a737-65d6fdf74604.png'; +export const iconDark = 'https://files.prismisskey.space/misskey/484efc68-de41-4786-b2b6-e5085c31c2c4.webp'; +export const iconLight = 'https://files.prismisskey.space/misskey/c3d722fe-379f-4c85-9414-90c232d53237.webp'; // TODO: それぞれいちいちwhereとかdefaultというキーを付けなきゃいけないの冗長なのでなんとかする(ただ型定義が面倒になりそう) // あと、現行の定義の仕方なら「whereが何であるかに関わらずキー名の重複不可」という制約を付けられるメリットもあるからそのメリットを引き継ぐ方法も考えないといけない @@ -129,6 +128,74 @@ export const defaultStore = markRaw(new Storage('base', { where: 'account', default: [], }, + reactions1: { + where: 'account', + default: ['👍', '❤️', '😆', '🤔', '😮', '🎉', '💢', '😥', '😇', '🍮'], + }, + pinnedEmojis1: { + where: 'account', + default: [], + }, + reactions2: { + where: 'account', + default: ['👍', '❤️', '😆', '🤔', '😮', '🎉', '💢', '😥', '😇', '🍮'], + }, + pinnedEmojis2: { + where: 'account', + default: [], + }, + reactions3: { + where: 'account', + default: ['👍', '❤️', '😆', '🤔', '😮', '🎉', '💢', '😥', '😇', '🍮'], + }, + pinnedEmojis3: { + where: 'account', + default: [], + }, + reactions4: { + where: 'account', + default: ['👍', '❤️', '😆', '🤔', '😮', '🎉', '💢', '😥', '😇', '🍮'], + }, + pinnedEmojis4: { + where: 'account', + default: [], + }, + reactions5: { + where: 'account', + default: ['👍', '❤️', '😆', '🤔', '😮', '🎉', '💢', '😥', '😇', '🍮'], + }, + pinnedEmojis5: { + where: 'account', + default: [], + }, + pickerProfileName: { + where: 'account', + default: 'default', + }, + pickerProfileName1: { + where: 'account', + default: '1', + }, + pickerProfileName2: { + where: 'account', + default: '2', + }, + pickerProfileName3: { + where: 'account', + default: '3', + }, + pickerProfileName4: { + where: 'account', + default: '4', + }, + pickerProfileName5: { + where: 'account', + default: '5', + }, + pickerProfileDefault: { + where: 'account', + default: 1, + }, reactionAcceptance: { where: 'account', default: 'nonSensitiveOnly' as 'likeOnly' | 'likeOnlyForRemote' | 'nonSensitiveOnly' | 'nonSensitiveOnlyForLocalLikeOnlyForRemote' | null, @@ -296,19 +363,19 @@ export const defaultStore = markRaw(new Storage('base', { }, gamingType: { where: 'device', - default: 'dark', + default: 'dark', }, indicatorCounterToggle: { where: 'device', default: 'true', }, - bannerUrl:{ + bannerUrl: { where: 'device', - default: bannerDark + default: bannerDark, }, - iconUrl:{ + iconUrl: { where: 'device', - default: iconDark + default: iconDark, }, instanceTicker: { where: 'device', @@ -334,9 +401,9 @@ export const defaultStore = markRaw(new Storage('base', { where: 'device', default: [] as string[], }, - enablehanntenn:{ - where:'device', - default: false + enablehanntenn: { + where: 'device', + default: false, }, recentlyUsedUsers: { where: 'device', @@ -378,39 +445,39 @@ export const defaultStore = markRaw(new Storage('base', { where: 'device', default: 3, }, - specifiedColor:{ + specifiedColor: { where: 'device', default: '#FFFF64', }, - followerColor:{ + followerColor: { where: 'device', default: '#FF00FF', }, - homeColor:{ + homeColor: { where: 'device', default: '#00FFFF', }, - localOnlyColor:{ - where:'device', - default: '#2b2c41' + localOnlyColor: { + where: 'device', + default: '#2b2c41', }, numberOfGamingSpeed: { where: 'device', default: 44, }, - onlyAndWithSave:{ + onlyAndWithSave: { where: 'device', default: false, }, - onlyFiles:{ + onlyFiles: { where: 'device', default: false, }, - withReplies:{ + withReplies: { where: 'device', default: true, }, - withRenotes:{ + withRenotes: { where: 'device', default: true, }, @@ -422,15 +489,15 @@ export const defaultStore = markRaw(new Storage('base', { where: 'device', default: false, }, - showMediaTimeline:{ + showMediaTimeline: { where: 'device', default: true, }, - showGlobalTimeline:{ + showGlobalTimeline: { where: 'device', default: true, }, - showVisibilityColor:{ + showVisibilityColor: { where: 'device', default: false, }, @@ -557,7 +624,6 @@ export const defaultStore = markRaw(new Storage('base', { }, })); - // TODO: 他のタブと永続化されたstateを同期 const PREFIX = 'miux:' as const;