mirror of
				https://codeberg.org/yeentown/barkey.git
				synced 2025-10-31 21:44:12 +00:00 
			
		
		
		
	feat: google analytics (#15451)
* wip backend * wip frontend * build misskey-js * implement control panel * fix * introduce analytics wrapper * spdx * Update analytics.ts * Update common.ts * wip * wip * wip * wip * wip * Update CHANGELOG.md --------- Co-authored-by: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com>
This commit is contained in:
		
							parent
							
								
									20cc6d3049
								
							
						
					
					
						commit
						2b6638e160
					
				
					 15 changed files with 327 additions and 16 deletions
				
			
		|  | @ -2,6 +2,7 @@ | ||||||
| 
 | 
 | ||||||
| ### General | ### General | ||||||
| - Feat: アクセストークン発行時に通知するように | - Feat: アクセストークン発行時に通知するように | ||||||
|  | - Feat: 実験的なGoogleAnalyticsサポートを追加 | ||||||
| - 依存関係の更新 | - 依存関係の更新 | ||||||
| 
 | 
 | ||||||
| ### Client | ### Client | ||||||
|  |  | ||||||
							
								
								
									
										16
									
								
								packages/backend/migration/1739006797620-GoogleAnalytics.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								packages/backend/migration/1739006797620-GoogleAnalytics.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,16 @@ | ||||||
|  | /* | ||||||
|  |  * SPDX-FileCopyrightText: syuilo and misskey-project | ||||||
|  |  * SPDX-License-Identifier: AGPL-3.0-only | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | export class GoogleAnalytics1739006797620 { | ||||||
|  |     name = 'GoogleAnalytics1739006797620' | ||||||
|  | 
 | ||||||
|  |     async up(queryRunner) { | ||||||
|  |         await queryRunner.query(`ALTER TABLE "meta" ADD "googleAnalyticsMeasurementId" character varying(64)`); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async down(queryRunner) { | ||||||
|  |         await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "googleAnalyticsMeasurementId"`); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -97,6 +97,7 @@ export class MetaEntityService { | ||||||
| 			enableTurnstile: instance.enableTurnstile, | 			enableTurnstile: instance.enableTurnstile, | ||||||
| 			turnstileSiteKey: instance.turnstileSiteKey, | 			turnstileSiteKey: instance.turnstileSiteKey, | ||||||
| 			enableTestcaptcha: instance.enableTestcaptcha, | 			enableTestcaptcha: instance.enableTestcaptcha, | ||||||
|  | 			googleAnalyticsMeasurementId: instance.googleAnalyticsMeasurementId, | ||||||
| 			swPublickey: instance.swPublicKey, | 			swPublickey: instance.swPublicKey, | ||||||
| 			themeColor: instance.themeColor, | 			themeColor: instance.themeColor, | ||||||
| 			mascotImageUrl: instance.mascotImageUrl ?? '/assets/ai.png', | 			mascotImageUrl: instance.mascotImageUrl ?? '/assets/ai.png', | ||||||
|  |  | ||||||
|  | @ -658,4 +658,10 @@ export class MiMeta { | ||||||
| 		default: '{}', | 		default: '{}', | ||||||
| 	}) | 	}) | ||||||
| 	public federationHosts: string[]; | 	public federationHosts: string[]; | ||||||
|  | 
 | ||||||
|  | 	@Column('varchar', { | ||||||
|  | 		length: 64, | ||||||
|  | 		nullable: true, | ||||||
|  | 	}) | ||||||
|  | 	public googleAnalyticsMeasurementId: string | null; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -119,6 +119,10 @@ export const packedMetaLiteSchema = { | ||||||
| 			type: 'boolean', | 			type: 'boolean', | ||||||
| 			optional: false, nullable: false, | 			optional: false, nullable: false, | ||||||
| 		}, | 		}, | ||||||
|  | 		googleAnalyticsMeasurementId: { | ||||||
|  | 			type: 'string', | ||||||
|  | 			optional: false, nullable: true, | ||||||
|  | 		}, | ||||||
| 		swPublickey: { | 		swPublickey: { | ||||||
| 			type: 'string', | 			type: 'string', | ||||||
| 			optional: false, nullable: true, | 			optional: false, nullable: true, | ||||||
|  |  | ||||||
|  | @ -73,6 +73,10 @@ export const meta = { | ||||||
| 				type: 'boolean', | 				type: 'boolean', | ||||||
| 				optional: false, nullable: false, | 				optional: false, nullable: false, | ||||||
| 			}, | 			}, | ||||||
|  | 			googleAnalyticsMeasurementId: { | ||||||
|  | 				type: 'string', | ||||||
|  | 				optional: false, nullable: true, | ||||||
|  | 			}, | ||||||
| 			swPublickey: { | 			swPublickey: { | ||||||
| 				type: 'string', | 				type: 'string', | ||||||
| 				optional: false, nullable: true, | 				optional: false, nullable: true, | ||||||
|  | @ -572,6 +576,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||||
| 				enableTurnstile: instance.enableTurnstile, | 				enableTurnstile: instance.enableTurnstile, | ||||||
| 				turnstileSiteKey: instance.turnstileSiteKey, | 				turnstileSiteKey: instance.turnstileSiteKey, | ||||||
| 				enableTestcaptcha: instance.enableTestcaptcha, | 				enableTestcaptcha: instance.enableTestcaptcha, | ||||||
|  | 				googleAnalyticsMeasurementId: instance.googleAnalyticsMeasurementId, | ||||||
| 				swPublickey: instance.swPublicKey, | 				swPublickey: instance.swPublicKey, | ||||||
| 				themeColor: instance.themeColor, | 				themeColor: instance.themeColor, | ||||||
| 				mascotImageUrl: instance.mascotImageUrl, | 				mascotImageUrl: instance.mascotImageUrl, | ||||||
|  |  | ||||||
|  | @ -84,6 +84,7 @@ export const paramDef = { | ||||||
| 		turnstileSiteKey: { type: 'string', nullable: true }, | 		turnstileSiteKey: { type: 'string', nullable: true }, | ||||||
| 		turnstileSecretKey: { type: 'string', nullable: true }, | 		turnstileSecretKey: { type: 'string', nullable: true }, | ||||||
| 		enableTestcaptcha: { type: 'boolean' }, | 		enableTestcaptcha: { type: 'boolean' }, | ||||||
|  | 		googleAnalyticsMeasurementId: { type: 'string', nullable: true }, | ||||||
| 		sensitiveMediaDetection: { type: 'string', enum: ['none', 'all', 'local', 'remote'] }, | 		sensitiveMediaDetection: { type: 'string', enum: ['none', 'all', 'local', 'remote'] }, | ||||||
| 		sensitiveMediaDetectionSensitivity: { type: 'string', enum: ['medium', 'low', 'high', 'veryLow', 'veryHigh'] }, | 		sensitiveMediaDetectionSensitivity: { type: 'string', enum: ['medium', 'low', 'high', 'veryLow', 'veryHigh'] }, | ||||||
| 		setSensitiveFlagAutomatically: { type: 'boolean' }, | 		setSensitiveFlagAutomatically: { type: 'boolean' }, | ||||||
|  | @ -371,6 +372,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | ||||||
| 				set.enableTestcaptcha = ps.enableTestcaptcha; | 				set.enableTestcaptcha = ps.enableTestcaptcha; | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
|  | 			if (ps.googleAnalyticsMeasurementId !== undefined) { | ||||||
|  | 				// 空文字列をnullにしたいので??は使わない
 | ||||||
|  | 				// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
 | ||||||
|  | 				set.googleAnalyticsMeasurementId = ps.googleAnalyticsMeasurementId || null; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
| 			if (ps.sensitiveMediaDetection !== undefined) { | 			if (ps.sensitiveMediaDetection !== undefined) { | ||||||
| 				set.sensitiveMediaDetection = ps.sensitiveMediaDetection; | 				set.sensitiveMediaDetection = ps.sensitiveMediaDetection; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
|  | @ -16,6 +16,7 @@ | ||||||
| 		"lint": "pnpm typecheck && pnpm eslint" | 		"lint": "pnpm typecheck && pnpm eslint" | ||||||
| 	}, | 	}, | ||||||
| 	"dependencies": { | 	"dependencies": { | ||||||
|  | 		"@analytics/google-analytics": "1.1.0", | ||||||
| 		"@discordapp/twemoji": "15.1.0", | 		"@discordapp/twemoji": "15.1.0", | ||||||
| 		"@github/webauthn-json": "2.1.1", | 		"@github/webauthn-json": "2.1.1", | ||||||
| 		"@mcaptcha/vanilla-glue": "0.1.0-alpha-3", | 		"@mcaptcha/vanilla-glue": "0.1.0-alpha-3", | ||||||
|  | @ -29,6 +30,7 @@ | ||||||
| 		"@vitejs/plugin-vue": "5.2.1", | 		"@vitejs/plugin-vue": "5.2.1", | ||||||
| 		"@vue/compiler-sfc": "3.5.13", | 		"@vue/compiler-sfc": "3.5.13", | ||||||
| 		"aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.15", | 		"aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.15", | ||||||
|  | 		"analytics": "0.8.16", | ||||||
| 		"astring": "1.9.0", | 		"astring": "1.9.0", | ||||||
| 		"broadcast-channel": "7.0.0", | 		"broadcast-channel": "7.0.0", | ||||||
| 		"buraha": "0.0.1", | 		"buraha": "0.0.1", | ||||||
|  |  | ||||||
							
								
								
									
										107
									
								
								packages/frontend/src/analytics.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								packages/frontend/src/analytics.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,107 @@ | ||||||
|  | /* | ||||||
|  |  * SPDX-FileCopyrightText: syuilo and misskey-project | ||||||
|  |  * SPDX-License-Identifier: AGPL-3.0-only | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | import * as Misskey from 'misskey-js'; | ||||||
|  | import type { AnalyticsInstance, AnalyticsPlugin } from 'analytics'; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * analytics moduleを読み込まなくても動作するようにするためのラッパー | ||||||
|  |  */ | ||||||
|  | class AnalyticsProxy implements AnalyticsInstance { | ||||||
|  | 	private analytics?: AnalyticsInstance; | ||||||
|  | 
 | ||||||
|  | 	constructor(analytics?: AnalyticsInstance) { | ||||||
|  | 		if (analytics) { | ||||||
|  | 			this.analytics = analytics; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public setAnalytics(analytics: AnalyticsInstance) { | ||||||
|  | 		if (this.analytics) { | ||||||
|  | 			throw new Error('Analytics instance already exists.'); | ||||||
|  | 		} | ||||||
|  | 		this.analytics = analytics; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public identify(...args: Parameters<AnalyticsInstance['identify']>) { | ||||||
|  | 		return this.analytics?.identify(...args) ?? Promise.resolve(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public track(...args: Parameters<AnalyticsInstance['track']>) { | ||||||
|  | 		return this.analytics?.track(...args) ?? Promise.resolve(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public page(...args: Parameters<AnalyticsInstance['page']>) { | ||||||
|  | 		return this.analytics?.page(...args) ?? Promise.resolve(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public user(...args: Parameters<AnalyticsInstance['user']>) { | ||||||
|  | 		return this.analytics?.user(...args) ?? Promise.resolve(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public reset(...args: Parameters<AnalyticsInstance['reset']>) { | ||||||
|  | 		return this.analytics?.reset(...args) ?? Promise.resolve(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public ready(...args: Parameters<AnalyticsInstance['ready']>) { | ||||||
|  | 		return this.analytics?.ready(...args) ?? function () { void 0; }; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public on(...args: Parameters<AnalyticsInstance['on']>) { | ||||||
|  | 		return this.analytics?.on(...args) ?? function () { void 0; }; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public once(...args: Parameters<AnalyticsInstance['once']>) { | ||||||
|  | 		return this.analytics?.once(...args) ?? function () { void 0; }; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public getState(...args: Parameters<AnalyticsInstance['getState']>) { | ||||||
|  | 		return this.analytics?.getState(...args) ?? Promise.resolve(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public get storage() { | ||||||
|  | 		return this.analytics?.storage ?? { | ||||||
|  | 			getItem: () => null, | ||||||
|  | 			setItem: () => void 0, | ||||||
|  | 			removeItem: () => void 0, | ||||||
|  | 		}; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public get plugins() { | ||||||
|  | 		return this.analytics?.plugins ?? { | ||||||
|  | 			enable: (p, c) => Promise.resolve(c ? c() : void 0), | ||||||
|  | 			disable: (p, c) => Promise.resolve(c ? c() : void 0), | ||||||
|  | 		}; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export const analytics = new AnalyticsProxy(); | ||||||
|  | 
 | ||||||
|  | export async function initAnalytics(instance: Misskey.entities.MetaDetailed) { | ||||||
|  | 	// アナリティクスプロバイダに関する設定がひとつもない場合は、アナリティクスモジュールを読み込まない
 | ||||||
|  | 	if (!instance.googleAnalyticsMeasurementId) { | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	const { default: Analytics } = await import('analytics'); | ||||||
|  | 	const plugins: AnalyticsPlugin[] = []; | ||||||
|  | 
 | ||||||
|  | 	// Google Analytics
 | ||||||
|  | 	if (instance.googleAnalyticsMeasurementId) { | ||||||
|  | 		const { default: googleAnalytics } = await import('@analytics/google-analytics'); | ||||||
|  | 
 | ||||||
|  | 		plugins.push(googleAnalytics({ | ||||||
|  | 			measurementIds: [instance.googleAnalyticsMeasurementId], | ||||||
|  | 			debug: _DEV_, | ||||||
|  | 		})); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	analytics.setAnalytics(Analytics({ | ||||||
|  | 		app: 'misskey', | ||||||
|  | 		version: _VERSION_, | ||||||
|  | 		debug: _DEV_, | ||||||
|  | 		plugins, | ||||||
|  | 	})); | ||||||
|  | } | ||||||
|  | @ -4,9 +4,9 @@ | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| import { computed, watch, version as vueVersion } from 'vue'; | import { computed, watch, version as vueVersion } from 'vue'; | ||||||
| import type { App } from 'vue'; |  | ||||||
| import { compareVersions } from 'compare-versions'; | import { compareVersions } from 'compare-versions'; | ||||||
| import { version, lang, updateLocale, locale } from '@@/js/config.js'; | import { version, lang, updateLocale, locale } from '@@/js/config.js'; | ||||||
|  | import type { App } from 'vue'; | ||||||
| import widgets from '@/widgets/index.js'; | import widgets from '@/widgets/index.js'; | ||||||
| import directives from '@/directives/index.js'; | import directives from '@/directives/index.js'; | ||||||
| import components from '@/components/index.js'; | import components from '@/components/index.js'; | ||||||
|  | @ -21,6 +21,7 @@ import { reloadChannel } from '@/scripts/unison-reload.js'; | ||||||
| import { getUrlWithoutLoginId } from '@/scripts/login-id.js'; | import { getUrlWithoutLoginId } from '@/scripts/login-id.js'; | ||||||
| import { getAccountFromId } from '@/scripts/get-account-from-id.js'; | import { getAccountFromId } from '@/scripts/get-account-from-id.js'; | ||||||
| import { deckStore } from '@/ui/deck/deck-store.js'; | import { deckStore } from '@/ui/deck/deck-store.js'; | ||||||
|  | import { analytics, initAnalytics } from '@/analytics.js'; | ||||||
| import { miLocalStorage } from '@/local-storage.js'; | import { miLocalStorage } from '@/local-storage.js'; | ||||||
| import { fetchCustomEmojis } from '@/custom-emojis.js'; | import { fetchCustomEmojis } from '@/custom-emojis.js'; | ||||||
| import { setupRouter } from '@/router/main.js'; | import { setupRouter } from '@/router/main.js'; | ||||||
|  | @ -241,6 +242,19 @@ export async function common(createVue: () => App<Element>) { | ||||||
| 		await fetchCustomEmojis(); | 		await fetchCustomEmojis(); | ||||||
| 	} catch (err) { /* empty */ } | 	} catch (err) { /* empty */ } | ||||||
| 
 | 
 | ||||||
|  | 	// analytics
 | ||||||
|  | 	fetchInstanceMetaPromise.then(async () => { | ||||||
|  | 		await initAnalytics(instance); | ||||||
|  | 
 | ||||||
|  | 		if ($i) { | ||||||
|  | 			analytics.identify($i.id); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		analytics.page({ | ||||||
|  | 			path: window.location.pathname, | ||||||
|  | 		}); | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
| 	const app = createVue(); | 	const app = createVue(); | ||||||
| 
 | 
 | ||||||
| 	setupRouter(app, createMainRouter); | 	setupRouter(app, createMainRouter); | ||||||
|  |  | ||||||
|  | @ -32,6 +32,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||||
| import { computed, onMounted, onUnmounted, provide, ref, shallowRef } from 'vue'; | import { computed, onMounted, onUnmounted, provide, ref, shallowRef } from 'vue'; | ||||||
| import { url } from '@@/js/config.js'; | import { url } from '@@/js/config.js'; | ||||||
| import { getScrollContainer } from '@@/js/scroll.js'; | import { getScrollContainer } from '@@/js/scroll.js'; | ||||||
|  | import type { PageMetadata } from '@/scripts/page-metadata.js'; | ||||||
| import RouterView from '@/components/global/RouterView.vue'; | import RouterView from '@/components/global/RouterView.vue'; | ||||||
| import MkWindow from '@/components/MkWindow.vue'; | import MkWindow from '@/components/MkWindow.vue'; | ||||||
| import { popout as _popout } from '@/scripts/popout.js'; | import { popout as _popout } from '@/scripts/popout.js'; | ||||||
|  | @ -39,11 +40,11 @@ import { copyToClipboard } from '@/scripts/copy-to-clipboard.js'; | ||||||
| import { useScrollPositionManager } from '@/nirax.js'; | import { useScrollPositionManager } from '@/nirax.js'; | ||||||
| import { i18n } from '@/i18n.js'; | import { i18n } from '@/i18n.js'; | ||||||
| import { provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js'; | import { provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js'; | ||||||
| import type { PageMetadata } from '@/scripts/page-metadata.js'; |  | ||||||
| import { openingWindowsCount } from '@/os.js'; | import { openingWindowsCount } from '@/os.js'; | ||||||
| import { claimAchievement } from '@/scripts/achievements.js'; | import { claimAchievement } from '@/scripts/achievements.js'; | ||||||
| import { useRouterFactory } from '@/router/supplier.js'; | import { useRouterFactory } from '@/router/supplier.js'; | ||||||
| import { mainRouter } from '@/router/main.js'; | import { mainRouter } from '@/router/main.js'; | ||||||
|  | import { analytics } from '@/analytics.js'; | ||||||
| 
 | 
 | ||||||
| const props = defineProps<{ | const props = defineProps<{ | ||||||
| 	initialPath: string; | 	initialPath: string; | ||||||
|  | @ -99,6 +100,14 @@ windowRouter.addListener('replace', ctx => { | ||||||
| 	history.value.push({ path: ctx.path, key: ctx.key }); | 	history.value.push({ path: ctx.path, key: ctx.key }); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
|  | windowRouter.addListener('change', ctx => { | ||||||
|  | 	console.log('windowRouter: change', ctx.path); | ||||||
|  | 	analytics.page({ | ||||||
|  | 		path: ctx.path, | ||||||
|  | 		title: ctx.path, | ||||||
|  | 	}); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
| windowRouter.init(); | windowRouter.init(); | ||||||
| 
 | 
 | ||||||
| provide('router', windowRouter); | provide('router', windowRouter); | ||||||
|  | @ -160,6 +169,11 @@ function popout() { | ||||||
| useScrollPositionManager(() => getScrollContainer(contents.value), windowRouter); | useScrollPositionManager(() => getScrollContainer(contents.value), windowRouter); | ||||||
| 
 | 
 | ||||||
| onMounted(() => { | onMounted(() => { | ||||||
|  | 	analytics.page({ | ||||||
|  | 		path: props.initialPath, | ||||||
|  | 		title: props.initialPath, | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
| 	openingWindowsCount.value++; | 	openingWindowsCount.value++; | ||||||
| 	if (openingWindowsCount.value >= 3) { | 	if (openingWindowsCount.value >= 3) { | ||||||
| 		claimAchievement('open3windows'); | 		claimAchievement('open3windows'); | ||||||
|  |  | ||||||
|  | @ -8,20 +8,34 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||||
| 	<template #header><XHeader :actions="headerActions" :tabs="headerTabs"/></template> | 	<template #header><XHeader :actions="headerActions" :tabs="headerTabs"/></template> | ||||||
| 	<MkSpacer :contentMax="700" :marginMin="16" :marginMax="32"> | 	<MkSpacer :contentMax="700" :marginMin="16" :marginMax="32"> | ||||||
| 		<FormSuspense :p="init"> | 		<FormSuspense :p="init"> | ||||||
| 			<MkFolder> | 			<div class="_gaps_m"> | ||||||
| 				<template #label>DeepL Translation</template> | 				<MkFolder> | ||||||
|  | 					<template #label>Google Analytics<span class="_beta">{{ i18n.ts.beta }}</span></template> | ||||||
| 
 | 
 | ||||||
| 				<div class="_gaps_m"> | 					<div class="_gaps_m"> | ||||||
| 					<MkInput v-model="deeplAuthKey"> | 						<MkInput v-model="googleAnalyticsMeasurementId"> | ||||||
| 						<template #prefix><i class="ti ti-key"></i></template> | 							<template #prefix><i class="ti ti-key"></i></template> | ||||||
| 						<template #label>DeepL Auth Key</template> | 							<template #label>Measurement ID</template> | ||||||
| 					</MkInput> | 						</MkInput> | ||||||
| 					<MkSwitch v-model="deeplIsPro"> | 						<MkButton primary @click="save_googleAnalytics">Save</MkButton> | ||||||
| 						<template #label>Pro account</template> | 					</div> | ||||||
| 					</MkSwitch> | 				</MkFolder> | ||||||
| 					<MkButton primary @click="save_deepl">Save</MkButton> | 
 | ||||||
| 				</div> | 				<MkFolder> | ||||||
| 			</MkFolder> | 					<template #label>DeepL Translation</template> | ||||||
|  | 
 | ||||||
|  | 					<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> | ||||||
|  | 			</div> | ||||||
| 		</FormSuspense> | 		</FormSuspense> | ||||||
| 	</MkSpacer> | 	</MkSpacer> | ||||||
| </MkStickyContainer> | </MkStickyContainer> | ||||||
|  | @ -44,10 +58,13 @@ import MkFolder from '@/components/MkFolder.vue'; | ||||||
| const deeplAuthKey = ref<string>(''); | const deeplAuthKey = ref<string>(''); | ||||||
| const deeplIsPro = ref<boolean>(false); | const deeplIsPro = ref<boolean>(false); | ||||||
| 
 | 
 | ||||||
|  | const googleAnalyticsMeasurementId = ref<string>(''); | ||||||
|  | 
 | ||||||
| async function init() { | async function init() { | ||||||
| 	const meta = await misskeyApi('admin/meta'); | 	const meta = await misskeyApi('admin/meta'); | ||||||
| 	deeplAuthKey.value = meta.deeplAuthKey; | 	deeplAuthKey.value = meta.deeplAuthKey ?? ''; | ||||||
| 	deeplIsPro.value = meta.deeplIsPro; | 	deeplIsPro.value = meta.deeplIsPro; | ||||||
|  | 	googleAnalyticsMeasurementId.value = meta.googleAnalyticsMeasurementId ?? ''; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function save_deepl() { | function save_deepl() { | ||||||
|  | @ -59,6 +76,14 @@ function save_deepl() { | ||||||
| 	}); | 	}); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | function save_googleAnalytics() { | ||||||
|  | 	os.apiWithDialog('admin/update-meta', { | ||||||
|  | 		googleAnalyticsMeasurementId: googleAnalyticsMeasurementId.value, | ||||||
|  | 	}).then(() => { | ||||||
|  | 		fetchInstance(true); | ||||||
|  | 	}); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| const headerActions = computed(() => []); | const headerActions = computed(() => []); | ||||||
| 
 | 
 | ||||||
| const headerTabs = computed(() => []); | const headerTabs = computed(() => []); | ||||||
|  |  | ||||||
|  | @ -7,6 +7,7 @@ import { EventEmitter } from 'eventemitter3'; | ||||||
| import type { IRouter, Resolved, RouteDef, RouterEvent, RouterFlag } from '@/nirax.js'; | import type { IRouter, Resolved, RouteDef, RouterEvent, RouterFlag } from '@/nirax.js'; | ||||||
| 
 | 
 | ||||||
| import type { App, ShallowRef } from 'vue'; | import type { App, ShallowRef } from 'vue'; | ||||||
|  | import { analytics } from '@/analytics.js'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * {@link Router}による画面遷移を可能とするために{@link mainRouter}をセットアップする。 |  * {@link Router}による画面遷移を可能とするために{@link mainRouter}をセットアップする。 | ||||||
|  | @ -29,6 +30,14 @@ export function setupRouter(app: App, routerFactory: ((path: string) => IRouter) | ||||||
| 		window.history.replaceState({ key: ctx.key }, '', ctx.path); | 		window.history.replaceState({ key: ctx.key }, '', ctx.path); | ||||||
| 	}); | 	}); | ||||||
| 
 | 
 | ||||||
|  | 	mainRouter.addListener('change', ctx => { | ||||||
|  | 		console.log('mainRouter: change', ctx.path); | ||||||
|  | 		analytics.page({ | ||||||
|  | 			path: ctx.path, | ||||||
|  | 			title: ctx.path, | ||||||
|  | 		}); | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
| 	mainRouter.init(); | 	mainRouter.init(); | ||||||
| 
 | 
 | ||||||
| 	setMainRouter(mainRouter); | 	setMainRouter(mainRouter); | ||||||
|  |  | ||||||
|  | @ -5042,6 +5042,7 @@ export type components = { | ||||||
|       enableTurnstile: boolean; |       enableTurnstile: boolean; | ||||||
|       turnstileSiteKey: string | null; |       turnstileSiteKey: string | null; | ||||||
|       enableTestcaptcha: boolean; |       enableTestcaptcha: boolean; | ||||||
|  |       googleAnalyticsMeasurementId: string | null; | ||||||
|       swPublickey: string | null; |       swPublickey: string | null; | ||||||
|       /** @default /assets/ai.png */ |       /** @default /assets/ai.png */ | ||||||
|       mascotImageUrl: string; |       mascotImageUrl: string; | ||||||
|  | @ -8251,6 +8252,7 @@ export type operations = { | ||||||
|             enableTurnstile: boolean; |             enableTurnstile: boolean; | ||||||
|             turnstileSiteKey: string | null; |             turnstileSiteKey: string | null; | ||||||
|             enableTestcaptcha: boolean; |             enableTestcaptcha: boolean; | ||||||
|  |             googleAnalyticsMeasurementId: string | null; | ||||||
|             swPublickey: string | null; |             swPublickey: string | null; | ||||||
|             /** @default /assets/ai.png */ |             /** @default /assets/ai.png */ | ||||||
|             mascotImageUrl: string | null; |             mascotImageUrl: string | null; | ||||||
|  | @ -10617,6 +10619,7 @@ export type operations = { | ||||||
|           turnstileSiteKey?: string | null; |           turnstileSiteKey?: string | null; | ||||||
|           turnstileSecretKey?: string | null; |           turnstileSecretKey?: string | null; | ||||||
|           enableTestcaptcha?: boolean; |           enableTestcaptcha?: boolean; | ||||||
|  |           googleAnalyticsMeasurementId?: string | null; | ||||||
|           /** @enum {string} */ |           /** @enum {string} */ | ||||||
|           sensitiveMediaDetection?: 'none' | 'all' | 'local' | 'remote'; |           sensitiveMediaDetection?: 'none' | 'all' | 'local' | 'remote'; | ||||||
|           /** @enum {string} */ |           /** @enum {string} */ | ||||||
|  |  | ||||||
							
								
								
									
										97
									
								
								pnpm-lock.yaml
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										97
									
								
								pnpm-lock.yaml
									
										
									
										generated
									
									
									
								
							|  | @ -685,6 +685,9 @@ importers: | ||||||
| 
 | 
 | ||||||
|   packages/frontend: |   packages/frontend: | ||||||
|     dependencies: |     dependencies: | ||||||
|  |       '@analytics/google-analytics': | ||||||
|  |         specifier: 1.1.0 | ||||||
|  |         version: 1.1.0 | ||||||
|       '@discordapp/twemoji': |       '@discordapp/twemoji': | ||||||
|         specifier: 15.1.0 |         specifier: 15.1.0 | ||||||
|         version: 15.1.0 |         version: 15.1.0 | ||||||
|  | @ -724,6 +727,9 @@ importers: | ||||||
|       aiscript-vscode: |       aiscript-vscode: | ||||||
|         specifier: github:aiscript-dev/aiscript-vscode#v0.1.15 |         specifier: github:aiscript-dev/aiscript-vscode#v0.1.15 | ||||||
|         version: https://codeload.github.com/aiscript-dev/aiscript-vscode/tar.gz/c3cde89e79a41d93540cf8a48cd619c3f2dcb1b7 |         version: https://codeload.github.com/aiscript-dev/aiscript-vscode/tar.gz/c3cde89e79a41d93540cf8a48cd619c3f2dcb1b7 | ||||||
|  |       analytics: | ||||||
|  |         specifier: 0.8.16 | ||||||
|  |         version: 0.8.16(@types/dlv@1.1.5) | ||||||
|       astring: |       astring: | ||||||
|         specifier: 1.9.0 |         specifier: 1.9.0 | ||||||
|         version: 1.9.0 |         version: 1.9.0 | ||||||
|  | @ -1448,6 +1454,30 @@ packages: | ||||||
|     resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} |     resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} | ||||||
|     engines: {node: '>=6.0.0'} |     engines: {node: '>=6.0.0'} | ||||||
| 
 | 
 | ||||||
|  |   '@analytics/cookie-utils@0.2.12': | ||||||
|  |     resolution: {integrity: sha512-2h/yuIu3kmu+ZJlKmlT6GoRvUEY2k1BbQBezEv5kGhnn9KpmzPz715Y3GmM2i+m7Y0QmBdVUoA260dQZkofs2A==} | ||||||
|  | 
 | ||||||
|  |   '@analytics/core@0.12.17': | ||||||
|  |     resolution: {integrity: sha512-GMxRm5Dp3Wam/w5NNvqNKMO6zWecozbVv21Kn4WhftCx6OjJI7zMlVtiLpjGjxa0RRZfVG80YhupF0Qh9XL2gw==} | ||||||
|  | 
 | ||||||
|  |   '@analytics/global-storage-utils@0.1.7': | ||||||
|  |     resolution: {integrity: sha512-V+spzGLZYm4biZT4uefaylm80SrLXf8WOTv9hCgA46cLcyxx3LD4GCpssp1lj+RcWLl/uXJQBRO4Mnn/o1x6Gw==} | ||||||
|  | 
 | ||||||
|  |   '@analytics/google-analytics@1.1.0': | ||||||
|  |     resolution: {integrity: sha512-i8uGyELMtwEUAf3GNWNLNBzhRvReDn1RUxvMdMhjUA7+GNGxPOM4kkzFfv3giQXKNxTEjfsh75kqNcscbJsuaA==} | ||||||
|  | 
 | ||||||
|  |   '@analytics/localstorage-utils@0.1.10': | ||||||
|  |     resolution: {integrity: sha512-uJS+Jp1yLG5VFCgA5T82ZODYBS0xuDQx0NtAZrgbqt9j51BX3TcgmOez5LVkrUNu/lpbxjCLq35I4TKj78VmOQ==} | ||||||
|  | 
 | ||||||
|  |   '@analytics/session-storage-utils@0.0.7': | ||||||
|  |     resolution: {integrity: sha512-PSv40UxG96HVcjY15e3zOqU2n8IqXnH8XvTkg1X43uXNTKVSebiI2kUjA3Q7ESFbw5DPwcLbJhV7GforpuBLDw==} | ||||||
|  | 
 | ||||||
|  |   '@analytics/storage-utils@0.4.2': | ||||||
|  |     resolution: {integrity: sha512-AXObwyVQw9h2uJh1t2hUgabtVxzYpW+7uKVbdHQK80vr3Td5rrmCxrCxarh7HUuAgSDZ0bZWqmYxVgmwKceaLg==} | ||||||
|  | 
 | ||||||
|  |   '@analytics/type-utils@0.6.2': | ||||||
|  |     resolution: {integrity: sha512-TD+xbmsBLyYy/IxFimW/YL/9L2IEnM7/EoV9Aeh56U64Ify8o27HJcKjo38XY9Tcn0uOq1AX3thkKgvtWvwFQg==} | ||||||
|  | 
 | ||||||
|   '@apidevtools/swagger-methods@3.0.2': |   '@apidevtools/swagger-methods@3.0.2': | ||||||
|     resolution: {integrity: sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==} |     resolution: {integrity: sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==} | ||||||
| 
 | 
 | ||||||
|  | @ -4226,6 +4256,9 @@ packages: | ||||||
|   '@types/disposable-email-domains@1.0.2': |   '@types/disposable-email-domains@1.0.2': | ||||||
|     resolution: {integrity: sha512-SDKwyYTjk3y5aZBxxc38yRecpJPjsqn57STz1bNxYYlv4k11bBe7QB8w4llXDTmQXKT1mFvgGmJv+8Zdu3YmJw==} |     resolution: {integrity: sha512-SDKwyYTjk3y5aZBxxc38yRecpJPjsqn57STz1bNxYYlv4k11bBe7QB8w4llXDTmQXKT1mFvgGmJv+8Zdu3YmJw==} | ||||||
| 
 | 
 | ||||||
|  |   '@types/dlv@1.1.5': | ||||||
|  |     resolution: {integrity: sha512-JHOWNfiWepAhfwlSw17kiWrWrk6od2dEQgHltJw9AS0JPFoLZJBge5+Dnil2NfdjAvJ/+vGSX60/BRW20PpUXw==} | ||||||
|  | 
 | ||||||
|   '@types/doctrine@0.0.9': |   '@types/doctrine@0.0.9': | ||||||
|     resolution: {integrity: sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA==} |     resolution: {integrity: sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA==} | ||||||
| 
 | 
 | ||||||
|  | @ -4864,6 +4897,14 @@ packages: | ||||||
|   alien-signals@1.0.3: |   alien-signals@1.0.3: | ||||||
|     resolution: {integrity: sha512-zQOh3wAYK5ujENxvBBR3CFGF/b6afaSzZ/c9yNhJ1ENrGHETvpUuKQsa93Qrclp0+PzTF93MaZ7scVp1uUozhA==} |     resolution: {integrity: sha512-zQOh3wAYK5ujENxvBBR3CFGF/b6afaSzZ/c9yNhJ1ENrGHETvpUuKQsa93Qrclp0+PzTF93MaZ7scVp1uUozhA==} | ||||||
| 
 | 
 | ||||||
|  |   analytics-utils@1.0.14: | ||||||
|  |     resolution: {integrity: sha512-9v0kPd8v0GuBvfQcg5BO48AElaEAr9IXMAfJWXYMAhrD3QprgozEIUgMp/de0vS136PUOBB+10XQH9eBgBmfMw==} | ||||||
|  |     peerDependencies: | ||||||
|  |       '@types/dlv': ^1.0.0 | ||||||
|  | 
 | ||||||
|  |   analytics@0.8.16: | ||||||
|  |     resolution: {integrity: sha512-LEFQ47G9V1zVp9WIh2xhnbmSFEJq+WEzSv6voJ5uba88lefiIIYeG2nq87gFu83ocz1qtb9u7XgeaKKVBbbgWA==} | ||||||
|  | 
 | ||||||
|   ansi-colors@4.1.3: |   ansi-colors@4.1.3: | ||||||
|     resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} |     resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} | ||||||
|     engines: {node: '>=6'} |     engines: {node: '>=6'} | ||||||
|  | @ -5947,6 +5988,9 @@ packages: | ||||||
|   disposable-email-domains@1.0.62: |   disposable-email-domains@1.0.62: | ||||||
|     resolution: {integrity: sha512-LBQvhRw7mznQTPoyZbsmYeNOZt1pN5aCsx4BAU/3siVFuiM9f2oyKzUaB8v1jbxFjE3aYqYiMo63kAL4pHgfWQ==} |     resolution: {integrity: sha512-LBQvhRw7mznQTPoyZbsmYeNOZt1pN5aCsx4BAU/3siVFuiM9f2oyKzUaB8v1jbxFjE3aYqYiMo63kAL4pHgfWQ==} | ||||||
| 
 | 
 | ||||||
|  |   dlv@1.1.3: | ||||||
|  |     resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} | ||||||
|  | 
 | ||||||
|   doctrine@2.1.0: |   doctrine@2.1.0: | ||||||
|     resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} |     resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} | ||||||
|     engines: {node: '>=0.10.0'} |     engines: {node: '>=0.10.0'} | ||||||
|  | @ -10953,6 +10997,42 @@ snapshots: | ||||||
|       '@jridgewell/gen-mapping': 0.3.5 |       '@jridgewell/gen-mapping': 0.3.5 | ||||||
|       '@jridgewell/trace-mapping': 0.3.25 |       '@jridgewell/trace-mapping': 0.3.25 | ||||||
| 
 | 
 | ||||||
|  |   '@analytics/cookie-utils@0.2.12': | ||||||
|  |     dependencies: | ||||||
|  |       '@analytics/global-storage-utils': 0.1.7 | ||||||
|  | 
 | ||||||
|  |   '@analytics/core@0.12.17(@types/dlv@1.1.5)': | ||||||
|  |     dependencies: | ||||||
|  |       '@analytics/global-storage-utils': 0.1.7 | ||||||
|  |       '@analytics/type-utils': 0.6.2 | ||||||
|  |       analytics-utils: 1.0.14(@types/dlv@1.1.5) | ||||||
|  |     transitivePeerDependencies: | ||||||
|  |       - '@types/dlv' | ||||||
|  | 
 | ||||||
|  |   '@analytics/global-storage-utils@0.1.7': | ||||||
|  |     dependencies: | ||||||
|  |       '@analytics/type-utils': 0.6.2 | ||||||
|  | 
 | ||||||
|  |   '@analytics/google-analytics@1.1.0': {} | ||||||
|  | 
 | ||||||
|  |   '@analytics/localstorage-utils@0.1.10': | ||||||
|  |     dependencies: | ||||||
|  |       '@analytics/global-storage-utils': 0.1.7 | ||||||
|  | 
 | ||||||
|  |   '@analytics/session-storage-utils@0.0.7': | ||||||
|  |     dependencies: | ||||||
|  |       '@analytics/global-storage-utils': 0.1.7 | ||||||
|  | 
 | ||||||
|  |   '@analytics/storage-utils@0.4.2': | ||||||
|  |     dependencies: | ||||||
|  |       '@analytics/cookie-utils': 0.2.12 | ||||||
|  |       '@analytics/global-storage-utils': 0.1.7 | ||||||
|  |       '@analytics/localstorage-utils': 0.1.10 | ||||||
|  |       '@analytics/session-storage-utils': 0.0.7 | ||||||
|  |       '@analytics/type-utils': 0.6.2 | ||||||
|  | 
 | ||||||
|  |   '@analytics/type-utils@0.6.2': {} | ||||||
|  | 
 | ||||||
|   '@apidevtools/swagger-methods@3.0.2': {} |   '@apidevtools/swagger-methods@3.0.2': {} | ||||||
| 
 | 
 | ||||||
|   '@asamuzakjp/css-color@2.8.3': |   '@asamuzakjp/css-color@2.8.3': | ||||||
|  | @ -14499,6 +14579,8 @@ snapshots: | ||||||
| 
 | 
 | ||||||
|   '@types/disposable-email-domains@1.0.2': {} |   '@types/disposable-email-domains@1.0.2': {} | ||||||
| 
 | 
 | ||||||
|  |   '@types/dlv@1.1.5': {} | ||||||
|  | 
 | ||||||
|   '@types/doctrine@0.0.9': {} |   '@types/doctrine@0.0.9': {} | ||||||
| 
 | 
 | ||||||
|   '@types/eslint@7.29.0': |   '@types/eslint@7.29.0': | ||||||
|  | @ -15312,6 +15394,19 @@ snapshots: | ||||||
| 
 | 
 | ||||||
|   alien-signals@1.0.3: {} |   alien-signals@1.0.3: {} | ||||||
| 
 | 
 | ||||||
|  |   analytics-utils@1.0.14(@types/dlv@1.1.5): | ||||||
|  |     dependencies: | ||||||
|  |       '@analytics/type-utils': 0.6.2 | ||||||
|  |       '@types/dlv': 1.1.5 | ||||||
|  |       dlv: 1.1.3 | ||||||
|  | 
 | ||||||
|  |   analytics@0.8.16(@types/dlv@1.1.5): | ||||||
|  |     dependencies: | ||||||
|  |       '@analytics/core': 0.12.17(@types/dlv@1.1.5) | ||||||
|  |       '@analytics/storage-utils': 0.4.2 | ||||||
|  |     transitivePeerDependencies: | ||||||
|  |       - '@types/dlv' | ||||||
|  | 
 | ||||||
|   ansi-colors@4.1.3: {} |   ansi-colors@4.1.3: {} | ||||||
| 
 | 
 | ||||||
|   ansi-escapes@4.3.2: |   ansi-escapes@4.3.2: | ||||||
|  | @ -16547,6 +16642,8 @@ snapshots: | ||||||
| 
 | 
 | ||||||
|   disposable-email-domains@1.0.62: {} |   disposable-email-domains@1.0.62: {} | ||||||
| 
 | 
 | ||||||
|  |   dlv@1.1.3: {} | ||||||
|  | 
 | ||||||
|   doctrine@2.1.0: |   doctrine@2.1.0: | ||||||
|     dependencies: |     dependencies: | ||||||
|       esutils: 2.0.3 |       esutils: 2.0.3 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		
		Reference in a new issue