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/build/commit_hash.js b/build/commit_hash.js index 8225817ee..2b13afb93 100644 --- a/build/commit_hash.js +++ b/build/commit_hash.js @@ -7,7 +7,9 @@ export const getCommitHash = () => { } else { try { return childProcess - .execSync('git rev-parse --short HEAD') + .execSync( + 'PATH=/usr/bin:/bin:/usr/local/bin:/sbin:/usr/sbin git rev-parse --short HEAD', + ) .toString() .trim() } catch (e) { diff --git a/build/emojis_plugin.js b/build/emojis_plugin.js index 9872f5331..b21c10048 100644 --- a/build/emojis_plugin.js +++ b/build/emojis_plugin.js @@ -35,7 +35,7 @@ const getAllAccessibleAnnotations = async (projectRoot) => { }), ) ) - .filter((k) => k) + .filter(Boolean) .join(',\n') return ` diff --git a/build/update-emoji.js b/build/update-emoji.js index dd965cf66..b2631ec6e 100644 --- a/build/update-emoji.js +++ b/build/update-emoji.js @@ -3,7 +3,7 @@ import emojis from '@kazvmoe-infra/unicode-emoji-json/data-by-group.json' with { type: 'json', } -Object.keys(emojis).map((k) => { +Object.keys(emojis).forEach((k) => { emojis[k].forEach((e) => { delete e.unicode_version delete e.emoji_version diff --git a/changelog.d/server-mute-render-modify.fix b/changelog.d/server-mute-render-modify.fix new file mode 100644 index 000000000..cb94bb826 --- /dev/null +++ b/changelog.d/server-mute-render-modify.fix @@ -0,0 +1 @@ +Fixed server-side domain mutes rendering/api calls diff --git a/changelog.d/theme2-font-cache.fix b/changelog.d/theme2-font-cache.fix new file mode 100644 index 000000000..d8782b467 --- /dev/null +++ b/changelog.d/theme2-font-cache.fix @@ -0,0 +1 @@ +Fix Theme 2 fonts falling back to serif after upgrade diff --git a/changelog.d/timeline_rendering_status_index_approx.fix b/changelog.d/timeline_rendering_status_index_approx.fix new file mode 100644 index 000000000..717464c58 --- /dev/null +++ b/changelog.d/timeline_rendering_status_index_approx.fix @@ -0,0 +1 @@ +Fixed status index approxmiation in timeline rendering for dynamically changing viewport geometries 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/sonar-project.properties b/sonar-project.properties index 15030e3cc..5fae237b8 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -1,3 +1,5 @@ sonar.projectKey=Pleroma-FE sonar.qualitygatesonar.host.url.wait=true sonar.host.url=https://sonarqube.pleroma.dev +sonar.sources=src,build,tools +sonar.tests=test diff --git a/src/api/admin.js b/src/api/admin.js index d4a9e8ff9..8bbee8b4f 100644 --- a/src/api/admin.js +++ b/src/api/admin.js @@ -237,10 +237,10 @@ export const changeStatusScope = ({ credentials, }) => { const payload = {} - if (typeof sensitive !== 'undefined') { + if (sensitive !== undefined) { payload['sensitive'] = sensitive } - if (typeof visibility !== 'undefined') { + if (visibility !== undefined) { payload['visibility'] = visibility } @@ -260,15 +260,15 @@ export const announcementToPayload = ({ }) => { const payload = { content } - if (typeof startsAt !== 'undefined') { + if (startsAt !== undefined) { payload.starts_at = startsAt ? new Date(startsAt).toISOString() : null } - if (typeof endsAt !== 'undefined') { + if (endsAt !== undefined) { payload.ends_at = endsAt ? new Date(endsAt).toISOString() : null } - if (typeof allDay !== 'undefined') { + if (allDay !== undefined) { payload.all_day = allDay } diff --git a/src/api/chats.js b/src/api/chats.js index 5d766dec4..b212380de 100644 --- a/src/api/chats.js +++ b/src/api/chats.js @@ -18,7 +18,7 @@ export const chats = ({ credentials }) => url: PLEROMA_CHATS_URL, credentials, }).then(({ data }) => ({ - data: data.map(parseChat).filter((c) => c), + data: data.map(parseChat).filter(Boolean), })) export const getOrCreateChat = ({ accountId, credentials }) => @@ -40,7 +40,7 @@ export const chatMessages = ({ method: 'GET', credentials, }).then(({ data }) => ({ - data: data.map(parseChatMessage).filter((c) => c), + data: data.map(parseChatMessage).filter(Boolean), })) } diff --git a/src/api/helpers.js b/src/api/helpers.js index b033d80b8..326275495 100644 --- a/src/api/helpers.js +++ b/src/api/helpers.js @@ -37,11 +37,7 @@ export const paramsString = (params = {}) => { arrays.forEach(([k, array]) => { array.forEach((v) => { - if ( - typeof v === 'object' || - typeof v === 'function' || - typeof v === 'undefined' - ) + if (typeof v === 'object' || typeof v === 'function' || v === undefined) throw new TypeError('Array param cannot contain non-primitives!') }) }) diff --git a/src/components/announcement/announcement.js b/src/components/announcement/announcement.js index ee427533f..6b45b2b90 100644 --- a/src/components/announcement/announcement.js +++ b/src/components/announcement/announcement.js @@ -29,9 +29,8 @@ const Announcement = { currentUser: (state) => state.users.currentUser, }), canEditAnnouncement() { - return ( - this.currentUser && - this.currentUser.privileges.has('announcements_manage_announcements') + return this.currentUser?.privileges.has( + 'announcements_manage_announcements', ) }, content() { diff --git a/src/components/announcements_page/announcements_page.js b/src/components/announcements_page/announcements_page.js index 0f7933e5f..802e90cf6 100644 --- a/src/components/announcements_page/announcements_page.js +++ b/src/components/announcements_page/announcements_page.js @@ -33,9 +33,8 @@ const AnnouncementsPage = { return useAnnouncementsStore().announcements }, canPostAnnouncement() { - return ( - this.currentUser && - this.currentUser.privileges.has('announcements_manage_announcements') + return this.currentUser?.privileges.has( + 'announcements_manage_announcements', ) }, }, diff --git a/src/components/attachment/attachment.js b/src/components/attachment/attachment.js index f7d6ebf10..6556b07a0 100644 --- a/src/components/attachment/attachment.js +++ b/src/components/attachment/attachment.js @@ -165,16 +165,16 @@ const Attachment = { useMediaViewerStore().setCurrentMedia(this.attachment) }, onEdit(event) { - this.edit && this.edit(this.attachment, event) + this.edit?.(this.attachment, event) }, onRemove() { - this.remove && this.remove(this.attachment) + this.remove?.(this.attachment) }, onShiftUp() { - this.shiftUp && this.shiftUp(this.attachment) + this.shiftUp?.(this.attachment) }, onShiftDn() { - this.shiftDn && this.shiftDn(this.attachment) + this.shiftDn?.(this.attachment) }, stopFlash() { this.$refs.flash.closePlayer() diff --git a/src/components/chat_message/chat_message.vue b/src/components/chat_message/chat_message.vue index 394cce4b7..88ab2fc0a 100644 --- a/src/components/chat_message/chat_message.vue +++ b/src/components/chat_message/chat_message.vue @@ -1,9 +1,9 @@ - + {{ $t('status.broken_reply') }} @@ -83,7 +86,10 @@ :user="author" /> -
+
@@ -98,7 +104,6 @@ @mouseenter="hovered = true" @mouseleave="hovered = false" > -
- +
@@ -232,7 +240,10 @@ v-else class="chat-message-date-separator" > - +
diff --git a/src/components/chat_message_list/chat_message_list.vue b/src/components/chat_message_list/chat_message_list.vue index 5cdbf6871..7833730e7 100644 --- a/src/components/chat_message_list/chat_message_list.vue +++ b/src/components/chat_message_list/chat_message_list.vue @@ -7,7 +7,7 @@ :previous-item="getPreviousItem(index)" :hovered-message-chain="chatItem.messageChainId === hoveredMessageChainId" :focused="chatItem.id === focusedId" - :repliedTo="chatItem.id === repliedId" + :replied-to="chatItem.id === repliedId" @hover="onMessageHover" @delete="onMessageDelete" @reply-requested="onReplyRequested" diff --git a/src/components/chat_view/chat_view.js b/src/components/chat_view/chat_view.js index f48b75c65..16b2ae1d7 100644 --- a/src/components/chat_view/chat_view.js +++ b/src/components/chat_view/chat_view.js @@ -97,7 +97,7 @@ const Chat = { mounted() { window.addEventListener('resize', this.handleResize) window.addEventListener('scroll', this.handleScroll) - if (typeof document.hidden !== 'undefined') { + if (document.hidden !== undefined) { document.addEventListener( 'visibilitychange', this.handleVisibilityChange, @@ -112,7 +112,7 @@ const Chat = { unmounted() { window.removeEventListener('scroll', this.handleScroll) window.removeEventListener('resize', this.handleResize) - if (typeof document.hidden !== 'undefined') + if (document.hidden !== undefined) document.removeEventListener( 'visibilitychange', this.handleVisibilityChange, diff --git a/src/components/chat_view/chat_view.vue b/src/components/chat_view/chat_view.vue index e6bef0f84..06862ab3c 100644 --- a/src/components/chat_view/chat_view.vue +++ b/src/components/chat_view/chat_view.vue @@ -24,7 +24,7 @@ v-if="messages[0]?.summary_raw_html" :html="messages[0].summary_raw_html" :emoji="messages[0].emojis" - /> + /> diff --git a/src/components/color_input/color_input.vue b/src/components/color_input/color_input.vue index d6667c28f..53396f532 100644 --- a/src/components/color_input/color_input.vue +++ b/src/components/color_input/color_input.vue @@ -11,11 +11,11 @@ {{ label }}
{ } else { conversation = filter(conversation, (status) => status.type !== 'retweet') } - return conversation.filter((_) => _).sort(sortById) + return conversation.filter(Boolean).sort(sortById) } const conversation = { @@ -239,9 +239,9 @@ const conversation = { depth, }, walk(forest, forest[id], depth + 1, processed), - ].reduce((a, b) => a.concat(b), []) + ].flat() }) - .reduce((a, b) => a.concat(b), []) + .flat() const linearized = walk( threads.forest, @@ -307,9 +307,9 @@ const conversation = { (tl, cur) => tl.filter( (k) => - this.getReplies(cur.id) + !this.getReplies(cur.id) .map((v) => v.id) - .indexOf(k.id) === -1, + .includes(k.id), ), this.conversation, ) @@ -375,7 +375,7 @@ const conversation = { return !!(this.expanded || this.isPage) }, hiddenStyle() { - const height = (this.status && this.status.virtualHeight) || '120px' + const height = this.status?.virtualHeight || '120px' return this.virtualHidden ? { height } : {} }, threadDisplayStatus() { diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue index 170ab41d6..0833c4f89 100644 --- a/src/components/conversation/conversation.vue +++ b/src/components/conversation/conversation.vue @@ -14,9 +14,9 @@ v-if="conversation[0]?.summary_raw_html" :html="conversation[0].summary_raw_html" :emoji="conversation[0].emojis" - /> + />