From 491f29ceecd0d650df6e8dae35982a605b1854d9 Mon Sep 17 00:00:00 2001 From: ItsOnlyBinary Date: Thu, 28 Nov 2024 14:28:17 +0000 Subject: [PATCH] Working colours --- package.json | 8 + src/components/App.vue | 7 + src/components/ControlInput.vue | 34 +- src/components/inputtools/Emoji.vue | 12 +- src/components/inputtools/TextStyle.vue | 361 +++++++++++++------ src/components/utils/NewIrcInput.vue | 444 ++++++++++++++---------- src/helpers/Html2Irc.js | 177 ++++++++++ src/libs/EmojiProvider.js | 26 +- src/libs/MessageFormatter.js | 37 +- src/libs/iconLibrary.js | 20 +- src/libs/renderers/Html.js | 9 +- src/res/locales/app.dev.po | 53 +++ static/themes/common/base.css | 306 ++++++++++++++++ tests/unit/Html2Irc.spec.js | 37 ++ yarn.lock | 133 +++++++ 15 files changed, 1348 insertions(+), 316 deletions(-) create mode 100644 src/helpers/Html2Irc.js create mode 100644 tests/unit/Html2Irc.spec.js diff --git a/package.json b/package.json index b195dc8168..d878abee75 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,13 @@ "@fortawesome/free-regular-svg-icons": "^6.7.1", "@fortawesome/free-solid-svg-icons": "^6.7.1", "@fortawesome/vue-fontawesome": "^3.0.8", + "@lexical/code": "^0.20.0", + "@lexical/history": "^0.20.0", + "@lexical/html": "^0.20.0", + "@lexical/plain-text": "^0.20.0", + "@lexical/rich-text": "^0.20.0", + "@lexical/selection": "^0.20.0", + "@lexical/utils": "^0.20.0", "buffer": "^6.0.3", "compare-versions": "^6.1.1", "css-vars-ponyfill": "^2.4.9", @@ -31,6 +38,7 @@ "ip-regex": "^5.0.0", "irc-framework": "^4.14.0", "json5": "^2.2.3", + "lexical": "^0.20.0", "lodash": "^4.17.21", "murmurhash3js": "^3.0.1", "sockjs-client": "^1.6.1", diff --git a/src/components/App.vue b/src/components/App.vue index bea1d60f71..ff469c5254 100755 --- a/src/components/App.vue +++ b/src/components/App.vue @@ -384,6 +384,13 @@ html, body, #kiwiirc { overflow: hidden; } +@supports (font-size: round(nearest, 90%, 1px)) and (line-height: round(nearest, 1.6em, 1px)) { + .kiwi-wrap { + font-size: ~'round(nearest, 90%, 1px)'; + line-height: ~'round(nearest, 1.6em, 1px)'; + } +} + /* .kiwi-workspace has ::before and ::after 4px above itself for the connection status */ .kiwi-workspace { position: relative; diff --git a/src/components/ControlInput.vue b/src/components/ControlInput.vue index 8085dc25cb..ddbfd3398e 100755 --- a/src/components/ControlInput.vue +++ b/src/components/ControlInput.vue @@ -108,13 +108,15 @@ v-if="shouldShowColorPicker" class="kiwi-controlinput-button" @click.prevent="onToolClickTextStyle" + @mousedown.prevent > - +
@@ -156,6 +158,7 @@ import * as settingTools from '@/libs/settingTools'; import NewIrcInput from '@/components/utils/NewIrcInput'; import autocompleteCommands from '@/res/autocompleteCommands'; import GlobalApi from '@/libs/GlobalApi'; +import { html2irc } from '@/helpers/Html2Irc'; import AutoComplete from './AutoComplete'; import ToolTextStyle from './inputtools/TextStyle'; import ToolEmoji from './inputtools/Emoji'; @@ -406,13 +409,13 @@ export default { } }, toggleBold() { - this.$refs.input.toggleBold(); + this.$refs.input.toggleStyle('bold'); }, toggleItalic() { - this.$refs.input.toggleItalic(); + this.$refs.input.toggleStyle('italic'); }, toggleUnderline() { - this.$refs.input.toggleUnderline(); + this.$refs.input.toggleStyle('underline'); }, onAutocompleteCancel() { this.autocomplete_open = false; @@ -449,14 +452,7 @@ export default { this.$refs.autocomplete.selectCurrentItem(); } - if (event.key === 'Enter' && ( - (event.altKey && !event.shiftKey && !event.metaKey && !event.ctrlKey) || - (event.shiftKey && !event.altKey && !event.metaKey && !event.ctrlKey) - )) { - // Add new line when shift+enter or alt+enter is pressed - event.preventDefault(); - this.$refs.input.insertText('\n'); - } else if (event.key === 'Enter') { + if (event.key === 'Enter' && !event.shiftKey && !event.altKey) { // Send message when enter is pressed event.preventDefault(); this.submitForm(); @@ -603,8 +599,8 @@ export default { this.autocomplete_filter = currentToken; } }, - async submitForm() { - let rawInput = await this.$refs.input.getValue(); + submitForm() { + let rawInput = this.$refs.input.getValue(); if (!rawInput) { if (!this.has_focus && this.keep_focus) { // Maybe triggered by the send button on empty input, @@ -614,7 +610,9 @@ export default { return; } - let ircText = this.$refs.input.buildIrcText(); + console.log('raw', JSON.stringify(rawInput)); + let ircText = html2irc(rawInput); + console.log('irc', JSON.stringify(ircText)); // Show a warning if a command is preceded by spaces let warnExpectedCommand = this.$state.setting('buffers.warn_expected_command'); @@ -1022,6 +1020,12 @@ export default { cursor: pointer; justify-content: center; align-items: center; + position: relative; + + .kiwi-controlinput-button-clicker { + position: absolute; + inset: 0; + } svg { font-size: 20px; diff --git a/src/components/inputtools/Emoji.vue b/src/components/inputtools/Emoji.vue index 17874998b3..ad56aaf082 100644 --- a/src/components/inputtools/Emoji.vue +++ b/src/components/inputtools/Emoji.vue @@ -5,7 +5,7 @@ v-for="(text, eCode) in emojis" :key="eCode" :style="{'background-image':`url(${location}${eCode})`}" - :data-code="text" + :data-ascii="text" class="kiwi-inputtool-emoji-emoji" @click="onImgClick" /> @@ -17,7 +17,7 @@ 'kiwi public'; import _ from 'lodash'; -import * as Misc from '@/helpers/Misc'; +import * as EmojiProvider from '@/libs/EmojiProvider.js'; export default { props: ['ircinput'], @@ -40,12 +40,8 @@ export default { }, methods: { onImgClick: function onImgClick(event) { - let url = window.getComputedStyle(event.target, null) - .getPropertyValue('background-image'); - - url = Misc.extractURL(url); - let code = event.target.dataset.code; - this.ircinput.addImg(code, url); + const [emoji] = EmojiProvider.getEmojis(event.target.dataset.ascii); + this.ircinput.addEmoji(emoji); }, }, }; diff --git a/src/components/inputtools/TextStyle.vue b/src/components/inputtools/TextStyle.vue index 16e0a9c012..b23f8a5b92 100644 --- a/src/components/inputtools/TextStyle.vue +++ b/src/components/inputtools/TextStyle.vue @@ -1,111 +1,163 @@ + + + diff --git a/src/helpers/Html2Irc.js b/src/helpers/Html2Irc.js new file mode 100644 index 0000000000..afa2998ab4 --- /dev/null +++ b/src/helpers/Html2Irc.js @@ -0,0 +1,177 @@ +import { getStyleObjectFromCSS } from '@lexical/selection'; +import * as htmlparser from 'htmlparser2'; + +const EMOJI_NODE_CLASS = 'emoji-node'; + +const varColourRegexp = /^var\(--irc-colour-(\d+)\)$/; +const endsWithFgColourRegexp = /\x03\d+$/; +const endsWithResetColourRegexp = /\x03$/; +const startsWithCommaDecimalRegexp = /^,\d+/; +const startsWithDecimalRegexp = /^\d+/; + +const defaultIrcStyle = { + fg: null, + bg: null, + bold: false, + italic: false, + underline: false, + strikethrough: false, +}; + +export function html2irc(source, args = {}) { + let ircText = ''; + + const currentIrcStyle = Object.assign({}, defaultIrcStyle); + + const openTags = []; + let checkSpace = false; + const parser = new htmlparser.Parser({ + onopentag: (name, attribs) => { + if (attribs.class === EMOJI_NODE_CLASS) { + openTags.push('emoji-span'); + const emoji = attribs['data-code']; + if (!emoji) { + return; + } + const needSpace = !['', ' '].includes(ircText.slice(-1)); + ircText += needSpace ? ` ${emoji}` : emoji; + checkSpace = true; + return; + } + + openTags.push(name); + + const style = getStyleObjectFromCSS(attribs.style || ''); + const ircStyle = style2IrcStyle(style); + const ircStyleDiff = getIrcStyleDiff(currentIrcStyle, ircStyle); + + if (!Object.keys(ircStyleDiff).length) { + // No changes needed + return; + } + + ircText += ircStyleDiff2IrcCodes(currentIrcStyle, ircStyleDiff); + + Object.assign(currentIrcStyle, ircStyleDiff); + }, + ontext: (text) => { + const tag = openTags.slice(-1)[0]; + if (tag === 'emoji-span') { + return; + } + + if ( + (startsWithCommaDecimalRegexp.test(text) && endsWithFgColourRegexp.test(ircText)) + || (startsWithDecimalRegexp.test(text) && endsWithResetColourRegexp.test(ircText)) + ) { + ircText += '\u2008'; + } + + if (checkSpace) { + checkSpace = false; + const needSpace = ircText.slice(0) !== ' '; + if (needSpace) { + ircText += ' '; + } + } + + ircText += text; + }, + onclosetag: (name) => { + openTags.pop(); + }, + }, { + decodeEntities: true, + }); + + parser.write(source); + parser.end(); + + return ircText; +} + +function style2IrcStyle(style) { + const ircStyle = Object.assign({}, defaultIrcStyle); + Object.entries(style).forEach(([key, value]) => { + if (key === 'color') { + ircStyle.fg = getColourCode(value); + } else if (key === 'background-color') { + ircStyle.bg = getColourCode(value); + } else if (key === 'font-weight') { + ircStyle.bold = value === 'bold'; + } else if (key === 'font-style') { + ircStyle.italic = value === 'italic'; + } else if (key === 'text-decoration') { + const decor = value ? value.split(' ') : []; + ircStyle.underline = decor.includes('underline'); + ircStyle.strikethrough = decor.includes('line-through'); + } + }); + return ircStyle; +} + +function ircStyleDiff2IrcCodes(currentIrcStyle, ircStyleDiff) { + let ircCodes = ''; + + if (shouldResetIrcStyles(currentIrcStyle, ircStyleDiff)) { + // Changes are resulting in defaults + // Just send the reset character code + return '\x0f'; + } + + if (ircStyleDiff.hasOwnProperty('fg')) { + if (ircStyleDiff.fg) { + ircCodes += `\x03${ircStyleDiff.fg}`; + + if (ircStyleDiff.bg) { + ircCodes += `,${ircStyleDiff.bg}`; + } + } else { + ircCodes += '\x03'; + } + } else if (ircStyleDiff.hasOwnProperty('bg')) { + if (!ircStyleDiff.bg && currentIrcStyle.fg) { + ircCodes += `\x03${currentIrcStyle.fg}`; + } + } + + if (ircStyleDiff.hasOwnProperty('bold')) { + ircCodes += '\x02'; + } + if (ircStyleDiff.hasOwnProperty('italic')) { + ircCodes += '\x1d'; + } + if (ircStyleDiff.hasOwnProperty('underline')) { + ircCodes += '\x1f'; + } + if (ircStyleDiff.hasOwnProperty('strikethrough')) { + ircCodes += '\x1e'; + } + + return ircCodes; +} + +function getIrcStyleDiff(a, b) { + const diff = {}; + Object.keys(a).forEach((key) => { + if (a[key] !== b[key]) { + diff[key] = b[key]; + } + }); + return diff; +} + +function getColourCode(styleValue) { + const match = varColourRegexp.exec(styleValue); + if (!match || match.length !== 2) { + return null; + } + return match[1]; +} + +function shouldResetIrcStyles(currentIrcStyle, diff) { + const resultIrcStyle = Object.assign({}, defaultIrcStyle, currentIrcStyle, diff); + return Object.keys(defaultIrcStyle).every( + (key) => defaultIrcStyle[key] === resultIrcStyle[key] + ); +} diff --git a/src/libs/EmojiProvider.js b/src/libs/EmojiProvider.js index ab0d163c7c..ad0cd1a19f 100644 --- a/src/libs/EmojiProvider.js +++ b/src/libs/EmojiProvider.js @@ -13,7 +13,7 @@ export function matchEmoji(word) { match: word, type: 'emoji', meta: { - emoji: emojis[0].code, + emoji: emojis[0], }, }]; } @@ -23,10 +23,9 @@ export function blockToHtml(block, isSingle, showEmoticons) { return block.content; } - const emojiLocation = getState().setting('emojiLocation'); const emoji = block.meta.emoji; const classes = 'kiwi-messagelist-emoji' + (isSingle ? ' kiwi-messagelist-emoji--single' : ''); - const src = `${emojiLocation}${emoji}`; + const src = emoji.url; return `${escape(block.content)}`; } @@ -39,8 +38,27 @@ export function getEmojis(word) { } return [{ ascii: word, - code: emojiList[word], + code: emojiList[word].split('.')[0], url: emojiLocation + emojiList[word], + ircValue: word, + // imgProps allows setting properties of + }]; +} + +export function getEmojiFromUnified(unifiedID) { + const emojiList = getState().setting('emojis'); + const emojiLocation = getState().setting('emojiLocation'); + const [emojiWord, emojiFile] = Object.entries(emojiList).find( + ([key, value]) => value.split('.')[0] === unifiedID + ); + if (!emojiWord) { + return []; + } + return [{ + ascii: emojiWord, + code: emojiFile.split('.')[0], + url: emojiLocation + emojiFile, + ircValue: emojiWord, // imgProps allows setting properties of }]; } diff --git a/src/libs/MessageFormatter.js b/src/libs/MessageFormatter.js index bda9b866a1..039b403465 100644 --- a/src/libs/MessageFormatter.js +++ b/src/libs/MessageFormatter.js @@ -130,7 +130,7 @@ tokens['`'] = { } // Backticks may be part of a word or NICK so only consider it a codeblock if - // it's at the start of a scentence or comes after a space + // it's at the start of a sentence or comes after a space if (pos > 0 && inp[pos - 1] !== ' ') { return -1; } @@ -215,6 +215,23 @@ tokens['\x1F'] = { }, }; +// Strikethrough +tokens['\x1E'] = { + token: '\x1E', + extra: false, + fn: function parseToken(inp, pos, block, prevBlock, openToks) { + if (openToks[this.token]) { + delete block.styles.strikethrough; + openToks[this.token] = null; + } else { + openToks[this.token] = true; + block.styles.strikethrough = true; + } + + return null; + }, +}; + // Clear all styles tokens['\x0F'] = { token: '\x0F', @@ -275,6 +292,24 @@ tokens['\x03'] = { }, }; +// Hex Colour (unimplemented) +tokens['\x04'] = { + token: '\x04', + extra: false, + fn: function parseToken(inp, pos, block, prevBlock, openToks) { + return null; + }, +}; + +// Monospace (unimplemented) +tokens['\x11'] = { + token: '\x11', + extra: false, + fn: function parseToken(inp, pos, block, prevBlock, openToks) { + return null; + }, +}; + // Reverse (unimplemented) tokens['\x16'] = { token: '\x16', diff --git a/src/libs/iconLibrary.js b/src/libs/iconLibrary.js index f08d3b8c40..533665c5c4 100644 --- a/src/libs/iconLibrary.js +++ b/src/libs/iconLibrary.js @@ -5,27 +5,32 @@ */ import { faAngleRight as fasAngleRight } from '@fortawesome/free-solid-svg-icons/faAngleRight'; +import { faAnglesRight as fasAnglesRight } from '@fortawesome/free-solid-svg-icons/faAnglesRight'; import { faArrowsRotate as fasArrowsRotate } from '@fortawesome/free-solid-svg-icons/faArrowsRotate'; import { faBackwardStep as fasBackwardStep } from '@fortawesome/free-solid-svg-icons/faBackwardStep'; import { faBan as fasBan } from '@fortawesome/free-solid-svg-icons/faBan'; import { faBars as fasBars } from '@fortawesome/free-solid-svg-icons/faBars'; +import { faBold as fasBold } from '@fortawesome/free-solid-svg-icons/faBold'; import { faCaretDown as fasCaretDown } from '@fortawesome/free-solid-svg-icons/faCaretDown'; import { faCaretRight as fasCaretRight } from '@fortawesome/free-solid-svg-icons/faCaretRight'; import { faCaretUp as fasCaretUp } from '@fortawesome/free-solid-svg-icons/faCaretUp'; import { faCircleArrowRight as fasCircleArrowRight } from '@fortawesome/free-solid-svg-icons/faCircleArrowRight'; -import { faCircleHalfStroke as fasCircleHalfStroke } from '@fortawesome/free-solid-svg-icons/faCircleHalfStroke'; import { faCog as fasCog } from '@fortawesome/free-solid-svg-icons/faCog'; +import { faEraser as fasEraser } from '@fortawesome/free-solid-svg-icons/faEraser'; import { faEye as fasEye } from '@fortawesome/free-solid-svg-icons/faEye'; import { faForwardStep as fasForwardStep } from '@fortawesome/free-solid-svg-icons/faForwardStep'; import { faGears as fasGears } from '@fortawesome/free-solid-svg-icons/faGears'; import { faGlobe as fasGlobe } from '@fortawesome/free-solid-svg-icons/faGlobe'; import { faInfo as fasInfo } from '@fortawesome/free-solid-svg-icons/faInfo'; +import { faItalic as fasItalic } from '@fortawesome/free-solid-svg-icons/faItalic'; import { faLock as fasLock } from '@fortawesome/free-solid-svg-icons/faLock'; import { faMagnifyingGlass as fasMagnifyingGlass } from '@fortawesome/free-solid-svg-icons/faMagnifyingGlass'; import { faMapPin as fasMapPin } from '@fortawesome/free-solid-svg-icons/faMapPin'; +import { faPalette as fasPalette } from '@fortawesome/free-solid-svg-icons/faPalette'; import { faPaperPlane as fasPaperPlane } from '@fortawesome/free-solid-svg-icons/faPaperPlane'; import { faPencil as fasPencil } from '@fortawesome/free-solid-svg-icons/faPencil'; import { faPlus as fasPlus } from '@fortawesome/free-solid-svg-icons/faPlus'; +import { faRectangleList as fasRectangleList } from '@fortawesome/free-solid-svg-icons/faRectangleList'; import { faRectangleXmark as fasRectangleXmark } from '@fortawesome/free-solid-svg-icons/faRectangleXmark'; import { faRefresh as fasRefresh } from '@fortawesome/free-solid-svg-icons/faRefresh'; import { faRightFromBracket as fasRightFromBracket } from '@fortawesome/free-solid-svg-icons/faRightFromBracket'; @@ -35,9 +40,11 @@ import { faScrewdriverWrench as fasScrewdriverWrench } from '@fortawesome/free-s import { faSearch as fasSearch } from '@fortawesome/free-solid-svg-icons/faSearch'; import { faServer as fasServer } from '@fortawesome/free-solid-svg-icons/faServer'; import { faShareFromSquare as fasShareFromSquare } from '@fortawesome/free-solid-svg-icons/faShareFromSquare'; +import { faStrikethrough as fasStrikethrough } from '@fortawesome/free-solid-svg-icons/faStrikethrough'; import { faTimes as fasTimes } from '@fortawesome/free-solid-svg-icons/faTimes'; import { faTrashCan as fasTrashCan } from '@fortawesome/free-solid-svg-icons/faTrashCan'; import { faTriangleExclamation as fasTriangleExclamation } from '@fortawesome/free-solid-svg-icons/faTriangleExclamation'; +import { faUnderline as fasUnderline } from '@fortawesome/free-solid-svg-icons/faUnderline'; import { faUnlock as fasUnlock } from '@fortawesome/free-solid-svg-icons/faUnlock'; import { faUser as fasUser } from '@fortawesome/free-solid-svg-icons/faUser'; import { faUsers as fasUsers } from '@fortawesome/free-solid-svg-icons/faUsers'; @@ -48,6 +55,7 @@ import { faCircleRight as farCircleRight } from '@fortawesome/free-regular-svg-i import { faComment as farComment } from '@fortawesome/free-regular-svg-icons/faComment'; import { faFaceFrown as farFaceFrown } from '@fortawesome/free-regular-svg-icons/faFaceFrown'; import { faFaceSmile as farFaceSmile } from '@fortawesome/free-regular-svg-icons/faFaceSmile'; +import { faRectangleList as farRectangleList } from '@fortawesome/free-regular-svg-icons/faRectangleList'; import { faSquareMinus as farSquareMinus } from '@fortawesome/free-regular-svg-icons/faSquareMinus'; import { faSquarePlus as farSquarePlus } from '@fortawesome/free-regular-svg-icons/faSquarePlus'; import { faStar as farStar } from '@fortawesome/free-regular-svg-icons/faStar'; @@ -56,27 +64,32 @@ import { library } from '@fortawesome/fontawesome-svg-core'; library.add( fasAngleRight, + fasAnglesRight, fasArrowsRotate, fasBackwardStep, fasBan, fasBars, + fasBold, fasCaretDown, fasCaretRight, fasCaretUp, fasCircleArrowRight, - fasCircleHalfStroke, fasCog, + fasEraser, fasEye, fasForwardStep, fasGears, fasGlobe, fasInfo, + fasItalic, fasLock, fasMagnifyingGlass, fasMapPin, + fasPalette, fasPaperPlane, fasPencil, fasPlus, + fasRectangleList, fasRectangleXmark, fasRefresh, fasRightFromBracket, @@ -86,9 +99,11 @@ library.add( fasSearch, fasServer, fasShareFromSquare, + fasStrikethrough, fasTimes, fasTrashCan, fasTriangleExclamation, + fasUnderline, fasUnlock, fasUser, fasUsers, @@ -98,6 +113,7 @@ library.add( farComment, farFaceFrown, farFaceSmile, + farRectangleList, farSquareMinus, farSquarePlus, farStar, diff --git a/src/libs/renderers/Html.js b/src/libs/renderers/Html.js index 130fbf8b63..a5791cf25a 100644 --- a/src/libs/renderers/Html.js +++ b/src/libs/renderers/Html.js @@ -20,10 +20,13 @@ function render(blocks, renderEmoticons) { // a let style = ''; let classes = ''; + let decoration = []; Object.keys(block.styles).forEach((s) => { if (s === 'underline') { - style += 'text-decoration:underline;'; + decoration.push('underline'); + } else if (s === 'strikethrough') { + decoration.push('line-through'); } else if (s === 'bold') { style += 'font-weight:bold;'; } else if (s === 'italic') { @@ -39,6 +42,10 @@ function render(blocks, renderEmoticons) { } }); + if (decoration.length) { + style += `text-decoration:${decoration.join(' ')};`; + } + let content; switch (block.type) { case 'url': diff --git a/src/res/locales/app.dev.po b/src/res/locales/app.dev.po index c85fbed7ad..2ec76573ae 100644 --- a/src/res/locales/app.dev.po +++ b/src/res/locales/app.dev.po @@ -1201,6 +1201,59 @@ msgid "dice_roll" msgstr "Rolls a {{sides}} sided dice and gets {{number}}" + +#: Control Input Style + +#: Sample Text +msgid "input_style_sample" +msgstr "Sample Text" + +#: Set Foreground Colour First +msgid "input_style_first" +msgstr "Set Foreground Colour First" + +#: Set Foreground Colour +msgid "input_style_fore" +msgstr "Set Foreground Colour" + +#: Set Background Colour +msgid "input_style_back" +msgstr "Set Background Colour" + +#: Bold +msgid "input_style_bold" +msgstr "Bold" + +#: Italic +msgid "input_style_italic" +msgstr "Italic" + +#: Underline +msgid "input_style_underline" +msgstr "Underline" + +#: Strikethrough +msgid "input_style_strikethrough" +msgstr "Strikethrough" + +#: Clear Current Styles +msgid "input_style_clear" +msgstr "Clear Current Styles" + +#: Remove All Styles +msgid "input_style_remove" +msgstr "Remove All Styles" + +#: Show Extended Colours +msgid "input_style_show" +msgstr "Show Extended Colours" + +#: Hide Extended Colours +msgid "input_style_hide" +msgstr "Hide Extended Colours" + + + #: Notifications #: You were mentioned in {{channel}} diff --git a/static/themes/common/base.css b/static/themes/common/base.css index 0d6fd28737..43f6129b94 100644 --- a/static/themes/common/base.css +++ b/static/themes/common/base.css @@ -1068,3 +1068,309 @@ :root { --brand-selected: rgba(0, 0, 255, 0.3); } + +/* stylelint-disable-next-line no-duplicate-selectors */ +:root { + --irc-colour-00: #fff; + --irc-colour-01: #000; + --irc-colour-02: #00f; + --irc-colour-03: #0f0; + --irc-colour-04: #ff5959; + --irc-colour-05: #743a00; + --irc-colour-06: #a500ff; + --irc-colour-07: #ff8c00; + --irc-colour-08: #ff0; + --irc-colour-09: #6fff6f; + --irc-colour-10: #0cc; + --irc-colour-11: #6dffff; + --irc-colour-12: #59b4ff; + --irc-colour-13: #f0f; + --irc-colour-14: #a6a6a6; + --irc-colour-15: #e2e2e2; + + /* do not edit 16-98 these are standardised values */ + --irc-colour-16: #470000; + --irc-colour-17: #472100; + --irc-colour-18: #474700; + --irc-colour-19: #324700; + --irc-colour-20: #004700; + --irc-colour-21: #00472c; + --irc-colour-22: #004747; + --irc-colour-23: #002747; + --irc-colour-24: #000047; + --irc-colour-25: #2e0047; + --irc-colour-26: #470047; + --irc-colour-27: #47002a; + --irc-colour-28: #740000; + --irc-colour-29: #743a00; + --irc-colour-30: #747400; + --irc-colour-31: #517400; + --irc-colour-32: #007400; + --irc-colour-33: #007449; + --irc-colour-34: #007474; + --irc-colour-35: #004074; + --irc-colour-36: #000074; + --irc-colour-37: #4b0074; + --irc-colour-38: #740074; + --irc-colour-39: #740045; + --irc-colour-40: #b50000; + --irc-colour-41: #b56300; + --irc-colour-42: #b5b500; + --irc-colour-43: #7db500; + --irc-colour-44: #00b500; + --irc-colour-45: #00b571; + --irc-colour-46: #00b5b5; + --irc-colour-47: #0063b5; + --irc-colour-48: #0000b5; + --irc-colour-49: #7500b5; + --irc-colour-50: #b500b5; + --irc-colour-51: #b5006b; + --irc-colour-52: #ff0000; + --irc-colour-53: #ff8c00; + --irc-colour-54: #ffff00; + --irc-colour-55: #b2ff00; + --irc-colour-56: #00ff00; + --irc-colour-57: #00ffa0; + --irc-colour-58: #00ffff; + --irc-colour-59: #008cff; + --irc-colour-60: #0000ff; + --irc-colour-61: #a500ff; + --irc-colour-62: #ff00ff; + --irc-colour-63: #ff0098; + --irc-colour-64: #ff5959; + --irc-colour-65: #ffb459; + --irc-colour-66: #ffff71; + --irc-colour-67: #cfff60; + --irc-colour-68: #6fff6f; + --irc-colour-69: #65ffc9; + --irc-colour-70: #6dffff; + --irc-colour-71: #59b4ff; + --irc-colour-72: #5959ff; + --irc-colour-73: #c459ff; + --irc-colour-74: #ff66ff; + --irc-colour-75: #ff59bc; + --irc-colour-76: #ff9c9c; + --irc-colour-77: #ffd39c; + --irc-colour-78: #ffff9c; + --irc-colour-79: #e2ff9c; + --irc-colour-80: #9cff9c; + --irc-colour-81: #9cffdb; + --irc-colour-82: #9cffff; + --irc-colour-83: #9cd3ff; + --irc-colour-84: #9c9cff; + --irc-colour-85: #dc9cff; + --irc-colour-86: #ff9cff; + --irc-colour-87: #ff94d3; + --irc-colour-88: #000000; + --irc-colour-89: #131313; + --irc-colour-90: #282828; + --irc-colour-91: #363636; + --irc-colour-92: #4d4d4d; + --irc-colour-93: #656565; + --irc-colour-94: #818181; + --irc-colour-95: #9f9f9f; + --irc-colour-96: #bcbcbc; + --irc-colour-97: #e2e2e2; + --irc-colour-98: #ffffff; +} + +/* IRC COLOURS 0/98 */ +.irc-bg-colour-0 { background-color: #fff; } +.irc-bg-colour-1 { background-color: #000; } +.irc-bg-colour-2 { background-color: #00f; } +.irc-bg-colour-3 { background-color: #0f0; } +.irc-bg-colour-4 { background-color: #ff5959; } +.irc-bg-colour-5 { background-color: #743a00; } +.irc-bg-colour-6 { background-color: #a500ff; } +.irc-bg-colour-7 { background-color: #ff8c00; } +.irc-bg-colour-8 { background-color: #ff0; } +.irc-bg-colour-9 { background-color: #6fff6f; } +.irc-bg-colour-10 { background-color: #0cc; } +.irc-bg-colour-11 { background-color: #6dffff; } +.irc-bg-colour-12 { background-color: #59b4ff; } +.irc-bg-colour-13 { background-color: #f0f; } +.irc-bg-colour-14 { background-color: #a6a6a6; } +.irc-bg-colour-15 { background-color: #e2e2e2; } +.irc-bg-colour-16 { background-color: #470000; } +.irc-bg-colour-17 { background-color: #472100; } +.irc-bg-colour-18 { background-color: #474700; } +.irc-bg-colour-19 { background-color: #324700; } +.irc-bg-colour-20 { background-color: #004700; } +.irc-bg-colour-21 { background-color: #00472c; } +.irc-bg-colour-22 { background-color: #004747; } +.irc-bg-colour-23 { background-color: #002747; } +.irc-bg-colour-24 { background-color: #000047; } +.irc-bg-colour-25 { background-color: #2e0047; } +.irc-bg-colour-26 { background-color: #470047; } +.irc-bg-colour-27 { background-color: #47002a; } +.irc-bg-colour-28 { background-color: #740000; } +.irc-bg-colour-29 { background-color: #743a00; } +.irc-bg-colour-30 { background-color: #747400; } +.irc-bg-colour-31 { background-color: #517400; } +.irc-bg-colour-32 { background-color: #007400; } +.irc-bg-colour-33 { background-color: #007449; } +.irc-bg-colour-34 { background-color: #007474; } +.irc-bg-colour-35 { background-color: #004074; } +.irc-bg-colour-36 { background-color: #000074; } +.irc-bg-colour-37 { background-color: #4b0074; } +.irc-bg-colour-38 { background-color: #740074; } +.irc-bg-colour-39 { background-color: #740045; } +.irc-bg-colour-40 { background-color: #b50000; } +.irc-bg-colour-41 { background-color: #b56300; } +.irc-bg-colour-42 { background-color: #b5b500; } +.irc-bg-colour-43 { background-color: #7db500; } +.irc-bg-colour-44 { background-color: #00b500; } +.irc-bg-colour-45 { background-color: #00b571; } +.irc-bg-colour-46 { background-color: #00b5b5; } +.irc-bg-colour-47 { background-color: #0063b5; } +.irc-bg-colour-48 { background-color: #0000b5; } +.irc-bg-colour-49 { background-color: #7500b5; } +.irc-bg-colour-50 { background-color: #b500b5; } +.irc-bg-colour-51 { background-color: #b5006b; } +.irc-bg-colour-52 { background-color: #ff0000; } +.irc-bg-colour-53 { background-color: #ff8c00; } +.irc-bg-colour-54 { background-color: #ffff00; } +.irc-bg-colour-55 { background-color: #b2ff00; } +.irc-bg-colour-56 { background-color: #00ff00; } +.irc-bg-colour-57 { background-color: #00ffa0; } +.irc-bg-colour-58 { background-color: #00ffff; } +.irc-bg-colour-59 { background-color: #008cff; } +.irc-bg-colour-60 { background-color: #0000ff; } +.irc-bg-colour-61 { background-color: #a500ff; } +.irc-bg-colour-62 { background-color: #ff00ff; } +.irc-bg-colour-63 { background-color: #ff0098; } +.irc-bg-colour-64 { background-color: #ff5959; } +.irc-bg-colour-65 { background-color: #ffb459; } +.irc-bg-colour-66 { background-color: #ffff71; } +.irc-bg-colour-67 { background-color: #cfff60; } +.irc-bg-colour-68 { background-color: #6fff6f; } +.irc-bg-colour-69 { background-color: #65ffc9; } +.irc-bg-colour-70 { background-color: #6dffff; } +.irc-bg-colour-71 { background-color: #59b4ff; } +.irc-bg-colour-72 { background-color: #5959ff; } +.irc-bg-colour-73 { background-color: #c459ff; } +.irc-bg-colour-74 { background-color: #ff66ff; } +.irc-bg-colour-75 { background-color: #ff59bc; } +.irc-bg-colour-76 { background-color: #ff9c9c; } +.irc-bg-colour-77 { background-color: #ffd39c; } +.irc-bg-colour-78 { background-color: #ffff9c; } +.irc-bg-colour-79 { background-color: #e2ff9c; } +.irc-bg-colour-80 { background-color: #9cff9c; } +.irc-bg-colour-81 { background-color: #9cffdb; } +.irc-bg-colour-82 { background-color: #9cffff; } +.irc-bg-colour-83 { background-color: #9cd3ff; } +.irc-bg-colour-84 { background-color: #9c9cff; } +.irc-bg-colour-85 { background-color: #dc9cff; } +.irc-bg-colour-86 { background-color: #ff9cff; } +.irc-bg-colour-87 { background-color: #ff94d3; } +.irc-bg-colour-88 { background-color: #000000; } +.irc-bg-colour-89 { background-color: #131313; } +.irc-bg-colour-90 { background-color: #282828; } +.irc-bg-colour-91 { background-color: #363636; } +.irc-bg-colour-92 { background-color: #4d4d4d; } +.irc-bg-colour-93 { background-color: #656565; } +.irc-bg-colour-94 { background-color: #818181; } +.irc-bg-colour-95 { background-color: #9f9f9f; } +.irc-bg-colour-96 { background-color: #bcbcbc; } +.irc-bg-colour-97 { background-color: #e2e2e2; } +.irc-bg-colour-98 { background-color: #ffffff; } + +.irc-fg-colour-0 { color: #fff; } +.irc-fg-colour-1 { color: #000; } +.irc-fg-colour-2 { color: #00f; } +.irc-fg-colour-3 { color: #0f0; } +.irc-fg-colour-4 { color: #ff5959; } +.irc-fg-colour-5 { color: #743a00; } +.irc-fg-colour-6 { color: #a500ff; } +.irc-fg-colour-7 { color: #ff8c00; } +.irc-fg-colour-8 { color: #ff0; } +.irc-fg-colour-9 { color: #6fff6f; } +.irc-fg-colour-10 { color: #0cc; } +.irc-fg-colour-11 { color: #6dffff; } +.irc-fg-colour-12 { color: #59b4ff; } +.irc-fg-colour-13 { color: #f0f; } +.irc-fg-colour-14 { color: #a6a6a6; } +.irc-fg-colour-15 { color: #e2e2e2; } +.irc-fg-colour-16 { color: #470000; } +.irc-fg-colour-17 { color: #472100; } +.irc-fg-colour-18 { color: #474700; } +.irc-fg-colour-19 { color: #324700; } +.irc-fg-colour-20 { color: #004700; } +.irc-fg-colour-21 { color: #00472c; } +.irc-fg-colour-22 { color: #004747; } +.irc-fg-colour-23 { color: #002747; } +.irc-fg-colour-24 { color: #000047; } +.irc-fg-colour-25 { color: #2e0047; } +.irc-fg-colour-26 { color: #470047; } +.irc-fg-colour-27 { color: #47002a; } +.irc-fg-colour-28 { color: #740000; } +.irc-fg-colour-29 { color: #743a00; } +.irc-fg-colour-30 { color: #747400; } +.irc-fg-colour-31 { color: #517400; } +.irc-fg-colour-32 { color: #007400; } +.irc-fg-colour-33 { color: #007449; } +.irc-fg-colour-34 { color: #007474; } +.irc-fg-colour-35 { color: #004074; } +.irc-fg-colour-36 { color: #000074; } +.irc-fg-colour-37 { color: #4b0074; } +.irc-fg-colour-38 { color: #740074; } +.irc-fg-colour-39 { color: #740045; } +.irc-fg-colour-40 { color: #b50000; } +.irc-fg-colour-41 { color: #b56300; } +.irc-fg-colour-42 { color: #b5b500; } +.irc-fg-colour-43 { color: #7db500; } +.irc-fg-colour-44 { color: #00b500; } +.irc-fg-colour-45 { color: #00b571; } +.irc-fg-colour-46 { color: #00b5b5; } +.irc-fg-colour-47 { color: #0063b5; } +.irc-fg-colour-48 { color: #0000b5; } +.irc-fg-colour-49 { color: #7500b5; } +.irc-fg-colour-50 { color: #b500b5; } +.irc-fg-colour-51 { color: #b5006b; } +.irc-fg-colour-52 { color: #ff0000; } +.irc-fg-colour-53 { color: #ff8c00; } +.irc-fg-colour-54 { color: #ffff00; } +.irc-fg-colour-55 { color: #b2ff00; } +.irc-fg-colour-56 { color: #00ff00; } +.irc-fg-colour-57 { color: #00ffa0; } +.irc-fg-colour-58 { color: #00ffff; } +.irc-fg-colour-59 { color: #008cff; } +.irc-fg-colour-60 { color: #0000ff; } +.irc-fg-colour-61 { color: #a500ff; } +.irc-fg-colour-62 { color: #ff00ff; } +.irc-fg-colour-63 { color: #ff0098; } +.irc-fg-colour-64 { color: #ff5959; } +.irc-fg-colour-65 { color: #ffb459; } +.irc-fg-colour-66 { color: #ffff71; } +.irc-fg-colour-67 { color: #cfff60; } +.irc-fg-colour-68 { color: #6fff6f; } +.irc-fg-colour-69 { color: #65ffc9; } +.irc-fg-colour-70 { color: #6dffff; } +.irc-fg-colour-71 { color: #59b4ff; } +.irc-fg-colour-72 { color: #5959ff; } +.irc-fg-colour-73 { color: #c459ff; } +.irc-fg-colour-74 { color: #ff66ff; } +.irc-fg-colour-75 { color: #ff59bc; } +.irc-fg-colour-76 { color: #ff9c9c; } +.irc-fg-colour-77 { color: #ffd39c; } +.irc-fg-colour-78 { color: #ffff9c; } +.irc-fg-colour-79 { color: #e2ff9c; } +.irc-fg-colour-80 { color: #9cff9c; } +.irc-fg-colour-81 { color: #9cffdb; } +.irc-fg-colour-82 { color: #9cffff; } +.irc-fg-colour-83 { color: #9cd3ff; } +.irc-fg-colour-84 { color: #9c9cff; } +.irc-fg-colour-85 { color: #dc9cff; } +.irc-fg-colour-86 { color: #ff9cff; } +.irc-fg-colour-87 { color: #ff94d3; } +.irc-fg-colour-88 { color: #000000; } +.irc-fg-colour-89 { color: #131313; } +.irc-fg-colour-90 { color: #282828; } +.irc-fg-colour-91 { color: #363636; } +.irc-fg-colour-92 { color: #4d4d4d; } +.irc-fg-colour-93 { color: #656565; } +.irc-fg-colour-94 { color: #818181; } +.irc-fg-colour-95 { color: #9f9f9f; } +.irc-fg-colour-96 { color: #bcbcbc; } +.irc-fg-colour-97 { color: #e2e2e2; } +.irc-fg-colour-98 { color: #ffffff; } diff --git a/tests/unit/Html2Irc.spec.js b/tests/unit/Html2Irc.spec.js new file mode 100644 index 0000000000..46ae2ee6b4 --- /dev/null +++ b/tests/unit/Html2Irc.spec.js @@ -0,0 +1,37 @@ +import { html2irc } from '@/helpers/Html2Irc'; + +const tests = [ + { + html: '

testtesttesttesttesttest testtest

', + irc: 'test\x0304test\x02test\x1Dtest\x1Ftest\x0302test :) test\x0Ftest', + }, + { + html: '

testtesttesttesttest

', + irc: 'test\x0304test\x02test\x03test\x0Ftest', + }, + { + html: '

test,02test03test

', + irc: '\x1Dtest\x0304\u2008,02test\x03\u200803test', + }, + { + html: '

testtesttesttest

', + irc: '\x0304,01test\x1Etest\x03testtest', + }, + { + html: '

testtesttest

', + irc: '\x1Ftest\x1Etest\x1Ftest', + }, + { + html: '

testtesttest

', + irc: '\x0304,01\x02test\x0308test\x0308test', + }, +]; + +describe('Html2Irc.js', () => { + it('should output valid irc codes', () => { + tests.forEach((test) => { + const irc = html2irc(test.html); + expect(test.irc).toEqual(irc); + }); + }); +}); diff --git a/yarn.lock b/yarn.lock index df05ff52b2..86bf034e5f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1931,6 +1931,117 @@ __metadata: languageName: node linkType: hard +"@lexical/clipboard@npm:0.20.0": + version: 0.20.0 + resolution: "@lexical/clipboard@npm:0.20.0" + dependencies: + "@lexical/html": "npm:0.20.0" + "@lexical/list": "npm:0.20.0" + "@lexical/selection": "npm:0.20.0" + "@lexical/utils": "npm:0.20.0" + lexical: "npm:0.20.0" + checksum: 10c0/7f69a9db79c14c2ff87e50d4f5fa94bc3abc65e1e025e11e9961395ce5acfd5c3ef85f86717d6db3070d66e6b0763bf68c02a59e539aa3f8824ce5657f5fbb12 + languageName: node + linkType: hard + +"@lexical/code@npm:^0.20.0": + version: 0.20.0 + resolution: "@lexical/code@npm:0.20.0" + dependencies: + "@lexical/utils": "npm:0.20.0" + lexical: "npm:0.20.0" + prismjs: "npm:^1.27.0" + checksum: 10c0/82b0396ec0223dfd2c05c75a9b2eb895ea1d905a5d6fef9d611b73785c40dd431c2aa94de6ef6321627cd2b1f9dbb8356df617b76f617c4fe307a91347c74e9d + languageName: node + linkType: hard + +"@lexical/history@npm:^0.20.0": + version: 0.20.0 + resolution: "@lexical/history@npm:0.20.0" + dependencies: + "@lexical/utils": "npm:0.20.0" + lexical: "npm:0.20.0" + checksum: 10c0/924518ac01cee53ad5c172ac39013e82b845be522c75bc2366a3090c071770a5bfe3fb595e96f8a7124cad236e0f6203bfb8492b19ea3e12ee287a2f607d0ab1 + languageName: node + linkType: hard + +"@lexical/html@npm:0.20.0, @lexical/html@npm:^0.20.0": + version: 0.20.0 + resolution: "@lexical/html@npm:0.20.0" + dependencies: + "@lexical/selection": "npm:0.20.0" + "@lexical/utils": "npm:0.20.0" + lexical: "npm:0.20.0" + checksum: 10c0/a64df25fa6cd014a0ee3e04a9b16d2f843a2e182bbc11054aff33108b2df24c057545d40dcbe350f62ce8b7e69d0c243f2361e02c965b84a00f3ea98468e5e1d + languageName: node + linkType: hard + +"@lexical/list@npm:0.20.0": + version: 0.20.0 + resolution: "@lexical/list@npm:0.20.0" + dependencies: + "@lexical/utils": "npm:0.20.0" + lexical: "npm:0.20.0" + checksum: 10c0/4f9861262988fb49bf6bc1f8e8b362102de0466435f573fc5a749b5fb79ffed2b9272987a14d59f53f430332136bd80c5b01c0da407711821100c13b4914b5a2 + languageName: node + linkType: hard + +"@lexical/plain-text@npm:^0.20.0": + version: 0.20.0 + resolution: "@lexical/plain-text@npm:0.20.0" + dependencies: + "@lexical/clipboard": "npm:0.20.0" + "@lexical/selection": "npm:0.20.0" + "@lexical/utils": "npm:0.20.0" + lexical: "npm:0.20.0" + checksum: 10c0/04f0c1cd8517d63c5f05ccfa8c1a6f98df251431712d654077fd44ba26eeace514280a68051c9f82e7e6548b452e21e1d034f004102033e762757072e30a0c8d + languageName: node + linkType: hard + +"@lexical/rich-text@npm:^0.20.0": + version: 0.20.0 + resolution: "@lexical/rich-text@npm:0.20.0" + dependencies: + "@lexical/clipboard": "npm:0.20.0" + "@lexical/selection": "npm:0.20.0" + "@lexical/utils": "npm:0.20.0" + lexical: "npm:0.20.0" + checksum: 10c0/56c596d423141d8d53f9fc9e63a721463295889ae461b78a69d80cad61945b5462cbe5fba3aadd47aa5af155cdeee0893f6be44a28e8610de3268a8e475f8505 + languageName: node + linkType: hard + +"@lexical/selection@npm:0.20.0, @lexical/selection@npm:^0.20.0": + version: 0.20.0 + resolution: "@lexical/selection@npm:0.20.0" + dependencies: + lexical: "npm:0.20.0" + checksum: 10c0/c43f1a7a0afadc87539decc1da1902bd508d00f6999173eef15491e2d7e6d8ba152bcb037b21385bef9a9d0a3a4c909d3220ee56a70b3e67f7745b7bada3c72b + languageName: node + linkType: hard + +"@lexical/table@npm:0.20.0": + version: 0.20.0 + resolution: "@lexical/table@npm:0.20.0" + dependencies: + "@lexical/clipboard": "npm:0.20.0" + "@lexical/utils": "npm:0.20.0" + lexical: "npm:0.20.0" + checksum: 10c0/1c848c514603f08e821f9a320fc73d4b9df99810d6db3ff16f36f8d37152003bfa16f9694eb87dc9f006a08084ff953e0146bf0bcc84c1e301f2064e4712b646 + languageName: node + linkType: hard + +"@lexical/utils@npm:0.20.0, @lexical/utils@npm:^0.20.0": + version: 0.20.0 + resolution: "@lexical/utils@npm:0.20.0" + dependencies: + "@lexical/list": "npm:0.20.0" + "@lexical/selection": "npm:0.20.0" + "@lexical/table": "npm:0.20.0" + lexical: "npm:0.20.0" + checksum: 10c0/1e451ebf9f9f86ee7b5f4f034b4260a10aedb29a58546076f1dedc2b3b74d2f3fd0e01c7533108cfb13eb392b8ba875e200f1db60a9f2dc55871c89bef33dc8d + languageName: node + linkType: hard + "@nicolo-ribaudo/eslint-scope-5-internals@npm:5.1.1-v1": version: 5.1.1-v1 resolution: "@nicolo-ribaudo/eslint-scope-5-internals@npm:5.1.1-v1" @@ -8565,6 +8676,13 @@ __metadata: "@fortawesome/free-solid-svg-icons": "npm:^6.7.1" "@fortawesome/vue-fontawesome": "npm:^3.0.8" "@kiwiirc/eslint-plugin": ./build/plugins/eslint-rules + "@lexical/code": "npm:^0.20.0" + "@lexical/history": "npm:^0.20.0" + "@lexical/html": "npm:^0.20.0" + "@lexical/plain-text": "npm:^0.20.0" + "@lexical/rich-text": "npm:^0.20.0" + "@lexical/selection": "npm:^0.20.0" + "@lexical/utils": "npm:^0.20.0" "@soda/friendly-errors-webpack-plugin": "npm:^1.8.1" "@stylistic/stylelint-plugin": "npm:^2.1.3" "@vue/compiler-sfc": "npm:^3.5.13" @@ -8612,6 +8730,7 @@ __metadata: json5: "npm:^2.2.3" less: "npm:^4.2.0" less-loader: "npm:^12.2.0" + lexical: "npm:^0.20.0" lodash: "npm:^4.17.21" mini-css-extract-plugin: "npm:^2.9.2" murmurhash3js: "npm:^3.0.1" @@ -8790,6 +8909,13 @@ __metadata: languageName: node linkType: hard +"lexical@npm:0.20.0, lexical@npm:^0.20.0": + version: 0.20.0 + resolution: "lexical@npm:0.20.0" + checksum: 10c0/f1dc5b0b9ffec64abee2b322688f0525944c80da0b60a3687e647429523ac872d80a18d521bd0d149b91515472f3c57b5c8ca154775f00971d87d868659c7ba5 + languageName: node + linkType: hard + "lilconfig@npm:^3.1.2": version: 3.1.2 resolution: "lilconfig@npm:3.1.2" @@ -11006,6 +11132,13 @@ __metadata: languageName: node linkType: hard +"prismjs@npm:^1.27.0": + version: 1.29.0 + resolution: "prismjs@npm:1.29.0" + checksum: 10c0/d906c4c4d01b446db549b4f57f72d5d7e6ccaca04ecc670fb85cea4d4b1acc1283e945a9cbc3d81819084a699b382f970e02f9d1378e14af9808d366d9ed7ec6 + languageName: node + linkType: hard + "proc-log@npm:^4.1.0, proc-log@npm:^4.2.0": version: 4.2.0 resolution: "proc-log@npm:4.2.0"