+
{{ i18n.ts.metadata }}
@@ -78,7 +78,7 @@ SPDX-License-Identifier: AGPL-3.0-only
{{ i18n.ts._externalResourceInstaller._meta.base }}
- {{ i18n.ts[extension.meta.base ?? 'none'] }}
+ {{ { light: i18n.ts.light, dark: i18n.ts.dark, none: i18n.ts.none }[extension.meta.base ?? 'none'] }}
diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue
index b9cb37e99a..043af4cc96 100644
--- a/packages/frontend/src/components/MkNote.vue
+++ b/packages/frontend/src/components/MkNote.vue
@@ -265,21 +265,19 @@ const currentClip = inject
[ | null>('currentClip', nul
let note = deepClone(props.note);
-// コンポーネント初期化に非同期的な処理を行うとTransitionのレンダリングがバグるため同期的に実行できるメソッドが実装されるのを待つ必要がある
-// https://github.com/aiscript-dev/aiscript/issues/937
-//// plugin
-//const noteViewInterruptors = getPluginHandlers('note_view_interruptor');
-//if (noteViewInterruptors.length > 0) {
-// let result: Misskey.entities.Note | null = deepClone(note);
-// for (const interruptor of noteViewInterruptors) {
-// try {
-// result = await interruptor.handler(result!) as Misskey.entities.Note | null;
-// } catch (err) {
-// console.error(err);
-// }
-// }
-// note = result as Misskey.entities.Note;
-//}
+// plugin
+const noteViewInterruptors = getPluginHandlers('note_view_interruptor');
+if (noteViewInterruptors.length > 0) {
+ let result: Misskey.entities.Note | null = deepClone(note);
+ for (const interruptor of noteViewInterruptors) {
+ try {
+ result = interruptor.handler(result!) as Misskey.entities.Note | null;
+ } catch (err) {
+ console.error(err);
+ }
+ }
+ note = result as Misskey.entities.Note;
+}
const isRenote = Misskey.note.isPureRenote(note);
const appearNote = getAppearNote(note) ?? note;
diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue
index c04959b97a..f3e990e65a 100644
--- a/packages/frontend/src/components/MkNoteDetailed.vue
+++ b/packages/frontend/src/components/MkNoteDetailed.vue
@@ -287,20 +287,19 @@ const inChannel = inject('inChannel', null);
let note = deepClone(props.note);
-// コンポーネント初期化に非同期的な処理を行うとTransitionのレンダリングがバグるため同期的に実行できるメソッドが実装されるのを待つ必要がある
-//// plugin
-//const noteViewInterruptors = getPluginHandlers('note_view_interruptor');
-//if (noteViewInterruptors.length > 0) {
-// let result: Misskey.entities.Note | null = deepClone(note);
-// for (const interruptor of noteViewInterruptors) {
-// try {
-// result = await interruptor.handler(result!) as Misskey.entities.Note | null;
-// } catch (err) {
-// console.error(err);
-// }
-// }
-// note = result as Misskey.entities.Note;
-//}
+// plugin
+const noteViewInterruptors = getPluginHandlers('note_view_interruptor');
+if (noteViewInterruptors.length > 0) {
+ let result: Misskey.entities.Note | null = deepClone(note);
+ for (const interruptor of noteViewInterruptors) {
+ try {
+ result = interruptor.handler(result!) as Misskey.entities.Note | null;
+ } catch (err) {
+ console.error(err);
+ }
+ }
+ note = result as Misskey.entities.Note;
+}
const isRenote = Misskey.note.isPureRenote(note);
const appearNote = getAppearNote(note);
diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue
index d76e3791e4..8dac862b9a 100644
--- a/packages/frontend/src/components/MkPostForm.vue
+++ b/packages/frontend/src/components/MkPostForm.vue
@@ -911,6 +911,11 @@ async function post(ev?: MouseEvent) {
if (uploader.items.value.some(x => x.uploaded == null)) {
await uploadFiles();
+
+ // アップロード失敗したものがあったら中止
+ if (uploader.items.value.some(x => x.uploaded == null)) {
+ return;
+ }
}
let postData = {
diff --git a/packages/frontend/src/components/MkSuperMenu.vue b/packages/frontend/src/components/MkSuperMenu.vue
index 5c89a6530d..dbc673333c 100644
--- a/packages/frontend/src/components/MkSuperMenu.vue
+++ b/packages/frontend/src/components/MkSuperMenu.vue
@@ -52,9 +52,9 @@ SPDX-License-Identifier: AGPL-3.0-only
{{ item.label }}
- {{ item.parentLabels.join(' > ') }}
+ {{ item.parentLabels.join(' > ') }}
]
- {{ item.label }}
+ {{ item.label }}
@@ -95,7 +95,7 @@ export type SuperMenuDef = {
@@ -77,7 +78,7 @@ const emit = defineEmits<{
const injectedPageMetadata = inject(DI.pageMetadata, ref(null));
const pageMetadata = computed(() => props.overridePageMetadata ?? injectedPageMetadata.value);
-const hideTitle = computed(() => inject('shouldOmitHeaderTitle', false) || props.hideTitle);
+const hideTitle = computed(() => inject('shouldOmitHeaderTitle', false) || props.hideTitle || (props.canOmitTitle && props.tabs.length > 0));
const thin_ = props.thin || inject('shouldHeaderThin', false);
const el = useTemplateRef('el');
diff --git a/packages/frontend/src/components/global/PageWithHeader.vue b/packages/frontend/src/components/global/PageWithHeader.vue
index d90afb652e..d368dee88a 100644
--- a/packages/frontend/src/components/global/PageWithHeader.vue
+++ b/packages/frontend/src/components/global/PageWithHeader.vue
@@ -6,14 +6,22 @@ SPDX-License-Identifier: AGPL-3.0-only
@@ -26,6 +34,7 @@ import { useScrollPositionKeeper } from '@/composables/use-scroll-position-keepe
import MkSwiper from '@/components/MkSwiper.vue';
import { useRouter } from '@/router.js';
import { prefer } from '@/preferences.js';
+import MkTabs from '@/components/MkTabs.vue';
const props = withDefaults(defineProps {
return rest;
});
+const pageHeaderPropsWithoutTabs = computed(() => {
+ const { reversed, tabs, ...rest } = props;
+ return rest;
+});
+
const tab = defineModel('tab');
const rootEl = useTemplateRef('rootEl');
@@ -68,4 +82,11 @@ defineExpose({
.body, .swiper {
min-height: calc(100cqh - (var(--MI-stickyTop, 0px) + var(--MI-stickyBottom, 0px)));
}
+
+.footerTabs {
+ background: color(from var(--MI_THEME-pageHeaderBg) srgb r g b / 0.75);
+ -webkit-backdrop-filter: var(--MI-blur, blur(15px));
+ backdrop-filter: var(--MI-blur, blur(15px));
+ border-top: solid 0.5px var(--MI_THEME-divider);
+}
diff --git a/packages/frontend/src/components/global/SearchText.vue b/packages/frontend/src/components/global/SearchText.vue
new file mode 100644
index 0000000000..27a284faf0
--- /dev/null
+++ b/packages/frontend/src/components/global/SearchText.vue
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
diff --git a/packages/frontend/src/components/index.ts b/packages/frontend/src/components/index.ts
index 19766e8575..6b1b80695f 100644
--- a/packages/frontend/src/components/index.ts
+++ b/packages/frontend/src/components/index.ts
@@ -31,7 +31,7 @@ import PageWithHeader from './global/PageWithHeader.vue';
import PageWithAnimBg from './global/PageWithAnimBg.vue';
import SearchMarker from './global/SearchMarker.vue';
import SearchLabel from './global/SearchLabel.vue';
-import SearchKeyword from './global/SearchKeyword.vue';
+import SearchText from './global/SearchText.vue';
import SearchIcon from './global/SearchIcon.vue';
import type { App } from 'vue';
@@ -71,7 +71,7 @@ export const components = {
PageWithAnimBg: PageWithAnimBg,
SearchMarker: SearchMarker,
SearchLabel: SearchLabel,
- SearchKeyword: SearchKeyword,
+ SearchText: SearchText,
SearchIcon: SearchIcon,
};
@@ -105,7 +105,7 @@ declare module '@vue/runtime-core' {
PageWithAnimBg: typeof PageWithAnimBg;
SearchMarker: typeof SearchMarker;
SearchLabel: typeof SearchLabel;
- SearchKeyword: typeof SearchKeyword;
+ SearchText: typeof SearchText;
SearchIcon: typeof SearchIcon;
}
}
diff --git a/packages/frontend/src/i18n.ts b/packages/frontend/src/i18n.ts
index 6ad503b089..0b2b206b7e 100644
--- a/packages/frontend/src/i18n.ts
+++ b/packages/frontend/src/i18n.ts
@@ -5,11 +5,12 @@
import { markRaw } from 'vue';
import { I18n } from '@@/js/i18n.js';
+import { locale } from '@@/js/locale.js';
import type { Locale } from '../../../locales/index.js';
-import { locale } from '@@/js/config.js';
export const i18n = markRaw(new I18n(locale, _DEV_));
+// test 以外では使わないこと。インライン化されてるのでだいたい意味がない
export function updateI18n(newLocale: Locale) {
i18n.locale = newLocale;
}
diff --git a/packages/frontend/src/lib/nirax.ts b/packages/frontend/src/lib/nirax.ts
index 70db47e24e..74dda9decd 100644
--- a/packages/frontend/src/lib/nirax.ts
+++ b/packages/frontend/src/lib/nirax.ts
@@ -165,6 +165,8 @@ function buildFullPath(args: {
const replaceRegex = new RegExp(`:${key}(\\?)?`, 'g');
fullPath = fullPath.replace(replaceRegex, value ? encodeURIComponent(value) : '');
}
+ // remove any optional parameters that are not provided
+ fullPath = fullPath.replace(/\/:\w+\?(?=\/|$)/g, '');
}
if (args.query) {
diff --git a/packages/frontend/src/local-storage.ts b/packages/frontend/src/local-storage.ts
index b64a8c5dd5..687983bcdb 100644
--- a/packages/frontend/src/local-storage.ts
+++ b/packages/frontend/src/local-storage.ts
@@ -22,8 +22,7 @@ export type Keys = (
'fontSize' |
'ui' |
'ui_temp' |
- 'locale' |
- 'localeVersion' |
+ 'bootloaderLocales' |
'theme' |
'themeId' |
'customCss' |
diff --git a/packages/frontend/src/pages/admin/bot-protection.vue b/packages/frontend/src/pages/admin/bot-protection.vue
index 6c580f87f1..bd919146f8 100644
--- a/packages/frontend/src/pages/admin/bot-protection.vue
+++ b/packages/frontend/src/pages/admin/bot-protection.vue
@@ -4,158 +4,161 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
-
-
- {{ i18n.ts.botProtection }}
- hCaptcha
- mCaptcha
- reCAPTCHA
- Turnstile
- testCaptcha
- {{ i18n.ts.none }} ({{ i18n.ts.notRecommended }})
-
-
-
+
+
+
+ {{ i18n.ts.botProtection }}
+ hCaptcha
+ mCaptcha
+ reCAPTCHA
+ Turnstile
+ testCaptcha
+ {{ i18n.ts.none }} ({{ i18n.ts.notRecommended }})
+
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
- {{ i18n.ts.hcaptchaSiteKey }}
-
-
-
- {{ i18n.ts.hcaptchaSecretKey }}
-
-
- {{ i18n.ts._captcha.verify }}
-
-
-
-
-
{{ i18n.ts._captcha.testSiteKeyMessage }}
-
-
ref: hCaptcha Developer Guide
+
+
+
+ {{ i18n.ts.hcaptchaSiteKey }}
+
+
+
+ {{ i18n.ts.hcaptchaSecretKey }}
+
+
+ {{ i18n.ts._captcha.verify }}
+
+
+
+
+
{{ i18n.ts._captcha.testSiteKeyMessage }}
+
-
-
-
+
+
-
-
-
- {{ i18n.ts.mcaptchaSiteKey }}
-
-
-
- {{ i18n.ts.mcaptchaSecretKey }}
-
-
-
- {{ i18n.ts.mcaptchaInstanceUrl }}
-
-
- {{ i18n.ts._captcha.verify }}
-
-
-
+
+
+
+ {{ i18n.ts.mcaptchaSiteKey }}
+
+
+
+ {{ i18n.ts.mcaptchaSecretKey }}
+
+
+
+ {{ i18n.ts.mcaptchaInstanceUrl }}
+
+
+ {{ i18n.ts._captcha.verify }}
+
+
+
-
-
-
- {{ i18n.ts.recaptchaSiteKey }}
-
-
-
- {{ i18n.ts.recaptchaSecretKey }}
-
-
- {{ i18n.ts._captcha.verify }}
-
-
-
-
-
{{ i18n.ts._captcha.testSiteKeyMessage }}
-
-
ref:
-
reCAPTCHA FAQ
+
+
+
+ {{ i18n.ts.recaptchaSiteKey }}
+
+
+
+ {{ i18n.ts.recaptchaSecretKey }}
+
+
+ {{ i18n.ts._captcha.verify }}
+
+
+
+
+
{{ i18n.ts._captcha.testSiteKeyMessage }}
+
-
-
-
+
+
-
-
-
- {{ i18n.ts.turnstileSiteKey }}
-
-
-
- {{ i18n.ts.turnstileSecretKey }}
-
-
- {{ i18n.ts._captcha.verify }}
-
-
-
-
-
- {{ i18n.ts._captcha.testSiteKeyMessage }}
+
+
+
+ {{ i18n.ts.turnstileSiteKey }}
+
+
+
+ {{ i18n.ts.turnstileSecretKey }}
+
+
+ {{ i18n.ts._captcha.verify }}
+
+
+
+
+
+ {{ i18n.ts._captcha.testSiteKeyMessage }}
+
+
-
-
-
-
+
+
-
-
-
- {{ i18n.ts._captcha.verify }}
-
-
-
-
-
+
+
+
+ {{ i18n.ts._captcha.verify }}
+
+
+
+
+
+
diff --git a/packages/frontend/src/ui/_common_/mobile-footer-menu.vue b/packages/frontend/src/ui/_common_/mobile-footer-menu.vue
index e2993230be..7ab8a45f51 100644
--- a/packages/frontend/src/ui/_common_/mobile-footer-menu.vue
+++ b/packages/frontend/src/ui/_common_/mobile-footer-menu.vue
@@ -86,7 +86,7 @@ watch(rootEl, () => {
box-sizing: border-box;
background: var(--MI_THEME-navBg);
color: var(--MI_THEME-navFg);
- box-shadow: 0px 0px 6px 6px #0000000f;
+ border-top: solid 0.5px var(--MI_THEME-divider);
}
.item {
diff --git a/packages/frontend/src/ui/deck.vue b/packages/frontend/src/ui/deck.vue
index d2b163a38f..7cd54f01ef 100644
--- a/packages/frontend/src/ui/deck.vue
+++ b/packages/frontend/src/ui/deck.vue
@@ -13,6 +13,8 @@ SPDX-License-Identifier: AGPL-3.0-only
+
+
@@ -81,12 +83,14 @@ SPDX-License-Identifier: AGPL-3.0-only