diff --git a/.gitignore b/.gitignore index faf39252..479d57c4 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ test/unit/coverage test/e2e/reports selenium-debug.log .idea/ +config/local.json diff --git a/README.md b/README.md index b6e5a586..181b6a0d 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,15 @@ npm run build npm run unit ``` +# For Contributors: + +You can create file `/config/local.json` (see [example](https://git.pleroma.social/pleroma/pleroma-fe/blob/develop/config/local.example.json)) to enable some convenience dev options: + +* `target`: makes local dev server redirect to some existing instance's BE instead of local BE, useful for testing things in near-production environment and searching for real-life use-cases. +* `staticConfigPreference`: makes FE's `/static/config.json` take preference of BE-served `/api/statusnet/config.json`. Only works in dev mode. + +FE Build process also leaves current commit hash in global variable `___pleromafe_commit_hash` so that you can easily see which pleroma-fe commit instance is running, also helps pinpointing which commit was used when FE was bundled into BE. + # Configuration Edit config.json for configuration. scopeOptionsEnabled gives you input fields for CWs and the scope settings. diff --git a/build/webpack.base.conf.js b/build/webpack.base.conf.js index 198532ca..ea46ce6f 100644 --- a/build/webpack.base.conf.js +++ b/build/webpack.base.conf.js @@ -2,6 +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 env = process.env.NODE_ENV // check env & config/index.js to decide weither to enable CSS Sourcemaps for the @@ -91,5 +92,10 @@ module.exports = { browsers: ['last 2 versions'] }) ] - } + }, + plugins: [ + new ServiceWorkerWebpackPlugin({ + entry: path.join(__dirname, '..', 'src/sw.js') + }) + ] } diff --git a/build/webpack.dev.conf.js b/build/webpack.dev.conf.js index 7e1a104f..9f34619c 100644 --- a/build/webpack.dev.conf.js +++ b/build/webpack.dev.conf.js @@ -18,7 +18,9 @@ module.exports = merge(baseWebpackConfig, { devtool: '#eval-source-map', plugins: [ new webpack.DefinePlugin({ - 'process.env': config.dev.env + 'process.env': config.dev.env, + 'COMMIT_HASH': JSON.stringify('DEV'), + 'DEV_OVERRIDES': JSON.stringify(config.dev.settings) }), // https://github.com/glenjamin/webpack-hot-middleware#installation--usage new webpack.optimize.OccurenceOrderPlugin(), diff --git a/build/webpack.prod.conf.js b/build/webpack.prod.conf.js index 6119f700..c02f8e86 100644 --- a/build/webpack.prod.conf.js +++ b/build/webpack.prod.conf.js @@ -7,8 +7,13 @@ var baseWebpackConfig = require('./webpack.base.conf') var ExtractTextPlugin = require('extract-text-webpack-plugin') var HtmlWebpackPlugin = require('html-webpack-plugin') var env = process.env.NODE_ENV === 'testing' - ? require('../config/test.env') - : config.build.env + ? require('../config/test.env') + : config.build.env + +let commitHash = require('child_process') + .execSync('git rev-parse --short HEAD') + .toString(); +console.log(commitHash) var webpackConfig = merge(baseWebpackConfig, { module: { @@ -29,7 +34,9 @@ var webpackConfig = merge(baseWebpackConfig, { plugins: [ // http://vuejs.github.io/vue-loader/workflow/production.html new webpack.DefinePlugin({ - 'process.env': env + 'process.env': env, + 'COMMIT_HASH': JSON.stringify(commitHash), + 'DEV_OVERRIDES': JSON.stringify(undefined) }), new webpack.optimize.UglifyJsPlugin({ compress: { diff --git a/config/index.js b/config/index.js index 7b0ef26c..56fa5940 100644 --- a/config/index.js +++ b/config/index.js @@ -1,5 +1,15 @@ // see http://vuejs-templates.github.io/webpack for documentation. -var path = require('path') +const path = require('path') +let settings = {} +try { + settings = require('./local.json') + console.log('Using local dev server settings (/config/local.json):') + console.log(JSON.stringify(settings, null, 2)) +} catch (e) { + console.log('Local dev server settings not found (/config/local.json)') +} + +const target = settings.target || 'http://localhost:4000/' module.exports = { build: { @@ -19,21 +29,22 @@ module.exports = { dev: { env: require('./dev.env'), port: 8080, + settings, assetsSubDirectory: 'static', assetsPublicPath: '/', proxyTable: { '/api': { - target: 'http://localhost:4000/', + target, changeOrigin: true, cookieDomainRewrite: 'localhost' }, '/nodeinfo': { - target: 'http://localhost:4000/', + target, changeOrigin: true, cookieDomainRewrite: 'localhost' }, '/socket': { - target: 'http://localhost:4000/', + target, changeOrigin: true, cookieDomainRewrite: 'localhost', ws: true diff --git a/config/local.example.json b/config/local.example.json new file mode 100644 index 00000000..2a3bd00d --- /dev/null +++ b/config/local.example.json @@ -0,0 +1,4 @@ +{ + "target": "https://pleroma.soykaf.com/", + "staticConfigPreference": false +} diff --git a/package.json b/package.json index b716e7c6..60e5ca02 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "dependencies": { "babel-plugin-add-module-exports": "^0.2.1", "babel-plugin-lodash": "^3.2.11", + "chromatism": "^3.0.0", "diff": "^3.0.1", "karma-mocha-reporter": "^2.2.1", "localforage": "^1.5.0", @@ -30,6 +31,7 @@ "vue-router": "^3.0.1", "vue-template-compiler": "^2.3.4", "vue-timeago": "^3.1.2", + "vuelidate": "^0.7.4", "vuex": "^3.0.1", "whatwg-fetch": "^2.0.3" }, @@ -88,6 +90,7 @@ "raw-loader": "^0.5.1", "selenium-server": "2.53.1", "semver": "^5.3.0", + "serviceworker-webpack-plugin": "0.2.3", "shelljs": "^0.7.4", "sinon": "^1.17.3", "sinon-chai": "^2.8.0", diff --git a/src/App.js b/src/App.js index 3bfd307f..89aed01d 100644 --- a/src/App.js +++ b/src/App.js @@ -59,7 +59,12 @@ export default { }) }, logo () { return this.$store.state.instance.logo }, - style () { return { 'background-image': `url(${this.background})` } }, + style () { + return { + '--body-background-image': `url(${this.background})`, + 'background-image': `url(${this.background})` + } + }, sitename () { return this.$store.state.instance.name }, chat () { return this.$store.state.chat.channel.state === 'joined' }, suggestionsEnabled () { return this.$store.state.instance.suggestionsEnabled }, diff --git a/src/App.scss b/src/App.scss index fcab3b1c..871f193f 100644 --- a/src/App.scss +++ b/src/App.scss @@ -34,10 +34,11 @@ h4 { body { font-family: sans-serif; + font-family: var(--interfaceFont, sans-serif); font-size: 14px; margin: 0; - color: $fallback--fg; - color: var(--fg, $fallback--fg); + color: $fallback--text; + color: var(--text, $fallback--text); max-width: 100vw; overflow-x: hidden; } @@ -50,19 +51,24 @@ a { button { user-select: none; - color: $fallback--fg; - color: var(--fg, $fallback--fg); - background-color: $fallback--btn; - background-color: var(--btn, $fallback--btn); + color: $fallback--text; + color: var(--btnText, $fallback--text); + background-color: $fallback--fg; + background-color: var(--btn, $fallback--fg); border: none; border-radius: $fallback--btnRadius; border-radius: var(--btnRadius, $fallback--btnRadius); cursor: pointer; - border-top: 1px solid rgba(255, 255, 255, 0.2); - border-bottom: 1px solid rgba(0, 0, 0, 0.2); - box-shadow: 0px 0px 2px black; + box-shadow: 0px 0px 2px 0px rgba(0, 0, 0, 1), 0px 1px 0px 0px rgba(255, 255, 255, 0.2) inset, 0px -1px 0px 0px rgba(0, 0, 0, 0.2) inset; + box-shadow: var(--buttonShadow); font-size: 14px; font-family: sans-serif; + font-family: var(--interfaceFont, sans-serif); + + i[class*=icon-] { + color: $fallback--text; + color: var(--btnText, $fallback--text); + } &::-moz-focus-inner { border: none; @@ -70,11 +76,12 @@ button { &:hover { box-shadow: 0px 0px 4px rgba(255, 255, 255, 0.3); + box-shadow: var(--buttonHoverShadow); } &:active { - border-bottom: 1px solid rgba(255, 255, 255, 0.2); - border-top: 1px solid rgba(0, 0, 0, 0.2); + box-shadow: 0px 0px 4px 0px rgba(255, 255, 255, 0.3), 0px 1px 0px 0px rgba(0, 0, 0, 0.2) inset, 0px -1px 0px 0px rgba(255, 255, 255, 0.2) inset; + box-shadow: var(--buttonPressedShadow); } &:disabled { @@ -99,32 +106,37 @@ input, textarea, .select { border: none; border-radius: $fallback--inputRadius; border-radius: var(--inputRadius, $fallback--inputRadius); - border-bottom: 1px solid rgba(255, 255, 255, 0.2); - border-top: 1px solid rgba(0, 0, 0, 0.2); - box-shadow: 0px 0px 2px black inset; - background-color: $fallback--input; - background-color: var(--input, $fallback--input); - color: $fallback--lightFg; - color: var(--lightFg, $fallback--lightFg); + box-shadow: 0px 1px 0px 0px rgba(0, 0, 0, 0.2) inset, 0px -1px 0px 0px rgba(255, 255, 255, 0.2) inset, 0px 0px 2px 0px rgba(0, 0, 0, 1) inset; + box-shadow: var(--inputShadow); + background-color: $fallback--fg; + background-color: var(--input, $fallback--fg); + color: $fallback--lightText; + color: var(--inputText, $fallback--lightText); font-family: sans-serif; + font-family: var(--inputFont, sans-serif); font-size: 14px; - padding: 8px 7px; + padding: 8px .5em; box-sizing: border-box; display: inline-block; position: relative; - height: 29px; + height: 28px; line-height: 16px; hyphens: none; + &:disabled, &[disabled=disabled] { + cursor: not-allowed; + opacity: 0.5; + } + .icon-down-open { position: absolute; top: 0; bottom: 0; right: 5px; height: 100%; - color: $fallback--fg; - color: var(--fg, $fallback--fg); - line-height: 29px; + color: $fallback--text; + color: var(--text, $fallback--text); + line-height: 28px; z-index: 0; pointer-events: none; } @@ -135,22 +147,33 @@ input, textarea, .select { appearance: none; background: transparent; border: none; + color: $fallback--text; + color: var(--text, $fallback--text); margin: 0; - color: $fallback--fg; - color: var(--fg, $fallback--fg); - padding: 4px 2em 3px 3px; + padding: 0 2em 0 .2em; + font-family: sans-serif; + font-family: var(--inputFont, sans-serif); + font-size: 14px; width: 100%; z-index: 1; - height: 29px; + height: 28px; line-height: 16px; } + &[type=range] { + background: none; + border: none; + margin: 0; + box-shadow: none; + flex: 1; + } + &[type=radio], &[type=checkbox] { display: none; &:checked + label::before { - color: $fallback--fg; - color: var(--fg, $fallback--fg); + color: $fallback--text; + color: var(--text, $fallback--text); } &:disabled, { @@ -166,14 +189,13 @@ input, textarea, .select { transition: color 200ms; width: 1.1em; height: 1.1em; - border-radius: $fallback--checkBoxRadius; - border-radius: var(--checkBoxRadius, $fallback--checkBoxRadius); - border-bottom: 1px solid rgba(255, 255, 255, 0.2); - border-top: 1px solid rgba(0, 0, 0, 0.2); + border-radius: $fallback--checkboxRadius; + border-radius: var(--checkboxRadius, $fallback--checkboxRadius); box-shadow: 0px 0px 2px black inset; + box-shadow: var(--inputShadow); margin-right: .5em; - background-color: $fallback--input; - background-color: var(--input, $fallback--input); + background-color: $fallback--fg; + background-color: var(--input, $fallback--fg); vertical-align: top; text-align: center; line-height: 1.1em; @@ -187,8 +209,8 @@ input, textarea, .select { } option { - color: $fallback--fg; - color: var(--fg, $fallback--fg); + color: $fallback--text; + color: var(--text, $fallback--text); background-color: $fallback--bg; background-color: var(--bg, $fallback--bg); } @@ -253,7 +275,7 @@ nav { mask-position: center; mask-size: contain; background-color: $fallback--fg; - background-color: var(--fg, $fallback--fg); + background-color: var(--topBarText, $fallback--fg); position: absolute; top: 0; bottom: 0; @@ -276,9 +298,9 @@ nav { margin: auto; height: 50px; - a i { + a, a i { color: $fallback--link; - color: var(--link, $fallback--link); + color: var(--topBarLink, $fallback--link); } } } @@ -301,15 +323,33 @@ main-router { .panel { display: flex; + position: relative; + flex-direction: column; margin: 0.5em; background-color: $fallback--bg; background-color: var(--bg, $fallback--bg); - border-radius: $fallback--panelRadius; - border-radius: var(--panelRadius, $fallback--panelRadius); - box-shadow: 1px 1px 4px rgba(0,0,0,.6); + &::after, & { + border-radius: $fallback--panelRadius; + border-radius: var(--panelRadius, $fallback--panelRadius); + } + + &::after { + content: ''; + position: absolute; + + top: 0; + bottom: 0; + left: 0; + right: 0; + + pointer-events: none; + + box-shadow: 1px 1px 4px rgba(0,0,0,.6); + box-shadow: var(--panelShadow); + } } .panel-body:empty::before { @@ -327,15 +367,23 @@ main-router { padding: .6em .6em; text-align: left; line-height: 28px; - background-color: $fallback--btn; - background-color: var(--btn, $fallback--btn); + color: var(--panelText); + background-color: $fallback--fg; + background-color: var(--panel, $fallback--fg); align-items: baseline; + box-shadow: var(--panelHeaderShadow); .title { flex: 1 0 auto; font-size: 1.3em; } + .faint { + background-color: transparent; + color: $fallback--faint; + color: var(--panelFaint, $fallback--faint); + } + .alert { white-space: nowrap; text-overflow: ellipsis; @@ -356,6 +404,11 @@ main-router { min-width: 1px; align-self: stretch; } + + a { + color: $fallback--link; + color: var(--panelLink, $fallback--link) + } } .panel-heading.stub { @@ -366,6 +419,11 @@ main-router { .panel-footer { border-radius: 0 0 $fallback--panelRadius $fallback--panelRadius; border-radius: 0 0 var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius); + + a { + color: $fallback--link; + color: var(--panelLink, $fallback--link) + } } .panel-body > p { @@ -384,11 +442,13 @@ main-router { nav { z-index: 1000; - background-color: $fallback--btn; - background-color: var(--btn, $fallback--btn); + color: var(--topBarText); + background-color: $fallback--fg; + background-color: var(--topBar, $fallback--fg); color: $fallback--faint; color: var(--faint, $fallback--faint); box-shadow: 0px 0px 4px rgba(0,0,0,.6); + box-shadow: var(--topBarShadow); .back-button { display: block; @@ -490,20 +550,46 @@ nav { flex-grow: 0; } } +.badge { + display: inline-block; + border-radius: 99px; + min-width: 22px; + max-width: 22px; + min-height: 22px; + max-height: 22px; + font-size: 15px; + line-height: 22px; + text-align: center; + vertical-align: middle; + white-space: nowrap; + padding: 0; + + &.badge-notification { + background-color: $fallback--cRed; + background-color: var(--badgeNotification, $fallback--cRed); + color: white; + color: var(--badgeNotificationText, white); + } +} .alert { margin: 0.35em; padding: 0.25em; border-radius: $fallback--tooltipRadius; border-radius: var(--tooltipRadius, $fallback--tooltipRadius); - color: $fallback--faint; - color: var(--faint, $fallback--faint); min-height: 28px; line-height: 28px; &.error { - background-color: $fallback--cAlertRed; - background-color: var(--cAlertRed, $fallback--cAlertRed); + background-color: $fallback--alertError; + background-color: var(--alertError, $fallback--alertError); + color: $fallback--text; + color: var(--alertErrorText, $fallback--text); + + .panel-heading & { + color: $fallback--text; + color: var(--alertErrorPanelText, $fallback--text); + } } } @@ -536,8 +622,8 @@ nav { cursor: pointer; .selected { - color: $fallback--lightFg; - color: var(--lightFg, $fallback--lightFg); + color: $fallback--lightText; + color: var(--lightText, $fallback--lightText); } .text-format { diff --git a/src/App.vue b/src/App.vue index 7716ac39..048c1e77 100644 --- a/src/App.vue +++ b/src/App.vue @@ -33,7 +33,7 @@ - + diff --git a/src/_variables.scss b/src/_variables.scss index b5222a6a..150e4fb5 100644 --- a/src/_variables.scss +++ b/src/_variables.scss @@ -3,24 +3,23 @@ $main-background: white; $darkened-background: whitesmoke; $fallback--bg: #121a24; -$fallback--btn: #182230; -$fallback--input: #182230; +$fallback--fg: #182230; $fallback--faint: rgba(185, 185, 186, .5); -$fallback--fg: #b9b9ba; +$fallback--text: #b9b9ba; $fallback--link: #d8a070; $fallback--icon: #666; $fallback--lightBg: rgb(21, 30, 42); -$fallback--lightFg: #b9b9ba; +$fallback--lightText: #b9b9ba; $fallback--border: #222; $fallback--cRed: #ff0000; $fallback--cBlue: #0095ff; $fallback--cGreen: #0fa00f; $fallback--cOrange: orange; -$fallback--cAlertRed: rgba(211,16,20,.5); +$fallback--alertError: rgba(211,16,20,.5); $fallback--panelRadius: 10px; -$fallback--checkBoxRadius: 2px; +$fallback--checkboxRadius: 2px; $fallback--btnRadius: 4px; $fallback--inputRadius: 4px; $fallback--tooltipRadius: 5px; diff --git a/src/boot/after_store.js b/src/boot/after_store.js index ea5d4ecd..07337595 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -17,17 +17,21 @@ import FollowRequests from '../components/follow_requests/follow_requests.vue' import OAuthCallback from '../components/oauth_callback/oauth_callback.vue' import UserSearch from '../components/user_search/user_search.vue' -const afterStoreSetup = ({store, i18n}) => { +const afterStoreSetup = ({ store, i18n }) => { window.fetch('/api/statusnet/config.json') .then((res) => res.json()) .then((data) => { - const {name, closed: registrationClosed, textlimit, server} = data.site + const { name, closed: registrationClosed, textlimit, server, vapidPublicKey } = data.site store.dispatch('setInstanceOption', { name: 'name', value: name }) store.dispatch('setInstanceOption', { name: 'registrationOpen', value: (registrationClosed === '0') }) store.dispatch('setInstanceOption', { name: 'textlimit', value: parseInt(textlimit) }) store.dispatch('setInstanceOption', { name: 'server', value: server }) + if (vapidPublicKey) { + store.dispatch('setInstanceOption', { name: 'vapidPublicKey', value: vapidPublicKey }) + } + var apiConfig = data.site.pleromafe window.fetch('/static/config.json') @@ -38,8 +42,17 @@ const afterStoreSetup = ({store, i18n}) => { return {} }) .then((staticConfig) => { + const overrides = window.___pleromafe_dev_overrides || {} + const env = window.___pleromafe_mode.NODE_ENV + // This takes static config and overrides properties that are present in apiConfig - var config = Object.assign({}, staticConfig, apiConfig) + let config = {} + if (overrides.staticConfigPreference && env === 'development') { + console.warn('OVERRIDING API CONFIG WITH STATIC CONFIG') + config = Object.assign({}, apiConfig, staticConfig) + } else { + config = Object.assign({}, staticConfig, apiConfig) + } var theme = (config.theme) var background = (config.background) @@ -58,6 +71,7 @@ const afterStoreSetup = ({store, i18n}) => { var loginMethod = (config.loginMethod) var scopeCopy = (config.scopeCopy) var subjectLineBehavior = (config.subjectLineBehavior) + var alwaysShowSubjectInput = (config.alwaysShowSubjectInput) store.dispatch('setInstanceOption', { name: 'theme', value: theme }) store.dispatch('setInstanceOption', { name: 'background', value: background }) @@ -75,6 +89,7 @@ const afterStoreSetup = ({store, i18n}) => { store.dispatch('setInstanceOption', { name: 'loginMethod', value: loginMethod }) store.dispatch('setInstanceOption', { name: 'scopeCopy', value: scopeCopy }) store.dispatch('setInstanceOption', { name: 'subjectLineBehavior', value: subjectLineBehavior }) + store.dispatch('setInstanceOption', { name: 'alwaysShowSubjectInput', value: alwaysShowSubjectInput }) if (chatDisabled) { store.dispatch('disableChat') } diff --git a/src/components/attachment/attachment.js b/src/components/attachment/attachment.js index 41730720..16114c30 100644 --- a/src/components/attachment/attachment.js +++ b/src/components/attachment/attachment.js @@ -13,6 +13,7 @@ const Attachment = { return { nsfwImage, hideNsfwLocal: this.$store.state.config.hideNsfw, + preloadImage: this.$store.state.config.preloadImage, loopVideo: this.$store.state.config.loopVideo, showHidden: false, loading: false, @@ -46,7 +47,7 @@ const Attachment = { } }, toggleHidden () { - if (this.img) { + if (this.img && !this.preloadImage) { if (this.img.onload) { this.img.onload() } else { diff --git a/src/components/attachment/attachment.vue b/src/components/attachment/attachment.vue index 40e2cf1b..5eaa0d1d 100644 --- a/src/components/attachment/attachment.vue +++ b/src/components/attachment/attachment.vue @@ -9,8 +9,7 @@
Hide
- - + @@ -161,6 +160,10 @@ display: flex; flex: 1; + &.hidden { + display: none; + } + .still-image { width: 100%; height: 100%; diff --git a/src/components/chat_panel/chat_panel.vue b/src/components/chat_panel/chat_panel.vue index 30070d3e..f174319a 100644 --- a/src/components/chat_panel/chat_panel.vue +++ b/src/components/chat_panel/chat_panel.vue @@ -55,8 +55,8 @@ .chat-heading { cursor: pointer; .icon-comment-empty { - color: $fallback--fg; - color: var(--fg, $fallback--fg); + color: $fallback--text; + color: var(--text, $fallback--text); } } diff --git a/src/components/color_input/color_input.vue b/src/components/color_input/color_input.vue new file mode 100644 index 00000000..34eec248 --- /dev/null +++ b/src/components/color_input/color_input.vue @@ -0,0 +1,53 @@ + + + + + diff --git a/src/components/contrast_ratio/contrast_ratio.vue b/src/components/contrast_ratio/contrast_ratio.vue new file mode 100644 index 00000000..bd971d00 --- /dev/null +++ b/src/components/contrast_ratio/contrast_ratio.vue @@ -0,0 +1,69 @@ + + + + + diff --git a/src/components/delete_button/delete_button.vue b/src/components/delete_button/delete_button.vue index d13547e2..b458b0dc 100644 --- a/src/components/delete_button/delete_button.vue +++ b/src/components/delete_button/delete_button.vue @@ -14,8 +14,8 @@ .icon-cancel,.delete-status { cursor: pointer; &:hover { - color: var(--cRed, $fallback--cRed); color: $fallback--cRed; + color: var(--cRed, $fallback--cRed); } } diff --git a/src/components/export_import/export_import.vue b/src/components/export_import/export_import.vue new file mode 100644 index 00000000..451a2668 --- /dev/null +++ b/src/components/export_import/export_import.vue @@ -0,0 +1,87 @@ + + + + + diff --git a/src/components/font_control/font_control.js b/src/components/font_control/font_control.js new file mode 100644 index 00000000..8e2b0e45 --- /dev/null +++ b/src/components/font_control/font_control.js @@ -0,0 +1,58 @@ +import { set } from 'vue' + +export default { + props: [ + 'name', 'label', 'value', 'fallback', 'options', 'no-inherit' + ], + data () { + return { + lValue: this.value, + availableOptions: [ + this.noInherit ? '' : 'inherit', + 'custom', + ...(this.options || []), + 'serif', + 'monospace', + 'sans-serif' + ].filter(_ => _) + } + }, + beforeUpdate () { + this.lValue = this.value + }, + computed: { + present () { + return typeof this.lValue !== 'undefined' + }, + dValue () { + return this.lValue || this.fallback || {} + }, + family: { + get () { + return this.dValue.family + }, + set (v) { + set(this.lValue, 'family', v) + this.$emit('input', this.lValue) + } + }, + isCustom () { + return this.preset === 'custom' + }, + preset: { + get () { + if (this.family === 'serif' || + this.family === 'sans-serif' || + this.family === 'monospace' || + this.family === 'inherit') { + return this.family + } else { + return 'custom' + } + }, + set (v) { + this.family = v === 'custom' ? '' : v + } + } + } +} diff --git a/src/components/font_control/font_control.vue b/src/components/font_control/font_control.vue new file mode 100644 index 00000000..ed36b280 --- /dev/null +++ b/src/components/font_control/font_control.vue @@ -0,0 +1,54 @@ + + + + + diff --git a/src/components/instance_specific_panel/instance_specific_panel.js b/src/components/instance_specific_panel/instance_specific_panel.js index 09e3d055..9bb5e945 100644 --- a/src/components/instance_specific_panel/instance_specific_panel.js +++ b/src/components/instance_specific_panel/instance_specific_panel.js @@ -2,6 +2,9 @@ const InstanceSpecificPanel = { computed: { instanceSpecificPanelContent () { return this.$store.state.instance.instanceSpecificPanelContent + }, + show () { + return !this.$store.state.config.hideISP } } } diff --git a/src/components/instance_specific_panel/instance_specific_panel.vue b/src/components/instance_specific_panel/instance_specific_panel.vue index ca8e00c0..a7b74667 100644 --- a/src/components/instance_specific_panel/instance_specific_panel.vue +++ b/src/components/instance_specific_panel/instance_specific_panel.vue @@ -1,5 +1,5 @@