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}) {