diff --git a/popup.html b/popup.html index 6b5a3d20..1ee6a9f0 100644 --- a/popup.html +++ b/popup.html @@ -147,6 +147,7 @@ +
diff --git a/popup/search-results.css b/popup/search-results.css index 27775368..67bf2d93 100755 --- a/popup/search-results.css +++ b/popup/search-results.css @@ -9,10 +9,19 @@ } #searchResults.hidden, +#searchResults-error.hidden, #load-search-results.hidden { display: none; } +#searchResults-error { + background-color: rgba(255, 0, 0, 0.4); + font-weight: bold; + padding: 5px; + text-align: center; + margin-right: 20px; +} + #searchResults, #searchResults-list { max-width: 200px; overflow: hidden; diff --git a/popup/search-results.js b/popup/search-results.js index 1acb72d6..75bd10a4 100755 --- a/popup/search-results.js +++ b/popup/search-results.js @@ -1,4 +1,4 @@ -/* global handleEvent */ +/* global handleEvent tryJSONparse */ 'use strict'; /** @@ -21,21 +21,19 @@ const UserStylesAPI = (() => { 'Content-type': 'application/json', 'Accept': '*/*' }; - let url = 'https://userstyles.org' + path; + + const url = new URL('https://userstyles.org'); + url.pathname = path; if (queryParams) { - url += '?' + queryParams; + url.search = '?' + queryParams; } const xhr = new XMLHttpRequest(); xhr.timeout = TIMEOUT; xhr.onload = () => { - if (xhr.status === 200 || url.protocol === 'file:') { - try { - resolve(JSON.parse(xhr.responseText)); - } catch (err) { - reject('Failed to parse JSON from ' + url + '\nJSON Text: ' + xhr.responseText); - } + if (xhr.status === 200) { + resolve(tryJSONparse(xhr.responseText)); } else { - reject('Error code ' + xhr.status); + reject(xhr.status); } }; xhr.onerror = reject; @@ -56,7 +54,37 @@ const UserStylesAPI = (() => { const SearchResults = (() => { let currentPage = 1; - return {load, next, prev} + return {load, next, prev}; + + /** Increments currentPage and loads results. */ + function next(event) { + currentPage += 1; + return load(event); + } + + /** Decrements currentPage and loads results. */ + function prev(event) { + currentPage = Math.max(1, currentPage - 1); + return load(event); + } + + /** + * Display error message to user. + * @param {string} message Message to display to user. + */ + function error(reason) { + let message; + if (reason === 404) { + // TODO: i18n message + message = 'No results found'; + } else { + console.log('Error loading search results: ' + reason); + message = 'Error loading search results: ' + reason; + } + $('#searchResults').classList.add('hidden'); + $('#searchResults-error').innerHTML = message; + $('#searchResults-error').classList.remove('hidden'); + } /** * Loads search result for the (page number is currentPage). @@ -70,6 +98,7 @@ const SearchResults = (() => { getActiveTab().then(tab => { $('#load-search-results').classList.add('hidden'); $('#searchResults').classList.remove('hidden'); + $('#searchResults-error').classList.add('hidden'); const hostname = new URL(tab.url).hostname.replace(/^(?:.*\.)?([^.]*\.(co\.)?[^.]*)$/i, '$1'); $('#searchResults-terms').textContent = hostname; @@ -79,6 +108,7 @@ const SearchResults = (() => { 'page=' + currentPage, 'per_page=3' ].join('&'); + UserStylesAPI.fetch('/api/v1/styles/search', queryParams) .then(searchResults => { /* @@ -91,33 +121,17 @@ const SearchResults = (() => { } */ if (searchResults.data.length === 0) { - throw 'No results found'; + throw 404; } currentPage = searchResults.current_page; updateSearchResultsNav(searchResults.current_page, searchResults.total_pages); searchResults.data.forEach(createSearchResult); }) - .catch(reason => { - $('#load-search-results').classList.remove('hidden'); - $('#searchResults').classList.add('hidden'); - alert('Error while loading search results: ' + reason); - }); - }); + .catch(error); + }); return true; } - /** Increments currentPage and loads results. */ - function next(event) { - currentPage += 1; - return load(event); - } - - /** Decrements currentPage and loads results. */ - function prev(event) { - currentPage = Math.max(1, currentPage - 1); - return load(event); - } - /** Updates prev/next buttons and currentPage/totalPage labels. */ function updateSearchResultsNav(currentPage, totalPages) { // Update 'next' button @@ -206,38 +220,22 @@ const SearchResults = (() => { // TODO: Rating const installButton = $('.searchResult-install', entry); - Object.assign(installButton, { - onclick: install - }); + installButton.onclick = install; /** Installs the current userstyleSearchResult into stylus. */ function install() { - UserStylesAPI.fetch('/api/v1/styles/' + userstyleSearchResult.id) - .then(styleObject => { - console.log('TODO: Install style ID', userstyleSearchResult.id); - console.log('Full styleObject:', styleObject); - /* - * FIXME - * Sample full styleObject: https://userstyles.org/api/v1/styles/70271 - * We need to convert this sytleObject into the format expected by saveStyleSafe - * I.e. styleObject.id is the ID of the userstyles.org style (e.g. 70271 above) - */ - - // messaging.js#saveStyleSafe({...}) expects an "id" referring to the Stylus ID (1-n). - delete styleObject.id; - - Object.assign(styleObject, { - // TODO: Massage styleObject into the format expected by saveStyleSafe - enabled: true, - reason: 'update', - notify: true - }); - saveStyleSafe(styleObject); - alert('TODO: Install style ID #' + userstyleSearchResult.id + ' name "' + searchResultName + '"'); + // TODO: Detect if style has customizations, point to style page if so. + const styleId = userstyleSearchResult.id; + const url = 'https://userstyles.org/styles/chrome/' + styleId + '.json'; + download(url) + .then(responseText => { + saveStyleSafe(tryJSONparse(responseText)); + installButton.disabled = 'disabled'; + installButton.textContent = 'Installed'; }) .catch(reason => { - console.log('Error during installation:', reason); - alert('Error installing style: ' + reason); + console.log('Error while installing from ' + url + ': ' + reason); + alert('Error while installing from ' + url + ': ' + reason); }); return true; }