diff --git a/.eslintrc b/.eslintrc index 91d40d2..b81c92f 100644 --- a/.eslintrc +++ b/.eslintrc @@ -5,136 +5,111 @@ }, "rules": { - // Customized + // === Configure rules for our style === + // imports must be resolvable + "import/no-unresolved": "error", + // use single quotes, + // unless a different style allows avoiding escapes + "quotes": ["error", "single", { + "avoidEscape": true, + "allowTemplateLiterals": true + }], + // allow else-if return + "no-else-return": [ "error", { "allowElseIf": true } ], + // expressions split over multiple lines + // should break after the operator + "operator-linebreak": [ "error", "after" ], + // require arrow parens only when needed + // and whenever the body is a block + "arrow-parens": ["error", "as-needed", { "requireForBlockBody": true }], + // what variables are errors in callbacks "handle-callback-err": [ "error","^(e$|(e|(.*(_e|E)))rr)" ], + // allow dangling commas in functions + // require them everywhere else "comma-dangle": ["error", { "arrays": "always-multiline", "objects": "always-multiline", "imports": "always-multiline", "exports": "always-multiline", - "functions": "never" + "functions": "only-multiline" }], + // we actually encourage `return await` "no-return-await": "off", - "no-constant-condition": "off", + // allow `while (true)` + "no-constant-condition": ["error", { "checkLoops": false }], + // allow ignoring an error with `catch` "no-empty": ["error", { "allowEmptyCatch": true }], - "no-underscore-dangle": "off", - "no-console": "off", + // allow `3 + 5 - 1`, but not `3 * 5 - 1` "no-mixed-operators": ["error", { "allowSamePrecedence": true }], + // require `'use strict';` "strict": ["error", "global"], - "consistent-return": "off", - "func-names": "off", - "no-tabs": "off", + // we actually use tabs for indentation "indent": ["error", "tab", { "SwitchCase": 1 }], + "no-tabs": "off", + // we want `== null` to also handle undefined "no-eq-null": "off", - "camelcase": "off", - "no-new": "off", - "no-shadow": "off", - "no-use-before-define": ["error", "nofunc"], - "no-prototype-builtins": "off", - "new-cap": "off", + // allow `for (..; i++)` "no-plusplus": ["error", { "allowForLoopAfterthoughts": true }], - "object-curly-newline": "off", - "no-restricted-globals": "off", - "function-paren-newline": "off", - "import/no-unresolved": "error", - "quotes": ["error", "single", { - "avoidEscape": true, - "allowTemplateLiterals": true + // allow using functions defined later + "no-use-before-define": ["error", "nofunc"], + // require consistent newlines before and after braces + // if contents are multiline + "object-curly-newline": ["error", { "consistent": true, "multiline": true }], + // require consistent linebreaks inline function parenthesis (arguments or params) + "function-paren-newline": ["error", "consistent"], + // only require const if all parts of destructuring can be const + "prefer-const": ["error", { "destructuring": "all" }], + // don't require destructuring for arrays or assignment + "prefer-destructuring": ["error", { + "VariableDeclarator": { "array": false, "object": true }, + "AssignmentExpression": { "array": false, "object": false } }], - "no-else-return": [ "error", { "allowElseIf": true } ], - "operator-linebreak": [ "error", "after" ], - "arrow-parens": ["error", "as-needed", { "requireForBlockBody": true }], + // identical to airbnb rule, except for allowing for..of, because we want to use it + "no-restricted-syntax": [ + "error", + { + "selector": "ForInStatement", + "message": "for..in loops iterate over the entire prototype chain, which is virtually never what you want. Use Object.{keys,values,entries}, and iterate over the resulting array." + }, + { + "selector": "LabeledStatement", + "message": "Labels are a form of GOTO; using them makes code confusing and hard to maintain and understand." + }, + { + "selector": "WithStatement", + "message": "`with` is disallowed in strict mode because it makes code impossible to predict and optimize." + } + ], + // allow lines of up to 120 characters + "max-len": ["error", { "code": 120, "tabWidth": 2, "ignoreUrls": true, "ignoreStrings": true, "ignoreTemplateLiterals": true, "ignoreRegExpLiterals": true }], - // ES6 - "prefer-rest-params": "off", - "prefer-spread": "off", - "prefer-arrow-callback": "off", - "prefer-template": "off", - "no-var": "off", + // === Disable rules === + // more liberal naming + "camelcase": "off", + "no-underscore-dangle": "off", + // don't require anonymous function names + "func-names": "off", + // allow console + "no-console": "off", + // allow new for side effects + // allow new with non-capitalized + "no-new": "off", + "new-cap": "off", + // allow shadowing variables (usually callbacks) + "no-shadow": "off", + // allow multiple empty lines in a row + "no-multiple-empty-lines": "off", + // allow not using object shorthand "object-shorthand": "off", - "vars-on-top": "off", - "prefer-destructuring": "off", // TODO + "consistent-return": "off", + "no-restricted-globals": "off", + "no-prototype-builtins": "off", "import/no-extraneous-dependencies": "off", "import/no-dynamic-require": "off", - "import/newline-after-import": "off", - "no-bitwise": "off", "global-require": "off", - "max-len": "off", "no-param-reassign": "off", - "no-restricted-syntax": "off", - "no-script-url": "off", - "default-case": "off", - "linebreak-style": "off", - - // "no-multi-assign": "off", - // "one-var": "off", - // "no-undef": "off", - // "max-nested-callbacks": "off", - // "no-mixed-requires": "off", - // "brace-style": "off", - // "max-statements-per-line": "off", - // "no-unused-vars": "off", - // "no-mixed-spaces-and-tabs": "off", - // "no-useless-concat": "off", - // "require-jsdoc": "off", - // "eqeqeq": "off", - // "no-negated-condition": "off", - // "one-var-declaration-per-line": "off", - // "no-lonely-if": "off", - // "radix": "off", - // "no-else-return": "off", - // "no-useless-escape": "off", - // "block-scoped-var": "off", - // "operator-assignment": "off", - // "yoda": "off", - // "no-loop-func": "off", - // "no-void": "off", - // "valid-jsdoc": "off", - // "no-cond-assign": "off", - // "no-redeclare": "off", - // "no-unreachable": "off", - // "no-nested-ternary": "off", - // "operator-linebreak": "off", - // "guard-for-in": "off", - // "no-unneeded-ternary": "off", - // "no-sequences": "off", - // "no-extend-native": "off", - // "no-shadow-restricted-names": "off", - // "no-extra-boolean-cast": "off", - // "no-path-concat": "off", - // "no-unused-expressions": "off", - // "no-return-assign": "off", - // "no-restricted-modules": "off", - // "object-curly-spacing": "off", - // "indent": "off", - // "padded-blocks": "off", - // "eol-last": "off", - // "lines-around-directive": "off", - // "strict": "off", - // "comma-dangle": "off", - // "no-multi-spaces": "off", - // "quotes": "off", - // "keyword-spacing": "off", - // "no-mixed-operators": "off", - // "comma-spacing": "off", - // "no-trailing-spaces": "off", - // "key-spacing": "off", - // "no-multiple-empty-lines": "off", - // "spaced-comment": "off", - // "space-in-parens": "off", - // "block-spacing": "off", - // "quote-props": "off", - // "space-unary-ops": "off", - // "no-empty": "off", - // "dot-notation": "off", - // "func-call-spacing": "off", - // "array-bracket-spacing": "off", - // "object-property-newline": "off", - // "no-continue": "off", - // "no-extra-semi": "off", - // "no-spaced-func": "off", - // "no-useless-return": "off" + "default-case": "off" } } diff --git a/commitlint.config.js b/commitlint.config.js index 6b18799..062d24b 100644 --- a/commitlint.config.js +++ b/commitlint.config.js @@ -1,3 +1,26 @@ 'use strict'; -module.exports = { extends: ['@commitlint/config-angular'] }; +module.exports = { + extends: ['@commitlint/config-angular'], + rules: { + 'header-max-length': [1, 'always', 72], + 'type-enum': [ + 2, + 'always', + [ + 'breaking', + 'build', + 'chore', + 'ci', + 'docs', + 'feat', + 'fix', + 'perf', + 'refactor', + 'revert', + 'style', + 'test', + ], + ], + }, +}; diff --git a/static/lib/emoji.js b/static/lib/emoji.js index 775815f..da0f018 100644 --- a/static/lib/emoji.js +++ b/static/lib/emoji.js @@ -1,6 +1,6 @@ 'use strict'; -/* globals $, define, socket, app, config */ +/* globals define, socket, app, config */ /** * DEVELOPER NOTE @@ -17,42 +17,49 @@ * for a hook to be fired by autocomplete, of which there is none right now. */ -define('quill-emoji', ['quill'], function (quill) { - var Emoji = { +define('quill-emoji', ['quill'], (quill) => { + const Emoji = { table: {}, - blots: {}, }; // Emoji Blot - var imageBlot = quill.import('formats/image'); - var emojiAttributes = ['alt', 'class']; - Emoji.blots.emoji = $.extend(true, imageBlot, { - blotName: 'emoji', - formats: function (domNode) { + const imageBlot = quill.import('formats/image'); + const emojiAttributes = ['alt', 'class']; + + class EmojiBlot extends imageBlot { + static create(value) { + const node = super.create(value.src); + node.setAttribute('class', value.class); + return node; + } + + static formats(domNode) { return emojiAttributes.reduce((formats, attribute) => { if (domNode.hasAttribute(attribute)) { formats[attribute] = domNode.getAttribute(attribute); } return formats; }, {}); - }, - }); - Emoji.blots.emoji.prototype.format = function (name, value) { - if (emojiAttributes.includes(name)) { - if (value) { - this.domNode.setAttribute(name, value); - } else { - this.domNode.removeAttribute(name); - } - } else { - imageBlot.format(name, value); } - }; - quill.register(Emoji.blots.emoji); + + static format(name, value) { + if (emojiAttributes.includes(name)) { + if (value) { + this.domNode.setAttribute(name, value); + } else { + this.domNode.removeAttribute(name); + } + } else { + super.format(name, value); + } + } + } + EmojiBlot.blotName = 'emoji'; + quill.register(EmojiBlot); Emoji.enable = function (quill) { if (!Object.keys(Emoji.table).length) { - socket.emit('plugins.composer-quill.getEmojiTable', {}, function (err, table) { + socket.emit('plugins.composer-quill.getEmojiTable', {}, (err, table) => { if (err) { app.alertError(err.message); } @@ -66,27 +73,29 @@ define('quill-emoji', ['quill'], function (quill) { }; Emoji.convert = function (delta) { - var quill = this; - var contents = quill.getContents(); - var emojiRegex = /:([\w+-]+):/g; + const quill = this; + const contents = quill.getContents(); + const emojiRegex = /:([\w+-]+):/g; // Special handling for emoji plugin if (!delta || delta.ops.some(command => command.insert && (command.insert === ':' || String(command.insert).endsWith(':') || String(command.insert).endsWith(': \n')))) { // Check all nodes for emoji shorthand and replace with image - contents.reduce(function (retain, cur) { - var match = emojiRegex.exec(cur.insert); - var contents; - var emojiObj; + contents.reduce((retain, cur) => { + let match = emojiRegex.exec(cur.insert); + let contents; + let emojiObj; while (match !== null) { emojiObj = Emoji.table[match[1]]; if (emojiObj) { contents = [{ insert: { - emoji: config.relative_path + '/plugins/nodebb-plugin-emoji/emoji/' + emojiObj.pack + '/' + emojiObj.image + '?' + app.cacheBuster, + emoji: { + url: `${config.relative_path}/plugins/nodebb-plugin-emoji/emoji/${emojiObj.pack}/${emojiObj.image}?${app.cacheBuster}`, + class: `not-responsive emoji emoji-${emojiObj.pack} emoji--${emojiObj.name}`, + }, }, attributes: { alt: emojiObj.character, - class: 'not-responsive emoji emoji-' + emojiObj.pack + ' emoji--' + emojiObj.name, }, }]; if (match[0].length) {