xi-conversations

Minimal clone of thunderbird conversations
git clone https://git.ce9e.org/xi-conversations.git

commit
f67930393ed014b0180288ca16962a4dde8d8bad
parent
92652cab5f3feedaffa8ab3a3f196d1ff8f57ed8
Author
Tobias Bengfort <tobias.bengfort@posteo.de>
Date
2023-09-04 20:52
avoid using html2element for message

Diffstat

M content/js/message.js 83 +++++++++++++++++++++++++++++++++++++------------------------
M content/js/util.js 37 +++++++++++++++++--------------------
M content/main.html 65 ------------------------------------------------------------

3 files changed, 68 insertions, 117 deletions


diff --git a/content/js/message.js b/content/js/message.js

@@ -1,4 +1,4 @@
    1    -1 import Mustache from './mustache.mjs';
   -1     1 /* global browser */
    2     2 
    3     3 import createBody from './body.js';
    4     4 import * as actions from './actions.js';
@@ -41,46 +41,66 @@ var autoMarkAsRead = function(e, msg) {
   41    41 	};
   42    42 };
   43    43 
   44    -1 var iconFilter = function() {
   45    -1 	return function(text, render) {
   46    -1 		var key = render(text);
   47    -1 		return util.createIcon(key).outerHTML;
   48    -1 	};
   -1    44 var createAuthor = function(author) {
   -1    45 	var a = util.h('a', {'class': 'message__author', 'href': `mailto:${author.email}`}, [author.name]);
   -1    46 	a.style.color = util.pseudoRandomColor(author.email);
   -1    47 	return a;
   49    48 };
   50    49 
   51    -1 var dateFilter = function() {
   52    -1 	return function(text, render) {
   53    -1 		var date = new Date(render(text));
   54    -1 		return util.createDate(date).outerHTML;
   55    -1 	};
   56    -1 };
   57    -1 
   58    -1 var authorColorFilter = function() {
   59    -1 	return function(text, render) {
   60    -1 		return util.pseudoRandomColor(render(text));
   61    -1 	};
   62    -1 };
   63    -1 
   64    -1 var stringFilter = function() {
   65    -1 	return function(text, render) {
   66    -1 		return browser.i18n.getMessage(render(text));
   67    -1 	};
   -1    50 var template = function(ctx) {
   -1    51 	var h = util.h;
   -1    52 	return h('article', {'class': ctx.isExpanded ? 'message is-expanded' : 'message', 'id': `msg-${ctx.id}`, 'tabindex': -1}, [
   -1    53 		h('header', {'class': 'message__header'}, [
   -1    54 			h('button', {'class': ctx.isFlagged ? 'star is-active' : 'star', 'data-action': 'toggleFlagged'}, [util.createIcon('star')]),
   -1    55 			createAuthor(ctx.author),
   -1    56 			' ',
   -1    57 			h('span', {'class': 'message__recipients'}, [
   -1    58 				browser.i18n.getMessage('to'),
   -1    59 				' ',
   -1    60 				...ctx.recipients.map(r => h('a', {'href': `mailto:${r.email}`}, [r.name])),
   -1    61 				...ctx.cc.map(r => h('a', {'href': `mailto:${r.email}`, 'class': 'message__recipients__cc'}, [r.name])),
   -1    62 				...ctx.bcc.map(r => h('a', {'href': `mailto:${r.email}`, 'class': 'message__recipients__bcc'}, [r.name])),
   -1    63 			]),
   -1    64 			h('span', {'class': 'message__summary'}, [ctx.summary]),
   -1    65 			ctx.hasAttachments ? util.createIcon('attachment') : null,
   -1    66 			util.createDate(ctx.date),
   -1    67 			h('span', {'class': 'message__actions'}, [
   -1    68 				ctx.canReplyAll
   -1    69 					? h('button', {'class': 'button', 'title': browser.i18n.getMessage('replyAll'), 'data-action': 'replyAll'}, [util.createIcon('reply_all')])
   -1    70 					: ctx.canReplyToList
   -1    71 						? h('button', {'class': 'button', 'title': browser.i18n.getMessage('replyList'), 'data-action': 'replyToList'}, [util.createIcon('list')])
   -1    72 						: h('button', {'class': 'button', 'title': browser.i18n.getMessage('reply'), 'data-action': 'replyToSender'}, [util.createIcon('reply')]),
   -1    73 				h('button', {'class': 'button dropdownToggle', 'title': browser.i18n.getMessage('more')}, [util.createIcon('menu')]),
   -1    74 				h('div', {'class': 'dropdown'}, [
   -1    75 					h('button', {'class': 'dropdown-item', 'data-action': 'replyToSender'}, [util.createIcon('reply'), ' ', browser.i18n.getMessage('reply')]),
   -1    76 					ctx.canReplyAll ? h('button', {'class': 'dropdown-item', 'data-action': 'replyAll'}, [util.createIcon('reply_all'), ' ', browser.i18n.getMessage('replyAll')]) : null,
   -1    77 					ctx.canReplyToList ? h('button', {'class': 'dropdown-item', 'data-action': 'replyToList'}, [util.createIcon('list'), ' ', browser.i18n.getMessage('replyList')]) : null,
   -1    78 					h('button', {'class': 'dropdown-item', 'data-action': 'forward'}, [util.createIcon('forward'), ' ', browser.i18n.getMessage('forward')]),
   -1    79 					h('button', {'class': 'dropdown-item', 'data-action': 'editAsNew'}, [util.createIcon('create'), ' ', browser.i18n.getMessage('edit')]),
   -1    80 					h('button', {'class': 'dropdown-item', 'data-action': 'viewClassic'}, [util.createIcon('open_in_new'), ' ', browser.i18n.getMessage('viewClassic')]),
   -1    81 					h('button', {'class': 'dropdown-item', 'data-action': 'viewSource'}, [util.createIcon('code'), ' ', browser.i18n.getMessage('viewSource')]),
   -1    82 				]),
   -1    83 			]),
   -1    84 		]),
   -1    85 		h('div', {'class': 'message__details'}, [
   -1    86 			ctx.isJunk ? util.createAlert(browser.i18n.getMessage('junk'), 'mode_heat', 'warning') : null,
   -1    87 			h('footer', {'class': 'message__footer'}, [
   -1    88 				h('ul', {'class': 'attachments'}, (ctx.attachments || []).map(a => h('li', {}, [
   -1    89 					h('a', {'class': 'attachment', 'href': a.url}, [util.createIcon('attachment'), ' ', a.name]),
   -1    90 				]))),
   -1    91 			]),
   -1    92 		]),
   -1    93 	]);
   68    94 };
   69    95 
   70    96 export default function(msg, expanded) {
   71    -1 	var tpl = document.getElementById('message-template').innerHTML;
   72    -1 	var html = Mustache.render(tpl, {
   73    -1 		icon: iconFilter,
   74    -1 		dateFilter: dateFilter,
   75    -1 		authorColor: authorColorFilter,
   76    -1 		str: stringFilter,
   77    -1 
   -1    97 	var e = template({
   78    98 		id: msg.id,
   79    99 		isExpanded: expanded,
   80   100 		isFlagged: msg.flagged,
   81   101 		isJunk: msg.junk,
   82   102 		isEncrypted: msg.isEncrypted,
   83    -1 		author: util.parseContacts([msg.author]),
   -1   103 		author: util.parseContacts([msg.author])[0],
   84   104 		recipients: util.parseContacts(msg.recipients),
   85   105 		cc: util.parseContacts(msg.ccList),
   86   106 		bcc: util.parseContacts(msg.bccList),
@@ -95,7 +115,6 @@ export default function(msg, expanded) {
   95   115 			+ util.parseContacts(msg.bccList).length
   96   116 		) > 1,
   97   117 	});
   98    -1 	var e = util.html2element(html);
   99   118 
  100   119 	autoMarkAsRead(e, msg);
  101   120 

diff --git a/content/js/util.js b/content/js/util.js

@@ -38,12 +38,17 @@ export var getBody = function(msgPart) {
   38    38 	}
   39    39 };
   40    40 
   41    -1 export var html2element = function(html) {
   42    -1 	// thunderbird 60 will remove some elements when directly assigning to
   43    -1 	// innerHTML
   44    -1 	var parser = new DOMParser();
   45    -1 	var doc = parser.parseFromString('<!DOCTYPE html>\n' + html, 'text/html');
   46    -1 	return doc.body.children[0];
   -1    41 export var h = function(tag, attrs, children) {
   -1    42 	var el = document.createElement(tag);
   -1    43 	for (let attr in attrs) {
   -1    44 		el.setAttribute(attr, attrs[attr]);
   -1    45 	}
   -1    46 	for (let child of children) {
   -1    47 		if (child) {
   -1    48 			el.append(child);
   -1    49 		}
   -1    50 	}
   -1    51 	return el;
   47    52 };
   48    53 
   49    54 export var createIcon = function(key) {
@@ -62,23 +67,15 @@ export var createIcon = function(key) {
   62    67 
   63    68 export var createDate = function(date) {
   64    69 	var now = new Date();
   65    -1 	var e = document.createElement('time');
   66    -1 	e.className = 'date';
   67    -1 	if (date.toDateString() === now.toDateString()) {
   68    -1 		e.textContent = date.toLocaleTimeString('sv');
   69    -1 	} else {
   70    -1 		e.textContent = date.toLocaleDateString('sv');
   71    -1 	}
   72    -1 	e.title = date.toLocaleString();
   73    -1 	return e;
   -1    70 	return h('time', {'class': 'date', 'title': date.toLocaleString()}, [
   -1    71 		date.toDateString() === now.toDateString()
   -1    72 			? date.toLocaleTimeString('sv')
   -1    73 			: date.toLocaleDateString('sv')
   -1    74 	]);
   74    75 };
   75    76 
   76    77 export var createAlert = function(text, icon, level) {
   77    -1 	var e = document.createElement('div');
   78    -1 	e.className = 'alert alert--' + level;
   79    -1 	e.textContent = text;
   80    -1 	e.prepend(createIcon(icon));
   81    -1 	return e;
   -1    78 	return h('div', {'class': `alert alert--${level}`}, [createIcon(icon), text]);
   82    79 };
   83    80 
   84    81 export var pseudoRandomColor = function(s) {

diff --git a/content/main.html b/content/main.html

@@ -6,71 +6,6 @@
    6     6 		<link rel="stylesheet" href="style.css" />
    7     7 	</head>
    8     8 	<body>
    9    -1 		<script id="message-template" type="text/template">
   10    -1 			<article class="message {{#isExpanded}}is-expanded{{/isExpanded}}" id="msg-{{ id }}" tabindex="-1">
   11    -1 				<style>
   12    -1 					{{#author}}
   13    -1 						.message__author[href="mailto:{{ email }}"] { color: {{#authorColor}}{{ email }}{{/authorColor}} }
   14    -1 					{{/author}}
   15    -1 				</style>
   16    -1 				<header class="message__header">
   17    -1 					<button class="star {{#isFlagged}}is-active{{/isFlagged}}" data-action="toggleFlagged">{{#icon}}star{{/icon}}</button>
   18    -1 					{{#author}}<a class="message__author" href="mailto:{{ email }}">{{ name }}</a>{{/author}}
   19    -1 					<span class="message__recipients">
   20    -1 						{{#str}}to{{/str}}
   21    -1 						{{#recipients}}
   22    -1 							<a href="mailto:{{ email }}">{{ name }}</a>
   23    -1 						{{/recipients}}
   24    -1 						{{#cc}}
   25    -1 							<a class="message__recipients__cc" href="mailto:{{ email }}">{{ name }}</a>
   26    -1 						{{/cc}}
   27    -1 						{{#bcc}}
   28    -1 							<a class="message__recipients__bcc" href="mailto:{{ email }}">{{ name }}</a>
   29    -1 						{{/bcc}}
   30    -1 					</span>
   31    -1 					<span class="message__summary">{{ summary }}</span>
   32    -1 
   33    -1 					{{#hasAttachments}}{{#icon}}attachment{{/icon}}{{/hasAttachments}}
   34    -1 					{{#dateFilter}}{{ date }}{{/dateFilter}}
   35    -1 
   36    -1 					<span class="message__actions">
   37    -1 						{{#canReplyAll}}<button class="button" title="{{#str}}replyAll{{/str}}" data-action="replyAll">{{#icon}}reply_all{{/icon}}</button{{/canReplyAll}}
   38    -1 						{{^canReplyAll}}
   39    -1 							{{#canReplyToList}}<button class="button" title="{{#str}}replyList{{/str}}" data-action="replyToList">{{#icon}}list{{/icon}}</button{{/canReplyToList}}
   40    -1 							{{^canReplyToList}}<button class="button" title="{{#str}}reply{{/str}}" data-action="replyToSender">{{#icon}}reply{{/icon}}</button{{/canReplyToList}}
   41    -1 						{{/canReplyAll}}
   42    -1 
   43    -1 						><button class="button dropdownToggle" title="{{#str}}more{{/str}}">{{#icon}}menu{{/icon}}</button>
   44    -1 						<div class="dropdown">
   45    -1 							<button class="dropdown-item" data-action="replyToSender">{{#icon}}reply{{/icon}} {{#str}}reply{{/str}}</button>
   46    -1 							{{#canReplyAll}}<button class="dropdown-item" data-action="replyAll">{{#icon}}reply_all{{/icon}} {{#str}}replyAll{{/str}}</button>{{/canReplyAll}}
   47    -1 							{{#canReplyToList}}<button class="dropdown-item" data-action="replyToList">{{#icon}}list{{/icon}} {{#str}}replyList{{/str}}</button>{{/canReplyToList}}
   48    -1 							<button class="dropdown-item" data-action="forward">{{#icon}}forward{{/icon}} {{#str}}forward{{/str}}</button>
   49    -1 							<button class="dropdown-item" data-action="editAsNew">{{#icon}}create{{/icon}} {{#str}}edit{{/str}}</button>
   50    -1 							<button class="dropdown-item" data-action="viewClassic">{{#icon}}open_in_new{{/icon}} {{#str}}viewClassic{{/str}}</button>
   51    -1 							<button class="dropdown-item" data-action="viewSource">{{#icon}}code{{/icon}} {{#str}}viewSource{{/str}}</button>
   52    -1 						</div>
   53    -1 					</span>
   54    -1 				</header>
   55    -1 
   56    -1 				<div class="message__details">
   57    -1 					{{#isJunk}}
   58    -1 						<div class="alert alert--warning">{{#icon}}mode_heat{{/icon}} {{#str}}junk{{/str}}</div>
   59    -1 					{{/isJunk}}
   60    -1 
   61    -1 					<footer class="message__footer">
   62    -1 						<ul class="attachments">
   63    -1 							{{#attachments}}
   64    -1 								<li>
   65    -1 									<a class="attachment" href="{{ url }}">{{#icon}}attachment{{/icon}} {{ name }}</a>
   66    -1 								</li>
   67    -1 							{{/attachments}}
   68    -1 						</ul>
   69    -1 					</footer>
   70    -1 				</div>
   71    -1 			<article>
   72    -1 		</script>
   73    -1 
   74     9 		<header class="conversation__header">
   75    10 			<h1 class="conversation__subject">Loading…</h1>
   76    11 		</header>