diff --git a/.eslintrc b/.eslintrc index 3fa0e794..9ddfdf44 100644 --- a/.eslintrc +++ b/.eslintrc @@ -50,7 +50,8 @@ "import/first": "error", "import/no-namespace": "error", "import/no-default-export": "error", - "babel/no-unused-expressions": "error" + "babel/no-unused-expressions": "error", + "node/no-callback-literal": "off" }, "parserOptions": { "sourceType": "module", diff --git a/.github/actions/linux-build/Dockerfile b/.github/actions/linux-build/Dockerfile index fe5bc00f..4f23d2da 100644 --- a/.github/actions/linux-build/Dockerfile +++ b/.github/actions/linux-build/Dockerfile @@ -4,7 +4,7 @@ COPY entrypoint.sh /entrypoint.sh RUN apt-get update RUN apt-get install -y build-essential git-core unzip curl pkg-config rpm -RUN curl -sL https://deb.nodesource.com/setup_13.x | sudo -E bash - +RUN curl -sL https://deb.nodesource.com/setup_15.x | sudo -E bash - RUN apt-get install -y nodejs RUN npm i -g grunt-cli diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index a90a6bcf..af87a9ec 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -9,11 +9,12 @@ jobs: steps: - name: Get current git tag id: get_tag - uses: keeweb/get-tag@v2 + uses: keeweb/get-git-tag@v3.0.2 with: tagRegex: "^v(\\d+\\.\\d+\\.\\d+)$" tagRegexGroup: 1 - - uses: actions/checkout@v2 + - name: Checkout + uses: actions/checkout@v2 with: repository: keeweb/keeweb ref: ${{ github.repository == 'keeweb/keeweb' && github.sha || 'develop' }} @@ -36,11 +37,12 @@ jobs: steps: - name: Get current git tag id: get_tag - uses: keeweb/get-tag@v2 + uses: keeweb/get-git-tag@v3.0.2 with: tagRegex: "^v(\\d+\\.\\d+\\.\\d+)$" tagRegexGroup: 1 - - uses: actions/checkout@v2 + - name: Checkout + uses: actions/checkout@v2 with: repository: keeweb/keeweb ref: ${{ github.repository == 'keeweb/keeweb' && github.sha || 'develop' }} @@ -97,11 +99,12 @@ jobs: steps: - name: Get current git tag id: get_tag - uses: keeweb/get-tag@v2 + uses: keeweb/get-git-tag@v3.0.2 with: tagRegex: "^v(\\d+\\.\\d+\\.\\d+)$" tagRegexGroup: 1 - - uses: actions/checkout@v2 + - name: Checkout + uses: actions/checkout@v2 with: repository: keeweb/keeweb ref: ${{ github.repository == 'keeweb/keeweb' && github.sha || 'develop' }} @@ -126,7 +129,8 @@ jobs: mkdir keys echo "$CODESIGN" > keys/codesign.json xcrun altool --store-password-in-keychain-item "AC_PASSWORD" -u "$APPLE_ID_USERNAME" -p "$APPLE_DEPLOY_PASSWORD" - - uses: keeweb/import-codesign-certs@v1 + - name: Import certificates + uses: keeweb/import-codesign-certs@v1 with: p12-file-base64: ${{ secrets.APPLE_CERTIFICATE }} p12-password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} @@ -145,11 +149,12 @@ jobs: steps: - name: Get current git tag id: get_tag - uses: keeweb/get-tag@v2 + uses: keeweb/get-git-tag@v3.0.2 with: tagRegex: "^v(\\d+\\.\\d+\\.\\d+)$" tagRegexGroup: 1 - - uses: actions/checkout@v2 + - name: Checkout + uses: actions/checkout@v2 with: repository: keeweb/keeweb ref: ${{ github.repository == 'keeweb/keeweb' && github.sha || 'develop' }} @@ -220,16 +225,18 @@ jobs: steps: - name: Get current git tag id: get_tag - uses: keeweb/get-tag@v2 + uses: keeweb/get-git-tag@v3.0.2 with: tagRegex: "^v(\\d+\\.\\d+\\.\\d+)$" tagRegexGroup: 1 - - uses: GoogleCloudPlatform/github-actions/setup-gcloud@master + - name: Setup GCloud + uses: google-github-actions/github-actions/setup-gcloud@master with: version: '285.0.0' service_account_key: ${{ secrets.GCP_SA_KEY }} export_default_credentials: true - - uses: actions/checkout@v2 + - name: Checkout + uses: actions/checkout@v2 with: repository: keeweb/keeweb path: keeweb diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index c38d607a..8aea04d8 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -6,11 +6,13 @@ jobs: publish: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - name: Checkout + uses: actions/checkout@v2 with: fetch-depth: 0 ref: 'gh-pages' - - uses: GoogleCloudPlatform/github-actions/setup-gcloud@master + - name: Setup GCloud + uses: google-github-actions/github-actions/setup-gcloud@master with: version: '285.0.0' service_account_key: ${{ secrets.GCP_SA_KEY }} diff --git a/Gruntfile.js b/Gruntfile.js index bc248e14..c3004a15 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -78,11 +78,6 @@ module.exports = function (grunt) { expand: true, nonull: true }, - favicon: { - src: 'app/favicon.png', - dest: 'tmp/favicon.png', - nonull: true - }, icons: { cwd: 'app/icons/', src: ['*.png', '*.svg'], @@ -111,13 +106,6 @@ module.exports = function (grunt) { expand: true, nonull: true }, - fonts: { - src: 'node_modules/font-awesome/fonts/fontawesome-webfont.*', - dest: 'tmp/fonts/', - nonull: true, - expand: true, - flatten: true - }, 'desktop-html': { src: 'dist/index.html', dest: 'tmp/desktop/app/index.html', @@ -246,7 +234,7 @@ module.exports = function (grunt) { algo: 'sha512', expected: { style: 1, - script: 3 + script: 2 } }, app: { @@ -454,7 +442,7 @@ module.exports = function (grunt) { options: { title: 'KeeWeb', icon: 'graphics/icon.icns', - background: 'graphics/background.png', + background: 'graphics/dmg-background.png', 'background-color': '#E0E6F9', 'icon-size': 80, window: { size: { width: 658, height: 498 } }, diff --git a/app/favicon.png b/app/favicon.png deleted file mode 100644 index ac940bf1..00000000 Binary files a/app/favicon.png and /dev/null differ diff --git a/app/icons/android-chrome-192x192.png b/app/icons/android-chrome-192x192.png index b4887a6a..c87012fc 100644 Binary files a/app/icons/android-chrome-192x192.png and b/app/icons/android-chrome-192x192.png differ diff --git a/app/icons/android-chrome-512x512.png b/app/icons/android-chrome-512x512.png index aec9d2bc..aa075ac4 100644 Binary files a/app/icons/android-chrome-512x512.png and b/app/icons/android-chrome-512x512.png differ diff --git a/app/icons/apple-touch-icon.png b/app/icons/apple-touch-icon.png index b31afc25..589634e7 100644 Binary files a/app/icons/apple-touch-icon.png and b/app/icons/apple-touch-icon.png differ diff --git a/app/icons/favicon-16x16.png b/app/icons/favicon-16x16.png index a8f0ad8f..33a08287 100644 Binary files a/app/icons/favicon-16x16.png and b/app/icons/favicon-16x16.png differ diff --git a/app/icons/favicon-32x32.png b/app/icons/favicon-32x32.png index 28912227..e67e6cbc 100644 Binary files a/app/icons/favicon-32x32.png and b/app/icons/favicon-32x32.png differ diff --git a/app/icons/mstile-150x150.png b/app/icons/mstile-150x150.png index 280ec049..86ee52d8 100644 Binary files a/app/icons/mstile-150x150.png and b/app/icons/mstile-150x150.png differ diff --git a/app/icons/mstile-310x150.png b/app/icons/mstile-310x150.png index fe8c7930..c3a1a397 100644 Binary files a/app/icons/mstile-310x150.png and b/app/icons/mstile-310x150.png differ diff --git a/app/icons/mstile-310x310.png b/app/icons/mstile-310x310.png index b0969331..d347e6d5 100644 Binary files a/app/icons/mstile-310x310.png and b/app/icons/mstile-310x310.png differ diff --git a/app/icons/mstile-70x70.png b/app/icons/mstile-70x70.png index bf893857..8bdcce56 100644 Binary files a/app/icons/mstile-70x70.png and b/app/icons/mstile-70x70.png differ diff --git a/app/icons/safari-pinned-tab.svg b/app/icons/safari-pinned-tab.svg index 15ee7a32..076d2082 100644 --- a/app/icons/safari-pinned-tab.svg +++ b/app/icons/safari-pinned-tab.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/app/icons/splash-1125x2436.png b/app/icons/splash-1125x2436.png index 11503d26..068c4bed 100644 Binary files a/app/icons/splash-1125x2436.png and b/app/icons/splash-1125x2436.png differ diff --git a/app/icons/splash-1242x2148.png b/app/icons/splash-1242x2148.png index 4a7489ed..a55d47dc 100644 Binary files a/app/icons/splash-1242x2148.png and b/app/icons/splash-1242x2148.png differ diff --git a/app/icons/splash-1536x2048.png b/app/icons/splash-1536x2048.png index b0884bf1..c3cd0736 100644 Binary files a/app/icons/splash-1536x2048.png and b/app/icons/splash-1536x2048.png differ diff --git a/app/icons/splash-1668x2224.png b/app/icons/splash-1668x2224.png index f415370e..107bdcf6 100644 Binary files a/app/icons/splash-1668x2224.png and b/app/icons/splash-1668x2224.png differ diff --git a/app/icons/splash-2048x2732.png b/app/icons/splash-2048x2732.png index 02eda81b..a3000013 100644 Binary files a/app/icons/splash-2048x2732.png and b/app/icons/splash-2048x2732.png differ diff --git a/app/icons/splash-640x1136.png b/app/icons/splash-640x1136.png index 817307c6..cbb1abc4 100644 Binary files a/app/icons/splash-640x1136.png and b/app/icons/splash-640x1136.png differ diff --git a/app/icons/splash-750x1294.png b/app/icons/splash-750x1294.png index 14490f49..9d87f0a1 100644 Binary files a/app/icons/splash-750x1294.png and b/app/icons/splash-750x1294.png differ diff --git a/app/index.html b/app/index.html index b542f693..f5af9960 100644 --- a/app/index.html +++ b/app/index.html @@ -19,7 +19,6 @@ " /> - - diff --git a/app/resources/svg/google-drive.svg b/app/resources/svg/google-drive.svg deleted file mode 100644 index cd8dd52a..00000000 --- a/app/resources/svg/google-drive.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/app/resources/svg/onedrive.svg b/app/resources/svg/onedrive.svg deleted file mode 100644 index e179dfa7..00000000 --- a/app/resources/svg/onedrive.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/app/resources/svg/usb-token.svg b/app/resources/svg/usb-token.svg deleted file mode 100644 index cd68b129..00000000 --- a/app/resources/svg/usb-token.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/app/scripts/auto-type/index.js b/app/scripts/auto-type/index.js index 7acd1192..1118d1e4 100644 --- a/app/scripts/auto-type/index.js +++ b/app/scripts/auto-type/index.js @@ -167,9 +167,7 @@ const AutoType = { } else { if (!windowInfo.url) { // try to find a URL in the title - const urlMatcher = new RegExp( - 'https?:\\/\\/(www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{2,256}\\.[a-z]{2,4}\\b([-a-zA-Z0-9@:%_\\+.~#?&//=]*)' - ); + const urlMatcher = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\\+~#=]{2,256}\.[a-z]{2,4}\b([-a-zA-Z0-9@:%_\\+.~#?&\/=]*)/; const urlMatches = urlMatcher.exec(windowInfo.title); windowInfo.url = urlMatches && urlMatches.length > 0 ? urlMatches[0] : null; } @@ -243,7 +241,7 @@ const AutoType = { this.selectEntryView.remove(); this.selectEntryView = null; this.hideWindow(() => { - if (result) { + if (result?.entry) { this.activeWindowMatches(evt.windowInfo, (matches, activeWindowInfo) => { if (matches) { this.runAndHandleResult(result, evt.windowInfo.id); diff --git a/app/scripts/comp/app/chal-resp-calculator.js b/app/scripts/comp/app/chal-resp-calculator.js index a2460c54..4d4fa6a9 100644 --- a/app/scripts/comp/app/chal-resp-calculator.js +++ b/app/scripts/comp/app/chal-resp-calculator.js @@ -150,7 +150,7 @@ const ChalRespCalculator = { header: Locale.yubiKeyNoKeyHeader, body: Locale.yubiKeyNoKeyBody.replace('{}', serial), buttons: [Alerts.buttons.cancel], - iconSvg: 'usb-token', + icon: 'usb-token', cancel: () => { logger.info('No key alert closed'); @@ -173,7 +173,7 @@ const ChalRespCalculator = { header: Locale.yubiKeyTouchRequestedHeader, body: Locale.yubiKeyTouchRequestedBody.replace('{}', serial), buttons: [Alerts.buttons.cancel], - iconSvg: 'usb-token', + icon: 'usb-token', cancel: () => { logger.info('Touch alert closed'); diff --git a/app/scripts/comp/browser/feature-tester.js b/app/scripts/comp/browser/feature-tester.js index 46444a1d..82f8c0d0 100644 --- a/app/scripts/comp/browser/feature-tester.js +++ b/app/scripts/comp/browser/feature-tester.js @@ -4,7 +4,8 @@ const FeatureTester = { test() { return Promise.resolve() .then(() => this.checkWebAssembly()) - .then(() => this.checkLocalStorage()); + .then(() => this.checkLocalStorage()) + .then(() => this.checkWebCrypto()); }, checkWebAssembly() { @@ -28,6 +29,12 @@ const FeatureTester = { } catch (e) { throw 'LocalStorage is not supported'; } + }, + + checkWebCrypto() { + if (!global.crypto.subtle) { + throw 'WebCrypto is not supported'; + } } }; diff --git a/app/scripts/comp/browser/idle-tracker.js b/app/scripts/comp/browser/idle-tracker.js index c7b8afda..ea317877 100644 --- a/app/scripts/comp/browser/idle-tracker.js +++ b/app/scripts/comp/browser/idle-tracker.js @@ -10,6 +10,7 @@ const IdleTracker = { const idleMinutes = (Date.now() - this.actionTime) / 1000 / 60; const maxIdleMinutes = AppSettingsModel.idleMinutes; if (maxIdleMinutes && idleMinutes > maxIdleMinutes) { + Events.emit('before-user-idle'); Events.emit('user-idle'); } }, diff --git a/app/scripts/comp/format/kdbx-to-html.js b/app/scripts/comp/format/kdbx-to-html.js index 3214be26..f9273899 100644 --- a/app/scripts/comp/format/kdbx-to-html.js +++ b/app/scripts/comp/format/kdbx-to-html.js @@ -2,7 +2,7 @@ import kdbxweb from 'kdbxweb'; import { RuntimeInfo } from 'const/runtime-info'; import { Links } from 'const/links'; -import { DateFormat } from 'util/formatting/date-format'; +import { DateFormat } from 'comp/i18n/date-format'; import { StringFormat } from 'util/formatting/string-format'; import { Locale } from 'util/locale'; diff --git a/app/scripts/comp/i18n/date-format.js b/app/scripts/comp/i18n/date-format.js new file mode 100644 index 00000000..c99751b7 --- /dev/null +++ b/app/scripts/comp/i18n/date-format.js @@ -0,0 +1,77 @@ +import { SettingsManager } from 'comp/settings/settings-manager'; +import { StringFormat } from 'util/formatting/string-format'; + +const DateFormat = { + months() { + const format = new Intl.DateTimeFormat(SettingsManager.activeLocale, { month: 'long' }); + const months = []; + for (let month = 0; month < 12; month++) { + months.push(format.format(new Date(2008, month))); + } + return months; + }, + + weekDays() { + const format = new Intl.DateTimeFormat(SettingsManager.activeLocale, { weekday: 'long' }); + const weekdays = []; + for (let day = 1; day < 8; day++) { + weekdays.push(format.format(new Date(2007, 9, 6 + day))); + } + return weekdays; + }, + + shortWeekDays() { + const format = new Intl.DateTimeFormat(SettingsManager.activeLocale, { weekday: 'short' }); + const weekdays = []; + for (let day = 1; day < 8; day++) { + weekdays.push(format.format(new Date(Date.UTC(2007, 9, 6 + day)))); + } + return weekdays; + }, + + dtStr(dt) { + if (typeof dt === 'number') { + dt = new Date(dt); + } + return dt + ? new Intl.DateTimeFormat(SettingsManager.activeLocale, { + dateStyle: 'medium', + timeStyle: 'medium' + }).format(dt) + : ''; + }, + + dStr(dt) { + if (typeof dt === 'number') { + dt = new Date(dt); + } + return dt + ? new Intl.DateTimeFormat(SettingsManager.activeLocale, { + year: 'numeric', + month: 'short', + day: 'numeric' + }).format(dt) + : ''; + }, + + dtStrFs(dt) { + if (typeof dt === 'number') { + dt = new Date(dt); + } + return dt + ? dt.getFullYear() + + '-' + + StringFormat.pad(dt.getMonth() + 1, 2) + + '-' + + StringFormat.pad(dt.getDate(), 2) + + 'T' + + StringFormat.pad(dt.getHours(), 2) + + '-' + + StringFormat.pad(dt.getMinutes(), 2) + + '-' + + StringFormat.pad(dt.getSeconds(), 2) + : ''; + } +}; + +export { DateFormat }; diff --git a/app/scripts/comp/settings/settings-manager.js b/app/scripts/comp/settings/settings-manager.js index c17c8d2a..8f865115 100644 --- a/app/scripts/comp/settings/settings-manager.js +++ b/app/scripts/comp/settings/settings-manager.js @@ -2,32 +2,26 @@ import { Events } from 'framework/events'; import { Features } from 'util/features'; import { Locale } from 'util/locale'; -const appleThemes = { - macdark: 'setGenThemeMacDark' -}; - -const extraThemes = Features.isMac || Features.isiOS ? appleThemes : {}; - const SettingsManager = { neutralLocale: null, - activeLocale: 'en', + activeLocale: 'en-US', activeTheme: null, allLocales: { - 'en': 'English', + 'en-US': 'English', 'de-DE': 'Deutsch', 'fr-FR': 'Français' }, allThemes: { + dark: 'setGenThemeDark', + light: 'setGenThemeLight', fb: 'setGenThemeFb', db: 'setGenThemeDb', sd: 'setGenThemeSd', sl: 'setGenThemeSl', - wh: 'setGenThemeWh', te: 'setGenThemeTe', - hc: 'setGenThemeHc', - ...extraThemes + hc: 'setGenThemeHc' }, customLocales: {}, @@ -46,7 +40,7 @@ const SettingsManager = { }, getDefaultTheme() { - return Features.isMac ? 'macdark' : 'fb'; + return 'dark'; }, setTheme(theme) { @@ -83,7 +77,7 @@ const SettingsManager = { return; } let localeValues; - if (loc !== 'en') { + if (loc !== 'en-US') { if (this.customLocales[loc]) { localeValues = this.customLocales[loc]; } else { @@ -100,8 +94,8 @@ const SettingsManager = { getBrowserLocale() { const language = (navigator.languages && navigator.languages[0]) || navigator.language; - if (language && language.lastIndexOf('en', 0) === 0) { - return 'en'; + if (language && language.startsWith('en')) { + return 'en-US'; } return language; } diff --git a/app/scripts/const/default-app-settings.js b/app/scripts/const/default-app-settings.js index 33721f09..a6af7112 100644 --- a/app/scripts/const/default-app-settings.js +++ b/app/scripts/const/default-app-settings.js @@ -35,7 +35,6 @@ const DefaultAppSettings = { allowIframes: false, // allow displaying the app in IFrames useGroupIconForEntries: false, // automatically use group icon when creating new entries enableUsb: true, // enable interaction with USB devices - nativeArgon2: true, // use native argon2 module fieldLabelDblClickAutoType: false, // trigger auto-type by doubleclicking field label yubiKeyShowIcon: true, // show an icon to open OTP codes from YubiKey @@ -66,6 +65,7 @@ const DefaultAppSettings = { webdav: true, // enable WebDAV integration webdavSaveMethod: 'move', // how to save files with WebDAV: "move" or "put" + webdavStatReload: false, // WebDAV: reload the file instead of relying on Last-Modified gdrive: true, // enable Google Drive integration gdriveClientId: null, // custom Google Drive client id diff --git a/app/scripts/const/icon-map.js b/app/scripts/const/icon-map.js index 1079d691..b8f35d26 100644 --- a/app/scripts/const/icon-map.js +++ b/app/scripts/const/icon-map.js @@ -3,12 +3,12 @@ const IconMap = [ 'globe', 'exclamation-triangle', 'server', - 'thumb-tack', - 'comments-o', + 'thumbtack', + 'comments', 'puzzle-piece', - 'pencil-square-o', + 'edit', 'plug', - 'newspaper-o', + 'address-card', 'paperclip', 'camera', 'wifi', @@ -18,32 +18,32 @@ const IconMap = [ 'certificate', 'bullseye', 'desktop', - 'envelope-o', + 'envelope', 'cog', 'clipboard', - 'paper-plane-o', - 'television', + 'paper-plane', + 'newspaper', 'bolt', 'inbox', - 'floppy-o', - 'hdd-o', - 'dot-circle-o', - 'expeditedssl', + 'save', + 'hdd', + 'dot-circle', + 'user-lock', 'terminal', 'print', - 'map-signs', + 'project-diagram', 'flag-checkered', 'wrench', 'laptop', 'archive', 'credit-card', 'windows', - 'clock-o', + 'clock', 'search', 'flask', 'gamepad', - 'trash-o', - 'sticky-note-o', + 'trash', + 'sticky-note', 'ban', 'question-circle', 'cube', @@ -53,20 +53,20 @@ const IconMap = [ 'unlock-alt', 'lock', 'check', - 'pencil', - 'picture-o', + 'pencil-alt', + 'image', 'book', 'list-alt', 'user-secret', - 'cutlery', + 'utensils', 'home', - 'star-o', + 'star', 'linux', 'map-pin', 'apple', 'wikipedia-w', - 'usd', - 'calendar', + 'dollar-sign', + 'signature', 'mobile' ]; diff --git a/app/scripts/const/links.js b/app/scripts/const/links.js index eadc7d7e..665fbc6d 100644 --- a/app/scripts/const/links.js +++ b/app/scripts/const/links.js @@ -6,6 +6,7 @@ const Links = { BetaWebApp: 'https://beta.keeweb.info', License: 'https://github.com/keeweb/keeweb/blob/master/LICENSE', LicenseApache: 'https://opensource.org/licenses/Apache-2.0', + LicenseLinkCCBY40: 'https://creativecommons.org/licenses/by/4.0/', UpdateDesktop: 'https://github.com/keeweb/keeweb/releases/download/v{ver}/UpdateDesktop.zip', ReleaseNotes: 'https://github.com/keeweb/keeweb/blob/master/release-notes.md#release-notes', SelfHostedDropbox: 'https://github.com/keeweb/keeweb#self-hosting', diff --git a/app/scripts/framework/views/resizable.js b/app/scripts/framework/views/resizable.js index 41c92534..b61cd0de 100644 --- a/app/scripts/framework/views/resizable.js +++ b/app/scripts/framework/views/resizable.js @@ -25,7 +25,7 @@ const Resizable = { if (dragInfo.auto !== undefined) { this.$el.css(dragInfo.prop, dragInfo.auto); } else { - this.$el.css(dragInfo.prop, 'auto'); + this.$el.css(dragInfo.prop, ''); } this.fixSize(dragInfo); this.emit('view-resize', null); @@ -55,7 +55,7 @@ const Resizable = { const propLower = prop.toLowerCase(); const min = this.getSizeProp('min' + prop); const max = this.getSizeProp('max' + prop); - const auto = this.getSizeProp('auto' + prop) || 'auto'; + const auto = this.getSizeProp('auto' + prop); const startSize = this.$el[propLower](); return { startSize, prop: propLower, min, max, auto }; }, diff --git a/app/scripts/framework/views/view.js b/app/scripts/framework/views/view.js index bc373768..529fa866 100644 --- a/app/scripts/framework/views/view.js +++ b/app/scripts/framework/views/view.js @@ -24,7 +24,7 @@ class View extends EventEmitter { model = undefined; options = {}; views = {}; - hidden = false; + hidden = undefined; removed = false; modal = undefined; eventListeners = {}; @@ -239,6 +239,10 @@ class View extends EventEmitter { if (visible === undefined) { visible = this.hidden; } + if (this.hidden === !visible) { + this.debugLogger?.debug('Toggle: noop', visible); + return; + } this.hidden = !visible; if (this.modal) { if (visible) { @@ -258,7 +262,7 @@ class View extends EventEmitter { } isHidden() { - return this.hidden; + return !!this.hidden; } isVisible() { diff --git a/app/scripts/hbs-helpers/index.js b/app/scripts/hbs-helpers/index.js index 0a916cfa..a7b23692 100644 --- a/app/scripts/hbs-helpers/index.js +++ b/app/scripts/hbs-helpers/index.js @@ -4,4 +4,3 @@ import 'hbs-helpers/ifeq'; import 'hbs-helpers/ifneq'; import 'hbs-helpers/ifemptyoreq'; import 'hbs-helpers/res'; -import 'hbs-helpers/svg'; diff --git a/app/scripts/hbs-helpers/svg.js b/app/scripts/hbs-helpers/svg.js deleted file mode 100644 index 8a5e6288..00000000 --- a/app/scripts/hbs-helpers/svg.js +++ /dev/null @@ -1,9 +0,0 @@ -import Handlebars from 'hbs'; - -Handlebars.registerHelper('svg', (name, cls) => { - const icon = require(`svg/${name}.svg`).default; - if (typeof cls === 'string') { - return ` { const option = { - cls: 'fa ' + color + '-color', + cls: `fa ${color}-color`, value: color, filterValue: color }; @@ -66,10 +66,51 @@ class MenuModel extends Model { ]); this.generalSection = new MenuSectionModel([ - { locTitle: 'menuSetGeneral', icon: 'cog', page: 'general', active: true } + { + locTitle: 'menuSetGeneral', + icon: 'cog', + page: 'general', + section: 'top', + active: true + }, + { + locTitle: 'setGenAppearance', + icon: '0', + page: 'general', + section: 'appearance', + active: true + }, + { + locTitle: 'setGenFunction', + icon: '0', + page: 'general', + section: 'function', + active: true + }, + { + locTitle: 'setGenLock', + icon: '0', + page: 'general', + section: 'lock', + active: true + }, + { + locTitle: 'setGenStorage', + icon: '0', + page: 'general', + section: 'storage', + active: true + }, + { + locTitle: 'advanced', + icon: '0', + page: 'general', + section: 'advanced', + active: true + } ]); this.shortcutsSection = new MenuSectionModel([ - { locTitle: 'shortcuts', icon: 'keyboard-o', page: 'shortcuts' } + { locTitle: 'shortcuts', icon: 'keyboard', page: 'shortcuts' } ]); this.pluginsSection = new MenuSectionModel([ { locTitle: 'plugins', icon: 'puzzle-piece', page: 'plugins' } @@ -116,9 +157,8 @@ class MenuModel extends Model { this.colorsItem.options.forEach((opt) => { opt.active = opt === sel.option; }); - const selColor = - sel.item === this.colorsItem && sel.option ? sel.option.value + '-color' : ''; - this.colorsItem.cls = 'menu__item-colors ' + selColor; + this.colorsItem.iconCls = + sel.item === this.colorsItem && sel.option ? sel.option.value + '-color' : null; const filterKey = sel.item.filterKey; const filterValue = (sel.option || sel.item).filterValue; const filter = {}; @@ -127,6 +167,7 @@ class MenuModel extends Model { } else if (sections === this.menus.settings) { Events.emit('set-page', { page: sel.item.page, + section: sel.item.section, file: sel.item.file }); } diff --git a/app/scripts/plugins/plugin.js b/app/scripts/plugins/plugin.js index 5272a141..5422a5e2 100644 --- a/app/scripts/plugins/plugin.js +++ b/app/scripts/plugins/plugin.js @@ -434,7 +434,7 @@ class Plugin extends Model { delete SettingsManager.allLocales[locale.name]; delete SettingsManager.customLocales[locale.name]; if (SettingsManager.activeLocale === locale.name) { - AppSettingsModel.locale = 'en'; + AppSettingsModel.locale = 'en-US'; } } diff --git a/app/scripts/plugins/theme-vars.js b/app/scripts/plugins/theme-vars.js index f9b50c55..677ddce8 100644 --- a/app/scripts/plugins/theme-vars.js +++ b/app/scripts/plugins/theme-vars.js @@ -24,13 +24,14 @@ const ThemeVars = { apply(cssStyle) { this.init(); - const lines = ThemeVarsScss.split('\n'); - for (const line of lines) { - const match = line.match(/\s*([^:]+):\s*(.*?),?\s*$/); - if (!match) { - continue; + const matches = ThemeVarsScss.replace(/[\n\s]+/g, '').matchAll(/([\w\-]+):([^:]+),(\$)?/g); + for (let [, name, def, last] of matches) { + if (last && def.endsWith(')')) { + // definitions are written like this: + // map-merge((def:val, def:val, ..., last-def:val),$t) + // so, the last item has "),$" captured, here we're removing that bracket + def = def.substr(0, def.length - 1); } - const [, name, def] = match; const propName = '--' + name; const currentValue = cssStyle.getPropertyValue(propName); if (currentValue) { diff --git a/app/scripts/presenters/entry-presenter.js b/app/scripts/presenters/entry-presenter.js index d1028138..44444146 100644 --- a/app/scripts/presenters/entry-presenter.js +++ b/app/scripts/presenters/entry-presenter.js @@ -1,4 +1,4 @@ -import { DateFormat } from 'util/formatting/date-format'; +import { DateFormat } from 'comp/i18n/date-format'; import { Locale } from 'util/locale'; const EntryPresenter = function (descField, noColor, activeEntryId) { diff --git a/app/scripts/storage/impl/storage-dropbox.js b/app/scripts/storage/impl/storage-dropbox.js index 09a13f9b..e20f04b8 100644 --- a/app/scripts/storage/impl/storage-dropbox.js +++ b/app/scripts/storage/impl/storage-dropbox.js @@ -82,7 +82,8 @@ class StorageDropbox extends StorageBase { clientSecret: this._getSecret(), pkce: true, width: 600, - height: 400 + height: 400, + urlParams: { 'token_access_type': 'offline' } }; } diff --git a/app/scripts/storage/impl/storage-gdrive.js b/app/scripts/storage/impl/storage-gdrive.js index ab27f468..4b1dd809 100644 --- a/app/scripts/storage/impl/storage-gdrive.js +++ b/app/scripts/storage/impl/storage-gdrive.js @@ -11,7 +11,7 @@ class StorageGDrive extends StorageBase { name = 'gdrive'; enabled = true; uipos = 30; - iconSvg = 'google-drive'; + icon = 'google-drive'; _baseUrl = 'https://www.googleapis.com/drive/v3'; _baseUrlUpload = 'https://www.googleapis.com/upload/drive/v3'; diff --git a/app/scripts/storage/impl/storage-onedrive.js b/app/scripts/storage/impl/storage-onedrive.js index f4cbc5c3..6adbefab 100644 --- a/app/scripts/storage/impl/storage-onedrive.js +++ b/app/scripts/storage/impl/storage-onedrive.js @@ -8,7 +8,7 @@ class StorageOneDrive extends StorageBase { name = 'onedrive'; enabled = true; uipos = 40; - iconSvg = 'onedrive'; + icon = 'onedrive'; _baseUrl = 'https://graph.microsoft.com/v1.0/me'; diff --git a/app/scripts/storage/impl/storage-webdav.js b/app/scripts/storage/impl/storage-webdav.js index 0cfb4fdc..05ec2d8a 100644 --- a/app/scripts/storage/impl/storage-webdav.js +++ b/app/scripts/storage/impl/storage-webdav.js @@ -1,4 +1,6 @@ +import kdbxweb from 'kdbxweb'; import { StorageBase } from 'storage/storage-base'; +import { Locale } from 'util/locale'; class StorageWebDav extends StorageBase { name = 'webdav'; @@ -48,6 +50,12 @@ class StorageWebDav extends StorageBase { type: 'select', value: this.appSettings.webdavSaveMethod || 'default', options: { default: 'webdavSaveMove', put: 'webdavSavePut' } + }, + { + id: 'webdavStatReload', + title: 'webdavStatReload', + type: 'checkbox', + value: !!this.appSettings.webdavStatReload } ] }; @@ -64,33 +72,67 @@ class StorageWebDav extends StorageBase { method: 'GET', path, user: opts ? opts.user : null, - password: opts ? opts.password : null + password: opts ? opts.password : null, + nostat: this.appSettings.webdavStatReload }, callback ? (err, xhr, stat) => { - callback(err, xhr.response, stat); + if (this.appSettings.webdavStatReload) { + this._calcStatByContent(xhr).then((stat) => + callback(err, xhr.response, stat) + ); + } else { + callback(err, xhr.response, stat); + } } : null ); } stat(path, opts, callback) { - this._request( - { - op: 'Stat', - method: 'HEAD', - path, - user: opts ? opts.user : null, - password: opts ? opts.password : null - }, - callback - ? (err, xhr, stat) => { - callback(err, stat); - } - : null + this._statRequest( + path, + opts, + 'Stat', + callback ? (err, xhr, stat) => callback(err, stat) : null ); } + _statRequest(path, opts, op, callback) { + if (this.appSettings.webdavStatReload) { + this._request( + { + op, + method: 'GET', + path, + user: opts ? opts.user : null, + password: opts ? opts.password : null, + nostat: true + }, + callback + ? (err, xhr) => { + this._calcStatByContent(xhr).then((stat) => callback(err, xhr, stat)); + } + : null + ); + } else { + this._request( + { + op, + method: 'HEAD', + path, + user: opts ? opts.user : null, + password: opts ? opts.password : null + }, + callback + ? (err, xhr, stat) => { + callback(err, xhr, stat); + } + : null + ); + } + } + save(path, opts, data, callback, rev) { const cb = function (err, xhr, stat) { if (callback) { @@ -104,143 +146,113 @@ class StorageWebDav extends StorageBase { user: opts ? opts.user : null, password: opts ? opts.password : null }; - const that = this; - this._request( - { - ...saveOpts, - op: 'Save:stat', - method: 'HEAD' - }, - (err, xhr, stat) => { - let useTmpPath = this.appSettings.webdavSaveMethod !== 'put'; - if (err) { - if (!err.notFound) { - return cb(err); - } else { - that.logger.debug('Save: not found, creating'); - useTmpPath = false; - } - } else if (stat.rev !== rev) { - that.logger.debug('Save error', path, 'rev conflict', stat.rev, rev); - return cb({ revConflict: true }, xhr, stat); - } - if (useTmpPath) { - that._request( - { - ...saveOpts, - op: 'Save:put', - method: 'PUT', - path: tmpPath, - data, - nostat: true - }, - (err) => { - if (err) { - return cb(err); - } - that._request( - { - ...saveOpts, - op: 'Save:stat', - method: 'HEAD' - }, - (err, xhr, stat) => { - if (err) { - that._request({ - ...saveOpts, - op: 'Save:delete', - method: 'DELETE', - path: tmpPath - }); - return cb(err, xhr, stat); - } - if (stat.rev !== rev) { - that.logger.debug( - 'Save error', - path, - 'rev conflict', - stat.rev, - rev - ); - that._request({ - ...saveOpts, - op: 'Save:delete', - method: 'DELETE', - path: tmpPath - }); - return cb({ revConflict: true }, xhr, stat); - } - let movePath = path; - if (movePath.indexOf('://') < 0) { - if (movePath.indexOf('/') === 0) { - movePath = - location.protocol + '//' + location.host + movePath; - } else { - movePath = location.href - .replace(/\?(.*)/, '') - .replace(/[^/]*$/, movePath); - } - } - that._request( - { - ...saveOpts, - op: 'Save:move', - method: 'MOVE', - path: tmpPath, - nostat: true, - headers: { - Destination: encodeURI(movePath), - 'Overwrite': 'T' - } - }, - (err) => { - if (err) { - return cb(err); - } - that._request( - { - ...saveOpts, - op: 'Save:stat', - method: 'HEAD' - }, - (err, xhr, stat) => { - cb(err, xhr, stat); - } - ); - } - ); - } - ); - } - ); + this._statRequest(path, opts, 'Save:stat', (err, xhr, stat) => { + let useTmpPath = this.appSettings.webdavSaveMethod !== 'put'; + if (err) { + if (!err.notFound) { + return cb(err); } else { - that._request( - { - ...saveOpts, - op: 'Save:put', - method: 'PUT', - data, - nostat: true - }, - (err) => { + this.logger.debug('Save: not found, creating'); + useTmpPath = false; + } + } else if (stat.rev !== rev) { + this.logger.debug('Save error', path, 'rev conflict', stat.rev, rev); + return cb({ revConflict: true }, xhr, stat); + } + if (useTmpPath) { + this._request( + { + ...saveOpts, + op: 'Save:put', + method: 'PUT', + path: tmpPath, + data, + nostat: true + }, + (err) => { + if (err) { + return cb(err); + } + this._statRequest(path, opts, 'Save:stat', (err, xhr, stat) => { if (err) { - return cb(err); + this._request({ + ...saveOpts, + op: 'Save:delete', + method: 'DELETE', + path: tmpPath + }); + return cb(err, xhr, stat); } - that._request( + if (stat.rev !== rev) { + this.logger.debug( + 'Save error', + path, + 'rev conflict', + stat.rev, + rev + ); + this._request({ + ...saveOpts, + op: 'Save:delete', + method: 'DELETE', + path: tmpPath + }); + return cb({ revConflict: true }, xhr, stat); + } + let movePath = path; + if (movePath.indexOf('://') < 0) { + if (movePath.indexOf('/') === 0) { + movePath = location.protocol + '//' + location.host + movePath; + } else { + movePath = location.href + .replace(/\?(.*)/, '') + .replace(/[^/]*$/, movePath); + } + } + this._request( { ...saveOpts, - op: 'Save:stat', - method: 'HEAD' + op: 'Save:move', + method: 'MOVE', + path: tmpPath, + nostat: true, + headers: { + Destination: encodeURI(movePath), + 'Overwrite': 'T' + } }, - (err, xhr, stat) => { - cb(err, xhr, stat); + (err) => { + if (err) { + return cb(err); + } + this._statRequest(path, opts, 'Save:stat', (err, xhr, stat) => { + cb(err, xhr, stat); + }); } ); + }); + } + ); + } else { + this._request( + { + ...saveOpts, + op: 'Save:put', + method: 'PUT', + data, + nostat: true + }, + (err) => { + if (err) { + return cb(err); } - ); - } + this._statRequest(path, opts, 'Save:stat', (err, xhr, stat) => { + cb(err, xhr, stat); + }); + } + ); } - ); + }); } fileOptsToStoreOpts(opts, file) { @@ -276,21 +288,20 @@ class StorageWebDav extends StorageBase { } _request(config, callback) { - const that = this; if (config.rev) { - that.logger.debug(config.op, config.path, config.rev); + this.logger.debug(config.op, config.path, config.rev); } else { - that.logger.debug(config.op, config.path); + this.logger.debug(config.op, config.path); } - const ts = that.logger.ts(); + const ts = this.logger.ts(); const xhr = new XMLHttpRequest(); xhr.addEventListener('load', () => { if ([200, 201, 204].indexOf(xhr.status) < 0) { - that.logger.debug( + this.logger.debug( config.op + ' error', config.path, xhr.status, - that.logger.ts(ts) + this.logger.ts(ts) ); let err; switch (xhr.status) { @@ -312,35 +323,35 @@ class StorageWebDav extends StorageBase { } const rev = xhr.getResponseHeader('Last-Modified'); if (!rev && !config.nostat) { - that.logger.debug( + this.logger.debug( config.op + ' error', config.path, 'no headers', - that.logger.ts(ts) + this.logger.ts(ts) ); if (callback) { - callback('No Last-Modified header', xhr); + callback(Locale.webdavNoLastModified, xhr); callback = null; } return; } const completedOpName = config.op + (config.op.charAt(config.op.length - 1) === 'e' ? 'd' : 'ed'); - that.logger.debug(completedOpName, config.path, rev, that.logger.ts(ts)); + this.logger.debug(completedOpName, config.path, rev, this.logger.ts(ts)); if (callback) { callback(null, xhr, rev ? { rev } : null); callback = null; } }); xhr.addEventListener('error', () => { - that.logger.debug(config.op + ' error', config.path, that.logger.ts(ts)); + this.logger.debug(config.op + ' error', config.path, this.logger.ts(ts)); if (callback) { callback('network error', xhr); callback = null; } }); xhr.addEventListener('abort', () => { - that.logger.debug(config.op + ' error', config.path, 'aborted', that.logger.ts(ts)); + this.logger.debug(config.op + ' error', config.path, 'aborted', this.logger.ts(ts)); if (callback) { callback('aborted', xhr); callback = null; @@ -369,6 +380,23 @@ class StorageWebDav extends StorageBase { xhr.send(); } } + + _calcStatByContent(xhr) { + if ( + xhr.status !== 200 || + xhr.responseType !== 'arraybuffer' || + !xhr.response || + !xhr.response.byteLength + ) { + this.logger.debug('Cannot calculate rev by content'); + return null; + } + return kdbxweb.CryptoEngine.sha256(xhr.response).then((hash) => { + const rev = kdbxweb.ByteUtils.bytesToHex(hash).substr(0, 10); + this.logger.debug('Calculated rev by content', `${xhr.response.byteLength} bytes`, rev); + return { rev }; + }); + } } export { StorageWebDav }; diff --git a/app/scripts/storage/storage-base.js b/app/scripts/storage/storage-base.js index 88f73564..c54ccb5b 100644 --- a/app/scripts/storage/storage-base.js +++ b/app/scripts/storage/storage-base.js @@ -15,7 +15,6 @@ const MaxRequestRetries = 3; class StorageBase { name = null; icon = null; - iconSvg = null; enabled = false; system = false; uipos = null; @@ -244,7 +243,8 @@ class StorageBase { 'state': session.state, 'redirect_uri': session.redirectUri, 'response_type': 'code', - ...pkceParams + ...pkceParams, + ...opts.urlParams }); if (listener) { diff --git a/app/scripts/util/features.js b/app/scripts/util/features.js index 1afc6e41..33734ab1 100644 --- a/app/scripts/util/features.js +++ b/app/scripts/util/features.js @@ -16,7 +16,6 @@ const Features = { !isDesktop && !/^http(s?):\/\/((localhost:8085)|((app|beta)\.keeweb\.info))/.test(location.href), isLocal: location.origin.indexOf('localhost') >= 0, - canUseWasmInWebWorker: !isDesktop && !/Chrome/.test(navigator.appVersion), // TODO: enable it back in Chrome supportsTitleBarStyles() { return this.isMac; diff --git a/app/scripts/util/formatting/date-format.js b/app/scripts/util/formatting/date-format.js deleted file mode 100644 index 982c8baf..00000000 --- a/app/scripts/util/formatting/date-format.js +++ /dev/null @@ -1,49 +0,0 @@ -import { StringFormat } from 'util/formatting/string-format'; -import { Locale } from 'util/locale'; - -const DateFormat = { - dtStr(dt) { - if (typeof dt === 'number') { - dt = new Date(dt); - } - return dt - ? this.dStr(dt) + - ' ' + - StringFormat.pad(dt.getHours(), 2) + - ':' + - StringFormat.pad(dt.getMinutes(), 2) + - ':' + - StringFormat.pad(dt.getSeconds(), 2) - : ''; - }, - - dStr(dt) { - if (typeof dt === 'number') { - dt = new Date(dt); - } - return dt - ? dt.getDate() + ' ' + Locale.monthsShort[dt.getMonth()] + ' ' + dt.getFullYear() - : ''; - }, - - dtStrFs(dt) { - if (typeof dt === 'number') { - dt = new Date(dt); - } - return dt - ? dt.getFullYear() + - '-' + - StringFormat.pad(dt.getMonth() + 1, 2) + - '-' + - StringFormat.pad(dt.getDate(), 2) + - 'T' + - StringFormat.pad(dt.getHours(), 2) + - '-' + - StringFormat.pad(dt.getMinutes(), 2) + - '-' + - StringFormat.pad(dt.getSeconds(), 2) - : ''; - } -}; - -export { DateFormat }; diff --git a/app/scripts/util/kdbxweb/kdbxweb-init.js b/app/scripts/util/kdbxweb/kdbxweb-init.js index b61e34b9..15416dd7 100644 --- a/app/scripts/util/kdbxweb/kdbxweb-init.js +++ b/app/scripts/util/kdbxweb/kdbxweb-init.js @@ -1,7 +1,6 @@ import kdbxweb from 'kdbxweb'; import { Logger } from 'util/logger'; import { Features } from 'util/features'; -import { AppSettingsModel } from 'models/app-settings-model'; import { NativeModules } from 'comp/launcher/native-modules'; const logger = new Logger('argon2'); @@ -29,7 +28,7 @@ const KdbxwebInit = { if (!global.WebAssembly) { return Promise.reject('WebAssembly is not supported'); } - if (Features.isDesktop && AppSettingsModel.nativeArgon2) { + if (Features.isDesktop) { logger.debug('Using native argon2'); this.runtimeModule = { hash(args) { @@ -113,116 +112,70 @@ const KdbxwebInit = { totalMemory ); - if (Features.canUseWasmInWebWorker) { - const memoryDecl = `var wasmMemory=new WebAssembly.Memory({initial:${initialMemory},maximum:${totalMemory}});`; - const moduleDecl = - 'var Module={' + - 'wasmJSMethod: "native-wasm",' + - 'wasmBinary: Uint8Array.from(atob("' + - wasmBinaryBase64 + - '"), c => c.charCodeAt(0)),' + - 'print(...args) { postMessage({op:"log",args}) },' + - 'printErr(...args) { postMessage({op:"log",args}) },' + - 'postRun:' + - this.workerPostRun.toString() + - ',' + - 'calcHash:' + - this.calcHash.toString() + - ',' + - 'wasmMemory:wasmMemory,' + - 'buffer:wasmMemory.buffer,' + - 'TOTAL_MEMORY:' + - initialMemory * WASM_PAGE_SIZE + - '}'; - const script = argon2LoaderCode.replace( - /^var Module.*?}/, - memoryDecl + moduleDecl - ); - const blob = new Blob([script], { type: 'application/javascript' }); - const objectUrl = URL.createObjectURL(blob); - const worker = new Worker(objectUrl); - const onMessage = (e) => { - switch (e.data.op) { - case 'log': - logger.debug(...e.data.args); - break; - case 'postRun': - logger.debug( - 'WebAssembly runtime loaded (web worker)', - logger.ts(ts) - ); - URL.revokeObjectURL(objectUrl); - clearTimeout(loadTimeout); - worker.removeEventListener('message', onMessage); - this.runtimeModule = { - hash(args) { - return new Promise((resolve, reject) => { - worker.postMessage(args); - const onHashMessage = (e) => { - worker.removeEventListener( - 'message', - onHashMessage - ); - worker.terminate(); - KdbxwebInit.runtimeModule = null; - if (!e.data || e.data.error || !e.data.hash) { - const ex = - (e.data && e.data.error) || - 'unexpected error'; - logger.error('Worker error', ex); - reject(ex); - } else { - resolve(e.data.hash); - } - }; - worker.addEventListener('message', onHashMessage); - }); - } - }; - resolve(this.runtimeModule); - break; - default: - logger.error('Unknown message', e.data); - URL.revokeObjectURL(objectUrl); - reject('Load error'); - } - }; - worker.addEventListener('message', onMessage); - } else { - // Chrome and Electron crash if we use WASM in WebWorker - // see https://github.com/keeweb/keeweb/issues/1263 - const wasmMemory = new WebAssembly.Memory({ - initial: initialMemory, - maximum: totalMemory - }); - global.Module = { - wasmJSMethod: 'native-wasm', - wasmBinary: Uint8Array.from(atob(wasmBinaryBase64), (c) => c.charCodeAt(0)), - print(...args) { - logger.debug(...args); - }, - printErr(...args) { - logger.debug(...args); - }, - postRun: () => { - logger.debug('WebAssembly runtime loaded (main thread)', logger.ts(ts)); + const memoryDecl = `var wasmMemory=new WebAssembly.Memory({initial:${initialMemory},maximum:${totalMemory}});`; + const moduleDecl = + 'var Module={' + + 'wasmJSMethod: "native-wasm",' + + 'wasmBinary: Uint8Array.from(atob("' + + wasmBinaryBase64 + + '"), c => c.charCodeAt(0)),' + + 'print(...args) { postMessage({op:"log",args}) },' + + 'printErr(...args) { postMessage({op:"log",args}) },' + + 'postRun:' + + this.workerPostRun.toString() + + ',' + + 'calcHash:' + + this.calcHash.toString() + + ',' + + 'wasmMemory:wasmMemory,' + + 'buffer:wasmMemory.buffer,' + + 'TOTAL_MEMORY:' + + initialMemory * WASM_PAGE_SIZE + + '}'; + const script = argon2LoaderCode.replace(/^var Module.*?}/, memoryDecl + moduleDecl); + const blob = new Blob([script], { type: 'application/javascript' }); + const objectUrl = URL.createObjectURL(blob); + const worker = new Worker(objectUrl); + const onMessage = (e) => { + switch (e.data.op) { + case 'log': + logger.debug(...e.data.args); + break; + case 'postRun': + logger.debug('WebAssembly runtime loaded (web worker)', logger.ts(ts)); + URL.revokeObjectURL(objectUrl); clearTimeout(loadTimeout); - resolve({ - hash: (args) => { - const hash = this.calcHash(global.Module, args); - global.Module.unloadRuntime(); - global.Module = undefined; - return Promise.resolve(hash); + worker.removeEventListener('message', onMessage); + this.runtimeModule = { + hash(args) { + return new Promise((resolve, reject) => { + worker.postMessage(args); + const onHashMessage = (e) => { + worker.removeEventListener('message', onHashMessage); + worker.terminate(); + KdbxwebInit.runtimeModule = null; + if (!e.data || e.data.error || !e.data.hash) { + const ex = + (e.data && e.data.error) || 'unexpected error'; + logger.error('Worker error', ex); + reject(ex); + } else { + resolve(e.data.hash); + } + }; + worker.addEventListener('message', onHashMessage); + }); } - }); - }, - wasmMemory, - buffer: wasmMemory.buffer, - TOTAL_MEMORY: initialMemory * WASM_PAGE_SIZE - }; - // eslint-disable-next-line no-eval - eval(argon2LoaderCode); - } + }; + resolve(this.runtimeModule); + break; + default: + logger.error('Unknown message', e.data); + URL.revokeObjectURL(objectUrl); + reject('Load error'); + } + }; + worker.addEventListener('message', onMessage); } catch (err) { reject(err); } diff --git a/app/scripts/views/app-view.js b/app/scripts/views/app-view.js index 9c8e8dd4..4dfba6de 100644 --- a/app/scripts/views/app-view.js +++ b/app/scripts/views/app-view.js @@ -427,6 +427,7 @@ class AppView extends View { minimizeInsteadOfClose ) { Launcher.minimizeApp(); + this.appMinimized(); return Launcher.preventExit(e); } } @@ -533,7 +534,6 @@ class AppView extends View { saveAndLock(complete) { let pendingCallbacks = 0; const errorFiles = []; - const that = this; this.model.files.forEach(function (file) { if (!file.dirty) { return; @@ -549,7 +549,7 @@ class AppView extends View { errorFiles.push(file.name); } if (--pendingCallbacks === 0) { - if (errorFiles.length && that.model.files.hasDirtyFiles()) { + if (errorFiles.length && this.model.files.hasDirtyFiles()) { if (!Alerts.alertDisplayed) { const alertBody = errorFiles.length > 1 @@ -564,7 +564,7 @@ class AppView extends View { complete(false); } } else { - that.closeAllFilesAndShowFirst(); + this.closeAllFilesAndShowFirst(); if (complete) { complete(true); } diff --git a/app/scripts/views/auto-type/auto-type-select-view.js b/app/scripts/views/auto-type/auto-type-select-view.js index 0d96e19d..f136bf5d 100644 --- a/app/scripts/views/auto-type/auto-type-select-view.js +++ b/app/scripts/views/auto-type/auto-type-select-view.js @@ -241,7 +241,7 @@ class AutoTypeSelectView extends View { if (entry.fields.otp) { options.push({ value: '{TOTP}', - icon: 'clock-o', + icon: 'clock', text: Locale.autoTypeSelectionOtp }); } diff --git a/app/scripts/views/details/details-fields.js b/app/scripts/views/details/details-fields.js index 365a44c2..d10e909f 100644 --- a/app/scripts/views/details/details-fields.js +++ b/app/scripts/views/details/details-fields.js @@ -1,6 +1,6 @@ import { Locale } from 'util/locale'; import { StringFormat } from 'util/formatting/string-format'; -import { DateFormat } from 'util/formatting/date-format'; +import { DateFormat } from 'comp/i18n/date-format'; import { AppModel } from 'models/app-model'; import { FieldViewReadOnly } from 'views/fields/field-view-read-only'; import { FieldViewOtp } from 'views/fields/field-view-otp'; diff --git a/app/scripts/views/details/details-history-view.js b/app/scripts/views/details/details-history-view.js index 0109ab17..97ed8c2e 100644 --- a/app/scripts/views/details/details-history-view.js +++ b/app/scripts/views/details/details-history-view.js @@ -1,7 +1,7 @@ import { View } from 'framework/views/view'; import { Alerts } from 'comp/ui/alerts'; import { Keys } from 'const/keys'; -import { DateFormat } from 'util/formatting/date-format'; +import { DateFormat } from 'comp/i18n/date-format'; import { StringFormat } from 'util/formatting/string-format'; import { Locale } from 'util/locale'; import { Copyable } from 'framework/views/copyable'; diff --git a/app/scripts/views/details/details-view.js b/app/scripts/views/details/details-view.js index e1070215..da627b69 100644 --- a/app/scripts/views/details/details-view.js +++ b/app/scripts/views/details/details-view.js @@ -73,6 +73,7 @@ class DetailsView extends View { ); this.onKey(Keys.DOM_VK_B, this.copyUserName, KeyHandler.SHORTCUT_ACTION); this.onKey(Keys.DOM_VK_U, this.copyUrl, KeyHandler.SHORTCUT_ACTION); + this.onKey(Keys.DOM_VK_2, this.copyOtp, KeyHandler.SHORTCUT_OPT); if (AutoType.enabled) { this.onKey(Keys.DOM_VK_T, () => this.autoType(), KeyHandler.SHORTCUT_ACTION); } @@ -235,7 +236,7 @@ class DetailsView extends View { if (fieldView.isHidden()) { moreOptions.push({ value: 'add:' + fieldView.model.name, - icon: 'pencil', + icon: 'pencil-alt', text: Locale.detMenuAddField.replace('{}', fieldView.model.title) }); } @@ -262,11 +263,11 @@ class DetailsView extends View { text: Locale.detMenuHideEmpty }); } - moreOptions.push({ value: 'otp', icon: 'clock-o', text: Locale.detSetupOtp }); + moreOptions.push({ value: 'otp', icon: 'clock', text: Locale.detSetupOtp }); if (AutoType.enabled) { moreOptions.push({ value: 'auto-type', - icon: 'keyboard-o', + icon: 'keyboard', text: Locale.detAutoTypeSettings }); } @@ -456,7 +457,7 @@ class DetailsView extends View { return; } - this.model.initOtpGenerator(); + this.model.initOtpGenerator?.(); if (this.model.external) { return; } @@ -823,7 +824,7 @@ class DetailsView extends View { Alerts.yesno({ header: Locale.detDelToTrash, body: Locale.detDelToTrashBody, - icon: 'trash', + icon: 'trash-alt', success: doMove }); } else { @@ -864,19 +865,19 @@ class DetailsView extends View { if (this.model.external) { options.push({ value: 'det-copy-otp', - icon: 'clipboard', + icon: 'copy', text: Locale.detMenuCopyOtp }); } else { options.push({ value: 'det-copy-password', - icon: 'clipboard', + icon: 'copy', text: Locale.detMenuCopyPassword }); } options.push({ value: 'det-copy-user', - icon: 'clipboard', + icon: 'copy', text: Locale.detMenuCopyUser }); } @@ -886,13 +887,13 @@ class DetailsView extends View { if (canCopy) { options.push({ value: 'copy-to-clipboard', - icon: 'copy', + icon: 'clipboard', text: Locale.detCopyEntryToClipboard }); } } if (AutoType.enabled) { - options.push({ value: 'det-auto-type', icon: 'keyboard-o', text: Locale.detAutoType }); + options.push({ value: 'det-auto-type', icon: 'keyboard', text: Locale.detAutoType }); } Events.emit('show-context-menu', Object.assign(e, { options })); } diff --git a/app/scripts/views/fields/field-view-date.js b/app/scripts/views/fields/field-view-date.js index bd636f87..e3c63777 100644 --- a/app/scripts/views/fields/field-view-date.js +++ b/app/scripts/views/fields/field-view-date.js @@ -1,6 +1,5 @@ import Pikaday from 'pikaday'; -import { DateFormat } from 'util/formatting/date-format'; -import { Locale } from 'util/locale'; +import { DateFormat } from 'comp/i18n/date-format'; import { FieldViewText } from 'views/fields/field-view-text'; class FieldViewDate extends FieldViewText { @@ -30,9 +29,9 @@ class FieldViewDate extends FieldViewText { i18n: { previousMonth: '', nextMonth: '', - months: Locale.months, - weekdays: Locale.weekdays, - weekdaysShort: Locale.weekdaysShort + months: DateFormat.months(), + weekdays: DateFormat.weekDays(), + weekdaysShort: DateFormat.shortWeekDays() } }); this.picker.adjustPosition = this.adjustPickerPosition.bind(this); diff --git a/app/scripts/views/fields/field-view-text.js b/app/scripts/views/fields/field-view-text.js index 3b231d3b..8f79b3e3 100644 --- a/app/scripts/views/fields/field-view-text.js +++ b/app/scripts/views/fields/field-view-text.js @@ -61,7 +61,7 @@ class FieldViewText extends FieldView { Events.on('click', fieldValueBlurBound); this.stopBlurListener = () => Events.off('click', fieldValueBlurBound); this.listenTo(Events, 'main-window-will-close', this.externalEndEdit); - this.listenTo(Events, 'user-idle', this.externalEndEdit); + this.listenTo(Events, 'before-user-idle', this.externalEndEdit); if (this.model.multiline) { this.setInputHeight(); } diff --git a/app/scripts/views/fields/field-view.js b/app/scripts/views/fields/field-view.js index f35a547e..078c749a 100644 --- a/app/scripts/views/fields/field-view.js +++ b/app/scripts/views/fields/field-view.js @@ -180,6 +180,8 @@ class FieldView extends View { textEqual = this.value.equals(newVal); } else if (newVal && newVal.isProtected) { textEqual = newVal.equals(this.value); + } else if (newVal instanceof Date && this.value instanceof Date) { + textEqual = newVal.toDateString() === this.value.toDateString(); } else { textEqual = isEqual(this.value, newVal); } @@ -236,7 +238,7 @@ class FieldView extends View { } if (AutoType.enabled && this.model.sequence) { - options.push({ value: 'autotype', icon: 'keyboard-o', text: Locale.detAutoTypeField }); + options.push({ value: 'autotype', icon: 'keyboard', text: Locale.detAutoTypeField }); } const rect = this.$el[0].getBoundingClientRect(); @@ -321,9 +323,9 @@ class FieldView extends View { const actions = []; if (this.value) { - actions.push({ name: 'copy', icon: 'clipboard' }); + actions.push({ name: 'copy', icon: 'copy' }); } - actions.push({ name: 'edit', icon: 'pencil' }); + actions.push({ name: 'edit', icon: 'pencil-alt' }); if (this.value instanceof kdbxweb.ProtectedValue) { actions.push({ name: 'reveal', icon: 'eye' }); } diff --git a/app/scripts/views/icon-select-view.js b/app/scripts/views/icon-select-view.js index 210a9401..d106c473 100644 --- a/app/scripts/views/icon-select-view.js +++ b/app/scripts/views/icon-select-view.js @@ -51,9 +51,10 @@ class IconSelectView extends View { return; } this.downloadingFavicon = true; - this.$el.find('.icon-select__icon-download>i').addClass('fa-spinner fa-spin'); + this.$el.find('.icon-select__icon-download>i').addClass('spin'); this.$el .find('.icon-select__icon-download') + .addClass('icon-select__icon--progress') .removeClass('icon-select__icon--download-error'); const url = this.getIconUrl(true); const img = document.createElement('img'); @@ -62,19 +63,20 @@ class IconSelectView extends View { img.onload = () => { this.setSpecialImage(img, 'download'); this.$el.find('.icon-select__icon-download img').remove(); - this.$el.find('.icon-select__icon-download>i').removeClass('fa-spinner fa-spin'); + this.$el.find('.icon-select__icon-download>i').removeClass('spin'); this.$el .find('.icon-select__icon-download') + .removeClass('icon-select__icon--progress') .addClass('icon-select__icon--custom-selected') .append(img); this.downloadingFavicon = false; }; img.onerror = (e) => { logger.error('Favicon download error: ' + url, e); - this.$el.find('.icon-select__icon-download>i').removeClass('fa-spinner fa-spin'); + this.$el.find('.icon-select__icon-download>i').removeClass('spin'); this.$el .find('.icon-select__icon-download') - .removeClass('icon-select__icon--custom-selected') + .removeClass('icon-select__icon--custom-selected icon-select__icon--progress') .addClass('icon-select__icon--download-error'); this.downloadingFavicon = false; }; diff --git a/app/scripts/views/list-search-view.js b/app/scripts/views/list-search-view.js index 0b259908..2ceedb78 100644 --- a/app/scripts/views/list-search-view.js +++ b/app/scripts/views/list-search-view.js @@ -39,64 +39,64 @@ class ListSearchView extends View { this.sortOptions = [ { value: 'title', - icon: 'sort-alpha-asc', + icon: 'sort-alpha-down', loc: () => StringFormat.capFirst(Locale.title) + ' ' + this.addArrow(Locale.searchAZ) }, { value: '-title', - icon: 'sort-alpha-desc', + icon: 'sort-alpha-down-alt', loc: () => StringFormat.capFirst(Locale.title) + ' ' + this.addArrow(Locale.searchZA) }, { value: 'website', - icon: 'sort-alpha-asc', + icon: 'sort-alpha-down', loc: () => StringFormat.capFirst(Locale.website) + ' ' + this.addArrow(Locale.searchAZ) }, { value: '-website', - icon: 'sort-alpha-desc', + icon: 'sort-alpha-down-alt', loc: () => StringFormat.capFirst(Locale.website) + ' ' + this.addArrow(Locale.searchZA) }, { value: 'user', - icon: 'sort-alpha-asc', + icon: 'sort-alpha-down', loc: () => StringFormat.capFirst(Locale.user) + ' ' + this.addArrow(Locale.searchAZ) }, { value: '-user', - icon: 'sort-alpha-desc', + icon: 'sort-alpha-down-alt', loc: () => StringFormat.capFirst(Locale.user) + ' ' + this.addArrow(Locale.searchZA) }, { value: 'created', - icon: 'sort-numeric-asc', + icon: 'sort-numeric-down', loc: () => Locale.searchCreated + ' ' + this.addArrow(Locale.searchON) }, { value: '-created', - icon: 'sort-numeric-desc', + icon: 'sort-numeric-down-alt', loc: () => Locale.searchCreated + ' ' + this.addArrow(Locale.searchNO) }, { value: 'updated', - icon: 'sort-numeric-asc', + icon: 'sort-numeric-down', loc: () => Locale.searchUpdated + ' ' + this.addArrow(Locale.searchON) }, { value: '-updated', - icon: 'sort-numeric-desc', + icon: 'sort-numeric-down-alt', loc: () => Locale.searchUpdated + ' ' + this.addArrow(Locale.searchNO) }, { value: '-attachments', - icon: 'sort-amount-desc', + icon: 'sort-amount-down', loc: () => Locale.searchAttachments }, - { value: '-rank', icon: 'sort-amount-desc', loc: () => Locale.searchRank } + { value: '-rank', icon: 'sort-amount-down', loc: () => Locale.searchRank } ]; this.sortIcons = {}; this.sortOptions.forEach((opt) => { diff --git a/app/scripts/views/menu/menu-item-view.js b/app/scripts/views/menu/menu-item-view.js index 32b323a5..9f2c3b29 100644 --- a/app/scripts/views/menu/menu-item-view.js +++ b/app/scripts/views/menu/menu-item-view.js @@ -37,6 +37,7 @@ class MenuItemView extends View { this.listenTo(this.model, 'change:active', this.changeActive); this.listenTo(this.model, 'change:expanded', this.changeExpanded); this.listenTo(this.model, 'change:cls', this.changeCls); + this.listenTo(this.model, 'change:iconCls', this.changeIconCls); this.listenTo(this.model, 'delete', this.remove); this.listenTo(this.model, 'insert', this.insertItem); const shortcut = this.model.shortcut; @@ -108,6 +109,16 @@ class MenuItemView extends View { this.$el.addClass(cls); } + changeIconCls(model, cls, oldCls) { + const iconEl = this.el.querySelector('.menu__item-icon'); + if (oldCls) { + iconEl.classList.remove(oldCls); + } + if (cls) { + iconEl.classList.add(cls); + } + } + mouseover(e) { if (!e.button) { this.$el.addClass('menu__item--hover'); diff --git a/app/scripts/views/menu/menu-view.js b/app/scripts/views/menu/menu-view.js index 45ade994..fed9d84f 100644 --- a/app/scripts/views/menu/menu-view.js +++ b/app/scripts/views/menu/menu-view.js @@ -20,7 +20,6 @@ class MenuView extends View { minWidth = 130; maxWidth = 300; - autoWidth = 150; constructor(model, options) { super(model, options); diff --git a/app/scripts/views/open-view.js b/app/scripts/views/open-view.js index 16f2864a..04e2a1ea 100644 --- a/app/scripts/views/open-view.js +++ b/app/scripts/views/open-view.js @@ -162,20 +162,16 @@ class OpenView extends View { getLastOpenFiles() { return this.model.fileInfos.map((fileInfo) => { - let icon = 'file-text'; + let icon = 'file-alt'; const storage = Storage[fileInfo.storage]; if (storage && storage.icon) { icon = storage.icon; } - if (storage && storage.iconSvg) { - icon = null; - } return { id: fileInfo.id, name: fileInfo.name, path: this.getDisplayedPath(fileInfo), - icon, - iconSvg: storage ? storage.iconSvg : undefined + icon }; }); } @@ -691,7 +687,7 @@ class OpenView extends View { Alerts.error({ header: Locale.openError, body: Locale.openErrorDescription, - pre: err.toString() + pre: this.errorToString(err) }); } } else { @@ -793,16 +789,12 @@ class OpenView extends View { dir: true }); } - const listView = new StorageFileListView({ - files, - showHiddenFiles: config && config.showHiddenFiles - }); + const listView = new StorageFileListView({ files }); listView.on('selected', (file) => { if (file.dir) { this.listStorage(storage, { dir: file.path, - prevDir: (config && config.dir) || '', - showHiddenFiles: true + prevDir: (config && config.dir) || '' }); } else { this.openStorageFile(storage, file); @@ -811,7 +803,7 @@ class OpenView extends View { Alerts.alert({ header: Locale.openSelectFile, body: Locale.openSelectFileBody, - icon: storage.icon || 'files-o', + icon: storage.icon || 'file-alt', buttons: [{ result: '', title: Locale.alertCancel }], esc: '', click: '', @@ -1031,7 +1023,7 @@ class OpenView extends View { Alerts.error({ header: Locale.openError, body: Locale.openErrorDescription, - pre: err.toString() + pre: this.errorToString(err) }); } this.otpDevice = null; @@ -1068,13 +1060,24 @@ class OpenView extends View { Alerts.alert({ header: Locale.openChalRespHeader, - iconSvg: 'usb-token', + icon: 'usb-token', buttons: [{ result: '', title: Locale.alertCancel }], esc: '', click: '', view: chalRespView }); } + + errorToString(err) { + const str = err.toString(); + if (str !== {}.toString()) { + return str; + } + if (err.ykError && err.code) { + return Locale.yubiKeyErrorWithCode.replace('{}', err.code); + } + return undefined; + } } export { OpenView }; diff --git a/app/scripts/views/settings/settings-about-view.js b/app/scripts/views/settings/settings-about-view.js index 8bcef3ac..8237433d 100644 --- a/app/scripts/views/settings/settings-about-view.js +++ b/app/scripts/views/settings/settings-about-view.js @@ -12,6 +12,7 @@ class SettingsAboutView extends View { version: RuntimeInfo.version, licenseLink: Links.License, licenseLinkApache: Links.LicenseApache, + licenseLinkCCBY40: Links.LicenseLinkCCBY40, repoLink: Links.Repo, donationLink: Links.Donation, isDesktop: Features.isDesktop diff --git a/app/scripts/views/settings/settings-file-view.js b/app/scripts/views/settings/settings-file-view.js index 7213d752..d0ab0f1f 100644 --- a/app/scripts/views/settings/settings-file-view.js +++ b/app/scripts/views/settings/settings-file-view.js @@ -8,7 +8,7 @@ import { YubiKey } from 'comp/app/yubikey'; import { UsbListener } from 'comp/app/usb-listener'; import { Links } from 'const/links'; import { AppSettingsModel } from 'models/app-settings-model'; -import { DateFormat } from 'util/formatting/date-format'; +import { DateFormat } from 'comp/i18n/date-format'; import { UrlFormat } from 'util/formatting/url-format'; import { PasswordPresenter } from 'util/formatting/password-presenter'; import { Locale } from 'util/locale'; @@ -85,7 +85,6 @@ class SettingsFileView extends View { storageProviders.push({ name: prv.name, icon: prv.icon, - iconSvg: prv.iconSvg, own: name === fileStorage, backup: prv.backup }); @@ -337,7 +336,7 @@ class SettingsFileView extends View { Alerts.alert({ header: '', body: '', - icon: storage.icon || 'files-o', + icon: storage.icon || 'file-alt', buttons: [Alerts.buttons.ok, Alerts.buttons.cancel], esc: '', opaque: true, diff --git a/app/scripts/views/settings/settings-general-view.js b/app/scripts/views/settings/settings-general-view.js index dc0ec473..cc19ff38 100644 --- a/app/scripts/views/settings/settings-general-view.js +++ b/app/scripts/views/settings/settings-general-view.js @@ -12,7 +12,7 @@ import { AppSettingsModel } from 'models/app-settings-model'; import { UpdateModel } from 'models/update-model'; import { SemVer } from 'util/data/semver'; import { Features } from 'util/features'; -import { DateFormat } from 'util/formatting/date-format'; +import { DateFormat } from 'comp/i18n/date-format'; import { Locale } from 'util/locale'; import { SettingsLogsView } from 'views/settings/settings-logs-view'; import { SettingsPrvView } from 'views/settings/settings-prv-view'; @@ -23,7 +23,7 @@ class SettingsGeneralView extends View { template = template; events = { - 'change .settings__general-theme': 'changeTheme', + 'click .settings__general-theme': 'changeTheme', 'change .settings__general-locale': 'changeLocale', 'change .settings__general-font-size': 'changeFontSize', 'change .settings__general-expand': 'changeExpandGroups', @@ -205,20 +205,29 @@ class SettingsGeneralView extends View { } changeTheme(e) { - const theme = e.target.value; - AppSettingsModel.theme = theme; + const theme = e.target.closest('.settings__general-theme').dataset.theme; + if (theme === '...') { + this.goToPlugins(); + } else { + AppSettingsModel.theme = theme; + this.render(); + } } changeLocale(e) { const locale = e.target.value; if (locale === '...') { - e.target.value = AppSettingsModel.locale || 'en'; - this.appModel.menu.select({ - item: this.appModel.menu.pluginsSection.items[0] - }); - return; + e.target.value = AppSettingsModel.locale || 'en-US'; + this.goToPlugins(); + } else { + AppSettingsModel.locale = locale; } - AppSettingsModel.locale = locale; + } + + goToPlugins() { + this.appModel.menu.select({ + item: this.appModel.menu.pluginsSection.items[0] + }); } changeFontSize(e) { diff --git a/app/scripts/views/settings/settings-help-view.js b/app/scripts/views/settings/settings-help-view.js index 1acb49ab..c6c38cb8 100644 --- a/app/scripts/views/settings/settings-help-view.js +++ b/app/scripts/views/settings/settings-help-view.js @@ -26,7 +26,7 @@ class SettingsHelpView extends View { issueLink: Links.Repo + '/issues/new?body=' + - encodeURIComponent('!please describe your issue here!\n\n' + appInfo), + encodeURIComponent('# please describe your issue here\n\n' + appInfo), desktopLink: Links.Desktop, webAppLink: Links.WebApp, appInfo diff --git a/app/scripts/views/settings/settings-logs-view.js b/app/scripts/views/settings/settings-logs-view.js index 5ffeb990..c12e5ec7 100644 --- a/app/scripts/views/settings/settings-logs-view.js +++ b/app/scripts/views/settings/settings-logs-view.js @@ -6,10 +6,12 @@ import template from 'templates/settings/settings-logs-view.hbs'; class SettingsLogsView extends View { parent = '.settings__general-advanced'; template = template; + levelToColor = { debug: 'muted', warn: 'yellow', error: 'red' }; render() { const logs = Logger.getLast().map((item) => ({ level: item.level, + color: this.levelToColor[item.level], msg: '[' + StringFormat.padStr(item.level.toUpperCase(), 5) + diff --git a/app/scripts/views/settings/settings-plugins-view.js b/app/scripts/views/settings/settings-plugins-view.js index 2e4f8e99..c293edca 100644 --- a/app/scripts/views/settings/settings-plugins-view.js +++ b/app/scripts/views/settings/settings-plugins-view.js @@ -10,7 +10,7 @@ import { PluginManager } from 'plugins/plugin-manager'; import { Comparators } from 'util/data/comparators'; import { SemVer } from 'util/data/semver'; import { Features } from 'util/features'; -import { DateFormat } from 'util/formatting/date-format'; +import { DateFormat } from 'comp/i18n/date-format'; import { Locale } from 'util/locale'; import template from 'templates/settings/settings-plugins.hbs'; diff --git a/app/scripts/views/settings/settings-prv-view.js b/app/scripts/views/settings/settings-prv-view.js index 1fddd997..12d79096 100644 --- a/app/scripts/views/settings/settings-prv-view.js +++ b/app/scripts/views/settings/settings-prv-view.js @@ -7,7 +7,8 @@ class SettingsPrvView extends View { events = { 'change .settings__general-prv-field-sel': 'changeField', - 'input .settings__general-prv-field-txt': 'changeField' + 'input .settings__general-prv-field-txt': 'changeField', + 'change .settings__general-prv-field-check': 'changeCheckbox' }; render() { @@ -29,6 +30,13 @@ class SettingsPrvView extends View { this.render(); } } + + changeCheckbox(e) { + const id = e.target.dataset.id; + const value = !!e.target.checked; + const storage = Storage[this.model.name]; + storage.applySetting(id, value); + } } export { SettingsPrvView }; diff --git a/app/scripts/views/settings/settings-view.js b/app/scripts/views/settings/settings-view.js index f4fc1685..be77ef45 100644 --- a/app/scripts/views/settings/settings-view.js +++ b/app/scripts/views/settings/settings-view.js @@ -32,7 +32,7 @@ class SettingsView extends View { } setPage(e) { - let { page, file } = e; + let { page, section, file } = e; if (page === 'file' && file && file.external) { page = 'file-external'; } @@ -48,6 +48,20 @@ class SettingsView extends View { this.file = file; this.page = page; this.pageResized(); + this.scrollToSection(section); + } + + scrollToSection(section) { + let scrollEl; + if (section) { + scrollEl = this.views.page.el.querySelector(`#${section}`); + } + if (!scrollEl) { + scrollEl = this.views.page.el.querySelector(`h1`); + } + if (scrollEl) { + scrollEl.scrollIntoView(true); + } } returnToApp() { diff --git a/app/scripts/views/storage-file-list-view.js b/app/scripts/views/storage-file-list-view.js index 16dd689b..5893c93d 100644 --- a/app/scripts/views/storage-file-list-view.js +++ b/app/scripts/views/storage-file-list-view.js @@ -14,7 +14,7 @@ class StorageFileListView extends View { constructor(model) { super(model); this.allStorageFiles = {}; - this.showHiddenFiles = !!this.model.showHiddenFiles; + this.showHiddenFiles = false; } render() { @@ -27,13 +27,11 @@ class StorageFileListView extends View { dir: file.dir }; }); - const visibleFiles = files.filter((f) => !f.dir && f.kdbx); + const visibleFiles = files.filter((f) => f.dir || f.kdbx); const canShowHiddenFiles = visibleFiles.length && files.length > visibleFiles.length; if (!this.showHiddenFiles) { if (visibleFiles.length > 0) { files = visibleFiles; - } else { - this.showHiddenFiles = true; } } const density = files.length > 14 ? 3 : files.length > 7 ? 2 : 1; diff --git a/app/styles/areas/_app.scss b/app/styles/areas/_app.scss index 64e0b5df..8bc05e36 100644 --- a/app/styles/areas/_app.scss +++ b/app/styles/areas/_app.scss @@ -31,7 +31,8 @@ &__menu { flex: 0 0 auto; display: flex; - width: 150px; + width: 15em; + background-color: var(--secondary-background-color); @include mobile { &:not(.menu-visible) { display: none; @@ -92,11 +93,14 @@ } &__list { - flex: 0 0 250px; + flex: 0 0 25em; display: flex; align-items: stretch; flex-direction: column; overflow-y: auto; + .titlebar-hidden & { + padding-top: $titlebar-padding-tiny; + } @include mobile { flex: 1 1; .app--details-visible & { @@ -134,6 +138,7 @@ &__footer { flex: 0 0 auto; border-top: light-border(); + background-color: var(--secondary-background-color); } &__beta { @@ -141,5 +146,8 @@ text-align: center; background-color: var(--error-color); color: var(--text-contrast-error-color); + > .fa { + vertical-align: bottom; + } } } diff --git a/app/styles/areas/_auto-type.scss b/app/styles/areas/_auto-type.scss index d10fa5b7..15d31223 100644 --- a/app/styles/areas/_auto-type.scss +++ b/app/styles/areas/_auto-type.scss @@ -9,7 +9,7 @@ box-sizing: border-box; z-index: $z-index-no-modal; opacity: 1; - padding: $base-padding; + padding: $medium-padding; .titlebar-hidden & { padding-top: $titlebar-padding-small; @@ -108,10 +108,10 @@ } } &__item { - @include area-selectable(right); + @include area-selectable(); &--active, &--active:hover { - @include area-selected(right); + @include area-selected(); cursor: pointer; } &--active { diff --git a/app/styles/areas/_details.scss b/app/styles/areas/_details.scss index f25d873c..ec500f6f 100644 --- a/app/styles/areas/_details.scss +++ b/app/styles/areas/_details.scss @@ -16,9 +16,11 @@ height: $mobile-back-button-height; font-size: 1.2em; > i { - margin-right: 0.3em; + margin-right: 0.7em; font-size: 1.2em; - vertical-align: text-bottom; + vertical-align: middle; + position: relative; + top: -0.15em; } } } @@ -38,12 +40,10 @@ padding: 3px 6px 1px; overflow: hidden; text-overflow: ellipsis; - border-radius: $base-border-radius; + border-radius: var(--input-border-radius); border: 1px solid transparent; - height: 36px; - line-height: 34px; - position: relative; - top: -2px; + height: 1.4em; + line-height: 1.4em; white-space: nowrap; &:hover { transition: border-color $base-duration $base-timing; @@ -54,34 +54,38 @@ } } input.details__header-title-input { - height: 42px; - line-height: 34px; + height: calc(1.4em + 6px); + line-height: 1.4em; user-select: text; flex: 1; margin: 0 6px; padding: 0 6px; font-size: $large-header-font-size; font-weight: bold; - position: relative; - top: -2px; min-width: 0; @include mobile { width: 100%; } } - &-color, - &-icon { + &-color { user-select: none; - @include area-selectable(); - display: inline; font-size: $large-header-font-size; - height: 1em; padding-top: 0.1em; } &-icon { - width: 1.4em; + @include area-selectable(); + user-select: none; + display: inline; + font-size: $large-header-font-size; + padding-top: 0.1em; + border-radius: var(--block-border-radius); + width: 1.8em; + height: 1.5em; text-align: center; } + &-icon-img { + vertical-align: middle; + } } &__colors-popup { @@ -92,8 +96,7 @@ border-radius: $base-border-radius; background: var(--background-color); box-shadow: 0 0 3px var(--background-color); - top: 13px; - left: 6px; + left: 0.2em; font-size: $large-header-font-size; &:hover, .details__header-color:hover & { @@ -102,7 +105,7 @@ } } &-item { - padding: 8px 12px; + padding: 0 12px 0; cursor: pointer; display: block; position: relative; @@ -116,8 +119,7 @@ content: $fa-var-bookmark; opacity: 0.3; position: absolute; - left: 12px; - top: 8px; + left: 0.4em; } } } @@ -230,7 +232,7 @@ color: transparent; } .details__field--editable & { - border-radius: $base-border-radius; + border-radius: var(--input-border-radius); &:hover { @include nomobile { transition: border-color $base-duration $base-timing; @@ -265,7 +267,7 @@ > textarea { margin: 0; padding: 0 $base-padding-h; - line-height: $details-field-line-height + 2px; + line-height: $details-field-line-height; width: 100%; height: 20px; .details__field--protected & { @@ -319,7 +321,7 @@ flex: 0 0 auto; } &-btn { - @include position(absolute, 0 0 null null); + @include position(absolute, -0.2em 0 null null); color: var(--muted-color); cursor: pointer; &:hover { @@ -327,7 +329,7 @@ } &:before { @include position(absolute, 0 0 null null); - @include fa-icon(); + @include fa-icon; cursor: pointer; padding: 0.3em $base-padding-h; } @@ -445,7 +447,7 @@ align-self: flex-start; width: 2em; text-align: center; - padding-top: 0.25em; + padding-top: 0.1em; .details__field:hover & { opacity: 1; transition: opacity $slow-transition-in; @@ -483,36 +485,26 @@ } &__attachment { - user-select: none; - @include area-selectable(); + @include bg-btn(); align-self: flex-end; flex: 0 1 auto; - border: light-border(); margin-right: $small-spacing; margin-top: $small-spacing; - padding: $base-padding; - text-align: center; - overflow: hidden; - text-overflow: ellipsis; - i { - margin-right: 0.4em; - } - &--active { - border-bottom: 1px solid var(--action-color); + &--active, + &--active:hover { + @include bg-btn-active(); } } &__attachment-add { + @include icon-btn(); user-select: none; align-self: flex-end; flex: 0 0 auto; color: var(--muted-color); - border: 1px solid transparent; margin-right: $small-spacing; - padding: $base-padding; text-align: center; overflow: hidden; - transition: color $base-duration $base-timing; &:hover { color: var(--medium-color); } @@ -640,9 +632,8 @@ } &-item { position: absolute; - top: 4px; + top: 0; cursor: pointer; - transform: translateX(-48%); &:hover { color: var(--text-semi-muted-color); } @@ -721,10 +712,9 @@ padding: $base-padding; display: inline-block; word-break: break-all; - @include area-selectable(bottom); + @include area-selectable(); &--selected { background-color: var(--secondary-background-color); - border-bottom: selected-hover-border(); } } } @@ -734,6 +724,12 @@ float: right; cursor: pointer; user-select: none; + &-post { + margin-left: $tiny-spacing; + margin-right: $small-spacing; + position: relative; + top: 0.2em; + } &-pre, &-post { display: none; @@ -744,9 +740,11 @@ padding: $base-padding; font-size: 1.2em; > i { - margin-right: 0.3em; + margin-right: 0.7em; font-size: 1.2em; - vertical-align: text-bottom; + vertical-align: middle; + position: relative; + top: -0.15em; } &-pre { display: inline; diff --git a/app/styles/areas/_footer.scss b/app/styles/areas/_footer.scss index 62f79c26..087c30e5 100644 --- a/app/styles/areas/_footer.scss +++ b/app/styles/areas/_footer.scss @@ -9,7 +9,7 @@ &__db { flex: 0 0 auto; - @include area-selectable(top); + @include area-selectable-on-secondary(); position: relative; padding: $medium-padding; padding-right: 1.3em; @@ -22,6 +22,9 @@ color: var(--medium-color); } } + &:first-of-type { + padding-left: $base-spacing; + } &--expanded { flex: 1; @@ -64,7 +67,7 @@ &__btn { flex: 0 0 auto; - @include area-selectable(top); + @include area-selectable-on-secondary(); padding: $base-padding; .standalone & { padding-top: $base-padding-v; @@ -73,6 +76,9 @@ font-size: 1.4em; text-align: center; width: 1em; + &:last-of-type { + padding-right: $base-spacing; + } } &__update-icon { diff --git a/app/styles/areas/_generator.scss b/app/styles/areas/_generator.scss index 5dccc8a1..535a4045 100644 --- a/app/styles/areas/_generator.scss +++ b/app/styles/areas/_generator.scss @@ -9,7 +9,6 @@ float: right; cursor: pointer; position: relative; - top: 2px; & ~ .gen__top-btn { margin-right: 0.5em; } @@ -45,6 +44,11 @@ } } &__check-hide { + &-label { + text-align: center; + position: relative; + top: -2px; + } & + label.gen__check-hide-label:before { @include fa-icon; content: $fa-var-eye; @@ -54,6 +58,9 @@ content: $fa-var-eye-slash; color: inherit; } + &:not([disabled]) + label.gen__check-hide-label:hover:before { + color: var(--text-color); + } } &__btn-wrap { text-align: center; diff --git a/app/styles/areas/_grp.scss b/app/styles/areas/_grp.scss index 14388a1b..83c48c60 100644 --- a/app/styles/areas/_grp.scss +++ b/app/styles/areas/_grp.scss @@ -17,7 +17,7 @@ &__content, &__buttons { - padding: $base-padding; + padding: $medium-padding; } &__icon { diff --git a/app/styles/areas/_list.scss b/app/styles/areas/_list.scss index 1f767b4c..ac46ba67 100644 --- a/app/styles/areas/_list.scss +++ b/app/styles/areas/_list.scss @@ -49,6 +49,7 @@ } &-field { width: 100%; + height: 2.5em; @include mobile { font-size: 1.05em !important; box-shadow: none !important; @@ -71,11 +72,12 @@ top: 0.5em; } } - &-btn-new { - @include icon-btn; - } + &-btn-new, &-btn-sort { @include icon-btn; + height: 2.5em; + line-height: 2.3em; + padding: 0 $base-padding-h; } &-btn-menu { display: none; @@ -98,6 +100,7 @@ flex-wrap: wrap; &-text { flex: 100%; + padding: $base-padding-v 0; } } &-check { @@ -127,15 +130,15 @@ } &__item { - padding: 6px 10px 0; + padding: $base-padding; white-space: nowrap; text-overflow: ellipsis; overflow: hidden; @include nomobile { - @include area-selectable(right); + @include area-selectable(); &--active, &--active:hover { - @include area-selected(right); + @include area-selected(); } } @include mobile { @@ -147,7 +150,8 @@ } &:not(.list__item--table) { - height: 3rem; + border-radius: var(--block-border-radius); + margin: 0 $small-spacing; } &--expired { @@ -158,9 +162,12 @@ } &-icon { - margin-right: 2px; + margin-right: 0.2em; width: 14px; height: 14px; + vertical-align: top; + position: relative; + top: -1px; @include mobile { margin-right: 4px; } @@ -197,6 +204,10 @@ display: block; text-overflow: ellipsis; overflow: hidden; + margin-bottom: $tiny-spacing; + .list__item--active & { + color: var(--selected-item-text-color); + } } } } diff --git a/app/styles/areas/_menu.scss b/app/styles/areas/_menu.scss index 31989cf7..efe55c6a 100644 --- a/app/styles/areas/_menu.scss +++ b/app/styles/areas/_menu.scss @@ -12,6 +12,7 @@ @include scrollbar-on-hover; position: relative; overflow: hidden; + padding: $small-spacing 0; &--grow { flex: 1; @@ -65,25 +66,23 @@ display: block; position: absolute; cursor: pointer; - @include position(absolute, 50% null null 1em); + @include position(absolute, 50% null null 1.6em); transform: translateY(-50%); } } &-body { - @include area-selectable(); + @include area-selectable-on-secondary(); padding: $base-padding; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; - border-right: selected-transparent-border(); - .menu__item--hover > & { - border-right: selected-hover-border(); - } + border-radius: var(--block-border-radius); + margin: 0 $small-spacing; .menu__item--active > &, .menu__item--active.menu__item--hover > & { - @include area-selected(right); + @include area-selected-on-secondary(); } .menu__item > .menu__item > & { @@ -127,6 +126,8 @@ &-icon { width: 0.8em; + position: relative; + top: 0.1em; &--image { width: 12px; height: 12px; @@ -160,11 +161,11 @@ display: none; opacity: 0; position: absolute; - right: 1.1em; - top: 0.75em; + right: 1.2em; + top: 0.55em; cursor: pointer; transition: opacity $base-duration $base-timing, color $base-duration $base-timing; - color: var(--muted-color); + color: var(--clickable-on-secondary-color); &:hover { color: var(--medium-color); } diff --git a/app/styles/areas/_open.scss b/app/styles/areas/_open.scss index d4c870a7..bb57511c 100644 --- a/app/styles/areas/_open.scss +++ b/app/styles/areas/_open.scss @@ -24,20 +24,19 @@ } &__icon { - text-align: center; - cursor: pointer; - margin: 20px; - transition: color $base-duration $base-timing; + @include icon-btn(); + color: var(--open-icon-color); + padding-top: 0; + margin: 0.2em 0.5em; &:hover { color: var(--medium-color); } &:focus { .open--show-focus & { - outline: focused-outline(); + box-shadow: focused-box-shadow(); } } - &-i, - &-svg { + &-i { font-size: 4em; } &-text { @@ -46,29 +45,6 @@ color: var(--medium-color); } } - &-svg { - line-height: 0; - > svg { - @include size(1em); - } - } - @include mobile() { - &-i, - &-svg { - font-size: 4.6em; - } - &-text { - font-size: 1.1em; - } - .open__icons--lower & { - margin: 14px; - &-i, - &-svg { - font-size: 4.2em; - margin-bottom: 0.1em; - } - } - } } &__pass { @@ -100,22 +76,26 @@ padding: 0.6em $base-spacing; position: absolute; left: 100%; + border-radius: var(--block-border-radius); @include mobile { left: auto; right: 0; } color: var(--muted-color); + line-height: 3em; + height: 3.2em; > i { font-size: 3em; + line-height: 1; @include mobile { - line-height: 0.8; + line-height: 1.1; } } } .open--show-focus & { &-enter-btn:focus, &-opening-icon:focus { - outline: focused-outline(); + box-shadow: focused-box-shadow(); } } &-enter-btn { @@ -204,6 +184,7 @@ padding-left: $base-padding-h; height: 2em; &-key-file { + border-radius: var(--block-border-radius); .open--file:not(.open--opening) & { cursor: pointer; } @@ -235,7 +216,7 @@ } &:focus { .open--show-focus & { - outline: focused-outline(); + box-shadow: focused-box-shadow(); } } } @@ -265,10 +246,11 @@ } &-img { fill: var(--muted-color); - width: 2em; + width: 1em; position: relative; - top: -0.25em; - margin-right: $base-padding-h; + top: -0.22em; + font-size: 1.5em; + margin-right: $tiny-spacing; cursor: pointer; &:hover { fill: var(--text-color); @@ -297,20 +279,16 @@ } color: var(--muted-color); padding: $base-padding; + border-radius: var(--block-border-radius); &:focus { .open--show-focus & { - outline: focused-outline(); + box-shadow: focused-box-shadow(); } } &-icon { width: 2em; - &--svg > svg { - vertical-align: middle; - @include size(1em); - path { - fill: var(--muted-color); - } - } + position: relative; + top: 0.1em; } &-text { flex-grow: 1; @@ -373,7 +351,7 @@ &__file { cursor: pointer; padding: $base-padding; - border-radius: $base-border-radius; + border-radius: var(--block-border-radius); box-sizing: border-box; flex-basis: 100%; @include nomobile { @@ -410,6 +388,7 @@ &__item { padding: $base-padding; cursor: pointer; + border-radius: var(--block-border-radius); &:hover { background-color: var(--action-background-color-focus-tr); } diff --git a/app/styles/areas/_settings.scss b/app/styles/areas/_settings.scss index 6a757a1c..865cef15 100644 --- a/app/styles/areas/_settings.scss +++ b/app/styles/areas/_settings.scss @@ -10,7 +10,7 @@ position: relative; &__content { - margin: $base-padding; + margin: $medium-padding; } > .scroller { @@ -58,6 +58,12 @@ &-post { display: none; } + &-post { + margin-left: $tiny-spacing; + margin-right: $small-spacing; + position: relative; + top: 0.2em; + } cursor: pointer; @include mobile { line-height: $mobile-back-button-height; @@ -65,9 +71,11 @@ padding: $base-padding; font-size: 1.2em; > i { - margin-right: 0.3em; + margin-right: 0.7em; font-size: 1.2em; - vertical-align: text-bottom; + vertical-align: middle; + position: relative; + top: -0.15em; } &-pre { display: inline; @@ -130,31 +138,31 @@ display: none; } - &__file-master-pass-warning, - &__file-confirm-master-pass-warning { - font-weight: normal; - float: right; - display: none; - } + &__file { + &-master-pass-warning, + &-confirm-master-pass-warning { + font-weight: normal; + float: right; + display: none; + } - &__file-save-to { - cursor: pointer; - display: inline-block; - margin-right: $base-padding-h; - text-align: center; - > i, - > svg { - display: block; - font-size: 3em; - padding: $base-padding-px; - margin: auto; - } - > svg { - @include size(1em); - } - &:hover { - transition: color $base-duration $base-timing; - color: var(--medium-color); + &-save-to { + cursor: pointer; + display: inline-block; + margin-right: $base-padding-h; + text-align: center; + + > i { + display: block; + font-size: 3em; + padding: $base-padding-px; + margin: auto; + } + + &:hover { + transition: color $base-duration $base-timing; + color: var(--medium-color); + } } } @@ -174,6 +182,49 @@ &__general-prv-logout { margin-bottom: $base-padding-v; } + &__general-themes { + width: calc(100% - 10em); + display: grid; + grid-template-columns: repeat(auto-fill, minmax(10em, 1fr)); + grid-gap: $base-spacing; + margin-bottom: $base-spacing; + @include mobile { + width: 100%; + } + } + &__general-theme { + padding: $base-padding; + border: light-border(); + border-radius: var(--input-border-radius); + text-align: center; + background: var(--background-color); + color: var(--text-color); + cursor: pointer; + body & { + --focus-shadow-spread: unset; + --form-box-shadow-color-focus: unset; + --form-box-shadow-color-hover: unset; + } + &-name { + border-bottom: light-border(); + padding-bottom: $tiny-spacing; + margin-bottom: $small-spacing; + } + &-button { + margin-bottom: $base-padding-v; + } + &-plugins-icon { + font-size: 3em; + } + &:hover { + box-shadow: form-box-shadow-hover(); + } + &--selected, + &--selected:hover { + border: 1px solid var(--action-color); + box-shadow: form-box-shadow-focus(); + } + } &__logs { user-select: text; margin-top: $base-padding-v; @@ -181,17 +232,6 @@ &-log { margin: 0; white-space: pre-wrap; - &--debug { - opacity: 0.8; - } - &--info { - } - &--warn { - color: $yellow; - } - &--error { - color: $red; - } } } &__plugins { @@ -293,6 +333,8 @@ } &__head-icon { + position: relative; + top: 0.1em; margin-right: 0.2em; } } diff --git a/app/styles/areas/_tag.scss b/app/styles/areas/_tag.scss index 6edd55d8..50daea48 100644 --- a/app/styles/areas/_tag.scss +++ b/app/styles/areas/_tag.scss @@ -6,7 +6,7 @@ justify-content: flex-start; width: 100%; user-select: none; - padding: $base-padding; + padding: $medium-padding; &__space { flex: 1; diff --git a/app/styles/base/_base.scss b/app/styles/base/_base.scss index ab487a81..12162e25 100644 --- a/app/styles/base/_base.scss +++ b/app/styles/base/_base.scss @@ -6,7 +6,9 @@ @import 'override-mixins'; @import 'colors'; @import 'variables'; +@import 'properties'; @import 'media'; +@import 'icon-font'; @import 'body'; @import 'grid-settings'; @import 'buttons'; diff --git a/app/styles/base/_buttons.scss b/app/styles/base/_buttons.scss index 6dcab6eb..ec742112 100644 --- a/app/styles/base/_buttons.scss +++ b/app/styles/base/_buttons.scss @@ -72,26 +72,56 @@ } } +.btn ~ .btn, +button ~ button { + margin-left: $small-spacing; +} + @mixin icon-btn($error: false) { - @include area-selectable(bottom); - padding: 0.7em 0.6em 0; - height: 1.6em; + @include area-selectable(); + padding: $medium-padding; + text-align: center; + border-radius: var(--block-border-radius); + transition: color $base-duration $base-timing; > i { - display: block; + vertical-align: middle; } @if $error { + color: var(--muted-color); &:hover { - border-color: var(--error-color); + color: var(--error-color); } } } -.svg-btn { - svg path { - transition: fill $base-duration $base-timing; - fill: var(--text-color); +@mixin bg-btn() { + user-select: none; + cursor: pointer; + -webkit-app-region: no-drag; + padding: $medium-padding; + text-align: center; + overflow: hidden; + text-overflow: ellipsis; + border-radius: var(--block-border-radius); + background-color: var(--intermediate-background-color); + transition: color $base-duration $base-timing; + > i { + margin-right: 0.4em; + line-height: inherit; + vertical-align: bottom; } - &:hover svg path { - fill: var(--medium-color); + &:hover { + background-color: var(--intermediate-background-color); + color: var(--medium-color); + transform: background-color $base-duration $base-timing; + } + &:active { + background-color: var(--intermediate-pressed-background-color); + color: var(--text-color); } } + +@mixin bg-btn-active() { + color: var(--selected-item-text-color); + background-color: var(--selected-item-color); +} diff --git a/app/styles/base/_colors.scss b/app/styles/base/_colors.scss index b4b9e514..b6eaa41d 100644 --- a/app/styles/base/_colors.scss +++ b/app/styles/base/_colors.scss @@ -1,26 +1,34 @@ -$black: #111; -$white: #d8e5f1; -$red: #df3c06; -$orange: #fbac45; -$yellow: #e9d92a; -$green: #0dc94b; -$blue: #4e6af8; -$violet: #d946db; - -$all-colors: ( - 'white': $white, - 'black': $black, - 'red': $red, - 'orange': $orange, - 'yellow': $yellow, - 'green': $green, - 'blue': $blue, - 'violet': $violet +$dark-colors: ( + white-color: #d8e5f1, + black-color: #111, + red-color: #ed5f5e, + orange-color: #e8873a, + yellow-color: #f7c644, + green-color: #78b756, + blue-color: #2f7cf7, + violet-color: #e55d9c ); -@each $col, $val in $all-colors { - .#{$col}-color { - color: #{$val}; +$light-colors: ( + white-color: #d8e5f1, + black-color: #111, + red-color: #d04745, + orange-color: #e9873a, + yellow-color: #f7c84e, + green-color: #79b656, + blue-color: #2f7cf7, + violet-color: #e55d9c +); + +body { + @each $name, $value in $dark-colors { + --#{$name}: #{$value}; + } +} + +@each $name in map-keys($dark-colors) { + .#{$name} { + color: var(--#{$name}); } } .muted-color { diff --git a/app/styles/base/_forms.scss b/app/styles/base/_forms.scss index d21f3e8a..09d94bbf 100644 --- a/app/styles/base/_forms.scss +++ b/app/styles/base/_forms.scss @@ -72,7 +72,7 @@ input:not([type]) { } &:focus { - border-color: var(--form-box-border-focus); + border-color: var(--form-box-border-color-focus); box-shadow: form-box-shadow-focus(); outline: none; } @@ -152,7 +152,7 @@ select { border-color: var(--accent-border-color); } &:focus { - border-color: var(--form-box-border-focus); + border-color: var(--form-box-border-color-focus); box-shadow: form-box-shadow-focus(); outline: none; } @@ -185,6 +185,10 @@ input[type='checkbox'] { display: inline-block; width: 1.3em; color: var(--text-color); + font-size: 1.2em; + vertical-align: bottom; + position: relative; + top: 0.08em; } &:checked + label:before { content: $fa-var-check-square-o; diff --git a/app/styles/base/_icon-font.scss b/app/styles/base/_icon-font.scss new file mode 100644 index 00000000..5d42ee6d --- /dev/null +++ b/app/styles/base/_icon-font.scss @@ -0,0 +1,191 @@ +@font-face { + font-family: 'Font Awesome 5 Free'; + font-style: normal; + font-weight: 400; + src: url('fontawesome.woff2') format('woff2'); +} + +@mixin fa-icon { + font-family: 'Font Awesome 5 Free'; + font-weight: 400; + font-style: normal; + text-rendering: auto; + -webkit-font-smoothing: antialiased; + display: inline-block; + line-height: $base-line-height; +} + +.fa { + @include fa-icon; +} + +$fa-glyph-counter: 61440; // 0xf000 + +@function next-fa-glyph() { + $fa-glyph-counter: $fa-glyph-counter + 1 !global; + + $lo-part: $fa-glyph-counter % 256; + $hi-part: ($fa-glyph-counter - $lo-part) / 256; + $hex-num-str: str-slice(#{rgb($hi-part, $lo-part, 1)}, 2, 5); + + $glyph: unquote('"\\#{$hex-num-str}"'); + + @return $glyph; +} + +// position fixes for icons that need to be shifted because they're special + +.fa-keyboard { + @include position(relative, 0.1em null null null); +} + +// icons listed below will be automatically added to the generated icon font, see build/loaders/fontawesome-loader.js +// if the icon has "-o" suffix, it will be used from the "regular" font, otherwise from "solid" or "brands" +// -o is used because it's similar to an empty bullet and this used to be a convention in fontawesome 4 + +$fa-var-square: next-fa-glyph(); +$fa-var-square-o: next-fa-glyph(); +$fa-var-check-square-o: next-fa-glyph(); +$fa-var-bookmark: next-fa-glyph(); +$fa-var-bookmark-o: next-fa-glyph(); +$fa-var-eye: next-fa-glyph(); +$fa-var-eye-slash: next-fa-glyph(); +$fa-var-bolt: next-fa-glyph(); +$fa-var-unlock: next-fa-glyph(); +$fa-var-lock: next-fa-glyph(); +$fa-var-check: next-fa-glyph(); +$fa-var-times: next-fa-glyph(); +$fa-var-folder: next-fa-glyph(); +$fa-var-folder-open: next-fa-glyph(); +$fa-var-ban: next-fa-glyph(); +$fa-var-dropbox: next-fa-glyph(); +$fa-var-google-drive: next-fa-glyph(); +$fa-var-plus: next-fa-glyph(); +$fa-var-ellipsis-h: next-fa-glyph(); +$fa-var-ellipsis-v: next-fa-glyph(); +$fa-var-magic: next-fa-glyph(); +$fa-var-cog: next-fa-glyph(); +$fa-var-server: next-fa-glyph(); +$fa-var-file-alt: next-fa-glyph(); +$fa-var-file-alt-o: next-fa-glyph(); +$fa-var-file-code: next-fa-glyph(); +$fa-var-file-pdf: next-fa-glyph(); +$fa-var-file-archive: next-fa-glyph(); +$fa-var-file-word: next-fa-glyph(); +$fa-var-file-excel: next-fa-glyph(); +$fa-var-file-powerpoint: next-fa-glyph(); +$fa-var-file-image: next-fa-glyph(); +$fa-var-file-video: next-fa-glyph(); +$fa-var-file-audio: next-fa-glyph(); +$fa-var-onedrive: next-fa-glyph(); +$fa-var-question: next-fa-glyph(); +$fa-var-sign-out-alt: next-fa-glyph(); +$fa-var-sync-alt: next-fa-glyph(); +$fa-var-level-down-alt: next-fa-glyph(); +$fa-var-tag: next-fa-glyph(); +$fa-var-tags: next-fa-glyph(); +$fa-var-th-large: next-fa-glyph(); +$fa-var-trash: next-fa-glyph(); +$fa-var-trash-alt: next-fa-glyph(); +$fa-var-keyboard: next-fa-glyph(); +$fa-var-puzzle-piece: next-fa-glyph(); +$fa-var-usb: next-fa-glyph(); +$fa-var-info: next-fa-glyph(); +$fa-var-info-circle: next-fa-glyph(); +$fa-var-key: next-fa-glyph(); +$fa-var-globe: next-fa-glyph(); +$fa-var-exclamation-triangle: next-fa-glyph(); +$fa-var-thumbtack: next-fa-glyph(); +$fa-var-comments: next-fa-glyph(); +$fa-var-edit: next-fa-glyph(); +$fa-var-plug: next-fa-glyph(); +$fa-var-newspaper: next-fa-glyph(); +$fa-var-paperclip: next-fa-glyph(); +$fa-var-camera: next-fa-glyph(); +$fa-var-wifi: next-fa-glyph(); +$fa-var-link: next-fa-glyph(); +$fa-var-battery-three-quarters: next-fa-glyph(); +$fa-var-bars: next-fa-glyph(); +$fa-var-barcode: next-fa-glyph(); +$fa-var-certificate: next-fa-glyph(); +$fa-var-signature: next-fa-glyph(); +$fa-var-bullseye: next-fa-glyph(); +$fa-var-desktop: next-fa-glyph(); +$fa-var-envelope: next-fa-glyph(); +$fa-var-clipboard: next-fa-glyph(); +$fa-var-paper-plane: next-fa-glyph(); +$fa-var-address-card: next-fa-glyph(); +$fa-var-inbox: next-fa-glyph(); +$fa-var-save: next-fa-glyph(); +$fa-var-hdd: next-fa-glyph(); +$fa-var-dot-circle: next-fa-glyph(); +$fa-var-user-lock: next-fa-glyph(); +$fa-var-terminal: next-fa-glyph(); +$fa-var-print: next-fa-glyph(); +$fa-var-project-diagram: next-fa-glyph(); +$fa-var-flag-checkered: next-fa-glyph(); +$fa-var-wrench: next-fa-glyph(); +$fa-var-laptop: next-fa-glyph(); +$fa-var-archive: next-fa-glyph(); +$fa-var-credit-card: next-fa-glyph(); +$fa-var-windows: next-fa-glyph(); +$fa-var-clock: next-fa-glyph(); +$fa-var-search: next-fa-glyph(); +$fa-var-flask: next-fa-glyph(); +$fa-var-gamepad: next-fa-glyph(); +$fa-var-sticky-note: next-fa-glyph(); +$fa-var-sticky-note-o: next-fa-glyph(); +$fa-var-question-circle: next-fa-glyph(); +$fa-var-cube: next-fa-glyph(); +$fa-var-folder-o: next-fa-glyph(); +$fa-var-folder-open-o: next-fa-glyph(); +$fa-var-database: next-fa-glyph(); +$fa-var-unlock-alt: next-fa-glyph(); +$fa-var-pencil-alt: next-fa-glyph(); +$fa-var-image: next-fa-glyph(); +$fa-var-book: next-fa-glyph(); +$fa-var-list-alt: next-fa-glyph(); +$fa-var-user-secret: next-fa-glyph(); +$fa-var-utensils: next-fa-glyph(); +$fa-var-home: next-fa-glyph(); +$fa-var-star: next-fa-glyph(); +$fa-var-linux: next-fa-glyph(); +$fa-var-map-pin: next-fa-glyph(); +$fa-var-apple: next-fa-glyph(); +$fa-var-wikipedia-w: next-fa-glyph(); +$fa-var-dollar-sign: next-fa-glyph(); +$fa-var-mobile: next-fa-glyph(); +$fa-var-spinner: next-fa-glyph(); +$fa-var-minus-circle: next-fa-glyph(); +$fa-var-keeweb: next-fa-glyph(); +$fa-var-copy: next-fa-glyph(); +$fa-var-clone: next-fa-glyph(); +$fa-var-chevron-down: next-fa-glyph(); +$fa-var-chevron-left: next-fa-glyph(); +$fa-var-qrcode: next-fa-glyph(); +$fa-var-sort-alpha-down: next-fa-glyph(); +$fa-var-sort-alpha-down-alt: next-fa-glyph(); +$fa-var-sort-numeric-down: next-fa-glyph(); +$fa-var-sort-numeric-down-alt: next-fa-glyph(); +$fa-var-sort-amount-down: next-fa-glyph(); +$fa-var-language: next-fa-glyph(); +$fa-var-circle: next-fa-glyph(); +$fa-var-circle-o: next-fa-glyph(); +$fa-var-arrow-circle-left: next-fa-glyph(); +$fa-var-cloud-download-alt: next-fa-glyph(); +$fa-var-caret-down: next-fa-glyph(); +$fa-var-long-arrow-alt-left: next-fa-glyph(); +$fa-var-long-arrow-alt-right: next-fa-glyph(); +$fa-var-github-alt: next-fa-glyph(); +$fa-var-code: next-fa-glyph(); +$fa-var-html5: next-fa-glyph(); +$fa-var-chrome: next-fa-glyph(); +$fa-var-firefox-browser: next-fa-glyph(); +$fa-var-safari: next-fa-glyph(); +$fa-var-opera: next-fa-glyph(); +$fa-var-edge: next-fa-glyph(); +$fa-var-twitter: next-fa-glyph(); +$fa-var-paint-brush: next-fa-glyph(); +$fa-var-at: next-fa-glyph(); +$fa-var-usb-token: next-fa-glyph(); +$fa-var-bell: next-fa-glyph(); diff --git a/app/styles/base/_properties.scss b/app/styles/base/_properties.scss new file mode 100644 index 00000000..196223ec --- /dev/null +++ b/app/styles/base/_properties.scss @@ -0,0 +1,9 @@ +body { + --focus-shadow-spread: 3px; + --button-border-radius: 3px; + --input-border-radius: 4px; + --block-border-radius: 5px; + --selected-item-text-color: var(--text-color); + --open-icon-color: var(--text-color); + --dropdown-box-shadow-color: rgba(0, 0, 0, 0.1); +} diff --git a/app/styles/base/_theme-vars.scss b/app/styles/base/_theme-vars.scss index 1e47ae73..6889543c 100644 --- a/app/styles/base/_theme-vars.scss +++ b/app/styles/base/_theme-vars.scss @@ -1,26 +1,57 @@ -/* prettier-ignore */ /* This file is also parsed in JS to build theme styles, see theme-vars.js */ @function set-theme-vars($t) { @return map-merge( - $t, ( - muted-color: mix(map-get($t, medium-color), map-get($t, background-color), map-get($t, mute-percent)), - muted-color-border: mix(map-get($t, medium-color), map-get($t, background-color), semi-mute-percent(map-get($t, mute-percent))), + muted-color: + mix( + map-get($t, medium-color), + map-get($t, background-color), + map-get($t, mute-percent) + ), + muted-color-border: + mix( + map-get($t, medium-color), + map-get($t, background-color), + semi-mute-percent(map-get($t, mute-percent)) + ), text-selection-bg-color: rgba(map-get($t, action-color), 0.3), text-selection-bg-color-error: rgba(map-get($t, error-color), 0.8), text-semi-muted-color: mix(map-get($t, action-color), map-get($t, text-color), 0.5), - text-contrast-action-color: text-contrast-color(map-get($t, action-color), map-get($t, color-lightness-shift), map-get($t, background-color), map-get($t, text-color)), - text-contrast-error-color: text-contrast-color(map-get($t, error-color), map-get($t, color-lightness-shift), map-get($t, background-color), map-get($t, text-color)), + text-contrast-action-color: + text-contrast-color( + map-get($t, action-color), + map-get($t, color-lightness-shift), + map-get($t, background-color), + map-get($t, text-color) + ), + text-contrast-error-color: + text-contrast-color( + map-get($t, error-color), + map-get($t, color-lightness-shift), + map-get($t, background-color), + map-get($t, text-color) + ), base-border-color: mix(map-get($t, medium-color), map-get($t, background-color), 50%), accent-border-color: mix(map-get($t, medium-color), map-get($t, background-color), 65%), - light-border-color: mix(map-get($t, medium-color), map-get($t, background-color), map-get($t, light-border-percent)), - form-box-border-focus: map-get($t, action-color), - form-box-shadow-color-focus: lightness-alpha(map-get($t, action-color), -5%, -0.3), + light-border-color: + mix( + map-get($t, medium-color), + map-get($t, background-color), + map-get($t, light-border-percent) + ), + form-box-border-color-focus: + mix(map-get($t, action-color), map-get($t, text-color), 70%), + form-box-shadow-color-focus: + rgba(mix(map-get($t, action-color), map-get($t, text-color), 70%), 0.7), + form-box-shadow-color-hover: + rgba(mix(map-get($t, action-color), map-get($t, text-color), 70%), 0.5), form-box-shadow-color-focus-error: lightness-alpha(map-get($t, error-color), -5%, -0.3), - dropdown-box-shadow-color: rgba(map-get($t, medium-color), 0.05), - secondary-background-color: mix(map-get($t, medium-color), map-get($t, background-color), 10%), - intermediate-background-color: mix(map-get($t, medium-color), map-get($t, background-color), 3%), - intermediate-pressed-background-color: mix(map-get($t, medium-color), map-get($t, background-color), 2.6%), + secondary-background-color: + mix(map-get($t, medium-color), map-get($t, background-color), 10%), + intermediate-background-color: + mix(map-get($t, medium-color), map-get($t, background-color), 3%), + intermediate-pressed-background-color: + mix(map-get($t, medium-color), map-get($t, background-color), 2.6%), disabled-background-color: shade(map-get($t, background-color), 5%), action-background-color-focus: shade(map-get($t, action-color), 20%), action-background-color-focus-tr: rgba(shade(map-get($t, action-color), 20%), 0.1), @@ -31,7 +62,15 @@ error-background-color-active: shade(map-get($t, error-color), 25%), error-background-color-active-tr: rgba(shade(map-get($t, error-color), 25%), 0.15), modal-background-color: rgba(map-get($t, background-color), map-get($t, modal-opacity)), - modal-background-color-tr: rgba(map-get($t, background-color), 0) - ) + modal-background-color-tr: rgba(map-get($t, background-color), 0), + selected-item-color: mix(map-get($t, action-color), map-get($t, background-color), 85%), + selected-on-secondary-item-color: + mix(map-get($t, medium-color), map-get($t, background-color), 30%), + selectable-on-secondary-item-color: + mix(map-get($t, medium-color), map-get($t, background-color), 14%), + clickable-on-secondary-color: + mix(map-get($t, medium-color), map-get($t, background-color), 75%) + ), + $t ); } diff --git a/app/styles/base/_typography.scss b/app/styles/base/_typography.scss index 823584b8..c1826127 100644 --- a/app/styles/base/_typography.scss +++ b/app/styles/base/_typography.scss @@ -93,11 +93,8 @@ img { max-width: 100%; } -body:not(.edge) * { - // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/12132854/ - &::selection { - background-color: var(--text-selection-bg-color); - } +::selection { + background-color: var(--text-selection-bg-color); } code { diff --git a/app/styles/base/_variables.scss b/app/styles/base/_variables.scss index fff8ed1b..78543097 100644 --- a/app/styles/base/_variables.scss +++ b/app/styles/base/_variables.scss @@ -7,16 +7,17 @@ $monospace-font-family: 'SFMono-Regular', Monaco, Consolas, 'Lucida Console', mo // Font Sizes $base-font-size: 12px; -$large-pass-font-size: 22px; +$large-pass-font-size: 2em; // Line height $base-line-height: 1.5; $heading-line-height: 1.2; // Other Sizes -$base-border-radius: 1px; +$base-border-radius: 2px; $base-spacing: $base-line-height * 1em; $small-spacing: $base-spacing / 2; +$tiny-spacing: $small-spacing / 2; $base-z-index: 0; $base-padding-v: 0.5em; $base-padding-h: 1em; @@ -28,6 +29,7 @@ $medium-padding: $medium-padding-v $medium-padding-h; $base-padding-px: 5px 10px; $modal-icon-size: 6em; $large-padding: 2em; +$titlebar-padding-tiny: 8px; $titlebar-padding-small: 24px; $titlebar-padding-large: 40px; @@ -38,20 +40,8 @@ $titlebar-padding-large: 40px; @function light-border() { @return 1px solid var(--light-border-color); } -@function selected-border-width() { - @return var(--selected-border-width); -} -@function selected-border() { - @return selected-border-width() solid var(--action-color); -} -@function selected-hover-border() { - @return var(--accent-border-width) solid var(--action-color); -} -@function selected-transparent-border() { - @return var(--accent-border-width) solid transparent; -} -@function focused-outline() { - @return 1px solid var(--action-color); +@function focused-box-shadow() { + @return 0 0 0 1px var(--action-color); } // Forms @@ -59,18 +49,19 @@ $titlebar-padding-large: 40px; @return inset 0 1px 3px rgba(0, 0, 0, 0.06); } @function form-box-shadow-focus() { - @return form-box-shadow(), - 0 0 var(--focus-shadow-blur) var(--focus-shadow-spread) var(--form-box-shadow-color-focus); + @return form-box-shadow(), 0 0 0 var(--focus-shadow-spread) var(--form-box-shadow-color-focus); +} +@function form-box-shadow-hover() { + @return form-box-shadow(), 0 0 0 var(--focus-shadow-spread) var(--form-box-shadow-color-hover); } @function form-box-shadow-focus-error() { @return form-box-shadow(), - 0 0 var(--focus-shadow-blur) var(--focus-shadow-spread) - var(--form-box-shadow-color-focus-error); + 0 0 0 var(--focus-shadow-spread) var(--form-box-shadow-color-focus-error); } // Shadows @function dropdown-box-shadow() { - @return 0 0 50px var(--dropdown-box-shadow-color); + @return 0 0.15rem 0.5rem 0.25rem var(--dropdown-box-shadow-color); } // Animations diff --git a/app/styles/common/_dropdown.scss b/app/styles/common/_dropdown.scss index f29c94e3..9d0d5a0e 100644 --- a/app/styles/common/_dropdown.scss +++ b/app/styles/common/_dropdown.scss @@ -1,23 +1,29 @@ .dropdown { position: absolute; z-index: $z-index-no-modal; - border-radius: $base-border-radius; + border-radius: var(--block-border-radius); background: var(--background-color); border: light-border(); box-shadow: dropdown-box-shadow(); + padding: $tiny-spacing; &__item { padding: 8px 12px; cursor: pointer; white-space: nowrap; + border-radius: var(--block-border-radius); &--active, &--active:hover { - @include area-selected(right); + @include area-selected(); } @include nomobile { - @include area-selectable(right); + @include area-selectable(); + &:hover { + color: var(--selected-item-text-color); + background-color: var(--action-color); + } &--active, &--active:hover { - @include area-selected(right); + @include area-selected(); } } &-icon { diff --git a/app/styles/common/_fx.scss b/app/styles/common/_fx.scss index 2b4fa531..f8e82020 100644 --- a/app/styles/common/_fx.scss +++ b/app/styles/common/_fx.scss @@ -25,6 +25,24 @@ animation: shake 50s cubic-bezier(0.36, 0.07, 0.19, 0.97) 0s; } +.rotate-90, +.fa.rotate-90:before { + transform: rotate(90deg); +} + +.spin { + animation: spin 2s linear infinite; +} + +@keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + @keyframes flip3d { from { transform: rotateY(0); diff --git a/app/styles/common/_icon-select.scss b/app/styles/common/_icon-select.scss index 6fc1376b..8b70564f 100644 --- a/app/styles/common/_icon-select.scss +++ b/app/styles/common/_icon-select.scss @@ -13,13 +13,12 @@ } } &__icon { - @include area-selectable(bottom); + @include icon-btn(); + font-size: 1.6em; width: 26px; - text-align: center; - font-size: 20px; - padding: 10px; + padding: $base-padding-v; &.icon-select__icon--active { - @include area-selected(bottom); + @include area-selected(); } &-btn { padding: 5px 10px; @@ -33,6 +32,9 @@ display: none; } } + &--progress > i:before { + content: $fa-var-spinner; + } &--download-error > i:before { content: $fa-var-ban; } diff --git a/app/styles/common/_modal.scss b/app/styles/common/_modal.scss index 941382da..20942dee 100644 --- a/app/styles/common/_modal.scss +++ b/app/styles/common/_modal.scss @@ -30,11 +30,6 @@ &__icon { font-size: $modal-icon-size; text-align: center; - &--svg { - fill: var(--text-color); - width: 1.4em; - align-self: center; - } } &__header { user-select: text; @@ -48,9 +43,6 @@ } &__buttons { text-align: right; - button ~ button { - margin-left: $small-spacing; - } > button { margin-bottom: $small-spacing; } diff --git a/app/styles/common/_scroll.scss b/app/styles/common/_scroll.scss index f7bf2901..f0e36744 100644 --- a/app/styles/common/_scroll.scss +++ b/app/styles/common/_scroll.scss @@ -10,13 +10,13 @@ z-index: 3; top: 10px; bottom: 10px; - right: 5px; - width: 8px; + right: 1px; + width: 7px; } &__bar { position: absolute; z-index: 1; - width: 8px; + width: 7px; border-radius: 3px; pointer-events: auto; -webkit-app-region: no-drag; diff --git a/app/styles/common/_tip.scss b/app/styles/common/_tip.scss index 6b64052d..2a26edde 100644 --- a/app/styles/common/_tip.scss +++ b/app/styles/common/_tip.scss @@ -1,7 +1,6 @@ .tip { position: absolute; padding: $base-padding; - border-radius: $base-border-radius; white-space: nowrap; z-index: $z-index-no-modal; pointer-events: none; diff --git a/app/styles/main.scss b/app/styles/main.scss index 67e6c266..bf00cae9 100644 --- a/app/styles/main.scss +++ b/app/styles/main.scss @@ -1,7 +1,6 @@ $fa-font-path: '~font-awesome/fonts'; @import '~normalize.css/normalize'; -@import '~font-awesome/scss/font-awesome'; @import '~pikaday/scss/pikaday'; @import '~bourbon'; diff --git a/app/styles/themes/_all-themes.scss b/app/styles/themes/_all-themes.scss index 4b0624af..6ea2009d 100644 --- a/app/styles/themes/_all-themes.scss +++ b/app/styles/themes/_all-themes.scss @@ -1,14 +1,14 @@ $themes: (); @import 'theme-defaults'; +@import 'dark'; +@import 'light'; @import 'dark-brown'; @import 'flat-blue'; -@import 'white'; @import 'terminal'; @import 'high-contrast'; @import 'solarized-dark'; @import 'solarized-light'; -@import 'macos-dark'; @each $theme-name, $theme-vars in $themes { $theme-vars: set-theme-vars($theme-vars); diff --git a/app/styles/themes/_dark.scss b/app/styles/themes/_dark.scss new file mode 100644 index 00000000..e60e4289 --- /dev/null +++ b/app/styles/themes/_dark.scss @@ -0,0 +1,26 @@ +$themes: map-merge( + $themes, + ( + dark: + map-merge( + $theme-defaults, + ( + background-color: #1e1e1e, + medium-color: #b7b7b8, + text-color: #f7f7f7, + action-color: #317ef6, + error-color: #ec655a + ) + ) + ) +); + +body.th-dark { + --form-box-border-color-focus: #407091; + --form-box-shadow-color-focus: #3a698b; + --form-box-shadow-color-hover: rgba(58, 105, 139, 0.8); + --light-border-color: rgb(68, 68, 69, 0.7); + --secondary-background-color: #2d2d2e; + --selected-item-color: #2463c8; + --selected-on-secondary-item-color: #403f40; +} diff --git a/app/styles/themes/_high-contrast.scss b/app/styles/themes/_high-contrast.scss index 46ffafb9..a75c429e 100644 --- a/app/styles/themes/_high-contrast.scss +++ b/app/styles/themes/_high-contrast.scss @@ -4,16 +4,24 @@ $themes: map-merge( hc: map-merge( $theme-defaults, - ( - background-color: #fafafa, - medium-color: #050505, - text-color: #050505, - action-color: #2d72d7, - error-color: #e74859, - mute-percent: 60%, - light-border-percent: 50%, - modal-opacity: 1 + map-merge( + $light-colors, + ( + background-color: #fafafa, + medium-color: #050505, + text-color: #050505, + action-color: #1e5db8, + error-color: #e74859, + mute-percent: 60%, + light-border-percent: 50%, + modal-opacity: 1 + ) ) ) ) ); + +body.th-hc { + --selected-item-color: #1e5db8; + --selected-item-text-color: #fafafa; +} diff --git a/app/styles/themes/_light.scss b/app/styles/themes/_light.scss new file mode 100644 index 00000000..59be93e5 --- /dev/null +++ b/app/styles/themes/_light.scss @@ -0,0 +1,32 @@ +$themes: map-merge( + $themes, + ( + light: + map-merge( + $theme-defaults, + map-merge( + $light-colors, + ( + background-color: #f6f6f6, + medium-color: #4c4c4c, + text-color: #303030, + action-color: #3063d4, + error-color: #ec655a, + mute-percent: 50% + ) + ) + ) + ) +); + +body.th-light { + --form-box-border-color-focus: #8aacec; + --form-box-shadow-color-focus: #90b2f2; + --form-box-shadow-color-hover: rgb(144, 178, 242, 0.8); + --light-border-color: #dedede; + --secondary-background-color: #efefef; + --selected-item-color: #2366d9; + --selected-on-secondary-item-color: #d6d6d6; + --selected-item-text-color: #f6f6f6; + --open-icon-color: var(--muted-color); +} diff --git a/app/styles/themes/_macos-dark.scss b/app/styles/themes/_macos-dark.scss deleted file mode 100644 index c09841c0..00000000 --- a/app/styles/themes/_macos-dark.scss +++ /dev/null @@ -1,98 +0,0 @@ -$themes: map-merge( - $themes, - ( - macdark: - map-merge( - $theme-defaults, - ( - background-color: #1f1f20, - medium-color: #b7b7b8, - text-color: #f7f7f7, - action-color: #3063d4, - error-color: #ec655a, - focus-shadow-blur: 0, - focus-shadow-spread: 3px, - button-border-radius: 2px, - input-border-radius: 3px, - selected-border-width: 0, - accent-border-width: 0 - ) - ) - ) -); - -body.th-macdark { - --light-border-color: rgb(68, 68, 69, 0.7); - --secondary-background-color: #2d2d2e; - --form-box-border-focus: #6697c0; - --form-box-shadow-color-focus: #44749d; - --selected-item-color: #2457c9; - .app__menu { - background-color: var(--secondary-background-color); - .menu__item.menu__item--active > .menu__item-body, - .menu__item.menu__item--active.menu__item--hover > .menu__item-body { - background-color: #515152; - } - .menu__item-edit, - .menu__item-empty-trash { - right: 0.5em; - } - .menu__item-edit:not(:hover), - .menu__item-empty-trash:not(:hover) { - color: #8f8f91; - } - } - .app__footer { - background-color: var(--secondary-background-color); - } - .at-select__table .at-select__item { - border-right-width: 0; - } - .at-select__table .at-select__item.at-select__item--active { - background-color: var(--selected-item-color); - } - .at-select__item--active .at-select__item-options:hover { - background: var(--secondary-background-color); - } - .dropdown__item--active, - .dropdown__item--active:hover { - background-color: var(--selected-item-color); - } - @include nomobile { - .list__item--active, - .list__item--active:hover { - background-color: var(--selected-item-color); - .list__item-descr { - color: #8f8f91; - } - } - input, - textarea, - select { - background-color: rgba(108, 108, 109, 0.025); - } - input:focus, - textarea:focus, - select:focus { - background-color: rgba(108, 108, 109, 0.05); - } - } - .yellow-color { - color: #e0c24c; - } - .red-color { - color: #ec655a; - } - .orange-color { - color: #e78f42; - } - .green-color { - color: #71bf47; - } - .blue-color { - color: #3062d1; - } - .violet-color { - color: #e55c9c; - } -} diff --git a/app/styles/themes/_solarized-dark.scss b/app/styles/themes/_solarized-dark.scss index 8325e2c4..8bce65a2 100644 --- a/app/styles/themes/_solarized-dark.scss +++ b/app/styles/themes/_solarized-dark.scss @@ -4,15 +4,22 @@ $themes: map-merge( sd: map-merge( $theme-defaults, - ( - background-color: #002b36, - medium-color: #93a1a1, - text-color: #839496, - action-color: #859900, - error-color: #dc322f, - mute-percent: 60%, - color-lightness-shift: 50% + map-merge( + $light-colors, + ( + background-color: #002b36, + medium-color: #93a1a1, + text-color: #839496, + action-color: #859900, + error-color: #dc322f, + mute-percent: 60%, + color-lightness-shift: 50% + ) ) ) ) ); + +body.th-sd { + --selected-item-text-color: #002b36; +} diff --git a/app/styles/themes/_solarized-light.scss b/app/styles/themes/_solarized-light.scss index 51f34bd6..2319886e 100644 --- a/app/styles/themes/_solarized-light.scss +++ b/app/styles/themes/_solarized-light.scss @@ -15,3 +15,7 @@ $themes: map-merge( ) ) ); + +body.th-sl { + --selected-item-text-color: #fdf6e3; +} diff --git a/app/styles/themes/_terminal.scss b/app/styles/themes/_terminal.scss index 5e6f9b86..e9aec619 100644 --- a/app/styles/themes/_terminal.scss +++ b/app/styles/themes/_terminal.scss @@ -8,7 +8,7 @@ $themes: map-merge( background-color: #222, medium-color: #999, text-color: #eee, - action-color: #22d66d, + action-color: #13a453, error-color: #c34034 ) ) diff --git a/app/styles/themes/_theme-defaults.scss b/app/styles/themes/_theme-defaults.scss index eb056701..e45ed3c3 100644 --- a/app/styles/themes/_theme-defaults.scss +++ b/app/styles/themes/_theme-defaults.scss @@ -1,12 +1,6 @@ $theme-defaults: ( mute-percent: 30%, - light-border-percent: 10%, + light-border-percent: 20%, modal-opacity: 0.9, - color-lightness-shift: 0, - focus-shadow-blur: 3px, - focus-shadow-spread: 0, - button-border-radius: 1px, - input-border-radius: 1px, - selected-border-width: 3px, - accent-border-width: 1px + color-lightness-shift: 0 ); diff --git a/app/styles/themes/_white.scss b/app/styles/themes/_white.scss deleted file mode 100644 index d178706f..00000000 --- a/app/styles/themes/_white.scss +++ /dev/null @@ -1,16 +0,0 @@ -$themes: map-merge( - $themes, - ( - wh: - map-merge( - $theme-defaults, - ( - background-color: #fafafa, - medium-color: #050505, - text-color: #424243, - action-color: #475fd7, - error-color: #e75675 - ) - ) - ) -); diff --git a/app/styles/utils/_auto-type-hint.scss b/app/styles/utils/_auto-type-hint.scss index 14bb337b..fd746a6c 100644 --- a/app/styles/utils/_auto-type-hint.scss +++ b/app/styles/utils/_auto-type-hint.scss @@ -2,7 +2,7 @@ @include common-dropdown; position: absolute; z-index: $z-index-no-modal; - border-radius: $base-border-radius; + border-radius: var(--block-border-radius); padding: $base-padding; box-sizing: border-box; overflow: hidden; diff --git a/app/styles/utils/_back-button.scss b/app/styles/utils/_back-button.scss index 4d0a1c36..7825222b 100644 --- a/app/styles/utils/_back-button.scss +++ b/app/styles/utils/_back-button.scss @@ -5,4 +5,10 @@ right: $base-padding-h; padding: $base-padding-v * 2 0 1px 0; z-index: 1; + &__post { + margin-left: $tiny-spacing; + margin-right: $small-spacing; + position: relative; + top: 0.2em; + } } diff --git a/app/styles/utils/_common-dropdown.scss b/app/styles/utils/_common-dropdown.scss index c107d5ce..653900df 100644 --- a/app/styles/utils/_common-dropdown.scss +++ b/app/styles/utils/_common-dropdown.scss @@ -4,7 +4,5 @@ background: var(--background-color); border: 1px solid var(--light-border-color); box-shadow: dropdown-box-shadow(); - .edge & { - border-width: 1px !important; - } // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/12132854/ + border-radius: var(--block-border-radius); } diff --git a/app/styles/utils/_selection.scss b/app/styles/utils/_selection.scss index f5a482d7..af769883 100644 --- a/app/styles/utils/_selection.scss +++ b/app/styles/utils/_selection.scss @@ -1,28 +1,38 @@ -@mixin area-selectable($border: false) { +@mixin area-selectable() { cursor: pointer; - border-radius: 0; -webkit-app-region: no-drag; - @if ($border) { - border-#{$border}: selected-transparent-border(); - } &:hover, &.sel--active { background-color: var(--intermediate-background-color); color: var(--medium-color); - @if ($border) { - border-#{$border}: selected-hover-border(); - } } &:active { background-color: var(--intermediate-pressed-background-color); } } -@mixin area-selected($border) { +@mixin area-selectable-on-secondary() { + @include area-selectable(); + &:hover, + &.sel--active { + background-color: var(--selectable-on-secondary-item-color); + } +} + +@mixin area-selected() { cursor: default; -webkit-app-region: no-drag; - border-#{$border}: selected-border(); - background-color: var(--secondary-background-color); + background-color: var(--selected-item-color); + color: var(--selected-item-text-color); + &:hover { + color: var(--selected-item-text-color); + } +} + +@mixin area-selected-on-secondary() { + @include area-selected(); + color: var(--text-color); + background-color: var(--selected-on-secondary-item-color); &:hover { color: var(--text-color); } diff --git a/app/templates/details/details-attachment.hbs b/app/templates/details/details-attachment.hbs index 3a9d8e0b..0f311fd3 100644 --- a/app/templates/details/details-attachment.hbs +++ b/app/templates/details/details-attachment.hbs @@ -1,6 +1,6 @@
- {{res 'detHistoryReturn'}} + {{res 'detHistoryReturn'}}
diff --git a/app/templates/details/details-history.hbs b/app/templates/details/details-history.hbs index b070f482..5fa246ff 100644 --- a/app/templates/details/details-history.hbs +++ b/app/templates/details/details-history.hbs @@ -1,13 +1,13 @@
- {{res 'detHistoryReturn'}} + {{res 'detHistoryReturn'}}
{{res 'detHistoryClickPoint'}}
-
-
+
+
diff --git a/app/templates/details/details.hbs b/app/templates/details/details.hbs index c5d1c2ef..f41b025b 100644 --- a/app/templates/details/details.hbs +++ b/app/templates/details/details.hbs @@ -47,7 +47,7 @@ {{#if deleted~}} {{~else~}} - + {{~/if~}}
diff --git a/app/templates/footer.hbs b/app/templates/footer.hbs index b88985a0..9240532e 100644 --- a/app/templates/footer.hbs +++ b/app/templates/footer.hbs @@ -4,9 +4,9 @@ data-file-id="{{file.id}}" id="footer__db--{{file.id}}"> {{file.name}} {{#if file.syncing~}} - + {{~else if file.syncError~}} - {{~else if file.modified~}} @@ -25,5 +25,5 @@ {{/if}}
- +
diff --git a/app/templates/generator-presets.hbs b/app/templates/generator-presets.hbs index db1fb539..9d803239 100644 --- a/app/templates/generator-presets.hbs +++ b/app/templates/generator-presets.hbs @@ -1,6 +1,6 @@
- {{res 'retToApp'}} + {{res 'retToApp'}}
diff --git a/app/templates/generator.hbs b/app/templates/generator.hbs index c3d7e6b9..97f0742e 100644 --- a/app/templates/generator.hbs +++ b/app/templates/generator.hbs @@ -1,6 +1,6 @@
{{res 'genLen'}}: {{opt.length}} - + {{#unless showToggleButton}}