mirror of
https://github.com/basecamp/once-campfire.git
synced 2026-08-29 10:02:37 +09:00
Replace Trix with Lexxy
This commit is contained in:
@@ -44,21 +44,20 @@ export default class extends Controller {
|
||||
}
|
||||
|
||||
replaceMessageContent(content) {
|
||||
const editor = this.textTarget.editor
|
||||
|
||||
editor.recordUndoEntry("Format reply")
|
||||
editor.setSelectedRange([0, editor.getDocument().toString().length])
|
||||
editor.deleteInDirection("forward")
|
||||
editor.insertHTML(content)
|
||||
editor.setSelectedRange([editor.getDocument().toString().length - 1])
|
||||
this.textTarget.value = content
|
||||
this.textTarget.focus()
|
||||
this.textTarget.selection.placeCursorAtTheEnd()
|
||||
}
|
||||
|
||||
submitByKeyboard(event) {
|
||||
if (event.key != "Enter" || this.textTarget.hasOpenPrompt) return
|
||||
|
||||
const toolbarVisible = this.element.classList.contains(this.toolbarClass)
|
||||
const metaEnter = event.key == "Enter" && (event.metaKey || event.ctrlKey)
|
||||
const plainEnter = event.keyCode == 13 && !event.shiftKey && !event.isComposing
|
||||
const metaEnter = event.metaKey || event.ctrlKey
|
||||
const plainEnter = !event.shiftKey && !event.isComposing
|
||||
|
||||
if (!this.#usingTouchDevice && (metaEnter || (plainEnter && !toolbarVisible))) {
|
||||
event.stopPropagation()
|
||||
this.submit(event)
|
||||
}
|
||||
}
|
||||
@@ -126,7 +125,7 @@ export default class extends Controller {
|
||||
}
|
||||
|
||||
#validInput() {
|
||||
return this.textTarget.textContent.trim().length > 0
|
||||
return !this.textTarget.isBlank
|
||||
}
|
||||
|
||||
async #submitFiles() {
|
||||
|
||||
@@ -89,7 +89,7 @@ export default class extends Controller {
|
||||
}
|
||||
|
||||
async editMyLastMessage() {
|
||||
const editorEmpty = document.querySelector("#composer trix-editor").matches(":empty")
|
||||
const editorEmpty = document.querySelector("#composer lexxy-editor").isBlank
|
||||
|
||||
if (editorEmpty && this.#paginator.upToDate) {
|
||||
this.#myLastMessage?.querySelector(".message__edit-btn")?.click()
|
||||
|
||||
@@ -11,7 +11,7 @@ export default class extends Controller {
|
||||
}
|
||||
|
||||
reply() {
|
||||
const content = `<blockquote>${this.#bodyContent}</blockquote><cite>${this.authorTarget.innerHTML} ${this.#linkToOriginal}</cite><br>`
|
||||
const content = `<blockquote>${this.#bodyContent}</blockquote><cite>${this.authorTarget.innerHTML} ${this.#linkToOriginal}</cite><p><br></p>`
|
||||
this.composerOutlet.replaceMessageContent(content)
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ export default class extends Controller {
|
||||
}
|
||||
|
||||
get #bodyContent() {
|
||||
const body = this.bodyTarget.querySelector(".trix-content").cloneNode(true)
|
||||
const body = this.bodyTarget.querySelector(".lexxy-content").cloneNode(true)
|
||||
return this.#stripMentionAttachments(this.#stripUnfurledAttachments(body)).innerHTML
|
||||
}
|
||||
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
import { Controller } from "@hotwired/stimulus"
|
||||
import MentionsAutocompleteHandler from "lib/autocomplete/mentions_autocomplete_handler"
|
||||
import { debounce } from "helpers/timing_helpers"
|
||||
|
||||
export default class extends Controller {
|
||||
static values = { url: String }
|
||||
|
||||
initialize() {
|
||||
this.handlers = []
|
||||
this.search = debounce(this.search.bind(this), 300)
|
||||
}
|
||||
|
||||
connect() {
|
||||
if (this.element == document.activeElement) {
|
||||
this.#installHandlers()
|
||||
}
|
||||
}
|
||||
|
||||
focus(event) {
|
||||
this.#installHandlers()
|
||||
}
|
||||
|
||||
search(event) {
|
||||
const content = this.editor.getDocument().toString()
|
||||
const position = this.editor.getPosition()
|
||||
this.handlers.forEach(handler => handler.updateWithContentAndPosition(content, position))
|
||||
}
|
||||
|
||||
blur(event) {
|
||||
this.#uninstallHandlers()
|
||||
}
|
||||
|
||||
#installHandlers() {
|
||||
this.#uninstallHandlers()
|
||||
this.handlers = [ new MentionsAutocompleteHandler(this.element, this.urlValue) ]
|
||||
}
|
||||
|
||||
#uninstallHandlers() {
|
||||
this.handlers.forEach(handler => handler.destroy())
|
||||
this.handlers = []
|
||||
}
|
||||
|
||||
get editor() {
|
||||
return this.element.editor
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import { Controller } from "@hotwired/stimulus"
|
||||
import { post } from "@rails/request.js"
|
||||
import { truncateString } from "helpers/string_helpers"
|
||||
import { escapeHTML } from "helpers/dom_helpers"
|
||||
|
||||
const OPENGRAPH_EMBED_CONTENT_TYPE = "application/vnd.actiontext.opengraph-embed"
|
||||
|
||||
const UNFURLED_TWITTER_AVATAR_CSS_CLASS = "cf-twitter-avatar"
|
||||
const TWITTER_AVATAR_URL_PREFIX = "https://pbs.twimg.com/profile_images"
|
||||
|
||||
// Unfurls URLs pasted into the rich text editor into OpenGraph preview attachments
|
||||
export default class extends Controller {
|
||||
#abortController
|
||||
|
||||
disconnect() {
|
||||
this.#abortController?.abort()
|
||||
}
|
||||
|
||||
async unfurl(event) {
|
||||
if (!this.element.permitsAttachmentContentType(OPENGRAPH_EMBED_CONTENT_TYPE)) return
|
||||
|
||||
const { url, insertBelowLink } = event.detail
|
||||
|
||||
const metadata = await this.#fetchOpengraphMetadata(url)
|
||||
|
||||
if (metadata) {
|
||||
insertBelowLink(this.#opengraphEmbedHTML(metadata), { attachment: { contentType: OPENGRAPH_EMBED_CONTENT_TYPE } })
|
||||
}
|
||||
}
|
||||
|
||||
async #fetchOpengraphMetadata(url) {
|
||||
this.#abortController?.abort()
|
||||
this.#abortController = new AbortController()
|
||||
|
||||
try {
|
||||
const response = await post("/unfurl_link", {
|
||||
body: { url },
|
||||
contentType: "application/json",
|
||||
signal: this.#abortController.signal
|
||||
})
|
||||
|
||||
if (response.ok && response.statusCode !== 204) {
|
||||
const { title, url: href, image, description } = await response.json
|
||||
if (title && href) return { title, href, image, description }
|
||||
}
|
||||
} catch {
|
||||
// Ignore aborted or failed requests, like the previous implementation did
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
#opengraphEmbedHTML({ title, href, image, description }) {
|
||||
return `<actiontext-opengraph-embed class="${this.#isTwitterAvatar(image) ? UNFURLED_TWITTER_AVATAR_CSS_CLASS : ""}">
|
||||
<div class="og-embed gap">
|
||||
<div class="og-embed__content">
|
||||
<div class="og-embed__title">
|
||||
<a href="${escapeHTML(href)}" rel="noreferrer" target="_blank">${escapeHTML(truncateString(title, 280))}</a>
|
||||
</div>
|
||||
<div class="og-embed__description">${escapeHTML(truncateString(description, 560))}</div>
|
||||
</div>
|
||||
${image ? `<div class="og-embed__image"><img src="${escapeHTML(image)}" class="image center" alt="" /></div>` : ""}
|
||||
</div>
|
||||
</actiontext-opengraph-embed>`
|
||||
}
|
||||
|
||||
#isTwitterAvatar(image) {
|
||||
return !!image?.startsWith(TWITTER_AVATAR_URL_PREFIX)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user