wip
This commit is contained in:
parent
583adec980
commit
048aa6f306
|
@ -20,7 +20,6 @@
|
||||||
"@twemoji/parser": "15.1.1",
|
"@twemoji/parser": "15.1.1",
|
||||||
"@vitejs/plugin-vue": "5.1.0",
|
"@vitejs/plugin-vue": "5.1.0",
|
||||||
"@vue/compiler-sfc": "3.4.37",
|
"@vue/compiler-sfc": "3.4.37",
|
||||||
"aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.11",
|
|
||||||
"astring": "1.8.6",
|
"astring": "1.8.6",
|
||||||
"buraha": "0.0.1",
|
"buraha": "0.0.1",
|
||||||
"compare-versions": "6.1.1",
|
"compare-versions": "6.1.1",
|
||||||
|
@ -30,7 +29,6 @@
|
||||||
"eventemitter3": "5.0.1",
|
"eventemitter3": "5.0.1",
|
||||||
"idb-keyval": "6.2.1",
|
"idb-keyval": "6.2.1",
|
||||||
"is-file-animated": "1.0.2",
|
"is-file-animated": "1.0.2",
|
||||||
"json5": "2.2.3",
|
|
||||||
"mfm-js": "0.24.0",
|
"mfm-js": "0.24.0",
|
||||||
"misskey-js": "workspace:*",
|
"misskey-js": "workspace:*",
|
||||||
"punycode": "2.3.1",
|
"punycode": "2.3.1",
|
||||||
|
|
|
@ -0,0 +1,85 @@
|
||||||
|
<!--
|
||||||
|
SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
-->
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div :class="$style.root" :style="bg">
|
||||||
|
<img v-if="faviconUrl" :class="$style.icon" :src="faviconUrl"/>
|
||||||
|
<div :class="$style.name">{{ instance.name }}</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import { instanceName } from '@/config.js';
|
||||||
|
import { instance as Instance } from '@/instance.js';
|
||||||
|
import { getProxiedImageUrlNullable } from '@/scripts/media-proxy.js';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
instance?: {
|
||||||
|
faviconUrl?: string | null
|
||||||
|
name?: string | null
|
||||||
|
themeColor?: string | null
|
||||||
|
}
|
||||||
|
}>();
|
||||||
|
|
||||||
|
// if no instance data is given, this is for the local instance
|
||||||
|
const instance = props.instance ?? {
|
||||||
|
name: instanceName,
|
||||||
|
themeColor: (document.querySelector('meta[name="theme-color-orig"]') as HTMLMetaElement).content,
|
||||||
|
};
|
||||||
|
|
||||||
|
const faviconUrl = computed(() => props.instance ? getProxiedImageUrlNullable(props.instance.faviconUrl, 'preview') : getProxiedImageUrlNullable(Instance.iconUrl, 'preview') ?? '/favicon.ico');
|
||||||
|
|
||||||
|
const themeColor = instance.themeColor ?? '#777777';
|
||||||
|
|
||||||
|
const bg = {
|
||||||
|
background: `linear-gradient(90deg, ${themeColor}, ${themeColor}00)`,
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" module>
|
||||||
|
$height: 2ex;
|
||||||
|
|
||||||
|
.root {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: $height;
|
||||||
|
border-radius: 4px 0 0 4px;
|
||||||
|
overflow: clip;
|
||||||
|
color: #fff;
|
||||||
|
text-shadow: /* .866 ≈ sin(60deg) */
|
||||||
|
1px 0 1px #000,
|
||||||
|
.866px .5px 1px #000,
|
||||||
|
.5px .866px 1px #000,
|
||||||
|
0 1px 1px #000,
|
||||||
|
-.5px .866px 1px #000,
|
||||||
|
-.866px .5px 1px #000,
|
||||||
|
-1px 0 1px #000,
|
||||||
|
-.866px -.5px 1px #000,
|
||||||
|
-.5px -.866px 1px #000,
|
||||||
|
0 -1px 1px #000,
|
||||||
|
.5px -.866px 1px #000,
|
||||||
|
.866px -.5px 1px #000;
|
||||||
|
mask-image: linear-gradient(90deg,
|
||||||
|
rgb(0,0,0),
|
||||||
|
rgb(0,0,0) calc(100% - 16px),
|
||||||
|
rgba(0,0,0,0) 100%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
height: $height;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
margin-left: 4px;
|
||||||
|
line-height: 1;
|
||||||
|
font-size: 0.9em;
|
||||||
|
font-weight: bold;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -129,13 +129,16 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
import { computed, inject, ref } from 'vue';
|
import { computed, inject, ref } from 'vue';
|
||||||
import * as mfm from 'mfm-js';
|
import * as mfm from 'mfm-js';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import EmMediaList from './EmMediaList.vue';
|
import EmMediaList from '@/components/EmMediaList.vue';
|
||||||
import EmNoteSub from '@/components/EmNoteSub.vue';
|
import EmNoteSub from '@/components/EmNoteSub.vue';
|
||||||
import EmNoteSimple from '@/components/EmNoteSimple.vue';
|
import EmNoteSimple from '@/components/EmNoteSimple.vue';
|
||||||
import EmReactionsViewer from '@/components/EmReactionsViewer.vue';
|
import EmReactionsViewer from '@/components/EmReactionsViewer.vue';
|
||||||
import EmCwButton from '@/components/EmCwButton.vue';
|
import EmCwButton from '@/components/EmCwButton.vue';
|
||||||
import EmPoll from '@/components/EmPoll.vue';
|
import EmPoll from '@/components/EmPoll.vue';
|
||||||
import EmInstanceTicker from '@/components/EmInstanceTicker.vue';
|
import EmInstanceTicker from '@/components/EmInstanceTicker.vue';
|
||||||
|
import EmA from '@/components/EmA.vue';
|
||||||
|
import EmAvatar from '@/components/EmAvatar.vue';
|
||||||
|
import EmTime from '@/components/EmTime.vue';
|
||||||
import { userPage } from '@/utils.js';
|
import { userPage } from '@/utils.js';
|
||||||
import { notePage } from '@/utils.js';
|
import { notePage } from '@/utils.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
|
|
@ -9,4 +9,5 @@ import { misskeyApi } from '@/misskey-api.js';
|
||||||
|
|
||||||
export const instance = {
|
export const instance = {
|
||||||
iconUrl: 'TODO',
|
iconUrl: 'TODO',
|
||||||
|
mediaProxy: 'TODO',
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { query } from './url.js';
|
||||||
|
import { url } from '@/config.js';
|
||||||
|
import { instance } from '@/instance.js';
|
||||||
|
|
||||||
|
export function getProxiedImageUrl(imageUrl: string, type?: 'preview' | 'emoji' | 'avatar', mustOrigin = false, noFallback = false): string {
|
||||||
|
const localProxy = `${url}/proxy`;
|
||||||
|
|
||||||
|
if (imageUrl.startsWith(instance.mediaProxy + '/') || imageUrl.startsWith('/proxy/') || imageUrl.startsWith(localProxy + '/')) {
|
||||||
|
// もう既にproxyっぽそうだったらurlを取り出す
|
||||||
|
imageUrl = (new URL(imageUrl)).searchParams.get('url') ?? imageUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${mustOrigin ? localProxy : instance.mediaProxy}/${
|
||||||
|
type === 'preview' ? 'preview.webp'
|
||||||
|
: 'image.webp'
|
||||||
|
}?${query({
|
||||||
|
url: imageUrl,
|
||||||
|
...(!noFallback ? { 'fallback': '1' } : {}),
|
||||||
|
...(type ? { [type]: '1' } : {}),
|
||||||
|
...(mustOrigin ? { origin: '1' } : {}),
|
||||||
|
})}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getProxiedImageUrlNullable(imageUrl: string | null | undefined, type?: 'preview'): string | null {
|
||||||
|
if (imageUrl == null) return null;
|
||||||
|
return getProxiedImageUrl(imageUrl, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getStaticImageUrl(baseUrl: string): string {
|
||||||
|
const u = baseUrl.startsWith('http') ? new URL(baseUrl) : new URL(baseUrl, url);
|
||||||
|
|
||||||
|
if (u.href.startsWith(`${url}/emoji/`)) {
|
||||||
|
// もう既にemojiっぽそうだったらsearchParams付けるだけ
|
||||||
|
u.searchParams.set('static', '1');
|
||||||
|
return u.href;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (u.href.startsWith(instance.mediaProxy + '/')) {
|
||||||
|
// もう既にproxyっぽそうだったらsearchParams付けるだけ
|
||||||
|
u.searchParams.set('static', '1');
|
||||||
|
return u.href;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${instance.mediaProxy}/static.webp?${query({
|
||||||
|
url: u.href,
|
||||||
|
static: '1',
|
||||||
|
})}`;
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* objを検査して
|
||||||
|
* 1. 配列に何も入っていない時はクエリを付けない
|
||||||
|
* 2. プロパティがundefinedの時はクエリを付けない
|
||||||
|
* (new URLSearchParams(obj)ではそこまで丁寧なことをしてくれない)
|
||||||
|
*/
|
||||||
|
export function query(obj: Record<string, any>): string {
|
||||||
|
const params = Object.entries(obj)
|
||||||
|
.filter(([, v]) => Array.isArray(v) ? v.length : v !== undefined)
|
||||||
|
.reduce((a, [k, v]) => (a[k] = v, a), {} as Record<string, any>);
|
||||||
|
|
||||||
|
return Object.entries(params)
|
||||||
|
.map((p) => `${p[0]}=${encodeURIComponent(p[1])}`)
|
||||||
|
.join('&');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function appendQuery(url: string, query: string): string {
|
||||||
|
return `${url}${/\?/.test(url) ? url.endsWith('?') ? '' : '&' : '?'}${query}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function extractDomain(url: string) {
|
||||||
|
const match = url.match(/^(?:https?:)?(?:\/\/)?(?:[^@\n]+@)?([^:\/\n]+)/im);
|
||||||
|
return match ? match[1] : null;
|
||||||
|
}
|
|
@ -5,7 +5,6 @@ import { type UserConfig, defineConfig } from 'vite';
|
||||||
import locales from '../../locales/index.js';
|
import locales from '../../locales/index.js';
|
||||||
import meta from '../../package.json';
|
import meta from '../../package.json';
|
||||||
import packageInfo from './package.json' with { type: 'json' };
|
import packageInfo from './package.json' with { type: 'json' };
|
||||||
import pluginJson5 from './vite.json5.js';
|
|
||||||
|
|
||||||
const extensions = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.json', '.json5', '.svg', '.sass', '.scss', '.css', '.vue'];
|
const extensions = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.json', '.json5', '.svg', '.sass', '.scss', '.css', '.vue'];
|
||||||
|
|
||||||
|
@ -67,7 +66,6 @@ export function getConfig(): UserConfig {
|
||||||
|
|
||||||
plugins: [
|
plugins: [
|
||||||
pluginVue(),
|
pluginVue(),
|
||||||
pluginJson5(),
|
|
||||||
],
|
],
|
||||||
|
|
||||||
resolve: {
|
resolve: {
|
||||||
|
|
|
@ -1,48 +0,0 @@
|
||||||
// Original: https://github.com/rollup/plugins/tree/8835dd2aed92f408d7dc72d7cc25a9728e16face/packages/json
|
|
||||||
|
|
||||||
import JSON5 from 'json5';
|
|
||||||
import { Plugin } from 'rollup';
|
|
||||||
import { createFilter, dataToEsm } from '@rollup/pluginutils';
|
|
||||||
import { RollupJsonOptions } from '@rollup/plugin-json';
|
|
||||||
|
|
||||||
// json5 extends SyntaxError with additional fields (without subclassing)
|
|
||||||
// https://github.com/json5/json5/blob/de344f0619bda1465a6e25c76f1c0c3dda8108d9/lib/parse.js#L1111-L1112
|
|
||||||
interface Json5SyntaxError extends SyntaxError {
|
|
||||||
lineNumber: number;
|
|
||||||
columnNumber: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function json5(options: RollupJsonOptions = {}): Plugin {
|
|
||||||
const filter = createFilter(options.include, options.exclude);
|
|
||||||
const indent = 'indent' in options ? options.indent : '\t';
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: 'json5',
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-shadow
|
|
||||||
transform(json, id) {
|
|
||||||
if (id.slice(-6) !== '.json5' || !filter(id)) return null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const parsed = JSON5.parse(json);
|
|
||||||
return {
|
|
||||||
code: dataToEsm(parsed, {
|
|
||||||
preferConst: options.preferConst,
|
|
||||||
compact: options.compact,
|
|
||||||
namedExports: options.namedExports,
|
|
||||||
indent,
|
|
||||||
}),
|
|
||||||
map: { mappings: '' },
|
|
||||||
};
|
|
||||||
} catch (err) {
|
|
||||||
if (!(err instanceof SyntaxError)) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
const message = 'Could not parse JSON5 file';
|
|
||||||
const { lineNumber, columnNumber } = err as Json5SyntaxError;
|
|
||||||
this.warn({ message, id, loc: { line: lineNumber, column: columnNumber } });
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
Loading…
Reference in New Issue