diff --git a/apps/wiki.json b/apps/wiki.json index 232b897d..d717a556 100644 --- a/apps/wiki.json +++ b/apps/wiki.json @@ -1,5 +1,5 @@ { "type": "tildefriends-app", "emoji": "📝", - "previous": "&r+JXDhWclHwMbeZHYjueOnSPaYCAXUlnl1Gyjgw6TxM=.sha256" + "previous": "&C6kk2ozHZKVTtxbEqLOD/xbsCjosnLb6HJWZuPZrtS8=.sha256" } \ No newline at end of file diff --git a/apps/wiki/tf-wiki-doc.js b/apps/wiki/tf-wiki-doc.js index f5e0b106..265c2240 100644 --- a/apps/wiki/tf-wiki-doc.js +++ b/apps/wiki/tf-wiki-doc.js @@ -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![${name}](${id})`; + 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 { + @input=${this.on_edit} + @paste=${this.paste} + .value=${this.blob ?? ''}>