Merge branch 'develop' into master

This commit is contained in:
antelle 2020-11-29 15:06:59 +01:00
commit 63367998bf
No known key found for this signature in database
GPG Key ID: 63C9777AAB7C563C
184 changed files with 6414 additions and 6518 deletions

View File

@ -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",

View File

@ -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

View File

@ -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

View File

@ -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 }}

View File

@ -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 } },

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 685 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -1 +1 @@
<svg version="1" xmlns="http://www.w3.org/2000/svg" width="922.667" height="922.667" viewBox="0 0 692.000000 692.000000"><path d="M266.9 57.4c-.2.2-4 .6-8.3 1-4.4.4-8.2.8-8.5 1-.3.2-2.7.7-5.3 1-5.5.7-21.6 4.8-29.8 7.5-46.3 15.4-93.3 48.9-126.5 90.1-25.7 31.8-46.8 74.8-52.9 107.4-.5 2.8-1.5 8.3-2.1 12.1-1.9 11.6-2.6 35.9-1.3 45 .3 2.7.9 7 1.2 9.5 3.3 27.4 16.9 55.4 36.1 74.5 12.1 12.1 27.3 22.7 40 27.8 3.9 1.6 7.9 3.3 9 3.7 1.1.5 4.3 1.5 7 2.4 4.4 1.3 7.1 1.9 18.5 4.1 12.1 2.4 42.4 2.4 56.5 0 1.7-.3 4.4-.7 6-1 1.7-.3 4.1-.7 5.5-1 1.4-.3 3.7-.7 5.1-1 4.5-.8 19.1-5.2 28.4-8.6 16.8-6.1 42.9-20.2 59.4-32.2l5.3-3.9 130.7 130.5C567.7 654 571.7 657.8 577.5 660c6.2 2.2 15.3 2.9 21.4 1.6 12.8-2.7 27.8-15.7 34.7-30.1 2.5-5.3 2.9-7.1 2.9-15-.1-15.8 1.1-14.3-49.6-65.1l-44-44 18.1-18.2 18.1-18.1 6.7 7.1c20.7 21.9 32.6 32.8 38.6 35.3 5.6 2.4 11.5-1.2 26.5-15.8 16.1-15.8 21.8-23.8 20-28.6-.9-2.3-4.6-7.8-7.4-10.7-.5-.6-4.1-4.8-8-9.2-7.2-8.3-64.7-65.9-91-91.1-8.2-7.9-16-14.7-17.3-15.2-6.8-2.6-12.6.8-28.4 16.3-13.1 12.9-19.8 21.8-19.8 26.3 0 5.6 13.3 21 37.4 43.4l5.8 5.4-18.1 18.1-18.1 18.1-67-67c-36.9-36.8-67-67.3-67-67.7 0-.4 3-5.1 6.7-10.4 16.1-23.5 30.3-53.8 35.8-76.4.7-2.8 4.3-22.5 5.3-28.5 1.1-6.5.8-41.1-.3-48.5-1.4-9.2-6.4-26.8-10-35.3-9.9-23.3-28.4-44.9-50.5-58.8-21.8-13.8-47.4-20.5-78.6-20.7-7.3-.1-13.3 0-13.5.2zm22 74c12.1 2.8 22.6 7.6 31.1 14.4 13.3 10.7 23.7 27.4 26.3 42.7 6 34.4-9.5 65.3-40.5 80.7-16.1 7.9-40.9 8.5-58.1 1.3-4.4-1.8-5.2-1.9-4-.3 1.1 1.4 4 9.8 4.8 13.9 5.3 25.7-.6 47.7-17.7 66-8.9 9.6-19.7 16.4-31.3 19.8-9.7 2.9-11.7 3.1-22.2 3.2-24.8 0-47.2-12.3-61-33.3-15.4-23.7-15.3-56.4.4-79.3 11.1-16.2 26.6-27.1 44.3-31.1 14.4-3.2 32.9-1.7 43.9 3.7 2 .9 3.7 1.6 3.8 1.5.2-.2-.8-3.4-2.2-7.3-3.4-9.7-3.7-11.4-4-23.3-.4-17.1 3.8-30.7 13.8-44 11.4-15.2 26.5-24.8 44.7-28.4 6.2-1.2 22.9-1.4 27.9-.2z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-64 -64 640 640"><path d="M136.736 512c-4.892-.008-10.322-2.061-15.92-4.997-11.941-6.265-20.13-14.185-13.293-27.218l166.101-316.638c-1.972-1.022-3.619-1.88-4.743-2.469-59.892-31.418-86.463-77.87-64.952-118.877 24.726-47.135 101.304-55.69 171.042-19.107 69.738 36.584 106.227 104.451 81.501 151.586-21.264 40.536-76.784 44.151-132.61 14.866-1.538-.807-4.033-2.103-6.857-3.567L150.768 502.471c-3.632 6.924-8.488 9.538-14.032 9.529zm-30.582-67.835l-3.144-1.649-.7-.366-42.546-22.32v-.001l-10.256-5.38c-1.738-.911-2.407-3.057-1.496-4.794l13.465-25.667c.906-1.728 3.036-2.399 4.768-1.509l.028.013 20.513 10.761c1.736.911 3.883.241 4.794-1.496l13.466-25.667c.911-1.737.24-3.883-1.497-4.794l-10.256-5.382-10.256-5.38c-1.737-.911-2.406-3.057-1.495-4.794l13.464-25.669c.911-1.737 3.058-2.406 4.794-1.494l10.258 5.38 26.697 14.005 16.548 8.681c.007.003.014.007.021.011l3.125 1.639-14.142 26.957-.973 1.855c0 .001-.001.003-.002.004l-20.063 38.245-2.041 3.888-11.424 21.779c-.002.004-.005.009-.008.014zm67.232-128.162l-3.145-1.651-14.938-7.837-11.794-6.187c-1.737-.911-2.408-3.057-1.497-4.794l13.466-25.667c.911-1.737 3.058-2.408 4.794-1.497l11.795 6.188 14.939 7.837c.007.003.014.007.021.01l3.125 1.64-1.651 3.145-6.731 12.834-6.733 12.833c-.003.005-.006.008-.009.013zm219.778-151.914c8.857.016 15.784-3.552 20.386-12.326 14.027-26.737-11.92-67.987-57.953-92.135-46.032-24.148-94.719-22.049-108.746 4.688-14.026 26.737 24.987 44.293 71.02 68.441 30.928 16.224 57.156 31.296 75.293 31.332z"/></svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1007 KiB

After

Width:  |  Height:  |  Size: 1013 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 947 KiB

After

Width:  |  Height:  |  Size: 960 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1020 KiB

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 315 KiB

After

Width:  |  Height:  |  Size: 322 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 405 KiB

After

Width:  |  Height:  |  Size: 408 KiB

View File

@ -19,7 +19,6 @@
"
/>
<meta name="application-name" content="KeeWeb" />
<meta name="kw-signature" content="" />
<meta name="kw-config" content="(no-config)" />
<meta
name="viewport"
@ -72,7 +71,6 @@
/>
<link rel="manifest" href="manifest.json" />
<link rel="stylesheet" href="css/app.css?__inline=true" />
<script src="js/vendor.js?__inline=true"></script>
<script src="js/app.js?__inline=true"></script>
<script src="js/runtime.js?__inline=true"></script>
</head>

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M339 314.9L175.4 32h161.2l163.6 282.9H339zm-137.5 23.6L120.9 480h310.5L512 338.5H201.5zM154.1 67.4L0 338.5 80.6 480 237 208.8 154.1 67.4z"/></svg>

Before

Width:  |  Height:  |  Size: 217 B

View File

@ -1 +0,0 @@
<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" width="256" height="256" version="1.1" viewBox="0 0 256 256"><g transform="translate(296.64282,-100.61434)"><g transform="translate(222.85714,-11.428576)"><g transform="matrix(0.83394139,0,0,0.83394139,-86.101383,10.950635)"><path d="m-419.5 365.94c-18.48-4.62-28.77-19.31-28.81-41.1-0.01-6.97 0.49-10.31 2.23-14.79 4.26-10.99 15.55-19.27 30.41-22.33 7.39-1.52 9.67-3.15 9.67-6.92 0-1.18 0.88-4.71 1.95-7.83 4.88-14.2 13.93-26.03 23.59-30.87 10.11-5.07 15.22-6.21 27.45-6.14 17.38 0.09 26.04 3.86 38.17 16.6l6.67 7 5.97-2.07c28.91-10.01 57.73 7.03 60.06 35.49l0.64 7.79 5.69 2.04c16.26 5.83 23.9 18.06 22.52 36.04-0.91 11.76-6.4 21.15-15.11 25.81l-4.09 2.19-91 0.18c-69.93 0.13-92.16-0.11-96-1.07zM-487.72 353.36" fill="#000"/><path d="m-487.72 353.36c-10.79-2.56-22.22-12.09-27.58-22.99-3.04-6.18-3.2-7.09-3.2-18.03 0-10.4 0.26-12.07 2.68-17.23 5.1-10.89 14.88-18.75 27.15-21.84 2.59-0.65 5.02-1.69 5.41-2.31 0.38-0.62 0.81-4 0.95-7.5 0.85-21.78 15.15-40.97 35.1-47.14 10.78-3.33 24.33-2.51 36.05 2.18 3.72 1.49 3.3 1.81 11.16-8.5 4.65-6.1 14.05-13.68 21.74-17.55 8.3-4.17 16.94-6.09 27.26-6.07 28.86 0.07 53.73 18.12 62.92 45.67 2.94 8.8 2.79 11.27-0.67 11.34-1.51 0.03-5.85 0.86-9.63 1.85l-6.88 1.79-6.28-6.28c-17.7-17.7-46.59-21.53-71.15-9.42-9.81 4.84-17.7 11.78-23.65 20.83-4.25 6.45-9.66 18.48-9.66 21.47 0 2.12-1.72 3.18-9.05 5.58-22.69 7.44-35.94 24.63-35.93 46.62 0 8 2.06 17.8 4.93 23.41 1.08 2.11 1.68 4.13 1.34 4.47-0.88 0.88-29.11 0.58-33.01-0.35z" /></g></g></g></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -1 +0,0 @@
<svg viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path d="M465.69 311.917V200.083H363.175v111.834zm-69.897-18.639a4.66 4.66 0 01-4.659-4.659v-65.237a4.66 4.66 0 014.659-4.66h37.278a4.66 4.66 0 014.66 4.66v65.237a4.66 4.66 0 01-4.66 4.66h-37.278z"/><path d="M400.453 251.34v-23.299h27.959v23.299zm0 32.618v-23.299h27.959v23.299zM46.31 187.656v136.688c0 8.564 6.968 15.532 15.532 15.532h21.746V204.742a4.66 4.66 0 019.319 0v135.134h245.415c8.565 0 15.533-6.968 15.533-15.532V187.656c0-8.564-6.968-15.532-15.533-15.532H61.842c-8.565 0-15.532 6.968-15.532 15.532zm241.936 68.6c.122 9.637-7.529 17.293-17.166 17.416-9.359-.354-17.016-8.006-16.662-17.366-.122-9.636 7.529-17.293 16.889-16.938 9.637-.124 17.293 7.528 16.939 16.888zm-41.921-28.528a4.658 4.658 0 011.679 6.372c-3.865 6.631-5.909 14.266-5.909 22.082v.014c-.143 8.152 1.746 15.601 5.603 22.206a4.66 4.66 0 01-8.048 4.7c-4.73-8.099-7.057-17.196-6.873-26.984v-.021c0-9.381 2.48-18.637 7.176-26.69a4.657 4.657 0 016.372-1.679zm-23.7-17.48a4.659 4.659 0 011.395 6.441c-7.725 11.993-11.78 25.622-11.613 39.406-.164 14.287 3.794 28.031 11.327 39.791a4.66 4.66 0 01-7.849 5.027c-8.511-13.288-12.909-28.806-12.799-44.815-.13-15.503 4.387-30.927 13.099-44.455a4.66 4.66 0 016.44-1.395zm-22.433-16.741a4.656 4.656 0 011.272 6.466c-11.136 16.59-17.074 35.936-17.174 55.945-.244 20.163 5.497 39.619 16.606 56.34a4.66 4.66 0 01-7.762 5.158c-11.901-17.914-18.172-38.71-18.172-60.224 0-.452.003-.901.008-1.354.108-21.815 6.594-42.941 18.756-61.059a4.66 4.66 0 016.466-1.272z"/></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -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);

View File

@ -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');

View File

@ -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';
}
}
};

View File

@ -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');
}
},

View File

@ -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';

View File

@ -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 };

View File

@ -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;
}

View File

@ -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

View File

@ -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'
];

View File

@ -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',

View File

@ -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 };
},

View File

@ -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() {

View File

@ -4,4 +4,3 @@ import 'hbs-helpers/ifeq';
import 'hbs-helpers/ifneq';
import 'hbs-helpers/ifemptyoreq';
import 'hbs-helpers/res';
import 'hbs-helpers/svg';

View File

@ -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 `<svg class="${cls}"` + icon.substr(4);
}
return icon;
});

View File

@ -1,35 +1,4 @@
{
"months": [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December"
],
"monthsShort": [
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec"
],
"weekdays": ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
"weekdaysShort": ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
"retToApp": "return to app",
"name": "name",
"icon": "icon",
@ -400,14 +369,15 @@
"setGenDownloadAndRestart": "Download update and restart",
"setGenAppearance": "Appearance",
"setGenTheme": "Theme",
"setGenThemeDark": "Dark",
"setGenThemeLight": "Light",
"setGenThemeFb": "Flat blue",
"setGenThemeDb": "Dark brown",
"setGenThemeWh": "White",
"setGenThemeTe": "Terminal",
"setGenThemeHc": "High contrast",
"setGenThemeSd": "Solarized dark",
"setGenThemeSl": "Solarized light",
"setGenThemeMacDark": "macOS Dark",
"setGenMoreThemes": "More themes",
"setGenLocale": "Language",
"setGenLocOther": "other languages are available as plugins",
"setGenFontSize": "Font size",
@ -621,7 +591,7 @@
"setAboutTitle": "About",
"setAboutBuilt": "This app is built with these awesome tools",
"setAboutLic": "License",
"setAboutLicComment": "The app itself and all included components which are not in public domain are licensed under MIT license",
"setAboutLicComment": "The app itself and all included components are licensed under MIT license",
"setAboutFirst": "This is an open-source app created by {}",
"setAboutSecond": "and licensed under {}.",
"setAboutSource": "The source code and issues are on {}.",
@ -659,7 +629,9 @@
"webdavSaveMethod": "Save method",
"webdavSaveMove": "Upload a temporary file and move",
"webdavSavePut": "Overwrite kdbx file with PUT",
"webdavSavePut": "Overwrite the kdbx file with PUT",
"webdavNoLastModified": "Last-Modified HTTP header is absent",
"webdavStatReload": "Always reload the file instead of relying on Last-Modified HTTP header",
"launcherSave": "Save Passwords Database",
"launcherFileFilter": "KeePass files",
@ -686,5 +658,6 @@
"yubiKeyTouchRequestedHeader": "Touch your YubiKey",
"yubiKeyTouchRequestedBody": "Please touch your YubiKey with serial number {}",
"yubiKeyDisabledErrorHeader": "USB is disabled",
"yubiKeyDisabledErrorBody": "YubiKey is required to open this file, please enable USB devices in settings."
"yubiKeyDisabledErrorBody": "YubiKey is required to open this file, please enable USB devices in settings.",
"yubiKeyErrorWithCode": "YubiKey error, code {}."
}

View File

@ -56,10 +56,10 @@
"notes": "Notizen",
"entry": "Eintrag",
"group": "Gruppe",
"noTitle": "Keinen Titel",
"noTitle": "Kein Titel",
"or": "oder",
"history": "Verlauf",
"template": "Template",
"template": "Vorlage",
"notImplemented": "Nicht implementiert",
"saveChanges": "Änderungen speichern",
"discardChanges": "Änderungen verwerfen",
@ -86,8 +86,8 @@
"menuSetAbout": "Info",
"menuSetDevices": "Geräte",
"menuAlertNoTags": "Keine Tags",
"menuAlertNoTagsBody": "Neue Tags können durch das Bearbeiten von Einträgen (Abschnitt “Tags”) hinzugefügt werden.",
"menuEmptyTrash": "Leere Papierkorb",
"menuAlertNoTagsBody": "Neue Tags können durch das Bearbeiten von Einträgen (Abschnitt „Tags“) hinzugefügt werden.",
"menuEmptyTrash": "Papierkorb leeren",
"menuEmptyTrashAlert": "Papierkorb leeren?",
"menuEmptyTrashAlertBody": "Die Einträge können nicht wiederhergestellt werden",
"menuItemCollapsed": "Doppelklick zum Aufklappen",
@ -109,21 +109,21 @@
"genLen": "Länge",
"genNewPass": "Neues Passwort",
"genPresetDefault": "Standardeinstellung",
"genPresetDerived": "unverändert",
"genPresetDerived": "Wie das alte Passwort",
"genPresetPronounceable": "aussprechbar",
"genPresetMed": "durchschnittlich lang",
"genPresetMed": "Durchschnittliche Länge",
"genPresetLong": "lang",
"genPresetPin4": "4-stellige PIN",
"genPresetMac": "MAC-Adresse",
"genPresetHash128": "128-Bit Hash",
"genPresetHash256": "256-Bit Hash",
"genPresetHash128": "128-Bit-Hash",
"genPresetHash256": "256-Bit-Hash",
"genHidePass": "Passwort ausblenden",
"genShowPass": "Passwort einblenden",
"grpTitle": "Gruppe",
"grpSearch": "Suche für Einträge in dieser Gruppe aktivieren",
"grpAutoType": "Auto-Type aktivieren",
"grpAutoTypeSeq": "Auto-Type Abfolge",
"grpAutoTypeSeqDefault": "Standard Auto-Type Abfolge verwenden",
"grpAutoTypeSeq": "Auto-Type-Sequenz",
"grpAutoTypeSeqDefault": "Standard-Auto-Type-Sequenz verwenden",
"grpTrash": "Gruppe und alle enthaltenen Einträge löschen",
"tagTitle": "Tag",
"tagTrash": "Diesen Tag von allen Einträgen entfernen",
@ -131,14 +131,14 @@
"tagTrashQuestion": "Diesen Tag von allen Einträgen entfernen?",
"tagTrashQuestionBody": "Der Tag wird von allen Einträgen entfernt. Diese Aktion kann nicht rückgängig gemacht werden.",
"tagExists": "Tag existiert bereits",
"tagExistsBody": "Ein Tag mit diesem Namen ist bereits vorhanden. Bitte wählen Sie einen anderen Namen.",
"tagExistsBody": "Ein Tag mit diesem Namen ist bereits vorhanden. Bitte einen anderen Namen wählen.",
"tagBadName": "Ungültiger Name",
"tagBadNameBody": "Der Tag-Name darf nicht die folgenden Zeichen enthalten: {}. Bitte entfernen Sie sie.",
"tagBadNameBody": "Der Tag-Name darf folgende Zeichen nicht enthalten: {}. Bitte diese entfernen.",
"genPsTitle": "Generator-Voreinstellungen",
"genPsCreate": "Neue Voreinstellung",
"genPsDelete": "Voreinstellung löschen",
"genPsNew": "Voreinstellung",
"genPsEnabled": "In Voreinstellungs-Liste anzeigen",
"genPsEnabled": "In Voreinstellungsliste anzeigen",
"genPsDefault": "Standardmäßig ausgewählt",
"genPsDefaultLength": "Standardlänge",
"genPsUpper": "Lateinische Großbuchstaben",
@ -151,16 +151,16 @@
"genPsInclude": "Zusätzliche Symbole angeben",
"genPsExample": "Beispiel für ein generiertes Passwort",
"genPsPattern": "Muster",
"genPsPatternHelp": "Mit Mustern können eigene Regeln zur Auswahl von Zeichen festgelegt werden. Beispielsweise werden mit 1-AA Passwörter generiert, die mit einer Ziffer starten, gefolgt von einem Bindestrich und zwei Großbuchstaben. Die folgenden Symbole stehen zur Verfügung:",
"genPsPatternHelp": "Mit Mustern können eigene Regeln zur Auswahl von Zeichen festgelegt werden. Beispielsweise werden mit 1-AA Passwörter generiert, die mit einer Ziffer starten, gefolgt von einem Bindestrich und zwei Großbuchstaben. Folgende Symbole stehen zur Verfügung:",
"genPsAllRanges": "Alle Zeichen",
"genPsIncluded": "Zusätzliche oben hinzugefügte Zeichen",
"keyChangeTitleRemote": "Hauptschlüssel geändert",
"keyChangeMessageRemote": "Der Hauptschlüssel für diese Datenbank wurde geändert. Bitte geben Sie das neue Passwort ein.",
"keyChangeMessageRemote": "Der Hauptschlüssel für diese Datenbank wurde geändert. Bitte das neue Passwort eingeben.",
"keyChangeTitleExpired": "Hauptschlüssel abgelaufen",
"keyChangeMessageExpired": "Der Hauptschlüssel für diese Datenkbank ist abgelaufen. Bitte geben Sie einen neuen Schlüssel ein.",
"keyChangeRepeatPassword": "Passwort (Wiederholung)",
"keyEnter": "Enter",
"iconFavTitle": "Website-Icon herunterladen und verwenden",
"iconFavTitle": "Website-Symbol herunterladen und verwenden",
"iconSelCustom": "Eigenes Icon auswählen",
"listEmptyTitle": "Leer",
"listEmptyAdd": "Zum Anlegen neuer Einträge den obigen {} Button benutzen",
@ -168,9 +168,9 @@
"listNoWebsite": "keine Website",
"listNoUser": "kein Benutzer",
"listNoAttachments": "keine Anhänge",
"listAddTemplateHeader": "Templates",
"listAddTemplateBody1": "Templates ermöglichen es, Einträge mit nur einem Klick zu erstellen. Fügen Sie etwas zum Template-Eintrag hinzu und klicken Sie auf {}, um dieses Template zu benutzen.",
"listAddTemplateBody2": "Sie können Ihre Templates in der Gruppe “{}” finden.",
"listAddTemplateHeader": "Vorlagen",
"listAddTemplateBody1": "Vorlagen ermöglichen es, Einträge mit nur einem Klick zu erstellen. Fügen Sie etwas zum Vorlage-Eintrag hinzu und klicken Sie auf {}, um diese Vorlage zu verwenden.",
"listAddTemplateBody2": "Sie können Ihre Vorlagen in der Gruppe “{}” finden.",
"searchAddNew": "Hinzufügen",
"searchSort": "Sortierung",
"searchCreated": "Erstelldatum",
@ -202,17 +202,17 @@
"openKeyFile": "Schlüsseldatei",
"openKeyFileDropbox": "(von Dropbox)",
"openDropHere": "Dateien hier ablegen",
"openFailedRead": "Konnte die Datei nicht lesen",
"openFailedRead": "Datei konnte nicht gelesen werden",
"openNothingFound": "Nichts gefunden",
"openNothingFoundBody": "Es konnten keine kompatiblen Dateien gefunden werden.",
"openSelectFile": "Datei auswählen",
"openSelectFileBody": "Wählen Sie eine Datei zum Öffnen aus",
"openPassFor": "Passwort für",
"openRemoveLastQuestion": "Lokale Datei löschen?",
"openRemoveLastQuestionBody": "Die Datei, die Sie löschen möchten, ist nur innerhalb der App gespeichert. Möchten Sie sie unwiederbringlich löschen?",
"openRemoveLastQuestionBody": "Die Datei, die Sie löschen möchten, ist nur innerhalb der App gespeichert. Möchten Sie diese unwiederbringlich löschen?",
"openRemoveLastQuestionModBody": "Die Datei, die Sie löschen möchten, beinhaltet lokale Änderungen. Möchten Sie diese Änderungen verwerfen und die Datei löschen?",
"openLocalFile": "Lokale Datei",
"openLocalFileBody": "Sie sind dabei, eine Datei zu öffnen, die innerhalb der App gespeichert wird. Wenn Sie an dieser Datei Änderungen vornehmen, werden diese nicht im Dateisystem gespeichert. Um die Datei mit den Änderungen zu erhalten, können Sie sie aus den Einstellungen heraus exportieren.",
"openLocalFileBody": "Sie sind dabei, eine Datei zu öffnen, die innerhalb der App gespeichert wird. Wenn Sie an dieser Datei Änderungen vornehmen, werden diese nicht im Dateisystem gespeichert. Um die Datei mit den Änderungen zu erhalten, können Sie diese aus den Einstellungen heraus exportieren.",
"openLocalFileDontShow": "Nicht wieder anzeigen",
"openWrongFile": "Inkompatibles Dateiformat",
"openWrongFileBody": "Dieses Dateiformat ist nicht kompatibel. Die App unterstützt KeePass-Datenbanken im kdbx-Format.",
@ -221,10 +221,10 @@
"openUrl": "URL",
"openUrlDesc": "https://server/pfad/datei.kdbx, oder einfach datei.kdbx",
"openUser": "Benutzername",
"openUserDesc": "WebDAV Server Benutzername (falls erforderlich)",
"openUserDesc": "WebDAV-Server-Benutzername (falls erforderlich)",
"openUserPlaceholder": "kein Benutzername",
"openPass": "Passwort",
"openPassDesc": "WebDAV Server-Passwort (nicht das KDBX-Datei-Passwort)",
"openPassDesc": "WebDAV-Server-Passwort (nicht das KDBX-Datei-Passwort)",
"openPassPlaceholder": "kein Passwort",
"openConfigError": "Fehler: {}",
"openConfigErrorNotFound": "Datei nicht gefunden",
@ -238,12 +238,13 @@
"openChalRespLoading": "Liste mit YubiKeys wird geladen",
"openChalRespSelectYubiKey": "Wählen Sie den YubiKey aus, den Sie benutzen wollen",
"openChalRespErrorEmpty": "Keine YubiKeys gefunden.",
"openChalRespErrorEmptyMac": "Verwenden Sie dieses Feature zum ersten Mal auf MacOS? KeeWeb muss in Ihren Sicherheitseinstellung im Input-Monitoring-Abschnitt hinzugefügt werden.",
"detAttDownload": "Umschalttaste + Klick auf den Anhang-Button zum Herunterladen oder",
"detAttDelToRemove": "Backspace zum entfernen",
"detAttSave": "Speichern unter...",
"detAttDelToRemove": "Backspace zum Entfernen",
"detAttSave": "Speichern unter",
"detEmpty": "Ihre Passwörter werden hier angezeigt",
"detGroupRestore": "Ziehen Sie diese Gruppe in eine andere Gruppe als den Papierkorb, um sie wiederherzustellen.",
"detHistoryClickPoint": "Klicken Sie auf einen der Punkte im Verlauf um den entsprechenden Zustand des Eintrags anzuzeigen",
"detHistoryClickPoint": "Klicken Sie auf einen der Punkte im Verlauf, um den entsprechenden Zustand des Eintrags anzuzeigen",
"detHistoryReturn": "zurück zum Eintrag",
"detHistoryRevert": "Auf diese Version zurücksetzen",
"detHistoryDel": "Version löschen",
@ -277,7 +278,7 @@
"detAttachments": "Anhänge",
"detDelFromTrash": "Aus dem Papierkorb löschen?",
"detDelFromTrashBody": "Es wird keine Möglichkeit zur Wiederherstellung geben.",
"detDelFromTrashBodyHint": "Um alle Dateien aus dem Papierkorb zu entfernen, klicken Sie auf das “Papierkorb” leeren-Icon im Papierkorb-Menüpunkt.",
"detDelFromTrashBodyHint": "Um alle Dateien aus dem Papierkorb zu entfernen, klicken Sie auf das „Papierkorb leeren“-Symbol im Papierkorb-Menüpunkt.",
"detDelToTrash": "Diesen Eintrag löschen?",
"detDelToTrashBody": "Der Eintrag wird in den Papierkorb verschoben.",
"detFieldCopied": "Kopiert",
@ -299,12 +300,12 @@
"detAutoType": "Auto-Type",
"detAutoTypeSettings": "Auto-Type Einstellungen",
"detAutoTypeEnabled": "Auto-Type für diesen Eintrag aktivieren",
"detAutoTypeSequence": "Tastensequenz",
"detAutoTypeSequence": "Tastenfolge",
"detAutoTypeInput": "Eingabe",
"detAutoTypeShortcutsDesc": "{} oder {} während die App inaktiv ist",
"detAutoTypeObfuscation": "Echte Tastenanschläge mit zufälligen mischen",
"detAutoTypeWindow": "Fenster",
"detAutoTypeInputWindow": "Fenster-Titel",
"detAutoTypeInputWindow": "Fenstertitel",
"detSetupOtpAlert": "QR-Code einscannen",
"detSetupOtpAlertBody": "Bitte kopieren Sie den QR-Code, der auf der Autorisierungsseite angezeigt wird.",
"detSetupOtpAlertBody1": "1. Gehen Sie zur Autorisierungsseite",
@ -317,14 +318,14 @@
"detSetupOtpAlertBodyWith": "mit {}",
"detOtpImageError": "Fehler beim Einlesen des Bildes",
"detOtpImageErrorBody": "Entschuldigung, wir konnten das Bildformat nicht lesen. Bitte kontaktieren Sie den Hersteller mit Einzelheiten zu diesem Fehler.",
"detOtpImageReading": "QR-Code wird eingelesen...",
"detOtpImageReading": "QR-Code wird eingelesen",
"detOtpQrError": "QR-Code Lesefehler",
"detOtpQrErrorBody": "Der QR-Code konnte leider nicht eingelesen werden. Bitte versuchen Sie es noch einmal oder kontaktieren Sie den Hersteller mit Einzelheiten zum aufgetretenen Fehler.",
"detOtpQrWrong": "Fehlerhafter QR-Code",
"detOtpQrWrongBody": "Ihr QR-Code wurde erfolgreich eingelesen, enthält jedoch keinen Einmal-Code.",
"detOtpField": "Einmal-Code",
"detOtpClickToTouch": "Zum Erzeugen hier klicken",
"detOtpGenerating": "Wird erzeugt...",
"detOtpGenerating": "Wird erzeugt",
"detOtpTouch": "Berühren Sie Ihren {}",
"detLockField": "Dieses Feld sperren, damit der Inhalt nicht durchsuchbar ist und angezeigt wird. Um ihn anzuzeigen, muss er angeklickt werden.",
"detUnlockField": "Dieses Feld entsperren. Dies führt dazu, dass der Inhalt direkt angezeigt wird und durchsuchbar ist.",
@ -334,8 +335,8 @@
"autoTypeEntryFields": "Feld-Platzhalter",
"autoTypeModifiers": "Hilfstasten",
"autoTypeKeys": "Tasten",
"autoTypeLink": "mehr...",
"autoTypeError": "Auto-Type Fehler",
"autoTypeLink": "mehr",
"autoTypeError": "Auto-Type-Fehler",
"autoTypeErrorGeneric": "Während der Ausführung von Auto-Type ist ein Fehler aufgetreten: {}",
"autoTypeErrorGlobal": "Um globale Tastenkürzel zu nutzen, wechseln Sie bitte zu der Anwendung, in der Sie das Passwort eingeben wollen.",
"autoTypeErrorNotInstalled": "{} ist nicht installiert",
@ -343,7 +344,7 @@
"autoTypeMsgNoWindow": "Wir konnten den Titel des aktiven Fensters leider nicht auslesen. Fangen Sie einfach an zu tippen, um nach dem richtigen Eintrag zu suchen.",
"autoTypeMsgMatchedByWindow": "Passwort auswählen für {}",
"autoTypeNoMatches": "keine passenden Einträge",
"autoTypeSelectionHint": "Die Auto-Type Abfolge eintippen",
"autoTypeSelectionHint": "Die Auto-Type-Sequenz eintippen",
"autoTypeSelectionHintAction": "Nur das Passwort eintippen",
"autoTypeSelectionHintOpt": "Nur den Benutzernamen eintippen",
"autoTypeSelectionHintShift": "Andere Felder",
@ -375,12 +376,12 @@
"setGenNewVersion": "Eine neue Version der App ist verfügbar und wurde heruntergeladen",
"setGenReleaseNotes": "Versionshinweise anzeigen",
"setGenReloadToUpdate": "Seite aktualisieren, um das Update durchzuführen",
"setGenUpdateManual": "Eine neue Version ist verfügbar. Sie wird nach Updates suchen und sie automatisch installieren, ein automatisches Update von Ihrer Version ist aber nicht möglich.",
"setGenUpdateManual": "Eine neue Version ist verfügbar. Sie wird nach Updates suchen und diese automatisch installieren, ein automatisches Update Ihrer Version ist aber nicht möglich.",
"setGenDownloadUpdate": "Update herunterladen",
"setGenUpdateAuto": "Automatisch herunterladen und installieren",
"setGenUpdateCheck": "Auf Updates überprüfen aber nicht installieren",
"setGenUpdateCheck": "Auf Updates überprüfen, aber nicht installieren",
"setGenNoUpdate": "Nie nach Updates suchen",
"setGenUpdateChecking": "Suche nach Updates",
"setGenUpdateChecking": "Updates werden gesucht",
"setGenCheckUpdate": "Nach Updates suchen",
"setGenErrorChecking": "Updatesuche fehlgeschlagen",
"setGenLastCheckSuccess": "Letzte erfolgreiche Überprüfung am {}",
@ -388,21 +389,21 @@
"setGenCheckedAt": "Überprüft am",
"setGenLatestVer": "Sie haben die aktuellste Version",
"setGenNewVer": "Neue Version {} verfügbar, veröffentlicht am",
"setGenDownloadingUpdate": "Update wird heruntergeladen...",
"setGenExtractingUpdate": "Update wird entpackt...",
"setGenDownloadingUpdate": "Update wird heruntergeladen",
"setGenExtractingUpdate": "Update wird entpackt",
"setGenCheckErr": "Beim Herunterladen der neuen Version ist ein Fehler aufgetreten",
"setGenNeverChecked": "Nie nach Updates gesucht",
"setGenRestartToUpdate": "App neu starten, um das Update durchzuführen",
"setGenDownloadAndRestart": "Update herunterladen und App neu starten",
"setGenAppearance": "Oberfläche",
"setGenTheme": "Theme",
"setGenThemeFb": "Flat blue",
"setGenThemeDb": "Dark brown",
"setGenThemeWh": "White",
"setGenThemeFb": "Mattblau",
"setGenThemeDb": "Dunkelbraun",
"setGenThemeWh": "Weiß",
"setGenThemeTe": "Terminal",
"setGenThemeHc": "High contrast",
"setGenThemeSd": "Solarized dark",
"setGenThemeSl": "Solarized light",
"setGenThemeHc": "Hoher Kontrast",
"setGenThemeSd": "Solarisiert dunkel",
"setGenThemeSl": "Solarisiert hell",
"setGenThemeMacDark": "macOS Dunkel",
"setGenLocale": "Sprache",
"setGenLocOther": "Andere Sprachen sind als Plugins verfügbar.",
@ -410,15 +411,15 @@
"setGenFontSizeNormal": "Standard",
"setGenFontSizeLarge": "Groß",
"setGenFontSizeLargest": "Größte",
"setGenTitlebarStyle": "Fenster-Stil (erfordert Neustart des Programms)",
"setGenTitlebarStyle": "Fensterstil (erfordert Neustart des Programms)",
"setGenTitlebarStyleDefault": "Standard",
"setGenTitlebarStyleHidden": "Eigener Titel",
"setGenTitlebarStyleHiddenInset": "Eigener Titel, verschiebbares Fenster",
"setGenShowSubgroups": "Einträge aus allen Untergruppen anzeigen",
"setGenTableView": "Einträge in Tabellenansicht anzeigen",
"setGenColorfulIcons": "Eigene Icons in der Listenansicht farbig anzeigen",
"setGenColorfulIcons": "Eigene Symbole in der Listenansicht farbig anzeigen",
"setGenUseMarkdown": "Markdown in Notizen",
"setGenUseGroupIconForEntries": "Für neue Einträge automatisch das Gruppen-Icon verwenden",
"setGenUseGroupIconForEntries": "Für neue Einträge automatisch das Gruppensymbol verwenden",
"setGenDirectAutotype": "Wenn nur ein passender Eintrag gefunden wird, diesen automatisch für Auto-Type auswählen",
"setGenFunction": "Arbeitsweise",
"setGenAutoSyncOnClose": "Beim Schließen speichern und synchronisieren",
@ -449,19 +450,19 @@
"setGenStorageLogout": "Abmelden",
"setGenShowAdvanced": "Erweiterte Einstellungen anzeigen",
"setGenDevTools": "Entwicklerwerkzeuge anzeigen",
"setGenTryBeta": "Beta-Version bis zum Schließen der App ausprobieren",
"setGenTryBeta": "Betaversion bis zum Schließen der App ausprobieren",
"setGenTryBetaWarning": "Ungespeicherte Dateien",
"setGenTryBetaWarningBody": "Bitte speichern Sie alle Dateien und Klicken Sie erneut auf diesen Button",
"setGenTryBetaWarningBody": "Bitte speichern Sie alle Dateien und klicken erneut auf diese Schaltfläche",
"setGenShowAppLogs": "App-Logs anzeigen",
"setGenReloadApp": "App neu laden",
"setGenFieldLabelDblClickAutoType": "Auto-Type durch anklicken von Beschriftungen aktivieren",
"setGenFieldLabelDblClickAutoType": "Auto-Type durch Anklicken von Beschriftungen aktivieren",
"setFilePath": "Dateipfad",
"setFileStorage": "Diese Datei wird von {} geladen.",
"setFileIntl": "Diese Datei ist im internen App-Speicher abgelegt",
"setFileLocalHint": "Sie wollen nahtlos mit lokalen Dateien arbeiten?",
"setFileDownloadApp": "Laden Sie die Desktop-App herunter",
"setFileSave": "Speichern",
"setFileSaveTo": "Speichern unter...",
"setFileSaveTo": "Speichern unter",
"setFileClose": "Sperren",
"setFileSync": "Synchronisierung",
"setFileSyncVerb": "Synchronisieren",
@ -475,7 +476,7 @@
"setFileConfirmPass": "Masterpasswort bestätigen",
"setFilePassChange": "Um das Passwort zu ändern, geben Sie es im Bestätigungsfeld an",
"setFilePassChanged": "Das Passwort wurde geändert. Lassen Sie das Feld leer, um das bisherige Passwort zu behalten.",
"setFilePassNotMatch": "Die Passwörter stimmen nicht überein, bitte geben Sie sie erneut ein",
"setFilePassNotMatch": "Die Passwörter stimmen nicht überein, bitte geben Sie diese erneut ein",
"setFileKeyFile": "Schlüsseldatei",
"setFileSelKeyFile": "Schlüsseldatei auswählen",
"setFileNames": "Namen",
@ -492,7 +493,7 @@
"setFileBackupPath": "Sicherungspfad",
"setFileBackupTime": "Sicherungen anlegen",
"setFileBackupNow": "Jetzt sichern",
"setFileBackupNowWorking": "Sichern...",
"setFileBackupNowWorking": "Ausführung läuft …",
"setFileBackupError": "Sicherungsfehler",
"setFileBackupErrorDescription": "Fehler beim Schreiben der Sicherungsdatei",
"setFileBackupErrorIsDir": "Der Sicherungspfad ist ungültig",
@ -512,9 +513,9 @@
"setFileUseGenKeyFile": "Generierte Schlüsseldatei benutzen",
"setFileUseOldKeyFile": "Alte Schlüsseldatei benutzen",
"setFileGenKeyFile": "Neue Schlüsseldatei generieren",
"setFileDontUseKeyFile": "Keine Schlüsseldatei benutzen",
"setFileDontUseKeyFile": "Keine Schlüsseldatei verwenden",
"setFileEmptyPass": "Leeres Passwort",
"setFileEmptyPassBody": "Eine Datenbank ohne Passwort zu speichern macht sie vollkommen ungeschützt. Möchten Sie sie trotzdem speichern?",
"setFileEmptyPassBody": "Eine Datenbank ohne Passwort zu speichern, macht diese vollkommen ungeschützt. Möchten Sie dies trotzdem tun?",
"setFileSaveError": "Fehler beim Speichern",
"setFileSaveErrorBody": "Schreibfehler beim Speichern",
"setFileAlreadyExists": "Existiert bereits",
@ -529,14 +530,16 @@
"setFileDeviceIntro": "Einmal-Codes aus diesem {} werden in der App angezeigt.",
"setFileDeviceSettings": "Einstellungen",
"setFileYubiKey": "YubiKey",
"setFileDontUseYubiKey": "Keinen YubiKey benutzen",
"setFileDontUseYubiKey": "Keinen YubiKey verwenden",
"setFileRefreshYubiKeyList": "Liste aktualisieren",
"setFileYubiKeyHeader": "YubiKey",
"setFileYubiKeyBody": "Es ist gefährlich, einen YubiKey als Teil des Hauptschlüssels zu verwenden. Falls etwas schief geht, besteht möglicherweise kein Zugriff mehr auf die Passwörter. Haben Sie ein Backup Ihrer Datei erstellt, bevor diese Einstellung geändert wurde?",
"setFileYubiKeyErrorEmptyMac": "Scheinbar wurde ein YubiKey angeschlossen, er ist aber nicht sichtbar. KeeWeb muss in den Sicherheitseinstellungen im Abschnitt Input-Monitoring hinzugefügt werden, um auf YubiKeys zugreifen zu können.",
"setShTitle": "Tastenkürzel",
"setShShowAll": "alle Einträge anzeigen",
"setShColors": "mit Farben markierte Einträge anzeigen",
"setShTrash": "Papierkorb öffnen",
"setShFind": "Suchfeld aktivieren; oder: einfach anfangen, den Suchbegriff zu tippen",
"setShFind": "Suchen oder einfach beginnen zu tippen",
"setShClearSearch": "Suchfeld leeren",
"setShCopyPass": "Passwort bzw. ausgewähltes Feld kopieren",
"setShCopyUser": "Benutzername kopieren",
@ -545,8 +548,8 @@
"setShPrev": "zum vorherigen Eintrag gehen",
"setShNext": "zum nächsten Eintrag gehen",
"setShCreateEntry": "Neuer Eintrag",
"setShOpen": "öffnen / neu",
"setShSave": "alle Dateien speichern",
"setShOpen": "Öffnen / Neu",
"setShSave": "Alle Dateien speichern",
"setShGen": "Passwort generieren",
"setShSet": "App-Einstellungen",
"setShCopyPassGlobal": "Passwort kopieren",
@ -556,12 +559,12 @@
"setShRestoreApp": "{} öffnen",
"setShGlobal": "Diese systemweiten Tastenkürzel funktionieren auch, wenn die App im Hintergrund läuft. Klicken Sie auf einen Eintrag, um ein eigenes Kürzel festzulegen.",
"setShLock": "Datenbank sperren",
"setShEdit": "Drücken Sie eine Tastenkombination, um diese als Kürzel zu setzen",
"setShEdit": "Drücken Sie eine Tastenkombination, um diese als Kürzel zu festzulegen",
"setPlInstallTitle": "Neue Plugins installieren",
"setPlInstallDesc": "KeeWeb-Plugins fügen Funktionen, Themes und Sprachen zu KeeWeb hinzu. Plugins werden mit denselben Rechten ausgeführt wie KeeWeb selbst, sie können auf all Ihre Passwörter zugreifen oder diese ändern. Installieren Sie niemals Plugins, denen Sie nicht vertrauen.",
"setPlInstallLabel": "Plugin-URL",
"setPlInstallBtn": "Installieren",
"setPlInstallBtnProgress": "Installiere",
"setPlInstallBtnProgress": "Installation läuft",
"setPlUninstallBtn": "Deinstallieren",
"setPlDisableBtn": "Deaktivieren",
"setPlEnableBtn": "Aktivieren",
@ -575,7 +578,7 @@
"setPlLoadTime": "geladen in {}",
"setPlLastUpdate": "Letzte Prüfung auf Updates",
"setPlLoadError": "Fehler beim Laden des Plugins",
"setPlGalleryLoading": "Plugins werden geladen, bitte warten Sie einen Moment",
"setPlGalleryLoading": "Plugins werden geladen, bitte einen Moment warten",
"setPlGalleryLoadError": "Fehler beim Laden der Plugins",
"setPlInstallUrlTitle": "Plugin von einer URL hinzufügen",
"setPlInstallUrlDesc": "Wenn das Plugin nicht in der Galerie verfügbar ist, können Sie es manuell von einer URL installieren",
@ -587,19 +590,35 @@
"setPlTranslateLink": "übersetzen Sie die App in Ihre Sprache",
"setPlAutoUpdate": "Automatisch aktualisieren",
"setPlLoadGallery": "Plugin-Galerie laden",
"setDevicesTitle": "Geräte",
"setDevicesEnableUsb": "Interaktion mit USB-Geräten aktivieren",
"setDevicesYubiKeyIntro": "Es wird empfehlen {} zu lesen, bevor ein YubiKey verwendet wird.",
"setDevicesYubiKeyIntroLink": "dieses Dokument",
"setDevicesYubiKeyToolsDesc": "Um YubiKey in diesem Modus verwenden zu können, müssen Sie vorher das Tool {} installieren.",
"setDevicesYubiKeyToolsDesc2": "{}, um mehr Informationen über dieses Tool zu erhalten.",
"setDevicesYubiKeyToolsDescLink": "Hier klicken",
"setDevicesYubiKeyToolsStatusChecking": "Prüfung, ob {} installiert ist",
"setDevicesYubiKeyToolsStatusOk": "{} ist installiert",
"setDevicesYubiKeyToolsStatusError": "{} ist nicht installiert oder funktioniert nicht korrekt",
"setDevicesYubiKeyOtpTitle": "Einmal-Codes",
"setDevicesYubiKeyOtpDesc": "Ein YubiKey kann genutzt werden, um Einmal-Codes für verschiedene Dienste zu erzeugen.",
"setDevicesYubiKeyOtpShowIcon": "Das Symbol auf der Startseite anzeigen",
"setDevicesYubiKeyOtpAutoOpen": "Einmal-Codes automatisch laden, wenn Dateien geöffnet sind",
"setDevicesYubiKeyOtpMatchEntries": "Passende Einmal-Codes in den Einträgen anzeigen",
"setDevicesYubiKeyChalRespTitle": "Challenge-Response",
"setDevicesYubiKeyChalRespDesc": "Es ist auch möglich, einen YubiKey im Challenge-Response-Modus zu verwenden, um einen Teil des Private Schlüssels, der zum Verschlüsseln der Dateien verwendet wird, auf dem YubiKey zu speichern.",
"setDevicesYubiKeyChalRespShow": "Optionen zum Verwenden eines YubiKeys beim Öffnen von Dateien anzeigen",
"setDevicesYubiKeyStuckWorkaround": "YubiKey entfernen und neu verbinden, falls der Ladeprozess hängt",
"setDevicesYubiKeyRememberChalResp": "Challenge-Response-Einstellungen merken, während die App geöffnet ist",
"setAboutTitle": "Über",
"setAboutBuilt": "Diese App wurde mit den folgenden Werkzeugen erstellt",
"setAboutBuilt": "Diese App wurde mit folgenden Werkzeugen erstellt",
"setAboutLic": "Lizenz",
"setAboutLicComment": "Die App selbst und alle enthaltenen Komponenten, die nicht Public Domain sind, stehen unter der MIT-Lizenz.",
"setAboutFirst": "Dies ist eine Open-Source App, erstellt von {}",
"setAboutSecond": "und lizenziert unter {}.",
"setAboutSource": "Quellcode und Tickets befinden sich auf {}.",
"setHelpFormat": "Dateiformat",
"setHelpFormatBody": "Dies ist eine Portierung der {} App, programmiert mit Webtechnologien. Sie ist kompatibel mit Dateien im KeePass-Format (kdbx). Sie können solche Dateien (Passwort-Datenbanken) entweder mit KeePass oder mit dieser App erstellen. Das Dateiformat ist zu 100% kompatibel und sollte in beiden Anwendungen nutzbar sein.",
"setHelpFormatBody": "Dies ist eine Portierung der App {}, programmiert mit Webtechnologien. Sie ist kompatibel mit Dateien im KeePass-Format (kdbx). Sie können solche Dateien (Passwort-Datenbanken) entweder mit KeePass oder mit dieser App erstellen. Das Dateiformat ist zu 100 % kompatibel und sollte in beiden Anwendungen nutzbar sein.",
"setHelpProblems": "Probleme?",
"setHelpProblems1": "Falls etwas schief geht, {}",
"setHelpProblems2": "oder {}",
@ -612,14 +631,15 @@
"setHelpUpdates": "Neuigkeiten",
"setHelpTwitter": "Twitter",
"dropboxSetupDesc": "Etwas Konfiguration ist notwendig, um Dropbox in einer selbst gehosteten Web-App zu verwenden. Bitte erstellen Sie eine eigene Dropbox-App und tragen Sie ihren App-Schlüssel unten ein.",
"dropboxAppKey": "Dropbox App-Schlüssel",
"dropboxAppKey": "Dropbox-App-Schlüssel",
"dropboxAppKeyDesc": "Kopieren Sie den Schlüssel aus Ihrer Dropbox-App (Entwicklereinstellungen)",
"dropboxAppSecret": "Dropbox App Secret",
"dropboxAppSecretDesc": "Das App Secret ist neben dem App Key zu finden",
"dropboxAppKeyHint": "Ihr App-Schlüssel",
"dropboxAppSecret": "Dropbox-App-Secret",
"dropboxAppSecretDesc": "Das App-Secret ist neben dem App-Schlüssel zu finden",
"dropboxFolder": "App-Ordner",
"dropboxFolderDesc": "Wenn Ihre App mit der gesamten Dropbox verlinkt ist (anstatt mit einem bestimmten Ordner), geben Sie hier den Pfad zum Ordner mit Ihren KDBX-Dateien an.",
"dropboxFolderSettingsDesc": "Wählen Sie einen Ordner in Ihrer Dropbox, in dem die Dateien gespeichert werden (standardmäßig das Stammverzeichnis)",
"dropboxFolderPlaceholder": "Standard-Ordner",
"dropboxFolderPlaceholder": "Standardordner",
"dropboxLink": "App verlinken mit",
"dropboxLinkApp": "App-Ordner (Apps/KeeWeb)",
"dropboxLinkFull": "Gesamte Dropbox oder beliebiger Ordner",
@ -628,10 +648,10 @@
"webdavSaveMethod": "Speichermethode",
"webdavSaveMove": "Eine temporäre Datei hochladen und verschieben",
"webdavSavePut": "kdbx-Datei mittels PUT überschreiben",
"launcherSave": "Passwort-Datenbank speichern",
"launcherSave": "Passwortdatenbank speichern",
"launcherFileFilter": "KeePass-Dateien",
"authPopupRequired": "Pop-Ups blockiert",
"authPopupRequiredBody": "Bitte erlauben Sie Pop-Ups in Ihrem Browser.",
"authPopupRequired": "Pop-ups blockiert",
"authPopupRequiredBody": "Bitte erlauben Sie Pop-ups in Ihrem Browser.",
"exportFileInfo": "Dateiinformationen",
"exportHtmlName": "Name",
"exportHtmlDate": "Exportdatum",
@ -641,5 +661,13 @@
"importCsvRun": "Importieren",
"importIgnoreField": "Ignorieren",
"importTo": "Einträge werden importiert nach:",
"importNewFile": "Neue Datei"
"importNewFile": "Neue Datei",
"yubiKeyStuckError": "Der YubiKey scheint nicht mehr zu funktionieren. Auto-Repair kann in den App-Einstellungen aktiviert werden.",
"yubiKeyNoKeyHeader": "YubiKey benötigt",
"yubiKeyNoKeyBody": "Bitte den YubiKey mit der Seriennummer {} anschließen",
"yubiKeySlot": "Anschluss",
"yubiKeyTouchRequestedHeader": "Bitte den YubiKey berühren",
"yubiKeyTouchRequestedBody": "Bitte den YubiKey mit der Seriennummer {} berühren",
"yubiKeyDisabledErrorHeader": "USB ist deaktiviert",
"yubiKeyDisabledErrorBody": "YubiKey ist zum Öffnen dieser Datei erforderlich. Bitte USB-Geräte in den Einstellungen aktivieren."
}

View File

@ -397,6 +397,8 @@
"setGenDownloadAndRestart": "Télécharger la mise à jour et redémarrer",
"setGenAppearance": "Apparence",
"setGenTheme": "Thème",
"setGenThemeDark": "Sombre",
"setGenThemeLight": "Clair",
"setGenThemeFb": "Bleu plat",
"setGenThemeDb": "Marron foncé",
"setGenThemeWh": "Blanc",
@ -404,6 +406,7 @@
"setGenThemeHc": "Contraste élevé",
"setGenThemeSd": "Solarized dark",
"setGenThemeSl": "Solarized light",
"setGenMoreThemes": "Plus de thèmes",
"setGenThemeMacDark": "Mode sombre de macOS",
"setGenLocale": "Langue",
"setGenLocOther": "d'autres langues sont disponibles en tant que modules",
@ -648,6 +651,8 @@
"webdavSaveMethod": "Méthode de sauvegarde",
"webdavSaveMove": "Envoyer un fichier temporaire et le déplacer",
"webdavSavePut": "Ecraser le fichier kdbx avec PUT",
"webdavNoLastModified": "L'entête HTTP \"Last-Modified\" est absent",
"webdavStatReload": "Toujours recharger le fichier au lieu de se fier à l'entête HTTP \"Last-Modified\"",
"launcherSave": "Sauvegarder base des mots de passe",
"launcherFileFilter": "Fichiers Keepass",
"authPopupRequired": "Les pop-up sont bloquées",
@ -667,5 +672,8 @@
"yubiKeyNoKeyBody": "Merci d'insérer votre YubiKey avec le numéro de série {}",
"yubiKeySlot": "port",
"yubiKeyTouchRequestedHeader": "Touchez votre YubiKey",
"yubiKeyTouchRequestedBody": "Merci de toucher votre YubiKey avec le numéro de série {}"
"yubiKeyTouchRequestedBody": "Merci de toucher votre YubiKey avec le numéro de série {}",
"yubiKeyDisabledErrorHeader": "L'USB est désactivé",
"yubiKeyDisabledErrorBody": "Yubikey est nécessaire pour ouvrir ce fichier, merci d'activer les appareils USB dans les paramètres",
"yubiKeyErrorWithCode": "Erreur Yubikey code {}."
}

View File

@ -16,7 +16,7 @@ import { YubiKeyOtpModel } from 'models/external/yubikey-otp-model';
import { MenuModel } from 'models/menu/menu-model';
import { PluginManager } from 'plugins/plugin-manager';
import { Features } from 'util/features';
import { DateFormat } from 'util/formatting/date-format';
import { DateFormat } from 'comp/i18n/date-format';
import { UrlFormat } from 'util/formatting/url-format';
import { IdGenerator } from 'util/generators/id-generator';
import { Locale } from 'util/locale';

View File

@ -21,6 +21,15 @@ class AppSettingsModel extends Model {
if (data.rememberKeyFiles === true) {
data.rememberKeyFiles = 'data';
}
if (data.locale === 'en') {
data.locale = 'en-US';
}
if (data.theme === 'macdark') {
data.theme = 'dark';
}
if (data.theme === 'wh') {
data.theme = 'light';
}
}
save() {

View File

@ -48,7 +48,7 @@ function getIcon(ext) {
case 'log':
case 'rtf':
case 'pem':
return 'file-text-o';
return 'file-alt';
case 'html':
case 'htm':
case 'js':
@ -74,9 +74,9 @@ function getIcon(ext) {
case 'yml':
case 'asm':
case 'bat':
return 'file-code-o';
return 'file-code';
case 'pdf':
return 'file-pdf-o';
return 'file-pdf';
case 'zip':
case 'rar':
case 'bz':
@ -89,16 +89,16 @@ function getIcon(ext) {
case 'ace':
case 'dmg':
case 'jar':
return 'file-archive-o';
return 'file-archive';
case 'doc':
case 'docx':
return 'file-word-o';
return 'file-word';
case 'xls':
case 'xlsx':
return 'file-excel-o';
return 'file-excel';
case 'ppt':
case 'pptx':
return 'file-powerpoint-o';
return 'file-powerpoint';
case 'jpeg':
case 'jpg':
case 'png':
@ -108,7 +108,7 @@ function getIcon(ext) {
case 'svg':
case 'ico':
case 'psd':
return 'file-image-o';
return 'file-image';
case 'avi':
case 'mp4':
case '3gp':
@ -117,13 +117,13 @@ function getIcon(ext) {
case 'mpeg':
case 'mpg':
case 'mpe':
return 'file-video-o';
return 'file-video';
case 'mp3':
case 'wav':
case 'flac':
return 'file-audio-o';
return 'file-audio';
}
return 'file-o';
return 'file';
}
function getMimeType(ext) {

View File

@ -71,7 +71,7 @@ class YubiKeyOtpModel extends ExternalOtpDeviceModel {
id: this.entryId(code.title, code.user),
device: this,
deviceSubId: serial,
icon: 'clock-o',
icon: 'clock',
title: code.title,
user: code.user,
needsTouch: code.needsTouch

View File

@ -47,6 +47,7 @@ MenuItemModel.defineModelProperties({
shortcut: null,
options: null,
cls: null,
iconCls: null,
disabled: false,
visible: true,
drag: false,
@ -57,7 +58,8 @@ MenuItemModel.defineModelProperties({
defaultItem: false,
page: null,
editable: false,
file: null
file: null,
section: null
});
export { MenuItemModel };

View File

@ -42,7 +42,7 @@ class MenuModel extends Model {
this.trashSection = new MenuSectionModel([
{
locTitle: 'menuTrash',
icon: 'trash',
icon: 'trash-alt',
shortcut: Keys.DOM_VK_D,
filterKey: 'trash',
filterValue: true,
@ -51,7 +51,7 @@ class MenuModel extends Model {
]);
Colors.AllColors.forEach((color) => {
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
});
}

View File

@ -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';
}
}

View File

@ -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) {

View File

@ -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) {

View File

@ -82,7 +82,8 @@ class StorageDropbox extends StorageBase {
clientSecret: this._getSecret(),
pkce: true,
width: 600,
height: 400
height: 400,
urlParams: { 'token_access_type': 'offline' }
};
}

View File

@ -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';

View File

@ -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';

View File

@ -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 };

View File

@ -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) {

View File

@ -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;

View File

@ -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 };

View File

@ -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);
}

View File

@ -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);
}

View File

@ -241,7 +241,7 @@ class AutoTypeSelectView extends View {
if (entry.fields.otp) {
options.push({
value: '{TOTP}',
icon: 'clock-o',
icon: 'clock',
text: Locale.autoTypeSelectionOtp
});
}

View File

@ -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';

View File

@ -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';

View File

@ -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 }));
}

View File

@ -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);

View File

@ -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();
}

View File

@ -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' });
}

View File

@ -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;
};

View File

@ -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) => {

View File

@ -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');

View File

@ -20,7 +20,6 @@ class MenuView extends View {
minWidth = 130;
maxWidth = 300;
autoWidth = 150;
constructor(model, options) {
super(model, options);

View File

@ -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 };

View File

@ -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

View File

@ -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,

View File

@ -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) {

View File

@ -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

View File

@ -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) +

View File

@ -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';

View File

@ -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 };

View File

@ -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() {

View File

@ -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;

View File

@ -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;
}
}
}

View File

@ -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 {

View File

@ -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;

View File

@ -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 {

View File

@ -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;

View File

@ -17,7 +17,7 @@
&__content,
&__buttons {
padding: $base-padding;
padding: $medium-padding;
}
&__icon {

View File

@ -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);
}
}
}
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -6,7 +6,7 @@
justify-content: flex-start;
width: 100%;
user-select: none;
padding: $base-padding;
padding: $medium-padding;
&__space {
flex: 1;

View File

@ -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';

View File

@ -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);
}

View File

@ -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 {

View File

@ -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;

View File

@ -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();

View File

@ -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);
}

Some files were not shown because too many files have changed in this diff Show More