RoomDelegate subtitle, take only 1 arg for getUser

This commit is contained in:
miruka 2019-07-04 00:24:21 -04:00
parent 5fa2892fda
commit 63645b73a5
10 changed files with 107 additions and 91 deletions

View File

@ -1,5 +1,6 @@
import asyncio
import json
import random
from pathlib import Path
from typing import Dict, Optional, Tuple
@ -7,6 +8,7 @@ from atomicfile import AtomicFile
from .app import App
from .events import users
from .html_filter import HTML_FILTER
from .matrix_client import MatrixClient
SavedAccounts = Dict[str, Dict[str, str]]
@ -127,3 +129,16 @@ class Backend:
with AtomicFile(self.saved_accounts_path, "w") as new:
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)

View File

@ -7,6 +7,7 @@ import mistune
from lxml.html import HtmlElement, etree # nosec
import html_sanitizer.sanitizer as sanitizer
from html_sanitizer.sanitizer import Sanitizer
class HtmlFilter:
@ -20,7 +21,8 @@ class HtmlFilter:
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
sanitizer.normalize_overall_whitespace = lambda html: html
@ -34,6 +36,10 @@ class HtmlFilter:
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:
html = self._sanitizer.sanitize(html)
tree = etree.fromstring(html, parser=etree.HTMLParser())
@ -53,33 +59,39 @@ class HtmlFilter:
return str(result, "utf-8").strip("\n")
@property
def sanitizer_settings(self) -> dict:
def sanitize_settings(self, inline: bool = False) -> dict:
# https://matrix.org/docs/spec/client_server/latest.html#m-room-message-msgtypes
# TODO: mx-reply, audio, video
inline_tags = {"font", "a", "sup", "sub", "b", "i", "s", "u", "code"}
tags = inline_tags | {
"h1", "h2", "h3", "h4", "h5", "h6","blockquote",
"p", "ul", "ol", "li", "hr", "br",
"table", "thead", "tbody", "tr", "th", "td",
"pre", "img",
}
inlines_attributes = {
# TODO: translate font attrs to qt html subset
"font": {"data-mx-bg-color", "data-mx-color"},
"a": {"href"},
"code": {"class"},
}
attributes = {**inlines_attributes, **{
"img": {"width", "height", "alt", "title", "src"},
"ol": {"start"},
}}
return {
"tags": {
# TODO: mx-reply, audio, video
"font", "h1", "h2", "h3", "h4", "h5", "h6",
"blockquote", "p", "a", "ul", "ol", "sup", "sub", "li",
"b", "i", "s", "u", "code", "hr", "br",
"table", "thead", "tbody", "tr", "th", "td",
"pre", "img",
},
"attributes": {
# TODO: translate font attrs to qt html subset
"font": {"data-mx-bg-color", "data-mx-color"},
"a": {"href"},
"img": {"width", "height", "alt", "title", "src"},
"ol": {"start"},
"code": {"class"},
},
"empty": {"hr", "br", "img"},
"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"
},
"whitespace": {},
"add_nofollow": False,
"autolink": { # FIXME: arg dict not working
"autolink": {
"link_regexes": self.link_regexes,
"avoid_hosts": [],
},
@ -107,7 +119,8 @@ class HtmlFilter:
if el.tag != "font":
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()
return el

View File

@ -16,7 +16,7 @@ Row {
}
Rectangle {
color: isMessage(model) ?
color: Utils.eventIsMessage(model) ?
HStyle.chat.message.background : HStyle.chat.event.background
//width: nameLabel.implicitWidth

View File

@ -1,6 +1,7 @@
import QtQuick 2.7
import QtQuick.Layouts 1.3
import "../../Base"
import "../../utils.js" as Utils
Column {
id: roomEventDelegate
@ -16,17 +17,12 @@ Column {
roomEventListView.model.get(model.index + nth) : null
}
function isMessage(item) {
return /^RoomMessage($|[A-Z])/.test(item.eventType)
}
property var previousItem: getPreviousItem()
signal reloadPreviousItem()
onReloadPreviousItem: previousItem = getPreviousItem()
property var senderInfo: null
Component.onCompleted:
senderInfo = models.users.getUser(chatPage.userId, model.senderId)
Component.onCompleted: senderInfo = models.users.getUser(model.senderId)
readonly property bool isOwn: chatPage.userId === model.senderId
@ -36,7 +32,7 @@ Column {
readonly property bool combine: Boolean(
model.date &&
previousItem && previousItem.eventType && previousItem.date &&
isMessage(previousItem) == isMessage(model) &&
Utils.eventIsMessage(previousItem) == Utils.eventIsMessage(model) &&
! talkBreak &&
! dayBreak &&
previousItem.senderId === model.senderId &&

View File

@ -6,6 +6,7 @@ function onAccountDeleted(user_id) {
models.accounts.popWhere({"userId": user_id}, 1)
}
// TODO: get updated from nio rooms
function onUserUpdated(user_id, display_name, avatar_url, status_message) {
models.users.upsert({"userId": user_id}, {
"userId": user_id,

View File

@ -11,24 +11,20 @@ QtObject {
}
property HListModel users: HListModel {
function getUser(as_account_id, wanted_user_id) {
wanted_user_id = wanted_user_id || as_account_id
var found = users.getWhere({"userId": wanted_user_id}, 1)
function getUser(user_id) {
var found = users.getWhere({"userId": user_id}, 1)
if (found.length > 0) { return found[0] }
users.append({
"userId": wanted_user_id,
"userId": user_id,
"displayName": "",
"avatarUrl": "",
"statusMessage": ""
})
py.callClientCoro(
as_account_id, "request_user_update_event", [wanted_user_id]
)
py.callCoro("request_user_update_event", [user_id])
return users.getWhere({"userId": wanted_user_id}, 1)[0]
return users.getWhere({"userId": user_id}, 1)[0]
}
}

View File

@ -12,6 +12,10 @@ Python {
signal willLoadAccounts(bool will)
property bool loadingAccounts: false
function callSync(name, args) {
return call_sync("APP.backend." + name, args)
}
function callCoro(name, args, kwargs, callback) {
call("APP.call_backend_coro", [name, args, kwargs], function(uuid){
pendingCoroutines[uuid] = callback || function() {}

View File

@ -36,10 +36,30 @@ MouseArea {
Layout.maximumWidth: parent.width
}
HLabel {
HRichLabel {
id: subtitleLabel
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
font.pixelSize: HStyle.fontSize.small

View File

@ -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>"
}
}

View File

@ -20,7 +20,7 @@ function hueFrom(string) {
}
function avatarHue(name) {
function avatarHue(name) { // TODO: bad name
return Qt.hsla(
hueFrom(name),
HStyle.avatar.background.saturation,
@ -30,7 +30,7 @@ function avatarHue(name) {
}
function nameHue(name) {
function nameHue(name) { // TODO: bad name
return Qt.hsla(
hueFrom(name),
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) {
// Replace special HTML characters by encoded alternatives
return string.replace("&", "&amp;")
@ -50,38 +57,31 @@ function escapeHtml(string) {
}
function eventIsMessage(ev) {
return /^RoomMessage($|[A-Z])/.test(ev.eventType)
}
function translatedEventContent(ev) {
// ev: models.timelines item
if (ev.translatable == false) { return ev.content }
// %S → sender display name
var name = models.users.getUser(ev.senderId).displayName
var text = ev.content.replace(
"%S",
"<font color='" + nameHue(name || stripUserId(ev.senderId)) + "'>" +
escapeHtml(name || ev.senderId) +
"</font>"
)
var text = ev.content.replace("%S", coloredNameHtml(name, ev.senderId))
// %T → target (event state_key) display name
if (ev.targetUserId) {
var target_name = models.users.getUser(ev.targetUserId).displayName
text = text.replace(
"%T",
"<font color='" +
nameHue(target_name || stripUserId(ev.targetUserId)) +
"'>" +
escapeHtml(target_name || ev.targetUserId) +
"</font>"
)
var tname = models.users.getUser(ev.targetUserId).displayName
text = text.replace("%T", coloredNameHtml(tname, ev.targetUserId))
}
text = qsTr(text)
if (model.translatable == true) { return text }
if (ev.translatable == true) { return text }
// Else, model.translatable should be an array of args
for (var i = 0; model.translatable.length; i++) {
text = text.arg(model.translatable[i])
for (var i = 0; ev.translatable.length; i++) {
text = text.arg(ev.translatable[i])
}
return text
}