├── LICENSE
├── README.md
├── dist
├── cobalt.editor-lite.js
├── cobalt.editor.js
└── cobalt.js
├── docs
├── api.md
├── cobalt-fast-render.txt
├── methods.txt
└── todo.txt
├── examples
├── cobalt.document.html
├── cobalt.editor-lite.html
├── cobalt.editor.html
└── styles.css
├── mjs
├── cobalt.annotation.js
├── cobalt.annotationlist.js
├── cobalt.document.js
├── cobalt.editor.js
├── cobalt.editor.keyboard.js
├── cobalt.editor.selection.js
├── cobalt.fragment.js
├── cobalt.html.js
├── cobalt.js
├── cobalt.mime.js
├── cobalt.range.js
└── cobalt.singlerange.js
├── package.json
├── rollup.config.js
├── src
├── cobalt.annotation.js
├── cobalt.dom.js
├── cobalt.editor-lite.js
├── cobalt.editor.js
├── cobalt.editor.selection.js
├── cobalt.fragment.js
├── cobalt.html.js
├── cobalt.js
├── cobalt.keyboard.js
├── cobalt.mime.js
└── cobalt.range.js
├── test
├── cobalt.annotation.js
├── cobalt.fragment.js
├── cobalt.html.js
└── cobalt.range.js
└── webpack.config.js
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Auke
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # cobalt
2 | A radically simpler way to markup documents.
3 |
4 | Cobalt is a follow up on [Hope](https://poef.github.io/hope/). It is a very simple implementation of
5 | Ted Nelson's ['Edit Decision List'](https://en.wikipedia.org/wiki/Edit_decision_list) concept for markup.
6 |
7 | Cobalt is a research implementation, to explore this concept better. But Hope has found its way into
8 | production code. It is the engine of change in [SimplyEdit](https://simplyedit.io/). This is because
9 | the browser API for contentEditable (the WYSIWYG editor built into almost all browsers) is pretty useless.
10 | If you want to implement your own replacement for the [execCommand](https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand)
11 | API, you'll need to apply the change to the DOM yourself. This is not trivial. With a good 1 to 1 translation
12 | from html to Hope and back, applying changes to the DOM do become trivial.
13 |
14 | ## Why?
15 |
16 | There are many subtle issues with the way markup in general and HTML in particular works. But the most important
17 | issue is about editing. HTML was designed to be written in a text editor, not in a What-You-See-Is-What-You-Get
18 | (WYSIWYG) editor.
19 |
20 | So to be able to see which part of a text has which markup applied to it, the natural solution is to wrap
21 | that part in a start and end tag. To see which start tag is ended by the end tag, the end tag re-states the tag
22 | name. So you get markup like this: `italic text`.
23 |
24 | To add an extra layer of markup, you add another start and end tag. But these tags may not overlap. You cannot
25 | do this: `italic and bold text`. Instead you do this:
26 | `italic and bold text`.
27 |
28 | In effect you are building a tree structure. When this tree structure gets a bit more complex, it becomes
29 | difficult to follow in a text editor, unless you indent the markup. Each level of markup is indented, so the
30 | tree becomes visible. But this adds extra whitespace and linefeeds, that are only there to allow you to indent
31 | the markup.
32 |
33 | ## Results so far
34 |
35 | I've been working on Hope and later Cobalt for quite some time, so what have I learned so far?
36 |
37 |
--------------------------------------------------------------------------------
/dist/cobalt.editor-lite.js:
--------------------------------------------------------------------------------
1 | (function (factory) {
2 | typeof define === 'function' && define.amd ? define(factory) :
3 | factory();
4 | }((function () { 'use strict';
5 |
6 | /*
7 | TODO:
8 | - add obligatory parents/children
9 | overwrite these with the correct tags/entries when available
10 | - create a hook system that allows alternative parsing and rendering of elements
11 | e.g. when parsing html turn into
12 | a cobalt-html-1 element, that renders the same html again. This allows you to keep
13 | nested html elements without text content.
14 | - allow the same hook system to render alternative elements when html won't allow the
15 | original.
16 | e.g. two overlapping anchors, render the overlapping part as
17 | text. Which combined with some javascript
18 | allows you to render and follow overlapping links.
19 | */
20 |
21 | /**
22 | * These rules define the behaviour of the rendering as well as the editor.
23 | */
24 | var rules = {
25 | block: ['h1','h2','h3','p','ol','ul','li','blockquote','hr','div'],
26 | inline: ['em','strong','a','img','br','span'],
27 | obligatoryChild: {
28 | 'ol': ['li'],
29 | 'ul': ['li']
30 | },
31 | obligatoryParent: {
32 | 'li': ['ol','ul']
33 | },
34 | nextTag: {
35 | 'h1' : 'p',
36 | 'h2' : 'p',
37 | 'h3' : 'p',
38 | 'p' : 'p',
39 | 'li' : 'li'
40 | },
41 | cannotHaveChildren: {
42 | 'br' : true,
43 | 'img': true,
44 | 'hr' : true
45 | },
46 | cannotHaveText: {
47 | 'ul': true,
48 | 'ol': true
49 | },
50 | specialRules: {
51 | 'a': function(node) {
52 | do {
53 | if (node.tagName && node.tagName=='a') {
54 | return false;
55 | }
56 | node = node.parentNode;
57 | } while(node);
58 | return true;
59 | }
60 | }
61 | };
62 | rules.alltags = rules.block.concat(rules.inline);
63 | rules.nesting = {
64 | 'h1': rules.inline,
65 | 'h2': rules.inline,
66 | 'h3': rules.inline,
67 | 'p': rules.inline,
68 | 'ol': ['li'],
69 | 'ul': ['li'],
70 | 'li': rules.alltags.filter(function(tag) { return tag!='li'; }),
71 | 'blockquote': rules.alltags,
72 | 'div': rules.alltags,
73 | 'em': rules.inline,
74 | 'strong': rules.inline,
75 | 'span': rules.inline,
76 | 'a': rules.inline.filter(function(tag) { return tag!='a'; })
77 | };
78 |
79 | })));
80 |
--------------------------------------------------------------------------------
/dist/cobalt.editor.js:
--------------------------------------------------------------------------------
1 | /******/ (function(modules) { // webpackBootstrap
2 | /******/ // The module cache
3 | /******/ var installedModules = {};
4 |
5 | /******/ // The require function
6 | /******/ function __webpack_require__(moduleId) {
7 |
8 | /******/ // Check if module is in cache
9 | /******/ if(installedModules[moduleId])
10 | /******/ return installedModules[moduleId].exports;
11 |
12 | /******/ // Create a new module (and put it into the cache)
13 | /******/ var module = installedModules[moduleId] = {
14 | /******/ exports: {},
15 | /******/ id: moduleId,
16 | /******/ loaded: false
17 | /******/ };
18 |
19 | /******/ // Execute the module function
20 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
21 |
22 | /******/ // Flag the module as loaded
23 | /******/ module.loaded = true;
24 |
25 | /******/ // Return the exports of the module
26 | /******/ return module.exports;
27 | /******/ }
28 |
29 |
30 | /******/ // expose the modules object (__webpack_modules__)
31 | /******/ __webpack_require__.m = modules;
32 |
33 | /******/ // expose the module cache
34 | /******/ __webpack_require__.c = installedModules;
35 |
36 | /******/ // __webpack_public_path__
37 | /******/ __webpack_require__.p = "";
38 |
39 | /******/ // Load entry module and return exports
40 | /******/ return __webpack_require__(0);
41 | /******/ })
42 | /************************************************************************/
43 | /******/ ([
44 | /* 0 */
45 | /***/ function(module, exports, __webpack_require__) {
46 |
47 | cobalt.editor = (function(self) {
48 |
49 |
50 | function cobaltEditor(el, debug)
51 | {
52 | var editor = this;
53 | this.container = ( typeof el == 'string' ? document.querySelector(el) : el );
54 | this.container.contentEditable = true;
55 | this.fragment = cobalt.fragment('','');
56 | this.selection = cobalt.editor.selection(this);
57 | this.container.addEventListener('keypress', function(evt) {
58 | handleKeyPressEvent.call(editor, evt);
59 | });
60 | this.container.addEventListener('keydown', function(evt) {
61 | handleKeyDownEvent.call(editor, evt);
62 | });
63 | this.mode = {
64 | strong: false,
65 | em: false,
66 | u: false
67 | };
68 | if (debug) {
69 | this.debug = (typeof debug=='string' ? document.querySelector(debug) : debug);
70 | }
71 | }
72 |
73 | cobaltEditor.prototype = {
74 | constructor: cobaltEditor,
75 | focus: function() {
76 | this.container.focus();
77 | },
78 | enterRange: function() {
79 | var fragment = this.fragment;
80 | var enters = [];
81 | var range = fragment.search(/^.*$/gm);
82 | for (var i=0,l=range.ranges.length;i';
329 | keyCodes[63] = '?';
330 | keyCodes[64] = '@';
331 |
332 | keyCodes[91] = 'OS'; // opera '[';
333 | keyCodes[92] = 'OS'; // opera '\\';
334 | keyCodes[93] = 'ContextMenu'; // opera ']';
335 | keyCodes[95] = 'Sleep';
336 | keyCodes[96] = '`';
337 |
338 | keyCodes[106] = '*'; // keypad
339 | keyCodes[107] = '+'; // keypad
340 | keyCodes[109] = '-'; // keypad
341 | keyCodes[110] = 'Separator';
342 | keyCodes[111] = '/'; // keypad
343 |
344 | keyCodes[144] = 'NumLock';
345 | keyCodes[145] = 'ScrollLock';
346 |
347 | keyCodes[160] = '^';
348 | keyCodes[161] = '!';
349 | keyCodes[162] = '"';
350 | keyCodes[163] = '#';
351 | keyCodes[164] = '$';
352 | keyCodes[165] = '%';
353 | keyCodes[166] = '&';
354 | keyCodes[167] = '_';
355 | keyCodes[168] = '(';
356 | keyCodes[169] = ')';
357 | keyCodes[170] = '*';
358 | keyCodes[171] = '+';
359 | keyCodes[172] = '|';
360 | keyCodes[173] = '-';
361 | keyCodes[174] = '{';
362 | keyCodes[175] = '}';
363 | keyCodes[176] = '~';
364 |
365 | keyCodes[181] = 'VolumeMute';
366 | keyCodes[182] = 'VolumeDown';
367 | keyCodes[183] = 'VolumeUp';
368 |
369 | keyCodes[186] = ';';
370 | keyCodes[187] = '=';
371 | keyCodes[188] = ',';
372 | keyCodes[189] = '-';
373 | keyCodes[190] = '.';
374 | keyCodes[191] = '/';
375 | keyCodes[192] = '`';
376 |
377 | keyCodes[219] = '[';
378 | keyCodes[220] = '\\';
379 | keyCodes[221] = ']';
380 | keyCodes[222] = "'";
381 | keyCodes[224] = 'Meta';
382 | keyCodes[225] = 'AltGraph';
383 |
384 | keyCodes[246] = 'Attn';
385 | keyCodes[247] = 'CrSel';
386 | keyCodes[248] = 'ExSel';
387 | keyCodes[249] = 'EREOF';
388 | keyCodes[250] = 'Play';
389 | keyCodes[251] = 'Zoom';
390 | keyCodes[254] = 'Clear';
391 |
392 | // a-z
393 | for ( var i=65; i<=90; i++ ) {
394 | keyCodes[i] = String.fromCharCode( i ).toLowerCase();
395 | }
396 |
397 | // 0-9
398 | for ( var i=48; i<=57; i++ ) {
399 | keyCodes[i] = String.fromCharCode( i );
400 | }
401 | // 0-9 keypad
402 | for ( var i=96; i<=105; i++ ) {
403 | keyCodes[i] = ''+(i-95);
404 | }
405 |
406 | // F1 - F24
407 | for ( var i=112; i<=135; i++ ) {
408 | keyCodes[i] = 'F'+(i-111);
409 | }
410 |
411 | function convertKeyNames( key ) {
412 | switch ( key ) {
413 | case ' ':
414 | return 'Spacebar';
415 | case 'Esc' :
416 | return 'Escape';
417 | case 'Left' :
418 | case 'Up' :
419 | case 'Right' :
420 | case 'Down' :
421 | return 'Arrow'+key;
422 | case 'Del' :
423 | return 'Delete';
424 | case 'Scroll' :
425 | return 'ScrollLock';
426 | case 'MediaNextTrack' :
427 | return 'MediaTrackNext';
428 | case 'MediaPreviousTrack' :
429 | return 'MediaTrackPrevious';
430 | case 'Crsel' :
431 | return 'CrSel';
432 | case 'Exsel' :
433 | return 'ExSel';
434 | case 'Zoom' :
435 | return 'ZoomToggle';
436 | case 'Multiply' :
437 | return '*';
438 | case 'Add' :
439 | return '+';
440 | case 'Subtract' :
441 | return '-';
442 | case 'Decimal' :
443 | return '.';
444 | case 'Divide' :
445 | return '/';
446 | case 'Apps' :
447 | return 'Menu';
448 | default:
449 | return key;
450 | }
451 | }
452 |
453 | self.getKey = function( evt ) {
454 | var keyInfo = '';
455 | if ( evt.ctrlKey && evt.keyCode != 17 ) {
456 | keyInfo += 'Control+';
457 | }
458 | if ( evt.metaKey && evt.keyCode != 224 ) {
459 | keyInfo += 'Meta+';
460 | }
461 | if ( evt.altKey && evt.keyCode != 18 ) {
462 | keyInfo += 'Alt+';
463 | }
464 | if ( evt.shiftKey && evt.keyCode != 16 ) {
465 | keyInfo += 'Shift+';
466 | }
467 | // evt.key turns shift+a into A, while keeping shiftKey, so it becomes Shift+A, instead of Shift+a.
468 | // so while it may be the future, i'm not using it here.
469 | if ( evt.charCode ) {
470 | keyInfo += String.fromCharCode( evt.charCode ).toLowerCase();
471 | } else if ( evt.keyCode ) {
472 | if ( typeof keyCodes[evt.keyCode] == 'undefined' ) {
473 | keyInfo += '('+evt.keyCode+')';
474 | } else {
475 | keyInfo += keyCodes[evt.keyCode];
476 | }
477 | } else {
478 | keyInfo += 'Unknown';
479 | }
480 | return keyInfo;
481 | }
482 |
483 | self.listen = function( el, key, callback, capture ) {
484 | return el.addEventListener('keydown', function(evt) {
485 | var pressedKey = self.getKey( evt );
486 | if ( key == pressedKey ) {
487 | callback.call( el, evt );
488 | }
489 | }, capture);
490 | }
491 |
492 | self.getCharacter = function(evt) {
493 | evt = evt || window.event;
494 | if ( evt.which!==0 && !evt.ctrlKey && !evt.metaKey && !evt.altKey ) {
495 | return String.fromCharCode(evt.which);
496 | }
497 | }
498 |
499 | return self;
500 | } )( cobalt.keyboard || {} );
501 |
502 | /***/ },
503 | /* 2 */
504 | /***/ function(module, exports) {
505 |
506 | module.exports = function(editor) {
507 | var treeWalker = document.createTreeWalker(
508 | editor.container,
509 | NodeFilter.SHOW_TEXT,
510 | function(node) {
511 | return NodeFilter.FILTER_ACCEPT;
512 | },
513 | false
514 | );
515 | var getPrevNode = function(node) {
516 | treeWalker.currentNode = node;
517 | return treeWalker.previousNode();
518 | };
519 | var getNextNode = function(node) {
520 | treeWalker.currentNode = node;
521 | return treeWalker.nextNode();
522 | };
523 | var getOffset = function(offset, node) {
524 | if (node.nodeType==Node.ELEMENT_NODE) {
525 | var cobaltOffset = 0;
526 | var newNode = node.childNodes.item(offset);
527 | if (newNode) {
528 | node = newNode;
529 | }
530 | } else {
531 | var cobaltOffset = offset;
532 | }
533 | var textContent = "";
534 |
535 | while (node = getPrevNode(node) ) {
536 | textContent = node.textContent;
537 | cobaltOffset += textContent.length;
538 | }
539 | return cobaltOffset;
540 | };
541 | var getOffsetAndNode = function(offset) {
542 | var node = getNextNode(editor.container);
543 | var currOffset = 0;
544 | var lastNode = editor.container;
545 | while (node && currOffset<=offset) {
546 | if ((node.textContent.length + currOffset) < offset ) {
547 | currOffset += node.textContent.length;
548 | lastNode = node;
549 | node = getNextNode(node);
550 | } else {
551 | break;
552 | }
553 | }
554 | if (!node) {
555 | node = lastNode;
556 | }
557 | return {
558 | node: node,
559 | offset: offset - currOffset
560 | }
561 | };
562 |
563 | return {
564 | get: function() {
565 | var sel = window.getSelection();
566 | var end = getOffset(sel.focusOffset, sel.focusNode);
567 | var start = getOffset(sel.anchorOffset, sel.anchorNode);
568 | if (start<=end) {
569 | return {
570 | range: cobalt.range(start, end),
571 | cursor: end
572 | }
573 | } else {
574 | return {
575 | range: cobalt.range(end, start),
576 | cursor: end
577 | }
578 | }
579 | },
580 | set: function(range, cursor) {
581 | if (cursor == range.start) {
582 | var end = getOffsetAndNode(range.start);
583 | var start = getOffsetAndNode(range.end);
584 | } else {
585 | var start = getOffsetAndNode(range.start);
586 | var end = getOffsetAndNode(range.end);
587 | }
588 | var range = document.createRange();
589 | range.setStart(start.node, start.offset);
590 | range.setEnd(end.node, end.offset);
591 | var selection = window.getSelection();
592 | selection.removeAllRanges();
593 | selection.addRange(range);
594 | }
595 | }
596 | };
597 |
598 | /***/ }
599 | /******/ ]);
--------------------------------------------------------------------------------
/docs/api.md:
--------------------------------------------------------------------------------
1 | # Cobalt API
2 |
3 | ## cobalt.range
4 | A cobalt range is a collection of [start, end] pairs of character offsets.
5 | Non of these pairs will overlap or be end-to-start or start-to-end consecutive with any other pair in the same range.
6 | A start offset will always be equal or larger than 0. The end offset will
7 | always be at equal or larger than the start offset.
8 | A range has a combined start and end offset, as well as a count of the number of start/end pairs it contains.
9 | An empty range has both start and end set to null and count set to 0.
10 |
11 | ### Usage
12 |
13 | Create a simple range:
14 | ```javascript
15 | var range1 = cobalt.range(10,20);
16 | ```
17 | Or like this:
18 | ```javascript
19 | var range2 = cobalt.range([10,20]);
20 | ```
21 | Create a range of ranges
22 | ```javascript
23 | var range3 = cobalt.range([[10,20],[30,40]]);
24 | ```
25 | Create a range from another range
26 | ```javascript
27 | var range4 = cobalt.range(range3);
28 | ```
29 |
30 | ### Properties
31 | - `start`: The smalles start offset in all pairs, or null.
32 | - `end`: The largest end offset in all pairs, or null.
33 | - `size`: The difference between end and start, or null.
34 | - `count`: The number of pairs in the range, 0 or more.
35 |
36 | ### Methods
37 | - `collapse(toEnd)`: Returns a new range with a single start/end pair, where the start and end are equal. If toEnd is set, start will be made equal to end - collapse to the end, if not, end will be made equal to start - collapse to the start.
38 |
39 | - `compare(range)`: Returns -1, 0 or 1 if the given range is smaller, equal or larger than this range.
40 |
41 | - `delete(range)`: Returns a new range with the given range deleted from it. This moves the start/end offsets, it cuts out the range.
42 |
43 | - `exclude(range)`: Returns a new range with the parts in the given range removed from this range. Only overlapping parts are removed. The positions do not move otherwise.
44 |
45 | - `foreach(f)`: Run function f on each sub range (consecutive part of this range). The argument passed to f is always a cobalt range.
46 |
47 | - `get(i)`: Returns a range of the start/end pair at position `i` in this range. First pair is 0.
48 |
49 | - `insert(range)`: Returns a new range, with all the space in the given range inserted into this range. This moves the start/end offsets.
50 |
51 | - `intersect(range)`: Return a new range consisting of the intersection or overlap of this range and the given range.
52 |
53 | - `invert(end)`: Returns a new range with all the ranges that are not in this range, in the domain range [0, end].
54 |
55 | - `join(range)`: Returns a new range with the given range joined. This is similar to set inclusion. Overlapping ranges will be merged. The positions
56 | do not move otherwise.
57 |
58 | - `move(length)`: Returns a new range with all the start/end offsets moved by `length`. A negative length moves the offsets towards 0. If any end offset moves below 0, that pair is removed. Otherwise any start offsets below 0 are set to 0.
59 |
60 | - `overlaps(range)`: Returns true if any part of the given range overlaps any part of this range. A collapsed range never overlaps.
61 |
62 | ## cobalt.annotation
63 | An annotation is a combination of a range and a tag. A tag is anything inside the '<' and '>' characters in xml or html, without the angle brackets.
64 |
65 | ### Usage
66 | Create a simple annotation:
67 | ```javascript
68 | var annotation1 = cobalt.annotation([10,20], 'div class="annotation"');
69 | ```
70 | Or written out:
71 | ```javascript
72 | var annotation2 = cobalt.annotation( cobalt.range( 10, 20 ), 'div class="annotation"');
73 | ```
74 | Create an annotation from another annotation
75 | ```javascript
76 | var annotation3 = cobalt.annotation(annotation1);
77 | ```
78 |
79 | ### Properties
80 | - `range`: A cobalt range object.
81 | - `tag`: A tag or markup string.
82 | - `tagName`: The tag name or first word of the tag, always in lowercase.
83 |
84 | ### Methods
85 | - `compare(annotation)`: Returns -1, 0, or 1, depending if the range in the given annotation is smaller, equal or larger than the annotation range.
86 |
87 | - `copy(range)`: Returns a new annotation with the overlapping part of the given range, or null if there is no overlap.
88 |
89 | - `delete(range)`: Returns a new annotation with this range deleted. Offsets can move. Returns null if the annotation range is fully deleted.
90 |
91 | - `exclude(range)`: Returns a new annotation with this range excluded. Offsets won't move otherwise. Returns null if the annotation range is fully excluded.
92 |
93 | - `has(tag)`: Returns true if the tagName in this tag is the same as the tagNamein the given tag.
94 |
95 | - `insert(range)`: Returns a new annotation with this range inserted. Offsets can move.
96 |
97 | - `join(range)`: Returns a new annotation with this range joined. Offsets won't move otherwise.
98 |
99 | ## cobalt.fragment
100 | A fragment is a combination of plain text and a list of annotations. The list of annotations will always be sorted by the start and after that the end offsets of their ranges. A fragment is immutable, all operations on it return a new fragment.
101 |
102 | ### Usage
103 | Create a fragment of two text parts
104 | ```javascript
105 | var fragment = cobalt.fragment("Some bold text","5-9:strong\n10-14:em");
106 | ```
107 | Create a fragment of text and a list of annotations
108 | ```javascript
109 | var fragment2 = cobalt.fragment("Some bold text",[
110 | cobalt.annotation([5,9],"strong"),
111 | cobalt.annotation([10,14],"em")
112 | ]);
113 | ```
114 | ### Properties
115 | - `text`: A plain jane string.
116 | - `annotations`: A list of annotation objects.
117 |
118 | ### Methods
119 | - `apply(range, tag)`: Returns a new fragment, with the given range/tag or annotation or annotationlist applied.
120 |
121 | - `copy(range)`: Returns a new fragment with only the parts that overlap the given range. The new fragment adds all text slices matching the range together as one single text. All annotations are moved/cut to match this new text.
122 |
123 | - `delete(range)`: Returns a new fragment where the given range is deleted (cut).
124 |
125 | - `insert(range)`: Returns a combined new fragment with the inserted fragment text and annotations inserted at the given position.
126 |
127 | - `query(selector)`: To be implemented.
128 |
129 | - `remove(range, tag)`: Returns a new fragment, with the given range/tag or annotation or annotationlist removed.
130 |
131 | - `search(re)`: Searches through the text using a regular expression or string. Returns a cobalt range encompassing all matches.
132 |
133 | ## cobalt.fragment.annotations
134 | A list of annotations in a fragment.
135 |
136 | ### Properties
137 | - `list`: An immutable array of annotations.
138 | - `count`: The number of annotations in the list
139 |
140 | ### Methods
141 | - `apply(range, tag)`: Returns a new list with a new annotation added.
142 | - `remove(range, tag)`:
143 | - `clear(range)`:
144 | - `delete(range)`:
145 | - `insert(range)`:
146 | - `filter(callback)`:
147 | - `map(callback)`:
148 | - `query(selector)`:
149 |
--------------------------------------------------------------------------------
/docs/cobalt-fast-render.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/poef/cobalt/56fcf1155991b68b7775d7c06d82c074fea19748/docs/cobalt-fast-render.txt
--------------------------------------------------------------------------------
/docs/methods.txt:
--------------------------------------------------------------------------------
1 | CobaltSingleRange
2 | - (int) size()
3 | - (int) compare(r)
4 | - (r) collapse(bool toEnd)
5 | - (bool) equals(r)
6 | - (bool) smallerThan(r)
7 | - (bool) largerThan(r)
8 | - (bool) leftOf(r)
9 | - (bool) rightOf(r)
10 | - (bool) overlaps(r)
11 | - (bool) contains(r)
12 | - (r) overlap(r)
13 | - (array) exclude(r)
14 | - (strint) toString()
15 |
16 | cobaltRange
17 | - constructor(int|array|R start?, int end?)
18 | - (int|null) start()
19 | - (int|null) end()
20 | - (int|null) size()
21 | - (int) count()
22 | - (R) get(i)
23 | - (R) delete(R)
24 | - forEach(f)
25 | - (R) insert(R)
26 | - (R) collapse(bool toEnd)
27 | - (R) join(R)
28 | - (R) exclude(R)
29 | - (R) excludeDisjointed(R)
30 | - (R) intersect(R)
31 | - (R) move(int by)
32 | - (R) invert(int end)
33 | - (R) compare(R)
34 | - (bool) overlaps(R)
35 | - (string) toString()
36 | - (array) explode() // return an array of ranges for each singleRange in the range
37 | missing
38 | - map
39 | - reduce
40 | - filter
41 |
42 | cobaltAnnotation
43 | - constructor(R|A|string, string tag?) // todo: support A, string format
44 | - (A) delete(R)
45 | - (A) insert(R)
46 | - (A) exclude(R)
47 | - (A) join(R)
48 | - (A|null) copy(R)
49 | - (int) compare(A)
50 | - (bool) has(string tag)
51 | - (string) toString()
52 | missing
53 | - start
54 | - end
55 | - size
56 | - count
57 | - get
58 | - forEach
59 | - excludeDisjointed
60 | - intersect
61 | - move
62 | - invert
63 | - overlaps
64 | - map
65 | - reduce
66 | - filter
67 |
68 | cobaltAnnotationList
69 | - constructor(L|array|string)
70 | - (int) count()
71 | - (A) item(i)
72 | - (L) apply(R, string tag)
73 | - (L) remove(R, string tag)
74 | - (L) clear(R)
75 | - (L) delete(R)
76 | - (L) insert(R)
77 | - (L) filter(f)
78 | - (L) map(f)
79 | - (L) reduce(acc, f)
80 | - (L) get(R) //todo rename to avoid confusion with R.get -> copy?
81 | - (R) query(string selector, int max)
82 | - forEach(f)
83 | - (bool) has(R, string tag)
84 | missing
85 | - exclude
86 | - excludeDisjointed
87 | - intersect
88 | - move
89 | - invert
90 | - overlaps
91 | - has
92 | --
93 | - copy (in plaats van get?)
94 |
95 |
96 | cobaltFragment
97 | - constructor(T|F, L?)
98 | - (int) length()
99 | - (F) delete(R) // TODO accepteer A
100 | - (F) copy(R) // TODO accepteer A
101 | - (F) insert(R, F) // TODO accepteer A, L
102 | - (F) apply(R, string tag) // accepteer Annotation
103 | - (F) remove(R, string tag) // accepteer Annotation
104 | - (R) search(string|RegExp RE) //todo check type of searchRe
105 | - (R) query(string selector)
106 | - (string) toString()
107 |
108 |
109 |
--------------------------------------------------------------------------------
/docs/todo.txt:
--------------------------------------------------------------------------------
1 | cobalt
2 | + window.cobaltEditor -> editor voor cobaltDocument
3 | + window.cobalt -> cobalt code
4 | + vormgeving
5 | + designsystem inladen
6 | + ds-space rondom editor
7 | + fonts
8 | + full page gebruiken
9 | + annotaties lijst rechts, donkere achtergrond
10 | - opslaan
11 | + localStorage
12 | - opslaan met url pad als naam van het item?
13 | - aangeven dat je lokale wijzigingen hebt?
14 | - beaker?
15 | - hosted? a la simplyedit?
16 | - alternatief voor mime verzinnen. Json root met slices?
17 | tenminste nested documenten toestaan, en namen aan parts geven
18 | - render zonder editor
19 | - api beschikbaar via window.cobaltDocument
20 | - UAE stijl document (