diff --git a/css/main.css b/css/main.css index 7c0a920f..fe279e29 100644 --- a/css/main.css +++ b/css/main.css @@ -88,7 +88,7 @@ label { float: right; } -.flex { +.flex:not([hidden]) { display: flex; } @@ -295,7 +295,7 @@ input.short { max-width: 7ch; } -input:not([type="submit"]):not([type="button"]):not([type="hidden"]) { +input:not([type="checkbox"]):not([type="submit"]):not([type="button"]):not([type="hidden"]) { background-color: var(--textbox-background); color: inherit; border: 1px solid var(--grey-90-a30); @@ -305,21 +305,51 @@ input:not([type="submit"]):not([type="button"]):not([type="hidden"]) { transition: box-shadow 0.15s cubic-bezier(.07,.95,0,1); } -input:not([type="submit"]):not([type="button"]):not([type="hidden"]):hover { +input:not([type="checkbox"]):not([type="submit"]):not([type="button"]):not([type="hidden"]):hover { border-color: var(--grey-90-a50); } -input:not([type="submit"]):not([type="button"]):not([type="hidden"]):focus { +input:not([type="checkbox"]):not([type="submit"]):not([type="button"]):not([type="hidden"]):focus { border-color: var(--blue-50); box-shadow: 0 0 0 1px var(--blue-50), 0 0 0 4px var(--blue-50-a30); } -input:not([type="submit"]):not([type="button"]):not([type="hidden"]).error, -input:not([type="submit"]):not([type="button"]):not([type="hidden"]):invalid { +input:not([type="checkbox"]):not([type="submit"]):not([type="button"]):not([type="hidden"]).error, +input:not([type="checkbox"]):not([type="submit"]):not([type="button"]):not([type="hidden"]):invalid { border-color: var(--red-50); box-shadow: 0 0 0 1px var(--red-50), 0 0 0 4px var(--red-50-a30); } +input[type="checkbox"] { + -moz-appearance: none; + border: 2px solid var(--grey-10-a10); + border-radius: 2px; + width: 16px; + height: 16px; + display: inline-block; +} + +input[type="checkbox"]::before { + display: inline-block; + width: 16px; + height: 16px; + margin: -2px; +} + +input[type="checkbox"]:checked::before { + content: url('data:image/svg+xml,') +} + +input[type="checkbox"]:indeterminate::before { + content: url('data:image/svg+xml,'); +} + +input[type="checkbox"]:indeterminate, +input[type="checkbox"]:checked { + border-color: transparent; + background-color: var(--blue-60); +} + .card { box-shadow: 0 2px 8px var(--grey-90-a10); background: var(--card-background); diff --git a/pages/settings/settings.css b/pages/settings/settings.css index 270944be..0ba032a2 100644 --- a/pages/settings/settings.css +++ b/pages/settings/settings.css @@ -10,6 +10,7 @@ main > * { .toolbar { align-items: center; + padding-left: calc(1rem + 10px); } .toolbar button { @@ -24,9 +25,14 @@ main > * { .feed { display: grid; padding: 10px; - grid-template-columns: 1fr min-content; - grid-template-areas: "title edit" - "siteUrl edit"; + grid-template-columns: min-content 1fr min-content; + grid-template-areas: "checkbox title edit" + "checkbox siteUrl edit"; + column-gap: 10px; +} + +.feed:first-child { + margin-top: 0; } .broken { @@ -47,6 +53,11 @@ main > * { font-weight: bold; } +.feed-checkbox { + grid-area: checkbox; + align-self: center; +} + .feed-title { grid-area: title; } diff --git a/pages/settings/settings.html b/pages/settings/settings.html index 2355c926..cae93df2 100644 --- a/pages/settings/settings.html +++ b/pages/settings/settings.html @@ -8,10 +8,17 @@
-
+

+
+
diff --git a/pages/settings/settings.js b/pages/settings/settings.js index cbcc4720..5c447938 100644 --- a/pages/settings/settings.js +++ b/pages/settings/settings.js @@ -80,6 +80,10 @@ window.onload = async () => { reader.readAsText(file); }); + document.querySelector("#delete-feed-selection").addEventListener("click", () => { + FeedMultiSelection.removeSelectedFeeds(); + }); + loadFeeds(); LivemarkStore.addChangeListener(loadFeeds); browser.bookmarks.onChanged.addListener(async (id) => { @@ -159,15 +163,84 @@ async function loadFeeds() { feed.title = I18N.getMessage("settings_brokenLivemark"); addFeedToList(feed, true); }); + + FeedMultiSelection.reset(); } +const FeedMultiSelection = { + selection: new Set(), + selectAllCheckbox: document.getElementById("select-all-checkbox"), + async _updateSelectAllCheckboxState() { + const size = await LivemarkStore.getSize(); + const isEmptySelection = this.selection.size == 0; + this.selectAllCheckbox.indeterminate = !isEmptySelection && this.selection.size < size; + this.selectAllCheckbox.checked = !isEmptySelection && this.selection.size == size; + this.selectAllCheckbox.disabled = size == 0; + document.getElementById("selection-toolbar").hidden = size == 0; + }, + addToSelection(feed) { + this.selection.add(feed); + this._updateSelectAllCheckboxState(); + }, + addAllToSelection() { + for (const element of document.querySelectorAll("#feeds > .feed")) { + this.selection.add(element.feedData); + element.querySelector(".feed-checkbox").checked = true; + } + this._updateSelectAllCheckboxState(); + }, + removeFromSelection(feed) { + this.selection.delete(feed); + this._updateSelectAllCheckboxState(); + }, + reset() { + this.selection.clear(); + for (const element of document.querySelectorAll("#feeds > .feed")) { + element.querySelector(".feed-checkbox").checked = false; + } + this._updateSelectAllCheckboxState(); + + // XXX: probably doesn't belong here... + this.selectAllCheckbox.onchange = () => { + if (this.selectAllCheckbox.checked) { + this.addAllToSelection(); + } else { + this.reset(); + } + }; + }, + + moveSelectedFeeds(folder) { + // XXX: todo + }, + async removeSelectedFeeds() { + for (const feed of this.selection) { + await LivemarkStore.remove(feed.id); + } + this.selection.clear(); + } +}; + function addFeedToList(feed, broken = false) { const item = document.createElement("div"); item.className = "feed card"; + item.feedData = feed; if (broken) { item.classList.add("broken"); } + const checkbox = document.createElement("input"); + checkbox.type = "checkbox"; + checkbox.className = "feed-checkbox"; + checkbox.onchange = () => { + if (checkbox.checked) { + FeedMultiSelection.addToSelection(feed); + } else { + FeedMultiSelection.removeFromSelection(feed); + } + }; + item.appendChild(checkbox); + const feedTitle = document.createElement("span"); feedTitle.textContent = feed.title; feedTitle.className = "feed-title"; diff --git a/shared/livemark-store.js b/shared/livemark-store.js index 01a22315..7cab1b5a 100644 --- a/shared/livemark-store.js +++ b/shared/livemark-store.js @@ -55,6 +55,17 @@ const LivemarkStore = { return all; }, + async getSize() { + const livemarks = await browser.storage.sync.get(); + let size = 0; + for (const key in livemarks) { + if (key.startsWith(PREFIX)) { + size++; + } + } + return size; + }, + async add(feed) { const { title, parentId } = feed; const bookmark = await browser.bookmarks.create({