HTML detector and switcher
HTML messages are detected and if present, the top of the message textedit displays a clickable area that switches HTML rendering on and off. Fixes #13
This commit is contained in:
parent
cf38385589
commit
ca4ab87f7d
|
@ -35,6 +35,7 @@ from addaddressdialog import *
|
||||||
from newsubscriptiondialog import *
|
from newsubscriptiondialog import *
|
||||||
from regenerateaddresses import *
|
from regenerateaddresses import *
|
||||||
from newchandialog import *
|
from newchandialog import *
|
||||||
|
from safehtmlparser import *
|
||||||
from specialaddressbehavior import *
|
from specialaddressbehavior import *
|
||||||
from emailgateway import *
|
from emailgateway import *
|
||||||
from settings import *
|
from settings import *
|
||||||
|
@ -4026,10 +4027,9 @@ class MyForm(settingsmixin.SMainWindow):
|
||||||
data = self.getCurrentMessageId()
|
data = self.getCurrentMessageId()
|
||||||
if data != False:
|
if data != False:
|
||||||
message = "Error occurred: could not load message from disk."
|
message = "Error occurred: could not load message from disk."
|
||||||
message = unicode(message, 'utf-8)')
|
|
||||||
messageTextedit.setCurrentFont(QtGui.QFont())
|
messageTextedit.setCurrentFont(QtGui.QFont())
|
||||||
messageTextedit.setTextColor(QtGui.QColor())
|
messageTextedit.setTextColor(QtGui.QColor())
|
||||||
messageTextedit.setPlainText(message)
|
messageTextedit.setContent(message)
|
||||||
|
|
||||||
def tableWidgetAddressBookItemChanged(self):
|
def tableWidgetAddressBookItemChanged(self):
|
||||||
currentRow = self.ui.tableWidgetAddressBook.currentRow()
|
currentRow = self.ui.tableWidgetAddressBook.currentRow()
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
# WARNING! All changes made in this file will be lost!
|
# WARNING! All changes made in this file will be lost!
|
||||||
|
|
||||||
from PyQt4 import QtCore, QtGui
|
from PyQt4 import QtCore, QtGui
|
||||||
|
from messageview import MessageView
|
||||||
import settingsmixin
|
import settingsmixin
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -123,7 +124,7 @@ class Ui_MainWindow(object):
|
||||||
self.tableWidgetInbox.verticalHeader().setVisible(False)
|
self.tableWidgetInbox.verticalHeader().setVisible(False)
|
||||||
self.tableWidgetInbox.verticalHeader().setDefaultSectionSize(26)
|
self.tableWidgetInbox.verticalHeader().setDefaultSectionSize(26)
|
||||||
self.verticalSplitter_7.addWidget(self.tableWidgetInbox)
|
self.verticalSplitter_7.addWidget(self.tableWidgetInbox)
|
||||||
self.textEditInboxMessage = QtGui.QTextEdit(self.inbox)
|
self.textEditInboxMessage = MessageView(self.inbox)
|
||||||
self.textEditInboxMessage.setBaseSize(QtCore.QSize(0, 500))
|
self.textEditInboxMessage.setBaseSize(QtCore.QSize(0, 500))
|
||||||
self.textEditInboxMessage.setReadOnly(True)
|
self.textEditInboxMessage.setReadOnly(True)
|
||||||
self.textEditInboxMessage.setObjectName(_fromUtf8("textEditInboxMessage"))
|
self.textEditInboxMessage.setObjectName(_fromUtf8("textEditInboxMessage"))
|
||||||
|
@ -428,7 +429,7 @@ class Ui_MainWindow(object):
|
||||||
self.tableWidgetInboxSubscriptions.verticalHeader().setVisible(False)
|
self.tableWidgetInboxSubscriptions.verticalHeader().setVisible(False)
|
||||||
self.tableWidgetInboxSubscriptions.verticalHeader().setDefaultSectionSize(26)
|
self.tableWidgetInboxSubscriptions.verticalHeader().setDefaultSectionSize(26)
|
||||||
self.verticalSplitter_4.addWidget(self.tableWidgetInboxSubscriptions)
|
self.verticalSplitter_4.addWidget(self.tableWidgetInboxSubscriptions)
|
||||||
self.textEditInboxMessageSubscriptions = QtGui.QTextEdit(self.subscriptions)
|
self.textEditInboxMessageSubscriptions = MessageView(self.subscriptions)
|
||||||
self.textEditInboxMessageSubscriptions.setBaseSize(QtCore.QSize(0, 500))
|
self.textEditInboxMessageSubscriptions.setBaseSize(QtCore.QSize(0, 500))
|
||||||
self.textEditInboxMessageSubscriptions.setReadOnly(True)
|
self.textEditInboxMessageSubscriptions.setReadOnly(True)
|
||||||
self.textEditInboxMessageSubscriptions.setObjectName(_fromUtf8("textEditInboxMessageSubscriptions"))
|
self.textEditInboxMessageSubscriptions.setObjectName(_fromUtf8("textEditInboxMessageSubscriptions"))
|
||||||
|
@ -527,7 +528,7 @@ class Ui_MainWindow(object):
|
||||||
self.tableWidgetInboxChans.verticalHeader().setVisible(False)
|
self.tableWidgetInboxChans.verticalHeader().setVisible(False)
|
||||||
self.tableWidgetInboxChans.verticalHeader().setDefaultSectionSize(26)
|
self.tableWidgetInboxChans.verticalHeader().setDefaultSectionSize(26)
|
||||||
self.verticalSplitter_8.addWidget(self.tableWidgetInboxChans)
|
self.verticalSplitter_8.addWidget(self.tableWidgetInboxChans)
|
||||||
self.textEditInboxMessageChans = QtGui.QTextEdit(self.chans)
|
self.textEditInboxMessageChans = MessageView(self.chans)
|
||||||
self.textEditInboxMessageChans.setBaseSize(QtCore.QSize(0, 500))
|
self.textEditInboxMessageChans.setBaseSize(QtCore.QSize(0, 500))
|
||||||
self.textEditInboxMessageChans.setReadOnly(True)
|
self.textEditInboxMessageChans.setReadOnly(True)
|
||||||
self.textEditInboxMessageChans.setObjectName(_fromUtf8("textEditInboxMessageChans"))
|
self.textEditInboxMessageChans.setObjectName(_fromUtf8("textEditInboxMessageChans"))
|
||||||
|
|
44
src/bitmessageqt/messageview.py
Normal file
44
src/bitmessageqt/messageview.py
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
from PyQt4 import QtCore, QtGui
|
||||||
|
|
||||||
|
from safehtmlparser import *
|
||||||
|
|
||||||
|
class MessageView(QtGui.QTextEdit):
|
||||||
|
MODE_PLAIN = 0
|
||||||
|
MODE_HTML = 1
|
||||||
|
TEXT_PLAIN = "HTML detected, click here to display"
|
||||||
|
TEXT_HTML = "Click here to disable HTML"
|
||||||
|
|
||||||
|
def __init__(self, parent = 0):
|
||||||
|
super(MessageView, self).__init__(parent)
|
||||||
|
self.mode = MessageView.MODE_PLAIN
|
||||||
|
self.html = None
|
||||||
|
|
||||||
|
def mousePressEvent(self, event):
|
||||||
|
#text = textCursor.block().text()
|
||||||
|
if event.button() == QtCore.Qt.LeftButton and self.html.has_html and self.cursorForPosition(event.pos()).block().blockNumber() == 0:
|
||||||
|
if self.mode == MessageView.MODE_PLAIN:
|
||||||
|
self.showHTML()
|
||||||
|
else:
|
||||||
|
self.showPlain()
|
||||||
|
else:
|
||||||
|
super(MessageView, self).mousePressEvent(event)
|
||||||
|
|
||||||
|
def showPlain(self):
|
||||||
|
self.mode = MessageView.MODE_PLAIN
|
||||||
|
out = self.html.raw
|
||||||
|
if self.html.has_html:
|
||||||
|
out = "<div align=\"center\" style=\"text-decoration: underline;\"><b>" + QtGui.QApplication.translate("MessageView", MessageView.TEXT_PLAIN) + "</b></div><br/>" + out
|
||||||
|
self.setHtml(QtCore.QString(out))
|
||||||
|
|
||||||
|
def showHTML(self):
|
||||||
|
self.mode = MessageView.MODE_HTML
|
||||||
|
out = self.html.sanitised
|
||||||
|
out = "<div align=\"center\" style=\"text-decoration: underline;\"><b>" + QtGui.QApplication.translate("MessageView", MessageView.TEXT_HTML) + "</b></div><br/>" + out
|
||||||
|
self.setHtml(QtCore.QString(out))
|
||||||
|
|
||||||
|
def setContent(self, data):
|
||||||
|
self.html = SafeHTMLParser()
|
||||||
|
self.html.allow_picture = True
|
||||||
|
self.html.feed(data)
|
||||||
|
self.html.close()
|
||||||
|
self.showPlain()
|
97
src/bitmessageqt/safehtmlparser.py
Normal file
97
src/bitmessageqt/safehtmlparser.py
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
from HTMLParser import HTMLParser
|
||||||
|
import inspect
|
||||||
|
from urllib import quote, quote_plus
|
||||||
|
|
||||||
|
class SafeHTMLParser(HTMLParser):
|
||||||
|
# from html5lib.sanitiser
|
||||||
|
acceptable_elements = ['a', 'abbr', 'acronym', 'address', 'area',
|
||||||
|
'article', 'aside', 'audio', 'b', 'big', 'blockquote', 'br', 'button',
|
||||||
|
'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup',
|
||||||
|
'command', 'datagrid', 'datalist', 'dd', 'del', 'details', 'dfn',
|
||||||
|
'dialog', 'dir', 'div', 'dl', 'dt', 'em', 'event-source', 'fieldset',
|
||||||
|
'figcaption', 'figure', 'footer', 'font', 'header', 'h1',
|
||||||
|
'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'img', 'ins',
|
||||||
|
'keygen', 'kbd', 'label', 'legend', 'li', 'm', 'map', 'menu', 'meter',
|
||||||
|
'multicol', 'nav', 'nextid', 'ol', 'output', 'optgroup', 'option',
|
||||||
|
'p', 'pre', 'progress', 'q', 's', 'samp', 'section', 'select',
|
||||||
|
'small', 'sound', 'source', 'spacer', 'span', 'strike', 'strong',
|
||||||
|
'sub', 'sup', 'table', 'tbody', 'td', 'textarea', 'time', 'tfoot',
|
||||||
|
'th', 'thead', 'tr', 'tt', 'u', 'ul', 'var', 'video']
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
HTMLParser.__init__(self, *args, **kwargs)
|
||||||
|
self.elements = set()
|
||||||
|
self.sanitised = u""
|
||||||
|
self.raw = u""
|
||||||
|
self.has_html = False
|
||||||
|
|
||||||
|
def reset_safe(self):
|
||||||
|
self.raw = u""
|
||||||
|
self.sanitised = u""
|
||||||
|
|
||||||
|
def add_if_acceptable(self, tag, attrs = None):
|
||||||
|
if not tag in self.acceptable_elements:
|
||||||
|
return
|
||||||
|
self.sanitised += "<"
|
||||||
|
if inspect.stack()[1][3] == "handle_endtag":
|
||||||
|
self.sanitised += "/"
|
||||||
|
self.sanitised += tag
|
||||||
|
if attrs is not None:
|
||||||
|
for attr in attrs:
|
||||||
|
if tag == "img" and attr[0] == "src" and not self.allow_picture:
|
||||||
|
attr[1] = ""
|
||||||
|
self.sanitised += " " + quote_plus(attr[0]) + "=\"" + attr[1] + "\""
|
||||||
|
if inspect.stack()[1][3] == "handle_startendtag":
|
||||||
|
self.sanitised += "/"
|
||||||
|
self.sanitised += ">"
|
||||||
|
|
||||||
|
def add_raw(self, tag, attrs = None):
|
||||||
|
self.raw += "<"
|
||||||
|
if inspect.stack()[1][3] == "handle_endtag":
|
||||||
|
self.raw += "/"
|
||||||
|
self.raw += tag
|
||||||
|
if attrs is not None:
|
||||||
|
for attr in attrs:
|
||||||
|
if tag == "img" and attr[0] == "src" and not self.allow_picture:
|
||||||
|
attr[1] = ""
|
||||||
|
self.raw += " " + attr[0] + "="" + attr[1] + """
|
||||||
|
if inspect.stack()[1][3] == "handle_startendtag":
|
||||||
|
self.raw += "/"
|
||||||
|
self.raw += ">"
|
||||||
|
|
||||||
|
def handle_starttag(self, tag, attrs):
|
||||||
|
if tag in self.acceptable_elements:
|
||||||
|
self.has_html = True
|
||||||
|
self.add_if_acceptable(tag, attrs)
|
||||||
|
self.add_raw(tag, attrs)
|
||||||
|
|
||||||
|
def handle_endtag(self, tag):
|
||||||
|
self.add_if_acceptable(tag)
|
||||||
|
self.add_raw(tag)
|
||||||
|
|
||||||
|
def handle_startendtag(self, tag, attrs):
|
||||||
|
if tag in self.acceptable_elements:
|
||||||
|
self.has_html = True
|
||||||
|
self.add_if_acceptable(tag, attrs)
|
||||||
|
self.add_raw(tag, attrs)
|
||||||
|
|
||||||
|
def handle_data(self, data):
|
||||||
|
self.sanitised += unicode(data, 'utf-8', 'replace')
|
||||||
|
tmp = data.replace("\n", "<br/>")
|
||||||
|
self.raw += unicode(tmp, 'utf-8', 'replace')
|
||||||
|
|
||||||
|
def handle_charref(self, name):
|
||||||
|
self.sanitised += "&#" + name + ";"
|
||||||
|
self.raw += quote("&#" + name + ";")
|
||||||
|
|
||||||
|
def handle_entityref(self, name):
|
||||||
|
self.sanitised += "&" + name + ";"
|
||||||
|
self.raw += quote("&" + name + ";")
|
||||||
|
|
||||||
|
def is_html(self, text = None, allow_picture = False):
|
||||||
|
if text:
|
||||||
|
self.reset()
|
||||||
|
self.reset_safe()
|
||||||
|
self.feed(text)
|
||||||
|
self.close()
|
||||||
|
self.allow_picture = allow_picture
|
||||||
|
return self.has_html
|
Loading…
Reference in New Issue
Block a user