diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 64723f22..ce5e2259 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -159,6 +159,30 @@ "message": "Theme", "description": "Label for the style editor's CSS theme." }, + "detailName": { + "message": "Name", + "description": "Label for the name of the style" + }, + "detailVersion": { + "message": "Version", + "description": "Label for the version of the style" + }, + "detailAuthor": { + "message": "Author", + "description": "Label for the author of the style" + }, + "detailHomepageURL": { + "message": "Homepage", + "description": "Label for the homepage of the style" + }, + "detailSupportURL": { + "message": "Support site", + "description": "Label for the support site of the style" + }, + "detailUpdateURL": { + "message": "Update URL", + "description": "Label for the update URL of the style" + }, "dysfunctional": { "message": "Stylus cannot function in private windows because Firefox disallows direct connection to the internal background page context of the extension.", "description": "Displayed in Firefox when its settings make Stylus dysfunctional" @@ -223,6 +247,10 @@ "message": "Are you sure you want to delete this style?", "description": "Confirmation before deleting a style" }, + "detailedInformation": { + "message": "Details", + "description": "The label for detailed information" + }, "dragDropMessage": { "message": "Drop your backup file anywhere on this page to import.", "description": "Drag'n'drop message" diff --git a/install-usercss/external.svg b/images/external.svg similarity index 100% rename from install-usercss/external.svg rename to images/external.svg diff --git a/install-usercss/install-usercss.js b/install-usercss/install-usercss.js index 312f9b2c..e15fa36b 100644 --- a/install-usercss/install-usercss.js +++ b/install-usercss/install-usercss.js @@ -1,4 +1,4 @@ -/* global CodeMirror semverCompare makeLink closeCurrentTab */ +/* global CodeMirror semverCompare makeLink closeCurrentTab makeAuthor */ 'use strict'; @@ -93,41 +93,6 @@ $('.external-link').appendChild(externalLink); } - function makeAuthor(text) { - const match = text.match(/^(.+?)(?:\s+<(.+?)>)?(?:\s+\((.+?)\))$/); - if (!match) { - return document.createTextNode(text); - } - const [, name, email, url] = match; - const frag = document.createDocumentFragment(); - if (email) { - frag.appendChild($element({ - tag: 'a', - textContent: name, - href: `mailto:${email}` - })); - } else { - frag.appendChild($element({ - tag: 'span', - textContent: name - })); - } - if (url) { - frag.appendChild($element({ - tag: 'a', - href: url, - target: '_blank', - rel: 'noopener', - appendChild: $element({ - tag: 'img', - className: 'icon', - src: '/install-usercss/external.svg' - }) - })); - } - return frag; - } - function makeExternalLink() { const urls = []; if (data.homepageURL) { diff --git a/js/dom.js b/js/dom.js index 003b02c7..39898125 100644 --- a/js/dom.js +++ b/js/dom.js @@ -194,11 +194,38 @@ function $element(opt) { } -function makeLink(href = '', textContent) { +function makeLink(href = '', content) { return $element({ tag: 'a', target: '_blank', href, - textContent, + rel: 'noopener', + appendChild: content, }); } + + +function makeAuthor(text) { + const match = text.match(/^(.+?)(?:\s+<(.+?)>)?(?:\s+\((.+?)\))$/); + if (!match) { + return document.createTextNode(text); + } + const [, name, email, url] = match; + const frag = document.createDocumentFragment(); + if (email) { + frag.appendChild(makeLink(`mailto:${email}`, name)); + } else { + frag.appendChild($element({ + tag: 'span', + textContent: name + })); + } + if (url) { + frag.appendChild(makeLink(url, $element({ + tag: 'img', + className: 'icon', + src: '/images/external.svg' + }))); + } + return frag; +} diff --git a/manage.html b/manage.html index 489a3f62..1734b977 100644 --- a/manage.html +++ b/manage.html @@ -124,6 +124,14 @@ + + @@ -152,6 +160,7 @@ + diff --git a/manage/detail-dialog.js b/manage/detail-dialog.js new file mode 100644 index 00000000..9a8428ec --- /dev/null +++ b/manage/detail-dialog.js @@ -0,0 +1,84 @@ +/* global messageBox makeLink makeAuthor */ + +'use strict'; + +function detailDialog(style) { + const TYPE_NAME = { + 'urls': t('appliesUrlOption'), + 'urlPrefixes': t('appliesUrlPrefixOption'), + 'domains': t('appliesDomainOption'), + 'regexps': t('appliesRegexpOption') + }; + + return messageBox({ + title: style.name, + className: 'detail-dialog', + contents: buildContent(), + buttons: [t('confirmClose')] + }); + + function buildContent() { + return $element({ + className: 'detail-table', + appendChild: [ + makeRow(t('detailName'), 'name'), + makeRow(t('detailVersion'), 'version', true), + makeRow(t('detailAuthor'), makeStyleAuthor()), + makeRow(t('detailHomepageURL'), 'url'), + makeRow(t('detailSupportURL'), 'supportURL', true), + makeRow(t('detailUpdateURL'), 'updateUrl'), + makeRow(t('appliesLabel'), makeAppliesTo()) + ] + }); + } + + function makeRow(label, content, isUsercss) { + if (typeof content === 'string') { + if (isUsercss) { + if (style.usercssData) { + content = style.usercssData[content] || ''; + } else { + content = ''; + } + } else { + content = style[content] || ''; + } + if (/^http[\S]+$/.test(content)) { + content = makeLink(content, content); + } + } + return $element({className: 'meta', appendChild: [ + $element({className: 'meta-label', textContent: label}), + $element({className: 'meta-value', appendChild: content}) + ]}); + } + + function makeStyleAuthor() { + const author = style.author || style.usercssData && style.usercssData.author; + if (!author) { + return ''; + } + return makeAuthor(author); + } + + function makeAppliesTo() { + return $element({ + 'tag': 'ul', + appendChild: getApplies().map(([type, value]) => $element({ + tag: 'li', textContent: `${type} - ${value}` + })) + }); + } + + function getApplies() { + const result = []; + for (const section of style.sections) { + for (const type of ['urls', 'urlPrefixes', 'domains', 'regexps']) { + if (section[type]) { + result.push(...section[type].map(pattern => ([TYPE_NAME[type], pattern]))); + } + } + } + return result; + } +} diff --git a/manage/manage.css b/manage/manage.css index 625f1c68..44682df2 100644 --- a/manage/manage.css +++ b/manage/manage.css @@ -697,6 +697,38 @@ fieldset > *:not(legend) { flex-shrink: 0; } +.detail-table { + border-collapse: collapse; + display: table; +} + +.detail-table > * { + display: table-row +} + +.detail-table > * > * { + display: table-cell; + padding: 0.3em 0.6em; + border: 1px solid silver; +} + +.detail-table .meta-label { + font-weight: bold; + white-space: nowrap; +} + +.detail-table ul { + margin: 0; + padding: 0; + list-style: none; +} + +.detail-table .icon { + height: 1.3em; + margin: -0.3em 0; + vertical-align: middle; +} + @keyframes fadein { from { opacity: 0; diff --git a/manage/manage.js b/manage/manage.js index 2a65607b..70df72d4 100644 --- a/manage/manage.js +++ b/manage/manage.js @@ -2,7 +2,7 @@ /* global filtersSelector, filterAndAppend */ /* global checkUpdate, handleUpdateInstalled */ /* global objectDiff */ -/* global configDialog */ +/* global configDialog detailDialog */ 'use strict'; let installed; @@ -196,6 +196,7 @@ function createStyleElement({style, name}) { if (shouldShowConfig() && newUI.enabled) { $('.actions', entry).appendChild(template.configureIcon.cloneNode(true)); } + $('.actions', entry).appendChild(template.informationIcon.cloneNode(true)); // name being supplied signifies we're invoked by showStyles() // which debounces its main loop thus loading the postponed favicons @@ -286,7 +287,12 @@ Object.assign(handleEvent, { '.update': 'update', '.delete': 'delete', '.applies-to .expander': 'expandTargets', - '.configure-usercss': 'config' + '.configure-usercss': 'config', + '.detailed-information': 'showDetails' + }, + + showDetails(event, {styleMeta: style}) { + detailDialog(style); }, config(event, {styleMeta: style}) {