forked from Mirrors/gomuks
all: use markdown for custom emojis, improve editing
Edits will now use a different HTML -> markdown converter than what is used to generate the body. This allows the plaintext body to have a plain shortcode for custom emojis, while still having the raw data for edits. Additionally, for sent events, the raw input is saved locally, which allows preserving commands and other such things. A future extension may store the raw input in a custom field in the Matrix event to allow lossless edits of messages sent from other clients.
This commit is contained in:
parent
2ff3f9120c
commit
5832a935cf
8 changed files with 75 additions and 10 deletions
2
go.mod
2
go.mod
|
@ -24,7 +24,7 @@ require (
|
|||
golang.org/x/text v0.19.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
maunium.net/go/mauflag v1.0.0
|
||||
maunium.net/go/mautrix v0.21.2-0.20241102103750-013afd06d341
|
||||
maunium.net/go/mautrix v0.21.2-0.20241102114451-83e60efa1558
|
||||
mvdan.cc/xurls/v2 v2.5.0
|
||||
)
|
||||
|
||||
|
|
4
go.sum
4
go.sum
|
@ -89,7 +89,7 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
maunium.net/go/mauflag v1.0.0 h1:YiaRc0tEI3toYtJMRIfjP+jklH45uDHtT80nUamyD4M=
|
||||
maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA=
|
||||
maunium.net/go/mautrix v0.21.2-0.20241102103750-013afd06d341 h1:5oQl6MlXHTlN+MVxmnJbhaQM1+1Lt/o3w4g8CfovQjM=
|
||||
maunium.net/go/mautrix v0.21.2-0.20241102103750-013afd06d341/go.mod h1:sjCZR1R/3NET/WjkcXPL6WpAHlWKku9HjRsdOkbM8Qw=
|
||||
maunium.net/go/mautrix v0.21.2-0.20241102114451-83e60efa1558 h1:GWrBpixB+7hm2vk5Z6JvMoUgAea6ma8Mg4QaO53csYs=
|
||||
maunium.net/go/mautrix v0.21.2-0.20241102114451-83e60efa1558/go.mod h1:b91JuKxF/JkOf0ra/YmhV44Sa/EtOjxkLJANzBJ1frg=
|
||||
mvdan.cc/xurls/v2 v2.5.0 h1:lyBNOm8Wo71UknhUs4QTFUNNMyxy2JEIaKKo0RWOh+8=
|
||||
mvdan.cc/xurls/v2 v2.5.0/go.mod h1:yQgaGQ1rFtJUzkmKiHYSSfuQxqfYmd//X6PxvholpeE=
|
||||
|
|
|
@ -293,6 +293,7 @@ type LocalContent struct {
|
|||
WasPlaintext bool `json:"was_plaintext,omitempty"`
|
||||
BigEmoji bool `json:"big_emoji,omitempty"`
|
||||
HasMath bool `json:"has_math,omitempty"`
|
||||
EditSource string `json:"edit_source,omitempty"`
|
||||
}
|
||||
|
||||
type Event struct {
|
||||
|
|
|
@ -30,10 +30,38 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
rainbowWithHTML = goldmark.New(format.Extensions, goldmark.WithExtensions(mdext.Math), format.HTMLOptions, goldmark.WithExtensions(rainbow.Extension))
|
||||
defaultWithHTML = goldmark.New(format.Extensions, goldmark.WithExtensions(mdext.Math), format.HTMLOptions)
|
||||
rainbowWithHTML = goldmark.New(format.Extensions, goldmark.WithExtensions(mdext.Math, mdext.CustomEmoji), format.HTMLOptions, goldmark.WithExtensions(rainbow.Extension))
|
||||
defaultWithHTML = goldmark.New(format.Extensions, goldmark.WithExtensions(mdext.Math, mdext.CustomEmoji), format.HTMLOptions)
|
||||
)
|
||||
|
||||
var htmlToMarkdownForInput = ptr.Clone(format.MarkdownHTMLParser)
|
||||
|
||||
func init() {
|
||||
htmlToMarkdownForInput.PillConverter = func(displayname, mxid, eventID string, ctx format.Context) string {
|
||||
switch {
|
||||
case len(mxid) == 0, mxid[0] == '@':
|
||||
return fmt.Sprintf("[%s](%s)", displayname, id.UserID(mxid).URI().MatrixToURL())
|
||||
case len(eventID) > 0:
|
||||
return fmt.Sprintf("[%s](%s)", displayname, id.RoomID(mxid).EventURI(id.EventID(eventID)).MatrixToURL())
|
||||
case mxid[0] == '!' && displayname == mxid:
|
||||
return fmt.Sprintf("[%s](%s)", displayname, id.RoomID(mxid).URI().MatrixToURL())
|
||||
case mxid[0] == '#':
|
||||
return fmt.Sprintf("[%s](%s)", displayname, id.RoomAlias(mxid).URI().MatrixToURL())
|
||||
default:
|
||||
return htmlToMarkdownForInput.LinkConverter(displayname, "https://matrix.to/#/"+mxid, ctx)
|
||||
}
|
||||
}
|
||||
htmlToMarkdownForInput.ImageConverter = func(src, alt, title, width, height string, isEmoji bool) string {
|
||||
if isEmoji {
|
||||
return fmt.Sprintf(``, alt, src, title)
|
||||
} else if title != "" {
|
||||
return fmt.Sprintf(``, alt, src, title)
|
||||
} else {
|
||||
return fmt.Sprintf(``, alt, src)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *HiClient) SendMessage(
|
||||
ctx context.Context,
|
||||
roomID id.RoomID,
|
||||
|
@ -44,6 +72,7 @@ func (h *HiClient) SendMessage(
|
|||
) (*database.Event, error) {
|
||||
var content event.MessageEventContent
|
||||
msgType := event.MsgText
|
||||
origText := text
|
||||
if strings.HasPrefix(text, "/me ") {
|
||||
msgType = event.MsgEmote
|
||||
text = strings.TrimPrefix(text, "/me ")
|
||||
|
@ -102,7 +131,7 @@ func (h *HiClient) SendMessage(
|
|||
content.RelatesTo = relatesTo
|
||||
}
|
||||
}
|
||||
return h.Send(ctx, roomID, event.EventMessage, &content)
|
||||
return h.send(ctx, roomID, event.EventMessage, &content, origText)
|
||||
}
|
||||
|
||||
func (h *HiClient) MarkRead(ctx context.Context, roomID id.RoomID, eventID id.EventID, receiptType event.ReceiptType) error {
|
||||
|
@ -153,6 +182,16 @@ func (h *HiClient) Send(
|
|||
roomID id.RoomID,
|
||||
evtType event.Type,
|
||||
content any,
|
||||
) (*database.Event, error) {
|
||||
return h.send(ctx, roomID, evtType, content, "")
|
||||
}
|
||||
|
||||
func (h *HiClient) send(
|
||||
ctx context.Context,
|
||||
roomID id.RoomID,
|
||||
evtType event.Type,
|
||||
content any,
|
||||
overrideEditSource string,
|
||||
) (*database.Event, error) {
|
||||
room, err := h.DB.Room.Get(ctx, roomID)
|
||||
if err != nil {
|
||||
|
@ -193,6 +232,9 @@ func (h *HiClient) Send(
|
|||
var inlineImages []id.ContentURI
|
||||
mautrixEvt := dbEvt.AsRawMautrix()
|
||||
dbEvt.LocalContent, inlineImages = h.calculateLocalContent(ctx, dbEvt, mautrixEvt)
|
||||
if overrideEditSource != "" {
|
||||
dbEvt.LocalContent.EditSource = overrideEditSource
|
||||
}
|
||||
_, err = h.DB.Event.Insert(ctx, dbEvt)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to insert event into database: %w", err)
|
||||
|
|
|
@ -25,6 +25,7 @@ import (
|
|||
"maunium.net/go/mautrix/crypto"
|
||||
"maunium.net/go/mautrix/crypto/olm"
|
||||
"maunium.net/go/mautrix/event"
|
||||
"maunium.net/go/mautrix/format"
|
||||
"maunium.net/go/mautrix/id"
|
||||
"maunium.net/go/mautrix/pushrules"
|
||||
|
||||
|
@ -366,7 +367,7 @@ func (h *HiClient) calculateLocalContent(ctx context.Context, dbEvt *database.Ev
|
|||
content = content.NewContent
|
||||
}
|
||||
if content != nil {
|
||||
var sanitizedHTML string
|
||||
var sanitizedHTML, editSource string
|
||||
var wasPlaintext, hasMath, bigEmoji bool
|
||||
var inlineImages []id.ContentURI
|
||||
if content.Format == event.FormatHTML && content.FormattedBody != "" {
|
||||
|
@ -384,6 +385,16 @@ func (h *HiClient) calculateLocalContent(ctx context.Context, dbEvt *database.Ev
|
|||
}
|
||||
inlineImages = nil
|
||||
}
|
||||
if dbEvt.LocalContent != nil && dbEvt.LocalContent.EditSource != "" {
|
||||
editSource = dbEvt.LocalContent.EditSource
|
||||
} else if evt.Sender == h.Account.UserID {
|
||||
editSource, _ = format.HTMLToMarkdownFull(htmlToMarkdownForInput, content.FormattedBody)
|
||||
if content.MsgType == event.MsgEmote {
|
||||
editSource = "/me " + editSource
|
||||
} else if content.MsgType == event.MsgNotice {
|
||||
editSource = "/notice " + editSource
|
||||
}
|
||||
}
|
||||
} else {
|
||||
hasSpecialCharacters := false
|
||||
for _, char := range content.Body {
|
||||
|
@ -400,6 +411,11 @@ func (h *HiClient) calculateLocalContent(ctx context.Context, dbEvt *database.Ev
|
|||
} else if len(content.Body) < 100 && emojirunes.IsOnlyEmojis(content.Body) {
|
||||
bigEmoji = true
|
||||
}
|
||||
if content.MsgType == event.MsgEmote {
|
||||
editSource = "/me " + content.Body
|
||||
} else if content.MsgType == event.MsgNotice {
|
||||
editSource = "/notice " + content.Body
|
||||
}
|
||||
wasPlaintext = true
|
||||
}
|
||||
return &database.LocalContent{
|
||||
|
@ -408,6 +424,7 @@ func (h *HiClient) calculateLocalContent(ctx context.Context, dbEvt *database.Ev
|
|||
WasPlaintext: wasPlaintext,
|
||||
BigEmoji: bigEmoji,
|
||||
HasMath: hasMath,
|
||||
EditSource: editSource,
|
||||
}, inlineImages
|
||||
}
|
||||
return nil, nil
|
||||
|
@ -597,7 +614,7 @@ func (h *HiClient) processStateAndTimeline(
|
|||
}
|
||||
processNewEvent := func(evt *event.Event, isTimeline, isUnread bool) (database.EventRowID, error) {
|
||||
evt.RoomID = room.ID
|
||||
dbEvt, err := h.processEvent(ctx, evt, summary, decryptionQueue, false)
|
||||
dbEvt, err := h.processEvent(ctx, evt, summary, decryptionQueue, evt.Unsigned.TransactionID != "")
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
|
|
@ -81,6 +81,7 @@ export enum UnreadType {
|
|||
|
||||
export interface LocalContent {
|
||||
sanitized_html?: TrustedHTML
|
||||
edit_source?: string
|
||||
html_version?: number
|
||||
was_plaintext?: boolean
|
||||
big_emoji?: boolean
|
||||
|
|
|
@ -107,7 +107,9 @@ const MessageComposer = () => {
|
|||
rawSetEditing(evt)
|
||||
setState({
|
||||
media: isMedia ? evtContent as MediaMessageEventContent : null,
|
||||
text: (!evt.content.filename || evt.content.filename !== evt.content.body) ? (evtContent.body ?? "") : "",
|
||||
text: (!evt.content.filename || evt.content.filename !== evt.content.body)
|
||||
? (evt.local_content?.edit_source ?? evtContent.body ?? "")
|
||||
: "",
|
||||
replyTo: null,
|
||||
})
|
||||
textInput.current?.focus()
|
||||
|
|
|
@ -105,7 +105,9 @@ function filterAndSort(
|
|||
export function emojiToMarkdown(emoji: PartialEmoji): string {
|
||||
if (emoji.u.startsWith("mxc://")) {
|
||||
const title = emoji.t && emoji.t !== emoji.n ? emoji.t : `:${emoji.n}:`
|
||||
return `<img data-mx-emoticon height="32" src="${emoji.u}" alt=":${emoji.n}:" title="${title}"/>`
|
||||
const escapedTitle = title.replaceAll(`\\`, `\\\\`).replaceAll(`"`, `\\"`)
|
||||
return ``
|
||||
//return `<img data-mx-emoticon height="32" src="${emoji.u}" alt=":${emoji.n}:" title="${title}"/>`
|
||||
}
|
||||
return emoji.u
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue