summaryrefslogtreecommitdiff
path: root/packages/frontend/src/pages/admin
diff options
context:
space:
mode:
authorsyuilo <4439005+syuilo@users.noreply.github.com>2025-08-03 11:02:20 +0900
committerGitHub <noreply@github.com>2025-08-03 11:02:20 +0900
commit6f3cc2cdf7e47a2dd4dd6d7478579746e2af652c (patch)
treeb6820cddaf963fe0489c7f1c44fd9324022c10e4 /packages/frontend/src/pages/admin
parentperf(frontend): tweak css performance (diff)
downloadmisskey-6f3cc2cdf7e47a2dd4dd6d7478579746e2af652c.tar.gz
misskey-6f3cc2cdf7e47a2dd4dd6d7478579746e2af652c.tar.bz2
misskey-6f3cc2cdf7e47a2dd4dd6d7478579746e2af652c.zip
コントロールパネルの検索 (#16343)
* Update settings.vue * Update settings.vue * Update settings.vue * Update settings.vue * Update settings.vue * Update performance.vue * Update performance.vue * Update performance.vue * Update external-services.vue * wip * wip * Update security.vue * Update settings.vue * Update CHANGELOG.md * wip * Update moderation.vue * wip * Update branding.vue * wip * Update email-settings.vue * Update system-webhook.vue * Update MkSuperMenu.vue * Update index.vue
Diffstat (limited to 'packages/frontend/src/pages/admin')
-rw-r--r--packages/frontend/src/pages/admin/bot-protection.vue280
-rw-r--r--packages/frontend/src/pages/admin/branding.vue211
-rw-r--r--packages/frontend/src/pages/admin/email-settings.vue115
-rw-r--r--packages/frontend/src/pages/admin/external-services.vue78
-rw-r--r--packages/frontend/src/pages/admin/index.vue11
-rw-r--r--packages/frontend/src/pages/admin/moderation.vue267
-rw-r--r--packages/frontend/src/pages/admin/object-storage.vue161
-rw-r--r--packages/frontend/src/pages/admin/performance.vue252
-rw-r--r--packages/frontend/src/pages/admin/relays.vue22
-rw-r--r--packages/frontend/src/pages/admin/security.vue222
-rw-r--r--packages/frontend/src/pages/admin/server-rules.vue23
-rw-r--r--packages/frontend/src/pages/admin/settings.vue563
-rw-r--r--packages/frontend/src/pages/admin/system-webhook.vue24
13 files changed, 1211 insertions, 1018 deletions
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
-->
<template>
-<MkFolder>
- <template #icon><i class="ti ti-shield"></i></template>
- <template #label>{{ i18n.ts.botProtection }}</template>
- <template v-if="botProtectionForm.savedState.provider === 'hcaptcha'" #suffix>hCaptcha</template>
- <template v-else-if="botProtectionForm.savedState.provider === 'mcaptcha'" #suffix>mCaptcha</template>
- <template v-else-if="botProtectionForm.savedState.provider === 'recaptcha'" #suffix>reCAPTCHA</template>
- <template v-else-if="botProtectionForm.savedState.provider === 'turnstile'" #suffix>Turnstile</template>
- <template v-else-if="botProtectionForm.savedState.provider === 'testcaptcha'" #suffix>testCaptcha</template>
- <template v-else #suffix>{{ i18n.ts.none }} ({{ i18n.ts.notRecommended }})</template>
- <template #footer>
- <MkFormFooter :canSaving="canSaving" :form="botProtectionForm"/>
- </template>
+<SearchMarker markerId="botProtection" :keywords="['bot', 'protection', 'captcha', 'hcaptcha', 'mcaptcha', 'recaptcha', 'turnstile']">
+ <MkFolder>
+ <template #icon><SearchIcon><i class="ti ti-shield"></i></SearchIcon></template>
+ <template #label><SearchLabel>{{ i18n.ts.botProtection }}</SearchLabel></template>
+ <template v-if="botProtectionForm.savedState.provider === 'hcaptcha'" #suffix>hCaptcha</template>
+ <template v-else-if="botProtectionForm.savedState.provider === 'mcaptcha'" #suffix>mCaptcha</template>
+ <template v-else-if="botProtectionForm.savedState.provider === 'recaptcha'" #suffix>reCAPTCHA</template>
+ <template v-else-if="botProtectionForm.savedState.provider === 'turnstile'" #suffix>Turnstile</template>
+ <template v-else-if="botProtectionForm.savedState.provider === 'testcaptcha'" #suffix>testCaptcha</template>
+ <template v-else #suffix>{{ i18n.ts.none }} ({{ i18n.ts.notRecommended }})</template>
+ <template #footer>
+ <MkFormFooter :canSaving="canSaving" :form="botProtectionForm"/>
+ </template>
- <div class="_gaps_m">
- <MkRadios v-model="botProtectionForm.state.provider">
- <option value="none">{{ i18n.ts.none }} ({{ i18n.ts.notRecommended }})</option>
- <option value="hcaptcha">hCaptcha</option>
- <option value="mcaptcha">mCaptcha</option>
- <option value="recaptcha">reCAPTCHA</option>
- <option value="turnstile">Turnstile</option>
- <option value="testcaptcha">testCaptcha</option>
- </MkRadios>
+ <div class="_gaps_m">
+ <MkRadios v-model="botProtectionForm.state.provider">
+ <option value="none">{{ i18n.ts.none }} ({{ i18n.ts.notRecommended }})</option>
+ <option value="hcaptcha">hCaptcha</option>
+ <option value="mcaptcha">mCaptcha</option>
+ <option value="recaptcha">reCAPTCHA</option>
+ <option value="turnstile">Turnstile</option>
+ <option value="testcaptcha">testCaptcha</option>
+ </MkRadios>
- <template v-if="botProtectionForm.state.provider === 'hcaptcha'">
- <MkInput v-model="botProtectionForm.state.hcaptchaSiteKey" debounce>
- <template #prefix><i class="ti ti-key"></i></template>
- <template #label>{{ i18n.ts.hcaptchaSiteKey }}</template>
- </MkInput>
- <MkInput v-model="botProtectionForm.state.hcaptchaSecretKey" debounce>
- <template #prefix><i class="ti ti-key"></i></template>
- <template #label>{{ i18n.ts.hcaptchaSecretKey }}</template>
- </MkInput>
- <FormSlot v-if="botProtectionForm.state.hcaptchaSiteKey">
- <template #label>{{ i18n.ts._captcha.verify }}</template>
- <MkCaptcha
- v-model="captchaResult"
- provider="hcaptcha"
- :sitekey="botProtectionForm.state.hcaptchaSiteKey"
- :secretKey="botProtectionForm.state.hcaptchaSecretKey"
- />
- </FormSlot>
- <MkInfo>
- <div :class="$style.captchaInfoMsg">
- <div>{{ i18n.ts._captcha.testSiteKeyMessage }}</div>
- <div>
- <span>ref: </span><a href="https://docs.hcaptcha.com/#integration-testing-test-keys" target="_blank">hCaptcha Developer Guide</a>
+ <template v-if="botProtectionForm.state.provider === 'hcaptcha'">
+ <MkInput v-model="botProtectionForm.state.hcaptchaSiteKey" debounce>
+ <template #prefix><i class="ti ti-key"></i></template>
+ <template #label>{{ i18n.ts.hcaptchaSiteKey }}</template>
+ </MkInput>
+ <MkInput v-model="botProtectionForm.state.hcaptchaSecretKey" debounce>
+ <template #prefix><i class="ti ti-key"></i></template>
+ <template #label>{{ i18n.ts.hcaptchaSecretKey }}</template>
+ </MkInput>
+ <FormSlot v-if="botProtectionForm.state.hcaptchaSiteKey">
+ <template #label>{{ i18n.ts._captcha.verify }}</template>
+ <MkCaptcha
+ v-model="captchaResult"
+ provider="hcaptcha"
+ :sitekey="botProtectionForm.state.hcaptchaSiteKey"
+ :secretKey="botProtectionForm.state.hcaptchaSecretKey"
+ />
+ </FormSlot>
+ <MkInfo>
+ <div :class="$style.captchaInfoMsg">
+ <div>{{ i18n.ts._captcha.testSiteKeyMessage }}</div>
+ <div>
+ <span>ref: </span><a href="https://docs.hcaptcha.com/#integration-testing-test-keys" target="_blank">hCaptcha Developer Guide</a>
+ </div>
</div>
- </div>
- </MkInfo>
- </template>
+ </MkInfo>
+ </template>
- <template v-else-if="botProtectionForm.state.provider === 'mcaptcha'">
- <MkInput v-model="botProtectionForm.state.mcaptchaSiteKey" debounce>
- <template #prefix><i class="ti ti-key"></i></template>
- <template #label>{{ i18n.ts.mcaptchaSiteKey }}</template>
- </MkInput>
- <MkInput v-model="botProtectionForm.state.mcaptchaSecretKey" debounce>
- <template #prefix><i class="ti ti-key"></i></template>
- <template #label>{{ i18n.ts.mcaptchaSecretKey }}</template>
- </MkInput>
- <MkInput v-model="botProtectionForm.state.mcaptchaInstanceUrl" debounce>
- <template #prefix><i class="ti ti-link"></i></template>
- <template #label>{{ i18n.ts.mcaptchaInstanceUrl }}</template>
- </MkInput>
- <FormSlot v-if="botProtectionForm.state.mcaptchaSiteKey && botProtectionForm.state.mcaptchaInstanceUrl">
- <template #label>{{ i18n.ts._captcha.verify }}</template>
- <MkCaptcha
- v-model="captchaResult"
- provider="mcaptcha"
- :sitekey="botProtectionForm.state.mcaptchaSiteKey"
- :secretKey="botProtectionForm.state.mcaptchaSecretKey"
- :instanceUrl="botProtectionForm.state.mcaptchaInstanceUrl"
- />
- </FormSlot>
- </template>
+ <template v-else-if="botProtectionForm.state.provider === 'mcaptcha'">
+ <MkInput v-model="botProtectionForm.state.mcaptchaSiteKey" debounce>
+ <template #prefix><i class="ti ti-key"></i></template>
+ <template #label>{{ i18n.ts.mcaptchaSiteKey }}</template>
+ </MkInput>
+ <MkInput v-model="botProtectionForm.state.mcaptchaSecretKey" debounce>
+ <template #prefix><i class="ti ti-key"></i></template>
+ <template #label>{{ i18n.ts.mcaptchaSecretKey }}</template>
+ </MkInput>
+ <MkInput v-model="botProtectionForm.state.mcaptchaInstanceUrl" debounce>
+ <template #prefix><i class="ti ti-link"></i></template>
+ <template #label>{{ i18n.ts.mcaptchaInstanceUrl }}</template>
+ </MkInput>
+ <FormSlot v-if="botProtectionForm.state.mcaptchaSiteKey && botProtectionForm.state.mcaptchaInstanceUrl">
+ <template #label>{{ i18n.ts._captcha.verify }}</template>
+ <MkCaptcha
+ v-model="captchaResult"
+ provider="mcaptcha"
+ :sitekey="botProtectionForm.state.mcaptchaSiteKey"
+ :secretKey="botProtectionForm.state.mcaptchaSecretKey"
+ :instanceUrl="botProtectionForm.state.mcaptchaInstanceUrl"
+ />
+ </FormSlot>
+ </template>
- <template v-else-if="botProtectionForm.state.provider === 'recaptcha'">
- <MkInput v-model="botProtectionForm.state.recaptchaSiteKey" debounce>
- <template #prefix><i class="ti ti-key"></i></template>
- <template #label>{{ i18n.ts.recaptchaSiteKey }}</template>
- </MkInput>
- <MkInput v-model="botProtectionForm.state.recaptchaSecretKey" debounce>
- <template #prefix><i class="ti ti-key"></i></template>
- <template #label>{{ i18n.ts.recaptchaSecretKey }}</template>
- </MkInput>
- <FormSlot v-if="botProtectionForm.state.recaptchaSiteKey">
- <template #label>{{ i18n.ts._captcha.verify }}</template>
- <MkCaptcha
- v-model="captchaResult"
- provider="recaptcha"
- :sitekey="botProtectionForm.state.recaptchaSiteKey"
- :secretKey="botProtectionForm.state.recaptchaSecretKey"
- />
- </FormSlot>
- <MkInfo>
- <div :class="$style.captchaInfoMsg">
- <div>{{ i18n.ts._captcha.testSiteKeyMessage }}</div>
- <div>
- <span>ref: </span>
- <a
- href="https://developers.google.com/recaptcha/docs/faq?hl=ja#id-like-to-run-automated-tests-with-recaptcha.-what-should-i-do"
- target="_blank"
- >reCAPTCHA FAQ</a>
+ <template v-else-if="botProtectionForm.state.provider === 'recaptcha'">
+ <MkInput v-model="botProtectionForm.state.recaptchaSiteKey" debounce>
+ <template #prefix><i class="ti ti-key"></i></template>
+ <template #label>{{ i18n.ts.recaptchaSiteKey }}</template>
+ </MkInput>
+ <MkInput v-model="botProtectionForm.state.recaptchaSecretKey" debounce>
+ <template #prefix><i class="ti ti-key"></i></template>
+ <template #label>{{ i18n.ts.recaptchaSecretKey }}</template>
+ </MkInput>
+ <FormSlot v-if="botProtectionForm.state.recaptchaSiteKey">
+ <template #label>{{ i18n.ts._captcha.verify }}</template>
+ <MkCaptcha
+ v-model="captchaResult"
+ provider="recaptcha"
+ :sitekey="botProtectionForm.state.recaptchaSiteKey"
+ :secretKey="botProtectionForm.state.recaptchaSecretKey"
+ />
+ </FormSlot>
+ <MkInfo>
+ <div :class="$style.captchaInfoMsg">
+ <div>{{ i18n.ts._captcha.testSiteKeyMessage }}</div>
+ <div>
+ <span>ref: </span>
+ <a
+ href="https://developers.google.com/recaptcha/docs/faq?hl=ja#id-like-to-run-automated-tests-with-recaptcha.-what-should-i-do"
+ target="_blank"
+ >reCAPTCHA FAQ</a>
+ </div>
</div>
- </div>
- </MkInfo>
- </template>
+ </MkInfo>
+ </template>
- <template v-else-if="botProtectionForm.state.provider === 'turnstile'">
- <MkInput v-model="botProtectionForm.state.turnstileSiteKey" debounce>
- <template #prefix><i class="ti ti-key"></i></template>
- <template #label>{{ i18n.ts.turnstileSiteKey }}</template>
- </MkInput>
- <MkInput v-model="botProtectionForm.state.turnstileSecretKey" debounce>
- <template #prefix><i class="ti ti-key"></i></template>
- <template #label>{{ i18n.ts.turnstileSecretKey }}</template>
- </MkInput>
- <FormSlot v-if="botProtectionForm.state.turnstileSiteKey">
- <template #label>{{ i18n.ts._captcha.verify }}</template>
- <MkCaptcha
- v-model="captchaResult"
- provider="turnstile"
- :sitekey="botProtectionForm.state.turnstileSiteKey"
- :secretKey="botProtectionForm.state.turnstileSecretKey"
- />
- </FormSlot>
- <MkInfo>
- <div :class="$style.captchaInfoMsg">
- <div>
- {{ i18n.ts._captcha.testSiteKeyMessage }}
- </div>
- <div>
- <span>ref: </span><a href="https://developers.cloudflare.com/turnstile/troubleshooting/testing/" target="_blank">Cloudflare Docs</a>
+ <template v-else-if="botProtectionForm.state.provider === 'turnstile'">
+ <MkInput v-model="botProtectionForm.state.turnstileSiteKey" debounce>
+ <template #prefix><i class="ti ti-key"></i></template>
+ <template #label>{{ i18n.ts.turnstileSiteKey }}</template>
+ </MkInput>
+ <MkInput v-model="botProtectionForm.state.turnstileSecretKey" debounce>
+ <template #prefix><i class="ti ti-key"></i></template>
+ <template #label>{{ i18n.ts.turnstileSecretKey }}</template>
+ </MkInput>
+ <FormSlot v-if="botProtectionForm.state.turnstileSiteKey">
+ <template #label>{{ i18n.ts._captcha.verify }}</template>
+ <MkCaptcha
+ v-model="captchaResult"
+ provider="turnstile"
+ :sitekey="botProtectionForm.state.turnstileSiteKey"
+ :secretKey="botProtectionForm.state.turnstileSecretKey"
+ />
+ </FormSlot>
+ <MkInfo>
+ <div :class="$style.captchaInfoMsg">
+ <div>
+ {{ i18n.ts._captcha.testSiteKeyMessage }}
+ </div>
+ <div>
+ <span>ref: </span><a href="https://developers.cloudflare.com/turnstile/troubleshooting/testing/" target="_blank">Cloudflare Docs</a>
+ </div>
</div>
- </div>
- </MkInfo>
- </template>
+ </MkInfo>
+ </template>
- <template v-else-if="botProtectionForm.state.provider === 'testcaptcha'">
- <MkInfo warn><span v-html="i18n.ts.testCaptchaWarning"></span></MkInfo>
- <FormSlot>
- <template #label>{{ i18n.ts._captcha.verify }}</template>
- <MkCaptcha v-model="captchaResult" provider="testcaptcha" :sitekey="null"/>
- </FormSlot>
- </template>
- </div>
-</MkFolder>
+ <template v-else-if="botProtectionForm.state.provider === 'testcaptcha'">
+ <MkInfo warn><span v-html="i18n.ts.testCaptchaWarning"></span></MkInfo>
+ <FormSlot>
+ <template #label>{{ i18n.ts._captcha.verify }}</template>
+ <MkCaptcha v-model="captchaResult" provider="testcaptcha" :sitekey="null"/>
+ </FormSlot>
+ </template>
+ </div>
+ </MkFolder>
+</SearchMarker>
</template>
<script lang="ts" setup>
import { computed, defineAsyncComponent, ref, watch } from 'vue';
import * as Misskey from 'misskey-js';
+import type { ApiWithDialogCustomErrors } from '@/os.js';
import MkRadios from '@/components/MkRadios.vue';
import MkInput from '@/components/MkInput.vue';
import FormSlot from '@/components/form/slot.vue';
@@ -167,7 +170,6 @@ import { useForm } from '@/composables/use-form.js';
import MkFormFooter from '@/components/MkFormFooter.vue';
import MkFolder from '@/components/MkFolder.vue';
import MkInfo from '@/components/MkInfo.vue';
-import type { ApiWithDialogCustomErrors } from '@/os.js';
const MkCaptcha = defineAsyncComponent(() => import('@/components/MkCaptcha.vue'));
diff --git a/packages/frontend/src/pages/admin/branding.vue b/packages/frontend/src/pages/admin/branding.vue
index 19258216f6..dc6495e06d 100644
--- a/packages/frontend/src/pages/admin/branding.vue
+++ b/packages/frontend/src/pages/admin/branding.vue
@@ -6,89 +6,117 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<PageWithHeader :tabs="headerTabs">
<div class="_spacer" style="--MI_SPACER-w: 700px; --MI_SPACER-min: 16px; --MI_SPACER-max: 32px;">
- <FormSuspense :p="init">
+ <SearchMarker path="/admin/branding" :label="i18n.ts.branding" :keywords="['branding']" icon="ti ti-paint">
<div class="_gaps_m">
- <MkInput v-model="iconUrl" type="url">
- <template #prefix><i class="ti ti-link"></i></template>
- <template #label>{{ i18n.ts._serverSettings.iconUrl }}</template>
- </MkInput>
+ <SearchMarker :keywords="['icon', 'image']">
+ <MkInput v-model="iconUrl" type="url">
+ <template #prefix><i class="ti ti-link"></i></template>
+ <template #label><SearchLabel>{{ i18n.ts._serverSettings.iconUrl }}</SearchLabel></template>
+ </MkInput>
+ </SearchMarker>
- <MkInput v-model="app192IconUrl" type="url">
- <template #prefix><i class="ti ti-link"></i></template>
- <template #label>{{ i18n.ts._serverSettings.iconUrl }} (App/192px)</template>
- <template #caption>
- <div>{{ i18n.tsx._serverSettings.appIconDescription({ host: instance.name ?? host }) }}</div>
- <div>({{ i18n.ts._serverSettings.appIconUsageExample }})</div>
- <div>{{ i18n.ts._serverSettings.appIconStyleRecommendation }}</div>
- <div><strong>{{ i18n.tsx._serverSettings.appIconResolutionMustBe({ resolution: '192x192px' }) }}</strong></div>
- </template>
- </MkInput>
+ <SearchMarker :keywords="['icon', 'image']">
+ <MkInput v-model="app192IconUrl" type="url">
+ <template #prefix><i class="ti ti-link"></i></template>
+ <template #label><SearchLabel>{{ i18n.ts._serverSettings.iconUrl }} (App/192px)</SearchLabel></template>
+ <template #caption>
+ <div>{{ i18n.tsx._serverSettings.appIconDescription({ host: instance.name ?? host }) }}</div>
+ <div>({{ i18n.ts._serverSettings.appIconUsageExample }})</div>
+ <div>{{ i18n.ts._serverSettings.appIconStyleRecommendation }}</div>
+ <div><strong>{{ i18n.tsx._serverSettings.appIconResolutionMustBe({ resolution: '192x192px' }) }}</strong></div>
+ </template>
+ </MkInput>
+ </SearchMarker>
- <MkInput v-model="app512IconUrl" type="url">
- <template #prefix><i class="ti ti-link"></i></template>
- <template #label>{{ i18n.ts._serverSettings.iconUrl }} (App/512px)</template>
- <template #caption>
- <div>{{ i18n.tsx._serverSettings.appIconDescription({ host: instance.name ?? host }) }}</div>
- <div>({{ i18n.ts._serverSettings.appIconUsageExample }})</div>
- <div>{{ i18n.ts._serverSettings.appIconStyleRecommendation }}</div>
- <div><strong>{{ i18n.tsx._serverSettings.appIconResolutionMustBe({ resolution: '512x512px' }) }}</strong></div>
- </template>
- </MkInput>
+ <SearchMarker :keywords="['icon', 'image']">
+ <MkInput v-model="app512IconUrl" type="url">
+ <template #prefix><i class="ti ti-link"></i></template>
+ <template #label><SearchLabel>{{ i18n.ts._serverSettings.iconUrl }} (App/512px)</SearchLabel></template>
+ <template #caption>
+ <div>{{ i18n.tsx._serverSettings.appIconDescription({ host: instance.name ?? host }) }}</div>
+ <div>({{ i18n.ts._serverSettings.appIconUsageExample }})</div>
+ <div>{{ i18n.ts._serverSettings.appIconStyleRecommendation }}</div>
+ <div><strong>{{ i18n.tsx._serverSettings.appIconResolutionMustBe({ resolution: '512x512px' }) }}</strong></div>
+ </template>
+ </MkInput>
+ </SearchMarker>
- <MkInput v-model="bannerUrl" type="url">
- <template #prefix><i class="ti ti-link"></i></template>
- <template #label>{{ i18n.ts.bannerUrl }}</template>
- </MkInput>
+ <SearchMarker :keywords="['banner', 'image']">
+ <MkInput v-model="bannerUrl" type="url">
+ <template #prefix><i class="ti ti-link"></i></template>
+ <template #label><SearchLabel>{{ i18n.ts.bannerUrl }}</SearchLabel></template>
+ </MkInput>
+ </SearchMarker>
- <MkInput v-model="backgroundImageUrl" type="url">
- <template #prefix><i class="ti ti-link"></i></template>
- <template #label>{{ i18n.ts.backgroundImageUrl }}</template>
- </MkInput>
+ <SearchMarker :keywords="['background', 'image']">
+ <MkInput v-model="backgroundImageUrl" type="url">
+ <template #prefix><i class="ti ti-link"></i></template>
+ <template #label><SearchLabel>{{ i18n.ts.backgroundImageUrl }}</SearchLabel></template>
+ </MkInput>
+ </SearchMarker>
- <MkInput v-model="notFoundImageUrl" type="url">
- <template #prefix><i class="ti ti-link"></i></template>
- <template #label>{{ i18n.ts.notFoundDescription }}</template>
- </MkInput>
+ <SearchMarker :keywords="['image']">
+ <MkInput v-model="notFoundImageUrl" type="url">
+ <template #prefix><i class="ti ti-link"></i></template>
+ <template #label><SearchLabel>{{ i18n.ts.notFoundDescription }}</SearchLabel></template>
+ </MkInput>
+ </SearchMarker>
- <MkInput v-model="infoImageUrl" type="url">
- <template #prefix><i class="ti ti-link"></i></template>
- <template #label>{{ i18n.ts.nothing }}</template>
- </MkInput>
+ <SearchMarker :keywords="['image']">
+ <MkInput v-model="infoImageUrl" type="url">
+ <template #prefix><i class="ti ti-link"></i></template>
+ <template #label><SearchLabel>{{ i18n.ts.nothing }}</SearchLabel></template>
+ </MkInput>
+ </SearchMarker>
- <MkInput v-model="serverErrorImageUrl" type="url">
- <template #prefix><i class="ti ti-link"></i></template>
- <template #label>{{ i18n.ts.somethingHappened }}</template>
- </MkInput>
+ <SearchMarker :keywords="['image']">
+ <MkInput v-model="serverErrorImageUrl" type="url">
+ <template #prefix><i class="ti ti-link"></i></template>
+ <template #label><SearchLabel>{{ i18n.ts.somethingHappened }}</SearchLabel></template>
+ </MkInput>
+ </SearchMarker>
- <MkColorInput v-model="themeColor">
- <template #label>{{ i18n.ts.themeColor }}</template>
- </MkColorInput>
+ <SearchMarker :keywords="['theme', 'color']">
+ <MkColorInput v-model="themeColor">
+ <template #label><SearchLabel>{{ i18n.ts.themeColor }}</SearchLabel></template>
+ </MkColorInput>
+ </SearchMarker>
- <MkTextarea v-model="defaultLightTheme">
- <template #label>{{ i18n.ts.instanceDefaultLightTheme }}</template>
- <template #caption>{{ i18n.ts.instanceDefaultThemeDescription }}</template>
- </MkTextarea>
+ <SearchMarker :keywords="['theme', 'default', 'light']">
+ <MkTextarea v-model="defaultLightTheme">
+ <template #label><SearchLabel>{{ i18n.ts.instanceDefaultLightTheme }}</SearchLabel></template>
+ <template #caption>{{ i18n.ts.instanceDefaultThemeDescription }}</template>
+ </MkTextarea>
+ </SearchMarker>
- <MkTextarea v-model="defaultDarkTheme">
- <template #label>{{ i18n.ts.instanceDefaultDarkTheme }}</template>
- <template #caption>{{ i18n.ts.instanceDefaultThemeDescription }}</template>
- </MkTextarea>
+ <SearchMarker :keywords="['theme', 'default', 'dark']">
+ <MkTextarea v-model="defaultDarkTheme">
+ <template #label><SearchLabel>{{ i18n.ts.instanceDefaultDarkTheme }}</SearchLabel></template>
+ <template #caption>{{ i18n.ts.instanceDefaultThemeDescription }}</template>
+ </MkTextarea>
+ </SearchMarker>
- <MkInput v-model="repositoryUrl" type="url">
- <template #prefix><i class="ti ti-link"></i></template>
- <template #label>{{ i18n.ts.repositoryUrl }}</template>
- </MkInput>
+ <SearchMarker>
+ <MkInput v-model="repositoryUrl" type="url">
+ <template #prefix><i class="ti ti-link"></i></template>
+ <template #label><SearchLabel>{{ i18n.ts.repositoryUrl }}</SearchLabel></template>
+ </MkInput>
+ </SearchMarker>
- <MkInput v-model="feedbackUrl" type="url">
- <template #prefix><i class="ti ti-link"></i></template>
- <template #label>{{ i18n.ts.feedbackUrl }}</template>
- </MkInput>
+ <SearchMarker>
+ <MkInput v-model="feedbackUrl" type="url">
+ <template #prefix><i class="ti ti-link"></i></template>
+ <template #label><SearchLabel>{{ i18n.ts.feedbackUrl }}</SearchLabel></template>
+ </MkInput>
+ </SearchMarker>
- <MkTextarea v-model="manifestJsonOverride">
- <template #label>{{ i18n.ts._serverSettings.manifestJsonOverride }}</template>
- </MkTextarea>
+ <SearchMarker>
+ <MkTextarea v-model="manifestJsonOverride">
+ <template #label><SearchLabel>{{ i18n.ts._serverSettings.manifestJsonOverride }}</SearchLabel></template>
+ </MkTextarea>
+ </SearchMarker>
</div>
- </FormSuspense>
+ </SearchMarker>
</div>
<template #footer>
<div :class="$style.footer">
@@ -106,7 +134,6 @@ import JSON5 from 'json5';
import { host } from '@@/js/config.js';
import MkInput from '@/components/MkInput.vue';
import MkTextarea from '@/components/MkTextarea.vue';
-import FormSuspense from '@/components/form/suspense.vue';
import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import { instance, fetchInstance } from '@/instance.js';
@@ -115,38 +142,22 @@ import { definePage } from '@/page.js';
import MkButton from '@/components/MkButton.vue';
import MkColorInput from '@/components/MkColorInput.vue';
-const iconUrl = ref<string | null>(null);
-const app192IconUrl = ref<string | null>(null);
-const app512IconUrl = ref<string | null>(null);
-const bannerUrl = ref<string | null>(null);
-const backgroundImageUrl = ref<string | null>(null);
-const themeColor = ref<string | null>(null);
-const defaultLightTheme = ref<string | null>(null);
-const defaultDarkTheme = ref<string | null>(null);
-const serverErrorImageUrl = ref<string | null>(null);
-const infoImageUrl = ref<string | null>(null);
-const notFoundImageUrl = ref<string | null>(null);
-const repositoryUrl = ref<string | null>(null);
-const feedbackUrl = ref<string | null>(null);
-const manifestJsonOverride = ref<string>('{}');
+const meta = await misskeyApi('admin/meta');
-async function init() {
- const meta = await misskeyApi('admin/meta');
- iconUrl.value = meta.iconUrl;
- app192IconUrl.value = meta.app192IconUrl;
- app512IconUrl.value = meta.app512IconUrl;
- bannerUrl.value = meta.bannerUrl;
- backgroundImageUrl.value = meta.backgroundImageUrl;
- themeColor.value = meta.themeColor;
- defaultLightTheme.value = meta.defaultLightTheme;
- defaultDarkTheme.value = meta.defaultDarkTheme;
- serverErrorImageUrl.value = meta.serverErrorImageUrl;
- infoImageUrl.value = meta.infoImageUrl;
- notFoundImageUrl.value = meta.notFoundImageUrl;
- repositoryUrl.value = meta.repositoryUrl;
- feedbackUrl.value = meta.feedbackUrl;
- manifestJsonOverride.value = meta.manifestJsonOverride === '' ? '{}' : JSON.stringify(JSON.parse(meta.manifestJsonOverride), null, '\t');
-}
+const iconUrl = ref(meta.iconUrl);
+const app192IconUrl = ref(meta.app192IconUrl);
+const app512IconUrl = ref(meta.app512IconUrl);
+const bannerUrl = ref(meta.bannerUrl);
+const backgroundImageUrl = ref(meta.backgroundImageUrl);
+const themeColor = ref(meta.themeColor);
+const defaultLightTheme = ref(meta.defaultLightTheme);
+const defaultDarkTheme = ref(meta.defaultDarkTheme);
+const serverErrorImageUrl = ref(meta.serverErrorImageUrl);
+const infoImageUrl = ref(meta.infoImageUrl);
+const notFoundImageUrl = ref(meta.notFoundImageUrl);
+const repositoryUrl = ref(meta.repositoryUrl);
+const feedbackUrl = ref(meta.feedbackUrl);
+const manifestJsonOverride = ref(meta.manifestJsonOverride === '' ? '{}' : JSON.stringify(JSON.parse(meta.manifestJsonOverride), null, '\t'));
function save() {
os.apiWithDialog('admin/update-meta', {
diff --git a/packages/frontend/src/pages/admin/email-settings.vue b/packages/frontend/src/pages/admin/email-settings.vue
index 17f2f8b593..8eb403f94c 100644
--- a/packages/frontend/src/pages/admin/email-settings.vue
+++ b/packages/frontend/src/pages/admin/email-settings.vue
@@ -6,48 +6,67 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<PageWithHeader :tabs="headerTabs">
<div class="_spacer" style="--MI_SPACER-w: 700px; --MI_SPACER-min: 16px; --MI_SPACER-max: 32px;">
- <FormSuspense :p="init">
+ <SearchMarker path="/admin/email-settings" :label="i18n.ts.emailServer" :keywords="['email']" icon="ti ti-mail">
<div class="_gaps_m">
- <MkSwitch v-model="enableEmail">
- <template #label>{{ i18n.ts.enableEmail }} ({{ i18n.ts.recommended }})</template>
- <template #caption>{{ i18n.ts.emailConfigInfo }}</template>
- </MkSwitch>
+ <SearchMarker>
+ <MkSwitch v-model="enableEmail">
+ <template #label><SearchLabel>{{ i18n.ts.enableEmail }}</SearchLabel> ({{ i18n.ts.recommended }})</template>
+ <template #caption><SearchText>{{ i18n.ts.emailConfigInfo }}</SearchText></template>
+ </MkSwitch>
+ </SearchMarker>
<template v-if="enableEmail">
- <MkInput v-model="email" type="email">
- <template #label>{{ i18n.ts.emailAddress }}</template>
- </MkInput>
+ <SearchMarker>
+ <MkInput v-model="email" type="email">
+ <template #label><SearchLabel>{{ i18n.ts.emailAddress }}</SearchLabel></template>
+ </MkInput>
+ </SearchMarker>
- <FormSection>
- <template #label>{{ i18n.ts.smtpConfig }}</template>
+ <SearchMarker>
+ <FormSection>
+ <template #label><SearchLabel>{{ i18n.ts.smtpConfig }}</SearchLabel></template>
- <div class="_gaps_m">
- <FormSplit :minWidth="280">
- <MkInput v-model="smtpHost">
- <template #label>{{ i18n.ts.smtpHost }}</template>
- </MkInput>
- <MkInput v-model="smtpPort" type="number">
- <template #label>{{ i18n.ts.smtpPort }}</template>
- </MkInput>
- </FormSplit>
- <FormSplit :minWidth="280">
- <MkInput v-model="smtpUser">
- <template #label>{{ i18n.ts.smtpUser }}</template>
- </MkInput>
- <MkInput v-model="smtpPass" type="password">
- <template #label>{{ i18n.ts.smtpPass }}</template>
- </MkInput>
- </FormSplit>
- <FormInfo>{{ i18n.ts.emptyToDisableSmtpAuth }}</FormInfo>
- <MkSwitch v-model="smtpSecure">
- <template #label>{{ i18n.ts.smtpSecure }}</template>
- <template #caption>{{ i18n.ts.smtpSecureInfo }}</template>
- </MkSwitch>
- </div>
- </FormSection>
+ <div class="_gaps_m">
+ <FormSplit :minWidth="280">
+ <SearchMarker>
+ <MkInput v-model="smtpHost">
+ <template #label><SearchLabel>{{ i18n.ts.smtpHost }}</SearchLabel></template>
+ </MkInput>
+ </SearchMarker>
+ <SearchMarker>
+ <MkInput v-model="smtpPort" type="number">
+ <template #label><SearchLabel>{{ i18n.ts.smtpPort }}</SearchLabel></template>
+ </MkInput>
+ </SearchMarker>
+ </FormSplit>
+
+ <FormSplit :minWidth="280">
+ <SearchMarker>
+ <MkInput v-model="smtpUser">
+ <template #label><SearchLabel>{{ i18n.ts.smtpUser }}</SearchLabel></template>
+ </MkInput>
+ </SearchMarker>
+ <SearchMarker>
+ <MkInput v-model="smtpPass" type="password">
+ <template #label><SearchLabel>{{ i18n.ts.smtpPass }}</SearchLabel></template>
+ </MkInput>
+ </SearchMarker>
+ </FormSplit>
+
+ <FormInfo>{{ i18n.ts.emptyToDisableSmtpAuth }}</FormInfo>
+
+ <SearchMarker>
+ <MkSwitch v-model="smtpSecure">
+ <template #label><SearchLabel>{{ i18n.ts.smtpSecure }}</SearchLabel></template>
+ <template #caption><SearchText>{{ i18n.ts.smtpSecureInfo }}</SearchText></template>
+ </MkSwitch>
+ </SearchMarker>
+ </div>
+ </FormSection>
+ </SearchMarker>
</template>
</div>
- </FormSuspense>
+ </SearchMarker>
</div>
<template #footer>
<div :class="$style.footer">
@@ -67,7 +86,6 @@ import { ref, computed } from 'vue';
import MkSwitch from '@/components/MkSwitch.vue';
import MkInput from '@/components/MkInput.vue';
import FormInfo from '@/components/MkInfo.vue';
-import FormSuspense from '@/components/form/suspense.vue';
import FormSplit from '@/components/form/split.vue';
import FormSection from '@/components/form/section.vue';
import * as os from '@/os.js';
@@ -77,24 +95,15 @@ import { i18n } from '@/i18n.js';
import { definePage } from '@/page.js';
import MkButton from '@/components/MkButton.vue';
-const enableEmail = ref<boolean>(false);
-const email = ref<string | null>(null);
-const smtpSecure = ref<boolean>(false);
-const smtpHost = ref<string>('');
-const smtpPort = ref<number>(0);
-const smtpUser = ref<string>('');
-const smtpPass = ref<string>('');
+const meta = await misskeyApi('admin/meta');
-async function init() {
- const meta = await misskeyApi('admin/meta');
- enableEmail.value = meta.enableEmail;
- email.value = meta.email;
- smtpSecure.value = meta.smtpSecure;
- smtpHost.value = meta.smtpHost;
- smtpPort.value = meta.smtpPort;
- smtpUser.value = meta.smtpUser;
- smtpPass.value = meta.smtpPass;
-}
+const enableEmail = ref(meta.enableEmail);
+const email = ref(meta.email);
+const smtpSecure = ref(meta.smtpSecure);
+const smtpHost = ref(meta.smtpHost);
+const smtpPort = ref(meta.smtpPort);
+const smtpUser = ref(meta.smtpUser);
+const smtpPass = ref(meta.smtpPass);
async function testEmail() {
const { canceled, result: destination } = await os.inputText({
diff --git a/packages/frontend/src/pages/admin/external-services.vue b/packages/frontend/src/pages/admin/external-services.vue
index 845fb12c5d..3a4eb8c3c7 100644
--- a/packages/frontend/src/pages/admin/external-services.vue
+++ b/packages/frontend/src/pages/admin/external-services.vue
@@ -6,36 +6,49 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<PageWithHeader :actions="headerActions" :tabs="headerTabs">
<div class="_spacer" style="--MI_SPACER-w: 700px; --MI_SPACER-min: 16px; --MI_SPACER-max: 32px;">
- <FormSuspense :p="init">
+ <SearchMarker path="/admin/external-services" :label="i18n.ts.externalServices" :keywords="['external', 'services', 'thirdparty']" icon="ti ti-link">
<div class="_gaps_m">
- <MkFolder>
- <template #label>Google Analytics<span class="_beta">{{ i18n.ts.beta }}</span></template>
+ <SearchMarker v-slot="slotProps">
+ <MkFolder :defaultOpen="slotProps.isParentOfTarget">
+ <template #label><SearchLabel>Google Analytics</SearchLabel><span class="_beta">{{ i18n.ts.beta }}</span></template>
- <div class="_gaps_m">
- <MkInput v-model="googleAnalyticsMeasurementId">
- <template #prefix><i class="ti ti-key"></i></template>
- <template #label>Measurement ID</template>
- </MkInput>
- <MkButton primary @click="save_googleAnalytics">Save</MkButton>
- </div>
- </MkFolder>
+ <div class="_gaps_m">
+ <SearchMarker>
+ <MkInput v-model="googleAnalyticsMeasurementId">
+ <template #prefix><i class="ti ti-key"></i></template>
+ <template #label><SearchLabel>Measurement ID</SearchLabel></template>
+ </MkInput>
+ </SearchMarker>
- <MkFolder>
- <template #label>DeepL Translation</template>
+ <MkButton primary @click="save_googleAnalytics">Save</MkButton>
+ </div>
+ </MkFolder>
+ </SearchMarker>
- <div class="_gaps_m">
- <MkInput v-model="deeplAuthKey">
- <template #prefix><i class="ti ti-key"></i></template>
- <template #label>DeepL Auth Key</template>
- </MkInput>
- <MkSwitch v-model="deeplIsPro">
- <template #label>Pro account</template>
- </MkSwitch>
- <MkButton primary @click="save_deepl">Save</MkButton>
- </div>
- </MkFolder>
+ <SearchMarker v-slot="slotProps">
+ <MkFolder :defaultOpen="slotProps.isParentOfTarget">
+ <template #label><SearchLabel>DeepL Translation</SearchLabel></template>
+
+ <div class="_gaps_m">
+ <SearchMarker>
+ <MkInput v-model="deeplAuthKey">
+ <template #prefix><i class="ti ti-key"></i></template>
+ <template #label><SearchLabel>Auth Key</SearchLabel></template>
+ </MkInput>
+ </SearchMarker>
+
+ <SearchMarker>
+ <MkSwitch v-model="deeplIsPro">
+ <template #label><SearchLabel>Pro account</SearchLabel></template>
+ </MkSwitch>
+ </SearchMarker>
+
+ <MkButton primary @click="save_deepl">Save</MkButton>
+ </div>
+ </MkFolder>
+ </SearchMarker>
</div>
- </FormSuspense>
+ </SearchMarker>
</div>
</PageWithHeader>
</template>
@@ -45,7 +58,6 @@ import { ref, computed } from 'vue';
import MkInput from '@/components/MkInput.vue';
import MkButton from '@/components/MkButton.vue';
import MkSwitch from '@/components/MkSwitch.vue';
-import FormSuspense from '@/components/form/suspense.vue';
import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import { fetchInstance } from '@/instance.js';
@@ -53,17 +65,11 @@ import { i18n } from '@/i18n.js';
import { definePage } from '@/page.js';
import MkFolder from '@/components/MkFolder.vue';
-const deeplAuthKey = ref<string>('');
-const deeplIsPro = ref<boolean>(false);
+const meta = await misskeyApi('admin/meta');
-const googleAnalyticsMeasurementId = ref<string>('');
-
-async function init() {
- const meta = await misskeyApi('admin/meta');
- deeplAuthKey.value = meta.deeplAuthKey ?? '';
- deeplIsPro.value = meta.deeplIsPro;
- googleAnalyticsMeasurementId.value = meta.googleAnalyticsMeasurementId ?? '';
-}
+const deeplAuthKey = ref(meta.deeplAuthKey ?? '');
+const deeplIsPro = ref(meta.deeplIsPro);
+const googleAnalyticsMeasurementId = ref(meta.googleAnalyticsMeasurementId ?? '');
function save_deepl() {
os.apiWithDialog('admin/update-meta', {
diff --git a/packages/frontend/src/pages/admin/index.vue b/packages/frontend/src/pages/admin/index.vue
index a87028f008..94994dc94c 100644
--- a/packages/frontend/src/pages/admin/index.vue
+++ b/packages/frontend/src/pages/admin/index.vue
@@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkInfo v-if="noEmailServer" warn>{{ i18n.ts.noEmailServerWarning }} <MkA to="/admin/email-settings" class="_link">{{ i18n.ts.configure }}</MkA></MkInfo>
</div>
- <MkSuperMenu :def="menuDef" :grid="narrow"></MkSuperMenu>
+ <MkSuperMenu :def="menuDef" :searchIndex="searchIndex" :grid="narrow"></MkSuperMenu>
</div>
</div>
</div>
@@ -44,6 +44,9 @@ import { misskeyApi } from '@/utility/misskey-api.js';
import { lookupUser, lookupUserByEmail, lookupFile } from '@/utility/admin-lookup.js';
import { definePage, provideMetadataReceiver, provideReactiveMetadata } from '@/page.js';
import { useRouter } from '@/router.js';
+import { genSearchIndexes } from '@/utility/inapp-search.js';
+
+const searchIndex = await import('search-index:admin').then(({ searchIndexes }) => genSearchIndexes(searchIndexes));
const isEmpty = (x: string | null) => x == null || x === '';
@@ -324,12 +327,6 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
definePage(() => INFO.value);
-
-defineExpose({
- header: {
- title: i18n.ts.controlPanel,
- },
-});
</script>
<style lang="scss" scoped>
diff --git a/packages/frontend/src/pages/admin/moderation.vue b/packages/frontend/src/pages/admin/moderation.vue
index 819f229c10..83438bb1d9 100644
--- a/packages/frontend/src/pages/admin/moderation.vue
+++ b/packages/frontend/src/pages/admin/moderation.vue
@@ -6,140 +6,162 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<PageWithHeader :tabs="headerTabs">
<div class="_spacer" style="--MI_SPACER-w: 700px; --MI_SPACER-min: 16px; --MI_SPACER-max: 32px;">
- <FormSuspense :p="init">
+ <SearchMarker path="/admin/moderation" :label="i18n.ts.moderation" :keywords="['moderation']" icon="ti ti-shield" :inlining="['serverRules']">
<div class="_gaps_m">
- <MkSwitch :modelValue="enableRegistration" @update:modelValue="onChange_enableRegistration">
- <template #label>{{ i18n.ts._serverSettings.openRegistration }}</template>
- <template #caption>
- <div>{{ i18n.ts._serverSettings.thisSettingWillAutomaticallyOffWhenModeratorsInactive }}</div>
- <div><i class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i> {{ i18n.ts._serverSettings.openRegistrationWarning }}</div>
- </template>
- </MkSwitch>
+ <SearchMarker :keywords="['open', 'registration']">
+ <MkSwitch :modelValue="enableRegistration" @update:modelValue="onChange_enableRegistration">
+ <template #label><SearchLabel>{{ i18n.ts._serverSettings.openRegistration }}</SearchLabel></template>
+ <template #caption>
+ <div><SearchText>{{ i18n.ts._serverSettings.thisSettingWillAutomaticallyOffWhenModeratorsInactive }}</SearchText></div>
+ <div><i class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i> <SearchText>{{ i18n.ts._serverSettings.openRegistrationWarning }}</SearchText></div>
+ </template>
+ </MkSwitch>
+ </SearchMarker>
- <MkSwitch v-model="emailRequiredForSignup" @change="onChange_emailRequiredForSignup">
- <template #label>{{ i18n.ts.emailRequiredForSignup }} ({{ i18n.ts.recommended }})</template>
- </MkSwitch>
+ <SearchMarker :keywords="['email', 'required', 'signup']">
+ <MkSwitch v-model="emailRequiredForSignup" @change="onChange_emailRequiredForSignup">
+ <template #label><SearchLabel>{{ i18n.ts.emailRequiredForSignup }}</SearchLabel> ({{ i18n.ts.recommended }})</template>
+ </MkSwitch>
+ </SearchMarker>
- <MkSelect v-model="ugcVisibilityForVisitor" @update:modelValue="onChange_ugcVisibilityForVisitor">
- <template #label>{{ i18n.ts._serverSettings.userGeneratedContentsVisibilityForVisitor }}</template>
- <option value="all">{{ i18n.ts._serverSettings._userGeneratedContentsVisibilityForVisitor.all }}</option>
- <option value="local">{{ i18n.ts._serverSettings._userGeneratedContentsVisibilityForVisitor.localOnly }} ({{ i18n.ts.recommended }})</option>
- <option value="none">{{ i18n.ts._serverSettings._userGeneratedContentsVisibilityForVisitor.none }}</option>
- <template #caption>
- <div>{{ i18n.ts._serverSettings.userGeneratedContentsVisibilityForVisitor_description }}</div>
- <div><i class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i> {{ i18n.ts._serverSettings.userGeneratedContentsVisibilityForVisitor_description2 }}</div>
- </template>
- </MkSelect>
+ <SearchMarker :keywords="['ugc', 'content', 'visibility', 'visitor', 'guest']">
+ <MkSelect v-model="ugcVisibilityForVisitor" @update:modelValue="onChange_ugcVisibilityForVisitor">
+ <template #label><SearchLabel>{{ i18n.ts._serverSettings.userGeneratedContentsVisibilityForVisitor }}</SearchLabel></template>
+ <option value="all">{{ i18n.ts._serverSettings._userGeneratedContentsVisibilityForVisitor.all }}</option>
+ <option value="local">{{ i18n.ts._serverSettings._userGeneratedContentsVisibilityForVisitor.localOnly }} ({{ i18n.ts.recommended }})</option>
+ <option value="none">{{ i18n.ts._serverSettings._userGeneratedContentsVisibilityForVisitor.none }}</option>
+ <template #caption>
+ <div><SearchText>{{ i18n.ts._serverSettings.userGeneratedContentsVisibilityForVisitor_description }}</SearchText></div>
+ <div><i class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i> <SearchText>{{ i18n.ts._serverSettings.userGeneratedContentsVisibilityForVisitor_description2 }}</SearchText></div>
+ </template>
+ </MkSelect>
+ </SearchMarker>
- <FormLink to="/admin/server-rules">{{ i18n.ts.serverRules }}</FormLink>
+ <XServerRules/>
- <MkFolder>
- <template #icon><i class="ti ti-lock-star"></i></template>
- <template #label>{{ i18n.ts.preservedUsernames }}</template>
+ <SearchMarker :keywords="['preserved', 'usernames']">
+ <MkFolder>
+ <template #icon><SearchIcon><i class="ti ti-lock-star"></i></SearchIcon></template>
+ <template #label><SearchLabel>{{ i18n.ts.preservedUsernames }}</SearchLabel></template>
- <div class="_gaps">
- <MkTextarea v-model="preservedUsernames">
- <template #caption>{{ i18n.ts.preservedUsernamesDescription }}</template>
- </MkTextarea>
- <MkButton primary @click="save_preservedUsernames">{{ i18n.ts.save }}</MkButton>
- </div>
- </MkFolder>
+ <div class="_gaps">
+ <MkTextarea v-model="preservedUsernames">
+ <template #caption>{{ i18n.ts.preservedUsernamesDescription }}</template>
+ </MkTextarea>
+ <MkButton primary @click="save_preservedUsernames">{{ i18n.ts.save }}</MkButton>
+ </div>
+ </MkFolder>
+ </SearchMarker>
- <MkFolder>
- <template #icon><i class="ti ti-message-exclamation"></i></template>
- <template #label>{{ i18n.ts.sensitiveWords }}</template>
+ <SearchMarker :keywords="['sensitive', 'words']">
+ <MkFolder>
+ <template #icon><SearchIcon><i class="ti ti-message-exclamation"></i></SearchIcon></template>
+ <template #label><SearchLabel>{{ i18n.ts.sensitiveWords }}</SearchLabel></template>
- <div class="_gaps">
- <MkTextarea v-model="sensitiveWords">
- <template #caption>{{ i18n.ts.sensitiveWordsDescription }}<br>{{ i18n.ts.sensitiveWordsDescription2 }}</template>
- </MkTextarea>
- <MkButton primary @click="save_sensitiveWords">{{ i18n.ts.save }}</MkButton>
- </div>
- </MkFolder>
+ <div class="_gaps">
+ <MkTextarea v-model="sensitiveWords">
+ <template #caption>{{ i18n.ts.sensitiveWordsDescription }}<br>{{ i18n.ts.sensitiveWordsDescription2 }}</template>
+ </MkTextarea>
+ <MkButton primary @click="save_sensitiveWords">{{ i18n.ts.save }}</MkButton>
+ </div>
+ </MkFolder>
+ </SearchMarker>
- <MkFolder>
- <template #icon><i class="ti ti-message-x"></i></template>
- <template #label>{{ i18n.ts.prohibitedWords }}</template>
+ <SearchMarker :keywords="['prohibited', 'words']">
+ <MkFolder>
+ <template #icon><SearchIcon><i class="ti ti-message-x"></i></SearchIcon></template>
+ <template #label><SearchLabel>{{ i18n.ts.prohibitedWords }}</SearchLabel></template>
- <div class="_gaps">
- <MkTextarea v-model="prohibitedWords">
- <template #caption>{{ i18n.ts.prohibitedWordsDescription }}<br>{{ i18n.ts.prohibitedWordsDescription2 }}</template>
- </MkTextarea>
- <MkButton primary @click="save_prohibitedWords">{{ i18n.ts.save }}</MkButton>
- </div>
- </MkFolder>
+ <div class="_gaps">
+ <MkTextarea v-model="prohibitedWords">
+ <template #caption>{{ i18n.ts.prohibitedWordsDescription }}<br>{{ i18n.ts.prohibitedWordsDescription2 }}</template>
+ </MkTextarea>
+ <MkButton primary @click="save_prohibitedWords">{{ i18n.ts.save }}</MkButton>
+ </div>
+ </MkFolder>
+ </SearchMarker>
- <MkFolder>
- <template #icon><i class="ti ti-user-x"></i></template>
- <template #label>{{ i18n.ts.prohibitedWordsForNameOfUser }}</template>
+ <SearchMarker :keywords="['prohibited', 'name', 'user']">
+ <MkFolder>
+ <template #icon><SearchIcon><i class="ti ti-user-x"></i></SearchIcon></template>
+ <template #label><SearchLabel>{{ i18n.ts.prohibitedWordsForNameOfUser }}</SearchLabel></template>
- <div class="_gaps">
- <MkTextarea v-model="prohibitedWordsForNameOfUser">
- <template #caption>{{ i18n.ts.prohibitedWordsForNameOfUserDescription }}<br>{{ i18n.ts.prohibitedWordsDescription2 }}</template>
- </MkTextarea>
- <MkButton primary @click="save_prohibitedWordsForNameOfUser">{{ i18n.ts.save }}</MkButton>
- </div>
- </MkFolder>
+ <div class="_gaps">
+ <MkTextarea v-model="prohibitedWordsForNameOfUser">
+ <template #caption>{{ i18n.ts.prohibitedWordsForNameOfUserDescription }}<br>{{ i18n.ts.prohibitedWordsDescription2 }}</template>
+ </MkTextarea>
+ <MkButton primary @click="save_prohibitedWordsForNameOfUser">{{ i18n.ts.save }}</MkButton>
+ </div>
+ </MkFolder>
+ </SearchMarker>
- <MkFolder>
- <template #icon><i class="ti ti-eye-off"></i></template>
- <template #label>{{ i18n.ts.hiddenTags }}</template>
+ <SearchMarker :keywords="['hidden', 'tags', 'hashtags']">
+ <MkFolder>
+ <template #icon><SearchIcon><i class="ti ti-eye-off"></i></SearchIcon></template>
+ <template #label><SearchLabel>{{ i18n.ts.hiddenTags }}</SearchLabel></template>
- <div class="_gaps">
- <MkTextarea v-model="hiddenTags">
- <template #caption>{{ i18n.ts.hiddenTagsDescription }}</template>
- </MkTextarea>
- <MkButton primary @click="save_hiddenTags">{{ i18n.ts.save }}</MkButton>
- </div>
- </MkFolder>
+ <div class="_gaps">
+ <MkTextarea v-model="hiddenTags">
+ <template #caption>{{ i18n.ts.hiddenTagsDescription }}</template>
+ </MkTextarea>
+ <MkButton primary @click="save_hiddenTags">{{ i18n.ts.save }}</MkButton>
+ </div>
+ </MkFolder>
+ </SearchMarker>
- <MkFolder>
- <template #icon><i class="ti ti-eye-off"></i></template>
- <template #label>{{ i18n.ts.silencedInstances }}</template>
+ <SearchMarker :keywords="['silenced', 'servers', 'hosts']">
+ <MkFolder>
+ <template #icon><SearchIcon><i class="ti ti-eye-off"></i></SearchIcon></template>
+ <template #label><SearchLabel>{{ i18n.ts.silencedInstances }}</SearchLabel></template>
- <div class="_gaps">
- <MkTextarea v-model="silencedHosts">
- <template #caption>{{ i18n.ts.silencedInstancesDescription }}</template>
- </MkTextarea>
- <MkButton primary @click="save_silencedHosts">{{ i18n.ts.save }}</MkButton>
- </div>
- </MkFolder>
+ <div class="_gaps">
+ <MkTextarea v-model="silencedHosts">
+ <template #caption>{{ i18n.ts.silencedInstancesDescription }}</template>
+ </MkTextarea>
+ <MkButton primary @click="save_silencedHosts">{{ i18n.ts.save }}</MkButton>
+ </div>
+ </MkFolder>
+ </SearchMarker>
- <MkFolder>
- <template #icon><i class="ti ti-eye-off"></i></template>
- <template #label>{{ i18n.ts.mediaSilencedInstances }}</template>
+ <SearchMarker :keywords="['media', 'silenced', 'servers', 'hosts']">
+ <MkFolder>
+ <template #icon><SearchIcon><i class="ti ti-eye-off"></i></SearchIcon></template>
+ <template #label><SearchLabel>{{ i18n.ts.mediaSilencedInstances }}</SearchLabel></template>
- <div class="_gaps">
- <MkTextarea v-model="mediaSilencedHosts">
- <template #caption>{{ i18n.ts.mediaSilencedInstancesDescription }}</template>
- </MkTextarea>
- <MkButton primary @click="save_mediaSilencedHosts">{{ i18n.ts.save }}</MkButton>
- </div>
- </MkFolder>
+ <div class="_gaps">
+ <MkTextarea v-model="mediaSilencedHosts">
+ <template #caption>{{ i18n.ts.mediaSilencedInstancesDescription }}</template>
+ </MkTextarea>
+ <MkButton primary @click="save_mediaSilencedHosts">{{ i18n.ts.save }}</MkButton>
+ </div>
+ </MkFolder>
+ </SearchMarker>
- <MkFolder>
- <template #icon><i class="ti ti-ban"></i></template>
- <template #label>{{ i18n.ts.blockedInstances }}</template>
+ <SearchMarker :keywords="['blocked', 'servers', 'hosts']">
+ <MkFolder>
+ <template #icon><SearchIcon><i class="ti ti-ban"></i></SearchIcon></template>
+ <template #label><SearchLabel>{{ i18n.ts.blockedInstances }}</SearchLabel></template>
- <div class="_gaps">
- <MkTextarea v-model="blockedHosts">
- <template #caption>{{ i18n.ts.blockedInstancesDescription }}</template>
- </MkTextarea>
- <MkButton primary @click="save_blockedHosts">{{ i18n.ts.save }}</MkButton>
- </div>
- </MkFolder>
+ <div class="_gaps">
+ <MkTextarea v-model="blockedHosts">
+ <template #caption>{{ i18n.ts.blockedInstancesDescription }}</template>
+ </MkTextarea>
+ <MkButton primary @click="save_blockedHosts">{{ i18n.ts.save }}</MkButton>
+ </div>
+ </MkFolder>
+ </SearchMarker>
</div>
- </FormSuspense>
+ </SearchMarker>
</div>
</PageWithHeader>
</template>
<script lang="ts" setup>
import { ref, computed } from 'vue';
+import XServerRules from './server-rules.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import MkInput from '@/components/MkInput.vue';
import MkTextarea from '@/components/MkTextarea.vue';
-import FormSuspense from '@/components/form/suspense.vue';
import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import { fetchInstance } from '@/instance.js';
@@ -150,32 +172,19 @@ import FormLink from '@/components/form/link.vue';
import MkFolder from '@/components/MkFolder.vue';
import MkSelect from '@/components/MkSelect.vue';
-const enableRegistration = ref<boolean>(false);
-const emailRequiredForSignup = ref<boolean>(false);
-const ugcVisibilityForVisitor = ref<string>('all');
-const sensitiveWords = ref<string>('');
-const prohibitedWords = ref<string>('');
-const prohibitedWordsForNameOfUser = ref<string>('');
-const hiddenTags = ref<string>('');
-const preservedUsernames = ref<string>('');
-const blockedHosts = ref<string>('');
-const silencedHosts = ref<string>('');
-const mediaSilencedHosts = ref<string>('');
+const meta = await misskeyApi('admin/meta');
-async function init() {
- const meta = await misskeyApi('admin/meta');
- enableRegistration.value = !meta.disableRegistration;
- emailRequiredForSignup.value = meta.emailRequiredForSignup;
- ugcVisibilityForVisitor.value = meta.ugcVisibilityForVisitor;
- sensitiveWords.value = meta.sensitiveWords.join('\n');
- prohibitedWords.value = meta.prohibitedWords.join('\n');
- prohibitedWordsForNameOfUser.value = meta.prohibitedWordsForNameOfUser.join('\n');
- hiddenTags.value = meta.hiddenTags.join('\n');
- preservedUsernames.value = meta.preservedUsernames.join('\n');
- blockedHosts.value = meta.blockedHosts.join('\n');
- silencedHosts.value = meta.silencedHosts?.join('\n') ?? '';
- mediaSilencedHosts.value = meta.mediaSilencedHosts.join('\n');
-}
+const enableRegistration = ref(!meta.disableRegistration);
+const emailRequiredForSignup = ref(meta.emailRequiredForSignup);
+const ugcVisibilityForVisitor = ref(meta.ugcVisibilityForVisitor);
+const sensitiveWords = ref(meta.sensitiveWords.join('\n'));
+const prohibitedWords = ref(meta.prohibitedWords.join('\n'));
+const prohibitedWordsForNameOfUser = ref(meta.prohibitedWordsForNameOfUser.join('\n'));
+const hiddenTags = ref(meta.hiddenTags.join('\n'));
+const preservedUsernames = ref(meta.preservedUsernames.join('\n'));
+const blockedHosts = ref(meta.blockedHosts.join('\n'));
+const silencedHosts = ref(meta.silencedHosts?.join('\n') ?? '');
+const mediaSilencedHosts = ref(meta.mediaSilencedHosts.join('\n'));
async function onChange_enableRegistration(value: boolean) {
if (value) {
diff --git a/packages/frontend/src/pages/admin/object-storage.vue b/packages/frontend/src/pages/admin/object-storage.vue
index 7a46ae41c6..d42c23a51e 100644
--- a/packages/frontend/src/pages/admin/object-storage.vue
+++ b/packages/frontend/src/pages/admin/object-storage.vue
@@ -6,70 +6,94 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<PageWithHeader :tabs="headerTabs">
<div class="_spacer" style="--MI_SPACER-w: 700px; --MI_SPACER-min: 16px; --MI_SPACER-max: 32px;">
- <FormSuspense :p="init">
+ <SearchMarker path="/admin/object-storage" :label="i18n.ts.objectStorage" :keywords="['objectStorage']" icon="ti ti-cloud">
<div class="_gaps_m">
- <MkSwitch v-model="useObjectStorage">{{ i18n.ts.useObjectStorage }}</MkSwitch>
+ <SearchMarker>
+ <MkSwitch v-model="useObjectStorage"><SearchLabel>{{ i18n.ts.useObjectStorage }}</SearchLabel></MkSwitch>
+ </SearchMarker>
<template v-if="useObjectStorage">
- <MkInput v-model="objectStorageBaseUrl" :placeholder="'https://example.com'" type="url">
- <template #label>{{ i18n.ts.objectStorageBaseUrl }}</template>
- <template #caption>{{ i18n.ts.objectStorageBaseUrlDesc }}</template>
- </MkInput>
+ <SearchMarker>
+ <MkInput v-model="objectStorageBaseUrl" :placeholder="'https://example.com'" type="url">
+ <template #label><SearchLabel>{{ i18n.ts.objectStorageBaseUrl }}</SearchLabel></template>
+ <template #caption><SearchText>{{ i18n.ts.objectStorageBaseUrlDesc }}</SearchText></template>
+ </MkInput>
+ </SearchMarker>
- <MkInput v-model="objectStorageBucket">
- <template #label>{{ i18n.ts.objectStorageBucket }}</template>
- <template #caption>{{ i18n.ts.objectStorageBucketDesc }}</template>
- </MkInput>
+ <SearchMarker>
+ <MkInput v-model="objectStorageBucket">
+ <template #label><SearchLabel>{{ i18n.ts.objectStorageBucket }}</SearchLabel></template>
+ <template #caption><SearchText>{{ i18n.ts.objectStorageBucketDesc }}</SearchText></template>
+ </MkInput>
+ </SearchMarker>
- <MkInput v-model="objectStoragePrefix">
- <template #label>{{ i18n.ts.objectStoragePrefix }}</template>
- <template #caption>{{ i18n.ts.objectStoragePrefixDesc }}</template>
- </MkInput>
+ <SearchMarker>
+ <MkInput v-model="objectStoragePrefix">
+ <template #label><SearchLabel>{{ i18n.ts.objectStoragePrefix }}</SearchLabel></template>
+ <template #caption><SearchText>{{ i18n.ts.objectStoragePrefixDesc }}</SearchText></template>
+ </MkInput>
+ </SearchMarker>
- <MkInput v-model="objectStorageEndpoint" :placeholder="'example.com'">
- <template #label>{{ i18n.ts.objectStorageEndpoint }}</template>
- <template #prefix>https://</template>
- <template #caption>{{ i18n.ts.objectStorageEndpointDesc }}</template>
- </MkInput>
+ <SearchMarker>
+ <MkInput v-model="objectStorageEndpoint" :placeholder="'example.com'">
+ <template #label><SearchLabel>{{ i18n.ts.objectStorageEndpoint }}</SearchLabel></template>
+ <template #prefix>https://</template>
+ <template #caption><SearchText>{{ i18n.ts.objectStorageEndpointDesc }}</SearchText></template>
+ </MkInput>
+ </SearchMarker>
- <MkInput v-model="objectStorageRegion">
- <template #label>{{ i18n.ts.objectStorageRegion }}</template>
- <template #caption>{{ i18n.ts.objectStorageRegionDesc }}</template>
- </MkInput>
+ <SearchMarker>
+ <MkInput v-model="objectStorageRegion">
+ <template #label><SearchLabel>{{ i18n.ts.objectStorageRegion }}</SearchLabel></template>
+ <template #caption><SearchText>{{ i18n.ts.objectStorageRegionDesc }}</SearchText></template>
+ </MkInput>
+ </SearchMarker>
<FormSplit :minWidth="280">
- <MkInput v-model="objectStorageAccessKey">
- <template #prefix><i class="ti ti-key"></i></template>
- <template #label>Access key</template>
- </MkInput>
+ <SearchMarker>
+ <MkInput v-model="objectStorageAccessKey">
+ <template #prefix><i class="ti ti-key"></i></template>
+ <template #label><SearchLabel>Access key</SearchLabel></template>
+ </MkInput>
+ </SearchMarker>
- <MkInput v-model="objectStorageSecretKey" type="password">
- <template #prefix><i class="ti ti-key"></i></template>
- <template #label>Secret key</template>
- </MkInput>
+ <SearchMarker>
+ <MkInput v-model="objectStorageSecretKey" type="password">
+ <template #prefix><i class="ti ti-key"></i></template>
+ <template #label><SearchLabel>Secret key</SearchLabel></template>
+ </MkInput>
+ </SearchMarker>
</FormSplit>
- <MkSwitch v-model="objectStorageUseSSL">
- <template #label>{{ i18n.ts.objectStorageUseSSL }}</template>
- <template #caption>{{ i18n.ts.objectStorageUseSSLDesc }}</template>
- </MkSwitch>
+ <SearchMarker>
+ <MkSwitch v-model="objectStorageUseSSL">
+ <template #label><SearchLabel>{{ i18n.ts.objectStorageUseSSL }}</SearchLabel></template>
+ <template #caption><SearchText>{{ i18n.ts.objectStorageUseSSLDesc }}</SearchText></template>
+ </MkSwitch>
+ </SearchMarker>
- <MkSwitch v-model="objectStorageUseProxy">
- <template #label>{{ i18n.ts.objectStorageUseProxy }}</template>
- <template #caption>{{ i18n.ts.objectStorageUseProxyDesc }}</template>
- </MkSwitch>
+ <SearchMarker>
+ <MkSwitch v-model="objectStorageUseProxy">
+ <template #label><SearchLabel>{{ i18n.ts.objectStorageUseProxy }}</SearchLabel></template>
+ <template #caption><SearchText>{{ i18n.ts.objectStorageUseProxyDesc }}</SearchText></template>
+ </MkSwitch>
+ </SearchMarker>
- <MkSwitch v-model="objectStorageSetPublicRead">
- <template #label>{{ i18n.ts.objectStorageSetPublicRead }}</template>
- </MkSwitch>
+ <SearchMarker>
+ <MkSwitch v-model="objectStorageSetPublicRead">
+ <template #label><SearchLabel>{{ i18n.ts.objectStorageSetPublicRead }}</SearchLabel></template>
+ </MkSwitch>
+ </SearchMarker>
- <MkSwitch v-model="objectStorageS3ForcePathStyle">
- <template #label>s3ForcePathStyle</template>
- <template #caption>{{ i18n.ts.s3ForcePathStyleDesc }}</template>
- </MkSwitch>
+ <SearchMarker>
+ <MkSwitch v-model="objectStorageS3ForcePathStyle">
+ <template #label><SearchLabel>s3ForcePathStyle</SearchLabel></template>
+ <template #caption><SearchText>{{ i18n.ts.s3ForcePathStyleDesc }}</SearchText></template>
+ </MkSwitch>
+ </SearchMarker>
</template>
</div>
- </FormSuspense>
+ </SearchMarker>
</div>
<template #footer>
<div :class="$style.footer">
@@ -94,36 +118,21 @@ import { i18n } from '@/i18n.js';
import { definePage } from '@/page.js';
import MkButton from '@/components/MkButton.vue';
-const useObjectStorage = ref<boolean>(false);
-const objectStorageBaseUrl = ref<string | null>(null);
-const objectStorageBucket = ref<string | null>(null);
-const objectStoragePrefix = ref<string | null>(null);
-const objectStorageEndpoint = ref<string | null>(null);
-const objectStorageRegion = ref<string | null>(null);
-const objectStoragePort = ref<number | null>(null);
-const objectStorageAccessKey = ref<string | null>(null);
-const objectStorageSecretKey = ref<string | null>(null);
-const objectStorageUseSSL = ref<boolean>(false);
-const objectStorageUseProxy = ref<boolean>(false);
-const objectStorageSetPublicRead = ref<boolean>(false);
-const objectStorageS3ForcePathStyle = ref<boolean>(true);
+const meta = await misskeyApi('admin/meta');
-async function init() {
- const meta = await misskeyApi('admin/meta');
- useObjectStorage.value = meta.useObjectStorage;
- objectStorageBaseUrl.value = meta.objectStorageBaseUrl;
- objectStorageBucket.value = meta.objectStorageBucket;
- objectStoragePrefix.value = meta.objectStoragePrefix;
- objectStorageEndpoint.value = meta.objectStorageEndpoint;
- objectStorageRegion.value = meta.objectStorageRegion;
- objectStoragePort.value = meta.objectStoragePort;
- objectStorageAccessKey.value = meta.objectStorageAccessKey;
- objectStorageSecretKey.value = meta.objectStorageSecretKey;
- objectStorageUseSSL.value = meta.objectStorageUseSSL;
- objectStorageUseProxy.value = meta.objectStorageUseProxy;
- objectStorageSetPublicRead.value = meta.objectStorageSetPublicRead;
- objectStorageS3ForcePathStyle.value = meta.objectStorageS3ForcePathStyle;
-}
+const useObjectStorage = ref(meta.useObjectStorage);
+const objectStorageBaseUrl = ref(meta.objectStorageBaseUrl);
+const objectStorageBucket = ref(meta.objectStorageBucket);
+const objectStoragePrefix = ref(meta.objectStoragePrefix);
+const objectStorageEndpoint = ref(meta.objectStorageEndpoint);
+const objectStorageRegion = ref(meta.objectStorageRegion);
+const objectStoragePort = ref(meta.objectStoragePort);
+const objectStorageAccessKey = ref(meta.objectStorageAccessKey);
+const objectStorageSecretKey = ref(meta.objectStorageSecretKey);
+const objectStorageUseSSL = ref(meta.objectStorageUseSSL);
+const objectStorageUseProxy = ref(meta.objectStorageUseProxy);
+const objectStorageSetPublicRead = ref(meta.objectStorageSetPublicRead);
+const objectStorageS3ForcePathStyle = ref(meta.objectStorageS3ForcePathStyle);
function save() {
os.apiWithDialog('admin/update-meta', {
diff --git a/packages/frontend/src/pages/admin/performance.vue b/packages/frontend/src/pages/admin/performance.vue
index ff3a5b9d7f..e3021778e7 100644
--- a/packages/frontend/src/pages/admin/performance.vue
+++ b/packages/frontend/src/pages/admin/performance.vue
@@ -6,131 +6,163 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<PageWithHeader :actions="headerActions" :tabs="headerTabs">
<div class="_spacer" style="--MI_SPACER-w: 700px; --MI_SPACER-min: 16px; --MI_SPACER-max: 32px;">
- <div class="_gaps">
- <div class="_panel" style="padding: 16px;">
- <MkSwitch v-model="enableServerMachineStats" @change="onChange_enableServerMachineStats">
- <template #label>{{ i18n.ts.enableServerMachineStats }}</template>
- <template #caption>{{ i18n.ts.turnOffToImprovePerformance }}</template>
- </MkSwitch>
- </div>
-
- <div class="_panel" style="padding: 16px;">
- <MkSwitch v-model="enableIdenticonGeneration" @change="onChange_enableIdenticonGeneration">
- <template #label>{{ i18n.ts.enableIdenticonGeneration }}</template>
- <template #caption>{{ i18n.ts.turnOffToImprovePerformance }}</template>
- </MkSwitch>
- </div>
+ <SearchMarker path="/admin/performance" :label="i18n.ts.performance" :keywords="['performance']" icon="ti ti-bolt">
+ <div class="_gaps">
+ <SearchMarker>
+ <div class="_panel" style="padding: 16px;">
+ <MkSwitch v-model="enableServerMachineStats" @change="onChange_enableServerMachineStats">
+ <template #label><SearchLabel>{{ i18n.ts.enableServerMachineStats }}</SearchLabel></template>
+ <template #caption>{{ i18n.ts.turnOffToImprovePerformance }}</template>
+ </MkSwitch>
+ </div>
+ </SearchMarker>
- <div class="_panel" style="padding: 16px;">
- <MkSwitch v-model="enableChartsForRemoteUser" @change="onChange_enableChartsForRemoteUser">
- <template #label>{{ i18n.ts.enableChartsForRemoteUser }}</template>
- <template #caption>{{ i18n.ts.turnOffToImprovePerformance }}</template>
- </MkSwitch>
- </div>
+ <SearchMarker>
+ <div class="_panel" style="padding: 16px;">
+ <MkSwitch v-model="enableIdenticonGeneration" @change="onChange_enableIdenticonGeneration">
+ <template #label><SearchLabel>{{ i18n.ts.enableIdenticonGeneration }}</SearchLabel></template>
+ <template #caption>{{ i18n.ts.turnOffToImprovePerformance }}</template>
+ </MkSwitch>
+ </div>
+ </SearchMarker>
- <div class="_panel" style="padding: 16px;">
- <MkSwitch v-model="enableStatsForFederatedInstances" @change="onChange_enableStatsForFederatedInstances">
- <template #label>{{ i18n.ts.enableStatsForFederatedInstances }}</template>
- <template #caption>{{ i18n.ts.turnOffToImprovePerformance }}</template>
- </MkSwitch>
- </div>
+ <SearchMarker>
+ <div class="_panel" style="padding: 16px;">
+ <MkSwitch v-model="enableChartsForRemoteUser" @change="onChange_enableChartsForRemoteUser">
+ <template #label><SearchLabel>{{ i18n.ts.enableChartsForRemoteUser }}</SearchLabel></template>
+ <template #caption>{{ i18n.ts.turnOffToImprovePerformance }}</template>
+ </MkSwitch>
+ </div>
+ </SearchMarker>
- <div class="_panel" style="padding: 16px;">
- <MkSwitch v-model="enableChartsForFederatedInstances" @change="onChange_enableChartsForFederatedInstances">
- <template #label>{{ i18n.ts.enableChartsForFederatedInstances }}</template>
- <template #caption>{{ i18n.ts.turnOffToImprovePerformance }}</template>
- </MkSwitch>
- </div>
+ <SearchMarker>
+ <div class="_panel" style="padding: 16px;">
+ <MkSwitch v-model="enableStatsForFederatedInstances" @change="onChange_enableStatsForFederatedInstances">
+ <template #label><SearchLabel>{{ i18n.ts.enableStatsForFederatedInstances }}</SearchLabel></template>
+ <template #caption>{{ i18n.ts.turnOffToImprovePerformance }}</template>
+ </MkSwitch>
+ </div>
+ </SearchMarker>
- <MkFolder :defaultOpen="true">
- <template #icon><i class="ti ti-bolt"></i></template>
- <template #label>Misskey® Fan-out Timeline Technology™ (FTT)</template>
- <template v-if="fttForm.savedState.enableFanoutTimeline" #suffix>Enabled</template>
- <template v-else #suffix>Disabled</template>
- <template v-if="fttForm.modified.value" #footer>
- <MkFormFooter :form="fttForm"/>
- </template>
+ <SearchMarker>
+ <div class="_panel" style="padding: 16px;">
+ <MkSwitch v-model="enableChartsForFederatedInstances" @change="onChange_enableChartsForFederatedInstances">
+ <template #label><SearchLabel>{{ i18n.ts.enableChartsForFederatedInstances }}</SearchLabel></template>
+ <template #caption>{{ i18n.ts.turnOffToImprovePerformance }}</template>
+ </MkSwitch>
+ </div>
+ </SearchMarker>
- <div class="_gaps">
- <MkSwitch v-model="fttForm.state.enableFanoutTimeline">
- <template #label>{{ i18n.ts.enable }}<span v-if="fttForm.modifiedStates.enableFanoutTimeline" class="_modified">{{ i18n.ts.modified }}</span></template>
- <template #caption>
- <div>{{ i18n.ts._serverSettings.fanoutTimelineDescription }}</div>
- <div><MkLink target="_blank" url="https://misskey-hub.net/docs/for-admin/features/ftt/">{{ i18n.ts.details }}</MkLink></div>
+ <SearchMarker>
+ <MkFolder :defaultOpen="true">
+ <template #icon><SearchIcon><i class="ti ti-bolt"></i></SearchIcon></template>
+ <template #label><SearchLabel>Misskey® Fan-out Timeline Technology™ (FTT)</SearchLabel></template>
+ <template v-if="fttForm.savedState.enableFanoutTimeline" #suffix>Enabled</template>
+ <template v-else #suffix>Disabled</template>
+ <template v-if="fttForm.modified.value" #footer>
+ <MkFormFooter :form="fttForm"/>
</template>
- </MkSwitch>
- <template v-if="fttForm.state.enableFanoutTimeline">
- <MkSwitch v-model="fttForm.state.enableFanoutTimelineDbFallback">
- <template #label>{{ i18n.ts._serverSettings.fanoutTimelineDbFallback }}<span v-if="fttForm.modifiedStates.enableFanoutTimelineDbFallback" class="_modified">{{ i18n.ts.modified }}</span></template>
- <template #caption>{{ i18n.ts._serverSettings.fanoutTimelineDbFallbackDescription }}</template>
- </MkSwitch>
+ <div class="_gaps">
+ <SearchMarker>
+ <MkSwitch v-model="fttForm.state.enableFanoutTimeline">
+ <template #label><SearchLabel>{{ i18n.ts.enable }}</SearchLabel><span v-if="fttForm.modifiedStates.enableFanoutTimeline" class="_modified">{{ i18n.ts.modified }}</span></template>
+ <template #caption>
+ <div><SearchText>{{ i18n.ts._serverSettings.fanoutTimelineDescription }}</SearchText></div>
+ <div><MkLink target="_blank" url="https://misskey-hub.net/docs/for-admin/features/ftt/">{{ i18n.ts.details }}</MkLink></div>
+ </template>
+ </MkSwitch>
+ </SearchMarker>
- <MkInput v-model="fttForm.state.perLocalUserUserTimelineCacheMax" type="number">
- <template #label>perLocalUserUserTimelineCacheMax<span v-if="fttForm.modifiedStates.perLocalUserUserTimelineCacheMax" class="_modified">{{ i18n.ts.modified }}</span></template>
- </MkInput>
+ <template v-if="fttForm.state.enableFanoutTimeline">
+ <SearchMarker :keywords="['db', 'database', 'fallback']">
+ <MkSwitch v-model="fttForm.state.enableFanoutTimelineDbFallback">
+ <template #label><SearchLabel>{{ i18n.ts._serverSettings.fanoutTimelineDbFallback }}</SearchLabel><span v-if="fttForm.modifiedStates.enableFanoutTimelineDbFallback" class="_modified">{{ i18n.ts.modified }}</span></template>
+ <template #caption><SearchText>{{ i18n.ts._serverSettings.fanoutTimelineDbFallbackDescription }}</SearchText></template>
+ </MkSwitch>
+ </SearchMarker>
- <MkInput v-model="fttForm.state.perRemoteUserUserTimelineCacheMax" type="number">
- <template #label>perRemoteUserUserTimelineCacheMax<span v-if="fttForm.modifiedStates.perRemoteUserUserTimelineCacheMax" class="_modified">{{ i18n.ts.modified }}</span></template>
- </MkInput>
+ <SearchMarker>
+ <MkInput v-model="fttForm.state.perLocalUserUserTimelineCacheMax" type="number">
+ <template #label><SearchLabel>perLocalUserUserTimelineCacheMax</SearchLabel><span v-if="fttForm.modifiedStates.perLocalUserUserTimelineCacheMax" class="_modified">{{ i18n.ts.modified }}</span></template>
+ </MkInput>
+ </SearchMarker>
- <MkInput v-model="fttForm.state.perUserHomeTimelineCacheMax" type="number">
- <template #label>perUserHomeTimelineCacheMax<span v-if="fttForm.modifiedStates.perUserHomeTimelineCacheMax" class="_modified">{{ i18n.ts.modified }}</span></template>
- </MkInput>
+ <SearchMarker>
+ <MkInput v-model="fttForm.state.perRemoteUserUserTimelineCacheMax" type="number">
+ <template #label><SearchLabel>perRemoteUserUserTimelineCacheMax</SearchLabel><span v-if="fttForm.modifiedStates.perRemoteUserUserTimelineCacheMax" class="_modified">{{ i18n.ts.modified }}</span></template>
+ </MkInput>
+ </SearchMarker>
- <MkInput v-model="fttForm.state.perUserListTimelineCacheMax" type="number">
- <template #label>perUserListTimelineCacheMax<span v-if="fttForm.modifiedStates.perUserListTimelineCacheMax" class="_modified">{{ i18n.ts.modified }}</span></template>
- </MkInput>
- </template>
- </div>
- </MkFolder>
+ <SearchMarker>
+ <MkInput v-model="fttForm.state.perUserHomeTimelineCacheMax" type="number">
+ <template #label><SearchLabel>perUserHomeTimelineCacheMax</SearchLabel><span v-if="fttForm.modifiedStates.perUserHomeTimelineCacheMax" class="_modified">{{ i18n.ts.modified }}</span></template>
+ </MkInput>
+ </SearchMarker>
- <MkFolder :defaultOpen="true">
- <template #icon><i class="ti ti-bolt"></i></template>
- <template #label>Misskey® Reactions Boost Technology™ (RBT)<span class="_beta">{{ i18n.ts.beta }}</span></template>
- <template v-if="rbtForm.savedState.enableReactionsBuffering" #suffix>Enabled</template>
- <template v-else #suffix>Disabled</template>
- <template v-if="rbtForm.modified.value" #footer>
- <MkFormFooter :form="rbtForm"/>
- </template>
+ <SearchMarker>
+ <MkInput v-model="fttForm.state.perUserListTimelineCacheMax" type="number">
+ <template #label><SearchLabel>perUserListTimelineCacheMax</SearchLabel><span v-if="fttForm.modifiedStates.perUserListTimelineCacheMax" class="_modified">{{ i18n.ts.modified }}</span></template>
+ </MkInput>
+ </SearchMarker>
+ </template>
+ </div>
+ </MkFolder>
+ </SearchMarker>
- <div class="_gaps_m">
- <MkSwitch v-model="rbtForm.state.enableReactionsBuffering">
- <template #label>{{ i18n.ts.enable }}<span v-if="rbtForm.modifiedStates.enableReactionsBuffering" class="_modified">{{ i18n.ts.modified }}</span></template>
- <template #caption>{{ i18n.ts._serverSettings.reactionsBufferingDescription }}</template>
- </MkSwitch>
- </div>
- </MkFolder>
+ <SearchMarker>
+ <MkFolder :defaultOpen="true">
+ <template #icon><SearchIcon><i class="ti ti-bolt"></i></SearchIcon></template>
+ <template #label><SearchLabel>Misskey® Reactions Boost Technology™ (RBT)</SearchLabel><span class="_beta">{{ i18n.ts.beta }}</span></template>
+ <template v-if="rbtForm.savedState.enableReactionsBuffering" #suffix>Enabled</template>
+ <template v-else #suffix>Disabled</template>
+ <template v-if="rbtForm.modified.value" #footer>
+ <MkFormFooter :form="rbtForm"/>
+ </template>
+
+ <div class="_gaps_m">
+ <SearchMarker>
+ <MkSwitch v-model="rbtForm.state.enableReactionsBuffering">
+ <template #label><SearchLabel>{{ i18n.ts.enable }}</SearchLabel><span v-if="rbtForm.modifiedStates.enableReactionsBuffering" class="_modified">{{ i18n.ts.modified }}</span></template>
+ <template #caption><SearchText>{{ i18n.ts._serverSettings.reactionsBufferingDescription }}</SearchText></template>
+ </MkSwitch>
+ </SearchMarker>
+ </div>
+ </MkFolder>
+ </SearchMarker>
- <MkFolder :defaultOpen="true">
- <template #icon><i class="ti ti-recycle"></i></template>
- <template #label>Remote Notes Cleaning (仮)</template>
- <template v-if="remoteNotesCleaningForm.savedState.enableRemoteNotesCleaning" #suffix>Enabled</template>
- <template v-else #suffix>Disabled</template>
- <template v-if="remoteNotesCleaningForm.modified.value" #footer>
- <MkFormFooter :form="remoteNotesCleaningForm"/>
- </template>
+ <SearchMarker>
+ <MkFolder :defaultOpen="true">
+ <template #icon><SearchIcon><i class="ti ti-recycle"></i></SearchIcon></template>
+ <template #label><SearchLabel>Remote Notes Cleaning (仮)</SearchLabel></template>
+ <template v-if="remoteNotesCleaningForm.savedState.enableRemoteNotesCleaning" #suffix>Enabled</template>
+ <template v-else #suffix>Disabled</template>
+ <template v-if="remoteNotesCleaningForm.modified.value" #footer>
+ <MkFormFooter :form="remoteNotesCleaningForm"/>
+ </template>
- <div class="_gaps_m">
- <MkSwitch v-model="remoteNotesCleaningForm.state.enableRemoteNotesCleaning">
- <template #label>{{ i18n.ts.enable }}<span v-if="remoteNotesCleaningForm.modifiedStates.enableRemoteNotesCleaning" class="_modified">{{ i18n.ts.modified }}</span></template>
- <template #caption>{{ i18n.ts._serverSettings.remoteNotesCleaning_description }}</template>
- </MkSwitch>
+ <div class="_gaps_m">
+ <MkSwitch v-model="remoteNotesCleaningForm.state.enableRemoteNotesCleaning">
+ <template #label><SearchLabel>{{ i18n.ts.enable }}</SearchLabel><span v-if="remoteNotesCleaningForm.modifiedStates.enableRemoteNotesCleaning" class="_modified">{{ i18n.ts.modified }}</span></template>
+ <template #caption><SearchText>{{ i18n.ts._serverSettings.remoteNotesCleaning_description }}</SearchText></template>
+ </MkSwitch>
- <template v-if="remoteNotesCleaningForm.state.enableRemoteNotesCleaning">
- <MkInput v-model="remoteNotesCleaningForm.state.remoteNotesCleaningExpiryDaysForEachNotes" type="number">
- <template #label>{{ i18n.ts._serverSettings.remoteNotesCleaningExpiryDaysForEachNotes }} ({{ i18n.ts.inDays }})<span v-if="remoteNotesCleaningForm.modifiedStates.remoteNotesCleaningExpiryDaysForEachNotes" class="_modified">{{ i18n.ts.modified }}</span></template>
- <template #suffix>{{ i18n.ts._time.day }}</template>
- </MkInput>
+ <template v-if="remoteNotesCleaningForm.state.enableRemoteNotesCleaning">
+ <MkInput v-model="remoteNotesCleaningForm.state.remoteNotesCleaningExpiryDaysForEachNotes" type="number">
+ <template #label><SearchLabel>{{ i18n.ts._serverSettings.remoteNotesCleaningExpiryDaysForEachNotes }}</SearchLabel> ({{ i18n.ts.inDays }})<span v-if="remoteNotesCleaningForm.modifiedStates.remoteNotesCleaningExpiryDaysForEachNotes" class="_modified">{{ i18n.ts.modified }}</span></template>
+ <template #suffix>{{ i18n.ts._time.day }}</template>
+ </MkInput>
- <MkInput v-model="remoteNotesCleaningForm.state.remoteNotesCleaningMaxProcessingDurationInMinutes" type="number">
- <template #label>{{ i18n.ts._serverSettings.remoteNotesCleaningMaxProcessingDuration }} ({{ i18n.ts.inMinutes }})<span v-if="remoteNotesCleaningForm.modifiedStates.remoteNotesCleaningMaxProcessingDurationInMinutes" class="_modified">{{ i18n.ts.modified }}</span></template>
- <template #suffix>{{ i18n.ts._time.minute }}</template>
- </MkInput>
- </template>
- </div>
- </MkFolder>
- </div>
+ <MkInput v-model="remoteNotesCleaningForm.state.remoteNotesCleaningMaxProcessingDurationInMinutes" type="number">
+ <template #label><SearchLabel>{{ i18n.ts._serverSettings.remoteNotesCleaningMaxProcessingDuration }}</SearchLabel> ({{ i18n.ts.inMinutes }})<span v-if="remoteNotesCleaningForm.modifiedStates.remoteNotesCleaningMaxProcessingDurationInMinutes" class="_modified">{{ i18n.ts.modified }}</span></template>
+ <template #suffix>{{ i18n.ts._time.minute }}</template>
+ </MkInput>
+ </template>
+ </div>
+ </MkFolder>
+ </SearchMarker>
+ </div>
+ </SearchMarker>
</div>
</PageWithHeader>
</template>
@@ -243,7 +275,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
definePage(() => ({
- title: i18n.ts.other,
- icon: 'ti ti-adjustments',
+ title: i18n.ts.performance,
+ icon: 'ti ti-bolt',
}));
</script>
diff --git a/packages/frontend/src/pages/admin/relays.vue b/packages/frontend/src/pages/admin/relays.vue
index aabf64342e..fca9776c0a 100644
--- a/packages/frontend/src/pages/admin/relays.vue
+++ b/packages/frontend/src/pages/admin/relays.vue
@@ -6,18 +6,20 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<PageWithHeader :actions="headerActions" :tabs="headerTabs">
<div class="_spacer" style="--MI_SPACER-w: 800px;">
- <div class="_gaps">
- <div v-for="relay in relays" :key="relay.inbox" class="relaycxt _panel" style="padding: 16px;">
- <div>{{ relay.inbox }}</div>
- <div style="margin: 8px 0;">
- <i v-if="relay.status === 'accepted'" class="ti ti-check" :class="$style.icon" style="color: var(--MI_THEME-success);"></i>
- <i v-else-if="relay.status === 'rejected'" class="ti ti-ban" :class="$style.icon" style="color: var(--MI_THEME-error);"></i>
- <i v-else class="ti ti-clock" :class="$style.icon"></i>
- <span>{{ i18n.ts._relayStatus[relay.status] }}</span>
+ <SearchMarker path="/admin/relays" :label="i18n.ts.relays" :keywords="['relays']" icon="ti ti-planet">
+ <div class="_gaps">
+ <div v-for="relay in relays" :key="relay.inbox" class="relaycxt _panel" style="padding: 16px;">
+ <div>{{ relay.inbox }}</div>
+ <div style="margin: 8px 0;">
+ <i v-if="relay.status === 'accepted'" class="ti ti-check" :class="$style.icon" style="color: var(--MI_THEME-success);"></i>
+ <i v-else-if="relay.status === 'rejected'" class="ti ti-ban" :class="$style.icon" style="color: var(--MI_THEME-error);"></i>
+ <i v-else class="ti ti-clock" :class="$style.icon"></i>
+ <span>{{ i18n.ts._relayStatus[relay.status] }}</span>
+ </div>
+ <MkButton class="button" inline danger @click="remove(relay.inbox)"><i class="ti ti-trash"></i> {{ i18n.ts.remove }}</MkButton>
</div>
- <MkButton class="button" inline danger @click="remove(relay.inbox)"><i class="ti ti-trash"></i> {{ i18n.ts.remove }}</MkButton>
</div>
- </div>
+ </SearchMarker>
</div>
</PageWithHeader>
</template>
diff --git a/packages/frontend/src/pages/admin/security.vue b/packages/frontend/src/pages/admin/security.vue
index 9e907a4469..27e35c7e69 100644
--- a/packages/frontend/src/pages/admin/security.vue
+++ b/packages/frontend/src/pages/admin/security.vue
@@ -6,115 +6,153 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<PageWithHeader :actions="headerActions" :tabs="headerTabs">
<div class="_spacer" style="--MI_SPACER-w: 700px; --MI_SPACER-min: 16px; --MI_SPACER-max: 32px;">
- <div class="_gaps_m">
- <XBotProtection/>
+ <SearchMarker path="/admin/security" :label="i18n.ts.security" :keywords="['security']" icon="ti ti-lock" :inlining="['botProtection']">
+ <div class="_gaps_m">
+ <XBotProtection/>
- <MkFolder>
- <template #icon><i class="ti ti-eye-off"></i></template>
- <template #label>{{ i18n.ts.sensitiveMediaDetection }}</template>
- <template v-if="sensitiveMediaDetectionForm.savedState.sensitiveMediaDetection === 'all'" #suffix>{{ i18n.ts.all }}</template>
- <template v-else-if="sensitiveMediaDetectionForm.savedState.sensitiveMediaDetection === 'local'" #suffix>{{ i18n.ts.localOnly }}</template>
- <template v-else-if="sensitiveMediaDetectionForm.savedState.sensitiveMediaDetection === 'remote'" #suffix>{{ i18n.ts.remoteOnly }}</template>
- <template v-else #suffix>{{ i18n.ts.none }}</template>
- <template v-if="sensitiveMediaDetectionForm.modified.value" #footer>
- <MkFormFooter :form="sensitiveMediaDetectionForm"/>
- </template>
+ <SearchMarker v-slot="slotProps" :keywords="['sensitive', 'media', 'detection']">
+ <MkFolder :defaultOpen="slotProps.isParentOfTarget">
+ <template #icon><SearchIcon><i class="ti ti-eye-off"></i></SearchIcon></template>
+ <template #label><SearchLabel>{{ i18n.ts.sensitiveMediaDetection }}</SearchLabel></template>
+ <template v-if="sensitiveMediaDetectionForm.savedState.sensitiveMediaDetection === 'all'" #suffix>{{ i18n.ts.all }}</template>
+ <template v-else-if="sensitiveMediaDetectionForm.savedState.sensitiveMediaDetection === 'local'" #suffix>{{ i18n.ts.localOnly }}</template>
+ <template v-else-if="sensitiveMediaDetectionForm.savedState.sensitiveMediaDetection === 'remote'" #suffix>{{ i18n.ts.remoteOnly }}</template>
+ <template v-else #suffix>{{ i18n.ts.none }}</template>
+ <template v-if="sensitiveMediaDetectionForm.modified.value" #footer>
+ <MkFormFooter :form="sensitiveMediaDetectionForm"/>
+ </template>
- <div class="_gaps_m">
- <span>{{ i18n.ts._sensitiveMediaDetection.description }}</span>
+ <div class="_gaps_m">
+ <div><SearchText>{{ i18n.ts._sensitiveMediaDetection.description }}</SearchText></div>
- <MkRadios v-model="sensitiveMediaDetectionForm.state.sensitiveMediaDetection">
- <option value="none">{{ i18n.ts.none }}</option>
- <option value="all">{{ i18n.ts.all }}</option>
- <option value="local">{{ i18n.ts.localOnly }}</option>
- <option value="remote">{{ i18n.ts.remoteOnly }}</option>
- </MkRadios>
+ <MkRadios v-model="sensitiveMediaDetectionForm.state.sensitiveMediaDetection">
+ <option value="none">{{ i18n.ts.none }}</option>
+ <option value="all">{{ i18n.ts.all }}</option>
+ <option value="local">{{ i18n.ts.localOnly }}</option>
+ <option value="remote">{{ i18n.ts.remoteOnly }}</option>
+ </MkRadios>
- <MkRange v-model="sensitiveMediaDetectionForm.state.sensitiveMediaDetectionSensitivity" :min="0" :max="4" :step="1" :textConverter="(v) => `${v + 1}`">
- <template #label>{{ i18n.ts._sensitiveMediaDetection.sensitivity }}</template>
- <template #caption>{{ i18n.ts._sensitiveMediaDetection.sensitivityDescription }}</template>
- </MkRange>
+ <SearchMarker :keywords="['sensitivity']">
+ <MkRange v-model="sensitiveMediaDetectionForm.state.sensitiveMediaDetectionSensitivity" :min="0" :max="4" :step="1" :textConverter="(v) => `${v + 1}`">
+ <template #label><SearchLabel>{{ i18n.ts._sensitiveMediaDetection.sensitivity }}</SearchLabel></template>
+ <template #caption><SearchText>{{ i18n.ts._sensitiveMediaDetection.sensitivityDescription }}</SearchText></template>
+ </MkRange>
+ </SearchMarker>
- <MkSwitch v-model="sensitiveMediaDetectionForm.state.enableSensitiveMediaDetectionForVideos">
- <template #label>{{ i18n.ts._sensitiveMediaDetection.analyzeVideos }}<span class="_beta">{{ i18n.ts.beta }}</span></template>
- <template #caption>{{ i18n.ts._sensitiveMediaDetection.analyzeVideosDescription }}</template>
- </MkSwitch>
+ <SearchMarker :keywords="['video', 'analyze']">
+ <MkSwitch v-model="sensitiveMediaDetectionForm.state.enableSensitiveMediaDetectionForVideos">
+ <template #label><SearchLabel>{{ i18n.ts._sensitiveMediaDetection.analyzeVideos }}</SearchLabel><span class="_beta">{{ i18n.ts.beta }}</span></template>
+ <template #caption><SearchText>{{ i18n.ts._sensitiveMediaDetection.analyzeVideosDescription }}</SearchText></template>
+ </MkSwitch>
+ </SearchMarker>
- <MkSwitch v-model="sensitiveMediaDetectionForm.state.setSensitiveFlagAutomatically">
- <template #label>{{ i18n.ts._sensitiveMediaDetection.setSensitiveFlagAutomatically }} ({{ i18n.ts.notRecommended }})</template>
- <template #caption>{{ i18n.ts._sensitiveMediaDetection.setSensitiveFlagAutomaticallyDescription }}</template>
- </MkSwitch>
+ <SearchMarker :keywords="['flag', 'automatically']">
+ <MkSwitch v-model="sensitiveMediaDetectionForm.state.setSensitiveFlagAutomatically">
+ <template #label><SearchLabel>{{ i18n.ts._sensitiveMediaDetection.setSensitiveFlagAutomatically }}</SearchLabel> ({{ i18n.ts.notRecommended }})</template>
+ <template #caption><SearchText>{{ i18n.ts._sensitiveMediaDetection.setSensitiveFlagAutomaticallyDescription }}</SearchText></template>
+ </MkSwitch>
+ </SearchMarker>
- <!-- 現状 false positive が多すぎて実用に耐えない
+ <!-- 現状 false positive が多すぎて実用に耐えない
<MkSwitch v-model="disallowUploadWhenPredictedAsPorn">
<template #label>{{ i18n.ts._sensitiveMediaDetection.disallowUploadWhenPredictedAsPorn }}</template>
</MkSwitch>
-->
- </div>
- </MkFolder>
+ </div>
+ </MkFolder>
+ </SearchMarker>
- <MkFolder>
- <template #label>Active Email Validation</template>
- <template v-if="emailValidationForm.savedState.enableActiveEmailValidation" #suffix>Enabled</template>
- <template v-else #suffix>Disabled</template>
- <template v-if="emailValidationForm.modified.value" #footer>
- <MkFormFooter :form="emailValidationForm"/>
- </template>
+ <SearchMarker v-slot="slotProps" :keywords="['email', 'validation']">
+ <MkFolder :defaultOpen="slotProps.isParentOfTarget">
+ <template #label><SearchLabel>Active Email Validation</SearchLabel></template>
+ <template v-if="emailValidationForm.savedState.enableActiveEmailValidation" #suffix>Enabled</template>
+ <template v-else #suffix>Disabled</template>
+ <template v-if="emailValidationForm.modified.value" #footer>
+ <MkFormFooter :form="emailValidationForm"/>
+ </template>
- <div class="_gaps_m">
- <span>{{ i18n.ts.activeEmailValidationDescription }}</span>
- <MkSwitch v-model="emailValidationForm.state.enableActiveEmailValidation">
- <template #label>Enable</template>
- </MkSwitch>
- <MkSwitch v-model="emailValidationForm.state.enableVerifymailApi">
- <template #label>Use Verifymail.io API</template>
- </MkSwitch>
- <MkInput v-model="emailValidationForm.state.verifymailAuthKey">
- <template #prefix><i class="ti ti-key"></i></template>
- <template #label>Verifymail.io API Auth Key</template>
- </MkInput>
- <MkSwitch v-model="emailValidationForm.state.enableTruemailApi">
- <template #label>Use TrueMail API</template>
- </MkSwitch>
- <MkInput v-model="emailValidationForm.state.truemailInstance">
- <template #prefix><i class="ti ti-key"></i></template>
- <template #label>TrueMail API Instance</template>
- </MkInput>
- <MkInput v-model="emailValidationForm.state.truemailAuthKey">
- <template #prefix><i class="ti ti-key"></i></template>
- <template #label>TrueMail API Auth Key</template>
- </MkInput>
- </div>
- </MkFolder>
+ <div class="_gaps_m">
+ <div><SearchText>{{ i18n.ts.activeEmailValidationDescription }}</SearchText></div>
- <MkFolder>
- <template #label>Banned Email Domains</template>
- <template v-if="bannedEmailDomainsForm.modified.value" #footer>
- <MkFormFooter :form="bannedEmailDomainsForm"/>
- </template>
+ <SearchMarker>
+ <MkSwitch v-model="emailValidationForm.state.enableActiveEmailValidation">
+ <template #label><SearchLabel>Enable</SearchLabel></template>
+ </MkSwitch>
+ </SearchMarker>
- <div class="_gaps_m">
- <MkTextarea v-model="bannedEmailDomainsForm.state.bannedEmailDomains">
- <template #label>Banned Email Domains List</template>
- </MkTextarea>
- </div>
- </MkFolder>
+ <SearchMarker>
+ <MkSwitch v-model="emailValidationForm.state.enableVerifymailApi">
+ <template #label><SearchLabel>Use Verifymail.io API</SearchLabel></template>
+ </MkSwitch>
+ </SearchMarker>
- <MkFolder>
- <template #label>Log IP address</template>
- <template v-if="ipLoggingForm.savedState.enableIpLogging" #suffix>Enabled</template>
- <template v-else #suffix>Disabled</template>
- <template v-if="ipLoggingForm.modified.value" #footer>
- <MkFormFooter :form="ipLoggingForm"/>
- </template>
+ <SearchMarker>
+ <MkInput v-model="emailValidationForm.state.verifymailAuthKey">
+ <template #prefix><i class="ti ti-key"></i></template>
+ <template #label><SearchLabel>Verifymail.io API Auth Key</SearchLabel></template>
+ </MkInput>
+ </SearchMarker>
- <div class="_gaps_m">
- <MkSwitch v-model="ipLoggingForm.state.enableIpLogging">
- <template #label>Enable</template>
- </MkSwitch>
- </div>
- </MkFolder>
- </div>
+ <SearchMarker>
+ <MkSwitch v-model="emailValidationForm.state.enableTruemailApi">
+ <template #label><SearchLabel>Use TrueMail API</SearchLabel></template>
+ </MkSwitch>
+ </SearchMarker>
+
+ <SearchMarker>
+ <MkInput v-model="emailValidationForm.state.truemailInstance">
+ <template #prefix><i class="ti ti-key"></i></template>
+ <template #label><SearchLabel>TrueMail API Instance</SearchLabel></template>
+ </MkInput>
+ </SearchMarker>
+
+ <SearchMarker>
+ <MkInput v-model="emailValidationForm.state.truemailAuthKey">
+ <template #prefix><i class="ti ti-key"></i></template>
+ <template #label><SearchLabel>TrueMail API Auth Key</SearchLabel></template>
+ </MkInput>
+ </SearchMarker>
+ </div>
+ </MkFolder>
+ </SearchMarker>
+
+ <SearchMarker v-slot="slotProps" :keywords="['banned', 'email', 'domains', 'blacklist']">
+ <MkFolder :defaultOpen="slotProps.isParentOfTarget">
+ <template #label><SearchLabel>Banned Email Domains</SearchLabel></template>
+ <template v-if="bannedEmailDomainsForm.modified.value" #footer>
+ <MkFormFooter :form="bannedEmailDomainsForm"/>
+ </template>
+
+ <div class="_gaps_m">
+ <SearchMarker>
+ <MkTextarea v-model="bannedEmailDomainsForm.state.bannedEmailDomains">
+ <template #label><SearchLabel>Banned Email Domains List</SearchLabel></template>
+ </MkTextarea>
+ </SearchMarker>
+ </div>
+ </MkFolder>
+ </SearchMarker>
+
+ <SearchMarker v-slot="slotProps" :keywords="['log', 'ipAddress']">
+ <MkFolder :defaultOpen="slotProps.isParentOfTarget">
+ <template #label><SearchLabel>Log IP address</SearchLabel></template>
+ <template v-if="ipLoggingForm.savedState.enableIpLogging" #suffix>Enabled</template>
+ <template v-else #suffix>Disabled</template>
+ <template v-if="ipLoggingForm.modified.value" #footer>
+ <MkFormFooter :form="ipLoggingForm"/>
+ </template>
+
+ <div class="_gaps_m">
+ <SearchMarker>
+ <MkSwitch v-model="ipLoggingForm.state.enableIpLogging">
+ <template #label><SearchLabel>Enable</SearchLabel></template>
+ </MkSwitch>
+ </SearchMarker>
+ </div>
+ </MkFolder>
+ </SearchMarker>
+ </div>
+ </SearchMarker>
</div>
</PageWithHeader>
</template>
diff --git a/packages/frontend/src/pages/admin/server-rules.vue b/packages/frontend/src/pages/admin/server-rules.vue
index 276a7590c4..d26f02b41c 100644
--- a/packages/frontend/src/pages/admin/server-rules.vue
+++ b/packages/frontend/src/pages/admin/server-rules.vue
@@ -4,10 +4,14 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<PageWithHeader :tabs="headerTabs">
- <div class="_spacer" style="--MI_SPACER-w: 700px; --MI_SPACER-min: 16px; --MI_SPACER-max: 32px;">
+<SearchMarker markerId="serverRules" :keywords="['rules']">
+ <MkFolder>
+ <template #icon><SearchIcon><i class="ti ti-checkbox"></i></SearchIcon></template>
+ <template #label><SearchLabel>{{ i18n.ts.serverRules }}</SearchLabel></template>
+
<div class="_gaps_m">
- <div>{{ i18n.ts._serverRules.description }}</div>
+ <div><SearchText>{{ i18n.ts._serverRules.description }}</SearchText></div>
+
<Sortable
v-model="serverRules"
class="_gaps_m"
@@ -33,8 +37,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkButton primary rounded @click="save"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton>
</div>
</div>
- </div>
-</PageWithHeader>
+ </MkFolder>
+</SearchMarker>
</template>
<script lang="ts" setup>
@@ -42,9 +46,9 @@ import { defineAsyncComponent, ref, computed } from 'vue';
import * as os from '@/os.js';
import { fetchInstance, instance } from '@/instance.js';
import { i18n } from '@/i18n.js';
-import { definePage } from '@/page.js';
import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/MkInput.vue';
+import MkFolder from '@/components/MkFolder.vue';
const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default));
@@ -60,13 +64,6 @@ const save = async () => {
const remove = (index: number): void => {
serverRules.value.splice(index, 1);
};
-
-const headerTabs = computed(() => []);
-
-definePage(() => ({
- title: i18n.ts.serverRules,
- icon: 'ti ti-checkbox',
-}));
</script>
<style lang="scss" module>
diff --git a/packages/frontend/src/pages/admin/settings.vue b/packages/frontend/src/pages/admin/settings.vue
index d079b4cb0c..541ee7c0cd 100644
--- a/packages/frontend/src/pages/admin/settings.vue
+++ b/packages/frontend/src/pages/admin/settings.vue
@@ -6,292 +6,369 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<PageWithHeader :tabs="headerTabs">
<div class="_spacer" style="--MI_SPACER-w: 700px; --MI_SPACER-min: 16px; --MI_SPACER-max: 32px;">
- <div class="_gaps_m">
- <MkFolder :defaultOpen="true">
- <template #icon><i class="ti ti-info-circle"></i></template>
- <template #label>{{ i18n.ts.info }}</template>
- <template v-if="infoForm.modified.value" #footer>
- <MkFormFooter :form="infoForm"/>
- </template>
+ <SearchMarker path="/admin/settings" :label="i18n.ts.general" :keywords="['general', 'settings']" icon="ti ti-settings">
+ <div class="_gaps_m">
+ <SearchMarker v-slot="slotProps" :keywords="['information', 'meta']">
+ <MkFolder :defaultOpen="true">
+ <template #icon><SearchIcon><i class="ti ti-info-circle"></i></SearchIcon></template>
+ <template #label><SearchLabel>{{ i18n.ts.info }}</SearchLabel></template>
+ <template v-if="infoForm.modified.value" #footer>
+ <MkFormFooter :form="infoForm"/>
+ </template>
- <div class="_gaps">
- <MkInput v-model="infoForm.state.name">
- <template #label>{{ i18n.ts.instanceName }}<span v-if="infoForm.modifiedStates.name" class="_modified">{{ i18n.ts.modified }}</span></template>
- </MkInput>
+ <div class="_gaps">
+ <SearchMarker :keywords="['name']">
+ <MkInput v-model="infoForm.state.name">
+ <template #label><SearchLabel>{{ i18n.ts.instanceName }}</SearchLabel><span v-if="infoForm.modifiedStates.name" class="_modified">{{ i18n.ts.modified }}</span></template>
+ </MkInput>
+ </SearchMarker>
- <MkInput v-model="infoForm.state.shortName">
- <template #label>{{ i18n.ts._serverSettings.shortName }} ({{ i18n.ts.optional }})<span v-if="infoForm.modifiedStates.shortName" class="_modified">{{ i18n.ts.modified }}</span></template>
- <template #caption>{{ i18n.ts._serverSettings.shortNameDescription }}</template>
- </MkInput>
+ <SearchMarker :keywords="['shortName']">
+ <MkInput v-model="infoForm.state.shortName">
+ <template #label><SearchLabel>{{ i18n.ts._serverSettings.shortName }}</SearchLabel> ({{ i18n.ts.optional }})<span v-if="infoForm.modifiedStates.shortName" class="_modified">{{ i18n.ts.modified }}</span></template>
+ <template #caption><SearchText>{{ i18n.ts._serverSettings.shortNameDescription }}</SearchText></template>
+ </MkInput>
+ </SearchMarker>
- <MkTextarea v-model="infoForm.state.description">
- <template #label>{{ i18n.ts.instanceDescription }}<span v-if="infoForm.modifiedStates.description" class="_modified">{{ i18n.ts.modified }}</span></template>
- </MkTextarea>
+ <SearchMarker :keywords="['description']">
+ <MkTextarea v-model="infoForm.state.description">
+ <template #label><SearchLabel>{{ i18n.ts.instanceDescription }}</SearchLabel><span v-if="infoForm.modifiedStates.description" class="_modified">{{ i18n.ts.modified }}</span></template>
+ </MkTextarea>
+ </SearchMarker>
- <FormSplit :minWidth="300">
- <MkInput v-model="infoForm.state.maintainerName">
- <template #label>{{ i18n.ts.maintainerName }}<span v-if="infoForm.modifiedStates.maintainerName" class="_modified">{{ i18n.ts.modified }}</span></template>
- </MkInput>
+ <FormSplit :minWidth="300">
+ <SearchMarker :keywords="['maintainer', 'name']">
+ <MkInput v-model="infoForm.state.maintainerName">
+ <template #label><SearchLabel>{{ i18n.ts.maintainerName }}</SearchLabel><span v-if="infoForm.modifiedStates.maintainerName" class="_modified">{{ i18n.ts.modified }}</span></template>
+ </MkInput>
+ </SearchMarker>
- <MkInput v-model="infoForm.state.maintainerEmail" type="email">
- <template #label>{{ i18n.ts.maintainerEmail }}<span v-if="infoForm.modifiedStates.maintainerEmail" class="_modified">{{ i18n.ts.modified }}</span></template>
- <template #prefix><i class="ti ti-mail"></i></template>
- </MkInput>
- </FormSplit>
+ <SearchMarker :keywords="['maintainer', 'email', 'contact']">
+ <MkInput v-model="infoForm.state.maintainerEmail" type="email">
+ <template #label><SearchLabel>{{ i18n.ts.maintainerEmail }}</SearchLabel><span v-if="infoForm.modifiedStates.maintainerEmail" class="_modified">{{ i18n.ts.modified }}</span></template>
+ <template #prefix><i class="ti ti-mail"></i></template>
+ </MkInput>
+ </SearchMarker>
+ </FormSplit>
- <MkInput v-model="infoForm.state.tosUrl" type="url">
- <template #label>{{ i18n.ts.tosUrl }}<span v-if="infoForm.modifiedStates.tosUrl" class="_modified">{{ i18n.ts.modified }}</span></template>
- <template #prefix><i class="ti ti-link"></i></template>
- </MkInput>
+ <SearchMarker :keywords="['tos', 'termsOfService']">
+ <MkInput v-model="infoForm.state.tosUrl" type="url">
+ <template #label><SearchLabel>{{ i18n.ts.tosUrl }}</SearchLabel><span v-if="infoForm.modifiedStates.tosUrl" class="_modified">{{ i18n.ts.modified }}</span></template>
+ <template #prefix><i class="ti ti-link"></i></template>
+ </MkInput>
+ </SearchMarker>
- <MkInput v-model="infoForm.state.privacyPolicyUrl" type="url">
- <template #label>{{ i18n.ts.privacyPolicyUrl }}<span v-if="infoForm.modifiedStates.privacyPolicyUrl" class="_modified">{{ i18n.ts.modified }}</span></template>
- <template #prefix><i class="ti ti-link"></i></template>
- </MkInput>
+ <SearchMarker :keywords="['privacyPolicy']">
+ <MkInput v-model="infoForm.state.privacyPolicyUrl" type="url">
+ <template #label><SearchLabel>{{ i18n.ts.privacyPolicyUrl }}</SearchLabel><span v-if="infoForm.modifiedStates.privacyPolicyUrl" class="_modified">{{ i18n.ts.modified }}</span></template>
+ <template #prefix><i class="ti ti-link"></i></template>
+ </MkInput>
+ </SearchMarker>
- <MkInput v-model="infoForm.state.inquiryUrl" type="url">
- <template #label>{{ i18n.ts._serverSettings.inquiryUrl }}<span v-if="infoForm.modifiedStates.inquiryUrl" class="_modified">{{ i18n.ts.modified }}</span></template>
- <template #caption>{{ i18n.ts._serverSettings.inquiryUrlDescription }}</template>
- <template #prefix><i class="ti ti-link"></i></template>
- </MkInput>
+ <SearchMarker :keywords="['inquiry', 'contact']">
+ <MkInput v-model="infoForm.state.inquiryUrl" type="url">
+ <template #label><SearchLabel>{{ i18n.ts._serverSettings.inquiryUrl }}</SearchLabel><span v-if="infoForm.modifiedStates.inquiryUrl" class="_modified">{{ i18n.ts.modified }}</span></template>
+ <template #caption><SearchText>{{ i18n.ts._serverSettings.inquiryUrlDescription }}</SearchText></template>
+ <template #prefix><i class="ti ti-link"></i></template>
+ </MkInput>
+ </SearchMarker>
- <MkInput v-model="infoForm.state.repositoryUrl" type="url">
- <template #label>{{ i18n.ts.repositoryUrl }}<span v-if="infoForm.modifiedStates.repositoryUrl" class="_modified">{{ i18n.ts.modified }}</span></template>
- <template #caption>{{ i18n.ts.repositoryUrlDescription }}</template>
- <template #prefix><i class="ti ti-link"></i></template>
- </MkInput>
+ <SearchMarker :keywords="['repository', 'url']">
+ <MkInput v-model="infoForm.state.repositoryUrl" type="url">
+ <template #label><SearchLabel>{{ i18n.ts.repositoryUrl }}</SearchLabel><span v-if="infoForm.modifiedStates.repositoryUrl" class="_modified">{{ i18n.ts.modified }}</span></template>
+ <template #caption><SearchText>{{ i18n.ts.repositoryUrlDescription }}</SearchText></template>
+ <template #prefix><i class="ti ti-link"></i></template>
+ </MkInput>
+ </SearchMarker>
- <MkInfo v-if="!instance.providesTarball && !infoForm.state.repositoryUrl" warn>
- {{ i18n.ts.repositoryUrlOrTarballRequired }}
- </MkInfo>
+ <MkInfo v-if="!instance.providesTarball && !infoForm.state.repositoryUrl" warn>
+ {{ i18n.ts.repositoryUrlOrTarballRequired }}
+ </MkInfo>
- <MkInput v-model="infoForm.state.impressumUrl" type="url">
- <template #label>{{ i18n.ts.impressumUrl }}<span v-if="infoForm.modifiedStates.impressumUrl" class="_modified">{{ i18n.ts.modified }}</span></template>
- <template #caption>{{ i18n.ts.impressumDescription }}</template>
- <template #prefix><i class="ti ti-link"></i></template>
- </MkInput>
- </div>
- </MkFolder>
+ <SearchMarker :keywords="['impressum', 'legalNotice']">
+ <MkInput v-model="infoForm.state.impressumUrl" type="url">
+ <template #label><SearchLabel>{{ i18n.ts.impressumUrl }}</SearchLabel><span v-if="infoForm.modifiedStates.impressumUrl" class="_modified">{{ i18n.ts.modified }}</span></template>
+ <template #caption><SearchText>{{ i18n.ts.impressumDescription }}</SearchText></template>
+ <template #prefix><i class="ti ti-link"></i></template>
+ </MkInput>
+ </SearchMarker>
+ </div>
+ </MkFolder>
+ </SearchMarker>
- <MkFolder>
- <template #icon><i class="ti ti-user-star"></i></template>
- <template #label>{{ i18n.ts.pinnedUsers }}</template>
- <template v-if="pinnedUsersForm.modified.value" #footer>
- <MkFormFooter :form="pinnedUsersForm"/>
- </template>
+ <SearchMarker v-slot="slotProps" :keywords="['pinned', 'users']">
+ <MkFolder :defaultOpen="slotProps.isParentOfTarget">
+ <template #icon><SearchIcon><i class="ti ti-user-star"></i></SearchIcon></template>
+ <template #label><SearchLabel>{{ i18n.ts.pinnedUsers }}</SearchLabel></template>
+ <template v-if="pinnedUsersForm.modified.value" #footer>
+ <MkFormFooter :form="pinnedUsersForm"/>
+ </template>
- <MkTextarea v-model="pinnedUsersForm.state.pinnedUsers">
- <template #label>{{ i18n.ts.pinnedUsers }}<span v-if="pinnedUsersForm.modifiedStates.pinnedUsers" class="_modified">{{ i18n.ts.modified }}</span></template>
- <template #caption>{{ i18n.ts.pinnedUsersDescription }}</template>
- </MkTextarea>
- </MkFolder>
+ <MkTextarea v-model="pinnedUsersForm.state.pinnedUsers">
+ <template #label>{{ i18n.ts.pinnedUsers }}<span v-if="pinnedUsersForm.modifiedStates.pinnedUsers" class="_modified">{{ i18n.ts.modified }}</span></template>
+ <template #caption><SearchText>{{ i18n.ts.pinnedUsersDescription }}</SearchText></template>
+ </MkTextarea>
+ </MkFolder>
+ </SearchMarker>
- <MkFolder>
- <template #icon><i class="ti ti-world-cog"></i></template>
- <template #label>ServiceWorker</template>
- <template v-if="serviceWorkerForm.modified.value" #footer>
- <MkFormFooter :form="serviceWorkerForm"/>
- </template>
+ <SearchMarker v-slot="slotProps" :keywords="['serviceWorker']">
+ <MkFolder :defaultOpen="slotProps.isParentOfTarget">
+ <template #icon><SearchIcon><i class="ti ti-world-cog"></i></SearchIcon></template>
+ <template #label><SearchLabel>ServiceWorker</SearchLabel></template>
+ <template v-if="serviceWorkerForm.modified.value" #footer>
+ <MkFormFooter :form="serviceWorkerForm"/>
+ </template>
- <div class="_gaps">
- <MkSwitch v-model="serviceWorkerForm.state.enableServiceWorker">
- <template #label>{{ i18n.ts.enableServiceworker }}<span v-if="serviceWorkerForm.modifiedStates.enableServiceWorker" class="_modified">{{ i18n.ts.modified }}</span></template>
- <template #caption>{{ i18n.ts.serviceworkerInfo }}</template>
- </MkSwitch>
+ <div class="_gaps">
+ <SearchMarker>
+ <MkSwitch v-model="serviceWorkerForm.state.enableServiceWorker">
+ <template #label><SearchLabel>{{ i18n.ts.enableServiceworker }}</SearchLabel><span v-if="serviceWorkerForm.modifiedStates.enableServiceWorker" class="_modified">{{ i18n.ts.modified }}</span></template>
+ <template #caption><SearchText>{{ i18n.ts.serviceworkerInfo }}</SearchText></template>
+ </MkSwitch>
+ </SearchMarker>
- <template v-if="serviceWorkerForm.state.enableServiceWorker">
- <MkInput v-model="serviceWorkerForm.state.swPublicKey">
- <template #label>Public key<span v-if="serviceWorkerForm.modifiedStates.swPublicKey" class="_modified">{{ i18n.ts.modified }}</span></template>
- <template #prefix><i class="ti ti-key"></i></template>
- </MkInput>
+ <template v-if="serviceWorkerForm.state.enableServiceWorker">
+ <SearchMarker>
+ <MkInput v-model="serviceWorkerForm.state.swPublicKey">
+ <template #label><SearchLabel>Public key</SearchLabel><span v-if="serviceWorkerForm.modifiedStates.swPublicKey" class="_modified">{{ i18n.ts.modified }}</span></template>
+ <template #prefix><i class="ti ti-key"></i></template>
+ </MkInput>
+ </SearchMarker>
- <MkInput v-model="serviceWorkerForm.state.swPrivateKey">
- <template #label>Private key<span v-if="serviceWorkerForm.modifiedStates.swPrivateKey" class="_modified">{{ i18n.ts.modified }}</span></template>
- <template #prefix><i class="ti ti-key"></i></template>
- </MkInput>
- </template>
- </div>
- </MkFolder>
+ <SearchMarker>
+ <MkInput v-model="serviceWorkerForm.state.swPrivateKey">
+ <template #label><SearchLabel>Private key</SearchLabel><span v-if="serviceWorkerForm.modifiedStates.swPrivateKey" class="_modified">{{ i18n.ts.modified }}</span></template>
+ <template #prefix><i class="ti ti-key"></i></template>
+ </MkInput>
+ </SearchMarker>
+ </template>
+ </div>
+ </MkFolder>
+ </SearchMarker>
- <MkFolder>
- <template #icon><i class="ti ti-ad"></i></template>
- <template #label>{{ i18n.ts._ad.adsSettings }}</template>
- <template v-if="adForm.modified.value" #footer>
- <MkFormFooter :form="adForm"/>
- </template>
+ <SearchMarker v-slot="slotProps" :keywords="['ads']">
+ <MkFolder :defaultOpen="slotProps.isParentOfTarget">
+ <template #icon><SearchIcon><i class="ti ti-ad"></i></SearchIcon></template>
+ <template #label><SearchLabel>{{ i18n.ts._ad.adsSettings }}</SearchLabel></template>
+ <template v-if="adForm.modified.value" #footer>
+ <MkFormFooter :form="adForm"/>
+ </template>
- <div class="_gaps">
- <div class="_gaps_s">
- <MkInput v-model="adForm.state.notesPerOneAd" :min="0" type="number">
- <template #label>{{ i18n.ts._ad.notesPerOneAd }}<span v-if="adForm.modifiedStates.notesPerOneAd" class="_modified">{{ i18n.ts.modified }}</span></template>
- <template #caption>{{ i18n.ts._ad.setZeroToDisable }}</template>
- </MkInput>
- <MkInfo v-if="adForm.state.notesPerOneAd > 0 && adForm.state.notesPerOneAd < 20" :warn="true">
- {{ i18n.ts._ad.adsTooClose }}
- </MkInfo>
- </div>
- </div>
- </MkFolder>
+ <div class="_gaps">
+ <div class="_gaps_s">
+ <SearchMarker>
+ <MkInput v-model="adForm.state.notesPerOneAd" :min="0" type="number">
+ <template #label><SearchLabel>{{ i18n.ts._ad.notesPerOneAd }}</SearchLabel><span v-if="adForm.modifiedStates.notesPerOneAd" class="_modified">{{ i18n.ts.modified }}</span></template>
+ <template #caption>{{ i18n.ts._ad.setZeroToDisable }}</template>
+ </MkInput>
+ </SearchMarker>
- <MkFolder>
- <template #icon><i class="ti ti-world-search"></i></template>
- <template #label>{{ i18n.ts._urlPreviewSetting.title }}</template>
- <template v-if="urlPreviewForm.modified.value" #footer>
- <MkFormFooter :form="urlPreviewForm"/>
- </template>
+ <MkInfo v-if="adForm.state.notesPerOneAd > 0 && adForm.state.notesPerOneAd < 20" :warn="true">
+ {{ i18n.ts._ad.adsTooClose }}
+ </MkInfo>
+ </div>
+ </div>
+ </MkFolder>
+ </SearchMarker>
+
+ <SearchMarker v-slot="slotProps" :keywords="['url', 'preview']">
+ <MkFolder :defaultOpen="slotProps.isParentOfTarget">
+ <template #icon><SearchIcon><i class="ti ti-world-search"></i></SearchIcon></template>
+ <template #label><SearchLabel>{{ i18n.ts._urlPreviewSetting.title }}</SearchLabel></template>
+ <template v-if="urlPreviewForm.modified.value" #footer>
+ <MkFormFooter :form="urlPreviewForm"/>
+ </template>
- <div class="_gaps">
- <MkSwitch v-model="urlPreviewForm.state.urlPreviewEnabled">
- <template #label>{{ i18n.ts._urlPreviewSetting.enable }}<span v-if="urlPreviewForm.modifiedStates.urlPreviewEnabled" class="_modified">{{ i18n.ts.modified }}</span></template>
- </MkSwitch>
+ <div class="_gaps">
+ <SearchMarker>
+ <MkSwitch v-model="urlPreviewForm.state.urlPreviewEnabled">
+ <template #label><SearchLabel>{{ i18n.ts._urlPreviewSetting.enable }}</SearchLabel><span v-if="urlPreviewForm.modifiedStates.urlPreviewEnabled" class="_modified">{{ i18n.ts.modified }}</span></template>
+ </MkSwitch>
+ </SearchMarker>
- <template v-if="urlPreviewForm.state.urlPreviewEnabled">
- <MkSwitch v-model="urlPreviewForm.state.urlPreviewAllowRedirect">
- <template #label>{{ i18n.ts._urlPreviewSetting.allowRedirect }}<span v-if="urlPreviewForm.modifiedStates.urlPreviewAllowRedirect" class="_modified">{{ i18n.ts.modified }}</span></template>
- <template #caption>{{ i18n.ts._urlPreviewSetting.allowRedirectDescription }}</template>
- </MkSwitch>
+ <template v-if="urlPreviewForm.state.urlPreviewEnabled">
+ <SearchMarker :keywords="['allow', 'redirect']">
+ <MkSwitch v-model="urlPreviewForm.state.urlPreviewAllowRedirect">
+ <template #label><SearchLabel>{{ i18n.ts._urlPreviewSetting.allowRedirect }}</SearchLabel><span v-if="urlPreviewForm.modifiedStates.urlPreviewAllowRedirect" class="_modified">{{ i18n.ts.modified }}</span></template>
+ <template #caption>{{ i18n.ts._urlPreviewSetting.allowRedirectDescription }}</template>
+ </MkSwitch>
+ </SearchMarker>
- <MkSwitch v-model="urlPreviewForm.state.urlPreviewRequireContentLength">
- <template #label>{{ i18n.ts._urlPreviewSetting.requireContentLength }}<span v-if="urlPreviewForm.modifiedStates.urlPreviewRequireContentLength" class="_modified">{{ i18n.ts.modified }}</span></template>
- <template #caption>{{ i18n.ts._urlPreviewSetting.requireContentLengthDescription }}</template>
- </MkSwitch>
+ <SearchMarker :keywords="['contentLength']">
+ <MkSwitch v-model="urlPreviewForm.state.urlPreviewRequireContentLength">
+ <template #label><SearchLabel>{{ i18n.ts._urlPreviewSetting.requireContentLength }}</SearchLabel><span v-if="urlPreviewForm.modifiedStates.urlPreviewRequireContentLength" class="_modified">{{ i18n.ts.modified }}</span></template>
+ <template #caption>{{ i18n.ts._urlPreviewSetting.requireContentLengthDescription }}</template>
+ </MkSwitch>
+ </SearchMarker>
- <MkInput v-model="urlPreviewForm.state.urlPreviewMaximumContentLength" type="number">
- <template #label>{{ i18n.ts._urlPreviewSetting.maximumContentLength }}<span v-if="urlPreviewForm.modifiedStates.urlPreviewMaximumContentLength" class="_modified">{{ i18n.ts.modified }}</span></template>
- <template #caption>{{ i18n.ts._urlPreviewSetting.maximumContentLengthDescription }}</template>
- </MkInput>
+ <SearchMarker :keywords="['contentLength']">
+ <MkInput v-model="urlPreviewForm.state.urlPreviewMaximumContentLength" type="number">
+ <template #label><SearchLabel>{{ i18n.ts._urlPreviewSetting.maximumContentLength }}</SearchLabel><span v-if="urlPreviewForm.modifiedStates.urlPreviewMaximumContentLength" class="_modified">{{ i18n.ts.modified }}</span></template>
+ <template #caption>{{ i18n.ts._urlPreviewSetting.maximumContentLengthDescription }}</template>
+ </MkInput>
+ </SearchMarker>
- <MkInput v-model="urlPreviewForm.state.urlPreviewTimeout" type="number">
- <template #label>{{ i18n.ts._urlPreviewSetting.timeout }}<span v-if="urlPreviewForm.modifiedStates.urlPreviewTimeout" class="_modified">{{ i18n.ts.modified }}</span></template>
- <template #caption>{{ i18n.ts._urlPreviewSetting.timeoutDescription }}</template>
- </MkInput>
+ <SearchMarker :keywords="['timeout']">
+ <MkInput v-model="urlPreviewForm.state.urlPreviewTimeout" type="number">
+ <template #label><SearchLabel>{{ i18n.ts._urlPreviewSetting.timeout }}</SearchLabel><span v-if="urlPreviewForm.modifiedStates.urlPreviewTimeout" class="_modified">{{ i18n.ts.modified }}</span></template>
+ <template #caption>{{ i18n.ts._urlPreviewSetting.timeoutDescription }}</template>
+ </MkInput>
+ </SearchMarker>
- <MkInput v-model="urlPreviewForm.state.urlPreviewUserAgent" type="text">
- <template #label>{{ i18n.ts._urlPreviewSetting.userAgent }}<span v-if="urlPreviewForm.modifiedStates.urlPreviewUserAgent" class="_modified">{{ i18n.ts.modified }}</span></template>
- <template #caption>{{ i18n.ts._urlPreviewSetting.userAgentDescription }}</template>
- </MkInput>
+ <SearchMarker :keywords="['userAgent']">
+ <MkInput v-model="urlPreviewForm.state.urlPreviewUserAgent" type="text">
+ <template #label><SearchLabel>{{ i18n.ts._urlPreviewSetting.userAgent }}</SearchLabel><span v-if="urlPreviewForm.modifiedStates.urlPreviewUserAgent" class="_modified">{{ i18n.ts.modified }}</span></template>
+ <template #caption>{{ i18n.ts._urlPreviewSetting.userAgentDescription }}</template>
+ </MkInput>
+ </SearchMarker>
- <div>
- <MkInput v-model="urlPreviewForm.state.urlPreviewSummaryProxyUrl" type="text">
- <template #label>{{ i18n.ts._urlPreviewSetting.summaryProxy }}<span v-if="urlPreviewForm.modifiedStates.urlPreviewSummaryProxyUrl" class="_modified">{{ i18n.ts.modified }}</span></template>
- <template #caption>[{{ i18n.ts.notUsePleaseLeaveBlank }}] {{ i18n.ts._urlPreviewSetting.summaryProxyDescription }}</template>
- </MkInput>
+ <div>
+ <SearchMarker :keywords="['proxy']">
+ <MkInput v-model="urlPreviewForm.state.urlPreviewSummaryProxyUrl" type="text">
+ <template #label><SearchLabel>{{ i18n.ts._urlPreviewSetting.summaryProxy }}</SearchLabel><span v-if="urlPreviewForm.modifiedStates.urlPreviewSummaryProxyUrl" class="_modified">{{ i18n.ts.modified }}</span></template>
+ <template #caption>[{{ i18n.ts.notUsePleaseLeaveBlank }}] {{ i18n.ts._urlPreviewSetting.summaryProxyDescription }}</template>
+ </MkInput>
+ </SearchMarker>
- <div :class="$style.subCaption">
- {{ i18n.ts._urlPreviewSetting.summaryProxyDescription2 }}
- <ul style="padding-left: 20px; margin: 4px 0">
- <li>{{ i18n.ts._urlPreviewSetting.timeout }} / key:timeout</li>
- <li>{{ i18n.ts._urlPreviewSetting.maximumContentLength }} / key:contentLengthLimit</li>
- <li>{{ i18n.ts._urlPreviewSetting.requireContentLength }} / key:contentLengthRequired</li>
- <li>{{ i18n.ts._urlPreviewSetting.userAgent }} / key:userAgent</li>
- </ul>
- </div>
+ <div :class="$style.subCaption">
+ {{ i18n.ts._urlPreviewSetting.summaryProxyDescription2 }}
+ <ul style="padding-left: 20px; margin: 4px 0">
+ <li>{{ i18n.ts._urlPreviewSetting.timeout }} / key:timeout</li>
+ <li>{{ i18n.ts._urlPreviewSetting.maximumContentLength }} / key:contentLengthLimit</li>
+ <li>{{ i18n.ts._urlPreviewSetting.requireContentLength }} / key:contentLengthRequired</li>
+ <li>{{ i18n.ts._urlPreviewSetting.userAgent }} / key:userAgent</li>
+ </ul>
+ </div>
+ </div>
+ </template>
</div>
- </template>
- </div>
- </MkFolder>
+ </MkFolder>
+ </SearchMarker>
- <MkFolder>
- <template #icon><i class="ti ti-planet"></i></template>
- <template #label>{{ i18n.ts.federation }}</template>
- <template v-if="federationForm.savedState.federation === 'all'" #suffix>{{ i18n.ts.all }}</template>
- <template v-else-if="federationForm.savedState.federation === 'specified'" #suffix>{{ i18n.ts.specifyHost }}</template>
- <template v-else-if="federationForm.savedState.federation === 'none'" #suffix>{{ i18n.ts.none }}</template>
- <template v-if="federationForm.modified.value" #footer>
- <MkFormFooter :form="federationForm"/>
- </template>
+ <SearchMarker v-slot="slotProps" :keywords="['federation']">
+ <MkFolder :defaultOpen="slotProps.isParentOfTarget">
+ <template #icon><SearchIcon><i class="ti ti-planet"></i></SearchIcon></template>
+ <template #label><SearchLabel>{{ i18n.ts.federation }}</SearchLabel></template>
+ <template v-if="federationForm.savedState.federation === 'all'" #suffix>{{ i18n.ts.all }}</template>
+ <template v-else-if="federationForm.savedState.federation === 'specified'" #suffix>{{ i18n.ts.specifyHost }}</template>
+ <template v-else-if="federationForm.savedState.federation === 'none'" #suffix>{{ i18n.ts.none }}</template>
+ <template v-if="federationForm.modified.value" #footer>
+ <MkFormFooter :form="federationForm"/>
+ </template>
- <div class="_gaps">
- <MkRadios v-model="federationForm.state.federation">
- <template #label>{{ i18n.ts.behavior }}<span v-if="federationForm.modifiedStates.federation" class="_modified">{{ i18n.ts.modified }}</span></template>
- <option value="all">{{ i18n.ts.all }}</option>
- <option value="specified">{{ i18n.ts.specifyHost }}</option>
- <option value="none">{{ i18n.ts.none }}</option>
- </MkRadios>
+ <div class="_gaps">
+ <SearchMarker>
+ <MkRadios v-model="federationForm.state.federation">
+ <template #label><SearchLabel>{{ i18n.ts.behavior }}</SearchLabel><span v-if="federationForm.modifiedStates.federation" class="_modified">{{ i18n.ts.modified }}</span></template>
+ <option value="all">{{ i18n.ts.all }}</option>
+ <option value="specified">{{ i18n.ts.specifyHost }}</option>
+ <option value="none">{{ i18n.ts.none }}</option>
+ </MkRadios>
+ </SearchMarker>
- <MkTextarea v-if="federationForm.state.federation === 'specified'" v-model="federationForm.state.federationHosts">
- <template #label>{{ i18n.ts.federationAllowedHosts }}<span v-if="federationForm.modifiedStates.federationHosts" class="_modified">{{ i18n.ts.modified }}</span></template>
- <template #caption>{{ i18n.ts.federationAllowedHostsDescription }}</template>
- </MkTextarea>
+ <SearchMarker :keywords="['hosts']">
+ <MkTextarea v-if="federationForm.state.federation === 'specified'" v-model="federationForm.state.federationHosts">
+ <template #label><SearchLabel>{{ i18n.ts.federationAllowedHosts }}</SearchLabel><span v-if="federationForm.modifiedStates.federationHosts" class="_modified">{{ i18n.ts.modified }}</span></template>
+ <template #caption>{{ i18n.ts.federationAllowedHostsDescription }}</template>
+ </MkTextarea>
+ </SearchMarker>
- <MkFolder>
- <template #icon><i class="ti ti-list"></i></template>
- <template #label><SearchLabel>{{ i18n.ts._serverSettings.deliverSuspendedSoftware }}</SearchLabel></template>
- <template #footer>
- <div class="_buttons">
- <MkButton @click="federationForm.state.deliverSuspendedSoftware.push({software: '', versionRange: ''})"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton>
- </div>
- </template>
+ <SearchMarker :keywords="['suspended', 'software']">
+ <MkFolder>
+ <template #icon><i class="ti ti-list"></i></template>
+ <template #label><SearchLabel>{{ i18n.ts._serverSettings.deliverSuspendedSoftware }}</SearchLabel></template>
+ <template #footer>
+ <div class="_buttons">
+ <MkButton @click="federationForm.state.deliverSuspendedSoftware.push({software: '', versionRange: ''})"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton>
+ </div>
+ </template>
- <div :class="$style.metadataRoot" class="_gaps_s">
- <MkInfo>{{ i18n.ts._serverSettings.deliverSuspendedSoftwareDescription }}</MkInfo>
- <div v-for="(element, index) in federationForm.state.deliverSuspendedSoftware" :key="index" v-panel :class="$style.fieldDragItem">
- <button class="_button" :class="$style.dragItemRemove" @click="federationForm.state.deliverSuspendedSoftware.splice(index, 1)"><i class="ti ti-x"></i></button>
- <div :class="$style.dragItemForm">
- <FormSplit :minWidth="200">
- <MkInput v-model="element.software" small :placeholder="i18n.ts.softwareName">
- </MkInput>
- <MkInput v-model="element.versionRange" small :placeholder="i18n.ts.version">
- </MkInput>
- </FormSplit>
- </div>
- </div>
- </div>
- </MkFolder>
+ <div :class="$style.metadataRoot" class="_gaps_s">
+ <MkInfo>{{ i18n.ts._serverSettings.deliverSuspendedSoftwareDescription }}</MkInfo>
+ <div v-for="(element, index) in federationForm.state.deliverSuspendedSoftware" :key="index" v-panel :class="$style.fieldDragItem">
+ <button class="_button" :class="$style.dragItemRemove" @click="federationForm.state.deliverSuspendedSoftware.splice(index, 1)"><i class="ti ti-x"></i></button>
+ <div :class="$style.dragItemForm">
+ <FormSplit :minWidth="200">
+ <MkInput v-model="element.software" small :placeholder="i18n.ts.softwareName">
+ </MkInput>
+ <MkInput v-model="element.versionRange" small :placeholder="i18n.ts.version">
+ </MkInput>
+ </FormSplit>
+ </div>
+ </div>
+ </div>
+ </MkFolder>
+ </SearchMarker>
- <MkSwitch v-model="federationForm.state.signToActivityPubGet">
- <template #label>{{ i18n.ts._serverSettings.signToActivityPubGet }}<span v-if="federationForm.modifiedStates.signToActivityPubGet" class="_modified">{{ i18n.ts.modified }}</span></template>
- <template #caption>{{ i18n.ts._serverSettings.signToActivityPubGet_description }}</template>
- </MkSwitch>
+ <SearchMarker :keywords="['sign', 'get']">
+ <MkSwitch v-model="federationForm.state.signToActivityPubGet">
+ <template #label><SearchLabel>{{ i18n.ts._serverSettings.signToActivityPubGet }}</SearchLabel><span v-if="federationForm.modifiedStates.signToActivityPubGet" class="_modified">{{ i18n.ts.modified }}</span></template>
+ <template #caption><SearchText>{{ i18n.ts._serverSettings.signToActivityPubGet_description }}</SearchText></template>
+ </MkSwitch>
+ </SearchMarker>
- <MkSwitch v-model="federationForm.state.proxyRemoteFiles">
- <template #label>{{ i18n.ts._serverSettings.proxyRemoteFiles }}<span v-if="federationForm.modifiedStates.proxyRemoteFiles" class="_modified">{{ i18n.ts.modified }}</span></template>
- <template #caption>{{ i18n.ts._serverSettings.proxyRemoteFiles_description }}</template>
- </MkSwitch>
+ <SearchMarker :keywords="['proxy', 'remote', 'files']">
+ <MkSwitch v-model="federationForm.state.proxyRemoteFiles">
+ <template #label><SearchLabel>{{ i18n.ts._serverSettings.proxyRemoteFiles }}</SearchLabel><span v-if="federationForm.modifiedStates.proxyRemoteFiles" class="_modified">{{ i18n.ts.modified }}</span></template>
+ <template #caption><SearchText>{{ i18n.ts._serverSettings.proxyRemoteFiles_description }}</SearchText></template>
+ </MkSwitch>
+ </SearchMarker>
- <MkSwitch v-model="federationForm.state.allowExternalApRedirect">
- <template #label>{{ i18n.ts._serverSettings.allowExternalApRedirect }}<span v-if="federationForm.modifiedStates.allowExternalApRedirect" class="_modified">{{ i18n.ts.modified }}</span></template>
- <template #caption>
- <div>{{ i18n.ts._serverSettings.allowExternalApRedirect_description }}</div>
- <div>{{ i18n.ts.needToRestartServerToApply }}</div>
- </template>
- </MkSwitch>
+ <SearchMarker :keywords="['allow', 'external', 'redirect']">
+ <MkSwitch v-model="federationForm.state.allowExternalApRedirect">
+ <template #label><SearchLabel>{{ i18n.ts._serverSettings.allowExternalApRedirect }}</SearchLabel><span v-if="federationForm.modifiedStates.allowExternalApRedirect" class="_modified">{{ i18n.ts.modified }}</span></template>
+ <template #caption>
+ <div><SearchText>{{ i18n.ts._serverSettings.allowExternalApRedirect_description }}</SearchText></div>
+ <div>{{ i18n.ts.needToRestartServerToApply }}</div>
+ </template>
+ </MkSwitch>
+ </SearchMarker>
- <MkSwitch v-model="federationForm.state.cacheRemoteFiles">
- <template #label>{{ i18n.ts.cacheRemoteFiles }}<span v-if="federationForm.modifiedStates.cacheRemoteFiles" class="_modified">{{ i18n.ts.modified }}</span></template>
- <template #caption>{{ i18n.ts.cacheRemoteFilesDescription }}{{ i18n.ts.youCanCleanRemoteFilesCache }}</template>
- </MkSwitch>
+ <SearchMarker :keywords="['cache', 'remote', 'files']">
+ <MkSwitch v-model="federationForm.state.cacheRemoteFiles">
+ <template #label><SearchLabel>{{ i18n.ts.cacheRemoteFiles }}</SearchLabel><span v-if="federationForm.modifiedStates.cacheRemoteFiles" class="_modified">{{ i18n.ts.modified }}</span></template>
+ <template #caption><SearchText>{{ i18n.ts.cacheRemoteFilesDescription }}</SearchText>{{ i18n.ts.youCanCleanRemoteFilesCache }}</template>
+ </MkSwitch>
+ </SearchMarker>
- <template v-if="federationForm.state.cacheRemoteFiles">
- <MkSwitch v-model="federationForm.state.cacheRemoteSensitiveFiles">
- <template #label>{{ i18n.ts.cacheRemoteSensitiveFiles }}<span v-if="federationForm.modifiedStates.cacheRemoteSensitiveFiles" class="_modified">{{ i18n.ts.modified }}</span></template>
- <template #caption>{{ i18n.ts.cacheRemoteSensitiveFilesDescription }}</template>
- </MkSwitch>
- </template>
- </div>
- </MkFolder>
+ <template v-if="federationForm.state.cacheRemoteFiles">
+ <SearchMarker :keywords="['cache', 'remote', 'sensitive', 'files']">
+ <MkSwitch v-model="federationForm.state.cacheRemoteSensitiveFiles">
+ <template #label><SearchLabel>{{ i18n.ts.cacheRemoteSensitiveFiles }}</SearchLabel><span v-if="federationForm.modifiedStates.cacheRemoteSensitiveFiles" class="_modified">{{ i18n.ts.modified }}</span></template>
+ <template #caption><SearchText>{{ i18n.ts.cacheRemoteSensitiveFilesDescription }}</SearchText></template>
+ </MkSwitch>
+ </SearchMarker>
+ </template>
+ </div>
+ </MkFolder>
+ </SearchMarker>
- <MkFolder>
- <template #icon><i class="ti ti-ghost"></i></template>
- <template #label>{{ i18n.ts.proxyAccount }}</template>
- <template v-if="proxyAccountForm.modified.value" #footer>
- <MkFormFooter :form="proxyAccountForm"/>
- </template>
+ <SearchMarker v-slot="slotProps" :keywords="['proxy', 'account']">
+ <MkFolder :defaultOpen="slotProps.isParentOfTarget">
+ <template #icon><SearchIcon><i class="ti ti-ghost"></i></SearchIcon></template>
+ <template #label><SearchLabel>{{ i18n.ts.proxyAccount }}</SearchLabel></template>
+ <template v-if="proxyAccountForm.modified.value" #footer>
+ <MkFormFooter :form="proxyAccountForm"/>
+ </template>
- <div class="_gaps">
- <MkInfo>{{ i18n.ts.proxyAccountDescription }}</MkInfo>
+ <div class="_gaps">
+ <MkInfo>{{ i18n.ts.proxyAccountDescription }}</MkInfo>
- <MkTextarea v-model="proxyAccountForm.state.description" :max="500" tall mfmAutocomplete :mfmPreview="true">
- <template #label>{{ i18n.ts._profile.description }}</template>
- <template #caption>{{ i18n.ts._profile.youCanIncludeHashtags }}</template>
- </MkTextarea>
- </div>
- </MkFolder>
+ <SearchMarker :keywords="['description']">
+ <MkTextarea v-model="proxyAccountForm.state.description" :max="500" tall mfmAutocomplete :mfmPreview="true">
+ <template #label><SearchLabel>{{ i18n.ts._profile.description }}</SearchLabel></template>
+ <template #caption>{{ i18n.ts._profile.youCanIncludeHashtags }}</template>
+ </MkTextarea>
+ </SearchMarker>
+ </div>
+ </MkFolder>
+ </SearchMarker>
- <MkButton primary @click="openSetupWizard">
- Open setup wizard
- </MkButton>
- </div>
+ <MkButton primary @click="openSetupWizard">
+ Open setup wizard
+ </MkButton>
+ </div>
+ </SearchMarker>
</div>
</PageWithHeader>
</template>
diff --git a/packages/frontend/src/pages/admin/system-webhook.vue b/packages/frontend/src/pages/admin/system-webhook.vue
index d5402f608c..0fd255d5f6 100644
--- a/packages/frontend/src/pages/admin/system-webhook.vue
+++ b/packages/frontend/src/pages/admin/system-webhook.vue
@@ -6,17 +6,21 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<PageWithHeader :actions="headerActions" :tabs="headerTabs">
<div class="_spacer" style="--MI_SPACER-w: 900px;">
- <div class="_gaps_m">
- <MkButton primary @click="onCreateWebhookClicked">
- <i class="ti ti-plus"></i> {{ i18n.ts._webhookSettings.createWebhook }}
- </MkButton>
+ <SearchMarker path="/admin/system-webhook" label="SystemWebhook" :keywords="['webhook']" icon="ti ti-webhook">
+ <div class="_gaps_m">
+ <SearchMarker>
+ <MkButton primary @click="onCreateWebhookClicked">
+ <i class="ti ti-plus"></i> <SearchLabel>{{ i18n.ts._webhookSettings.createWebhook }}</SearchLabel>
+ </MkButton>
+ </SearchMarker>
- <FormSection>
- <div class="_gaps">
- <XItem v-for="item in webhooks" :key="item.id" :entity="item" @edit="onEditButtonClicked" @delete="onDeleteButtonClicked"/>
- </div>
- </FormSection>
- </div>
+ <FormSection>
+ <div class="_gaps">
+ <XItem v-for="item in webhooks" :key="item.id" :entity="item" @edit="onEditButtonClicked" @delete="onDeleteButtonClicked"/>
+ </div>
+ </FormSection>
+ </div>
+ </SearchMarker>
</div>
</PageWithHeader>
</template>