├── 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