diff --git a/.babelrc b/.babelrc
index 94521147..373d2c59 100644
--- a/.babelrc
+++ b/.babelrc
@@ -1,5 +1,5 @@
{
- "presets": ["@babel/preset-env", "@vue/babel-preset-jsx"],
- "plugins": ["@babel/plugin-transform-runtime", "lodash"],
+ "presets": ["@babel/preset-env"],
+ "plugins": ["@babel/plugin-transform-runtime", "lodash", "@vue/babel-plugin-jsx"],
"comments": false
}
diff --git a/.eslintrc.js b/.eslintrc.js
index 3c48baa8..361cff5f 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -1,7 +1,7 @@
module.exports = {
root: true,
parserOptions: {
- parser: 'babel-eslint',
+ parser: '@babel/eslint-parser',
sourceType: 'module'
},
// https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style
@@ -21,6 +21,7 @@ module.exports = {
'generator-star-spacing': 0,
// allow debugger during development
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
- 'vue/require-prop-types': 0
+ 'vue/require-prop-types': 0,
+ 'vue/multi-word-component-names': 0
}
}
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 85d3ee44..305155d8 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,7 +1,7 @@
# This file is a template, and might need editing before it works on your project.
# Official framework image. Look for the different tagged releases at:
# https://hub.docker.com/r/library/node/tags/
-image: node:10
+image: node:16
stages:
- lint
diff --git a/.node-version b/.node-version
index b26a34e4..431076a9 100644
--- a/.node-version
+++ b/.node-version
@@ -1 +1 @@
-7.2.1
+16.16.0
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 50eac575..b7eea727 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,26 +5,63 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## Unreleased
### Fixed
+- AdminFE button no longer scrolls page to top when clicked
+- 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
+- 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
### Changed
-- Settings window has been throughly rearranged to make make more sense and make navication settings easier.
+- Using Vue 3 now
+- (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.
+- 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)
### Added
+- 3 column mode: only enables when there's space for it (opt-out, customizable)
+- 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
+- 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
+
## [2.4.2] - 2022-01-09
-### Added
+### Added
- Added Apply and Reset buttons to the bottom of theme tab to minimize UI travel
- Implemented user option to always show floating New Post button (normally mobile-only)
-- Display reasons for instance specific policies
+- Display reasons for instance specific policies
- Added functionality to cancel follow request
### Fixed
- Fixed link to external profile not working on user profiles
-- Fixed mobile shoutbox display
+- Fixed mobile shoutbox display
- Fixed favicon badge not working in Chrome
-- Escape html more properly in subject/display name
+- Escape html more properly in subject/display name
## [2.4.0] - 2021-08-08
diff --git a/build/webpack.base.conf.js b/build/webpack.base.conf.js
index 900d824b..d8a4228d 100644
--- a/build/webpack.base.conf.js
+++ b/build/webpack.base.conf.js
@@ -4,6 +4,9 @@ var utils = require('./utils')
var projectRoot = path.resolve(__dirname, '../')
var ServiceWorkerWebpackPlugin = require('serviceworker-webpack-plugin')
var CopyPlugin = require('copy-webpack-plugin');
+var { VueLoaderPlugin } = require('vue-loader')
+var ESLintPlugin = require('eslint-webpack-plugin');
+
var env = process.env.NODE_ENV
// check env & config/index.js to decide weither to enable CSS Sourcemaps for the
@@ -29,38 +32,43 @@ module.exports = {
}
},
resolve: {
- extensions: ['.js', '.vue'],
+ extensions: ['.mjs', '.js', '.jsx', '.vue'],
modules: [
path.join(__dirname, '../node_modules')
],
alias: {
- 'vue$': 'vue/dist/vue.runtime.common',
'static': path.resolve(__dirname, '../static'),
'src': path.resolve(__dirname, '../src'),
'assets': path.resolve(__dirname, '../src/assets'),
- 'components': path.resolve(__dirname, '../src/components')
+ 'components': path.resolve(__dirname, '../src/components'),
+ 'vue-i18n': 'vue-i18n/dist/vue-i18n.runtime.esm-bundler.js'
}
},
module: {
noParse: /node_modules\/localforage\/dist\/localforage.js/,
rules: [
{
- enforce: 'pre',
- test: /\.(js|vue)$/,
- include: projectRoot,
- exclude: /node_modules/,
- use: {
- loader: 'eslint-loader',
- options: {
- formatter: require('eslint-friendly-formatter'),
- sourceMap: config.build.productionSourceMap,
- extract: true
- }
- }
+ enforce: 'post',
+ test: /\.(json5?|ya?ml)$/, // target json, json5, yaml and yml files
+ type: 'javascript/auto',
+ loader: '@intlify/vue-i18n-loader',
+ include: [ // Use `Rule.include` to specify the files of locale messages to be pre-compiled
+ path.resolve(__dirname, '../src/i18n')
+ ]
},
{
test: /\.vue$/,
- use: 'vue-loader'
+ loader: 'vue-loader',
+ options: {
+ compilerOptions: {
+ isCustomElement(tag) {
+ if (tag === 'pinch-zoom') {
+ return true
+ }
+ return false
+ }
+ }
+ }
},
{
test: /\.jsx?$/,
@@ -88,6 +96,11 @@ module.exports = {
}
}
},
+ {
+ test: /\.mjs$/,
+ include: /node_modules/,
+ type: 'javascript/auto'
+ }
]
},
plugins: [
@@ -95,11 +108,16 @@ module.exports = {
entry: path.join(__dirname, '..', 'src/sw.js'),
filename: 'sw-pleroma.js'
}),
+ new ESLintPlugin({
+ extensions: ['js', 'vue'],
+ formatter: require('eslint-formatter-friendly')
+ }),
+ new VueLoaderPlugin(),
// This copies Ruffle's WASM to a directory so that JS side can access it
new CopyPlugin({
patterns: [
{
- from: "node_modules/ruffle-mirror/*",
+ from: "node_modules/@ruffle-rs/ruffle/*",
to: "static/ruffle",
flatten: true
},
diff --git a/build/webpack.dev.conf.js b/build/webpack.dev.conf.js
index 159572ba..4605b93d 100644
--- a/build/webpack.dev.conf.js
+++ b/build/webpack.dev.conf.js
@@ -21,7 +21,9 @@ module.exports = merge(baseWebpackConfig, {
new webpack.DefinePlugin({
'process.env': config.dev.env,
'COMMIT_HASH': JSON.stringify('DEV'),
- 'DEV_OVERRIDES': JSON.stringify(config.dev.settings)
+ 'DEV_OVERRIDES': JSON.stringify(config.dev.settings),
+ '__VUE_OPTIONS_API__': true,
+ '__VUE_PROD_DEVTOOLS__': false
}),
// https://github.com/glenjamin/webpack-hot-middleware#installation--usage
new webpack.HotModuleReplacementPlugin(),
diff --git a/build/webpack.prod.conf.js b/build/webpack.prod.conf.js
index ed11ebad..a67ed2f6 100644
--- a/build/webpack.prod.conf.js
+++ b/build/webpack.prod.conf.js
@@ -36,7 +36,9 @@ var webpackConfig = merge(baseWebpackConfig, {
new webpack.DefinePlugin({
'process.env': env,
'COMMIT_HASH': JSON.stringify(commitHash),
- 'DEV_OVERRIDES': JSON.stringify(undefined)
+ 'DEV_OVERRIDES': JSON.stringify(undefined),
+ '__VUE_OPTIONS_API__': true,
+ '__VUE_PROD_DEVTOOLS__': false
}),
// extract css into its own file
new MiniCssExtractPlugin({
diff --git a/config/index.js b/config/index.js
index 7cb87c3b..023d4c9b 100644
--- a/config/index.js
+++ b/config/index.js
@@ -52,7 +52,10 @@ module.exports = {
target,
changeOrigin: true,
cookieDomainRewrite: 'localhost',
- ws: true
+ ws: true,
+ headers: {
+ 'Origin': target
+ }
},
'/oauth/revoke': {
target,
diff --git a/package.json b/package.json
index 5134a8b1..e7033b75 100644
--- a/package.json
+++ b/package.json
@@ -16,107 +16,112 @@
"lint-fix": "eslint --fix --ext .js,.vue src test/unit/specs test/e2e/specs"
},
"dependencies": {
- "@babel/runtime": "^7.7.6",
- "@chenfengyuan/vue-qrcode": "^1.0.0",
- "@fortawesome/fontawesome-svg-core": "^1.2.32",
- "@fortawesome/free-regular-svg-icons": "^5.15.1",
- "@fortawesome/free-solid-svg-icons": "^5.15.1",
- "@fortawesome/vue-fontawesome": "^2.0.0",
- "body-scroll-lock": "^2.6.4",
- "chromatism": "^3.0.0",
- "cropperjs": "^1.4.3",
- "diff": "^3.0.1",
- "escape-html": "^1.0.3",
- "localforage": "^1.5.0",
- "parse-link-header": "^1.0.1",
- "phoenix": "^1.3.0",
- "portal-vue": "^2.1.4",
- "punycode.js": "^2.1.0",
- "ruffle-mirror": "^2021.4.10",
- "v-click-outside": "^2.1.1",
- "vue": "^2.6.11",
- "vue-i18n": "^7.3.2",
- "vue-router": "^3.0.1",
- "vue-template-compiler": "^2.6.11",
- "vuelidate": "^0.7.4",
- "vuex": "^3.0.1"
+ "@babel/runtime": "7.18.9",
+ "@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",
+ "@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",
+ "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",
+ "localforage": "1.10.0",
+ "parse-link-header": "1.0.1",
+ "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"
},
"devDependencies": {
- "@babel/core": "^7.7.5",
- "@babel/plugin-transform-runtime": "^7.7.6",
- "@babel/preset-env": "^7.7.6",
- "@babel/register": "^7.7.4",
- "@ungap/event-target": "^0.1.0",
- "@vue/babel-helper-vue-jsx-merge-props": "^1.2.1",
- "@vue/babel-preset-jsx": "^1.2.4",
- "@vue/test-utils": "^1.0.0-beta.26",
- "autoprefixer": "^6.4.0",
- "babel-eslint": "^7.0.0",
- "babel-loader": "^8.0.6",
- "babel-plugin-lodash": "^3.3.4",
- "chai": "^3.5.0",
- "chalk": "^1.1.3",
- "chromedriver": "^87.0.1",
- "connect-history-api-fallback": "^1.1.0",
- "copy-webpack-plugin": "^6.4.1",
- "cross-spawn": "^4.0.2",
- "css-loader": "^0.28.0",
- "custom-event-polyfill": "^1.0.7",
- "eslint": "^5.16.0",
- "eslint-config-standard": "^12.0.0",
- "eslint-friendly-formatter": "^2.0.5",
- "eslint-loader": "^2.1.0",
- "eslint-plugin-import": "^2.13.0",
- "eslint-plugin-node": "^7.0.0",
- "eslint-plugin-promise": "^4.0.0",
- "eslint-plugin-standard": "^4.0.0",
- "eslint-plugin-vue": "^5.2.2",
- "eventsource-polyfill": "^0.9.6",
- "express": "^4.13.3",
- "file-loader": "^3.0.1",
- "function-bind": "^1.0.2",
- "html-webpack-plugin": "^3.0.0",
- "http-proxy-middleware": "^0.17.2",
- "inject-loader": "^2.0.1",
- "iso-639-1": "^2.0.3",
- "isparta-loader": "^2.0.0",
- "json-loader": "^0.5.4",
- "karma": "^3.0.0",
- "karma-coverage": "^1.1.1",
- "karma-firefox-launcher": "^1.1.0",
- "karma-mocha": "^1.2.0",
- "karma-mocha-reporter": "^2.2.1",
- "karma-sinon-chai": "^2.0.2",
- "karma-sourcemap-loader": "^0.3.7",
- "karma-spec-reporter": "0.0.26",
- "karma-webpack": "^4.0.0-rc.3",
- "lodash": "^4.16.4",
- "lolex": "^1.4.0",
- "mini-css-extract-plugin": "^0.5.0",
- "mocha": "^3.1.0",
- "nightwatch": "^0.9.8",
- "opn": "^4.0.2",
- "ora": "^0.3.0",
- "postcss-loader": "^3.0.0",
- "raw-loader": "^0.5.1",
- "sass": "^1.17.3",
- "sass-loader": "git://github.com/webpack-contrib/sass-loader",
+ "@babel/core": "7.18.9",
+ "@babel/plugin-transform-runtime": "7.18.9",
+ "@babel/preset-env": "7.18.9",
+ "@babel/register": "7.18.9",
+ "@babel/eslint-parser": "7.18.9",
+ "@intlify/vue-i18n-loader": "^5.0.0",
+ "@ungap/event-target": "0.2.3",
+ "@vue/babel-helper-vue-jsx-merge-props": "1.2.1",
+ "@vue/babel-plugin-jsx": "1.1.1",
+ "@vue/compiler-sfc": "3.2.37",
+ "@vue/test-utils": "2.0.2",
+ "autoprefixer": "6.7.7",
+ "babel-loader": "8.2.5",
+ "babel-plugin-lodash": "3.3.4",
+ "chai": "3.5.0",
+ "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",
+ "custom-event-polyfill": "1.0.7",
+ "eslint": "8.20.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",
+ "eventsource-polyfill": "0.9.6",
+ "express": "4.18.1",
+ "file-loader": "3.0.1",
+ "function-bind": "1.1.1",
+ "html-webpack-plugin": "3.2.0",
+ "http-proxy-middleware": "0.21.0",
+ "inject-loader": "2.0.1",
+ "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-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",
+ "lodash": "4.17.21",
+ "lolex": "1.6.0",
+ "mini-css-extract-plugin": "0.12.0",
+ "mocha": "3.5.3",
+ "nightwatch": "0.9.21",
+ "opn": "4.0.2",
+ "ora": "0.4.1",
+ "postcss-loader": "3.0.0",
+ "raw-loader": "0.5.1",
+ "sass": "1.54.0",
+ "sass-loader": "7.3.1",
"selenium-server": "2.53.1",
- "semver": "^5.3.0",
- "serviceworker-webpack-plugin": "^1.0.0",
- "shelljs": "^0.8.4",
- "sinon": "^2.1.0",
- "sinon-chai": "^2.8.0",
- "stylelint": "^13.6.1",
- "stylelint-config-standard": "^20.0.0",
- "stylelint-rscss": "^0.4.0",
- "url-loader": "^1.1.2",
- "vue-loader": "^14.0.0",
- "vue-style-loader": "^4.0.0",
- "webpack": "^4.44.0",
- "webpack-dev-middleware": "^3.6.0",
- "webpack-hot-middleware": "^2.12.2",
- "webpack-merge": "^0.14.1"
+ "semver": "5.7.1",
+ "serviceworker-webpack-plugin": "1.0.1",
+ "shelljs": "0.8.5",
+ "sinon": "2.4.1",
+ "sinon-chai": "2.14.0",
+ "stylelint": "13.13.1",
+ "stylelint-config-standard": "20.0.0",
+ "stylelint-rscss": "0.4.0",
+ "url-loader": "1.1.2",
+ "vue-loader": "^16.0.0",
+ "vue-style-loader": "4.1.3",
+ "webpack": "4.46.0",
+ "webpack-dev-middleware": "3.7.3",
+ "webpack-hot-middleware": "2.25.1",
+ "webpack-merge": "0.20.0"
},
"engines": {
"node": ">= 4.0.0",
diff --git a/renovate.json b/renovate.json
new file mode 100644
index 00000000..39a2b6e9
--- /dev/null
+++ b/renovate.json
@@ -0,0 +1,6 @@
+{
+ "$schema": "https://docs.renovatebot.com/renovate-schema.json",
+ "extends": [
+ "config:base"
+ ]
+}
diff --git a/src/App.js b/src/App.js
index f5e0b9e9..d5967685 100644
--- a/src/App.js
+++ b/src/App.js
@@ -1,11 +1,9 @@
import UserPanel from './components/user_panel/user_panel.vue'
import NavPanel from './components/nav_panel/nav_panel.vue'
-import Notifications from './components/notifications/notifications.vue'
import InstanceSpecificPanel from './components/instance_specific_panel/instance_specific_panel.vue'
import FeaturesPanel from './components/features_panel/features_panel.vue'
import WhoToFollowPanel from './components/who_to_follow_panel/who_to_follow_panel.vue'
import ShoutPanel from './components/shout_panel/shout_panel.vue'
-import SettingsModal from './components/settings_modal/settings_modal.vue'
import MediaModal from './components/media_modal/media_modal.vue'
import SideDrawer from './components/side_drawer/side_drawer.vue'
import MobilePostStatusButton from './components/mobile_post_status_button/mobile_post_status_button.vue'
@@ -16,13 +14,14 @@ import PostStatusModal from './components/post_status_modal/post_status_modal.vu
import GlobalNoticeList from './components/global_notice_list/global_notice_list.vue'
import { windowWidth, windowHeight } from './services/window_utils/window_utils'
import { mapGetters } from 'vuex'
+import { defineAsyncComponent } from 'vue'
export default {
name: 'app',
components: {
UserPanel,
NavPanel,
- Notifications,
+ Notifications: defineAsyncComponent(() => import('./components/notifications/notifications.vue')),
InstanceSpecificPanel,
FeaturesPanel,
WhoToFollowPanel,
@@ -32,7 +31,7 @@ export default {
MobilePostStatusButton,
MobileNav,
DesktopNav,
- SettingsModal,
+ SettingsModal: defineAsyncComponent(() => import('./components/settings_modal/settings_modal.vue')),
UserReportingModal,
PostStatusModal,
GlobalNoticeList
@@ -46,10 +45,20 @@ export default {
this.$store.dispatch('setOption', { name: 'interfaceLanguage', value: val })
window.addEventListener('resize', this.updateMobileState)
},
- destroyed () {
+ unmounted () {
window.removeEventListener('resize', this.updateMobileState)
},
computed: {
+ classes () {
+ return [
+ {
+ '-reverse': this.reverseLayout,
+ '-no-sticky-headers': this.noSticky,
+ '-has-new-post-button': this.newPostButtonShown
+ },
+ '-' + this.layoutType
+ ]
+ },
currentUser () { return this.$store.state.users.currentUser },
userBackground () { return this.currentUser.background_image },
instanceBackground () {
@@ -65,38 +74,45 @@ export default {
}
}
},
- shout () { return this.$store.state.shout.channel.state === 'joined' },
+ shout () { return this.$store.state.shout.joined },
suggestionsEnabled () { return this.$store.state.instance.suggestionsEnabled },
showInstanceSpecificPanel () {
return this.$store.state.instance.showInstanceSpecificPanel &&
!this.$store.getters.mergedConfig.hideISP &&
this.$store.state.instance.instanceSpecificPanelContent
},
+ isChats () {
+ return this.$route.name === 'chat' || this.$route.name === 'chats'
+ },
+ newPostButtonShown () {
+ if (this.isChats) return false
+ return this.$store.getters.mergedConfig.alwaysShowNewPostButton || this.layoutType === 'mobile'
+ },
showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel },
shoutboxPosition () {
- return this.$store.getters.mergedConfig.showNewPostButton || false
+ return this.$store.getters.mergedConfig.alwaysShowNewPostButton || false
},
hideShoutbox () {
return this.$store.getters.mergedConfig.hideShoutbox
},
- isMobileLayout () { return this.$store.state.interface.mobileLayout },
+ layoutType () { return this.$store.state.interface.layoutType },
privateMode () { return this.$store.state.instance.private },
- sidebarAlign () {
- return {
- 'order': this.$store.getters.mergedConfig.sidebarRight ? 99 : 0
+ reverseLayout () {
+ const { thirdColumnMode, sidebarRight: reverseSetting } = this.$store.getters.mergedConfig
+ if (this.layoutType !== 'wide') {
+ return reverseSetting
+ } else {
+ return thirdColumnMode === 'notifications' ? reverseSetting : !reverseSetting
}
},
+ noSticky () { return this.$store.getters.mergedConfig.disableStickyHeaders },
+ showScrollbars () { return this.$store.getters.mergedConfig.showScrollbars },
...mapGetters(['mergedConfig'])
},
methods: {
updateMobileState () {
- const mobileLayout = windowWidth() <= 800
- const layoutHeight = windowHeight()
- const changed = mobileLayout !== this.isMobileLayout
- if (changed) {
- this.$store.dispatch('setMobileLayout', mobileLayout)
- }
- this.$store.dispatch('setLayoutHeight', layoutHeight)
+ this.$store.dispatch('setLayoutWidth', windowWidth())
+ this.$store.dispatch('setLayoutHeight', windowHeight())
}
}
}
diff --git a/src/App.scss b/src/App.scss
index bc027f4f..ab025d63 100644
--- a/src/App.scss
+++ b/src/App.scss
@@ -1,77 +1,339 @@
+// stylelint-disable rscss/class-format
@import './_variables.scss';
-#app {
- min-height: 100vh;
- max-width: 100%;
- overflow: hidden;
-}
-
-.app-bg-wrapper {
- position: fixed;
- z-index: -1;
- height: 100%;
- left: 0;
- right: -20px;
- background-size: cover;
- background-repeat: no-repeat;
- background-color: var(--wallpaper);
- background-image: var(--body-background-image);
- background-position: 50% 50px;
-}
-
-i[class^='icon-'] {
- user-select: none;
-}
-
-h4 {
- margin: 0;
-}
-
-#content {
- box-sizing: border-box;
- padding-top: 60px;
- margin: auto;
- min-height: 100vh;
- max-width: 980px;
- align-content: flex-start;
-}
-
-.underlay {
- background-color: rgba(0,0,0,0.15);
- background-color: var(--underlay, rgba(0,0,0,0.15));
-}
-
-.text-center {
- text-align: center;
+: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;
}
html {
font-size: 14px;
+ // overflow-x: clip causes my browser's tab to crash with SIGILL lul
}
body {
- overscroll-behavior-y: none;
font-family: sans-serif;
font-family: var(--interfaceFont, sans-serif);
margin: 0;
color: $fallback--text;
color: var(--text, $fallback--text);
- max-width: 100vw;
- overflow-x: hidden;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
+ overscroll-behavior-y: none;
+ overflow-x: clip;
+ overflow-y: scroll;
&.hidden {
display: none;
}
}
+// ## Custom scrollbars
+// Only show custom scrollbars on devices which
+// have a cursor/pointer to operate them
+@media (any-pointer: fine) {
+ * {
+ scrollbar-color: var(--btn) transparent;
+
+ &::-webkit-scrollbar {
+ background: transparent;
+ }
+
+ &::-webkit-scrollbar-button,
+ &::-webkit-scrollbar-thumb {
+ background-color: var(--btn);
+ box-shadow: var(--buttonShadow);
+ border-radius: var(--btnRadius);
+ }
+
+ // horizontal/vertical/increment/decrement are webkit-specific stuff
+ // that indicates whether we're affecting vertical scrollbar, increase button etc
+ // stylelint-disable selector-pseudo-class-no-unknown
+ &::-webkit-scrollbar-button {
+ --___bgPadding: 2px;
+
+ color: var(--btnText);
+ background-repeat: no-repeat, no-repeat;
+
+ &:horizontal {
+ background-size: 50% calc(50% - var(--___bgPadding)), 50% calc(50% - var(--___bgPadding));
+
+ &:increment {
+ background-image:
+ linear-gradient(45deg, var(--btnText) 50%, transparent 51%),
+ linear-gradient(-45deg, transparent 50%, var(--btnText) 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%);
+ background-position: bottom var(--___bgPadding) right 50%, left 50% top var(--___bgPadding);
+ }
+ }
+
+ &:vertical {
+ background-size: calc(50% - var(--___bgPadding)) 50%, calc(50% - var(--___bgPadding)) 50%;
+
+ &:increment {
+ background-image:
+ linear-gradient(-45deg, transparent 50%, var(--btnText) 51%),
+ linear-gradient(45deg, transparent 50%, var(--btnText) 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%);
+ background-position: left var(--___bgPadding) top 50%, right var(--___bgPadding) top 50%;
+ }
+ }
+ }
+ // stylelint-enable selector-pseudo-class-no-unknown
+ }
+ // Body should have background to scrollbar otherwise it will use white (body color?)
+ html {
+ scrollbar-color: var(--selectedMenu) var(--wallpaper);
+ background: var(--wallpaper);
+ }
+}
+
a {
text-decoration: none;
color: $fallback--link;
color: var(--link, $fallback--link);
}
+h4 {
+ margin: 0;
+}
+
+i[class*=icon-],
+.svg-inline--fa {
+ color: $fallback--icon;
+ color: var(--icon, $fallback--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-sizing: border-box;
+ height: var(--navbar-height);
+ position: fixed;
+}
+
+#sidebar {
+ grid-area: sidebar;
+}
+
+.column.-scrollable {
+ top: var(--navbar-height);
+ position: sticky;
+}
+
+#main-scroller {
+ grid-area: content;
+ position: relative;
+}
+
+#notifs-column {
+ grid-area: notifs;
+}
+
+.app-bg-wrapper {
+ position: fixed;
+ height: 100%;
+ top: var(--navbar-height);
+ z-index: -1000;
+ left: 0;
+ right: -20px;
+ background-size: cover;
+ background-repeat: no-repeat;
+ background-color: var(--wallpaper);
+ background-image: var(--body-background-image);
+ background-position: 50%;
+}
+
+.underlay {
+ grid-column-start: 1;
+ grid-column-end: span 3;
+ grid-row-start: 1;
+ grid-row-end: 1;
+ pointer-events: none;
+ background-color: rgba(0, 0, 0, 0.15);
+ background-color: var(--underlay, rgba(0, 0, 0, 0.15));
+ z-index: -1000;
+}
+
+.app-layout {
+ --miniColumn: 25rem;
+ --maxiColumn: minmax(var(--miniColumn), 45rem);
+ --columnGap: 1em;
+ --status-margin: 0.75em;
+
+ position: relative;
+ display: grid;
+ grid-template-columns: var(--miniColumn) var(--maxiColumn);
+ grid-template-areas: "sidebar content";
+ grid-template-rows: 1fr;
+ box-sizing: border-box;
+ margin: 0 auto;
+ align-content: flex-start;
+ flex-wrap: wrap;
+ justify-content: center;
+ min-height: 100vh;
+ overflow-x: clip;
+
+ .column {
+ --___columnMargin: var(--columnGap);
+
+ display: grid;
+ grid-template-columns: 100%;
+ box-sizing: border-box;
+ grid-row-start: 1;
+ grid-row-end: 1;
+ margin: 0 calc(var(--___columnMargin) / 2);
+ padding: calc(var(--___columnMargin)) 0;
+ row-gap: var(--___columnMargin);
+ align-content: start;
+
+ &:not(.-scrollable) {
+ margin-top: var(--navbar-height);
+ }
+
+ &:hover {
+ z-index: 2;
+ }
+
+ &.-full-height {
+ margin-bottom: 0;
+ padding-top: 0;
+ padding-bottom: 0;
+ }
+
+ &.-scrollable {
+ --___paddingIncrease: calc(var(--columnGap) / 2);
+
+ position: sticky;
+ top: var(--navbar-height);
+ max-height: calc(100vh - var(--navbar-height));
+ overflow-y: auto;
+ overflow-x: hidden;
+ margin-left: calc(var(--___paddingIncrease) * -1);
+ padding-left: calc(var(--___paddingIncrease) + var(--___columnMargin) / 2);
+
+ // On browsers that don't support hiding scrollbars we enforce "show scrolbars" mode
+ // might implement old style of hiding scrollbars later if there's demand
+ @supports (scrollbar-width: none) or (-webkit-text-fill-color: initial) {
+ &:not(.-show-scrollbar) {
+ scrollbar-width: none;
+ margin-right: calc(var(--___paddingIncrease) * -1);
+ padding-right: calc(var(--___paddingIncrease) + var(--___columnMargin) / 2);
+
+ &::-webkit-scrollbar {
+ display: block;
+ width: 0;
+ }
+ }
+ }
+
+ .panel-heading.-sticky {
+ top: calc(var(--columnGap) / -1);
+ }
+ }
+ }
+
+ &.-has-new-post-button {
+ .column {
+ padding-bottom: 10rem;
+ }
+ }
+
+ &.-no-sticky-headers {
+ .column {
+ .panel-heading.-sticky {
+ position: relative;
+ top: 0;
+ }
+ }
+ }
+
+ .column-inner {
+ display: grid;
+ grid-template-columns: 100%;
+ box-sizing: border-box;
+ row-gap: 1em;
+ align-content: start;
+ }
+
+ &.-reverse:not(.-wide):not(.-mobile) {
+ grid-template-columns: var(--maxiColumn) var(--miniColumn);
+ grid-template-areas: "content sidebar";
+ }
+
+ &.-wide {
+ grid-template-columns: var(--miniColumn) var(--maxiColumn) var(--miniColumn);
+ grid-template-areas: "sidebar content notifs";
+
+ &.-reverse {
+ grid-template-areas: "notifs content sidebar";
+ }
+ }
+
+ &.-mobile {
+ grid-template-columns: 100vw;
+ grid-template-areas: "content";
+ padding: 0;
+
+ .column {
+ margin-left: 0;
+ margin-right: 0;
+ padding-top: 0;
+ margin-top: var(--navbar-height);
+ margin-bottom: 0;
+ }
+
+ .panel-heading,
+ .panel-heading::after,
+ .panel-heading::before,
+ .panel,
+ .panel::after {
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
+ }
+
+ #sidebar,
+ #notifs-column {
+ display: none;
+ }
+ }
+
+ &.-normal {
+ #notifs-column {
+ display: none;
+ }
+ }
+}
+
+.text-center {
+ text-align: center;
+}
+
.button-default {
user-select: none;
color: $fallback--text;
@@ -84,7 +346,7 @@ a {
cursor: pointer;
box-shadow: $fallback--buttonShadow;
box-shadow: var(--buttonShadow);
- font-size: 14px;
+ font-size: 1em;
font-family: sans-serif;
font-family: var(--interfaceFont, sans-serif);
@@ -103,12 +365,12 @@ a {
}
&:hover {
- box-shadow: 0px 0px 4px rgba(255, 255, 255, 0.3);
+ box-shadow: 0 0 4px rgba(255, 255, 255, 0.3);
box-shadow: var(--buttonHoverShadow);
}
&:active {
- box-shadow: 0px 0px 4px 0px rgba(255, 255, 255, 0.3), 0px 1px 0px 0px rgba(0, 0, 0, 0.2) inset, 0px -1px 0px 0px rgba(255, 255, 255, 0.2) inset;
+ 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);
@@ -141,7 +403,7 @@ a {
color: var(--btnToggledText, $fallback--text);
background-color: $fallback--fg;
background-color: var(--btnToggled, $fallback--fg);
- box-shadow: 0px 0px 4px 0px rgba(255, 255, 255, 0.3), 0px 1px 0px 0px rgba(0, 0, 0, 0.2) inset, 0px -1px 0px 0px rgba(255, 255, 255, 0.2) inset;
+ 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,
@@ -191,8 +453,9 @@ a {
}
}
-input, textarea, .input {
-
+input,
+textarea,
+.input {
&.unstyled {
border-radius: 0;
background: none;
@@ -200,10 +463,12 @@ input, textarea, .input {
height: unset;
}
+ --_padding: 0.5em;
+
border: none;
border-radius: $fallback--inputRadius;
border-radius: var(--inputRadius, $fallback--inputRadius);
- box-shadow: 0px 1px 0px 0px rgba(0, 0, 0, 0.2) inset, 0px -1px 0px 0px rgba(255, 255, 255, 0.2) inset, 0px 0px 2px 0px rgba(0, 0, 0, 1) inset;
+ 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);
@@ -211,17 +476,18 @@ input, textarea, .input {
color: var(--inputText, $fallback--lightText);
font-family: sans-serif;
font-family: var(--inputFont, sans-serif);
- font-size: 14px;
+ font-size: 1em;
margin: 0;
box-sizing: border-box;
display: inline-block;
position: relative;
- height: 28px;
- line-height: 16px;
+ line-height: 2;
hyphens: none;
- padding: 8px .5em;
+ padding: 0 var(--_padding);
- &:disabled, &[disabled=disabled], &.disabled {
+ &:disabled,
+ &[disabled=disabled],
+ &.disabled {
cursor: not-allowed;
opacity: 0.5;
}
@@ -236,18 +502,21 @@ input, textarea, .input {
&[type=radio] {
display: none;
+
&:checked + label::before {
- box-shadow: 0px 0px 2px black inset, 0px 0px 0px 4px $fallback--fg inset;
- box-shadow: var(--inputShadow), 0px 0px 0px 4px var(--fg, $fallback--fg) inset;
+ 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);
}
+
&:disabled {
&,
& + label,
& + label::before {
- opacity: .5;
+ opacity: 0.5;
}
}
+
+ label::before {
flex-shrink: 0;
display: inline-block;
@@ -256,35 +525,37 @@ input, textarea, .input {
width: 1.1em;
height: 1.1em;
border-radius: 100%; // Radio buttons should always be circle
- box-shadow: 0px 0px 2px black inset;
+ box-shadow: 0 0 2px black inset;
box-shadow: var(--inputShadow);
- margin-right: .5em;
+ margin-right: 0.5em;
background-color: $fallback--fg;
background-color: var(--input, $fallback--fg);
vertical-align: top;
text-align: center;
- line-height: 1.1em;
+ line-height: 1.1;
font-size: 1.1em;
box-sizing: border-box;
color: transparent;
overflow: hidden;
- box-sizing: border-box;
}
}
&[type=checkbox] {
display: none;
+
&:checked + label::before {
color: $fallback--text;
color: var(--inputText, $fallback--text);
}
+
&:disabled {
&,
& + label,
& + label::before {
- opacity: .5;
+ opacity: 0.5;
}
}
+
+ label::before {
flex-shrink: 0;
display: inline-block;
@@ -294,19 +565,18 @@ input, textarea, .input {
height: 1.1em;
border-radius: $fallback--checkboxRadius;
border-radius: var(--checkboxRadius, $fallback--checkboxRadius);
- box-shadow: 0px 0px 2px black inset;
+ box-shadow: 0 0 2px black inset;
box-shadow: var(--inputShadow);
- margin-right: .5em;
+ margin-right: 0.5em;
background-color: $fallback--fg;
background-color: var(--input, $fallback--fg);
vertical-align: top;
text-align: center;
- line-height: 1.1em;
+ line-height: 1.1;
font-size: 1.1em;
box-sizing: border-box;
color: transparent;
overflow: hidden;
- box-sizing: border-box;
}
}
@@ -315,6 +585,12 @@ input, textarea, .input {
}
}
+// Textareas should have stock line-height + vertical padding instead of huge line-height
+textarea {
+ padding: var(--_padding);
+ line-height: var(--post-line-height);
+}
+
option {
color: $fallback--text;
color: var(--text, $fallback--text);
@@ -324,6 +600,7 @@ option {
.hide-number-spinner {
-moz-appearance: textfield;
+
&[type=number]::-webkit-inner-spin-button,
&[type=number]::-webkit-outer-spin-button {
opacity: 0;
@@ -331,11 +608,6 @@ option {
}
}
-i[class*=icon-], .svg-inline--fa {
- color: $fallback--icon;
- color: var(--icon, $fallback--icon);
-}
-
.btn-block {
display: block;
width: 100%;
@@ -362,273 +634,16 @@ i[class*=icon-], .svg-inline--fa {
}
}
-.container {
- display: flex;
- flex-wrap: wrap;
- margin: 0;
- padding: 0 10px 0 10px;
-}
-
-.auto-size {
- flex: 1
-}
-
-main-router {
- flex: 1;
-}
-
-.status.compact {
- color: rgba(0, 0, 0, 0.42);
- font-weight: 300;
-
- p {
- margin: 0;
- font-size: 0.8em
- }
-}
-
-/* Panel */
-
-.panel {
- display: flex;
- position: relative;
-
- flex-direction: column;
- margin: 0.5em;
-
- background-color: $fallback--bg;
- background-color: var(--bg, $fallback--bg);
-
- &::after, & {
- border-radius: $fallback--panelRadius;
- border-radius: var(--panelRadius, $fallback--panelRadius);
- }
-
- &::after {
- content: '';
- position: absolute;
-
- top: 0;
- bottom: 0;
- left: 0;
- right: 0;
-
- pointer-events: none;
-
- box-shadow: 1px 1px 4px rgba(0,0,0,.6);
- box-shadow: var(--panelShadow);
- }
-}
-
-.panel-body:empty::before {
- content: "¯\\_(ツ)_/¯"; // Could use words but it'd require translations
- display: block;
- margin: 1em;
- text-align: center;
-}
-
-.panel-heading {
- display: flex;
- flex: none;
- border-radius: $fallback--panelRadius $fallback--panelRadius 0 0;
- border-radius: var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius) 0 0;
- background-size: cover;
- padding: .6em .6em;
- text-align: left;
- line-height: 28px;
- color: var(--panelText);
- background-color: $fallback--fg;
- background-color: var(--panel, $fallback--fg);
- align-items: baseline;
- box-shadow: var(--panelHeaderShadow);
-
- .title {
- flex: 1 0 auto;
- font-size: 1.3em;
- }
-
- .faint {
- background-color: transparent;
- color: $fallback--faint;
- color: var(--panelFaint, $fallback--faint);
- }
-
- .faint-link {
- color: $fallback--faint;
- color: var(--faintLink, $fallback--faint);
- }
-
- .alert {
- white-space: nowrap;
- text-overflow: ellipsis;
- overflow-x: hidden;
- }
-
- .button-default,
- .alert {
- // height: 100%;
- line-height: 21px;
- min-height: 0;
- box-sizing: border-box;
- margin: 0;
- margin-left: .5em;
- min-width: 1px;
- align-self: stretch;
- }
-
- .button-default {
- flex-shrink: 0;
-
- &,
- i[class*=icon-] {
- color: $fallback--text;
- color: var(--btnPanelText, $fallback--text);
- }
-
- &:active {
- background-color: $fallback--fg;
- background-color: var(--btnPressedPanel, $fallback--fg);
- color: $fallback--text;
- color: var(--btnPressedPanelText, $fallback--text);
- }
-
- &:disabled {
- color: $fallback--text;
- color: var(--btnDisabledPanelText, $fallback--text);
- }
-
- &.toggled {
- color: $fallback--text;
- color: var(--btnToggledPanelText, $fallback--text);
- }
- }
-
- a,
- .-link {
- color: $fallback--link;
- color: var(--panelLink, $fallback--link)
- }
-}
-
-.panel-heading.stub {
- border-radius: $fallback--panelRadius;
- border-radius: var(--panelRadius, $fallback--panelRadius);
-}
-
-/* TODO Should remove timeline-footer from here when we refactor panels into
- * separate component and utilize slots
- */
-.panel-footer, .timeline-footer {
- display: flex;
- border-radius: 0 0 $fallback--panelRadius $fallback--panelRadius;
- border-radius: 0 0 var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius);
- flex: none;
- padding: 0.6em 0.6em;
- text-align: left;
- line-height: 28px;
- align-items: baseline;
- border-width: 1px 0 0 0;
- border-style: solid;
- border-color: var(--border, $fallback--border);
-
- .faint {
- color: $fallback--faint;
- color: var(--panelFaint, $fallback--faint);
- }
-
- a,
- .-link {
- color: $fallback--link;
- color: var(--panelLink, $fallback--link);
- }
-}
-
-.panel-body > p {
- line-height: 18px;
- padding: 1em;
- margin: 0;
-}
-
-.container > * {
- min-width: 0px;
-}
+@import './panel.scss';
.fa {
color: grey;
}
-nav {
- z-index: 1000;
- color: var(--topBarText);
- background-color: $fallback--fg;
- background-color: var(--topBar, $fallback--fg);
- color: $fallback--faint;
- color: var(--faint, $fallback--faint);
- box-shadow: 0px 0px 4px rgba(0,0,0,.6);
- box-shadow: var(--topBarShadow);
- box-sizing: border-box;
-}
-
-.fade-enter-active, .fade-leave-active {
- transition: opacity .2s
-}
-.fade-enter, .fade-leave-active {
- opacity: 0
-}
-
-.main {
- flex-basis: 50%;
- flex-grow: 1;
- flex-shrink: 1;
-}
-
-.sidebar-bounds {
- flex: 0;
- flex-basis: 35%;
-}
-
-.sidebar-flexer {
- flex: 1;
- flex-basis: 345px;
- width: 365px;
-}
-
.mobile-shown {
display: none;
}
-@media all and (min-width: 800px) {
- body {
- overflow-y: scroll;
- }
-
- .sidebar-bounds {
- overflow: hidden;
- max-height: 100vh;
- width: 345px;
- position: fixed;
- margin-top: -10px;
-
- .sidebar-scroller {
- height: 96vh;
- width: 365px;
- padding-top: 10px;
- padding-right: 50px;
- overflow-x: hidden;
- overflow-y: scroll;
- }
-
- .sidebar {
- width: 345px;
- }
- }
- .sidebar-flexer {
- max-height: 96vh;
- flex-shrink: 0;
- flex-grow: 0;
- }
-}
-
.badge {
box-sizing: border-box;
display: inline-block;
@@ -656,12 +671,10 @@ nav {
}
.alert {
- margin: 0.35em;
- padding: 0.25em;
+ margin: 0 0.35em;
+ padding: 0 0.25em;
border-radius: $fallback--tooltipRadius;
border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
- min-height: 28px;
- line-height: 28px;
&.error {
background-color: $fallback--alertError;
@@ -712,7 +725,7 @@ nav {
}
.visibility-notice {
- padding: .5em;
+ padding: 0.5em;
border: 1px solid $fallback--faint;
border: 1px solid var(--faint, $fallback--faint);
border-radius: $fallback--inputRadius;
@@ -727,7 +740,7 @@ nav {
position: absolute;
top: 0;
right: 0;
- padding: .5em;
+ padding: 0.5em;
color: inherit;
}
}
@@ -744,72 +757,6 @@ nav {
}
}
-@keyframes shakeError {
- 0% {
- transform: translateX(0);
- }
- 15% {
- transform: translateX(0.375rem);
- }
- 30% {
- transform: translateX(-0.375rem);
- }
- 45% {
- transform: translateX(0.375rem);
- }
- 60% {
- transform: translateX(-0.375rem);
- }
- 75% {
- transform: translateX(0.375rem);
- }
- 90% {
- transform: translateX(-0.375rem);
- }
- 100% {
- transform: translateX(0);
- }
-}
-
-@media all and (max-width: 800px) {
- .mobile-hidden {
- display: none;
- }
-
- .panel-switcher {
- display: flex;
- }
-
- .container {
- padding: 0;
- }
-
- .panel {
- margin: 0.5em 0 0.5em 0;
- }
-
- .menu-button {
- display: block;
- margin-right: 0.8em;
- }
-
- .main {
- margin-bottom: 7em;
- }
-}
-
-.setting-list,
-.option-list{
- list-style-type: none;
- padding-left: 2em;
- li {
- margin-bottom: 0.5em;
- }
- .suboptions {
- margin-top: 0.3em
- }
-}
-
.login-hint {
text-align: center;
@@ -819,18 +766,26 @@ nav {
a {
display: inline-block;
- padding: 1em 0px;
+ padding: 1em 0;
width: 100%;
}
}
.btn.button-default {
- min-height: 28px;
+ min-height: 2em;
}
-.animate-spin {
- animation: spin 2s infinite linear;
- display: inline-block;
+.new-status-notification {
+ position: relative;
+ font-size: 1.1em;
+ z-index: 1;
+ flex: 1;
+}
+
+@media all and (max-width: 800px) {
+ .mobile-hidden {
+ display: none;
+ }
}
@keyframes spin {
@@ -843,49 +798,47 @@ nav {
}
}
-.new-status-notification {
- position: relative;
- font-size: 1.1em;
- z-index: 1;
- flex: 1;
-}
-
-.chat-layout {
- // Needed for smoother chat navigation in the desktop Safari (otherwise the chat layout "jumps" as the chat opens).
- overflow: hidden;
- height: 100%;
-
- // Get rid of scrollbar on body as scrolling happens on different element
- body {
- overflow: hidden;
+@keyframes shakeError {
+ 0% {
+ transform: translateX(0);
}
- // Ensures the fixed position of the mobile browser bars on scroll up / down events.
- // Prevents the mobile browser bars from overlapping or hiding the message posting form.
- @media all and (max-width: 800px) {
- body {
- height: 100%;
- }
+ 15% {
+ transform: translateX(0.375rem);
+ }
- #app {
- height: 100%;
- overflow: hidden;
- min-height: auto;
- }
+ 30% {
+ transform: translateX(-0.375rem);
+ }
- #app_bg_wrapper {
- overflow: hidden;
- }
+ 45% {
+ transform: translateX(0.375rem);
+ }
- .main {
- overflow: hidden;
- height: 100%;
- }
+ 60% {
+ transform: translateX(-0.375rem);
+ }
- #content {
- padding-top: 0;
- height: 100%;
- overflow: visible;
- }
+ 75% {
+ transform: translateX(0.375rem);
+ }
+
+ 90% {
+ transform: translateX(-0.375rem);
+ }
+
+ 100% {
+ transform: translateX(0);
}
}
+
+// Vue transitions
+.fade-enter-active,
+.fade-leave-active {
+ transition: opacity 0.3s;
+}
+
+.fade-enter-from,
+.fade-leave-active {
+ opacity: 0;
+}
diff --git a/src/App.vue b/src/App.vue
index eb65b548..0efadaf0 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -1,39 +1,40 @@
-
+
-
+
+
-
diff --git a/src/_variables.scss b/src/_variables.scss
index 9004d551..099d3606 100644
--- a/src/_variables.scss
+++ b/src/_variables.scss
@@ -30,3 +30,5 @@ $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/boot/after_store.js b/src/boot/after_store.js
index cc0c7c5e..908d905a 100644
--- a/src/boot/after_store.js
+++ b/src/boot/after_store.js
@@ -1,8 +1,14 @@
-import Vue from 'vue'
-import VueRouter from 'vue-router'
-import routes from './routes'
+import { createApp } from 'vue'
+import { createRouter, createWebHistory } from 'vue-router'
+import vClickOutside from 'click-outside-vue3'
+
+import { FontAwesomeIcon, FontAwesomeLayers } from '@fortawesome/vue-fontawesome'
+
import App from '../App.vue'
-import { windowWidth } from '../services/window_utils/window_utils'
+import routes from './routes'
+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'
@@ -115,6 +121,7 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => {
copyInstanceOption('nsfwCensorImage')
copyInstanceOption('background')
copyInstanceOption('hidePostStats')
+ copyInstanceOption('hideBotIndication')
copyInstanceOption('hideUserStats')
copyInstanceOption('hideFilteredStatuses')
copyInstanceOption('logo')
@@ -149,7 +156,7 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => {
copyInstanceOption('hideSitename')
copyInstanceOption('sidebarRight')
- return store.dispatch('setTheme', config['theme'])
+ return store.dispatch('setTheme', config.theme)
}
const getTOS = async ({ store }) => {
@@ -190,7 +197,7 @@ const getStickers = async ({ store }) => {
const stickers = (await Promise.all(
Object.entries(values).map(async ([name, path]) => {
const resPack = await window.fetch(path + 'pack.json')
- var meta = {}
+ let meta = {}
if (resPack.ok) {
meta = await resPack.json()
}
@@ -312,6 +319,7 @@ 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 {
@@ -325,8 +333,8 @@ const checkOAuthToken = async ({ store }) => {
}
const afterStoreSetup = async ({ store, i18n }) => {
- const width = windowWidth()
- store.dispatch('setMobileLayout', width <= 800)
+ store.dispatch('setLayoutWidth', windowWidth())
+ store.dispatch('setLayoutHeight', windowHeight())
FaviconService.initFaviconService()
@@ -366,25 +374,35 @@ const afterStoreSetup = async ({ store, i18n }) => {
getTOS({ store })
getStickers({ store })
- const router = new VueRouter({
- mode: 'history',
+ const router = createRouter({
+ history: createWebHistory(),
routes: routes(store),
scrollBehavior: (to, _from, savedPosition) => {
if (to.matched.some(m => m.meta.dontScroll)) {
return false
}
- return savedPosition || { x: 0, y: 0 }
+ return savedPosition || { left: 0, top: 0 }
}
})
- /* eslint-disable no-new */
- return new Vue({
- router,
- store,
- i18n,
- el: '#app',
- render: h => h(App)
- })
+ const app = createApp(App)
+
+ app.use(router)
+ app.use(store)
+ app.use(i18n)
+
+ app.use(vClickOutside)
+ app.use(VBodyScrollLock)
+
+ app.component('FAIcon', FontAwesomeIcon)
+ app.component('FALayers', FontAwesomeLayers)
+
+ // remove after vue 3.3
+ app.config.unwrapInjectedRef = true
+
+ app.mount('#app')
+
+ return app
}
export default afterStoreSetup
diff --git a/src/boot/routes.js b/src/boot/routes.js
index 1bc1f9f7..c8194d5f 100644
--- a/src/boot/routes.js
+++ b/src/boot/routes.js
@@ -31,7 +31,8 @@ export default (store) => {
}
let routes = [
- { name: 'root',
+ {
+ name: 'root',
path: '/',
redirect: _to => {
return (store.state.users.currentUser
@@ -45,12 +46,14 @@ 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: 'remote-user-profile-acct',
- path: '/remote-users/(@?):username([^/@]+)@:hostname([^/@]+)',
+ {
+ name: 'remote-user-profile-acct',
+ path: '/remote-users/:_(@)?:username([^/@]+)@:hostname([^/@]+)',
component: RemoteUserResolver,
beforeEnter: validateAuthenticatedRoute
},
- { name: 'remote-user-profile',
+ {
+ name: 'remote-user-profile',
path: '/remote-users/:hostname/:username',
component: RemoteUserResolver,
beforeEnter: validateAuthenticatedRoute
@@ -62,14 +65,14 @@ export default (store) => {
{ name: 'password-reset', path: '/password-reset', component: PasswordReset, props: true },
{ name: 'registration-token', path: '/registration/:token', component: Registration },
{ name: 'friend-requests', path: '/friend-requests', component: FollowRequests, beforeEnter: validateAuthenticatedRoute },
- { name: 'notifications', path: '/:username/notifications', component: Notifications, beforeEnter: validateAuthenticatedRoute },
+ { name: 'notifications', path: '/:username/notifications', component: Notifications, props: () => ({ disableTeleport: true }), beforeEnter: validateAuthenticatedRoute },
{ name: 'login', path: '/login', component: AuthForm },
{ name: 'shout-panel', path: '/shout-panel', component: ShoutPanel, props: () => ({ floating: false }) },
{ name: 'oauth-callback', path: '/oauth-callback', component: OAuthCallback, props: (route) => ({ code: route.query.code }) },
{ name: 'search', path: '/search', component: Search, props: (route) => ({ query: route.query.query }) },
{ name: 'who-to-follow', path: '/who-to-follow', component: WhoToFollow, beforeEnter: validateAuthenticatedRoute },
{ name: 'about', path: '/about', component: About },
- { name: 'user-profile', path: '/(users/)?:name', component: UserProfile }
+ { name: 'user-profile', path: '/:_(users)?/:name', component: UserProfile }
]
if (store.state.instance.pleromaChatMessagesAvailable) {
diff --git a/src/components/about/about.vue b/src/components/about/about.vue
index 518f6184..33586c97 100644
--- a/src/components/about/about.vue
+++ b/src/components/about/about.vue
@@ -1,5 +1,5 @@
-
-
+
+
diff --git a/src/components/desktop_nav/desktop_nav.js b/src/components/desktop_nav/desktop_nav.js
index e048f53d..08c0e44e 100644
--- a/src/components/desktop_nav/desktop_nav.js
+++ b/src/components/desktop_nav/desktop_nav.js
@@ -46,23 +46,27 @@ export default {
enableMask () { return this.supportsMask && this.$store.state.instance.logoMask },
logoStyle () {
return {
- 'visibility': this.enableMask ? 'hidden' : 'visible'
+ visibility: this.enableMask ? 'hidden' : 'visible'
}
},
logoMaskStyle () {
- return this.enableMask ? {
- 'mask-image': `url(${this.$store.state.instance.logo})`
- } : {
- 'background-color': this.enableMask ? '' : 'transparent'
- }
+ return this.enableMask
+ ? {
+ 'mask-image': `url(${this.$store.state.instance.logo})`
+ }
+ : {
+ 'background-color': this.enableMask ? '' : 'transparent'
+ }
},
logoBgStyle () {
return Object.assign({
- 'margin': `${this.$store.state.instance.logoMargin} 0`,
+ margin: `${this.$store.state.instance.logoMargin} 0`,
opacity: this.searchBarHidden ? 1 : 0
- }, this.enableMask ? {} : {
- 'background-color': this.enableMask ? '' : 'transparent'
- })
+ }, this.enableMask
+ ? {}
+ : {
+ 'background-color': this.enableMask ? '' : 'transparent'
+ })
},
logo () { return this.$store.state.instance.logo },
sitename () { return this.$store.state.instance.name },
diff --git a/src/components/desktop_nav/desktop_nav.scss b/src/components/desktop_nav/desktop_nav.scss
index 2d468588..71202244 100644
--- a/src/components/desktop_nav/desktop_nav.scss
+++ b/src/components/desktop_nav/desktop_nav.scss
@@ -1,9 +1,12 @@
@import '../../_variables.scss';
.DesktopNav {
- height: 50px;
width: 100%;
- position: fixed;
+ z-index: var(--ZI_navbar);
+
+ input {
+ color: var(--inputTopbarText, var(--inputText));
+ }
a {
color: var(--topBarLink, $fallback--link);
@@ -11,7 +14,7 @@
.inner-nav {
display: grid;
- grid-template-rows: 50px;
+ grid-template-rows: var(--navbar-height);
grid-template-columns: 2fr auto 2fr;
grid-template-areas: "sitename logo actions";
box-sizing: border-box;
@@ -20,7 +23,7 @@
max-width: 980px;
}
- &.-logoLeft {
+ &.-logoLeft .inner-nav {
grid-template-columns: auto 2fr 2fr;
grid-template-areas: "logo sitename actions";
}
@@ -77,7 +80,7 @@
img {
display: inline-block;
- height: 50px;
+ height: var(--navbar-height);
}
}
@@ -103,8 +106,8 @@
.item {
flex: 1;
- line-height: 50px;
- height: 50px;
+ line-height: var(--navbar-height);
+ height: var(--navbar-height);
overflow: hidden;
display: flex;
flex-wrap: wrap;
diff --git a/src/components/desktop_nav/desktop_nav.vue b/src/components/desktop_nav/desktop_nav.vue
index 762aa610..f352c78c 100644
--- a/src/components/desktop_nav/desktop_nav.vue
+++ b/src/components/desktop_nav/desktop_nav.vue
@@ -34,11 +34,11 @@
-
+
diff --git a/src/components/font_control/font_control.js b/src/components/font_control/font_control.js
index 137ef9c0..92ee3f30 100644
--- a/src/components/font_control/font_control.js
+++ b/src/components/font_control/font_control.js
@@ -1,4 +1,4 @@
-import { set } from 'vue'
+import { set } from 'lodash'
import Select from '../select/select.vue'
export default {
@@ -6,11 +6,12 @@ export default {
Select
},
props: [
- 'name', 'label', 'value', 'fallback', 'options', 'no-inherit'
+ 'name', 'label', 'modelValue', 'fallback', 'options', 'no-inherit'
],
+ emits: ['update:modelValue'],
data () {
return {
- lValue: this.value,
+ lValue: this.modelValue,
availableOptions: [
this.noInherit ? '' : 'inherit',
'custom',
@@ -22,7 +23,7 @@ export default {
}
},
beforeUpdate () {
- this.lValue = this.value
+ this.lValue = this.modelValue
},
computed: {
present () {
@@ -37,7 +38,7 @@ export default {
},
set (v) {
set(this.lValue, 'family', v)
- this.$emit('input', this.lValue)
+ this.$emit('update:modelValue', this.lValue)
}
},
isCustom () {
diff --git a/src/components/font_control/font_control.vue b/src/components/font_control/font_control.vue
index 29605084..83c1cef7 100644
--- a/src/components/font_control/font_control.vue
+++ b/src/components/font_control/font_control.vue
@@ -15,13 +15,14 @@
class="opt exlcude-disabled"
type="checkbox"
:checked="present"
- @input="$emit('input', typeof value === 'undefined' ? fallback : undefined)"
+ @change="$emit('update:modelValue', typeof modelValue === 'undefined' ? fallback : undefined)"
>
+ {{ ' ' }}
diff --git a/src/components/instance_specific_panel/instance_specific_panel.vue b/src/components/instance_specific_panel/instance_specific_panel.vue
index 7448ca06..c8ed0a2d 100644
--- a/src/components/instance_specific_panel/instance_specific_panel.vue
+++ b/src/components/instance_specific_panel/instance_specific_panel.vue
@@ -10,4 +10,4 @@
-
+
diff --git a/src/components/interactions/interactions.js b/src/components/interactions/interactions.js
index 7fe5e76d..cc6a15e1 100644
--- a/src/components/interactions/interactions.js
+++ b/src/components/interactions/interactions.js
@@ -1,4 +1,5 @@
import Notifications from '../notifications/notifications.vue'
+import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx'
const tabModeDict = {
mentions: ['mention'],
@@ -11,7 +12,7 @@ const Interactions = {
data () {
return {
allowFollowingMove: this.$store.state.users.currentUser.allow_following_move,
- filterMode: tabModeDict['mentions']
+ filterMode: tabModeDict.mentions
}
},
methods: {
@@ -20,7 +21,8 @@ const Interactions = {
}
},
components: {
- Notifications
+ Notifications,
+ TabSwitcher
}
}
diff --git a/src/components/interface_language_switcher/interface_language_switcher.vue b/src/components/interface_language_switcher/interface_language_switcher.vue
index cf307a24..6997f149 100644
--- a/src/components/interface_language_switcher/interface_language_switcher.vue
+++ b/src/components/interface_language_switcher/interface_language_switcher.vue
@@ -1,11 +1,12 @@
+ {{ ' ' }}
-
+
diff --git a/src/components/mfa_form/totp_form.vue b/src/components/mfa_form/totp_form.vue
index 4ee13992..709eb9b8 100644
--- a/src/components/mfa_form/totp_form.vue
+++ b/src/components/mfa_form/totp_form.vue
@@ -58,12 +58,16 @@
>
{{ error }}
-
+ >
+
+
diff --git a/src/components/mobile_nav/mobile_nav.js b/src/components/mobile_nav/mobile_nav.js
index 9e736cfb..877d52a9 100644
--- a/src/components/mobile_nav/mobile_nav.js
+++ b/src/components/mobile_nav/mobile_nav.js
@@ -78,7 +78,8 @@ const MobileNav = {
this.$store.dispatch('logout')
},
markNotificationsAsSeen () {
- this.$refs.notifications.markAsSeen()
+ // this.$refs.notifications.markAsSeen()
+ this.$store.dispatch('markNotificationsAsSeen')
},
onScroll ({ target: { scrollTop, clientHeight, scrollHeight } }) {
if (scrollTop + clientHeight >= scrollHeight) {
diff --git a/src/components/mobile_nav/mobile_nav.vue b/src/components/mobile_nav/mobile_nav.vue
index 0f0ea457..949cf17e 100644
--- a/src/components/mobile_nav/mobile_nav.vue
+++ b/src/components/mobile_nav/mobile_nav.vue
@@ -5,7 +5,6 @@