diff --git a/edit.html b/edit.html index f36ca5bc..87612e11 100644 --- a/edit.html +++ b/edit.html @@ -9,6 +9,7 @@ + @@ -33,14 +34,15 @@ - - - + + + + diff --git a/edit/edit.js b/edit/edit.js index b136f1be..1b15e1dd 100644 --- a/edit/edit.js +++ b/edit/edit.js @@ -1,5 +1,5 @@ /* eslint brace-style: 0, operator-linebreak: 0 */ -/* global CodeMirror exports parserlib CSSLint */ +/* global CodeMirror exports css_beautify parserlib CSSLint initLintHooks setLinter updateLintReport renderLintReport */ 'use strict'; let styleId = null; @@ -168,7 +168,7 @@ function initCodeMirror() { matchBrackets: true, highlightSelectionMatches: {showToken: /[#.\-\w]/, annotateScrollbar: true}, hintOptions: {}, - lint: {getAnnotations: CodeMirror.lint.css, delay: prefs.get('editor.lintDelay')}, + lint: setLinter('csslint'), lintReportDelay: prefs.get('editor.lintReportDelay'), styleActiveLine: true, theme: 'default', @@ -350,11 +350,15 @@ function acmeEventListener(event) { } break; case 'linter': - if (value !== null && (editors.lastActive || editors[0])) { + if (value !== null && editors.length) { if (prefs.get(el.id) !== value) { prefs.set(el.id, value || 'csslint'); } - CodeMirror.signal(editors.lastActive || editors[0], "change"); + editors.forEach(cm => { + console.log('set linter to', value); + cm.setOption('lint', setLinter(value || 'csslint')); + }); + // CodeMirror.signal(editors.lastActive || editors[0], "change"); // save(); } break; @@ -1058,143 +1062,6 @@ function getEditorInSight(nearbyElement) { } } -function updateLintReport(cm, delay) { - if (delay === 0) { - // immediately show pending csslint/stylelint messages in onbeforeunload and save - update(cm); - return; - } - if (delay > 0) { - setTimeout(cm => { cm.performLint(); update(cm); }, delay, cm); - return; - } - // eslint-disable-next-line no-var - var state = cm.state.lint; - if (!state) { - return; - } - // user is editing right now: postpone updating the report for the new issues (default: 500ms lint + 4500ms) - // or update it as soon as possible (default: 500ms lint + 100ms) in case an existing issue was just fixed - clearTimeout(state.reportTimeout); - state.reportTimeout = setTimeout(update, state.options.delay + 100, cm); - state.postponeNewIssues = delay === undefined || delay === null; - - function update(cm) { - const scope = cm ? [cm] : editors; - let changed = false; - let fixedOldIssues = false; - scope.forEach(cm => { - const linter = prefs.get('editor.linter'); - const scopedState = cm.state.lint || {}; - const oldMarkers = scopedState.markedLast || {}; - const newMarkers = {}; - const html = !scopedState.marked || scopedState.marked.length === 0 ? '' : '' + - scopedState.marked.map(mark => { - const info = mark.__annotation; - const isActiveLine = info.from.line === cm.getCursor().line; - const pos = isActiveLine ? 'cursor' : (info.from.line + ',' + info.from.ch); - // stylelint rule added in parentheses at the end - const rule = linter === 'stylelint' ? - info.message.substring(info.message.lastIndexOf('('), info.message.length) : - / at line \d.+$/; - // csslint - let message = escapeHtml(info.message.replace(rule, '')); - if (isActiveLine || oldMarkers[pos] === message) { - delete oldMarkers[pos]; - } - newMarkers[pos] = message; - return ` - - ${info.severity} - - ${info.from.line + 1} - : - ${info.from.ch + 1} - ${message} - ` - }).join('') + ''; - scopedState.markedLast = newMarkers; - fixedOldIssues |= scopedState.reportDisplayed && Object.keys(oldMarkers).length > 0; - if (scopedState.html !== html) { - scopedState.html = html; - changed = true; - } - }); - if (changed) { - clearTimeout(state ? state.renderTimeout : undefined); - if (!state || !state.postponeNewIssues || fixedOldIssues) { - renderLintReport(true); - } else { - state.renderTimeout = setTimeout(() => { - renderLintReport(true); - }, CodeMirror.defaults.lintReportDelay); - } - } - } - function escapeHtml(html) { - const chars = {'&': '&', '<': '<', '>': '>', '"': '"', "'": ''', '/': '/'}; - return html.replace(/[&<>"'/]/g, char => chars[char]); - } -} - -function renderLintReport(someBlockChanged) { - const container = document.getElementById('lint'); - const content = container.children[1]; - const label = t('sectionCode'); - const newContent = content.cloneNode(false); - let issueCount = 0; - editors.forEach((cm, index) => { - if (cm.state.lint && cm.state.lint.html) { - const html = '' + label + ' ' + (index + 1) + '' + cm.state.lint.html; - const newBlock = newContent.appendChild(tHTML(html, 'table')); - - newBlock.cm = cm; - issueCount += newBlock.rows.length; - - const block = content.children[newContent.children.length - 1]; - const blockChanged = !block || cm !== block.cm || html !== block.innerHTML; - someBlockChanged |= blockChanged; - cm.state.lint.reportDisplayed = blockChanged; - } - }); - if (someBlockChanged || newContent.children.length !== content.children.length) { - document.getElementById('issue-count').textContent = issueCount; - container.replaceChild(newContent, content); - container.style.display = newContent.children.length ? 'block' : 'none'; - resizeLintReport(null, newContent); - } -} - -function resizeLintReport(event, content) { - content = content || document.getElementById('lint').children[1]; - if (content.children.length) { - const bounds = content.getBoundingClientRect(); - const newMaxHeight = bounds.bottom <= innerHeight ? '' : (innerHeight - bounds.top) + 'px'; - if (newMaxHeight !== content.style.maxHeight) { - content.style.maxHeight = newMaxHeight; - } - } -} - -function gotoLintIssue(event) { - const issue = event.target.closest('tr'); - if (!issue) { - return; - } - const block = issue.closest('table'); - makeSectionVisible(block.cm); - block.cm.focus(); - block.cm.setSelection({ - line: parseInt(issue.querySelector('td[role="line"]').textContent) - 1, - ch: parseInt(issue.querySelector('td[role="col"]').textContent) - 1 - }); -} - -function toggleLintReport() { - document.getElementById('lint').classList.toggle('collapsed'); -} - function beautify(event) { const script = document.head.appendChild(document.createElement('script')); script.src = 'vendor-overwrites/beautify/beautify-css-mod.js'; @@ -1384,14 +1251,7 @@ function initHooks() { document.getElementById('sections-help').addEventListener('click', showSectionHelp, false); document.getElementById('keyMap-help').addEventListener('click', showKeyMapHelp, false); document.getElementById('cancel-button').addEventListener('click', goBackToManage); - document.getElementById('lint-help').addEventListener('click', showLintHelp); - document.getElementById('lint').addEventListener('click', gotoLintIssue); - window.addEventListener('resize', resizeLintReport); - - // touch devices don't have onHover events so the element we'll be toggled via clicking (touching) - if ('ontouchstart' in document.body) { - document.querySelector('#lint h2').addEventListener('click', toggleLintReport); - } + initLintHooks(); if (!FIREFOX) { $$([ @@ -1878,29 +1738,6 @@ function showKeyMapHelp() { } } -function showLintHelp() { - let list = ''); -} - function showRegExpTester(event, section = getSectionForChild(this)) { const GET_FAVICON_URL = 'https://www.google.com/s2/favicons?domain='; const OWN_ICON = chrome.runtime.getManifest().icons['16']; @@ -2123,6 +1960,7 @@ function onRuntimeMessage(request) { break; case 'styleDeleted': if (styleId && styleId === request.id) { + // eslint-disable-next-line no-empty-function window.onbeforeunload = () => {}; window.close(); break; diff --git a/edit/lint.js b/edit/lint.js new file mode 100644 index 00000000..a7d01ee0 --- /dev/null +++ b/edit/lint.js @@ -0,0 +1,180 @@ +/* global CodeMirror CSSLint editors makeSectionVisible showHelp */ +'use strict'; + +function initLintHooks() { + document.getElementById('lint-help').addEventListener('click', showLintHelp); + document.getElementById('lint').addEventListener('click', gotoLintIssue); + window.addEventListener('resize', resizeLintReport); + + // touch devices don't have onHover events so the element we'll be toggled via clicking (touching) + if ('ontouchstart' in document.body) { + document.querySelector('#lint h2').addEventListener('click', toggleLintReport); + } +} + +function setLinter(name) { + return { + getAnnotations: CodeMirror.lint[name], + delay: prefs.get('editor.lintDelay') + }; +} + +function updateLintReport(cm, delay) { + if (delay === 0) { + // immediately show pending csslint/stylelint messages in onbeforeunload and save + update(cm); + return; + } + if (delay > 0) { + setTimeout(cm => { cm.performLint(); update(cm); }, delay, cm); + return; + } + // eslint-disable-next-line no-var + var state = cm.state.lint; + if (!state) { + return; + } + // user is editing right now: postpone updating the report for the new issues (default: 500ms lint + 4500ms) + // or update it as soon as possible (default: 500ms lint + 100ms) in case an existing issue was just fixed + clearTimeout(state.reportTimeout); + state.reportTimeout = setTimeout(update, state.options.delay + 100, cm); + state.postponeNewIssues = delay === undefined || delay === null; + + function update(cm) { + const scope = cm ? [cm] : editors; + let changed = false; + let fixedOldIssues = false; + scope.forEach(cm => { + const linter = prefs.get('editor.linter'); + const scopedState = cm.state.lint || {}; + const oldMarkers = scopedState.markedLast || {}; + const newMarkers = {}; + const html = !scopedState.marked || scopedState.marked.length === 0 ? '' : '' + + scopedState.marked.map(mark => { + const info = mark.__annotation; + const isActiveLine = info.from.line === cm.getCursor().line; + const pos = isActiveLine ? 'cursor' : (info.from.line + ',' + info.from.ch); + // stylelint rule added in parentheses at the end + const rule = linter === 'stylelint' ? + info.message.substring(info.message.lastIndexOf('('), info.message.length) : + / at line \d.+$/; + // csslint + const message = escapeHtml(info.message.replace(rule, '')); + if (isActiveLine || oldMarkers[pos] === message) { + delete oldMarkers[pos]; + } + newMarkers[pos] = message; + return ` + + ${info.severity} + + ${info.from.line + 1} + : + ${info.from.ch + 1} + ${message} + `; + }).join('') + ''; + scopedState.markedLast = newMarkers; + fixedOldIssues |= scopedState.reportDisplayed && Object.keys(oldMarkers).length > 0; + if (scopedState.html !== html) { + scopedState.html = html; + changed = true; + } + }); + if (changed) { + clearTimeout(state ? state.renderTimeout : undefined); + if (!state || !state.postponeNewIssues || fixedOldIssues) { + renderLintReport(true); + } else { + state.renderTimeout = setTimeout(() => { + renderLintReport(true); + }, CodeMirror.defaults.lintReportDelay); + } + } + } + function escapeHtml(html) { + const chars = {'&': '&', '<': '<', '>': '>', '"': '"', "'": ''', '/': '/'}; + return html.replace(/[&<>"'/]/g, char => chars[char]); + } +} + +function renderLintReport(someBlockChanged) { + const container = document.getElementById('lint'); + const content = container.children[1]; + const label = t('sectionCode'); + const newContent = content.cloneNode(false); + let issueCount = 0; + editors.forEach((cm, index) => { + if (cm.state.lint && cm.state.lint.html) { + const html = '' + label + ' ' + (index + 1) + '' + cm.state.lint.html; + const newBlock = newContent.appendChild(tHTML(html, 'table')); + + newBlock.cm = cm; + issueCount += newBlock.rows.length; + + const block = content.children[newContent.children.length - 1]; + const blockChanged = !block || cm !== block.cm || html !== block.innerHTML; + someBlockChanged |= blockChanged; + cm.state.lint.reportDisplayed = blockChanged; + } + }); + if (someBlockChanged || newContent.children.length !== content.children.length) { + document.getElementById('issue-count').textContent = issueCount; + container.replaceChild(newContent, content); + container.style.display = newContent.children.length ? 'block' : 'none'; + resizeLintReport(null, newContent); + } +} + +function resizeLintReport(event, content) { + content = content || document.getElementById('lint').children[1]; + if (content.children.length) { + const bounds = content.getBoundingClientRect(); + const newMaxHeight = bounds.bottom <= innerHeight ? '' : (innerHeight - bounds.top) + 'px'; + if (newMaxHeight !== content.style.maxHeight) { + content.style.maxHeight = newMaxHeight; + } + } +} + +function gotoLintIssue(event) { + const issue = event.target.closest('tr'); + if (!issue) { + return; + } + const block = issue.closest('table'); + makeSectionVisible(block.cm); + block.cm.focus(); + block.cm.setSelection({ + line: parseInt(issue.querySelector('td[role="line"]').textContent) - 1, + ch: parseInt(issue.querySelector('td[role="col"]').textContent) - 1 + }); +} + +function toggleLintReport() { + document.getElementById('lint').classList.toggle('collapsed'); +} + +function showLintHelp() { + let list = ''); +} diff --git a/vendor-overwrites/codemirror/addon/lint/linter.js b/vendor-overwrites/codemirror/addon/lint/linter.js index fc156eea..36751c52 100644 --- a/vendor-overwrites/codemirror/addon/lint/linter.js +++ b/vendor-overwrites/codemirror/addon/lint/linter.js @@ -15,10 +15,9 @@ })(function(CodeMirror) { "use strict"; -CodeMirror.registerHelper("lint", "css", function(text) { +CodeMirror.registerHelper("lint", "csslint", function(text) { let found = []; - const linter = prefs.get('editor.linter'); - if (linter === 'csslint' && window.CSSLint) { + if (window.CSSLint) { /* STYLISH: hack start (part 1) */ var rules = CSSLint.getRules(); @@ -59,31 +58,35 @@ CodeMirror.registerHelper("lint", "css", function(text) { severity : message.type }); } - } else if (linter === 'stylelint') { - const stylelint = require('stylelint').lint; - if (stylelint) { - return stylelint({ - code: text, - // stylelintConfig stored in stylelint-config.js & loaded by edit.html - config: stylelintConfig - }).then(output => { - const warnings = output.results.length ? output.results[0].warnings : [], - len = warnings.length; - let i, warning; - if (len) { - for (i = 0; i < len; i++) { - warning = warnings[i]; - found.push({ - from: CodeMirror.Pos(warning.line - 1, warning.column - 1), - to: CodeMirror.Pos(warning.line - 1, warning.column), - message: warning.text, - severity : warning.severity - }); - } - } - return found; - }); - } + } + return found; +}); + +CodeMirror.registerHelper("lint", "stylelint", function(text) { + let found = []; + const stylelint = require('stylelint').lint; + if (stylelint) { + return stylelint({ + code: text, + // stylelintConfig stored in stylelint-config.js & loaded by edit.html + config: stylelintConfig + }).then(output => { + const warnings = output.results.length ? output.results[0].warnings : [], + len = warnings.length; + let i, warning; + if (len) { + for (i = 0; i < len; i++) { + warning = warnings[i]; + found.push({ + from: CodeMirror.Pos(warning.line - 1, warning.column - 1), + to: CodeMirror.Pos(warning.line - 1, warning.column), + message: warning.text, + severity : warning.severity + }); + } + } + return found; + }); } return found; });