diff --git a/lang/en.json b/lang/en.json index ba66be4b..1ac783ae 100644 --- a/lang/en.json +++ b/lang/en.json @@ -172,5 +172,10 @@ "This action cannot be undone.": "This action cannot be undone.", "Yes": "Yes", "Toggle time filter for places": "Toggle time filter for places", - "The search index needs to be rebuilt.": "The search index needs to be rebuilt." + "The search index needs to be rebuilt.": "The search index needs to be rebuilt.", + "Ask something about your ancestors": "Ask something about your ancestors", + "Chat messages": "Chat messages", + "Manage semantic search index": "Manage semantic search index", + "Update semantic search index": "Update semantic search index", + "Updating the semantic search index requires substantial time and computational resources. Run this operation only when necessary.": "Updating the semantic search index requires substantial time and computational resources. Run this operation only when necessary." } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index ddf5dad8..4deefbcf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -73,6 +73,7 @@ "eslint-config-prettier": "^7.2.0", "eslint-config-standard": "^16.0.3", "eslint-plugin-import": "^2.24.2", + "eslint-plugin-lit-a11y": "^4.1.4", "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^5.1.0", "fetch-mock": "^9.11.0", @@ -1734,18 +1735,24 @@ } }, "node_modules/@babel/runtime-corejs3": { - "version": "7.15.3", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.15.3.tgz", - "integrity": "sha512-30A3lP+sRL6ml8uhoJSs+8jwpKzbw8CqBvDc1laeptxPm5FahumJxirigcbD2qTs71Sonvj1cyZB0OKGAmxQ+A==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.25.0.tgz", + "integrity": "sha512-BOehWE7MgQ8W8Qn0CQnMtg2tHPHPulcS/5AVpFvs2KCK1ET+0WqZqPvnpRpFN81gYoFopdIEJX9Sgjw3ZBccPg==", "dev": true, "dependencies": { - "core-js-pure": "^3.16.0", - "regenerator-runtime": "^0.13.4" + "core-js-pure": "^3.30.2", + "regenerator-runtime": "^0.14.0" }, "engines": { "node": ">=6.9.0" } }, + "node_modules/@babel/runtime-corejs3/node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "dev": true + }, "node_modules/@babel/template": { "version": "7.22.15", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", @@ -3604,6 +3611,47 @@ "eslint-plugin-wc": "^1.2.0" } }, + "node_modules/@open-wc/eslint-config/node_modules/aria-query": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", + "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.10.2", + "@babel/runtime-corejs3": "^7.10.2" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/@open-wc/eslint-config/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/@open-wc/eslint-config/node_modules/eslint-plugin-lit-a11y": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-lit-a11y/-/eslint-plugin-lit-a11y-1.1.0.tgz", + "integrity": "sha512-reJqT0UG/Y8OC2z7pfgm0ODK1D6o5TgQpGdlgN1ja0HjdREXLqFVoYiEv013oNx3kBhTUaLlic64rRNw+386xw==", + "dev": true, + "dependencies": { + "aria-query": "^4.2.2", + "axe-core": "^4.3.3", + "axobject-query": "^2.2.0", + "dom5": "^3.0.1", + "emoji-regex": "^9.2.0", + "eslint": "^7.6.0", + "eslint-rule-extender": "0.0.1", + "intl-list-format": "^1.0.3", + "parse5": "^5.1.1", + "parse5-htmlparser2-tree-adapter": "^6.0.1", + "requireindex": "~1.2.0" + }, + "peerDependencies": { + "eslint": ">= 5" + } + }, "node_modules/@open-wc/rollup-plugin-html": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/@open-wc/rollup-plugin-html/-/rollup-plugin-html-1.2.5.tgz", @@ -3815,6 +3863,15 @@ "magic-string": "^0.25.0" } }, + "node_modules/@thepassle/axobject-query": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@thepassle/axobject-query/-/axobject-query-4.0.0.tgz", + "integrity": "sha512-/LHo+2jOdxs2WtbGocr3/lDSzsnjgCV6DSoBf4Y1Q0D24Hu67NPWuneoJimfHu5auqqSWi1fAvtln2013VxVqg==", + "dev": true, + "dependencies": { + "dequal": "^2.0.3" + } + }, "node_modules/@types/accepts": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.5.tgz", @@ -5461,16 +5518,12 @@ } }, "node_modules/aria-query": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", - "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", "dev": true, "dependencies": { - "@babel/runtime": "^7.10.2", - "@babel/runtime-corejs3": "^7.10.2" - }, - "engines": { - "node": ">=6.0" + "dequal": "^2.0.3" } }, "node_modules/arr-union": { @@ -6733,9 +6786,9 @@ } }, "node_modules/core-js-pure": { - "version": "3.16.4", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.16.4.tgz", - "integrity": "sha512-bY1K3/1Jy9D8Jd12eoeVahNXHLfHFb4TXWI8SQ4y8bImR9qDPmGITBAfmcffTkgUvbJn87r8dILOTWW5kZzkgA==", + "version": "3.38.1", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.38.1.tgz", + "integrity": "sha512-BY8Etc1FZqdw1glX0XNOq2FDwfrg/VGqoZOZCdaL+UmdaqDwQwYXkMJT4t6In+zfEfOJDcM9T0KdbBeJg8KKCQ==", "dev": true, "hasInstallScript": true, "funding": { @@ -7378,6 +7431,15 @@ "node": ">= 0.6.0" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/destroy": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", @@ -7566,9 +7628,9 @@ "dev": true }, "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz", + "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==", "dev": true }, "node_modules/encodeurl": { @@ -8076,9 +8138,9 @@ "dev": true }, "node_modules/eslint-plugin-lit": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-lit/-/eslint-plugin-lit-1.5.1.tgz", - "integrity": "sha512-pYB0QM11uyOk5L55QfGhBmWi8a56PkNsnx+zVpY4bxz9YVquEo4BeRnFmf9AwFyT89rhGud9QruFhM2xJ4piwg==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-lit/-/eslint-plugin-lit-1.14.0.tgz", + "integrity": "sha512-J4w+CgO31621GreLFCdTUbTr5yeV2/RJ/M0myw0dykD5p9FGGIRLityQiNa6SG+JpVbmeQTQPJy4pNFmiurJ/w==", "dev": true, "dependencies": { "parse5": "^6.0.1", @@ -8093,37 +8155,49 @@ } }, "node_modules/eslint-plugin-lit-a11y": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-lit-a11y/-/eslint-plugin-lit-a11y-1.0.1.tgz", - "integrity": "sha512-c+GgGSXb9HMgbzJGp0yl+msHk2rBXcA7KwbobbLonSXdHm6ln7zRwAEj4i7527FOaCKkhxiN6RXfOJcZT1/Bow==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-lit-a11y/-/eslint-plugin-lit-a11y-4.1.4.tgz", + "integrity": "sha512-u39vE1KNOzO99Nrz51oVGY0Iauzf59l9tZgBluE/cU1l86X9/peBMQHUAeGC536dlV4acFYj5yq/VLPsalvnzA==", "dev": true, "dependencies": { - "aria-query": "^4.2.2", - "axe-core": "^4.0.2", - "axobject-query": "^2.2.0", + "@thepassle/axobject-query": "^4.0.0", + "aria-query": "^5.1.3", + "axe-core": "^4.3.3", "dom5": "^3.0.1", - "emoji-regex": "^9.0.0", - "eslint": "^7.6.0", + "emoji-regex": "^10.2.1", + "eslint-plugin-lit": "^1.10.1", "eslint-rule-extender": "0.0.1", - "intl-list-format": "^1.0.3", - "parse5": "^5.1.1", - "parse5-htmlparser2-tree-adapter": "^6.0.0", - "requireindex": "~1.1.0" - }, - "engines": { - "node": ">=0.10.0" + "language-tags": "^1.0.5", + "parse5": "^7.1.2", + "parse5-htmlparser2-tree-adapter": "^6.0.1", + "requireindex": "~1.2.0" }, "peerDependencies": { "eslint": ">= 5" } }, - "node_modules/eslint-plugin-lit-a11y/node_modules/requireindex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/requireindex/-/requireindex-1.1.0.tgz", - "integrity": "sha1-5UBLgVV+91225JxacgBIk/4D4WI=", + "node_modules/eslint-plugin-lit-a11y/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "dev": true, "engines": { - "node": ">=0.10.5" + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/eslint-plugin-lit-a11y/node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dev": true, + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" } }, "node_modules/eslint-plugin-lit/node_modules/parse5": { @@ -10776,6 +10850,24 @@ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, + "node_modules/language-subtag-registry": { + "version": "0.3.23", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", + "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==", + "dev": true + }, + "node_modules/language-tags": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", + "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", + "dev": true, + "dependencies": { + "language-subtag-registry": "^0.3.20" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/leaflet": { "version": "1.9.4", "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", @@ -17914,13 +18006,21 @@ } }, "@babel/runtime-corejs3": { - "version": "7.15.3", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.15.3.tgz", - "integrity": "sha512-30A3lP+sRL6ml8uhoJSs+8jwpKzbw8CqBvDc1laeptxPm5FahumJxirigcbD2qTs71Sonvj1cyZB0OKGAmxQ+A==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.25.0.tgz", + "integrity": "sha512-BOehWE7MgQ8W8Qn0CQnMtg2tHPHPulcS/5AVpFvs2KCK1ET+0WqZqPvnpRpFN81gYoFopdIEJX9Sgjw3ZBccPg==", "dev": true, "requires": { - "core-js-pure": "^3.16.0", - "regenerator-runtime": "^0.13.4" + "core-js-pure": "^3.30.2", + "regenerator-runtime": "^0.14.0" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "dev": true + } } }, "@babel/template": { @@ -19634,6 +19734,43 @@ "eslint-plugin-lit-a11y": "^1.0.1", "eslint-plugin-no-only-tests": "^2.4.0", "eslint-plugin-wc": "^1.2.0" + }, + "dependencies": { + "aria-query": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", + "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", + "dev": true, + "requires": { + "@babel/runtime": "^7.10.2", + "@babel/runtime-corejs3": "^7.10.2" + } + }, + "emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "eslint-plugin-lit-a11y": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-lit-a11y/-/eslint-plugin-lit-a11y-1.1.0.tgz", + "integrity": "sha512-reJqT0UG/Y8OC2z7pfgm0ODK1D6o5TgQpGdlgN1ja0HjdREXLqFVoYiEv013oNx3kBhTUaLlic64rRNw+386xw==", + "dev": true, + "requires": { + "aria-query": "^4.2.2", + "axe-core": "^4.3.3", + "axobject-query": "^2.2.0", + "dom5": "^3.0.1", + "emoji-regex": "^9.2.0", + "eslint": "^7.6.0", + "eslint-rule-extender": "0.0.1", + "intl-list-format": "^1.0.3", + "parse5": "^5.1.1", + "parse5-htmlparser2-tree-adapter": "^6.0.1", + "requireindex": "~1.2.0" + } + } } }, "@open-wc/rollup-plugin-html": { @@ -19812,6 +19949,15 @@ "magic-string": "^0.25.0" } }, + "@thepassle/axobject-query": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@thepassle/axobject-query/-/axobject-query-4.0.0.tgz", + "integrity": "sha512-/LHo+2jOdxs2WtbGocr3/lDSzsnjgCV6DSoBf4Y1Q0D24Hu67NPWuneoJimfHu5auqqSWi1fAvtln2013VxVqg==", + "dev": true, + "requires": { + "dequal": "^2.0.3" + } + }, "@types/accepts": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.5.tgz", @@ -21235,13 +21381,12 @@ } }, "aria-query": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", - "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", "dev": true, "requires": { - "@babel/runtime": "^7.10.2", - "@babel/runtime-corejs3": "^7.10.2" + "dequal": "^2.0.3" } }, "arr-union": { @@ -22180,9 +22325,9 @@ } }, "core-js-pure": { - "version": "3.16.4", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.16.4.tgz", - "integrity": "sha512-bY1K3/1Jy9D8Jd12eoeVahNXHLfHFb4TXWI8SQ4y8bImR9qDPmGITBAfmcffTkgUvbJn87r8dILOTWW5kZzkgA==", + "version": "3.38.1", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.38.1.tgz", + "integrity": "sha512-BY8Etc1FZqdw1glX0XNOq2FDwfrg/VGqoZOZCdaL+UmdaqDwQwYXkMJT4t6In+zfEfOJDcM9T0KdbBeJg8KKCQ==", "dev": true }, "cosmiconfig": { @@ -22668,6 +22813,12 @@ "integrity": "sha512-9YLIBURXj4DJMFALxXw9K3Y3rwb5Fk0X5/8ipCzaN84+gKxoHK43tVKRNakCQbiEx07E8Uwhuq21BpUagFhZ8w==", "dev": true }, + "dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true + }, "destroy": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", @@ -22823,9 +22974,9 @@ "dev": true }, "emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz", + "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==", "dev": true }, "encodeurl": { @@ -23352,9 +23503,9 @@ } }, "eslint-plugin-lit": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-lit/-/eslint-plugin-lit-1.5.1.tgz", - "integrity": "sha512-pYB0QM11uyOk5L55QfGhBmWi8a56PkNsnx+zVpY4bxz9YVquEo4BeRnFmf9AwFyT89rhGud9QruFhM2xJ4piwg==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-lit/-/eslint-plugin-lit-1.14.0.tgz", + "integrity": "sha512-J4w+CgO31621GreLFCdTUbTr5yeV2/RJ/M0myw0dykD5p9FGGIRLityQiNa6SG+JpVbmeQTQPJy4pNFmiurJ/w==", "dev": true, "requires": { "parse5": "^6.0.1", @@ -23371,29 +23522,38 @@ } }, "eslint-plugin-lit-a11y": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-lit-a11y/-/eslint-plugin-lit-a11y-1.0.1.tgz", - "integrity": "sha512-c+GgGSXb9HMgbzJGp0yl+msHk2rBXcA7KwbobbLonSXdHm6ln7zRwAEj4i7527FOaCKkhxiN6RXfOJcZT1/Bow==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-lit-a11y/-/eslint-plugin-lit-a11y-4.1.4.tgz", + "integrity": "sha512-u39vE1KNOzO99Nrz51oVGY0Iauzf59l9tZgBluE/cU1l86X9/peBMQHUAeGC536dlV4acFYj5yq/VLPsalvnzA==", "dev": true, "requires": { - "aria-query": "^4.2.2", - "axe-core": "^4.0.2", - "axobject-query": "^2.2.0", + "@thepassle/axobject-query": "^4.0.0", + "aria-query": "^5.1.3", + "axe-core": "^4.3.3", "dom5": "^3.0.1", - "emoji-regex": "^9.0.0", - "eslint": "^7.6.0", + "emoji-regex": "^10.2.1", + "eslint-plugin-lit": "^1.10.1", "eslint-rule-extender": "0.0.1", - "intl-list-format": "^1.0.3", - "parse5": "^5.1.1", - "parse5-htmlparser2-tree-adapter": "^6.0.0", - "requireindex": "~1.1.0" + "language-tags": "^1.0.5", + "parse5": "^7.1.2", + "parse5-htmlparser2-tree-adapter": "^6.0.1", + "requireindex": "~1.2.0" }, "dependencies": { - "requireindex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/requireindex/-/requireindex-1.1.0.tgz", - "integrity": "sha1-5UBLgVV+91225JxacgBIk/4D4WI=", + "entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "dev": true + }, + "parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dev": true, + "requires": { + "entities": "^4.4.0" + } } } }, @@ -25214,6 +25374,21 @@ } } }, + "language-subtag-registry": { + "version": "0.3.23", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", + "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==", + "dev": true + }, + "language-tags": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", + "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", + "dev": true, + "requires": { + "language-subtag-registry": "^0.3.20" + } + }, "leaflet": { "version": "1.9.4", "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", diff --git a/package.json b/package.json index 0e95eae3..25183492 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "eslint-config-prettier": "^7.2.0", "eslint-config-standard": "^16.0.3", "eslint-plugin-import": "^2.24.2", + "eslint-plugin-lit-a11y": "^4.1.4", "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^5.1.0", "fetch-mock": "^9.11.0", @@ -127,4 +128,4 @@ "pwa-helpers": "^0.9.1", "tippy.js": "^6.3.7" } -} \ No newline at end of file +} diff --git a/src/GrampsJs.js b/src/GrampsJs.js index 1453f2b0..bf22567c 100644 --- a/src/GrampsJs.js +++ b/src/GrampsJs.js @@ -524,6 +524,7 @@ export class GrampsJs extends LitElement {
@@ -547,6 +548,7 @@ export class GrampsJs extends LitElement { .canEdit="${this.canEdit}" .canViewPrivate="${this.canViewPrivate}" .canManageUsers="${this.canManageUsers}" + .canUseChat="${this.canUseChat}" > @@ -604,7 +606,8 @@ export class GrampsJs extends LitElement { this.addEventListener('drawer:toggle', this._toggleDrawer) window.addEventListener('keydown', event => this._handleKey(event)) document.addEventListener('visibilitychange', this._handleVisibilityChange) - window.addEventListener('online', this._handleOnline) + window.addEventListener('online', () => this._handleOnline()) + window.addEventListener('token:refresh', () => this._handleRefresh()) const browserLang = getBrowserLanguage() if (browserLang && !this.settings.lang) { @@ -834,13 +837,19 @@ export class GrampsJs extends LitElement { _handleVisibilityChange() { if (document.visibilityState === 'visible') { // refresh auth token when app becomes visible again - apiRefreshAuthToken() + this._handleRefresh() } } // eslint-disable-next-line class-methods-use-this _handleOnline() { - apiRefreshAuthToken() + this._handleRefresh() + } + + // eslint-disable-next-line class-methods-use-this + async _handleRefresh() { + await apiRefreshAuthToken() + this.setPermissions() } update(changed) { @@ -994,18 +1003,12 @@ export class GrampsJs extends LitElement { setPermissions() { const permissions = getPermissions() // If permissions is null, authorization is disabled and anything goes - if (permissions === null) { - this.canAdd = true - this.canEdit = true - this.canViewPrivate = true - // managing users not meaningful in this case - this.canManageUsers = false - } else { - this.canAdd = permissions.includes('AddObject') - this.canEdit = permissions.includes('EditObject') - this.canViewPrivate = permissions.includes('ViewPrivate') - this.canManageUsers = permissions.includes('EditOtherUser') - } + this.canAdd = permissions.includes('AddObject') + this.canEdit = permissions.includes('EditObject') + this.canViewPrivate = permissions.includes('ViewPrivate') + this.canManageUsers = permissions.includes('EditOtherUser') + this.canUseChat = + permissions.includes('UseChat') && this._dbInfo?.server?.chat } _(s) { diff --git a/src/SharedStyles.js b/src/SharedStyles.js index df49ab8e..84860023 100644 --- a/src/SharedStyles.js +++ b/src/SharedStyles.js @@ -306,6 +306,18 @@ export const sharedStyles = css` border-left-color: rgba(251, 192, 45, 0.7); } + .success { + color: #41ad49; + } + + .error { + color: #bf360c; + } + + .warn { + color: #f9a825; + } + @keyframes shine { to { background-position-x: -200%; diff --git a/src/api.js b/src/api.js index cea254bc..5126fb03 100644 --- a/src/api.js +++ b/src/api.js @@ -115,6 +115,33 @@ export function setRecentObjects(data) { localStorage.setItem('recentObjects', stringData) } +export function getChatHistory() { + try { + const string = localStorage.getItem('chatMessages') + const data = JSON.parse(string) + const tree = getTreeId() + if (tree) { + return data[tree] + } + return [] + } catch (e) { + return [] + } +} + +export function setChatHistory(data) { + const tree = getTreeId() + if (!tree) { + return + } + const stringDataAll = localStorage.getItem('chatMessages') + const objectDataAll = JSON.parse(stringDataAll) + const objectDataNew = {[tree]: data} + const objectData = {...objectDataAll, ...objectDataNew} + const stringData = JSON.stringify(objectData) + localStorage.setItem('chatMessages', stringData) +} + export async function apiResetPassword(username) { try { const resp = await fetch( diff --git a/src/components/GrampsjsButtonGroup.js b/src/components/GrampsjsButtonGroup.js new file mode 100644 index 00000000..d2af5670 --- /dev/null +++ b/src/components/GrampsjsButtonGroup.js @@ -0,0 +1,41 @@ +import {LitElement, html, css} from 'lit' + +import '@material/mwc-icon-button' + +import {sharedStyles} from '../SharedStyles.js' +import {GrampsjsTranslateMixin} from '../mixins/GrampsjsTranslateMixin.js' +import './GrampsjsTooltip.js' + +export class GrampsjsButtonGroup extends GrampsjsTranslateMixin(LitElement) { + static get styles() { + return [ + sharedStyles, + css` + #button-container { + --mdc-typography-button-font-size: 12px; + margin: 12px 0; + } + + #button-container div { + border: 1px solid var(--mdc-theme-primary); + opacity: 0.9; + border-radius: 8px; + display: inline-block; + padding: 4px; + } + `, + ] + } + + render() { + return html` +
+
+ +
+
+ ` + } +} + +window.customElements.define('grampsjs-button-group', GrampsjsButtonGroup) diff --git a/src/components/GrampsjsChat.js b/src/components/GrampsjsChat.js new file mode 100644 index 00000000..51dd0694 --- /dev/null +++ b/src/components/GrampsjsChat.js @@ -0,0 +1,263 @@ +import {html, css, LitElement} from 'lit' +import '@material/mwc-button' + +import {sharedStyles} from '../SharedStyles.js' +import {GrampsjsTranslateMixin} from '../mixins/GrampsjsTranslateMixin.js' +import './GrampsjsChatPrompt.js' +import './GrampsjsChatMessage.js' +import {setChatHistory, getChatHistory, apiPost} from '../api.js' +import {renderMarkdownLinks} from '../util.js' + +function delay(ms) { + return new Promise(resolve => setTimeout(resolve, ms)) +} + +class GrampsjsChat extends GrampsjsTranslateMixin(LitElement) { + static get styles() { + return [ + sharedStyles, + css` + :host { + height: 100%; + display: flex; + flex-direction: column; + } + + .outer { + height: 100%; + display: flex; + flex-direction: column; + } + + .container { + height: 100%; + display: flex; + flex-direction: column; + justify-content: flex-end; + overflow: hidden; + clear: left; + } + + .conversation { + flex: 1; + overflow-y: auto; + display: flex; + flex-direction: column-reverse; + padding: 0 10px 20px 10px; + } + + .prompt { + padding: 10px; + flex-shrink: 0; + } + + .loading { + display: flex; + align-items: center; + justify-content: center; + height: 24px; + width: 48px; + font-size: 24px; + } + + .dot { + width: 8px; + height: 8px; + margin: 0 4px; + background-color: #888; + border-radius: 50%; + animation: flash 1.4s infinite ease-in-out both; + } + + .dot:nth-child(1) { + animation-delay: -0.32s; + } + + .dot:nth-child(2) { + animation-delay: -0.16s; + } + + @keyframes flash { + 0%, + 80%, + 100% { + opacity: 0; + } + 40% { + opacity: 1; + } + } + + .clear-btn { + position: relative; + float: left; + top: 20px; + left: 0px; + margin: 1px solid red; + } + `, + ] + } + + static get properties() { + return { + messages: {type: Array}, + loading: {type: Boolean}, + } + } + + constructor() { + super() + this.messages = getChatHistory() || [] + this.loading = false + } + + render() { + return html` +
+ +
+
+
+
+ ${ + this.loading + ? html` +
+
+
+
` + : '' + } + ${this.messages + .toReversed() + .map( + message => html` + ${renderMarkdownLinks( + message.message + )} + ` + )} +
+
+ +
+
+
+
+ ` + } + + async _addMessage(message, maxLength) { + if (!message.message) { + return + } + const {messages} = this + + if (message.role === 'ai') { + // for AI messages, we display the message word by word + // to simulate streaming response (which it's not, but + // users may be used to it.) + const words = message.message.split(' ') + const nWords = words.length + for (let end = 1; end <= nWords; end += 1) { + this.messages = [ + ...messages.slice(-(maxLength - 1)), + {role: 'ai', message: words.slice(0, end).join(' ')}, + ] + // eslint-disable-next-line no-await-in-loop + await delay(Math.ceil(1000 / nWords)) + } + } else { + this.messages = [...messages.slice(-(maxLength - 1)), message] + } + } + + _handlePrompt(event) { + const message = { + role: 'human', + message: event.detail.message, + } + this._addMessage(message, 7) + setChatHistory(this.messages) + this._generateResponse() + } + + async _generateResponse() { + this.loading = true + const payload = { + query: this.messages[this.messages.length - 1].message, + } + if (this.messages.length > 1) { + payload.history = this.messages.slice(0, this.messages.length - 1) + } + const data = await apiPost('/api/chat/', payload) + let message + if ('error' in data || !data?.data?.response) { + message = { + role: 'error', + message: this._(data.error), + } + } else { + message = { + role: 'ai', + message: data.data.response, + } + } + + this.loading = false + await this._addMessage(message, 6) + setChatHistory(this.messages) + } + + _handleClear() { + this.messages = [] + setChatHistory(this.messages) + } + + _scrollToLastMessage() { + const conversationDiv = this.renderRoot.querySelector('.conversation') + if (conversationDiv != null) { + conversationDiv.scrollTop = conversationDiv.scrollHeight + } + } + + focusInput(retry = true) { + const ele = this.renderRoot.querySelector('grampsjs-chat-prompt') + if (ele !== null) { + ele.focusInput() + } else if (retry) { + setTimeout(() => this.focusInput(false), 500) + } + this._scrollToLastMessage() + } + + _handleStorage() { + this.messages = getChatHistory() + } + + connectedCallback() { + super.connectedCallback() + window.addEventListener('storage', event => this._handleStorage(event)) + } +} + +window.customElements.define('grampsjs-chat', GrampsjsChat) diff --git a/src/components/GrampsjsChatMessage.js b/src/components/GrampsjsChatMessage.js new file mode 100644 index 00000000..21109da8 --- /dev/null +++ b/src/components/GrampsjsChatMessage.js @@ -0,0 +1,103 @@ +import {html, css, LitElement} from 'lit' +import {classMap} from 'lit/directives/class-map.js' +import '@material/web/icon/icon.js' +import {mdiFamilyTree} from '@mdi/js' + +import {sharedStyles} from '../SharedStyles.js' +import {GrampsjsTranslateMixin} from '../mixins/GrampsjsTranslateMixin.js' +import {renderIconSvg} from '../icons.js' + +class GrampsjsChatMessage extends GrampsjsTranslateMixin(LitElement) { + static get styles() { + return [ + sharedStyles, + css` + .container { + margin: 15px 0; + font-size: 16px; + line-height: 26px; + font-weight: 340; + clear: right; + max-width: 90%; + display: flex; + align-items: flex-start; + } + + .container.human { + background-color: rgba(109, 76, 65, 0.12); + color: rgba(27, 19, 16); + padding: 10px 20px; + border-radius: 16px; + float: right; + max-width: 70%; + margin-right: 10px; + } + + .container.alert { + max-width: 70%; + margin-left: auto; + margin-right: auto; + width: fit-content; + border-radius: 16px; + border: 0; + } + + .slot-wrap { + white-space: pre-wrap; + flex-grow: 1; + overflow: hidden; + } + + .avatar { + width: 35px; + height: 35px; + flex-shrink: 0; + } + + .avatar md-icon { + --md-icon-size: 20px; + position: relative; + top: 3px; + } + `, + ] + } + + static get properties() { + return { + type: {type: String}, + } + } + + constructor() { + super() + this.type = 'human' + } + + render() { + return html` +
+ ${this.type === 'ai' + ? html` +
+ ${renderIconSvg(mdiFamilyTree, '#999', 270)} +
+ ` + : ''} + + +
+
+ ` + } +} + +window.customElements.define('grampsjs-chat-message', GrampsjsChatMessage) diff --git a/src/components/GrampsjsChatPermissions.js b/src/components/GrampsjsChatPermissions.js new file mode 100644 index 00000000..561ce107 --- /dev/null +++ b/src/components/GrampsjsChatPermissions.js @@ -0,0 +1,95 @@ +import {css, html} from 'lit' +import '@material/mwc-checkbox' + +import {sharedStyles} from '../SharedStyles.js' +import {GrampsjsConnectedComponent} from './GrampsjsConnectedComponent.js' +import {fireEvent} from '../util.js' +import {apiPut} from '../api.js' + +// options for min_role_ai +const roleAiOptions = { + 4: 'Owners and administrators', + 3: 'Editor and above', + 2: 'Contributor and above', + 1: 'Member and above', + 0: 'Everybody', + 99: 'Nobody', +} + +export class GrampsjsChatPermissions extends GrampsjsConnectedComponent { + static get styles() { + return [ + sharedStyles, + css` + p { + padding-bottom: 15px; + } + + .margin-left { + margin-left: 1em; + } + + .hidden { + visibility: hidden; + } + `, + ] + } + + renderContent() { + let minRoleAi = this._data?.data?.min_role_ai ?? 99 + minRoleAi = minRoleAi > 5 ? 99 : minRoleAi + return html`

+ ${this._('User groups allowed to use AI chat:')} + + ${Object.keys(roleAiOptions).map( + key => html` + +

${this._(roleAiOptions[key])}
+ + ` + )} + +

` + } + + // eslint-disable-next-line class-methods-use-this + renderLoading() { + return html` +

+ ${this._('User groups allowed to use AI chat:')} + + + +

+ ` + } + + async _handleChange(event) { + const minRoleAi = parseInt(event.target.value, 10) + const payload = {min_role_ai: minRoleAi} + const data = await apiPut('/api/trees/-', payload) + if ('error' in data) { + fireEvent(this, 'grampsjs:error', {message: data.error}) + } else { + fireEvent(this, 'token:refresh', {}) + } + } + + // eslint-disable-next-line class-methods-use-this + getUrl() { + return '/api/trees/-' + } +} + +window.customElements.define( + 'grampsjs-chat-permissions', + GrampsjsChatPermissions +) diff --git a/src/components/GrampsjsChatPrompt.js b/src/components/GrampsjsChatPrompt.js new file mode 100644 index 00000000..cd81fec1 --- /dev/null +++ b/src/components/GrampsjsChatPrompt.js @@ -0,0 +1,135 @@ +import {html, css, LitElement} from 'lit' +import '@material/web/textfield/outlined-text-field' +import '@material/web/iconbutton/filled-icon-button' +import '@material/web/icon/icon.js' + +import {mdiSend} from '@mdi/js' +import {sharedStyles} from '../SharedStyles.js' +import {GrampsjsTranslateMixin} from '../mixins/GrampsjsTranslateMixin.js' +import {fireEvent} from '../util.js' +import {renderIconSvg} from '../icons.js' + +class GrampsjsChatPrompt extends GrampsjsTranslateMixin(LitElement) { + static get styles() { + return [ + sharedStyles, + css` + .container { + display: flex; + align-items: end; + justify-content: center; + } + + md-outlined-text-field { + flex: 1; + --md-outlined-text-field-container-shape: 28px; + --md-outlined-text-field-input-text-placeholder-color: #777; + resize: none; + } + + md-filled-icon-button.send { + --md-filled-icon-button-container-color: rgba(109, 76, 65, 1); + position: relative; + margin-left: 16px; + margin-top: 9px; + margin-bottom: 9px; + margin-right: 0; + --md-filled-icon-button-icon-size: 22px; + --md-filled-icon-button-state-layer-height: 66px; + --md-filled-icon-button-state-layer-width: 66px; + } + `, + ] + } + + static get properties() { + return { + value: {type: String}, + maxRows: {type: Number}, + nRows: {type: Number}, + loading: {type: Boolean}, + } + } + + constructor() { + super() + this.value = '' + this.maxRows = 5 + this.nRows = 1 + this.loading = false + } + + render() { + return html` +
+ + + + ${renderIconSvg(mdiSend, '#ffffff')} + +
+ ` + } + + _handleBtnClick() { + this._submit() + } + + _handleKey(event) { + if (event.code === 'Enter' && !event.shiftKey) { + event.preventDefault() + event.stopPropagation() + this._submit() + } else if (event.code === 'Escape') { + this._clear() + } + } + + _handleInput() { + this.value = this.renderRoot.querySelector('md-outlined-text-field').value + this._updateNRows() + } + + _clear() { + const input = this.renderRoot.querySelector('md-outlined-text-field') + if (input !== null) { + input.value = '' + this.value = '' + } + this._updateNRows() + } + + _submit() { + if (this.value.trim() && !this.loading) { + fireEvent(this, 'chat:prompt', {message: this.value.trim()}) + this._clear() + } + } + + _updateNRows() { + if (!this.value) { + this.nRows = 1 + } + this.nRows = Math.min(this.maxRows, this.value.split('\n').length) + } + + focusInput() { + const textField = this.renderRoot.querySelector('md-outlined-text-field') + if (textField !== null) { + textField.focus() + } + } +} + +window.customElements.define('grampsjs-chat-prompt', GrampsjsChatPrompt) diff --git a/src/components/GrampsjsFilters.js b/src/components/GrampsjsFilters.js index d21bcf06..7c18b8d1 100644 --- a/src/components/GrampsjsFilters.js +++ b/src/components/GrampsjsFilters.js @@ -8,6 +8,7 @@ import '@material/mwc-button' import '@material/web/textfield/outlined-text-field' import '@material/web/button/filled-button' +import './GrampsjsButtonGroup.js' import {renderIconSvg} from '../icons.js' import {GrampsjsTranslateMixin} from '../mixins/GrampsjsTranslateMixin.js' import { @@ -38,19 +39,6 @@ export class GrampsjsFilters extends GrampsjsTranslateMixin(LitElement) { margin-right: 10px; } - #filter-type-buttons { - --mdc-typography-button-font-size: 12px; - margin: 12px 0; - } - - #filter-type-buttons div { - border: 1px solid var(--mdc-theme-primary); - opacity: 0.9; - border-radius: 8px; - display: inline-block; - padding: 4px; - } - #input-gql-container { align-items: center; margin: 20px 0 30px 0; @@ -134,22 +122,20 @@ export class GrampsjsFilters extends GrampsjsTranslateMixin(LitElement) { class="${classMap({hidden: !this.open})}" @filter:changed="${this._handleFilterChanged}" > -
-
- ${this._('simple')} - GQL -
-
+ + ${this._('simple')} + GQL +
${this._('Family Tree')} ${renderIcon(mdiFamilyTree)} + ${this.canUseChat + ? html` + + ${this._('Chat')} + ${renderIcon(mdiChat)} + + ` + : ''}
  • ${this._('History')} diff --git a/src/components/GrampsjsMediaStatus.js b/src/components/GrampsjsMediaStatus.js index ce4ad5c5..e7a458b8 100644 --- a/src/components/GrampsjsMediaStatus.js +++ b/src/components/GrampsjsMediaStatus.js @@ -27,18 +27,6 @@ export class GrampsjsMediaStatus extends GrampsjsConnectedComponent { position: relative; } - .success { - color: #41ad49; - } - - .error { - color: #bf360c; - } - - .warn { - color: #f9a825; - } - .inline { display: inline-block; margin-right: 1em; diff --git a/src/components/GrampsjsPages.js b/src/components/GrampsjsPages.js index 68119fc2..f1228988 100644 --- a/src/components/GrampsjsPages.js +++ b/src/components/GrampsjsPages.js @@ -17,6 +17,7 @@ import '../views/GrampsjsViewCitations.js' import '../views/GrampsjsViewRepositories.js' import '../views/GrampsjsViewNotes.js' import '../views/GrampsjsViewMediaObjects.js' +import '../views/GrampsjsViewChat.js' import '../views/GrampsjsViewExport.js' import '../views/GrampsjsViewPerson.js' import '../views/GrampsjsViewFamily.js' @@ -74,6 +75,7 @@ class GrampsjsPages extends GrampsjsTranslateMixin(LitElement) { canEdit: {type: Boolean}, canManageUsers: {type: Boolean}, canViewPrivate: {type: Boolean}, + canUseChat: {type: Boolean}, homePersonDetails: {type: Object}, strings: {type: Object}, dbInfo: {type: Object}, @@ -88,6 +90,7 @@ class GrampsjsPages extends GrampsjsTranslateMixin(LitElement) { this.canAdd = false this.canEdit = false this.canManageUsers = false + this.canUseChat = false this.homePersonDetails = {} this.strings = {} this.dbInfo = {} @@ -249,7 +252,15 @@ class GrampsjsPages extends GrampsjsTranslateMixin(LitElement) { ?canEdit="${this.canEdit}" .dbInfo="${this.dbInfo}" > - + ${this.canUseChat + ? html` + + ` + : ''} @@ -127,6 +134,22 @@ export class GrampsjsTreeQuotas extends GrampsjsConnectedComponent { ( ${usageMedia} / ${quotaMedia} ) + ${'usage_ai' in data + ? html` + + ${this._('Chat messages')} + + + + + ${Math.round(100 * progressAi)}% + ( ${usageAi} / ${quotaAi} ) + + + ` + : ''} ` } diff --git a/src/util.js b/src/util.js index 15551885..5ad7f5d8 100644 --- a/src/util.js +++ b/src/util.js @@ -611,6 +611,34 @@ export function linkUrls(text, textOnly = true) { )}` } +export function renderMarkdownLinks(markdown) { + // Regular expression to match markdown links + const markdownLinkPattern = /\[([^\]]+)\]\(([^)]+)\)/g + + // Split the markdown into segments, with text and links separated + const segments = [] + let lastIndex = 0 + let match + + // eslint-disable-next-line no-cond-assign + while ((match = markdownLinkPattern.exec(markdown)) !== null) { + // Push the text before the link + if (match.index > lastIndex) { + segments.push(markdown.substring(lastIndex, match.index)) + } + // Push the link as an HTML node + segments.push(html`${match[1]}`) + lastIndex = markdownLinkPattern.lastIndex + } + + // Push the remaining text after the last link + if (lastIndex < markdown.length) { + segments.push(markdown.substring(lastIndex)) + } + + return segments +} + export function getGregorianYears(date) { const MOD_TEXTONLY = 6 if ( @@ -691,3 +719,5 @@ export function stripHtml(input) { const doc = parser.parseFromString(input, 'text/html') return doc.body.textContent || '' } + +// diff --git a/src/views/GrampsjsViewAdminSettings.js b/src/views/GrampsjsViewAdminSettings.js index c69d9280..da213930 100644 --- a/src/views/GrampsjsViewAdminSettings.js +++ b/src/views/GrampsjsViewAdminSettings.js @@ -62,6 +62,17 @@ export class GrampsjsViewAdminSettings extends GrampsjsView { .bold { font-weight: 500; } + + mwc-icon.status { + font-size: 18px; + top: 4px; + position: relative; + margin-right: 5px; + } + + .small { + font-size: 16px; + } `, ] } @@ -72,6 +83,8 @@ export class GrampsjsViewAdminSettings extends GrampsjsView { dbInfo: {type: Object}, userInfo: {type: Object}, _repairResults: {type: Object}, + _buttonUpdateSearchDisabled: {type: Boolean}, + _buttonUpdateSearchSemanticDisabled: {type: Boolean}, } } @@ -81,6 +94,8 @@ export class GrampsjsViewAdminSettings extends GrampsjsView { this.dbInfo = {} this.userInfo = {} this._repairResults = {} + this._buttonUpdateSearchDisabled = false + this._buttonUpdateSearchSemanticDisabled = false } renderContent() { @@ -102,6 +117,8 @@ export class GrampsjsViewAdminSettings extends GrampsjsView {

    ${this._('Manage search index')}

    + ${this._renderSearchStatus()} +

    ${this._( 'Manually updating the search index is usually unnecessary, but it may become necessary after an upgrade.' @@ -109,7 +126,8 @@ export class GrampsjsViewAdminSettings extends GrampsjsView {

    ${this._('Update search index')} @@ -122,6 +140,34 @@ export class GrampsjsViewAdminSettings extends GrampsjsView { @task:complete="${this._handleSuccessUpdateSearch}" > + ${this.dbInfo?.server?.semantic_search + ? html` +

    ${this._('Manage semantic search index')}

    + + ${this._renderSearchStatus(true)} + +

    + ${this._( + 'Updating the semantic search index requires substantial time and computational resources. Run this operation only when necessary.' + )} +

    + ${this._('Update semantic search index')} + + ` + : ''}

    ${this._('Check and Repair Database')}

    @@ -192,6 +238,25 @@ export class GrampsjsViewAdminSettings extends GrampsjsView { ` } + _renderSearchStatus(semantic = false) { + const property = semantic ? 'count_semantic' : 'count' + const count = this.dbInfo?.search?.sifts?.[property] ?? -1 + const objCounts = this.dbInfo?.object_counts ?? {} + const objCount = Object.values(objCounts).reduce( + (sum, value) => sum + value, + 0 + ) + const iconError = html`error` + const iconOk = html`check_circle` + const icon = objCount === 0 || count / objCount > 0.98 ? iconOk : iconError + return html`

    + ${icon} ${this._('Status')}: + ${count === -1 ? this._('unknown') : count}/${objCount} +

    ` + } + _openDeleteAll() { if (isTokenFresh()) { this.renderRoot.querySelector('grampsjs-delete-all').show() @@ -220,26 +285,47 @@ export class GrampsjsViewAdminSettings extends GrampsjsView { } } - async _updateSearch() { - const prog = this.renderRoot.querySelector('#progress-update-search') + async _updateSearch(semantic = false) { + const id = semantic + ? 'progress-update-search-semantic' + : 'progress-update-search' + const prog = this.renderRoot.querySelector(`#${id}`) prog.reset() prog.open = true - const data = await apiPost('/api/search/index/?full=1') + const url = semantic + ? '/api/search/index/?full=1&semantic=1' + : '/api/search/index/?full=1' + if (semantic) { + this._buttonUpdateSearchSemanticDisabled = true + } else { + this._buttonUpdateSearchDisabled = true + } + const data = await apiPost(url) if ('error' in data) { prog.setError() prog.errorMessage = data.error + this._doneUpdateSearch(semantic) } else if ('task' in data) { prog.taskId = data.task?.id || '' } else { prog.setComplete() - this._handleSuccessUpdateSearch() + this._handleSuccessUpdateSearch(semantic) } } - _handleSuccessUpdateSearch() { + _handleSuccessUpdateSearch(semantic = false) { + this._doneUpdateSearch(semantic) fireEvent(this, 'db:changed') } + _doneUpdateSearch(semantic = false) { + if (semantic) { + this._buttonUpdateSearchSemanticDisabled = false + } else { + this._buttonUpdateSearchDisabled = false + } + } + async _checkRepair() { this._repairResults = {} const prog = this.renderRoot.querySelector('#progress-repair') diff --git a/src/views/GrampsjsViewChat.js b/src/views/GrampsjsViewChat.js new file mode 100644 index 00000000..2128dfd4 --- /dev/null +++ b/src/views/GrampsjsViewChat.js @@ -0,0 +1,44 @@ +import {html, css} from 'lit' + +import '../components/GrampsjsChat.js' +import {GrampsjsView} from './GrampsjsView.js' + +export class GrampsjsViewChat extends GrampsjsView { + static get styles() { + return [ + super.styles, + css` + :host { + height: calc(100vh - 85px); + margin-top: 0; + margin-bottom: 0; + display: flex; + overflow: hidden; + } + `, + ] + } + + update(changed) { + super.update(changed) + if (changed.has('active')) { + this._focus() + } + } + + _focus() { + if (this.active) { + this.renderRoot.querySelector('grampsjs-chat').focusInput() + } + } + + renderContent() { + return html` ` + } + + firstUpdated() { + this._focus() + } +} + +window.customElements.define('grampsjs-view-chat', GrampsjsViewChat) diff --git a/src/views/GrampsjsViewMediaLightbox.js b/src/views/GrampsjsViewMediaLightbox.js index b9c8e36d..95028b5f 100644 --- a/src/views/GrampsjsViewMediaLightbox.js +++ b/src/views/GrampsjsViewMediaLightbox.js @@ -109,6 +109,7 @@ export class GrampsjsViewMediaLightbox extends GrampsjsView { return html` diff --git a/src/views/GrampsjsViewSearch.js b/src/views/GrampsjsViewSearch.js index c9dd59f2..c7fe1b3a 100644 --- a/src/views/GrampsjsViewSearch.js +++ b/src/views/GrampsjsViewSearch.js @@ -4,6 +4,7 @@ import {GrampsjsView} from './GrampsjsView.js' import '../components/GrampsjsSearchResultList.js' import '../components/GrampsjsPagination.js' import '../components/GrampsjsButtonToggle.js' +import '../components/GrampsjsButtonGroup.js' import {apiGet} from '../api.js' import {objectTypeToEndpoint, objectIcon, debounce} from '../util.js' import '@material/mwc-textfield' @@ -24,6 +25,11 @@ export class GrampsjsViewSearch extends GrampsjsView { justify-content: center; max-width: 100%; min-width: 80%; + clear: left; + } + + .mode-toggle { + float: right; } mwc-textfield#search-field { @@ -54,6 +60,8 @@ export class GrampsjsViewSearch extends GrampsjsView { static get properties() { return { + semantic: {type: Boolean}, + dbInfo: {type: Object}, _data: {type: Array}, _totalCount: {type: Number}, _page: {type: Number}, @@ -64,6 +72,8 @@ export class GrampsjsViewSearch extends GrampsjsView { constructor() { super() + this.semantic = false + this.dbInfo = {} this._data = [] this._totalCount = -1 this._page = 1 @@ -77,7 +87,9 @@ export class GrampsjsViewSearch extends GrampsjsView { renderContent() { return html` -

    Search

    + ${this._semanticEnabled() ? this._renderModeToggle() : ''} + +

    ${this._('Search')}

    + + ${this._('full-text')} + ${this._('semantic')} + +
    + ` + } + + async _handleModeClick() { + this.semantic = !this.semantic + this._executeSearch() + } + renderFilters() { return html`
    @@ -142,6 +180,13 @@ export class GrampsjsViewSearch extends GrampsjsView { ` } + _semanticEnabled() { + return ( + !!this.dbInfo?.server?.semantic_search && + !!this.dbInfo?.search?.sifts?.count_semantic + ) + } + _handleFilterToggle(e) { const key = e.target.id.split('-', 2)[1] if (key === 'all') { @@ -277,6 +322,9 @@ export class GrampsjsViewSearch extends GrampsjsView { let url = `/api/search/?query=${query}&locale=${ this.strings?.__lang__ || 'en' }&profile=all&page=${page}&pagesize=20` + if (this._semanticEnabled()) { + url = `${url}&semantic=${this.semantic ? 1 : 0}` + } if (!window._oldSearchBackend) { const objectTypes = Object.keys(this._objectTypes).filter( key => this._objectTypes[key] diff --git a/src/views/GrampsjsViewUserManagement.js b/src/views/GrampsjsViewUserManagement.js index 7f79d738..4b0766de 100644 --- a/src/views/GrampsjsViewUserManagement.js +++ b/src/views/GrampsjsViewUserManagement.js @@ -1,8 +1,12 @@ import {css, html} from 'lit' +import '@material/web/select/filled-select' +import '@material/web/select/select-option' + import {GrampsjsView} from './GrampsjsView.js' import '../components/GrampsjsUsers.js' import '../components/GrampsjsShareUrl.js' +import '../components/GrampsjsChatPermissions.js' import {apiPost, apiPut, apiGet, getTreeId} from '../api.js' export class GrampsjsViewUserManagement extends GrampsjsView { @@ -55,6 +59,11 @@ export class GrampsjsViewUserManagement extends GrampsjsView { renderContent() { return html` + ${this.dbInfo?.server?.chat + ? html`` + : ''} ${this.dbInfo?.server?.multi_tree ? html`

    ${this._('Registration link')}: