diff --git a/app/templates/settings/settings-help.hbs b/app/templates/settings/settings-help.hbs
index 59a877fd..3224ab96 100644
--- a/app/templates/settings/settings-help.hbs
+++ b/app/templates/settings/settings-help.hbs
@@ -18,9 +18,9 @@
diff --git a/app/templates/settings/settings.hbs b/app/templates/settings/settings.hbs
index 7c50af48..335eefaf 100644
--- a/app/templates/settings/settings.hbs
+++ b/app/templates/settings/settings.hbs
@@ -1,6 +1,6 @@
- {{res 'retToApp'}}
+ {{res 'retToApp'}}
diff --git a/app/templates/storage-file-list.hbs b/app/templates/storage-file-list.hbs
index 71375dab..9780bdd9 100644
--- a/app/templates/storage-file-list.hbs
+++ b/app/templates/storage-file-list.hbs
@@ -15,7 +15,7 @@
{{#each files as |file|}}
{{#unless file.dir}}
-
+
{{file.name}}
{{/unless}}
diff --git a/app/templates/tag.hbs b/app/templates/tag.hbs
index 75e7f463..11dbd982 100644
--- a/app/templates/tag.hbs
+++ b/app/templates/tag.hbs
@@ -1,6 +1,6 @@
- {{res 'retToApp'}}
+ {{res 'retToApp'}}
{{res 'tagTitle'}}
@@ -10,6 +10,6 @@
-
+
diff --git a/build/loaders/fontawesome-loader.js b/build/loaders/fontawesome-loader.js
new file mode 100644
index 00000000..4eedcfa6
--- /dev/null
+++ b/build/loaders/fontawesome-loader.js
@@ -0,0 +1,108 @@
+const fs = require('fs');
+const path = require('path');
+
+const SVGIcons2SVGFontStream = require('svgicons2svgfont');
+const svg2ttf = require('svg2ttf');
+const wawoff2 = require('wawoff2');
+
+const svgBaseDir = path.resolve('node_modules/@fortawesome/fontawesome-free/svgs/');
+const svgDirs = ['brands', 'regular', 'solid']
+ .map((dir) => path.join(svgBaseDir, dir))
+ .concat('graphics/svg');
+
+const allIcons = {};
+
+for (const svgDir of svgDirs) {
+ const suffix = svgDir.endsWith('regular') ? '-o' : '';
+ fs.readdirSync(path.join(svgDir))
+ .filter((icon) => icon.endsWith('.svg'))
+ .forEach((icon) => {
+ const svgIconPath = path.join(svgDir, icon);
+ const iconName = icon.substr(0, icon.length - 4) + suffix;
+ allIcons[iconName] = svgIconPath;
+ });
+}
+
+module.exports = function makeFontAwesomeWoff2() {
+ const callback = this.async();
+ if (this.cacheable) {
+ this.cacheable();
+ }
+
+ const iconFontScssPath = path.resolve('app/styles/base/_icon-font.scss');
+ this.addDependency(iconFontScssPath);
+
+ fs.readFile(iconFontScssPath, 'utf-8', async (err, scssSource) => {
+ if (err) {
+ return callback(err);
+ }
+ process.stdout.write('Building fontawesome.woff2... ');
+ const startTime = Date.now();
+ try {
+ const { fontData, iconsCount } = await buildFont(this, scssSource);
+ const kb = (fontData.byteLength / 1024).toFixed(2);
+ const time = Date.now() - startTime;
+ process.stdout.write(`ok: ${time}ms, ${iconsCount} icons, ${kb} KiB\n`);
+ const fontCss = fontData.toString('base64');
+ callback(null, `module.exports = "data:font/woff2;base64,${fontCss}"`);
+ } catch (ex) {
+ process.stdout.write('error\n');
+ callback(ex);
+ }
+ });
+};
+
+function buildFont(loader, scssSource) {
+ const includedIcons = {};
+ const includedIconList = [...scssSource.matchAll(/\n\$fa-var-([\w-]+):/g)].map(
+ ([, name]) => name
+ );
+ for (const iconName of includedIconList) {
+ if (includedIcons[iconName]) {
+ throw new Error(`Duplicate icon: $fa-var-${iconName}`);
+ }
+ if (!allIcons[iconName]) {
+ throw new Error(`Icon not found: "${iconName}"`);
+ }
+ includedIcons[iconName] = true;
+ }
+
+ const fontStream = new SVGIcons2SVGFontStream({
+ fontName: 'Font Awesome 5 Free',
+ round: 10e12,
+ log() {}
+ });
+
+ const fontData = [];
+ fontStream.on('data', (chunk) => fontData.push(chunk));
+
+ let charCode = 0xf000;
+ for (const iconName of includedIconList) {
+ ++charCode;
+ const svgIconPath = allIcons[iconName];
+
+ loader.addDependency(svgIconPath);
+
+ const glyph = fs.createReadStream(svgIconPath);
+ glyph.metadata = { name: iconName, unicode: [String.fromCharCode(charCode)] };
+
+ fontStream.write(glyph);
+ }
+ fontStream.end();
+
+ return new Promise((resolve, reject) => {
+ fontStream.on('end', async () => {
+ try {
+ let data = Buffer.concat(fontData);
+ data = Buffer.from(svg2ttf(data.toString('utf8')).buffer);
+ data = Buffer.from(await wawoff2.compress(data));
+
+ resolve({ fontData: data, iconsCount: includedIconList.length });
+ } catch (ex) {
+ reject(ex);
+ }
+ });
+ });
+}
+
+module.exports.raw = true;
diff --git a/build/loaders/scss-add-icons-loader.js b/build/loaders/scss-add-icons-loader.js
new file mode 100644
index 00000000..9fb0c362
--- /dev/null
+++ b/build/loaders/scss-add-icons-loader.js
@@ -0,0 +1,23 @@
+const fs = require('fs');
+const path = require('path');
+
+module.exports = function loadScss(scssSource) {
+ const callback = this.async();
+
+ const iconFontScssPath = path.resolve('app/styles/base/_icon-font.scss');
+
+ this.addDependency(iconFontScssPath);
+
+ fs.readFile(iconFontScssPath, 'utf-8', (err, iconFontScssSource) => {
+ if (err) {
+ return callback(err);
+ }
+ scssSource +=
+ '\n' +
+ [...iconFontScssSource.matchAll(/\n\$fa-var-([\w-]+):/g)]
+ .map(([, name]) => name)
+ .map((icon) => `.fa-${icon}:before { content: $fa-var-${icon}; }`)
+ .join('\n');
+ callback(null, scssSource);
+ });
+};
diff --git a/build/webpack.config.js b/build/webpack.config.js
index 291ad4e8..2727d44e 100644
--- a/build/webpack.config.js
+++ b/build/webpack.config.js
@@ -62,7 +62,7 @@ function config(options) {
'public-key.pem': path.join(rootDir, 'app/resources/public-key.pem'),
'public-key-new.pem': path.join(rootDir, 'app/resources/public-key-new.pem'),
'demo.kdbx': path.join(rootDir, 'app/resources/Demo.kdbx'),
- svg: path.join(rootDir, 'app/resources/svg')
+ 'fontawesome.woff2': '@fortawesome/fontawesome-free/webfonts/fa-regular-400.woff2'
},
fallback: {
console: false,
@@ -77,6 +77,9 @@ function config(options) {
moment: false
}
},
+ resolveLoader: {
+ modules: ['node_modules', path.join(__dirname, 'loaders')]
+ },
module: {
rules: [
{
@@ -166,17 +169,13 @@ function config(options) {
MiniCssExtractPlugin.loader,
{ loader: 'css-loader', options: { sourceMap: devMode } },
{ loader: 'postcss-loader', options: { sourceMap: devMode } },
- { loader: 'sass-loader', options: { sourceMap: devMode } }
+ { loader: 'sass-loader', options: { sourceMap: devMode } },
+ { loader: 'scss-add-icons-loader' }
]
},
- {
- test: /fonts[\\/].*\.(woff|ttf|eot|svg)$/,
- use: ['base64-inline-loader', 'ignore-loader']
- },
- { test: /\.woff2$/, loader: 'base64-inline-loader' },
+ { test: /fontawesome.*\.woff2$/, loader: 'fontawesome-loader' },
{ test: /\.pem$/, loader: 'raw-loader' },
- { test: /\.kdbx$/, loader: 'base64-loader' },
- { test: /\.svg$/, loader: 'raw-loader' }
+ { test: /\.kdbx$/, loader: 'base64-loader' }
]
},
optimization: {
diff --git a/graphics/svg/keeweb.svg b/graphics/svg/keeweb.svg
new file mode 100644
index 00000000..c9eb4c9b
--- /dev/null
+++ b/graphics/svg/keeweb.svg
@@ -0,0 +1 @@
+
diff --git a/graphics/svg/onedrive.svg b/graphics/svg/onedrive.svg
new file mode 100644
index 00000000..20470e40
--- /dev/null
+++ b/graphics/svg/onedrive.svg
@@ -0,0 +1 @@
+
diff --git a/graphics/svg/usb-token.svg b/graphics/svg/usb-token.svg
new file mode 100644
index 00000000..9bf05e88
--- /dev/null
+++ b/graphics/svg/usb-token.svg
@@ -0,0 +1 @@
+
diff --git a/grunt.tasks.js b/grunt.tasks.js
index 24dba630..a010e989 100644
--- a/grunt.tasks.js
+++ b/grunt.tasks.js
@@ -9,7 +9,6 @@ module.exports = function (grunt) {
'copy:favicon',
'copy:icons',
'copy:manifest',
- 'copy:fonts',
'webpack:app',
'inline',
'htmlmin',
diff --git a/package-lock.json b/package-lock.json
index e759c0ae..254e59e7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1908,6 +1908,11 @@
}
}
},
+ "@fortawesome/fontawesome-free": {
+ "version": "5.15.1",
+ "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-5.15.1.tgz",
+ "integrity": "sha512-OEdH7SyC1suTdhBGW91/zBfR6qaIhThbcN8PUXtXilY4GYnSBbVqOntdHbC1vXwsDnX0Qix2m2+DSU1J51ybOQ=="
+ },
"@google-cloud/common": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-3.5.0.tgz",
@@ -4869,6 +4874,11 @@
}
}
},
+ "cubic2quad": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/cubic2quad/-/cubic2quad-1.1.1.tgz",
+ "integrity": "sha1-abGcYaP1tB7PLx1fro+wNBWqixU="
+ },
"cuint": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/cuint/-/cuint-0.2.2.tgz",
@@ -6775,11 +6785,6 @@
}
}
},
- "font-awesome": {
- "version": "4.7.0",
- "resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz",
- "integrity": "sha1-j6jPBBGhoxr9B7BtKQK7n8gVoTM="
- },
"for-in": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
@@ -7583,6 +7588,11 @@
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
"integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="
},
+ "geometry-interfaces": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/geometry-interfaces/-/geometry-interfaces-1.1.4.tgz",
+ "integrity": "sha512-qD6OdkT6NcES9l4Xx3auTpwraQruU7dARbQPVO71MKvkGYw5/z/oIiGymuFXrRaEQa5Y67EIojUpaLeGEa5hGA=="
+ },
"get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
@@ -10614,6 +10624,11 @@
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
"integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
},
+ "microbuffer": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/microbuffer/-/microbuffer-1.0.0.tgz",
+ "integrity": "sha1-izgy7UDIfVH0e7I0kTppinVtGdI="
+ },
"micromatch": {
"version": "2.3.11",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz",
@@ -11223,6 +11238,14 @@
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
"integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc="
},
+ "neatequal": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/neatequal/-/neatequal-1.0.0.tgz",
+ "integrity": "sha1-LuEhG8n6bkxVcV/SELsFYC6xrjs=",
+ "requires": {
+ "varstream": "^0.3.2"
+ }
+ },
"negotiator": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
@@ -15357,6 +15380,16 @@
}
}
},
+ "string.fromcodepoint": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/string.fromcodepoint/-/string.fromcodepoint-0.2.1.tgz",
+ "integrity": "sha1-jZeDM8C8klOPUPOD5IiPPlYZ1lM="
+ },
+ "string.prototype.codepointat": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz",
+ "integrity": "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg=="
+ },
"string.prototype.trimend": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.2.tgz",
@@ -15610,6 +15643,41 @@
"has-flag": "^3.0.0"
}
},
+ "svg-pathdata": {
+ "version": "5.0.5",
+ "resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-5.0.5.tgz",
+ "integrity": "sha512-TAAvLNSE3fEhyl/Da19JWfMAdhSXTYeviXsLSoDT1UM76ADj5ndwAPX1FKQEgB/gFMPavOy6tOqfalXKUiXrow=="
+ },
+ "svg2ttf": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/svg2ttf/-/svg2ttf-5.0.0.tgz",
+ "integrity": "sha512-xv4ERtuuaY+RQu7G57nKF7agrAGP+Bqmox72+kIbKek5vyAo02Dus901fR995p0E6adc27+kRhPVynDdShUWWw==",
+ "requires": {
+ "argparse": "^1.0.6",
+ "cubic2quad": "^1.0.0",
+ "lodash": "^4.17.10",
+ "microbuffer": "^1.0.0",
+ "svgpath": "^2.1.5",
+ "xmldom": "~0.1.22"
+ }
+ },
+ "svgicons2svgfont": {
+ "version": "9.1.1",
+ "resolved": "https://registry.npmjs.org/svgicons2svgfont/-/svgicons2svgfont-9.1.1.tgz",
+ "integrity": "sha512-iOj7lqHP/oMrLg7S2Iv89LOJUfmIuePefXcs5ul4IsKwcYvL/T/Buahz+nQQJygyuvEMBBXqnCRmnvJggHeJzA==",
+ "requires": {
+ "commander": "^2.12.2",
+ "geometry-interfaces": "^1.1.4",
+ "glob": "^7.1.2",
+ "neatequal": "^1.0.0",
+ "readable-stream": "^2.3.3",
+ "sax": "^1.2.4",
+ "string.fromcodepoint": "^0.2.1",
+ "string.prototype.codepointat": "^0.2.0",
+ "svg-pathdata": "^5.0.0",
+ "transformation-matrix-js": "^2.7.1"
+ }
+ },
"svgo": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz",
@@ -15630,6 +15698,11 @@
"util.promisify": "~1.0.0"
}
},
+ "svgpath": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/svgpath/-/svgpath-2.3.0.tgz",
+ "integrity": "sha512-N/4UDu3Y2ICik0daMmFW1tplw0XPs1nVIEVYkTiQfj9/JQZeEtAKaSYwheCwje1I4pQ5r22fGpoaNIvGgsyJyg=="
+ },
"table": {
"version": "5.4.6",
"resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz",
@@ -16079,6 +16152,11 @@
"punycode": "^2.1.1"
}
},
+ "transformation-matrix-js": {
+ "version": "2.7.6",
+ "resolved": "https://registry.npmjs.org/transformation-matrix-js/-/transformation-matrix-js-2.7.6.tgz",
+ "integrity": "sha512-1CxDIZmCQ3vA0GGnkdMQqxUXVm3xXAFmglPYRS1hr37LzSg22TC7QAWOT38OmdUvMEs/rqcnkFoAsqvzdiluDg=="
+ },
"trim-newlines": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz",
@@ -16594,6 +16672,37 @@
"spdx-expression-parse": "^3.0.0"
}
},
+ "varstream": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/varstream/-/varstream-0.3.2.tgz",
+ "integrity": "sha1-GKxklHZfP/GjWtmkvgU77BiKXeE=",
+ "requires": {
+ "readable-stream": "^1.0.33"
+ },
+ "dependencies": {
+ "isarray": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+ "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
+ },
+ "readable-stream": {
+ "version": "1.1.14",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
+ "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
+ "requires": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.1",
+ "isarray": "0.0.1",
+ "string_decoder": "~0.10.x"
+ }
+ },
+ "string_decoder": {
+ "version": "0.10.31",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
+ "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
+ }
+ }
+ },
"vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
@@ -16623,6 +16732,14 @@
"graceful-fs": "^4.1.2"
}
},
+ "wawoff2": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wawoff2/-/wawoff2-1.0.2.tgz",
+ "integrity": "sha512-qxuTwf5tAP/XojrRc6cmR0hGvqgD3XUxv2fzfzURKPDfE7AeHmtRuankVxdJ4DRdSKXaE5QlyJT49yBis2vb6Q==",
+ "requires": {
+ "argparse": "^1.0.6"
+ }
+ },
"wbuf": {
"version": "1.7.3",
"resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz",
diff --git a/package.json b/package.json
index a8c348cd..9b9bf6a5 100644
--- a/package.json
+++ b/package.json
@@ -15,6 +15,7 @@
"@babel/plugin-proposal-class-properties": "^7.12.1",
"@babel/plugin-proposal-optional-chaining": "^7.12.1",
"@babel/preset-env": "^7.12.1",
+ "@fortawesome/fontawesome-free": "^5.15.1",
"@keeweb/keeweb-native-modules": "https://github.com/keeweb/keeweb-native-modules/releases/download/0.4.1/keeweb-native-modules.tgz",
"adm-zip": "^0.4.16",
"argon2-browser": "1.15.2",
@@ -44,7 +45,6 @@
"eslint-plugin-promise": "4.2.1",
"eslint-plugin-standard": "4.1.0",
"exports-loader": "1.1.1",
- "font-awesome": "4.7.0",
"fs-extra": "^9.0.1",
"grunt": "1.3.0",
"grunt-chmod": "^1.1.1",
@@ -91,9 +91,12 @@
"string-replace-loader": "^3.0.1",
"strip-sourcemap-loader": "0.0.1",
"sumchecker": "^3.0.1",
+ "svg2ttf": "^5.0.0",
+ "svgicons2svgfont": "^9.1.1",
"terser-webpack-plugin": "^5.0.3",
"time-grunt": "2.0.0",
"url-loader": "^4.1.1",
+ "wawoff2": "^1.0.2",
"webpack": "^5.6.0",
"webpack-bundle-analyzer": "^4.1.0",
"webpack-dev-server": "^3.11.0"