feat: add localHost option to allow instances to have seperate user hosts from the instance url

This commit is contained in:
Freya Murphy 2026-03-06 15:33:11 -05:00
commit 0a8f8e7762
No known key found for this signature in database
GPG key ID: 9FBC6FFD6D2DBF17
40 changed files with 106 additions and 73 deletions

View file

@ -21,6 +21,11 @@ setupPassword: example_password_please_change_this_or_you_will_get_hacked
# Final accessible URL seen by a user.
url: 'http://misskey.local'
# Use a seperate hostname for federation. All users on this
# instance will appear with this host instead of the one with
# the misskey web url if set.
#localHost: 'misskey.local'
# ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE
# URL SETTINGS AFTER THAT!

View file

@ -9,6 +9,11 @@
# You can set url from an environment variable instead.
url: https://example.tld/
# Use a seperate hostname for federation. All users on this
# instance will appear with this host instead of the one with
# the misskey web url if set.
#localHost: 'example.tld'
# ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE
# URL SETTINGS AFTER THAT!

View file

@ -79,6 +79,11 @@
# Final accessible URL seen by a user.
url: https://example.tld/
# Use a seperate hostname for federation. All users on this
# instance will appear with this host instead of the one with
# the misskey web url if set.
#localHost: 'example.tld'
# ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE
# URL SETTINGS AFTER THAT!

View file

@ -8,6 +8,11 @@
# Final accessible URL seen by a user.
url: http://127.0.0.1:3000/
# Use a seperate hostname for federation. All users on this
# instance will appear with this host instead of the one with
# the misskey web url if set.
#localHost: '127.0.0.1:3000'
# ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE
# URL SETTINGS AFTER THAT!

View file

@ -147,6 +147,7 @@
### General
- 依存関係の更新
- Added `localHost` option to allow misskey instances to federate their users on a different host than the one from the accessable web url.
### Client
- Enhance: アプリ内ウィンドウの初期サイズを画面サイズに応じて自動で調整するように

View file

@ -25,6 +25,7 @@ type RedisOptionsSource = Partial<RedisOptions> & {
*/
type Source = {
url?: string;
localHost?: string;
port?: number;
socket?: string;
trustProxy?: FastifyServerOptions['trustProxy'];
@ -119,6 +120,7 @@ type Source = {
export type Config = {
url: string;
localHost: string;
port: number;
socket: string | undefined;
trustProxy: NonNullable<FastifyServerOptions['trustProxy']>;
@ -259,6 +261,7 @@ export function loadConfig(): Config {
const hostname = url.hostname;
const scheme = url.protocol.replace(/:$/, '');
const wsScheme = scheme.replace('http', 'ws');
const localHost = config.localHost ?? host;
const dbDb = config.db.db ?? process.env.DATABASE_DB ?? '';
const dbUser = config.db.user ?? process.env.DATABASE_USER ?? '';
@ -275,6 +278,7 @@ export function loadConfig(): Config {
publishTarballInsteadOfProvideRepositoryUrl: !!config.publishTarballInsteadOfProvideRepositoryUrl,
setupPassword: config.setupPassword,
url: url.origin,
localHost,
port: config.port ?? parseInt(process.env.PORT ?? '', 10),
socket: config.socket,
trustProxy: config.trustProxy ?? [

View file

@ -386,7 +386,7 @@ export class MfmService {
const remoteUserInfo = mentionedRemoteUsers.find(remoteUser => remoteUser.username.toLowerCase() === username.toLowerCase() && remoteUser.host?.toLowerCase() === host?.toLowerCase());
const href = remoteUserInfo
? (remoteUserInfo.url ? remoteUserInfo.url : remoteUserInfo.uri)
: `${this.config.url}/${acct.endsWith(`@${this.config.url}`) ? acct.substring(0, acct.length - this.config.url.length - 1) : acct}`;
: `${this.config.url}/${acct.endsWith(`@${this.config.localHost}`) ? acct.substring(0, acct.length - this.config.localHost.length - 1) : acct}`;
try {
const url = new URL(href);
return `<a href="${escapeHtml(url.href)}" class="u-url mention">${escapeHtml(acct)}</a>`;

View file

@ -56,7 +56,7 @@ export class RemoteUserResolveService {
host = this.utilityService.toPuny(host);
if (host === this.utilityService.toPuny(this.config.host)) {
if ((host === this.utilityService.toPuny(this.config.localHost)) || (host === this.utilityService.toPuny(this.config.host))) {
this.logger.info(`return local user: ${usernameLower}`);
return await this.usersRepository.findOneBy({ usernameLower, host: IsNull() }).then(u => {
if (u == null) {

View file

@ -26,13 +26,14 @@ export class UtilityService {
@bindThis
public getFullApAccount(username: string, host: string | null): string {
return host ? `${username}@${this.toPuny(host)}` : `${username}@${this.toPuny(this.config.host)}`;
return host ? `${username}@${this.toPuny(host)}` : `${username}@${this.toPuny(this.config.localHost)}`;
}
@bindThis
public isSelfHost(host: string | null): boolean {
if (host == null) return true;
return this.toPuny(this.config.host) === this.toPuny(host);
return (this.toPuny(this.config.localHost) === this.toPuny(host)) ||
(this.toPuny(this.config.host) === this.toPuny(host));
}
@bindThis

View file

@ -305,7 +305,8 @@ export class ApPersonService implements OnModuleInit {
if (typeof uri !== 'string') throw new Error('uri is not string');
const host = this.utilityService.punyHost(uri);
if (host === this.utilityService.toPuny(this.config.host)) {
if ((host === this.utilityService.toPuny(this.config.localHost)) ||
(host === this.utilityService.toPuny(this.config.host))) {
throw new StatusError('cannot resolve local user', 400, 'cannot resolve local user');
}

View file

@ -384,10 +384,10 @@ export class UserEntityService implements OnModuleInit {
@bindThis
public getIdenticonUrl(user: MiUser): string {
if ((user.host == null || user.host === this.config.host) && user.username.includes('.') && this.meta.iconUrl) { // ローカルのシステムアカウントの場合
if ((user.host == null || user.host === this.config.host || user.host === this.config.localHost) && user.username.includes('.') && this.meta.iconUrl) { // ローカルのシステムアカウントの場合
return this.meta.iconUrl;
} else {
return `${this.config.url}/identicon/${user.username.toLowerCase()}@${user.host ?? this.config.host}`;
return `${this.config.url}/identicon/${user.username.toLowerCase()}@${user.host ?? this.config.localHost}`;
}
}

View file

@ -213,7 +213,7 @@ export class ServerService implements OnApplicationShutdown {
const user = await this.usersRepository.findOne({
where: {
usernameLower: username.toLowerCase(),
host: (host == null) || (host === this.config.host) ? IsNull() : host,
host: (host == null) || (host === this.config.host) || (host === this.config.localHost) ? IsNull() : host,
isSuspended: false,
},
});

View file

@ -137,7 +137,7 @@ fastify.get('/.well-known/change-password', async (request, reply) => {
resource));
const fromAcct = (acct: Acct.Acct): FindOptionsWhere<MiUser> | number =>
!acct.host || acct.host === this.config.host.toLowerCase() ? {
!acct.host || acct.host === this.config.host.toLowerCase() || acct.host === this.config.localHost.toLowerCase() ? {
usernameLower: acct.username.toLowerCase(),
host: IsNull(),
isSuspended: false,
@ -162,7 +162,7 @@ fastify.get('/.well-known/change-password', async (request, reply) => {
return;
}
const subject = `acct:${user.username}@${this.config.host}`;
const subject = `acct:${user.username}@${this.config.localHost}`;
const self = {
rel: 'self',
type: 'application/activity+json',

View file

@ -60,7 +60,7 @@ export class FeedService {
const feed = new Feed({
id: author.link,
title: `${author.name} (@${user.username}@${this.config.host})`,
title: `${author.name} (@${user.username}@${this.config.localHost})`,
updated: notes.length !== 0 ? this.idService.parse(notes[0].id).date : undefined,
generator: 'Misskey',
description: `${user.notesCount} Notes, ${profile.followingVisibility === 'public' ? user.followingCount : '?'} Following, ${profile.followersVisibility === 'public' ? user.followersCount : '?'} Followers${profile.description ? ` · ${profile.description}` : ''}`,

View file

@ -165,6 +165,7 @@ export class HtmlTemplateService {
infoImageUrl: this.meta.infoImageUrl ?? 'https://xn--931a.moe/assets/info.jpg',
notFoundImageUrl: this.meta.notFoundImageUrl ?? 'https://xn--931a.moe/assets/not-found.jpg',
instanceUrl: this.config.url,
localHost: this.config.localHost,
metaJson: htmlSafeJsonStringify(await this.metaEntityService.packDetailed(this.meta)),
now: Date.now(),
federationEnabled: this.meta.federation !== 'none',

View file

@ -40,6 +40,7 @@ export type CommonData = MinimumCommonData & {
infoImageUrl: string;
notFoundImageUrl: string;
instanceUrl: string;
localHost: string;
now: number;
federationEnabled: boolean;
frontendViteFiles: ViteFiles | null;

View file

@ -41,6 +41,7 @@ export function BaseEmbed(props: PropsWithChildren<CommonProps<{
<meta name="theme-color-orig" content={props.themeColor ?? '#86b300'} />
<meta property="og:site_name" content={props.instanceName || 'Misskey'} />
<meta property="instance_url" content={props.instanceUrl} />
<meta property="local_host" content={props.localHost} />
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover" />
<meta name="format-detection" content="telephone=no,date=no,address=no,email=no,url=no" />
<link rel="icon" href={props.icon ?? '/favicon.ico'} />

View file

@ -43,6 +43,7 @@ export function Layout(props: PropsWithChildren<CommonProps<{
<meta name="theme-color-orig" content={props.themeColor ?? '#86b300'} />
<meta property="og:site_name" content={props.instanceName || 'Misskey'} />
<meta property="instance_url" content={props.instanceUrl} />
<meta property="local_host" content={props.localHost} />
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover" />
<meta name="format-detection" content="telephone=no,date=no,address=no,email=no,url=no" />
<link rel="icon" href={props.icon || '/favicon.ico'} />

View file

@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import * as Misskey from 'misskey-js';
import { toUnicode } from 'punycode.js';
import { host as hostRaw } from '@@/js/config.js';
import { localHost as hostRaw } from '@@/js/config.js';
defineProps<{
user: Misskey.entities.UserLite;

View file

@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only
import { toUnicode } from 'punycode.js';
import { } from 'vue';
import tinycolor from 'tinycolor2';
import { host as localHost } from '@@/js/config.js';
import { localHost } from '@@/js/config.js';
const props = defineProps<{
username: string;

View file

@ -7,7 +7,7 @@ import { h, provide } from 'vue';
import type { VNode, SetupContext } from 'vue';
import * as mfm from 'mfm-js';
import * as Misskey from 'misskey-js';
import { host } from '@@/js/config.js';
import { localHost } from '@@/js/config.js';
import EmUrl from '@/components/EmUrl.vue';
import EmTime from '@/components/EmTime.vue';
import EmLink from '@/components/EmLink.vue';
@ -347,7 +347,7 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven
case 'mention': {
return [h(EmMention, {
key: Math.random(),
host: (token.props.host == null && props.author && props.author.host != null ? props.author.host : token.props.host) ?? host,
host: (token.props.host == null && props.author && props.author.host != null ? props.author.host : token.props.host) ?? localHost,
username: token.props.username,
})];
}

View file

@ -5,11 +5,13 @@
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
const address = new URL(window.document.querySelector<HTMLMetaElement>('meta[property="instance_url"]')?.content || window.location.href);
const theLocalHost = window.document.querySelector<HTMLMetaElement>('meta[property="local_host"]')?.content;
const siteName = window.document.querySelector<HTMLMetaElement>('meta[property="og:site_name"]')?.content;
export const host = address.host;
export const hostname = address.hostname;
export const url = address.origin;
export const localHost = theLocalHost ?? host;
export const port = address.port;
export const apiUrl = window.location.origin + '/api';
export const wsOrigin = window.location.origin;

View file

@ -5,7 +5,7 @@
import { defineAsyncComponent, ref } from 'vue';
import * as Misskey from 'misskey-js';
import { apiUrl, host } from '@@/js/config.js';
import { apiUrl, localHost } from '@@/js/config.js';
import type { MenuItem } from '@/types/menu.js';
import { showSuspendedDialog } from '@/utility/show-suspended-dialog.js';
import { i18n } from '@/i18n.js';
@ -131,7 +131,7 @@ export function updateCurrentAccount(accountData: Misskey.entities.MeDetailed) {
for (const [key, value] of Object.entries(accountData)) {
($i[key as keyof typeof accountData] as any) = value;
}
store.set('accountInfos', { ...store.s.accountInfos, [host + '/' + $i.id]: $i });
store.set('accountInfos', { ...store.s.accountInfos, [localHost + '/' + $i.id]: $i });
$i.token = token;
miLocalStorage.setItem('account', JSON.stringify($i));
}
@ -142,7 +142,7 @@ export function updateCurrentAccountPartial(accountData: Partial<Misskey.entitie
($i[key as keyof typeof accountData] as any) = value;
}
store.set('accountInfos', { ...store.s.accountInfos, [host + '/' + $i.id]: $i });
store.set('accountInfos', { ...store.s.accountInfos, [localHost + '/' + $i.id]: $i });
miLocalStorage.setItem('account', JSON.stringify($i));
}
@ -152,7 +152,7 @@ export async function refreshCurrentAccount() {
const me = $i;
return fetchAccount($i.token, $i.id).then(updateCurrentAccount).catch(reason => {
if (reason === isAccountDeleted) {
removeAccount(host, me.id);
removeAccount(localHost, me.id);
if (Object.keys(store.s.accountTokens).length > 0) {
login(Object.values(store.s.accountTokens)[0]);
} else {
@ -181,7 +181,7 @@ export async function login(token: AccountWithToken['token'], redirect?: string)
token,
}));
await addAccount(host, me, token);
await addAccount(localHost, me, token);
if (redirect) {
// 他のタブは再読み込みするだけ
@ -296,7 +296,7 @@ export async function getAccountMenu(opts: {
});
if (opts.includeCurrentAccount) {
menuItems.push(createItem(host, $i.id, $i.username, $i, $i.token));
menuItems.push(createItem(localHost, $i.id, $i.username, $i, $i.token));
}
menuItems.push(...accountItems);
@ -319,7 +319,7 @@ export async function getAccountMenu(opts: {
action: () => {
getAccountWithSignupDialog().then(res => {
if (res != null) {
switchAccount(host, res.id);
switchAccount(localHost, res.id);
}
});
},
@ -332,7 +332,7 @@ export async function getAccountMenu(opts: {
});
} else {
if (opts.includeCurrentAccount) {
menuItems.push(createItem(host, $i.id, $i.username, $i, $i.token));
menuItems.push(createItem(localHost, $i.id, $i.username, $i, $i.token));
}
menuItems.push(...accountItems);
@ -346,7 +346,7 @@ export function getAccountWithSigninDialog(): Promise<{ id: string, token: strin
const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkSigninDialog.vue')), {}, {
done: async (res: Misskey.entities.SigninFlowResponse & { finished: true }) => {
const user = await fetchAccount(res.i, res.id, true);
await addAccount(host, user, res.i);
await addAccount(localHost, user, res.i);
resolve({ id: res.id, token: res.i });
},
cancelled: () => {
@ -365,7 +365,7 @@ export function getAccountWithSignupDialog(): Promise<{ id: string, token: strin
done: async (res: Misskey.entities.SignupResponse) => {
const user = JSON.parse(JSON.stringify(res));
delete user.token;
await addAccount(host, user, res.token);
await addAccount(localHost, user, res.token);
resolve({ id: res.id, token: res.token });
},
cancelled: () => {

View file

@ -16,7 +16,7 @@ import { ref } from 'vue';
import * as Misskey from 'misskey-js';
import MkMention from './MkMention.vue';
import { i18n } from '@/i18n.js';
import { host as localHost } from '@@/js/config.js';
import { localHost } from '@@/js/config.js';
import { misskeyApi } from '@/utility/misskey-api.js';
const user = ref<Misskey.entities.UserLite>();

View file

@ -37,7 +37,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { onBeforeUnmount, onMounted, ref } from 'vue';
import * as Misskey from 'misskey-js';
import { host } from '@@/js/config.js';
import { localHost } from '@@/js/config.js';
import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import { useStream } from '@/stream.js';
@ -84,7 +84,7 @@ async function onClick() {
const isLoggedIn = await pleaseLogin({
openOnRemote: {
type: 'web',
path: `/@${props.user.username}@${props.user.host ?? host}`,
path: `/@${props.user.username}@${props.user.host ?? localHost}`,
},
});
if (!isLoggedIn) return;

View file

@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { toUnicode } from 'punycode.js';
import { computed } from 'vue';
import { host as localHost } from '@@/js/config.js';
import { localHost } from '@@/js/config.js';
import type { MkABehavior } from '@/components/global/MkA.vue';
import { $i } from '@/i.js';
import { getStaticImageUrl } from '@/utility/media-proxy.js';

View file

@ -119,7 +119,7 @@ import * as mfm from 'mfm-js';
import * as Misskey from 'misskey-js';
import insertTextAtCursor from 'insert-text-at-cursor';
import { toASCII } from 'punycode.js';
import { host, url } from '@@/js/config.js';
import { localHost, url } from '@@/js/config.js';
import MkUploaderItems from './MkUploaderItems.vue';
import type { ShallowRef } from 'vue';
import type { PostFormProps } from '@/types/post-form.js';
@ -355,7 +355,7 @@ if (props.mention) {
text.value += ' ';
}
if (replyTargetNote.value && (replyTargetNote.value.user.username !== $i.username || (replyTargetNote.value.user.host != null && replyTargetNote.value.user.host !== host))) {
if (replyTargetNote.value && (replyTargetNote.value.user.username !== $i.username || (replyTargetNote.value.user.host != null && replyTargetNote.value.user.host !== localHost))) {
text.value = `@${replyTargetNote.value.user.username}${replyTargetNote.value.user.host != null ? '@' + toASCII(replyTargetNote.value.user.host) : ''} `;
}
@ -366,12 +366,12 @@ if (replyTargetNote.value && replyTargetNote.value.text != null) {
for (const x of extractMentions(ast)) {
const mention = x.host ?
`@${x.username}@${toASCII(x.host)}` :
(otherHost == null || otherHost === host) ?
(otherHost == null || otherHost === localHost) ?
`@${x.username}` :
`@${x.username}@${toASCII(otherHost)}`;
//
if ($i.username === x.username && (x.host == null || x.host === host)) continue;
if ($i.username === x.username && (x.host == null || x.host === localHost)) continue;
//
if (text.value.includes(`${mention} `)) continue;

View file

@ -57,7 +57,7 @@ import { ref } from 'vue';
import { toUnicode } from 'punycode.js';
import { query, extractDomain } from '@@/js/url.js';
import { host as configHost } from '@@/js/config.js';
import { localHost } from '@@/js/config.js';
import type { OpenOnRemoteOptions } from '@/utility/please-login.js';
import { i18n } from '@/i18n.js';
import * as os from '@/os.js';
@ -81,7 +81,7 @@ const emit = defineEmits<{
(ev: 'passkeyClick', v: PointerEvent): void;
}>();
const host = toUnicode(configHost);
const host = toUnicode(localHost);
const username = ref(props.initialUsername ?? '');

View file

@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #label>{{ i18n.ts.username }}</template>
<template #prefix>@</template>
</MkInput>
<MkInput v-model="host" :datalist="[hostname]" @update:modelValue="search">
<MkInput v-model="host" :datalist="[localHost]" @update:modelValue="search">
<template #label>{{ i18n.ts.host }}</template>
<template #prefix>@</template>
</MkInput>
@ -63,7 +63,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { onMounted, ref, computed, useTemplateRef } from 'vue';
import * as Misskey from 'misskey-js';
import { host as currentHost, hostname } from '@@/js/config.js';
import { localHost } from '@@/js/config.js';
import MkInput from '@/components/MkInput.vue';
import FormSplit from '@/components/form/split.vue';
import MkModalWindow from '@/components/MkModalWindow.vue';

View file

@ -13,12 +13,12 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import * as Misskey from 'misskey-js';
import { toUnicode } from 'punycode.js';
import { host as hostRaw } from '@@/js/config.js';
import { localHost } from '@@/js/config.js';
defineProps<{
user: Misskey.entities.UserLite;
detail?: boolean;
}>();
const host = toUnicode(hostRaw);
const host = toUnicode(localHost);
</script>

View file

@ -6,7 +6,7 @@
import { h } from 'vue';
import * as mfm from 'mfm-js';
import * as Misskey from 'misskey-js';
import { host } from '@@/js/config.js';
import { localHost } from '@@/js/config.js';
import type { VNode, SetupContext } from 'vue';
import type { MkABehavior } from '@/components/global/MkA.vue';
import MkUrl from '@/components/global/MkUrl.vue';
@ -369,7 +369,7 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven
case 'mention': {
return [h(MkMention, {
key: Math.random(),
host: (token.props.host == null && props.author && props.author.host != null ? props.author.host : token.props.host) ?? host,
host: (token.props.host == null && props.author && props.author.host != null ? props.author.host : token.props.host) ?? localHost,
username: token.props.username,
navigationBehavior: props.linkNavigationBehavior,
})];

View file

@ -182,10 +182,10 @@ function showMenu(ev: PointerEvent, contextmenu = false) {
text: i18n.ts.reportAbuse,
icon: 'ti ti-exclamation-circle',
action: async () => {
const localUrl = `${url}/chat/messages/${props.message.id}`;
const localHost = `${url}/chat/messages/${props.message.id}`;
const { dispose } = await os.popupAsyncWithDialog(import('@/components/MkAbuseReportWindow.vue').then(x => x.default), {
user: props.message.fromUser!,
initialComment: `${localUrl}\n-----\n`,
initialComment: `${localHost}\n-----\n`,
}, {
closed: () => dispose(),
});

View file

@ -29,7 +29,7 @@ SPDX-License-Identifier: AGPL-3.0-only
import tinycolor from 'tinycolor2';
import QRCodeStyling from 'qr-code-styling';
import { computed, ref, shallowRef, watch, onMounted, onUnmounted, useTemplateRef } from 'vue';
import { url, host } from '@@/js/config.js';
import { url, localHost } from '@@/js/config.js';
import type { Directive } from 'vue';
import { instance } from '@/instance.js';
import { ensureSignin } from '@/i.js';
@ -40,7 +40,7 @@ import { i18n } from '@/i18n.js';
const $i = ensureSignin();
const acct = computed(() => `@${$i.username}@${host}`);
const acct = computed(() => `@${$i.username}@${localHost}`);
const userProfileUrl = computed(() => userPage($i, undefined, true));
const shareData = computed(() => ({
title: i18n.tsx._qr.shareTitle({ name: userName($i), acct: acct.value }),

View file

@ -119,7 +119,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { computed, markRaw, ref, shallowRef, toRef } from 'vue';
import { host as localHost } from '@@/js/config.js';
import { host } from '@@/js/config.js';
import type * as Misskey from 'misskey-js';
import { $i } from '@/i.js';
import { i18n } from '@/i18n.js';
@ -221,7 +221,7 @@ type SearchParams = {
};
const fixHostIfLocal = (target: string | null | undefined) => {
if (!target || target === localHost) return '.';
if (!target || target === host) return '.';
return target;
};

View file

@ -106,7 +106,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { hostname, port } from '@@/js/config';
import { localHost, port } from '@@/js/config';
import { useTemplateRef, ref } from 'vue';
import MkButton from '@/components/MkButton.vue';
import MkModalWindow from '@/components/MkModalWindow.vue';
@ -162,7 +162,7 @@ function downloadBackupCodes() {
const txtBlob = new Blob([backupCodes.value.join('\n')], { type: 'text/plain' });
const dummya = window.document.createElement('a');
dummya.href = URL.createObjectURL(txtBlob);
dummya.download = `${$i.username}@${hostname}` + (port !== '' ? `_${port}` : '') + '-2fa-backup-codes.txt';
dummya.download = `${$i.username}@${localHost}` + (port !== '' ? `_${port}` : '') + '-2fa-backup-codes.txt';
dummya.click();
}
}

View file

@ -78,8 +78,8 @@ import tinycolor from 'tinycolor2';
import JSON5 from 'json5';
import lightTheme from '@@/themes/_light.json5';
import darkTheme from '@@/themes/_dark.json5';
import { host } from '@@/js/config.js';
import type { Theme } from '@@/js/theme.js';
import { localHost } from '@@/js/config.js';
import type { Theme } from '@/theme.js';
import { genId } from '@/utility/id.js';
import MkButton from '@/components/MkButton.vue';
import MkCodeEditor from '@/components/MkCodeEditor.vue';
@ -129,7 +129,7 @@ const fgColors = [
const theme = ref<Theme>({
id: genId(),
name: 'untitled',
author: `@${$i.username}@${toUnicode(host)}`,
author: `@${$i.username}@${toUnicode(localHost)}`,
base: 'light',
props: deepClone(lightTheme.props),
});

View file

@ -60,7 +60,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkInput v-model="username" pattern="^[a-zA-Z0-9_]{1,20}$" :spellcheck="false" required data-cy-admin-username>
<template #label>{{ i18n.ts.username }} <div v-tooltip:dialog="i18n.ts.usernameInfo" class="_button _help"><i class="ti ti-help-circle"></i></div></template>
<template #prefix>@</template>
<template #suffix>@{{ host }}</template>
<template #suffix>@{{ localHost }}</template>
</MkInput>
<MkInput v-model="password" type="password" data-cy-admin-password>
<template #label>{{ i18n.ts.password }}</template>
@ -125,7 +125,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref } from 'vue';
import { host, version } from '@@/js/config.js';
import { localHost, version } from '@@/js/config.js';
import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/MkInput.vue';
import * as os from '@/os.js';

View file

@ -5,7 +5,7 @@
import { customRef, ref, watch, onScopeDispose } from 'vue';
import { EventEmitter } from 'eventemitter3';
import { host, version } from '@@/js/config.js';
import { localHost, version } from '@@/js/config.js';
import { PREF_DEF } from './def.js';
import type { Ref } from 'vue';
import type { MenuItem } from '@/types/menu.js';
@ -155,28 +155,28 @@ function normalizePreferences(preferences: PossiblyNonNormalizedPreferencesProfi
const v = getInitialPrefValue(key as keyof typeof PREF_DEF);
if (isAccountDependentKey(key as keyof typeof PREF_DEF)) {
data[key] = account ? [[makeScope({}), v, {}], [makeScope({
server: host,
server: localHost,
account: account.id,
}), v, {}]] : [[makeScope({}), v, {}]];
} else if (isServerDependentKey(key as keyof typeof PREF_DEF)) {
data[key] = [[makeScope({
server: host,
server: localHost,
}), v, {}]];
} else {
data[key] = [[makeScope({}), v, {}]];
}
continue;
} else {
if (account && isAccountDependentKey(key as keyof typeof PREF_DEF) && !records.some(([scope]) => parseScope(scope).server === host && parseScope(scope).account === account.id)) {
if (account && isAccountDependentKey(key as keyof typeof PREF_DEF) && !records.some(([scope]) => parseScope(scope).server === localHost && parseScope(scope).account === account.id)) {
data[key] = records.concat([[makeScope({
server: host,
server: localHost,
account: account.id,
}), getInitialPrefValue(key as keyof typeof PREF_DEF), {}]]);
continue;
}
if (account && isServerDependentKey(key as keyof typeof PREF_DEF) && !records.some(([scope]) => parseScope(scope).server === host)) {
if (account && isServerDependentKey(key as keyof typeof PREF_DEF) && !records.some(([scope]) => parseScope(scope).server === localHost)) {
data[key] = records.concat([[makeScope({
server: host,
server: localHost,
}), getInitialPrefValue(key as keyof typeof PREF_DEF), {}]]);
continue;
}
@ -273,7 +273,7 @@ export class PreferencesManager extends EventEmitter<PreferencesManagerEvents> {
if (parseScope(record[0]).account == null && isAccountDependentKey(key) && currentAccount != null) {
this.profile.preferences[key].push([makeScope({
server: host,
server: localHost,
account: currentAccount.id,
}), v, {}]);
_save();
@ -282,7 +282,7 @@ export class PreferencesManager extends EventEmitter<PreferencesManagerEvents> {
if (parseScope(record[0]).server == null && isServerDependentKey(key)) {
this.profile.preferences[key].push([makeScope({
server: host,
server: localHost,
}), v, {}]);
_save();
return;
@ -401,10 +401,10 @@ export class PreferencesManager extends EventEmitter<PreferencesManagerEvents> {
return record;
}
const accountOverrideRecord = records.find(([scope, v]) => parseScope(scope).server === host && parseScope(scope).account === currentAccount.id);
const accountOverrideRecord = records.find(([scope, v]) => parseScope(scope).server === localHost && parseScope(scope).account === currentAccount.id);
if (accountOverrideRecord) return accountOverrideRecord;
const serverOverrideRecord = records.find(([scope, v]) => parseScope(scope).server === host && parseScope(scope).account == null);
const serverOverrideRecord = records.find(([scope, v]) => parseScope(scope).server === localHost && parseScope(scope).account == null);
if (serverOverrideRecord) return serverOverrideRecord;
const record = records.find(([scope, v]) => parseScope(scope).account == null);
@ -418,7 +418,7 @@ export class PreferencesManager extends EventEmitter<PreferencesManagerEvents> {
public isAccountOverrided<K extends keyof PREF>(key: K): boolean {
const currentAccount = this.currentAccount; // TSを黙らせるため
if (currentAccount == null) return false;
return this.profile.preferences[key].some(([scope, v]) => parseScope(scope).server === host && parseScope(scope).account === currentAccount.id);
return this.profile.preferences[key].some(([scope, v]) => parseScope(scope).server === localHost && parseScope(scope).account === currentAccount.id);
}
public setAccountOverride<K extends keyof PREF>(key: K) {
@ -429,7 +429,7 @@ export class PreferencesManager extends EventEmitter<PreferencesManagerEvents> {
const records = this.profile.preferences[key];
records.push([makeScope({
server: host,
server: localHost,
account: currentAccount.id,
}), this.s[key], {}]);
@ -443,7 +443,7 @@ export class PreferencesManager extends EventEmitter<PreferencesManagerEvents> {
const records = this.profile.preferences[key];
const index = records.findIndex(([scope, v]) => parseScope(scope).server === host && parseScope(scope).account === currentAccount.id);
const index = records.findIndex(([scope, v]) => parseScope(scope).server === localHost && parseScope(scope).account === currentAccount.id);
if (index === -1) return;
records.splice(index, 1);

View file

@ -140,10 +140,10 @@ export function getAbuseNoteMenu(note: Misskey.entities.Note, text: string): Men
icon: 'ti ti-exclamation-circle',
text,
action: async (): Promise<void> => {
const localUrl = `${url}/notes/${note.id}`;
const localHost = `${url}/notes/${note.id}`;
let noteInfo = '';
if (note.url ?? note.uri != null) noteInfo = `Note: ${note.url ?? note.uri}\n`;
noteInfo += `Local Note: ${localUrl}\n`;
noteInfo += `Local Note: ${localHost}\n`;
const { dispose } = await os.popupAsyncWithDialog(import('@/components/MkAbuseReportWindow.vue').then(x => x.default), {
user: note.user,
initialComment: `${noteInfo}-----\n`,

View file

@ -6,7 +6,7 @@
import { toUnicode } from 'punycode.js';
import { defineAsyncComponent, ref, watch } from 'vue';
import * as Misskey from 'misskey-js';
import { host, url } from '@@/js/config.js';
import { localHost, url } from '@@/js/config.js';
import type { Router } from '@/router.js';
import type { MenuItem } from '@/types/menu.js';
import { i18n } from '@/i18n.js';
@ -171,7 +171,7 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: Router
icon: 'ti ti-at',
text: i18n.ts.copyUsername,
action: () => {
copyToClipboard(`@${user.username}@${user.host ?? host}`);
copyToClipboard(`@${user.username}@${user.host ?? localHost}`);
},
});
@ -188,7 +188,7 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: Router
icon: 'ti ti-rss',
text: i18n.ts.copyRSS,
action: () => {
copyToClipboard(`${user.host ?? host}/@${user.username}.atom`);
copyToClipboard(`${user.host ?? url}/@${user.username}.atom`);
},
});