diff --git a/components/common/CommonInputImage.vue b/components/common/CommonInputImage.vue index 4a002ed2..947cb284 100644 --- a/components/common/CommonInputImage.vue +++ b/components/common/CommonInputImage.vue @@ -35,6 +35,8 @@ const previewImage = ref('') const imageSrc = computed(() => previewImage.value || defaultImage.value) const pickImage = async () => { + if (process.server) + return const image = await fileOpen({ description: 'Image', mimeTypes: props.allowedFileTypes, diff --git a/components/modal/ModalMediaPreviewCarousel.vue b/components/modal/ModalMediaPreviewCarousel.vue index 810185ac..af555b49 100644 --- a/components/modal/ModalMediaPreviewCarousel.vue +++ b/components/modal/ModalMediaPreviewCarousel.vue @@ -21,7 +21,7 @@ const { modelValue } = defineModel<{ const target = ref() const animateTimeout = useTimeout(10) -const reduceMotion = useReducedMotion() +const reduceMotion = process.server ? ref(false) : useReducedMotion() const canAnimate = computed(() => !reduceMotion.value && animateTimeout.value) diff --git a/components/tiptap/TiptapEmojiList.vue b/components/tiptap/TiptapEmojiList.vue index 0efcda9c..8d946899 100644 --- a/components/tiptap/TiptapEmojiList.vue +++ b/components/tiptap/TiptapEmojiList.vue @@ -12,6 +12,9 @@ const { items, command } = defineProps<{ }>() const emojis = computed(() => { + if (process.server) + return [] + return items.map((item: CustomEmoji | Emoji) => { if (isCustomEmoji(item)) { return { diff --git a/composables/masto/publish.ts b/composables/masto/publish.ts index c57ab458..b70daeba 100644 --- a/composables/masto/publish.ts +++ b/composables/masto/publish.ts @@ -150,6 +150,8 @@ export function useUploadMediaAttachment(draftRef: Ref) { } async function pickAttachments() { + if (process.server) + return const mimeTypes = currentInstance.value!.configuration?.mediaAttachments.supportedMimeTypes const files = await fileOpen({ description: 'Attachments', diff --git a/composables/tiptap.ts b/composables/tiptap.ts index 64c1d9da..a9a80dac 100644 --- a/composables/tiptap.ts +++ b/composables/tiptap.ts @@ -1,3 +1,4 @@ +import type { Editor } from '@tiptap/vue-3' import { Extension, useEditor } from '@tiptap/vue-3' import Placeholder from '@tiptap/extension-placeholder' import Document from '@tiptap/extension-document' @@ -27,6 +28,9 @@ export interface UseTiptapOptions { } export function useTiptap(options: UseTiptapOptions) { + if (process.server) + return { editor: ref() } + const { autofocus, content, diff --git a/composables/tiptap/suggestion.ts b/composables/tiptap/suggestion.ts index f359bfc2..f8b3bd4d 100644 --- a/composables/tiptap/suggestion.ts +++ b/composables/tiptap/suggestion.ts @@ -16,18 +16,20 @@ export { Emoji } export type CustomEmoji = (mastodon.v1.CustomEmoji & { custom: true }) export const isCustomEmoji = (emoji: CustomEmoji | Emoji): emoji is CustomEmoji => !!(emoji as CustomEmoji).custom -export const TiptapMentionSuggestion: Partial = { - pluginKey: new PluginKey('mention'), - char: '@', - async items({ query }) { - if (query.length === 0) - return [] +export const TiptapMentionSuggestion: Partial = process.server + ? {} + : { + pluginKey: new PluginKey('mention'), + char: '@', + async items({ query }) { + if (query.length === 0) + return [] - const results = await useMastoClient().v2.search({ q: query, type: 'accounts', limit: 25, resolve: true }) - return results.accounts - }, - render: createSuggestionRenderer(TiptapMentionList), -} + const results = await useMastoClient().v2.search({ q: query, type: 'accounts', limit: 25, resolve: true }) + return results.accounts + }, + render: createSuggestionRenderer(TiptapMentionList), + } export const TiptapHashtagSuggestion: Partial = { pluginKey: new PluginKey('hashtag'), @@ -52,7 +54,7 @@ export const TiptapEmojiSuggestion: Partial = { pluginKey: new PluginKey('emoji'), char: ':', async items({ query }): Promise<(CustomEmoji | Emoji)[]> { - if (query.length === 0) + if (process.server || query.length === 0) return [] if (currentCustomEmojis.value.emojis.length === 0) diff --git a/composables/users.ts b/composables/users.ts index ab69cf5f..48668220 100644 --- a/composables/users.ts +++ b/composables/users.ts @@ -19,7 +19,7 @@ import { useAsyncIDBKeyval } from '~/composables/idb' const mock = process.mock -const initializeUsers = async (): Promise | RemovableRef> => { +const initializeUsers = (): Promise | RemovableRef> | Ref | RemovableRef => { let defaultUsers = mock ? [mock.user] : [] // Backward compatibility with localStorage @@ -34,7 +34,7 @@ const initializeUsers = async (): Promise | RemovableRef(defaultUsers) - : await useAsyncIDBKeyval(STORAGE_KEY_USERS, defaultUsers, { deep: true }) + : useAsyncIDBKeyval(STORAGE_KEY_USERS, defaultUsers, { deep: true }) if (removeUsersOnLocalStorage) globalThis.localStorage.removeItem(STORAGE_KEY_USERS) @@ -42,7 +42,7 @@ const initializeUsers = async (): Promise | RemovableRef | RemovableRef : await initializeUsers() const nodes = useLocalStorage>(STORAGE_KEY_NODES, {}, { deep: true }) const currentUserHandle = useLocalStorage(STORAGE_KEY_CURRENT_USER_HANDLE, mock ? mock.user.account.id : '') export const instanceStorage = useLocalStorage>(STORAGE_KEY_SERVERS, mock ? mock.server : {}, { deep: true }) diff --git a/mocks/class.ts b/mocks/class.ts new file mode 100644 index 00000000..93caa691 --- /dev/null +++ b/mocks/class.ts @@ -0,0 +1,3 @@ +export default class SomeClass { + +} diff --git a/mocks/prosemirror.ts b/mocks/prosemirror.ts new file mode 100644 index 00000000..84e65835 --- /dev/null +++ b/mocks/prosemirror.ts @@ -0,0 +1,8 @@ +import proxy from 'unenv/runtime/mock/proxy' + +export const Plugin = proxy +export const PluginKey = proxy +export const Decoration = proxy +export const DecorationSet = proxy + +export { proxy as default } diff --git a/mocks/tiptap.ts b/mocks/tiptap.ts new file mode 100644 index 00000000..9872644b --- /dev/null +++ b/mocks/tiptap.ts @@ -0,0 +1,17 @@ +import proxy from 'unenv/runtime/mock/proxy' + +export const Extension = proxy +export const useEditor = proxy +export const EditorContent = proxy +export const NodeViewContent = proxy +export const NodeViewWrapper = proxy +export const nodeViewProps = proxy +export const Node = proxy +export const mergeAttributes = proxy +export const nodeInputRule = proxy +export const nodePasteRule = proxy +export const VueNodeViewRenderer = proxy +export const findChildren = proxy +export const VueRenderer = proxy + +export { proxy as default } diff --git a/nuxt.config.ts b/nuxt.config.ts index 1f22a4ec..abf34990 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -128,10 +128,10 @@ export default defineNuxtConfig({ }, }, }, - build: { - transpile: ['masto'], - }, nitro: { + alias: { + 'isomorphic-ws': 'unenv/runtime/mock/proxy', + }, esbuild: { options: { target: 'esnext', @@ -143,12 +143,37 @@ export default defineNuxtConfig({ ignore: ['/settings'], }, }, + sourcemap: !isDevelopment, hooks: { 'nitro:config': function (config) { const nuxt = useNuxt() config.virtual = config.virtual || {} config.virtual['#storage-config'] = `export const driver = ${JSON.stringify(nuxt.options.appConfig.storage.driver)}` }, + 'vite:extendConfig': function (config, { isServer }) { + if (isServer) { + const alias = config.resolve!.alias as Record + for (const dep of ['eventemitter3', 'isomorphic-ws']) + alias[dep] = resolve('./mocks/class') + for (const dep of ['shiki-es', 'fuse.js']) + alias[dep] = 'unenv/runtime/mock/proxy' + const resolver = createResolver(import.meta.url) + + config.plugins!.unshift({ + name: 'mock', + enforce: 'pre', + resolveId(id) { + if (id.match(/(^|\/)(@tiptap)\//)) + return resolver.resolve('./mocks/tiptap.ts') + if (id.match(/(^|\/)(prosemirror)/)) + return resolver.resolve('./mocks/prosemirror.ts') + }, + }) + + const noExternal = config.ssr!.noExternal as string[] + noExternal.push('masto', '@fnando/sparkline', 'vue-i18n', '@mastojs/ponyfills') + } + }, }, app: { keepalive: true, diff --git a/pages/settings/users/index.vue b/pages/settings/users/index.vue index 04df33a7..7d667b9d 100644 --- a/pages/settings/users/index.vue +++ b/pages/settings/users/index.vue @@ -12,6 +12,9 @@ useHeadFixed({ const loggedInUsers = useUsers() async function exportTokens() { + if (process.server) + return + if (!confirm('Please aware that the tokens represent the **full access** to your accounts, and should be treated as sensitive information. Are you sure you want to export the tokens?')) return @@ -28,6 +31,8 @@ async function exportTokens() { } async function importTokens() { + if (process.server) + return const file = await fileOpen({ description: 'Token File', mimeTypes: ['application/json'],