Support pasting and showing images in the wiki.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4655 ed5197a5-7fde-0310-b194-c3ffbd925b24
This commit is contained in:
		@@ -1,5 +1,5 @@
 | 
			
		||||
{
 | 
			
		||||
  "type": "tildefriends-app",
 | 
			
		||||
  "emoji": "📝",
 | 
			
		||||
  "previous": "&r+JXDhWclHwMbeZHYjueOnSPaYCAXUlnl1Gyjgw6TxM=.sha256"
 | 
			
		||||
  "previous": "&C6kk2ozHZKVTtxbEqLOD/xbsCjosnLb6HJWZuPZrtS8=.sha256"
 | 
			
		||||
}
 | 
			
		||||
@@ -20,9 +20,9 @@ class TfWikiDocElement extends LitElement {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	markdown(md) {
 | 
			
		||||
		var reader = new commonmark.Parser({safe: true});
 | 
			
		||||
		var writer = new commonmark.HtmlRenderer();
 | 
			
		||||
		var parsed = reader.parse(md || '');
 | 
			
		||||
		let reader = new commonmark.Parser({safe: true});
 | 
			
		||||
		let writer = new commonmark.HtmlRenderer();
 | 
			
		||||
		let parsed = reader.parse(md || '');
 | 
			
		||||
		let walker = parsed.walker();
 | 
			
		||||
		let event;
 | 
			
		||||
		while ((event = walker.next())) {
 | 
			
		||||
@@ -33,6 +33,10 @@ class TfWikiDocElement extends LitElement {
 | 
			
		||||
						node.destination.indexOf('/') == -1) {
 | 
			
		||||
						node.destination = `#${this.wiki?.name}/${node.destination}`;
 | 
			
		||||
					}
 | 
			
		||||
				} else if (node.type == 'image') {
 | 
			
		||||
					if (node.destination.startsWith('&')) {
 | 
			
		||||
						node.destination = '/' + node.destination + '/view';
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
@@ -71,13 +75,12 @@ class TfWikiDocElement extends LitElement {
 | 
			
		||||
			key: this.value.id,
 | 
			
		||||
			parent: this.value.parent,
 | 
			
		||||
			blob: id,
 | 
			
		||||
			mentions: this.blob.match(/(&.{44}.sha256)/g)?.map(x => ({link: x})),
 | 
			
		||||
		};
 | 
			
		||||
		if (draft) {
 | 
			
		||||
			message.recps = this.value.editors;
 | 
			
		||||
			print(message);
 | 
			
		||||
			message = await tfrpc.rpc.encrypt(this.whoami, this.value.editors, JSON.stringify(message));
 | 
			
		||||
		}
 | 
			
		||||
		print(message);
 | 
			
		||||
		await tfrpc.rpc.appendMessage(this.whoami, message);
 | 
			
		||||
		this.is_editing = false;
 | 
			
		||||
	}
 | 
			
		||||
@@ -90,6 +93,74 @@ class TfWikiDocElement extends LitElement {
 | 
			
		||||
		return this.append_message(false);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	convert_to_format(buffer, type, mime_type) {
 | 
			
		||||
		return new Promise(function(resolve, reject) {
 | 
			
		||||
			let img = new Image();
 | 
			
		||||
			img.onload = function() {
 | 
			
		||||
				let canvas = document.createElement('canvas');
 | 
			
		||||
				let width_scale = Math.min(img.width, 1024) / img.width;
 | 
			
		||||
				let height_scale = Math.min(img.height, 1024) / img.height;
 | 
			
		||||
				let scale = Math.min(width_scale, height_scale);
 | 
			
		||||
				canvas.width = img.width * scale;
 | 
			
		||||
				canvas.height = img.height * scale;
 | 
			
		||||
				let context = canvas.getContext('2d');
 | 
			
		||||
				context.drawImage(img, 0, 0, canvas.width, canvas.height);
 | 
			
		||||
				let data_url = canvas.toDataURL(mime_type);
 | 
			
		||||
				let result = atob(data_url.split(',')[1]).split('').map(x => x.charCodeAt(0));
 | 
			
		||||
				resolve(result);
 | 
			
		||||
			};
 | 
			
		||||
			img.onerror = function(event) {
 | 
			
		||||
				reject(new Error('Failed to load image.'));
 | 
			
		||||
			};
 | 
			
		||||
			let raw = Array.from(new Uint8Array(buffer)).map(b => String.fromCharCode(b)).join('');
 | 
			
		||||
			let original = `data:${type};base64,${btoa(raw)}`;
 | 
			
		||||
			img.src = original;
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	async add_file(editor, file) {
 | 
			
		||||
		try {
 | 
			
		||||
			let self = this;
 | 
			
		||||
			let buffer = await file.arrayBuffer();
 | 
			
		||||
			let type = file.type;
 | 
			
		||||
			if (type.startsWith('image/')) {
 | 
			
		||||
				let best_buffer;
 | 
			
		||||
				let best_type;
 | 
			
		||||
				for (let format of ['image/png', 'image/jpeg', 'image/webp']) {
 | 
			
		||||
					let test_buffer = await self.convert_to_format(buffer, file.type, format);
 | 
			
		||||
					if (!best_buffer || test_buffer.length < best_buffer.length) {
 | 
			
		||||
						best_buffer = test_buffer;
 | 
			
		||||
						best_type = format;
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				buffer = best_buffer;
 | 
			
		||||
				type = best_type;
 | 
			
		||||
			} else {
 | 
			
		||||
				buffer = Array.from(new Uint8Array(buffer));
 | 
			
		||||
			}
 | 
			
		||||
			let id = await tfrpc.rpc.store_blob(buffer);
 | 
			
		||||
			let name = type.split('/')[0] + ':' + file.name;
 | 
			
		||||
			editor.value += `\n`;
 | 
			
		||||
			self.on_edit({srcElement: editor});
 | 
			
		||||
		} catch(e) {
 | 
			
		||||
			alert(e?.message);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	paste(event) {
 | 
			
		||||
		let self = this;
 | 
			
		||||
		for (let item of event.clipboardData.items) {
 | 
			
		||||
			if (item.type?.startsWith('image/')) {
 | 
			
		||||
				let file = item.getAsFile();
 | 
			
		||||
				if (!file) {
 | 
			
		||||
					continue;
 | 
			
		||||
				}
 | 
			
		||||
				self.add_file(event.srcElement, file);
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	render() {
 | 
			
		||||
		let value = JSON.stringify(this.value);
 | 
			
		||||
		if (this.blob_for_value != value) {
 | 
			
		||||
@@ -110,7 +181,9 @@ class TfWikiDocElement extends LitElement {
 | 
			
		||||
				<textarea
 | 
			
		||||
					?hidden=${!this.is_editing}
 | 
			
		||||
					style="flex: 1 1; min-height: 10em"
 | 
			
		||||
					@input=${this.on_edit} .value=${this.blob ?? ''}></textarea>
 | 
			
		||||
					@input=${this.on_edit}
 | 
			
		||||
					@paste=${this.paste}
 | 
			
		||||
					.value=${this.blob ?? ''}></textarea>
 | 
			
		||||
				<div style="flex: 1 1">${unsafeHTML(this.markdown(this.blob))}</div>
 | 
			
		||||
			</div>
 | 
			
		||||
		`;
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user