From b16ee42debe0d3705be906e9242e867e0ac0d655 Mon Sep 17 00:00:00 2001 From: derv82 Date: Sat, 2 Dec 2017 04:34:37 -0800 Subject: [PATCH] Only one "find styles" link. Hides result after installing. * "Find more styles for this site" renamed to "Search on userstyles.org". * Only appears below search results after "Find more styles" is clicked. * When style is installed from serach results, it no longer appears in the results. * "Install" uses already-fetched .JSON data. Avoiding useless call to download(). --- _locales/en/messages.json | 6 +- popup.html | 12 +-- popup/popup.js | 4 +- popup/search-results.css | 4 +- popup/search-results.js | 179 +++++++++++++++++++++++--------------- 5 files changed, 121 insertions(+), 84 deletions(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 1104238e..b75b6027 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -333,9 +333,9 @@ } } }, - "loadStylesForSite": { - "message": "Load Styles", - "description": "Text for a link that loads styles within the popup" + "openSearchWebsite": { + "message": "Search on userstyles.org", + "description": "Text for a link that searches for styles for this current site on userstyles.org" }, "searchResultInstall": { "message": "Install", diff --git a/popup.html b/popup.html index 1190ab76..4af33b3c 100644 --- a/popup.html +++ b/popup.html @@ -143,14 +143,14 @@
-
- +
+
-
- +
diff --git a/popup/popup.js b/popup/popup.js index eff7f6db..99faed49 100644 --- a/popup/popup.js +++ b/popup/popup.js @@ -106,8 +106,8 @@ function initPopup(url) { installed); } - $('#find-styles-link').onclick = handleEvent.openURLandHide; - $('#find-styles-link').href += + $('#open-search-link').onclick = handleEvent.openURLandHide; + $('#open-search-link').href += url.startsWith(location.protocol) ? '?search_terms=Stylus' : 'all/' + encodeURIComponent(url.startsWith('file:') ? 'file:' : url); diff --git a/popup/search-results.css b/popup/search-results.css index 5823682b..76412c1d 100755 --- a/popup/search-results.css +++ b/popup/search-results.css @@ -4,13 +4,13 @@ } #searchResults, -#load-search-results { +#find-styles { display: block; } #searchResults.hidden, #searchResults-error.hidden, -#load-search-results.hidden { +#find-styles.hidden { display: none; } diff --git a/popup/search-results.js b/popup/search-results.js index 2d14c8b9..d838471f 100755 --- a/popup/search-results.js +++ b/popup/search-results.js @@ -6,10 +6,11 @@ * @returns {Object} Exposed methods representing the search results on userstyles.org */ function SearchUserstyles() { - let totalPages, totalResults; + let totalPages; let currentPage = 1; + let exhausted = false; - return {getCurrentPage, getTotalPages, getTotalResults, getCategory, search, fetchStyleJson}; + return {getCurrentPage, getTotalPages, getCategory, isExhausted, search, fetchStyleJson}; function getCurrentPage() { return currentPage; @@ -19,8 +20,8 @@ function SearchUserstyles() { return totalPages; } - function getTotalResults() { - return totalResults; + function isExhausted() { + return exhausted; } function getCategory(url) { @@ -63,12 +64,13 @@ function SearchUserstyles() { /** * Fetches (and JSON-parses) search results from a userstyles.org search API. - * Automatically sets currentPage, totalPages, and totalResults. + * Automatically sets currentPage and totalPages. * @param {string} category The usrestyles.org "category" (subcategory) OR a any search string. * @return {Object} Response object from userstyles.org */ function search(category) { return new Promise((resolve, reject) => { + console.log('search(' + category + ') currentPage:' + currentPage + ' totalPages:' + totalPages); if (totalPages !== undefined && currentPage > totalPages) { resolve({'data':[]}); } @@ -91,9 +93,10 @@ function SearchUserstyles() { const responseJson = tryJSONparse(xhr.responseText); currentPage = responseJson.current_page + 1; totalPages = responseJson.total_pages; - totalResults = responseJson.total_entries; + exhausted = (currentPage > totalPages); resolve(responseJson); } else { + exhausted = true; reject(xhr.status); } }; @@ -113,11 +116,13 @@ function SearchUserstyles() { */ const SearchResults = (() => { const DISPLAYED_RESULTS_PER_PAGE = 3; // Number of results to display in popup.html - const DELAY_BETWEEN_RESULTS_MS = 500; // Millisecs to wait before fetching next batch of search results. - const DELAY_BETWEEN_FETCHING_STYLES = 0; // Millisecs to wait before fetching .JSON for next search result. + const DELAY_AFTER_FETCHING_STYLES = 500; // Millisecs to wait before fetching next batch of search results. + const DELAY_BEFORE_SEARCHING_STYLES = 0; // Millisecs to wait before fetching .JSON for next search result. const searchAPI = SearchUserstyles(); const unprocessedResults = []; // Search results not yet processed. const processedResults = []; // Search results that are not installed and apply ot the page (includes 'json' field with full style). + const BLANK_PIXEL_DATA = 'data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAA' + + 'C1HAwCAAAAC0lEQVR42mOcXQ8AAbsBHLLDr5MAAAAASUVORK5CYII='; let loading = false; let tabURL; // The active tab's URL. let currentDisplayedPage = 1; // Current page number in popup.html @@ -143,8 +148,11 @@ const SearchResults = (() => { } $('#searchResultsNav-currentPage').textContent = currentDisplayedPage; - // Hack: Add 1 page if there's results left to process. - const totalResultsCount = processedResults.length + (unprocessedResults.length ? DISPLAYED_RESULTS_PER_PAGE : 0); + let totalResultsCount = processedResults.length; + if (unprocessedResults.length > 0) { + // Add 1 page if there's results left to process. + totalResultsCount += DISPLAYED_RESULTS_PER_PAGE; + } const totalPageCount = Math.ceil(Math.max(1, totalResultsCount / DISPLAYED_RESULTS_PER_PAGE)); if (currentDisplayedPage >= totalPageCount || loading) { $('#searchResultsNav-next').setAttribute('disabled', 'disabled'); @@ -162,9 +170,9 @@ const SearchResults = (() => { } function shouldLoadMore() { - const result = (processedResults.length < currentDisplayedPage * DISPLAYED_RESULTS_PER_PAGE) + const result = (processedResults.length < currentDisplayedPage * DISPLAYED_RESULTS_PER_PAGE); console.log('shouldLoadMore:', - result ? 'YES' : 'NO', + result === true ? 'YES' : 'NO', ' processedResults.length(' + processedResults.length + ')', '< currentDisplayedPage(' + currentDisplayedPage + ')', '* DISPLAYED_RESULTS_PER_PAGE(' + DISPLAYED_RESULTS_PER_PAGE + ')'); @@ -176,7 +184,7 @@ const SearchResults = (() => { console.log('loadMoreIfNeeded: YES.'); loading = true; render(); - setTimeout(load, 1000); + setTimeout(load, DELAY_BEFORE_SEARCHING_STYLES); } else { console.log('loadMoreIfNeeded: NO.'); loading = false; @@ -238,7 +246,14 @@ const SearchResults = (() => { return true; } - $('#load-search-results').classList.add('hidden'); + if (searchAPI.isExhausted()) { + console.log('searchAPI is exhausted'); + loading = false; + render(); + return true; + } + + $('#find-styles').classList.add('hidden'); $('#searchResults').classList.remove('hidden'); $('#searchResults-error').classList.add('hidden'); @@ -283,70 +298,77 @@ const SearchResults = (() => { // Process the next result in the queue. const nextResult = unprocessedResults.shift(); - const matchingStyles = getMatchingInstalledStyles(nextResult); - if (matchingStyles.length > 0) { - // Style already installed, skip it. - // TODO: Include the style anyway with option to "Uninstall" (?) - console.log('[' + unprocessedResults.length + '] style "' + nextResult.name + '" already installed: CONTINUING'); - alreadyInstalledResults += 1; - setTimeout(processNextResult, DELAY_BETWEEN_FETCHING_STYLES); // Keep processing - } else if (nextResult.category !== 'site') { - // Style is not for a website, skip it. - console.log('[' + unprocessedResults.length + '] style "' + nextResult.name + '" category is for "' + nextResult.category + '", not "site": CONTINUING'); - nonApplicableResults += 1; - setTimeout(processNextResult, 0); // Keep processing - } else { - // Style not installed, fetch full style to see if it applies to this site. - console.log('[' + unprocessedResults.length + '] fetching "' + nextResult.name + '": CONTINUING'); - searchAPI.fetchStyleJson(nextResult.id) - .then(userstyleJson => { - // Extract applicable sections (i.e. styles that apply to the current site) - const applicableSections = BG.getApplicableSections({ - style: userstyleJson, - matchUrl: tabURL, - stopOnFirst: true - }); - if (applicableSections.length === 0) { - // Style is invalid (does not apply to this site). - nonApplicableResults += 1; - } else { - // Style is valid (can apply to this site). - nextResult.json = userstyleJson; // Store Style JSON for easy installing later. - processedResults.push(nextResult); - render(); - } - console.log('[' + unprocessedResults.length + '] Processed "' + nextResult.name + '"', - 'processedResults=' + processedResults.length, - 'skipped-installed=' + alreadyInstalledResults, - 'skipped-irrelevant=' + nonApplicableResults, - 'CONTINUING @ sleep=' + DELAY_BETWEEN_RESULTS_MS); - setTimeout(processNextResult, DELAY_BETWEEN_RESULTS_MS); // Keep processing - }) - .catch(reason => { - console.log('[' + unprocessedResults.length + '] Error while loading style ID ' + nextResult.id + ': ' + reason); - setTimeout(processNextResult, DELAY_BETWEEN_RESULTS_MS); // Keep processing - }); - } + isStyleInstalled(nextResult) + .then(isInstalled => { + if (isInstalled) { + // Style already installed, skip it. + // TODO: Include the style anyway with option to "Uninstall" (?) + console.log('[' + unprocessedResults.length + '] style "' + nextResult.name + + '" already installed: CONTINUING'); + alreadyInstalledResults += 1; + setTimeout(processNextResult, DELAY_AFTER_FETCHING_STYLES); // Keep processing + } else if (nextResult.category !== 'site') { + // Style is not for a website, skip it. + console.log('[' + unprocessedResults.length + '] style "' + nextResult.name + + '" category is for "' + nextResult.category + '", not "site": CONTINUING'); + nonApplicableResults += 1; + setTimeout(processNextResult, 0); // Keep processing + } else { + // Style not installed, fetch full style to see if it applies to this site. + console.log('[' + unprocessedResults.length + '] fetching "' + nextResult.name + '": CONTINUING'); + searchAPI.fetchStyleJson(nextResult.id) + .then(userstyleJson => { + // Extract applicable sections (i.e. styles that apply to the current site) + const applicableSections = BG.getApplicableSections({ + style: userstyleJson, + matchUrl: tabURL, + stopOnFirst: true + }); + if (applicableSections.length === 0) { + // Style is invalid (does not apply to this site). + nonApplicableResults += 1; + } else { + // Style is valid (can apply to this site). + nextResult.json = userstyleJson; // Store Style JSON for easy installing later. + processedResults.push(nextResult); + render(); + } + console.log('[' + unprocessedResults.length + '] Processed "' + nextResult.name + '"', + 'processedResults=' + processedResults.length, + 'skipped-installed=' + alreadyInstalledResults, + 'skipped-irrelevant=' + nonApplicableResults, + 'CONTINUING @ sleep=' + DELAY_AFTER_FETCHING_STYLES); + setTimeout(processNextResult, DELAY_AFTER_FETCHING_STYLES); // Keep processing + }) + .catch(reason => { + console.log('[' + unprocessedResults.length + '] Error while loading style ID ' + + nextResult.id + ': ' + reason); + setTimeout(processNextResult, DELAY_AFTER_FETCHING_STYLES); // Keep processing + }); + } + }); } /** - * Promises a list of installed styles that match the provided search result. + * Promises if the given searchResult matches an already-installed style. * @param {Object} userstyleSearchResult Search result object from userstyles.org + * @returns {Promise} Resolves if the style is installed. */ - function getMatchingInstalledStyles(userstyleSearchResult) { + function isStyleInstalled(userstyleSearchResult) { return new Promise(function (resolve, reject) { getStylesSafe() .then(installedStyles => { + console.log('Seeing if searchResult(', userstyleSearchResult, ') is in matchingStyles'); const matchingStyles = installedStyles.filter(installedStyle => { // Compare installed name to search result name. let isMatch = installedStyle.name === userstyleSearchResult.name; - // Also compare if search result ID (userstyles ID) is mentioned in the installed updateUrl. + // Compare if search result ID (userstyles ID) is mentioned in the installed updateUrl. if (installedStyle.updateUrl) { isMatch &= installedStyle.updateUrl.indexOf('/' + userstyleSearchResult.id + '.json') >= 0; } return isMatch; }); - resolve(matchingStyles); + resolve(matchingStyles.length > 0); }) .catch(reject); }); @@ -371,6 +393,10 @@ const SearchResults = (() => { */ console.log('createSearchResultNode(', userstyleSearchResult, ')'); + if (userstyleSearchResult.installed) { + return; + } + const entry = template.searchResult.cloneNode(true); Object.assign(entry, { id: 'searchResult-' + userstyleSearchResult.id @@ -389,7 +415,7 @@ const SearchResults = (() => { const screenshot = $('.searchResult-screenshot', entry); let screenshotUrl = userstyleSearchResult.screenshot_url; if (screenshotUrl === null) { - screenshotUrl = 'data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mOcXQ8AAbsBHLLDr5MAAAAASUVORK5CYII='; + screenshotUrl = BLANK_PIXEL_DATA; } else if (RegExp(/^[0-9]*_after.(jpe?g|png|gif)$/i).test(screenshotUrl)) { screenshotUrl = 'https://userstyles.org/style_screenshot_thumbnails/' + screenshotUrl; screenshot.classList.remove('no-screenshot'); @@ -429,13 +455,24 @@ const SearchResults = (() => { // TODO on Install: Promise.all([fetchJSON, fetchHTML]) -> popup if customization is present, install otheriwse. const styleId = userstyleSearchResult.id; const url = 'https://userstyles.org/styles/chrome/' + styleId + '.json'; - download(url) - .then(responseText => { - saveStyleSafe(tryJSONparse(responseText)) - .then(() => { - // Hide search result after installing - entry.parentNode.removeChild(entry); - }); + saveStyleSafe(userstyleSearchResult.json) + .then(() => { + // Remove search result after installing + let matchingIndex = -1; + processedResults.forEach((processedResult, index) => { + console.log('processedResult[' + index + '].id =', processedResult.id, + 'userstyleSearchResult.id =', userstyleSearchResult.id); + if (processedResult.id === userstyleSearchResult.id) { + matchingIndex = index; + } + }); + console.log('matchingIndex =', matchingIndex); + if (matchingIndex >= 0) { + console.log('processedResults.length before', processedResults.length); + processedResults.splice(matchingIndex, 1); + console.log('processedResults.length after', processedResults.length); + } + processNextResult(); }) .catch(reason => { console.log('install:download(', url, ') => [ERROR]: ', reason); @@ -447,7 +484,7 @@ const SearchResults = (() => { })(); onDOMready().then(() => { - $('#load-search-results-link').onclick = SearchResults.load; + $('#find-styles-link').onclick = SearchResults.load; $('#searchResultsNav-prev').onclick = SearchResults.prev; $('#searchResultsNav-next').onclick = SearchResults.next; });