diff options
| author | syuilo <Syuilotan@yahoo.co.jp> | 2023-02-22 18:06:25 +0900 |
|---|---|---|
| committer | syuilo <Syuilotan@yahoo.co.jp> | 2023-02-22 18:06:25 +0900 |
| commit | 76583510419a7a21dfc3c43c9a6e29de791da0fa (patch) | |
| tree | 07c05b0e5331256140becb55dc85928b6618e272 /packages/frontend | |
| parent | Merge branch 'develop' (diff) | |
| parent | 13.7.0 (diff) | |
| download | misskey-76583510419a7a21dfc3c43c9a6e29de791da0fa.tar.gz misskey-76583510419a7a21dfc3c43c9a6e29de791da0fa.tar.bz2 misskey-76583510419a7a21dfc3c43c9a6e29de791da0fa.zip | |
Merge branch 'develop'
Diffstat (limited to 'packages/frontend')
265 files changed, 1406 insertions, 2603 deletions
diff --git a/packages/frontend/.eslintrc.js b/packages/frontend/.eslintrc.js index 6c3bfb5a6e..e8e0e57d2a 100644 --- a/packages/frontend/.eslintrc.js +++ b/packages/frontend/.eslintrc.js @@ -55,6 +55,7 @@ module.exports = { 'vue/multi-word-component-names': 'warn', 'vue/require-v-for-key': 'warn', 'vue/no-unused-components': 'warn', + 'vue/no-unused-vars': 'warn', 'vue/valid-v-for': 'warn', 'vue/return-in-computed-property': 'warn', 'vue/no-setup-props-destructure': 'warn', diff --git a/packages/frontend/assets/label-red.svg b/packages/frontend/assets/label-red.svg index 45996aa9ce..c89d3f5f3a 100644 --- a/packages/frontend/assets/label-red.svg +++ b/packages/frontend/assets/label-red.svg @@ -1,6 +1,6 @@ -<?xml version="1.0" encoding="utf-8"?>
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px"
- y="0px" width="96px" height="96px" viewBox="0 0 96 96" enable-background="new 0 0 96 96" xml:space="preserve">
-<polygon fill="#ea2412" points="0,45.255 45.254,0 84.854,0 0,84.854 "/>
-</svg>
+<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" + y="0px" width="96px" height="96px" viewBox="0 0 96 96" enable-background="new 0 0 96 96" xml:space="preserve"> +<polygon fill="#ea2412" points="0,45.255 45.254,0 84.854,0 0,84.854 "/> +</svg> diff --git a/packages/frontend/assets/label.svg b/packages/frontend/assets/label.svg index b1f85f3c07..997335f505 100644 --- a/packages/frontend/assets/label.svg +++ b/packages/frontend/assets/label.svg @@ -1,6 +1,6 @@ -<?xml version="1.0" encoding="utf-8"?>
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px"
- y="0px" width="96px" height="96px" viewBox="0 0 96 96" enable-background="new 0 0 96 96" xml:space="preserve">
-<polygon fill="#0B8AEA" points="0,45.255 45.254,0 84.854,0 0,84.854 "/>
-</svg>
+<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" + y="0px" width="96px" height="96px" viewBox="0 0 96 96" enable-background="new 0 0 96 96" xml:space="preserve"> +<polygon fill="#0B8AEA" points="0,45.255 45.254,0 84.854,0 0,84.854 "/> +</svg> diff --git a/packages/frontend/assets/unread.svg b/packages/frontend/assets/unread.svg index 8c3cc9f475..8bd4156e51 100644 --- a/packages/frontend/assets/unread.svg +++ b/packages/frontend/assets/unread.svg @@ -1,7 +1,7 @@ -<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 16.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="レイヤー_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px"
- y="0px" width="32px" height="32px" viewBox="0 0 32 32" enable-background="new 0 0 32 32" xml:space="preserve">
-<circle fill="#3AA2DC" cx="16.5" cy="16.5" r="6"/>
-</svg>
+<?xml version="1.0" encoding="utf-8"?> +<!-- Generator: Adobe Illustrator 16.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<svg version="1.1" id="レイヤー_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" + y="0px" width="32px" height="32px" viewBox="0 0 32 32" enable-background="new 0 0 32 32" xml:space="preserve"> +<circle fill="#3AA2DC" cx="16.5" cy="16.5" r="6"/> +</svg> diff --git a/packages/frontend/package.json b/packages/frontend/package.json index bf22f7aaad..f89a946282 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -4,7 +4,9 @@ "scripts": { "watch": "vite", "build": "vite build", - "lint": "vue-tsc --noEmit && eslint --quiet \"src/**/*.{ts,vue}\"" + "typecheck": "vue-tsc --noEmit", + "eslint": "eslint --quiet \"src/**/*.{ts,vue}\"", + "lint": "pnpm typecheck && pnpm eslint" }, "dependencies": { "@discordapp/twemoji": "14.0.2", @@ -17,11 +19,11 @@ "@vue/compiler-sfc": "3.2.47", "autobind-decorator": "2.4.0", "autosize": "5.0.2", - "blurhash": "2.0.4", + "blurhash": "2.0.5", "broadcast-channel": "4.20.2", "browser-image-resizer": "git+https://github.com/misskey-dev/browser-image-resizer#v2.2.1-misskey.3", "canvas-confetti": "1.6.0", - "chart.js": "4.2.0", + "chart.js": "4.2.1", "chartjs-adapter-date-fns": "3.0.0", "chartjs-chart-matrix": "2.0.1", "chartjs-plugin-gradient": "0.6.1", @@ -36,7 +38,7 @@ "insert-text-at-cursor": "0.3.0", "is-file-animated": "1.0.2", "json5": "2.2.3", - "matter-js": "0.18.0", + "matter-js": "0.19.0", "mfm-js": "0.23.3", "misskey-js": "0.0.15", "photoswipe": "5.3.5", @@ -44,13 +46,12 @@ "punycode": "2.3.0", "querystring": "0.2.1", "rndstr": "1.0.0", - "rollup": "3.14.0", + "rollup": "3.17.2", "s-age": "1.1.2", - "sanitize-html": "2.9.0", - "sass": "1.58.0", + "sanitize-html": "2.10.0", + "sass": "1.58.3", "seedrandom": "3.0.5", "strict-event-emitter-types": "2.0.0", - "stringz": "2.1.0", "syuilo-password-strength": "0.0.1", "textarea-caret": "3.1.0", "three": "0.149.0", @@ -63,7 +64,7 @@ "uuid": "9.0.0", "vanilla-tilt": "1.8.0", "vue-plyr": "7.0.0", - "vite": "4.1.1", + "vite": "4.1.2", "vue": "3.2.47", "vue-prism-editor": "2.0.0-alpha.2", "vuedraggable": "next" @@ -74,7 +75,7 @@ "@types/gulp": "4.0.10", "@types/gulp-rename": "2.0.1", "@types/matter-js": "0.18.2", - "@types/node": "18.13.0", + "@types/node": "18.14.0", "@types/punycode": "2.1.0", "@types/sanitize-html": "2.8.0", "@types/seedrandom": "3.0.4", @@ -83,16 +84,16 @@ "@types/uuid": "9.0.0", "@types/websocket": "1.0.5", "@types/ws": "8.5.4", - "@typescript-eslint/eslint-plugin": "5.51.0", - "@typescript-eslint/parser": "5.51.0", + "@typescript-eslint/eslint-plugin": "5.52.0", + "@typescript-eslint/parser": "5.52.0", "@vue/runtime-core": "3.2.47", "cross-env": "7.0.3", - "cypress": "12.5.1", - "eslint": "8.33.0", + "cypress": "12.6.0", + "eslint": "8.34.0", "eslint-plugin-import": "2.27.5", "eslint-plugin-vue": "9.9.0", - "start-server-and-test": "1.15.3", + "start-server-and-test": "1.15.4", "vue-eslint-parser": "9.1.0", - "vue-tsc": "1.0.24" + "vue-tsc": "1.1.4" } } diff --git a/packages/frontend/src/components/MkAbuseReport.vue b/packages/frontend/src/components/MkAbuseReport.vue index 0e18a5a83d..dee80378e6 100644 --- a/packages/frontend/src/components/MkAbuseReport.vue +++ b/packages/frontend/src/components/MkAbuseReport.vue @@ -39,7 +39,6 @@ import MkButton from '@/components/MkButton.vue'; import MkSwitch from '@/components/MkSwitch.vue'; import MkKeyValue from '@/components/MkKeyValue.vue'; -import { acct, userPage } from '@/filters/user'; import * as os from '@/os'; import { i18n } from '@/i18n'; import { dateString } from '@/filters/date'; diff --git a/packages/frontend/src/components/MkAbuseReportWindow.vue b/packages/frontend/src/components/MkAbuseReportWindow.vue index a76a1e0f54..9f2bf99338 100644 --- a/packages/frontend/src/components/MkAbuseReportWindow.vue +++ b/packages/frontend/src/components/MkAbuseReportWindow.vue @@ -43,7 +43,7 @@ const emit = defineEmits<{ }>(); const uiWindow = shallowRef<InstanceType<typeof MkWindow>>(); -const comment = ref(props.initialComment || ''); +const comment = ref(props.initialComment ?? ''); function send() { os.apiWithDialog('users/report-abuse', { diff --git a/packages/frontend/src/components/MkAnalogClock.vue b/packages/frontend/src/components/MkAnalogClock.vue index 139e49cc40..1218202616 100644 --- a/packages/frontend/src/components/MkAnalogClock.vue +++ b/packages/frontend/src/components/MkAnalogClock.vue @@ -73,7 +73,7 @@ </template> <script lang="ts" setup> -import { ref, computed, onMounted, onBeforeUnmount, shallowRef, nextTick } from 'vue'; +import { computed, onMounted, onBeforeUnmount } from 'vue'; import tinycolor from 'tinycolor2'; import { globalEvents } from '@/events.js'; diff --git a/packages/frontend/src/components/MkAsUi.vue b/packages/frontend/src/components/MkAsUi.vue index 4f463d73d9..6ade5316c6 100644 --- a/packages/frontend/src/components/MkAsUi.vue +++ b/packages/frontend/src/components/MkAsUi.vue @@ -48,7 +48,7 @@ </template> <script lang="ts" setup> -import { computed, defineAsyncComponent, onMounted, onUnmounted, Ref } from 'vue'; +import { Ref } from 'vue'; import * as os from '@/os'; import MkButton from '@/components/MkButton.vue'; import MkInput from '@/components/MkInput.vue'; diff --git a/packages/frontend/src/components/MkAutocomplete.vue b/packages/frontend/src/components/MkAutocomplete.vue index e523b988b0..663c57623d 100644 --- a/packages/frontend/src/components/MkAutocomplete.vue +++ b/packages/frontend/src/components/MkAutocomplete.vue @@ -43,7 +43,6 @@ import * as os from '@/os'; import { MFM_TAGS } from '@/scripts/mfm-tags'; import { defaultStore } from '@/store'; import { emojilist } from '@/scripts/emojilist'; -import { instance } from '@/instance'; import { i18n } from '@/i18n'; import { miLocalStorage } from '@/local-storage'; import { customEmojis } from '@/custom-emojis'; @@ -210,7 +209,7 @@ function exec() { } } else if (props.type === 'hashtag') { if (!props.q || props.q === '') { - hashtags.value = JSON.parse(miLocalStorage.getItem('hashtags') || '[]'); + hashtags.value = JSON.parse(miLocalStorage.getItem('hashtags') ?? '[]'); fetching.value = false; } else { const cacheKey = `autocomplete:hashtag:${props.q}`; diff --git a/packages/frontend/src/components/MkCaptcha.vue b/packages/frontend/src/components/MkCaptcha.vue index 8db2e54e88..c72cc2ab1b 100644 --- a/packages/frontend/src/components/MkCaptcha.vue +++ b/packages/frontend/src/components/MkCaptcha.vue @@ -69,7 +69,7 @@ const captcha = computed<Captcha>(() => window[variable.value] || {} as unknown if (loaded) { available.value = true; } else { - (document.getElementById(scriptId.value) || document.head.appendChild(Object.assign(document.createElement('script'), { + (document.getElementById(scriptId.value) ?? document.head.appendChild(Object.assign(document.createElement('script'), { async: true, id: scriptId.value, src: src.value, diff --git a/packages/frontend/src/components/MkChart.vue b/packages/frontend/src/components/MkChart.vue index 57efda44b1..06d5b9949a 100644 --- a/packages/frontend/src/components/MkChart.vue +++ b/packages/frontend/src/components/MkChart.vue @@ -14,7 +14,7 @@ id-denylist violation when setting it. This is causing about 60+ lint issues. As this is part of Chart.js's API it makes sense to disable the check here. */ -import { onMounted, ref, shallowRef, watch, PropType, onUnmounted } from 'vue'; +import { onMounted, ref, shallowRef, watch, PropType } from 'vue'; import { Chart } from 'chart.js'; import gradient from 'chartjs-plugin-gradient'; import * as os from '@/os'; diff --git a/packages/frontend/src/components/MkChartLegend.vue b/packages/frontend/src/components/MkChartLegend.vue index b950f2836e..7bc6194b7a 100644 --- a/packages/frontend/src/components/MkChartLegend.vue +++ b/packages/frontend/src/components/MkChartLegend.vue @@ -8,7 +8,6 @@ </template> <script lang="ts" setup> -import { onMounted, ref, shallowRef, watch, PropType, onUnmounted } from 'vue'; import { Chart, LegendItem } from 'chart.js'; const props = defineProps({ diff --git a/packages/frontend/src/components/MkClickerGame.vue b/packages/frontend/src/components/MkClickerGame.vue index 68e0f8185d..da6439fd2c 100644 --- a/packages/frontend/src/components/MkClickerGame.vue +++ b/packages/frontend/src/components/MkClickerGame.vue @@ -14,7 +14,7 @@ </template> <script lang="ts" setup> -import { computed, defineAsyncComponent, onMounted, onUnmounted } from 'vue'; +import { computed, onMounted, onUnmounted } from 'vue'; import MkPlusOneEffect from '@/components/MkPlusOneEffect.vue'; import * as os from '@/os'; import { useInterval } from '@/scripts/use-interval'; @@ -22,9 +22,6 @@ import * as game from '@/scripts/clicker-game'; import number from '@/filters/number'; import { claimAchievement } from '@/scripts/achievements'; -defineProps<{ -}>(); - const saveData = game.saveData; const cookies = computed(() => saveData.value?.cookies); let cps = $ref(0); diff --git a/packages/frontend/src/components/MkCropperDialog.vue b/packages/frontend/src/components/MkCropperDialog.vue index 7e4d2016be..d1b5cc5118 100644 --- a/packages/frontend/src/components/MkCropperDialog.vue +++ b/packages/frontend/src/components/MkCropperDialog.vue @@ -26,7 +26,7 @@ </template> <script lang="ts" setup> -import { nextTick, onMounted } from 'vue'; +import { onMounted } from 'vue'; import * as misskey from 'misskey-js'; import Cropper from 'cropperjs'; import tinycolor from 'tinycolor2'; diff --git a/packages/frontend/src/components/MkCwButton.vue b/packages/frontend/src/components/MkCwButton.vue index e0885f5550..7d5579040a 100644 --- a/packages/frontend/src/components/MkCwButton.vue +++ b/packages/frontend/src/components/MkCwButton.vue @@ -7,7 +7,6 @@ <script lang="ts" setup> import { computed } from 'vue'; -import { length } from 'stringz'; import * as misskey from 'misskey-js'; import { concat } from '@/scripts/array'; import { i18n } from '@/i18n'; @@ -23,7 +22,7 @@ const emit = defineEmits<{ const label = computed(() => { return concat([ - props.note.text ? [i18n.t('_cw.chars', { count: length(props.note.text) })] : [], + props.note.text ? [i18n.t('_cw.chars', { count: props.note.text.length })] : [], props.note.files && props.note.files.length !== 0 ? [i18n.t('_cw.files', { count: props.note.files.length })] : [], props.note.poll != null ? [i18n.ts.poll] : [], ] as string[][]).join(' / '); diff --git a/packages/frontend/src/components/MkDialog.vue b/packages/frontend/src/components/MkDialog.vue index 9690353432..863ea702cd 100644 --- a/packages/frontend/src/components/MkDialog.vue +++ b/packages/frontend/src/components/MkDialog.vue @@ -14,8 +14,12 @@ </div> <header v-if="title" :class="$style.title"><Mfm :text="title"/></header> <div v-if="text" :class="$style.text"><Mfm :text="text"/></div> - <MkInput v-if="input" v-model="inputValue" autofocus :type="input.type || 'text'" :placeholder="input.placeholder || undefined" @keydown="onInputKeydown"> + <MkInput v-if="input" v-model="inputValue" autofocus :type="input.type || 'text'" :placeholder="input.placeholder || undefined" :autocomplete="input.autocomplete" @keydown="onInputKeydown"> <template v-if="input.type === 'password'" #prefix><i class="ti ti-lock"></i></template> + <template #caption> + <span v-if="okButtonDisabled && disabledReason === 'charactersExceeded'" v-text="i18n.t('_dialog.charactersExceeded', { current: (inputValue as string).length, max: input.maxLength ?? 'NaN' })" /> + <span v-else-if="okButtonDisabled && disabledReason === 'charactersBelow'" v-text="i18n.t('_dialog.charactersBelow', { current: (inputValue as string).length, min: input.minLength ?? 'NaN' })" /> + </template> </MkInput> <MkSelect v-if="select" v-model="selectedValue" autofocus> <template v-if="select.items"> @@ -28,7 +32,7 @@ </template> </MkSelect> <div v-if="(showOkButton || showCancelButton) && !actions" :class="$style.buttons"> - <MkButton v-if="showOkButton" inline primary :autofocus="!input && !select" @click="ok">{{ okText ?? ((showCancelButton || input || select) ? i18n.ts.ok : i18n.ts.gotIt) }}</MkButton> + <MkButton v-if="showOkButton" inline primary :autofocus="!input && !select" :disabled="okButtonDisabled" @click="ok">{{ okText ?? ((showCancelButton || input || select) ? i18n.ts.ok : i18n.ts.gotIt) }}</MkButton> <MkButton v-if="showCancelButton || input || select" inline @click="cancel">{{ cancelText ?? i18n.ts.cancel }}</MkButton> </div> <div v-if="actions" :class="$style.buttons"> @@ -47,9 +51,12 @@ import MkSelect from '@/components/MkSelect.vue'; import { i18n } from '@/i18n'; type Input = { - type: HTMLInputElement['type']; + type: 'text' | 'number' | 'password' | 'email' | 'url' | 'date' | 'time' | 'search' | 'datetime-local'; placeholder?: string | null; - default: any | null; + autocomplete?: string; + default: string | number | null; + minLength?: number; + maxLength?: number; }; type Select = { @@ -98,8 +105,28 @@ const emit = defineEmits<{ const modal = shallowRef<InstanceType<typeof MkModal>>(); -const inputValue = ref(props.input?.default || null); -const selectedValue = ref(props.select?.default || null); +const inputValue = ref<string | number | null>(props.input?.default ?? null); +const selectedValue = ref(props.select?.default ?? null); + +let disabledReason = $ref<null | 'charactersExceeded' | 'charactersBelow'>(null); +const okButtonDisabled = $computed<boolean>(() => { + if (props.input) { + if (props.input.minLength) { + if ((inputValue.value || inputValue.value === '') && (inputValue.value as string).length < props.input.minLength) { + disabledReason = 'charactersBelow'; + return true; + } + } + if (props.input.maxLength) { + if (inputValue.value && (inputValue.value as string).length > props.input.maxLength) { + disabledReason = 'charactersExceeded'; + return true; + } + } + } + + return false; +}); function done(canceled: boolean, result?) { emit('done', { canceled, result }); diff --git a/packages/frontend/src/components/MkDonation.vue b/packages/frontend/src/components/MkDonation.vue index 707444abc9..9baa90ebfe 100644 --- a/packages/frontend/src/components/MkDonation.vue +++ b/packages/frontend/src/components/MkDonation.vue @@ -31,7 +31,6 @@ </template> <script lang="ts" setup> -import { onMounted, shallowRef } from 'vue'; import MkButton from '@/components/MkButton.vue'; import MkLink from '@/components/MkLink.vue'; import { host } from '@/config'; @@ -51,7 +50,7 @@ function close() { } function neverShow() { - miLocalStorage.setItem('neverShowDonationInfo', 'true') + miLocalStorage.setItem('neverShowDonationInfo', 'true'); close(); } </script> diff --git a/packages/frontend/src/components/MkDrive.vue b/packages/frontend/src/components/MkDrive.vue index af7175e5cd..bfec57d6a0 100644 --- a/packages/frontend/src/components/MkDrive.vue +++ b/packages/frontend/src/components/MkDrive.vue @@ -88,7 +88,7 @@ </template> <script lang="ts" setup> -import { markRaw, nextTick, onActivated, onBeforeUnmount, onMounted, ref, shallowRef, watch } from 'vue'; +import { nextTick, onActivated, onBeforeUnmount, onMounted, ref, shallowRef, watch } from 'vue'; import * as Misskey from 'misskey-js'; import MkButton from './MkButton.vue'; import XNavFolder from '@/components/MkDrive.navFolder.vue'; diff --git a/packages/frontend/src/components/MkEmojiPicker.vue b/packages/frontend/src/components/MkEmojiPicker.vue index c7556ec36e..7d280f2f4b 100644 --- a/packages/frontend/src/components/MkEmojiPicker.vue +++ b/packages/frontend/src/components/MkEmojiPicker.vue @@ -95,7 +95,6 @@ import MkRippleEffect from '@/components/MkRippleEffect.vue'; import * as os from '@/os'; import { isTouchUsing } from '@/scripts/touch'; import { deviceKind } from '@/scripts/device-kind'; -import { instance } from '@/instance'; import { i18n } from '@/i18n'; import { defaultStore } from '@/store'; import { customEmojiCategories, customEmojis } from '@/custom-emojis'; diff --git a/packages/frontend/src/components/MkEmojiPickerWindow.vue b/packages/frontend/src/components/MkEmojiPickerWindow.vue index ca7dbccdc8..84970410e9 100644 --- a/packages/frontend/src/components/MkEmojiPickerWindow.vue +++ b/packages/frontend/src/components/MkEmojiPickerWindow.vue @@ -7,7 +7,7 @@ :front="true" @closed="emit('closed')" > - <MkEmojiPicker :show-pinned="showPinned" :as-reaction-picker="asReactionPicker" as-window @chosen="chosen" :class="$style.picker"/> + <MkEmojiPicker :show-pinned="showPinned" :as-reaction-picker="asReactionPicker" as-window :class="$style.picker" @chosen="chosen"/> </MkWindow> </template> diff --git a/packages/frontend/src/components/MkFileListForAdmin.vue b/packages/frontend/src/components/MkFileListForAdmin.vue index f340acaf2d..71a35ae6e8 100644 --- a/packages/frontend/src/components/MkFileListForAdmin.vue +++ b/packages/frontend/src/components/MkFileListForAdmin.vue @@ -32,12 +32,10 @@ </template> <script lang="ts" setup> -import { computed } from 'vue'; import * as Acct from 'misskey-js/built/acct'; import MkPagination from '@/components/MkPagination.vue'; import MkDriveFileThumbnail from '@/components/MkDriveFileThumbnail.vue'; import bytes from '@/filters/bytes'; -import * as os from '@/os'; import { i18n } from '@/i18n'; import { dateString } from '@/filters/date'; diff --git a/packages/frontend/src/components/MkFlashPreview.vue b/packages/frontend/src/components/MkFlashPreview.vue index a96934ddb1..7c9ae155ab 100644 --- a/packages/frontend/src/components/MkFlashPreview.vue +++ b/packages/frontend/src/components/MkFlashPreview.vue @@ -15,9 +15,7 @@ <script lang="ts" setup> import { } from 'vue'; -import * as misskey from 'misskey-js'; import { userName } from '@/filters/user'; -import * as os from '@/os'; const props = defineProps<{ //flash: misskey.entities.Flash; diff --git a/packages/frontend/src/components/MkFolder.vue b/packages/frontend/src/components/MkFolder.vue index a1d7210d7e..b97e36cd5f 100644 --- a/packages/frontend/src/components/MkFolder.vue +++ b/packages/frontend/src/components/MkFolder.vue @@ -1,13 +1,20 @@ <template> <div ref="rootEl" :class="[$style.root, { [$style.opened]: opened }]"> <div :class="$style.header" class="_button" @click="toggle"> - <span :class="$style.headerIcon"><slot name="icon"></slot></span> - <span :class="$style.headerText"><slot name="label"></slot></span> - <span :class="$style.headerRight"> + <div :class="$style.headerIcon"><slot name="icon"></slot></div> + <div :class="$style.headerText"> + <div :class="$style.headerTextMain"> + <slot name="label"></slot> + </div> + <div :class="$style.headerTextSub"> + <slot name="caption"></slot> + </div> + </div> + <div :class="$style.headerRight"> <span :class="$style.headerRightText"><slot name="suffix"></slot></span> <i v-if="opened" class="ti ti-chevron-up icon"></i> <i v-else class="ti ti-chevron-down icon"></i> - </span> + </div> </div> <div v-if="openedAtLeastOnce" :class="[$style.body, { [$style.bgSame]: bgSame }]" :style="{ maxHeight: maxHeight ? `${maxHeight}px` : null }"> <Transition @@ -139,6 +146,17 @@ onMounted(() => { } } +.headerUpper { + display: flex; + align-items: center; +} + +.headerLower { + color: var(--fgTransparentWeak); + font-size: .85em; + padding-left: 4px; +} + .headerIcon { margin-right: 0.75em; flex-shrink: 0; @@ -161,6 +179,15 @@ onMounted(() => { padding-right: 12px; } +.headerTextMain { + +} + +.headerTextSub { + color: var(--fgTransparentWeak); + font-size: .85em; +} + .headerRight { margin-left: auto; opacity: 0.7; diff --git a/packages/frontend/src/components/MkGalleryPostPreview.vue b/packages/frontend/src/components/MkGalleryPostPreview.vue index 42f8853bd4..2c5032119f 100644 --- a/packages/frontend/src/components/MkGalleryPostPreview.vue +++ b/packages/frontend/src/components/MkGalleryPostPreview.vue @@ -16,9 +16,7 @@ <script lang="ts" setup> import { } from 'vue'; -import { userName } from '@/filters/user'; import ImgWithBlurhash from '@/components/MkImgWithBlurhash.vue'; -import * as os from '@/os'; const props = defineProps<{ post: any; diff --git a/packages/frontend/src/components/MkHeatmap.vue b/packages/frontend/src/components/MkHeatmap.vue index f222fca9a1..e5a39a759b 100644 --- a/packages/frontend/src/components/MkHeatmap.vue +++ b/packages/frontend/src/components/MkHeatmap.vue @@ -8,14 +8,11 @@ </template> <script lang="ts" setup> -import { markRaw, version as vueVersion, onMounted, onBeforeUnmount, nextTick, watch } from 'vue'; +import { onMounted, nextTick, watch } from 'vue'; import { Chart } from 'chart.js'; -import tinycolor from 'tinycolor2'; -import { MatrixController, MatrixElement } from 'chartjs-chart-matrix'; import * as os from '@/os'; import { defaultStore } from '@/store'; import { useChartTooltip } from '@/scripts/use-chart-tooltip'; -import { chartVLine } from '@/scripts/chart-vline'; import { alpha } from '@/scripts/color'; import { initChart } from '@/scripts/init-chart'; diff --git a/packages/frontend/src/components/MkInput.vue b/packages/frontend/src/components/MkInput.vue index e3f68caa9b..3e3d7354c1 100644 --- a/packages/frontend/src/components/MkInput.vue +++ b/packages/frontend/src/components/MkInput.vue @@ -23,7 +23,7 @@ @input="onInput" > <datalist v-if="datalist" :id="id"> - <option v-for="data in datalist" :value="data"/> + <option v-for="data in datalist" :key="data" :value="data"/> </datalist> <div ref="suffixEl" class="suffix"><slot name="suffix"></slot></div> </div> @@ -34,14 +34,14 @@ </template> <script lang="ts" setup> -import { onMounted, onUnmounted, nextTick, ref, shallowRef, watch, computed, toRefs } from 'vue'; +import { onMounted, nextTick, ref, shallowRef, watch, computed, toRefs } from 'vue'; import { debounce } from 'throttle-debounce'; import MkButton from '@/components/MkButton.vue'; import { useInterval } from '@/scripts/use-interval'; import { i18n } from '@/i18n'; const props = defineProps<{ - modelValue: string | number; + modelValue: string | number | null; type?: 'text' | 'number' | 'password' | 'email' | 'url' | 'date' | 'time' | 'search' | 'datetime-local'; required?: boolean; readonly?: boolean; @@ -49,7 +49,7 @@ const props = defineProps<{ pattern?: string; placeholder?: string; autofocus?: boolean; - autocomplete?: boolean; + autocomplete?: string; spellcheck?: boolean; step?: any; datalist?: string[]; diff --git a/packages/frontend/src/components/MkLaunchPad.vue b/packages/frontend/src/components/MkLaunchPad.vue index 40c99b60ec..80e5cc8270 100644 --- a/packages/frontend/src/components/MkLaunchPad.vue +++ b/packages/frontend/src/components/MkLaunchPad.vue @@ -23,11 +23,8 @@ import { } from 'vue'; import MkModal from '@/components/MkModal.vue'; import { navbarItemDef } from '@/navbar'; -import { instanceName } from '@/config'; import { defaultStore } from '@/store'; -import { i18n } from '@/i18n'; import { deviceKind } from '@/scripts/device-kind'; -import * as os from '@/os'; const props = withDefaults(defineProps<{ src?: HTMLElement; diff --git a/packages/frontend/src/components/MkMediaList.vue b/packages/frontend/src/components/MkMediaList.vue index 29d15989b7..a12bb78e35 100644 --- a/packages/frontend/src/components/MkMediaList.vue +++ b/packages/frontend/src/components/MkMediaList.vue @@ -23,7 +23,6 @@ import XImage from '@/components/MkMediaImage.vue'; import XVideo from '@/components/MkMediaVideo.vue'; import * as os from '@/os'; import { FILE_TYPE_BROWSERSAFE } from '@/const'; -import { defaultStore } from '@/store'; const props = defineProps<{ mediaList: misskey.entities.DriveFile[]; @@ -46,8 +45,8 @@ onMounted(() => { src: media.url, w: media.properties.width, h: media.properties.height, - alt: media.comment || media.name, - comment: media.comment || media.name, + alt: media.comment ?? media.name, + comment: media.comment ?? media.name, }; if (media.properties.orientation != null && media.properties.orientation >= 5) { [item.w, item.h] = [item.h, item.w]; @@ -91,8 +90,8 @@ onMounted(() => { [itemData.w, itemData.h] = [itemData.h, itemData.w]; } itemData.msrc = file.thumbnailUrl; - itemData.alt = file.comment || file.name; - itemData.comment = file.comment || file.name; + itemData.alt = file.comment ?? file.name; + itemData.comment = file.comment ?? file.name; itemData.thumbCropped = true; }); diff --git a/packages/frontend/src/components/MkMenu.child.vue b/packages/frontend/src/components/MkMenu.child.vue index 0ff8794c5d..cdd9d96b96 100644 --- a/packages/frontend/src/components/MkMenu.child.vue +++ b/packages/frontend/src/components/MkMenu.child.vue @@ -5,11 +5,9 @@ </template> <script lang="ts" setup> -import { on } from 'events'; -import { nextTick, onBeforeUnmount, onMounted, onUnmounted, ref, shallowRef, watch } from 'vue'; +import { nextTick, onMounted, shallowRef, watch } from 'vue'; import MkMenu from './MkMenu.vue'; import { MenuItem } from '@/types/menu'; -import * as os from '@/os'; const props = defineProps<{ items: MenuItem[]; diff --git a/packages/frontend/src/components/MkMenu.vue b/packages/frontend/src/components/MkMenu.vue index eee77a9475..52aba58455 100644 --- a/packages/frontend/src/components/MkMenu.vue +++ b/packages/frontend/src/components/MkMenu.vue @@ -56,7 +56,7 @@ </template> <script lang="ts" setup> -import { defineAsyncComponent, nextTick, onBeforeUnmount, onMounted, onUnmounted, Ref, ref, watch } from 'vue'; +import { defineAsyncComponent, nextTick, onBeforeUnmount, onMounted, watch } from 'vue'; import { focusPrev, focusNext } from '@/scripts/focus'; import MkSwitch from '@/components/MkSwitch.vue'; import { MenuItem, InnerMenuItem, MenuPending, MenuAction } from '@/types/menu'; diff --git a/packages/frontend/src/components/MkMiniChart.vue b/packages/frontend/src/components/MkMiniChart.vue index c64ce163f9..89050e10f0 100644 --- a/packages/frontend/src/components/MkMiniChart.vue +++ b/packages/frontend/src/components/MkMiniChart.vue @@ -26,7 +26,7 @@ </template> <script lang="ts" setup> -import { onUnmounted, watch } from 'vue'; +import { watch } from 'vue'; import { v4 as uuid } from 'uuid'; import tinycolor from 'tinycolor2'; import { useInterval } from '@/scripts/use-interval'; diff --git a/packages/frontend/src/components/MkModalPageWindow.vue b/packages/frontend/src/components/MkModalPageWindow.vue index ed892bc007..68a3eda3d8 100644 --- a/packages/frontend/src/components/MkModalPageWindow.vue +++ b/packages/frontend/src/components/MkModalPageWindow.vue @@ -29,7 +29,7 @@ import { url } from '@/config'; import * as os from '@/os'; import { mainRouter, routes } from '@/router'; import { i18n } from '@/i18n'; -import { PageMetadata, provideMetadataReceiver, setPageMetadata } from '@/scripts/page-metadata'; +import { PageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata'; import { Router } from '@/nirax'; const props = defineProps<{ diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue index c9c512c36e..4986f1b646 100644 --- a/packages/frontend/src/components/MkNote.vue +++ b/packages/frontend/src/components/MkNote.vue @@ -9,7 +9,7 @@ > <MkNoteSub v-if="appearNote.reply && !renoteCollapsed" :note="appearNote.reply" :class="$style.replyTo"/> <div v-if="pinned" :class="$style.tip"><i class="ti ti-pin"></i> {{ i18n.ts.pinnedNote }}</div> - <!--<div v-if="appearNote._prId_" class="tip"><i class="fas fa-bullhorn"></i> {{ i18n.ts.promotion }}<button class="_textButton hide" @click="readPromo()">{{ i18n.ts.hideThisNote }} <i class="ti ti-x"></i></button></div>--> + <!--<div v-if="appearNote._prId_" class="tip"><i class="ti ti-speakerphone"></i> {{ i18n.ts.promotion }}<button class="_textButton hide" @click="readPromo()">{{ i18n.ts.hideThisNote }} <i class="ti ti-x"></i></button></div>--> <!--<div v-if="appearNote._featuredId_" class="tip"><i class="ti ti-bolt"></i> {{ i18n.ts.featured }}</div>--> <div v-if="isRenote" :class="$style.renote"> <MkAvatar :class="$style.renoteAvatar" :user="note.user" link preview/> @@ -126,7 +126,7 @@ </template> <script lang="ts" setup> -import { computed, inject, onMounted, onUnmounted, reactive, ref, shallowRef, Ref, defineAsyncComponent } from 'vue'; +import { computed, inject, onMounted, ref, shallowRef, Ref, defineAsyncComponent } from 'vue'; import * as mfm from 'mfm-js'; import * as misskey from 'misskey-js'; import MkNoteSub from '@/components/MkNoteSub.vue'; @@ -195,6 +195,8 @@ const isMyRenote = $i && ($i.id === note.userId); const showContent = ref(false); const urls = appearNote.text ? extractUrlFromMfm(mfm.parse(appearNote.text)) : null; const isLong = (appearNote.cw == null && appearNote.text != null && ( + (appearNote.text.includes('$[x3')) || + (appearNote.text.includes('$[x4')) || (appearNote.text.split('\n').length > 9) || (appearNote.text.length > 500) || (appearNote.files.length >= 5) || diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue index 92bdadc562..82e0f3e689 100644 --- a/packages/frontend/src/components/MkNoteDetailed.vue +++ b/packages/frontend/src/components/MkNoteDetailed.vue @@ -133,7 +133,7 @@ </template> <script lang="ts" setup> -import { computed, inject, onMounted, onUnmounted, reactive, ref, shallowRef } from 'vue'; +import { computed, inject, onMounted, ref, shallowRef } from 'vue'; import * as mfm from 'mfm-js'; import * as misskey from 'misskey-js'; import MkNoteSub from '@/components/MkNoteSub.vue'; diff --git a/packages/frontend/src/components/MkNotification.vue b/packages/frontend/src/components/MkNotification.vue index 0d42e8ffbf..38bf416ea8 100644 --- a/packages/frontend/src/components/MkNotification.vue +++ b/packages/frontend/src/components/MkNotification.vue @@ -9,7 +9,6 @@ <i v-if="notification.type === 'follow'" class="ti ti-plus"></i> <i v-else-if="notification.type === 'receiveFollowRequest'" class="ti ti-clock"></i> <i v-else-if="notification.type === 'followRequestAccepted'" class="ti ti-check"></i> - <i v-else-if="notification.type === 'groupInvited'" class="ti ti-certificate-2"></i> <i v-else-if="notification.type === 'renote'" class="ti ti-repeat"></i> <i v-else-if="notification.type === 'reply'" class="ti ti-arrow-back-up"></i> <i v-else-if="notification.type === 'mention'" class="ti ti-at"></i> @@ -74,12 +73,6 @@ <button class="_textButton" @click="acceptFollowRequest()">{{ i18n.ts.accept }}</button> | <button class="_textButton" @click="rejectFollowRequest()">{{ i18n.ts.reject }}</button> </div> </template> - <template v-else-if="notification.type === 'groupInvited'"> - <span :class="$style.text" style="opacity: 0.6;">{{ i18n.ts.groupInvited }}: <b>{{ notification.invitation.group.name }}</b></span> - <div v-if="full && !groupInviteDone"> - <button class="_textButton" @click="acceptGroupInvitation()">{{ i18n.ts.accept }}</button> | <button class="_textButton" @click="rejectGroupInvitation()">{{ i18n.ts.reject }}</button> - </div> - </template> <span v-else-if="notification.type === 'app'" :class="$style.text"> <Mfm :text="notification.body" :nowrap="false"/> </span> @@ -145,7 +138,6 @@ onUnmounted(() => { }); const followRequestDone = ref(false); -const groupInviteDone = ref(false); const acceptFollowRequest = () => { followRequestDone.value = true; @@ -157,16 +149,6 @@ const rejectFollowRequest = () => { os.api('following/requests/reject', { userId: props.notification.user.id }); }; -const acceptGroupInvitation = () => { - groupInviteDone.value = true; - os.apiWithDialog('users/groups/invitations/accept', { invitationId: props.notification.invitation.id }); -}; - -const rejectGroupInvitation = () => { - groupInviteDone.value = true; - os.api('users/groups/invitations/reject', { invitationId: props.notification.invitation.id }); -}; - useTooltip(reactionRef, (showing) => { os.popup(XReactionTooltip, { showing, @@ -224,7 +206,7 @@ useTooltip(reactionRef, (showing) => { } } -.t_follow, .t_followRequestAccepted, .t_receiveFollowRequest, .t_groupInvited { +.t_follow, .t_followRequestAccepted, .t_receiveFollowRequest { padding: 3px; background: #36aed2; pointer-events: none; diff --git a/packages/frontend/src/components/MkNotificationSettingWindow.vue b/packages/frontend/src/components/MkNotificationSettingWindow.vue index e303403872..2d8d30e337 100644 --- a/packages/frontend/src/components/MkNotificationSettingWindow.vue +++ b/packages/frontend/src/components/MkNotificationSettingWindow.vue @@ -54,7 +54,7 @@ const props = withDefaults(defineProps<{ showGlobalToggle: true, }); -let includingTypes = $computed(() => props.includingTypes || []); +let includingTypes = $computed(() => props.includingTypes ?? []); const dialog = $shallowRef<InstanceType<typeof MkModalWindow>>(); diff --git a/packages/frontend/src/components/MkNotifications.vue b/packages/frontend/src/components/MkNotifications.vue index ab5dff8db5..37ce7635a3 100644 --- a/packages/frontend/src/components/MkNotifications.vue +++ b/packages/frontend/src/components/MkNotifications.vue @@ -17,13 +17,12 @@ </template> <script lang="ts" setup> -import { defineComponent, markRaw, onUnmounted, onMounted, computed, shallowRef } from 'vue'; +import { onUnmounted, onMounted, computed, shallowRef } from 'vue'; import { notificationTypes } from 'misskey-js'; import MkPagination, { Paging } from '@/components/MkPagination.vue'; import XNotification from '@/components/MkNotification.vue'; import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue'; import XNote from '@/components/MkNote.vue'; -import * as os from '@/os'; import { stream } from '@/stream'; import { $i } from '@/account'; import { i18n } from '@/i18n'; diff --git a/packages/frontend/src/components/MkNumber.vue b/packages/frontend/src/components/MkNumber.vue index 58279be0b6..51fc4d02bb 100644 --- a/packages/frontend/src/components/MkNumber.vue +++ b/packages/frontend/src/components/MkNumber.vue @@ -3,7 +3,7 @@ </template> <script lang="ts" setup> -import { ref, reactive, watch } from 'vue'; +import { reactive, watch } from 'vue'; import gsap from 'gsap'; import number from '@/filters/number'; diff --git a/packages/frontend/src/components/MkObjectView.value.vue b/packages/frontend/src/components/MkObjectView.value.vue index 0c7230d783..e7fc73bce3 100644 --- a/packages/frontend/src/components/MkObjectView.value.vue +++ b/packages/frontend/src/components/MkObjectView.value.vue @@ -29,7 +29,7 @@ </template> <script lang="ts"> -import { computed, defineComponent, reactive, ref } from 'vue'; +import { defineComponent, reactive } from 'vue'; import number from '@/filters/number'; export default defineComponent({ diff --git a/packages/frontend/src/components/MkOmit.vue b/packages/frontend/src/components/MkOmit.vue index 5a834c9800..a806d92b22 100644 --- a/packages/frontend/src/components/MkOmit.vue +++ b/packages/frontend/src/components/MkOmit.vue @@ -8,7 +8,7 @@ </template> <script lang="ts" setup> -import { nextTick, onMounted } from 'vue'; +import { onMounted } from 'vue'; const props = withDefaults(defineProps<{ maxHeight: number; diff --git a/packages/frontend/src/components/MkPagePreview.vue b/packages/frontend/src/components/MkPagePreview.vue index a78431e2a7..4fda6df5e9 100644 --- a/packages/frontend/src/components/MkPagePreview.vue +++ b/packages/frontend/src/components/MkPagePreview.vue @@ -18,7 +18,6 @@ import { } from 'vue'; import * as misskey from 'misskey-js'; import { userName } from '@/filters/user'; -import * as os from '@/os'; const props = defineProps<{ page: misskey.entities.Page; diff --git a/packages/frontend/src/components/MkPageWindow.vue b/packages/frontend/src/components/MkPageWindow.vue index d12aafd06d..02ce58451d 100644 --- a/packages/frontend/src/components/MkPageWindow.vue +++ b/packages/frontend/src/components/MkPageWindow.vue @@ -18,23 +18,22 @@ </template> <div :class="$style.root" :style="{ background: pageMetadata?.value?.bg }" style="container-type: inline-size;"> - <RouterView :router="router"/> + <RouterView :key="reloadCount" :router="router"/> </div> </MkWindow> </template> <script lang="ts" setup> -import { ComputedRef, inject, onMounted, onUnmounted, provide } from 'vue'; +import { ComputedRef, onMounted, onUnmounted, provide } from 'vue'; import RouterView from '@/components/global/RouterView.vue'; import MkWindow from '@/components/MkWindow.vue'; import { popout as _popout } from '@/scripts/popout'; import copyToClipboard from '@/scripts/copy-to-clipboard'; import { url } from '@/config'; -import * as os from '@/os'; import { mainRouter, routes } from '@/router'; import { Router } from '@/nirax'; import { i18n } from '@/i18n'; -import { PageMetadata, provideMetadataReceiver, setPageMetadata } from '@/scripts/page-metadata'; +import { PageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata'; import { openingWindowsCount } from '@/os'; import { claimAchievement } from '@/scripts/achievements'; @@ -68,6 +67,10 @@ const buttonsLeft = $computed(() => { }); const buttonsRight = $computed(() => { const buttons = [{ + icon: 'ti ti-reload', + title: i18n.ts.reload, + onClick: reload, + }, { icon: 'ti ti-player-eject', title: i18n.ts.showInPage, onClick: expand, @@ -75,6 +78,7 @@ const buttonsRight = $computed(() => { return buttons; }); +let reloadCount = $ref(0); router.addListener('push', ctx => { history.push({ path: ctx.path, key: ctx.key }); @@ -116,6 +120,10 @@ function back() { router.replace(history[history.length - 1].path, history[history.length - 1].key); } +function reload() { + reloadCount++; +} + function close() { windowEl.close(); } diff --git a/packages/frontend/src/components/MkPagination.vue b/packages/frontend/src/components/MkPagination.vue index 224a42cdc2..84ba94361e 100644 --- a/packages/frontend/src/components/MkPagination.vue +++ b/packages/frontend/src/components/MkPagination.vue @@ -104,7 +104,7 @@ const { enableInfiniteScroll, } = defaultStore.reactiveState; -const contentEl = $computed(() => props.pagination.pageEl || rootEl); +const contentEl = $computed(() => props.pagination.pageEl ?? rootEl); const scrollableElement = $computed(() => getScrollContainer(contentEl)); // 先頭が表示されているかどうかを検出 diff --git a/packages/frontend/src/components/MkPoll.vue b/packages/frontend/src/components/MkPoll.vue index 33e625a058..fcbd8ad351 100644 --- a/packages/frontend/src/components/MkPoll.vue +++ b/packages/frontend/src/components/MkPoll.vue @@ -22,7 +22,7 @@ </template> <script lang="ts" setup> -import { computed, onUnmounted, ref, toRef } from 'vue'; +import { computed, ref } from 'vue'; import * as misskey from 'misskey-js'; import { sum } from '@/scripts/array'; import { pleaseLogin } from '@/scripts/please-login'; diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue index f15906c1c1..54512aa4d8 100644 --- a/packages/frontend/src/components/MkPostForm.vue +++ b/packages/frontend/src/components/MkPostForm.vue @@ -73,10 +73,8 @@ import { inject, watch, nextTick, onMounted, defineAsyncComponent } from 'vue'; import * as mfm from 'mfm-js'; import * as misskey from 'misskey-js'; import insertTextAtCursor from 'insert-text-at-cursor'; -import { length } from 'stringz'; import { toASCII } from 'punycode/'; import * as Acct from 'misskey-js/built/acct'; -import { throttle } from 'throttle-debounce'; import MkNoteSimple from '@/components/MkNoteSimple.vue'; import XNotePreview from '@/components/MkNotePreview.vue'; import XPostFormAttaches from '@/components/MkPostFormAttaches.vue'; @@ -87,7 +85,6 @@ import { extractMentions } from '@/scripts/extract-mentions'; import { formatTimeString } from '@/scripts/format-time-string'; import { Autocomplete } from '@/scripts/autocomplete'; import * as os from '@/os'; -import { stream } from '@/stream'; import { selectFiles } from '@/scripts/select-file'; import { defaultStore, notePostInterruptors, postFormActions } from '@/store'; import MkInfo from '@/components/MkInfo.vue'; @@ -157,15 +154,9 @@ let autocomplete = $ref(null); let draghover = $ref(false); let quoteId = $ref(null); let hasNotSpecifiedMentions = $ref(false); -let recentHashtags = $ref(JSON.parse(miLocalStorage.getItem('hashtags') || '[]')); +let recentHashtags = $ref(JSON.parse(miLocalStorage.getItem('hashtags') ?? '[]')); let imeText = $ref(''); -const typing = throttle(3000, () => { - if (props.channel) { - stream.send('typingOnChannel', { channel: props.channel.id }); - } -}); - const draftKey = $computed((): string => { let key = props.channel ? `channel:${props.channel.id}` : ''; @@ -209,7 +200,7 @@ const submitText = $computed((): string => { }); const textLength = $computed((): number => { - return length((text + imeText).trim()); + return (text + imeText).trim().length; }); const maxTextLength = $computed((): number => { @@ -228,7 +219,7 @@ const hashtags = $computed(defaultStore.makeGetterSetter('postFormHashtags')); watch($$(text), () => { checkMissingMention(); -}); +}, { immediate: true }); watch($$(visibleUsers), () => { checkMissingMention(); @@ -447,12 +438,10 @@ function clear() { function onKeydown(ev: KeyboardEvent) { if ((ev.which === 10 || ev.which === 13) && (ev.ctrlKey || ev.metaKey) && canPost) post(); if (ev.which === 27) emit('esc'); - typing(); } function onCompositionUpdate(ev: CompositionEvent) { imeText = ev.data; - typing(); } function onCompositionEnd(ev: CompositionEvent) { @@ -544,7 +533,7 @@ function onDrop(ev): void { } function saveDraft() { - const draftData = JSON.parse(miLocalStorage.getItem('drafts') || '{}'); + const draftData = JSON.parse(miLocalStorage.getItem('drafts') ?? '{}'); draftData[draftKey] = { updatedAt: new Date(), @@ -653,7 +642,7 @@ async function post(ev?: MouseEvent) { emit('posted'); if (postData.text && postData.text !== '') { const hashtags_ = mfm.parse(postData.text).filter(x => x.type === 'hashtag').map(x => x.props.hashtag); - const history = JSON.parse(miLocalStorage.getItem('hashtags') || '[]') as string[]; + const history = JSON.parse(miLocalStorage.getItem('hashtags') ?? '[]') as string[]; miLocalStorage.setItem('hashtags', JSON.stringify(unique(hashtags_.concat(history)))); } posting = false; @@ -757,7 +746,7 @@ onMounted(() => { nextTick(() => { // 書きかけの投稿を復元 if (!props.instant && !props.mention && !props.specified) { - const draft = JSON.parse(miLocalStorage.getItem('drafts') || '{}')[draftKey]; + const draft = JSON.parse(miLocalStorage.getItem('drafts') ?? '{}')[draftKey]; if (draft) { text = draft.data.text; useCw = draft.data.useCw; diff --git a/packages/frontend/src/components/MkPostFormAttaches.vue b/packages/frontend/src/components/MkPostFormAttaches.vue index 766cc9a06c..5fb820f03f 100644 --- a/packages/frontend/src/components/MkPostFormAttaches.vue +++ b/packages/frontend/src/components/MkPostFormAttaches.vue @@ -15,10 +15,9 @@ </template> <script lang="ts" setup> -import { defineAsyncComponent, watch } from 'vue'; +import { defineAsyncComponent } from 'vue'; import MkDriveFileThumbnail from '@/components/MkDriveFileThumbnail.vue'; import * as os from '@/os'; -import { deepClone } from '@/scripts/clone'; import { i18n } from '@/i18n'; const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default)); diff --git a/packages/frontend/src/components/MkReactedUsersDialog.vue b/packages/frontend/src/components/MkReactedUsersDialog.vue index 2a8dffc014..1506e24ce8 100644 --- a/packages/frontend/src/components/MkReactedUsersDialog.vue +++ b/packages/frontend/src/components/MkReactedUsersDialog.vue @@ -10,15 +10,21 @@ <MkSpacer :margin-min="20" :margin-max="28"> <div v-if="note" class="_gaps"> - <div :class="$style.tabs"> - <button v-for="reaction in reactions" :key="reaction" :class="[$style.tab, { [$style.tabActive]: tab === reaction }]" class="_button" @click="tab = reaction"> - <MkReactionIcon :reaction="reaction"/> - <span style="margin-left: 4px;">{{ note.reactions[reaction] }}</span> - </button> + <div v-if="reactions.length === 0" class="_fullinfo"> + <img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/> + <div>{{ i18n.ts.nothing }}</div> </div> - <MkA v-for="user in users" :key="user.id" :to="userPage(user)"> - <MkUserCardMini :user="user" :with-chart="false"/> - </MkA> + <template v-else> + <div :class="$style.tabs"> + <button v-for="reaction in reactions" :key="reaction" :class="[$style.tab, { [$style.tabActive]: tab === reaction }]" class="_button" @click="tab = reaction"> + <MkReactionIcon :reaction="reaction"/> + <span style="margin-left: 4px;">{{ note.reactions[reaction] }}</span> + </button> + </div> + <MkA v-for="user in users" :key="user.id" :to="userPage(user)"> + <MkUserCardMini :user="user" :with-chart="false"/> + </MkA> + </template> </div> <div v-else> <MkLoading/> diff --git a/packages/frontend/src/components/MkReactionsViewer.reaction.vue b/packages/frontend/src/components/MkReactionsViewer.reaction.vue index 4abd2562df..fd0f42e9fc 100644 --- a/packages/frontend/src/components/MkReactionsViewer.reaction.vue +++ b/packages/frontend/src/components/MkReactionsViewer.reaction.vue @@ -12,7 +12,7 @@ </template> <script lang="ts" setup> -import { computed, onMounted, ref, shallowRef, watch } from 'vue'; +import { computed, onMounted, shallowRef, watch } from 'vue'; import * as misskey from 'misskey-js'; import XDetails from '@/components/MkReactionsViewer.details.vue'; import MkReactionIcon from '@/components/MkReactionIcon.vue'; diff --git a/packages/frontend/src/components/MkReactionsViewer.vue b/packages/frontend/src/components/MkReactionsViewer.vue index cdd6f528e7..76faffe926 100644 --- a/packages/frontend/src/components/MkReactionsViewer.vue +++ b/packages/frontend/src/components/MkReactionsViewer.vue @@ -51,7 +51,7 @@ watch([() => props.note.reactions, () => props.maxNumber], ([newSource, maxNumbe ...Object.entries(newSource) .sort(([, a], [, b]) => b - a) .filter(([y], i) => i < maxNumber && !newReactionsNames.includes(y)), - ] + ]; newReactions = newReactions.slice(0, props.maxNumber); diff --git a/packages/frontend/src/components/MkRetentionHeatmap.vue b/packages/frontend/src/components/MkRetentionHeatmap.vue index 52c8b6d026..8326ec7ef3 100644 --- a/packages/frontend/src/components/MkRetentionHeatmap.vue +++ b/packages/frontend/src/components/MkRetentionHeatmap.vue @@ -8,14 +8,11 @@ </template> <script lang="ts" setup> -import { markRaw, version as vueVersion, onMounted, onBeforeUnmount, nextTick } from 'vue'; +import { onMounted, nextTick } from 'vue'; import { Chart } from 'chart.js'; -import tinycolor from 'tinycolor2'; -import { MatrixController, MatrixElement } from 'chartjs-chart-matrix'; import * as os from '@/os'; import { defaultStore } from '@/store'; import { useChartTooltip } from '@/scripts/use-chart-tooltip'; -import { chartVLine } from '@/scripts/chart-vline'; import { alpha } from '@/scripts/color'; import { initChart } from '@/scripts/init-chart'; diff --git a/packages/frontend/src/components/MkRolePreview.vue b/packages/frontend/src/components/MkRolePreview.vue index 2edc224b1c..2f5866f340 100644 --- a/packages/frontend/src/components/MkRolePreview.vue +++ b/packages/frontend/src/components/MkRolePreview.vue @@ -1,10 +1,15 @@ <template> -<MkA v-adaptive-bg :to="`/admin/roles/${role.id}`" class="_panel" :class="$style.root" tabindex="-1" :style="{ '--color': role.color }"> +<MkA v-adaptive-bg :to="forModeration ? `/admin/roles/${role.id}` : `/roles/${role.id}`" class="_panel" :class="$style.root" tabindex="-1" :style="{ '--color': role.color }"> <div :class="$style.title"> <span :class="$style.icon"> - <i v-if="role.isAdministrator" class="ti ti-crown" style="color: var(--accent);"></i> - <i v-else-if="role.isModerator" class="ti ti-shield" style="color: var(--accent);"></i> - <i v-else class="ti ti-user" style="opacity: 0.7;"></i> + <template v-if="role.iconUrl"> + <img :class="$style.badge" :src="role.iconUrl"/> + </template> + <template v-else> + <i v-if="role.isAdministrator" class="ti ti-crown" style="color: var(--accent);"></i> + <i v-else-if="role.isModerator" class="ti ti-shield" style="color: var(--accent);"></i> + <i v-else class="ti ti-user" style="opacity: 0.7;"></i> + </template> </span> <span :class="$style.name">{{ role.name }}</span> <span v-if="role.target === 'manual'" :class="$style.users">{{ role.usersCount }} users</span> @@ -16,12 +21,11 @@ <script lang="ts" setup> import { } from 'vue'; -import * as misskey from 'misskey-js'; -import * as os from '@/os'; import { i18n } from '@/i18n'; const props = defineProps<{ role: any; + forModeration: boolean; }>(); </script> @@ -40,6 +44,11 @@ const props = defineProps<{ margin-right: 8px; } +.badge { + height: 1.3em; + vertical-align: -20%; +} + .name { font-weight: bold; } diff --git a/packages/frontend/src/components/MkSelect.vue b/packages/frontend/src/components/MkSelect.vue index 12099153eb..2de890186a 100644 --- a/packages/frontend/src/components/MkSelect.vue +++ b/packages/frontend/src/components/MkSelect.vue @@ -27,14 +27,14 @@ </template> <script lang="ts" setup> -import { onMounted, onUnmounted, nextTick, ref, watch, computed, toRefs, VNode, useSlots } from 'vue'; +import { onMounted, nextTick, ref, watch, computed, toRefs, VNode, useSlots } from 'vue'; import MkButton from '@/components/MkButton.vue'; import * as os from '@/os'; import { useInterval } from '@/scripts/use-interval'; import { i18n } from '@/i18n'; const props = defineProps<{ - modelValue: string; + modelValue: string | null; required?: boolean; readonly?: boolean; disabled?: boolean; @@ -48,7 +48,7 @@ const props = defineProps<{ const emit = defineEmits<{ (ev: 'change', _ev: KeyboardEvent): void; - (ev: 'update:modelValue', value: string): void; + (ev: 'update:modelValue', value: string | null): void; }>(); const slots = useSlots(); diff --git a/packages/frontend/src/components/MkSignin.vue b/packages/frontend/src/components/MkSignin.vue index 8cce1d43f6..ffc5e82b56 100644 --- a/packages/frontend/src/components/MkSignin.vue +++ b/packages/frontend/src/components/MkSignin.vue @@ -10,7 +10,7 @@ <template #prefix>@</template> <template #suffix>@{{ host }}</template> </MkInput> - <MkInput v-if="!user || user && !user.usePasswordLessLogin" v-model="password" :placeholder="i18n.ts.password" type="password" :with-password-toggle="true" required data-cy-signin-password> + <MkInput v-if="!user || user && !user.usePasswordLessLogin" v-model="password" :placeholder="i18n.ts.password" type="password" autocomplete="current-password" :with-password-toggle="true" required data-cy-signin-password> <template #prefix><i class="ti ti-lock"></i></template> <template #caption><button class="_textButton" type="button" @click="resetPassword">{{ i18n.ts.forgotPassword }}</button></template> </MkInput> @@ -28,11 +28,11 @@ </div> <div class="twofa-group totp-group"> <p style="margin-bottom:0;">{{ i18n.ts.twoStepAuthentication }}</p> - <MkInput v-if="user && user.usePasswordLessLogin" v-model="password" type="password" :with-password-toggle="true" required> + <MkInput v-if="user && user.usePasswordLessLogin" v-model="password" type="password" autocomplete="current-password" :with-password-toggle="true" required> <template #label>{{ i18n.ts.password }}</template> <template #prefix><i class="ti ti-lock"></i></template> </MkInput> - <MkInput v-model="token" type="text" pattern="^[0-9]{6}$" autocomplete="off" :spellcheck="false" required> + <MkInput v-model="token" type="text" pattern="^[0-9]{6}$" autocomplete="one-time-code" :spellcheck="false" required> <template #label>{{ i18n.ts.token }}</template> <template #prefix><i class="ti ti-123"></i></template> </MkInput> @@ -50,7 +50,7 @@ import { showSuspendedDialog } from '../scripts/show-suspended-dialog'; import MkButton from '@/components/MkButton.vue'; import MkInput from '@/components/MkInput.vue'; import MkInfo from '@/components/MkInfo.vue'; -import { apiUrl, host as configHost } from '@/config'; +import { host as configHost } from '@/config'; import { byteify, hexify } from '@/scripts/2fa'; import * as os from '@/os'; import { login } from '@/account'; diff --git a/packages/frontend/src/components/MkSuperMenu.vue b/packages/frontend/src/components/MkSuperMenu.vue index 5d33ad0ad3..2a8e43c570 100644 --- a/packages/frontend/src/components/MkSuperMenu.vue +++ b/packages/frontend/src/components/MkSuperMenu.vue @@ -24,7 +24,7 @@ </template> <script lang="ts"> -import { defineComponent, ref, unref } from 'vue'; +import { defineComponent } from 'vue'; export default defineComponent({ props: { diff --git a/packages/frontend/src/components/MkSwitch.vue b/packages/frontend/src/components/MkSwitch.vue index 133f437ca1..8bb8637dda 100644 --- a/packages/frontend/src/components/MkSwitch.vue +++ b/packages/frontend/src/components/MkSwitch.vue @@ -22,7 +22,6 @@ <script lang="ts" setup> import { toRefs, Ref } from 'vue'; -import * as os from '@/os'; import { i18n } from '@/i18n'; const props = defineProps<{ diff --git a/packages/frontend/src/components/MkTagCloud.vue b/packages/frontend/src/components/MkTagCloud.vue index 9f7e76f18e..4e8d5bab7f 100644 --- a/packages/frontend/src/components/MkTagCloud.vue +++ b/packages/frontend/src/components/MkTagCloud.vue @@ -10,7 +10,7 @@ </template> <script lang="ts" setup> -import { onMounted, ref, watch, PropType, onBeforeUnmount } from 'vue'; +import { onMounted, watch, onBeforeUnmount } from 'vue'; import tinycolor from 'tinycolor2'; const loaded = !!window.TagCanvas; diff --git a/packages/frontend/src/components/MkTextarea.vue b/packages/frontend/src/components/MkTextarea.vue index 0147ac7f83..82b631edda 100644 --- a/packages/frontend/src/components/MkTextarea.vue +++ b/packages/frontend/src/components/MkTextarea.vue @@ -27,7 +27,7 @@ </template> <script lang="ts"> -import { defineComponent, onMounted, onUnmounted, nextTick, ref, watch, computed, toRefs } from 'vue'; +import { defineComponent, onMounted, nextTick, ref, watch, computed, toRefs } from 'vue'; import { debounce } from 'throttle-debounce'; import MkButton from '@/components/MkButton.vue'; import { i18n } from '@/i18n'; diff --git a/packages/frontend/src/components/MkTimeline.vue b/packages/frontend/src/components/MkTimeline.vue index 831a194ce3..87f7c61a92 100644 --- a/packages/frontend/src/components/MkTimeline.vue +++ b/packages/frontend/src/components/MkTimeline.vue @@ -1,11 +1,10 @@ <template> -<XNotes ref="tlComponent" :no-gap="!$store.state.showGapBetweenNotesInTimeline" :pagination="pagination" @queue="emit('queue', $event)"/> +<MkNotes ref="tlComponent" :no-gap="!$store.state.showGapBetweenNotesInTimeline" :pagination="pagination" @queue="emit('queue', $event)"/> </template> <script lang="ts" setup> -import { ref, computed, provide, onUnmounted } from 'vue'; -import XNotes from '@/components/MkNotes.vue'; -import * as os from '@/os'; +import { computed, provide, onUnmounted } from 'vue'; +import MkNotes from '@/components/MkNotes.vue'; import { stream } from '@/stream'; import * as sound from '@/scripts/sound'; import { $i } from '@/account'; @@ -25,7 +24,7 @@ const emit = defineEmits<{ provide('inChannel', computed(() => props.src === 'channel')); -const tlComponent: InstanceType<typeof XNotes> = $ref(); +const tlComponent: InstanceType<typeof MkNotes> = $ref(); const prepend = note => { tlComponent.pagingComponent?.prepend(note); diff --git a/packages/frontend/src/components/MkToast.vue b/packages/frontend/src/components/MkToast.vue index 95ab23f76d..1aa48f88e6 100644 --- a/packages/frontend/src/components/MkToast.vue +++ b/packages/frontend/src/components/MkToast.vue @@ -17,7 +17,7 @@ </template> <script lang="ts" setup> -import { onMounted, ref } from 'vue'; +import { onMounted } from 'vue'; import * as os from '@/os'; defineProps<{ diff --git a/packages/frontend/src/components/MkTooltip.vue b/packages/frontend/src/components/MkTooltip.vue index 17c8ea0128..0b0556de39 100644 --- a/packages/frontend/src/components/MkTooltip.vue +++ b/packages/frontend/src/components/MkTooltip.vue @@ -16,7 +16,7 @@ </template> <script lang="ts" setup> -import { nextTick, onMounted, onUnmounted, ref, shallowRef } from 'vue'; +import { nextTick, onMounted, onUnmounted, shallowRef } from 'vue'; import * as os from '@/os'; import { calcPopupPosition } from '@/scripts/popup-position'; diff --git a/packages/frontend/src/components/MkUrlPreview.vue b/packages/frontend/src/components/MkUrlPreview.vue index 62e58e1553..b97b7cf07b 100644 --- a/packages/frontend/src/components/MkUrlPreview.vue +++ b/packages/frontend/src/components/MkUrlPreview.vue @@ -45,7 +45,7 @@ </template> <script lang="ts" setup> -import { defineAsyncComponent, onMounted, onUnmounted } from 'vue'; +import { defineAsyncComponent, onUnmounted } from 'vue'; import { url as local } from '@/config'; import { i18n } from '@/i18n'; import * as os from '@/os'; diff --git a/packages/frontend/src/components/MkUserList.vue b/packages/frontend/src/components/MkUserList.vue index b7bc200a55..51eb426e97 100644 --- a/packages/frontend/src/components/MkUserList.vue +++ b/packages/frontend/src/components/MkUserList.vue @@ -7,25 +7,26 @@ </div> </template> - <template #default="{ items: users }"> + <template #default="{ items }"> <div class="efvhhmdq"> - <MkUserInfo v-for="user in users" :key="user.id" class="user" :user="user"/> + <MkUserInfo v-for="item in items" :key="item.id" class="user" :user="extractor(item)"/> </div> </template> </MkPagination> </template> <script lang="ts" setup> -import { shallowRef } from 'vue'; import MkUserInfo from '@/components/MkUserInfo.vue'; import MkPagination, { Paging } from '@/components/MkPagination.vue'; -import { userPage } from '@/filters/user'; import { i18n } from '@/i18n'; -const props = defineProps<{ +const props = withDefaults(defineProps<{ pagination: Paging; noGap?: boolean; -}>(); + extractor?: (item: any) => any; +}>(), { + extractor: (item) => item, +}); </script> <style lang="scss" scoped> diff --git a/packages/frontend/src/components/MkUserPreview.vue b/packages/frontend/src/components/MkUserPreview.vue index eacc66de4f..1086a2c651 100644 --- a/packages/frontend/src/components/MkUserPreview.vue +++ b/packages/frontend/src/components/MkUserPreview.vue @@ -89,7 +89,7 @@ onMounted(() => { <style lang="scss" scoped> .popup-enter-active, .popup-leave-active { - transition: opacity 0.3s, transform 0.3s !important; + transition: opacity 0.15s, transform 0.15s !important; } .popup-enter-from, .popup-leave-to { opacity: 0; @@ -183,8 +183,10 @@ onMounted(() => { > .menu { position: absolute; top: 8px; - right: 42px; - padding: 8px; + right: 44px; + padding: 6px; + background: var(--panel); + border-radius: 999px; } > .koudoku-button { diff --git a/packages/frontend/src/components/MkUserSelectDialog.vue b/packages/frontend/src/components/MkUserSelectDialog.vue index c17b97f283..dc78bbf42d 100644 --- a/packages/frontend/src/components/MkUserSelectDialog.vue +++ b/packages/frontend/src/components/MkUserSelectDialog.vue @@ -16,7 +16,7 @@ <template #label>{{ i18n.ts.username }}</template> <template #prefix>@</template> </MkInput> - <MkInput v-model="host" @update:model-value="search"> + <MkInput v-model="host" :datalist="[hostname]" @update:model-value="search"> <template #label>{{ i18n.ts.host }}</template> <template #prefix>@</template> </MkInput> @@ -52,7 +52,7 @@ </template> <script lang="ts" setup> -import { nextTick, onMounted } from 'vue'; +import { onMounted } from 'vue'; import * as misskey from 'misskey-js'; import MkInput from '@/components/MkInput.vue'; import FormSplit from '@/components/form/split.vue'; @@ -61,6 +61,7 @@ import * as os from '@/os'; import { defaultStore } from '@/store'; import { i18n } from '@/i18n'; import { $i } from '@/account'; +import { hostname } from '@/config'; const emit = defineEmits<{ (ev: 'ok', selected: misskey.entities.UserDetailed): void; @@ -115,7 +116,7 @@ onMounted(() => { os.api('users/show', { userIds: defaultStore.state.recentlyUsedUsers, }).then(users => { - if (props.includeSelf) { + if (props.includeSelf && users.find(x => $i ? x.id === $i.id : true) == null) { recentUsers = [$i, ...users]; } else { recentUsers = users; diff --git a/packages/frontend/src/components/MkWidgets.vue b/packages/frontend/src/components/MkWidgets.vue index eff64c12e5..19c735c5f8 100644 --- a/packages/frontend/src/components/MkWidgets.vue +++ b/packages/frontend/src/components/MkWidgets.vue @@ -43,14 +43,13 @@ export type DefaultStoredWidget = { } & Widget; </script> <script lang="ts" setup> -import { defineAsyncComponent, reactive, ref, computed } from 'vue'; +import { defineAsyncComponent, ref } from 'vue'; import { v4 as uuid } from 'uuid'; import MkSelect from '@/components/MkSelect.vue'; import MkButton from '@/components/MkButton.vue'; import { widgets as widgetDefs } from '@/widgets'; import * as os from '@/os'; import { i18n } from '@/i18n'; -import { deepClone } from '@/scripts/clone'; const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default)); diff --git a/packages/frontend/src/components/global/MkA.vue b/packages/frontend/src/components/global/MkA.vue index 5a0ba0d8d3..40d134dffb 100644 --- a/packages/frontend/src/components/global/MkA.vue +++ b/packages/frontend/src/components/global/MkA.vue @@ -5,7 +5,6 @@ </template> <script lang="ts" setup> -import { inject } from 'vue'; import * as os from '@/os'; import copyToClipboard from '@/scripts/copy-to-clipboard'; import { url } from '@/config'; diff --git a/packages/frontend/src/components/global/MkAvatar.vue b/packages/frontend/src/components/global/MkAvatar.vue index 9ad06545f2..d392ec6d6f 100644 --- a/packages/frontend/src/components/global/MkAvatar.vue +++ b/packages/frontend/src/components/global/MkAvatar.vue @@ -18,7 +18,7 @@ </template> <script lang="ts" setup> -import { onMounted, watch } from 'vue'; +import { watch } from 'vue'; import * as misskey from 'misskey-js'; import { getStaticImageUrl } from '@/scripts/media-proxy'; import { extractAvgColorFromBlurhash } from '@/scripts/extract-avg-color-from-blurhash'; diff --git a/packages/frontend/src/components/global/MkCustomEmoji.vue b/packages/frontend/src/components/global/MkCustomEmoji.vue index e6dedd0354..82aad44c1f 100644 --- a/packages/frontend/src/components/global/MkCustomEmoji.vue +++ b/packages/frontend/src/components/global/MkCustomEmoji.vue @@ -24,7 +24,7 @@ const rawUrl = computed(() => { return props.url; } if (props.host == null && !customEmojiName.value.includes('@')) { - return customEmojis.value.find(x => x.name === customEmojiName.value)?.url || null; + return customEmojis.value.find(x => x.name === customEmojiName.value)?.url ?? null; } return props.host ? `/emoji/${customEmojiName.value}@${props.host}.webp` : `/emoji/${customEmojiName.value}.webp`; }); @@ -32,7 +32,7 @@ const rawUrl = computed(() => { const url = computed(() => defaultStore.reactiveState.disableShowingAnimatedImages.value && rawUrl.value ? getStaticImageUrl(rawUrl.value) - : rawUrl.value + : rawUrl.value, ); const alt = computed(() => `:${customEmojiName.value}:`); diff --git a/packages/frontend/src/components/global/MkError.vue b/packages/frontend/src/components/global/MkError.vue index d412934a31..7390a9dfb9 100644 --- a/packages/frontend/src/components/global/MkError.vue +++ b/packages/frontend/src/components/global/MkError.vue @@ -3,7 +3,7 @@ <div :class="$style.root"> <img :class="$style.img" src="https://xn--931a.moe/assets/error.jpg" class="_ghost"/> <p :class="$style.text"><i class="ti ti-alert-triangle"></i> {{ i18n.ts.somethingHappened }}</p> - <MkButton :class="$style.button" @click="() => $emit('retry')">{{ i18n.ts.retry }}</MkButton> + <MkButton :class="$style.button" @click="() => emit('retry')">{{ i18n.ts.retry }}</MkButton> </div> </Transition> </template> @@ -11,6 +11,10 @@ <script lang="ts" setup> import MkButton from '@/components/MkButton.vue'; import { i18n } from '@/i18n'; + +const emit = defineEmits<{ + (ev: 'retry'): void; +}>(); </script> <style lang="scss" module> diff --git a/packages/frontend/src/components/global/MkPageHeader.tabs.vue b/packages/frontend/src/components/global/MkPageHeader.tabs.vue index dae68c7e9c..3beedf34b2 100644 --- a/packages/frontend/src/components/global/MkPageHeader.tabs.vue +++ b/packages/frontend/src/components/global/MkPageHeader.tabs.vue @@ -1,23 +1,33 @@ <template> - <div ref="el" :class="$style.tabs" @wheel="onTabWheel"> - <div :class="$style.tabsInner"> - <button v-for="t in tabs" :ref="(el) => tabRefs[t.key] = (el as HTMLElement)" v-tooltip.noDelay="t.title" - class="_button" :class="[$style.tab, { [$style.active]: t.key != null && t.key === props.tab, [$style.animate]: defaultStore.reactiveState.animation.value }]" - @mousedown="(ev) => onTabMousedown(t, ev)" @click="(ev) => onTabClick(t, ev)"> - <div :class="$style.tabInner"> - <i v-if="t.icon" :class="[$style.tabIcon, t.icon]"></i> - <div v-if="!t.iconOnly || (!defaultStore.reactiveState.animation.value && t.key === tab)" - :class="$style.tabTitle">{{ t.title }}</div> - <Transition v-else @enter="enter" @after-enter="afterEnter" @leave="leave" @after-leave="afterLeave" - mode="in-out"> - <div v-show="t.key === tab" :class="[$style.tabTitle, $style.animate]">{{ t.title }}</div> - </Transition> +<div ref="el" :class="$style.tabs" @wheel="onTabWheel"> + <div :class="$style.tabsInner"> + <button + v-for="t in tabs" :ref="(el) => tabRefs[t.key] = (el as HTMLElement)" v-tooltip.noDelay="t.title" + class="_button" :class="[$style.tab, { [$style.active]: t.key != null && t.key === props.tab, [$style.animate]: defaultStore.reactiveState.animation.value }]" + @mousedown="(ev) => onTabMousedown(t, ev)" @click="(ev) => onTabClick(t, ev)" + > + <div :class="$style.tabInner"> + <i v-if="t.icon" :class="[$style.tabIcon, t.icon]"></i> + <div + v-if="!t.iconOnly || (!defaultStore.reactiveState.animation.value && t.key === tab)" + :class="$style.tabTitle" + > + {{ t.title }} </div> - </button> - </div> - <div ref="tabHighlightEl" - :class="[$style.tabHighlight, { [$style.animate]: defaultStore.reactiveState.animation.value }]"></div> + <Transition + v-else mode="in-out" @enter="enter" @after-enter="afterEnter" @leave="leave" + @after-leave="afterLeave" + > + <div v-show="t.key === tab" :class="[$style.tabTitle, $style.animate]">{{ t.title }}</div> + </Transition> + </div> + </button> </div> + <div + ref="tabHighlightEl" + :class="[$style.tabHighlight, { [$style.animate]: defaultStore.reactiveState.animation.value }]" + ></div> +</div> </template> <script lang="ts"> @@ -34,7 +44,7 @@ export type Tab = { </script> <script lang="ts" setup> -import { onMounted, onUnmounted, watch, nextTick, Transition, shallowRef } from 'vue'; +import { onMounted, onUnmounted, watch, nextTick, shallowRef } from 'vue'; import { defaultStore } from '@/store'; const props = withDefaults(defineProps<{ @@ -206,8 +216,8 @@ onUnmounted(() => { align-items: center; } -.tabIcon+.tabTitle { - padding-left: 8px; +.tabIcon + .tabTitle { + padding-left: 4px; } .tabTitle { diff --git a/packages/frontend/src/components/global/MkPageHeader.vue b/packages/frontend/src/components/global/MkPageHeader.vue index 803efb1690..a4e25bbe1a 100644 --- a/packages/frontend/src/components/global/MkPageHeader.vue +++ b/packages/frontend/src/components/global/MkPageHeader.vue @@ -2,9 +2,9 @@ <div v-if="show" ref="el" :class="[$style.root]" :style="{ background: bg }"> <div :class="[$style.upper, { [$style.slim]: narrow, [$style.thin]: thin_ }]"> <div v-if="!thin_ && narrow && props.displayMyAvatar && $i" class="_button" :class="$style.buttonsLeft" @click="openAccountMenu"> - <MkAvatar :class="$style.avatar" :user="$i" /> + <MkAvatar :class="$style.avatar" :user="$i"/> </div> - <div v-else-if="!thin_ && narrow && !hideTitle" :class="$style.buttonsLeft" /> + <div v-else-if="!thin_ && narrow && !hideTitle" :class="$style.buttonsLeft"/> <template v-if="metadata"> <div v-if="!hideTitle" :class="$style.titleContainer" @click="top"> @@ -19,7 +19,7 @@ </div> </div> </div> - <XTabs v-if="!narrow || hideTitle" :class="$style.tabs" :tab="tab" @update:tab="key => emit('update:tab', key)" :tabs="tabs" :root-el="el" @tab-click="onTabClick"/> + <XTabs v-if="!narrow || hideTitle" :class="$style.tabs" :tab="tab" :tabs="tabs" :root-el="el" @update:tab="key => emit('update:tab', key)" @tab-click="onTabClick"/> </template> <div v-if="(!thin_ && narrow && !hideTitle) || (actions && actions.length > 0)" :class="$style.buttonsRight"> <template v-for="action in actions"> @@ -28,7 +28,7 @@ </div> </div> <div v-if="(narrow && !hideTitle) && hasTabs" :class="[$style.lower, { [$style.slim]: narrow, [$style.thin]: thin_ }]"> - <XTabs :class="$style.tabs" :tab="tab" @update:tab="key => emit('update:tab', key)" :tabs="tabs" :root-el="el" @tab-click="onTabClick"/> + <XTabs :class="$style.tabs" :tab="tab" :tabs="tabs" :root-el="el" @update:tab="key => emit('update:tab', key)" @tab-click="onTabClick"/> </div> </div> </template> @@ -36,11 +36,11 @@ <script lang="ts" setup> import { onMounted, onUnmounted, ref, inject } from 'vue'; import tinycolor from 'tinycolor2'; +import XTabs, { Tab } from './MkPageHeader.tabs.vue'; import { scrollToTop } from '@/scripts/scroll'; import { globalEvents } from '@/events'; import { injectPageMetadata } from '@/scripts/page-metadata'; import { $i, openAccountMenu as openAccountMenu_ } from '@/account'; -import XTabs, { Tab } from './MkPageHeader.tabs.vue' const props = withDefaults(defineProps<{ tabs?: Tab[]; @@ -96,7 +96,7 @@ function onTabClick(): void { } const calcBg = () => { - const rawBg = metadata?.bg || 'var(--bg)'; + const rawBg = metadata?.bg ?? 'var(--bg)'; const tinyBg = tinycolor(rawBg.startsWith('var(') ? getComputedStyle(document.documentElement).getPropertyValue(rawBg.slice(4, -1)) : rawBg); tinyBg.setAlpha(0.85); bg.value = tinyBg.toRgbString(); diff --git a/packages/frontend/src/components/global/MkSpacer.vue b/packages/frontend/src/components/global/MkSpacer.vue index 78e9a1a9c2..ba7c0400c7 100644 --- a/packages/frontend/src/components/global/MkSpacer.vue +++ b/packages/frontend/src/components/global/MkSpacer.vue @@ -7,7 +7,7 @@ </template> <script lang="ts" setup> -import { inject, onMounted, onUnmounted, ref } from 'vue'; +import { inject } from 'vue'; import { deviceKind } from '@/scripts/device-kind'; const props = withDefaults(defineProps<{ diff --git a/packages/frontend/src/components/global/RouterView.vue b/packages/frontend/src/components/global/RouterView.vue index e5270ffefa..5763c84e81 100644 --- a/packages/frontend/src/components/global/RouterView.vue +++ b/packages/frontend/src/components/global/RouterView.vue @@ -11,7 +11,7 @@ </template> <script lang="ts" setup> -import { inject, nextTick, onBeforeUnmount, onMounted, onUnmounted, provide, watch } from 'vue'; +import { inject, onBeforeUnmount, provide } from 'vue'; import { Resolved, Router } from '@/nirax'; import { defaultStore } from '@/store'; diff --git a/packages/frontend/src/components/mfm.ts b/packages/frontend/src/components/mfm.ts index 816a42a5fb..e84eabcbcc 100644 --- a/packages/frontend/src/components/mfm.ts +++ b/packages/frontend/src/components/mfm.ts @@ -5,13 +5,11 @@ import MkLink from '@/components/MkLink.vue'; import MkMention from '@/components/MkMention.vue'; import MkEmoji from '@/components/global/MkEmoji.vue'; import MkCustomEmoji from '@/components/global/MkCustomEmoji.vue'; -import { concat } from '@/scripts/array'; import MkCode from '@/components/MkCode.vue'; import MkGoogle from '@/components/MkGoogle.vue'; import MkSparkle from '@/components/MkSparkle.vue'; import MkA from '@/components/global/MkA.vue'; import { host } from '@/config'; -import { MFM_TAGS } from '@/scripts/mfm-tags'; import { defaultStore } from '@/store'; const QUOTE_STYLE = ` @@ -280,7 +278,7 @@ export default defineComponent({ case 'hashtag': { return [h(MkA, { key: Math.random(), - to: this.isNote ? `/tags/${encodeURIComponent(token.props.hashtag)}` : `/explore/tags/${encodeURIComponent(token.props.hashtag)}`, + to: this.isNote ? `/tags/${encodeURIComponent(token.props.hashtag)}` : `/user-tags/${encodeURIComponent(token.props.hashtag)}`, style: 'color:var(--hashtag);', }, `#${token.props.hashtag}`)]; } diff --git a/packages/frontend/src/components/page/page.canvas.vue b/packages/frontend/src/components/page/page.canvas.vue index 80f6c8339c..82ff36ec36 100644 --- a/packages/frontend/src/components/page/page.canvas.vue +++ b/packages/frontend/src/components/page/page.canvas.vue @@ -6,7 +6,6 @@ <script lang="ts"> import { defineComponent, onMounted, PropType, Ref, ref } from 'vue'; -import * as os from '@/os'; import { CanvasBlock } from '@/scripts/hpml/block'; import { Hpml } from '@/scripts/hpml/evaluator'; diff --git a/packages/frontend/src/components/page/page.counter.vue b/packages/frontend/src/components/page/page.counter.vue index a9e1f41a54..63fde6a120 100644 --- a/packages/frontend/src/components/page/page.counter.vue +++ b/packages/frontend/src/components/page/page.counter.vue @@ -7,7 +7,6 @@ <script lang="ts"> import { computed, defineComponent, PropType } from 'vue'; import MkButton from '../MkButton.vue'; -import * as os from '@/os'; import { CounterVarBlock } from '@/scripts/hpml/block'; import { Hpml } from '@/scripts/hpml/evaluator'; diff --git a/packages/frontend/src/components/page/page.image.vue b/packages/frontend/src/components/page/page.image.vue index 8ba70c5855..0237644d29 100644 --- a/packages/frontend/src/components/page/page.image.vue +++ b/packages/frontend/src/components/page/page.image.vue @@ -5,9 +5,8 @@ </template> <script lang="ts" setup> -import { defineComponent, PropType } from 'vue'; +import { PropType } from 'vue'; import ImgWithBlurhash from '@/components/MkImgWithBlurhash.vue'; -import * as os from '@/os'; import { ImageBlock } from '@/scripts/hpml/block'; import { Hpml } from '@/scripts/hpml/evaluator'; diff --git a/packages/frontend/src/components/page/page.number-input.vue b/packages/frontend/src/components/page/page.number-input.vue index 4c5aae1040..72c1b6deb0 100644 --- a/packages/frontend/src/components/page/page.number-input.vue +++ b/packages/frontend/src/components/page/page.number-input.vue @@ -9,7 +9,6 @@ <script lang="ts"> import { computed, defineComponent, PropType } from 'vue'; import MkInput from '../MkInput.vue'; -import * as os from '@/os'; import { Hpml } from '@/scripts/hpml/evaluator'; import { NumberInputVarBlock } from '@/scripts/hpml/block'; diff --git a/packages/frontend/src/components/page/page.radio-button.vue b/packages/frontend/src/components/page/page.radio-button.vue index 2ae8d00ffc..ce8f252e44 100644 --- a/packages/frontend/src/components/page/page.radio-button.vue +++ b/packages/frontend/src/components/page/page.radio-button.vue @@ -8,7 +8,6 @@ <script lang="ts"> import { computed, defineComponent, PropType } from 'vue'; import MkRadio from '../MkRadio.vue'; -import * as os from '@/os'; import { Hpml } from '@/scripts/hpml/evaluator'; import { RadioButtonVarBlock } from '@/scripts/hpml/block'; diff --git a/packages/frontend/src/components/page/page.section.vue b/packages/frontend/src/components/page/page.section.vue index 630c1f5179..50181b3905 100644 --- a/packages/frontend/src/components/page/page.section.vue +++ b/packages/frontend/src/components/page/page.section.vue @@ -10,7 +10,6 @@ <script lang="ts"> import { defineComponent, defineAsyncComponent, PropType } from 'vue'; -import * as os from '@/os'; import { SectionBlock } from '@/scripts/hpml/block'; import { Hpml } from '@/scripts/hpml/evaluator'; diff --git a/packages/frontend/src/components/page/page.switch.vue b/packages/frontend/src/components/page/page.switch.vue index d0d2637afa..b5f3464512 100644 --- a/packages/frontend/src/components/page/page.switch.vue +++ b/packages/frontend/src/components/page/page.switch.vue @@ -7,7 +7,6 @@ <script lang="ts"> import { computed, defineComponent, PropType } from 'vue'; import MkSwitch from '../MkSwitch.vue'; -import * as os from '@/os'; import { Hpml } from '@/scripts/hpml/evaluator'; import { SwitchVarBlock } from '@/scripts/hpml/block'; diff --git a/packages/frontend/src/components/page/page.text-input.vue b/packages/frontend/src/components/page/page.text-input.vue index 50731a9c9d..d020a99de8 100644 --- a/packages/frontend/src/components/page/page.text-input.vue +++ b/packages/frontend/src/components/page/page.text-input.vue @@ -9,7 +9,6 @@ <script lang="ts"> import { computed, defineComponent, PropType } from 'vue'; import MkInput from '../MkInput.vue'; -import * as os from '@/os'; import { Hpml } from '@/scripts/hpml/evaluator'; import { TextInputVarBlock } from '@/scripts/hpml/block'; diff --git a/packages/frontend/src/components/page/page.textarea-input.vue b/packages/frontend/src/components/page/page.textarea-input.vue index 7905c7eded..db3a96dd1b 100644 --- a/packages/frontend/src/components/page/page.textarea-input.vue +++ b/packages/frontend/src/components/page/page.textarea-input.vue @@ -9,9 +9,7 @@ <script lang="ts"> import { computed, defineComponent, PropType } from 'vue'; import MkTextarea from '../MkTextarea.vue'; -import * as os from '@/os'; import { Hpml } from '@/scripts/hpml/evaluator'; -import { HpmlTextInput } from '@/scripts/hpml'; import { TextInputVarBlock } from '@/scripts/hpml/block'; export default defineComponent({ diff --git a/packages/frontend/src/components/page/page.vue b/packages/frontend/src/components/page/page.vue index 87a288befe..5f1f62581e 100644 --- a/packages/frontend/src/components/page/page.vue +++ b/packages/frontend/src/components/page/page.vue @@ -5,12 +5,11 @@ </template> <script lang="ts"> -import { defineComponent, onMounted, nextTick, onUnmounted, PropType } from 'vue'; +import { defineComponent, onMounted, nextTick, PropType } from 'vue'; import XBlock from './page.block.vue'; import { Hpml } from '@/scripts/hpml/evaluator'; import { url } from '@/config'; import { $i } from '@/account'; -import { defaultStore } from '@/store'; export default defineComponent({ components: { diff --git a/packages/frontend/src/init.ts b/packages/frontend/src/init.ts index 64c252ce55..8c657295f9 100644 --- a/packages/frontend/src/init.ts +++ b/packages/frontend/src/init.ts @@ -25,7 +25,7 @@ import JSON5 from 'json5'; import widgets from '@/widgets'; import directives from '@/directives'; import components from '@/components'; -import { version, ui, lang, host, updateLocale } from '@/config'; +import { version, ui, lang, updateLocale } from '@/config'; import { applyTheme } from '@/scripts/theme'; import { isDeviceDarkmode } from '@/scripts/is-device-darkmode'; import { i18n, updateI18n } from '@/i18n'; @@ -505,15 +505,6 @@ if ($i) { updateAccount({ hasUnreadSpecifiedNotes: false }); }); - main.on('readAllMessagingMessages', () => { - updateAccount({ hasUnreadMessagingMessage: false }); - }); - - main.on('unreadMessagingMessage', () => { - updateAccount({ hasUnreadMessagingMessage: true }); - sound.play('chatBg'); - }); - main.on('readAllAntennas', () => { updateAccount({ hasUnreadAntenna: false }); }); diff --git a/packages/frontend/src/instance.ts b/packages/frontend/src/instance.ts index 08dbd9737c..f4c1988704 100644 --- a/packages/frontend/src/instance.ts +++ b/packages/frontend/src/instance.ts @@ -1,4 +1,4 @@ -import { computed, reactive } from 'vue'; +import { reactive } from 'vue'; import * as Misskey from 'misskey-js'; import { api } from './os'; import { miLocalStorage } from './local-storage'; diff --git a/packages/frontend/src/navbar.ts b/packages/frontend/src/navbar.ts index 4f809d888e..95bf6e8181 100644 --- a/packages/frontend/src/navbar.ts +++ b/packages/frontend/src/navbar.ts @@ -1,4 +1,4 @@ -import { computed, ref, reactive } from 'vue'; +import { computed, reactive } from 'vue'; import { $i } from './account'; import { miLocalStorage } from './local-storage'; import { search } from '@/scripts/search'; @@ -15,13 +15,6 @@ export const navbarItemDef = reactive({ indicated: computed(() => $i != null && $i.hasUnreadNotification), to: '/my/notifications', }, - messaging: { - title: i18n.ts.messaging, - icon: 'ti ti-messages', - show: computed(() => $i != null), - indicated: computed(() => $i != null && $i.hasUnreadMessagingMessage), - to: '/my/messaging', - }, drive: { title: i18n.ts.drive, icon: 'ti ti-cloud', @@ -57,14 +50,6 @@ export const navbarItemDef = reactive({ show: computed(() => $i != null), to: '/my/lists', }, - /* - groups: { - title: i18n.ts.groups, - icon: 'ti ti-users', - show: computed(() => $i != null), - to: '/my/groups', - }, - */ antennas: { title: i18n.ts.antennas, icon: 'ti ti-antenna', diff --git a/packages/frontend/src/nirax.ts b/packages/frontend/src/nirax.ts index 53e73a8d48..68977ed796 100644 --- a/packages/frontend/src/nirax.ts +++ b/packages/frontend/src/nirax.ts @@ -1,7 +1,7 @@ // NIRAX --- A lightweight router import { EventEmitter } from 'eventemitter3'; -import { Ref, Component, ref, shallowRef, ShallowRef } from 'vue'; +import { Component, shallowRef, ShallowRef } from 'vue'; import { pleaseLogin } from '@/scripts/please-login'; import { safeURIDecode } from '@/scripts/safe-uri-decode'; diff --git a/packages/frontend/src/os.ts b/packages/frontend/src/os.ts index 6bff12661f..a69fe73f30 100644 --- a/packages/frontend/src/os.ts +++ b/packages/frontend/src/os.ts @@ -246,7 +246,10 @@ export function inputText(props: { title?: string | null; text?: string | null; placeholder?: string | null; + autocomplete?: string; default?: string | null; + minLength?: number; + maxLength?: number; }): Promise<{ canceled: true; result: undefined; } | { canceled: false; result: string; }> { @@ -257,7 +260,10 @@ export function inputText(props: { input: { type: props.type, placeholder: props.placeholder, + autocomplete: props.autocomplete, default: props.default, + minLength: props.minLength, + maxLength: props.maxLength, }, }, { done: result => { @@ -271,6 +277,7 @@ export function inputNumber(props: { title?: string | null; text?: string | null; placeholder?: string | null; + autocomplete?: string; default?: number | null; }): Promise<{ canceled: true; result: undefined; } | { canceled: false; result: number; @@ -282,6 +289,7 @@ export function inputNumber(props: { input: { type: 'number', placeholder: props.placeholder, + autocomplete: props.autocomplete, default: props.default, }, }, { diff --git a/packages/frontend/src/pages/about-misskey.vue b/packages/frontend/src/pages/about-misskey.vue index bc3d248193..782fe9fdb2 100644 --- a/packages/frontend/src/pages/about-misskey.vue +++ b/packages/frontend/src/pages/about-misskey.vue @@ -84,6 +84,10 @@ </div> <p>{{ i18n.ts._aboutMisskey.morePatrons }}</p> </FormSection> + <FormSection> + <template #label>Credits</template> + <p>Misskeyで使われる画像の一部は、許可を得て「あの子がこっちを見てるメーカー」で作成したものが含まれます。</p> + </FormSection> </div> </MkSpacer> </div> @@ -111,6 +115,12 @@ const patronsWithIcon = [{ }, { name: 'だれかさん', icon: 'https://misskey-hub.net/patrons/f7409b5e5a88477a9b9d740c408de125.jpg', +}, { + name: 'narazaka', + icon: 'https://misskey-hub.net/patrons/e3affff31ffb4877b1196c7360abc3e5.jpg', +}, { + name: 'ひとぅ', + icon: 'https://misskey-hub.net/patrons/8cc0d0a0a6d84c88bca1aedabf6ed5ab.jpg', }]; const patrons = [ diff --git a/packages/frontend/src/pages/about.emojis.vue b/packages/frontend/src/pages/about.emojis.vue index d964e48b31..7f3b4fd937 100644 --- a/packages/frontend/src/pages/about.emojis.vue +++ b/packages/frontend/src/pages/about.emojis.vue @@ -31,14 +31,11 @@ </template> <script lang="ts" setup> -import { defineComponent, computed, watch } from 'vue'; +import { watch } from 'vue'; import XEmoji from './emojis.emoji.vue'; import MkButton from '@/components/MkButton.vue'; import MkInput from '@/components/MkInput.vue'; -import MkSelect from '@/components/MkSelect.vue'; import MkFoldableSection from '@/components/MkFoldableSection.vue'; -import MkTab from '@/components/MkTab.vue'; -import * as os from '@/os'; import { customEmojis, customEmojiCategories, getCustomEmojiTags } from '@/custom-emojis'; import { i18n } from '@/i18n'; import * as Misskey from 'misskey-js'; diff --git a/packages/frontend/src/pages/about.federation.vue b/packages/frontend/src/pages/about.federation.vue index 90d6893f37..8fe613a9a8 100644 --- a/packages/frontend/src/pages/about.federation.vue +++ b/packages/frontend/src/pages/about.federation.vue @@ -46,15 +46,12 @@ <script lang="ts" setup> import { computed } from 'vue'; -import MkButton from '@/components/MkButton.vue'; import MkInput from '@/components/MkInput.vue'; import MkSelect from '@/components/MkSelect.vue'; import MkPagination from '@/components/MkPagination.vue'; import MkInstanceCardMini from '@/components/MkInstanceCardMini.vue'; import FormSplit from '@/components/form/split.vue'; -import * as os from '@/os'; import { i18n } from '@/i18n'; -import { dateString } from '@/filters/date'; let host = $ref(''); let state = $ref('federating'); diff --git a/packages/frontend/src/pages/about.vue b/packages/frontend/src/pages/about.vue index e5b9aecc61..be0c1828a3 100644 --- a/packages/frontend/src/pages/about.vue +++ b/packages/frontend/src/pages/about.vue @@ -86,10 +86,10 @@ </template> <script lang="ts" setup> -import { ref, computed, watch } from 'vue'; +import { computed, watch } from 'vue'; import XEmojis from './about.emojis.vue'; import XFederation from './about.federation.vue'; -import { version, instanceName, host } from '@/config'; +import { version, host } from '@/config'; import FormLink from '@/components/form/link.vue'; import FormSection from '@/components/form/section.vue'; import FormSuspense from '@/components/form/suspense.vue'; diff --git a/packages/frontend/src/pages/achievements.vue b/packages/frontend/src/pages/achievements.vue index effa2fcaf4..1eef7a53fe 100644 --- a/packages/frontend/src/pages/achievements.vue +++ b/packages/frontend/src/pages/achievements.vue @@ -8,7 +8,7 @@ </template> <script lang="ts" setup> -import { onActivated, onDeactivated, onMounted, onUnmounted, ref } from 'vue'; +import { onActivated, onDeactivated, onMounted, onUnmounted } from 'vue'; import MkAchievements from '@/components/MkAchievements.vue'; import { i18n } from '@/i18n'; import { definePageMetadata } from '@/scripts/page-metadata'; diff --git a/packages/frontend/src/pages/admin-file.vue b/packages/frontend/src/pages/admin-file.vue index 6aac064c36..1d309a7377 100644 --- a/packages/frontend/src/pages/admin-file.vue +++ b/packages/frontend/src/pages/admin-file.vue @@ -75,7 +75,6 @@ import bytes from '@/filters/bytes'; import * as os from '@/os'; import { i18n } from '@/i18n'; import { definePageMetadata } from '@/scripts/page-metadata'; -import { acct } from '@/filters/user'; import { iAmAdmin, iAmModerator } from '@/account'; let tab = $ref('overview'); diff --git a/packages/frontend/src/pages/admin/RolesEditorFormula.vue b/packages/frontend/src/pages/admin/RolesEditorFormula.vue index 5bd3803486..07729b8cf9 100644 --- a/packages/frontend/src/pages/admin/RolesEditorFormula.vue +++ b/packages/frontend/src/pages/admin/RolesEditorFormula.vue @@ -52,12 +52,7 @@ import { computed, defineAsyncComponent, ref, watch } from 'vue'; import { v4 as uuid } from 'uuid'; import MkInput from '@/components/MkInput.vue'; import MkSelect from '@/components/MkSelect.vue'; -import MkTextarea from '@/components/MkTextarea.vue'; -import MkFolder from '@/components/MkFolder.vue'; -import MkSwitch from '@/components/MkSwitch.vue'; import MkButton from '@/components/MkButton.vue'; -import FormSlot from '@/components/form/slot.vue'; -import * as os from '@/os'; import { i18n } from '@/i18n'; import { deepClone } from '@/scripts/clone'; diff --git a/packages/frontend/src/pages/admin/_header_.vue b/packages/frontend/src/pages/admin/_header_.vue index a342644516..372c63ff4c 100644 --- a/packages/frontend/src/pages/admin/_header_.vue +++ b/packages/frontend/src/pages/admin/_header_.vue @@ -28,13 +28,11 @@ </template> <script lang="ts" setup> -import { computed, onMounted, onUnmounted, ref, shallowRef, inject, watch, nextTick } from 'vue'; +import { computed, onMounted, onUnmounted, ref, shallowRef, watch, nextTick } from 'vue'; import tinycolor from 'tinycolor2'; import { popupMenu } from '@/os'; -import { url } from '@/config'; import { scrollToTop } from '@/scripts/scroll'; import MkButton from '@/components/MkButton.vue'; -import { i18n } from '@/i18n'; import { globalEvents } from '@/events'; import { injectPageMetadata } from '@/scripts/page-metadata'; @@ -115,7 +113,7 @@ function onTabClick(tab: Tab, ev: MouseEvent): void { } const calcBg = () => { - const rawBg = metadata?.bg || 'var(--bg)'; + const rawBg = metadata?.bg ?? 'var(--bg)'; const tinyBg = tinycolor(rawBg.startsWith('var(') ? getComputedStyle(document.documentElement).getPropertyValue(rawBg.slice(4, -1)) : rawBg); tinyBg.setAlpha(0.85); bg.value = tinyBg.toRgbString(); diff --git a/packages/frontend/src/pages/admin/abuses.vue b/packages/frontend/src/pages/admin/abuses.vue index 1c8557400f..9e8af43024 100644 --- a/packages/frontend/src/pages/admin/abuses.vue +++ b/packages/frontend/src/pages/admin/abuses.vue @@ -50,11 +50,9 @@ import { computed } from 'vue'; import XHeader from './_header_.vue'; -import MkInput from '@/components/MkInput.vue'; import MkSelect from '@/components/MkSelect.vue'; import MkPagination from '@/components/MkPagination.vue'; import XAbuseReport from '@/components/MkAbuseReport.vue'; -import * as os from '@/os'; import { i18n } from '@/i18n'; import { definePageMetadata } from '@/scripts/page-metadata'; diff --git a/packages/frontend/src/pages/admin/ads.vue b/packages/frontend/src/pages/admin/ads.vue index 701ec31b65..828bfe6007 100644 --- a/packages/frontend/src/pages/admin/ads.vue +++ b/packages/frontend/src/pages/admin/ads.vue @@ -29,6 +29,9 @@ <MkInput v-model="ad.ratio" type="number"> <template #label>{{ i18n.ts.ratio }}</template> </MkInput> + <MkInput v-model="ad.startsAt" type="datetime-local"> + <template #label>{{ i18n.ts.startingperiod }}</template> + </MkInput> <MkInput v-model="ad.expiresAt" type="datetime-local"> <template #label>{{ i18n.ts.expiration }}</template> </MkInput> @@ -41,6 +44,9 @@ <MkButton class="button" inline danger @click="remove(ad)"><i class="ti ti-trash"></i> {{ i18n.ts.remove }}</MkButton> </div> </div> + <MkButton class="button" @click="more()"> + <i class="ti ti-reload"></i>{{ i18n.ts.more }} + </MkButton> </div> </MkSpacer> </MkStickyContainer> @@ -66,11 +72,14 @@ const localTimeDiff = localTime.getTimezoneOffset() * 60 * 1000; os.api('admin/ad/list').then(adsResponse => { ads = adsResponse.map(r => { - const date = new Date(r.expiresAt); - date.setMilliseconds(date.getMilliseconds() - localTimeDiff); + const exdate = new Date(r.expiresAt); + const stdate = new Date(r.startsAt); + exdate.setMilliseconds(exdate.getMilliseconds() - localTimeDiff); + stdate.setMilliseconds(stdate.getMilliseconds() - localTimeDiff); return { ...r, - expiresAt: date.toISOString().slice(0, 16), + expiresAt: exdate.toISOString().slice(0, 16), + startsAt: stdate.toISOString().slice(0, 16), }; }); }); @@ -85,6 +94,7 @@ function add() { url: '', imageUrl: null, expiresAt: null, + startsAt: null, }); } @@ -106,15 +116,31 @@ function save(ad) { os.apiWithDialog('admin/ad/create', { ...ad, expiresAt: new Date(ad.expiresAt).getTime(), + startsAt: new Date(ad.startsAt).getTime(), }); } else { os.apiWithDialog('admin/ad/update', { ...ad, expiresAt: new Date(ad.expiresAt).getTime(), + startsAt: new Date(ad.startsAt).getTime(), }); } } - +function more() { + os.api('admin/ad/list', { untilId: ads.reduce((acc, ad) => ad.id != null ? ad : acc).id }).then(adsResponse => { + ads = ads.concat(adsResponse.map(r => { + const exdate = new Date(r.expiresAt); + const stdate = new Date(r.startsAt); + exdate.setMilliseconds(exdate.getMilliseconds() - localTimeDiff); + stdate.setMilliseconds(stdate.getMilliseconds() - localTimeDiff); + return { + ...r, + expiresAt: exdate.toISOString().slice(0, 16), + startsAt: stdate.toISOString().slice(0, 16), + }; + })); + }); +} const headerActions = $computed(() => [{ asFullButton: true, icon: 'ti ti-plus', diff --git a/packages/frontend/src/pages/admin/federation.vue b/packages/frontend/src/pages/admin/federation.vue index f2c114ca21..6af1610431 100644 --- a/packages/frontend/src/pages/admin/federation.vue +++ b/packages/frontend/src/pages/admin/federation.vue @@ -54,15 +54,12 @@ <script lang="ts" setup> import { computed } from 'vue'; import XHeader from './_header_.vue'; -import MkButton from '@/components/MkButton.vue'; import MkInput from '@/components/MkInput.vue'; import MkSelect from '@/components/MkSelect.vue'; import MkPagination from '@/components/MkPagination.vue'; import MkInstanceCardMini from '@/components/MkInstanceCardMini.vue'; import FormSplit from '@/components/form/split.vue'; -import * as os from '@/os'; import { i18n } from '@/i18n'; -import { dateString } from '@/filters/date'; import { definePageMetadata } from '@/scripts/page-metadata'; let host = $ref(''); diff --git a/packages/frontend/src/pages/admin/files.vue b/packages/frontend/src/pages/admin/files.vue index 1d61cdf8e4..c189437246 100644 --- a/packages/frontend/src/pages/admin/files.vue +++ b/packages/frontend/src/pages/admin/files.vue @@ -33,14 +33,11 @@ </template> <script lang="ts" setup> -import { computed, defineAsyncComponent } from 'vue'; -import * as Acct from 'misskey-js/built/acct'; +import { computed } from 'vue'; import XHeader from './_header_.vue'; -import MkButton from '@/components/MkButton.vue'; import MkInput from '@/components/MkInput.vue'; import MkSelect from '@/components/MkSelect.vue'; import MkFileListForAdmin from '@/components/MkFileListForAdmin.vue'; -import bytes from '@/filters/bytes'; import * as os from '@/os'; import { i18n } from '@/i18n'; import { definePageMetadata } from '@/scripts/page-metadata'; diff --git a/packages/frontend/src/pages/admin/index.vue b/packages/frontend/src/pages/admin/index.vue index 9a07d3c959..b054999303 100644 --- a/packages/frontend/src/pages/admin/index.vue +++ b/packages/frontend/src/pages/admin/index.vue @@ -23,16 +23,15 @@ </template> <script lang="ts" setup> -import { defineAsyncComponent, inject, nextTick, onMounted, onUnmounted, provide, watch } from 'vue'; +import { onMounted, onUnmounted, provide, watch } from 'vue'; import { i18n } from '@/i18n'; import MkSuperMenu from '@/components/MkSuperMenu.vue'; import MkInfo from '@/components/MkInfo.vue'; -import { scroll } from '@/scripts/scroll'; import { instance } from '@/instance'; import * as os from '@/os'; import { lookupUser } from '@/scripts/lookup-user'; import { useRouter } from '@/router'; -import { definePageMetadata, provideMetadataReceiver, setPageMetadata } from '@/scripts/page-metadata'; +import { definePageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata'; const isEmpty = (x: string | null) => x == null || x === ''; diff --git a/packages/frontend/src/pages/admin/object-storage.vue b/packages/frontend/src/pages/admin/object-storage.vue index 6a6af637ed..bd7c203512 100644 --- a/packages/frontend/src/pages/admin/object-storage.vue +++ b/packages/frontend/src/pages/admin/object-storage.vue @@ -75,7 +75,6 @@ import MkSwitch from '@/components/MkSwitch.vue'; import MkInput from '@/components/MkInput.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'; import { fetchInstance } from '@/instance'; import { i18n } from '@/i18n'; diff --git a/packages/frontend/src/pages/admin/overview.active-users.vue b/packages/frontend/src/pages/admin/overview.active-users.vue index 3ec7694f26..fc10ad75f8 100644 --- a/packages/frontend/src/pages/admin/overview.active-users.vue +++ b/packages/frontend/src/pages/admin/overview.active-users.vue @@ -8,15 +8,13 @@ </template> <script lang="ts" setup> -import { markRaw, version as vueVersion, onMounted, onBeforeUnmount, nextTick } from 'vue'; +import { onMounted } from 'vue'; import { Chart } from 'chart.js'; -import tinycolor from 'tinycolor2'; import gradient from 'chartjs-plugin-gradient'; import * as os from '@/os'; import { defaultStore } from '@/store'; import { useChartTooltip } from '@/scripts/use-chart-tooltip'; import { chartVLine } from '@/scripts/chart-vline'; -import { alpha } from '@/scripts/color'; import { initChart } from '@/scripts/init-chart'; initChart(); diff --git a/packages/frontend/src/pages/admin/overview.ap-requests.vue b/packages/frontend/src/pages/admin/overview.ap-requests.vue index efb335fff1..ad8e623415 100644 --- a/packages/frontend/src/pages/admin/overview.ap-requests.vue +++ b/packages/frontend/src/pages/admin/overview.ap-requests.vue @@ -15,15 +15,10 @@ </template> <script lang="ts" setup> -import { onMounted, onUnmounted, ref } from 'vue'; +import { onMounted } from 'vue'; import { Chart } from 'chart.js'; import gradient from 'chartjs-plugin-gradient'; -import tinycolor from 'tinycolor2'; -import MkMiniChart from '@/components/MkMiniChart.vue'; import * as os from '@/os'; -import number from '@/filters/number'; -import MkNumberDiff from '@/components/MkNumberDiff.vue'; -import { i18n } from '@/i18n'; import { useChartTooltip } from '@/scripts/use-chart-tooltip'; import { chartVLine } from '@/scripts/chart-vline'; import { defaultStore } from '@/store'; diff --git a/packages/frontend/src/pages/admin/overview.federation.vue b/packages/frontend/src/pages/admin/overview.federation.vue index 2789adf643..ab78c4c393 100644 --- a/packages/frontend/src/pages/admin/overview.federation.vue +++ b/packages/frontend/src/pages/admin/overview.federation.vue @@ -41,9 +41,8 @@ </template> <script lang="ts" setup> -import { onMounted, onUnmounted, ref } from 'vue'; +import { onMounted } from 'vue'; import XPie from './overview.pie.vue'; -import MkMiniChart from '@/components/MkMiniChart.vue'; import * as os from '@/os'; import number from '@/filters/number'; import MkNumberDiff from '@/components/MkNumberDiff.vue'; diff --git a/packages/frontend/src/pages/admin/overview.instances.vue b/packages/frontend/src/pages/admin/overview.instances.vue index 15dbdc4639..7d530d6b95 100644 --- a/packages/frontend/src/pages/admin/overview.instances.vue +++ b/packages/frontend/src/pages/admin/overview.instances.vue @@ -12,7 +12,7 @@ </template> <script lang="ts" setup> -import { onMounted, onUnmounted, ref } from 'vue'; +import { ref } from 'vue'; import * as os from '@/os'; import { useInterval } from '@/scripts/use-interval'; import MkInstanceCardMini from '@/components/MkInstanceCardMini.vue'; diff --git a/packages/frontend/src/pages/admin/overview.moderators.vue b/packages/frontend/src/pages/admin/overview.moderators.vue index 445217d825..ff689b8bf9 100644 --- a/packages/frontend/src/pages/admin/overview.moderators.vue +++ b/packages/frontend/src/pages/admin/overview.moderators.vue @@ -12,10 +12,8 @@ </template> <script lang="ts" setup> -import { onMounted, onUnmounted, ref } from 'vue'; +import { onMounted } from 'vue'; import * as os from '@/os'; -import number from '@/filters/number'; -import { i18n } from '@/i18n'; let moderators: any = $ref(null); let fetching = $ref(true); diff --git a/packages/frontend/src/pages/admin/overview.pie.vue b/packages/frontend/src/pages/admin/overview.pie.vue index 416e963356..08a29bf550 100644 --- a/packages/frontend/src/pages/admin/overview.pie.vue +++ b/packages/frontend/src/pages/admin/overview.pie.vue @@ -3,10 +3,8 @@ </template> <script lang="ts" setup> -import { onMounted, onUnmounted, ref, shallowRef } from 'vue'; +import { onMounted, shallowRef } from 'vue'; import { Chart } from 'chart.js'; -import number from '@/filters/number'; -import { defaultStore } from '@/store'; import { useChartTooltip } from '@/scripts/use-chart-tooltip'; import { initChart } from '@/scripts/init-chart'; diff --git a/packages/frontend/src/pages/admin/overview.queue.chart.vue b/packages/frontend/src/pages/admin/overview.queue.chart.vue index 0162d53665..6a11e8b768 100644 --- a/packages/frontend/src/pages/admin/overview.queue.chart.vue +++ b/packages/frontend/src/pages/admin/overview.queue.chart.vue @@ -3,10 +3,8 @@ </template> <script lang="ts" setup> -import { watch, onMounted, onUnmounted, ref, shallowRef } from 'vue'; +import { onMounted, shallowRef } from 'vue'; import { Chart } from 'chart.js'; -import number from '@/filters/number'; -import * as os from '@/os'; import { defaultStore } from '@/store'; import { useChartTooltip } from '@/scripts/use-chart-tooltip'; import { chartVLine } from '@/scripts/chart-vline'; diff --git a/packages/frontend/src/pages/admin/overview.queue.vue b/packages/frontend/src/pages/admin/overview.queue.vue index 7e58882938..1f56a2826a 100644 --- a/packages/frontend/src/pages/admin/overview.queue.vue +++ b/packages/frontend/src/pages/admin/overview.queue.vue @@ -33,9 +33,7 @@ import { markRaw, onMounted, onUnmounted, ref } from 'vue'; import XChart from './overview.queue.chart.vue'; import number from '@/filters/number'; -import * as os from '@/os'; import { stream } from '@/stream'; -import { i18n } from '@/i18n'; const connection = markRaw(stream.useChannel('queueStats')); diff --git a/packages/frontend/src/pages/admin/overview.stats.vue b/packages/frontend/src/pages/admin/overview.stats.vue index bd636cc3ef..3dc1ed8ec5 100644 --- a/packages/frontend/src/pages/admin/overview.stats.vue +++ b/packages/frontend/src/pages/admin/overview.stats.vue @@ -56,10 +56,8 @@ </template> <script lang="ts" setup> -import { onMounted, onUnmounted, ref } from 'vue'; -import MkMiniChart from '@/components/MkMiniChart.vue'; +import { onMounted } from 'vue'; import * as os from '@/os'; -import number from '@/filters/number'; import MkNumberDiff from '@/components/MkNumberDiff.vue'; import MkNumber from '@/components/MkNumber.vue'; import { i18n } from '@/i18n'; diff --git a/packages/frontend/src/pages/admin/overview.users.vue b/packages/frontend/src/pages/admin/overview.users.vue index 5390d9d8cb..3379d064cd 100644 --- a/packages/frontend/src/pages/admin/overview.users.vue +++ b/packages/frontend/src/pages/admin/overview.users.vue @@ -12,7 +12,6 @@ </template> <script lang="ts" setup> -import { onMounted, onUnmounted, ref } from 'vue'; import * as os from '@/os'; import { useInterval } from '@/scripts/use-interval'; import MkUserCardMini from '@/components/MkUserCardMini.vue'; diff --git a/packages/frontend/src/pages/admin/overview.vue b/packages/frontend/src/pages/admin/overview.vue index 0166724e01..5c96c07bfb 100644 --- a/packages/frontend/src/pages/admin/overview.vue +++ b/packages/frontend/src/pages/admin/overview.vue @@ -60,7 +60,7 @@ </template> <script lang="ts" setup> -import { markRaw, version as vueVersion, onMounted, onBeforeUnmount, nextTick } from 'vue'; +import { markRaw, onMounted, onBeforeUnmount, nextTick } from 'vue'; import XFederation from './overview.federation.vue'; import XInstances from './overview.instances.vue'; import XQueue from './overview.queue.vue'; @@ -71,14 +71,10 @@ import XStats from './overview.stats.vue'; import XRetention from './overview.retention.vue'; import XModerators from './overview.moderators.vue'; import XHeatmap from './overview.heatmap.vue'; -import MkTagCloud from '@/components/MkTagCloud.vue'; -import { version, url } from '@/config'; import * as os from '@/os'; import { stream } from '@/stream'; import { i18n } from '@/i18n'; import { definePageMetadata } from '@/scripts/page-metadata'; -import { defaultStore } from '@/store'; -import MkFileListForAdmin from '@/components/MkFileListForAdmin.vue'; import MkFoldableSection from '@/components/MkFoldableSection.vue'; const rootEl = $shallowRef<HTMLElement>(); diff --git a/packages/frontend/src/pages/admin/queue.chart.chart.vue b/packages/frontend/src/pages/admin/queue.chart.chart.vue index a0c05df983..1a1f6a9db4 100644 --- a/packages/frontend/src/pages/admin/queue.chart.chart.vue +++ b/packages/frontend/src/pages/admin/queue.chart.chart.vue @@ -3,10 +3,8 @@ </template> <script lang="ts" setup> -import { watch, onMounted, onUnmounted, ref, shallowRef } from 'vue'; +import { onMounted, shallowRef } from 'vue'; import { Chart } from 'chart.js'; -import number from '@/filters/number'; -import * as os from '@/os'; import { defaultStore } from '@/store'; import { useChartTooltip } from '@/scripts/use-chart-tooltip'; import { chartVLine } from '@/scripts/chart-vline'; diff --git a/packages/frontend/src/pages/admin/queue.vue b/packages/frontend/src/pages/admin/queue.vue index 8d19b49fc5..80e97fed93 100644 --- a/packages/frontend/src/pages/admin/queue.vue +++ b/packages/frontend/src/pages/admin/queue.vue @@ -9,10 +9,8 @@ </template> <script lang="ts" setup> -import { markRaw, onMounted, onBeforeUnmount, nextTick } from 'vue'; import XQueue from './queue.chart.vue'; import XHeader from './_header_.vue'; -import MkButton from '@/components/MkButton.vue'; import * as os from '@/os'; import * as config from '@/config'; import { i18n } from '@/i18n'; diff --git a/packages/frontend/src/pages/admin/roles.edit.vue b/packages/frontend/src/pages/admin/roles.edit.vue index 3cb4e2deb9..ae884c0111 100644 --- a/packages/frontend/src/pages/admin/roles.edit.vue +++ b/packages/frontend/src/pages/admin/roles.edit.vue @@ -13,13 +13,6 @@ import { computed } from 'vue'; import XHeader from './_header_.vue'; import XEditor from './roles.editor.vue'; -import MkInput from '@/components/MkInput.vue'; -import MkSelect from '@/components/MkSelect.vue'; -import MkTextarea from '@/components/MkTextarea.vue'; -import MkFolder from '@/components/MkFolder.vue'; -import MkSwitch from '@/components/MkSwitch.vue'; -import MkButton from '@/components/MkButton.vue'; -import FormSlot from '@/components/form/slot.vue'; import * as os from '@/os'; import { i18n } from '@/i18n'; import { definePageMetadata } from '@/scripts/page-metadata'; diff --git a/packages/frontend/src/pages/admin/roles.editor.vue b/packages/frontend/src/pages/admin/roles.editor.vue index d89a68f982..4eea827de7 100644 --- a/packages/frontend/src/pages/admin/roles.editor.vue +++ b/packages/frontend/src/pages/admin/roles.editor.vue @@ -384,7 +384,7 @@ </template> <script lang="ts" setup> -import { computed, reactive, watch } from 'vue'; +import { reactive, watch } from 'vue'; import { v4 as uuid } from 'uuid'; import RolesEditorFormula from './RolesEditorFormula.vue'; import MkInput from '@/components/MkInput.vue'; diff --git a/packages/frontend/src/pages/admin/roles.role.vue b/packages/frontend/src/pages/admin/roles.role.vue index 0365165b5d..1b9f0e7c53 100644 --- a/packages/frontend/src/pages/admin/roles.role.vue +++ b/packages/frontend/src/pages/admin/roles.role.vue @@ -16,16 +16,29 @@ <MkFolder v-if="role.target === 'manual'" default-open> <template #icon><i class="ti ti-users"></i></template> <template #label>{{ i18n.ts.users }}</template> - <template #suffix>{{ role.users.length }}</template> + <template #suffix>{{ role.usersCount }}</template> <div class="_gaps"> <MkButton primary rounded @click="assign"><i class="ti ti-plus"></i> {{ i18n.ts.assign }}</MkButton> - <div v-for="user in role.users" :key="user.id" :class="$style.userItem"> - <MkA :class="$style.user" :to="`/user-info/${user.id}`"> - <MkUserCardMini :user="user"/> - </MkA> - <button class="_button" :class="$style.unassign" @click="unassign(user, $event)"><i class="ti ti-x"></i></button> - </div> + <MkPagination :pagination="usersPagination"> + <template #empty> + <div class="_fullinfo"> + <img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/> + <div>{{ i18n.ts.noUsers }}</div> + </div> + </template> + + <template #default="{ items }"> + <div class="_gaps_s"> + <div v-for="item in items" :key="item.user.id" :class="$style.userItem"> + <MkA :class="$style.user" :to="`/user-info/${item.user.id}`"> + <MkUserCardMini :user="item.user"/> + </MkA> + <button class="_button" :class="$style.unassign" @click="unassign(item.user, $event)"><i class="ti ti-x"></i></button> + </div> + </div> + </template> + </MkPagination> </div> </MkFolder> <MkInfo v-else>{{ i18n.ts._role.isConditionalRole }}</MkInfo> @@ -47,6 +60,7 @@ import { useRouter } from '@/router'; import MkButton from '@/components/MkButton.vue'; import MkUserCardMini from '@/components/MkUserCardMini.vue'; import MkInfo from '@/components/MkInfo.vue'; +import MkPagination, { Paging } from '@/components/MkPagination.vue'; const router = useRouter(); @@ -54,6 +68,14 @@ const props = defineProps<{ id?: string; }>(); +const usersPagination = { + endpoint: 'admin/roles/users' as const, + limit: 20, + params: computed(() => ({ + roleId: props.id, + })), +}; + const role = reactive(await os.api('admin/roles/show', { roleId: props.id, })); diff --git a/packages/frontend/src/pages/admin/roles.vue b/packages/frontend/src/pages/admin/roles.vue index ff8f8a356f..d89f0d2a7d 100644 --- a/packages/frontend/src/pages/admin/roles.vue +++ b/packages/frontend/src/pages/admin/roles.vue @@ -133,7 +133,7 @@ </div> </MkFolder> <div class="_gaps_s"> - <MkRolePreview v-for="role in roles" :key="role.id" :role="role"/> + <MkRolePreview v-for="role in roles" :key="role.id" :role="role" :for-moderation="true"/> </div> </div> </MkSpacer> @@ -145,8 +145,6 @@ import { computed, reactive } from 'vue'; import XHeader from './_header_.vue'; import MkInput from '@/components/MkInput.vue'; -import MkSelect from '@/components/MkSelect.vue'; -import MkPagination from '@/components/MkPagination.vue'; import MkFolder from '@/components/MkFolder.vue'; import MkSwitch from '@/components/MkSwitch.vue'; import MkButton from '@/components/MkButton.vue'; diff --git a/packages/frontend/src/pages/admin/security.vue b/packages/frontend/src/pages/admin/security.vue index 191da506e9..cd8ef9e68b 100644 --- a/packages/frontend/src/pages/admin/security.vue +++ b/packages/frontend/src/pages/admin/security.vue @@ -108,7 +108,6 @@ import XHeader from './_header_.vue'; import MkFolder from '@/components/MkFolder.vue'; import MkRadios from '@/components/MkRadios.vue'; import MkSwitch from '@/components/MkSwitch.vue'; -import FormInfo from '@/components/MkInfo.vue'; import FormSuspense from '@/components/form/suspense.vue'; import MkRange from '@/components/MkRange.vue'; import MkInput from '@/components/MkInput.vue'; diff --git a/packages/frontend/src/pages/admin/settings.vue b/packages/frontend/src/pages/admin/settings.vue index 12d852a90e..7840c55ee4 100644 --- a/packages/frontend/src/pages/admin/settings.vue +++ b/packages/frontend/src/pages/admin/settings.vue @@ -143,7 +143,6 @@ import XHeader from './_header_.vue'; import MkSwitch from '@/components/MkSwitch.vue'; import MkInput from '@/components/MkInput.vue'; import MkTextarea from '@/components/MkTextarea.vue'; -import FormInfo from '@/components/MkInfo.vue'; import FormSection from '@/components/form/section.vue'; import FormSplit from '@/components/form/split.vue'; import FormSuspense from '@/components/form/suspense.vue'; diff --git a/packages/frontend/src/pages/antenna-timeline.vue b/packages/frontend/src/pages/antenna-timeline.vue index ff31c3ab2c..cf803d6c7f 100644 --- a/packages/frontend/src/pages/antenna-timeline.vue +++ b/packages/frontend/src/pages/antenna-timeline.vue @@ -4,7 +4,7 @@ <div ref="rootEl" v-hotkey.global="keymap" class="tqmomfks"> <div v-if="queue > 0" class="new"><button class="_buttonPrimary" @click="top()">{{ $ts.newNoteRecived }}</button></div> <div class="tl"> - <XTimeline + <MkTimeline ref="tlEl" :key="antennaId" class="tl" src="antenna" @@ -18,8 +18,8 @@ </template> <script lang="ts" setup> -import { computed, inject, watch } from 'vue'; -import XTimeline from '@/components/MkTimeline.vue'; +import { computed, watch } from 'vue'; +import MkTimeline from '@/components/MkTimeline.vue'; import { scroll } from '@/scripts/scroll'; import * as os from '@/os'; import { useRouter } from '@/router'; @@ -35,7 +35,7 @@ const props = defineProps<{ let antenna = $ref(null); let queue = $ref(0); let rootEl = $shallowRef<HTMLElement>(); -let tlEl = $shallowRef<InstanceType<typeof XTimeline>>(); +let tlEl = $shallowRef<InstanceType<typeof MkTimeline>>(); const keymap = $computed(() => ({ 't': focus, })); @@ -72,7 +72,7 @@ watch(() => props.antennaId, async () => { }, { immediate: true }); const headerActions = $computed(() => antenna ? [{ - icon: 'fas fa-calendar-alt', + icon: 'ti ti-calendar-time', text: i18n.ts.jumpToSpecifiedDate, handler: timetravel, }, { diff --git a/packages/frontend/src/pages/auth.vue b/packages/frontend/src/pages/auth.vue index 50afffc460..4f8afb9ea2 100644 --- a/packages/frontend/src/pages/auth.vue +++ b/packages/frontend/src/pages/auth.vue @@ -1,12 +1,12 @@ <template> <MkStickyContainer> - <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs" /></template> + <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template> <MkSpacer :content-max="500"> <div v-if="state == 'fetch-session-error'"> <p>{{ i18n.ts.somethingHappened }}</p> </div> <div v-else-if="$i && !session"> - <MkLoading /> + <MkLoading/> </div> <div v-else-if="$i && session"> <XForm @@ -21,15 +21,16 @@ </div> <div v-if="state == 'accepted' && session"> <h1>{{ session.app.isAuthorized ? $t('already-authorized') : i18n.ts.allowed }}</h1> - <p v-if="session.app.callbackUrl">{{ i18n.ts._auth.callback }} - <MkEllipsis /> + <p v-if="session.app.callbackUrl"> + {{ i18n.ts._auth.callback }} + <MkEllipsis/> </p> <p v-if="!session.app.callbackUrl">{{ i18n.ts._auth.pleaseGoBack }}</p> </div> </div> <div v-else> <p :class="$style.loginMessage">{{ i18n.ts._auth.pleaseLogin }}</p> - <MkSignin @login="onLogin" /> + <MkSignin @login="onLogin"/> </div> </MkSpacer> </MkStickyContainer> @@ -37,12 +38,12 @@ <script lang="ts" setup> import { onMounted } from 'vue'; +import { AuthSession } from 'misskey-js/built/entities'; import XForm from './auth.form.vue'; import MkSignin from '@/components/MkSignin.vue'; import * as os from '@/os'; import { $i, login } from '@/account'; import { definePageMetadata } from '@/scripts/page-metadata'; -import { AuthSession } from 'misskey-js/built/entities'; import { i18n } from '@/i18n'; const props = defineProps<{ @@ -82,7 +83,7 @@ onMounted(async () => { } else { state = 'waiting'; } - } catch (e) { + } catch (err) { state = 'fetch-session-error'; } }); diff --git a/packages/frontend/src/pages/channel-editor.vue b/packages/frontend/src/pages/channel-editor.vue index df8417e4ad..38c5b1e082 100644 --- a/packages/frontend/src/pages/channel-editor.vue +++ b/packages/frontend/src/pages/channel-editor.vue @@ -27,7 +27,7 @@ </template> <script lang="ts" setup> -import { computed, inject, watch } from 'vue'; +import { computed, watch } from 'vue'; import MkTextarea from '@/components/MkTextarea.vue'; import MkButton from '@/components/MkButton.vue'; import MkInput from '@/components/MkInput.vue'; diff --git a/packages/frontend/src/pages/channel.vue b/packages/frontend/src/pages/channel.vue index 0fb33e30f7..0e6d0e2691 100644 --- a/packages/frontend/src/pages/channel.vue +++ b/packages/frontend/src/pages/channel.vue @@ -25,17 +25,16 @@ <MkPostForm v-if="$i" :channel="channel" class="post-form _panel _margin" fixed/> - <XTimeline :key="channelId" class="_margin" src="channel" :channel="channelId" @before="before" @after="after"/> + <MkTimeline :key="channelId" class="_margin" src="channel" :channel="channelId" @before="before" @after="after"/> </div> </MkSpacer> </MkStickyContainer> </template> <script lang="ts" setup> -import { computed, inject, watch } from 'vue'; -import MkContainer from '@/components/MkContainer.vue'; +import { computed, watch } from 'vue'; import MkPostForm from '@/components/MkPostForm.vue'; -import XTimeline from '@/components/MkTimeline.vue'; +import MkTimeline from '@/components/MkTimeline.vue'; import XChannelFollowButton from '@/components/MkChannelFollowButton.vue'; import * as os from '@/os'; import { useRouter } from '@/router'; diff --git a/packages/frontend/src/pages/channels.vue b/packages/frontend/src/pages/channels.vue index 9043d06c52..3550c7f436 100644 --- a/packages/frontend/src/pages/channels.vue +++ b/packages/frontend/src/pages/channels.vue @@ -23,7 +23,7 @@ </template> <script lang="ts" setup> -import { computed, defineComponent, inject } from 'vue'; +import { computed } from 'vue'; import MkChannelPreview from '@/components/MkChannelPreview.vue'; import MkPagination from '@/components/MkPagination.vue'; import MkButton from '@/components/MkButton.vue'; diff --git a/packages/frontend/src/pages/clicker.vue b/packages/frontend/src/pages/clicker.vue index 082a303e6f..24eae32e13 100644 --- a/packages/frontend/src/pages/clicker.vue +++ b/packages/frontend/src/pages/clicker.vue @@ -8,9 +8,7 @@ </template> <script lang="ts" setup> -import { ref } from 'vue'; import MkClickerGame from '@/components/MkClickerGame.vue'; -import { i18n } from '@/i18n'; import { definePageMetadata } from '@/scripts/page-metadata'; definePageMetadata({ diff --git a/packages/frontend/src/pages/clip.vue b/packages/frontend/src/pages/clip.vue index cd9cec0d4f..d4e8f27005 100644 --- a/packages/frontend/src/pages/clip.vue +++ b/packages/frontend/src/pages/clip.vue @@ -12,7 +12,7 @@ </div> </div> - <XNotes :pagination="pagination" :detail="true"/> + <MkNotes :pagination="pagination" :detail="true"/> </div> </MkSpacer> </MkStickyContainer> @@ -21,7 +21,7 @@ <script lang="ts" setup> import { computed, watch, provide } from 'vue'; import * as misskey from 'misskey-js'; -import XNotes from '@/components/MkNotes.vue'; +import MkNotes from '@/components/MkNotes.vue'; import { $i } from '@/account'; import { i18n } from '@/i18n'; import * as os from '@/os'; diff --git a/packages/frontend/src/pages/custom-emojis-manager.vue b/packages/frontend/src/pages/custom-emojis-manager.vue index 87d205ed78..59cb3262b7 100644 --- a/packages/frontend/src/pages/custom-emojis-manager.vue +++ b/packages/frontend/src/pages/custom-emojis-manager.vue @@ -68,11 +68,10 @@ </template> <script lang="ts" setup> -import { computed, defineAsyncComponent, defineComponent, ref, shallowRef } from 'vue'; +import { computed, defineAsyncComponent, ref, shallowRef } from 'vue'; import MkButton from '@/components/MkButton.vue'; import MkInput from '@/components/MkInput.vue'; import MkPagination from '@/components/MkPagination.vue'; -import MkTab from '@/components/MkTab.vue'; import MkSwitch from '@/components/MkSwitch.vue'; import FormSplit from '@/components/form/split.vue'; import { selectFile, selectFiles } from '@/scripts/select-file'; diff --git a/packages/frontend/src/pages/drive.vue b/packages/frontend/src/pages/drive.vue index 04ade5c207..0b398684ca 100644 --- a/packages/frontend/src/pages/drive.vue +++ b/packages/frontend/src/pages/drive.vue @@ -7,7 +7,6 @@ <script lang="ts" setup> import { computed } from 'vue'; import XDrive from '@/components/MkDrive.vue'; -import * as os from '@/os'; import { i18n } from '@/i18n'; import { definePageMetadata } from '@/scripts/page-metadata'; diff --git a/packages/frontend/src/pages/emoji-edit-dialog.vue b/packages/frontend/src/pages/emoji-edit-dialog.vue index 4d84ed7f16..9be30f76a0 100644 --- a/packages/frontend/src/pages/emoji-edit-dialog.vue +++ b/packages/frontend/src/pages/emoji-edit-dialog.vue @@ -34,7 +34,6 @@ import MkModalWindow from '@/components/MkModalWindow.vue'; import MkButton from '@/components/MkButton.vue'; import MkInput from '@/components/MkInput.vue'; import * as os from '@/os'; -import { unique } from '@/scripts/array'; import { i18n } from '@/i18n'; import { customEmojiCategories } from '@/custom-emojis'; diff --git a/packages/frontend/src/pages/explore.featured.vue b/packages/frontend/src/pages/explore.featured.vue index 18a371a086..a972ae04ec 100644 --- a/packages/frontend/src/pages/explore.featured.vue +++ b/packages/frontend/src/pages/explore.featured.vue @@ -4,13 +4,13 @@ <option value="notes">{{ i18n.ts.notes }}</option> <option value="polls">{{ i18n.ts.poll }}</option> </MkTab> - <XNotes v-if="tab === 'notes'" :pagination="paginationForNotes"/> - <XNotes v-else-if="tab === 'polls'" :pagination="paginationForPolls"/> + <MkNotes v-if="tab === 'notes'" :pagination="paginationForNotes"/> + <MkNotes v-else-if="tab === 'polls'" :pagination="paginationForPolls"/> </MkSpacer> </template> <script lang="ts" setup> -import XNotes from '@/components/MkNotes.vue'; +import MkNotes from '@/components/MkNotes.vue'; import MkTab from '@/components/MkTab.vue'; import { i18n } from '@/i18n'; diff --git a/packages/frontend/src/pages/explore.roles.vue b/packages/frontend/src/pages/explore.roles.vue new file mode 100644 index 0000000000..8be11008c2 --- /dev/null +++ b/packages/frontend/src/pages/explore.roles.vue @@ -0,0 +1,22 @@ +<template> +<MkSpacer :content-max="1200"> + <div class="_gaps_s"> + <MkRolePreview v-for="role in roles" :key="role.id" :role="role" :for-moderation="false"/> + </div> +</MkSpacer> +</template> + +<script lang="ts" setup> +import { } from 'vue'; +import MkRolePreview from '@/components/MkRolePreview.vue'; +import * as os from '@/os'; + +let roles = $ref(); + +os.api('roles/list', { + limit: 30, +}).then(res => { + roles = res; +}); +</script> + diff --git a/packages/frontend/src/pages/explore.users.vue b/packages/frontend/src/pages/explore.users.vue index 3a74e8518d..c441407d97 100644 --- a/packages/frontend/src/pages/explore.users.vue +++ b/packages/frontend/src/pages/explore.users.vue @@ -7,20 +7,20 @@ <div v-if="origin === 'local'"> <template v-if="tag == null"> <MkFoldableSection class="_margin" persist-key="explore-pinned-users"> - <template #header><i class="fas fa-bookmark ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.pinnedUsers }}</template> - <XUserList :pagination="pinnedUsers"/> + <template #header><i class="ti ti-bookmark ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.pinnedUsers }}</template> + <MkUserList :pagination="pinnedUsers"/> </MkFoldableSection> <MkFoldableSection class="_margin" persist-key="explore-popular-users"> - <template #header><i class="fas fa-chart-line ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.popularUsers }}</template> - <XUserList :pagination="popularUsers"/> + <template #header><i class="ti ti-chart-line ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.popularUsers }}</template> + <MkUserList :pagination="popularUsers"/> </MkFoldableSection> <MkFoldableSection class="_margin" persist-key="explore-recently-updated-users"> - <template #header><i class="fas fa-comment-alt ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.recentlyUpdatedUsers }}</template> - <XUserList :pagination="recentlyUpdatedUsers"/> + <template #header><i class="ti ti-message ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.recentlyUpdatedUsers }}</template> + <MkUserList :pagination="recentlyUpdatedUsers"/> </MkFoldableSection> <MkFoldableSection class="_margin" persist-key="explore-recently-registered-users"> <template #header><i class="ti ti-plus ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.recentlyRegisteredUsers }}</template> - <XUserList :pagination="recentlyRegisteredUsers"/> + <MkUserList :pagination="recentlyRegisteredUsers"/> </MkFoldableSection> </template> </div> @@ -29,28 +29,28 @@ <template #header><i class="ti ti-hash ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.popularTags }}</template> <div class="vxjfqztj"> - <MkA v-for="tag in tagsLocal" :key="'local:' + tag.tag" :to="`/explore/tags/${tag.tag}`" class="local">{{ tag.tag }}</MkA> - <MkA v-for="tag in tagsRemote" :key="'remote:' + tag.tag" :to="`/explore/tags/${tag.tag}`">{{ tag.tag }}</MkA> + <MkA v-for="tag in tagsLocal" :key="'local:' + tag.tag" :to="`/user-tags/${tag.tag}`" class="local">{{ tag.tag }}</MkA> + <MkA v-for="tag in tagsRemote" :key="'remote:' + tag.tag" :to="`/user-tags/${tag.tag}`">{{ tag.tag }}</MkA> </div> </MkFoldableSection> <MkFoldableSection v-if="tag != null" :key="`${tag}`" class="_margin"> <template #header><i class="ti ti-hash ti-fw" style="margin-right: 0.5em;"></i>{{ tag }}</template> - <XUserList :pagination="tagUsers"/> + <MkUserList :pagination="tagUsers"/> </MkFoldableSection> <template v-if="tag == null"> <MkFoldableSection class="_margin"> - <template #header><i class="fas fa-chart-line ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.popularUsers }}</template> - <XUserList :pagination="popularUsersF"/> + <template #header><i class="ti ti-chart-line ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.popularUsers }}</template> + <MkUserList :pagination="popularUsersF"/> </MkFoldableSection> <MkFoldableSection class="_margin"> - <template #header><i class="fas fa-comment-alt ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.recentlyUpdatedUsers }}</template> - <XUserList :pagination="recentlyUpdatedUsersF"/> + <template #header><i class="ti ti-message ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.recentlyUpdatedUsers }}</template> + <MkUserList :pagination="recentlyUpdatedUsersF"/> </MkFoldableSection> <MkFoldableSection class="_margin"> - <template #header><i class="fas fa-rocket ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.recentlyDiscoveredUsers }}</template> - <XUserList :pagination="recentlyRegisteredUsersF"/> + <template #header><i class="ti ti-rocket ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.recentlyDiscoveredUsers }}</template> + <MkUserList :pagination="recentlyRegisteredUsersF"/> </MkFoldableSection> </template> </div> @@ -58,14 +58,12 @@ </template> <script lang="ts" setup> -import { computed, watch } from 'vue'; -import XUserList from '@/components/MkUserList.vue'; +import { watch } from 'vue'; +import MkUserList from '@/components/MkUserList.vue'; import MkFoldableSection from '@/components/MkFoldableSection.vue'; import MkTab from '@/components/MkTab.vue'; -import number from '@/filters/number'; import * as os from '@/os'; import { i18n } from '@/i18n'; -import { instance } from '@/instance'; const props = defineProps<{ tag?: string; diff --git a/packages/frontend/src/pages/explore.vue b/packages/frontend/src/pages/explore.vue index 57df58bbb8..0ed0a7ebc2 100644 --- a/packages/frontend/src/pages/explore.vue +++ b/packages/frontend/src/pages/explore.vue @@ -8,6 +8,9 @@ <div v-else-if="tab === 'users'"> <XUsers/> </div> + <div v-else-if="tab === 'roles'"> + <XRoles/> + </div> <div v-else-if="tab === 'search'"> <MkSpacer :content-max="1200"> <div> @@ -22,7 +25,7 @@ </MkRadios> </div> - <XUserList v-if="searchQuery" ref="searchEl" class="_margin" :pagination="searchPagination"/> + <MkUserList v-if="searchQuery" ref="searchEl" class="_margin" :pagination="searchPagination"/> </MkSpacer> </div> </div> @@ -33,15 +36,13 @@ import { computed, watch } from 'vue'; import XFeatured from './explore.featured.vue'; import XUsers from './explore.users.vue'; +import XRoles from './explore.roles.vue'; import MkFoldableSection from '@/components/MkFoldableSection.vue'; import MkInput from '@/components/MkInput.vue'; import MkRadios from '@/components/MkRadios.vue'; -import number from '@/filters/number'; -import * as os from '@/os'; import { definePageMetadata } from '@/scripts/page-metadata'; import { i18n } from '@/i18n'; -import { instance } from '@/instance'; -import XUserList from '@/components/MkUserList.vue'; +import MkUserList from '@/components/MkUserList.vue'; const props = withDefaults(defineProps<{ tag?: string; @@ -79,7 +80,12 @@ const headerTabs = $computed(() => [{ icon: 'ti ti-users', title: i18n.ts.users, }, { + key: 'roles', + icon: 'ti ti-badges', + title: i18n.ts.roles, +}, { key: 'search', + icon: 'ti ti-search', title: i18n.ts.search, }]); diff --git a/packages/frontend/src/pages/favorites.vue b/packages/frontend/src/pages/favorites.vue index 0bbed411b1..07dd768499 100644 --- a/packages/frontend/src/pages/favorites.vue +++ b/packages/frontend/src/pages/favorites.vue @@ -21,7 +21,6 @@ </template> <script lang="ts" setup> -import { ref } from 'vue'; import MkPagination from '@/components/MkPagination.vue'; import XNote from '@/components/MkNote.vue'; import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue'; diff --git a/packages/frontend/src/pages/flash/flash-edit.vue b/packages/frontend/src/pages/flash/flash-edit.vue index 734c467e3b..2b7fcf74e1 100644 --- a/packages/frontend/src/pages/flash/flash-edit.vue +++ b/packages/frontend/src/pages/flash/flash-edit.vue @@ -24,10 +24,9 @@ </template> <script lang="ts" setup> -import { computed, onDeactivated, onUnmounted, Ref, ref, watch } from 'vue'; +import { computed } from 'vue'; import MkButton from '@/components/MkButton.vue'; import * as os from '@/os'; -import { url } from '@/config'; import { i18n } from '@/i18n'; import { definePageMetadata } from '@/scripts/page-metadata'; import MkTextarea from '@/components/MkTextarea.vue'; @@ -174,6 +173,119 @@ var cursor = 0 do() `; +const PRESET_QUIZ = `/// @ 0.12.4 +let title = '地理クイズ' + +let qas = [{ + q: 'オーストラリアの首都は?' + choices: ['シドニー' 'キャンベラ' 'メルボルン'] + a: 'キャンベラ' + aDescription: '最大の都市はシドニーですが首都はキャンベラです。' +} { + q: '国土面積2番目の国は?' + choices: ['カナダ' 'アメリカ' '中国'] + a: 'カナダ' + aDescription: '大きい順にロシア、カナダ、アメリカ、中国です。' +} { + q: '二重内陸国ではないのは?' + choices: ['リヒテンシュタイン' 'ウズベキスタン' 'レソト'] + a: 'レソト' + aDescription: 'レソトは(一重)内陸国です。' +} { + q: '閘門がない運河は?' + choices: ['キール運河' 'スエズ運河' 'パナマ運河'] + a: 'スエズ運河' + aDescription: 'スエズ運河は高低差がないので閘門はありません。' +}] + +let qaEls = [Ui:C:container({ + align: 'center' + children: [ + Ui:C:text({ + size: 1.5 + bold: true + text: title + }) + ] +})] + +var qn = 0 +each (let qa, qas) { + qn += 1 + qa.id = Util:uuid() + qaEls.push(Ui:C:container({ + align: 'center' + bgColor: '#000' + fgColor: '#fff' + padding: 16 + rounded: true + children: [ + Ui:C:text({ + text: \`Q{qn} {qa.q}\` + }) + Ui:C:select({ + items: qa.choices.map(@(c) {{ text: c, value: c }}) + onChange: @(v) { qa.userAnswer = v } + }) + Ui:C:container({ + children: [] + } \`{qa.id}:a\`) + ] + } qa.id)) +} + +@finish() { + var score = 0 + + each (let qa, qas) { + let correct = qa.userAnswer == qa.a + if (correct) score += 1 + let el = Ui:get(\`{qa.id}:a\`) + el.update({ + children: [ + Ui:C:text({ + size: 1.2 + bold: true + color: if (correct) '#f00' else '#00f' + text: if (correct) '🎉正解' else '不正解' + }) + Ui:C:text({ + text: qa.aDescription + }) + ] + }) + } + + let result = \`{title}の結果は{qas.len}問中{score}問正解でした。\` + Ui:get('footer').update({ + children: [ + Ui:C:postFormButton({ + text: '結果を共有' + rounded: true + primary: true + form: { + text: \`{result}{Str:lf}{THIS_URL}\` + } + }) + ] + }) +} + +qaEls.push(Ui:C:container({ + align: 'center' + children: [ + Ui:C:button({ + text: '答え合わせ' + primary: true + rounded: true + onClick: finish + }) + ] +} 'footer')) + +Ui:render(qaEls) +`; + const PRESET_TIMELINE = `/// @ 0.12.4 // APIリクエストを行いローカルタイムラインを表示するプリセット @@ -260,6 +372,11 @@ function selectPreset(ev: MouseEvent) { script = PRESET_SHUFFLE; }, }, { + text: 'Quiz', + action: () => { + script = PRESET_QUIZ; + }, + }, { text: 'Timeline viewer', action: () => { script = PRESET_TIMELINE; diff --git a/packages/frontend/src/pages/flash/flash-index.vue b/packages/frontend/src/pages/flash/flash-index.vue index a3a48d3b97..f1dca5f240 100644 --- a/packages/frontend/src/pages/flash/flash-index.vue +++ b/packages/frontend/src/pages/flash/flash-index.vue @@ -33,7 +33,7 @@ </template> <script lang="ts" setup> -import { computed, inject } from 'vue'; +import { computed } from 'vue'; import MkFlashPreview from '@/components/MkFlashPreview.vue'; import MkPagination from '@/components/MkPagination.vue'; import MkButton from '@/components/MkButton.vue'; diff --git a/packages/frontend/src/pages/flash/flash.vue b/packages/frontend/src/pages/flash/flash.vue index c82559d55a..3528e7e145 100644 --- a/packages/frontend/src/pages/flash/flash.vue +++ b/packages/frontend/src/pages/flash/flash.vue @@ -52,18 +52,14 @@ <script lang="ts" setup> import { computed, onDeactivated, onUnmounted, Ref, ref, watch } from 'vue'; -import { Interpreter, Parser, utils, values } from '@syuilo/aiscript'; +import { Interpreter, Parser, values } from '@syuilo/aiscript'; import MkButton from '@/components/MkButton.vue'; import * as os from '@/os'; import { url } from '@/config'; -import MkFollowButton from '@/components/MkFollowButton.vue'; -import MkContainer from '@/components/MkContainer.vue'; -import MkPagination from '@/components/MkPagination.vue'; -import MkPagePreview from '@/components/MkPagePreview.vue'; import { i18n } from '@/i18n'; import { definePageMetadata } from '@/scripts/page-metadata'; import MkAsUi from '@/components/MkAsUi.vue'; -import { AsUiComponent, AsUiRoot, patch, registerAsUiLib, render } from '@/scripts/aiscript/ui'; +import { AsUiComponent, AsUiRoot, registerAsUiLib } from '@/scripts/aiscript/ui'; import { createAiScriptEnv } from '@/scripts/aiscript/api'; import MkFolder from '@/components/MkFolder.vue'; import MkTextarea from '@/components/MkTextarea.vue'; diff --git a/packages/frontend/src/pages/gallery/edit.vue b/packages/frontend/src/pages/gallery/edit.vue index f47632c85f..1fae7686e5 100644 --- a/packages/frontend/src/pages/gallery/edit.vue +++ b/packages/frontend/src/pages/gallery/edit.vue @@ -31,7 +31,7 @@ </template> <script lang="ts" setup> -import { computed, inject, watch } from 'vue'; +import { computed, watch } from 'vue'; import MkButton from '@/components/MkButton.vue'; import MkInput from '@/components/MkInput.vue'; import MkTextarea from '@/components/MkTextarea.vue'; diff --git a/packages/frontend/src/pages/gallery/index.vue b/packages/frontend/src/pages/gallery/index.vue index 1001c3b2e7..de8f448da1 100644 --- a/packages/frontend/src/pages/gallery/index.vue +++ b/packages/frontend/src/pages/gallery/index.vue @@ -42,16 +42,10 @@ </template> <script lang="ts" setup> -import { computed, defineComponent, watch } from 'vue'; -import XUserList from '@/components/MkUserList.vue'; +import { watch } from 'vue'; import MkFoldableSection from '@/components/MkFoldableSection.vue'; -import MkInput from '@/components/MkInput.vue'; -import MkButton from '@/components/MkButton.vue'; -import MkTab from '@/components/MkTab.vue'; import MkPagination from '@/components/MkPagination.vue'; import MkGalleryPostPreview from '@/components/MkGalleryPostPreview.vue'; -import number from '@/filters/number'; -import * as os from '@/os'; import { definePageMetadata } from '@/scripts/page-metadata'; import { i18n } from '@/i18n'; import { useRouter } from '@/router'; diff --git a/packages/frontend/src/pages/gallery/post.vue b/packages/frontend/src/pages/gallery/post.vue index 31e2727bb5..4bf7c8c514 100644 --- a/packages/frontend/src/pages/gallery/post.vue +++ b/packages/frontend/src/pages/gallery/post.vue @@ -47,7 +47,7 @@ </MkPagination> </MkContainer> </div> - <MkError v-else-if="error" @retry="fetch()"/> + <MkError v-else-if="error" @retry="fetchPost()"/> <MkLoading v-else/> </Transition> </div> @@ -56,11 +56,10 @@ </template> <script lang="ts" setup> -import { computed, defineComponent, inject, watch } from 'vue'; +import { computed, watch } from 'vue'; import MkButton from '@/components/MkButton.vue'; import * as os from '@/os'; import MkContainer from '@/components/MkContainer.vue'; -import ImgWithBlurhash from '@/components/MkImgWithBlurhash.vue'; import MkPagination from '@/components/MkPagination.vue'; import MkGalleryPostPreview from '@/components/MkGalleryPostPreview.vue'; import MkFollowButton from '@/components/MkFollowButton.vue'; diff --git a/packages/frontend/src/pages/instance-info.vue b/packages/frontend/src/pages/instance-info.vue index 714f95add9..ba5fda137a 100644 --- a/packages/frontend/src/pages/instance-info.vue +++ b/packages/frontend/src/pages/instance-info.vue @@ -124,7 +124,6 @@ import MkSelect from '@/components/MkSelect.vue'; import MkSwitch from '@/components/MkSwitch.vue'; import * as os from '@/os'; import number from '@/filters/number'; -import bytes from '@/filters/bytes'; import { iAmModerator } from '@/account'; import { definePageMetadata } from '@/scripts/page-metadata'; import { i18n } from '@/i18n'; diff --git a/packages/frontend/src/pages/messaging/index.vue b/packages/frontend/src/pages/messaging/index.vue deleted file mode 100644 index 3d11cf13e9..0000000000 --- a/packages/frontend/src/pages/messaging/index.vue +++ /dev/null @@ -1,305 +0,0 @@ -<template> -<MkStickyContainer> - <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template> - <MkSpacer :content-max="800"> - <div class="yweeujhr"> - <MkButton primary class="start" @click="start"><i class="ti ti-plus"></i> {{ $ts.startMessaging }}</MkButton> - - <div v-if="messages.length > 0" class="history"> - <MkA - v-for="(message, i) in messages" - :key="message.id" - v-anim="i" - class="message _panel" - :class="{ isMe: isMe(message), isRead: message.groupId ? message.reads.includes($i.id) : message.isRead }" - :to="message.groupId ? `/my/messaging/group/${message.groupId}` : `/my/messaging/${getAcct(isMe(message) ? message.recipient : message.user)}`" - :data-index="i" - > - <div> - <MkAvatar class="avatar" :user="message.groupId ? message.user : isMe(message) ? message.recipient : message.user" indicator link preview/> - <header v-if="message.groupId"> - <span class="name">{{ message.group.name }}</span> - <MkTime :time="message.createdAt" class="time"/> - </header> - <header v-else> - <span class="name"><MkUserName :user="isMe(message) ? message.recipient : message.user"/></span> - <span class="username">@{{ acct(isMe(message) ? message.recipient : message.user) }}</span> - <MkTime :time="message.createdAt" class="time"/> - </header> - <div class="body"> - <p class="text"><span v-if="isMe(message)" class="me">{{ $ts.you }}:</span>{{ message.text }}</p> - </div> - </div> - </MkA> - </div> - <div v-if="!fetching && messages.length == 0" class="_fullinfo"> - <img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/> - <div>{{ $ts.noHistory }}</div> - </div> - <MkLoading v-if="fetching"/> - </div> - </MkSpacer> -</MkStickyContainer> -</template> - -<script lang="ts" setup> -import { defineAsyncComponent, defineComponent, inject, markRaw, onMounted, onUnmounted } from 'vue'; -import * as Acct from 'misskey-js/built/acct'; -import MkButton from '@/components/MkButton.vue'; -import { acct } from '@/filters/user'; -import * as os from '@/os'; -import { stream } from '@/stream'; -import { useRouter } from '@/router'; -import { i18n } from '@/i18n'; -import { definePageMetadata } from '@/scripts/page-metadata'; -import { $i } from '@/account'; - -const router = useRouter(); - -let fetching = $ref(true); -let moreFetching = $ref(false); -let messages = $ref([]); -let connection = $ref(null); - -const getAcct = Acct.toString; - -function isMe(message) { - return message.userId === $i.id; -} - -function onMessage(message) { - if (message.recipientId) { - messages = messages.filter(m => !( - (m.recipientId === message.recipientId && m.userId === message.userId) || - (m.recipientId === message.userId && m.userId === message.recipientId))); - - messages.unshift(message); - } else if (message.groupId) { - messages = messages.filter(m => m.groupId !== message.groupId); - messages.unshift(message); - } -} - -function onRead(ids) { - for (const id of ids) { - const found = messages.find(m => m.id === id); - if (found) { - if (found.recipientId) { - found.isRead = true; - } else if (found.groupId) { - found.reads.push($i.id); - } - } - } -} - -function start(ev) { - os.popupMenu([{ - text: i18n.ts.messagingWithUser, - icon: 'ti ti-user', - action: () => { startUser(); }, - }, { - text: i18n.ts.messagingWithGroup, - icon: 'ti ti-users', - action: () => { startGroup(); }, - }], ev.currentTarget ?? ev.target); -} - -async function startUser() { - os.selectUser().then(user => { - router.push(`/my/messaging/${Acct.toString(user)}`); - }); -} - -async function startGroup() { - const groups1 = await os.api('users/groups/owned'); - const groups2 = await os.api('users/groups/joined'); - if (groups1.length === 0 && groups2.length === 0) { - os.alert({ - type: 'warning', - title: i18n.ts.youHaveNoGroups, - text: i18n.ts.joinOrCreateGroup, - }); - return; - } - const { canceled, result: group } = await os.select({ - title: i18n.ts.group, - items: groups1.concat(groups2).map(group => ({ - value: group, text: group.name, - })), - }); - if (canceled) return; - router.push(`/my/messaging/group/${group.id}`); -} - -onMounted(() => { - connection = markRaw(stream.useChannel('messagingIndex')); - - connection.on('message', onMessage); - connection.on('read', onRead); - - os.api('messaging/history', { group: false }).then(userMessages => { - os.api('messaging/history', { group: true }).then(groupMessages => { - const _messages = userMessages.concat(groupMessages); - _messages.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()); - messages = _messages; - fetching = false; - }); - }); -}); - -onUnmounted(() => { - if (connection) connection.dispose(); -}); - -const headerActions = $computed(() => []); - -const headerTabs = $computed(() => []); - -definePageMetadata({ - title: i18n.ts.messaging, - icon: 'ti ti-messages', -}); -</script> - -<style lang="scss" scoped> -.yweeujhr { - - > .start { - margin: 0 auto var(--margin) auto; - } - - > .history { - > .message { - display: block; - text-decoration: none; - margin-bottom: var(--margin); - - * { - pointer-events: none; - user-select: none; - } - - &:hover { - .avatar { - filter: saturate(200%); - } - } - - &:active { - } - - &.isRead, - &.isMe { - opacity: 0.8; - } - - &:not(.isMe):not(.isRead) { - > div { - background-image: url("/client-assets/unread.svg"); - background-repeat: no-repeat; - background-position: 0 center; - } - } - - &:after { - content: ""; - display: block; - clear: both; - } - - > div { - padding: 20px 30px; - - &:after { - content: ""; - display: block; - clear: both; - } - - > header { - display: flex; - align-items: center; - margin-bottom: 2px; - white-space: nowrap; - overflow: hidden; - - > .name { - margin: 0; - padding: 0; - overflow: hidden; - text-overflow: ellipsis; - font-size: 1em; - font-weight: bold; - transition: all 0.1s ease; - } - - > .username { - margin: 0 8px; - } - - > .time { - margin: 0 0 0 auto; - } - } - - > .avatar { - float: left; - width: 54px; - height: 54px; - margin: 0 16px 0 0; - border-radius: 8px; - transition: all 0.1s ease; - } - - > .body { - - > .text { - display: block; - margin: 0 0 0 0; - padding: 0; - overflow: hidden; - overflow-wrap: break-word; - font-size: 1.1em; - color: var(--faceText); - - .me { - opacity: 0.7; - } - } - - > .image { - display: block; - max-width: 100%; - max-height: 512px; - } - } - } - } - } -} - -@container (max-width: 400px) { - .yweeujhr { - > .history { - > .message { - &:not(.isMe):not(.isRead) { - > div { - background-image: none; - border-left: solid 4px #3aa2dc; - } - } - - > div { - padding: 16px; - font-size: 0.9em; - - > .avatar { - margin: 0 12px 0 0; - } - } - } - } - } -} -</style> diff --git a/packages/frontend/src/pages/messaging/messaging-room.form.vue b/packages/frontend/src/pages/messaging/messaging-room.form.vue deleted file mode 100644 index d6113668dd..0000000000 --- a/packages/frontend/src/pages/messaging/messaging-room.form.vue +++ /dev/null @@ -1,366 +0,0 @@ -<template> -<div - :class="$style['root']" - @dragover.stop="onDragover" - @drop.stop="onDrop" -> - <textarea - :class="$style['textarea']" - class="_acrylic" - ref="textEl" - v-model="text" - :placeholder="i18n.ts.inputMessageHere" - @keydown="onKeydown" - @compositionupdate="onCompositionUpdate" - @paste="onPaste" - ></textarea> - <footer :class="$style['footer']"> - <div v-if="file" :class="$style['file']" @click="file = null">{{ file.name }}</div> - <div :class="$style['buttons']"> - <button class="_button" :class="$style['button']" @click="chooseFile"><i class="ti ti-photo-plus"></i></button> - <button class="_button" :class="$style['button']" @click="insertEmoji"><i class="ti ti-mood-happy"></i></button> - <button class="_button" :class="[$style['button'], $style['send']]" :disabled="!canSend || sending" :title="i18n.ts.send" @click="send"> - <template v-if="!sending"><i class="ti ti-send"></i></template><template v-if="sending"><MkLoading :em="true"/></template> - </button> - </div> - </footer> - <input :class="$style['file-input']" ref="fileEl" type="file" @change="onChangeFile"/> -</div> -</template> - -<script lang="ts" setup> -import { onMounted, watch } from 'vue'; -import * as Misskey from 'misskey-js'; -import autosize from 'autosize'; -//import insertTextAtCursor from 'insert-text-at-cursor'; -import { throttle } from 'throttle-debounce'; -import { formatTimeString } from '@/scripts/format-time-string'; -import { selectFile } from '@/scripts/select-file'; -import * as os from '@/os'; -import { stream } from '@/stream'; -import { defaultStore } from '@/store'; -import { i18n } from '@/i18n'; -//import { Autocomplete } from '@/scripts/autocomplete'; -import { uploadFile } from '@/scripts/upload'; -import { miLocalStorage } from '@/local-storage'; - -const props = defineProps<{ - user?: Misskey.entities.UserDetailed | null; - group?: Misskey.entities.UserGroup | null; -}>(); - -let textEl = $shallowRef<HTMLTextAreaElement>(); -let fileEl = $shallowRef<HTMLInputElement>(); - -let text = $ref<string>(''); -let file = $ref<Misskey.entities.DriveFile | null>(null); -let sending = $ref(false); -const typing = throttle(3000, () => { - stream.send('typingOnMessaging', props.user ? { partner: props.user.id } : { group: props.group?.id }); -}); - -let draftKey = $computed(() => props.user ? 'user:' + props.user.id : 'group:' + props.group?.id); -let canSend = $computed(() => (text != null && text !== '') || file != null); - -watch([$$(text), $$(file)], saveDraft); - -async function onPaste(ev: ClipboardEvent) { - if (!ev.clipboardData) return; - - const clipboardData = ev.clipboardData; - const items = clipboardData.items; - - if (items.length === 1) { - if (items[0].kind === 'file') { - const pastedFile = items[0].getAsFile(); - if (!pastedFile) return; - const lio = pastedFile.name.lastIndexOf('.'); - const ext = lio >= 0 ? pastedFile.name.slice(lio) : ''; - const formatted = formatTimeString(new Date(pastedFile.lastModified), defaultStore.state.pastedFileName).replace(/{{number}}/g, '1') + ext; - if (formatted) upload(pastedFile, formatted); - } - } else { - if (items[0].kind === 'file') { - os.alert({ - type: 'error', - text: i18n.ts.onlyOneFileCanBeAttached, - }); - } - } -} - -function onDragover(ev: DragEvent) { - if (!ev.dataTransfer) return; - - const isFile = ev.dataTransfer.items[0].kind === 'file'; - const isDriveFile = ev.dataTransfer.types[0] === _DATA_TRANSFER_DRIVE_FILE_; - if (isFile || isDriveFile) { - ev.preventDefault(); - switch (ev.dataTransfer.effectAllowed) { - case 'all': - case 'uninitialized': - case 'copy': - case 'copyLink': - case 'copyMove': - ev.dataTransfer.dropEffect = 'copy'; - break; - case 'linkMove': - case 'move': - ev.dataTransfer.dropEffect = 'move'; - break; - default: - ev.dataTransfer.dropEffect = 'none'; - break; - } - } -} - -function onDrop(ev: DragEvent): void { - if (!ev.dataTransfer) return; - - // ファイルだったら - if (ev.dataTransfer.files.length === 1) { - ev.preventDefault(); - upload(ev.dataTransfer.files[0]); - return; - } else if (ev.dataTransfer.files.length > 1) { - ev.preventDefault(); - os.alert({ - type: 'error', - text: i18n.ts.onlyOneFileCanBeAttached, - }); - return; - } - - //#region ドライブのファイル - const driveFile = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); - if (driveFile != null && driveFile !== '') { - file = JSON.parse(driveFile); - ev.preventDefault(); - } - //#endregion -} - -function onKeydown(ev: KeyboardEvent) { - typing(); - if ((ev.key === 'Enter') && (ev.ctrlKey || ev.metaKey) && canSend) { - send(); - } -} - -function onCompositionUpdate() { - typing(); -} - -function chooseFile(ev: MouseEvent) { - selectFile(ev.currentTarget ?? ev.target, i18n.ts.selectFile).then(selectedFile => { - file = selectedFile; - }); -} - -function onChangeFile() { - if (fileEl.files![0]) upload(fileEl.files[0]); -} - -function upload(fileToUpload: File, name?: string) { - uploadFile(fileToUpload, defaultStore.state.uploadFolder, name).then(res => { - file = res; - }); -} - -function send() { - sending = true; - os.api('messaging/messages/create', { - userId: props.user ? props.user.id : undefined, - groupId: props.group ? props.group.id : undefined, - text: text ? text : undefined, - fileId: file ? file.id : undefined, - }).then(message => { - clear(); - }).catch(err => { - console.error(err); - }).then(() => { - sending = false; - }); -} - -function clear() { - text = ''; - file = null; - deleteDraft(); -} - -function saveDraft() { - const drafts = JSON.parse(miLocalStorage.getItem('message_drafts') || '{}'); - - drafts[draftKey] = { - updatedAt: new Date(), - // eslint-disable-next-line id-denylist - data: { - text: text, - file: file, - }, - }; - - miLocalStorage.setItem('message_drafts', JSON.stringify(drafts)); -} - -function deleteDraft() { - const drafts = JSON.parse(miLocalStorage.getItem('message_drafts') || '{}'); - - delete drafts[draftKey]; - - miLocalStorage.setItem('message_drafts', JSON.stringify(drafts)); -} - -async function insertEmoji(ev: MouseEvent) { - os.openEmojiPicker(ev.currentTarget ?? ev.target, {}, textEl); -} - -onMounted(() => { - autosize(textEl); - - // TODO: detach when unmount - // TODO - //new Autocomplete(textEl, this, { model: 'text' }); - - // 書きかけの投稿を復元 - const draft = JSON.parse(miLocalStorage.getItem('message_drafts') || '{}')[draftKey]; - if (draft) { - text = draft.data.text; - file = draft.data.file; - } -}); - -defineExpose({ - file, - upload, -}); -</script> - -<style lang="scss" module> -.root { - position: relative; -} - -.textarea { - cursor: auto; - display: block; - width: 100%; - min-width: 100%; - max-width: 100%; - min-height: 80px; - margin: 0; - padding: 16px 16px 0 16px; - resize: none; - font-size: 1em; - font-family: inherit; - outline: none; - border: none; - border-radius: 0; - box-shadow: none; - box-sizing: border-box; - color: var(--fg); -} - -.footer { - position: sticky; - bottom: 0; - background: var(--panel); -} - -.file { - padding: 8px; - color: var(--fg); - background: transparent; - cursor: pointer; -} -/* -.files { - display: block; - margin: 0; - padding: 0 8px; - list-style: none; - - &:after { - content: ''; - display: block; - clear: both; - } - - > li { - display: block; - float: left; - margin: 4px; - padding: 0; - width: 64px; - height: 64px; - background-color: #eee; - background-repeat: no-repeat; - background-position: center center; - background-size: cover; - cursor: move; - - &:hover { - > .remove { - display: block; - } - } - } -} - -.file-remove { - display: none; - position: absolute; - right: -6px; - top: -6px; - margin: 0; - padding: 0; - background: transparent; - outline: none; - border: none; - border-radius: 0; - box-shadow: none; - cursor: pointer; -} -*/ - -.buttons { - display: flex; -} - -.button { - margin: 0; - padding: 16px; - font-size: 1em; - font-weight: normal; - text-decoration: none; - transition: color 0.1s ease; - - &:hover { - color: var(--accent); - } - - &:active { - color: var(--accentDarken); - transition: color 0s ease; - } -} -.send { - margin-left: auto; - color: var(--accent); - - &:hover { - color: var(--accentLighten); - } - - &:active { - color: var(--accentDarken); - transition: color 0s ease; - } -} - -.file-input { - display: none; -} -</style> diff --git a/packages/frontend/src/pages/messaging/messaging-room.message.vue b/packages/frontend/src/pages/messaging/messaging-room.message.vue deleted file mode 100644 index d10798b92e..0000000000 --- a/packages/frontend/src/pages/messaging/messaging-room.message.vue +++ /dev/null @@ -1,338 +0,0 @@ -<template> -<div class="thvuemwp" :class="{ isMe }"> - <MkAvatar class="avatar" :user="message.user" indicator link preview/> - <div class="content"> - <div class="balloon" :class="{ noText: message.text == null }"> - <button v-if="isMe" class="delete-button" :title="$ts.delete" @click="del"> - <img src="/client-assets/remove.png" alt="Delete"/> - </button> - <div v-if="!message.isDeleted" class="content"> - <Mfm v-if="message.text" ref="text" class="text" :text="message.text" :i="$i"/> - <div v-if="message.file" class="file"> - <a :href="message.file.url" rel="noopener" target="_blank" :title="message.file.name"> - <img v-if="message.file.type.split('/')[0] == 'image'" :src="message.file.url" :alt="message.file.name"/> - <p v-else>{{ message.file.name }}</p> - </a> - </div> - </div> - <div v-else class="content"> - <p class="is-deleted">{{ $ts.deleted }}</p> - </div> - </div> - <div></div> - <MkUrlPreview v-for="url in urls" :key="url" :url="url" style="margin: 8px 0;"/> - <footer> - <template v-if="isGroup"> - <span v-if="message.reads.length > 0" class="read">{{ $ts.messageRead }} {{ message.reads.length }}</span> - </template> - <template v-else> - <span v-if="isMe && message.isRead" class="read">{{ $ts.messageRead }}</span> - </template> - <MkTime :time="message.createdAt"/> - <template v-if="message.is_edited"><i class="ti ti-pencil"></i></template> - </footer> - </div> -</div> -</template> - -<script lang="ts" setup> -import { } from 'vue'; -import * as mfm from 'mfm-js'; -import * as Misskey from 'misskey-js'; -import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm'; -import MkUrlPreview from '@/components/MkUrlPreview.vue'; -import * as os from '@/os'; -import { $i } from '@/account'; - -const props = defineProps<{ - message: Misskey.entities.MessagingMessage; - isGroup?: boolean; -}>(); - -const isMe = $computed(() => props.message.userId === $i?.id); -const urls = $computed(() => props.message.text ? extractUrlFromMfm(mfm.parse(props.message.text)) : []); - -function del(): void { - os.api('messaging/messages/delete', { - messageId: props.message.id, - }); -} -</script> - -<style lang="scss" scoped> -.thvuemwp { - $me-balloon-color: var(--accent); - - position: relative; - background-color: transparent; - display: flex; - - > .avatar { - position: sticky; - top: calc(var(--stickyTop, 0px) + 16px); - display: block; - width: 54px; - height: 54px; - transition: all 0.1s ease; - } - - > .content { - min-width: 0; - - > .balloon { - position: relative; - display: inline-flex; - align-items: center; - padding: 0; - min-height: 38px; - border-radius: 16px; - max-width: 100%; - - &:before { - content: ""; - pointer-events: none; - display: block; - position: absolute; - top: 12px; - } - - & + * { - clear: both; - } - - &:hover { - > .delete-button { - display: block; - } - } - - > .delete-button { - display: none; - position: absolute; - z-index: 1; - top: -4px; - right: -4px; - margin: 0; - padding: 0; - cursor: pointer; - outline: none; - border: none; - border-radius: 0; - box-shadow: none; - background: transparent; - - > img { - vertical-align: bottom; - width: 16px; - height: 16px; - cursor: pointer; - } - } - - > .content { - max-width: 100%; - - > .is-deleted { - display: block; - margin: 0; - padding: 0; - overflow: hidden; - overflow-wrap: break-word; - font-size: 1em; - color: rgba(#000, 0.5); - } - - > .text { - display: block; - margin: 0; - padding: 12px 18px; - overflow: hidden; - overflow-wrap: break-word; - word-break: break-word; - font-size: 1em; - color: rgba(#000, 0.8); - - & + .file { - > a { - border-radius: 0 0 16px 16px; - } - } - } - - > .file { - > a { - display: block; - max-width: 100%; - border-radius: 16px; - overflow: hidden; - text-decoration: none; - - &:hover { - text-decoration: none; - - > p { - background: #ccc; - } - } - - > * { - display: block; - margin: 0; - width: 100%; - max-height: 512px; - object-fit: contain; - box-sizing: border-box; - } - - > p { - padding: 30px; - text-align: center; - color: #555; - background: #ddd; - } - } - } - } - } - - > footer { - display: block; - margin: 2px 0 0 0; - font-size: 0.65em; - - > .read { - margin: 0 8px; - } - - > i { - margin-left: 4px; - } - } - } - - &:not(.isMe) { - padding-left: var(--margin); - - > .content { - padding-left: 16px; - padding-right: 32px; - - > .balloon { - $color: var(--messageBg); - background: $color; - - &.noText { - background: transparent; - } - - &:not(.noText):before { - left: -14px; - border-top: solid 8px transparent; - border-right: solid 8px $color; - border-bottom: solid 8px transparent; - border-left: solid 8px transparent; - } - - > .content { - > .text { - color: var(--fg); - } - } - } - - > footer { - text-align: left; - } - } - } - - &.isMe { - flex-direction: row-reverse; - padding-right: var(--margin); - right: var(--margin); // 削除時にposition: absoluteになったときに使う - - > .content { - padding-right: 16px; - padding-left: 32px; - text-align: right; - - > .balloon { - background: $me-balloon-color; - text-align: left; - - ::selection { - color: var(--accent); - background-color: #fff; - } - - &.noText { - background: transparent; - } - - &:not(.noText):before { - right: -14px; - left: auto; - border-top: solid 8px transparent; - border-right: solid 8px transparent; - border-bottom: solid 8px transparent; - border-left: solid 8px $me-balloon-color; - } - - > .content { - - > p.is-deleted { - color: rgba(#fff, 0.5); - } - - > .text { - &, ::v-deep(*) { - color: var(--fgOnAccent) !important; - } - } - } - } - - > footer { - text-align: right; - - > .read { - user-select: none; - } - } - } - } -} - -@container (max-width: 400px) { - .thvuemwp { - > .avatar { - width: 48px; - height: 48px; - } - - > .content { - > .balloon { - > .content { - > .text { - font-size: 0.9em; - } - } - } - } - } -} - -@container (max-width: 500px) { - .thvuemwp { - > .content { - > .balloon { - > .content { - > .text { - padding: 8px 16px; - } - } - } - } - } -} -</style> diff --git a/packages/frontend/src/pages/messaging/messaging-room.vue b/packages/frontend/src/pages/messaging/messaging-room.vue deleted file mode 100644 index 0867f003a3..0000000000 --- a/packages/frontend/src/pages/messaging/messaging-room.vue +++ /dev/null @@ -1,415 +0,0 @@ -<template> -<MkStickyContainer> -<template #header> - <MkPageHeader /> -</template> -<div - ref="rootEl" - :class="$style['root']" - @dragover.prevent.stop="onDragover" - @drop.prevent.stop="onDrop" -> - <div :class="$style['body']"> - <MkPagination v-if="pagination" ref="pagingComponent" :key="userAcct || groupId" :pagination="pagination"> - <template #empty> - <div class="_fullinfo"> - <img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/> - <div>{{ i18n.ts.noMessagesYet }}</div> - </div> - </template> - <template #default="{ items: messages, fetching: pFetching }"> - <MkDateSeparatedList - v-if="messages.length > 0" - v-slot="{ item: message }" - :class="{ [$style['messages']]: true, 'deny-move-transition': pFetching }" - :items="messages" - direction="up" - reversed - > - <XMessage :key="message.id" :message="message" :is-group="group != null"/> - </MkDateSeparatedList> - </template> - </MkPagination> - </div> - <footer :class="$style['footer']"> - <div v-if="typers.length > 0" :class="$style['typers']"> - <I18n :src="i18n.ts.typingUsers" text-tag="span"> - <template #users> - <b v-for="typer in typers" :key="typer.id" :class="$style['user']">{{ typer.username }}</b> - </template> - </I18n> - <MkEllipsis/> - </div> - <Transition :name="animation ? 'fade' : ''"> - <div v-show="showIndicator" :class="$style['new-message']"> - <button class="_buttonPrimary" @click="onIndicatorClick" :class="$style['new-message-button']"> - <i class="fas ti-fw fa-arrow-circle-down" :class="$style['new-message-icon']"></i>{{ i18n.ts.newMessageExists }} - </button> - </div> - </Transition> - <XForm v-if="!fetching" ref="formEl" :user="user" :group="group" :class="$style['form']"/> - </footer> -</div> -</MkStickyContainer> -</template> - -<script lang="ts" setup> -import { computed, watch, onMounted, nextTick, onBeforeUnmount } from 'vue'; -import * as Misskey from 'misskey-js'; -import * as Acct from 'misskey-js/built/acct'; -import XMessage from './messaging-room.message.vue'; -import XForm from './messaging-room.form.vue'; -import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue'; -import MkPagination, { Paging } from '@/components/MkPagination.vue'; -import { isBottomVisible, onScrollBottom, scrollToBottom } from '@/scripts/scroll'; -import * as os from '@/os'; -import { stream } from '@/stream'; -import * as sound from '@/scripts/sound'; -import { i18n } from '@/i18n'; -import { $i } from '@/account'; -import { defaultStore } from '@/store'; -import { definePageMetadata } from '@/scripts/page-metadata'; - -const props = defineProps<{ - userAcct?: string; - groupId?: string; -}>(); - -let rootEl = $shallowRef<HTMLDivElement>(); -let formEl = $shallowRef<InstanceType<typeof XForm>>(); -let pagingComponent = $shallowRef<InstanceType<typeof MkPagination>>(); - -let fetching = $ref(true); -let user: Misskey.entities.UserDetailed | null = $ref(null); -let group: Misskey.entities.UserGroup | null = $ref(null); -let typers: Misskey.entities.User[] = $ref([]); -let connection: Misskey.ChannelConnection<Misskey.Channels['messaging']> | null = $ref(null); -let showIndicator = $ref(false); -const { - animation, -} = defaultStore.reactiveState; - -let pagination: Paging | null = $ref(null); - -watch([() => props.userAcct, () => props.groupId], () => { - if (connection) connection.dispose(); - fetch(); -}); - -async function fetch() { - fetching = true; - - if (props.userAcct) { - const acct = Acct.parse(props.userAcct); - user = await os.api('users/show', { username: acct.username, host: acct.host || undefined }); - group = null; - - pagination = { - endpoint: 'messaging/messages', - limit: 20, - params: { - userId: user.id, - }, - reversed: true, - pageEl: $$(rootEl).value, - }; - connection = stream.useChannel('messaging', { - otherparty: user.id, - }); - } else { - user = null; - group = await os.api('users/groups/show', { groupId: props.groupId }); - - pagination = { - endpoint: 'messaging/messages', - limit: 20, - params: { - groupId: group?.id, - }, - reversed: true, - pageEl: $$(rootEl).value, - }; - connection = stream.useChannel('messaging', { - group: group?.id, - }); - } - - connection.on('message', onMessage); - connection.on('read', onRead); - connection.on('deleted', onDeleted); - connection.on('typers', _typers => { - typers = _typers.filter(u => u.id !== $i?.id); - }); - - document.addEventListener('visibilitychange', onVisibilitychange); - - nextTick(() => { - pagingComponent.inited.then(() => { - thisScrollToBottom(); - }); - window.setTimeout(() => { - fetching = false; - }, 300); - }); -} - -function onDragover(ev: DragEvent) { - if (!ev.dataTransfer) return; - - const isFile = ev.dataTransfer.items[0].kind === 'file'; - const isDriveFile = ev.dataTransfer.types[0] === _DATA_TRANSFER_DRIVE_FILE_; - - if (isFile || isDriveFile) { - switch (ev.dataTransfer.effectAllowed) { - case 'all': - case 'uninitialized': - case 'copy': - case 'copyLink': - case 'copyMove': - ev.dataTransfer.dropEffect = 'copy'; - break; - case 'linkMove': - case 'move': - ev.dataTransfer.dropEffect = 'move'; - break; - default: - ev.dataTransfer.dropEffect = 'none'; - break; - } - } else { - ev.dataTransfer.dropEffect = 'none'; - } -} - -function onDrop(ev: DragEvent): void { - if (!ev.dataTransfer) return; - - // ファイルだったら - if (ev.dataTransfer.files.length === 1) { - formEl.upload(ev.dataTransfer.files[0]); - return; - } else if (ev.dataTransfer.files.length > 1) { - os.alert({ - type: 'error', - text: i18n.ts.onlyOneFileCanBeAttached, - }); - return; - } - - //#region ドライブのファイル - const driveFile = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); - if (driveFile != null && driveFile !== '') { - const file = JSON.parse(driveFile); - formEl.file = file; - } - //#endregion -} - -function onMessage(message) { - sound.play('chat'); - - const _isBottom = isBottomVisible(rootEl, 64); - - pagingComponent.prepend(message); - if (message.userId !== $i?.id && !document.hidden) { - connection?.send('read', { - id: message.id, - }); - } - - if (_isBottom) { - // Scroll to bottom - nextTick(() => { - thisScrollToBottom(); - }); - } else if (message.userId !== $i?.id) { - // Notify - notifyNewMessage(); - } -} - -function onRead(x) { - if (user) { - if (!Array.isArray(x)) x = [x]; - for (const id of x) { - if (pagingComponent.items.some(y => y.id === id)) { - const exist = pagingComponent.items.map(y => y.id).indexOf(id); - pagingComponent.items[exist] = { - ...pagingComponent.items[exist], - isRead: true, - }; - } - } - } else if (group) { - for (const id of x.ids) { - if (pagingComponent.items.some(y => y.id === id)) { - const exist = pagingComponent.items.map(y => y.id).indexOf(id); - pagingComponent.items[exist] = { - ...pagingComponent.items[exist], - reads: [...pagingComponent.items[exist].reads, x.userId], - }; - } - } - } -} - -function onDeleted(id) { - const msg = pagingComponent.items.find(m => m.id === id); - if (msg) { - pagingComponent.items = pagingComponent.items.filter(m => m.id !== msg.id); - } -} - -function thisScrollToBottom() { - scrollToBottom($$(rootEl).value, { behavior: 'smooth' }); -} - -function onIndicatorClick() { - showIndicator = false; - thisScrollToBottom(); -} - -let scrollRemove: (() => void) | null = $ref(null); - -function notifyNewMessage() { - showIndicator = true; - - scrollRemove = onScrollBottom(rootEl, () => { - showIndicator = false; - scrollRemove = null; - }); -} - -function onVisibilitychange() { - if (document.hidden) return; - for (const message of pagingComponent.items) { - if (message.userId !== $i?.id && !message.isRead) { - connection?.send('read', { - id: message.id, - }); - } - } -} - -onMounted(() => { - fetch(); -}); - -onBeforeUnmount(() => { - connection?.dispose(); - document.removeEventListener('visibilitychange', onVisibilitychange); - if (scrollRemove) scrollRemove(); -}); - -definePageMetadata(computed(() => !fetching ? user ? { - userName: user, - avatar: user, -} : { - title: group?.name, - icon: 'ti ti-users', -} : null)); -</script> - -<style lang="scss" module> -.root { - display: content; -} - -.body { - min-height: 80%; -} - -.more { - display: block; - margin: 16px auto; - padding: 0 12px; - line-height: 24px; - color: #fff; - background: rgba(#000, 0.3); - border-radius: 12px; - &:hover { - background: rgba(#000, 0.4); - } - &:active { - background: rgba(#000, 0.5); - } - > i { - margin-right: 4px; - } -} - -.fetching { - cursor: wait; -} - -.messages { - padding: 16px 0 0; - - > * { - margin-bottom: 16px; - } -} - -.footer { - width: 100%; - position: sticky; - z-index: 2; - padding-top: 8px; - bottom: var(--minBottomSpacing); -} - -.new-message { - width: 100%; - padding-bottom: 8px; - text-align: center; -} - -.new-message-button { - display: inline-block; - margin: 0; - padding: 0 12px; - line-height: 32px; - font-size: 12px; - border-radius: 16px; -} - -.new-message-icon { - display: inline-block; - margin-right: 8px; -} - -.typers { - position: absolute; - bottom: 100%; - padding: 0 8px 0 8px; - font-size: 0.9em; - color: var(--fgTransparentWeak); -} - - -.user + .user:before { - content: ", "; - font-weight: normal; -} - -.user:last-of-type:after { - content: " "; -} - -.form { - max-height: 12em; - overflow-y: scroll; - border-top: solid 0.5px var(--divider); - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; -} - -.fade-enter-active, .fade-leave-active { - transition: opacity 0.1s; -} - -.fade-enter-from, .fade-leave-to { - transition: opacity 0.5s; - opacity: 0; -} -</style> diff --git a/packages/frontend/src/pages/my-antennas/create.vue b/packages/frontend/src/pages/my-antennas/create.vue index 005b036696..c35af3e22a 100644 --- a/packages/frontend/src/pages/my-antennas/create.vue +++ b/packages/frontend/src/pages/my-antennas/create.vue @@ -5,7 +5,6 @@ </template> <script lang="ts" setup> -import { inject } from 'vue'; import XAntenna from './editor.vue'; import { i18n } from '@/i18n'; import { definePageMetadata } from '@/scripts/page-metadata'; @@ -17,7 +16,6 @@ let draft = $ref({ name: '', src: 'all', userListId: null, - userGroupId: null, users: [], keywords: [], excludeKeywords: [], diff --git a/packages/frontend/src/pages/my-antennas/edit.vue b/packages/frontend/src/pages/my-antennas/edit.vue index cb583faaeb..913fbde8e9 100644 --- a/packages/frontend/src/pages/my-antennas/edit.vue +++ b/packages/frontend/src/pages/my-antennas/edit.vue @@ -5,7 +5,6 @@ </template> <script lang="ts" setup> -import { inject, watch } from 'vue'; import XAntenna from './editor.vue'; import * as os from '@/os'; import { i18n } from '@/i18n'; diff --git a/packages/frontend/src/pages/my-antennas/editor.vue b/packages/frontend/src/pages/my-antennas/editor.vue index 5c1eb66947..26b7bcc71b 100644 --- a/packages/frontend/src/pages/my-antennas/editor.vue +++ b/packages/frontend/src/pages/my-antennas/editor.vue @@ -11,16 +11,11 @@ <!--<option value="home">{{ i18n.ts._antennaSources.homeTimeline }}</option>--> <option value="users">{{ i18n.ts._antennaSources.users }}</option> <!--<option value="list">{{ i18n.ts._antennaSources.userList }}</option>--> - <!--<option value="group">{{ i18n.ts._antennaSources.userGroup }}</option>--> </MkSelect> <MkSelect v-if="src === 'list'" v-model="userListId"> <template #label>{{ i18n.ts.userList }}</template> <option v-for="list in userLists" :key="list.id" :value="list.id">{{ list.name }}</option> </MkSelect> - <MkSelect v-else-if="src === 'group'" v-model="userGroupId"> - <template #label>{{ i18n.ts.userGroup }}</template> - <option v-for="group in userGroups" :key="group.id" :value="group.id">{{ group.name }}</option> - </MkSelect> <MkTextarea v-else-if="src === 'users'" v-model="users"> <template #label>{{ i18n.ts.users }}</template> <template #caption>{{ i18n.ts.antennaUsersDescription }} <button class="_textButton" @click="addUser">{{ i18n.ts.addUser }}</button></template> @@ -70,7 +65,6 @@ const emit = defineEmits<{ let name: string = $ref(props.antenna.name); let src: string = $ref(props.antenna.src); let userListId: any = $ref(props.antenna.userListId); -let userGroupId: any = $ref(props.antenna.userGroupId); let users: string = $ref(props.antenna.users.join('\n')); let keywords: string = $ref(props.antenna.keywords.map(x => x.join(' ')).join('\n')); let excludeKeywords: string = $ref(props.antenna.excludeKeywords.map(x => x.join(' ')).join('\n')); @@ -79,19 +73,11 @@ let withReplies: boolean = $ref(props.antenna.withReplies); let withFile: boolean = $ref(props.antenna.withFile); let notify: boolean = $ref(props.antenna.notify); let userLists: any = $ref(null); -let userGroups: any = $ref(null); watch(() => src, async () => { if (src === 'list' && userLists === null) { userLists = await os.api('users/lists/list'); } - - if (src === 'group' && userGroups === null) { - const groups1 = await os.api('users/groups/owned'); - const groups2 = await os.api('users/groups/joined'); - - userGroups = [...groups1, ...groups2]; - } }); async function saveAntenna() { @@ -99,7 +85,6 @@ async function saveAntenna() { name, src, userListId, - userGroupId, withReplies, withFile, notify, diff --git a/packages/frontend/src/pages/note.vue b/packages/frontend/src/pages/note.vue index 86b3fce3c5..165e357ebd 100644 --- a/packages/frontend/src/pages/note.vue +++ b/packages/frontend/src/pages/note.vue @@ -6,7 +6,7 @@ <Transition :name="$store.state.animation ? 'fade' : ''" mode="out-in"> <div v-if="note" class="note"> <div v-if="showNext" class="_margin"> - <XNotes class="" :pagination="nextPagination" :no-gap="true"/> + <MkNotes class="" :pagination="nextPagination" :no-gap="true"/> </div> <div class="main _margin"> @@ -29,10 +29,10 @@ </div> <div v-if="showPrev" class="_margin"> - <XNotes class="" :pagination="prevPagination" :no-gap="true"/> + <MkNotes class="" :pagination="prevPagination" :no-gap="true"/> </div> </div> - <MkError v-else-if="error" @retry="fetch()"/> + <MkError v-else-if="error" @retry="fetchNote()"/> <MkLoading v-else/> </Transition> </div> @@ -41,11 +41,10 @@ </template> <script lang="ts" setup> -import { computed, defineComponent, watch } from 'vue'; +import { computed, watch } from 'vue'; import * as misskey from 'misskey-js'; -import XNote from '@/components/MkNote.vue'; import XNoteDetailed from '@/components/MkNoteDetailed.vue'; -import XNotes from '@/components/MkNotes.vue'; +import MkNotes from '@/components/MkNotes.vue'; import MkRemoteCaution from '@/components/MkRemoteCaution.vue'; import MkButton from '@/components/MkButton.vue'; import * as os from '@/os'; diff --git a/packages/frontend/src/pages/notifications.vue b/packages/frontend/src/pages/notifications.vue index 7106951de2..0fcf0f65c4 100644 --- a/packages/frontend/src/pages/notifications.vue +++ b/packages/frontend/src/pages/notifications.vue @@ -6,10 +6,10 @@ <XNotifications class="notifications" :include-types="includeTypes" :unread-only="unreadOnly"/> </div> <div v-else-if="tab === 'mentions'"> - <XNotes :pagination="mentionsPagination"/> + <MkNotes :pagination="mentionsPagination"/> </div> <div v-else-if="tab === 'directNotes'"> - <XNotes :pagination="directNotesPagination"/> + <MkNotes :pagination="directNotesPagination"/> </div> </MkSpacer> </MkStickyContainer> @@ -19,7 +19,7 @@ import { computed } from 'vue'; import { notificationTypes } from 'misskey-js'; import XNotifications from '@/components/MkNotifications.vue'; -import XNotes from '@/components/MkNotes.vue'; +import MkNotes from '@/components/MkNotes.vue'; import * as os from '@/os'; import { i18n } from '@/i18n'; import { definePageMetadata } from '@/scripts/page-metadata'; diff --git a/packages/frontend/src/pages/page-editor/els/page-editor.el.image.vue b/packages/frontend/src/pages/page-editor/els/page-editor.el.image.vue index a84cb1e80e..fe230ad095 100644 --- a/packages/frontend/src/pages/page-editor/els/page-editor.el.image.vue +++ b/packages/frontend/src/pages/page-editor/els/page-editor.el.image.vue @@ -1,10 +1,10 @@ <template> <!-- eslint-disable vue/no-mutating-props --> <XContainer :draggable="true" @remove="() => $emit('remove')"> - <template #header><i class="fas fa-image"></i> {{ $ts._pages.blocks.image }}</template> + <template #header><i class="ti ti-photo"></i> {{ $ts._pages.blocks.image }}</template> <template #func> <button @click="choose()"> - <i class="fas fa-folder-open"></i> + <i class="ti ti-folder"></i> </button> </template> diff --git a/packages/frontend/src/pages/page-editor/els/page-editor.el.text.vue b/packages/frontend/src/pages/page-editor/els/page-editor.el.text.vue index 6f11e2a08b..ee494b7574 100644 --- a/packages/frontend/src/pages/page-editor/els/page-editor.el.text.vue +++ b/packages/frontend/src/pages/page-editor/els/page-editor.el.text.vue @@ -1,7 +1,7 @@ <template> <!-- eslint-disable vue/no-mutating-props --> <XContainer :draggable="true" @remove="() => $emit('remove')"> - <template #header><i class="fas fa-align-left"></i> {{ $ts._pages.blocks.text }}</template> + <template #header><i class="ti ti-align-left"></i> {{ $ts._pages.blocks.text }}</template> <section class="vckmsadr"> <textarea v-model="text"></textarea> diff --git a/packages/frontend/src/pages/page-editor/page-editor.blocks.vue b/packages/frontend/src/pages/page-editor/page-editor.blocks.vue index f99fcb202f..97bdcfe80f 100644 --- a/packages/frontend/src/pages/page-editor/page-editor.blocks.vue +++ b/packages/frontend/src/pages/page-editor/page-editor.blocks.vue @@ -15,8 +15,6 @@ import XSection from './els/page-editor.el.section.vue'; import XText from './els/page-editor.el.text.vue'; import XImage from './els/page-editor.el.image.vue'; import XNote from './els/page-editor.el.note.vue'; -import * as os from '@/os'; -import { deepClone } from '@/scripts/clone'; export default defineComponent({ components: { diff --git a/packages/frontend/src/pages/page-editor/page-editor.vue b/packages/frontend/src/pages/page-editor/page-editor.vue index 11575ae7f4..c4b37c91c6 100644 --- a/packages/frontend/src/pages/page-editor/page-editor.vue +++ b/packages/frontend/src/pages/page-editor/page-editor.vue @@ -56,10 +56,9 @@ </template> <script lang="ts" setup> -import { defineAsyncComponent, computed, provide, watch } from 'vue'; +import { computed, provide, watch } from 'vue'; import { v4 as uuid } from 'uuid'; import XBlocks from './page-editor.blocks.vue'; -import MkTextarea from '@/components/MkTextarea.vue'; import MkButton from '@/components/MkButton.vue'; import MkSelect from '@/components/MkSelect.vue'; import MkSwitch from '@/components/MkSwitch.vue'; diff --git a/packages/frontend/src/pages/pages.vue b/packages/frontend/src/pages/pages.vue index af5f631caf..0427332ab2 100644 --- a/packages/frontend/src/pages/pages.vue +++ b/packages/frontend/src/pages/pages.vue @@ -25,7 +25,7 @@ </template> <script lang="ts" setup> -import { computed, inject } from 'vue'; +import { computed } from 'vue'; import MkPagePreview from '@/components/MkPagePreview.vue'; import MkPagination from '@/components/MkPagination.vue'; import MkButton from '@/components/MkButton.vue'; diff --git a/packages/frontend/src/pages/registry.keys.vue b/packages/frontend/src/pages/registry.keys.vue index 2c2a1444c1..c687b89eab 100644 --- a/packages/frontend/src/pages/registry.keys.vue +++ b/packages/frontend/src/pages/registry.keys.vue @@ -28,7 +28,7 @@ </template> <script lang="ts" setup> -import { ref, watch } from 'vue'; +import { watch } from 'vue'; import JSON5 from 'json5'; import * as os from '@/os'; import { i18n } from '@/i18n'; diff --git a/packages/frontend/src/pages/registry.value.vue b/packages/frontend/src/pages/registry.value.vue index bd4dbe7679..00e2ca5e03 100644 --- a/packages/frontend/src/pages/registry.value.vue +++ b/packages/frontend/src/pages/registry.value.vue @@ -40,13 +40,11 @@ </template> <script lang="ts" setup> -import { ref, watch } from 'vue'; +import { watch } from 'vue'; import JSON5 from 'json5'; import * as os from '@/os'; import { i18n } from '@/i18n'; import { definePageMetadata } from '@/scripts/page-metadata'; -import FormLink from '@/components/form/link.vue'; -import FormSection from '@/components/form/section.vue'; import MkButton from '@/components/MkButton.vue'; import MkKeyValue from '@/components/MkKeyValue.vue'; import MkTextarea from '@/components/MkTextarea.vue'; diff --git a/packages/frontend/src/pages/registry.vue b/packages/frontend/src/pages/registry.vue index a2c65294fc..5a029cb0c7 100644 --- a/packages/frontend/src/pages/registry.vue +++ b/packages/frontend/src/pages/registry.vue @@ -15,7 +15,6 @@ </template> <script lang="ts" setup> -import { ref, watch } from 'vue'; import JSON5 from 'json5'; import * as os from '@/os'; import { i18n } from '@/i18n'; diff --git a/packages/frontend/src/pages/role.vue b/packages/frontend/src/pages/role.vue new file mode 100644 index 0000000000..2e9d3d6169 --- /dev/null +++ b/packages/frontend/src/pages/role.vue @@ -0,0 +1,47 @@ +<template> +<MkStickyContainer> + <template #header><MkPageHeader/></template> + + <MkSpacer :content-max="1200"> + <div class="_gaps_s"> + <div v-if="role">{{ role.description }}</div> + <MkUserList :pagination="users" :extractor="(item) => item.user"/> + </div> + </MkSpacer> +</MkStickyContainer> +</template> + +<script lang="ts" setup> +import { computed, watch } from 'vue'; +import * as os from '@/os'; +import MkUserList from '@/components/MkUserList.vue'; +import { definePageMetadata } from '@/scripts/page-metadata'; + +const props = defineProps<{ + role: string; +}>(); + +let role = $ref(); + +watch(() => props.role, () => { + os.api('roles/show', { + roleId: props.role, + }).then(res => { + role = res; + }); +}, { immediate: true }); + +const users = $computed(() => ({ + endpoint: 'roles/users' as const, + limit: 30, + params: { + roleId: props.role, + }, +})); + +definePageMetadata(computed(() => ({ + title: role?.name, + icon: 'ti ti-badge', +}))); +</script> + diff --git a/packages/frontend/src/pages/scratchpad.vue b/packages/frontend/src/pages/scratchpad.vue index 6075dde326..fb78546cb1 100644 --- a/packages/frontend/src/pages/scratchpad.vue +++ b/packages/frontend/src/pages/scratchpad.vue @@ -44,7 +44,7 @@ import * as os from '@/os'; import { $i } from '@/account'; import { i18n } from '@/i18n'; import { definePageMetadata } from '@/scripts/page-metadata'; -import { AsUiComponent, AsUiRoot, patch, registerAsUiLib, render } from '@/scripts/aiscript/ui'; +import { AsUiComponent, AsUiRoot, registerAsUiLib } from '@/scripts/aiscript/ui'; import MkAsUi from '@/components/MkAsUi.vue'; import { miLocalStorage } from '@/local-storage'; import { claimAchievement } from '@/scripts/achievements'; diff --git a/packages/frontend/src/pages/search.vue b/packages/frontend/src/pages/search.vue index 7918f9f577..e52c97b350 100644 --- a/packages/frontend/src/pages/search.vue +++ b/packages/frontend/src/pages/search.vue @@ -2,14 +2,14 @@ <MkStickyContainer> <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template> <MkSpacer :content-max="800"> - <XNotes ref="notes" :pagination="pagination"/> + <MkNotes ref="notes" :pagination="pagination"/> </MkSpacer> </MkStickyContainer> </template> <script lang="ts" setup> import { computed } from 'vue'; -import XNotes from '@/components/MkNotes.vue'; +import MkNotes from '@/components/MkNotes.vue'; import { i18n } from '@/i18n'; import { definePageMetadata } from '@/scripts/page-metadata'; import * as os from '@/os'; diff --git a/packages/frontend/src/pages/settings/2fa.qrdialog.vue b/packages/frontend/src/pages/settings/2fa.qrdialog.vue new file mode 100644 index 0000000000..1d836db5f5 --- /dev/null +++ b/packages/frontend/src/pages/settings/2fa.qrdialog.vue @@ -0,0 +1,82 @@ +<template> +<MkModal + ref="dialogEl" + :prefer-type="'dialog'" + :z-priority="'low'" + @click="cancel" + @close="cancel" + @closed="emit('closed')" +> + <div :class="$style.root" class="_gaps_m"> + <I18n :src="i18n.ts._2fa.step1" tag="div"> + <template #a> + <a href="https://authy.com/" rel="noopener" target="_blank" class="_link">Authy</a> + </template> + <template #b> + <a href="https://support.google.com/accounts/answer/1066447" rel="noopener" target="_blank" class="_link">Google Authenticator</a> + </template> + </I18n> + <div> + {{ i18n.ts._2fa.step2 }}<br> + {{ i18n.ts._2fa.step2Click }} + </div> + <a :href="twoFactorData.url"><img :class="$style.qr" :src="twoFactorData.qr"></a> + <MkKeyValue :copy="twoFactorData.url"> + <template #key>{{ i18n.ts._2fa.step2Url }}</template> + <template #value>{{ twoFactorData.url }}</template> + </MkKeyValue> + <div class="_buttons"> + <MkButton primary @click="ok">{{ i18n.ts.next }}</MkButton> + <MkButton @click="cancel">{{ i18n.ts.cancel }}</MkButton> + </div> + </div> +</MkModal> +</template> + +<script lang="ts" setup> +import MkButton from '@/components/MkButton.vue'; +import MkModal from '@/components/MkModal.vue'; +import MkKeyValue from '@/components/MkKeyValue.vue'; +import { i18n } from '@/i18n'; + +defineProps<{ + twoFactorData: { + qr: string; + url: string; + }; +}>(); + +const emit = defineEmits<{ + (ev: 'ok'): void; + (ev: 'cancel'): void; + (ev: 'closed'): void; +}>(); + +const cancel = () => { + emit('cancel'); + emit('closed'); +}; + +const ok = () => { + emit('ok'); + emit('closed'); +}; +</script> + +<style lang="scss" module> +.root { + position: relative; + margin: auto; + padding: 32px; + min-width: 320px; + max-width: calc(100svw - 64px); + box-sizing: border-box; + background: var(--panel); + border-radius: var(--radius); +} + +.qr { + width: 20em; + max-width: 100%; +} +</style> diff --git a/packages/frontend/src/pages/settings/2fa.vue b/packages/frontend/src/pages/settings/2fa.vue index e6ef09668c..891934d706 100644 --- a/packages/frontend/src/pages/settings/2fa.vue +++ b/packages/frontend/src/pages/settings/2fa.vue @@ -1,216 +1,258 @@ <template> -<div> - <MkButton v-if="!twoFactorData && !$i.twoFactorEnabled" @click="register">{{ i18n.ts._2fa.registerDevice }}</MkButton> - <template v-if="$i.twoFactorEnabled"> - <p>{{ i18n.ts._2fa.alreadyRegistered }}</p> - <MkButton @click="unregister">{{ i18n.ts.unregister }}</MkButton> +<FormSection :first="first"> + <template #label>{{ i18n.ts['2fa'] }}</template> - <template v-if="supportsCredentials"> - <hr class="totp-method-sep"> - - <h2 class="heading">{{ i18n.ts.securityKey }}</h2> - <p>{{ i18n.ts._2fa.securityKeyInfo }}</p> - <div class="key-list"> - <div v-for="key in $i.securityKeysList" class="key"> - <h3>{{ key.name }}</h3> - <div class="last-used">{{ i18n.ts.lastUsed }}<MkTime :time="key.lastUsed"/></div> - <MkButton @click="unregisterKey(key)">{{ i18n.ts.unregister }}</MkButton> - </div> + <div v-if="$i" class="_gaps_s"> + <MkFolder> + <template #icon><i class="ti ti-shield-lock"></i></template> + <template #label>{{ i18n.ts.totp }}</template> + <template #caption>{{ i18n.ts.totpDescription }}</template> + <div v-if="$i.twoFactorEnabled" class="_gaps_s"> + <div v-text="i18n.ts._2fa.alreadyRegistered"/> + <template v-if="$i.securityKeysList.length > 0"> + <MkButton @click="renewTOTP">{{ i18n.ts._2fa.renewTOTP }}</MkButton> + <MkInfo>{{ i18n.ts._2fa.whyTOTPOnlyRenew }}</MkInfo> + </template> + <MkButton v-else @click="unregisterTOTP">{{ i18n.ts.unregister }}</MkButton> </div> - <MkSwitch v-if="$i.securityKeysList.length > 0" v-model="usePasswordLessLogin" @update:model-value="updatePasswordLessLogin">{{ i18n.ts.passwordLessLogin }}</MkSwitch> + <MkButton v-else-if="!twoFactorData && !$i.twoFactorEnabled" @click="registerTOTP">{{ i18n.ts._2fa.registerTOTP }}</MkButton> + </MkFolder> + + <MkFolder> + <template #icon><i class="ti ti-key"></i></template> + <template #label>{{ i18n.ts.securityKeyAndPasskey }}</template> + <div class="_gaps_s"> + <MkInfo> + {{ i18n.ts._2fa.securityKeyInfo }}<br> + <br> + {{ i18n.ts._2fa.chromePasskeyNotSupported }} + </MkInfo> - <MkInfo v-if="registration && registration.error" warn>{{ i18n.ts.error }} {{ registration.error }}</MkInfo> - <MkButton v-if="!registration || registration.error" @click="addSecurityKey">{{ i18n.ts._2fa.registerKey }}</MkButton> + <MkInfo v-if="!supportsCredentials" warn> + {{ i18n.ts._2fa.securityKeyNotSupported }} + </MkInfo> - <ol v-if="registration && !registration.error"> - <li v-if="registration.stage >= 0"> - {{ i18n.ts.tapSecurityKey }} - <MkLoading v-if="registration.saving && registration.stage == 0" :em="true"/> - </li> - <li v-if="registration.stage >= 1"> - <MkForm :disabled="registration.stage != 1 || registration.saving"> - <MkInput v-model="keyName" :max="30"> - <template #label>{{ i18n.ts.securityKeyName }}</template> - </MkInput> - <MkButton :disabled="keyName.length == 0" @click="registerKey">{{ i18n.ts.registerSecurityKey }}</MkButton> - <MkLoading v-if="registration.saving && registration.stage == 1" :em="true"/> - </MkForm> - </li> - </ol> - </template> - </template> - <div v-if="twoFactorData && !$i.twoFactorEnabled"> - <ol style="margin: 0; padding: 0 0 0 1em;"> - <li> - <I18n :src="i18n.ts._2fa.step1" tag="span"> - <template #a> - <a href="https://authy.com/" rel="noopener" target="_blank" class="_link">Authy</a> - </template> - <template #b> - <a href="https://support.google.com/accounts/answer/1066447" rel="noopener" target="_blank" class="_link">Google Authenticator</a> - </template> - </I18n> - </li> - <li>{{ i18n.ts._2fa.step2 }}<br><img :src="twoFactorData.qr"><p>{{ $ts._2fa.step2Url }}<br>{{ twoFactorData.url }}</p></li> - <li> - {{ i18n.ts._2fa.step3 }}<br> - <MkInput v-model="token" type="text" pattern="^[0-9]{6}$" autocomplete="off" :spellcheck="false"><template #label>{{ i18n.ts.token }}</template></MkInput> - <MkButton primary @click="submit">{{ i18n.ts.done }}</MkButton> - </li> - </ol> - <MkInfo>{{ i18n.ts._2fa.step4 }}</MkInfo> + <MkInfo v-else-if="supportsCredentials && !$i.twoFactorEnabled" warn> + {{ i18n.ts._2fa.registerTOTPBeforeKey }} + </MkInfo> + + <template v-else> + <MkButton primary @click="addSecurityKey">{{ i18n.ts._2fa.registerSecurityKey }}</MkButton> + <MkFolder v-for="key in $i.securityKeysList" :key="key.id"> + <template #label>{{ key.name }}</template> + <template #suffix><I18n :src="i18n.ts.lastUsedAt"><template #t><MkTime :time="key.lastUsed"/></template></I18n></template> + <div class="_buttons"> + <MkButton @click="renameKey(key)"><i class="ti ti-forms"></i> {{ i18n.ts.rename }}</MkButton> + <MkButton danger @click="unregisterKey(key)"><i class="ti ti-trash"></i> {{ i18n.ts.unregister }}</MkButton> + </div> + </MkFolder> + </template> + </div> + </MkFolder> + + <MkSwitch :disabled="!$i.twoFactorEnabled || $i.securityKeysList.length === 0" :model-value="usePasswordLessLogin" @update:model-value="v => updatePasswordLessLogin(v)"> + <template #label>{{ i18n.ts.passwordLessLogin }}</template> + <template #caption>{{ i18n.ts.passwordLessLoginDescription }}</template> + </MkSwitch> </div> -</div> +</FormSection> </template> <script lang="ts" setup> -import { ref } from 'vue'; +import { ref, defineAsyncComponent } from 'vue'; import { hostname } from '@/config'; import { byteify, hexify, stringify } from '@/scripts/2fa'; import MkButton from '@/components/MkButton.vue'; import MkInfo from '@/components/MkInfo.vue'; -import MkInput from '@/components/MkInput.vue'; import MkSwitch from '@/components/MkSwitch.vue'; +import FormSection from '@/components/form/section.vue'; +import MkFolder from '@/components/MkFolder.vue'; import * as os from '@/os'; import { $i } from '@/account'; import { i18n } from '@/i18n'; +// メモ: 各エンドポイントはmeUpdatedを発行するため、refreshAccountは不要 + +withDefaults(defineProps<{ + first?: boolean; +}>(), { + first: false, +}); + const twoFactorData = ref<any>(null); const supportsCredentials = ref(!!navigator.credentials); -const usePasswordLessLogin = ref($i!.usePasswordLessLogin); -const registration = ref<any>(null); -const keyName = ref(''); -const token = ref(null); +const usePasswordLessLogin = $computed(() => $i!.usePasswordLessLogin); -function register() { - os.inputText({ - title: i18n.ts.password, +async function registerTOTP() { + const password = await os.inputText({ + title: i18n.ts._2fa.registerTOTP, + text: i18n.ts._2fa.passwordToTOTP, type: 'password', - }).then(({ canceled, result: password }) => { - if (canceled) return; - os.api('i/2fa/register', { - password: password, - }).then(data => { - twoFactorData.value = data; - }); + autocomplete: 'current-password', + }); + if (password.canceled) return; + + const twoFactorData = await os.apiWithDialog('i/2fa/register', { + password: password.result, + }); + + const qrdialog = await new Promise<boolean>(res => { + os.popup(defineAsyncComponent(() => import('./2fa.qrdialog.vue')), { + twoFactorData, + }, { + 'ok': () => res(true), + 'cancel': () => res(false), + }, 'closed'); + }); + if (!qrdialog) return; + + const token = await os.inputNumber({ + title: i18n.ts._2fa.step3Title, + text: i18n.ts._2fa.step3, + autocomplete: 'one-time-code', + }); + if (token.canceled) return; + + await os.apiWithDialog('i/2fa/done', { + token: token.result.toString(), + }); + + await os.alert({ + type: 'success', + text: i18n.ts._2fa.step4, }); } -function unregister() { +function unregisterTOTP() { os.inputText({ title: i18n.ts.password, type: 'password', + autocomplete: 'current-password', }).then(({ canceled, result: password }) => { if (canceled) return; - os.api('i/2fa/unregister', { + os.apiWithDialog('i/2fa/unregister', { password: password, - }).then(() => { - usePasswordLessLogin.value = false; - updatePasswordLessLogin(); - }).then(() => { - os.success(); - $i!.twoFactorEnabled = false; + }).catch(error => { + os.alert({ + type: 'error', + text: error, + }); }); }); } -function submit() { - os.api('i/2fa/done', { - token: token.value, - }).then(() => { - os.success(); - $i!.twoFactorEnabled = true; - }).catch(err => { - os.alert({ - type: 'error', - text: err, - }); +function renewTOTP() { + os.confirm({ + type: 'question', + title: i18n.ts._2fa.renewTOTP, + text: i18n.ts._2fa.renewTOTPConfirm, + okText: i18n.ts._2fa.renewTOTPOk, + cancelText: i18n.ts._2fa.renewTOTPCancel, + }).then(({ canceled }) => { + if (canceled) return; + registerTOTP(); }); } -function registerKey() { - registration.value.saving = true; - os.api('i/2fa/key-done', { - password: registration.value.password, - name: keyName.value, - challengeId: registration.value.challengeId, - // we convert each 16 bits to a string to serialise - clientDataJSON: stringify(registration.value.credential.response.clientDataJSON), - attestationObject: hexify(registration.value.credential.response.attestationObject), - }).then(key => { - registration.value = null; - key.lastUsed = new Date(); - os.success(); +async function unregisterKey(key) { + const confirm = await os.confirm({ + type: 'question', + title: i18n.ts._2fa.removeKey, + text: i18n.t('_2fa.removeKeyConfirm', { name: key.name }), }); -} + if (confirm.canceled) return; -function unregisterKey(key) { - os.inputText({ + const password = await os.inputText({ title: i18n.ts.password, type: 'password', - }).then(({ canceled, result: password }) => { - if (canceled) return; - return os.api('i/2fa/remove-key', { - password, - credentialId: key.id, - }).then(() => { - usePasswordLessLogin.value = false; - updatePasswordLessLogin(); - }).then(() => { - os.success(); - }); + autocomplete: 'current-password', + }); + if (password.canceled) return; + + await os.apiWithDialog('i/2fa/remove-key', { + password: password.result, + credentialId: key.id, }); + os.success(); } -function addSecurityKey() { - os.inputText({ +async function renameKey(key) { + const name = await os.inputText({ + title: i18n.ts.rename, + default: key.name, + type: 'text', + minLength: 1, + maxLength: 30, + }); + if (name.canceled) return; + + await os.apiWithDialog('i/2fa/update-key', { + name: name.result, + credentialId: key.id, + }); +} + +async function addSecurityKey() { + const password = await os.inputText({ title: i18n.ts.password, type: 'password', - }).then(({ canceled, result: password }) => { - if (canceled) return; - os.api('i/2fa/register-key', { - password, - }).then(reg => { - registration.value = { - password, - challengeId: reg!.challengeId, - stage: 0, - publicKeyOptions: { - challenge: byteify(reg!.challenge, 'base64'), - rp: { - id: hostname, - name: 'Misskey', - }, - user: { - id: byteify($i!.id, 'ascii'), - name: $i!.username, - displayName: $i!.name, - }, - pubKeyCredParams: [{ alg: -7, type: 'public-key' }], - timeout: 60000, - attestation: 'direct', - }, - saving: true, - }; - return navigator.credentials.create({ - publicKey: registration.value.publicKeyOptions, - }); - }).then(credential => { - registration.value.credential = credential; - registration.value.saving = false; - registration.value.stage = 1; - }).catch(err => { - console.warn('Error while registering?', err); - registration.value.error = err.message; - registration.value.stage = -1; - }); + autocomplete: 'current-password', + }); + if (password.canceled) return; + + const challenge: any = await os.apiWithDialog('i/2fa/register-key', { + password: password.result, + }); + + const name = await os.inputText({ + title: i18n.ts._2fa.registerSecurityKey, + text: i18n.ts._2fa.securityKeyName, + type: 'text', + minLength: 1, + maxLength: 30, + }); + if (name.canceled) return; + + const webAuthnCreation = navigator.credentials.create({ + publicKey: { + challenge: byteify(challenge.challenge, 'base64'), + rp: { + id: hostname, + name: 'Misskey', + }, + user: { + id: byteify($i!.id, 'ascii'), + name: $i!.username, + displayName: $i!.name, + }, + pubKeyCredParams: [{ alg: -7, type: 'public-key' }], + timeout: 60000, + attestation: 'direct', + }, + }) as Promise<PublicKeyCredential & { response: AuthenticatorAttestationResponse; } | null>; + + const credential = await os.promiseDialog( + webAuthnCreation, + null, + () => {}, // ユーザーのキャンセルはrejectなのでエラーダイアログを出さない + i18n.ts._2fa.tapSecurityKey, + ); + if (!credential) return; + + await os.apiWithDialog('i/2fa/key-done', { + password: password.result, + name: name.result, + challengeId: challenge.challengeId, + // we convert each 16 bits to a string to serialise + clientDataJSON: stringify(credential.response.clientDataJSON), + attestationObject: hexify(credential.response.attestationObject), }); } -async function updatePasswordLessLogin() { - await os.api('i/2fa/password-less', { - value: !!usePasswordLessLogin.value, +async function updatePasswordLessLogin(value: boolean) { + await os.apiWithDialog('i/2fa/password-less', { + value, }); } </script> diff --git a/packages/frontend/src/pages/settings/deck.vue b/packages/frontend/src/pages/settings/deck.vue index 3f1f2820f0..bc0179b3aa 100644 --- a/packages/frontend/src/pages/settings/deck.vue +++ b/packages/frontend/src/pages/settings/deck.vue @@ -13,14 +13,10 @@ </template> <script lang="ts" setup> -import { computed, watch } from 'vue'; +import { computed } from 'vue'; import MkSwitch from '@/components/MkSwitch.vue'; -import FormLink from '@/components/form/link.vue'; import MkRadios from '@/components/MkRadios.vue'; -import MkInput from '@/components/MkInput.vue'; import { deckStore } from '@/ui/deck/deck-store'; -import * as os from '@/os'; -import { unisonReload } from '@/scripts/unison-reload'; import { i18n } from '@/i18n'; import { definePageMetadata } from '@/scripts/page-metadata'; diff --git a/packages/frontend/src/pages/settings/drive.vue b/packages/frontend/src/pages/settings/drive.vue index 4099a3c11d..a23bdfe69e 100644 --- a/packages/frontend/src/pages/settings/drive.vue +++ b/packages/frontend/src/pages/settings/drive.vue @@ -30,7 +30,7 @@ <FormLink @click="chooseUploadFolder()"> {{ i18n.ts.uploadFolder }} <template #suffix>{{ uploadFolder ? uploadFolder.name : '-' }}</template> - <template #suffixIcon><i class="fas fa-folder-open"></i></template> + <template #suffixIcon><i class="ti ti-folder"></i></template> </FormLink> <MkSwitch v-model="keepOriginalUploading"> <template #label>{{ i18n.ts.keepOriginalUploading }}</template> diff --git a/packages/frontend/src/pages/settings/email.vue b/packages/frontend/src/pages/settings/email.vue index 57b07b1cc1..1734dcfe42 100644 --- a/packages/frontend/src/pages/settings/email.vue +++ b/packages/frontend/src/pages/settings/email.vue @@ -34,9 +34,6 @@ <MkSwitch v-model="emailNotification_receiveFollowRequest"> {{ i18n.ts._notification._types.receiveFollowRequest }} </MkSwitch> - <MkSwitch v-model="emailNotification_groupInvited"> - {{ i18n.ts._notification._types.groupInvited }} - </MkSwitch> </div> </FormSection> </div> @@ -78,7 +75,6 @@ const emailNotification_reply = ref($i!.emailNotificationTypes.includes('reply') const emailNotification_quote = ref($i!.emailNotificationTypes.includes('quote')); const emailNotification_follow = ref($i!.emailNotificationTypes.includes('follow')); const emailNotification_receiveFollowRequest = ref($i!.emailNotificationTypes.includes('receiveFollowRequest')); -const emailNotification_groupInvited = ref($i!.emailNotificationTypes.includes('groupInvited')); const saveNotificationSettings = () => { os.api('i/update', { @@ -88,12 +84,11 @@ const saveNotificationSettings = () => { ...[emailNotification_quote.value ? 'quote' : null], ...[emailNotification_follow.value ? 'follow' : null], ...[emailNotification_receiveFollowRequest.value ? 'receiveFollowRequest' : null], - ...[emailNotification_groupInvited.value ? 'groupInvited' : null], ].filter(x => x != null), }); }; -watch([emailNotification_mention, emailNotification_reply, emailNotification_quote, emailNotification_follow, emailNotification_receiveFollowRequest, emailNotification_groupInvited], () => { +watch([emailNotification_mention, emailNotification_reply, emailNotification_quote, emailNotification_follow, emailNotification_receiveFollowRequest], () => { saveNotificationSettings(); }); diff --git a/packages/frontend/src/pages/settings/index.vue b/packages/frontend/src/pages/settings/index.vue index 8631f3e341..e6752460a8 100644 --- a/packages/frontend/src/pages/settings/index.vue +++ b/packages/frontend/src/pages/settings/index.vue @@ -22,16 +22,15 @@ </template> <script setup lang="ts"> -import { computed, defineAsyncComponent, inject, nextTick, onActivated, onMounted, onUnmounted, provide, ref, shallowRef, watch } from 'vue'; +import { computed, onActivated, onMounted, onUnmounted, ref, shallowRef, watch } from 'vue'; import { i18n } from '@/i18n'; import MkInfo from '@/components/MkInfo.vue'; import MkSuperMenu from '@/components/MkSuperMenu.vue'; -import { scroll } from '@/scripts/scroll'; import { signout, $i } from '@/account'; import { unisonReload } from '@/scripts/unison-reload'; import { instance } from '@/instance'; import { useRouter } from '@/router'; -import { definePageMetadata, provideMetadataReceiver, setPageMetadata } from '@/scripts/page-metadata'; +import { definePageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata'; import * as os from '@/os'; import { miLocalStorage } from '@/local-storage'; import { fetchCustomEmojis } from '@/custom-emojis'; diff --git a/packages/frontend/src/pages/settings/mute-block.vue b/packages/frontend/src/pages/settings/mute-block.vue index 48579aa069..a08308f0ce 100644 --- a/packages/frontend/src/pages/settings/mute-block.vue +++ b/packages/frontend/src/pages/settings/mute-block.vue @@ -34,7 +34,6 @@ import MkTab from '@/components/MkTab.vue'; import FormInfo from '@/components/MkInfo.vue'; import FormLink from '@/components/form/link.vue'; import { userPage } from '@/filters/user'; -import * as os from '@/os'; import { i18n } from '@/i18n'; import { definePageMetadata } from '@/scripts/page-metadata'; diff --git a/packages/frontend/src/pages/settings/notifications.vue b/packages/frontend/src/pages/settings/notifications.vue index db32ee862c..f64202fff2 100644 --- a/packages/frontend/src/pages/settings/notifications.vue +++ b/packages/frontend/src/pages/settings/notifications.vue @@ -5,7 +5,6 @@ <div class="_gaps_m"> <FormLink @click="readAllNotifications">{{ i18n.ts.markAsReadAllNotifications }}</FormLink> <FormLink @click="readAllUnreadNotes">{{ i18n.ts.markAsReadAllUnreadNotes }}</FormLink> - <FormLink @click="readAllMessagingMessages">{{ i18n.ts.markAsReadAllTalkMessages }}</FormLink> </div> </FormSection> <FormSection> @@ -29,7 +28,6 @@ <script lang="ts" setup> import { defineAsyncComponent } from 'vue'; import { notificationTypes } from 'misskey-js'; -import MkButton from '@/components/MkButton.vue'; import FormLink from '@/components/form/link.vue'; import FormSection from '@/components/form/section.vue'; import MkSwitch from '@/components/MkSwitch.vue'; @@ -47,10 +45,6 @@ async function readAllUnreadNotes() { await os.api('i/read-all-unread-notes'); } -async function readAllMessagingMessages() { - await os.api('i/read-all-messaging-messages'); -} - async function readAllNotifications() { await os.api('notifications/mark-all-as-read'); } diff --git a/packages/frontend/src/pages/settings/profile.vue b/packages/frontend/src/pages/settings/profile.vue index 3647e90ce7..41563c441f 100644 --- a/packages/frontend/src/pages/settings/profile.vue +++ b/packages/frontend/src/pages/settings/profile.vue @@ -78,7 +78,6 @@ import MkSelect from '@/components/MkSelect.vue'; import FormSplit from '@/components/form/split.vue'; import MkFolder from '@/components/MkFolder.vue'; import FormSlot from '@/components/form/slot.vue'; -import { host } from '@/config'; import { selectFile } from '@/scripts/select-file'; import * as os from '@/os'; import { i18n } from '@/i18n'; @@ -125,11 +124,11 @@ function saveFields() { function save() { os.apiWithDialog('i/update', { - name: profile.name || null, - description: profile.description || null, - location: profile.location || null, - birthday: profile.birthday || null, - lang: profile.lang || null, + name: profile.name ?? null, + description: profile.description ?? null, + location: profile.location ?? null, + birthday: profile.birthday ?? null, + lang: profile.lang ?? null, isBot: !!profile.isBot, isCat: !!profile.isCat, showTimelineReplies: !!profile.showTimelineReplies, diff --git a/packages/frontend/src/pages/settings/reaction.vue b/packages/frontend/src/pages/settings/reaction.vue index c8b47b8299..ed913731d3 100644 --- a/packages/frontend/src/pages/settings/reaction.vue +++ b/packages/frontend/src/pages/settings/reaction.vue @@ -57,7 +57,6 @@ <script lang="ts" setup> import { defineAsyncComponent, watch } from 'vue'; import Sortable from 'vuedraggable'; -import MkInput from '@/components/MkInput.vue'; import MkRadios from '@/components/MkRadios.vue'; import FromSlot from '@/components/form/slot.vue'; import MkButton from '@/components/MkButton.vue'; diff --git a/packages/frontend/src/pages/settings/security.vue b/packages/frontend/src/pages/settings/security.vue index b09c4ffd2f..0cc2df09c5 100644 --- a/packages/frontend/src/pages/settings/security.vue +++ b/packages/frontend/src/pages/settings/security.vue @@ -5,11 +5,8 @@ <MkButton primary @click="change()">{{ i18n.ts.changePassword }}</MkButton> </FormSection> - <FormSection> - <template #label>{{ i18n.ts.twoStepAuthentication }}</template> - <X2fa/> - </FormSection> - + <X2fa/> + <FormSection> <template #label>{{ i18n.ts.signinHistory }}</template> <MkPagination :pagination="pagination" disable-auto-load> @@ -56,18 +53,21 @@ async function change() { const { canceled: canceled1, result: currentPassword } = await os.inputText({ title: i18n.ts.currentPassword, type: 'password', + autocomplete: 'current-password', }); if (canceled1) return; const { canceled: canceled2, result: newPassword } = await os.inputText({ title: i18n.ts.newPassword, type: 'password', + autocomplete: 'new-password', }); if (canceled2) return; const { canceled: canceled3, result: newPassword2 } = await os.inputText({ title: i18n.ts.newPasswordRetype, type: 'password', + autocomplete: 'new-password', }); if (canceled3) return; @@ -109,7 +109,7 @@ definePageMetadata({ <style lang="scss" scoped> .timnmucd { - padding: 16px; + padding: 12px; &:first-child { border-top-left-radius: 6px; diff --git a/packages/frontend/src/pages/settings/sounds.vue b/packages/frontend/src/pages/settings/sounds.vue index f9e301aef9..006a2377d4 100644 --- a/packages/frontend/src/pages/settings/sounds.vue +++ b/packages/frontend/src/pages/settings/sounds.vue @@ -25,12 +25,9 @@ import { computed, ref } from 'vue'; import XSound from './sounds.sound.vue'; import MkRange from '@/components/MkRange.vue'; import MkButton from '@/components/MkButton.vue'; -import FormLink from '@/components/form/link.vue'; import FormSection from '@/components/form/section.vue'; import MkFolder from '@/components/MkFolder.vue'; -import * as os from '@/os'; import { ColdDeviceStorage } from '@/store'; -import { playFile } from '@/scripts/sound'; import { i18n } from '@/i18n'; import { definePageMetadata } from '@/scripts/page-metadata'; @@ -43,7 +40,7 @@ const masterVolume = computed({ }, }); -const volumeIcon = computed(() => masterVolume.value === 0 ? 'fas fa-volume-mute' : 'fas fa-volume-up'); +const volumeIcon = computed(() => masterVolume.value === 0 ? 'ti ti-volume-3' : 'ti ti-volume'); const sounds = ref({ note: ColdDeviceStorage.get('sound_note'), diff --git a/packages/frontend/src/pages/settings/statusbar.statusbar.vue b/packages/frontend/src/pages/settings/statusbar.statusbar.vue index eee65e0e95..81ff873e9e 100644 --- a/packages/frontend/src/pages/settings/statusbar.statusbar.vue +++ b/packages/frontend/src/pages/settings/statusbar.statusbar.vue @@ -81,14 +81,13 @@ </template> <script lang="ts" setup> -import { computed, reactive, ref, watch } from 'vue'; +import { reactive, watch } from 'vue'; import MkSelect from '@/components/MkSelect.vue'; import MkInput from '@/components/MkInput.vue'; import MkSwitch from '@/components/MkSwitch.vue'; import MkRadios from '@/components/MkRadios.vue'; import MkButton from '@/components/MkButton.vue'; import MkRange from '@/components/MkRange.vue'; -import * as os from '@/os'; import { defaultStore } from '@/store'; import { i18n } from '@/i18n'; import { deepClone } from '@/scripts/clone'; diff --git a/packages/frontend/src/pages/settings/statusbar.vue b/packages/frontend/src/pages/settings/statusbar.vue index ab081964c2..cb46858c5a 100644 --- a/packages/frontend/src/pages/settings/statusbar.vue +++ b/packages/frontend/src/pages/settings/statusbar.vue @@ -10,15 +10,13 @@ </template> <script lang="ts" setup> -import { computed, onMounted, ref, watch } from 'vue'; +import { onMounted } from 'vue'; import { v4 as uuid } from 'uuid'; import XStatusbar from './statusbar.statusbar.vue'; -import MkRadios from '@/components/MkRadios.vue'; import MkFolder from '@/components/MkFolder.vue'; import MkButton from '@/components/MkButton.vue'; import * as os from '@/os'; import { defaultStore } from '@/store'; -import { unisonReload } from '@/scripts/unison-reload'; import { i18n } from '@/i18n'; import { definePageMetadata } from '@/scripts/page-metadata'; diff --git a/packages/frontend/src/pages/settings/webhook.vue b/packages/frontend/src/pages/settings/webhook.vue index 01c31688cc..e10f65b0af 100644 --- a/packages/frontend/src/pages/settings/webhook.vue +++ b/packages/frontend/src/pages/settings/webhook.vue @@ -30,9 +30,6 @@ import { } from 'vue'; import MkPagination from '@/components/MkPagination.vue'; import FormSection from '@/components/form/section.vue'; import FormLink from '@/components/form/link.vue'; -import { userPage } from '@/filters/user'; -import * as os from '@/os'; -import { i18n } from '@/i18n'; import { definePageMetadata } from '@/scripts/page-metadata'; const pagination = { diff --git a/packages/frontend/src/pages/tag.vue b/packages/frontend/src/pages/tag.vue index 5d6d01d2ae..511052c424 100644 --- a/packages/frontend/src/pages/tag.vue +++ b/packages/frontend/src/pages/tag.vue @@ -2,14 +2,14 @@ <MkStickyContainer> <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template> <MkSpacer :content-max="800"> - <XNotes class="" :pagination="pagination"/> + <MkNotes class="" :pagination="pagination"/> </MkSpacer> </MkStickyContainer> </template> <script lang="ts" setup> import { computed } from 'vue'; -import XNotes from '@/components/MkNotes.vue'; +import MkNotes from '@/components/MkNotes.vue'; import { definePageMetadata } from '@/scripts/page-metadata'; const props = defineProps<{ diff --git a/packages/frontend/src/pages/timeline.vue b/packages/frontend/src/pages/timeline.vue index a071361150..d982a76d03 100644 --- a/packages/frontend/src/pages/timeline.vue +++ b/packages/frontend/src/pages/timeline.vue @@ -8,7 +8,7 @@ <div v-if="queue > 0" :class="$style.new"><button class="_buttonPrimary" @click="top()">{{ i18n.ts.newNoteRecived }}</button></div> <div :class="$style.tl"> - <XTimeline + <MkTimeline ref="tlComponent" :key="src" :src="src" @@ -23,7 +23,8 @@ <script lang="ts" setup> import { defineAsyncComponent, computed, watch, provide } from 'vue'; -import XTimeline from '@/components/MkTimeline.vue'; +import type { Tab } from '@/components/global/MkPageHeader.tabs.vue'; +import MkTimeline from '@/components/MkTimeline.vue'; import MkPostForm from '@/components/MkPostForm.vue'; import { scroll } from '@/scripts/scroll'; import * as os from '@/os'; @@ -32,7 +33,6 @@ import { i18n } from '@/i18n'; import { instance } from '@/instance'; import { $i } from '@/account'; import { definePageMetadata } from '@/scripts/page-metadata'; -import type { Tab } from '@/components/global/MkPageHeader.tabs.vue'; provide('shouldOmitHeaderTitle', true); @@ -44,7 +44,7 @@ const keymap = { 't': focus, }; -const tlComponent = $shallowRef<InstanceType<typeof XTimeline>>(); +const tlComponent = $shallowRef<InstanceType<typeof MkTimeline>>(); const rootEl = $shallowRef<HTMLElement>(); let queue = $ref(0); @@ -83,7 +83,9 @@ async function chooseAntenna(ev: MouseEvent): Promise<void> { } async function chooseChannel(ev: MouseEvent): Promise<void> { - const channels = await os.api('channels/followed'); + const channels = await os.api('channels/followed', { + limit: 100, + }); const items = channels.map(channel => ({ type: 'link' as const, text: channel.name, diff --git a/packages/frontend/src/pages/user-info.vue b/packages/frontend/src/pages/user-info.vue index 7ba8a3d16b..13a06286f6 100644 --- a/packages/frontend/src/pages/user-info.vue +++ b/packages/frontend/src/pages/user-info.vue @@ -112,7 +112,7 @@ <MkButton v-if="user.host == null && iAmModerator" primary rounded @click="assignRole"><i class="ti ti-plus"></i> {{ i18n.ts.assign }}</MkButton> <div v-for="role in info.roles" :key="role.id" :class="$style.roleItem"> - <MkRolePreview :class="$style.role" :role="role"/> + <MkRolePreview :class="$style.role" :role="role" :for-moderation="true"/> <button v-if="role.target === 'manual'" class="_button" :class="$style.roleUnassign" @click="unassignRole(role, $event)"><i class="ti ti-x"></i></button> <button v-else class="_button" :class="$style.roleUnassign" disabled><i class="ti ti-ban"></i></button> </div> @@ -181,8 +181,6 @@ import MkSwitch from '@/components/MkSwitch.vue'; import FormLink from '@/components/form/link.vue'; import FormSection from '@/components/form/section.vue'; import MkButton from '@/components/MkButton.vue'; -import MkInput from '@/components/MkInput.vue'; -import FormSplit from '@/components/form/split.vue'; import MkFolder from '@/components/MkFolder.vue'; import MkKeyValue from '@/components/MkKeyValue.vue'; import MkSelect from '@/components/MkSelect.vue'; @@ -190,14 +188,11 @@ import FormSuspense from '@/components/form/suspense.vue'; import MkFileListForAdmin from '@/components/MkFileListForAdmin.vue'; import MkInfo from '@/components/MkInfo.vue'; import * as os from '@/os'; -import number from '@/filters/number'; -import bytes from '@/filters/bytes'; import { url } from '@/config'; import { userPage, acct } from '@/filters/user'; import { definePageMetadata } from '@/scripts/page-metadata'; import { i18n } from '@/i18n'; import { iAmAdmin, iAmModerator } from '@/account'; -import { instance } from '@/instance'; import MkRolePreview from '@/components/MkRolePreview.vue'; const props = withDefaults(defineProps<{ diff --git a/packages/frontend/src/pages/user-list-timeline.vue b/packages/frontend/src/pages/user-list-timeline.vue index 3f47edfd44..acf7ea9b2c 100644 --- a/packages/frontend/src/pages/user-list-timeline.vue +++ b/packages/frontend/src/pages/user-list-timeline.vue @@ -4,7 +4,7 @@ <div ref="rootEl" class="eqqrhokj"> <div v-if="queue > 0" class="new"><button class="_buttonPrimary" @click="top()">{{ i18n.ts.newNoteRecived }}</button></div> <div class="tl"> - <XTimeline + <MkTimeline ref="tlEl" :key="listId" class="tl" src="list" @@ -18,8 +18,8 @@ </template> <script lang="ts" setup> -import { computed, watch, inject } from 'vue'; -import XTimeline from '@/components/MkTimeline.vue'; +import { computed, watch } from 'vue'; +import MkTimeline from '@/components/MkTimeline.vue'; import { scroll } from '@/scripts/scroll'; import * as os from '@/os'; import { useRouter } from '@/router'; @@ -34,7 +34,7 @@ const props = defineProps<{ let list = $ref(null); let queue = $ref(0); -let tlEl = $shallowRef<InstanceType<typeof XTimeline>>(); +let tlEl = $shallowRef<InstanceType<typeof MkTimeline>>(); let rootEl = $shallowRef<HTMLElement>(); watch(() => props.listId, async () => { @@ -65,7 +65,7 @@ async function timetravel() { } const headerActions = $computed(() => list ? [{ - icon: 'fas fa-calendar-alt', + icon: 'ti ti-calendar-time', text: i18n.ts.jumpToSpecifiedDate, handler: timetravel, }, { diff --git a/packages/frontend/src/pages/user-tag.vue b/packages/frontend/src/pages/user-tag.vue new file mode 100644 index 0000000000..fac7593e9c --- /dev/null +++ b/packages/frontend/src/pages/user-tag.vue @@ -0,0 +1,38 @@ +<template> +<MkStickyContainer> + <template #header><MkPageHeader/></template> + + <MkSpacer :content-max="1200"> + <div class="_gaps_s"> + <MkUserList :pagination="tagUsers"/> + </div> + </MkSpacer> +</MkStickyContainer> +</template> + +<script lang="ts" setup> +import { computed, watch } from 'vue'; +import * as os from '@/os'; +import MkUserList from '@/components/MkUserList.vue'; +import { definePageMetadata } from '@/scripts/page-metadata'; + +const props = defineProps<{ + tag: string; +}>(); + +const tagUsers = $computed(() => ({ + endpoint: 'hashtags/users' as const, + limit: 30, + params: { + tag: props.tag, + origin: 'combined', + sort: '+follower', + }, +})); + +definePageMetadata(computed(() => ({ + title: props.tag, + icon: 'ti ti-user-search', +}))); +</script> + diff --git a/packages/frontend/src/pages/user/achievements.vue b/packages/frontend/src/pages/user/achievements.vue index 615613b7fc..1b3a6e24b3 100644 --- a/packages/frontend/src/pages/user/achievements.vue +++ b/packages/frontend/src/pages/user/achievements.vue @@ -5,10 +5,9 @@ </template> <script lang="ts" setup> -import { onActivated, onDeactivated, onMounted, onUnmounted, ref } from 'vue'; +import { onActivated, onDeactivated, onMounted, onUnmounted } from 'vue'; import * as misskey from 'misskey-js'; import MkAchievements from '@/components/MkAchievements.vue'; -import { i18n } from '@/i18n'; import { claimAchievement } from '@/scripts/achievements'; import { $i } from '@/account'; diff --git a/packages/frontend/src/pages/user/activity.following.vue b/packages/frontend/src/pages/user/activity.following.vue index 500995e392..54360024f3 100644 --- a/packages/frontend/src/pages/user/activity.following.vue +++ b/packages/frontend/src/pages/user/activity.following.vue @@ -9,17 +9,14 @@ </template> <script lang="ts" setup> -import { markRaw, version as vueVersion, onMounted, onBeforeUnmount, nextTick } from 'vue'; +import { onMounted } from 'vue'; import { Chart, ChartDataset } from 'chart.js'; -import tinycolor from 'tinycolor2'; import * as misskey from 'misskey-js'; import gradient from 'chartjs-plugin-gradient'; -import { satisfies } from 'compare-versions'; import * as os from '@/os'; import { defaultStore } from '@/store'; import { useChartTooltip } from '@/scripts/use-chart-tooltip'; import { chartVLine } from '@/scripts/chart-vline'; -import { alpha } from '@/scripts/color'; import { initChart } from '@/scripts/init-chart'; import { chartLegend } from '@/scripts/chart-legend'; import MkChartLegend from '@/components/MkChartLegend.vue'; diff --git a/packages/frontend/src/pages/user/activity.heatmap.vue b/packages/frontend/src/pages/user/activity.heatmap.vue index 202201afb5..2dcb754c9b 100644 --- a/packages/frontend/src/pages/user/activity.heatmap.vue +++ b/packages/frontend/src/pages/user/activity.heatmap.vue @@ -8,14 +8,12 @@ </template> <script lang="ts" setup> -import { markRaw, version as vueVersion, onMounted, onBeforeUnmount, nextTick, watch } from 'vue'; +import { onMounted, nextTick, watch } from 'vue'; import { Chart } from 'chart.js'; -import tinycolor from 'tinycolor2'; import * as misskey from 'misskey-js'; import * as os from '@/os'; import { defaultStore } from '@/store'; import { useChartTooltip } from '@/scripts/use-chart-tooltip'; -import { chartVLine } from '@/scripts/chart-vline'; import { alpha } from '@/scripts/color'; import { initChart } from '@/scripts/init-chart'; diff --git a/packages/frontend/src/pages/user/activity.notes.vue b/packages/frontend/src/pages/user/activity.notes.vue index 8763997f8e..7dd02ad6d4 100644 --- a/packages/frontend/src/pages/user/activity.notes.vue +++ b/packages/frontend/src/pages/user/activity.notes.vue @@ -9,17 +9,14 @@ </template> <script lang="ts" setup> -import { markRaw, version as vueVersion, onMounted, onBeforeUnmount, nextTick } from 'vue'; +import { onMounted } from 'vue'; import { Chart, ChartDataset } from 'chart.js'; -import tinycolor from 'tinycolor2'; import * as misskey from 'misskey-js'; import gradient from 'chartjs-plugin-gradient'; -import { satisfies } from 'compare-versions'; import * as os from '@/os'; import { defaultStore } from '@/store'; import { useChartTooltip } from '@/scripts/use-chart-tooltip'; import { chartVLine } from '@/scripts/chart-vline'; -import { alpha } from '@/scripts/color'; import { initChart } from '@/scripts/init-chart'; import { chartLegend } from '@/scripts/chart-legend'; import MkChartLegend from '@/components/MkChartLegend.vue'; diff --git a/packages/frontend/src/pages/user/activity.pv.vue b/packages/frontend/src/pages/user/activity.pv.vue index d23f89a31e..6a7506e388 100644 --- a/packages/frontend/src/pages/user/activity.pv.vue +++ b/packages/frontend/src/pages/user/activity.pv.vue @@ -9,16 +9,14 @@ </template> <script lang="ts" setup> -import { markRaw, version as vueVersion, onMounted, onBeforeUnmount, nextTick } from 'vue'; +import { onMounted } from 'vue'; import { Chart, ChartDataset } from 'chart.js'; -import tinycolor from 'tinycolor2'; import * as misskey from 'misskey-js'; import gradient from 'chartjs-plugin-gradient'; import * as os from '@/os'; import { defaultStore } from '@/store'; import { useChartTooltip } from '@/scripts/use-chart-tooltip'; import { chartVLine } from '@/scripts/chart-vline'; -import { alpha } from '@/scripts/color'; import { initChart } from '@/scripts/init-chart'; import { chartLegend } from '@/scripts/chart-legend'; import MkChartLegend from '@/components/MkChartLegend.vue'; diff --git a/packages/frontend/src/pages/user/activity.vue b/packages/frontend/src/pages/user/activity.vue index 6d7c7e7722..cd538ad61f 100644 --- a/packages/frontend/src/pages/user/activity.vue +++ b/packages/frontend/src/pages/user/activity.vue @@ -22,7 +22,6 @@ </template> <script lang="ts" setup> -import { computed } from 'vue'; import * as misskey from 'misskey-js'; import XHeatmap from './activity.heatmap.vue'; import XPv from './activity.pv.vue'; diff --git a/packages/frontend/src/pages/user/followers.vue b/packages/frontend/src/pages/user/followers.vue index 8859928784..20573e67e9 100644 --- a/packages/frontend/src/pages/user/followers.vue +++ b/packages/frontend/src/pages/user/followers.vue @@ -14,7 +14,7 @@ </template> <script lang="ts" setup> -import { defineAsyncComponent, computed, inject, onMounted, onUnmounted, watch } from 'vue'; +import { computed, watch } from 'vue'; import * as Acct from 'misskey-js/built/acct'; import * as misskey from 'misskey-js'; import XFollowList from './follow-list.vue'; diff --git a/packages/frontend/src/pages/user/following.vue b/packages/frontend/src/pages/user/following.vue index 51015905c6..3825f138cf 100644 --- a/packages/frontend/src/pages/user/following.vue +++ b/packages/frontend/src/pages/user/following.vue @@ -14,7 +14,7 @@ </template> <script lang="ts" setup> -import { defineAsyncComponent, computed, inject, onMounted, onUnmounted, watch } from 'vue'; +import { computed, watch } from 'vue'; import * as Acct from 'misskey-js/built/acct'; import * as misskey from 'misskey-js'; import XFollowList from './follow-list.vue'; diff --git a/packages/frontend/src/pages/user/home.vue b/packages/frontend/src/pages/user/home.vue index 6d942d4391..441b19440c 100644 --- a/packages/frontend/src/pages/user/home.vue +++ b/packages/frontend/src/pages/user/home.vue @@ -100,7 +100,7 @@ <XPhotos :key="user.id" :user="user"/> <XActivity :key="user.id" :user="user"/> </template> - <XNotes :no-gap="true" :pagination="pagination"/> + <MkNotes :class="$style.tl" :no-gap="true" :pagination="pagination"/> </div> </div> <div v-if="!narrow" class="sub _gaps" style="container-type: inline-size;"> @@ -112,28 +112,25 @@ </template> <script lang="ts" setup> -import { defineAsyncComponent, computed, inject, onMounted, onUnmounted, watch } from 'vue'; +import { defineAsyncComponent, computed, onMounted, onUnmounted } from 'vue'; import calcAge from 's-age'; import * as misskey from 'misskey-js'; import XNote from '@/components/MkNote.vue'; import MkFollowButton from '@/components/MkFollowButton.vue'; -import MkContainer from '@/components/MkContainer.vue'; -import MkFoldableSection from '@/components/MkFoldableSection.vue'; import MkRemoteCaution from '@/components/MkRemoteCaution.vue'; -import MkTab from '@/components/MkTab.vue'; import MkOmit from '@/components/MkOmit.vue'; import MkInfo from '@/components/MkInfo.vue'; import { getScrollPosition } from '@/scripts/scroll'; import { getUserMenu } from '@/scripts/get-user-menu'; import number from '@/filters/number'; -import { userPage, acct as getAcct } from '@/filters/user'; +import { userPage } from '@/filters/user'; import * as os from '@/os'; import { useRouter } from '@/router'; import { i18n } from '@/i18n'; import { $i } from '@/account'; import { dateString } from '@/filters/date'; import { confetti } from '@/scripts/confetti'; -import XNotes from '@/components/MkNotes.vue'; +import MkNotes from '@/components/MkNotes.vue'; const XPhotos = defineAsyncComponent(() => import('./index.photos.vue')); const XActivity = defineAsyncComponent(() => import('./index.activity.vue')); @@ -522,3 +519,11 @@ onUnmounted(() => { } } </style> + +<style lang="scss" module> +.tl { + background: var(--bg); + border-radius: var(--radius); + overflow: clip; +} +</style> diff --git a/packages/frontend/src/pages/user/index.timeline.vue b/packages/frontend/src/pages/user/index.timeline.vue index 34e16c707d..d8fc253910 100644 --- a/packages/frontend/src/pages/user/index.timeline.vue +++ b/packages/frontend/src/pages/user/index.timeline.vue @@ -8,7 +8,7 @@ <option value="files">{{ i18n.ts.withFiles }}</option> </MkTab> </template> - <XNotes :no-gap="true" :pagination="pagination" :class="$style.tl"/> + <MkNotes :no-gap="true" :pagination="pagination" :class="$style.tl"/> </MkStickyContainer> </MkSpacer> </template> @@ -16,9 +16,8 @@ <script lang="ts" setup> import { ref, computed } from 'vue'; import * as misskey from 'misskey-js'; -import XNotes from '@/components/MkNotes.vue'; +import MkNotes from '@/components/MkNotes.vue'; import MkTab from '@/components/MkTab.vue'; -import * as os from '@/os'; import { i18n } from '@/i18n'; const props = defineProps<{ @@ -32,7 +31,7 @@ const pagination = { limit: 10, params: computed(() => ({ userId: props.user.id, - includeReplies: include.value === 'replies', + includeReplies: include.value === 'replies' || include.value === 'files', withFiles: include.value === 'files', })), }; diff --git a/packages/frontend/src/pages/user/index.vue b/packages/frontend/src/pages/user/index.vue index 29aef21859..03a226cc09 100644 --- a/packages/frontend/src/pages/user/index.vue +++ b/packages/frontend/src/pages/user/index.vue @@ -26,7 +26,6 @@ import * as Acct from 'misskey-js/built/acct'; import * as misskey from 'misskey-js'; import { acct as getAcct } from '@/filters/user'; import * as os from '@/os'; -import { useRouter } from '@/router'; import { definePageMetadata } from '@/scripts/page-metadata'; import { i18n } from '@/i18n'; import { $i } from '@/account'; diff --git a/packages/frontend/src/pages/welcome.entrance.a.vue b/packages/frontend/src/pages/welcome.entrance.a.vue index bfa54d39f2..f62a6461c5 100644 --- a/packages/frontend/src/pages/welcome.entrance.a.vue +++ b/packages/frontend/src/pages/welcome.entrance.a.vue @@ -1,18 +1,18 @@ <template> <div v-if="meta" class="rsqzvsbo"> - <div class="top"> - <MkFeaturedPhotos class="bg"/> - <XTimeline class="tl"/> - <div class="shape1"></div> - <div class="shape2"></div> - <img src="/client-assets/misskey.svg" class="misskey"/> - <div class="emojis"> - <MkEmoji :normal="true" :no-style="true" emoji="👍"/> - <MkEmoji :normal="true" :no-style="true" emoji="❤"/> - <MkEmoji :normal="true" :no-style="true" emoji="😆"/> - <MkEmoji :normal="true" :no-style="true" emoji="🎉"/> - <MkEmoji :normal="true" :no-style="true" emoji="🍮"/> - </div> + <MkFeaturedPhotos class="bg"/> + <XTimeline class="tl"/> + <div class="shape1"></div> + <div class="shape2"></div> + <img src="/client-assets/misskey.svg" class="misskey"/> + <div class="emojis"> + <MkEmoji :normal="true" :no-style="true" emoji="👍"/> + <MkEmoji :normal="true" :no-style="true" emoji="❤"/> + <MkEmoji :normal="true" :no-style="true" emoji="😆"/> + <MkEmoji :normal="true" :no-style="true" emoji="🎉"/> + <MkEmoji :normal="true" :no-style="true" emoji="🍮"/> + </div> + <div class="contents"> <div class="main"> <img :src="$instance.iconUrl || $instance.faviconUrl || '/favicon.ico'" alt="" class="icon"/> <button class="_button _acrylic menu" @click="showMenu"><i class="ti ti-dots"></i></button> @@ -26,66 +26,55 @@ <!-- eslint-disable-next-line vue/no-v-html --> <div class="desc" v-html="meta.description || i18n.ts.headlineMisskey"></div> </div> - <div class="action"> - <MkButton inline rounded gradate data-cy-signup style="margin-right: 12px;" @click="signup()">{{ i18n.ts.signup }}</MkButton> - <MkButton inline rounded data-cy-signin @click="signin()">{{ i18n.ts.login }}</MkButton> + <div class="action _gaps_s"> + <MkButton full rounded gradate data-cy-signup style="margin-right: 12px;" @click="signup()">{{ i18n.ts.joinThisServer }}</MkButton> + <MkButton full rounded @click="exploreOtherServers()">{{ i18n.ts.exploreOtherServers }}</MkButton> + <MkButton full rounded data-cy-signin @click="signin()">{{ i18n.ts.login }}</MkButton> </div> </div> </div> - <div v-if="instances" class="federation"> - <MarqueeText :duration="40"> - <MkA v-for="instance in instances" :key="instance.id" :class="$style.federationInstance" :to="`/instance-info/${instance.host}`" behavior="window"> - <!--<MkInstanceCardMini :instance="instance"/>--> - <img v-if="instance.iconUrl" class="icon" :src="instance.iconUrl" alt=""/> - <span class="name _monospace">{{ instance.host }}</span> - </MkA> - </MarqueeText> + <div v-if="instance.policies.ltlAvailable" class="tl"> + <div class="title">{{ i18n.ts.letsLookAtTimeline }}</div> + <div class="body"> + <MkTimeline src="local"/> + </div> </div> </div> + <div v-if="instances && instances.length > 0" class="federation"> + <MarqueeText :duration="40"> + <MkA v-for="instance in instances" :key="instance.id" :class="$style.federationInstance" :to="`/instance-info/${instance.host}`" behavior="window"> + <!--<MkInstanceCardMini :instance="instance"/>--> + <img v-if="instance.iconUrl" class="icon" :src="instance.iconUrl" alt=""/> + <span class="name _monospace">{{ instance.host }}</span> + </MkA> + </MarqueeText> + </div> </div> </template> <script lang="ts" setup> import { } from 'vue'; -import { toUnicode } from 'punycode/'; +import { Instance } from 'misskey-js/built/entities'; import XTimeline from './welcome.timeline.vue'; import MarqueeText from '@/components/MkMarquee.vue'; import XSigninDialog from '@/components/MkSigninDialog.vue'; import XSignupDialog from '@/components/MkSignupDialog.vue'; import MkButton from '@/components/MkButton.vue'; -import XNote from '@/components/MkNote.vue'; import MkFeaturedPhotos from '@/components/MkFeaturedPhotos.vue'; -import { host, instanceName } from '@/config'; +import MkTimeline from '@/components/MkTimeline.vue'; +import { instanceName } from '@/config'; import * as os from '@/os'; -import number from '@/filters/number'; import { i18n } from '@/i18n'; +import { instance } from '@/instance'; -let meta = $ref(); -let stats = $ref(); -let tags = $ref(); -let onlineUsersCount = $ref(); -let instances = $ref(); +let meta = $ref<Instance>(); +let instances = $ref<any[]>(); os.api('meta', { detail: true }).then(_meta => { meta = _meta; }); -os.api('stats').then(_stats => { - stats = _stats; -}); - -os.api('get-online-users-count').then(res => { - onlineUsersCount = res.count; -}); - -os.api('hashtags/list', { - sort: '+mentionedLocalUsers', - limit: 8, -}).then(_tags => { - tags = _tags; -}); - -os.api('federation/instances', { +os.apiGet('federation/instances', { sort: '+pubSub', limit: 20, }).then(_instances => { @@ -125,99 +114,103 @@ function showMenu(ev) { }, }], ev.currentTarget ?? ev.target); } + +function exploreOtherServers() { + // TODO: 言語をよしなに + window.open('https://join.misskey.page/ja-JP/instances', '_blank'); +} </script> <style lang="scss" scoped> .rsqzvsbo { - > .top { - display: flex; - text-align: center; - min-height: 100vh; - box-sizing: border-box; - padding: 16px; + > .bg { + position: fixed; + top: 0; + right: 0; + width: 80vw; // 100%からshapeの幅を引いている + height: 100vh; + } + + > .tl { + position: fixed; + top: 0; + bottom: 0; + right: 64px; + margin: auto; + padding: 128px 0; + width: 500px; + height: calc(100% - 256px); + overflow: hidden; + -webkit-mask-image: linear-gradient(0deg, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 128px, rgba(0,0,0,1) calc(100% - 128px), rgba(0,0,0,0) 100%); + mask-image: linear-gradient(0deg, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 128px, rgba(0,0,0,1) calc(100% - 128px), rgba(0,0,0,0) 100%); - > .bg { - position: absolute; - top: 0; - right: 0; - width: 80%; // 100%からshapeの幅を引いている - height: 100%; + @media (max-width: 1200px) { + display: none; } + } - > .tl { - position: absolute; - top: 0; - bottom: 0; - right: 64px; - margin: auto; - width: 500px; - height: calc(100% - 128px); - overflow: hidden; - -webkit-mask-image: linear-gradient(0deg, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 128px, rgba(0,0,0,1) calc(100% - 128px), rgba(0,0,0,0) 100%); - mask-image: linear-gradient(0deg, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 128px, rgba(0,0,0,1) calc(100% - 128px), rgba(0,0,0,0) 100%); + > .shape1 { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background: var(--accent); + clip-path: polygon(0% 0%, 45% 0%, 20% 100%, 0% 100%); + } + > .shape2 { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background: var(--accent); + clip-path: polygon(0% 0%, 25% 0%, 35% 100%, 0% 100%); + opacity: 0.5; + } - @media (max-width: 1200px) { - display: none; - } - } + > .misskey { + position: fixed; + top: 42px; + left: 42px; + width: 140px; - > .shape1 { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: var(--accent); - clip-path: polygon(0% 0%, 45% 0%, 20% 100%, 0% 100%); - } - > .shape2 { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: var(--accent); - clip-path: polygon(0% 0%, 25% 0%, 35% 100%, 0% 100%); - opacity: 0.5; + @media (max-width: 450px) { + width: 130px; } + } - > .misskey { - position: absolute; - top: 42px; - left: 42px; - width: 140px; + > .emojis { + position: fixed; + bottom: 32px; + left: 35px; - @media (max-width: 450px) { - width: 130px; - } + > * { + margin-right: 8px; } - > .emojis { - position: absolute; - bottom: 32px; - left: 35px; + @media (max-width: 1200px) { + display: none; + } + } - > * { - margin-right: 8px; - } + > .contents { + position: relative; + width: min(430px, calc(100% - 32px)); + margin-left: 128px; + padding: 150px 0 100px 0; - @media (max-width: 1200px) { - display: none; - } + @media (max-width: 1200px) { + margin: auto; } > .main { position: relative; - width: min(480px, 100%); - margin: auto auto auto 128px; background: var(--panel); border-radius: var(--radius); box-shadow: 0 12px 32px rgb(0 0 0 / 25%); - - @media (max-width: 1200px) { - margin: auto; - } - + text-align: center; + > .icon { width: 85px; margin-top: -47px; @@ -266,25 +259,44 @@ function showMenu(ev) { } } - > .federation { - position: absolute; - bottom: 16px; - left: 0; - right: 0; - margin: auto; - background: var(--acrylicPanel); - -webkit-backdrop-filter: var(--blur, blur(15px)); - backdrop-filter: var(--blur, blur(15px)); - border-radius: 999px; + > .tl { + position: relative; + background: var(--panel); + border-radius: var(--radius); overflow: clip; - width: 800px; - padding: 8px 0; + box-shadow: 0 12px 32px rgb(0 0 0 / 25%); + margin-top: 16px; + + > .title { + padding: 12px 16px; + border-bottom: solid 1px var(--divider); + } - @media (max-width: 900px) { - display: none; + > .body { + height: 350px; + overflow: auto; } } } + + > .federation { + position: fixed; + bottom: 16px; + left: 0; + right: 0; + margin: auto; + background: var(--acrylicPanel); + -webkit-backdrop-filter: var(--blur, blur(15px)); + backdrop-filter: var(--blur, blur(15px)); + border-radius: 999px; + overflow: clip; + width: 800px; + padding: 8px 0; + + @media (max-width: 900px) { + display: none; + } + } } </style> diff --git a/packages/frontend/src/pages/welcome.timeline.vue b/packages/frontend/src/pages/welcome.timeline.vue index ed3c5a4af7..c34d43dc1c 100644 --- a/packages/frontend/src/pages/welcome.timeline.vue +++ b/packages/frontend/src/pages/welcome.timeline.vue @@ -1,62 +1,54 @@ <template> -<div class="civpbkhh"> - <div ref="scroll" class="scrollbox" v-bind:class="{ scroll: isScrolling }"> - <div v-for="note in notes" class="note"> - <div class="content _panel"> - <div class="body"> +<div :class="$style.root"> + <div ref="scrollEl" :class="[$style.scrollbox, { [$style.scroll]: isScrolling }]"> + <div v-for="note in notes" :key="note.id" :class="$style.note"> + <div class="_panel" :class="$style.content"> + <div :class="$style.body"> <MkA v-if="note.replyId" class="reply" :to="`/notes/${note.replyId}`"><i class="ti ti-arrow-back-up"></i></MkA> - <Mfm v-if="note.text" :text="note.text" :author="note.user" :i="$i"/> + <Mfm v-if="note.text" :text="note.text" :author="note.user" :i="$i" /> <MkA v-if="note.renoteId" class="rp" :to="`/notes/${note.renoteId}`">RN: ...</MkA> </div> - <div v-if="note.files.length > 0" class="richcontent"> - <MkMediaList :media-list="note.files"/> + <div v-if="note.files.length > 0" :class="$style.richcontent"> + <MkMediaList :media-list="note.files" /> </div> <div v-if="note.poll"> - <MkPoll :note="note" :readOnly="true"/> + <MkPoll :note="note" :readOnly="true" /> </div> </div> - <MkReactionsViewer ref="reactionsViewer" :note="note"/> + <MkReactionsViewer ref="reactionsViewer" :note="note" /> </div> </div> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> import MkReactionsViewer from '@/components/MkReactionsViewer.vue'; import MkMediaList from '@/components/MkMediaList.vue'; import MkPoll from '@/components/MkPoll.vue'; import * as os from '@/os'; +import { Note } from 'misskey-js/built/entities'; +import { onUpdated } from 'vue'; +import { getScrollContainer } from '@/scripts/scroll'; -export default defineComponent({ - components: { - MkReactionsViewer, - MkMediaList, - MkPoll, - }, +let notes = $ref<Note[]>([]); +let isScrolling = $ref(false); +let scrollEl = $ref<HTMLElement>(); - data() { - return { - notes: [], - isScrolling: false, - }; - }, - - created() { - os.api('notes/featured').then(notes => { - this.notes = notes; - }); - }, +os.apiGet('notes/featured').then(_notes => { + notes = _notes; +}); - updated() { - if (this.$refs.scroll.clientHeight > window.innerHeight) { - this.isScrolling = true; - } - }, +onUpdated(() => { + if (!scrollEl) return; + const container = getScrollContainer(scrollEl); + const containerHeight = container ? container.clientHeight : window.innerHeight; + if (scrollEl.offsetHeight > containerHeight) { + isScrolling = true; + } }); </script> -<style lang="scss" scoped> +<style lang="scss" module> @keyframes scroll { 0% { transform: translate3d(0, 0, 0); @@ -72,28 +64,28 @@ export default defineComponent({ } } -.civpbkhh { +.root { text-align: right; +} - > .scrollbox { - &.scroll { - animation: scroll 45s linear infinite; - } +.scrollbox { + &.scroll { + animation: scroll 45s linear infinite; + } +} - > .note { - margin: 16px 0 16px auto; +.note { + margin: 16px 0 16px auto; +} - > .content { - padding: 16px; - margin: 0 0 0 auto; - max-width: max-content; - border-radius: 16px; +.content { + padding: 16px; + margin: 0 0 0 auto; + max-width: max-content; + border-radius: 16px; +} - > .richcontent { - min-width: 250px; - } - } - } - } +.richcontent { + min-width: 250px; } </style> diff --git a/packages/frontend/src/pizzax.ts b/packages/frontend/src/pizzax.ts index 2ca89b7351..2616a8a1d5 100644 --- a/packages/frontend/src/pizzax.ts +++ b/packages/frontend/src/pizzax.ts @@ -48,8 +48,8 @@ export class Storage<T extends StateDef> { // 簡易的にキューイングして占有ロックとする private currentIdbJob: Promise<any> = Promise.resolve(); private addIdbSetJob<T>(job: () => Promise<T>) { - const promise = this.currentIdbJob.then(job, e => { - console.error('Pizzax failed to save data to idb!', e); + const promise = this.currentIdbJob.then(job, err => { + console.error('Pizzax failed to save data to idb!', err); return job(); }); this.currentIdbJob = promise; @@ -130,22 +130,22 @@ export class Storage<T extends StateDef> { await defaultStore.ready; api('i/registry/get-all', { scope: ['client', this.key] }) - .then(kvs => { - const cache: Partial<T> = {}; - for (const [k, v] of Object.entries(this.def) as [keyof T, T[keyof T]['default']][]) { - if (v.where === 'account') { - if (Object.prototype.hasOwnProperty.call(kvs, k)) { - this.reactiveState[k].value = this.state[k] = (kvs as Partial<T>)[k]; - cache[k] = (kvs as Partial<T>)[k]; - } else { - this.reactiveState[k].value = this.state[k] = v.default; + .then(kvs => { + const cache: Partial<T> = {}; + for (const [k, v] of Object.entries(this.def) as [keyof T, T[keyof T]['default']][]) { + if (v.where === 'account') { + if (Object.prototype.hasOwnProperty.call(kvs, k)) { + this.reactiveState[k].value = this.state[k] = (kvs as Partial<T>)[k]; + cache[k] = (kvs as Partial<T>)[k]; + } else { + this.reactiveState[k].value = this.state[k] = v.default; + } } } - } - return set(this.registryCacheKeyName, cache); - }) - .then(() => resolve()); + return set(this.registryCacheKeyName, cache); + }) + .then(() => resolve()); }, 1); } else { resolve(); diff --git a/packages/frontend/src/router.ts b/packages/frontend/src/router.ts index 9004262689..9521e01910 100644 --- a/packages/frontend/src/router.ts +++ b/packages/frontend/src/router.ts @@ -3,7 +3,6 @@ import { Router } from '@/nirax'; import { $i, iAmModerator } from '@/account'; import MkLoading from '@/pages/_loading_.vue'; import MkError from '@/pages/_error_.vue'; -import { ui } from '@/config'; const page = (loader: AsyncComponentLoader<any>) => defineAsyncComponent({ loader: loader, @@ -199,8 +198,11 @@ export const routes = [{ component: page(() => import('./pages/theme-editor.vue')), loginRequired: true, }, { - path: '/explore/tags/:tag', - component: page(() => import('./pages/explore.vue')), + path: '/roles/:role', + component: page(() => import('./pages/role.vue')), +}, { + path: '/user-tags/:tag', + component: page(() => import('./pages/user-tag.vue')), }, { path: '/explore', component: page(() => import('./pages/explore.vue')), @@ -421,19 +423,6 @@ export const routes = [{ component: page(() => import('./pages/achievements.vue')), loginRequired: true, }, { - name: 'messaging', - path: '/my/messaging', - component: page(() => import('./pages/messaging/index.vue')), - loginRequired: true, -}, { - path: '/my/messaging/:userAcct', - component: page(() => import('./pages/messaging/messaging-room.vue')), - loginRequired: true, -}, { - path: '/my/messaging/group/:groupId', - component: page(() => import('./pages/messaging/messaging-room.vue')), - loginRequired: true, -}, { path: '/my/drive/folder/:folder', component: page(() => import('./pages/drive.vue')), loginRequired: true, diff --git a/packages/frontend/src/scripts/aiscript/ui.ts b/packages/frontend/src/scripts/aiscript/ui.ts index fb73c0b4b7..6b8041d78e 100644 --- a/packages/frontend/src/scripts/aiscript/ui.ts +++ b/packages/frontend/src/scripts/aiscript/ui.ts @@ -1,4 +1,4 @@ -import { Interpreter, Parser, utils, values } from '@syuilo/aiscript'; +import { utils, values } from '@syuilo/aiscript'; import { v4 as uuid } from 'uuid'; import { ref, Ref } from 'vue'; diff --git a/packages/frontend/src/scripts/get-note-menu.ts b/packages/frontend/src/scripts/get-note-menu.ts index b5d2251d28..9da7447bfd 100644 --- a/packages/frontend/src/scripts/get-note-menu.ts +++ b/packages/frontend/src/scripts/get-note-menu.ts @@ -1,6 +1,5 @@ -import { defineAsyncComponent, Ref, inject } from 'vue'; +import { defineAsyncComponent, Ref } from 'vue'; import * as misskey from 'misskey-js'; -import { pleaseLogin } from './please-login'; import { claimAchievement } from './achievements'; import { $i } from '@/account'; import { i18n } from '@/i18n'; @@ -9,7 +8,6 @@ import * as os from '@/os'; import copyToClipboard from '@/scripts/copy-to-clipboard'; import { url } from '@/config'; import { noteActions } from '@/store'; -import { notePage } from '@/filters/note'; import { miLocalStorage } from '@/local-storage'; export function getNoteMenu(props: { @@ -202,7 +200,7 @@ export function getNoteMenu(props: { props.translating.value = true; const res = await os.api('notes/translate', { noteId: appearNote.id, - targetLang: miLocalStorage.getItem('lang') || navigator.language, + targetLang: miLocalStorage.getItem('lang') ?? navigator.language, }); props.translating.value = false; props.translation.value = res; @@ -242,7 +240,7 @@ export function getNoteMenu(props: { icon: 'ti ti-external-link', text: i18n.ts.showOnRemote, action: () => { - window.open(appearNote.url || appearNote.uri, '_blank'); + window.open(appearNote.url ?? appearNote.uri, '_blank'); }, } : undefined, { @@ -292,7 +290,7 @@ export function getNoteMenu(props: { ...($i.isModerator || $i.isAdmin ? [ null, { - icon: 'fas fa-bullhorn', + icon: 'ti ti-speakerphone', text: i18n.ts.promote, action: promote }] @@ -304,7 +302,7 @@ export function getNoteMenu(props: { icon: 'ti ti-exclamation-circle', text: i18n.ts.reportAbuse, action: () => { - const u = appearNote.url || appearNote.uri || `${url}/notes/${appearNote.id}`; + const u = appearNote.url ?? appearNote.uri ?? `${url}/notes/${appearNote.id}`; os.popup(defineAsyncComponent(() => import('@/components/MkAbuseReportWindow.vue')), { user: appearNote.user, initialComment: `Note: ${u}\n-----\n`, @@ -346,7 +344,7 @@ export function getNoteMenu(props: { icon: 'ti ti-external-link', text: i18n.ts.showOnRemote, action: () => { - window.open(appearNote.url || appearNote.uri, '_blank'); + window.open(appearNote.url ?? appearNote.uri, '_blank'); }, } : undefined] .filter(x => x !== undefined); diff --git a/packages/frontend/src/scripts/get-user-menu.ts b/packages/frontend/src/scripts/get-user-menu.ts index 941d9a0db9..557b257f62 100644 --- a/packages/frontend/src/scripts/get-user-menu.ts +++ b/packages/frontend/src/scripts/get-user-menu.ts @@ -1,4 +1,3 @@ -import * as Acct from 'misskey-js/built/acct'; import { defineAsyncComponent } from 'vue'; import { i18n } from '@/i18n'; import copyToClipboard from '@/scripts/copy-to-clipboard'; @@ -35,28 +34,6 @@ export function getUserMenu(user, router: Router = mainRouter) { }); } - async function inviteGroup() { - const groups = await os.api('users/groups/owned'); - if (groups.length === 0) { - os.alert({ - type: 'error', - text: i18n.ts.youHaveNoGroups, - }); - return; - } - const { canceled, result: groupId } = await os.select({ - title: i18n.ts.group, - items: groups.map(group => ({ - value: group.id, text: group.name, - })), - }); - if (canceled) return; - os.apiWithDialog('users/groups/invite', { - groupId: groupId, - userId: user.id, - }); - } - async function toggleMute() { if (user.isMuted) { os.apiWithDialog('mute/delete', { @@ -156,20 +133,11 @@ export function getUserMenu(user, router: Router = mainRouter) { action: () => { os.post({ specified: user }); }, - }, meId !== user.id ? { - type: 'link', - icon: 'ti ti-messages', - text: i18n.ts.startMessaging, - to: '/my/messaging/' + Acct.toString(user), - } : undefined, null, { + }, null, { icon: 'ti ti-list', text: i18n.ts.addToList, action: pushList, - }, meId !== user.id ? { - icon: 'ti ti-users', - text: i18n.ts.inviteToGroup, - action: inviteGroup, - } : undefined] as any; + }] as any; if ($i && meId !== user.id) { menu = menu.concat([null, { diff --git a/packages/frontend/src/scripts/get-user-name.ts b/packages/frontend/src/scripts/get-user-name.ts index d499ea0203..4daf203e06 100644 --- a/packages/frontend/src/scripts/get-user-name.ts +++ b/packages/frontend/src/scripts/get-user-name.ts @@ -1,3 +1,3 @@ export default function(user: { name?: string | null, username: string }): string { - return user.name || user.username; + return user.name === '' ? user.username : user.name ?? user.username; } diff --git a/packages/frontend/src/scripts/hpml/evaluator.ts b/packages/frontend/src/scripts/hpml/evaluator.ts index d4090ea15c..7bddd3f62d 100644 --- a/packages/frontend/src/scripts/hpml/evaluator.ts +++ b/packages/frontend/src/scripts/hpml/evaluator.ts @@ -1,11 +1,10 @@ import autobind from 'autobind-decorator'; -import { markRaw, ref, Ref, unref } from 'vue'; +import { ref, Ref, unref } from 'vue'; import { collectPageVars } from '../collect-page-vars'; -import { initHpmlLib, initAiLib } from './lib'; +import { initHpmlLib } from './lib'; import { Expr, isLiteralValue, Variable } from './expr'; import { PageVar, envVarsDef, Fn, HpmlScope, HpmlError } from '.'; import { version } from '@/config'; -import * as os from '@/os'; /** * Hpml evaluator diff --git a/packages/frontend/src/scripts/hpml/index.ts b/packages/frontend/src/scripts/hpml/index.ts index 9a55a5c286..587c6a36c8 100644 --- a/packages/frontend/src/scripts/hpml/index.ts +++ b/packages/frontend/src/scripts/hpml/index.ts @@ -15,12 +15,12 @@ export type Type = 'string' | 'number' | 'boolean' | 'stringArray' | null; export const literalDefs: Record<string, { out: any; category: string; icon: any; }> = { text: { out: 'string', category: 'value', icon: 'ti ti-quote' }, - multiLineText: { out: 'string', category: 'value', icon: 'fas fa-align-left' }, - textList: { out: 'stringArray', category: 'value', icon: 'fas fa-list' }, - number: { out: 'number', category: 'value', icon: 'fas fa-sort-numeric-up' }, - ref: { out: null, category: 'value', icon: 'fas fa-magic' }, - aiScriptVar: { out: null, category: 'value', icon: 'fas fa-magic' }, - fn: { out: 'function', category: 'value', icon: 'fas fa-square-root-alt' }, + multiLineText: { out: 'string', category: 'value', icon: 'ti ti-align-left' }, + textList: { out: 'stringArray', category: 'value', icon: 'ti ti-list' }, + number: { out: 'number', category: 'value', icon: 'ti ti-list-numbers' }, + ref: { out: null, category: 'value', icon: 'ti ti-wand' }, + aiScriptVar: { out: null, category: 'value', icon: 'ti ti-wand' }, + fn: { out: 'function', category: 'value', icon: 'ti ti-math-function' }, }; export const blockDefs = [ @@ -58,7 +58,7 @@ export class HpmlScope { constructor(layerdStates: HpmlScope['layerdStates'], name?: HpmlScope['name']) { this.layerdStates = layerdStates; - this.name = name || 'anonymous'; + this.name = name ?? 'anonymous'; } @autobind diff --git a/packages/frontend/src/scripts/hpml/lib.ts b/packages/frontend/src/scripts/hpml/lib.ts index 02d663b31b..88db82dd27 100644 --- a/packages/frontend/src/scripts/hpml/lib.ts +++ b/packages/frontend/src/scripts/hpml/lib.ts @@ -1,4 +1,3 @@ -import tinycolor from 'tinycolor2'; import seedrandom from 'seedrandom'; import { Hpml } from './evaluator'; import { Expr } from './expr'; @@ -130,42 +129,42 @@ export function initAiLib(hpml: Hpml) { export const funcDefs: Record<string, { in: any[]; out: any; category: string; icon: any; }> = { if: { in: ['boolean', 0, 0], out: 0, category: 'flow', icon: 'ti ti-share' }, - for: { in: ['number', 'function'], out: null, category: 'flow', icon: 'fas fa-recycle' }, - not: { in: ['boolean'], out: 'boolean', category: 'logical', icon: 'fas fa-flag' }, - or: { in: ['boolean', 'boolean'], out: 'boolean', category: 'logical', icon: 'fas fa-flag' }, - and: { in: ['boolean', 'boolean'], out: 'boolean', category: 'logical', icon: 'fas fa-flag' }, + for: { in: ['number', 'function'], out: null, category: 'flow', icon: 'ti ti-recycle' }, + not: { in: ['boolean'], out: 'boolean', category: 'logical', icon: 'ti ti-flag' }, + or: { in: ['boolean', 'boolean'], out: 'boolean', category: 'logical', icon: 'ti ti-flag' }, + and: { in: ['boolean', 'boolean'], out: 'boolean', category: 'logical', icon: 'ti ti-flag' }, add: { in: ['number', 'number'], out: 'number', category: 'operation', icon: 'ti ti-plus' }, subtract: { in: ['number', 'number'], out: 'number', category: 'operation', icon: 'ti ti-minus' }, multiply: { in: ['number', 'number'], out: 'number', category: 'operation', icon: 'ti ti-x' }, - divide: { in: ['number', 'number'], out: 'number', category: 'operation', icon: 'fas fa-divide' }, - mod: { in: ['number', 'number'], out: 'number', category: 'operation', icon: 'fas fa-divide' }, - round: { in: ['number'], out: 'number', category: 'operation', icon: 'fas fa-calculator' }, - eq: { in: [0, 0], out: 'boolean', category: 'comparison', icon: 'fas fa-equals' }, - notEq: { in: [0, 0], out: 'boolean', category: 'comparison', icon: 'fas fa-not-equal' }, - gt: { in: ['number', 'number'], out: 'boolean', category: 'comparison', icon: 'fas fa-greater-than' }, - lt: { in: ['number', 'number'], out: 'boolean', category: 'comparison', icon: 'fas fa-less-than' }, - gtEq: { in: ['number', 'number'], out: 'boolean', category: 'comparison', icon: 'fas fa-greater-than-equal' }, - ltEq: { in: ['number', 'number'], out: 'boolean', category: 'comparison', icon: 'fas fa-less-than-equal' }, + divide: { in: ['number', 'number'], out: 'number', category: 'operation', icon: 'ti ti-divide' }, + mod: { in: ['number', 'number'], out: 'number', category: 'operation', icon: 'ti ti-divide' }, + round: { in: ['number'], out: 'number', category: 'operation', icon: 'ti ti-calculator' }, + eq: { in: [0, 0], out: 'boolean', category: 'comparison', icon: 'ti ti-equal' }, + notEq: { in: [0, 0], out: 'boolean', category: 'comparison', icon: 'ti ti-equal-not' }, + gt: { in: ['number', 'number'], out: 'boolean', category: 'comparison', icon: 'ti ti-math-greater' }, + lt: { in: ['number', 'number'], out: 'boolean', category: 'comparison', icon: 'ti ti-math-lower' }, + gtEq: { in: ['number', 'number'], out: 'boolean', category: 'comparison', icon: 'ti ti-math-equal-greater' }, + ltEq: { in: ['number', 'number'], out: 'boolean', category: 'comparison', icon: 'ti ti-math-equal-lower' }, strLen: { in: ['string'], out: 'number', category: 'text', icon: 'ti ti-quote' }, strPick: { in: ['string', 'number'], out: 'string', category: 'text', icon: 'ti ti-quote' }, strReplace: { in: ['string', 'string', 'string'], out: 'string', category: 'text', icon: 'ti ti-quote' }, strReverse: { in: ['string'], out: 'string', category: 'text', icon: 'ti ti-quote' }, join: { in: ['stringArray', 'string'], out: 'string', category: 'text', icon: 'ti ti-quote' }, - stringToNumber: { in: ['string'], out: 'number', category: 'convert', icon: 'fas fa-exchange-alt' }, - numberToString: { in: ['number'], out: 'string', category: 'convert', icon: 'fas fa-exchange-alt' }, - splitStrByLine: { in: ['string'], out: 'stringArray', category: 'convert', icon: 'fas fa-exchange-alt' }, - pick: { in: [null, 'number'], out: null, category: 'list', icon: 'fas fa-indent' }, - listLen: { in: [null], out: 'number', category: 'list', icon: 'fas fa-indent' }, - rannum: { in: ['number', 'number'], out: 'number', category: 'random', icon: 'fas fa-dice' }, - dailyRannum: { in: ['number', 'number'], out: 'number', category: 'random', icon: 'fas fa-dice' }, - seedRannum: { in: [null, 'number', 'number'], out: 'number', category: 'random', icon: 'fas fa-dice' }, - random: { in: ['number'], out: 'boolean', category: 'random', icon: 'fas fa-dice' }, - dailyRandom: { in: ['number'], out: 'boolean', category: 'random', icon: 'fas fa-dice' }, - seedRandom: { in: [null, 'number'], out: 'boolean', category: 'random', icon: 'fas fa-dice' }, - randomPick: { in: [0], out: 0, category: 'random', icon: 'fas fa-dice' }, - dailyRandomPick: { in: [0], out: 0, category: 'random', icon: 'fas fa-dice' }, - seedRandomPick: { in: [null, 0], out: 0, category: 'random', icon: 'fas fa-dice' }, - DRPWPM: { in: ['stringArray'], out: 'string', category: 'random', icon: 'fas fa-dice' }, // dailyRandomPickWithProbabilityMapping + stringToNumber: { in: ['string'], out: 'number', category: 'convert', icon: 'ti ti-arrows-right-left' }, + numberToString: { in: ['number'], out: 'string', category: 'convert', icon: 'ti ti-arrows-right-left' }, + splitStrByLine: { in: ['string'], out: 'stringArray', category: 'convert', icon: 'ti ti-arrows-right-left' }, + pick: { in: [null, 'number'], out: null, category: 'list', icon: 'ti ti-indent-increase' }, + listLen: { in: [null], out: 'number', category: 'list', icon: 'ti ti-indent-increase' }, + rannum: { in: ['number', 'number'], out: 'number', category: 'random', icon: 'ti ti-dice' }, + dailyRannum: { in: ['number', 'number'], out: 'number', category: 'random', icon: 'ti ti-dice' }, + seedRannum: { in: [null, 'number', 'number'], out: 'number', category: 'random', icon: 'ti ti-dice' }, + random: { in: ['number'], out: 'boolean', category: 'random', icon: 'ti ti-dice' }, + dailyRandom: { in: ['number'], out: 'boolean', category: 'random', icon: 'ti ti-dice' }, + seedRandom: { in: [null, 'number'], out: 'boolean', category: 'random', icon: 'ti ti-dice' }, + randomPick: { in: [0], out: 0, category: 'random', icon: 'ti ti-dice' }, + dailyRandomPick: { in: [0], out: 0, category: 'random', icon: 'ti ti-dice' }, + seedRandomPick: { in: [null, 0], out: 0, category: 'random', icon: 'ti ti-dice' }, + DRPWPM: { in: ['stringArray'], out: 'string', category: 'random', icon: 'ti ti-dice' }, // dailyRandomPickWithProbabilityMapping }; export function initHpmlLib(expr: Expr, scope: HpmlScope, randomSeed: string, visitor?: any) { diff --git a/packages/frontend/src/scripts/hpml/type-checker.ts b/packages/frontend/src/scripts/hpml/type-checker.ts index 24c9ed8bcb..692826fc90 100644 --- a/packages/frontend/src/scripts/hpml/type-checker.ts +++ b/packages/frontend/src/scripts/hpml/type-checker.ts @@ -63,7 +63,7 @@ export class HpmlTypeChecker { @autobind public getExpectedType(v: Expr, slot: number): Type { - const def = funcDefs[v.type || '']; + const def = funcDefs[v.type ?? '']; if (def == null) { throw new Error('Unknown type: ' + v.type); } @@ -107,7 +107,7 @@ export class HpmlTypeChecker { return pageVar.type; } - const envVar = envVarsDef[v.value || '']; + const envVar = envVarsDef[v.value ?? '']; if (envVar !== undefined) { return envVar; } diff --git a/packages/frontend/src/scripts/popup-position.ts b/packages/frontend/src/scripts/popup-position.ts index e84eebf103..cb45002202 100644 --- a/packages/frontend/src/scripts/popup-position.ts +++ b/packages/frontend/src/scripts/popup-position.ts @@ -1,4 +1,3 @@ -import { Ref } from 'vue'; export function calcPopupPosition(el: HTMLElement, props: { anchorElement: HTMLElement | null; diff --git a/packages/frontend/src/scripts/scroll.ts b/packages/frontend/src/scripts/scroll.ts index e3d9dc00c2..a002f02b5a 100644 --- a/packages/frontend/src/scripts/scroll.ts +++ b/packages/frontend/src/scripts/scroll.ts @@ -10,7 +10,7 @@ export function getScrollContainer(el: HTMLElement | null): HTMLElement | null { } } -export function getStickyTop(el: HTMLElement, container: HTMLElement | null = null, top: number = 0) { +export function getStickyTop(el: HTMLElement, container: HTMLElement | null = null, top = 0) { if (!el.parentElement) return top; const data = el.dataset.stickyContainerHeaderHeight; const newTop = data ? Number(data) + top : top; @@ -23,14 +23,14 @@ export function getScrollPosition(el: HTMLElement | null): number { return container == null ? window.scrollY : container.scrollTop; } -export function onScrollTop(el: HTMLElement, cb: () => unknown, tolerance: number = 1, once: boolean = false) { +export function onScrollTop(el: HTMLElement, cb: () => unknown, tolerance = 1, once = false) { // とりあえず評価してみる if (isTopVisible(el)) { cb(); if (once) return null; } - const container = getScrollContainer(el) || window; + const container = getScrollContainer(el) ?? window; const onScroll = ev => { if (!document.body.contains(el)) return; @@ -45,7 +45,7 @@ export function onScrollTop(el: HTMLElement, cb: () => unknown, tolerance: numbe return removeListener; } -export function onScrollBottom(el: HTMLElement, cb: () => unknown, tolerance: number = 1, once: boolean = false) { +export function onScrollBottom(el: HTMLElement, cb: () => unknown, tolerance = 1, once = false) { const container = getScrollContainer(el); // とりあえず評価してみる @@ -54,7 +54,7 @@ export function onScrollBottom(el: HTMLElement, cb: () => unknown, tolerance: nu if (once) return null; } - const containerOrWindow = container || window; + const containerOrWindow = container ?? window; const onScroll = ev => { if (!document.body.contains(el)) return; if (isBottomVisible(el, 1, container)) { @@ -104,12 +104,12 @@ export function scrollToBottom( } else { window.scroll({ top: (el.scrollHeight - window.innerHeight + getStickyTop(el, container) + (window.innerWidth <= 500 ? 96 : 0)) || 0, - ...options + ...options, }); } } -export function isTopVisible(el: HTMLElement, tolerance: number = 1): boolean { +export function isTopVisible(el: HTMLElement, tolerance = 1): boolean { const scrollTop = getScrollPosition(el); return scrollTop <= tolerance; } @@ -124,6 +124,6 @@ export function getBodyScrollHeight() { return Math.max( document.body.scrollHeight, document.documentElement.scrollHeight, document.body.offsetHeight, document.documentElement.offsetHeight, - document.body.clientHeight, document.documentElement.clientHeight + document.body.clientHeight, document.documentElement.clientHeight, ); } diff --git a/packages/frontend/src/scripts/search.ts b/packages/frontend/src/scripts/search.ts index 64914d3d65..69f1586b77 100644 --- a/packages/frontend/src/scripts/search.ts +++ b/packages/frontend/src/scripts/search.ts @@ -35,7 +35,7 @@ export async function search() { // TODO //v.$root.$emit('warp', date); os.alert({ - icon: 'fas fa-history', + icon: 'ti ti-history', iconOnly: true, autoClose: true, }); return; diff --git a/packages/frontend/src/scripts/use-leave-guard.ts b/packages/frontend/src/scripts/use-leave-guard.ts index a93b84d1fe..146b012471 100644 --- a/packages/frontend/src/scripts/use-leave-guard.ts +++ b/packages/frontend/src/scripts/use-leave-guard.ts @@ -1,6 +1,4 @@ -import { inject, onUnmounted, Ref } from 'vue'; -import { i18n } from '@/i18n'; -import * as os from '@/os'; +import { Ref } from 'vue'; export function useLeaveGuard(enabled: Ref<boolean>) { /* TODO diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts index 46e55900cd..54c159ed6b 100644 --- a/packages/frontend/src/store.ts +++ b/packages/frontend/src/store.ts @@ -1,6 +1,5 @@ import { markRaw, ref } from 'vue'; import { Storage } from './pizzax'; -import { Theme } from './scripts/theme'; interface PostFormAction { title: string, diff --git a/packages/frontend/src/theme-store.ts b/packages/frontend/src/theme-store.ts index aa1244665b..580c7da007 100644 --- a/packages/frontend/src/theme-store.ts +++ b/packages/frontend/src/theme-store.ts @@ -1,13 +1,13 @@ -import { api } from '@/os'; -import { $i } from '@/account'; import { Theme } from './scripts/theme'; import { miLocalStorage } from './local-storage'; +import { api } from '@/os'; +import { $i } from '@/account'; const lsCacheKey = $i ? `themes:${$i.id}` as const : null; export function getThemes(): Theme[] { if ($i == null) return []; - return JSON.parse(miLocalStorage.getItem(lsCacheKey!) || '[]'); + return JSON.parse(miLocalStorage.getItem(lsCacheKey!) ?? '[]'); } export async function fetchThemes(): Promise<void> { diff --git a/packages/frontend/src/ui/_common_/common.vue b/packages/frontend/src/ui/_common_/common.vue index 342b3f2dbf..976345f9ee 100644 --- a/packages/frontend/src/ui/_common_/common.vue +++ b/packages/frontend/src/ui/_common_/common.vue @@ -30,11 +30,11 @@ </template> <script lang="ts" setup> -import { defineAsyncComponent, nextTick } from 'vue'; +import { defineAsyncComponent } from 'vue'; import * as misskey from 'misskey-js'; import { swInject } from './sw-inject'; import XNotification from './notification.vue'; -import { popup, popups, pendingApiRequestsCount } from '@/os'; +import { popups, pendingApiRequestsCount } from '@/os'; import { uploads } from '@/scripts/upload'; import * as sound from '@/scripts/sound'; import { $i } from '@/account'; diff --git a/packages/frontend/src/ui/_common_/navbar-for-mobile.vue b/packages/frontend/src/ui/_common_/navbar-for-mobile.vue index 357db5599f..935aceea7c 100644 --- a/packages/frontend/src/ui/_common_/navbar-for-mobile.vue +++ b/packages/frontend/src/ui/_common_/navbar-for-mobile.vue @@ -43,7 +43,7 @@ </template> <script lang="ts" setup> -import { computed, defineAsyncComponent, ref, toRef, watch } from 'vue'; +import { computed, defineAsyncComponent, toRef } from 'vue'; import { openInstanceMenu } from './common'; import * as os from '@/os'; import { navbarItemDef } from '@/navbar'; diff --git a/packages/frontend/src/ui/_common_/statusbar-federation.vue b/packages/frontend/src/ui/_common_/statusbar-federation.vue index 70d683d755..fe95460ba4 100644 --- a/packages/frontend/src/ui/_common_/statusbar-federation.vue +++ b/packages/frontend/src/ui/_common_/statusbar-federation.vue @@ -20,13 +20,11 @@ </template> <script lang="ts" setup> -import { computed, defineAsyncComponent, ref, toRef, watch } from 'vue'; +import { ref } from 'vue'; import * as misskey from 'misskey-js'; import MarqueeText from '@/components/MkMarquee.vue'; import * as os from '@/os'; import { useInterval } from '@/scripts/use-interval'; -import { getNoteSummary } from '@/scripts/get-note-summary'; -import { notePage } from '@/filters/note'; import { getProxiedImageUrlNullable } from '@/scripts/media-proxy'; const props = defineProps<{ diff --git a/packages/frontend/src/ui/_common_/statusbar-rss.vue b/packages/frontend/src/ui/_common_/statusbar-rss.vue index e59ace8876..44b6b278ea 100644 --- a/packages/frontend/src/ui/_common_/statusbar-rss.vue +++ b/packages/frontend/src/ui/_common_/statusbar-rss.vue @@ -16,9 +16,8 @@ </template> <script lang="ts" setup> -import { computed, defineAsyncComponent, ref, toRef, watch } from 'vue'; +import { ref } from 'vue'; import MarqueeText from '@/components/MkMarquee.vue'; -import * as os from '@/os'; import { useInterval } from '@/scripts/use-interval'; import { shuffle } from '@/scripts/shuffle'; diff --git a/packages/frontend/src/ui/_common_/statusbar-user-list.vue b/packages/frontend/src/ui/_common_/statusbar-user-list.vue index 6fec81de36..16df69d968 100644 --- a/packages/frontend/src/ui/_common_/statusbar-user-list.vue +++ b/packages/frontend/src/ui/_common_/statusbar-user-list.vue @@ -20,7 +20,7 @@ </template> <script lang="ts" setup> -import { computed, defineAsyncComponent, ref, toRef, watch } from 'vue'; +import { ref, watch } from 'vue'; import * as misskey from 'misskey-js'; import MarqueeText from '@/components/MkMarquee.vue'; import * as os from '@/os'; diff --git a/packages/frontend/src/ui/_common_/statusbars.vue b/packages/frontend/src/ui/_common_/statusbars.vue index 114ca5be8c..f84695c15f 100644 --- a/packages/frontend/src/ui/_common_/statusbars.vue +++ b/packages/frontend/src/ui/_common_/statusbars.vue @@ -18,8 +18,7 @@ </template> <script lang="ts" setup> -import { computed, defineAsyncComponent, ref, toRef, watch } from 'vue'; -import * as os from '@/os'; +import { defineAsyncComponent } from 'vue'; import { defaultStore } from '@/store'; const XRss = defineAsyncComponent(() => import('./statusbar-rss.vue')); const XFederation = defineAsyncComponent(() => import('./statusbar-federation.vue')); diff --git a/packages/frontend/src/ui/classic.vue b/packages/frontend/src/ui/classic.vue index 02dafcc3b6..a359463d9b 100644 --- a/packages/frontend/src/ui/classic.vue +++ b/packages/frontend/src/ui/classic.vue @@ -41,14 +41,14 @@ </template> <script lang="ts" setup> -import { defineAsyncComponent, markRaw, ComputedRef, ref, onMounted, provide } from 'vue'; +import { defineAsyncComponent, ComputedRef, onMounted, provide } from 'vue'; import XSidebar from './classic.sidebar.vue'; import XCommon from './_common_/common.vue'; import { instanceName } from '@/config'; import { StickySidebar } from '@/scripts/sticky-sidebar'; import * as os from '@/os'; import { mainRouter } from '@/router'; -import { PageMetadata, provideMetadataReceiver, setPageMetadata } from '@/scripts/page-metadata'; +import { PageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata'; import { defaultStore } from '@/store'; import { i18n } from '@/i18n'; import { miLocalStorage } from '@/local-storage'; @@ -105,7 +105,7 @@ function onContextmenu(ev: MouseEvent) { type: 'label', text: path, }, { - icon: fullView ? 'fas fa-compress' : 'fas fa-expand', + icon: fullView ? 'ti ti-minimize' : 'ti ti-maximize', text: fullView ? i18n.ts.quitFullView : i18n.ts.fullView, action: () => { fullView = !fullView; @@ -124,8 +124,8 @@ function onAiClick(ev) { } if (window.innerWidth < 1024) { - const currentUI = miLocalStorage.getItem('ui') - miLocalStorage.setItem('ui_temp', currentUI || 'default'); + const currentUI = miLocalStorage.getItem('ui'); + miLocalStorage.setItem('ui_temp', currentUI ?? 'default'); miLocalStorage.setItem('ui', 'default'); location.reload(); } diff --git a/packages/frontend/src/ui/deck.vue b/packages/frontend/src/ui/deck.vue index b09721dec9..4e93359591 100644 --- a/packages/frontend/src/ui/deck.vue +++ b/packages/frontend/src/ui/deck.vue @@ -4,7 +4,7 @@ <div :class="$style.main"> <XStatusBars/> - <div ref="columnsEl" :class="[$style.columns, deckStore.reactiveState.columnAlign.value]" @contextmenu.self.prevent="onContextmenu"> + <div ref="columnsEl" :class="[$style.columns, deckStore.reactiveState.columnAlign.value, { [$style.snapScroll]: snapScroll }]" @contextmenu.self.prevent="onContextmenu"> <template v-for="ids in layout"> <!-- sectionを利用しているのは、deck.vue側でcolumnに対してfirst-of-typeを効かせるため --> <section @@ -83,7 +83,7 @@ </template> <script lang="ts" setup> -import { computed, defineAsyncComponent, onMounted, provide, ref, watch } from 'vue'; +import { computed, defineAsyncComponent, ref, watch } from 'vue'; import { v4 as uuid } from 'uuid'; import XCommon from './_common_/common.vue'; import { deckStore, addColumn as addColumnToStore, loadDeck, getProfiles, deleteProfile as deleteProfile_ } from './deck/deck-store'; @@ -115,6 +115,7 @@ window.addEventListener('resize', () => { isMobile.value = window.innerWidth <= 500; }); +const snapScroll = isMobile; const drawerMenuShowing = ref(false); const route = 'TODO'; @@ -297,9 +298,14 @@ async function deleteProfile() { margin-right: auto; } } + + &.snapScroll { + scroll-snap-type: x mandatory; + } } .column { + scroll-snap-align: start; flex-shrink: 0; border-right: solid var(--deckDividerThickness) var(--deckDivider); diff --git a/packages/frontend/src/ui/deck/antenna-column.vue b/packages/frontend/src/ui/deck/antenna-column.vue index 4c69c8e8e8..76a8b6e760 100644 --- a/packages/frontend/src/ui/deck/antenna-column.vue +++ b/packages/frontend/src/ui/deck/antenna-column.vue @@ -4,7 +4,7 @@ <i class="ti ti-antenna"></i><span style="margin-left: 8px;">{{ column.name }}</span> </template> - <XTimeline v-if="column.antennaId" ref="timeline" src="antenna" :antenna="column.antennaId" @after="() => emit('loaded')"/> + <MkTimeline v-if="column.antennaId" ref="timeline" src="antenna" :antenna="column.antennaId" @after="() => emit('loaded')"/> </XColumn> </template> @@ -12,7 +12,7 @@ import { onMounted } from 'vue'; import XColumn from './column.vue'; import { updateColumn, Column } from './deck-store'; -import XTimeline from '@/components/MkTimeline.vue'; +import MkTimeline from '@/components/MkTimeline.vue'; import * as os from '@/os'; import { i18n } from '@/i18n'; @@ -26,7 +26,7 @@ const emit = defineEmits<{ (ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; }>(); -let timeline = $shallowRef<InstanceType<typeof XTimeline>>(); +let timeline = $shallowRef<InstanceType<typeof MkTimeline>>(); onMounted(() => { if (props.column.antennaId == null) { diff --git a/packages/frontend/src/ui/deck/channel-column.vue b/packages/frontend/src/ui/deck/channel-column.vue index 5a84237c80..4c6b41e42e 100644 --- a/packages/frontend/src/ui/deck/channel-column.vue +++ b/packages/frontend/src/ui/deck/channel-column.vue @@ -8,7 +8,7 @@ <div style="padding: 8px; text-align: center;"> <MkButton primary gradate rounded inline @click="post"><i class="ti ti-pencil"></i></MkButton> </div> - <XTimeline ref="timeline" src="channel" :channel="column.channelId" @after="() => emit('loaded')"/> + <MkTimeline ref="timeline" src="channel" :channel="column.channelId" @after="() => emit('loaded')"/> </template> </XColumn> </template> @@ -17,7 +17,7 @@ import { } from 'vue'; import XColumn from './column.vue'; import { updateColumn, Column } from './deck-store'; -import XTimeline from '@/components/MkTimeline.vue'; +import MkTimeline from '@/components/MkTimeline.vue'; import MkButton from '@/components/MkButton.vue'; import * as os from '@/os'; import { i18n } from '@/i18n'; @@ -32,14 +32,16 @@ const emit = defineEmits<{ (ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; }>(); -let timeline = $shallowRef<InstanceType<typeof XTimeline>>(); +let timeline = $shallowRef<InstanceType<typeof MkTimeline>>(); if (props.column.channelId == null) { setChannel(); } async function setChannel() { - const channels = await os.api('channels/followed'); + const channels = await os.api('channels/followed', { + limit: 100, + }); const { canceled, result: channel } = await os.select({ title: i18n.ts.selectChannel, items: channels.map(x => ({ diff --git a/packages/frontend/src/ui/deck/column.vue b/packages/frontend/src/ui/deck/column.vue index 339421a13e..38ee37de27 100644 --- a/packages/frontend/src/ui/deck/column.vue +++ b/packages/frontend/src/ui/deck/column.vue @@ -29,8 +29,8 @@ </template> <script lang="ts" setup> -import { onBeforeUnmount, onMounted, provide, Ref, watch } from 'vue'; -import { updateColumn, swapLeftColumn, swapRightColumn, swapUpColumn, swapDownColumn, stackLeftColumn, popRightColumn, removeColumn, swapColumn, Column, deckStore } from './deck-store'; +import { onBeforeUnmount, onMounted, provide, watch } from 'vue'; +import { updateColumn, swapLeftColumn, swapRightColumn, swapUpColumn, swapDownColumn, stackLeftColumn, popRightColumn, removeColumn, swapColumn, Column } from './deck-store'; import * as os from '@/os'; import { i18n } from '@/i18n'; import { MenuItem } from '@/types/menu'; diff --git a/packages/frontend/src/ui/deck/deck-store.ts b/packages/frontend/src/ui/deck/deck-store.ts index 80c202a2ef..1420ad8b30 100644 --- a/packages/frontend/src/ui/deck/deck-store.ts +++ b/packages/frontend/src/ui/deck/deck-store.ts @@ -2,7 +2,6 @@ import { throttle } from 'throttle-debounce'; import { markRaw } from 'vue'; import { notificationTypes } from 'misskey-js'; import { Storage } from '../../pizzax'; -import { i18n } from '@/i18n'; import { api } from '@/os'; import { deepClone } from '@/scripts/clone'; diff --git a/packages/frontend/src/ui/deck/direct-column.vue b/packages/frontend/src/ui/deck/direct-column.vue index 75b018cacd..15b76c4d92 100644 --- a/packages/frontend/src/ui/deck/direct-column.vue +++ b/packages/frontend/src/ui/deck/direct-column.vue @@ -2,15 +2,15 @@ <XColumn :column="column" :is-stacked="isStacked" @parent-focus="$event => emit('parent-focus', $event)"> <template #header><i class="ti ti-mail" style="margin-right: 8px;"></i>{{ column.name }}</template> - <XNotes :pagination="pagination"/> + <MkNotes :pagination="pagination"/> </XColumn> </template> <script lang="ts" setup> import { } from 'vue'; import XColumn from './column.vue'; -import XNotes from '@/components/MkNotes.vue'; import { Column } from './deck-store'; +import MkNotes from '@/components/MkNotes.vue'; defineProps<{ column: Column; diff --git a/packages/frontend/src/ui/deck/list-column.vue b/packages/frontend/src/ui/deck/list-column.vue index 58633e5672..352c1d246a 100644 --- a/packages/frontend/src/ui/deck/list-column.vue +++ b/packages/frontend/src/ui/deck/list-column.vue @@ -4,7 +4,7 @@ <i class="ti ti-list"></i><span style="margin-left: 8px;">{{ column.name }}</span> </template> - <XTimeline v-if="column.listId" ref="timeline" src="list" :list="column.listId" @after="() => emit('loaded')"/> + <MkTimeline v-if="column.listId" ref="timeline" src="list" :list="column.listId" @after="() => emit('loaded')"/> </XColumn> </template> @@ -12,7 +12,7 @@ import { } from 'vue'; import XColumn from './column.vue'; import { updateColumn, Column } from './deck-store'; -import XTimeline from '@/components/MkTimeline.vue'; +import MkTimeline from '@/components/MkTimeline.vue'; import * as os from '@/os'; import { i18n } from '@/i18n'; @@ -26,7 +26,7 @@ const emit = defineEmits<{ (ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; }>(); -let timeline = $shallowRef<InstanceType<typeof XTimeline>>(); +let timeline = $shallowRef<InstanceType<typeof MkTimeline>>(); if (props.column.listId == null) { setList(); diff --git a/packages/frontend/src/ui/deck/main-column.vue b/packages/frontend/src/ui/deck/main-column.vue index 0c66172397..f3826a8d31 100644 --- a/packages/frontend/src/ui/deck/main-column.vue +++ b/packages/frontend/src/ui/deck/main-column.vue @@ -18,7 +18,7 @@ import { deckStore, Column } from '@/ui/deck/deck-store'; import * as os from '@/os'; import { i18n } from '@/i18n'; import { mainRouter } from '@/router'; -import { PageMetadata, provideMetadataReceiver, setPageMetadata } from '@/scripts/page-metadata'; +import { PageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata'; defineProps<{ column: Column; diff --git a/packages/frontend/src/ui/deck/mentions-column.vue b/packages/frontend/src/ui/deck/mentions-column.vue index 16962956a0..852d7a8f7e 100644 --- a/packages/frontend/src/ui/deck/mentions-column.vue +++ b/packages/frontend/src/ui/deck/mentions-column.vue @@ -2,15 +2,15 @@ <XColumn :column="column" :is-stacked="isStacked" @parent-focus="$event => emit('parent-focus', $event)"> <template #header><i class="ti ti-at" style="margin-right: 8px;"></i>{{ column.name }}</template> - <XNotes :pagination="pagination"/> + <MkNotes :pagination="pagination"/> </XColumn> </template> <script lang="ts" setup> import { } from 'vue'; import XColumn from './column.vue'; -import XNotes from '@/components/MkNotes.vue'; import { Column } from './deck-store'; +import MkNotes from '@/components/MkNotes.vue'; defineProps<{ column: Column; diff --git a/packages/frontend/src/ui/deck/tl-column.vue b/packages/frontend/src/ui/deck/tl-column.vue index 8bde7e4ce3..a947e27e57 100644 --- a/packages/frontend/src/ui/deck/tl-column.vue +++ b/packages/frontend/src/ui/deck/tl-column.vue @@ -15,7 +15,7 @@ </p> <p :class="$style.disabledDescription">{{ $t('disabled-timeline.description') }}</p> </div> - <XTimeline v-else-if="column.tl" ref="timeline" :key="column.tl" :src="column.tl" @after="() => emit('loaded')"/> + <MkTimeline v-else-if="column.tl" ref="timeline" :key="column.tl" :src="column.tl" @after="() => emit('loaded')"/> </XColumn> </template> @@ -23,10 +23,9 @@ import { onMounted } from 'vue'; import XColumn from './column.vue'; import { removeColumn, updateColumn, Column } from './deck-store'; -import XTimeline from '@/components/MkTimeline.vue'; +import MkTimeline from '@/components/MkTimeline.vue'; import * as os from '@/os'; import { $i } from '@/account'; -import { instance } from '@/instance'; import { i18n } from '@/i18n'; const props = defineProps<{ diff --git a/packages/frontend/src/ui/universal.vue b/packages/frontend/src/ui/universal.vue index 65a1ce0fce..beae799f5c 100644 --- a/packages/frontend/src/ui/universal.vue +++ b/packages/frontend/src/ui/universal.vue @@ -84,7 +84,7 @@ </template> <script lang="ts" setup> -import { defineAsyncComponent, provide, onMounted, computed, ref, watch, ComputedRef } from 'vue'; +import { defineAsyncComponent, provide, onMounted, computed, ref, ComputedRef } from 'vue'; import XCommon from './_common_/common.vue'; import { instanceName } from '@/config'; import { StickySidebar } from '@/scripts/sticky-sidebar'; @@ -94,9 +94,8 @@ import { defaultStore } from '@/store'; import { navbarItemDef } from '@/navbar'; import { i18n } from '@/i18n'; import { $i } from '@/account'; -import { Router } from '@/nirax'; import { mainRouter } from '@/router'; -import { PageMetadata, provideMetadataReceiver, setPageMetadata } from '@/scripts/page-metadata'; +import { PageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata'; import { deviceKind } from '@/scripts/device-kind'; import { miLocalStorage } from '@/local-storage'; const XWidgets = defineAsyncComponent(() => import('./universal.widgets.vue')); diff --git a/packages/frontend/src/ui/visitor.vue b/packages/frontend/src/ui/visitor.vue index 797e2aa6c3..6c96440ebd 100644 --- a/packages/frontend/src/ui/visitor.vue +++ b/packages/frontend/src/ui/visitor.vue @@ -4,7 +4,7 @@ </template> <script lang="ts"> -import { defineComponent, defineAsyncComponent } from 'vue'; +import { defineComponent } from 'vue'; //import DesignA from './visitor/a.vue'; import DesignB from './visitor/b.vue'; import XCommon from './_common_/common.vue'; diff --git a/packages/frontend/src/ui/visitor/b.vue b/packages/frontend/src/ui/visitor/b.vue index 058a9482fa..163f038b43 100644 --- a/packages/frontend/src/ui/visitor/b.vue +++ b/packages/frontend/src/ui/visitor/b.vue @@ -11,7 +11,10 @@ <div class="contents"> <XHeader v-if="!root" class="header"/> - <main style="container-type: inline-size;"> + <main v-if="!root" style="container-type: inline-size;"> + <RouterView/> + </main> + <main v-else> <RouterView/> </main> <div v-if="!root" class="powered-by"> @@ -58,13 +61,11 @@ import { host, instanceName } from '@/config'; import { search } from '@/scripts/search'; import * as os from '@/os'; import { instance } from '@/instance'; -import MkPagination from '@/components/MkPagination.vue'; import XSigninDialog from '@/components/MkSigninDialog.vue'; import XSignupDialog from '@/components/MkSignupDialog.vue'; -import MkButton from '@/components/MkButton.vue'; import { ColdDeviceStorage, defaultStore } from '@/store'; import { mainRouter } from '@/router'; -import { PageMetadata, provideMetadataReceiver, setPageMetadata } from '@/scripts/page-metadata'; +import { PageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata'; const DESKTOP_THRESHOLD = 1100; @@ -83,7 +84,7 @@ const announcements = { limit: 10, }; -const isTimelineAvailable = instance.policies.ltlAvailable || instance.policies.gtlAvailable; +const isTimelineAvailable = $ref(instance.policies?.ltlAvailable || instance.policies?.gtlAvailable); let showMenu = $ref(false); let isDesktop = $ref(window.innerWidth >= DESKTOP_THRESHOLD); diff --git a/packages/frontend/src/ui/visitor/kanban.vue b/packages/frontend/src/ui/visitor/kanban.vue index 51e47f277d..05ded834ee 100644 --- a/packages/frontend/src/ui/visitor/kanban.vue +++ b/packages/frontend/src/ui/visitor/kanban.vue @@ -38,7 +38,7 @@ </template> <script lang="ts"> -import { defineComponent, defineAsyncComponent } from 'vue'; +import { defineComponent } from 'vue'; import { host, instanceName } from '@/config'; import * as os from '@/os'; import MkPagination from '@/components/MkPagination.vue'; diff --git a/packages/frontend/src/ui/zen.vue b/packages/frontend/src/ui/zen.vue index d8fda1f7c3..628390e3f7 100644 --- a/packages/frontend/src/ui/zen.vue +++ b/packages/frontend/src/ui/zen.vue @@ -10,7 +10,7 @@ import { provide, ComputedRef } from 'vue'; import XCommon from './_common_/common.vue'; import { mainRouter } from '@/router'; -import { PageMetadata, provideMetadataReceiver, setPageMetadata } from '@/scripts/page-metadata'; +import { PageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata'; import { instanceName } from '@/config'; let pageMetadata = $ref<null | ComputedRef<PageMetadata>>(); diff --git a/packages/frontend/src/widgets/WidgetActivity.vue b/packages/frontend/src/widgets/WidgetActivity.vue index da1cab6f88..7acf2140cf 100644 --- a/packages/frontend/src/widgets/WidgetActivity.vue +++ b/packages/frontend/src/widgets/WidgetActivity.vue @@ -15,8 +15,8 @@ </template> <script lang="ts" setup> -import { onMounted, onUnmounted, reactive, ref, watch } from 'vue'; -import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; +import { ref } from 'vue'; +import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget'; import XCalendar from './WidgetActivity.calendar.vue'; import XChart from './WidgetActivity.chart.vue'; import { GetFormResultType } from '@/scripts/form'; diff --git a/packages/frontend/src/widgets/WidgetAichan.vue b/packages/frontend/src/widgets/WidgetAichan.vue index 1bb3089d16..cb055a56f6 100644 --- a/packages/frontend/src/widgets/WidgetAichan.vue +++ b/packages/frontend/src/widgets/WidgetAichan.vue @@ -5,8 +5,8 @@ </template> <script lang="ts" setup> -import { onMounted, onUnmounted, reactive, ref, shallowRef } from 'vue'; -import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; +import { onMounted, onUnmounted, shallowRef } from 'vue'; +import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget'; import { GetFormResultType } from '@/scripts/form'; const name = 'ai'; diff --git a/packages/frontend/src/widgets/WidgetAiscript.vue b/packages/frontend/src/widgets/WidgetAiscript.vue index 9e489227ff..33218a110b 100644 --- a/packages/frontend/src/widgets/WidgetAiscript.vue +++ b/packages/frontend/src/widgets/WidgetAiscript.vue @@ -14,9 +14,9 @@ </template> <script lang="ts" setup> -import { onMounted, onUnmounted, ref, watch } from 'vue'; +import { ref } from 'vue'; import { Interpreter, Parser, utils } from '@syuilo/aiscript'; -import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; +import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget'; import { GetFormResultType } from '@/scripts/form'; import * as os from '@/os'; import MkContainer from '@/components/MkContainer.vue'; diff --git a/packages/frontend/src/widgets/WidgetAiscriptApp.vue b/packages/frontend/src/widgets/WidgetAiscriptApp.vue index 9a2b60eb05..455a6e6ea5 100644 --- a/packages/frontend/src/widgets/WidgetAiscriptApp.vue +++ b/packages/frontend/src/widgets/WidgetAiscriptApp.vue @@ -8,16 +8,16 @@ </template> <script lang="ts" setup> -import { onMounted, onUnmounted, Ref, ref, watch } from 'vue'; -import { Interpreter, Parser, utils, values } from '@syuilo/aiscript'; -import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; +import { onMounted, Ref, ref, watch } from 'vue'; +import { Interpreter, Parser } from '@syuilo/aiscript'; +import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget'; import { GetFormResultType } from '@/scripts/form'; import * as os from '@/os'; import { createAiScriptEnv } from '@/scripts/aiscript/api'; import { $i } from '@/account'; import MkAsUi from '@/components/MkAsUi.vue'; import MkContainer from '@/components/MkContainer.vue'; -import { AsUiComponent, AsUiRoot, patch, registerAsUiLib, render } from '@/scripts/aiscript/ui'; +import { AsUiComponent, AsUiRoot, registerAsUiLib } from '@/scripts/aiscript/ui'; const name = 'aiscriptApp'; diff --git a/packages/frontend/src/widgets/WidgetButton.vue b/packages/frontend/src/widgets/WidgetButton.vue index 6c2c366aa2..462f1e5a5d 100644 --- a/packages/frontend/src/widgets/WidgetButton.vue +++ b/packages/frontend/src/widgets/WidgetButton.vue @@ -7,9 +7,8 @@ </template> <script lang="ts" setup> -import { onMounted, onUnmounted, ref, watch } from 'vue'; -import { Interpreter, Parser, utils } from '@syuilo/aiscript'; -import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; +import { Interpreter, Parser } from '@syuilo/aiscript'; +import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget'; import { GetFormResultType } from '@/scripts/form'; import * as os from '@/os'; import { createAiScriptEnv } from '@/scripts/aiscript/api'; diff --git a/packages/frontend/src/widgets/WidgetCalendar.vue b/packages/frontend/src/widgets/WidgetCalendar.vue index fe31e10215..083d8588af 100644 --- a/packages/frontend/src/widgets/WidgetCalendar.vue +++ b/packages/frontend/src/widgets/WidgetCalendar.vue @@ -33,8 +33,8 @@ </template> <script lang="ts" setup> -import { onUnmounted, ref } from 'vue'; -import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; +import { ref } from 'vue'; +import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget'; import { GetFormResultType } from '@/scripts/form'; import { i18n } from '@/i18n'; import { useInterval } from '@/scripts/use-interval'; diff --git a/packages/frontend/src/widgets/WidgetClicker.vue b/packages/frontend/src/widgets/WidgetClicker.vue index 66c3fbd9d2..981788a3c5 100644 --- a/packages/frontend/src/widgets/WidgetClicker.vue +++ b/packages/frontend/src/widgets/WidgetClicker.vue @@ -7,10 +7,8 @@ </template> <script lang="ts" setup> -import { onMounted, onUnmounted, Ref, ref, watch } from 'vue'; -import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; +import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget'; import { GetFormResultType } from '@/scripts/form'; -import { $i } from '@/account'; import MkContainer from '@/components/MkContainer.vue'; import MkClickerGame from '@/components/MkClickerGame.vue'; diff --git a/packages/frontend/src/widgets/WidgetClock.vue b/packages/frontend/src/widgets/WidgetClock.vue index 7f9c96b93b..ecbb03b570 100644 --- a/packages/frontend/src/widgets/WidgetClock.vue +++ b/packages/frontend/src/widgets/WidgetClock.vue @@ -19,7 +19,7 @@ <script lang="ts" setup> import { } from 'vue'; -import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; +import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget'; import { GetFormResultType } from '@/scripts/form'; import MkContainer from '@/components/MkContainer.vue'; import MkAnalogClock from '@/components/MkAnalogClock.vue'; diff --git a/packages/frontend/src/widgets/WidgetDigitalClock.vue b/packages/frontend/src/widgets/WidgetDigitalClock.vue index 0f9c46f1c7..1780a1c8d2 100644 --- a/packages/frontend/src/widgets/WidgetDigitalClock.vue +++ b/packages/frontend/src/widgets/WidgetDigitalClock.vue @@ -9,8 +9,7 @@ </template> <script lang="ts" setup> -import { onUnmounted, ref, watch } from 'vue'; -import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; +import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget'; import { GetFormResultType } from '@/scripts/form'; import { timezones } from '@/scripts/timezones'; import MkDigitalClock from '@/components/MkDigitalClock.vue'; diff --git a/packages/frontend/src/widgets/WidgetFederation.vue b/packages/frontend/src/widgets/WidgetFederation.vue index 806c5d1801..a8095acf65 100644 --- a/packages/frontend/src/widgets/WidgetFederation.vue +++ b/packages/frontend/src/widgets/WidgetFederation.vue @@ -20,8 +20,8 @@ </template> <script lang="ts" setup> -import { onMounted, onUnmounted, ref } from 'vue'; -import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; +import { ref } from 'vue'; +import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget'; import { GetFormResultType } from '@/scripts/form'; import MkContainer from '@/components/MkContainer.vue'; import MkMiniChart from '@/components/MkMiniChart.vue'; diff --git a/packages/frontend/src/widgets/WidgetInstanceCloud.vue b/packages/frontend/src/widgets/WidgetInstanceCloud.vue index 1068c5ac4b..b157807655 100644 --- a/packages/frontend/src/widgets/WidgetInstanceCloud.vue +++ b/packages/frontend/src/widgets/WidgetInstanceCloud.vue @@ -14,7 +14,7 @@ <script lang="ts" setup> import { } from 'vue'; -import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; +import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget'; import { GetFormResultType } from '@/scripts/form'; import MkContainer from '@/components/MkContainer.vue'; import MkTagCloud from '@/components/MkTagCloud.vue'; diff --git a/packages/frontend/src/widgets/WidgetInstanceInfo.vue b/packages/frontend/src/widgets/WidgetInstanceInfo.vue index 990802d847..3a3b071b7d 100644 --- a/packages/frontend/src/widgets/WidgetInstanceInfo.vue +++ b/packages/frontend/src/widgets/WidgetInstanceInfo.vue @@ -15,8 +15,7 @@ </template> <script lang="ts" setup> -import { onMounted, onUnmounted, Ref, ref, watch } from 'vue'; -import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; +import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget'; import { GetFormResultType } from '@/scripts/form'; import { host } from '@/config'; diff --git a/packages/frontend/src/widgets/WidgetJobQueue.vue b/packages/frontend/src/widgets/WidgetJobQueue.vue index e19cbb185a..69912e21f7 100644 --- a/packages/frontend/src/widgets/WidgetJobQueue.vue +++ b/packages/frontend/src/widgets/WidgetJobQueue.vue @@ -46,13 +46,12 @@ </template> <script lang="ts" setup> -import { onMounted, onUnmounted, reactive, ref } from 'vue'; -import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; +import { onUnmounted, reactive } from 'vue'; +import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget'; import { GetFormResultType } from '@/scripts/form'; import { stream } from '@/stream'; import number from '@/filters/number'; import * as sound from '@/scripts/sound'; -import * as os from '@/os'; import { deepClone } from '@/scripts/clone'; const name = 'jobQueue'; diff --git a/packages/frontend/src/widgets/WidgetMemo.vue b/packages/frontend/src/widgets/WidgetMemo.vue index 689dbe0e6f..149d20af47 100644 --- a/packages/frontend/src/widgets/WidgetMemo.vue +++ b/packages/frontend/src/widgets/WidgetMemo.vue @@ -11,10 +11,9 @@ </template> <script lang="ts" setup> -import { onMounted, onUnmounted, reactive, ref, watch } from 'vue'; -import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; +import { ref, watch } from 'vue'; +import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget'; import { GetFormResultType } from '@/scripts/form'; -import * as os from '@/os'; import MkContainer from '@/components/MkContainer.vue'; import { defaultStore } from '@/store'; import { i18n } from '@/i18n'; diff --git a/packages/frontend/src/widgets/WidgetNotifications.vue b/packages/frontend/src/widgets/WidgetNotifications.vue index e4ae37950c..63400b09d0 100644 --- a/packages/frontend/src/widgets/WidgetNotifications.vue +++ b/packages/frontend/src/widgets/WidgetNotifications.vue @@ -12,7 +12,7 @@ <script lang="ts" setup> import { defineAsyncComponent } from 'vue'; -import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; +import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget'; import { GetFormResultType } from '@/scripts/form'; import MkContainer from '@/components/MkContainer.vue'; import XNotifications from '@/components/MkNotifications.vue'; diff --git a/packages/frontend/src/widgets/WidgetOnlineUsers.vue b/packages/frontend/src/widgets/WidgetOnlineUsers.vue index 705be1c8a7..a096cc8fe8 100644 --- a/packages/frontend/src/widgets/WidgetOnlineUsers.vue +++ b/packages/frontend/src/widgets/WidgetOnlineUsers.vue @@ -7,8 +7,8 @@ </template> <script lang="ts" setup> -import { onMounted, onUnmounted, ref } from 'vue'; -import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; +import { ref } from 'vue'; +import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget'; import { GetFormResultType } from '@/scripts/form'; import * as os from '@/os'; import { useInterval } from '@/scripts/use-interval'; diff --git a/packages/frontend/src/widgets/WidgetPhotos.vue b/packages/frontend/src/widgets/WidgetPhotos.vue index 2d6f0a5ec7..8746ababbb 100644 --- a/packages/frontend/src/widgets/WidgetPhotos.vue +++ b/packages/frontend/src/widgets/WidgetPhotos.vue @@ -17,8 +17,8 @@ </template> <script lang="ts" setup> -import { onMounted, onUnmounted, reactive, ref } from 'vue'; -import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; +import { onUnmounted, ref } from 'vue'; +import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget'; import { GetFormResultType } from '@/scripts/form'; import { stream } from '@/stream'; import { getStaticImageUrl } from '@/scripts/media-proxy'; diff --git a/packages/frontend/src/widgets/WidgetPostForm.vue b/packages/frontend/src/widgets/WidgetPostForm.vue index f8bebcbf96..9953bca65f 100644 --- a/packages/frontend/src/widgets/WidgetPostForm.vue +++ b/packages/frontend/src/widgets/WidgetPostForm.vue @@ -4,7 +4,7 @@ <script lang="ts" setup> import { } from 'vue'; -import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; +import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget'; import { GetFormResultType } from '@/scripts/form'; import MkPostForm from '@/components/MkPostForm.vue'; diff --git a/packages/frontend/src/widgets/WidgetProfile.vue b/packages/frontend/src/widgets/WidgetProfile.vue index 7e8ac429e5..819663a366 100644 --- a/packages/frontend/src/widgets/WidgetProfile.vue +++ b/packages/frontend/src/widgets/WidgetProfile.vue @@ -17,8 +17,7 @@ </template> <script lang="ts" setup> -import { onMounted, onUnmounted, Ref, ref, watch } from 'vue'; -import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; +import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget'; import { GetFormResultType } from '@/scripts/form'; import { $i } from '@/account'; import { userPage } from '@/filters/user'; diff --git a/packages/frontend/src/widgets/WidgetRss.vue b/packages/frontend/src/widgets/WidgetRss.vue index e212003f34..965bb89153 100644 --- a/packages/frontend/src/widgets/WidgetRss.vue +++ b/packages/frontend/src/widgets/WidgetRss.vue @@ -19,7 +19,7 @@ <script lang="ts" setup> import { ref, watch, computed } from 'vue'; -import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; +import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget'; import { GetFormResultType } from '@/scripts/form'; import MkContainer from '@/components/MkContainer.vue'; import { url as base } from '@/config'; diff --git a/packages/frontend/src/widgets/WidgetRssTicker.vue b/packages/frontend/src/widgets/WidgetRssTicker.vue index a7488f4ca2..b0408f0d7f 100644 --- a/packages/frontend/src/widgets/WidgetRssTicker.vue +++ b/packages/frontend/src/widgets/WidgetRssTicker.vue @@ -23,7 +23,7 @@ <script lang="ts" setup> import { ref, watch, computed } from 'vue'; -import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; +import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget'; import MarqueeText from '@/components/MkMarquee.vue'; import { GetFormResultType } from '@/scripts/form'; import MkContainer from '@/components/MkContainer.vue'; diff --git a/packages/frontend/src/widgets/WidgetSlideshow.vue b/packages/frontend/src/widgets/WidgetSlideshow.vue index c2ad23b311..ffb77b281a 100644 --- a/packages/frontend/src/widgets/WidgetSlideshow.vue +++ b/packages/frontend/src/widgets/WidgetSlideshow.vue @@ -12,8 +12,8 @@ </template> <script lang="ts" setup> -import { nextTick, onMounted, onUnmounted, reactive, ref, shallowRef } from 'vue'; -import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; +import { onMounted, ref, shallowRef } from 'vue'; +import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget'; import { GetFormResultType } from '@/scripts/form'; import * as os from '@/os'; import { useInterval } from '@/scripts/use-interval'; diff --git a/packages/frontend/src/widgets/WidgetTimeline.vue b/packages/frontend/src/widgets/WidgetTimeline.vue index e6abf561b9..d6be6532a6 100644 --- a/packages/frontend/src/widgets/WidgetTimeline.vue +++ b/packages/frontend/src/widgets/WidgetTimeline.vue @@ -16,19 +16,18 @@ </template> <div> - <XTimeline :key="widgetProps.src === 'list' ? `list:${widgetProps.list.id}` : widgetProps.src === 'antenna' ? `antenna:${widgetProps.antenna.id}` : widgetProps.src" :src="widgetProps.src" :list="widgetProps.list ? widgetProps.list.id : null" :antenna="widgetProps.antenna ? widgetProps.antenna.id : null"/> + <MkTimeline :key="widgetProps.src === 'list' ? `list:${widgetProps.list.id}` : widgetProps.src === 'antenna' ? `antenna:${widgetProps.antenna.id}` : widgetProps.src" :src="widgetProps.src" :list="widgetProps.list ? widgetProps.list.id : null" :antenna="widgetProps.antenna ? widgetProps.antenna.id : null"/> </div> </MkContainer> </template> <script lang="ts" setup> -import { onMounted, onUnmounted, reactive, ref, watch } from 'vue'; -import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; +import { ref } from 'vue'; +import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget'; import { GetFormResultType } from '@/scripts/form'; import * as os from '@/os'; import MkContainer from '@/components/MkContainer.vue'; -import XTimeline from '@/components/MkTimeline.vue'; -import { $i } from '@/account'; +import MkTimeline from '@/components/MkTimeline.vue'; import { i18n } from '@/i18n'; const name = 'timeline'; diff --git a/packages/frontend/src/widgets/WidgetTrends.vue b/packages/frontend/src/widgets/WidgetTrends.vue index 76651c1d0f..1423ae076c 100644 --- a/packages/frontend/src/widgets/WidgetTrends.vue +++ b/packages/frontend/src/widgets/WidgetTrends.vue @@ -19,8 +19,8 @@ </template> <script lang="ts" setup> -import { onMounted, onUnmounted, ref } from 'vue'; -import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; +import { ref } from 'vue'; +import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget'; import { GetFormResultType } from '@/scripts/form'; import MkContainer from '@/components/MkContainer.vue'; import MkMiniChart from '@/components/MkMiniChart.vue'; diff --git a/packages/frontend/src/widgets/WidgetUnixClock.vue b/packages/frontend/src/widgets/WidgetUnixClock.vue index cf85ac782c..22162d2b2c 100644 --- a/packages/frontend/src/widgets/WidgetUnixClock.vue +++ b/packages/frontend/src/widgets/WidgetUnixClock.vue @@ -12,7 +12,7 @@ <script lang="ts" setup> import { onUnmounted, ref, watch } from 'vue'; -import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; +import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget'; import { GetFormResultType } from '@/scripts/form'; const name = 'unixClock'; diff --git a/packages/frontend/src/widgets/WidgetUserList.vue b/packages/frontend/src/widgets/WidgetUserList.vue index cc49c92550..b8811d2fed 100644 --- a/packages/frontend/src/widgets/WidgetUserList.vue +++ b/packages/frontend/src/widgets/WidgetUserList.vue @@ -19,8 +19,7 @@ </template> <script lang="ts" setup> -import { onMounted, onUnmounted, ref } from 'vue'; -import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; +import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget'; import { GetFormResultType } from '@/scripts/form'; import MkContainer from '@/components/MkContainer.vue'; import * as os from '@/os'; diff --git a/packages/frontend/src/widgets/server-metric/index.vue b/packages/frontend/src/widgets/server-metric/index.vue index f79858db26..72c88d9a00 100644 --- a/packages/frontend/src/widgets/server-metric/index.vue +++ b/packages/frontend/src/widgets/server-metric/index.vue @@ -15,8 +15,8 @@ </template> <script lang="ts" setup> -import { onMounted, onUnmounted, ref } from 'vue'; -import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from '../widget'; +import { onUnmounted, ref } from 'vue'; +import { useWidgetPropsManager, Widget, WidgetComponentExpose } from '../widget'; import XCpuMemory from './cpu-mem.vue'; import XNet from './net.vue'; import XCpu from './cpu.vue'; diff --git a/packages/frontend/src/widgets/server-metric/mem.vue b/packages/frontend/src/widgets/server-metric/mem.vue index 6018eb4265..d6a5d021ad 100644 --- a/packages/frontend/src/widgets/server-metric/mem.vue +++ b/packages/frontend/src/widgets/server-metric/mem.vue @@ -2,7 +2,7 @@ <div class="zlxnikvl"> <XPie class="pie" :value="usage"/> <div> - <p><i class="fas fa-memory"></i>RAM</p> + <p><i class="ti ti-section"></i>RAM</p> <p>Total: {{ bytes(total, 1) }}</p> <p>Used: {{ bytes(used, 1) }}</p> <p>Free: {{ bytes(free, 1) }}</p> diff --git a/packages/frontend/vite.json5.ts b/packages/frontend/vite.json5.ts index 0a37fbff44..87b67c2142 100644 --- a/packages/frontend/vite.json5.ts +++ b/packages/frontend/vite.json5.ts @@ -5,6 +5,13 @@ 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'; @@ -28,9 +35,12 @@ export default function json5(options: RollupJsonOptions = {}): Plugin { map: { mappings: '' }, }; } catch (err) { - const message = 'Could not parse JSON file'; - const position = parseInt(/[\d]/.exec(err.message)[0], 10); - this.warn({ message, id, position }); + 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; } }, |