From 374244fcebc1820bb419185491b7d4bc9cb5454c Mon Sep 17 00:00:00 2001 From: tophf Date: Sat, 14 Mar 2015 01:26:26 +0300 Subject: [PATCH 01/11] Editor: remember size/pos only for detached windows --- edit.js | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/edit.js b/edit.js index 929c97fb..8fb886db 100644 --- a/edit.js +++ b/edit.js @@ -3,6 +3,7 @@ var styleId = null; var dirty = false; var lockScroll; // ensure the section doesn't jump when clicking selected text +var isSeparateWindow; var appliesToTemplate = document.createElement("li"); appliesToTemplate.innerHTML = ''; @@ -218,13 +219,19 @@ document.addEventListener("keydown", function(e) { } }); +chrome.tabs.query({currentWindow: true}, function(tabs) { + isSeparateWindow = tabs.length == 1; +}); + window.onbeforeunload = function() { - prefs.setPref('windowPosition', { - left: screenLeft, - top: screenTop, - width: outerWidth, - height: outerHeight - }); + if (isSeparateWindow) { + prefs.setPref('windowPosition', { + left: screenLeft, + top: screenTop, + width: outerWidth, + height: outerHeight + }); + } document.activeElement.blur(); return !isCleanGlobal() ? t('styleChangesNotSaved') : null; } @@ -308,7 +315,7 @@ function addSection(event, section) { setupCodeMirror(div.querySelector('.code')); if (section) { var index = Array.prototype.indexOf.call(sections.children, section); - var cm = editors.pop(); + var cm = editors.pop(); editors.splice(index, 0, cm); cm.focus(); } From ed9ce87d99be04328b61df4f86c3b7009a00d1c4 Mon Sep 17 00:00:00 2001 From: tophf Date: Sat, 14 Mar 2015 02:06:22 +0300 Subject: [PATCH 02/11] Editor: add sections asynchronously on init Improves perceived responsiveness. Becomes noticeable with ~3 sections. Becomes indispensable with ~10 sections. --- edit.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/edit.js b/edit.js index 8fb886db..bab1ba39 100644 --- a/edit.js +++ b/edit.js @@ -489,7 +489,11 @@ function initWithStyle(style) { Array.prototype.forEach.call(document.querySelectorAll("#sections > div"), function(div) { div.parentNode.removeChild(div); }); - style.sections.forEach(function(section) { addSection(null, section) }); + style.sections.forEach(function(section) { + setTimeout(function() { + addSection(null, section) + }, 0); + }); setupGlobalSearch(); setCleanGlobal(null); initTitle(); From 31a9be7c2afc64b3a056a7f075023af09aafe476 Mon Sep 17 00:00:00 2001 From: tophf Date: Sat, 14 Mar 2015 06:47:43 +0300 Subject: [PATCH 03/11] Editor: add "!important" word to autocomplete in css properties --- edit.js | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/edit.js b/edit.js index bab1ba39..1886ee87 100644 --- a/edit.js +++ b/edit.js @@ -94,7 +94,7 @@ function initCodeMirror() { lint: CodeMirror.lint.css, keyMap: "sublime", extraKeys: {"Ctrl-Space": "autocomplete"} - }; + } mergeOptions(stylishOptions, CM.defaults); mergeOptions(userOptions, CM.defaults); @@ -108,7 +108,20 @@ function initCodeMirror() { cc.jumpToLine = jumpToLine; cc.nextBuffer = nextBuffer; cc.prevBuffer = prevBuffer; - // cc.save = save; + + var cssHintHandler = CM.hint.css; + CM.hint.css = function(cm) { + var cursor = cm.getCursor(); + var token = cm.getTokenAt(cursor); + if (token.state.state === "prop" && "!important".indexOf(token.string) === 0) { + return { + from: CM.Pos(cursor.line, token.start), + to: CM.Pos(cursor.line, token.end), + list: ["!important"] + } + } + return cssHintHandler(cm); + } // user option values CM.getOption = function (o) { From 603da2736fe67f03ff8040e628192a3fffb2eec0 Mon Sep 17 00:00:00 2001 From: tophf Date: Sat, 14 Mar 2015 23:07:35 +0300 Subject: [PATCH 04/11] Editor: don't scroll sections into view on click --- edit.js | 51 ++++++++++++++++++++------------------------------- 1 file changed, 20 insertions(+), 31 deletions(-) diff --git a/edit.js b/edit.js index 1886ee87..ee0e8782 100644 --- a/edit.js +++ b/edit.js @@ -173,35 +173,6 @@ function setupCodeMirror(textarea) { cm.lastChange = cm.changeGeneration(); cm.on("change", indicateCodeChange); - // ensure the entire section is visible on focus - cm.on("focus", function(cm) { - var section = cm.display.wrapper.parentNode; - var bounds = section.getBoundingClientRect(); - if ((bounds.bottom > window.innerHeight && bounds.top > 0) || (bounds.top < 0 && bounds.bottom < window.innerHeight)) { - lockScroll = null; - if (bounds.top < 0) { - window.scrollBy(0, bounds.top - 1); - } else { - window.scrollBy(0, bounds.bottom - window.innerHeight + 1); - } - - // prevent possible double fire of selection change event induced by window.scrollBy - var selectionChangeCount = 0, selection; - function beforeSelectionChange(cm, obj) { - if (++selectionChangeCount == 1) { - selection = obj.ranges; - } else { - obj.update(selection); - cm.off("beforeSelectionChange", beforeSelectionChange); - } - } - cm.on("beforeSelectionChange", beforeSelectionChange); - setTimeout(function() { - cm.off("beforeSelectionChange", beforeSelectionChange) - }, 200); - } - }); - // ensure the section doesn't jump when clicking selected text cm.on("cursorActivity", function(cm) { setTimeout(function() { @@ -363,6 +334,19 @@ function removeSection(event) { setCleanItem(section, !section.defaultValue); } +function makeSectionVisible(cm) { + var section = cm.display.wrapper.parentNode; + var bounds = section.getBoundingClientRect(); + if ((bounds.bottom > window.innerHeight && bounds.top > 0) || (bounds.top < 0 && bounds.bottom < window.innerHeight)) { + lockScroll = null; + if (bounds.top < 0) { + window.scrollBy(0, bounds.top - 1); + } else { + window.scrollBy(0, bounds.bottom - window.innerHeight + 1); + } + } +} + function setupGlobalSearch() { var originalCommand = { find: CodeMirror.commands.find, @@ -426,6 +410,7 @@ function setupGlobalSearch() { var searchCursor = cm.getSearchCursor(state.query, pos, shouldIgnoreCase(state.query)); if (searchCursor.find(reverse) || editors.length == 1) { if (editors.length > 1) { + makeSectionVisible(cm); cm.focus(); } // speedup the original findNext @@ -456,10 +441,14 @@ function jumpToLine(cm) { } function nextBuffer(cm) { - editors[(editors.indexOf(cm) + 1) % editors.length].focus(); + var cm = editors[(editors.indexOf(cm) + 1) % editors.length]; + makeSectionVisible(cm); + cm.focus(); } function prevBuffer(cm) { - editors[(editors.indexOf(cm) - 1 + editors.length) % editors.length].focus(); + var cm = editors[(editors.indexOf(cm) - 1 + editors.length) % editors.length]; + makeSectionVisible(cm); + cm.focus(); } window.addEventListener("load", init, false); From fec53060377b3bcd94ce3291f4368fb6ea686c5c Mon Sep 17 00:00:00 2001 From: tophf Date: Sun, 15 Mar 2015 01:05:02 +0300 Subject: [PATCH 05/11] Editor: make Ctrl-S work in input boxes --- edit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/edit.js b/edit.js index ee0e8782..3974257b 100644 --- a/edit.js +++ b/edit.js @@ -196,7 +196,7 @@ document.addEventListener("scroll", function(e) { } }); -document.addEventListener("keydown", function(e) { +window.addEventListener("keydown", function(e) { if (e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey && e.keyCode == 83) { e.preventDefault(); save(); From 8e6e1f97b21b63766ecdc8937d6954278638f3fb Mon Sep 17 00:00:00 2001 From: tophf Date: Sat, 14 Mar 2015 01:45:38 +0300 Subject: [PATCH 06/11] Manager: "Edit" button re-uses existing tabs, shift-click opens a new window --- background.js | 22 ++++++++++++++++++++++ manage.js | 25 +++++++++++++++++++++++++ popup.js | 10 ++-------- 3 files changed, 49 insertions(+), 8 deletions(-) diff --git a/background.js b/background.js index a5022cdb..4056abdb 100644 --- a/background.js +++ b/background.js @@ -33,6 +33,9 @@ chrome.extension.onMessage.addListener(function(request, sender, sendResponse) { case "healthCheck": getDatabase(function() { sendResponse(true); }, function() { sendResponse(false); }); break; + case "openURL": + openURL(request); + break; } }); @@ -321,3 +324,22 @@ chrome.tabs.onAttached.addListener(function(tabId, data) { } }); }); + +function openURL(options) { + chrome.tabs.query({currentWindow: true, url: options.url}, function(tabs) { + // switch to an existing tab with the requested url + if (tabs.length) { + chrome.tabs.highlight({windowId: tabs[0].windowId, tabs: tabs[0].index}, function (window) {}); + } else { + delete options.method; + chrome.tabs.query({currentWindow: true, active: true}, function(tabs) { + // re-use an active new tab page + if (tabs.length && tabs[0].url.match(/^chrome:\/\/newtab\/?$/)) { + chrome.tabs.update(options); + } else { + chrome.tabs.create(options); + } + }); + } + }); +} diff --git a/manage.js b/manage.js index 5bad7721..41c3a57a 100644 --- a/manage.js +++ b/manage.js @@ -93,6 +93,31 @@ function createStyleElement(style) { } var editLink = e.querySelector(".style-edit-link"); editLink.setAttribute("href", editLink.getAttribute("href") + style.id); + editLink.addEventListener("click", function(event) { + if (!event.altKey) { + var left = event.button == 0, middle = event.button == 1, + shift = event.shiftKey, ctrl = event.ctrlKey; + var openWindow = left && shift && !ctrl; + var openBackgroundTab = (middle && !shift) || (left && ctrl && !shift); + var openForegroundTab = (middle && shift) || (left && ctrl && shift); + if (openWindow || openBackgroundTab || openForegroundTab) { + event.preventDefault(); + event.stopPropagation(); + var url = event.target.href || event.target.parentNode.href; + if (openWindow) { + var options = prefs.getPref('windowPosition', {}); + options.url = url; + chrome.windows.create(options); + } else { + chrome.extension.sendMessage({ + method: "openURL", + url: url, + active: openForegroundTab + }); + } + } + } + }); e.querySelector(".enable").addEventListener("click", function(event) { enable(event, true); }, false); e.querySelector(".disable").addEventListener("click", function(event) { enable(event, false); }, false); e.querySelector(".check-update").addEventListener("click", doCheckUpdate, false); diff --git a/popup.js b/popup.js index 63ecb5a0..10f578bf 100644 --- a/popup.js +++ b/popup.js @@ -156,14 +156,8 @@ function openLinkInTabOrWindow(event) { function openLink(event) { event.preventDefault(); - chrome.tabs.query({currentWindow: true, active: true}, function (tabs) { - if (tabs && tabs.length && tabs[0].url.match(/^chrome:\/\/newtab\/?$/)) { - chrome.tabs.update({url: event.target.href}); - close(); // close the popup - } else { - chrome.tabs.create({url: event.target.href}); - } - }); + chrome.extension.sendMessage({method: "openURL", url: event.target.href}); + close(); } function handleUpdate(style) { From 3dd8eb1fc63ea776b01538c539f59d7aa9d0ce97 Mon Sep 17 00:00:00 2001 From: tophf Date: Sun, 15 Mar 2015 07:21:08 +0300 Subject: [PATCH 07/11] Manager: "Apply all updates" button --- _locales/en/messages.json | 8 +++++ manage.html | 4 +++ manage.js | 61 +++++++++++++++++++++++++++++++++++++-- 3 files changed, 70 insertions(+), 3 deletions(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 43de23ac..29772243 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -60,6 +60,10 @@ "message": "URLs starting with", "description": "Option to make the style apply to the entered string as a URL prefix" }, + "applyAllUpdates": { + "message": "Apply all updates", + "description": "Label for the button to apply all detected updates" + }, "checkAllUpdates": { "message": "Check all styles for updates", "description": "Label for the button to check all styles for updates" @@ -288,6 +292,10 @@ "message": "Style is up to date.", "description": "Text that displays when an update check completed and no update is available" }, + "updateAllCheckSucceededNoUpdate": { + "message": "All styles are up to date.", + "description": "Text that displays when an update all check completed and no updates are available" + }, "updateCompleted": { "message": "Update completed.", "description": "Text that displays when an update completed" diff --git a/manage.html b/manage.html index 150cf5a2..0faf51d8 100644 --- a/manage.html +++ b/manage.html @@ -140,6 +140,10 @@

+

+ + +

diff --git a/manage.js b/manage.js index 41c3a57a..f99dfc67 100644 --- a/manage.js +++ b/manage.js @@ -188,11 +188,52 @@ function doCheckUpdate(event) { checkUpdate(getStyleElement(event)); } -function checkUpdateAll() { - Array.prototype.forEach.call(document.querySelectorAll("[style-update-url]"), checkUpdate); +function applyUpdateAll() { + var btnApply = document.getElementById("apply-all-updates"); + btnApply.disabled = true; + setTimeout(function() { + btnApply.style.display = "none"; + btnApply.disabled = false; + }, 1000); + + Array.prototype.forEach.call(document.querySelectorAll(".can-update .update"), function(button) { + button.click(); + }); } -function checkUpdate(element) { +function checkUpdateAll() { + var btnCheck = document.getElementById("check-all-updates"); + var btnApply = document.getElementById("apply-all-updates"); + var noUpdates = document.getElementById("update-all-no-updates"); + + btnCheck.disabled = true; + btnApply.classList.add("hidden"); + noUpdates.classList.add("hidden"); + + var elements = document.querySelectorAll("[style-update-url]"); + var toCheckCount = elements.length; + var updatableCount = 0; + Array.prototype.forEach.call(elements, function(element) { + checkUpdate(element, function(success) { + if (success) { + ++updatableCount; + } + if (--toCheckCount == 0) { + btnCheck.disabled = false; + if (updatableCount) { + btnApply.classList.remove("hidden"); + } else { + noUpdates.classList.remove("hidden"); + setTimeout(function() { + noUpdates.classList.add("hidden"); + }, 10000); + } + } + }); + }); +} + +function checkUpdate(element, callback) { element.querySelector(".update-note").innerHTML = t('checkingForUpdate'); element.className = element.className.replace("checking-update", "").replace("no-update", "").replace("can-update", "") + " checking-update"; var id = element.getAttribute("style-id"); @@ -203,10 +244,15 @@ function checkUpdate(element) { function handleSuccess(forceUpdate, serverJson) { chrome.extension.sendMessage({method: "getStyles", id: id}, function(styles) { var style = styles[0]; + var needsUpdate = false; if (!forceUpdate && codeIsEqual(style.sections, serverJson.sections)) { handleNeedsUpdate("no", id, serverJson); } else { handleNeedsUpdate("yes", id, serverJson); + needsUpdate = true; + } + if (callback) { + callback(needsUpdate); } }); } @@ -217,6 +263,9 @@ function checkUpdate(element) { } else { handleNeedsUpdate(t('updateCheckFailBadResponseCode', [status]), id, null); } + if (callback) { + callback(false); + } } if (!md5Url || !originalMd5) { @@ -228,6 +277,9 @@ function checkUpdate(element) { checkUpdateFullCode(url, true, handleSuccess, handleFailure); } else { handleNeedsUpdate("no", id, null); + if (callback) { + callback(false); + } } }, handleFailure); } @@ -367,6 +419,8 @@ document.title = t("manageTitle"); tE("manage-heading", "manageHeading"); tE("manage-text", "manageText", null, false); tE("check-all-updates", "checkAllUpdates"); +tE("apply-all-updates", "applyAllUpdates"); +tE("update-all-no-updates", "updateAllCheckSucceededNoUpdate"); tE("add-style-label", "addStyleLabel"); tE("options-heading", "optionsHeading"); tE("show-badge-label", "prefShowBadge"); @@ -376,6 +430,7 @@ tE("filters", "manageFilters"); tE("stylesFirst-label", "popupStylesFirst"); document.getElementById("check-all-updates").addEventListener("click", checkUpdateAll, false); +document.getElementById("apply-all-updates").addEventListener("click", applyUpdateAll, false); function onFilterChange (className, event) { var container = document.getElementById("installed"), From 6b710e9f5651b072d2cb21238c30a78480d13562 Mon Sep 17 00:00:00 2001 From: tophf Date: Sun, 15 Mar 2015 06:35:30 +0300 Subject: [PATCH 08/11] HTML fixes: close a tag, replace 0px with 0 --- edit.html | 14 +++++++------- manage.html | 12 ++++++------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/edit.html b/edit.html index fbe7b101..2a451d58 100644 --- a/edit.html +++ b/edit.html @@ -34,19 +34,19 @@