diff --git a/.eslintrc.js b/.eslintrc.js index 800f9a4f..b526df67 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,14 +1,17 @@ module.exports = { root: true, - parser: 'babel-eslint', parserOptions: { + parser: 'babel-eslint', sourceType: 'module' }, // https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style - extends: 'standard', + extends: [ + 'standard', + 'plugin:vue/recommended' + ], // required to lint *.vue files plugins: [ - 'html' + 'vue' ], // add your custom rules here rules: { @@ -17,6 +20,28 @@ module.exports = { // allow async-await 'generator-star-spacing': 0, // allow debugger during development - 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0 + 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0, + // Webpack 4 update commit, most of these probably should be fixed and removed in a separate MR + // A lot of errors come from .vue files that are now properly linted + 'vue/valid-v-if': 1, + 'vue/use-v-on-exact': 1, + 'vue/no-parsing-error': 1, + 'vue/require-v-for-key': 1, + 'vue/valid-v-for': 1, + 'vue/require-prop-types': 1, + 'vue/no-use-v-if-with-v-for': 1, + 'indent': 1, + 'import/first': 1, // ???? + 'import-first': 1, + 'object-curly-spacing': 1, + 'prefer-promise-reject-errors': 1, + 'eol-last': 1, + 'no-return-await': 1, + 'no-multi-spaces': 1, + 'no-trailing-spaces': 1, + 'no-unused-expressions': 1, + 'no-mixed-operators': 1, + 'camelcase': 1, + 'no-multiple-empty-lines': 1 } } diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6c83a123..67824ac3 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:7 +image: node:8 stages: - lint @@ -16,7 +16,12 @@ lint: test: stage: test + variables: + APT_CACHE_DIR: apt-cache script: + - mkdir -pv $APT_CACHE_DIR && apt-get -qq update + - apt install firefox-esr -y --no-install-recommends + - firefox --version - yarn - npm run unit diff --git a/build/utils.js b/build/utils.js index 5b90db14..b45ffc16 100644 --- a/build/utils.js +++ b/build/utils.js @@ -1,61 +1,62 @@ var path = require('path') var config = require('../config') -var ExtractTextPlugin = require('extract-text-webpack-plugin') +var sass = require('sass') +var MiniCssExtractPlugin = require('mini-css-extract-plugin') exports.assetsPath = function (_path) { var assetsSubDirectory = process.env.NODE_ENV === 'production' - ? config.build.assetsSubDirectory - : config.dev.assetsSubDirectory + ? config.build.assetsSubDirectory + : config.dev.assetsSubDirectory return path.posix.join(assetsSubDirectory, _path) } exports.cssLoaders = function (options) { options = options || {} - // generate loader string to be used with extract text plugin - function generateLoaders (loaders) { - var sourceLoader = loaders.map(function (loader) { - var extraParamChar - if (/\?/.test(loader)) { - loader = loader.replace(/\?/, '-loader?') - extraParamChar = '&' - } else { - loader = loader + '-loader' - extraParamChar = '?' - } - return loader + (options.sourceMap ? extraParamChar + 'sourceMap' : '') - }).join('!') + function generateLoaders (loaders) { // Extract CSS when that option is specified // (which is the case during production build) if (options.extract) { - return ExtractTextPlugin.extract('vue-style-loader', sourceLoader) + return [MiniCssExtractPlugin.loader].concat(loaders) } else { - return ['vue-style-loader', sourceLoader].join('!') + return ['vue-style-loader'].concat(loaders) } } // http://vuejs.github.io/vue-loader/configurations/extract-css.html - return { - css: generateLoaders(['css']), - postcss: generateLoaders(['css']), - less: generateLoaders(['css', 'less']), - sass: generateLoaders(['css', 'sass?indentedSyntax']), - scss: generateLoaders(['css', 'sass']), - stylus: generateLoaders(['css', 'stylus']), - styl: generateLoaders(['css', 'stylus']) - } + return [ + { + test: /\.(post)?css$/, + use: generateLoaders(['css-loader']), + }, + { + test: /\.less$/, + use: generateLoaders(['css-loader', 'less-loader']), + }, + { + test: /\.sass$/, + use: generateLoaders([ + 'css-loader', + { + loader: 'sass-loader', + options: { + indentedSyntax: true + } + } + ]) + }, + { + test: /\.scss$/, + use: generateLoaders(['css-loader', 'sass-loader']) + }, + { + test: /\.styl(us)?$/, + use: generateLoaders(['css-loader', 'stylus-loader']), + }, + ] } // Generate loaders for standalone style files (outside of .vue) exports.styleLoaders = function (options) { - var output = [] - var loaders = exports.cssLoaders(options) - for (var extension in loaders) { - var loader = loaders[extension] - output.push({ - test: new RegExp('\\.' + extension + '$'), - loader: loader - }) - } - return output + return exports.cssLoaders(options) } diff --git a/build/webpack.base.conf.js b/build/webpack.base.conf.js index e07bb7a2..f8968966 100644 --- a/build/webpack.base.conf.js +++ b/build/webpack.base.conf.js @@ -20,9 +20,16 @@ module.exports = { publicPath: process.env.NODE_ENV === 'production' ? config.build.assetsPublicPath : config.dev.assetsPublicPath, filename: '[name].js' }, + optimization: { + splitChunks: { + chunks: 'all' + } + }, resolve: { - extensions: ['', '.js', '.vue'], - fallback: [path.join(__dirname, '../node_modules')], + extensions: ['.js', '.vue'], + modules: [ + path.join(__dirname, '../node_modules') + ], alias: { 'vue$': 'vue/dist/vue.runtime.common', 'src': path.resolve(__dirname, '../src'), @@ -30,67 +37,53 @@ module.exports = { 'components': path.resolve(__dirname, '../src/components') } }, - resolveLoader: { - fallback: [path.join(__dirname, '../node_modules')] - }, module: { noParse: /node_modules\/localforage\/dist\/localforage.js/, - preLoaders: [ + rules: [ { - test: /\.vue$/, - loader: 'eslint', + enforce: 'pre', + test: /\.(js|vue)$/, include: projectRoot, - exclude: /node_modules/ + exclude: /node_modules/, + use: { + loader: 'eslint-loader', + options: { + formatter: require('eslint-friendly-formatter'), + sourceMap: config.build.productionSourceMap, + extract: true + } + } }, - { - test: /\.js$/, - loader: 'eslint', - include: projectRoot, - exclude: /node_modules/ - } - ], - loaders: [ { test: /\.vue$/, - loader: 'vue' + use: 'vue-loader' }, { test: /\.jsx?$/, - loader: 'babel', include: projectRoot, - exclude: /node_modules\/(?!tributejs)/ - }, - { - test: /\.json$/, - loader: 'json' + exclude: /node_modules\/(?!tributejs)/, + use: 'babel-loader' }, { test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, - loader: 'url', - query: { - limit: 10000, - name: utils.assetsPath('img/[name].[hash:7].[ext]') + use: { + loader: 'url-loader', + options: { + limit: 10000, + name: utils.assetsPath('img/[name].[hash:7].[ext]') + } } }, { test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, - loader: 'url', - query: { - limit: 10000, - name: utils.assetsPath('fonts/[name].[hash:7].[ext]') + use: { + loader: 'url-loader', + options: { + limit: 10000, + name: utils.assetsPath('fonts/[name].[hash:7].[ext]') + } } - } - ] - }, - eslint: { - formatter: require('eslint-friendly-formatter') - }, - vue: { - loaders: utils.cssLoaders({ sourceMap: useCssSourceMap }), - postcss: [ - require('autoprefixer')({ - browsers: ['last 2 versions'] - }) + }, ] }, plugins: [ diff --git a/build/webpack.dev.conf.js b/build/webpack.dev.conf.js index 9f34619c..159572ba 100644 --- a/build/webpack.dev.conf.js +++ b/build/webpack.dev.conf.js @@ -12,8 +12,9 @@ Object.keys(baseWebpackConfig.entry).forEach(function (name) { module.exports = merge(baseWebpackConfig, { module: { - loaders: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap }) + rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap }) }, + mode: 'development', // eval-source-map is faster for development devtool: '#eval-source-map', plugins: [ @@ -23,9 +24,7 @@ module.exports = merge(baseWebpackConfig, { 'DEV_OVERRIDES': JSON.stringify(config.dev.settings) }), // https://github.com/glenjamin/webpack-hot-middleware#installation--usage - new webpack.optimize.OccurenceOrderPlugin(), new webpack.HotModuleReplacementPlugin(), - new webpack.NoErrorsPlugin(), // https://github.com/ampedandwired/html-webpack-plugin new HtmlWebpackPlugin({ filename: 'index.html', diff --git a/build/webpack.prod.conf.js b/build/webpack.prod.conf.js index 9699f221..ed11ebad 100644 --- a/build/webpack.prod.conf.js +++ b/build/webpack.prod.conf.js @@ -4,7 +4,7 @@ var utils = require('./utils') var webpack = require('webpack') var merge = require('webpack-merge') var baseWebpackConfig = require('./webpack.base.conf') -var ExtractTextPlugin = require('extract-text-webpack-plugin') +var MiniCssExtractPlugin = require('mini-css-extract-plugin') var HtmlWebpackPlugin = require('html-webpack-plugin') var env = process.env.NODE_ENV === 'testing' ? require('../config/test.env') @@ -13,23 +13,23 @@ var env = process.env.NODE_ENV === 'testing' let commitHash = require('child_process') .execSync('git rev-parse --short HEAD') .toString(); -console.log(commitHash) var webpackConfig = merge(baseWebpackConfig, { + mode: 'production', module: { - loaders: utils.styleLoaders({ sourceMap: config.build.productionSourceMap, extract: true }) + rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, extract: true }) }, devtool: config.build.productionSourceMap ? '#source-map' : false, + optimization: { + minimize: true, + splitChunks: { + chunks: 'all' + } + }, output: { path: config.build.assetsRoot, filename: utils.assetsPath('js/[name].[chunkhash].js'), - chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') - }, - vue: { - loaders: utils.cssLoaders({ - sourceMap: config.build.productionSourceMap, - extract: true - }) + chunkFilename: utils.assetsPath('js/[name].[chunkhash].js') }, plugins: [ // http://vuejs.github.io/vue-loader/workflow/production.html @@ -38,14 +38,10 @@ var webpackConfig = merge(baseWebpackConfig, { 'COMMIT_HASH': JSON.stringify(commitHash), 'DEV_OVERRIDES': JSON.stringify(undefined) }), - new webpack.optimize.UglifyJsPlugin({ - compress: { - warnings: false - } - }), - new webpack.optimize.OccurenceOrderPlugin(), // extract css into its own file - new ExtractTextPlugin(utils.assetsPath('css/[name].[contenthash].css')), + new MiniCssExtractPlugin({ + filename: utils.assetsPath('css/[name].[contenthash].css') + }), // generate dist index.html with correct asset hash for caching. // you can customize output by editing /index.html // see https://github.com/ampedandwired/html-webpack-plugin @@ -67,25 +63,11 @@ var webpackConfig = merge(baseWebpackConfig, { chunksSortMode: 'dependency' }), // split vendor js into its own file - new webpack.optimize.CommonsChunkPlugin({ - name: 'vendor', - minChunks: function (module, count) { - // any required modules inside node_modules are extracted to vendor - return ( - module.resource && - /\.js$/.test(module.resource) && - module.resource.indexOf( - path.join(__dirname, '../node_modules') - ) === 0 - ) - } - }), // extract webpack runtime and module manifest to its own file in order to // prevent vendor hash from being updated whenever app bundle is updated - new webpack.optimize.CommonsChunkPlugin({ - name: 'manifest', - chunks: ['vendor'] - }) + // new webpack.optimize.SplitChunksPlugin({ + // name: ['app', 'vendor'] + // }), ] }) diff --git a/index.html b/index.html index d8defc2e..63161f3c 100644 --- a/index.html +++ b/index.html @@ -2,7 +2,7 @@ - + Pleroma diff --git a/package.json b/package.json index c80e0f63..30a317b2 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ "unit:watch": "karma start test/unit/karma.conf.js --single-run=false", "e2e": "node test/e2e/runner.js", "test": "npm run unit && npm run e2e", - "lint": "eslint --ext .js,.vue src test/unit/specs test/e2e/specs" + "lint": "eslint --ext .js,.vue src test/unit/specs test/e2e/specs", + "lint-fix": "eslint --fix --ext .js,.vue src test/unit/specs test/e2e/specs" }, "dependencies": { "babel-plugin-add-module-exports": "^0.2.1", @@ -21,15 +22,13 @@ "diff": "^3.0.1", "karma-mocha-reporter": "^2.2.1", "localforage": "^1.5.0", - "node-sass": "^3.10.1", "object-path": "^0.11.3", "phoenix": "^1.3.0", "popper.js": "^1.14.7", "sanitize-html": "^1.13.0", - "sass-loader": "^4.0.2", + "v-click-outside": "^2.1.1", "vue": "^2.5.13", "vue-chat-scroll": "^1.2.1", - "vue-compose": "^0.7.1", "vue-i18n": "^7.3.2", "vue-popperjs": "^2.0.3", "vue-router": "^3.0.1", @@ -46,7 +45,7 @@ "babel-core": "^6.0.0", "babel-eslint": "^7.0.0", "babel-helper-vue-jsx-merge-props": "^2.0.3", - "babel-loader": "^6.0.0", + "babel-loader": "^7.0.0", "babel-plugin-syntax-jsx": "^6.18.0", "babel-plugin-transform-runtime": "^6.0.0", "babel-plugin-transform-vue-jsx": "3", @@ -59,52 +58,55 @@ "chromedriver": "^2.21.2", "connect-history-api-fallback": "^1.1.0", "cross-spawn": "^4.0.2", - "css-loader": "^0.25.0", - "eslint": "^3.7.1", - "eslint-config-standard": "^6.1.0", + "css-loader": "^0.28.0", + "eslint": "^5.16.0", + "eslint-config-standard": "^12.0.0", "eslint-friendly-formatter": "^2.0.5", - "eslint-loader": "^1.5.0", - "eslint-plugin-html": "^1.5.5", - "eslint-plugin-promise": "^2.0.1", - "eslint-plugin-standard": "^2.0.1", + "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", - "extract-text-webpack-plugin": "^1.0.1", - "file-loader": "^0.9.0", + "file-loader": "^3.0.1", "function-bind": "^1.0.2", - "html-webpack-plugin": "^2.8.1", + "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": "^1.3.0", + "karma": "^3.0.0", "karma-coverage": "^1.1.1", "karma-mocha": "^1.2.0", - "karma-phantomjs-launcher": "^1.0.0", - "karma-sinon-chai": "^1.2.0", + "karma-firefox-launcher": "^1.1.0", + "karma-sinon-chai": "^2.0.2", "karma-sourcemap-loader": "^0.3.7", "karma-spec-reporter": "0.0.26", - "karma-webpack": "^1.7.0", + "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", - "phantomjs-prebuilt": "^2.1.3", "raw-loader": "^0.5.1", + "sass": "^1.17.3", + "sass-loader": "git://github.com/webpack-contrib/sass-loader", "selenium-server": "2.53.1", "semver": "^5.3.0", - "serviceworker-webpack-plugin": "0.2.3", + "serviceworker-webpack-plugin": "^1.0.0", "shelljs": "^0.7.4", - "sinon": "^1.17.3", + "sinon": "^2.1.0", "sinon-chai": "^2.8.0", - "url-loader": "^0.5.7", - "vue-loader": "^11.1.0", - "vue-style-loader": "^2.0.0", - "webpack": "^1.13.2", - "webpack-dev-middleware": "^1.8.3", + "url-loader": "^1.1.2", + "vue-loader": "^14.0.0", + "vue-style-loader": "^4.0.0", + "webpack": "^4.0.0", + "webpack-dev-middleware": "^3.6.0", "webpack-hot-middleware": "^2.12.2", "webpack-merge": "^0.14.1" }, diff --git a/src/App.js b/src/App.js index 46145b16..e72c73e3 100644 --- a/src/App.js +++ b/src/App.js @@ -10,6 +10,7 @@ import MediaModal from './components/media_modal/media_modal.vue' import SideDrawer from './components/side_drawer/side_drawer.vue' import MobilePostStatusModal from './components/mobile_post_status_modal/mobile_post_status_modal.vue' import MobileNav from './components/mobile_nav/mobile_nav.vue' +import UserReportingModal from './components/user_reporting_modal/user_reporting_modal.vue' import { windowWidth } from './services/window_utils/window_utils' export default { @@ -26,7 +27,8 @@ export default { MediaModal, SideDrawer, MobilePostStatusModal, - MobileNav + MobileNav, + UserReportingModal }, data: () => ({ mobileActivePanel: 'timeline', diff --git a/src/App.scss b/src/App.scss index b1c65ade..52a786ad 100644 --- a/src/App.scss +++ b/src/App.scss @@ -379,6 +379,7 @@ main-router { .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; @@ -624,21 +625,6 @@ nav { text-align: right; } -.visibility-tray { - font-size: 1.2em; - padding: 3px; - cursor: pointer; - - .selected { - color: $fallback--lightText; - color: var(--lightText, $fallback--lightText); - } - - div { - padding-top: 5px; - } -} - .visibility-notice { padding: .5em; border: 1px solid $fallback--faint; @@ -647,6 +633,19 @@ nav { border-radius: var(--inputRadius, $fallback--inputRadius); } +.notice-dismissible { + padding-right: 4rem; + position: relative; + + .dismiss { + position: absolute; + top: 0; + right: 0; + padding: .5em; + color: inherit; + } +} + @keyframes modal-background-fadein { from { background-color: rgba(0, 0, 0, 0); @@ -726,6 +725,70 @@ nav { } } +.setting-item { + border-bottom: 2px solid var(--fg, $fallback--fg); + margin: 1em 1em 1.4em; + padding-bottom: 1.4em; + + > div { + margin-bottom: .5em; + &:last-child { + margin-bottom: 0; + } + } + + &:last-child { + border-bottom: none; + padding-bottom: 0; + margin-bottom: 1em; + } + + select { + min-width: 10em; + } + + + textarea { + width: 100%; + max-width: 100%; + height: 100px; + } + + .unavailable, + .unavailable i { + color: var(--cRed, $fallback--cRed); + color: $fallback--cRed; + } + + .btn { + min-height: 28px; + min-width: 10em; + padding: 0 2em; + } + + .number-input { + max-width: 6em; + } +} +.select-multiple { + display: flex; + .option-list { + margin: 0; + padding-left: .5em; + } +} +.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; @@ -793,4 +856,4 @@ nav { background-color: var(--lightBg, $fallback--fg); } } -} \ No newline at end of file +} diff --git a/src/App.vue b/src/App.vue index 3b8623ad..21abd694 100644 --- a/src/App.vue +++ b/src/App.vue @@ -18,17 +18,19 @@ -
-
- +
diff --git a/src/components/image_cropper/image_cropper.js b/src/components/image_cropper/image_cropper.js index 5ba8f04e..01361e25 100644 --- a/src/components/image_cropper/image_cropper.js +++ b/src/components/image_cropper/image_cropper.js @@ -70,22 +70,10 @@ const ImageCropper = { this.dataUrl = undefined this.$emit('close') }, - submit () { + submit (cropping = true) { this.submitting = true this.avatarUploadError = null - this.submitHandler(this.cropper, this.file) - .then(() => this.destroy()) - .catch((err) => { - this.submitError = err - }) - .finally(() => { - this.submitting = false - }) - }, - submitWithoutCropping () { - this.submitting = true - this.avatarUploadError = null - this.submitHandler(false, this.dataUrl) + this.submitHandler(cropping && this.cropper, this.file) .then(() => this.destroy()) .catch((err) => { this.submitError = err diff --git a/src/components/image_cropper/image_cropper.vue b/src/components/image_cropper/image_cropper.vue index 129e6f46..d2b86e9e 100644 --- a/src/components/image_cropper/image_cropper.vue +++ b/src/components/image_cropper/image_cropper.vue @@ -5,9 +5,9 @@
- + - +
diff --git a/src/components/importer/importer.js b/src/components/importer/importer.js new file mode 100644 index 00000000..c5f9e4d2 --- /dev/null +++ b/src/components/importer/importer.js @@ -0,0 +1,53 @@ +const Importer = { + props: { + submitHandler: { + type: Function, + required: true + }, + submitButtonLabel: { + type: String, + default () { + return this.$t('importer.submit') + } + }, + successMessage: { + type: String, + default () { + return this.$t('importer.success') + } + }, + errorMessage: { + type: String, + default () { + return this.$t('importer.error') + } + } + }, + data () { + return { + file: null, + error: false, + success: false, + submitting: false + } + }, + methods: { + change () { + this.file = this.$refs.input.files[0] + }, + submit () { + this.dismiss() + this.submitting = true + this.submitHandler(this.file) + .then(() => { this.success = true }) + .catch(() => { this.error = true }) + .finally(() => { this.submitting = false }) + }, + dismiss () { + this.success = false + this.error = false + } + } +} + +export default Importer diff --git a/src/components/importer/importer.vue b/src/components/importer/importer.vue new file mode 100644 index 00000000..0c5aa93d --- /dev/null +++ b/src/components/importer/importer.vue @@ -0,0 +1,28 @@ + + + + + diff --git a/src/components/interactions/interactions.js b/src/components/interactions/interactions.js new file mode 100644 index 00000000..d4e3cc17 --- /dev/null +++ b/src/components/interactions/interactions.js @@ -0,0 +1,25 @@ +import Notifications from '../notifications/notifications.vue' + +const tabModeDict = { + mentions: ['mention'], + 'likes+repeats': ['repeat', 'like'], + follows: ['follow'] +} + +const Interactions = { + data () { + return { + filterMode: tabModeDict['mentions'] + } + }, + methods: { + onModeSwitch (index, dataset) { + this.filterMode = tabModeDict[dataset.filter] + } + }, + components: { + Notifications + } +} + +export default Interactions diff --git a/src/components/interactions/interactions.vue b/src/components/interactions/interactions.vue new file mode 100644 index 00000000..38b2670d --- /dev/null +++ b/src/components/interactions/interactions.vue @@ -0,0 +1,25 @@ + + + diff --git a/src/components/interface_language_switcher/interface_language_switcher.vue b/src/components/interface_language_switcher/interface_language_switcher.vue index 3f58af2c..9f7877c6 100644 --- a/src/components/interface_language_switcher/interface_language_switcher.vue +++ b/src/components/interface_language_switcher/interface_language_switcher.vue @@ -26,7 +26,7 @@ }, languageNames () { - return _.map(this.languageCodes, ISO6391.getName) + return _.map(this.languageCodes, this.getLanguageName) }, language: { @@ -36,6 +36,17 @@ this.$i18n.locale = val } } + }, + + methods: { + getLanguageName (code) { + const specialLanguageNames = { + 'ja': 'Japanese (やさしいにほんご)', + 'ja_pedantic': 'Japanese (日本語)', + 'zh': 'Chinese (简体中文)' + } + return specialLanguageNames[code] || ISO6391.getName(code) + } } } diff --git a/src/components/list/list.vue b/src/components/list/list.vue new file mode 100644 index 00000000..7136915b --- /dev/null +++ b/src/components/list/list.vue @@ -0,0 +1,42 @@ + + + + + diff --git a/src/components/login_form/login_form.vue b/src/components/login_form/login_form.vue index 27a8e48a..c6be2e00 100644 --- a/src/components/login_form/login_form.vue +++ b/src/components/login_form/login_form.vue @@ -50,6 +50,10 @@ @import '../../_variables.scss'; .login-form { + display: flex; + flex-direction: column; + padding: 0.6em; + .btn { min-height: 28px; width: 10em; @@ -66,9 +70,30 @@ align-items: center; justify-content: space-between; } -} -.login { + .form-group { + display: flex; + flex-direction: column; + padding: 0.3em 0.5em 0.6em; + line-height:24px; + } + + .form-bottom { + display: flex; + padding: 0.5em; + height: 32px; + + button { + width: 10em; + } + + p { + margin: 0.35em; + padding: 0.35em; + display: flex; + } + } + .error { text-align: center; diff --git a/src/components/media_modal/media_modal.vue b/src/components/media_modal/media_modal.vue index 7f666603..a4c12d74 100644 --- a/src/components/media_modal/media_modal.vue +++ b/src/components/media_modal/media_modal.vue @@ -33,6 +33,8 @@ @import '../../_variables.scss'; .media-modal-view { + z-index: 1001; + &:hover { .modal-view-button-arrow { opacity: 0.75; diff --git a/src/components/mentions/mentions.vue b/src/components/mentions/mentions.vue index bba06da6..6b4e96e0 100644 --- a/src/components/mentions/mentions.vue +++ b/src/components/mentions/mentions.vue @@ -1,5 +1,5 @@ diff --git a/src/components/mobile_nav/mobile_nav.js b/src/components/mobile_nav/mobile_nav.js index bc63d2ba..9b341a3b 100644 --- a/src/components/mobile_nav/mobile_nav.js +++ b/src/components/mobile_nav/mobile_nav.js @@ -63,6 +63,11 @@ const MobileNav = { }, markNotificationsAsSeen () { this.$refs.notifications.markAsSeen() + }, + onScroll ({ target: { scrollTop, clientHeight, scrollHeight } }) { + if (this.$store.state.config.autoLoad && scrollTop + clientHeight >= scrollHeight) { + this.$refs.notifications.fetchOlderNotifications() + } } }, watch: { diff --git a/src/components/mobile_nav/mobile_nav.vue b/src/components/mobile_nav/mobile_nav.vue index 5fa41638..dcac440a 100644 --- a/src/components/mobile_nav/mobile_nav.vue +++ b/src/components/mobile_nav/mobile_nav.vue @@ -1,25 +1,26 @@ @@ -79,6 +81,8 @@ transition-property: transform; transition-duration: 0.25s; transform: translateX(0); + z-index: 1001; + -webkit-overflow-scrolling: touch; &.closed { transform: translateX(100%); diff --git a/src/components/mobile_post_status_modal/mobile_post_status_modal.js b/src/components/mobile_post_status_modal/mobile_post_status_modal.js index 2f24dd08..91b730e7 100644 --- a/src/components/mobile_post_status_modal/mobile_post_status_modal.js +++ b/src/components/mobile_post_status_modal/mobile_post_status_modal.js @@ -1,5 +1,5 @@ import PostStatusForm from '../post_status_form/post_status_form.vue' -import { throttle } from 'lodash' +import { debounce } from 'lodash' const MobilePostStatusModal = { components: { @@ -16,11 +16,15 @@ const MobilePostStatusModal = { } }, created () { - window.addEventListener('scroll', this.handleScroll) + if (this.autohideFloatingPostButton) { + this.activateFloatingPostButtonAutohide() + } window.addEventListener('resize', this.handleOSK) }, destroyed () { - window.removeEventListener('scroll', this.handleScroll) + if (this.autohideFloatingPostButton) { + this.deactivateFloatingPostButtonAutohide() + } window.removeEventListener('resize', this.handleOSK) }, computed: { @@ -28,10 +32,30 @@ const MobilePostStatusModal = { return this.$store.state.users.currentUser }, isHidden () { - return this.hidden || this.inputActive + return this.autohideFloatingPostButton && (this.hidden || this.inputActive) + }, + autohideFloatingPostButton () { + return !!this.$store.state.config.autohideFloatingPostButton + } + }, + watch: { + autohideFloatingPostButton: function (isEnabled) { + if (isEnabled) { + this.activateFloatingPostButtonAutohide() + } else { + this.deactivateFloatingPostButtonAutohide() + } } }, methods: { + activateFloatingPostButtonAutohide () { + window.addEventListener('scroll', this.handleScrollStart) + window.addEventListener('scroll', this.handleScrollEnd) + }, + deactivateFloatingPostButtonAutohide () { + window.removeEventListener('scroll', this.handleScrollStart) + window.removeEventListener('scroll', this.handleScrollEnd) + }, openPostForm () { this.postFormOpen = true this.hidden = true @@ -65,26 +89,19 @@ const MobilePostStatusModal = { this.inputActive = false } }, - handleScroll: throttle(function () { - const scrollAmount = window.scrollY - this.oldScrollPos - const scrollingDown = scrollAmount > 0 - - if (scrollingDown !== this.scrollingDown) { - this.amountScrolled = 0 - this.scrollingDown = scrollingDown - if (!scrollingDown) { - this.hidden = false - } - } else if (scrollingDown) { - this.amountScrolled += scrollAmount - if (this.amountScrolled > 100 && !this.hidden) { - this.hidden = true - } + handleScrollStart: debounce(function () { + if (window.scrollY > this.oldScrollPos) { + this.hidden = true + } else { + this.hidden = false } - this.oldScrollPos = window.scrollY - this.scrollingDown = scrollingDown - }, 100) + }, 100, {leading: true, trailing: false}), + + handleScrollEnd: debounce(function () { + this.hidden = false + this.oldScrollPos = window.scrollY + }, 100, {leading: false, trailing: true}) } } diff --git a/src/components/mobile_post_status_modal/mobile_post_status_modal.vue b/src/components/mobile_post_status_modal/mobile_post_status_modal.vue index 0a451c28..c762705b 100644 --- a/src/components/mobile_post_status_modal/mobile_post_status_modal.vue +++ b/src/components/mobile_post_status_modal/mobile_post_status_modal.vue @@ -7,7 +7,7 @@ >
{{$t('post_status.new_status')}}
- +