Merge pull request #150 from Mottie/stylelint

Include both CSSLint and stylelint
This commit is contained in:
tophf 2017-08-28 08:12:10 +03:00 committed by GitHub
commit da565a5fa5
33 changed files with 4769 additions and 430 deletions

View File

@ -27,9 +27,9 @@
"message": "Изнасяне", "message": "Изнасяне",
"description": "Label for the button to export a style ('edit' page) or all styles ('manage' page)" "description": "Label for the button to export a style ('edit' page) or all styles ('manage' page)"
}, },
"issues": { "linterIssues": {
"message": "Проблеми", "message": "Проблеми",
"description": "Label for the CSSLint issues block on the style edit page" "description": "Label for the CSS linter issues block on the style edit page"
}, },
"optionsBadgeNormal": { "optionsBadgeNormal": {
"message": "Цвят на фона", "message": "Цвят на фона",
@ -664,9 +664,14 @@
"message": "Прозорец за настройките", "message": "Прозорец за настройките",
"description": "Go to Options UI" "description": "Go to Options UI"
}, },
"issuesHelp": { "linterIssuesHelp": {
"message": "Проблеми, намерени от <a href='https://github.com/CSSLint/csslint' target='_blank'>CSSLint</a> при следните правила:", "message": "Проблеми, намерени от $link$ при следните правила:",
"description": "Help popup message for the CSSLint issues block on the style edit page" "description": "Help popup message for the selected CSS linter issues block on the style edit page",
"placeholders": {
"link": {
"content": "$1"
}
}
}, },
"optionsCustomizeBadge": { "optionsCustomizeBadge": {
"message": "Значка на иконката на лентата", "message": "Значка на иконката на лентата",

View File

@ -27,9 +27,9 @@
"message": "Exportovat", "message": "Exportovat",
"description": "Label for the button to export a style ('edit' page) or all styles ('manage' page)" "description": "Label for the button to export a style ('edit' page) or all styles ('manage' page)"
}, },
"issues": { "linterIssues": {
"message": "Problémy", "message": "Problémy",
"description": "Label for the CSSLint issues block on the style edit page" "description": "Label for the CSS linter issues block on the style edit page"
}, },
"optionsBadgeNormal": { "optionsBadgeNormal": {
"message": "Barva pozadí", "message": "Barva pozadí",
@ -652,9 +652,14 @@
"message": "Možnosti rozhraní", "message": "Možnosti rozhraní",
"description": "Go to Options UI" "description": "Go to Options UI"
}, },
"issuesHelp": { "linterIssuesHelp": {
"message": "Problémy nalezené aplikací <a href='https://github.com/CSSLint/csslint' target='_blank'>CSSLint</a> s těmito povolenými pravidly:", "message": "Problémy nalezené aplikací $link$ s těmito povolenými pravidly:",
"description": "Help popup message for the CSSLint issues block on the style edit page" "description": "Help popup message for the selected CSS linter issues block on the style edit page",
"placeholders": {
"link": {
"content": "$1"
}
}
}, },
"optionsCustomizeBadge": { "optionsCustomizeBadge": {
"message": "Ikona tlačítka na panelu", "message": "Ikona tlačítka na panelu",

View File

@ -27,9 +27,9 @@
"message": "Exportieren", "message": "Exportieren",
"description": "Label for the button to export a style ('edit' page) or all styles ('manage' page)" "description": "Label for the button to export a style ('edit' page) or all styles ('manage' page)"
}, },
"issues": { "linterIssues": {
"message": "Probleme", "message": "Probleme",
"description": "Label for the CSSLint issues block on the style edit page" "description": "Label for the CSS linter issues block on the style edit page"
}, },
"optionsBadgeNormal": { "optionsBadgeNormal": {
"message": "Hintergrundfarbe", "message": "Hintergrundfarbe",
@ -632,9 +632,14 @@
"message": "Optionen", "message": "Optionen",
"description": "Go to Options UI" "description": "Go to Options UI"
}, },
"issuesHelp": { "linterIssuesHelp": {
"message": "Die von <a href='https://github.com/CSSLint/csslint' target='_blank'>CSSLint</a> gefunden Fehler haben die folgenden Einstellungen:", "message": "Die von $link$ gefunden Fehler haben die folgenden Einstellungen:",
"description": "Help popup message for the CSSLint issues block on the style edit page" "description": "Help popup message for the selected CSS linter issues block on the style edit page",
"placeholders": {
"link": {
"content": "$1"
}
}
}, },
"optionsCustomizeBadge": { "optionsCustomizeBadge": {
"message": "Badge auf dem Toolbar-Icon", "message": "Badge auf dem Toolbar-Icon",

View File

@ -107,6 +107,26 @@
"message": "Word wrap", "message": "Word wrap",
"description": "Label for the checkbox controlling word wrap option for the style editor." "description": "Label for the checkbox controlling word wrap option for the style editor."
}, },
"cm_linter": {
"message": "CSS Linter",
"description": "Select the linter to check for CSS issues"
},
"cm_matchHighlight": {
"message": "Highlight",
"description": "Label for the drop-down list controlling the automatic highlighting of current word/selection occurrences in the style editor."
},
"cm_matchHighlightSelection": {
"message": "Selection only",
"description": "Style editor's 'highglight' drop-down list option: highlight the occurrences of currently selected text"
},
"cm_matchHighlightToken": {
"message": "Token under cursor",
"description": "Style editor's 'highglight' drop-down list option: highlight the occurrences of of the word/token under cursor even if nothing is selected"
},
"cm_resizeGripHint": {
"message": "Double-click to maximize/restore the height",
"description": "Tooltip for the resize grip in style editor"
},
"cm_smartIndent": { "cm_smartIndent": {
"message": "Use smart indentation", "message": "Use smart indentation",
"description": "Label for the checkbox controlling smart indentation option for the style editor." "description": "Label for the checkbox controlling smart indentation option for the style editor."
@ -119,22 +139,6 @@
"message": "Theme", "message": "Theme",
"description": "Label for the style editor's CSS theme." "description": "Label for the style editor's CSS theme."
}, },
"cm_matchHighlight": {
"message": "Highlight",
"description": "Label for the drop-down list controlling the automatic highlighting of current word/selection occurrences in the style editor."
},
"cm_matchHighlightToken": {
"message": "Token under cursor",
"description": "Style editor's 'highglight' drop-down list option: highlight the occurrences of of the word/token under cursor even if nothing is selected"
},
"cm_matchHighlightSelection": {
"message": "Selection only",
"description": "Style editor's 'highglight' drop-down list option: highlight the occurrences of currently selected text"
},
"cm_resizeGripHint": {
"message": "Double-click to maximize/restore the height",
"description": "Tooltip for the resize grip in style editor"
},
"dysfunctional": { "dysfunctional": {
"message": "Stylus cannot function because Firefox is either in private mode or is applying its website cookies policy to IndexedDB storage used by Stylus, which erroneously marks the secure moz-extension:// origin as insecure even though WebExtensions aren't websites and Stylus doesn't use cookies.\n\n1. Open Firefox options\n2. Go to 'Privacy & Security'\n3. Set 'History' mode to 'Use custom settings'\n4. Click 'Exceptions'\n5. Paste our manifest URL and click 'Allow'\n6. Click 'Save settings'\n7. Uncheck 'Always use private browsing mode'\n\nThe actual manifest URL is shown below.\nYou can also find it on about:debugging page.", "message": "Stylus cannot function because Firefox is either in private mode or is applying its website cookies policy to IndexedDB storage used by Stylus, which erroneously marks the secure moz-extension:// origin as insecure even though WebExtensions aren't websites and Stylus doesn't use cookies.\n\n1. Open Firefox options\n2. Go to 'Privacy & Security'\n3. Set 'History' mode to 'Use custom settings'\n4. Click 'Exceptions'\n5. Paste our manifest URL and click 'Allow'\n6. Click 'Save settings'\n7. Uncheck 'Always use private browsing mode'\n\nThe actual manifest URL is shown below.\nYou can also find it on about:debugging page.",
"description": "Displayed in Firefox when its settings make Stylus dysfunctional" "description": "Displayed in Firefox when its settings make Stylus dysfunctional"
@ -151,6 +155,14 @@
"message": "History", "message": "History",
"description": "Used in various places to show a history log of something" "description": "Used in various places to show a history log of something"
}, },
"genericResetLabel": {
"message": "Reset",
"description": "Used in various parts of UI to indicate that something may be reset to its original state"
},
"genericSavedMessage": {
"message": "Saved",
"description": "Used in various parts of the UI to indicate that something was saved"
},
"confirmNo": { "confirmNo": {
"message": "No", "message": "No",
"description": "'No' button in a confirm dialog" "description": "'No' button in a confirm dialog"
@ -337,13 +349,51 @@
"message": "Install update", "message": "Install update",
"description": "Label for the button to install an update for a single style" "description": "Label for the button to install an update for a single style"
}, },
"issues": { "linterConfigPopupTitle": {
"message": "Issues", "message": "Set $linter$ rules configuration",
"description": "Label for the CSSLint issues block on the style edit page" "description": "Stylelint or CSSLint popup header",
"placeholders": {
"linter": {
"content": "$1"
}
}
}, },
"issuesHelp": { "linterConfigTooltip": {
"message": "The issues found by <a href='https://github.com/CSSLint/csslint' target='_blank'>CSSLint</a> with these rules enabled:", "message": "Click to configure this linter",
"description": "Help popup message for the CSSLint issues block on the style edit page" "description": "Icon tooltip to indicate that it opens a popup with the selected linter configuration"
},
"linterCSSLintSettings": {
"message": "(Set rule as: 0 = disabled; 1 = warning; 2 = error)",
"description": "CSSLint rule config values"
},
"linterInvalidConfigError": {
"message": "Not saved due to these invalid configuration settings:",
"description": "Invalid linter config will show a message followed by a list of invalid entries"
},
"linterIssues": {
"message": "Issues",
"description": "Label for the CSS linter issues block on the style edit page"
},
"linterIssuesHelp": {
"message": "These issues were found by $link$:",
"description": "Help popup message for the selected CSS linter issues block on the style edit page",
"placeholders": {
"link": {
"content": "$1"
}
}
},
"linterJSONError": {
"message": "Invalid JSON format",
"description": "Setting linter config with invalid JSON"
},
"linterResetMessage": {
"message": "To undo accidental reset, press Ctrl-Z (or Cmd-Z) in the text box",
"description": "Reset button tooltip to inform user on how to undo an accidental reset"
},
"linterRulesLink": {
"message": "See a full list of rules",
"description": "Stylelint or CSSLint rules label added immediately before a link"
}, },
"manageFilters": { "manageFilters": {
"message": "Filters", "message": "Filters",

View File

@ -27,9 +27,9 @@
"message": "Exportar", "message": "Exportar",
"description": "Label for the button to export a style ('edit' page) or all styles ('manage' page)" "description": "Label for the button to export a style ('edit' page) or all styles ('manage' page)"
}, },
"issues": { "linterIssues": {
"message": "Problemas", "message": "Problemas",
"description": "Label for the CSSLint issues block on the style edit page" "description": "Label for the CSS linter issues block on the style edit page"
}, },
"optionsBadgeNormal": { "optionsBadgeNormal": {
"message": "Color de fondo", "message": "Color de fondo",
@ -656,9 +656,14 @@
"message": "Interfaz de opciones", "message": "Interfaz de opciones",
"description": "Go to Options UI" "description": "Go to Options UI"
}, },
"issuesHelp": { "linterIssuesHelp": {
"message": "Problemas encontrados por <a href='https://github.com/CSSLint/csslint' target='_blank'>CSSLint</a> con estas reglas aplicadas:", "message": "Problemas encontrados por $link$ con estas reglas aplicadas:",
"description": "Help popup message for the CSSLint issues block on the style edit page" "description": "Help popup message for the selected CSS linter issues block on the style edit page",
"placeholders": {
"link": {
"content": "$1"
}
}
}, },
"optionsCustomizeBadge": { "optionsCustomizeBadge": {
"message": "Distintivo en el icono de barra de herramientas", "message": "Distintivo en el icono de barra de herramientas",

View File

@ -27,9 +27,9 @@
"message": "Ekspordi", "message": "Ekspordi",
"description": "Label for the button to export a style ('edit' page) or all styles ('manage' page)" "description": "Label for the button to export a style ('edit' page) or all styles ('manage' page)"
}, },
"issues": { "linterIssues": {
"message": "Vead", "message": "Vead",
"description": "Label for the CSSLint issues block on the style edit page" "description": "Label for the CSS linter issues block on the style edit page"
}, },
"optionsBadgeNormal": { "optionsBadgeNormal": {
"message": "Taustavärv", "message": "Taustavärv",
@ -652,9 +652,14 @@
"message": "Valikute liides", "message": "Valikute liides",
"description": "Go to Options UI" "description": "Go to Options UI"
}, },
"issuesHelp": { "linterIssuesHelp": {
"message": "<a href='https://github.com/CSSLint/csslint' target='_blank'>CSSLint</a> poolt leitud vead nende lubatud reeglitega:", "message": "$link$ poolt leitud vead nende lubatud reeglitega:",
"description": "Help popup message for the CSSLint issues block on the style edit page" "description": "Help popup message for the selected CSS linter issues block on the style edit page",
"placeholders": {
"link": {
"content": "$1"
}
}
}, },
"optionsCustomizeBadge": { "optionsCustomizeBadge": {
"message": "Number tööriistaribaikoonil", "message": "Number tööriistaribaikoonil",

View File

@ -27,9 +27,9 @@
"message": "エクスポート", "message": "エクスポート",
"description": "Label for the button to export a style ('edit' page) or all styles ('manage' page)" "description": "Label for the button to export a style ('edit' page) or all styles ('manage' page)"
}, },
"issues": { "linterIssues": {
"message": "問題点", "message": "問題点",
"description": "Label for the CSSLint issues block on the style edit page" "description": "Label for the CSS linter issues block on the style edit page"
}, },
"optionsBadgeNormal": { "optionsBadgeNormal": {
"message": "背景色", "message": "背景色",
@ -648,9 +648,14 @@
"message": "オプション UI", "message": "オプション UI",
"description": "Go to Options UI" "description": "Go to Options UI"
}, },
"issuesHelp": { "linterIssuesHelp": {
"message": "これらのルールを有効にして <a href='https://github.com/CSSLint/csslint' target='_blank'>CSSLint</a> で見つかった問題:", "message": "これらのルールを有効にして $link$ で見つかった問題:",
"description": "Help popup message for the CSSLint issues block on the style edit page" "description": "Help popup message for the selected CSS linter issues block on the style edit page",
"placeholders": {
"link": {
"content": "$1"
}
}
}, },
"optionsCustomizeBadge": { "optionsCustomizeBadge": {
"message": "ツールバーアイコンのバッジ", "message": "ツールバーアイコンのバッジ",

View File

@ -11,9 +11,9 @@
"message": "Exporteren", "message": "Exporteren",
"description": "Label for the button to export a style ('edit' page) or all styles ('manage' page)" "description": "Label for the button to export a style ('edit' page) or all styles ('manage' page)"
}, },
"issues": { "linterIssues": {
"message": "Problemen", "message": "Problemen",
"description": "Label for the CSSLint issues block on the style edit page" "description": "Label for the CSS linter issues block on the style edit page"
}, },
"cm_tabSize": { "cm_tabSize": {
"message": "Tabgrootte", "message": "Tabgrootte",
@ -344,9 +344,14 @@
"message": "Controleer alle stijlen op updates", "message": "Controleer alle stijlen op updates",
"description": "Label for the button to check all styles for updates" "description": "Label for the button to check all styles for updates"
}, },
"issuesHelp": { "linterIssuesHelp": {
"message": "De door CSSLint gevonden problemen, <a href='https://github.com/CSSLint/csslint' target='_blank'>CSSLint</a>, met deze ingeschakelde regels:", "message": "De door stylelint gevonden problemen, $link$, met deze ingeschakelde regels:",
"description": "Help popup message for the CSSLint issues block on the style edit page" "description": "Help popup message for the selected CSS linter issues block on the style edit page",
"placeholders": {
"link": {
"content": "$1"
}
}
}, },
"confirmNo": { "confirmNo": {
"message": "Nee", "message": "Nee",

View File

@ -27,9 +27,9 @@
"message": "Eksportuj", "message": "Eksportuj",
"description": "Label for the button to export a style ('edit' page) or all styles ('manage' page)" "description": "Label for the button to export a style ('edit' page) or all styles ('manage' page)"
}, },
"issues": { "linterIssues": {
"message": "Problemy", "message": "Problemy",
"description": "Label for the CSSLint issues block on the style edit page" "description": "Label for the CSS linter issues block on the style edit page"
}, },
"optionsBadgeNormal": { "optionsBadgeNormal": {
"message": "Kolor tła", "message": "Kolor tła",
@ -656,9 +656,14 @@
"message": "Opcje interfejsu", "message": "Opcje interfejsu",
"description": "Go to Options UI" "description": "Go to Options UI"
}, },
"issuesHelp": { "linterIssuesHelp": {
"message": "Problemy znalezione przez <a href='https://github.com/CSSLint/csslint' target='_blank'>CSSLint</a> z tymi włączonymi regułami:", "message": "Problemy znalezione przez $link$ z tymi włączonymi regułami:",
"description": "Help popup message for the CSSLint issues block on the style edit page" "description": "Help popup message for the selected CSS linter issues block on the style edit page",
"placeholders": {
"link": {
"content": "$1"
}
}
}, },
"optionsCustomizeBadge": { "optionsCustomizeBadge": {
"message": "Emblemat na ikonie paska narzędzi", "message": "Emblemat na ikonie paska narzędzi",

View File

@ -27,9 +27,9 @@
"message": "Экспорт", "message": "Экспорт",
"description": "Label for the button to export a style ('edit' page) or all styles ('manage' page)" "description": "Label for the button to export a style ('edit' page) or all styles ('manage' page)"
}, },
"issues": { "linterIssues": {
"message": "Проблемы", "message": "Проблемы",
"description": "Label for the CSSLint issues block on the style edit page" "description": "Label for the CSS linter issues block on the style edit page"
}, },
"optionsBadgeNormal": { "optionsBadgeNormal": {
"message": "Цвет фона", "message": "Цвет фона",
@ -656,9 +656,14 @@
"message": "Настройки", "message": "Настройки",
"description": "Go to Options UI" "description": "Go to Options UI"
}, },
"issuesHelp": { "linterIssuesHelp": {
"message": "Проблемы и предупреждения по версии <a href='https://github.com/CSSLint/csslint' target='_blank'>CSSLint</a> с данными включенными правилами:", "message": "Проблемы и предупреждения по версии $link$ с данными включенными правилами:",
"description": "Help popup message for the CSSLint issues block on the style edit page" "description": "Help popup message for the selected CSS linter issues block on the style edit page",
"placeholders": {
"link": {
"content": "$1"
}
}
}, },
"optionsCustomizeBadge": { "optionsCustomizeBadge": {
"message": "Бейдж на пиктограмме в тулбаре", "message": "Бейдж на пиктограмме в тулбаре",

View File

@ -11,9 +11,9 @@
"message": "Извези", "message": "Извези",
"description": "Label for the button to export a style ('edit' page) or all styles ('manage' page)" "description": "Label for the button to export a style ('edit' page) or all styles ('manage' page)"
}, },
"issues": { "linterIssues": {
"message": "Проблеми", "message": "Проблеми",
"description": "Label for the CSSLint issues block on the style edit page" "description": "Label for the CSS linter issues block on the style edit page"
}, },
"cm_tabSize": { "cm_tabSize": {
"message": "Величина картице", "message": "Величина картице",
@ -356,9 +356,14 @@
"message": "Проверите ажурирања за све стилове", "message": "Проверите ажурирања за све стилове",
"description": "Label for the button to check all styles for updates" "description": "Label for the button to check all styles for updates"
}, },
"issuesHelp": { "linterIssuesHelp": {
"message": "Проблем пронађен од стране <a href='https://github.com/CSSLint/csslint' target='_blank'>CSSLint</a> са овим омогућеним правилима:", "message": "Проблем пронађен од стране $link$ са овим омогућеним правилима:",
"description": "Help popup message for the CSSLint issues block on the style edit page" "description": "Help popup message for the selected CSS linter issues block on the style edit page",
"placeholders": {
"link": {
"content": "$1"
}
}
}, },
"confirmNo": { "confirmNo": {
"message": "Не", "message": "Не",

View File

@ -27,9 +27,9 @@
"message": "导出", "message": "导出",
"description": "Label for the button to export a style ('edit' page) or all styles ('manage' page)" "description": "Label for the button to export a style ('edit' page) or all styles ('manage' page)"
}, },
"issues": { "linterIssues": {
"message": "问题", "message": "问题",
"description": "Label for the CSSLint issues block on the style edit page" "description": "Label for the CSS linter issues block on the style edit page"
}, },
"optionsBadgeNormal": { "optionsBadgeNormal": {
"message": "背景颜色", "message": "背景颜色",
@ -656,9 +656,14 @@
"message": "设置用户界面", "message": "设置用户界面",
"description": "Go to Options UI" "description": "Go to Options UI"
}, },
"issuesHelp": { "linterIssuesHelp": {
"message": "<a href='https://github.com/CSSLint/csslint' target='_blank'>CSSLint</a> 在已启用的这些规则中找到问题:", "message": "$link$ 在已启用的这些规则中找到问题:",
"description": "Help popup message for the CSSLint issues block on the style edit page" "description": "Help popup message for the selected CSS linter issues block on the style edit page",
"placeholders": {
"link": {
"content": "$1"
}
}
}, },
"optionsCustomizeBadge": { "optionsCustomizeBadge": {
"message": "样式计数器", "message": "样式计数器",

View File

@ -27,9 +27,9 @@
"message": "導出", "message": "導出",
"description": "Label for the button to export a style ('edit' page) or all styles ('manage' page)" "description": "Label for the button to export a style ('edit' page) or all styles ('manage' page)"
}, },
"issues": { "linterIssues": {
"message": "問題", "message": "問題",
"description": "Label for the CSSLint issues block on the style edit page" "description": "Label for the CSS linter issues block on the style edit page"
}, },
"optionsBadgeNormal": { "optionsBadgeNormal": {
"message": "背景顏色", "message": "背景顏色",
@ -660,9 +660,14 @@
"message": "選項介面", "message": "選項介面",
"description": "Go to Options UI" "description": "Go to Options UI"
}, },
"issuesHelp": { "linterIssuesHelp": {
"message": "由<a href='https://github.com/CSSLint/csslint' target='_blank'>CSSLint</a>發現啟用這些規則會產生衝突", "message": "由$link$發現啟用這些規則會產生衝突",
"description": "Help popup message for the CSSLint issues block on the style edit page" "description": "Help popup message for the selected CSS linter issues block on the style edit page",
"placeholders": {
"link": {
"content": "$1"
}
}
}, },
"optionsCustomizeBadge": { "optionsCustomizeBadge": {
"message": "在工具列圖示上的徽章", "message": "在工具列圖示上的徽章",

View File

@ -1,3 +1,4 @@
/* global LZString */
'use strict'; 'use strict';
const RX_NAMESPACE = new RegExp([/[\s\r\n]*/, const RX_NAMESPACE = new RegExp([/[\s\r\n]*/,
@ -41,6 +42,26 @@ var chromeLocal = {
}, },
}; };
// eslint-disable-next-line no-var
var chromeSync = {
get(options) {
return new Promise(resolve => {
chrome.storage.sync.get(options, data => resolve(data));
});
},
set(data) {
return new Promise(resolve => {
chrome.storage.sync.set(data, () => resolve(data));
});
},
getValue(key) {
return chromeSync.get(key).then(data => tryJSONparse(LZString.decompressFromUTF16(data[key])));
},
setValue(key, value) {
return chromeSync.set({[key]: LZString.compressToUTF16(JSON.stringify(value))});
}
};
function dbExec(method, data) { function dbExec(method, data) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {

View File

@ -9,6 +9,7 @@
<script src="content/apply.js"></script> <script src="content/apply.js"></script>
<link rel="stylesheet" href="edit/edit.css"> <link rel="stylesheet" href="edit/edit.css">
<script src="edit/edit.js"></script> <script src="edit/edit.js"></script>
<script src="edit/lint.js"></script>
<script src="vendor/codemirror/lib/codemirror.js"></script> <script src="vendor/codemirror/lib/codemirror.js"></script>
<link rel="stylesheet" href="vendor/codemirror/lib/codemirror.css"> <link rel="stylesheet" href="vendor/codemirror/lib/codemirror.css">
@ -33,11 +34,6 @@
<script src="vendor/codemirror/addon/edit/matchbrackets.js"></script> <script src="vendor/codemirror/addon/edit/matchbrackets.js"></script>
<link rel="stylesheet" href="vendor/codemirror/addon/lint/lint.css" />
<script src="vendor/csslint/csslint-worker.js"></script>
<script src="vendor/codemirror/addon/lint/lint.js"></script>
<script src="vendor-overwrites/codemirror/addon/lint/css-lint.js"></script>
<link rel="stylesheet" href="vendor/codemirror/addon/hint/show-hint.css" /> <link rel="stylesheet" href="vendor/codemirror/addon/hint/show-hint.css" />
<script src="vendor/codemirror/addon/hint/show-hint.js"></script> <script src="vendor/codemirror/addon/hint/show-hint.js"></script>
<script src="vendor/codemirror/addon/hint/css-hint.js"></script> <script src="vendor/codemirror/addon/hint/css-hint.js"></script>
@ -186,8 +182,20 @@
<option i18n-text="genericDisabledLabel" value=""> <option i18n-text="genericDisabledLabel" value="">
</select> </select>
</div> </div>
<div class="option aligned">
<label id="linter-label" for="editor.linter" i18n-text="cm_linter"></label>
<select id="editor.linter">
<option value="csslint" selected>CSSLint</option>
<option value="stylelint">Stylelint</option>
<option value="null" i18n-text="genericDisabledLabel"></option>
</select>
<span class="linter-settings" i18n-title="linterConfigTooltip">
<svg id="linter-settings" class="svg-icon settings">
<use xlink:href="#svg-icon-settings"/>
</svg>&nbsp;
</span>
</section> </section>
<section id="lint"><h2 i18n-text="issues">: <span id="issue-count"></span><svg id="lint-help" class="svg-icon info"><use xlink:href="#svg-icon-help"/></svg></h2><div></div></section> <section id="lint"><h2 i18n-text="linterIssues">: <span id="issue-count"></span><svg id="lint-help" class="svg-icon info"><use xlink:href="#svg-icon-help"/></svg></h2><div></div></section>
</div> </div>
<section id="sections"> <section id="sections">
<h2><span id="sections-heading" i18n-text="styleSectionsTitle"></span><svg id="sections-help" class="svg-icon info"><use xlink:href="#svg-icon-help"/></svg></h2> <h2><span id="sections-heading" i18n-text="styleSectionsTitle"></span><svg id="sections-help" class="svg-icon info"><use xlink:href="#svg-icon-help"/></svg></h2>
@ -207,6 +215,9 @@
<symbol id="svg-icon-close" height="16" width="12" viewBox="0 0 12 16"> <symbol id="svg-icon-close" height="16" width="12" viewBox="0 0 12 16">
<path fill-rule="evenodd" d="M7.48 8l3.75 3.75-1.48 1.48L6 9.48l-3.75 3.75-1.48-1.48L4.52 8 .77 4.25l1.48-1.48L6 6.52l3.75-3.75 1.48 1.48z"></path> <path fill-rule="evenodd" d="M7.48 8l3.75 3.75-1.48 1.48L6 9.48l-3.75 3.75-1.48-1.48L4.52 8 .77 4.25l1.48-1.48L6 6.52l3.75-3.75 1.48 1.48z"></path>
</symbol> </symbol>
<symbol id="svg-icon-settings" height="12" width="12" viewBox="0 0 16 16">
<path d="M16 9.45V6.52l-1.8-.3c-.14-.5-.34-.95-.56-1.35L14.7 3.4l-2.07-2.1-1.5 1.03c-.43-.24-.9-.43-1.37-.56L9.46 0H6.54l-.3 1.8c-.5.13-.94.33-1.36.55L3.4 1.32 1.3 3.4l1.07 1.45c-.24.45-.44.92-.57 1.42L0 6.53v2.95l1.8.3c.14.52.33.95.57 1.37l-1.07 1.5 2.06 2.08 1.5-1.07c.44.24.9.45 1.4.57l.3 1.77H9.5l.32-1.8c.47-.13.95-.32 1.34-.56l1.5 1.06 2.07-2.05-1.03-1.5c.24-.45.44-.9.56-1.36L16 9.44v-.02zm-8 1.6C6.3 11.05 4.93 9.7 4.93 8S6.33 4.9 8 4.9s3.06 1.4 3.06 3.1S9.7 11.04 8 11.04z"/>
</symbol>
</svg> </svg>
</body> </body>

49
edit/csslint-config.js Normal file
View File

@ -0,0 +1,49 @@
'use strict';
/**
* CSSLint Config values
* 0 = disabled; 1 = warning; 2 = error
*/
window.csslintDefaultConfig = {
// Default warnings
'display-property-grouping': 1,
'duplicate-properties': 1,
'empty-rules': 1,
'errors': 1,
'known-properties': 1,
// Default disabled
'adjoining-classes': 0,
'box-model': 0,
'box-sizing': 0,
'bulletproof-font-face': 0,
'compatible-vendor-prefixes': 0,
'duplicate-background-images': 0,
'fallback-colors': 0,
'floats': 0,
'font-faces': 0,
'font-sizes': 0,
'gradients': 0,
'ids': 0,
'import': 0,
'import-ie-limit': 0,
'important': 0,
'order-alphabetical': 0,
'outline-none': 0,
'overqualified-elements': 0,
'qualified-headings': 0,
'regex-selectors': 0,
'rules-count': 0,
'selector-max': 0,
'selector-max-approaching': 0,
'selector-newline': 0,
'shorthand': 0,
'star-property-hack': 0,
'text-indent': 0,
'underscore-property-hack': 0,
'unique-headings': 0,
'universal-selector': 0,
'unqualified-attributes': 0,
'vendor-prefix': 0,
'zero-units': 0
};

View File

@ -73,16 +73,19 @@ input[type="checkbox"] {
h2 .svg-icon, label .svg-icon { h2 .svg-icon, label .svg-icon {
margin-top: -1px; margin-top: -1px;
} }
.svg-icon.info { .svg-icon.info,
.svg-icon.settings {
width: 14px; width: 14px;
height: 16px; height: 16px;
} }
.svg-icon:hover, .svg-icon:hover,
.svg-icon.info { .svg-icon.info,
.svg-icon.settings {
fill: #666; fill: #666;
} }
.svg-icon, .svg-icon,
.svg-icon.info:hover { .svg-icon.info:hover,
.svg-icon.settings:hover {
fill: #000; fill: #000;
} }
#enabled { #enabled {
@ -361,11 +364,26 @@ body[data-match-highlight="selection"] .CodeMirror-selection-highlight-scrollbar
max-height: calc(100vh - 8rem); max-height: calc(100vh - 8rem);
overflow-y: auto; overflow-y: auto;
} }
#help-popup .settings {
min-width: 500px;
min-height: 200px;
max-width: 48vw;
}
#help-popup .dismiss { #help-popup .dismiss {
position: absolute; position: absolute;
right: 4px; right: 4px;
top: .5em; top: .5em;
} }
#help-popup .saved-message {
display: none;
color: #090;
margin-left: 10px;
font-weight: bold;
}
#help-popup .saved-message.show,
#options .linter-settings {
display: inline-block;
}
.keymap-list { .keymap-list {
font-size: 85%; font-size: 85%;
@ -397,6 +415,12 @@ body[data-match-highlight="selection"] .CodeMirror-selection-highlight-scrollbar
pointer-events: all; pointer-events: all;
opacity: 1.0; opacity: 1.0;
} }
#help-popup .rules {
padding: 0 15px;
}
#help-popup button {
margin-right: 3px;
}
/************ lint ************/ /************ lint ************/
#lint { #lint {
@ -428,7 +452,6 @@ body[data-match-highlight="selection"] .CodeMirror-selection-highlight-scrollbar
#lint td[role="severity"] { #lint td[role="severity"] {
font-size: 0; font-size: 0;
width: 16px; width: 16px;
padding-right: 0.25rem;
} }
#lint td[role="line"], #lint td[role="sep"] { #lint td[role="line"], #lint td[role="sep"] {
text-align: right; text-align: right;
@ -441,6 +464,9 @@ body[data-match-highlight="selection"] .CodeMirror-selection-highlight-scrollbar
#lint td[role="message"] { #lint td[role="message"] {
text-align: left; text-align: left;
} }
#message-box.center.lint-config #message-box-contents {
text-align: left;
}
/************ CSS beautifier ************/ /************ CSS beautifier ************/
.beautify-options { .beautify-options {
@ -543,7 +569,7 @@ body[data-match-highlight="selection"] .CodeMirror-selection-highlight-scrollbar
margin-bottom: 0; margin-bottom: 0;
} }
#lint > div { #lint > div {
max-height: 0; max-height: 20vh;
} }
#lint.collapsed > div { #lint.collapsed > div {
display: none; display: none;
@ -552,6 +578,12 @@ body[data-match-highlight="selection"] .CodeMirror-selection-highlight-scrollbar
margin-top: 1em; margin-top: 1em;
max-height: 30vh; max-height: 30vh;
} }
#lint table {
width: 100%;
}
#lint td[role="message"] {
max-width: none;
}
#sections { #sections {
padding-left: 0; padding-left: 0;
} }

View File

@ -1,12 +1,17 @@
/* eslint brace-style: 0, operator-linebreak: 0 */ /* eslint brace-style: 0, operator-linebreak: 0 */
/* global CodeMirror exports parserlib CSSLint */ /* global CodeMirror parserlib */
/* global exports css_beautify onDOMscripted */
/* global CSSLint initLint getLinterConfigForCodeMirror updateLintReport renderLintReport updateLinter */
'use strict'; 'use strict';
let styleId = null; let styleId = null;
let dirty = {}; // only the actually dirty items here // only the actually dirty items here
const editors = []; // array of all CodeMirror instances let dirty = {};
// array of all CodeMirror instances
const editors = [];
let saveSizeOnClose; let saveSizeOnClose;
let useHistoryBack; // use browser history back when 'back to manage' is clicked // use browser history back when 'back to manage' is clicked
let useHistoryBack;
// direct & reverse mapping of @-moz-document keywords and internal property names // direct & reverse mapping of @-moz-document keywords and internal property names
const propertyToCss = {urls: 'url', urlPrefixes: 'url-prefix', domains: 'domain', regexps: 'regexp'}; const propertyToCss = {urls: 'url', urlPrefixes: 'url-prefix', domains: 'domain', regexps: 'regexp'};
@ -32,7 +37,8 @@ Element.prototype.closest = Element.prototype.closest || function (selector) {
}; };
// eslint-disable-next-line no-extend-native // eslint-disable-next-line no-extend-native
Array.prototype.rotate = function (amount) { // negative amount == rotate left Array.prototype.rotate = function (amount) {
// negative amount == rotate left
const r = this.slice(-amount, this.length); const r = this.slice(-amount, this.length);
Array.prototype.push.apply(r, this.slice(0, this.length - r.length)); Array.prototype.push.apply(r, this.slice(0, this.length - r.length));
return r; return r;
@ -43,7 +49,7 @@ Object.defineProperty(Array.prototype, 'last', {get: function () { return this[t
// preload the theme so that CodeMirror can calculate its metrics in DOMContentLoaded->setupLivePrefs() // preload the theme so that CodeMirror can calculate its metrics in DOMContentLoaded->setupLivePrefs()
new MutationObserver((mutations, observer) => { new MutationObserver((mutations, observer) => {
const themeElement = document.getElementById('cm-theme'); const themeElement = $('#cm-theme');
if (themeElement) { if (themeElement) {
themeElement.href = prefs.get('editor.theme') === 'default' ? '' themeElement.href = prefs.get('editor.theme') === 'default' ? ''
: 'vendor/codemirror/theme/' + prefs.get('editor.theme') + '.css'; : 'vendor/codemirror/theme/' + prefs.get('editor.theme') + '.css';
@ -91,7 +97,8 @@ function onChange(event) {
} else { } else {
// the manually added section's applies-to is dirty only when the value is non-empty // the manually added section's applies-to is dirty only when the value is non-empty
setCleanItem(node, node.localName !== 'input' || !node.value.trim()); setCleanItem(node, node.localName !== 'input' || !node.value.trim());
delete node.savedValue; // only valid when actually saved // only valid when actually saved
delete node.savedValue;
} }
updateTitle(); updateTitle();
} }
@ -124,7 +131,7 @@ function setCleanItem(node, isClean) {
function isCleanGlobal() { function isCleanGlobal() {
const clean = Object.keys(dirty).length === 0; const clean = Object.keys(dirty).length === 0;
setDirtyClass(document.body, !clean); setDirtyClass(document.body, !clean);
// let saveBtn = document.getElementById('save-button') // let saveBtn = $('#save-button')
// if (clean){ // if (clean){
// //saveBtn.removeAttribute('disabled'); // //saveBtn.removeAttribute('disabled');
// }else{ // }else{
@ -134,12 +141,13 @@ function isCleanGlobal() {
} }
function setCleanGlobal() { function setCleanGlobal() {
document.querySelectorAll('#header, #sections > div').forEach(setCleanSection); $$('#header, #sections > div').forEach(setCleanSection);
dirty = {}; // forget the dirty applies-to ids from a deleted section after the style was saved // forget the dirty applies-to ids from a deleted section after the style was saved
dirty = {};
} }
function setCleanSection(section) { function setCleanSection(section) {
section.querySelectorAll('.style-contributor').forEach(node => { setCleanItem(node, true); }); $$('.style-contributor', section).forEach(node => { setCleanItem(node, true); });
// #header section has no codemirror // #header section has no codemirror
const cm = section.CodeMirror; const cm = section.CodeMirror;
@ -152,6 +160,9 @@ function setCleanSection(section) {
function initCodeMirror() { function initCodeMirror() {
const CM = CodeMirror; const CM = CodeMirror;
const isWindowsOS = navigator.appVersion.indexOf('Windows') > 0; const isWindowsOS = navigator.appVersion.indexOf('Windows') > 0;
// lint.js is not loaded initially
const hasLinter = typeof getLinterConfigForCodeMirror !== 'undefined' ?
getLinterConfigForCodeMirror(prefs.get('editor.linter')) : false;
// CodeMirror miserably fails on keyMap='' so let's ensure it's not // CodeMirror miserably fails on keyMap='' so let's ensure it's not
if (!prefs.get('editor.keyMap')) { if (!prefs.get('editor.keyMap')) {
@ -168,12 +179,13 @@ function initCodeMirror() {
matchBrackets: true, matchBrackets: true,
highlightSelectionMatches: {showToken: /[#.\-\w]/, annotateScrollbar: true}, highlightSelectionMatches: {showToken: /[#.\-\w]/, annotateScrollbar: true},
hintOptions: {}, hintOptions: {},
lint: {getAnnotations: CodeMirror.lint.css, delay: prefs.get('editor.lintDelay')}, lint: hasLinter,
lintReportDelay: prefs.get('editor.lintReportDelay'), lintReportDelay: prefs.get('editor.lintReportDelay'),
styleActiveLine: true, styleActiveLine: true,
theme: 'default', theme: 'default',
keyMap: prefs.get('editor.keyMap'), keyMap: prefs.get('editor.keyMap'),
extraKeys: { // independent of current keyMap extraKeys: {
// independent of current keyMap
'Alt-Enter': 'toggleStyle', 'Alt-Enter': 'toggleStyle',
'Alt-PageDown': 'nextEditor', 'Alt-PageDown': 'nextEditor',
'Alt-PageUp': 'prevEditor' 'Alt-PageUp': 'prevEditor'
@ -203,9 +215,12 @@ function initCodeMirror() {
CM.keyMap.macDefault['Cmd-J'] = 'jumpToLine'; CM.keyMap.macDefault['Cmd-J'] = 'jumpToLine';
} }
if (!extraKeysCommands.autocomplete) { if (!extraKeysCommands.autocomplete) {
CM.keyMap.pcDefault['Ctrl-Space'] = 'autocomplete'; // will be used by 'sublime' on PC via fallthrough // will be used by 'sublime' on PC via fallthrough
CM.keyMap.macDefault['Alt-Space'] = 'autocomplete'; // OSX uses Ctrl-Space and Cmd-Space for something else CM.keyMap.pcDefault['Ctrl-Space'] = 'autocomplete';
CM.keyMap.emacsy['Alt-/'] = 'autocomplete'; // copied from 'emacs' keymap // OSX uses Ctrl-Space and Cmd-Space for something else
CM.keyMap.macDefault['Alt-Space'] = 'autocomplete';
// copied from 'emacs' keymap
CM.keyMap.emacsy['Alt-/'] = 'autocomplete';
// 'vim' and 'emacs' define their own autocomplete hotkeys // 'vim' and 'emacs' define their own autocomplete hotkeys
} }
if (!extraKeysCommands.blockComment) { if (!extraKeysCommands.blockComment) {
@ -223,8 +238,10 @@ function initCodeMirror() {
// try to remap non-interceptable Ctrl-(Shift-)N/T/W hotkeys // try to remap non-interceptable Ctrl-(Shift-)N/T/W hotkeys
['N', 'T', 'W'].forEach(char => { ['N', 'T', 'W'].forEach(char => {
[{from: 'Ctrl-', to: ['Alt-', 'Ctrl-Alt-']}, [
{from: 'Shift-Ctrl-', to: ['Ctrl-Alt-', 'Shift-Ctrl-Alt-']} // Note: modifier order in CM is S-C-A {from: 'Ctrl-', to: ['Alt-', 'Ctrl-Alt-']},
// Note: modifier order in CM is S-C-A
{from: 'Shift-Ctrl-', to: ['Ctrl-Alt-', 'Shift-Ctrl-Alt-']}
].forEach(remap => { ].forEach(remap => {
const oldKey = remap.from + char; const oldKey = remap.from + char;
Object.keys(CM.keyMap).forEach(keyMapName => { Object.keys(CM.keyMap).forEach(keyMapName => {
@ -267,7 +284,8 @@ function initCodeMirror() {
} }
parent.appendChild(fragment); parent.appendChild(fragment);
} }
const themeControl = document.getElementById('editor.theme'); // no need to escape the period in the id
const themeControl = $('#editor.theme');
const themeList = localStorage.codeMirrorThemes; const themeList = localStorage.codeMirrorThemes;
if (themeList) { if (themeList) {
optionsFromArray(themeControl, themeList.split(/\s+/)); optionsFromArray(themeControl, themeList.split(/\s+/));
@ -282,7 +300,7 @@ function initCodeMirror() {
}); });
} }
optionsFromArray($('#editor.keyMap'), Object.keys(CM.keyMap).sort()); optionsFromArray($('#editor.keyMap'), Object.keys(CM.keyMap).sort());
document.getElementById('options').addEventListener('change', acmeEventListener, false); $('#options').addEventListener('change', acmeEventListener, false);
setupLivePrefs(); setupLivePrefs();
hotkeyRerouter.setState(true); hotkeyRerouter.setState(true);
@ -302,7 +320,7 @@ function acmeEventListener(event) {
CodeMirror.setOption('indentUnit', Number(value)); CodeMirror.setOption('indentUnit', Number(value));
break; break;
case 'theme': { case 'theme': {
const themeLink = document.getElementById('cm-theme'); const themeLink = $('#cm-theme');
// use non-localized 'default' internally // use non-localized 'default' internally
if (!value || value === 'default' || value === t('defaultTheme')) { if (!value || value === 'default' || value === t('defaultTheme')) {
value = 'default'; value = 'default';
@ -314,7 +332,8 @@ function acmeEventListener(event) {
break; break;
} }
const url = chrome.runtime.getURL('vendor/codemirror/theme/' + value + '.css'); const url = chrome.runtime.getURL('vendor/codemirror/theme/' + value + '.css');
if (themeLink.href === url) { // preloaded in initCodeMirror() if (themeLink.href === url) {
// preloaded in initCodeMirror()
break; break;
} }
// avoid flicker: wait for the second stylesheet to load, then apply the theme // avoid flicker: wait for the second stylesheet to load, then apply the theme
@ -348,6 +367,10 @@ function acmeEventListener(event) {
default: default:
value = null; value = null;
} }
break;
case 'linter':
updateLinter(value);
break;
} }
CodeMirror.setOption(option, value); CodeMirror.setOption(option, value);
} }
@ -391,8 +414,10 @@ function setupCodeMirror(textarea, index) {
} }
lastClickTime = Date.now(); lastClickTime = Date.now();
const minHeight = cm.defaultTextHeight() + const minHeight = cm.defaultTextHeight() +
cm.display.lineDiv.offsetParent.offsetTop + /* .CodeMirror-lines padding */ /* .CodeMirror-lines padding */
wrapper.offsetHeight - wrapper.clientHeight; /* borders */ cm.display.lineDiv.offsetParent.offsetTop +
/* borders */
wrapper.offsetHeight - wrapper.clientHeight;
wrapper.style.pointerEvents = 'none'; wrapper.style.pointerEvents = 'none';
document.body.style.cursor = 's-resize'; document.body.style.cursor = 's-resize';
function resize(e) { function resize(e) {
@ -419,7 +444,7 @@ function indicateCodeChange(cm) {
const section = cm.getSection(); const section = cm.getSection();
setCleanItem(section, cm.isClean(section.savedValue)); setCleanItem(section, cm.isClean(section.savedValue));
updateTitle(); updateTitle();
updateLintReport(cm); updateLintReportIfEnabled(cm);
} }
function getSectionForChild(e) { function getSectionForChild(e) {
@ -427,13 +452,13 @@ function getSectionForChild(e) {
} }
function getSections() { function getSections() {
return document.querySelectorAll('#sections > div'); return $$('#sections > div');
} }
// remind Chrome to repaint a previously invisible editor box by toggling any element's transform // remind Chrome to repaint a previously invisible editor box by toggling any element's transform
// this bug is present in some versions of Chrome (v37-40 or something) // this bug is present in some versions of Chrome (v37-40 or something)
document.addEventListener('scroll', () => { document.addEventListener('scroll', () => {
const style = document.getElementById('name').style; const style = $('#name').style;
style.webkitTransform = style.webkitTransform ? '' : 'scale(1)'; style.webkitTransform = style.webkitTransform ? '' : 'scale(1)';
}); });
@ -546,12 +571,12 @@ window.onbeforeunload = () => {
if (isCleanGlobal()) { if (isCleanGlobal()) {
return; return;
} }
updateLintReport(null, 0); updateLintReportIfEnabled(null, 0);
return confirm(t('styleChangesNotSaved')); return confirm(t('styleChangesNotSaved'));
}; };
function addAppliesTo(list, name, value) { function addAppliesTo(list, name, value) {
const showingEverything = list.querySelector('.applies-to-everything') !== null; const showingEverything = $('.applies-to-everything', list) !== null;
// blow away 'Everything' if it's there // blow away 'Everything' if it's there
if (showingEverything) { if (showingEverything) {
list.removeChild(list.firstChild); list.removeChild(list.firstChild);
@ -559,19 +584,19 @@ function addAppliesTo(list, name, value) {
let e; let e;
if (name && value) { if (name && value) {
e = template.appliesTo.cloneNode(true); e = template.appliesTo.cloneNode(true);
e.querySelector('[name=applies-type]').value = name; $('[name=applies-type]', e).value = name;
e.querySelector('[name=applies-value]').value = value; $('[name=applies-value]', e).value = value;
e.querySelector('.remove-applies-to').addEventListener('click', removeAppliesTo, false); $('.remove-applies-to', e).addEventListener('click', removeAppliesTo, false);
} else if (showingEverything || list.hasChildNodes()) { } else if (showingEverything || list.hasChildNodes()) {
e = template.appliesTo.cloneNode(true); e = template.appliesTo.cloneNode(true);
if (list.hasChildNodes()) { if (list.hasChildNodes()) {
e.querySelector('[name=applies-type]').value = list.querySelector('li:last-child [name="applies-type"]').value; $('[name=applies-type]', e).value = $('li:last-child [name="applies-type"]', list).value;
} }
e.querySelector('.remove-applies-to').addEventListener('click', removeAppliesTo, false); $('.remove-applies-to', e).addEventListener('click', removeAppliesTo, false);
} else { } else {
e = template.appliesToEverything.cloneNode(true); e = template.appliesToEverything.cloneNode(true);
} }
e.querySelector('.add-applies-to').addEventListener('click', function () { $('.add-applies-to', e).addEventListener('click', function () {
addAppliesTo(this.parentNode.parentNode); addAppliesTo(this.parentNode.parentNode);
}, false); }, false);
list.appendChild(e); list.appendChild(e);
@ -579,13 +604,13 @@ function addAppliesTo(list, name, value) {
function addSection(event, section) { function addSection(event, section) {
const div = template.section.cloneNode(true); const div = template.section.cloneNode(true);
div.querySelector('.applies-to-help').addEventListener('click', showAppliesToHelp, false); $('.applies-to-help', div).addEventListener('click', showAppliesToHelp, false);
div.querySelector('.remove-section').addEventListener('click', removeSection, false); $('.remove-section', div).addEventListener('click', removeSection, false);
div.querySelector('.add-section').addEventListener('click', addSection, false); $('.add-section', div).addEventListener('click', addSection, false);
div.querySelector('.beautify-section').addEventListener('click', beautify); $('.beautify-section', div).addEventListener('click', beautify);
const codeElement = div.querySelector('.code'); const codeElement = $('.code', div);
const appliesTo = div.querySelector('.applies-to-list'); const appliesTo = $('.applies-to-list', div);
let appliesToAdded = false; let appliesToAdded = false;
if (section) { if (section) {
@ -608,24 +633,25 @@ function addSection(event, section) {
toggleTestRegExpVisibility(); toggleTestRegExpVisibility();
appliesTo.addEventListener('change', toggleTestRegExpVisibility); appliesTo.addEventListener('change', toggleTestRegExpVisibility);
div.querySelector('.test-regexp').onclick = showRegExpTester; $('.test-regexp', div).onclick = showRegExpTester;
function toggleTestRegExpVisibility() { function toggleTestRegExpVisibility() {
const show = [...appliesTo.children].some(item => const show = [...appliesTo.children].some(item =>
!item.matches('.applies-to-everything') && !item.matches('.applies-to-everything') &&
item.querySelector('.applies-type').value === 'regexp' && $('.applies-type', item).value === 'regexp' &&
item.querySelector('.applies-value').value.trim()); $('.applies-value', item).value.trim()
);
div.classList.toggle('has-regexp', show); div.classList.toggle('has-regexp', show);
appliesTo.oninput = appliesTo.oninput || show && (event => { appliesTo.oninput = appliesTo.oninput || show && (event => {
if ( if (
event.target.matches('.applies-value') && event.target.matches('.applies-value') &&
event.target.parentElement.querySelector('.applies-type').value === 'regexp' $('.applies-type', event.target.parentElement).value === 'regexp'
) { ) {
showRegExpTester(null, div); showRegExpTester(null, div);
} }
}); });
} }
const sections = document.getElementById('sections'); const sections = $('#sections');
let cm; let cm;
if (event) { if (event) {
const clickedSection = getSectionForChild(event.target); const clickedSection = getSectionForChild(event.target);
@ -639,7 +665,6 @@ function addSection(event, section) {
sections.appendChild(div); sections.appendChild(div);
cm = setupCodeMirror(codeElement); cm = setupCodeMirror(codeElement);
} }
div.CodeMirror = cm; div.CodeMirror = cm;
setCleanSection(div); setCleanSection(div);
return div; return div;
@ -663,7 +688,7 @@ function removeSection(event) {
} }
function removeAreaAndSetDirty(area) { function removeAreaAndSetDirty(area) {
const contributors = area.querySelectorAll('.style-contributor'); const contributors = $$('.style-contributor', area);
if (!contributors.length) { if (!contributors.length) {
setCleanItem(area, false); setCleanItem(area, false);
} }
@ -707,9 +732,11 @@ function setupGlobalSearch() {
const originalOpenDialog = CodeMirror.prototype.openDialog; const originalOpenDialog = CodeMirror.prototype.openDialog;
const originalOpenConfirm = CodeMirror.prototype.openConfirm; const originalOpenConfirm = CodeMirror.prototype.openConfirm;
let curState; // cm.state.search for last used 'find' // cm.state.search for last used 'find'
let curState;
function shouldIgnoreCase(query) { // treat all-lowercase non-regexp queries as case-insensitive function shouldIgnoreCase(query) {
// treat all-lowercase non-regexp queries as case-insensitive
return typeof query === 'string' && query === query.toLowerCase(); return typeof query === 'string' && query === query.toLowerCase();
} }
@ -779,7 +806,8 @@ function setupGlobalSearch() {
return; return;
} }
let pos = activeCM.getCursor(reverse ? 'from' : 'to'); let pos = activeCM.getCursor(reverse ? 'from' : 'to');
activeCM.setSelection(activeCM.getCursor()); // clear the selection, don't move the cursor // clear the selection, don't move the cursor
activeCM.setSelection(activeCM.getCursor());
const rxQuery = typeof state.query === 'object' const rxQuery = typeof state.query === 'object'
? state.query : stringAsRegExp(state.query, shouldIgnoreCase(state.query) ? 'i' : ''); ? state.query : stringAsRegExp(state.query, shouldIgnoreCase(state.query) ? 'i' : '');
@ -820,7 +848,7 @@ function setupGlobalSearch() {
originalCommand[reverse ? 'findPrev' : 'findNext'](activeCM); originalCommand[reverse ? 'findPrev' : 'findNext'](activeCM);
function searchAppliesTo(cm) { function searchAppliesTo(cm) {
let inputs = [].slice.call(cm.getSection().querySelectorAll('.applies-value')); let inputs = $$('.applies-value', cm.getSection());
if (reverse) { if (reverse) {
inputs = inputs.reverse(); inputs = inputs.reverse();
} }
@ -883,7 +911,7 @@ function setupGlobalSearch() {
} else { } else {
doConfirm(cm); doConfirm(cm);
callback(replacement); callback(replacement);
if (!cm.getWrapperElement().querySelector('.CodeMirror-dialog')) { if (!$('.CodeMirror-dialog', cm.getWrapperElement())) {
// no dialog == nothing found in the current CM, move to the next // no dialog == nothing found in the current CM, move to the next
doReplace(); doReplace();
} }
@ -907,7 +935,7 @@ function setupGlobalSearch() {
const cmp = CodeMirror.cmpPos(cm.getCursor(), pos); const cmp = CodeMirror.cmpPos(cm.getCursor(), pos);
wrapAround |= cmp <= 0; wrapAround |= cmp <= 0;
const dlg = cm.getWrapperElement().querySelector('.CodeMirror-dialog'); const dlg = $('.CodeMirror-dialog', cm.getWrapperElement());
if (!dlg || cmp === 0 || wrapAround && CodeMirror.cmpPos(cm.getCursor(), origPos) >= 0) { if (!dlg || cmp === 0 || wrapAround && CodeMirror.cmpPos(cm.getCursor(), origPos) >= 0) {
if (dlg) { if (dlg) {
dlg.remove(); dlg.remove();
@ -1000,14 +1028,14 @@ function autocompletePicked(cm) {
function refocusMinidialog(cm) { function refocusMinidialog(cm) {
const section = cm.getSection(); const section = cm.getSection();
if (!section.querySelector('.CodeMirror-dialog')) { if (!$('.CodeMirror-dialog', section)) {
return; return;
} }
// close the currently opened minidialog // close the currently opened minidialog
cm.focus(); cm.focus();
// make sure to focus the input in newly opened minidialog // make sure to focus the input in newly opened minidialog
setTimeout(() => { setTimeout(() => {
section.querySelector('.CodeMirror-dialog').focus(); $('.CodeMirror-dialog', section).focus();
}, 0); }, 0);
} }
@ -1037,7 +1065,8 @@ function getEditorInSight(nearbyElement) {
return cm; return cm;
function offscreenDistance(cm) { function offscreenDistance(cm) {
const LINES_VISIBLE = 2; // closest editor should have at least # lines visible // closest editor should have at least # lines visible
const LINES_VISIBLE = 2;
const bounds = cm.getSection().getBoundingClientRect(); const bounds = cm.getSection().getBoundingClientRect();
if (bounds.top < 0) { if (bounds.top < 0) {
return -bounds.top; return -bounds.top;
@ -1049,145 +1078,11 @@ function getEditorInSight(nearbyElement) {
} }
} }
function updateLintReport(cm, delay) {
if (delay === 0) {
// immediately show pending csslint 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 scopedState = cm.state.lint || {};
const oldMarkers = scopedState.markedLast || {};
const newMarkers = {};
const html = !scopedState.marked || scopedState.marked.length === 0 ? '' : '<tbody>' +
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);
let message = escapeHtml(info.message.replace(/ at line \d.+$/, ''));
if (message.length > 100) {
message = message.substr(0, 100) + '...';
}
if (isActiveLine || oldMarkers[pos] === message) {
delete oldMarkers[pos];
}
newMarkers[pos] = message;
return '<tr class="' + info.severity + '">' +
'<td role="severity" class="CodeMirror-lint-marker-' + info.severity + '">' +
info.severity + '</td>' +
'<td role="line">' + (info.from.line + 1) + '</td>' +
'<td role="sep">:</td>' +
'<td role="col">' + (info.from.ch + 1) + '</td>' +
'<td role="message">' + message + '</td></tr>';
}).join('') + '</tbody>';
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 = {'&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;', '/': '&#x2F;'};
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 = '<caption>' + label + ' ' + (index + 1) + '</caption>' + 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) { function beautify(event) {
if (exports.css_beautify) { // thanks to csslint's definition of 'exports' const script = $('script[src*="beautify-css-mod"]') ?
doBeautify(); [] : ['vendor-overwrites/beautify/beautify-css-mod.js'];
} else { onDOMscripted(script).then(doBeautify);
const script = document.head.appendChild(document.createElement('script'));
script.src = 'vendor-overwrites/beautify/beautify-css-mod.js';
script.onload = doBeautify;
}
function doBeautify() { function doBeautify() {
const tabs = prefs.get('editor.indentWithTabs'); const tabs = prefs.get('editor.indentWithTabs');
const options = prefs.get('editor.beautify'); const options = prefs.get('editor.beautify');
@ -1210,7 +1105,7 @@ function beautify(event) {
'</div>' + '</div>' +
'<div><button role="undo"></button></div>'); '<div><button role="undo"></button></div>');
const undoButton = document.querySelector('#help-popup button[role="undo"]'); const undoButton = $('#help-popup button[role="undo"]');
undoButton.textContent = t(scope.length === 1 ? 'undo' : 'undoGlobal'); undoButton.textContent = t(scope.length === 1 ? 'undo' : 'undoGlobal');
undoButton.addEventListener('click', () => { undoButton.addEventListener('click', () => {
let undoable = false; let undoable = false;
@ -1231,7 +1126,7 @@ function beautify(event) {
[].concat.apply([], cm.doc.sel.ranges.map(r => [].concat.apply([], cm.doc.sel.ranges.map(r =>
[Object.assign({}, r.anchor), Object.assign({}, r.head)])); [Object.assign({}, r.anchor), Object.assign({}, r.head)]));
const text = cm.getValue(); const text = cm.getValue();
const newText = exports.css_beautify(text, options); const newText = css_beautify(text, options);
if (newText !== text) { if (newText !== text) {
if (!cm.beautifyChange || !cm.beautifyChange[cm.changeGeneration()]) { if (!cm.beautifyChange || !cm.beautifyChange[cm.changeGeneration()]) {
// clear the list if last change wasn't a css-beautify // clear the list if last change wasn't a css-beautify
@ -1249,7 +1144,7 @@ function beautify(event) {
}, 0); }, 0);
}); });
document.querySelector('.beautify-options').onchange = ({target}) => { $('.beautify-options').onchange = ({target}) => {
const value = target.type === 'checkbox' ? target.checked : target.selectedIndex > 0; const value = target.type === 'checkbox' ? target.checked : target.selectedIndex > 0;
prefs.set('editor.beautify', Object.assign(options, {[target.dataset.option]: value})); prefs.set('editor.beautify', Object.assign(options, {[target.dataset.option]: value}));
if (target.parentNode.hasAttribute('newline')) { if (target.parentNode.hasAttribute('newline')) {
@ -1275,7 +1170,8 @@ document.addEventListener('DOMContentLoaded', init);
function init() { function init() {
initCodeMirror(); initCodeMirror();
const params = getParams(); const params = getParams();
if (!params.id) { // match should be 2 - one for the whole thing, one for the parentheses if (!params.id) {
// match should be 2 - one for the whole thing, one for the parentheses
// This is an add // This is an add
$('#heading').textContent = t('addStyleTitle'); $('#heading').textContent = t('addStyleTitle');
const section = {code: ''}; const section = {code: ''};
@ -1289,7 +1185,7 @@ function init() {
addSection(null, section); addSection(null, section);
editors[0].setOption('lint', CodeMirror.defaults.lint); editors[0].setOption('lint', CodeMirror.defaults.lint);
// default to enabled // default to enabled
document.getElementById('enabled').checked = true; $('#enabled').checked = true;
initHooks(); initHooks();
}; };
return; return;
@ -1316,9 +1212,9 @@ function init() {
} }
function setStyleMeta(style) { function setStyleMeta(style) {
document.getElementById('name').value = style.name; $('#name').value = style.name;
document.getElementById('enabled').checked = style.enabled; $('#enabled').checked = style.enabled;
document.getElementById('url').href = style.url; $('#url').href = style.url;
} }
function initWithStyle({style, codeIsUpdated}) { function initWithStyle({style, codeIsUpdated}) {
@ -1350,35 +1246,30 @@ function initWithStyle({style, codeIsUpdated}) {
const sectionDiv = addSection(null, queue.shift()); const sectionDiv = addSection(null, queue.shift());
maximizeCodeHeight(sectionDiv, !queue.length); maximizeCodeHeight(sectionDiv, !queue.length);
const cm = sectionDiv.CodeMirror; const cm = sectionDiv.CodeMirror;
if (CodeMirror.lint) {
setTimeout(() => { setTimeout(() => {
cm.setOption('lint', CodeMirror.defaults.lint); cm.setOption('lint', CodeMirror.defaults.lint);
updateLintReport(cm, 0); updateLintReport(cm, 0);
}, prefs.get('editor.lintDelay')); }, prefs.get('editor.lintDelay'));
} }
}
} }
function initHooks() { function initHooks() {
document.querySelectorAll('#header .style-contributor').forEach(node => { $$('#header .style-contributor').forEach(node => {
node.addEventListener('change', onChange); node.addEventListener('change', onChange);
node.addEventListener('input', onChange); node.addEventListener('input', onChange);
}); });
document.getElementById('toggle-style-help').addEventListener('click', showToggleStyleHelp); $('#toggle-style-help').addEventListener('click', showToggleStyleHelp);
document.getElementById('to-mozilla').addEventListener('click', showMozillaFormat, false); $('#to-mozilla').addEventListener('click', showMozillaFormat, false);
document.getElementById('to-mozilla-help').addEventListener('click', showToMozillaHelp, false); $('#to-mozilla-help').addEventListener('click', showToMozillaHelp, false);
document.getElementById('from-mozilla').addEventListener('click', fromMozillaFormat); $('#from-mozilla').addEventListener('click', fromMozillaFormat);
document.getElementById('beautify').addEventListener('click', beautify); $('#beautify').addEventListener('click', beautify);
document.getElementById('save-button').addEventListener('click', save, false); $('#save-button').addEventListener('click', save, false);
document.getElementById('sections-help').addEventListener('click', showSectionHelp, false); $('#sections-help').addEventListener('click', showSectionHelp, false);
document.getElementById('keyMap-help').addEventListener('click', showKeyMapHelp, false); $('#keyMap-help').addEventListener('click', showKeyMapHelp, false);
document.getElementById('cancel-button').addEventListener('click', goBackToManage); $('#cancel-button').addEventListener('click', goBackToManage);
document.getElementById('lint-help').addEventListener('click', showLintHelp); initLint();
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);
}
if (!FIREFOX) { if (!FIREFOX) {
$$([ $$([
@ -1443,7 +1334,7 @@ function maximizeCodeHeight(sectionDiv, isLast) {
return; return;
} }
// scale heights to fill the gap between last section and bottom edge of the window // scale heights to fill the gap between last section and bottom edge of the window
const sections = document.getElementById('sections'); const sections = $('#sections');
const available = window.innerHeight - sections.getBoundingClientRect().bottom - const available = window.innerHeight - sections.getBoundingClientRect().bottom -
parseFloat(getComputedStyle(sections).marginBottom); parseFloat(getComputedStyle(sections).marginBottom);
if (available <= 0) { if (available <= 0) {
@ -1460,25 +1351,25 @@ function maximizeCodeHeight(sectionDiv, isLast) {
function updateTitle() { function updateTitle() {
const DIRTY_TITLE = '* $'; const DIRTY_TITLE = '* $';
const name = document.getElementById('name').savedValue; const name = $('#name').savedValue;
const clean = isCleanGlobal(); const clean = isCleanGlobal();
const title = styleId === null ? t('addStyleTitle') : t('editStyleTitle', [name]); const title = styleId === null ? t('addStyleTitle') : t('editStyleTitle', [name]);
document.title = clean ? title : DIRTY_TITLE.replace('$', title); document.title = clean ? title : DIRTY_TITLE.replace('$', title);
} }
function validate() { function validate() {
const name = document.getElementById('name').value; const name = $('#name').value;
if (name === '') { if (name === '') {
return t('styleMissingName'); return t('styleMissingName');
} }
// validate the regexps // validate the regexps
if (document.querySelectorAll('.applies-to-list').some(list => { if ($$('.applies-to-list').some(list => {
list.childNodes.some(li => { list.childNodes.some(li => {
if (li.className === template.appliesToEverything.className) { if (li.className === template.appliesToEverything.className) {
return false; return false;
} }
const valueElement = li.querySelector('[name=applies-value]'); const valueElement = $('[name=applies-value]', li);
const type = li.querySelector('[name=applies-type]').value; const type = $('[name=applies-type]', li).value;
const value = valueElement.value; const value = valueElement.value;
if (type && value) { if (type && value) {
if (type === 'regexp') { if (type === 'regexp') {
@ -1498,8 +1389,14 @@ function validate() {
return null; return null;
} }
function updateLintReportIfEnabled(cm, time) {
if (CodeMirror.lint) {
updateLintReport(cm, time);
}
}
function save() { function save() {
updateLintReport(null, 0); updateLintReportIfEnabled(null, 0);
// save the contents of the CodeMirror editors back into the textareas // save the contents of the CodeMirror editors back into the textareas
for (let i = 0; i < editors.length; i++) { for (let i = 0; i < editors.length; i++) {
@ -1511,8 +1408,8 @@ function save() {
alert(error); alert(error);
return; return;
} }
const name = document.getElementById('name').value; const name = $('#name').value;
const enabled = document.getElementById('enabled').checked; const enabled = $('#enabled').checked;
saveStyleSafe({ saveStyleSafe({
id: styleId, id: styleId,
name: name, name: name,
@ -1539,12 +1436,12 @@ function getSectionsHashes() {
function getMeta(e) { function getMeta(e) {
const meta = {urls: [], urlPrefixes: [], domains: [], regexps: []}; const meta = {urls: [], urlPrefixes: [], domains: [], regexps: []};
e.querySelector('.applies-to-list').childNodes.forEach(li => { $('.applies-to-list', e).childNodes.forEach(li => {
if (li.className === template.appliesToEverything.className) { if (li.className === template.appliesToEverything.className) {
return; return;
} }
const type = li.querySelector('[name=applies-type]').value; const type = $('[name=applies-type]', li).value;
const value = li.querySelector('[name=applies-value]').value; const value = $('[name=applies-value]', li).value;
if (type && value) { if (type && value) {
const property = CssToProperty[type]; const property = CssToProperty[type];
meta[property].push(value); meta[property].push(value);
@ -1593,12 +1490,12 @@ function fromMozillaFormat() {
</div>` </div>`
)); ));
const contents = popup.querySelector('.contents'); const contents = $('.contents', popup);
contents.insertBefore(popup.codebox.display.wrapper, contents.firstElementChild); contents.insertBefore(popup.codebox.display.wrapper, contents.firstElementChild);
popup.codebox.focus(); popup.codebox.focus();
popup.querySelector('[name="import-append"]').addEventListener('click', doImport); $('[name="import-append"]', popup).addEventListener('click', doImport);
popup.querySelector('[name="import-replace"]').addEventListener('click', doImport); $('[name="import-replace"]', popup).addEventListener('click', doImport);
popup.codebox.on('change', () => { popup.codebox.on('change', () => {
clearTimeout(popup.mozillaTimeout); clearTimeout(popup.mozillaTimeout);
@ -1607,9 +1504,15 @@ function fromMozillaFormat() {
}, 100); }, 100);
}); });
function doImport() { function doImport(event) {
const replaceOldStyle = this.name === 'import-replace'; // parserlib contained in CSSLint-worker.js
popup.querySelector('.dismiss').onclick(); onDOMscripted(['vendor-overwrites/csslint/csslint-worker.js'])
.then(() => doImportWhenReady(event.target));
}
function doImportWhenReady(target) {
const replaceOldStyle = target.name === 'import-replace';
$('.dismiss', popup).onclick();
const mozStyle = trimNewLines(popup.codebox.getValue()); const mozStyle = trimNewLines(popup.codebox.getValue());
const parser = new parserlib.css.Parser(); const parser = new parserlib.css.Parser();
const lines = mozStyle.split('\n'); const lines = mozStyle.split('\n');
@ -1668,7 +1571,7 @@ function fromMozillaFormat() {
firstAddedCM.focus(); firstAddedCM.focus();
if (errors.length) { if (errors.length) {
showHelp(t('issues'), $element({ showHelp(t('linterIssues'), $element({
tag: 'pre', tag: 'pre',
textContent: errors.join('\n'), textContent: errors.join('\n'),
})); }));
@ -1718,8 +1621,12 @@ function fromMozillaFormat() {
// do onetime housekeeping as the imported text is confirmed to be a valid style // do onetime housekeeping as the imported text is confirmed to be a valid style
function initFirstSection(section) { function initFirstSection(section) {
// skip adding the first global section when there's no code/comments // skip adding the first global section when there's no code/comments
if (!section.code.replace('@namespace url(http://www.w3.org/1999/xhtml);', '') /* ignore boilerplate NS */ if (
.replace(/[\s\n]/g, '')) { /* ignore all whitespace including new lines */ /* ignore boilerplate NS */
!section.code.replace('@namespace url(http://www.w3.org/1999/xhtml);', '')
/* ignore all whitespace including new lines */
.replace(/[\s\n]/g, '')
) {
return false; return false;
} }
if (replaceOldStyle) { if (replaceOldStyle) {
@ -1728,7 +1635,7 @@ function fromMozillaFormat() {
}); });
} else if (!editors.last.getValue()) { } else if (!editors.last.getValue()) {
// nuke the last blank section // nuke the last blank section
if (editors.last.getSection().querySelector('.applies-to-everything')) { if ($('.applies-to-everything', editors.last.getSection())) {
removeSection({target: editors.last.getSection()}); removeSection({target: editors.last.getSection()});
} }
} }
@ -1780,10 +1687,10 @@ function showKeyMapHelp() {
'</tbody>' + '</tbody>' +
'</table>'); '</table>');
const table = document.querySelector('#help-popup table'); const table = $('#help-popup table');
table.addEventListener('input', filterTable); table.addEventListener('input', filterTable);
const inputs = table.querySelectorAll('input'); const inputs = $$('input', table);
inputs[0].addEventListener('keydown', hotkeyHandler); inputs[0].addEventListener('keydown', hotkeyHandler);
inputs[1].focus(); inputs[1].focus();
@ -1865,24 +1772,17 @@ function showKeyMapHelp() {
} }
} }
function showLintHelp() {
showHelp(t('issues'), t('issuesHelp') + '<ul>' +
CSSLint.getRules().map(rule =>
'<li><b>' + rule.name + '</b><br>' + rule.desc + '</li>'
).join('') + '</ul>'
);
}
function showRegExpTester(event, section = getSectionForChild(this)) { function showRegExpTester(event, section = getSectionForChild(this)) {
const GET_FAVICON_URL = 'https://www.google.com/s2/favicons?domain='; const GET_FAVICON_URL = 'https://www.google.com/s2/favicons?domain=';
const OWN_ICON = chrome.runtime.getManifest().icons['16']; const OWN_ICON = chrome.runtime.getManifest().icons['16'];
const cachedRegexps = showRegExpTester.cachedRegexps = const cachedRegexps = showRegExpTester.cachedRegexps =
showRegExpTester.cachedRegexps || new Map(); showRegExpTester.cachedRegexps || new Map();
const regexps = [...section.querySelector('.applies-to-list').children] const regexps = [...$('.applies-to-list', section).children]
.map(item => .map(item =>
!item.matches('.applies-to-everything') && !item.matches('.applies-to-everything') &&
item.querySelector('.applies-type').value === 'regexp' && $('.applies-type', item).value === 'regexp' &&
item.querySelector('.applies-value').value.trim()) $('.applies-value', item).value.trim()
)
.filter(item => item) .filter(item => item)
.map(text => { .map(text => {
const rxData = Object.assign({text}, cachedRegexps.get(text)); const rxData = Object.assign({text}, cachedRegexps.get(text));
@ -1895,7 +1795,7 @@ function showRegExpTester(event, section = getSectionForChild(this)) {
return rxData; return rxData;
}); });
chrome.tabs.onUpdated.addListener(function _(tabId, info) { chrome.tabs.onUpdated.addListener(function _(tabId, info) {
if (document.querySelector('.regexp-report')) { if ($('.regexp-report')) {
if (info.url) { if (info.url) {
showRegExpTester(event, section); showRegExpTester(event, section);
} }
@ -2003,7 +1903,7 @@ function showRegExpTester(event, section = getSectionForChild(this)) {
} }
showHelp(t('styleRegexpTestTitle'), report); showHelp(t('styleRegexpTestTitle'), report);
document.querySelector('.regexp-report').onclick = event => { $('.regexp-report').onclick = event => {
const target = event.target.closest('a, .regexp-report div'); const target = event.target.closest('a, .regexp-report div');
if (target) { if (target) {
openURL({url: target.href || target.textContent}); openURL({url: target.href || target.textContent});
@ -2022,7 +1922,8 @@ function showHelp(title, body) {
if (getComputedStyle(div).display === 'none') { if (getComputedStyle(div).display === 'none') {
document.addEventListener('keydown', closeHelp); document.addEventListener('keydown', closeHelp);
div.querySelector('.dismiss').onclick = closeHelp; // avoid chaining on multiple showHelp() calls // avoid chaining on multiple showHelp() calls
$('.dismiss', div).onclick = closeHelp;
} }
div.style.display = 'block'; div.style.display = 'block';
@ -2035,7 +1936,9 @@ function showHelp(title, body) {
((e.keyCode || e.which) === 27 && !e.altKey && !e.ctrlKey && !e.shiftKey && !e.metaKey) ((e.keyCode || e.which) === 27 && !e.altKey && !e.ctrlKey && !e.shiftKey && !e.metaKey)
) { ) {
div.style.display = ''; div.style.display = '';
document.querySelector('.contents').textContent = ''; const contents = $('.contents');
contents.textContent = '';
clearTimeout(contents.timer);
document.removeEventListener('keydown', closeHelp); document.removeEventListener('keydown', closeHelp);
} }
} }
@ -2045,14 +1948,14 @@ function showCodeMirrorPopup(title, html, options) {
const popup = showHelp(title, html); const popup = showHelp(title, html);
popup.classList.add('big'); popup.classList.add('big');
popup.codebox = CodeMirror(popup.querySelector('.contents'), Object.assign({ popup.codebox = CodeMirror($('.contents', popup), Object.assign({
mode: 'css', mode: 'css',
lineNumbers: true, lineNumbers: true,
lineWrapping: true, lineWrapping: true,
foldGutter: true, foldGutter: true,
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter', 'CodeMirror-lint-markers'], gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter', 'CodeMirror-lint-markers'],
matchBrackets: true, matchBrackets: true,
lint: {getAnnotations: CodeMirror.lint.css, delay: 0}, lint: getLinterConfigForCodeMirror(prefs.get('editor.linter')),
styleActiveLine: true, styleActiveLine: true,
theme: prefs.get('editor.theme'), theme: prefs.get('editor.theme'),
keyMap: prefs.get('editor.keyMap') keyMap: prefs.get('editor.keyMap')

432
edit/lint.js Normal file
View File

@ -0,0 +1,432 @@
/* global CodeMirror messageBox */
/* global editors makeSectionVisible showCodeMirrorPopup showHelp */
/* global stylelintDefaultConfig csslintDefaultConfig onDOMscripted injectCSS require */
'use strict';
function initLint() {
$('#lint-help').addEventListener('click', showLintHelp);
$('#lint').addEventListener('click', gotoLintIssue);
window.addEventListener('resize', resizeLintReport);
$('#linter-settings').addEventListener('click', openStylelintSettings);
// touch devices don't have onHover events so the element we'll be toggled via clicking (touching)
if ('ontouchstart' in document.body) {
$('#lint h2').addEventListener('click', toggleLintReport);
}
// initialize storage of linter config
BG.chromeSync.getValue('editorStylelintConfig').then(config => setStylelintConfig(config));
BG.chromeSync.getValue('editorCSSLintConfig').then(config => setCSSLintConfig(config));
}
function setStylelintConfig(config) {
// can't use default parameters, because config may be null
if (Object.keys(config || []).length === 0 && typeof stylelintDefaultConfig !== 'undefined') {
config = deepCopy(stylelintDefaultConfig.rules);
}
BG.chromeSync.setValue('editorStylelintConfig', config);
return config;
}
function setCSSLintConfig(config) {
if (Object.keys(config || []).length === 0 && typeof csslintDefaultConfig !== 'undefined') {
config = Object.assign({}, csslintDefaultConfig);
}
BG.chromeSync.setValue('editorCSSLintConfig', config);
return config;
}
function getLinterConfigForCodeMirror(name) {
return CodeMirror.lint && CodeMirror.lint[name] ? {
getAnnotations: CodeMirror.lint[name],
delay: prefs.get('editor.lintDelay')
} : false;
}
function updateLinter(linter) {
function updateEditors() {
const options = getLinterConfigForCodeMirror(linter);
CodeMirror.defaults.lint = options === 'null' ? false : options;
editors.forEach(cm => {
// set lint to "null" to disable
cm.setOption('lint', options);
// enabling/disabling linting changes the gutter width
cm.refresh();
updateLintReport(cm, 200);
});
}
// load scripts
loadSelectedLinter(linter).then(() => {
updateEditors();
});
$('#linter-settings').style.display = linter === 'null' ? 'none' : 'inline-block';
}
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 scopedState = cm.state.lint || {};
const oldMarkers = scopedState.markedLast || {};
const newMarkers = {};
const html = !scopedState.marked || scopedState.marked.length === 0 ? '' : '<tbody>' +
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; extract it out for the stylelint info popup
const lintRuleName = info.message
.substring(info.message.lastIndexOf('('), info.message.length)
.replace(/[()]/g, '');
const title = escapeHtml(info.message);
const message = title.length > 100 ? title.substr(0, 100) + '...' : title;
if (isActiveLine || oldMarkers[pos] === message) {
delete oldMarkers[pos];
}
newMarkers[pos] = message;
return `<tr class="${info.severity}">
<td role="severity" data-rule="${lintRuleName}">
<div class="CodeMirror-lint-marker-${info.severity}">${info.severity}</div>
</td>
<td role="line">${info.from.line + 1}</td>
<td role="sep">:</td>
<td role="col">${info.from.ch + 1}</td>
<td role="message" title="${title}">${message}</td>
</tr>`;
}).join('') + '</tbody>';
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 = {'&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;', '/': '&#x2F;'};
return html.replace(/[&<>"'/]/g, char => chars[char]);
}
}
function renderLintReport(someBlockChanged) {
const container = $('#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 = '<caption>' + label + ' ' + (index + 1) + '</caption>' + 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) {
$('#issue-count').textContent = issueCount;
container.replaceChild(newContent, content);
container.style.display = newContent.children.length ? 'block' : 'none';
resizeLintReport();
}
}
function resizeLintReport() {
// subtracted value to prevent scrollbar
const magicBuffer = 20;
const content = $('#lint table');
if (content) {
const bounds = content.getBoundingClientRect();
const newMaxHeight = bounds.bottom <= window.innerHeight ? '' :
// subtract out a bit of padding or the vertical scrollbar extends beyond the viewport
(window.innerHeight - bounds.top - magicBuffer) + 'px';
if (newMaxHeight !== content.style.maxHeight) {
content.parentNode.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($('td[role="line"]', issue).textContent) - 1,
ch: parseInt($('td[role="col"]', issue).textContent) - 1
});
}
function toggleLintReport() {
$('#lint').classList.toggle('collapsed');
}
function showLintHelp() {
const makeLink = (url, txt) => `<a target="_blank" href="${url}">${txt}</a>`;
const linter = prefs.get('editor.linter');
const url = linter === 'stylelint'
? 'https://stylelint.io/user-guide/rules/'
// some CSSLint rules do not have a url
: 'https://github.com/CSSLint/csslint/issues/535';
const rules = [];
let template;
let list = '<ul class="rules">';
let header = '';
if (linter === 'csslint') {
const CSSLintRules = window.CSSLint.getRules();
const findCSSLintRule = id => CSSLintRules.find(rule => rule.id === id);
header = t('linterIssuesHelp', makeLink('https://github.com/CSSLint/csslint/wiki/Rules-by-ID', 'CSSLint'));
template = ruleID => {
const rule = findCSSLintRule(ruleID);
return rule ? `<li><b>${makeLink(rule.url || url, rule.name)}</b><br>${rule.desc}</li>` : '';
};
} else {
header = t('linterIssuesHelp', makeLink(url, 'stylelint'));
template = rule => `<li>${makeLink(url + rule, rule)}</li>`;
}
// Only show rules with issues in the popup
$$('#lint td[role="severity"]').forEach(el => {
const rule = el.dataset.rule;
if (!rules.includes(rule)) {
list += template(rule);
rules.push(rule);
}
});
return showHelp(t('linterIssues'), header + list + '</ul>');
}
function showLinterErrorMessage(title, contents) {
messageBox({
title,
contents,
className: 'danger center lint-config',
buttons: [t('confirmOK')],
});
}
function showSavedMessage() {
$('#help-popup .saved-message').classList.add('show');
clearTimeout($('#help-popup .contents').timer);
$('#help-popup .contents').timer = setTimeout(() => {
// popup may be closed at this point
const msg = $('#help-popup .saved-message');
if (msg) {
msg.classList.remove('show');
}
}, 2000);
}
function checkLinter(linter = prefs.get('editor.linter')) {
linter = linter.toLowerCase();
if (prefs.get('editor.linter') !== linter) {
prefs.set('editor.linter', linter);
}
return linter;
}
function checkConfigRules(linter, config) {
const invalid = [];
const linterRules = linter === 'stylelint'
? Object.keys(window.stylelint.rules)
: window.CSSLint.getRules().map(rule => rule.id);
Object.keys(config).forEach(setting => {
if (!linterRules.includes(setting)) {
invalid.push(setting);
}
});
return invalid;
}
function stringifyConfig(config) {
return JSON.stringify(config, null, 2)
.replace(/,\n\s+\{\n\s+("severity":\s"\w+")\n\s+\}/g, ', {$1}');
}
function setupLinterSettingsEvents(popup) {
$('.save', popup).addEventListener('click', event => {
event.preventDefault();
const linter = checkLinter(event.target.dataset.linter);
const json = tryJSONparse(popup.codebox.getValue());
if (json) {
const invalid = checkConfigRules(linter, json);
if (invalid.length) {
return showLinterErrorMessage(
linter,
t('linterInvalidConfigError') + `<ul><li>${invalid.join('</li><li>')}</li></ul>`
);
}
if (linter === 'stylelint') {
setStylelintConfig(json);
} else {
setCSSLintConfig(json);
}
updateLinter(linter);
showSavedMessage();
} else {
showLinterErrorMessage(linter, t('linterJSONError'));
}
popup.codebox.focus();
});
$('.reset', popup).addEventListener('click', event => {
event.preventDefault();
const linter = checkLinter(event.target.dataset.linter);
let config;
if (linter === 'stylelint') {
setStylelintConfig();
config = stylelintDefaultConfig.rules;
} else {
setCSSLintConfig();
config = csslintDefaultConfig;
}
popup.codebox.setValue(stringifyConfig(config));
popup.codebox.focus();
});
$('.cancel', popup).addEventListener('click', event => {
event.preventDefault();
$('.dismiss').dispatchEvent(new Event('click'));
});
}
function openStylelintSettings() {
const linter = prefs.get('editor.linter');
BG.chromeSync.getValue(
linter === 'stylelint'
? 'editorStylelintConfig'
: 'editorCSSLintConfig'
).then(config => {
if (!config || config.length === 0) {
config = linter === 'stylelint'
? setStylelintConfig(config)
: setCSSLintConfig(config);
}
const configString = stringifyConfig(config);
setupLinterPopup(configString);
});
}
function setupLinterPopup(config) {
const linter = prefs.get('editor.linter');
const linterTitle = linter === 'stylelint' ? 'Stylelint' : 'CSSLint';
function makeButton(className, text, options = {}) {
return $element(Object.assign(options, {
tag: 'button',
className,
type: 'button',
textContent: t(text),
dataset: {linter}
}));
}
function makeLink(url, textContent) {
return $element({tag: 'a', target: '_blank', href: url, textContent});
}
function setJSONMode(cm) {
cm.setOption('mode', 'application/json');
cm.setOption('lint', 'json');
}
const popup = showCodeMirrorPopup(t('linterConfigPopupTitle', linterTitle), $element({
appendChild: [
$element({
tag: 'p',
appendChild: [
t('linterRulesLink') + ' ',
makeLink(
linter === 'stylelint'
? 'https://stylelint.io/user-guide/rules/'
: 'https://github.com/CSSLint/csslint/wiki/Rules-by-ID',
linterTitle
),
linter === 'csslint' ? ' ' + t('linterCSSLintSettings') : ''
]
}),
makeButton('save', 'styleSaveLabel'),
makeButton('cancel', 'confirmCancel'),
makeButton('reset', 'genericResetLabel', {title: t('linterResetMessage')}),
$element({
tag: 'span',
className: 'saved-message',
textContent: t('genericSavedMessage')
})
]
}));
const contents = $('.contents', popup);
const loadJSON = window.jsonlint ? [] : [
'vendor/codemirror/mode/javascript/javascript.js',
'vendor/codemirror/addon/lint/json-lint.js',
'vendor/jsonlint/jsonlint.js'
];
contents.insertBefore(popup.codebox.display.wrapper, contents.firstElementChild);
popup.codebox.focus();
popup.codebox.setValue(config);
popup.codebox.clearHistory();
onDOMscripted(loadJSON).then(() => setJSONMode(popup.codebox));
setupLinterSettingsEvents(popup);
}
function loadSelectedLinter(name) {
const scripts = [];
if (name !== 'null' && !$('script[src*="css-lint.js"]')) {
// inject css
injectCSS('vendor/codemirror/addon/lint/lint.css');
injectCSS('msgbox/msgbox.css');
// load CodeMirror lint code
scripts.push(
'vendor/codemirror/addon/lint/lint.js',
'vendor-overwrites/codemirror/addon/lint/css-lint.js',
'msgbox/msgbox.js'
);
}
if (name === 'csslint' && !window.CSSLint) {
scripts.push(
'edit/csslint-config.js',
'vendor-overwrites/csslint/csslint-worker.js'
);
} else if (name === 'stylelint' && !window.stylelint) {
scripts.push(
'vendor-overwrites/stylelint/stylelint-bundle.min.js',
'edit/stylelint-config.js'
);
}
return onDOMscripted(scripts);
}

170
edit/stylelint-config.js Normal file
View File

@ -0,0 +1,170 @@
'use strict';
window.stylelintDefaultConfig = (defaultSeverity => ({
// 'sugarss' is a indent-based syntax like Sass or Stylus
// ref: https://github.com/postcss/postcss#syntaxes
syntax: 'sugarss',
// ** recommended rules **
// ref: https://github.com/stylelint/stylelint-config-recommended/blob/master/index.js
rules: {
'at-rule-no-unknown': [true, defaultSeverity],
'block-no-empty': [true, defaultSeverity],
'color-no-invalid-hex': [true, defaultSeverity],
'declaration-block-no-duplicate-properties': [true, {
'ignore': ['consecutive-duplicates-with-different-values'],
'severity': 'warning'
}],
'declaration-block-no-shorthand-property-overrides': [true, defaultSeverity],
'font-family-no-duplicate-names': [true, defaultSeverity],
'function-calc-no-unspaced-operator': [true, defaultSeverity],
'function-linear-gradient-no-nonstandard-direction': [true, defaultSeverity],
'keyframe-declaration-no-important': [true, defaultSeverity],
'media-feature-name-no-unknown': [true, defaultSeverity],
/* recommended true */
'no-empty-source': false,
'no-extra-semicolons': [true, defaultSeverity],
'no-invalid-double-slash-comments': [true, defaultSeverity],
'property-no-unknown': [true, defaultSeverity],
'selector-pseudo-class-no-unknown': [true, defaultSeverity],
'selector-pseudo-element-no-unknown': [true, defaultSeverity],
'selector-type-no-unknown': [true, defaultSeverity],
'string-no-newline': [true, defaultSeverity],
'unit-no-unknown': [true, defaultSeverity],
// ** non-essential rules
'comment-no-empty': false,
'declaration-block-no-redundant-longhand-properties': false,
'shorthand-property-no-redundant-values': false,
// ** stylistic rules **
/*
'at-rule-empty-line-before': [
'always',
{
'except': [
'blockless-after-same-name-blockless',
'first-nested'
],
'ignore': [
'after-comment'
]
}
],
'at-rule-name-case': 'lower',
'at-rule-name-space-after': 'always-single-line',
'at-rule-semicolon-newline-after': 'always',
'block-closing-brace-empty-line-before': 'never',
'block-closing-brace-newline-after': 'always',
'block-closing-brace-newline-before': 'always-multi-line',
'block-closing-brace-space-before': 'always-single-line',
'block-opening-brace-newline-after': 'always-multi-line',
'block-opening-brace-space-after': 'always-single-line',
'block-opening-brace-space-before': 'always',
'color-hex-case': 'lower',
'color-hex-length': 'short',
'comment-empty-line-before': [
'always',
{
'except': [
'first-nested'
],
'ignore': [
'stylelint-commands'
]
}
],
'comment-whitespace-inside': 'always',
'custom-property-empty-line-before': [
'always',
{
'except': [
'after-custom-property',
'first-nested'
],
'ignore': [
'after-comment',
'inside-single-line-block'
]
}
],
'declaration-bang-space-after': 'never',
'declaration-bang-space-before': 'always',
'declaration-block-semicolon-newline-after': 'always-multi-line',
'declaration-block-semicolon-space-after': 'always-single-line',
'declaration-block-semicolon-space-before': 'never',
'declaration-block-single-line-max-declarations': 1,
'declaration-block-trailing-semicolon': 'always',
'declaration-colon-newline-after': 'always-multi-line',
'declaration-colon-space-after': 'always-single-line',
'declaration-colon-space-before': 'never',
'declaration-empty-line-before': [
'always',
{
'except': [
'after-declaration',
'first-nested'
],
'ignore': [
'after-comment',
'inside-single-line-block'
]
}
],
'function-comma-newline-after': 'always-multi-line',
'function-comma-space-after': 'always-single-line',
'function-comma-space-before': 'never',
'function-max-empty-lines': 0,
'function-name-case': 'lower',
'function-parentheses-newline-inside': 'always-multi-line',
'function-parentheses-space-inside': 'never-single-line',
'function-whitespace-after': 'always',
'indentation': 2,
'length-zero-no-unit': true,
'max-empty-lines': 1,
'media-feature-colon-space-after': 'always',
'media-feature-colon-space-before': 'never',
'media-feature-name-case': 'lower',
'media-feature-parentheses-space-inside': 'never',
'media-feature-range-operator-space-after': 'always',
'media-feature-range-operator-space-before': 'always',
'media-query-list-comma-newline-after': 'always-multi-line',
'media-query-list-comma-space-after': 'always-single-line',
'media-query-list-comma-space-before': 'never',
'no-eol-whitespace': true,
'no-missing-end-of-source-newline': true,
'number-leading-zero': 'always',
'number-no-trailing-zeros': true,
'property-case': 'lower',
'rule-empty-line-before': [
'always-multi-line',
{
'except': [
'first-nested'
],
'ignore': [
'after-comment'
]
}
],
'selector-attribute-brackets-space-inside': 'never',
'selector-attribute-operator-space-after': 'never',
'selector-attribute-operator-space-before': 'never',
'selector-combinator-space-after': 'always',
'selector-combinator-space-before': 'always',
'selector-descendant-combinator-no-non-space': true,
'selector-list-comma-newline-after': 'always',
'selector-list-comma-space-before': 'never',
'selector-max-empty-lines': 0,
'selector-pseudo-class-case': 'lower',
'selector-pseudo-class-parentheses-space-inside': 'never',
'selector-pseudo-element-case': 'lower',
'selector-pseudo-element-colon-notation': 'double',
'selector-type-case': 'lower',
'unit-case': 'lower',
'value-list-comma-newline-after': 'always-multi-line',
'value-list-comma-space-after': 'always-single-line',
'value-list-comma-space-before': 'never',
'value-list-max-empty-lines': 0
*/
}
}))({severity: 'warning'});

View File

@ -67,6 +67,86 @@ function onDOMready() {
} }
function onDOMscripted(scripts) {
if (scripts) {
return new Promise(resolve => {
addResolver(resolve);
onDOMscripted.scriptQueue = scripts;
loadNextScript();
});
}
if (onDOMscripted.scriptQueue) {
return new Promise(resolve => addResolver(resolve));
}
if (document.readyState !== 'loading') {
if (onDOMscripted.resolveOnReady) {
onDOMscripted.resolveOnReady.forEach(r => r());
onDOMscripted.resolveOnReady = null;
}
return Promise.resolve();
}
return onDOMready().then(onDOMscripted);
function loadNextScript() {
const next = onDOMscripted.scriptQueue.shift();
if (!next) {
onDOMscripted.scriptQueue = null;
onDOMscripted();
} else if (typeof next === 'function') {
Promise.resolve(next())
.then(loadNextScript);
} else {
Promise.all(
(next instanceof Array ? next : [next]).map(next =>
typeof next === 'function'
? next()
: injectScript({src: next, async: true})
)
).then(loadNextScript);
}
}
function addResolver(r) {
if (!onDOMscripted.resolveOnReady) {
onDOMscripted.resolveOnReady = [];
}
onDOMscripted.resolveOnReady.push(r);
}
}
function injectScript(properties) {
if (typeof properties === 'string') {
properties = {src: properties};
}
if (!properties || !properties.src) {
return;
}
const script = document.head.appendChild(document.createElement('script'));
Object.assign(script, properties);
if (!properties.onload) {
return new Promise(resolve => {
script.onload = () => {
script.onload = null;
resolve();
};
});
}
}
function injectCSS(url) {
if (!url) {
return;
}
document.head.appendChild($element({
tag: 'link',
rel: 'stylesheet',
href: url
}));
}
function scrollElementIntoView(element) { function scrollElementIntoView(element) {
// align to the top/bottom of the visible area if wasn't visible // align to the top/bottom of the visible area if wasn't visible
const bounds = element.getBoundingClientRect(); const bounds = element.getBoundingClientRect();

View File

@ -42,6 +42,7 @@ var prefs = new function Prefs() {
indent_conditional: true, indent_conditional: true,
}, },
'editor.lintDelay': 500, // lint gutter marker update delay, ms 'editor.lintDelay': 500, // lint gutter marker update delay, ms
'editor.linter': 'csslint', // Choose csslint or stylelint
'editor.lintReportDelay': 4500, // lint report update delay, ms 'editor.lintReportDelay': 4500, // lint report update delay, ms
'editor.matchHighlight': 'token', // token = token/word under cursor even if nothing is selected 'editor.matchHighlight': 'token', // token = token/word under cursor even if nothing is selected
// selection = only when something is selected // selection = only when something is selected
@ -321,11 +322,11 @@ var prefs = new function Prefs() {
// and establishes a two-way connection between the document elements and the actual prefs // and establishes a two-way connection between the document elements and the actual prefs
function setupLivePrefs( function setupLivePrefs(
IDs = Object.getOwnPropertyNames(prefs.readOnlyValues) IDs = Object.getOwnPropertyNames(prefs.readOnlyValues)
.filter(id => document.getElementById(id)) .filter(id => $('#' + id))
) { ) {
const checkedProps = {}; const checkedProps = {};
for (const id of IDs) { for (const id of IDs) {
const element = document.getElementById(id); const element = $('#' + id);
checkedProps[id] = element.type === 'checkbox' ? 'checked' : 'value'; checkedProps[id] = element.type === 'checkbox' ? 'checked' : 'value';
updateElement({id, element, force: true}); updateElement({id, element, force: true});
element.addEventListener('change', onChange); element.addEventListener('change', onChange);
@ -341,7 +342,7 @@ function setupLivePrefs(
function updateElement({ function updateElement({
id, id,
value = prefs.get(id), value = prefs.get(id),
element = document.getElementById(id), element = $('#' + id),
force, force,
}) { }) {
const prop = checkedProps[id]; const prop = checkedProps[id];

View File

@ -21,6 +21,7 @@
"background": { "background": {
"scripts": [ "scripts": [
"js/messaging.js", "js/messaging.js",
"vendor-overwrites/lz-string/LZString-2xspeedup.js",
"background/storage.js", "background/storage.js",
"js/prefs.js", "js/prefs.js",
"background/background.js", "background/background.js",

View File

@ -143,7 +143,8 @@ function initPopup(url) {
title: `url-prefix("${url}")`, title: `url-prefix("${url}")`,
textContent: prefs.get('popup.breadcrumbs.usePath') textContent: prefs.get('popup.breadcrumbs.usePath')
? new URL(url).pathname.slice(1) ? new URL(url).pathname.slice(1)
: t('writeStyleForURL').replace(/ /g, '\u00a0'), // this&nbsp;URL // this&nbsp;URL
: t('writeStyleForURL').replace(/ /g, '\u00a0'),
onclick: handleEvent.openLink, onclick: handleEvent.openLink,
}); });
if (prefs.get('popup.breadcrumbs')) { if (prefs.get('popup.breadcrumbs')) {

View File

@ -3,62 +3,112 @@
// Depends on csslint.js from https://github.com/stubbornella/csslint // Depends on csslint.js from https://github.com/stubbornella/csslint
// declare global: CSSLint /* global CodeMirror require define */
/* global CSSLint stylelint stylelintDefaultConfig csslintDefaultConfig */
'use strict';
(function(mod) { (mod => {
if (typeof exports == "object" && typeof module == "object") // CommonJS if (typeof exports === 'object' && typeof module === 'object') {
mod(require("../../lib/codemirror")); // CommonJS
else if (typeof define == "function" && define.amd) // AMD mod(require('../../lib/codemirror'));
define(["../../lib/codemirror"], mod); } else if (typeof define === 'function' && define.amd) {
else // Plain browser env // AMD
define(['../../lib/codemirror'], mod);
} else {
// Plain browser env
mod(CodeMirror); mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
CodeMirror.registerHelper("lint", "css", function(text) {
var found = [];
if (!window.CSSLint) return found;
/* STYLISH: hack start (part 1) */
var rules = CSSLint.getRules();
var allowedRules = ["display-property-grouping", "duplicate-properties", "empty-rules", "errors", "known-properties"];
CSSLint.clearRules();
rules.forEach(function(rule) {
if (allowedRules.indexOf(rule.id) >= 0) {
CSSLint.addRule(rule);
} }
}); })(CodeMirror => {
/* STYLISH: hack end */ CodeMirror.registerHelper('lint', 'csslint', text => {
const found = [];
if (!window.CSSLint) {
return found;
}
/* STYLUS: hack start (part 1) */
return BG.chromeSync.getValue('editorCSSLintConfig').then(config => {
// csslintDefaultConfig stored in csslint-config.js & loaded by edit/lint.js
if (Object.keys(config || []).length === 0) {
config = Object.assign({}, csslintDefaultConfig);
}
const results = CSSLint.verify(text, config);
const messages = results.messages;
const hslRegex = /hsla?\(\s*(-?\d+)%?\s*,\s*(-?\d+)%\s*,\s*(-?\d+|-?\d*.\d+)%(\s*,\s*(-?\d+|-?\d*.\d+))?\s*\)/;
let message = null;
/* STYLUS: hack end */
var results = CSSLint.verify(text), messages = results.messages, message = null; for (let i = 0; i < messages.length; i++) {
for ( var i = 0; i < messages.length; i++) {
message = messages[i]; message = messages[i];
/* STYLISH: hack start (part 2) */ /* STYLUS: hack start (part 2) */
if (message.type === 'warning') { if (message.type === 'warning') {
// @font-face {font-family: 'Ampersand'; unicode-range: U+26;} // @font-face {font-family: 'Ampersand'; unicode-range: U+26;}
if (message.message.indexOf('unicode-range') !== -1) { if (message.message.indexOf('unicode-range') !== -1) {
continue; continue;
} } else if (
else if ( // color: hsl(210, 100%, 2.2%); or color: hsla(210, 100%, 2.2%, 0.3); // color: hsl(210, 100%, 2.2%); or color: hsla(210, 100%, 2.2%, 0.3);
message.message.startsWith('Expected (<color>) but found \'hsl') && message.message.startsWith('Expected (<color>) but found \'hsl') &&
/hsla?\(\s*(-?\d+)%?\s*,\s*(-?\d+)%\s*,\s*(-?\d+|-?\d*.\d+)%(\s*,\s*(-?\d+|-?\d*.\d+))?\s*\)/.test(message.message) hslRegex.test(message.message)
) { ) {
continue; continue;
} }
//
} }
/* STYLISH: hack end */ const startLine = message.line - 1;
const endLine = message.line - 1;
const startCol = message.col - 1;
const endCol = message.col;
/* STYLUS: hack end */
var startLine = message.line -1, endLine = message.line -1, startCol = message.col -1, endCol = message.col;
found.push({ found.push({
from: CodeMirror.Pos(startLine, startCol), from: CodeMirror.Pos(startLine, startCol),
to: CodeMirror.Pos(endLine, endCol), to: CodeMirror.Pos(endLine, endCol),
message: message.message, message: message.message + ` (${message.rule.id})`,
severity : message.type severity : message.type
}); });
} }
return found; return found;
}); });
});
CodeMirror.registerHelper('lint', 'stylelint', text => {
const found = [];
window.stylelint = require('stylelint');
if (window.stylelint) {
return BG.chromeSync.getValue('editorStylelintConfig').then(rules => {
// stylelintDefaultConfig stored in stylelint-config.js & loaded by edit/lint.js
if (Object.keys(rules || []).length === 0) {
rules = stylelintDefaultConfig.rules;
}
return stylelint.lint({
code: text,
config: {
syntax: stylelintDefaultConfig.syntax,
rules: rules
}
}).then(output => {
const warnings = output.results.length ? output.results[0].warnings : [];
const len = warnings.length;
let warning;
let message;
if (len) {
for (let i = 0; i < len; i++) {
warning = warnings[i];
message = warning.text
.replace('Unexpected ', '')
.replace(/^./, function (firstLetter) {
return firstLetter.toUpperCase();
});
found.push({
from: CodeMirror.Pos(warning.line - 1, warning.column - 1),
to: CodeMirror.Pos(warning.line - 1, warning.column),
message,
severity : warning.severity
});
}
}
return found;
});
});
}
return found;
});
}); });

View File

@ -9,6 +9,9 @@
2. Apply our hacks unless supported natively 2. Apply our hacks unless supported natively
(use git history for the file as this warning may be obsolete): (use git history for the file as this warning may be obsolete):
* 449a27cc Add CSSLint position sticky rule
* d49e44dd CSS variables
* 2e86c958 fire startdocument on {
* bc63ecca support "i" in attribute selector * bc63ecca support "i" in attribute selector
* 2468784e fix crashing on unclosed calc() at eof * 2468784e fix crashing on unclosed calc() at eof
* 3287b79f Support :any(), :-webkit-any(), :-moz-any() * 3287b79f Support :any(), :-webkit-any(), :-moz-any()

View File

@ -3714,7 +3714,7 @@ var Properties = module.exports = {
"pitch-range" : 1, "pitch-range" : 1,
"play-during" : 1, "play-during" : 1,
"pointer-events" : "auto | none | visiblePainted | visibleFill | visibleStroke | visible | painted | fill | stroke | all", "pointer-events" : "auto | none | visiblePainted | visibleFill | visibleStroke | visible | painted | fill | stroke | all",
"position" : "static | relative | absolute | fixed", "position" : "static | relative | absolute | fixed | sticky",
"presentation-level" : 1, "presentation-level" : 1,
"punctuation-trim" : 1, "punctuation-trim" : 1,

View File

@ -0,0 +1,512 @@
// ==UserScript==
// @name LZString-2xspeedup
// @description 2x speedup via ES6 Map and Set
// @version 1.4.4
// ==/UserScript==
// Copyright (c) 2013 Pieroxy <pieroxy@pieroxy.net>
// This work is free. You can redistribute it and/or modify it
// under the terms of the WTFPL, Version 2
// For more information see LICENSE.txt or http://www.wtfpl.net/
//
// For more information, the home page:
// http://pieroxy.net/blog/pages/lz-string/testing.html
//
// LZ-based compression algorithm, version 1.4.4
var LZString = (function() {
// private property
var f = String.fromCharCode;
var keyStrBase64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var keyStrUriSafe = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$";
var baseReverseDic = {};
function getBaseValue(alphabet, character) {
if (!baseReverseDic[alphabet]) {
baseReverseDic[alphabet] = {};
for (var i=0 ; i<alphabet.length ; i++) {
baseReverseDic[alphabet][alphabet.charAt(i)] = i;
}
}
return baseReverseDic[alphabet][character];
}
var LZString = {
compressToBase64 : function (input) {
if (input == null) return "";
var res = LZString._compress(input, 6, function(a){return keyStrBase64.charAt(a);});
switch (res.length % 4) { // To produce valid Base64
default: // When could this happen ?
case 0 : return res;
case 1 : return res+"===";
case 2 : return res+"==";
case 3 : return res+"=";
}
},
decompressFromBase64 : function (input) {
if (input == null) return "";
if (input == "") return null;
return LZString._decompress(input.length, 32, function(index) { return getBaseValue(keyStrBase64, input.charAt(index)); });
},
compressToUTF16 : function (input) {
if (input == null) return "";
return LZString._compress(input, 15, function(a){return f(a+32);}) + " ";
},
decompressFromUTF16: function (compressed) {
if (compressed == null) return "";
if (compressed == "") return null;
return LZString._decompress(compressed.length, 16384, function(index) { return compressed.charCodeAt(index) - 32; });
},
//compress into uint8array (UCS-2 big endian format)
compressToUint8Array: function (uncompressed) {
var compressed = LZString.compress(uncompressed);
var buf=new Uint8Array(compressed.length*2); // 2 bytes per character
for (var i=0, TotalLen=compressed.length; i<TotalLen; i++) {
var current_value = compressed.charCodeAt(i);
buf[i*2] = current_value >>> 8;
buf[i*2+1] = current_value % 256;
}
return buf;
},
//decompress from uint8array (UCS-2 big endian format)
decompressFromUint8Array:function (compressed) {
if (compressed===null || compressed===undefined){
return LZString.decompress(compressed);
} else {
var buf=new Array(compressed.length/2); // 2 bytes per character
for (var i=0, TotalLen=buf.length; i<TotalLen; i++) {
buf[i]=compressed[i*2]*256+compressed[i*2+1];
}
var result = [];
buf.forEach(function (c) {
result.push(f(c));
});
return LZString.decompress(result.join(''));
}
},
//compress into a string that is already URI encoded
compressToEncodedURIComponent: function (input) {
if (input == null) return "";
return LZString._compress(input, 6, function(a){return keyStrUriSafe.charAt(a);});
},
//decompress from an output of compressToEncodedURIComponent
decompressFromEncodedURIComponent:function (input) {
if (input == null) return "";
if (input == "") return null;
input = input.replace(/ /g, "+");
return LZString._decompress(input.length, 32, function(index) { return getBaseValue(keyStrUriSafe, input.charAt(index)); });
},
compress: function (uncompressed) {
return LZString._compress(uncompressed, 16, function(a){return f(a);});
},
_compress: function (uncompressed, bitsPerChar, getCharFromInt) {
if (uncompressed == null) return "";
var i, value,
context_dictionary= new Map(),
context_dictionaryToCreate= new Set(),
context_c="",
context_wc="",
context_w="",
context_enlargeIn= 2, // Compensate for the first entry which should not count
context_dictSize= 3,
context_numBits= 2,
context_data=[],
context_data_val=0,
context_data_position=0,
ii;
for (ii = 0; ii < uncompressed.length; ii += 1) {
context_c = uncompressed.charAt(ii);
if (!context_dictionary.has(context_c)) {
context_dictionary.set(context_c, context_dictSize++);
context_dictionaryToCreate.add(context_c);
}
context_wc = context_w + context_c;
if (context_dictionary.has(context_wc)) {
context_w = context_wc;
} else {
if (context_dictionaryToCreate.has(context_w)) {
if (context_w.charCodeAt(0)<256) {
for (i=0 ; i<context_numBits ; i++) {
context_data_val = (context_data_val << 1);
if (context_data_position == bitsPerChar-1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
}
value = context_w.charCodeAt(0);
for (i=0 ; i<8 ; i++) {
context_data_val = (context_data_val << 1) | (value&1);
if (context_data_position == bitsPerChar-1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
value = value >> 1;
}
} else {
value = 1;
for (i=0 ; i<context_numBits ; i++) {
context_data_val = (context_data_val << 1) | value;
if (context_data_position ==bitsPerChar-1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
value = 0;
}
value = context_w.charCodeAt(0);
for (i=0 ; i<16 ; i++) {
context_data_val = (context_data_val << 1) | (value&1);
if (context_data_position == bitsPerChar-1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
value = value >> 1;
}
}
context_enlargeIn--;
if (context_enlargeIn == 0) {
context_enlargeIn = Math.pow(2, context_numBits);
context_numBits++;
}
context_dictionaryToCreate.delete(context_w);
} else {
value = context_dictionary.get(context_w);
for (i=0 ; i<context_numBits ; i++) {
context_data_val = (context_data_val << 1) | (value&1);
if (context_data_position == bitsPerChar-1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
value = value >> 1;
}
}
context_enlargeIn--;
if (context_enlargeIn == 0) {
context_enlargeIn = Math.pow(2, context_numBits);
context_numBits++;
}
// Add wc to the dictionary.
context_dictionary.set(context_wc, context_dictSize++);
context_w = String(context_c);
}
}
// Output the code for w.
if (context_w !== "") {
if (context_dictionaryToCreate.has(context_w)) {
if (context_w.charCodeAt(0)<256) {
for (i=0 ; i<context_numBits ; i++) {
context_data_val = (context_data_val << 1);
if (context_data_position == bitsPerChar-1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
}
value = context_w.charCodeAt(0);
for (i=0 ; i<8 ; i++) {
context_data_val = (context_data_val << 1) | (value&1);
if (context_data_position == bitsPerChar-1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
value = value >> 1;
}
} else {
value = 1;
for (i=0 ; i<context_numBits ; i++) {
context_data_val = (context_data_val << 1) | value;
if (context_data_position == bitsPerChar-1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
value = 0;
}
value = context_w.charCodeAt(0);
for (i=0 ; i<16 ; i++) {
context_data_val = (context_data_val << 1) | (value&1);
if (context_data_position == bitsPerChar-1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
value = value >> 1;
}
}
context_enlargeIn--;
if (context_enlargeIn == 0) {
context_enlargeIn = Math.pow(2, context_numBits);
context_numBits++;
}
context_dictionaryToCreate.delete(context_w);
} else {
value = context_dictionary.get(context_w);
for (i=0 ; i<context_numBits ; i++) {
context_data_val = (context_data_val << 1) | (value&1);
if (context_data_position == bitsPerChar-1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
value = value >> 1;
}
}
context_enlargeIn--;
if (context_enlargeIn == 0) {
context_enlargeIn = Math.pow(2, context_numBits);
context_numBits++;
}
}
// Mark the end of the stream
value = 2;
for (i=0 ; i<context_numBits ; i++) {
context_data_val = (context_data_val << 1) | (value&1);
if (context_data_position == bitsPerChar-1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
value = value >> 1;
}
// Flush the last char
while (true) {
context_data_val = (context_data_val << 1);
if (context_data_position == bitsPerChar-1) {
context_data.push(getCharFromInt(context_data_val));
break;
}
else context_data_position++;
}
return context_data.join('');
},
decompress: function (compressed) {
if (compressed == null) return "";
if (compressed == "") return null;
return LZString._decompress(compressed.length, 32768, function(index) { return compressed.charCodeAt(index); });
},
_decompress: function (length, resetValue, getNextValue) {
var dictionary = [],
next,
enlargeIn = 4,
dictSize = 4,
numBits = 3,
entry = "",
result = [],
i,
w,
bits, resb, maxpower, power,
c,
data = {val:getNextValue(0), position:resetValue, index:1};
for (i = 0; i < 3; i += 1) {
dictionary[i] = i;
}
bits = 0;
maxpower = Math.pow(2,2);
power=1;
while (power!=maxpower) {
resb = data.val & data.position;
data.position >>= 1;
if (data.position == 0) {
data.position = resetValue;
data.val = getNextValue(data.index++);
}
bits |= (resb>0 ? 1 : 0) * power;
power <<= 1;
}
switch (next = bits) {
case 0:
bits = 0;
maxpower = Math.pow(2,8);
power=1;
while (power!=maxpower) {
resb = data.val & data.position;
data.position >>= 1;
if (data.position == 0) {
data.position = resetValue;
data.val = getNextValue(data.index++);
}
bits |= (resb>0 ? 1 : 0) * power;
power <<= 1;
}
c = f(bits);
break;
case 1:
bits = 0;
maxpower = Math.pow(2,16);
power=1;
while (power!=maxpower) {
resb = data.val & data.position;
data.position >>= 1;
if (data.position == 0) {
data.position = resetValue;
data.val = getNextValue(data.index++);
}
bits |= (resb>0 ? 1 : 0) * power;
power <<= 1;
}
c = f(bits);
break;
case 2:
return "";
}
dictionary[3] = c;
w = c;
result.push(c);
while (true) {
if (data.index > length) {
return "";
}
bits = 0;
maxpower = Math.pow(2,numBits);
power=1;
while (power!=maxpower) {
resb = data.val & data.position;
data.position >>= 1;
if (data.position == 0) {
data.position = resetValue;
data.val = getNextValue(data.index++);
}
bits |= (resb>0 ? 1 : 0) * power;
power <<= 1;
}
switch (c = bits) {
case 0:
bits = 0;
maxpower = Math.pow(2,8);
power=1;
while (power!=maxpower) {
resb = data.val & data.position;
data.position >>= 1;
if (data.position == 0) {
data.position = resetValue;
data.val = getNextValue(data.index++);
}
bits |= (resb>0 ? 1 : 0) * power;
power <<= 1;
}
dictionary[dictSize++] = f(bits);
c = dictSize-1;
enlargeIn--;
break;
case 1:
bits = 0;
maxpower = Math.pow(2,16);
power=1;
while (power!=maxpower) {
resb = data.val & data.position;
data.position >>= 1;
if (data.position == 0) {
data.position = resetValue;
data.val = getNextValue(data.index++);
}
bits |= (resb>0 ? 1 : 0) * power;
power <<= 1;
}
dictionary[dictSize++] = f(bits);
c = dictSize-1;
enlargeIn--;
break;
case 2:
return result.join('');
}
if (enlargeIn == 0) {
enlargeIn = Math.pow(2, numBits);
numBits++;
}
if (dictionary[c]) {
entry = dictionary[c];
} else {
if (c === dictSize) {
entry = w + w.charAt(0);
} else {
return null;
}
}
result.push(entry);
// Add w+entry[0] to the dictionary.
dictionary[dictSize++] = w + entry.charAt(0);
enlargeIn--;
w = entry;
if (enlargeIn == 0) {
enlargeIn = Math.pow(2, numBits);
numBits++;
}
}
}
};
return LZString;
})();
if (typeof define === 'function' && define.amd) {
define(function () { return LZString; });
} else if( typeof module !== 'undefined' && module != null ) {
module.exports = LZString
} else if( typeof angular !== 'undefined' && angular != null ) {
angular.module('LZString', [])
.factory('LZString', function () {
return LZString;
});
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
Stylelint bundle file from https://github.com/Mottie/stylelint/tree/mod - see the readme for details.

View File

@ -0,0 +1,37 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE
// Depends on jsonlint.js from https://github.com/zaach/jsonlint
// declare global: jsonlint
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
CodeMirror.registerHelper("lint", "json", function(text) {
var found = [];
if (!window.jsonlint) {
if (window.console) {
window.console.error("Error: window.jsonlint not defined, CodeMirror JSON linting cannot run.");
}
return found;
}
jsonlint.parseError = function(str, hash) {
var loc = hash.loc;
found.push({from: CodeMirror.Pos(loc.first_line - 1, loc.first_column),
to: CodeMirror.Pos(loc.last_line - 1, loc.last_column),
message: str});
};
try { jsonlint.parse(text); }
catch(e) {}
return found;
});
});

View File

@ -0,0 +1,818 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
function expressionAllowed(stream, state, backUp) {
return /^(?:operator|sof|keyword c|case|new|export|default|[\[{}\(,;:]|=>)$/.test(state.lastType) ||
(state.lastType == "quasi" && /\{\s*$/.test(stream.string.slice(0, stream.pos - (backUp || 0))))
}
CodeMirror.defineMode("javascript", function(config, parserConfig) {
var indentUnit = config.indentUnit;
var statementIndent = parserConfig.statementIndent;
var jsonldMode = parserConfig.jsonld;
var jsonMode = parserConfig.json || jsonldMode;
var isTS = parserConfig.typescript;
var wordRE = parserConfig.wordCharacters || /[\w$\xa1-\uffff]/;
// Tokenizer
var keywords = function(){
function kw(type) {return {type: type, style: "keyword"};}
var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c");
var operator = kw("operator"), atom = {type: "atom", style: "atom"};
var jsKeywords = {
"if": kw("if"), "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B,
"return": C, "break": C, "continue": C, "new": kw("new"), "delete": C, "throw": C, "debugger": C,
"var": kw("var"), "const": kw("var"), "let": kw("var"),
"function": kw("function"), "catch": kw("catch"),
"for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"),
"in": operator, "typeof": operator, "instanceof": operator,
"true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom,
"this": kw("this"), "class": kw("class"), "super": kw("atom"),
"yield": C, "export": kw("export"), "import": kw("import"), "extends": C,
"await": C
};
// Extend the 'normal' keywords with the TypeScript language extensions
if (isTS) {
var type = {type: "variable", style: "type"};
var tsKeywords = {
// object-like things
"interface": kw("class"),
"implements": C,
"namespace": C,
"module": kw("module"),
"enum": kw("module"),
// scope modifiers
"public": kw("modifier"),
"private": kw("modifier"),
"protected": kw("modifier"),
"abstract": kw("modifier"),
// types
"string": type, "number": type, "boolean": type, "any": type
};
for (var attr in tsKeywords) {
jsKeywords[attr] = tsKeywords[attr];
}
}
return jsKeywords;
}();
var isOperatorChar = /[+\-*&%=<>!?|~^@]/;
var isJsonldKeyword = /^@(context|id|value|language|type|container|list|set|reverse|index|base|vocab|graph)"/;
function readRegexp(stream) {
var escaped = false, next, inSet = false;
while ((next = stream.next()) != null) {
if (!escaped) {
if (next == "/" && !inSet) return;
if (next == "[") inSet = true;
else if (inSet && next == "]") inSet = false;
}
escaped = !escaped && next == "\\";
}
}
// Used as scratch variables to communicate multiple values without
// consing up tons of objects.
var type, content;
function ret(tp, style, cont) {
type = tp; content = cont;
return style;
}
function tokenBase(stream, state) {
var ch = stream.next();
if (ch == '"' || ch == "'") {
state.tokenize = tokenString(ch);
return state.tokenize(stream, state);
} else if (ch == "." && stream.match(/^\d+(?:[eE][+\-]?\d+)?/)) {
return ret("number", "number");
} else if (ch == "." && stream.match("..")) {
return ret("spread", "meta");
} else if (/[\[\]{}\(\),;\:\.]/.test(ch)) {
return ret(ch);
} else if (ch == "=" && stream.eat(">")) {
return ret("=>", "operator");
} else if (ch == "0" && stream.eat(/x/i)) {
stream.eatWhile(/[\da-f]/i);
return ret("number", "number");
} else if (ch == "0" && stream.eat(/o/i)) {
stream.eatWhile(/[0-7]/i);
return ret("number", "number");
} else if (ch == "0" && stream.eat(/b/i)) {
stream.eatWhile(/[01]/i);
return ret("number", "number");
} else if (/\d/.test(ch)) {
stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/);
return ret("number", "number");
} else if (ch == "/") {
if (stream.eat("*")) {
state.tokenize = tokenComment;
return tokenComment(stream, state);
} else if (stream.eat("/")) {
stream.skipToEnd();
return ret("comment", "comment");
} else if (expressionAllowed(stream, state, 1)) {
readRegexp(stream);
stream.match(/^\b(([gimyu])(?![gimyu]*\2))+\b/);
return ret("regexp", "string-2");
} else {
stream.eatWhile(isOperatorChar);
return ret("operator", "operator", stream.current());
}
} else if (ch == "`") {
state.tokenize = tokenQuasi;
return tokenQuasi(stream, state);
} else if (ch == "#") {
stream.skipToEnd();
return ret("error", "error");
} else if (isOperatorChar.test(ch)) {
if (ch != ">" || !state.lexical || state.lexical.type != ">")
stream.eatWhile(isOperatorChar);
return ret("operator", "operator", stream.current());
} else if (wordRE.test(ch)) {
stream.eatWhile(wordRE);
var word = stream.current()
if (state.lastType != ".") {
if (keywords.propertyIsEnumerable(word)) {
var kw = keywords[word]
return ret(kw.type, kw.style, word)
}
if (word == "async" && stream.match(/^\s*[\(\w]/, false))
return ret("async", "keyword", word)
}
return ret("variable", "variable", word)
}
}
function tokenString(quote) {
return function(stream, state) {
var escaped = false, next;
if (jsonldMode && stream.peek() == "@" && stream.match(isJsonldKeyword)){
state.tokenize = tokenBase;
return ret("jsonld-keyword", "meta");
}
while ((next = stream.next()) != null) {
if (next == quote && !escaped) break;
escaped = !escaped && next == "\\";
}
if (!escaped) state.tokenize = tokenBase;
return ret("string", "string");
};
}
function tokenComment(stream, state) {
var maybeEnd = false, ch;
while (ch = stream.next()) {
if (ch == "/" && maybeEnd) {
state.tokenize = tokenBase;
break;
}
maybeEnd = (ch == "*");
}
return ret("comment", "comment");
}
function tokenQuasi(stream, state) {
var escaped = false, next;
while ((next = stream.next()) != null) {
if (!escaped && (next == "`" || next == "$" && stream.eat("{"))) {
state.tokenize = tokenBase;
break;
}
escaped = !escaped && next == "\\";
}
return ret("quasi", "string-2", stream.current());
}
var brackets = "([{}])";
// This is a crude lookahead trick to try and notice that we're
// parsing the argument patterns for a fat-arrow function before we
// actually hit the arrow token. It only works if the arrow is on
// the same line as the arguments and there's no strange noise
// (comments) in between. Fallback is to only notice when we hit the
// arrow, and not declare the arguments as locals for the arrow
// body.
function findFatArrow(stream, state) {
if (state.fatArrowAt) state.fatArrowAt = null;
var arrow = stream.string.indexOf("=>", stream.start);
if (arrow < 0) return;
if (isTS) { // Try to skip TypeScript return type declarations after the arguments
var m = /:\s*(?:\w+(?:<[^>]*>|\[\])?|\{[^}]*\})\s*$/.exec(stream.string.slice(stream.start, arrow))
if (m) arrow = m.index
}
var depth = 0, sawSomething = false;
for (var pos = arrow - 1; pos >= 0; --pos) {
var ch = stream.string.charAt(pos);
var bracket = brackets.indexOf(ch);
if (bracket >= 0 && bracket < 3) {
if (!depth) { ++pos; break; }
if (--depth == 0) { if (ch == "(") sawSomething = true; break; }
} else if (bracket >= 3 && bracket < 6) {
++depth;
} else if (wordRE.test(ch)) {
sawSomething = true;
} else if (/["'\/]/.test(ch)) {
return;
} else if (sawSomething && !depth) {
++pos;
break;
}
}
if (sawSomething && !depth) state.fatArrowAt = pos;
}
// Parser
var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true, "this": true, "jsonld-keyword": true};
function JSLexical(indented, column, type, align, prev, info) {
this.indented = indented;
this.column = column;
this.type = type;
this.prev = prev;
this.info = info;
if (align != null) this.align = align;
}
function inScope(state, varname) {
for (var v = state.localVars; v; v = v.next)
if (v.name == varname) return true;
for (var cx = state.context; cx; cx = cx.prev) {
for (var v = cx.vars; v; v = v.next)
if (v.name == varname) return true;
}
}
function parseJS(state, style, type, content, stream) {
var cc = state.cc;
// Communicate our context to the combinators.
// (Less wasteful than consing up a hundred closures on every call.)
cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc; cx.style = style;
if (!state.lexical.hasOwnProperty("align"))
state.lexical.align = true;
while(true) {
var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement;
if (combinator(type, content)) {
while(cc.length && cc[cc.length - 1].lex)
cc.pop()();
if (cx.marked) return cx.marked;
if (type == "variable" && inScope(state, content)) return "variable-2";
return style;
}
}
}
// Combinator utils
var cx = {state: null, column: null, marked: null, cc: null};
function pass() {
for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]);
}
function cont() {
pass.apply(null, arguments);
return true;
}
function register(varname) {
function inList(list) {
for (var v = list; v; v = v.next)
if (v.name == varname) return true;
return false;
}
var state = cx.state;
cx.marked = "def";
if (state.context) {
if (inList(state.localVars)) return;
state.localVars = {name: varname, next: state.localVars};
} else {
if (inList(state.globalVars)) return;
if (parserConfig.globalVars)
state.globalVars = {name: varname, next: state.globalVars};
}
}
// Combinators
var defaultVars = {name: "this", next: {name: "arguments"}};
function pushcontext() {
cx.state.context = {prev: cx.state.context, vars: cx.state.localVars};
cx.state.localVars = defaultVars;
}
function popcontext() {
cx.state.localVars = cx.state.context.vars;
cx.state.context = cx.state.context.prev;
}
function pushlex(type, info) {
var result = function() {
var state = cx.state, indent = state.indented;
if (state.lexical.type == "stat") indent = state.lexical.indented;
else for (var outer = state.lexical; outer && outer.type == ")" && outer.align; outer = outer.prev)
indent = outer.indented;
state.lexical = new JSLexical(indent, cx.stream.column(), type, null, state.lexical, info);
};
result.lex = true;
return result;
}
function poplex() {
var state = cx.state;
if (state.lexical.prev) {
if (state.lexical.type == ")")
state.indented = state.lexical.indented;
state.lexical = state.lexical.prev;
}
}
poplex.lex = true;
function expect(wanted) {
function exp(type) {
if (type == wanted) return cont();
else if (wanted == ";") return pass();
else return cont(exp);
};
return exp;
}
function statement(type, value) {
if (type == "var") return cont(pushlex("vardef", value.length), vardef, expect(";"), poplex);
if (type == "keyword a") return cont(pushlex("form"), parenExpr, statement, poplex);
if (type == "keyword b") return cont(pushlex("form"), statement, poplex);
if (type == "{") return cont(pushlex("}"), block, poplex);
if (type == ";") return cont();
if (type == "if") {
if (cx.state.lexical.info == "else" && cx.state.cc[cx.state.cc.length - 1] == poplex)
cx.state.cc.pop()();
return cont(pushlex("form"), parenExpr, statement, poplex, maybeelse);
}
if (type == "function") return cont(functiondef);
if (type == "for") return cont(pushlex("form"), forspec, statement, poplex);
if (type == "variable") {
if (isTS && value == "type") {
cx.marked = "keyword"
return cont(typeexpr, expect("operator"), typeexpr, expect(";"));
} else {
return cont(pushlex("stat"), maybelabel);
}
}
if (type == "switch") return cont(pushlex("form"), parenExpr, expect("{"), pushlex("}", "switch"),
block, poplex, poplex);
if (type == "case") return cont(expression, expect(":"));
if (type == "default") return cont(expect(":"));
if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"),
statement, poplex, popcontext);
if (type == "class") return cont(pushlex("form"), className, poplex);
if (type == "export") return cont(pushlex("stat"), afterExport, poplex);
if (type == "import") return cont(pushlex("stat"), afterImport, poplex);
if (type == "module") return cont(pushlex("form"), pattern, expect("{"), pushlex("}"), block, poplex, poplex)
if (type == "async") return cont(statement)
if (value == "@") return cont(expression, statement)
return pass(pushlex("stat"), expression, expect(";"), poplex);
}
function expression(type) {
return expressionInner(type, false);
}
function expressionNoComma(type) {
return expressionInner(type, true);
}
function parenExpr(type) {
if (type != "(") return pass()
return cont(pushlex(")"), expression, expect(")"), poplex)
}
function expressionInner(type, noComma) {
if (cx.state.fatArrowAt == cx.stream.start) {
var body = noComma ? arrowBodyNoComma : arrowBody;
if (type == "(") return cont(pushcontext, pushlex(")"), commasep(pattern, ")"), poplex, expect("=>"), body, popcontext);
else if (type == "variable") return pass(pushcontext, pattern, expect("=>"), body, popcontext);
}
var maybeop = noComma ? maybeoperatorNoComma : maybeoperatorComma;
if (atomicTypes.hasOwnProperty(type)) return cont(maybeop);
if (type == "function") return cont(functiondef, maybeop);
if (type == "class") return cont(pushlex("form"), classExpression, poplex);
if (type == "keyword c" || type == "async") return cont(noComma ? maybeexpressionNoComma : maybeexpression);
if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeop);
if (type == "operator" || type == "spread") return cont(noComma ? expressionNoComma : expression);
if (type == "[") return cont(pushlex("]"), arrayLiteral, poplex, maybeop);
if (type == "{") return contCommasep(objprop, "}", null, maybeop);
if (type == "quasi") return pass(quasi, maybeop);
if (type == "new") return cont(maybeTarget(noComma));
return cont();
}
function maybeexpression(type) {
if (type.match(/[;\}\)\],]/)) return pass();
return pass(expression);
}
function maybeexpressionNoComma(type) {
if (type.match(/[;\}\)\],]/)) return pass();
return pass(expressionNoComma);
}
function maybeoperatorComma(type, value) {
if (type == ",") return cont(expression);
return maybeoperatorNoComma(type, value, false);
}
function maybeoperatorNoComma(type, value, noComma) {
var me = noComma == false ? maybeoperatorComma : maybeoperatorNoComma;
var expr = noComma == false ? expression : expressionNoComma;
if (type == "=>") return cont(pushcontext, noComma ? arrowBodyNoComma : arrowBody, popcontext);
if (type == "operator") {
if (/\+\+|--/.test(value)) return cont(me);
if (value == "?") return cont(expression, expect(":"), expr);
return cont(expr);
}
if (type == "quasi") { return pass(quasi, me); }
if (type == ";") return;
if (type == "(") return contCommasep(expressionNoComma, ")", "call", me);
if (type == ".") return cont(property, me);
if (type == "[") return cont(pushlex("]"), maybeexpression, expect("]"), poplex, me);
if (isTS && value == "as") { cx.marked = "keyword"; return cont(typeexpr, me) }
}
function quasi(type, value) {
if (type != "quasi") return pass();
if (value.slice(value.length - 2) != "${") return cont(quasi);
return cont(expression, continueQuasi);
}
function continueQuasi(type) {
if (type == "}") {
cx.marked = "string-2";
cx.state.tokenize = tokenQuasi;
return cont(quasi);
}
}
function arrowBody(type) {
findFatArrow(cx.stream, cx.state);
return pass(type == "{" ? statement : expression);
}
function arrowBodyNoComma(type) {
findFatArrow(cx.stream, cx.state);
return pass(type == "{" ? statement : expressionNoComma);
}
function maybeTarget(noComma) {
return function(type) {
if (type == ".") return cont(noComma ? targetNoComma : target);
else return pass(noComma ? expressionNoComma : expression);
};
}
function target(_, value) {
if (value == "target") { cx.marked = "keyword"; return cont(maybeoperatorComma); }
}
function targetNoComma(_, value) {
if (value == "target") { cx.marked = "keyword"; return cont(maybeoperatorNoComma); }
}
function maybelabel(type) {
if (type == ":") return cont(poplex, statement);
return pass(maybeoperatorComma, expect(";"), poplex);
}
function property(type) {
if (type == "variable") {cx.marked = "property"; return cont();}
}
function objprop(type, value) {
if (type == "async") {
cx.marked = "property";
return cont(objprop);
} else if (type == "variable" || cx.style == "keyword") {
cx.marked = "property";
if (value == "get" || value == "set") return cont(getterSetter);
return cont(afterprop);
} else if (type == "number" || type == "string") {
cx.marked = jsonldMode ? "property" : (cx.style + " property");
return cont(afterprop);
} else if (type == "jsonld-keyword") {
return cont(afterprop);
} else if (type == "modifier") {
return cont(objprop)
} else if (type == "[") {
return cont(expression, expect("]"), afterprop);
} else if (type == "spread") {
return cont(expression, afterprop);
} else if (type == ":") {
return pass(afterprop)
}
}
function getterSetter(type) {
if (type != "variable") return pass(afterprop);
cx.marked = "property";
return cont(functiondef);
}
function afterprop(type) {
if (type == ":") return cont(expressionNoComma);
if (type == "(") return pass(functiondef);
}
function commasep(what, end, sep) {
function proceed(type, value) {
if (sep ? sep.indexOf(type) > -1 : type == ",") {
var lex = cx.state.lexical;
if (lex.info == "call") lex.pos = (lex.pos || 0) + 1;
return cont(function(type, value) {
if (type == end || value == end) return pass()
return pass(what)
}, proceed);
}
if (type == end || value == end) return cont();
return cont(expect(end));
}
return function(type, value) {
if (type == end || value == end) return cont();
return pass(what, proceed);
};
}
function contCommasep(what, end, info) {
for (var i = 3; i < arguments.length; i++)
cx.cc.push(arguments[i]);
return cont(pushlex(end, info), commasep(what, end), poplex);
}
function block(type) {
if (type == "}") return cont();
return pass(statement, block);
}
function maybetype(type, value) {
if (isTS) {
if (type == ":") return cont(typeexpr);
if (value == "?") return cont(maybetype);
}
}
function typeexpr(type) {
if (type == "variable") {cx.marked = "type"; return cont(afterType);}
if (type == "string" || type == "number" || type == "atom") return cont(afterType);
if (type == "{") return cont(pushlex("}"), commasep(typeprop, "}", ",;"), poplex, afterType)
if (type == "(") return cont(commasep(typearg, ")"), maybeReturnType)
}
function maybeReturnType(type) {
if (type == "=>") return cont(typeexpr)
}
function typeprop(type, value) {
if (type == "variable" || cx.style == "keyword") {
cx.marked = "property"
return cont(typeprop)
} else if (value == "?") {
return cont(typeprop)
} else if (type == ":") {
return cont(typeexpr)
} else if (type == "[") {
return cont(expression, maybetype, expect("]"), typeprop)
}
}
function typearg(type) {
if (type == "variable") return cont(typearg)
else if (type == ":") return cont(typeexpr)
}
function afterType(type, value) {
if (value == "<") return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, afterType)
if (value == "|" || type == ".") return cont(typeexpr)
if (type == "[") return cont(expect("]"), afterType)
if (value == "extends") return cont(typeexpr)
}
function vardef() {
return pass(pattern, maybetype, maybeAssign, vardefCont);
}
function pattern(type, value) {
if (type == "modifier") return cont(pattern)
if (type == "variable") { register(value); return cont(); }
if (type == "spread") return cont(pattern);
if (type == "[") return contCommasep(pattern, "]");
if (type == "{") return contCommasep(proppattern, "}");
}
function proppattern(type, value) {
if (type == "variable" && !cx.stream.match(/^\s*:/, false)) {
register(value);
return cont(maybeAssign);
}
if (type == "variable") cx.marked = "property";
if (type == "spread") return cont(pattern);
if (type == "}") return pass();
return cont(expect(":"), pattern, maybeAssign);
}
function maybeAssign(_type, value) {
if (value == "=") return cont(expressionNoComma);
}
function vardefCont(type) {
if (type == ",") return cont(vardef);
}
function maybeelse(type, value) {
if (type == "keyword b" && value == "else") return cont(pushlex("form", "else"), statement, poplex);
}
function forspec(type) {
if (type == "(") return cont(pushlex(")"), forspec1, expect(")"), poplex);
}
function forspec1(type) {
if (type == "var") return cont(vardef, expect(";"), forspec2);
if (type == ";") return cont(forspec2);
if (type == "variable") return cont(formaybeinof);
return pass(expression, expect(";"), forspec2);
}
function formaybeinof(_type, value) {
if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression); }
return cont(maybeoperatorComma, forspec2);
}
function forspec2(type, value) {
if (type == ";") return cont(forspec3);
if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression); }
return pass(expression, expect(";"), forspec3);
}
function forspec3(type) {
if (type != ")") cont(expression);
}
function functiondef(type, value) {
if (value == "*") {cx.marked = "keyword"; return cont(functiondef);}
if (type == "variable") {register(value); return cont(functiondef);}
if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, maybetype, statement, popcontext);
if (isTS && value == "<") return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, functiondef)
}
function funarg(type) {
if (type == "spread") return cont(funarg);
return pass(pattern, maybetype, maybeAssign);
}
function classExpression(type, value) {
// Class expressions may have an optional name.
if (type == "variable") return className(type, value);
return classNameAfter(type, value);
}
function className(type, value) {
if (type == "variable") {register(value); return cont(classNameAfter);}
}
function classNameAfter(type, value) {
if (value == "<") return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, classNameAfter)
if (value == "extends" || value == "implements" || (isTS && type == ","))
return cont(isTS ? typeexpr : expression, classNameAfter);
if (type == "{") return cont(pushlex("}"), classBody, poplex);
}
function classBody(type, value) {
if (type == "variable" || cx.style == "keyword") {
if ((value == "async" || value == "static" || value == "get" || value == "set" ||
(isTS && (value == "public" || value == "private" || value == "protected" || value == "readonly" || value == "abstract"))) &&
cx.stream.match(/^\s+[\w$\xa1-\uffff]/, false)) {
cx.marked = "keyword";
return cont(classBody);
}
cx.marked = "property";
return cont(isTS ? classfield : functiondef, classBody);
}
if (type == "[")
return cont(expression, expect("]"), isTS ? classfield : functiondef, classBody)
if (value == "*") {
cx.marked = "keyword";
return cont(classBody);
}
if (type == ";") return cont(classBody);
if (type == "}") return cont();
if (value == "@") return cont(expression, classBody)
}
function classfield(type, value) {
if (value == "?") return cont(classfield)
if (type == ":") return cont(typeexpr, maybeAssign)
if (value == "=") return cont(expressionNoComma)
return pass(functiondef)
}
function afterExport(type, value) {
if (value == "*") { cx.marked = "keyword"; return cont(maybeFrom, expect(";")); }
if (value == "default") { cx.marked = "keyword"; return cont(expression, expect(";")); }
if (type == "{") return cont(commasep(exportField, "}"), maybeFrom, expect(";"));
return pass(statement);
}
function exportField(type, value) {
if (value == "as") { cx.marked = "keyword"; return cont(expect("variable")); }
if (type == "variable") return pass(expressionNoComma, exportField);
}
function afterImport(type) {
if (type == "string") return cont();
return pass(importSpec, maybeMoreImports, maybeFrom);
}
function importSpec(type, value) {
if (type == "{") return contCommasep(importSpec, "}");
if (type == "variable") register(value);
if (value == "*") cx.marked = "keyword";
return cont(maybeAs);
}
function maybeMoreImports(type) {
if (type == ",") return cont(importSpec, maybeMoreImports)
}
function maybeAs(_type, value) {
if (value == "as") { cx.marked = "keyword"; return cont(importSpec); }
}
function maybeFrom(_type, value) {
if (value == "from") { cx.marked = "keyword"; return cont(expression); }
}
function arrayLiteral(type) {
if (type == "]") return cont();
return pass(commasep(expressionNoComma, "]"));
}
function isContinuedStatement(state, textAfter) {
return state.lastType == "operator" || state.lastType == "," ||
isOperatorChar.test(textAfter.charAt(0)) ||
/[,.]/.test(textAfter.charAt(0));
}
// Interface
return {
startState: function(basecolumn) {
var state = {
tokenize: tokenBase,
lastType: "sof",
cc: [],
lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false),
localVars: parserConfig.localVars,
context: parserConfig.localVars && {vars: parserConfig.localVars},
indented: basecolumn || 0
};
if (parserConfig.globalVars && typeof parserConfig.globalVars == "object")
state.globalVars = parserConfig.globalVars;
return state;
},
token: function(stream, state) {
if (stream.sol()) {
if (!state.lexical.hasOwnProperty("align"))
state.lexical.align = false;
state.indented = stream.indentation();
findFatArrow(stream, state);
}
if (state.tokenize != tokenComment && stream.eatSpace()) return null;
var style = state.tokenize(stream, state);
if (type == "comment") return style;
state.lastType = type == "operator" && (content == "++" || content == "--") ? "incdec" : type;
return parseJS(state, style, type, content, stream);
},
indent: function(state, textAfter) {
if (state.tokenize == tokenComment) return CodeMirror.Pass;
if (state.tokenize != tokenBase) return 0;
var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical, top
// Kludge to prevent 'maybelse' from blocking lexical scope pops
if (!/^\s*else\b/.test(textAfter)) for (var i = state.cc.length - 1; i >= 0; --i) {
var c = state.cc[i];
if (c == poplex) lexical = lexical.prev;
else if (c != maybeelse) break;
}
while ((lexical.type == "stat" || lexical.type == "form") &&
(firstChar == "}" || ((top = state.cc[state.cc.length - 1]) &&
(top == maybeoperatorComma || top == maybeoperatorNoComma) &&
!/^[,\.=+\-*:?[\(]/.test(textAfter))))
lexical = lexical.prev;
if (statementIndent && lexical.type == ")" && lexical.prev.type == "stat")
lexical = lexical.prev;
var type = lexical.type, closing = firstChar == type;
if (type == "vardef") return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? lexical.info + 1 : 0);
else if (type == "form" && firstChar == "{") return lexical.indented;
else if (type == "form") return lexical.indented + indentUnit;
else if (type == "stat")
return lexical.indented + (isContinuedStatement(state, textAfter) ? statementIndent || indentUnit : 0);
else if (lexical.info == "switch" && !closing && parserConfig.doubleIndentSwitch != false)
return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit);
else if (lexical.align) return lexical.column + (closing ? 0 : 1);
else return lexical.indented + (closing ? 0 : indentUnit);
},
electricInput: /^\s*(?:case .*?:|default:|\{|\})$/,
blockCommentStart: jsonMode ? null : "/*",
blockCommentEnd: jsonMode ? null : "*/",
lineComment: jsonMode ? null : "//",
fold: "brace",
closeBrackets: "()[]{}''\"\"``",
helperType: jsonMode ? "json" : "javascript",
jsonldMode: jsonldMode,
jsonMode: jsonMode,
expressionAllowed: expressionAllowed,
skipExpression: function(state) {
var top = state.cc[state.cc.length - 1]
if (top == expression || top == expressionNoComma) state.cc.pop()
}
};
});
CodeMirror.registerHelper("wordChars", "javascript", /[\w$]/);
CodeMirror.defineMIME("text/javascript", "javascript");
CodeMirror.defineMIME("text/ecmascript", "javascript");
CodeMirror.defineMIME("application/javascript", "javascript");
CodeMirror.defineMIME("application/x-javascript", "javascript");
CodeMirror.defineMIME("application/ecmascript", "javascript");
CodeMirror.defineMIME("application/json", {name: "javascript", json: true});
CodeMirror.defineMIME("application/x-json", {name: "javascript", json: true});
CodeMirror.defineMIME("application/ld+json", {name: "javascript", jsonld: true});
CodeMirror.defineMIME("text/typescript", { name: "javascript", typescript: true });
CodeMirror.defineMIME("application/typescript", { name: "javascript", typescript: true });
});

432
vendor/jsonlint/jsonlint.js vendored Normal file
View File

@ -0,0 +1,432 @@
/* Jison generated parser */
var jsonlint = (function(){
var parser = {trace: function trace() { },
yy: {},
symbols_: {"error":2,"JSONString":3,"STRING":4,"JSONNumber":5,"NUMBER":6,"JSONNullLiteral":7,"NULL":8,"JSONBooleanLiteral":9,"TRUE":10,"FALSE":11,"JSONText":12,"JSONValue":13,"EOF":14,"JSONObject":15,"JSONArray":16,"{":17,"}":18,"JSONMemberList":19,"JSONMember":20,":":21,",":22,"[":23,"]":24,"JSONElementList":25,"$accept":0,"$end":1},
terminals_: {2:"error",4:"STRING",6:"NUMBER",8:"NULL",10:"TRUE",11:"FALSE",14:"EOF",17:"{",18:"}",21:":",22:",",23:"[",24:"]"},
productions_: [0,[3,1],[5,1],[7,1],[9,1],[9,1],[12,2],[13,1],[13,1],[13,1],[13,1],[13,1],[13,1],[15,2],[15,3],[20,3],[19,1],[19,3],[16,2],[16,3],[25,1],[25,3]],
performAction: function anonymous(yytext,yyleng,yylineno,yy,yystate,$$,_$) {
var $0 = $$.length - 1;
switch (yystate) {
case 1: // replace escaped characters with actual character
this.$ = yytext.replace(/\\(\\|")/g, "$"+"1")
.replace(/\\n/g,'\n')
.replace(/\\r/g,'\r')
.replace(/\\t/g,'\t')
.replace(/\\v/g,'\v')
.replace(/\\f/g,'\f')
.replace(/\\b/g,'\b');
break;
case 2:this.$ = Number(yytext);
break;
case 3:this.$ = null;
break;
case 4:this.$ = true;
break;
case 5:this.$ = false;
break;
case 6:return this.$ = $$[$0-1];
break;
case 13:this.$ = {};
break;
case 14:this.$ = $$[$0-1];
break;
case 15:this.$ = [$$[$0-2], $$[$0]];
break;
case 16:this.$ = {}; this.$[$$[$0][0]] = $$[$0][1];
break;
case 17:this.$ = $$[$0-2]; $$[$0-2][$$[$0][0]] = $$[$0][1];
break;
case 18:this.$ = [];
break;
case 19:this.$ = $$[$0-1];
break;
case 20:this.$ = [$$[$0]];
break;
case 21:this.$ = $$[$0-2]; $$[$0-2].push($$[$0]);
break;
}
},
table: [{3:5,4:[1,12],5:6,6:[1,13],7:3,8:[1,9],9:4,10:[1,10],11:[1,11],12:1,13:2,15:7,16:8,17:[1,14],23:[1,15]},{1:[3]},{14:[1,16]},{14:[2,7],18:[2,7],22:[2,7],24:[2,7]},{14:[2,8],18:[2,8],22:[2,8],24:[2,8]},{14:[2,9],18:[2,9],22:[2,9],24:[2,9]},{14:[2,10],18:[2,10],22:[2,10],24:[2,10]},{14:[2,11],18:[2,11],22:[2,11],24:[2,11]},{14:[2,12],18:[2,12],22:[2,12],24:[2,12]},{14:[2,3],18:[2,3],22:[2,3],24:[2,3]},{14:[2,4],18:[2,4],22:[2,4],24:[2,4]},{14:[2,5],18:[2,5],22:[2,5],24:[2,5]},{14:[2,1],18:[2,1],21:[2,1],22:[2,1],24:[2,1]},{14:[2,2],18:[2,2],22:[2,2],24:[2,2]},{3:20,4:[1,12],18:[1,17],19:18,20:19},{3:5,4:[1,12],5:6,6:[1,13],7:3,8:[1,9],9:4,10:[1,10],11:[1,11],13:23,15:7,16:8,17:[1,14],23:[1,15],24:[1,21],25:22},{1:[2,6]},{14:[2,13],18:[2,13],22:[2,13],24:[2,13]},{18:[1,24],22:[1,25]},{18:[2,16],22:[2,16]},{21:[1,26]},{14:[2,18],18:[2,18],22:[2,18],24:[2,18]},{22:[1,28],24:[1,27]},{22:[2,20],24:[2,20]},{14:[2,14],18:[2,14],22:[2,14],24:[2,14]},{3:20,4:[1,12],20:29},{3:5,4:[1,12],5:6,6:[1,13],7:3,8:[1,9],9:4,10:[1,10],11:[1,11],13:30,15:7,16:8,17:[1,14],23:[1,15]},{14:[2,19],18:[2,19],22:[2,19],24:[2,19]},{3:5,4:[1,12],5:6,6:[1,13],7:3,8:[1,9],9:4,10:[1,10],11:[1,11],13:31,15:7,16:8,17:[1,14],23:[1,15]},{18:[2,17],22:[2,17]},{18:[2,15],22:[2,15]},{22:[2,21],24:[2,21]}],
defaultActions: {16:[2,6]},
parseError: function parseError(str, hash) {
throw new Error(str);
},
parse: function parse(input) {
var self = this,
stack = [0],
vstack = [null], // semantic value stack
lstack = [], // location stack
table = this.table,
yytext = '',
yylineno = 0,
yyleng = 0,
recovering = 0,
TERROR = 2,
EOF = 1;
//this.reductionCount = this.shiftCount = 0;
this.lexer.setInput(input);
this.lexer.yy = this.yy;
this.yy.lexer = this.lexer;
if (typeof this.lexer.yylloc == 'undefined')
this.lexer.yylloc = {};
var yyloc = this.lexer.yylloc;
lstack.push(yyloc);
if (typeof this.yy.parseError === 'function')
this.parseError = this.yy.parseError;
function popStack (n) {
stack.length = stack.length - 2*n;
vstack.length = vstack.length - n;
lstack.length = lstack.length - n;
}
function lex() {
var token;
token = self.lexer.lex() || 1; // $end = 1
// if token isn't its numeric value, convert
if (typeof token !== 'number') {
token = self.symbols_[token] || token;
}
return token;
}
var symbol, preErrorSymbol, state, action, a, r, yyval={},p,len,newState, expected;
while (true) {
// retreive state number from top of stack
state = stack[stack.length-1];
// use default actions if available
if (this.defaultActions[state]) {
action = this.defaultActions[state];
} else {
if (symbol == null)
symbol = lex();
// read action for current state and first input
action = table[state] && table[state][symbol];
}
// handle parse error
_handle_error:
if (typeof action === 'undefined' || !action.length || !action[0]) {
if (!recovering) {
// Report error
expected = [];
for (p in table[state]) if (this.terminals_[p] && p > 2) {
expected.push("'"+this.terminals_[p]+"'");
}
var errStr = '';
if (this.lexer.showPosition) {
errStr = 'Parse error on line '+(yylineno+1)+":\n"+this.lexer.showPosition()+"\nExpecting "+expected.join(', ') + ", got '" + this.terminals_[symbol]+ "'";
} else {
errStr = 'Parse error on line '+(yylineno+1)+": Unexpected " +
(symbol == 1 /*EOF*/ ? "end of input" :
("'"+(this.terminals_[symbol] || symbol)+"'"));
}
this.parseError(errStr,
{text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected});
}
// just recovered from another error
if (recovering == 3) {
if (symbol == EOF) {
throw new Error(errStr || 'Parsing halted.');
}
// discard current lookahead and grab another
yyleng = this.lexer.yyleng;
yytext = this.lexer.yytext;
yylineno = this.lexer.yylineno;
yyloc = this.lexer.yylloc;
symbol = lex();
}
// try to recover from error
while (1) {
// check for error recovery rule in this state
if ((TERROR.toString()) in table[state]) {
break;
}
if (state == 0) {
throw new Error(errStr || 'Parsing halted.');
}
popStack(1);
state = stack[stack.length-1];
}
preErrorSymbol = symbol; // save the lookahead token
symbol = TERROR; // insert generic error symbol as new lookahead
state = stack[stack.length-1];
action = table[state] && table[state][TERROR];
recovering = 3; // allow 3 real symbols to be shifted before reporting a new error
}
// this shouldn't happen, unless resolve defaults are off
if (action[0] instanceof Array && action.length > 1) {
throw new Error('Parse Error: multiple actions possible at state: '+state+', token: '+symbol);
}
switch (action[0]) {
case 1: // shift
//this.shiftCount++;
stack.push(symbol);
vstack.push(this.lexer.yytext);
lstack.push(this.lexer.yylloc);
stack.push(action[1]); // push state
symbol = null;
if (!preErrorSymbol) { // normal execution/no error
yyleng = this.lexer.yyleng;
yytext = this.lexer.yytext;
yylineno = this.lexer.yylineno;
yyloc = this.lexer.yylloc;
if (recovering > 0)
recovering--;
} else { // error just occurred, resume old lookahead f/ before error
symbol = preErrorSymbol;
preErrorSymbol = null;
}
break;
case 2: // reduce
//this.reductionCount++;
len = this.productions_[action[1]][1];
// perform semantic action
yyval.$ = vstack[vstack.length-len]; // default to $$ = $1
// default location, uses first token for firsts, last for lasts
yyval._$ = {
first_line: lstack[lstack.length-(len||1)].first_line,
last_line: lstack[lstack.length-1].last_line,
first_column: lstack[lstack.length-(len||1)].first_column,
last_column: lstack[lstack.length-1].last_column
};
r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack);
if (typeof r !== 'undefined') {
return r;
}
// pop off stack
if (len) {
stack = stack.slice(0,-1*len*2);
vstack = vstack.slice(0, -1*len);
lstack = lstack.slice(0, -1*len);
}
stack.push(this.productions_[action[1]][0]); // push nonterminal (reduce)
vstack.push(yyval.$);
lstack.push(yyval._$);
// goto new state = table[STATE][NONTERMINAL]
newState = table[stack[stack.length-2]][stack[stack.length-1]];
stack.push(newState);
break;
case 3: // accept
return true;
}
}
return true;
}};
/* Jison generated lexer */
var lexer = (function(){
var lexer = ({EOF:1,
parseError:function parseError(str, hash) {
if (this.yy.parseError) {
this.yy.parseError(str, hash);
} else {
throw new Error(str);
}
},
setInput:function (input) {
this._input = input;
this._more = this._less = this.done = false;
this.yylineno = this.yyleng = 0;
this.yytext = this.matched = this.match = '';
this.conditionStack = ['INITIAL'];
this.yylloc = {first_line:1,first_column:0,last_line:1,last_column:0};
return this;
},
input:function () {
var ch = this._input[0];
this.yytext+=ch;
this.yyleng++;
this.match+=ch;
this.matched+=ch;
var lines = ch.match(/\n/);
if (lines) this.yylineno++;
this._input = this._input.slice(1);
return ch;
},
unput:function (ch) {
this._input = ch + this._input;
return this;
},
more:function () {
this._more = true;
return this;
},
less:function (n) {
this._input = this.match.slice(n) + this._input;
},
pastInput:function () {
var past = this.matched.substr(0, this.matched.length - this.match.length);
return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, "");
},
upcomingInput:function () {
var next = this.match;
if (next.length < 20) {
next += this._input.substr(0, 20-next.length);
}
return (next.substr(0,20)+(next.length > 20 ? '...':'')).replace(/\n/g, "");
},
showPosition:function () {
var pre = this.pastInput();
var c = new Array(pre.length + 1).join("-");
return pre + this.upcomingInput() + "\n" + c+"^";
},
next:function () {
if (this.done) {
return this.EOF;
}
if (!this._input) this.done = true;
var token,
match,
tempMatch,
index,
col,
lines;
if (!this._more) {
this.yytext = '';
this.match = '';
}
var rules = this._currentRules();
for (var i=0;i < rules.length; i++) {
tempMatch = this._input.match(this.rules[rules[i]]);
if (tempMatch && (!match || tempMatch[0].length > match[0].length)) {
match = tempMatch;
index = i;
if (!this.options.flex) break;
}
}
if (match) {
lines = match[0].match(/\n.*/g);
if (lines) this.yylineno += lines.length;
this.yylloc = {first_line: this.yylloc.last_line,
last_line: this.yylineno+1,
first_column: this.yylloc.last_column,
last_column: lines ? lines[lines.length-1].length-1 : this.yylloc.last_column + match[0].length}
this.yytext += match[0];
this.match += match[0];
this.yyleng = this.yytext.length;
this._more = false;
this._input = this._input.slice(match[0].length);
this.matched += match[0];
token = this.performAction.call(this, this.yy, this, rules[index],this.conditionStack[this.conditionStack.length-1]);
if (this.done && this._input) this.done = false;
if (token) return token;
else return;
}
if (this._input === "") {
return this.EOF;
} else {
this.parseError('Lexical error on line '+(this.yylineno+1)+'. Unrecognized text.\n'+this.showPosition(),
{text: "", token: null, line: this.yylineno});
}
},
lex:function lex() {
var r = this.next();
if (typeof r !== 'undefined') {
return r;
} else {
return this.lex();
}
},
begin:function begin(condition) {
this.conditionStack.push(condition);
},
popState:function popState() {
return this.conditionStack.pop();
},
_currentRules:function _currentRules() {
return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules;
},
topState:function () {
return this.conditionStack[this.conditionStack.length-2];
},
pushState:function begin(condition) {
this.begin(condition);
}});
lexer.options = {};
lexer.performAction = function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) {
var YYSTATE=YY_START
switch($avoiding_name_collisions) {
case 0:/* skip whitespace */
break;
case 1:return 6
break;
case 2:yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2); return 4
break;
case 3:return 17
break;
case 4:return 18
break;
case 5:return 23
break;
case 6:return 24
break;
case 7:return 22
break;
case 8:return 21
break;
case 9:return 10
break;
case 10:return 11
break;
case 11:return 8
break;
case 12:return 14
break;
case 13:return 'INVALID'
break;
}
};
lexer.rules = [/^(?:\s+)/,/^(?:(-?([0-9]|[1-9][0-9]+))(\.[0-9]+)?([eE][-+]?[0-9]+)?\b)/,/^(?:"(?:\\[\\"bfnrt/]|\\u[a-fA-F0-9]{4}|[^\\\0-\x09\x0a-\x1f"])*")/,/^(?:\{)/,/^(?:\})/,/^(?:\[)/,/^(?:\])/,/^(?:,)/,/^(?::)/,/^(?:true\b)/,/^(?:false\b)/,/^(?:null\b)/,/^(?:$)/,/^(?:.)/];
lexer.conditions = {"INITIAL":{"rules":[0,1,2,3,4,5,6,7,8,9,10,11,12,13],"inclusive":true}};
;
return lexer;})()
parser.lexer = lexer;
return parser;
})();
if (typeof require !== 'undefined' && typeof exports !== 'undefined') {
exports.parser = jsonlint;
exports.parse = function () { return jsonlint.parse.apply(jsonlint, arguments); }
exports.main = function commonjsMain(args) {
if (!args[1])
throw new Error('Usage: '+args[0]+' FILE');
if (typeof process !== 'undefined') {
var source = require('fs').readFileSync(require('path').join(process.cwd(), args[1]), "utf8");
} else {
var cwd = require("file").path(require("file").cwd());
var source = cwd.join(args[1]).read({charset: "utf-8"});
}
return exports.parser.parse(source);
}
if (typeof module !== 'undefined' && require.main === module) {
exports.main(typeof process !== 'undefined' ? process.argv.slice(1) : require("system").args);
}
}