diff --git a/.babelrc b/.babelrc index 373d2c59..4ec10416 100644 --- a/.babelrc +++ b/.babelrc @@ -1,5 +1,5 @@ { "presets": ["@babel/preset-env"], "plugins": ["@babel/plugin-transform-runtime", "lodash", "@vue/babel-plugin-jsx"], - "comments": false + "comments": true } diff --git a/.gitignore b/.gitignore index 479d57c4..4df5ec83 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ test/e2e/reports selenium-debug.log .idea/ config/local.json +static/emoji.json diff --git a/.gitlab/issue_templates/Bug.md b/.gitlab/issue_templates/Bug.md new file mode 100644 index 00000000..bfd5e7b4 --- /dev/null +++ b/.gitlab/issue_templates/Bug.md @@ -0,0 +1,25 @@ +# Environment info + + +* Browser, version, OS, platform: +* Instance URL: +* Frontend version (see settings -> about): +* Backend version (see settings -> about): +* Browser extensions (ublock, rikaichamp etc): +* Known instance/user customizations (i.e. pleromafe mods/forks, instance styles etc) + +# Bug description & reproduction steps + + + + + +# Bug seriousness + + +* How annoying it is: +* How often does it happen: +* How many people does it affect: +* Is there a workaround for it: + +/label ~Bug diff --git a/.gitlab/issue_templates/Suggestion.md b/.gitlab/issue_templates/Suggestion.md new file mode 100644 index 00000000..7472981a --- /dev/null +++ b/.gitlab/issue_templates/Suggestion.md @@ -0,0 +1,11 @@ +# Behavior suggestion/Feature request + + +/label ~suggestion + diff --git a/.gitlab/issue_templates/default.md b/.gitlab/issue_templates/default.md new file mode 100644 index 00000000..4ada0702 --- /dev/null +++ b/.gitlab/issue_templates/default.md @@ -0,0 +1,7 @@ + + +/label ~needs-triage + diff --git a/.gitlab/merge_request_templates/default.md b/.gitlab/merge_request_templates/default.md new file mode 100644 index 00000000..ed9d54cb --- /dev/null +++ b/.gitlab/merge_request_templates/default.md @@ -0,0 +1,30 @@ + +# Changes + +* +* +* + + + + + + + +/label ~needs-review diff --git a/.node-version b/.node-version index 431076a9..5397c87f 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -16.16.0 +16.18.1 diff --git a/.stylelintrc.json b/.stylelintrc.json index fbf3a245..d6689cc0 100644 --- a/.stylelintrc.json +++ b/.stylelintrc.json @@ -1,19 +1,41 @@ { "extends": [ "stylelint-rscss/config", - "stylelint-config-recommended", - "stylelint-config-standard" + "stylelint-config-standard", + "stylelint-config-recommended-scss", + "stylelint-config-html", + "stylelint-config-recommended-vue/scss" ], "rules": { "declaration-no-important": true, "rscss/no-descendant-combinator": false, "rscss/class-format": [ - true, + false, { "component": "pascal-case", "variant": "^-[a-z]\\w+", "element": "^[a-z]\\w+" } + ], + "selector-class-pattern": null, + "import-notation": null, + "custom-property-pattern": null, + "keyframes-name-pattern": null, + "scss/operator-no-newline-after": null, + "declaration-block-no-redundant-longhand-properties": [ + true, + { + "ignoreShorthands": [ + "grid-template", + "margin", + "padding", + "border", + "border-width", + "border-style", + "border-color", + "border-radius" + ] + } ] } } diff --git a/CHANGELOG.md b/CHANGELOG.md index b7eea727..8ed1c186 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,51 +3,76 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). -## Unreleased +## 2.5.0 - 23.12.2022 ### Fixed -- AdminFE button no longer scrolls page to top when clicked +- UI no longer lags when switching between mobile and desktop mode +- Popovers no longer constrained by DOM hierarchy, shouldn't be cut off by anything +- Emoji autocomplete popover and picker popover stick to the text cursor. +- Attachments are ALWAYS in same order as user uploaded, no more "videos first" - Pinned statuses no longer appear at bottom of user timeline (still appear as part of the timeline when fetched deep enough) - Fixed many many bugs related to new mentions, including spacing and alignment issues - Links in profile bios now properly open in new tabs +- "Always show mobile button" is working now - Inline images now respect their intended width/height attributes - Links with `&` in them work properly now -- Interaction list popovers now properly emojify names -- Completely hidden posts still had 1px border -- Attachments are ALWAYS in same order as user uploaded, no more "videos first" - Attachment description is prefilled with backend-provided default when uploading - Proper visual feedback that next image is loading when browsing -- UI no longer lags when switching between mobile and desktop mode -- Popovers no longer constrained by DOM hierarchy, shouldn't be cut off by anything -- "Always show mobile button" is working now +- Additional HTML sanitization on frontend side in case backend sanitization fails +- Interaction list popovers now properly emojify names +- AdminFE button no longer scrolls page to top when clicked +- User handles with non-ascii domains now have less intrusive indicator for the domain name +- Completely hidden posts still no longer have 1px border +- A lot of accessibility improvements ### Changed - Using Vue 3 now -- (You)s are optional (opt-in) now, bolding your nickname is also optional (opt-out) +- A lot of internal dependencies updated +- "(You)s" are optional (opt-in) now, bolding your nickname is also optional (opt-out) - User highlight background now also covers the `@` - Reverted back to textual `@`, svg version is opt-in. - Settings window has been thoroughly rearranged to make more sense and make navigation settings easier. - Uploaded attachments are uniform with displayed attachments - Flash is watchable in media-modal (takes up nearly full screen though due to sizing issues) -- Notifications about likes/repeats/emoji reacts are now minimized so they always take up same amount of space irrelevant to size of post. +- Notifications about likes/repeats/emoji reacts are now minimized so they always take up same amount of space irrelevant to size of post. (You can expand them to full if need be) - Slight width/spacing adjustments - More sizing stuff is font-size dependent now - Scrollbars are styled/colorized now - Scrollbars are toggleable (for stuff that didn't have visible scrollbars before) (opt-in) +- Updated localization files +- Top bar is more useful in mobile mode now. +- "Show new" button is way more compact in mobile mode +- Slightly adjusted placement and spacing of the topbar buttons so it's less easy to accidentally log yourself out ### Added - 3 column mode: only enables when there's space for it (opt-out, customizable) +- Apologetic pleroma-tan +- New button on timeline header to change some of the new and often-used settings +- Support for lists +- Added ability to edit posts and view post edit history etc. +- Added ability to add personal note to users +- Added initial support for admin announcements +- Added ui for account migration +- Added ui for backups +- Added ability to force-unfollow a user from you +- Emoji are now grouped by pack +- Ability to pin navigation items and collapse the navigation menu +- Ability to rearrange order of attachments when uploading +- Ability to scroll column (or page) to top via panel header button - Options to show domains in mentions - Option to show user avatars in mention links (opt-in) - Option to disable the tooltip for mentions - Option to completely hide muted threads +- Option to customize what clicking user avatar does in user popover +- Notifications for poll results +- "Favorites" link in navigation +- Very early and somewhat experimental system for automatic settings sync (used only for pinned navigation and apologetic pleroma-tan) +- Implemented remote interaction with statuses for anon visitors - Ability to open videos in modal even if you disabled that feature, via an icon button - New button on attachment that indicates that attachment has a description and shows a bar filled with description - Attachments are truncated just like post contents - Media modal now also displays description and counter position in gallery (i.e. 1/5) -- Ability to rearrange order of attachments when uploading - Enabled users to zoom and pan images in media viewer with mouse and touch -- Timelines/panels and conversations have sticky headers now -- Added frontend ui for account migration +- Timelines/panels and conversations have sticky headers now (a bit glitchy on some browsers like safari) (opt-out) ## [2.4.2] - 2022-01-09 diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index f666a4ef..dec262de 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -10,3 +10,5 @@ Contributors of this project. - shpuld (shpuld@shitposter.club): CSS and styling - Vincent Guth (https://unsplash.com/photos/XrwVIFy6rTw): Background images. - hj (hj@shigusegubu.club): Code +- Sean King (seanking@kazv.moe): Code +- tusooa (tusooa@kazv.moe): Code diff --git a/README.md b/README.md index 54529a70..6a37195d 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,19 @@ # Pleroma-FE -> A single column frontend designed for Pleroma. +> Highly-customizable frontend designed for Pleroma. -![screenshot](/uploads/796c5ecf985ed1e2b0943ee0df131ed0/DJVqSJ0.png) +![screenshot](./image-1.png) # For Translators -To translate Pleroma-FE, add your language to [src/i18n/messages.js](https://git.pleroma.social/pleroma/pleroma-fe/blob/develop/src/i18n/messages.js). Pleroma-FE will set your language by your browser locale, but you can temporarily force it in the code by changing the locale in main.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/blob/develop/src/i18n/) folder and added in a list within [src/i18n/languages.js](https://git.pleroma.social/pleroma/pleroma-fe/blob/develop/src/i18n/languages.js). -# FOR ADMINS +Pleroma-FE will set your language by your browser locale, but you can change language in settings. -You don't need to build Pleroma-FE yourself. Those using the Pleroma backend will be able to use it out of the box. +# For instance admins +You don't need to build Pleroma-FE yourself. Those using the Pleroma backend will be able to use it out of the box. Information of customizing PleromaFE settings/defaults is in our [guide](https://docs-develop.pleroma.social/frontend/CONFIGURATION/) and in case you want to build your own custom version there's [another](https://docs-develop.pleroma.social/frontend/HACKING/) -## Build Setup +# Build Setup ``` bash # install dependencies @@ -20,13 +21,13 @@ npm install -g yarn yarn # serve with hot reload at localhost:8080 -npm run dev +yarn dev # build for production with minification -npm run build +yarn build # run unit tests -npm run unit +yarn unit ``` # For Contributors: @@ -40,10 +41,4 @@ FE Build process also leaves current commit hash in global variable `___pleromaf # Configuration -Edit config.json for configuration. - -## Options - -### Login methods - -```loginMethod``` can be set to either ```password``` (the default) or ```token```, which will use the full oauth redirection flow, which is useful for SSO situations. +Set configuration settings in AdminFE, additionally you can edit config.json. For more details see [documentation](https://docs-develop.pleroma.social/frontend/CONFIGURATION/). diff --git a/build/build.js b/build/build.js index b3c9aad4..8242bc5f 100644 --- a/build/build.js +++ b/build/build.js @@ -18,6 +18,9 @@ console.log( var spinner = ora('building for production...') spinner.start() +var updateEmoji = require('./update-emoji').updateEmoji +updateEmoji() + var assetsPath = path.join(config.build.assetsRoot, config.build.assetsSubDirectory) rm('-rf', assetsPath) mkdir('-p', assetsPath) @@ -33,4 +36,8 @@ webpack(webpackConfig, function (err, stats) { chunks: false, chunkModules: false }) + '\n') + if (stats.hasErrors()) { + console.error('See above for errors.') + process.exit(1) + } }) diff --git a/build/dev-server.js b/build/dev-server.js index c06192bd..e51ba948 100644 --- a/build/dev-server.js +++ b/build/dev-server.js @@ -10,6 +10,9 @@ var webpackConfig = process.env.NODE_ENV === 'testing' ? require('./webpack.prod.conf') : require('./webpack.dev.conf') +var updateEmoji = require('./update-emoji').updateEmoji +updateEmoji() + // default port where dev server listens for incoming traffic var port = process.env.PORT || config.dev.port // Define HTTP proxies to your custom API backend @@ -29,18 +32,20 @@ var devMiddleware = require('webpack-dev-middleware')(compiler, { }) var hotMiddleware = require('webpack-hot-middleware')(compiler) -// force page reload when html-webpack-plugin template changes -compiler.plugin('compilation', function (compilation) { - compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) { - // FIXME: This supposed to reload whole page when index.html is changed, - // however now it reloads entire page on every breath, i suppose the order - // of plugins changed or something. It's a minor thing and douesn't hurt - // disabling it, constant reloads hurt much more - // hotMiddleware.publish({ action: 'reload' }) - // cb() - }) -}) +// FIXME: The statement below gives error about hooks being required in webpack 5. +// force page reload when html-webpack-plugin template changes +// compiler.plugin('compilation', function (compilation) { +// compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) { +// // FIXME: This supposed to reload whole page when index.html is changed, +// // however now it reloads entire page on every breath, i suppose the order +// // of plugins changed or something. It's a minor thing and douesn't hurt +// // disabling it, constant reloads hurt much more + +// // hotMiddleware.publish({ action: 'reload' }) +// // cb() +// }) +// }) // proxy api requests Object.keys(proxyTable).forEach(function (context) { @@ -48,7 +53,7 @@ Object.keys(proxyTable).forEach(function (context) { if (typeof options === 'string') { options = { target: options } } - app.use(proxyMiddleware(context, options)) + app.use(proxyMiddleware.createProxyMiddleware(context, options)) }) // handle fallback for HTML5 history API diff --git a/build/update-emoji.js b/build/update-emoji.js new file mode 100644 index 00000000..9f4b4e67 --- /dev/null +++ b/build/update-emoji.js @@ -0,0 +1,27 @@ + +module.exports = { + updateEmoji () { + const emojis = require('@kazvmoe-infra/unicode-emoji-json/data-by-group') + const fs = require('fs') + + Object.keys(emojis) + .map(k => { + emojis[k].map(e => { + delete e.unicode_version + delete e.emoji_version + delete e.skin_tone_support_unicode_version + }) + }) + + const res = {} + Object.keys(emojis) + .map(k => { + const groupId = k.replace('&', 'and').replace(/ /g, '-').toLowerCase() + res[groupId] = emojis[k] + }) + + console.info('Updating emojis...') + fs.writeFileSync('static/emoji.json', JSON.stringify(res)) + console.info('Done.') + } +} diff --git a/build/webpack.base.conf.js b/build/webpack.base.conf.js index d8a4228d..7e69352a 100644 --- a/build/webpack.base.conf.js +++ b/build/webpack.base.conf.js @@ -2,11 +2,11 @@ var path = require('path') var config = require('../config') var utils = require('./utils') var projectRoot = path.resolve(__dirname, '../') -var ServiceWorkerWebpackPlugin = require('serviceworker-webpack-plugin') +var ServiceWorkerWebpackPlugin = require('serviceworker-webpack5-plugin') var CopyPlugin = require('copy-webpack-plugin'); var { VueLoaderPlugin } = require('vue-loader') var ESLintPlugin = require('eslint-webpack-plugin'); - +var StylelintPlugin = require('stylelint-webpack-plugin'); var env = process.env.NODE_ENV // check env & config/index.js to decide weither to enable CSS Sourcemaps for the @@ -24,7 +24,8 @@ module.exports = { output: { path: config.build.assetsRoot, publicPath: process.env.NODE_ENV === 'production' ? config.build.assetsPublicPath : config.dev.assetsPublicPath, - filename: '[name].js' + filename: '[name].js', + chunkFilename: '[name].js' }, optimization: { splitChunks: { @@ -42,6 +43,10 @@ module.exports = { 'assets': path.resolve(__dirname, '../src/assets'), 'components': path.resolve(__dirname, '../src/components'), 'vue-i18n': 'vue-i18n/dist/vue-i18n.runtime.esm-bundler.js' + }, + fallback: { + 'querystring': require.resolve('querystring-es3'), + 'url': require.resolve('url/') } }, module: { @@ -78,22 +83,16 @@ module.exports = { }, { test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, - use: { - loader: 'url-loader', - options: { - limit: 10000, - name: utils.assetsPath('img/[name].[hash:7].[ext]') - } + type: 'asset', + generator: { + filename: utils.assetsPath('img/[name].[hash:7][ext]') } }, { test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, - use: { - loader: 'url-loader', - options: { - limit: 10000, - name: utils.assetsPath('fonts/[name].[hash:7].[ext]') - } + type: 'asset', + generator: { + filename: utils.assetsPath('fonts/[name].[hash:7][ext]') } }, { @@ -112,14 +111,14 @@ module.exports = { extensions: ['js', 'vue'], formatter: require('eslint-formatter-friendly') }), + new StylelintPlugin({}), new VueLoaderPlugin(), // This copies Ruffle's WASM to a directory so that JS side can access it new CopyPlugin({ patterns: [ { - from: "node_modules/@ruffle-rs/ruffle/*", - to: "static/ruffle", - flatten: true + from: "node_modules/@ruffle-rs/ruffle/**/*", + to: "static/ruffle/[name][ext]" }, ], options: { diff --git a/build/webpack.dev.conf.js b/build/webpack.dev.conf.js index 4605b93d..97799f82 100644 --- a/build/webpack.dev.conf.js +++ b/build/webpack.dev.conf.js @@ -16,7 +16,7 @@ module.exports = merge(baseWebpackConfig, { }, mode: 'development', // eval-source-map is faster for development - devtool: '#eval-source-map', + devtool: 'eval-source-map', plugins: [ new webpack.DefinePlugin({ 'process.env': config.dev.env, diff --git a/build/webpack.prod.conf.js b/build/webpack.prod.conf.js index a67ed2f6..7de93721 100644 --- a/build/webpack.prod.conf.js +++ b/build/webpack.prod.conf.js @@ -5,6 +5,7 @@ var webpack = require('webpack') var merge = require('webpack-merge') var baseWebpackConfig = require('./webpack.base.conf') var MiniCssExtractPlugin = require('mini-css-extract-plugin') +const CssMinimizerPlugin = require("css-minimizer-webpack-plugin") var HtmlWebpackPlugin = require('html-webpack-plugin') var env = process.env.NODE_ENV === 'testing' ? require('../config/test.env') @@ -19,12 +20,16 @@ var webpackConfig = merge(baseWebpackConfig, { module: { rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, extract: true }) }, - devtool: config.build.productionSourceMap ? '#source-map' : false, + devtool: config.build.productionSourceMap ? 'source-map' : false, optimization: { minimize: true, splitChunks: { chunks: 'all' - } + }, + minimizer: [ + `...`, + new CssMinimizerPlugin() + ] }, output: { path: config.build.assetsRoot, @@ -60,9 +65,7 @@ var webpackConfig = merge(baseWebpackConfig, { ignoreCustomComments: [/server-generated-meta/] // more options: // https://github.com/kangax/html-minifier#options-quick-reference - }, - // necessary to consistently work with multiple chunks via CommonsChunkPlugin - chunksSortMode: 'dependency' + } }), // split vendor js into its own file // extract webpack runtime and module manifest to its own file in order to diff --git a/docs/HACKING.md b/docs/HACKING.md index 7f2964b4..a5c49113 100644 --- a/docs/HACKING.md +++ b/docs/HACKING.md @@ -25,7 +25,17 @@ This could be a bit trickier, you basically need steps 1-4 from *develop build* ### Replacing your instance's frontend with custom FE build -This is the most easiest way to use and test FE build: you just need to copy or symlink contents of `dist` folder into backend's [static directory](../backend/configuration/static_dir.md), by default it is located in `instance/static`, or in `/var/lib/pleroma/static` for OTP release installations, create it if it doesn't exist already. Be aware that running `yarn build` wipes the contents of `dist` folder. +#### New way (via AdminFE, a bit janky but works) + +In backend's [static directory](../backend/configuration/static_dir.md) there should be a folder called `frontends` if you installed any frontends from AdminFE before, otherwise you can create it yourself (ensuring correct permissions). Backend will serve given frontend from path `frontends/{frontend}/{reference}`, where `{frontend}` is name of frontend (`pleroma-fe`) and `{reference}` is version. You could make a production build, move `dist` folder into `frontends/pleroma-fe` and rename it into something like `myCustomVersion`. To actually make backend serve this frontend by default, in AdminFE you'll need to set name/reference in Settings -> Frontend -> Frontends -> Primary. + +You could also install from a zip file (i.e. CI build) but AdminFE UI is a bit buggy and lacking, so this approach is not recommended. + +Take note that frontend management is in early development and currently there's no way for user to change frontend or version for themselves, primary frontend becomes default frontend for all users and visitors. + +#### Old way (replaces everything, hard to maintain, not recommended) + +Copy or symlink contents of `dist` folder into backend's [static directory](../backend/configuration/static_dir.md), by default it is located in `instance/static`, or in `/var/lib/pleroma/static` for OTP release installations, create it if it doesn't exist already. Be aware that running `yarn build` wipes the contents of `dist` folder, and this could remove emojis, other frontends etc. and therefore this approach is not recommended. ### Running production build locally or on a separate server diff --git a/image-1.png b/image-1.png new file mode 100644 index 00000000..602cbb26 Binary files /dev/null and b/image-1.png differ diff --git a/image.png b/image.png new file mode 100644 index 00000000..c1004c7c Binary files /dev/null and b/image.png differ diff --git a/index.html b/index.html index ba072eda..4af84a59 100644 --- a/index.html +++ b/index.html @@ -10,5 +10,6 @@
+
diff --git a/package.json b/package.json index e7033b75..a256eebb 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,9 @@ { "name": "pleroma_fe", - "version": "1.0.0", - "description": "A Qvitter-style frontend for certain GS servers.", - "author": "Roger Braun ", - "private": true, + "version": "2.5.0", + "description": "Pleroma frontend, the default frontend of Pleroma social network server", + "author": "Pleroma contributors ", + "private": false, "scripts": { "dev": "node build/dev-server.js", "build": "node build/build.js", @@ -11,120 +11,125 @@ "unit:watch": "karma start test/unit/karma.conf.js --single-run=false", "e2e": "node test/e2e/runner.js", "test": "npm run unit && npm run e2e", - "stylelint": "npx stylelint src/components/status/status.scss", + "stylelint": "npx stylelint '**/*.scss' '**/*.vue'", "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" }, "dependencies": { - "@babel/runtime": "7.18.9", + "@babel/runtime": "7.20.7", "@chenfengyuan/vue-qrcode": "2.0.0", - "@fortawesome/fontawesome-svg-core": "6.1.2", - "@fortawesome/free-regular-svg-icons": "6.1.2", - "@fortawesome/free-solid-svg-icons": "6.1.2", - "@fortawesome/vue-fontawesome": "3.0.1", + "@fortawesome/fontawesome-svg-core": "6.2.1", + "@fortawesome/free-regular-svg-icons": "6.2.1", + "@fortawesome/free-solid-svg-icons": "6.2.1", + "@fortawesome/vue-fontawesome": "3.0.2", "@kazvmoe-infra/pinch-zoom-element": "1.2.0", - "@ruffle-rs/ruffle": "^0.1.0-nightly.2022.7.12", - "@vuelidate/core": "2.0.0-alpha.43", - "@vuelidate/validators": "2.0.0-alpha.31", + "@kazvmoe-infra/unicode-emoji-json": "0.4.0", + "@ruffle-rs/ruffle": "0.1.0-nightly.2022.7.12", + "@vuelidate/core": "2.0.0", + "@vuelidate/validators": "2.0.0", "body-scroll-lock": "3.1.5", "chromatism": "3.0.0", "click-outside-vue3": "4.0.1", "cropperjs": "1.5.12", - "diff": "3.5.0", "escape-html": "1.0.3", - "js-cookie": "^3.0.1", + "js-cookie": "3.0.1", "localforage": "1.10.0", - "parse-link-header": "1.0.1", + "parse-link-header": "2.0.0", "phoenix": "1.6.2", "punycode.js": "2.1.0", - "qrcode": "1", - "utf8": "^3.0.0", - "vue": "3.2.37", - "vue-i18n": "9.2.0", - "vue-router": "4.1.3", - "vue-template-compiler": "2.7.8", - "vuex": "4.0.2" + "qrcode": "1.5.0", + "querystring-es3": "0.2.1", + "url": "0.11.0", + "utf8": "3.0.0", + "vue": "3.2.45", + "vue-i18n": "9.2.2", + "vue-router": "4.1.6", + "vue-template-compiler": "2.7.14", + "vue-virtual-scroller": "^2.0.0-beta.7", + "vuex": "4.1.0" }, "devDependencies": { - "@babel/core": "7.18.9", - "@babel/plugin-transform-runtime": "7.18.9", - "@babel/preset-env": "7.18.9", + "@babel/core": "7.20.7", + "@babel/eslint-parser": "7.19.1", + "@babel/plugin-transform-runtime": "7.19.6", + "@babel/preset-env": "7.20.2", "@babel/register": "7.18.9", - "@babel/eslint-parser": "7.18.9", - "@intlify/vue-i18n-loader": "^5.0.0", + "@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-helper-vue-jsx-merge-props": "1.4.0", "@vue/babel-plugin-jsx": "1.1.1", - "@vue/compiler-sfc": "3.2.37", - "@vue/test-utils": "2.0.2", - "autoprefixer": "6.7.7", - "babel-loader": "8.2.5", + "@vue/compiler-sfc": "3.2.45", + "@vue/test-utils": "2.2.7", + "autoprefixer": "10.4.13", + "babel-loader": "9.1.0", "babel-plugin-lodash": "3.3.4", - "chai": "3.5.0", + "chai": "4.3.7", "chalk": "1.1.3", - "chromedriver": "103.0.0", - "connect-history-api-fallback": "1.6.0", - "copy-webpack-plugin": "6.4.1", - "cross-spawn": "4.0.2", - "css-loader": "0.28.11", + "chromedriver": "108.0.0", + "connect-history-api-fallback": "2.0.0", + "copy-webpack-plugin": "11.0.0", + "cross-spawn": "7.0.3", + "css-loader": "6.7.3", + "css-minimizer-webpack-plugin": "4.2.2", "custom-event-polyfill": "1.0.7", - "eslint": "8.20.0", + "eslint": "8.32.0", "eslint-config-standard": "17.0.0", "eslint-formatter-friendly": "7.0.0", - "eslint-webpack-plugin": "2.7.0", "eslint-plugin-import": "2.26.0", - "eslint-plugin-n": "15.2.4", - "eslint-plugin-promise": "6.0.0", - "eslint-plugin-vue": "9.3.0", + "eslint-plugin-n": "15.6.1", + "eslint-plugin-promise": "6.1.1", + "eslint-plugin-vue": "9.8.0", + "eslint-webpack-plugin": "3.2.0", "eventsource-polyfill": "0.9.6", - "express": "4.18.1", - "file-loader": "3.0.1", + "express": "4.18.2", "function-bind": "1.1.1", - "html-webpack-plugin": "3.2.0", - "http-proxy-middleware": "0.21.0", - "inject-loader": "2.0.1", + "html-webpack-plugin": "5.5.0", + "http-proxy-middleware": "2.0.6", "iso-639-1": "2.1.15", - "isparta-loader": "2.0.0", "json-loader": "0.5.7", - "karma": "6.4.0", - "karma-coverage": "1.1.2", - "karma-firefox-launcher": "1.3.0", + "karma": "6.4.1", + "karma-coverage": "2.2.0", + "karma-firefox-launcher": "2.1.2", "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.34", - "karma-webpack": "4.0.2", + "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", + "mini-css-extract-plugin": "2.7.2", + "mocha": "10.2.0", + "nightwatch": "2.6.10", + "opn": "5.5.0", "ora": "0.4.1", - "postcss-loader": "3.0.0", - "raw-loader": "0.5.1", - "sass": "1.54.0", - "sass-loader": "7.3.1", + "postcss": "8.4.20", + "postcss-html": "^1.5.0", + "postcss-loader": "7.0.2", + "postcss-scss": "^4.0.6", + "sass": "1.57.1", + "sass-loader": "13.2.0", "selenium-server": "2.53.1", - "semver": "5.7.1", - "serviceworker-webpack-plugin": "1.0.1", + "semver": "7.3.8", + "serviceworker-webpack5-plugin": "2.0.0", "shelljs": "0.8.5", - "sinon": "2.4.1", - "sinon-chai": "2.14.0", - "stylelint": "13.13.1", - "stylelint-config-standard": "20.0.0", + "sinon": "15.0.1", + "sinon-chai": "3.7.0", + "stylelint": "14.16.1", + "stylelint-config-html": "^1.1.0", + "stylelint-config-recommended-scss": "^8.0.0", + "stylelint-config-recommended-vue": "^1.4.0", + "stylelint-config-standard": "29.0.0", "stylelint-rscss": "0.4.0", - "url-loader": "1.1.2", - "vue-loader": "^16.0.0", + "stylelint-webpack-plugin": "^3.3.0", + "vue-loader": "17.0.1", "vue-style-loader": "4.1.3", - "webpack": "4.46.0", + "webpack": "5.75.0", "webpack-dev-middleware": "3.7.3", - "webpack-hot-middleware": "2.25.1", + "webpack-hot-middleware": "2.25.3", "webpack-merge": "0.20.0" }, "engines": { - "node": ">= 4.0.0", + "node": ">= 16.0.0", "npm": ">= 3.0.0" } } diff --git a/src/App.js b/src/App.js index d5967685..b7eb2f72 100644 --- a/src/App.js +++ b/src/App.js @@ -10,7 +10,9 @@ import MobilePostStatusButton from './components/mobile_post_status_button/mobil import MobileNav from './components/mobile_nav/mobile_nav.vue' import DesktopNav from './components/desktop_nav/desktop_nav.vue' import UserReportingModal from './components/user_reporting_modal/user_reporting_modal.vue' +import EditStatusModal from './components/edit_status_modal/edit_status_modal.vue' import PostStatusModal from './components/post_status_modal/post_status_modal.vue' +import StatusHistoryModal from './components/status_history_modal/status_history_modal.vue' import GlobalNoticeList from './components/global_notice_list/global_notice_list.vue' import { windowWidth, windowHeight } from './services/window_utils/window_utils' import { mapGetters } from 'vuex' @@ -32,8 +34,11 @@ export default { MobileNav, DesktopNav, SettingsModal: defineAsyncComponent(() => import('./components/settings_modal/settings_modal.vue')), + UpdateNotification: defineAsyncComponent(() => import('./components/update_notification/update_notification.vue')), UserReportingModal, PostStatusModal, + EditStatusModal, + StatusHistoryModal, GlobalNoticeList }, data: () => ({ @@ -59,6 +64,13 @@ export default { '-' + this.layoutType ] }, + navClasses () { + const { navbarColumnStretch } = this.$store.getters.mergedConfig + return [ + '-' + this.layoutType, + ...(navbarColumnStretch ? ['-column-stretch'] : []) + ] + }, currentUser () { return this.$store.state.users.currentUser }, userBackground () { return this.currentUser.background_image }, instanceBackground () { @@ -84,11 +96,16 @@ export default { isChats () { return this.$route.name === 'chat' || this.$route.name === 'chats' }, + isListEdit () { + return this.$route.name === 'lists-edit' + }, newPostButtonShown () { if (this.isChats) return false + if (this.isListEdit) return false return this.$store.getters.mergedConfig.alwaysShowNewPostButton || this.layoutType === 'mobile' }, showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel }, + editingAvailable () { return this.$store.state.instance.editingAvailable }, shoutboxPosition () { return this.$store.getters.mergedConfig.alwaysShowNewPostButton || false }, diff --git a/src/App.scss b/src/App.scss index ab025d63..1c4c8941 100644 --- a/src/App.scss +++ b/src/App.scss @@ -1,16 +1,18 @@ // stylelint-disable rscss/class-format -@import './_variables.scss'; +/* stylelint-disable no-descending-specificity */ +@import "./variables"; +@import "./panel"; :root { --navbar-height: 3.5rem; --post-line-height: 1.4; // Z-Index stuff - --ZI_media_modal: 90000; - --ZI_modals_popovers: 85000; - --ZI_modals: 80000; - --ZI_navbar_popovers: 75000; - --ZI_navbar: 70000; - --ZI_popovers: 60000; + --ZI_media_modal: 9000; + --ZI_modals_popovers: 8500; + --ZI_modals: 8000; + --ZI_navbar_popovers: 7500; + --ZI_navbar: 7000; + --ZI_popovers: 6000; } html { @@ -117,20 +119,35 @@ h4 { margin: 0; } -i[class*=icon-], -.svg-inline--fa { +.iconLetter { + display: inline-block; + text-align: center; + font-weight: 1000; +} + +i[class*="icon-"], +.svg-inline--fa, +.iconLetter { color: $fallback--icon; color: var(--icon, $fallback--icon); } +.button-unstyled:hover, +a:hover { + > i[class*="icon-"], + > .svg-inline--fa, + > .iconLetter { + color: var(--text); + } +} + nav { z-index: var(--ZI_navbar); - color: var(--topBarText); background-color: $fallback--fg; background-color: var(--topBar, $fallback--fg); color: $fallback--faint; color: var(--faint, $fallback--faint); - box-shadow: 0 0 4px rgba(0, 0, 0, 0.6); + box-shadow: 0 0 4px rgb(0 0 0 / 60%); box-shadow: var(--topBarShadow); box-sizing: border-box; height: var(--navbar-height); @@ -141,6 +158,11 @@ nav { grid-area: sidebar; } +#modal { + position: absolute; + z-index: var(--ZI_modals); +} + .column.-scrollable { top: var(--navbar-height); position: sticky; @@ -170,25 +192,28 @@ nav { } .underlay { - grid-column-start: 1; - grid-column-end: span 3; - grid-row-start: 1; - grid-row-end: 1; + grid-column: 1 / span 3; + grid-row: 1 / 1; pointer-events: none; - background-color: rgba(0, 0, 0, 0.15); - background-color: var(--underlay, rgba(0, 0, 0, 0.15)); + background-color: rgb(0 0 0 / 15%); + background-color: var(--underlay, rgb(0 0 0 / 15%)); z-index: -1000; } .app-layout { --miniColumn: 25rem; - --maxiColumn: minmax(var(--miniColumn), 45rem); + --maxiColumn: 45rem; --columnGap: 1em; --status-margin: 0.75em; + --effectiveSidebarColumnWidth: minmax(var(--miniColumn), var(--sidebarColumnWidth, var(--miniColumn))); + --effectiveNotifsColumnWidth: minmax(var(--miniColumn), var(--notifsColumnWidth, var(--miniColumn))); + --effectiveContentColumnWidth: minmax(var(--miniColumn), var(--contentColumnWidth, var(--maxiColumn))); position: relative; display: grid; - grid-template-columns: var(--miniColumn) var(--maxiColumn); + grid-template-columns: + var(--effectiveSidebarColumnWidth) + var(--effectiveContentColumnWidth); grid-template-areas: "sidebar content"; grid-template-rows: 1fr; box-sizing: border-box; @@ -205,8 +230,7 @@ nav { display: grid; grid-template-columns: 100%; box-sizing: border-box; - grid-row-start: 1; - grid-row-end: 1; + grid-row: 1 / 1; margin: 0 calc(var(--___columnMargin) / 2); padding: calc(var(--___columnMargin)) 0; row-gap: var(--___columnMargin); @@ -281,16 +305,25 @@ nav { align-content: start; } - &.-reverse:not(.-wide):not(.-mobile) { - grid-template-columns: var(--maxiColumn) var(--miniColumn); + &.-reverse:not(.-wide, .-mobile) { + grid-template-columns: + var(--effectiveContentColumnWidth) + var(--effectiveSidebarColumnWidth); grid-template-areas: "content sidebar"; } &.-wide { - grid-template-columns: var(--miniColumn) var(--maxiColumn) var(--miniColumn); + grid-template-columns: + var(--effectiveSidebarColumnWidth) + var(--effectiveContentColumnWidth) + var(--effectiveNotifsColumnWidth); grid-template-areas: "sidebar content notifs"; &.-reverse { + grid-template-columns: + var(--effectiveNotifsColumnWidth) + var(--effectiveContentColumnWidth) + var(--effectiveSidebarColumnWidth); grid-template-areas: "notifs content sidebar"; } } @@ -301,11 +334,8 @@ nav { padding: 0; .column { - margin-left: 0; - margin-right: 0; padding-top: 0; - margin-top: var(--navbar-height); - margin-bottom: 0; + margin: var(--navbar-height) 0 0 0; } .panel-heading, @@ -354,7 +384,7 @@ nav { background: transparent; } - i[class*=icon-], + i[class*="icon-"], .svg-inline--fa { color: $fallback--text; color: var(--btnText, $fallback--text); @@ -365,12 +395,15 @@ nav { } &:hover { - box-shadow: 0 0 4px rgba(255, 255, 255, 0.3); + box-shadow: 0 0 4px rgb(255 255 255 / 30%); box-shadow: var(--buttonHoverShadow); } &:active { - box-shadow: 0 0 4px 0 rgba(255, 255, 255, 0.3), 0 1px 0 0 rgba(0, 0, 0, 0.2) inset, 0 -1px 0 0 rgba(255, 255, 255, 0.2) inset; + box-shadow: + 0 0 4px 0 rgb(255 255 255 / 30%), + 0 1px 0 0 rgb(0 0 0 / 20%) inset, + 0 -1px 0 0 rgb(255 255 255 / 20%) inset; box-shadow: var(--buttonPressedShadow); color: $fallback--text; color: var(--btnPressedText, $fallback--text); @@ -403,7 +436,10 @@ nav { color: var(--btnToggledText, $fallback--text); background-color: $fallback--fg; background-color: var(--btnToggled, $fallback--fg); - box-shadow: 0 0 4px 0 rgba(255, 255, 255, 0.3), 0 1px 0 0 rgba(0, 0, 0, 0.2) inset, 0 -1px 0 0 rgba(255, 255, 255, 0.2) inset; + box-shadow: + 0 0 4px 0 rgb(255 255 255 / 30%), + 0 1px 0 0 rgb(0 0 0 / 20%) inset, + 0 -1px 0 0 rgb(255 255 255 / 20%) inset; box-shadow: var(--buttonPressedShadow); svg, @@ -468,7 +504,10 @@ textarea, border: none; border-radius: $fallback--inputRadius; border-radius: var(--inputRadius, $fallback--inputRadius); - box-shadow: 0 1px 0 0 rgba(0, 0, 0, 0.2) inset, 0 -1px 0 0 rgba(255, 255, 255, 0.2) inset, 0 0 2px 0 rgba(0, 0, 0, 1) inset; + box-shadow: + 0 1px 0 0 rgb(0 0 0 / 20%) inset, + 0 -1px 0 0 rgb(255 255 255 / 20%) inset, + 0 0 2px 0 rgb(0 0 0 / 100%) inset; box-shadow: var(--inputShadow); background-color: $fallback--fg; background-color: var(--input, $fallback--fg); @@ -486,13 +525,13 @@ textarea, padding: 0 var(--_padding); &:disabled, - &[disabled=disabled], + &[disabled="disabled"], &.disabled { cursor: not-allowed; opacity: 0.5; } - &[type=range] { + &[type="range"] { background: none; border: none; margin: 0; @@ -500,7 +539,7 @@ textarea, flex: 1; } - &[type=radio] { + &[type="radio"] { display: none; &:checked + label::before { @@ -520,7 +559,7 @@ textarea, + label::before { flex-shrink: 0; display: inline-block; - content: ''; + content: ""; transition: box-shadow 200ms; width: 1.1em; height: 1.1em; @@ -540,7 +579,7 @@ textarea, } } - &[type=checkbox] { + &[type="checkbox"] { display: none; &:checked + label::before { @@ -559,7 +598,7 @@ textarea, + label::before { flex-shrink: 0; display: inline-block; - content: '✓'; + content: "✓"; transition: color 200ms; width: 1.1em; height: 1.1em; @@ -599,10 +638,10 @@ option { } .hide-number-spinner { - -moz-appearance: textfield; + appearance: textfield; - &[type=number]::-webkit-inner-spin-button, - &[type=number]::-webkit-outer-spin-button { + &[type="number"]::-webkit-inner-spin-button, + &[type="number"]::-webkit-outer-spin-button { opacity: 0; display: none; } @@ -634,8 +673,6 @@ option { } } -@import './panel.scss'; - .fa { color: grey; } @@ -651,7 +688,7 @@ option { max-width: 10em; min-width: 1.7em; height: 1.3em; - padding: 0.15em 0.15em; + padding: 0.15em; vertical-align: middle; font-weight: normal; font-style: normal; @@ -746,17 +783,24 @@ option { } .fa-scale-110 { - &.svg-inline--fa { + &.svg-inline--fa, + &.iconLetter { font-size: 1.1em; } } .fa-old-padding { - &.svg-inline--fa { + &.iconLetter, + &.svg-inline--fa, + &-layer { padding: 0 0.3em; } } +.veryfaint { + opacity: 0.25; +} + .login-hint { text-align: center; @@ -842,3 +886,4 @@ option { .fade-leave-active { opacity: 0; } +/* stylelint-enable no-descending-specificity */ diff --git a/src/App.vue b/src/App.vue index 0efadaf0..23a388a6 100644 --- a/src/App.vue +++ b/src/App.vue @@ -8,7 +8,10 @@ class="app-bg-wrapper" /> - +
-
-
+
+ + +