diff --git a/css/animation.css b/css/animation.css index 347faca7..f7041996 100755 --- a/css/animation.css +++ b/css/animation.css @@ -7,3 +7,31 @@ opacity: 1; } } + +@keyframes shake { + 0%, to { + transform: translateZ(0); + } + + 10%, 30%, 50%, 70%, 90% { + transform: translate3d(-10px, 0, 0); + } + + 20%, 40%, 60%, 80% { + transform: translate3d(10px, 0, 0); + } +} + +@keyframes pop { + 0% { + transform: scale(1); + } + + 50% { + transform: scale(1.1); + } + + 100% { + transform: scale(1); + } +} diff --git a/css/button.css b/css/button.css index 09f7cfa6..1bf73a1a 100755 --- a/css/button.css +++ b/css/button.css @@ -43,6 +43,7 @@ input[type="submit"]:focus { border-bottom-color: rgb(var(--gray-08)); color: rgb(var(--button-text-hover-focus)); outline: none; + text-decoration: none; } button:active, @@ -54,6 +55,7 @@ input[type="submit"]:active { border-bottom-color: rgb(var(--theme-accent)); color: rgb(var(--button-text-active)); transition: none; + text-decoration: none; } button[disabled], @@ -68,6 +70,7 @@ button[disabled]:active, border-color: transparent; color: rgb(var(--button-text-disabled)); cursor: default; + text-decoration: none; } .button-text { @@ -91,6 +94,10 @@ button [class*=" icon-"], font-size: 0.8em; } +.button-large { + font-size: 1.2em; +} + .button-block { display: flex; width: 100%; diff --git a/css/form.css b/css/form.css index 61427c8b..374f71b1 100755 --- a/css/form.css +++ b/css/form.css @@ -565,7 +565,8 @@ input[type="range"][disabled]~.input-helper { .input-button input[type="checkbox"]+label, .input-button input[type="radio"]+label, -.input-button input[type="color"]+label { +.input-button input[type="color"]+label, +.input-button input[type="file"]+label { background-color: rgb(var(--gray-02)); padding: 0.125em 1em; margin: 0; @@ -597,7 +598,9 @@ input[type="range"][disabled]~.input-helper { .input-button input[type="radio"]:hover+label, .input-button input[type="radio"]:focus+label, .input-button input[type="color"]:hover+label, -.input-button input[type="color"]:focus+label { +.input-button input[type="color"]:focus+label, +.input-button input[type="file"]:hover+label, +.input-button input[type="file"]:focus+label { background-color: rgb(var(--gray-03)); border-bottom-color: rgb(var(--gray-08)); color: rgb(var(--button-text-hover-focus)); @@ -606,7 +609,8 @@ input[type="range"][disabled]~.input-helper { .input-button input[type="checkbox"]:active+label, .input-button input[type="radio"]:active+label, -.input-button input[type="color"]:active+label { +.input-button input[type="color"]:active+label, +.input-button input[type="file"]:active+label { background-color: rgb(var(--gray-04)); border-bottom-color: rgb(var(--theme-accent)); color: rgb(var(--button-text-active)); @@ -615,7 +619,8 @@ input[type="range"][disabled]~.input-helper { .input-button input[type="checkbox"]:checked+label, .input-button input[type="radio"]:checked+label, -.input-button input[type="color"]:checked+label { +.input-button input[type="color"]:checked+label, +.input-button input[type="file"]:checked+label { background-color: rgb(var(--gray-04)); border-bottom-color: rgb(var(--theme-accent)); color: rgb(var(--button-text-active)); @@ -633,7 +638,11 @@ input[type="range"][disabled]~.input-helper { .input-button input[type="color"][disabled]+label, .input-button input[type="color"][disabled]:hover+label, .input-button input[type="color"][disabled]:focus+label, -.input-button input[type="color"][disabled]:active+label { +.input-button input[type="color"][disabled]:active+label, +.input-button input[type="file"][disabled]+label, +.input-button input[type="file"][disabled]:hover+label, +.input-button input[type="file"][disabled]:focus+label, +.input-button input[type="file"][disabled]:active+label { background-color: rgb(var(--gray-02)); border-color: transparent; color: rgb(var(--button-text-disabled)); @@ -642,7 +651,8 @@ input[type="range"][disabled]~.input-helper { .input-button-link input[type="checkbox"]+label, .input-button-link input[type="radio"]+label, -.input-button-link input[type="color"]+label { +.input-button-link input[type="color"]+label, +.input-button-link input[type="file"]+label { background-color: transparent; border: 0; color: rgb(var(--button-link-text)); @@ -656,21 +666,26 @@ input[type="range"][disabled]~.input-helper { .input-button-link input[type="radio"]:checked+label, .input-button-link input[type="color"]:hover+label, .input-button-link input[type="color"]:focus+label, -.input-button-link input[type="color"]:checked+label { +.input-button-link input[type="color"]:checked+label, +.input-button-link input[type="file"]:hover+label, +.input-button-link input[type="file"]:focus+label, +.input-button-link input[type="file"]:checked+label { background-color: transparent; color: rgb(var(--button-link-text-hover-focus)); } .input-button-link input[type="checkbox"]:active+label, .input-button-link input[type="radio"]:active+label, -.input-button-link input[type="color"]:active+label { +.input-button-link input[type="color"]:active+label, +.input-button-link input[type="file"]:active+label { background-color: transparent; color: rgb(var(--button-link-text-active)); } .input-button-link input[disabled][type="checkbox"]+label, .input-button-link input[disabled][type="radio"]+label, -.input-button-link input[disabled][type="color"]+label { +.input-button-link input[disabled][type="color"]+label, +.input-button-link input[disabled][type="file"]+label { background-color: transparent; color: transparent; pointer-events: none; @@ -681,7 +696,9 @@ input[type="range"][disabled]~.input-helper { .input-button-link input[disabled][type="radio"]:hover+label, .input-button-link input[disabled][type="radio"]:focus+label, .input-button-link input[disabled][type="color"]:hover+label, -.input-button-link input[disabled][type="color"]:focus+label { +.input-button-link input[disabled][type="color"]:focus+label, +.input-button-link input[disabled][type="file"]:hover+label, +.input-button-link input[disabled][type="file"]:focus+label { background-color: transparent; color: transparent; } @@ -723,11 +740,14 @@ input[type="range"][disabled]~.input-helper { .input-hide input[type="checkbox"]+label:before, .input-hide input[type="checkbox"]:checked+label:before, .input-hide input[type="radio"]+label:before, -.input-hide input[type="radio"]:checked+label:before { +.input-hide input[type="radio"]:checked+label:before, +.input-hide input[type="file"]+label:before, +.input-hide input[type="file"]:checked+label:before { content: none; } -.input-hide input[type="color"] { +.input-hide input[type="color"], +.input-hide input[type="file"] { opacity: 0; width: 1px; height: 1px; @@ -903,3 +923,14 @@ input[type="range"][disabled]~.input-helper { .form-group.nested-button *:last-child input[type="color"]+label { border-radius: 0 var(--theme-radius) var(--theme-radius) 0; } + +.form-feedback { + padding: 1em 1.5em; + background-color: rgb(var(--gray-02)); + /* border-bottom: var(--line-width) solid rgb(var(--gray-04)); */ + border-radius: var(--theme-radius); +} + +.form-feedback:not(:last-child) { + margin-bottom: 1em; +} diff --git a/css/menu.css b/css/menu.css index c707cf1b..0c9a397d 100755 --- a/css/menu.css +++ b/css/menu.css @@ -117,10 +117,6 @@ .menu { width: 80vw; } - - .menu-nav-button { - flex-basis: 0; - } } @media (min-width: 700px) { @@ -175,6 +171,12 @@ } } +@media (min-width: 1100px) { + .menu-nav-button { + flex-basis: 0; + } +} + @media (min-width: 1600px) { .menu { width: 50vw; diff --git a/css/typography.css b/css/typography.css index e815f0f6..6f833bfa 100755 --- a/css/typography.css +++ b/css/typography.css @@ -46,10 +46,14 @@ h6 { p { color: rgb(var(--style-neutral-text)); - margin: 0 0 1em 0; + margin: 0; line-height: 1.5; } +p:not(:last-child) { + margin-bottom: 0.5em; +} + hr { border: 0; border-top: 1px solid rgb(var(--gray-02)); diff --git a/css/utilities.css b/css/utilities.css index 66e8e9eb..92072667 100755 --- a/css/utilities.css +++ b/css/utilities.css @@ -30,3 +30,11 @@ .scroll-y-disabled { overflow-y: hidden; } + +.is-shake { + animation: shake var(--animation-speed-slow) 1; +} + +.is-pop { + animation: pop var(--animation-speed-fast) 1; +} diff --git a/index.html b/index.html index 2051ddfc..dcd549a7 100644 --- a/index.html +++ b/index.html @@ -107,14 +107,15 @@
@@ -996,6 +997,45 @@ + + diff --git a/js/control.js b/js/control.js index ec01136e..cc674319 100644 --- a/js/control.js +++ b/js/control.js @@ -2084,6 +2084,25 @@ var control = (function() { func: function() { background.render(); } + }, { + element: helper.e(".control-data-import"), + type: "file", + func: function() { + data.importData(); + } + }, { + element: helper.e(".control-data-export"), + type: "a", + func: function() { + data.exportData(); + } + }, { + element: helper.e(".control-data-clear"), + type: "a", + func: function() { + menu.close(); + data.clearData(); + } }]; var _setValue = function(path, value) { @@ -2101,13 +2120,15 @@ var control = (function() { var bind = function() { var eventType = { + a: "click", button: "click", checkbox: "change", radio: "change", text: "input", number: "input", range: "input", - color: "change" + color: "change", + file: "change" }; var valueType = { checkbox: function(object) { @@ -2158,8 +2179,7 @@ var control = (function() { }; var bindControl = function(object) { var action = { - input: function(object, event) { - changeValue(object); + a: function(object, event) { if (object.func) { object.func(); }; @@ -2168,6 +2188,12 @@ var control = (function() { if (object.func) { object.func(); }; + }, + input: function(object, event) { + changeValue(object); + if (object.func) { + object.func(); + }; } }; object.element.addEventListener(eventType[object.type], function(event) { @@ -2868,8 +2894,9 @@ var control = (function() { })); } }; + var supportedType = ["checkbox", "radio", "text", "number", "range", "color"]; _allControl.forEach(function(arrayItem, index) { - if (arrayItem.element.tagName.toLowerCase() == "input") { + if (supportedType.includes(arrayItem.element.type)) { setValue[arrayItem.type](arrayItem); }; }); diff --git a/js/data.js b/js/data.js index 898573c6..482f1115 100644 --- a/js/data.js +++ b/js/data.js @@ -1,6 +1,6 @@ var data = (function() { - var saveName = "nitghTab"; + var _saveName = "nitghTab"; var set = function(key, data) { localStorage.setItem(key, data); @@ -16,38 +16,146 @@ var data = (function() { var save = function() { var data = { + nighttab: true, version: version.get(), state: state.get(), bookmarks: bookmarks.get() }; - set(saveName, JSON.stringify(data)); + set(_saveName, JSON.stringify(data)); }; var wipe = function() { - remove(saveName); + remove(_saveName); }; var load = function() { - return JSON.parse(get(saveName)); + return JSON.parse(get(_saveName)); }; - var restore = function() { - var data = load(); + var restore = function(data) { if (data) { if (!("version" in data) || data.version != version.get()) { console.log("data version " + data.version + " found less than current"); data = update.run(data); - set(saveName, JSON.stringify(data)); + set(_saveName, JSON.stringify(data)); } else { console.log("data version " + version.get() + " no need to run update"); + set(_saveName, JSON.stringify(data)); }; } else { console.log("no data found to load"); }; }; + var importData = function() { + // get files from input + var fileList = helper.e(".control-data-import").files; + // if file was added + if (fileList.length > 0) { + // validate the file + _validateJsonFile(fileList); + }; + }; + + var exportData = function() { + var tempAchor = helper.node("a"); + var timeStamp = helper.getDateTime(); + var _timeStampPrefix = function(value) { + if (value < 10) { + value = "0" + value; + }; + return value + }; + timeStamp.hours = _timeStampPrefix(timeStamp.hours); + timeStamp.minutes = _timeStampPrefix(timeStamp.minutes); + timeStamp.seconds = _timeStampPrefix(timeStamp.seconds); + timeStamp.date = _timeStampPrefix(timeStamp.date); + timeStamp.month = _timeStampPrefix(timeStamp.month + 1); + timeStamp.year = _timeStampPrefix(timeStamp.year); + timeStamp = timeStamp.hours + " " + timeStamp.minutes + " " + timeStamp.seconds + " - " + timeStamp.date + "." + timeStamp.month + "." + timeStamp.year; + console.log(timeStamp); + var fileName = "nightTab backup - " + timeStamp + ".json"; + var exportData = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(load())); + tempAchor.setAttribute("href", exportData); + tempAchor.setAttribute("download", fileName); + helper.e("html").appendChild(tempAchor); + tempAchor.click(); + tempAchor.remove(); + }; + + var clearData = function() { + var clearContent = helper.node("div"); + var para1 = helper.node("p:Are you sure you want to clear all nightTab Bookmarks and Settings?. nightTab will restore to the default state."); + var para2 = helper.node("p:This can not be undone."); + clearContent.appendChild(para1); + clearContent.appendChild(para2); + modal.render({ + heading: "Clear all nightTab data?", + content: clearContent, + successAction: function() { + wipe(); + location.reload(); + }, + actionText: "Clear all data", + size: "small" + }); + }; + + var _validateJsonFile = function(fileList) { + var controlDataImportFeedback = helper.e(".control-data-import-feedback"); + var feedbackMessage = { + success: "Success! Restoring nightTab Bookmarks and Settings.", + fail: { + notNightTabJson: "Not the right kind of JSON file. Make sure the selected file came from nightTab.", + notJson: "Not a JSON file. Make sure the selected file came from nightTab." + } + }; + var _feedback = function(message, animationClass) { + while (controlDataImportFeedback.lastChild) { + controlDataImportFeedback.removeChild(controlDataImportFeedback.lastChild); + }; + var name = helper.node("p:" + fileList[0].name); + var messageText = helper.node("p:" + message + "|class:muted small"); + controlDataImportFeedback.appendChild(name); + controlDataImportFeedback.appendChild(messageText); + controlDataImportFeedback.addEventListener("animationend", _resetFeedback, false); + helper.removeClass(controlDataImportFeedback, "is-hidden"); + helper.addClass(controlDataImportFeedback, animationClass); + }; + var _resetFeedback = function() { + helper.removeClass(controlDataImportFeedback, "is-shake"); + helper.removeClass(controlDataImportFeedback, "is-pop"); + controlDataImportFeedback.removeEventListener("animationend", _resetFeedback, false); + }; + // make new file reader + var reader = new FileReader(); + // define the on load event for the reader + reader.onload = function(event) { + // is this a JSON file + if (helper.isJsonString(event.target.result)) { + // is this a nightTab JSON + if (JSON.parse(event.target.result).nighttab) { + // console.log("is a JSON and a nightTab file"); + _feedback(feedbackMessage.success, "is-pop"); + controlDataImportFeedback.addEventListener("animationend", function() { + restore(JSON.parse(event.target.result)); + location.reload(); + }, false); + } else { + // console.log("is a JSON file but not a nightTab file"); + _feedback(feedbackMessage.fail.notNightTabJson, "is-shake"); + }; + } else { + // console.log("not a JSON file"); + _feedback(feedbackMessage.fail.notJson, "is-shake"); + }; + }; + // invoke the reader + reader.readAsText(fileList.item(0)); + }; + var init = function() { - restore(); + restore(data.load()); }; return { @@ -58,7 +166,10 @@ var data = (function() { get: get, load: load, wipe: wipe, - restore: restore + restore: restore, + importData: importData, + exportData: exportData, + clearData: clearData }; })(); diff --git a/js/helper.js b/js/helper.js index fcbbf80f..816a9c59 100644 --- a/js/helper.js +++ b/js/helper.js @@ -555,6 +555,15 @@ var helper = (function() { return number + "th"; }; + var isJsonString = function(string) { + try { + JSON.parse(string); + } catch (error) { + return false; + }; + return true; + }; + // exposed methods return { e: e, @@ -579,7 +588,8 @@ var helper = (function() { randomNumber: randomNumber, toWords: toWords, ordinalWords: ordinalWords, - ordinalNumber: ordinalNumber + ordinalNumber: ordinalNumber, + isJsonString: isJsonString }; })(); diff --git a/js/link.js b/js/link.js index 23c7d7d7..20db991c 100644 --- a/js/link.js +++ b/js/link.js @@ -533,7 +533,7 @@ var link = (function() { var searchInput = helper.e(".search-input"); var div = helper.node("div|class:link-empty"); var h1 = helper.node("h1:No matching bookmarks found|class:link-empty-heading"); - var para = helper.node("p:Enter to Search " + state.get().header.search.engine[state.get().header.search.engine.selected].name + "|class:small muted"); + var para = helper.node("p:\"Enter\" to Search " + state.get().header.search.engine[state.get().header.search.engine.selected].name + "|class:small muted"); div.appendChild(h1); div.appendChild(para); return div; diff --git a/js/update.js b/js/update.js index 0404331a..5bcb180a 100644 --- a/js/update.js +++ b/js/update.js @@ -554,6 +554,12 @@ var update = (function() { return data; }; + var _update_3180 = function(data) { + data.version = "3.18.0"; + data.nighttab = true; + return data; + }; + // var _update_300 = function(data) { // data.version = 3.00; // return data; @@ -688,6 +694,10 @@ var update = (function() { console.log("\t= running update 3.15.0"); data = _update_3150(data); }; + if (version.compare(data.version, "3.18.0") == -1) { + console.log("\t= running update 3.18.0"); + data = _update_3180(data); + }; }; // if no update is needed // version bump diff --git a/js/version.js b/js/version.js index b2292937..51487557 100644 --- a/js/version.js +++ b/js/version.js @@ -1,6 +1,6 @@ var version = (function() { - var current = "3.17.1"; + var current = "3.18.0"; var compare = function(a, b) { var pa = a.split("."); diff --git a/manifest.json b/manifest.json index 08087602..8cb7b8dc 100644 --- a/manifest.json +++ b/manifest.json @@ -2,7 +2,7 @@ "name": "nightTab", "short_name": "nightTab", "description": "A neutral new tab page accented with a chosen colour. Customise the layout, style, background and bookmarks in nightTab.", - "version": "3.17.1", + "version": "3.18.0", "manifest_version": 2, "chrome_url_overrides": { "newtab": "index.html"