diff --git a/.eslintrc.js b/.eslintrc.js index 1b0e906..1273c43 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -46,7 +46,7 @@ module.exports = { "error", { "escape": { - "taggedTemplates": ["escaped"] + "taggedTemplates": ["Utils.escaped"] } } ], diff --git a/.stylelintrc b/.stylelintrc index 5acc3e5..0161b95 100644 --- a/.stylelintrc +++ b/.stylelintrc @@ -10,6 +10,11 @@ "rules": { "declaration-block-no-duplicate-properties": true, "order/declaration-block-properties-alphabetical-order": true, + "property-no-unknown": [ + true, { + ignoreProperties: + ["inset-block-end", "inset-block-start"] + }], "property-blacklist": [ "/(min[-]|max[-])height/", "/width/", diff --git a/src/css/popup.css b/src/css/popup.css index 3ce65aa..293a970 100644 --- a/src/css/popup.css +++ b/src/css/popup.css @@ -91,7 +91,7 @@ table { } .scrollable { - border-block-start: 1px solid #f1f1f1; + flex: 1; inline-size: 100%; max-block-size: 400px; overflow: auto; @@ -107,6 +107,7 @@ table { /* Effect borrowed from tabs in Firefox, ensure that the element flexes to the full width */ .truncate-text { + inline-size: 100%; mask-image: linear-gradient(to left, transparent, black 1em); overflow: hidden; white-space: nowrap; @@ -206,6 +207,10 @@ table { --identity-icon: url("/img/usercontext.svg#chill"); } +[data-identity-icon="fence"] { + --identity-icon: url("/img/usercontext.svg#fence"); +} + #current-tab [data-identity-icon="default-tab"] { background: center center no-repeat url("/img/blank-tab.svg"); fill: currentColor; @@ -231,18 +236,6 @@ table { background-color: rgba(0, 0, 0, 0.05); } -/* Text links with actions */ - -.action-link:link { - color: var(--primary-action-color); - text-decoration: none; -} - -.action-link:active, -.action-link:hover { - text-decoration: underline; -} - /* Panels keep everything together */ .panel { display: flex; @@ -262,29 +255,11 @@ table { min-block-size: 360px; } -.panel .columns { - display: flex; - flex: 1; -} - .panel-content { flex: 1; + padding-block-start: 16px; } -/* Column panels for edit screens */ -.column-panel-content { - display: flex; - flex-direction: column; - inline-size: var(--column-panel-inline-size); -} - -.column-panel-content .panel-footer { - align-items: center; - display: flex; - justify-content: center; -} - -.column-panel-content .button, .panel-footer .button { align-items: center; block-size: 100%; @@ -293,28 +268,6 @@ table { justify-content: center; } -/* Column panels have a special back arrow */ -.panel-back-arrow { - align-items: center; - background: #ebebeb; - box-shadow: inset -2px 0 4px -2px rgba(0, 0, 0, 0.4); - display: flex; - flex: 0 0 32px; - flex-direction: column; - justify-content: center; -} - -.panel-back-arrow:hover, -.panel-back-arrow:focus { - background: #dedede; -} - -.back-arrow-img { - block-size: 16px; - inline-size: 16px; - transform: rotate(180deg); -} - /* Onboarding styles */ .onboarding * { text-align: center; @@ -406,217 +359,10 @@ manage things like container crud */ justify-content: center; } -.pop-button:hover, -.pop-button:focus, -.panel-footer-secondary:focus, -.panel-footer-secondary:hover { - background-color: rgba(0, 0, 0, 0.05); -} - -.pop-button:focus, -.panel-footer-secondary:focus { - background-color: rgba(0, 0, 0, 0.08); -} - -.pop-button a, -.panel-footer a, -.panel-footer-secondary a { +.panel-footer a { text-decoration: none; } -.pop-button-image { - block-size: 20px; - flex: 0 0 20px; - margin-block-end: auto; - margin-block-start: auto; - margin-inline-end: auto; - margin-inline-start: auto; -} - -.pop-button-image-small { - block-size: 12px; - flex: 0 0 12px; -} - -/* Panel Header */ -.panel-header { - align-items: center; - block-size: 48px; - display: flex; - justify-content: space-between; -} - -.panel-header .usercontext-icon { - inline-size: var(--icon-button-size); -} - -.column-panel-content .panel-header { - flex: 0 0 48px; - inline-size: 100%; -} - -.panel-header-text { - color: var(--text-normal-color); - flex: 1; - font-size: var(--font-size-heading); - font-weight: normal; - margin-block-end: 0; - margin-block-start: 0; - margin-inline-end: 0; - margin-inline-start: 0; - padding-block-end: 16px; - padding-block-start: 16px; - padding-inline-end: 16px; - padding-inline-start: 16px; -} - -#container-panel .panel-header { - background-color: #efefef; - block-size: 26px; - font-size: 14px; -} - -#container-panel .panel-header-text { - color: #727272; - font-size: 14px; - padding-block-end: 0; - padding-block-start: 0; - text-transform: uppercase; -} - -.container-panel-controls { - display: flex; - justify-content: flex-end; - margin-block-end: var(--block-line-space-size); - margin-block-start: var(--block-line-space-size); - margin-inline-end: var(--inline-item-space-size); - margin-inline-start: var(--inline-item-space-size); -} - -#container-panel #sort-containers-link { - align-items: center; - block-size: var(--block-url-label-size); - border: 1px solid #d8d8d8; - border-radius: var(--small-radius); - color: var(--title-text-color); - display: flex; - font-size: var(--small-text-size); - inline-size: var(--inline-button-size); - justify-content: center; - text-decoration: none; -} - -#container-panel #sort-containers-link:hover, -#container-panel #sort-containers-link:focus { - background: #f2f2f2; -} - -span ~ .panel-header-text { - padding-block-end: 0; - padding-block-start: 0; - padding-inline-end: 0; - padding-inline-start: 0; -} - -#current-tab { - align-items: center; - color: var(--text-normal-color); - display: grid; - font-size: var(--small-text-size); - grid-column-gap: var(--inline-item-space-size); - grid-row-gap: var(--block-line-space-size); - grid-template-columns: var(--icon-size) var(--icon-size) 1fr; - margin-block-end: var(--block-line-space-size); - margin-block-start: var(--block-line-separation-size); - margin-inline-end: var(--inline-start-size); - margin-inline-start: var(--inline-start-size); - max-inline-size: 100%; -} - -#current-tab img { - max-block-size: var(--icon-size); -} - -#current-tab > h3 { - color: var(--text-heading-color); - font-weight: normal; - grid-column: span 3; - margin-block-end: 0; - margin-block-start: 0; - margin-inline-end: 0; - margin-inline-start: 0; -} - -#current-page { - display: contents; -} - -#current-tab .page-title { - font-size: var(--font-size-heading); - grid-column: 2 / 4; -} - -#current-tab > label { - display: contents; - font-size: var(--small-text-size); -} - -#current-tab > label > input { - -moz-appearance: none; - block-size: var(--icon-size); - border: 1px solid #d8d8d8; - border-radius: var(--small-radius); - display: block; - grid-column-start: 2; - inline-size: var(--icon-size); - margin-block-end: 0; - margin-block-start: 0; - margin-inline-end: 0; - margin-inline-start: 0; -} - -#current-tab > label > input[disabled] { - background-color: #efefef; -} - -#current-tab > label > input:checked { - background-image: url("/img/check.svg"); - background-position: -1px -1px; - background-size: var(--icon-size); -} - -#current-container { - color: var(--identity-tab-color); - flex: 1; -} - -#current-tab > label > .usercontext-icon { - background-size: 16px; - block-size: 16px; - display: block; - flex: 0 0 20px; - inline-size: 20px; - margin-inline-end: 3px; - margin-inline-start: 3px; -} - -/* Rows used when iterating over panels */ -.container-panel-row { - align-items: center; - background-color: #fefefe !important; - border-block-end: 1px solid #f1f1f1; - box-sizing: border-box; - display: flex; - justify-content: space-between; -} - -.container-panel-row .container-name { - flex: 1; - max-inline-size: 160px; - padding-inline-end: 4px; - padding-inline-start: 4px; -} - .edit-containers-panel .userContext-wrapper { max-inline-size: calc(var(--overflow-size) + 203px); } @@ -633,11 +379,6 @@ span ~ .panel-header-text { transition: background-color 75ms; } -.container-panel-row:hover .clickable.userContext-wrapper, -.container-panel-row:focus .clickable.userContext-wrapper { - background: #f2f2f2; -} - .userContext-icon-wrapper { block-size: var(--icon-button-size); flex: 0 0 var(--icon-button-size); @@ -649,12 +390,20 @@ span ~ .panel-header-text { background-image: var(--identity-icon); background-position: center center; background-repeat: no-repeat; - background-size: 20px 20px; + background-size: 16px; block-size: 100%; fill: var(--identity-icon-color); filter: url('/img/filters.svg#fill'); } +.mac-icon { + background-image: url('/img/multiaccountcontainer-16.svg'); + background-position: center center; + background-repeat: no-repeat; + background-size: 16px; + block-size: 100%; +} + .container-panel-row:hover .clickable .usercontext-icon, .container-panel-row:focus .clickable .usercontext-icon, .container-panel-row .clickable:focus .usercontext-icon { @@ -681,62 +430,6 @@ span ~ .panel-header-text { justify-content: space-between; } -.edit-containers-text { - align-items: center; - block-size: 100%; - border-inline-end: solid 1px #d8d8d8; - display: flex; - flex: 1; - justify-content: center; -} - -.edit-containers-text a { - align-items: center; - block-size: 100%; - color: #0a0a0a; - display: flex; - flex: 1; - justify-content: center; -} - -/* Container info list */ -.container-info-tab-title { - display: flex; -} - -.container-info-tab-row:hover .container-info-tab-title .truncate-text { - inline-size: calc(var(--column-panel-inline-size) - 58px); -} - -#container-info-hideorshow { - margin-block-start: 4px; -} - -#container-info-movetabs-incompat { - font-size: 10px; - opacity: 0.3; -} - -.container-info-tab-row:not(.clickable), -.select-row:not(.clickable) { - opacity: 0.3; -} - -.container-close-tab { - transform: scale(0.7); - visibility: collapse; -} - -.container-info-tab-row:hover .container-close-tab { - opacity: 0.5; - visibility: visible; -} - -.container-info-tab-row .container-close-tab:hover { - opacity: 1; - visibility: visible; -} - .container-info-has-tabs, .container-info-tab-row { align-items: center; @@ -763,51 +456,6 @@ span ~ .panel-header-text { margin-inline-end: 0; } -.container-info-list { - display: flex; - flex-direction: column; - margin-block-start: 4px; - padding-block-start: 4px; -} - -.container-info-list tbody { - display: contents; -} - -.clickable { - cursor: pointer; -} - -.clickable:hover, -.clickable:focus { - background-color: #ebebeb; -} - -.edit-containers-exit-text { - align-items: center; - background: var(--primary-action-color); - block-size: 100%; - color: #fff; - display: inline-block; - justify-content: center; - padding-block-start: 6px; - padding-inline-start: 30%; -} - -.edit-containers-panel-footer { - background: var(--primary-action-color); -} - -.exit-edit-mode-link img { - block-size: 16px; - display: inline; - filter: grayscale(100%) brightness(5); - inline-size: 16px; - margin-inline-end: 5px; - transform: scaleX(-1); - vertical-align: bottom; -} - .delete-container-confirm { padding-inline-end: 20px; padding-inline-start: 20px; @@ -818,23 +466,6 @@ span ~ .panel-header-text { font-size: var(--font-size-heading); } -/* Form info */ -.column-panel-content form { - flex: 1; - padding-block-end: 16px; - padding-block-start: 16px; - padding-inline-end: 16px; - padding-inline-start: 16px; -} - -.edit-container-panel .columns { - overflow: hidden; /* Bugfix: issue 948 */ -} - -#edit-sites-assigned { - flex: 1000; /* Bugfix: issue 948 */ -} - #edit-sites-assigned h3 { font-size: 14px; font-weight: normal; @@ -854,21 +485,13 @@ span ~ .panel-header-text { margin-inline-end: 10px; } -.assigned-sites-list > div > .delete-assignment { - display: none; -} - -.assigned-sites-list > div:hover > .delete-assignment { - display: block; -} - .assigned-sites-list > div > .hostname { flex: 1; } .radio-choice > .radio-container { align-items: center; - block-size: 29px; + block-size: 25px; display: flex; flex: 0 0 calc(100% / var(--icon-fit)); } @@ -928,7 +551,7 @@ span ~ .panel-header-text { display: flex; flex-direction: row; flex-wrap: wrap; - inline-size: 100%; + inline-size: 80%; margin-block-end: 10px; margin-inline-end: 0; margin-inline-start: 0; @@ -953,10 +576,17 @@ span ~ .panel-header-text { padding-inline-start: 5px; } -.edit-container-panel legend { +.edit-container-panel legend, +.options-header { flex: 1 0; font-size: 14px !important; - padding-block-end: 6px; + margin-block-end: 4px; + margin-block-start: -6px; +} + +.options-header { + margin-block-end: 8px; + margin-block-start: 6px; } /* Achievement panel elements */ @@ -1008,3 +638,270 @@ span ~ .panel-header-text { .amo-rate-cta { background: #0f1126; } + +body { + color: #000; + font-family: 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; + font-size: 13px; + inline-size: 320px; + letter-spacing: -0.1px; + + --highlight-blue: #1296f8; + --hr-grey: #e3e3e3; + --text-grey: #737373; +} + +h3.title { + block-size: 40px; + color: #000; + font-size: 13px; + font-weight: bold; + inline-size: 100%; + letter-spacing: -0.1px; + line-height: 40px; + text-align: center; +} + +.menu { + border-style: none; + inline-size: 100%; +} + +.menu-item { + cursor: pointer; + height: 24px; + inline-size: 100%; + line-height: 24px; +} + +.disabled-menu-item { + color: grey; + cursor: default; + font-style: italic; +} + +.hover-highlight:hover, +.hover-highlight:focus { + background: var(--highlight-blue); + color: #fff; +} + +.menu-text { + line-height: 24px; +} + +.menu-icon { + display: block; + float: left; + height: 16px; + inline-size: 16px; + margin-block-end: 4px; + margin-block-start: 4px; + margin-inline-end: 8px; + margin-inline-start: 16px; + text-align: center; +} + +.menu-right-float { + display: inline-block; + float: right; + height: 24px; + inline-size: 60px; + text-align: right; +} + +.container-count { + opacity: 0.6; + padding-block-end: 0; + padding-block-start: 0; + padding-inline-end: 6px; + padding-inline-start: 0; + text-align: right; +} + +.menu-arrow { + display: inline-block; + float: right; + height: 24px; + inline-size: 18px; + padding-block-end: 6px; + padding-block-start: 6px; + padding-inline-end: 12px; + padding-inline-start: 0; + text-align: center; +} + +.menu-arrow img { + height: 12px; + inline-size: 12px; + padding-block-end: 2px; + padding-block-start: 2px; + padding-inline-end: 2px; + padding-inline-start: 2px; +} + +hr { + border: 0; + border-block-start: 1px solid var(--hr-grey); + display: block; + margin-block-end: 0; + margin-block-start: 6px; + margin-inline-end: 0; + margin-inline-start: 0; + padding-block-end: 6px; + padding-block-start: 0; + padding-inline-end: 0; + padding-inline-start: 0; +} + +.sub-header { + color: var(--text-grey); + height: 24px; + line-height: 24px; + padding-block-end: 0; + padding-block-start: 0; + padding-inline-end: 16px; + padding-inline-start: 16px; +} + +.edit-form { + color: var(--text-grey); + flex: 1; + padding-block-end: 0; + padding-block-start: 0; + padding-inline-end: 16px; + padding-inline-start: 16px; +} + +.identities-list { + margin-block-end: 41px; + margin-block-start: 0; + margin-inline-end: 0; + margin-inline-start: 0; +} + +.bottom-btn { + background-color: var(--hr-grey); + border: solid 1px #d9d9d9; + cursor: pointer; + height: 41px; + inline-size: 100%; + inset-block-end: 0; + line-height: 41px; + padding-block-end: 0; + padding-block-start: 0; + padding-inline-end: 16px; + padding-inline-start: 16px; + position: fixed; +} + +.delete-container { + background-color: #fff; + border-block-start: solid 1px var(--hr-grey); + cursor: default; + display: flex; + height: 65px; + inline-size: 100%; + justify-content: space-between; + padding-block-end: 27px; + padding-block-start: 9px; + padding-inline-end: 18px; + padding-inline-start: 17px; +} + +.delete-btn { + background-color: rgba(12, 12, 13, 0.1); + border: 0; + border-radius: 2px; + cursor: pointer; + height: 30px; + inline-size: 100%; + line-height: 30px; + text-align: center; +} + +.btn-return.arrow-left { + background-color: rgba(255, 255, 255, 1); + background-image: url("/img/arrow-icon-left.svg"); + border: 0; + cursor: pointer; + height: 1.2rem; + inline-size: 1.2rem; + inset-block-start: 15px; + left: 15px; + position: absolute; +} + +input { + border: solid 1px #bebebe; + border-radius: 2px; +} + +.form-header { + height: 23px; + line-height: 23px; + padding-block-end: 0; + padding-block-start: 0; + padding-inline-end: 0; + padding-inline-start: 0; +} + +.edit-container-panel-name-input { + height: 29px; +} + +.container-options { + height: 23px; +} + +.site-isolation { + inset-block-end: auto; + position: fixed; +} + +.options-label { + cursor: pointer; + padding-inline-start: 25px; +} + +.manage-assigned-sites-list { + color: var(--highlight-blue); +} + +.info-icon { + cursor: pointer; + height: 16px; + inline-size: 16px; + inset-block-start: 13px; + position: absolute; + right: 13px; + text-align: center; + text-decoration: none; +} + +.delete-warning { + padding-block-end: 8px; + padding-block-start: 8px; + padding-inline-end: 0; + padding-inline-start: 0; +} + +.trash-button { + display: inline-block; + float: right; + height: 16px; + inline-size: 16px; + margin-block-end: 4px; + margin-block-start: 4px; + margin-inline-end: 10px; + margin-inline-start: 0; + text-align: center; +} + +tr > td > .trash-button { + display: none; +} + +tr:hover > td > .trash-button { + display: block; +} diff --git a/src/img/Account.svg b/src/img/Account.svg index 4d049d4..27d6146 100644 --- a/src/img/Account.svg +++ b/src/img/Account.svg @@ -1 +1,3 @@ -account \ No newline at end of file +account \ No newline at end of file diff --git a/src/img/Sync.svg b/src/img/Sync.svg index d52b768..d67f786 100644 --- a/src/img/Sync.svg +++ b/src/img/Sync.svg @@ -1 +1,3 @@ -Sync \ No newline at end of file +Sync \ No newline at end of file diff --git a/src/img/amo-icon.svg b/src/img/amo-icon.svg index 69357cf..bafd00e 100644 --- a/src/img/amo-icon.svg +++ b/src/img/amo-icon.svg @@ -1 +1,3 @@ - Created with Sketch. \ No newline at end of file + Created with Sketch. \ No newline at end of file diff --git a/src/img/arrow-icon-left.svg b/src/img/arrow-icon-left.svg new file mode 100644 index 0000000..2060238 --- /dev/null +++ b/src/img/arrow-icon-left.svg @@ -0,0 +1,3 @@ + diff --git a/src/img/arrow-icon-right.svg b/src/img/arrow-icon-right.svg new file mode 100644 index 0000000..fa99e25 --- /dev/null +++ b/src/img/arrow-icon-right.svg @@ -0,0 +1,24 @@ + + + + + Arrow + Created with Sketch. + + + + + + + + + + + + + + + + diff --git a/src/img/blank-tab.svg b/src/img/blank-tab.svg index 351945b..bb3d5eb 100644 --- a/src/img/blank-tab.svg +++ b/src/img/blank-tab.svg @@ -1,3 +1,5 @@ - + \ No newline at end of file diff --git a/src/img/container-add.svg b/src/img/container-add.svg deleted file mode 100644 index 2c18378..0000000 --- a/src/img/container-add.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - -firefox - - - - diff --git a/src/img/container-arrow.svg b/src/img/container-arrow.svg deleted file mode 100644 index 2a4e2ba..0000000 --- a/src/img/container-arrow.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - diff --git a/src/img/container-close-tab.svg b/src/img/container-close-tab.svg index a36fa01..4c8aebc 100644 --- a/src/img/container-close-tab.svg +++ b/src/img/container-close-tab.svg @@ -1,3 +1,5 @@ - - + + \ No newline at end of file diff --git a/src/img/container-delete.svg b/src/img/container-delete.svg index 1e685d5..5fe16db 100644 --- a/src/img/container-delete.svg +++ b/src/img/container-delete.svg @@ -1,9 +1,11 @@ - + diff --git a/src/img/container-edit.svg b/src/img/container-edit.svg deleted file mode 100644 index 52c1df4..0000000 --- a/src/img/container-edit.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - diff --git a/src/img/container-hide.svg b/src/img/container-hide.svg deleted file mode 100644 index a684eaf..0000000 --- a/src/img/container-hide.svg +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - diff --git a/src/img/container-newtab.svg b/src/img/container-newtab.svg index 477ace6..f41e140 100644 --- a/src/img/container-newtab.svg +++ b/src/img/container-newtab.svg @@ -1,4 +1,7 @@ + diff --git a/src/img/container-openin-16.svg b/src/img/container-openin-16.svg new file mode 100644 index 0000000..6786b5e --- /dev/null +++ b/src/img/container-openin-16.svg @@ -0,0 +1,9 @@ + + + + + + + diff --git a/src/img/container-site-d-192.png b/src/img/container-site-d-192.png deleted file mode 100644 index f1b56c8..0000000 Binary files a/src/img/container-site-d-192.png and /dev/null differ diff --git a/src/img/container-site-light.svg b/src/img/container-site-light.svg deleted file mode 100644 index 410fe3a..0000000 --- a/src/img/container-site-light.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/img/container-site-w-192.png b/src/img/container-site-w-192.png deleted file mode 100644 index 3a96317..0000000 Binary files a/src/img/container-site-w-192.png and /dev/null differ diff --git a/src/img/container-site-w-24.png b/src/img/container-site-w-24.png deleted file mode 100644 index 95c9d75..0000000 Binary files a/src/img/container-site-w-24.png and /dev/null differ diff --git a/src/img/container-site-w-48.png b/src/img/container-site-w-48.png deleted file mode 100644 index d3cfc6f..0000000 Binary files a/src/img/container-site-w-48.png and /dev/null differ diff --git a/src/img/container-site-w-96.png b/src/img/container-site-w-96.png deleted file mode 100644 index e137c22..0000000 Binary files a/src/img/container-site-w-96.png and /dev/null differ diff --git a/src/img/container-site.svg b/src/img/container-site.svg deleted file mode 100644 index 689d150..0000000 --- a/src/img/container-site.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/img/container-sort.svg b/src/img/container-sort.svg deleted file mode 100644 index 3d14a48..0000000 --- a/src/img/container-sort.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - diff --git a/src/img/container-unhide.svg b/src/img/container-unhide.svg deleted file mode 100644 index d12b9a3..0000000 --- a/src/img/container-unhide.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - diff --git a/src/img/info-thin-16.svg b/src/img/info-thin-16.svg new file mode 100644 index 0000000..c3edf49 --- /dev/null +++ b/src/img/info-thin-16.svg @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/src/img/movetowindow-16.svg b/src/img/movetowindow-16.svg new file mode 100644 index 0000000..80181a3 --- /dev/null +++ b/src/img/movetowindow-16.svg @@ -0,0 +1,7 @@ + + + + + diff --git a/src/img/multiaccountcontainer-16-dark.svg b/src/img/multiaccountcontainer-16-dark.svg new file mode 100644 index 0000000..3c1e24c --- /dev/null +++ b/src/img/multiaccountcontainer-16-dark.svg @@ -0,0 +1,9 @@ + + + + + + + \ No newline at end of file diff --git a/src/img/multiaccountcontainer-16.svg b/src/img/multiaccountcontainer-16.svg new file mode 100644 index 0000000..d6a13d1 --- /dev/null +++ b/src/img/multiaccountcontainer-16.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/img/check.svg b/src/img/new-16.svg similarity index 63% rename from src/img/check.svg rename to src/img/new-16.svg index bcbcfc0..b759168 100644 --- a/src/img/check.svg +++ b/src/img/new-16.svg @@ -2,5 +2,5 @@ - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> - + diff --git a/src/img/password-hide.svg b/src/img/password-hide.svg new file mode 100644 index 0000000..af7818d --- /dev/null +++ b/src/img/password-hide.svg @@ -0,0 +1,9 @@ + + + + + + + diff --git a/src/img/refresh-16.svg b/src/img/refresh-16.svg new file mode 100644 index 0000000..2e40ef6 --- /dev/null +++ b/src/img/refresh-16.svg @@ -0,0 +1,6 @@ + + + + diff --git a/src/img/sort-16_1.svg b/src/img/sort-16_1.svg new file mode 100644 index 0000000..83ae0ee --- /dev/null +++ b/src/img/sort-16_1.svg @@ -0,0 +1,6 @@ + + + + diff --git a/src/img/tab-new-16.svg b/src/img/tab-new-16.svg new file mode 100644 index 0000000..d8c7ba6 --- /dev/null +++ b/src/img/tab-new-16.svg @@ -0,0 +1,6 @@ + + + + diff --git a/src/img/usercontext.svg b/src/img/usercontext.svg index f58067a..fb48e4a 100644 --- a/src/img/usercontext.svg +++ b/src/img/usercontext.svg @@ -13,6 +13,7 @@ display: none; } + + + + + + + + + diff --git a/src/img/webicon-facebook.svg b/src/img/webicon-facebook.svg index 39a3cae..c87adaf 100755 --- a/src/img/webicon-facebook.svg +++ b/src/img/webicon-facebook.svg @@ -1,4 +1,7 @@ + diff --git a/src/img/webicon-twitter.svg b/src/img/webicon-twitter.svg index 6b05f3f..6636eaa 100755 --- a/src/img/webicon-twitter.svg +++ b/src/img/webicon-twitter.svg @@ -1,4 +1,7 @@ + diff --git a/src/js/.eslintrc.js b/src/js/.eslintrc.js index 11cd71a..4941e75 100644 --- a/src/js/.eslintrc.js +++ b/src/js/.eslintrc.js @@ -8,6 +8,7 @@ module.exports = { "backgroundLogic": true, "identityState": true, "messageHandler": true, - "sync": true + "sync": true, + "Utils": true } }; diff --git a/src/js/background/assignManager.js b/src/js/background/assignManager.js index 9dd6e88..deee22a 100644 --- a/src/js/background/assignManager.js +++ b/src/js/background/assignManager.js @@ -205,10 +205,33 @@ window.assignManager = { return {}; } const userContextId = this.getUserContextIdFromCookieStore(tab); - if (!siteSettings - || userContextId === siteSettings.userContextId - || this.storageArea.isExempted(options.url, tab.id)) { - return {}; + + // https://github.com/mozilla/multi-account-containers/issues/847 + // + // Handle the case where this request's URL is not assigned to any particular + // container. We must do the following check: + // + // If the current tab's container is "unlocked", we can just go ahead + // and open the URL in the current tab, since an "unlocked" container accepts + // any-and-all sites. + // + // But if the current tab's container has been "locked" by the user, then we must + // re-open the page in the default container, because the user doesn't want random + // sites polluting their locked container. + // + // For example: + // - the current tab's container is locked and only allows "www.google.com" + // - the incoming request is for "www.amazon.com", which has no specific container assignment + // - in this case, we must re-open "www.amazon.com" in a new tab in the default container + const siteIsolatedReloadInDefault = + await this._maybeSiteIsolatedReloadInDefault(siteSettings, tab); + + if (!siteIsolatedReloadInDefault) { + if (!siteSettings + || userContextId === siteSettings.userContextId + || this.storageArea.isExempted(options.url, tab.id)) { + return {}; + } } const removeTab = backgroundLogic.NEW_TAB_PAGES.has(tab.url) || (messageHandler.lastCreatedTab @@ -257,15 +280,24 @@ window.assignManager = { } } - this.reloadPageInContainer( - options.url, - userContextId, - siteSettings.userContextId, - tab.index + 1, - tab.active, - siteSettings.neverAsk, - openTabId - ); + if (siteIsolatedReloadInDefault) { + this.reloadPageInDefaultContainer( + options.url, + tab.index + 1, + tab.active, + openTabId + ); + } else { + this.reloadPageInContainer( + options.url, + userContextId, + siteSettings.userContextId, + tab.index + 1, + tab.active, + siteSettings.neverAsk, + openTabId + ); + } this.calculateContextMenu(tab); /* Removal of existing tabs: @@ -299,6 +331,29 @@ window.assignManager = { }; }, + async _maybeSiteIsolatedReloadInDefault(siteSettings, tab) { + // Tab doesn't support cookies, so containers not supported either. + if (!("cookieStoreId" in tab)) { + return false; + } + + // Requested page has been assigned to a specific container. + // I.e. it will be opened in that container anyway, so we don't need to check if the + // current tab's container is locked or not. + if (siteSettings) { + return false; + } + + //tab is alredy reopening in the default container + if (tab.cookieStoreId === "firefox-default") { + return false; + } + // Requested page is not assigned to a specific container. If the current tab's container + // is locked, then the page must be reloaded in the default container. + const currentContainerState = await identityState.storageArea.get(tab.cookieStoreId); + return currentContainerState && currentContainerState.isIsolated; + }, + init() { browser.contextMenus.onClicked.addListener((info, tab) => { info.bookmarkId ? @@ -475,7 +530,6 @@ window.assignManager = { async _setOrRemoveAssignment(tabId, pageUrl, userContextId, remove) { let actionName; - // https://github.com/mozilla/testpilot-containers/issues/626 // Context menu has stored context IDs as strings, so we need to coerce // the value to a string for accurate checking @@ -500,16 +554,40 @@ window.assignManager = { userContextId, neverAsk: false }, exemptedTabIds); - actionName = "added"; + actionName = "assigned site to always open in this container"; } else { + // Remove assignment await this.storageArea.remove(pageUrl); - actionName = "removed"; + + actionName = "removed from assigned sites list"; + + // remove site isolation if now empty + await this._maybeRemoveSiteIsolation(userContextId); } - browser.tabs.sendMessage(tabId, { - text: `Successfully ${actionName} site to always open in this container` - }); - const tab = await browser.tabs.get(tabId); - this.calculateContextMenu(tab); + + if (tabId) { + const tab = await browser.tabs.get(tabId); + setTimeout(function(){ + browser.tabs.sendMessage(tabId, { + text: `Successfully ${actionName}` + }); + }, 1000); + + + this.calculateContextMenu(tab); + } + }, + + async _maybeRemoveSiteIsolation(userContextId) { + const assignments = await this.storageArea.getByContainer(userContextId); + const hasAssignments = assignments && Object.keys(assignments).length > 0; + if (hasAssignments) { + return; + } + await backgroundLogic.addRemoveSiteIsolation( + backgroundLogic.cookieStoreId(userContextId), + true + ); }, async _getAssignment(tab) { @@ -589,13 +667,35 @@ window.assignManager = { }); }, + reloadPageInDefaultContainer(url, index, active, openerTabId) { + // To create a new tab in the default container, it is easiest just to omit the + // cookieStoreId entirely. + // + // Unfortunately, if you create a new tab WITHOUT a cookieStoreId but WITH an openerTabId, + // then the new tab automatically inherits the opener tab's cookieStoreId. + // I.e. it opens in the wrong container! + // + // So we have to explicitly pass in a cookieStoreId when creating the tab, since we + // are specifying the openerTabId. There doesn't seem to be any way + // to look up the default container's cookieStoreId programatically, so sadly + // we have to hardcode it here as "firefox-default". This is potentially + // not cross-browser compatible. + // + // Note that we could have just omitted BOTH cookieStoreId and openerTabId. But the + // drawback then is that if the user later closes the newly-created tab, the browser + // does not automatically return to the original opener tab. To get this desired behaviour, + // we MUST specify the openerTabId when creating the new tab. + const cookieStoreId = "firefox-default"; + browser.tabs.create({url, cookieStoreId, index, active, openerTabId}); + }, + reloadPageInContainer(url, currentUserContextId, userContextId, index, active, neverAsk = false, openerTabId = null) { const cookieStoreId = backgroundLogic.cookieStoreId(userContextId); const loadPage = browser.extension.getURL("confirm-page.html"); // False represents assignment is not permitted // If the user has explicitly checked "Never Ask Again" on the warning page we will send them straight there if (neverAsk) { - browser.tabs.create({url, cookieStoreId, index, active, openerTabId}); + return browser.tabs.create({url, cookieStoreId, index, active, openerTabId}); } else { let confirmUrl = `${loadPage}?url=${this.encodeURLProperty(url)}&cookieStoreId=${cookieStoreId}`; let currentCookieStoreId; @@ -603,7 +703,7 @@ window.assignManager = { currentCookieStoreId = backgroundLogic.cookieStoreId(currentUserContextId); confirmUrl += `¤tCookieStoreId=${currentCookieStoreId}`; } - browser.tabs.create({ + return browser.tabs.create({ url: confirmUrl, cookieStoreId: currentCookieStoreId, openerTabId, diff --git a/src/js/background/backgroundLogic.js b/src/js/background/backgroundLogic.js index d68e51e..7643221 100644 --- a/src/js/background/backgroundLogic.js +++ b/src/js/background/backgroundLogic.js @@ -6,7 +6,20 @@ const backgroundLogic = { "about:home", "about:blank" ]), + NUMBER_OF_KEYBOARD_SHORTCUTS: 10, unhideQueue: [], + init() { + browser.commands.onCommand.addListener(function (command) { + for (let i=0; i < backgroundLogic.NUMBER_OF_KEYBOARD_SHORTCUTS; i++) { + const key = "open_container_" + i; + const cookieStoreId = identityState.keyboardShortcut[key]; + if (command === key) { + if (cookieStoreId === "none") return; + browser.tabs.create({cookieStoreId}); + } + } + }); + }, async getExtensionInfo() { const manifestPath = browser.extension.getURL("manifest.json"); @@ -123,6 +136,20 @@ const backgroundLogic = { } }, + // https://github.com/mozilla/multi-account-containers/issues/847 + async addRemoveSiteIsolation(cookieStoreId, remove = false) { + const containerState = await identityState.storageArea.get(cookieStoreId); + try { + if ("isIsolated" in containerState || remove) { + delete containerState.isIsolated; + } else { + containerState.isIsolated = "locked"; + } + return await identityState.storageArea.set(cookieStoreId, containerState); + } catch (error) { + console.error(`No container: ${cookieStoreId}`); + } + }, async moveTabsToWindow(options) { const requiredArguments = ["cookieStoreId", "windowId"]; @@ -229,7 +256,8 @@ const backgroundLogic = { hasHiddenTabs: !!containerState.hiddenTabs.length, hasOpenTabs: !!openTabs.length, numberOfHiddenTabs: containerState.hiddenTabs.length, - numberOfOpenTabs: openTabs.length + numberOfOpenTabs: openTabs.length, + isIsolated: !!containerState.isIsolated }; return; }); @@ -329,6 +357,10 @@ const backgroundLogic = { }, cookieStoreId(userContextId) { + if(userContextId === 0) return "firefox-default"; return `firefox-container-${userContextId}`; } -}; \ No newline at end of file +}; + + +backgroundLogic.init(); \ No newline at end of file diff --git a/src/js/background/identityState.js b/src/js/background/identityState.js index 8c01eb7..9114240 100644 --- a/src/js/background/identityState.js +++ b/src/js/background/identityState.js @@ -1,4 +1,5 @@ window.identityState = { + keyboardShortcut: {}, storageArea: { area: browser.storage.local, @@ -27,7 +28,7 @@ window.identityState = { await this.set(cookieStoreId, defaultContainerState); return defaultContainerState; } - throw new Error (`${cookieStoreId} not found`); + return false; }, set(cookieStoreId, data) { @@ -42,6 +43,29 @@ window.identityState = { return this.area.remove([storeKey]); }, + async setKeyboardShortcut(shortcutId, cookieStoreId) { + identityState.keyboardShortcut[shortcutId] = cookieStoreId; + return this.area.set({[shortcutId]: cookieStoreId}); + }, + + async loadKeyboardShortcuts () { + const identities = await browser.contextualIdentities.query({}); + for (let i=0; i < backgroundLogic.NUMBER_OF_KEYBOARD_SHORTCUTS; i++) { + const key = "open_container_" + i; + const storageObject = await this.area.get(key); + if (storageObject[key]){ + identityState.keyboardShortcut[key] = storageObject[key]; + continue; + } + if (identities[i]) { + identityState.keyboardShortcut[key] = identities[i].cookieStoreId; + continue; + } + identityState.keyboardShortcut[key] = "none"; + } + return identityState.keyboardShortcut; + }, + /* * Looks for abandoned identity keys in local storage, and makes sure all * identities registered in the browser are also in local storage. (this @@ -72,7 +96,8 @@ window.identityState = { } } } - } + }, + }, _createTabObject(tab) { @@ -153,8 +178,14 @@ window.identityState = { macAddonUUID: uuidv4() }; }, + + init() { + this.storageArea.loadKeyboardShortcuts(); + } }; +identityState.init(); + function uuidv4() { // https://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c => diff --git a/src/js/background/messageHandler.js b/src/js/background/messageHandler.js index f4236f1..b3270e5 100644 --- a/src/js/background/messageHandler.js +++ b/src/js/background/messageHandler.js @@ -6,10 +6,17 @@ const messageHandler = { init() { // Handles messages from webextension code - browser.runtime.onMessage.addListener((m) => { + browser.runtime.onMessage.addListener(async (m) => { let response; + let tab; switch (m.method) { + case "getShortcuts": + response = identityState.storageArea.loadKeyboardShortcuts(); + break; + case "setShortcut": + identityState.storageArea.setKeyboardShortcut(m.shortcut, m.cookieStoreId); + break; case "resetSync": response = sync.resetSync(); break; @@ -25,6 +32,9 @@ const messageHandler = { case "neverAsk": assignManager._neverAsk(m); break; + case "addRemoveSiteIsolation": + response = backgroundLogic.addRemoveSiteIsolation(m.cookieStoreId); + break; case "getAssignment": response = browser.tabs.get(m.tabId).then((tab) => { return assignManager._getAssignment(tab); @@ -36,9 +46,7 @@ const messageHandler = { case "setOrRemoveAssignment": // m.tabId is used for where to place the in content message // m.url is the assignment to be removed/added - response = browser.tabs.get(m.tabId).then((tab) => { - return assignManager._setOrRemoveAssignment(tab.id, m.url, m.userContextId, m.value); - }); + response = assignManager._setOrRemoveAssignment(m.tabId, m.url, m.userContextId, m.value); break; case "sortTabs": backgroundLogic.sortTabs(); @@ -73,6 +81,31 @@ const messageHandler = { case "exemptContainerAssignment": response = assignManager._exemptTab(m); break; + case "reloadInContainer": + response = assignManager.reloadPageInContainer( + m.url, + m.currentUserContextId, + m.newUserContextId, + m.tabIndex, + m.active, + true + ); + break; + case "assignAndReloadInContainer": + tab = await assignManager.reloadPageInContainer( + m.url, + m.currentUserContextId, + m.newUserContextId, + m.tabIndex, + m.active, + true + ); + // m.tabId is used for where to place the in content message + // m.url is the assignment to be removed/added + response = browser.tabs.get(tab.id).then((tab) => { + return assignManager._setOrRemoveAssignment(tab.id, m.url, m.newUserContextId, m.value); + }); + break; } return response; }); diff --git a/src/js/options.js b/src/js/options.js index c225e63..2d5f910 100644 --- a/src/js/options.js +++ b/src/js/options.js @@ -1,3 +1,5 @@ +const NUMBER_OF_KEYBOARD_SHORTCUTS = 10; + async function requestPermissions() { const checkbox = document.querySelector("#bookmarksPermissions"); if (checkbox.checked) { @@ -22,7 +24,7 @@ async function enableDisableSync() { browser.runtime.sendMessage({ method: "resetSync" }); } -async function restoreOptions() { +async function setupOptions() { const hasPermission = await browser.permissions.contains({permissions: ["bookmarks"]}); const { syncEnabled } = await browser.storage.local.get("syncEnabled"); if (hasPermission) { @@ -33,9 +35,56 @@ async function restoreOptions() { } else { document.querySelector("#syncCheck").checked = false; } + setupContainerShortcutSelects(); } +async function setupContainerShortcutSelects () { + const keyboardShortcut = await browser.runtime.sendMessage({method: "getShortcuts"}); + const identities = await browser.contextualIdentities.query({}); + const fragment = document.createDocumentFragment(); + const noneOption = document.createElement("option"); + noneOption.value = "none"; + noneOption.id = "none"; + noneOption.textContent = "None"; + fragment.append(noneOption); -document.addEventListener("DOMContentLoaded", restoreOptions); + for (const identity of identities) { + const option = document.createElement("option"); + option.value = identity.cookieStoreId; + option.id = identity.cookieStoreId; + option.textContent = identity.name; + fragment.append(option); + } + + for (let i=0; i < NUMBER_OF_KEYBOARD_SHORTCUTS; i++) { + const shortcutKey = "open_container_"+i; + const shortcutSelect = document.getElementById(shortcutKey); + shortcutSelect.appendChild(fragment.cloneNode(true)); + if (keyboardShortcut && keyboardShortcut[shortcutKey]) { + const cookieStoreId = keyboardShortcut[shortcutKey]; + shortcutSelect.querySelector("#" + cookieStoreId).selected = true; + } + } +} + +function storeShortcutChoice (event) { + browser.runtime.sendMessage({ + method: "setShortcut", + shortcut: event.target.id, + cookieStoreId: event.target.value + }); +} + +function resetOnboarding() { + browser.storage.local.set({"onboarding-stage": 0}); +} + +document.addEventListener("DOMContentLoaded", setupOptions); document.querySelector("#bookmarksPermissions").addEventListener( "change", requestPermissions); document.querySelector("#syncCheck").addEventListener( "change", enableDisableSync); +document.querySelector("button").addEventListener("click", resetOnboarding); + +for (let i=0; i < NUMBER_OF_KEYBOARD_SHORTCUTS; i++) { + document.querySelector("#open_container_"+i) + .addEventListener("change", storeShortcutChoice); +} \ No newline at end of file diff --git a/src/js/pageAction.js b/src/js/pageAction.js new file mode 100644 index 0000000..91445ce --- /dev/null +++ b/src/js/pageAction.js @@ -0,0 +1,35 @@ +async function init() { + const fragment = document.createDocumentFragment(); + + const identities = await browser.contextualIdentities.query({}); + + identities.forEach(identity => { + const tr = document.createElement("tr"); + tr.classList.add("menu-item", "hover-highlight"); + const td = document.createElement("td"); + + td.innerHTML = Utils.escaped` + + ${identity.name}`; + + tr.appendChild(td); + fragment.appendChild(tr); + + Utils.addEnterHandler(tr, async () => { + Utils.alwaysOpenInContainer(identity); + window.close(); + }); + }); + + const list = document.querySelector("#picker-identities-list"); + + list.innerHTML = ""; + list.appendChild(fragment); +} + +init(); diff --git a/src/js/popup.js b/src/js/popup.js index 1349ffa..904e864 100644 --- a/src/js/popup.js +++ b/src/js/popup.js @@ -2,8 +2,8 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ -const CONTAINER_HIDE_SRC = "/img/container-hide.svg"; -const CONTAINER_UNHIDE_SRC = "/img/container-unhide.svg"; +const CONTAINER_HIDE_SRC = "/img/password-hide.svg"; +const CONTAINER_UNHIDE_SRC = "/img/password-hide.svg"; const DEFAULT_COLOR = "blue"; const DEFAULT_ICON = "circle"; @@ -20,47 +20,16 @@ const P_ONBOARDING_5 = "onboarding5"; const P_ONBOARDING_6 = "onboarding6"; const P_ONBOARDING_7 = "onboarding7"; const P_CONTAINERS_LIST = "containersList"; -const P_CONTAINERS_EDIT = "containersEdit"; +const OPEN_NEW_CONTAINER_PICKER = "new-tab"; +const MANAGE_CONTAINERS_PICKER = "manage"; +const REOPEN_IN_CONTAINER_PICKER = "reopen-in"; +const ALWAYS_OPEN_IN_PICKER = "always-open-in"; const P_CONTAINER_INFO = "containerInfo"; const P_CONTAINER_EDIT = "containerEdit"; const P_CONTAINER_DELETE = "containerDelete"; const P_CONTAINERS_ACHIEVEMENT = "containersAchievement"; +const P_CONTAINER_ASSIGNMENTS = "containerAssignments"; -/** - * Escapes any occurances of &, ", <, > or / with XML entities. - * - * @param {string} str - * The string to escape. - * @return {string} The escaped string. - */ -function escapeXML(str) { - const replacements = { "&": "&", "\"": """, "'": "'", "<": "<", ">": ">", "/": "/" }; - return String(str).replace(/[&"'<>/]/g, m => replacements[m]); -} - -/** - * A tagged template function which escapes any XML metacharacters in - * interpolated values. - * - * @param {Array} strings - * An array of literal strings extracted from the templates. - * @param {Array} values - * An array of interpolated values extracted from the template. - * @returns {string} - * The result of the escaped values interpolated with the literal - * strings. - */ -function escaped(strings, ...values) { - const result = []; - - for (const [i, string] of strings.entries()) { - result.push(string); - if (i < values.length) - result.push(escapeXML(values[i])); - } - - return result.join(""); -} async function getExtensionInfo() { const manifestPath = browser.extension.getURL("manifest.json"); @@ -74,7 +43,7 @@ const Logic = { _identities: [], _currentIdentity: null, _currentPanel: null, - _previousPanel: null, + _previousPanelPath: [], _panels: {}, _onboardingVariation: null, @@ -197,50 +166,23 @@ const Logic = { } }, - addEnterHandler(element, handler) { - element.addEventListener("click", (e) => { - handler(e); - }); - element.addEventListener("keydown", (e) => { - if (e.keyCode === 13) { - e.preventDefault(); - handler(e); - } - }); - }, - - userContextId(cookieStoreId = "") { - const userContextId = cookieStoreId.replace("firefox-container-", ""); - return (userContextId !== cookieStoreId) ? Number(userContextId) : false; - }, - - async currentTab() { - const activeTabs = await browser.tabs.query({ active: true, windowId: browser.windows.WINDOW_ID_CURRENT }); - if (activeTabs.length > 0) { - return activeTabs[0]; - } - return false; - }, - async numTabs() { const activeTabs = await browser.tabs.query({ windowId: browser.windows.WINDOW_ID_CURRENT }); return activeTabs.length; }, - _disableMoveTabs(message) { - const moveTabsEl = document.querySelector("#container-info-movetabs"); - const fragment = document.createDocumentFragment(); - const incompatEl = document.createElement("div"); + _disableMenuItem(message, elementToDisable = document.querySelector("#move-to-new-window")) { + elementToDisable.setAttribute("title", message); + elementToDisable.removeAttribute("tabindex"); + elementToDisable.classList.remove("hover-highlight"); + elementToDisable.classList.add("disabled-menu-item"); + }, - moveTabsEl.classList.remove("clickable"); - moveTabsEl.setAttribute("title", message); - - fragment.appendChild(incompatEl); - incompatEl.setAttribute("id", "container-info-movetabs-incompat"); - incompatEl.textContent = message; - incompatEl.classList.add("container-info-tab-row"); - - moveTabsEl.parentNode.insertBefore(fragment, moveTabsEl.nextSibling); + _enableMenuItems(elementToEnable = document.querySelector("#move-to-new-window")) { + elementToEnable.removeAttribute("title"); + elementToEnable.setAttribute("tabindex", "0"); + elementToEnable.classList.add("hover-highlight"); + elementToEnable.classList.remove("disabled-menu-item"); }, async refreshIdentities() { @@ -260,6 +202,7 @@ const Logic = { identity.hasHiddenTabs = stateObject.hasHiddenTabs; identity.numberOfHiddenTabs = stateObject.numberOfHiddenTabs; identity.numberOfOpenTabs = stateObject.numberOfOpenTabs; + identity.isIsolated = stateObject.isIsolated; } return identity; }); @@ -275,13 +218,15 @@ const Logic = { } }, - async showPanel(panel, currentIdentity = null) { + async showPanel(panel, currentIdentity = null, backwards = false) { // Invalid panel... ?!? if (!(panel in this._panels)) { throw new Error("Something really bad happened. Unknown panel: " + panel); } + if (!backwards || !this._currentPanel) { + this._previousPanelPath.push(this._currentPanel); + } - this._previousPanel = this._currentPanel; this._currentPanel = panel; this._currentIdentity = currentIdentity; @@ -308,11 +253,10 @@ const Logic = { }, showPreviousPanel() { - if (!this._previousPanel) { + if (!this._previousPanelPath) { throw new Error("Current panel not set!"); } - - this.showPanel(this._previousPanel, this._currentIdentity); + this.showPanel(this._previousPanelPath.pop(), this._currentIdentity, true); }, registerPanel(panelName, panelObject) { @@ -333,7 +277,7 @@ const Logic = { currentUserContextId() { const identity = Logic.currentIdentity(); - return Logic.userContextId(identity.cookieStoreId); + return Utils.userContextId(identity.cookieStoreId); }, currentCookieStoreId() { @@ -369,16 +313,6 @@ const Logic = { }); }, - setOrRemoveAssignment(tabId, url, userContextId, value) { - return browser.runtime.sendMessage({ - method: "setOrRemoveAssignment", - tabId, - url, - userContextId, - value - }); - }, - generateIdentityName() { const defaultName = "Container #"; const ids = []; @@ -405,6 +339,16 @@ const Logic = { const panelItem = this._panels[this._currentPanel]; return document.querySelector(this.getPanelSelector(panelItem)); }, + + listenToPickerBackButton() { + const closeContEl = document.querySelector("#close-container-picker-panel"); + if (!this._listenerSet) { + Utils.addEnterHandler(closeContEl, () => { + Logic.showPreviousPanel(); + }); + this._listenerSet = true; + } + } }; // P_ONBOARDING_1: First page for Onboarding. @@ -418,7 +362,7 @@ Logic.registerPanel(P_ONBOARDING_1, { initialize() { // Let's move to the next panel. [...document.querySelectorAll(".onboarding-start-button")].forEach(startElement => { - Logic.addEnterHandler(startElement, async () => { + Utils.addEnterHandler(startElement, async () => { await Logic.setOnboardingStage(1); Logic.showPanel(P_ONBOARDING_2); }); @@ -442,7 +386,7 @@ Logic.registerPanel(P_ONBOARDING_2, { initialize() { // Let's move to the containers list panel. [...document.querySelectorAll(".onboarding-next-button")].forEach(nextElement => { - Logic.addEnterHandler(nextElement, async () => { + Utils.addEnterHandler(nextElement, async () => { await Logic.setOnboardingStage(2); Logic.showPanel(P_ONBOARDING_3); }); @@ -466,7 +410,7 @@ Logic.registerPanel(P_ONBOARDING_3, { initialize() { // Let's move to the containers list panel. [...document.querySelectorAll(".onboarding-almost-done-button")].forEach(almostElement => { - Logic.addEnterHandler(almostElement, async () => { + Utils.addEnterHandler(almostElement, async () => { await Logic.setOnboardingStage(3); Logic.showPanel(P_ONBOARDING_4); }); @@ -488,7 +432,7 @@ Logic.registerPanel(P_ONBOARDING_4, { // This method is called when the object is registered. initialize() { // Let's move to the containers list panel. - Logic.addEnterHandler(document.querySelector("#onboarding-done-button"), async () => { + Utils.addEnterHandler(document.querySelector("#onboarding-done-button"), async () => { await Logic.setOnboardingStage(4); Logic.showPanel(P_ONBOARDING_5); }); @@ -509,7 +453,7 @@ Logic.registerPanel(P_ONBOARDING_5, { // This method is called when the object is registered. initialize() { // Let's move to the containers list panel. - Logic.addEnterHandler(document.querySelector("#onboarding-longpress-button"), async () => { + Utils.addEnterHandler(document.querySelector("#onboarding-longpress-button"), async () => { await Logic.setOnboardingStage(5); Logic.showPanel(P_ONBOARDING_6); }); @@ -530,7 +474,7 @@ Logic.registerPanel(P_ONBOARDING_6, { // This method is called when the object is registered. initialize() { // Let's move to the containers list panel. - Logic.addEnterHandler(document.querySelector("#start-sync-button"), async () => { + Utils.addEnterHandler(document.querySelector("#start-sync-button"), async () => { await Logic.setOnboardingStage(6); await browser.storage.local.set({syncEnabled: true}); await browser.runtime.sendMessage({ @@ -538,7 +482,7 @@ Logic.registerPanel(P_ONBOARDING_6, { }); Logic.showPanel(P_ONBOARDING_7); }); - Logic.addEnterHandler(document.querySelector("#no-sync"), async () => { + Utils.addEnterHandler(document.querySelector("#no-sync"), async () => { await Logic.setOnboardingStage(7); await browser.storage.local.set({syncEnabled: false}); await browser.runtime.sendMessage({ @@ -563,14 +507,14 @@ Logic.registerPanel(P_ONBOARDING_7, { // This method is called when the object is registered. initialize() { // Let's move to the containers list panel. - Logic.addEnterHandler(document.querySelector("#sign-in"), async () => { + Utils.addEnterHandler(document.querySelector("#sign-in"), async () => { browser.tabs.create({ url: "https://accounts.firefox.com/?service=sync&action=email&context=fx_desktop_v3&entrypoint=multi-account-containers&utm_source=addon&utm_medium=panel&utm_campaign=container-sync", }); await Logic.setOnboardingStage(7); Logic.showPanel(P_CONTAINERS_LIST); }); - Logic.addEnterHandler(document.querySelector("#no-sign-in"), async () => { + Utils.addEnterHandler(document.querySelector("#no-sign-in"), async () => { await Logic.setOnboardingStage(7); Logic.showPanel(P_CONTAINERS_LIST); }); @@ -589,17 +533,24 @@ Logic.registerPanel(P_CONTAINERS_LIST, { // This method is called when the object is registered. async initialize() { - Logic.addEnterHandler(document.querySelector("#container-add-link"), () => { - Logic.showPanel(P_CONTAINER_EDIT, { name: Logic.generateIdentityName() }); - }); - - Logic.addEnterHandler(document.querySelector("#edit-containers-link"), (e) => { + Utils.addEnterHandler(document.querySelector("#manage-containers-link"), (e) => { if (!e.target.classList.contains("disable-edit-containers")) { - Logic.showPanel(P_CONTAINERS_EDIT); + Logic.showPanel(MANAGE_CONTAINERS_PICKER); } }); - - Logic.addEnterHandler(document.querySelector("#sort-containers-link"), async () => { + Utils.addEnterHandler(document.querySelector("#open-new-tab-in"), () => { + Logic.showPanel(OPEN_NEW_CONTAINER_PICKER); + }); + Utils.addEnterHandler(document.querySelector("#reopen-site-in"), () => { + Logic.showPanel(REOPEN_IN_CONTAINER_PICKER); + }); + Utils.addEnterHandler(document.querySelector("#always-open-in"), () => { + Logic.showPanel(ALWAYS_OPEN_IN_PICKER); + }); + Utils.addEnterHandler(document.querySelector("#info-icon"), () => { + browser.runtime.openOptionsPage(); + }); + Utils.addEnterHandler(document.querySelector("#sort-containers-link"), async () => { try { await browser.runtime.sendMessage({ method: "sortTabs" @@ -609,8 +560,18 @@ Logic.registerPanel(P_CONTAINERS_LIST, { window.close(); } }); - document.addEventListener("keydown", (e) => { + function openNewContainerTab(identity) { + try { + browser.tabs.create({ + cookieStoreId: identity.cookieStoreId + }); + window.close(); + } catch (e) { + window.close(); + } + } + const identities = Logic.identities(); const selectables = [...document.querySelectorAll(".open-newtab[tabindex='0']")]; const element = document.activeElement; const index = selectables.indexOf(element) || 0; @@ -652,158 +613,75 @@ Logic.registerPanel(P_CONTAINERS_LIST, { default: if ((e.keyCode >= 49 && e.keyCode <= 57) && Logic._currentPanel === "containersList") { - const element = selectables[e.keyCode - 49]; - if (element) { - element.click(); + const identity = identities[e.keyCode - 49]; + if (identity) { + openNewContainerTab(identity); } } break; } }); - - // When the popup is open sometimes the tab will still be updating it's state - this.tabUpdateHandler = (tabId, changeInfo) => { - const propertiesToUpdate = ["title", "favIconUrl"]; - const hasChanged = Object.keys(changeInfo).find((changeInfoKey) => { - if (propertiesToUpdate.includes(changeInfoKey)) { - return true; - } - }); - if (hasChanged) { - this.prepareCurrentTabHeader(); - } - }; - browser.tabs.onUpdated.addListener(this.tabUpdateHandler); }, unregister() { - browser.tabs.onUpdated.removeListener(this.tabUpdateHandler); - }, - setupAssignmentCheckbox(siteSettings, currentUserContextId) { - const assignmentCheckboxElement = document.getElementById("container-page-assigned"); - let checked = false; - if (siteSettings && Number(siteSettings.userContextId) === currentUserContextId) { - checked = true; - } - assignmentCheckboxElement.checked = checked; - let disabled = false; - if (siteSettings === false) { - disabled = true; - } - assignmentCheckboxElement.disabled = disabled; - }, - - async prepareCurrentTabHeader() { - const currentTab = await Logic.currentTab(); - const currentTabElement = document.getElementById("current-tab"); - const assignmentCheckboxElement = document.getElementById("container-page-assigned"); - const currentTabUserContextId = Logic.userContextId(currentTab.cookieStoreId); - assignmentCheckboxElement.addEventListener("change", () => { - Logic.setOrRemoveAssignment(currentTab.id, currentTab.url, currentTabUserContextId, !assignmentCheckboxElement.checked); - }); - currentTabElement.hidden = !currentTab; - this.setupAssignmentCheckbox(false, currentTabUserContextId); - if (currentTab) { - const identity = await Logic.identity(currentTab.cookieStoreId); - const siteSettings = await Logic.getAssignment(currentTab); - this.setupAssignmentCheckbox(siteSettings, currentTabUserContextId); - const currentPage = document.getElementById("current-page"); - currentPage.innerHTML = escaped`${currentTab.title}`; - const favIconElement = Utils.createFavIconElement(currentTab.favIconUrl || ""); - currentPage.prepend(favIconElement); - - const currentContainer = document.getElementById("current-container"); - currentContainer.innerText = identity.name; - - currentContainer.setAttribute("data-identity-color", identity.color); - } }, // This method is called when the panel is shown. async prepare() { const fragment = document.createDocumentFragment(); - this.prepareCurrentTabHeader(); - Logic.identities().forEach(identity => { - const hasTabs = (identity.hasHiddenTabs || identity.hasOpenTabs); const tr = document.createElement("tr"); - const context = document.createElement("td"); - const manage = document.createElement("td"); + tr.classList.add("menu-item", "hover-highlight"); + tr.setAttribute("tabindex", "0"); + const td = document.createElement("td"); + const openTabs = identity.numberOfOpenTabs || "" ; - tr.classList.add("container-panel-row"); - - context.classList.add("userContext-wrapper", "open-newtab", "clickable", "firstTabindex"); - manage.classList.add("show-tabs", "pop-button"); - manage.setAttribute("title", `View ${identity.name} container`); - context.setAttribute("tabindex", "0"); - context.setAttribute("title", `Create ${identity.name} tab`); - context.innerHTML = escaped` -
+ td.innerHTML = Utils.escaped` + -
`; - context.querySelector(".container-name").textContent = identity.name; - manage.innerHTML = ""; + ${identity.name} + + ${openTabs} + + Container Info + + `; fragment.appendChild(tr); - tr.appendChild(context); + tr.appendChild(td); - if (hasTabs) { - tr.appendChild(manage); - } - - Logic.addEnterHandler(tr, async (e) => { - if (e.target.matches(".open-newtab") - || e.target.parentNode.matches(".open-newtab") - || e.type === "keydown") { - try { - browser.tabs.create({ - cookieStoreId: identity.cookieStoreId - }); - window.close(); - } catch (e) { - window.close(); - } - } else if (hasTabs) { - Logic.showPanel(P_CONTAINER_INFO, identity); - } + Utils.addEnterHandler(tr, () => { + Logic.showPanel(P_CONTAINER_INFO, identity); }); }); - const list = document.querySelector(".identities-list tbody"); + const list = document.querySelector("#identities-list"); list.innerHTML = ""; list.appendChild(fragment); /* Not sure why extensions require a focus for the doorhanger, however it allows us to have a tabindex before the first selected item */ - const focusHandler = () => { - const identityList = list.querySelector("tr .clickable"); - if (identityList) { - // otherwise this throws an error when there are no containers present. - identityList.focus(); - document.removeEventListener("focus", focusHandler); - } - }; - document.addEventListener("focus", focusHandler); - /* If the user mousedown's first then remove the focus handler */ - document.addEventListener("mousedown", () => { - document.removeEventListener("focus", focusHandler); - }); - /* If no container is present disable the Edit Containers button */ - const editContainer = document.querySelector("#edit-containers-link"); - if (Logic.identities().length === 0) { - editContainer.classList.add("disable-edit-containers"); - } else { - editContainer.classList.remove("disable-edit-containers"); - } - + // const focusHandler = () => { + // const identityList = list.querySelector("tr .clickable"); + // if (identityList) { + // // otherwise this throws an error when there are no containers present. + // identityList.focus(); + // document.removeEventListener("focus", focusHandler); + // } + // }; + // document.addEventListener("focus", focusHandler); + // /* If the user mousedown's first then remove the focus handler */ + // document.addEventListener("mousedown", () => { + // document.removeEventListener("focus", focusHandler); + // }); return Promise.resolve(); }, }); @@ -817,15 +695,101 @@ Logic.registerPanel(P_CONTAINER_INFO, { // This method is called when the object is registered. async initialize() { const closeContEl = document.querySelector("#close-container-info-panel"); - closeContEl.setAttribute("tabindex", "0"); - closeContEl.classList.add("firstTabindex"); - Logic.addEnterHandler(closeContEl, () => { + Utils.addEnterHandler(closeContEl, () => { Logic.showPreviousPanel(); }); - const hideContEl = document.querySelector("#container-info-hideorshow"); - hideContEl.setAttribute("tabindex", "0"); - Logic.addEnterHandler(hideContEl, async () => { - const identity = Logic.currentIdentity(); + + // Check if the user has incompatible add-ons installed + // Note: this is not implemented in messageHandler.js + let incompatible = false; + try { + incompatible = await browser.runtime.sendMessage({ + method: "checkIncompatibleAddons" + }); + } catch (e) { + throw new Error("Could not check for incompatible add-ons."); + } + + const moveTabsEl = document.querySelector("#move-to-new-window"); + const numTabs = await Logic.numTabs(); + if (incompatible) { + Logic._disableMenuItem("Moving container tabs is incompatible with Pulse, PageShot, and SnoozeTabs."); + return; + } else if (numTabs === 1) { + Logic._disableMenuItem("Cannot move a tab from a single-tab window."); + return; + } + + Utils.addEnterHandler(moveTabsEl, async () => { + await browser.runtime.sendMessage({ + method: "moveTabsToWindow", + windowId: browser.windows.WINDOW_ID_CURRENT, + cookieStoreId: Logic.currentIdentity().cookieStoreId, + }); + window.close(); + }); + + + }, + + // This method is called when the panel is shown. + async prepare() { + const identity = Logic.currentIdentity(); + + const newTab = document.querySelector("#open-new-tab-in-info"); + Utils.addEnterHandler(newTab, () => { + try { + browser.tabs.create({ + cookieStoreId: identity.cookieStoreId + }); + window.close(); + } catch (e) { + window.close(); + } + }); + // Populating the panel: name and icon + document.getElementById("container-info-title").textContent = identity.name; + + const alwaysOpen = document.querySelector("#always-open-in-info-panel"); + Utils.addEnterHandler(alwaysOpen, async () => { + Utils.alwaysOpenInContainer(identity); + window.close(); + }); + // Show or not the has-tabs section. + for (let trHasTabs of document.getElementsByClassName("container-info-has-tabs")) { // eslint-disable-line prefer-const + trHasTabs.style.display = !identity.hasHiddenTabs && !identity.hasOpenTabs ? "none" : ""; + } + + if (identity.numberOfOpenTabs === 0) { + Logic._disableMenuItem("No tabs available for this container"); + } else { + Logic._enableMenuItems(); + } + + this.intializeShowHide(identity); + + // Let's retrieve the list of tabs. + const tabs = await browser.runtime.sendMessage({ + method: "getTabs", + windowId: browser.windows.WINDOW_ID_CURRENT, + cookieStoreId: Logic.currentIdentity().cookieStoreId + }); + const manageContainer = document.querySelector("#manage-container-link"); + Utils.addEnterHandler(manageContainer, async () => { + Logic.showPanel(P_CONTAINER_EDIT, identity); + }); + return this.buildOpenTabTable(tabs); + }, + + intializeShowHide(identity) { + const hideContEl = document.querySelector("#hideorshow-container"); + if (identity.numberOfOpenTabs === 0 && !identity.hasHiddenTabs) { + return Logic._disableMenuItem("No tabs available for this container", hideContEl); + } else { + Logic._enableMenuItems(hideContEl); + } + + Utils.addEnterHandler(hideContEl, async () => { try { browser.runtime.sendMessage({ method: identity.hasHiddenTabs ? "showTabs" : "hideTabs", @@ -838,114 +802,48 @@ Logic.registerPanel(P_CONTAINER_INFO, { } }); - // Check if the user has incompatible add-ons installed - let incompatible = false; - try { - incompatible = await browser.runtime.sendMessage({ - method: "checkIncompatibleAddons" - }); - } catch (e) { - throw new Error("Could not check for incompatible add-ons."); - } - const moveTabsEl = document.querySelector("#container-info-movetabs"); - moveTabsEl.setAttribute("tabindex","0"); - const numTabs = await Logic.numTabs(); - if (incompatible) { - Logic._disableMoveTabs("Moving container tabs is incompatible with Pulse, PageShot, and SnoozeTabs."); - return; - } else if (numTabs === 1) { - Logic._disableMoveTabs("Cannot move a tab from a single-tab window."); - return; - } - Logic.addEnterHandler(moveTabsEl, async () => { - await browser.runtime.sendMessage({ - method: "moveTabsToWindow", - windowId: browser.windows.WINDOW_ID_CURRENT, - cookieStoreId: Logic.currentIdentity().cookieStoreId, - }); - window.close(); - }); - }, - - // This method is called when the panel is shown. - async prepare() { - const identity = Logic.currentIdentity(); - - // Populating the panel: name and icon - document.getElementById("container-info-name").textContent = identity.name; - - const icon = document.getElementById("container-info-icon"); - icon.setAttribute("data-identity-icon", identity.icon); - icon.setAttribute("data-identity-color", identity.color); - - // Show or not the has-tabs section. - for (let trHasTabs of document.getElementsByClassName("container-info-has-tabs")) { // eslint-disable-line prefer-const - trHasTabs.style.display = !identity.hasHiddenTabs && !identity.hasOpenTabs ? "none" : ""; - } - const hideShowIcon = document.getElementById("container-info-hideorshow-icon"); hideShowIcon.src = identity.hasHiddenTabs ? CONTAINER_UNHIDE_SRC : CONTAINER_HIDE_SRC; const hideShowLabel = document.getElementById("container-info-hideorshow-label"); hideShowLabel.textContent = identity.hasHiddenTabs ? "Show this container" : "Hide this container"; + return; + }, + buildOpenTabTable(tabs) { // Let's remove all the previous tabs. const table = document.getElementById("container-info-table"); while (table.firstChild) { table.firstChild.remove(); } - // Let's retrieve the list of tabs. - const tabs = await browser.runtime.sendMessage({ - method: "getTabs", - windowId: browser.windows.WINDOW_ID_CURRENT, - cookieStoreId: Logic.currentIdentity().cookieStoreId - }); - return this.buildInfoTable(tabs); - }, - - buildInfoTable(tabs) { // For each one, let's create a new line. const fragment = document.createDocumentFragment(); for (let tab of tabs) { // eslint-disable-line prefer-const const tr = document.createElement("tr"); fragment.appendChild(tr); - tr.classList.add("container-info-tab-row"); - tr.innerHTML = escaped` - -
${tab.title}
`; - tr.querySelector("td").appendChild(Utils.createFavIconElement(tab.favIconUrl)); + tr.classList.add("menu-item", "hover-highlight"); tr.setAttribute("tabindex", "0"); - document.getElementById("container-info-table").appendChild(fragment); + tr.innerHTML = Utils.escaped` + +
+ ${tab.title} + + `; + tr.querySelector(".favicon").appendChild(Utils.createFavIconElement(tab.favIconUrl)); + tr.setAttribute("tabindex", "0"); + table.appendChild(fragment); // On click, we activate this tab. But only if this tab is active. if (!tab.hiddenState) { - const closeImage = document.createElement("img"); - closeImage.src = "/img/container-close-tab.svg"; - closeImage.className = "container-close-tab"; - closeImage.title = "Close tab"; - closeImage.id = tab.id; - const tabTitle = tr.querySelector(".container-info-tab-title"); - tabTitle.appendChild(closeImage); - - // On hover, we add truncate-text class to add close-tab-image after tab title truncates - const tabTitleHoverEvent = () => { - tabTitle.classList.toggle("truncate-text"); - tr.querySelector(".container-tab-title").classList.toggle("truncate-text"); - }; - - tr.addEventListener("mouseover", tabTitleHoverEvent); - tr.addEventListener("mouseout", tabTitleHoverEvent); - - tr.classList.add("clickable"); - Logic.addEnterHandler(tr, async () => { + Utils.addEnterHandler(tr, async () => { await browser.tabs.update(tab.id, { active: true }); window.close(); }); - const closeTab = document.getElementById(tab.id); + const closeTab = document.querySelector(".trash-button"); if (closeTab) { - Logic.addEnterHandler(closeTab, async (e) => { + Utils.addEnterHandler(closeTab, async (e) => { await browser.tabs.remove(Number(e.target.id)); window.close(); }); @@ -955,67 +853,353 @@ Logic.registerPanel(P_CONTAINER_INFO, { }, }); -// P_CONTAINERS_EDIT: Makes the list editable. +// OPEN_NEW_CONTAINER_PICKER: Opens a new container tab. // ---------------------------------------------------------------------------- -Logic.registerPanel(P_CONTAINERS_EDIT, { - panelSelector: "#edit-containers-panel", +Logic.registerPanel(OPEN_NEW_CONTAINER_PICKER, { + panelSelector: "#container-picker-panel", // This method is called when the object is registered. initialize() { - Logic.addEnterHandler(document.querySelector("#exit-edit-mode-link"), () => { - Logic.showPanel(P_CONTAINERS_LIST); - }); }, // This method is called when the panel is shown. prepare() { + Logic.listenToPickerBackButton(); + document.getElementById("picker-title").textContent = "Open a New Tab in"; const fragment = document.createDocumentFragment(); + const pickedFunction = function (identity) { + try { + browser.tabs.create({ + cookieStoreId: identity.cookieStoreId + }); + window.close(); + } catch (e) { + window.close(); + } + }; + + document.getElementById("new-container-div").innerHTML = ""; + Logic.identities().forEach(identity => { const tr = document.createElement("tr"); - fragment.appendChild(tr); - tr.classList.add("container-panel-row"); - tr.innerHTML = escaped` - -
-
-
+ tr.classList.add("menu-item", "hover-highlight"); + tr.setAttribute("tabindex", "0"); + const td = document.createElement("td"); + + td.innerHTML = Utils.escaped` + + ${identity.name}`; + fragment.appendChild(tr); - Logic.addEnterHandler(tr, e => { - if (e.target.matches(".edit-container-icon") || e.target.parentNode.matches(".edit-container-icon")) { - Logic.showPanel(P_CONTAINER_EDIT, identity); - } else if (e.target.matches(".delete-container-icon") || e.target.parentNode.matches(".delete-container-icon")) { - Logic.showPanel(P_CONTAINER_DELETE, identity); - } + tr.appendChild(td); + + Utils.addEnterHandler(tr, () => { + pickedFunction(identity); }); }); - const list = document.querySelector("#edit-identities-list"); + const list = document.querySelector("#picker-identities-list"); list.innerHTML = ""; list.appendChild(fragment); return Promise.resolve(null); + } +}); + +// MANAGE_CONTAINERS_PICKER: Makes the list editable. +// ---------------------------------------------------------------------------- + +Logic.registerPanel(MANAGE_CONTAINERS_PICKER, { + panelSelector: "#container-picker-panel", + + // This method is called when the object is registered. + initialize() { + }, + + // This method is called when the panel is shown. + prepare() { + Logic.listenToPickerBackButton(); + const closeContEl = document.querySelector("#close-container-picker-panel"); + if (!this._listenerSet) { + Utils.addEnterHandler(closeContEl, () => { + Logic.showPreviousPanel(); + }); + this._listenerSet = true; + } + document.getElementById("picker-title").textContent = "Manage Containers"; + const fragment = document.createDocumentFragment(); + const pickedFunction = function (identity) { + Logic.showPanel(P_CONTAINER_EDIT, identity); + }; + + document.getElementById("new-container-div").innerHTML = Utils.escaped` + + + + + +
+ `; + + Utils.addEnterHandler(document.querySelector("#new-container"), () => { + Logic.showPanel(P_CONTAINER_EDIT, { name: Logic.generateIdentityName() }); + }); + + Logic.identities().forEach(identity => { + const tr = document.createElement("tr"); + tr.classList.add("menu-item", "hover-highlight"); + tr.setAttribute("tabindex", "0"); + const td = document.createElement("td"); + + td.innerHTML = Utils.escaped` + + ${identity.name}`; + + fragment.appendChild(tr); + + tr.appendChild(td); + + Utils.addEnterHandler(tr, () => { + pickedFunction(identity); + }); + }); + + const list = document.querySelector("#picker-identities-list"); + + list.innerHTML = ""; + list.appendChild(fragment); + + return Promise.resolve(null); + } +}); + +// REOPEN_IN_CONTAINER_PICKER: Makes the list editable. +// ---------------------------------------------------------------------------- + +Logic.registerPanel(REOPEN_IN_CONTAINER_PICKER, { + panelSelector: "#container-picker-panel", + + // This method is called when the object is registered. + initialize() { + }, + + // This method is called when the panel is shown. + async prepare() { + Logic.listenToPickerBackButton(); + document.getElementById("picker-title").textContent = "Reopen This Site in"; + const fragment = document.createDocumentFragment(); + const currentTab = await Utils.currentTab(); + const pickedFunction = function (identity) { + const newUserContextId = Utils.userContextId(identity.cookieStoreId); + Utils.reloadInContainer( + currentTab.url, + false, + newUserContextId, + currentTab.index + 1, + currentTab.active + ); + window.close(); + }; + + document.getElementById("new-container-div").innerHTML = ""; + + if (currentTab.cookieStoreId !== "firefox-default") { + const tr = document.createElement("tr"); + tr.classList.add("menu-item", "hover-highlight"); + const td = document.createElement("td"); + + td.innerHTML = Utils.escaped` + + Default Container`; + + fragment.appendChild(tr); + + tr.appendChild(td); + + Utils.addEnterHandler(tr, () => { + Utils.reloadInContainer( + currentTab.url, + false, + 0, + currentTab.index + 1, + currentTab.active + ); + window.close(); + }); + } + + Logic.identities().forEach(identity => { + if (currentTab.cookieStoreId !== identity.cookieStoreId) { + const tr = document.createElement("tr"); + tr.classList.add("menu-item", "hover-highlight"); + tr.setAttribute("tabindex", "0"); + const td = document.createElement("td"); + + td.innerHTML = Utils.escaped` + + ${identity.name}`; + + fragment.appendChild(tr); + + tr.appendChild(td); + + Utils.addEnterHandler(tr, () => { + pickedFunction(identity); + }); + } + }); + + const list = document.querySelector("#picker-identities-list"); + + list.innerHTML = ""; + list.appendChild(fragment); + + return Promise.resolve(null); + } +}); + +// ALWAYS_OPEN_IN_PICKER: Makes the list editable. +// ---------------------------------------------------------------------------- + +Logic.registerPanel(ALWAYS_OPEN_IN_PICKER, { + panelSelector: "#container-picker-panel", + + // This method is called when the object is registered. + initialize() { + }, + + // This method is called when the panel is shown. + prepare() { + Logic.listenToPickerBackButton(); + document.getElementById("picker-title").textContent = "Reopen This Site in"; + const fragment = document.createDocumentFragment(); + + document.getElementById("new-container-div").innerHTML = ""; + + Logic.identities().forEach(identity => { + const tr = document.createElement("tr"); + tr.classList.add("menu-item", "hover-highlight"); + tr.setAttribute("tabindex", "0"); + const td = document.createElement("td"); + + td.innerHTML = Utils.escaped` + + ${identity.name}`; + + fragment.appendChild(tr); + + tr.appendChild(td); + + Utils.addEnterHandler(tr, () => { + Utils.alwaysOpenInContainer(identity); + window.close(); + }); + }); + + const list = document.querySelector("#picker-identities-list"); + + list.innerHTML = ""; + list.appendChild(fragment); + + return Promise.resolve(null); + } +}); + +// P_CONTAINER_ASSIGNMENTS: Shows Site Assignments and allows editing. +// ---------------------------------------------------------------------------- + +Logic.registerPanel(P_CONTAINER_ASSIGNMENTS, { + panelSelector: "#edit-container-assignments", + + // This method is called when the object is registered. + initialize() { + const closeContEl = document.querySelector("#close-container-assignment-panel"); + Utils.addEnterHandler(closeContEl, () => { + Logic.showPreviousPanel(); + }); + }, + + // This method is called when the panel is shown. + async prepare() { + const identity = Logic.currentIdentity(); + + // Populating the panel: name and icon + document.getElementById("edit-assignments-title").textContent = identity.name; + + const userContextId = Logic.currentUserContextId(); + const assignments = await Logic.getAssignmentObjectByContainer(userContextId); + this.showAssignedContainers(assignments); + + return Promise.resolve(null); + }, + + showAssignedContainers(assignments) { + const assignmentPanel = document.getElementById("edit-sites-assigned"); + const assignmentKeys = Object.keys(assignments); + assignmentPanel.hidden = !(assignmentKeys.length > 0); + if (assignments) { + const tableElement = document.querySelector("#edit-sites-assigned"); + /* Remove previous assignment list, + after removing one we rerender the list */ + while (tableElement.firstChild) { + tableElement.firstChild.remove(); + } + assignmentKeys.forEach((siteKey) => { + const site = assignments[siteKey]; + const trElement = document.createElement("tr"); + /* As we don't have the full or correct path the best we can assume is the path is HTTPS and then replace with a broken icon later if it doesn't load. + This is pending a better solution for favicons from web extensions */ + const assumedUrl = `https://${site.hostname}/favicon.ico`; + trElement.innerHTML = Utils.escaped` + +
+ ${site.hostname} + + `; + trElement.getElementsByClassName("favicon")[0].appendChild(Utils.createFavIconElement(assumedUrl)); + const deleteButton = trElement.querySelector(".trash-button"); + Utils.addEnterHandler(deleteButton, async () => { + const userContextId = Logic.currentUserContextId(); + // Lets show the message to the current tab + // const currentTab = await Utils.currentTab(); + Utils.setOrRemoveAssignment(false, assumedUrl, userContextId, true); + delete assignments[siteKey]; + this.showAssignedContainers(assignments); + }); + trElement.classList.add("menu-item", "hover-highlight"); + tableElement.appendChild(trElement); + }); + } }, }); @@ -1028,8 +1212,7 @@ Logic.registerPanel(P_CONTAINER_EDIT, { // This method is called when the object is registered. initialize() { this.initializeRadioButtons(); - - Logic.addEnterHandler(document.querySelector("#edit-container-panel-back-arrow"), () => { + Utils.addEnterHandler(document.querySelector("#close-container-edit-panel"), () => { const formValues = new FormData(this._editForm); if (formValues.get("container-id") !== NEW_CONTAINER_ID) { this._submitForm(); @@ -1038,23 +1221,17 @@ Logic.registerPanel(P_CONTAINER_EDIT, { } }); - Logic.addEnterHandler(document.querySelector("#edit-container-cancel-link"), () => { - Logic.showPreviousPanel(); - }); - this._editForm = document.getElementById("edit-container-panel-form"); - const editLink = document.querySelector("#edit-container-ok-link"); - Logic.addEnterHandler(editLink, () => { - this._submitForm(); - }); - editLink.addEventListener("submit", () => { - this._submitForm(); - }); this._editForm.addEventListener("submit", () => { this._submitForm(); }); + Utils.addEnterHandler(document.querySelector("#create-container-cancel-link"), () => { + Logic.showPreviousPanel(); + }); - + Utils.addEnterHandler(document.querySelector("#create-container-ok-link"), () => { + this._submitForm(); + }); }, async _submitForm() { @@ -1078,54 +1255,9 @@ Logic.registerPanel(P_CONTAINER_EDIT, { } }, - showAssignedContainers(assignments) { - const assignmentPanel = document.getElementById("edit-sites-assigned"); - const assignmentKeys = Object.keys(assignments); - assignmentPanel.hidden = !(assignmentKeys.length > 0); - if (assignments) { - const tableElement = assignmentPanel.querySelector(".assigned-sites-list"); - /* Remove previous assignment list, - after removing one we rerender the list */ - while (tableElement.firstChild) { - tableElement.firstChild.remove(); - } - - assignmentKeys.forEach((siteKey) => { - const site = assignments[siteKey]; - const trElement = document.createElement("div"); - /* As we don't have the full or correct path the best we can assume is the path is HTTPS and then replace with a broken icon later if it doesn't load. - This is pending a better solution for favicons from web extensions */ - const assumedUrl = `https://${site.hostname}/favicon.ico`; - trElement.innerHTML = escaped` -
-
- ${site.hostname} -
- `; - trElement.getElementsByClassName("favicon")[0].appendChild(Utils.createFavIconElement(assumedUrl)); - const deleteButton = trElement.querySelector(".delete-assignment"); - const that = this; - Logic.addEnterHandler(deleteButton, async () => { - const userContextId = Logic.currentUserContextId(); - // Lets show the message to the current tab - // TODO remove then when firefox supports arrow fn async - const currentTab = await Logic.currentTab(); - Logic.setOrRemoveAssignment(currentTab.id, assumedUrl, userContextId, true); - delete assignments[siteKey]; - that.showAssignedContainers(assignments); - }); - trElement.classList.add("container-info-tab-row", "clickable"); - tableElement.appendChild(trElement); - }); - } - }, - initializeRadioButtons() { const colorRadioTemplate = (containerColor) => { - return escaped` + return Utils.escaped`
-
-
-

Current Tab

-
- + -
-
-
- Panel Back Arrow -
-
-
- -

-
-
- Hide Container icon - Hide this container -
-
Move tabs to a new window
-
- -
-
-
+ - - -
-
-

Edit Containers

-
-
- - +
+
+ + +
- +
-
-
-
- Panel Back Arrow -
-
-
- -
- Name - -
-
- Choose a color -
-
- Choose an icon -
-
-