diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 00000000..a7b8dc30 --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +src/js/lib/*.js \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 00000000..334dc929 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,40 @@ +module.exports = { + env: { + browser: true, + es2021: true, + }, + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'prettier', + 'plugin:prettier/recommended', + ], + overrides: [], + parser: '@typescript-eslint/parser', + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + }, + plugins: ['simple-import-sort', '@typescript-eslint'], + rules: { + 'simple-import-sort/imports': [ + 'error', + { + groups: [ + // Packages `react` related packages come first. + ['^react', '^@?\\w'], + // Internal packages. + ['^(@|components)(/.*|$)'], + // Side effect imports. + ['^\\u0000'], + // Parent imports. Put `..` last. + ['^\\.\\.(?!/?$)', '^\\.\\./?$'], + // Other relative imports. Put same-folder imports and `.` last. + ['^\\./(?=.*/)(?!/?$)', '^\\.(?!/?$)', '^\\./?$'], + // Style imports. + ['^.+\\.?(css)$'], + ], + }, + ], + }, +}; diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ff393adc..356024f6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,7 +11,7 @@ jobs: uses: actions/setup-node@v2 with: node-version: 16.x - cache: "yarn" + cache: 'yarn' - name: Install dependencies run: yarn --frozen-lockfile - name: Build @@ -24,7 +24,7 @@ jobs: uses: actions/setup-node@v2 with: node-version: 16.x - cache: "yarn" + cache: 'yarn' - name: Install dependencies run: yarn --frozen-lockfile - name: Lint @@ -37,7 +37,7 @@ jobs: uses: actions/setup-node@v2 with: node-version: 16.x - cache: "yarn" + cache: 'yarn' - name: Install dependencies run: yarn --frozen-lockfile - name: Test diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..2e356bc8 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,2 @@ +src/css/cropper.min.css +build \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..17c23a18 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,9 @@ +{ + "useTabs": false, + "singleQuote": true, + "printWidth": 100, + "semi": true, + "trailingComma": "all", + "arrowParens": "always", + "tabWidth": 2 +} diff --git a/README.md b/README.md index 92313de9..0f97ed55 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,33 @@ # Iris + Iris is like the social networking apps we're used to, but better. -* No phone number or signup required. Just type in your name or alias and go! -* Secure: It's open source. Users can validate that big brother doesn't read your private messages. -* Available: It works offline-first and is not dependent on any single centrally managed server. Users can even connect directly to each other. + +- No phone number or signup required. Just type in your name or alias and go! +- Secure: It's open source. Users can validate that big brother doesn't read your private messages. +- Available: It works offline-first and is not dependent on any single centrally managed server. Users can even connect directly to each other. ![Screenshot](screenshot.png) ## Use + Browser application: [iris.to](https://iris.to) -* No installation required -* Progressive web app - * Use offline - * Save as an app to home screen or desktop + +- No installation required +- Progressive web app + - Use offline + - Save as an app to home screen or desktop Desktop application: ([download](https://github.com/irislib/iris-electron/releases), [source code](https://github.com/irislib/iris-electron)): -* Communicate and synchronize with local network peers without Internet access - * When local peers eventually connect to the Internet, your messages are relayed globally - * Bluetooth support upcoming -* Opens to background on login: stay online and get message notifications! -* More secure and available: no need to open the browser application from a server. + +- Communicate and synchronize with local network peers without Internet access + - When local peers eventually connect to the Internet, your messages are relayed globally + - Bluetooth support upcoming +- Opens to background on login: stay online and get message notifications! +- More secure and available: no need to open the browser application from a server. ## Develop -``` bash + +```bash # install dependencies yarn @@ -41,6 +47,7 @@ yarn test [iris-lib](https://github.com/irislib/iris-lib) is a core part of the application. You can clone it and run `yarn link` in the iris-lib directory. Then run `yarn link iris-lib` in the iris-messenger directory. ## Privacy + The application is an unaudited proof-of-concept implementation, so don't use it for security critical purposes. Private messages are end-to-end encrypted, but message timestamps and the number of chats aren't. In a decentralized network this information is potentially available to anyone. @@ -52,6 +59,7 @@ In that regard, Iris prioritizes decentralization and availability over perfect Profile names, photos and online status are currently public. That can be changed when advanced group permissions are developed. ## Contact + Join our [Discord](https://discord.gg/4CJc74JEUY) (will be moved onto Iris when group chat is ready) or send me a message on [Iris](https://iris.to/?chatWith=hyECQHwSo7fgr2MVfPyakvayPeixxsaAWVtZ-vbaiSc.TXIp8MnCtrnW6n2MrYquWPcc-DTmZzMBmc2yaGv9gIU&s=HlzYzNrhUsrn2PLi4yuRt6DiFUNM3hOmN8nFpgw6T-g&k=zvDfsInsMOI1). --- diff --git a/package.json b/package.json index 752f7fdc..5d374fe3 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,9 @@ "build": "preact build --no-prerender", "serve": "sirv build --port 8080 --cors --single", "dev": "preact watch --host localhost --sw", - "lint": "eslint src", + "lint": "eslint 'src/**/*.{js,ts,tsx}'", + "lint:fix": "eslint --fix --quiet 'src/**/*.{js,ts,tsx}'", + "format": "prettier --plugin-search-dir . --write .", "test": "jest" }, "eslintConfig": { @@ -33,16 +35,23 @@ }, "devDependencies": { "@types/jquery": "3.5.14", - "@types/lodash": "4.14.186", + "@types/lodash": "4.14.187", "@types/react-helmet": "6.1.5", "@types/webtorrent": "0.109.3", + "@typescript-eslint/eslint-plugin": "^5.42.0", + "@typescript-eslint/parser": "^5.42.0", "enzyme": "^3.11.0", "enzyme-adapter-preact-pure": "^4.0.1", - "eslint": "^8.24.0", + "eslint": "^8.26.0", "eslint-config-preact": "^1.3.0", - "jest": "^27.0.6", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-prettier": "^4.2.1", + "eslint-plugin-simple-import-sort": "^8.0.0", + "gun": "^0.2020.1238", + "jest": "^29.2.2", "jest-preset-preact": "^4.0.5", "preact-cli": "^3.4.1", + "prettier": "^2.7.1", "sirv-cli": "2.0.2", "webpack-build-notifier": "^2.3.0" }, @@ -50,7 +59,7 @@ "@walletconnect/web3-provider": "^1.8.0", "@zxing/library": "^0.19.1", "aether-torrent": "^0.3.0", - "alchemy-sdk": "^2.1.0", + "alchemy-sdk": "^2.2.0", "elliptic": "^6.5.4", "fuse.js": "^6.6.2", "history": "5.3.0", @@ -61,10 +70,10 @@ "jsxstyle": "^2.5.1", "localforage": "^1.10.0", "lodash": "^4.17.21", - "preact": "^10.11.0", + "preact": "^10.11.2", "preact-async-route": "2.2.1", "preact-custom-element": "^4.2.1", - "preact-render-to-string": "^5.2.4", + "preact-render-to-string": "^5.2.6", "preact-router": "^4.1.0", "preact-scroll-viewport": "^0.2.0", "react-helmet": "^6.1.0", diff --git a/preact.config.js b/preact.config.js index 29da3700..d52fb3c6 100644 --- a/preact.config.js +++ b/preact.config.js @@ -7,12 +7,12 @@ export default { config.plugins = config.plugins || []; config.plugins.push( new WebpackBuildNotifierPlugin({ - title: "Iris Webpack Build", - logo: path.resolve("./src/assets/img/icon128.png"), + title: 'Iris Webpack Build', + logo: path.resolve('./src/assets/img/icon128.png'), suppressSuccess: true, // don't spam success notifications warningSound: false, suppressWarning: true, - }) - ) - } -} \ No newline at end of file + }), + ); + }, +}; diff --git a/scripts/TranslationsToCsv.mjs b/scripts/TranslationsToCsv.mjs index b2e3bae3..47e63d20 100644 --- a/scripts/TranslationsToCsv.mjs +++ b/scripts/TranslationsToCsv.mjs @@ -1,5 +1,6 @@ -import {AVAILABLE_LANGUAGE_KEYS} from "../src/js/translations/Translation.mjs"; -import fs from "fs"; +import fs from 'fs'; + +import { AVAILABLE_LANGUAGE_KEYS } from '../src/js/translations/Translation.mjs'; // Create a csv file where each row is a translation key and the column is the translation in different languages. // The file is created in the current working directory. @@ -8,68 +9,69 @@ import fs from "fs"; // TODO: read translations from .mjs files in ../src/js/translations/ async function translationsToCsv() { - let csv = ''; - let languages = []; - let translationKeys = []; - let translations = {}; + let csv = ''; + let languages = []; + let translationKeys = []; + let translations = {}; - for (let lang of AVAILABLE_LANGUAGE_KEYS) { - const translation = (await import (`../src/js/translations/${lang}.mjs`)).default; - translations[lang] = translation; - languages.push(lang); - for (let key in translation) { - if (translationKeys.indexOf(key) === -1) { - translationKeys.push(key); - } - } + for (let lang of AVAILABLE_LANGUAGE_KEYS) { + const translation = (await import(`../src/js/translations/${lang}.mjs`)).default; + translations[lang] = translation; + languages.push(lang); + for (let key in translation) { + if (translationKeys.indexOf(key) === -1) { + translationKeys.push(key); + } } + } - languages.sort(); - translationKeys.sort(); + languages.sort(); + translationKeys.sort(); - // add language names to csv - csv += '"","'; - for (let i = 0; i < languages.length; i++) { - csv += languages[i]; - if (i < languages.length - 1) { - csv += '","'; - } else { - csv += '"\n'; - } + // add language names to csv + csv += '"","'; + for (let i = 0; i < languages.length; i++) { + csv += languages[i]; + if (i < languages.length - 1) { + csv += '","'; + } else { + csv += '"\n'; } + } - csv += '"'; - for (let key of translationKeys) { - let row = key; - for (let lang of languages) { - row += '","' + (translations[lang][key] || '').replace(/"/g, '""'); - } - csv += row + '"\n'; - if (key !== translationKeys[translationKeys.length - 1]) { - csv += '"'; - }; + csv += '"'; + for (let key of translationKeys) { + let row = key; + for (let lang of languages) { + row += '","' + (translations[lang][key] || '').replace(/"/g, '""'); } + csv += row + '"\n'; + if (key !== translationKeys[translationKeys.length - 1]) { + csv += '"'; + } + } - // output csv to file - fs.writeFileSync('translations.csv', csv); - console.log('wrote translations.csv'); + // output csv to file + fs.writeFileSync('translations.csv', csv); + console.log('wrote translations.csv'); } // convert the csv back to Translations.mjs in the same format as the original Translations.mjs file -function csvToTranslations() { // TODO: work in progress - let csv = fs.readFileSync('translations.csv', 'utf8'); - let lines = csv.split('\n'); - let translations = {}; - let languages = lines[0].split(','); - languages.shift(); - for (let i = 1; i < lines.length; i++) { - let line = lines[i].split(','); - let key = line[0]; - line.shift(); - for (let j = 0; j < languages.length; j++) { - translations[key][languages[j]] = line[j] || null; - } +function csvToTranslations() { + // TODO: work in progress + let csv = fs.readFileSync('translations.csv', 'utf8'); + let lines = csv.split('\n'); + let translations = {}; + let languages = lines[0].split(','); + languages.shift(); + for (let i = 1; i < lines.length; i++) { + let line = lines[i].split(','); + let key = line[0]; + line.shift(); + for (let j = 0; j < languages.length; j++) { + translations[key][languages[j]] = line[j] || null; } + } } -translationsToCsv(); \ No newline at end of file +translationsToCsv(); diff --git a/src/css/style.css b/src/css/style.css index b7bb4467..45015fca 100644 --- a/src/css/style.css +++ b/src/css/style.css @@ -1,62 +1,62 @@ @font-face { - font-family: "Futura"; - src: url("./Futura-Medium.woff") format('woff'); + font-family: 'Futura'; + src: url('./Futura-Medium.woff') format('woff'); } @font-face { - font-family: "Metuo-Black"; - src: url("./Metuo-Black.woff") format('woff'); + font-family: 'Metuo-Black'; + src: url('./Metuo-Black.woff') format('woff'); } :root { - --body-bg: #2e3036; - --text-color: #dbdcde; - --text-time: rgba(255, 255, 255, 0.45); - --main-color: #35383f; - --gallery-background: rgba(0, 0, 0, 0.80); - --header-color: #2e3036; - --chat-active: #393b43; - --chat-hover: #33363c; - --msg-content-background: #2e3036; - --msg-border: none; - --our-msg: #9E64FE; - --notify: #5E02F6; - --warning-background: #FE5493; - --heart-color: #FC005D; - --login-background: #1f2125; - --seen-indicator: rgba(255, 255, 255, 0.45); - --input-bg: #1f2125; - --input-text: #dbdcde; - --input-placeholder: #ccc; - --small-text-color: #8d9197; - --msg-form-button-color: #b8b9be; - --button-bg: #853CFE; - --button-bg-hover: #5E02F6; - --button-color: #fefffe; - --button-border: 0; - --button-border-size: 0; - --menu-bg: #1f2125; - --sidebar-bg: #2e3036; - --sidebar-border-right: 1px solid #292b31; - --sidebar-color: #8d9197; - --zebra-stripe-bg: #2e3036; - --emoji-picker-bg: #35383f; - --emoji-picker-color: #fff; - --emoji-picker-border-color: #1f2125; - --link-color: #33B7FE; - --day-separator-bg: rgba(30,32,37,0.85); - --day-separator-color: rgba(255, 255, 255, 0.88); - --non-string-value-color: #9E64FE; - --nav-border-bottom: 1px solid #222526; - --nav-shadow: 0 1px 3px rgba(0,0,0,0.6), 0 1px 2px rgba(0,0,0,0.8); - --positive-color: #83FC00; - --radio-button-background: #666; - --radio-button-checked-background: #33B7FE; + --body-bg: #2e3036; + --text-color: #dbdcde; + --text-time: rgba(255, 255, 255, 0.45); + --main-color: #35383f; + --gallery-background: rgba(0, 0, 0, 0.8); + --header-color: #2e3036; + --chat-active: #393b43; + --chat-hover: #33363c; + --msg-content-background: #2e3036; + --msg-border: none; + --our-msg: #9e64fe; + --notify: #5e02f6; + --warning-background: #fe5493; + --heart-color: #fc005d; + --login-background: #1f2125; + --seen-indicator: rgba(255, 255, 255, 0.45); + --input-bg: #1f2125; + --input-text: #dbdcde; + --input-placeholder: #ccc; + --small-text-color: #8d9197; + --msg-form-button-color: #b8b9be; + --button-bg: #853cfe; + --button-bg-hover: #5e02f6; + --button-color: #fefffe; + --button-border: 0; + --button-border-size: 0; + --menu-bg: #1f2125; + --sidebar-bg: #2e3036; + --sidebar-border-right: 1px solid #292b31; + --sidebar-color: #8d9197; + --zebra-stripe-bg: #2e3036; + --emoji-picker-bg: #35383f; + --emoji-picker-color: #fff; + --emoji-picker-border-color: #1f2125; + --link-color: #33b7fe; + --day-separator-bg: rgba(30, 32, 37, 0.85); + --day-separator-color: rgba(255, 255, 255, 0.88); + --non-string-value-color: #9e64fe; + --nav-border-bottom: 1px solid #222526; + --nav-shadow: 0 1px 3px rgba(0, 0, 0, 0.6), 0 1px 2px rgba(0, 0, 0, 0.8); + --positive-color: #83fc00; + --radio-button-background: #666; + --radio-button-checked-background: #33b7fe; } ::-webkit-scrollbar { width: 8px; - background-color: rgba(0,0,0,0); + background-color: rgba(0, 0, 0, 0); -webkit-border-radius: 100px; } ::-webkit-scrollbar:hover { @@ -64,33 +64,33 @@ } ::-webkit-scrollbar-thumb:vertical { - background: rgba(0,0,0,0.5); + background: rgba(0, 0, 0, 0.5); -webkit-border-radius: 100px; } ::-webkit-scrollbar-thumb:vertical:active { - background: rgba(0,0,0,0.61); + background: rgba(0, 0, 0, 0.61); -webkit-border-radius: 100px; } * { - box-sizing: border-box; - font-family: Futura, "Trebuchet MS", Arial, sans-serif; + box-sizing: border-box; + font-family: Futura, 'Trebuchet MS', Arial, sans-serif; } html { - height:100%; - overflow:hidden; + height: 100%; + overflow: hidden; -webkit-text-size-adjust: 100%; } body { font-size: 15px; color: var(--text-color); - font-family: Futura, "Trebuchet MS", Arial, sans-serif; + font-family: Futura, 'Trebuchet MS', Arial, sans-serif; padding: 0; margin: 0; - height:100%; - overflow:hidden; + height: 100%; + overflow: hidden; background-color: var(--body-bg); } @@ -100,12 +100,13 @@ a { color: var(--link-color); } -a:hover, a:focus { +a:hover, +a:focus { text-decoration: underline; outline: none; } -*[contenteditable="true"]:not(:focus) { +*[contenteditable='true']:not(:focus) { cursor: pointer; } @@ -115,15 +116,15 @@ a:hover, a:focus { } [placeholder]:empty:focus::before { - content: ""; + content: ''; } img { - -khtml-user-select: none; - -o-user-select: none; - -moz-user-select: none; - -webkit-user-select: none; - user-select: none; + -khtml-user-select: none; + -o-user-select: none; + -moz-user-select: none; + -webkit-user-select: none; + user-select: none; } textarea { @@ -140,7 +141,8 @@ textarea { resize: none; } -input, button { +input, +button { padding: 15px; outline: none; border: 0; @@ -148,61 +150,62 @@ input, button { border-radius: 50px; } -input:not(:last-child), button:not(:last-child) { +input:not(:last-child), +button:not(:last-child) { margin-right: 7px; } -[type="radio"]:checked, -[type="radio"]:not(:checked) { - position: absolute; - left: -9999px; +[type='radio']:checked, +[type='radio']:not(:checked) { + position: absolute; + left: -9999px; } -[type="radio"]:checked + label, -[type="radio"]:not(:checked) + label -{ - position: relative; - padding-left: 28px; - cursor: pointer; - line-height: 20px; - display: inline-block; +[type='radio']:checked + label, +[type='radio']:not(:checked) + label { + position: relative; + padding-left: 28px; + cursor: pointer; + line-height: 20px; + display: inline-block; } -[type="radio"]:checked + label:before, -[type="radio"]:not(:checked) + label:before { - content: ''; - position: absolute; - left: 0; - top: 0; - width: 18px; - height: 18px; - border: 1px solid #ddd; - border-radius: 100%; - background: var(--radio-button-background); +[type='radio']:checked + label:before, +[type='radio']:not(:checked) + label:before { + content: ''; + position: absolute; + left: 0; + top: 0; + width: 18px; + height: 18px; + border: 1px solid #ddd; + border-radius: 100%; + background: var(--radio-button-background); } -[type="radio"]:checked + label:after, -[type="radio"]:not(:checked) + label:after { - content: ''; - width: 12px; - height: 12px; - background: var(--radio-button-checked-background); - position: absolute; - top: 4px; - left: 4px; - border-radius: 100%; - -webkit-transition: all 0.2s ease; - transition: all 0.2s ease; +[type='radio']:checked + label:after, +[type='radio']:not(:checked) + label:after { + content: ''; + width: 12px; + height: 12px; + background: var(--radio-button-checked-background); + position: absolute; + top: 4px; + left: 4px; + border-radius: 100%; + -webkit-transition: all 0.2s ease; + transition: all 0.2s ease; } -[type="radio"]:not(:checked) + label:after { - opacity: 0; - -webkit-transform: scale(0); - transform: scale(0); +[type='radio']:not(:checked) + label:after { + opacity: 0; + -webkit-transform: scale(0); + transform: scale(0); } -[type="radio"]:checked + label:after { - opacity: 1; - -webkit-transform: scale(1); - transform: scale(1); +[type='radio']:checked + label:after { + opacity: 1; + -webkit-transform: scale(1); + transform: scale(1); } -.btn-group button, .btn-group input { +.btn-group button, +.btn-group input { margin-bottom: 15px; } @@ -210,7 +213,8 @@ input:not(:last-child), button:not(:last-child) { margin-bottom: -15px; } -input, label { +input, +label { margin-right: 7px; } @@ -223,7 +227,8 @@ input { color: var(--input-placeholder); } -img, video { +img, +video { max-width: 100%; max-height: 80vh; } @@ -241,7 +246,8 @@ img, video { width: 100%; } -.msg-content video, .message-form video { +.msg-content video, +.message-form video { width: 100%; outline: none; margin-bottom: 15px; @@ -252,7 +258,7 @@ img, video { } .footer { - border-top: 1px solid rgba(0,0,0,.08); + border-top: 1px solid rgba(0, 0, 0, 0.08); } .footer a { @@ -269,7 +275,7 @@ img, video { } .application-list a.logo span { - margin-top: 2px; + margin-top: 2px; } .nav .connected-peers small { @@ -277,11 +283,13 @@ img, video { align-items: center; } -.connected-peers:hover small, .connected-peers:focus small { +.connected-peers:hover small, +.connected-peers:focus small { opacity: 0.8; } -.connected-peers:focus, .connected-peers:active { +.connected-peers:focus, +.connected-peers:active { text-decoration: none; } @@ -315,7 +323,8 @@ a.logo img:not(:last-child) { -webkit-app-region: drag; } -.header a, .header input { +.header a, +.header input { -webkit-app-region: no-drag; } @@ -332,7 +341,7 @@ a.logo img:not(:last-child) { } .nav .identicon img { - border: 1px solid rgba(0,0,0,0); + border: 1px solid rgba(0, 0, 0, 0); } .nav .active .identicon img { @@ -382,13 +391,14 @@ a.logo img:not(:last-child) { } .search-box label:before { - content: ""; + content: ''; position: absolute; left: 10px; top: 0; bottom: 0; width: 20px; - background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' x='0px' y='0px' width='28.931px' height='28.932px' viewBox='0 0 28.931 28.932' style='enable-background:new 0 0 28.931 28.932;' xml:space='preserve'%3E%3Cpath fill='%23ccc' d='M28.344,25.518l-6.114-6.115c1.486-2.067,2.303-4.537,2.303-7.137c0-3.275-1.275-6.355-3.594-8.672 C18.625,1.278,15.543,0,12.266,0C8.99,0,5.909,1.275,3.593,3.594C1.277,5.909,0.001,8.99,0.001,12.266 c0,3.276,1.275,6.356,3.592,8.674c2.316,2.316,5.396,3.594,8.673,3.594c2.599,0,5.067-0.813,7.136-2.303l6.114,6.115 c0.392,0.391,0.902,0.586,1.414,0.586c0.513,0,1.024-0.195,1.414-0.586C29.125,27.564,29.125,26.299,28.344,25.518z M6.422,18.111 c-1.562-1.562-2.421-3.639-2.421-5.846S4.86,7.983,6.422,6.421c1.561-1.562,3.636-2.422,5.844-2.422s4.284,0.86,5.845,2.422 c1.562,1.562,2.422,3.638,2.422,5.845s-0.859,4.283-2.422,5.846c-1.562,1.562-3.636,2.42-5.845,2.42S7.981,19.672,6.422,18.111z'/%3E%3C/svg%3E") center / contain no-repeat; + background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' x='0px' y='0px' width='28.931px' height='28.932px' viewBox='0 0 28.931 28.932' style='enable-background:new 0 0 28.931 28.932;' xml:space='preserve'%3E%3Cpath fill='%23ccc' d='M28.344,25.518l-6.114-6.115c1.486-2.067,2.303-4.537,2.303-7.137c0-3.275-1.275-6.355-3.594-8.672 C18.625,1.278,15.543,0,12.266,0C8.99,0,5.909,1.275,3.593,3.594C1.277,5.909,0.001,8.99,0.001,12.266 c0,3.276,1.275,6.356,3.592,8.674c2.316,2.316,5.396,3.594,8.673,3.594c2.599,0,5.067-0.813,7.136-2.303l6.114,6.115 c0.392,0.391,0.902,0.586,1.414,0.586c0.513,0,1.024-0.195,1.414-0.586C29.125,27.564,29.125,26.299,28.344,25.518z M6.422,18.111 c-1.562-1.562-2.421-3.639-2.421-5.846S4.86,7.983,6.422,6.421c1.561-1.562,3.636-2.422,5.844-2.422s4.284,0.86,5.845,2.422 c1.562,1.562,2.422,3.638,2.422,5.845s-0.859,4.283-2.422,5.846c-1.562,1.562-3.636,2.42-5.845,2.42S7.981,19.672,6.422,18.111z'/%3E%3C/svg%3E") + center / contain no-repeat; } .search-box { @@ -417,7 +427,7 @@ a.logo img:not(:last-child) { } .search-box-results .result.selected { - background-color: var(--button-bg-hover); + background-color: var(--button-bg-hover); } @media (max-width: 625px) { @@ -510,9 +520,9 @@ a.logo img:not(:last-child) { .nav { display: flex; - flex:1; + flex: 1; background-color: var(--header-color); - max-height:60px; + max-height: 60px; border-bottom: var(--nav-border-bottom); box-shadow: var(--nav-shadow); } @@ -523,7 +533,7 @@ a.logo img:not(:last-child) { .nav small { display: inline-block; - min-height:1em; + min-height: 1em; } .nav .header-content { @@ -548,7 +558,7 @@ a.logo img:not(:last-child) { padding: 15px 5px; transition: all 0.25s ease; user-select: none; - border-bottom: 2px solid rgba(0,0,0,0); + border-bottom: 2px solid rgba(0, 0, 0, 0); } .tabs a.active { @@ -559,7 +569,9 @@ a.logo img:not(:last-child) { background: var(--msg-content-background); } -.tabs a:focus, a:hover, a:active { +.tabs a:focus, +a:hover, +a:active { text-decoration: none; } @@ -568,7 +580,7 @@ header.footer .header-content { } .media-player audio::-webkit-media-controls-play-button, - .media-player audio::-webkit-media-controls-panel { +.media-player audio::-webkit-media-controls-panel { background-color: var(--main-color); color: var(--text-color); } @@ -590,8 +602,9 @@ header.footer .header-content { margin-bottom: 3px; } -.media-player .info, .media-player .cover { - margin-right: 15px; +.media-player .info, +.media-player .cover { + margin-right: 15px; } .media-player .info { @@ -615,7 +628,7 @@ header.footer .header-content { .media-player .close { padding: 13px; - padding-right:28px; + padding-right: 28px; cursor: pointer; } @@ -628,7 +641,8 @@ header.footer .header-content { font-size: 0.7em; } - .media-player .info, .media-player .cover { + .media-player .info, + .media-player .cover { margin-right: 0px; } } @@ -648,12 +662,12 @@ header.footer .header-content { max-height: 40vh; } -.torrent audio, .torrent video { +.torrent audio, +.torrent video { width: 100%; } .torrent video { - } .torrent .details { @@ -699,7 +713,7 @@ header.footer .header-content { @media (max-width: 625px) { .flex-table .flex-row { - /* flex-direction: column;*/ + /* flex-direction: column;*/ } } @@ -710,7 +724,7 @@ header.footer .header-content { } .main-view { - flex:1; + flex: 1; padding: 10px 15px; overflow-y: auto; height: 100%; @@ -755,7 +769,8 @@ header.footer .header-content { cursor: pointer; } -.public-messages-view .img-container, .torrent { +.public-messages-view .img-container, +.torrent { margin-left: -14px; margin-right: -14px; } @@ -808,13 +823,13 @@ header.footer .header-content { } .windows-titlebar { - -webkit-app-region: drag; - height: 24px; - background-color: var(--menu-bg); - padding: none; - margin: 0px; - display: flex; - align-items: center; + -webkit-app-region: drag; + height: 24px; + background-color: var(--menu-bg); + padding: none; + margin: 0px; + display: flex; + align-items: center; } .windows-titlebar img { @@ -822,10 +837,10 @@ header.footer .header-content { } .windows-titlebar .title-bar-btns { - -webkit-app-region: no-drag; - position: fixed; - top: 0px; - right: 0px; + -webkit-app-region: no-drag; + position: fixed; + top: 0px; + right: 0px; } .title-bar-btns button { @@ -877,15 +892,17 @@ header.footer .header-content { justify-content: left; } -.application-list a.logo img:last-child,.settings-list a.logo img:last-child { +.application-list a.logo img:last-child, +.settings-list a.logo img:last-child { margin-left: 5px; } -h1, .application-list a.logo span { - font-family: "Metuo-Black", Futura, Arial; +h1, +.application-list a.logo span { + font-family: 'Metuo-Black', Futura, Arial; } -@media (min-width:626px) and (max-width:1268px) { +@media (min-width: 626px) and (max-width: 1268px) { a.logo img:not(:last-child) { margin-right: 0; } @@ -899,7 +916,7 @@ h1, .application-list a.logo span { } } -@media (max-width:1268px) { +@media (max-width: 1268px) { .application-list { width: 78px; margin-left: 0; @@ -909,8 +926,9 @@ h1, .application-list a.logo span { padding: 15px; } - .application-list a.logo, .settings-list a.logo { - padding: 15px; + .application-list a.logo, + .settings-list a.logo { + padding: 15px; } .application-list a.logo img:first-child { @@ -922,47 +940,46 @@ h1, .application-list a.logo span { .application-list .text { display: none; } - } -@media (min-width:626px){ +@media (min-width: 626px) { .settings-list { background: var(--menu-bg); } } -@media (max-width:625px) { +@media (max-width: 625px) { .application-list { width: 250px; margin-left: -250px; position: fixed; top: 0; - left:0; + left: 0; bottom: 0; - } .settings-list { flex: 1; background: var(--body-bg); } - .participant-list, .participant-list.open { + .participant-list, + .participant-list.open { width: 250px; margin-right: -250px; position: fixed; top: 0; - right:0; + right: 0; bottom: 0; } - .application-list a{ + .application-list a { padding: 0; } - .application-list .text{ + .application-list .text { display: flex; } - .menu-visible-xs .application-list{ + .menu-visible-xs .application-list { margin-left: 0; } @@ -972,8 +989,12 @@ h1, .application-list a.logo span { } } -.application-list a:hover, .application-list a:focus, .application-list a:active, -.settings-list a:hover, .settings-list a:focus, .settings-list a:active{ +.application-list a:hover, +.application-list a:focus, +.application-list a:active, +.settings-list a:hover, +.settings-list a:focus, +.settings-list a:active { text-decoration: none; } @@ -985,14 +1006,17 @@ h1, .application-list a.logo span { background: var(--chat-active); } -.application-list .icon, .notifications-button , .settings-list .icon{ +.application-list .icon, +.notifications-button, +.settings-list .icon { padding: 7px; min-width: 55px; text-align: center; position: relative; } -.application-list .icon .unseen, .notifications-button .unseen{ +.application-list .icon .unseen, +.notifications-button .unseen { position: absolute; right: 5px; bottom: 5px; @@ -1008,11 +1032,11 @@ h1, .application-list a.logo span { .settings-list { z-index: 1; min-width: 220px; - + transition: all 0.25s ease; border-right: var(--sidebar-border-right); color: var(--text-color); - font-family: "Metuo-Black", Futura, Arial; + font-family: 'Metuo-Black', Futura, Arial; } .settings-list .electron-padding { @@ -1039,7 +1063,8 @@ h1, .application-list a.logo span { background: var(--chat-hover); } -.settings-list a:focus:not(.active), .application-list a:focus:not(.active) { +.settings-list a:focus:not(.active), +.application-list a:focus:not(.active) { background: var(--chat-hover); } @@ -1063,7 +1088,9 @@ a.msg { font-weight: bold; } -.hashtag-list a:active,.hashtag-list a:focus,.hashtag-list a:visited { +.hashtag-list a:active, +.hashtag-list a:focus, +.hashtag-list a:visited { text-decoration: none; } @@ -1087,7 +1114,7 @@ a.msg { .public-messages-view .msg-sender { margin-bottom: 15px; - display:flex; + display: flex; align-items: center; } @@ -1114,7 +1141,8 @@ a.msg { flex-direction: column; } -.public-messages-view .msg .text, .public-messages-view .msg .time { +.public-messages-view .msg .text, +.public-messages-view .msg .time { margin-bottom: 5px; } @@ -1144,7 +1172,7 @@ a.msg { bottom: 0; left: 0; margin: auto; - background: url("../assets/img/heart.png") no-repeat center/contain; + background: url('../assets/img/heart.png') no-repeat center/contain; opacity: 0; transform: scale(0); } @@ -1156,11 +1184,22 @@ a.msg { } @keyframes like-heart-animation { - 0% { opacity:0; transform:scale(0); } - 15% { opacity:.9; transform:scale(1.2); } - 30% { transform:scale(.95); } + 0% { + opacity: 0; + transform: scale(0); + } + 15% { + opacity: 0.9; + transform: scale(1.2); + } + 30% { + transform: scale(0.95); + } 45%, - 80% { opacity:.9; transform:scale(1); } + 80% { + opacity: 0.9; + transform: scale(1); + } } .dropbtn { @@ -1181,7 +1220,7 @@ a.msg { position: absolute; right: 0; min-width: 160px; - box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); + box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2); border-radius: 5px; z-index: 1; } @@ -1208,7 +1247,9 @@ a.msg { } /* Change color of dropdown links on hover */ -.dropdown-content a:hover {background-color: var(--header-color)} +.dropdown-content a:hover { + background-color: var(--header-color); +} /* Show the dropdown menu on hover */ .dropdown:hover .dropdown-content { @@ -1238,7 +1279,6 @@ a.msg { padding: 0; } - #chat-view { display: flex; flex-direction: row; @@ -1256,10 +1296,10 @@ a.msg { } #chat-main { - display:flex; - flex-direction:column; - flex:3; - max-width:100%; + display: flex; + flex-direction: column; + flex: 3; + max-width: 100%; min-width: 0; } @@ -1267,7 +1307,8 @@ a.msg { margin-right: 5px; } -.seen { /* omg, this fixes mess-up by iris-lib style */ +.seen { + /* omg, this fixes mess-up by iris-lib style */ color: inherit; } @@ -1296,7 +1337,8 @@ a.msg { display: none; } -.seen .iris-delivered-checkmark, .delivered .iris-delivered-checkmark { +.seen .iris-delivered-checkmark, +.delivered .iris-delivered-checkmark { display: initial; } @@ -1304,11 +1346,14 @@ a.msg { align-items: flex-start; } -.msg.their .msg-content, .msg.our .msg-content { +.msg.their .msg-content, +.msg.our .msg-content { max-width: 75%; } -.msg.their + .msg.our .msg-content, .day-separator + .msg.our .msg-content, .from-separator + .msg.our .msg-content { +.msg.their + .msg.our .msg-content, +.day-separator + .msg.our .msg-content, +.from-separator + .msg.our .msg-content { margin-top: 15px; border-radius: 8px 0px 8px 8px; } @@ -1332,7 +1377,9 @@ a.msg { display: none; } -.msg.our + .msg.their .msg-content, .day-separator + .msg.their .msg-content, .from-separator + .msg.their .msg-content { +.msg.our + .msg.their .msg-content, +.day-separator + .msg.their .msg-content, +.from-separator + .msg.their .msg-content { margin-top: 15px; border-radius: 0px 8px 8px 8px; } @@ -1355,7 +1402,8 @@ a.msg { color: var(--text-time); } -.btn, .msg-btn { +.btn, +.msg-btn { user-select: none; } @@ -1433,19 +1481,21 @@ a.msg { } #login ::-webkit-input-placeholder { - text-align: center; + text-align: center; } -#login :-moz-placeholder { /* Firefox 18- */ - text-align: center; +#login :-moz-placeholder { + /* Firefox 18- */ + text-align: center; } -#login ::-moz-placeholder { /* Firefox 19+ */ - text-align: center; +#login ::-moz-placeholder { + /* Firefox 19+ */ + text-align: center; } #login :-ms-input-placeholder { - text-align: center; + text-align: center; } #login a { @@ -1528,9 +1578,9 @@ hr { } .chat-message-form { - flex:1; + flex: 1; flex-direction: row; - max-height:70px; + max-height: 70px; background-color: var(--header-color); padding: 10px 15px; } @@ -1545,9 +1595,10 @@ hr { margin: 0; } - - -.message-form button, .message-form button:hover, .message-form button:active, .message-form button:focus { +.message-form button, +.message-form button:hover, +.message-form button:active, +.message-form button:focus { flex: none; color: var(--msg-form-button-color); background-color: transparent; @@ -1566,12 +1617,12 @@ hr { .user-info { width: 100%; - flex:1; + flex: 1; display: flex; flex-direction: row; align-self: center; align-items: flex-start; - max-height:60px; + max-height: 60px; overflow-y: hidden; overflow-x: hidden; padding: 10px 0 10px 15px; @@ -1604,7 +1655,7 @@ hr { } .chat-list { - flex:1; + flex: 1; overflow-y: auto; } @@ -1635,11 +1686,10 @@ hr { background: var(--chat-hover); } - .chat-new-item-inner { padding: 0px 10px 0px 15px; - align-items: center; - flex-direction: row; + align-items: center; + flex-direction: row; display: flex; margin-top: -1px; } @@ -1647,24 +1697,29 @@ hr { background-color: var(--body-bg); } -.chat-item .identicon-container,.chat-new-item .identicon-container { +.chat-item .identicon-container, +.chat-new-item .identicon-container { flex: none; align-self: center; margin-right: 15px !important; } -.chat-item.has-unseen .text, .chat-item.has-unseen .name { +.chat-item.has-unseen .text, +.chat-item.has-unseen .name { font-weight: bold; } -.chat-item .text,.chat-new-item .text { +.chat-item .text, +.chat-new-item .text { padding-right: 5px; padding-top: 16px; border-bottom: 1px solid var(--chat-hover); line-height: 20px; } -.chat-item .text, .nav .text,.chat-new-item .text { +.chat-item .text, +.nav .text, +.chat-new-item .text { flex: 1; text-overflow: ellipsis; white-space: nowrap; @@ -1675,15 +1730,16 @@ hr { text-align: center; } -.chat-item .latest-time,.chat-new-item-qrcode { +.chat-item .latest-time, +.chat-new-item-qrcode { float: right; } .unseen { display: inline-block; border-radius: 1.1em; - padding: .3em .4em; - min-width: .8em; + padding: 0.3em 0.4em; + min-width: 0.8em; background-color: var(--heart-color); color: white; text-align: center; @@ -1696,32 +1752,38 @@ hr { float: right; } -small, .text-muted { +small, +.text-muted { font-size: 12px; color: var(--small-text-color); } -.chat-item.active-item, .chat-item.active-item:hover { +.chat-item.active-item, +.chat-item.active-item:hover { background: var(--chat-active); } -.chat-item:hover, .chat-item:focus { +.chat-item:hover, +.chat-item:focus { background: var(--chat-hover); outline: none; } -.chat-item.new, .chat-item.public-messages { +.chat-item.new, +.chat-item.public-messages { align-items: center; } -.chat-item.new ,.chat-new-item { +.chat-item.new, +.chat-new-item { border-bottom: 1px solid var(--chat-hover); } .chat-item.public-messages { } -.chat-item.new svg,.chat-new-item svg{ +.chat-item.new svg, +.chat-new-item svg { color: var(--small-text-color); } @@ -1769,7 +1831,9 @@ small, .text-muted { color: #262626; } -#not-seen-by-them button:hover, #not-seen-by-them button:active, #not-seen-by-them button:focus { +#not-seen-by-them button:hover, +#not-seen-by-them button:active, +#not-seen-by-them button:focus { background: #ccc; } @@ -1792,7 +1856,8 @@ small, .text-muted { color: var(--text-color); } -#enable-notifications-prompt:hover a, #enable-notifications-prompt:focus a { +#enable-notifications-prompt:hover a, +#enable-notifications-prompt:focus a { text-decoration: underline; } @@ -1808,11 +1873,11 @@ small, .text-muted { max-height: 400px; } -@media (max-width:625px) { +@media (max-width: 625px) { .profile-photo-picker.open { position: fixed; z-index: 10; - background: rgba(0,0,0,0.85); + background: rgba(0, 0, 0, 0.85); left: 0; right: 0; top: 0; @@ -1844,7 +1909,7 @@ small, .text-muted { } button { - cursor: pointer; + cursor: pointer; } form.public button { @@ -1870,7 +1935,7 @@ form.public div { position: relative; } -form.public button[type="submit"] { +form.public button[type='submit'] { position: absolute; right: 0; } @@ -1880,16 +1945,17 @@ form.public { } .profile-header-stuff { - display:flex; - flex-direction:column; + display: flex; + flex-direction: column; } .profile-header-stuff .profile-about { margin-bottom: 15px; - flex:1; + flex: 1; } -.profile-actions button, .feed-container > p button { +.profile-actions button, +.feed-container > p button { margin-right: 5px; margin-bottom: 5px; padding: 5px 20px; @@ -1915,13 +1981,15 @@ form.public { font-weight: bold; } -.follow, .block { +.follow, +.block { min-width: 110px; transition: background-color 0.1s ease; padding: 5px 20px; } -.block.blocked, .follow.following { +.block.blocked, +.follow.following { background-color: var(--notify); color: var(--text-color); border: var(--button-border-size) solid var(--notify); @@ -1939,15 +2007,18 @@ form.public { background-color: var(--warning-background); } -.block .hover, .follow .hover { +.block .hover, +.follow .hover { display: none; } -.block.blocked:hover .hover, .follow.following:hover .hover { +.block.blocked:hover .hover, +.follow.following:hover .hover { display: inline; } -.block.blocked:hover .nonhover, .follow.following:hover .nonhover { +.block.blocked:hover .nonhover, +.follow.following:hover .nonhover { display: none; } @@ -1967,12 +2038,15 @@ form.public { margin-right: 15px; } -.qr-container img, .qr-container canvas { +.qr-container img, +.qr-container canvas { border: 5px solid white; } /* Places where emojis may appear */ -.user-name, .name, input[type=text] { +.user-name, +.name, +input[type='text'] { line-height: 1.3em; } @@ -2018,7 +2092,8 @@ form.public { padding: 7px 15px; } -.online > .online-indicator, .active > .online-indicator { +.online > .online-indicator, +.active > .online-indicator { background-color: #f6a539; } @@ -2040,11 +2115,13 @@ form.public { } .add-friend { - background-color: hsla(194, 55%, 87%, 1);; + background-color: hsla(194, 55%, 87%, 1); } -.add-friend:hover, .add-friend:active, .add-friend:focus { - background-color: hsla(194, 55%, 80%, 1);; +.add-friend:hover, +.add-friend:active, +.add-friend:focus { + background-color: hsla(194, 55%, 80%, 1); } .round-borders { @@ -2166,7 +2243,6 @@ form.public { margin-right: 30px; } - /* Tooltip container */ .tooltip { position: relative; @@ -2201,7 +2277,8 @@ form.public { /* Needs to be visible for tooltip */ @media (min-width: 626px) { - .ReactVirtualized__Grid, .ReactVirtualized__Grid__innerScrollContainer { + .ReactVirtualized__Grid, + .ReactVirtualized__Grid__innerScrollContainer { overflow: visible !important; } } @@ -2258,16 +2335,19 @@ form.public { #profile { padding: 0; } - #profile .content, .main-view.public-messages-view { + #profile .content, + .main-view.public-messages-view { padding: 0; } .main-view.public-messages-view .centered-container > h3 { - padding-left: 15px; + padding-left: 15px; } - .public-messages-view .msg .msg-content, textarea { + .public-messages-view .msg .msg-content, + textarea { border-radius: 0 !important; } - .public-messages-view .img-container, .torrent { + .public-messages-view .img-container, + .torrent { margin-left: -15px; margin-right: -15px; } @@ -2309,7 +2389,11 @@ form.public { .chat-message-form button { padding: 5px 10px; } - .profile-photo-container .identicon-container * { max-width: 80px; max-height: 80px; text-align: center;} + .profile-photo-container .identicon-container * { + max-width: 80px; + max-height: 80px; + text-align: center; + } .side-padding-xs { padding: 5px 0 0 5px; } @@ -2319,15 +2403,17 @@ form.public { } .profile-header { - margin-bottom: 0; + margin-bottom: 0; } } @media (min-width: 626px) { - .visible-xs-block, .visible-xs-inline-block, .visible-xs-flex { + .visible-xs-block, + .visible-xs-inline-block, + .visible-xs-flex { display: none !important; } .msg.standalone { - margin-top: 15px; + margin-top: 15px; } } diff --git a/src/js/BaseComponent.ts b/src/js/BaseComponent.ts index 1df7917f..e0aff3a9 100644 --- a/src/js/BaseComponent.ts +++ b/src/js/BaseComponent.ts @@ -1,39 +1,44 @@ -import { PureComponent } from 'preact/compat'; +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { PureComponent } from 'react'; +import type { GunCallbackOn, GunSchema, IGunOnEvent } from 'gun'; -type EL = { - off: Function; -}; +type EL = IGunOnEvent; type OwnState = { ogImageUrl?: any; }; -export default abstract class BaseComponent extends PureComponent { +export default abstract class BaseComponent extends PureComponent< + Props, + State & OwnState +> { unmounted?: boolean; eventListeners: Record = {}; - sub(callback: Function, path?: string) { - return (v: unknown, k: string, x: unknown, e: EL | undefined, f: unknown) => { + sub(callback: CallableFunction, path?: string): GunCallbackOn { + const cb = (data, key, message, event, f): void => { if (this.unmounted) { - e && e.off(); + event && event.off(); return; } - this.eventListeners[path ?? k] = e; - callback(v,k,x,e,f); - } + this.eventListeners[path ?? key] = event; + callback(data, key, message, event, f); + }; + + return cb as any; } - inject(name?: string, path?: string) { + inject(name?: string, path?: string): GunCallbackOn { return this.sub((v: unknown, k: string) => { - const newState: Partial = {}; + const newState: any = {}; newState[(name ?? k) as keyof State] = v as any; this.setState(newState); }, path); } unsubscribe() { - Object.keys(this.eventListeners).forEach(k => { + Object.keys(this.eventListeners).forEach((k) => { const l = this.eventListeners[k]; l && l.off(); delete this.eventListeners[k]; @@ -48,10 +53,12 @@ export default abstract class BaseComponent extends Pure isUserAgentCrawler() { // return true; // for testing const ua = navigator.userAgent.toLowerCase(); - return (ua.indexOf('prerender') !== -1 || + return ( + ua.indexOf('prerender') !== -1 || ua.indexOf('whatsapp') !== -1 || ua.indexOf('crawl') !== -1 || - ua.indexOf('bot') !== -1); + ua.indexOf('bot') !== -1 + ); } async setOgImageUrl(imgSrc?: string) { @@ -66,10 +73,12 @@ export default abstract class BaseComponent extends Pure const { default: pica } = await import('./lib/pica.min'); await pica().resize(image, resizedCanvas); const ogImage = resizedCanvas.toDataURL('image/jpeg', 0.1); - const ogImageUrl = `https://iris-base64-decoder.herokuapp.com/?s=${encodeURIComponent(ogImage)}`; + const ogImageUrl = `https://iris-base64-decoder.herokuapp.com/?s=${encodeURIComponent( + ogImage, + )}`; console.log(ogImageUrl); - this.state.ogImageUrl - this.setState({ogImageUrl}); + this.state.ogImageUrl; + this.setState({ ogImageUrl }); }; image.src = imgSrc; } diff --git a/src/js/Helpers.tsx b/src/js/Helpers.tsx index 0eeb041d..2adae222 100644 --- a/src/js/Helpers.tsx +++ b/src/js/Helpers.tsx @@ -1,13 +1,16 @@ -import {translate as t} from './translations/Translation'; -import $ from 'jquery'; -import _ from 'lodash'; -import { route } from 'preact-router'; +/* eslint-disable @typescript-eslint/no-explicit-any */ import reactStringReplace from 'react-string-replace'; import iris from 'iris-lib'; -import Name from "./components/Name"; +import $ from 'jquery'; +import throttle from 'lodash/throttle'; +import { route } from 'preact-router'; -const emojiRegex = /([\u{1f300}-\u{1f5ff}\u{1f900}-\u{1f9ff}\u{1f600}-\u{1f64f}\u{1f680}-\u{1f6ff}\u{2600}-\u{26ff}\u{2700}-\u{27bf}\u{1f1e6}-\u{1f1ff}\u{1f191}-\u{1f251}\u{1f004}\u{1f0cf}\u{1f170}-\u{1f171}\u{1f17e}-\u{1f17f}\u{1f18e}\u{3030}\u{2b50}\u{2b55}\u{2934}-\u{2935}\u{2b05}-\u{2b07}\u{2b1b}-\u{2b1c}\u{3297}\u{3299}\u{303d}\u{00a9}\u{00ae}\u{2122}\u{23f3}\u{24c2}\u{23e9}-\u{23ef}\u{25b6}\u{23f8}-\u{23fa}]+)/ug; -const pubKeyRegex = /(\B\@[\w-_]{20,50}\.[\w-_]{20,50}\b)/g; +import Name from './components/Name'; +import { translate as t } from './translations/Translation'; + +const emojiRegex = + /([\u{1f300}-\u{1f5ff}\u{1f900}-\u{1f9ff}\u{1f600}-\u{1f64f}\u{1f680}-\u{1f6ff}\u{2600}-\u{26ff}\u{2700}-\u{27bf}\u{1f1e6}-\u{1f1ff}\u{1f191}-\u{1f251}\u{1f004}\u{1f0cf}\u{1f170}-\u{1f171}\u{1f17e}-\u{1f17f}\u{1f18e}\u{3030}\u{2b50}\u{2b55}\u{2934}-\u{2935}\u{2b05}-\u{2b07}\u{2b1b}-\u{2b1c}\u{3297}\u{3299}\u{303d}\u{00a9}\u{00ae}\u{2122}\u{23f3}\u{24c2}\u{23e9}-\u{23ef}\u{25b6}\u{23f8}-\u{23fa}]+)/gu; +const pubKeyRegex = /(\B@[\w-_]{20,50}\.[\w-_]{20,50}\b)/g; function setImgSrc(el: JQuery, src: string): JQuery { if (src && src.indexOf('data:image') === 0) { @@ -17,7 +20,7 @@ function setImgSrc(el: JQuery, src: string): JQuery { } const userAgent = navigator.userAgent.toLowerCase(); -const isElectron = (userAgent.indexOf(' electron/') > -1); +const isElectron = userAgent.indexOf(' electron/') > -1; export default { wtClient: undefined as any, @@ -35,16 +38,24 @@ export default { highlightEverything(s: string): any[] { let replacedText = reactStringReplace(s, emojiRegex, (match, i) => { - return {match}; + return ( + + {match} + + ); }); replacedText = reactStringReplace(replacedText, pubKeyRegex, (match, i) => { const link = `/profile/${match.slice(1)}`; - return ( - @ - ); + return ( + + @ + + ); }); replacedText = reactStringReplace(replacedText, /(https?:\/\/\S+)/g, (match, i) => ( - {match} + + {match} + )); return replacedText; }, @@ -54,11 +65,13 @@ export default { const s = str.split('?'); let chatId; if (s.length === 2) { - chatId = iris.util.getUrlParameter('chatWith', s[1]) || iris.util.getUrlParameter('channelId', s[1]); + chatId = + iris.util.getUrlParameter('chatWith', s[1]) || + iris.util.getUrlParameter('channelId', s[1]); } if (chatId) { iris.session.newChannel(chatId, str); - route(`/chat/${ chatId}`); // TODO + route(`/chat/${chatId}`); // TODO return true; } if (str.indexOf('https://iris.to') === 0) { @@ -71,63 +84,66 @@ export default { copyToClipboard(text: string): boolean { if (window.clipboardData && window.clipboardData.setData) { // Internet Explorer-specific code path to prevent textarea being shown while dialog is visible. - window.clipboardData.setData("Text", text); + window.clipboardData.setData('Text', text); return true; - } - else if (document.queryCommandSupported && document.queryCommandSupported("copy")) { - let textarea = document.createElement("textarea"); + } else if (document.queryCommandSupported && document.queryCommandSupported('copy')) { + const textarea = document.createElement('textarea'); textarea.textContent = text; - textarea.style.position = "fixed"; // Prevent scrolling to bottom of page in Microsoft Edge. + textarea.style.position = 'fixed'; // Prevent scrolling to bottom of page in Microsoft Edge. document.body.appendChild(textarea); textarea.select(); try { - return document.execCommand("copy"); // Security exception may be thrown by some browsers. - } - catch (ex) { - console.warn("Copy to clipboard failed.", ex); + return document.execCommand('copy'); // Security exception may be thrown by some browsers. + } catch (ex) { + console.warn('Copy to clipboard failed.', ex); return false; - } - finally { + } finally { document.body.removeChild(textarea); - return true; } } }, getUrlParameter(sParam: string, sParams?: string) { - let sPageURL = sParams ?? window.location.search.substring(1), - sURLVariables = sPageURL.split('&'), - sParameterName, - i; + const sPageURL = sParams ?? window.location.search.substring(1), + sURLVariables = sPageURL.split('&'); + let sParameterName, i; for (i = 0; i < sURLVariables.length; i++) { sParameterName = sURLVariables[i].split('='); if (sParameterName[0] === sParam) { - return sParameterName[1] === undefined ? true : decodeURIComponent(sParameterName[1]); + return sParameterName[1] === undefined ? '' : decodeURIComponent(sParameterName[1]); } } }, showConsoleWarning(): void { - let i = "Stop!", - j = "This is a browser feature intended for developers. If someone told you to copy-paste something here to enable a feature or \"hack\" someone's account, it is a scam and will give them access to your account."; + const i = 'Stop!', + j = + 'This is a browser feature intended for developers. If someone told you to copy-paste something here to enable a feature or "hack" someone\'s account, it is a scam and will give them access to your account.'; - if ((window.chrome || window.safari)) { - let l = 'font-family:helvetica; font-size:20px; '; + if (window.chrome || window.safari) { + const l = 'font-family:helvetica; font-size:20px; '; [ - [i, `${l }font-size:50px; font-weight:bold; color:red; -webkit-text-stroke:1px black;`], - [j, l], - ['', ''] + [i, `${l}font-size:50px; font-weight:bold; color:red; -webkit-text-stroke:1px black;`], + [j, l], + ['', ''], ].map((r) => { - setTimeout(console.log.bind(console, `\n%c${ r[0]}`, r[1])); + setTimeout(console.log.bind(console, `\n%c${r[0]}`, r[1])); }); } }, getRelativeTimeText(date: Date): string { - let text = date && iris.util.getDaySeparatorText(date, date.toLocaleDateString(undefined, {dateStyle:'short'})); + let text = + date && + iris.util.getDaySeparatorText( + date, + date.toLocaleDateString(undefined, { dateStyle: 'short' }), + ); text = t(text); - if (text === t('today')) { text = iris.util.formatTime(date); } + if (text === t('today')) { + text = iris.util.formatTime(date); + } return text; }, @@ -140,37 +156,38 @@ export default { const i = Math.floor(Math.log(bytes) / Math.log(k)); - return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) } ${ sizes[i]}`; + return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`; }, download(filename: string, data: string, type: string, charset: string, href: string): void { - let hiddenElement; if (charset === null) { charset = 'utf-8'; } - hiddenElement = document.createElement('a'); - hiddenElement.href = href || (`data:${type};charset=${charset},${encodeURI(data)}`); + const hiddenElement = document.createElement('a'); + hiddenElement.href = href || `data:${type};charset=${charset},${encodeURI(data)}`; hiddenElement.target = '_blank'; hiddenElement.download = filename; hiddenElement.click(); }, getBase64(file: Blob): Promise { - let reader = new FileReader(); + const reader = new FileReader(); reader.readAsDataURL(file); return new Promise((resolve, reject) => { reader.onload = function () { resolve(reader.result); }; reader.onerror = function (error) { - reject(`Error: ${ error}`); + reject(`Error: ${error}`); }; }); }, - scrollToMessageListBottom: _.throttle(() => { + scrollToMessageListBottom: throttle(() => { if ($('#message-view')[0]) { - $('#message-view').scrollTop($('#message-view')[0].scrollHeight - $('#message-view')[0].clientHeight); + $('#message-view').scrollTop( + $('#message-view')[0].scrollHeight - $('#message-view')[0].clientHeight, + ); } }, 100), @@ -178,17 +195,29 @@ export default { animateScrollTop: (selector: string): void => { const el = $(selector); - el.css({overflow:'hidden'}); + el.css({ overflow: 'hidden' }); setTimeout(() => { - el.css({overflow:''}); - el.on("scroll mousedown wheel DOMMouseScroll mousewheel keyup touchstart", e => { - if (e.which && e.which > 0 || e.type === "mousedown" || e.type === "mousewheel" || e.type === 'touchstart') { + el.css({ overflow: '' }); + el.on('scroll mousedown wheel DOMMouseScroll mousewheel keyup touchstart', (e) => { + if ( + (e.which && e.which > 0) || + e.type === 'mousedown' || + e.type === 'mousewheel' || + e.type === 'touchstart' + ) { el.stop(true); } }); - el.stop().animate({ scrollTop: 0 }, {duration: 400, queue: false, always: () => { - el.off("scroll mousedown wheel DOMMouseScroll mousewheel keyup touchstart"); - }}); + el.stop().animate( + { scrollTop: 0 }, + { + duration: 400, + queue: false, + always: () => { + el.off('scroll mousedown wheel DOMMouseScroll mousewheel keyup touchstart'); + }, + }, + ); }, 10); }, @@ -197,5 +226,5 @@ export default { }, isElectron, - pubKeyRegex + pubKeyRegex, }; diff --git a/src/js/Icons.tsx b/src/js/Icons.tsx index 0f16c82b..76c906ed 100644 --- a/src/js/Icons.tsx +++ b/src/js/Icons.tsx @@ -1,34 +1,179 @@ export default { - settings: - , + settings: ( + + + + + + ), - home: , + home: ( + + + + ), - videoCall: , + videoCall: ( + + + + + + ), //voiceCall: , + chat: ( + + + + ), - chat: , + circle: ( + + + + ), - circle: , + folder: ( + + + + ), - folder: , + feed: ( + + + + + + ), - feed: , + store: ( + + + + + + + + + + + + + + + + ), - store: , + close: ( + + + + ), - close: , + play: ( + + + + + ), - play: , + pause: ( + + + + + + ), - pause: -, - - user: - - + - - , - - group: - - + s219.965-43.768,285.409,48.415c24.601,34.653,37.807,76.104,37.786,118.602C443.744,281.405,430.46,322.802,405.753,357.336z" + /> + + ), + group: ( + - - - - - - - - - - - - - - - - - - - - , - heartEmpty: , - heartFull: , - herokuButton: - - - - - - - - - - + S385.535,31.459,331.109,31.459z" + /> - - - , - menu: , - language: , - search: , - backArrow: - + + - + + + + + + + + + + + + + + ), + heartEmpty: ( + + + + ), + heartFull: ( + + + + ), + herokuButton: ( + + + + + + + + + + + + + + ), + menu: ( + + + + + + + + ), + language: ( + + + + + ), + search: ( + + + + ), + backArrow: ( + + + + C449.654,211.494,437.806,195.059,420.361,192.229z" + /> + + + ), + info: ( + + + + ), + network: ( + + + + + + + + + + + + - - , - info: , - network: , - QRcode: + + + + + + + + + + + + ), + QRcode: ( + + + + + + ), }; diff --git a/src/js/Main.tsx b/src/js/Main.tsx index e2bd8c97..cd2b4278 100644 --- a/src/js/Main.tsx +++ b/src/js/Main.tsx @@ -1,34 +1,31 @@ -import Component from './BaseComponent'; -import { Router, RouterOnChangeArgs, CustomHistory } from 'preact-router'; -import AsyncRoute from 'preact-async-route'; +import { Helmet } from 'react-helmet'; import { createHashHistory } from 'history'; -import {Helmet} from "react-helmet"; -import { translationLoaded } from "./translations/Translation"; - -import Helpers from './Helpers'; -import QRScanner from './QRScanner'; - -import Settings from './views/settings/Settings'; -import LogoutConfirmation from './views/LogoutConfirmation'; -import Chat from './views/chat/Chat'; -import Notifications from './views/Notifications'; -import Hashtags from './views/Hashtags'; -import Login from './views/Login'; -import Profile from './views/Profile'; -import Group from './views/Group'; -import Message from './views/Message'; -import Follows from './views/Follows'; -import Feed from './views/Feed'; -import About from './views/About'; -import Contacts from './views/Contacts'; -import Torrent from './views/Torrent'; +import iris from 'iris-lib'; +import AsyncRoute from 'preact-async-route'; +import { CustomHistory, Router, RouterOnChangeArgs } from 'preact-router'; +import Footer from './components/Footer'; +import MediaPlayer from './components/MediaPlayer'; import Menu from './components/Menu'; import VideoCall from './components/VideoCall'; -import MediaPlayer from './components/MediaPlayer'; -import Footer from './components/Footer'; - -import iris from 'iris-lib'; +import { translationLoaded } from './translations/Translation'; +import About from './views/About'; +import Chat from './views/chat/Chat'; +import Contacts from './views/Contacts'; +import Feed from './views/Feed'; +import Follows from './views/Follows'; +import Group from './views/Group'; +import Hashtags from './views/Hashtags'; +import Login from './views/Login'; +import LogoutConfirmation from './views/LogoutConfirmation'; +import Message from './views/Message'; +import Notifications from './views/Notifications'; +import Profile from './views/Profile'; +import Settings from './views/settings/Settings'; +import Torrent from './views/Torrent'; +import Component from './BaseComponent'; +import Helpers from './Helpers'; +import QRScanner from './QRScanner'; import '../css/style.css'; import '../css/cropper.min.css'; @@ -37,7 +34,7 @@ if (window.location.host === 'iris.to' && window.location.pathname !== '/') { window.location.href = window.location.href.replace(window.location.pathname, '/'); } -type Props = {}; +type Props = Record; type ReactState = { loggedIn: boolean; @@ -46,82 +43,94 @@ type ReactState = { activeRoute: string; platform: string; translationLoaded: boolean; -} +}; -iris.session.init({autologin: window.location.hash.length > 2}); +iris.session.init({ autologin: window.location.hash.length > 2 }); -class Main extends Component { +class Main extends Component { componentDidMount() { iris.local().get('loggedIn').on(this.inject()); iris.local().get('toggleMenu').put(false); - iris.local().get('toggleMenu').on((show: boolean) => this.toggleMenu(show)); + iris + .local() + .get('toggleMenu') + .on((show: boolean) => this.toggleMenu(show)); iris.electron && iris.electron.get('platform').on(this.inject()); iris.local().get('unseenMsgsTotal').on(this.inject()); - translationLoaded.then(() => this.setState({translationLoaded: true})); + translationLoaded.then(() => this.setState({ translationLoaded: true })); } handleRoute(e: RouterOnChangeArgs) { - let activeRoute = e.url; - this.setState({activeRoute}); + const activeRoute = e.url; + this.setState({ activeRoute }); iris.local().get('activeRoute').put(activeRoute); QRScanner.cleanupScanner(); } onClickOverlay(): void { if (this.state.showMenu) { - this.setState({showMenu: false}); + this.setState({ showMenu: false }); } } toggleMenu(show: boolean): void { - this.setState({showMenu: typeof show === 'undefined' ? !this.state.showMenu : show}); + this.setState({ + showMenu: typeof show === 'undefined' ? !this.state.showMenu : show, + }); } electronCmd(name: string): void { - iris.electron.get('cmd').put({name, time: new Date().toISOString()}); + iris.electron.get('cmd').put({ name, time: new Date().toISOString() }); } render() { - let title = ""; + let title = ''; const s = this.state; if (s.activeRoute && s.activeRoute.length > 1) { title = Helpers.capitalize(s.activeRoute.replace('/', '')); } const isDesktopNonMac = s.platform && s.platform !== 'darwin'; - const titleTemplate = s.unseenMsgsTotal ? `(${s.unseenMsgsTotal}) %s | iris` : "%s | iris"; + const titleTemplate = s.unseenMsgsTotal ? `(${s.unseenMsgsTotal}) %s | iris` : '%s | iris'; const defaultTitle = s.unseenMsgsTotal ? `(${s.unseenMsgsTotal}) iris` : 'iris'; if (!s.translationLoaded) { - return ( -
- ); + return
; } if (!s.loggedIn && window.location.pathname.length > 2) { - return ( -
- ); + return
; } if (!s.loggedIn) { return (
- +
- ) + ); } const history = createHashHistory() as unknown; // TODO: align types between 'history' and 'preact-router' return (
{isDesktopNonMac ? (
- iris -
- - - -
-
+ iris +
+ + + +
+
) : null} -
- +
+ {title} @@ -134,65 +143,65 @@ class Main extends Component {
this.onClickOverlay()}>
- this.handleRoute(e)}> - - - - - - - - - - - - - - - - - - - - - - + this.handleRoute(e)}> + + + + + + + + + + + + + + + + + + + + + + {/* Lazy load stuff that is used less often */} import('./views/Store').then(module => module.default)} + path="/store/:store?" + getComponent={() => import('./views/Store').then((module) => module.default)} /> import('./views/Checkout').then(module => module.default)} + path="/checkout/:store?" + getComponent={() => import('./views/Checkout').then((module) => module.default)} /> import('./views/Product').then(module => module.default)} + path="/product/:product/:store" + getComponent={() => import('./views/Product').then((module) => module.default)} /> import('./views/Product').then(module => module.default)} + path="/product/new" + store={iris.session.getPubKey()} + getComponent={() => import('./views/Product').then((module) => module.default)} /> import('./views/Explorer').then(module => module.default)} + path="/explorer/:node" + getComponent={() => import('./views/Explorer').then((module) => module.default)} /> import('./views/Explorer').then(module => module.default)} + path="/explorer" + store={iris.session.getPubKey()} + getComponent={() => import('./views/Explorer').then((module) => module.default)} /> - - - + + +
- -
- + +
+
); } diff --git a/src/js/QRScanner.js b/src/js/QRScanner.js index 83c34023..2f08758d 100644 --- a/src/js/QRScanner.js +++ b/src/js/QRScanner.js @@ -1,8 +1,8 @@ let codeReader; function startPrivKeyQRScanner() { - return new Promise(resolve => { - startQRScanner('privkey-qr-video', result => { + return new Promise((resolve) => { + startQRScanner('privkey-qr-video', (result) => { let qr = JSON.parse(result.text); if (qr.priv !== undefined) { resolve(qr); @@ -17,28 +17,35 @@ function startChatLinkQRScanner(callback) { } async function startQRScanner(videoElementId, callback) { - const { BrowserQRCodeReader } = await import('@zxing/library'); - codeReader = new BrowserQRCodeReader(); - codeReader.decodeFromInputVideoDevice(undefined, videoElementId) - .then(result => { - if (callback(result)) { - cleanupScanner(); - } - }).catch(err => { - if (err != undefined) { - console.error(err) - } - if (codeReader != undefined && codeReader != null) { - cleanupScanner() - } - }); + const { BrowserQRCodeReader } = await import('@zxing/library'); + codeReader = new BrowserQRCodeReader(); + codeReader + .decodeFromInputVideoDevice(undefined, videoElementId) + .then((result) => { + if (callback(result)) { + cleanupScanner(); + } + }) + .catch((err) => { + if (err != undefined) { + console.error(err); + } + if (codeReader != undefined && codeReader != null) { + cleanupScanner(); + } + }); } function cleanupScanner() { - if (codeReader != undefined || codeReader != null) { - codeReader.reset(); - codeReader = null; - } + if (codeReader != undefined || codeReader != null) { + codeReader.reset(); + codeReader = null; + } } -export default {cleanupScanner, startQRScanner, startChatLinkQRScanner, startPrivKeyQRScanner}; +export default { + cleanupScanner, + startQRScanner, + startChatLinkQRScanner, + startPrivKeyQRScanner, +}; diff --git a/src/js/SMS.ts b/src/js/SMS.ts index 30602ee9..467fd0b6 100644 --- a/src/js/SMS.ts +++ b/src/js/SMS.ts @@ -1 +1,2 @@ -export const SMS_VERIFIER_PUB = 'ysavwX9TVnlDw93w9IxezCJqSDMyzIU-qpD8VTN5yko.3ll1dFdxLkgyVpejFkEMOFkQzp_tRrkT3fImZEx94Co'; +export const SMS_VERIFIER_PUB = + 'ysavwX9TVnlDw93w9IxezCJqSDMyzIU-qpD8VTN5yko.3ll1dFdxLkgyVpejFkEMOFkQzp_tRrkT3fImZEx94Co'; diff --git a/src/js/components/CopyButton.tsx b/src/js/components/CopyButton.tsx index f6d24fff..15217076 100644 --- a/src/js/components/CopyButton.tsx +++ b/src/js/components/CopyButton.tsx @@ -1,10 +1,12 @@ -import { Component } from 'preact'; -import Helpers from '../Helpers'; -import {translate as t} from '../translations/Translation'; -import $ from 'jquery'; -import { OptionalGetter } from '../types'; -import Button from './basic/Button'; import iris from 'iris-lib'; +import $ from 'jquery'; +import { Component } from 'preact'; + +import Helpers from '../Helpers'; +import { translate as t } from '../translations/Translation'; +import { OptionalGetter } from '../types'; + +import Button from './basic/Button'; type Props = { copyStr: OptionalGetter; @@ -22,7 +24,7 @@ class CopyButton extends Component { timeout?: ReturnType; copy(e: MouseEvent, copyStr: string) { - if (e.target === null){ + if (e.target === null) { return; } Helpers.copyToClipboard(copyStr); @@ -35,19 +37,20 @@ class CopyButton extends Component { this.originalWidth = this.originalWidth || width + 1; target.width(this.originalWidth); - this.setState({copied:true}); + this.setState({ copied: true }); if (this.timeout !== undefined) { clearTimeout(this.timeout); } - this.timeout = setTimeout(() => this.setState({copied:false}), 2000); + this.timeout = setTimeout(() => this.setState({ copied: false }), 2000); } onClick(e: MouseEvent) { e.preventDefault(); - const copyStr = typeof this.props.copyStr === 'function' ? this.props.copyStr() : this.props.copyStr; + const copyStr = + typeof this.props.copyStr === 'function' ? this.props.copyStr() : this.props.copyStr; if (iris.util.isMobile && !this.props.notShareable) { - navigator.share({url: copyStr, title: this.props.title}).catch(err => { + navigator.share({ url: copyStr, title: this.props.title }).catch((err) => { console.error('share failed', err); this.copy(e, copyStr); }); @@ -57,9 +60,9 @@ class CopyButton extends Component { } render() { - const text = this.state.copied ? t('copied') : (this.props.text || t('copy')); + const text = this.state.copied ? t('copied') : this.props.text || t('copy'); return ( - ); diff --git a/src/js/components/ExplorerNode.js b/src/js/components/ExplorerNode.js index d192eba1..505bd28c 100644 --- a/src/js/components/ExplorerNode.js +++ b/src/js/components/ExplorerNode.js @@ -1,31 +1,50 @@ -import BaseComponent from "../BaseComponent"; -import iris from "iris-lib"; -import {html} from "htm/preact"; -import Name from "./Name"; -import Text from "./Text"; -import Button from "./basic/Button"; +import { html } from 'htm/preact'; +import iris from 'iris-lib'; + +import BaseComponent from '../BaseComponent'; + +import Button from './basic/Button'; +import Name from './Name'; +import Text from './Text'; const hashRegex = /^(?:[A-Za-z0-9+/]{4}){10}(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)+$/; -const pubKeyRegex = /^[A-Za-z0-9\-\_]{40,50}\.[A-Za-z0-9\_\-]{40,50}$/; +const pubKeyRegex = /^[A-Za-z0-9\-_]{40,50}\.[A-Za-z0-9_-]{40,50}$/; const SHOW_CHILDREN_COUNT = 50; const chevronDown = html` - - - + + + `; const chevronRight = html` - - - + + + `; class ExplorerNode extends BaseComponent { constructor() { super(); this.children = []; - this.state = {groups:{}, children: {}, shownChildrenCount: SHOW_CHILDREN_COUNT}; + this.state = { + groups: {}, + children: {}, + shownChildrenCount: SHOW_CHILDREN_COUNT, + }; } getNode() { @@ -35,7 +54,12 @@ class ExplorerNode extends BaseComponent { path = path.replace('/Users', ''); } path = path.split('/'); - return path.slice(1).reduce((sum, current) => (current && sum.get(decodeURIComponent(current))) || sum, this.props.gun); + return path + .slice(1) + .reduce( + (sum, current) => (current && sum.get(decodeURIComponent(current))) || sum, + this.props.gun, + ); } return this.props.gun; } @@ -44,8 +68,9 @@ class ExplorerNode extends BaseComponent { return true; } - componentDidMount() { // TODO: this is messy; create separate classes for Public / Group / Local - this.isMine = this.props.path.indexOf(`Public/Users/~${ iris.session.getPubKey()}`) === 0; + componentDidMount() { + // TODO: this is messy; create separate classes for Public / Group / Local + this.isMine = this.props.path.indexOf(`Public/Users/~${iris.session.getPubKey()}`) === 0; this.isGroup = this.props.path.indexOf('Group/') === 0; this.isPublicRoot = this.props.path === 'Public'; this.isUserList = this.props.path === 'Public/Users'; @@ -53,66 +78,77 @@ class ExplorerNode extends BaseComponent { this.isLocal = this.props.path.indexOf('Local') === 0; this.children = {}; - if (this.props.children && typeof this.props.children === "object") { + if (this.props.children && typeof this.props.children === 'object') { this.children = Object.assign(this.children, this.props.children); } if (this.isPublicRoot) { this.children = Object.assign(this.children, { - '#':{value:{_:1}, displayName: "ContentAddressed"}, - Users:{value:{_:1}} + '#': { value: { _: 1 }, displayName: 'ContentAddressed' }, + Users: { value: { _: 1 } }, }); } - if (this.isUserList) { // always add yourself to the user list + if (this.isUserList) { + // always add yourself to the user list const obj = {}; - obj[`~${iris.session.getPubKey()}`] = {value:{_:1}}; + obj[`~${iris.session.getPubKey()}`] = { value: { _: 1 } }; this.children = Object.assign(this.children, obj); } if (this.isGroupRoot) { const groups = {}; - iris.local().get('groups').map(this.sub( - (v,k) => { - if (v) { - groups[k] = true; - } else { - delete groups[k]; - } - this.setState({groups}); - } - )); - } - this.setState({children: this.children, shownChildrenCount: SHOW_CHILDREN_COUNT}); - - const cb = this.sub( - async (v, k, c, e, from) => { - if (k === '_') { return; } - if (this.isPublicRoot && k.indexOf('~') === 0) { return; } - if (this.isUserList && !k.substr(1).match(pubKeyRegex)) { return; } - let encryption; - if (typeof v === 'string' && v.indexOf('SEA{') === 0) { - try { - const myKey = iris.session.getKey(); - let dec = await iris.SEA.decrypt(v, myKey); - if (dec === undefined) { - if (!this.mySecret) { - this.mySecret = await iris.SEA.secret(myKey.epub, myKey); - dec = await iris.SEA.decrypt(v, this.mySecret); - } - } - if (dec !== undefined) { - v = dec; - encryption = 'Decrypted'; + iris + .local() + .get('groups') + .map( + this.sub((v, k) => { + if (v) { + groups[k] = true; } else { - encryption = 'Encrypted'; + delete groups[k]; } - } catch(e) { - null; - } - } - const prev = this.children[k] || {}; - this.children[k] = Object.assign(prev, { value: v, encryption, from }); - this.setState({children: this.children}); + this.setState({ groups }); + }), + ); + } + this.setState({ + children: this.children, + shownChildrenCount: SHOW_CHILDREN_COUNT, + }); + + const cb = this.sub(async (v, k, c, e, from) => { + if (k === '_') { + return; } - ); + if (this.isPublicRoot && k.indexOf('~') === 0) { + return; + } + if (this.isUserList && !k.substr(1).match(pubKeyRegex)) { + return; + } + let encryption; + if (typeof v === 'string' && v.indexOf('SEA{') === 0) { + try { + const myKey = iris.session.getKey(); + let dec = await iris.SEA.decrypt(v, myKey); + if (dec === undefined) { + if (!this.mySecret) { + this.mySecret = await iris.SEA.secret(myKey.epub, myKey); + dec = await iris.SEA.decrypt(v, this.mySecret); + } + } + if (dec !== undefined) { + v = dec; + encryption = 'Decrypted'; + } else { + encryption = 'Encrypted'; + } + } catch (e) { + null; + } + } + const prev = this.children[k] || {}; + this.children[k] = Object.assign(prev, { value: v, encryption, from }); + this.setState({ children: this.children }); + }); if (this.isGroupRoot) { return; @@ -127,31 +163,41 @@ class ExplorerNode extends BaseComponent { onChildObjectClick(e, k) { e.preventDefault(); this.children[k].open = !this.children[k].open; - this.setState({children: this.children}); + this.setState({ children: this.children }); } onShowMoreClick(e, k) { e.preventDefault(); this.children[k].showMore = !this.children[k].showMore; - this.setState({children: this.children}); + this.setState({ children: this.children }); } renderChildObject(k, displayName) { - const path = `${this.props.path }/${ encodeURIComponent(k)}`; + const path = `${this.props.path}/${encodeURIComponent(k)}`; const substr = k.substr(1); return html`
- this.onChildObjectClick(e, k)}>${this.state.children[k].open ? chevronDown : chevronRight} + this.onChildObjectClick(e, k)} + >${this.state.children[k].open ? chevronDown : chevronRight} - - ${typeof k === 'string' && substr.match(pubKeyRegex) ? - html`<${Name} key=${k} pub=${substr} placeholder="user"/>` : - (displayName || k)} - + + ${typeof k === 'string' && substr.match(pubKeyRegex) + ? html`<${Name} key=${k} pub=${substr} placeholder="user" />` + : displayName || k} + ${iris.session.getPubKey() === substr ? html`(you)` : ''}
- ${this.state.children[k].open ? html`<${ExplorerNode} gun=${this.props.gun} indent=${this.props.indent + 1} key=${path} path=${path} isGroup=${this.props.isGroup}/>` : ''} + ${this.state.children[k].open + ? html`<${ExplorerNode} + gun=${this.props.gun} + indent=${this.props.indent + 1} + key=${path} + path=${path} + isGroup=${this.props.isGroup} + />` + : ''} `; } @@ -160,10 +206,18 @@ class ExplorerNode extends BaseComponent { const encryption = this.children[k].encryption; const from = this.children[k].from; const decrypted = encryption === 'Decrypted'; - const lnk = (href, text, cls) => html`${text}`; + const lnk = (href, text, cls) => + html`${text}`; const keyLinks = html` - ${typeof k === 'string' && k.match(hashRegex) ? lnk(`/post/${encodeURIComponent(k)}`, '#') : ''} - ${typeof k === 'string' && k.match(pubKeyRegex) ? lnk(`/explorer/Public%2F~${encodeURIComponent(encodeURIComponent(k))}`, html`<${Name} key=${k} pub=${k} placeholder="user"/>`) : ''} + ${typeof k === 'string' && k.match(hashRegex) + ? lnk(`/post/${encodeURIComponent(k)}`, '#') + : ''} + ${typeof k === 'string' && k.match(pubKeyRegex) + ? lnk( + `/explorer/Public%2F~${encodeURIComponent(encodeURIComponent(k))}`, + html`<${Name} key=${k} pub=${k} placeholder="user" />`, + ) + : ''} `; if (encryption) { if (!decrypted) { @@ -173,12 +227,14 @@ class ExplorerNode extends BaseComponent { } } else { const pub = iris.session.getPubKey(); - const path = (this.isMine || this.isLocal) && (`${this.props.path }/${ encodeURIComponent(k)}`) - .replace(`Public/Users/~${ pub }/`, '') - .replace(`Local/`, ''); + const path = + (this.isMine || this.isLocal) && + `${this.props.path}/${encodeURIComponent(k)}` + .replace(`Public/Users/~${pub}/`, '') + .replace(`Local/`, ''); if (typeof v === 'string' && v.indexOf('data:image') === 0) { - s = this.isMine ? html`` : html``; + s = this.isMine ? html`` : html``; } else { let stringified = JSON.stringify(v); let showToggle; @@ -190,45 +246,79 @@ class ExplorerNode extends BaseComponent { } const valueLinks = html` - ${typeof v === 'string' && v.match(hashRegex) ? lnk(`/post/${encodeURIComponent(v)}`, '#') : ''} - ${k !== 'epub' && typeof v === 'string' && v.match(pubKeyRegex) ? lnk(`/explorer/Public%2F~${encodeURIComponent(encodeURIComponent(v))}`, html`<${Name} key=${v} pub=${v} placeholder="user"/>`) : ''} - ${typeof from === 'string' ? html` from ${lnk(`/explorer/Public%2F~${encodeURIComponent(encodeURIComponent(from))}`, html`<${Name} key=${from} pub=${from} placeholder="user"/>`, '')}` : ''} + ${typeof v === 'string' && v.match(hashRegex) + ? lnk(`/post/${encodeURIComponent(v)}`, '#') + : ''} + ${k !== 'epub' && typeof v === 'string' && v.match(pubKeyRegex) + ? lnk( + `/explorer/Public%2F~${encodeURIComponent(encodeURIComponent(v))}`, + html`<${Name} key=${v} pub=${v} placeholder="user" />`, + ) + : ''} + ${typeof from === 'string' + ? html` + from + ${lnk( + `/explorer/Public%2F~${encodeURIComponent(encodeURIComponent(from))}`, + html`<${Name} key=${from} pub=${from} placeholder="user" />`, + '', + )}` + : ''} `; // TODO: || isGroup where you're participating - s = this.isMine || this.isLocal ? html` - <${Text} gun=${this.props.gun} placeholder="empty" key=${path} user=${this.isLocal ? null : pub} path=${path} editable=${true} json=${true}/> - ${valueLinks} - ` : - html` - - ${stringified} - ${showToggle ? html` - this.onShowMoreClick(e, k)} href="">${this.state.children[k].showMore ? 'less' : 'more'} - ` : ''} - ${valueLinks} - - `; + s = + this.isMine || this.isLocal + ? html` + <${Text} + gun=${this.props.gun} + placeholder="empty" + key=${path} + user=${this.isLocal ? null : pub} + path=${path} + editable=${true} + json=${true} + /> + ${valueLinks} + ` + : html` + + ${stringified} + ${showToggle + ? html` + this.onShowMoreClick(e, k)} href="" + >${this.state.children[k].showMore ? 'less' : 'more'} + ` + : ''} + ${valueLinks} + + `; } } return html`
${k} ${keyLinks}: - ${encryption ? html` - ${encryption} value - ${decrypted ? '🔓' : ''} - - ` : ''} ${s} + ${encryption + ? html` + ${encryption} value + ${decrypted ? '🔓' : ''} + + ` + : ''} + ${s}
`; } onExpandClicked() { const expandAll = !this.state.expandAll; - Object.keys(this.children).forEach(k => { + Object.keys(this.children).forEach((k) => { this.children[k].open = expandAll; }); - this.setState({expandAll, children: this.children}); + this.setState({ expandAll, children: this.children }); } onNewItemSubmit(e) { @@ -236,20 +326,22 @@ class ExplorerNode extends BaseComponent { if (this.state.newItemName) { let name = this.state.newItemName.trim(); if (this.state.newItemType === 'object') { - this.getNode().get(name).put({a:null}); + this.getNode().get(name).put({ a: null }); } else { this.getNode().get(name).put(''); } - this.setState({newItemType: false, newItemName: ''}); + this.setState({ newItemType: false, newItemName: '' }); } } onNewItemNameInput(e) { - this.setState({newItemName: e.target.value.trimStart().replace(' ', ' ')}); + this.setState({ + newItemName: e.target.value.trimStart().replace(' ', ' '), + }); } showNewItemClicked(type) { - this.setState({newItemType:type}); + this.setState({ newItemType: type }); setTimeout(() => document.querySelector('#newItemNameInput').focus(), 0); } @@ -261,54 +353,80 @@ class ExplorerNode extends BaseComponent { childrenKeys.unshift(a[0]); } - const renderChildren = children => { - return children.map(k => { + const renderChildren = (children) => { + return children.map((k) => { const v = this.state.children[k].value; const n = this.state.children[k].displayName; if (typeof v === 'object' && v && v['_']) { return this.renderChildObject(k, n); } - return this.renderChildValue(k, v); - + return this.renderChildValue(k, v); }); - } + }; const showMoreBtn = childrenKeys.length > this.state.shownChildrenCount; return html` - ${this.props.indent === 0 ? html` -
- ${this.props.showTools ? html` -

- this.onExpandClicked()}>${this.state.expandAll ? 'Close all' : 'Expand all'} - this.showNewItemClicked('object')}>New object - this.showNewItemClicked('value')}>New value - ${childrenKeys.length} items -

- `: ''} - ${this.state.newItemType ? html` + ${this.props.indent === 0 + ? html` +
+ ${this.props.showTools + ? html` +

+ this.onExpandClicked()} + >${this.state.expandAll ? 'Close all' : 'Expand all'} + this.showNewItemClicked('object')}>New object + this.showNewItemClicked('value')}>New value + ${childrenKeys.length} items +

+ ` + : ''} + ${this.state.newItemType + ? html`

this.onNewItemSubmit(e)}> - this.onNewItemNameInput(e)} value=${this.state.newItemName} placeholder="New ${this.state.newItemType} name"/> + + this.onNewItemNameInput(e)} value=${this.state.newItemName} placeholder="New ${ + this.state.newItemType + } name"/> <${Button} type="submit">Create - <${Button} onClick=${() => this.setState({newItemType: false})}>Cancel + <${Button} onClick=${() => this.setState({ newItemType: false })}>Cancel

- ` : ''} -
- `: ''} - - ${this.isGroupRoot ? Object.keys(this.state.groups).map(group => html` -
- ${chevronRight} - ${group} -
- `) : renderChildren(childrenKeys.slice(0, this.state.shownChildrenCount))} - - ${showMoreBtn ? html` - {e.preventDefault();this.setState({shownChildrenCount: this.state.shownChildrenCount + SHOW_CHILDREN_COUNT})}}>More (${childrenKeys.length - this.state.shownChildrenCount}) - ` : ''} + ` + : ''} +
+ ` + : ''} + ${this.isGroupRoot + ? Object.keys(this.state.groups).map( + (group) => html` +
+ ${chevronRight} + ${group} +
+ `, + ) + : renderChildren(childrenKeys.slice(0, this.state.shownChildrenCount))} + ${showMoreBtn + ? html` + { + e.preventDefault(); + this.setState({ + shownChildrenCount: this.state.shownChildrenCount + SHOW_CHILDREN_COUNT, + }); + }} + >More (${childrenKeys.length - this.state.shownChildrenCount}) + ` + : ''} `; } } -export default ExplorerNode; \ No newline at end of file +export default ExplorerNode; diff --git a/src/js/components/FeedMessageForm.js b/src/js/components/FeedMessageForm.js index 46994560..1ba4d5c5 100644 --- a/src/js/components/FeedMessageForm.js +++ b/src/js/components/FeedMessageForm.js @@ -1,25 +1,26 @@ -import { createRef } from 'preact'; -import Helpers from '../Helpers'; import { html } from 'htm/preact'; -import { translate as t } from '../translations/Translation'; import iris from 'iris-lib'; - -import SafeImg from './SafeImg'; -import Torrent from './Torrent'; import $ from 'jquery'; -import EmojiButton from '../lib/emoji-button'; -import SearchBox from './SearchBox'; -import MessageForm from './MessageForm'; +import { createRef } from 'preact'; -const mentionRegex = /\B\@[\u00BF-\u1FFF\u2C00-\uD7FF\w]*$/; +import Helpers from '../Helpers'; +import EmojiButton from '../lib/emoji-button'; +import { translate as t } from '../translations/Translation'; + +import MessageForm from './MessageForm'; +import SafeImg from './SafeImg'; +import SearchBox from './SearchBox'; +import Torrent from './Torrent'; + +const mentionRegex = /\B@[\u00BF-\u1FFF\u2C00-\uD7FF\w]*$/; class FeedMessageForm extends MessageForm { newMsgRef = createRef(); componentDidMount() { const textEl = $(this.newMsgRef.current); - this.picker = new EmojiButton({position: 'top-start'}); - this.picker.on('emoji', emoji => { + this.picker = new EmojiButton({ position: 'top-start' }); + this.picker.on('emoji', (emoji) => { textEl.val(textEl.val() + emoji); textEl.focus(); }); @@ -27,7 +28,12 @@ class FeedMessageForm extends MessageForm { textEl.focus(); } if (!this.props.replyingTo) { - iris.local().get('channels').get('public').get('msgDraft').once(t => !textEl.val() && textEl.val(t)); + iris + .local() + .get('channels') + .get('public') + .get('msgDraft') + .once((t) => !textEl.val() && textEl.val(t)); } } @@ -38,9 +44,13 @@ class FeedMessageForm extends MessageForm { } const textEl = $(this.newMsgRef.current); const text = textEl.val(); - if (!text.length && !this.state.attachments && !this.state.torrentId) { return; } - if (this.props.index === 'media' && !this.state.torrentId) { return; } - const msg = {text}; + if (!text.length && !this.state.attachments && !this.state.torrentId) { + return; + } + if (this.props.index === 'media' && !this.state.torrentId) { + return; + } + const msg = { text }; if (this.props.replyingTo) { msg.replyingTo = this.props.replyingTo; } @@ -50,23 +60,32 @@ class FeedMessageForm extends MessageForm { if (this.state.torrentId) { msg.torrentId = this.state.torrentId; } - this.sendPublic(msg).then(hash => { + this.sendPublic(msg).then((hash) => { if (this.props.replyingToUser && this.props.replyingToUser !== iris.session.getPubKey()) { - const title = `${iris.session.getMyName() } replied to your message`; - const body = `'${text.length > 100 ? `${text.slice(0, 100) }...` : text}'`; - iris.notifications.sendIrisNotification(this.props.replyingToUser, {event:'reply', target: hash}); - iris.notifications.sendWebPushNotification(this.props.replyingToUser, {title,body}); + const title = `${iris.session.getMyName()} replied to your message`; + const body = `'${text.length > 100 ? `${text.slice(0, 100)}...` : text}'`; + iris.notifications.sendIrisNotification(this.props.replyingToUser, { + event: 'reply', + target: hash, + }); + iris.notifications.sendWebPushNotification(this.props.replyingToUser, { + title, + body, + }); } const mentions = text.match(Helpers.pubKeyRegex); if (mentions) { - mentions.forEach(match => { - iris.notifications.sendIrisNotification(match.slice(1), {event:'mention', target: hash}); + mentions.forEach((match) => { + iris.notifications.sendIrisNotification(match.slice(1), { + event: 'mention', + target: hash, + }); }); } }); - this.setState({attachments:null, torrentId:null}); + this.setState({ attachments: null, torrentId: null }); textEl.val(''); - textEl.height(""); + textEl.height(''); this.props.onSubmit && this.props.onSubmit(msg); } @@ -76,16 +95,19 @@ class FeedMessageForm extends MessageForm { } setTextareaHeight(textarea) { - textarea.style.height = ""; - textarea.style.height = `${textarea.scrollHeight }px`; + textarea.style.height = ''; + textarea.style.height = `${textarea.scrollHeight}px`; } onMsgTextPaste(event) { const pasted = (event.clipboardData || window.clipboardData).getData('text'); const magnetRegex = /^magnet:\?xt=urn:btih:*/; - if (pasted !== this.state.torrentId && pasted.indexOf('.torrent') > -1 || pasted.match(magnetRegex)) { + if ( + (pasted !== this.state.torrentId && pasted.indexOf('.torrent') > -1) || + pasted.match(magnetRegex) + ) { event.preventDefault(); - this.setState({torrentId: pasted}); + this.setState({ torrentId: pasted }); } } @@ -111,11 +133,11 @@ class FeedMessageForm extends MessageForm { attachmentsChanged(event) { let files = event.target.files; if (files) { - for (let i = 0;i < files.length;i++) { - Helpers.getBase64(files[i]).then(base64 => { + for (let i = 0; i < files.length; i++) { + Helpers.getBase64(files[i]).then((base64) => { const a = this.state.attachments || []; - a.push({type: 'image', data: base64}); - this.setState({attachments: a}); + a.push({ type: 'image', data: base64 }); + this.setState({ attachments: a }); }); } $(event.target).val(null); @@ -126,51 +148,149 @@ class FeedMessageForm extends MessageForm { onSelectMention(item) { const textarea = $(this.base).find('textarea').get(0); const pos = textarea.selectionStart; - const join = [textarea.value.slice(0, pos).replace(mentionRegex, '@'), item.key, textarea.value.slice(pos)].join('') + const join = [ + textarea.value.slice(0, pos).replace(mentionRegex, '@'), + item.key, + textarea.value.slice(pos), + ].join(''); textarea.value = `${join} `; textarea.focus(); textarea.selectionStart = textarea.selectionEnd = pos + item.key.length; } render() { - const textareaPlaceholder = this.props.index === 'media' ? 'type_a_message_or_paste_a_magnet_link' : 'type_a_message'; - return html`
this.onMsgFormSubmit(e)}> - this.attachmentsChanged(e)}/> - ${this.props.index === 'media' ? html` -

- -

- `: ''} -