From 701b703466fc32ce4d362276286d2cb1f5e7a292 Mon Sep 17 00:00:00 2001 From: Aman Harwara Date: Fri, 21 Jan 2022 20:30:57 +0530 Subject: [PATCH 01/65] fix: panel_resizer mobx action warnings (#820) --- .../javascripts/ui_models/panel_resizer.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/ui_models/panel_resizer.ts b/app/assets/javascripts/ui_models/panel_resizer.ts index c53b49c9d..f06ff07eb 100644 --- a/app/assets/javascripts/ui_models/panel_resizer.ts +++ b/app/assets/javascripts/ui_models/panel_resizer.ts @@ -90,14 +90,18 @@ export class PanelResizerState { pressed: observable, collapsed: observable, - onMouseUp: action, - onMouseDown: action, - onDblClick: action, - handleWidthEvent: action, + addInvisibleOverlay: action, + finishSettingWidth: action, handleLeftEvent: action, - setWidth: action, - setMinWidth: action, + handleWidthEvent: action, + onDblClick: action, + onMouseDown: action, + onMouseUp: action, reloadDefaultValues: action, + removeInvisibleOverlay: action, + setLeft: action, + setMinWidth: action, + setWidth: action, appFrame: computed, }); From 829a240f63b4ee78d837f2d7c94e20e7e1d5559b Mon Sep 17 00:00:00 2001 From: Aman Harwara Date: Fri, 21 Jan 2022 21:32:03 +0530 Subject: [PATCH 02/65] fix: tags state mobx warning (#821) --- app/assets/javascripts/ui_models/app_state/tags_state.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/ui_models/app_state/tags_state.ts b/app/assets/javascripts/ui_models/app_state/tags_state.ts index 70b654f32..ed7fd4983 100644 --- a/app/assets/javascripts/ui_models/app_state/tags_state.ts +++ b/app/assets/javascripts/ui_models/app_state/tags_state.ts @@ -8,7 +8,7 @@ import { SNSmartTag, SNTag, TagMutator, - UuidString + UuidString, } from '@standardnotes/snjs'; import { action, @@ -16,7 +16,7 @@ import { makeAutoObservable, makeObservable, observable, - runInAction + runInAction, } from 'mobx'; import { WebApplication } from '../application'; import { FeaturesState, SMART_TAGS_FEATURE_NAME } from './features_state'; @@ -148,7 +148,9 @@ export class TagsState { appEventListeners.push( this.application.addNoteCountChangeObserver((tagUuid) => { if (!tagUuid) { - this.allNotesCount_ = this.application.allCountableNotesCount(); + runInAction(() => { + this.allNotesCount_ = this.application.allCountableNotesCount(); + }); } else { this.tagsCountsState.update([ this.application.findItem(tagUuid) as SNTag, From 86b38e104166d88fb7f584ca19ab11b05fdc4aad Mon Sep 17 00:00:00 2001 From: Laurent Senta Date: Mon, 24 Jan 2022 15:50:41 +0100 Subject: [PATCH 03/65] fix: tag placeholder align (#819) * fix: padding in navigation no-tag-placeholder * fix: lint-staged was disabled * fix: make findArray typesafe --- .../javascripts/ui_models/app_state/notes_view_state.ts | 5 ++++- app/assets/stylesheets/_navigation.scss | 2 +- package.json | 4 ++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/ui_models/app_state/notes_view_state.ts b/app/assets/javascripts/ui_models/app_state/notes_view_state.ts index dda719f08..e0a31dfb1 100644 --- a/app/assets/javascripts/ui_models/app_state/notes_view_state.ts +++ b/app/assets/javascripts/ui_models/app_state/notes_view_state.ts @@ -95,7 +95,10 @@ export class NotesViewState { /** A tag could have changed its relationships, so we need to reload the filter */ this.reloadNotesDisplayOptions(); this.reloadNotes(); - if (findInArray(tags, 'uuid', this.appState.selectedTag?.uuid)) { + if ( + this.appState.selectedTag && + findInArray(tags, 'uuid', this.appState.selectedTag.uuid) + ) { /** Tag title could have changed */ this.reloadPanelTitle(); } diff --git a/app/assets/stylesheets/_navigation.scss b/app/assets/stylesheets/_navigation.scss index 7faf812ca..c0de65551 100644 --- a/app/assets/stylesheets/_navigation.scss +++ b/app/assets/stylesheets/_navigation.scss @@ -31,7 +31,7 @@ } .no-tags-placeholder { - padding: 0px 12px; + padding: 0px 14px; font-size: 12px; opacity: 0.4; margin-top: -5px; diff --git a/package.json b/package.json index 28343a799..913d5078e 100644 --- a/package.json +++ b/package.json @@ -99,7 +99,7 @@ "react-dnd-touch-backend": "^14.1.1" }, "lint-staged": { - "app/*.{js,ts,jsx,tsx}": "eslint --cache --fix", - "app/*.{js,ts,jsx,tsx,css,md}": "prettier --write" + "app/**/*.{js,ts,jsx,tsx}": "eslint --cache --fix", + "app/**/*.{js,ts,jsx,tsx,css,md}": "prettier --write" } } From 7726b1249c3eef7aedb4e31955775902111e2ed3 Mon Sep 17 00:00:00 2001 From: Laurent Senta Date: Wed, 26 Jan 2022 09:21:24 +0100 Subject: [PATCH 04/65] feat: display folders even without the premium (#826) --- .../javascripts/components/Tags/RootTagDropZone.tsx | 10 ++-------- .../javascripts/components/Tags/TagsListItem.tsx | 4 ++-- .../javascripts/ui_models/app_state/tags_state.ts | 4 ++-- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/app/assets/javascripts/components/Tags/RootTagDropZone.tsx b/app/assets/javascripts/components/Tags/RootTagDropZone.tsx index 3e758536c..10dcf0dfa 100644 --- a/app/assets/javascripts/components/Tags/RootTagDropZone.tsx +++ b/app/assets/javascripts/components/Tags/RootTagDropZone.tsx @@ -18,7 +18,6 @@ export const RootTagDropZone: React.FC = observer( ({ tagsState, featuresState }) => { const premiumModal = usePremiumModal(); const isNativeFoldersEnabled = featuresState.enableNativeFoldersFeature; - const hasFolders = featuresState.hasFolders; const [{ isOver, canDrop }, dropRef] = useDrop( () => ({ @@ -27,11 +26,6 @@ export const RootTagDropZone: React.FC = observer( return true; }, drop: (item) => { - if (!hasFolders) { - premiumModal.activate(TAG_FOLDERS_FEATURE_NAME); - return; - } - tagsState.assignParent(item.uuid, undefined); }, collect: (monitor) => ({ @@ -39,10 +33,10 @@ export const RootTagDropZone: React.FC = observer( canDrop: !!monitor.canDrop(), }), }), - [tagsState, hasFolders, premiumModal] + [tagsState, premiumModal] ); - if (!isNativeFoldersEnabled || !hasFolders) { + if (!isNativeFoldersEnabled) { return null; } diff --git a/app/assets/javascripts/components/Tags/TagsListItem.tsx b/app/assets/javascripts/components/Tags/TagsListItem.tsx index ed16e34a0..0aacdd462 100644 --- a/app/assets/javascripts/components/Tags/TagsListItem.tsx +++ b/app/assets/javascripts/components/Tags/TagsListItem.tsx @@ -120,7 +120,7 @@ export const TagsListItem: FunctionComponent = observer( isDragging: !!monitor.isDragging(), }), }), - [tag, hasFolders] + [tag, isNativeFoldersEnabled] ); const [{ isOver, canDrop }, dropRef] = useDrop( @@ -160,7 +160,7 @@ export const TagsListItem: FunctionComponent = observer( > {!tag.errorDecrypting ? (
- {hasFolders && isNativeFoldersEnabled && hasAtLeastOneFolder && ( + {isNativeFoldersEnabled && hasAtLeastOneFolder && (
Date: Thu, 27 Jan 2022 18:37:29 +0530 Subject: [PATCH 05/65] fix: Notes list options menu getting hidden (#827) --- app/assets/javascripts/components/NotesList.tsx | 2 +- app/assets/stylesheets/_notes.scss | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/components/NotesList.tsx b/app/assets/javascripts/components/NotesList.tsx index 1ca640822..56a0c6a33 100644 --- a/app/assets/javascripts/components/NotesList.tsx +++ b/app/assets/javascripts/components/NotesList.tsx @@ -84,7 +84,7 @@ export const NotesList: FunctionComponent = observer( return (
Date: Fri, 28 Jan 2022 09:54:28 -0400 Subject: [PATCH 06/65] chore(deps): upgrade components and features (#829) * chore(deps): upgrade components and features * chore: bundle Co-authored-by: Johnny Almonte --- package.json | 4 ++-- public/components/checksums.json | 6 +++--- .../dist/dist.css | 2 +- .../dist/vendor/styles/kendo.fiori.min.css | 2 +- .../package.json | 2 +- yarn.lock | 18 +++++++++++++----- 6 files changed, 21 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index 913d5078e..26f3007c4 100644 --- a/package.json +++ b/package.json @@ -85,8 +85,8 @@ "@reach/dialog": "^0.16.2", "@reach/listbox": "^0.16.2", "@reach/tooltip": "^0.16.2", - "@standardnotes/components": "1.4.3", - "@standardnotes/features": "1.25.0", + "@standardnotes/components": "1.4.4", + "@standardnotes/features": "1.26.1", "@standardnotes/snjs": "2.41.0", "@standardnotes/settings": "^1.10.0", "@standardnotes/sncrypto-web": "1.6.0", diff --git a/public/components/checksums.json b/public/components/checksums.json index b8d3e68ed..0bc31426c 100644 --- a/public/components/checksums.json +++ b/public/components/checksums.json @@ -80,9 +80,9 @@ "binary": "04d6c4765de4e2cdd4e28f3f8c30e79a49e980fa1cdd9df812e844f8486795f3" }, "org.standardnotes.standard-sheets": { - "version": "1.4.3", - "base64": "e38ed30e821bd7a4dc13026898215c892911c91217e5dcdf9d9ebdc25e05b711", - "binary": "c51b4bfbb11ca60d4f81c7c28fba7d24da56b6dcdb799d12e34e1882d9873db3" + "version": "1.4.4", + "base64": "9f583da4e61a6da1422a96ab4d1ba2835421e09d2de3c5efee2e8391431e045e", + "binary": "2464c763615910476edea2869418352b727f39847280d6f7745e07f19541f3e1" }, "org.standardnotes.file-safe": { "version": "2.0.10", diff --git a/public/components/org.standardnotes.standard-sheets/dist/dist.css b/public/components/org.standardnotes.standard-sheets/dist/dist.css index 3bf8779ab..f0d0149f9 100644 --- a/public/components/org.standardnotes.standard-sheets/dist/dist.css +++ b/public/components/org.standardnotes.standard-sheets/dist/dist.css @@ -1,4 +1,4 @@ :root{--sn-stylekit-base-font-size: 13px;--sn-stylekit-font-size-p: 1.0rem;--sn-stylekit-font-size-editor: 1.21rem;--sn-stylekit-font-size-h6: 0.8rem;--sn-stylekit-font-size-h5: 0.9rem;--sn-stylekit-font-size-h4: 1.0rem;--sn-stylekit-font-size-h3: 1.1rem;--sn-stylekit-font-size-h2: 1.2rem;--sn-stylekit-font-size-h1: 1.3rem;--sn-stylekit-neutral-color: #989898;--sn-stylekit-neutral-contrast-color: white;--sn-stylekit-info-color: #086DD6;--sn-stylekit-info-contrast-color: white;--sn-stylekit-success-color: #2B9612;--sn-stylekit-success-contrast-color: white;--sn-stylekit-warning-color: #f6a200;--sn-stylekit-warning-contrast-color: white;--sn-stylekit-danger-color: #F80324;--sn-stylekit-danger-contrast-color: white;--sn-stylekit-shadow-color: #C8C8C8;--sn-stylekit-background-color: white;--sn-stylekit-border-color: #e3e3e3;--sn-stylekit-foreground-color: black;--sn-stylekit-contrast-background-color: #F6F6F6;--sn-stylekit-contrast-foreground-color: #2e2e2e;--sn-stylekit-contrast-border-color: #e3e3e3;--sn-stylekit-secondary-background-color: #F6F6F6;--sn-stylekit-secondary-foreground-color: #2e2e2e;--sn-stylekit-secondary-border-color: #e3e3e3;--sn-stylekit-secondary-contrast-background-color: #e3e3e3;--sn-stylekit-secondary-contrast-foreground-color: #2e2e2e;--sn-styleki--secondary-contrast-border-color: #a2a2a2;--sn-stylekit-editor-background-color: var(--sn-stylekit-background-color);--sn-stylekit-editor-foreground-color: var(--sn-stylekit-foreground-color);--sn-stylekit-paragraph-text-color: #454545;--sn-stylekit-input-placeholder-color: rgb(168, 168, 168);--sn-stylekit-input-border-color: #e3e3e3;--sn-stylekit-scrollbar-thumb-color: #dfdfdf;--sn-stylekit-scrollbar-track-border-color: #E7E7E7;--sn-stylekit-general-border-radius: 2px;--sn-stylekit-simplified-chinese-font: "Microsoft Yahei", "微软雅黑体";--sn-stylekit-monospace-font: "Ubuntu Mono", courier, monospace;--sn-stylekit-sans-serif-font: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", - "Helvetica Neue", var(--sn-stylekit-simplified-chinese-font), sans-serif}.sn-component{font-family:var(--sn-stylekit-sans-serif-font);-webkit-font-smoothing:antialiased;color:var(--sn-stylekit-foreground-color)}.sn-component .sk-panel{box-shadow:0px 2px 5px var(--sn-stylekit-shadow-color);background-color:var(--sn-stylekit-background-color);border:1px solid var(--sn-stylekit-border-color);border-radius:var(--sn-stylekit-general-border-radius);display:flex;flex-direction:column;overflow:auto;flex-grow:1}.sn-component .sk-panel a:hover{text-decoration:underline}.sn-component .sk-panel.static{box-shadow:none;border:none;border-radius:0}.sn-component .sk-panel .sk-panel-header{flex-shrink:0;display:flex;justify-content:space-between;padding:1.1rem 2rem;border-bottom:1px solid var(--sn-stylekit-contrast-border-color);background-color:var(--sn-stylekit-contrast-background-color);color:var(--sn-stylekit-contrast-foreground-color);align-items:center}.sn-component .sk-panel .sk-panel-header .sk-panel-header-title{font-size:var(--sn-stylekit-font-size-h1);font-weight:500}.sn-component .sk-panel .sk-panel-header .close-button{font-weight:bold}.sn-component .sk-panel .sk-footer,.sn-component .sk-panel .sk-panel-footer{padding:1rem 2rem;border-top:1px solid var(--sn-stylekit-border-color);box-sizing:border-box}.sn-component .sk-panel .sk-footer.extra-padding,.sn-component .sk-panel .sk-panel-footer.extra-padding{padding:2rem 2rem}.sn-component .sk-panel .sk-footer .left,.sn-component .sk-panel .sk-panel-footer .left{text-align:left;display:block}.sn-component .sk-panel .sk-footer .right,.sn-component .sk-panel .sk-panel-footer .right{text-align:right;display:block}.sn-component .sk-panel .sk-panel-content{padding:1.6rem 2rem;padding-bottom:0;flex-grow:1;overflow:scroll;height:100%;overflow-y:auto !important;overflow-x:auto !important}.sn-component .sk-panel .sk-panel-content .sk-p,.sn-component .sk-panel .sk-panel-content .sk-li{color:var(--sn-stylekit-paragraph-text-color);line-height:1.3}.sn-component .sk-panel-section{padding-bottom:1.6rem;display:flex;flex-direction:column}.sn-component .sk-panel-section.sk-panel-hero{text-align:center}.sn-component .sk-panel-section .sk-p:last-child{margin-bottom:0}.sn-component .sk-panel-section:not(:last-child){margin-bottom:1.5rem;border-bottom:1px solid var(--sn-stylekit-border-color)}.sn-component .sk-panel-section:not(:last-child).no-border{border-bottom:none}.sn-component .sk-panel-section:last-child{margin-bottom:0.5rem}.sn-component .sk-panel-section.no-bottom-pad{padding-bottom:0;margin-bottom:0}.sn-component .sk-panel-section .sk-panel-section-title{margin-bottom:0.5rem;font-weight:bold;font-size:var(--sn-stylekit-font-size-h3)}.sn-component .sk-panel-section .sk-panel-section-outer-title{border-bottom:1px solid var(--sn-stylekit-border-color);padding-bottom:0.9rem;margin-top:2.1rem;margin-bottom:15px;font-size:var(--sn-stylekit-font-size-h3)}.sn-component .sk-panel-section .sk-panel-section-subtitle{font-size:var(--sn-stylekit-font-size-h5);margin-bottom:2px}.sn-component .sk-panel-section .sk-panel-section-subtitle.subtle{font-weight:normal;opacity:0.6}.sn-component .sk-panel-section .text-content .sk-p{margin-bottom:1rem}.sn-component .sk-panel-section .text-content p:first-child{margin-top:0.3rem}.sn-component .sk-panel-row{display:flex;justify-content:space-between;align-items:center;padding-top:0.4rem}.sn-component .sk-panel-row.centered{justify-content:center}.sn-component .sk-panel-row.justify-right{justify-content:flex-end}.sn-component .sk-panel-row.justify-left{justify-content:flex-start}.sn-component .sk-panel-row.align-top{align-items:flex-start}.sn-component .sk-panel-row .sk-panel-column.stretch{width:100%}.sn-component .sk-panel-row.default-padding,.sn-component .sk-panel-row:not(:last-child){padding-bottom:0.4rem}.sn-component .sk-panel-row.condensed{padding-top:0.2rem;padding-bottom:0.2rem}.sn-component .sk-panel-row .sk-p{margin:0;padding:0}.sn-component .vertical-rule{background-color:var(--sn-stylekit-border-color);height:1.5rem;width:1px}.sn-component .sk-panel-form{width:100%}.sn-component .sk-panel-form.half{width:50%}.sn-component .sk-panel-form .form-submit{margin-top:0.15rem}.sn-component .right-aligned{justify-content:flex-end;text-align:right}.sn-component .sk-menu-panel{background-color:var(--sn-stylekit-background-color);border:1px solid var(--sn-stylekit-contrast-border-color);border-radius:var(--sn-stylekit-general-border-radius);overflow:scroll;user-select:none;overflow-y:auto !important;overflow-x:auto !important}.sn-component .sk-menu-panel .sk-menu-panel-header{padding:0.8rem 1rem;border-bottom:1px solid var(--sn-stylekit-contrast-border-color);background-color:var(--sn-stylekit-contrast-background-color);color:var(--sn-stylekit-contrast-foreground-color);display:flex;justify-content:space-between;align-items:center}.sn-component .sk-menu-panel .sk-menu-panel-header-title{font-weight:bold;font-size:var(--sn-stylekit-font-size-h4)}.sn-component .sk-menu-panel .sk-menu-panel-header-subtitle{margin-top:0.2rem;opacity:0.6}.sn-component .sk-menu-panel .sk-menu-panel-row{padding:1rem 1rem;cursor:pointer;display:flex;flex-direction:row;justify-content:space-between;border-bottom:1px solid var(--sn-stylekit-border-color)}.sn-component .sk-menu-panel .sk-menu-panel-row:hover{background-color:var(--sn-stylekit-contrast-background-color);color:var(--sn-stylekit-contrast-foreground-color);border-color:var(--sn-stylekit-contrast-border-color)}.sn-component .sk-menu-panel .sk-menu-panel-row .sk-menu-panel-column{display:flex;justify-content:center;flex-direction:column}.sn-component .sk-menu-panel .sk-menu-panel-row .sk-menu-panel-column:not(:first-child){padding-left:1.0rem;padding-right:0.15rem}.sn-component .sk-menu-panel .sk-menu-panel-row .sk-menu-panel-column.stretch{width:100%}.sn-component .sk-menu-panel .sk-menu-panel-row .sk-menu-panel-column .sk-menu-panel-subrows{margin-top:1rem}.sn-component .sk-menu-panel .sk-menu-panel-row .sk-menu-panel-column .sk-menu-panel-row,.sn-component .sk-menu-panel .sk-menu-panel-row .sk-menu-panel-column .sk-menu-panel-subrow{border:1px solid var(--sn-stylekit-contrast-border-color);margin-top:-1px}.sn-component .sk-menu-panel .sk-menu-panel-row .sk-menu-panel-column .sk-menu-panel-row:hover,.sn-component .sk-menu-panel .sk-menu-panel-row .sk-menu-panel-column .sk-menu-panel-subrow:hover{background-color:var(--sn-stylekit-background-color)}.sn-component .sk-menu-panel .sk-menu-panel-row .sk-menu-panel-column .left{display:flex}.sn-component .sk-menu-panel .sk-menu-panel-row .sk-button .sk-label,.sn-component .sk-menu-panel .sk-menu-panel-row .sk-box .sk-label,.sn-component .sk-menu-panel .sk-menu-panel-row .sk-button .sk-panel-section .sk-panel-section-subtitle,.sn-component .sk-panel-section .sk-menu-panel .sk-menu-panel-row .sk-button .sk-panel-section-subtitle,.sn-component .sk-menu-panel .sk-menu-panel-row .sk-box .sk-panel-section .sk-panel-section-subtitle,.sn-component .sk-panel-section .sk-menu-panel .sk-menu-panel-row .sk-box .sk-panel-section-subtitle{font-size:var(--sn-stylekit-font-size-h6);font-weight:normal}.sn-component .sk-menu-panel .sk-menu-panel-row .sk-label,.sn-component .sk-menu-panel .sk-menu-panel-row .sk-panel-section .sk-panel-section-subtitle,.sn-component .sk-panel-section .sk-menu-panel .sk-menu-panel-row .sk-panel-section-subtitle{font-size:var(--sn-stylekit-font-size-p);font-weight:bold}.sn-component .sk-menu-panel .sk-menu-panel-row .sk-sublabel{font-size:var(--sn-stylekit-font-size-h5);margin-top:0.2rem;opacity:0.6}.sn-component .red{color:var(--sn-stylekit-danger-color)}.sn-component .tinted{color:var(--sn-stylekit-info-color)}.sn-component .selectable{user-select:text !important;-ms-user-select:text !important;-moz-user-select:text !important;-webkit-user-select:text !important}.sn-component .sk-h1,.sn-component .sk-h2,.sn-component .sk-h3,.sn-component .sk-h4,.sn-component .sk-h5{margin:0;padding:0;font-weight:normal}.sn-component .sk-h1{font-weight:500;font-size:var(--sn-stylekit-font-size-h1);line-height:1.9rem}.sn-component .sk-h2{font-size:var(--sn-stylekit-font-size-h2);line-height:1.8rem}.sn-component .sk-h3{font-size:var(--sn-stylekit-font-size-h3);line-height:1.7rem}.sn-component .sk-h4{font-size:var(--sn-stylekit-font-size-p);line-height:1.4rem}.sn-component .sk-h5{font-size:var(--sn-stylekit-font-size-h5)}.sn-component .sk-bold{font-weight:bold}.sn-component .sk-font-small{font-size:var(--sn-stylekit-font-size-h5)}.sn-component .sk-font-normal{font-size:var(--sn-stylekit-font-size-p)}.sn-component .sk-font-large{font-size:var(--sn-stylekit-font-size-h3)}.sn-component a.sk-a{cursor:pointer;user-select:none}.sn-component a.sk-a.disabled{color:var(--sn-stylekit-neutral-color);opacity:0.6}.sn-component a.sk-a.boxed{border-radius:var(--sn-stylekit-general-border-radius);padding:0.3rem 0.4rem}.sn-component a.sk-a.boxed:hover{text-decoration:none}.sn-component a.sk-a.boxed.neutral{background-color:var(--sn-stylekit-neutral-color);color:var(--sn-stylekit-neutral-contrast-color)}.sn-component a.sk-a.boxed.info{background-color:var(--sn-stylekit-info-color);color:var(--sn-stylekit-info-contrast-color)}.sn-component a.sk-a.boxed.warning{background-color:var(--sn-stylekit-warning-color);color:var(--sn-stylekit-warning-contrast-color)}.sn-component a.sk-a.boxed.danger{background-color:var(--sn-stylekit-danger-color);color:var(--sn-stylekit-danger-contrast-color)}.sn-component a.sk-a.boxed.success{background-color:var(--sn-stylekit-success-color);color:var(--sn-stylekit-success-contrast-color)}.sn-component .wrap{word-wrap:break-word}.sn-component *.sk-base{color:var(--sn-stylekit-foreground-color)}.sn-component *.contrast{color:var(--sn-stylekit-contrast-foreground-color)}.sn-component *.neutral{color:var(--sn-stylekit-neutral-color)}.sn-component *.info{color:var(--sn-stylekit-info-color)}.sn-component *.info-contrast{color:var(--sn-stylekit-info-contrast-color)}.sn-component *.warning{color:var(--sn-stylekit-warning-color)}.sn-component *.danger{color:var(--sn-stylekit-danger-color)}.sn-component *.success{color:var(--sn-stylekit-success-color)}.sn-component *.info-i{color:var(--sn-stylekit-info-color) !important}.sn-component *.warning-i{color:var(--sn-stylekit-warning-color) !important}.sn-component *.danger-i{color:var(--sn-stylekit-danger-color) !important}.sn-component *.success-i{color:var(--sn-stylekit-success-color) !important}.sn-component *.clear{background-color:transparent;border:none}.sn-component .center-text{text-align:center !important;justify-content:center !important}.sn-component p.sk-p{margin:0.5rem 0}.sn-component input.sk-input{box-sizing:border-box;padding:0.7rem 0.8rem;margin:0.30rem 0;border:none;font-size:var(--sn-stylekit-font-size-h3);width:100%;outline:0;resize:none}.sn-component input.sk-input.clear{color:var(--sn-stylekit-foreground-color);background-color:transparent;border:none}.sn-component input.sk-input.no-border{border:none}.sn-component .sk-label,.sn-component .sk-panel-section .sk-panel-section-subtitle{font-weight:bold}.sn-component .sk-label.no-bold,.sn-component .sk-panel-section .no-bold.sk-panel-section-subtitle{font-weight:normal}.sn-component label.sk-label,.sn-component .sk-panel-section label.sk-panel-section-subtitle{margin:0.7rem 0;display:block}.sn-component label.sk-label input[type='checkbox'],.sn-component .sk-panel-section label.sk-panel-section-subtitle input[type='checkbox'],.sn-component input[type='radio']{width:auto;margin-right:0.45rem;vertical-align:middle}.sn-component .sk-horizontal-group>*,.sn-component .sk-input-group>*{display:inline-block;vertical-align:middle}.sn-component .sk-horizontal-group>*:not(:first-child),.sn-component .sk-input-group>*:not(:first-child){margin-left:0.9rem}.sn-component .sk-border-bottom{border-bottom:1px solid var(--sn-stylekit-border-color)}.sn-component .sk-checkbox-group{padding-top:0.5rem;padding-bottom:0.3rem}.sn-component ::placeholder{color:var(--sn-stylekit-input-placeholder-color)}.sn-component :-ms-input-placeholder{color:var(--sn-stylekit-input-placeholder-color)}.sn-component ::-ms-input-placeholder{color:var(--sn-stylekit-input-placeholder-color)}.sn-component .sk-button-group.stretch{display:flex;width:100%}.sn-component .sk-button-group.stretch .sk-button,.sn-component .sk-button-group.stretch .sk-box{display:block;flex-grow:1;text-align:center}.sn-component .sk-button-group .sk-button,.sn-component .sk-button-group .sk-box{display:inline-block;vertical-align:middle}.sn-component .sk-button-group .sk-button:not(:last-child),.sn-component .sk-button-group .sk-box:not(:last-child){margin-right:5px}.sn-component .sk-button-group .sk-button:not(:last-child).featured,.sn-component .sk-button-group .sk-box:not(:last-child).featured{margin-right:8px}.sn-component .sk-segmented-buttons{display:flex;flex-direction:row}.sn-component .sk-segmented-buttons .sk-button,.sn-component .sk-segmented-buttons .sk-box{border-radius:0;white-space:nowrap;margin:0;margin-left:0 !important;margin-right:0 !important}.sn-component .sk-segmented-buttons .sk-button:not(:last-child),.sn-component .sk-segmented-buttons .sk-box:not(:last-child){border-right:none;border-radius:0}.sn-component .sk-segmented-buttons .sk-button:first-child,.sn-component .sk-segmented-buttons .sk-box:first-child{border-top-left-radius:var(--sn-stylekit-general-border-radius);border-bottom-left-radius:var(--sn-stylekit-general-border-radius);border-right:none;border-top-right-radius:0;border-bottom-right-radius:0}.sn-component .sk-segmented-buttons .sk-button:last-child,.sn-component .sk-segmented-buttons .sk-box:last-child{border-top-right-radius:var(--sn-stylekit-general-border-radius);border-bottom-right-radius:var(--sn-stylekit-general-border-radius);border-left:none;border-top-left-radius:0;border-bottom-left-radius:0}.sn-component .sk-box-group .sk-box{display:inline-block}.sn-component .sk-box-group .sk-box:not(:last-child){margin-right:5px}.sn-component .sk-a.button{text-decoration:none}.sn-component .sk-button,.sn-component .sk-box{display:table;padding:0.5rem 0.7rem;font-size:var(--sn-stylekit-font-size-h5);cursor:pointer;text-align:center;user-select:none}.sn-component .sk-button.no-hover-border:after,.sn-component .no-hover-border.sk-box:after{color:transparent !important}.sn-component .sk-button.wide,.sn-component .wide.sk-box{padding:0.3rem 1.7rem}.sn-component .sk-button>.sk-label,.sn-component .sk-box>.sk-label,.sn-component .sk-panel-section .sk-button>.sk-panel-section-subtitle,.sn-component .sk-panel-section .sk-box>.sk-panel-section-subtitle{font-weight:bold;display:block;text-align:center}.sn-component .sk-button.big,.sn-component .big.sk-box{font-size:var(--sn-stylekit-font-size-h3);padding:0.7rem 2.5rem}.sn-component .sk-box{padding:2.5rem 1.5rem}.sn-component .sk-button.sk-base,.sn-component .sk-base.sk-box,.sn-component .sk-box.sk-base,.sn-component .sk-circle.sk-base{color:var(--sn-stylekit-foreground-color);position:relative;background-color:var(--sn-stylekit-background-color);overflow:hidden;border-radius:var(--sn-stylekit-general-border-radius);border-color:var(--sn-stylekit-background-color)}.sn-component .sk-button.sk-base *,.sn-component .sk-base.sk-box *,.sn-component .sk-box.sk-base *,.sn-component .sk-circle.sk-base *{position:relative}.sn-component .sk-button.sk-base:before,.sn-component .sk-base.sk-box:before,.sn-component .sk-box.sk-base:before,.sn-component .sk-circle.sk-base:before{content:"";position:absolute;top:0;left:0;width:100%;height:100%;background-color:var(--sn-stylekit-background-color);opacity:1.0;border-radius:var(--sn-stylekit-general-border-radius)}.sn-component .sk-button.sk-base:after,.sn-component .sk-base.sk-box:after,.sn-component .sk-box.sk-base:after,.sn-component .sk-circle.sk-base:after{content:'';display:block;height:100%;position:absolute;top:0;left:0;width:100%;border-radius:var(--sn-stylekit-general-border-radius);pointer-events:none;box-shadow:inset 0 0 0 1px;color:var(--sn-stylekit-background-color)}.sn-component .sk-button.sk-base:hover:before,.sn-component .sk-base.sk-box:hover:before,.sn-component .sk-box.sk-base:hover:before,.sn-component .sk-circle.sk-base:hover:before{filter:brightness(130%)}.sn-component .sk-button.sk-base.no-bg,.sn-component .sk-base.no-bg.sk-box,.sn-component .sk-box.sk-base.no-bg,.sn-component .sk-circle.sk-base.no-bg{background-color:transparent}.sn-component .sk-button.sk-base.no-bg:before,.sn-component .sk-base.no-bg.sk-box:before,.sn-component .sk-box.sk-base.no-bg:before,.sn-component .sk-circle.sk-base.no-bg:before{content:none}.sn-component .sk-button.sk-base.featured,.sn-component .sk-base.featured.sk-box,.sn-component .sk-box.sk-base.featured,.sn-component .sk-circle.sk-base.featured{border:none;padding:0.75rem 1.25rem;font-size:var(--sn-stylekit-font-size-h3)}.sn-component .sk-button.sk-base.featured:before,.sn-component .sk-base.featured.sk-box:before,.sn-component .sk-box.sk-base.featured:before,.sn-component .sk-circle.sk-base.featured:before{opacity:1.0}.sn-component .sk-button.contrast,.sn-component .contrast.sk-box,.sn-component .sk-box.contrast,.sn-component .sk-circle.contrast{color:var(--sn-stylekit-contrast-foreground-color);position:relative;background-color:var(--sn-stylekit-contrast-background-color);overflow:hidden;border-radius:var(--sn-stylekit-general-border-radius);border-color:var(--sn-stylekit-contrast-background-color)}.sn-component .sk-button.contrast *,.sn-component .contrast.sk-box *,.sn-component .sk-box.contrast *,.sn-component .sk-circle.contrast *{position:relative}.sn-component .sk-button.contrast:before,.sn-component .contrast.sk-box:before,.sn-component .sk-box.contrast:before,.sn-component .sk-circle.contrast:before{content:"";position:absolute;top:0;left:0;width:100%;height:100%;background-color:var(--sn-stylekit-contrast-background-color);opacity:1.0;border-radius:var(--sn-stylekit-general-border-radius)}.sn-component .sk-button.contrast:after,.sn-component .contrast.sk-box:after,.sn-component .sk-box.contrast:after,.sn-component .sk-circle.contrast:after{content:'';display:block;height:100%;position:absolute;top:0;left:0;width:100%;border-radius:var(--sn-stylekit-general-border-radius);pointer-events:none;box-shadow:inset 0 0 0 1px;color:var(--sn-stylekit-contrast-background-color)}.sn-component .sk-button.contrast:hover:before,.sn-component .contrast.sk-box:hover:before,.sn-component .sk-box.contrast:hover:before,.sn-component .sk-circle.contrast:hover:before{filter:brightness(130%)}.sn-component .sk-button.contrast.no-bg,.sn-component .contrast.no-bg.sk-box,.sn-component .sk-box.contrast.no-bg,.sn-component .sk-circle.contrast.no-bg{background-color:transparent}.sn-component .sk-button.contrast.no-bg:before,.sn-component .contrast.no-bg.sk-box:before,.sn-component .sk-box.contrast.no-bg:before,.sn-component .sk-circle.contrast.no-bg:before{content:none}.sn-component .sk-button.contrast.featured,.sn-component .contrast.featured.sk-box,.sn-component .sk-box.contrast.featured,.sn-component .sk-circle.contrast.featured{border:none;padding:0.75rem 1.25rem;font-size:var(--sn-stylekit-font-size-h3)}.sn-component .sk-button.contrast.featured:before,.sn-component .contrast.featured.sk-box:before,.sn-component .sk-box.contrast.featured:before,.sn-component .sk-circle.contrast.featured:before{opacity:1.0}.sn-component .sk-button.sk-secondary,.sn-component .sk-secondary.sk-box,.sn-component .sk-box.sk-secondary,.sn-component .sk-circle.sk-secondary{color:var(--sn-stylekit-secondary-foreground-color);position:relative;background-color:var(--sn-stylekit-secondary-background-color);overflow:hidden;border-radius:var(--sn-stylekit-general-border-radius);border-color:var(--sn-stylekit-secondary-background-color)}.sn-component .sk-button.sk-secondary *,.sn-component .sk-secondary.sk-box *,.sn-component .sk-box.sk-secondary *,.sn-component .sk-circle.sk-secondary *{position:relative}.sn-component .sk-button.sk-secondary:before,.sn-component .sk-secondary.sk-box:before,.sn-component .sk-box.sk-secondary:before,.sn-component .sk-circle.sk-secondary:before{content:"";position:absolute;top:0;left:0;width:100%;height:100%;background-color:var(--sn-stylekit-secondary-background-color);opacity:1.0;border-radius:var(--sn-stylekit-general-border-radius)}.sn-component .sk-button.sk-secondary:after,.sn-component .sk-secondary.sk-box:after,.sn-component .sk-box.sk-secondary:after,.sn-component .sk-circle.sk-secondary:after{content:'';display:block;height:100%;position:absolute;top:0;left:0;width:100%;border-radius:var(--sn-stylekit-general-border-radius);pointer-events:none;box-shadow:inset 0 0 0 1px;color:var(--sn-stylekit-secondary-background-color)}.sn-component .sk-button.sk-secondary:hover:before,.sn-component .sk-secondary.sk-box:hover:before,.sn-component .sk-box.sk-secondary:hover:before,.sn-component .sk-circle.sk-secondary:hover:before{filter:brightness(130%)}.sn-component .sk-button.sk-secondary.no-bg,.sn-component .sk-secondary.no-bg.sk-box,.sn-component .sk-box.sk-secondary.no-bg,.sn-component .sk-circle.sk-secondary.no-bg{background-color:transparent}.sn-component .sk-button.sk-secondary.no-bg:before,.sn-component .sk-secondary.no-bg.sk-box:before,.sn-component .sk-box.sk-secondary.no-bg:before,.sn-component .sk-circle.sk-secondary.no-bg:before{content:none}.sn-component .sk-button.sk-secondary.featured,.sn-component .sk-secondary.featured.sk-box,.sn-component .sk-box.sk-secondary.featured,.sn-component .sk-circle.sk-secondary.featured{border:none;padding:0.75rem 1.25rem;font-size:var(--sn-stylekit-font-size-h3)}.sn-component .sk-button.sk-secondary.featured:before,.sn-component .sk-secondary.featured.sk-box:before,.sn-component .sk-box.sk-secondary.featured:before,.sn-component .sk-circle.sk-secondary.featured:before{opacity:1.0}.sn-component .sk-button.sk-secondary-contrast,.sn-component .sk-secondary-contrast.sk-box,.sn-component .sk-box.sk-secondary-contrast,.sn-component .sk-circle.sk-secondary-contrast{color:var(--sn-stylekit-secondary-contrast-foreground-color);position:relative;background-color:var(--sn-stylekit-secondary-contrast-background-color);overflow:hidden;border-radius:var(--sn-stylekit-general-border-radius);border-color:var(--sn-stylekit-secondary-contrast-background-color)}.sn-component .sk-button.sk-secondary-contrast *,.sn-component .sk-secondary-contrast.sk-box *,.sn-component .sk-box.sk-secondary-contrast *,.sn-component .sk-circle.sk-secondary-contrast *{position:relative}.sn-component .sk-button.sk-secondary-contrast:before,.sn-component .sk-secondary-contrast.sk-box:before,.sn-component .sk-box.sk-secondary-contrast:before,.sn-component .sk-circle.sk-secondary-contrast:before{content:"";position:absolute;top:0;left:0;width:100%;height:100%;background-color:var(--sn-stylekit-secondary-contrast-background-color);opacity:1.0;border-radius:var(--sn-stylekit-general-border-radius)}.sn-component .sk-button.sk-secondary-contrast:after,.sn-component .sk-secondary-contrast.sk-box:after,.sn-component .sk-box.sk-secondary-contrast:after,.sn-component .sk-circle.sk-secondary-contrast:after{content:'';display:block;height:100%;position:absolute;top:0;left:0;width:100%;border-radius:var(--sn-stylekit-general-border-radius);pointer-events:none;box-shadow:inset 0 0 0 1px;color:var(--sn-stylekit-secondary-contrast-background-color)}.sn-component .sk-button.sk-secondary-contrast:hover:before,.sn-component .sk-secondary-contrast.sk-box:hover:before,.sn-component .sk-box.sk-secondary-contrast:hover:before,.sn-component .sk-circle.sk-secondary-contrast:hover:before{filter:brightness(130%)}.sn-component .sk-button.sk-secondary-contrast.no-bg,.sn-component .sk-secondary-contrast.no-bg.sk-box,.sn-component .sk-box.sk-secondary-contrast.no-bg,.sn-component .sk-circle.sk-secondary-contrast.no-bg{background-color:transparent}.sn-component .sk-button.sk-secondary-contrast.no-bg:before,.sn-component .sk-secondary-contrast.no-bg.sk-box:before,.sn-component .sk-box.sk-secondary-contrast.no-bg:before,.sn-component .sk-circle.sk-secondary-contrast.no-bg:before{content:none}.sn-component .sk-button.sk-secondary-contrast.featured,.sn-component .sk-secondary-contrast.featured.sk-box,.sn-component .sk-box.sk-secondary-contrast.featured,.sn-component .sk-circle.sk-secondary-contrast.featured{border:none;padding:0.75rem 1.25rem;font-size:var(--sn-stylekit-font-size-h3)}.sn-component .sk-button.sk-secondary-contrast.featured:before,.sn-component .sk-secondary-contrast.featured.sk-box:before,.sn-component .sk-box.sk-secondary-contrast.featured:before,.sn-component .sk-circle.sk-secondary-contrast.featured:before{opacity:1.0}.sn-component .sk-button.neutral,.sn-component .neutral.sk-box,.sn-component .sk-box.neutral,.sn-component .sk-circle.neutral{color:var(--sn-stylekit-neutral-contrast-color);position:relative;background-color:var(--sn-stylekit-neutral-color);overflow:hidden;border-radius:var(--sn-stylekit-general-border-radius);border-color:var(--sn-stylekit-neutral-color)}.sn-component .sk-button.neutral *,.sn-component .neutral.sk-box *,.sn-component .sk-box.neutral *,.sn-component .sk-circle.neutral *{position:relative}.sn-component .sk-button.neutral:before,.sn-component .neutral.sk-box:before,.sn-component .sk-box.neutral:before,.sn-component .sk-circle.neutral:before{content:"";position:absolute;top:0;left:0;width:100%;height:100%;background-color:var(--sn-stylekit-neutral-color);opacity:1.0;border-radius:var(--sn-stylekit-general-border-radius)}.sn-component .sk-button.neutral:after,.sn-component .neutral.sk-box:after,.sn-component .sk-box.neutral:after,.sn-component .sk-circle.neutral:after{content:'';display:block;height:100%;position:absolute;top:0;left:0;width:100%;border-radius:var(--sn-stylekit-general-border-radius);pointer-events:none;box-shadow:inset 0 0 0 1px;color:var(--sn-stylekit-neutral-color)}.sn-component .sk-button.neutral:hover:before,.sn-component .neutral.sk-box:hover:before,.sn-component .sk-box.neutral:hover:before,.sn-component .sk-circle.neutral:hover:before{filter:brightness(130%)}.sn-component .sk-button.neutral.no-bg,.sn-component .neutral.no-bg.sk-box,.sn-component .sk-box.neutral.no-bg,.sn-component .sk-circle.neutral.no-bg{background-color:transparent}.sn-component .sk-button.neutral.no-bg:before,.sn-component .neutral.no-bg.sk-box:before,.sn-component .sk-box.neutral.no-bg:before,.sn-component .sk-circle.neutral.no-bg:before{content:none}.sn-component .sk-button.neutral.featured,.sn-component .neutral.featured.sk-box,.sn-component .sk-box.neutral.featured,.sn-component .sk-circle.neutral.featured{border:none;padding:0.75rem 1.25rem;font-size:var(--sn-stylekit-font-size-h3)}.sn-component .sk-button.neutral.featured:before,.sn-component .neutral.featured.sk-box:before,.sn-component .sk-box.neutral.featured:before,.sn-component .sk-circle.neutral.featured:before{opacity:1.0}.sn-component .sk-button.info,.sn-component .info.sk-box,.sn-component .sk-box.info,.sn-component .sk-circle.info{color:var(--sn-stylekit-info-contrast-color);position:relative;background-color:var(--sn-stylekit-info-color);overflow:hidden;border-radius:var(--sn-stylekit-general-border-radius);border-color:var(--sn-stylekit-info-color)}.sn-component .sk-button.info *,.sn-component .info.sk-box *,.sn-component .sk-box.info *,.sn-component .sk-circle.info *{position:relative}.sn-component .sk-button.info:before,.sn-component .info.sk-box:before,.sn-component .sk-box.info:before,.sn-component .sk-circle.info:before{content:"";position:absolute;top:0;left:0;width:100%;height:100%;background-color:var(--sn-stylekit-info-color);opacity:1.0;border-radius:var(--sn-stylekit-general-border-radius)}.sn-component .sk-button.info:after,.sn-component .info.sk-box:after,.sn-component .sk-box.info:after,.sn-component .sk-circle.info:after{content:'';display:block;height:100%;position:absolute;top:0;left:0;width:100%;border-radius:var(--sn-stylekit-general-border-radius);pointer-events:none;box-shadow:inset 0 0 0 1px;color:var(--sn-stylekit-info-color)}.sn-component .sk-button.info:hover:before,.sn-component .info.sk-box:hover:before,.sn-component .sk-box.info:hover:before,.sn-component .sk-circle.info:hover:before{filter:brightness(130%)}.sn-component .sk-button.info.no-bg,.sn-component .info.no-bg.sk-box,.sn-component .sk-box.info.no-bg,.sn-component .sk-circle.info.no-bg{background-color:transparent}.sn-component .sk-button.info.no-bg:before,.sn-component .info.no-bg.sk-box:before,.sn-component .sk-box.info.no-bg:before,.sn-component .sk-circle.info.no-bg:before{content:none}.sn-component .sk-button.info.featured,.sn-component .info.featured.sk-box,.sn-component .sk-box.info.featured,.sn-component .sk-circle.info.featured{border:none;padding:0.75rem 1.25rem;font-size:var(--sn-stylekit-font-size-h3)}.sn-component .sk-button.info.featured:before,.sn-component .info.featured.sk-box:before,.sn-component .sk-box.info.featured:before,.sn-component .sk-circle.info.featured:before{opacity:1.0}.sn-component .sk-button.warning,.sn-component .warning.sk-box,.sn-component .sk-box.warning,.sn-component .sk-circle.warning{color:var(--sn-stylekit-warning-contrast-color);position:relative;background-color:var(--sn-stylekit-warning-color);overflow:hidden;border-radius:var(--sn-stylekit-general-border-radius);border-color:var(--sn-stylekit-warning-color)}.sn-component .sk-button.warning *,.sn-component .warning.sk-box *,.sn-component .sk-box.warning *,.sn-component .sk-circle.warning *{position:relative}.sn-component .sk-button.warning:before,.sn-component .warning.sk-box:before,.sn-component .sk-box.warning:before,.sn-component .sk-circle.warning:before{content:"";position:absolute;top:0;left:0;width:100%;height:100%;background-color:var(--sn-stylekit-warning-color);opacity:1.0;border-radius:var(--sn-stylekit-general-border-radius)}.sn-component .sk-button.warning:after,.sn-component .warning.sk-box:after,.sn-component .sk-box.warning:after,.sn-component .sk-circle.warning:after{content:'';display:block;height:100%;position:absolute;top:0;left:0;width:100%;border-radius:var(--sn-stylekit-general-border-radius);pointer-events:none;box-shadow:inset 0 0 0 1px;color:var(--sn-stylekit-warning-color)}.sn-component .sk-button.warning:hover:before,.sn-component .warning.sk-box:hover:before,.sn-component .sk-box.warning:hover:before,.sn-component .sk-circle.warning:hover:before{filter:brightness(130%)}.sn-component .sk-button.warning.no-bg,.sn-component .warning.no-bg.sk-box,.sn-component .sk-box.warning.no-bg,.sn-component .sk-circle.warning.no-bg{background-color:transparent}.sn-component .sk-button.warning.no-bg:before,.sn-component .warning.no-bg.sk-box:before,.sn-component .sk-box.warning.no-bg:before,.sn-component .sk-circle.warning.no-bg:before{content:none}.sn-component .sk-button.warning.featured,.sn-component .warning.featured.sk-box,.sn-component .sk-box.warning.featured,.sn-component .sk-circle.warning.featured{border:none;padding:0.75rem 1.25rem;font-size:var(--sn-stylekit-font-size-h3)}.sn-component .sk-button.warning.featured:before,.sn-component .warning.featured.sk-box:before,.sn-component .sk-box.warning.featured:before,.sn-component .sk-circle.warning.featured:before{opacity:1.0}.sn-component .sk-button.danger,.sn-component .danger.sk-box,.sn-component .sk-box.danger,.sn-component .sk-circle.danger{color:var(--sn-stylekit-danger-contrast-color);position:relative;background-color:var(--sn-stylekit-danger-color);overflow:hidden;border-radius:var(--sn-stylekit-general-border-radius);border-color:var(--sn-stylekit-danger-color)}.sn-component .sk-button.danger *,.sn-component .danger.sk-box *,.sn-component .sk-box.danger *,.sn-component .sk-circle.danger *{position:relative}.sn-component .sk-button.danger:before,.sn-component .danger.sk-box:before,.sn-component .sk-box.danger:before,.sn-component .sk-circle.danger:before{content:"";position:absolute;top:0;left:0;width:100%;height:100%;background-color:var(--sn-stylekit-danger-color);opacity:1.0;border-radius:var(--sn-stylekit-general-border-radius)}.sn-component .sk-button.danger:after,.sn-component .danger.sk-box:after,.sn-component .sk-box.danger:after,.sn-component .sk-circle.danger:after{content:'';display:block;height:100%;position:absolute;top:0;left:0;width:100%;border-radius:var(--sn-stylekit-general-border-radius);pointer-events:none;box-shadow:inset 0 0 0 1px;color:var(--sn-stylekit-danger-color)}.sn-component .sk-button.danger:hover:before,.sn-component .danger.sk-box:hover:before,.sn-component .sk-box.danger:hover:before,.sn-component .sk-circle.danger:hover:before{filter:brightness(130%)}.sn-component .sk-button.danger.no-bg,.sn-component .danger.no-bg.sk-box,.sn-component .sk-box.danger.no-bg,.sn-component .sk-circle.danger.no-bg{background-color:transparent}.sn-component .sk-button.danger.no-bg:before,.sn-component .danger.no-bg.sk-box:before,.sn-component .sk-box.danger.no-bg:before,.sn-component .sk-circle.danger.no-bg:before{content:none}.sn-component .sk-button.danger.featured,.sn-component .danger.featured.sk-box,.sn-component .sk-box.danger.featured,.sn-component .sk-circle.danger.featured{border:none;padding:0.75rem 1.25rem;font-size:var(--sn-stylekit-font-size-h3)}.sn-component .sk-button.danger.featured:before,.sn-component .danger.featured.sk-box:before,.sn-component .sk-box.danger.featured:before,.sn-component .sk-circle.danger.featured:before{opacity:1.0}.sn-component .sk-button.success,.sn-component .success.sk-box,.sn-component .sk-box.success,.sn-component .sk-circle.success{color:var(--sn-stylekit-success-contrast-color);position:relative;background-color:var(--sn-stylekit-success-color);overflow:hidden;border-radius:var(--sn-stylekit-general-border-radius);border-color:var(--sn-stylekit-success-color)}.sn-component .sk-button.success *,.sn-component .success.sk-box *,.sn-component .sk-box.success *,.sn-component .sk-circle.success *{position:relative}.sn-component .sk-button.success:before,.sn-component .success.sk-box:before,.sn-component .sk-box.success:before,.sn-component .sk-circle.success:before{content:"";position:absolute;top:0;left:0;width:100%;height:100%;background-color:var(--sn-stylekit-success-color);opacity:1.0;border-radius:var(--sn-stylekit-general-border-radius)}.sn-component .sk-button.success:after,.sn-component .success.sk-box:after,.sn-component .sk-box.success:after,.sn-component .sk-circle.success:after{content:'';display:block;height:100%;position:absolute;top:0;left:0;width:100%;border-radius:var(--sn-stylekit-general-border-radius);pointer-events:none;box-shadow:inset 0 0 0 1px;color:var(--sn-stylekit-success-color)}.sn-component .sk-button.success:hover:before,.sn-component .success.sk-box:hover:before,.sn-component .sk-box.success:hover:before,.sn-component .sk-circle.success:hover:before{filter:brightness(130%)}.sn-component .sk-button.success.no-bg,.sn-component .success.no-bg.sk-box,.sn-component .sk-box.success.no-bg,.sn-component .sk-circle.success.no-bg{background-color:transparent}.sn-component .sk-button.success.no-bg:before,.sn-component .success.no-bg.sk-box:before,.sn-component .sk-box.success.no-bg:before,.sn-component .sk-circle.success.no-bg:before{content:none}.sn-component .sk-button.success.featured,.sn-component .success.featured.sk-box,.sn-component .sk-box.success.featured,.sn-component .sk-circle.success.featured{border:none;padding:0.75rem 1.25rem;font-size:var(--sn-stylekit-font-size-h3)}.sn-component .sk-button.success.featured:before,.sn-component .success.featured.sk-box:before,.sn-component .sk-box.success.featured:before,.sn-component .sk-circle.success.featured:before{opacity:1.0}.sn-component .sk-notification.contrast,.sn-component .sk-input.contrast{color:var(--sn-stylekit-contrast-foreground-color);position:relative;background-color:var(--sn-stylekit-contrast-background-color);overflow:hidden;border-radius:var(--sn-stylekit-general-border-radius);border-color:var(--sn-stylekit-contrast-border-color);border:1px solid var(--sn-stylekit-contrast-border-color)}.sn-component .sk-notification.contrast *,.sn-component .sk-input.contrast *{position:relative}.sn-component .sk-notification.contrast:before,.sn-component .sk-input.contrast:before{content:"";position:absolute;top:0;left:0;width:100%;height:100%;background-color:var(--sn-stylekit-contrast-background-color);opacity:1.0;border-radius:var(--sn-stylekit-general-border-radius)}.sn-component .sk-notification.contrast:after,.sn-component .sk-input.contrast:after{content:'';display:block;height:100%;position:absolute;top:0;left:0;width:100%;border-radius:var(--sn-stylekit-general-border-radius);pointer-events:none;color:var(--sn-stylekit-contrast-border-color);border-color:var(--sn-stylekit-contrast-border-color)}.sn-component .sk-notification.contrast.no-bg,.sn-component .sk-input.contrast.no-bg{background-color:transparent}.sn-component .sk-notification.contrast.no-bg:before,.sn-component .sk-input.contrast.no-bg:before{content:none}.sn-component .sk-notification.contrast.featured,.sn-component .sk-input.contrast.featured{border:none;padding:0.75rem 1.25rem;font-size:var(--sn-stylekit-font-size-h3)}.sn-component .sk-notification.contrast.featured:before,.sn-component .sk-input.contrast.featured:before{opacity:1.0}.sn-component .sk-notification.sk-secondary,.sn-component .sk-input.sk-secondary{color:var(--sn-stylekit-secondary-foreground-color);position:relative;background-color:var(--sn-stylekit-secondary-background-color);overflow:hidden;border-radius:var(--sn-stylekit-general-border-radius);border-color:var(--sn-stylekit-secondary-border-color);border:1px solid var(--sn-stylekit-secondary-border-color)}.sn-component .sk-notification.sk-secondary *,.sn-component .sk-input.sk-secondary *{position:relative}.sn-component .sk-notification.sk-secondary:before,.sn-component .sk-input.sk-secondary:before{content:"";position:absolute;top:0;left:0;width:100%;height:100%;background-color:var(--sn-stylekit-secondary-background-color);opacity:1.0;border-radius:var(--sn-stylekit-general-border-radius)}.sn-component .sk-notification.sk-secondary:after,.sn-component .sk-input.sk-secondary:after{content:'';display:block;height:100%;position:absolute;top:0;left:0;width:100%;border-radius:var(--sn-stylekit-general-border-radius);pointer-events:none;color:var(--sn-stylekit-secondary-border-color);border-color:var(--sn-stylekit-secondary-border-color)}.sn-component .sk-notification.sk-secondary.no-bg,.sn-component .sk-input.sk-secondary.no-bg{background-color:transparent}.sn-component .sk-notification.sk-secondary.no-bg:before,.sn-component .sk-input.sk-secondary.no-bg:before{content:none}.sn-component .sk-notification.sk-secondary.featured,.sn-component .sk-input.sk-secondary.featured{border:none;padding:0.75rem 1.25rem;font-size:var(--sn-stylekit-font-size-h3)}.sn-component .sk-notification.sk-secondary.featured:before,.sn-component .sk-input.sk-secondary.featured:before{opacity:1.0}.sn-component .sk-notification.sk-secondary-contrast,.sn-component .sk-input.sk-secondary-contrast{color:var(--sn-stylekit-secondary-contrast-foreground-color);position:relative;background-color:var(--sn-stylekit-secondary-contrast-background-color);overflow:hidden;border-radius:var(--sn-stylekit-general-border-radius);border-color:var(--sn-stylekit-secondary-contrast-border-color);border:1px solid var(--sn-stylekit-secondary-contrast-border-color)}.sn-component .sk-notification.sk-secondary-contrast *,.sn-component .sk-input.sk-secondary-contrast *{position:relative}.sn-component .sk-notification.sk-secondary-contrast:before,.sn-component .sk-input.sk-secondary-contrast:before{content:"";position:absolute;top:0;left:0;width:100%;height:100%;background-color:var(--sn-stylekit-secondary-contrast-background-color);opacity:1.0;border-radius:var(--sn-stylekit-general-border-radius)}.sn-component .sk-notification.sk-secondary-contrast:after,.sn-component .sk-input.sk-secondary-contrast:after{content:'';display:block;height:100%;position:absolute;top:0;left:0;width:100%;border-radius:var(--sn-stylekit-general-border-radius);pointer-events:none;color:var(--sn-stylekit-secondary-contrast-border-color);border-color:var(--sn-stylekit-secondary-contrast-border-color)}.sn-component .sk-notification.sk-secondary-contrast.no-bg,.sn-component .sk-input.sk-secondary-contrast.no-bg{background-color:transparent}.sn-component .sk-notification.sk-secondary-contrast.no-bg:before,.sn-component .sk-input.sk-secondary-contrast.no-bg:before{content:none}.sn-component .sk-notification.sk-secondary-contrast.featured,.sn-component .sk-input.sk-secondary-contrast.featured{border:none;padding:0.75rem 1.25rem;font-size:var(--sn-stylekit-font-size-h3)}.sn-component .sk-notification.sk-secondary-contrast.featured:before,.sn-component .sk-input.sk-secondary-contrast.featured:before{opacity:1.0}.sn-component .sk-notification.sk-base,.sn-component .sk-input.sk-base{color:var(--sn-stylekit-foreground-color);position:relative;background-color:var(--sn-stylekit-background-color);overflow:hidden;border-radius:var(--sn-stylekit-general-border-radius);border-color:var(--sn-stylekit-border-color);border:1px solid var(--sn-stylekit-border-color)}.sn-component .sk-notification.sk-base *,.sn-component .sk-input.sk-base *{position:relative}.sn-component .sk-notification.sk-base:before,.sn-component .sk-input.sk-base:before{content:"";position:absolute;top:0;left:0;width:100%;height:100%;background-color:var(--sn-stylekit-background-color);opacity:1.0;border-radius:var(--sn-stylekit-general-border-radius)}.sn-component .sk-notification.sk-base:after,.sn-component .sk-input.sk-base:after{content:'';display:block;height:100%;position:absolute;top:0;left:0;width:100%;border-radius:var(--sn-stylekit-general-border-radius);pointer-events:none;color:var(--sn-stylekit-border-color);border-color:var(--sn-stylekit-border-color)}.sn-component .sk-notification.sk-base.no-bg,.sn-component .sk-input.sk-base.no-bg{background-color:transparent}.sn-component .sk-notification.sk-base.no-bg:before,.sn-component .sk-input.sk-base.no-bg:before{content:none}.sn-component .sk-notification.sk-base.featured,.sn-component .sk-input.sk-base.featured{border:none;padding:0.75rem 1.25rem;font-size:var(--sn-stylekit-font-size-h3)}.sn-component .sk-notification.sk-base.featured:before,.sn-component .sk-input.sk-base.featured:before{opacity:1.0}.sn-component .sk-notification.neutral,.sn-component .sk-input.neutral{color:var(--sn-stylekit-neutral-contrast-color);position:relative;background-color:var(--sn-stylekit-neutral-color);overflow:hidden;border-radius:var(--sn-stylekit-general-border-radius);border-color:var(--sn-stylekit-neutral-color)}.sn-component .sk-notification.neutral *,.sn-component .sk-input.neutral *{position:relative}.sn-component .sk-notification.neutral:before,.sn-component .sk-input.neutral:before{content:"";position:absolute;top:0;left:0;width:100%;height:100%;background-color:var(--sn-stylekit-neutral-color);opacity:1.0;border-radius:var(--sn-stylekit-general-border-radius)}.sn-component .sk-notification.neutral:after,.sn-component .sk-input.neutral:after{content:'';display:block;height:100%;position:absolute;top:0;left:0;width:100%;border-radius:var(--sn-stylekit-general-border-radius);pointer-events:none;box-shadow:inset 0 0 0 1px;color:var(--sn-stylekit-neutral-color)}.sn-component .sk-notification.neutral.no-bg,.sn-component .sk-input.neutral.no-bg{background-color:transparent}.sn-component .sk-notification.neutral.no-bg:before,.sn-component .sk-input.neutral.no-bg:before{content:none}.sn-component .sk-notification.neutral.featured,.sn-component .sk-input.neutral.featured{border:none;padding:0.75rem 1.25rem;font-size:var(--sn-stylekit-font-size-h3)}.sn-component .sk-notification.neutral.featured:before,.sn-component .sk-input.neutral.featured:before{opacity:1.0}.sn-component .sk-notification.info,.sn-component .sk-input.info{color:var(--sn-stylekit-info-contrast-color);position:relative;background-color:var(--sn-stylekit-info-color);overflow:hidden;border-radius:var(--sn-stylekit-general-border-radius);border-color:var(--sn-stylekit-info-color)}.sn-component .sk-notification.info *,.sn-component .sk-input.info *{position:relative}.sn-component .sk-notification.info:before,.sn-component .sk-input.info:before{content:"";position:absolute;top:0;left:0;width:100%;height:100%;background-color:var(--sn-stylekit-info-color);opacity:1.0;border-radius:var(--sn-stylekit-general-border-radius)}.sn-component .sk-notification.info:after,.sn-component .sk-input.info:after{content:'';display:block;height:100%;position:absolute;top:0;left:0;width:100%;border-radius:var(--sn-stylekit-general-border-radius);pointer-events:none;box-shadow:inset 0 0 0 1px;color:var(--sn-stylekit-info-color)}.sn-component .sk-notification.info.no-bg,.sn-component .sk-input.info.no-bg{background-color:transparent}.sn-component .sk-notification.info.no-bg:before,.sn-component .sk-input.info.no-bg:before{content:none}.sn-component .sk-notification.info.featured,.sn-component .sk-input.info.featured{border:none;padding:0.75rem 1.25rem;font-size:var(--sn-stylekit-font-size-h3)}.sn-component .sk-notification.info.featured:before,.sn-component .sk-input.info.featured:before{opacity:1.0}.sn-component .sk-notification.warning,.sn-component .sk-input.warning{color:var(--sn-stylekit-warning-contrast-color);position:relative;background-color:var(--sn-stylekit-warning-color);overflow:hidden;border-radius:var(--sn-stylekit-general-border-radius);border-color:var(--sn-stylekit-warning-color)}.sn-component .sk-notification.warning *,.sn-component .sk-input.warning *{position:relative}.sn-component .sk-notification.warning:before,.sn-component .sk-input.warning:before{content:"";position:absolute;top:0;left:0;width:100%;height:100%;background-color:var(--sn-stylekit-warning-color);opacity:1.0;border-radius:var(--sn-stylekit-general-border-radius)}.sn-component .sk-notification.warning:after,.sn-component .sk-input.warning:after{content:'';display:block;height:100%;position:absolute;top:0;left:0;width:100%;border-radius:var(--sn-stylekit-general-border-radius);pointer-events:none;box-shadow:inset 0 0 0 1px;color:var(--sn-stylekit-warning-color)}.sn-component .sk-notification.warning.no-bg,.sn-component .sk-input.warning.no-bg{background-color:transparent}.sn-component .sk-notification.warning.no-bg:before,.sn-component .sk-input.warning.no-bg:before{content:none}.sn-component .sk-notification.warning.featured,.sn-component .sk-input.warning.featured{border:none;padding:0.75rem 1.25rem;font-size:var(--sn-stylekit-font-size-h3)}.sn-component .sk-notification.warning.featured:before,.sn-component .sk-input.warning.featured:before{opacity:1.0}.sn-component .sk-notification.danger,.sn-component .sk-input.danger{color:var(--sn-stylekit-danger-contrast-color);position:relative;background-color:var(--sn-stylekit-danger-color);overflow:hidden;border-radius:var(--sn-stylekit-general-border-radius);border-color:var(--sn-stylekit-danger-color)}.sn-component .sk-notification.danger *,.sn-component .sk-input.danger *{position:relative}.sn-component .sk-notification.danger:before,.sn-component .sk-input.danger:before{content:"";position:absolute;top:0;left:0;width:100%;height:100%;background-color:var(--sn-stylekit-danger-color);opacity:1.0;border-radius:var(--sn-stylekit-general-border-radius)}.sn-component .sk-notification.danger:after,.sn-component .sk-input.danger:after{content:'';display:block;height:100%;position:absolute;top:0;left:0;width:100%;border-radius:var(--sn-stylekit-general-border-radius);pointer-events:none;box-shadow:inset 0 0 0 1px;color:var(--sn-stylekit-danger-color)}.sn-component .sk-notification.danger.no-bg,.sn-component .sk-input.danger.no-bg{background-color:transparent}.sn-component .sk-notification.danger.no-bg:before,.sn-component .sk-input.danger.no-bg:before{content:none}.sn-component .sk-notification.danger.featured,.sn-component .sk-input.danger.featured{border:none;padding:0.75rem 1.25rem;font-size:var(--sn-stylekit-font-size-h3)}.sn-component .sk-notification.danger.featured:before,.sn-component .sk-input.danger.featured:before{opacity:1.0}.sn-component .sk-notification.success,.sn-component .sk-input.success{color:var(--sn-stylekit-success-contrast-color);position:relative;background-color:var(--sn-stylekit-success-color);overflow:hidden;border-radius:var(--sn-stylekit-general-border-radius);border-color:var(--sn-stylekit-success-color)}.sn-component .sk-notification.success *,.sn-component .sk-input.success *{position:relative}.sn-component .sk-notification.success:before,.sn-component .sk-input.success:before{content:"";position:absolute;top:0;left:0;width:100%;height:100%;background-color:var(--sn-stylekit-success-color);opacity:1.0;border-radius:var(--sn-stylekit-general-border-radius)}.sn-component .sk-notification.success:after,.sn-component .sk-input.success:after{content:'';display:block;height:100%;position:absolute;top:0;left:0;width:100%;border-radius:var(--sn-stylekit-general-border-radius);pointer-events:none;box-shadow:inset 0 0 0 1px;color:var(--sn-stylekit-success-color)}.sn-component .sk-notification.success.no-bg,.sn-component .sk-input.success.no-bg{background-color:transparent}.sn-component .sk-notification.success.no-bg:before,.sn-component .sk-input.success.no-bg:before{content:none}.sn-component .sk-notification.success.featured,.sn-component .sk-input.success.featured{border:none;padding:0.75rem 1.25rem;font-size:var(--sn-stylekit-font-size-h3)}.sn-component .sk-notification.success.featured:before,.sn-component .sk-input.success.featured:before{opacity:1.0}.sn-component .sk-notification{padding:1.1rem 1rem;margin:1.4rem 0;text-align:left;cursor:default}.sn-component .sk-notification.one-line{padding:0rem 0.4rem}.sn-component .sk-notification.stretch{width:100%}.sn-component .sk-notification.dashed{border-style:dashed;border-width:2px}.sn-component .sk-notification.dashed:after{box-shadow:none}.sn-component .sk-notification .sk-notification-title{font-size:var(--sn-stylekit-font-size-h1);font-weight:bold;line-height:1.9rem}.sn-component .sk-notification .sk-notification-text{line-height:1.5rem;font-size:var(--sn-stylekit-font-size-p);text-align:left;font-weight:normal}.sn-component .sk-circle{border:1px solid;cursor:pointer;border-color:var(--sn-stylekit-contrast-foreground-color);background-color:var(--sn-stylekit-contrast-background-color);padding:0;border-radius:50% !important;flex-shrink:0}.sn-component .sk-circle:before{border-radius:50% !important}.sn-component .sk-circle:after{border-radius:50% !important}.sn-component .sk-circle.small{width:11px;height:11px}.sn-component .sk-spinner{border:1px solid var(--sn-stylekit-neutral-color);border-radius:50%;animation:rotate 0.8s infinite linear;border-right-color:transparent}.sn-component .sk-spinner.small{width:12px;height:12px}.sn-component .sk-spinner.info-contrast{border-color:var(--sn-stylekit-info-contrast-color);border-right-color:transparent}.sn-component .sk-spinner.info{border-color:var(--sn-stylekit-info-color);border-right-color:transparent}.sn-component .sk-spinner.warning{border-color:var(--sn-stylekit-warning-color);border-right-color:transparent}.sn-component .sk-spinner.danger{border-color:var(--sn-stylekit-danger-color);border-right-color:transparent}.sn-component .sk-spinner.success{border-color:var(--sn-stylekit-success-color);border-right-color:transparent}@keyframes rotate{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.sn-component .sk-app-bar{display:flex;width:100%;height:2rem;padding:0.0rem 0.8rem;background-color:var(--sn-stylekit-contrast-background-color);color:var(--sn-stylekit-contrast-foreground-color);justify-content:space-between;align-items:center;border:1px solid var(--sn-stylekit-contrast-border-color);user-select:none}.sn-component .sk-app-bar.no-edges{border-left:0;border-right:0}.sn-component .sk-app-bar.no-bottom-edge{border-bottom:0}.sn-component .sk-app-bar .left,.sn-component .sk-app-bar .right{display:flex;height:100%}.sn-component .sk-app-bar .sk-app-bar-item{flex-grow:1;cursor:pointer;display:flex;align-items:center;justify-content:center}.sn-component .sk-app-bar .sk-app-bar-item:not(:first-child){margin-left:1rem}.sn-component .sk-app-bar .sk-app-bar-item.border{border-left:1px solid var(--sn-stylekit-contrast-border-color)}.sn-component .sk-app-bar .sk-app-bar-item>.sk-app-bar-item-column{height:100%;display:flex;align-items:center}.sn-component .sk-app-bar .sk-app-bar-item>.sk-app-bar-item-column:not(:first-child){margin-left:0.5rem}.sn-component .sk-app-bar .sk-app-bar-item>.sk-app-bar-item-column.underline{border-bottom:2px solid var(--sn-stylekit-info-color)}.sn-component .sk-app-bar .sk-app-bar-item.no-pointer{cursor:default}.sn-component .sk-app-bar .sk-app-bar-item:hover>.sk-label:not(.subtle),.sn-component .sk-app-bar .sk-panel-section .sk-app-bar-item:hover>.sk-panel-section-subtitle:not(.subtle),.sn-component .sk-panel-section .sk-app-bar .sk-app-bar-item:hover>.sk-panel-section-subtitle:not(.subtle),.sn-component .sk-app-bar .sk-app-bar-item:hover>.sk-sublabel:not(.subtle),.sn-component .sk-app-bar .sk-app-bar-item:hover>.sk-app-bar-item-column>.sk-label:not(.subtle),.sn-component .sk-app-bar .sk-panel-section .sk-app-bar-item:hover>.sk-app-bar-item-column>.sk-panel-section-subtitle:not(.subtle),.sn-component .sk-panel-section .sk-app-bar .sk-app-bar-item:hover>.sk-app-bar-item-column>.sk-panel-section-subtitle:not(.subtle),.sn-component .sk-app-bar .sk-app-bar-item:hover>.sk-app-bar-item-column>.sk-sublabel:not(.subtle){color:var(--sn-stylekit-info-color)}.sn-component .sk-app-bar .sk-app-bar-item>.sk-label,.sn-component .sk-app-bar .sk-panel-section .sk-app-bar-item>.sk-panel-section-subtitle,.sn-component .sk-panel-section .sk-app-bar .sk-app-bar-item>.sk-panel-section-subtitle,.sn-component .sk-app-bar .sk-app-bar-item>.sk-app-bar-item-column>.sk-label,.sn-component .sk-app-bar .sk-panel-section .sk-app-bar-item>.sk-app-bar-item-column>.sk-panel-section-subtitle,.sn-component .sk-panel-section .sk-app-bar .sk-app-bar-item>.sk-app-bar-item-column>.sk-panel-section-subtitle{font-weight:bold;font-size:var(--sn-stylekit-font-size-h5);white-space:nowrap}.sn-component .sk-app-bar .sk-app-bar-item>.sk-sublabel,.sn-component .sk-app-bar .sk-app-bar-item>.sk-app-bar-item-column>.sk-sublabel{font-size:var(--sn-stylekit-font-size-h5);font-weight:normal;white-space:nowrap}.sn-component .sk-app-bar .sk-app-bar-item .subtle{font-weight:normal;opacity:0.6}.sn-component .sk-panel-table{display:flex;flex-wrap:wrap;padding-left:1px;padding-top:1px}.sn-component .sk-panel-table .sk-panel-table-item{flex:45%;flex-flow:wrap;border:1px solid var(--sn-stylekit-border-color);padding:1rem;margin-left:-1px;margin-top:-1px;display:flex;flex-direction:column;justify-content:space-between}.sn-component .sk-panel-table .sk-panel-table-item img{max-width:100%;margin-bottom:1rem}.sn-component .sk-panel-table .sk-panel-table-item .sk-panel-table-item-content{display:flex;flex-direction:row}.sn-component .sk-panel-table .sk-panel-table-item .sk-panel-table-item-column{align-items:center}.sn-component .sk-panel-table .sk-panel-table-item .sk-panel-table-item-column.stretch{width:100%}.sn-component .sk-panel-table .sk-panel-table-item .sk-panel-table-item-column:not(:first-child){padding-left:0.75rem}.sn-component .sk-panel-table .sk-panel-table-item .sk-panel-table-item-column.quarter{flex-basis:25%}.sn-component .sk-panel-table .sk-panel-table-item .sk-panel-table-item-column.three-quarters{flex-basis:75%}.sn-component .sk-panel-table .sk-panel-table-item .sk-panel-table-item-footer{margin-top:1.25rem}.sn-component .sk-panel-table .sk-panel-table-item.no-border{border:none}.sn-component .sk-modal{position:fixed;margin-left:auto;margin-right:auto;left:0;right:0;top:0;bottom:0;z-index:10000;width:100vw;height:100vh;background-color:transparent;color:var(--sn-stylekit-contrast-foreground-color);display:flex;align-items:center;justify-content:center}.sn-component .sk-modal .sn-component{height:100%}.sn-component .sk-modal .sn-component .sk-panel{height:100%}.sn-component .sk-modal.auto-height>.sk-modal-content{height:auto !important}.sn-component .sk-modal.large>.sk-modal-content{width:900px;height:600px}.sn-component .sk-modal.medium>.sk-modal-content{width:700px;height:500px}.sn-component .sk-modal.small>.sk-modal-content{width:700px;height:344px}.sn-component .sk-modal .sk-modal-background{position:absolute;z-index:-1;width:100%;height:100%;background-color:var(--sn-stylekit-contrast-background-color);opacity:0.7}.sn-component .sk-modal>.sk-modal-content{overflow-y:auto;width:auto;padding:0;padding-bottom:0;min-width:300px;-webkit-box-shadow:0px 2px 35px 0px rgba(0,0,0,0.19);-moz-box-shadow:0px 2px 35px 0px rgba(0,0,0,0.19);box-shadow:0px 2px 35px 0px rgba(0,0,0,0.19)}.sn-component.no-select{user-select:none}input,textarea,[contenteditable]{caret-color:var(--sn-stylekit-editor-foreground-color)}.windows-web,.windows-desktop,.linux-web,.linux-desktop{scrollbar-width:thin}.windows-web ::-webkit-scrollbar,.windows-desktop ::-webkit-scrollbar,.linux-web ::-webkit-scrollbar,.linux-desktop ::-webkit-scrollbar{width:17px;height:18px;border-left:0.5px solid var(--sn-stylekit-scrollbar-track-border-color-color)}.windows-web ::-webkit-scrollbar-thumb,.windows-desktop ::-webkit-scrollbar-thumb,.linux-web ::-webkit-scrollbar-thumb,.linux-desktop ::-webkit-scrollbar-thumb{border:4px solid rgba(0,0,0,0);background-clip:padding-box;-webkit-border-radius:10px;background-color:var(--sn-stylekit-scrollbar-thumb-color);-webkit-box-shadow:inset -1px -1px 0px rgba(0,0,0,0.05),inset 1px 1px 0px rgba(0,0,0,0.05)}.windows-web ::-webkit-scrollbar-button,.windows-desktop ::-webkit-scrollbar-button,.linux-web ::-webkit-scrollbar-button,.linux-desktop ::-webkit-scrollbar-button{width:0;height:0;display:none}.windows-web ::-webkit-scrollbar-corner,.windows-desktop ::-webkit-scrollbar-corner,.linux-web ::-webkit-scrollbar-corner,.linux-desktop ::-webkit-scrollbar-corner{background-color:transparent}body,html{font-family:sans-serif;height:100%;width:100%;margin:0;padding:0;overflow:hidden;overflow-y:hidden !important}#spreadsheet{border:none;width:100%;height:100%;color:var(--sn-stylekit-editor-foreground-color);background-color:var(--sn-stylekit-editor-background-color)}.k-spreadsheet-column-header,.k-spreadsheet-row-header,.k-spreadsheet-top-corner{color:var(--sn-stylekit-editor-foreground-color) !important;background-color:var(--sn-stylekit-contrast-background-color) !important}.k-spreadsheet .k-spreadsheet-action-bar{color:var(--sn-stylekit-editor-foreground-color) !important;background-color:var(--sn-stylekit-contrast-background-color) !important;border-color:var(--sn-stylekit-border-color) !important;border-top-width:1px !important}.k-spreadsheet .k-spreadsheet-formula-input{color:var(--sn-stylekit-editor-foreground-color) !important;background-color:var(--sn-stylekit-editor-background-color) !important}.k-spreadsheet .k-spreadsheet-name-editor .k-select{background-color:var(--sn-stylekit-contrast-background-color) !important}.k-block,.k-content,.k-dropdown .k-input,.k-popup,.k-toolbar,.k-widget{color:var(--sn-stylekit-editor-foreground-color) !important;background-color:var(--sn-stylekit-editor-background-color) !important}.k-spreadsheet-filter-menu .k-spreadsheet-value-treeview-wrapper{color:var(--sn-stylekit-editor-foreground-color) !important;background-color:var(--sn-stylekit-contrast-background-color) !important}.k-grid-header .k-header>.k-link,.k-header,.k-treemap-title{color:var(--sn-stylekit-editor-foreground-color) !important}.k-dropdown .k-input,.k-dropdown .k-state-focused .k-input,.k-menu .k-popup{color:var(--sn-stylekit-editor-foreground-color) !important}.k-panelbar>li.k-state-default>.k-link,.k-tabstrip-items .k-state-default .k-link{color:var(--sn-stylekit-editor-foreground-color) !important}.k-input,.k-multiselect-wrap,.k-textbox>input,input.k-textbox,input.k-textbox:hover,textarea.k-textbox,textarea.k-textbox:hover{color:var(--sn-stylekit-editor-foreground-color) !important}.k-spreadsheet-active-cell{background-color:var(--sn-stylekit-contrast-background-color) !important}.k-spreadsheet-active-cell.k-single{color:var(--sn-stylekit-editor-foreground-color) !important;background-color:var(--sn-stylekit-contrast-background-color) !important}.k-spreadsheet .k-spreadsheet-action-bar .k-spreadsheet-name-editor{border-color:var(--sn-stylekit-border-color) !important}.k-autocomplete .k-input,.k-autocomplete.k-state-focused .k-input,.k-dropdown-wrap .k-input,.k-dropdown-wrap.k-state-focused .k-input,.k-numeric-wrap.k-state-focused .k-input,.k-picker-wrap.k-state-focused .k-input,.k-textbox>input{border-color:var(--sn-stylekit-border-color) !important}.k-autocomplete,.k-block,.k-button-group .k-tool,.k-calendar th,.k-content,.k-dateinput.k-state-disabled>.k-textbox:hover,.k-dropdown-wrap,.k-dropzone-active,.k-editable-area,.k-editor-dialog .k-tabstrip-items,.k-filter-row>th,.k-footer-template td,.k-grid td,.k-grid td.k-state-selected,.k-grid-content-locked,.k-grid-footer,.k-grid-footer-locked,.k-grid-footer-wrap,.k-grid-header,.k-grid-header-locked,.k-grid-header-wrap,.k-group,.k-group-footer td,.k-grouping-header,.k-grouping-header .k-group-indicator,.k-header,.k-input,.k-maskedtextbox.k-state-disabled>.k-textbox:hover,.k-pager-refresh,.k-pager-wrap,.k-pager-wrap .k-link,.k-panel>.k-item>.k-link,.k-panelbar .k-content,.k-panelbar .k-panel,.k-panelbar>.k-item>.k-link,.k-popup.k-align .k-list .k-item:last-child,.k-separator,.k-slider-track,.k-splitbar,.k-state-default,.k-state-default .k-select,.k-state-disabled,.k-textbox,.k-textbox>input,.k-tiles,.k-toolbar,.k-tooltip,.k-treemap-tile,.k-upload-files,.k-widget{border-color:var(--sn-stylekit-border-color) !important}.k-input,.k-multiselect-wrap,.k-textbox>input,input.k-textbox,input.k-textbox:hover,textarea.k-textbox,textarea.k-textbox:hover{color:var(--sn-stylekit-editor-foreground-color) !important;background-color:var(--sn-stylekit-contrast-background-color) !important}.k-spreadsheet-pane .k-spreadsheet-haxis,.k-spreadsheet-pane .k-spreadsheet-vaxis{border-color:var(--sn-stylekit-border-color) !important}.k-spreadsheet-pane .k-spreadsheet-column-header,.k-spreadsheet-pane .k-spreadsheet-row-header{border-color:var(--sn-stylekit-border-color) !important}.k-spreadsheet-toolbar>.k-separator{border-color:var(--sn-stylekit-border-color) !important}.k-spreadsheet-filter-menu .k-details{border-color:var(--sn-stylekit-border-color) !important}.k-list-container{color:var(--sn-stylekit-editor-foreground-color) !important;background-color:var(--sn-stylekit-contrast-background-color) !important;border-color:var(--sn-stylekit-border-color) !important}.k-list-container .k-button{color:var(--sn-stylekit-editor-foreground-color) !important}.k-spreadsheet-popup .k-button{color:var(--sn-stylekit-editor-foreground-color) !important}.k-calendar .k-header,.k-header.k-scheduler-toolbar,.k-menu.k-header,.k-scheduler-footer>.k-header,.k-tabstrip .k-tabstrip-items .k-item,.k-tabstrip.k-header{background-color:var(--sn-stylekit-contrast-background-color) !important;border-color:transparent !important}.k-gantt-toolbar .k-state-default,.k-grid .k-grouping-header,.k-grid-header,.k-grid-header-wrap,.k-grouping-header .k-group-indicator,.k-header,.k-pager-wrap,.k-pager-wrap .k-link,.k-pager-wrap .k-textbox{border-color:var(--sn-stylekit-border-color) !important}.k-spreadsheet .k-spreadsheet-action-bar .k-spreadsheet-formula-bar::before{border-color:var(--sn-stylekit-border-color) !important}.k-active-filter,.k-state-active,.k-state-active:hover{border-color:var(--sn-stylekit-border-color) !important}.k-tabstrip .k-tabstrip-items .k-state-active{border-color:var(--sn-stylekit-info-color) !important}.k-syntax-paren-match{background-color:var(--sn-stylekit-info-color) !important}.k-autocomplete,.k-draghandle,.k-dropdown-wrap,.k-grid-header,.k-grouping-header,.k-header,.k-numeric-wrap,.k-pager-wrap,.k-panelbar .k-tabstrip-items .k-item,.k-picker-wrap,.k-progressbar,.k-state-highlight,.k-tabstrip-items .k-item,.k-textbox,.k-toolbar,.km-pane-wrapper>.km-pane>.km-view>.km-content{background-color:var(--sn-stylekit-contrast-background-color) !important;background-image:none}.k-button .k-icon,.k-button .k-image,.k-button .k-sprite{color:var(--sn-stylekit-info-color) !important}.k-tabstrip .k-tabstrip-items .k-state-active .k-link{color:var(--sn-stylekit-info-color) !important}div.k-window,div.k-window.k-state-focused{background-color:var(--sn-stylekit-contrast-background-color) !important;border-color:var(--sn-stylekit-border-color) !important}.k-content,.k-editable-area,.k-panel>li.k-item,.k-panelbar>li.k-item,.k-tiles{background-color:var(--sn-stylekit-contrast-background-color) !important}.k-action-window .k-action-buttons{background-color:var(--sn-stylekit-contrast-background-color) !important;border-color:var(--sn-stylekit-border-color) !important}.k-state-hover>.k-select{border-color:var(--sn-stylekit-info-color) !important}.k-icon,.k-tool-icon{color:var(--sn-stylekit-info-color) !important}.k-button{color:var(--sn-stylekit-info-color) !important;background-color:var(--sn-stylekit-contrast-background-color) !important;border:none !important;font-weight:bold}.k-button:hover{background-color:var(--sn-stylekit-background-color) !important}.k-list .k-item{border:none !important;cursor:pointer !important}.k-list .k-item .k-icon{color:var(--sn-stylekit-info-color) !important}.k-list .k-item.k-state-hovered{background-color:var(--sn-stylekit-background-color) !important}.k-list .k-item.k-state-selected{background-color:var(--sn-stylekit-info-color) !important;color:var(--sn-stylekit-info-contrast-color) !important}.k-list .k-item.k-state-selected .k-icon{color:var(--sn-stylekit-info-contrast-color) !important}.k-button.k-primary{color:var(--sn-stylekit-info-contrast-color) !important;border-color:var(--sn-stylekit-info-color) !important;background-color:var(--sn-stylekit-info-color) !important}.k-button.k-primary:hover:not(:disabled){filter:brightness(130%)}.k-tabstrip-items-wrapper{background-color:var(--sn-stylekit-contrast-background-color) !important}.k-spreadsheet-pane .k-spreadsheet-merged-cell{background-color:var(--sn-stylekit-background-color) !important} + "Helvetica Neue", var(--sn-stylekit-simplified-chinese-font), sans-serif}.sn-component{font-family:var(--sn-stylekit-sans-serif-font);-webkit-font-smoothing:antialiased;color:var(--sn-stylekit-foreground-color)}.sn-component .sk-panel{box-shadow:0px 2px 5px var(--sn-stylekit-shadow-color);background-color:var(--sn-stylekit-background-color);border:1px solid var(--sn-stylekit-border-color);border-radius:var(--sn-stylekit-general-border-radius);display:flex;flex-direction:column;overflow:auto;flex-grow:1}.sn-component .sk-panel a:hover{text-decoration:underline}.sn-component .sk-panel.static{box-shadow:none;border:none;border-radius:0}.sn-component .sk-panel .sk-panel-header{flex-shrink:0;display:flex;justify-content:space-between;padding:1.1rem 2rem;border-bottom:1px solid var(--sn-stylekit-contrast-border-color);background-color:var(--sn-stylekit-contrast-background-color);color:var(--sn-stylekit-contrast-foreground-color);align-items:center}.sn-component .sk-panel .sk-panel-header .sk-panel-header-title{font-size:var(--sn-stylekit-font-size-h1);font-weight:500}.sn-component .sk-panel .sk-panel-header .close-button{font-weight:bold}.sn-component .sk-panel .sk-footer,.sn-component .sk-panel .sk-panel-footer{padding:1rem 2rem;border-top:1px solid var(--sn-stylekit-border-color);box-sizing:border-box}.sn-component .sk-panel .sk-footer.extra-padding,.sn-component .sk-panel .sk-panel-footer.extra-padding{padding:2rem 2rem}.sn-component .sk-panel .sk-footer .left,.sn-component .sk-panel .sk-panel-footer .left{text-align:left;display:block}.sn-component .sk-panel .sk-footer .right,.sn-component .sk-panel .sk-panel-footer .right{text-align:right;display:block}.sn-component .sk-panel .sk-panel-content{padding:1.6rem 2rem;padding-bottom:0;flex-grow:1;overflow:scroll;height:100%;overflow-y:auto !important;overflow-x:auto !important}.sn-component .sk-panel .sk-panel-content .sk-p,.sn-component .sk-panel .sk-panel-content .sk-li{color:var(--sn-stylekit-paragraph-text-color);line-height:1.3}.sn-component .sk-panel-section{padding-bottom:1.6rem;display:flex;flex-direction:column}.sn-component .sk-panel-section.sk-panel-hero{text-align:center}.sn-component .sk-panel-section .sk-p:last-child{margin-bottom:0}.sn-component .sk-panel-section:not(:last-child){margin-bottom:1.5rem;border-bottom:1px solid var(--sn-stylekit-border-color)}.sn-component .sk-panel-section:not(:last-child).no-border{border-bottom:none}.sn-component .sk-panel-section:last-child{margin-bottom:0.5rem}.sn-component .sk-panel-section.no-bottom-pad{padding-bottom:0;margin-bottom:0}.sn-component .sk-panel-section .sk-panel-section-title{margin-bottom:0.5rem;font-weight:bold;font-size:var(--sn-stylekit-font-size-h3)}.sn-component .sk-panel-section .sk-panel-section-outer-title{border-bottom:1px solid var(--sn-stylekit-border-color);padding-bottom:0.9rem;margin-top:2.1rem;margin-bottom:15px;font-size:var(--sn-stylekit-font-size-h3)}.sn-component .sk-panel-section .sk-panel-section-subtitle{font-size:var(--sn-stylekit-font-size-h5);margin-bottom:2px}.sn-component .sk-panel-section .sk-panel-section-subtitle.subtle{font-weight:normal;opacity:0.6}.sn-component .sk-panel-section .text-content .sk-p{margin-bottom:1rem}.sn-component .sk-panel-section .text-content p:first-child{margin-top:0.3rem}.sn-component .sk-panel-row{display:flex;justify-content:space-between;align-items:center;padding-top:0.4rem}.sn-component .sk-panel-row.centered{justify-content:center}.sn-component .sk-panel-row.justify-right{justify-content:flex-end}.sn-component .sk-panel-row.justify-left{justify-content:flex-start}.sn-component .sk-panel-row.align-top{align-items:flex-start}.sn-component .sk-panel-row .sk-panel-column.stretch{width:100%}.sn-component .sk-panel-row.default-padding,.sn-component .sk-panel-row:not(:last-child){padding-bottom:0.4rem}.sn-component .sk-panel-row.condensed{padding-top:0.2rem;padding-bottom:0.2rem}.sn-component .sk-panel-row .sk-p{margin:0;padding:0}.sn-component .vertical-rule{background-color:var(--sn-stylekit-border-color);height:1.5rem;width:1px}.sn-component .sk-panel-form{width:100%}.sn-component .sk-panel-form.half{width:50%}.sn-component .sk-panel-form .form-submit{margin-top:0.15rem}.sn-component .right-aligned{justify-content:flex-end;text-align:right}.sn-component .sk-menu-panel{background-color:var(--sn-stylekit-background-color);border:1px solid var(--sn-stylekit-contrast-border-color);border-radius:var(--sn-stylekit-general-border-radius);overflow:scroll;user-select:none;overflow-y:auto !important;overflow-x:auto !important}.sn-component .sk-menu-panel .sk-menu-panel-header{padding:0.8rem 1rem;border-bottom:1px solid var(--sn-stylekit-contrast-border-color);background-color:var(--sn-stylekit-contrast-background-color);color:var(--sn-stylekit-contrast-foreground-color);display:flex;justify-content:space-between;align-items:center}.sn-component .sk-menu-panel .sk-menu-panel-header-title{font-weight:bold;font-size:var(--sn-stylekit-font-size-h4)}.sn-component .sk-menu-panel .sk-menu-panel-header-subtitle{margin-top:0.2rem;opacity:0.6}.sn-component .sk-menu-panel .sk-menu-panel-row{padding:1rem 1rem;cursor:pointer;display:flex;flex-direction:row;justify-content:space-between;border-bottom:1px solid var(--sn-stylekit-border-color)}.sn-component .sk-menu-panel .sk-menu-panel-row:hover{background-color:var(--sn-stylekit-contrast-background-color);color:var(--sn-stylekit-contrast-foreground-color);border-color:var(--sn-stylekit-contrast-border-color)}.sn-component .sk-menu-panel .sk-menu-panel-row .sk-menu-panel-column{display:flex;justify-content:center;flex-direction:column}.sn-component .sk-menu-panel .sk-menu-panel-row .sk-menu-panel-column:not(:first-child){padding-left:1.0rem;padding-right:0.15rem}.sn-component .sk-menu-panel .sk-menu-panel-row .sk-menu-panel-column.stretch{width:100%}.sn-component .sk-menu-panel .sk-menu-panel-row .sk-menu-panel-column .sk-menu-panel-subrows{margin-top:1rem}.sn-component .sk-menu-panel .sk-menu-panel-row .sk-menu-panel-column .sk-menu-panel-row,.sn-component .sk-menu-panel .sk-menu-panel-row .sk-menu-panel-column .sk-menu-panel-subrow{border:1px solid var(--sn-stylekit-contrast-border-color);margin-top:-1px}.sn-component .sk-menu-panel .sk-menu-panel-row .sk-menu-panel-column .sk-menu-panel-row:hover,.sn-component .sk-menu-panel .sk-menu-panel-row .sk-menu-panel-column .sk-menu-panel-subrow:hover{background-color:var(--sn-stylekit-background-color)}.sn-component .sk-menu-panel .sk-menu-panel-row .sk-menu-panel-column .left{display:flex}.sn-component .sk-menu-panel .sk-menu-panel-row .sk-button .sk-label,.sn-component .sk-menu-panel .sk-menu-panel-row .sk-box .sk-label,.sn-component .sk-menu-panel .sk-menu-panel-row .sk-button .sk-panel-section .sk-panel-section-subtitle,.sn-component .sk-panel-section .sk-menu-panel .sk-menu-panel-row .sk-button .sk-panel-section-subtitle,.sn-component .sk-menu-panel .sk-menu-panel-row .sk-box .sk-panel-section .sk-panel-section-subtitle,.sn-component .sk-panel-section .sk-menu-panel .sk-menu-panel-row .sk-box .sk-panel-section-subtitle{font-size:var(--sn-stylekit-font-size-h6);font-weight:normal}.sn-component .sk-menu-panel .sk-menu-panel-row .sk-label,.sn-component .sk-menu-panel .sk-menu-panel-row .sk-panel-section .sk-panel-section-subtitle,.sn-component .sk-panel-section .sk-menu-panel .sk-menu-panel-row .sk-panel-section-subtitle{font-size:var(--sn-stylekit-font-size-p);font-weight:bold}.sn-component .sk-menu-panel .sk-menu-panel-row .sk-sublabel{font-size:var(--sn-stylekit-font-size-h5);margin-top:0.2rem;opacity:0.6}.sn-component .red{color:var(--sn-stylekit-danger-color)}.sn-component .tinted{color:var(--sn-stylekit-info-color)}.sn-component .selectable{user-select:text !important;-ms-user-select:text !important;-moz-user-select:text !important;-webkit-user-select:text !important}.sn-component .sk-h1,.sn-component .sk-h2,.sn-component .sk-h3,.sn-component .sk-h4,.sn-component .sk-h5{margin:0;padding:0;font-weight:normal}.sn-component .sk-h1{font-weight:500;font-size:var(--sn-stylekit-font-size-h1);line-height:1.9rem}.sn-component .sk-h2{font-size:var(--sn-stylekit-font-size-h2);line-height:1.8rem}.sn-component .sk-h3{font-size:var(--sn-stylekit-font-size-h3);line-height:1.7rem}.sn-component .sk-h4{font-size:var(--sn-stylekit-font-size-p);line-height:1.4rem}.sn-component .sk-h5{font-size:var(--sn-stylekit-font-size-h5)}.sn-component .sk-bold{font-weight:bold}.sn-component .sk-font-small{font-size:var(--sn-stylekit-font-size-h5)}.sn-component .sk-font-normal{font-size:var(--sn-stylekit-font-size-p)}.sn-component .sk-font-large{font-size:var(--sn-stylekit-font-size-h3)}.sn-component a.sk-a{cursor:pointer;user-select:none}.sn-component a.sk-a.disabled{color:var(--sn-stylekit-neutral-color);opacity:0.6}.sn-component a.sk-a.boxed{border-radius:var(--sn-stylekit-general-border-radius);padding:0.3rem 0.4rem}.sn-component a.sk-a.boxed:hover{text-decoration:none}.sn-component a.sk-a.boxed.neutral{background-color:var(--sn-stylekit-neutral-color);color:var(--sn-stylekit-neutral-contrast-color)}.sn-component a.sk-a.boxed.info{background-color:var(--sn-stylekit-info-color);color:var(--sn-stylekit-info-contrast-color)}.sn-component a.sk-a.boxed.warning{background-color:var(--sn-stylekit-warning-color);color:var(--sn-stylekit-warning-contrast-color)}.sn-component a.sk-a.boxed.danger{background-color:var(--sn-stylekit-danger-color);color:var(--sn-stylekit-danger-contrast-color)}.sn-component a.sk-a.boxed.success{background-color:var(--sn-stylekit-success-color);color:var(--sn-stylekit-success-contrast-color)}.sn-component .wrap{word-wrap:break-word}.sn-component *.sk-base{color:var(--sn-stylekit-foreground-color)}.sn-component *.contrast{color:var(--sn-stylekit-contrast-foreground-color)}.sn-component *.neutral{color:var(--sn-stylekit-neutral-color)}.sn-component *.info{color:var(--sn-stylekit-info-color)}.sn-component *.info-contrast{color:var(--sn-stylekit-info-contrast-color)}.sn-component *.warning{color:var(--sn-stylekit-warning-color)}.sn-component *.danger{color:var(--sn-stylekit-danger-color)}.sn-component *.success{color:var(--sn-stylekit-success-color)}.sn-component *.info-i{color:var(--sn-stylekit-info-color) !important}.sn-component *.warning-i{color:var(--sn-stylekit-warning-color) !important}.sn-component *.danger-i{color:var(--sn-stylekit-danger-color) !important}.sn-component *.success-i{color:var(--sn-stylekit-success-color) !important}.sn-component *.clear{background-color:transparent;border:none}.sn-component .center-text{text-align:center !important;justify-content:center !important}.sn-component p.sk-p{margin:0.5rem 0}.sn-component input.sk-input{box-sizing:border-box;padding:0.7rem 0.8rem;margin:0.30rem 0;border:none;font-size:var(--sn-stylekit-font-size-h3);width:100%;outline:0;resize:none}.sn-component input.sk-input.clear{color:var(--sn-stylekit-foreground-color);background-color:transparent;border:none}.sn-component input.sk-input.no-border{border:none}.sn-component .sk-label,.sn-component .sk-panel-section .sk-panel-section-subtitle{font-weight:bold}.sn-component .sk-label.no-bold,.sn-component .sk-panel-section .no-bold.sk-panel-section-subtitle{font-weight:normal}.sn-component label.sk-label,.sn-component .sk-panel-section label.sk-panel-section-subtitle{margin:0.7rem 0;display:block}.sn-component label.sk-label input[type='checkbox'],.sn-component .sk-panel-section label.sk-panel-section-subtitle input[type='checkbox'],.sn-component input[type='radio']{width:auto;margin-right:0.45rem;vertical-align:middle}.sn-component .sk-horizontal-group>*,.sn-component .sk-input-group>*{display:inline-block;vertical-align:middle}.sn-component .sk-horizontal-group>*:not(:first-child),.sn-component .sk-input-group>*:not(:first-child){margin-left:0.9rem}.sn-component .sk-border-bottom{border-bottom:1px solid var(--sn-stylekit-border-color)}.sn-component .sk-checkbox-group{padding-top:0.5rem;padding-bottom:0.3rem}.sn-component ::placeholder{color:var(--sn-stylekit-input-placeholder-color)}.sn-component :-ms-input-placeholder{color:var(--sn-stylekit-input-placeholder-color)}.sn-component ::-ms-input-placeholder{color:var(--sn-stylekit-input-placeholder-color)}.sn-component .sk-button-group.stretch{display:flex;width:100%}.sn-component .sk-button-group.stretch .sk-button,.sn-component .sk-button-group.stretch .sk-box{display:block;flex-grow:1;text-align:center}.sn-component .sk-button-group .sk-button,.sn-component .sk-button-group .sk-box{display:inline-block;vertical-align:middle}.sn-component .sk-button-group .sk-button:not(:last-child),.sn-component .sk-button-group .sk-box:not(:last-child){margin-right:5px}.sn-component .sk-button-group .sk-button:not(:last-child).featured,.sn-component .sk-button-group .sk-box:not(:last-child).featured{margin-right:8px}.sn-component .sk-segmented-buttons{display:flex;flex-direction:row}.sn-component .sk-segmented-buttons .sk-button,.sn-component .sk-segmented-buttons .sk-box{border-radius:0;white-space:nowrap;margin:0;margin-left:0 !important;margin-right:0 !important}.sn-component .sk-segmented-buttons .sk-button:not(:last-child),.sn-component .sk-segmented-buttons .sk-box:not(:last-child){border-right:none;border-radius:0}.sn-component .sk-segmented-buttons .sk-button:first-child,.sn-component .sk-segmented-buttons .sk-box:first-child{border-top-left-radius:var(--sn-stylekit-general-border-radius);border-bottom-left-radius:var(--sn-stylekit-general-border-radius);border-right:none;border-top-right-radius:0;border-bottom-right-radius:0}.sn-component .sk-segmented-buttons .sk-button:last-child,.sn-component .sk-segmented-buttons .sk-box:last-child{border-top-right-radius:var(--sn-stylekit-general-border-radius);border-bottom-right-radius:var(--sn-stylekit-general-border-radius);border-left:none;border-top-left-radius:0;border-bottom-left-radius:0}.sn-component .sk-box-group .sk-box{display:inline-block}.sn-component .sk-box-group .sk-box:not(:last-child){margin-right:5px}.sn-component .sk-a.button{text-decoration:none}.sn-component .sk-button,.sn-component .sk-box{display:table;padding:0.5rem 0.7rem;font-size:var(--sn-stylekit-font-size-h5);cursor:pointer;text-align:center;user-select:none}.sn-component .sk-button.no-hover-border:after,.sn-component .no-hover-border.sk-box:after{color:transparent !important}.sn-component .sk-button.wide,.sn-component .wide.sk-box{padding:0.3rem 1.7rem}.sn-component .sk-button>.sk-label,.sn-component .sk-box>.sk-label,.sn-component .sk-panel-section .sk-button>.sk-panel-section-subtitle,.sn-component .sk-panel-section .sk-box>.sk-panel-section-subtitle{font-weight:bold;display:block;text-align:center}.sn-component .sk-button.big,.sn-component .big.sk-box{font-size:var(--sn-stylekit-font-size-h3);padding:0.7rem 2.5rem}.sn-component .sk-box{padding:2.5rem 1.5rem}.sn-component .sk-button.sk-base,.sn-component .sk-base.sk-box,.sn-component .sk-box.sk-base,.sn-component .sk-circle.sk-base{color:var(--sn-stylekit-foreground-color);position:relative;background-color:var(--sn-stylekit-background-color);overflow:hidden;border-radius:var(--sn-stylekit-general-border-radius);border-color:var(--sn-stylekit-background-color)}.sn-component .sk-button.sk-base *,.sn-component .sk-base.sk-box *,.sn-component .sk-box.sk-base *,.sn-component .sk-circle.sk-base *{position:relative}.sn-component .sk-button.sk-base:before,.sn-component .sk-base.sk-box:before,.sn-component .sk-box.sk-base:before,.sn-component .sk-circle.sk-base:before{content:"";position:absolute;top:0;left:0;width:100%;height:100%;background-color:var(--sn-stylekit-background-color);opacity:1.0;border-radius:var(--sn-stylekit-general-border-radius)}.sn-component .sk-button.sk-base:after,.sn-component .sk-base.sk-box:after,.sn-component .sk-box.sk-base:after,.sn-component .sk-circle.sk-base:after{content:'';display:block;height:100%;position:absolute;top:0;left:0;width:100%;border-radius:var(--sn-stylekit-general-border-radius);pointer-events:none;box-shadow:inset 0 0 0 1px;color:var(--sn-stylekit-background-color)}.sn-component .sk-button.sk-base:hover:before,.sn-component .sk-base.sk-box:hover:before,.sn-component .sk-box.sk-base:hover:before,.sn-component .sk-circle.sk-base:hover:before{filter:brightness(130%)}.sn-component .sk-button.sk-base.no-bg,.sn-component .sk-base.no-bg.sk-box,.sn-component .sk-box.sk-base.no-bg,.sn-component .sk-circle.sk-base.no-bg{background-color:transparent}.sn-component .sk-button.sk-base.no-bg:before,.sn-component .sk-base.no-bg.sk-box:before,.sn-component .sk-box.sk-base.no-bg:before,.sn-component .sk-circle.sk-base.no-bg:before{content:none}.sn-component .sk-button.sk-base.featured,.sn-component .sk-base.featured.sk-box,.sn-component .sk-box.sk-base.featured,.sn-component .sk-circle.sk-base.featured{border:none;padding:0.75rem 1.25rem;font-size:var(--sn-stylekit-font-size-h3)}.sn-component .sk-button.sk-base.featured:before,.sn-component .sk-base.featured.sk-box:before,.sn-component .sk-box.sk-base.featured:before,.sn-component .sk-circle.sk-base.featured:before{opacity:1.0}.sn-component .sk-button.contrast,.sn-component .contrast.sk-box,.sn-component .sk-box.contrast,.sn-component .sk-circle.contrast{color:var(--sn-stylekit-contrast-foreground-color);position:relative;background-color:var(--sn-stylekit-contrast-background-color);overflow:hidden;border-radius:var(--sn-stylekit-general-border-radius);border-color:var(--sn-stylekit-contrast-background-color)}.sn-component .sk-button.contrast *,.sn-component .contrast.sk-box *,.sn-component .sk-box.contrast *,.sn-component .sk-circle.contrast *{position:relative}.sn-component .sk-button.contrast:before,.sn-component .contrast.sk-box:before,.sn-component .sk-box.contrast:before,.sn-component .sk-circle.contrast:before{content:"";position:absolute;top:0;left:0;width:100%;height:100%;background-color:var(--sn-stylekit-contrast-background-color);opacity:1.0;border-radius:var(--sn-stylekit-general-border-radius)}.sn-component .sk-button.contrast:after,.sn-component .contrast.sk-box:after,.sn-component .sk-box.contrast:after,.sn-component .sk-circle.contrast:after{content:'';display:block;height:100%;position:absolute;top:0;left:0;width:100%;border-radius:var(--sn-stylekit-general-border-radius);pointer-events:none;box-shadow:inset 0 0 0 1px;color:var(--sn-stylekit-contrast-background-color)}.sn-component .sk-button.contrast:hover:before,.sn-component .contrast.sk-box:hover:before,.sn-component .sk-box.contrast:hover:before,.sn-component .sk-circle.contrast:hover:before{filter:brightness(130%)}.sn-component .sk-button.contrast.no-bg,.sn-component .contrast.no-bg.sk-box,.sn-component .sk-box.contrast.no-bg,.sn-component .sk-circle.contrast.no-bg{background-color:transparent}.sn-component .sk-button.contrast.no-bg:before,.sn-component .contrast.no-bg.sk-box:before,.sn-component .sk-box.contrast.no-bg:before,.sn-component .sk-circle.contrast.no-bg:before{content:none}.sn-component .sk-button.contrast.featured,.sn-component .contrast.featured.sk-box,.sn-component .sk-box.contrast.featured,.sn-component .sk-circle.contrast.featured{border:none;padding:0.75rem 1.25rem;font-size:var(--sn-stylekit-font-size-h3)}.sn-component .sk-button.contrast.featured:before,.sn-component .contrast.featured.sk-box:before,.sn-component .sk-box.contrast.featured:before,.sn-component .sk-circle.contrast.featured:before{opacity:1.0}.sn-component .sk-button.sk-secondary,.sn-component .sk-secondary.sk-box,.sn-component .sk-box.sk-secondary,.sn-component .sk-circle.sk-secondary{color:var(--sn-stylekit-secondary-foreground-color);position:relative;background-color:var(--sn-stylekit-secondary-background-color);overflow:hidden;border-radius:var(--sn-stylekit-general-border-radius);border-color:var(--sn-stylekit-secondary-background-color)}.sn-component .sk-button.sk-secondary *,.sn-component .sk-secondary.sk-box *,.sn-component .sk-box.sk-secondary *,.sn-component .sk-circle.sk-secondary *{position:relative}.sn-component .sk-button.sk-secondary:before,.sn-component .sk-secondary.sk-box:before,.sn-component .sk-box.sk-secondary:before,.sn-component .sk-circle.sk-secondary:before{content:"";position:absolute;top:0;left:0;width:100%;height:100%;background-color:var(--sn-stylekit-secondary-background-color);opacity:1.0;border-radius:var(--sn-stylekit-general-border-radius)}.sn-component .sk-button.sk-secondary:after,.sn-component .sk-secondary.sk-box:after,.sn-component .sk-box.sk-secondary:after,.sn-component .sk-circle.sk-secondary:after{content:'';display:block;height:100%;position:absolute;top:0;left:0;width:100%;border-radius:var(--sn-stylekit-general-border-radius);pointer-events:none;box-shadow:inset 0 0 0 1px;color:var(--sn-stylekit-secondary-background-color)}.sn-component .sk-button.sk-secondary:hover:before,.sn-component .sk-secondary.sk-box:hover:before,.sn-component .sk-box.sk-secondary:hover:before,.sn-component .sk-circle.sk-secondary:hover:before{filter:brightness(130%)}.sn-component .sk-button.sk-secondary.no-bg,.sn-component .sk-secondary.no-bg.sk-box,.sn-component .sk-box.sk-secondary.no-bg,.sn-component .sk-circle.sk-secondary.no-bg{background-color:transparent}.sn-component .sk-button.sk-secondary.no-bg:before,.sn-component .sk-secondary.no-bg.sk-box:before,.sn-component .sk-box.sk-secondary.no-bg:before,.sn-component .sk-circle.sk-secondary.no-bg:before{content:none}.sn-component .sk-button.sk-secondary.featured,.sn-component .sk-secondary.featured.sk-box,.sn-component .sk-box.sk-secondary.featured,.sn-component .sk-circle.sk-secondary.featured{border:none;padding:0.75rem 1.25rem;font-size:var(--sn-stylekit-font-size-h3)}.sn-component .sk-button.sk-secondary.featured:before,.sn-component .sk-secondary.featured.sk-box:before,.sn-component .sk-box.sk-secondary.featured:before,.sn-component .sk-circle.sk-secondary.featured:before{opacity:1.0}.sn-component .sk-button.sk-secondary-contrast,.sn-component .sk-secondary-contrast.sk-box,.sn-component .sk-box.sk-secondary-contrast,.sn-component .sk-circle.sk-secondary-contrast{color:var(--sn-stylekit-secondary-contrast-foreground-color);position:relative;background-color:var(--sn-stylekit-secondary-contrast-background-color);overflow:hidden;border-radius:var(--sn-stylekit-general-border-radius);border-color:var(--sn-stylekit-secondary-contrast-background-color)}.sn-component .sk-button.sk-secondary-contrast *,.sn-component .sk-secondary-contrast.sk-box *,.sn-component .sk-box.sk-secondary-contrast *,.sn-component .sk-circle.sk-secondary-contrast *{position:relative}.sn-component .sk-button.sk-secondary-contrast:before,.sn-component .sk-secondary-contrast.sk-box:before,.sn-component .sk-box.sk-secondary-contrast:before,.sn-component .sk-circle.sk-secondary-contrast:before{content:"";position:absolute;top:0;left:0;width:100%;height:100%;background-color:var(--sn-stylekit-secondary-contrast-background-color);opacity:1.0;border-radius:var(--sn-stylekit-general-border-radius)}.sn-component .sk-button.sk-secondary-contrast:after,.sn-component .sk-secondary-contrast.sk-box:after,.sn-component .sk-box.sk-secondary-contrast:after,.sn-component .sk-circle.sk-secondary-contrast:after{content:'';display:block;height:100%;position:absolute;top:0;left:0;width:100%;border-radius:var(--sn-stylekit-general-border-radius);pointer-events:none;box-shadow:inset 0 0 0 1px;color:var(--sn-stylekit-secondary-contrast-background-color)}.sn-component .sk-button.sk-secondary-contrast:hover:before,.sn-component .sk-secondary-contrast.sk-box:hover:before,.sn-component .sk-box.sk-secondary-contrast:hover:before,.sn-component .sk-circle.sk-secondary-contrast:hover:before{filter:brightness(130%)}.sn-component .sk-button.sk-secondary-contrast.no-bg,.sn-component .sk-secondary-contrast.no-bg.sk-box,.sn-component .sk-box.sk-secondary-contrast.no-bg,.sn-component .sk-circle.sk-secondary-contrast.no-bg{background-color:transparent}.sn-component .sk-button.sk-secondary-contrast.no-bg:before,.sn-component .sk-secondary-contrast.no-bg.sk-box:before,.sn-component .sk-box.sk-secondary-contrast.no-bg:before,.sn-component .sk-circle.sk-secondary-contrast.no-bg:before{content:none}.sn-component .sk-button.sk-secondary-contrast.featured,.sn-component .sk-secondary-contrast.featured.sk-box,.sn-component .sk-box.sk-secondary-contrast.featured,.sn-component .sk-circle.sk-secondary-contrast.featured{border:none;padding:0.75rem 1.25rem;font-size:var(--sn-stylekit-font-size-h3)}.sn-component .sk-button.sk-secondary-contrast.featured:before,.sn-component .sk-secondary-contrast.featured.sk-box:before,.sn-component .sk-box.sk-secondary-contrast.featured:before,.sn-component .sk-circle.sk-secondary-contrast.featured:before{opacity:1.0}.sn-component .sk-button.neutral,.sn-component .neutral.sk-box,.sn-component .sk-box.neutral,.sn-component .sk-circle.neutral{color:var(--sn-stylekit-neutral-contrast-color);position:relative;background-color:var(--sn-stylekit-neutral-color);overflow:hidden;border-radius:var(--sn-stylekit-general-border-radius);border-color:var(--sn-stylekit-neutral-color)}.sn-component .sk-button.neutral *,.sn-component .neutral.sk-box *,.sn-component .sk-box.neutral *,.sn-component .sk-circle.neutral *{position:relative}.sn-component .sk-button.neutral:before,.sn-component .neutral.sk-box:before,.sn-component .sk-box.neutral:before,.sn-component .sk-circle.neutral:before{content:"";position:absolute;top:0;left:0;width:100%;height:100%;background-color:var(--sn-stylekit-neutral-color);opacity:1.0;border-radius:var(--sn-stylekit-general-border-radius)}.sn-component .sk-button.neutral:after,.sn-component .neutral.sk-box:after,.sn-component .sk-box.neutral:after,.sn-component .sk-circle.neutral:after{content:'';display:block;height:100%;position:absolute;top:0;left:0;width:100%;border-radius:var(--sn-stylekit-general-border-radius);pointer-events:none;box-shadow:inset 0 0 0 1px;color:var(--sn-stylekit-neutral-color)}.sn-component .sk-button.neutral:hover:before,.sn-component .neutral.sk-box:hover:before,.sn-component .sk-box.neutral:hover:before,.sn-component .sk-circle.neutral:hover:before{filter:brightness(130%)}.sn-component .sk-button.neutral.no-bg,.sn-component .neutral.no-bg.sk-box,.sn-component .sk-box.neutral.no-bg,.sn-component .sk-circle.neutral.no-bg{background-color:transparent}.sn-component .sk-button.neutral.no-bg:before,.sn-component .neutral.no-bg.sk-box:before,.sn-component .sk-box.neutral.no-bg:before,.sn-component .sk-circle.neutral.no-bg:before{content:none}.sn-component .sk-button.neutral.featured,.sn-component .neutral.featured.sk-box,.sn-component .sk-box.neutral.featured,.sn-component .sk-circle.neutral.featured{border:none;padding:0.75rem 1.25rem;font-size:var(--sn-stylekit-font-size-h3)}.sn-component .sk-button.neutral.featured:before,.sn-component .neutral.featured.sk-box:before,.sn-component .sk-box.neutral.featured:before,.sn-component .sk-circle.neutral.featured:before{opacity:1.0}.sn-component .sk-button.info,.sn-component .info.sk-box,.sn-component .sk-box.info,.sn-component .sk-circle.info{color:var(--sn-stylekit-info-contrast-color);position:relative;background-color:var(--sn-stylekit-info-color);overflow:hidden;border-radius:var(--sn-stylekit-general-border-radius);border-color:var(--sn-stylekit-info-color)}.sn-component .sk-button.info *,.sn-component .info.sk-box *,.sn-component .sk-box.info *,.sn-component .sk-circle.info *{position:relative}.sn-component .sk-button.info:before,.sn-component .info.sk-box:before,.sn-component .sk-box.info:before,.sn-component .sk-circle.info:before{content:"";position:absolute;top:0;left:0;width:100%;height:100%;background-color:var(--sn-stylekit-info-color);opacity:1.0;border-radius:var(--sn-stylekit-general-border-radius)}.sn-component .sk-button.info:after,.sn-component .info.sk-box:after,.sn-component .sk-box.info:after,.sn-component .sk-circle.info:after{content:'';display:block;height:100%;position:absolute;top:0;left:0;width:100%;border-radius:var(--sn-stylekit-general-border-radius);pointer-events:none;box-shadow:inset 0 0 0 1px;color:var(--sn-stylekit-info-color)}.sn-component .sk-button.info:hover:before,.sn-component .info.sk-box:hover:before,.sn-component .sk-box.info:hover:before,.sn-component .sk-circle.info:hover:before{filter:brightness(130%)}.sn-component .sk-button.info.no-bg,.sn-component .info.no-bg.sk-box,.sn-component .sk-box.info.no-bg,.sn-component .sk-circle.info.no-bg{background-color:transparent}.sn-component .sk-button.info.no-bg:before,.sn-component .info.no-bg.sk-box:before,.sn-component .sk-box.info.no-bg:before,.sn-component .sk-circle.info.no-bg:before{content:none}.sn-component .sk-button.info.featured,.sn-component .info.featured.sk-box,.sn-component .sk-box.info.featured,.sn-component .sk-circle.info.featured{border:none;padding:0.75rem 1.25rem;font-size:var(--sn-stylekit-font-size-h3)}.sn-component .sk-button.info.featured:before,.sn-component .info.featured.sk-box:before,.sn-component .sk-box.info.featured:before,.sn-component .sk-circle.info.featured:before{opacity:1.0}.sn-component .sk-button.warning,.sn-component .warning.sk-box,.sn-component .sk-box.warning,.sn-component .sk-circle.warning{color:var(--sn-stylekit-warning-contrast-color);position:relative;background-color:var(--sn-stylekit-warning-color);overflow:hidden;border-radius:var(--sn-stylekit-general-border-radius);border-color:var(--sn-stylekit-warning-color)}.sn-component .sk-button.warning *,.sn-component .warning.sk-box *,.sn-component .sk-box.warning *,.sn-component .sk-circle.warning *{position:relative}.sn-component .sk-button.warning:before,.sn-component .warning.sk-box:before,.sn-component .sk-box.warning:before,.sn-component .sk-circle.warning:before{content:"";position:absolute;top:0;left:0;width:100%;height:100%;background-color:var(--sn-stylekit-warning-color);opacity:1.0;border-radius:var(--sn-stylekit-general-border-radius)}.sn-component .sk-button.warning:after,.sn-component .warning.sk-box:after,.sn-component .sk-box.warning:after,.sn-component .sk-circle.warning:after{content:'';display:block;height:100%;position:absolute;top:0;left:0;width:100%;border-radius:var(--sn-stylekit-general-border-radius);pointer-events:none;box-shadow:inset 0 0 0 1px;color:var(--sn-stylekit-warning-color)}.sn-component .sk-button.warning:hover:before,.sn-component .warning.sk-box:hover:before,.sn-component .sk-box.warning:hover:before,.sn-component .sk-circle.warning:hover:before{filter:brightness(130%)}.sn-component .sk-button.warning.no-bg,.sn-component .warning.no-bg.sk-box,.sn-component .sk-box.warning.no-bg,.sn-component .sk-circle.warning.no-bg{background-color:transparent}.sn-component .sk-button.warning.no-bg:before,.sn-component .warning.no-bg.sk-box:before,.sn-component .sk-box.warning.no-bg:before,.sn-component .sk-circle.warning.no-bg:before{content:none}.sn-component .sk-button.warning.featured,.sn-component .warning.featured.sk-box,.sn-component .sk-box.warning.featured,.sn-component .sk-circle.warning.featured{border:none;padding:0.75rem 1.25rem;font-size:var(--sn-stylekit-font-size-h3)}.sn-component .sk-button.warning.featured:before,.sn-component .warning.featured.sk-box:before,.sn-component .sk-box.warning.featured:before,.sn-component .sk-circle.warning.featured:before{opacity:1.0}.sn-component .sk-button.danger,.sn-component .danger.sk-box,.sn-component .sk-box.danger,.sn-component .sk-circle.danger{color:var(--sn-stylekit-danger-contrast-color);position:relative;background-color:var(--sn-stylekit-danger-color);overflow:hidden;border-radius:var(--sn-stylekit-general-border-radius);border-color:var(--sn-stylekit-danger-color)}.sn-component .sk-button.danger *,.sn-component .danger.sk-box *,.sn-component .sk-box.danger *,.sn-component .sk-circle.danger *{position:relative}.sn-component .sk-button.danger:before,.sn-component .danger.sk-box:before,.sn-component .sk-box.danger:before,.sn-component .sk-circle.danger:before{content:"";position:absolute;top:0;left:0;width:100%;height:100%;background-color:var(--sn-stylekit-danger-color);opacity:1.0;border-radius:var(--sn-stylekit-general-border-radius)}.sn-component .sk-button.danger:after,.sn-component .danger.sk-box:after,.sn-component .sk-box.danger:after,.sn-component .sk-circle.danger:after{content:'';display:block;height:100%;position:absolute;top:0;left:0;width:100%;border-radius:var(--sn-stylekit-general-border-radius);pointer-events:none;box-shadow:inset 0 0 0 1px;color:var(--sn-stylekit-danger-color)}.sn-component .sk-button.danger:hover:before,.sn-component .danger.sk-box:hover:before,.sn-component .sk-box.danger:hover:before,.sn-component .sk-circle.danger:hover:before{filter:brightness(130%)}.sn-component .sk-button.danger.no-bg,.sn-component .danger.no-bg.sk-box,.sn-component .sk-box.danger.no-bg,.sn-component .sk-circle.danger.no-bg{background-color:transparent}.sn-component .sk-button.danger.no-bg:before,.sn-component .danger.no-bg.sk-box:before,.sn-component .sk-box.danger.no-bg:before,.sn-component .sk-circle.danger.no-bg:before{content:none}.sn-component .sk-button.danger.featured,.sn-component .danger.featured.sk-box,.sn-component .sk-box.danger.featured,.sn-component .sk-circle.danger.featured{border:none;padding:0.75rem 1.25rem;font-size:var(--sn-stylekit-font-size-h3)}.sn-component .sk-button.danger.featured:before,.sn-component .danger.featured.sk-box:before,.sn-component .sk-box.danger.featured:before,.sn-component .sk-circle.danger.featured:before{opacity:1.0}.sn-component .sk-button.success,.sn-component .success.sk-box,.sn-component .sk-box.success,.sn-component .sk-circle.success{color:var(--sn-stylekit-success-contrast-color);position:relative;background-color:var(--sn-stylekit-success-color);overflow:hidden;border-radius:var(--sn-stylekit-general-border-radius);border-color:var(--sn-stylekit-success-color)}.sn-component .sk-button.success *,.sn-component .success.sk-box *,.sn-component .sk-box.success *,.sn-component .sk-circle.success *{position:relative}.sn-component .sk-button.success:before,.sn-component .success.sk-box:before,.sn-component .sk-box.success:before,.sn-component .sk-circle.success:before{content:"";position:absolute;top:0;left:0;width:100%;height:100%;background-color:var(--sn-stylekit-success-color);opacity:1.0;border-radius:var(--sn-stylekit-general-border-radius)}.sn-component .sk-button.success:after,.sn-component .success.sk-box:after,.sn-component .sk-box.success:after,.sn-component .sk-circle.success:after{content:'';display:block;height:100%;position:absolute;top:0;left:0;width:100%;border-radius:var(--sn-stylekit-general-border-radius);pointer-events:none;box-shadow:inset 0 0 0 1px;color:var(--sn-stylekit-success-color)}.sn-component .sk-button.success:hover:before,.sn-component .success.sk-box:hover:before,.sn-component .sk-box.success:hover:before,.sn-component .sk-circle.success:hover:before{filter:brightness(130%)}.sn-component .sk-button.success.no-bg,.sn-component .success.no-bg.sk-box,.sn-component .sk-box.success.no-bg,.sn-component .sk-circle.success.no-bg{background-color:transparent}.sn-component .sk-button.success.no-bg:before,.sn-component .success.no-bg.sk-box:before,.sn-component .sk-box.success.no-bg:before,.sn-component .sk-circle.success.no-bg:before{content:none}.sn-component .sk-button.success.featured,.sn-component .success.featured.sk-box,.sn-component .sk-box.success.featured,.sn-component .sk-circle.success.featured{border:none;padding:0.75rem 1.25rem;font-size:var(--sn-stylekit-font-size-h3)}.sn-component .sk-button.success.featured:before,.sn-component .success.featured.sk-box:before,.sn-component .sk-box.success.featured:before,.sn-component .sk-circle.success.featured:before{opacity:1.0}.sn-component .sk-notification.contrast,.sn-component .sk-input.contrast{color:var(--sn-stylekit-contrast-foreground-color);position:relative;background-color:var(--sn-stylekit-contrast-background-color);overflow:hidden;border-radius:var(--sn-stylekit-general-border-radius);border-color:var(--sn-stylekit-contrast-border-color);border:1px solid var(--sn-stylekit-contrast-border-color)}.sn-component .sk-notification.contrast *,.sn-component .sk-input.contrast *{position:relative}.sn-component .sk-notification.contrast:before,.sn-component .sk-input.contrast:before{content:"";position:absolute;top:0;left:0;width:100%;height:100%;background-color:var(--sn-stylekit-contrast-background-color);opacity:1.0;border-radius:var(--sn-stylekit-general-border-radius)}.sn-component .sk-notification.contrast:after,.sn-component .sk-input.contrast:after{content:'';display:block;height:100%;position:absolute;top:0;left:0;width:100%;border-radius:var(--sn-stylekit-general-border-radius);pointer-events:none;color:var(--sn-stylekit-contrast-border-color);border-color:var(--sn-stylekit-contrast-border-color)}.sn-component .sk-notification.contrast.no-bg,.sn-component .sk-input.contrast.no-bg{background-color:transparent}.sn-component .sk-notification.contrast.no-bg:before,.sn-component .sk-input.contrast.no-bg:before{content:none}.sn-component .sk-notification.contrast.featured,.sn-component .sk-input.contrast.featured{border:none;padding:0.75rem 1.25rem;font-size:var(--sn-stylekit-font-size-h3)}.sn-component .sk-notification.contrast.featured:before,.sn-component .sk-input.contrast.featured:before{opacity:1.0}.sn-component .sk-notification.sk-secondary,.sn-component .sk-input.sk-secondary{color:var(--sn-stylekit-secondary-foreground-color);position:relative;background-color:var(--sn-stylekit-secondary-background-color);overflow:hidden;border-radius:var(--sn-stylekit-general-border-radius);border-color:var(--sn-stylekit-secondary-border-color);border:1px solid var(--sn-stylekit-secondary-border-color)}.sn-component .sk-notification.sk-secondary *,.sn-component .sk-input.sk-secondary *{position:relative}.sn-component .sk-notification.sk-secondary:before,.sn-component .sk-input.sk-secondary:before{content:"";position:absolute;top:0;left:0;width:100%;height:100%;background-color:var(--sn-stylekit-secondary-background-color);opacity:1.0;border-radius:var(--sn-stylekit-general-border-radius)}.sn-component .sk-notification.sk-secondary:after,.sn-component .sk-input.sk-secondary:after{content:'';display:block;height:100%;position:absolute;top:0;left:0;width:100%;border-radius:var(--sn-stylekit-general-border-radius);pointer-events:none;color:var(--sn-stylekit-secondary-border-color);border-color:var(--sn-stylekit-secondary-border-color)}.sn-component .sk-notification.sk-secondary.no-bg,.sn-component .sk-input.sk-secondary.no-bg{background-color:transparent}.sn-component .sk-notification.sk-secondary.no-bg:before,.sn-component .sk-input.sk-secondary.no-bg:before{content:none}.sn-component .sk-notification.sk-secondary.featured,.sn-component .sk-input.sk-secondary.featured{border:none;padding:0.75rem 1.25rem;font-size:var(--sn-stylekit-font-size-h3)}.sn-component .sk-notification.sk-secondary.featured:before,.sn-component .sk-input.sk-secondary.featured:before{opacity:1.0}.sn-component .sk-notification.sk-secondary-contrast,.sn-component .sk-input.sk-secondary-contrast{color:var(--sn-stylekit-secondary-contrast-foreground-color);position:relative;background-color:var(--sn-stylekit-secondary-contrast-background-color);overflow:hidden;border-radius:var(--sn-stylekit-general-border-radius);border-color:var(--sn-stylekit-secondary-contrast-border-color);border:1px solid var(--sn-stylekit-secondary-contrast-border-color)}.sn-component .sk-notification.sk-secondary-contrast *,.sn-component .sk-input.sk-secondary-contrast *{position:relative}.sn-component .sk-notification.sk-secondary-contrast:before,.sn-component .sk-input.sk-secondary-contrast:before{content:"";position:absolute;top:0;left:0;width:100%;height:100%;background-color:var(--sn-stylekit-secondary-contrast-background-color);opacity:1.0;border-radius:var(--sn-stylekit-general-border-radius)}.sn-component .sk-notification.sk-secondary-contrast:after,.sn-component .sk-input.sk-secondary-contrast:after{content:'';display:block;height:100%;position:absolute;top:0;left:0;width:100%;border-radius:var(--sn-stylekit-general-border-radius);pointer-events:none;color:var(--sn-stylekit-secondary-contrast-border-color);border-color:var(--sn-stylekit-secondary-contrast-border-color)}.sn-component .sk-notification.sk-secondary-contrast.no-bg,.sn-component .sk-input.sk-secondary-contrast.no-bg{background-color:transparent}.sn-component .sk-notification.sk-secondary-contrast.no-bg:before,.sn-component .sk-input.sk-secondary-contrast.no-bg:before{content:none}.sn-component .sk-notification.sk-secondary-contrast.featured,.sn-component .sk-input.sk-secondary-contrast.featured{border:none;padding:0.75rem 1.25rem;font-size:var(--sn-stylekit-font-size-h3)}.sn-component .sk-notification.sk-secondary-contrast.featured:before,.sn-component .sk-input.sk-secondary-contrast.featured:before{opacity:1.0}.sn-component .sk-notification.sk-base,.sn-component .sk-input.sk-base{color:var(--sn-stylekit-foreground-color);position:relative;background-color:var(--sn-stylekit-background-color);overflow:hidden;border-radius:var(--sn-stylekit-general-border-radius);border-color:var(--sn-stylekit-border-color);border:1px solid var(--sn-stylekit-border-color)}.sn-component .sk-notification.sk-base *,.sn-component .sk-input.sk-base *{position:relative}.sn-component .sk-notification.sk-base:before,.sn-component .sk-input.sk-base:before{content:"";position:absolute;top:0;left:0;width:100%;height:100%;background-color:var(--sn-stylekit-background-color);opacity:1.0;border-radius:var(--sn-stylekit-general-border-radius)}.sn-component .sk-notification.sk-base:after,.sn-component .sk-input.sk-base:after{content:'';display:block;height:100%;position:absolute;top:0;left:0;width:100%;border-radius:var(--sn-stylekit-general-border-radius);pointer-events:none;color:var(--sn-stylekit-border-color);border-color:var(--sn-stylekit-border-color)}.sn-component .sk-notification.sk-base.no-bg,.sn-component .sk-input.sk-base.no-bg{background-color:transparent}.sn-component .sk-notification.sk-base.no-bg:before,.sn-component .sk-input.sk-base.no-bg:before{content:none}.sn-component .sk-notification.sk-base.featured,.sn-component .sk-input.sk-base.featured{border:none;padding:0.75rem 1.25rem;font-size:var(--sn-stylekit-font-size-h3)}.sn-component .sk-notification.sk-base.featured:before,.sn-component .sk-input.sk-base.featured:before{opacity:1.0}.sn-component .sk-notification.neutral,.sn-component .sk-input.neutral{color:var(--sn-stylekit-neutral-contrast-color);position:relative;background-color:var(--sn-stylekit-neutral-color);overflow:hidden;border-radius:var(--sn-stylekit-general-border-radius);border-color:var(--sn-stylekit-neutral-color)}.sn-component .sk-notification.neutral *,.sn-component .sk-input.neutral *{position:relative}.sn-component .sk-notification.neutral:before,.sn-component .sk-input.neutral:before{content:"";position:absolute;top:0;left:0;width:100%;height:100%;background-color:var(--sn-stylekit-neutral-color);opacity:1.0;border-radius:var(--sn-stylekit-general-border-radius)}.sn-component .sk-notification.neutral:after,.sn-component .sk-input.neutral:after{content:'';display:block;height:100%;position:absolute;top:0;left:0;width:100%;border-radius:var(--sn-stylekit-general-border-radius);pointer-events:none;box-shadow:inset 0 0 0 1px;color:var(--sn-stylekit-neutral-color)}.sn-component .sk-notification.neutral.no-bg,.sn-component .sk-input.neutral.no-bg{background-color:transparent}.sn-component .sk-notification.neutral.no-bg:before,.sn-component .sk-input.neutral.no-bg:before{content:none}.sn-component .sk-notification.neutral.featured,.sn-component .sk-input.neutral.featured{border:none;padding:0.75rem 1.25rem;font-size:var(--sn-stylekit-font-size-h3)}.sn-component .sk-notification.neutral.featured:before,.sn-component .sk-input.neutral.featured:before{opacity:1.0}.sn-component .sk-notification.info,.sn-component .sk-input.info{color:var(--sn-stylekit-info-contrast-color);position:relative;background-color:var(--sn-stylekit-info-color);overflow:hidden;border-radius:var(--sn-stylekit-general-border-radius);border-color:var(--sn-stylekit-info-color)}.sn-component .sk-notification.info *,.sn-component .sk-input.info *{position:relative}.sn-component .sk-notification.info:before,.sn-component .sk-input.info:before{content:"";position:absolute;top:0;left:0;width:100%;height:100%;background-color:var(--sn-stylekit-info-color);opacity:1.0;border-radius:var(--sn-stylekit-general-border-radius)}.sn-component .sk-notification.info:after,.sn-component .sk-input.info:after{content:'';display:block;height:100%;position:absolute;top:0;left:0;width:100%;border-radius:var(--sn-stylekit-general-border-radius);pointer-events:none;box-shadow:inset 0 0 0 1px;color:var(--sn-stylekit-info-color)}.sn-component .sk-notification.info.no-bg,.sn-component .sk-input.info.no-bg{background-color:transparent}.sn-component .sk-notification.info.no-bg:before,.sn-component .sk-input.info.no-bg:before{content:none}.sn-component .sk-notification.info.featured,.sn-component .sk-input.info.featured{border:none;padding:0.75rem 1.25rem;font-size:var(--sn-stylekit-font-size-h3)}.sn-component .sk-notification.info.featured:before,.sn-component .sk-input.info.featured:before{opacity:1.0}.sn-component .sk-notification.warning,.sn-component .sk-input.warning{color:var(--sn-stylekit-warning-contrast-color);position:relative;background-color:var(--sn-stylekit-warning-color);overflow:hidden;border-radius:var(--sn-stylekit-general-border-radius);border-color:var(--sn-stylekit-warning-color)}.sn-component .sk-notification.warning *,.sn-component .sk-input.warning *{position:relative}.sn-component .sk-notification.warning:before,.sn-component .sk-input.warning:before{content:"";position:absolute;top:0;left:0;width:100%;height:100%;background-color:var(--sn-stylekit-warning-color);opacity:1.0;border-radius:var(--sn-stylekit-general-border-radius)}.sn-component .sk-notification.warning:after,.sn-component .sk-input.warning:after{content:'';display:block;height:100%;position:absolute;top:0;left:0;width:100%;border-radius:var(--sn-stylekit-general-border-radius);pointer-events:none;box-shadow:inset 0 0 0 1px;color:var(--sn-stylekit-warning-color)}.sn-component .sk-notification.warning.no-bg,.sn-component .sk-input.warning.no-bg{background-color:transparent}.sn-component .sk-notification.warning.no-bg:before,.sn-component .sk-input.warning.no-bg:before{content:none}.sn-component .sk-notification.warning.featured,.sn-component .sk-input.warning.featured{border:none;padding:0.75rem 1.25rem;font-size:var(--sn-stylekit-font-size-h3)}.sn-component .sk-notification.warning.featured:before,.sn-component .sk-input.warning.featured:before{opacity:1.0}.sn-component .sk-notification.danger,.sn-component .sk-input.danger{color:var(--sn-stylekit-danger-contrast-color);position:relative;background-color:var(--sn-stylekit-danger-color);overflow:hidden;border-radius:var(--sn-stylekit-general-border-radius);border-color:var(--sn-stylekit-danger-color)}.sn-component .sk-notification.danger *,.sn-component .sk-input.danger *{position:relative}.sn-component .sk-notification.danger:before,.sn-component .sk-input.danger:before{content:"";position:absolute;top:0;left:0;width:100%;height:100%;background-color:var(--sn-stylekit-danger-color);opacity:1.0;border-radius:var(--sn-stylekit-general-border-radius)}.sn-component .sk-notification.danger:after,.sn-component .sk-input.danger:after{content:'';display:block;height:100%;position:absolute;top:0;left:0;width:100%;border-radius:var(--sn-stylekit-general-border-radius);pointer-events:none;box-shadow:inset 0 0 0 1px;color:var(--sn-stylekit-danger-color)}.sn-component .sk-notification.danger.no-bg,.sn-component .sk-input.danger.no-bg{background-color:transparent}.sn-component .sk-notification.danger.no-bg:before,.sn-component .sk-input.danger.no-bg:before{content:none}.sn-component .sk-notification.danger.featured,.sn-component .sk-input.danger.featured{border:none;padding:0.75rem 1.25rem;font-size:var(--sn-stylekit-font-size-h3)}.sn-component .sk-notification.danger.featured:before,.sn-component .sk-input.danger.featured:before{opacity:1.0}.sn-component .sk-notification.success,.sn-component .sk-input.success{color:var(--sn-stylekit-success-contrast-color);position:relative;background-color:var(--sn-stylekit-success-color);overflow:hidden;border-radius:var(--sn-stylekit-general-border-radius);border-color:var(--sn-stylekit-success-color)}.sn-component .sk-notification.success *,.sn-component .sk-input.success *{position:relative}.sn-component .sk-notification.success:before,.sn-component .sk-input.success:before{content:"";position:absolute;top:0;left:0;width:100%;height:100%;background-color:var(--sn-stylekit-success-color);opacity:1.0;border-radius:var(--sn-stylekit-general-border-radius)}.sn-component .sk-notification.success:after,.sn-component .sk-input.success:after{content:'';display:block;height:100%;position:absolute;top:0;left:0;width:100%;border-radius:var(--sn-stylekit-general-border-radius);pointer-events:none;box-shadow:inset 0 0 0 1px;color:var(--sn-stylekit-success-color)}.sn-component .sk-notification.success.no-bg,.sn-component .sk-input.success.no-bg{background-color:transparent}.sn-component .sk-notification.success.no-bg:before,.sn-component .sk-input.success.no-bg:before{content:none}.sn-component .sk-notification.success.featured,.sn-component .sk-input.success.featured{border:none;padding:0.75rem 1.25rem;font-size:var(--sn-stylekit-font-size-h3)}.sn-component .sk-notification.success.featured:before,.sn-component .sk-input.success.featured:before{opacity:1.0}.sn-component .sk-notification{padding:1.1rem 1rem;margin:1.4rem 0;text-align:left;cursor:default}.sn-component .sk-notification.one-line{padding:0rem 0.4rem}.sn-component .sk-notification.stretch{width:100%}.sn-component .sk-notification.dashed{border-style:dashed;border-width:2px}.sn-component .sk-notification.dashed:after{box-shadow:none}.sn-component .sk-notification .sk-notification-title{font-size:var(--sn-stylekit-font-size-h1);font-weight:bold;line-height:1.9rem}.sn-component .sk-notification .sk-notification-text{line-height:1.5rem;font-size:var(--sn-stylekit-font-size-p);text-align:left;font-weight:normal}.sn-component .sk-circle{border:1px solid;cursor:pointer;border-color:var(--sn-stylekit-contrast-foreground-color);background-color:var(--sn-stylekit-contrast-background-color);padding:0;border-radius:50% !important;flex-shrink:0}.sn-component .sk-circle:before{border-radius:50% !important}.sn-component .sk-circle:after{border-radius:50% !important}.sn-component .sk-circle.small{width:11px;height:11px}.sn-component .sk-spinner{border:1px solid var(--sn-stylekit-neutral-color);border-radius:50%;animation:rotate 0.8s infinite linear;border-right-color:transparent}.sn-component .sk-spinner.small{width:12px;height:12px}.sn-component .sk-spinner.info-contrast{border-color:var(--sn-stylekit-info-contrast-color);border-right-color:transparent}.sn-component .sk-spinner.info{border-color:var(--sn-stylekit-info-color);border-right-color:transparent}.sn-component .sk-spinner.warning{border-color:var(--sn-stylekit-warning-color);border-right-color:transparent}.sn-component .sk-spinner.danger{border-color:var(--sn-stylekit-danger-color);border-right-color:transparent}.sn-component .sk-spinner.success{border-color:var(--sn-stylekit-success-color);border-right-color:transparent}@keyframes rotate{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.sn-component .sk-app-bar{display:flex;width:100%;height:2rem;padding:0.0rem 0.8rem;background-color:var(--sn-stylekit-contrast-background-color);color:var(--sn-stylekit-contrast-foreground-color);justify-content:space-between;align-items:center;border:1px solid var(--sn-stylekit-contrast-border-color);user-select:none}.sn-component .sk-app-bar.no-edges{border-left:0;border-right:0}.sn-component .sk-app-bar.no-bottom-edge{border-bottom:0}.sn-component .sk-app-bar .left,.sn-component .sk-app-bar .right{display:flex;height:100%}.sn-component .sk-app-bar .sk-app-bar-item{flex-grow:1;cursor:pointer;display:flex;align-items:center;justify-content:center}.sn-component .sk-app-bar .sk-app-bar-item:not(:first-child){margin-left:1rem}.sn-component .sk-app-bar .sk-app-bar-item.border{border-left:1px solid var(--sn-stylekit-contrast-border-color)}.sn-component .sk-app-bar .sk-app-bar-item>.sk-app-bar-item-column{height:100%;display:flex;align-items:center}.sn-component .sk-app-bar .sk-app-bar-item>.sk-app-bar-item-column:not(:first-child){margin-left:0.5rem}.sn-component .sk-app-bar .sk-app-bar-item>.sk-app-bar-item-column.underline{border-bottom:2px solid var(--sn-stylekit-info-color)}.sn-component .sk-app-bar .sk-app-bar-item.no-pointer{cursor:default}.sn-component .sk-app-bar .sk-app-bar-item:hover>.sk-label:not(.subtle),.sn-component .sk-app-bar .sk-panel-section .sk-app-bar-item:hover>.sk-panel-section-subtitle:not(.subtle),.sn-component .sk-panel-section .sk-app-bar .sk-app-bar-item:hover>.sk-panel-section-subtitle:not(.subtle),.sn-component .sk-app-bar .sk-app-bar-item:hover>.sk-sublabel:not(.subtle),.sn-component .sk-app-bar .sk-app-bar-item:hover>.sk-app-bar-item-column>.sk-label:not(.subtle),.sn-component .sk-app-bar .sk-panel-section .sk-app-bar-item:hover>.sk-app-bar-item-column>.sk-panel-section-subtitle:not(.subtle),.sn-component .sk-panel-section .sk-app-bar .sk-app-bar-item:hover>.sk-app-bar-item-column>.sk-panel-section-subtitle:not(.subtle),.sn-component .sk-app-bar .sk-app-bar-item:hover>.sk-app-bar-item-column>.sk-sublabel:not(.subtle){color:var(--sn-stylekit-info-color)}.sn-component .sk-app-bar .sk-app-bar-item>.sk-label,.sn-component .sk-app-bar .sk-panel-section .sk-app-bar-item>.sk-panel-section-subtitle,.sn-component .sk-panel-section .sk-app-bar .sk-app-bar-item>.sk-panel-section-subtitle,.sn-component .sk-app-bar .sk-app-bar-item>.sk-app-bar-item-column>.sk-label,.sn-component .sk-app-bar .sk-panel-section .sk-app-bar-item>.sk-app-bar-item-column>.sk-panel-section-subtitle,.sn-component .sk-panel-section .sk-app-bar .sk-app-bar-item>.sk-app-bar-item-column>.sk-panel-section-subtitle{font-weight:bold;font-size:var(--sn-stylekit-font-size-h5);white-space:nowrap}.sn-component .sk-app-bar .sk-app-bar-item>.sk-sublabel,.sn-component .sk-app-bar .sk-app-bar-item>.sk-app-bar-item-column>.sk-sublabel{font-size:var(--sn-stylekit-font-size-h5);font-weight:normal;white-space:nowrap}.sn-component .sk-app-bar .sk-app-bar-item .subtle{font-weight:normal;opacity:0.6}.sn-component .sk-panel-table{display:flex;flex-wrap:wrap;padding-left:1px;padding-top:1px}.sn-component .sk-panel-table .sk-panel-table-item{flex:45%;flex-flow:wrap;border:1px solid var(--sn-stylekit-border-color);padding:1rem;margin-left:-1px;margin-top:-1px;display:flex;flex-direction:column;justify-content:space-between}.sn-component .sk-panel-table .sk-panel-table-item img{max-width:100%;margin-bottom:1rem}.sn-component .sk-panel-table .sk-panel-table-item .sk-panel-table-item-content{display:flex;flex-direction:row}.sn-component .sk-panel-table .sk-panel-table-item .sk-panel-table-item-column{align-items:center}.sn-component .sk-panel-table .sk-panel-table-item .sk-panel-table-item-column.stretch{width:100%}.sn-component .sk-panel-table .sk-panel-table-item .sk-panel-table-item-column:not(:first-child){padding-left:0.75rem}.sn-component .sk-panel-table .sk-panel-table-item .sk-panel-table-item-column.quarter{flex-basis:25%}.sn-component .sk-panel-table .sk-panel-table-item .sk-panel-table-item-column.three-quarters{flex-basis:75%}.sn-component .sk-panel-table .sk-panel-table-item .sk-panel-table-item-footer{margin-top:1.25rem}.sn-component .sk-panel-table .sk-panel-table-item.no-border{border:none}.sn-component .sk-modal{position:fixed;margin-left:auto;margin-right:auto;left:0;right:0;top:0;bottom:0;z-index:10000;width:100vw;height:100vh;background-color:transparent;color:var(--sn-stylekit-contrast-foreground-color);display:flex;align-items:center;justify-content:center}.sn-component .sk-modal .sn-component{height:100%}.sn-component .sk-modal .sn-component .sk-panel{height:100%}.sn-component .sk-modal.auto-height>.sk-modal-content{height:auto !important}.sn-component .sk-modal.large>.sk-modal-content{width:900px;height:600px}.sn-component .sk-modal.medium>.sk-modal-content{width:700px;height:500px}.sn-component .sk-modal.small>.sk-modal-content{width:700px;height:344px}.sn-component .sk-modal .sk-modal-background{position:absolute;z-index:-1;width:100%;height:100%;background-color:var(--sn-stylekit-contrast-background-color);opacity:0.7}.sn-component .sk-modal>.sk-modal-content{overflow-y:auto;width:auto;padding:0;padding-bottom:0;min-width:300px;-webkit-box-shadow:0px 2px 35px 0px rgba(0,0,0,0.19);-moz-box-shadow:0px 2px 35px 0px rgba(0,0,0,0.19);box-shadow:0px 2px 35px 0px rgba(0,0,0,0.19)}.sn-component.no-select{user-select:none}input,textarea,[contenteditable]{caret-color:var(--sn-stylekit-editor-foreground-color)}.windows-web,.windows-desktop,.linux-web,.linux-desktop{scrollbar-width:thin}.windows-web ::-webkit-scrollbar,.windows-desktop ::-webkit-scrollbar,.linux-web ::-webkit-scrollbar,.linux-desktop ::-webkit-scrollbar{width:17px;height:18px;border-left:0.5px solid var(--sn-stylekit-scrollbar-track-border-color-color)}.windows-web ::-webkit-scrollbar-thumb,.windows-desktop ::-webkit-scrollbar-thumb,.linux-web ::-webkit-scrollbar-thumb,.linux-desktop ::-webkit-scrollbar-thumb{border:4px solid rgba(0,0,0,0);background-clip:padding-box;-webkit-border-radius:10px;background-color:var(--sn-stylekit-scrollbar-thumb-color);-webkit-box-shadow:inset -1px -1px 0px rgba(0,0,0,0.05),inset 1px 1px 0px rgba(0,0,0,0.05)}.windows-web ::-webkit-scrollbar-button,.windows-desktop ::-webkit-scrollbar-button,.linux-web ::-webkit-scrollbar-button,.linux-desktop ::-webkit-scrollbar-button{width:0;height:0;display:none}.windows-web ::-webkit-scrollbar-corner,.windows-desktop ::-webkit-scrollbar-corner,.linux-web ::-webkit-scrollbar-corner,.linux-desktop ::-webkit-scrollbar-corner{background-color:transparent}body,html{font-family:sans-serif;height:100%;width:100%;margin:0;padding:0;overflow:hidden;overflow-y:hidden !important}#spreadsheet{border:none;width:100%;height:100%;color:var(--sn-stylekit-editor-foreground-color);background-color:var(--sn-stylekit-editor-background-color)}.k-spreadsheet-column-header,.k-spreadsheet-row-header,.k-spreadsheet-top-corner{color:var(--sn-stylekit-editor-foreground-color) !important;background-color:var(--sn-stylekit-contrast-background-color) !important}.k-spreadsheet .k-spreadsheet-action-bar{color:var(--sn-stylekit-editor-foreground-color) !important;background-color:var(--sn-stylekit-contrast-background-color) !important;border-color:var(--sn-stylekit-border-color) !important;border-top-width:1px !important}.k-spreadsheet .k-spreadsheet-formula-input{color:var(--sn-stylekit-editor-foreground-color) !important;background-color:var(--sn-stylekit-editor-background-color) !important}.k-spreadsheet .k-spreadsheet-name-editor .k-select{background-color:var(--sn-stylekit-contrast-background-color) !important}.k-block,.k-content,.k-dropdown .k-input,.k-popup,.k-toolbar,.k-widget{color:var(--sn-stylekit-editor-foreground-color) !important;background-color:var(--sn-stylekit-editor-background-color) !important}.k-spreadsheet-filter-menu .k-spreadsheet-value-treeview-wrapper{color:var(--sn-stylekit-editor-foreground-color) !important;background-color:var(--sn-stylekit-contrast-background-color) !important}.k-grid-header .k-header>.k-link,.k-header,.k-treemap-title{color:var(--sn-stylekit-editor-foreground-color) !important}.k-dropdown .k-input,.k-dropdown .k-state-focused .k-input,.k-menu .k-popup{color:var(--sn-stylekit-editor-foreground-color) !important}.k-panelbar>li.k-state-default>.k-link,.k-tabstrip-items .k-state-default .k-link{color:var(--sn-stylekit-editor-foreground-color) !important}.k-input,.k-multiselect-wrap,.k-textbox>input,input.k-textbox,input.k-textbox:hover,textarea.k-textbox,textarea.k-textbox:hover{color:var(--sn-stylekit-editor-foreground-color) !important}.k-spreadsheet-active-cell{background-color:var(--sn-stylekit-contrast-background-color) !important}.k-spreadsheet-active-cell.k-single{color:var(--sn-stylekit-editor-foreground-color) !important;background-color:var(--sn-stylekit-contrast-background-color) !important}.k-spreadsheet .k-spreadsheet-action-bar .k-spreadsheet-name-editor{border-color:var(--sn-stylekit-border-color) !important}.k-autocomplete .k-input,.k-autocomplete.k-state-focused .k-input,.k-dropdown-wrap .k-input,.k-dropdown-wrap.k-state-focused .k-input,.k-numeric-wrap.k-state-focused .k-input,.k-picker-wrap.k-state-focused .k-input,.k-textbox>input{border-color:var(--sn-stylekit-border-color) !important}.k-autocomplete,.k-block,.k-button-group .k-tool,.k-calendar th,.k-content,.k-dateinput.k-state-disabled>.k-textbox:hover,.k-dropdown-wrap,.k-dropzone-active,.k-editable-area,.k-editor-dialog .k-tabstrip-items,.k-filter-row>th,.k-footer-template td,.k-grid td,.k-grid td.k-state-selected,.k-grid-content-locked,.k-grid-footer,.k-grid-footer-locked,.k-grid-footer-wrap,.k-grid-header,.k-grid-header-locked,.k-grid-header-wrap,.k-group,.k-group-footer td,.k-grouping-header,.k-grouping-header .k-group-indicator,.k-header,.k-input,.k-maskedtextbox.k-state-disabled>.k-textbox:hover,.k-pager-refresh,.k-pager-wrap,.k-pager-wrap .k-link,.k-panel>.k-item>.k-link,.k-panelbar .k-content,.k-panelbar .k-panel,.k-panelbar>.k-item>.k-link,.k-popup.k-align .k-list .k-item:last-child,.k-separator,.k-slider-track,.k-splitbar,.k-state-default,.k-state-default .k-select,.k-state-disabled,.k-textbox,.k-textbox>input,.k-tiles,.k-toolbar,.k-tooltip,.k-treemap-tile,.k-upload-files,.k-widget{border-color:var(--sn-stylekit-border-color) !important}.k-input,.k-multiselect-wrap,.k-textbox>input,input.k-textbox,input.k-textbox:hover,textarea.k-textbox,textarea.k-textbox:hover{color:var(--sn-stylekit-editor-foreground-color) !important;background-color:var(--sn-stylekit-contrast-background-color) !important}.k-spreadsheet-pane .k-spreadsheet-haxis,.k-spreadsheet-pane .k-spreadsheet-vaxis{border-color:var(--sn-stylekit-border-color) !important}.k-spreadsheet-pane .k-spreadsheet-column-header,.k-spreadsheet-pane .k-spreadsheet-row-header{border-color:var(--sn-stylekit-border-color) !important}.k-spreadsheet-toolbar>.k-separator{border-color:var(--sn-stylekit-border-color) !important}.k-spreadsheet-filter-menu .k-details{border-color:var(--sn-stylekit-border-color) !important}.k-list-container{color:var(--sn-stylekit-editor-foreground-color) !important;background-color:var(--sn-stylekit-contrast-background-color) !important;border-color:var(--sn-stylekit-border-color) !important}.k-list-container .k-button{color:var(--sn-stylekit-editor-foreground-color) !important}.k-spreadsheet-popup .k-button{color:var(--sn-stylekit-editor-foreground-color) !important}.k-calendar .k-header,.k-header.k-scheduler-toolbar,.k-menu.k-header,.k-scheduler-footer>.k-header,.k-tabstrip .k-tabstrip-items .k-item,.k-tabstrip.k-header{background-color:var(--sn-stylekit-contrast-background-color) !important;border-color:transparent !important}.k-gantt-toolbar .k-state-default,.k-grid .k-grouping-header,.k-grid-header,.k-grid-header-wrap,.k-grouping-header .k-group-indicator,.k-header,.k-pager-wrap,.k-pager-wrap .k-link,.k-pager-wrap .k-textbox{border-color:var(--sn-stylekit-border-color) !important}.k-spreadsheet .k-spreadsheet-action-bar .k-spreadsheet-formula-bar::before{border-color:var(--sn-stylekit-border-color) !important}.k-active-filter,.k-state-active,.k-state-active:hover{border-color:var(--sn-stylekit-border-color) !important}.k-tabstrip .k-tabstrip-items .k-state-active{border-color:var(--sn-stylekit-info-color) !important}.k-syntax-paren-match{background-color:var(--sn-stylekit-info-color) !important}.k-autocomplete,.k-draghandle,.k-dropdown-wrap,.k-grid-header,.k-grouping-header,.k-header,.k-numeric-wrap,.k-pager-wrap,.k-panelbar .k-tabstrip-items .k-item,.k-picker-wrap,.k-progressbar,.k-state-highlight,.k-tabstrip-items .k-item,.k-textbox,.k-toolbar,.km-pane-wrapper>.km-pane>.km-view>.km-content{background-color:var(--sn-stylekit-contrast-background-color) !important;background-image:none}.k-button .k-icon,.k-button .k-image,.k-button .k-sprite{color:var(--sn-stylekit-info-color) !important}.k-tabstrip .k-tabstrip-items .k-state-active .k-link{color:var(--sn-stylekit-info-color) !important}div.k-window,div.k-window.k-state-focused{background-color:var(--sn-stylekit-contrast-background-color) !important;border-color:var(--sn-stylekit-border-color) !important}.k-content,.k-editable-area,.k-panel>li.k-item,.k-panelbar>li.k-item,.k-tiles{background-color:var(--sn-stylekit-contrast-background-color) !important}.k-action-window .k-action-buttons{background-color:var(--sn-stylekit-contrast-background-color) !important;border-color:var(--sn-stylekit-border-color) !important}.k-state-hover>.k-select{border-color:var(--sn-stylekit-info-color) !important}.k-icon,.k-tool-icon{color:var(--sn-stylekit-info-color) !important}.k-button{color:var(--sn-stylekit-info-color) !important;background-color:var(--sn-stylekit-contrast-background-color) !important;border:none !important;font-weight:bold}.k-button:hover{background-color:var(--sn-stylekit-background-color) !important}.k-list .k-item{border:none !important;cursor:pointer !important}.k-list .k-item .k-icon{color:var(--sn-stylekit-info-color) !important}.k-list .k-item.k-state-hovered{background-color:var(--sn-stylekit-background-color) !important}.k-list .k-item.k-state-selected{background-color:var(--sn-stylekit-info-color) !important;color:var(--sn-stylekit-info-contrast-color) !important}.k-list .k-item.k-state-selected .k-icon{color:var(--sn-stylekit-info-contrast-color) !important}.k-button.k-primary{color:var(--sn-stylekit-info-contrast-color) !important;border-color:var(--sn-stylekit-info-color) !important;background-color:var(--sn-stylekit-info-color) !important}.k-button.k-primary:hover:not(:disabled){filter:brightness(130%)}.k-tabstrip-items-wrapper{background-color:var(--sn-stylekit-contrast-background-color) !important}.k-spreadsheet-pane .k-spreadsheet-merged-cell{background-color:var(--sn-stylekit-background-color)} diff --git a/public/components/org.standardnotes.standard-sheets/dist/vendor/styles/kendo.fiori.min.css b/public/components/org.standardnotes.standard-sheets/dist/vendor/styles/kendo.fiori.min.css index 8a3a474bc..1890cf78a 100644 --- a/public/components/org.standardnotes.standard-sheets/dist/vendor/styles/kendo.fiori.min.css +++ b/public/components/org.standardnotes.standard-sheets/dist/vendor/styles/kendo.fiori.min.css @@ -1240,5 +1240,5 @@ -*/.k-spreadsheet-column-header,.k-spreadsheet-row-header{background-color:#fff}.k-spreadsheet-column-header,.k-spreadsheet-row-header,.k-spreadsheet-top-corner{background-color:#f2f2f2;background-image:none;color:#000;border-color:#bfbfbf}.k-spreadsheet-top-corner{border-color:#bfbfbf}.k-spreadsheet-top-corner:after{border-color:transparent #bfbfbf #bfbfbf transparent}.k-spreadsheet-pane{border-color:#bfbfbf}.k-spreadsheet-pane .k-spreadsheet-haxis,.k-spreadsheet-pane .k-spreadsheet-vaxis{border-color:#dedede}.k-spreadsheet-pane .k-spreadsheet-column-header,.k-spreadsheet-pane .k-spreadsheet-row-header{border-color:#bfbfbf}.k-spreadsheet-pane .k-spreadsheet-merged-cell{background-color:#fff}.k-spreadsheet-pane .k-selection-full,.k-spreadsheet-pane .k-selection-partial{border-color:rgba(0,124,192,.2);background-color:rgba(0,124,192,.2)}.k-spreadsheet-pane .k-filter-range{border-color:#007cc0}.k-spreadsheet-pane .k-spreadsheet-column-header .k-selection-full,.k-spreadsheet-pane .k-spreadsheet-column-header .k-selection-partial{border-bottom-color:#007cc0}.k-spreadsheet-pane .k-spreadsheet-row-header .k-selection-full,.k-spreadsheet-pane .k-spreadsheet-row-header .k-selection-partial{border-right-color:#007cc0}.k-auto-fill,.k-spreadsheet-selection{border-color:#007cc0;box-shadow:inset 0 0 0 1px #007cc0}.k-spreadsheet-selection{background-color:rgba(0,124,192,.2)}.k-spreadsheet-active-cell{box-shadow:inset 0 0 0 1px #007cc0;background-color:#fff}.k-spreadsheet-active-cell.k-right{box-shadow:inset 0 0 0 1px #007cc0,inset -1px 0 0 1px #007cc0}.k-spreadsheet-active-cell.k-bottom{box-shadow:inset 0 0 0 1px #007cc0,inset 0 -1px 0 1px #007cc0}.k-spreadsheet-active-cell.k-bottom.k-right{box-shadow:inset 0 0 0 1px #007cc0,inset -1px -1px 0 1px #007cc0}.k-spreadsheet-active-cell.k-single{color:#333;background-color:#fff}.k-spreadsheet .k-spreadsheet-action-bar{background-color:#fff;border-color:#bfbfbf}.k-spreadsheet .k-spreadsheet-action-bar .k-spreadsheet-name-editor{border-color:#bfbfbf}.k-spreadsheet .k-spreadsheet-action-bar .k-spreadsheet-formula-bar>.k-i-formula-fx{border-color:#bfbfbf}.k-spreadsheet .k-spreadsheet-formula-input{background-color:#fff;color:#333}.k-spreadsheet .k-resize-handle,.k-spreadsheet .k-resize-hint-handle,.k-spreadsheet .k-resize-hint-marker{background-color:#007cc0}.k-spreadsheet .k-resize-hint-vertical .k-resize-hint-handle,.k-spreadsheet .k-resize-hint-vertical .k-resize-hint-marker{background-color:#007cc0}.k-spreadsheet .k-single-selection::after{background-color:#007cc0;border-color:#fff}.k-spreadsheet .k-auto-fill-punch{background-color:rgba(255,255,255,.5)}.k-spreadsheet .k-single-selection.k-dim-auto-fill-handle::after{background-color:rgba(0,124,192,.5)}.k-spreadsheet .k-spreadsheet-cell-comment{color:#333;background-color:#fff;border-color:#bfbfbf}.k-spreadsheet .k-spreadsheet-has-comment:after{border-color:#007cc0 #007cc0 transparent transparent}.k-spreadsheet .k-dirty{border-color:#d70000 transparent transparent #d70000}.k-spreadsheet-format-cells .k-spreadsheet-preview{border-color:#bfbfbf}.k-spreadsheet-filter{border-radius:0;background-color:#fff;box-shadow:inset 0 0 0 1px #dedede}.k-spreadsheet-filter.k-state-active{color:#fff;background-color:#007cc0}.k-spreadsheet-filter:hover{color:#333;background:#eaeaea;border-color:#d6d6d6}.k-action-window .k-action-buttons{border-color:#bfbfbf;background:#f2f2f2}.k-spreadsheet-sample{color:grey}.k-state-selected .k-spreadsheet-sample{color:inherit}.k-spreadsheet-window .k-list{border-color:#bfbfbf;border-radius:0}.k-spreadsheet-popup{border-radius:0}.k-spreadsheet-popup .k-separator{background-color:#bfbfbf}.k-spreadsheet-popup .k-button{background-color:transparent}.k-spreadsheet-popup .k-button:hover{background-color:#eaeaea}.k-spreadsheet-popup .k-state-active{background-color:#007cc0;color:#000}.k-spreadsheet-popup .k-state-active:hover{background-color:#005b8d}.k-spreadsheet-filter-menu .k-details{border-color:#bfbfbf}.k-spreadsheet-filter-menu .k-details-content .k-space-right{background-color:#fff}.k-spreadsheet-filter-menu .k-spreadsheet-value-treeview-wrapper{background-color:#fff;border-color:#bfbfbf;border-radius:0}.k-syntax-ref{color:#f82}.k-syntax-num{color:#09f}.k-syntax-func{font-weight:700}.k-syntax-str{color:#38b714}.k-syntax-error{color:red}.k-syntax-bool{color:#a9169c}.k-syntax-startexp{font-weight:700}.k-syntax-paren-match{background-color:#caf200}.k-series-a{border-color:#008fd3;background-color:rgba(0,143,211,.15)}.k-series-b{border-color:#99d101;background-color:rgba(153,209,1,.15)}.k-series-c{border-color:#f39b02;background-color:rgba(243,155,2,.15)}.k-series-d{border-color:#f05662;background-color:rgba(240,86,98,.15)}.k-series-e{border-color:#c03c53;background-color:rgba(192,60,83,.15)}.k-series-f{border-color:#acacac;background-color:rgba(172,172,172,.15)}.k-spreadsheet-sheets-remove:hover .k-icon{color:#c22}.k-spreadsheet-formula-list .k-state-focused{background-color:#007cc0;color:#fff}.k-spreadsheet-insert-image-dialog{border-color:#bfbfbf;border-radius:0}.k-spreadsheet-insert-image-dialog .k-spreadsheet-has-image.k-state-hovered,.k-spreadsheet-insert-image-dialog .k-spreadsheet-has-image:hover{box-shadow:inset 0 0 0 2000px rgba(0,0,0,.5);border-radius:0}.k-spreadsheet-insert-image-dialog .k-spreadsheet-has-image.k-state-hovered div,.k-spreadsheet-insert-image-dialog .k-spreadsheet-has-image:hover div{color:#fff}.k-spreadsheet-drawing.k-spreadsheet-active-drawing{outline-color:#007cc0}.k-spreadsheet-drawing .k-spreadsheet-drawing-handle{border-color:#fff;background-color:#007cc0;border-radius:50%}.k-spreadsheet-drawing-anchor-cell{background:rgba(0,124,192,.2)}.k-rtl .k-spreadsheet .k-spreadsheet-has-comment::after{border-color:#007cc0 transparent transparent #007cc0}.k-rtl .k-spreadsheet .k-dirty{border-color:#d70000 #d70000 transparent transparent}.k-spreadsheet-name-editor .k-select{background-color:#eaeaea}.k-spreadsheet-sheets-bar{color:inherit;background-color:transparent}.k-numeric-wrap .k-i-warning{color:#d70000;position:absolute;top:0;right:2.2em;width:2.2em}.k-numeric-wrap.k-state-invalid{border-color:#d70000}.k-numeric-wrap.k-state-invalid .k-select{border-color:#d70000}.k-numeric-wrap.k-state-invalid input{color:#d70000}.k-rtl .k-numeric-wrap.k-state-invalid .k-i-warning{right:auto;left:2.2em}.k-maskedtextbox.k-state-invalid .k-textbox{border-color:#d70000;color:#d70000}.k-maskedtextbox.k-state-invalid .k-i-warning{color:#d70000;opacity:1}.k-dateinput.k-state-invalid .k-textbox{color:#d70000;border-color:#d70000}.k-dateinput.k-state-invalid .k-i-warning{opacity:1;margin-left:0;margin-right:.8em;color:#d70000}.k-rtl .k-dateinput .k-i-warning{margin-right:0;margin-left:.8em}.k-datepicker .k-picker-wrap.k-state-invalid,.k-timepicker .k-picker-wrap.k-state-invalid{border-color:#d70000}.k-datepicker .k-picker-wrap.k-state-invalid .k-input,.k-timepicker .k-picker-wrap.k-state-invalid .k-input{color:#d70000}.k-datepicker .k-picker-wrap .k-i-warning,.k-timepicker .k-picker-wrap .k-i-warning{color:#d70000;margin-left:0;margin-right:2.8em}.k-rtl .k-datepicker .k-picker-wrap .k-i-warning,.k-rtl .k-timepicker .k-picker-wrap .k-i-warning{margin-right:0;margin-left:2.8em}.k-datetimepicker .k-picker-wrap.k-state-invalid{border-color:#d70000}.k-datetimepicker .k-picker-wrap.k-state-invalid .k-input{color:#d70000}.k-datetimepicker .k-picker-wrap .k-i-warning{color:#d70000;margin-left:0;margin-right:5.6em}.k-rtl .k-datetimepicker .k-picker-wrap .k-icon.k-i-warning{margin-right:0;margin-left:5.6em}.k-time-header .k-time-now{color:#007cc0;background:0 0}.k-time-header .k-time-now:focus,.k-time-header .k-time-now:hover{color:#006fac}.k-time-list-wrapper{background-color:#fff}.k-time-list-wrapper .k-title{border-color:#bfbfbf;color:#7d7d7d;background-color:#f2f2f2;background:#fff}.k-time-list-wrapper.k-state-focused .k-title{color:#000;opacity:1}.k-time-list-wrapper.k-state-focused::after,.k-time-list-wrapper.k-state-focused::before{background-color:rgba(0,0,0,.04)}.k-time-list::after,.k-time-list::before{box-shadow:0 0 3em 1.5em #fff}.k-time-list .k-item:hover{color:#007cc0}.k-time-container{background:0 0}.k-time-highlight{background-color:#fff;border-color:#bfbfbf}.k-datetime-container .k-date-tab .k-datetime-buttongroup,.k-datetime-container .k-date-tab .k-datetime-selector{background-color:#fff}.k-listbox .k-list-scroller{border-color:#bfbfbf;background-color:#fff}.k-listbox .k-state-selected{border-color:transparent}.k-listbox .k-drop-hint{height:0;border-top:1px solid #007cc0}.k-grid-header .k-i-sort-asc-sm,.k-grid-header .k-i-sort-desc-sm,.k-grid-header .k-sort-order{color:#007cc0;opacity:1;margin-bottom:-1px}.k-menu-scroll-button{border-width:0;border-color:#bfbfbf;color:#333;background-color:#fff}.k-menu-scroll-wrapper.horizontal .k-scroll-left{border-right-width:1px}.k-menu-scroll-wrapper.horizontal .k-scroll-right{border-left-width:1px}.k-menu-scroll-wrapper.vertical .k-scroll-up{border-bottom-width:1px}.k-menu-scroll-wrapper.vertical .k-scroll-down{border-top-width:1px}.k-dropdowngrid-popup{border-color:#bfbfbf;color:#333;background-color:#fff}.k-dropdowngrid-popup .k-header{border-color:#bfbfbf;color:#333;background-color:#f2f2f2}.k-dropdowngrid-popup .k-group-header{border-color:#bfbfbf;color:#333;background-color:#f2f2f2}.k-dropdowngrid-popup .k-cell{border-color:#bfbfbf}.k-dropdowngrid-popup .k-item:nth-child(2n){background-color:transparent}.k-dropdowngrid-popup .k-footer{border-color:#bfbfbf;color:#333;background-color:#f2f2f2}.k-dropdowngrid-popup .k-item.k-state-hover{border-color:#bfbfbf;color:#333;background-color:#eaeaea}.k-dropdowngrid-popup .k-item.k-state-selected{border-color:#bfbfbf;color:#333;background-color:#51adfb}.k-dropdowngrid-popup .k-group-cell span{background-color:#333;color:#fff}.k-grid-list>.k-item.k-last>.k-cell,.k-grid-list>.k-item.k-last>.k-group-cell,.k-grid-list>.k-item.k-last>.k-spacer-cell{border-bottom-color:#333}.k-calendar{text-transform:uppercase}.k-calendar td{background-color:#f5f5f5}.k-calendar .k-content{border-spacing:1px}.k-master-row .k-grid-content-sticky{background-color:#fff}.k-master-row.k-alt .k-grid-content-sticky{background-color:#fff}.k-master-row.k-state-selected .k-grid-content-sticky{background-color:#51adfb;background-position:50% 50%}.k-master-row.k-state-hover .k-grid-content-sticky,.k-master-row:hover .k-grid-content-sticky{background-color:#eaeaea}.k-master-row.k-state-selected.k-state-hover .k-grid-content-sticky,.k-master-row.k-state-selected:hover .k-grid-content-sticky{background-color:#3da6ff;background-image:none}.k-footer-template .k-grid-footer-sticky,.k-grid-header .k-grid-header-sticky,.k-grouping-row .k-grid-content-sticky,.k-master-row .k-grid-content-sticky{border-left-color:#737373;border-right-color:#737373}.k-grid-header .k-filter-row .k-grid-header-sticky{background-color:#f2f2f2}.k-grid-content-locked,.k-grid-content-locked td,.k-grid-header-locked,.k-grid-header-locked .k-header{border-left-color:#737373;border-right-color:#737373}.k-check-all-wrap{border-color:#bfbfbf} +*/.k-spreadsheet-column-header,.k-spreadsheet-row-header,.k-spreadsheet-top-corner{background-color:#f2f2f2;background-image:none;color:#000;border-color:#bfbfbf}.k-spreadsheet-top-corner{border-color:#bfbfbf}.k-spreadsheet-top-corner:after{border-color:transparent #bfbfbf #bfbfbf transparent}.k-spreadsheet-pane{border-color:#bfbfbf}.k-spreadsheet-pane .k-spreadsheet-haxis,.k-spreadsheet-pane .k-spreadsheet-vaxis{border-color:#dedede}.k-spreadsheet-pane .k-spreadsheet-column-header,.k-spreadsheet-pane .k-spreadsheet-row-header{border-color:#bfbfbf}.k-spreadsheet-pane .k-selection-full,.k-spreadsheet-pane .k-selection-partial{border-color:rgba(0,124,192,.2);background-color:rgba(0,124,192,.2)}.k-spreadsheet-pane .k-filter-range{border-color:#007cc0}.k-spreadsheet-pane .k-spreadsheet-column-header .k-selection-full,.k-spreadsheet-pane .k-spreadsheet-column-header .k-selection-partial{border-bottom-color:#007cc0}.k-spreadsheet-pane .k-spreadsheet-row-header .k-selection-full,.k-spreadsheet-pane .k-spreadsheet-row-header .k-selection-partial{border-right-color:#007cc0}.k-auto-fill,.k-spreadsheet-selection{border-color:#007cc0;box-shadow:inset 0 0 0 1px #007cc0}.k-spreadsheet-selection{background-color:rgba(0,124,192,.2)}.k-spreadsheet-active-cell{box-shadow:inset 0 0 0 1px #007cc0;background-color:#fff}.k-spreadsheet-active-cell.k-right{box-shadow:inset 0 0 0 1px #007cc0,inset -1px 0 0 1px #007cc0}.k-spreadsheet-active-cell.k-bottom{box-shadow:inset 0 0 0 1px #007cc0,inset 0 -1px 0 1px #007cc0}.k-spreadsheet-active-cell.k-bottom.k-right{box-shadow:inset 0 0 0 1px #007cc0,inset -1px -1px 0 1px #007cc0}.k-spreadsheet-active-cell.k-single{color:#333;background-color:#fff}.k-spreadsheet .k-spreadsheet-action-bar{background-color:#fff;border-color:#bfbfbf}.k-spreadsheet .k-spreadsheet-action-bar .k-spreadsheet-name-editor{border-color:#bfbfbf}.k-spreadsheet .k-spreadsheet-formula-input{background-color:#fff;color:#333}.k-spreadsheet .k-resize-handle,.k-spreadsheet .k-resize-hint-handle,.k-spreadsheet .k-resize-hint-marker{background-color:#007cc0}.k-spreadsheet .k-resize-hint-vertical .k-resize-hint-handle,.k-spreadsheet .k-resize-hint-vertical .k-resize-hint-marker{background-color:#007cc0}.k-spreadsheet .k-single-selection::after{background-color:#007cc0;border-color:#fff}.k-spreadsheet .k-auto-fill-punch{background-color:rgba(255,255,255,.5)}.k-spreadsheet .k-single-selection.k-dim-auto-fill-handle::after{background-color:rgba(0,124,192,.5)}.k-spreadsheet .k-spreadsheet-cell-comment{color:#333;background-color:#fff;border-color:#bfbfbf}.k-spreadsheet .k-spreadsheet-has-comment:after{border-color:#007cc0 #007cc0 transparent transparent}.k-spreadsheet .k-dirty{border-color:#d70000 transparent transparent #d70000}.k-spreadsheet-format-cells .k-spreadsheet-preview{border-color:#bfbfbf}.k-spreadsheet-filter{border-radius:0;background-color:#fff;box-shadow:inset 0 0 0 1px #dedede}.k-spreadsheet-filter.k-state-active{color:#fff;background-color:#007cc0}.k-spreadsheet-filter:hover{color:#333;background:#eaeaea;border-color:#d6d6d6}.k-action-window .k-action-buttons{border-color:#bfbfbf;background:#f2f2f2}.k-spreadsheet-sample{color:grey}.k-state-selected .k-spreadsheet-sample{color:inherit}.k-spreadsheet-window .k-list{border-color:#bfbfbf;border-radius:0}.k-spreadsheet-popup{border-radius:0}.k-spreadsheet-popup .k-separator{background-color:#bfbfbf}.k-spreadsheet-popup .k-button{background-color:transparent}.k-spreadsheet-popup .k-button:hover{background-color:#eaeaea}.k-spreadsheet-popup .k-state-active{background-color:#007cc0;color:#000}.k-spreadsheet-popup .k-state-active:hover{background-color:#005b8d}.k-spreadsheet-filter-menu .k-details{border-color:#bfbfbf}.k-spreadsheet-filter-menu .k-details-content .k-space-right{background-color:#fff}.k-spreadsheet-filter-menu .k-spreadsheet-value-treeview-wrapper{background-color:#fff;border-color:#bfbfbf;border-radius:0}.k-syntax-ref{color:#f82}.k-syntax-num{color:#09f}.k-syntax-func{font-weight:700}.k-syntax-str{color:#38b714}.k-syntax-error{color:red}.k-syntax-bool{color:#a9169c}.k-syntax-startexp{font-weight:700}.k-syntax-paren-match{background-color:#caf200}.k-series-a{border-color:#008fd3;background-color:rgba(0,143,211,.15)}.k-series-b{border-color:#99d101;background-color:rgba(153,209,1,.15)}.k-series-c{border-color:#f39b02;background-color:rgba(243,155,2,.15)}.k-series-d{border-color:#f05662;background-color:rgba(240,86,98,.15)}.k-series-e{border-color:#c03c53;background-color:rgba(192,60,83,.15)}.k-series-f{border-color:#acacac;background-color:rgba(172,172,172,.15)}.k-spreadsheet-sheets-remove:hover .k-icon{color:#c22}.k-spreadsheet-formula-list .k-state-focused{background-color:#007cc0;color:#fff}.k-spreadsheet-insert-image-dialog{border-color:#bfbfbf;border-radius:0}.k-spreadsheet-insert-image-dialog .k-spreadsheet-has-image.k-state-hovered,.k-spreadsheet-insert-image-dialog .k-spreadsheet-has-image:hover{box-shadow:inset 0 0 0 2000px rgba(0,0,0,.5);border-radius:0}.k-spreadsheet-insert-image-dialog .k-spreadsheet-has-image.k-state-hovered div,.k-spreadsheet-insert-image-dialog .k-spreadsheet-has-image:hover div{color:#fff}.k-spreadsheet-drawing.k-spreadsheet-active-drawing{outline-color:#007cc0}.k-spreadsheet-drawing .k-spreadsheet-drawing-handle{border-color:#fff;background-color:#007cc0;border-radius:50%}.k-spreadsheet-drawing-anchor-cell{background:rgba(0,124,192,.2)}.k-rtl .k-spreadsheet .k-spreadsheet-has-comment::after{border-color:#007cc0 transparent transparent #007cc0}.k-rtl .k-spreadsheet .k-dirty{border-color:#d70000 #d70000 transparent transparent}.k-spreadsheet-name-editor .k-select{background-color:#eaeaea}.k-spreadsheet-sheets-bar{color:inherit;background-color:transparent}.k-numeric-wrap .k-i-warning{color:#d70000;position:absolute;top:0;right:2.2em;width:2.2em}.k-numeric-wrap.k-state-invalid{border-color:#d70000}.k-numeric-wrap.k-state-invalid .k-select{border-color:#d70000}.k-numeric-wrap.k-state-invalid input{color:#d70000}.k-rtl .k-numeric-wrap.k-state-invalid .k-i-warning{right:auto;left:2.2em}.k-maskedtextbox.k-state-invalid .k-textbox{border-color:#d70000;color:#d70000}.k-maskedtextbox.k-state-invalid .k-i-warning{color:#d70000;opacity:1}.k-dateinput.k-state-invalid .k-textbox{color:#d70000;border-color:#d70000}.k-dateinput.k-state-invalid .k-i-warning{opacity:1;margin-left:0;margin-right:.8em;color:#d70000}.k-rtl .k-dateinput .k-i-warning{margin-right:0;margin-left:.8em}.k-datepicker .k-picker-wrap.k-state-invalid,.k-timepicker .k-picker-wrap.k-state-invalid{border-color:#d70000}.k-datepicker .k-picker-wrap.k-state-invalid .k-input,.k-timepicker .k-picker-wrap.k-state-invalid .k-input{color:#d70000}.k-datepicker .k-picker-wrap .k-i-warning,.k-timepicker .k-picker-wrap .k-i-warning{color:#d70000;margin-left:0;margin-right:2.8em}.k-rtl .k-datepicker .k-picker-wrap .k-i-warning,.k-rtl .k-timepicker .k-picker-wrap .k-i-warning{margin-right:0;margin-left:2.8em}.k-datetimepicker .k-picker-wrap.k-state-invalid{border-color:#d70000}.k-datetimepicker .k-picker-wrap.k-state-invalid .k-input{color:#d70000}.k-datetimepicker .k-picker-wrap .k-i-warning{color:#d70000;margin-left:0;margin-right:5.6em}.k-rtl .k-datetimepicker .k-picker-wrap .k-icon.k-i-warning{margin-right:0;margin-left:5.6em}.k-time-header .k-time-now{color:#007cc0;background:0 0}.k-time-header .k-time-now:focus,.k-time-header .k-time-now:hover{color:#006fac}.k-time-list-wrapper{background-color:#fff}.k-time-list-wrapper .k-title{border-color:#bfbfbf;color:#7d7d7d;background-color:#f2f2f2;background:#fff}.k-time-list-wrapper.k-state-focused .k-title{color:#000;opacity:1}.k-time-list-wrapper.k-state-focused::after,.k-time-list-wrapper.k-state-focused::before{background-color:rgba(0,0,0,.04)}.k-time-list::after,.k-time-list::before{box-shadow:0 0 3em 1.5em #fff}.k-time-list .k-item:hover{color:#007cc0}.k-time-container{background:0 0}.k-time-highlight{background-color:#fff;border-color:#bfbfbf}.k-datetime-container .k-date-tab .k-datetime-buttongroup,.k-datetime-container .k-date-tab .k-datetime-selector{background-color:#fff}.k-listbox .k-list-scroller{border-color:#bfbfbf;background-color:#fff}.k-listbox .k-state-selected{border-color:transparent}.k-listbox .k-drop-hint{height:0;border-top:1px solid #007cc0}.k-grid-header .k-i-sort-asc-sm,.k-grid-header .k-i-sort-desc-sm,.k-grid-header .k-sort-order{color:#007cc0;opacity:1;margin-bottom:-1px}.k-menu-scroll-button{border-width:0;border-color:#bfbfbf;color:#333;background-color:#fff}.k-menu-scroll-wrapper.horizontal .k-scroll-left{border-right-width:1px}.k-menu-scroll-wrapper.horizontal .k-scroll-right{border-left-width:1px}.k-menu-scroll-wrapper.vertical .k-scroll-up{border-bottom-width:1px}.k-menu-scroll-wrapper.vertical .k-scroll-down{border-top-width:1px}.k-dropdowngrid-popup{border-color:#bfbfbf;color:#333;background-color:#fff}.k-dropdowngrid-popup .k-header{border-color:#bfbfbf;color:#333;background-color:#f2f2f2}.k-dropdowngrid-popup .k-group-header{border-color:#bfbfbf;color:#333;background-color:#f2f2f2}.k-dropdowngrid-popup .k-cell{border-color:#bfbfbf}.k-dropdowngrid-popup .k-item:nth-child(2n){background-color:transparent}.k-dropdowngrid-popup .k-footer{border-color:#bfbfbf;color:#333;background-color:#f2f2f2}.k-dropdowngrid-popup .k-item.k-state-hover{border-color:#bfbfbf;color:#333;background-color:#eaeaea}.k-dropdowngrid-popup .k-item.k-state-selected{border-color:#bfbfbf;color:#333;background-color:#51adfb}.k-dropdowngrid-popup .k-group-cell span{background-color:#333;color:#fff}.k-grid-list>.k-item.k-last>.k-cell,.k-grid-list>.k-item.k-last>.k-group-cell,.k-grid-list>.k-item.k-last>.k-spacer-cell{border-bottom-color:#333}.k-calendar{text-transform:uppercase}.k-calendar td{background-color:#f5f5f5}.k-calendar .k-content{border-spacing:1px}.k-master-row .k-grid-content-sticky{background-color:#fff}.k-master-row.k-alt .k-grid-content-sticky{background-color:#fff}.k-master-row.k-state-selected .k-grid-content-sticky{background-color:#51adfb;background-position:50% 50%}.k-master-row.k-state-hover .k-grid-content-sticky,.k-master-row:hover .k-grid-content-sticky{background-color:#eaeaea}.k-master-row.k-state-selected.k-state-hover .k-grid-content-sticky,.k-master-row.k-state-selected:hover .k-grid-content-sticky{background-color:#3da6ff;background-image:none}.k-footer-template .k-grid-footer-sticky,.k-grid-header .k-grid-header-sticky,.k-grouping-row .k-grid-content-sticky,.k-master-row .k-grid-content-sticky{border-left-color:#737373;border-right-color:#737373}.k-grid-header .k-filter-row .k-grid-header-sticky{background-color:#f2f2f2}.k-grid-content-locked,.k-grid-content-locked td,.k-grid-header-locked,.k-grid-header-locked .k-header{border-left-color:#737373;border-right-color:#737373}.k-check-all-wrap{border-color:#bfbfbf} /*# sourceMappingURL=kendo.fiori.min.css.map */ diff --git a/public/components/org.standardnotes.standard-sheets/package.json b/public/components/org.standardnotes.standard-sheets/package.json index 0ff909928..345f6f096 100644 --- a/public/components/org.standardnotes.standard-sheets/package.json +++ b/public/components/org.standardnotes.standard-sheets/package.json @@ -1,6 +1,6 @@ { "name": "sn-spreadsheets", - "version": "1.4.3", + "version": "1.4.4", "main": "dist/dist.js", "scripts": { "lint": "eslint app/ --ext .js", diff --git a/yarn.lock b/yarn.lock index 399ce06f3..ba3abb492 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2610,10 +2610,10 @@ resolved "https://registry.yarnpkg.com/@standardnotes/common/-/common-1.8.0.tgz#af72ad85f0d410ae31c0c110137911e2595634de" integrity sha512-R3nfAvhaXp5ufMB0M0fmV9yizE/Of2PGNJKnxtdxwowAq/ZakHu8Rh/v485PXrCaCFREVOZQO8kg0RQM8YngSw== -"@standardnotes/components@1.4.3": - version "1.4.3" - resolved "https://registry.yarnpkg.com/@standardnotes/components/-/components-1.4.3.tgz#e48205a00b2727a9d68c051785806f35985754b5" - integrity sha512-MUVPEQmRICLgQ3s+TPNP09w6GBbIO1B0WaqAoYJX3x21RAxyHwxIKzuz66g2VmlzrHevD92hepix3/ps0qwa4g== +"@standardnotes/components@1.4.4": + version "1.4.4" + resolved "https://registry.yarnpkg.com/@standardnotes/components/-/components-1.4.4.tgz#2db82b5f35d34be8c766e22d8559e2b80a6aa996" + integrity sha512-sxG5iIETpZUYVt6zQBTRxUC1qCZRmKyOuERyDLADju2gnGddDiLWU/TpgE7k8C/pLkw4OXprAmEVxbOagDrIxw== "@standardnotes/domain-events@^2.17.1": version "2.17.1" @@ -2622,7 +2622,15 @@ dependencies: "@standardnotes/auth" "^3.15.3" -"@standardnotes/features@1.25.0", "@standardnotes/features@^1.25.0": +"@standardnotes/features@1.26.1": + version "1.26.1" + resolved "https://registry.yarnpkg.com/@standardnotes/features/-/features-1.26.1.tgz#d4e4aed2ba91e40c407484e569a96c339477bba2" + integrity sha512-zxoeH9fjQtcTUbc5qaYD7AZETKDniM+tIvmLrWqwSC9B6/IL0R39G51BEkOzen+KhR/WgH3itHDVM+zrSMg53Q== + dependencies: + "@standardnotes/auth" "^3.15.3" + "@standardnotes/common" "^1.8.0" + +"@standardnotes/features@^1.25.0": version "1.25.0" resolved "https://registry.yarnpkg.com/@standardnotes/features/-/features-1.25.0.tgz#7ea32b547030ae82a60a2b30c458073072bd60c9" integrity sha512-8mwP6/WC/5YSfjNw/uburRDpXltebXyKWVmTWlewNdwWEnXMaTm9FqmuYvs1EDPPBIlKYheLQfK6bu78eEjLMQ== From b932e2a45e2eeb789fb03a53b3d5b0d2ad1e9ec4 Mon Sep 17 00:00:00 2001 From: Aman Harwara Date: Sat, 29 Jan 2022 01:53:39 +0530 Subject: [PATCH 07/65] feat: Add new "Change Editor" option to note context menu (#823) * feat: add editor icon * refactor: remove 'any' type and format * refactor: move NotesOptions and add ChangeEditorOption * refactor: fix type for using regular RefObject * feat: add hide-if-last-child util class * feat: add Change Editor option * feat: make radio btn gray if not checked * fix: accordion menu header and item sizing/spacing * feat: add Escape key to KeyboardKey enum * refactor: Remove Editor Menu * feat: add editor select functionality * refactor: move plain editor name to constant * feat: add premium editors with modal if no subscription refactor: simplify menu group creation * feat: show alert when switching to non-interchangeable editor * fix: change editor menu going out of bounds * feat: increase group header & editor item size * fix: change editor menu close on blur * refactor: Use KeyboardKey enum & remove else statement * feat: add keyboard navigation to change editor menu * fix: editor menu separators * feat: improve change editor menu sizing & spacing * feat: show alert only if editor is not interchangeable * feat: don't show alert when switching to/from plain editor * chore: bump snjs version * feat: temporarily remove change editor alert * feat: dynamically get footer height * refactor: move magic number to const * refactor: move constants to constants file * feat: use const instead of magic number --- app/assets/icons/ic-editor.svg | 3 + app/assets/javascripts/app.ts | 2 - app/assets/javascripts/components/Icon.tsx | 2 + .../components/NotesContextMenu.tsx | 19 +- .../NotesOptions/ChangeEditorOption.tsx | 288 ++++++++++++++++++ .../{ => NotesOptions}/NotesOptions.tsx | 114 ++++--- .../changeEditor/EditorAccordionMenu.tsx | 256 ++++++++++++++++ .../changeEditor/createEditorMenuGroups.ts | 128 ++++++++ .../components/NotesOptionsPanel.tsx | 143 +++++---- app/assets/javascripts/components/utils.ts | 2 +- .../directives/views/editorMenu.ts | 81 ----- .../javascripts/directives/views/index.ts | 1 - app/assets/javascripts/services/ioService.ts | 1 + .../ui_models/app_state/notes_state.ts | 42 +-- app/assets/javascripts/views/constants.ts | 2 + .../javascripts/views/note_view/note-view.pug | 14 - .../javascripts/views/note_view/note_view.ts | 160 +++------- app/assets/stylesheets/_sn.scss | 18 +- .../templates/directives/editor-menu.pug | 30 -- package.json | 2 +- yarn.lock | 8 +- 21 files changed, 932 insertions(+), 384 deletions(-) create mode 100644 app/assets/icons/ic-editor.svg create mode 100644 app/assets/javascripts/components/NotesOptions/ChangeEditorOption.tsx rename app/assets/javascripts/components/{ => NotesOptions}/NotesOptions.tsx (85%) create mode 100644 app/assets/javascripts/components/NotesOptions/changeEditor/EditorAccordionMenu.tsx create mode 100644 app/assets/javascripts/components/NotesOptions/changeEditor/createEditorMenuGroups.ts delete mode 100644 app/assets/javascripts/directives/views/editorMenu.ts delete mode 100644 app/assets/templates/directives/editor-menu.pug diff --git a/app/assets/icons/ic-editor.svg b/app/assets/icons/ic-editor.svg new file mode 100644 index 000000000..209be8f42 --- /dev/null +++ b/app/assets/icons/ic-editor.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/assets/javascripts/app.ts b/app/assets/javascripts/app.ts index 49fcb298c..f5d61825a 100644 --- a/app/assets/javascripts/app.ts +++ b/app/assets/javascripts/app.ts @@ -64,7 +64,6 @@ import { } from './directives/functional'; import { ActionsMenu, - EditorMenu, HistoryMenu, InputModal, MenuRow, @@ -160,7 +159,6 @@ const startApplication: StartApplication = async function startApplication( .directive('actionsMenu', () => new ActionsMenu()) .directive('challengeModal', () => new ChallengeModal()) .directive('componentView', ComponentViewDirective) - .directive('editorMenu', () => new EditorMenu()) .directive('inputModal', () => new InputModal()) .directive('menuRow', () => new MenuRow()) .directive('panelResizer', () => new PanelResizer()) diff --git a/app/assets/javascripts/components/Icon.tsx b/app/assets/javascripts/components/Icon.tsx index ef347b6b4..bbf20bbfa 100644 --- a/app/assets/javascripts/components/Icon.tsx +++ b/app/assets/javascripts/components/Icon.tsx @@ -1,3 +1,4 @@ +import EditorIcon from '../../icons/ic-editor.svg'; import PremiumFeatureIcon from '../../icons/ic-premium-feature.svg'; import PencilOffIcon from '../../icons/ic-pencil-off.svg'; import PlainTextIcon from '../../icons/ic-text-paragraph.svg'; @@ -68,6 +69,7 @@ import { toDirective } from './utils'; import { FunctionalComponent } from 'preact'; const ICONS = { + 'editor': EditorIcon, 'menu-arrow-down-alt': MenuArrowDownAlt, 'menu-arrow-right': MenuArrowRight, notes: NotesIcon, diff --git a/app/assets/javascripts/components/NotesContextMenu.tsx b/app/assets/javascripts/components/NotesContextMenu.tsx index 1c4c367f5..e24e54387 100644 --- a/app/assets/javascripts/components/NotesContextMenu.tsx +++ b/app/assets/javascripts/components/NotesContextMenu.tsx @@ -1,7 +1,7 @@ import { AppState } from '@/ui_models/app_state'; import { toDirective, useCloseOnBlur, useCloseOnClickOutside } from './utils'; import { observer } from 'mobx-react-lite'; -import { NotesOptions } from './NotesOptions'; +import { NotesOptions } from './NotesOptions/NotesOptions'; import { useCallback, useEffect, useRef } from 'preact/hooks'; import { WebApplication } from '@/ui_models/application'; @@ -11,21 +11,16 @@ type Props = { }; const NotesContextMenu = observer(({ application, appState }: Props) => { - const { - contextMenuOpen, - contextMenuPosition, - contextMenuMaxHeight, - } = appState.notes; + const { contextMenuOpen, contextMenuPosition, contextMenuMaxHeight } = + appState.notes; const contextMenuRef = useRef(null); - const [closeOnBlur] = useCloseOnBlur( - contextMenuRef as any, - (open: boolean) => appState.notes.setContextMenuOpen(open) + const [closeOnBlur] = useCloseOnBlur(contextMenuRef, (open: boolean) => + appState.notes.setContextMenuOpen(open) ); - useCloseOnClickOutside( - contextMenuRef as any, - (open: boolean) => appState.notes.setContextMenuOpen(open) + useCloseOnClickOutside(contextMenuRef, (open: boolean) => + appState.notes.setContextMenuOpen(open) ); const reloadContextMenuLayout = useCallback(() => { diff --git a/app/assets/javascripts/components/NotesOptions/ChangeEditorOption.tsx b/app/assets/javascripts/components/NotesOptions/ChangeEditorOption.tsx new file mode 100644 index 000000000..0aacc230a --- /dev/null +++ b/app/assets/javascripts/components/NotesOptions/ChangeEditorOption.tsx @@ -0,0 +1,288 @@ +import { KeyboardKey } from '@/services/ioService'; +import { STRING_EDIT_LOCKED_ATTEMPT } from '@/strings'; +import { WebApplication } from '@/ui_models/application'; +import { AppState } from '@/ui_models/app_state'; +import { + MENU_MARGIN_FROM_APP_BORDER, + MAX_MENU_SIZE_MULTIPLIER, +} from '@/views/constants'; +import { + reloadFont, + transactionForAssociateComponentWithCurrentNote, + transactionForDisassociateComponentWithCurrentNote, +} from '@/views/note_view/note_view'; +import { + Disclosure, + DisclosureButton, + DisclosurePanel, +} from '@reach/disclosure'; +import { + ComponentArea, + ItemMutator, + NoteMutator, + PrefKey, + SNComponent, + SNNote, + TransactionalMutation, +} from '@standardnotes/snjs'; +import { FunctionComponent } from 'preact'; +import { useEffect, useRef, useState } from 'preact/hooks'; +import { Icon, IconType } from '../Icon'; +import { PremiumModalProvider } from '../Premium'; +import { createEditorMenuGroups } from './changeEditor/createEditorMenuGroups'; +import { EditorAccordionMenu } from './changeEditor/EditorAccordionMenu'; + +type ChangeEditorOptionProps = { + appState: AppState; + application: WebApplication; + note: SNNote; + closeOnBlur: (event: { relatedTarget: EventTarget | null }) => void; +}; + +type AccordionMenuGroup = { + icon?: IconType; + iconClassName?: string; + title: string; + items: Array; +}; + +export type EditorMenuItem = { + name: string; + component?: SNComponent; + isPremiumFeature?: boolean; +}; + +export type EditorMenuGroup = AccordionMenuGroup; + +export const ChangeEditorOption: FunctionComponent = ({ + application, + appState, + closeOnBlur, + note, +}) => { + const [changeEditorMenuOpen, setChangeEditorMenuOpen] = useState(false); + const [changeEditorMenuPosition, setChangeEditorMenuPosition] = useState<{ + top?: number | 'auto'; + right?: number | 'auto'; + bottom: number | 'auto'; + left?: number | 'auto'; + }>({ + right: 0, + bottom: 0, + }); + const changeEditorMenuRef = useRef(null); + const changeEditorButtonRef = useRef(null); + const [editors] = useState(() => + application.componentManager + .componentsForArea(ComponentArea.Editor) + .sort((a, b) => { + return a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1; + }) + ); + const [editorMenuGroups, setEditorMenuGroups] = useState( + [] + ); + const [selectedEditor, setSelectedEditor] = useState(() => + application.componentManager.editorForNote(note) + ); + + useEffect(() => { + setEditorMenuGroups(createEditorMenuGroups(editors)); + }, [editors]); + + useEffect(() => { + setSelectedEditor(application.componentManager.editorForNote(note)); + }, [application, note]); + + const toggleChangeEditorMenu = () => { + const defaultFontSize = window.getComputedStyle( + document.documentElement + ).fontSize; + const maxChangeEditorMenuSize = + parseFloat(defaultFontSize) * MAX_MENU_SIZE_MULTIPLIER; + const { clientWidth, clientHeight } = document.documentElement; + const buttonRect = changeEditorButtonRef.current?.getBoundingClientRect(); + const buttonParentRect = + changeEditorButtonRef.current?.parentElement?.getBoundingClientRect(); + const footerElementRect = document + .getElementById('footer-bar') + ?.getBoundingClientRect(); + const footerHeightInPx = footerElementRect?.height; + + if (buttonRect && buttonParentRect && footerHeightInPx) { + let positionBottom = + clientHeight - buttonRect.bottom - buttonRect.height / 2; + + if (positionBottom < footerHeightInPx) { + positionBottom = footerHeightInPx + MENU_MARGIN_FROM_APP_BORDER; + } + + if (buttonRect.right + maxChangeEditorMenuSize > clientWidth) { + setChangeEditorMenuPosition({ + top: positionBottom - buttonParentRect.height / 2, + right: clientWidth - buttonRect.left, + bottom: 'auto', + }); + } else { + setChangeEditorMenuPosition({ + bottom: positionBottom, + left: buttonRect.right, + }); + } + } + + setChangeEditorMenuOpen(!changeEditorMenuOpen); + }; + + useEffect(() => { + if (changeEditorMenuOpen) { + const defaultFontSize = window.getComputedStyle( + document.documentElement + ).fontSize; + const maxChangeEditorMenuSize = + parseFloat(defaultFontSize) * MAX_MENU_SIZE_MULTIPLIER; + const changeEditorMenuBoundingRect = + changeEditorMenuRef.current?.getBoundingClientRect(); + const buttonRect = changeEditorButtonRef.current?.getBoundingClientRect(); + + if (changeEditorMenuBoundingRect && buttonRect) { + if (changeEditorMenuBoundingRect.y < MENU_MARGIN_FROM_APP_BORDER) { + if ( + buttonRect.right + maxChangeEditorMenuSize > + document.documentElement.clientWidth + ) { + setChangeEditorMenuPosition({ + ...changeEditorMenuPosition, + top: MENU_MARGIN_FROM_APP_BORDER + buttonRect.height, + bottom: 'auto', + }); + } else { + setChangeEditorMenuPosition({ + ...changeEditorMenuPosition, + top: MENU_MARGIN_FROM_APP_BORDER, + bottom: 'auto', + }); + } + } + } + } + }, [changeEditorMenuOpen, changeEditorMenuPosition]); + + const selectComponent = async (component: SNComponent | null) => { + if (component) { + if (component.conflictOf) { + application.changeAndSaveItem(component.uuid, (mutator) => { + mutator.conflictOf = undefined; + }); + } + } + + const transactions: TransactionalMutation[] = []; + + if (appState.getActiveNoteController()?.isTemplateNote) { + await appState.getActiveNoteController().insertTemplatedNote(); + } + + if (note.locked) { + application.alertService.alert(STRING_EDIT_LOCKED_ATTEMPT); + return; + } + + if (!component) { + if (!note.prefersPlainEditor) { + transactions.push({ + itemUuid: note.uuid, + mutate: (m: ItemMutator) => { + const noteMutator = m as NoteMutator; + noteMutator.prefersPlainEditor = true; + }, + }); + } + const currentEditor = application.componentManager.editorForNote(note); + if (currentEditor?.isExplicitlyEnabledForItem(note.uuid)) { + transactions.push( + transactionForDisassociateComponentWithCurrentNote( + currentEditor, + note + ) + ); + } + reloadFont(application.getPreference(PrefKey.EditorMonospaceEnabled)); + } else if (component.area === ComponentArea.Editor) { + const currentEditor = application.componentManager.editorForNote(note); + if (currentEditor && component.uuid !== currentEditor.uuid) { + transactions.push( + transactionForDisassociateComponentWithCurrentNote( + currentEditor, + note + ) + ); + } + const prefersPlain = note.prefersPlainEditor; + if (prefersPlain) { + transactions.push({ + itemUuid: note.uuid, + mutate: (m: ItemMutator) => { + const noteMutator = m as NoteMutator; + noteMutator.prefersPlainEditor = false; + }, + }); + } + transactions.push( + transactionForAssociateComponentWithCurrentNote(component, note) + ); + } + + await application.runTransactionalMutations(transactions); + /** Dirtying can happen above */ + application.sync(); + + setSelectedEditor(application.componentManager.editorForNote(note)); + }; + + return ( + + { + if (event.key === KeyboardKey.Escape) { + setChangeEditorMenuOpen(false); + } + }} + onBlur={closeOnBlur} + ref={changeEditorButtonRef} + className="sn-dropdown-item justify-between" + > +
+ + Change editor +
+ +
+ { + if (event.key === KeyboardKey.Escape) { + setChangeEditorMenuOpen(false); + changeEditorButtonRef.current?.focus(); + } + }} + style={{ + ...changeEditorMenuPosition, + position: 'fixed', + }} + className="sn-dropdown flex flex-col py-1 max-h-120 min-w-68 fixed overflow-y-auto" + > + + + + +
+ ); +}; diff --git a/app/assets/javascripts/components/NotesOptions.tsx b/app/assets/javascripts/components/NotesOptions/NotesOptions.tsx similarity index 85% rename from app/assets/javascripts/components/NotesOptions.tsx rename to app/assets/javascripts/components/NotesOptions/NotesOptions.tsx index 0525d75c8..d83b6c5a2 100644 --- a/app/assets/javascripts/components/NotesOptions.tsx +++ b/app/assets/javascripts/components/NotesOptions/NotesOptions.tsx @@ -1,6 +1,6 @@ import { AppState } from '@/ui_models/app_state'; -import { Icon } from './Icon'; -import { Switch } from './Switch'; +import { Icon } from '../Icon'; +import { Switch } from '../Switch'; import { observer } from 'mobx-react-lite'; import { useRef, useState, useEffect, useMemo } from 'preact/hooks'; import { @@ -8,12 +8,17 @@ import { DisclosureButton, DisclosurePanel, } from '@reach/disclosure'; -import { SNApplication, SNNote } from '@standardnotes/snjs/dist/@types'; +import { SNApplication, SNNote } from '@standardnotes/snjs'; import { WebApplication } from '@/ui_models/application'; import { KeyboardModifier } from '@/services/ioService'; import { FunctionComponent } from 'preact'; +import { ChangeEditorOption } from './ChangeEditorOption'; +import { + MENU_MARGIN_FROM_APP_BORDER, + MAX_MENU_SIZE_MULTIPLIER, +} from '@/views/constants'; -type Props = { +export type NotesOptionsProps = { application: WebApplication; appState: AppState; closeOnBlur: (event: { relatedTarget: EventTarget | null }) => void; @@ -21,7 +26,7 @@ type Props = { }; type DeletePermanentlyButtonProps = { - closeOnBlur: Props['closeOnBlur']; + closeOnBlur: NotesOptionsProps['closeOnBlur']; onClick: () => void; }; @@ -86,7 +91,10 @@ const formatDate = (date: Date | undefined) => { return `${date.toDateString()} ${date.toLocaleTimeString()}`; }; -const NoteAttributes: FunctionComponent<{ application: SNApplication, note: SNNote }> = ({ application, note }) => { +const NoteAttributes: FunctionComponent<{ + application: SNApplication; + note: SNNote; +}> = ({ application, note }) => { const { words, characters, paragraphs } = useMemo( () => countNoteAttributes(note.text), [note.text] @@ -136,15 +144,19 @@ const NoteAttributes: FunctionComponent<{ application: SNApplication, note: SNNo }; const SpellcheckOptions: FunctionComponent<{ - appState: AppState, note: SNNote + appState: AppState; + note: SNNote; }> = ({ appState, note }) => { - const editor = appState.application.componentManager.editorForNote(note); const spellcheckControllable = Boolean( !editor || - appState.application.getFeature(editor.identifier)?.spellcheckControl + appState.application.getFeature(editor.identifier)?.spellcheckControl ); - const noteSpellcheck = !spellcheckControllable ? true : note ? appState.notes.getSpellcheckStateForNote(note) : undefined; + const noteSpellcheck = !spellcheckControllable + ? true + : note + ? appState.notes.getSpellcheckStateForNote(note) + : undefined; return (
@@ -157,19 +169,26 @@ const SpellcheckOptions: FunctionComponent<{ }} > - + Spellcheck {!spellcheckControllable && ( -

Spellcheck cannot be controlled for this editor.

+

+ Spellcheck cannot be controlled for this editor. +

)}
); }; export const NotesOptions = observer( - ({ application, appState, closeOnBlur, onSubmenuChange }: Props) => { + ({ + application, + appState, + closeOnBlur, + onSubmenuChange, + }: NotesOptionsProps) => { const [tagsMenuOpen, setTagsMenuOpen] = useState(false); const [tagsMenuPosition, setTagsMenuPosition] = useState<{ top: number; @@ -232,25 +251,39 @@ export const NotesOptions = observer( const defaultFontSize = window.getComputedStyle( document.documentElement ).fontSize; - const maxTagsMenuSize = parseFloat(defaultFontSize) * 30; + const maxTagsMenuSize = + parseFloat(defaultFontSize) * MAX_MENU_SIZE_MULTIPLIER; const { clientWidth, clientHeight } = document.documentElement; - const buttonRect = tagsButtonRef.current!.getBoundingClientRect(); - const footerHeight = 32; + const buttonRect = tagsButtonRef.current?.getBoundingClientRect(); + const footerElementRect = document + .getElementById('footer-bar') + ?.getBoundingClientRect(); + const footerHeightInPx = footerElementRect?.height; - if (buttonRect.top + maxTagsMenuSize > clientHeight - footerHeight) { - setTagsMenuMaxHeight(clientHeight - buttonRect.top - footerHeight - 2); - } + if (buttonRect && footerHeightInPx) { + if ( + buttonRect.top + maxTagsMenuSize > + clientHeight - footerHeightInPx + ) { + setTagsMenuMaxHeight( + clientHeight - + buttonRect.top - + footerHeightInPx - + MENU_MARGIN_FROM_APP_BORDER + ); + } - if (buttonRect.right + maxTagsMenuSize > clientWidth) { - setTagsMenuPosition({ - top: buttonRect.top, - right: clientWidth - buttonRect.left, - }); - } else { - setTagsMenuPosition({ - top: buttonRect.top, - left: buttonRect.right, - }); + if (buttonRect.right + maxTagsMenuSize > clientWidth) { + setTagsMenuPosition({ + top: buttonRect.top, + right: clientWidth - buttonRect.left, + }); + } else { + setTagsMenuPosition({ + top: buttonRect.top, + left: buttonRect.right, + }); + } } setTagsMenuOpen(!tagsMenuOpen); @@ -360,7 +393,7 @@ export const NotesOptions = observer( onKeyDown={(event) => { if (event.key === 'Escape') { setTagsMenuOpen(false); - tagsButtonRef.current!.focus(); + tagsButtonRef.current?.focus(); } }} style={{ @@ -383,9 +416,10 @@ export const NotesOptions = observer( > {tag.title} @@ -516,16 +550,18 @@ export const NotesOptions = observer( )} - - {notes.length === 1 ? ( <>
- - - + +
+
- ) : null} diff --git a/app/assets/javascripts/components/NotesOptions/changeEditor/EditorAccordionMenu.tsx b/app/assets/javascripts/components/NotesOptions/changeEditor/EditorAccordionMenu.tsx new file mode 100644 index 000000000..b18ffe129 --- /dev/null +++ b/app/assets/javascripts/components/NotesOptions/changeEditor/EditorAccordionMenu.tsx @@ -0,0 +1,256 @@ +import { Icon } from '@/components/Icon'; +import { usePremiumModal } from '@/components/Premium'; +import { KeyboardKey } from '@/services/ioService'; +import { WebApplication } from '@/ui_models/application'; +import { SNComponent } from '@standardnotes/snjs'; +import { Fragment, FunctionComponent } from 'preact'; +import { useCallback, useEffect, useRef, useState } from 'preact/hooks'; +import { EditorMenuItem, EditorMenuGroup } from '../ChangeEditorOption'; +import { PLAIN_EDITOR_NAME } from './createEditorMenuGroups'; + +type EditorAccordionMenuProps = { + application: WebApplication; + closeOnBlur: (event: { relatedTarget: EventTarget | null }) => void; + groups: EditorMenuGroup[]; + isOpen: boolean; + selectComponent: (component: SNComponent | null) => Promise; + selectedEditor: SNComponent | undefined; +}; + +const getGroupId = (group: EditorMenuGroup) => + group.title.toLowerCase().replace(/\s/, '-'); + +const getGroupBtnId = (groupId: string) => groupId + '-button'; + +export const EditorAccordionMenu: FunctionComponent< + EditorAccordionMenuProps +> = ({ + application, + closeOnBlur, + groups, + isOpen, + selectComponent, + selectedEditor, +}) => { + const [activeGroupId, setActiveGroupId] = useState(''); + const menuItemRefs = useRef<(HTMLButtonElement | null)[]>([]); + const [focusedItemIndex, setFocusedItemIndex] = useState(); + const premiumModal = usePremiumModal(); + + const isSelectedEditor = useCallback( + (item: EditorMenuItem) => { + if (selectedEditor) { + if (item?.component?.identifier === selectedEditor.identifier) { + return true; + } + } else if (item.name === PLAIN_EDITOR_NAME) { + return true; + } + return false; + }, + [selectedEditor] + ); + + useEffect(() => { + const activeGroup = groups.find((group) => { + return group.items.some(isSelectedEditor); + }); + + if (activeGroup) { + const newActiveGroupId = getGroupId(activeGroup); + setActiveGroupId(newActiveGroupId); + } + }, [groups, selectedEditor, isSelectedEditor]); + + useEffect(() => { + if ( + typeof focusedItemIndex === 'undefined' && + activeGroupId.length && + menuItemRefs.current.length + ) { + const activeGroupIndex = menuItemRefs.current.findIndex( + (item) => item?.id === getGroupBtnId(activeGroupId) + ); + setFocusedItemIndex(activeGroupIndex); + } + }, [activeGroupId, focusedItemIndex]); + + useEffect(() => { + if ( + typeof focusedItemIndex === 'number' && + focusedItemIndex > -1 && + isOpen + ) { + const focusedItem = menuItemRefs.current[focusedItemIndex]; + const containingGroupId = focusedItem?.closest( + '[data-accordion-group]' + )?.id; + if ( + !focusedItem?.id && + containingGroupId && + containingGroupId !== activeGroupId + ) { + setActiveGroupId(containingGroupId); + } + focusedItem?.focus(); + } + }, [activeGroupId, focusedItemIndex, isOpen]); + + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + switch (e.key) { + case KeyboardKey.Up: { + if ( + typeof focusedItemIndex === 'number' && + menuItemRefs.current.length + ) { + let previousItemIndex = focusedItemIndex - 1; + if (previousItemIndex < 0) { + previousItemIndex = menuItemRefs.current.length - 1; + } + setFocusedItemIndex(previousItemIndex); + } + e.preventDefault(); + break; + } + case KeyboardKey.Down: { + if ( + typeof focusedItemIndex === 'number' && + menuItemRefs.current.length + ) { + let nextItemIndex = focusedItemIndex + 1; + if (nextItemIndex > menuItemRefs.current.length - 1) { + nextItemIndex = 0; + } + setFocusedItemIndex(nextItemIndex); + } + e.preventDefault(); + break; + } + } + }; + + document.addEventListener('keydown', handleKeyDown); + + return () => { + document.removeEventListener('keydown', handleKeyDown); + }; + }, [focusedItemIndex, groups]); + + const selectEditor = (item: EditorMenuItem) => { + if (item.component) { + selectComponent(item.component); + } else if (item.isPremiumFeature) { + premiumModal.activate(item.name); + } else { + selectComponent(null); + } + }; + + return ( + <> + {groups.map((group) => { + const groupId = getGroupId(group); + const buttonId = getGroupBtnId(groupId); + const contentId = `${groupId}-content`; + + if (!group.items || !group.items.length) { + return null; + } + + return ( + +
+

+ +

+
+
+ {group.items.map((item) => { + return ( + + ); + })} +
+
+
+
+
+ ); + })} + + ); +}; diff --git a/app/assets/javascripts/components/NotesOptions/changeEditor/createEditorMenuGroups.ts b/app/assets/javascripts/components/NotesOptions/changeEditor/createEditorMenuGroups.ts new file mode 100644 index 000000000..6df9d95c6 --- /dev/null +++ b/app/assets/javascripts/components/NotesOptions/changeEditor/createEditorMenuGroups.ts @@ -0,0 +1,128 @@ +import { + ComponentArea, + FeatureDescription, + Features, + NoteType, +} from '@standardnotes/features'; +import { ContentType, SNComponent } from '@standardnotes/snjs'; +import { EditorMenuItem, EditorMenuGroup } from '../ChangeEditorOption'; + +/** @todo Implement interchangeable alert */ + +export const PLAIN_EDITOR_NAME = 'Plain Editor'; + +type EditorGroup = NoteType | 'plain' | 'others'; + +const getEditorGroup = ( + featureDescription: FeatureDescription +): EditorGroup => { + if (featureDescription.note_type) { + return featureDescription.note_type; + } else if (featureDescription.file_type) { + switch (featureDescription.file_type) { + case 'txt': + return 'plain'; + case 'html': + return NoteType.RichText; + case 'md': + return NoteType.Markdown; + default: + return 'others'; + } + } + return 'others'; +}; + +export const createEditorMenuGroups = (editors: SNComponent[]) => { + const editorItems: Record = { + plain: [ + { + name: PLAIN_EDITOR_NAME, + }, + ], + 'rich-text': [], + markdown: [], + task: [], + code: [], + spreadsheet: [], + authentication: [], + others: [], + }; + + Features.filter( + (feature) => + feature.content_type === ContentType.Component && + feature.area === ComponentArea.Editor + ).forEach((editorFeature) => { + if ( + !editors.find((editor) => editor.identifier === editorFeature.identifier) + ) { + editorItems[getEditorGroup(editorFeature)].push({ + name: editorFeature.name as string, + isPremiumFeature: true, + }); + } + }); + + editors.forEach((editor) => { + const editorItem: EditorMenuItem = { + name: editor.name, + component: editor, + }; + + editorItems[getEditorGroup(editor.package_info)].push(editorItem); + }); + + const editorMenuGroups: EditorMenuGroup[] = [ + { + icon: 'plain-text', + iconClassName: 'color-accessory-tint-1', + title: 'Plain text', + items: editorItems.plain, + }, + { + icon: 'rich-text', + iconClassName: 'color-accessory-tint-1', + title: 'Rich text', + items: editorItems['rich-text'], + }, + { + icon: 'markdown', + iconClassName: 'color-accessory-tint-2', + title: 'Markdown text', + items: editorItems.markdown, + }, + { + icon: 'tasks', + iconClassName: 'color-accessory-tint-3', + title: 'Todo', + items: editorItems.task, + }, + { + icon: 'code', + iconClassName: 'color-accessory-tint-4', + title: 'Code', + items: editorItems.code, + }, + { + icon: 'spreadsheets', + iconClassName: 'color-accessory-tint-5', + title: 'Spreadsheet', + items: editorItems.spreadsheet, + }, + { + icon: 'authenticator', + iconClassName: 'color-accessory-tint-6', + title: 'Authentication', + items: editorItems.authentication, + }, + { + icon: 'editor', + iconClassName: 'color-neutral', + title: 'Others', + items: editorItems.others, + }, + ]; + + return editorMenuGroups; +}; diff --git a/app/assets/javascripts/components/NotesOptionsPanel.tsx b/app/assets/javascripts/components/NotesOptionsPanel.tsx index 923c2fb43..7b06fa2a1 100644 --- a/app/assets/javascripts/components/NotesOptionsPanel.tsx +++ b/app/assets/javascripts/components/NotesOptionsPanel.tsx @@ -9,7 +9,7 @@ import { } from '@reach/disclosure'; import { useRef, useState } from 'preact/hooks'; import { observer } from 'mobx-react-lite'; -import { NotesOptions } from './NotesOptions'; +import { NotesOptions } from './NotesOptions/NotesOptions'; import { WebApplication } from '@/ui_models/application'; type Props = { @@ -17,76 +17,85 @@ type Props = { appState: AppState; }; -export const NotesOptionsPanel = observer(({ application, appState }: Props) => { - const [open, setOpen] = useState(false); - const [position, setPosition] = useState({ - top: 0, - right: 0, - }); - const [maxHeight, setMaxHeight] = useState('auto'); - const buttonRef = useRef(null); - const panelRef = useRef(null); - const [closeOnBlur] = useCloseOnBlur(panelRef as any, setOpen); - const [submenuOpen, setSubmenuOpen] = useState(false); +export const NotesOptionsPanel = observer( + ({ application, appState }: Props) => { + const [open, setOpen] = useState(false); + const [position, setPosition] = useState({ + top: 0, + right: 0, + }); + const [maxHeight, setMaxHeight] = useState('auto'); + const buttonRef = useRef(null); + const panelRef = useRef(null); + const [closeOnBlur] = useCloseOnBlur(panelRef, setOpen); + const [submenuOpen, setSubmenuOpen] = useState(false); - const onSubmenuChange = (open: boolean) => { - setSubmenuOpen(open); - }; + const onSubmenuChange = (open: boolean) => { + setSubmenuOpen(open); + }; - return ( - { - const rect = buttonRef.current!.getBoundingClientRect(); - const { clientHeight } = document.documentElement; - const footerHeight = 32; - setMaxHeight(clientHeight - rect.bottom - footerHeight - 2); - setPosition({ - top: rect.bottom, - right: document.body.clientWidth - rect.right, - }); - setOpen(!open); - }} - > - { - if (event.key === 'Escape' && !submenuOpen) { - setOpen(false); + return ( + { + const rect = buttonRef.current?.getBoundingClientRect(); + if (rect) { + const { clientHeight } = document.documentElement; + const footerElementRect = document + .getElementById('footer-bar') + ?.getBoundingClientRect(); + const footerHeightInPx = footerElementRect?.height; + if (footerHeightInPx) { + setMaxHeight(clientHeight - rect.bottom - footerHeightInPx - 2); + } + setPosition({ + top: rect.bottom, + right: document.body.clientWidth - rect.right, + }); + setOpen(!open); } }} - onBlur={closeOnBlur} - ref={buttonRef} - className="sn-icon-button" > - Actions - - - { - if (event.key === 'Escape' && !submenuOpen) { - setOpen(false); - buttonRef.current!.focus(); - } - }} - ref={panelRef} - style={{ - ...position, - maxHeight, - }} - className="sn-dropdown sn-dropdown--animated min-w-80 max-h-120 max-w-xs flex flex-col py-2 overflow-y-auto fixed" - onBlur={closeOnBlur} - > - {open && ( - - )} - - - ); -}); + { + if (event.key === 'Escape' && !submenuOpen) { + setOpen(false); + } + }} + onBlur={closeOnBlur} + ref={buttonRef} + className="sn-icon-button" + > + Actions + + + { + if (event.key === 'Escape' && !submenuOpen) { + setOpen(false); + buttonRef.current?.focus(); + } + }} + ref={panelRef} + style={{ + ...position, + maxHeight, + }} + className="sn-dropdown sn-dropdown--animated min-w-80 max-h-120 max-w-xs flex flex-col py-2 overflow-y-auto fixed" + onBlur={closeOnBlur} + > + {open && ( + + )} + + + ); + } +); export const NotesOptionsPanelDirective = toDirective(NotesOptionsPanel); diff --git a/app/assets/javascripts/components/utils.ts b/app/assets/javascripts/components/utils.ts index 5c0d1adb4..5f2c29517 100644 --- a/app/assets/javascripts/components/utils.ts +++ b/app/assets/javascripts/components/utils.ts @@ -8,7 +8,7 @@ import { StateUpdater, useCallback, useState, useEffect } from 'preact/hooks'; * monitored. */ export function useCloseOnBlur( - container: { current?: HTMLDivElement }, + container: { current?: HTMLDivElement | null }, setOpen: (open: boolean) => void ): [ (event: { relatedTarget: EventTarget | null }) => void, diff --git a/app/assets/javascripts/directives/views/editorMenu.ts b/app/assets/javascripts/directives/views/editorMenu.ts deleted file mode 100644 index 646e943e9..000000000 --- a/app/assets/javascripts/directives/views/editorMenu.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { WebDirective } from './../../types'; -import { WebApplication } from '@/ui_models/application'; -import { SNComponent, SNItem, ComponentArea } from '@standardnotes/snjs'; -import { isDesktopApplication } from '@/utils'; -import template from '%/directives/editor-menu.pug'; -import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl'; - -interface EditorMenuScope { - callback: (component: SNComponent) => void; - selectedEditorUuid: string; - currentItem: SNItem; - application: WebApplication; -} - -class EditorMenuCtrl extends PureViewCtrl implements EditorMenuScope { - callback!: () => (component: SNComponent) => void; - selectedEditorUuid!: string; - currentItem!: SNItem; - application!: WebApplication; - - /* @ngInject */ - constructor($timeout: ng.ITimeoutService) { - super($timeout); - this.state = { - isDesktop: isDesktopApplication(), - }; - } - - public isEditorSelected(editor: SNComponent) { - if (!this.selectedEditorUuid) { - return false; - } - return this.selectedEditorUuid === editor.uuid; - } - - $onInit() { - super.$onInit(); - const editors = this.application.componentManager - .componentsForArea(ComponentArea.Editor) - .sort((a, b) => { - return a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1; - }); - this.setState({ - editors: editors, - }); - } - - selectComponent(component: SNComponent) { - if (component) { - if (component.conflictOf) { - this.application.changeAndSaveItem(component.uuid, (mutator) => { - mutator.conflictOf = undefined; - }); - } - } - this.$timeout(() => { - this.callback()(component); - }); - } - - offlineAvailableForComponent(component: SNComponent) { - return component.local_url && this.state.isDesktop; - } -} - -export class EditorMenu extends WebDirective { - constructor() { - super(); - this.restrict = 'E'; - this.template = template; - this.controller = EditorMenuCtrl; - this.controllerAs = 'self'; - this.bindToController = true; - this.scope = { - callback: '&', - selectedEditorUuid: '=', - currentItem: '=', - application: '=', - }; - } -} diff --git a/app/assets/javascripts/directives/views/index.ts b/app/assets/javascripts/directives/views/index.ts index c66459c2b..88c4d2818 100644 --- a/app/assets/javascripts/directives/views/index.ts +++ b/app/assets/javascripts/directives/views/index.ts @@ -1,5 +1,4 @@ export { ActionsMenu } from './actionsMenu'; -export { EditorMenu } from './editorMenu'; export { InputModal } from './inputModal'; export { MenuRow } from './menuRow'; export { PanelResizer } from './panelResizer'; diff --git a/app/assets/javascripts/services/ioService.ts b/app/assets/javascripts/services/ioService.ts index 315c892fa..c4885ec37 100644 --- a/app/assets/javascripts/services/ioService.ts +++ b/app/assets/javascripts/services/ioService.ts @@ -5,6 +5,7 @@ export enum KeyboardKey { Up = 'ArrowUp', Down = 'ArrowDown', Enter = 'Enter', + Escape = 'Escape', } export enum KeyboardModifier { diff --git a/app/assets/javascripts/ui_models/app_state/notes_state.ts b/app/assets/javascripts/ui_models/app_state/notes_state.ts index 6f141ad30..df192d03a 100644 --- a/app/assets/javascripts/ui_models/app_state/notes_state.ts +++ b/app/assets/javascripts/ui_models/app_state/notes_state.ts @@ -1,6 +1,7 @@ import { confirmDialog } from '@/services/alertService'; import { KeyboardModifier } from '@/services/ioService'; import { StringEmptyTrash, Strings, StringUtils } from '@/strings'; +import { MENU_MARGIN_FROM_APP_BORDER } from '@/views/constants'; import { UuidString, SNNote, @@ -205,32 +206,39 @@ export class NotesState { document.documentElement ).fontSize; const maxContextMenuHeight = parseFloat(defaultFontSize) * 30; - const footerHeight = 32; + const footerElementRect = document + .getElementById('footer-bar') + ?.getBoundingClientRect(); + const footerHeightInPx = footerElementRect?.height; // Open up-bottom is default behavior let openUpBottom = true; - const bottomSpace = - clientHeight - footerHeight - this.contextMenuClickLocation.y; - const upSpace = this.contextMenuClickLocation.y; + if (footerHeightInPx) { + const bottomSpace = + clientHeight - footerHeightInPx - this.contextMenuClickLocation.y; + const upSpace = this.contextMenuClickLocation.y; - // If not enough space to open up-bottom - if (maxContextMenuHeight > bottomSpace) { - // If there's enough space, open bottom-up - if (upSpace > maxContextMenuHeight) { - openUpBottom = false; - this.setContextMenuMaxHeight('auto'); - // Else, reduce max height (menu will be scrollable) and open in whichever direction there's more space - } else { - if (upSpace > bottomSpace) { - this.setContextMenuMaxHeight(upSpace - 2); + // If not enough space to open up-bottom + if (maxContextMenuHeight > bottomSpace) { + // If there's enough space, open bottom-up + if (upSpace > maxContextMenuHeight) { openUpBottom = false; + this.setContextMenuMaxHeight('auto'); + // Else, reduce max height (menu will be scrollable) and open in whichever direction there's more space } else { - this.setContextMenuMaxHeight(bottomSpace - 2); + if (upSpace > bottomSpace) { + this.setContextMenuMaxHeight(upSpace - MENU_MARGIN_FROM_APP_BORDER); + openUpBottom = false; + } else { + this.setContextMenuMaxHeight( + bottomSpace - MENU_MARGIN_FROM_APP_BORDER + ); + } } + } else { + this.setContextMenuMaxHeight('auto'); } - } else { - this.setContextMenuMaxHeight('auto'); } if (openUpBottom) { diff --git a/app/assets/javascripts/views/constants.ts b/app/assets/javascripts/views/constants.ts index a84c79f91..fc5422b31 100644 --- a/app/assets/javascripts/views/constants.ts +++ b/app/assets/javascripts/views/constants.ts @@ -2,3 +2,5 @@ export const PANEL_NAME_NOTES = 'notes'; export const PANEL_NAME_NAVIGATION = 'navigation'; export const EMAIL_REGEX = /^([a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?)$/; +export const MENU_MARGIN_FROM_APP_BORDER = 5; +export const MAX_MENU_SIZE_MULTIPLIER = 30; diff --git a/app/assets/javascripts/views/note_view/note-view.pug b/app/assets/javascripts/views/note_view/note-view.pug index 1c851734f..a40f29e08 100644 --- a/app/assets/javascripts/views/note_view/note-view.pug +++ b/app/assets/javascripts/views/note_view/note-view.pug @@ -63,20 +63,6 @@ .sn-component(ng-if='self.note') #editor-menu-bar.sk-app-bar.no-edges .left - .sk-app-bar-item( - click-outside=`self.setMenuState('showEditorMenu', false)` - is-open='self.state.showEditorMenu', - ng-class="{'selected' : self.state.showEditorMenu}", - ng-click="self.toggleMenu('showEditorMenu')" - ) - .sk-label Editor - editor-menu( - callback='self.editorMenuOnSelect', - current-item='self.note', - ng-if='self.state.showEditorMenu', - selected-editor-uuid='self.state.editorComponentViewer && self.state.editorComponentViewer.component.uuid', - application='self.application' - ) .sk-app-bar-item( click-outside=`self.setMenuState('showActionsMenu', false)`, is-open='self.state.showActionsMenu', diff --git a/app/assets/javascripts/views/note_view/note_view.ts b/app/assets/javascripts/views/note_view/note_view.ts index ad3bdfdf1..62a5c1251 100644 --- a/app/assets/javascripts/views/note_view/note_view.ts +++ b/app/assets/javascripts/views/note_view/note_view.ts @@ -8,7 +8,6 @@ import { ContentType, SNComponent, SNNote, - NoteMutator, ComponentArea, PrefKey, ComponentMutator, @@ -28,7 +27,6 @@ import { EventSource } from '@/ui_models/app_state'; import { STRING_DELETE_PLACEHOLDER_ATTEMPT, STRING_DELETE_LOCKED_ATTEMPT, - STRING_EDIT_LOCKED_ATTEMPT, StringDeleteNote, } from '@/strings'; import { confirmDialog } from '@/services/alertService'; @@ -59,7 +57,6 @@ type EditorState = { isDesktop?: boolean; syncTakingTooLong: boolean; showActionsMenu: boolean; - showEditorMenu: boolean; showHistoryMenu: boolean; spellcheck: boolean; /** Setting to true then false will allow the main content textarea to be destroyed @@ -83,6 +80,46 @@ function sortAlphabetically(array: SNComponent[]): SNComponent[] { ); } +export const transactionForAssociateComponentWithCurrentNote = ( + component: SNComponent, + note: SNNote +) => { + const transaction: TransactionalMutation = { + itemUuid: component.uuid, + mutate: (m: ItemMutator) => { + const mutator = m as ComponentMutator; + mutator.removeDisassociatedItemId(note.uuid); + mutator.associateWithItem(note.uuid); + }, + }; + return transaction; +}; + +export const transactionForDisassociateComponentWithCurrentNote = ( + component: SNComponent, + note: SNNote +) => { + const transaction: TransactionalMutation = { + itemUuid: component.uuid, + mutate: (m: ItemMutator) => { + const mutator = m as ComponentMutator; + mutator.removeAssociatedItemId(note.uuid); + mutator.disassociateWithItem(note.uuid); + }, + }; + return transaction; +}; + +export const reloadFont = (monospaceFont?: boolean) => { + const root = document.querySelector(':root') as HTMLElement; + const propertyName = '--sn-stylekit-editor-font-family'; + if (monospaceFont) { + root.style.setProperty(propertyName, 'var(--sn-stylekit-monospace-font)'); + } else { + root.style.setProperty(propertyName, 'var(--sn-stylekit-sans-serif-font)'); + } +}; + export class NoteView extends PureViewCtrl { /** Passed through template */ readonly application!: WebApplication; @@ -114,7 +151,6 @@ export class NoteView extends PureViewCtrl { onReady: () => this.reloadPreferences(), }; - this.editorMenuOnSelect = this.editorMenuOnSelect.bind(this); this.onPanelResizeFinish = this.onPanelResizeFinish.bind(this); this.setScrollPosition = this.setScrollPosition.bind(this); this.resetScrollPosition = this.resetScrollPosition.bind(this); @@ -146,7 +182,6 @@ export class NoteView extends PureViewCtrl { this.onEditorComponentLoad = undefined; this.statusTimeout = undefined; (this.onPanelResizeFinish as unknown) = undefined; - (this.editorMenuOnSelect as unknown) = undefined; super.deinit(); } @@ -248,7 +283,6 @@ export class NoteView extends PureViewCtrl { spellcheck: true, syncTakingTooLong: false, showActionsMenu: false, - showEditorMenu: false, showHistoryMenu: false, noteStatus: undefined, textareaUnloading: false, @@ -441,7 +475,7 @@ export class NoteView extends PureViewCtrl { editorStateDidLoad: true, }); } - this.reloadFont(); + reloadFont(this.state.monospaceFont); } else { await this.setState({ editorStateDidLoad: true, @@ -462,7 +496,7 @@ export class NoteView extends PureViewCtrl { } closeAllMenus(exclude?: string) { - const allMenus = ['showEditorMenu', 'showActionsMenu', 'showHistoryMenu']; + const allMenus = ['showActionsMenu', 'showHistoryMenu']; const menuState: any = {}; for (const candidate of allMenus) { if (candidate !== exclude) { @@ -472,69 +506,6 @@ export class NoteView extends PureViewCtrl { this.setState(menuState); } - async editorMenuOnSelect(component?: SNComponent) { - const transactions: TransactionalMutation[] = []; - - this.setMenuState('showEditorMenu', false); - - if (this.appState.getActiveNoteController()?.isTemplateNote) { - await this.appState.getActiveNoteController().insertTemplatedNote(); - } - - if (this.note.locked) { - this.application.alertService.alert(STRING_EDIT_LOCKED_ATTEMPT); - return; - } - - if (!component) { - if (!this.note.prefersPlainEditor) { - transactions.push({ - itemUuid: this.note.uuid, - mutate: (m: ItemMutator) => { - const noteMutator = m as NoteMutator; - noteMutator.prefersPlainEditor = true; - }, - }); - } - if ( - this.state.editorComponentViewer?.component.isExplicitlyEnabledForItem( - this.note.uuid - ) - ) { - transactions.push( - this.transactionForDisassociateComponentWithCurrentNote( - this.state.editorComponentViewer.component - ) - ); - } - this.reloadFont(); - } else if (component.area === ComponentArea.Editor) { - const currentEditor = this.state.editorComponentViewer?.component; - if (currentEditor && component.uuid !== currentEditor.uuid) { - transactions.push( - this.transactionForDisassociateComponentWithCurrentNote(currentEditor) - ); - } - const prefersPlain = this.note.prefersPlainEditor; - if (prefersPlain) { - transactions.push({ - itemUuid: this.note.uuid, - mutate: (m: ItemMutator) => { - const noteMutator = m as NoteMutator; - noteMutator.prefersPlainEditor = false; - }, - }); - } - transactions.push( - this.transactionForAssociateComponentWithCurrentNote(component) - ); - } - - await this.application.runTransactionalMutations(transactions); - /** Dirtying can happen above */ - this.application.sync(); - } - hasAvailableExtensions() { return ( this.application.actionsManager.extensionsInContextOfItem(this.note) @@ -702,7 +673,7 @@ export class NoteView extends PureViewCtrl { if (spellcheck !== this.state.spellcheck) { await this.setState({ textareaUnloading: true }); await this.setState({ textareaUnloading: false }); - this.reloadFont(); + reloadFont(this.state.monospaceFont); await this.setState({ spellcheck, @@ -733,7 +704,7 @@ export class NoteView extends PureViewCtrl { return; } - this.reloadFont(); + reloadFont(this.state.monospaceFont); if ( this.state.marginResizersEnabled && @@ -753,19 +724,6 @@ export class NoteView extends PureViewCtrl { } } - reloadFont() { - const root = document.querySelector(':root') as HTMLElement; - const propertyName = '--sn-stylekit-editor-font-family'; - if (this.state.monospaceFont) { - root.style.setProperty(propertyName, 'var(--sn-stylekit-monospace-font)'); - } else { - root.style.setProperty( - propertyName, - 'var(--sn-stylekit-sans-serif-font)' - ); - } - } - /** @components */ registerComponentManagerEventObserver() { @@ -844,42 +802,16 @@ export class NoteView extends PureViewCtrl { async disassociateComponentWithCurrentNote(component: SNComponent) { return this.application.runTransactionalMutation( - this.transactionForDisassociateComponentWithCurrentNote(component) + transactionForDisassociateComponentWithCurrentNote(component, this.note) ); } - transactionForDisassociateComponentWithCurrentNote(component: SNComponent) { - const note = this.note; - const transaction: TransactionalMutation = { - itemUuid: component.uuid, - mutate: (m: ItemMutator) => { - const mutator = m as ComponentMutator; - mutator.removeAssociatedItemId(note.uuid); - mutator.disassociateWithItem(note.uuid); - }, - }; - return transaction; - } - async associateComponentWithCurrentNote(component: SNComponent) { return this.application.runTransactionalMutation( - this.transactionForAssociateComponentWithCurrentNote(component) + transactionForAssociateComponentWithCurrentNote(component, this.note) ); } - transactionForAssociateComponentWithCurrentNote(component: SNComponent) { - const note = this.note; - const transaction: TransactionalMutation = { - itemUuid: component.uuid, - mutate: (m: ItemMutator) => { - const mutator = m as ComponentMutator; - mutator.removeDisassociatedItemId(note.uuid); - mutator.associateWithItem(note.uuid); - }, - }; - return transaction; - } - registerKeyboardShortcuts() { this.removeTrashKeyObserver = this.application.io.addKeyObserver({ key: KeyboardKey.Backspace, diff --git a/app/assets/stylesheets/_sn.scss b/app/assets/stylesheets/_sn.scss index 3cbcf3f1a..21b268ba4 100644 --- a/app/assets/stylesheets/_sn.scss +++ b/app/assets/stylesheets/_sn.scss @@ -249,6 +249,10 @@ margin-left: 0rem !important; } +.ml-0\.5 { + margin-left: 0.125rem; +} + .ml-3 { margin-left: 0.75rem; } @@ -374,6 +378,10 @@ min-width: 7.5rem; } +.min-w-68 { + min-width: 17rem; +} + .min-w-90 { min-width: 22.5rem; } @@ -687,9 +695,13 @@ @extend .h-4; @extend .border-2; @extend .border-solid; - @extend .border-info; @extend .rounded-full; @extend .relative; + border-color: var(--sn-stylekit-grey-1); + + &--checked { + @extend .border-info; + } } .pseudo-radio-btn--checked::after { @@ -819,3 +831,7 @@ cursor: default; pointer-events: none; } + +.hide-if-last-child:last-child { + display: none; +} diff --git a/app/assets/templates/directives/editor-menu.pug b/app/assets/templates/directives/editor-menu.pug deleted file mode 100644 index d537e81f3..000000000 --- a/app/assets/templates/directives/editor-menu.pug +++ /dev/null @@ -1,30 +0,0 @@ -.sn-component - .sk-menu-panel.dropdown-menu - .sk-menu-panel-section - .sk-menu-panel-header - .sk-menu-panel-header-title Note Editor - menu-row( - action='self.selectComponent(null)', - circle="!self.selectedEditorUuid && 'success'", - label="'Plain Editor'" - ) - menu-row( - ng-repeat='editor in self.state.editors track by editor.uuid' - action='self.selectComponent(editor)', - circle="self.isEditorSelected(editor) && 'success'", - label='editor.name', - subtitle="self.isEditorSelected(editor) && 'Version ' + editor.package_info.version", - ) - .sk-menu-panel-column( - ng-if='editor.conflictOf' - ) - .info( - ng-if='editor.conflictOf' - ) Conflicted copy - a.no-decoration( - href='https://standardnotes.com/plans', - ng-if='self.state.editors.length == 0', - rel='noopener', - target='blank' - ) - menu-row(label="'Download More Editors'") diff --git a/package.json b/package.json index 26f3007c4..a08ec4db5 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,7 @@ "@reach/tooltip": "^0.16.2", "@standardnotes/components": "1.4.4", "@standardnotes/features": "1.26.1", - "@standardnotes/snjs": "2.41.0", + "@standardnotes/snjs": "2.42.0", "@standardnotes/settings": "^1.10.0", "@standardnotes/sncrypto-web": "1.6.0", "mobx": "^6.3.5", diff --git a/yarn.lock b/yarn.lock index ba3abb492..a6ff4252e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2657,10 +2657,10 @@ buffer "^6.0.3" libsodium-wrappers "^0.7.9" -"@standardnotes/snjs@2.41.0": - version "2.41.0" - resolved "https://registry.yarnpkg.com/@standardnotes/snjs/-/snjs-2.41.0.tgz#48d02e981780a9d7d823f6186497a1c6459bbd66" - integrity sha512-Rnl5wWbMKTQ+bQb7zGK/7HFt43bMBlP06G5zoEE0Vj/uxyneLxaEX+iPNXRUDqeHfxvedtTnIXsLGFEux/G1dg== +"@standardnotes/snjs@2.42.0": + version "2.42.0" + resolved "https://registry.yarnpkg.com/@standardnotes/snjs/-/snjs-2.42.0.tgz#11e28210fdab5cfd464438ee3d7ab93b5edbe9c7" + integrity sha512-XjevxZeru5Ryo4c7N4u9dhaqRd+ybk+guCbBT6sBfICD67B8/8qpS0GgKjeF5BlGTDvWvB/ECXyZo1bQVkKtgw== dependencies: "@standardnotes/auth" "^3.15.3" "@standardnotes/common" "^1.8.0" From 0ecbde6bac0c93ef232fea9c9f7fcad037d739f1 Mon Sep 17 00:00:00 2001 From: Aman Harwara Date: Sun, 30 Jan 2022 20:28:35 +0530 Subject: [PATCH 08/65] feat: improve change editor menu keyboard navigation (#831) --- .../javascripts/components/NotesList.tsx | 7 +- .../NotesOptions/ChangeEditorOption.tsx | 2 +- .../changeEditor/EditorAccordionMenu.tsx | 151 ++++++++++-------- app/assets/javascripts/views/constants.ts | 5 + app/assets/stylesheets/_sn.scss | 5 + 5 files changed, 98 insertions(+), 72 deletions(-) diff --git a/app/assets/javascripts/components/NotesList.tsx b/app/assets/javascripts/components/NotesList.tsx index 56a0c6a33..abf742e29 100644 --- a/app/assets/javascripts/components/NotesList.tsx +++ b/app/assets/javascripts/components/NotesList.tsx @@ -6,6 +6,10 @@ import { SNNote } from '@standardnotes/snjs'; import { observer } from 'mobx-react-lite'; import { FunctionComponent } from 'preact'; import { NotesListItem } from './NotesListItem'; +import { + FOCUSABLE_BUT_NOT_TABBABLE, + NOTES_LIST_SCROLL_THRESHOLD, +} from '@/views/constants'; type Props = { application: WebApplication; @@ -16,9 +20,6 @@ type Props = { paginate: () => void; }; -const FOCUSABLE_BUT_NOT_TABBABLE = -1; -const NOTES_LIST_SCROLL_THRESHOLD = 200; - export const NotesList: FunctionComponent = observer( ({ application, diff --git a/app/assets/javascripts/components/NotesOptions/ChangeEditorOption.tsx b/app/assets/javascripts/components/NotesOptions/ChangeEditorOption.tsx index 0aacc230a..35422a18d 100644 --- a/app/assets/javascripts/components/NotesOptions/ChangeEditorOption.tsx +++ b/app/assets/javascripts/components/NotesOptions/ChangeEditorOption.tsx @@ -270,7 +270,7 @@ export const ChangeEditorOption: FunctionComponent = ({ ...changeEditorMenuPosition, position: 'fixed', }} - className="sn-dropdown flex flex-col py-1 max-h-120 min-w-68 fixed overflow-y-auto" + className="sn-dropdown flex flex-col py-0.5 max-h-120 min-w-68 fixed overflow-y-auto" > const getGroupBtnId = (groupId: string) => groupId + '-button'; +const isElementHidden = (element: Element) => !element.clientHeight; + export const EditorAccordionMenu: FunctionComponent< EditorAccordionMenuProps > = ({ @@ -34,7 +37,6 @@ export const EditorAccordionMenu: FunctionComponent< }) => { const [activeGroupId, setActiveGroupId] = useState(''); const menuItemRefs = useRef<(HTMLButtonElement | null)[]>([]); - const [focusedItemIndex, setFocusedItemIndex] = useState(); const premiumModal = usePremiumModal(); const isSelectedEditor = useCallback( @@ -64,78 +66,85 @@ export const EditorAccordionMenu: FunctionComponent< useEffect(() => { if ( - typeof focusedItemIndex === 'undefined' && - activeGroupId.length && - menuItemRefs.current.length + isOpen && + !menuItemRefs.current.some((btn) => btn === document.activeElement) ) { - const activeGroupIndex = menuItemRefs.current.findIndex( - (item) => item?.id === getGroupBtnId(activeGroupId) - ); - setFocusedItemIndex(activeGroupIndex); - } - }, [activeGroupId, focusedItemIndex]); + const selectedEditor = groups + .map((group) => group.items) + .flat() + .find((item) => isSelectedEditor(item)); - useEffect(() => { - if ( - typeof focusedItemIndex === 'number' && - focusedItemIndex > -1 && - isOpen - ) { - const focusedItem = menuItemRefs.current[focusedItemIndex]; - const containingGroupId = focusedItem?.closest( - '[data-accordion-group]' - )?.id; - if ( - !focusedItem?.id && - containingGroupId && - containingGroupId !== activeGroupId - ) { - setActiveGroupId(containingGroupId); + if (selectedEditor) { + const editorButton = menuItemRefs.current.find( + (btn) => btn?.dataset.itemName === selectedEditor.name + ); + editorButton?.focus(); } - focusedItem?.focus(); } - }, [activeGroupId, focusedItemIndex, isOpen]); + }, [groups, isOpen, isSelectedEditor]); - useEffect(() => { - const handleKeyDown = (e: KeyboardEvent) => { - switch (e.key) { - case KeyboardKey.Up: { - if ( - typeof focusedItemIndex === 'number' && - menuItemRefs.current.length - ) { - let previousItemIndex = focusedItemIndex - 1; - if (previousItemIndex < 0) { - previousItemIndex = menuItemRefs.current.length - 1; - } - setFocusedItemIndex(previousItemIndex); - } - e.preventDefault(); - break; - } - case KeyboardKey.Down: { - if ( - typeof focusedItemIndex === 'number' && - menuItemRefs.current.length - ) { - let nextItemIndex = focusedItemIndex + 1; - if (nextItemIndex > menuItemRefs.current.length - 1) { - nextItemIndex = 0; - } - setFocusedItemIndex(nextItemIndex); - } - e.preventDefault(); - break; - } + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === KeyboardKey.Down || e.key === KeyboardKey.Up) { + e.preventDefault(); + } else { + return; + } + + let items = menuItemRefs.current; + + if (!activeGroupId) { + items = items.filter((btn) => btn?.id); + } + + const currentItemIndex = + items.findIndex((btn) => btn === document.activeElement) ?? 0; + + if (e.key === KeyboardKey.Up) { + let previousItemIndex = currentItemIndex - 1; + if (previousItemIndex < 0) { + previousItemIndex = items.length - 1; } - }; + const previousItem = items[previousItemIndex]; + if (previousItem) { + if (isElementHidden(previousItem)) { + const previousItemGroupId = previousItem.closest( + '[data-accordion-group]' + )?.id; + if (previousItemGroupId) { + setActiveGroupId(previousItemGroupId); + } + setTimeout(() => { + previousItem.focus(); + }, 10); + } - document.addEventListener('keydown', handleKeyDown); + previousItem.focus(); + } + } - return () => { - document.removeEventListener('keydown', handleKeyDown); - }; - }, [focusedItemIndex, groups]); + if (e.key === KeyboardKey.Down) { + let nextItemIndex = currentItemIndex + 1; + if (nextItemIndex > items.length - 1) { + nextItemIndex = 0; + } + const nextItem = items[nextItemIndex]; + if (nextItem) { + if (isElementHidden(nextItem)) { + const nextItemGroupId = nextItem.closest( + '[data-accordion-group]' + )?.id; + if (nextItemGroupId) { + setActiveGroupId(nextItemGroupId); + } + setTimeout(() => { + nextItem.focus(); + }, 10); + } + + nextItem?.focus(); + } + } + }; const selectEditor = (item: EditorMenuItem) => { if (item.component) { @@ -160,12 +169,17 @@ export const EditorAccordionMenu: FunctionComponent< return ( -
+

-
+
); })} diff --git a/app/assets/javascripts/views/constants.ts b/app/assets/javascripts/views/constants.ts index fc5422b31..dd83ea727 100644 --- a/app/assets/javascripts/views/constants.ts +++ b/app/assets/javascripts/views/constants.ts @@ -1,6 +1,11 @@ export const PANEL_NAME_NOTES = 'notes'; export const PANEL_NAME_NAVIGATION = 'navigation'; + export const EMAIL_REGEX = /^([a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?)$/; + export const MENU_MARGIN_FROM_APP_BORDER = 5; export const MAX_MENU_SIZE_MULTIPLIER = 30; + +export const FOCUSABLE_BUT_NOT_TABBABLE = -1; +export const NOTES_LIST_SCROLL_THRESHOLD = 200; diff --git a/app/assets/stylesheets/_sn.scss b/app/assets/stylesheets/_sn.scss index 21b268ba4..38b7d7c6c 100644 --- a/app/assets/stylesheets/_sn.scss +++ b/app/assets/stylesheets/_sn.scss @@ -504,6 +504,11 @@ padding-right: 3rem; } +.py-0\.5 { + padding-top: 0.125rem; + padding-bottom: 0.125rem; +} + .sn-component .py-2\.5 { padding-top: 0.625rem; padding-bottom: 0.625rem; From 50c92619ce813d4c378e674eed0f61735269e456 Mon Sep 17 00:00:00 2001 From: Mo Date: Sun, 30 Jan 2022 19:01:30 -0600 Subject: [PATCH 09/65] refactor: migrate remaining angular components to react (#833) * refactor: menuRow directive to MenuRow component * refactor: migrate footer to react * refactor: migrate actions menu to react * refactor: migrate history menu to react * fix: click outside handler use capture to trigger event before re-render occurs which would otherwise cause node.contains to return incorrect result (specifically for the account menu) * refactor: migrate revision preview modal to react * refactor: migrate permissions modal to react * refactor: migrate password wizard to react * refactor: remove unused input modal directive * refactor: remove unused delay hide component * refactor: remove unused filechange directive * refactor: remove unused elemReady directive * refactor: remove unused sn-enter directive * refactor: remove unused lowercase directive * refactor: remove unused autofocus directive * refactor(wip): note view to react * refactor: use mutation observer to deinit textarea listeners * refactor: migrate challenge modal to react * refactor: migrate note group view to react * refactor(wip): migrate remaining classes * fix: navigation parent ref * refactor: fully remove angular assets * fix: account switcher * fix: application view state * refactor: remove unused password wizard type * fix: revision preview and permissions modal * fix: remove angular comment * refactor: react panel resizers for editor * feat: simple panel resizer * fix: use simple panel resizer everywhere * fix: simplify panel resizer state * chore: rename simple panel resizer to panel resizer * refactor: simplify column layout * fix: editor mount safety check * fix: use inline onLoad callback for iframe, as setting onload after it loads will never call it * chore: fix note view test * chore(deps): upgrade snjs --- .babelrc | 1 - app/assets/icons/ic-user-switch.svg | 3 + app/assets/javascripts/app.ts | 224 ----- app/assets/javascripts/app.tsx | 77 ++ .../Abstract/PureComponent.tsx} | 110 +-- .../AccountMenu/AdvancedOptions.tsx | 3 +- .../components/AccountMenu/index.tsx | 22 +- .../components/AccountSwitcher.tsx | 169 ++++ .../javascripts/components/ActionsMenu.tsx | 390 +++++++++ .../components/ApplicationGroupView.tsx | 51 ++ .../components/ApplicationView.tsx | 258 ++++++ .../javascripts/components/ChallengeModal.tsx | 371 +++++++++ .../components/ComponentView/index.tsx | 72 +- .../components/ConfirmSignoutModal.tsx | 151 ++-- app/assets/javascripts/components/Footer.tsx | 571 +++++++++++++ .../javascripts/components/HistoryMenu.tsx | 311 +++++++ app/assets/javascripts/components/Icon.tsx | 10 +- app/assets/javascripts/components/MenuRow.tsx | 120 +++ .../components/MultipleSelectedNotes.tsx | 47 +- .../javascripts/components/Navigation.tsx | 78 +- .../components/NoAccountWarning.tsx | 3 - .../javascripts/components/NoteGroupView.tsx | 66 ++ .../components/NoteTagsContainer.tsx | 36 +- .../NoteView/NoteView.test.ts} | 11 +- .../NoteView/NoteView.tsx} | 771 +++++++++++++----- .../components/NotesContextMenu.tsx | 10 +- .../components/NotesListOptionsMenu.tsx | 16 +- .../NotesOptions/ChangeEditorOption.tsx | 2 +- .../components/NotesOptionsPanel.tsx | 4 +- .../javascripts/components/NotesView.tsx | 30 +- .../javascripts/components/PanelResizer.tsx | 380 +++++++-- .../javascripts/components/PasswordWizard.tsx | 344 ++++++++ .../components/PermissionsModal.tsx | 94 +++ .../javascripts/components/PinNoteButton.tsx | 3 - .../components/ProtectedNoteOverlay.tsx | 11 +- .../QuickSettingsMenu/QuickSettingsMenu.tsx | 17 +- .../components/RevisionPreviewModal.tsx | 181 ++++ .../javascripts/components/SearchOptions.tsx | 4 +- .../javascripts/components/SessionsModal.tsx | 32 +- .../components/SyncResolutionMenu.tsx | 187 +++++ app/assets/javascripts/components/utils.ts | 52 +- .../directives/functional/autofocus.ts | 19 - .../directives/functional/click-outside.ts | 37 - .../directives/functional/delay-hide.ts | 45 - .../directives/functional/elemReady.ts | 14 - .../directives/functional/file-change.ts | 19 - .../directives/functional/index.ts | 8 - .../directives/functional/lowercase.ts | 24 - .../directives/functional/selectOnFocus.ts | 17 - .../directives/functional/snEnter.ts | 18 - .../directives/views/actionsMenu.ts | 286 ------- .../directives/views/historyMenu.ts | 177 ---- .../javascripts/directives/views/index.ts | 9 - .../directives/views/inputModal.ts | 53 -- .../javascripts/directives/views/menuRow.ts | 54 -- .../directives/views/panelResizer.ts | 406 --------- .../directives/views/passwordWizard.ts | 244 ------ .../directives/views/permissionsModal.ts | 46 -- .../directives/views/revisionPreviewModal.ts | 139 ---- .../directives/views/syncResolutionMenu.ts | 69 -- app/assets/javascripts/filters/index.ts | 1 - app/assets/javascripts/filters/trusted.ts | 6 - app/assets/javascripts/index.ts | 1 - app/assets/javascripts/messages.ts | 3 - .../preferences/components/Content.tsx | 5 +- app/assets/javascripts/preferences/index.ts | 9 - .../preferences/panes/Extensions.tsx | 2 +- .../javascripts/preferences/panes/Listed.tsx | 10 +- .../preferences/panes/account/Credentials.tsx | 114 +-- .../preferences/panes/account/SignOutView.tsx | 2 - .../extensions-segments/ExtensionItem.tsx | 173 ++-- .../panes/general-segments/Defaults.tsx | 7 +- .../panes/general-segments/Tools.tsx | 2 +- .../preferences/panes/listed/BlogItem.tsx | 5 +- app/assets/javascripts/purchaseFlow/index.ts | 8 - app/assets/javascripts/routes.ts | 15 - .../javascripts/services/desktopManager.ts | 28 +- app/assets/javascripts/types.ts | 51 +- .../ui_models/app_state/account_menu_state.ts | 1 + .../ui_models/app_state/app_state.ts | 49 +- .../ui_models/app_state/notes_view_state.ts | 17 +- .../javascripts/ui_models/application.ts | 134 +-- .../ui_models/application_group.ts | 35 +- .../javascripts/ui_models/panel_resizer.ts | 326 -------- .../account_switcher/account-switcher.pug | 31 - .../account_switcher/account_switcher.ts | 105 --- .../views/application/application-view.pug | 50 -- .../views/application/application_view.ts | 207 ----- .../application-group-view.pug | 6 - .../application_group_view.ts | 43 - .../views/challenge_modal/challenge_modal.tsx | 410 ---------- .../javascripts/views/footer/footer-view.pug | 90 -- .../javascripts/views/footer/footer_view.ts | 410 ---------- app/assets/javascripts/views/index.ts | 7 - .../views/note_group_view/note-group-view.pug | 14 - .../views/note_group_view/note_group_view.ts | 47 -- .../javascripts/views/note_view/note-view.pug | 163 ---- .../javascripts/web_device_interface.ts | 4 +- app/assets/stylesheets/_columns.scss | 20 + app/assets/stylesheets/_focused.scss | 12 +- app/assets/stylesheets/_main.scss | 3 +- app/assets/stylesheets/_navigation.scss | 3 - app/assets/stylesheets/_notes.scss | 12 +- app/assets/stylesheets/index.css.scss | 1 + .../templates/directives/actions-menu.pug | 43 - .../templates/directives/history-menu.pug | 55 -- .../templates/directives/input-modal.pug | 24 - app/assets/templates/directives/menu-row.pug | 38 - .../templates/directives/panel-resizer.pug | 1 - .../templates/directives/password-wizard.pug | 51 -- .../directives/permissions-modal.pug | 27 - .../directives/revision-preview-modal.pug | 37 - .../directives/sync-resolution-menu.pug | 57 -- app/views/application/app.html.erb | 2 +- index.html | 1 - package.json | 5 +- yarn.lock | 64 +- 117 files changed, 4715 insertions(+), 5309 deletions(-) create mode 100644 app/assets/icons/ic-user-switch.svg delete mode 100644 app/assets/javascripts/app.ts create mode 100644 app/assets/javascripts/app.tsx rename app/assets/javascripts/{views/abstract/pure_view_ctrl.ts => components/Abstract/PureComponent.tsx} (52%) create mode 100644 app/assets/javascripts/components/AccountSwitcher.tsx create mode 100644 app/assets/javascripts/components/ActionsMenu.tsx create mode 100644 app/assets/javascripts/components/ApplicationGroupView.tsx create mode 100644 app/assets/javascripts/components/ApplicationView.tsx create mode 100644 app/assets/javascripts/components/ChallengeModal.tsx create mode 100644 app/assets/javascripts/components/Footer.tsx create mode 100644 app/assets/javascripts/components/HistoryMenu.tsx create mode 100644 app/assets/javascripts/components/MenuRow.tsx create mode 100644 app/assets/javascripts/components/NoteGroupView.tsx rename app/assets/javascripts/{views/note_view/note_view.test.ts => components/NoteView/NoteView.test.ts} (94%) rename app/assets/javascripts/{views/note_view/note_view.ts => components/NoteView/NoteView.tsx} (53%) create mode 100644 app/assets/javascripts/components/PasswordWizard.tsx create mode 100644 app/assets/javascripts/components/PermissionsModal.tsx create mode 100644 app/assets/javascripts/components/RevisionPreviewModal.tsx create mode 100644 app/assets/javascripts/components/SyncResolutionMenu.tsx delete mode 100644 app/assets/javascripts/directives/functional/autofocus.ts delete mode 100644 app/assets/javascripts/directives/functional/click-outside.ts delete mode 100644 app/assets/javascripts/directives/functional/delay-hide.ts delete mode 100644 app/assets/javascripts/directives/functional/elemReady.ts delete mode 100644 app/assets/javascripts/directives/functional/file-change.ts delete mode 100644 app/assets/javascripts/directives/functional/index.ts delete mode 100644 app/assets/javascripts/directives/functional/lowercase.ts delete mode 100644 app/assets/javascripts/directives/functional/selectOnFocus.ts delete mode 100644 app/assets/javascripts/directives/functional/snEnter.ts delete mode 100644 app/assets/javascripts/directives/views/actionsMenu.ts delete mode 100644 app/assets/javascripts/directives/views/historyMenu.ts delete mode 100644 app/assets/javascripts/directives/views/index.ts delete mode 100644 app/assets/javascripts/directives/views/inputModal.ts delete mode 100644 app/assets/javascripts/directives/views/menuRow.ts delete mode 100644 app/assets/javascripts/directives/views/panelResizer.ts delete mode 100644 app/assets/javascripts/directives/views/passwordWizard.ts delete mode 100644 app/assets/javascripts/directives/views/permissionsModal.ts delete mode 100644 app/assets/javascripts/directives/views/revisionPreviewModal.ts delete mode 100644 app/assets/javascripts/directives/views/syncResolutionMenu.ts delete mode 100644 app/assets/javascripts/filters/index.ts delete mode 100644 app/assets/javascripts/filters/trusted.ts delete mode 100644 app/assets/javascripts/messages.ts delete mode 100644 app/assets/javascripts/preferences/index.ts delete mode 100644 app/assets/javascripts/purchaseFlow/index.ts delete mode 100644 app/assets/javascripts/routes.ts delete mode 100644 app/assets/javascripts/ui_models/panel_resizer.ts delete mode 100644 app/assets/javascripts/views/account_switcher/account-switcher.pug delete mode 100644 app/assets/javascripts/views/account_switcher/account_switcher.ts delete mode 100644 app/assets/javascripts/views/application/application-view.pug delete mode 100644 app/assets/javascripts/views/application/application_view.ts delete mode 100644 app/assets/javascripts/views/application_group/application-group-view.pug delete mode 100644 app/assets/javascripts/views/application_group/application_group_view.ts delete mode 100644 app/assets/javascripts/views/challenge_modal/challenge_modal.tsx delete mode 100644 app/assets/javascripts/views/footer/footer-view.pug delete mode 100644 app/assets/javascripts/views/footer/footer_view.ts delete mode 100644 app/assets/javascripts/views/index.ts delete mode 100644 app/assets/javascripts/views/note_group_view/note-group-view.pug delete mode 100644 app/assets/javascripts/views/note_group_view/note_group_view.ts delete mode 100644 app/assets/javascripts/views/note_view/note-view.pug create mode 100644 app/assets/stylesheets/_columns.scss delete mode 100644 app/assets/templates/directives/actions-menu.pug delete mode 100644 app/assets/templates/directives/history-menu.pug delete mode 100644 app/assets/templates/directives/input-modal.pug delete mode 100644 app/assets/templates/directives/menu-row.pug delete mode 100644 app/assets/templates/directives/panel-resizer.pug delete mode 100644 app/assets/templates/directives/password-wizard.pug delete mode 100644 app/assets/templates/directives/permissions-modal.pug delete mode 100644 app/assets/templates/directives/revision-preview-modal.pug delete mode 100644 app/assets/templates/directives/sync-resolution-menu.pug diff --git a/.babelrc b/.babelrc index 766002c6b..643b8d31e 100644 --- a/.babelrc +++ b/.babelrc @@ -4,7 +4,6 @@ "@babel/preset-env" ], "plugins": [ - "angularjs-annotate", ["@babel/plugin-transform-react-jsx", { "pragma": "h", "pragmaFrag": "Fragment" diff --git a/app/assets/icons/ic-user-switch.svg b/app/assets/icons/ic-user-switch.svg new file mode 100644 index 000000000..74d80bb4d --- /dev/null +++ b/app/assets/icons/ic-user-switch.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/assets/javascripts/app.ts b/app/assets/javascripts/app.ts deleted file mode 100644 index f5d61825a..000000000 --- a/app/assets/javascripts/app.ts +++ /dev/null @@ -1,224 +0,0 @@ -'use strict'; - -declare global { - interface Window { - // eslint-disable-next-line camelcase - _bugsnag_api_key?: string; - // eslint-disable-next-line camelcase - _purchase_url?: string; - // eslint-disable-next-line camelcase - _plans_url?: string; - // eslint-disable-next-line camelcase - _dashboard_url?: string; - // eslint-disable-next-line camelcase - _default_sync_server: string; - // eslint-disable-next-line camelcase - _enable_unfinished_features: boolean; - // eslint-disable-next-line camelcase - _websocket_url: string; - startApplication?: StartApplication; - - _devAccountEmail?: string; - _devAccountPassword?: string; - _devAccountServer?: string; - } -} - -import { ComponentViewDirective } from '@/components/ComponentView'; -import { NavigationDirective } from '@/components/Navigation'; -import { PinNoteButtonDirective } from '@/components/PinNoteButton'; -import { IsWebPlatform, WebAppVersion } from '@/version'; -import { - ApplicationGroupView, - ApplicationView, - ChallengeModal, - FooterView, - NoteGroupViewDirective, - NoteViewDirective, -} from '@/views'; -import { SNLog } from '@standardnotes/snjs'; -import angular from 'angular'; -import { AccountMenuDirective } from './components/AccountMenu'; -import { ConfirmSignoutDirective } from './components/ConfirmSignoutModal'; -import { IconDirective } from './components/Icon'; -import { MultipleSelectedNotesDirective } from './components/MultipleSelectedNotes'; -import { NoAccountWarningDirective } from './components/NoAccountWarning'; -import { NotesContextMenuDirective } from './components/NotesContextMenu'; -import { NotesListOptionsDirective } from './components/NotesListOptionsMenu'; -import { NotesOptionsPanelDirective } from './components/NotesOptionsPanel'; -import { NotesViewDirective } from './components/NotesView'; -import { NoteTagsContainerDirective } from './components/NoteTagsContainer'; -import { ProtectedNoteOverlayDirective } from './components/ProtectedNoteOverlay'; -import { QuickSettingsMenuDirective } from './components/QuickSettingsMenu/QuickSettingsMenu'; -import { SearchOptionsDirective } from './components/SearchOptions'; -import { SessionsModalDirective } from './components/SessionsModal'; -import { - autofocus, - clickOutside, - delayHide, - elemReady, - fileChange, - lowercase, - selectOnFocus, - snEnter, -} from './directives/functional'; -import { - ActionsMenu, - HistoryMenu, - InputModal, - MenuRow, - PanelResizer, - PasswordWizard, - PermissionsModal, - RevisionPreviewModal, - SyncResolutionMenu, -} from './directives/views'; -import { trusted } from './filters'; -import { PreferencesDirective } from './preferences'; -import { PurchaseFlowDirective } from './purchaseFlow'; -import { configRoutes } from './routes'; -import { Bridge } from './services/bridge'; -import { BrowserBridge } from './services/browserBridge'; -import { startErrorReporting } from './services/errorReporting'; -import { StartApplication } from './startApplication'; -import { ApplicationGroup } from './ui_models/application_group'; -import { isDev } from './utils'; -import { AccountSwitcher } from './views/account_switcher/account_switcher'; - -function reloadHiddenFirefoxTab(): boolean { - /** - * For Firefox pinned tab issue: - * When a new browser session is started, and SN is in a pinned tab, - * SN exhibits strange behavior until the tab is reloaded. - */ - if ( - document.hidden && - navigator.userAgent.toLowerCase().includes('firefox') - ) { - document.addEventListener('visibilitychange', () => { - if (!document.hidden) { - location.reload(); - } - }); - return true; - } else { - return false; - } -} - -const startApplication: StartApplication = async function startApplication( - defaultSyncServerHost: string, - bridge: Bridge, - enableUnfinishedFeatures: boolean, - webSocketUrl: string -) { - if (reloadHiddenFirefoxTab()) { - return; - } - - SNLog.onLog = console.log; - startErrorReporting(); - - angular.module('app', []); - - // Config - angular - .module('app') - .config(configRoutes) - .constant('bridge', bridge) - .constant('defaultSyncServerHost', defaultSyncServerHost) - .constant('appVersion', bridge.appVersion) - .constant('enableUnfinishedFeatures', enableUnfinishedFeatures) - .constant('webSocketUrl', webSocketUrl); - - // Controllers - angular - .module('app') - .directive('applicationGroupView', () => new ApplicationGroupView()) - .directive('applicationView', () => new ApplicationView()) - .directive('noteGroupView', () => new NoteGroupViewDirective()) - .directive('noteView', () => new NoteViewDirective()) - .directive('footerView', () => new FooterView()); - - // Directives - Functional - angular - .module('app') - .directive('snAutofocus', ['$timeout', autofocus]) - .directive('clickOutside', ['$document', clickOutside]) - .directive('delayHide', delayHide) - .directive('elemReady', elemReady) - .directive('fileChange', fileChange) - .directive('lowercase', lowercase) - .directive('selectOnFocus', ['$window', selectOnFocus]) - .directive('snEnter', snEnter); - - // Directives - Views - angular - .module('app') - .directive('accountSwitcher', () => new AccountSwitcher()) - .directive('actionsMenu', () => new ActionsMenu()) - .directive('challengeModal', () => new ChallengeModal()) - .directive('componentView', ComponentViewDirective) - .directive('inputModal', () => new InputModal()) - .directive('menuRow', () => new MenuRow()) - .directive('panelResizer', () => new PanelResizer()) - .directive('passwordWizard', () => new PasswordWizard()) - .directive('permissionsModal', () => new PermissionsModal()) - .directive('revisionPreviewModal', () => new RevisionPreviewModal()) - .directive('historyMenu', () => new HistoryMenu()) - .directive('syncResolutionMenu', () => new SyncResolutionMenu()) - .directive('sessionsModal', SessionsModalDirective) - .directive('accountMenu', AccountMenuDirective) - .directive('quickSettingsMenu', QuickSettingsMenuDirective) - .directive('noAccountWarning', NoAccountWarningDirective) - .directive('protectedNotePanel', ProtectedNoteOverlayDirective) - .directive('searchOptions', SearchOptionsDirective) - .directive('confirmSignout', ConfirmSignoutDirective) - .directive('multipleSelectedNotesPanel', MultipleSelectedNotesDirective) - .directive('notesContextMenu', NotesContextMenuDirective) - .directive('notesOptionsPanel', NotesOptionsPanelDirective) - .directive('notesListOptionsMenu', NotesListOptionsDirective) - .directive('icon', IconDirective) - .directive('noteTagsContainer', NoteTagsContainerDirective) - .directive('navigation', NavigationDirective) - .directive('preferences', PreferencesDirective) - .directive('purchaseFlow', PurchaseFlowDirective) - .directive('notesView', NotesViewDirective) - .directive('pinNoteButton', PinNoteButtonDirective); - - // Filters - angular.module('app').filter('trusted', ['$sce', trusted]); - - // Services - angular.module('app').service('mainApplicationGroup', ApplicationGroup); - - // Debug - if (isDev) { - Object.defineProperties(window, { - application: { - get: () => - ( - angular - .element(document) - .injector() - .get('mainApplicationGroup') as any - ).primaryApplication, - }, - }); - } - - angular.element(document).ready(() => { - angular.bootstrap(document, ['app']); - }); -}; - -if (IsWebPlatform) { - startApplication( - window._default_sync_server, - new BrowserBridge(WebAppVersion), - window._enable_unfinished_features, - window._websocket_url - ); -} else { - window.startApplication = startApplication; -} diff --git a/app/assets/javascripts/app.tsx b/app/assets/javascripts/app.tsx new file mode 100644 index 000000000..2abec7be4 --- /dev/null +++ b/app/assets/javascripts/app.tsx @@ -0,0 +1,77 @@ +'use strict'; + +declare global { + interface Window { + // eslint-disable-next-line camelcase + _bugsnag_api_key?: string; + // eslint-disable-next-line camelcase + _purchase_url?: string; + // eslint-disable-next-line camelcase + _plans_url?: string; + // eslint-disable-next-line camelcase + _dashboard_url?: string; + // eslint-disable-next-line camelcase + _default_sync_server: string; + // eslint-disable-next-line camelcase + _enable_unfinished_features: boolean; + // eslint-disable-next-line camelcase + _websocket_url: string; + startApplication?: StartApplication; + + _devAccountEmail?: string; + _devAccountPassword?: string; + _devAccountServer?: string; + } +} + +import { IsWebPlatform, WebAppVersion } from '@/version'; +import { SNLog } from '@standardnotes/snjs'; +import { render } from 'preact'; +import { ApplicationGroupView } from './components/ApplicationGroupView'; +import { Bridge } from './services/bridge'; +import { BrowserBridge } from './services/browserBridge'; +import { startErrorReporting } from './services/errorReporting'; +import { StartApplication } from './startApplication'; +import { ApplicationGroup } from './ui_models/application_group'; +import { isDev } from './utils'; + +const startApplication: StartApplication = async function startApplication( + defaultSyncServerHost: string, + bridge: Bridge, + enableUnfinishedFeatures: boolean, + webSocketUrl: string +) { + SNLog.onLog = console.log; + startErrorReporting(); + + const mainApplicationGroup = new ApplicationGroup( + defaultSyncServerHost, + bridge, + enableUnfinishedFeatures, + webSocketUrl + ); + + if (isDev) { + Object.defineProperties(window, { + application: { + get: () => mainApplicationGroup.primaryApplication, + }, + }); + } + + render( + , + document.body.appendChild(document.createElement('div')) + ); +}; + +if (IsWebPlatform) { + startApplication( + window._default_sync_server, + new BrowserBridge(WebAppVersion), + window._enable_unfinished_features, + window._websocket_url + ); +} else { + window.startApplication = startApplication; +} diff --git a/app/assets/javascripts/views/abstract/pure_view_ctrl.ts b/app/assets/javascripts/components/Abstract/PureComponent.tsx similarity index 52% rename from app/assets/javascripts/views/abstract/pure_view_ctrl.ts rename to app/assets/javascripts/components/Abstract/PureComponent.tsx index 8e2691359..9a56d834d 100644 --- a/app/assets/javascripts/views/abstract/pure_view_ctrl.ts +++ b/app/assets/javascripts/components/Abstract/PureComponent.tsx @@ -2,38 +2,27 @@ import { ApplicationEvent } from '@standardnotes/snjs'; import { WebApplication } from '@/ui_models/application'; import { AppState } from '@/ui_models/app_state'; import { autorun, IReactionDisposer, IReactionPublic } from 'mobx'; +import { Component } from 'preact'; +import { findDOMNode, unmountComponentAtNode } from 'preact/compat'; -export type CtrlState = Partial>; -export type CtrlProps = Partial>; +export type PureComponentState = Partial>; +export type PureComponentProps = Partial>; -export class PureViewCtrl

{ - $timeout: ng.ITimeoutService; - /** Passed through templates */ - application!: WebApplication; - state: S = {} as any; - private unsubApp: any; - private unsubState: any; - private stateTimeout?: ng.IPromise; - /** - * Subclasses can optionally add an ng-if=ctrl.templateReady to make sure that - * no Angular handlebars/syntax render in the UI before display data is ready. - */ - protected templateReady = false; +export abstract class PureComponent< + P = PureComponentProps, + S = PureComponentState +> extends Component { + private unsubApp!: () => void; + private unsubState!: () => void; private reactionDisposers: IReactionDisposer[] = []; - /* @ngInject */ - constructor($timeout: ng.ITimeoutService, public props: P = {} as any) { - this.$timeout = $timeout; + constructor(props: P, protected application: WebApplication) { + super(props); } - $onInit(): void { - this.state = { - ...this.getInitialState(), - ...this.state, - }; + componentDidMount() { this.addAppEventObserver(); this.addAppStateObserver(); - this.templateReady = true; } deinit(): void { @@ -43,63 +32,38 @@ export class PureViewCtrl

{ disposer(); } this.reactionDisposers.length = 0; - this.unsubApp = undefined; - this.unsubState = undefined; - if (this.stateTimeout) { - this.$timeout.cancel(this.stateTimeout); - } + (this.unsubApp as unknown) = undefined; + (this.unsubState as unknown) = undefined; } - $onDestroy(): void { + protected dismissModal(): void { + const elem = this.getElement(); + if (!elem) { + return; + } + + const parent = elem.parentElement; + if (!parent) { + return; + } + parent.remove(); + unmountComponentAtNode(parent); + } + + componentWillUnmount(): void { this.deinit(); } + render() { + return

Must override
; + } + public get appState(): AppState { return this.application.getAppState(); } - /** @private */ - async resetState(): Promise { - this.state = this.getInitialState(); - await this.setState(this.state); - } - - /** @override */ - getInitialState(): S { - return {} as any; - } - - async setState(state: Partial): Promise { - if (!this.$timeout) { - return; - } - return new Promise((resolve) => { - this.stateTimeout = this.$timeout(() => { - /** - * State changes must be *inside* the timeout block for them to be affected in the UI - * Otherwise UI controllers will need to use $timeout everywhere - */ - this.state = Object.freeze(Object.assign({}, this.state, state)); - resolve(); - this.afterStateChange(); - }); - }); - } - - /** @override */ - // eslint-disable-next-line @typescript-eslint/no-empty-function - afterStateChange(): void {} - - /** @returns a promise that resolves after the UI has been updated. */ - flushUI(): angular.IPromise { - return this.$timeout(); - } - - initProps(props: CtrlProps): void { - if (Object.keys(this.props).length > 0) { - throw 'Already init-ed props.'; - } - this.props = Object.freeze(Object.assign({}, this.props, props)); + protected getElement(): Element | null { + return findDOMNode(this); } autorun(view: (r: IReactionPublic) => void): void { @@ -151,7 +115,7 @@ export class PureViewCtrl

{ /** @override */ async onAppStart() { - await this.resetState(); + /** Optional override */ } onLocalDataLoaded() { diff --git a/app/assets/javascripts/components/AccountMenu/AdvancedOptions.tsx b/app/assets/javascripts/components/AccountMenu/AdvancedOptions.tsx index eb16d545c..1a75878df 100644 --- a/app/assets/javascripts/components/AccountMenu/AdvancedOptions.tsx +++ b/app/assets/javascripts/components/AccountMenu/AdvancedOptions.tsx @@ -1,9 +1,8 @@ import { WebApplication } from '@/ui_models/application'; import { AppState } from '@/ui_models/app_state'; -import { isDev } from '@/utils'; import { observer } from 'mobx-react-lite'; import { FunctionComponent } from 'preact'; -import { useEffect, useState } from 'preact/hooks'; +import { useState } from 'preact/hooks'; import { Checkbox } from '../Checkbox'; import { Icon } from '../Icon'; import { InputWithIcon } from '../InputWithIcon'; diff --git a/app/assets/javascripts/components/AccountMenu/index.tsx b/app/assets/javascripts/components/AccountMenu/index.tsx index 12f6a12b0..36bc5cae0 100644 --- a/app/assets/javascripts/components/AccountMenu/index.tsx +++ b/app/assets/javascripts/components/AccountMenu/index.tsx @@ -1,8 +1,8 @@ import { observer } from 'mobx-react-lite'; -import { toDirective } from '@/components/utils'; +import { useCloseOnClickOutside } from '@/components/utils'; import { AppState } from '@/ui_models/app_state'; import { WebApplication } from '@/ui_models/application'; -import { useState } from 'preact/hooks'; +import { useRef, useState } from 'preact/hooks'; import { GeneralAccountMenu } from './GeneralAccountMenu'; import { FunctionComponent } from 'preact'; import { SignInPane } from './SignIn'; @@ -21,9 +21,12 @@ export enum AccountMenuPane { type Props = { appState: AppState; application: WebApplication; + onClickOutside: () => void; }; -type PaneSelectorProps = Props & { +type PaneSelectorProps = { + appState: AppState; + application: WebApplication; menuPane: AccountMenuPane; setMenuPane: (pane: AccountMenuPane) => void; closeMenu: () => void; @@ -79,8 +82,8 @@ const MenuPaneSelector: FunctionComponent = observer( } ); -const AccountMenu: FunctionComponent = observer( - ({ application, appState }) => { +export const AccountMenu: FunctionComponent = observer( + ({ application, appState, onClickOutside }) => { const { currentPane, setCurrentPane, @@ -88,6 +91,11 @@ const AccountMenu: FunctionComponent = observer( closeAccountMenu, } = appState.accountMenu; + const ref = useRef(null); + useCloseOnClickOutside(ref, () => { + onClickOutside(); + }); + const handleKeyDown: JSXInternal.KeyboardEventHandler = ( event ) => { @@ -105,7 +113,7 @@ const AccountMenu: FunctionComponent = observer( }; return ( -

+
= observer( ); } ); - -export const AccountMenuDirective = toDirective(AccountMenu); diff --git a/app/assets/javascripts/components/AccountSwitcher.tsx b/app/assets/javascripts/components/AccountSwitcher.tsx new file mode 100644 index 000000000..a94e65bd8 --- /dev/null +++ b/app/assets/javascripts/components/AccountSwitcher.tsx @@ -0,0 +1,169 @@ +import { ApplicationGroup } from '@/ui_models/application_group'; +import { WebApplication } from '@/ui_models/application'; +import { ApplicationDescriptor } from '@standardnotes/snjs'; +import { PureComponent } from '@/components/Abstract/PureComponent'; +import { JSX } from 'preact'; + +type Props = { + application: WebApplication; + mainApplicationGroup: ApplicationGroup; +}; + +type State = { + descriptors: ApplicationDescriptor[]; + editingDescriptor?: ApplicationDescriptor; +}; + +export class AccountSwitcher extends PureComponent { + private removeAppGroupObserver: any; + activeApplication!: WebApplication; + + constructor(props: Props) { + super(props, props.application); + this.removeAppGroupObserver = + props.mainApplicationGroup.addApplicationChangeObserver(() => { + this.activeApplication = props.mainApplicationGroup + .primaryApplication as WebApplication; + this.reloadApplications(); + }); + } + + reloadApplications() { + this.setState({ + descriptors: this.props.mainApplicationGroup.getDescriptors(), + }); + } + + addNewApplication = () => { + this.dismiss(); + this.props.mainApplicationGroup.addNewApplication(); + }; + + selectDescriptor = (descriptor: ApplicationDescriptor) => { + this.dismiss(); + this.props.mainApplicationGroup.loadApplicationForDescriptor(descriptor); + }; + + inputForDescriptor(descriptor: ApplicationDescriptor) { + return document.getElementById(`input-${descriptor.identifier}`); + } + + renameDescriptor = (event: Event, descriptor: ApplicationDescriptor) => { + event.stopPropagation(); + + this.setState({ editingDescriptor: descriptor }); + + setTimeout(() => { + this.inputForDescriptor(descriptor)?.focus(); + }); + }; + + submitRename = () => { + this.props.mainApplicationGroup.renameDescriptor( + this.state.editingDescriptor!, + this.state.editingDescriptor!.label + ); + this.setState({ editingDescriptor: undefined }); + }; + + deinit() { + super.deinit(); + this.removeAppGroupObserver(); + this.removeAppGroupObserver = undefined; + } + + onDescriptorInputChange = ( + descriptor: ApplicationDescriptor, + { currentTarget }: JSX.TargetedEvent + ) => { + descriptor.label = currentTarget.value; + }; + + dismiss = () => { + this.dismissModal(); + }; + + render() { + return ( +
+
+
+
+ +
+
+ ); + } +} diff --git a/app/assets/javascripts/components/ActionsMenu.tsx b/app/assets/javascripts/components/ActionsMenu.tsx new file mode 100644 index 000000000..906b9afe9 --- /dev/null +++ b/app/assets/javascripts/components/ActionsMenu.tsx @@ -0,0 +1,390 @@ +import { WebApplication } from '@/ui_models/application'; +import { + SNItem, + Action, + SNActionsExtension, + UuidString, + CopyPayload, + SNNote, +} from '@standardnotes/snjs'; +import { ActionResponse } from '@standardnotes/snjs'; +import { render } from 'preact'; +import { PureComponent } from './Abstract/PureComponent'; +import { MenuRow } from './MenuRow'; +import { RevisionPreviewModal } from './RevisionPreviewModal'; +type ActionsMenuScope = { + application: WebApplication; + item: SNItem; +}; + +type ActionSubRow = { + onClick: () => void; + label: string; + subtitle: string; + spinnerClass?: string; +}; + +type ExtensionState = { + loading: boolean; + error: boolean; +}; + +type MenuItem = { + uuid: UuidString; + name: string; + loading: boolean; + error: boolean; + hidden: boolean; + deprecation?: string; + actions: (Action & { + subrows?: ActionSubRow[]; + })[]; +}; + +type ActionState = { + error: boolean; + running: boolean; +}; + +type ActionsMenuState = { + extensions: SNActionsExtension[]; + extensionsState: Record; + hiddenExtensions: Record; + selectedActionId?: number; + menuItems: MenuItem[]; + actionState: Record; +}; + +type Props = { + application: WebApplication; + item: SNNote; +}; + +export class ActionsMenu + extends PureComponent + implements ActionsMenuScope +{ + application!: WebApplication; + item!: SNItem; + + constructor(props: Props) { + super(props, props.application); + + const extensions = props.application.actionsManager + .getExtensions() + .sort((a, b) => { + return a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1; + }) + .map((extension) => { + return new SNActionsExtension( + CopyPayload(extension.payload, { + content: { + ...extension.payload.safeContent, + actions: [], + }, + }) + ); + }); + const extensionsState: Record = {}; + extensions.map((extension) => { + extensionsState[extension.uuid] = { + loading: true, + error: false, + }; + }); + + this.state = { + extensions, + extensionsState, + hiddenExtensions: {}, + menuItems: [], + actionState: {}, + }; + } + + componentDidMount() { + this.loadExtensions(); + this.autorun(() => { + this.rebuildMenuState({ + hiddenExtensions: this.appState.actionsMenu.hiddenExtensions, + }); + }); + } + + rebuildMenuState({ + extensions = this.state.extensions, + extensionsState = this.state.extensionsState, + selectedActionId = this.state.selectedActionId, + hiddenExtensions = this.appState.actionsMenu.hiddenExtensions, + } = {}) { + return this.setState({ + extensions, + extensionsState, + selectedActionId, + menuItems: extensions.map((extension) => { + const state = extensionsState[extension.uuid]; + const hidden = hiddenExtensions[extension.uuid]; + const item: MenuItem = { + uuid: extension.uuid, + name: extension.name, + loading: state?.loading ?? false, + error: state?.error ?? false, + hidden: hidden ?? false, + deprecation: extension.deprecation!, + actions: extension + .actionsWithContextForItem(this.props.item) + .map((action) => { + if (action.id === selectedActionId) { + return { + ...action, + subrows: this.subRowsForAction(action, extension), + }; + } else { + return action; + } + }), + }; + return item; + }), + }); + } + + async loadExtensions() { + await Promise.all( + this.state.extensions.map(async (extension: SNActionsExtension) => { + this.setLoadingExtension(extension.uuid, true); + const updatedExtension = + await this.props.application.actionsManager.loadExtensionInContextOfItem( + extension, + this.props.item + ); + if (updatedExtension) { + await this.updateExtension(updatedExtension!); + } else { + this.setErrorExtension(extension.uuid, true); + } + this.setLoadingExtension(extension.uuid, false); + }) + ); + } + + executeAction = async (action: Action, extensionUuid: UuidString) => { + if (action.verb === 'nested') { + this.rebuildMenuState({ + selectedActionId: action.id, + }); + return; + } + + const extension = this.props.application.findItem( + extensionUuid + ) as SNActionsExtension; + + this.updateActionState(action, { running: true, error: false }); + + const response = await this.props.application.actionsManager.runAction( + action, + this.props.item, + async () => { + /** @todo */ + return ''; + } + ); + if (response.error) { + this.updateActionState(action, { error: true, running: false }); + return; + } + + this.updateActionState(action, { running: false, error: false }); + this.handleActionResponse(action, response); + await this.reloadExtension(extension); + }; + + handleActionResponse(action: Action, result: ActionResponse) { + switch (action.verb) { + case 'render': { + const item = result.item; + render( + , + document.body.appendChild(document.createElement('div')) + ); + } + } + } + + private subRowsForAction( + parentAction: Action, + extension: Pick + ): ActionSubRow[] | undefined { + if (!parentAction.subactions) { + return undefined; + } + return parentAction.subactions.map((subaction) => { + return { + id: subaction.id, + onClick: () => { + this.executeAction(subaction, extension.uuid); + }, + label: subaction.label, + subtitle: subaction.desc, + spinnerClass: this.getActionState(subaction).running + ? 'info' + : undefined, + }; + }); + } + + private updateActionState(action: Action, actionState: ActionState): void { + const state = this.state.actionState; + state[action.id] = actionState; + this.setState({ actionState: state }); + } + + private getActionState(action: Action): ActionState { + return this.state.actionState[action.id] || {}; + } + + private async updateExtension(extension: SNActionsExtension) { + const extensions = this.state.extensions.map((ext: SNActionsExtension) => { + if (extension.uuid === ext.uuid) { + return extension; + } + return ext; + }); + await this.rebuildMenuState({ + extensions, + }); + } + + private async reloadExtension(extension: SNActionsExtension) { + const extensionInContext = + await this.props.application.actionsManager.loadExtensionInContextOfItem( + extension, + this.props.item + ); + const extensions = this.state.extensions.map((ext: SNActionsExtension) => { + if (extension.uuid === ext.uuid) { + return extensionInContext!; + } + return ext; + }); + this.rebuildMenuState({ + extensions, + }); + } + + public toggleExtensionVisibility(extensionUuid: UuidString) { + this.appState.actionsMenu.toggleExtensionVisibility(extensionUuid); + } + + private setLoadingExtension(extensionUuid: UuidString, value = false) { + const { extensionsState } = this.state; + extensionsState[extensionUuid].loading = value; + this.rebuildMenuState({ + extensionsState, + }); + } + + private setErrorExtension(extensionUuid: UuidString, value = false) { + const { extensionsState } = this.state; + extensionsState[extensionUuid].error = value; + this.rebuildMenuState({ + extensionsState, + }); + } + + renderMenuItem(item: MenuItem) { + return ( +
+
{ + this.toggleExtensionVisibility(item.uuid); + $event.stopPropagation(); + }} + > +
+
{item.name}
+ {item.hidden &&
} + {item.deprecation && !item.hidden && ( +
+ {item.deprecation} +
+ )} +
+ + {item.loading &&
} +
+ +
+ {item.error && !item.hidden && ( + + )} + + {!item.actions.length && !item.hidden && ( + + )} + + {!item.hidden && + !item.loading && + !item.error && + item.actions.map((action, index) => { + return ( + + {action.access_type && ( +
+ {'Uses '} + {action.access_type} + {' access to this note.'} +
+ )} +
+ ); + })} +
+
+ ); + } + + render() { + return ( +
+
+ {this.state.extensions.length == 0 && ( + + + + )} + {this.state.menuItems.map((extension) => + this.renderMenuItem(extension) + )} +
+
+ ); + } +} diff --git a/app/assets/javascripts/components/ApplicationGroupView.tsx b/app/assets/javascripts/components/ApplicationGroupView.tsx new file mode 100644 index 000000000..adafaa496 --- /dev/null +++ b/app/assets/javascripts/components/ApplicationGroupView.tsx @@ -0,0 +1,51 @@ +import { ApplicationGroup } from '@/ui_models/application_group'; +import { WebApplication } from '@/ui_models/application'; +import { Component } from 'preact'; +import { ApplicationView } from './ApplicationView'; + +type State = { + applications: WebApplication[]; + activeApplication?: WebApplication; +}; + +type Props = { + mainApplicationGroup: ApplicationGroup; +}; + +export class ApplicationGroupView extends Component { + constructor(props: Props) { + super(props); + this.state = { + applications: [], + }; + props.mainApplicationGroup.addApplicationChangeObserver(() => { + this.setState({ + activeApplication: props.mainApplicationGroup + .primaryApplication as WebApplication, + applications: + props.mainApplicationGroup.getApplications() as WebApplication[], + }); + }); + props.mainApplicationGroup.initialize(); + } + + render() { + return ( + <> + {this.state.applications.map((application) => { + if (application === this.state.activeApplication) { + return ( +
+ +
+ ); + } + })} + + ); + } +} diff --git a/app/assets/javascripts/components/ApplicationView.tsx b/app/assets/javascripts/components/ApplicationView.tsx new file mode 100644 index 000000000..74fa60157 --- /dev/null +++ b/app/assets/javascripts/components/ApplicationView.tsx @@ -0,0 +1,258 @@ +import { ApplicationGroup } from '@/ui_models/application_group'; +import { getPlatformString } from '@/utils'; +import { AppStateEvent, PanelResizedData } from '@/ui_models/app_state'; +import { + ApplicationEvent, + Challenge, + PermissionDialog, + removeFromArray, +} from '@standardnotes/snjs'; +import { PANEL_NAME_NOTES, PANEL_NAME_NAVIGATION } from '@/views/constants'; +import { STRING_DEFAULT_FILE_ERROR } from '@/strings'; +import { alertDialog } from '@/services/alertService'; +import { WebAppEvent, WebApplication } from '@/ui_models/application'; +import { PureComponent } from '@/components/Abstract/PureComponent'; +import { Navigation } from '@/components/Navigation'; +import { NotesView } from '@/components/NotesView'; +import { NoteGroupView } from '@/components/NoteGroupView'; +import { Footer } from '@/components/Footer'; +import { SessionsModal } from '@/components/SessionsModal'; +import { PreferencesViewWrapper } from '@/preferences/PreferencesViewWrapper'; +import { ChallengeModal } from '@/components/ChallengeModal'; +import { NotesContextMenu } from '@/components/NotesContextMenu'; +import { PurchaseFlowWrapper } from '@/purchaseFlow/PurchaseFlowWrapper'; +import { render } from 'preact'; +import { PermissionsModal } from './PermissionsModal'; + +type Props = { + application: WebApplication; + mainApplicationGroup: ApplicationGroup; +}; + +type State = { + started?: boolean; + launched?: boolean; + needsUnlock?: boolean; + appClass: string; + challenges: Challenge[]; +}; + +export class ApplicationView extends PureComponent { + public readonly platformString = getPlatformString(); + + constructor(props: Props) { + super(props, props.application); + this.state = { + appClass: '', + challenges: [], + }; + this.onDragDrop = this.onDragDrop.bind(this); + this.onDragOver = this.onDragOver.bind(this); + this.addDragDropHandlers(); + } + + deinit() { + (this.application as unknown) = undefined; + window.removeEventListener('dragover', this.onDragOver, true); + window.removeEventListener('drop', this.onDragDrop, true); + (this.onDragDrop as unknown) = undefined; + (this.onDragOver as unknown) = undefined; + super.deinit(); + } + + componentDidMount(): void { + super.componentDidMount(); + this.loadApplication(); + } + + async loadApplication() { + this.application.componentManager.setDesktopManager( + this.application.getDesktopService() + ); + await this.application.prepareForLaunch({ + receiveChallenge: async (challenge) => { + const challenges = this.state.challenges.slice(); + challenges.push(challenge); + this.setState({ challenges: challenges }); + }, + }); + await this.application.launch(); + } + + public removeChallenge = async (challenge: Challenge) => { + const challenges = this.state.challenges.slice(); + removeFromArray(challenges, challenge); + this.setState({ challenges: challenges }); + }; + + async onAppStart() { + super.onAppStart(); + this.setState({ + started: true, + needsUnlock: this.application.hasPasscode(), + }); + + this.application.componentManager.presentPermissionsDialog = + this.presentPermissionsDialog; + } + + async onAppLaunch() { + super.onAppLaunch(); + this.setState({ + launched: true, + needsUnlock: false, + }); + this.handleDemoSignInFromParams(); + } + + onUpdateAvailable() { + this.application.notifyWebEvent(WebAppEvent.NewUpdateAvailable); + } + + /** @override */ + async onAppEvent(eventName: ApplicationEvent) { + super.onAppEvent(eventName); + switch (eventName) { + case ApplicationEvent.LocalDatabaseReadError: + alertDialog({ + text: 'Unable to load local database. Please restart the app and try again.', + }); + break; + case ApplicationEvent.LocalDatabaseWriteError: + alertDialog({ + text: 'Unable to write to local database. Please restart the app and try again.', + }); + break; + } + } + + /** @override */ + async onAppStateEvent(eventName: AppStateEvent, data?: unknown) { + if (eventName === AppStateEvent.PanelResized) { + const { panel, collapsed } = data as PanelResizedData; + let appClass = ''; + if (panel === PANEL_NAME_NOTES && collapsed) { + appClass += 'collapsed-notes'; + } + if (panel === PANEL_NAME_NAVIGATION && collapsed) { + appClass += ' collapsed-navigation'; + } + this.setState({ appClass }); + } else if (eventName === AppStateEvent.WindowDidFocus) { + if (!(await this.application.isLocked())) { + this.application.sync(); + } + } + } + + addDragDropHandlers() { + /** + * Disable dragging and dropping of files (but allow text) into main SN interface. + * both 'dragover' and 'drop' are required to prevent dropping of files. + * This will not prevent extensions from receiving drop events. + */ + window.addEventListener('dragover', this.onDragOver, true); + window.addEventListener('drop', this.onDragDrop, true); + } + + onDragOver(event: DragEvent) { + if (event.dataTransfer?.files.length) { + event.preventDefault(); + } + } + + onDragDrop(event: DragEvent) { + if (event.dataTransfer?.files.length) { + event.preventDefault(); + void alertDialog({ + text: STRING_DEFAULT_FILE_ERROR, + }); + } + } + + async handleDemoSignInFromParams() { + if ( + window.location.href.includes('demo') && + !this.application.hasAccount() + ) { + await this.application.setCustomHost( + 'https://syncing-server-demo.standardnotes.com' + ); + this.application.signIn('demo@standardnotes.org', 'password'); + } + } + + presentPermissionsDialog = (dialog: PermissionDialog) => { + render( + , + document.body.appendChild(document.createElement('div')) + ); + }; + + render() { + return ( +
+ {!this.state.needsUnlock && this.state.launched && ( +
+ + + + + +
+ )} + + {!this.state.needsUnlock && this.state.launched && ( +
+ )} + + + + + + {this.state.challenges.map((challenge) => { + return ( +
+ +
+ ); + })} + + + + +
+ ); + } +} diff --git a/app/assets/javascripts/components/ChallengeModal.tsx b/app/assets/javascripts/components/ChallengeModal.tsx new file mode 100644 index 000000000..9a92c53e9 --- /dev/null +++ b/app/assets/javascripts/components/ChallengeModal.tsx @@ -0,0 +1,371 @@ +import { WebApplication } from '@/ui_models/application'; +import { Dialog } from '@reach/dialog'; +import { + ChallengeValue, + removeFromArray, + Challenge, + ChallengeReason, + ChallengePrompt, + ChallengeValidation, + ProtectionSessionDurations, +} from '@standardnotes/snjs'; +import { confirmDialog } from '@/services/alertService'; +import { STRING_SIGN_OUT_CONFIRMATION } from '@/strings'; +import { createRef } from 'preact'; +import { PureComponent } from '@/components/Abstract/PureComponent'; + +type InputValue = { + prompt: ChallengePrompt; + value: string | number | boolean; + invalid: boolean; +}; + +type Values = Record; + +type State = { + prompts: ChallengePrompt[]; + values: Partial; + processing: boolean; + forgotPasscode: boolean; + showForgotPasscodeLink: boolean; + processingPrompts: ChallengePrompt[]; + hasAccount: boolean; + protectedNoteAccessDuration: number; +}; + +type Props = { + challenge: Challenge; + application: WebApplication; + onDismiss: (challenge: Challenge) => void; +}; + +export class ChallengeModal extends PureComponent { + submitting = false; + protectionsSessionDurations = ProtectionSessionDurations; + protectionsSessionValidation = ChallengeValidation.ProtectionSessionDuration; + private initialFocusRef = createRef(); + + constructor(props: Props) { + super(props, props.application); + + const values = {} as Values; + const prompts = this.props.challenge.prompts; + for (const prompt of prompts) { + values[prompt.id] = { + prompt, + value: prompt.initialValue ?? '', + invalid: false, + }; + } + const showForgotPasscodeLink = [ + ChallengeReason.ApplicationUnlock, + ChallengeReason.Migration, + ].includes(this.props.challenge.reason); + this.state = { + prompts, + values, + processing: false, + forgotPasscode: false, + showForgotPasscodeLink, + hasAccount: this.application.hasAccount(), + processingPrompts: [], + protectedNoteAccessDuration: ProtectionSessionDurations[0].valueInSeconds, + }; + } + + componentDidMount(): void { + super.componentDidMount(); + + this.application.addChallengeObserver(this.props.challenge, { + onValidValue: (value) => { + this.state.values[value.prompt.id]!.invalid = false; + removeFromArray(this.state.processingPrompts, value.prompt); + this.reloadProcessingStatus(); + this.afterStateChange(); + }, + onInvalidValue: (value) => { + this.state.values[value.prompt.id]!.invalid = true; + /** If custom validation, treat all values together and not individually */ + if (!value.prompt.validates) { + this.setState({ processingPrompts: [], processing: false }); + } else { + removeFromArray(this.state.processingPrompts, value.prompt); + this.reloadProcessingStatus(); + } + this.afterStateChange(); + }, + onComplete: () => { + this.dismiss(); + }, + onCancel: () => { + this.dismiss(); + }, + }); + } + + deinit() { + (this.application as unknown) = undefined; + (this.props.challenge as unknown) = undefined; + super.deinit(); + } + + reloadProcessingStatus() { + return this.setState({ + processing: this.state.processingPrompts.length > 0, + }); + } + + destroyLocalData = async () => { + if ( + await confirmDialog({ + text: STRING_SIGN_OUT_CONFIRMATION, + confirmButtonStyle: 'danger', + }) + ) { + await this.application.signOut(); + this.dismiss(); + } + }; + + cancel = () => { + if (this.props.challenge.cancelable) { + this.application!.cancelChallenge(this.props.challenge); + } + }; + + onForgotPasscodeClick = () => { + this.setState({ + forgotPasscode: true, + }); + }; + + onTextValueChange = (prompt: ChallengePrompt) => { + const values = this.state.values; + values[prompt.id]!.invalid = false; + this.setState({ values }); + }; + + onNumberValueChange(prompt: ChallengePrompt, value: number) { + const values = this.state.values; + values[prompt.id]!.invalid = false; + values[prompt.id]!.value = value; + this.setState({ values }); + } + + validate() { + let failed = 0; + for (const prompt of this.state.prompts) { + const value = this.state.values[prompt.id]!; + if (typeof value.value === 'string' && value.value.length === 0) { + this.state.values[prompt.id]!.invalid = true; + failed++; + } + } + return failed === 0; + } + + submit = async () => { + if (!this.validate()) { + return; + } + if (this.submitting || this.state.processing) { + return; + } + this.submitting = true; + await this.setState({ processing: true }); + const values: ChallengeValue[] = []; + for (const inputValue of Object.values(this.state.values)) { + const rawValue = inputValue!.value; + const value = new ChallengeValue(inputValue!.prompt, rawValue); + values.push(value); + } + const processingPrompts = values.map((v) => v.prompt); + await this.setState({ + processingPrompts: processingPrompts, + processing: processingPrompts.length > 0, + }); + /** + * Unfortunately neccessary to wait 50ms so that the above setState call completely + * updates the UI to change processing state, before we enter into UI blocking operation + * (crypto key generation) + */ + setTimeout(() => { + if (values.length > 0) { + this.application.submitValuesForChallenge(this.props.challenge, values); + } else { + this.setState({ processing: false }); + } + this.submitting = false; + }, 50); + }; + + afterStateChange() { + this.render(); + } + + dismiss = () => { + this.props.onDismiss(this.props.challenge); + }; + + private renderChallengePrompts() { + return this.state.prompts.map((prompt, index) => ( + <> + {/** ProtectionSessionDuration can't just be an input field */} + {prompt.validation === ChallengeValidation.ProtectionSessionDuration ? ( +
+
+
Allow protected access for
+ {ProtectionSessionDurations.map((option) => ( + { + event.preventDefault(); + this.onNumberValueChange(prompt, option.valueInSeconds); + }} + > + {option.label} + + ))} +
+
+ ) : ( +
+
{ + event.preventDefault(); + this.submit(); + }} + > + { + const value = (event.target as HTMLInputElement).value; + this.state.values[prompt.id]!.value = value; + this.onTextValueChange(prompt); + }} + ref={index === 0 ? this.initialFocusRef : undefined} + placeholder={prompt.title} + type={prompt.secureTextEntry ? 'password' : 'text'} + /> +
+
+ )} + + {this.state.values[prompt.id]!.invalid && ( +
+ +
+ )} + + )); + } + + render() { + if (!this.state.prompts) { + return <>; + } + return ( + { + if (this.props.challenge.cancelable) { + this.cancel(); + } + }} + > +
+
+
+
+
+ {this.props.challenge.modalTitle} +
+
+
+
+
+ {this.props.challenge.heading} +
+ {this.props.challenge.subheading && ( +
+ {this.props.challenge.subheading} +
+ )} +
+ +
+ {this.renderChallengePrompts()} +
+
+
+ + {this.props.challenge.cancelable && ( + <> +
+ this.cancel()} + > + Cancel + + + )} +
+ {this.state.showForgotPasscodeLink && ( +
+ {this.state.forgotPasscode ? ( + <> +

+ {this.state.hasAccount + ? 'If you forgot your application passcode, your ' + + 'only option is to clear your local data from this ' + + 'device and sign back in to your account.' + : 'If you forgot your application passcode, your ' + + 'only option is to delete your data.'} +

+ { + this.destroyLocalData(); + }} + > + Delete Local Data + + + ) : ( + this.onForgotPasscodeClick()} + > + Forgot your passcode? + + )} +
+
+ )} +
+
+
+
+ ); + } +} diff --git a/app/assets/javascripts/components/ComponentView/index.tsx b/app/assets/javascripts/components/ComponentView/index.tsx index 436397b70..9f4931465 100644 --- a/app/assets/javascripts/components/ComponentView/index.tsx +++ b/app/assets/javascripts/components/ComponentView/index.tsx @@ -9,8 +9,13 @@ import { } from '@standardnotes/snjs'; import { WebApplication } from '@/ui_models/application'; import { FunctionalComponent } from 'preact'; -import { toDirective } from '@/components/utils'; -import { useCallback, useEffect, useRef, useState } from 'preact/hooks'; +import { + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from 'preact/hooks'; import { observer } from 'mobx-react-lite'; import { OfflineRestricted } from '@/components/ComponentView/OfflineRestricted'; import { UrlMissing } from '@/components/ComponentView/UrlMissing'; @@ -66,20 +71,6 @@ export const ComponentView: FunctionalComponent = observer( openSubscriptionDashboard(application); }, [application]); - useEffect(() => { - const loadTimeout = setTimeout(() => { - handleIframeTakingTooLongToLoad(); - }, MaxLoadThreshold); - - excessiveLoadingTimeout.current = loadTimeout; - - return () => { - excessiveLoadingTimeout.current && - clearTimeout(excessiveLoadingTimeout.current); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - const reloadValidityStatus = useCallback(() => { setFeatureStatus(componentViewer.getFeatureStatus()); if (!componentViewer.lockReadonly) { @@ -128,28 +119,35 @@ export const ComponentView: FunctionalComponent = observer( } else { document.addEventListener(VisibilityChangeKey, onVisibilityChange); } - }, [componentViewer, didAttemptReload, onVisibilityChange, requestReload]); + }, [didAttemptReload, onVisibilityChange, componentViewer, requestReload]); - useEffect(() => { - if (!iframeRef.current) { - return; - } + useMemo(() => { + const loadTimeout = setTimeout(() => { + handleIframeTakingTooLongToLoad(); + }, MaxLoadThreshold); - const iframe = iframeRef.current as HTMLIFrameElement; - iframe.onload = () => { - const contentWindow = iframe.contentWindow as Window; + excessiveLoadingTimeout.current = loadTimeout; + + return () => { excessiveLoadingTimeout.current && clearTimeout(excessiveLoadingTimeout.current); - - componentViewer.setWindow(contentWindow); - - setTimeout(() => { - setIsLoading(false); - setHasIssueLoading(false); - onLoad?.(component); - }, MSToWaitAfterIframeLoadToAvoidFlicker); }; - }, [onLoad, component, componentViewer]); + }, [handleIframeTakingTooLongToLoad]); + + const onIframeLoad = useCallback(() => { + const iframe = iframeRef.current as HTMLIFrameElement; + const contentWindow = iframe.contentWindow as Window; + excessiveLoadingTimeout.current && + clearTimeout(excessiveLoadingTimeout.current); + + componentViewer.setWindow(contentWindow); + + setTimeout(() => { + setIsLoading(false); + setHasIssueLoading(false); + onLoad?.(component); + }, MSToWaitAfterIframeLoadToAvoidFlicker); + }, [componentViewer, onLoad, component, excessiveLoadingTimeout]); useEffect(() => { const removeFeaturesChangedObserver = componentViewer.addEventObserver( @@ -236,6 +234,7 @@ export const ComponentView: FunctionalComponent = observer( {component.uuid && isComponentValid && (