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().
This commit is contained in:
derv82 2017-12-02 04:34:37 -08:00
parent 1daa12b59f
commit b16ee42deb
5 changed files with 121 additions and 84 deletions

View File

@ -333,9 +333,9 @@
} }
} }
}, },
"loadStylesForSite": { "openSearchWebsite": {
"message": "Load Styles", "message": "Search on userstyles.org",
"description": "Text for a link that loads styles within the popup" "description": "Text for a link that searches for styles for this current site on userstyles.org"
}, },
"searchResultInstall": { "searchResultInstall": {
"message": "Install", "message": "Install",

View File

@ -143,14 +143,14 @@
</div> </div>
<div class="left-gutter"></div> <div class="left-gutter"></div>
<div class="main-controls"> <div class="main-controls">
<div id="load-search-results"> <div id="find-styles">
<a id="load-search-results-link" href="#" <a id="find-styles-link" href="#"
i18n-text="loadStylesForSite"></a> i18n-text="findStylesForSite"></a>
</div> </div>
<div id="searchResults-error" class="hidden"></div> <div id="searchResults-error" class="hidden"></div>
<div id="find-styles"> <div id="open-search">
<a id="find-styles-link" href="https://userstyles.org/styles/browse/" <a id="open-search-link" href="https://userstyles.org/styles/browse/"
i18n-text="findStylesForSite"></a> i18n-text="openSearchWebsite"></a>
</div> </div>
<div id="write-style"> <div id="write-style">
<span id="write-style-for" i18n-text="writeStyleFor"></span> <span id="write-style-for" i18n-text="writeStyleFor"></span>

View File

@ -106,8 +106,8 @@ function initPopup(url) {
installed); installed);
} }
$('#find-styles-link').onclick = handleEvent.openURLandHide; $('#open-search-link').onclick = handleEvent.openURLandHide;
$('#find-styles-link').href += $('#open-search-link').href +=
url.startsWith(location.protocol) ? url.startsWith(location.protocol) ?
'?search_terms=Stylus' : '?search_terms=Stylus' :
'all/' + encodeURIComponent(url.startsWith('file:') ? 'file:' : url); 'all/' + encodeURIComponent(url.startsWith('file:') ? 'file:' : url);

View File

@ -4,13 +4,13 @@
} }
#searchResults, #searchResults,
#load-search-results { #find-styles {
display: block; display: block;
} }
#searchResults.hidden, #searchResults.hidden,
#searchResults-error.hidden, #searchResults-error.hidden,
#load-search-results.hidden { #find-styles.hidden {
display: none; display: none;
} }

View File

@ -6,10 +6,11 @@
* @returns {Object} Exposed methods representing the search results on userstyles.org * @returns {Object} Exposed methods representing the search results on userstyles.org
*/ */
function SearchUserstyles() { function SearchUserstyles() {
let totalPages, totalResults; let totalPages;
let currentPage = 1; let currentPage = 1;
let exhausted = false;
return {getCurrentPage, getTotalPages, getTotalResults, getCategory, search, fetchStyleJson}; return {getCurrentPage, getTotalPages, getCategory, isExhausted, search, fetchStyleJson};
function getCurrentPage() { function getCurrentPage() {
return currentPage; return currentPage;
@ -19,8 +20,8 @@ function SearchUserstyles() {
return totalPages; return totalPages;
} }
function getTotalResults() { function isExhausted() {
return totalResults; return exhausted;
} }
function getCategory(url) { function getCategory(url) {
@ -63,12 +64,13 @@ function SearchUserstyles() {
/** /**
* Fetches (and JSON-parses) search results from a userstyles.org search API. * 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. * @param {string} category The usrestyles.org "category" (subcategory) OR a any search string.
* @return {Object} Response object from userstyles.org * @return {Object} Response object from userstyles.org
*/ */
function search(category) { function search(category) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
console.log('search(' + category + ') currentPage:' + currentPage + ' totalPages:' + totalPages);
if (totalPages !== undefined && currentPage > totalPages) { if (totalPages !== undefined && currentPage > totalPages) {
resolve({'data':[]}); resolve({'data':[]});
} }
@ -91,9 +93,10 @@ function SearchUserstyles() {
const responseJson = tryJSONparse(xhr.responseText); const responseJson = tryJSONparse(xhr.responseText);
currentPage = responseJson.current_page + 1; currentPage = responseJson.current_page + 1;
totalPages = responseJson.total_pages; totalPages = responseJson.total_pages;
totalResults = responseJson.total_entries; exhausted = (currentPage > totalPages);
resolve(responseJson); resolve(responseJson);
} else { } else {
exhausted = true;
reject(xhr.status); reject(xhr.status);
} }
}; };
@ -113,11 +116,13 @@ function SearchUserstyles() {
*/ */
const SearchResults = (() => { const SearchResults = (() => {
const DISPLAYED_RESULTS_PER_PAGE = 3; // Number of results to display in popup.html 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_AFTER_FETCHING_STYLES = 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_BEFORE_SEARCHING_STYLES = 0; // Millisecs to wait before fetching .JSON for next search result.
const searchAPI = SearchUserstyles(); const searchAPI = SearchUserstyles();
const unprocessedResults = []; // Search results not yet processed. 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 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 loading = false;
let tabURL; // The active tab's URL. let tabURL; // The active tab's URL.
let currentDisplayedPage = 1; // Current page number in popup.html let currentDisplayedPage = 1; // Current page number in popup.html
@ -143,8 +148,11 @@ const SearchResults = (() => {
} }
$('#searchResultsNav-currentPage').textContent = currentDisplayedPage; $('#searchResultsNav-currentPage').textContent = currentDisplayedPage;
// Hack: Add 1 page if there's results left to process. let totalResultsCount = processedResults.length;
const totalResultsCount = processedResults.length + (unprocessedResults.length ? DISPLAYED_RESULTS_PER_PAGE : 0); 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)); const totalPageCount = Math.ceil(Math.max(1, totalResultsCount / DISPLAYED_RESULTS_PER_PAGE));
if (currentDisplayedPage >= totalPageCount || loading) { if (currentDisplayedPage >= totalPageCount || loading) {
$('#searchResultsNav-next').setAttribute('disabled', 'disabled'); $('#searchResultsNav-next').setAttribute('disabled', 'disabled');
@ -162,9 +170,9 @@ const SearchResults = (() => {
} }
function shouldLoadMore() { function shouldLoadMore() {
const result = (processedResults.length < currentDisplayedPage * DISPLAYED_RESULTS_PER_PAGE) const result = (processedResults.length < currentDisplayedPage * DISPLAYED_RESULTS_PER_PAGE);
console.log('shouldLoadMore:', console.log('shouldLoadMore:',
result ? 'YES' : 'NO', result === true ? 'YES' : 'NO',
' processedResults.length(' + processedResults.length + ')', ' processedResults.length(' + processedResults.length + ')',
'< currentDisplayedPage(' + currentDisplayedPage + ')', '< currentDisplayedPage(' + currentDisplayedPage + ')',
'* DISPLAYED_RESULTS_PER_PAGE(' + DISPLAYED_RESULTS_PER_PAGE + ')'); '* DISPLAYED_RESULTS_PER_PAGE(' + DISPLAYED_RESULTS_PER_PAGE + ')');
@ -176,7 +184,7 @@ const SearchResults = (() => {
console.log('loadMoreIfNeeded: YES.'); console.log('loadMoreIfNeeded: YES.');
loading = true; loading = true;
render(); render();
setTimeout(load, 1000); setTimeout(load, DELAY_BEFORE_SEARCHING_STYLES);
} else { } else {
console.log('loadMoreIfNeeded: NO.'); console.log('loadMoreIfNeeded: NO.');
loading = false; loading = false;
@ -238,7 +246,14 @@ const SearchResults = (() => {
return true; 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').classList.remove('hidden');
$('#searchResults-error').classList.add('hidden'); $('#searchResults-error').classList.add('hidden');
@ -283,16 +298,19 @@ const SearchResults = (() => {
// Process the next result in the queue. // Process the next result in the queue.
const nextResult = unprocessedResults.shift(); const nextResult = unprocessedResults.shift();
const matchingStyles = getMatchingInstalledStyles(nextResult); isStyleInstalled(nextResult)
if (matchingStyles.length > 0) { .then(isInstalled => {
if (isInstalled) {
// Style already installed, skip it. // Style already installed, skip it.
// TODO: Include the style anyway with option to "Uninstall" (?) // TODO: Include the style anyway with option to "Uninstall" (?)
console.log('[' + unprocessedResults.length + '] style "' + nextResult.name + '" already installed: CONTINUING'); console.log('[' + unprocessedResults.length + '] style "' + nextResult.name +
'" already installed: CONTINUING');
alreadyInstalledResults += 1; alreadyInstalledResults += 1;
setTimeout(processNextResult, DELAY_BETWEEN_FETCHING_STYLES); // Keep processing setTimeout(processNextResult, DELAY_AFTER_FETCHING_STYLES); // Keep processing
} else if (nextResult.category !== 'site') { } else if (nextResult.category !== 'site') {
// Style is not for a website, skip it. // Style is not for a website, skip it.
console.log('[' + unprocessedResults.length + '] style "' + nextResult.name + '" category is for "' + nextResult.category + '", not "site": CONTINUING'); console.log('[' + unprocessedResults.length + '] style "' + nextResult.name +
'" category is for "' + nextResult.category + '", not "site": CONTINUING');
nonApplicableResults += 1; nonApplicableResults += 1;
setTimeout(processNextResult, 0); // Keep processing setTimeout(processNextResult, 0); // Keep processing
} else { } else {
@ -319,34 +337,38 @@ const SearchResults = (() => {
'processedResults=' + processedResults.length, 'processedResults=' + processedResults.length,
'skipped-installed=' + alreadyInstalledResults, 'skipped-installed=' + alreadyInstalledResults,
'skipped-irrelevant=' + nonApplicableResults, 'skipped-irrelevant=' + nonApplicableResults,
'CONTINUING @ sleep=' + DELAY_BETWEEN_RESULTS_MS); 'CONTINUING @ sleep=' + DELAY_AFTER_FETCHING_STYLES);
setTimeout(processNextResult, DELAY_BETWEEN_RESULTS_MS); // Keep processing setTimeout(processNextResult, DELAY_AFTER_FETCHING_STYLES); // Keep processing
}) })
.catch(reason => { .catch(reason => {
console.log('[' + unprocessedResults.length + '] Error while loading style ID ' + nextResult.id + ': ' + reason); console.log('[' + unprocessedResults.length + '] Error while loading style ID ' +
setTimeout(processNextResult, DELAY_BETWEEN_RESULTS_MS); // Keep processing 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 * @param {Object} userstyleSearchResult Search result object from userstyles.org
* @returns {Promise<boolean>} Resolves if the style is installed.
*/ */
function getMatchingInstalledStyles(userstyleSearchResult) { function isStyleInstalled(userstyleSearchResult) {
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
getStylesSafe() getStylesSafe()
.then(installedStyles => { .then(installedStyles => {
console.log('Seeing if searchResult(', userstyleSearchResult, ') is in matchingStyles');
const matchingStyles = installedStyles.filter(installedStyle => { const matchingStyles = installedStyles.filter(installedStyle => {
// Compare installed name to search result name. // Compare installed name to search result name.
let isMatch = installedStyle.name === userstyleSearchResult.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) { if (installedStyle.updateUrl) {
isMatch &= installedStyle.updateUrl.indexOf('/' + userstyleSearchResult.id + '.json') >= 0; isMatch &= installedStyle.updateUrl.indexOf('/' + userstyleSearchResult.id + '.json') >= 0;
} }
return isMatch; return isMatch;
}); });
resolve(matchingStyles); resolve(matchingStyles.length > 0);
}) })
.catch(reject); .catch(reject);
}); });
@ -371,6 +393,10 @@ const SearchResults = (() => {
*/ */
console.log('createSearchResultNode(', userstyleSearchResult, ')'); console.log('createSearchResultNode(', userstyleSearchResult, ')');
if (userstyleSearchResult.installed) {
return;
}
const entry = template.searchResult.cloneNode(true); const entry = template.searchResult.cloneNode(true);
Object.assign(entry, { Object.assign(entry, {
id: 'searchResult-' + userstyleSearchResult.id id: 'searchResult-' + userstyleSearchResult.id
@ -389,7 +415,7 @@ const SearchResults = (() => {
const screenshot = $('.searchResult-screenshot', entry); const screenshot = $('.searchResult-screenshot', entry);
let screenshotUrl = userstyleSearchResult.screenshot_url; let screenshotUrl = userstyleSearchResult.screenshot_url;
if (screenshotUrl === null) { 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)) { } else if (RegExp(/^[0-9]*_after.(jpe?g|png|gif)$/i).test(screenshotUrl)) {
screenshotUrl = 'https://userstyles.org/style_screenshot_thumbnails/' + screenshotUrl; screenshotUrl = 'https://userstyles.org/style_screenshot_thumbnails/' + screenshotUrl;
screenshot.classList.remove('no-screenshot'); 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. // TODO on Install: Promise.all([fetchJSON, fetchHTML]) -> popup if customization is present, install otheriwse.
const styleId = userstyleSearchResult.id; const styleId = userstyleSearchResult.id;
const url = 'https://userstyles.org/styles/chrome/' + styleId + '.json'; const url = 'https://userstyles.org/styles/chrome/' + styleId + '.json';
download(url) saveStyleSafe(userstyleSearchResult.json)
.then(responseText => {
saveStyleSafe(tryJSONparse(responseText))
.then(() => { .then(() => {
// Hide search result after installing // Remove search result after installing
entry.parentNode.removeChild(entry); 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 => { .catch(reason => {
console.log('install:download(', url, ') => [ERROR]: ', reason); console.log('install:download(', url, ') => [ERROR]: ', reason);
@ -447,7 +484,7 @@ const SearchResults = (() => {
})(); })();
onDOMready().then(() => { onDOMready().then(() => {
$('#load-search-results-link').onclick = SearchResults.load; $('#find-styles-link').onclick = SearchResults.load;
$('#searchResultsNav-prev').onclick = SearchResults.prev; $('#searchResultsNav-prev').onclick = SearchResults.prev;
$('#searchResultsNav-next').onclick = SearchResults.next; $('#searchResultsNav-next').onclick = SearchResults.next;
}); });