diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6c607eeea..e2946b162 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -157,7 +157,7 @@ e2e-pleroma: variables: PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: "1" FF_NETWORK_PER_BUILD: "true" - PLEROMA_IMAGE: git.pleroma.social:5050/pleroma/pleroma:stable + PLEROMA_IMAGE: git.pleroma.social/pleroma/pleroma:stable POSTGRES_USER: pleroma POSTGRES_PASSWORD: pleroma POSTGRES_DB: pleroma diff --git a/changelog.d/more-fixes.skip b/changelog.d/more-fixes.skip new file mode 100644 index 000000000..e69de29bb diff --git a/docker-compose.e2e.yml b/docker-compose.e2e.yml index c49984684..c740e69c4 100644 --- a/docker-compose.e2e.yml +++ b/docker-compose.e2e.yml @@ -12,7 +12,7 @@ services: retries: 30 pleroma: - image: ${PLEROMA_IMAGE:-git.pleroma.social:5050/pleroma/pleroma:stable} + image: ${PLEROMA_IMAGE:-git.pleroma.social/pleroma/pleroma:stable} environment: DB_USER: pleroma DB_PASS: pleroma diff --git a/src/App.js b/src/App.js index 7de7a0696..934b96713 100644 --- a/src/App.js +++ b/src/App.js @@ -4,6 +4,7 @@ import { defineAsyncComponent } from 'vue' import DesktopNav from 'src/components/desktop_nav/desktop_nav.vue' import FeaturesPanel from 'src/components/features_panel/features_panel.vue' +import GlobalError from 'src/components/global_error/global_error.vue' import GlobalNoticeList from 'src/components/global_notice_list/global_notice_list.vue' import InstanceSpecificPanel from 'src/components/instance_specific_panel/instance_specific_panel.vue' import MobileNav from 'src/components/mobile_nav/mobile_nav.vue' @@ -68,6 +69,7 @@ export default { () => import('src/components/status_history_modal/status_history_modal.vue'), ), + GlobalError, GlobalNoticeList, }, data: () => ({ diff --git a/src/App.scss b/src/App.scss index 9618a2ac4..0de5a3577 100644 --- a/src/App.scss +++ b/src/App.scss @@ -144,6 +144,25 @@ h4 { margin: 0; } +code { + background: var(--background); + border: 1px solid var(--border); + border-radius: var(--roundness); + padding: 0 0.2em; + + & pre, + pre & { + display: block; + overflow-x: auto; + padding: 0.2em; + } + + &.pre { + white-space: pre; + display: block; + } +} + .iconLetter { display: inline-block; text-align: center; diff --git a/src/App.vue b/src/App.vue index c1a7199d7..84a6f7d3d 100644 --- a/src/App.vue +++ b/src/App.vue @@ -73,6 +73,7 @@ + diff --git a/src/api/admin.js b/src/api/admin.js index 67fb5038b..c33f863a7 100644 --- a/src/api/admin.js +++ b/src/api/admin.js @@ -435,7 +435,7 @@ export const downloadRemoteEmojiPackZIP = ({ return promisedRequest({ url: EMOJI_PACKS_DL_REMOTE_ZIP_URL, method: 'POST', - payload: data, + formData: data, }) } @@ -460,7 +460,7 @@ export const addNewEmojiFile = ({ packName, file, shortcode, filename }) => { return promisedRequest({ url: EMOJI_UPDATE_FILE_URL(packName), method: 'POST', - payload: data, + formData: data, }) } diff --git a/src/api/chats.js b/src/api/chats.js index 51e83c74f..114038e52 100644 --- a/src/api/chats.js +++ b/src/api/chats.js @@ -4,7 +4,7 @@ import { parseChat } from 'src/services/entity_normalizer/entity_normalizer.serv const PLEROMA_CHATS_URL = '/api/v1/pleroma/chats' const PLEROMA_CHAT_URL = (id) => `/api/v1/pleroma/chats/by-account-id/${id}` -const PLEROMA_CHAT_MESSAGES_URL = (id, { maxId, sinceId, limit }) => +const PLEROMA_CHAT_MESSAGES_URL = (id, { maxId, sinceId, limit } = {}) => `/api/v1/pleroma/chats/${id}/messages${paramsString({ maxId, sinceId, limit })}` const PLEROMA_CHAT_READ_URL = (id) => `/api/v1/pleroma/chats/${id}/read` const PLEROMA_DELETE_CHAT_MESSAGE_URL = (chatId, messageId) => diff --git a/src/api/helpers.js b/src/api/helpers.js index 40b879232..4bf16e0cc 100644 --- a/src/api/helpers.js +++ b/src/api/helpers.js @@ -17,8 +17,6 @@ export const paramsString = (params = {}) => { } })() - if (entries.length === 0) return '' - const arrays = [] const nonArrays = [] @@ -48,6 +46,8 @@ export const paramsString = (params = {}) => { }) }) + if (nonArrays.length + arrays.length === 0) return '' + return ( '?' + [ @@ -71,6 +71,7 @@ export const promisedRequest = async ({ url, payload, formData, + cache, credentials, headers = {}, }) => { @@ -87,6 +88,10 @@ export const promisedRequest = async ({ options.headers['Content-Type'] = 'application/json' } + if (cache) { + options.cache = cache + } + if (formData || payload) { options.body = formData || JSON.stringify(payload) } diff --git a/src/api/user.js b/src/api/user.js index b9035da77..17e13195d 100644 --- a/src/api/user.js +++ b/src/api/user.js @@ -181,9 +181,6 @@ export const unmuteConversation = ({ id, credentials }) => }).then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) })) export const vote = ({ pollId, choices, credentials }) => { - const form = new FormData() - form.append('choices', choices) - return promisedRequest({ url: MASTODON_VOTE_URL(encodeURIComponent(pollId)), method: 'POST', diff --git a/src/boot/after_store.js b/src/boot/after_store.js index 0b4825ca0..4ccfa4d41 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -467,6 +467,16 @@ const afterStoreSetup = async ({ pinia, store, storageError, i18n }) => { // "Plugins are only applied to stores created after the plugins themselves, and after pinia is passed to the app, otherwise they won't be applied." app.use(pinia) + app.config.errorHandler = (error, instance, info) => { + console.error( + 'Global Vue Error Handler caught an error:', + error, + instance, + info, + ) + useInterfaceStore().setGlobalError({ error, instance, info }) + } + const waitForAllStoresToLoad = async () => { // the stores that do not persist technically do not need to be awaited here, // but that involves either hard-coding the stores in some place (prone to errors) diff --git a/src/boot/routes.js b/src/boot/routes.js index 9b00a8004..d50baab04 100644 --- a/src/boot/routes.js +++ b/src/boot/routes.js @@ -1,5 +1,6 @@ import { defineAsyncComponent } from 'vue' +import AuthForm from 'src/components/auth_form/auth_form.js' import BookmarkTimeline from 'src/components/bookmark_timeline/bookmark_timeline.vue' import BubbleTimeline from 'src/components/bubble_timeline/bubble_timeline.vue' import ConversationPage from 'src/components/conversation-page/conversation-page.vue' @@ -147,9 +148,7 @@ export default (store) => { { name: 'login', path: '/login', - component: defineAsyncComponent( - () => import('src/components/auth_form/auth_form.js'), - ), + component: AuthForm, }, { name: 'shout-panel', diff --git a/src/components/attachment/attachment.js b/src/components/attachment/attachment.js index d5bce12a7..f7d6ebf10 100644 --- a/src/components/attachment/attachment.js +++ b/src/components/attachment/attachment.js @@ -52,6 +52,7 @@ const Attachment = { 'shiftDn', 'edit', ], + emits: ['play', 'pause', 'naturalSizeLoad'], data() { return { localDescription: this.description || this.attachment.description, diff --git a/src/components/basic_user_card/basic_user_card.vue b/src/components/basic_user_card/basic_user_card.vue index 2de8b7b36..49786bf1b 100644 --- a/src/components/basic_user_card/basic_user_card.vue +++ b/src/components/basic_user_card/basic_user_card.vue @@ -54,7 +54,8 @@ diff --git a/src/components/gallery/gallery.js b/src/components/gallery/gallery.js index 252a32bf6..c244a8cb3 100644 --- a/src/components/gallery/gallery.js +++ b/src/components/gallery/gallery.js @@ -27,8 +27,10 @@ const Gallery = { return { sizes: {}, hidingLong: true, + playingMedia: new Set(), } }, + emits: ['play', 'pause'], components: { Attachment }, computed: { rows() { @@ -115,11 +117,21 @@ const Gallery = { return this.attachmentsDimensionalScore > 1 } }, + hasPlayingMedia() { + return this.playingMedia.size > 0 + }, }, methods: { onNaturalSizeLoad({ id, width, height }) { set(this.sizes, id, { width, height }) }, + onMediaStateChange(playing, id) { + if (playing) { + this.playingMedia.add(id) + } else { + this.playingMedia.delete(id) + } + }, rowStyle(row) { if (row.audio) { return { 'padding-bottom': '25%' } // fixed reduced height for audio @@ -146,6 +158,15 @@ const Gallery = { useMediaViewerStore().setMedia(this.attachments) }, }, + watch: { + hasPlayingMedia(newValue) { + if (newValue) { + this.$emit('play') + } else { + this.$emit('pause') + } + }, + }, } export default Gallery diff --git a/src/components/gallery/gallery.vue b/src/components/gallery/gallery.vue index 11637f3c2..a32d4feb7 100644 --- a/src/components/gallery/gallery.vue +++ b/src/components/gallery/gallery.vue @@ -34,6 +34,8 @@ :style="itemStyle(attachment.id, row.items)" @set-media="onMedia" @natural-size-load="onNaturalSizeLoad" + @play="() => onMediaStateChange(true, attachment.id)" + @pause="() => onMediaStateChange(false, attachment.id)" /> diff --git a/src/components/global_error/global_error.js b/src/components/global_error/global_error.js new file mode 100644 index 000000000..b4768b8ad --- /dev/null +++ b/src/components/global_error/global_error.js @@ -0,0 +1,57 @@ +import { mapActions, mapState } from 'pinia' +import { defineAsyncComponent } from 'vue' + +import { useInterfaceStore } from 'src/stores/interface.js' + +const GlobalError = { + components: { + ErrorModal: defineAsyncComponent( + () => import('src/components/error_modal/error_modal.vue'), + ), + }, + computed: { + title() { + if (this.globalError == null) return null + return this.globalError.title && this.$t(this.globalError.title) + }, + content() { + if (this.globalError == null) return null + if (this.globalError.content) { + return this.$t(this.globalError.content, [this.globalError.error]) + } else { + return null + } + }, + details() { + if (this.globalError == null) return null + if (this.globalError.error != null) { + return ( + this.globalError.error.toString() + + '\n\n' + + this.globalError.error.stack + ) + } else { + return this.globalError.details + } + }, + recoverText() { + if (this.globalError == null) return null + if (this.globalError.recoverText == null) return null + return this.$t(this.globalError.recoverText) + }, + ...mapState(useInterfaceStore, ['globalError']), + }, + methods: { + clear() { + this.globalError.clear?.() + this.clearGlobalError() + }, + recover() { + this.globalError.recover?.() + this.clearGlobalError() + }, + ...mapActions(useInterfaceStore, ['clearGlobalError']), + }, +} + +export default GlobalError diff --git a/src/components/global_error/global_error.vue b/src/components/global_error/global_error.vue new file mode 100644 index 000000000..2b6e83655 --- /dev/null +++ b/src/components/global_error/global_error.vue @@ -0,0 +1,26 @@ + + + diff --git a/src/components/lists_edit/lists_edit.vue b/src/components/lists_edit/lists_edit.vue index 713a40673..25b818deb 100644 --- a/src/components/lists_edit/lists_edit.vue +++ b/src/components/lists_edit/lists_edit.vue @@ -50,7 +50,7 @@
0 - if (scrollTop + clientHeight >= scrollHeight) { - this.$refs.notifications.fetchOlderNotifications() - } }, }, watch: { diff --git a/src/components/mobile_nav/mobile_nav.vue b/src/components/mobile_nav/mobile_nav.vue index 09009782a..743b7deb0 100644 --- a/src/components/mobile_nav/mobile_nav.vue +++ b/src/components/mobile_nav/mobile_nav.vue @@ -94,6 +94,7 @@ />
+
- + > + + +
{{ ' ' }}
+