diff --git a/.browserslistrc b/.browserslistrc new file mode 100644 index 00000000..df2d4716 --- /dev/null +++ b/.browserslistrc @@ -0,0 +1,7 @@ +>0.2% +not op_mini all +Safari > 15 +Firefox >= 115 +Firefox ESR +Android > 4 +not dead diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..c5b9ea10 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +/build/webpack.prod.conf.js export-subst diff --git a/.gitignore b/.gitignore index 4df5ec83..0d5befd2 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ selenium-debug.log .idea/ config/local.json static/emoji.json +logs/ diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 305155d8..f4c5cf43 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -4,11 +4,36 @@ image: node:16 stages: + - check-changelog - lint - build - test - deploy +# https://git.pleroma.social/help/ci/yaml/workflow.md#switch-between-branch-pipelines-and-merge-request-pipelines +workflow: + rules: + - if: $CI_PIPELINE_SOURCE == "merge_request_event" + - if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS + when: never + - if: $CI_COMMIT_BRANCH + +check-changelog: + stage: check-changelog + image: alpine + rules: + - if: $CI_MERGE_REQUEST_SOURCE_PROJECT_PATH == 'pleroma/pleroma-fe' && $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME =~ /^renovate/ + when: never + - if: $CI_MERGE_REQUEST_SOURCE_PROJECT_PATH == 'pleroma/pleroma-fe' && $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME == 'weblate' + when: never + - if: $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "develop" + before_script: '' + after_script: '' + cache: {} + script: + - apk add git + - sh ./tools/check-changelog + lint: stage: lint script: @@ -18,6 +43,9 @@ lint: test: stage: test + tags: + - amd64 + - himem variables: APT_CACHE_DIR: apt-cache script: @@ -29,6 +57,9 @@ test: build: stage: build + tags: + - amd64 + - himem script: - yarn - npm run build 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 8ed1c186..9844319e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,133 @@ 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/). +## 2.7.1 +Bugfix release. Added small optimizations to emoji picker that should make it a bit more responsive, however it needs rather large change to make it more performant which might come in a major release. + +### Fixed +- Instance default theme not respected +- Nested panel header having wrong sticky position if navbar height != panel header height +- Toggled buttons having bad contrast (when using v2 theme) + +### Changed +- Simplify the OAuth client_name to 'PleromaFE' +- Small optimizations to emoji picker + + +## 2.7.0 + +### Known issues +We got some reports related to emoji picker performance, this hopefully will be fixed in 2.7.1. + +### Notes +This release overhauls how themes work, themes now need to be "compiled", which can cause some delay when loading for the first time and temporarily look "wrong" in some places (popups, menus, dialogs). Please do report any issues, especially if your theme looks wrong or breaks interface when loading. Also report issues if you're experiencing constant performance issues. + +To admins: remember that you can update PleromaFE to recent `master` or `develop` in admin dashboard in "Front-ends" tab, scroll down to find PleromaFE box and click "Reinstall `master`" or dropdown and then "Reinstall `develop`". Currently there is no mechanism to check if there is an update or not. + +### Changed +- Overhauled the way themes work, migrating to new Pleroma Interface Style Sheets system aka "Themes 3". +- Notifications are no longer sorted by "seen" status since interacting with them can change their read status and makes UI jumpy. Old behavior can be restored in settings. +- Notifications are now shown through a ServiceWorker (since mobile chrome does not allow them otherwise), it's always enabled, even if previously we only enabled it for WebPush notifications only. If you don't like websites "running" while closed, check how to disable them in your browser. Old way to show notifications will be used as a fallback but might not have all the new features. +- Reorganized Settings modal to move out visual stuff into Appearance tab + +### Added +- Emoji pack management to the admin panel +- Support `status` notification type (subscriptions/bell, fixes PleromaFE on newer PleromaBE versions) +- Poll end notifications. +- Added option to not mark all notifications when closing notifications drawer on mobile, this creates a new button to mark all as seen. +- Option to always "show" notifications when using web push for better compatibility with some browsers (chrome, edge, safari) +- Option to toggle what notification types appear in native notifications, by default less important ones (likes, repeats, etc) will no longer show up in native notifications. +- Option to treat non-interactive notifications (likes, repeats et all) as seen for visual purposes (no read mark, ignored in counters, still can show in native notifications) +- Ability to resize UI (and certain components) scale independent of browser/text scale +- Ability to override certain aspects of UI style independent of theme used (UI roundness, fonts, underlay) +- Theme selector with visual previews of the theme +- Display loading and error indicator for conversation page +- Option to only show scrobbles that are recent enough +- Interacting (opening reply box etc) or simply clicking on non-interactive notifications now marks them as read. Clicking on native notifications for non-interactive ones also marks them as seen. +- Support group actors +- Focusing into a tab clears all current desktop notifications +- Ability to change size of emoji +- Ability to view APNG (Animated PNG) attachments. +- Support showing extra notifications in the notifications column +- Create a link to the URL of the scrobble when it's present +- Allow hiding custom emojis in picker. +- Ability to mute sensitive posts (ported from eintei). +- Native notifications now also have "badge" property that matches instance's favicon (visible in Android Chromium at least) +- Display public favorites on user profiles +- Display quotes count on posts and add quotes list page +- Show a dedicated registration notice page when further action is required after registering + +### Fixed +- Synchronized requested notification types with backend, hopefully should fix missing notifications for polls and follow requests +- Error that appeared on mobile Chromium (and derivatives) when native notifications are allowed +- Being unable to set notification visibility for reports and follow requests +- Native notifications appearing as many times as there are open tabs. Clicking on notification will focus last focused tab. +- The expiry date indication won't be shown if the poll never expires +- Profile mentions causing a 422 error on newer PleromaBE versions. +- Color inputs are less ugly now +- Unread notifications should now properly catch up between sessions (eventually) in polling mode +- Video posters on Safari + + +## 2.6.1 +### Fixed +- fix admin dashboard not having any feedback on frontend installation +- Fix frontend admin tab crashing when no primary frontend is set +- Add aria attributes to react and extra buttons + +## 2.6.0 +### Added +- add the initial i18n translation file for Taiwanese (Hokkien), and modify some related files. +- Implemented a very basic instance administration screen +- Implement quoting + +### Fixed +- Keep aspect ratio of custom emoji reaction in notification +- Fix openSettingsModalTab so that it correctly opens Settings modal instead of Admin modal +- Add alt text to emoji picker buttons +- Use export-subst gitattribute to allow tarball builds +- fix reports now showing reason/content +- Fix HTML attribute parsing, discard attributes not strating with a letter +- Make MentionsLine aware of line breaking by non-br elements +- Fix a bug where mentioning a user twice will not fill the mention into the textarea +- Fix parsing non-ascii tags +- Fix OAuth2 token lingering after revocation +- fix regex issue in HTML parser/renderer +- don't display quoted status twice +- fix typo in code that prevented cards from showing at all +- Fix react button not working if reaction accounts are not loaded +- Fix react button misalignment on safari ios +- Fix pinned statuses gone when reloading user timeline +- Fix scrolling emoji selector in modal in safari ios + +## 2.5.1 +### Fixed +- Checkboxes in settings can now work with screenreaders +- Autocomplete in edit boxes can now work with screenreaders +- Status interact buttons now have focus indicator for anonymous users +- Top bar buttons now correctly have text labels +- It is now possible to register if the site admin requires birthday to register +- User cards from search results will correctly popup +- Fix notification attachment icon overflow +- Editing mute words is less laggy +- Repeater's name will no longer mess up with the directionality of the text sitting on the same line +- Unauthenticated access will give better error messages +- It is now easier to close the media viewer with a mouse when there is only one image +- Deleting profile fields can work properly +- Clicking the react button will correctly focus the search box +- Clicking buttons on the top-bar will no longer bring you to the top of the page +- Emoji picker is much faster to load +- `blockquote`s have a better display style +- Announcements posting and editing are now available to everyone with such a privilege, not just admins +- Adding or removing list members will actually work +- Emojis without a pack are now correctly displayed in emoji picker +- Changing notification settings will actually work + +### Added +- You can now set and see birthdays +- Optional confirmation dialogs when performing various actions +- You can now set fallback languages + ## 2.5.0 - 23.12.2022 ### Fixed - UI no longer lags when switching between mobile and desktop mode diff --git a/build/webpack.base.conf.js b/build/webpack.base.conf.js index bf946922..7e69352a 100644 --- a/build/webpack.base.conf.js +++ b/build/webpack.base.conf.js @@ -6,7 +6,7 @@ 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 @@ -111,6 +111,7 @@ 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({ diff --git a/build/webpack.prod.conf.js b/build/webpack.prod.conf.js index 7de93721..7a108f68 100644 --- a/build/webpack.prod.conf.js +++ b/build/webpack.prod.conf.js @@ -11,9 +11,16 @@ var env = process.env.NODE_ENV === 'testing' ? require('../config/test.env') : config.build.env -let commitHash = require('child_process') - .execSync('git rev-parse --short HEAD') - .toString(); +let commitHash = (() => { + const subst = "$Format:%h$"; + if(!subst.match(/Format:/)) { + return subst; + } else { + return require('child_process') + .execSync('git rev-parse --short HEAD') + .toString(); + } +})(); var webpackConfig = merge(baseWebpackConfig, { mode: 'production', diff --git a/changelog.d/backend-repo-url.skip b/changelog.d/backend-repo-url.skip new file mode 100644 index 00000000..e69de29b diff --git a/changelog.d/better-shadow-control.fix b/changelog.d/better-shadow-control.fix new file mode 100644 index 00000000..585ef6d2 --- /dev/null +++ b/changelog.d/better-shadow-control.fix @@ -0,0 +1 @@ +Updated shadow editor, hopefully fixed long-standing bugs, added ability to specify shadow's name. diff --git a/changelog.d/bookmark-folders.add b/changelog.d/bookmark-folders.add new file mode 100644 index 00000000..f2296660 --- /dev/null +++ b/changelog.d/bookmark-folders.add @@ -0,0 +1 @@ +Support bookmark folders diff --git a/changelog.d/browsers-support.change b/changelog.d/browsers-support.change new file mode 100644 index 00000000..a62e5024 --- /dev/null +++ b/changelog.d/browsers-support.change @@ -0,0 +1,9 @@ +Updated our build system to support browsers: + Safari >= 15 + Firefox >= 115 + Android > 4 + no Opera Mini support + no IE support + no "dead" (unmaintained) browsers support + +This does not guarantee that browsers will or will not work. diff --git a/changelog.d/checkbox.fix b/changelog.d/checkbox.fix new file mode 100644 index 00000000..6a947ec8 --- /dev/null +++ b/changelog.d/checkbox.fix @@ -0,0 +1 @@ +checkbox vertical alignment has been fixed diff --git a/changelog.d/colorfuncs.fix b/changelog.d/colorfuncs.fix new file mode 100644 index 00000000..18c49e2f --- /dev/null +++ b/changelog.d/colorfuncs.fix @@ -0,0 +1 @@ +Fix some of the color manipulation functions diff --git a/changelog.d/custom.add b/changelog.d/custom.add new file mode 100644 index 00000000..97848d7e --- /dev/null +++ b/changelog.d/custom.add @@ -0,0 +1 @@ +Added support for fetching /{resource}.custom.ext to allow adding instance-specific themes without altering sourcetree diff --git a/changelog.d/date-absolute.add b/changelog.d/date-absolute.add new file mode 100644 index 00000000..d9365f46 --- /dev/null +++ b/changelog.d/date-absolute.add @@ -0,0 +1 @@ +Support displaying time in absolute format diff --git a/changelog.d/deprecate-subscribe.change b/changelog.d/deprecate-subscribe.change new file mode 100644 index 00000000..10fb34f4 --- /dev/null +++ b/changelog.d/deprecate-subscribe.change @@ -0,0 +1 @@ +Use /api/v1/accounts/:id/follow for account subscriptions instead of the deprecated routes \ No newline at end of file diff --git a/changelog.d/emoji-size.fix b/changelog.d/emoji-size.fix new file mode 100644 index 00000000..a1189b83 --- /dev/null +++ b/changelog.d/emoji-size.fix @@ -0,0 +1 @@ +fix emoji inconsistencies in notifications, fix some emoji not scaling with interface diff --git a/changelog.d/misc-markup.fix b/changelog.d/misc-markup.fix new file mode 100644 index 00000000..7af934a2 --- /dev/null +++ b/changelog.d/misc-markup.fix @@ -0,0 +1 @@ +Fix small markup inconsistencies diff --git a/changelog.d/multiple-status-mute-reasons.fix b/changelog.d/multiple-status-mute-reasons.fix new file mode 100644 index 00000000..952ccea8 --- /dev/null +++ b/changelog.d/multiple-status-mute-reasons.fix @@ -0,0 +1 @@ +Fix whitespaces for multiple status mute reasons, display bot status reason diff --git a/changelog.d/non-anonymous-polls.add b/changelog.d/non-anonymous-polls.add new file mode 100644 index 00000000..9ff7f3ad --- /dev/null +++ b/changelog.d/non-anonymous-polls.add @@ -0,0 +1 @@ +Inform users that Smithereen public polls are public \ No newline at end of file diff --git a/changelog.d/oauth-app-name.change b/changelog.d/oauth-app-name.change new file mode 100644 index 00000000..15d6f87e --- /dev/null +++ b/changelog.d/oauth-app-name.change @@ -0,0 +1 @@ +Simplify the OAuth client_name to 'PleromaFE' diff --git a/changelog.d/panel-stack.fix b/changelog.d/panel-stack.fix new file mode 100644 index 00000000..518ff32f --- /dev/null +++ b/changelog.d/panel-stack.fix @@ -0,0 +1 @@ +proper sticky header for conversations on user page diff --git a/changelog.d/piss-fix.skip b/changelog.d/piss-fix.skip new file mode 100644 index 00000000..e69de29b diff --git a/changelog.d/piss-serialization.skip b/changelog.d/piss-serialization.skip new file mode 100644 index 00000000..e69de29b diff --git a/changelog.d/quote-buttons.fix b/changelog.d/quote-buttons.fix new file mode 100644 index 00000000..859e5d6c --- /dev/null +++ b/changelog.d/quote-buttons.fix @@ -0,0 +1 @@ +reply-or-quote buttons now take less space diff --git a/changelog.d/show-bookmarks-on-mobile.fix b/changelog.d/show-bookmarks-on-mobile.fix new file mode 100644 index 00000000..3b5db483 --- /dev/null +++ b/changelog.d/show-bookmarks-on-mobile.fix @@ -0,0 +1 @@ +Bookmarks visible again on mobile diff --git a/changelog.d/splashfix.skip b/changelog.d/splashfix.skip new file mode 100644 index 00000000..e69de29b diff --git a/changelog.d/splashscreen.add b/changelog.d/splashscreen.add new file mode 100644 index 00000000..f1f56551 --- /dev/null +++ b/changelog.d/splashscreen.add @@ -0,0 +1 @@ +Splash screen + loading indicator to make process of identifying initialization issues and load performance diff --git a/changelog.d/tabs.change b/changelog.d/tabs.change new file mode 100644 index 00000000..e716ad42 --- /dev/null +++ b/changelog.d/tabs.change @@ -0,0 +1 @@ +Tabs now have indentation for better visibility of which tab is currently active diff --git a/changelog.d/themes3.add b/changelog.d/themes3.add new file mode 100644 index 00000000..040957ce --- /dev/null +++ b/changelog.d/themes3.add @@ -0,0 +1 @@ +UI for making v3 themes and palettes, support for bundling v3 themes diff --git a/changelog.d/user-link.add b/changelog.d/user-link.add new file mode 100644 index 00000000..ca65aa5f --- /dev/null +++ b/changelog.d/user-link.add @@ -0,0 +1 @@ +Make UserLink wrappable diff --git a/changelog.d/weird-absolute-time-format.fix b/changelog.d/weird-absolute-time-format.fix new file mode 100644 index 00000000..727d5876 --- /dev/null +++ b/changelog.d/weird-absolute-time-format.fix @@ -0,0 +1 @@ +Show only month and day instead of weird "day, hour" format. While at it, fixed typo "defualt" in a comment. \ No newline at end of file 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/index.html b/index.html index 4af84a59..63f071aa 100644 --- a/index.html +++ b/index.html @@ -3,12 +3,164 @@ + + + + - - + -
+
+ + +
+ +
+ + + +
+ +

+      
+
+ +
diff --git a/package.json b/package.json index 671a1fc2..fa08593f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pleroma_fe", - "version": "2.5.0", + "version": "2.7.1", "description": "Pleroma frontend, the default frontend of Pleroma social network server", "author": "Pleroma contributors ", "private": false, @@ -11,33 +11,35 @@ "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.20.7", + "@babel/runtime": "7.21.5", "@chenfengyuan/vue-qrcode": "2.0.0", - "@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", + "@fortawesome/fontawesome-svg-core": "6.4.0", + "@fortawesome/free-regular-svg-icons": "6.4.0", + "@fortawesome/free-solid-svg-icons": "6.4.0", + "@fortawesome/vue-fontawesome": "3.0.3", "@kazvmoe-infra/pinch-zoom-element": "1.2.0", "@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", + "@ruffle-rs/ruffle": "0.1.0-nightly.2024.8.21", + "@vuelidate/core": "2.0.3", + "@vuelidate/validators": "2.0.4", "body-scroll-lock": "3.1.5", "chromatism": "3.0.0", "click-outside-vue3": "4.0.1", - "cropperjs": "1.5.12", + "cropperjs": "1.5.13", "escape-html": "1.0.3", - "js-cookie": "3.0.1", + "hash-sum": "^2.0.0", + "js-cookie": "3.0.5", "localforage": "1.10.0", + "pako": "^2.1.0", "parse-link-header": "2.0.0", - "phoenix": "1.6.2", - "punycode.js": "2.1.0", - "qrcode": "1.5.0", + "phoenix": "1.7.7", + "punycode.js": "2.3.0", + "qrcode": "1.5.3", "querystring-es3": "0.2.1", "url": "0.11.0", "utf8": "3.0.0", @@ -49,19 +51,19 @@ "vuex": "4.1.0" }, "devDependencies": { - "@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", - "@intlify/vue-i18n-loader": "5.0.0", - "@ungap/event-target": "0.2.3", + "@babel/core": "7.21.8", + "@babel/eslint-parser": "7.21.8", + "@babel/plugin-transform-runtime": "7.21.4", + "@babel/preset-env": "7.21.5", + "@babel/register": "7.21.0", + "@intlify/vue-i18n-loader": "5.0.1", + "@ungap/event-target": "0.2.4", "@vue/babel-helper-vue-jsx-merge-props": "1.4.0", - "@vue/babel-plugin-jsx": "1.1.1", + "@vue/babel-plugin-jsx": "1.2.2", "@vue/compiler-sfc": "3.2.45", - "@vue/test-utils": "2.2.6", - "autoprefixer": "10.4.13", - "babel-loader": "9.1.0", + "@vue/test-utils": "2.2.8", + "autoprefixer": "10.4.19", + "babel-loader": "9.1.3", "babel-plugin-lodash": "3.3.4", "chai": "4.3.7", "chalk": "1.1.3", @@ -69,27 +71,27 @@ "connect-history-api-fallback": "2.0.0", "copy-webpack-plugin": "11.0.0", "cross-spawn": "7.0.3", - "css-loader": "6.7.3", + "css-loader": "6.10.0", "css-minimizer-webpack-plugin": "4.2.2", "custom-event-polyfill": "1.0.7", - "eslint": "8.31.0", + "eslint": "8.33.0", "eslint-config-standard": "17.0.0", "eslint-formatter-friendly": "7.0.0", - "eslint-plugin-import": "2.26.0", - "eslint-plugin-n": "15.6.0", + "eslint-plugin-import": "2.27.5", + "eslint-plugin-n": "15.6.1", "eslint-plugin-promise": "6.1.1", - "eslint-plugin-vue": "9.8.0", + "eslint-plugin-vue": "9.9.0", "eslint-webpack-plugin": "3.2.0", "eventsource-polyfill": "0.9.6", "express": "4.18.2", "function-bind": "1.1.1", - "html-webpack-plugin": "5.5.0", + "html-webpack-plugin": "5.5.1", "http-proxy-middleware": "2.0.6", "iso-639-1": "2.1.15", "json-loader": "0.5.7", - "karma": "6.4.1", + "karma": "6.4.4", "karma-coverage": "2.2.0", - "karma-firefox-launcher": "2.1.2", + "karma-firefox-launcher": "2.1.3", "karma-mocha": "2.0.1", "karma-mocha-reporter": "2.2.5", "karma-sinon-chai": "2.0.2", @@ -97,24 +99,30 @@ "karma-spec-reporter": "0.0.36", "karma-webpack": "5.0.0", "lodash": "4.17.21", - "mini-css-extract-plugin": "2.7.2", + "mini-css-extract-plugin": "2.7.6", "mocha": "10.2.0", - "nightwatch": "2.6.4", + "nightwatch": "2.6.25", "opn": "5.5.0", "ora": "0.4.1", - "postcss": "8.4.20", + "postcss": "8.4.23", + "postcss-html": "^1.5.0", "postcss-loader": "7.0.2", - "sass": "1.57.1", - "sass-loader": "13.2.0", + "postcss-scss": "^4.0.6", + "sass": "1.60.0", + "sass-loader": "13.2.2", "selenium-server": "2.53.1", "semver": "7.3.8", "serviceworker-webpack5-plugin": "2.0.0", "shelljs": "0.8.5", - "sinon": "15.0.1", + "sinon": "15.0.4", "sinon-chai": "3.7.0", - "stylelint": "13.13.1", - "stylelint-config-standard": "20.0.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", + "stylelint-webpack-plugin": "^3.3.0", "vue-loader": "17.0.1", "vue-style-loader": "4.1.3", "webpack": "5.75.0", @@ -125,5 +133,6 @@ "engines": { "node": ">= 16.0.0", "npm": ">= 3.0.0" - } + }, + "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" } diff --git a/preview.style.js b/preview.style.js new file mode 100644 index 00000000..e69de29b diff --git a/src/App.js b/src/App.js index b7eb2f72..befcece8 100644 --- a/src/App.js +++ b/src/App.js @@ -44,16 +44,32 @@ export default { data: () => ({ mobileActivePanel: 'timeline' }), + watch: { + themeApplied (value) { + this.removeSplash() + } + }, created () { // Load the locale from the storage const val = this.$store.getters.mergedConfig.interfaceLanguage this.$store.dispatch('setOption', { name: 'interfaceLanguage', value: val }) window.addEventListener('resize', this.updateMobileState) }, + mounted () { + if (this.$store.state.interface.themeApplied) { + this.removeSplash() + } + }, unmounted () { window.removeEventListener('resize', this.updateMobileState) }, computed: { + themeApplied () { + return this.$store.state.interface.themeApplied + }, + layoutModalClass () { + return '-' + this.layoutType + }, classes () { return [ { @@ -130,6 +146,15 @@ export default { updateMobileState () { this.$store.dispatch('setLayoutWidth', windowWidth()) this.$store.dispatch('setLayoutHeight', windowHeight()) + }, + removeSplash () { + document.querySelector('#status').textContent = this.$t('splash.fun_' + Math.ceil(Math.random() * 4)) + const splashscreenRoot = document.querySelector('#splash') + splashscreenRoot.addEventListener('transitionend', () => { + splashscreenRoot.remove() + }) + splashscreenRoot.classList.add('hidden') + document.querySelector('#app').classList.remove('hidden') } } } diff --git a/src/App.scss b/src/App.scss index 75b2667c..f52ba06b 100644 --- a/src/App.scss +++ b/src/App.scss @@ -1,8 +1,9 @@ // stylelint-disable rscss/class-format -@import './_variables.scss'; +/* stylelint-disable no-descending-specificity */ +@import "./panel"; :root { - --navbar-height: 3.5rem; + --status-margin: 0.75em; --post-line-height: 1.4; // Z-Index stuff --ZI_media_modal: 9000; @@ -11,19 +12,25 @@ --ZI_navbar_popovers: 7500; --ZI_navbar: 7000; --ZI_popovers: 6000; + + // Fallback for when stuff is loading + --background: var(--bg); } html { - font-size: 14px; + font-size: var(--textSize, 14px); + + --navbar-height: var(--navbarSize, 3.5rem); + --emoji-size: var(--emojiSize, 32px); + --panel-header-height: var(--panelHeaderSize, 3.2rem); // overflow-x: clip causes my browser's tab to crash with SIGILL lul } body { font-family: sans-serif; - font-family: var(--interfaceFont, sans-serif); + font-family: var(--font); margin: 0; - color: $fallback--text; - color: var(--text, $fallback--text); + color: var(--text); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; overscroll-behavior-y: none; @@ -40,17 +47,35 @@ body { // have a cursor/pointer to operate them @media (any-pointer: fine) { * { - scrollbar-color: var(--btn) transparent; + scrollbar-color: var(--fg) transparent; &::-webkit-scrollbar { background: transparent; } + &::-webkit-scrollbar-corner { + background: transparent; + } + + &::-webkit-resizer { + /* stylelint-disable-next-line declaration-no-important */ + background-color: transparent !important; + background-image: + linear-gradient( + 135deg, + transparent calc(50% - 1px), + var(--textFaint) 50%, + transparent calc(50% + 1px), + transparent calc(75% - 1px), + var(--textFaint) 75%, + transparent calc(75% + 1px), + ); + } + &::-webkit-scrollbar-button, &::-webkit-scrollbar-thumb { - background-color: var(--btn); - box-shadow: var(--buttonShadow); - border-radius: var(--btnRadius); + box-shadow: var(--shadow); + border-radius: var(--roundness); } // horizontal/vertical/increment/decrement are webkit-specific stuff @@ -59,7 +84,7 @@ body { &::-webkit-scrollbar-button { --___bgPadding: 2px; - color: var(--btnText); + color: var(--text); background-repeat: no-repeat, no-repeat; &:horizontal { @@ -67,15 +92,15 @@ body { &:increment { background-image: - linear-gradient(45deg, var(--btnText) 50%, transparent 51%), - linear-gradient(-45deg, transparent 50%, var(--btnText) 51%); + linear-gradient(45deg, var(--text) 50%, transparent 51%), + linear-gradient(-45deg, transparent 50%, var(--text) 51%); background-position: top var(--___bgPadding) left 50%, right 50% bottom var(--___bgPadding); } &:decrement { background-image: - linear-gradient(45deg, transparent 50%, var(--btnText) 51%), - linear-gradient(-45deg, var(--btnText) 50%, transparent 51%); + linear-gradient(45deg, transparent 50%, var(--text) calc(50% + 1px)), + linear-gradient(-45deg, var(--text) 50%, transparent 51%); background-position: bottom var(--___bgPadding) right 50%, left 50% top var(--___bgPadding); } } @@ -85,15 +110,15 @@ body { &:increment { background-image: - linear-gradient(-45deg, transparent 50%, var(--btnText) 51%), - linear-gradient(45deg, transparent 50%, var(--btnText) 51%); + linear-gradient(-45deg, transparent 50%, var(--text) 51%), + linear-gradient(45deg, transparent 50%, var(--text) 51%); background-position: right var(--___bgPadding) top 50%, left var(--___bgPadding) top 50%; } &:decrement { background-image: - linear-gradient(-45deg, var(--btnText) 50%, transparent 51%), - linear-gradient(45deg, var(--btnText) 50%, transparent 51%); + linear-gradient(-45deg, var(--text) 50%, transparent 51%), + linear-gradient(45deg, var(--text) 50%, transparent 51%); background-position: left var(--___bgPadding) top 50%, right var(--___bgPadding) top 50%; } } @@ -102,15 +127,14 @@ body { } // Body should have background to scrollbar otherwise it will use white (body color?) html { - scrollbar-color: var(--selectedMenu) var(--wallpaper); + scrollbar-color: var(--fg) var(--wallpaper); background: var(--wallpaper); } } a { text-decoration: none; - color: $fallback--link; - color: var(--link, $fallback--link); + color: var(--link); } h4 { @@ -123,33 +147,18 @@ h4 { font-weight: 1000; } -i[class*=icon-], +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); - } + color: var(--icon); } 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: var(--topBarShadow); + box-shadow: var(--shadow); box-sizing: border-box; height: var(--navbar-height); + font-size: calc(var(--navbar-height) / 3.5); position: fixed; } @@ -191,21 +200,17 @@ 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: var(--underlay); z-index: -1000; } .app-layout { --miniColumn: 25rem; --maxiColumn: 45rem; - --columnGap: 1em; - --status-margin: 0.75em; + --columnGap: 1rem; --effectiveSidebarColumnWidth: minmax(var(--miniColumn), var(--sidebarColumnWidth, var(--miniColumn))); --effectiveNotifsColumnWidth: minmax(var(--miniColumn), var(--notifsColumnWidth, var(--miniColumn))); --effectiveContentColumnWidth: minmax(var(--miniColumn), var(--contentColumnWidth, var(--maxiColumn))); @@ -231,8 +236,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); @@ -307,7 +311,7 @@ nav { align-content: start; } - &.-reverse:not(.-wide):not(.-mobile) { + &.-reverse:not(.-wide, .-mobile) { grid-template-columns: var(--effectiveContentColumnWidth) var(--effectiveSidebarColumnWidth); @@ -336,11 +340,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, @@ -371,100 +372,112 @@ nav { .button-default { user-select: none; - color: $fallback--text; - color: var(--btnText, $fallback--text); - background-color: $fallback--fg; - background-color: var(--btn, $fallback--fg); + color: var(--text); border: none; - border-radius: $fallback--btnRadius; - border-radius: var(--btnRadius, $fallback--btnRadius); cursor: pointer; - box-shadow: $fallback--buttonShadow; - box-shadow: var(--buttonShadow); + background-color: var(--background); + box-shadow: var(--shadow); font-size: 1em; font-family: sans-serif; - font-family: var(--interfaceFont, sans-serif); - - &.-sublime { - background: transparent; - } - - i[class*=icon-], - .svg-inline--fa { - color: $fallback--text; - color: var(--btnText, $fallback--text); - } + font-family: var(--font); &::-moz-focus-inner { border: none; } - &:hover { - box-shadow: 0 0 4px rgba(255, 255, 255, 0.3); - 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: var(--buttonPressedShadow); - color: $fallback--text; - color: var(--btnPressedText, $fallback--text); - background-color: $fallback--fg; - background-color: var(--btnPressed, $fallback--fg); - - svg, - i { - color: $fallback--text; - color: var(--btnPressedText, $fallback--text); - } - } - &:disabled { cursor: not-allowed; - color: $fallback--text; - color: var(--btnDisabledText, $fallback--text); - background-color: $fallback--fg; - background-color: var(--btnDisabled, $fallback--fg); + } +} - svg, - i { - color: $fallback--text; - color: var(--btnDisabledText, $fallback--text); - } +.menu-item, +.list-item { + display: block; + box-sizing: border-box; + border: none; + outline: none; + text-align: initial; + font-size: inherit; + font-family: inherit; + font-weight: 400; + cursor: pointer; + color: inherit; + clear: both; + position: relative; + white-space: nowrap; + border-color: var(--border); + border-style: solid; + border-width: 0; + border-top-width: 1px; + width: 100%; + line-height: var(--__line-height); + padding: var(--__vertical-gap) var(--__horizontal-gap); + background: transparent; + + --__line-height: 1.5em; + --__horizontal-gap: 0.75em; + --__vertical-gap: 0.5em; + + &.-non-interactive { + cursor: auto; } - &.toggled { - color: $fallback--text; - 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: var(--buttonPressedShadow); - - svg, - i { - color: $fallback--text; - color: var(--btnToggledText, $fallback--text); - } + &.-active, + &:hover { + border-top-width: 1px; + border-bottom-width: 1px; } - &.danger { - // TODO: add better color variable - color: $fallback--text; - color: var(--alertErrorPanelText, $fallback--text); - background-color: $fallback--alertError; - background-color: var(--alertError, $fallback--alertError); + &.-active + &, + &:hover + & { + border-top-width: 0; + } + + &:hover + .menu-item-collapsible:not(.-expanded) + &, + &.-active + .menu-item-collapsible:not(.-expanded) + & { + border-top-width: 0; + } + + &[aria-expanded="true"] { + border-bottom-width: 1px; + } + + a, + button:not(.button-default) { + text-align: initial; + padding: 0; + background: none; + border: none; + outline: none; + display: inline; + font-size: 100%; + font-family: inherit; + line-height: unset; + color: var(--text); + } + + &:first-child { + border-top-right-radius: var(--roundness); + border-top-left-radius: var(--roundness); + border-top-width: 0; + } + + &:last-child { + border-bottom-right-radius: var(--roundness); + border-bottom-left-radius: var(--roundness); + border-bottom-width: 0; } } .button-unstyled { - background: none; border: none; outline: none; display: inline; text-align: initial; font-size: 100%; font-family: inherit; + box-shadow: var(--shadow); + background-color: transparent; padding: 0; line-height: unset; cursor: pointer; @@ -472,28 +485,23 @@ nav { color: inherit; &.-link { - color: $fallback--link; - color: var(--link, $fallback--link); - } - - &.-fullwidth { - width: 100%; - } - - &.-hover-highlight { - &:hover svg { - color: $fallback--lightText; - color: var(--lightText, $fallback--lightText); - } + /* stylelint-disable-next-line declaration-no-important */ + color: var(--link) !important; } } input, -textarea, +textarea { + border: none; + display: inline-block; + outline: none; +} + .input { &.unstyled { border-radius: 0; - background: none; + /* stylelint-disable-next-line declaration-no-important */ + background: none !important; box-shadow: none; height: unset; } @@ -501,16 +509,10 @@ textarea, --_padding: 0.5em; 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: var(--inputShadow); - background-color: $fallback--fg; - background-color: var(--input, $fallback--fg); - color: $fallback--lightText; - color: var(--inputText, $fallback--lightText); - font-family: sans-serif; - font-family: var(--inputFont, sans-serif); + background-color: var(--background); + color: var(--text); + box-shadow: var(--shadow); + font-family: var(--font); font-size: 1em; margin: 0; box-sizing: border-box; @@ -521,13 +523,12 @@ 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; @@ -535,13 +536,13 @@ textarea, flex: 1; } - &[type=radio] { + &[type="radio"] { display: none; &:checked + label::before { - box-shadow: 0 0 2px black inset, 0 0 0 4px $fallback--fg inset; - box-shadow: var(--inputShadow), 0 0 0 4px var(--fg, $fallback--fg) inset; - background-color: var(--accent, $fallback--link); + box-shadow: var(--shadow); + background-color: var(--background); + color: var(--text); } &:disabled { @@ -555,16 +556,14 @@ textarea, + label::before { flex-shrink: 0; display: inline-block; - content: ''; + content: "•"; transition: box-shadow 200ms; width: 1.1em; height: 1.1em; border-radius: 100%; // Radio buttons should always be circle - box-shadow: 0 0 2px black inset; - box-shadow: var(--inputShadow); + background-color: var(--background); + box-shadow: var(--shadow); margin-right: 0.5em; - background-color: $fallback--fg; - background-color: var(--input, $fallback--fg); vertical-align: top; text-align: center; line-height: 1.1; @@ -575,12 +574,11 @@ textarea, } } - &[type=checkbox] { - display: none; - + &[type="checkbox"] { &:checked + label::before { - color: $fallback--text; - color: var(--inputText, $fallback--text); + color: var(--text); + background-color: var(--background); + box-shadow: var(--shadow); } &:disabled { @@ -594,17 +592,13 @@ textarea, + label::before { flex-shrink: 0; display: inline-block; - content: '✓'; + content: "✓"; transition: color 200ms; width: 1.1em; height: 1.1em; - border-radius: $fallback--checkboxRadius; - border-radius: var(--checkboxRadius, $fallback--checkboxRadius); - box-shadow: 0 0 2px black inset; - box-shadow: var(--inputShadow); + border-radius: var(--roundness); + box-shadow: var(--shadow); margin-right: 0.5em; - background-color: $fallback--fg; - background-color: var(--input, $fallback--fg); vertical-align: top; text-align: center; line-height: 1.1; @@ -620,29 +614,52 @@ textarea, } } +.input, +.button-default { + --_roundness-left: var(--roundness); + --_roundness-right: var(--roundness); + + border-top-left-radius: var(--_roundness-left); + border-bottom-left-radius: var(--_roundness-left); + border-top-right-radius: var(--_roundness-right); + border-bottom-right-radius: var(--_roundness-right); +} + // Textareas should have stock line-height + vertical padding instead of huge line-height -textarea { +textarea.input { padding: var(--_padding); line-height: var(--post-line-height); } option { - color: $fallback--text; - color: var(--text, $fallback--text); - background-color: $fallback--bg; - background-color: var(--bg, $fallback--bg); + color: var(--text); + background-color: var(--background); } .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; } } +.cards-list { + list-style: none; + display: grid; + grid-auto-flow: row dense; + grid-template-columns: 1fr 1fr; + + li { + border: 1px solid var(--border); + border-radius: var(--roundness); + padding: 0.5em; + margin: 0.25em; + } +} + .btn-block { display: block; width: 100%; @@ -653,24 +670,26 @@ option { display: inline-flex; vertical-align: middle; - button { + > *, + > * .button-default { + --_roundness-left: 0; + --_roundness-right: 0; + position: relative; flex: 1 1 auto; + } - &:not(:last-child) { - border-top-right-radius: 0; - border-bottom-right-radius: 0; - } + > *:first-child, + > *:first-child .button-default { + --_roundness-left: var(--roundness); + } - &:not(:first-child) { - border-top-left-radius: 0; - border-bottom-left-radius: 0; - } + > *:last-child, + > *:last-child .button-default { + --_roundness-right: var(--roundness); } } -@import './panel.scss'; - .fa { color: grey; } @@ -686,7 +705,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; @@ -697,74 +716,58 @@ option { overflow: hidden; text-overflow: ellipsis; - &.badge-notification { - background-color: $fallback--cRed; - background-color: var(--badgeNotification, $fallback--cRed); - color: white; - color: var(--badgeNotificationText, white); + &.-dot, + &.-counter { + margin: 0; + position: absolute; + } + + &.-dot { + min-height: 8px; + max-height: 8px; + min-width: 8px; + max-width: 8px; + padding: 0; + line-height: 0; + font-size: 0; + left: calc(50% - 4px); + top: calc(50% - 4px); + margin-left: 6px; + margin-top: -6px; + } + + &.-counter { + border-radius: var(--roundness); + font-size: 0.75em; + line-height: 1; + text-align: right; + padding: 0.2em; + min-width: 0; + left: calc(50% - 0.5em); + top: calc(50% - 0.4em); + margin-left: 0.7em; + margin-top: -1em; } } .alert { margin: 0 0.35em; padding: 0 0.25em; - border-radius: $fallback--tooltipRadius; - border-radius: var(--tooltipRadius, $fallback--tooltipRadius); - - &.error { - background-color: $fallback--alertError; - background-color: var(--alertError, $fallback--alertError); - color: $fallback--text; - color: var(--alertErrorText, $fallback--text); - - .panel-heading & { - color: $fallback--text; - color: var(--alertErrorPanelText, $fallback--text); - } - } - - &.warning { - background-color: $fallback--alertWarning; - background-color: var(--alertWarning, $fallback--alertWarning); - color: $fallback--text; - color: var(--alertWarningText, $fallback--text); - - .panel-heading & { - color: $fallback--text; - color: var(--alertWarningPanelText, $fallback--text); - } - } - - &.success { - background-color: var(--alertSuccess, $fallback--alertWarning); - color: var(--alertSuccessText, $fallback--text); - - .panel-heading & { - color: var(--alertSuccessPanelText, $fallback--text); - } - } + border-radius: var(--roundness); + border: 1px solid var(--border); } .faint { - color: $fallback--faint; - color: var(--faint, $fallback--faint); -} + --text: var(--textFaint); + --link: var(--linkFaint); -.faint-link { - color: $fallback--faint; - color: var(--faint, $fallback--faint); - - &:hover { - text-decoration: underline; - } + color: var(--text); } .visibility-notice { padding: 0.5em; - border: 1px solid $fallback--faint; - border: 1px solid var(--faint, $fallback--faint); - border-radius: $fallback--inputRadius; - border-radius: var(--inputRadius, $fallback--inputRadius); + border: 1px solid var(--textFaint); + border-radius: var(--roundness); } .notice-dismissible { @@ -785,11 +788,16 @@ option { &.iconLetter { font-size: 1.1em; } + + &.svg-inline--fa { + vertical-align: -0.15em; + } } .fa-old-padding { &.iconLetter, - &.svg-inline--fa, &-layer { + &.svg-inline--fa, + &-layer { padding: 0 0.3em; } } @@ -798,6 +806,11 @@ option { opacity: 0.25; } +.timeago { + --link: var(--text); + --linkFaint: var(--textFaint); +} + .login-hint { text-align: center; @@ -883,3 +896,187 @@ option { .fade-leave-active { opacity: 0; } +/* stylelint-enable no-descending-specificity */ + +.visible-for-screenreader-only { + display: block; + width: 1px; + height: 1px; + margin: -1px; + overflow: hidden; + visibility: visible; + clip: rect(0 0 0 0); + padding: 0; + position: absolute; +} + +*::selection { + color: var(--selectionText); + background-color: var(--selectionBackground); +} + +#splash { + pointer-events: none; + transition: opacity 2s; + opacity: 1; + + &.hidden { + opacity: 0; + } + + #status { + &.css-ok { + &::before { + display: inline-block; + content: "CSS OK"; + } + } + + .initial-text { + display: none; + } + } + + #throbber { + animation-duration: 3s; + animation-name: bounce; + animation-iteration-count: infinite; + animation-direction: normal; + transform-origin: bottom center; + + &.dead { + animation-name: dead; + animation-duration: 2s; + animation-iteration-count: 1; + transform: rotateX(90deg) rotateY(0) rotateZ(-45deg); + } + + @keyframes dead { + 0% { + transform: rotateX(0) rotateY(0) rotateZ(0); + } + + 5% { + transform: rotateX(0) rotateY(0) rotateZ(1deg); + } + + 10% { + transform: rotateX(0) rotateY(0) rotateZ(-2deg); + } + + 15% { + transform: rotateX(0) rotateY(0) rotateZ(3deg); + } + + 20% { + transform: rotateX(0) rotateY(0) rotateZ(0); + } + + 25% { + transform: rotateX(0) rotateY(0) rotateZ(0); + } + + 30% { + transform: rotateX(10deg) rotateY(0) rotateZ(0); + } + + 35% { + transform: rotateX(-10deg) rotateY(0) rotateZ(0); + } + + 40% { + transform: rotateX(10deg) rotateY(0) rotateZ(0); + } + + 45% { + transform: rotateX(-10deg) rotateY(0) rotateZ(0); + } + + 50% { + transform: rotateX(10deg) rotateY(0) rotateZ(0); + } + + 100% { + transform: rotateX(90deg) rotateY(0) rotateZ(-45deg); + transition-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); /* easeInQuint */ + } + } + + @keyframes bounce { + 0% { + scale: 1 1; + translate: 0 0; + animation-timing-function: ease-out; + } + + 10% { + scale: 1.2 0.8; + translate: 0 0; + transform: rotateZ(var(--defaultZ)); + animation-timing-function: ease-out; + } + + 30% { + scale: 0.9 1.1; + translate: 0 -40%; + transform: rotateZ(var(--defaultZ)); + animation-timing-function: ease-in; + } + + 40% { + scale: 1.1 0.9; + translate: 0 -50%; + transform: rotateZ(var(--defaultZ)); + animation-timing-function: ease-in; + } + + 45% { + scale: 0.9 1.1; + translate: 0 -45%; + transform: rotateZ(var(--defaultZ)); + animation-timing-function: ease-in; + } + + 50% { + scale: 1.05 0.95; + translate: 0 -40%; + animation-timing-function: ease-in; + } + + 55% { + scale: 0.985 1.025; + translate: 0 -35%; + transform: rotateZ(var(--defaultZ)); + animation-timing-function: ease-in; + } + + 60% { + scale: 1.0125 0.9985; + translate: 0 -30%; + transform: rotateZ(var(--defaultZ)); + animation-timing-function: ease-in; + } + + 80% { + scale: 1.0063 0.9938; + translate: 0 -10%; + transform: rotateZ(var(--defaultZ)); + animation-timing-function: ease-in-ou; + } + + 90% { + scale: 1.2 0.8; + translate: 0 0; + transform: rotateZ(var(--defaultZ)); + animation-timing-function: ease-out; + } + + 100% { + scale: 1 1; + translate: 0 0; + transform: rotateZ(var(--defaultZ)); + animation-timing-function: ease-out; + } + } + } +} diff --git a/src/App.vue b/src/App.vue index 23a388a6..762e1851 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,5 +1,6 @@ diff --git a/src/_mixins.scss b/src/_mixins.scss index 1fed16c3..e99fe26f 100644 --- a/src/_mixins.scss +++ b/src/_mixins.scss @@ -1,13 +1,14 @@ @mixin unfocused-style { @content; - &:focus:not(:focus-visible):not(:hover) { + &:focus:not(:focus-visible, :hover) { @content; } } @mixin focused-style { - &:hover, &:focus { + &:hover, + &:focus { @content; } diff --git a/src/_variables.scss b/src/_variables.scss deleted file mode 100644 index 099d3606..00000000 --- a/src/_variables.scss +++ /dev/null @@ -1,34 +0,0 @@ -$main-color: #f58d2c; -$main-background: white; -$darkened-background: whitesmoke; - -$fallback--bg: #121a24; -$fallback--fg: #182230; -$fallback--faint: rgba(185, 185, 186, .5); -$fallback--text: #b9b9ba; -$fallback--link: #d8a070; -$fallback--icon: #666; -$fallback--lightBg: rgb(21, 30, 42); -$fallback--lightText: #b9b9ba; -$fallback--border: #222; -$fallback--cRed: #ff0000; -$fallback--cBlue: #0095ff; -$fallback--cGreen: #0fa00f; -$fallback--cOrange: orange; - -$fallback--alertError: rgba(211,16,20,.5); -$fallback--alertWarning: rgba(111,111,20,.5); - -$fallback--panelRadius: 10px; -$fallback--checkboxRadius: 2px; -$fallback--btnRadius: 4px; -$fallback--inputRadius: 4px; -$fallback--tooltipRadius: 5px; -$fallback--avatarRadius: 4px; -$fallback--avatarAltRadius: 10px; -$fallback--attachmentRadius: 10px; -$fallback--chatMessageRadius: 10px; - -$fallback--buttonShadow: 0px 0px 2px 0px rgba(0, 0, 0, 1), 0px 1px 0px 0px rgba(255, 255, 255, 0.2) inset, 0px -1px 0px 0px rgba(0, 0, 0, 0.2) inset; - -$status-margin: 0.75em; diff --git a/src/assets/pleromatan_apology.png b/src/assets/pleromatan_apology.png deleted file mode 100644 index 36ad7aeb..00000000 Binary files a/src/assets/pleromatan_apology.png and /dev/null differ diff --git a/src/assets/pleromatan_apology.png b/src/assets/pleromatan_apology.png new file mode 120000 index 00000000..a7f6191f --- /dev/null +++ b/src/assets/pleromatan_apology.png @@ -0,0 +1 @@ +../../static/pleromatan_apology.png \ No newline at end of file diff --git a/src/assets/pleromatan_apology_fox.png b/src/assets/pleromatan_apology_fox.png deleted file mode 100644 index 17f87694..00000000 Binary files a/src/assets/pleromatan_apology_fox.png and /dev/null differ diff --git a/src/assets/pleromatan_apology_fox.png b/src/assets/pleromatan_apology_fox.png new file mode 120000 index 00000000..b3db4af3 --- /dev/null +++ b/src/assets/pleromatan_apology_fox.png @@ -0,0 +1 @@ +../../static/pleromatan_apology_fox.png \ No newline at end of file diff --git a/src/boot/after_store.js b/src/boot/after_store.js index 1fa9dd2a..65a15178 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -13,9 +13,9 @@ import VBodyScrollLock from 'src/directives/body_scroll_lock' import { windowWidth, windowHeight } from '../services/window_utils/window_utils' import { getOrCreateApp, getClientToken } from '../services/new_api/oauth.js' import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js' -import { CURRENT_VERSION } from '../services/theme_data/theme_data.service.js' -import { applyTheme, applyConfig } from '../services/style_setter/style_setter.js' +import { applyConfig } from '../services/style_setter/style_setter.js' import FaviconService from '../services/favicon_service/favicon_service.js' +import { initServiceWorker, updateFocus } from '../services/sw/sw.js' let staticInitialResults = null @@ -60,6 +60,8 @@ const getInstanceConfig = async ({ store }) => { store.dispatch('setInstanceOption', { name: 'textlimit', value: textlimit }) store.dispatch('setInstanceOption', { name: 'accountApprovalRequired', value: data.approval_required }) + store.dispatch('setInstanceOption', { name: 'birthdayRequired', value: !!data.pleroma.metadata.birthday_required }) + store.dispatch('setInstanceOption', { name: 'birthdayMinAge', value: data.pleroma.metadata.birthday_min_age || 0 }) if (vapidPublicKey) { store.dispatch('setInstanceOption', { name: 'vapidPublicKey', value: vapidPublicKey }) @@ -120,6 +122,9 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => { store.dispatch('setInstanceOption', { name, value: config[name] }) } + copyInstanceOption('theme') + copyInstanceOption('style') + copyInstanceOption('palette') copyInstanceOption('nsfwCensorImage') copyInstanceOption('background') copyInstanceOption('hidePostStats') @@ -157,8 +162,6 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => { copyInstanceOption('showFeaturesPanel') copyInstanceOption('hideSitename') copyInstanceOption('sidebarRight') - - return store.dispatch('setTheme', config.theme) } const getTOS = async ({ store }) => { @@ -240,7 +243,7 @@ const resolveStaffAccounts = ({ store, accounts }) => { const getNodeInfo = async ({ store }) => { try { - const res = await preloadFetch('/nodeinfo/2.0.json') + const res = await preloadFetch('/nodeinfo/2.1.json') if (res.ok) { const data = await res.json() const metadata = data.metadata @@ -251,11 +254,15 @@ const getNodeInfo = async ({ store }) => { store.dispatch('setInstanceOption', { name: 'safeDM', value: features.includes('safe_dm_mentions') }) store.dispatch('setInstanceOption', { name: 'shoutAvailable', value: features.includes('chat') }) store.dispatch('setInstanceOption', { name: 'pleromaChatMessagesAvailable', value: features.includes('pleroma_chat_messages') }) + store.dispatch('setInstanceOption', { name: 'pleromaCustomEmojiReactionsAvailable', value: features.includes('pleroma_custom_emoji_reactions') }) + store.dispatch('setInstanceOption', { name: 'pleromaBookmarkFoldersAvailable', value: features.includes('pleroma:bookmark_folders') }) store.dispatch('setInstanceOption', { name: 'gopherAvailable', value: features.includes('gopher') }) store.dispatch('setInstanceOption', { name: 'pollsAvailable', value: features.includes('polls') }) store.dispatch('setInstanceOption', { name: 'editingAvailable', value: features.includes('editing') }) store.dispatch('setInstanceOption', { name: 'pollLimits', value: metadata.pollLimits }) store.dispatch('setInstanceOption', { name: 'mailerEnabled', value: metadata.mailerEnabled }) + store.dispatch('setInstanceOption', { name: 'quotingAvailable', value: features.includes('quote_posting') }) + store.dispatch('setInstanceOption', { name: 'groupActorAvailable', value: features.includes('pleroma:group_actors') }) const uploadLimits = metadata.uploadLimits store.dispatch('setInstanceOption', { name: 'uploadlimit', value: parseInt(uploadLimits.general) }) @@ -273,6 +280,7 @@ const getNodeInfo = async ({ store }) => { const software = data.software store.dispatch('setInstanceOption', { name: 'backendVersion', value: software.version }) + store.dispatch('setInstanceOption', { name: 'backendRepository', value: software.repository }) store.dispatch('setInstanceOption', { name: 'pleromaBackend', value: software.name === 'pleroma' }) const priv = metadata.private @@ -322,17 +330,10 @@ const setConfig = async ({ store }) => { } const checkOAuthToken = async ({ store }) => { - // eslint-disable-next-line no-async-promise-executor - return new Promise(async (resolve, reject) => { - if (store.getters.getUserToken()) { - try { - await store.dispatch('loginUser', store.getters.getUserToken()) - } catch (e) { - console.error(e) - } - } - resolve() - }) + if (store.getters.getUserToken()) { + return store.dispatch('loginUser', store.getters.getUserToken()) + } + return Promise.resolve() } const afterStoreSetup = async ({ store, i18n }) => { @@ -340,30 +341,23 @@ const afterStoreSetup = async ({ store, i18n }) => { store.dispatch('setLayoutHeight', windowHeight()) FaviconService.initFaviconService() + initServiceWorker(store) + + window.addEventListener('focus', () => updateFocus()) const overrides = window.___pleromafe_dev_overrides || {} const server = (typeof overrides.target !== 'undefined') ? overrides.target : window.location.origin store.dispatch('setInstanceOption', { name: 'server', value: server }) await setConfig({ store }) - - const { customTheme, customThemeSource } = store.state.config - const { theme } = store.state.instance - const customThemePresent = customThemeSource || customTheme - - if (customThemePresent) { - if (customThemeSource && customThemeSource.themeEngineVersion === CURRENT_VERSION) { - applyTheme(customThemeSource) - } else { - applyTheme(customTheme) - } - } else if (theme) { - // do nothing, it will load asynchronously - } else { - console.error('Failed to load any theme!') + try { + await store.dispatch('applyTheme').catch((e) => { console.error('Error setting theme', e) }) + } catch (e) { + window.splashError(e) + return Promise.reject(e) } - applyConfig(store.state.config) + applyConfig(store.state.config, i18n.global) // Now we can try getting the server settings and logging in // Most of these are preloaded into the index.html so blocking is minimized @@ -372,7 +366,7 @@ const afterStoreSetup = async ({ store, i18n }) => { getInstancePanel({ store }), getNodeInfo({ store }), getInstanceConfig({ store }) - ]) + ]).catch(e => Promise.reject(e)) // Start fetching things that don't need to block the UI store.dispatch('fetchMutes') @@ -397,6 +391,13 @@ const afterStoreSetup = async ({ store, i18n }) => { app.use(store) app.use(i18n) + // Little thing to get out of invalid theme state + window.resetThemes = () => { + store.dispatch('resetThemeV3') + store.dispatch('resetThemeV3Palette') + store.dispatch('resetThemeV2') + } + app.use(vClickOutside) app.use(VBodyScrollLock) app.use(VueVirtualScroller) @@ -408,7 +409,6 @@ const afterStoreSetup = async ({ store, i18n }) => { app.config.unwrapInjectedRef = true app.mount('#app') - return app } diff --git a/src/boot/routes.js b/src/boot/routes.js index 2dc900e7..f87b2ec8 100644 --- a/src/boot/routes.js +++ b/src/boot/routes.js @@ -25,6 +25,9 @@ import ListsTimeline from 'components/lists_timeline/lists_timeline.vue' import ListsEdit from 'components/lists_edit/lists_edit.vue' import NavPanel from 'src/components/nav_panel/nav_panel.vue' import AnnouncementsPage from 'components/announcements_page/announcements_page.vue' +import QuotesTimeline from '../components/quotes_timeline/quotes_timeline.vue' +import BookmarkFolders from '../components/bookmark_folders/bookmark_folders.vue' +import BookmarkFolderEdit from '../components/bookmark_folder_edit/bookmark_folder_edit.vue' export default (store) => { const validateAuthenticatedRoute = (to, from, next) => { @@ -51,6 +54,7 @@ export default (store) => { { name: 'tag-timeline', path: '/tag/:tag', component: TagTimeline }, { name: 'bookmarks', path: '/bookmarks', component: BookmarkTimeline }, { name: 'conversation', path: '/notice/:id', component: ConversationPage, meta: { dontScroll: true } }, + { name: 'quotes', path: '/notice/:id/quotes', component: QuotesTimeline }, { name: 'remote-user-profile-acct', path: '/remote-users/:_(@)?:username([^/@]+)@:hostname([^/@]+)', @@ -84,7 +88,11 @@ export default (store) => { { name: 'lists-timeline', path: '/lists/:id', component: ListsTimeline }, { name: 'lists-edit', path: '/lists/:id/edit', component: ListsEdit }, { name: 'lists-new', path: '/lists/new', component: ListsEdit }, - { name: 'edit-navigation', path: '/nav-edit', component: NavPanel, props: () => ({ forceExpand: true, forceEditMode: true }), beforeEnter: validateAuthenticatedRoute } + { name: 'edit-navigation', path: '/nav-edit', component: NavPanel, props: () => ({ forceExpand: true, forceEditMode: true }), beforeEnter: validateAuthenticatedRoute }, + { name: 'bookmark-folders', path: '/bookmark_folders', component: BookmarkFolders }, + { name: 'bookmark-folder-new', path: '/bookmarks/new-folder', component: BookmarkFolderEdit }, + { name: 'bookmark-folder', path: '/bookmarks/:id', component: BookmarkTimeline }, + { name: 'bookmark-folder-edit', path: '/bookmarks/:id/edit', component: BookmarkFolderEdit } ] if (store.state.instance.pleromaChatMessagesAvailable) { diff --git a/src/components/about/about.vue b/src/components/about/about.vue index 33586c97..8a551485 100644 --- a/src/components/about/about.vue +++ b/src/components/about/about.vue @@ -9,6 +9,3 @@ - - diff --git a/src/components/account_actions/account_actions.js b/src/components/account_actions/account_actions.js index c23407f9..acd93e06 100644 --- a/src/components/account_actions/account_actions.js +++ b/src/components/account_actions/account_actions.js @@ -2,6 +2,7 @@ import { mapState } from 'vuex' import ProgressButton from '../progress_button/progress_button.vue' import Popover from '../popover/popover.vue' import UserListMenu from 'src/components/user_list_menu/user_list_menu.vue' +import ConfirmModal from '../confirm_modal/confirm_modal.vue' import { library } from '@fortawesome/fontawesome-svg-core' import { faEllipsisV @@ -16,14 +17,30 @@ const AccountActions = { 'user', 'relationship' ], data () { - return { } + return { + showingConfirmBlock: false, + showingConfirmRemoveFollower: false + } }, components: { ProgressButton, Popover, - UserListMenu + UserListMenu, + ConfirmModal }, methods: { + showConfirmBlock () { + this.showingConfirmBlock = true + }, + hideConfirmBlock () { + this.showingConfirmBlock = false + }, + showConfirmRemoveUserFromFollowers () { + this.showingConfirmRemoveFollower = true + }, + hideConfirmRemoveUserFromFollowers () { + this.showingConfirmRemoveFollower = false + }, showRepeats () { this.$store.dispatch('showReblogs', this.user.id) }, @@ -31,13 +48,29 @@ const AccountActions = { this.$store.dispatch('hideReblogs', this.user.id) }, blockUser () { + if (!this.shouldConfirmBlock) { + this.doBlockUser() + } else { + this.showConfirmBlock() + } + }, + doBlockUser () { this.$store.dispatch('blockUser', this.user.id) + this.hideConfirmBlock() }, unblockUser () { this.$store.dispatch('unblockUser', this.user.id) }, removeUserFromFollowers () { + if (!this.shouldConfirmRemoveUserFromFollowers) { + this.doRemoveUserFromFollowers() + } else { + this.showConfirmRemoveUserFromFollowers() + } + }, + doRemoveUserFromFollowers () { this.$store.dispatch('removeUserFromFollowers', this.user.id) + this.hideConfirmRemoveUserFromFollowers() }, reportUser () { this.$store.dispatch('openUserReportingModal', { userId: this.user.id }) @@ -50,6 +83,12 @@ const AccountActions = { } }, computed: { + shouldConfirmBlock () { + return this.$store.getters.mergedConfig.modalOnBlock + }, + shouldConfirmRemoveUserFromFollowers () { + return this.$store.getters.mergedConfig.modalOnRemoveUserFromFollowers + }, ...mapState({ pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable }) diff --git a/src/components/account_actions/account_actions.vue b/src/components/account_actions/account_actions.vue index 218aa6b3..62862a8f 100644 --- a/src/components/account_actions/account_actions.vue +++ b/src/components/account_actions/account_actions.vue @@ -11,14 +11,14 @@ + + + + + + + + + + + + + +
diff --git a/src/components/alert.style.js b/src/components/alert.style.js new file mode 100644 index 00000000..86851476 --- /dev/null +++ b/src/components/alert.style.js @@ -0,0 +1,57 @@ +export default { + name: 'Alert', + selector: '.alert', + validInnerComponents: [ + 'Text', + 'Icon', + 'Link', + 'Border', + 'ButtonUnstyled' + ], + variants: { + normal: '.neutral', + error: '.error', + warning: '.warning', + success: '.success' + }, + editor: { + border: 1, + aspect: '3 / 1' + }, + defaultRules: [ + { + directives: { + background: '--text', + opacity: 0.5, + blur: '9px' + } + }, + { + parent: { + component: 'Alert' + }, + component: 'Border', + directives: { + textColor: '--parent' + } + }, + { + variant: 'error', + directives: { + background: '--cRed' + } + }, + { + variant: 'warning', + directives: { + background: '--cOrange' + } + }, + { + variant: 'success', + directives: { + background: '--cGreen' + } + } + ] +} diff --git a/src/components/announcement/announcement.vue b/src/components/announcement/announcement.vue index c1b35981..92880831 100644 --- a/src/components/announcement/announcement.vue +++ b/src/components/announcement/announcement.vue @@ -99,22 +99,20 @@ diff --git a/src/components/attachment/attachment.js b/src/components/attachment/attachment.js index 5dc50475..6e14b24d 100644 --- a/src/components/attachment/attachment.js +++ b/src/components/attachment/attachment.js @@ -36,6 +36,7 @@ library.add( const Attachment = { props: [ 'attachment', + 'compact', 'description', 'hideDescription', 'nsfw', @@ -71,7 +72,8 @@ const Attachment = { { '-loading': this.loading, '-nsfw-placeholder': this.hidden, - '-editable': this.edit !== undefined + '-editable': this.edit !== undefined, + '-compact': this.compact }, '-type-' + this.type, this.size && '-size-' + this.size, diff --git a/src/components/attachment/attachment.scss b/src/components/attachment/attachment.scss index b2dea98d..13afbe64 100644 --- a/src/components/attachment/attachment.scss +++ b/src/components/attachment/attachment.scss @@ -1,5 +1,3 @@ -@import '../../_variables.scss'; - .Attachment { display: inline-flex; flex-direction: column; @@ -9,10 +7,8 @@ height: 100%; border-style: solid; border-width: 1px; - border-radius: $fallback--attachmentRadius; - border-radius: var(--attachmentRadius, $fallback--attachmentRadius); - border-color: $fallback--border; - border-color: var(--border, $fallback--border); + border-radius: var(--roundness); + border-color: var(--border); .attachment-wrapper { flex: 1 1 auto; @@ -84,6 +80,13 @@ } } + .video-container { + border: none; + outline: none; + color: inherit; + background: transparent; + } + .audio-container { display: flex; align-items: flex-end; @@ -102,14 +105,13 @@ padding-top: 0.5em; } - .play-icon { position: absolute; font-size: 64px; top: calc(50% - 32px); left: calc(50% - 32px); - color: rgba(255, 255, 255, 0.75); - text-shadow: 0 0 2px rgba(0, 0, 0, 0.4); + color: rgb(255 255 255 / 75%); + text-shadow: 0 0 2px rgb(0 0 0 / 40%); &::before { margin: 0; @@ -127,23 +129,26 @@ .attachment-button { padding: 0; - border-radius: $fallback--tooltipRadius; - border-radius: var(--tooltipRadius, $fallback--tooltipRadius); + border-radius: var(--roundness); text-align: center; width: 2em; height: 2em; margin-left: 0.5em; font-size: 1.25em; - // TODO: theming? hard to theme with unknown background image color - background: rgba(230, 230, 230, 0.7); + } + } - .svg-inline--fa { - color: rgba(0, 0, 0, 0.6); - } + &.-contain-fit { + img, + canvas { + object-fit: contain; + } + } - &:hover .svg-inline--fa { - color: rgba(0, 0, 0, 0.9); - } + &.-cover-fit { + img, + canvas { + object-fit: cover; } } @@ -160,8 +165,9 @@ .image { flex: 1; + img { - border: 0px; + border: 0; border-radius: 5px; height: 100%; object-fit: cover; @@ -172,9 +178,10 @@ flex: 2; margin: 8px; word-break: break-all; + h1 { font-size: 1rem; - margin: 0px; + margin: 0; } } } @@ -202,8 +209,7 @@ &.-placeholder { display: inline-block; - color: $fallback--link; - color: var(--postLink, $fallback--link); + color: var(--link); overflow: hidden; white-space: nowrap; height: auto; @@ -252,17 +258,9 @@ cursor: progress; } - &.-contain-fit { - img, - canvas { - object-fit: contain; - } - } - - &.-cover-fit { - img, - canvas { - object-fit: cover; + &.-compact { + .placeholder-container { + padding-bottom: 0.5em; } } } diff --git a/src/components/attachment/attachment.style.js b/src/components/attachment/attachment.style.js new file mode 100644 index 00000000..fde8a50a --- /dev/null +++ b/src/components/attachment/attachment.style.js @@ -0,0 +1,25 @@ +export default { + name: 'Attachment', + selector: '.Attachment', + notEditable: true, + validInnerComponents: [ + 'Border', + 'ButtonUnstyled', + 'Input' + ], + defaultRules: [ + { + directives: { + roundness: 3 + } + }, + { + component: 'ButtonUnstyled', + parent: { component: 'Attachment' }, + directives: { + background: '#FFFFFF', + opacity: 0.5 + } + } + ] +} diff --git a/src/components/attachment/attachment.vue b/src/components/attachment/attachment.vue index 2a89886d..9abc2627 100644 --- a/src/components/attachment/attachment.vue +++ b/src/components/attachment/attachment.vue @@ -38,7 +38,7 @@ v-if="edit" v-model="localDescription" type="text" - class="description-field" + class="input description-field" :placeholder="$t('post_status.media_description')" @keydown.enter.prevent="" > @@ -162,10 +162,11 @@ target="_blank" > -

+

{{ localDescription }}

@@ -174,7 +175,6 @@ :is="videoTag" v-if="type === 'video' && !hidden" class="video-container" - :class="{ 'button-unstyled': 'isModal' }" :href="attachment.url" @click.stop.prevent="openModal" > @@ -252,7 +252,7 @@ v-if="edit" v-model="localDescription" type="text" - class="description-field" + class="input description-field" :placeholder="$t('post_status.media_description')" @keydown.enter.prevent="" > diff --git a/src/components/autosuggest/autosuggest.vue b/src/components/autosuggest/autosuggest.vue index f283ab82..04f41801 100644 --- a/src/components/autosuggest/autosuggest.vue +++ b/src/components/autosuggest/autosuggest.vue @@ -1,3 +1,4 @@ + diff --git a/src/components/border.style.js b/src/components/border.style.js new file mode 100644 index 00000000..7f2c3016 --- /dev/null +++ b/src/components/border.style.js @@ -0,0 +1,13 @@ +export default { + name: 'Border', + selector: '/*border*/', + virtual: true, + defaultRules: [ + { + directives: { + textColor: '$mod(--parent 10)', + textAuto: 'no-auto' + } + } + ] +} diff --git a/src/components/button.style.js b/src/components/button.style.js new file mode 100644 index 00000000..747cfd5e --- /dev/null +++ b/src/components/button.style.js @@ -0,0 +1,129 @@ +export default { + name: 'Button', // Name of the component + selector: '.button-default', // CSS selector/prefix + // outOfTreeSelector: '' // out-of-tree selector is used when other components are laid over it but it's not part of the tree, see Underlay component + // States, system witll calculate ALL possible combinations of those and prepend "normal" to them + standalone "normal" state + states: { + // States are a bit expensive - the amount of combinations generated is about (1/6)n^3+n, so adding more state increased number of combination by an order of magnitude! + // All states inherit from "normal" state, there is no other inheirtance, i.e. hover+disabled only inherits from "normal", not from hover nor disabled. + // However, cascading still works, so resulting state will be result of merging of all relevant states/variants + // normal: '' // normal state is implicitly added, it is always included + toggled: '.toggled', + focused: ':focus-visible', + pressed: ':focus:active', + hover: ':hover:not(:disabled)', + disabled: ':disabled' + }, + // Variants are mutually exclusive, each component implicitly has "normal" variant, and all other variants inherit from it. + variants: { + // Variants save on computation time since adding new variant just adds one more "set". + // normal: '', // you can override normal variant, it will be appenended to the main class + danger: '.danger' + // Overall the compuation difficulty is N*((1/6)M^3+M) where M is number of distinct states and N is number of variants. + // This (currently) is further multipled by number of places where component can exist. + }, + editor: { + aspect: '2 / 1' + }, + // This lists all other components that can possibly exist within one. Recursion is currently not supported (and probably won't be supported ever). + validInnerComponents: [ + 'Text', + 'Icon' + ], + // Default rules, used as "default theme", essentially. + defaultRules: [ + { + component: 'Root', + directives: { + '--buttonDefaultHoverGlow': 'shadow | 0 0 4 --text / 0.5', + '--buttonDefaultFocusGlow': 'shadow | 0 0 4 4 --link / 0.5', + '--buttonDefaultShadow': 'shadow | 0 0 2 #000000', + '--buttonDefaultBevel': 'shadow | $borderSide(#FFFFFF top 0.2 2), $borderSide(#000000 bottom 0.2 2)', + '--buttonPressedBevel': 'shadow | $borderSide(#FFFFFF bottom 0.2 2), $borderSide(#000000 top 0.2 2)' + } + }, + { + // component: 'Button', // no need to specify components every time unless you're specifying how other component should look + // like within it + directives: { + background: '--fg', + shadow: ['--buttonDefaultShadow', '--buttonDefaultBevel'], + roundness: 3 + } + }, + { + state: ['hover'], + directives: { + shadow: ['--buttonDefaultHoverGlow', '--buttonDefaultBevel'] + } + }, + { + state: ['focused'], + directives: { + shadow: ['--buttonDefaultFocusGlow', '--buttonDefaultBevel'] + } + }, + { + state: ['pressed'], + directives: { + shadow: ['--buttonDefaultShadow', '--buttonPressedBevel'] + } + }, + { + state: ['pressed', 'hover'], + directives: { + shadow: ['--buttonPressedBevel', '--buttonDefaultHoverGlow'] + } + }, + { + state: ['toggled'], + directives: { + background: '--inheritedBackground,-14.2', + shadow: ['--buttonDefaultShadow', '--buttonPressedBevel'] + } + }, + { + state: ['toggled', 'hover'], + directives: { + background: '--inheritedBackground,-14.2', + shadow: ['--buttonDefaultHoverGlow', '--buttonPressedBevel'] + } + }, + { + state: ['toggled', 'disabled'], + directives: { + background: '$blend(--inheritedBackground 0.25 --parent)', + shadow: ['--buttonPressedBevel'] + } + }, + { + state: ['disabled'], + directives: { + background: '$blend(--inheritedBackground 0.25 --parent)', + shadow: ['--buttonDefaultBevel'] + } + }, + { + component: 'Text', + parent: { + component: 'Button', + state: ['disabled'] + }, + directives: { + textOpacity: 0.25, + textOpacityMode: 'blend' + } + }, + { + component: 'Icon', + parent: { + component: 'Button', + state: ['disabled'] + }, + directives: { + textOpacity: 0.25, + textOpacityMode: 'blend' + } + } + ] +} diff --git a/src/components/button_unstyled.style.js b/src/components/button_unstyled.style.js new file mode 100644 index 00000000..a4f0f6f9 --- /dev/null +++ b/src/components/button_unstyled.style.js @@ -0,0 +1,97 @@ +export default { + name: 'ButtonUnstyled', + selector: '.button-unstyled', + notEditable: true, + states: { + toggled: '.toggled', + disabled: ':disabled', + hover: ':hover:not(:disabled)', + focused: ':focus-within' + }, + validInnerComponents: [ + 'Text', + 'Icon', + 'Badge' + ], + defaultRules: [ + { + directives: { + background: '#ffffff', + opacity: 0, + shadow: [] + } + }, + { + component: 'Icon', + parent: { + component: 'ButtonUnstyled', + state: ['hover'] + }, + directives: { + textColor: '--parent--text' + } + }, + { + component: 'Icon', + parent: { + component: 'ButtonUnstyled', + state: ['toggled'] + }, + directives: { + textColor: '--parent--text' + } + }, + { + component: 'Icon', + parent: { + component: 'ButtonUnstyled', + state: ['toggled', 'hover'] + }, + directives: { + textColor: '--parent--text' + } + }, + { + component: 'Icon', + parent: { + component: 'ButtonUnstyled', + state: ['toggled', 'focused'] + }, + directives: { + textColor: '--parent--text' + } + }, + { + component: 'Icon', + parent: { + component: 'ButtonUnstyled', + state: ['toggled', 'focused', 'hover'] + }, + directives: { + textColor: '--parent--text' + } + }, + { + component: 'Text', + parent: { + component: 'ButtonUnstyled', + state: ['disabled'] + }, + directives: { + textOpacity: 0.25, + textOpacityMode: 'blend' + } + }, + { + component: 'Icon', + parent: { + component: 'ButtonUnstyled', + state: ['disabled'] + }, + directives: { + textOpacity: 0.25, + textOpacityMode: 'blend' + } + } + ] +} diff --git a/src/components/chat/chat.scss b/src/components/chat/chat.scss index f2e154ab..8af710ae 100644 --- a/src/components/chat/chat.scss +++ b/src/components/chat/chat.scss @@ -11,15 +11,15 @@ .chat-view-body { box-sizing: border-box; - background-color: var(--chatBg, $fallback--bg); display: flex; flex-direction: column; width: 100%; overflow: visible; min-height: calc(100vh - var(--navbar-height)); - margin: 0 0 0 0; - border-radius: 10px 10px 0 0; - border-radius: var(--panelRadius, 10px) var(--panelRadius, 10px) 0 0; + margin: 0; + border-radius: var(--roundness); + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; &::after { border-radius: 0; @@ -37,8 +37,6 @@ .footer { position: sticky; bottom: 0; - background-color: $fallback--bg; - background-color: var(--bg, $fallback--bg); z-index: 1; } @@ -61,12 +59,10 @@ position: absolute; right: 1.3em; top: -3.2em; - background-color: $fallback--fg; - background-color: var(--btn, $fallback--fg); display: flex; justify-content: center; align-items: center; - box-shadow: 0 1px 1px rgba(0, 0, 0, 0.3), 0 2px 4px rgba(0, 0, 0, 0.3); + box-shadow: 0 1px 1px rgb(0 0 0 / 30%), 0 2px 4px rgb(0 0 0 / 30%); z-index: 10; transition: 0.35s all; transition-timing-function: cubic-bezier(0, 1, 0.5, 1); @@ -79,12 +75,6 @@ visibility: visible; } - i { - font-size: 1em; - color: $fallback--text; - color: var(--text, $fallback--text); - } - .unread-message-count { font-size: 0.8em; left: 50%; diff --git a/src/components/chat/chat.style.js b/src/components/chat/chat.style.js new file mode 100644 index 00000000..9ae2b7d7 --- /dev/null +++ b/src/components/chat/chat.style.js @@ -0,0 +1,19 @@ +export default { + name: 'Chat', + selector: '.chat-message-list', + validInnerComponents: [ + 'Text', + 'Link', + 'Icon', + 'Avatar', + 'ChatMessage' + ], + defaultRules: [ + { + directives: { + background: '--bg', + blur: '5px' + } + } + ] +} diff --git a/src/components/chat/chat.vue b/src/components/chat/chat.vue index 2e7df7bd..6efe576b 100644 --- a/src/components/chat/chat.vue +++ b/src/components/chat/chat.vue @@ -26,7 +26,7 @@
diff --git a/src/components/desktop_nav/desktop_nav.js b/src/components/desktop_nav/desktop_nav.js index 08c0e44e..f6a2e294 100644 --- a/src/components/desktop_nav/desktop_nav.js +++ b/src/components/desktop_nav/desktop_nav.js @@ -1,4 +1,5 @@ import SearchBar from 'components/search_bar/search_bar.vue' +import ConfirmModal from '../confirm_modal/confirm_modal.vue' import { library } from '@fortawesome/fontawesome-svg-core' import { faSignInAlt, @@ -30,7 +31,8 @@ library.add( export default { components: { - SearchBar + SearchBar, + ConfirmModal }, data: () => ({ searchBarHidden: true, @@ -40,7 +42,8 @@ export default { window.CSS.supports('-moz-mask-size', 'contain') || window.CSS.supports('-ms-mask-size', 'contain') || window.CSS.supports('-o-mask-size', 'contain') - ) + ), + showingConfirmLogout: false }), computed: { enableMask () { return this.supportsMask && this.$store.state.instance.logoMask }, @@ -73,21 +76,41 @@ export default { hideSitename () { return this.$store.state.instance.hideSitename }, logoLeft () { return this.$store.state.instance.logoLeft }, currentUser () { return this.$store.state.users.currentUser }, - privateMode () { return this.$store.state.instance.private } + privateMode () { return this.$store.state.instance.private }, + shouldConfirmLogout () { + return this.$store.getters.mergedConfig.modalOnLogout + } }, methods: { scrollToTop () { window.scrollTo(0, 0) }, + showConfirmLogout () { + this.showingConfirmLogout = true + }, + hideConfirmLogout () { + this.showingConfirmLogout = false + }, logout () { + if (!this.shouldConfirmLogout) { + this.doLogout() + } else { + this.showConfirmLogout() + } + }, + doLogout () { this.$router.replace('/main/public') this.$store.dispatch('logout') + this.hideConfirmLogout() }, onSearchBarToggled (hidden) { this.searchBarHidden = hidden }, openSettingsModal () { - this.$store.dispatch('openSettingsModal') + this.$store.dispatch('openSettingsModal', 'user') + }, + openAdminModal () { + this.$store.dispatch('openSettingsModal', 'admin') } } } diff --git a/src/components/desktop_nav/desktop_nav.scss b/src/components/desktop_nav/desktop_nav.scss index 1ec25385..61d2541c 100644 --- a/src/components/desktop_nav/desktop_nav.scss +++ b/src/components/desktop_nav/desktop_nav.scss @@ -1,5 +1,3 @@ -@import '../../_variables.scss'; - .DesktopNav { width: 100%; z-index: var(--ZI_navbar); @@ -9,7 +7,7 @@ } a { - color: var(--topBarLink, $fallback--link); + color: var(--link); } .inner-nav { @@ -27,20 +25,13 @@ --miniColumn: 25rem; --maxiColumn: 45rem; --columnGap: 1em; - max-width: calc( - var(--sidebarColumnWidth, var(--miniColumn)) + - var(--contentColumnWidth, var(--maxiColumn)) + - var(--columnGap) - ); - } - &.-column-stretch.-wide .inner-nav { - max-width: calc( - var(--sidebarColumnWidth, var(--miniColumn)) + - var(--contentColumnWidth, var(--maxiColumn)) + - var(--notifsColumnWidth, var(--miniColumn)) + - var(--columnGap) - ); + max-width: + calc( + var(--sidebarColumnWidth, var(--miniColumn)) + + var(--contentColumnWidth, var(--maxiColumn)) + + var(--columnGap) + ); } &.-logoLeft .inner-nav { @@ -48,29 +39,20 @@ grid-template-areas: "logo sitename actions"; } + &.-column-stretch.-wide .inner-nav { + max-width: + calc( + var(--sidebarColumnWidth, var(--miniColumn)) + + var(--contentColumnWidth, var(--maxiColumn)) + + var(--notifsColumnWidth, var(--miniColumn)) + + var(--columnGap) + ); + } + .button-default { - &, svg { - color: $fallback--text; - color: var(--btnTopBarText, $fallback--text); - } - - &:active { - background-color: $fallback--fg; - background-color: var(--btnPressedTopBar, $fallback--fg); - color: $fallback--text; - color: var(--btnPressedTopBarText, $fallback--text); - } - - &:disabled { - color: $fallback--text; - color: var(--btnDisabledTopBarText, $fallback--text); - } - - &.toggled { - color: $fallback--text; - color: var(--btnToggledTopBarText, $fallback--text); - background-color: $fallback--fg; - background-color: var(--btnToggledTopBar, $fallback--fg) + &, + svg { + color: var(--text); } } @@ -82,6 +64,7 @@ transition-duration: 100ms; @media all and (min-width: 800px) { + /* stylelint-disable-next-line declaration-no-important */ opacity: 1 !important; } @@ -89,8 +72,7 @@ mask-repeat: no-repeat; mask-position: center; mask-size: contain; - background-color: $fallback--fg; - background-color: var(--topBarText, $fallback--fg); + background-color: var(--text); position: absolute; top: 0; bottom: 0; @@ -111,8 +93,7 @@ text-align: center; .svg-inline--fa { - color: $fallback--link; - color: var(--topBarLink, $fallback--link); + color: var(--link); } } diff --git a/src/components/desktop_nav/desktop_nav.vue b/src/components/desktop_nav/desktop_nav.vue index 5db7fc79..49382f8e 100644 --- a/src/components/desktop_nav/desktop_nav.vue +++ b/src/components/desktop_nav/desktop_nav.vue @@ -20,6 +20,7 @@ class="logo" :to="{ name: 'root' }" :style="logoBgStyle" + :title="sitename" >
- - +
+ + + {{ $t('login.logout_confirm') }} + + diff --git a/src/components/dialog_modal/dialog_modal.vue b/src/components/dialog_modal/dialog_modal.vue index 06b270c3..93b8ac0f 100644 --- a/src/components/dialog_modal/dialog_modal.vue +++ b/src/components/dialog_modal/dialog_modal.vue @@ -8,11 +8,11 @@ @click.stop="" >
-
+

-

+
-
+