diff --git a/.eslintrc.js b/.eslintrc.js
index 800f9a4f..7d090208 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,27 @@ 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,
+ '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/BREAKING_CHANGES.md b/BREAKING_CHANGES.md
new file mode 100644
index 00000000..924c38da
--- /dev/null
+++ b/BREAKING_CHANGES.md
@@ -0,0 +1,10 @@
+# v1.0
+## Removed features/radically changed behavior
+### minimalScopesMode
+As of !633, `scopeOptions` is no longer available and instead is changed for `minimalScopesMode` (default: `false`)
+
+Reasoning is that scopeOptions option originally existed mostly as a backwards-compatibility with GNU Social which only had `public` scope available and using scope selector would''t work. Since at some point we dropped GNU Social support, this option was mostly a nuisance (being default `false`'), however some people think scopes are an annoyance to a certain degree and want as less of that feature as possible.
+
+Solution - to only show minimal set among: *Direct*, *User default* and *Scope of post replying to*. This also makes it impossible to reply to a DM with a non-DM post from UI.
+
+*This setting is admin-default, user-configurable. Admin can choose different default for their instance but user can override it.*
diff --git a/README.md b/README.md
index 80938c45..889f0837 100644
--- a/README.md
+++ b/README.md
@@ -41,7 +41,7 @@ FE Build process also leaves current commit hash in global variable `___pleromaf
# Configuration
-Edit config.json for configuration. scopeOptionsEnabled gives you input fields for CWs and the scope settings.
+Edit config.json for configuration.
## Options
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 03228133..0e7572fa 100644
--- a/package.json
+++ b/package.json
@@ -11,9 +11,11 @@
"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": {
+ "@chenfengyuan/vue-qrcode": "^1.0.0",
"babel-plugin-add-module-exports": "^0.2.1",
"babel-plugin-lodash": "^3.2.11",
"chromatism": "^3.0.0",
@@ -21,15 +23,16 @@
"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",
+ "portal-vue": "^2.1.4",
"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",
"vue-template-compiler": "^2.3.4",
"vue-timeago": "^3.1.2",
@@ -44,7 +47,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",
@@ -57,52 +60,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 5c27a3df..e72c73e3 100644
--- a/src/App.js
+++ b/src/App.js
@@ -9,7 +9,9 @@ import ChatPanel from './components/chat_panel/chat_panel.vue'
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 { unseenNotificationsFromStore } from './services/notification_utils/notification_utils'
+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 {
name: 'app',
@@ -24,7 +26,9 @@ export default {
ChatPanel,
MediaModal,
SideDrawer,
- MobilePostStatusModal
+ MobilePostStatusModal,
+ MobileNav,
+ UserReportingModal
},
data: () => ({
mobileActivePanel: 'timeline',
@@ -40,6 +44,10 @@ export default {
created () {
// Load the locale from the storage
this.$i18n.locale = this.$store.state.config.interfaceLanguage
+ window.addEventListener('resize', this.updateMobileState)
+ },
+ destroyed () {
+ window.removeEventListener('resize', this.updateMobileState)
},
computed: {
currentUser () { return this.$store.state.users.currentUser },
@@ -82,13 +90,8 @@ export default {
chat () { return this.$store.state.chat.channel.state === 'joined' },
suggestionsEnabled () { return this.$store.state.instance.suggestionsEnabled },
showInstanceSpecificPanel () { return this.$store.state.instance.showInstanceSpecificPanel },
- unseenNotifications () {
- return unseenNotificationsFromStore(this.$store)
- },
- unseenNotificationsCount () {
- return this.unseenNotifications.length
- },
- showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel }
+ showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel },
+ isMobileLayout () { return this.$store.state.interface.mobileLayout }
},
methods: {
scrollToTop () {
@@ -101,8 +104,12 @@ export default {
onFinderToggled (hidden) {
this.finderHidden = hidden
},
- toggleMobileSidebar () {
- this.$refs.sideDrawer.toggleDrawer()
+ updateMobileState () {
+ const mobileLayout = windowWidth() <= 800
+ const changed = mobileLayout !== this.isMobileLayout
+ if (changed) {
+ this.$store.dispatch('setMobileLayout', mobileLayout)
+ }
}
}
}
diff --git a/src/App.scss b/src/App.scss
index ae068e4f..52a786ad 100644
--- a/src/App.scss
+++ b/src/App.scss
@@ -101,6 +101,14 @@ button {
background-color: $fallback--bg;
background-color: var(--bg, $fallback--bg)
}
+
+ &.danger {
+ // TODO: add better color variable
+ color: $fallback--text;
+ color: var(--alertErrorPanelText, $fallback--text);
+ background-color: $fallback--alertError;
+ background-color: var(--alertError, $fallback--alertError);
+ }
}
label.select {
@@ -371,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;
@@ -484,24 +493,6 @@ nav {
}
}
-.menu-button {
- display: none;
- position: relative;
-}
-
-.alert-dot {
- border-radius: 100%;
- height: 8px;
- width: 8px;
- position: absolute;
- left: calc(50% - 4px);
- top: calc(50% - 4px);
- margin-left: 6px;
- margin-top: -6px;
- background-color: $fallback--cRed;
- background-color: var(--badgeNotification, $fallback--cRed);
-}
-
.fade-enter-active, .fade-leave-active {
transition: opacity .2s
}
@@ -530,20 +521,6 @@ nav {
display: none;
}
-.panel-switcher {
- display: none;
- width: 100%;
- height: 46px;
-
- button {
- display: block;
- flex: 1;
- max-height: 32px;
- margin: 0.5em;
- padding: 0.5em;
- }
-}
-
@media all and (min-width: 800px) {
body {
overflow-y: scroll;
@@ -648,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;
@@ -671,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);
@@ -750,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;
@@ -817,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 4fff3d1d..769e075d 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -1,17 +1,14 @@
-
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 @@
+
+
+
+
+ {{ $t("nav.interactions") }}
+
+
+
+
+
+
+
+
+
+
+
+
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.js b/src/components/login_form/login_form.js
index fb6dc651..93214646 100644
--- a/src/components/login_form/login_form.js
+++ b/src/components/login_form/login_form.js
@@ -1,50 +1,80 @@
+import { mapState, mapGetters, mapActions, mapMutations } from 'vuex'
import oauthApi from '../../services/new_api/oauth.js'
+
const LoginForm = {
data: () => ({
user: {},
- authError: false
+ error: false
}),
computed: {
- loginMethod () { return this.$store.state.instance.loginMethod },
- loggingIn () { return this.$store.state.users.loggingIn },
- registrationOpen () { return this.$store.state.instance.registrationOpen }
+ isPasswordAuth () { return this.requiredPassword },
+ isTokenAuth () { return this.requiredToken },
+ ...mapState({
+ registrationOpen: state => state.instance.registrationOpen,
+ instance: state => state.instance,
+ loggingIn: state => state.users.loggingIn,
+ oauth: state => state.oauth
+ }),
+ ...mapGetters(
+ 'authFlow', ['requiredPassword', 'requiredToken', 'requiredMFA']
+ )
},
methods: {
- oAuthLogin () {
- oauthApi.login({
- oauth: this.$store.state.oauth,
- instance: this.$store.state.instance.server,
- commit: this.$store.commit
- })
- },
+ ...mapMutations('authFlow', ['requireMFA']),
+ ...mapActions({ login: 'authFlow/login' }),
submit () {
+ this.isTokenAuth ? this.submitToken() : this.submitPassword()
+ },
+ submitToken () {
+ const { clientId } = this.oauth
const data = {
- oauth: this.$store.state.oauth,
- instance: this.$store.state.instance.server
+ clientId,
+ instance: this.instance.server,
+ commit: this.$store.commit
}
- this.clearError()
+
+ oauthApi.getOrCreateApp(data)
+ .then((app) => { oauthApi.login({ ...app, ...data }) })
+ },
+ submitPassword () {
+ const { clientId } = this.oauth
+ const data = {
+ clientId,
+ oauth: this.oauth,
+ instance: this.instance.server,
+ commit: this.$store.commit
+ }
+ this.error = false
+
oauthApi.getOrCreateApp(data).then((app) => {
oauthApi.getTokenWithCredentials(
{
- app,
+ ...app,
instance: data.instance,
username: this.user.username,
password: this.user.password
}
).then((result) => {
if (result.error) {
- this.authError = result.error
- this.user.password = ''
+ if (result.error === 'mfa_required') {
+ this.requireMFA({app: app, settings: result})
+ } else {
+ this.error = result.error
+ this.focusOnPasswordInput()
+ }
return
}
- this.$store.commit('setToken', result.access_token)
- this.$store.dispatch('loginUser', result.access_token)
- this.$router.push({name: 'friends'})
+ this.login(result).then(() => {
+ this.$router.push({name: 'friends'})
+ })
})
})
},
- clearError () {
- this.authError = false
+ clearError () { this.error = false },
+ focusOnPasswordInput () {
+ let passwordInput = this.$refs.passwordInput
+ passwordInput.focus()
+ passwordInput.setSelectionRange(0, passwordInput.value.length)
}
}
}
diff --git a/src/components/login_form/login_form.vue b/src/components/login_form/login_form.vue
index 27a8e48a..a2c5cf8f 100644
--- a/src/components/login_form/login_form.vue
+++ b/src/components/login_form/login_form.vue
@@ -1,47 +1,53 @@
-
-
-
- {{$t('login.login')}}
-
-