diff --git a/txt.js/txt.js b/txt.js/txt.js index 42f977c..2177fb3 100644 --- a/txt.js/txt.js +++ b/txt.js/txt.js @@ -1,470 +1,60 @@ -let txtjs = {} +this.txtjs = {}; txtjs.open = function(url) { - fetch(url).then(function(response) { - return response.text() - }).then(txtjs.renderText) -} + Ox.load(function() { + Ox.get(url, function(text) { + var $body = Ox.$('body') + .css({ + backgroundColor: 'rgb(255, 255, 255)', + overflowX: 'hidden' + }), + $text = Ox.$('
') + .css({ + padding: '10% 20% 10% 10%', + fontFamily: 'Georgia, Palatino, DejaVu Serif, Book Antiqua, Palatino Linotype, Times New Roman, serif', + fontSize: '20px', + lineHeight: '30px' + }) + .appendTo($body), + $scroll = Ox.$('
') + .css({ + position: 'fixed', + right: '24px', + top: '16px', + width: '7%', + bottom: '16px', + overflow: 'hidden' + }) + .appendTo($body), + $scrollText = Ox.$('
') + .css({ + fontSize: '2px', + lineHeight: '3px', + cursor: 'pointer', + WebkitUserSelect: 'none' + }) + .on({ + mousedown: function(e) { + var offset = 'offsetY' in e ? e.offsetY : e.layerY; + document.documentElement.scrollTop = offset / factor + margin - 16; + } + }) + .appendTo($scroll), + factor, margin; + text = Ox.encodeHTMLEntities(text) + .replace(/\r\n/g, '\n') + .replace(/[\r\n]/g, '
'); + $text.html(text); + $scrollText.html(text); + window.onresize = function() { + margin = $text.width() * 0.1; + factor = $scrollText[0].clientHeight / $text[0].clientHeight; + }; + window.onscroll = function() { + $scroll[0].scrollTop = (window.pageYOffset - margin + 16) * factor; + }; + window.onresize(); + }); + }); +}; -txtjs.mark = function(notes) { - notes.forEach(function(note) { - if (!txtjs.notes.includes(note)) { - txtjs.notes.push(note) - } - txtjs.renderNote(note) - }) -} - -txtjs.notes = [] - -;(function() { - let listener = addEventListener || attachEvent - let event = addEventListener ? 'message' : 'onmessage' - listener(event, function(e) { - let message = JSON.parse(e.message || e.data) - txtjs.onMessage(message.action, message.data) - }) -}()) - -txtjs.addNoteFromSelection = function() { - let note = txtjs.getNoteFromSelection() - if (!note || txtjs.noteExists(note)) { - return - } - txtjs.renderNote(note) - txtjs.notes.push(note) - txtjs.selectNote(note.id) - getSelection().removeAllRanges() - txtjs.postMessage('addNote', note) -} - -txtjs.cancelEdit = function() { - let editing = document.querySelector('g.editing') - editing && editing.classList.remove('editing') -} - -txtjs.createSVGElement = function(name) { - return document.createElementNS('http://www.w3.org/2000/svg', name) -} - -txtjs.editNote = function() { - let editing = document.querySelector('g.editing') - let note = txtjs.getNoteFromSelection() - if (!editing || !note) { - return - } - let id = txtjs.getNoteId(editing) - note = Object.assign(Ox.getObjectById(txtjs.notes, id), { - position: note.position, - text: note.text - }) - document.querySelector('svg').removeChild(editing) - txtjs.renderNote(note) - txtjs.selectNote(note.id) - txtjs.postMessage('editNote', { - id: id, - position: note.position, - text: note.text - }) -} - -txtjs.getNewId = function() { - let ids = txtjs.notes.map(function(note) { - return note.id - }) - let i = 1 - while (ids.includes(Ox.encodeBase26(i))) { - i++ - } - return Ox.encodeBase26(i) -} - -txtjs.getNoteId = function(element) { - let classNames = Array.from(element.classList).filter(function(className) { - return className.startsWith('note-') - }) - if (classNames.length == 0) { - return - } - return classNames[0].substr(5) -} - -txtjs.getNoteFromSelection = function() { - let selection = getSelection() - try { - var range = selection.getRangeAt(0) - } catch(e) { - return - } - if (range.collapsed) { - return - } - let container = range.commonAncestorContainer - if (container.id != 'txt') { - while (container != document.body) { - container = container.parentElement - if (container.id == 'txt') { - break - } - } - } - if (container.id != 'txt') { - return - } - let position = txtjs.getPosition(range) - let pos = position.split(':').map(function(v) { - return parseInt(v) - }) - let note = { - id: txtjs.getNewId(), - position: position, - text: txtjs.text.substr(pos[0], pos[1] - pos[0]), - editable: true - } - if (txtjs.noteExists(note)) { - return - } - return note -} - -txtjs.getPosition = function(range) { - let container = document.querySelector('#txt') - let nodes = Array.from(container.childNodes) - let startNodeIndex = nodes.indexOf(range.startContainer) - let endNodeIndex = nodes.indexOf(range.endContainer) - let index = 0 - let start = 0 - let end = 0 - for (let i = 0; i <= endNodeIndex; i++) { - if (i == startNodeIndex) { - start = index + range.startOffset - } - if (i == endNodeIndex) { - end = index + range.endOffset - } - if (nodes[i].nodeType == 1) { //
- index++ - } else { - index += nodes[i].textContent.length - } - } - return start + ':' + end -} - -txtjs.getRange = function(id, start, end) { - let startContainer, startOffset, endContainer, endOffset - let container = document.querySelector('#' + id) - let nodes = Array.from(container.childNodes) - let index = 0 - for (let i = 0; i < nodes.length; i++) { - if (start < index + nodes[i].textContent.length && startOffset === void 0) { - startContainer = nodes[i] - startOffset = start - index - } - if (end <= index + nodes[i].textContent.length) { - endContainer = nodes[i] - endOffset = end - index - break - } - if (nodes[i].nodeType == 1) { //
- index++ - } else { - index += nodes[i].textContent.length - } - } - let range = document.createRange() - range.setStart(startContainer, startOffset) - range.setEnd(endContainer, endOffset) - return range -} - -txtjs.getSelectedNote = function() { - let elements = Array.from(document.querySelectorAll('g.selected')) - if (elements.length == 0) { - return - } - let id = txtjs.getNoteId(elements[0]) - return Object.assign(Ox.getObjectById(txtjs.notes, id), { - elements: elements - }) -} - -txtjs.moveNote = function() { - let selected = txtjs.getSelectedNote() - if (!selected || !selected.elements[0].classList.contains('editable')) { - return - } - selected.elements.forEach(function(element) { - element.classList.add('editing') - }) -} - -txtjs.noteExists = function(note) { - return txtjs.notes.some(function(note_) { - return note_.position == note.position - }) -} - -txtjs.onMessage = function(action, data) { - -} - -txtjs.postMessage = function(action, data) { - console.log('postMessage', action, data) - parent.postMessage(JSON.stringify({action: action, data: data}), '*') -} - -txtjs.removeNote = function() { - let selected = txtjs.getSelectedNote() - if (!selected) { - return - } - let id = txtjs.getNoteId(selected.elements[0]) - selected.elements.forEach(function(element) { - element.parentElement.removeChild(element) - }) - let index = txtjs.notes.map(function(note) { - return note.id - }).indexOf(id) - txtjs.notes.splice(index, 1) - txtjs.postMessage('removeNote', { - id: id - }) -} - -txtjs.renderNote = function(note) { - let pos = note.position.split(':').map(function(v) { - return parseInt(v) - }) - let ids = ['txt', 'txt-scroll'] - ids.forEach(function(id) { - let range = txtjs.getRange(id, pos[0], pos[1]) - let rects = Array.from(range.getClientRects()) - let size = rects.reduce(function(width, rect) { - return width + rect.width - }, 0) - let maxHeight = 8192 - let firstIndex = Math.floor((rects[0].top + window.pageYOffset) / maxHeight) - let lastIndex = Math.floor((rects[rects.length - 1].top + window.pageYOffset + rects[rects.length - 1].height) / maxHeight) - for (let index = firstIndex; index <= lastIndex; index++) { - let g = txtjs.createSVGElement('g') - g.classList.add('note-' + note.id) - g.classList.add('selectable') - if (note.editable) { - g.classList.add('editable') - } - g.setAttribute('data-size', size) - g.setAttribute('pointer-events', id == 'txt' ? 'all' : 'none') - rects.forEach(function(rect) { - let element = txtjs.createSVGElement('rect') - let x = id == 'txt' ? rect.left - : rect.left - document.querySelector('#scroll').getBoundingClientRect().left + 8 - let y = id == 'txt' ? rect.top + window.pageYOffset - index * maxHeight - : rect.top + document.querySelector('#scroll').scrollTop - 16 - index * maxHeight - element.setAttribute('x', x) - element.setAttribute('y', y) - element.setAttribute('width', rect.width) - element.setAttribute('height', rect.height) - g.appendChild(element) - }) - let svg = document.querySelector('svg#svg-' + id + '-' + index) - for (let i = 0; i < svg.children.length; i++) { - let childSize = parseInt(svg.children[i].getAttribute('data-size')) - if (size > childSize) { - svg.insertBefore(g, svg.children[i]) - break - } - } - if (!g.parentElement) { - svg.appendChild(g) - } - } - }) -} - -txtjs.renderSVG = function(id, index, width, height) { - function mousedown(e) { - svg.addEventListener('mouseup', mouseup) - timeout = setTimeout(function() { - svg.removeEventListener('mouseup', mouseup) - e.target.classList.remove('selectable') - e.target.setAttribute('pointer-events', 'none') - document.addEventListener('mouseup', function() { - e.target.classList.add('selectable') - e.target.setAttribute('pointer-events', 'all') - }) - }, 250) - } - function mouseup(e) { - clearTimeout(timeout) - txtjs.selectNote(txtjs.getNoteId(e.target.parentElement)) - } - let timeout - let svg = txtjs.createSVGElement('svg') - svg.setAttribute('id', 'svg-' + id + '-' + index) - svg.setAttribute('pointer-events', 'none') - if (id == 'txt') { - svg.style.left = 0 - } else { - svg.style.right = '8px' - } - svg.style.top = index * 8192 + 'px' - svg.style.width = width + 'px' - svg.style.height = height + 'px' - if (id == 'txt') { - svg.addEventListener('mousedown', mousedown) - } - let parentElement = id == 'txt' ? document.body : document.querySelector('#scroll') - parentElement.appendChild(svg) -} - -txtjs.renderSVGs = function() { - let maxHeight = 8192 - let ids = ['txt', 'txt-scroll'] - ids.forEach(function(id) { - let rect = document.querySelector('#' + id).getBoundingClientRect() - let lastHeight = rect.height % maxHeight - let n = Math.ceil(rect.height / maxHeight) - for (let i = 0; i < n; i++) { - txtjs.renderSVG(id, i, rect.width, i < n - 1 ? maxHeight : lastHeight) - } - }) -} - -txtjs.renderText = function(text) { - txtjs.text = text - html = text.replace(//g, '>') - html = html.replace().replace(/\r\n/g, '\n').replace(/[\r\n]/g, '
') - txtjs.html = Ox.encodeHTMLEntities(text).replace(/\r\n/g, '\n').replace(/[\r\n]/g, '
') - window.addEventListener('resize', onResize) - window.addEventListener('resizeend', onResizeend) - window.addEventListener('scroll', onScroll) - document.addEventListener('keydown', function(e) { - console.log(e.keyCode) - if (e.keyCode == 13) { // ENTER - txtjs.editNote() - } else if (e.keyCode == 27) { // ESCAPE - txtjs.selectNote(null) - txtjs.cancelEdit() - } else if (e.keyCode == 37) { // LEFT - txtjs.selectNextNote(-1) - } else if (e.keyCode == 39) { // RIGHT - txtjs.selectNextNote(1) - } else if (e.keyCode == 46) { // DELETE - txtjs.removeNote() - } else if (e.keyCode == 77) { // M - txtjs.moveNote() - } else if (e.keyCode == 78) { // N - txtjs.addNoteFromSelection() - } - }) - let style = document.createElement('style') - style.innerText = [ - 'svg { mix-blend-mode: multiply; position: absolute }', - 'g { fill: rgb(255, 255, 192); fill-opacity: 0.5 }', - 'g.selectable { cursor: pointer }', - 'g.editable { fill: rgb(255, 255, 0) }', - 'g.selected { fill: rgb(224, 240, 255) }', - 'g.editable.selected { fill: rgb(128, 192, 255) }', - 'g.editable.editing { fill: rgb(128, 255, 128) }', - '::selection { background: rgb(192, 192, 192) }' - ].join('\n') - document.head.appendChild(style) - document.body.style.backgroundColor = 'rgb(255, 255, 255)' - document.body.style.margin = 0 - document.body.style.overflowX = 'hidden' - let textElement = document.createElement('div') - textElement.id = 'txt' - textElement.style.fontFamily = 'Georgia, Palatino, DejaVu Serif, Book Antiqua, Palatino Linotype, Times New Roman, serif', - textElement.style.fontSize = '20px' - textElement.style.lineHeight = '30px' - textElement.style.padding = '10% 20% 10% 10%' - textElement.innerHTML = txtjs.html - textElement.addEventListener('mousedown', function() { - txtjs.selectNote(null) - }) - document.body.appendChild(textElement) - let scrollElement = document.createElement('div') - scrollElement.id = 'scroll' - scrollElement.style.bottom = '16px' - scrollElement.style.overflow = 'hidden' - scrollElement.style.position = 'fixed' - scrollElement.style.right = '24px' - scrollElement.style.width = '7%' - scrollElement.style.top = '16px' - document.body.appendChild(scrollElement) - let scrollTextElement = document.createElement('div') - scrollTextElement.id = 'txt-scroll' - scrollTextElement.style.cursor = 'pointer' - scrollTextElement.style.fontFamily = 'Georgia, Palatino, DejaVu Serif, Book Antiqua, Palatino Linotype, Times New Roman, serif', - scrollTextElement.style.fontSize = '2px' - scrollTextElement.style.lineHeight = '3px' - scrollTextElement.style.MozUserSelect = 'none' - scrollTextElement.style.WebkitUserSelect = 'none' - scrollTextElement.innerHTML = txtjs.html - scrollTextElement.addEventListener('mousedown', function(e) { - let offset = 'offsetY' in e ? e.offsetY : e.layerY - document.documentElement.scrollTop = offset / factor + margin - 16 - }) - scrollElement.appendChild(scrollTextElement) - txtjs.renderSVGs() - let factor, margin - onResize() - function onResize() { - factor = scrollTextElement.clientHeight / textElement.clientHeight - margin = textElement.offsetWidth * 0.1 - setTimeout(function() { - Array.from(document.querySelectorAll('svg')).forEach(function(svg) { - svg.parentElement.removeChild(svg) - }) - txtjs.renderSVGs() - txtjs.mark(txtjs.notes) - }) - } - function onResizeend() { - - } - function onScroll() { - scrollElement.scrollTop = (window.pageYOffset - margin + 16) * factor - } - txtjs.mark([{id: 'A', position: '417:468', editable: false}]) -} - -txtjs.selectNextNote = function(direction) { - let selected = txtjs.getSelectedNote() - if (!selected) { - return - } - let id = txtjs.getNoteId(selected.elements[0]) - let ids = txtjs.notes.sort(function(a, b) { - return parseInt(a.position.split()[0]) - parseInt(b.position.split()[0]) - }).map(function(note) { - return note.id - }) - txtjs.selectNote(ids[Ox.mod(ids.indexOf(id) + direction, ids.length)]) -} - -txtjs.selectNote = function(id) { - let selected = txtjs.getSelectedNote() - if (selected) { - selected.elements.forEach(function(element) { - element.classList.remove('selected') - }) - } - if (id) { - let editing = Array.from(document.querySelectorAll('g.editing')) - if (editing.length && txtjs.getNoteId(editing) != id) { - editing.forEach(function(element) { - element.classList.remove('editing') - }) - } - let elements = Array.from(document.querySelectorAll('g.note-' + id)) - elements.forEach(function(element) { - element.classList.add('selected') - }) - // FIXME: SCROLL - // elements[0].scrollIntoView() - // window.scrollTo(0, elements[0].offsetTop) - } - txtjs.postMessage('selectNote', {id: id}) -}