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.

+[](https://ci.pleroma.com/repos/2) [](https://sonarqube.pleroma.dev/dashboard?id=Pleroma-FE) [](https://sonarqube.pleroma.dev/dashboard?id=Pleroma-FE) [](https://sonarqube.pleroma.dev/dashboard?id=Pleroma-FE) [](https://sonarqube.pleroma.dev/dashboard?id=Pleroma-FE)
+
# 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 @@