Merge remote-tracking branch 'origin/develop' into scrolltotop
* origin/develop: (47 commits) Update dependency eslint-plugin-vue to v9.4.0 Update dependency opn to v5 fix notices being under the navbar, also change offset to use variable fix modals not having proper z index reduce indexes to be below 9999 so that develop error messages appear above Fix react & extra buttons not styled on tab-focus Fix popover not popping up Fix styling on Safari Use :focus-visible instead of :focus for focus markers Optimize Reply badge position Add badges to status interacting buttons Update dependency nightwatch to v2 Update dependency eslint-plugin-n to v15.2.5 Update dependency mocha to v10 Update dependency karma-coverage to v2 Update dependency sass to v1.54.5 Update dependency karma-firefox-launcher to v2 Update dependency vue-template-compiler to v2.7.9 Pin dependencies Refresh yarn.lock ...
This commit is contained in:
		
						commit
						4e339d9be3
					
				
					 47 changed files with 2384 additions and 3660 deletions
				
			
		|  | @ -29,18 +29,20 @@ var devMiddleware = require('webpack-dev-middleware')(compiler, { | |||
| }) | ||||
| 
 | ||||
| var hotMiddleware = require('webpack-hot-middleware')(compiler) | ||||
| // force page reload when html-webpack-plugin template changes
 | ||||
| compiler.plugin('compilation', function (compilation) { | ||||
|   compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) { | ||||
|     // FIXME: This supposed to reload whole page when index.html is changed,
 | ||||
|     // however now it reloads entire page on every breath, i suppose the order
 | ||||
|     // of plugins changed or something. It's a minor thing and douesn't hurt
 | ||||
|     // disabling it, constant reloads hurt much more
 | ||||
| 
 | ||||
|     // hotMiddleware.publish({ action: 'reload' })
 | ||||
|     // cb()
 | ||||
|   }) | ||||
| }) | ||||
| // FIXME: The statement below gives error about hooks being required in webpack 5.
 | ||||
| // force page reload when html-webpack-plugin template changes
 | ||||
| // compiler.plugin('compilation', function (compilation) {
 | ||||
| //   compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
 | ||||
| //     // FIXME: This supposed to reload whole page when index.html is changed,
 | ||||
| //     // however now it reloads entire page on every breath, i suppose the order
 | ||||
| //     // of plugins changed or something. It's a minor thing and douesn't hurt
 | ||||
| //     // disabling it, constant reloads hurt much more
 | ||||
| 
 | ||||
| //     // hotMiddleware.publish({ action: 'reload' })
 | ||||
| //     // cb()
 | ||||
| //   })
 | ||||
| // })
 | ||||
| 
 | ||||
| // proxy api requests
 | ||||
| Object.keys(proxyTable).forEach(function (context) { | ||||
|  | @ -48,7 +50,7 @@ Object.keys(proxyTable).forEach(function (context) { | |||
|   if (typeof options === 'string') { | ||||
|     options = { target: options } | ||||
|   } | ||||
|   app.use(proxyMiddleware(context, options)) | ||||
|   app.use(proxyMiddleware.createProxyMiddleware(context, options)) | ||||
| }) | ||||
| 
 | ||||
| // handle fallback for HTML5 history API
 | ||||
|  |  | |||
|  | @ -2,7 +2,7 @@ var path = require('path') | |||
| var config = require('../config') | ||||
| var utils = require('./utils') | ||||
| var projectRoot = path.resolve(__dirname, '../') | ||||
| var ServiceWorkerWebpackPlugin = require('serviceworker-webpack-plugin') | ||||
| var ServiceWorkerWebpackPlugin = require('serviceworker-webpack5-plugin') | ||||
| var CopyPlugin = require('copy-webpack-plugin'); | ||||
| var { VueLoaderPlugin } = require('vue-loader') | ||||
| var ESLintPlugin = require('eslint-webpack-plugin'); | ||||
|  | @ -42,6 +42,10 @@ module.exports = { | |||
|       'assets': path.resolve(__dirname, '../src/assets'), | ||||
|       'components': path.resolve(__dirname, '../src/components'), | ||||
|       'vue-i18n': 'vue-i18n/dist/vue-i18n.runtime.esm-bundler.js' | ||||
|     }, | ||||
|     fallback: { | ||||
|       'querystring': require.resolve('querystring-es3'), | ||||
|       'url': require.resolve('url/') | ||||
|     } | ||||
|   }, | ||||
|   module: { | ||||
|  | @ -78,22 +82,16 @@ module.exports = { | |||
|       }, | ||||
|       { | ||||
|         test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, | ||||
|         use: { | ||||
|           loader: 'url-loader', | ||||
|           options: { | ||||
|             limit: 10000, | ||||
|             name: utils.assetsPath('img/[name].[hash:7].[ext]') | ||||
|           } | ||||
|         type: 'asset', | ||||
|         generator: { | ||||
|           filename: utils.assetsPath('img/[name].[hash:7][ext]') | ||||
|         } | ||||
|       }, | ||||
|       { | ||||
|         test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, | ||||
|         use: { | ||||
|           loader: 'url-loader', | ||||
|           options: { | ||||
|             limit: 10000, | ||||
|             name: utils.assetsPath('fonts/[name].[hash:7].[ext]') | ||||
|           } | ||||
|         type: 'asset', | ||||
|         generator: { | ||||
|           filename: utils.assetsPath('fonts/[name].[hash:7][ext]') | ||||
|         } | ||||
|       }, | ||||
|       { | ||||
|  | @ -117,9 +115,8 @@ module.exports = { | |||
|     new CopyPlugin({ | ||||
|       patterns: [ | ||||
|         { | ||||
|           from: "node_modules/@ruffle-rs/ruffle/*", | ||||
|           to: "static/ruffle", | ||||
|           flatten: true | ||||
|           from: "node_modules/@ruffle-rs/ruffle/**/*", | ||||
|           to: "static/ruffle/[name][ext]" | ||||
|         }, | ||||
|       ], | ||||
|       options: { | ||||
|  |  | |||
|  | @ -16,7 +16,7 @@ module.exports = merge(baseWebpackConfig, { | |||
|   }, | ||||
|   mode: 'development', | ||||
|   // eval-source-map is faster for development
 | ||||
|   devtool: '#eval-source-map', | ||||
|   devtool: 'eval-source-map', | ||||
|   plugins: [ | ||||
|     new webpack.DefinePlugin({ | ||||
|       'process.env': config.dev.env, | ||||
|  |  | |||
|  | @ -5,6 +5,7 @@ var webpack = require('webpack') | |||
| var merge = require('webpack-merge') | ||||
| var baseWebpackConfig = require('./webpack.base.conf') | ||||
| var MiniCssExtractPlugin = require('mini-css-extract-plugin') | ||||
| const CssMinimizerPlugin = require("css-minimizer-webpack-plugin") | ||||
| var HtmlWebpackPlugin = require('html-webpack-plugin') | ||||
| var env = process.env.NODE_ENV === 'testing' | ||||
|     ? require('../config/test.env') | ||||
|  | @ -19,12 +20,16 @@ var webpackConfig = merge(baseWebpackConfig, { | |||
|   module: { | ||||
|     rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, extract: true }) | ||||
|   }, | ||||
|   devtool: config.build.productionSourceMap ? '#source-map' : false, | ||||
|   devtool: config.build.productionSourceMap ? 'source-map' : false, | ||||
|   optimization: { | ||||
|     minimize: true, | ||||
|     splitChunks: { | ||||
|       chunks: 'all' | ||||
|     } | ||||
|     }, | ||||
|     minimizer: [ | ||||
|       `...`, | ||||
|       new CssMinimizerPlugin() | ||||
|     ] | ||||
|   }, | ||||
|   output: { | ||||
|     path: config.build.assetsRoot, | ||||
|  | @ -60,9 +65,7 @@ var webpackConfig = merge(baseWebpackConfig, { | |||
|         ignoreCustomComments: [/server-generated-meta/] | ||||
|         // more options:
 | ||||
|         // https://github.com/kangax/html-minifier#options-quick-reference
 | ||||
|       }, | ||||
|       // necessary to consistently work with multiple chunks via CommonsChunkPlugin
 | ||||
|       chunksSortMode: 'dependency' | ||||
|       } | ||||
|     }), | ||||
|     // split vendor js into its own file
 | ||||
|     // extract webpack runtime and module manifest to its own file in order to
 | ||||
|  |  | |||
							
								
								
									
										56
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										56
									
								
								package.json
									
									
									
									
									
								
							|  | @ -34,91 +34,91 @@ | |||
|     "escape-html": "1.0.3", | ||||
|     "js-cookie": "3.0.1", | ||||
|     "localforage": "1.10.0", | ||||
|     "parse-link-header": "1.0.1", | ||||
|     "parse-link-header": "2.0.0", | ||||
|     "phoenix": "1.6.2", | ||||
|     "punycode.js": "2.1.0", | ||||
|     "qrcode": "1.5.0", | ||||
|     "querystring-es3": "0.2.1", | ||||
|     "url": "0.11.0", | ||||
|     "utf8": "3.0.0", | ||||
|     "vue": "3.2.37", | ||||
|     "vue-i18n": "9.2.2", | ||||
|     "vue-router": "4.1.3", | ||||
|     "vue-template-compiler": "2.7.8", | ||||
|     "vue-template-compiler": "2.7.9", | ||||
|     "vuex": "4.0.2" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@babel/core": "7.18.10", | ||||
|     "@babel/eslint-parser": "7.18.9", | ||||
|     "@babel/plugin-transform-runtime": "7.18.10", | ||||
|     "@babel/preset-env": "7.18.10", | ||||
|     "@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", | ||||
|     "autoprefixer": "10.4.8", | ||||
|     "babel-loader": "8.2.5", | ||||
|     "babel-plugin-lodash": "3.3.4", | ||||
|     "chai": "4.3.6", | ||||
|     "chalk": "1.1.3", | ||||
|     "chromedriver": "104.0.0", | ||||
|     "connect-history-api-fallback": "2.0.0", | ||||
|     "copy-webpack-plugin": "6.4.1", | ||||
|     "copy-webpack-plugin": "11.0.0", | ||||
|     "cross-spawn": "7.0.3", | ||||
|     "css-loader": "0.28.11", | ||||
|     "css-loader": "6.7.1", | ||||
|     "css-minimizer-webpack-plugin": "4.0.0", | ||||
|     "custom-event-polyfill": "1.0.7", | ||||
|     "eslint": "8.22.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-n": "15.2.5", | ||||
|     "eslint-plugin-promise": "6.0.0", | ||||
|     "eslint-plugin-vue": "9.3.0", | ||||
|     "eslint-plugin-vue": "9.4.0", | ||||
|     "eslint-webpack-plugin": "3.2.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", | ||||
|     "html-webpack-plugin": "5.5.0", | ||||
|     "http-proxy-middleware": "2.0.6", | ||||
|     "iso-639-1": "2.1.15", | ||||
|     "isparta-loader": "2.0.0", | ||||
|     "json-loader": "0.5.7", | ||||
|     "karma": "6.4.0", | ||||
|     "karma-coverage": "1.1.2", | ||||
|     "karma-firefox-launcher": "1.3.0", | ||||
|     "karma-coverage": "2.2.0", | ||||
|     "karma-firefox-launcher": "2.1.2", | ||||
|     "karma-mocha": "2.0.1", | ||||
|     "karma-mocha-reporter": "2.2.5", | ||||
|     "karma-sinon-chai": "2.0.2", | ||||
|     "karma-sourcemap-loader": "0.3.8", | ||||
|     "karma-spec-reporter": "0.0.34", | ||||
|     "karma-webpack": "4.0.2", | ||||
|     "karma-webpack": "5.0.0", | ||||
|     "lodash": "4.17.21", | ||||
|     "lolex": "1.6.0", | ||||
|     "mini-css-extract-plugin": "0.12.0", | ||||
|     "mocha": "3.5.3", | ||||
|     "nightwatch": "0.9.21", | ||||
|     "opn": "4.0.2", | ||||
|     "mini-css-extract-plugin": "2.6.1", | ||||
|     "mocha": "10.0.0", | ||||
|     "nightwatch": "2.3.3", | ||||
|     "opn": "5.5.0", | ||||
|     "ora": "0.4.1", | ||||
|     "postcss-loader": "3.0.0", | ||||
|     "raw-loader": "0.5.1", | ||||
|     "sass": "1.54.4", | ||||
|     "sass-loader": "7.3.1", | ||||
|     "postcss": "8.4.16", | ||||
|     "postcss-loader": "7.0.1", | ||||
|     "sass": "1.54.5", | ||||
|     "sass-loader": "13.0.2", | ||||
|     "selenium-server": "2.53.1", | ||||
|     "semver": "5.7.1", | ||||
|     "serviceworker-webpack-plugin": "1.0.1", | ||||
|     "serviceworker-webpack5-plugin": "2.0.0", | ||||
|     "shelljs": "0.8.5", | ||||
|     "sinon": "2.4.1", | ||||
|     "sinon-chai": "2.14.0", | ||||
|     "stylelint": "13.13.1", | ||||
|     "stylelint-config-standard": "20.0.0", | ||||
|     "stylelint-rscss": "0.4.0", | ||||
|     "url-loader": "1.1.2", | ||||
|     "vue-loader": "16.8.3", | ||||
|     "vue-loader": "17.0.0", | ||||
|     "vue-style-loader": "4.1.3", | ||||
|     "webpack": "4.46.0", | ||||
|     "webpack": "5.74.0", | ||||
|     "webpack-dev-middleware": "3.7.3", | ||||
|     "webpack-hot-middleware": "2.25.2", | ||||
|     "webpack-merge": "0.20.0" | ||||
|  |  | |||
|  | @ -60,6 +60,13 @@ export default { | |||
|         '-' + this.layoutType | ||||
|       ] | ||||
|     }, | ||||
|     navClasses () { | ||||
|       const { navbarColumnStretch } = this.$store.getters.mergedConfig | ||||
|       return [ | ||||
|         '-' + this.layoutType, | ||||
|         ...(navbarColumnStretch ? ['-column-stretch'] : []) | ||||
|       ] | ||||
|     }, | ||||
|     currentUser () { return this.$store.state.users.currentUser }, | ||||
|     userBackground () { return this.currentUser.background_image }, | ||||
|     instanceBackground () { | ||||
|  |  | |||
							
								
								
									
										41
									
								
								src/App.scss
									
									
									
									
									
								
							
							
						
						
									
										41
									
								
								src/App.scss
									
									
									
									
									
								
							|  | @ -5,12 +5,12 @@ | |||
|   --navbar-height: 3.5rem; | ||||
|   --post-line-height: 1.4; | ||||
|   // Z-Index stuff | ||||
|   --ZI_media_modal: 90000; | ||||
|   --ZI_modals_popovers: 85000; | ||||
|   --ZI_modals: 80000; | ||||
|   --ZI_navbar_popovers: 75000; | ||||
|   --ZI_navbar: 70000; | ||||
|   --ZI_popovers: 60000; | ||||
|   --ZI_media_modal: 9000; | ||||
|   --ZI_modals_popovers: 8500; | ||||
|   --ZI_modals: 8000; | ||||
|   --ZI_navbar_popovers: 7500; | ||||
|   --ZI_navbar: 7000; | ||||
|   --ZI_popovers: 6000; | ||||
| } | ||||
| 
 | ||||
| html { | ||||
|  | @ -141,6 +141,11 @@ nav { | |||
|   grid-area: sidebar; | ||||
| } | ||||
| 
 | ||||
| #modal { | ||||
|   position: absolute; | ||||
|   z-index: var(--ZI_modals); | ||||
| } | ||||
| 
 | ||||
| .column.-scrollable { | ||||
|   top: var(--navbar-height); | ||||
|   position: sticky; | ||||
|  | @ -182,13 +187,18 @@ nav { | |||
| 
 | ||||
| .app-layout { | ||||
|   --miniColumn: 25rem; | ||||
|   --maxiColumn: minmax(var(--miniColumn), 45rem); | ||||
|   --maxiColumn: 45rem; | ||||
|   --columnGap: 1em; | ||||
|   --status-margin: 0.75em; | ||||
|   --effectiveSidebarColumnWidth: minmax(var(--miniColumn), var(--sidebarColumnWidth, var(--miniColumn))); | ||||
|   --effectiveNotifsColumnWidth: minmax(var(--miniColumn), var(--notifsColumnWidth, var(--miniColumn))); | ||||
|   --effectiveContentColumnWidth: minmax(var(--miniColumn), var(--contentColumnWidth, var(--maxiColumn))); | ||||
| 
 | ||||
|   position: relative; | ||||
|   display: grid; | ||||
|   grid-template-columns: var(--miniColumn) var(--maxiColumn); | ||||
|   grid-template-columns: | ||||
|     var(--effectiveSidebarColumnWidth) | ||||
|     var(--effectiveContentColumnWidth); | ||||
|   grid-template-areas: "sidebar content"; | ||||
|   grid-template-rows: 1fr; | ||||
|   box-sizing: border-box; | ||||
|  | @ -282,15 +292,24 @@ nav { | |||
|   } | ||||
| 
 | ||||
|   &.-reverse:not(.-wide):not(.-mobile) { | ||||
|     grid-template-columns: var(--maxiColumn) var(--miniColumn); | ||||
|     grid-template-columns: | ||||
|       var(--effectiveContentColumnWidth) | ||||
|       var(--effectiveSidebarColumnWidth); | ||||
|     grid-template-areas: "content sidebar"; | ||||
|   } | ||||
| 
 | ||||
|   &.-wide { | ||||
|     grid-template-columns: var(--miniColumn) var(--maxiColumn) var(--miniColumn); | ||||
|     grid-template-columns: | ||||
|       var(--effectiveSidebarColumnWidth) | ||||
|       var(--effectiveContentColumnWidth) | ||||
|       var(--effectiveNotifsColumnWidth); | ||||
|     grid-template-areas: "sidebar content notifs"; | ||||
| 
 | ||||
|     &.-reverse { | ||||
|       grid-template-columns: | ||||
|         var(--effectiveNotifsColumnWidth) | ||||
|         var(--effectiveContentColumnWidth) | ||||
|         var(--effectiveSidebarColumnWidth); | ||||
|       grid-template-areas: "notifs content sidebar"; | ||||
|     } | ||||
|   } | ||||
|  | @ -752,7 +771,7 @@ option { | |||
| } | ||||
| 
 | ||||
| .fa-old-padding { | ||||
|   &.svg-inline--fa { | ||||
|   &.svg-inline--fa, &-layer { | ||||
|     padding: 0 0.3em; | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -8,7 +8,10 @@ | |||
|       class="app-bg-wrapper" | ||||
|     /> | ||||
|     <MobileNav v-if="layoutType === 'mobile'" /> | ||||
|     <DesktopNav v-else /> | ||||
|     <DesktopNav | ||||
|       v-else | ||||
|       :class="navClasses" | ||||
|     /> | ||||
|     <Notifications v-if="currentUser" /> | ||||
|     <div | ||||
|       id="content" | ||||
|  |  | |||
							
								
								
									
										17
									
								
								src/_mixins.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/_mixins.scss
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,17 @@ | |||
| @mixin unfocused-style { | ||||
|   @content; | ||||
| 
 | ||||
|   &:focus:not(:focus-visible):not(:hover) { | ||||
|     @content; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| @mixin focused-style { | ||||
|   &:hover, &:focus { | ||||
|     @content; | ||||
|   } | ||||
| 
 | ||||
|   &:focus-visible { | ||||
|     @content; | ||||
|   } | ||||
| } | ||||
|  | @ -12,7 +12,7 @@ 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 } from '../services/style_setter/style_setter.js' | ||||
| import { applyTheme, applyConfig } from '../services/style_setter/style_setter.js' | ||||
| import FaviconService from '../services/favicon_service/favicon_service.js' | ||||
| 
 | ||||
| let staticInitialResults = null | ||||
|  | @ -360,6 +360,8 @@ const afterStoreSetup = async ({ store, i18n }) => { | |||
|     console.error('Failed to load any theme!') | ||||
|   } | ||||
| 
 | ||||
|   applyConfig(store.state.config) | ||||
| 
 | ||||
|   // Now we can try getting the server settings and logging in
 | ||||
|   // Most of these are preloaded into the index.html so blocking is minimized
 | ||||
|   await Promise.all([ | ||||
|  |  | |||
|  | @ -61,7 +61,7 @@ export default (store) => { | |||
|       component: RemoteUserResolver, | ||||
|       beforeEnter: validateAuthenticatedRoute | ||||
|     }, | ||||
|     { name: 'external-user-profile', path: '/users/:id', component: UserProfile }, | ||||
|     { name: 'external-user-profile', path: '/users/$:id', component: UserProfile }, | ||||
|     { name: 'interactions', path: '/users/:username/interactions', component: Interactions, beforeEnter: validateAuthenticatedRoute }, | ||||
|     { name: 'dms', path: '/users/:username/dms', component: DMs, beforeEnter: validateAuthenticatedRoute }, | ||||
|     { name: 'registration', path: '/registration', component: Registration }, | ||||
|  | @ -75,7 +75,8 @@ export default (store) => { | |||
|     { 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 }, | ||||
|     { name: 'legacy-user-profile', path: '/:name', component: UserProfile }, | ||||
|     { name: 'lists', path: '/lists', component: Lists }, | ||||
|     { name: 'lists-timeline', path: '/lists/:id', component: ListsTimeline }, | ||||
|     { name: 'lists-edit', path: '/lists/:id/edit', component: ListsEdit } | ||||
|  |  | |||
|  | @ -23,6 +23,26 @@ | |||
|     max-width: 980px; | ||||
|   } | ||||
| 
 | ||||
|   &.-column-stretch .inner-nav { | ||||
|     --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) | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   &.-logoLeft .inner-nav { | ||||
|     grid-template-columns: auto 2fr 2fr; | ||||
|     grid-template-areas: "logo sitename actions"; | ||||
|  |  | |||
|  | @ -6,7 +6,9 @@ import { | |||
|   faEyeSlash, | ||||
|   faThumbtack, | ||||
|   faShareAlt, | ||||
|   faExternalLinkAlt | ||||
|   faExternalLinkAlt, | ||||
|   faPlus, | ||||
|   faTimes | ||||
| } from '@fortawesome/free-solid-svg-icons' | ||||
| import { | ||||
|   faBookmark as faBookmarkReg, | ||||
|  | @ -21,13 +23,26 @@ library.add( | |||
|   faThumbtack, | ||||
|   faShareAlt, | ||||
|   faExternalLinkAlt, | ||||
|   faFlag | ||||
|   faFlag, | ||||
|   faPlus, | ||||
|   faTimes | ||||
| ) | ||||
| 
 | ||||
| const ExtraButtons = { | ||||
|   props: ['status'], | ||||
|   components: { Popover }, | ||||
|   data () { | ||||
|     return { | ||||
|       expanded: false | ||||
|     } | ||||
|   }, | ||||
|   methods: { | ||||
|     onShow () { | ||||
|       this.expanded = true | ||||
|     }, | ||||
|     onClose () { | ||||
|       this.expanded = false | ||||
|     }, | ||||
|     deleteStatus () { | ||||
|       const confirmed = window.confirm(this.$t('status.delete_confirm')) | ||||
|       if (confirmed) { | ||||
|  |  | |||
|  | @ -6,6 +6,8 @@ | |||
|     :offset="{ y: 5 }" | ||||
|     :bound-to="{ x: 'container' }" | ||||
|     remove-padding | ||||
|     @show="onShow" | ||||
|     @close="onClose" | ||||
|   > | ||||
|     <template #content="{close}"> | ||||
|       <div class="dropdown-menu"> | ||||
|  | @ -122,10 +124,24 @@ | |||
|     </template> | ||||
|     <template #trigger> | ||||
|       <span class="button-unstyled popover-trigger"> | ||||
|         <FAIcon | ||||
|           class="fa-scale-110 fa-old-padding" | ||||
|           icon="ellipsis-h" | ||||
|         /> | ||||
|         <FALayers class="fa-old-padding-layer"> | ||||
|           <FAIcon | ||||
|             class="fa-scale-110 " | ||||
|             icon="ellipsis-h" | ||||
|           /> | ||||
|           <FAIcon | ||||
|             v-show="!expanded" | ||||
|             class="focus-marker" | ||||
|             transform="shrink-6 up-8 right-16" | ||||
|             icon="plus" | ||||
|           /> | ||||
|           <FAIcon | ||||
|             v-show="expanded" | ||||
|             class="focus-marker" | ||||
|             transform="shrink-6 up-8 right-16" | ||||
|             icon="times" | ||||
|           /> | ||||
|         </FALayers> | ||||
|       </span> | ||||
|     </template> | ||||
|   </Popover> | ||||
|  | @ -135,6 +151,7 @@ | |||
| 
 | ||||
| <style lang="scss"> | ||||
| @import '../../_variables.scss'; | ||||
| @import '../../_mixins.scss'; | ||||
| 
 | ||||
| .ExtraButtons { | ||||
|   /* override of popover internal stuff */ | ||||
|  | @ -151,6 +168,21 @@ | |||
|       color: $fallback--text; | ||||
|       color: var(--text, $fallback--text); | ||||
|     } | ||||
| 
 | ||||
|   } | ||||
| 
 | ||||
|   .popover-trigger-button { | ||||
|     @include unfocused-style { | ||||
|       .focus-marker { | ||||
|         visibility: hidden; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     @include focused-style { | ||||
|       .focus-marker { | ||||
|         visibility: visible; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </style> | ||||
|  |  | |||
|  | @ -1,13 +1,21 @@ | |||
| import { mapGetters } from 'vuex' | ||||
| import { library } from '@fortawesome/fontawesome-svg-core' | ||||
| import { faStar } from '@fortawesome/free-solid-svg-icons' | ||||
| import { | ||||
|   faStar, | ||||
|   faPlus, | ||||
|   faMinus, | ||||
|   faCheck | ||||
| } from '@fortawesome/free-solid-svg-icons' | ||||
| import { | ||||
|   faStar as faStarRegular | ||||
| } from '@fortawesome/free-regular-svg-icons' | ||||
| 
 | ||||
| library.add( | ||||
|   faStar, | ||||
|   faStarRegular | ||||
|   faStarRegular, | ||||
|   faPlus, | ||||
|   faMinus, | ||||
|   faCheck | ||||
| ) | ||||
| 
 | ||||
| const FavoriteButton = { | ||||
|  |  | |||
|  | @ -7,11 +7,31 @@ | |||
|       :title="$t('tool_tip.favorite')" | ||||
|       @click.prevent="favorite()" | ||||
|     > | ||||
|       <FAIcon | ||||
|         class="fa-scale-110 fa-old-padding" | ||||
|         :icon="[status.favorited ? 'fas' : 'far', 'star']" | ||||
|         :spin="animated" | ||||
|       /> | ||||
|       <FALayers class="fa-scale-110 fa-old-padding-layer"> | ||||
|         <FAIcon | ||||
|           class="fa-scale-110" | ||||
|           :icon="[status.favorited ? 'fas' : 'far', 'star']" | ||||
|           :spin="animated" | ||||
|         /> | ||||
|         <FAIcon | ||||
|           v-if="status.favorited" | ||||
|           class="active-marker" | ||||
|           transform="shrink-6 up-9 right-12" | ||||
|           icon="check" | ||||
|         /> | ||||
|         <FAIcon | ||||
|           v-if="!status.favorited" | ||||
|           class="focus-marker" | ||||
|           transform="shrink-6 up-9 right-12" | ||||
|           icon="plus" | ||||
|         /> | ||||
|         <FAIcon | ||||
|           v-else | ||||
|           class="focus-marker" | ||||
|           transform="shrink-6 up-9 right-12" | ||||
|           icon="minus" | ||||
|         /> | ||||
|       </FALayers> | ||||
|     </button> | ||||
|     <span v-else> | ||||
|       <FAIcon | ||||
|  | @ -33,6 +53,7 @@ | |||
| 
 | ||||
| <style lang="scss"> | ||||
| @import '../../_variables.scss'; | ||||
| @import '../../_mixins.scss'; | ||||
| 
 | ||||
| .FavoriteButton { | ||||
|   display: flex; | ||||
|  | @ -57,6 +78,26 @@ | |||
|       color: $fallback--cOrange; | ||||
|       color: var(--cOrange, $fallback--cOrange); | ||||
|     } | ||||
| 
 | ||||
|     @include unfocused-style { | ||||
|       .focus-marker { | ||||
|         visibility: hidden; | ||||
|       } | ||||
| 
 | ||||
|       .active-marker { | ||||
|         visibility: visible; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     @include focused-style { | ||||
|       .focus-marker { | ||||
|         visibility: visible; | ||||
|       } | ||||
| 
 | ||||
|       .active-marker { | ||||
|         visibility: hidden; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </style> | ||||
|  |  | |||
|  | @ -29,10 +29,10 @@ | |||
| 
 | ||||
| .global-notice-list { | ||||
|   position: fixed; | ||||
|   top: 50px; | ||||
|   top: calc(var(--navbar-height) + 0.5em); | ||||
|   width: 100%; | ||||
|   pointer-events: none; | ||||
|   z-index: var(--ZI_popovers); | ||||
|   z-index: var(--ZI_navbar_popovers); | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   align-items: center; | ||||
|  |  | |||
|  | @ -1,15 +1,21 @@ | |||
| import Popover from '../popover/popover.vue' | ||||
| import { library } from '@fortawesome/fontawesome-svg-core' | ||||
| import { faPlus, faTimes } from '@fortawesome/free-solid-svg-icons' | ||||
| import { faSmileBeam } from '@fortawesome/free-regular-svg-icons' | ||||
| import { trim } from 'lodash' | ||||
| 
 | ||||
| library.add(faSmileBeam) | ||||
| library.add( | ||||
|   faPlus, | ||||
|   faTimes, | ||||
|   faSmileBeam | ||||
| ) | ||||
| 
 | ||||
| const ReactButton = { | ||||
|   props: ['status'], | ||||
|   data () { | ||||
|     return { | ||||
|       filterWord: '' | ||||
|       filterWord: '', | ||||
|       expanded: false | ||||
|     } | ||||
|   }, | ||||
|   components: { | ||||
|  | @ -25,6 +31,13 @@ const ReactButton = { | |||
|       } | ||||
|       close() | ||||
|     }, | ||||
|     onShow () { | ||||
|       this.expanded = true | ||||
|       this.focusInput() | ||||
|     }, | ||||
|     onClose () { | ||||
|       this.expanded = false | ||||
|     }, | ||||
|     focusInput () { | ||||
|       this.$nextTick(() => { | ||||
|         const input = this.$el.querySelector('input') | ||||
|  |  | |||
|  | @ -7,7 +7,8 @@ | |||
|     :bound-to="{ x: 'container' }" | ||||
|     remove-padding | ||||
|     popover-class="ReactButton popover-default" | ||||
|     @show="focusInput" | ||||
|     @show="onShow" | ||||
|     @close="onClose" | ||||
|   > | ||||
|     <template #content="{close}"> | ||||
|       <div class="reaction-picker-filter"> | ||||
|  | @ -46,10 +47,24 @@ | |||
|         class="button-unstyled popover-trigger" | ||||
|         :title="$t('tool_tip.add_reaction')" | ||||
|       > | ||||
|         <FAIcon | ||||
|           class="fa-scale-110 fa-old-padding" | ||||
|           :icon="['far', 'smile-beam']" | ||||
|         /> | ||||
|         <FALayers> | ||||
|           <FAIcon | ||||
|             class="fa-scale-110 fa-old-padding" | ||||
|             :icon="['far', 'smile-beam']" | ||||
|           /> | ||||
|           <FAIcon | ||||
|             v-show="!expanded" | ||||
|             class="focus-marker" | ||||
|             transform="shrink-6 up-9 right-17" | ||||
|             icon="plus" | ||||
|           /> | ||||
|           <FAIcon | ||||
|             v-show="expanded" | ||||
|             class="focus-marker" | ||||
|             transform="shrink-6 up-9 right-17" | ||||
|             icon="times" | ||||
|           /> | ||||
|         </FALayers> | ||||
|       </span> | ||||
|     </template> | ||||
|   </Popover> | ||||
|  | @ -59,6 +74,7 @@ | |||
| 
 | ||||
| <style lang="scss"> | ||||
| @import '../../_variables.scss'; | ||||
| @import '../../_mixins.scss'; | ||||
| 
 | ||||
| .ReactButton { | ||||
|   .reaction-picker-filter { | ||||
|  | @ -125,6 +141,21 @@ | |||
|       color: $fallback--text; | ||||
|       color: var(--text, $fallback--text); | ||||
|     } | ||||
| 
 | ||||
|   } | ||||
| 
 | ||||
|   .popover-trigger-button { | ||||
|     @include unfocused-style { | ||||
|       .focus-marker { | ||||
|         visibility: hidden; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     @include focused-style { | ||||
|       .focus-marker { | ||||
|         visibility: visible; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,7 +1,15 @@ | |||
| import { library } from '@fortawesome/fontawesome-svg-core' | ||||
| import { faReply } from '@fortawesome/free-solid-svg-icons' | ||||
| import { | ||||
|   faReply, | ||||
|   faPlus, | ||||
|   faTimes | ||||
| } from '@fortawesome/free-solid-svg-icons' | ||||
| 
 | ||||
| library.add(faReply) | ||||
| library.add( | ||||
|   faReply, | ||||
|   faPlus, | ||||
|   faTimes | ||||
| ) | ||||
| 
 | ||||
| const ReplyButton = { | ||||
|   name: 'ReplyButton', | ||||
|  |  | |||
|  | @ -7,10 +7,24 @@ | |||
|       :title="$t('tool_tip.reply')" | ||||
|       @click.prevent="$emit('toggle')" | ||||
|     > | ||||
|       <FAIcon | ||||
|         class="fa-scale-110 fa-old-padding" | ||||
|         icon="reply" | ||||
|       /> | ||||
|       <FALayers class="fa-old-padding-layer"> | ||||
|         <FAIcon | ||||
|           class="fa-scale-110" | ||||
|           icon="reply" | ||||
|         /> | ||||
|         <FAIcon | ||||
|           v-if="!replying" | ||||
|           class="focus-marker" | ||||
|           transform="shrink-6 up-8 right-11" | ||||
|           icon="plus" | ||||
|         /> | ||||
|         <FAIcon | ||||
|           v-else | ||||
|           class="focus-marker" | ||||
|           transform="shrink-6 up-8 right-11" | ||||
|           icon="times" | ||||
|         /> | ||||
|       </FALayers> | ||||
|     </button> | ||||
|     <span v-else> | ||||
|       <FAIcon | ||||
|  | @ -32,6 +46,7 @@ | |||
| 
 | ||||
| <style lang="scss"> | ||||
| @import '../../_variables.scss'; | ||||
| @import '../../_mixins.scss'; | ||||
| 
 | ||||
| .ReplyButton { | ||||
|   display: flex; | ||||
|  | @ -52,6 +67,18 @@ | |||
|       color: $fallback--cBlue; | ||||
|       color: var(--cBlue, $fallback--cBlue); | ||||
|     } | ||||
| 
 | ||||
|     @include unfocused-style { | ||||
|       .focus-marker { | ||||
|         visibility: hidden; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     @include focused-style { | ||||
|       .focus-marker { | ||||
|         visibility: visible; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -1,7 +1,17 @@ | |||
| import { library } from '@fortawesome/fontawesome-svg-core' | ||||
| import { faRetweet } from '@fortawesome/free-solid-svg-icons' | ||||
| import { | ||||
|   faRetweet, | ||||
|   faPlus, | ||||
|   faMinus, | ||||
|   faCheck | ||||
| } from '@fortawesome/free-solid-svg-icons' | ||||
| 
 | ||||
| library.add(faRetweet) | ||||
| library.add( | ||||
|   faRetweet, | ||||
|   faPlus, | ||||
|   faMinus, | ||||
|   faCheck | ||||
| ) | ||||
| 
 | ||||
| const RetweetButton = { | ||||
|   props: ['status', 'loggedIn', 'visibility'], | ||||
|  |  | |||
|  | @ -7,11 +7,31 @@ | |||
|       :title="$t('tool_tip.repeat')" | ||||
|       @click.prevent="retweet()" | ||||
|     > | ||||
|       <FAIcon | ||||
|         class="fa-scale-110 fa-old-padding" | ||||
|         icon="retweet" | ||||
|         :spin="animated" | ||||
|       /> | ||||
|       <FALayers class="fa-old-padding-layer"> | ||||
|         <FAIcon | ||||
|           class="fa-scale-110" | ||||
|           icon="retweet" | ||||
|           :spin="animated" | ||||
|         /> | ||||
|         <FAIcon | ||||
|           v-if="status.repeated" | ||||
|           class="active-marker" | ||||
|           transform="shrink-6 up-9 right-12" | ||||
|           icon="check" | ||||
|         /> | ||||
|         <FAIcon | ||||
|           v-if="!status.repeated" | ||||
|           class="focus-marker" | ||||
|           transform="shrink-6 up-9 right-12" | ||||
|           icon="plus" | ||||
|         /> | ||||
|         <FAIcon | ||||
|           v-else | ||||
|           class="focus-marker" | ||||
|           transform="shrink-6 up-9 right-12" | ||||
|           icon="minus" | ||||
|         /> | ||||
|       </FALayers> | ||||
|     </button> | ||||
|     <span v-else-if="loggedIn"> | ||||
|       <FAIcon | ||||
|  | @ -40,6 +60,7 @@ | |||
| 
 | ||||
| <style lang="scss"> | ||||
| @import '../../_variables.scss'; | ||||
| @import '../../_mixins.scss'; | ||||
| 
 | ||||
| .RetweetButton { | ||||
|   display: flex; | ||||
|  | @ -64,6 +85,26 @@ | |||
|       color: $fallback--cGreen; | ||||
|       color: var(--cGreen, $fallback--cGreen); | ||||
|     } | ||||
| 
 | ||||
|     @include unfocused-style { | ||||
|       .focus-marker { | ||||
|         visibility: hidden; | ||||
|       } | ||||
| 
 | ||||
|       .active-marker { | ||||
|         visibility: visible; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     @include focused-style { | ||||
|       .focus-marker { | ||||
|         visibility: visible; | ||||
|       } | ||||
| 
 | ||||
|       .active-marker { | ||||
|         visibility: hidden; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </style> | ||||
|  |  | |||
|  | @ -42,6 +42,9 @@ export default { | |||
|   methods: { | ||||
|     update (e) { | ||||
|       set(this.$parent, this.path, e) | ||||
|     }, | ||||
|     reset () { | ||||
|       set(this.$parent, this.path, this.defaultState) | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -15,7 +15,12 @@ | |||
|         <slot /> | ||||
|       </span> | ||||
|       {{ ' ' }} | ||||
|       <ModifiedIndicator :changed="isChanged" /><ServerSideIndicator :server-side="isServerSide" /> </Checkbox> | ||||
|       <ModifiedIndicator | ||||
|         :changed="isChanged" | ||||
|         :onclick="reset" | ||||
|       /> | ||||
|       <ServerSideIndicator :server-side="isServerSide" /> | ||||
|     </Checkbox> | ||||
|   </label> | ||||
| </template> | ||||
| 
 | ||||
|  |  | |||
|  | @ -43,6 +43,9 @@ export default { | |||
|   methods: { | ||||
|     update (e) { | ||||
|       set(this.$parent, this.path, e) | ||||
|     }, | ||||
|     reset () { | ||||
|       set(this.$parent, this.path, this.defaultState) | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -19,7 +19,10 @@ | |||
|         {{ option.value === defaultState ? $t('settings.instance_default_simple') : '' }} | ||||
|       </option> | ||||
|     </Select> | ||||
|     <ModifiedIndicator :changed="isChanged" /> | ||||
|     <ModifiedIndicator | ||||
|       :changed="isChanged" | ||||
|       :onclick="reset" | ||||
|     /> | ||||
|     <ServerSideIndicator :server-side="isServerSide" /> | ||||
|   </label> | ||||
| </template> | ||||
|  |  | |||
|  | @ -36,6 +36,9 @@ export default { | |||
|   methods: { | ||||
|     update (e) { | ||||
|       set(this.$parent, this.path, parseInt(e.target.value)) | ||||
|     }, | ||||
|     reset () { | ||||
|       set(this.$parent, this.path, this.defaultState) | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -17,7 +17,10 @@ | |||
|       @change="update" | ||||
|     > | ||||
|     {{ ' ' }} | ||||
|     <ModifiedIndicator :changed="isChanged" /> | ||||
|     <ModifiedIndicator | ||||
|       :changed="isChanged" | ||||
|       :onclick="reset" | ||||
|     /> | ||||
|   </span> | ||||
| </template> | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										67
									
								
								src/components/settings_modal/helpers/size_setting.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								src/components/settings_modal/helpers/size_setting.js
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,67 @@ | |||
| import { get, set } from 'lodash' | ||||
| import ModifiedIndicator from './modified_indicator.vue' | ||||
| import Select from 'src/components/select/select.vue' | ||||
| 
 | ||||
| export const allCssUnits = ['cm', 'mm', 'in', 'px', 'pt', 'pc', 'em', 'ex', 'ch', 'rem', 'vw', 'vh', 'vmin', 'vmax', '%'] | ||||
| export const defaultHorizontalUnits = ['px', 'rem', 'vw'] | ||||
| export const defaultVerticalUnits = ['px', 'rem', 'vh'] | ||||
| 
 | ||||
| export default { | ||||
|   components: { | ||||
|     ModifiedIndicator, | ||||
|     Select | ||||
|   }, | ||||
|   props: { | ||||
|     path: String, | ||||
|     disabled: Boolean, | ||||
|     min: Number, | ||||
|     units: { | ||||
|       type: [String], | ||||
|       default: () => allCssUnits | ||||
|     }, | ||||
|     expert: [Number, String] | ||||
|   }, | ||||
|   computed: { | ||||
|     pathDefault () { | ||||
|       const [firstSegment, ...rest] = this.path.split('.') | ||||
|       return [firstSegment + 'DefaultValue', ...rest].join('.') | ||||
|     }, | ||||
|     stateUnit () { | ||||
|       return (this.state || '').replace(/\d+/, '') | ||||
|     }, | ||||
|     stateValue () { | ||||
|       return (this.state || '').replace(/\D+/, '') | ||||
|     }, | ||||
|     state () { | ||||
|       const value = get(this.$parent, this.path) | ||||
|       if (value === undefined) { | ||||
|         return this.defaultState | ||||
|       } else { | ||||
|         return value | ||||
|       } | ||||
|     }, | ||||
|     defaultState () { | ||||
|       return get(this.$parent, this.pathDefault) | ||||
|     }, | ||||
|     isChanged () { | ||||
|       return this.state !== this.defaultState | ||||
|     }, | ||||
|     matchesExpertLevel () { | ||||
|       return (this.expert || 0) <= this.$parent.expertLevel | ||||
|     } | ||||
|   }, | ||||
|   methods: { | ||||
|     update (e) { | ||||
|       set(this.$parent, this.path, e) | ||||
|     }, | ||||
|     reset () { | ||||
|       set(this.$parent, this.path, this.defaultState) | ||||
|     }, | ||||
|     updateValue (e) { | ||||
|       set(this.$parent, this.path, parseInt(e.target.value) + this.stateUnit) | ||||
|     }, | ||||
|     updateUnit (e) { | ||||
|       set(this.$parent, this.path, this.stateValue + e.target.value) | ||||
|     } | ||||
|   } | ||||
| } | ||||
							
								
								
									
										54
									
								
								src/components/settings_modal/helpers/size_setting.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								src/components/settings_modal/helpers/size_setting.vue
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,54 @@ | |||
| <template> | ||||
|   <span | ||||
|     v-if="matchesExpertLevel" | ||||
|     class="SizeSetting" | ||||
|   > | ||||
|     <label | ||||
|       :for="path" | ||||
|       class="size-label" | ||||
|     > | ||||
|       <slot /> | ||||
|     </label> | ||||
|     <input | ||||
|       :id="path" | ||||
|       class="number-input" | ||||
|       type="number" | ||||
|       step="1" | ||||
|       :disabled="disabled" | ||||
|       :min="min || 0" | ||||
|       :value="stateValue" | ||||
|       @change="updateValue" | ||||
|     > | ||||
|     <Select | ||||
|       :id="path" | ||||
|       :model-value="stateUnit" | ||||
|       :disabled="disabled" | ||||
|       class="css-unit-input" | ||||
|       @change="updateUnit" | ||||
|     > | ||||
|       <option | ||||
|         v-for="option in units" | ||||
|         :key="option" | ||||
|         :value="option" | ||||
|       > | ||||
|         {{ option }} | ||||
|       </option> | ||||
|     </Select> | ||||
|     {{ ' ' }} | ||||
|     <ModifiedIndicator | ||||
|       :changed="isChanged" | ||||
|       :onclick="reset" | ||||
|     /> | ||||
|   </span> | ||||
| </template> | ||||
| 
 | ||||
| <script src="./size_setting.js"></script> | ||||
| 
 | ||||
| <style lang="scss"> | ||||
| .css-unit-input, .css-unit-input select { | ||||
|   margin-left: 0.5em; | ||||
|   width: 4em !important; | ||||
|   max-width: 4em !important; | ||||
|   min-width: 4em !important; | ||||
| } | ||||
| </style> | ||||
|  | @ -2,6 +2,7 @@ import BooleanSetting from '../helpers/boolean_setting.vue' | |||
| import ChoiceSetting from '../helpers/choice_setting.vue' | ||||
| import ScopeSelector from 'src/components/scope_selector/scope_selector.vue' | ||||
| import IntegerSetting from '../helpers/integer_setting.vue' | ||||
| import SizeSetting, { defaultHorizontalUnits } from '../helpers/size_setting.vue' | ||||
| import InterfaceLanguageSwitcher from 'src/components/interface_language_switcher/interface_language_switcher.vue' | ||||
| 
 | ||||
| import SharedComputedObject from '../helpers/shared_computed_object.js' | ||||
|  | @ -43,6 +44,11 @@ const GeneralTab = { | |||
|         value: mode, | ||||
|         label: this.$t(`settings.third_column_mode_${mode}`) | ||||
|       })), | ||||
|       userPopoverAvatarActionOptions: ['close', 'zoom', 'open'].map(mode => ({ | ||||
|         key: mode, | ||||
|         value: mode, | ||||
|         label: this.$t(`settings.user_popover_avatar_action_${mode}`) | ||||
|       })), | ||||
|       loopSilentAvailable: | ||||
|       // Firefox
 | ||||
|       Object.getOwnPropertyDescriptor(HTMLVideoElement.prototype, 'mozHasAudio') || | ||||
|  | @ -56,11 +62,15 @@ const GeneralTab = { | |||
|     BooleanSetting, | ||||
|     ChoiceSetting, | ||||
|     IntegerSetting, | ||||
|     SizeSetting, | ||||
|     InterfaceLanguageSwitcher, | ||||
|     ScopeSelector, | ||||
|     ServerSideIndicator | ||||
|   }, | ||||
|   computed: { | ||||
|     horizontalUnits () { | ||||
|       return defaultHorizontalUnits | ||||
|     }, | ||||
|     postFormats () { | ||||
|       return this.$store.state.instance.postFormats || [] | ||||
|     }, | ||||
|  | @ -71,6 +81,17 @@ const GeneralTab = { | |||
|         label: this.$t(`post_status.content_type["${format}"]`) | ||||
|       })) | ||||
|     }, | ||||
|     columns () { | ||||
|       const mode = this.$store.getters.mergedConfig.thirdColumnMode | ||||
| 
 | ||||
|       const notif = mode === 'none' ? [] : ['notifs'] | ||||
| 
 | ||||
|       if (this.$store.getters.mergedConfig.sidebarRight || mode === 'postform') { | ||||
|         return [...notif, 'content', 'sidebar'] | ||||
|       } else { | ||||
|         return ['sidebar', 'content', ...notif] | ||||
|       } | ||||
|     }, | ||||
|     instanceSpecificPanelPresent () { return this.$store.state.instance.showInstanceSpecificPanel }, | ||||
|     instanceWallpaperUsed () { | ||||
|       return this.$store.state.instance.background && | ||||
|  |  | |||
|  | @ -15,11 +15,6 @@ | |||
|             {{ $t('settings.hide_isp') }} | ||||
|           </BooleanSetting> | ||||
|         </li> | ||||
|         <li> | ||||
|           <BooleanSetting path="sidebarRight"> | ||||
|             {{ $t('settings.right_sidebar') }} | ||||
|           </BooleanSetting> | ||||
|         </li> | ||||
|         <li v-if="instanceWallpaperUsed"> | ||||
|           <BooleanSetting path="hideInstanceWallpaper"> | ||||
|             {{ $t('settings.hide_wallpaper') }} | ||||
|  | @ -65,22 +60,14 @@ | |||
|           </BooleanSetting> | ||||
|         </li> | ||||
|         <li> | ||||
|           <BooleanSetting path="disableStickyHeaders"> | ||||
|             {{ $t('settings.disable_sticky_headers') }} | ||||
|           </BooleanSetting> | ||||
|         </li> | ||||
|         <li> | ||||
|           <BooleanSetting path="showScrollbars"> | ||||
|             {{ $t('settings.show_scrollbars') }} | ||||
|           </BooleanSetting> | ||||
|         </li> | ||||
|         <li> | ||||
|           <BooleanSetting | ||||
|             path="userPopoverZoom" | ||||
|           <ChoiceSetting | ||||
|             id="userPopoverAvatarAction" | ||||
|             path="userPopoverAvatarAction" | ||||
|             :options="userPopoverAvatarActionOptions" | ||||
|             expert="1" | ||||
|           > | ||||
|             {{ $t('settings.user_popover_avatar_zoom') }} | ||||
|           </BooleanSetting> | ||||
|             {{ $t('settings.user_popover_avatar_action') }} | ||||
|           </ChoiceSetting> | ||||
|         </li> | ||||
|         <li> | ||||
|           <BooleanSetting | ||||
|  | @ -90,16 +77,6 @@ | |||
|             {{ $t('settings.user_popover_avatar_overlay') }} | ||||
|           </BooleanSetting> | ||||
|         </li> | ||||
|         <li> | ||||
|           <ChoiceSetting | ||||
|             v-if="user" | ||||
|             id="thirdColumnMode" | ||||
|             path="thirdColumnMode" | ||||
|             :options="thirdColumnModeOptions" | ||||
|           > | ||||
|             {{ $t('settings.third_column_mode') }} | ||||
|           </ChoiceSetting> | ||||
|         </li> | ||||
|         <li> | ||||
|           <BooleanSetting | ||||
|             path="alwaysShowNewPostButton" | ||||
|  | @ -147,6 +124,11 @@ | |||
|             {{ $t('settings.right_sidebar') }} | ||||
|           </BooleanSetting> | ||||
|         </li> | ||||
|         <li> | ||||
|           <BooleanSetting path="navbarColumnStretch"> | ||||
|             {{ $t('settings.navbar_column_stretch') }} | ||||
|           </BooleanSetting> | ||||
|         </li> | ||||
|         <li> | ||||
|           <ChoiceSetting | ||||
|             v-if="user" | ||||
|  | @ -480,3 +462,16 @@ | |||
| </template> | ||||
| 
 | ||||
| <script src="./general_tab.js"></script> | ||||
| 
 | ||||
| <style lang="scss"> | ||||
| .column-settings { | ||||
|   display: flex; | ||||
|   justify-content: space-evenly; | ||||
|   flex-wrap: wrap; | ||||
| } | ||||
| .column-settings .size-label { | ||||
|   display: block; | ||||
|   margin-bottom: 0.5em; | ||||
|   margin-top: 0.5em; | ||||
| } | ||||
| </style> | ||||
|  |  | |||
|  | @ -11,8 +11,8 @@ const UserPopover = { | |||
|     Popover: defineAsyncComponent(() => import('../popover/popover.vue')) | ||||
|   }, | ||||
|   computed: { | ||||
|     userPopoverZoom () { | ||||
|       return this.$store.getters.mergedConfig.userPopoverZoom | ||||
|     userPopoverAvatarAction () { | ||||
|       return this.$store.getters.mergedConfig.userPopoverAvatarAction | ||||
|     }, | ||||
|     userPopoverOverlay () { | ||||
|       return this.$store.getters.mergedConfig.userPopoverOverlay | ||||
|  |  | |||
|  | @ -14,7 +14,7 @@ | |||
|         class="user-popover" | ||||
|         :user-id="userId" | ||||
|         :hide-bio="true" | ||||
|         :avatar-action="userPopoverZoom ? 'zoom' : close" | ||||
|         :avatar-action="userPopoverAvatarAction == 'close' ? close : userPopoverAvatarAction" | ||||
|         :on-close="close" | ||||
|       /> | ||||
|     </template> | ||||
|  |  | |||
|  | @ -45,7 +45,7 @@ const UserProfile = { | |||
|   }, | ||||
|   created () { | ||||
|     const routeParams = this.$route.params | ||||
|     this.load(routeParams.name || routeParams.id) | ||||
|     this.load({ name: routeParams.name, id: routeParams.id }) | ||||
|     this.tab = get(this.$route, 'query.tab', defaultTabKey) | ||||
|   }, | ||||
|   unmounted () { | ||||
|  | @ -106,12 +106,17 @@ const UserProfile = { | |||
|       this.userId = null | ||||
|       this.error = false | ||||
| 
 | ||||
|       const maybeId = userNameOrId.id | ||||
|       const maybeName = userNameOrId.name | ||||
| 
 | ||||
|       // Check if user data is already loaded in store
 | ||||
|       const user = this.$store.getters.findUser(userNameOrId) | ||||
|       const user = maybeId ? this.$store.getters.findUser(maybeId) : this.$store.getters.findUserByName(maybeName) | ||||
|       if (user) { | ||||
|         loadById(user.id) | ||||
|       } else { | ||||
|         this.$store.dispatch('fetchUser', userNameOrId) | ||||
|         (maybeId | ||||
|           ? this.$store.dispatch('fetchUser', maybeId) | ||||
|           : this.$store.dispatch('fetchUserByName', maybeName)) | ||||
|           .then(({ id }) => loadById(id)) | ||||
|           .catch((reason) => { | ||||
|             const errorMessage = get(reason, 'error.error') | ||||
|  | @ -150,12 +155,12 @@ const UserProfile = { | |||
|   watch: { | ||||
|     '$route.params.id': function (newVal) { | ||||
|       if (newVal) { | ||||
|         this.switchUser(newVal) | ||||
|         this.switchUser({ id: newVal }) | ||||
|       } | ||||
|     }, | ||||
|     '$route.params.name': function (newVal) { | ||||
|       if (newVal) { | ||||
|         this.switchUser(newVal) | ||||
|         this.switchUser({ name: newVal }) | ||||
|       } | ||||
|     }, | ||||
|     '$route.query': function (newVal) { | ||||
|  |  | |||
|  | @ -412,6 +412,7 @@ | |||
|     "hide_isp": "Hide instance-specific panel", | ||||
|     "hide_shoutbox": "Hide instance shoutbox", | ||||
|     "right_sidebar": "Reverse order of columns", | ||||
|     "navbar_column_stretch": "Stretch navbar to columns width", | ||||
|     "always_show_post_button": "Always show floating New Post button", | ||||
|     "hide_wallpaper": "Hide instance wallpaper", | ||||
|     "preload_images": "Preload images", | ||||
|  | @ -533,6 +534,11 @@ | |||
|     "third_column_mode_none": "Don't show third column at all", | ||||
|     "third_column_mode_notifications": "Notifications column", | ||||
|     "third_column_mode_postform": "Main post form and navigation", | ||||
|     "columns": "Columns", | ||||
|     "column_sizes": "Column sizes", | ||||
|     "column_sizes_sidebar": "Sidebar", | ||||
|     "column_sizes_content": "Content", | ||||
|     "column_sizes_notifs": "Notifications", | ||||
|     "tree_advanced": "Allow more flexible navigation in tree view", | ||||
|     "tree_fade_ancestors": "Display ancestors of the current status in faint text", | ||||
|     "conversation_display_linear": "Linear-style", | ||||
|  | @ -573,7 +579,10 @@ | |||
|     "mention_link_show_avatar_quick": "Show user avatar next to mentions", | ||||
|     "mention_link_fade_domain": "Fade domains (e.g. {'@'}example.org in {'@'}foo{'@'}example.org)", | ||||
|     "mention_link_bolden_you": "Highlight mention of you when you are mentioned", | ||||
|     "user_popover_avatar_zoom": "Clicking on user avatar in popover zooms it instead of closing the popover", | ||||
|     "user_popover_avatar_action": "Popover avatar click action", | ||||
|     "user_popover_avatar_action_zoom": "Zoom the avatar", | ||||
|     "user_popover_avatar_action_close": "Close the popover", | ||||
|     "user_popover_avatar_action_open": "Open profile", | ||||
|     "user_popover_avatar_overlay": "Show user popover over user avatar", | ||||
|     "fun": "Fun", | ||||
|     "greentext": "Meme arrows", | ||||
|  |  | |||
|  | @ -456,6 +456,15 @@ | |||
|     "subject_line_mastodon": "Как в Mastodon: скопировать как есть", | ||||
|     "subject_line_email": "Как в электронной почте: \"re: тема\"", | ||||
|     "subject_line_behavior": "Копировать тему в ответах", | ||||
|     "third_column_mode": "Когда недостаточно места, показывать третью колонку содержащую", | ||||
|     "third_column_mode_none": "Не показывать третью колонку совсем", | ||||
|     "third_column_mode_notifications": "Колонку уведомлений", | ||||
|     "third_column_mode_postform": "Форму отправки сообщения и навигацию", | ||||
|     "columns": "Колонки", | ||||
|     "column_sizes": "Размеры колонок", | ||||
|     "column_sizes_sidebar": "Боковой", | ||||
|     "column_sizes_content": "Содержимого", | ||||
|     "column_sizes_notifs": "Уведомлений", | ||||
|     "no_mutes": "Нет игнорируемых", | ||||
|     "no_blocks": "Нет блокировок", | ||||
|     "notification_visibility_emoji_reactions": "Реакции", | ||||
|  |  | |||
|  | @ -1,5 +1,5 @@ | |||
| import Cookies from 'js-cookie' | ||||
| import { setPreset, applyTheme } from '../services/style_setter/style_setter.js' | ||||
| import { setPreset, applyTheme, applyConfig } from '../services/style_setter/style_setter.js' | ||||
| import messages from '../i18n/messages' | ||||
| import localeService from '../services/locale/locale.service.js' | ||||
| 
 | ||||
|  | @ -17,7 +17,8 @@ export const multiChoiceProperties = [ | |||
|   'subjectLineBehavior', | ||||
|   'conversationDisplay', // tree | linear
 | ||||
|   'conversationOtherRepliesButton', // below | inside
 | ||||
|   'mentionLinkDisplay' // short | full_for_remote | full
 | ||||
|   'mentionLinkDisplay', // short | full_for_remote | full
 | ||||
|   'userPopoverAvatarAction' // close | zoom | open
 | ||||
| ] | ||||
| 
 | ||||
| export const defaultState = { | ||||
|  | @ -82,11 +83,12 @@ export const defaultState = { | |||
|   useContainFit: true, | ||||
|   disableStickyHeaders: false, | ||||
|   showScrollbars: false, | ||||
|   userPopoverZoom: false, | ||||
|   userPopoverAvatarAction: 'close', | ||||
|   userPopoverOverlay: true, | ||||
|   sidebarColumnWidth: '25rem', | ||||
|   contentColumnWidth: '45rem', | ||||
|   notifsColumnWidth: '25rem', | ||||
|   navbarColumnStretch: false, | ||||
|   listsNavigation: false, | ||||
|   greentext: undefined, // instance default
 | ||||
|   useAtIcon: undefined, // instance default
 | ||||
|  | @ -165,12 +167,17 @@ const config = { | |||
|     setHighlight ({ commit, dispatch }, { user, color, type }) { | ||||
|       commit('setHighlight', { user, color, type }) | ||||
|     }, | ||||
|     setOption ({ commit, dispatch }, { name, value }) { | ||||
|     setOption ({ commit, dispatch, state }, { name, value }) { | ||||
|       commit('setOption', { name, value }) | ||||
|       switch (name) { | ||||
|         case 'theme': | ||||
|           setPreset(value) | ||||
|           break | ||||
|         case 'sidebarColumnWidth': | ||||
|         case 'contentColumnWidth': | ||||
|         case 'notifsColumnWidth': | ||||
|           applyConfig(state) | ||||
|           break | ||||
|         case 'customTheme': | ||||
|         case 'customThemeSource': | ||||
|           applyTheme(value) | ||||
|  |  | |||
|  | @ -16,9 +16,6 @@ export const mergeOrAdd = (arr, obj, item) => { | |||
|     // This is a new item, prepare it
 | ||||
|     arr.push(item) | ||||
|     obj[item.id] = item | ||||
|     if (item.screen_name && !item.screen_name.includes('@')) { | ||||
|       obj[item.screen_name.toLowerCase()] = item | ||||
|     } | ||||
|     return { item, new: true } | ||||
|   } | ||||
| } | ||||
|  | @ -162,7 +159,11 @@ export const mutations = { | |||
|       if (user.relationship) { | ||||
|         state.relationships[user.relationship.id] = user.relationship | ||||
|       } | ||||
|       mergeOrAdd(state.users, state.usersObject, user) | ||||
|       const res = mergeOrAdd(state.users, state.usersObject, user) | ||||
|       const item = res.item | ||||
|       if (res.new && item.screen_name && !item.screen_name.includes('@')) { | ||||
|         state.usersByNameObject[item.screen_name.toLowerCase()] = item | ||||
|       } | ||||
|     }) | ||||
|   }, | ||||
|   updateUserRelationship (state, relationships) { | ||||
|  | @ -239,12 +240,10 @@ export const mutations = { | |||
| 
 | ||||
| export const getters = { | ||||
|   findUser: state => query => { | ||||
|     const result = state.usersObject[query] | ||||
|     // In case it's a screen_name, we can try searching case-insensitive
 | ||||
|     if (!result && typeof query === 'string') { | ||||
|       return state.usersObject[query.toLowerCase()] | ||||
|     } | ||||
|     return result | ||||
|     return state.usersObject[query] | ||||
|   }, | ||||
|   findUserByName: state => query => { | ||||
|     return state.usersByNameObject[query.toLowerCase()] | ||||
|   }, | ||||
|   findUserByUrl: state => query => { | ||||
|     return state.users | ||||
|  | @ -263,6 +262,7 @@ export const defaultState = { | |||
|   currentUser: false, | ||||
|   users: [], | ||||
|   usersObject: {}, | ||||
|   usersByNameObject: {}, | ||||
|   signUpPending: false, | ||||
|   signUpErrors: [], | ||||
|   relationships: {} | ||||
|  | @ -285,6 +285,13 @@ const users = { | |||
|           return user | ||||
|         }) | ||||
|     }, | ||||
|     fetchUserByName (store, name) { | ||||
|       return store.rootState.api.backendInteractor.fetchUserByName({ name }) | ||||
|         .then((user) => { | ||||
|           store.commit('addNewUsers', [user]) | ||||
|           return user | ||||
|         }) | ||||
|     }, | ||||
|     fetchUserRelationship (store, id) { | ||||
|       if (store.state.currentUser) { | ||||
|         store.rootState.api.backendInteractor.fetchUserRelationship({ id }) | ||||
|  |  | |||
|  | @ -50,6 +50,7 @@ const MASTODON_USER_HOME_TIMELINE_URL = '/api/v1/timelines/home' | |||
| const MASTODON_STATUS_URL = id => `/api/v1/statuses/${id}` | ||||
| const MASTODON_STATUS_CONTEXT_URL = id => `/api/v1/statuses/${id}/context` | ||||
| const MASTODON_USER_URL = '/api/v1/accounts' | ||||
| const MASTODON_USER_LOOKUP_URL = '/api/v1/accounts/lookup' | ||||
| const MASTODON_USER_RELATIONSHIPS_URL = '/api/v1/accounts/relationships' | ||||
| const MASTODON_USER_TIMELINE_URL = id => `/api/v1/accounts/${id}/statuses` | ||||
| const MASTODON_LIST_URL = id => `/api/v1/lists/${id}` | ||||
|  | @ -318,6 +319,25 @@ const fetchUser = ({ id, credentials }) => { | |||
|     .then((data) => parseUser(data)) | ||||
| } | ||||
| 
 | ||||
| const fetchUserByName = ({ name, credentials }) => { | ||||
|   return promisedRequest({ | ||||
|     url: MASTODON_USER_LOOKUP_URL, | ||||
|     credentials, | ||||
|     params: { acct: name } | ||||
|   }) | ||||
|     .then(data => data.id) | ||||
|     .catch(error => { | ||||
|       if (error && error.statusCode === 404) { | ||||
|         // Either the backend does not support lookup endpoint,
 | ||||
|         // or there is no user with such name. Fallback and treat name as id.
 | ||||
|         return name | ||||
|       } else { | ||||
|         throw error | ||||
|       } | ||||
|     }) | ||||
|     .then(id => fetchUser({ id, credentials })) | ||||
| } | ||||
| 
 | ||||
| const fetchUserRelationship = ({ id, credentials }) => { | ||||
|   const url = `${MASTODON_USER_RELATIONSHIPS_URL}/?id=${id}` | ||||
|   return fetch(url, { headers: authHeaders(credentials) }) | ||||
|  | @ -1481,6 +1501,7 @@ const apiService = { | |||
|   blockUser, | ||||
|   unblockUser, | ||||
|   fetchUser, | ||||
|   fetchUserByName, | ||||
|   fetchUserRelationship, | ||||
|   favorite, | ||||
|   unfavorite, | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| import runtime from 'serviceworker-webpack-plugin/lib/runtime' | ||||
| import runtime from 'serviceworker-webpack5-plugin/lib/runtime' | ||||
| 
 | ||||
| function urlBase64ToUint8Array (base64String) { | ||||
|   const padding = '='.repeat((4 - base64String.length % 4) % 4) | ||||
|  |  | |||
|  | @ -1,6 +1,7 @@ | |||
| import { convert } from 'chromatism' | ||||
| import { rgb2hex, hex2rgb, rgba2css, getCssColor, relativeLuminance } from '../color_convert/color_convert.js' | ||||
| import { getColors, computeDynamicColor, getOpacitySlot } from '../theme_data/theme_data.service.js' | ||||
| import { defaultState } from '../../modules/config.js' | ||||
| 
 | ||||
| export const applyTheme = (input) => { | ||||
|   const { rules } = generatePreset(input) | ||||
|  | @ -20,6 +21,36 @@ export const applyTheme = (input) => { | |||
|   body.classList.remove('hidden') | ||||
| } | ||||
| 
 | ||||
| const configColumns = ({ sidebarColumnWidth, contentColumnWidth, notifsColumnWidth }) => | ||||
|   ({ sidebarColumnWidth, contentColumnWidth, notifsColumnWidth }) | ||||
| 
 | ||||
| const defaultConfigColumns = configColumns(defaultState) | ||||
| 
 | ||||
| export const applyConfig = (config) => { | ||||
|   const columns = configColumns(config) | ||||
| 
 | ||||
|   if (columns === defaultConfigColumns) { | ||||
|     return | ||||
|   } | ||||
| 
 | ||||
|   const head = document.head | ||||
|   const body = document.body | ||||
|   body.classList.add('hidden') | ||||
| 
 | ||||
|   const rules = Object | ||||
|     .entries(columns) | ||||
|     .filter(([k, v]) => v) | ||||
|     .map(([k, v]) => `--${k}: ${v}`).join(';') | ||||
| 
 | ||||
|   const styleEl = document.createElement('style') | ||||
|   head.appendChild(styleEl) | ||||
|   const styleSheet = styleEl.sheet | ||||
| 
 | ||||
|   styleSheet.toString() | ||||
|   styleSheet.insertRule(`:root { ${rules} }`, 'index-max') | ||||
|   body.classList.remove('hidden') | ||||
| } | ||||
| 
 | ||||
| export const getCssShadow = (input, usesDropShadow) => { | ||||
|   if (input.length === 0) { | ||||
|     return 'none' | ||||
|  |  | |||
|  | @ -16,7 +16,7 @@ const webpackConfig = merge(baseConfig, { | |||
|   module: { | ||||
|     rules: utils.styleLoaders() | ||||
|   }, | ||||
|   devtool: '#inline-source-map', | ||||
|   devtool: 'inline-source-map', | ||||
|   // vue: {
 | ||||
|   //   loaders: {
 | ||||
|   //     js: 'isparta'
 | ||||
|  |  | |||
|  | @ -15,6 +15,7 @@ const actions = { | |||
| 
 | ||||
| const testGetters = { | ||||
|   findUser: state => getters.findUser(state.users), | ||||
|   findUserByName: state => getters.findUserByName(state.users), | ||||
|   relationship: state => getters.relationship(state.users), | ||||
|   mergedConfig: state => ({ | ||||
|     colors: '', | ||||
|  | @ -95,6 +96,7 @@ const externalProfileStore = createStore({ | |||
|         credentials: '' | ||||
|       }, | ||||
|       usersObject: { 100: extUser }, | ||||
|       usersByNameObject: {}, | ||||
|       users: [extUser], | ||||
|       relationships: {} | ||||
|     } | ||||
|  | @ -163,7 +165,8 @@ const localProfileStore = createStore({ | |||
|       currentUser: { | ||||
|         credentials: '' | ||||
|       }, | ||||
|       usersObject: { 100: localUser, testuser: localUser }, | ||||
|       usersObject: { 100: localUser }, | ||||
|       usersByNameObject: { testuser: localUser }, | ||||
|       users: [localUser], | ||||
|       relationships: {} | ||||
|     } | ||||
|  |  | |||
|  | @ -57,24 +57,27 @@ describe('The users module', () => { | |||
|   }) | ||||
| 
 | ||||
|   describe('findUser', () => { | ||||
|     it('returns user with matching screen_name', () => { | ||||
|     it('does not return user with matching screen_name', () => { | ||||
|       const user = { screen_name: 'Guy', id: '1' } | ||||
|       const state = { | ||||
|         usersObject: { | ||||
|           1: user, | ||||
|           1: user | ||||
|         }, | ||||
|         usersByNameObject: { | ||||
|           guy: user | ||||
|         } | ||||
|       } | ||||
|       const name = 'Guy' | ||||
|       const expected = { screen_name: 'Guy', id: '1' } | ||||
|       expect(getters.findUser(state)(name)).to.eql(expected) | ||||
|       expect(getters.findUser(state)(name)).to.eql(undefined) | ||||
|     }) | ||||
| 
 | ||||
|     it('returns user with matching id', () => { | ||||
|       const user = { screen_name: 'Guy', id: '1' } | ||||
|       const state = { | ||||
|         usersObject: { | ||||
|           1: user, | ||||
|           1: user | ||||
|         }, | ||||
|         usersByNameObject: { | ||||
|           guy: user | ||||
|         } | ||||
|       } | ||||
|  | @ -83,4 +86,35 @@ describe('The users module', () => { | |||
|       expect(getters.findUser(state)(id)).to.eql(expected) | ||||
|     }) | ||||
|   }) | ||||
| 
 | ||||
|   describe('findUserByName', () => { | ||||
|     it('returns user with matching screen_name', () => { | ||||
|       const user = { screen_name: 'Guy', id: '1' } | ||||
|       const state = { | ||||
|         usersObject: { | ||||
|           1: user | ||||
|         }, | ||||
|         usersByNameObject: { | ||||
|           guy: user | ||||
|         } | ||||
|       } | ||||
|       const name = 'Guy' | ||||
|       const expected = { screen_name: 'Guy', id: '1' } | ||||
|       expect(getters.findUserByName(state)(name)).to.eql(expected) | ||||
|     }) | ||||
| 
 | ||||
|     it('does not return user with matching id', () => { | ||||
|       const user = { screen_name: 'Guy', id: '1' } | ||||
|       const state = { | ||||
|         usersObject: { | ||||
|           1: user | ||||
|         }, | ||||
|         usersByNameObject: { | ||||
|           guy: user | ||||
|         } | ||||
|       } | ||||
|       const id = '1' | ||||
|       expect(getters.findUserByName(state)(id)).to.eql(undefined) | ||||
|     }) | ||||
|   }) | ||||
| }) | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue
	
	 Henry Jameson
						Henry Jameson