1import { reactionLabels } from "lib/message_reaction_markup" 2 3// The long-press menu is rendered twice: in messages/_message_actions_menu 4// .html.erb for the server pass, and here for messages that arrive over the 5// socket. Labels come from the same hidden element the reaction row reads, so 6// a socket message stays in the viewer's language. 7// 8// The socket-built <li> carries only the message-reactions and message-actions 9// controllers, so this menu offers react, copy and save. Correct needs markup 10// that only the server pass renders; it appears on the next load. 11const FALLBACK_LABELS = { 12 menu: "Message actions", 13 reply: "Reply", 14 copy: "Copy", 15 copied: "Copied" 16} 17 18export function menuLabels() { 19 const element = document.getElementById("message-reaction-labels") 20 if (!element) return { ...FALLBACK_LABELS } 21 22 return { 23 menu: element.dataset.menuLabel || FALLBACK_LABELS.menu, 24 reply: element.dataset.replyLabel || FALLBACK_LABELS.reply, 25 copy: element.dataset.copyLabel || FALLBACK_LABELS.copy, 26 copied: element.dataset.copiedLabel || FALLBACK_LABELS.copied 27 } 28} 29 30export function buildMessageMenu(messageId, { saveForm = null, reply = true } = {}) { 31 const labels = { ...reactionLabels(), ...menuLabels() } 32 33 const overlay = document.createElement("div") 34 overlay.className = "kb-message-menu" 35 overlay.dataset.messageActionsTarget = "menu" 36 overlay.hidden = true 37 38 const card = document.createElement("div") 39 card.className = "kb-message-menu__card kb-message-menu__card--closed" 40 card.dataset.messageActionsTarget = "card" 41 card.dataset.messageMenu = messageId 42 card.setAttribute("role", "menu") 43 card.setAttribute("aria-label", labels.menu) 44 45 const reactions = document.createElement("div") 46 reactions.className = "kb-message-menu__reactions" 47 reactions.setAttribute("role", "group") 48 reactions.setAttribute("aria-label", labels.picker) 49 50 labels.emojis.forEach((emoji) => { 51 const option = document.createElement("button") 52 option.type = "button" 53 option.className = "kb-message-menu__emoji" 54 option.dataset.action = "message-reactions#select message-actions#close" 55 option.dataset.messageReactionEmoji = emoji 56 option.setAttribute("role", "menuitem") 57 option.setAttribute("aria-pressed", "false") 58 option.setAttribute("aria-label", labels.reactWith.replace("%{emoji}", emoji)) 59 60 const glyph = document.createElement("span") 61 glyph.setAttribute("aria-hidden", "true") 62 glyph.textContent = emoji 63 option.appendChild(glyph) 64 reactions.appendChild(option) 65 }) 66 67 const items = document.createElement("div") 68 items.className = "kb-message-menu__items" 69 70 if (reply) { 71 const replyButton = document.createElement("button") 72 replyButton.type = "button" 73 replyButton.className = "kb-message-menu__item" 74 replyButton.dataset.action = "message-actions#reply" 75 replyButton.dataset.messageMenuAction = "reply" 76 replyButton.setAttribute("role", "menuitem") 77 replyButton.textContent = labels.reply 78 items.appendChild(replyButton) 79 } 80 81 const copy = document.createElement("button") 82 copy.type = "button" 83 copy.className = "kb-message-menu__item" 84 copy.dataset.action = "message-actions#copy" 85 copy.dataset.messageMenuAction = "copy" 86 copy.setAttribute("role", "menuitem") 87 copy.textContent = labels.copy 88 items.appendChild(copy) 89 90 if (saveForm) items.appendChild(saveForm) 91 92 card.append(reactions, items) 93 overlay.appendChild(card) 94 return overlay 95} 96 97// Keyboard and screen-reader path into the menu; hidden until focused. 98export function buildMenuTrigger() { 99 const trigger = document.createElement("button") 100 trigger.type = "button" 101 trigger.className = "kb-message-menu-trigger text-[11px] font-semibold" 102 trigger.style.color = "var(--kb-caption)" 103 trigger.dataset.messageActionsTarget = "trigger" 104 trigger.dataset.action = "message-actions#open" 105 trigger.setAttribute("aria-haspopup", "menu") 106 trigger.setAttribute("aria-expanded", "false") 107 trigger.textContent = menuLabels().menu 108 return trigger 109} 110 111export const BUBBLE_PRESS_ACTIONS = [ 112 "touchstart->message-actions#pressStart", 113 "touchend->message-actions#pressEnd", 114 "touchmove->message-actions#pressMove", 115 "touchcancel->message-actions#pressEnd", 116 "contextmenu->message-actions#open" 117].join(" ")
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.