RoomDelegate subtitle, take only 1 arg for getUser
This commit is contained in:
parent
5fa2892fda
commit
63645b73a5
@ -1,5 +1,6 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import json
|
import json
|
||||||
|
import random
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict, Optional, Tuple
|
from typing import Dict, Optional, Tuple
|
||||||
|
|
||||||
@ -7,6 +8,7 @@ from atomicfile import AtomicFile
|
|||||||
|
|
||||||
from .app import App
|
from .app import App
|
||||||
from .events import users
|
from .events import users
|
||||||
|
from .html_filter import HTML_FILTER
|
||||||
from .matrix_client import MatrixClient
|
from .matrix_client import MatrixClient
|
||||||
|
|
||||||
SavedAccounts = Dict[str, Dict[str, str]]
|
SavedAccounts = Dict[str, Dict[str, str]]
|
||||||
@ -127,3 +129,16 @@ class Backend:
|
|||||||
|
|
||||||
with AtomicFile(self.saved_accounts_path, "w") as new:
|
with AtomicFile(self.saved_accounts_path, "w") as new:
|
||||||
new.write(js)
|
new.write(js)
|
||||||
|
|
||||||
|
|
||||||
|
# General functions
|
||||||
|
|
||||||
|
async def request_user_update_event(self, user_id: str) -> None:
|
||||||
|
client = self.clients.get(user_id,
|
||||||
|
random.choice(tuple(self.clients.values())))
|
||||||
|
await client.request_user_update_event(user_id)
|
||||||
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def inlinify(html: str) -> str:
|
||||||
|
return HTML_FILTER.filter_inline(html)
|
||||||
|
@ -7,6 +7,7 @@ import mistune
|
|||||||
from lxml.html import HtmlElement, etree # nosec
|
from lxml.html import HtmlElement, etree # nosec
|
||||||
|
|
||||||
import html_sanitizer.sanitizer as sanitizer
|
import html_sanitizer.sanitizer as sanitizer
|
||||||
|
from html_sanitizer.sanitizer import Sanitizer
|
||||||
|
|
||||||
|
|
||||||
class HtmlFilter:
|
class HtmlFilter:
|
||||||
@ -20,7 +21,8 @@ class HtmlFilter:
|
|||||||
|
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self._sanitizer = sanitizer.Sanitizer(self.sanitizer_settings)
|
self._sanitizer = Sanitizer(self.sanitize_settings())
|
||||||
|
self._inline_sanitizer = Sanitizer(self.sanitize_settings(inline=True))
|
||||||
|
|
||||||
# The whitespace remover doesn't take <pre> into account
|
# The whitespace remover doesn't take <pre> into account
|
||||||
sanitizer.normalize_overall_whitespace = lambda html: html
|
sanitizer.normalize_overall_whitespace = lambda html: html
|
||||||
@ -34,6 +36,10 @@ class HtmlFilter:
|
|||||||
return self.filter(self._markdown_to_html(text))
|
return self.filter(self._markdown_to_html(text))
|
||||||
|
|
||||||
|
|
||||||
|
def filter_inline(self, html: str) -> str:
|
||||||
|
return self._inline_sanitizer.sanitize(html)
|
||||||
|
|
||||||
|
|
||||||
def filter(self, html: str) -> str:
|
def filter(self, html: str) -> str:
|
||||||
html = self._sanitizer.sanitize(html)
|
html = self._sanitizer.sanitize(html)
|
||||||
tree = etree.fromstring(html, parser=etree.HTMLParser())
|
tree = etree.fromstring(html, parser=etree.HTMLParser())
|
||||||
@ -53,33 +59,39 @@ class HtmlFilter:
|
|||||||
return str(result, "utf-8").strip("\n")
|
return str(result, "utf-8").strip("\n")
|
||||||
|
|
||||||
|
|
||||||
@property
|
def sanitize_settings(self, inline: bool = False) -> dict:
|
||||||
def sanitizer_settings(self) -> dict:
|
|
||||||
# https://matrix.org/docs/spec/client_server/latest.html#m-room-message-msgtypes
|
# https://matrix.org/docs/spec/client_server/latest.html#m-room-message-msgtypes
|
||||||
return {
|
|
||||||
"tags": {
|
|
||||||
# TODO: mx-reply, audio, video
|
# TODO: mx-reply, audio, video
|
||||||
"font", "h1", "h2", "h3", "h4", "h5", "h6",
|
|
||||||
"blockquote", "p", "a", "ul", "ol", "sup", "sub", "li",
|
inline_tags = {"font", "a", "sup", "sub", "b", "i", "s", "u", "code"}
|
||||||
"b", "i", "s", "u", "code", "hr", "br",
|
tags = inline_tags | {
|
||||||
|
"h1", "h2", "h3", "h4", "h5", "h6","blockquote",
|
||||||
|
"p", "ul", "ol", "li", "hr", "br",
|
||||||
"table", "thead", "tbody", "tr", "th", "td",
|
"table", "thead", "tbody", "tr", "th", "td",
|
||||||
"pre", "img",
|
"pre", "img",
|
||||||
},
|
}
|
||||||
"attributes": {
|
|
||||||
|
inlines_attributes = {
|
||||||
# TODO: translate font attrs to qt html subset
|
# TODO: translate font attrs to qt html subset
|
||||||
"font": {"data-mx-bg-color", "data-mx-color"},
|
"font": {"data-mx-bg-color", "data-mx-color"},
|
||||||
"a": {"href"},
|
"a": {"href"},
|
||||||
|
"code": {"class"},
|
||||||
|
}
|
||||||
|
attributes = {**inlines_attributes, **{
|
||||||
"img": {"width", "height", "alt", "title", "src"},
|
"img": {"width", "height", "alt", "title", "src"},
|
||||||
"ol": {"start"},
|
"ol": {"start"},
|
||||||
"code": {"class"},
|
}}
|
||||||
},
|
|
||||||
"empty": {"hr", "br", "img"},
|
return {
|
||||||
"separate": {
|
"tags": inline_tags if inline else tags,
|
||||||
|
"attributes": inlines_attributes if inline else attributes,
|
||||||
|
"empty": {} if inline else {"hr", "br", "img"},
|
||||||
|
"separate": {"a"} if inline else {
|
||||||
"a", "p", "li", "table", "tr", "th", "td", "br", "hr"
|
"a", "p", "li", "table", "tr", "th", "td", "br", "hr"
|
||||||
},
|
},
|
||||||
"whitespace": {},
|
"whitespace": {},
|
||||||
"add_nofollow": False,
|
"add_nofollow": False,
|
||||||
"autolink": { # FIXME: arg dict not working
|
"autolink": {
|
||||||
"link_regexes": self.link_regexes,
|
"link_regexes": self.link_regexes,
|
||||||
"avoid_hosts": [],
|
"avoid_hosts": [],
|
||||||
},
|
},
|
||||||
@ -107,7 +119,8 @@ class HtmlFilter:
|
|||||||
if el.tag != "font":
|
if el.tag != "font":
|
||||||
return el
|
return el
|
||||||
|
|
||||||
if not self.sanitizer_settings["attributes"]["font"] & set(el.keys()):
|
settings = self.sanitize_settings()
|
||||||
|
if not settings["attributes"]["font"] & set(el.keys()):
|
||||||
el.clear()
|
el.clear()
|
||||||
|
|
||||||
return el
|
return el
|
||||||
|
@ -16,7 +16,7 @@ Row {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
color: isMessage(model) ?
|
color: Utils.eventIsMessage(model) ?
|
||||||
HStyle.chat.message.background : HStyle.chat.event.background
|
HStyle.chat.message.background : HStyle.chat.event.background
|
||||||
|
|
||||||
//width: nameLabel.implicitWidth
|
//width: nameLabel.implicitWidth
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import QtQuick 2.7
|
import QtQuick 2.7
|
||||||
import QtQuick.Layouts 1.3
|
import QtQuick.Layouts 1.3
|
||||||
import "../../Base"
|
import "../../Base"
|
||||||
|
import "../../utils.js" as Utils
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
id: roomEventDelegate
|
id: roomEventDelegate
|
||||||
@ -16,17 +17,12 @@ Column {
|
|||||||
roomEventListView.model.get(model.index + nth) : null
|
roomEventListView.model.get(model.index + nth) : null
|
||||||
}
|
}
|
||||||
|
|
||||||
function isMessage(item) {
|
|
||||||
return /^RoomMessage($|[A-Z])/.test(item.eventType)
|
|
||||||
}
|
|
||||||
|
|
||||||
property var previousItem: getPreviousItem()
|
property var previousItem: getPreviousItem()
|
||||||
signal reloadPreviousItem()
|
signal reloadPreviousItem()
|
||||||
onReloadPreviousItem: previousItem = getPreviousItem()
|
onReloadPreviousItem: previousItem = getPreviousItem()
|
||||||
|
|
||||||
property var senderInfo: null
|
property var senderInfo: null
|
||||||
Component.onCompleted:
|
Component.onCompleted: senderInfo = models.users.getUser(model.senderId)
|
||||||
senderInfo = models.users.getUser(chatPage.userId, model.senderId)
|
|
||||||
|
|
||||||
readonly property bool isOwn: chatPage.userId === model.senderId
|
readonly property bool isOwn: chatPage.userId === model.senderId
|
||||||
|
|
||||||
@ -36,7 +32,7 @@ Column {
|
|||||||
readonly property bool combine: Boolean(
|
readonly property bool combine: Boolean(
|
||||||
model.date &&
|
model.date &&
|
||||||
previousItem && previousItem.eventType && previousItem.date &&
|
previousItem && previousItem.eventType && previousItem.date &&
|
||||||
isMessage(previousItem) == isMessage(model) &&
|
Utils.eventIsMessage(previousItem) == Utils.eventIsMessage(model) &&
|
||||||
! talkBreak &&
|
! talkBreak &&
|
||||||
! dayBreak &&
|
! dayBreak &&
|
||||||
previousItem.senderId === model.senderId &&
|
previousItem.senderId === model.senderId &&
|
||||||
|
@ -6,6 +6,7 @@ function onAccountDeleted(user_id) {
|
|||||||
models.accounts.popWhere({"userId": user_id}, 1)
|
models.accounts.popWhere({"userId": user_id}, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: get updated from nio rooms
|
||||||
function onUserUpdated(user_id, display_name, avatar_url, status_message) {
|
function onUserUpdated(user_id, display_name, avatar_url, status_message) {
|
||||||
models.users.upsert({"userId": user_id}, {
|
models.users.upsert({"userId": user_id}, {
|
||||||
"userId": user_id,
|
"userId": user_id,
|
||||||
|
@ -11,24 +11,20 @@ QtObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
property HListModel users: HListModel {
|
property HListModel users: HListModel {
|
||||||
function getUser(as_account_id, wanted_user_id) {
|
function getUser(user_id) {
|
||||||
wanted_user_id = wanted_user_id || as_account_id
|
var found = users.getWhere({"userId": user_id}, 1)
|
||||||
|
|
||||||
var found = users.getWhere({"userId": wanted_user_id}, 1)
|
|
||||||
if (found.length > 0) { return found[0] }
|
if (found.length > 0) { return found[0] }
|
||||||
|
|
||||||
users.append({
|
users.append({
|
||||||
"userId": wanted_user_id,
|
"userId": user_id,
|
||||||
"displayName": "",
|
"displayName": "",
|
||||||
"avatarUrl": "",
|
"avatarUrl": "",
|
||||||
"statusMessage": ""
|
"statusMessage": ""
|
||||||
})
|
})
|
||||||
|
|
||||||
py.callClientCoro(
|
py.callCoro("request_user_update_event", [user_id])
|
||||||
as_account_id, "request_user_update_event", [wanted_user_id]
|
|
||||||
)
|
|
||||||
|
|
||||||
return users.getWhere({"userId": wanted_user_id}, 1)[0]
|
return users.getWhere({"userId": user_id}, 1)[0]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,6 +12,10 @@ Python {
|
|||||||
signal willLoadAccounts(bool will)
|
signal willLoadAccounts(bool will)
|
||||||
property bool loadingAccounts: false
|
property bool loadingAccounts: false
|
||||||
|
|
||||||
|
function callSync(name, args) {
|
||||||
|
return call_sync("APP.backend." + name, args)
|
||||||
|
}
|
||||||
|
|
||||||
function callCoro(name, args, kwargs, callback) {
|
function callCoro(name, args, kwargs, callback) {
|
||||||
call("APP.call_backend_coro", [name, args, kwargs], function(uuid){
|
call("APP.call_backend_coro", [name, args, kwargs], function(uuid){
|
||||||
pendingCoroutines[uuid] = callback || function() {}
|
pendingCoroutines[uuid] = callback || function() {}
|
||||||
|
@ -36,10 +36,30 @@ MouseArea {
|
|||||||
Layout.maximumWidth: parent.width
|
Layout.maximumWidth: parent.width
|
||||||
}
|
}
|
||||||
|
|
||||||
HLabel {
|
HRichLabel {
|
||||||
id: subtitleLabel
|
id: subtitleLabel
|
||||||
visible: Boolean(text)
|
visible: Boolean(text)
|
||||||
//text: models.timelines.getWhere({"roomId": model.roomId}, 1)[0].content
|
text: {
|
||||||
|
for (var i = 0; i < models.timelines.count; i++) {
|
||||||
|
var item = models.timelines.get(i) // TODO: standardize
|
||||||
|
|
||||||
|
if (item.roomId == model.roomId) {
|
||||||
|
var ev = item
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! ev) { return "" }
|
||||||
|
|
||||||
|
if (! Utils.eventIsMessage(ev)) {
|
||||||
|
return Utils.translatedEventContent(ev)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Utils.coloredNameHtml(
|
||||||
|
models.users.getUser(ev.senderId).displayName,
|
||||||
|
ev.senderId
|
||||||
|
) + ": " + py.callSync("inlinify", [ev.content])
|
||||||
|
}
|
||||||
textFormat: Text.StyledText
|
textFormat: Text.StyledText
|
||||||
|
|
||||||
font.pixelSize: HStyle.fontSize.small
|
font.pixelSize: HStyle.fontSize.small
|
||||||
|
@ -1,29 +0,0 @@
|
|||||||
.import "../Chat/utils.js" as ChatJS
|
|
||||||
|
|
||||||
|
|
||||||
function getLastRoomEventText(roomId, accountId) {
|
|
||||||
var eventsModel = Backend.roomEvents.get(roomId)
|
|
||||||
if (eventsModel.count < 1) { return "" }
|
|
||||||
var ev = eventsModel.get(0)
|
|
||||||
|
|
||||||
var name = Backend.users.get(ev.dict.sender).displayName.value
|
|
||||||
|
|
||||||
var undecryptable = ev.type === "OlmEvent" || ev.type === "MegolmEvent"
|
|
||||||
|
|
||||||
if (undecryptable || ev.type.startsWith("RoomMessage")) {
|
|
||||||
var color = Qt.hsla(Backend.hueFromString(name), 0.32, 0.3, 1)
|
|
||||||
|
|
||||||
return "<font color='" + color + "'>" +
|
|
||||||
name +
|
|
||||||
":</font> " +
|
|
||||||
(undecryptable ?
|
|
||||||
"<font color='darkred'>" + qsTr("Undecryptable") + "<font>" :
|
|
||||||
ev.dict.body)
|
|
||||||
} else {
|
|
||||||
return "<font color='" + (undecryptable ? "darkred" : "#444") + "'>" +
|
|
||||||
name +
|
|
||||||
" " +
|
|
||||||
ChatJS.getEventText(ev.type, ev.dict) +
|
|
||||||
"</font>"
|
|
||||||
}
|
|
||||||
}
|
|
@ -20,7 +20,7 @@ function hueFrom(string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function avatarHue(name) {
|
function avatarHue(name) { // TODO: bad name
|
||||||
return Qt.hsla(
|
return Qt.hsla(
|
||||||
hueFrom(name),
|
hueFrom(name),
|
||||||
HStyle.avatar.background.saturation,
|
HStyle.avatar.background.saturation,
|
||||||
@ -30,7 +30,7 @@ function avatarHue(name) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function nameHue(name) {
|
function nameHue(name) { // TODO: bad name
|
||||||
return Qt.hsla(
|
return Qt.hsla(
|
||||||
hueFrom(name),
|
hueFrom(name),
|
||||||
HStyle.displayName.saturation,
|
HStyle.displayName.saturation,
|
||||||
@ -40,6 +40,13 @@ function nameHue(name) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function coloredNameHtml(name, alt_id) {
|
||||||
|
return "<font color='" + nameHue(name || stripUserId(alt_id)) + "'>" +
|
||||||
|
escapeHtml(name || alt_id) +
|
||||||
|
"</font>"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function escapeHtml(string) {
|
function escapeHtml(string) {
|
||||||
// Replace special HTML characters by encoded alternatives
|
// Replace special HTML characters by encoded alternatives
|
||||||
return string.replace("&", "&")
|
return string.replace("&", "&")
|
||||||
@ -50,38 +57,31 @@ function escapeHtml(string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function eventIsMessage(ev) {
|
||||||
|
return /^RoomMessage($|[A-Z])/.test(ev.eventType)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function translatedEventContent(ev) {
|
function translatedEventContent(ev) {
|
||||||
// ev: models.timelines item
|
// ev: models.timelines item
|
||||||
if (ev.translatable == false) { return ev.content }
|
if (ev.translatable == false) { return ev.content }
|
||||||
|
|
||||||
// %S → sender display name
|
// %S → sender display name
|
||||||
var name = models.users.getUser(ev.senderId).displayName
|
var name = models.users.getUser(ev.senderId).displayName
|
||||||
var text = ev.content.replace(
|
var text = ev.content.replace("%S", coloredNameHtml(name, ev.senderId))
|
||||||
"%S",
|
|
||||||
"<font color='" + nameHue(name || stripUserId(ev.senderId)) + "'>" +
|
|
||||||
escapeHtml(name || ev.senderId) +
|
|
||||||
"</font>"
|
|
||||||
)
|
|
||||||
|
|
||||||
// %T → target (event state_key) display name
|
// %T → target (event state_key) display name
|
||||||
if (ev.targetUserId) {
|
if (ev.targetUserId) {
|
||||||
var target_name = models.users.getUser(ev.targetUserId).displayName
|
var tname = models.users.getUser(ev.targetUserId).displayName
|
||||||
text = text.replace(
|
text = text.replace("%T", coloredNameHtml(tname, ev.targetUserId))
|
||||||
"%T",
|
|
||||||
"<font color='" +
|
|
||||||
nameHue(target_name || stripUserId(ev.targetUserId)) +
|
|
||||||
"'>" +
|
|
||||||
escapeHtml(target_name || ev.targetUserId) +
|
|
||||||
"</font>"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
text = qsTr(text)
|
text = qsTr(text)
|
||||||
if (model.translatable == true) { return text }
|
if (ev.translatable == true) { return text }
|
||||||
|
|
||||||
// Else, model.translatable should be an array of args
|
// Else, model.translatable should be an array of args
|
||||||
for (var i = 0; model.translatable.length; i++) {
|
for (var i = 0; ev.translatable.length; i++) {
|
||||||
text = text.arg(model.translatable[i])
|
text = text.arg(ev.translatable[i])
|
||||||
}
|
}
|
||||||
return text
|
return text
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user