diff --git a/.woodpecker/build-package.yaml b/.woodpecker/build-package.yaml new file mode 100644 index 000000000..d06a267cd --- /dev/null +++ b/.woodpecker/build-package.yaml @@ -0,0 +1,36 @@ +when: + - event: push + branch: ${CI_REPO_DEFAULT_BRANCH} + - event: manual + branch: ${CI_REPO_DEFAULT_BRANCH} + +depends_on: + - test + - test-e2e + +labels: + platform: linux/amd64 + memory: 'high' + +steps: + build: + image: docker.io/node:20-alpine + commands: + - apk add --no-cache zip git + - yarn --frozen-lockfile + - yarn build + - if [ "${CI_PIPELINE_EVENT}" = "push" ] || [ "${CI_PIPELINE_EVENT}" = "manual" ]; then zip -9qr ${CI_REPO_DEFAULT_BRANCH}.zip dist/; fi + + upload-artifacts: + image: docker.io/woodpeckercommunity/plugin-gitea-package:0.5.0 + settings: + user: + from_secret: pleroma-ci-user + password: + from_secret: pleroma-ci-password + update: true + owner: 'pleroma' + package_name: 'pleroma-fe-builds' + package_version: ${CI_REPO_DEFAULT_BRANCH} + file_source: ./${CI_REPO_DEFAULT_BRANCH}.zip + file_name: latest.zip diff --git a/.woodpecker/build.yaml b/.woodpecker/build.yaml index af0bb98e3..d6bd36f3e 100644 --- a/.woodpecker/build.yaml +++ b/.woodpecker/build.yaml @@ -6,38 +6,12 @@ when: - event: manual branch: ${CI_REPO_DEFAULT_BRANCH} -depends_on: - - test - - test-e2e - labels: platform: linux/amd64 - memory: 'high' steps: build: image: docker.io/node:20-alpine commands: - - apk add --no-cache zip git - yarn --frozen-lockfile - yarn build - - if [ "${CI_PIPELINE_EVENT}" = "push" ] || [ "${CI_PIPELINE_EVENT}" = "manual" ]; then zip -9qr ${CI_REPO_DEFAULT_BRANCH}.zip dist/; fi - - upload-artifacts: - image: docker.io/woodpeckercommunity/plugin-gitea-package:0.5.0 - when: - - event: push - branch: ${CI_REPO_DEFAULT_BRANCH} - - event: manual - branch: ${CI_REPO_DEFAULT_BRANCH} - settings: - user: - from_secret: pleroma-ci-user - password: - from_secret: pleroma-ci-password - update: true - owner: 'pleroma' - package_name: 'pleroma-fe-builds' - package_version: ${CI_REPO_DEFAULT_BRANCH} - file_source: ./${CI_REPO_DEFAULT_BRANCH}.zip - file_name: latest.zip diff --git a/.woodpecker/code-analisys.yaml b/.woodpecker/code-analisys.yaml new file mode 100644 index 000000000..11ee853ee --- /dev/null +++ b/.woodpecker/code-analisys.yaml @@ -0,0 +1,45 @@ +when: + - event: push + branch: ${CI_REPO_DEFAULT_BRANCH} + - event: manual + branch: ${CI_REPO_DEFAULT_BRANCH} + +labels: + platform: linux/amd64 + memory: 'high' + +depends_on: + - build + - test + - test-e2e + +clone: + - name: git + image: woodpeckerci/plugin-git + settings: + depth: 0 + partial: false + +variables: + script_file_entrypoint: &script_file_entrypoint + - /bin/sh + - -c + - 'printf "%s" "$CI_SCRIPT" | base64 -d > /tmp/ci-script.sh && /bin/sh -xe /tmp/ci-script.sh' + +steps: + generate-coverage: + image: mcr.microsoft.com/playwright:v1.61.0-jammy + environment: + FF_NETWORK_PER_BUILD: "true" + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: "1" + entrypoint: *script_file_entrypoint + commands: + - yarn --frozen-lockfile + - yarn unit-ci-coverage + + code-analysis: + image: sonarsource/sonar-scanner-cli:11 + environment: + SONAR_TOKEN: + from_secret: sonarqube-token + entrypoint: sonar-scanner diff --git a/.woodpecker/test-e2e.yaml b/.woodpecker/test-e2e.yaml index 2a7d1511d..8f7e407f8 100644 --- a/.woodpecker/test-e2e.yaml +++ b/.woodpecker/test-e2e.yaml @@ -10,6 +10,10 @@ labels: platform: linux/amd64 memory: 'high' +depends_on: + - build + - test + variables: artifacts_uploader_settings: &artifacts_uploader_settings user: diff --git a/.woodpecker/test.yaml b/.woodpecker/test.yaml index 9c3c24a53..c3f2c3793 100644 --- a/.woodpecker/test.yaml +++ b/.woodpecker/test.yaml @@ -10,6 +10,9 @@ labels: platform: linux/amd64 memory: 'high' +depends_on: + - build + variables: artifacts_uploader_settings: &artifacts_uploader_settings user: @@ -45,20 +48,6 @@ steps: exit 1 fi - code-analysis: - image: sonarsource/sonar-scanner-cli:11 - when: - - event: push - branch: ${CI_REPO_DEFAULT_BRANCH} - - event: manual - branch: ${CI_REPO_DEFAULT_BRANCH} - environment: - SONAR_TOKEN: - from_secret: sonarqube-token - entrypoint: *script_file_entrypoint - commands: - - sonar-scanner - upload-artifacts: image: docker.io/woodpeckercommunity/plugin-gitea-package:0.5.0 when: diff --git a/README.md b/README.md index 16d32dcd2..a1dabba35 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,14 @@ -# Pleroma-FE +# Pleroma-FE > Highly-customizable frontend designed for Pleroma. ![screenshot](./image-1.png) +[![Pipeline Status](https://ci.pleroma.com/api/badges/2/status.svg)](https://ci.pleroma.com/repos/2) [![Coverage](https://sonarqube.pleroma.dev/api/project_badges/measure?project=Pleroma-FE&metric=coverage&token=sqb_f8a5ec836caede0e2c6c62a9bfe66e62a89e2b01)](https://sonarqube.pleroma.dev/dashboard?id=Pleroma-FE) [![Maintainability Rating](https://sonarqube.pleroma.dev/api/project_badges/measure?project=Pleroma-FE&metric=software_quality_maintainability_rating&token=sqb_f8a5ec836caede0e2c6c62a9bfe66e62a89e2b01)](https://sonarqube.pleroma.dev/dashboard?id=Pleroma-FE) [![Reliability Rating](https://sonarqube.pleroma.dev/api/project_badges/measure?project=Pleroma-FE&metric=software_quality_reliability_rating&token=sqb_f8a5ec836caede0e2c6c62a9bfe66e62a89e2b01)](https://sonarqube.pleroma.dev/dashboard?id=Pleroma-FE) [![Security Rating](https://sonarqube.pleroma.dev/api/project_badges/measure?project=Pleroma-FE&metric=software_quality_security_rating&token=sqb_f8a5ec836caede0e2c6c62a9bfe66e62a89e2b01)](https://sonarqube.pleroma.dev/dashboard?id=Pleroma-FE) Translation status + # For Translators -To translate Pleroma-FE, use our weblate server: https://translate.pleroma.social/. If you need to add your language it should be added as a json file in [src/i18n/](https://git.pleroma.social/pleroma/pleroma-fe/src/src/i18n/) folder and added in a list within [src/i18n/languages.js](https://git.pleroma.social/pleroma/pleroma-fe/src/src/i18n/languages.js). +To translate Pleroma-FE, use our weblate server: https://translate.pleroma.social/. If you need to add your language it should be added as a json file in [src/i18n/](https://git.pleroma.social/pleroma/pleroma-fe/src/src/i18n/) folder and added in a list within [src/i18n/languages.js](https://git.pleroma.social/pleroma/pleroma-fe/src/src/i18n/languages.js). Pleroma-FE will set your language by your browser locale, but you can change language in settings. @@ -32,9 +34,14 @@ yarn unit # For Contributors: +You can make local build proxy requests to specific instance with an environment variable `VITE_PROXY_TARGET`, generally it is a good idea to also set port for that specific proxy, if you intend on testing multiple instances, i.e.: + +```bash +yarn && PORT=8080 VITE_PROXY_TARGET=https://coolinstance.tld yarn dev +``` + You can create file `/config/local.json` (see [example](https://git.pleroma.social/pleroma/pleroma-fe/src/config/local.example.json)) to enable some convenience dev options: -* `target`: makes local dev server redirect to some existing instance's BE instead of local BE, useful for testing things in near-production environment and searching for real-life use-cases. * `staticConfigPreference`: makes FE's `/static/config.json` take preference of BE-served `/api/pleroma/frontend_configurations`. Only works in dev mode. FE Build process also leaves current commit hash in global variable `___pleromafe_commit_hash` so that you can easily see which pleroma-fe commit instance is running, also helps pinpointing which commit was used when FE was bundled into BE. diff --git a/changelog.d/avatar_upload.fix b/changelog.d/avatar_upload.fix new file mode 100644 index 000000000..b5dd9ee56 --- /dev/null +++ b/changelog.d/avatar_upload.fix @@ -0,0 +1 @@ +fix image cropper error preventing avatar upload diff --git a/changelog.d/third_column.fix b/changelog.d/third_column.fix new file mode 100644 index 000000000..191e914d5 --- /dev/null +++ b/changelog.d/third_column.fix @@ -0,0 +1 @@ +Fixed layout selector (third column/reverse) not working immideately upon change diff --git a/package.json b/package.json index f8bacaf15..50138e6a2 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ "dev": "node build/update-emoji.js && vite dev", "build": "node build/update-emoji.js && vite build", "unit": "node build/update-emoji.js && vitest --run --coverage", - "unit-ci": "node build/update-emoji.js && vitest --run --coverage --browser.headless", + "unit-ci": "node build/update-emoji.js && vitest --run --browser.headless", + "unit-ci-coverage": "node build/update-emoji.js && vitest --run --coverage --browser.headless", "unit:watch": "node build/update-emoji.js && vitest --coverage", "e2e:pw": "playwright test --config test/e2e-playwright/playwright.config.mjs", "e2e": "sh ./tools/e2e/run.sh", diff --git a/src/App.js b/src/App.js index 4ce6f1abc..d7a63bd8a 100644 --- a/src/App.js +++ b/src/App.js @@ -74,6 +74,8 @@ export default { }, data: () => ({ mobileActivePanel: 'timeline', + updateMobileState: null, + updateScrollState: null, }), provide() { return { @@ -211,31 +213,32 @@ export default { hideShoutbox() { return this.isChats || useMergedConfigStore().mergedConfig.hideShoutbox }, + thirdColumnMode() { + return this.mergedConfig.thirdColumnMode + }, + reverseSetting() { + return this.mergedConfig.sidebarRight + }, reverseLayout() { - const { thirdColumnMode, sidebarRight: reverseSetting } = - useMergedConfigStore().mergedConfig if (this.layoutType !== 'wide') { - return reverseSetting + return this.reverseSetting } else { - return thirdColumnMode === 'notifications' - ? reverseSetting - : !reverseSetting + return this.thirdColumnMode === 'notifications' + ? this.reverseSetting + : !this.reverseSetting } }, noSticky() { - return useMergedConfigStore().mergedConfig.disableStickyHeaders + return this.mergedConfig.disableStickyHeaders }, showScrollbars() { - return useMergedConfigStore().mergedConfig.showScrollbars + return this.mergedConfig.showScrollbars }, scrollParent() { return window /* this.$refs.appContentRef */ }, showInstanceSpecificPanel() { - return ( - this.instanceSpecificPanelPresent && - !useMergedConfigStore().mergedConfig.hideISP - ) + return this.instanceSpecificPanelPresent && !this.mergedConfig.hideISP }, ...mapState(useMergedConfigStore, ['mergedConfig']), ...mapState(useInterfaceStore, [ diff --git a/src/components/image_cropper/image_cropper.js b/src/components/image_cropper/image_cropper.js index b85ef6626..599c95caf 100644 --- a/src/components/image_cropper/image_cropper.js +++ b/src/components/image_cropper/image_cropper.js @@ -70,6 +70,9 @@ const ImageCropper = { ) }, onCropperSelectionChange(event) { + if (!this.$refs.cropperCanvas) { + return // Cropper sends even before component is fully initialized + } const cropperCanvas = this.$refs.cropperCanvas const cropperCanvasRect = cropperCanvas.getBoundingClientRect() const selection = event.detail diff --git a/src/components/notifications/notifications.js b/src/components/notifications/notifications.js index 393aef639..feb59857e 100644 --- a/src/components/notifications/notifications.js +++ b/src/components/notifications/notifications.js @@ -134,12 +134,11 @@ const Notifications = { return this.minimalMode || layoutType === 'mobile' }, teleportTarget() { - const { layoutType } = useInterfaceStore() const map = { wide: '#notifs-column', mobile: '#mobile-notifications', } - return map[layoutType] || '#notifs-sidebar' + return map[this.layoutType] || '#notifs-sidebar' }, popoversZLayer() { const { layoutType } = useInterfaceStore() @@ -162,6 +161,7 @@ const Notifications = { }, ...mapState(useAnnouncementsStore, ['unreadAnnouncementCount']), ...mapState(useChatsStore, ['unreadChatsCount']), + ...mapState(useInterfaceStore, ['layoutType']), }, mounted() { this.scrollerRef = this.$refs.root.closest('.column.-scrollable') diff --git a/src/components/poll/poll_form.js b/src/components/poll/poll_form.js index 5d0334a63..a9c915127 100644 --- a/src/components/poll/poll_form.js +++ b/src/components/poll/poll_form.js @@ -25,6 +25,10 @@ export default { }, emits: ['update:modelValue'], computed: { + // Model value + options() { + return pollFallback(this.modelValue, 'options') + }, pollType: { get() { return pollFallback(this.modelValue, 'pollType') @@ -36,14 +40,6 @@ export default { }) }, }, - options: { - get() { - return pollFallback(this.modelValue, 'options') - }, - set(newVal) { - this.$emit('update:modelValue', { ...this.modelValue, options: newVal }) - }, - }, expiryAmount: { get() { return pollFallback(this.modelValue, 'expiryAmount') @@ -66,6 +62,8 @@ export default { }) }, }, + + // Configuration pollLimits() { return useInstanceStore().limits.pollLimits }, @@ -116,19 +114,34 @@ export default { }, addOption() { if (this.options.length < this.maxOptions) { - this.options = [...this.options, ''] + this.$emit('update:modelValue', { + ...this.modelValue, + options: [...this.options, ''], + }) + return true } return false }, deleteOption(index) { if (this.options.length > 2) { - this.options.splice(index, 1) - this.options = this.options + const options = [...this.options] + options.splice(index, 1) + + this.$emit('update:modelValue', { + ...this.modelValue, + options, + }) } }, updateOption(index, value) { - this.options = this.options + const options = [...this.options] + options[index] = value + + this.$emit('update:modelValue', { + ...this.modelValue, + options, + }) }, convertExpiryToUnit(unit, amount) { // Note: we want seconds and not milliseconds diff --git a/src/components/poll/poll_form.vue b/src/components/poll/poll_form.vue index cb8a6f91f..b5db1314a 100644 --- a/src/components/poll/poll_form.vue +++ b/src/components/poll/poll_form.vue @@ -11,14 +11,15 @@