diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 1506b7bc..0dfc8909 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -1259,6 +1259,10 @@ "message": "Specify @name in the code", "description": "Placeholder text for the empty name input field when creating a new Usercss style" }, + "usercssAvoidOverwriting": { + "message": "Please change the value of @name or @namespace to avoid overwriting an existing style.", + "description": "Shown in a message box when attempting to save a new Usercss style that would overwrite an existing one." + }, "usercssReplaceTemplateConfirmation": { "message": "Replace the default template for new Usercss styles with the current code?" }, diff --git a/background/usercss-helper.js b/background/usercss-helper.js index f6eb02a6..c2f2fe84 100644 --- a/background/usercss-helper.js +++ b/background/usercss-helper.js @@ -65,10 +65,21 @@ return style; } - // Parse the source and find the duplication - function build({sourceCode, checkDup = false}) { - return buildMeta({sourceCode}) - .then(usercss.buildCode) + /** + * Parse the source and find the duplication + * @param _ + * @param {String} _.sourceCode + * @param {Boolean=} _.checkDup + * @param {Boolean=} _.metaOnly + * @returns {Promise<{style, dup:Boolean?}>} + */ + function build({ + sourceCode, + checkDup, + metaOnly, + }) { + const task = buildMeta({sourceCode}); + return (metaOnly ? task : task.then(usercss.buildCode)) .then(style => ({ style, dup: checkDup && find(style), diff --git a/edit/source-editor.js b/edit/source-editor.js index 90fc6106..c6db55dc 100644 --- a/edit/source-editor.js +++ b/edit/source-editor.js @@ -122,11 +122,11 @@ function createSourceEditor(style) { style.sourceCode = ''; chromeSync.getLZValue('usercssTemplate').then(code => { + const name = style.name || t('usercssReplaceTemplateName'); + const date = new Date().toLocaleString(); code = code || DEFAULT_CODE; code = code.replace(/@name(\s*)(?=[\r\n])/, (str, space) => - `${str}${space ? '' : ' '}${ - style.name || - t('usercssReplaceTemplateName') + ' - ' + new Date().toLocaleString()}`); + `${str}${space ? '' : ' '}${name} - ${date}`); // strip the last dummy section if any, add an empty line followed by the section style.sourceCode = code.replace(/\s*@-moz-document[^{]*\{[^}]*\}\s*$|\s+$/g, '') + '\n\n' + section; cm.startOperation(); @@ -202,8 +202,8 @@ function createSourceEditor(style) { function save() { if (!dirty.isDirty()) return; const code = cm.getValue(); - return ( - API.saveUsercssUnsafe({ + return ensureUniqueStyle(code) + .then(() => API.saveUsercssUnsafe({ id: style.id, reason: 'editSave', enabled: style.enabled, @@ -214,6 +214,7 @@ function createSourceEditor(style) { if (errors) return Promise.reject(errors); }) .catch(err => { + if (err.handled) return; if (err.message === t('styleMissingMeta', 'name')) { messageBox.confirm(t('usercssReplaceTemplateConfirmation')).then(ok => ok && chromeSync.setLZValue('usercssTemplate', code) @@ -233,6 +234,20 @@ function createSourceEditor(style) { }); } + function ensureUniqueStyle(code) { + return style.id ? Promise.resolve() : + API.buildUsercss({ + sourceCode: code, + checkDup: true, + metaOnly: true, + }).then(({dup}) => { + if (dup) { + messageBox.alert(t('usercssAvoidOverwriting'), 'danger', t('genericError')); + return Promise.reject({handled: true}); + } + }); + } + function drawLinePointer(pos) { const SIZE = 60; const line = cm.getLine(pos.line); diff --git a/msgbox/msgbox.js b/msgbox/msgbox.js index 81d7b8fe..7a6ba81e 100644 --- a/msgbox/msgbox.js +++ b/msgbox/msgbox.js @@ -150,10 +150,12 @@ function messageBox({ /** * @param {String|Node|Array} contents * @param {String} [className] like 'pre' for monospace font + * @param {String} [title] * @returns {Promise} same as messageBox */ -messageBox.alert = (contents, className) => +messageBox.alert = (contents, className, title) => messageBox({ + title, contents, className: `center ${className || ''}`, buttons: [t('confirmClose')]