diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 34af3774..00000000 --- a/.eslintignore +++ /dev/null @@ -1,2 +0,0 @@ -build/*.js -config/*.js diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 28d39d03..00000000 --- a/.eslintrc.js +++ /dev/null @@ -1,30 +0,0 @@ -module.exports = { - root: true, - parserOptions: { - parser: '@babel/eslint-parser', - sourceType: 'module' - }, - // https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style - extends: [ - 'plugin:vue/recommended' - ], - // required to lint *.vue files - plugins: [ - 'vue', - 'import' - ], - // add your custom rules here - rules: { - // allow paren-less arrow functions - 'arrow-parens': 0, - // allow async-await - 'generator-star-spacing': 0, - // allow debugger during development - 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0, - 'vue/require-prop-types': 0, - 'vue/no-unused-vars': 0, - 'no-tabs': 0, - 'vue/multi-word-component-names': 0, - 'vue/no-reserved-component-names': 0 - } -} diff --git a/.node-version b/.node-version deleted file mode 100644 index b26a34e4..00000000 --- a/.node-version +++ /dev/null @@ -1 +0,0 @@ -7.2.1 diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 00000000..958fb369 --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +nodejs 20.12.2 diff --git a/.woodpecker.yml b/.woodpecker.yml index 723bfb7c..4baa941b 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -1,24 +1,25 @@ -platform: linux/amd64 -pipeline: +labels: + platform: linux/amd64 + +steps: lint: when: event: - pull_request - image: node:18 + image: node:20 commands: - yarn - yarn lint - #- yarn stylelint test: when: event: - pull_request - image: node:18 + image: node:20 commands: - apt update - apt install firefox-esr -y --no-install-recommends - - yarn + - yarn - yarn unit build: @@ -28,7 +29,7 @@ pipeline: branch: - develop - stable - image: node:18 + image: node:20 commands: - yarn - yarn build @@ -40,15 +41,15 @@ pipeline: branch: - develop - stable - image: node:18 + image: node:20 secrets: - SCW_ACCESS_KEY - SCW_SECRET_KEY - SCW_DEFAULT_ORGANIZATION_ID commands: - apt-get update && apt-get install -y rclone wget zip - - wget https://github.com/scaleway/scaleway-cli/releases/download/v2.5.1/scaleway-cli_2.5.1_linux_amd64 - - mv scaleway-cli_2.5.1_linux_amd64 scaleway-cli + - wget https://github.com/scaleway/scaleway-cli/releases/download/v2.30.0/scaleway-cli_2.30.0_linux_amd64 + - mv scaleway-cli_2.30.0_linux_amd64 scaleway-cli - chmod +x scaleway-cli - ./scaleway-cli object config install type=rclone - zip akkoma-fe.zip -r dist @@ -70,8 +71,8 @@ pipeline: - SCW_DEFAULT_ORGANIZATION_ID commands: - apt-get update && apt-get install -y rclone wget git zip - - wget https://github.com/scaleway/scaleway-cli/releases/download/v2.5.1/scaleway-cli_2.5.1_linux_amd64 - - mv scaleway-cli_2.5.1_linux_amd64 scaleway-cli + - wget https://github.com/scaleway/scaleway-cli/releases/download/v2.30.0/scaleway-cli_2.30.0_linux_amd64 + - mv scaleway-cli_2.30.0_linux_amd64 scaleway-cli - chmod +x scaleway-cli - ./scaleway-cli object config install type=rclone - cd docs @@ -79,4 +80,4 @@ pipeline: - mkdocs build - zip -r docs.zip site/* - cd site - - rclone copy . scaleway:akkoma-docs/frontend/$CI_COMMIT_BRANCH/ + - rclone copy . scaleway:akkoma-docs/frontend/$CI_COMMIT_BRANCH/ diff --git a/README.md b/README.md index fc8db83c..e7ebc45a 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,8 @@ To use Akkoma-FE in Akkoma, use the [frontend](https://docs.akkoma.dev/stable/ad ## Build Setup +Make sure you have [Node.js](https://nodejs.org/) installed. You can check `/.woodpecker.yml` for which node version the Akkoma CI currently uses. + ``` bash # install dependencies corepack enable diff --git a/build/build.js b/build/build.js index b3c9aad4..bca7ead3 100644 --- a/build/build.js +++ b/build/build.js @@ -1,36 +1,36 @@ // https://github.com/shelljs/shelljs -require('./check-versions')() -require('shelljs/global') -env.NODE_ENV = 'production' +require("./check-versions")(); +require("shelljs/global"); +env.NODE_ENV = "production"; -var path = require('path') -var config = require('../config') -var ora = require('ora') -var webpack = require('webpack') -var webpackConfig = require('./webpack.prod.conf') +var path = require("path"); +var config = require("../config"); +var webpack = require("webpack"); +var webpackConfig = require("./webpack.prod.conf"); console.log( - ' Tip:\n' + - ' Built files are meant to be served over an HTTP server.\n' + - ' Opening index.html over file:// won\'t work.\n' -) + " Tip:\n" + + " Built files are meant to be served over an HTTP server.\n" + + " Opening index.html over file:// won't work.\n", +); -var spinner = ora('building for production...') -spinner.start() - -var assetsPath = path.join(config.build.assetsRoot, config.build.assetsSubDirectory) -rm('-rf', assetsPath) -mkdir('-p', assetsPath) -cp('-R', 'static/*', assetsPath) +var assetsPath = path.join( + config.build.assetsRoot, + config.build.assetsSubDirectory, +); +rm("-rf", assetsPath); +mkdir("-p", assetsPath); +cp("-R", "static/*", assetsPath); webpack(webpackConfig, function (err, stats) { - spinner.stop() - if (err) throw err - process.stdout.write(stats.toString({ - colors: true, - modules: false, - children: false, - chunks: false, - chunkModules: false - }) + '\n') -}) + if (err) throw err; + process.stdout.write( + stats.toString({ + colors: true, + modules: false, + children: false, + chunks: false, + chunkModules: false, + }) + "\n", + ); +}); diff --git a/build/dev-server.js b/build/dev-server.js index 5acd0fed..7fd95778 100644 --- a/build/dev-server.js +++ b/build/dev-server.js @@ -5,7 +5,7 @@ var path = require('path') var express = require('express') var webpack = require('webpack') var opn = require('opn') -var proxyMiddleware = require('http-proxy-middleware') +const { createProxyMiddleware } = require('http-proxy-middleware'); var webpackConfig = process.env.NODE_ENV === 'testing' ? require('./webpack.prod.conf') : require('./webpack.dev.conf') @@ -36,7 +36,13 @@ Object.keys(proxyTable).forEach(function (context) { if (typeof options === 'string') { options = { target: options } } - app.use(proxyMiddleware(context, options)) + const targetUrl = new URL(options.target); + // add path + targetUrl.pathname = context; + options.target = targetUrl.toString(); + + console.log("Proxying", context, "to", options.target); + app.use(context, createProxyMiddleware(options)) }) // handle fallback for HTML5 history API diff --git a/build/webpack.base.conf.js b/build/webpack.base.conf.js index 2a3db96e..bd4c722c 100644 --- a/build/webpack.base.conf.js +++ b/build/webpack.base.conf.js @@ -3,6 +3,7 @@ var config = require('../config') var utils = require('./utils') var projectRoot = path.resolve(__dirname, '../') var { VueLoaderPlugin } = require('vue-loader') +const ESLintPlugin = require('eslint-webpack-plugin'); var env = process.env.NODE_ENV // check env & config/index.js to decide weither to enable CSS Sourcemaps for the @@ -35,6 +36,7 @@ module.exports = { ], fallback: { "url": require.resolve("url/"), + querystring: require.resolve("querystring-es3") }, alias: { 'static': path.resolve(__dirname, '../static'), @@ -47,20 +49,6 @@ module.exports = { module: { noParse: /node_modules\/localforage\/dist\/localforage.js/, rules: [ - { - enforce: 'pre', - test: /\.(js|vue)$/, - include: projectRoot, - exclude: /node_modules/, - use: { - loader: 'eslint-loader', - options: { - formatter: require('eslint-friendly-formatter'), - sourceMap: config.build.productionSourceMap, - extract: true - } - } - }, { enforce: 'post', test: /\.(json5?|ya?ml)$/, // target json, json5, yaml and yml files @@ -118,6 +106,9 @@ module.exports = { ] }, plugins: [ - new VueLoaderPlugin() + new VueLoaderPlugin(), + new ESLintPlugin({ + configType: 'flat' + }) ] } diff --git a/config/test.env.js b/config/test.env.js index 646a6ef8..8643fbd6 100644 --- a/config/test.env.js +++ b/config/test.env.js @@ -2,5 +2,4 @@ var { merge } = require('webpack-merge') var devEnv = require('./dev.env') module.exports = merge(devEnv, { - NODE_ENV: '"testing"' }) diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 00000000..13bac794 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,31 @@ +const pluginVue = require('eslint-plugin-vue') +const pluginImport = require('eslint-plugin-import') + +module.exports = [ + ...pluginVue.configs['flat/recommended'], + { + languageOptions: { + parserOptions: { + parser: '@babel/eslint-parser', + sourceType: 'module' + } + }, + rules: { + // allow paren-less arrow functions + 'arrow-parens': 0, + // allow async-await + 'generator-star-spacing': 0, + // allow debugger during development + 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0, + 'vue/require-prop-types': 0, + 'vue/no-unused-vars': 0, + 'no-tabs': 0, + 'vue/multi-word-component-names': 0, + 'vue/no-reserved-component-names': 0 + }, + ignores: [ + 'build/*.js', + 'config/*.js' + ] + } +] diff --git a/package.json b/package.json index 51ee4830..e812bac0 100644 --- a/package.json +++ b/package.json @@ -12,120 +12,118 @@ "e2e": "node test/e2e/runner.js", "test": "npm run unit && npm run e2e", "stylelint": "stylelint src/**/*.scss", - "lint": "eslint --ext .js,.vue src test/unit/specs test/e2e/specs", - "lint-fix": "eslint --fix --ext .js,.vue src test/unit/specs test/e2e/specs" + "lint": "eslint src test/unit/specs test/e2e/specs", + "lint-fix": "eslint --fix src test/unit/specs test/e2e/specs" }, "dependencies": { "@babel/runtime": "7.17.8", - "@chenfengyuan/vue-qrcode": "2.0.0", + "@chenfengyuan/vue-qrcode": "^2.0.0", "@floatingghost/pinch-zoom-element": "^1.3.1", - "@fortawesome/fontawesome-svg-core": "1.3.0", - "@fortawesome/free-regular-svg-icons": "^6.1.2", - "@fortawesome/free-solid-svg-icons": "^6.2.0", - "@fortawesome/vue-fontawesome": "3.0.1", - "@vuelidate/core": "^2.0.0", - "@vuelidate/validators": "^2.0.0", - "blurhash": "^2.0.4", - "body-scroll-lock": "2.7.1", - "chromatism": "3.0.0", - "click-outside-vue3": "4.0.1", - "cropperjs": "1.5.12", - "diff": "3.5.0", - "escape-html": "1.0.3", + "@fortawesome/fontawesome-svg-core": "^6.5.2", + "@fortawesome/free-regular-svg-icons": "^6.5.2", + "@fortawesome/free-solid-svg-icons": "^6.5.2", + "@fortawesome/vue-fontawesome": "^3.0.8", + "@vuelidate/core": "^2.0.3", + "@vuelidate/validators": "^2.0.4", + "blurhash": "^2.0.5", + "body-scroll-lock": "^3.1.5", + "chromatism": "^3.0.0", + "click-outside-vue3": "^4.0.1", + "cropperjs": "^1.6.2", + "diff": "^5.2.0", + "escape-html": "^1.0.3", "iso-639-1": "^2.1.15", "js-cookie": "^3.0.1", - "localforage": "1.10.0", + "localforage": "^1.10.0", "parse-link-header": "^2.0.0", - "phoenix": "1.6.2", - "punycode.js": "2.1.0", - "qrcode": "1", - "url": "^0.11.0", - "vue": "^3.2.31", - "vue-i18n": "^9.2.2", - "vue-router": "4.0.14", - "vue-template-compiler": "2.6.11", - "vuex": "4.0.2" + "phoenix": "^1.7.12", + "punycode.js": "^2.3.1", + "qrcode": "^1.5.3", + "querystring-es3": "^0.2.1", + "url": "^0.11.3", + "vue": "^3.4.38", + "vue-i18n": "^9.14.0", + "vue-router": "^4.4.3", + "vue-template-compiler": "^2.7.16", + "vuex": "^4.1.0" }, "devDependencies": { - "@babel/core": "7.17.8", + "@babel/core": "^7.24.6", "@babel/eslint-parser": "^7.19.1", - "@babel/plugin-transform-runtime": "7.17.0", - "@babel/preset-env": "7.16.11", - "@babel/register": "7.17.7", + "@babel/plugin-transform-runtime": "^7.24.6", + "@babel/preset-env": "^7.24.6", + "@babel/register": "^7.24.6", "@intlify/vue-i18n-loader": "^5.0.0", - "@ungap/event-target": "0.2.3", - "@vue/babel-helper-vue-jsx-merge-props": "1.2.1", - "@vue/babel-plugin-jsx": "1.1.1", + "@ungap/event-target": "^0.2.4", + "@vue/babel-helper-vue-jsx-merge-props": "^1.4.0", + "@vue/babel-plugin-jsx": "^1.2.2", "@vue/compiler-sfc": "^3.1.0", "@vue/test-utils": "^2.0.2", - "autoprefixer": "6.7.7", + "autoprefixer": "^10.4.19", "babel-loader": "^9.1.0", - "babel-plugin-lodash": "3.3.4", + "babel-plugin-lodash": "^3.3.4", "chai": "^4.3.7", - "chalk": "1.1.3", - "chromedriver": "^107.0.3", + "chalk": "^1.1.3", + "chromedriver": "^119.0.1", "connect-history-api-fallback": "^2.0.0", "cross-spawn": "^7.0.3", - "css-loader": "^6.7.2", + "css-loader": "^7.1.2", "custom-event-polyfill": "^1.0.7", - "eslint": "^7.32.0", - "eslint-config-standard": "^17.0.0", + "eslint": "^9.3.0", + "eslint-config-standard": "^17.1.0", "eslint-friendly-formatter": "^4.0.1", - "eslint-loader": "^4.0.2", - "eslint-plugin-import": "^2.26.0", + "eslint-plugin-import": "^2.29.1", "eslint-plugin-node": "^11.1.0", - "eslint-plugin-promise": "^6.1.1", + "eslint-plugin-promise": "^6.2.0", "eslint-plugin-standard": "^5.0.0", - "eslint-plugin-vue": "^9.7.0", - "eventsource-polyfill": "0.9.6", - "express": "4.17.3", + "eslint-plugin-vue": "^9.26.0", + "eslint-webpack-plugin": "^4.2.0", + "eventsource-polyfill": "^0.9.6", + "express": "^4.19.2", "file-loader": "^6.2.0", - "function-bind": "1.1.1", + "function-bind": "^1.1.2", "html-webpack-plugin": "^5.5.0", - "http-proxy-middleware": "0.21.0", - "inject-loader": "2.0.1", - "isparta-loader": "2.0.0", - "json-loader": "0.5.7", - "karma": "6.3.17", - "karma-coverage": "1.1.2", - "karma-firefox-launcher": "1.3.0", - "karma-mocha": "2.0.1", - "karma-mocha-reporter": "2.2.5", - "karma-sinon-chai": "2.0.2", - "karma-sourcemap-loader": "0.3.8", - "karma-spec-reporter": "0.0.33", + "http-proxy-middleware": "^3.0.0", + "json-loader": "^0.5.7", + "karma": "^6.4.3", + "karma-coverage": "^2.2.1", + "karma-firefox-launcher": "^2.1.3", + "karma-mocha": "^2.0.1", + "karma-mocha-reporter": "^2.2.5", + "karma-sinon-chai": "^2.0.2", + "karma-sourcemap-loader": "^0.4.0", + "karma-spec-reporter": "^0.0.36", "karma-webpack": "^5.0.0", - "lodash": "4.17.21", - "lolex": "1.6.0", - "mini-css-extract-plugin": "0.12.0", - "mocha": "3.5.3", - "nightwatch": "0.9.21", - "opn": "4.0.2", - "ora": "0.4.1", + "lodash": "^4.17.21", + "lolex": "^6.0.0", + "mini-css-extract-plugin": "^2.9.0", + "mocha": "^10.4.0", + "nightwatch": "^3.6.3", + "opn": "^6.0.0", "postcss-html": "^1.5.0", - "postcss-loader": "3.0.0", + "postcss-loader": "^8.1.1", "postcss-sass": "^0.5.0", - "raw-loader": "0.5.1", - "sass": "^1.56.0", - "sass-loader": "^13.2.0", - "selenium-server": "2.53.1", - "semver": "5.7.1", - "shelljs": "0.8.5", - "sinon": "2.4.1", - "sinon-chai": "2.14.0", + "raw-loader": "^4.0.2", + "sass": "^1.77.2", + "sass-loader": "^14.2.1", + "selenium-server": "^3.141.59", + "semver": "^7.6.2", + "shelljs": "^0.8.5", + "sinon": "^18.0.0", + "sinon-chai": "^3.7.0", "stylelint": "^14.15.0", "stylelint-config-recommended-vue": "^1.4.0", "stylelint-config-standard": "^29.0.0", "stylelint-config-standard-scss": "^6.1.0", "stylelint-rscss": "^0.4.0", "url-loader": "^4.1.1", - "vue-loader": "^17.0.0", - "vue-style-loader": "^4.1.2", - "webpack": "^5.75.0", - "webpack-dev-middleware": "^5.3.3", - "webpack-hot-middleware": "^2.25.1", - "webpack-merge": "^5.8.0", - "workbox-webpack-plugin": "^6.5.4" + "vue-loader": "^17.4.2", + "vue-style-loader": "^4.1.3", + "webpack": "^5.91.0", + "webpack-dev-middleware": "^7.2.1", + "webpack-hot-middleware": "^2.26.1", + "webpack-merge": "^5.10.0", + "workbox-webpack-plugin": "^7.1.0" }, "engines": { "node": ">= 16.0.0", diff --git a/src/boot/after_store.js b/src/boot/after_store.js index d45584c0..d27d4fb8 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -183,6 +183,12 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => { copyInstanceOption('renderMisskeyMarkdown') copyInstanceOption('sidebarRight') + if (config.backendCommitUrl) + copyInstanceOption('backendCommitUrl') + + if (config.frontendCommitUrl) + copyInstanceOption('frontendCommitUrl') + return store.dispatch('setTheme', config['theme']) } diff --git a/src/components/about/about.vue b/src/components/about/about.vue index df9bb196..edf16b0b 100644 --- a/src/components/about/about.vue +++ b/src/components/about/about.vue @@ -9,7 +9,7 @@ - + diff --git a/src/components/account_actions/account_actions.vue b/src/components/account_actions/account_actions.vue index 126f6fa9..656fe802 100644 --- a/src/components/account_actions/account_actions.vue +++ b/src/components/account_actions/account_actions.vue @@ -6,7 +6,7 @@ :bound-to="{ x: 'container' }" remove-padding > - + - + - + diff --git a/src/components/attachment/attachment.vue b/src/components/attachment/attachment.vue index 0ccdb776..1d37bf43 100644 --- a/src/components/attachment/attachment.vue +++ b/src/components/attachment/attachment.vue @@ -246,8 +246,8 @@ ref="flash" class="flash" :src="attachment.large_thumb_url || attachment.url" - @playerOpened="setFlashLoaded(true)" - @playerClosed="setFlashLoaded(false)" + @player-opened="setFlashLoaded(true)" + @player-closed="setFlashLoaded(false)" /> diff --git a/src/components/avatar_list/avatar_list.vue b/src/components/avatar_list/avatar_list.vue index e1b6e971..9a6ca3f6 100644 --- a/src/components/avatar_list/avatar_list.vue +++ b/src/components/avatar_list/avatar_list.vue @@ -14,7 +14,7 @@ - + + - - diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index f8df9eb5..68b90c72 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -267,11 +267,11 @@ const conversation = { }, replies () { let i = 1 - // eslint-disable-next-line camelcase + return reduce(this.conversation, (result, { id, in_reply_to_status_id }) => { - /* eslint-disable camelcase */ + const irid = in_reply_to_status_id - /* eslint-enable camelcase */ + if (irid) { result[irid] = result[irid] || [] result[irid].push({ diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue index 1adbe250..61f1358a 100644 --- a/src/components/conversation/conversation.vue +++ b/src/components/conversation/conversation.vue @@ -91,7 +91,7 @@ :controlled-set-media-playing="(newVal) => toggleStatusContentProperty(status.id, 'mediaPlaying', newVal)" @goto="setHighlight" - @toggleExpanded="toggleExpanded" + @toggle-expanded="toggleExpanded" /> diff --git a/src/components/desktop_nav/desktop_nav.vue b/src/components/desktop_nav/desktop_nav.vue index f50d1b3e..aa5edd40 100644 --- a/src/components/desktop_nav/desktop_nav.vue +++ b/src/components/desktop_nav/desktop_nav.vue @@ -44,9 +44,9 @@ /> {{ $t('domain_mute_card.unmute') }} - + {{ $t('domain_mute_card.unmute_progress') }} @@ -19,7 +19,7 @@ class="btn button-default" > {{ $t('domain_mute_card.mute') }} - + {{ $t('domain_mute_card.mute_progress') }} diff --git a/src/components/edit_status_modal/edit_status_modal.vue b/src/components/edit_status_modal/edit_status_modal.vue index 00dde7de..ab3ea0b8 100644 --- a/src/components/edit_status_modal/edit_status_modal.vue +++ b/src/components/edit_status_modal/edit_status_modal.vue @@ -2,7 +2,7 @@ @@ -11,10 +11,10 @@ diff --git a/src/components/emoji_input/emoji_input.vue b/src/components/emoji_input/emoji_input.vue index d4760edc..aeacf544 100644 --- a/src/components/emoji_input/emoji_input.vue +++ b/src/components/emoji_input/emoji_input.vue @@ -43,7 +43,10 @@ :class="{ highlighted: index === highlighted }" @click.stop.prevent="onClick($event, suggestion)" > - + { const screenNameAlphabetically = a.screen_name > b.screen_name ? 1 : -1 return diff + nameAlphabetically + screenNameAlphabetically - /* eslint-disable camelcase */ + }).map(({ screen_name, screen_name_ui, name, profile_image_url_original }) => ({ displayText: screen_name_ui, detailText: name, imageUrl: profile_image_url_original, replacement: '@' + screen_name + ' ' })) - /* eslint-enable camelcase */ + suggestions = newSuggestions || [] return suggestions diff --git a/src/components/emoji_reactions/emoji_reactions.vue b/src/components/emoji_reactions/emoji_reactions.vue index d5e4301e..be70d522 100644 --- a/src/components/emoji_reactions/emoji_reactions.vue +++ b/src/components/emoji_reactions/emoji_reactions.vue @@ -42,7 +42,7 @@ - +