diff --git a/.woodpecker/test.yaml b/.woodpecker/test.yaml index 9c3c24a53..89f6f2f93 100644 --- a/.woodpecker/test.yaml +++ b/.woodpecker/test.yaml @@ -45,20 +45,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/build/check-versions.mjs b/build/check-versions.mjs new file mode 100644 index 000000000..c22004b00 --- /dev/null +++ b/build/check-versions.mjs @@ -0,0 +1,41 @@ +import chalk from 'chalk' +import semver from 'semver' + +import packageConfig from '../package.json' with { type: 'json' } + +var versionRequirements = [ + { + name: 'node', + currentVersion: semver.clean(process.version), + versionRequirement: packageConfig.engines.node, + }, +] + +export default function () { + const warnings = [] + for (let i = 0; i < versionRequirements.length; i++) { + const mod = versionRequirements[i] + if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { + warnings.push( + mod.name + + ': ' + + chalk.red(mod.currentVersion) + + ' should be ' + + chalk.green(mod.versionRequirement), + ) + } + } + + if (warnings.length) { + console.warn( + chalk.yellow( + '\nTo use this template, you must update following to modules:\n', + ), + ) + for (let i = 0; i < warnings.length; i++) { + const warning = warnings[i] + console.warn(' ' + warning) + } + process.exit(1) + } +} diff --git a/build/commit_hash.js b/build/commit_hash.js index 8225817ee..c60355804 100644 --- a/build/commit_hash.js +++ b/build/commit_hash.js @@ -1,8 +1,8 @@ -import childProcess from 'node:child_process' +import childProcess from 'child_process' export const getCommitHash = () => { const subst = '$Format:%h$' - if (!/Format:/.exec(subst)) { + if (!subst.match(/Format:/)) { return subst } else { try { diff --git a/build/sw_plugin.js b/build/sw_plugin.js index e6be7a738..2f0a4819d 100644 --- a/build/sw_plugin.js +++ b/build/sw_plugin.js @@ -31,6 +31,7 @@ export const buildSwPlugin = ({ swSrc, swDest }) => { name: 'build-sw-plugin', enforce: 'post', configResolved(resolvedConfig) { + resolvedConfig config = { define: resolvedConfig.define, resolve: resolvedConfig.resolve, @@ -59,7 +60,7 @@ export const buildSwPlugin = ({ swSrc, swDest }) => { sequential: true, async handler(_, bundle) { const assets = Object.keys(bundle) - .filter((name) => !name.endsWith('.map')) + .filter((name) => !/\.map$/.test(name)) .map((name) => '/' + name) config.plugins.push({ diff --git a/build/update-emoji.js b/build/update-emoji.js index dd965cf66..4ff7e1de8 100644 --- a/build/update-emoji.js +++ b/build/update-emoji.js @@ -1,10 +1,10 @@ -import fs from 'node:fs' import emojis from '@kazvmoe-infra/unicode-emoji-json/data-by-group.json' with { type: 'json', } +import fs from 'fs' Object.keys(emojis).map((k) => { - emojis[k].forEach((e) => { + emojis[k].map((e) => { delete e.unicode_version delete e.emoji_version delete e.skin_tone_support_unicode_version @@ -12,15 +12,11 @@ Object.keys(emojis).map((k) => { }) const res = {} -Object.keys(emojis).forEach((k) => { - const groupId = k.replace('&', 'and').replaceAll(' ', '-').toLowerCase() +Object.keys(emojis).map((k) => { + const groupId = k.replace('&', 'and').replace(/ /g, '-').toLowerCase() res[groupId] = emojis[k] }) console.info('Updating emojis...') -try { - fs.writeFileSync('src/assets/emoji.json', JSON.stringify(res)) - console.info('Done.') -} catch (e) { - console.error('Failed updating emoji', e) -} +fs.writeFileSync('src/assets/emoji.json', JSON.stringify(res)) +console.info('Done.') diff --git a/docker/e2e/Dockerfile.e2e b/docker/e2e/Dockerfile.e2e index e0b93d83d..7e3fbfbf1 100644 --- a/docker/e2e/Dockerfile.e2e +++ b/docker/e2e/Dockerfile.e2e @@ -1,19 +1,15 @@ FROM mcr.microsoft.com/playwright:v1.61.0-jammy -RUN npm install -g yarn@1.22.22 - -RUN adduser --system playwright --group --shell=/bin/bash - -USER playwright - WORKDIR /app ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 +RUN npm install -g yarn@1.22.22 + COPY package.json yarn.lock ./ RUN yarn --frozen-lockfile -COPY --chown=playwright ./build ./src ./test ./public ./static ./yarn.lock ./package.json . +COPY . . ENV CI=1 diff --git a/index.html b/index.html index 346b9f06c..26eeee19b 100644 --- a/index.html +++ b/index.html @@ -31,7 +31,7 @@
- A cute mascot +
diff --git a/public/static/terms-of-service.html b/public/static/terms-of-service.html index 19db6408e..2b7bf7697 100644 --- a/public/static/terms-of-service.html +++ b/public/static/terms-of-service.html @@ -6,4 +6,4 @@ Pleroma install containing the real ToS for your instance.

See the Pleroma documentation for more information.


-Pleroma Logo + diff --git a/sonar-project.properties b/sonar-project.properties deleted file mode 100644 index 15030e3cc..000000000 --- a/sonar-project.properties +++ /dev/null @@ -1,3 +0,0 @@ -sonar.projectKey=Pleroma-FE -sonar.qualitygatesonar.host.url.wait=true -sonar.host.url=https://sonarqube.pleroma.dev diff --git a/src/App.scss b/src/App.scss index ccdf636bb..0de5a3577 100644 --- a/src/App.scss +++ b/src/App.scss @@ -1,11 +1,10 @@ +// stylelint-disable rscss/class-format +/* stylelint-disable no-descending-specificity */ @use "panel"; @import '@fortawesome/fontawesome-svg-core/styles.css'; @import '@kazvmoe-infra/pinch-zoom-element/dist/pinch-zoom.css'; -// stylelint-disable rscss/class-format -/* stylelint-disable no-descending-specificity */ - :root { --status-margin: 0.75em; --post-line-height: 1.4; @@ -935,6 +934,7 @@ option { #splash { pointer-events: none; + // transition: opacity 0.5s; #status { &.css-ok { diff --git a/src/api/admin.js b/src/api/admin.js index d4a9e8ff9..c33f863a7 100644 --- a/src/api/admin.js +++ b/src/api/admin.js @@ -39,7 +39,7 @@ const USERS_URL_LIST = ({ isAdmin && 'is_admin', isModerator && 'is_moderator', ] - .filter(Boolean) + .filter((x) => x) .join(',') return `/api/v1/pleroma/admin/users?page=${page}&page_size=${pageSize}&filters=${filters_str}&query=${query}&name=${name}&email=${email}` } @@ -236,7 +236,7 @@ export const changeStatusScope = ({ opts: { id, sensitive, visibility }, credentials, }) => { - const payload = {} + var payload = {} if (typeof sensitive !== 'undefined') { payload['sensitive'] = sensitive } diff --git a/src/api/helpers.js b/src/api/helpers.js index cb2f2bdd5..4bf16e0cc 100644 --- a/src/api/helpers.js +++ b/src/api/helpers.js @@ -6,12 +6,12 @@ export const paramsString = (params = {}) => { if (params == null || params === undefined) return '' if (typeof params !== 'object' || Array.isArray(params)) { - throw new TypeError('Params are not an object!') + throw new Error('Params are not an object!') } const entries = (() => { if (params instanceof Map) { - return [...params.entries()] + return params.entries() } else { return Object.entries(params) } @@ -26,7 +26,7 @@ export const paramsString = (params = {}) => { (typeof v === 'object' && !Array.isArray(v)) || typeof v === 'function' ) { - throw new TypeError('Param cannot be non-primitive!') + throw new Error('Param cannot be non-primitive!') } if (Array.isArray(v)) { arrays.push([k, v]) @@ -42,7 +42,7 @@ export const paramsString = (params = {}) => { typeof v === 'function' || typeof v === 'undefined' ) - throw new TypeError('Array param cannot contain non-primitives!') + throw new Error('Array param cannot contain non-primitives!') }) }) @@ -109,9 +109,7 @@ export const promisedRequest = async ({ .get('content-type') .split(';') .map((x) => x.toLowerCase().trim()) - const contentLength = Number.parseInt( - response.headers.get('content-length'), - ) + const contentLength = parseInt(response.headers.get('content-length')) if (contentLength === 0) return null switch (contentType) { diff --git a/src/api/public.js b/src/api/public.js index 78db90bfe..e001ba749 100644 --- a/src/api/public.js +++ b/src/api/public.js @@ -105,7 +105,7 @@ export const fetchUserByName = ({ name, credentials }) => }) .then(({ data }) => data.id) .catch((error) => { - if (error?.statusCode === 404) { + if (error && error.statusCode === 404) { // Either the backend does not support lookup endpoint, // or there is no user with such name. Fallback and treat name as id. return name diff --git a/src/api/user.js b/src/api/user.js index a94c10241..9a55a6b93 100644 --- a/src/api/user.js +++ b/src/api/user.js @@ -1,4 +1,4 @@ -import { last } from 'lodash' +import { concat, last } from 'lodash' import { paramsString, promisedRequest } from './helpers.js' import { fetchFriends, MASTODON_STATUS_URL } from './public.js' @@ -220,7 +220,7 @@ export const postStatus = ({ }) if (pollOptions.some((option) => option !== '')) { const normalizedPoll = { - expires_in: Number.parseInt(poll.expiresIn, 10), + expires_in: parseInt(poll.expiresIn, 10), multiple: poll.multiple, } Object.keys(normalizedPoll).forEach((key) => { @@ -278,7 +278,7 @@ export const editStatus = ({ if (pollOptions.some((option) => option !== '')) { const normalizedPoll = { - expires_in: Number.parseInt(poll.expiresIn, 10), + expires_in: parseInt(poll.expiresIn, 10), multiple: poll.multiple, } Object.keys(normalizedPoll).forEach((key) => { @@ -412,7 +412,7 @@ export const exportFriends = ({ id, credentials }) => { credentials, withRelationships: true, }) - friends = [...friends, ...users] + friends = concat(friends, users) if (users.length === 0) { more = false } @@ -613,7 +613,7 @@ export const listAliases = ({ credentials }) => method: 'GET', credentials, params: { - _cacheBooster: Date().now(), + _cacheBooster: new Date().getTime(), }, }) @@ -799,7 +799,7 @@ export const listBackups = ({ credentials }) => method: 'GET', credentials, params: { - _cacheBooster: Date().now(), + _cacheBooster: new Date().getTime(), }, }) diff --git a/src/api/websocket.js b/src/api/websocket.js index ae2e5358f..d952372e5 100644 --- a/src/api/websocket.js +++ b/src/api/websocket.js @@ -141,11 +141,13 @@ export const handleMastoWS = ( if (data.result === 'success') { console.debug('[WS] Successfully authenticated') onAuthenticated() - } else if (data.error === 'already_authenticated') { - onAuthenticated() } else { - console.error('[WS] Unable to authenticate:', data.error) - wsEvent.target.close() + if (data.error === 'already_authenticated') { + onAuthenticated() + } else { + console.error('[WS] Unable to authenticate:', data.error) + wsEvent.target.close() + } } } return null diff --git a/src/boot/after_store.js b/src/boot/after_store.js index 986586a80..4ccfa4d41 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -63,14 +63,14 @@ const parsedInitialResults = () => { const decodeUTF8Base64 = (data) => { const rawData = atob(data) - const array = Uint8Array.from([...rawData].map((char) => char.codePointAt(0))) + const array = Uint8Array.from([...rawData].map((char) => char.charCodeAt(0))) const text = new TextDecoder().decode(array) return text } const preloadFetch = async (request) => { const data = parsedInitialResults() - if (!data?.[request]) { + if (!data || !data[request]) { return window.fetch(request) } const decoded = decodeUTF8Base64(data[request]) @@ -170,9 +170,9 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => { let config = {} if (overrides.staticConfigPreference && env === 'development') { console.warn('OVERRIDING API CONFIG WITH STATIC CONFIG') - config = { ...apiConfig, ...staticConfig } + config = Object.assign({}, apiConfig, staticConfig) } else { - config = { ...staticConfig, ...apiConfig } + config = Object.assign({}, staticConfig, apiConfig) } Object.keys(INSTANCE_IDENTITY_DEFAULT_DEFINITIONS).forEach((source) => { @@ -353,19 +353,19 @@ const getNodeInfo = async ({ store }) => { const uploadLimits = metadata.uploadLimits useInstanceStore().set({ path: 'limits.uploadlimit', - value: Number.parseInt(uploadLimits.general), + value: parseInt(uploadLimits.general), }) useInstanceStore().set({ path: 'limits.avatarlimit', - value: Number.parseInt(uploadLimits.avatar), + value: parseInt(uploadLimits.avatar), }) useInstanceStore().set({ path: 'limits.backgroundlimit', - value: Number.parseInt(uploadLimits.background), + value: parseInt(uploadLimits.background), }) useInstanceStore().set({ path: 'limits.bannerlimit', - value: Number.parseInt(uploadLimits.banner), + value: parseInt(uploadLimits.banner), }) useInstanceStore().set({ path: 'limits.fieldsLimits', @@ -409,7 +409,7 @@ const getNodeInfo = async ({ store }) => { useInstanceCapabilitiesStore().set( 'tagPolicyAvailable', - federation.mrf_policies === undefined + typeof federation.mrf_policies === 'undefined' ? false : metadata.federation.mrf_policies.includes('TagPolicy'), ) @@ -420,7 +420,8 @@ const getNodeInfo = async ({ store }) => { }) useInstanceStore().set({ path: 'federating', - value: federation.enabled === undefined ? true : federation.enabled, + value: + typeof federation.enabled === 'undefined' ? true : federation.enabled, }) const accountActivationRequired = metadata.accountActivationRequired @@ -442,7 +443,7 @@ const getNodeInfo = async ({ store }) => { const setConfig = async ({ store }) => { // apiConfig, staticConfig const configInfos = await Promise.all([ - getBackendProvidedConfig(), + getBackendProvidedConfig({ store }), getStaticConfig(), ]) const apiConfig = configInfos[0] @@ -456,7 +457,7 @@ const checkOAuthToken = async ({ store }) => { if (oauth.userToken) { return store.dispatch('loginUser', oauth.userToken) } - return + return Promise.resolve() } const afterStoreSetup = async ({ pinia, store, storageError, i18n }) => { @@ -484,7 +485,7 @@ const afterStoreSetup = async ({ pinia, store, storageError, i18n }) => { if (process.env.NODE_ENV === 'development') { // do some checks to avoid common errors if (!Object.keys(allStores).length) { - throw new TypeError( + throw new Error( 'No stores are available. Check the code in src/boot/after_store.js', ) } @@ -494,7 +495,7 @@ const afterStoreSetup = async ({ pinia, store, storageError, i18n }) => { const isStoreName = (name) => name.startsWith('use') if (process.env.NODE_ENV === 'development') { if (Object.keys(mod).filter(isStoreName).length !== 1) { - throw new TypeError( + throw new Error( 'Each store file must export exactly one store as a named export. Check your code in src/stores/', ) } @@ -503,13 +504,13 @@ const afterStoreSetup = async ({ pinia, store, storageError, i18n }) => { if (storeFuncName && typeof mod[storeFuncName] === 'function') { const p = mod[storeFuncName]().$persistLoaded if (!(p instanceof Promise)) { - throw new TypeError( + throw new Error( `${name} store's $persistLoaded is not a Promise. The persist plugin is not applied.`, ) } await p } else { - throw new TypeError( + throw new Error( `Store module ${name} does not export a 'use...' function`, ) } @@ -517,15 +518,14 @@ const afterStoreSetup = async ({ pinia, store, storageError, i18n }) => { ) } - let newStorageError try { await waitForAllStoresToLoad() } catch (e) { console.error('Cannot load stores:', e) - newStorageError = e + storageError = e } - if (storageError || newStorageError) { + if (storageError) { useInterfaceStore().pushGlobalNotice({ messageKey: 'errors.storage_unavailable', level: 'error', @@ -547,7 +547,9 @@ const afterStoreSetup = async ({ pinia, store, storageError, i18n }) => { const overrides = window.___pleromafe_dev_overrides || {} const server = - overrides.target !== undefined ? overrides.target : window.location.origin + typeof overrides.target !== 'undefined' + ? overrides.target + : window.location.origin useInstanceStore().set({ path: 'server', value: server }) await setConfig({ store }) @@ -559,7 +561,7 @@ const afterStoreSetup = async ({ pinia, store, storageError, i18n }) => { }) } catch (e) { window.splashError(e) - throw e + return Promise.reject(e) } applyStyleConfig(useMergedConfigStore().mergedConfig, i18n.global) @@ -571,7 +573,7 @@ const afterStoreSetup = async ({ pinia, store, storageError, i18n }) => { getInstancePanel({ store }), getNodeInfo({ store }), getInstanceConfig({ store }), - ]) + ]).catch((e) => Promise.reject(e)) getTOS({ store }) getStickers({ store }) @@ -581,7 +583,7 @@ const afterStoreSetup = async ({ pinia, store, storageError, i18n }) => { routes: routes(store), scrollBehavior: (to, _from, savedPosition) => { if (to.matched.some((m) => m.meta.dontScroll)) { - return {} + return false } return savedPosition || { left: 0, top: 0 } }, diff --git a/src/components/chat_list_item/chat_list_item.js b/src/components/chat_list_item/chat_list_item.js index 8bb2afd93..c95895def 100644 --- a/src/components/chat_list_item/chat_list_item.js +++ b/src/components/chat_list_item/chat_list_item.js @@ -39,7 +39,7 @@ const ChatListItem = { messageForStatusContent() { const message = this.chat.lastMessage const messageEmojis = message ? message.emojis : [] - const isYou = message?.account_id === this.currentUser.id + const isYou = message && message.account_id === this.currentUser.id const content = message ? this.attachmentInfo || message.content : '' const messagePreview = isYou ? `${this.$t('chats.you')} ${content}` diff --git a/src/components/chat_message/chat_message.js b/src/components/chat_message/chat_message.js index aabdec31f..78be870b4 100644 --- a/src/components/chat_message/chat_message.js +++ b/src/components/chat_message/chat_message.js @@ -111,7 +111,7 @@ const ChatMessage = { const user = this.$store.getters.findUser( this.message.in_reply_to_user_id, ) - return user?.screen_name_ui + return user && user.screen_name_ui } }, replyProfileLink() { diff --git a/src/components/chat_message/chat_message.vue b/src/components/chat_message/chat_message.vue index 394cce4b7..b772a9b20 100644 --- a/src/components/chat_message/chat_message.vue +++ b/src/components/chat_message/chat_message.vue @@ -53,7 +53,7 @@ :user-screen-name="message.in_reply_to_screen_name" /> -