diff --git a/.pnp.cjs b/.pnp.cjs index c1a5a3a5e..cd7f836b1 100644 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -47,6 +47,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@percy/cypress", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:3.1.2"],\ ["@popperjs/core", "npm:2.11.6"],\ ["@rollup/pluginutils", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:5.0.2"],\ + ["@twuni/emojify", "npm:1.0.2"],\ ["@vitejs/plugin-vue", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:3.1.2"],\ ["@vue/test-utils", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:2.1.0"],\ ["bootstrap", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:5.2.2"],\ @@ -2226,6 +2227,15 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ + ["@twuni/emojify", [\ + ["npm:1.0.2", {\ + "packageLocation": "./.yarn/cache/@twuni-emojify-npm-1.0.2-a45d6eb0a7-0044c83b05.zip/node_modules/@twuni/emojify/",\ + "packageDependencies": [\ + ["@twuni/emojify", "npm:1.0.2"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["@types/estree", [\ ["npm:1.0.0", {\ "packageLocation": "./.yarn/cache/@types-estree-npm-1.0.0-eddde5b631-910d97fb70.zip/node_modules/@types/estree/",\ @@ -8580,6 +8590,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@percy/cypress", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:3.1.2"],\ ["@popperjs/core", "npm:2.11.6"],\ ["@rollup/pluginutils", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:5.0.2"],\ + ["@twuni/emojify", "npm:1.0.2"],\ ["@vitejs/plugin-vue", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:3.1.2"],\ ["@vue/test-utils", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:2.1.0"],\ ["bootstrap", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:5.2.2"],\ diff --git a/.yarn/cache/@twuni-emojify-npm-1.0.2-a45d6eb0a7-0044c83b05.zip b/.yarn/cache/@twuni-emojify-npm-1.0.2-a45d6eb0a7-0044c83b05.zip new file mode 100644 index 000000000..4bcf04a90 Binary files /dev/null and b/.yarn/cache/@twuni-emojify-npm-1.0.2-a45d6eb0a7-0044c83b05.zip differ diff --git a/client/components/ChatLog.vue b/client/components/ChatLog.vue index 779734f91..d393b1866 100644 --- a/client/components/ChatLog.vue +++ b/client/components/ChatLog.vue @@ -22,6 +22,8 @@ <script setup> import { onMounted, reactive } from 'vue' import { DateTime } from 'luxon' +import { emojify } from '@twuni/emojify' +import uniq from 'lodash-es/uniq' import { NTimeline, NTimelineItem @@ -61,9 +63,12 @@ const colors = [ onMounted(() => { const authorColors = {} + // Get chat log data from embedded json tag const chatLog = JSON.parse(document.getElementById(`${props.componentId}-data`).textContent || '[]') if (chatLog.length > 0) { + const authorNames = uniq(chatLog.map(l => l.author)) + let idx = 1 let colorIdx = 0 for (const logItem of chatLog) { @@ -75,12 +80,22 @@ onMounted(() => { colorIdx = 0 } } + + // -> Format text + let txt = emojify(logItem.text) + if (txt.indexOf('@') >= 0) { + for (const authorName of authorNames) { + txt = txt.replaceAll(`@${authorName}`, `<span class="user-mention">${authorName}</span>`) + } + } + txt = txt.replaceAll('href="/user_uploads/', 'href="https://zulip.ietf.org/user_uploads/') + // -> Generate log item state.items.push({ id: `logitem-${idx}`, color: authorColors[logItem.author], author: logItem.author, - text: logItem.text, + text: txt, time: DateTime.fromISO(logItem.time).toFormat('dd LLLL yyyy \'at\' HH:mm:ss a ZZZZ') }) idx++ @@ -90,9 +105,58 @@ onMounted(() => { </script> <style lang="scss"> +@import '../shared/colors.scss'; + .chatlog { - .n-timeline-item-content__content > div > p { - margin-bottom: 0; + .n-timeline-item-content__content { + > div > p:last-child { + margin-bottom: 0; + } + + blockquote { + background-color: $gray-100; + border-radius: 5px; + padding: 8px; + margin-top: -8px; + + > p:last-child { + margin-bottom: 0; + } + } + + .message_inline_image { + display: none; + } + + // Manual user mention + .user-mention { + display: inline-block; + padding: 1px 5px; + background-color: rgba($purple, .05); + color: $purple; + font-weight: 500; + border-radius: 4px; + + > .user-mention { + padding: 0; + + &::before { + display: none; + } + } + + &::before { + content: '@'; + } + } + + // User reply mention + .user-mention + a { + text-decoration: none; + color: $purple; + font-style: italic; + cursor: default; + } } } </style> diff --git a/client/shared/colors.scss b/client/shared/colors.scss new file mode 100644 index 000000000..e34c459e9 --- /dev/null +++ b/client/shared/colors.scss @@ -0,0 +1,139 @@ +// Bootstrap 5 Color Variables +// Extracted from https://github.com/twbs/bootstrap/blob/main/scss/_variables.scss +// Copyright (c) 2011-2022 Twitter, Inc. +// Copyright (c) 2011-2022 The Bootstrap Authors + +// Tint a color: mix a color with white +@function tint-color($color, $weight) { + @return mix(white, $color, $weight); +} + +// Shade a color: mix a color with black +@function shade-color($color, $weight) { + @return mix(black, $color, $weight); +} + +// Color system + +$white: #fff !default; +$gray-100: #f8f9fa !default; +$gray-200: #e9ecef !default; +$gray-300: #dee2e6 !default; +$gray-400: #ced4da !default; +$gray-500: #adb5bd !default; +$gray-600: #6c757d !default; +$gray-700: #495057 !default; +$gray-800: #343a40 !default; +$gray-900: #212529 !default; +$black: #000 !default; + +$blue: #0d6efd !default; +$indigo: #6610f2 !default; +$purple: #6f42c1 !default; +$pink: #d63384 !default; +$red: #dc3545 !default; +$orange: #fd7e14 !default; +$yellow: #ffc107 !default; +$green: #198754 !default; +$teal: #20c997 !default; +$cyan: #0dcaf0 !default; + +$blue-100: tint-color($blue, 80%) !default; +$blue-200: tint-color($blue, 60%) !default; +$blue-300: tint-color($blue, 40%) !default; +$blue-400: tint-color($blue, 20%) !default; +$blue-500: $blue !default; +$blue-600: shade-color($blue, 20%) !default; +$blue-700: shade-color($blue, 40%) !default; +$blue-800: shade-color($blue, 60%) !default; +$blue-900: shade-color($blue, 80%) !default; + +$indigo-100: tint-color($indigo, 80%) !default; +$indigo-200: tint-color($indigo, 60%) !default; +$indigo-300: tint-color($indigo, 40%) !default; +$indigo-400: tint-color($indigo, 20%) !default; +$indigo-500: $indigo !default; +$indigo-600: shade-color($indigo, 20%) !default; +$indigo-700: shade-color($indigo, 40%) !default; +$indigo-800: shade-color($indigo, 60%) !default; +$indigo-900: shade-color($indigo, 80%) !default; + +$purple-100: tint-color($purple, 80%) !default; +$purple-200: tint-color($purple, 60%) !default; +$purple-300: tint-color($purple, 40%) !default; +$purple-400: tint-color($purple, 20%) !default; +$purple-500: $purple !default; +$purple-600: shade-color($purple, 20%) !default; +$purple-700: shade-color($purple, 40%) !default; +$purple-800: shade-color($purple, 60%) !default; +$purple-900: shade-color($purple, 80%) !default; + +$pink-100: tint-color($pink, 80%) !default; +$pink-200: tint-color($pink, 60%) !default; +$pink-300: tint-color($pink, 40%) !default; +$pink-400: tint-color($pink, 20%) !default; +$pink-500: $pink !default; +$pink-600: shade-color($pink, 20%) !default; +$pink-700: shade-color($pink, 40%) !default; +$pink-800: shade-color($pink, 60%) !default; +$pink-900: shade-color($pink, 80%) !default; + +$red-100: tint-color($red, 80%) !default; +$red-200: tint-color($red, 60%) !default; +$red-300: tint-color($red, 40%) !default; +$red-400: tint-color($red, 20%) !default; +$red-500: $red !default; +$red-600: shade-color($red, 20%) !default; +$red-700: shade-color($red, 40%) !default; +$red-800: shade-color($red, 60%) !default; +$red-900: shade-color($red, 80%) !default; + +$orange-100: tint-color($orange, 80%) !default; +$orange-200: tint-color($orange, 60%) !default; +$orange-300: tint-color($orange, 40%) !default; +$orange-400: tint-color($orange, 20%) !default; +$orange-500: $orange !default; +$orange-600: shade-color($orange, 20%) !default; +$orange-700: shade-color($orange, 40%) !default; +$orange-800: shade-color($orange, 60%) !default; +$orange-900: shade-color($orange, 80%) !default; + +$yellow-100: tint-color($yellow, 80%) !default; +$yellow-200: tint-color($yellow, 60%) !default; +$yellow-300: tint-color($yellow, 40%) !default; +$yellow-400: tint-color($yellow, 20%) !default; +$yellow-500: $yellow !default; +$yellow-600: shade-color($yellow, 20%) !default; +$yellow-700: shade-color($yellow, 40%) !default; +$yellow-800: shade-color($yellow, 60%) !default; +$yellow-900: shade-color($yellow, 80%) !default; + +$green-100: tint-color($green, 80%) !default; +$green-200: tint-color($green, 60%) !default; +$green-300: tint-color($green, 40%) !default; +$green-400: tint-color($green, 20%) !default; +$green-500: $green !default; +$green-600: shade-color($green, 20%) !default; +$green-700: shade-color($green, 40%) !default; +$green-800: shade-color($green, 60%) !default; +$green-900: shade-color($green, 80%) !default; + +$teal-100: tint-color($teal, 80%) !default; +$teal-200: tint-color($teal, 60%) !default; +$teal-300: tint-color($teal, 40%) !default; +$teal-400: tint-color($teal, 20%) !default; +$teal-500: $teal !default; +$teal-600: shade-color($teal, 20%) !default; +$teal-700: shade-color($teal, 40%) !default; +$teal-800: shade-color($teal, 60%) !default; +$teal-900: shade-color($teal, 80%) !default; + +$cyan-100: tint-color($cyan, 80%) !default; +$cyan-200: tint-color($cyan, 60%) !default; +$cyan-300: tint-color($cyan, 40%) !default; +$cyan-400: tint-color($cyan, 20%) !default; +$cyan-500: $cyan !default; +$cyan-600: shade-color($cyan, 20%) !default; +$cyan-700: shade-color($cyan, 40%) !default; +$cyan-800: shade-color($cyan, 60%) !default; +$cyan-900: shade-color($cyan, 80%) !default; diff --git a/docker/scripts/app-rsync-extras.sh b/docker/scripts/app-rsync-extras.sh index ef6966224..b99082b53 100755 --- a/docker/scripts/app-rsync-extras.sh +++ b/docker/scripts/app-rsync-extras.sh @@ -43,7 +43,6 @@ cat << EOF > "$EXCLUDE" *.doc *.exe *.html -*.json *.mib *.new *.p7s diff --git a/package.json b/package.json index 98fe90225..d96c37b0a 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "@fullcalendar/timegrid": "5.11.3", "@fullcalendar/vue3": "5.11.2", "@popperjs/core": "2.11.6", + "@twuni/emojify": "1.0.2", "bootstrap": "5.2.2", "bootstrap-icons": "1.9.1", "browser-fs-access": "0.31.1", diff --git a/yarn.lock b/yarn.lock index 66ed75d83..dda4f64ca 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1719,6 +1719,13 @@ __metadata: languageName: node linkType: hard +"@twuni/emojify@npm:1.0.2": + version: 1.0.2 + resolution: "@twuni/emojify@npm:1.0.2" + checksum: 0044c83b0589767dae1c1bb933cd56f2e5031a438f0fc993413e4cc229080e29c275cdd836be33ee02ddd59a5d1d6223a718685650f11ecfffc69c881c072152 + languageName: node + linkType: hard + "@types/estree@npm:^1.0.0": version: 1.0.0 resolution: "@types/estree@npm:1.0.0" @@ -7138,6 +7145,7 @@ browserlist@latest: "@percy/cypress": 3.1.2 "@popperjs/core": 2.11.6 "@rollup/pluginutils": 5.0.2 + "@twuni/emojify": 1.0.2 "@vitejs/plugin-vue": 3.1.2 "@vue/test-utils": 2.1.0 bootstrap: 5.2.2