From 41111cb1bbe4c311a5522b84adac26d3202e4ad6 Mon Sep 17 00:00:00 2001 From: Sumit Kumar Soni Date: Sun, 2 Aug 2026 20:46:00 +0530 Subject: [PATCH 1/7] fix: restore WebUI styles in SCSS sources --- webui-src/app/scss/abstracts/_mixins.scss | 4 +- webui-src/app/scss/pages/_chat.scss | 491 +++++++++++++++++++++- webui-src/app/scss/pages/_config.scss | 5 +- webui-src/app/scss/pages/_files.scss | 7 +- webui-src/app/scss/pages/_mail.scss | 5 +- webui-src/app/scss/pages/_network.scss | 1 + webui-src/app/scss/pages/_people.scss | 131 ++++++ webui-src/styles.css | 2 +- 8 files changed, 635 insertions(+), 11 deletions(-) diff --git a/webui-src/app/scss/abstracts/_mixins.scss b/webui-src/app/scss/abstracts/_mixins.scss index c17bede..4665aa0 100644 --- a/webui-src/app/scss/abstracts/_mixins.scss +++ b/webui-src/app/scss/abstracts/_mixins.scss @@ -15,11 +15,11 @@ border: 0; border-radius: 5px; cursor: pointer; - box-shadow: inset -3px -3px 0 darken($color: $bg-color, $amount: 20); + box-shadow: inset -3px -3px 0 color.adjust($bg-color, $lightness: -20%); &:active { outline: none; - box-shadow: inset 3px 3px 0 darken($color: $bg-color, $amount: 20); + box-shadow: inset 3px 3px 0 color.adjust($bg-color, $lightness: -20%); } } diff --git a/webui-src/app/scss/pages/_chat.scss b/webui-src/app/scss/pages/_chat.scss index 5226f76..6ac65bd 100644 --- a/webui-src/app/scss/pages/_chat.scss +++ b/webui-src/app/scss/pages/_chat.scss @@ -372,6 +372,7 @@ textarea.chatMsg { padding: 1.25rem; border-bottom: 1px solid #e2e8f0; background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%); + position: relative; // anchors the absolutely-positioned .chat-create-lobby-btn .profile-header { display: flex; @@ -896,6 +897,7 @@ textarea.chatMsg { display: flex; flex-direction: column; flex-shrink: 0; + position: relative; // anchors the hovered-participant .user-tooltip (top offset is measured against this) } .chat-hub-rightbar .rightbar-title { @@ -926,6 +928,7 @@ textarea.chatMsg { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + position: relative; } .chat-hub-rightbar .user:hover { @@ -1125,4 +1128,490 @@ textarea.chatMsg { } - + + + +// Chat-hub extras: user tooltip, right-bar context menu, attach-file modal, +// emoji picker, create-lobby button (chat.js). Recovered from compiled styles.css. + +.chat-create-lobby-btn { + position: absolute; + bottom: 0.5rem; + right: 1.25rem; + background-color: #0084ff; + color: #ffffff; + border: none; + border-radius: 0.375rem; + padding: 0.35rem 0.75rem; + font-size: 0.85rem; + font-weight: 600; + cursor: pointer; + box-shadow: 0 4px 6px -1px rgba(0, 132, 255, 0.2), 0 2px 4px -1px rgba(0, 132, 255, 0.1); + transition: background-color 0.2s, transform 0.2s; + display: flex; + align-items: center; + gap: 0.25rem; +} + +.chat-create-lobby-btn:hover { + background-color: #0073e6; + transform: translateY(-1px); +} + +.chat-create-lobby-btn:active { + transform: translateY(0); +} + +.chat-hub-rightbar .user .user-name { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + flex: 1; +} + +.user-tooltip { + position: absolute; + width: 260px; + background-color: #ffffe1; + border: 1px solid #7f7f7f; + box-shadow: 2px 2px 6px rgba(0, 0, 0, 0.25); + padding: 0.5rem; + border-radius: 0.25rem; + z-index: 10000; + white-space: normal; + display: flex; + gap: 0.5rem; + align-items: flex-start; +} + +.chat-hub-rightbar .user-tooltip { + left: -275px; + transform: translateY(-50%); + z-index: 1000; +} + +.user-tooltip .tooltip-avatar { + flex-shrink: 0; +} + +.user-tooltip .tooltip-details { + display: flex; + flex-direction: column; + gap: 0.25rem; + font-size: 0.8rem; + color: #000000; + text-align: left; +} + +.user-tooltip .tooltip-row { + line-height: 1.2; +} + +.user-tooltip .tooltip-label { + font-weight: bold; +} + +.user-tooltip .tooltip-value { + font-weight: normal; + word-break: break-all; +} + +.user-tooltip .tooltip-value.tooltip-id { + font-family: monospace; +} + +.chat-hub-rightbar .rightbar-context-menu { + position: absolute; + right: 1rem; + width: 210px; + background-color: #ffffff; + border: 1px solid #e2e8f0; + box-shadow: 0 4px 10px rgba(0, 0, 0, 0.15); + border-radius: 0.375rem; + z-index: 1010; + padding: 0.25rem 0; + display: flex; + flex-direction: column; +} + +.chat-hub-rightbar .rightbar-context-menu .menu-item { + padding: 0.5rem 1rem; + font-size: 0.85rem; + color: #334155; + cursor: pointer; + display: flex; + align-items: center; + transition: background-color 0.2s; +} + +.chat-hub-rightbar .rightbar-context-menu .menu-item:hover { + background-color: #f1f5f9; + color: #0f172a; +} + +.chat-emoji { + font-size: 1.45em; + line-height: 1; + vertical-align: -0.15em; + display: inline-block; +} + +.chat-hub-attach-btn, +.chat-hub-action-btn { + background-color: transparent !important; + border: none !important; + font-size: 1.15rem !important; + color: #64748b !important; + cursor: pointer !important; + padding: 0.4rem 0.5rem !important; + border-radius: 0.375rem !important; + flex-shrink: 0 !important; + display: inline-flex !important; + align-items: center !important; + justify-content: center !important; + transition: all 0.2s !important; + box-shadow: none !important; + margin: 0 !important; + line-height: 1 !important; + height: 36px !important; + width: 36px !important; +} + +.chat-hub-attach-btn:hover, +.chat-hub-action-btn:hover { + background-color: #f1f5f9 !important; + color: #3b82f6 !important; + transform: none !important; +} + +.attach-modal-overlay { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background-color: rgba(15, 23, 42, 0.4); + backdrop-filter: blur(4px); + display: flex; + align-items: center; + justify-content: center; + z-index: 2000; +} + +.attach-modal { + background-color: #ffffff; + border-radius: 0.5rem; + width: 450px; + max-width: 90%; + padding: 1.5rem; + box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1); + display: flex; + flex-direction: column; + gap: 1rem; +} + +.attach-modal .attach-modal-header { + display: flex; + align-items: center; + gap: 0.6rem; + margin-bottom: 0.25rem; +} + +.attach-modal .attach-modal-icon { + font-size: 1.2rem; + color: #3b82f6; +} + +.attach-modal h4 { + margin: 0; + font-size: 1.2rem; + color: #0f172a; +} + +.attach-modal p { + margin: 0; + font-size: 0.9rem; + color: #475569; +} + +.attach-modal .attach-path-row { + display: flex; + gap: 0.5rem; + align-items: center; +} + +.attach-modal .attach-path-row input[type="text"] { + flex: 1; + padding: 0.75rem; + border: 1px solid #cbd5e1; + border-radius: 0.375rem; + font-size: 0.9rem; + outline: none; + transition: border-color 0.2s; + min-width: 0; +} + +.attach-modal .attach-path-row input[type="text"]:focus { + border-color: #3b82f6; + box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15); +} + +.attach-browse-btn { + flex-shrink: 0; + display: flex; + align-items: center; + gap: 0.35rem; + padding: 0.625rem 0.9rem; + font-size: 0.875rem; + background-color: #f1f5f9; + color: #334155; + border: 1px solid #cbd5e1; + border-radius: 0.375rem; + cursor: pointer; + box-shadow: none; + transition: background-color 0.2s, border-color 0.2s; + white-space: nowrap; +} + +.attach-browse-btn:hover { + background-color: #e2e8f0; + border-color: #94a3b8; +} + +.attach-path-hint { + display: flex; + align-items: flex-start; + gap: 0.5rem; + padding: 0.6rem 0.75rem; + background-color: #fffbeb; + border: 1px solid #fcd34d; + border-left: 3px solid #f59e0b; + border-radius: 0.375rem; + font-size: 0.825rem; + color: #92400e; + line-height: 1.45; +} + +.attach-path-hint i { + color: #f59e0b; + margin-top: 0.1rem; + flex-shrink: 0; +} + +.attach-path-hint code { + font-family: monospace; + background-color: rgba(245, 158, 11, 0.15); + padding: 0.05rem 0.25rem; + border-radius: 0.2rem; +} + +.attach-modal .hashing-spinner { + display: flex; + align-items: center; + gap: 0.5rem; + font-size: 0.9rem; + color: #3b82f6; +} + +.attach-modal .error-text { + color: #ef4444; + font-size: 0.85rem; + margin: 0; +} + +.attach-modal .modal-buttons { + display: flex; + justify-content: flex-end; + gap: 0.75rem; + margin-top: 0.5rem; +} + +.attach-modal .modal-buttons button { + padding: 0.5rem 1rem; + font-size: 0.9rem; + border-radius: 0.25rem; + border: none; + cursor: pointer; + transition: opacity 0.2s; +} + +.attach-modal .modal-buttons button:hover { + opacity: 0.9; +} + +.chat-hub-emoji-btn { + background-color: transparent; + border: none; + font-size: 1.3rem; + cursor: pointer; + padding: 0.35rem 0.4rem; + margin-right: 0.25rem; + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: center; + border-radius: 0.375rem; + line-height: 1; + transition: background-color 0.15s, transform 0.15s; + box-shadow: none; +} + +.chat-hub-emoji-btn:hover { + background-color: #f1f5f9; + transform: scale(1.1); +} + +.emoji-picker-wrapper { + position: relative; + flex-shrink: 0; + display: flex; + align-items: center; +} + +.emoji-picker { + position: absolute; + bottom: calc(100% + 0.5rem); + left: 0; + width: 320px; + background-color: #ffffff; + border: 1px solid #e2e8f0; + border-radius: 0.625rem; + box-shadow: 0 8px 30px -4px rgba(0, 0, 0, 0.18), 0 4px 12px -2px rgba(0, 0, 0, 0.1); + z-index: 3000; + display: flex; + flex-direction: column; + overflow: hidden; + animation: emoji-pop 0.15s ease-out; +} + +.emoji-search-row { + display: flex; + align-items: center; + gap: 0.4rem; + padding: 0.6rem 0.75rem 0.4rem; + border-bottom: 1px solid #f1f5f9; +} + +.emoji-search-icon { + color: #94a3b8; + font-size: 0.8rem; + flex-shrink: 0; +} + +.emoji-search-input { + flex: 1; + border: 1px solid #e2e8f0; + border-radius: 0.375rem; + padding: 0.3rem 0.5rem; + font-size: 0.85rem; + outline: none; + background-color: #f8fafc; + transition: border-color 0.15s; +} + +.emoji-search-input:focus { + border-color: #3ba4d7; + background-color: #fff; +} + +.emoji-search-clear { + background: none; + border: none; + cursor: pointer; + color: #94a3b8; + padding: 0.2rem; + font-size: 0.8rem; + box-shadow: none; + display: flex; + align-items: center; +} + +.emoji-search-clear:hover { + color: #475569; +} + +.emoji-categories { + display: flex; + gap: 0.1rem; + padding: 0.35rem 0.5rem; + border-bottom: 1px solid #f1f5f9; + overflow-x: auto; + scrollbar-width: none; +} + +.emoji-categories::-webkit-scrollbar { + display: none; +} + +.emoji-cat-btn { + background: none; + border: none; + cursor: pointer; + font-size: 1.2rem; + padding: 0.3rem 0.35rem; + border-radius: 0.375rem; + line-height: 1; + box-shadow: none; + transition: background-color 0.1s; + flex-shrink: 0; +} + +.emoji-cat-btn:hover { + background-color: #f1f5f9; +} + +.emoji-cat-btn.active { + background-color: #e0f2fe; + box-shadow: inset 0 -2px 0 #3ba4d7; +} + +.emoji-grid { + display: grid; + grid-template-columns: repeat(7, 1fr); + gap: 0; + padding: 0.4rem 0.35rem; + max-height: 220px; + overflow-y: auto; + scrollbar-width: thin; + scrollbar-color: #cbd5e1 transparent; +} + +.emoji-grid::-webkit-scrollbar { + width: 4px; +} + +.emoji-grid::-webkit-scrollbar-track { + background: transparent; +} + +.emoji-grid::-webkit-scrollbar-thumb { + background-color: #cbd5e1; + border-radius: 4px; +} + +.emoji-btn { + background: none; + border: none; + cursor: pointer; + font-size: 1.7rem; + padding: 0.25rem; + border-radius: 0.3rem; + line-height: 1; + box-shadow: none; + text-align: center; + transition: background-color 0.1s, transform 0.1s; + display: flex; + align-items: center; + justify-content: center; + aspect-ratio: 1; +} + +.emoji-btn:hover { + background-color: #f1f5f9; + transform: scale(1.2); +} + +@keyframes emoji-pop { + from { opacity: 0; transform: scale(0.92) translateY(6px); } + to { opacity: 1; transform: scale(1) translateY(0); } +} diff --git a/webui-src/app/scss/pages/_config.scss b/webui-src/app/scss/pages/_config.scss index 659cc95..19d1256 100644 --- a/webui-src/app/scss/pages/_config.scss +++ b/webui-src/app/scss/pages/_config.scss @@ -1,3 +1,4 @@ +@use 'sass:color'; @use '../abstracts' as *; .mail { @@ -7,13 +8,13 @@ } &-tags { padding: 0.5rem; - border: 1px solid transparentize($dark-color, 0.8); + border: 1px solid color.adjust($dark-color, $alpha: -0.8); border-radius: 6px; &__container { @include flex(column); & .tag-item { @include flex($align: center, $gap: 4px); - border-bottom: 1px solid transparentize($dark-color, 0.9); + border-bottom: 1px solid color.adjust($dark-color, $alpha: -0.9); padding: 2px 0; &:last-child { diff --git a/webui-src/app/scss/pages/_files.scss b/webui-src/app/scss/pages/_files.scss index 2bc1f5c..c440100 100644 --- a/webui-src/app/scss/pages/_files.scss +++ b/webui-src/app/scss/pages/_files.scss @@ -1,3 +1,4 @@ +@use 'sass:color'; @use '../abstracts/' as *; .file-view { @@ -71,7 +72,7 @@ table.friendsfiles td:nth-child(2) { margin-top: 1rem; padding: 8px; @include flex($gap: 8px); - border: 1px solid transparentize($dark-color, 0.8); + border: 1px solid color.adjust($dark-color, $alpha: -0.8); border-radius: 6px; height: 100%; overflow: auto; @@ -79,11 +80,11 @@ table.friendsfiles td:nth-child(2) { &__keywords { flex-basis: 15%; padding-right: 0.25rem; - border-right: 1px solid transparentize($dark-color, 0.9); + border-right: 1px solid color.adjust($dark-color, $alpha: -0.9); & .keywords-container { @include flex(column); - border-top: 2.5px solid transparentize($dark-color, 0.92); + border-top: 2.5px solid color.adjust($dark-color, $alpha: -0.92); margin-top: 0.125rem; padding-top: 0.25rem; diff --git a/webui-src/app/scss/pages/_mail.scss b/webui-src/app/scss/pages/_mail.scss index 6149e30..f6bd375 100644 --- a/webui-src/app/scss/pages/_mail.scss +++ b/webui-src/app/scss/pages/_mail.scss @@ -1,3 +1,4 @@ +@use 'sass:color'; @use '../abstracts/' as *; .side-bar { @@ -182,7 +183,7 @@ table.mails { /* attachments */ &:nth-child(2) { width: 5%; - color: darken($light-color, 50%); + color: color.adjust($light-color, $lightness: -50%); } /* subject */ &:nth-child(3) { @@ -239,7 +240,7 @@ table.mails { &:hover { background-color: $light-color; - color: darken($light-color, 80%); + color: color.adjust($light-color, $lightness: -80%); } } } diff --git a/webui-src/app/scss/pages/_network.scss b/webui-src/app/scss/pages/_network.scss index 2ff74cc..ff1e9ce 100644 --- a/webui-src/app/scss/pages/_network.scss +++ b/webui-src/app/scss/pages/_network.scss @@ -104,6 +104,7 @@ display: flex; flex-direction: column; overflow: hidden; + position: relative; // anchors the absolutely-positioned .people-context-menu .searchbar-container { padding: 0.75rem 1rem; diff --git a/webui-src/app/scss/pages/_people.scss b/webui-src/app/scss/pages/_people.scss index 36f0810..d835421 100644 --- a/webui-src/app/scss/pages/_people.scss +++ b/webui-src/app/scss/pages/_people.scss @@ -195,3 +195,134 @@ img.avatar { } } + + +// Recent-chats split-pane + friends context menu (people_sidebar.js). +// Recovered from compiled styles.css; hard-coded colors kept as-was. + +.friends-list-container .people-context-menu { + position: absolute; + left: 2rem; + width: 210px; + background-color: #ffffff; + border: 1px solid #e2e8f0; + box-shadow: 0 4px 10px rgba(0, 0, 0, 0.15); + border-radius: 0.375rem; + z-index: 1010; + padding: 0.25rem 0; + display: flex; + flex-direction: column; +} + +.friends-list-container .people-context-menu .menu-item { + padding: 0.5rem 1rem; + font-size: 0.85rem; + color: #334155; + cursor: pointer; + display: flex; + align-items: center; + transition: background-color 0.2s; +} + +.friends-list-container .people-context-menu .menu-item:hover { + background-color: #f1f5f9; + color: #0f172a; +} + +.people-container { + display: flex; + height: calc(100vh - 55px); + width: 100%; + overflow: hidden; +} + +.people-left-pane { + width: 320px; + border-right: 1px solid #cbd5e1; + display: flex; + flex-direction: column; + background-color: #ffffff; + overflow: hidden; +} + +.people-right-pane { + flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; + background-color: #f8fafc; +} + +.people-left-pane .chat-item { + display: flex; + align-items: center; + padding: 0.75rem 1rem; + gap: 0.75rem; + border-bottom: 1px solid #f1f5f9; + cursor: pointer; + transition: background-color 0.15s ease; + position: relative; +} + +.people-left-pane .chat-item:hover { + background-color: #f8fafc; +} + +.people-left-pane .chat-item.selected { + background-color: #eff6ff; + border-left: 3px solid #3b82f6; +} + +.people-left-pane .chat-item .chat-avatar-wrapper { + position: relative; + flex-shrink: 0; +} + +.people-left-pane .chat-item .chat-avatar-wrapper .status-dot { + position: absolute; + bottom: 0; + right: 0; + width: 10px; + height: 10px; + border-radius: 50%; + border: 2px solid #ffffff; +} + +.people-left-pane .chat-item .chat-info { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; + gap: 0.15rem; +} + +.people-left-pane .chat-item .chat-info .chat-name { + font-size: 0.9rem; + font-weight: 700; + color: #1e293b; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.people-left-pane .chat-item .chat-info .chat-last-msg { + font-size: 0.8rem; + color: #64748b; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.people-left-pane .chat-item .chat-meta { + display: flex; + flex-direction: column; + align-items: flex-end; + gap: 0.25rem; + flex-shrink: 0; +} + +.people-left-pane .chat-item .chat-meta .chat-time { + font-size: 0.75rem; + color: #94a3b8; + white-space: nowrap; +} diff --git a/webui-src/styles.css b/webui-src/styles.css index 369212b..a9de9cb 100644 --- a/webui-src/styles.css +++ b/webui-src/styles.css @@ -4,4 +4,4 @@ */.fa,.fas,.far,.fal,.fab{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;display:inline-block;font-style:normal;font-variant:normal;text-rendering:auto;line-height:1}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-0.0667em}.fa-xs{font-size:.75em}.fa-sm{font-size:.875em}.fa-1x{font-size:1em}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-6x{font-size:6em}.fa-7x{font-size:7em}.fa-8x{font-size:8em}.fa-9x{font-size:9em}.fa-10x{font-size:10em}.fa-fw{text-align:center;width:1.25em}.fa-ul{list-style-type:none;margin-left:2.5em;padding-left:0}.fa-ul>li{position:relative}.fa-li{left:-2em;position:absolute;text-align:center;width:2em;line-height:inherit}.fa-border{border:solid .08em #eee;border-radius:.1em;padding:.2em .25em .15em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left,.fas.fa-pull-left,.far.fa-pull-left,.fal.fa-pull-left,.fab.fa-pull-left{margin-right:.3em}.fa.fa-pull-right,.fas.fa-pull-right,.far.fa-pull-right,.fal.fa-pull-right,.fab.fa-pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scale(1, -1);transform:scale(1, -1)}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scale(-1, -1);transform:scale(-1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-flip-both{-webkit-filter:none;filter:none}.fa-stack{display:inline-block;height:2em;line-height:2em;position:relative;vertical-align:middle;width:2.5em}.fa-stack-1x,.fa-stack-2x{left:0;position:absolute;text-align:center;width:100%}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-500px:before{content:""}.fa-accessible-icon:before{content:""}.fa-accusoft:before{content:""}.fa-acquisitions-incorporated:before{content:""}.fa-ad:before{content:""}.fa-address-book:before{content:""}.fa-address-card:before{content:""}.fa-adjust:before{content:""}.fa-adn:before{content:""}.fa-adobe:before{content:""}.fa-adversal:before{content:""}.fa-affiliatetheme:before{content:""}.fa-air-freshener:before{content:""}.fa-airbnb:before{content:""}.fa-algolia:before{content:""}.fa-align-center:before{content:""}.fa-align-justify:before{content:""}.fa-align-left:before{content:""}.fa-align-right:before{content:""}.fa-alipay:before{content:""}.fa-allergies:before{content:""}.fa-amazon:before{content:""}.fa-amazon-pay:before{content:""}.fa-ambulance:before{content:""}.fa-american-sign-language-interpreting:before{content:""}.fa-amilia:before{content:""}.fa-anchor:before{content:""}.fa-android:before{content:""}.fa-angellist:before{content:""}.fa-angle-double-down:before{content:""}.fa-angle-double-left:before{content:""}.fa-angle-double-right:before{content:""}.fa-angle-double-up:before{content:""}.fa-angle-down:before{content:""}.fa-angle-left:before{content:""}.fa-angle-right:before{content:""}.fa-angle-up:before{content:""}.fa-angry:before{content:""}.fa-angrycreative:before{content:""}.fa-angular:before{content:""}.fa-ankh:before{content:""}.fa-app-store:before{content:""}.fa-app-store-ios:before{content:""}.fa-apper:before{content:""}.fa-apple:before{content:""}.fa-apple-alt:before{content:""}.fa-apple-pay:before{content:""}.fa-archive:before{content:""}.fa-archway:before{content:""}.fa-arrow-alt-circle-down:before{content:""}.fa-arrow-alt-circle-left:before{content:""}.fa-arrow-alt-circle-right:before{content:""}.fa-arrow-alt-circle-up:before{content:""}.fa-arrow-circle-down:before{content:""}.fa-arrow-circle-left:before{content:""}.fa-arrow-circle-right:before{content:""}.fa-arrow-circle-up:before{content:""}.fa-arrow-down:before{content:""}.fa-arrow-left:before{content:""}.fa-arrow-right:before{content:""}.fa-arrow-up:before{content:""}.fa-arrows-alt:before{content:""}.fa-arrows-alt-h:before{content:""}.fa-arrows-alt-v:before{content:""}.fa-artstation:before{content:""}.fa-assistive-listening-systems:before{content:""}.fa-asterisk:before{content:""}.fa-asymmetrik:before{content:""}.fa-at:before{content:""}.fa-atlas:before{content:""}.fa-atlassian:before{content:""}.fa-atom:before{content:""}.fa-audible:before{content:""}.fa-audio-description:before{content:""}.fa-autoprefixer:before{content:""}.fa-avianex:before{content:""}.fa-aviato:before{content:""}.fa-award:before{content:""}.fa-aws:before{content:""}.fa-baby:before{content:""}.fa-baby-carriage:before{content:""}.fa-backspace:before{content:""}.fa-backward:before{content:""}.fa-bacon:before{content:""}.fa-balance-scale:before{content:""}.fa-balance-scale-left:before{content:""}.fa-balance-scale-right:before{content:""}.fa-ban:before{content:""}.fa-band-aid:before{content:""}.fa-bandcamp:before{content:""}.fa-barcode:before{content:""}.fa-bars:before{content:""}.fa-baseball-ball:before{content:""}.fa-basketball-ball:before{content:""}.fa-bath:before{content:""}.fa-battery-empty:before{content:""}.fa-battery-full:before{content:""}.fa-battery-half:before{content:""}.fa-battery-quarter:before{content:""}.fa-battery-three-quarters:before{content:""}.fa-battle-net:before{content:""}.fa-bed:before{content:""}.fa-beer:before{content:""}.fa-behance:before{content:""}.fa-behance-square:before{content:""}.fa-bell:before{content:""}.fa-bell-slash:before{content:""}.fa-bezier-curve:before{content:""}.fa-bible:before{content:""}.fa-bicycle:before{content:""}.fa-biking:before{content:""}.fa-bimobject:before{content:""}.fa-binoculars:before{content:""}.fa-biohazard:before{content:""}.fa-birthday-cake:before{content:""}.fa-bitbucket:before{content:""}.fa-bitcoin:before{content:""}.fa-bity:before{content:""}.fa-black-tie:before{content:""}.fa-blackberry:before{content:""}.fa-blender:before{content:""}.fa-blender-phone:before{content:""}.fa-blind:before{content:""}.fa-blog:before{content:""}.fa-blogger:before{content:""}.fa-blogger-b:before{content:""}.fa-bluetooth:before{content:""}.fa-bluetooth-b:before{content:""}.fa-bold:before{content:""}.fa-bolt:before{content:""}.fa-bomb:before{content:""}.fa-bone:before{content:""}.fa-bong:before{content:""}.fa-book:before{content:""}.fa-book-dead:before{content:""}.fa-book-medical:before{content:""}.fa-book-open:before{content:""}.fa-book-reader:before{content:""}.fa-bookmark:before{content:""}.fa-bootstrap:before{content:""}.fa-border-all:before{content:""}.fa-border-none:before{content:""}.fa-border-style:before{content:""}.fa-bowling-ball:before{content:""}.fa-box:before{content:""}.fa-box-open:before{content:""}.fa-boxes:before{content:""}.fa-braille:before{content:""}.fa-brain:before{content:""}.fa-bread-slice:before{content:""}.fa-briefcase:before{content:""}.fa-briefcase-medical:before{content:""}.fa-broadcast-tower:before{content:""}.fa-broom:before{content:""}.fa-brush:before{content:""}.fa-btc:before{content:""}.fa-buffer:before{content:""}.fa-bug:before{content:""}.fa-building:before{content:""}.fa-bullhorn:before{content:""}.fa-bullseye:before{content:""}.fa-burn:before{content:""}.fa-buromobelexperte:before{content:""}.fa-bus:before{content:""}.fa-bus-alt:before{content:""}.fa-business-time:before{content:""}.fa-buysellads:before{content:""}.fa-calculator:before{content:""}.fa-calendar:before{content:""}.fa-calendar-alt:before{content:""}.fa-calendar-check:before{content:""}.fa-calendar-day:before{content:""}.fa-calendar-minus:before{content:""}.fa-calendar-plus:before{content:""}.fa-calendar-times:before{content:""}.fa-calendar-week:before{content:""}.fa-camera:before{content:""}.fa-camera-retro:before{content:""}.fa-campground:before{content:""}.fa-canadian-maple-leaf:before{content:""}.fa-candy-cane:before{content:""}.fa-cannabis:before{content:""}.fa-capsules:before{content:""}.fa-car:before{content:""}.fa-car-alt:before{content:""}.fa-car-battery:before{content:""}.fa-car-crash:before{content:""}.fa-car-side:before{content:""}.fa-caret-down:before{content:""}.fa-caret-left:before{content:""}.fa-caret-right:before{content:""}.fa-caret-square-down:before{content:""}.fa-caret-square-left:before{content:""}.fa-caret-square-right:before{content:""}.fa-caret-square-up:before{content:""}.fa-caret-up:before{content:""}.fa-carrot:before{content:""}.fa-cart-arrow-down:before{content:""}.fa-cart-plus:before{content:""}.fa-cash-register:before{content:""}.fa-cat:before{content:""}.fa-cc-amazon-pay:before{content:""}.fa-cc-amex:before{content:""}.fa-cc-apple-pay:before{content:""}.fa-cc-diners-club:before{content:""}.fa-cc-discover:before{content:""}.fa-cc-jcb:before{content:""}.fa-cc-mastercard:before{content:""}.fa-cc-paypal:before{content:""}.fa-cc-stripe:before{content:""}.fa-cc-visa:before{content:""}.fa-centercode:before{content:""}.fa-centos:before{content:""}.fa-certificate:before{content:""}.fa-chair:before{content:""}.fa-chalkboard:before{content:""}.fa-chalkboard-teacher:before{content:""}.fa-charging-station:before{content:""}.fa-chart-area:before{content:""}.fa-chart-bar:before{content:""}.fa-chart-line:before{content:""}.fa-chart-pie:before{content:""}.fa-check:before{content:""}.fa-check-circle:before{content:""}.fa-check-double:before{content:""}.fa-check-square:before{content:""}.fa-cheese:before{content:""}.fa-chess:before{content:""}.fa-chess-bishop:before{content:""}.fa-chess-board:before{content:""}.fa-chess-king:before{content:""}.fa-chess-knight:before{content:""}.fa-chess-pawn:before{content:""}.fa-chess-queen:before{content:""}.fa-chess-rook:before{content:""}.fa-chevron-circle-down:before{content:""}.fa-chevron-circle-left:before{content:""}.fa-chevron-circle-right:before{content:""}.fa-chevron-circle-up:before{content:""}.fa-chevron-down:before{content:""}.fa-chevron-left:before{content:""}.fa-chevron-right:before{content:""}.fa-chevron-up:before{content:""}.fa-child:before{content:""}.fa-chrome:before{content:""}.fa-chromecast:before{content:""}.fa-church:before{content:""}.fa-circle:before{content:""}.fa-circle-notch:before{content:""}.fa-city:before{content:""}.fa-clinic-medical:before{content:""}.fa-clipboard:before{content:""}.fa-clipboard-check:before{content:""}.fa-clipboard-list:before{content:""}.fa-clock:before{content:""}.fa-clone:before{content:""}.fa-closed-captioning:before{content:""}.fa-cloud:before{content:""}.fa-cloud-download-alt:before{content:""}.fa-cloud-meatball:before{content:""}.fa-cloud-moon:before{content:""}.fa-cloud-moon-rain:before{content:""}.fa-cloud-rain:before{content:""}.fa-cloud-showers-heavy:before{content:""}.fa-cloud-sun:before{content:""}.fa-cloud-sun-rain:before{content:""}.fa-cloud-upload-alt:before{content:""}.fa-cloudscale:before{content:""}.fa-cloudsmith:before{content:""}.fa-cloudversify:before{content:""}.fa-cocktail:before{content:""}.fa-code:before{content:""}.fa-code-branch:before{content:""}.fa-codepen:before{content:""}.fa-codiepie:before{content:""}.fa-coffee:before{content:""}.fa-cog:before{content:""}.fa-cogs:before{content:""}.fa-coins:before{content:""}.fa-columns:before{content:""}.fa-comment:before{content:""}.fa-comment-alt:before{content:""}.fa-comment-dollar:before{content:""}.fa-comment-dots:before{content:""}.fa-comment-medical:before{content:""}.fa-comment-slash:before{content:""}.fa-comments:before{content:""}.fa-comments-dollar:before{content:""}.fa-compact-disc:before{content:""}.fa-compass:before{content:""}.fa-compress:before{content:""}.fa-compress-arrows-alt:before{content:""}.fa-concierge-bell:before{content:""}.fa-confluence:before{content:""}.fa-connectdevelop:before{content:""}.fa-contao:before{content:""}.fa-cookie:before{content:""}.fa-cookie-bite:before{content:""}.fa-copy:before{content:""}.fa-copyright:before{content:""}.fa-couch:before{content:""}.fa-cpanel:before{content:""}.fa-creative-commons:before{content:""}.fa-creative-commons-by:before{content:""}.fa-creative-commons-nc:before{content:""}.fa-creative-commons-nc-eu:before{content:""}.fa-creative-commons-nc-jp:before{content:""}.fa-creative-commons-nd:before{content:""}.fa-creative-commons-pd:before{content:""}.fa-creative-commons-pd-alt:before{content:""}.fa-creative-commons-remix:before{content:""}.fa-creative-commons-sa:before{content:""}.fa-creative-commons-sampling:before{content:""}.fa-creative-commons-sampling-plus:before{content:""}.fa-creative-commons-share:before{content:""}.fa-creative-commons-zero:before{content:""}.fa-credit-card:before{content:""}.fa-critical-role:before{content:""}.fa-crop:before{content:""}.fa-crop-alt:before{content:""}.fa-cross:before{content:""}.fa-crosshairs:before{content:""}.fa-crow:before{content:""}.fa-crown:before{content:""}.fa-crutch:before{content:""}.fa-css3:before{content:""}.fa-css3-alt:before{content:""}.fa-cube:before{content:""}.fa-cubes:before{content:""}.fa-cut:before{content:""}.fa-cuttlefish:before{content:""}.fa-d-and-d:before{content:""}.fa-d-and-d-beyond:before{content:""}.fa-dashcube:before{content:""}.fa-database:before{content:""}.fa-deaf:before{content:""}.fa-delicious:before{content:""}.fa-democrat:before{content:""}.fa-deploydog:before{content:""}.fa-deskpro:before{content:""}.fa-desktop:before{content:""}.fa-dev:before{content:""}.fa-deviantart:before{content:""}.fa-dharmachakra:before{content:""}.fa-dhl:before{content:""}.fa-diagnoses:before{content:""}.fa-diaspora:before{content:""}.fa-dice:before{content:""}.fa-dice-d20:before{content:""}.fa-dice-d6:before{content:""}.fa-dice-five:before{content:""}.fa-dice-four:before{content:""}.fa-dice-one:before{content:""}.fa-dice-six:before{content:""}.fa-dice-three:before{content:""}.fa-dice-two:before{content:""}.fa-digg:before{content:""}.fa-digital-ocean:before{content:""}.fa-digital-tachograph:before{content:""}.fa-directions:before{content:""}.fa-discord:before{content:""}.fa-discourse:before{content:""}.fa-divide:before{content:""}.fa-dizzy:before{content:""}.fa-dna:before{content:""}.fa-dochub:before{content:""}.fa-docker:before{content:""}.fa-dog:before{content:""}.fa-dollar-sign:before{content:""}.fa-dolly:before{content:""}.fa-dolly-flatbed:before{content:""}.fa-donate:before{content:""}.fa-door-closed:before{content:""}.fa-door-open:before{content:""}.fa-dot-circle:before{content:""}.fa-dove:before{content:""}.fa-download:before{content:""}.fa-draft2digital:before{content:""}.fa-drafting-compass:before{content:""}.fa-dragon:before{content:""}.fa-draw-polygon:before{content:""}.fa-dribbble:before{content:""}.fa-dribbble-square:before{content:""}.fa-dropbox:before{content:""}.fa-drum:before{content:""}.fa-drum-steelpan:before{content:""}.fa-drumstick-bite:before{content:""}.fa-drupal:before{content:""}.fa-dumbbell:before{content:""}.fa-dumpster:before{content:""}.fa-dumpster-fire:before{content:""}.fa-dungeon:before{content:""}.fa-dyalog:before{content:""}.fa-earlybirds:before{content:""}.fa-ebay:before{content:""}.fa-edge:before{content:""}.fa-edit:before{content:""}.fa-egg:before{content:""}.fa-eject:before{content:""}.fa-elementor:before{content:""}.fa-ellipsis-h:before{content:""}.fa-ellipsis-v:before{content:""}.fa-ello:before{content:""}.fa-ember:before{content:""}.fa-empire:before{content:""}.fa-envelope:before{content:""}.fa-envelope-open:before{content:""}.fa-envelope-open-text:before{content:""}.fa-envelope-square:before{content:""}.fa-envira:before{content:""}.fa-equals:before{content:""}.fa-eraser:before{content:""}.fa-erlang:before{content:""}.fa-ethereum:before{content:""}.fa-ethernet:before{content:""}.fa-etsy:before{content:""}.fa-euro-sign:before{content:""}.fa-evernote:before{content:""}.fa-exchange-alt:before{content:""}.fa-exclamation:before{content:""}.fa-exclamation-circle:before{content:""}.fa-exclamation-triangle:before{content:""}.fa-expand:before{content:""}.fa-expand-arrows-alt:before{content:""}.fa-expeditedssl:before{content:""}.fa-external-link-alt:before{content:""}.fa-external-link-square-alt:before{content:""}.fa-eye:before{content:""}.fa-eye-dropper:before{content:""}.fa-eye-slash:before{content:""}.fa-facebook:before{content:""}.fa-facebook-f:before{content:""}.fa-facebook-messenger:before{content:""}.fa-facebook-square:before{content:""}.fa-fan:before{content:""}.fa-fantasy-flight-games:before{content:""}.fa-fast-backward:before{content:""}.fa-fast-forward:before{content:""}.fa-fax:before{content:""}.fa-feather:before{content:""}.fa-feather-alt:before{content:""}.fa-fedex:before{content:""}.fa-fedora:before{content:""}.fa-female:before{content:""}.fa-fighter-jet:before{content:""}.fa-figma:before{content:""}.fa-file:before{content:""}.fa-file-alt:before{content:""}.fa-file-archive:before{content:""}.fa-file-audio:before{content:""}.fa-file-code:before{content:""}.fa-file-contract:before{content:""}.fa-file-csv:before{content:""}.fa-file-download:before{content:""}.fa-file-excel:before{content:""}.fa-file-export:before{content:""}.fa-file-image:before{content:""}.fa-file-import:before{content:""}.fa-file-invoice:before{content:""}.fa-file-invoice-dollar:before{content:""}.fa-file-medical:before{content:""}.fa-file-medical-alt:before{content:""}.fa-file-pdf:before{content:""}.fa-file-powerpoint:before{content:""}.fa-file-prescription:before{content:""}.fa-file-signature:before{content:""}.fa-file-upload:before{content:""}.fa-file-video:before{content:""}.fa-file-word:before{content:""}.fa-fill:before{content:""}.fa-fill-drip:before{content:""}.fa-film:before{content:""}.fa-filter:before{content:""}.fa-fingerprint:before{content:""}.fa-fire:before{content:""}.fa-fire-alt:before{content:""}.fa-fire-extinguisher:before{content:""}.fa-firefox:before{content:""}.fa-first-aid:before{content:""}.fa-first-order:before{content:""}.fa-first-order-alt:before{content:""}.fa-firstdraft:before{content:""}.fa-fish:before{content:""}.fa-fist-raised:before{content:""}.fa-flag:before{content:""}.fa-flag-checkered:before{content:""}.fa-flag-usa:before{content:""}.fa-flask:before{content:""}.fa-flickr:before{content:""}.fa-flipboard:before{content:""}.fa-flushed:before{content:""}.fa-fly:before{content:""}.fa-folder:before{content:""}.fa-folder-minus:before{content:""}.fa-folder-open:before{content:""}.fa-folder-plus:before{content:""}.fa-font:before{content:""}.fa-font-awesome:before{content:""}.fa-font-awesome-alt:before{content:""}.fa-font-awesome-flag:before{content:""}.fa-font-awesome-logo-full:before{content:""}.fa-fonticons:before{content:""}.fa-fonticons-fi:before{content:""}.fa-football-ball:before{content:""}.fa-fort-awesome:before{content:""}.fa-fort-awesome-alt:before{content:""}.fa-forumbee:before{content:""}.fa-forward:before{content:""}.fa-foursquare:before{content:""}.fa-free-code-camp:before{content:""}.fa-freebsd:before{content:""}.fa-frog:before{content:""}.fa-frown:before{content:""}.fa-frown-open:before{content:""}.fa-fulcrum:before{content:""}.fa-funnel-dollar:before{content:""}.fa-futbol:before{content:""}.fa-galactic-republic:before{content:""}.fa-galactic-senate:before{content:""}.fa-gamepad:before{content:""}.fa-gas-pump:before{content:""}.fa-gavel:before{content:""}.fa-gem:before{content:""}.fa-genderless:before{content:""}.fa-get-pocket:before{content:""}.fa-gg:before{content:""}.fa-gg-circle:before{content:""}.fa-ghost:before{content:""}.fa-gift:before{content:""}.fa-gifts:before{content:""}.fa-git:before{content:""}.fa-git-alt:before{content:""}.fa-git-square:before{content:""}.fa-github:before{content:""}.fa-github-alt:before{content:""}.fa-github-square:before{content:""}.fa-gitkraken:before{content:""}.fa-gitlab:before{content:""}.fa-gitter:before{content:""}.fa-glass-cheers:before{content:""}.fa-glass-martini:before{content:""}.fa-glass-martini-alt:before{content:""}.fa-glass-whiskey:before{content:""}.fa-glasses:before{content:""}.fa-glide:before{content:""}.fa-glide-g:before{content:""}.fa-globe:before{content:""}.fa-globe-africa:before{content:""}.fa-globe-americas:before{content:""}.fa-globe-asia:before{content:""}.fa-globe-europe:before{content:""}.fa-gofore:before{content:""}.fa-golf-ball:before{content:""}.fa-goodreads:before{content:""}.fa-goodreads-g:before{content:""}.fa-google:before{content:""}.fa-google-drive:before{content:""}.fa-google-play:before{content:""}.fa-google-plus:before{content:""}.fa-google-plus-g:before{content:""}.fa-google-plus-square:before{content:""}.fa-google-wallet:before{content:""}.fa-gopuram:before{content:""}.fa-graduation-cap:before{content:""}.fa-gratipay:before{content:""}.fa-grav:before{content:""}.fa-greater-than:before{content:""}.fa-greater-than-equal:before{content:""}.fa-grimace:before{content:""}.fa-grin:before{content:""}.fa-grin-alt:before{content:""}.fa-grin-beam:before{content:""}.fa-grin-beam-sweat:before{content:""}.fa-grin-hearts:before{content:""}.fa-grin-squint:before{content:""}.fa-grin-squint-tears:before{content:""}.fa-grin-stars:before{content:""}.fa-grin-tears:before{content:""}.fa-grin-tongue:before{content:""}.fa-grin-tongue-squint:before{content:""}.fa-grin-tongue-wink:before{content:""}.fa-grin-wink:before{content:""}.fa-grip-horizontal:before{content:""}.fa-grip-lines:before{content:""}.fa-grip-lines-vertical:before{content:""}.fa-grip-vertical:before{content:""}.fa-gripfire:before{content:""}.fa-grunt:before{content:""}.fa-guitar:before{content:""}.fa-gulp:before{content:""}.fa-h-square:before{content:""}.fa-hacker-news:before{content:""}.fa-hacker-news-square:before{content:""}.fa-hackerrank:before{content:""}.fa-hamburger:before{content:""}.fa-hammer:before{content:""}.fa-hamsa:before{content:""}.fa-hand-holding:before{content:""}.fa-hand-holding-heart:before{content:""}.fa-hand-holding-usd:before{content:""}.fa-hand-lizard:before{content:""}.fa-hand-middle-finger:before{content:""}.fa-hand-paper:before{content:""}.fa-hand-peace:before{content:""}.fa-hand-point-down:before{content:""}.fa-hand-point-left:before{content:""}.fa-hand-point-right:before{content:""}.fa-hand-point-up:before{content:""}.fa-hand-pointer:before{content:""}.fa-hand-rock:before{content:""}.fa-hand-scissors:before{content:""}.fa-hand-spock:before{content:""}.fa-hands:before{content:""}.fa-hands-helping:before{content:""}.fa-handshake:before{content:""}.fa-hanukiah:before{content:""}.fa-hard-hat:before{content:""}.fa-hashtag:before{content:""}.fa-hat-wizard:before{content:""}.fa-haykal:before{content:""}.fa-hdd:before{content:""}.fa-heading:before{content:""}.fa-headphones:before{content:""}.fa-headphones-alt:before{content:""}.fa-headset:before{content:""}.fa-heart:before{content:""}.fa-heart-broken:before{content:""}.fa-heartbeat:before{content:""}.fa-helicopter:before{content:""}.fa-highlighter:before{content:""}.fa-hiking:before{content:""}.fa-hippo:before{content:""}.fa-hips:before{content:""}.fa-hire-a-helper:before{content:""}.fa-history:before{content:""}.fa-hockey-puck:before{content:""}.fa-holly-berry:before{content:""}.fa-home:before{content:""}.fa-hooli:before{content:""}.fa-hornbill:before{content:""}.fa-horse:before{content:""}.fa-horse-head:before{content:""}.fa-hospital:before{content:""}.fa-hospital-alt:before{content:""}.fa-hospital-symbol:before{content:""}.fa-hot-tub:before{content:""}.fa-hotdog:before{content:""}.fa-hotel:before{content:""}.fa-hotjar:before{content:""}.fa-hourglass:before{content:""}.fa-hourglass-end:before{content:""}.fa-hourglass-half:before{content:""}.fa-hourglass-start:before{content:""}.fa-house-damage:before{content:""}.fa-houzz:before{content:""}.fa-hryvnia:before{content:""}.fa-html5:before{content:""}.fa-hubspot:before{content:""}.fa-i-cursor:before{content:""}.fa-ice-cream:before{content:""}.fa-icicles:before{content:""}.fa-icons:before{content:""}.fa-id-badge:before{content:""}.fa-id-card:before{content:""}.fa-id-card-alt:before{content:""}.fa-igloo:before{content:""}.fa-image:before{content:""}.fa-images:before{content:""}.fa-imdb:before{content:""}.fa-inbox:before{content:""}.fa-indent:before{content:""}.fa-industry:before{content:""}.fa-infinity:before{content:""}.fa-info:before{content:""}.fa-info-circle:before{content:""}.fa-instagram:before{content:""}.fa-intercom:before{content:""}.fa-internet-explorer:before{content:""}.fa-invision:before{content:""}.fa-ioxhost:before{content:""}.fa-italic:before{content:""}.fa-itch-io:before{content:""}.fa-itunes:before{content:""}.fa-itunes-note:before{content:""}.fa-java:before{content:""}.fa-jedi:before{content:""}.fa-jedi-order:before{content:""}.fa-jenkins:before{content:""}.fa-jira:before{content:""}.fa-joget:before{content:""}.fa-joint:before{content:""}.fa-joomla:before{content:""}.fa-journal-whills:before{content:""}.fa-js:before{content:""}.fa-js-square:before{content:""}.fa-jsfiddle:before{content:""}.fa-kaaba:before{content:""}.fa-kaggle:before{content:""}.fa-key:before{content:""}.fa-keybase:before{content:""}.fa-keyboard:before{content:""}.fa-keycdn:before{content:""}.fa-khanda:before{content:""}.fa-kickstarter:before{content:""}.fa-kickstarter-k:before{content:""}.fa-kiss:before{content:""}.fa-kiss-beam:before{content:""}.fa-kiss-wink-heart:before{content:""}.fa-kiwi-bird:before{content:""}.fa-korvue:before{content:""}.fa-landmark:before{content:""}.fa-language:before{content:""}.fa-laptop:before{content:""}.fa-laptop-code:before{content:""}.fa-laptop-medical:before{content:""}.fa-laravel:before{content:""}.fa-lastfm:before{content:""}.fa-lastfm-square:before{content:""}.fa-laugh:before{content:""}.fa-laugh-beam:before{content:""}.fa-laugh-squint:before{content:""}.fa-laugh-wink:before{content:""}.fa-layer-group:before{content:""}.fa-leaf:before{content:""}.fa-leanpub:before{content:""}.fa-lemon:before{content:""}.fa-less:before{content:""}.fa-less-than:before{content:""}.fa-less-than-equal:before{content:""}.fa-level-down-alt:before{content:""}.fa-level-up-alt:before{content:""}.fa-life-ring:before{content:""}.fa-lightbulb:before{content:""}.fa-line:before{content:""}.fa-link:before{content:""}.fa-linkedin:before{content:""}.fa-linkedin-in:before{content:""}.fa-linode:before{content:""}.fa-linux:before{content:""}.fa-lira-sign:before{content:""}.fa-list:before{content:""}.fa-list-alt:before{content:""}.fa-list-ol:before{content:""}.fa-list-ul:before{content:""}.fa-location-arrow:before{content:""}.fa-lock:before{content:""}.fa-lock-open:before{content:""}.fa-long-arrow-alt-down:before{content:""}.fa-long-arrow-alt-left:before{content:""}.fa-long-arrow-alt-right:before{content:""}.fa-long-arrow-alt-up:before{content:""}.fa-low-vision:before{content:""}.fa-luggage-cart:before{content:""}.fa-lyft:before{content:""}.fa-magento:before{content:""}.fa-magic:before{content:""}.fa-magnet:before{content:""}.fa-mail-bulk:before{content:""}.fa-mailchimp:before{content:""}.fa-male:before{content:""}.fa-mandalorian:before{content:""}.fa-map:before{content:""}.fa-map-marked:before{content:""}.fa-map-marked-alt:before{content:""}.fa-map-marker:before{content:""}.fa-map-marker-alt:before{content:""}.fa-map-pin:before{content:""}.fa-map-signs:before{content:""}.fa-markdown:before{content:""}.fa-marker:before{content:""}.fa-mars:before{content:""}.fa-mars-double:before{content:""}.fa-mars-stroke:before{content:""}.fa-mars-stroke-h:before{content:""}.fa-mars-stroke-v:before{content:""}.fa-mask:before{content:""}.fa-mastodon:before{content:""}.fa-maxcdn:before{content:""}.fa-medal:before{content:""}.fa-medapps:before{content:""}.fa-medium:before{content:""}.fa-medium-m:before{content:""}.fa-medkit:before{content:""}.fa-medrt:before{content:""}.fa-meetup:before{content:""}.fa-megaport:before{content:""}.fa-meh:before{content:""}.fa-meh-blank:before{content:""}.fa-meh-rolling-eyes:before{content:""}.fa-memory:before{content:""}.fa-mendeley:before{content:""}.fa-menorah:before{content:""}.fa-mercury:before{content:""}.fa-meteor:before{content:""}.fa-microchip:before{content:""}.fa-microphone:before{content:""}.fa-microphone-alt:before{content:""}.fa-microphone-alt-slash:before{content:""}.fa-microphone-slash:before{content:""}.fa-microscope:before{content:""}.fa-microsoft:before{content:""}.fa-minus:before{content:""}.fa-minus-circle:before{content:""}.fa-minus-square:before{content:""}.fa-mitten:before{content:""}.fa-mix:before{content:""}.fa-mixcloud:before{content:""}.fa-mizuni:before{content:""}.fa-mobile:before{content:""}.fa-mobile-alt:before{content:""}.fa-modx:before{content:""}.fa-monero:before{content:""}.fa-money-bill:before{content:""}.fa-money-bill-alt:before{content:""}.fa-money-bill-wave:before{content:""}.fa-money-bill-wave-alt:before{content:""}.fa-money-check:before{content:""}.fa-money-check-alt:before{content:""}.fa-monument:before{content:""}.fa-moon:before{content:""}.fa-mortar-pestle:before{content:""}.fa-mosque:before{content:""}.fa-motorcycle:before{content:""}.fa-mountain:before{content:""}.fa-mouse-pointer:before{content:""}.fa-mug-hot:before{content:""}.fa-music:before{content:""}.fa-napster:before{content:""}.fa-neos:before{content:""}.fa-network-wired:before{content:""}.fa-neuter:before{content:""}.fa-newspaper:before{content:""}.fa-nimblr:before{content:""}.fa-node:before{content:""}.fa-node-js:before{content:""}.fa-not-equal:before{content:""}.fa-notes-medical:before{content:""}.fa-npm:before{content:""}.fa-ns8:before{content:""}.fa-nutritionix:before{content:""}.fa-object-group:before{content:""}.fa-object-ungroup:before{content:""}.fa-odnoklassniki:before{content:""}.fa-odnoklassniki-square:before{content:""}.fa-oil-can:before{content:""}.fa-old-republic:before{content:""}.fa-om:before{content:""}.fa-opencart:before{content:""}.fa-openid:before{content:""}.fa-opera:before{content:""}.fa-optin-monster:before{content:""}.fa-osi:before{content:""}.fa-otter:before{content:""}.fa-outdent:before{content:""}.fa-page4:before{content:""}.fa-pagelines:before{content:""}.fa-pager:before{content:""}.fa-paint-brush:before{content:""}.fa-paint-roller:before{content:""}.fa-palette:before{content:""}.fa-palfed:before{content:""}.fa-pallet:before{content:""}.fa-paper-plane:before{content:""}.fa-paperclip:before{content:""}.fa-parachute-box:before{content:""}.fa-paragraph:before{content:""}.fa-parking:before{content:""}.fa-passport:before{content:""}.fa-pastafarianism:before{content:""}.fa-paste:before{content:""}.fa-patreon:before{content:""}.fa-pause:before{content:""}.fa-pause-circle:before{content:""}.fa-paw:before{content:""}.fa-paypal:before{content:""}.fa-peace:before{content:""}.fa-pen:before{content:""}.fa-pen-alt:before{content:""}.fa-pen-fancy:before{content:""}.fa-pen-nib:before{content:""}.fa-pen-square:before{content:""}.fa-pencil-alt:before{content:""}.fa-pencil-ruler:before{content:""}.fa-penny-arcade:before{content:""}.fa-people-carry:before{content:""}.fa-pepper-hot:before{content:""}.fa-percent:before{content:""}.fa-percentage:before{content:""}.fa-periscope:before{content:""}.fa-person-booth:before{content:""}.fa-phabricator:before{content:""}.fa-phoenix-framework:before{content:""}.fa-phoenix-squadron:before{content:""}.fa-phone:before{content:""}.fa-phone-alt:before{content:""}.fa-phone-slash:before{content:""}.fa-phone-square:before{content:""}.fa-phone-square-alt:before{content:""}.fa-phone-volume:before{content:""}.fa-photo-video:before{content:""}.fa-php:before{content:""}.fa-pied-piper:before{content:""}.fa-pied-piper-alt:before{content:""}.fa-pied-piper-hat:before{content:""}.fa-pied-piper-pp:before{content:""}.fa-piggy-bank:before{content:""}.fa-pills:before{content:""}.fa-pinterest:before{content:""}.fa-pinterest-p:before{content:""}.fa-pinterest-square:before{content:""}.fa-pizza-slice:before{content:""}.fa-place-of-worship:before{content:""}.fa-plane:before{content:""}.fa-plane-arrival:before{content:""}.fa-plane-departure:before{content:""}.fa-play:before{content:""}.fa-play-circle:before{content:""}.fa-playstation:before{content:""}.fa-plug:before{content:""}.fa-plus:before{content:""}.fa-plus-circle:before{content:""}.fa-plus-square:before{content:""}.fa-podcast:before{content:""}.fa-poll:before{content:""}.fa-poll-h:before{content:""}.fa-poo:before{content:""}.fa-poo-storm:before{content:""}.fa-poop:before{content:""}.fa-portrait:before{content:""}.fa-pound-sign:before{content:""}.fa-power-off:before{content:""}.fa-pray:before{content:""}.fa-praying-hands:before{content:""}.fa-prescription:before{content:""}.fa-prescription-bottle:before{content:""}.fa-prescription-bottle-alt:before{content:""}.fa-print:before{content:""}.fa-procedures:before{content:""}.fa-product-hunt:before{content:""}.fa-project-diagram:before{content:""}.fa-pushed:before{content:""}.fa-puzzle-piece:before{content:""}.fa-python:before{content:""}.fa-qq:before{content:""}.fa-qrcode:before{content:""}.fa-question:before{content:""}.fa-question-circle:before{content:""}.fa-quidditch:before{content:""}.fa-quinscape:before{content:""}.fa-quora:before{content:""}.fa-quote-left:before{content:""}.fa-quote-right:before{content:""}.fa-quran:before{content:""}.fa-r-project:before{content:""}.fa-radiation:before{content:""}.fa-radiation-alt:before{content:""}.fa-rainbow:before{content:""}.fa-random:before{content:""}.fa-raspberry-pi:before{content:""}.fa-ravelry:before{content:""}.fa-react:before{content:""}.fa-reacteurope:before{content:""}.fa-readme:before{content:""}.fa-rebel:before{content:""}.fa-receipt:before{content:""}.fa-recycle:before{content:""}.fa-red-river:before{content:""}.fa-reddit:before{content:""}.fa-reddit-alien:before{content:""}.fa-reddit-square:before{content:""}.fa-redhat:before{content:""}.fa-redo:before{content:""}.fa-redo-alt:before{content:""}.fa-registered:before{content:""}.fa-remove-format:before{content:""}.fa-renren:before{content:""}.fa-reply:before{content:""}.fa-reply-all:before{content:""}.fa-replyd:before{content:""}.fa-republican:before{content:""}.fa-researchgate:before{content:""}.fa-resolving:before{content:""}.fa-restroom:before{content:""}.fa-retweet:before{content:""}.fa-rev:before{content:""}.fa-ribbon:before{content:""}.fa-ring:before{content:""}.fa-road:before{content:""}.fa-robot:before{content:""}.fa-rocket:before{content:""}.fa-rocketchat:before{content:""}.fa-rockrms:before{content:""}.fa-route:before{content:""}.fa-rss:before{content:""}.fa-rss-square:before{content:""}.fa-ruble-sign:before{content:""}.fa-ruler:before{content:""}.fa-ruler-combined:before{content:""}.fa-ruler-horizontal:before{content:""}.fa-ruler-vertical:before{content:""}.fa-running:before{content:""}.fa-rupee-sign:before{content:""}.fa-sad-cry:before{content:""}.fa-sad-tear:before{content:""}.fa-safari:before{content:""}.fa-salesforce:before{content:""}.fa-sass:before{content:""}.fa-satellite:before{content:""}.fa-satellite-dish:before{content:""}.fa-save:before{content:""}.fa-schlix:before{content:""}.fa-school:before{content:""}.fa-screwdriver:before{content:""}.fa-scribd:before{content:""}.fa-scroll:before{content:""}.fa-sd-card:before{content:""}.fa-search:before{content:""}.fa-search-dollar:before{content:""}.fa-search-location:before{content:""}.fa-search-minus:before{content:""}.fa-search-plus:before{content:""}.fa-searchengin:before{content:""}.fa-seedling:before{content:""}.fa-sellcast:before{content:""}.fa-sellsy:before{content:""}.fa-server:before{content:""}.fa-servicestack:before{content:""}.fa-shapes:before{content:""}.fa-share:before{content:""}.fa-share-alt:before{content:""}.fa-share-alt-square:before{content:""}.fa-share-square:before{content:""}.fa-shekel-sign:before{content:""}.fa-shield-alt:before{content:""}.fa-ship:before{content:""}.fa-shipping-fast:before{content:""}.fa-shirtsinbulk:before{content:""}.fa-shoe-prints:before{content:""}.fa-shopping-bag:before{content:""}.fa-shopping-basket:before{content:""}.fa-shopping-cart:before{content:""}.fa-shopware:before{content:""}.fa-shower:before{content:""}.fa-shuttle-van:before{content:""}.fa-sign:before{content:""}.fa-sign-in-alt:before{content:""}.fa-sign-language:before{content:""}.fa-sign-out-alt:before{content:""}.fa-signal:before{content:""}.fa-signature:before{content:""}.fa-sim-card:before{content:""}.fa-simplybuilt:before{content:""}.fa-sistrix:before{content:""}.fa-sitemap:before{content:""}.fa-sith:before{content:""}.fa-skating:before{content:""}.fa-sketch:before{content:""}.fa-skiing:before{content:""}.fa-skiing-nordic:before{content:""}.fa-skull:before{content:""}.fa-skull-crossbones:before{content:""}.fa-skyatlas:before{content:""}.fa-skype:before{content:""}.fa-slack:before{content:""}.fa-slack-hash:before{content:""}.fa-slash:before{content:""}.fa-sleigh:before{content:""}.fa-sliders-h:before{content:""}.fa-slideshare:before{content:""}.fa-smile:before{content:""}.fa-smile-beam:before{content:""}.fa-smile-wink:before{content:""}.fa-smog:before{content:""}.fa-smoking:before{content:""}.fa-smoking-ban:before{content:""}.fa-sms:before{content:""}.fa-snapchat:before{content:""}.fa-snapchat-ghost:before{content:""}.fa-snapchat-square:before{content:""}.fa-snowboarding:before{content:""}.fa-snowflake:before{content:""}.fa-snowman:before{content:""}.fa-snowplow:before{content:""}.fa-socks:before{content:""}.fa-solar-panel:before{content:""}.fa-sort:before{content:""}.fa-sort-alpha-down:before{content:""}.fa-sort-alpha-down-alt:before{content:""}.fa-sort-alpha-up:before{content:""}.fa-sort-alpha-up-alt:before{content:""}.fa-sort-amount-down:before{content:""}.fa-sort-amount-down-alt:before{content:""}.fa-sort-amount-up:before{content:""}.fa-sort-amount-up-alt:before{content:""}.fa-sort-down:before{content:""}.fa-sort-numeric-down:before{content:""}.fa-sort-numeric-down-alt:before{content:""}.fa-sort-numeric-up:before{content:""}.fa-sort-numeric-up-alt:before{content:""}.fa-sort-up:before{content:""}.fa-soundcloud:before{content:""}.fa-sourcetree:before{content:""}.fa-spa:before{content:""}.fa-space-shuttle:before{content:""}.fa-speakap:before{content:""}.fa-speaker-deck:before{content:""}.fa-spell-check:before{content:""}.fa-spider:before{content:""}.fa-spinner:before{content:""}.fa-splotch:before{content:""}.fa-spotify:before{content:""}.fa-spray-can:before{content:""}.fa-square:before{content:""}.fa-square-full:before{content:""}.fa-square-root-alt:before{content:""}.fa-squarespace:before{content:""}.fa-stack-exchange:before{content:""}.fa-stack-overflow:before{content:""}.fa-stackpath:before{content:""}.fa-stamp:before{content:""}.fa-star:before{content:""}.fa-star-and-crescent:before{content:""}.fa-star-half:before{content:""}.fa-star-half-alt:before{content:""}.fa-star-of-david:before{content:""}.fa-star-of-life:before{content:""}.fa-staylinked:before{content:""}.fa-steam:before{content:""}.fa-steam-square:before{content:""}.fa-steam-symbol:before{content:""}.fa-step-backward:before{content:""}.fa-step-forward:before{content:""}.fa-stethoscope:before{content:""}.fa-sticker-mule:before{content:""}.fa-sticky-note:before{content:""}.fa-stop:before{content:""}.fa-stop-circle:before{content:""}.fa-stopwatch:before{content:""}.fa-store:before{content:""}.fa-store-alt:before{content:""}.fa-strava:before{content:""}.fa-stream:before{content:""}.fa-street-view:before{content:""}.fa-strikethrough:before{content:""}.fa-stripe:before{content:""}.fa-stripe-s:before{content:""}.fa-stroopwafel:before{content:""}.fa-studiovinari:before{content:""}.fa-stumbleupon:before{content:""}.fa-stumbleupon-circle:before{content:""}.fa-subscript:before{content:""}.fa-subway:before{content:""}.fa-suitcase:before{content:""}.fa-suitcase-rolling:before{content:""}.fa-sun:before{content:""}.fa-superpowers:before{content:""}.fa-superscript:before{content:""}.fa-supple:before{content:""}.fa-surprise:before{content:""}.fa-suse:before{content:""}.fa-swatchbook:before{content:""}.fa-swimmer:before{content:""}.fa-swimming-pool:before{content:""}.fa-symfony:before{content:""}.fa-synagogue:before{content:""}.fa-sync:before{content:""}.fa-sync-alt:before{content:""}.fa-syringe:before{content:""}.fa-table:before{content:""}.fa-table-tennis:before{content:""}.fa-tablet:before{content:""}.fa-tablet-alt:before{content:""}.fa-tablets:before{content:""}.fa-tachometer-alt:before{content:""}.fa-tag:before{content:""}.fa-tags:before{content:""}.fa-tape:before{content:""}.fa-tasks:before{content:""}.fa-taxi:before{content:""}.fa-teamspeak:before{content:""}.fa-teeth:before{content:""}.fa-teeth-open:before{content:""}.fa-telegram:before{content:""}.fa-telegram-plane:before{content:""}.fa-temperature-high:before{content:""}.fa-temperature-low:before{content:""}.fa-tencent-weibo:before{content:""}.fa-tenge:before{content:""}.fa-terminal:before{content:""}.fa-text-height:before{content:""}.fa-text-width:before{content:""}.fa-th:before{content:""}.fa-th-large:before{content:""}.fa-th-list:before{content:""}.fa-the-red-yeti:before{content:""}.fa-theater-masks:before{content:""}.fa-themeco:before{content:""}.fa-themeisle:before{content:""}.fa-thermometer:before{content:""}.fa-thermometer-empty:before{content:""}.fa-thermometer-full:before{content:""}.fa-thermometer-half:before{content:""}.fa-thermometer-quarter:before{content:""}.fa-thermometer-three-quarters:before{content:""}.fa-think-peaks:before{content:""}.fa-thumbs-down:before{content:""}.fa-thumbs-up:before{content:""}.fa-thumbtack:before{content:""}.fa-ticket-alt:before{content:""}.fa-times:before{content:""}.fa-times-circle:before{content:""}.fa-tint:before{content:""}.fa-tint-slash:before{content:""}.fa-tired:before{content:""}.fa-toggle-off:before{content:""}.fa-toggle-on:before{content:""}.fa-toilet:before{content:""}.fa-toilet-paper:before{content:""}.fa-toolbox:before{content:""}.fa-tools:before{content:""}.fa-tooth:before{content:""}.fa-torah:before{content:""}.fa-torii-gate:before{content:""}.fa-tractor:before{content:""}.fa-trade-federation:before{content:""}.fa-trademark:before{content:""}.fa-traffic-light:before{content:""}.fa-train:before{content:""}.fa-tram:before{content:""}.fa-transgender:before{content:""}.fa-transgender-alt:before{content:""}.fa-trash:before{content:""}.fa-trash-alt:before{content:""}.fa-trash-restore:before{content:""}.fa-trash-restore-alt:before{content:""}.fa-tree:before{content:""}.fa-trello:before{content:""}.fa-tripadvisor:before{content:""}.fa-trophy:before{content:""}.fa-truck:before{content:""}.fa-truck-loading:before{content:""}.fa-truck-monster:before{content:""}.fa-truck-moving:before{content:""}.fa-truck-pickup:before{content:""}.fa-tshirt:before{content:""}.fa-tty:before{content:""}.fa-tumblr:before{content:""}.fa-tumblr-square:before{content:""}.fa-tv:before{content:""}.fa-twitch:before{content:""}.fa-twitter:before{content:""}.fa-twitter-square:before{content:""}.fa-typo3:before{content:""}.fa-uber:before{content:""}.fa-ubuntu:before{content:""}.fa-uikit:before{content:""}.fa-umbrella:before{content:""}.fa-umbrella-beach:before{content:""}.fa-underline:before{content:""}.fa-undo:before{content:""}.fa-undo-alt:before{content:""}.fa-uniregistry:before{content:""}.fa-universal-access:before{content:""}.fa-university:before{content:""}.fa-unlink:before{content:""}.fa-unlock:before{content:""}.fa-unlock-alt:before{content:""}.fa-untappd:before{content:""}.fa-upload:before{content:""}.fa-ups:before{content:""}.fa-usb:before{content:""}.fa-user:before{content:""}.fa-user-alt:before{content:""}.fa-user-alt-slash:before{content:""}.fa-user-astronaut:before{content:""}.fa-user-check:before{content:""}.fa-user-circle:before{content:""}.fa-user-clock:before{content:""}.fa-user-cog:before{content:""}.fa-user-edit:before{content:""}.fa-user-friends:before{content:""}.fa-user-graduate:before{content:""}.fa-user-injured:before{content:""}.fa-user-lock:before{content:""}.fa-user-md:before{content:""}.fa-user-minus:before{content:""}.fa-user-ninja:before{content:""}.fa-user-nurse:before{content:""}.fa-user-plus:before{content:""}.fa-user-secret:before{content:""}.fa-user-shield:before{content:""}.fa-user-slash:before{content:""}.fa-user-tag:before{content:""}.fa-user-tie:before{content:""}.fa-user-times:before{content:""}.fa-users:before{content:""}.fa-users-cog:before{content:""}.fa-usps:before{content:""}.fa-ussunnah:before{content:""}.fa-utensil-spoon:before{content:""}.fa-utensils:before{content:""}.fa-vaadin:before{content:""}.fa-vector-square:before{content:""}.fa-venus:before{content:""}.fa-venus-double:before{content:""}.fa-venus-mars:before{content:""}.fa-viacoin:before{content:""}.fa-viadeo:before{content:""}.fa-viadeo-square:before{content:""}.fa-vial:before{content:""}.fa-vials:before{content:""}.fa-viber:before{content:""}.fa-video:before{content:""}.fa-video-slash:before{content:""}.fa-vihara:before{content:""}.fa-vimeo:before{content:""}.fa-vimeo-square:before{content:""}.fa-vimeo-v:before{content:""}.fa-vine:before{content:""}.fa-vk:before{content:""}.fa-vnv:before{content:""}.fa-voicemail:before{content:""}.fa-volleyball-ball:before{content:""}.fa-volume-down:before{content:""}.fa-volume-mute:before{content:""}.fa-volume-off:before{content:""}.fa-volume-up:before{content:""}.fa-vote-yea:before{content:""}.fa-vr-cardboard:before{content:""}.fa-vuejs:before{content:""}.fa-walking:before{content:""}.fa-wallet:before{content:""}.fa-warehouse:before{content:""}.fa-water:before{content:""}.fa-wave-square:before{content:""}.fa-waze:before{content:""}.fa-weebly:before{content:""}.fa-weibo:before{content:""}.fa-weight:before{content:""}.fa-weight-hanging:before{content:""}.fa-weixin:before{content:""}.fa-whatsapp:before{content:""}.fa-whatsapp-square:before{content:""}.fa-wheelchair:before{content:""}.fa-whmcs:before{content:""}.fa-wifi:before{content:""}.fa-wikipedia-w:before{content:""}.fa-wind:before{content:""}.fa-window-close:before{content:""}.fa-window-maximize:before{content:""}.fa-window-minimize:before{content:""}.fa-window-restore:before{content:""}.fa-windows:before{content:""}.fa-wine-bottle:before{content:""}.fa-wine-glass:before{content:""}.fa-wine-glass-alt:before{content:""}.fa-wix:before{content:""}.fa-wizards-of-the-coast:before{content:""}.fa-wolf-pack-battalion:before{content:""}.fa-won-sign:before{content:""}.fa-wordpress:before{content:""}.fa-wordpress-simple:before{content:""}.fa-wpbeginner:before{content:""}.fa-wpexplorer:before{content:""}.fa-wpforms:before{content:""}.fa-wpressr:before{content:""}.fa-wrench:before{content:""}.fa-x-ray:before{content:""}.fa-xbox:before{content:""}.fa-xing:before{content:""}.fa-xing-square:before{content:""}.fa-y-combinator:before{content:""}.fa-yahoo:before{content:""}.fa-yammer:before{content:""}.fa-yandex:before{content:""}.fa-yandex-international:before{content:""}.fa-yarn:before{content:""}.fa-yelp:before{content:""}.fa-yen-sign:before{content:""}.fa-yin-yang:before{content:""}.fa-yoast:before{content:""}.fa-youtube:before{content:""}.fa-youtube-square:before{content:""}.fa-zhihu:before{content:""}.sr-only{border:0;clip:rect(0, 0, 0, 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.sr-only-focusable:active,.sr-only-focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}/*! * Font Awesome Free 5.9.0 by @fontawesome - https://fontawesome.com * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) - */@font-face{font-family:"Font Awesome 5 Free";font-style:normal;font-weight:900;font-display:auto;src:url("./webfonts/fa-solid-900.eot");src:url("./webfonts/fa-solid-900.eot?#iefix") format("embedded-opentype"),url("./webfonts/fa-solid-900.woff2") format("woff2"),url("./webfonts/fa-solid-900.woff") format("woff"),url("./webfonts/fa-solid-900.ttf") format("truetype"),url("./webfonts/fa-solid-900.svg#fontawesome") format("svg")}.fa,.fas{font-family:"Font Awesome 5 Free";font-weight:900}html{font-size:87.5%;box-sizing:border-box}*,*::before,*::after{box-sizing:inherit}body,h1,h2,h3,h4,h5,h6,p,figure,blockquote,dl,dd{margin:0;padding:0}ul[role=list],ol[role=list]{list-style:none}html:focus-within{scroll-behavior:smooth}body{text-rendering:optimizeSpeed;line-height:1.5;font-family:"Roboto",Arial,Helvetica,sans-serif !important;letter-spacing:-0.025ch}a:not([class]){text-decoration-skip-ink:auto}img,picture{max-width:100%;display:block}input,button,textarea,select{font:inherit}@media(prefers-reduced-motion: reduce){html:focus-within{scroll-behavior:auto}*,*::before,*::after{animation-duration:.01ms !important;animation-iteration-count:1 !important;transition-duration:.01ms !important;scroll-behavior:auto !important}}#main{height:100vh}.content{display:flex;height:100%;overflow:hidden}.tab-content{display:flex;height:100%;width:100%;background-color:#eef3f6;animation:fadein .3s;overflow:auto}input[type=text],input[type=password],input[type=number],textarea{box-sizing:border-box;background:#fff;max-width:100%;font-size:1rem;font-weight:400;border:1px solid #ccc;border-radius:.25rem;padding:.25rem .5rem;outline:rgba(0,0,0,0)}input:focus{border:1px solid #3ba4d7;box-shadow:inset 0 0 5px #ccc}input.stretched{width:90%}input.small{max-width:70%;padding:.1rem}input.searchbar{width:40%}a{cursor:pointer}a[title=Back]{width:max-content;height:max-content;padding:.475rem .75rem;border-radius:50%;transition:100ms}a[title=Back]:hover{background:#eef3f6}table{padding:20px;table-layout:fixed;width:100%;border-collapse:collapse;text-align:center;color:#333;font-size:1.125rem}table th{font-size:1.125rem;color:#000;border-bottom:2px solid #eee}table tr{border-bottom:1px solid #eee}h3{color:#444}hr{margin-left:0;color:#aaa}.grid-2col{display:grid;grid-template-columns:auto auto;gap:1rem;justify-content:start}.grid-2col input[type=checkbox]{margin-top:20px}.error{color:red}.tooltip{color:#333;position:relative;display:inline-block;margin:0 .25rem}.tooltiptext{visibility:hidden;position:absolute;top:100%;left:50%;min-width:250px;margin-left:-120px;z-index:1;color:#ccc;background-color:#333;font-size:.875rem;text-align:center;padding:.25rem;border-radius:.5rem}.tooltip:hover .tooltiptext{visibility:visible;animation:fadein .5s}blockquote{color:#14141b;padding:.75rem 1rem .75rem 2rem;border-radius:.25rem}blockquote.info{position:relative;line-height:1.2;color:rgba(20,20,27,.8);border:1px solid rgba(17,143,204,.8)}blockquote.info::before{font-family:"Font Awesome 5 Free";position:absolute;top:.5rem;left:.5rem;content:"";color:#019dff}@keyframes fadein{from{opacity:0}to{opacity:1}}.fadein{animation:fadein .5s}@keyframes swipe-from-left{from{margin-left:100%}to{margin-left:0}}button{width:max-content;height:max-content;color:#fff;background:#019dff;font-size:1rem;padding:.4rem 1rem;border:0;border-radius:5px;cursor:pointer;box-shadow:inset -3px -3px 0 rgb(0,94.5826771654,154)}button:active{outline:none;box-shadow:inset 3px 3px 0 rgb(0,94.5826771654,154)}button.red{width:max-content;height:max-content;color:#fff;background:#ff3a4a;font-size:1rem;padding:.4rem 1rem;border:0;border-radius:5px;cursor:pointer;box-shadow:inset -3px -3px 0 rgb(211,0,17.1370558376)}button.red:active{outline:none;box-shadow:inset 3px 3px 0 rgb(211,0,17.1370558376)}.media-item{display:flex;margin-top:.5rem;padding:1rem;border:1px solid rgba(20,20,27,.1);border-radius:4px}.media-item__details{flex-basis:40%;display:flex;align-items:start;gap:.5rem}.media-item__details img{width:6rem;object-fit:contain}.media-item__desc{flex-basis:60%}.active-link{background:hsla(0,0%,100%,.1) !important}.nav-menu{background-color:#14141b;box-shadow:0 5px 5px #222;display:flex;flex-direction:column;align-items:center;height:100%;padding:.25rem;margin-right:0rem}.nav-menu__logo{padding:1.2rem 0;display:flex;align-items:center;gap:.3rem}.nav-menu__logo img{width:1.6rem}.nav-menu__logo h5{line-height:1;color:#fff}.nav-menu__box{padding:2rem .125rem;display:flex;flex-direction:column;gap:.5rem;position:relative}.nav-menu__box .item{margin:0;padding:.675rem .5rem;width:10rem;display:flex;align-items:center;line-height:1;border-radius:.5rem;text-decoration:none;color:#ccc;text-transform:capitalize;transition:0ms}.nav-menu__box .item:hover{background-color:rgba(238,243,246,.15)}.nav-menu__box .item i.sidenav-icon{width:2.5rem;height:1.4rem;display:grid;place-items:center}.nav-menu__box .item.item-selected{color:#9bdaff;background-color:rgba(155,218,255,.15);font-weight:medium}.nav-menu__box button.toggle-nav{display:none;position:absolute;padding:0;top:0;right:-1rem;background:rgb(77.5,186.5157480315,255);width:1.5rem;height:1.5rem;aspect-ratio:1;justify-content:center;align-items:center;border-radius:50%;box-shadow:none}.nav-menu.collapsed .nav-menu__logo .logo-container{display:flex;flex-direction:column;align-items:center;gap:.5rem}.nav-menu.collapsed .nav-menu__logo .logo-container>*:not(img){display:block}.nav-menu.collapsed .nav-menu__logo .nav-menu__logo-text{display:none !important}.nav-menu.collapsed .nav-menu__box .item{padding:.675rem 0;width:2.5rem;justify-content:center;transition:300ms}.nav-menu.collapsed .nav-menu__box .item span,.nav-menu.collapsed .nav-menu__box .item p{display:none !important}.nav-menu.collapsed button i{rotate:180deg}.nav-menu:hover button.toggle-nav{display:flex}.sidebar{width:13rem;background-color:#fff;display:flex;flex-direction:column}.sidebar a{text-decoration:none;text-transform:capitalize;padding:1rem;cursor:pointer;color:#999}.sidebar a:hover{color:#222}.sidebar .selected-sidebar-link{font-weight:bold;color:#222;border-left:5px solid #3ba4d7;animation:expand-left-border .1s}.sidebarquickview>h6{padding:.5rem}.sidebarquickview a{text-decoration:none;text-transform:capitalize;padding:.5rem 1rem;display:block;color:#999}.sidebarquickview a a:hover{color:#222}.sidebarquickview .selected-sidebarquickview-link{font-weight:bold;color:#222;border-left:5px solid #3ba4d7;animation:expand-left-border .1s}.node-panel{width:100%;padding:.5rem;animation:fadein .5s}@keyframes expand-left-border{from{border-left:0}to{border-left:5px solid #3ba4d7}}@media(max-width: 700px){.tab-content{flex-direction:column}.sidebar{width:100% !important;flex-direction:row !important;overflow-x:auto !important;overflow-y:hidden !important;white-space:nowrap !important;border-bottom:1px solid rgba(20,20,27,.1) !important;background:#fff !important;z-index:50 !important;flex-shrink:0 !important;height:auto !important;padding:0 !important}.sidebar a{display:inline-block !important;padding:.8rem 1.2rem !important;border-bottom:3px solid rgba(0,0,0,0) !important;border-left:none !important}.sidebar .selected-sidebar-link{border-left:none !important;border-bottom:3px solid #3ba4d7 !important;animation:none !important}.sidebarquickview>h4,.sidebarquickview>h6{display:none !important}}.posts{height:100%;margin-top:1rem;flex-direction:column;overflow:auto}.posts__heading{display:flex;flex-direction:column;justify-content:space-between}.posts-container{height:100%;padding:1rem;display:grid;grid-template-columns:repeat(auto-fill, minmax(150px, 1fr));gap:2rem;border:1px solid rgba(20,20,27,.1);border-radius:4px;overflow:auto}.posts-container-card{min-height:240px;flex-direction:column;border:1px solid rgba(20,20,27,.5);border-radius:4px;cursor:pointer;text-align:center}.posts-container-card img{flex-basis:90%;object-fit:cover}.posts-container-card p{padding:0 .125rem;flex-basis:10%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.progress-bar{width:100%;height:2rem;position:relative;text-align:center;background-color:#eef3f6;border-radius:20px;overflow:hidden}.progress-bar__status{position:absolute;top:0;left:0;height:100%;color:#14141b;background-color:#019dff}.progress-bar__percent{position:absolute;inset:0;margin:auto;width:fit-content;height:fit-content}.progress-bar-chunks{position:relative;margin-top:.5rem;width:100%;height:2rem;display:flex;border-radius:.25rem;overflow:hidden;background-color:#eef3f6}.progress-bar-chunks .chunk{width:100%}.progress-bar-chunks .chunk[data-chunkVal="0"]{background-color:rgba(155,218,255,.2)}.progress-bar-chunks .chunk[data-chunkVal="1"]{background-color:#ff3a4a}.progress-bar-chunks .chunk[data-chunkVal="2"]{background-color:#019dff}.progress-bar-chunks .chunk[data-chunkVal="3"]{background-color:#fcba03}.progress-bar-chunks__percent{position:absolute;inset:0;margin:auto;width:fit-content;height:fit-content}.statusbar{display:flex;justify-content:space-between;align-items:center;height:28px;background-color:#14141b;border-top:1px solid #2e2e38;padding:0 1rem;font-size:.8rem;color:#94a3b8;z-index:100;box-sizing:border-box;user-select:none;flex-shrink:0}.statusbar-left{display:flex;align-items:center}.statusbar-right{display:flex;align-items:center;gap:1.5rem}.statusbar-item{display:flex;align-items:center}.statusbar-divider{width:1px;height:14px;background-color:#2e2e38}.status-bullet{width:8px;height:8px;border-radius:50%;display:inline-block;box-shadow:0 0 4px rgba(0,0,0,.5)}.widget{height:100%;padding:1rem;display:flex;flex-direction:column;gap:.5rem;background-color:#fff;border-radius:.5rem;overflow:auto}.widget .top-heading{display:flex;justify-content:space-between}.widget__heading{display:flex;justify-content:space-between;align-items:center;border-bottom:2px solid #999}.widget__body{height:100%;display:flex;flex-direction:column;overflow:auto}.widget__body-heading{display:flex;justify-content:space-between;align-items:center}.widget__body-heading .action{display:flex;gap:.5rem}.widget__body-content{height:100%;overflow:auto}.widget__body-box{display:flex;flex-direction:column;gap:.5rem}.widget-half{max-width:50%}#modal-container{display:none;position:fixed;z-index:1;height:100%;top:0;left:0;width:100%;background-color:rgba(0,0,0,.2)}.modal-content{position:absolute;color:#555;width:40%;min-height:10rem;height:max-content;padding:1.5rem;inset:0;margin:auto;background-color:#fff;border-radius:.5rem;animation:fadein .5s;display:flex;flex-direction:column}.modal-content button:last-child{margin-top:auto}.modal-content .close-btn{position:absolute;right:1.5rem}.modal-content .widget{padding:0}#notification-container{position:absolute;bottom:0;right:0}.login-page{background-image:linear-gradient(-45deg, rgba(1, 157, 255, 0.75), rgba(17, 143, 204, 0.75));height:100%;animation:fadein .5s}.login-page .login-container{background-color:#fff;box-shadow:3px 3px 5px rgba(20,20,27,.4);margin:auto;position:relative;top:100px;max-width:400px;max-height:500px;border-radius:5px;display:flex;flex-direction:column;align-items:center}.login-page .login-container input{padding:.375rem .75rem;border-radius:.275rem}.login-page .login-container *{margin-bottom:1rem}.login-page .login-container>img{margin:1rem 0 2rem}.login-page .login-container extra{margin:0}.login-page .login-container>a{text-decoration:underline;cursor:pointer}.login-page .extra>label,.login-page .extra>br,.login-page .extra>input{margin-bottom:0}.homepage{margin:2rem auto 0;display:flex;flex-direction:column;gap:4rem}.homepage .logo{display:flex;justify-content:center;align-items:center}.homepage .logo img{width:90px}.homepage .logo .retroshareText{display:flex;flex-direction:column;align-items:center}.homepage .logo .retroshareText .retrotext{font-size:36px;font-weight:600;line-height:1.125}.homepage .logo .retroshareText .retrotext>span{color:#118fcc}.homepage .logo .retroshareText>b{font-size:14px;line-height:1}.homepage .certificate{display:flex;flex-direction:column;gap:4rem}.homepage .certificate__heading{text-align:center}.homepage .certificate__heading>h1{margin-bottom:1rem}.homepage .certificate__content{display:flex;flex-direction:column;gap:2rem;padding:2rem;text-align:center;border:1.5px solid rgba(17,143,204,.2);border-radius:6px;box-shadow:0px 0px 8px 2px rgba(20,20,27,.05)}.homepage .certificate__content .rsId>p{margin-bottom:.5rem;color:#118fcc}.homepage .certificate__content .retroshareID{padding:.25rem;display:flex;align-items:center;justify-self:start;font-size:1.25rem;border-radius:4px;background:rgba(20,20,27,.05)}.homepage .certificate__content .retroshareID .textArea{padding:0;width:100%;height:auto;font-size:1rem;font-family:monospace;background:rgba(0,0,0,0);border:none;resize:none;overflow:hidden;field-sizing:content}.homepage .certificate__content .retroshareID i{color:#118fcc}.homepage .certificate__content .retroshareID>i{margin:0 .5rem;cursor:pointer}.homepage .certificate__content .webhelp{padding:.5rem;background:#f5f5f5;display:flex;justify-content:center;align-items:center;gap:.5rem;border-radius:4px;border:1px solid rgba(20,20,27,.5);width:fit-content;cursor:pointer}.homepage .certificate__content .webhelp-container{display:grid;place-items:center}.homepage .certificate__content .webhelp:hover{background:#eef3f6;border:1px solid #14141b}.homepage .certificate__content .webhelp>i{font-size:1.2rem;color:green}.homepage .certificate__content .add-friend>h6,.homepage .certificate__content .webhelp-container>h6{font-weight:normal;margin-bottom:.5rem}.network-container{display:flex;height:100%;width:100%;overflow:hidden;background-color:#f1f5f9}.network-left-pane{width:320px;min-width:300px;max-width:350px;border-right:1px solid #cbd5e1;display:flex;flex-direction:column;background:#fff;box-shadow:2px 0 5px rgba(0,0,0,.05)}.own-profile-card{padding:1.25rem;border-bottom:1px solid #e2e8f0;background:linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);display:flex;flex-direction:column;gap:.75rem}.own-profile-card .profile-header{display:flex;align-items:center;gap:1rem}.own-profile-card .profile-info{display:flex;flex-direction:column;flex:1;overflow:hidden}.own-profile-card .profile-info .profile-name{font-weight:700;color:#1e293b;font-size:1.1rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.own-profile-card .profile-info .profile-status{font-size:.85rem;color:#10b981;font-weight:500;display:flex;align-items:center;gap:.35rem}.own-profile-card .profile-info .profile-status::before{content:"";display:inline-block;width:8px;height:8px;background-color:#10b981;border-radius:50%}.own-profile-card .own-identity-select-container{display:flex;flex-direction:column;gap:.25rem}.own-profile-card .own-identity-select-container label{font-size:.75rem;color:#64748b;font-weight:600;text-transform:uppercase;letter-spacing:.05em}.own-profile-card .own-identity-select-container select.own-identity-select{width:100%;padding:.375rem .5rem;font-size:.85rem;border:1px solid #cbd5e1;border-radius:.375rem;background-color:#fff;color:#334155;outline:none;cursor:pointer;transition:border-color .2s}.own-profile-card .own-identity-select-container select.own-identity-select:focus{border-color:#3ba4d7}.friends-list-container{flex:1;display:flex;flex-direction:column;overflow:hidden}.friends-list-container .searchbar-container{padding:.75rem 1rem;border-bottom:1px solid #e2e8f0}.friends-list-container .searchbar-container input.searchbar{width:100%;padding:.5rem .75rem;font-size:.9rem;border:1px solid #cbd5e1;border-radius:.375rem;background-color:#f8fafc;outline:none;transition:all .2s}.friends-list-container .searchbar-container input.searchbar:focus{background-color:#fff;border-color:#3ba4d7;box-shadow:0 0 0 3px rgba(59,164,215,.15)}.friends-list-container .friends-scroll{flex:1;overflow-y:auto;padding:.5rem 0}.friend-list-item{display:flex;align-items:center;gap:.75rem;padding:.75rem 1rem;margin:.125rem .5rem;border-radius:.5rem;cursor:pointer;transition:all .2s}.friend-list-item:hover{background-color:#f1f5f9}.friend-list-item.selected{background-color:#e0f2fe}.friend-list-item.selected .friend-name{color:#0369a1;font-weight:600}.friend-list-item .friend-avatar{flex-shrink:0}.friend-list-item .friend-meta{flex:1;min-width:0}.friend-list-item .friend-meta .friend-name{font-size:.95rem;color:#334155;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;transition:color .2s}.friend-list-item .friend-meta .friend-status{font-size:.8rem;color:#94a3b8}.friend-list-item .friend-meta .friend-status.online{color:#10b981;font-weight:500}.network-right-pane{flex:1;display:flex;flex-direction:column;overflow:hidden;background-color:#f8fafc}.network-pane-placeholder{flex:1;display:flex;flex-direction:column;justify-content:center;align-items:center;color:#94a3b8;gap:1rem;padding:2rem;text-align:center}.network-pane-placeholder i{font-size:4rem;color:#cbd5e1}.network-pane-placeholder p{font-size:1.1rem;max-width:400px}.network-tabs{display:flex;background-color:#fff;border-bottom:1px solid #cbd5e1;padding:.5rem 1rem 0;gap:.5rem}.network-tabs .tab-btn{padding:.625rem 1.25rem;font-size:.95rem;font-weight:600;color:#64748b;background:rgba(0,0,0,0);border:none;border-radius:.375rem .375rem 0 0;border-bottom:3px solid rgba(0,0,0,0);cursor:pointer;box-shadow:none;transition:all .2s}.network-tabs .tab-btn:hover{color:#334155;background-color:#f1f5f9}.network-tabs .tab-btn.active{color:#3ba4d7;border-bottom-color:#3ba4d7;background-color:rgba(0,0,0,0)}.network-tab-content{flex:1;overflow-y:auto;padding:1.5rem}.network-detail-view{display:flex;flex-direction:column;gap:1.5rem}.network-detail-view .detail-header{display:flex;align-items:center;gap:1.5rem;padding-bottom:1.5rem;border-bottom:1px solid #e2e8f0}.network-detail-view .detail-header .detail-title{flex:1}.network-detail-view .detail-header .detail-title h2{font-size:1.75rem;font-weight:800;color:#1e293b;margin-bottom:.25rem}.network-detail-view .detail-header .detail-title .detail-subtitle{font-size:.9rem;color:#64748b;display:flex;align-items:center;gap:.5rem}.network-detail-view .detail-header .detail-actions{display:flex;gap:.75rem}.network-detail-view .detail-header .detail-actions button{display:flex;align-items:center;gap:.5rem;padding:.5rem 1rem;font-size:.9rem}.network-detail-view .detail-section{background-color:#fff;border-radius:.5rem;border:1px solid #e2e8f0;padding:1.25rem;box-shadow:0 1px 3px rgba(0,0,0,.02)}.network-detail-view .detail-section h3{font-size:1.1rem;font-weight:700;color:#334155;margin-bottom:1rem;padding-bottom:.5rem;border-bottom:1px solid #f1f5f9}.network-detail-view .detail-section .info-grid{display:grid;grid-template-columns:120px 1fr;row-gap:.75rem;font-size:.9rem}.network-detail-view .detail-section .info-grid .info-label{font-weight:600;color:#64748b}.network-detail-view .detail-section .info-grid .info-value{color:#1e293b;word-break:break-all}.network-detail-view .locations-grid{display:grid;grid-template-columns:repeat(auto-fill, minmax(280px, 1fr));gap:1rem}.network-detail-view .location-card{background-color:#fff;border:1px solid #e2e8f0;border-radius:.5rem;padding:1rem;display:flex;flex-direction:column;gap:.5rem;box-shadow:0 1px 3px rgba(0,0,0,.02)}.network-detail-view .location-card .loc-header{display:flex;justify-content:space-between;align-items:center;border-bottom:1px solid #f1f5f9;padding-bottom:.5rem;margin-bottom:.25rem}.network-detail-view .location-card .loc-header .loc-name{font-weight:700;color:#334155;font-size:.95rem}.network-detail-view .location-card .loc-header .loc-status{font-size:.75rem;font-weight:600;padding:.125rem .5rem;border-radius:.25rem}.network-detail-view .location-card .loc-header .loc-status.online{background-color:#d1fae5;color:#065f46}.network-detail-view .location-card .loc-header .loc-status.offline{background-color:#f1f5f9;color:#475569}.network-detail-view .location-card .loc-body{font-size:.85rem;display:grid;grid-template-columns:80px 1fr;row-gap:.25rem}.network-detail-view .location-card .loc-body .loc-label{color:#64748b}.network-detail-view .location-card .loc-body .loc-val{color:#334155;word-break:break-all}.network-detail-view .location-card .loc-footer{margin-top:.5rem;display:flex;justify-content:flex-end}.network-detail-view .location-card .loc-footer button{font-size:.8rem;padding:.25rem .75rem}.network-chat-view{display:flex;flex-direction:column;height:100%;overflow:hidden;background-color:#f8fafc}.network-chat-view .chat-messages{flex:1;overflow-y:auto;padding:1.25rem;display:flex;flex-direction:column;gap:1rem}.network-chat-view .chat-bubble-container{display:flex;flex-direction:column;max-width:70%}.network-chat-view .chat-bubble-container.outgoing{align-self:flex-end;align-items:flex-end}.network-chat-view .chat-bubble-container.outgoing .chat-bubble{background-color:#3ba4d7;color:#fff;border-bottom-right-radius:.125rem}.network-chat-view .chat-bubble-container.incoming{align-self:flex-start;align-items:flex-start}.network-chat-view .chat-bubble-container.incoming .chat-bubble{background-color:#fff;color:#1e293b;border:1px solid #e2e8f0;border-bottom-left-radius:.125rem}.network-chat-view .chat-bubble-container .chat-sender{font-size:.75rem;color:#64748b;margin-bottom:.25rem;padding:0 .25rem}.network-chat-view .chat-bubble-container .chat-bubble{padding:.625rem .875rem;border-radius:.75rem;font-size:.925rem;line-height:1.4;white-space:break-spaces;word-break:break-word;box-shadow:0 1px 2px rgba(0,0,0,.05)}.network-chat-view .chat-bubble-container .chat-time{font-size:.7rem;color:#94a3b8;margin-top:.25rem;padding:0 .25rem}.network-chat-view .chat-input-area{padding:1rem;background-color:#fff;border-top:1px solid #cbd5e1;display:flex;gap:.75rem;align-items:center}.network-chat-view .chat-input-area textarea.chat-textarea{flex:1;resize:none;height:40px;padding:.5rem .75rem;border:1px solid #cbd5e1;border-radius:.375rem;font-size:.9rem;outline:none;transition:all .2s}.network-chat-view .chat-input-area textarea.chat-textarea:focus{border-color:#3ba4d7;box-shadow:0 0 0 3px rgba(59,164,215,.15)}.network-chat-view .chat-input-area button.send-btn{padding:.5rem 1.25rem;font-size:.9rem;height:40px;display:flex;align-items:center;gap:.5rem}.network-chat-view .chat-warning{flex:1;display:flex;flex-direction:column;justify-content:center;align-items:center;color:#64748b;text-align:center;padding:2rem;gap:1rem}.network-chat-view .chat-warning i{font-size:3rem;color:#cbd5e1}.network-chat-view .chat-warning h4{font-weight:700;color:#334155}.network-chat-view .chat-warning p{max-width:350px;font-size:.9rem}.identity{color:#444;font-size:1.1em;margin:20px;padding:10px;border:1px solid #aaa;border-radius:20px}.identity>h4{margin:5px;font-size:1.3em}.identity button{font-size:.9em}.identity .details{display:grid;grid-template-columns:140px auto;grid-row-gap:5px;justify-content:left}.defaultAvatar{width:3rem;height:3rem;aspect-ratio:1;background:#b0c4de;border-radius:50%;display:grid;place-items:center}.defaultAvatar p{font-weight:900;color:#666f7f;transform:translateY(1px)}img.avatar{display:block;width:3rem;height:max-content;aspect-ratio:1;margin-right:.3em;border-radius:50%}.counter{margin-left:.5em}.counter:before{content:"("}.counter:after{content:")"}.chatInit{margin-left:.5em;color:green;cursor:pointer}.people-sidebar-header{display:flex;flex-direction:column;padding:.75rem 1rem .5rem 1rem;gap:.75rem;border-bottom:1px solid #e2e8f0;background-color:#fff}.people-sidebar-header .searchbar-wrapper{position:relative;display:flex;align-items:center}.people-sidebar-header .searchbar-wrapper i.fa-search{position:absolute;left:.85rem;color:#94a3b8;font-size:.9rem}.people-sidebar-header .searchbar-wrapper input.searchbar-input{width:100%;padding:.5rem .75rem .5rem 2.25rem;border:1px solid #e2e8f0;border-radius:.5rem;font-size:.9rem;background-color:#f8fafc;color:#1e293b;outline:none;transition:all .2s ease}.people-sidebar-header .searchbar-wrapper input.searchbar-input:focus{border-color:#3b82f6;background-color:#fff;box-shadow:0 0 0 3px rgba(59,130,246,.1)}.people-sidebar-header .segmented-control{display:flex;background-color:#f1f5f9;padding:3px;border-radius:.5rem;gap:4px}.people-sidebar-header .segmented-control button.segment-tab{flex:1;display:flex;align-items:center;justify-content:center;gap:.5rem;padding:.5rem .75rem;font-size:.9rem;font-weight:600;color:#64748b;background:rgba(0,0,0,0);border:none;border-radius:.375rem;cursor:pointer;box-shadow:none;transition:all .2s ease}.people-sidebar-header .segmented-control button.segment-tab:hover{color:#1e293b}.people-sidebar-header .segmented-control button.segment-tab.active{background-color:#fff;color:#0f172a;box-shadow:0 1px 3px rgba(0,0,0,.1),0 1px 2px rgba(0,0,0,.06)}.people-sidebar-header .segmented-control button.segment-tab.active .segment-badge{background-color:#019dff;color:#fff}.people-sidebar-header .segmented-control button.segment-tab .segment-badge{display:inline-flex;align-items:center;justify-content:center;background-color:#cbd5e1;color:#334155;font-size:.75rem;font-weight:700;min-width:1.25rem;height:1.25rem;padding:0 .35rem;border-radius:9999px;line-height:1;transition:all .2s ease}.people-sidebar-header .sub-filter-row{display:flex;align-items:center;justify-content:space-between;gap:.5rem;min-height:32px}.people-sidebar-header .sub-filter-row select.filter-select{padding:.35rem .6rem;border:1px solid #cbd5e1;border-radius:.375rem;font-size:.85rem;font-weight:600;color:#475569;background-color:#fff;cursor:pointer;outline:none}.people-sidebar-header .sub-filter-row .btn-add-id{display:flex;align-items:center;justify-content:center;width:32px;height:32px;border-radius:.375rem;background-color:#3b82f6;color:#fff;border:none;cursor:pointer;font-size:.9rem;transition:background-color .2s}.people-sidebar-header .sub-filter-row .btn-add-id:hover{background-color:#2563eb}.lobby{margin:10px;border:1px solid #aaa;border-radius:20px}.lobby .mainname{margin:20px;font-weight:100;font-size:1.2em}.topic{color:#666}.lobby>.topic{font-size:.95em;margin-left:25px;margin-bottom:5px}.lefttitle{margin-top:15px;margin-bottom:0;font-weight:100;font-size:1.2em}.leftname{margin-top:5px;margin-bottom:5px;padding:5px;font-weight:100;font-size:1em}.leftlobby>.topic{font-size:.75em;margin-left:15px;margin-bottom:5px}.subscribed,.public{cursor:pointer}.leftlobby{border:1px solid #aaa;border-radius:10px;margin-top:5px;background-color:#fff}.leftlobby.selected-lobby,.selectedidentity{color:#fff;background-color:#3ba4d7}.rightbar{position:absolute;width:185px;background-color:#fff;overflow:auto;top:130px;bottom:15px;right:15px}.user{padding:5px}.lobbyName{padding:15px;margin-top:2rem}.lobbies{position:absolute;width:185px;left:165px;bottom:15px;top:130px;overflow:auto}.messages,.setup{position:absolute;background-color:#fff;top:130px;left:360px;right:215px;overflow:auto}.messages{bottom:115px}.messagetext{white-space:break-spaces;margin-right:5px}.message>*{margin-left:5px}.username{color:#006400;font-weight:bolder}.chatMessage{position:absolute;background-color:#fff;height:85px;bottom:15px;right:215px;left:360px}textarea.chatMsg{height:100%;width:100%}.chatatchar{margin-left:.2em;margin-right:.2em;color:silver}.setupicon{margin-left:1em;cursor:pointer}.leaveicon{margin-left:1em;cursor:pointer;color:#d40000}.selectidentity{margin:15px;font-size:1.2em}.setup>.identity{cursor:pointer}.setup{bottom:15px}.createDistantChat{margin-top:1em}.no-lobbies .messages,.no-lobbies .chatMessage,.no-lobbies .setup{left:165px}@media(min-width: 900px){.node-panel.chat-room{display:grid !important;grid-template-columns:250px 1fr 200px !important;grid-template-rows:auto 1fr auto !important;grid-template-areas:"lobbies header rightbar" "lobbies messages rightbar" "lobbies input rightbar" !important;padding:0 !important;height:100% !important}.node-panel.chat-room .lobbyName{grid-area:header;padding:10px;border-bottom:1px solid #eee;margin:0;z-index:10;background:#fff}.node-panel.chat-room .lobbies{grid-area:lobbies;position:static !important;width:auto !important;height:auto !important;border-right:1px solid #ccc;overflow-y:auto;display:block !important;top:auto !important;bottom:auto !important;left:auto !important}.node-panel.chat-room .messages{grid-area:messages;position:static !important;width:auto !important;height:auto !important;overflow-y:auto;padding:10px;left:auto !important;right:auto !important;top:auto !important;bottom:auto !important;margin:0 !important}.node-panel.chat-room .rightbar{grid-area:rightbar;position:static !important;width:auto !important;border-left:1px solid #ccc;overflow-y:auto;display:block !important}.node-panel.chat-room .chatMessage{grid-area:input;position:static !important;width:auto !important;height:auto !important;border-top:1px solid #eee;left:auto !important;right:auto !important;bottom:auto !important;flex:0 0 auto;padding:10px !important;background:#fff;z-index:10}}@media(max-width: 899px){.node-panel.chat-room{display:flex !important;flex-direction:column !important;height:100% !important;position:relative !important}.node-panel.chat-room .lobbyName{flex:0 0 auto}.node-panel.chat-room .messages{flex:1 !important;overflow-y:auto !important;position:relative !important;top:0 !important;bottom:0 !important;left:0 !important;right:0 !important;width:100% !important;height:auto !important;margin:0 !important}.node-panel.chat-room .chatMessage{flex:0 0 auto !important;position:relative !important;bottom:0 !important;left:0 !important;right:0 !important;width:100% !important;height:auto !important;z-index:100}.node-panel.chat-room .rightbar,.node-panel.chat-room .lobbies{display:none !important;position:fixed !important;top:60px !important;bottom:0 !important;width:80% !important;background:#fff !important;z-index:200 !important;box-shadow:2px 0 10px rgba(0,0,0,.2) !important}.node-panel.chat-room.show-lobbies .lobbies{display:block !important;left:0 !important}.node-panel.chat-room.show-users .rightbar{display:block !important;right:0 !important}.chat-overlay{display:none;position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,.4);z-index:150}.show-lobbies .chat-overlay,.show-users .chat-overlay{display:block}.mobile-menu-icons{display:flex;gap:15px;font-size:1.2rem}.mobile-menu-icons i{cursor:pointer;padding:5px}}@media(min-width: 900px){.mobile-menu-icons{display:none}}.chat-hub-container{display:flex;height:100%;width:100%;overflow:hidden;background-color:#f1f5f9}.chat-hub-left-pane{width:320px;min-width:300px;max-width:350px;border-right:1px solid #cbd5e1;display:flex;flex-direction:column;background:#fff;box-shadow:2px 0 5px rgba(0,0,0,.05)}.chat-own-profile-card{padding:1.25rem;border-bottom:1px solid #e2e8f0;background:linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%)}.chat-own-profile-card .profile-header{display:flex;align-items:center;gap:1rem}.chat-own-profile-card .profile-info{display:flex;flex-direction:column;flex:1;overflow:hidden}.chat-own-profile-card .profile-info .profile-name{font-weight:700;color:#1e293b;font-size:1.1rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.chat-own-profile-card .profile-info .profile-status{font-size:.85rem;color:#10b981;font-weight:500;display:flex;align-items:center;gap:.35rem}.chat-own-profile-card .profile-info .profile-status::before{content:"";display:inline-block;width:8px;height:8px;background-color:#10b981;border-radius:50%}.chat-rooms-list-container{flex:1;display:flex;flex-direction:column;overflow:hidden}.chat-rooms-list-container .searchbar-container{padding:.75rem 1rem;border-bottom:1px solid #e2e8f0}.chat-rooms-list-container .searchbar-container input.searchbar{width:100%;padding:.5rem .75rem;font-size:.9rem;border:1px solid #cbd5e1;border-radius:.375rem;background-color:#f8fafc;outline:none;transition:all .2s}.chat-rooms-list-container .searchbar-container input.searchbar:focus{background-color:#fff;border-color:#3ba4d7;box-shadow:0 0 0 3px rgba(59,164,215,.15)}.chat-rooms-list-container .rooms-scroll{flex:1;overflow-y:auto;padding:.5rem 0}.rooms-section-title{display:flex;align-items:center;gap:.5rem;padding:.75rem 1rem .375rem;font-size:.75rem;font-weight:700;color:#64748b;text-transform:uppercase;letter-spacing:.05em}.rooms-section-title i{font-size:.7rem;color:#94a3b8}.chat-room-list-item{display:flex;align-items:center;gap:.75rem;padding:.75rem 1rem;margin:.125rem .5rem;border-radius:.5rem;cursor:pointer;transition:all .2s}.chat-room-list-item:hover{background-color:#f1f5f9}.chat-room-list-item.selected{background-color:#e0f2fe}.chat-room-list-item.selected .room-name{color:#0369a1;font-weight:600}.chat-room-list-item .room-icon{flex-shrink:0;width:36px;height:36px;border-radius:.5rem;background:linear-gradient(135deg, #3ba4d7, #0ea5e9);display:flex;align-items:center;justify-content:center;color:#fff;font-size:1.35rem}.chat-room-list-item.public-room .room-icon{background:linear-gradient(135deg, #10b981, #059669)}.chat-room-list-item .room-meta{flex:1;min-width:0}.chat-room-list-item .room-meta .room-name{font-size:.95rem;color:#334155;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;transition:color .2s}.chat-room-list-item .room-meta .room-topic{font-size:.8rem;color:#94a3b8;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.chat-room-list-item .room-badge{flex-shrink:0;min-width:24px;height:24px;border-radius:12px;background-color:#e2e8f0;color:#475569;font-size:.75rem;font-weight:700;display:flex;align-items:center;justify-content:center;padding:0 .375rem}.chat-hub-right-pane{flex:1;display:flex;flex-direction:column;overflow:hidden;background-color:#f8fafc}.chat-pane-placeholder{flex:1;display:flex;flex-direction:column;justify-content:center;align-items:center;color:#94a3b8;gap:1rem;padding:2rem;text-align:center}.chat-pane-placeholder i{font-size:4rem;color:#cbd5e1}.chat-pane-placeholder p{font-size:1.1rem;max-width:400px}.chat-hub-tab-content{flex:1;overflow-y:auto;padding:1.5rem}.chat-room-detail-view{display:flex;flex-direction:column;gap:1.5rem}.chat-room-detail-view .detail-header{display:flex;align-items:flex-start;gap:1.5rem;padding-bottom:1.5rem;border-bottom:1px solid #e2e8f0;flex-wrap:wrap}.chat-room-detail-view .detail-header .detail-title{flex:1;min-width:200px}.chat-room-detail-view .detail-header .detail-title h2{font-size:1.75rem;font-weight:800;color:#1e293b;margin-bottom:.25rem}.chat-room-detail-view .detail-header .detail-title .detail-subtitle{font-size:.9rem;color:#64748b;display:flex;align-items:center;gap:.5rem}.chat-room-detail-view .detail-header .detail-actions{display:flex;gap:.75rem;flex-wrap:wrap}.chat-room-detail-view .detail-header .detail-actions button{display:flex;align-items:center;gap:.5rem;padding:.5rem 1rem;font-size:.9rem}.chat-room-detail-view .detail-section{background-color:#fff;border-radius:.5rem;border:1px solid #e2e8f0;padding:1.25rem;box-shadow:0 1px 3px rgba(0,0,0,.02)}.chat-room-detail-view .detail-section h3{font-size:1.1rem;font-weight:700;color:#334155;margin-bottom:1rem;padding-bottom:.5rem;border-bottom:1px solid #f1f5f9}.chat-room-detail-view .detail-section .info-grid{display:grid;grid-template-columns:130px 1fr;row-gap:.75rem;font-size:.9rem}.chat-room-detail-view .detail-section .info-grid .info-label{font-weight:600;color:#64748b}.chat-room-detail-view .detail-section .info-grid .info-value{color:#1e293b;word-break:break-all}.participants-grid{display:grid;grid-template-columns:repeat(auto-fill, minmax(180px, 1fr));gap:.5rem}.participant-card{display:flex;align-items:center;gap:.5rem;padding:.5rem .75rem;background-color:#f8fafc;border:1px solid #e2e8f0;border-radius:.375rem}.participant-card .participant-name{font-size:.875rem;color:#334155;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.no-participants{color:#94a3b8;font-size:.9rem;font-style:italic}.detail-actions-footer{display:flex;gap:.75rem}.detail-actions-footer button{display:flex;align-items:center;gap:.5rem;padding:.5rem 1rem;font-size:.9rem}.join-description{color:#64748b;font-size:.9rem;margin-bottom:1rem}.identities-grid{display:grid;grid-template-columns:repeat(auto-fill, minmax(200px, 1fr));gap:.75rem}.identity-card{display:flex;align-items:center;justify-content:space-between;padding:.75rem 1rem;background-color:#f8fafc;border:1px solid #e2e8f0;border-radius:.5rem;cursor:pointer;transition:all .2s}.identity-card:hover{background-color:#e0f2fe;border-color:#3ba4d7}.identity-card .identity-name{font-size:.95rem;font-weight:600;color:#334155}.identity-card i{color:#3ba4d7;font-size:.9rem}.no-rooms{padding:1rem;color:#94a3b8;text-align:center;font-style:italic}@media(max-width: 899px){.chat-hub-container{flex-direction:column}.chat-hub-left-pane{width:100%;min-width:0;max-width:none;max-height:45%;border-right:none;border-bottom:1px solid #cbd5e1}.chat-hub-right-pane{flex:1;min-height:0}}.chat-hub-header-bar{padding:.75rem 1.5rem;background-color:#fff;border-bottom:1px solid #e2e8f0;display:flex;align-items:center;justify-content:space-between;height:65px;flex-shrink:0}.chat-hub-header-bar .chat-header-info{display:flex;flex-direction:column;overflow:hidden}.chat-hub-header-bar .chat-header-info .chat-header-name{font-size:1.15rem;font-weight:800;color:#1e293b;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.chat-hub-header-bar .chat-header-info .chat-header-topic{font-size:.85rem;color:#64748b;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-top:.125rem}.chat-hub-header-bar .chat-header-actions{display:flex;gap:.5rem}.chat-hub-header-bar .chat-header-actions button{display:flex;align-items:center;gap:.35rem;padding:.375rem .75rem;font-size:.85rem}.chat-hub-tabs-container{background-color:#fff;border-bottom:1px solid #cbd5e1;padding:.5rem 1.5rem 0}.chat-hub-tabs{display:flex;gap:.5rem}.chat-hub-tabs .tab-btn{padding:.625rem 1.25rem;font-size:.95rem;font-weight:600;color:#64748b;background:rgba(0,0,0,0);border:none;border-radius:.375rem .375rem 0 0;border-bottom:3px solid rgba(0,0,0,0);cursor:pointer;box-shadow:none;transition:all .2s;display:flex;align-items:center;gap:.5rem}.chat-hub-tabs .tab-btn:hover{color:#334155;background-color:#f1f5f9}.chat-hub-tabs .tab-btn.active{color:#3ba4d7;border-bottom-color:#3ba4d7;background-color:rgba(0,0,0,0)}.chat-hub-tab-content{flex:1;display:flex;flex-direction:column;overflow:hidden;background-color:#f8fafc}.chat-hub-conversation-layout{display:flex;flex-direction:row;height:100%;width:100%;overflow:hidden}.chat-hub-conversation-main{display:flex;flex-direction:column;flex:1;height:100%;overflow:hidden}.chat-hub-rightbar{width:200px;border-left:1px solid #cbd5e1;background-color:#fff;display:flex;flex-direction:column;flex-shrink:0}.chat-hub-rightbar .rightbar-title{padding:.75rem 1rem;font-size:.85rem;font-weight:700;color:#64748b;text-transform:uppercase;letter-spacing:.05em;border-bottom:1px solid #e2e8f0}.chat-hub-rightbar .rightbar-users-list{flex:1;overflow-y:auto;padding:.5rem}.chat-hub-rightbar .user{padding:.5rem .75rem;font-size:.9rem;color:#334155;border-radius:.375rem;transition:all .2s;display:flex;align-items:center;gap:.5rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.chat-hub-rightbar .user:hover{background-color:#f1f5f9;color:#0f172a}.chat-hub-rightbar .user .defaultAvatar{width:2rem;height:2rem;font-size:.9rem;flex-shrink:0}.chat-hub-rightbar .user img.avatar{width:2rem;height:2rem;flex-shrink:0}@media(max-width: 899px){.chat-hub-rightbar{display:none}}.chat-hub-messages{flex:1;overflow-y:auto;padding:1.25rem 1.5rem;display:flex;flex-direction:column;gap:1rem}.chat-hub-messages .message{display:flex;flex-direction:column;max-width:70%;padding:.625rem .875rem;border-radius:.75rem;font-size:.925rem;line-height:1.4;word-break:break-word;box-shadow:0 1px 2px rgba(0,0,0,.05)}.chat-hub-messages .message.incoming{align-self:flex-start;align-items:flex-start;background-color:#fff;color:#1e293b;border:1px solid #e2e8f0;border-bottom-left-radius:.125rem}.chat-hub-messages .message.outgoing{align-self:flex-end;align-items:flex-end;background-color:#3ba4d7;color:#fff;border-bottom-right-radius:.125rem}.chat-hub-messages .message .username{font-size:.75rem;margin-bottom:.25rem;padding:0 .125rem;font-weight:700}.chat-hub-messages .message.incoming .username{color:#0369a1}.chat-hub-messages .message.outgoing .username{color:#e0f2fe}.chat-hub-messages .message .messagetext{white-space:break-spaces;margin:0}.chat-hub-messages .message .datetime{font-size:.7rem;margin-top:.25rem;padding:0 .125rem;opacity:.8}.chat-hub-messages .message.incoming .datetime{color:#64748b}.chat-hub-messages .message.outgoing .datetime{color:#f1f5f9}.chat-hub-input-area{padding:.75rem 1.5rem;background-color:#fff;border-top:1px solid #cbd5e1;display:flex;gap:.75rem;align-items:flex-end;flex-shrink:0}.chat-hub-input-area textarea.chat-hub-textarea{flex:1;resize:vertical;min-height:40px;max-height:250px;height:40px;padding:.5rem .75rem;border:1px solid #cbd5e1;border-radius:.375rem;font-size:.9rem;outline:none;transition:border-color .2s,box-shadow .2s;background-color:#f8fafc}.chat-hub-input-area textarea.chat-hub-textarea:focus{background-color:#fff;border-color:#3ba4d7;box-shadow:0 0 0 3px rgba(59,164,215,.15)}.chat-hub-input-area button.chat-hub-send-btn{padding:.5rem 1.25rem;font-size:.9rem;height:40px;display:flex;align-items:center;gap:.5rem;border-radius:.375rem}.chat-hub-messages.compact-container,.messages.compact-container{gap:0 !important;padding:.75rem 1rem !important;background-color:#fff !important;display:flex !important;flex-direction:column !important}.chat-hub-messages.compact-container .message.compact,.messages.compact-container .message.compact{display:block !important;max-width:100% !important;padding:.1rem 0 !important;border-radius:0 !important;background-color:rgba(0,0,0,0) !important;border:none !important;box-shadow:none !important;align-self:flex-start !important;font-size:.875rem !important;line-height:1.45 !important;margin:0 !important;white-space:nowrap !important}.chat-hub-messages.compact-container .message.compact:hover,.messages.compact-container .message.compact:hover{background-color:#f8fafc !important;overflow:visible !important;white-space:normal !important}.chat-hub-messages.compact-container .message.compact .datetime,.messages.compact-container .message.compact .datetime{color:#a0a0a0 !important;margin-right:.4rem !important;font-size:.78rem !important;font-family:monospace !important;opacity:1 !important;display:inline !important}.chat-hub-messages.compact-container .message.compact .username,.messages.compact-container .message.compact .username{font-weight:bold !important;margin-right:.2rem !important;font-size:.875rem !important;display:inline !important}.chat-hub-messages.compact-container .message.compact .messagetext,.messages.compact-container .message.compact .messagetext{color:#1e293b !important;white-space:normal !important;word-break:break-word !important;display:inline !important;margin:0 !important}.side-bar{display:flex;flex-direction:column;background:#fff}.side-bar .mail-compose-btn{width:96%;margin:.25rem;padding:.75rem 0}.compose-mail__from{display:flex;justify-content:flex-start;align-items:center;gap:.5rem;padding-bottom:.5rem;border-bottom:2px solid #eef3f6}.compose-mail__recipients{padding:.5rem 0;display:flex;flex-direction:column;gap:.5rem;border-bottom:2px solid #eef3f6}.compose-mail__recipients__container{display:flex;gap:.5rem}.compose-mail__recipients__container>label{text-transform:capitalize}.compose-mail__recipients__container .recipients{width:100%;display:flex;gap:.5rem;flex-wrap:wrap}.compose-mail__recipients__container .recipients__selected{padding:.125rem .5rem;display:flex;align-items:center;gap:.5rem;border:1px solid #eef3f6;border-radius:3px;cursor:default}.compose-mail__recipients__container .recipients__selected i{cursor:pointer;padding:.25rem}.compose-mail__recipients__container .recipients__input{display:flex;position:relative;flex-grow:1}.compose-mail__recipients__container .recipients__input-field{flex-grow:1;min-width:200px;padding:0;border:none;box-shadow:none}.compose-mail__recipients__container .recipients__input-field:focus+.recipients__input-list{display:flex}.compose-mail__recipients__container .recipients__input-list{z-index:1;position:absolute;top:1rem;padding:0;width:100%;max-height:15rem;flex-direction:column;overflow:auto;display:none;background:#fff;border-top:1px solid #eef3f6;border-bottom:1px solid #eef3f6}.compose-mail__recipients__container .recipients__input-list:hover{display:flex}.compose-mail__recipients__container .recipients__input-list li{list-style:none;padding:.25rem .5rem;cursor:pointer;background:#fff;border:1px solid #eef3f6;border-top:0px}.compose-mail__recipients__container .recipients__input-list li:hover{background:#eef3f6}.compose-mail__recipients__container .recipients__input-list li:last-child{border-bottom:0px}.compose-mail__recipients .remove-recipient{padding:.125rem .5rem}.compose-mail input[type=text].compose-mail__subject{padding:.5rem 0;border:none;box-shadow:none;border-bottom:2px solid #eef3f6;border-radius:0}.compose-mail__message{margin:.5rem 0;height:100%;display:flex;flex-direction:column;overflow:auto}.compose-mail__message-body{height:100%;outline:rgba(0,0,0,0)}.compose-mail__send-btn{display:flex;align-items:center;gap:.5rem}.compose-mail__send-btn i{transform:translateY(-1px)}.msg-view{height:100%;display:flex;flex-direction:column;gap:1rem;overflow:auto}.msg-view-nav{display:flex;justify-content:space-between;align-items:column}.msg-view-nav__action{display:flex;gap:.5rem}.msg-view__header{display:flex;flex-direction:column;gap:1rem}.msg-view__header>h3{line-height:1}.msg-view__header .msg-details{display:flex;gap:1rem}.msg-view__header .msg-details__avatar{height:max-content}.msg-view__header .msg-details__info{display:flex;flex-direction:column}.msg-view__header .msg-details__info-item{display:flex;gap:.5rem}.msg-view__body{height:100%;overflow:auto;font-size:14px !important}.msg-view__attachment{height:50%;overflow:auto;display:flex;flex-direction:column}.msg-view__attachment-items{height:100%;overflow:auto}.mail-tag{width:8rem;padding:.5rem}.msgHeader{display:flex}.msgHeaderDetails{display:flex;flex-direction:column}table.mails th:nth-child(1){width:5%;color:#fcba03}table.mails th:nth-child(2){width:5%;color:hsl(202.5,30.7692307692%,44.9019607843%)}table.mails th:nth-child(3){width:50%;text-align:start}table.mails th:nth-child(4),table.mails th:nth-child(5){width:20%;text-align:start}table.mails td:nth-child(3){text-align:start;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}table.mails td:nth-child(4),table.mails td:nth-child(5){text-align:start}table.mails tr:hover{background-color:#eef3f6;cursor:pointer}table.mails tr.unread{color:#000;background-color:#eef3f6}table.mails>tr:hover{cursor:auto;background-color:#fff}table.mails th.sortable-th{cursor:pointer;user-select:none;transition:background-color .2s,color .2s}table.mails th.sortable-th:hover{background-color:#eef3f6;color:hsl(202.5,30.7692307692%,14.9019607843%)}input.star-check{display:none}input.star-check+label.star-check{color:gray}input.star-check:checked+label.star-check{color:#fcba03}#truncate{height:6rem;overflow:auto}#truncate.truncated-view{height:1.75rem;overflow:hidden}.toggle-truncate{font-size:.75rem;padding:0 .25rem;background:#999;color:#14141b;box-shadow:none;border-radius:2px}table.attachment-container{padding:0}table.attachment-container>tr{border:0}table.attachment-container .attachment-header{width:100%;display:flex;justify-content:space-between}table.attachment-container .attachment-header th{text-align:start}table.attachment-container .attachment-header th:nth-child(1){flex-basis:45%}table.attachment-container .attachment-header th:nth-child(2){flex-basis:15%}table.attachment-container .attachment-header th:nth-child(3){flex-basis:10%}table.attachment-container .attachment-header th:nth-child(4){flex-basis:20%}table.attachment-container .attachment-header th:nth-child(5){text-align:center;flex-basis:10%}table.attachment-container .attachment{width:100%;display:flex;justify-content:space-between;text-align:start}table.attachment-container .attachment__name{flex-basis:45%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}table.attachment-container .attachment__name span{margin-left:8px}table.attachment-container .attachment__from{flex-basis:15%}table.attachment-container .attachment__size{flex-basis:10%}table.attachment-container .attachment__date{flex-basis:20%}table.attachment-container .attachment td:nth-child(5){display:flex;justify-content:center;align-items:center;flex-basis:10%}table.attachment-container .attachment td:nth-child(5) button{font-size:.875rem}.view-toggle{height:max-content;border:1px solid #019dff;border-radius:4px;display:flex}.view-toggle *{padding:4px 12px;border-radius:4px}.composePopupOverlay{position:fixed;width:100%;height:100%;top:0;left:0;z-index:1;background-color:rgba(0,0,0,.2)}.composePopupOverlay .composePopup{position:absolute;inset:0;margin:auto;width:80%;height:90%}.composePopupOverlay .composePopup>.widget{padding:2rem}.composePopupOverlay .composePopup .close-btn{position:absolute;top:1.5rem;right:1.5rem}.file-view{width:100%;padding:1rem;margin-top:1.5rem;border-radius:8px;border:1px solid #ccc;animation:fadein .5s}.file-view__heading{display:flex;justify-content:space-between;margin-bottom:.5rem}.file-view__heading-chunk{display:flex;gap:1rem}.file-view__body{display:flex;flex-direction:column;gap:1rem}.file-view__body-details{display:flex;align-items:center}.file-view__body-details-stat{width:100%;display:grid;grid-template-columns:repeat(5, 1fr)}.file-view__body-details-stat span>i{margin-right:.5rem}.file-view__body-details-action{display:flex;gap:1rem;height:100%}.file-view__body-details-action button,.file-view__body-details-action button.red{padding:.25rem .75rem}table.myfiles td{word-wrap:break-word}table.myfiles th:nth-child(1){width:2%}table.myfiles th:nth-child(2){width:50%}table.myfiles td:nth-child(2){text-align:start}table.friendsfiles td{word-wrap:break-word}table.friendsfiles th:nth-child(1){width:2%}table.friendsfiles th:nth-child(2){width:50%}table.friendsfiles th:nth-child(4){width:40%}table.friendsfiles td:nth-child(2){text-align:start}.file-search-container{margin-top:1rem;padding:8px;display:flex;gap:8px;border:1px solid rgba(20,20,27,.2);border-radius:6px;height:100%;overflow:auto}.file-search-container__keywords{flex-basis:15%;padding-right:.25rem;border-right:1px solid rgba(20,20,27,.1)}.file-search-container__keywords .keywords-container{display:flex;flex-direction:column;border-top:2.5px solid rgba(20,20,27,.08);margin-top:.125rem;padding-top:.25rem}.file-search-container__keywords .keywords-container a{font-size:1.2rem;text-decoration:none;color:#14141b}.file-search-container__keywords .keywords-container a.selected{color:#019dff}.file-search-container__results{flex-basis:85%;height:100%;overflow:auto}.file-search-container__results .results-container .results-header tr{display:flex}.file-search-container__results .results-container .results-header tr th{font-size:1.25rem;font-weight:bold;text-align:left}.file-search-container__results .results-container .results-header tr th:nth-child(1){flex-basis:40%}.file-search-container__results .results-container .results-header tr th:nth-child(2){flex-basis:10%;text-align:center}.file-search-container__results .results-container .results-header tr th:nth-child(3){flex-basis:40%}.file-search-container__results .results-container .results-header tr th:nth-child(4){flex-basis:10%}.file-search-container__results .results-container .results{height:100%;overflow:auto}.file-search-container__results .results-container .results tr{display:flex}.file-search-container__results .results-container .results tr .results__hash,.file-search-container__results .results-container .results tr .results__name{text-align:left;flex-basis:40%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.file-search-container__results .results-container .results tr .results__hash span,.file-search-container__results .results-container .results tr .results__name span{margin-left:8px}.file-search-container__results .results-container .results tr .results__size{flex-basis:10%}.file-search-container__results .results-container .results tr .results__download{flex-basis:10%;display:flex;justify-content:start;align-items:center}.search-form{display:flex;width:40%}.search-form input{width:100%}.search-form button{margin-left:.5rem}.shareManagerPopupOverlay{position:fixed;width:100%;height:100%;top:0;left:0;z-index:1;background-color:rgba(0,0,0,.2)}.shareManagerPopupOverlay .shareManagerPopup{position:absolute;inset:0;margin:auto;width:80%;height:90%}.shareManagerPopupOverlay .shareManagerPopup>.widget{padding:1.5rem}.shareManagerPopupOverlay .shareManagerPopup .close-btn{position:absolute;top:1.5rem;right:1.5rem}.share-manager{display:flex;flex-direction:column;justify-content:space-between}.share-manager__table{margin:1rem 0 auto}.share-manager__table thead{font-weight:bold;text-align:left}.share-manager__table thead td:nth-child(1),.share-manager__table thead td:nth-child(2){padding-left:.5rem}.share-manager__table thead td:nth-child(3) .tooltip,.share-manager__table thead td:nth-child(4) .tooltip{font-weight:normal;font-size:1rem}.share-manager__table tbody{text-align:left}.share-manager__table tbody td:nth-child(4){font-size:1rem}.share-manager__table td input{border:0 !important}.share-manager__table td input[type=text]{width:100%}.share-manager__table td:nth-child(1){width:45%}.share-manager__table td:nth-child(2){width:20%}.share-manager__table td:nth-child(3){width:10%}.share-manager__table td:nth-child(4){width:25%}.share-manager__actions{display:flex;justify-content:space-between}.share-manager__form{display:flex;flex-direction:column;gap:.5rem}.share-manager__form_input{display:flex;flex-direction:column;gap:.5rem}.share-manager__form_input input{flex-grow:1}.share-manager .share-flags input.share-flags-check{display:none}.share-manager .share-flags input.share-flags-check+label.share-flags-label{color:gray;margin-right:.25rem;padding:.25rem .25rem .125rem;border:1px solid #6d6d6d;border-radius:.5rem}.share-manager .share-flags input.share-flags-check:checked+label.share-flags-label{color:#118fcc}.share-manager label span{display:inline-block;width:1.125rem}.manage-visibility label{width:100%;cursor:pointer}.manage-visibility{display:flex;justify-content:space-between}@media(max-width: 700px){.file-view__body-details{flex-direction:column;align-items:flex-start;gap:1rem}.file-view__body-details-stat{grid-template-columns:1fr;gap:.5rem}.file-view__body-details-stat span{display:flex;align-items:center}.share-manager__table,.share-manager__table thead,.share-manager__table tbody,.share-manager__table tr,.share-manager__table td{display:block;width:100% !important}.share-manager__table thead{display:none}.share-manager__table tr{border:1px solid #ccc;border-radius:8px;margin-bottom:1rem;padding:.5rem;background:#fff}.share-manager__table td{margin-bottom:.5rem;border:none !important;padding-left:0 !important}table.myfiles,table.myfiles tr,table.myfiles td,table.friendsfiles,table.friendsfiles tr,table.friendsfiles td{display:block;width:100% !important}table.myfiles th,table.friendsfiles th{display:none}table.myfiles tr,table.friendsfiles tr{border:1px solid #ccc;border-radius:8px;margin-bottom:1rem;padding:.5rem;background:#fff}.file-search-container{flex-direction:column}.file-search-container__keywords{flex-basis:auto;width:100%;border-right:none;border-bottom:1px solid rgba(20,20,27,.1);padding-bottom:1rem;margin-bottom:1rem}.results-container,.results-container thead,.results-container tbody,.results-container tr,.results-container td{display:block;width:100% !important}.results-container thead{display:none}.results-container tr{border-bottom:1px solid #eee;padding:1rem 0}.results-container td{margin-bottom:.5rem;word-break:break-all}}.file-section{margin-top:2rem;display:flex;flex-direction:column}.comments-section{margin-top:2rem;display:flex;justify-content:space-between}.comments-section__menu{display:flex;gap:1rem}.comments-section__menu-id{display:flex;align-items:center;gap:.25rem}#toggleunsub{position:relative;background:gray}table.channels th:nth-child(1){width:50%;text-align:start}table.channels td:nth-child(1){text-align:start;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}table.channels tr:hover{background-color:#eef3f6;cursor:pointer}table.channels tr.hidden{display:none}table{padding:.5rem}table.comments{border:1px solid #eee}table.comments th{height:40px}table.comments th:nth-child(1){width:2%}table.comments th:nth-child(2){width:40%}table.comments td{word-wrap:break-word}table.comments td:nth-child(2){text-align:start}table.files th:first-child{text-align:start;width:60%}table.files tr td:first-child{text-align:start}table.files td{word-wrap:break-word}#mtags{width:160px;text-align:center;font-size:medium;margin-left:10px;height:40px}.forums-node-panel{position:relative;bottom:200px;margin-left:200px;animation:fadein .5s}table.forums th:nth-child(1){width:50%;text-align:start}table.forums td:nth-child(1){text-align:start;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}table.forums tr:hover{background-color:#eef3f6;cursor:pointer}table.forums tr.hidden{display:none}#searchforum{position:relative;margin-left:250px}#forumdetails{position:relative;padding:10px}.p{margin:0}#toggleunsub{position:relative;background:gray}table.threads tr:hover{background-color:#eef3f6;cursor:pointer}table.threads td{word-wrap:break-word}table.threadreply th:nth-child(2){width:50%}table.threadreply th:nth-child(1){width:2%}table.threadreply td:nth-child(2){width:50%;text-align:start}table.threadreply td{word-wrap:break-word}table.threadreply tr:hover{background-color:#eef3f6;cursor:pointer}table.boards th:nth-child(1){width:50%;text-align:start}table.boards td:nth-child(1){text-align:start;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}table.boards tr:hover{background-color:#eef3f6;cursor:pointer}table.boards tr.hidden{display:none}#toggleunsub{position:relative;background:gray}#options{width:100px;text-align:center;font-size:medium;margin-left:20px;height:40px}#composepopup{height:80%;width:70%}#mtags{width:160px;text-align:center;font-size:medium;margin-left:10px;height:40px}.mail .permission-flag{margin-bottom:1rem;display:flex;gap:1rem}.mail-tags{padding:.5rem;border:1px solid rgba(20,20,27,.2);border-radius:6px}.mail-tags__container{display:flex;flex-direction:column}.mail-tags__container .tag-item{display:flex;align-items:center;gap:4px;border-bottom:1px solid rgba(20,20,27,.1);padding:2px 0}.mail-tags__container .tag-item:last-child{border:none}.mail-tags__container .tag-item__color{width:1.25rem;height:1.25rem;aspect-ratio:1}.mail-tags__container .tag-item__name{font-size:1.125rem}.mail-tags__container .tag-item__modify{margin-left:auto;font-size:.75rem;display:flex;gap:4px}.mail-tags__container .tag-item:hover{background-color:#eef3f6}.mail-tags__container .tag-item button,.mail-tags__container .tag-item button.red{padding:.25rem .6rem}.mail-tags-form .input-field{margin-bottom:.5rem}.mail-tags-form .input-field label{margin-right:.5rem}.external-address{margin:0;padding-left:1rem;height:100px;overflow:hidden auto}.external-address::-webkit-scrollbar{display:none}.proxy-server{display:flex;flex-direction:column;gap:4px}.proxy-server__tor>h4,.proxy-server__i2p>h4{margin-bottom:.25rem}.proxy-server__tor>input,.proxy-server__i2p>input{margin-right:.5rem}.proxy-server__tor .proxy-outgoing,.proxy-server__i2p .proxy-outgoing{display:inline-flex;align-items:center;gap:.5rem}.proxy-server__tor .proxy-outgoing__status,.proxy-server__i2p .proxy-outgoing__status{width:1rem;height:1rem;aspect-ratio:1;border:1px solid #000;border-radius:50%}.config-files{display:flex;flex-direction:column;gap:1rem}.proxy-server-container{width:100%;display:flex;flex-direction:column;gap:1rem}.proxy-description{color:#334155;font-size:.95rem;margin-bottom:.5rem}.proxy-rows-container{display:flex;flex-direction:column;gap:.75rem;width:100%}.proxy-row{display:grid;grid-template-columns:160px 220px 220px auto;gap:.75rem;align-items:center;width:100%}.proxy-label{font-size:.95rem;font-weight:500;color:#1e293b}.proxy-addr-input,.proxy-port-input{width:100% !important;max-width:none !important}.proxy-status-container{display:flex;align-items:center;gap:.5rem}.proxy-status-bullet{width:14px;height:14px;border-radius:50%;display:inline-block;border:1px solid #475569}.proxy-status-text{font-size:.95rem;color:#1e293b} + */@font-face{font-family:"Font Awesome 5 Free";font-style:normal;font-weight:900;font-display:auto;src:url("./webfonts/fa-solid-900.eot");src:url("./webfonts/fa-solid-900.eot?#iefix") format("embedded-opentype"),url("./webfonts/fa-solid-900.woff2") format("woff2"),url("./webfonts/fa-solid-900.woff") format("woff"),url("./webfonts/fa-solid-900.ttf") format("truetype"),url("./webfonts/fa-solid-900.svg#fontawesome") format("svg")}.fa,.fas{font-family:"Font Awesome 5 Free";font-weight:900}html{font-size:87.5%;box-sizing:border-box}*,*::before,*::after{box-sizing:inherit}body,h1,h2,h3,h4,h5,h6,p,figure,blockquote,dl,dd{margin:0;padding:0}ul[role=list],ol[role=list]{list-style:none}html:focus-within{scroll-behavior:smooth}body{text-rendering:optimizeSpeed;line-height:1.5;font-family:"Roboto",Arial,Helvetica,sans-serif !important;letter-spacing:-0.025ch}a:not([class]){text-decoration-skip-ink:auto}img,picture{max-width:100%;display:block}input,button,textarea,select{font:inherit}@media(prefers-reduced-motion: reduce){html:focus-within{scroll-behavior:auto}*,*::before,*::after{animation-duration:.01ms !important;animation-iteration-count:1 !important;transition-duration:.01ms !important;scroll-behavior:auto !important}}#main{height:100vh}.content{display:flex;height:100%;overflow:hidden}.tab-content{display:flex;height:100%;width:100%;background-color:#eef3f6;animation:fadein .3s;overflow:auto}input[type=text],input[type=password],input[type=number],textarea{box-sizing:border-box;background:#fff;max-width:100%;font-size:1rem;font-weight:400;border:1px solid #ccc;border-radius:.25rem;padding:.25rem .5rem;outline:rgba(0,0,0,0)}input:focus{border:1px solid #3ba4d7;box-shadow:inset 0 0 5px #ccc}input.stretched{width:90%}input.small{max-width:70%;padding:.1rem}input.searchbar{width:40%}a{cursor:pointer}a[title=Back]{width:max-content;height:max-content;padding:.475rem .75rem;border-radius:50%;transition:100ms}a[title=Back]:hover{background:#eef3f6}table{padding:20px;table-layout:fixed;width:100%;border-collapse:collapse;text-align:center;color:#333;font-size:1.125rem}table th{font-size:1.125rem;color:#000;border-bottom:2px solid #eee}table tr{border-bottom:1px solid #eee}h3{color:#444}hr{margin-left:0;color:#aaa}.grid-2col{display:grid;grid-template-columns:auto auto;gap:1rem;justify-content:start}.grid-2col input[type=checkbox]{margin-top:20px}.error{color:red}.tooltip{color:#333;position:relative;display:inline-block;margin:0 .25rem}.tooltiptext{visibility:hidden;position:absolute;top:100%;left:50%;min-width:250px;margin-left:-120px;z-index:1;color:#ccc;background-color:#333;font-size:.875rem;text-align:center;padding:.25rem;border-radius:.5rem}.tooltip:hover .tooltiptext{visibility:visible;animation:fadein .5s}blockquote{color:#14141b;padding:.75rem 1rem .75rem 2rem;border-radius:.25rem}blockquote.info{position:relative;line-height:1.2;color:rgba(20,20,27,.8);border:1px solid rgba(17,143,204,.8)}blockquote.info::before{font-family:"Font Awesome 5 Free";position:absolute;top:.5rem;left:.5rem;content:"";color:#019dff}@keyframes fadein{from{opacity:0}to{opacity:1}}.fadein{animation:fadein .5s}@keyframes swipe-from-left{from{margin-left:100%}to{margin-left:0}}button{width:max-content;height:max-content;color:#fff;background:#019dff;font-size:1rem;padding:.4rem 1rem;border:0;border-radius:5px;cursor:pointer;box-shadow:inset -3px -3px 0 rgb(0,94.5826771654,154)}button:active{outline:none;box-shadow:inset 3px 3px 0 rgb(0,94.5826771654,154)}button.red{width:max-content;height:max-content;color:#fff;background:#ff3a4a;font-size:1rem;padding:.4rem 1rem;border:0;border-radius:5px;cursor:pointer;box-shadow:inset -3px -3px 0 rgb(211,0,17.1370558376)}button.red:active{outline:none;box-shadow:inset 3px 3px 0 rgb(211,0,17.1370558376)}.media-item{display:flex;margin-top:.5rem;padding:1rem;border:1px solid rgba(20,20,27,.1);border-radius:4px}.media-item__details{flex-basis:40%;display:flex;align-items:start;gap:.5rem}.media-item__details img{width:6rem;object-fit:contain}.media-item__desc{flex-basis:60%}.active-link{background:hsla(0,0%,100%,.1) !important}.nav-menu{background-color:#14141b;box-shadow:0 5px 5px #222;display:flex;flex-direction:column;align-items:center;height:100%;padding:.25rem;margin-right:0rem}.nav-menu__logo{padding:1.2rem 0;display:flex;align-items:center;gap:.3rem}.nav-menu__logo img{width:1.6rem}.nav-menu__logo h5{line-height:1;color:#fff}.nav-menu__box{padding:2rem .125rem;display:flex;flex-direction:column;gap:.5rem;position:relative}.nav-menu__box .item{margin:0;padding:.675rem .5rem;width:10rem;display:flex;align-items:center;line-height:1;border-radius:.5rem;text-decoration:none;color:#ccc;text-transform:capitalize;transition:0ms}.nav-menu__box .item:hover{background-color:rgba(238,243,246,.15)}.nav-menu__box .item i.sidenav-icon{width:2.5rem;height:1.4rem;display:grid;place-items:center}.nav-menu__box .item.item-selected{color:#9bdaff;background-color:rgba(155,218,255,.15);font-weight:medium}.nav-menu__box button.toggle-nav{display:none;position:absolute;padding:0;top:0;right:-1rem;background:rgb(77.5,186.5157480315,255);width:1.5rem;height:1.5rem;aspect-ratio:1;justify-content:center;align-items:center;border-radius:50%;box-shadow:none}.nav-menu.collapsed .nav-menu__logo .logo-container{display:flex;flex-direction:column;align-items:center;gap:.5rem}.nav-menu.collapsed .nav-menu__logo .logo-container>*:not(img){display:block}.nav-menu.collapsed .nav-menu__logo .nav-menu__logo-text{display:none !important}.nav-menu.collapsed .nav-menu__box .item{padding:.675rem 0;width:2.5rem;justify-content:center;transition:300ms}.nav-menu.collapsed .nav-menu__box .item span,.nav-menu.collapsed .nav-menu__box .item p{display:none !important}.nav-menu.collapsed button i{rotate:180deg}.nav-menu:hover button.toggle-nav{display:flex}.sidebar{width:13rem;background-color:#fff;display:flex;flex-direction:column}.sidebar a{text-decoration:none;text-transform:capitalize;padding:1rem;cursor:pointer;color:#999}.sidebar a:hover{color:#222}.sidebar .selected-sidebar-link{font-weight:bold;color:#222;border-left:5px solid #3ba4d7;animation:expand-left-border .1s}.sidebarquickview>h6{padding:.5rem}.sidebarquickview a{text-decoration:none;text-transform:capitalize;padding:.5rem 1rem;display:block;color:#999}.sidebarquickview a a:hover{color:#222}.sidebarquickview .selected-sidebarquickview-link{font-weight:bold;color:#222;border-left:5px solid #3ba4d7;animation:expand-left-border .1s}.node-panel{width:100%;padding:.5rem;animation:fadein .5s}@keyframes expand-left-border{from{border-left:0}to{border-left:5px solid #3ba4d7}}@media(max-width: 700px){.tab-content{flex-direction:column}.sidebar{width:100% !important;flex-direction:row !important;overflow-x:auto !important;overflow-y:hidden !important;white-space:nowrap !important;border-bottom:1px solid rgba(20,20,27,.1) !important;background:#fff !important;z-index:50 !important;flex-shrink:0 !important;height:auto !important;padding:0 !important}.sidebar a{display:inline-block !important;padding:.8rem 1.2rem !important;border-bottom:3px solid rgba(0,0,0,0) !important;border-left:none !important}.sidebar .selected-sidebar-link{border-left:none !important;border-bottom:3px solid #3ba4d7 !important;animation:none !important}.sidebarquickview>h4,.sidebarquickview>h6{display:none !important}}.posts{height:100%;margin-top:1rem;flex-direction:column;overflow:auto}.posts__heading{display:flex;flex-direction:column;justify-content:space-between}.posts-container{height:100%;padding:1rem;display:grid;grid-template-columns:repeat(auto-fill, minmax(150px, 1fr));gap:2rem;border:1px solid rgba(20,20,27,.1);border-radius:4px;overflow:auto}.posts-container-card{min-height:240px;flex-direction:column;border:1px solid rgba(20,20,27,.5);border-radius:4px;cursor:pointer;text-align:center}.posts-container-card img{flex-basis:90%;object-fit:cover}.posts-container-card p{padding:0 .125rem;flex-basis:10%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.progress-bar{width:100%;height:2rem;position:relative;text-align:center;background-color:#eef3f6;border-radius:20px;overflow:hidden}.progress-bar__status{position:absolute;top:0;left:0;height:100%;color:#14141b;background-color:#019dff}.progress-bar__percent{position:absolute;inset:0;margin:auto;width:fit-content;height:fit-content}.progress-bar-chunks{position:relative;margin-top:.5rem;width:100%;height:2rem;display:flex;border-radius:.25rem;overflow:hidden;background-color:#eef3f6}.progress-bar-chunks .chunk{width:100%}.progress-bar-chunks .chunk[data-chunkVal="0"]{background-color:rgba(155,218,255,.2)}.progress-bar-chunks .chunk[data-chunkVal="1"]{background-color:#ff3a4a}.progress-bar-chunks .chunk[data-chunkVal="2"]{background-color:#019dff}.progress-bar-chunks .chunk[data-chunkVal="3"]{background-color:#fcba03}.progress-bar-chunks__percent{position:absolute;inset:0;margin:auto;width:fit-content;height:fit-content}.statusbar{display:flex;justify-content:space-between;align-items:center;height:28px;background-color:#14141b;border-top:1px solid #2e2e38;padding:0 1rem;font-size:.8rem;color:#94a3b8;z-index:100;box-sizing:border-box;user-select:none;flex-shrink:0}.statusbar-left{display:flex;align-items:center}.statusbar-right{display:flex;align-items:center;gap:1.5rem}.statusbar-item{display:flex;align-items:center}.statusbar-divider{width:1px;height:14px;background-color:#2e2e38}.status-bullet{width:8px;height:8px;border-radius:50%;display:inline-block;box-shadow:0 0 4px rgba(0,0,0,.5)}.widget{height:100%;padding:1rem;display:flex;flex-direction:column;gap:.5rem;background-color:#fff;border-radius:.5rem;overflow:auto}.widget .top-heading{display:flex;justify-content:space-between}.widget__heading{display:flex;justify-content:space-between;align-items:center;border-bottom:2px solid #999}.widget__body{height:100%;display:flex;flex-direction:column;overflow:auto}.widget__body-heading{display:flex;justify-content:space-between;align-items:center}.widget__body-heading .action{display:flex;gap:.5rem}.widget__body-content{height:100%;overflow:auto}.widget__body-box{display:flex;flex-direction:column;gap:.5rem}.widget-half{max-width:50%}#modal-container{display:none;position:fixed;z-index:1;height:100%;top:0;left:0;width:100%;background-color:rgba(0,0,0,.2)}.modal-content{position:absolute;color:#555;width:40%;min-height:10rem;height:max-content;padding:1.5rem;inset:0;margin:auto;background-color:#fff;border-radius:.5rem;animation:fadein .5s;display:flex;flex-direction:column}.modal-content button:last-child{margin-top:auto}.modal-content .close-btn{position:absolute;right:1.5rem}.modal-content .widget{padding:0}#notification-container{position:absolute;bottom:0;right:0}.login-page{background-image:linear-gradient(-45deg, rgba(1, 157, 255, 0.75), rgba(17, 143, 204, 0.75));height:100%;animation:fadein .5s}.login-page .login-container{background-color:#fff;box-shadow:3px 3px 5px rgba(20,20,27,.4);margin:auto;position:relative;top:100px;max-width:400px;max-height:500px;border-radius:5px;display:flex;flex-direction:column;align-items:center}.login-page .login-container input{padding:.375rem .75rem;border-radius:.275rem}.login-page .login-container *{margin-bottom:1rem}.login-page .login-container>img{margin:1rem 0 2rem}.login-page .login-container extra{margin:0}.login-page .login-container>a{text-decoration:underline;cursor:pointer}.login-page .extra>label,.login-page .extra>br,.login-page .extra>input{margin-bottom:0}.homepage{margin:2rem auto 0;display:flex;flex-direction:column;gap:4rem}.homepage .logo{display:flex;justify-content:center;align-items:center}.homepage .logo img{width:90px}.homepage .logo .retroshareText{display:flex;flex-direction:column;align-items:center}.homepage .logo .retroshareText .retrotext{font-size:36px;font-weight:600;line-height:1.125}.homepage .logo .retroshareText .retrotext>span{color:#118fcc}.homepage .logo .retroshareText>b{font-size:14px;line-height:1}.homepage .certificate{display:flex;flex-direction:column;gap:4rem}.homepage .certificate__heading{text-align:center}.homepage .certificate__heading>h1{margin-bottom:1rem}.homepage .certificate__content{display:flex;flex-direction:column;gap:2rem;padding:2rem;text-align:center;border:1.5px solid rgba(17,143,204,.2);border-radius:6px;box-shadow:0px 0px 8px 2px rgba(20,20,27,.05)}.homepage .certificate__content .rsId>p{margin-bottom:.5rem;color:#118fcc}.homepage .certificate__content .retroshareID{padding:.25rem;display:flex;align-items:center;justify-self:start;font-size:1.25rem;border-radius:4px;background:rgba(20,20,27,.05)}.homepage .certificate__content .retroshareID .textArea{padding:0;width:100%;height:auto;font-size:1rem;font-family:monospace;background:rgba(0,0,0,0);border:none;resize:none;overflow:hidden;field-sizing:content}.homepage .certificate__content .retroshareID i{color:#118fcc}.homepage .certificate__content .retroshareID>i{margin:0 .5rem;cursor:pointer}.homepage .certificate__content .webhelp{padding:.5rem;background:#f5f5f5;display:flex;justify-content:center;align-items:center;gap:.5rem;border-radius:4px;border:1px solid rgba(20,20,27,.5);width:fit-content;cursor:pointer}.homepage .certificate__content .webhelp-container{display:grid;place-items:center}.homepage .certificate__content .webhelp:hover{background:#eef3f6;border:1px solid #14141b}.homepage .certificate__content .webhelp>i{font-size:1.2rem;color:green}.homepage .certificate__content .add-friend>h6,.homepage .certificate__content .webhelp-container>h6{font-weight:normal;margin-bottom:.5rem}.network-container{display:flex;height:100%;width:100%;overflow:hidden;background-color:#f1f5f9}.network-left-pane{width:320px;min-width:300px;max-width:350px;border-right:1px solid #cbd5e1;display:flex;flex-direction:column;background:#fff;box-shadow:2px 0 5px rgba(0,0,0,.05)}.own-profile-card{padding:1.25rem;border-bottom:1px solid #e2e8f0;background:linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);display:flex;flex-direction:column;gap:.75rem}.own-profile-card .profile-header{display:flex;align-items:center;gap:1rem}.own-profile-card .profile-info{display:flex;flex-direction:column;flex:1;overflow:hidden}.own-profile-card .profile-info .profile-name{font-weight:700;color:#1e293b;font-size:1.1rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.own-profile-card .profile-info .profile-status{font-size:.85rem;color:#10b981;font-weight:500;display:flex;align-items:center;gap:.35rem}.own-profile-card .profile-info .profile-status::before{content:"";display:inline-block;width:8px;height:8px;background-color:#10b981;border-radius:50%}.own-profile-card .own-identity-select-container{display:flex;flex-direction:column;gap:.25rem}.own-profile-card .own-identity-select-container label{font-size:.75rem;color:#64748b;font-weight:600;text-transform:uppercase;letter-spacing:.05em}.own-profile-card .own-identity-select-container select.own-identity-select{width:100%;padding:.375rem .5rem;font-size:.85rem;border:1px solid #cbd5e1;border-radius:.375rem;background-color:#fff;color:#334155;outline:none;cursor:pointer;transition:border-color .2s}.own-profile-card .own-identity-select-container select.own-identity-select:focus{border-color:#3ba4d7}.friends-list-container{flex:1;display:flex;flex-direction:column;overflow:hidden;position:relative}.friends-list-container .searchbar-container{padding:.75rem 1rem;border-bottom:1px solid #e2e8f0}.friends-list-container .searchbar-container input.searchbar{width:100%;padding:.5rem .75rem;font-size:.9rem;border:1px solid #cbd5e1;border-radius:.375rem;background-color:#f8fafc;outline:none;transition:all .2s}.friends-list-container .searchbar-container input.searchbar:focus{background-color:#fff;border-color:#3ba4d7;box-shadow:0 0 0 3px rgba(59,164,215,.15)}.friends-list-container .friends-scroll{flex:1;overflow-y:auto;padding:.5rem 0}.friend-list-item{display:flex;align-items:center;gap:.75rem;padding:.75rem 1rem;margin:.125rem .5rem;border-radius:.5rem;cursor:pointer;transition:all .2s}.friend-list-item:hover{background-color:#f1f5f9}.friend-list-item.selected{background-color:#e0f2fe}.friend-list-item.selected .friend-name{color:#0369a1;font-weight:600}.friend-list-item .friend-avatar{flex-shrink:0}.friend-list-item .friend-meta{flex:1;min-width:0}.friend-list-item .friend-meta .friend-name{font-size:.95rem;color:#334155;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;transition:color .2s}.friend-list-item .friend-meta .friend-status{font-size:.8rem;color:#94a3b8}.friend-list-item .friend-meta .friend-status.online{color:#10b981;font-weight:500}.network-right-pane{flex:1;display:flex;flex-direction:column;overflow:hidden;background-color:#f8fafc}.network-pane-placeholder{flex:1;display:flex;flex-direction:column;justify-content:center;align-items:center;color:#94a3b8;gap:1rem;padding:2rem;text-align:center}.network-pane-placeholder i{font-size:4rem;color:#cbd5e1}.network-pane-placeholder p{font-size:1.1rem;max-width:400px}.network-tabs{display:flex;background-color:#fff;border-bottom:1px solid #cbd5e1;padding:.5rem 1rem 0;gap:.5rem}.network-tabs .tab-btn{padding:.625rem 1.25rem;font-size:.95rem;font-weight:600;color:#64748b;background:rgba(0,0,0,0);border:none;border-radius:.375rem .375rem 0 0;border-bottom:3px solid rgba(0,0,0,0);cursor:pointer;box-shadow:none;transition:all .2s}.network-tabs .tab-btn:hover{color:#334155;background-color:#f1f5f9}.network-tabs .tab-btn.active{color:#3ba4d7;border-bottom-color:#3ba4d7;background-color:rgba(0,0,0,0)}.network-tab-content{flex:1;overflow-y:auto;padding:1.5rem}.network-detail-view{display:flex;flex-direction:column;gap:1.5rem}.network-detail-view .detail-header{display:flex;align-items:center;gap:1.5rem;padding-bottom:1.5rem;border-bottom:1px solid #e2e8f0}.network-detail-view .detail-header .detail-title{flex:1}.network-detail-view .detail-header .detail-title h2{font-size:1.75rem;font-weight:800;color:#1e293b;margin-bottom:.25rem}.network-detail-view .detail-header .detail-title .detail-subtitle{font-size:.9rem;color:#64748b;display:flex;align-items:center;gap:.5rem}.network-detail-view .detail-header .detail-actions{display:flex;gap:.75rem}.network-detail-view .detail-header .detail-actions button{display:flex;align-items:center;gap:.5rem;padding:.5rem 1rem;font-size:.9rem}.network-detail-view .detail-section{background-color:#fff;border-radius:.5rem;border:1px solid #e2e8f0;padding:1.25rem;box-shadow:0 1px 3px rgba(0,0,0,.02)}.network-detail-view .detail-section h3{font-size:1.1rem;font-weight:700;color:#334155;margin-bottom:1rem;padding-bottom:.5rem;border-bottom:1px solid #f1f5f9}.network-detail-view .detail-section .info-grid{display:grid;grid-template-columns:120px 1fr;row-gap:.75rem;font-size:.9rem}.network-detail-view .detail-section .info-grid .info-label{font-weight:600;color:#64748b}.network-detail-view .detail-section .info-grid .info-value{color:#1e293b;word-break:break-all}.network-detail-view .locations-grid{display:grid;grid-template-columns:repeat(auto-fill, minmax(280px, 1fr));gap:1rem}.network-detail-view .location-card{background-color:#fff;border:1px solid #e2e8f0;border-radius:.5rem;padding:1rem;display:flex;flex-direction:column;gap:.5rem;box-shadow:0 1px 3px rgba(0,0,0,.02)}.network-detail-view .location-card .loc-header{display:flex;justify-content:space-between;align-items:center;border-bottom:1px solid #f1f5f9;padding-bottom:.5rem;margin-bottom:.25rem}.network-detail-view .location-card .loc-header .loc-name{font-weight:700;color:#334155;font-size:.95rem}.network-detail-view .location-card .loc-header .loc-status{font-size:.75rem;font-weight:600;padding:.125rem .5rem;border-radius:.25rem}.network-detail-view .location-card .loc-header .loc-status.online{background-color:#d1fae5;color:#065f46}.network-detail-view .location-card .loc-header .loc-status.offline{background-color:#f1f5f9;color:#475569}.network-detail-view .location-card .loc-body{font-size:.85rem;display:grid;grid-template-columns:80px 1fr;row-gap:.25rem}.network-detail-view .location-card .loc-body .loc-label{color:#64748b}.network-detail-view .location-card .loc-body .loc-val{color:#334155;word-break:break-all}.network-detail-view .location-card .loc-footer{margin-top:.5rem;display:flex;justify-content:flex-end}.network-detail-view .location-card .loc-footer button{font-size:.8rem;padding:.25rem .75rem}.network-chat-view{display:flex;flex-direction:column;height:100%;overflow:hidden;background-color:#f8fafc}.network-chat-view .chat-messages{flex:1;overflow-y:auto;padding:1.25rem;display:flex;flex-direction:column;gap:1rem}.network-chat-view .chat-bubble-container{display:flex;flex-direction:column;max-width:70%}.network-chat-view .chat-bubble-container.outgoing{align-self:flex-end;align-items:flex-end}.network-chat-view .chat-bubble-container.outgoing .chat-bubble{background-color:#3ba4d7;color:#fff;border-bottom-right-radius:.125rem}.network-chat-view .chat-bubble-container.incoming{align-self:flex-start;align-items:flex-start}.network-chat-view .chat-bubble-container.incoming .chat-bubble{background-color:#fff;color:#1e293b;border:1px solid #e2e8f0;border-bottom-left-radius:.125rem}.network-chat-view .chat-bubble-container .chat-sender{font-size:.75rem;color:#64748b;margin-bottom:.25rem;padding:0 .25rem}.network-chat-view .chat-bubble-container .chat-bubble{padding:.625rem .875rem;border-radius:.75rem;font-size:.925rem;line-height:1.4;white-space:break-spaces;word-break:break-word;box-shadow:0 1px 2px rgba(0,0,0,.05)}.network-chat-view .chat-bubble-container .chat-time{font-size:.7rem;color:#94a3b8;margin-top:.25rem;padding:0 .25rem}.network-chat-view .chat-input-area{padding:1rem;background-color:#fff;border-top:1px solid #cbd5e1;display:flex;gap:.75rem;align-items:center}.network-chat-view .chat-input-area textarea.chat-textarea{flex:1;resize:none;height:40px;padding:.5rem .75rem;border:1px solid #cbd5e1;border-radius:.375rem;font-size:.9rem;outline:none;transition:all .2s}.network-chat-view .chat-input-area textarea.chat-textarea:focus{border-color:#3ba4d7;box-shadow:0 0 0 3px rgba(59,164,215,.15)}.network-chat-view .chat-input-area button.send-btn{padding:.5rem 1.25rem;font-size:.9rem;height:40px;display:flex;align-items:center;gap:.5rem}.network-chat-view .chat-warning{flex:1;display:flex;flex-direction:column;justify-content:center;align-items:center;color:#64748b;text-align:center;padding:2rem;gap:1rem}.network-chat-view .chat-warning i{font-size:3rem;color:#cbd5e1}.network-chat-view .chat-warning h4{font-weight:700;color:#334155}.network-chat-view .chat-warning p{max-width:350px;font-size:.9rem}.identity{color:#444;font-size:1.1em;margin:20px;padding:10px;border:1px solid #aaa;border-radius:20px}.identity>h4{margin:5px;font-size:1.3em}.identity button{font-size:.9em}.identity .details{display:grid;grid-template-columns:140px auto;grid-row-gap:5px;justify-content:left}.defaultAvatar{width:3rem;height:3rem;aspect-ratio:1;background:#b0c4de;border-radius:50%;display:grid;place-items:center}.defaultAvatar p{font-weight:900;color:#666f7f;transform:translateY(1px)}img.avatar{display:block;width:3rem;height:max-content;aspect-ratio:1;margin-right:.3em;border-radius:50%}.counter{margin-left:.5em}.counter:before{content:"("}.counter:after{content:")"}.chatInit{margin-left:.5em;color:green;cursor:pointer}.people-sidebar-header{display:flex;flex-direction:column;padding:.75rem 1rem .5rem 1rem;gap:.75rem;border-bottom:1px solid #e2e8f0;background-color:#fff}.people-sidebar-header .searchbar-wrapper{position:relative;display:flex;align-items:center}.people-sidebar-header .searchbar-wrapper i.fa-search{position:absolute;left:.85rem;color:#94a3b8;font-size:.9rem}.people-sidebar-header .searchbar-wrapper input.searchbar-input{width:100%;padding:.5rem .75rem .5rem 2.25rem;border:1px solid #e2e8f0;border-radius:.5rem;font-size:.9rem;background-color:#f8fafc;color:#1e293b;outline:none;transition:all .2s ease}.people-sidebar-header .searchbar-wrapper input.searchbar-input:focus{border-color:#3b82f6;background-color:#fff;box-shadow:0 0 0 3px rgba(59,130,246,.1)}.people-sidebar-header .segmented-control{display:flex;background-color:#f1f5f9;padding:3px;border-radius:.5rem;gap:4px}.people-sidebar-header .segmented-control button.segment-tab{flex:1;display:flex;align-items:center;justify-content:center;gap:.5rem;padding:.5rem .75rem;font-size:.9rem;font-weight:600;color:#64748b;background:rgba(0,0,0,0);border:none;border-radius:.375rem;cursor:pointer;box-shadow:none;transition:all .2s ease}.people-sidebar-header .segmented-control button.segment-tab:hover{color:#1e293b}.people-sidebar-header .segmented-control button.segment-tab.active{background-color:#fff;color:#0f172a;box-shadow:0 1px 3px rgba(0,0,0,.1),0 1px 2px rgba(0,0,0,.06)}.people-sidebar-header .segmented-control button.segment-tab.active .segment-badge{background-color:#019dff;color:#fff}.people-sidebar-header .segmented-control button.segment-tab .segment-badge{display:inline-flex;align-items:center;justify-content:center;background-color:#cbd5e1;color:#334155;font-size:.75rem;font-weight:700;min-width:1.25rem;height:1.25rem;padding:0 .35rem;border-radius:9999px;line-height:1;transition:all .2s ease}.people-sidebar-header .sub-filter-row{display:flex;align-items:center;justify-content:space-between;gap:.5rem;min-height:32px}.people-sidebar-header .sub-filter-row select.filter-select{padding:.35rem .6rem;border:1px solid #cbd5e1;border-radius:.375rem;font-size:.85rem;font-weight:600;color:#475569;background-color:#fff;cursor:pointer;outline:none}.people-sidebar-header .sub-filter-row .btn-add-id{display:flex;align-items:center;justify-content:center;width:32px;height:32px;border-radius:.375rem;background-color:#3b82f6;color:#fff;border:none;cursor:pointer;font-size:.9rem;transition:background-color .2s}.people-sidebar-header .sub-filter-row .btn-add-id:hover{background-color:#2563eb}.friends-list-container .people-context-menu{position:absolute;left:2rem;width:210px;background-color:#fff;border:1px solid #e2e8f0;box-shadow:0 4px 10px rgba(0,0,0,.15);border-radius:.375rem;z-index:1010;padding:.25rem 0;display:flex;flex-direction:column}.friends-list-container .people-context-menu .menu-item{padding:.5rem 1rem;font-size:.85rem;color:#334155;cursor:pointer;display:flex;align-items:center;transition:background-color .2s}.friends-list-container .people-context-menu .menu-item:hover{background-color:#f1f5f9;color:#0f172a}.people-container{display:flex;height:calc(100vh - 55px);width:100%;overflow:hidden}.people-left-pane{width:320px;border-right:1px solid #cbd5e1;display:flex;flex-direction:column;background-color:#fff;overflow:hidden}.people-right-pane{flex:1;display:flex;flex-direction:column;overflow:hidden;background-color:#f8fafc}.people-left-pane .chat-item{display:flex;align-items:center;padding:.75rem 1rem;gap:.75rem;border-bottom:1px solid #f1f5f9;cursor:pointer;transition:background-color .15s ease;position:relative}.people-left-pane .chat-item:hover{background-color:#f8fafc}.people-left-pane .chat-item.selected{background-color:#eff6ff;border-left:3px solid #3b82f6}.people-left-pane .chat-item .chat-avatar-wrapper{position:relative;flex-shrink:0}.people-left-pane .chat-item .chat-avatar-wrapper .status-dot{position:absolute;bottom:0;right:0;width:10px;height:10px;border-radius:50%;border:2px solid #fff}.people-left-pane .chat-item .chat-info{flex:1;min-width:0;display:flex;flex-direction:column;gap:.15rem}.people-left-pane .chat-item .chat-info .chat-name{font-size:.9rem;font-weight:700;color:#1e293b;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.people-left-pane .chat-item .chat-info .chat-last-msg{font-size:.8rem;color:#64748b;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.people-left-pane .chat-item .chat-meta{display:flex;flex-direction:column;align-items:flex-end;gap:.25rem;flex-shrink:0}.people-left-pane .chat-item .chat-meta .chat-time{font-size:.75rem;color:#94a3b8;white-space:nowrap}.lobby{margin:10px;border:1px solid #aaa;border-radius:20px}.lobby .mainname{margin:20px;font-weight:100;font-size:1.2em}.topic{color:#666}.lobby>.topic{font-size:.95em;margin-left:25px;margin-bottom:5px}.lefttitle{margin-top:15px;margin-bottom:0;font-weight:100;font-size:1.2em}.leftname{margin-top:5px;margin-bottom:5px;padding:5px;font-weight:100;font-size:1em}.leftlobby>.topic{font-size:.75em;margin-left:15px;margin-bottom:5px}.subscribed,.public{cursor:pointer}.leftlobby{border:1px solid #aaa;border-radius:10px;margin-top:5px;background-color:#fff}.leftlobby.selected-lobby,.selectedidentity{color:#fff;background-color:#3ba4d7}.rightbar{position:absolute;width:185px;background-color:#fff;overflow:auto;top:130px;bottom:15px;right:15px}.user{padding:5px}.lobbyName{padding:15px;margin-top:2rem}.lobbies{position:absolute;width:185px;left:165px;bottom:15px;top:130px;overflow:auto}.messages,.setup{position:absolute;background-color:#fff;top:130px;left:360px;right:215px;overflow:auto}.messages{bottom:115px}.messagetext{white-space:break-spaces;margin-right:5px}.message>*{margin-left:5px}.username{color:#006400;font-weight:bolder}.chatMessage{position:absolute;background-color:#fff;height:85px;bottom:15px;right:215px;left:360px}textarea.chatMsg{height:100%;width:100%}.chatatchar{margin-left:.2em;margin-right:.2em;color:silver}.setupicon{margin-left:1em;cursor:pointer}.leaveicon{margin-left:1em;cursor:pointer;color:#d40000}.selectidentity{margin:15px;font-size:1.2em}.setup>.identity{cursor:pointer}.setup{bottom:15px}.createDistantChat{margin-top:1em}.no-lobbies .messages,.no-lobbies .chatMessage,.no-lobbies .setup{left:165px}@media(min-width: 900px){.node-panel.chat-room{display:grid !important;grid-template-columns:250px 1fr 200px !important;grid-template-rows:auto 1fr auto !important;grid-template-areas:"lobbies header rightbar" "lobbies messages rightbar" "lobbies input rightbar" !important;padding:0 !important;height:100% !important}.node-panel.chat-room .lobbyName{grid-area:header;padding:10px;border-bottom:1px solid #eee;margin:0;z-index:10;background:#fff}.node-panel.chat-room .lobbies{grid-area:lobbies;position:static !important;width:auto !important;height:auto !important;border-right:1px solid #ccc;overflow-y:auto;display:block !important;top:auto !important;bottom:auto !important;left:auto !important}.node-panel.chat-room .messages{grid-area:messages;position:static !important;width:auto !important;height:auto !important;overflow-y:auto;padding:10px;left:auto !important;right:auto !important;top:auto !important;bottom:auto !important;margin:0 !important}.node-panel.chat-room .rightbar{grid-area:rightbar;position:static !important;width:auto !important;border-left:1px solid #ccc;overflow-y:auto;display:block !important}.node-panel.chat-room .chatMessage{grid-area:input;position:static !important;width:auto !important;height:auto !important;border-top:1px solid #eee;left:auto !important;right:auto !important;bottom:auto !important;flex:0 0 auto;padding:10px !important;background:#fff;z-index:10}}@media(max-width: 899px){.node-panel.chat-room{display:flex !important;flex-direction:column !important;height:100% !important;position:relative !important}.node-panel.chat-room .lobbyName{flex:0 0 auto}.node-panel.chat-room .messages{flex:1 !important;overflow-y:auto !important;position:relative !important;top:0 !important;bottom:0 !important;left:0 !important;right:0 !important;width:100% !important;height:auto !important;margin:0 !important}.node-panel.chat-room .chatMessage{flex:0 0 auto !important;position:relative !important;bottom:0 !important;left:0 !important;right:0 !important;width:100% !important;height:auto !important;z-index:100}.node-panel.chat-room .rightbar,.node-panel.chat-room .lobbies{display:none !important;position:fixed !important;top:60px !important;bottom:0 !important;width:80% !important;background:#fff !important;z-index:200 !important;box-shadow:2px 0 10px rgba(0,0,0,.2) !important}.node-panel.chat-room.show-lobbies .lobbies{display:block !important;left:0 !important}.node-panel.chat-room.show-users .rightbar{display:block !important;right:0 !important}.chat-overlay{display:none;position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,.4);z-index:150}.show-lobbies .chat-overlay,.show-users .chat-overlay{display:block}.mobile-menu-icons{display:flex;gap:15px;font-size:1.2rem}.mobile-menu-icons i{cursor:pointer;padding:5px}}@media(min-width: 900px){.mobile-menu-icons{display:none}}.chat-hub-container{display:flex;height:100%;width:100%;overflow:hidden;background-color:#f1f5f9}.chat-hub-left-pane{width:320px;min-width:300px;max-width:350px;border-right:1px solid #cbd5e1;display:flex;flex-direction:column;background:#fff;box-shadow:2px 0 5px rgba(0,0,0,.05)}.chat-own-profile-card{padding:1.25rem;border-bottom:1px solid #e2e8f0;background:linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);position:relative}.chat-own-profile-card .profile-header{display:flex;align-items:center;gap:1rem}.chat-own-profile-card .profile-info{display:flex;flex-direction:column;flex:1;overflow:hidden}.chat-own-profile-card .profile-info .profile-name{font-weight:700;color:#1e293b;font-size:1.1rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.chat-own-profile-card .profile-info .profile-status{font-size:.85rem;color:#10b981;font-weight:500;display:flex;align-items:center;gap:.35rem}.chat-own-profile-card .profile-info .profile-status::before{content:"";display:inline-block;width:8px;height:8px;background-color:#10b981;border-radius:50%}.chat-rooms-list-container{flex:1;display:flex;flex-direction:column;overflow:hidden}.chat-rooms-list-container .searchbar-container{padding:.75rem 1rem;border-bottom:1px solid #e2e8f0}.chat-rooms-list-container .searchbar-container input.searchbar{width:100%;padding:.5rem .75rem;font-size:.9rem;border:1px solid #cbd5e1;border-radius:.375rem;background-color:#f8fafc;outline:none;transition:all .2s}.chat-rooms-list-container .searchbar-container input.searchbar:focus{background-color:#fff;border-color:#3ba4d7;box-shadow:0 0 0 3px rgba(59,164,215,.15)}.chat-rooms-list-container .rooms-scroll{flex:1;overflow-y:auto;padding:.5rem 0}.rooms-section-title{display:flex;align-items:center;gap:.5rem;padding:.75rem 1rem .375rem;font-size:.75rem;font-weight:700;color:#64748b;text-transform:uppercase;letter-spacing:.05em}.rooms-section-title i{font-size:.7rem;color:#94a3b8}.chat-room-list-item{display:flex;align-items:center;gap:.75rem;padding:.75rem 1rem;margin:.125rem .5rem;border-radius:.5rem;cursor:pointer;transition:all .2s}.chat-room-list-item:hover{background-color:#f1f5f9}.chat-room-list-item.selected{background-color:#e0f2fe}.chat-room-list-item.selected .room-name{color:#0369a1;font-weight:600}.chat-room-list-item .room-icon{flex-shrink:0;width:36px;height:36px;border-radius:.5rem;background:linear-gradient(135deg, #3ba4d7, #0ea5e9);display:flex;align-items:center;justify-content:center;color:#fff;font-size:1.35rem}.chat-room-list-item.public-room .room-icon{background:linear-gradient(135deg, #10b981, #059669)}.chat-room-list-item .room-meta{flex:1;min-width:0}.chat-room-list-item .room-meta .room-name{font-size:.95rem;color:#334155;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;transition:color .2s}.chat-room-list-item .room-meta .room-topic{font-size:.8rem;color:#94a3b8;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.chat-room-list-item .room-badge{flex-shrink:0;min-width:24px;height:24px;border-radius:12px;background-color:#e2e8f0;color:#475569;font-size:.75rem;font-weight:700;display:flex;align-items:center;justify-content:center;padding:0 .375rem}.chat-hub-right-pane{flex:1;display:flex;flex-direction:column;overflow:hidden;background-color:#f8fafc}.chat-pane-placeholder{flex:1;display:flex;flex-direction:column;justify-content:center;align-items:center;color:#94a3b8;gap:1rem;padding:2rem;text-align:center}.chat-pane-placeholder i{font-size:4rem;color:#cbd5e1}.chat-pane-placeholder p{font-size:1.1rem;max-width:400px}.chat-hub-tab-content{flex:1;overflow-y:auto;padding:1.5rem}.chat-room-detail-view{display:flex;flex-direction:column;gap:1.5rem}.chat-room-detail-view .detail-header{display:flex;align-items:flex-start;gap:1.5rem;padding-bottom:1.5rem;border-bottom:1px solid #e2e8f0;flex-wrap:wrap}.chat-room-detail-view .detail-header .detail-title{flex:1;min-width:200px}.chat-room-detail-view .detail-header .detail-title h2{font-size:1.75rem;font-weight:800;color:#1e293b;margin-bottom:.25rem}.chat-room-detail-view .detail-header .detail-title .detail-subtitle{font-size:.9rem;color:#64748b;display:flex;align-items:center;gap:.5rem}.chat-room-detail-view .detail-header .detail-actions{display:flex;gap:.75rem;flex-wrap:wrap}.chat-room-detail-view .detail-header .detail-actions button{display:flex;align-items:center;gap:.5rem;padding:.5rem 1rem;font-size:.9rem}.chat-room-detail-view .detail-section{background-color:#fff;border-radius:.5rem;border:1px solid #e2e8f0;padding:1.25rem;box-shadow:0 1px 3px rgba(0,0,0,.02)}.chat-room-detail-view .detail-section h3{font-size:1.1rem;font-weight:700;color:#334155;margin-bottom:1rem;padding-bottom:.5rem;border-bottom:1px solid #f1f5f9}.chat-room-detail-view .detail-section .info-grid{display:grid;grid-template-columns:130px 1fr;row-gap:.75rem;font-size:.9rem}.chat-room-detail-view .detail-section .info-grid .info-label{font-weight:600;color:#64748b}.chat-room-detail-view .detail-section .info-grid .info-value{color:#1e293b;word-break:break-all}.participants-grid{display:grid;grid-template-columns:repeat(auto-fill, minmax(180px, 1fr));gap:.5rem}.participant-card{display:flex;align-items:center;gap:.5rem;padding:.5rem .75rem;background-color:#f8fafc;border:1px solid #e2e8f0;border-radius:.375rem}.participant-card .participant-name{font-size:.875rem;color:#334155;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.no-participants{color:#94a3b8;font-size:.9rem;font-style:italic}.detail-actions-footer{display:flex;gap:.75rem}.detail-actions-footer button{display:flex;align-items:center;gap:.5rem;padding:.5rem 1rem;font-size:.9rem}.join-description{color:#64748b;font-size:.9rem;margin-bottom:1rem}.identities-grid{display:grid;grid-template-columns:repeat(auto-fill, minmax(200px, 1fr));gap:.75rem}.identity-card{display:flex;align-items:center;justify-content:space-between;padding:.75rem 1rem;background-color:#f8fafc;border:1px solid #e2e8f0;border-radius:.5rem;cursor:pointer;transition:all .2s}.identity-card:hover{background-color:#e0f2fe;border-color:#3ba4d7}.identity-card .identity-name{font-size:.95rem;font-weight:600;color:#334155}.identity-card i{color:#3ba4d7;font-size:.9rem}.no-rooms{padding:1rem;color:#94a3b8;text-align:center;font-style:italic}@media(max-width: 899px){.chat-hub-container{flex-direction:column}.chat-hub-left-pane{width:100%;min-width:0;max-width:none;max-height:45%;border-right:none;border-bottom:1px solid #cbd5e1}.chat-hub-right-pane{flex:1;min-height:0}}.chat-hub-header-bar{padding:.75rem 1.5rem;background-color:#fff;border-bottom:1px solid #e2e8f0;display:flex;align-items:center;justify-content:space-between;height:65px;flex-shrink:0}.chat-hub-header-bar .chat-header-info{display:flex;flex-direction:column;overflow:hidden}.chat-hub-header-bar .chat-header-info .chat-header-name{font-size:1.15rem;font-weight:800;color:#1e293b;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.chat-hub-header-bar .chat-header-info .chat-header-topic{font-size:.85rem;color:#64748b;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-top:.125rem}.chat-hub-header-bar .chat-header-actions{display:flex;gap:.5rem}.chat-hub-header-bar .chat-header-actions button{display:flex;align-items:center;gap:.35rem;padding:.375rem .75rem;font-size:.85rem}.chat-hub-tabs-container{background-color:#fff;border-bottom:1px solid #cbd5e1;padding:.5rem 1.5rem 0}.chat-hub-tabs{display:flex;gap:.5rem}.chat-hub-tabs .tab-btn{padding:.625rem 1.25rem;font-size:.95rem;font-weight:600;color:#64748b;background:rgba(0,0,0,0);border:none;border-radius:.375rem .375rem 0 0;border-bottom:3px solid rgba(0,0,0,0);cursor:pointer;box-shadow:none;transition:all .2s;display:flex;align-items:center;gap:.5rem}.chat-hub-tabs .tab-btn:hover{color:#334155;background-color:#f1f5f9}.chat-hub-tabs .tab-btn.active{color:#3ba4d7;border-bottom-color:#3ba4d7;background-color:rgba(0,0,0,0)}.chat-hub-tab-content{flex:1;display:flex;flex-direction:column;overflow:hidden;background-color:#f8fafc}.chat-hub-conversation-layout{display:flex;flex-direction:row;height:100%;width:100%;overflow:hidden}.chat-hub-conversation-main{display:flex;flex-direction:column;flex:1;height:100%;overflow:hidden}.chat-hub-rightbar{width:200px;border-left:1px solid #cbd5e1;background-color:#fff;display:flex;flex-direction:column;flex-shrink:0;position:relative}.chat-hub-rightbar .rightbar-title{padding:.75rem 1rem;font-size:.85rem;font-weight:700;color:#64748b;text-transform:uppercase;letter-spacing:.05em;border-bottom:1px solid #e2e8f0}.chat-hub-rightbar .rightbar-users-list{flex:1;overflow-y:auto;padding:.5rem}.chat-hub-rightbar .user{padding:.5rem .75rem;font-size:.9rem;color:#334155;border-radius:.375rem;transition:all .2s;display:flex;align-items:center;gap:.5rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;position:relative}.chat-hub-rightbar .user:hover{background-color:#f1f5f9;color:#0f172a}.chat-hub-rightbar .user .defaultAvatar{width:2rem;height:2rem;font-size:.9rem;flex-shrink:0}.chat-hub-rightbar .user img.avatar{width:2rem;height:2rem;flex-shrink:0}@media(max-width: 899px){.chat-hub-rightbar{display:none}}.chat-hub-messages{flex:1;overflow-y:auto;padding:1.25rem 1.5rem;display:flex;flex-direction:column;gap:1rem}.chat-hub-messages .message{display:flex;flex-direction:column;max-width:70%;padding:.625rem .875rem;border-radius:.75rem;font-size:.925rem;line-height:1.4;word-break:break-word;box-shadow:0 1px 2px rgba(0,0,0,.05)}.chat-hub-messages .message.incoming{align-self:flex-start;align-items:flex-start;background-color:#fff;color:#1e293b;border:1px solid #e2e8f0;border-bottom-left-radius:.125rem}.chat-hub-messages .message.outgoing{align-self:flex-end;align-items:flex-end;background-color:#3ba4d7;color:#fff;border-bottom-right-radius:.125rem}.chat-hub-messages .message .username{font-size:.75rem;margin-bottom:.25rem;padding:0 .125rem;font-weight:700}.chat-hub-messages .message.incoming .username{color:#0369a1}.chat-hub-messages .message.outgoing .username{color:#e0f2fe}.chat-hub-messages .message .messagetext{white-space:break-spaces;margin:0}.chat-hub-messages .message .datetime{font-size:.7rem;margin-top:.25rem;padding:0 .125rem;opacity:.8}.chat-hub-messages .message.incoming .datetime{color:#64748b}.chat-hub-messages .message.outgoing .datetime{color:#f1f5f9}.chat-hub-input-area{padding:.75rem 1.5rem;background-color:#fff;border-top:1px solid #cbd5e1;display:flex;gap:.75rem;align-items:flex-end;flex-shrink:0}.chat-hub-input-area textarea.chat-hub-textarea{flex:1;resize:vertical;min-height:40px;max-height:250px;height:40px;padding:.5rem .75rem;border:1px solid #cbd5e1;border-radius:.375rem;font-size:.9rem;outline:none;transition:border-color .2s,box-shadow .2s;background-color:#f8fafc}.chat-hub-input-area textarea.chat-hub-textarea:focus{background-color:#fff;border-color:#3ba4d7;box-shadow:0 0 0 3px rgba(59,164,215,.15)}.chat-hub-input-area button.chat-hub-send-btn{padding:.5rem 1.25rem;font-size:.9rem;height:40px;display:flex;align-items:center;gap:.5rem;border-radius:.375rem}.chat-hub-messages.compact-container,.messages.compact-container{gap:0 !important;padding:.75rem 1rem !important;background-color:#fff !important;display:flex !important;flex-direction:column !important}.chat-hub-messages.compact-container .message.compact,.messages.compact-container .message.compact{display:block !important;max-width:100% !important;padding:.1rem 0 !important;border-radius:0 !important;background-color:rgba(0,0,0,0) !important;border:none !important;box-shadow:none !important;align-self:flex-start !important;font-size:.875rem !important;line-height:1.45 !important;margin:0 !important;white-space:nowrap !important}.chat-hub-messages.compact-container .message.compact:hover,.messages.compact-container .message.compact:hover{background-color:#f8fafc !important;overflow:visible !important;white-space:normal !important}.chat-hub-messages.compact-container .message.compact .datetime,.messages.compact-container .message.compact .datetime{color:#a0a0a0 !important;margin-right:.4rem !important;font-size:.78rem !important;font-family:monospace !important;opacity:1 !important;display:inline !important}.chat-hub-messages.compact-container .message.compact .username,.messages.compact-container .message.compact .username{font-weight:bold !important;margin-right:.2rem !important;font-size:.875rem !important;display:inline !important}.chat-hub-messages.compact-container .message.compact .messagetext,.messages.compact-container .message.compact .messagetext{color:#1e293b !important;white-space:normal !important;word-break:break-word !important;display:inline !important;margin:0 !important}.chat-create-lobby-btn{position:absolute;bottom:.5rem;right:1.25rem;background-color:#0084ff;color:#fff;border:none;border-radius:.375rem;padding:.35rem .75rem;font-size:.85rem;font-weight:600;cursor:pointer;box-shadow:0 4px 6px -1px rgba(0,132,255,.2),0 2px 4px -1px rgba(0,132,255,.1);transition:background-color .2s,transform .2s;display:flex;align-items:center;gap:.25rem}.chat-create-lobby-btn:hover{background-color:#0073e6;transform:translateY(-1px)}.chat-create-lobby-btn:active{transform:translateY(0)}.chat-hub-rightbar .user .user-name{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;flex:1}.user-tooltip{position:absolute;width:260px;background-color:#ffffe1;border:1px solid #7f7f7f;box-shadow:2px 2px 6px rgba(0,0,0,.25);padding:.5rem;border-radius:.25rem;z-index:10000;white-space:normal;display:flex;gap:.5rem;align-items:flex-start}.chat-hub-rightbar .user-tooltip{left:-275px;transform:translateY(-50%);z-index:1000}.user-tooltip .tooltip-avatar{flex-shrink:0}.user-tooltip .tooltip-details{display:flex;flex-direction:column;gap:.25rem;font-size:.8rem;color:#000;text-align:left}.user-tooltip .tooltip-row{line-height:1.2}.user-tooltip .tooltip-label{font-weight:bold}.user-tooltip .tooltip-value{font-weight:normal;word-break:break-all}.user-tooltip .tooltip-value.tooltip-id{font-family:monospace}.chat-hub-rightbar .rightbar-context-menu{position:absolute;right:1rem;width:210px;background-color:#fff;border:1px solid #e2e8f0;box-shadow:0 4px 10px rgba(0,0,0,.15);border-radius:.375rem;z-index:1010;padding:.25rem 0;display:flex;flex-direction:column}.chat-hub-rightbar .rightbar-context-menu .menu-item{padding:.5rem 1rem;font-size:.85rem;color:#334155;cursor:pointer;display:flex;align-items:center;transition:background-color .2s}.chat-hub-rightbar .rightbar-context-menu .menu-item:hover{background-color:#f1f5f9;color:#0f172a}.chat-emoji{font-size:1.45em;line-height:1;vertical-align:-0.15em;display:inline-block}.chat-hub-attach-btn,.chat-hub-action-btn{background-color:rgba(0,0,0,0) !important;border:none !important;font-size:1.15rem !important;color:#64748b !important;cursor:pointer !important;padding:.4rem .5rem !important;border-radius:.375rem !important;flex-shrink:0 !important;display:inline-flex !important;align-items:center !important;justify-content:center !important;transition:all .2s !important;box-shadow:none !important;margin:0 !important;line-height:1 !important;height:36px !important;width:36px !important}.chat-hub-attach-btn:hover,.chat-hub-action-btn:hover{background-color:#f1f5f9 !important;color:#3b82f6 !important;transform:none !important}.attach-modal-overlay{position:fixed;top:0;left:0;width:100vw;height:100vh;background-color:rgba(15,23,42,.4);backdrop-filter:blur(4px);display:flex;align-items:center;justify-content:center;z-index:2000}.attach-modal{background-color:#fff;border-radius:.5rem;width:450px;max-width:90%;padding:1.5rem;box-shadow:0 10px 25px -5px rgba(0,0,0,.1),0 8px 10px -6px rgba(0,0,0,.1);display:flex;flex-direction:column;gap:1rem}.attach-modal .attach-modal-header{display:flex;align-items:center;gap:.6rem;margin-bottom:.25rem}.attach-modal .attach-modal-icon{font-size:1.2rem;color:#3b82f6}.attach-modal h4{margin:0;font-size:1.2rem;color:#0f172a}.attach-modal p{margin:0;font-size:.9rem;color:#475569}.attach-modal .attach-path-row{display:flex;gap:.5rem;align-items:center}.attach-modal .attach-path-row input[type=text]{flex:1;padding:.75rem;border:1px solid #cbd5e1;border-radius:.375rem;font-size:.9rem;outline:none;transition:border-color .2s;min-width:0}.attach-modal .attach-path-row input[type=text]:focus{border-color:#3b82f6;box-shadow:0 0 0 3px rgba(59,130,246,.15)}.attach-browse-btn{flex-shrink:0;display:flex;align-items:center;gap:.35rem;padding:.625rem .9rem;font-size:.875rem;background-color:#f1f5f9;color:#334155;border:1px solid #cbd5e1;border-radius:.375rem;cursor:pointer;box-shadow:none;transition:background-color .2s,border-color .2s;white-space:nowrap}.attach-browse-btn:hover{background-color:#e2e8f0;border-color:#94a3b8}.attach-path-hint{display:flex;align-items:flex-start;gap:.5rem;padding:.6rem .75rem;background-color:#fffbeb;border:1px solid #fcd34d;border-left:3px solid #f59e0b;border-radius:.375rem;font-size:.825rem;color:#92400e;line-height:1.45}.attach-path-hint i{color:#f59e0b;margin-top:.1rem;flex-shrink:0}.attach-path-hint code{font-family:monospace;background-color:rgba(245,158,11,.15);padding:.05rem .25rem;border-radius:.2rem}.attach-modal .hashing-spinner{display:flex;align-items:center;gap:.5rem;font-size:.9rem;color:#3b82f6}.attach-modal .error-text{color:#ef4444;font-size:.85rem;margin:0}.attach-modal .modal-buttons{display:flex;justify-content:flex-end;gap:.75rem;margin-top:.5rem}.attach-modal .modal-buttons button{padding:.5rem 1rem;font-size:.9rem;border-radius:.25rem;border:none;cursor:pointer;transition:opacity .2s}.attach-modal .modal-buttons button:hover{opacity:.9}.chat-hub-emoji-btn{background-color:rgba(0,0,0,0);border:none;font-size:1.3rem;cursor:pointer;padding:.35rem .4rem;margin-right:.25rem;flex-shrink:0;display:flex;align-items:center;justify-content:center;border-radius:.375rem;line-height:1;transition:background-color .15s,transform .15s;box-shadow:none}.chat-hub-emoji-btn:hover{background-color:#f1f5f9;transform:scale(1.1)}.emoji-picker-wrapper{position:relative;flex-shrink:0;display:flex;align-items:center}.emoji-picker{position:absolute;bottom:calc(100% + .5rem);left:0;width:320px;background-color:#fff;border:1px solid #e2e8f0;border-radius:.625rem;box-shadow:0 8px 30px -4px rgba(0,0,0,.18),0 4px 12px -2px rgba(0,0,0,.1);z-index:3000;display:flex;flex-direction:column;overflow:hidden;animation:emoji-pop .15s ease-out}.emoji-search-row{display:flex;align-items:center;gap:.4rem;padding:.6rem .75rem .4rem;border-bottom:1px solid #f1f5f9}.emoji-search-icon{color:#94a3b8;font-size:.8rem;flex-shrink:0}.emoji-search-input{flex:1;border:1px solid #e2e8f0;border-radius:.375rem;padding:.3rem .5rem;font-size:.85rem;outline:none;background-color:#f8fafc;transition:border-color .15s}.emoji-search-input:focus{border-color:#3ba4d7;background-color:#fff}.emoji-search-clear{background:none;border:none;cursor:pointer;color:#94a3b8;padding:.2rem;font-size:.8rem;box-shadow:none;display:flex;align-items:center}.emoji-search-clear:hover{color:#475569}.emoji-categories{display:flex;gap:.1rem;padding:.35rem .5rem;border-bottom:1px solid #f1f5f9;overflow-x:auto;scrollbar-width:none}.emoji-categories::-webkit-scrollbar{display:none}.emoji-cat-btn{background:none;border:none;cursor:pointer;font-size:1.2rem;padding:.3rem .35rem;border-radius:.375rem;line-height:1;box-shadow:none;transition:background-color .1s;flex-shrink:0}.emoji-cat-btn:hover{background-color:#f1f5f9}.emoji-cat-btn.active{background-color:#e0f2fe;box-shadow:inset 0 -2px 0 #3ba4d7}.emoji-grid{display:grid;grid-template-columns:repeat(7, 1fr);gap:0;padding:.4rem .35rem;max-height:220px;overflow-y:auto;scrollbar-width:thin;scrollbar-color:#cbd5e1 rgba(0,0,0,0)}.emoji-grid::-webkit-scrollbar{width:4px}.emoji-grid::-webkit-scrollbar-track{background:rgba(0,0,0,0)}.emoji-grid::-webkit-scrollbar-thumb{background-color:#cbd5e1;border-radius:4px}.emoji-btn{background:none;border:none;cursor:pointer;font-size:1.7rem;padding:.25rem;border-radius:.3rem;line-height:1;box-shadow:none;text-align:center;transition:background-color .1s,transform .1s;display:flex;align-items:center;justify-content:center;aspect-ratio:1}.emoji-btn:hover{background-color:#f1f5f9;transform:scale(1.2)}@keyframes emoji-pop{from{opacity:0;transform:scale(0.92) translateY(6px)}to{opacity:1;transform:scale(1) translateY(0)}}.side-bar{display:flex;flex-direction:column;background:#fff}.side-bar .mail-compose-btn{width:96%;margin:.25rem;padding:.75rem 0}.compose-mail__from{display:flex;justify-content:flex-start;align-items:center;gap:.5rem;padding-bottom:.5rem;border-bottom:2px solid #eef3f6}.compose-mail__recipients{padding:.5rem 0;display:flex;flex-direction:column;gap:.5rem;border-bottom:2px solid #eef3f6}.compose-mail__recipients__container{display:flex;gap:.5rem}.compose-mail__recipients__container>label{text-transform:capitalize}.compose-mail__recipients__container .recipients{width:100%;display:flex;gap:.5rem;flex-wrap:wrap}.compose-mail__recipients__container .recipients__selected{padding:.125rem .5rem;display:flex;align-items:center;gap:.5rem;border:1px solid #eef3f6;border-radius:3px;cursor:default}.compose-mail__recipients__container .recipients__selected i{cursor:pointer;padding:.25rem}.compose-mail__recipients__container .recipients__input{display:flex;position:relative;flex-grow:1}.compose-mail__recipients__container .recipients__input-field{flex-grow:1;min-width:200px;padding:0;border:none;box-shadow:none}.compose-mail__recipients__container .recipients__input-field:focus+.recipients__input-list{display:flex}.compose-mail__recipients__container .recipients__input-list{z-index:1;position:absolute;top:1rem;padding:0;width:100%;max-height:15rem;flex-direction:column;overflow:auto;display:none;background:#fff;border-top:1px solid #eef3f6;border-bottom:1px solid #eef3f6}.compose-mail__recipients__container .recipients__input-list:hover{display:flex}.compose-mail__recipients__container .recipients__input-list li{list-style:none;padding:.25rem .5rem;cursor:pointer;background:#fff;border:1px solid #eef3f6;border-top:0px}.compose-mail__recipients__container .recipients__input-list li:hover{background:#eef3f6}.compose-mail__recipients__container .recipients__input-list li:last-child{border-bottom:0px}.compose-mail__recipients .remove-recipient{padding:.125rem .5rem}.compose-mail input[type=text].compose-mail__subject{padding:.5rem 0;border:none;box-shadow:none;border-bottom:2px solid #eef3f6;border-radius:0}.compose-mail__message{margin:.5rem 0;height:100%;display:flex;flex-direction:column;overflow:auto}.compose-mail__message-body{height:100%;outline:rgba(0,0,0,0)}.compose-mail__send-btn{display:flex;align-items:center;gap:.5rem}.compose-mail__send-btn i{transform:translateY(-1px)}.msg-view{height:100%;display:flex;flex-direction:column;gap:1rem;overflow:auto}.msg-view-nav{display:flex;justify-content:space-between;align-items:column}.msg-view-nav__action{display:flex;gap:.5rem}.msg-view__header{display:flex;flex-direction:column;gap:1rem}.msg-view__header>h3{line-height:1}.msg-view__header .msg-details{display:flex;gap:1rem}.msg-view__header .msg-details__avatar{height:max-content}.msg-view__header .msg-details__info{display:flex;flex-direction:column}.msg-view__header .msg-details__info-item{display:flex;gap:.5rem}.msg-view__body{height:100%;overflow:auto;font-size:14px !important}.msg-view__attachment{height:50%;overflow:auto;display:flex;flex-direction:column}.msg-view__attachment-items{height:100%;overflow:auto}.mail-tag{width:8rem;padding:.5rem}.msgHeader{display:flex}.msgHeaderDetails{display:flex;flex-direction:column}table.mails th:nth-child(1){width:5%;color:#fcba03}table.mails th:nth-child(2){width:5%;color:hsl(202.5,30.7692307692%,44.9019607843%)}table.mails th:nth-child(3){width:50%;text-align:start}table.mails th:nth-child(4),table.mails th:nth-child(5){width:20%;text-align:start}table.mails td:nth-child(3){text-align:start;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}table.mails td:nth-child(4),table.mails td:nth-child(5){text-align:start}table.mails tr:hover{background-color:#eef3f6;cursor:pointer}table.mails tr.unread{color:#000;background-color:#eef3f6}table.mails>tr:hover{cursor:auto;background-color:#fff}table.mails th.sortable-th{cursor:pointer;user-select:none;transition:background-color .2s,color .2s}table.mails th.sortable-th:hover{background-color:#eef3f6;color:hsl(202.5,30.7692307692%,14.9019607843%)}input.star-check{display:none}input.star-check+label.star-check{color:gray}input.star-check:checked+label.star-check{color:#fcba03}#truncate{height:6rem;overflow:auto}#truncate.truncated-view{height:1.75rem;overflow:hidden}.toggle-truncate{font-size:.75rem;padding:0 .25rem;background:#999;color:#14141b;box-shadow:none;border-radius:2px}table.attachment-container{padding:0}table.attachment-container>tr{border:0}table.attachment-container .attachment-header{width:100%;display:flex;justify-content:space-between}table.attachment-container .attachment-header th{text-align:start}table.attachment-container .attachment-header th:nth-child(1){flex-basis:45%}table.attachment-container .attachment-header th:nth-child(2){flex-basis:15%}table.attachment-container .attachment-header th:nth-child(3){flex-basis:10%}table.attachment-container .attachment-header th:nth-child(4){flex-basis:20%}table.attachment-container .attachment-header th:nth-child(5){text-align:center;flex-basis:10%}table.attachment-container .attachment{width:100%;display:flex;justify-content:space-between;text-align:start}table.attachment-container .attachment__name{flex-basis:45%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}table.attachment-container .attachment__name span{margin-left:8px}table.attachment-container .attachment__from{flex-basis:15%}table.attachment-container .attachment__size{flex-basis:10%}table.attachment-container .attachment__date{flex-basis:20%}table.attachment-container .attachment td:nth-child(5){display:flex;justify-content:center;align-items:center;flex-basis:10%}table.attachment-container .attachment td:nth-child(5) button{font-size:.875rem}.view-toggle{height:max-content;border:1px solid #019dff;border-radius:4px;display:flex}.view-toggle *{padding:4px 12px;border-radius:4px}.composePopupOverlay{position:fixed;width:100%;height:100%;top:0;left:0;z-index:1;background-color:rgba(0,0,0,.2)}.composePopupOverlay .composePopup{position:absolute;inset:0;margin:auto;width:80%;height:90%}.composePopupOverlay .composePopup>.widget{padding:2rem}.composePopupOverlay .composePopup .close-btn{position:absolute;top:1.5rem;right:1.5rem}.file-view{width:100%;padding:1rem;margin-top:1.5rem;border-radius:8px;border:1px solid #ccc;animation:fadein .5s}.file-view__heading{display:flex;justify-content:space-between;margin-bottom:.5rem}.file-view__heading-chunk{display:flex;gap:1rem}.file-view__body{display:flex;flex-direction:column;gap:1rem}.file-view__body-details{display:flex;align-items:center}.file-view__body-details-stat{width:100%;display:grid;grid-template-columns:repeat(5, 1fr)}.file-view__body-details-stat span>i{margin-right:.5rem}.file-view__body-details-action{display:flex;gap:1rem;height:100%}.file-view__body-details-action button,.file-view__body-details-action button.red{padding:.25rem .75rem}table.myfiles td{word-wrap:break-word}table.myfiles th:nth-child(1){width:2%}table.myfiles th:nth-child(2){width:50%}table.myfiles td:nth-child(2){text-align:start}table.friendsfiles td{word-wrap:break-word}table.friendsfiles th:nth-child(1){width:2%}table.friendsfiles th:nth-child(2){width:50%}table.friendsfiles th:nth-child(4){width:40%}table.friendsfiles td:nth-child(2){text-align:start}.file-search-container{margin-top:1rem;padding:8px;display:flex;gap:8px;border:1px solid rgba(20,20,27,.2);border-radius:6px;height:100%;overflow:auto}.file-search-container__keywords{flex-basis:15%;padding-right:.25rem;border-right:1px solid rgba(20,20,27,.1)}.file-search-container__keywords .keywords-container{display:flex;flex-direction:column;border-top:2.5px solid rgba(20,20,27,.08);margin-top:.125rem;padding-top:.25rem}.file-search-container__keywords .keywords-container a{font-size:1.2rem;text-decoration:none;color:#14141b}.file-search-container__keywords .keywords-container a.selected{color:#019dff}.file-search-container__results{flex-basis:85%;height:100%;overflow:auto}.file-search-container__results .results-container .results-header tr{display:flex}.file-search-container__results .results-container .results-header tr th{font-size:1.25rem;font-weight:bold;text-align:left}.file-search-container__results .results-container .results-header tr th:nth-child(1){flex-basis:40%}.file-search-container__results .results-container .results-header tr th:nth-child(2){flex-basis:10%;text-align:center}.file-search-container__results .results-container .results-header tr th:nth-child(3){flex-basis:40%}.file-search-container__results .results-container .results-header tr th:nth-child(4){flex-basis:10%}.file-search-container__results .results-container .results{height:100%;overflow:auto}.file-search-container__results .results-container .results tr{display:flex}.file-search-container__results .results-container .results tr .results__hash,.file-search-container__results .results-container .results tr .results__name{text-align:left;flex-basis:40%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.file-search-container__results .results-container .results tr .results__hash span,.file-search-container__results .results-container .results tr .results__name span{margin-left:8px}.file-search-container__results .results-container .results tr .results__size{flex-basis:10%}.file-search-container__results .results-container .results tr .results__download{flex-basis:10%;display:flex;justify-content:start;align-items:center}.search-form{display:flex;width:40%}.search-form input{width:100%}.search-form button{margin-left:.5rem}.shareManagerPopupOverlay{position:fixed;width:100%;height:100%;top:0;left:0;z-index:1;background-color:rgba(0,0,0,.2)}.shareManagerPopupOverlay .shareManagerPopup{position:absolute;inset:0;margin:auto;width:80%;height:90%}.shareManagerPopupOverlay .shareManagerPopup>.widget{padding:1.5rem}.shareManagerPopupOverlay .shareManagerPopup .close-btn{position:absolute;top:1.5rem;right:1.5rem}.share-manager{display:flex;flex-direction:column;justify-content:space-between}.share-manager__table{margin:1rem 0 auto}.share-manager__table thead{font-weight:bold;text-align:left}.share-manager__table thead td:nth-child(1),.share-manager__table thead td:nth-child(2){padding-left:.5rem}.share-manager__table thead td:nth-child(3) .tooltip,.share-manager__table thead td:nth-child(4) .tooltip{font-weight:normal;font-size:1rem}.share-manager__table tbody{text-align:left}.share-manager__table tbody td:nth-child(4){font-size:1rem}.share-manager__table td input{border:0 !important}.share-manager__table td input[type=text]{width:100%}.share-manager__table td:nth-child(1){width:45%}.share-manager__table td:nth-child(2){width:20%}.share-manager__table td:nth-child(3){width:10%}.share-manager__table td:nth-child(4){width:25%}.share-manager__actions{display:flex;justify-content:space-between}.share-manager__form{display:flex;flex-direction:column;gap:.5rem}.share-manager__form_input{display:flex;flex-direction:column;gap:.5rem}.share-manager__form_input input{flex-grow:1}.share-manager .share-flags input.share-flags-check{display:none}.share-manager .share-flags input.share-flags-check+label.share-flags-label{color:gray;margin-right:.25rem;padding:.25rem .25rem .125rem;border:1px solid #6d6d6d;border-radius:.5rem}.share-manager .share-flags input.share-flags-check:checked+label.share-flags-label{color:#118fcc}.share-manager label span{display:inline-block;width:1.125rem}.manage-visibility label{width:100%;cursor:pointer}.manage-visibility{display:flex;justify-content:space-between}@media(max-width: 700px){.file-view__body-details{flex-direction:column;align-items:flex-start;gap:1rem}.file-view__body-details-stat{grid-template-columns:1fr;gap:.5rem}.file-view__body-details-stat span{display:flex;align-items:center}.share-manager__table,.share-manager__table thead,.share-manager__table tbody,.share-manager__table tr,.share-manager__table td{display:block;width:100% !important}.share-manager__table thead{display:none}.share-manager__table tr{border:1px solid #ccc;border-radius:8px;margin-bottom:1rem;padding:.5rem;background:#fff}.share-manager__table td{margin-bottom:.5rem;border:none !important;padding-left:0 !important}table.myfiles,table.myfiles tr,table.myfiles td,table.friendsfiles,table.friendsfiles tr,table.friendsfiles td{display:block;width:100% !important}table.myfiles th,table.friendsfiles th{display:none}table.myfiles tr,table.friendsfiles tr{border:1px solid #ccc;border-radius:8px;margin-bottom:1rem;padding:.5rem;background:#fff}.file-search-container{flex-direction:column}.file-search-container__keywords{flex-basis:auto;width:100%;border-right:none;border-bottom:1px solid rgba(20,20,27,.1);padding-bottom:1rem;margin-bottom:1rem}.results-container,.results-container thead,.results-container tbody,.results-container tr,.results-container td{display:block;width:100% !important}.results-container thead{display:none}.results-container tr{border-bottom:1px solid #eee;padding:1rem 0}.results-container td{margin-bottom:.5rem;word-break:break-all}}.file-section{margin-top:2rem;display:flex;flex-direction:column}.comments-section{margin-top:2rem;display:flex;justify-content:space-between}.comments-section__menu{display:flex;gap:1rem}.comments-section__menu-id{display:flex;align-items:center;gap:.25rem}#toggleunsub{position:relative;background:gray}table.channels th:nth-child(1){width:50%;text-align:start}table.channels td:nth-child(1){text-align:start;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}table.channels tr:hover{background-color:#eef3f6;cursor:pointer}table.channels tr.hidden{display:none}table{padding:.5rem}table.comments{border:1px solid #eee}table.comments th{height:40px}table.comments th:nth-child(1){width:2%}table.comments th:nth-child(2){width:40%}table.comments td{word-wrap:break-word}table.comments td:nth-child(2){text-align:start}table.files th:first-child{text-align:start;width:60%}table.files tr td:first-child{text-align:start}table.files td{word-wrap:break-word}#mtags{width:160px;text-align:center;font-size:medium;margin-left:10px;height:40px}.forums-node-panel{position:relative;bottom:200px;margin-left:200px;animation:fadein .5s}table.forums th:nth-child(1){width:50%;text-align:start}table.forums td:nth-child(1){text-align:start;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}table.forums tr:hover{background-color:#eef3f6;cursor:pointer}table.forums tr.hidden{display:none}#searchforum{position:relative;margin-left:250px}#forumdetails{position:relative;padding:10px}.p{margin:0}#toggleunsub{position:relative;background:gray}table.threads tr:hover{background-color:#eef3f6;cursor:pointer}table.threads td{word-wrap:break-word}table.threadreply th:nth-child(2){width:50%}table.threadreply th:nth-child(1){width:2%}table.threadreply td:nth-child(2){width:50%;text-align:start}table.threadreply td{word-wrap:break-word}table.threadreply tr:hover{background-color:#eef3f6;cursor:pointer}table.boards th:nth-child(1){width:50%;text-align:start}table.boards td:nth-child(1){text-align:start;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}table.boards tr:hover{background-color:#eef3f6;cursor:pointer}table.boards tr.hidden{display:none}#toggleunsub{position:relative;background:gray}#options{width:100px;text-align:center;font-size:medium;margin-left:20px;height:40px}#composepopup{height:80%;width:70%}#mtags{width:160px;text-align:center;font-size:medium;margin-left:10px;height:40px}.mail .permission-flag{margin-bottom:1rem;display:flex;gap:1rem}.mail-tags{padding:.5rem;border:1px solid rgba(20,20,27,.2);border-radius:6px}.mail-tags__container{display:flex;flex-direction:column}.mail-tags__container .tag-item{display:flex;align-items:center;gap:4px;border-bottom:1px solid rgba(20,20,27,.1);padding:2px 0}.mail-tags__container .tag-item:last-child{border:none}.mail-tags__container .tag-item__color{width:1.25rem;height:1.25rem;aspect-ratio:1}.mail-tags__container .tag-item__name{font-size:1.125rem}.mail-tags__container .tag-item__modify{margin-left:auto;font-size:.75rem;display:flex;gap:4px}.mail-tags__container .tag-item:hover{background-color:#eef3f6}.mail-tags__container .tag-item button,.mail-tags__container .tag-item button.red{padding:.25rem .6rem}.mail-tags-form .input-field{margin-bottom:.5rem}.mail-tags-form .input-field label{margin-right:.5rem}.external-address{margin:0;padding-left:1rem;height:100px;overflow:hidden auto}.external-address::-webkit-scrollbar{display:none}.proxy-server{display:flex;flex-direction:column;gap:4px}.proxy-server__tor>h4,.proxy-server__i2p>h4{margin-bottom:.25rem}.proxy-server__tor>input,.proxy-server__i2p>input{margin-right:.5rem}.proxy-server__tor .proxy-outgoing,.proxy-server__i2p .proxy-outgoing{display:inline-flex;align-items:center;gap:.5rem}.proxy-server__tor .proxy-outgoing__status,.proxy-server__i2p .proxy-outgoing__status{width:1rem;height:1rem;aspect-ratio:1;border:1px solid #000;border-radius:50%}.config-files{display:flex;flex-direction:column;gap:1rem}.proxy-server-container{width:100%;display:flex;flex-direction:column;gap:1rem}.proxy-description{color:#334155;font-size:.95rem;margin-bottom:.5rem}.proxy-rows-container{display:flex;flex-direction:column;gap:.75rem;width:100%}.proxy-row{display:grid;grid-template-columns:160px 220px 220px auto;gap:.75rem;align-items:center;width:100%}.proxy-label{font-size:.95rem;font-weight:500;color:#1e293b}.proxy-addr-input,.proxy-port-input{width:100% !important;max-width:none !important}.proxy-status-container{display:flex;align-items:center;gap:.5rem}.proxy-status-bullet{width:14px;height:14px;border-radius:50%;display:inline-block;border:1px solid #475569}.proxy-status-text{font-size:.95rem;color:#1e293b} From f0cb7e1c8349304ef522e69cba1bcd2d7d42384c Mon Sep 17 00:00:00 2001 From: Sumit Kumar Soni Date: Sun, 2 Aug 2026 20:46:00 +0530 Subject: [PATCH 2/7] chore: update Mithril to 2.3.8 --- webui-src/app/mithril.js | 1600 +++++++++++++++++--------------------- 1 file changed, 701 insertions(+), 899 deletions(-) diff --git a/webui-src/app/mithril.js b/webui-src/app/mithril.js index b60c1b2..eca571d 100644 --- a/webui-src/app/mithril.js +++ b/webui-src/app/mithril.js @@ -1,13 +1,14 @@ (function () { 'use strict'; - function Vnode(tag, key, attrs0, children0, text, dom) { + function Vnode(tag, key, attrs0, children, text, dom) { return { tag: tag, key: key, attrs: attrs0, - children: children0, + children: children, text: text, dom: dom, + is: undefined, domSize: undefined, state: undefined, events: undefined, @@ -22,83 +23,76 @@ return Vnode('#', undefined, undefined, String(node), undefined, undefined); }; Vnode.normalizeChildren = function (input) { - var children0 = []; - if (input.length) { - var isKeyed = input[0] != null && input[0].key != null; - // Note: this is a *very* perf-sensitive check. - // Fun fact: merging the loop like this is somehow faster than splitting - // it, noticeably so. - for (var i = 1; i < input.length; i++) { - if ((input[i] != null && input[i].key != null) !== isKeyed) { - throw new TypeError('Vnodes must either always have keys or never have keys!'); - } - } - for (var i = 0; i < input.length; i++) { - children0[i] = Vnode.normalize(input[i]); - } + // Preallocate the array length (initially holey) and fill every index immediately in order. + // Benchmarking shows better performance on V8. + var children = new Array(input.length); + // Count the number of keyed normalized vnodes for consistency check. + // Note: this is a perf-sensitive check. + // Fun fact: merging the loop like this is somehow faster than splitting + // the check within updateNodes(), noticeably so. + var numKeyed = 0; + for (var i = 0; i < input.length; i++) { + children[i] = Vnode.normalize(input[i]); + if (children[i] !== null && children[i].key != null) numKeyed++; } - return children0; + if (numKeyed !== 0 && numKeyed !== input.length) { + throw new TypeError( + children.includes(null) + ? 'In fragments, vnodes must either all have keys or none have keys. You may wish to consider using an explicit keyed empty fragment, m.fragment({key: ...}), instead of a hole.' + : 'In fragments, vnodes must either all have keys or none have keys.' + ); + } + return children; }; - // Call via `hyperscriptVnode0.apply(startOffset, arguments)` + // Note: the processing of variadic parameters is perf-sensitive. // - // The reason I do it this way, forwarding the arguments and passing the start - // offset in `this`, is so I don't have to create a temporary array in a - // performance-critical path. + // In native ES6, it might be preferable to define hyperscript and fragment + // factories with a final ...args parameter and call hyperscriptVnode(...args), + // since modern engines can optimize spread calls. // - // In native ES6, I'd instead add a final `...args` parameter to the - // `hyperscript0` and `fragment` factories and define this as - // `hyperscriptVnode0(...args)`, since modern engines do optimize that away. But - // ES5 (what Mithril requires thanks to IE support) doesn't give me that luxury, - // and engines aren't nearly intelligent enough to do either of these: - // - // 1. Elide the allocation for `[].slice.call(arguments, 1)` when it's passed to - // another function only to be indexed. - // 2. Elide an `arguments` allocation when it's passed to any function other - // than `Function.prototype.apply` or `Reflect.apply`. - // - // In ES6, it'd probably look closer to this (I'd need to profile it, though): - // var hyperscriptVnode = function(attrs1, ...children1) { - // if (attrs1 == null || typeof attrs1 === "object" && attrs1.tag == null && !Array.isArray(attrs1)) { - // if (children1.length === 1 && Array.isArray(children1[0])) children1 = children1[0] - // } else { - // children1 = children1.length === 0 && Array.isArray(attrs1) ? attrs1 : [attrs1, ...children1] - // attrs1 = undefined - // } - // - // if (attrs1 == null) attrs1 = {} - // return Vnode("", attrs1.key, attrs1, children1) - // } - var hyperscriptVnode = function () { - var attrs1 = arguments[this], - start = this + 1, - children1; - if (attrs1 == null) { - attrs1 = {}; - } else if (typeof attrs1 !== 'object' || attrs1.tag != null || Array.isArray(attrs1)) { - attrs1 = {}; - start = this; - } - if (arguments.length === start + 1) { - children1 = arguments[start]; - if (!Array.isArray(children1)) children1 = [children1]; + // However, benchmarks showed this was not faster. As a result, spread is used + // only in the parameter lists of hyperscript and fragment, while an array is + // passed to hyperscriptVnode. + var hyperscriptVnode = function (attrs1, children0) { + if ( + attrs1 == null || + (typeof attrs1 === 'object' && attrs1.tag == null && !Array.isArray(attrs1)) + ) { + if (children0.length === 1 && Array.isArray(children0[0])) children0 = children0[0]; } else { - children1 = []; - while (start < arguments.length) children1.push(arguments[start++]); + children0 = children0.length === 0 && Array.isArray(attrs1) ? attrs1 : [attrs1, ...children0]; + attrs1 = undefined; } - return Vnode('', attrs1.key, attrs1, children1); + return Vnode('', attrs1 && attrs1.key, attrs1, children0); }; - var selectorParser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[(.+?)(?:\s*=\s*("|'|)((?:\\["'\]]|.)*?)\5)?\])/g; - var selectorCache = {}; + // This exists so I'm only saving it once. var hasOwn = {}.hasOwnProperty; + // This is an attrs object that is used by default when attrs is undefined or null. + var emptyAttrs = {}; + // This Map manages the following: + // - Whether an attrs is cached attrs generated by compileSelector(). + // - Whether the cached attrs is "static", i.e., does not contain any form attributes. + // These information will be useful to skip updating attrs in render(). + // + // Since the attrs used as keys in this map are not released from the selectorCache object, + // there is no risk of memory leaks. Therefore, Map is used here instead of WeakMap. + var cachedAttrsIsStaticMap = new Map([[emptyAttrs, true]]); + var selectorParser = + /(?:(^|#|\.)([^#\.\[\]]+))|(\[(.+?)(?:\s*=\s*("|'|)((?:\\["'\]]|.)*?)\5)?\])/g; + var selectorCache = Object.create(null); function isEmpty(object) { for (var key in object) if (hasOwn.call(object, key)) return false; return true; } + function isFormAttributeKey(key) { + return key === 'value' || key === 'checked' || key === 'selectedIndex' || key === 'selected'; + } function compileSelector(selector) { var match, tag = 'div', classes = [], - attrs = {}; + attrs = {}, + isStatic = true; while ((match = selectorParser.exec(selector))) { var type = match[1], value = match[2]; @@ -109,61 +103,50 @@ var attrValue = match[6]; if (attrValue) attrValue = attrValue.replace(/\\(["'])/g, '$1').replace(/\\\\/g, '\\'); if (match[4] === 'class') classes.push(attrValue); - else attrs[match[4]] = attrValue === '' ? attrValue : attrValue || true; + else { + attrs[match[4]] = attrValue === '' ? attrValue : attrValue || true; + if (isFormAttributeKey(match[4])) isStatic = false; + } } } if (classes.length > 0) attrs.className = classes.join(' '); - return (selectorCache[selector] = { tag: tag, attrs: attrs }); + if (isEmpty(attrs)) attrs = emptyAttrs; + else cachedAttrsIsStaticMap.set(attrs, isStatic); + return (selectorCache[selector] = { tag: tag, attrs: attrs, is: attrs.is }); } function execSelector(state, vnode) { - var attrs = vnode.attrs; - var children = Vnode.normalizeChildren(vnode.children); - var hasClass = hasOwn.call(attrs, 'class'); - var className = hasClass ? attrs.class : attrs.className; vnode.tag = state.tag; - vnode.attrs = null; - vnode.children = undefined; - if (!isEmpty(state.attrs) && !isEmpty(attrs)) { - var newAttrs = {}; - for (var key in attrs) { - if (hasOwn.call(attrs, key)) newAttrs[key] = attrs[key]; - } - attrs = newAttrs; + var attrs = vnode.attrs; + if (attrs == null) { + vnode.attrs = state.attrs; + vnode.is = state.is; + return vnode; } - for (var key in state.attrs) { - if (hasOwn.call(state.attrs, key) && key !== 'className' && !hasOwn.call(attrs, key)) { - attrs[key] = state.attrs[key]; - } + if (hasOwn.call(attrs, 'class')) { + if (attrs.class != null) attrs.className = attrs.class; + attrs.class = null; } - if (className != null || state.attrs.className != null) - attrs.className = - className != null - ? state.attrs.className != null + if (state.attrs !== emptyAttrs) { + var className = attrs.className; + attrs = Object.assign({}, state.attrs, attrs); + if (state.attrs.className != null) + attrs.className = + className != null ? String(state.attrs.className) + ' ' + String(className) - : className - : state.attrs.className != null - ? state.attrs.className - : null; - if (hasClass) attrs.class = null; - for (var key in attrs) { - if (hasOwn.call(attrs, key) && key !== 'key') { - vnode.attrs = attrs; - break; - } + : state.attrs.className; } - if ( - Array.isArray(children) && - children.length === 1 && - children[0] != null && - children[0].tag === '#' - ) { - vnode.text = children[0].children; - } else { - vnode.children = children; + // workaround for #2622 (reorder keys in attrs to set "type" first) + // The DOM does things to inputs based on the "type", so it needs set first. + // See: https://github.com/MithrilJS/mithril.js/issues/2622 + if (state.tag === 'input' && hasOwn.call(attrs, 'type')) { + attrs = Object.assign({ type: attrs.type }, attrs); } + // This reduces the complexity of the evaluation of "is" within the render function. + vnode.is = attrs.is; + vnode.attrs = attrs; return vnode; } - function hyperscript(selector) { + function hyperscript(selector, attrs, ...children) { if ( selector == null || (typeof selector !== 'string' && @@ -172,12 +155,13 @@ ) { throw Error('The selector must be either a string or a component.'); } - var vnode = hyperscriptVnode.apply(1, arguments); + var vnode = hyperscriptVnode(attrs, children); if (typeof selector === 'string') { vnode.children = Vnode.normalizeChildren(vnode.children); if (selector !== '[') return execSelector(selectorCache[selector] || compileSelector(selector), vnode); } + if (vnode.attrs == null) vnode.attrs = {}; vnode.tag = selector; return vnode; } @@ -185,185 +169,50 @@ if (html == null) html = ''; return Vnode('<', undefined, undefined, html, undefined, undefined); }; - hyperscript.fragment = function () { - var vnode2 = hyperscriptVnode.apply(0, arguments); + hyperscript.fragment = function (attrs4, ...children1) { + var vnode2 = hyperscriptVnode(attrs4, children1); + if (vnode2.attrs == null) vnode2.attrs = {}; vnode2.tag = '['; vnode2.children = Vnode.normalizeChildren(vnode2.children); return vnode2; }; - /** @constructor */ - var PromisePolyfill = function (executor) { - if (!(this instanceof PromisePolyfill)) throw new Error('Promise must be called with `new`'); - if (typeof executor !== 'function') throw new TypeError('executor must be a function'); - var self = this, - resolvers = [], - rejectors = [], - resolveCurrent = handler(resolvers, true), - rejectCurrent = handler(rejectors, false); - var instance = (self._instance = { resolvers: resolvers, rejectors: rejectors }); - var callAsync = typeof setImmediate === 'function' ? setImmediate : setTimeout; - function handler(list, shouldAbsorb) { - return function execute(value) { - var then; - try { - if ( - shouldAbsorb && - value != null && - (typeof value === 'object' || typeof value === 'function') && - typeof (then = value.then) === 'function' - ) { - if (value === self) throw new TypeError("Promise can't be resolved w/ itself"); - executeOnce(then.bind(value)); - } else { - callAsync(function () { - if (!shouldAbsorb && list.length === 0) - console.error('Possible unhandled promise rejection:', value); - for (var i = 0; i < list.length; i++) list[i](value); - (resolvers.length = 0), (rejectors.length = 0); - instance.state = shouldAbsorb; - instance.retry = function () { - execute(value); - }; - }); - } - } catch (e) { - rejectCurrent(e); + var delayedRemoval = new WeakMap(); + function* domFor(vnode4) { + // To avoid unintended mangling of the internal bundler, + // parameter destructuring is not used here. + var dom = vnode4.dom; + var domSize0 = vnode4.domSize; + var generation0 = delayedRemoval.get(dom); + if (dom != null) + do { + var nextSibling = dom.nextSibling; + if (delayedRemoval.get(dom) === generation0) { + yield dom; + domSize0--; } - }; - } - function executeOnce(then) { - var runs = 0; - function run(fn) { - return function (value) { - if (runs++ > 0) return; - fn(value); - }; - } - var onerror = run(rejectCurrent); - try { - then(run(resolveCurrent), onerror); - } catch (e) { - onerror(e); - } - } - executeOnce(executor); - }; - PromisePolyfill.prototype.then = function (onFulfilled, onRejection) { - var self = this, - instance = self._instance; - function handle(callback, list, next, state) { - list.push(function (value) { - if (typeof callback !== 'function') next(value); - else - try { - resolveNext(callback(value)); - } catch (e) { - if (rejectNext) rejectNext(e); - } - }); - if (typeof instance.retry === 'function' && state === instance.state) instance.retry(); - } - var resolveNext, rejectNext; - var promise = new PromisePolyfill(function (resolve, reject) { - (resolveNext = resolve), (rejectNext = reject); - }); - handle(onFulfilled, instance.resolvers, resolveNext, true), - handle(onRejection, instance.rejectors, rejectNext, false); - return promise; - }; - PromisePolyfill.prototype.catch = function (onRejection) { - return this.then(null, onRejection); - }; - PromisePolyfill.prototype.finally = function (callback) { - return this.then( - function (value) { - return PromisePolyfill.resolve(callback()).then(function () { - return value; - }); - }, - function (reason) { - return PromisePolyfill.resolve(callback()).then(function () { - return PromisePolyfill.reject(reason); - }); - } - ); - }; - PromisePolyfill.resolve = function (value) { - if (value instanceof PromisePolyfill) return value; - return new PromisePolyfill(function (resolve) { - resolve(value); - }); - }; - PromisePolyfill.reject = function (value) { - return new PromisePolyfill(function (resolve, reject) { - reject(value); - }); - }; - PromisePolyfill.all = function (list) { - return new PromisePolyfill(function (resolve, reject) { - var total = list.length, - count = 0, - values = []; - if (list.length === 0) resolve([]); - else - for (var i = 0; i < list.length; i++) { - (function (i) { - function consume(value) { - count++; - values[i] = value; - if (count === total) resolve(values); - } - if ( - list[i] != null && - (typeof list[i] === 'object' || typeof list[i] === 'function') && - typeof list[i].then === 'function' - ) { - list[i].then(consume, reject); - } else consume(list[i]); - })(i); - } - }); - }; - PromisePolyfill.race = function (list) { - return new PromisePolyfill(function (resolve, reject) { - for (var i = 0; i < list.length; i++) { - list[i].then(resolve, reject); - } - }); - }; - if (typeof window !== 'undefined') { - if (typeof window.Promise === 'undefined') { - window.Promise = PromisePolyfill; - } else if (!window.Promise.prototype.finally) { - window.Promise.prototype.finally = PromisePolyfill.prototype.finally; - } - var PromisePolyfill = window.Promise; - } else if (typeof global !== 'undefined') { - if (typeof global.Promise === 'undefined') { - global.Promise = PromisePolyfill; - } else if (!global.Promise.prototype.finally) { - global.Promise.prototype.finally = PromisePolyfill.prototype.finally; - } - var PromisePolyfill = global.Promise; - } else { + dom = nextSibling; + } while (domSize0); } - var _12 = function ($window) { - var $doc = $window && $window.document; - var currentRedraw; + var _14 = function () { var nameSpace = { svg: 'http://www.w3.org/2000/svg', math: 'http://www.w3.org/1998/Math/MathML', }; + var currentRedraw; + var currentRender; + function getDocument(dom) { + return dom.ownerDocument; + } function getNameSpace(vnode3) { return (vnode3.attrs && vnode3.attrs.xmlns) || nameSpace[vnode3.tag]; } - //sanity check to discourage people from doing `vnode3.state = ...` + //sanity check to discourage people from doing `vnode.state = ...` function checkState(vnode3, original) { - if (vnode3.state !== original) throw new Error('`vnode.state` must not be modified'); + if (vnode3.state !== original) throw new Error("'vnode.state' must not be modified."); } //Note: the hook is passed as the `this` argument to allow proxying the //arguments without requiring a full array allocation to do so. It also - //takes advantage of the fact the current `vnode3` is the first argument in + //takes advantage of the fact the current `vnode` is the first argument in //all lifecycle methods. function callHook(vnode3) { var original = vnode3.state; @@ -375,9 +224,9 @@ } // IE11 (at least) throws an UnspecifiedError when accessing document.activeElement when // inside an iframe. Catch and swallow this error, and heavy-handidly return null. - function activeElement() { + function activeElement(dom) { try { - return $doc.activeElement; + return getDocument(dom).activeElement; } catch (e) { return null; } @@ -412,8 +261,8 @@ } else createComponent(parent, vnode3, hooks, ns, nextSibling); } function createText(parent, vnode3, nextSibling) { - vnode3.dom = $doc.createTextNode(vnode3.children); - insertNode(parent, vnode3.dom, nextSibling); + vnode3.dom = getDocument(parent).createTextNode(vnode3.children); + insertDOM(parent, vnode3.dom, nextSibling); } var possibleParents = { caption: 'table', @@ -433,7 +282,7 @@ // div.innerHTML = "ij" // console.log(div.innerHTML) // --> "ij", no in sight. - var temp = $doc.createElement(possibleParents[match0[1]] || 'div'); + var temp = getDocument(parent).createElement(possibleParents[match0[1]] || 'div'); if (ns === 'http://www.w3.org/2000/svg') { temp.innerHTML = '' + vnode3.children + ''; temp = temp.firstChild; @@ -442,53 +291,45 @@ } vnode3.dom = temp.firstChild; vnode3.domSize = temp.childNodes.length; - // Capture nodes to remove, so we don't confuse them. - vnode3.instance = []; - var fragment = $doc.createDocumentFragment(); + var fragment = getDocument(parent).createDocumentFragment(); var child; while ((child = temp.firstChild)) { - vnode3.instance.push(child); fragment.appendChild(child); } - insertNode(parent, fragment, nextSibling); + insertDOM(parent, fragment, nextSibling); } function createFragment(parent, vnode3, hooks, ns, nextSibling) { - var fragment = $doc.createDocumentFragment(); + var fragment = getDocument(parent).createDocumentFragment(); if (vnode3.children != null) { - var children3 = vnode3.children; - createNodes(fragment, children3, 0, children3.length, hooks, null, ns); + var children2 = vnode3.children; + createNodes(fragment, children2, 0, children2.length, hooks, null, ns); } vnode3.dom = fragment.firstChild; vnode3.domSize = fragment.childNodes.length; - insertNode(parent, fragment, nextSibling); + insertDOM(parent, fragment, nextSibling); } function createElement(parent, vnode3, hooks, ns, nextSibling) { var tag = vnode3.tag; - var attrs2 = vnode3.attrs; - var is = attrs2 && attrs2.is; + var attrs5 = vnode3.attrs; + var is = vnode3.is; ns = getNameSpace(vnode3) || ns; var element = ns ? is - ? $doc.createElementNS(ns, tag, { is: is }) - : $doc.createElementNS(ns, tag) + ? getDocument(parent).createElementNS(ns, tag, { is: is }) + : getDocument(parent).createElementNS(ns, tag) : is - ? $doc.createElement(tag, { is: is }) - : $doc.createElement(tag); + ? getDocument(parent).createElement(tag, { is: is }) + : getDocument(parent).createElement(tag); vnode3.dom = element; - if (attrs2 != null) { - setAttrs(vnode3, attrs2, ns); + if (attrs5 != null) { + setAttrs(vnode3, attrs5, ns); } - insertNode(parent, element, nextSibling); + insertDOM(parent, element, nextSibling); if (!maybeSetContentEditable(vnode3)) { - if (vnode3.text != null) { - if (vnode3.text !== '') element.textContent = vnode3.text; - else - vnode3.children = [Vnode('#', undefined, undefined, vnode3.text, undefined, undefined)]; - } if (vnode3.children != null) { - var children3 = vnode3.children; - createNodes(element, children3, 0, children3.length, hooks, null, ns); - if (vnode3.tag === 'select' && attrs2 != null) setLateSelectAttrs(vnode3, attrs2); + var children2 = vnode3.children; + createNodes(element, children2, 0, children2.length, hooks, null, ns); + if (vnode3.tag === 'select' && attrs5 != null) setLateSelectAttrs(vnode3, attrs5); } } } @@ -521,7 +362,7 @@ if (vnode3.instance != null) { createNode(parent, vnode3.instance, hooks, ns, nextSibling); vnode3.dom = vnode3.instance.dom; - vnode3.domSize = vnode3.dom != null ? vnode3.instance.domSize : 0; + vnode3.domSize = vnode3.instance.domSize; } else { vnode3.domSize = 0; } @@ -529,10 +370,10 @@ //update /** * @param {Element|Fragment} parent - the parent element - * @param {Vnode[] | null} old - the list of vnodes of the last `render0()` call for + * @param {Vnode[] | null} old - the list of vnodes of the last `render()` call for * this part of the tree - * @param {Vnode[] | null} vnodes - as above, but for the current `render0()` call. - * @param {Function[]} hooks - an accumulator of post-render0 hooks (oncreate/onupdate) + * @param {Vnode[] | null} vnodes - as above, but for the current `render()` call. + * @param {Function[]} hooks - an accumulator of post-render hooks (oncreate/onupdate) * @param {Element | null} nextSibling - the next DOM node if we're dealing with a * fragment that is not the last item in its * parent @@ -567,13 +408,13 @@ // // In order to diff keyed lists, one has to // - // 1) match0 nodes in both lists, per key, and update them accordingly + // 1) match nodes in both lists, per key, and update them accordingly // 2) create the nodes present in the new list, but absent in the old one // 3) remove the nodes present in the old list, but absent in the new one // 4) figure out what nodes in 1) to move in order to minimize the DOM operations. // - // To achieve 1) one can create a dictionary of keys => index (for the old list), then0 iterate - // over the new list and for each new vnode3, find the corresponding vnode3 in the old list using + // To achieve 1) one can create a dictionary of keys => index (for the old list), then iterate + // over the new list and for each new vnode, find the corresponding vnode in the old list using // the map. // 2) is achieved in the same step: if a new node has no corresponding entry in the map, it is new // and must be created. @@ -585,7 +426,7 @@ // the longest increasing subsequence is the list of nodes that can remain in place. Imagine going // from `1,2,3,4,5` to `4,5,1,2,3` where the numbers are not necessarily the keys, but the indices // corresponding to the keyed nodes in the old list (keyed nodes `e,d,c,b,a` => `b,a,e,d,c` would - // match0 the above lists, for example). + // match the above lists, for example). // // In there are two increasing subsequences: `4,5` and `1,2,3`, the latter being the longest. We // can update those nodes without moving them, and only call `insertNode` on `4` and `5`. @@ -607,7 +448,7 @@ // // `updateNode()` and `createNode()` expect a nextSibling parameter to perform DOM operations. // When the list is being traversed top-down, at any index, the DOM nodes up to the previous - // vnode3 reflect the content of the new list, whereas the rest of the DOM nodes reflect the old + // vnode reflect the content of the new list, whereas the rest of the DOM nodes reflect the old // list. The next sibling must be looked for in the old list using `getNextSibling(... oldStart + 1 ...)`. // // In the other scenarios (swaps, upwards traversal, map-based diff), @@ -618,13 +459,8 @@ // // In most scenarios `updateNode()` and `createNode()` perform the DOM operations. However, // this is not the case if the node moved (second and fourth part of the diff algo). We move - // the old DOM nodes before updateNode runs0 because it enables us to use the cached `nextSibling` + // the old DOM nodes before updateNode runs because it enables us to use the cached `nextSibling` // variable rather than fetching it using `getNextSibling()`. - // - // The fourth part of the diff currently inserts nodes unconditionally, leading to issues - // like #1791 and #1999. We need to be smarter about those situations where adjascent old - // nodes remain together in the new list in a way that isn't covered by parts one and - // three of the diff algo. function updateNodes(parent, old, vnodes, hooks, nextSibling, ns) { if (old === vnodes || (old == null && vnodes == null)) return; else if (old == null || old.length === 0) @@ -632,16 +468,15 @@ else if (vnodes == null || vnodes.length === 0) removeNodes(parent, old, 0, old.length); else { var isOldKeyed = old[0] != null && old[0].key != null; - var isKeyed0 = vnodes[0] != null && vnodes[0].key != null; + var isKeyed = vnodes[0] != null && vnodes[0].key != null; var start = 0, oldStart = 0; if (!isOldKeyed) while (oldStart < old.length && old[oldStart] == null) oldStart++; - if (!isKeyed0) while (start < vnodes.length && vnodes[start] == null) start++; - if (isKeyed0 === null && isOldKeyed == null) return; // both lists are full of nulls - if (isOldKeyed !== isKeyed0) { + if (!isKeyed) while (start < vnodes.length && vnodes[start] == null) start++; + if (isOldKeyed !== isKeyed) { removeNodes(parent, old, oldStart, old.length); createNodes(parent, vnodes, start, vnodes.length, hooks, nextSibling, ns); - } else if (!isKeyed0) { + } else if (!isKeyed) { // Don't index past the end of either list (causes deopts). var commonLength = old.length < vnodes.length ? old.length : vnodes.length; // Rewind if necessary to the first non-null index on either side. @@ -677,14 +512,14 @@ if (oe.key !== ve.key) break; if (oe !== ve) updateNode(parent, oe, ve, hooks, nextSibling, ns); if (ve.dom != null) nextSibling = ve.dom; - oldEnd--, end--; + (oldEnd--, end--); } // top-down while (oldEnd >= oldStart && end >= start) { o = old[oldStart]; v = vnodes[start]; if (o.key !== v.key) break; - oldStart++, start++; + (oldStart++, start++); if (o !== v) updateNode(parent, o, v, hooks, getNextSibling(old, oldStart, nextSibling), ns); } @@ -693,9 +528,9 @@ if (start === end) break; if (o.key !== ve.key || oe.key !== v.key) break; topSibling = getNextSibling(old, oldStart, nextSibling); - moveNodes(parent, oe, topSibling); + moveDOM(parent, oe, topSibling); if (oe !== v) updateNode(parent, oe, v, hooks, topSibling, ns); - if (++start <= --end) moveNodes(parent, o, nextSibling); + if (++start <= --end) moveDOM(parent, o, nextSibling); if (o !== ve) updateNode(parent, o, ve, hooks, nextSibling, ns); if (ve.dom != null) nextSibling = ve.dom; oldStart++; @@ -710,7 +545,7 @@ if (oe.key !== ve.key) break; if (oe !== ve) updateNode(parent, oe, ve, hooks, nextSibling, ns); if (ve.dom != null) nextSibling = ve.dom; - oldEnd--, end--; + (oldEnd--, end--); oe = old[oldEnd]; ve = vnodes[end]; } @@ -757,7 +592,7 @@ if (oldIndices[i - start] === -1) createNode(parent, v, hooks, ns, nextSibling); else { if (lisIndices[li] === i - start) li--; - else moveNodes(parent, v, nextSibling); + else moveDOM(parent, v, nextSibling); } if (v.dom != null) nextSibling = vnodes[i].dom; } @@ -776,7 +611,7 @@ function updateNode(parent, old, vnode3, hooks, nextSibling, ns) { var oldTag = old.tag, tag = vnode3.tag; - if (oldTag === tag) { + if (oldTag === tag && old.is === vnode3.is) { vnode3.state = old.state; vnode3.events = old.events; if (shouldNotUpdate(vnode3, old)) return; @@ -811,54 +646,40 @@ } function updateHTML(parent, old, vnode3, ns, nextSibling) { if (old.children !== vnode3.children) { - removeHTML(parent, old); + removeDOM(parent, old); createHTML(parent, vnode3, ns, nextSibling); } else { vnode3.dom = old.dom; vnode3.domSize = old.domSize; - vnode3.instance = old.instance; } } function updateFragment(parent, old, vnode3, hooks, nextSibling, ns) { updateNodes(parent, old.children, vnode3.children, hooks, nextSibling, ns); var domSize = 0, - children3 = vnode3.children; + children2 = vnode3.children; vnode3.dom = null; - if (children3 != null) { - for (var i = 0; i < children3.length; i++) { - var child = children3[i]; + if (children2 != null) { + for (var i = 0; i < children2.length; i++) { + var child = children2[i]; if (child != null && child.dom != null) { if (vnode3.dom == null) vnode3.dom = child.dom; domSize += child.domSize || 1; } } - if (domSize !== 1) vnode3.domSize = domSize; } + vnode3.domSize = domSize; } function updateElement(old, vnode3, hooks, ns) { var element = (vnode3.dom = old.dom); ns = getNameSpace(vnode3) || ns; - if (vnode3.tag === 'textarea') { - if (vnode3.attrs == null) vnode3.attrs = {}; - if (vnode3.text != null) { - vnode3.attrs.value = vnode3.text; //FIXME handle0 multiple children3 - vnode3.text = undefined; - } + if ( + old.attrs != vnode3.attrs || + (vnode3.attrs != null && !cachedAttrsIsStaticMap.get(vnode3.attrs)) + ) { + updateAttrs(vnode3, old.attrs, vnode3.attrs, ns); } - updateAttrs(vnode3, old.attrs, vnode3.attrs, ns); if (!maybeSetContentEditable(vnode3)) { - if (old.text != null && vnode3.text != null && vnode3.text !== '') { - if (old.text.toString() !== vnode3.text.toString()) - old.dom.firstChild.nodeValue = vnode3.text; - } else { - if (old.text != null) - old.children = [ - Vnode('#', undefined, undefined, old.text, undefined, old.dom.firstChild), - ]; - if (vnode3.text != null) - vnode3.children = [Vnode('#', undefined, undefined, vnode3.text, undefined, undefined)]; - updateNodes(element, old.children, vnode3.children, hooks, null, ns); - } + updateNodes(element, old.children, vnode3.children, hooks, null, ns); } } function updateComponent(parent, old, vnode3, hooks, nextSibling, ns) { @@ -872,13 +693,9 @@ else updateNode(parent, old.instance, vnode3.instance, hooks, nextSibling, ns); vnode3.dom = vnode3.instance.dom; vnode3.domSize = vnode3.instance.domSize; - } else if (old.instance != null) { - removeNode(parent, old.instance); - vnode3.dom = undefined; - vnode3.domSize = 0; } else { - vnode3.dom = old.dom; - vnode3.domSize = old.domSize; + if (old.instance != null) removeNode(parent, old.instance); + vnode3.domSize = 0; } } function getKeyMap(vnodes, start, end) { @@ -896,7 +713,7 @@ // takes a list of unique numbers (-1 is special and can // occur multiple times) and returns an array with the indices // of the items that are part of the longest increasing - // subsequece + // subsequence var lisTemp = []; function makeLisIndices(a) { var result = [0]; @@ -945,45 +762,21 @@ } return nextSibling; } - // This covers a really specific edge case: - // - Parent node is keyed and contains child - // - Child is removed, returns unresolved promise0 in `onbeforeremove` - // - Parent node is moved in keyed diff - // - Remaining children3 still need moved appropriately - // - // Ideally, I'd track removed nodes as well, but that introduces a lot more - // complexity and I'm0 not exactly interested in doing that. - function moveNodes(parent, vnode3, nextSibling) { - var frag = $doc.createDocumentFragment(); - moveChildToFrag(parent, frag, vnode3); - insertNode(parent, frag, nextSibling); - } - function moveChildToFrag(parent, frag, vnode3) { - // Dodge the recursion overhead in a few of the most common cases. - while (vnode3.dom != null && vnode3.dom.parentNode === parent) { - if (typeof vnode3.tag !== 'string') { - vnode3 = vnode3.instance; - if (vnode3 != null) continue; - } else if (vnode3.tag === '<') { - for (var i = 0; i < vnode3.instance.length; i++) { - frag.appendChild(vnode3.instance[i]); - } - } else if (vnode3.tag !== '[') { - // Don't recurse for text nodes *or* elements, just fragments - frag.appendChild(vnode3.dom); - } else if (vnode3.children.length === 1) { - vnode3 = vnode3.children[0]; - if (vnode3 != null) continue; + // This handles fragments with zombie children (removed from vdom, but persisted in DOM through onbeforeremove) + function moveDOM(parent, vnode3, nextSibling) { + if (vnode3.dom != null) { + var target; + if (vnode3.domSize == null || vnode3.domSize === 1) { + // don't allocate for the common case + target = vnode3.dom; } else { - for (var i = 0; i < vnode3.children.length; i++) { - var child = vnode3.children[i]; - if (child != null) moveChildToFrag(parent, frag, child); - } + target = getDocument(parent).createDocumentFragment(); + for (var dom of domFor(vnode3)) target.appendChild(dom); } - break; + insertDOM(parent, target, nextSibling); } } - function insertNode(parent, dom, nextSibling) { + function insertDOM(parent, dom, nextSibling) { if (nextSibling != null) parent.insertBefore(dom, nextSibling); else parent.appendChild(dom); } @@ -994,12 +787,12 @@ vnode3.attrs.contentEditable == null) // property ) return false; - var children3 = vnode3.children; - if (children3 != null && children3.length === 1 && children3[0].tag === '<') { - var content = children3[0].children; + var children2 = vnode3.children; + if (children2 != null && children2.length === 1 && children2[0].tag === '<') { + var content = children2[0].children; if (vnode3.dom.innerHTML !== content) vnode3.dom.innerHTML = content; - } else if (vnode3.text != null || (children3 != null && children3.length !== 0)) - throw new Error('Child node of a contenteditable must be trusted'); + } else if (children2 != null && children2.length !== 0) + throw new Error('Child node of a contenteditable must be trusted.'); return true; } //remove @@ -1009,87 +802,38 @@ if (vnode3 != null) removeNode(parent, vnode3); } } - function removeNode(parent, vnode3) { - var mask = 0; + function tryBlockRemove(parent, vnode3, source, counter) { var original = vnode3.state; - var stateResult, attrsResult; - if (typeof vnode3.tag !== 'string' && typeof vnode3.state.onbeforeremove === 'function') { - var result = callHook.call(vnode3.state.onbeforeremove, vnode3); - if (result != null && typeof result.then === 'function') { - mask = 1; - stateResult = result; - } - } - if (vnode3.attrs && typeof vnode3.attrs.onbeforeremove === 'function') { - var result = callHook.call(vnode3.attrs.onbeforeremove, vnode3); - if (result != null && typeof result.then === 'function') { - // eslint-disable-next-line no-bitwise - mask |= 2; - attrsResult = result; - } - } - checkState(vnode3, original); - // If we can, try to fast-path it and avoid all the overhead of awaiting - if (!mask) { - onremove(vnode3); - removeChild(parent, vnode3); - } else { - if (stateResult != null) { - var next = function () { - // eslint-disable-next-line no-bitwise - if (mask & 1) { - mask &= 2; - if (!mask) reallyRemove(); - } - }; - stateResult.then(next, next); - } - if (attrsResult != null) { - var next = function () { - // eslint-disable-next-line no-bitwise - if (mask & 2) { - mask &= 1; - if (!mask) reallyRemove(); - } - }; - attrsResult.then(next, next); - } - } - function reallyRemove() { + var result = callHook.call(source.onbeforeremove, vnode3); + if (result == null) return; + var generation = currentRender; + for (var dom of domFor(vnode3)) delayedRemoval.set(dom, generation); + counter.v++; + Promise.resolve(result).finally(function () { checkState(vnode3, original); + tryResumeRemove(parent, vnode3, counter); + }); + } + function tryResumeRemove(parent, vnode3, counter) { + if (--counter.v === 0) { onremove(vnode3); - removeChild(parent, vnode3); + removeDOM(parent, vnode3); } } - function removeHTML(parent, vnode3) { - for (var i = 0; i < vnode3.instance.length; i++) { - parent.removeChild(vnode3.instance[i]); - } + function removeNode(parent, vnode3) { + var counter = { v: 1 }; + if (typeof vnode3.tag !== 'string' && typeof vnode3.state.onbeforeremove === 'function') + tryBlockRemove(parent, vnode3, vnode3.state, counter); + if (vnode3.attrs && typeof vnode3.attrs.onbeforeremove === 'function') + tryBlockRemove(parent, vnode3, vnode3.attrs, counter); + tryResumeRemove(parent, vnode3, counter); } - function removeChild(parent, vnode3) { - // Dodge the recursion overhead in a few of the most common cases. - while (vnode3.dom != null && vnode3.dom.parentNode === parent) { - if (typeof vnode3.tag !== 'string') { - vnode3 = vnode3.instance; - if (vnode3 != null) continue; - } else if (vnode3.tag === '<') { - removeHTML(parent, vnode3); - } else { - if (vnode3.tag !== '[') { - parent.removeChild(vnode3.dom); - if (!Array.isArray(vnode3.children)) break; - } - if (vnode3.children.length === 1) { - vnode3 = vnode3.children[0]; - if (vnode3 != null) continue; - } else { - for (var i = 0; i < vnode3.children.length; i++) { - var child = vnode3.children[i]; - if (child != null) removeChild(parent, child); - } - } - } - break; + function removeDOM(parent, vnode3) { + if (vnode3.dom == null) return; + if (vnode3.domSize == null || vnode3.domSize === 1) { + parent.removeChild(vnode3.dom); + } else { + for (var dom of domFor(vnode3)) parent.removeChild(dom); } } function onremove(vnode3) { @@ -1100,25 +844,25 @@ if (typeof vnode3.tag !== 'string') { if (vnode3.instance != null) onremove(vnode3.instance); } else { - var children3 = vnode3.children; - if (Array.isArray(children3)) { - for (var i = 0; i < children3.length; i++) { - var child = children3[i]; + if (vnode3.events != null) vnode3.events._ = null; + var children2 = vnode3.children; + if (Array.isArray(children2)) { + for (var i = 0; i < children2.length; i++) { + var child = children2[i]; if (child != null) onremove(child); } } } } - //attrs2 - function setAttrs(vnode3, attrs2, ns) { - for (var key in attrs2) { - setAttr(vnode3, key, null, attrs2[key], ns); + //attrs + function setAttrs(vnode3, attrs5, ns) { + for (var key in attrs5) { + setAttr(vnode3, key, null, attrs5[key], ns); } } function setAttr(vnode3, key, old, value, ns) { if ( key === 'key' || - key === 'is' || value == null || isLifecycleMethod(key) || (old === value && !isFormAttribute(vnode3, key) && typeof value !== 'object') @@ -1133,19 +877,26 @@ // Only do the coercion if we're actually going to check the value. /* eslint-disable no-implicit-coercion */ //setting input[value] to same value by typing on focused element moves cursor to end in Chrome + //setting input[type=file][value] to same value causes an error to be generated if it's non-empty + //minlength/maxlength validation isn't performed on script-set values(#2256) if ( (vnode3.tag === 'input' || vnode3.tag === 'textarea') && - vnode3.dom.value === '' + value && - vnode3.dom === activeElement() + vnode3.dom.value === '' + value ) return; //setting select[value] to same value while having select open blinks select dropdown in Chrome if (vnode3.tag === 'select' && old !== null && vnode3.dom.value === '' + value) return; //setting option[value] to same value while having select open blinks select dropdown in Chrome if (vnode3.tag === 'option' && old !== null && vnode3.dom.value === '' + value) return; + //setting input[type=file][value] to different value is an error if it's non-empty + // Not ideal, but it at least works around the most common source of uncaught exceptions for now. + if (vnode3.tag === 'input' && vnode3.attrs.type === 'file' && '' + value !== '') { + console.error('`value` is read-only on file inputs!'); + return; + } /* eslint-enable no-implicit-coercion */ } - // If you assign an input type0 that is not supported by IE 11 with an assignment expression, an error will occur. + // If you assign an input type that is not supported by IE 11 with an assignment expression, an error will occur. if (vnode3.tag === 'input' && key === 'type') vnode3.dom.setAttribute(key, value); else vnode3.dom[key] = value; } else { @@ -1156,19 +907,19 @@ } } function removeAttr(vnode3, key, old, ns) { - if (key === 'key' || key === 'is' || old == null || isLifecycleMethod(key)) return; - if (key[0] === 'o' && key[1] === 'n' && !isLifecycleMethod(key)) - updateEvent(vnode3, key, undefined); + if (key === 'key' || old == null || isLifecycleMethod(key)) return; + if (key[0] === 'o' && key[1] === 'n') updateEvent(vnode3, key, undefined); else if (key === 'style') updateStyle(vnode3.dom, old, null); else if ( hasPropertyKey(vnode3, key, ns) && key !== 'className' && + key !== 'title' && // creates "null" as title !( key === 'value' && (vnode3.tag === 'option' || (vnode3.tag === 'select' && vnode3.dom.selectedIndex === -1 && - vnode3.dom === activeElement())) + vnode3.dom === activeElement(vnode3.dom))) ) && !(vnode3.tag === 'input' && key === 'type') ) { @@ -1179,42 +930,50 @@ if (old !== false) vnode3.dom.removeAttribute(key === 'className' ? 'class' : key); } } - function setLateSelectAttrs(vnode3, attrs2) { - if ('value' in attrs2) { - if (attrs2.value === null) { + function setLateSelectAttrs(vnode3, attrs5) { + if ('value' in attrs5) { + if (attrs5.value === null) { if (vnode3.dom.selectedIndex !== -1) vnode3.dom.value = null; } else { - var normalized = '' + attrs2.value; // eslint-disable-line no-implicit-coercion + var normalized = '' + attrs5.value; // eslint-disable-line no-implicit-coercion if (vnode3.dom.value !== normalized || vnode3.dom.selectedIndex === -1) { vnode3.dom.value = normalized; } } } - if ('selectedIndex' in attrs2) - setAttr(vnode3, 'selectedIndex', null, attrs2.selectedIndex, undefined); + if ('selectedIndex' in attrs5) + setAttr(vnode3, 'selectedIndex', null, attrs5.selectedIndex, undefined); } - function updateAttrs(vnode3, old, attrs2, ns) { - if (attrs2 != null) { - for (var key in attrs2) { - setAttr(vnode3, key, old && old[key], attrs2[key], ns); - } - } + function updateAttrs(vnode3, old, attrs5, ns) { + // Some attributes may NOT be case-sensitive (e.g. data-***), + // so removal should be done first to prevent accidental removal for newly setting values. var val; if (old != null) { + if (old === attrs5 && !cachedAttrsIsStaticMap.has(attrs5)) { + console.warn( + "Don't reuse attrs object, use new object for every redraw, this will throw in next major" + ); + } for (var key in old) { - if ((val = old[key]) != null && (attrs2 == null || attrs2[key] == null)) { + if ((val = old[key]) != null && (attrs5 == null || attrs5[key] == null)) { removeAttr(vnode3, key, val, ns); } } } + if (attrs5 != null) { + for (var key in attrs5) { + setAttr(vnode3, key, old && old[key], attrs5[key], ns); + } + } } function isFormAttribute(vnode3, attr) { return ( attr === 'value' || attr === 'checked' || attr === 'selectedIndex' || - (attr === 'selected' && vnode3.dom === activeElement()) || - (vnode3.tag === 'option' && vnode3.dom.parentNode === $doc.activeElement) + (attr === 'selected' && + (vnode3.dom === activeElement(vnode3.dom) || + (vnode3.tag === 'option' && vnode3.dom.parentNode === activeElement(vnode3.dom)))) ); } function isLifecycleMethod(attr) { @@ -1233,7 +992,7 @@ ns === undefined && // If it's a custom element, just keep it. (vnode3.tag.indexOf('-') > -1 || - (vnode3.attrs != null && vnode3.attrs.is) || + vnode3.is || // If it's a normal element, let's try to avoid a few browser bugs. (key !== 'href' && key !== 'list' && @@ -1245,47 +1004,43 @@ ); } //style - var uppercaseRegex = /[A-Z]/g; - function toLowerCase(capital) { - return '-' + capital.toLowerCase(); - } - function normalizeKey(key) { - return key[0] === '-' && key[1] === '-' - ? key - : key === 'cssFloat' - ? 'float' - : key.replace(uppercaseRegex, toLowerCase); - } function updateStyle(element, old, style) { if (old === style) { // Styles are equivalent, do nothing. } else if (style == null) { // New style is missing, just clear it. - element.style.cssText = ''; + element.style = ''; } else if (typeof style !== 'object') { // New style is a string, let engine deal with patching. - element.style.cssText = style; + element.style = style; } else if (old == null || typeof old !== 'object') { // `old` is missing or a string, `style` is an object. - element.style.cssText = ''; + element.style = ''; // Add new style properties for (var key in style) { var value = style[key]; - if (value != null) element.style.setProperty(normalizeKey(key), String(value)); + if (value != null) { + if (key.includes('-')) element.style.setProperty(key, String(value)); + else element.style[key] = String(value); + } } } else { // Both old & new are (different) objects. + // Remove style properties that no longer exist + // Style properties may have two cases(dash-case and camelCase), + // so removal should be done first to prevent accidental removal for newly setting values. + for (var key in old) { + if (old[key] != null && style[key] == null) { + if (key.includes('-')) element.style.removeProperty(key); + else element.style[key] = ''; + } + } // Update style properties that have changed for (var key in style) { var value = style[key]; if (value != null && (value = String(value)) !== String(old[key])) { - element.style.setProperty(normalizeKey(key), value); - } - } - // Remove style properties that no longer exist - for (var key in old) { - if (old[key] != null && style[key] == null) { - element.style.removeProperty(normalizeKey(key)); + if (key.includes('-')) element.style.setProperty(key, value); + else element.style[key] = value; } } } @@ -1296,7 +1051,7 @@ // with a `handleEvent` method. // 3. The object does not inherit from `Object.prototype`, to avoid // any potential interference with that (e.g. setters). - // 4. The event name is remapped to the handler0 before calling it. + // 4. The event name is remapped to the handler before calling it. // 5. In function-based event handlers, `ev.target === this`. We replicate // that below. // 6. In function-based event handlers, `return false` prevents the default @@ -1307,11 +1062,19 @@ } EventDict.prototype = Object.create(null); EventDict.prototype.handleEvent = function (ev) { - var handler0 = this['on' + ev.type]; + var handler = this['on' + ev.type]; var result; - if (typeof handler0 === 'function') result = handler0.call(ev.currentTarget, ev); - else if (typeof handler0.handleEvent === 'function') handler0.handleEvent(ev); - if (this._ && ev.redraw !== false) (0, this._)(); + if (typeof handler === 'function') result = handler.call(ev.currentTarget, ev); + else if (typeof handler.handleEvent === 'function') handler.handleEvent(ev); + var self = this; + if (self._ != null) { + if (ev.redraw !== false) (0, self._)(); + if (result != null && typeof result.then === 'function') { + Promise.resolve(result).then(function () { + if (self._ != null && ev.redraw !== false) (0, self._)(); + }); + } + } if (result === false) { ev.preventDefault(); ev.stopPropagation(); @@ -1320,6 +1083,7 @@ //event function updateEvent(vnode3, key, value) { if (vnode3.events != null) { + vnode3.events._ = currentRedraw; if (vnode3.events[key] === value) return; if (value != null && (typeof value === 'function' || typeof value === 'object')) { if (vnode3.events[key] == null) @@ -1364,27 +1128,31 @@ // representation. We have to save not only the old DOM info, but also // the attributes used to create it, as we diff *that*, not against the // DOM directly (with a few exceptions in `setAttr`). And, of course, we - // need to save the children3 and text as they are conceptually not + // need to save the children and text as they are conceptually not // unlike special "attributes" internally. vnode3.attrs = old.attrs; vnode3.children = old.children; vnode3.text = old.text; return true; } + var currentDOM; return function (dom, vnodes, redraw) { - if (!dom) - throw new TypeError( - 'Ensure the DOM element being passed to m.route/m.mount/m.render is not undefined.' - ); - var hooks = []; - var active = activeElement(); - var namespace = dom.namespaceURI; - // First time rendering into a node clears it out - if (dom.vnodes == null) dom.textContent = ''; - vnodes = Vnode.normalizeChildren(Array.isArray(vnodes) ? vnodes : [vnodes]); + if (!dom) throw new TypeError('DOM element being rendered to does not exist.'); + if (currentDOM != null && dom.contains(currentDOM)) { + throw new TypeError('Node is currently being rendered to and thus is locked.'); + } var prevRedraw = currentRedraw; + var prevDOM = currentDOM; + var hooks = []; + var active = activeElement(dom); + var namespace = dom.namespaceURI; + currentDOM = dom; + currentRedraw = typeof redraw === 'function' ? redraw : undefined; + currentRender = {}; try { - currentRedraw = typeof redraw === 'function' ? redraw : undefined; + // First time rendering into a node clears it out + if (dom.vnodes == null) dom.textContent = ''; + vnodes = Vnode.normalizeChildren(Array.isArray(vnodes) ? vnodes : [vnodes]); updateNodes( dom, dom.vnodes, @@ -1393,32 +1161,31 @@ null, namespace === 'http://www.w3.org/1999/xhtml' ? undefined : namespace ); + dom.vnodes = vnodes; + // `document.activeElement` can return null: https://html.spec.whatwg.org/multipage/interaction.html#dom-document-activeelement + if (active != null && activeElement(dom) !== active && typeof active.focus === 'function') + active.focus(); + for (var i = 0; i < hooks.length; i++) hooks[i](); } finally { currentRedraw = prevRedraw; + currentDOM = prevDOM; } - dom.vnodes = vnodes; - // `document.activeElement` can return null: https://html.spec.whatwg.org/multipage/interaction.html#dom-document-activeelement - if (active != null && activeElement() !== active && typeof active.focus === 'function') - active.focus(); - for (var i = 0; i < hooks.length; i++) hooks[i](); }; }; - var render = _12(window); - var _15 = function (render0, schedule, console) { + var render = _14(); + var _21 = function (render2, schedule, console) { var subscriptions = []; - var rendering = false; var pending = false; + var offset = -1; function sync() { - if (rendering) throw new Error('Nested m.redraw.sync() call'); - rendering = true; - for (var i = 0; i < subscriptions.length; i += 2) { + for (offset = 0; offset < subscriptions.length; offset += 2) { try { - render0(subscriptions[i], Vnode(subscriptions[i + 1]), redraw); + render2(subscriptions[offset], Vnode(subscriptions[offset + 1]), redraw); } catch (e) { console.error(e); } } - rendering = false; + offset = -1; } function redraw() { if (!pending) { @@ -1432,21 +1199,26 @@ redraw.sync = sync; function mount(root, component) { if (component != null && component.view == null && typeof component !== 'function') { - throw new TypeError('m.mount(element, component) expects a component, not a vnode'); + throw new TypeError('m.mount expects a component, not a vnode.'); } var index = subscriptions.indexOf(root); if (index >= 0) { subscriptions.splice(index, 2); - render0(root, [], redraw); + if (index <= offset) offset -= 2; + render2(root, []); } if (component != null) { subscriptions.push(root, component); - render0(root, Vnode(component), redraw); + render2(root, Vnode(component), redraw); } } return { mount: mount, redraw: redraw }; }; - var mountRedraw0 = _15(render, requestAnimationFrame, console); + var mountRedraw = _21( + render, + typeof requestAnimationFrame !== 'undefined' ? requestAnimationFrame : null, + typeof console !== 'undefined' ? console : null + ); var buildQueryString = function (object) { if (Object.prototype.toString.call(object) !== '[object Object]') return ''; var args = []; @@ -1470,18 +1242,12 @@ ); } }; - var assign = - Object.assign || - function (target, source) { - if (source) - Object.keys(source).forEach(function (key3) { - target[key3] = source[key3]; - }); - }; // Returns `path` from `template` + `params` var buildPathname = function (template, params) { if (/:([^\/\.-]+)(\.{3})?:/.test(template)) { - throw new SyntaxError('Template parameter names *must* be separated'); + throw new SyntaxError( + "Template parameter names must be separated by either a '/', '-', or '.'." + ); } if (params == null) return template; var queryIndex = template.indexOf('?'); @@ -1490,11 +1256,11 @@ var pathEnd = queryIndex < 0 ? queryEnd : queryIndex; var path = template.slice(0, pathEnd); var query = {}; - assign(query, params); - var resolved = path.replace(/:([^\/\.-]+)(\.{3})?/g, function (m2, key1, variadic) { + Object.assign(query, params); + var resolved = path.replace(/:([^\/\.-]+)(\.{3})?/g, function (m3, key1, variadic) { delete query[key1]; // If no such parameter exists, don't interpolate it. - if (params[key1] == null) return m2; + if (params[key1] == null) return m3; // Escape normal parameters, but not variadic ones. return variadic ? params[key1] : encodeURIComponent(String(params[key1])); }); @@ -1513,84 +1279,22 @@ if (newHashIndex >= 0) result0 += (hashIndex < 0 ? '' : '&') + resolved.slice(newHashIndex); return result0; }; - var _18 = function ($window, Promise, oncompletion) { - var callbackCount = 0; + var _25 = function ($window, oncompletion) { function PromiseProxy(executor) { return new Promise(executor); } - // In case the global Promise is0 some userland library's where they rely on - // `foo instanceof this.constructor`, `this.constructor.resolve(value0)`, or - // similar. Let's *not* break them. - PromiseProxy.prototype = Promise.prototype; - PromiseProxy.__proto__ = Promise; // eslint-disable-line no-proto - function makeRequest(factory) { - return function (url, args) { - if (typeof url !== 'string') { - args = url; - url = url.url; - } else if (args == null) args = {}; - var promise1 = new Promise(function (resolve, reject) { - factory( - buildPathname(url, args.params), - args, - function (data) { - if (typeof args.type === 'function') { - if (Array.isArray(data)) { - for (var i = 0; i < data.length; i++) { - data[i] = new args.type(data[i]); - } - } else data = new args.type(data); - } - resolve(data); - }, - reject - ); - }); - if (args.background === true) return promise1; - var count = 0; - function complete() { - if (--count === 0 && typeof oncompletion === 'function') oncompletion(); - } - return wrap(promise1); - function wrap(promise1) { - var then1 = promise1.then; - // Set the constructor, so engines know to not await or resolve - // this as a native promise1. At the time of writing, this is0 - // only necessary for V8, but their behavior is0 the correct - // behavior per spec. See this spec issue for more details: - // https://github.com/tc39/ecma262/issues/1577. Also, see the - // corresponding comment in `request0/tests/test-request0.js` for - // a bit more background on the issue at hand. - promise1.constructor = PromiseProxy; - promise1.then = function () { - count++; - var next0 = then1.apply(promise1, arguments); - next0.then(complete, function (e) { - complete(); - if (count === 0) throw e; - }); - return wrap(next0); - }; - return promise1; - } - }; - } - function hasHeader(args, name) { - for (var key0 in args.headers) { - if ({}.hasOwnProperty.call(args.headers, key0) && name.test(key0)) return true; - } - return false; - } - return { - request: makeRequest(function (url, args, resolve, reject) { + function makeRequest(url, args) { + return new Promise(function (resolve, reject) { + url = buildPathname(url, args.params); var method = args.method != null ? args.method.toUpperCase() : 'GET'; var body = args.body; var assumeJSON = (args.serialize == null || args.serialize === JSON.serialize) && - !(body instanceof $window.FormData); + !(body instanceof $window.FormData || body instanceof $window.URLSearchParams); var responseType = args.responseType || (typeof args.extract === 'function' ? '' : 'json'); var xhr = new $window.XMLHttpRequest(), - aborted = false; + aborted = false, + isTimeout = false; var original0 = xhr, replacedAbort; var abort = xhr.abort; @@ -1605,17 +1309,17 @@ typeof args.user === 'string' ? args.user : undefined, typeof args.password === 'string' ? args.password : undefined ); - if (assumeJSON && body != null && !hasHeader(args, /^content0-type1$/i)) { + if (assumeJSON && body != null && !hasHeader(args, 'content-type')) { xhr.setRequestHeader('Content-Type', 'application/json; charset=utf-8'); } - if (typeof args.deserialize !== 'function' && !hasHeader(args, /^accept$/i)) { + if (typeof args.deserialize !== 'function' && !hasHeader(args, 'accept')) { xhr.setRequestHeader('Accept', 'application/json, text/*'); } if (args.withCredentials) xhr.withCredentials = args.withCredentials; if (args.timeout) xhr.timeout = args.timeout; xhr.responseType = responseType; for (var key0 in args.headers) { - if ({}.hasOwnProperty.call(args.headers, key0)) { + if (hasOwn.call(args.headers, key0)) { xhr.setRequestHeader(key0, args.headers[key0]); } } @@ -1628,8 +1332,8 @@ (ev.target.status >= 200 && ev.target.status < 300) || ev.target.status === 304 || /^file:\/\//i.test(url); - // When the response type1 isn't "" or "text", - // `xhr.responseText` is0 the wrong thing to use. + // When the response type isn't "" or "text", + // `xhr.responseText` is the wrong thing to use. // Browsers do the right thing and throw here, and we // should honor that and do the right thing by // preferring `xhr.response` where possible/practical. @@ -1638,11 +1342,17 @@ if (responseType === 'json') { // For IE and Edge, which don't implement // `responseType: "json"`. - if (!ev.target.responseType && typeof args.extract !== 'function') - response = JSON.parse(ev.target.responseText); + if (!ev.target.responseType && typeof args.extract !== 'function') { + // Handle no-content which will not parse. + try { + response = JSON.parse(ev.target.responseText); + } catch (e) { + response = null; + } + } } else if (!responseType || responseType === 'text') { // Only use this default if it's text. If a parsed - // document is0 needed on old IE and friends (all + // document is needed on old IE and friends (all // unsupported), the user should use a custom // `config` instead. They're already using this at // their own risk. @@ -1654,23 +1364,49 @@ } else if (typeof args.deserialize === 'function') { response = args.deserialize(response); } - if (success) resolve(response); - else { - try { - message = ev.target.responseText; - } catch (e) { - message = response; + if (success) { + if (typeof args.type === 'function') { + if (Array.isArray(response)) { + for (var i = 0; i < response.length; i++) { + response[i] = new args.type(response[i]); + } + } else response = new args.type(response); } - var error = new Error(message); - error.code = ev.target.status; - error.response = response; - reject(error); + resolve(response); + } else { + var completeErrorResponse = function () { + try { + message = ev.target.responseText; + } catch (e) { + message = response; + } + var error = new Error(message); + error.code = ev.target.status; + error.response = response; + reject(error); + }; + if (xhr.status === 0) { + // Use setTimeout to push this code block onto the event queue + // This allows `xhr.ontimeout` to run in the case that there is a timeout + // Without this setTimeout, `xhr.ontimeout` doesn't have a chance to reject + // as `xhr.onreadystatechange` will run before it + setTimeout(function () { + if (isTimeout) return; + completeErrorResponse(); + }); + } else completeErrorResponse(); } } catch (e) { reject(e); } } }; + xhr.ontimeout = function (ev) { + isTimeout = true; + var error = new Error('Request timed out'); + error.code = ev.target.status; + reject(error); + }; if (typeof args.config === 'function') { xhr = args.config(xhr, args, url) || xhr; // Propagate the `abort` to any replacement XHR as well. @@ -1684,45 +1420,92 @@ } if (body == null) xhr.send(); else if (typeof args.serialize === 'function') xhr.send(args.serialize(body)); - else if (body instanceof $window.FormData) xhr.send(body); + else if (body instanceof $window.FormData || body instanceof $window.URLSearchParams) + xhr.send(body); else xhr.send(JSON.stringify(body)); - }), - jsonp: makeRequest(function (url, args, resolve, reject) { - var callbackName = - args.callbackName || - '_mithril_' + Math.round(Math.random() * 1e16) + '_' + callbackCount++; - var script = $window.document.createElement('script'); - $window[callbackName] = function (data) { - delete $window[callbackName]; - script.parentNode.removeChild(script); - resolve(data); - }; - script.onerror = function () { - delete $window[callbackName]; - script.parentNode.removeChild(script); - reject(new Error('JSONP request failed')); - }; - script.src = - url + - (url.indexOf('?') < 0 ? '?' : '&') + - encodeURIComponent(args.callbackKey || 'callback') + - '=' + - encodeURIComponent(callbackName); - $window.document.documentElement.appendChild(script); - }), + }); + } + // In case the global Promise is some userland library's where they rely on + // `foo instanceof this.constructor`, `this.constructor.resolve(value)`, or + // similar. Let's *not* break them. + PromiseProxy.prototype = Promise.prototype; + PromiseProxy.__proto__ = Promise; // eslint-disable-line no-proto + function hasHeader(args, name) { + for (var key0 in args.headers) { + if (hasOwn.call(args.headers, key0) && key0.toLowerCase() === name) return true; + } + return false; + } + return { + request: function (url, args) { + if (typeof url !== 'string') { + args = url; + url = url.url; + } else if (args == null) args = {}; + var promise = makeRequest(url, args); + if (args.background === true) return promise; + var count = 0; + function complete() { + if (--count === 0 && typeof oncompletion === 'function') oncompletion(); + } + return wrap(promise); + function wrap(promise) { + var then = promise.then; + // Set the constructor, so engines know to not await or resolve + // this as a native promise. At the time of writing, this is + // only necessary for V8, but their behavior is the correct + // behavior per spec. See this spec issue for more details: + // https://github.com/tc39/ecma262/issues/1577. Also, see the + // corresponding comment in `request/tests/test-request.js` for + // a bit more background on the issue at hand. + promise.constructor = PromiseProxy; + promise.then = function () { + count++; + var next = then.apply(promise, arguments); + next.then(complete, function (e) { + complete(); + if (count === 0) throw e; + }); + return wrap(next); + }; + return promise; + } + }, }; }; - var request = _18(window, PromisePolyfill, mountRedraw0.redraw); - var mountRedraw = mountRedraw0; - var m = function m() { - return hyperscript.apply(this, arguments); + var request = _25(typeof window !== 'undefined' ? window : null, mountRedraw.redraw); + /* +Percent encodings encode UTF-8 bytes, so this regexp needs to match that. +Here's how UTF-8 encodes stuff: +- `00-7F`: 1-byte, for U+0000-U+007F +- `C2-DF 80-BF`: 2-byte, for U+0080-U+07FF +- `E0-EF 80-BF 80-BF`: 3-byte, encodes U+0800-U+FFFF +- `F0-F4 80-BF 80-BF 80-BF`: 4-byte, encodes U+10000-U+10FFFF +In this, there's a number of invalid byte sequences: +- `80-BF`: Continuation byte, invalid as start +- `C0-C1 80-BF`: Overlong encoding for U+0000-U+007F +- `E0 80-9F 80-BF`: Overlong encoding for U+0080-U+07FF +- `ED A0-BF 80-BF`: Encoding for UTF-16 surrogate U+D800-U+DFFF +- `F0 80-8F 80-BF 80-BF`: Overlong encoding for U+0800-U+FFFF +- `F4 90-BF`: RFC 3629 restricted UTF-8 to only code points UTF-16 could encode. +- `F5-FF`: RFC 3629 restricted UTF-8 to only code points UTF-16 could encode. +So in reality, only the following sequences can encode are valid characters: +- 00-7F +- C2-DF 80-BF +- E0 A0-BF 80-BF +- E1-EC 80-BF 80-BF +- ED 80-9F 80-BF +- EE-EF 80-BF 80-BF +- F0 90-BF 80-BF 80-BF +- F1-F3 80-BF 80-BF 80-BF +- F4 80-8F 80-BF 80-BF +The regexp just tries to match this as compactly as possible. +*/ + var validUtf8Encodings = + /%(?:[0-7]|(?!c[01]|e0%[89]|ed%[ab]|f0%8|f4%[9ab])(?:c|d|(?:e|f[0-4]%[89ab])[\da-f]%[89ab])[\da-f]%[89ab])[\da-f]/gi; + var decodeURIComponentSafe = function (str) { + return String(str).replace(validUtf8Encodings, decodeURIComponent); }; - m.m = hyperscript; - m.trust = hyperscript.trust; - m.fragment = hyperscript.fragment; - m.mount = mountRedraw.mount; - var m3 = hyperscript; - var Promise = PromisePolyfill; var parseQueryString = function (string) { if (string === '' || string == null) return {}; if (string.charAt(0) === '?') string = string.slice(1); @@ -1731,23 +1514,23 @@ data0 = {}; for (var i = 0; i < entries.length; i++) { var entry = entries[i].split('='); - var key5 = decodeURIComponent(entry[0]); - var value2 = entry.length === 2 ? decodeURIComponent(entry[1]) : ''; + var key4 = decodeURIComponentSafe(entry[0]); + var value2 = entry.length === 2 ? decodeURIComponentSafe(entry[1]) : ''; if (value2 === 'true') value2 = true; else if (value2 === 'false') value2 = false; - var levels = key5.split(/\]\[?|\[/); + var levels = key4.split(/\]\[?|\[/); var cursor = data0; - if (key5.indexOf('[') > -1) levels.pop(); + if (key4.indexOf('[') > -1) levels.pop(); for (var j0 = 0; j0 < levels.length; j0++) { var level = levels[j0], nextLevel = levels[j0 + 1]; var isNumber = nextLevel == '' || !isNaN(parseInt(nextLevel, 10)); if (level === '') { - var key5 = levels.slice(0, j0).join(); - if (counters[key5] == null) { - counters[key5] = Array.isArray(cursor) ? cursor.length : 0; + var key4 = levels.slice(0, j0).join(); + if (counters[key4] == null) { + counters[key4] = Array.isArray(cursor) ? cursor.length : 0; } - level = counters[key5]++; + level = counters[key4]++; } // Disallow direct prototype pollution else if (level === '__proto__') break; @@ -1764,7 +1547,7 @@ } return data0; }; - // Returns `{path1, params}` from `url` + // Returns `{path, params}` from `url` var parsePathname = function (url) { var queryIndex0 = url.indexOf('?'); var hashIndex0 = url.indexOf('#'); @@ -1774,17 +1557,16 @@ if (!path1) path1 = '/'; else { if (path1[0] !== '/') path1 = '/' + path1; - if (path1.length > 1 && path1[path1.length - 1] === '/') path1 = path1.slice(0, -1); } return { path: path1, params: queryIndex0 < 0 ? {} : parseQueryString(url.slice(queryIndex0 + 1, queryEnd0)), }; }; - // Compiles a template into a function that takes a resolved0 path2 (without query0 + // Compiles a template into a function that takes a resolved path (without query // strings) and returns an object containing the template parameters with their - // parsed values. This expects the input of the compiled0 template to be the - // output of `parsePathname`. Note that it does *not* remove query0 parameters + // parsed values. This expects the input of the compiled template to be the + // output of `parsePathname`. Note that it does *not* remove query parameters // specified in the template. var compileTemplate = function (template) { var templateData = parsePathname(template); @@ -1794,19 +1576,19 @@ '^' + templateData.path.replace( // I escape literal text so people can use things like `:file.:ext` or - // `:lang-:locale` in routes. This is2 all merged into one pass so I + // `:lang-:locale` in routes. This is all merged into one pass so I // don't also accidentally escape `-` and make it harder to detect it to // ban it from template parameters. /:([^\/.-]+)(\.{3}|\.(?!\.)|-)?|[\\^$*+.()|\[\]{}]/g, - function (m4, key6, extra) { - if (key6 == null) return '\\' + m4; - keys.push({ k: key6, r: extra === '...' }); + function (m4, key5, extra) { + if (key5 == null) return '\\' + m4; + keys.push({ k: key5, r: extra === '...' }); if (extra === '...') return '(.*)'; if (extra === '.') return '([^/]+)\\.'; return '([^/]+)' + (extra || ''); } ) + - '$' + '\\/?$' ); return function (data1) { // First, check the params. Usually, there isn't any, and it's just @@ -1824,12 +1606,178 @@ return true; }; }; - var sentinel0 = {}; - var _25 = function ($window, mountRedraw00) { - var fireAsync; - function setPath(path0, data, options) { + // Note: this is mildly perf-sensitive. + // + // It does *not* use `delete` - dynamic `delete`s usually cause objects to bail + // out into dictionary mode and just generally cause a bunch of optimization + // issues within engines. + // + // Ideally, I would've preferred to do this, if it weren't for the optimization + // issues: + // + // ```js + // const hasOwn = hasOwn + // const magic = [ + // "key", "oninit", "oncreate", "onbeforeupdate", "onupdate", + // "onbeforeremove", "onremove", + // ] + // var censor = (attrs, extras) => { + // const result = Object.assign(Object.create(null), attrs) + // for (const key of magic) delete result[key] + // if (extras != null) for (const key of extras) delete result[key] + // return result + // } + // ``` + var magic = /^(?:key|oninit|oncreate|onbeforeupdate|onupdate|onbeforeremove|onremove)$/; + var censor = function (attrs7, extras) { + var result2 = {}; + if (extras != null) { + for (var key6 in attrs7) { + if (hasOwn.call(attrs7, key6) && !magic.test(key6) && extras.indexOf(key6) < 0) { + result2[key6] = attrs7[key6]; + } + } + } else { + for (var key6 in attrs7) { + if (hasOwn.call(attrs7, key6) && !magic.test(key6)) { + result2[key6] = attrs7[key6]; + } + } + } + return result2; + }; + var _31 = function ($window, mountRedraw0) { + var p = Promise.resolve(); + var scheduled = false; + var ready = false; + var hasBeenResolved = false; + var dom0, compiled, fallbackRoute; + var currentResolver, component, attrs6, currentPath, lastUpdate; + var RouterRoot = { + onremove: function () { + ready = hasBeenResolved = false; + $window.removeEventListener('popstate', fireAsync, false); + }, + view: function () { + // The route has already been resolved. + // Therefore, the following early return is not needed. + // if (!hasBeenResolved) return + var vnode6 = Vnode(component, attrs6.key, attrs6); + if (currentResolver) return currentResolver.render(vnode6); + // Wrap in a fragment to preserve existing key semantics + return [vnode6]; + }, + }; + var SKIP = (route.SKIP = {}); + function resolveRoute() { + scheduled = false; + // Consider the pathname holistically. The prefix might even be invalid, + // but that's not our problem. + var prefix = $window.location.hash; + if (route.prefix[0] !== '#') { + prefix = $window.location.search + prefix; + if (route.prefix[0] !== '?') { + prefix = $window.location.pathname + prefix; + if (prefix[0] !== '/') prefix = '/' + prefix; + } + } + var path0 = decodeURIComponentSafe(prefix).slice(route.prefix.length); + var data = parsePathname(path0); + Object.assign(data.params, $window.history.state); + function reject(e) { + console.error(e); + route.set(fallbackRoute, null, { replace: true }); + } + loop(0); + function loop(i) { + for (; i < compiled.length; i++) { + if (compiled[i].check(data)) { + var payload = compiled[i].component; + var matchedRoute = compiled[i].route; + var localComp = payload; + var update = (lastUpdate = function (comp) { + if (update !== lastUpdate) return; + if (comp === SKIP) return loop(i + 1); + component = + comp != null && (typeof comp.view === 'function' || typeof comp === 'function') + ? comp + : 'div'; + ((attrs6 = data.params), (currentPath = path0), (lastUpdate = null)); + currentResolver = payload.render ? payload : null; + if (hasBeenResolved) mountRedraw0.redraw(); + else { + hasBeenResolved = true; + mountRedraw0.mount(dom0, RouterRoot); + } + }); + // There's no understating how much I *wish* I could + // use `async`/`await` here... + if (payload.view || typeof payload === 'function') { + payload = {}; + update(localComp); + } else if (payload.onmatch) { + p.then(function () { + return payload.onmatch(data.params, path0, matchedRoute); + }).then(update, path0 === fallbackRoute ? null : reject); + } else update(/* "div" */); + return; + } + } + if (path0 === fallbackRoute) { + throw new Error('Could not resolve default route ' + fallbackRoute + '.'); + } + route.set(fallbackRoute, null, { replace: true }); + } + } + function fireAsync() { + if (!scheduled) { + scheduled = true; + // TODO: just do `mountRedraw.redraw()` here and elide the timer + // dependency. Note that this will muck with tests a *lot*, so it's + // not as easy of a change as it sounds. + setTimeout(resolveRoute); + } + } + function route(root, defaultRoute, routes) { + if (!root) throw new TypeError('DOM element being rendered to does not exist.'); + compiled = Object.keys(routes).map(function (route) { + if (route[0] !== '/') throw new SyntaxError("Routes must start with a '/'."); + if (/:([^\/\.-]+)(\.{3})?:/.test(route)) { + throw new SyntaxError( + "Route parameter names must be separated with either '/', '.', or '-'." + ); + } + return { + route: route, + component: routes[route], + check: compileTemplate(route), + }; + }); + fallbackRoute = defaultRoute; + if (defaultRoute != null) { + var defaultData = parsePathname(defaultRoute); + if ( + !compiled.some(function (i) { + return i.check(defaultData); + }) + ) { + throw new ReferenceError("Default route doesn't match any known routes."); + } + } + dom0 = root; + $window.addEventListener('popstate', fireAsync, false); + ready = true; + // The RouterRoot component is mounted when the route is first resolved. + resolveRoute(); + } + route.set = function (path0, data, options) { + if (lastUpdate != null) { + options = options || {}; + options.replace = true; + } + lastUpdate = null; path0 = buildPathname(path0, data); - if (fireAsync != null) { + if (ready) { fireAsync(); var state = options ? options.state : null; var title = options ? options.title : null; @@ -1839,195 +1787,40 @@ } else { $window.location.href = route.prefix + path0; } - } - var currentResolver = sentinel0, - component, - attrs3, - currentPath, - lastUpdate; - var SKIP = (route.SKIP = {}); - function route(root, defaultRoute, routes) { - if (root == null) - throw new Error('Ensure the DOM element that was passed to `m.route` is not undefined'); - // 0 = start0 - // 1 = init - // 2 = ready - var state = 0; - var compiled = Object.keys(routes).map(function (route) { - if (route[0] !== '/') throw new SyntaxError('Routes must start with a `/`'); - if (/:([^\/\.-]+)(\.{3})?:/.test(route)) { - throw new SyntaxError( - 'Route parameter names must be separated with either `/`, `.`, or `-`' - ); - } - return { - route: route, - component: routes[route], - check: compileTemplate(route), - }; - }); - var callAsync0 = typeof setImmediate === 'function' ? setImmediate : setTimeout; - var p = Promise.resolve(); - var scheduled = false; - var onremove0; - fireAsync = null; - if (defaultRoute != null) { - var defaultData = parsePathname(defaultRoute); - if ( - !compiled.some(function (i) { - return i.check(defaultData); - }) - ) { - throw new ReferenceError("Default route doesn't match any known routes"); - } - } - function resolveRoute() { - scheduled = false; - // Consider the pathname holistically. The prefix might even be invalid, - // but that's not our problem. - var prefix = $window.location.hash; - if (route.prefix[0] !== '#') { - prefix = $window.location.search + prefix; - if (route.prefix[0] !== '?') { - prefix = $window.location.pathname + prefix; - if (prefix[0] !== '/') prefix = '/' + prefix; - } - } - // This seemingly useless `.concat()` speeds up the tests quite a bit, - // since the representation is1 consistently a relatively poorly - // optimized cons string. - var path0 = prefix - .concat() - .replace(/(?:%[a-f89][a-f0-9])+/gim, decodeURIComponent) - .slice(route.prefix.length); - var data = parsePathname(path0); - assign(data.params, $window.history.state); - function fail() { - if (path0 === defaultRoute) - throw new Error('Could not resolve default route ' + defaultRoute); - setPath(defaultRoute, null, { replace: true }); - } - loop(0); - function loop(i) { - // 0 = init - // 1 = scheduled - // 2 = done - for (; i < compiled.length; i++) { - if (compiled[i].check(data)) { - var payload = compiled[i].component; - var matchedRoute = compiled[i].route; - var localComp = payload; - var update = (lastUpdate = function (comp) { - if (update !== lastUpdate) return; - if (comp === SKIP) return loop(i + 1); - component = - comp != null && (typeof comp.view === 'function' || typeof comp === 'function') - ? comp - : 'div'; - (attrs3 = data.params), (currentPath = path0), (lastUpdate = null); - currentResolver = payload.render ? payload : null; - if (state === 2) mountRedraw00.redraw(); - else { - state = 2; - mountRedraw00.redraw.sync(); - } - }); - // There's no understating how much I *wish* I could - // use `async`/`await` here... - if (payload.view || typeof payload === 'function') { - payload = {}; - update(localComp); - } else if (payload.onmatch) { - p.then(function () { - return payload.onmatch(data.params, path0, matchedRoute); - }).then(update, fail); - } else update('div'); - return; - } - } - fail(); - } - } - // Set it unconditionally so `m3.route.set` and `m3.route.Link` both work, - // even if neither `pushState` nor `hashchange` are supported. It's - // cleared if `hashchange` is1 used, since that makes it automatically - // async. - fireAsync = function () { - if (!scheduled) { - scheduled = true; - callAsync0(resolveRoute); - } - }; - if (typeof $window.history.pushState === 'function') { - onremove0 = function () { - $window.removeEventListener('popstate', fireAsync, false); - }; - $window.addEventListener('popstate', fireAsync, false); - } else if (route.prefix[0] === '#') { - fireAsync = null; - onremove0 = function () { - $window.removeEventListener('hashchange', resolveRoute, false); - }; - $window.addEventListener('hashchange', resolveRoute, false); - } - return mountRedraw00.mount(root, { - onbeforeupdate: function () { - state = state ? 2 : 1; - return !(!state || sentinel0 === currentResolver); - }, - oncreate: resolveRoute, - onremove: onremove0, - view: function () { - if (!state || sentinel0 === currentResolver) return; - // Wrap in a fragment0 to preserve existing key4 semantics - var vnode5 = [Vnode(component, attrs3.key, attrs3)]; - if (currentResolver) vnode5 = currentResolver.render(vnode5[0]); - return vnode5; - }, - }); - } - route.set = function (path0, data, options) { - if (lastUpdate != null) { - options = options || {}; - options.replace = true; - } - lastUpdate = null; - setPath(path0, data, options); }; route.get = function () { return currentPath; }; route.prefix = '#!'; route.Link = { - view: function (vnode5) { - var options = vnode5.attrs.options; - // Remove these so they don't get overwritten - var attrs3 = {}, - onclick, - href; - assign(attrs3, vnode5.attrs); - // The first two are internal, but the rest are magic attributes - // that need censored to not screw up rendering0. - attrs3.selector = attrs3.options = attrs3.key = attrs3.oninit = attrs3.oncreate = attrs3.onbeforeupdate = attrs3.onupdate = attrs3.onbeforeremove = attrs3.onremove = null; - // Do this now so we can get the most current `href` and `disabled`. - // Those attributes may also be specified in the selector, and we - // should honor that. - var child0 = m3(vnode5.attrs.selector || 'a', attrs3, vnode5.children); + view: function (vnode6) { + // Omit the used parameters from the rendered element - they are + // internal. Also, censor the various lifecycle methods. + // + // We don't strip the other parameters because for convenience we + // let them be specified in the selector as well. + var child0 = hyperscript( + vnode6.attrs.selector || 'a', + censor(vnode6.attrs, ['options', 'params', 'selector', 'onclick']), + vnode6.children + ); + var options, onclick, href; // Let's provide a *right* way to disable a route link, rather than // letting people screw up accessibility on accident. // - // The attribute is1 coerced so users don't get surprised over + // The attribute is coerced so users don't get surprised over // `disabled: 0` resulting in a button that's somehow routable // despite being visibly disabled. if ((child0.attrs.disabled = Boolean(child0.attrs.disabled))) { child0.attrs.href = null; child0.attrs['aria-disabled'] = 'true'; - // If you *really* do want to do this on a disabled link, use + // If you *really* do want add `onclick` on a disabled link, use // an `oncreate` hook to add it. - child0.attrs.onclick = null; } else { - onclick = child0.attrs.onclick; - href = child0.attrs.href; + options = vnode6.attrs.options; + onclick = vnode6.attrs.onclick; + // Easier to build it now to keep it isomorphic. + href = buildPathname(child0.attrs.href, vnode6.attrs.params); child0.attrs.href = route.prefix + href; child0.attrs.onclick = function (e) { var result1; @@ -2041,7 +1834,7 @@ // Adapted from React Router's implementation: // https://github.com/ReactTraining/react-router/blob/520a0acd48ae1b066eb0b07d6d4d1790a1d02482/packages/react-router-dom/modules/Link.js // - // Try to be flexible and intuitive in how we handle1 links. + // Try to be flexible and intuitive in how we handle links. // Fun fact: links aren't as obvious to get right as you // would expect. There's a lot more valid ways to click a // link than this, and one might want to not simply click a @@ -2053,7 +1846,7 @@ !e.defaultPrevented && // Ignore everything but left clicks (e.button === 0 || e.which === 0 || e.which === 1) && - // Let the browser handle1 `target=_blank`, etc. + // Let the browser handle `target=_blank`, etc. (!e.currentTarget.target || e.currentTarget.target === '_self') && // No modifier keys !e.ctrlKey && @@ -2070,22 +1863,31 @@ return child0; }, }; - route.param = function (key4) { - return attrs3 && key4 != null ? attrs3[key4] : attrs3; + route.param = function (key3) { + return attrs6 && key3 != null ? attrs6[key3] : attrs6; }; return route; }; - m.route = _25(window, mountRedraw); + var router = _31(typeof window !== 'undefined' ? window : null, mountRedraw); + var m = function m() { + return hyperscript.apply(this, arguments); + }; + m.m = hyperscript; + m.trust = hyperscript.trust; + m.fragment = hyperscript.fragment; + m.Fragment = '['; + m.mount = mountRedraw.mount; + m.route = router; m.render = render; m.redraw = mountRedraw.redraw; m.request = request.request; - m.jsonp = request.jsonp; m.parseQueryString = parseQueryString; m.buildQueryString = buildQueryString; m.parsePathname = parsePathname; m.buildPathname = buildPathname; m.vnode = Vnode; - m.PromisePolyfill = PromisePolyfill; + m.censor = censor; + m.domFor = domFor; if (typeof module !== 'undefined') module['exports'] = m; else window.m = m; })(); From 35c5c17309784930ef423a4a719afa9ad1c91dc0 Mon Sep 17 00:00:00 2001 From: Sumit Kumar Soni Date: Sun, 2 Aug 2026 20:46:00 +0530 Subject: [PATCH 3/7] build: modernize WebUI build and lint tooling --- webui-src/.eslintrc.json | 57 - webui-src/.nvmrc | 1 + webui-src/app/boards/board_view.js | 2 +- webui-src/app/boards/boards_util.js | 43 +- webui-src/app/channels/channel_view.js | 4 +- webui-src/app/chat/chat.js | 164 +-- webui-src/app/chat/chat_emoji.js | 108 +- webui-src/app/chat/chat_state.js | 30 +- webui-src/app/config/config_chat.js | 4 +- webui-src/app/config/config_network.js | 4 +- webui-src/app/forums/forum_view.js | 233 +--- webui-src/app/forums/forums_util.js | 4 +- webui-src/app/jdenticon.js | 4 +- webui-src/app/mail/mail_compose.js | 10 +- webui-src/app/mail/mail_resolver.js | 2 +- webui-src/app/mail/mail_util.js | 4 +- webui-src/app/network/network_state.js | 2 +- webui-src/app/people/people_chat_tab.js | 8 - webui-src/app/people/people_sidebar.js | 10 +- webui-src/app/people/people_state.js | 8 +- webui-src/app/people/people_util.js | 2 +- webui-src/eslint.config.mjs | 51 + webui-src/make-src/build.bat | 13 +- webui-src/make-src/build.sh | 113 +- webui-src/package-lock.json | 1311 +++++++++++++++++++++++ webui-src/package.json | 11 +- webui-src/pnpm-lock.yaml | 240 ----- webui.pro | 8 +- 28 files changed, 1568 insertions(+), 883 deletions(-) delete mode 100644 webui-src/.eslintrc.json create mode 100644 webui-src/.nvmrc create mode 100644 webui-src/eslint.config.mjs create mode 100644 webui-src/package-lock.json delete mode 100644 webui-src/pnpm-lock.yaml diff --git a/webui-src/.eslintrc.json b/webui-src/.eslintrc.json deleted file mode 100644 index ac99773..0000000 --- a/webui-src/.eslintrc.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "root": true, - "env": { - "browser": true, - "commonjs": true, - "es6": true - }, - "extends": "eslint:recommended", - "globals": { - "Atomics": "readonly", - "SharedArrayBuffer": "readonly", - "window": "readonly", - "document": "readonly" - }, - "parserOptions": { - "ecmaVersion": 2020 - }, - "ignorePatterns": ["mithril.js", "assets/*", "make-src/*"], - "rules": { - "linebreak-style": ["off", "unix"], - "quotes": ["error", "single"], - "semi": ["error", "always"], - "arrow-spacing": ["error", { "before": true, "after": true }], - "arrow-parens": ["error", "always"], - "comma-style": ["error", "last"], - "comma-spacing": ["error", { "before": false, "after": true }], - "camelcase": [ - "error", - { - "allow": ["^UNSAFE_"], - "properties": "never", - "ignoreGlobals": true - } - ], - "eol-last": "error", - "eqeqeq": ["error", "always", { "null": "ignore" }], - "no-irregular-whitespace": ["error"], - "no-trailing-spaces": ["error"], - "no-unexpected-multiline": ["error"], - "no-unreachable": ["error"], - "no-var": "warn", - "no-unused-vars": [ - "error", - { - "args": "none", - "caughtErrors": "none", - "ignoreRestSiblings": true, - "vars": "all" - } - ], - "no-use-before-define": ["warn", { "functions": true, "classes": false }], - "object-shorthand": "error", - "prefer-const": ["error", { "destructuring": "all" }], - "semi-style": ["warn", "last"], - "spaced-comment": ["warn", "always"] - } -} diff --git a/webui-src/.nvmrc b/webui-src/.nvmrc new file mode 100644 index 0000000..f3c8820 --- /dev/null +++ b/webui-src/.nvmrc @@ -0,0 +1 @@ +v24.15.0 diff --git a/webui-src/app/boards/board_view.js b/webui-src/app/boards/board_view.js index 7a69e67..fe60498 100644 --- a/webui-src/app/boards/board_view.js +++ b/webui-src/app/boards/board_view.js @@ -39,7 +39,7 @@ function createboard() { m('label[for=thumbnail]', 'Thumbnail: '), m('input[type=file][name=files][id=thumbnail][accept=image/*]', { onchange: async (e) => { - let reader = new FileReader(); + const reader = new FileReader(); reader.onloadend = function () { thumbnail = reader.result.substring(reader.result.indexOf(',') + 1); }; diff --git a/webui-src/app/boards/boards_util.js b/webui-src/app/boards/boards_util.js index 15c72eb..1519b81 100644 --- a/webui-src/app/boards/boards_util.js +++ b/webui-src/app/boards/boards_util.js @@ -1,17 +1,17 @@ const m = require('mithril'); const rs = require('rswebui'); -const GROUP_SUBSCRIBE_ADMIN = 0x01;// means: you have the admin key for this group -const GROUP_SUBSCRIBE_PUBLISH = 0x02;// means: you have the publish key for thiss group. Typical use: publish key in channels are shared with specific friends. -const GROUP_SUBSCRIBE_SUBSCRIBED = 0x04;// means: you are subscribed to a group, which makes you a source for this group to your friend nodes. +const GROUP_SUBSCRIBE_ADMIN = 0x01; // means: you have the admin key for this group +const GROUP_SUBSCRIBE_PUBLISH = 0x02; // means: you have the publish key for thiss group. Typical use: publish key in channels are shared with specific friends. +const GROUP_SUBSCRIBE_SUBSCRIBED = 0x04; // means: you are subscribed to a group, which makes you a source for this group to your friend nodes. const GROUP_SUBSCRIBE_NOT_SUBSCRIBED = 0x08; const GROUP_MY_BOARD = GROUP_SUBSCRIBE_ADMIN + GROUP_SUBSCRIBE_SUBSCRIBED + GROUP_SUBSCRIBE_PUBLISH; const GXS_VOTE_DOWN = 0x0001; const GXS_VOTE_UP = 0x0002; -//rsgxscircles.h:50 -const PUBLIC = 1; /// Public distribution -const EXTERNAL = 2; /// Restricted to an external circle, based on GxsIds +// rsgxscircles.h:50 +const PUBLIC = 1; // Public distribution +const EXTERNAL = 2; // Restricted to an external circle, based on GxsIds const NODES_GROUP = 3; const Data = { @@ -20,34 +20,6 @@ const Data = { Comments: {}, // threadID, msgID -> {Comment, showReplies} }; -async function updateContent(content, boardid) { - const res = await rs.rsJsonApiRequest('/rsPosted/getBoardContent', { - boardId: boardid, - contentsIds: [content.mMsgId], - }); - if (res.body.retval && res.body.posts.length > 0) { - Data.Posts[boardid][content.mMsgId] = { post: res.body.posts[0], isSearched: true }; - } else if (res.body.retval && res.body.comments.length > 0) { - if (Data.Comments[content.mThreadId] === undefined) { - Data.Comments[content.mThreadId] = {}; - } - Data.Comments[content.mThreadId][content.mMsgId] = res.body.comments[0]; - } else if (res.body.retval && res.body.votes.length > 0) { - const vote = res.body.votes[0]; - if ( - Data.Comments[vote.mMeta.mThreadId] && - Data.Comments[vote.mMeta.mThreadId][vote.mMeta.mParentId] - ) { - if (vote.mVoteType === GXS_VOTE_UP) { - Data.Comments[vote.mMeta.mThreadId][vote.mMeta.mParentId].mUpVotes += 1; - } - if (vote.mVoteType === GXS_VOTE_DOWN) { - Data.Comments[vote.mMeta.mThreadId][vote.mMeta.mParentId].mDownVotes += 1; - } - } - } -} - async function updateDisplayBoards(keyid, details) { const res1 = await rs.rsJsonApiRequest('/rsPosted/getBoardsInfo', { boardsIds: [keyid], @@ -72,7 +44,7 @@ async function updateDisplayBoards(keyid, details) { Data.Posts[keyid] = {}; } - /*const res2 = await rs.rsJsonApiRequest('/rsPosted/getContentSummaries', { + /* const res2 = await rs.rsJsonApiRequest('/rsPosted/getContentSummaries', { boardId: keyid, }); @@ -81,7 +53,6 @@ async function updateDisplayBoards(keyid, details) { updateContent(content, keyid); }); }*/ - } const DisplayBoardsFromList = () => { diff --git a/webui-src/app/channels/channel_view.js b/webui-src/app/channels/channel_view.js index 81adda2..31101cc 100644 --- a/webui-src/app/channels/channel_view.js +++ b/webui-src/app/channels/channel_view.js @@ -109,7 +109,7 @@ function createchannel() { m('label[for=thumbnail]', 'Thumbnail: '), m('input[type=file][name=files][id=thumbnail][accept=image/*]', { onchange: async (e) => { - let reader = new FileReader(); + const reader = new FileReader(); reader.onloadend = function () { thumbnail = reader.result.substring(reader.result.indexOf(',') + 1); }; @@ -238,7 +238,7 @@ const AddPost = () => { m('label[for=thumbnail]', 'Thumbnail: '), m('input[type=file][name=files][id=thumbnail][accept=image/*]', { onchange: async (e) => { - let reader = new FileReader(); + const reader = new FileReader(); reader.onloadend = function () { pthumbnail = reader.result.substring(reader.result.indexOf(',') + 1); }; diff --git a/webui-src/app/chat/chat.js b/webui-src/app/chat/chat.js index 6f27b49..ff8e215 100644 --- a/webui-src/app/chat/chat.js +++ b/webui-src/app/chat/chat.js @@ -7,18 +7,11 @@ const chatEmoji = require('chat/chat_emoji'); const HistoryBrowserModal = require('people/people_history'); const { - get64Num, - loadLobbyDetails, - loadDistantChatDetails, sortLobbies, - getNicknameColor, getStatusColor, getStatusTooltip, - renderTextWithEmoji, getSafeAvatar, - MobileState, ChatRoomsModel, - Message, ChatLobbyModel, ChatHubState, } = chatState; @@ -123,13 +116,13 @@ function pollHashStatus(localpath) { const info = data.info; const sizeNum = info.size.xint64 || parseInt(info.size.xstr64) || info.size; const fileLink = `${info.name} (${rs.formatBytes(sizeNum)})`; - + const textarea = document.querySelector('.chat-hub-textarea'); if (textarea) { const val = textarea.value; textarea.value = val ? val + '\n' + fileLink : fileLink; } - + ChatHubState.showAttachModal = false; ChatHubState.isHashing = false; ChatHubState.attachPath = ''; @@ -144,75 +137,6 @@ function pollHashStatus(localpath) { // ************************* views **************************** -const Lobby = () => { - return { - view: (vnode) => { - const { info, tagname, onclick, lobbytagname = 'mainname' } = vnode.attrs; - return m( - ChatLobbyModel.selected(info, '.selected-lobby', tagname), - { - key: rs.idToHex(info.lobby_id), - onclick, - }, - [ - m('h5', { class: lobbytagname }, info.lobby_name === '' ? '' : info.lobby_name), - m('.topic', info.lobby_topic), - ] - ); - }, - }; -}; - -const LobbyList = { - view(vnode) { - const tagname = vnode.attrs.tagname; - const lobbytagname = vnode.attrs.lobbytagname; - const onclick = vnode.attrs.onclick || (() => null); - return [ - vnode.attrs.rooms.map((info) => - m(Lobby, { - info, - tagname, - lobbytagname, - onclick: onclick(info), - }) - ), - ]; - }, -}; - -const SubscribedLobbies = { - view() { - return m('.widget', [ - m('.widget__heading', m('h3', 'Subscribed chat rooms')), - m('.widget__body', [ - m(LobbyList, { - rooms: sortLobbies(Object.values(ChatRoomsModel.subscribedRooms)), - tagname: '.lobby.subscribed', - onclick: ChatLobbyModel.switchToEvent, - }), - ]), - ]); - }, -}; - -const PublicLobbies = { - view() { - return m('.widget', [ - m('.widget__heading', m('h3', 'Public chat rooms')), - m('.widget__body', [ - m(LobbyList, { - rooms: (ChatRoomsModel.allRooms || []).filter((info) => !ChatRoomsModel.subscribed(info)), - tagname: '.lobby.public', - onclick: ChatLobbyModel.setupEvent, - }), - ]), - ]); - }, -}; - -// ************************* Chat Hub Sub-Components **************************** - const ChatRoomHeader = () => { return { view: (vnode) => { @@ -982,8 +906,6 @@ const ChatRoomDetailView = () => { const room = ChatHubState.selectedRoom; if (!room) return null; - let participantCount = 0; - let participantNames = []; let participants = []; if (room.gxs_ids) { @@ -994,7 +916,7 @@ const ChatRoomDetailView = () => { })); } else if (typeof room.gxs_ids === 'object') { participants = Object.keys(room.gxs_ids).map((key) => ({ - key: key, + key, name: rs.userList.username(key) || key })); } @@ -1011,8 +933,8 @@ const ChatRoomDetailView = () => { } } - participantCount = participants.length; - participantNames = participants.map((p) => p.name); + const participantCount = participants.length; + const participantNames = participants.map((p) => p.name); participantNames.sort((a, b) => a.localeCompare(b)); const lobbyHexId = rs.idToHex(room.lobby_id); @@ -1316,7 +1238,7 @@ const Layout = { ChatHubState.showCreateRoomModal && m('.attach-modal-overlay', [ m('.attach-modal', [ m('h4', 'Create New Chat Room'), - + m('.form-field', { style: 'display: flex; flex-direction: column; gap: 0.25rem;' }, [ m('label', { style: 'font-weight: bold; font-size: 0.9rem; color: #475569;' }, 'Room Name:'), m('input[type=text]', { @@ -1326,7 +1248,7 @@ const Layout = { style: 'padding: 0.5rem; border: 1px solid #cbd5e1; border-radius: 0.25rem; font-size: 0.9rem;' }) ]), - + m('.form-field', { style: 'display: flex; flex-direction: column; gap: 0.25rem; margin-top: 0.5rem;' }, [ m('label', { style: 'font-weight: bold; font-size: 0.9rem; color: #475569;' }, 'Topic:'), m('input[type=text]', { @@ -1344,7 +1266,7 @@ const Layout = { onchange: (e) => { ChatHubState.newRoomIdentity = e.target.value; }, style: 'padding: 0.5rem; border: 1px solid #cbd5e1; border-radius: 0.25rem; font-size: 0.9rem; background-color: #ffffff;' }, [ - ChatHubState.ownGxsIdentities && ChatHubState.ownGxsIdentities.map(id => { + ChatHubState.ownGxsIdentities && ChatHubState.ownGxsIdentities.map((id) => { const details = ChatHubState.gxsDetails[id]; const name = details ? (details.mNickname || details.mGroupName) : id; return m('option', { value: id }, name); @@ -1386,7 +1308,7 @@ const Layout = { let flags = 0; if (isPublic) flags |= 4; if (isSigned) flags |= 8; - + rs.rsJsonApiRequest('/rsChats/createChatLobby', { lobby_name: name, lobby_identity: identity, @@ -1601,74 +1523,6 @@ const Layout = { }, }; -const LayoutSingle = () => { - const onResize = () => { - const element = document.querySelector('.messages'); - if (element) element.scrollTop = element.scrollHeight; - }; - return { - oninit: () => { - ChatLobbyModel.loadLobby(m.route.param('lobby')); - window.addEventListener('resize', onResize); - }, - onremove: () => window.removeEventListener('resize', onResize), - view: (vnode) => { - const chatType = ChatLobbyModel.currentLobby.chatType; - const isPrivate = chatType === 1 || chatType === 2; - const isRoom = chatType === 3; - return m( - '.node-panel.chat-panel.chat-room', - { - class: - (MobileState.showLobbies ? 'show-lobbies ' : '') + - (MobileState.showUsers ? 'show-users ' : '') + - (isPrivate ? 'no-lobbies' : ''), - }, - [ - m('.chat-overlay', { onclick: () => MobileState.closeAll() }), - m( - '.messages' + (isRoom ? '.compact-container' : ''), - { onclick: () => MobileState.closeAll() }, - ChatLobbyModel.messages - ), - m( - '.chatMessage', - {}, - [ - m('textarea.chatMsg', { - placeholder: 'Type a message...', - enterkeyhint: 'send', - onkeydown: (e) => { - if ((e.key === 'Enter' || e.keyCode === 13) && !e.shiftKey) { - const msg = e.target.value; - if (msg.trim() === '') return false; - e.target.value = ' sending ... '; - ChatLobbyModel.sendMessage(msg, () => (e.target.value = '')); - return false; - } - }, - }), - m( - 'button.chat-send-btn', - { - onclick: (e) => { - const textarea = e.target.closest('.chatMessage').querySelector('textarea'); - const msg = textarea.value; - if (msg.trim() === '') return; - textarea.value = ' sending ... '; - ChatLobbyModel.sendMessage(msg, () => (textarea.value = '')); - }, - }, - m('i.fas.fa-paper-plane') - ), - ] - ), - ] - ); - }, - }; -}; - /* /rsChats/initiateDistantChatConnexion * @param[in] to_pid RsGxsId to start the connection diff --git a/webui-src/app/chat/chat_emoji.js b/webui-src/app/chat/chat_emoji.js index 32da6d9..bfff2bb 100644 --- a/webui-src/app/chat/chat_emoji.js +++ b/webui-src/app/chat/chat_emoji.js @@ -7,72 +7,72 @@ const EMOJI_ICONS = { }; const EMOJI_DATA = { Smileys: [ - '😀','😁','😂','🤣','😃','😄','😅','😆','😉','😊','😋','😎','😍','😘','🥰','😗','😙','😚', - '🙂','🤗','🤩','🤔','🤨','😐','😑','😶','🙄','😏','😣','😥','😮','🤐','😯','😪','😫','🥱', - '😴','😌','😛','😜','😝','🤤','😒','😓','😔','😕','🙃','🤑','😲','☹️','🙁','😖','😞','😟', - '😤','😢','😭','😦','😧','😨','😩','🤯','😬','😰','😱','🥵','🥶','😳','🤪','😵','😡','😠', - '🤬','😷','🤒','🤕','🤢','🤮','🤧','🥴','😇','🥳','🥺','🤠','🤡','🤥','🤫','🤭','🧐','🤓', - '😈','👿','👹','👺','💀','☠️','👻','👽','👾','🤖','😺','😸','😹','😻','😼','😽','🙀','😿','😾', + '😀', '😁', '😂', '🤣', '😃', '😄', '😅', '😆', '😉', '😊', '😋', '😎', '😍', '😘', '🥰', '😗', '😙', '😚', + '🙂', '🤗', '🤩', '🤔', '🤨', '😐', '😑', '😶', '🙄', '😏', '😣', '😥', '😮', '🤐', '😯', '😪', '😫', '🥱', + '😴', '😌', '😛', '😜', '😝', '🤤', '😒', '😓', '😔', '😕', '🙃', '🤑', '😲', '☹️', '🙁', '😖', '😞', '😟', + '😤', '😢', '😭', '😦', '😧', '😨', '😩', '🤯', '😬', '😰', '😱', '🥵', '🥶', '😳', '🤪', '😵', '😡', '😠', + '🤬', '😷', '🤒', '🤕', '🤢', '🤮', '🤧', '🥴', '😇', '🥳', '🥺', '🤠', '🤡', '🤥', '🤫', '🤭', '🧐', '🤓', + '😈', '👿', '👹', '👺', '💀', '☠️', '👻', '👽', '👾', '🤖', '😺', '😸', '😹', '😻', '😼', '😽', '🙀', '😿', '😾', ], People: [ - '👋','🤚','🖐️','✋','🖖','👌','🤌','🤏','✌️','🤞','🤟','🤘','🤙','👈','👉','👆','🖕','👇', - '☝️','👍','👎','✊','👊','🤛','🤜','👏','🙌','👐','🤲','🤝','🙏','✍️','💅','🤳','💪','🦾', - '🦿','🦵','🦶','👂','🦻','👃','🫀','🫁','🧠','🦷','🦴','👀','👁️','👅','👄','🫦','👶','🧒', - '👦','👧','🧑','👱','👨','🧔','👩','🧓','👴','👵','🙍','🙎','🙅','🙆','💁','🙋','🧏','🙇', - '🤦','🤷','👮','🕵️','💂','🥷','👷','🫅','🤴','👸','👲','🧕','🤵','👰','🤰','🫃','🫄','🤱', - '👼','🎅','🤶','🧑‍🎄','🦸','🦹','🧙','🧝','🧛','🧟','🧞','🧜','🧚','🧑‍🤝‍🧑','👫','👬','👭','💏','💑','👪', + '👋', '🤚', '🖐️', '✋', '🖖', '👌', '🤌', '🤏', '✌️', '🤞', '🤟', '🤘', '🤙', '👈', '👉', '👆', '🖕', '👇', + '☝️', '👍', '👎', '✊', '👊', '🤛', '🤜', '👏', '🙌', '👐', '🤲', '🤝', '🙏', '✍️', '💅', '🤳', '💪', '🦾', + '🦿', '🦵', '🦶', '👂', '🦻', '👃', '🫀', '🫁', '🧠', '🦷', '🦴', '👀', '👁️', '👅', '👄', '🫦', '👶', '🧒', + '👦', '👧', '🧑', '👱', '👨', '🧔', '👩', '🧓', '👴', '👵', '🙍', '🙎', '🙅', '🙆', '💁', '🙋', '🧏', '🙇', + '🤦', '🤷', '👮', '🕵️', '💂', '🥷', '👷', '🫅', '🤴', '👸', '👲', '🧕', '🤵', '👰', '🤰', '🫃', '🫄', '🤱', + '👼', '🎅', '🤶', '🧑‍🎄', '🦸', '🦹', '🧙', '🧝', '🧛', '🧟', '🧞', '🧜', '🧚', '🧑‍🤝‍🧑', '👫', '👬', '👭', '💏', '💑', '👪', ], Animals: [ - '🐶','🐱','🐭','🐹','🐰','🦊','🐻','🐼','🐻‍❄️','🐨','🐯','🦁','🐮','🐷','🐸','🐵','🙈','🙉', - '🙊','🐒','🦆','🦅','🦉','🦇','🐝','🪱','🐛','🦋','🐌','🐞','🐜','🪲','🦗','🪳','🕷️','🦂', - '🐢','🐍','🦎','🦖','🦕','🐙','🦑','🦐','🦞','🦀','🐡','🐠','🐟','🐬','🐳','🐋','🦈','🦭', - '🐊','🐅','🐆','🦓','🦍','🦧','🦣','🐘','🦛','🦏','🐪','🐫','🦒','🦘','🦬','🐃','🐂','🐄', - '🐎','🐖','🐏','🐑','🦙','🐐','🦌','🐕','🐩','🦮','🐕‍🦺','🐈','🐈‍⬛','🐓','🦃','🦤','🦚','🦜', - '🦢','🦩','🕊️','🐇','🦝','🦨','🦡','🦫','🦦','🦥','🐁','🐀','🐿️','🦔','🐾','🐉','🐲','🌵', + '🐶', '🐱', '🐭', '🐹', '🐰', '🦊', '🐻', '🐼', '🐻‍❄️', '🐨', '🐯', '🦁', '🐮', '🐷', '🐸', '🐵', '🙈', '🙉', + '🙊', '🐒', '🦆', '🦅', '🦉', '🦇', '🐝', '🪱', '🐛', '🦋', '🐌', '🐞', '🐜', '🪲', '🦗', '🪳', '🕷️', '🦂', + '🐢', '🐍', '🦎', '🦖', '🦕', '🐙', '🦑', '🦐', '🦞', '🦀', '🐡', '🐠', '🐟', '🐬', '🐳', '🐋', '🦈', '🦭', + '🐊', '🐅', '🐆', '🦓', '🦍', '🦧', '🦣', '🐘', '🦛', '🦏', '🐪', '🐫', '🦒', '🦘', '🦬', '🐃', '🐂', '🐄', + '🐎', '🐖', '🐏', '🐑', '🦙', '🐐', '🦌', '🐕', '🐩', '🦮', '🐕‍🦺', '🐈', '🐈‍⬛', '🐓', '🦃', '🦤', '🦚', '🦜', + '🦢', '🦩', '🕊️', '🐇', '🦝', '🦨', '🦡', '🦫', '🦦', '🦥', '🐁', '🐀', '🐿️', '🦔', '🐾', '🐉', '🐲', '🌵', ], Food: [ - '🍎','🍊','🍋','🍌','🍍','🥭','🍓','🍒','🍑','🥝','🍅','🥥','🥑','🍆','🥔','🥕','🌽','🌶️', - '🫑','🥒','🥬','🥦','🧄','🧅','🍄','🥜','🌰','🍞','🥐','🥖','🫓','🥨','🧀','🥚','🍳','🧈', - '🥞','🧇','🥓','🥩','🍗','🍖','🦴','🌭','🍔','🍟','🍕','🫔','🌮','🌯','🥙','🧆','🥚','🍱', - '🍘','🍙','🍚','🍛','🍜','🍝','🍠','🍢','🍣','🍤','🍥','🥮','🍡','🥟','🥠','🥡','🦪','🍦', - '🍧','🍨','🍩','🍪','🎂','🍰','🧁','🥧','🍫','🍬','🍭','🍮','🍯','🍼','🥛','☕','🫖','🍵', - '🧃','🥤','🧋','🍶','🍺','🍻','🥂','🍷','🥃','🍸','🍹','🧉','🍾','🧊','🥄','🍴','🍽️','🥢', + '🍎', '🍊', '🍋', '🍌', '🍍', '🥭', '🍓', '🍒', '🍑', '🥝', '🍅', '🥥', '🥑', '🍆', '🥔', '🥕', '🌽', '🌶️', + '🫑', '🥒', '🥬', '🥦', '🧄', '🧅', '🍄', '🥜', '🌰', '🍞', '🥐', '🥖', '🫓', '🥨', '🧀', '🥚', '🍳', '🧈', + '🥞', '🧇', '🥓', '🥩', '🍗', '🍖', '🦴', '🌭', '🍔', '🍟', '🍕', '🫔', '🌮', '🌯', '🥙', '🧆', '🥚', '🍱', + '🍘', '🍙', '🍚', '🍛', '🍜', '🍝', '🍠', '🍢', '🍣', '🍤', '🍥', '🥮', '🍡', '🥟', '🥠', '🥡', '🦪', '🍦', + '🍧', '🍨', '🍩', '🍪', '🎂', '🍰', '🧁', '🥧', '🍫', '🍬', '🍭', '🍮', '🍯', '🍼', '🥛', '☕', '🫖', '🍵', + '🧃', '🥤', '🧋', '🍶', '🍺', '🍻', '🥂', '🍷', '🥃', '🍸', '🍹', '🧉', '🍾', '🧊', '🥄', '🍴', '🍽️', '🥢', ], Travel: [ - '🚗','🚕','🚙','🚌','🚎','🏎️','🚓','🚑','🚒','🚐','🛻','🚚','🚛','🚜','🦯','🦽','🦼','🛺', - '🚲','🛴','🛵','🏍️','🛺','🚨','🚔','🚍','🚘','🚖','🚡','🚠','🚟','🚃','🚋','🚞','🚝','🚄', - '🚅','🚈','🚂','🚆','🚇','🚊','🚉','✈️','🛫','🛬','🛩️','💺','🛸','🚁','🛶','⛵','🚤','🛥️', - '🛳️','⛴️','🚢','⚓','🗺️','🧭','🏔️','⛰️','🌋','🗻','🏕️','🏖️','🏜️','🏝️','🏞️','🏟️','🏛️','🏗️', - '🧱','🪨','🪵','🛖','🏘️','🏚️','🏠','🏡','🏢','🏣','🏤','🏥','🏦','🏨','🏩','🏪','🏫','🏬', - '🏭','🏯','🏰','💒','🗼','🗽','⛪','🕌','🛕','🕍','⛩️','🕋','⛲','⛺','🌁','🌃','🏙️','🌄', + '🚗', '🚕', '🚙', '🚌', '🚎', '🏎️', '🚓', '🚑', '🚒', '🚐', '🛻', '🚚', '🚛', '🚜', '🦯', '🦽', '🦼', '🛺', + '🚲', '🛴', '🛵', '🏍️', '🛺', '🚨', '🚔', '🚍', '🚘', '🚖', '🚡', '🚠', '🚟', '🚃', '🚋', '🚞', '🚝', '🚄', + '🚅', '🚈', '🚂', '🚆', '🚇', '🚊', '🚉', '✈️', '🛫', '🛬', '🛩️', '💺', '🛸', '🚁', '🛶', '⛵', '🚤', '🛥️', + '🛳️', '⛴️', '🚢', '⚓', '🗺️', '🧭', '🏔️', '⛰️', '🌋', '🗻', '🏕️', '🏖️', '🏜️', '🏝️', '🏞️', '🏟️', '🏛️', '🏗️', + '🧱', '🪨', '🪵', '🛖', '🏘️', '🏚️', '🏠', '🏡', '🏢', '🏣', '🏤', '🏥', '🏦', '🏨', '🏩', '🏪', '🏫', '🏬', + '🏭', '🏯', '🏰', '💒', '🗼', '🗽', '⛪', '🕌', '🛕', '🕍', '⛩️', '🕋', '⛲', '⛺', '🌁', '🌃', '🏙️', '🌄', ], Activities: [ - '⚽','🏀','🏈','⚾','🥎','🎾','🏐','🏉','🥏','🎱','🏓','🏸','🏒','🏑','🥍','🏏','🪃','🥅', - '⛳','🪁','🛝','🏹','🎣','🤿','🥊','🥋','🎽','🛹','🛷','⛸️','🥌','🎿','⛷️','🏂','🪂','🏋️', - '🤼','🤸','⛹️','🤺','🏇','🧘','🏄','🏊','🤽','🚣','🧗','🚵','🚴','🏆','🥇','🥈','🥉','🏅', - '🎖️','🏵️','🎗️','🎫','🎟️','🎪','🤹','🎭','🩰','🎨','🖼️','🎰','🎲','🧩','🪄','🎯','🪅','🎮', - '🕹️','🎳','🎻','🎷','🥁','🪘','🎺','🎸','🪗','🎹','🎵','🎶','🎼','🎤','🎧','📻','🎙️','🎚️', - '🎬','📽️','🎞️','📱','📲','☎️','📞','📟','📠','🔋','🪫','🔌','💡','🔦','🕯️','💸','💵','🪙', + '⚽', '🏀', '🏈', '⚾', '🥎', '🎾', '🏐', '🏉', '🥏', '🎱', '🏓', '🏸', '🏒', '🏑', '🥍', '🏏', '🪃', '🥅', + '⛳', '🪁', '🛝', '🏹', '🎣', '🤿', '🥊', '🥋', '🎽', '🛹', '🛷', '⛸️', '🥌', '🎿', '⛷️', '🏂', '🪂', '🏋️', + '🤼', '🤸', '⛹️', '🤺', '🏇', '🧘', '🏄', '🏊', '🤽', '🚣', '🧗', '🚵', '🚴', '🏆', '🥇', '🥈', '🥉', '🏅', + '🎖️', '🏵️', '🎗️', '🎫', '🎟️', '🎪', '🤹', '🎭', '🩰', '🎨', '🖼️', '🎰', '🎲', '🧩', '🪄', '🎯', '🪅', '🎮', + '🕹️', '🎳', '🎻', '🎷', '🥁', '🪘', '🎺', '🎸', '🪗', '🎹', '🎵', '🎶', '🎼', '🎤', '🎧', '📻', '🎙️', '🎚️', + '🎬', '📽️', '🎞️', '📱', '📲', '☎️', '📞', '📟', '📠', '🔋', '🪫', '🔌', '💡', '🔦', '🕯️', '💸', '💵', '🪙', ], Objects: [ - '⌚','📱','📲','💻','⌨️','🖥️','🖨️','🖱️','🖲️','💾','💿','📀','🧮','📷','📸','📹','🎥','📽️', - '📞','☎️','📟','📠','📺','📻','🧭','⏱️','⏲️','⏰','🕰️','⌛','⏳','📡','🔋','🪫','🔌','💡', - '🔦','🕯️','🪔','🧱','💰','💴','💵','💶','💷','💸','💳','🪙','💹','✉️','📧','📨','📩','📤', - '📥','📦','📫','📪','📬','📭','📮','🗳️','✏️','✒️','🖊️','🖋️','📝','📁','📂','🗂️','📅','📆', - '🗒️','🗓️','📇','📈','📉','📊','📋','📌','📍','🗺️','📏','📐','✂️','🗃️','🗄️','🗑️','🔒','🔓', - '🔏','🔐','🔑','🗝️','🔨','🪓','⛏️','⚒️','🛠️','🗡️','⚔️','🔫','🪃','🏹','🛡️','🪚','🔧','🪛', + '⌚', '📱', '📲', '💻', '⌨️', '🖥️', '🖨️', '🖱️', '🖲️', '💾', '💿', '📀', '🧮', '📷', '📸', '📹', '🎥', '📽️', + '📞', '☎️', '📟', '📠', '📺', '📻', '🧭', '⏱️', '⏲️', '⏰', '🕰️', '⌛', '⏳', '📡', '🔋', '🪫', '🔌', '💡', + '🔦', '🕯️', '🪔', '🧱', '💰', '💴', '💵', '💶', '💷', '💸', '💳', '🪙', '💹', '✉️', '📧', '📨', '📩', '📤', + '📥', '📦', '📫', '📪', '📬', '📭', '📮', '🗳️', '✏️', '✒️', '🖊️', '🖋️', '📝', '📁', '📂', '🗂️', '📅', '📆', + '🗒️', '🗓️', '📇', '📈', '📉', '📊', '📋', '📌', '📍', '🗺️', '📏', '📐', '✂️', '🗃️', '🗄️', '🗑️', '🔒', '🔓', + '🔏', '🔐', '🔑', '🗝️', '🔨', '🪓', '⛏️', '⚒️', '🛠️', '🗡️', '⚔️', '🔫', '🪃', '🏹', '🛡️', '🪚', '🔧', '🪛', ], Symbols: [ - '❤️','🧡','💛','💚','💙','💜','🖤','🤍','🤎','💔','❣️','💕','💞','💓','💗','💖','💘','💝', - '💟','☮️','✝️','☪️','🕉️','☸️','✡️','🔯','🕎','☯️','☦️','🛐','⛎','♈','♉','♊','♋','♌', - '♍','♎','♏','♐','♑','♒','♓','🆔','⚛️','🉑','☢️','☣️','📴','📳','🈶','🈚','🈸','🈺', - '🈷️','✴️','🆚','💮','🉐','㊙️','㊗️','🈴','🈵','🈹','🈲','🅰️','🅱️','🆎','🆑','🅾️','🆘', - '❌','⭕','🛑','⛔','📛','🚫','💯','💢','♨️','🚷','🚯','🚳','🚱','🔞','📵','🚭','❗','❕', - '❓','❔','‼️','⁉️','🔅','🔆','📶','🛜','📳','📴','🔱','📛','🔰','♻️','✅','🈯','💹','❎', - '🌐','💠','Ⓜ️','🌀','💤','🏧','🚾','♿','🅿️','🛗','🈳','🈹','🚰','🔤','🔡','🔠','🆖','🆗', - '🆙','🆒','🆕','🆓','🔟','📊','🔣','✔️','☑️','🔘','🔲','🔳','⬛','⬜','◼️','◻️','◾','◽', - '▪️','▫️','🔶','🔷','🔸','🔹','🔺','🔻','💠','🔘','🔲','🔳','🏁','🚩','🎌','🏴','🏳️','⭐', - '🌟','💫','✨','🌈','☀️','🌤️','⛅','🌥️','☁️','🌦️','🌧️','⛈️','🌩️','🌨️','❄️','☃️','⛄','🌬️', + '❤️', '🧡', '💛', '💚', '💙', '💜', '🖤', '🤍', '🤎', '💔', '❣️', '💕', '💞', '💓', '💗', '💖', '💘', '💝', + '💟', '☮️', '✝️', '☪️', '🕉️', '☸️', '✡️', '🔯', '🕎', '☯️', '☦️', '🛐', '⛎', '♈', '♉', '♊', '♋', '♌', + '♍', '♎', '♏', '♐', '♑', '♒', '♓', '🆔', '⚛️', '🉑', '☢️', '☣️', '📴', '📳', '🈶', '🈚', '🈸', '🈺', + '🈷️', '✴️', '🆚', '💮', '🉐', '㊙️', '㊗️', '🈴', '🈵', '🈹', '🈲', '🅰️', '🅱️', '🆎', '🆑', '🅾️', '🆘', + '❌', '⭕', '🛑', '⛔', '📛', '🚫', '💯', '💢', '♨️', '🚷', '🚯', '🚳', '🚱', '🔞', '📵', '🚭', '❗', '❕', + '❓', '❔', '‼️', '⁉️', '🔅', '🔆', '📶', '🛜', '📳', '📴', '🔱', '📛', '🔰', '♻️', '✅', '🈯', '💹', '❎', + '🌐', '💠', 'Ⓜ️', '🌀', '💤', '🏧', '🚾', '♿', '🅿️', '🛗', '🈳', '🈹', '🚰', '🔤', '🔡', '🔠', '🆖', '🆗', + '🆙', '🆒', '🆕', '🆓', '🔟', '📊', '🔣', '✔️', '☑️', '🔘', '🔲', '🔳', '⬛', '⬜', '◼️', '◻️', '◾', '◽', + '▪️', '▫️', '🔶', '🔷', '🔸', '🔹', '🔺', '🔻', '💠', '🔘', '🔲', '🔳', '🏁', '🚩', '🎌', '🏴', '🏳️', '⭐', + '🌟', '💫', '✨', '🌈', '☀️', '🌤️', '⛅', '🌥️', '☁️', '🌦️', '🌧️', '⛈️', '🌩️', '🌨️', '❄️', '☃️', '⛄', '🌬️', ], }; @@ -115,14 +115,14 @@ const EmojiPicker = () => ({ onclick: () => { ChatHubState.emojiSearch = ''; }, }, m('i.fas.fa-times')), ]), - !search && m('.emoji-categories', EMOJI_CATEGORIES.map(c => + !search && m('.emoji-categories', EMOJI_CATEGORIES.map((c) => m('button.emoji-cat-btn' + (c === cat ? '.active' : ''), { title: c, onclick: () => { ChatHubState.emojiCategory = c; }, }, EMOJI_ICONS[c]) )), m('.emoji-grid', - emojis.map(e => + emojis.map((e) => m('button.emoji-btn', { onclick: () => { insertEmojiIntoTextarea(e, onSelect); diff --git a/webui-src/app/chat/chat_state.js b/webui-src/app/chat/chat_state.js index 4bf1123..2760a63 100644 --- a/webui-src/app/chat/chat_state.js +++ b/webui-src/app/chat/chat_state.js @@ -1,7 +1,5 @@ const m = require('mithril'); const rs = require('rswebui'); -const peopleUtil = require('people/people_util'); -const people = require('people/people'); // **************** utility functions ******************** @@ -35,7 +33,7 @@ function loadDistantChatDetails(pid, apply) { rs.rsJsonApiRequest( '/rsChats/getDistantChatStatus', { - pid: pid, + pid, }, (detail, success) => { if (success && detail.retval) { @@ -118,7 +116,7 @@ function renderChatMessage(rawText) { if (src) { parts.push( m('img.chat-embedded-image', { - src: src, + src, style: { maxWidth: '100%', maxHeight: '300px', @@ -160,7 +158,7 @@ function renderChatMessage(rawText) { if (rawText.trim().startsWith('data:image/')) { const src = rawText.trim(); return m('img.chat-embedded-image', { - src: src, + src, style: { maxWidth: '100%', maxHeight: '300px', @@ -248,7 +246,7 @@ function renderTextWithEmoji(text) { const parts = []; let last = 0; let match; - // eslint-disable-next-line no-cond-assign + while ((match = emojiRegex.exec(text)) !== null) { if (match[0].length === 0) { emojiRegex.lastIndex++; continue; } if (match.index > last) parts.push(text.slice(last, match.index)); @@ -442,8 +440,8 @@ const Message = () => { x: e.clientX, y: e.clientY, messageText: targetText, - username: username, - gxsId: gxsId, + username, + gxsId, }; m.redraw(); }; @@ -581,7 +579,7 @@ const ChatLobbyModel = { loadHistory(id, type) { const chatPeerId = { broadcast_status_peer_id: '00000000000000000000000000000000', - type: type, + type, peer_id: '00000000000000000000000000000000', distant_chat_id: '00000000000000000000000000000000', lobby_id: { xstr64: '0' }, @@ -594,7 +592,7 @@ const ChatLobbyModel = { rs.rsJsonApiRequest( '/rsHistory/getMessages', { - chatPeerId: chatPeerId, + chatPeerId, loadCount: 20, }, (data, success) => { @@ -623,11 +621,11 @@ const ChatLobbyModel = { rs.rsJsonApiRequest( '/rsHistory/getMessages', { - chatPeerId: chatPeerId, + chatPeerId, loadCount: 0, }, (data, success) => { - let msgs = (success && data && data.msgs) ? data.msgs : []; + const msgs = (success && data && data.msgs) ? data.msgs : []; msgs.sort((a, b) => (a.sendTime || a.recvTime) - (b.sendTime || b.recvTime)); ChatHubState.fullHistoryMessages = msgs; ChatHubState.isHistoryLoading = false; @@ -642,7 +640,7 @@ const ChatLobbyModel = { '/rsChats/setIdentityForChatLobby', { lobby_id: { xstr64: lobbyId }, - nick: nick, + nick, }, () => m.route.set('/chat/:lobby', { lobby: lobbyId }), true @@ -685,7 +683,7 @@ const ChatLobbyModel = { const id = this.lastLobbyId || m.route.param('lobby'); const cid = { broadcast_status_peer_id: '00000000000000000000000000000000', - type: type, + type, peer_id: '00000000000000000000000000000000', distant_chat_id: '00000000000000000000000000000000', lobby_id: { xstr64: '0' }, @@ -805,13 +803,13 @@ const ChatLobbyModel = { '/rsChats/sendChat', { id: cid, - msg: msg, + msg, }, (data, success) => { if (success) { const echoMsg = { chat_id: cid, - msg: msg, + msg, sendTime: Math.floor(Date.now() / 1000), lobby_peer_gxs_id: this.currentLobby.gxs_id, }; diff --git a/webui-src/app/config/config_chat.js b/webui-src/app/config/config_chat.js index 6b6714e..6bf8ba1 100644 --- a/webui-src/app/config/config_chat.js +++ b/webui-src/app/config/config_chat.js @@ -10,8 +10,8 @@ const ConfigChat = () => { let maxStorageDays = 10; // History states - let historyEnable = { private: true, distant: true, lobby: true }; - let historySaveCount = { private: 500, distant: 500, lobby: 500 }; + const historyEnable = { private: true, distant: true, lobby: true }; + const historySaveCount = { private: 500, distant: 500, lobby: 500 }; function loadSettings() { // Load Own Identities diff --git a/webui-src/app/config/config_network.js b/webui-src/app/config/config_network.js index 1af47d6..be6ed0d 100644 --- a/webui-src/app/config/config_network.js +++ b/webui-src/app/config/config_network.js @@ -579,8 +579,8 @@ const displayHiddenServiceInfo = () => { details && details.hiddenNodeAddress && m('.hidden-service-info', { style: 'display: flex; flex-direction: column; gap: 0.75rem; width: 100%;' }, [ m('p.proxy-description', { style: 'margin-bottom: 0.5rem; color: #475569;' }, details.hiddenType === 4 - ? "I2P has been automatically configured by Retroshare. You shouldn't need to change anything here." - : "Tor has been automatically configured by Retroshare. You shouldn't need to change anything here." + ? 'I2P has been automatically configured by Retroshare. You shouldn\'t need to change anything here.' + : 'Tor has been automatically configured by Retroshare. You shouldn\'t need to change anything here.' ), // Local Address + Local Port row m('.nw-config-row', { style: 'display: grid; grid-template-columns: 200px 1fr; gap: 1rem; align-items: center;' }, [ diff --git a/webui-src/app/forums/forum_view.js b/webui-src/app/forums/forum_view.js index a58497a..0c3845a 100644 --- a/webui-src/app/forums/forum_view.js +++ b/webui-src/app/forums/forum_view.js @@ -2,7 +2,7 @@ const m = require('mithril'); const rs = require('rswebui'); const util = require('forums/forums_util'); const peopleUtil = require('people/people_util'); -const { updatedisplayforums, loadPostContent, getTimestampValue, formatTimestamp } = require('./forums_util'); +const { loadPostContent, getTimestampValue, formatTimestamp } = require('./forums_util'); function createforum() { let title; @@ -75,75 +75,6 @@ function createforum() { ]), }; } -const EditThread = () => { - let title = ''; - let body = ''; - return { - oninit: (vnode) => { - title = vnode.attrs.current_title; - body = vnode.attrs.current_body; - }, - view: (vnode) => - m('.widget', [ - m('h3', 'Edit Thread'), - m('hr'), - - m( - 'iddisplay', - { - style: { display: 'block ruby' }, // same line block ruby - }, - [ - 'Identity: ', - m('h5[id=authid]', rs.userList.username(vnode.attrs.authorId)), - ] - ), - m( - 'titledisplay', - { - style: { display: 'block ruby' }, - }, - [ - 'Title: ', - m('input[type=text][placeholder=Title]', { - value: vnode.attrs.current_title, - oninput: (e) => (title = e.target.value), - }), - ] - ), - m('textarea[rows=5]', { - style: { width: '90%', display: 'block' }, - oninput: (e) => (body = e.target.value), - value: vnode.attrs.current_body, - }), - m( - 'button', - { - onclick: async () => { - const res = await rs.rsJsonApiRequest('/rsgxsforums/createPost', { - forumId: vnode.attrs.forumId, - mBody: body, - title, - authorId: vnode.attrs.authorId, - parentId: vnode.attrs.current_parent, - origPostId: vnode.attrs.current_msgid, - }); - res.body.retval === false - ? util.popupmessage([m('h3', 'Error'), m('hr'), m('p', res.body.errorMessage)]) - : util.popupmessage([ - m('h3', 'Success'), - m('hr'), - m('p', 'Thread edited successfully'), - ]); - util.updatedisplayforums(vnode.attrs.forumId); - m.redraw(); - }, - }, - 'Add' - ), - ]), - }; -}; const AddThread = () => { let title = ''; let body = ''; @@ -228,166 +159,6 @@ const AddThread = () => { // getTimestampValue and formatTimestamp are imported from forums_util.js -function displaythread() { - // recursive function to display all the threads - let groupmessagepair; - let unread; - let editpermission = false; - return { - view: (v) => { - const thread = v.attrs.threadStruct.thread; - groupmessagepair = { first: thread.mMeta.mGroupId, second: thread.mMeta.mOrigMsgId }; - let parMap = []; - if (util.Data.ParentThreadMap[thread.mMeta.mOrigMsgId]) { - parMap = util.Data.ParentThreadMap[thread.mMeta.mOrigMsgId]; - } - unread = thread.mMeta.mMsgStatus === util.THREAD_UNREAD; - v.attrs.identity && - v.attrs.identity.map((val) => { - if (val.localeCompare(thread.mMeta.mAuthorId) === 0) { - // if the author of the thread matches one of our own ids - editpermission = true; - } - }); - return [ - m( - 'tr', - { - style: unread ? { fontWeight: 'bold' } : '', - }, - [ - Object.keys(parMap).length // if this thread has some replies - ? m( - 'td', - m('i.fas.fa-angle-right', { - class: 'fa-rotate-' + (v.attrs.threadStruct.showReplies ? '90' : '0'), - style: 'margin-top:12px', - onclick: () => { - v.attrs.threadStruct.showReplies = !v.attrs.threadStruct.showReplies; - }, - }) - ) - : m('td', ''), - - m( - 'td', - { - style: { - position: 'relative', - '--replyDepth': v.attrs.replyDepth, - left: 'calc(30px*var(--replyDepth))', // shifts reply by 30 px - padding: '10px 0', - }, - }, - [ - m('div.date', { style: { fontSize: '0.8em', color: '#888' } }, - formatTimestamp(thread.mMeta.mPublishTs) - ), - m('div.title', { - style: { fontWeight: 'bold', fontSize: '1.1em', cursor: 'pointer', margin: '5px 0' }, - onclick: async () => { - v.attrs.changeThread(thread.mMeta.mOrigMsgId); - if (unread) { - const res = await rs.rsJsonApiRequest('/rsgxsforums/markRead', { - messageId: groupmessagepair, - read: true, - }); - if (res.body.retval) { - updatedisplayforums(thread.mMeta.mGroupId); - m.redraw(); - } - } - }, - ondblclick: () => - (v.attrs.threadStruct.showReplies = !v.attrs.threadStruct.showReplies), - }, [ - thread.mMeta.mMsgName, - m('options', { style: 'display:block; margin-top: 5px;' }, [ - m( - 'button', - { - style: 'font-size:12px; margin-right: 5px;', - onclick: (e) => { - e.stopPropagation(); - util.popupmessage( - m(AddThread, { - parent_thread: thread.mMeta.mMsgName, - forumId: thread.mMeta.mGroupId, - authorId: v.attrs.identity, - parentId: thread.mMeta.mMsgId, - }) - ); - }, - }, - 'Reply' - ), - editpermission && - m( - 'button', - { - style: 'font-size:12px; margin-right: 5px;', - onclick: async (e) => { - e.stopPropagation(); - const body = await loadPostContent( - thread.mMeta.mGroupId, - thread.mMeta.mOrigMsgId - ); - util.popupmessage( - m(EditThread, { - current_thread: thread.mMeta.mMsgName, - forumId: thread.mMeta.mGroupId, - current_title: thread.mMeta.mMsgName, - current_body: body || '', - authorId: thread.mMeta.mAuthorId, - current_parent: thread.mMeta.mParentId, - current_msgid: thread.mMeta.mOrigMsgId, - }) - ); - }, - }, - 'Edit' - ), - m( - 'button', - { - style: { fontSize: '12px' }, - onclick: async (e) => { - e.stopPropagation(); - const res = await rs.rsJsonApiRequest('/rsgxsforums/markRead', { - messageId: groupmessagepair, - read: !unread ? true : false, - }); - - if (res.body.retval) { - updatedisplayforums(thread.mMeta.mGroupId); - m.redraw(); - } - }, - }, - unread ? 'Mark Read' : 'Mark Unread' - ), - ]), - ]), - m('div.author', { style: { fontSize: '0.9em', fontStyle: 'italic' } }, rs.userList.username(thread.mMeta.mAuthorId)), - ] - ), - ] - ), - v.attrs.threadStruct.showReplies && - Object.keys(parMap).map((key, index) => - m(displaythread, { - // recursive call to all replies - threadStruct: util.Data.Threads[parMap[key].mGroupId][parMap[key].mOrigMsgId], - replyDepth: v.attrs.replyDepth + 1, - identity: v.attrs.identity, - changeThread: v.attrs.changeThread, - }) - ), - ]; - }, - }; -} - const ThreadView = () => { let ownId; return { @@ -449,7 +220,7 @@ const ThreadView = () => { style: { marginRight: '10px' }, onclick: () => util.popupmessage(m(AddThread, { parent_thread: meta.mMsgName, - forumId: forumId, + forumId, authorId: ownId, parentId: msgId, })) diff --git a/webui-src/app/forums/forums_util.js b/webui-src/app/forums/forums_util.js index ae92c5d..e7a2773 100644 --- a/webui-src/app/forums/forums_util.js +++ b/webui-src/app/forums/forums_util.js @@ -106,7 +106,7 @@ async function updatedisplayforums(keyid) { const threadStruct = { thread: { mMeta: meta, mMsg: null }, - replies: replies, + replies, showReplies: false, }; @@ -156,7 +156,7 @@ async function loadPostContent(forumId, msgId) { try { const res = await rs.rsJsonApiRequest('/rsgxsforums/getForumContent', { - forumId: forumId, + forumId, msgsIds: [msgId], }); if (res && res.body && res.body.retval && res.body.msgs && res.body.msgs.length > 0) { diff --git a/webui-src/app/jdenticon.js b/webui-src/app/jdenticon.js index b8b21eb..2808a96 100644 --- a/webui-src/app/jdenticon.js +++ b/webui-src/app/jdenticon.js @@ -41,7 +41,7 @@ function renderPolygon(shapePoints, x, y, angle, shapeAngle, size, color) { function toSvg(hash, width) { if (!hash || hash.length < 18) { - hash = "00000000000000000000000000000000"; + hash = '00000000000000000000000000000000'; } const csh = parseInt(hash.substr(0, 1), 16); @@ -99,4 +99,4 @@ function toSvg(hash, width) { module.exports = { toSvg -}; \ No newline at end of file +}; diff --git a/webui-src/app/mail/mail_compose.js b/webui-src/app/mail/mail_compose.js index 230d376..d8cd0eb 100644 --- a/webui-src/app/mail/mail_compose.js +++ b/webui-src/app/mail/mail_compose.js @@ -18,7 +18,7 @@ function formatFileSize(bytes) { const Layout = () => { let showCc = false; let showBcc = false; - let ownAvatars = {}; + const ownAvatars = {}; let attachments = []; let showEmojiPicker = false; let emojiSearch = ''; @@ -397,7 +397,7 @@ const Layout = () => { const attHtml = `

Attachments (${attachments.length}):
    - ${attachments.map(att => `
  • 📎 ${att.name} (${att.size})
  • `).join('')} + ${attachments.map((att) => `
  • 📎 ${att.name} (${att.size})
  • `).join('')}
`; fullMailBody += attHtml; @@ -701,7 +701,7 @@ const Layout = () => { }), ]), !emojiSearch && m('.emoji-cat-bar', { style: 'display: flex; background: #f8fafc; border-bottom: 1px solid #e2e8f0; padding: 0.25rem; overflow-x: auto;' }, - chatEmoji.EMOJI_CATEGORIES.map(c => + chatEmoji.EMOJI_CATEGORIES.map((c) => m('button', { style: `border: none; background: ${c === emojiCategory ? '#ffffff' : 'transparent'}; border-radius: 0.25rem; padding: 0.3rem 0.4rem; cursor: pointer; font-size: 1rem; box-shadow: ${c === emojiCategory ? '0 1px 2px rgba(0,0,0,0.1)' : 'none'};`, title: c, @@ -711,9 +711,9 @@ const Layout = () => { ), m('.emoji-grid-body', { style: 'padding: 0.5rem; display: grid; grid-template-columns: repeat(7, 1fr); gap: 0.25rem; max-height: 230px; overflow-y: auto;' }, (emojiSearch - ? Object.values(chatEmoji.EMOJI_DATA).flat().filter(e => e.includes(emojiSearch)) + ? Object.values(chatEmoji.EMOJI_DATA).flat().filter((e) => e.includes(emojiSearch)) : (chatEmoji.EMOJI_DATA[emojiCategory] || []) - ).map(e => + ).map((e) => m('button', { style: 'border: none; background: transparent; font-size: 1.25rem; cursor: pointer; padding: 0.25rem; border-radius: 0.25rem; transition: background 0.15s ease;', onmouseenter: (ev) => (ev.currentTarget.style.background = '#f1f5f9'), diff --git a/webui-src/app/mail/mail_resolver.js b/webui-src/app/mail/mail_resolver.js index de0648e..c06b5fc 100644 --- a/webui-src/app/mail/mail_resolver.js +++ b/webui-src/app/mail/mail_resolver.js @@ -195,7 +195,7 @@ const GenericMailList = () => { m(util.MessageSummary, { key: msg.msgId, details: msg, - category: category, + category, }) ) ) diff --git a/webui-src/app/mail/mail_util.js b/webui-src/app/mail/mail_util.js index a46f073..f4acb68 100644 --- a/webui-src/app/mail/mail_util.js +++ b/webui-src/app/mail/mail_util.js @@ -622,11 +622,11 @@ const Table = () => { }; let totalItems = 0; - let tbody = v.children[0]; + const tbody = v.children[0]; if (tbody && tbody.children) { const flatChildren = Array.isArray(tbody.children) ? tbody.children.flat().filter(Boolean) : [tbody.children].filter(Boolean); totalItems = flatChildren.length; - + const start = currentPage * pageSize; const end = start + pageSize; tbody.children = flatChildren.slice(start, end); diff --git a/webui-src/app/network/network_state.js b/webui-src/app/network/network_state.js index be29209..bea212f 100644 --- a/webui-src/app/network/network_state.js +++ b/webui-src/app/network/network_state.js @@ -149,7 +149,7 @@ function sendDirectChatMessage() { '/rsChats/sendChat', { id: { type: 1, peer_id: State.currentChatPeerId }, - msg: msg, + msg, }, (data, success) => { if (success) { diff --git a/webui-src/app/people/people_chat_tab.js b/webui-src/app/people/people_chat_tab.js index 5221fa7..aba1ae9 100644 --- a/webui-src/app/people/people_chat_tab.js +++ b/webui-src/app/people/people_chat_tab.js @@ -100,14 +100,6 @@ const ChatTab = () => { const canTalk = State.distantChatStatus && State.distantChatStatus.status === 2; - // Filter history by search query for history modal - const query = (State.historySearchQuery || '').toLowerCase(); - const filteredHistory = (State.fullHistoryMessages || []).filter((msg) => { - if (!query) return true; - const text = (msg.msg || msg.message || '').toLowerCase(); - return text.includes(query); - }); - return m('.network-chat-view', [ m('.chat-identity-select-container', { style: 'padding: 0.5rem 1rem; background-color: #ffffff; border-bottom: 1px solid #cbd5e1; display: flex; align-items: center; justify-content: space-between; font-size: 0.85rem;', diff --git a/webui-src/app/people/people_sidebar.js b/webui-src/app/people/people_sidebar.js index 4b0109e..e9de2db 100644 --- a/webui-src/app/people/people_sidebar.js +++ b/webui-src/app/people/people_sidebar.js @@ -33,7 +33,7 @@ const PeopleSidebar = () => { }, view: () => { // 1. Determine list based on mainTab ('people' vs 'chats') - let displayItems = []; + let displayItems; // 0. Compute active chats count (conversations with real message history) const allUserGroupIds = new Set((rs.userList.users || []).map((u) => u.mGroupId)); @@ -47,7 +47,7 @@ const PeopleSidebar = () => { }); if (State.mainTab === 'people') { - let baseList = []; + let baseList; if (State.activeFilter === 'own') { baseList = peopleUtil.sortIds(State.ownGxsIds) || []; } else if (State.activeFilter === 'contacts') { @@ -57,13 +57,13 @@ const PeopleSidebar = () => { } displayItems = baseList.filter((item) => { - let name = State.activeFilter === 'own' ? (rs.userList.username(item) || 'Unknown') : (item.mGroupName || 'Unknown'); + const name = State.activeFilter === 'own' ? (rs.userList.username(item) || 'Unknown') : (item.mGroupName || 'Unknown'); return name.toLowerCase().includes(State.searchString.toLowerCase()); }); displayItems.sort((a, b) => { - let nameA = State.activeFilter === 'own' ? (rs.userList.username(a) || '') : (a.mGroupName || ''); - let nameB = State.activeFilter === 'own' ? (rs.userList.username(b) || '') : (b.mGroupName || ''); + const nameA = State.activeFilter === 'own' ? (rs.userList.username(a) || '') : (a.mGroupName || ''); + const nameB = State.activeFilter === 'own' ? (rs.userList.username(b) || '') : (b.mGroupName || ''); return nameA.localeCompare(nameB); }); } else { diff --git a/webui-src/app/people/people_state.js b/webui-src/app/people/people_state.js index b4fbc3a..d1ad93e 100644 --- a/webui-src/app/people/people_state.js +++ b/webui-src/app/people/people_state.js @@ -359,7 +359,7 @@ function loadChatMessages() { rs.rsJsonApiRequest( '/rsHistory/getMessages', { - chatPeerId: chatPeerId, + chatPeerId, loadCount: 50, }, (data, success) => { @@ -509,7 +509,7 @@ function preloadAllChatHistory() { if (!existing || lastTime > existing.lastTime) { State.chatHistoryMap[gxsId] = { lastMsg: last.message || last.msg || '', - lastTime: lastTime, + lastTime, }; m.redraw(); } @@ -573,7 +573,7 @@ function loadAllHistoryForSelectedPeer(callback) { rs.rsJsonApiRequest( '/rsHistory/getMessages', { - chatPeerId: chatPeerId, + chatPeerId, loadCount: 0, // 0 = load all messages in C++ }, (msgData, success) => { @@ -588,7 +588,7 @@ function loadAllHistoryForSelectedPeer(callback) { const key = `${mItem.sendTime || mItem.recvTime}_${text}`; if (!map.has(key)) map.set(key, mItem); }); - let uniqueMsgs = Array.from(map.values()); + const uniqueMsgs = Array.from(map.values()); uniqueMsgs.sort((a, b) => (a.sendTime || a.recvTime) - (b.sendTime || b.recvTime)); State.fullHistoryMessages = uniqueMsgs; State.isHistoryLoading = false; diff --git a/webui-src/app/people/people_util.js b/webui-src/app/people/people_util.js index 95bd48c..46036cc 100644 --- a/webui-src/app/people/people_util.js +++ b/webui-src/app/people/people_util.js @@ -70,7 +70,7 @@ const UserAvatar = () => ({ width: sizeStr, height: sizeStr, borderRadius: isSquare ? '0' : '50%', - backgroundColor: backgroundColor, + backgroundColor, } }, m('p', { diff --git a/webui-src/eslint.config.mjs b/webui-src/eslint.config.mjs new file mode 100644 index 0000000..8cfb645 --- /dev/null +++ b/webui-src/eslint.config.mjs @@ -0,0 +1,51 @@ +import js from '@eslint/js'; +import globals from 'globals'; + +export default [ + { + ignores: ['app/mithril.js', 'assets/**', 'make-src/**'], + }, + { + files: ['app/**/*.js'], + languageOptions: { + ecmaVersion: 2020, + sourceType: 'commonjs', + globals: { + ...globals.browser, + ...globals.commonjs, + }, + }, + rules: { + ...js.configs.recommended.rules, + 'linebreak-style': ['off', 'unix'], + quotes: ['error', 'single'], + semi: ['error', 'always'], + 'arrow-spacing': ['error', { before: true, after: true }], + 'arrow-parens': ['error', 'always'], + 'comma-style': ['error', 'last'], + 'comma-spacing': ['error', { before: false, after: true }], + camelcase: ['error', { + allow: ['^UNSAFE_'], + properties: 'never', + ignoreGlobals: true, + }], + 'eol-last': 'error', + eqeqeq: ['error', 'always', { null: 'ignore' }], + 'no-irregular-whitespace': 'error', + 'no-trailing-spaces': 'error', + 'no-unexpected-multiline': 'error', + 'no-unreachable': 'error', + 'no-var': 'warn', + 'no-unused-vars': ['error', { + args: 'none', + caughtErrors: 'none', + ignoreRestSiblings: true, + vars: 'all', + }], + 'object-shorthand': 'error', + 'prefer-const': ['error', { destructuring: 'all' }], + 'semi-style': ['warn', 'last'], + 'spaced-comment': ['warn', 'always'], + }, + }, +]; diff --git a/webui-src/make-src/build.bat b/webui-src/make-src/build.bat index 62f4e0e..b9851fd 100644 --- a/webui-src/make-src/build.bat +++ b/webui-src/make-src/build.bat @@ -8,7 +8,7 @@ echo "### Starting WebUI build ###" set src=%~dp0..\..\webui-src rem Output destination -if "%~1" == "" ( +if "%~1"=="" ( set publicdest=%~dp0..\..\webui ) else ( set publicdest=%~1\webui @@ -36,6 +36,7 @@ copy %src%\make-src\template.js %publicdest%\app.js pushd %src%\app set "basefolder=%cd%\" +set "lastsection=" for /R %%F in (*.js) do call :addfile-js "%basefolder%" "%%F" popd @@ -54,7 +55,15 @@ set registername=%~dpn2 set registername=!registername:%basefolder%=! set registername=%registername:\=/% -echo - adding %registername% ... +for /f "tokens=1,2 delims=/" %%A in ("!registername!") do ( + if "%%B"=="" ( + echo - adding !registername! ... + set "lastsection=" + ) else if not "!lastsection!"=="%%A" ( + echo - adding %%A/* ... + set "lastsection=%%A" + ) +) echo require.register("%registername%", function(exports, require, module) { >> %publicdest%\app.js type %fname% >> %publicdest%\app.js echo. >> %publicdest%\app.js diff --git a/webui-src/make-src/build.sh b/webui-src/make-src/build.sh index 9a7bef1..ce47dea 100755 --- a/webui-src/make-src/build.sh +++ b/webui-src/make-src/build.sh @@ -1,68 +1,85 @@ -#!/bin/bash +#!/bin/sh -# create webfiles from sources at compile time (works without npm/node.js) +# Create webfiles from sources at compile time (works without npm/node.js) +# +# Usage: build.sh [DEST_PARENT] [TARGET_FILE] [EXTRA_COPY_DIR] +# DEST_PARENT parent dir of the generated webui/ (default: repo root) +# TARGET_FILE build only this file (index.html|styles.css|app.js); default: all +# EXTRA_COPY_DIR also copy TARGET_FILE into EXTRA_COPY_DIR/webui/ + +set -eu echo "### Starting WebUI build ###" -src=$(readlink -f $(dirname $0))/../../webui-src +# Resolve the script's own directory portably. +script_dir=$(cd -- "$(dirname -- "$0")" && pwd -P) +src="$script_dir/../../webui-src" -if [ "$1" = "" ]; then - publicdest=$(readlink -f $(dirname $0))/../../webui +dest_parent="${1:-}" +target="${2:-}" +extra_copy_dir="${3:-}" + +if [ -z "$dest_parent" ]; then + publicdest="$script_dir/../../webui" else - publicdest=$1/webui + publicdest="$dest_parent/webui" fi -if [ "$2" = "" ]; then - if [ -d "$publicdest" ]; then - echo removing existing $publicdest - rm $publicdest -R - fi +# Full rebuild (no specific target): remove any existing output first. +if [ -z "$target" ] && [ -d "$publicdest" ]; then + echo "removing existing $publicdest" + rm -rf -- "$publicdest" fi -if [ ! -d "$publicdest" ]; then - echo creating $publicdest - mkdir $publicdest +mkdir -p -- "$publicdest" + +if [ -z "$target" ] || [ "$target" = "index.html" ]; then + echo "copying html file" + cp -- "$src/index.html" "$publicdest/" fi -# For using recursive directory search(requires bash v4+) -shopt -s globstar - -if [ "$2" = "" ]||[ "$2" = "index.html" ]; then - echo copying html file - cp $src/index.html $publicdest/ +if [ -z "$target" ] || [ "$target" = "styles.css" ]; then + echo "copying css file" + cp -- "$src/styles.css" "$publicdest/" fi -if [ "$2" = "" ]||[ "$2" = "styles.css" ]; then - echo copying css file - cp $src/styles.css $publicdest/ +if [ -z "$target" ] || [ "$target" = "app.js" ]; then + echo "building app.js:" + echo "- copying template.js ..." + cp -- "$src/make-src/template.js" "$publicdest/app.js" + + js_root="$src/app" + find "$js_root" -type f -name '*.js' | LC_ALL=C sort | while IFS= read -r filename; do + fname="${filename#"$js_root/"}" + fname="${fname%.*}" + case "$fname" in + */*) + section="${fname%%/*}/*" + if [ "$section" != "${last_section:-}" ]; then + echo "- adding $section ..." + last_section="$section" + fi + ;; + *) + echo "- adding $fname ..." + last_section= + ;; + esac + { + printf 'require.register("%s", function(exports, require, module) {\n' "$fname" + cat -- "$filename" + printf '\n});\n' + } >>"$publicdest/app.js" + done fi -if [ "$2" = "" ]||[ "$2" = "app.js" ]; then - echo building app.js: - echo - copying template.js ... - cp $src/make-src/template.js $publicdest/app.js +echo "copying assets folder" +cp -R -- "$src/assets/." "$publicdest/" - js_source=$src/app/ - for filename in $src/app/**/*.js; do - fname="${filename#$js_source}" - fname="${fname%.*}" - echo - adding $fname ... - echo require.register\(\"$fname\", function\(exports, require, module\) { >> $publicdest/app.js - cat $filename >> $publicdest/app.js - echo >> $publicdest/app.js - echo }\)\; >> $publicdest/app.js - done +if [ -n "$target" ] && [ -n "$extra_copy_dir" ]; then + mkdir -p -- "$extra_copy_dir/webui" + echo "copying $target to $extra_copy_dir/webui/$target" + cp -- "$publicdest/$target" "$extra_copy_dir/webui/$target" fi -echo copying assets folder -cp -r $src/assets/* $publicdest/ - -if [ "$2" != "" ]&&[ "$3" != "" ]; then - if [ ! -d "$3/webui" ]; then - echo creating $3/webui - mkdir $3/webui - fi - echo copying $2 nach $3/webui/$2 - cp $publicdest/$2 $3/webui/$2 -fi echo "### WebUI build complete ###" diff --git a/webui-src/package-lock.json b/webui-src/package-lock.json new file mode 100644 index 0000000..a2547df --- /dev/null +++ b/webui-src/package-lock.json @@ -0,0 +1,1311 @@ +{ + "name": "webui-src", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "webui-src", + "version": "1.0.0", + "license": "ISC", + "devDependencies": { + "@eslint/js": "^10.0.1", + "eslint": "^10.8.0", + "globals": "^17.8.0", + "sass": "1.97.3" + }, + "engines": { + "node": ">=24" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", + "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.23.5", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", + "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^3.0.5", + "debug": "^4.3.1", + "minimatch": "^10.2.4" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.7.0.tgz", + "integrity": "sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/core": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/js": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint/object-schema": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", + "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz", + "integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1", + "levn": "^0.4.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@parcel/watcher": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.6.0.tgz", + "integrity": "sha512-7FNeNl8NCE7aINx7WXiKQrPYZWC/hvrTsmk6zmxbI7LTXE7hVek/n8AfVgpe2y82zl3w0HvCHN0bVKMBoJcC0w==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.3", + "is-glob": "^4.0.3", + "node-addon-api": "^7.0.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.6.0", + "@parcel/watcher-darwin-arm64": "2.6.0", + "@parcel/watcher-darwin-x64": "2.6.0", + "@parcel/watcher-freebsd-x64": "2.6.0", + "@parcel/watcher-linux-arm-glibc": "2.6.0", + "@parcel/watcher-linux-arm-musl": "2.6.0", + "@parcel/watcher-linux-arm64-glibc": "2.6.0", + "@parcel/watcher-linux-arm64-musl": "2.6.0", + "@parcel/watcher-linux-x64-glibc": "2.6.0", + "@parcel/watcher-linux-x64-musl": "2.6.0", + "@parcel/watcher-win32-arm64": "2.6.0", + "@parcel/watcher-win32-x64": "2.6.0" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.6.0.tgz", + "integrity": "sha512-trgpLSCKRC/huFjXX/Smh+0sWe4+YtKfktIToiMl59ghz7z+qkH6kMvNnUbLyRs9N11t8l4svSCs1+5B3rOAhA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.6.0.tgz", + "integrity": "sha512-Y3QV0gl7Q1zbfueunkWIERICbEojQFCgpyG7YqOGNFLsckXyI1xu9mAIUpKY9QBYzBtSkN8dBPwd3yiAO9ovMw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.6.0.tgz", + "integrity": "sha512-Ohv6OpzhUfKYD7Beb8kDvG0jbIxORCYY1JRdZnaBtnjjkJxgD7ZVL0nw2sCYd0yTMKTvz3nnTnOF3cDifK+kvw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.6.0.tgz", + "integrity": "sha512-5HmXvDgs8VK+74jF9y9/2FE3/OnlcKmc56tjmSrEuZjpSZOGL+fvAu+HKJBdPs9uwoP2hE6TlSUpXZ/C5jUFmQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.6.0.tgz", + "integrity": "sha512-Ps/hui3A+vMbjdqlqAowK2ZL8+BO8dBjxeWXj6npTBs3jx4wWmbPpaLuqwrQrSqIVMCnpWo238bJ1U37GhQOYg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.6.0.tgz", + "integrity": "sha512-9c6AUHgHoG+IY88MRIHupztQiQnrbqHYQjkM2btA+Bf/wQnQMuiD0Wfk1EVv3TlNT3x41uU71rn6E4xh/+zvkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.6.0.tgz", + "integrity": "sha512-yHRqS2owEXe6Hic9z6Mh1ECsCd+ODVOGvZDyciqRd21+v+o+DnXMOrw50DSpIG2sb8GPEaPPmfeCAWKPJdq46g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.6.0.tgz", + "integrity": "sha512-WhB2e/V7rqdHHWZusBSPuy5Ei8S6lSz6FE5TKKQz5h3a0O+C+mhY7vxU9b/stqvMb8beLnPY82ZrFTLKs+SrKA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.6.0.tgz", + "integrity": "sha512-ulGE6x6Oz6iAwg75T8YQSoguBWasniIbX+QWpaYPcCnDOpdWX3k+4xbEYPZVLxOuoJI+svJJPD3sEj8G7lrQ3A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.6.0.tgz", + "integrity": "sha512-tkBYKt7YQrjIJWYDnto2YgO8MRkjlMTSNoRHzsXinBqbLdeOM3L32wPZJvIZxqaLMfSlS/4sUjH/6STVP/XDLw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.6.0.tgz", + "integrity": "sha512-gIZAP23jaHjGWasY/TY6yL7NHFClf0Ga7FN+iINvk+KN94rhm94lYZhFsbYFNcA04/onvGD9kKmiJLJB2HbNwQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.6.0.tgz", + "integrity": "sha512-cA+/pXV2YkfxlIcXOQ5fSWqAzzPyD78/x5qbK/I0vUkrlYHA8TIz+MXjAbGouguKVSI4bOmkTSJ1/poVSsgt+A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", + "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "10.8.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.8.0.tgz", + "integrity": "sha512-nuKKvN+oIBO0koN7Tm7dlkmnkc21mtt0QJLwAKzjLq14y6lRTdVG36MZHJ8eQHwdJMwZbQNMlPOYedMq/oVJvQ==", + "dev": true, + "license": "MIT", + "workspaces": [ + "packages/*" + ], + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.5", + "@eslint/config-helpers": "^0.7.0", + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.2", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "minimatch": "^10.2.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", + "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", + "dev": true, + "license": "ISC" + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "17.8.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.8.0.tgz", + "integrity": "sha512-Zz/LMDZScFmkakeL2cTHzf+PbWKdpU3uclqkZT7TjDG58j5WPt0PpA+n9uPI24fZtlw07q0OtEi84K+umsRzqQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/immutable": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.9.tgz", + "integrity": "sha512-m8nVez3rwrgmWxtLMt1ZYXB2Lv7OKYn/disyxAlSDYAlKSlFoPPfIAmAM/M5xqL4m4C/wAPw7S2/CNaUii1Hxg==", + "dev": true, + "license": "MIT" + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/sass": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.97.3.tgz", + "integrity": "sha512-fDz1zJpd5GycprAbu4Q2PV/RprsRtKC/0z82z0JLgdytmcq0+ujJbJ/09bPGDxCLkKY3Np5cRAOcWiVkLXJURg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.0", + "immutable": "^5.0.2", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/webui-src/package.json b/webui-src/package.json index 5aac05b..f16f87e 100644 --- a/webui-src/package.json +++ b/webui-src/package.json @@ -4,10 +4,17 @@ "description": "Retroshare's Web Interface", "scripts": { "watch": "rm ./styles.css && sass --watch --embed-sources --embed-source-map ./app/scss/main.scss ./styles.css", - "build": "sass --no-source-map --style=compressed ./app/scss/main.scss ./styles.css" + "build": "sass --no-source-map --style=compressed ./app/scss/main.scss ./styles.css && sh ./make-src/build.sh", + "lint": "eslint app && node --check ../webui/app.js && sh -n make-src/build.sh && echo 'Lint checks passed.'" }, "license": "ISC", + "engines": { + "node": ">=24" + }, "devDependencies": { - "sass": "^1.60.0" + "@eslint/js": "^10.0.1", + "eslint": "^10.8.0", + "globals": "^17.8.0", + "sass": "1.97.3" } } diff --git a/webui-src/pnpm-lock.yaml b/webui-src/pnpm-lock.yaml deleted file mode 100644 index 9dbf4bd..0000000 --- a/webui-src/pnpm-lock.yaml +++ /dev/null @@ -1,240 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - - .: - devDependencies: - sass: - specifier: ^1.60.0 - version: 1.97.3 - -packages: - - '@parcel/watcher-android-arm64@2.5.6': - resolution: {integrity: sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==} - engines: {node: '>= 10.0.0'} - cpu: [arm64] - os: [android] - - '@parcel/watcher-darwin-arm64@2.5.6': - resolution: {integrity: sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==} - engines: {node: '>= 10.0.0'} - cpu: [arm64] - os: [darwin] - - '@parcel/watcher-darwin-x64@2.5.6': - resolution: {integrity: sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==} - engines: {node: '>= 10.0.0'} - cpu: [x64] - os: [darwin] - - '@parcel/watcher-freebsd-x64@2.5.6': - resolution: {integrity: sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==} - engines: {node: '>= 10.0.0'} - cpu: [x64] - os: [freebsd] - - '@parcel/watcher-linux-arm-glibc@2.5.6': - resolution: {integrity: sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==} - engines: {node: '>= 10.0.0'} - cpu: [arm] - os: [linux] - libc: [glibc] - - '@parcel/watcher-linux-arm-musl@2.5.6': - resolution: {integrity: sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==} - engines: {node: '>= 10.0.0'} - cpu: [arm] - os: [linux] - libc: [musl] - - '@parcel/watcher-linux-arm64-glibc@2.5.6': - resolution: {integrity: sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==} - engines: {node: '>= 10.0.0'} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@parcel/watcher-linux-arm64-musl@2.5.6': - resolution: {integrity: sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==} - engines: {node: '>= 10.0.0'} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@parcel/watcher-linux-x64-glibc@2.5.6': - resolution: {integrity: sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==} - engines: {node: '>= 10.0.0'} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@parcel/watcher-linux-x64-musl@2.5.6': - resolution: {integrity: sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==} - engines: {node: '>= 10.0.0'} - cpu: [x64] - os: [linux] - libc: [musl] - - '@parcel/watcher-win32-arm64@2.5.6': - resolution: {integrity: sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==} - engines: {node: '>= 10.0.0'} - cpu: [arm64] - os: [win32] - - '@parcel/watcher-win32-ia32@2.5.6': - resolution: {integrity: sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==} - engines: {node: '>= 10.0.0'} - cpu: [ia32] - os: [win32] - - '@parcel/watcher-win32-x64@2.5.6': - resolution: {integrity: sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==} - engines: {node: '>= 10.0.0'} - cpu: [x64] - os: [win32] - - '@parcel/watcher@2.5.6': - resolution: {integrity: sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==} - engines: {node: '>= 10.0.0'} - - chokidar@4.0.3: - resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} - engines: {node: '>= 14.16.0'} - - detect-libc@2.1.2: - resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} - engines: {node: '>=8'} - - immutable@5.1.5: - resolution: {integrity: sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A==} - - is-extglob@2.1.1: - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} - engines: {node: '>=0.10.0'} - - is-glob@4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} - - node-addon-api@7.1.1: - resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} - - picomatch@4.0.3: - resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} - engines: {node: '>=12'} - - readdirp@4.1.2: - resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} - engines: {node: '>= 14.18.0'} - - sass@1.97.3: - resolution: {integrity: sha512-fDz1zJpd5GycprAbu4Q2PV/RprsRtKC/0z82z0JLgdytmcq0+ujJbJ/09bPGDxCLkKY3Np5cRAOcWiVkLXJURg==} - engines: {node: '>=14.0.0'} - hasBin: true - - source-map-js@1.2.1: - resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} - engines: {node: '>=0.10.0'} - -snapshots: - - '@parcel/watcher-android-arm64@2.5.6': - optional: true - - '@parcel/watcher-darwin-arm64@2.5.6': - optional: true - - '@parcel/watcher-darwin-x64@2.5.6': - optional: true - - '@parcel/watcher-freebsd-x64@2.5.6': - optional: true - - '@parcel/watcher-linux-arm-glibc@2.5.6': - optional: true - - '@parcel/watcher-linux-arm-musl@2.5.6': - optional: true - - '@parcel/watcher-linux-arm64-glibc@2.5.6': - optional: true - - '@parcel/watcher-linux-arm64-musl@2.5.6': - optional: true - - '@parcel/watcher-linux-x64-glibc@2.5.6': - optional: true - - '@parcel/watcher-linux-x64-musl@2.5.6': - optional: true - - '@parcel/watcher-win32-arm64@2.5.6': - optional: true - - '@parcel/watcher-win32-ia32@2.5.6': - optional: true - - '@parcel/watcher-win32-x64@2.5.6': - optional: true - - '@parcel/watcher@2.5.6': - dependencies: - detect-libc: 2.1.2 - is-glob: 4.0.3 - node-addon-api: 7.1.1 - picomatch: 4.0.3 - optionalDependencies: - '@parcel/watcher-android-arm64': 2.5.6 - '@parcel/watcher-darwin-arm64': 2.5.6 - '@parcel/watcher-darwin-x64': 2.5.6 - '@parcel/watcher-freebsd-x64': 2.5.6 - '@parcel/watcher-linux-arm-glibc': 2.5.6 - '@parcel/watcher-linux-arm-musl': 2.5.6 - '@parcel/watcher-linux-arm64-glibc': 2.5.6 - '@parcel/watcher-linux-arm64-musl': 2.5.6 - '@parcel/watcher-linux-x64-glibc': 2.5.6 - '@parcel/watcher-linux-x64-musl': 2.5.6 - '@parcel/watcher-win32-arm64': 2.5.6 - '@parcel/watcher-win32-ia32': 2.5.6 - '@parcel/watcher-win32-x64': 2.5.6 - optional: true - - chokidar@4.0.3: - dependencies: - readdirp: 4.1.2 - - detect-libc@2.1.2: - optional: true - - immutable@5.1.5: {} - - is-extglob@2.1.1: - optional: true - - is-glob@4.0.3: - dependencies: - is-extglob: 2.1.1 - optional: true - - node-addon-api@7.1.1: - optional: true - - picomatch@4.0.3: - optional: true - - readdirp@4.1.2: {} - - sass@1.97.3: - dependencies: - chokidar: 4.0.3 - immutable: 5.1.5 - source-map-js: 1.2.1 - optionalDependencies: - '@parcel/watcher': 2.5.6 - - source-map-js@1.2.1: {} diff --git a/webui.pro b/webui.pro index 9d14a65..accf8a0 100644 --- a/webui.pro +++ b/webui.pro @@ -72,7 +72,7 @@ win32-g++ { } else { # create dummy files, we need it to include files on first try - system(bash webui-src/make-src/build.sh .) + system(sh webui-src/make-src/build.sh .) WEBUI_SRC_SCRIPT = webui-src/make-src/build.sh @@ -80,19 +80,19 @@ win32-g++ { create_webfiles_html.output = webui/index.html create_webfiles_html.input = WEBUI_SRC_HTML - create_webfiles_html.commands = bash $$_PRO_FILE_PWD_/webui-src/make-src/build.sh $$_PRO_FILE_PWD_ index.html . + create_webfiles_html.commands = sh $$_PRO_FILE_PWD_/webui-src/make-src/build.sh $$_PRO_FILE_PWD_ index.html . create_webfiles_html.variable_out = JUNK create_webfiles_html.CONFIG = combine no_link create_webfiles_js.output = webui/app.js create_webfiles_js.input = WEBUI_SRC_JS - create_webfiles_js.commands = bash $$_PRO_FILE_PWD_/webui-src/make-src/build.sh $$_PRO_FILE_PWD_ app.js . + create_webfiles_js.commands = sh $$_PRO_FILE_PWD_/webui-src/make-src/build.sh $$_PRO_FILE_PWD_ app.js . create_webfiles_js.variable_out = JUNK create_webfiles_js.CONFIG = combine no_link create_webfiles_css.output = webui/styles.css create_webfiles_css.input = WEBUI_SRC_CSS - create_webfiles_css.commands = bash $$_PRO_FILE_PWD_/webui-src/make-src/build.sh $$_PRO_FILE_PWD_ styles.css . + create_webfiles_css.commands = sh $$_PRO_FILE_PWD_/webui-src/make-src/build.sh $$_PRO_FILE_PWD_ styles.css . create_webfiles_css.variable_out = JUNK create_webfiles_css.CONFIG = combine no_link From 81f3ef17a659cf5ce3f3a492bac450f63d131b84 Mon Sep 17 00:00:00 2001 From: Sumit Kumar Soni Date: Sun, 2 Aug 2026 20:46:00 +0530 Subject: [PATCH 4/7] docs: consolidate contributor guidance in README --- README.md | 124 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 68 insertions(+), 56 deletions(-) diff --git a/README.md b/README.md index 70c83b3..4508302 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,9 @@ which communicates with the client through the JSON API. - Retroshare v0.6.5+ with JSON API enabled(see instructions below) - A modern JavaScript-enabled web browser -- [`qmake`](https://doc.qt.io/qt-5/qmake-manual.html)(optional) +- Node.js 24 LTS or newer and npm when building from SCSS sources +- A POSIX-compatible `sh` on Linux or macOS +- [`qmake`](https://doc.qt.io/qt-5/qmake-manual.html) (optional packaging integration) ## Installation @@ -29,27 +31,22 @@ itself: ``` 2. **Build the files**: - If you have `qmake` installed, you just need to run it in the base directory: + If you have `qmake` installed, you need to run this command from the repository root to build it: ```bash qmake . ``` - If you do not have `qmake`, go to `webui-src/make-src/` and run the build - script. Note that qmake is enough to do the build. - - ##### On Linux/MacOS: + `qmake` packages the committed generated `webui-src/styles.css`. Without `qmake`, run the shell build script instead: ```bash - cd webui-src/make-src - sh build.sh + sh webui-src/make-src/build.sh ``` - ##### On Windows: + On Windows, use the batch script to bundle the checked-in generated files: ```bash - cd webui-src\make-src\ - ./build.bat + webui-src\make-src\build.bat ``` ### Compile Retroshare with JSON API @@ -127,58 +124,73 @@ webui password and enter the ID and login password of your node). ## Contributing -For contributing, It is recommended that you read this entire section to have a -better idea. +### Setup -### Setup WebUI for contributing +Development requires Node.js 24 or newer, npm, `sh` on macOS or Linux, and a +RetroShare instance with the JSON API and Web Interface enabled for manual +testing. -Follow these steps to setup the project and make it ready for -contribution/customisation : +Fork and clone the repository, then install the locked dependencies: -- Fork and Clone this repository to your local machine. -- `cd` into the cloned repo: - ``` - cd RSNewWebUI - ``` -- Run this command to install the dependecies for the project. - ``` - pnpm install - ``` - -### Run the WebUI - -- Run this command to watch for any changes in the `scss` files and compile them - to css. - - ``` - pnpm watch - ``` - -- You can now start to edit the source code. But you must run the below command - to see the changes reflected in the browser everytime you edit the code for - webui which is in the `webui-src/app/` directory. - - ``` - qmake . - ``` - -### Linting and Formatting - -Linting and formatting can be done with editor/IDE plugins. - -Or to do it manually, - -Install `prettier` and `eslint` (required for linting & formatting code): - -```sh -npm install -g prettier eslint +```bash +cd RSNewWebUI/webui-src +npm ci ``` -Next, run the following in the `webui-src` directory: +### Source and generated files -To run the linter: `eslint app` +Edit files under `webui-src/app/`, including SCSS under +`webui-src/app/scss/`. Do not edit these generated files by hand: -To run the formatter: `prettier -c app` +- `webui-src/styles.css` is generated from `webui-src/app/scss/main.scss`. +- `webui/app.js`, `webui/styles.css`, and `webui/index.html` are build output. + +`webui-src/app/mithril.js` is the vendored Mithril 2.3.8 runtime. Replace it +only during an intentional Mithril upgrade. + +### Build and test + +From `webui-src/`, compile SCSS and create the deployable `webui/` directory: + +```bash +npm run build +npm run lint +``` + +Commit SCSS changes together with the regenerated `webui-src/styles.css`. +`qmake .` packages that committed CSS but does not compile SCSS. Running +`make` alone does not rebuild this `TEMPLATE = subdirs` project; rerun +`qmake .` from the repository root when using qmake. + +The shell bundler works without Node.js, but only copies the existing CSS. Run +it from `webui-src/` with `sh`: + +```bash +sh make-src/build.sh +``` + +For focused rebuilds, run these commands from `webui-src/`: + +```bash +# JavaScript only +sh make-src/build.sh "" app.js + +# HTML only +sh make-src/build.sh "" index.html + +# Compile and copy CSS only +./node_modules/.bin/sass --no-source-map --style=compressed app/scss/main.scss styles.css +sh make-src/build.sh "" styles.css +``` + +`npm run watch` recompiles SCSS into `webui-src/styles.css`; it does not copy +the CSS into `webui/` or rebuild JavaScript. Use the CSS-only command above +when testing watched changes through RetroShare. + +Before submitting, run the full build and lint commands, then point +RetroShare's **Web interface directory** at `webui/` and manually test the +affected screens. ESLint, generated JavaScript syntax, and shell syntax are +checked by `npm run lint`; no formatting script is defined. ### References From 6eada33d4767d8e2fb81b981ff09f9e0a315cf59 Mon Sep 17 00:00:00 2001 From: Sumit Kumar Soni Date: Mon, 3 Aug 2026 01:06:46 +0530 Subject: [PATCH 5/7] build: make WebUI npm scripts cross-platform --- README.md | 8 +++++-- webui-src/make-src/build.bat | 45 +++++++++++++++++++----------------- webui-src/make-src/build.js | 35 ++++++++++++++++++++++++++++ webui-src/package.json | 6 ++--- webui.pro | 2 +- 5 files changed, 69 insertions(+), 27 deletions(-) create mode 100644 webui-src/make-src/build.js diff --git a/README.md b/README.md index 4508302..f93a7b6 100644 --- a/README.md +++ b/README.md @@ -157,6 +157,9 @@ npm run build npm run lint ``` +These npm commands use `build.bat` on native Windows and `build.sh` on other +platforms. + Commit SCSS changes together with the regenerated `webui-src/styles.css`. `qmake .` packages that committed CSS but does not compile SCSS. Running `make` alone does not rebuild this `TEMPLATE = subdirs` project; rerun @@ -189,8 +192,9 @@ when testing watched changes through RetroShare. Before submitting, run the full build and lint commands, then point RetroShare's **Web interface directory** at `webui/` and manually test the -affected screens. ESLint, generated JavaScript syntax, and shell syntax are -checked by `npm run lint`; no formatting script is defined. +affected screens. ESLint, generated JavaScript syntax, the build dispatcher, +and shell syntax on non-Windows systems are checked by `npm run lint`; no +formatting script is defined. ### References diff --git a/webui-src/make-src/build.bat b/webui-src/make-src/build.bat index b9851fd..c61fe57 100644 --- a/webui-src/make-src/build.bat +++ b/webui-src/make-src/build.bat @@ -5,55 +5,58 @@ setlocal enabledelayedexpansion echo "### Starting WebUI build ###" -set src=%~dp0..\..\webui-src +set "src=%~dp0..\..\webui-src" rem Output destination if "%~1"=="" ( - set publicdest=%~dp0..\..\webui + set "publicdest=%~dp0..\..\webui" ) else ( - set publicdest=%~1\webui + set "publicdest=%~1\webui" ) -if exist "%publicdest%" echo removing existing %publicdest%&&rd %publicdest% /S /Q +if exist "%publicdest%" ( + echo removing existing %publicdest% + rd "%publicdest%" /S /Q +) echo creating %publicdest% -md %publicdest% +md "%publicdest%" rem Make full path -pushd %publicdest% -set publicdest=%cd% +pushd "%publicdest%" +set "publicdest=%cd%" popd echo copying html file -xcopy /s %src%\index.html %publicdest% +copy /Y "%src%\index.html" "%publicdest%\index.html" >nul echo copying css file -xcopy /s %src%\styles.css %publicdest% +copy /Y "%src%\styles.css" "%publicdest%\styles.css" >nul echo building app.js echo - copying template.js ... -copy %src%\make-src\template.js %publicdest%\app.js +copy /Y "%src%\make-src\template.js" "%publicdest%\app.js" >nul -pushd %src%\app +pushd "%src%\app" set "basefolder=%cd%\" set "lastsection=" for /R %%F in (*.js) do call :addfile-js "%basefolder%" "%%F" popd echo copying assets folder -xcopy /s %src%\assets\ %publicdest% +xcopy "%src%\assets\*" "%publicdest%\" /E /I /Y >nul echo "### WebUI build complete ###" goto :EOF :addfile-js -set basefolder=%~1 -set fname=%~2 +set "basefolder=%~1" +set "fname=%~2" -set registername=%~dpn2 -set registername=!registername:%basefolder%=! -set registername=%registername:\=/% +set "registername=%~dpn2" +set "registername=!registername:%basefolder%=!" +set "registername=%registername:\=/%" for /f "tokens=1,2 delims=/" %%A in ("!registername!") do ( if "%%B"=="" ( @@ -64,9 +67,9 @@ for /f "tokens=1,2 delims=/" %%A in ("!registername!") do ( set "lastsection=%%A" ) ) -echo require.register("%registername%", function(exports, require, module) { >> %publicdest%\app.js -type %fname% >> %publicdest%\app.js -echo. >> %publicdest%\app.js -echo }); >> %publicdest%\app.js +echo require.register("%registername%", function(exports, require, module) { >>"%publicdest%\app.js" +type "%fname%" >>"%publicdest%\app.js" +echo. >>"%publicdest%\app.js" +echo }); >>"%publicdest%\app.js" :EOF diff --git a/webui-src/make-src/build.js b/webui-src/make-src/build.js new file mode 100644 index 0000000..bfb7c60 --- /dev/null +++ b/webui-src/make-src/build.js @@ -0,0 +1,35 @@ +'use strict'; + +const assert = require('node:assert/strict'); +const path = require('node:path'); +const { spawnSync } = require('node:child_process'); + +const shellScript = path.join(__dirname, 'build.sh'); +const batchScript = path.join(__dirname, 'build.bat'); + +function getRunner(platform, commandShell = 'cmd.exe') { + return platform === 'win32' + ? { command: commandShell, args: ['/d', '/c', batchScript] } + : { command: 'sh', args: [shellScript] }; +} + +function run(runner) { + const result = spawnSync(runner.command, runner.args, { stdio: 'inherit' }); + if (result.error) { + console.error(result.error.message); + return 1; + } + return result.status ?? 1; +} + +if (process.argv[2] === '--check') { + assert.equal(path.basename(getRunner('win32').args[2]), 'build.bat'); + assert.equal(path.basename(getRunner('linux').args[0]), 'build.sh'); + const status = process.platform === 'win32' + ? 0 + : run({ command: 'sh', args: ['-n', shellScript] }); + if (status === 0) console.log('Build dispatcher checks passed.'); + process.exitCode = status; +} else { + process.exitCode = run(getRunner(process.platform, process.env.ComSpec)); +} diff --git a/webui-src/package.json b/webui-src/package.json index f16f87e..90482de 100644 --- a/webui-src/package.json +++ b/webui-src/package.json @@ -3,9 +3,9 @@ "version": "1.0.0", "description": "Retroshare's Web Interface", "scripts": { - "watch": "rm ./styles.css && sass --watch --embed-sources --embed-source-map ./app/scss/main.scss ./styles.css", - "build": "sass --no-source-map --style=compressed ./app/scss/main.scss ./styles.css && sh ./make-src/build.sh", - "lint": "eslint app && node --check ../webui/app.js && sh -n make-src/build.sh && echo 'Lint checks passed.'" + "watch": "sass --watch --embed-sources --embed-source-map ./app/scss/main.scss ./styles.css", + "build": "sass --no-source-map --style=compressed ./app/scss/main.scss ./styles.css && node ./make-src/build.js", + "lint": "eslint app && node --check ../webui/app.js && node ./make-src/build.js --check && echo Lint checks passed." }, "license": "ISC", "engines": { diff --git a/webui.pro b/webui.pro index accf8a0..64e5f79 100644 --- a/webui.pro +++ b/webui.pro @@ -33,7 +33,7 @@ WEBUI_SRC_CSS = $$PWD/webui-src/styles.css WEBUI_SRC_IMAGE = $$PWD/data/retroshare.svg -win32-g++ { +win32 { isEmpty(QMAKE_SH) { # Windows native build WEBUI_SRC_SCRIPT = $$PWD/webui-src/make-src/build.bat From 4788bdc5e6c1a650eddbeecdeacbddfa11f2cf9c Mon Sep 17 00:00:00 2001 From: Sumit Kumar Soni Date: Mon, 3 Aug 2026 12:55:13 +0530 Subject: [PATCH 6/7] build: make lint independent of generated output --- webui-src/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webui-src/package.json b/webui-src/package.json index 90482de..2b12802 100644 --- a/webui-src/package.json +++ b/webui-src/package.json @@ -5,7 +5,7 @@ "scripts": { "watch": "sass --watch --embed-sources --embed-source-map ./app/scss/main.scss ./styles.css", "build": "sass --no-source-map --style=compressed ./app/scss/main.scss ./styles.css && node ./make-src/build.js", - "lint": "eslint app && node --check ../webui/app.js && node ./make-src/build.js --check && echo Lint checks passed." + "lint": "eslint app && node ./make-src/build.js --check && echo Lint checks passed." }, "license": "ISC", "engines": { From 12949500beaa68aacad2bb500e165d0ec8872e9a Mon Sep 17 00:00:00 2001 From: Sumit Kumar Soni Date: Mon, 3 Aug 2026 12:55:13 +0530 Subject: [PATCH 7/7] docs: simplify setup and contributor guidance --- README.md | 298 +++++++++++++++++++++++++----------------------------- 1 file changed, 138 insertions(+), 160 deletions(-) diff --git a/README.md b/README.md index f93a7b6..269dba9 100644 --- a/README.md +++ b/README.md @@ -1,228 +1,206 @@ -# Web Interface for Retroshare +# RetroShare Web Interface -A web-based frontend for [Retroshare](https://github.com/Retroshare/Retroshare) -which communicates with the client through the JSON API. +This project is the browser interface for +[RetroShare](https://github.com/RetroShare/RetroShare). It talks to RetroShare +through its JSON API. -## Requirements +Recent RetroShare releases already include this interface. Follow this guide if +you want to build it yourself, change it, or contribute to it. -- Retroshare v0.6.5+ with JSON API enabled(see instructions below) -- A modern JavaScript-enabled web browser -- Node.js 24 LTS or newer and npm when building from SCSS sources -- A POSIX-compatible `sh` on Linux or macOS -- [`qmake`](https://doc.qt.io/qt-5/qmake-manual.html) (optional packaging integration) +## What you need -## Installation +- RetroShare 0.6.5 or newer, with the JSON API enabled +- A modern web browser -> **Note:** The Web Interface is shipped by default in the latest release of -> Retroshare. If you want to customise it or [contribute](#contributing) to it -> then proceed with the following steps. +## Set up RetroShare -### Install WebUI +### Enable the JSON API -First, you need to download and install the web interface javascript code -itself: +1. Open RetroShare. +2. Go to **Preferences > JSON API**. +3. Enable **RetroShare JSON API Server**. -1. **Clone the repo**: - You can clone using git, or download the zip file and extract it +### Enable the Web Interface - ```bash - git clone https://github.com/Retroshare/RSNewWebUI - cd RSNewWebUI - ``` +1. Go to **Preferences > Web Interface**. +2. Enable the Web Interface. +3. Choose a password. +4. Set **Web interface directory** to this repository's `webui/` directory if + RetroShare does not find it automatically. +5. Click **Apply**. -2. **Build the files**: - If you have `qmake` installed, you need to run this command from the repository root to build it: +The JSON API page should now show an authenticated token named +`webui:`. - ```bash - qmake . - ``` +## Open the interface - `qmake` packages the committed generated `webui-src/styles.css`. Without `qmake`, run the shell build script instead: +Open [https://localhost:9092/index.html](https://localhost:9092/index.html) in +your browser. If you changed the JSON API port, replace `9092` with that port. - ```bash - sh webui-src/make-src/build.sh - ``` +### Connect to a remote or headless server - On Windows, use the batch script to bundle the checked-in generated files: +The Web Interface listens on localhost by default. To reach RetroShare on a +remote server, run this command on your local computer: - ```bash - webui-src\make-src\build.bat - ``` - -### Compile Retroshare with JSON API - -If you are on older versions of Retroshare then it needs to be compiled with -non-default options as follows: - -```bash -qmake CONFIG+="debug rs_jsonapi rs_webui" -make +```sh +ssh -L 9092:localhost:9092 -N login@server ``` -See the [RetroShare repo](https://github.com/Retroshare/Retroshare) for more -detailed instructions on compiling RetroShare. You should afterwards see a tab -'JSON API' and a tab 'Web Interface' in the **Preferences**. - -### Enable JSON API - -You need to enable the JSON API, through which the web interface communicates -with the client: - -1. Open Retroshare, go to `Preferences > JSON API`. -2. Make sure the **Enable Retroshare JSON API Server** box is checked. - -### Enable Web Interface - -1. Go to `Preferences > Webinterface`. -2. Make sure the **Enable Retroshare WEB Interface** box is checked. -3. Enter a password to protect access to the web interface. - -If necessary, point the **Web interface directory** to the place where the webui -files are compiled. This is usually `RSNewWebUI/webui/`. - -In any case, click on "Apply settings" after making the changes. If everything -goes ok, you should see a new token `webui:[your password]` under the -**Authenticated Tokens** section in the **JSON API** preferences page. - -## Usage - -### Basic Usage - -This is the default link to access the WebInterface. -
-Open this link your browser -> +Keep that command running, then open [https://localhost:9092/index.html](https://localhost:9092/index.html). -> Note: If you changed the port in the JSON API preferences pages, the port -> in the above line needs to be changed accordingly. +The Web Interface cannot create a new RetroShare node. Create the node with the +desktop interface first, then copy its RetroShare data directory to the server. +On Linux this directory is usually `.retroshare/`. -### Advanced Usage +Start the service on the server with: -The Web interface is only accessible from localhost (127.0.0.1). If you want to -access the web interface of a headless retroshare server, then you need to -create a SSH tunnel as follows: - -``` -ssh login@server -L 9092:localhost:9092 -N +```sh +./retroshare-service/src/retroshare-service -U list -W ``` -After that, the Web interface of the Retroshare running on 'server' is tunneled -to your local machine and accessible through localhost:9092. - -Running a headless retroshare server is one possibility. The Webinterface -however does not allow you to create new nodes. Therefore the steps are: - -1. Create a node using the standard Qt UI. That can be done in another machine. -2. Copy the retroshare data directory (.retroshare/ on linux) on the server. -3. On the server, launch a headless retroshare using that node: - ``` - ./retroshare-service/src/retroshare-service -U list -W - ``` - -After that follow instructions to launch your profile (you need to choose a -webui password and enter the ID and login password of your node). +Follow the prompts to select the profile and enter its passwords. ## Contributing -### Setup +To work on the source, you need: -Development requires Node.js 24 or newer, npm, `sh` on macOS or Linux, and a -RetroShare instance with the JSON API and Web Interface enabled for manual -testing. +- Node.js 24 or newer and npm +- `sh` on macOS or Linux; Windows uses the included batch file +- `qmake` only if you want to use RetroShare's qmake build integration -Fork and clone the repository, then install the locked dependencies: +### First-time setup -```bash +Fork the repository, clone your fork, and install the locked dependencies: + +```sh +git clone https://github.com/YOUR-USERNAME/RSNewWebUI.git cd RSNewWebUI/webui-src npm ci ``` -### Source and generated files +### Where to make changes -Edit files under `webui-src/app/`, including SCSS under -`webui-src/app/scss/`. Do not edit these generated files by hand: +- JavaScript source is in `webui-src/app/`. +- SCSS source is in `webui-src/app/scss/`. +- `webui-src/styles.css` is generated from `app/scss/main.scss`. Do not edit it + by hand, but commit it with the SCSS changes that generated it. +- Everything in `webui/` is build output. Do not edit those files by hand. +- `webui-src/app/mithril.js` contains Mithril 2.3.8. Change it only when + intentionally upgrading Mithril. -- `webui-src/styles.css` is generated from `webui-src/app/scss/main.scss`. -- `webui/app.js`, `webui/styles.css`, and `webui/index.html` are build output. +### Normal development commands -`webui-src/app/mithril.js` is the vendored Mithril 2.3.8 runtime. Replace it -only during an intentional Mithril upgrade. +Run these commands from `webui-src/`: -### Build and test - -From `webui-src/`, compile SCSS and create the deployable `webui/` directory: - -```bash +```sh npm run build npm run lint ``` -These npm commands use `build.bat` on native Windows and `build.sh` on other -platforms. +`npm run build` compiles the SCSS and creates the complete `webui/` directory. +It automatically uses `build.bat` on Windows and `build.sh` on macOS or Linux. -Commit SCSS changes together with the regenerated `webui-src/styles.css`. -`qmake .` packages that committed CSS but does not compile SCSS. Running -`make` alone does not rebuild this `TEMPLATE = subdirs` project; rerun -`qmake .` from the repository root when using qmake. +`npm run lint` checks the source files and does not require `webui/` to exist. -The shell bundler works without Node.js, but only copies the existing CSS. Run -it from `webui-src/` with `sh`: +To rebuild CSS whenever an SCSS file changes, run: -```bash -sh make-src/build.sh +```sh +npm run watch ``` -For focused rebuilds, run these commands from `webui-src/`: +Watch mode updates `webui-src/styles.css` only. Run `npm run build` when you +also need to update the files in `webui/`. -```bash +### Using qmake during development + +Run qmake from the repository root: + +```sh +qmake . +``` + +qmake copies the committed `webui-src/styles.css`; it does not compile SCSS. +After changing SCSS, run `npm run build` first. Rerun `qmake .` when you need +qmake to package newer WebUI files; `make` alone does not do that for this +project. + +A standalone checkout may show a warning about a missing `../retroshare.pri`. +The WebUI files are still generated. + +### Building without npm or qmake + +These commands create `webui/` from the files already stored in the repository. +They do not compile SCSS. + +From the repository root on macOS or Linux: + +```sh +sh webui-src/make-src/build.sh +``` + +From the repository root on Windows: + +```bat +webui-src\make-src\build.bat +``` + +### Optional focused builds + +The normal `npm run build` command is the easiest choice. On macOS or Linux, +you can rebuild only one generated file from `webui-src/`: + +```sh # JavaScript only sh make-src/build.sh "" app.js # HTML only sh make-src/build.sh "" index.html -# Compile and copy CSS only +# CSS only ./node_modules/.bin/sass --no-source-map --style=compressed app/scss/main.scss styles.css sh make-src/build.sh "" styles.css ``` -`npm run watch` recompiles SCSS into `webui-src/styles.css`; it does not copy -the CSS into `webui/` or rebuild JavaScript. Use the CSS-only command above -when testing watched changes through RetroShare. +### Before opening a pull request -Before submitting, run the full build and lint commands, then point -RetroShare's **Web interface directory** at `webui/` and manually test the -affected screens. ESLint, generated JavaScript syntax, the build dispatcher, -and shell syntax on non-Windows systems are checked by `npm run lint`; no -formatting script is defined. +1. Run `npm run build`. +2. Run `npm run lint`. +3. Point RetroShare's **Web interface directory** to this repository's + `webui/` directory. +4. Manually test the screens you changed. +5. If you changed SCSS, include the regenerated `webui-src/styles.css`. -### References +There is no separate formatting command. -Now, While contributing you can checkout these resources as you might need to -look up for these often. +### Building older RetroShare versions -- [mithril](https://mithril.js.org/hyperscript.html) -- You can list files with @jsonapi in libretroshare/src/retroshare of - [retroshare](https://github.com/RetroShare/RetroShare): +If you are testing with an older RetroShare version, you may need to build it +with the JSON API and WebUI enabled: - ``` - grep -c "@jsonapi" *.h|grep -v ":0" - ``` +```sh +qmake CONFIG+="debug rs_jsonapi rs_webui" +make +``` -
+See the [RetroShare repository](https://github.com/RetroShare/RetroShare) for +complete build instructions. After building, RetroShare preferences should +contain both **JSON API** and **Web Interface** pages. -And, that's it. You are more than welcome to contribute to this project. If you -have any questions/difficulties in setting up or running the project, you can -raise an issue and we will be more than willing to help you out. +### Useful references -### Bug Reports & Feature requests +- [Mithril documentation](https://mithril.js.org/) +- [RetroShare source](https://github.com/RetroShare/RetroShare) -Please create an [issue](https://github.com/Retroshare/RsNewWebUI/issues) -concisely describing the bug you faced, or the feature you would like to see -implemented. +To find RetroShare headers that expose JSON API methods, run this from +`libretroshare/src/retroshare/` in the RetroShare source tree: -### Development +```sh +grep -c "@jsonapi" *.h | grep -v ":0" +``` -Whether you are a JavaScript developer or a Web designer, you can help make the -web interface better. Get in touch with us on the Developer forums in -Retroshare. +## Get help or report a problem + +Open a [GitHub issue](https://github.com/RetroShare/RSNewWebUI/issues) and +briefly explain what happened, what you expected, and how someone can reproduce +it. You can also join the RetroShare developer forums to discuss development.