From 2c4bebf70630b226180ef45a52637c36fc993679 Mon Sep 17 00:00:00 2001 From: Firebase Operations Date: Wed, 6 Jun 2018 18:06:53 +0000 Subject: [PATCH] [firebase-release] Removed change log and reset repo after 1.5.0 release --- changelog.txt | 1 - dist/firepad.css | 351 --- dist/firepad.eot | Bin 4144 -> 0 bytes dist/firepad.js | 6214 ------------------------------------------- dist/firepad.min.js | 16 - 5 files changed, 6582 deletions(-) delete mode 100644 dist/firepad.css delete mode 100644 dist/firepad.eot delete mode 100644 dist/firepad.js delete mode 100644 dist/firepad.min.js diff --git a/changelog.txt b/changelog.txt index d297ac5bf..e69de29bb 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1 +0,0 @@ -feature - Added support for Monaco editor. See examples/monaco.html for usage. diff --git a/dist/firepad.css b/dist/firepad.css deleted file mode 100644 index af89fc702..000000000 --- a/dist/firepad.css +++ /dev/null @@ -1,351 +0,0 @@ -.firepad { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - width: 100%; - height: 100%; - position: relative; - text-align: left; - line-height: normal; -} - -.firepad .CodeMirror { - position: absolute; - top: 0; - bottom: 0; - right: 0; - left: 0; - height: auto; -} - -.firepad-with-toolbar .CodeMirror { - top: 83px; - padding-left: 10px; -} - -.firepad-with-toolbar .CodeMirror-lines { - padding-top: 10px; -} - -.firepad-toolbar { - height: 82px; - line-height: 82px; - padding-left: 10px; - border-bottom: 1px solid #c9c9c9; - - /* Don't select text when double-clicking in toolbar */ - -webkit-touch-callout: none; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.firepad-toolbar-wrapper { - display: inline-block; - line-height: 14px; -} - -.firepad-richtext .CodeMirror { - font-family: Verdana, sans-serif; - font-size: 14px; -} - -.firepad-spacer { - height: 10px; - width: 1px; -} - -/** Styles for all of the rich-text formatting we support. */ -.firepad-b { font-weight: bold; } -.firepad-i { font-style: italic; } -.firepad-u { text-decoration: underline; } -.firepad-s { text-decoration: line-through; } -.firepad-u.firepad-s { text-decoration: underline line-through; } - -.firepad-f-arial { font-family: Arial, Helvetica, sans-serif; } -.firepad-f-comic-sans-ms { font-family: "Comic Sans MS", cursive, sans-serif; } -.firepad-f-courier-new { font-family: "Courier New", Courier, monospace; } -.firepad-f-impact { font-family: Impact, Charcoal, sans-serif; } -.firepad-f-times-new-roman { font-family: "Times New Roman", Times, serif; } -.firepad-f-verdana { font-family: Verdana, Geneva, sans-serif; } - -.firepad-la-left { text-align: left; } -.firepad-la-center { text-align: center; } -.firepad-la-right { text-align: right; } - -/** Line Styles */ -pre.firepad-lt-o, pre.firepad-lt-u, pre.firepad-lt-t, pre.firepad-lt-tc { - padding-left: 40px; -} - -.firepad-list-left { - display:inline-block; - margin-left: -40px; - width: 40px; - padding-right: 5px; - text-align: right; -} - -.firepad-todo-left { - display:inline-block; - margin-left: -20px; - width: 20px; -} - -.powered-by-firepad { - position: absolute; - display: block; - z-index: 5; - right: 20px; - bottom: 20px; - width: 129px; - height: 23px; - background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIEAAAAXCAYAAAAsnywOAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDowN0M3OUFEQTk4ODUxMUUyQUFBRTg2NUQxMTExQTUxQyIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDowN0M3OUFEQjk4ODUxMUUyQUFBRTg2NUQxMTExQTUxQyI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkRBNEIyOTA5OTg4NDExRTJBQUFFODY1RDExMTFBNTFDIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkRBNEIyOTBBOTg4NDExRTJBQUFFODY1RDExMTFBNTFDIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+o21teAAABctJREFUeNrEWttV8zgQFjl5X1EBTgVr3vKGqYBQQZIKgAoMFUAqiKkgTgUxT/CGUwH+K8DbwVrnjDbzKzOjS9jdOUcnCVjyXD7NTTp7f39XkZQPoxjGn8PI4Lulbhi3w2jht4bnzXMX8Jkx6zbD+DWMSnj3Yhh38H2J3vMTlCN+DfUeXixlwJPVSwfrtDBeE/g0a85hbhf4fDmM7TDqmBdNp1N1FgACDco3Rp/Bb4mM8p6G8ZxojEeY79Ia+MC0DDSUS8ZYV/ApAfMFeOmZ/5fAr4/MOg8R/O3Q5jIgWMEaHN0jfdvnK4HvIBBoMPgNfP7X5AJBUnYIEGYA4sLxXD8NSokePIa09AxGdeksYU7l8w4GBCMCUQaF3yDk/wEAatdKu03i07jor2FsYI0i4f17ZuctItcpPV5UC8ZUHt63Qvg0sn+iMHdEY0dhsS68g9E6rmcPa2We+XieXWvr5BTrAD7WkFP0xN+zRPD1sHtrxqCxZL1rRQD1MzBnaZj/3QTM3YFHe5FAoAONXiND9Z5nS/h8A0apmN4yqG+B8ewEBfeRgLbJ3J4Bld1dOhFYc4LHTeDcPzxGdmXJGE9zA8n7P7KNnF0pZbGG+QnsjiZAwS28jNtNPfO+BRj/ixDOzLlm5t0xPEjUILkmwO8T8MvJd8XkDecQt8/g+5LI7AtC1lBPlXt0jcnKQslRgH41BYIeGO8j405MbHcNIClZMwBo4LMnlJQH8rwEY10zxpISTDf/qIkKQqPhyxNS9SflLQXwdQuAeHT4yzEQRgSizgmldLH1J2GgLNBAnLDYA3BeJCeAVjNAK8AIdjwHGGxDGPYV8b2A+P4F62nPeplQSVAhTwuhjNODLdkvCSCUbk6AGw9ZpFv1EZW41IygGVNnt6iJEuNGMybkLJj3L5k1HoXyLDSud45X4UrSF9BZwezwlPyhg420RiC5//j4WI0dpnom9mwRExpqbktvgmvnBOaSylww5KcnLlIhpgiY44IjJ8LNPBJkHK08fDWoJ9EQILg60SO3INs3ts0YjLpTv7dLqRJsLSheqhSoWP0akXSpwH4FFdtTSzlNxPhTCTeLSkEGqT+xAF20sF6XwMcRsEbq0PdOFdjXhYvJ2lOaUzahperv2OZQBbHTl9XHUANrvnj0VavDWUzJhFAbLmfgGWeCjjn9rqU+wSlC1h4X6xqtSSiDKCC9Kr5HvmYA0xBA9x30NJG82UR6RQBqHlF5+DxWz4Skvwi9lsz6b2MQ/jExvnRMIsUBQCm+z58FKncFCu483ilnXHKVIKuJ0/eCJ2oBKHukF07GxQ9svB71azJCVtxx3QiezPBZjZEhpfZsDQu/CeVZaB28OgEEvtM0KQQ1gQDQhGfh8p1JZFwuBe8U4wVwyCoYj1p6wph57+10Ou3HaHfeEfXlRMW1Xl33lhHIywJqWyWUY3O0CyRDKk8ymqPncojBOdGTkAAaAwDOCzyAjmeMh6FKdu1Jpn09j1ahlv0YMZgTbjsUABoplbpsghWxQ8lc7eklcHnDDgzQoBK1c4QsIiocl3YOf6ckhlIu0CLvZN53AfG8QSCkjqy1J+RKdHRHYiwgPaZNHHvsbGMVLps0s0u4I9iMaPo0sON/nWgwDZ6xFnZbk+AZJe9UEfLFJou+BP6J4nsU0XjxJSopdCGUjT0A5DrC7RbgCn/i2tmDJxy0kaDSTK4l5Q9U67lzbBNySHatDucuigNBm9h4kBobPqrV7zd2toTbsrxdquNDEEnhXcJOxUq+dJTbnugpe8LgvUfnSwYkywAw2g00kYxvCV8vK9ThgCT2TpyZ84VQ26O4tke7qfEgdwPur1b8ieZC+a+9naMEibsX2anDhdBOHV9oceX7dhR/mRBibGfWHrP7Nt4CeQNbxTVE2PhEsqxiymDujmGRuItsFtud6FUyFX7DdqYOJ4Ia1c+VUAnElLju+woiCf235HP16quGshS5DAj+FmAA6gfBqr4bldAAAAAASUVORK5CYII='); - opacity: 0.5; -} - -.powered-by-firepad:hover { - opacity: 1; -} - -.firepad-btn-group { - margin: 5px 7px 0 0; - display: inline-block; -} - -a.firepad-btn, a.firepad-btn:visited, a.firepad-btn:active { - font-family: "Arial" sans-serif; - cursor: pointer; - text-decoration: none; - display: inline-block; - padding: 6px 6px 4px 6px; - text-align: center; - vertical-align: middle; - font-size: 16px; - background-color: #fcfcfc; - border: 1px solid #c9c9c9; - border-bottom-width: 4px; - color: #9c9c9c; -} - -a.firepad-btn:hover { - color: #fff; - background-color: #ffbf86; - border-color: #e6a165; - text-decoration: none; -} - -a.firepad-btn:active { - -webkit-box-shadow: inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05); - -moz-box-shadow: inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05); - box-shadow: inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05); -} - -.firepad-btn-group > .firepad-btn { - -webkit-border-radius: 0; - -moz-border-radius: 0; - border-radius: 0; - margin-left: -1px; -} - - -.firepad-btn-group > .firepad-btn:first-child { - border-bottom-left-radius: 6px; - border-top-left-radius: 6px; - -webkit-border-bottom-left-radius: 6px; - -webkit-border-top-left-radius: 6px; - -moz-border-radius-bottomleft: 6px; - -moz-border-radius-topleft: 6px; - margin-left: 0px; -} - -.firepad-btn-group > .firepad-btn:last-child { - border-bottom-right-radius: 6px; - border-top-right-radius: 6px; - -webkit-border-bottom-right-radius: 6px; - -webkit-border-top-right-radius: 6px; - -moz-border-radius-bottomright: 6px; - -moz-border-radius-topright: 6px; -} - -.firepad-dropdown { - position: relative; -} - -.firepad-dropdown-menu { - position: absolute; - top: 100%; - left: 0; - z-index: 1000; - display: none; - float: left; - padding: 4px 0; - margin: 4px 0 0; - list-style: none; - background-color: #ffffff; - border: 1px solid #ccc; - border: 1px solid rgba(0, 0, 0, 0.2); - *border-right-width: 2px; - *border-bottom-width: 2px; - -webkit-border-radius: 5px; - -moz-border-radius: 5px; - border-radius: 5px; - -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); - -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); - box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); - -webkit-background-clip: padding-box; - -moz-background-clip: padding; - background-clip: padding-box; -} - -.firepad-dropdown-menu a { - text-align: left; - display: block; - padding: 3px 15px; - clear: both; - font-weight: normal; - line-height: 18px; - color: #333333; - white-space: nowrap; -} - -.firepad-dropdown-menu a:hover { - color: #fff; - text-decoration: none; - background-color: #ffbf86; -} - -.firepad-color-dropdown-item { - height: 25px; - width: 25px; -} - -.firepad-dialog { - position: absolute; - left: 0px; - top: 0px; - width: 100%; - height: 100%; - z-index: 1000; -} - -.firepad-dialog-div { - position: relative; - width: 400px; - height: 100px; - margin: 100px auto; - background-color: #fff; - border: 1px solid #000; - padding: 15px; -} - -.firepad-dialog-input { - width: 80%; - display: block; - padding: 5px 5px; - margin: 10px 10px 10px 5px; - clear: both; - font-weight: normal; - line-height: 25px; - color: #333333; - white-space: nowrap; -} - -/******************************************************************** - * Generated via icomoon.io. - If you want to make changes, you can go to http://icomoon.io/app/, go to the bottom right and click the - database-looking icon, then "Load Session" and use the checked-in font/firepad-icomoon.json file. - - Note: When you download the generated font, turn on the "Base 64 Encode ..." option to generate the font inline - in the CSS (to avoid needing to distribute a font file with firepad). - */ -@font-face { - font-family: 'firepad'; - src:url('firepad.eot'); -} -@font-face { - font-family: 'firepad'; - src: url(data:application/font-woff;charset=utf-8;base64,d09GRk9UVE8AAAzsAAsAAAAAFegAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABDRkYgAAABCAAACZUAABDLmL2mHkZGVE0AAAqgAAAAGgAAABxoZGqgR0RFRgAACrwAAAAdAAAAIABFAARPUy8yAAAK3AAAAEsAAABgL9zcQGNtYXAAAAsoAAAAXgAAAX7gqNO7aGVhZAAAC4gAAAAuAAAANv1GCI1oaGVhAAALuAAAAB4AAAAkBBD/5GhtdHgAAAvYAAAAHgAAADYEYAEQbWF4cAAAC/gAAAAGAAAABgAYUABuYW1lAAAMAAAAAOAAAAGGNHbrq3Bvc3QAAAzgAAAADAAAACAAAwAAeJy9V3l0lNUVf1+YjWQyQJghBMIMZafTCoERQUQWIaCDYGGkiNKypuBIC5hYUERB9o9FYMoOrWgrEPDkQMqpcKpCkUqnRyONCBRo2FKSgbAY+L7yDdz+7vcmw5ZqT//omTP33nff3d6977vvPUVYLEJRFEfexKnjJ48eJ5QUoYjH9U4peuc6emOL6qyjOi3eVJE1NINUNUk47WpYj+gF1mzlsXrZQtTPVsINsoUzu74vQ2SxDYeoJxqJZqKV8ItOopvoJfqLQeJZ8YIYK14UU8Q08YaYJ5aIiFgvNoutoqjg5xNzO3bsCNQviXIk6iRRZ4kCEj0sUReJHpGoq0TdJOotUR+JnpCor0T9TJQjHeVIRznSUY50lCMd5SQc5SZSVJMpIZT5ygJlobJIUZXFyhJlqbJMeVtZrqxQVioR5VfKKmW1skZZq6xT1isblI2iMaclRTjFLcWrHKrzjk23++wT7AvsW+yf2csd3tSqtCfTNqUddjqcp9Id6TnpH7jedKl6viUe0SN2Fymjr7xH4tJ21UOidfuuRBUrrpJoXN6K6OtHT6tEX87d7CD6MHyQ6GDk0QTA0OtXg2pYVfVQoRrFz0oiF1Oim5R8lYXm+WCgbMcKouqiMURn+44gutSrD5zs+TWM/qTeCyRWDC3wGG4SnT4RdhJNjQskGh1oTCL79/tJtHzqcQmoovsFyaNzlsY+kJ8IoxQxN+u2jkTmwTye/BPL9pCALvAE8+jMwTwfUdGbwnDbXHpID3mM/HDUZgTi7qCqhrV8VS0MqlpAd2MVLq2aBcbZEXmsch/RjdOLiC7ubUF0eUoboqtZw7GGJc9gOP4c0T9teRA5fprofNY6tQYyV9Rt7ZOSov66bNZuS8JVvoUtLiTRsO8plYTb09OhjQvCHQKctpJE/83LiD6eP4nE8JFTGOxk3iASvXh28PxJXm2c3SiNuz3NU116RMv3EL3YY6cNqzzUFfa2PkJ0/JVcEmnDDhEdnV2iIs/RG/OJSutOR7YGNyHRoqg8AfyYkLwAzwbqTmPeAhjDLJ0c3MQXtSHKlpNRvEuF86CVUYSCpj6GlOtDIDbkIVSrzyCiY6zqZOpI1RqV4TCVuXuIvvnjTpb8IWrIVFkqqnnB/gFbnAsnl56B4IllmyQkkRF6zeSS8F3fLyWF0iqLiFqdBNWuGNT3UWTF3xfUD55ymAkIaxHbvQs4zwtoaq6CFz8tsagozz6YEF/Qdl8ArmQAVyC2428yfNHmRq5ckmjfYJvKcILK3AVy+ZA8LFMC7QOcpvps8VVO3TWZSsBG708i0t/4CFRmkOhaz5nYEL0HOEgU9Dpgc2mWGHa241gjEvY2lZAERVqbSp/fbuIEp4bt9dsYVzAnU6qwsi9mTxqpSLIzvX/v7ilMzJgmpA+2E7bVZt4XtkvzNcFkmsreQttdMUofMO/TW+2tPfgaMzXWM6VTo/i+6BN2CmuPXivWQvfZNw1V/G/2M5P2Jdvr50+qtvC735P7C//X3FfeFUytuU94XW9Yvm3jPJCb+4NPpOa7gs9MBl9Re/D3VeS/3TjanG/bOQ9s+wcqm8jCf9g5Lo1bf/NUo9S4C8UjWrXne6lGyJIAEIua01roDooHCj3BcKG/0BY2mnqiW9VSP06OqKpODU5S+8TMA8QaDXr84Wg4aP5jfNRYWsC+/26fMQ+zMGiRmpgP3DUfDzwwb5TePR95UB/2a4MurdgS1KqjdqM65kkQUEtQfkkUxjyaWw36rVHNjUNYjcasuBGc2FlEyp4dh1GMDg22E5WYvXznzd8weZTBOjTdXZBigHNfjChDJ5v5MJFxtR0oFTcA7RdorJ62k9DTcn78uQkgmbX4rwwWc+NcNY+o/OUAxkuPw94t2BcpdTKs4E7ZBu4y9O0j4c9UHuPgLZ9cDqkVlzcBXN1iSk0wuXzaTNzDZtawwQJ4TLGOMg3CY53bfyCKK2jPlonTQe22gPodTuFbvT6FvaMNCxh0hj2j+DWI8xXF+PoLUAv3QtwzC9RFPkRvxbdzfCGrzAJS0xareNr6jkkC1Ed8YlL6coAGE6wmg6fasNAZiHbIuMaqvNZRR3CIjPqql8e0yLarrFKWSi435WTj2GBZc8zLvnoR4Eq56Xw1czdxRfh+VnJxMfsakwzRkpWQgsciM8RzbHsiS51HiAOfxtE2cCAubES/RZ4qZqcjJa5hoF7ejU9leVuiyqNfwd7n718B2JJjNfNq1oWT/VJdriHXZfK79yY7a+mz4E69hupGuqlcw6YAS1ycmPrvcmLamFLHTcCbYKUuy4vxseQmsPqsXLNcrt4vUY5VfwH1ZBzUc/iqUz5+Hfba8rLbVs6EPUsQOynOR7KlXSdQ+Tht61TO5OrNdmA/z6gaFkamF4/A4b66+DYuAp1wbfmmoU63q+ecIvqX/SUHYpnRYZMRMkIa/hjNrJgO0/8YJUhYe+zCpS2M68aYE5eI/twyg2iNccGBT4yUsSVjPLAztzkJmx6B7ZxtRM6LiHDx8y2JJgRToNGihMTPLI2lFbGxZUNpmW7uHwTPNKvRfM30q4WsLj2ALtUyVcPt1KMFNDcaTQyfpur3c8Uu9R7A558y/vWH1HAh3T4++7B6RyffKPbEVANaRsBwoy358XlDKx1apaw1o0pqvZXBN123zg0hErXH/MhPg7W4MLvycJ+9Coqq8vJ8RsBuEglWDd/rN9xGBKGh/1aNP4uJdc2kKhvxadC6Yy3J92qluLFrASQZAkr/zgHtPbh1ljQnur4RFUkd68bVqkNZAlzfsJ1BFQ9PSQCRkOSZasL5RRnzMqQu87z3ds07MNEb3VoEjQ5p8Vu/QzISRW9E5mOcXoseiQfiEZueX+jRquMho9qulZrhN5l5E31uli0BzGGSR5Wg6DxT55PDWnhJXW/MmOPxa62CzxtuLeA3IqhSBFUimrpxMtGSMW8TbS7DzlnSXPDb6qMPkeXWVi/Rp/sG4O11Hk+SY6fQU8UufFjGj9CNxWg4Mbr0g7Thex3wZDoeL2f6biA6HX4Cw73j8DjpYsXXd23RPlSu5yt4FChTi0j03vwlnjEnwiS63OqQAOlKlOj28CGghu5GB2nhBLVSR4cUHJ3gOMVIH662U8v6MBAybDGyuTBX4cOzxa16VGcqXvYNhUVR7M/9dG1QK43a03A/Hqrj1bbdkQDmMMmjs6DoBFMnksNaeEldb1pQx/ZOi/lV4xy+A6093iP6EQuDXB7GvfF8W5KPhmMHJRxHcn2qdi4Ws6b9G/R4y/0AAAB4nGNgYGBkAIIztovOg+hzwskvYDQASW0G1AAAeJxjYGRgYOADYgkGEGBiYARCcSBmAfMYAAVHAEoAAAB4nGNgZmJgnMDAysDB6MOYxsDA4A6lvzJIMrQwMDAxsDIzwIEAgskQkOaawuDwgOEDA+OD/w8Y9BgfMCg0MDAwwhUoACEjABBCDB8AeJzljdkRgCAMRB+I94UH+mNpFmK5lmAHGGCsws1ksjvJmwAZqQ8UQZckFbPhlFlJ6bC43YP30fE5q+JtEUktpCGXVApT09DS0TMwYtmZmFlYcWyghEkP/6sX448KEgAAeJxjYGRgYADiA48eTY3nt/nKwM3EAALnhJNfIOj/D5gYGB8AuRwMYGkAXjsL7AAAeJxjYGRgYHzw/wGDHhMDA8M/BiAJFEEBzABt5wP2AAB4nGNigAAmBoYEBgcghgAFBmSgwGCAwmcAADLOAXUAAAAAUAAAGAAAeJx1jk9qwkAUh79otJRK6ap0OeCmm4RkXAgeIAfown2gYwhIIqNCT9KVR3DpMXqAHqHH6C/2bbpw4DHf++b9GWDGJwnDSbjjyXgkfjUeM+fDOJU/G0944Mt4Kv+jyiS9l3m8dg08Er8Yj6kojFP5k/GEZy7GU/lvNrREAjtq3mHTxrCrBW9yDUe28lFpaI7bWlDR03G43lEVAYcn1zbHSvF/3p/zLMhYKrzqSmVUfXeo+tgE5/PCrZztFflFtsx8Uaro1t/WcpG9Xoe/OE0c9rMOcd/2nSvz4mbvL7EuORF4nGNgZsALAAB9AAQ=) format('woff'), - url(data:application/x-font-ttf;charset=utf-8;base64,AAEAAAANAIAAAwBQRkZUTWhkaqAAAA9wAAAAHEdERUYARwAGAAAPUAAAACBPUy8yL7vcIAAAAVgAAABWY21hcODA1NYAAAHwAAABfmdhc3D//wADAAAPSAAAAAhnbHlmMPUBFgAAA6gAAAkIaGVhZP1GCI0AAADcAAAANmhoZWEEEf/lAAABFAAAACRobXR4BQoBEQAAAbAAAAA+bG9jYR04G1IAAANwAAAANm1heHAAZQCTAAABOAAAACBuYW1lNHbrqwAADLAAAAGGcG9zdFMv72QAAA44AAABDgABAAAAAQAABST+1l8PPPUACwIAAAAAAM4TY+gAAAAAzhNj6AAA/98CAAHhAAAACAACAAAAAAAAAAEAAAHh/98ALgIAAAD+AAIAAAEAAAAAAAAAAAAAAAAAAAAFAAEAAAAaAJAACQAAAAAAAgAAAAEAAQAAAEAAAAAAAAAAAQIAAZAABQAIAUwBZgAAAEcBTAFmAAAA9QAZAIQAAAIABQMAAAAAAAAAAAAAEAAAAAAAAAAAAAAAUGZFZABA4ADwAAHg/+AALgHhACGAAAABAAAAAAAAAgAAAAAAAAAAqgAAAAAAAAIAAGAAQABgAAAAAAAAACAAAAAAAAAAAAAAAAAAIAAxAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAADAAAAHAABAAAAAAB4AAMAAQAAABwABABcAAAACAAIAAIAAAAA4BXwAP//AAAAAOAA8AD//wAAAAAQAwABAAAABgAAAAAABAAFAAYABwAIAAkACgALAAwADQAOAA8AEAARABIAGQATABQAFQAWABcAGAAAAQYAAAEAAAAAAAAAAQIAAAACAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4APgBWAHwAugF0AbAB7AISAjgCXgKEAq4DFAM0A1QDfgOoA+AEDAQ2BGAEhAAAAAEAAP/gAgAB4AACAAARASECAP4AAeD+AAAAAAADAGAAAAGgAcAAEQAZACEAACU2NTQmKwMROwIyNjU0JiczMhYUBisBFyM1MzIWFAYBYh5LNUBAICBAYDVLIr4zFR4eFTNQUFAVHx/uIy81S/5ASzUiO6MmNCbAgCY0JgABAEAAAAHAAcAACwAAARUjAzMVIzUzEyM1AcBAoEDgQKBAAcAg/oAgIAGAIAAAAAACAGAAAAGgAcAAEQAVAAABMxUUBiImPQEzFRQXFjI3NjUFIRUhAWBAXoReQBocVBwa/wABQP7AAcDQPFRUPNDQHxcaGhcfsEAAAAAAAQAAAAACAAHAACoAACUVIxYVFAcGIicmNTMUFjI2NCYjITUzJicmNDc2MhcWFSM0JiIGFBYzMhcCAHUVMi6ALjJAOU45OSf/AJYCAjIyLoAuMkA5Tjk5Jz0t4CAeIjglIyMlOBomJjQmIAECJXAlIyMlOBomJjQmIAAAAAkAAP/gAgAB4AAPAB8ALwA/AE8AXwBvAH8AjwAANyMiBh0BFBY7ATI2PQE0JgcUBisBIiY9ATQ2OwEyFhUlISIGHQEUFjMhMjY9ATQmJyEiBh0BFBYzITI2PQE0JiUjIgYdARQWOwEyNj0BNCYHFAYrASImPQE0NjsBMhYVFyMiBh0BFBY7ATI2PQE0JgcUBisBIiY9ATQ2OwEyFhUlISIGHQEUFjMhMjY9ATQmcGAHCQkHYAcJCRcJByAHCQkHIAcJAZD+4AcJCQcBIAcJCQf+4AcJCQcBIAcJCf55YAcJCQdgBgoJFwkHIAcJCQcgBwkQYAcJCQdgBwkJFwkHIAcJCQcgBwkBkP7gBwkJBwEgBwkJYAkHYAcJCQdgBwlQBwkJByAHCQkHEAkHIAcJCQcgBwnACQcgBwkJByAHCeAJB2AHCRgIUAcJUAcJCQcgBwkJB5AJB2AHCQkHYAcJUAcJCQcgBwkJB9AJByAHCQkHIAcJAAYAAP/gAgAB4AADAAcACwATABsAIwAAEyEVIRUhFSEVIRUhAhQWMjY0JiIGFBYyNjQmIgYUFjI2NCYiwAFA/sABQP7AAUD+wMAmNCYmNCYmNCYmNCYmNCYmNAHAQIBAgEABujQmJjQm5jQmJjQm5jQmJjQmAAAAAAYAIP/gAgAB4AADAAcACwARAB0AKQAANyEVIREhFSERIRUhJxUjNSM1ExUzFSM1NzUjNTMdAiM1MzUjNTM1IzXAAUD+wAFA/sABQP7AYCAgIEBgQEBgYEBAQEBAQAEAQAEAQGCAYCD++RkgSR4ZIEl3oCAgICAgAAUAAAAAAgABwAADAAcACwAPABMAABEhFSEVIRUhFSEVITUhFSEVIRUhAgD+AAFA/sABQP7AAgD+AAIA/gABwEAgQIBAoECAQAAAAAAFAAAAAAIAAcAAAwAHAAsADwATAAARIRUhFyEVIRUhFSEnIRUhFSEVIQIA/gBgAUD+wAFA/sBgAgD+AAIA/gABwEAgQIBAoECAQAAABQAAAAACAAHAAAMABwALAA8AEwAAESEVIRchFSEVIRUhJyEVIRUhFSECAP4AwAFA/sABQP7AwAIA/gACAP4AAcBAIECAQKBAgEAAAAUAAAAAAgABwAADAAcACwAPABMAABEhFSEVIRUhFSEVIRUhFSEVIRUhAgD+AAIA/gACAP4AAgD+AAIA/gABwEAgQCBAIEAgQAAAAAAGAAAAIAIAAYAAAwAHAAsADwASABUAABEhFSE1IRUhFSEVIRUhFSElFzc1JwcBYP6gAWD+oAFg/qABYP6gAYBAQEBAASBAoECAQCBAoGBgIGBgAAACAAD/4AIAAeEAIABBAAABJyYiDwEGFB8BFhc3Ji8BJjQ/ATYyHwEWFA8BFgc3NjQHJicHFh8BFhQPAQYiLwEmND8BJjcHBhQfARYyPwE2NCcB3QIkZCNuIyMCBgcoCAUCExNtEzYTAhQUMQ0BTSPEBgcoCAUCExNtEzYTAhQUMQ0BTSMjAiRkI24jIwG7AiMjbSRkJAIFBSgEBgITNhNtFBQCEzYTMh8jTSNkeQUFKAQGAhM2E20UFAITNhMyHyNNI2QkAiMjbSRkJAABACD/4AHPAeAAEAAABT4BLgIHFSc3FTYeAg4BAX0SEwclVkDAwEFiOBkLKSAhREY1IAF/wMB8ASM8UVdZAAAAAQAx/98B4AHgABAAAAE1Fwc1Jg4CFhcuAj4CASDAwEBWJQcTEiMpCxk4YgFkfMDAfwEgNUZEISVZV1E8IwAAAAMAAAAAAgABwAALABIAFgAAASEHERQWMyEyNjURASczNTMVMyU3IRcBoP7AYAkHAeAHCf8AoGCAYP6tIAEmIAHAYP6wBwkJBwFQ/uCAYGDAICAAAAMAAAAAAgABwAALABIAFgAAASEHERQWMyEyNjURBxUjNSM3FyU3IRcBoP7AYAkHAeAHCcCAYKCg/q0gASYgAcBg/rAHCQkHAVDAYGCAgOAgIAAAAAQAAAAAAgABwAADABcAGwAjAAATIRUhBSEiBh0BFBY7ARUhNTMyNj0BNCYDIzUzNhQGIiY0NjKAAQD/AAFg/kANExMNYAEAYA0TE43AwIcNFA0NFAHAQCATDaANE4CAEw2gDRP+wKB6FA0NFA0AAAAGAAAAAAIAAcAAAwAHAAsADwATABYAABEhFSEXIRUhFSEVIRUhFSEHIRUhExUnAgD+AMABQP7AAUD+wAFA/sDAAgD+AICAAcBAIEAgQCBAIEABQMBgAAAABgAAAAACAAHAAAMABwALAA8AEwAWAAARIRUhFyEVIRUhFSEVIRUhByEVIT0BFwIA/gDAAUD+wAFA/sABQP7AwAIA/gCAAcBAIEAgQCBAIECAwGAABAAAAAACAAGgAAMABwAPABQAABkBIREDIREhBhQWMjY0JiITIRMXNwIAIP5AAcCAHCgcHChE/oBggEABoP5gAaD+gAFgPCgcHCgc/uABAKAwAAAAAQAA/+ACAAHAABQAABIyFhQGIyInDgEHNT4BNTQnLgE1NJbUlpZqFBQmWTkcJAEsMwHAeqx6AyYbAg4NLBkHBx5UMFYAAAAAAAAMAJYAAQAAAAAAAQAHABAAAQAAAAAAAgAHACgAAQAAAAAAAwAjAHgAAQAAAAAABAAHAKwAAQAAAAAABQALAMwAAQAAAAAABgAHAOgAAwABBAkAAQAOAAAAAwABBAkAAgAOABgAAwABBAkAAwBGADAAAwABBAkABAAOAJwAAwABBAkABQAWALQAAwABBAkABgAOANgAZgBpAHIAZQBwAGEAZAAAZmlyZXBhZAAAUgBlAGcAdQBsAGEAcgAAUmVndWxhcgAARgBvAG4AdABGAG8AcgBnAGUAIAAyAC4AMAAgADoAIABmAGkAcgBlAHAAYQBkACAAOgAgADIAMwAtADcALQAyADAAMQAzAABGb250Rm9yZ2UgMi4wIDogZmlyZXBhZCA6IDIzLTctMjAxMwAAZgBpAHIAZQBwAGEAZAAAZmlyZXBhZAAAVgBlAHIAcwBpAG8AbgAgADEALgAwAABWZXJzaW9uIDEuMAAAZgBpAHIAZQBwAGEAZAAAZmlyZXBhZAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABoAAAABAAIBAgEDAQQBBQEGAQcBCAEJAQoBCwEMAQ0BDgEPARABEQESARMBFAEVARYBFwEYB3VuaUYwMDAHdW5pRTAwMAd1bmlFMDAxB3VuaUUwMDIHdW5pRTAwMwd1bmlFMDA0B3VuaUUwMDUHdW5pRTAwNgd1bmlFMDA3B3VuaUUwMDgHdW5pRTAwOQd1bmlFMDBBB3VuaUUwMEIHdW5pRTAwQwd1bmlFMDBEB3VuaUUwMEUHdW5pRTAxMAd1bmlFMDExB3VuaUUwMTIHdW5pRTAxMwd1bmlFMDE0B3VuaUUwMTUHdW5pRTAwRgAAAAAAAf//AAIAAQAAAA4AAAAYAAAAAAACAAEAAwAZAAEABAAAAAIAAAAAAAEAAAAAzD2izwAAAADOE2PoAAAAAM4TY+g=) format('truetype'); - font-weight: normal; - font-style: normal; -} - -.firepad-tb-bold, .firepad-tb-italic, .firepad-tb-underline, .firepad-tb-strikethrough, .firepad-tb-list, .firepad-tb-list-2, .firepad-tb-numbered-list, .firepad-tb-paragraph-left, .firepad-tb-paragraph-center, .firepad-tb-paragraph-right, .firepad-tb-paragraph-justify, .firepad-tb-menu, .firepad-tb-link, .firepad-tb-undo, .firepad-tb-redo, .firepad-tb-box-add, .firepad-tb-box-remove, .firepad-tb-print, .firepad-tb-indent-decrease, .firepad-tb-indent-increase, .firepad-tb-insert-image, .firepad-tb-bubble { - font-family: 'firepad'; - speak: none; - font-style: normal; - font-weight: normal; - font-variant: normal; - text-transform: none; - line-height: 1; - -webkit-font-smoothing: antialiased; -} -.firepad-tb-bold:before { - content: "\e000"; -} -.firepad-tb-italic:before { - content: "\e001"; -} -.firepad-tb-underline:before { - content: "\e002"; -} -.firepad-tb-strikethrough:before { - content: "\e003"; -} -.firepad-tb-list:before { - content: "\e004"; -} -.firepad-tb-list-2:before { - content: "\e005"; -} -.firepad-tb-numbered-list:before { - content: "\e006"; -} -.firepad-tb-paragraph-left:before { - content: "\e007"; -} -.firepad-tb-paragraph-center:before { - content: "\e008"; -} -.firepad-tb-paragraph-right:before { - content: "\e009"; -} -.firepad-tb-paragraph-justify:before { - content: "\e00a"; -} -.firepad-tb-menu:before { - content: "\e00b"; -} -.firepad-tb-link:before { - content: "\e00c"; -} -.firepad-tb-undo:before { - content: "\e00d"; -} -.firepad-tb-redo:before { - content: "\e00e"; -} -.firepad-tb-box-add:before { - content: "\e010"; -} -.firepad-tb-box-remove:before { - content: "\e011"; -} -.firepad-tb-print:before { - content: "\e012"; -} -.firepad-tb-indent-decrease:before { - content: "\e013"; -} -.firepad-tb-indent-increase:before { - content: "\e014"; -} -.firepad-tb-insert-image:before { - content: "\e015"; -} -.firepad-tb-bubble:before { - content: "\e00f"; -} diff --git a/dist/firepad.eot b/dist/firepad.eot deleted file mode 100644 index 3711d27959538d0aa02ede62d28ce4d6352505d0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4144 zcmds4O>7&-6@G8|vyx?!qLx3FEK5tRNXkttiAydi$Fj3-ZMucyMsWnC1`20oOa2M0 zAINrV8*aP@qrEh!fdW4DK(v>jO$#4-N`PVyJqC>rfzd+>(}xxTTC~3S&_l_7vpcjX zRZie*hn#&sGw;25Z{ED!neHe0X@ZDJA<-0ygnR^?dn~Xw<^q~9GiYGD0(;aNj|80V zXPhq3W!j)+TBk)?!cd$}gStr9=?XX-;ACh4oDI5)nQOF4I+bXWrv16wrNI>Ih{0xv z=IZtNQM3}}6Iab>ykgTMcVXgB8bkJs0( zUVD4$UkFPa;~e*WgMX3n)}wb5v(Kn6*oQx9=f8wJ1+e{CA$|g~M{W~D@qf~OOp{`M zORzGwMmek8Rel#rg({)>(EFheL(TB+a5e0N zkuM^2?Iv=nDYiVxIR%Ny&6Xdekl252u!p!0QjREJwJL>ILdiplDX?PM%%q~PaMrMV zjL(gnv}{|~ZO1w}_J?wMWF%dlo106Ij{eh_vQD<_lh)XwUll3^&&30_ZRq!)zYj2N zgvx1X(hQ3|yJ0uc+0$FDuCuF)1u0$YNkgKXPDRHGhXu@L%v#lo~TAHXPt#RGnO^bPoxHZtjy8XYSZta2)2Rx1S~XO#=Z zLZwzUvzfF}DU3x^nX;Kx=z7|mbSF*w;CBxmEVk*sqL^Kb!v~uB$kK*zSQOy{O?&DPE5S!J*Tq&p`!otf+6|77;p9{vz zxnR7wjn03KeD-U`x9eYb)ZSiIJhHv2E^s{fUaKik_8DDx>!#yRkM8Q8-XAc(LWdKW z5O(IIjb62X)1bGBHvOr4dN6Nu>b;%hA8rbvo^)a!A{F_j(Oxo$G-OQYrvy7MHd6T}~%j&W_q%O4oDey_eI~@v#Ft(%lf-rpur3QM`F{ z#7lg!i2O+4O@kUEBG!qN(3a2EtfIIu?uqUuCX zMA5Vrt*R-hR4K-%j6d$gG^7N7q8P@?_|mu%iA;o}3glK&DFMt;a)c)pHC?Qwt0PLB`9}kqnqROz@94Ri!M^=^?pWQ@`Htt^W@GlPcg`TT zOr`c?ZU*vfWmT&XS2Ed2<%q(%=h+K+RU0t&_YKaR_yv8lTkHM74+m5{;Z#5Ae zk++@GDe_iD*V09C0G=Nzznlq<>vkOmwms+N^d4rs6?fFzrPhVkPHYkvNeVDmd&0!JTW{xajfM!u8nPV&|P+B1#`I7WNJ)foh;9oC$tTS?-66H z7-!Ww!d9g?iO2oV@85qfl`5P$I6Tg;mf5@Y(XCKnuM&^FI;g56^V17ZgSMOQ3vFmj zDzZGl72p#R58;h*L*ilZKazL^_2N^Bi|^Ji;U9;4!HGj#fGfcFNIXP!T$}=a82n#I zJc6D3y~Ly7|9Shh?84=Z<@LoS+I~4Zy?pWdl||gr>i*U2nh!T{eOo4-O#hmu(`&%D zUep4fNo6`fHFOiJDuJT<+Um9X+Q!9Y-JG1(U(=;sU}pJ1?SMI5!lbRb==s`Sa7|oT z-ne>sZB;K#V%uIM>(@+w4_@>b^7lt!g+mh;yj_37zp0grFOlB7)u%Mw;3v?Q!bSd(x@!h;eXmGGMqeoMk*5*`m=N!l+- zyCrG2B<+@@-BQPH-46oehJ=CwTx1h&JZORr;hUWOGllmInxC(q9{%NLg6O`B{uhus BmHYqz diff --git a/dist/firepad.js b/dist/firepad.js deleted file mode 100644 index 124600e86..000000000 --- a/dist/firepad.js +++ /dev/null @@ -1,6214 +0,0 @@ -/*! - * Firepad is an open-source, collaborative code and text editor. It was designed - * to be embedded inside larger applications. Since it uses Firebase as a backend, - * it requires no server-side code and can be added to any web app simply by - * including a couple JavaScript files. - * - * Firepad 0.0.0 - * http://www.firepad.io/ - * License: MIT - * Copyright: 2014 Firebase - * With code from ot.js (Copyright 2012-2013 Tim Baumann) - */ - -(function (name, definition, context) { - //try CommonJS, then AMD (require.js), then use global. - if (typeof module != 'undefined' && module.exports) module.exports = definition(); - else if (typeof context['define'] == 'function' && context['define']['amd']) define(definition); - else context[name] = definition(); -})('Firepad', function () {var firepad = firepad || { }; -firepad.utils = { }; - -firepad.utils.makeEventEmitter = function(clazz, opt_allowedEVents) { - clazz.prototype.allowedEvents_ = opt_allowedEVents; - - clazz.prototype.on = function(eventType, callback, context) { - this.validateEventType_(eventType); - this.eventListeners_ = this.eventListeners_ || { }; - this.eventListeners_[eventType] = this.eventListeners_[eventType] || []; - this.eventListeners_[eventType].push({ callback: callback, context: context }); - }; - - clazz.prototype.off = function(eventType, callback) { - this.validateEventType_(eventType); - this.eventListeners_ = this.eventListeners_ || { }; - var listeners = this.eventListeners_[eventType] || []; - for(var i = 0; i < listeners.length; i++) { - if (listeners[i].callback === callback) { - listeners.splice(i, 1); - return; - } - } - }; - - clazz.prototype.trigger = function(eventType /*, args ... */) { - this.eventListeners_ = this.eventListeners_ || { }; - var listeners = this.eventListeners_[eventType] || []; - for(var i = 0; i < listeners.length; i++) { - listeners[i].callback.apply(listeners[i].context, Array.prototype.slice.call(arguments, 1)); - } - }; - - clazz.prototype.validateEventType_ = function(eventType) { - if (this.allowedEvents_) { - var allowed = false; - for(var i = 0; i < this.allowedEvents_.length; i++) { - if (this.allowedEvents_[i] === eventType) { - allowed = true; - break; - } - } - if (!allowed) { - throw new Error('Unknown event "' + eventType + '"'); - } - } - }; -}; - -firepad.utils.elt = function(tag, content, attrs) { - var e = document.createElement(tag); - if (typeof content === "string") { - firepad.utils.setTextContent(e, content); - } else if (content) { - for (var i = 0; i < content.length; ++i) { e.appendChild(content[i]); } - } - for(var attr in (attrs || { })) { - e.setAttribute(attr, attrs[attr]); - } - return e; -}; - -firepad.utils.setTextContent = function(e, str) { - e.innerHTML = ""; - e.appendChild(document.createTextNode(str)); -}; - - -firepad.utils.on = function(emitter, type, f, capture) { - if (emitter.addEventListener) { - emitter.addEventListener(type, f, capture || false); - } else if (emitter.attachEvent) { - emitter.attachEvent("on" + type, f); - } -}; - -firepad.utils.off = function(emitter, type, f, capture) { - if (emitter.removeEventListener) { - emitter.removeEventListener(type, f, capture || false); - } else if (emitter.detachEvent) { - emitter.detachEvent("on" + type, f); - } -}; - -firepad.utils.preventDefault = function(e) { - if (e.preventDefault) { - e.preventDefault(); - } else { - e.returnValue = false; - } -}; - -firepad.utils.stopPropagation = function(e) { - if (e.stopPropagation) { - e.stopPropagation(); - } else { - e.cancelBubble = true; - } -}; - -firepad.utils.stopEvent = function(e) { - firepad.utils.preventDefault(e); - firepad.utils.stopPropagation(e); -}; - -firepad.utils.stopEventAnd = function(fn) { - return function(e) { - fn(e); - firepad.utils.stopEvent(e); - return false; - }; -}; - -firepad.utils.trim = function(str) { - return str.replace(/^\s+/g, '').replace(/\s+$/g, ''); -}; - -firepad.utils.stringEndsWith = function(str, suffix) { - var list = (typeof suffix == 'string') ? [suffix] : suffix; - for (var i = 0; i < list.length; i++) { - var suffix = list[i]; - if (str.indexOf(suffix, str.length - suffix.length) !== -1) - return true; - } - return false; -}; - -firepad.utils.assert = function assert (b, msg) { - if (!b) { - throw new Error(msg || "assertion error"); - } -}; - -firepad.utils.log = function() { - if (typeof console !== 'undefined' && typeof console.log !== 'undefined') { - var args = ['Firepad:']; - for(var i = 0; i < arguments.length; i++) { - args.push(arguments[i]); - } - console.log.apply(console, args); - } -}; - -var firepad = firepad || { }; -firepad.Span = (function () { - function Span(pos, length) { - this.pos = pos; - this.length = length; - } - - Span.prototype.end = function() { - return this.pos + this.length; - }; - - return Span; -}()); - -var firepad = firepad || { }; - -firepad.TextOp = (function() { - var utils = firepad.utils; - - // Operation are essentially lists of ops. There are three types of ops: - // - // * Retain ops: Advance the cursor position by a given number of characters. - // Represented by positive ints. - // * Insert ops: Insert a given string at the current cursor position. - // Represented by strings. - // * Delete ops: Delete the next n characters. Represented by negative ints. - function TextOp(type) { - this.type = type; - this.chars = null; - this.text = null; - this.attributes = null; - - if (type === 'insert') { - this.text = arguments[1]; - utils.assert(typeof this.text === 'string'); - this.attributes = arguments[2] || { }; - utils.assert (typeof this.attributes === 'object'); - } else if (type === 'delete') { - this.chars = arguments[1]; - utils.assert(typeof this.chars === 'number'); - } else if (type === 'retain') { - this.chars = arguments[1]; - utils.assert(typeof this.chars === 'number'); - this.attributes = arguments[2] || { }; - utils.assert (typeof this.attributes === 'object'); - } - } - - TextOp.prototype.isInsert = function() { return this.type === 'insert'; }; - TextOp.prototype.isDelete = function() { return this.type === 'delete'; }; - TextOp.prototype.isRetain = function() { return this.type === 'retain'; }; - - TextOp.prototype.equals = function(other) { - return (this.type === other.type && - this.text === other.text && - this.chars === other.chars && - this.attributesEqual(other.attributes)); - }; - - TextOp.prototype.attributesEqual = function(otherAttributes) { - for (var attr in this.attributes) { - if (this.attributes[attr] !== otherAttributes[attr]) { return false; } - } - - for (attr in otherAttributes) { - if (this.attributes[attr] !== otherAttributes[attr]) { return false; } - } - - return true; - }; - - TextOp.prototype.hasEmptyAttributes = function() { - var empty = true; - for (var attr in this.attributes) { - empty = false; - break; - } - - return empty; - }; - - return TextOp; -})(); - -var firepad = firepad || { }; - -firepad.TextOperation = (function () { - 'use strict'; - var TextOp = firepad.TextOp; - var utils = firepad.utils; - - // Constructor for new operations. - function TextOperation () { - if (!this || this.constructor !== TextOperation) { - // => function was called without 'new' - return new TextOperation(); - } - - // When an operation is applied to an input string, you can think of this as - // if an imaginary cursor runs over the entire string and skips over some - // parts, deletes some parts and inserts characters at some positions. These - // actions (skip/delete/insert) are stored as an array in the "ops" property. - this.ops = []; - // An operation's baseLength is the length of every string the operation - // can be applied to. - this.baseLength = 0; - // The targetLength is the length of every string that results from applying - // the operation on a valid input string. - this.targetLength = 0; - } - - TextOperation.prototype.equals = function (other) { - if (this.baseLength !== other.baseLength) { return false; } - if (this.targetLength !== other.targetLength) { return false; } - if (this.ops.length !== other.ops.length) { return false; } - for (var i = 0; i < this.ops.length; i++) { - if (!this.ops[i].equals(other.ops[i])) { return false; } - } - return true; - }; - - - // After an operation is constructed, the user of the library can specify the - // actions of an operation (skip/insert/delete) with these three builder - // methods. They all return the operation for convenient chaining. - - // Skip over a given number of characters. - TextOperation.prototype.retain = function (n, attributes) { - if (typeof n !== 'number' || n < 0) { - throw new Error("retain expects a positive integer."); - } - if (n === 0) { return this; } - this.baseLength += n; - this.targetLength += n; - attributes = attributes || { }; - var prevOp = (this.ops.length > 0) ? this.ops[this.ops.length - 1] : null; - if (prevOp && prevOp.isRetain() && prevOp.attributesEqual(attributes)) { - // The last op is a retain op with the same attributes => we can merge them into one op. - prevOp.chars += n; - } else { - // Create a new op. - this.ops.push(new TextOp('retain', n, attributes)); - } - return this; - }; - - // Insert a string at the current position. - TextOperation.prototype.insert = function (str, attributes) { - if (typeof str !== 'string') { - throw new Error("insert expects a string"); - } - if (str === '') { return this; } - attributes = attributes || { }; - this.targetLength += str.length; - var prevOp = (this.ops.length > 0) ? this.ops[this.ops.length - 1] : null; - var prevPrevOp = (this.ops.length > 1) ? this.ops[this.ops.length - 2] : null; - if (prevOp && prevOp.isInsert() && prevOp.attributesEqual(attributes)) { - // Merge insert op. - prevOp.text += str; - } else if (prevOp && prevOp.isDelete()) { - // It doesn't matter when an operation is applied whether the operation - // is delete(3), insert("something") or insert("something"), delete(3). - // Here we enforce that in this case, the insert op always comes first. - // This makes all operations that have the same effect when applied to - // a document of the right length equal in respect to the `equals` method. - if (prevPrevOp && prevPrevOp.isInsert() && prevPrevOp.attributesEqual(attributes)) { - prevPrevOp.text += str; - } else { - this.ops[this.ops.length - 1] = new TextOp('insert', str, attributes); - this.ops.push(prevOp); - } - } else { - this.ops.push(new TextOp('insert', str, attributes)); - } - return this; - }; - - // Delete a string at the current position. - TextOperation.prototype['delete'] = function (n) { - if (typeof n === 'string') { n = n.length; } - if (typeof n !== 'number' || n < 0) { - throw new Error("delete expects a positive integer or a string"); - } - if (n === 0) { return this; } - this.baseLength += n; - var prevOp = (this.ops.length > 0) ? this.ops[this.ops.length - 1] : null; - if (prevOp && prevOp.isDelete()) { - prevOp.chars += n; - } else { - this.ops.push(new TextOp('delete', n)); - } - return this; - }; - - // Tests whether this operation has no effect. - TextOperation.prototype.isNoop = function () { - return this.ops.length === 0 || - (this.ops.length === 1 && (this.ops[0].isRetain() && this.ops[0].hasEmptyAttributes())); - }; - - TextOperation.prototype.clone = function() { - var clone = new TextOperation(); - for(var i = 0; i < this.ops.length; i++) { - if (this.ops[i].isRetain()) { - clone.retain(this.ops[i].chars, this.ops[i].attributes); - } else if (this.ops[i].isInsert()) { - clone.insert(this.ops[i].text, this.ops[i].attributes); - } else { - clone['delete'](this.ops[i].chars); - } - } - - return clone; - }; - - // Pretty printing. - TextOperation.prototype.toString = function () { - // map: build a new array by applying a function to every element in an old - // array. - var map = Array.prototype.map || function (fn) { - var arr = this; - var newArr = []; - for (var i = 0, l = arr.length; i < l; i++) { - newArr[i] = fn(arr[i]); - } - return newArr; - }; - return map.call(this.ops, function (op) { - if (op.isRetain()) { - return "retain " + op.chars; - } else if (op.isInsert()) { - return "insert '" + op.text + "'"; - } else { - return "delete " + (op.chars); - } - }).join(', '); - }; - - // Converts operation into a JSON value. - TextOperation.prototype.toJSON = function () { - var ops = []; - for(var i = 0; i < this.ops.length; i++) { - // We prefix ops with their attributes if non-empty. - if (!this.ops[i].hasEmptyAttributes()) { - ops.push(this.ops[i].attributes); - } - if (this.ops[i].type === 'retain') { - ops.push(this.ops[i].chars); - } else if (this.ops[i].type === 'insert') { - ops.push(this.ops[i].text); - } else if (this.ops[i].type === 'delete') { - ops.push(-this.ops[i].chars); - } - } - // Return an array with /something/ in it, since an empty array will be treated as null by Firebase. - if (ops.length === 0) { - ops.push(0); - } - return ops; - }; - - // Converts a plain JS object into an operation and validates it. - TextOperation.fromJSON = function (ops) { - var o = new TextOperation(); - for (var i = 0, l = ops.length; i < l; i++) { - var op = ops[i]; - var attributes = { }; - if (typeof op === 'object') { - attributes = op; - i++; - op = ops[i]; - } - if (typeof op === 'number') { - if (op > 0) { - o.retain(op, attributes); - } else { - o['delete'](-op); - } - } else { - utils.assert(typeof op === 'string'); - o.insert(op, attributes); - } - } - return o; - }; - - // Apply an operation to a string, returning a new string. Throws an error if - // there's a mismatch between the input string and the operation. - TextOperation.prototype.apply = function (str, oldAttributes, newAttributes) { - var operation = this; - oldAttributes = oldAttributes || []; - newAttributes = newAttributes || []; - if (str.length !== operation.baseLength) { - throw new Error("The operation's base length must be equal to the string's length."); - } - var newStringParts = [], j = 0, k, attr; - var oldIndex = 0; - var ops = this.ops; - for (var i = 0, l = ops.length; i < l; i++) { - var op = ops[i]; - if (op.isRetain()) { - if (oldIndex + op.chars > str.length) { - throw new Error("Operation can't retain more characters than are left in the string."); - } - // Copy skipped part of the retained string. - newStringParts[j++] = str.slice(oldIndex, oldIndex + op.chars); - - // Copy (and potentially update) attributes for each char in retained string. - for(k = 0; k < op.chars; k++) { - var currAttributes = oldAttributes[oldIndex + k] || { }, updatedAttributes = { }; - for(attr in currAttributes) { - updatedAttributes[attr] = currAttributes[attr]; - utils.assert(updatedAttributes[attr] !== false); - } - for(attr in op.attributes) { - if (op.attributes[attr] === false) { - delete updatedAttributes[attr]; - } else { - updatedAttributes[attr] = op.attributes[attr]; - } - utils.assert(updatedAttributes[attr] !== false); - } - newAttributes.push(updatedAttributes); - } - - oldIndex += op.chars; - } else if (op.isInsert()) { - // Insert string. - newStringParts[j++] = op.text; - - // Insert attributes for each char. - for(k = 0; k < op.text.length; k++) { - var insertedAttributes = { }; - for(attr in op.attributes) { - insertedAttributes[attr] = op.attributes[attr]; - utils.assert(insertedAttributes[attr] !== false); - } - newAttributes.push(insertedAttributes); - } - } else { // delete op - oldIndex += op.chars; - } - } - if (oldIndex !== str.length) { - throw new Error("The operation didn't operate on the whole string."); - } - var newString = newStringParts.join(''); - utils.assert(newString.length === newAttributes.length); - - return newString; - }; - - // Computes the inverse of an operation. The inverse of an operation is the - // operation that reverts the effects of the operation, e.g. when you have an - // operation 'insert("hello "); skip(6);' then the inverse is 'delete("hello "); - // skip(6);'. The inverse should be used for implementing undo. - TextOperation.prototype.invert = function (str) { - var strIndex = 0; - var inverse = new TextOperation(); - var ops = this.ops; - for (var i = 0, l = ops.length; i < l; i++) { - var op = ops[i]; - if (op.isRetain()) { - inverse.retain(op.chars); - strIndex += op.chars; - } else if (op.isInsert()) { - inverse['delete'](op.text.length); - } else { // delete op - inverse.insert(str.slice(strIndex, strIndex + op.chars)); - strIndex += op.chars; - } - } - return inverse; - }; - - // Compose merges two consecutive operations into one operation, that - // preserves the changes of both. Or, in other words, for each input string S - // and a pair of consecutive operations A and B, - // apply(apply(S, A), B) = apply(S, compose(A, B)) must hold. - TextOperation.prototype.compose = function (operation2) { - var operation1 = this; - if (operation1.targetLength !== operation2.baseLength) { - throw new Error("The base length of the second operation has to be the target length of the first operation"); - } - - function composeAttributes(first, second, firstOpIsInsert) { - var merged = { }, attr; - for(attr in first) { - merged[attr] = first[attr]; - } - for(attr in second) { - if (firstOpIsInsert && second[attr] === false) { - delete merged[attr]; - } else { - merged[attr] = second[attr]; - } - } - return merged; - } - - var operation = new TextOperation(); // the combined operation - var ops1 = operation1.clone().ops, ops2 = operation2.clone().ops; - var i1 = 0, i2 = 0; // current index into ops1 respectively ops2 - var op1 = ops1[i1++], op2 = ops2[i2++]; // current ops - var attributes; - while (true) { - // Dispatch on the type of op1 and op2 - if (typeof op1 === 'undefined' && typeof op2 === 'undefined') { - // end condition: both ops1 and ops2 have been processed - break; - } - - if (op1 && op1.isDelete()) { - operation['delete'](op1.chars); - op1 = ops1[i1++]; - continue; - } - if (op2 && op2.isInsert()) { - operation.insert(op2.text, op2.attributes); - op2 = ops2[i2++]; - continue; - } - - if (typeof op1 === 'undefined') { - throw new Error("Cannot compose operations: first operation is too short."); - } - if (typeof op2 === 'undefined') { - throw new Error("Cannot compose operations: first operation is too long."); - } - - if (op1.isRetain() && op2.isRetain()) { - attributes = composeAttributes(op1.attributes, op2.attributes); - if (op1.chars > op2.chars) { - operation.retain(op2.chars, attributes); - op1.chars -= op2.chars; - op2 = ops2[i2++]; - } else if (op1.chars === op2.chars) { - operation.retain(op1.chars, attributes); - op1 = ops1[i1++]; - op2 = ops2[i2++]; - } else { - operation.retain(op1.chars, attributes); - op2.chars -= op1.chars; - op1 = ops1[i1++]; - } - } else if (op1.isInsert() && op2.isDelete()) { - if (op1.text.length > op2.chars) { - op1.text = op1.text.slice(op2.chars); - op2 = ops2[i2++]; - } else if (op1.text.length === op2.chars) { - op1 = ops1[i1++]; - op2 = ops2[i2++]; - } else { - op2.chars -= op1.text.length; - op1 = ops1[i1++]; - } - } else if (op1.isInsert() && op2.isRetain()) { - attributes = composeAttributes(op1.attributes, op2.attributes, /*firstOpIsInsert=*/true); - if (op1.text.length > op2.chars) { - operation.insert(op1.text.slice(0, op2.chars), attributes); - op1.text = op1.text.slice(op2.chars); - op2 = ops2[i2++]; - } else if (op1.text.length === op2.chars) { - operation.insert(op1.text, attributes); - op1 = ops1[i1++]; - op2 = ops2[i2++]; - } else { - operation.insert(op1.text, attributes); - op2.chars -= op1.text.length; - op1 = ops1[i1++]; - } - } else if (op1.isRetain() && op2.isDelete()) { - if (op1.chars > op2.chars) { - operation['delete'](op2.chars); - op1.chars -= op2.chars; - op2 = ops2[i2++]; - } else if (op1.chars === op2.chars) { - operation['delete'](op2.chars); - op1 = ops1[i1++]; - op2 = ops2[i2++]; - } else { - operation['delete'](op1.chars); - op2.chars -= op1.chars; - op1 = ops1[i1++]; - } - } else { - throw new Error( - "This shouldn't happen: op1: " + - JSON.stringify(op1) + ", op2: " + - JSON.stringify(op2) - ); - } - } - return operation; - }; - - function getSimpleOp (operation) { - var ops = operation.ops; - switch (ops.length) { - case 1: - return ops[0]; - case 2: - return ops[0].isRetain() ? ops[1] : (ops[1].isRetain() ? ops[0] : null); - case 3: - if (ops[0].isRetain() && ops[2].isRetain()) { return ops[1]; } - } - return null; - } - - function getStartIndex (operation) { - if (operation.ops[0].isRetain()) { return operation.ops[0].chars; } - return 0; - } - - // When you use ctrl-z to undo your latest changes, you expect the program not - // to undo every single keystroke but to undo your last sentence you wrote at - // a stretch or the deletion you did by holding the backspace key down. This - // This can be implemented by composing operations on the undo stack. This - // method can help decide whether two operations should be composed. It - // returns true if the operations are consecutive insert operations or both - // operations delete text at the same position. You may want to include other - // factors like the time since the last change in your decision. - TextOperation.prototype.shouldBeComposedWith = function (other) { - if (this.isNoop() || other.isNoop()) { return true; } - - var startA = getStartIndex(this), startB = getStartIndex(other); - var simpleA = getSimpleOp(this), simpleB = getSimpleOp(other); - if (!simpleA || !simpleB) { return false; } - - if (simpleA.isInsert() && simpleB.isInsert()) { - return startA + simpleA.text.length === startB; - } - - if (simpleA.isDelete() && simpleB.isDelete()) { - // there are two possibilities to delete: with backspace and with the - // delete key. - return (startB + simpleB.chars === startA) || startA === startB; - } - - return false; - }; - - // Decides whether two operations should be composed with each other - // if they were inverted, that is - // `shouldBeComposedWith(a, b) = shouldBeComposedWithInverted(b^{-1}, a^{-1})`. - TextOperation.prototype.shouldBeComposedWithInverted = function (other) { - if (this.isNoop() || other.isNoop()) { return true; } - - var startA = getStartIndex(this), startB = getStartIndex(other); - var simpleA = getSimpleOp(this), simpleB = getSimpleOp(other); - if (!simpleA || !simpleB) { return false; } - - if (simpleA.isInsert() && simpleB.isInsert()) { - return startA + simpleA.text.length === startB || startA === startB; - } - - if (simpleA.isDelete() && simpleB.isDelete()) { - return startB + simpleB.chars === startA; - } - - return false; - }; - - - TextOperation.transformAttributes = function(attributes1, attributes2) { - var attributes1prime = { }, attributes2prime = { }; - var attr, allAttrs = { }; - for(attr in attributes1) { allAttrs[attr] = true; } - for(attr in attributes2) { allAttrs[attr] = true; } - - for (attr in allAttrs) { - var attr1 = attributes1[attr], attr2 = attributes2[attr]; - utils.assert(attr1 != null || attr2 != null); - if (attr1 == null) { - // Only modified by attributes2; keep it. - attributes2prime[attr] = attr2; - } else if (attr2 == null) { - // only modified by attributes1; keep it - attributes1prime[attr] = attr1; - } else if (attr1 === attr2) { - // Both set it to the same value. Nothing to do. - } else { - // attr1 and attr2 are different. Prefer attr1. - attributes1prime[attr] = attr1; - } - } - return [attributes1prime, attributes2prime]; - }; - - // Transform takes two operations A and B that happened concurrently and - // produces two operations A' and B' (in an array) such that - // `apply(apply(S, A), B') = apply(apply(S, B), A')`. This function is the - // heart of OT. - TextOperation.transform = function (operation1, operation2) { - if (operation1.baseLength !== operation2.baseLength) { - throw new Error("Both operations have to have the same base length"); - } - - var operation1prime = new TextOperation(); - var operation2prime = new TextOperation(); - var ops1 = operation1.clone().ops, ops2 = operation2.clone().ops; - var i1 = 0, i2 = 0; - var op1 = ops1[i1++], op2 = ops2[i2++]; - while (true) { - // At every iteration of the loop, the imaginary cursor that both - // operation1 and operation2 have that operates on the input string must - // have the same position in the input string. - - if (typeof op1 === 'undefined' && typeof op2 === 'undefined') { - // end condition: both ops1 and ops2 have been processed - break; - } - - // next two cases: one or both ops are insert ops - // => insert the string in the corresponding prime operation, skip it in - // the other one. If both op1 and op2 are insert ops, prefer op1. - if (op1 && op1.isInsert()) { - operation1prime.insert(op1.text, op1.attributes); - operation2prime.retain(op1.text.length); - op1 = ops1[i1++]; - continue; - } - if (op2 && op2.isInsert()) { - operation1prime.retain(op2.text.length); - operation2prime.insert(op2.text, op2.attributes); - op2 = ops2[i2++]; - continue; - } - - if (typeof op1 === 'undefined') { - throw new Error("Cannot transform operations: first operation is too short."); - } - if (typeof op2 === 'undefined') { - throw new Error("Cannot transform operations: first operation is too long."); - } - - var minl; - if (op1.isRetain() && op2.isRetain()) { - // Simple case: retain/retain - var attributesPrime = TextOperation.transformAttributes(op1.attributes, op2.attributes); - if (op1.chars > op2.chars) { - minl = op2.chars; - op1.chars -= op2.chars; - op2 = ops2[i2++]; - } else if (op1.chars === op2.chars) { - minl = op2.chars; - op1 = ops1[i1++]; - op2 = ops2[i2++]; - } else { - minl = op1.chars; - op2.chars -= op1.chars; - op1 = ops1[i1++]; - } - - operation1prime.retain(minl, attributesPrime[0]); - operation2prime.retain(minl, attributesPrime[1]); - } else if (op1.isDelete() && op2.isDelete()) { - // Both operations delete the same string at the same position. We don't - // need to produce any operations, we just skip over the delete ops and - // handle the case that one operation deletes more than the other. - if (op1.chars > op2.chars) { - op1.chars -= op2.chars; - op2 = ops2[i2++]; - } else if (op1.chars === op2.chars) { - op1 = ops1[i1++]; - op2 = ops2[i2++]; - } else { - op2.chars -= op1.chars; - op1 = ops1[i1++]; - } - // next two cases: delete/retain and retain/delete - } else if (op1.isDelete() && op2.isRetain()) { - if (op1.chars > op2.chars) { - minl = op2.chars; - op1.chars -= op2.chars; - op2 = ops2[i2++]; - } else if (op1.chars === op2.chars) { - minl = op2.chars; - op1 = ops1[i1++]; - op2 = ops2[i2++]; - } else { - minl = op1.chars; - op2.chars -= op1.chars; - op1 = ops1[i1++]; - } - operation1prime['delete'](minl); - } else if (op1.isRetain() && op2.isDelete()) { - if (op1.chars > op2.chars) { - minl = op2.chars; - op1.chars -= op2.chars; - op2 = ops2[i2++]; - } else if (op1.chars === op2.chars) { - minl = op1.chars; - op1 = ops1[i1++]; - op2 = ops2[i2++]; - } else { - minl = op1.chars; - op2.chars -= op1.chars; - op1 = ops1[i1++]; - } - operation2prime['delete'](minl); - } else { - throw new Error("The two operations aren't compatible"); - } - } - - return [operation1prime, operation2prime]; - }; - - // convenience method to write transform(a, b) as a.transform(b) - TextOperation.prototype.transform = function(other) { - return TextOperation.transform(this, other); - }; - - return TextOperation; -}()); - -var firepad = firepad || { }; - -// TODO: Rewrite this (probably using a splay tree) to be efficient. Right now it's based on a linked list -// so all operations are O(n), where n is the number of spans in the list. -firepad.AnnotationList = (function () { - var Span = firepad.Span; - - function assert(bool, text) { - if (!bool) { - throw new Error('AnnotationList assertion failed' + (text ? (': ' + text) : '')); - } - } - - function OldAnnotatedSpan(pos, node) { - this.pos = pos; - this.length = node.length; - this.annotation = node.annotation; - this.attachedObject_ = node.attachedObject; - } - - OldAnnotatedSpan.prototype.getAttachedObject = function() { - return this.attachedObject_; - }; - - function NewAnnotatedSpan(pos, node) { - this.pos = pos; - this.length = node.length; - this.annotation = node.annotation; - this.node_ = node; - } - - NewAnnotatedSpan.prototype.attachObject = function(object) { - this.node_.attachedObject = object; - }; - - var NullAnnotation = { equals: function() { return false; } }; - - function AnnotationList(changeHandler) { - // There's always a head node; to avoid special cases. - this.head_ = new Node(0, NullAnnotation); - this.changeHandler_ = changeHandler; - } - - AnnotationList.prototype.insertAnnotatedSpan = function(span, annotation) { - this.wrapOperation_(new Span(span.pos, 0), function(oldPos, old) { - assert(!old || old.next === null); // should be 0 or 1 nodes. - var toInsert = new Node(span.length, annotation); - if (!old) { - return toInsert; - } else { - assert (span.pos > oldPos && span.pos < oldPos + old.length); - var newNodes = new Node(0, NullAnnotation); - // Insert part of old before insertion point. - newNodes.next = new Node(span.pos - oldPos, old.annotation); - // Insert new node. - newNodes.next.next = toInsert; - // Insert part of old after insertion point. - toInsert.next = new Node(oldPos + old.length - span.pos, old.annotation); - return newNodes.next; - } - }); - }; - - AnnotationList.prototype.removeSpan = function(removeSpan) { - if (removeSpan.length === 0) { return; } - - this.wrapOperation_(removeSpan, function(oldPos, old) { - assert (old !== null); - var newNodes = new Node(0, NullAnnotation), current = newNodes; - // Add new node for part before the removed span (if any). - if (removeSpan.pos > oldPos) { - current.next = new Node(removeSpan.pos - oldPos, old.annotation); - current = current.next; - } - - // Skip over removed nodes. - while (removeSpan.end() > oldPos + old.length) { - oldPos += old.length; - old = old.next; - } - - // Add new node for part after the removed span (if any). - var afterChars = oldPos + old.length - removeSpan.end(); - if (afterChars > 0) { - current.next = new Node(afterChars, old.annotation); - } - - return newNodes.next; - }); - }; - - AnnotationList.prototype.updateSpan = function (span, updateFn) { - if (span.length === 0) { return; } - - this.wrapOperation_(span, function(oldPos, old) { - assert (old !== null); - var newNodes = new Node(0, NullAnnotation), current = newNodes, currentPos = oldPos; - - // Add node for any characters before the span we're updating. - var beforeChars = span.pos - currentPos; - assert(beforeChars < old.length); - if (beforeChars > 0) { - current.next = new Node(beforeChars, old.annotation); - current = current.next; - currentPos += current.length; - } - - // Add updated nodes for entirely updated nodes. - while (old !== null && span.end() >= oldPos + old.length) { - var length = oldPos + old.length - currentPos; - current.next = new Node(length, updateFn(old.annotation, length)); - current = current.next; - oldPos += old.length; - old = old.next; - currentPos = oldPos; - } - - // Add updated nodes for last node. - var updateChars = span.end() - currentPos; - if (updateChars > 0) { - assert(updateChars < old.length); - current.next = new Node(updateChars, updateFn(old.annotation, updateChars)); - current = current.next; - currentPos += current.length; - - // Add non-updated remaining part of node. - current.next = new Node(oldPos + old.length - currentPos, old.annotation); - } - - return newNodes.next; - }); - }; - - AnnotationList.prototype.wrapOperation_ = function(span, operationFn) { - if (span.pos < 0) { - throw new Error('Span start cannot be negative.'); - } - var oldNodes = [], newNodes = []; - - var res = this.getAffectedNodes_(span); - - var tail; - if (res.start !== null) { - tail = res.end.next; - // Temporarily truncate list so we can pass it to operationFn. We'll splice it back in later. - res.end.next = null; - } else { - // start and end are null, because span is empty and lies on the border of two nodes. - tail = res.succ; - } - - // Create a new segment to replace the affected nodes. - var newSegment = operationFn(res.startPos, res.start); - - var includePredInOldNodes = false, includeSuccInOldNodes = false; - if (newSegment) { - this.mergeNodesWithSameAnnotations_(newSegment); - - var newPos; - if (res.pred && res.pred.annotation.equals(newSegment.annotation)) { - // We can merge the pred node with newSegment's first node. - includePredInOldNodes = true; - newSegment.length += res.pred.length; - - // Splice newSegment in after beforePred. - res.beforePred.next = newSegment; - newPos = res.predPos; - } else { - // Splice newSegment in after beforeStart. - res.beforeStart.next = newSegment; - newPos = res.startPos; - } - - // Generate newNodes, but not the last one (since we may be able to merge it with succ). - while(newSegment.next) { - newNodes.push(new NewAnnotatedSpan(newPos, newSegment)); - newPos += newSegment.length; - newSegment = newSegment.next; - } - - if (res.succ && res.succ.annotation.equals(newSegment.annotation)) { - // We can merge newSegment's last node with the succ node. - newSegment.length += res.succ.length; - includeSuccInOldNodes = true; - - // Splice rest of list after succ after newSegment. - newSegment.next = res.succ.next; - } else { - // Splice tail after newSegment. - newSegment.next = tail; - } - - // Add last newSegment node to newNodes. - newNodes.push(new NewAnnotatedSpan(newPos, newSegment)); - - } else { - // newList is empty. Try to merge pred and succ. - if (res.pred && res.succ && res.pred.annotation.equals(res.succ.annotation)) { - includePredInOldNodes = true; - includeSuccInOldNodes = true; - - // Create succ + pred merged node and splice list together. - newSegment = new Node(res.pred.length + res.succ.length, res.pred.annotation); - res.beforePred.next = newSegment; - newSegment.next = res.succ.next; - - newNodes.push(new NewAnnotatedSpan(res.startPos - res.pred.length, newSegment)); - } else { - // Just splice list back together. - res.beforeStart.next = tail; - } - } - - // Build list of oldNodes. - - if (includePredInOldNodes) { - oldNodes.push(new OldAnnotatedSpan(res.predPos, res.pred)); - } - - var oldPos = res.startPos, oldSegment = res.start; - while (oldSegment !== null) { - oldNodes.push(new OldAnnotatedSpan(oldPos, oldSegment)); - oldPos += oldSegment.length; - oldSegment = oldSegment.next; - } - - if (includeSuccInOldNodes) { - oldNodes.push(new OldAnnotatedSpan(oldPos, res.succ)); - } - - this.changeHandler_(oldNodes, newNodes); - }; - - AnnotationList.prototype.getAffectedNodes_ = function(span) { - // We want to find nodes 'start', 'end', 'beforeStart', 'pred', and 'succ' where: - // - 'start' contains the first character in span. - // - 'end' contains the last character in span. - // - 'beforeStart' is the node before 'start'. - // - 'beforePred' is the node before 'pred'. - // - 'succ' contains the node after 'end' if span.end() was on a node boundary, else null. - // - 'pred' contains the node before 'start' if span.pos was on a node boundary, else null. - - var result = {}; - - var prevprev = null, prev = this.head_, current = prev.next, currentPos = 0; - while (current !== null && span.pos >= currentPos + current.length) { - currentPos += current.length; - prevprev = prev; - prev = current; - current = current.next; - } - if (current === null && !(span.length === 0 && span.pos === currentPos)) { - throw new Error('Span start exceeds the bounds of the AnnotationList.'); - } - - result.startPos = currentPos; - // Special case if span is empty and on the border of two nodes - if (span.length === 0 && span.pos === currentPos) { - result.start = null; - } else { - result.start = current; - } - result.beforeStart = prev; - - if (currentPos === span.pos && currentPos > 0) { - result.pred = prev; - result.predPos = currentPos - prev.length; - result.beforePred = prevprev; - } else { - result.pred = null; - } - - while (current !== null && span.end() > currentPos) { - currentPos += current.length; - prev = current; - current = current.next; - } - if (span.end() > currentPos) { - throw new Error('Span end exceeds the bounds of the AnnotationList.'); - } - - // Special case if span is empty and on the border of two nodes. - if (span.length === 0 && span.end() === currentPos) { - result.end = null; - } else { - result.end = prev; - } - result.succ = (currentPos === span.end()) ? current : null; - - return result; - }; - - AnnotationList.prototype.mergeNodesWithSameAnnotations_ = function(list) { - if (!list) { return; } - var prev = null, curr = list; - while (curr) { - if (prev && prev.annotation.equals(curr.annotation)) { - prev.length += curr.length; - prev.next = curr.next; - } else { - prev = curr; - } - curr = curr.next; - } - }; - - AnnotationList.prototype.forEach = function(callback) { - var current = this.head_.next; - while (current !== null) { - callback(current.length, current.annotation, current.attachedObject); - current = current.next; - } - }; - - AnnotationList.prototype.getAnnotatedSpansForPos = function(pos) { - var currentPos = 0; - var current = this.head_.next, prev = null; - while (current !== null && currentPos + current.length <= pos) { - currentPos += current.length; - prev = current; - current = current.next; - } - if (current === null && currentPos !== pos) { - throw new Error('pos exceeds the bounds of the AnnotationList'); - } - - var res = []; - if (currentPos === pos && prev) { - res.push(new OldAnnotatedSpan(currentPos - prev.length, prev)); - } - if (current) { - res.push(new OldAnnotatedSpan(currentPos, current)); - } - return res; - }; - - AnnotationList.prototype.getAnnotatedSpansForSpan = function(span) { - if (span.length === 0) { - return []; - } - var oldSpans = []; - var res = this.getAffectedNodes_(span); - var currentPos = res.startPos, current = res.start; - while (current !== null && currentPos < span.end()) { - var start = Math.max(currentPos, span.pos), end = Math.min(currentPos + current.length, span.end()); - var oldSpan = new Span(start, end - start); - oldSpan.annotation = current.annotation; - oldSpans.push(oldSpan); - - currentPos += current.length; - current = current.next; - } - return oldSpans; - }; - - // For testing. - AnnotationList.prototype.count = function() { - var count = 0; - var current = this.head_.next, prev = null; - while(current !== null) { - if (prev) { - assert(!prev.annotation.equals(current.annotation)); - } - prev = current; - current = current.next; - count++; - } - return count; - }; - - function Node(length, annotation) { - this.length = length; - this.annotation = annotation; - this.attachedObject = null; - this.next = null; - } - - Node.prototype.clone = function() { - var node = new Node(this.spanLength, this.annotation); - node.next = this.next; - return node; - }; - - return AnnotationList; -}()); - -var firepad = firepad || { }; -firepad.Cursor = (function () { - 'use strict'; - - // A cursor has a `position` and a `selectionEnd`. Both are zero-based indexes - // into the document. When nothing is selected, `selectionEnd` is equal to - // `position`. When there is a selection, `position` is always the side of the - // selection that would move if you pressed an arrow key. - function Cursor (position, selectionEnd) { - this.position = position; - this.selectionEnd = selectionEnd; - } - - Cursor.fromJSON = function (obj) { - return new Cursor(obj.position, obj.selectionEnd); - }; - - Cursor.prototype.equals = function (other) { - return this.position === other.position && - this.selectionEnd === other.selectionEnd; - }; - - // Return the more current cursor information. - Cursor.prototype.compose = function (other) { - return other; - }; - - // Update the cursor with respect to an operation. - Cursor.prototype.transform = function (other) { - function transformIndex (index) { - var newIndex = index; - var ops = other.ops; - for (var i = 0, l = other.ops.length; i < l; i++) { - if (ops[i].isRetain()) { - index -= ops[i].chars; - } else if (ops[i].isInsert()) { - newIndex += ops[i].text.length; - } else { - newIndex -= Math.min(index, ops[i].chars); - index -= ops[i].chars; - } - if (index < 0) { break; } - } - return newIndex; - } - - var newPosition = transformIndex(this.position); - if (this.position === this.selectionEnd) { - return new Cursor(newPosition, newPosition); - } - return new Cursor(newPosition, transformIndex(this.selectionEnd)); - }; - - return Cursor; - -}()); - - -var firepad = firepad || { }; - -firepad.FirebaseAdapter = (function (global) { - - if (typeof firebase === "undefined" && typeof require === 'function' && typeof Firebase !== 'function') { - firebase = require('firebase'); - } - - var TextOperation = firepad.TextOperation; - var utils = firepad.utils; - - // Save a checkpoint every 100 edits. - var CHECKPOINT_FREQUENCY = 100; - - function FirebaseAdapter (ref, userId, userColor) { - this.ref_ = ref; - this.ready_ = false; - this.firebaseCallbacks_ = []; - this.zombie_ = false; - - // We store the current document state as a TextOperation so we can write checkpoints to Firebase occasionally. - // TODO: Consider more efficient ways to do this. (composing text operations is ~linear in the length of the document). - this.document_ = new TextOperation(); - - // The next expected revision. - this.revision_ = 0; - - // This is used for two purposes: - // 1) On initialization, we fill this with the latest checkpoint and any subsequent operations and then - // process them all together. - // 2) If we ever receive revisions out-of-order (e.g. rev 5 before rev 4), we queue them here until it's time - // for them to be handled. [this should never happen with well-behaved clients; but if it /does/ happen we want - // to handle it gracefully.] - this.pendingReceivedRevisions_ = { }; - - var self = this; - - if (userId) { - this.setUserId(userId); - this.setColor(userColor); - - var connectedRef = ref.root.child('.info/connected') - - this.firebaseOn_(connectedRef, 'value', function(snapshot) { - if (snapshot.val() === true) { - self.initializeUserData_(); - } - }, this); - - // Once we're initialized, start tracking users' cursors. - this.on('ready', function() { - self.monitorCursors_(); - }); - } else { - this.userId_ = ref.push().key; - } - - // Avoid triggering any events until our callers have had a chance to attach their listeners. - setTimeout(function() { - self.monitorHistory_(); - }, 0); - - } - utils.makeEventEmitter(FirebaseAdapter, ['ready', 'cursor', 'operation', 'ack', 'retry']); - - FirebaseAdapter.prototype.dispose = function() { - var self = this; - - if (!this.ready_) { - // TODO: this completes loading the text even though we're no longer interested in it. - this.on('ready', function() { - self.dispose(); - }); - return; - } - - this.removeFirebaseCallbacks_(); - - if (this.userRef_) { - this.userRef_.child('cursor').remove(); - this.userRef_.child('color').remove(); - } - - this.ref_ = null; - this.document_ = null; - this.zombie_ = true; - }; - - FirebaseAdapter.prototype.setUserId = function(userId) { - if (this.userRef_) { - // Clean up existing data. Avoid nuking another user's data - // (if a future user takes our old name). - this.userRef_.child('cursor').remove(); - this.userRef_.child('cursor').onDisconnect().cancel(); - this.userRef_.child('color').remove(); - this.userRef_.child('color').onDisconnect().cancel(); - } - - this.userId_ = userId; - this.userRef_ = this.ref_.child('users').child(userId); - - this.initializeUserData_(); - }; - - FirebaseAdapter.prototype.isHistoryEmpty = function() { - assert(this.ready_, "Not ready yet."); - return this.revision_ === 0; - }; - - /* - * Send operation, retrying on connection failure. Takes an optional callback with signature: - * function(error, committed). - * An exception will be thrown on transaction failure, which should only happen on - * catastrophic failure like a security rule violation. - */ - FirebaseAdapter.prototype.sendOperation = function (operation, callback) { - var self = this; - - // If we're not ready yet, do nothing right now, and trigger a retry when we're ready. - if (!this.ready_) { - this.on('ready', function() { - self.trigger('retry'); - }); - return; - } - - // Sanity check that this operation is valid. - assert(this.document_.targetLength === operation.baseLength, "sendOperation() called with invalid operation."); - - // Convert revision into an id that will sort properly lexicographically. - var revisionId = revisionToId(this.revision_); - - function doTransaction(revisionId, revisionData) { - - self.ref_.child('history').child(revisionId).transaction(function(current) { - if (current === null) { - return revisionData; - } - }, function(error, committed, snapshot) { - if (error) { - if (error.message === 'disconnect') { - if (self.sent_ && self.sent_.id === revisionId) { - // We haven't seen our transaction succeed or fail. Send it again. - setTimeout(function() { - doTransaction(revisionId, revisionData); - }, 0); - } else if (callback) { - callback(error, false); - } - } else { - utils.log('Transaction failure!', error); - throw error; - } - } else { - if (callback) callback(null, committed); - } - }, /*applyLocally=*/false); - } - - this.sent_ = { id: revisionId, op: operation }; - doTransaction(revisionId, { a: self.userId_, o: operation.toJSON(), t: firebase.database.ServerValue.TIMESTAMP }); - }; - - FirebaseAdapter.prototype.sendCursor = function (obj) { - this.userRef_.child('cursor').set(obj); - this.cursor_ = obj; - }; - - FirebaseAdapter.prototype.setColor = function(color) { - this.userRef_.child('color').set(color); - this.color_ = color; - }; - - FirebaseAdapter.prototype.getDocument = function() { - return this.document_; - }; - - FirebaseAdapter.prototype.registerCallbacks = function(callbacks) { - for (var eventType in callbacks) { - this.on(eventType, callbacks[eventType]); - } - }; - - FirebaseAdapter.prototype.initializeUserData_ = function() { - this.userRef_.child('cursor').onDisconnect().remove(); - this.userRef_.child('color').onDisconnect().remove(); - - this.sendCursor(this.cursor_ || null); - this.setColor(this.color_ || null); - }; - - FirebaseAdapter.prototype.monitorCursors_ = function() { - var usersRef = this.ref_.child('users'), self = this; - - function childChanged(childSnap) { - var userId = childSnap.key; - var userData = childSnap.val(); - self.trigger('cursor', userId, userData.cursor, userData.color); - } - - this.firebaseOn_(usersRef, 'child_added', childChanged); - this.firebaseOn_(usersRef, 'child_changed', childChanged); - - this.firebaseOn_(usersRef, 'child_removed', function(childSnap) { - var userId = childSnap.key; - self.trigger('cursor', userId, null); - }); - }; - - FirebaseAdapter.prototype.monitorHistory_ = function() { - var self = this; - // Get the latest checkpoint as a starting point so we don't have to re-play entire history. - this.ref_.child('checkpoint').once('value', function(s) { - if (self.zombie_) { return; } // just in case we were cleaned up before we got the checkpoint data. - var revisionId = s.child('id').val(), op = s.child('o').val(), author = s.child('a').val(); - if (op != null && revisionId != null && author !== null) { - self.pendingReceivedRevisions_[revisionId] = { o: op, a: author }; - self.checkpointRevision_ = revisionFromId(revisionId); - self.monitorHistoryStartingAt_(self.checkpointRevision_ + 1); - } else { - self.checkpointRevision_ = 0; - self.monitorHistoryStartingAt_(self.checkpointRevision_); - } - }); - }; - - FirebaseAdapter.prototype.monitorHistoryStartingAt_ = function(revision) { - var historyRef = this.ref_.child('history').startAt(null, revisionToId(revision)); - var self = this; - - setTimeout(function() { - self.firebaseOn_(historyRef, 'child_added', function(revisionSnapshot) { - var revisionId = revisionSnapshot.key; - self.pendingReceivedRevisions_[revisionId] = revisionSnapshot.val(); - if (self.ready_) { - self.handlePendingReceivedRevisions_(); - } - }); - - historyRef.once('value', function() { - self.handleInitialRevisions_(); - }); - }, 0); - }; - - FirebaseAdapter.prototype.handleInitialRevisions_ = function() { - assert(!this.ready_, "Should not be called multiple times."); - - // Compose the checkpoint and all subsequent revisions into a single operation to apply at once. - this.revision_ = this.checkpointRevision_; - var revisionId = revisionToId(this.revision_), pending = this.pendingReceivedRevisions_; - while (pending[revisionId] != null) { - var revision = this.parseRevision_(pending[revisionId]); - if (!revision) { - // If a misbehaved client adds a bad operation, just ignore it. - utils.log('Invalid operation.', this.ref_.toString(), revisionId, pending[revisionId]); - } else { - this.document_ = this.document_.compose(revision.operation); - } - - delete pending[revisionId]; - this.revision_++; - revisionId = revisionToId(this.revision_); - } - - this.trigger('operation', this.document_); - - this.ready_ = true; - var self = this; - setTimeout(function() { - self.trigger('ready'); - }, 0); - }; - - FirebaseAdapter.prototype.handlePendingReceivedRevisions_ = function() { - var pending = this.pendingReceivedRevisions_; - var revisionId = revisionToId(this.revision_); - var triggerRetry = false; - while (pending[revisionId] != null) { - this.revision_++; - - var revision = this.parseRevision_(pending[revisionId]); - if (!revision) { - // If a misbehaved client adds a bad operation, just ignore it. - utils.log('Invalid operation.', this.ref_.toString(), revisionId, pending[revisionId]); - } else { - this.document_ = this.document_.compose(revision.operation); - if (this.sent_ && revisionId === this.sent_.id) { - // We have an outstanding change at this revision id. - if (this.sent_.op.equals(revision.operation) && revision.author === this.userId_) { - // This is our change; it succeeded. - if (this.revision_ % CHECKPOINT_FREQUENCY === 0) { - this.saveCheckpoint_(); - } - this.sent_ = null; - this.trigger('ack'); - } else { - // our op failed. Trigger a retry after we're done catching up on any incoming ops. - triggerRetry = true; - this.trigger('operation', revision.operation); - } - } else { - this.trigger('operation', revision.operation); - } - } - delete pending[revisionId]; - - revisionId = revisionToId(this.revision_); - } - - if (triggerRetry) { - this.sent_ = null; - this.trigger('retry'); - } - }; - - FirebaseAdapter.prototype.parseRevision_ = function(data) { - // We could do some of this validation via security rules. But it's nice to be robust, just in case. - if (typeof data !== 'object') { return null; } - if (typeof data.a !== 'string' || typeof data.o !== 'object') { return null; } - var op = null; - try { - op = TextOperation.fromJSON(data.o); - } - catch (e) { - return null; - } - - if (op.baseLength !== this.document_.targetLength) { - return null; - } - return { author: data.a, operation: op } - }; - - FirebaseAdapter.prototype.saveCheckpoint_ = function() { - this.ref_.child('checkpoint').set({ - a: this.userId_, - o: this.document_.toJSON(), - id: revisionToId(this.revision_ - 1) // use the id for the revision we just wrote. - }); - }; - - FirebaseAdapter.prototype.firebaseOn_ = function(ref, eventType, callback, context) { - this.firebaseCallbacks_.push({ref: ref, eventType: eventType, callback: callback, context: context }); - ref.on(eventType, callback, context); - return callback; - }; - - FirebaseAdapter.prototype.firebaseOff_ = function(ref, eventType, callback, context) { - ref.off(eventType, callback, context); - for(var i = 0; i < this.firebaseCallbacks_.length; i++) { - var l = this.firebaseCallbacks_[i]; - if (l.ref === ref && l.eventType === eventType && l.callback === callback && l.context === context) { - this.firebaseCallbacks_.splice(i, 1); - break; - } - } - }; - - FirebaseAdapter.prototype.removeFirebaseCallbacks_ = function() { - for(var i = 0; i < this.firebaseCallbacks_.length; i++) { - var l = this.firebaseCallbacks_[i]; - l.ref.off(l.eventType, l.callback, l.context); - } - this.firebaseCallbacks_ = []; - }; - - // Throws an error if the first argument is falsy. Useful for debugging. - function assert (b, msg) { - if (!b) { - throw new Error(msg || "assertion error"); - } - } - - // Based off ideas from http://www.zanopha.com/docs/elen.pdf - var characters = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; - function revisionToId(revision) { - if (revision === 0) { - return 'A0'; - } - - var str = ''; - while (revision > 0) { - var digit = (revision % characters.length); - str = characters[digit] + str; - revision -= digit; - revision /= characters.length; - } - - // Prefix with length (starting at 'A' for length 1) to ensure the id's sort lexicographically. - var prefix = characters[str.length + 9]; - return prefix + str; - } - - function revisionFromId(revisionId) { - assert (revisionId.length > 0 && revisionId[0] === characters[revisionId.length + 8]); - var revision = 0; - for(var i = 1; i < revisionId.length; i++) { - revision *= characters.length; - revision += characters.indexOf(revisionId[i]); - } - return revision; - } - - return FirebaseAdapter; -}()); - -var firepad = firepad || { }; - -firepad.RichTextToolbar = (function(global) { - var utils = firepad.utils; - - function RichTextToolbar(imageInsertionUI) { - this.imageInsertionUI = imageInsertionUI; - this.element_ = this.makeElement_(); - } - - utils.makeEventEmitter(RichTextToolbar, ['bold', 'italic', 'underline', 'strike', 'font', 'font-size', 'color', - 'left', 'center', 'right', 'unordered-list', 'ordered-list', 'todo-list', 'indent-increase', 'indent-decrease', - 'undo', 'redo', 'insert-image']); - - RichTextToolbar.prototype.element = function() { return this.element_; }; - - RichTextToolbar.prototype.makeButton_ = function(eventName, iconName) { - var self = this; - iconName = iconName || eventName; - var btn = utils.elt('a', [utils.elt('span', '', { 'class': 'firepad-tb-' + iconName } )], { 'class': 'firepad-btn' }); - utils.on(btn, 'click', utils.stopEventAnd(function() { self.trigger(eventName); })); - return btn; - } - - RichTextToolbar.prototype.makeElement_ = function() { - var self = this; - - var font = this.makeFontDropdown_(); - var fontSize = this.makeFontSizeDropdown_(); - var color = this.makeColorDropdown_(); - - var toolbarOptions = [ - utils.elt('div', [font], { 'class': 'firepad-btn-group'}), - utils.elt('div', [fontSize], { 'class': 'firepad-btn-group'}), - utils.elt('div', [color], { 'class': 'firepad-btn-group'}), - utils.elt('div', [self.makeButton_('bold'), self.makeButton_('italic'), self.makeButton_('underline'), self.makeButton_('strike', 'strikethrough')], { 'class': 'firepad-btn-group'}), - utils.elt('div', [self.makeButton_('unordered-list', 'list-2'), self.makeButton_('ordered-list', 'numbered-list'), self.makeButton_('todo-list', 'list')], { 'class': 'firepad-btn-group'}), - utils.elt('div', [self.makeButton_('indent-decrease'), self.makeButton_('indent-increase')], { 'class': 'firepad-btn-group'}), - utils.elt('div', [self.makeButton_('left', 'paragraph-left'), self.makeButton_('center', 'paragraph-center'), self.makeButton_('right', 'paragraph-right')], { 'class': 'firepad-btn-group'}), - utils.elt('div', [self.makeButton_('undo'), self.makeButton_('redo')], { 'class': 'firepad-btn-group'}) - ]; - - if (self.imageInsertionUI) { - toolbarOptions.push(utils.elt('div', [self.makeButton_('insert-image')], { 'class': 'firepad-btn-group' })); - } - - var toolbarWrapper = utils.elt('div', toolbarOptions, { 'class': 'firepad-toolbar-wrapper' }); - var toolbar = utils.elt('div', null, { 'class': 'firepad-toolbar' }); - toolbar.appendChild(toolbarWrapper) - - return toolbar; - }; - - RichTextToolbar.prototype.makeFontDropdown_ = function() { - // NOTE: There must be matching .css styles in firepad.css. - var fonts = ['Arial', 'Comic Sans MS', 'Courier New', 'Impact', 'Times New Roman', 'Verdana']; - - var items = []; - for(var i = 0; i < fonts.length; i++) { - var content = utils.elt('span', fonts[i]); - content.setAttribute('style', 'font-family:' + fonts[i]); - items.push({ content: content, value: fonts[i] }); - } - return this.makeDropdown_('Font', 'font', items); - }; - - RichTextToolbar.prototype.makeFontSizeDropdown_ = function() { - // NOTE: There must be matching .css styles in firepad.css. - var sizes = [9, 10, 12, 14, 18, 24, 32, 42]; - - var items = []; - for(var i = 0; i < sizes.length; i++) { - var content = utils.elt('span', sizes[i].toString()); - content.setAttribute('style', 'font-size:' + sizes[i] + 'px; line-height:' + (sizes[i]-6) + 'px;'); - items.push({ content: content, value: sizes[i] }); - } - return this.makeDropdown_('Size', 'font-size', items, 'px'); - }; - - RichTextToolbar.prototype.makeColorDropdown_ = function() { - var colors = ['black', 'red', 'green', 'blue', 'yellow', 'cyan', 'magenta', 'grey']; - - var items = []; - for(var i = 0; i < colors.length; i++) { - var content = utils.elt('div'); - content.className = 'firepad-color-dropdown-item'; - content.setAttribute('style', 'background-color:' + colors[i]); - items.push({ content: content, value: colors[i] }); - } - return this.makeDropdown_('Color', 'color', items); - }; - - RichTextToolbar.prototype.makeDropdown_ = function(title, eventName, items, value_suffix) { - value_suffix = value_suffix || ""; - var self = this; - var button = utils.elt('a', title + ' \u25be', { 'class': 'firepad-btn firepad-dropdown' }); - var list = utils.elt('ul', [ ], { 'class': 'firepad-dropdown-menu' }); - button.appendChild(list); - - var isShown = false; - function showDropdown() { - if (!isShown) { - list.style.display = 'block'; - utils.on(document, 'click', hideDropdown, /*capture=*/true); - isShown = true; - } - } - - var justDismissed = false; - function hideDropdown() { - if (isShown) { - list.style.display = ''; - utils.off(document, 'click', hideDropdown, /*capture=*/true); - isShown = false; - } - // HACK so we can avoid re-showing the dropdown if you click on the dropdown header to dismiss it. - justDismissed = true; - setTimeout(function() { justDismissed = false; }, 0); - } - - function addItem(content, value) { - if (typeof content !== 'object') { - content = document.createTextNode(String(content)); - } - var element = utils.elt('a', [content]); - - utils.on(element, 'click', utils.stopEventAnd(function() { - hideDropdown(); - self.trigger(eventName, value + value_suffix); - })); - - list.appendChild(element); - } - - for(var i = 0; i < items.length; i++) { - var content = items[i].content, value = items[i].value; - addItem(content, value); - } - - utils.on(button, 'click', utils.stopEventAnd(function() { - if (!justDismissed) { - showDropdown(); - } - })); - - return button; - }; - - return RichTextToolbar; -})(); - -var firepad = firepad || { }; -firepad.WrappedOperation = (function (global) { - 'use strict'; - - // A WrappedOperation contains an operation and corresponing metadata. - function WrappedOperation (operation, meta) { - this.wrapped = operation; - this.meta = meta; - } - - WrappedOperation.prototype.apply = function () { - return this.wrapped.apply.apply(this.wrapped, arguments); - }; - - WrappedOperation.prototype.invert = function () { - var meta = this.meta; - return new WrappedOperation( - this.wrapped.invert.apply(this.wrapped, arguments), - meta && typeof meta === 'object' && typeof meta.invert === 'function' ? - meta.invert.apply(meta, arguments) : meta - ); - }; - - // Copy all properties from source to target. - function copy (source, target) { - for (var key in source) { - if (source.hasOwnProperty(key)) { - target[key] = source[key]; - } - } - } - - function composeMeta (a, b) { - if (a && typeof a === 'object') { - if (typeof a.compose === 'function') { return a.compose(b); } - var meta = {}; - copy(a, meta); - copy(b, meta); - return meta; - } - return b; - } - - WrappedOperation.prototype.compose = function (other) { - return new WrappedOperation( - this.wrapped.compose(other.wrapped), - composeMeta(this.meta, other.meta) - ); - }; - - function transformMeta (meta, operation) { - if (meta && typeof meta === 'object') { - if (typeof meta.transform === 'function') { - return meta.transform(operation); - } - } - return meta; - } - - WrappedOperation.transform = function (a, b) { - var pair = a.wrapped.transform(b.wrapped); - return [ - new WrappedOperation(pair[0], transformMeta(a.meta, b.wrapped)), - new WrappedOperation(pair[1], transformMeta(b.meta, a.wrapped)) - ]; - }; - - // convenience method to write transform(a, b) as a.transform(b) - WrappedOperation.prototype.transform = function(other) { - return WrappedOperation.transform(this, other); - }; - - return WrappedOperation; - -}()); - -var firepad = firepad || { }; - -firepad.UndoManager = (function () { - 'use strict'; - - var NORMAL_STATE = 'normal'; - var UNDOING_STATE = 'undoing'; - var REDOING_STATE = 'redoing'; - - // Create a new UndoManager with an optional maximum history size. - function UndoManager (maxItems) { - this.maxItems = maxItems || 50; - this.state = NORMAL_STATE; - this.dontCompose = false; - this.undoStack = []; - this.redoStack = []; - } - - // Add an operation to the undo or redo stack, depending on the current state - // of the UndoManager. The operation added must be the inverse of the last - // edit. When `compose` is true, compose the operation with the last operation - // unless the last operation was alread pushed on the redo stack or was hidden - // by a newer operation on the undo stack. - UndoManager.prototype.add = function (operation, compose) { - if (this.state === UNDOING_STATE) { - this.redoStack.push(operation); - this.dontCompose = true; - } else if (this.state === REDOING_STATE) { - this.undoStack.push(operation); - this.dontCompose = true; - } else { - var undoStack = this.undoStack; - if (!this.dontCompose && compose && undoStack.length > 0) { - undoStack.push(operation.compose(undoStack.pop())); - } else { - undoStack.push(operation); - if (undoStack.length > this.maxItems) { undoStack.shift(); } - } - this.dontCompose = false; - this.redoStack = []; - } - }; - - function transformStack (stack, operation) { - var newStack = []; - var Operation = operation.constructor; - for (var i = stack.length - 1; i >= 0; i--) { - var pair = Operation.transform(stack[i], operation); - if (typeof pair[0].isNoop !== 'function' || !pair[0].isNoop()) { - newStack.push(pair[0]); - } - operation = pair[1]; - } - return newStack.reverse(); - } - - // Transform the undo and redo stacks against a operation by another client. - UndoManager.prototype.transform = function (operation) { - this.undoStack = transformStack(this.undoStack, operation); - this.redoStack = transformStack(this.redoStack, operation); - }; - - // Perform an undo by calling a function with the latest operation on the undo - // stack. The function is expected to call the `add` method with the inverse - // of the operation, which pushes the inverse on the redo stack. - UndoManager.prototype.performUndo = function (fn) { - this.state = UNDOING_STATE; - if (this.undoStack.length === 0) { throw new Error("undo not possible"); } - fn(this.undoStack.pop()); - this.state = NORMAL_STATE; - }; - - // The inverse of `performUndo`. - UndoManager.prototype.performRedo = function (fn) { - this.state = REDOING_STATE; - if (this.redoStack.length === 0) { throw new Error("redo not possible"); } - fn(this.redoStack.pop()); - this.state = NORMAL_STATE; - }; - - // Is the undo stack not empty? - UndoManager.prototype.canUndo = function () { - return this.undoStack.length !== 0; - }; - - // Is the redo stack not empty? - UndoManager.prototype.canRedo = function () { - return this.redoStack.length !== 0; - }; - - // Whether the UndoManager is currently performing an undo. - UndoManager.prototype.isUndoing = function () { - return this.state === UNDOING_STATE; - }; - - // Whether the UndoManager is currently performing a redo. - UndoManager.prototype.isRedoing = function () { - return this.state === REDOING_STATE; - }; - - return UndoManager; - -}()); - -var firepad = firepad || { }; -firepad.Client = (function () { - 'use strict'; - - // Client constructor - function Client () { - this.state = synchronized_; // start state - } - - Client.prototype.setState = function (state) { - this.state = state; - }; - - // Call this method when the user changes the document. - Client.prototype.applyClient = function (operation) { - this.setState(this.state.applyClient(this, operation)); - }; - - // Call this method with a new operation from the server - Client.prototype.applyServer = function (operation) { - this.setState(this.state.applyServer(this, operation)); - }; - - Client.prototype.serverAck = function () { - this.setState(this.state.serverAck(this)); - }; - - Client.prototype.serverRetry = function() { - this.setState(this.state.serverRetry(this)); - }; - - // Override this method. - Client.prototype.sendOperation = function (operation) { - throw new Error("sendOperation must be defined in child class"); - }; - - // Override this method. - Client.prototype.applyOperation = function (operation) { - throw new Error("applyOperation must be defined in child class"); - }; - - - // In the 'Synchronized' state, there is no pending operation that the client - // has sent to the server. - function Synchronized () {} - Client.Synchronized = Synchronized; - - Synchronized.prototype.applyClient = function (client, operation) { - // When the user makes an edit, send the operation to the server and - // switch to the 'AwaitingConfirm' state - client.sendOperation(operation); - return new AwaitingConfirm(operation); - }; - - Synchronized.prototype.applyServer = function (client, operation) { - // When we receive a new operation from the server, the operation can be - // simply applied to the current document - client.applyOperation(operation); - return this; - }; - - Synchronized.prototype.serverAck = function (client) { - throw new Error("There is no pending operation."); - }; - - Synchronized.prototype.serverRetry = function(client) { - throw new Error("There is no pending operation."); - }; - - // Singleton - var synchronized_ = new Synchronized(); - - - // In the 'AwaitingConfirm' state, there's one operation the client has sent - // to the server and is still waiting for an acknowledgement. - function AwaitingConfirm (outstanding) { - // Save the pending operation - this.outstanding = outstanding; - } - Client.AwaitingConfirm = AwaitingConfirm; - - AwaitingConfirm.prototype.applyClient = function (client, operation) { - // When the user makes an edit, don't send the operation immediately, - // instead switch to 'AwaitingWithBuffer' state - return new AwaitingWithBuffer(this.outstanding, operation); - }; - - AwaitingConfirm.prototype.applyServer = function (client, operation) { - // This is another client's operation. Visualization: - // - // /\ - // this.outstanding / \ operation - // / \ - // \ / - // pair[1] \ / pair[0] (new outstanding) - // (can be applied \/ - // to the client's - // current document) - var pair = this.outstanding.transform(operation); - client.applyOperation(pair[1]); - return new AwaitingConfirm(pair[0]); - }; - - AwaitingConfirm.prototype.serverAck = function (client) { - // The client's operation has been acknowledged - // => switch to synchronized state - return synchronized_; - }; - - AwaitingConfirm.prototype.serverRetry = function (client) { - client.sendOperation(this.outstanding); - return this; - }; - - // In the 'AwaitingWithBuffer' state, the client is waiting for an operation - // to be acknowledged by the server while buffering the edits the user makes - function AwaitingWithBuffer (outstanding, buffer) { - // Save the pending operation and the user's edits since then - this.outstanding = outstanding; - this.buffer = buffer; - } - Client.AwaitingWithBuffer = AwaitingWithBuffer; - - AwaitingWithBuffer.prototype.applyClient = function (client, operation) { - // Compose the user's changes onto the buffer - var newBuffer = this.buffer.compose(operation); - return new AwaitingWithBuffer(this.outstanding, newBuffer); - }; - - AwaitingWithBuffer.prototype.applyServer = function (client, operation) { - // Operation comes from another client - // - // /\ - // this.outstanding / \ operation - // / \ - // /\ / - // this.buffer / \* / pair1[0] (new outstanding) - // / \/ - // \ / - // pair2[1] \ / pair2[0] (new buffer) - // the transformed \/ - // operation -- can - // be applied to the - // client's current - // document - // - // * pair1[1] - var pair1 = this.outstanding.transform(operation); - var pair2 = this.buffer.transform(pair1[1]); - client.applyOperation(pair2[1]); - return new AwaitingWithBuffer(pair1[0], pair2[0]); - }; - - AwaitingWithBuffer.prototype.serverRetry = function (client) { - // Merge with our buffer and resend. - var outstanding = this.outstanding.compose(this.buffer); - client.sendOperation(outstanding); - return new AwaitingConfirm(outstanding); - }; - - AwaitingWithBuffer.prototype.serverAck = function (client) { - // The pending operation has been acknowledged - // => send buffer - client.sendOperation(this.buffer); - return new AwaitingConfirm(this.buffer); - }; - - return Client; - -}()); - -var firepad = firepad || { }; - -firepad.EditorClient = (function () { - 'use strict'; - - var Client = firepad.Client; - var Cursor = firepad.Cursor; - var UndoManager = firepad.UndoManager; - var WrappedOperation = firepad.WrappedOperation; - - function SelfMeta (cursorBefore, cursorAfter) { - this.cursorBefore = cursorBefore; - this.cursorAfter = cursorAfter; - } - - SelfMeta.prototype.invert = function () { - return new SelfMeta(this.cursorAfter, this.cursorBefore); - }; - - SelfMeta.prototype.compose = function (other) { - return new SelfMeta(this.cursorBefore, other.cursorAfter); - }; - - SelfMeta.prototype.transform = function (operation) { - return new SelfMeta( - this.cursorBefore ? this.cursorBefore.transform(operation) : null, - this.cursorAfter ? this.cursorAfter.transform(operation) : null - ); - }; - - function OtherClient (id, editorAdapter) { - this.id = id; - this.editorAdapter = editorAdapter; - } - - OtherClient.prototype.setColor = function (color) { - this.color = color; - }; - - OtherClient.prototype.updateCursor = function (cursor) { - this.removeCursor(); - this.cursor = cursor; - this.mark = this.editorAdapter.setOtherCursor( - cursor, - this.color, - this.id - ); - }; - - OtherClient.prototype.removeCursor = function () { - if (this.mark) { this.mark.clear(); } - }; - - function EditorClient (serverAdapter, editorAdapter) { - Client.call(this); - this.serverAdapter = serverAdapter; - this.editorAdapter = editorAdapter; - this.undoManager = new UndoManager(); - - this.clients = { }; - - var self = this; - - this.editorAdapter.registerCallbacks({ - change: function (operation, inverse) { self.onChange(operation, inverse); }, - cursorActivity: function () { self.onCursorActivity(); }, - blur: function () { self.onBlur(); }, - focus: function () { self.onFocus(); } - }); - this.editorAdapter.registerUndo(function () { self.undo(); }); - this.editorAdapter.registerRedo(function () { self.redo(); }); - - this.serverAdapter.registerCallbacks({ - ack: function () { - self.serverAck(); - if (self.focused && self.state instanceof Client.Synchronized) { - self.updateCursor(); - self.sendCursor(self.cursor); - } - self.emitStatus(); - }, - retry: function() { self.serverRetry(); }, - operation: function (operation) { - self.applyServer(operation); - }, - cursor: function (clientId, cursor, color) { - if (self.serverAdapter.userId_ === clientId || - !(self.state instanceof Client.Synchronized)) { - return; - } - var client = self.getClientObject(clientId); - if (cursor) { - if (color) client.setColor(color); - client.updateCursor(Cursor.fromJSON(cursor)); - } else { - client.removeCursor(); - } - } - }); - } - - inherit(EditorClient, Client); - - EditorClient.prototype.getClientObject = function (clientId) { - var client = this.clients[clientId]; - if (client) { return client; } - return this.clients[clientId] = new OtherClient( - clientId, - this.editorAdapter - ); - }; - - EditorClient.prototype.applyUnredo = function (operation) { - this.undoManager.add(this.editorAdapter.invertOperation(operation)); - this.editorAdapter.applyOperation(operation.wrapped); - this.cursor = operation.meta.cursorAfter; - if (this.cursor) - this.editorAdapter.setCursor(this.cursor); - this.applyClient(operation.wrapped); - }; - - EditorClient.prototype.undo = function () { - var self = this; - if (!this.undoManager.canUndo()) { return; } - this.undoManager.performUndo(function (o) { self.applyUnredo(o); }); - }; - - EditorClient.prototype.redo = function () { - var self = this; - if (!this.undoManager.canRedo()) { return; } - this.undoManager.performRedo(function (o) { self.applyUnredo(o); }); - }; - - EditorClient.prototype.onChange = function (textOperation, inverse) { - var cursorBefore = this.cursor; - this.updateCursor(); - - var compose = this.undoManager.undoStack.length > 0 && - inverse.shouldBeComposedWithInverted(last(this.undoManager.undoStack).wrapped); - var inverseMeta = new SelfMeta(this.cursor, cursorBefore); - this.undoManager.add(new WrappedOperation(inverse, inverseMeta), compose); - this.applyClient(textOperation); - }; - - EditorClient.prototype.updateCursor = function () { - this.cursor = this.editorAdapter.getCursor(); - }; - - EditorClient.prototype.onCursorActivity = function () { - var oldCursor = this.cursor; - this.updateCursor(); - if (!this.focused || oldCursor && this.cursor.equals(oldCursor)) { return; } - this.sendCursor(this.cursor); - }; - - EditorClient.prototype.onBlur = function () { - this.cursor = null; - this.sendCursor(null); - this.focused = false; - }; - - EditorClient.prototype.onFocus = function () { - this.focused = true; - this.onCursorActivity(); - }; - - EditorClient.prototype.sendCursor = function (cursor) { - if (this.state instanceof Client.AwaitingWithBuffer) { return; } - this.serverAdapter.sendCursor(cursor); - }; - - EditorClient.prototype.sendOperation = function (operation) { - this.serverAdapter.sendOperation(operation); - this.emitStatus(); - }; - - EditorClient.prototype.applyOperation = function (operation) { - this.editorAdapter.applyOperation(operation); - this.updateCursor(); - this.undoManager.transform(new WrappedOperation(operation, null)); - }; - - EditorClient.prototype.emitStatus = function() { - var self = this; - setTimeout(function() { - self.trigger('synced', self.state instanceof Client.Synchronized); - }, 0); - }; - - // Set Const.prototype.__proto__ to Super.prototype - function inherit (Const, Super) { - function F () {} - F.prototype = Super.prototype; - Const.prototype = new F(); - Const.prototype.constructor = Const; - } - - function last (arr) { return arr[arr.length - 1]; } - - return EditorClient; -}()); - -firepad.utils.makeEventEmitter(firepad.EditorClient, ['synced']); - -var firepad, - __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, - __slice = [].slice; - -if (typeof firepad === "undefined" || firepad === null) { - firepad = {}; -} - -firepad.ACEAdapter = (function() { - ACEAdapter.prototype.ignoreChanges = false; - - function ACEAdapter(aceInstance) { - this.onCursorActivity = __bind(this.onCursorActivity, this); - this.onFocus = __bind(this.onFocus, this); - this.onBlur = __bind(this.onBlur, this); - this.onChange = __bind(this.onChange, this); - var _ref; - this.ace = aceInstance; - this.aceSession = this.ace.getSession(); - this.aceDoc = this.aceSession.getDocument(); - this.aceDoc.setNewLineMode('unix'); - this.grabDocumentState(); - this.ace.on('change', this.onChange); - this.ace.on('blur', this.onBlur); - this.ace.on('focus', this.onFocus); - this.aceSession.selection.on('changeCursor', this.onCursorActivity); - if (this.aceRange == null) { - this.aceRange = ((_ref = ace.require) != null ? _ref : require)("ace/range").Range; - } - } - - ACEAdapter.prototype.grabDocumentState = function() { - this.lastDocLines = this.aceDoc.getAllLines(); - return this.lastCursorRange = this.aceSession.selection.getRange(); - }; - - ACEAdapter.prototype.detach = function() { - this.ace.removeListener('change', this.onChange); - this.ace.removeListener('blur', this.onBlur); - this.ace.removeListener('focus', this.onFocus); - return this.aceSession.selection.removeListener('changeCursor', this.onCursorActivity); - }; - - ACEAdapter.prototype.onChange = function(change) { - var pair; - if (!this.ignoreChanges) { - pair = this.operationFromACEChange(change); - this.trigger.apply(this, ['change'].concat(__slice.call(pair))); - return this.grabDocumentState(); - } - }; - - ACEAdapter.prototype.onBlur = function() { - if (this.ace.selection.isEmpty()) { - return this.trigger('blur'); - } - }; - - ACEAdapter.prototype.onFocus = function() { - return this.trigger('focus'); - }; - - ACEAdapter.prototype.onCursorActivity = function() { - var _this = this; - return setTimeout(function() { - return _this.trigger('cursorActivity'); - }, 0); - }; - - ACEAdapter.prototype.operationFromACEChange = function(change) { - var action, delete_op, delta, insert_op, restLength, start, text, _ref; - if (change.data) { - delta = change.data; - if ((_ref = delta.action) === 'insertLines' || _ref === 'removeLines') { - text = delta.lines.join('\n') + '\n'; - action = delta.action.replace('Lines', ''); - } else { - text = delta.text.replace(this.aceDoc.getNewLineCharacter(), '\n'); - action = delta.action.replace('Text', ''); - } - start = this.indexFromPos(delta.range.start); - } else { - text = change.lines.join('\n'); - start = this.indexFromPos(change.start); - } - restLength = this.lastDocLines.join('\n').length - start; - if (change.action === 'remove') { - restLength -= text.length; - } - insert_op = new firepad.TextOperation().retain(start).insert(text).retain(restLength); - delete_op = new firepad.TextOperation().retain(start)["delete"](text).retain(restLength); - if (change.action === 'remove') { - return [delete_op, insert_op]; - } else { - return [insert_op, delete_op]; - } - }; - - ACEAdapter.prototype.applyOperationToACE = function(operation) { - var from, index, op, range, to, _i, _len, _ref; - index = 0; - _ref = operation.ops; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - op = _ref[_i]; - if (op.isRetain()) { - index += op.chars; - } else if (op.isInsert()) { - this.aceDoc.insert(this.posFromIndex(index), op.text); - index += op.text.length; - } else if (op.isDelete()) { - from = this.posFromIndex(index); - to = this.posFromIndex(index + op.chars); - range = this.aceRange.fromPoints(from, to); - this.aceDoc.remove(range); - } - } - return this.grabDocumentState(); - }; - - ACEAdapter.prototype.posFromIndex = function(index) { - var line, row, _i, _len, _ref; - _ref = this.aceDoc.$lines; - for (row = _i = 0, _len = _ref.length; _i < _len; row = ++_i) { - line = _ref[row]; - if (index <= line.length) { - break; - } - index -= line.length + 1; - } - return { - row: row, - column: index - }; - }; - - ACEAdapter.prototype.indexFromPos = function(pos, lines) { - var i, index, _i, _ref; - if (lines == null) { - lines = this.lastDocLines; - } - index = 0; - for (i = _i = 0, _ref = pos.row; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) { - index += this.lastDocLines[i].length + 1; - } - return index += pos.column; - }; - - ACEAdapter.prototype.getValue = function() { - return this.aceDoc.getValue(); - }; - - ACEAdapter.prototype.getCursor = function() { - var e, e2, end, start, _ref, _ref1; - try { - start = this.indexFromPos(this.aceSession.selection.getRange().start, this.aceDoc.$lines); - end = this.indexFromPos(this.aceSession.selection.getRange().end, this.aceDoc.$lines); - } catch (_error) { - e = _error; - try { - start = this.indexFromPos(this.lastCursorRange.start); - end = this.indexFromPos(this.lastCursorRange.end); - } catch (_error) { - e2 = _error; - console.log("Couldn't figure out the cursor range:", e2, "-- setting it to 0:0."); - _ref = [0, 0], start = _ref[0], end = _ref[1]; - } - } - if (start > end) { - _ref1 = [end, start], start = _ref1[0], end = _ref1[1]; - } - return new firepad.Cursor(start, end); - }; - - ACEAdapter.prototype.setCursor = function(cursor) { - var end, start, _ref; - start = this.posFromIndex(cursor.position); - end = this.posFromIndex(cursor.selectionEnd); - if (cursor.position > cursor.selectionEnd) { - _ref = [end, start], start = _ref[0], end = _ref[1]; - } - return this.aceSession.selection.setSelectionRange(new this.aceRange(start.row, start.column, end.row, end.column)); - }; - - ACEAdapter.prototype.setOtherCursor = function(cursor, color, clientId) { - var clazz, css, cursorRange, end, justCursor, self, start, _ref, - _this = this; - if (this.otherCursors == null) { - this.otherCursors = {}; - } - cursorRange = this.otherCursors[clientId]; - if (cursorRange) { - cursorRange.start.detach(); - cursorRange.end.detach(); - this.aceSession.removeMarker(cursorRange.id); - } - start = this.posFromIndex(cursor.position); - end = this.posFromIndex(cursor.selectionEnd); - if (cursor.selectionEnd < cursor.position) { - _ref = [end, start], start = _ref[0], end = _ref[1]; - } - clazz = "other-client-selection-" + (color.replace('#', '')); - justCursor = cursor.position === cursor.selectionEnd; - if (justCursor) { - clazz = clazz.replace('selection', 'cursor'); - } - css = "." + clazz + " {\n position: absolute;\n background-color: " + (justCursor ? 'transparent' : color) + ";\n border-left: 2px solid " + color + ";\n}"; - this.addStyleRule(css); - this.otherCursors[clientId] = cursorRange = new this.aceRange(start.row, start.column, end.row, end.column); - self = this; - cursorRange.clipRows = function() { - var range; - range = self.aceRange.prototype.clipRows.apply(this, arguments); - range.isEmpty = function() { - return false; - }; - return range; - }; - cursorRange.start = this.aceDoc.createAnchor(cursorRange.start); - cursorRange.end = this.aceDoc.createAnchor(cursorRange.end); - cursorRange.id = this.aceSession.addMarker(cursorRange, clazz, "text"); - return { - clear: function() { - cursorRange.start.detach(); - cursorRange.end.detach(); - return _this.aceSession.removeMarker(cursorRange.id); - } - }; - }; - - ACEAdapter.prototype.addStyleRule = function(css) { - var styleElement; - if (typeof document === "undefined" || document === null) { - return; - } - if (!this.addedStyleRules) { - this.addedStyleRules = {}; - styleElement = document.createElement('style'); - document.documentElement.getElementsByTagName('head')[0].appendChild(styleElement); - this.addedStyleSheet = styleElement.sheet; - } - if (this.addedStyleRules[css]) { - return; - } - this.addedStyleRules[css] = true; - return this.addedStyleSheet.insertRule(css, 0); - }; - - ACEAdapter.prototype.registerCallbacks = function(callbacks) { - this.callbacks = callbacks; - }; - - ACEAdapter.prototype.trigger = function() { - var args, event, _ref, _ref1; - event = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : []; - return (_ref = this.callbacks) != null ? (_ref1 = _ref[event]) != null ? _ref1.apply(this, args) : void 0 : void 0; - }; - - ACEAdapter.prototype.applyOperation = function(operation) { - if (!operation.isNoop()) { - this.ignoreChanges = true; - } - this.applyOperationToACE(operation); - return this.ignoreChanges = false; - }; - - ACEAdapter.prototype.registerUndo = function(undoFn) { - return this.ace.undo = undoFn; - }; - - ACEAdapter.prototype.registerRedo = function(redoFn) { - return this.ace.redo = redoFn; - }; - - ACEAdapter.prototype.invertOperation = function(operation) { - return operation.invert(this.getValue()); - }; - - return ACEAdapter; - -})(); - -firepad.MonacoAdapter = (function() { - MonacoAdapter.prototype.ignoreChanges = false; - - function MonacoAdapter(monacoInstance) { - this.onCursorActivity = __bind(this.onCursorActivity, this); - this.onFocus = __bind(this.onFocus, this); - this.onBlur = __bind(this.onBlur, this); - this.onChange = __bind(this.onChange, this); - this.monaco = monacoInstance; - this.monacoModel = this.monaco.getModel(); - - this.grabDocumentState(); - var changeHandler = this.monaco.onDidChangeModelContent(this.onChange); - var didBlurHandler = this.monaco.onDidBlurEditor(this.onBlur); - var didFocusHandler = this.monaco.onDidFocusEditor(this.onFocus); - var didChangeCursorPositionHandler = this.monaco.onDidChangeCursorPosition(this.onCursorActivity); - - this.detach = function() { - changeHandler.dispose(); - didBlurHandler.dispose(); - didFocusHandler.dispose(); - didChangeCursorPositionHandler.dispose(); - }; - } - - MonacoAdapter.prototype.grabDocumentState = function() { - this.lastDocLines = this.monacoModel.getLinesContent(); - var range = this.monaco.getSelection(); - return this.lastCursorRange = range; - }; - - MonacoAdapter.prototype.onChange = function(change) { - var pair; - if (!this.ignoreChanges) { - pair = this.operationFromMonacoChange(change); - this.trigger.apply(this, ['change'].concat(__slice.call(pair))); - return this.grabDocumentState(); - } - }; - - MonacoAdapter.prototype.onBlur = function() { - if (this.monaco.getSelection().isEmpty()) { - return this.trigger('blur'); - } - }; - - MonacoAdapter.prototype.onFocus = function() { - return this.trigger('focus'); - }; - - MonacoAdapter.prototype.onCursorActivity = function() { - var _this = this; - return setTimeout(function() { - return _this.trigger('cursorActivity'); - }, 0); - }; - - MonacoAdapter.prototype.operationFromMonacoChange = function(change) { - text = change.changes[0].text; - start = this.indexFromPos(change.changes[0].range, 'start'); - restLength = this.lastDocLines.join('\n').length - start; - text_ins = change.changes[0].text; - rangeLen = change.changes[0].rangeLength; - - if (change.changes[0].rangeLength > 0) { - restLength -= rangeLen; - text = this.lastDocLines.join('\n') - .slice(start, start+rangeLen); - } - - insert_op = new firepad.TextOperation().retain(start).insert(text) - .retain(restLength); - delete_op = new firepad.TextOperation().retain(start)["delete"](text) - .retain(restLength); - ins_op_ne = new firepad.TextOperation().retain(start)["delete"](text_ins) - .insert(text).retain(restLength); - del_op_ne = new firepad.TextOperation().retain(start)["delete"](text) - .insert(text_ins).retain(restLength); - - if (change.changes[0].text === "" && rangeLen > 0) { - return [delete_op, insert_op]; - } else if (change.changes[0].text !== "" && rangeLen > 0) { - return [del_op_ne, ins_op_ne]; - } else { - return [insert_op, delete_op]; - } - }; - - MonacoAdapter.prototype.applyOperationToMonaco = function(operation) { - var from, index, op, to, _i, _len, _ref, id, ops, range; - index = 0; - _ref = operation.ops; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - op = _ref[_i]; - if (op.isRetain()) { - index += op.chars; - } else if (op.isInsert()) { - range = new monaco.Range( - this.posFromIndex(index).row, - this.posFromIndex(index).column, - this.posFromIndex(index).row, - this.posFromIndex(index).column - ); - id = { major: 1, minor: 1 }; - ops = {identifier: id, range: range, text: op.text, - forceMoveMarkers: true - }; - this.monaco.executeEdits("my-source", [ops]); - index += op.text.length; - } else if (op.isDelete()) { - content = this.monacoModel.getLinesContent(); - from = this.posFromIndex(index, content); - to = this.posFromIndex(index + op.chars, content); - - range = new monaco.Range(from.row, from.column, to.row, to.column); - id = { major: 1, minor: 2 }; - ops = {identifier: id, range: range, text: "", - forceMoveMarkers: true - }; - this.monaco.executeEdits("my-source", [ops]); - } - } - return this.grabDocumentState(); - }; - - // Index in monaco starts from 1 - MonacoAdapter.prototype.posFromIndex = function(index, content) { - var line, row, _i, _len, _ref; - if (content == null){ - _ref = this.lastDocLines; - } - else{ - _ref = content; - } - for (row = _i = 0, _len = _ref.length; _i < _len; row = ++_i) { - line = _ref[row]; - if (index <= line.length) { - break; - } - index -= line.length + 1; - } - return { - row: row + 1, - column: index + 1 - }; - }; - - // Index in monaco start from 1 - MonacoAdapter.prototype.indexFromPos = function(range, upto, lines) { - var index; - if (lines == null) { - lines = this.lastDocLines; - } - index = 0; - if (upto === 'start') { - for (row = 1; row < range.startLineNumber; row++) { - index += lines[row-1].length + 1 - } - return index + range.startColumn -1; - } else { - for (row = 1; row < range.endLineNumber; row++) { - index += lines[row-1].length + 1 - } - return index + range.endColumn -1; - } - }; - - MonacoAdapter.prototype.getValue = function() { - return this.monacoModel.getValue(); - }; - - MonacoAdapter.prototype.getCursor = function() { - var e, e2, end, start, _ref, _ref1; - try { - selection = this.monaco.getSelection(); - start = this.indexFromPos(selection, 'start', this.lastDocLines); - end = this.indexFromPos(selection, 'end', this.lastDocLines); - } catch (_error) { - e = _error; - try { - start = this.indexFromPos(this.lastCursorRange, 'start'); - end = this.indexFromPos(this.lastCursorRange, 'end'); - } catch (_error) { - e2 = _error; - console.log("Couldn't figure out the cursor range:", - e2, "-- setting it to 0:0."); - _ref = [0, 0], start = _ref[0], end = _ref[1]; - } - } - if (start > end) { - _ref1 = [end, start], start = _ref1[0], end = _ref1[1]; - } - return new firepad.Cursor(start, end); - }; - - MonacoAdapter.prototype.setCursor = function(cursor) { - var end, start, _ref; - start = this.posFromIndex(cursor.position); - end = this.posFromIndex(cursor.selectionEnd); - if (cursor.position > cursor.selectionEnd) { - _ref = [end, start], start = _ref[0], end = _ref[1]; - } - return this.monaco.setSelection( - new monaco.Range(start.row, start.column, end.row, end.column) - ); - }; - - MonacoAdapter.prototype.setOtherCursor = function(cursor, color, clientId) { - if (this.otherCursors == null){ - this.otherCursors = {}; - this.otherCursors[clientId] = []; - } - if(this.otherCursors[clientId]){ - this.otherCursors[clientId] = this.monaco.deltaDecorations( - this.otherCursors[clientId], [] - ); - } - start = this.posFromIndex(cursor.position); - end = this.posFromIndex(cursor.selectionEnd); - clazz = "other-client-selection-" + (color.replace('#', '')); - if (start < 0 || end < 0 || start > end) { - return; - } - justCursor = cursor.position === cursor.selectionEnd; - if(justCursor){ - // show cursor - clazz = clazz.replace('selection', 'cursor'); - } - css = "." + clazz + " {\n position: relative;\n" + - "background-color: " + (justCursor ? 'transparent' : color) + ";\n" + - "border-left: 2px solid " + color + ";\n}"; - this.addStyleRule(css); - this.otherCursors[clientId] = this.monaco.deltaDecorations( - this.otherCursors[clientId], [{ range: new monaco.Range( - start.row, start.column, end.row, end.column), - options: {className: clazz}}, - ] - ); - _this = this; - return {clear: function () { - return _this.monaco.deltaDecorations( - _this.otherCursors[clientId], []); - } - }; - }; - - MonacoAdapter.prototype.addStyleRule = function(css) { - var styleElement; - if (typeof document === "undefined" || document === null) { - return; - } - if (!this.addedStyleRules) { - this.addedStyleRules = {}; - styleElement = document.createElement('style'); - document.documentElement.getElementsByTagName('head')[0] - .appendChild(styleElement); - this.addedStyleSheet = styleElement.sheet; - } - if (this.addedStyleRules[css]) { - return; - } - this.addedStyleRules[css] = true; - return this.addedStyleSheet.insertRule(css, 0); - }; - - MonacoAdapter.prototype.registerCallbacks = function(callbacks) { - this.callbacks = callbacks; - }; - - MonacoAdapter.prototype.trigger = function (event) { - var args = Array.prototype.slice.call(arguments, 1); - var action = this.callbacks && this.callbacks[event]; - if (action) { action.apply(this, args); } - }; - - MonacoAdapter.prototype.applyOperation = function(operation) { - if (!operation.isNoop()) { - this.ignoreChanges = true; - } - this.applyOperationToMonaco(operation); - return this.ignoreChanges = false; - }; - - MonacoAdapter.prototype.registerUndo = function(undoFn) { - return this.monacoModel.undo = undoFn; - }; - - MonacoAdapter.prototype.registerRedo = function(redoFn) { - return this.monacoModel.redo = redoFn; - }; - - MonacoAdapter.prototype.invertOperation = function(operation) { - return operation.invert(this.getValue()); - }; - - return MonacoAdapter; - -})(); - -var firepad = firepad || { }; - -firepad.AttributeConstants = { - BOLD: 'b', - ITALIC: 'i', - UNDERLINE: 'u', - STRIKE: 's', - FONT: 'f', - FONT_SIZE: 'fs', - COLOR: 'c', - BACKGROUND_COLOR: 'bc', - ENTITY_SENTINEL: 'ent', - -// Line Attributes - LINE_SENTINEL: 'l', - LINE_INDENT: 'li', - LINE_ALIGN: 'la', - LIST_TYPE: 'lt' -}; - -firepad.sentinelConstants = { - // A special character we insert at the beginning of lines so we can attach attributes to it to represent - // "line attributes." E000 is from the unicode "private use" range. - LINE_SENTINEL_CHARACTER: '\uE000', - - // A special character used to represent any "entity" inserted into the document (e.g. an image). - ENTITY_SENTINEL_CHARACTER: '\uE001' -}; - -var firepad = firepad || { }; - -firepad.EntityManager = (function () { - var utils = firepad.utils; - - function EntityManager() { - this.entities_ = {}; - - var attrs = ['src', 'alt', 'width', 'height', 'style', 'class']; - this.register('img', { - render: function(info) { - utils.assert(info.src, "image entity should have 'src'!"); - var attrs = ['src', 'alt', 'width', 'height', 'style', 'class']; - var html = '= 4) { - this.codeMirror.on('changes', this.onCodeMirrorChange_); - } else { - this.codeMirror.on('change', this.onCodeMirrorChange_); - } - this.codeMirror.on('beforeChange', this.onCodeMirrorBeforeChange_); - this.codeMirror.on('cursorActivity', this.onCursorActivity_); - - this.changeId_ = 0; - this.outstandingChanges_ = { }; - this.dirtyLines_ = []; - } - utils.makeEventEmitter(RichTextCodeMirror, ['change', 'attributesChange', 'newLine']); - - - var LineSentinelCharacter = firepad.sentinelConstants.LINE_SENTINEL_CHARACTER; - var EntitySentinelCharacter = firepad.sentinelConstants.ENTITY_SENTINEL_CHARACTER; - - RichTextCodeMirror.prototype.detach = function() { - this.codeMirror.off('beforeChange', this.onCodeMirrorBeforeChange_); - this.codeMirror.off('change', this.onCodeMirrorChange_); - this.codeMirror.off('changes', this.onCodeMirrorChange_); - this.codeMirror.off('cursorActivity', this.onCursorActivity_); - this.clearAnnotations_(); - }; - - RichTextCodeMirror.prototype.toggleAttribute = function(attribute, value) { - var trueValue = value || true; - if (this.emptySelection_()) { - var attrs = this.getCurrentAttributes_(); - if (attrs[attribute] === trueValue) { - delete attrs[attribute]; - } else { - attrs[attribute] = trueValue; - } - this.currentAttributes_ = attrs; - } else { - var attributes = this.getCurrentAttributes_(); - var newValue = (attributes[attribute] !== trueValue) ? trueValue : false; - this.setAttribute(attribute, newValue); - } - }; - - RichTextCodeMirror.prototype.setAttribute = function(attribute, value) { - var cm = this.codeMirror; - if (this.emptySelection_()) { - var attrs = this.getCurrentAttributes_(); - if (value === false) { - delete attrs[attribute]; - } else { - attrs[attribute] = value; - } - this.currentAttributes_ = attrs; - } else { - this.updateTextAttributes(cm.indexFromPos(cm.getCursor('start')), cm.indexFromPos(cm.getCursor('end')), - function(attributes) { - if (value === false) { - delete attributes[attribute]; - } else { - attributes[attribute] = value; - } - }); - - this.updateCurrentAttributes_(); - } - }; - - RichTextCodeMirror.prototype.updateTextAttributes = function(start, end, updateFn, origin, doLineAttributes) { - var newChanges = []; - var pos = start, self = this; - this.annotationList_.updateSpan(new Span(start, end - start), function(annotation, length) { - var attributes = { }; - for(var attr in annotation.attributes) { - attributes[attr] = annotation.attributes[attr]; - } - - // Don't modify if this is a line sentinel. - if (!attributes[ATTR.LINE_SENTINEL] || doLineAttributes) - updateFn(attributes); - - // changedAttributes will be the attributes we changed, with their new values. - // changedAttributesInverse will be the attributes we changed, with their old values. - var changedAttributes = { }, changedAttributesInverse = { }; - self.computeChangedAttributes_(annotation.attributes, attributes, changedAttributes, changedAttributesInverse); - if (!emptyAttributes(changedAttributes)) { - newChanges.push({ start: pos, end: pos + length, attributes: changedAttributes, attributesInverse: changedAttributesInverse, origin: origin }); - } - - pos += length; - return new RichTextAnnotation(attributes); - }); - - if (newChanges.length > 0) { - this.trigger('attributesChange', this, newChanges); - } - }; - - RichTextCodeMirror.prototype.computeChangedAttributes_ = function(oldAttrs, newAttrs, changed, inverseChanged) { - var attrs = { }, attr; - for(attr in oldAttrs) { attrs[attr] = true; } - for(attr in newAttrs) { attrs[attr] = true; } - - for (attr in attrs) { - if (!(attr in newAttrs)) { - // it was removed. - changed[attr] = false; - inverseChanged[attr] = oldAttrs[attr]; - } else if (!(attr in oldAttrs)) { - // it was added. - changed[attr] = newAttrs[attr]; - inverseChanged[attr] = false; - } else if (oldAttrs[attr] !== newAttrs[attr]) { - // it was changed. - changed[attr] = newAttrs[attr]; - inverseChanged[attr] = oldAttrs[attr]; - } - } - }; - - RichTextCodeMirror.prototype.toggleLineAttribute = function(attribute, value) { - var currentAttributes = this.getCurrentLineAttributes_(); - var newValue; - if (!(attribute in currentAttributes) || currentAttributes[attribute] !== value) { - newValue = value; - } else { - newValue = false; - } - this.setLineAttribute(attribute, newValue); - }; - - RichTextCodeMirror.prototype.setLineAttribute = function(attribute, value) { - this.updateLineAttributesForSelection(function(attributes) { - if (value === false) { - delete attributes[attribute]; - } else { - attributes[attribute] = value; - } - }); - }; - - RichTextCodeMirror.prototype.updateLineAttributesForSelection = function(updateFn) { - var cm = this.codeMirror; - var start = cm.getCursor('start'), end = cm.getCursor('end'); - var startLine = start.line, endLine = end.line; - var endLineText = cm.getLine(endLine); - var endsAtBeginningOfLine = this.areLineSentinelCharacters_(endLineText.substr(0, end.ch)); - if (endLine > startLine && endsAtBeginningOfLine) { - // If the selection ends at the beginning of a line, don't include that line. - endLine--; - } - - this.updateLineAttributes(startLine, endLine, updateFn); - }; - - RichTextCodeMirror.prototype.updateLineAttributes = function(startLine, endLine, updateFn) { - // TODO: Batch this into a single operation somehow. - for(var line = startLine; line <= endLine; line++) { - var text = this.codeMirror.getLine(line); - var lineStartIndex = this.codeMirror.indexFromPos({line: line, ch: 0}); - // Create line sentinel character if necessary. - if (text[0] !== LineSentinelCharacter) { - var attributes = { }; - attributes[ATTR.LINE_SENTINEL] = true; - updateFn(attributes); - this.insertText(lineStartIndex, LineSentinelCharacter, attributes); - } else { - this.updateTextAttributes(lineStartIndex, lineStartIndex + 1, updateFn, /*origin=*/null, /*doLineAttributes=*/true); - } - } - }; - - RichTextCodeMirror.prototype.replaceText = function(start, end, text, attributes, origin) { - this.changeId_++; - var newOrigin = RichTextOriginPrefix + this.changeId_; - this.outstandingChanges_[newOrigin] = { origOrigin: origin, attributes: attributes }; - - var cm = this.codeMirror; - var from = cm.posFromIndex(start); - var to = typeof end === 'number' ? cm.posFromIndex(end) : null; - cm.replaceRange(text, from, to, newOrigin); - }; - - RichTextCodeMirror.prototype.insertText = function(index, text, attributes, origin) { - var cm = this.codeMirror; - var cursor = cm.getCursor(); - var resetCursor = origin == 'RTCMADAPTER' && !cm.somethingSelected() && index == cm.indexFromPos(cursor); - this.replaceText(index, null, text, attributes, origin); - if (resetCursor) cm.setCursor(cursor); - }; - - RichTextCodeMirror.prototype.removeText = function(start, end, origin) { - var cm = this.codeMirror; - cm.replaceRange("", cm.posFromIndex(start), cm.posFromIndex(end), origin); - }; - - RichTextCodeMirror.prototype.insertEntityAtCursor = function(type, info, origin) { - var cm = this.codeMirror; - var index = cm.indexFromPos(cm.getCursor('head')); - this.insertEntityAt(index, type, info, origin); - }; - - RichTextCodeMirror.prototype.insertEntityAt = function(index, type, info, origin) { - var cm = this.codeMirror; - this.insertEntity_(index, new firepad.Entity(type, info), origin); - }; - - RichTextCodeMirror.prototype.insertEntity_ = function(index, entity, origin) { - this.replaceText(index, null, EntitySentinelCharacter, entity.toAttributes(), origin); - }; - - RichTextCodeMirror.prototype.getAttributeSpans = function(start, end) { - var spans = []; - var annotatedSpans = this.annotationList_.getAnnotatedSpansForSpan(new Span(start, end - start)); - for(var i = 0; i < annotatedSpans.length; i++) { - spans.push({ length: annotatedSpans[i].length, attributes: annotatedSpans[i].annotation.attributes }); - } - - return spans; - }; - - RichTextCodeMirror.prototype.end = function() { - var lastLine = this.codeMirror.lineCount() - 1; - return this.codeMirror.indexFromPos({line: lastLine, ch: this.codeMirror.getLine(lastLine).length}); - }; - - RichTextCodeMirror.prototype.getRange = function(start, end) { - var from = this.codeMirror.posFromIndex(start), to = this.codeMirror.posFromIndex(end); - return this.codeMirror.getRange(from, to); - }; - - RichTextCodeMirror.prototype.initAnnotationList_ = function() { - // Insert empty annotation span for existing content. - var end = this.end(); - if (end !== 0) { - this.annotationList_.insertAnnotatedSpan(new Span(0, end), new RichTextAnnotation()); - } - }; - - /** - * Updates the nodes of an Annotation. - * @param {Array.} oldNodes The list of nodes to replace. - * @param {Array.} newNodes The new list of nodes. - */ - RichTextCodeMirror.prototype.onAnnotationsChanged_ = function(oldNodes, newNodes) { - var marker; - - var linesToReMark = { }; - - // Update any entities in-place that we can. This will remove them from the oldNodes/newNodes lists - // so we don't remove and recreate them below. - this.tryToUpdateEntitiesInPlace(oldNodes, newNodes); - - for(var i = 0; i < oldNodes.length; i++) { - var attributes = oldNodes[i].annotation.attributes; - if (ATTR.LINE_SENTINEL in attributes) { - linesToReMark[this.codeMirror.posFromIndex(oldNodes[i].pos).line] = true; - } - marker = oldNodes[i].getAttachedObject(); - if (marker) { - marker.clear(); - } - } - - for (i = 0; i < newNodes.length; i++) { - var annotation = newNodes[i].annotation; - var forLine = (ATTR.LINE_SENTINEL in annotation.attributes); - var entity = (ATTR.ENTITY_SENTINEL in annotation.attributes); - - var from = this.codeMirror.posFromIndex(newNodes[i].pos); - if (forLine) { - linesToReMark[from.line] = true; - } else if (entity) { - this.markEntity_(newNodes[i]); - } else { - var className = this.getClassNameForAttributes_(annotation.attributes); - if (className !== '') { - var to = this.codeMirror.posFromIndex(newNodes[i].pos + newNodes[i].length); - marker = this.codeMirror.markText(from, to, { className: className }); - newNodes[i].attachObject(marker); - } - } - } - - for(var line in linesToReMark) { - this.dirtyLines_.push(this.codeMirror.getLineHandle(Number(line))); - this.queueLineMarking_(); - } - }; - - RichTextCodeMirror.prototype.tryToUpdateEntitiesInPlace = function(oldNodes, newNodes) { - // Loop over nodes in reverse order so we can easily splice them out as necessary. - var oldNodesLen = oldNodes.length; - while (oldNodesLen--) { - var oldNode = oldNodes[oldNodesLen]; - var newNodesLen = newNodes.length; - while (newNodesLen--) { - var newNode = newNodes[newNodesLen]; - if (oldNode.pos == newNode.pos && - oldNode.length == newNode.length && - oldNode.annotation.attributes['ent'] && - oldNode.annotation.attributes['ent'] == newNode.annotation.attributes['ent']) { - var entityType = newNode.annotation.attributes['ent']; - if (this.entityManager_.entitySupportsUpdate(entityType)) { - // Update it in place and remove the change from oldNodes / newNodes so we don't process it below. - oldNodes.splice(oldNodesLen, 1); - newNodes.splice(newNodesLen, 1); - var marker = oldNode.getAttachedObject(); - marker.update(newNode.annotation.attributes); - newNode.attachObject(marker); - } - } - } - } - }; - - RichTextCodeMirror.prototype.queueLineMarking_ = function() { - if (this.lineMarkTimeout_ != null) return; - var self = this; - - this.lineMarkTimeout_ = setTimeout(function() { - self.lineMarkTimeout_ = null; - var dirtyLineNumbers = []; - for(var i = 0; i < self.dirtyLines_.length; i++) { - var lineNum = self.codeMirror.getLineNumber(self.dirtyLines_[i]); - dirtyLineNumbers.push(Number(lineNum)); - } - self.dirtyLines_ = []; - - dirtyLineNumbers.sort(function(a, b) { return a - b; }); - var lastLineMarked = -1; - for(i = 0; i < dirtyLineNumbers.length; i++) { - var lineNumber = dirtyLineNumbers[i]; - if (lineNumber > lastLineMarked) { - lastLineMarked = self.markLineSentinelCharactersForChangedLines_(lineNumber, lineNumber); - } - } - }, 0); - }; - - RichTextCodeMirror.prototype.addStyleWithCSS_ = function(css) { - var head = document.getElementsByTagName('head')[0], - style = document.createElement('style'); - - style.type = 'text/css'; - if (style.styleSheet){ - style.styleSheet.cssText = css; - } else { - style.appendChild(document.createTextNode(css)); - } - - head.appendChild(style); - }; - - RichTextCodeMirror.prototype.getClassNameForAttributes_ = function(attributes) { - var globalClassName = ''; - for (var attr in attributes) { - var val = attributes[attr]; - if (attr === ATTR.LINE_SENTINEL) { - firepad.utils.assert(val === true, "LINE_SENTINEL attribute should be true if it exists."); - } else { - var className = (this.options_['cssPrefix'] || RichTextClassPrefixDefault) + attr; - if (val !== true) { - // Append "px" to font size if it's missing. - // Probably could be removed now as parseHtml automatically adds px when required - if (attr === ATTR.FONT_SIZE && typeof val !== "string") { - val = val + "px"; - } - - var classVal = val.toString().toLowerCase().replace(/[^a-z0-9-_]/g, '-'); - className += '-' + classVal; - if (DynamicStyleAttributes[attr]) { - if (!StyleCache_[attr]) StyleCache_[attr] = {}; - if (!StyleCache_[attr][classVal]) { - StyleCache_[attr][classVal] = true; - var dynStyle = DynamicStyleAttributes[attr]; - var css = (typeof dynStyle === 'function') ? - dynStyle(val) : - dynStyle + ": " + val; - - var selector = (attr == ATTR.LINE_INDENT) ? - 'pre.' + className : - '.' + className; - - this.addStyleWithCSS_(selector + ' { ' + css + ' }'); - } - } - } - globalClassName = globalClassName + ' ' + className; - } - } - return globalClassName; - }; - - RichTextCodeMirror.prototype.markEntity_ = function(annotationNode) { - var attributes = annotationNode.annotation.attributes; - var entity = firepad.Entity.fromAttributes(attributes); - var cm = this.codeMirror; - var self = this; - - var markers = []; - for(var i = 0; i < annotationNode.length; i++) { - var from = cm.posFromIndex(annotationNode.pos + i); - var to = cm.posFromIndex(annotationNode.pos + i + 1); - - var options = { collapsed: true, atomic: true, inclusiveLeft: false, inclusiveRight: false }; - - var entityHandle = this.createEntityHandle_(entity, annotationNode.pos); - - var element = this.entityManager_.renderToElement(entity, entityHandle); - if (element) { - options.replacedWith = element; - } - var marker = cm.markText(from, to, options); - markers.push(marker); - entityHandle.setMarker(marker); - } - - annotationNode.attachObject({ - clear: function() { - for(var i = 0; i < markers.length; i++) { - markers[i].clear(); - } - }, - - /** - * Updates the attributes of all the AnnotationNode entities. - * @param {Object.} info The full list of new - * attributes to apply. - */ - update: function(info) { - var entity = firepad.Entity.fromAttributes(info); - for(var i = 0; i < markers.length; i++) { - self.entityManager_.updateElement(entity, markers[i].replacedWith); - } - } - }); - - // This probably shouldn't be necessary. There must be a lurking CodeMirror bug. - this.queueRefresh_(); - }; - - RichTextCodeMirror.prototype.queueRefresh_ = function() { - var self = this; - if (!this.refreshTimer_) { - this.refreshTimer_ = setTimeout(function() { - self.codeMirror.refresh(); - self.refreshTimer_ = null; - }, 0); - } - }; - - RichTextCodeMirror.prototype.createEntityHandle_ = function(entity, location) { - var marker = null; - var self = this; - - function find() { - if (marker) { - var where = marker.find(); - return where ? self.codeMirror.indexFromPos(where.from) : null; - } else { - return location; - } - } - - function remove() { - var at = find(); - if (at != null) { - self.codeMirror.focus(); - self.removeText(at, at + 1); - } - } - - /** - * Updates the attributes of an Entity. Will call .update() if the entity supports it, - * else it'll just remove / re-create the entity. - * @param {Object.} info The full list of new - * attributes to apply. - */ - function replace(info) { - var ATTR = firepad.AttributeConstants; - var SENTINEL = ATTR.ENTITY_SENTINEL; - var PREFIX = SENTINEL + '_'; - - var at = find(); - - self.updateTextAttributes(at, at+1, function(attrs) { - for (var member in attrs) { - delete attrs[member]; - } - attrs[SENTINEL] = entity.type; - - for(var attr in info) { - attrs[PREFIX + attr] = info[attr]; - } - }); - } - - function setMarker(m) { - marker = m; - } - - return { find: find, remove: remove, replace: replace, - setMarker: setMarker }; - }; - - RichTextCodeMirror.prototype.lineClassRemover_ = function(lineNum) { - var cm = this.codeMirror; - var lineHandle = cm.getLineHandle(lineNum); - return { - clear: function() { - // HACK to remove all classes (since CodeMirror treats this as a regex internally). - cm.removeLineClass(lineHandle, "text", ".*"); - } - } - }; - - RichTextCodeMirror.prototype.emptySelection_ = function() { - var start = this.codeMirror.getCursor('start'), end = this.codeMirror.getCursor('end'); - return (start.line === end.line && start.ch === end.ch); - }; - - RichTextCodeMirror.prototype.onCodeMirrorBeforeChange_ = function(cm, change) { - // Remove LineSentinelCharacters from incoming input (e.g copy/pasting) - if (change.origin === '+input' || change.origin === 'paste') { - var newText = []; - for(var i = 0; i < change.text.length; i++) { - var t = change.text[i]; - t = t.replace(new RegExp('[' + LineSentinelCharacter + EntitySentinelCharacter + ']', 'g'), ''); - newText.push(t); - } - change.update(change.from, change.to, newText); - } - }; - - function cmpPos (a, b) { - return (a.line - b.line) || (a.ch - b.ch); - } - function posEq (a, b) { return cmpPos(a, b) === 0; } - function posLe (a, b) { return cmpPos(a, b) <= 0; } - - function last (arr) { return arr[arr.length - 1]; } - - function sumLengths (strArr) { - if (strArr.length === 0) { return 0; } - var sum = 0; - for (var i = 0; i < strArr.length; i++) { sum += strArr[i].length; } - return sum + strArr.length - 1; - } - - RichTextCodeMirror.prototype.onCodeMirrorChange_ = function(cm, cmChanges) { - // Handle single change objects and linked lists of change objects. - if (typeof cmChanges.from === 'object') { - var changeArray = []; - while (cmChanges) { - changeArray.push(cmChanges); - cmChanges = cmChanges.next; - } - cmChanges = changeArray; - } - - var changes = this.convertCoordinateSystemForChanges_(cmChanges); - var newChanges = []; - - for (var i = 0; i < changes.length; i++) { - var change = changes[i]; - var start = change.start, end = change.end, text = change.text, removed = change.removed, origin = change.origin; - - // When text with multiple sets of attributes on it is removed, we need to split it into separate remove changes. - if (removed.length > 0) { - var oldAnnotationSpans = this.annotationList_.getAnnotatedSpansForSpan(new Span(start, removed.length)); - var removedPos = 0; - for(var j = 0; j < oldAnnotationSpans.length; j++) { - var span = oldAnnotationSpans[j]; - newChanges.push({ start: start, end: start + span.length, removedAttributes: span.annotation.attributes, - removed: removed.substr(removedPos, span.length), attributes: { }, text: "", origin: change.origin }); - removedPos += span.length; - } - - this.annotationList_.removeSpan(new Span(start, removed.length)); - } - - if (text.length > 0) { - var attributes; - // TODO: Handle 'paste' differently? - if (change.origin === '+input' || change.origin === 'paste') { - attributes = this.currentAttributes_ || { }; - } else if (origin in this.outstandingChanges_) { - attributes = this.outstandingChanges_[origin].attributes; - origin = this.outstandingChanges_[origin].origOrigin; - delete this.outstandingChanges_[origin]; - } else { - attributes = {}; - } - - this.annotationList_.insertAnnotatedSpan(new Span(start, text.length), new RichTextAnnotation(attributes)); - - newChanges.push({ start: start, end: start, removedAttributes: { }, removed: "", text: text, - attributes: attributes, origin: origin }); - } - } - - this.markLineSentinelCharactersForChanges_(cmChanges); - - if (newChanges.length > 0) { - this.trigger('change', this, newChanges); - } - }; - - RichTextCodeMirror.prototype.convertCoordinateSystemForChanges_ = function(changes) { - // We have to convert the positions in the pre-change coordinate system to indexes. - // CodeMirror's `indexFromPos` method does this for the current state of the editor. - // We can use the information of a single change object to convert a post-change - // coordinate system to a pre-change coordinate system. We can now proceed inductively - // to get a pre-change coordinate system for all changes in the linked list. A - // disadvantage of this approach is its complexity `O(n^2)` in the length of the - // linked list of changes. - - var self = this; - var indexFromPos = function (pos) { - return self.codeMirror.indexFromPos(pos); - }; - - function updateIndexFromPos (indexFromPos, change) { - return function (pos) { - if (posLe(pos, change.from)) { return indexFromPos(pos); } - if (posLe(change.to, pos)) { - return indexFromPos({ - line: pos.line + change.text.length - 1 - (change.to.line - change.from.line), - ch: (change.to.line < pos.line) ? - pos.ch : - (change.text.length <= 1) ? - pos.ch - (change.to.ch - change.from.ch) + sumLengths(change.text) : - pos.ch - change.to.ch + last(change.text).length - }) + sumLengths(change.removed) - sumLengths(change.text); - } - if (change.from.line === pos.line) { - return indexFromPos(change.from) + pos.ch - change.from.ch; - } - return indexFromPos(change.from) + - sumLengths(change.removed.slice(0, pos.line - change.from.line)) + - 1 + pos.ch; - }; - } - - var newChanges = []; - for (var i = changes.length - 1; i >= 0; i--) { - var change = changes[i]; - indexFromPos = updateIndexFromPos(indexFromPos, change); - - var start = indexFromPos(change.from); - - var removedText = change.removed.join('\n'); - var text = change.text.join('\n'); - newChanges.unshift({ start: start, end: start + removedText.length, removed: removedText, text: text, - origin: change.origin}); - } - return newChanges; - }; - - /** - * Detects whether any line sentinel characters were added or removed by the change and if so, - * re-marks line sentinel characters on the affected range of lines. - * @param changes - * @private - */ - RichTextCodeMirror.prototype.markLineSentinelCharactersForChanges_ = function(changes) { - // TODO: This doesn't handle multiple changes correctly (overlapping, out-of-oder, etc.). - // But In practice, people using firepad for rich-text editing don't batch multiple changes - // together, so this isn't quite as bad as it seems. - var startLine = Number.MAX_VALUE, endLine = -1; - - for (var i = 0; i < changes.length; i++) { - var change = changes[i]; - var line = change.from.line, ch = change.from.ch; - - if (change.removed.length > 1 || change.removed[0].indexOf(LineSentinelCharacter) >= 0) { - // We removed 1+ newlines or line sentinel characters. - startLine = Math.min(startLine, line); - endLine = Math.max(endLine, line); - } - - if (change.text.length > 1) { // 1+ newlines - startLine = Math.min(startLine, line); - endLine = Math.max(endLine, line + change.text.length - 1); - } else if (change.text[0].indexOf(LineSentinelCharacter) >= 0) { - startLine = Math.min(startLine, line); - endLine = Math.max(endLine, line); - } - } - - // HACK: Because the above code doesn't handle multiple changes correctly, endLine might be invalid. To - // avoid crashing, we just cap it at the line count. - endLine = Math.min(endLine, this.codeMirror.lineCount() - 1); - - this.markLineSentinelCharactersForChangedLines_(startLine, endLine); - }; - - RichTextCodeMirror.prototype.markLineSentinelCharactersForChangedLines_ = function(startLine, endLine) { - // Back up to first list item. - if (startLine < Number.MAX_VALUE) { - while(startLine > 0 && this.lineIsListItemOrIndented_(startLine-1)) { - startLine--; - } - } - - // Advance to last list item. - if (endLine > -1) { - var lineCount = this.codeMirror.lineCount(); - while (endLine + 1 < lineCount && this.lineIsListItemOrIndented_(endLine+1)) { - endLine++; - } - } - - // keeps track of the list number at each indent level. - var listNumber = []; - - var cm = this.codeMirror; - for(var line = startLine; line <= endLine; line++) { - var text = cm.getLine(line); - - // Remove any existing line classes. - var lineHandle = cm.getLineHandle(line); - cm.removeLineClass(lineHandle, "text", ".*"); - - if (text.length > 0) { - var markIndex = text.indexOf(LineSentinelCharacter); - while (markIndex >= 0) { - var markStartIndex = markIndex; - - // Find the end of this series of sentinel characters, and remove any existing markers. - while (markIndex < text.length && text[markIndex] === LineSentinelCharacter) { - var marks = cm.findMarksAt({ line: line, ch: markIndex }); - for(var i = 0; i < marks.length; i++) { - if (marks[i].isForLineSentinel) { - marks[i].clear(); - } - } - - markIndex++; - } - - this.markLineSentinelCharacters_(line, markStartIndex, markIndex, listNumber); - markIndex = text.indexOf(LineSentinelCharacter, markIndex); - } - } else { - // Reset all indents. - listNumber = []; - } - } - return endLine; - }; - - RichTextCodeMirror.prototype.markLineSentinelCharacters_ = function(line, startIndex, endIndex, listNumber) { - var cm = this.codeMirror; - // If the mark is at the beginning of the line and it represents a list element, we need to replace it with - // the appropriate html element for the list heading. - var element = null; - var marker = null; - var getMarkerLine = function() { - var span = marker.find(); - return span ? span.from.line : null; - }; - - if (startIndex === 0) { - var attributes = this.getLineAttributes_(line); - var listType = attributes[ATTR.LIST_TYPE]; - var indent = attributes[ATTR.LINE_INDENT] || 0; - if (listType && indent === 0) { indent = 1; } - while (indent >= listNumber.length) { - listNumber.push(1); - } - if (listType === 'o') { - element = this.makeOrderedListElement_(listNumber[indent]); - listNumber[indent]++; - } else if (listType === 'u') { - element = this.makeUnorderedListElement_(); - listNumber[indent] = 1; - } else if (listType === 't') { - element = this.makeTodoListElement_(false, getMarkerLine); - listNumber[indent] = 1; - } else if (listType === 'tc') { - element = this.makeTodoListElement_(true, getMarkerLine); - listNumber[indent] = 1; - } - - var className = this.getClassNameForAttributes_(attributes); - if (className !== '') { - this.codeMirror.addLineClass(line, "text", className); - } - - // Reset deeper indents back to 1. - listNumber = listNumber.slice(0, indent+1); - } - - // Create a marker to cover this series of sentinel characters. - // NOTE: The reason we treat them as a group (one marker for all subsequent sentinel characters instead of - // one marker for each sentinel character) is that CodeMirror seems to get angry if we don't. - var markerOptions = { inclusiveLeft: true, collapsed: true }; - if (element) { - markerOptions.replacedWith = element; - } - var marker = cm.markText({line: line, ch: startIndex }, { line: line, ch: endIndex }, markerOptions); - // track that it's a line-sentinel character so we can identify it later. - marker.isForLineSentinel = true; - }; - - RichTextCodeMirror.prototype.makeOrderedListElement_ = function(number) { - return utils.elt('div', number + '.', { - 'class': 'firepad-list-left' - }); - }; - - RichTextCodeMirror.prototype.makeUnorderedListElement_ = function() { - return utils.elt('div', '\u2022', { - 'class': 'firepad-list-left' - }); - }; - - RichTextCodeMirror.prototype.toggleTodo = function(noRemove) { - var attribute = ATTR.LIST_TYPE; - var currentAttributes = this.getCurrentLineAttributes_(); - var newValue; - if (!(attribute in currentAttributes) || ((currentAttributes[attribute] !== 't') && (currentAttributes[attribute] !== 'tc'))) { - newValue = 't'; - } else if (currentAttributes[attribute] === 't') { - newValue = 'tc'; - } else if (currentAttributes[attribute] === 'tc') { - newValue = noRemove ? 't' : false; - } - this.setLineAttribute(attribute, newValue); - }; - - RichTextCodeMirror.prototype.makeTodoListElement_ = function(checked, getMarkerLine) { - var params = { - 'type': "checkbox", - 'class': 'firepad-todo-left' - }; - if (checked) params['checked'] = true; - var el = utils.elt('input', false, params); - var self = this; - utils.on(el, 'click', utils.stopEventAnd(function(e) { - self.codeMirror.setCursor({line: getMarkerLine(), ch: 1}); - self.toggleTodo(true); - })); - return el; - }; - - RichTextCodeMirror.prototype.lineIsListItemOrIndented_ = function(lineNum) { - var attrs = this.getLineAttributes_(lineNum); - return ((attrs[ATTR.LIST_TYPE] || false) !== false) || - ((attrs[ATTR.LINE_INDENT] || 0) !== 0); - }; - - RichTextCodeMirror.prototype.onCursorActivity_ = function() { - var self = this; - setTimeout(function() { - self.updateCurrentAttributes_(); - }, 1); - }; - - RichTextCodeMirror.prototype.getCurrentAttributes_ = function() { - if (!this.currentAttributes_) { - this.updateCurrentAttributes_(); - } - return this.currentAttributes_; - }; - - RichTextCodeMirror.prototype.updateCurrentAttributes_ = function() { - var cm = this.codeMirror; - var anchor = cm.indexFromPos(cm.getCursor('anchor')), head = cm.indexFromPos(cm.getCursor('head')); - var pos = head; - if (anchor > head) { // backwards selection - // Advance past any newlines or line sentinels. - while(pos < this.end()) { - var c = this.getRange(pos, pos+1); - if (c !== '\n' && c !== LineSentinelCharacter) - break; - pos++; - } - if (pos < this.end()) - pos++; // since we're going to look at the annotation span to the left to decide what attributes to use. - } else { - // Back up before any newlines or line sentinels. - while(pos > 0) { - c = this.getRange(pos-1, pos); - if (c !== '\n' && c !== LineSentinelCharacter) - break; - pos--; - } - } - var spans = this.annotationList_.getAnnotatedSpansForPos(pos); - this.currentAttributes_ = {}; - - var attributes = {}; - // Use the attributes to the left unless they're line attributes (in which case use the ones to the right. - if (spans.length > 0 && (!(ATTR.LINE_SENTINEL in spans[0].annotation.attributes))) { - attributes = spans[0].annotation.attributes; - } else if (spans.length > 1) { - firepad.utils.assert(!(ATTR.LINE_SENTINEL in spans[1].annotation.attributes), "Cursor can't be between two line sentinel characters."); - attributes = spans[1].annotation.attributes; - } - for(var attr in attributes) { - // Don't copy line or entity attributes. - if (attr !== 'l' && attr !== 'lt' && attr !== 'li' && attr.indexOf(ATTR.ENTITY_SENTINEL) !== 0) { - this.currentAttributes_[attr] = attributes[attr]; - } - } - }; - - RichTextCodeMirror.prototype.getCurrentLineAttributes_ = function() { - var cm = this.codeMirror; - var anchor = cm.getCursor('anchor'), head = cm.getCursor('head'); - var line = head.line; - // If it's a forward selection and the cursor is at the beginning of a line, use the previous line. - if (head.ch === 0 && anchor.line < head.line) { - line--; - } - return this.getLineAttributes_(line); - }; - - RichTextCodeMirror.prototype.getLineAttributes_ = function(lineNum) { - var attributes = {}; - var line = this.codeMirror.getLine(lineNum); - if (line.length > 0 && line[0] === LineSentinelCharacter) { - var lineStartIndex = this.codeMirror.indexFromPos({ line: lineNum, ch: 0 }); - var spans = this.annotationList_.getAnnotatedSpansForSpan(new Span(lineStartIndex, 1)); - firepad.utils.assert(spans.length === 1); - for(var attr in spans[0].annotation.attributes) { - attributes[attr] = spans[0].annotation.attributes[attr]; - } - } - return attributes; - }; - - RichTextCodeMirror.prototype.clearAnnotations_ = function() { - this.annotationList_.updateSpan(new Span(0, this.end()), function(annotation, length) { - return new RichTextAnnotation({ }); - }); - }; - - RichTextCodeMirror.prototype.newline = function() { - var cm = this.codeMirror; - var self = this; - if (!this.emptySelection_()) { - cm.replaceSelection('\n', 'end', '+input'); - } else { - var cursorLine = cm.getCursor('head').line; - var lineAttributes = this.getLineAttributes_(cursorLine); - var listType = lineAttributes[ATTR.LIST_TYPE]; - - if (listType && cm.getLine(cursorLine).length === 1) { - // They hit enter on a line with just a list heading. Just remove the list heading. - this.updateLineAttributes(cursorLine, cursorLine, function(attributes) { - delete attributes[ATTR.LIST_TYPE]; - delete attributes[ATTR.LINE_INDENT]; - }); - } else { - cm.replaceSelection('\n', 'end', '+input'); - - // Copy line attributes forward. - this.updateLineAttributes(cursorLine+1, cursorLine+1, function(attributes) { - for(var attr in lineAttributes) { - attributes[attr] = lineAttributes[attr]; - } - - // Don't mark new todo items as completed. - if (listType === 'tc') attributes[ATTR.LIST_TYPE] = 't'; - self.trigger('newLine', {line: cursorLine+1, attr: attributes}); - }); - } - } - }; - - RichTextCodeMirror.prototype.deleteLeft = function() { - var cm = this.codeMirror; - var cursorPos = cm.getCursor('head'); - var lineAttributes = this.getLineAttributes_(cursorPos.line); - var listType = lineAttributes[ATTR.LIST_TYPE]; - var indent = lineAttributes[ATTR.LINE_INDENT]; - - var backspaceAtStartOfLine = this.emptySelection_() && cursorPos.ch === 1; - - if (backspaceAtStartOfLine && listType) { - // They hit backspace at the beginning of a line with a list heading. Just remove the list heading. - this.updateLineAttributes(cursorPos.line, cursorPos.line, function(attributes) { - delete attributes[ATTR.LIST_TYPE]; - delete attributes[ATTR.LINE_INDENT]; - }); - } else if (backspaceAtStartOfLine && indent && indent > 0) { - this.unindent(); - } else { - cm.deleteH(-1, "char"); - } - }; - - RichTextCodeMirror.prototype.deleteRight = function() { - var cm = this.codeMirror; - var cursorPos = cm.getCursor('head'); - - var text = cm.getLine(cursorPos.line); - var emptyLine = this.areLineSentinelCharacters_(text); - var nextLineText = (cursorPos.line + 1 < cm.lineCount()) ? cm.getLine(cursorPos.line + 1) : ""; - if (this.emptySelection_() && emptyLine && nextLineText[0] === LineSentinelCharacter) { - // Delete the empty line but not the line sentinel character on the next line. - cm.replaceRange('', { line: cursorPos.line, ch: 0 }, { line: cursorPos.line + 1, ch: 0}, '+input'); - - // HACK: Once we've deleted this line, the cursor will be between the newline on the previous - // line and the line sentinel character on the next line, which is an invalid position. - // CodeMirror tends to therefore move it to the end of the previous line, which is undesired. - // So we explicitly set it to ch: 0 on the current line, which seems to move it after the line - // sentinel character(s) as desired. - // (see https://github.com/firebase/firepad/issues/209). - cm.setCursor({ line: cursorPos.line, ch: 0 }); - } else { - cm.deleteH(1, "char"); - } - }; - - RichTextCodeMirror.prototype.indent = function() { - this.updateLineAttributesForSelection(function(attributes) { - var indent = attributes[ATTR.LINE_INDENT]; - var listType = attributes[ATTR.LIST_TYPE]; - - if (indent) { - attributes[ATTR.LINE_INDENT]++; - } else if (listType) { - // lists are implicitly already indented once. - attributes[ATTR.LINE_INDENT] = 2; - } else { - attributes[ATTR.LINE_INDENT] = 1; - } - }); - }; - - RichTextCodeMirror.prototype.unindent = function() { - this.updateLineAttributesForSelection(function(attributes) { - var indent = attributes[ATTR.LINE_INDENT]; - - if (indent && indent > 1) { - attributes[ATTR.LINE_INDENT] = indent - 1; - } else { - delete attributes[ATTR.LIST_TYPE]; - delete attributes[ATTR.LINE_INDENT]; - } - }); - }; - - RichTextCodeMirror.prototype.getText = function() { - return this.codeMirror.getValue().replace(new RegExp(LineSentinelCharacter, "g"), ''); - }; - - RichTextCodeMirror.prototype.areLineSentinelCharacters_ = function(text) { - for(var i = 0; i < text.length; i++) { - if (text[i] !== LineSentinelCharacter) - return false; - } - return true; - }; - - /** - * Used for the annotations we store in our AnnotationList. - * @param attributes - * @constructor - */ - function RichTextAnnotation(attributes) { - this.attributes = attributes || { }; - } - - RichTextAnnotation.prototype.equals = function(other) { - if (!(other instanceof RichTextAnnotation)) { - return false; - } - var attr; - for(attr in this.attributes) { - if (other.attributes[attr] !== this.attributes[attr]) { - return false; - } - } - - for(attr in other.attributes) { - if (other.attributes[attr] !== this.attributes[attr]) { - return false; - } - } - - return true; - }; - - function emptyAttributes(attributes) { - for(var attr in attributes) { - return false; - } - return true; - } - - // Bind a method to an object, so it doesn't matter whether you call - // object.method() directly or pass object.method as a reference to another - // function. - function bind (obj, method) { - var fn = obj[method]; - obj[method] = function () { - fn.apply(obj, arguments); - }; - } - - return RichTextCodeMirror; -})(); - -var firepad = firepad || { }; - -// TODO: Can this derive from CodeMirrorAdapter or similar? -firepad.RichTextCodeMirrorAdapter = (function () { - 'use strict'; - - var TextOperation = firepad.TextOperation; - var WrappedOperation = firepad.WrappedOperation; - var Cursor = firepad.Cursor; - - function RichTextCodeMirrorAdapter (rtcm) { - this.rtcm = rtcm; - this.cm = rtcm.codeMirror; - - bind(this, 'onChange'); - bind(this, 'onAttributesChange'); - bind(this, 'onCursorActivity'); - bind(this, 'onFocus'); - bind(this, 'onBlur'); - - this.rtcm.on('change', this.onChange); - this.rtcm.on('attributesChange', this.onAttributesChange); - this.cm.on('cursorActivity', this.onCursorActivity); - this.cm.on('focus', this.onFocus); - this.cm.on('blur', this.onBlur); - } - - // Removes all event listeners from the CodeMirror instance. - RichTextCodeMirrorAdapter.prototype.detach = function () { - this.rtcm.off('change', this.onChange); - this.rtcm.off('attributesChange', this.onAttributesChange); - - this.cm.off('cursorActivity', this.onCursorActivity); - this.cm.off('focus', this.onFocus); - this.cm.off('blur', this.onBlur); - }; - - function cmpPos (a, b) { - if (a.line < b.line) { return -1; } - if (a.line > b.line) { return 1; } - if (a.ch < b.ch) { return -1; } - if (a.ch > b.ch) { return 1; } - return 0; - } - function posEq (a, b) { return cmpPos(a, b) === 0; } - function posLe (a, b) { return cmpPos(a, b) <= 0; } - - function codemirrorLength (cm) { - var lastLine = cm.lineCount() - 1; - return cm.indexFromPos({line: lastLine, ch: cm.getLine(lastLine).length}); - } - - // Converts a CodeMirror change object into a TextOperation and its inverse - // and returns them as a two-element array. - RichTextCodeMirrorAdapter.operationFromCodeMirrorChanges = function (changes, cm) { - // Approach: Replay the changes, beginning with the most recent one, and - // construct the operation and its inverse. We have to convert the position - // in the pre-change coordinate system to an index. We have a method to - // convert a position in the coordinate system after all changes to an index, - // namely CodeMirror's `indexFromPos` method. We can use the information of - // a single change object to convert a post-change coordinate system to a - // pre-change coordinate system. We can now proceed inductively to get a - // pre-change coordinate system for all changes in the linked list. - // A disadvantage of this approach is its complexity `O(n^2)` in the length - // of the linked list of changes. - - var docEndLength = codemirrorLength(cm); - var operation = new TextOperation().retain(docEndLength); - var inverse = new TextOperation().retain(docEndLength); - - for (var i = changes.length - 1; i >= 0; i--) { - var change = changes[i]; - var fromIndex = change.start; - var restLength = docEndLength - fromIndex - change.text.length; - - operation = new TextOperation() - .retain(fromIndex) - ['delete'](change.removed.length) - .insert(change.text, change.attributes) - .retain(restLength) - .compose(operation); - - inverse = inverse.compose(new TextOperation() - .retain(fromIndex) - ['delete'](change.text.length) - .insert(change.removed, change.removedAttributes) - .retain(restLength) - ); - - docEndLength += change.removed.length - change.text.length; - } - - return [operation, inverse]; - }; - - // Converts an attributes changed object to an operation and its inverse. - RichTextCodeMirrorAdapter.operationFromAttributesChanges = function (changes, cm) { - var docEndLength = codemirrorLength(cm); - - var operation = new TextOperation(), inverse = new TextOperation(); - var pos = 0; - - for (var i = 0; i < changes.length; i++) { - var change = changes[i]; - var toRetain = change.start - pos; - assert(toRetain >= 0); // changes should be in order and non-overlapping. - operation.retain(toRetain); - inverse.retain(toRetain); - - var length = change.end - change.start; - operation.retain(length, change.attributes); - inverse.retain(length, change.attributesInverse); - pos = change.start + length; - } - - operation.retain(docEndLength - pos); - inverse.retain(docEndLength - pos); - - return [operation, inverse]; - }; - - RichTextCodeMirrorAdapter.prototype.registerCallbacks = function (cb) { - this.callbacks = cb; - }; - - RichTextCodeMirrorAdapter.prototype.onChange = function (_, changes) { - if (changes[0].origin !== 'RTCMADAPTER') { - var pair = RichTextCodeMirrorAdapter.operationFromCodeMirrorChanges(changes, this.cm); - this.trigger('change', pair[0], pair[1]); - } - }; - - RichTextCodeMirrorAdapter.prototype.onAttributesChange = function (_, changes) { - if (changes[0].origin !== 'RTCMADAPTER') { - var pair = RichTextCodeMirrorAdapter.operationFromAttributesChanges(changes, this.cm); - this.trigger('change', pair[0], pair[1]); - } - }; - - RichTextCodeMirrorAdapter.prototype.onCursorActivity = function () { - // We want to push cursor changes to Firebase AFTER edits to the history, - // because the cursor coordinates will already be in post-change units. - // Sleeping for 1ms ensures that sendCursor happens after sendOperation. - var self = this; - setTimeout(function() { - self.trigger('cursorActivity'); - }, 1); - } - - RichTextCodeMirrorAdapter.prototype.onFocus = function () { - this.trigger('focus'); - }; - - RichTextCodeMirrorAdapter.prototype.onBlur = function () { - if (!this.cm.somethingSelected()) { this.trigger('blur'); } - }; - - RichTextCodeMirrorAdapter.prototype.getValue = function () { - return this.cm.getValue(); - }; - - RichTextCodeMirrorAdapter.prototype.getCursor = function () { - var cm = this.cm; - var cursorPos = cm.getCursor(); - var position = cm.indexFromPos(cursorPos); - var selectionEnd; - if (cm.somethingSelected()) { - var startPos = cm.getCursor(true); - var selectionEndPos = posEq(cursorPos, startPos) ? cm.getCursor(false) : startPos; - selectionEnd = cm.indexFromPos(selectionEndPos); - } else { - selectionEnd = position; - } - - return new Cursor(position, selectionEnd); - }; - - RichTextCodeMirrorAdapter.prototype.setCursor = function (cursor) { - this.cm.setSelection( - this.cm.posFromIndex(cursor.position), - this.cm.posFromIndex(cursor.selectionEnd) - ); - }; - - RichTextCodeMirrorAdapter.prototype.addStyleRule = function(css) { - if (typeof document === "undefined" || document === null) { - return; - } - if (!this.addedStyleRules) { - this.addedStyleRules = {}; - var styleElement = document.createElement('style'); - document.documentElement.getElementsByTagName('head')[0].appendChild(styleElement); - this.addedStyleSheet = styleElement.sheet; - } - if (this.addedStyleRules[css]) { - return; - } - this.addedStyleRules[css] = true; - return this.addedStyleSheet.insertRule(css, 0); - }; - - RichTextCodeMirrorAdapter.prototype.setOtherCursor = function (cursor, color, clientId) { - var cursorPos = this.cm.posFromIndex(cursor.position); - if (typeof color !== 'string' || !color.match(/^#[a-fA-F0-9]{3,6}$/)) { - return; - } - var end = this.rtcm.end(); - if (typeof cursor !== 'object' || typeof cursor.position !== 'number' || typeof cursor.selectionEnd !== 'number') { - return; - } - if (cursor.position < 0 || cursor.position > end || cursor.selectionEnd < 0 || cursor.selectionEnd > end) { - return; - } - - if (cursor.position === cursor.selectionEnd) { - // show cursor - var cursorCoords = this.cm.cursorCoords(cursorPos); - var cursorEl = document.createElement('span'); - cursorEl.className = 'other-client'; - cursorEl.style.borderLeftWidth = '2px'; - cursorEl.style.borderLeftStyle = 'solid'; - cursorEl.style.borderLeftColor = color; - cursorEl.style.marginLeft = cursorEl.style.marginRight = '-1px'; - cursorEl.style.height = (cursorCoords.bottom - cursorCoords.top) * 0.9 + 'px'; - cursorEl.setAttribute('data-clientid', clientId); - cursorEl.style.zIndex = 0; - - return this.cm.setBookmark(cursorPos, { widget: cursorEl, insertLeft: true }); - } else { - // show selection - var selectionClassName = 'selection-' + color.replace('#', ''); - var transparency = 0.4; - var rule = '.' + selectionClassName + ' {' + - // fallback for browsers w/out rgba (rgb w/ transparency) - ' background: ' + hex2rgb(color) + ';\n' + - // rule with alpha takes precedence if supported - ' background: ' + hex2rgb(color, transparency) + ';' + - '}'; - this.addStyleRule(rule); - - var fromPos, toPos; - if (cursor.selectionEnd > cursor.position) { - fromPos = cursorPos; - toPos = this.cm.posFromIndex(cursor.selectionEnd); - } else { - fromPos = this.cm.posFromIndex(cursor.selectionEnd); - toPos = cursorPos; - } - return this.cm.markText(fromPos, toPos, { - className: selectionClassName - }); - } - }; - - RichTextCodeMirrorAdapter.prototype.trigger = function (event) { - var args = Array.prototype.slice.call(arguments, 1); - var action = this.callbacks && this.callbacks[event]; - if (action) { action.apply(this, args); } - }; - - // Apply an operation to a CodeMirror instance. - RichTextCodeMirrorAdapter.prototype.applyOperation = function (operation) { - // HACK: If there are a lot of operations; hide CodeMirror so that it doesn't re-render constantly. - if (operation.ops.length > 10) - this.rtcm.codeMirror.getWrapperElement().setAttribute('style', 'display: none'); - - var ops = operation.ops; - var index = 0; // holds the current index into CodeMirror's content - for (var i = 0, l = ops.length; i < l; i++) { - var op = ops[i]; - if (op.isRetain()) { - if (!emptyAttributes(op.attributes)) { - this.rtcm.updateTextAttributes(index, index + op.chars, function(attributes) { - for(var attr in op.attributes) { - if (op.attributes[attr] === false) { - delete attributes[attr]; - } else { - attributes[attr] = op.attributes[attr]; - } - } - }, 'RTCMADAPTER', /*doLineAttributes=*/true); - } - index += op.chars; - } else if (op.isInsert()) { - this.rtcm.insertText(index, op.text, op.attributes, 'RTCMADAPTER'); - index += op.text.length; - } else if (op.isDelete()) { - this.rtcm.removeText(index, index + op.chars, 'RTCMADAPTER'); - } - } - - if (operation.ops.length > 10) { - this.rtcm.codeMirror.getWrapperElement().setAttribute('style', ''); - this.rtcm.codeMirror.refresh(); - } - }; - - RichTextCodeMirrorAdapter.prototype.registerUndo = function (undoFn) { - this.cm.undo = undoFn; - }; - - RichTextCodeMirrorAdapter.prototype.registerRedo = function (redoFn) { - this.cm.redo = redoFn; - }; - - RichTextCodeMirrorAdapter.prototype.invertOperation = function(operation) { - var pos = 0, cm = this.rtcm.codeMirror, spans, i; - var inverse = new TextOperation(); - for(var opIndex = 0; opIndex < operation.wrapped.ops.length; opIndex++) { - var op = operation.wrapped.ops[opIndex]; - if (op.isRetain()) { - if (emptyAttributes(op.attributes)) { - inverse.retain(op.chars); - pos += op.chars; - } else { - spans = this.rtcm.getAttributeSpans(pos, pos + op.chars); - for(i = 0; i < spans.length; i++) { - var inverseAttributes = { }; - for(var attr in op.attributes) { - var opValue = op.attributes[attr]; - var curValue = spans[i].attributes[attr]; - - if (opValue === false) { - if (curValue) { - inverseAttributes[attr] = curValue; - } - } else if (opValue !== curValue) { - inverseAttributes[attr] = curValue || false; - } - } - - inverse.retain(spans[i].length, inverseAttributes); - pos += spans[i].length; - } - } - } else if (op.isInsert()) { - inverse['delete'](op.text.length); - } else if (op.isDelete()) { - var text = cm.getRange(cm.posFromIndex(pos), cm.posFromIndex(pos + op.chars)); - - spans = this.rtcm.getAttributeSpans(pos, pos + op.chars); - var delTextPos = 0; - for(i = 0; i < spans.length; i++) { - inverse.insert(text.substr(delTextPos, spans[i].length), spans[i].attributes); - delTextPos += spans[i].length; - } - - pos += op.chars; - } - } - - return new WrappedOperation(inverse, operation.meta.invert()); - }; - - // Throws an error if the first argument is falsy. Useful for debugging. - function assert (b, msg) { - if (!b) { - throw new Error(msg || "assertion error"); - } - } - - // Bind a method to an object, so it doesn't matter whether you call - // object.method() directly or pass object.method as a reference to another - // function. - function bind (obj, method) { - var fn = obj[method]; - obj[method] = function () { - fn.apply(obj, arguments); - }; - } - - function emptyAttributes(attrs) { - for(var attr in attrs) { - return false; - } - return true; - } - - function hex2rgb (hex, transparency) { - if (typeof hex !== 'string') { - throw new TypeError('Expected a string'); - } - hex = hex.replace(/^#/, ''); - if (hex.length === 3) { - hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2]; - } - var num = parseInt(hex, 16); - var rgb = [num >> 16, num >> 8 & 255, num & 255]; - var type = 'rgb'; - if (exists(transparency)) { - type = 'rgba'; - rgb.push(transparency); - } - // rgb(r, g, b) or rgba(r, g, b, t) - return type + '(' + rgb.join(',') + ')'; - } - - function exists (val) { - return val !== null && val !== undefined; - } - - return RichTextCodeMirrorAdapter; -}()); - -var firepad = firepad || { }; - -/** - * Immutable object to represent text formatting. Formatting can be modified by chaining method calls. - * - * @constructor - * @type {Function} - */ -firepad.Formatting = (function() { - var ATTR = firepad.AttributeConstants; - - function Formatting(attributes) { - // Allow calling without new. - if (!(this instanceof Formatting)) { return new Formatting(attributes); } - - this.attributes = attributes || { }; - } - - Formatting.prototype.cloneWithNewAttribute_ = function(attribute, value) { - var attributes = { }; - - // Copy existing. - for(var attr in this.attributes) { - attributes[attr] = this.attributes[attr]; - } - - // Add new one. - if (value === false) { - delete attributes[attribute]; - } else { - attributes[attribute] = value; - } - - return new Formatting(attributes); - }; - - Formatting.prototype.bold = function(val) { - return this.cloneWithNewAttribute_(ATTR.BOLD, val); - }; - - Formatting.prototype.italic = function(val) { - return this.cloneWithNewAttribute_(ATTR.ITALIC, val); - }; - - Formatting.prototype.underline = function(val) { - return this.cloneWithNewAttribute_(ATTR.UNDERLINE, val); - }; - - Formatting.prototype.strike = function(val) { - return this.cloneWithNewAttribute_(ATTR.STRIKE, val); - }; - - Formatting.prototype.font = function(font) { - return this.cloneWithNewAttribute_(ATTR.FONT, font); - }; - - Formatting.prototype.fontSize = function(size) { - return this.cloneWithNewAttribute_(ATTR.FONT_SIZE, size); - }; - - Formatting.prototype.color = function(color) { - return this.cloneWithNewAttribute_(ATTR.COLOR, color); - }; - - Formatting.prototype.backgroundColor = function(color) { - return this.cloneWithNewAttribute_(ATTR.BACKGROUND_COLOR, color); - }; - - return Formatting; -})(); - -var firepad = firepad || { }; - -/** - * Object to represent Formatted text. - * - * @type {Function} - */ -firepad.Text = (function() { - function Text(text, formatting) { - // Allow calling without new. - if (!(this instanceof Text)) { return new Text(text, formatting); } - - this.text = text; - this.formatting = formatting || firepad.Formatting(); - } - - return Text; -})(); - -var firepad = firepad || { }; - -/** - * Immutable object to represent line formatting. Formatting can be modified by chaining method calls. - * - * @constructor - * @type {Function} - */ -firepad.LineFormatting = (function() { - var ATTR = firepad.AttributeConstants; - - function LineFormatting(attributes) { - // Allow calling without new. - if (!(this instanceof LineFormatting)) { return new LineFormatting(attributes); } - - this.attributes = attributes || { }; - this.attributes[ATTR.LINE_SENTINEL] = true; - } - - LineFormatting.LIST_TYPE = { - NONE: false, - ORDERED: 'o', - UNORDERED: 'u', - TODO: 't', - TODOCHECKED: 'tc' - }; - - LineFormatting.prototype.cloneWithNewAttribute_ = function(attribute, value) { - var attributes = { }; - - // Copy existing. - for(var attr in this.attributes) { - attributes[attr] = this.attributes[attr]; - } - - // Add new one. - if (value === false) { - delete attributes[attribute]; - } else { - attributes[attribute] = value; - } - - return new LineFormatting(attributes); - }; - - LineFormatting.prototype.indent = function(indent) { - return this.cloneWithNewAttribute_(ATTR.LINE_INDENT, indent); - }; - - LineFormatting.prototype.align = function(align) { - return this.cloneWithNewAttribute_(ATTR.LINE_ALIGN, align); - }; - - LineFormatting.prototype.listItem = function(val) { - firepad.utils.assert(val === false || val === 'u' || val === 'o' || val === 't' || val === 'tc'); - return this.cloneWithNewAttribute_(ATTR.LIST_TYPE, val); - }; - - LineFormatting.prototype.getIndent = function() { - return this.attributes[ATTR.LINE_INDENT] || 0; - }; - - LineFormatting.prototype.getAlign = function() { - return this.attributes[ATTR.LINE_ALIGN] || 0; - }; - - LineFormatting.prototype.getListItem = function() { - return this.attributes[ATTR.LIST_TYPE] || false; - }; - - return LineFormatting; -})(); - -var firepad = firepad || { }; - -/** - * Object to represent Formatted line. - * - * @type {Function} - */ -firepad.Line = (function() { - function Line(textPieces, formatting) { - // Allow calling without new. - if (!(this instanceof Line)) { return new Line(textPieces, formatting); } - - if(Object.prototype.toString.call(textPieces) !== '[object Array]') { - if (typeof textPieces === 'undefined') { - textPieces = []; - } else { - textPieces = [textPieces]; - } - } - - this.textPieces = textPieces; - this.formatting = formatting || firepad.LineFormatting(); - } - - return Line; -})(); - -var firepad = firepad || { }; - -/** - * Helper to parse html into Firepad-compatible lines / text. - * @type {*} - */ -firepad.ParseHtml = (function () { - var LIST_TYPE = firepad.LineFormatting.LIST_TYPE; - - /** - * Represents the current parse state as an immutable structure. To create a new ParseState, use - * the withXXX methods. - * - * @param opt_listType - * @param opt_lineFormatting - * @param opt_textFormatting - * @constructor - */ - function ParseState(opt_listType, opt_lineFormatting, opt_textFormatting) { - this.listType = opt_listType || LIST_TYPE.UNORDERED; - this.lineFormatting = opt_lineFormatting || firepad.LineFormatting(); - this.textFormatting = opt_textFormatting || firepad.Formatting(); - } - - ParseState.prototype.withTextFormatting = function(textFormatting) { - return new ParseState(this.listType, this.lineFormatting, textFormatting); - }; - - ParseState.prototype.withLineFormatting = function(lineFormatting) { - return new ParseState(this.listType, lineFormatting, this.textFormatting); - }; - - ParseState.prototype.withListType = function(listType) { - return new ParseState(listType, this.lineFormatting, this.textFormatting); - }; - - ParseState.prototype.withIncreasedIndent = function() { - var lineFormatting = this.lineFormatting.indent(this.lineFormatting.getIndent() + 1); - return new ParseState(this.listType, lineFormatting, this.textFormatting); - }; - - ParseState.prototype.withAlign = function(align) { - var lineFormatting = this.lineFormatting.align(align); - return new ParseState(this.listType, lineFormatting, this.textFormatting); - }; - - /** - * Mutable structure representing the current parse output. - * @constructor - */ - function ParseOutput() { - this.lines = [ ]; - this.currentLine = []; - this.currentLineListItemType = null; - } - - ParseOutput.prototype.newlineIfNonEmpty = function(state) { - this.cleanLine_(); - if (this.currentLine.length > 0) { - this.newline(state); - } - }; - - ParseOutput.prototype.newlineIfNonEmptyOrListItem = function(state) { - this.cleanLine_(); - if (this.currentLine.length > 0 || this.currentLineListItemType !== null) { - this.newline(state); - } - }; - - ParseOutput.prototype.newline = function(state) { - this.cleanLine_(); - var lineFormatting = state.lineFormatting; - if (this.currentLineListItemType !== null) { - lineFormatting = lineFormatting.listItem(this.currentLineListItemType); - this.currentLineListItemType = null; - } - - this.lines.push(firepad.Line(this.currentLine, lineFormatting)); - this.currentLine = []; - }; - - ParseOutput.prototype.makeListItem = function(type) { - this.currentLineListItemType = type; - }; - - ParseOutput.prototype.cleanLine_ = function() { - // Kinda' a hack, but we remove leading and trailing spaces (since these aren't significant in html) and - // replaces nbsp's with normal spaces. - if (this.currentLine.length > 0) { - var last = this.currentLine.length - 1; - this.currentLine[0].text = this.currentLine[0].text.replace(/^ +/, ''); - this.currentLine[last].text = this.currentLine[last].text.replace(/ +$/g, ''); - for(var i = 0; i < this.currentLine.length; i++) { - this.currentLine[i].text = this.currentLine[i].text.replace(/\u00a0/g, ' '); - } - } - // If after stripping trailing whitespace, there's nothing left, clear currentLine out. - if (this.currentLine.length === 1 && this.currentLine[0].text === '') { - this.currentLine = []; - } - }; - - var entityManager_; - function parseHtml(html, entityManager) { - // Create DIV with HTML (as a convenient way to parse it). - var div = (firepad.document || document).createElement('div'); - div.innerHTML = html; - - // HACK until I refactor this. - entityManager_ = entityManager; - - var output = new ParseOutput(); - var state = new ParseState(); - parseNode(div, state, output); - - return output.lines; - } - - // Fix IE8. - var Node = Node || { - ELEMENT_NODE: 1, - TEXT_NODE: 3 - }; - - function parseNode(node, state, output) { - // Give entity manager first crack at it. - if (node.nodeType === Node.ELEMENT_NODE) { - var entity = entityManager_.fromElement(node); - if (entity) { - output.currentLine.push(new firepad.Text( - firepad.sentinelConstants.ENTITY_SENTINEL_CHARACTER, - new firepad.Formatting(entity.toAttributes()) - )); - return; - } - } - - switch (node.nodeType) { - case Node.TEXT_NODE: - // This probably isn't exactly right, but mostly works... - var text = node.nodeValue.replace(/[ \n\t]+/g, ' '); - output.currentLine.push(firepad.Text(text, state.textFormatting)); - break; - case Node.ELEMENT_NODE: - var style = node.getAttribute('style') || ''; - state = parseStyle(state, style); - switch (node.nodeName.toLowerCase()) { - case 'div': - case 'h1': - case 'h2': - case 'h3': - case 'p': - output.newlineIfNonEmpty(state); - parseChildren(node, state, output); - output.newlineIfNonEmpty(state); - break; - case 'center': - state = state.withAlign('center'); - output.newlineIfNonEmpty(state); - parseChildren(node, state.withAlign('center'), output); - output.newlineIfNonEmpty(state); - break; - case 'b': - case 'strong': - parseChildren(node, state.withTextFormatting(state.textFormatting.bold(true)), output); - break; - case 'u': - parseChildren(node, state.withTextFormatting(state.textFormatting.underline(true)), output); - break; - case 'i': - case 'em': - parseChildren(node, state.withTextFormatting(state.textFormatting.italic(true)), output); - break; - case 's': - parseChildren(node, state.withTextFormatting(state.textFormatting.strike(true)), output); - break; - case 'font': - var face = node.getAttribute('face'); - var color = node.getAttribute('color'); - var size = parseInt(node.getAttribute('size')); - if (face) { state = state.withTextFormatting(state.textFormatting.font(face)); } - if (color) { state = state.withTextFormatting(state.textFormatting.color(color)); } - if (size) { state = state.withTextFormatting(state.textFormatting.fontSize(size)); } - parseChildren(node, state, output); - break; - case 'br': - output.newline(state); - break; - case 'ul': - output.newlineIfNonEmptyOrListItem(state); - var listType = node.getAttribute('class') === 'firepad-todo' ? LIST_TYPE.TODO : LIST_TYPE.UNORDERED; - parseChildren(node, state.withListType(listType).withIncreasedIndent(), output); - output.newlineIfNonEmpty(state); - break; - case 'ol': - output.newlineIfNonEmptyOrListItem(state); - parseChildren(node, state.withListType(LIST_TYPE.ORDERED).withIncreasedIndent(), output); - output.newlineIfNonEmpty(state); - break; - case 'li': - parseListItem(node, state, output); - break; - case 'style': // ignore. - break; - default: - parseChildren(node, state, output); - break; - } - break; - default: - // Ignore other nodes (comments, etc.) - break; - } - } - - function parseChildren(node, state, output) { - if (node.hasChildNodes()) { - for(var i = 0; i < node.childNodes.length; i++) { - parseNode(node.childNodes[i], state, output); - } - } - } - - function parseListItem(node, state, output) { - // Note:
  • is weird: - // * Only the first line in the
  • tag should be a list item (i.e. with a bullet or number next to it). - // *
  • should create an empty list item line;
  • should create two. - - output.newlineIfNonEmptyOrListItem(state); - - var listType = (node.getAttribute('class') === 'firepad-checked') ? LIST_TYPE.TODOCHECKED : state.listType; - output.makeListItem(listType); - var oldLine = output.currentLine; - - parseChildren(node, state, output); - - if (oldLine === output.currentLine || output.currentLine.length > 0) { - output.newline(state); - } - } - - function parseStyle(state, styleString) { - var textFormatting = state.textFormatting; - var lineFormatting = state.lineFormatting; - var styles = styleString.split(';'); - for(var i = 0; i < styles.length; i++) { - var stylePieces = styles[i].split(':'); - if (stylePieces.length !== 2) - continue; - var prop = firepad.utils.trim(stylePieces[0]).toLowerCase(); - var val = firepad.utils.trim(stylePieces[1]).toLowerCase(); - switch (prop) { - case 'text-decoration': - var underline = val.indexOf('underline') >= 0; - var strike = val.indexOf('line-through') >= 0; - textFormatting = textFormatting.underline(underline).strike(strike); - break; - case 'font-weight': - var bold = (val === 'bold') || parseInt(val) >= 600; - textFormatting = textFormatting.bold(bold); - break; - case 'font-style': - var italic = (val === 'italic' || val === 'oblique'); - textFormatting = textFormatting.italic(italic); - break; - case 'color': - textFormatting = textFormatting.color(val); - break; - case 'background-color': - textFormatting = textFormatting.backgroundColor(val); - break; - case 'text-align': - lineFormatting = lineFormatting.align(val); - break; - case 'font-size': - var size = null; - var allowedValues = ['px','pt','%','em','xx-small','x-small','small','medium','large','x-large','xx-large','smaller','larger']; - if (firepad.utils.stringEndsWith(val, allowedValues)) { - size = val; - } - else if (parseInt(val)) { - size = parseInt(val)+'px'; - } - if (size) { - textFormatting = textFormatting.fontSize(size); - } - break; - case 'font-family': - var font = firepad.utils.trim(val.split(',')[0]); // get first font. - font = font.replace(/['"]/g, ''); // remove quotes. - font = font.replace(/\w\S*/g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase() }); - textFormatting = textFormatting.font(font); - break; - } - } - return state.withLineFormatting(lineFormatting).withTextFormatting(textFormatting); - } - - return parseHtml; -})(); - -var firepad = firepad || { }; - -/** - * Helper to turn Firebase contents into HMTL. - * Takes a doc and an entity manager - */ -firepad.SerializeHtml = (function () { - - var utils = firepad.utils; - var ATTR = firepad.AttributeConstants; - var LIST_TYPE = firepad.LineFormatting.LIST_TYPE; - var TODO_STYLE = '\n'; - - function open(listType) { - return (listType === LIST_TYPE.ORDERED) ? '
      ' : - (listType === LIST_TYPE.UNORDERED) ? '
        ' : - '
          '; - } - - function close(listType) { - return (listType === LIST_TYPE.ORDERED) ? '
    ' : ''; - } - - function compatibleListType(l1, l2) { - return (l1 === l2) || - (l1 === LIST_TYPE.TODO && l2 === LIST_TYPE.TODOCHECKED) || - (l1 === LIST_TYPE.TODOCHECKED && l2 === LIST_TYPE.TODO); - } - - function textToHtml(text) { - return text.replace(/&/g, '&') - .replace(/"/g, '"') - .replace(/'/g, ''') - .replace(//g, '>') - .replace(/\u00a0/g, ' ') - } - - function serializeHtml(doc, entityManager) { - var html = ''; - var newLine = true; - var listTypeStack = []; - var inListItem = false; - var firstLine = true; - var emptyLine = true; - var i = 0, op = doc.ops[i]; - var usesTodo = false; - while(op) { - utils.assert(op.isInsert()); - var attrs = op.attributes; - - if (newLine) { - newLine = false; - - var indent = 0, listType = null, lineAlign = 'left'; - if (ATTR.LINE_SENTINEL in attrs) { - indent = attrs[ATTR.LINE_INDENT] || 0; - listType = attrs[ATTR.LIST_TYPE] || null; - lineAlign = attrs[ATTR.LINE_ALIGN] || 'left'; - } - if (listType) { - indent = indent || 1; // lists are automatically indented at least 1. - } - - if (inListItem) { - html += ''; - inListItem = false; - } else if (!firstLine) { - if (emptyLine) { - html += '
    '; - } - html += ''; - } - firstLine = false; - - // Close any extra lists. - utils.assert(indent >= 0, "Indent must not be negative."); - while (listTypeStack.length > indent || - (indent === listTypeStack.length && listType !== null && !compatibleListType(listType, listTypeStack[listTypeStack.length - 1]))) { - html += close(listTypeStack.pop()); - } - - // Open any needed lists. - while (listTypeStack.length < indent) { - var toOpen = listType || LIST_TYPE.UNORDERED; // default to unordered lists for indenting non-list-item lines. - usesTodo = listType == LIST_TYPE.TODO || listType == LIST_TYPE.TODOCHECKED || usesTodo; - html += open(toOpen); - listTypeStack.push(toOpen); - } - - var style = (lineAlign !== 'left') ? ' style="text-align:' + lineAlign + '"': ''; - if (listType) { - var clazz = ''; - switch (listType) - { - case LIST_TYPE.TODOCHECKED: - clazz = ' class="firepad-checked"'; - break; - case LIST_TYPE.TODO: - clazz = ' class="firepad-unchecked"'; - break; - } - html += ""; - inListItem = true; - } else { - // start line div. - html += ''; - } - emptyLine = true; - } - - if (ATTR.LINE_SENTINEL in attrs) { - op = doc.ops[++i]; - continue; - } - - if (ATTR.ENTITY_SENTINEL in attrs) { - for(var j = 0; j < op.text.length; j++) { - var entity = firepad.Entity.fromAttributes(attrs); - var element = entityManager.exportToElement(entity); - html += element.outerHTML; - } - - op = doc.ops[++i]; - continue; - } - - var prefix = '', suffix = ''; - for(var attr in attrs) { - var value = attrs[attr]; - var start, end; - if (attr === ATTR.BOLD || attr === ATTR.ITALIC || attr === ATTR.UNDERLINE || attr === ATTR.STRIKE) { - utils.assert(value === true); - start = end = attr; - } else if (attr === ATTR.FONT_SIZE) { - start = 'span style="font-size: ' + value; - start += (typeof value !== "string" || value.indexOf("px", value.length - 2) === -1) ? 'px"' : '"'; - end = 'span'; - } else if (attr === ATTR.FONT) { - start = 'span style="font-family: ' + value + '"'; - end = 'span'; - } else if (attr === ATTR.COLOR) { - start = 'span style="color: ' + value + '"'; - end = 'span'; - } else if (attr === ATTR.BACKGROUND_COLOR) { - start = 'span style="background-color: ' + value + '"'; - end = 'span'; - } - else { - utils.log(false, "Encountered unknown attribute while rendering html: " + attr); - } - if (start) prefix += '<' + start + '>'; - if (end) suffix = '' + suffix; - } - - var text = op.text; - var newLineIndex = text.indexOf('\n'); - if (newLineIndex >= 0) { - newLine = true; - if (newLineIndex < text.length - 1) { - // split op. - op = new firepad.TextOp('insert', text.substr(newLineIndex+1), attrs); - } else { - op = doc.ops[++i]; - } - text = text.substr(0, newLineIndex); - } else { - op = doc.ops[++i]; - } - - // Replace leading, trailing, and consecutive spaces with nbsp's to make sure they're preserved. - text = text.replace(/ +/g, function(str) { - return new Array(str.length + 1).join('\u00a0'); - }).replace(/^ /, '\u00a0').replace(/ $/, '\u00a0'); - if (text.length > 0) { - emptyLine = false; - } - - html += prefix + textToHtml(text) + suffix; - } - - if (inListItem) { - html += ''; - } else if (!firstLine) { - if (emptyLine) { - html += ' '; - } - html += ''; - } - - // Close any extra lists. - while (listTypeStack.length > 0) { - html += close(listTypeStack.pop()); - } - - if (usesTodo) { - html = TODO_STYLE + html; - } - - return html; - } - - return serializeHtml; -})(); - -var firepad = firepad || { }; - -/** - * Helper to turn pieces of text into insertable operations - */ -firepad.textPiecesToInserts = function(atNewLine, textPieces) { - var inserts = []; - - function insert(string, attributes) { - if (string instanceof firepad.Text) { - attributes = string.formatting.attributes; - string = string.text; - } - - inserts.push({string: string, attributes: attributes}); - atNewLine = string[string.length-1] === '\n'; - } - - function insertLine(line, withNewline) { - // HACK: We should probably force a newline if there isn't one already. But due to - // the way this is used for inserting HTML, we end up inserting a "line" in the middle - // of text, in which case we don't want to actually insert a newline. - if (atNewLine) { - insert(firepad.sentinelConstants.LINE_SENTINEL_CHARACTER, line.formatting.attributes); - } - - for(var i = 0; i < line.textPieces.length; i++) { - insert(line.textPieces[i]); - } - - if (withNewline) insert('\n'); - } - - for(var i = 0; i < textPieces.length; i++) { - if (textPieces[i] instanceof firepad.Line) { - insertLine(textPieces[i], i', function(err, window) { - if (firepad.document) { - // Return if we've already made a jsdom to avoid making more than one - // This would be easier with promises but we want to avoid introducing - // another dependency for just headless mode. - window.close(); - return callback(); - } - firepad.document = window.document; - callback(); - }); - } - } - - Headless.prototype.getHtml = function(callback) { - var self = this; - - if (this.zombie_) { - throw new Error('You can\'t use a firepad.Headless after calling dispose()!'); - } - - self.initializeFakeDom(function() { - self.getDocument(function(doc) { - callback(firepad.SerializeHtml(doc, self.entityManager_)); - }); - }); - } - - Headless.prototype.setHtml = function(html, callback) { - var self = this; - - if (this.zombie_) { - throw new Error('You can\'t use a firepad.Headless after calling dispose()!'); - } - - self.initializeFakeDom(function() { - var textPieces = ParseHtml(html, self.entityManager_); - var inserts = firepad.textPiecesToInserts(true, textPieces); - var op = new TextOperation(); - - for (var i = 0; i < inserts.length; i++) { - op.insert(inserts[i].string, inserts[i].attributes); - } - - self.sendOperationWithRetry(op, callback); - }); - } - - Headless.prototype.sendOperationWithRetry = function(operation, callback) { - var self = this; - - self.getDocument(function(doc) { - var op = operation.clone()['delete'](doc.targetLength); - self.firebaseAdapter_.sendOperation(op, function(err, committed) { - if (committed) { - if (typeof callback !== "undefined") { - callback(null, committed); - } - } else { - self.sendOperationWithRetry(operation, callback); - } - }); - }); - } - - Headless.prototype.dispose = function() { - this.zombie_ = true; // We've been disposed. No longer valid to do anything. - - this.firebaseAdapter_.dispose(); - }; - - return Headless; -})(); - -var firepad = firepad || { }; - -firepad.Firepad = (function(global) { - if (!firepad.RichTextCodeMirrorAdapter) { - throw new Error("Oops! It looks like you're trying to include lib/firepad.js directly. This is actually one of many source files that make up firepad. You want dist/firepad.js instead."); - } - var RichTextCodeMirrorAdapter = firepad.RichTextCodeMirrorAdapter; - var RichTextCodeMirror = firepad.RichTextCodeMirror; - var RichTextToolbar = firepad.RichTextToolbar; - var ACEAdapter = firepad.ACEAdapter; - var MonacoAdapter = firepad.MonacoAdapter; - var FirebaseAdapter = firepad.FirebaseAdapter; - var EditorClient = firepad.EditorClient; - var EntityManager = firepad.EntityManager; - var ATTR = firepad.AttributeConstants; - var utils = firepad.utils; - var LIST_TYPE = firepad.LineFormatting.LIST_TYPE; - var CodeMirror = global.CodeMirror; - var ace = global.ace; - var monaco = global.monaco; - - function Firepad(ref, place, options) { - if (!(this instanceof Firepad)) { return new Firepad(ref, place, options); } - - if (!CodeMirror && !ace && !global.monaco) { - throw new Error('Couldn\'t find CodeMirror, ACE or Monaco. Did you forget to include codemirror.js/ace.js or import monaco?'); - } - - this.zombie_ = false; - - if (CodeMirror && place instanceof CodeMirror) { - this.codeMirror_ = this.editor_ = place; - var curValue = this.codeMirror_.getValue(); - if (curValue !== '') { - throw new Error("Can't initialize Firepad with a CodeMirror instance that already contains text."); - } - } else if (ace && place && place.session instanceof ace.EditSession) { - this.ace_ = this.editor_ = place; - curValue = this.ace_.getValue(); - if (curValue !== '') { - throw new Error("Can't initialize Firepad with an ACE instance that already contains text."); - } - } else if (global.monaco && place && place instanceof global.monaco.constructor) { - monaco = global.monaco; - this.monaco_ = this.editor_ = place; - curValue = this.monaco_.getValue(); - if (curValue !== '') { - throw new Error("Can't initialize Firepad with a Monaco instance that already contains text."); - } - } else { - this.codeMirror_ = this.editor_ = new CodeMirror(place); - } - - var editorWrapper; - if (this.codeMirror_) { - editorWrapper = this.codeMirror_.getWrapperElement(); - } else if (this.ace_) { - editorWrapper = this.ace_.container; - } else { - editorWrapper = this.monaco_.getDomNode() - } - - // var editorWrapper = this.codeMirror_ ? this.codeMirror_.getWrapperElement() : this.ace_.container; - this.firepadWrapper_ = utils.elt("div", null, { 'class': 'firepad' }); - editorWrapper.parentNode.replaceChild(this.firepadWrapper_, editorWrapper); - this.firepadWrapper_.appendChild(editorWrapper); - - // Don't allow drag/drop because it causes issues. See https://github.com/firebase/firepad/issues/36 - utils.on(editorWrapper, 'dragstart', utils.stopEvent); - - // Provide an easy way to get the firepad instance associated with this CodeMirror instance. - this.editor_.firepad = this; - - this.options_ = options || { }; - - if (this.getOption('richTextShortcuts', false)) { - if (!CodeMirror.keyMap['richtext']) { - this.initializeKeyMap_(); - } - this.codeMirror_.setOption('keyMap', 'richtext'); - this.firepadWrapper_.className += ' firepad-richtext'; - } - - this.imageInsertionUI = this.getOption('imageInsertionUI', true); - - if (this.getOption('richTextToolbar', false)) { - this.addToolbar_(); - this.firepadWrapper_.className += ' firepad-richtext firepad-with-toolbar'; - } - - this.addPoweredByLogo_(); - - // Now that we've mucked with CodeMirror, refresh it. - if (this.codeMirror_) - this.codeMirror_.refresh(); - - var userId = this.getOption('userId', ref.push().key); - var userColor = this.getOption('userColor', colorFromUserId(userId)); - - this.entityManager_ = new EntityManager(); - - this.firebaseAdapter_ = new FirebaseAdapter(ref, userId, userColor); - if (this.codeMirror_) { - this.richTextCodeMirror_ = new RichTextCodeMirror(this.codeMirror_, this.entityManager_, { cssPrefix: 'firepad-' }); - this.editorAdapter_ = new RichTextCodeMirrorAdapter(this.richTextCodeMirror_); - } else if (this.ace_) { - this.editorAdapter_ = new ACEAdapter(this.ace_); - } else { - this.editorAdapter_ = new MonacoAdapter(this.monaco_); - } - this.client_ = new EditorClient(this.firebaseAdapter_, this.editorAdapter_); - - var self = this; - this.firebaseAdapter_.on('cursor', function() { - self.trigger.apply(self, ['cursor'].concat([].slice.call(arguments))); - }); - - if (this.codeMirror_) { - this.richTextCodeMirror_.on('newLine', function() { - self.trigger.apply(self, ['newLine'].concat([].slice.call(arguments))); - }); - } - - this.firebaseAdapter_.on('ready', function() { - self.ready_ = true; - - if (this.ace_) { - this.editorAdapter_.grabDocumentState(); - } - if (this.monaco_) { - this.editorAdapter_.grabDocumentState(); - } - - var defaultText = self.getOption('defaultText', null); - if (defaultText && self.isHistoryEmpty()) { - self.setText(defaultText); - } - - self.trigger('ready'); - }); - - this.client_.on('synced', function(isSynced) { self.trigger('synced', isSynced)} ); - - // Hack for IE8 to make font icons work more reliably. - // http://stackoverflow.com/questions/9809351/ie8-css-font-face-fonts-only-working-for-before-content-on-over-and-sometimes - if (navigator.appName == 'Microsoft Internet Explorer' && navigator.userAgent.match(/MSIE 8\./)) { - window.onload = function() { - var head = document.getElementsByTagName('head')[0], - style = document.createElement('style'); - style.type = 'text/css'; - style.styleSheet.cssText = ':before,:after{content:none !important;}'; - head.appendChild(style); - setTimeout(function() { - head.removeChild(style); - }, 0); - }; - } - } - utils.makeEventEmitter(Firepad); - - // For readability, these are the primary "constructors", even though right now they're just aliases for Firepad. - Firepad.fromCodeMirror = Firepad; - Firepad.fromACE = Firepad; - Firepad.fromMonaco = Firepad; - - - Firepad.prototype.dispose = function() { - this.zombie_ = true; // We've been disposed. No longer valid to do anything. - - // Unwrap the editor. - // var editorWrapper = this.codeMirror_ ? this.codeMirror_.getWrapperElement() : this.ace_.container; - if (this.codeMirror_) { - editorWrapper = this.codeMirror_.getWrapperElement(); - } else if (this.ace_) { - editorWrapper = this.ace_.container; - } else { - editorWrapper = this.monaco_.getDomNode() - } - - this.firepadWrapper_.removeChild(editorWrapper); - this.firepadWrapper_.parentNode.replaceChild(editorWrapper, this.firepadWrapper_); - - this.editor_.firepad = null; - - if (this.codeMirror_ && this.codeMirror_.getOption('keyMap') === 'richtext') { - this.codeMirror_.setOption('keyMap', 'default'); - } - - this.firebaseAdapter_.dispose(); - this.editorAdapter_.detach(); - if (this.richTextCodeMirror_) - this.richTextCodeMirror_.detach(); - }; - - Firepad.prototype.setUserId = function(userId) { - this.firebaseAdapter_.setUserId(userId); - }; - - Firepad.prototype.setUserColor = function(color) { - this.firebaseAdapter_.setColor(color); - }; - - Firepad.prototype.getText = function() { - this.assertReady_('getText'); - if (this.codeMirror_) - return this.richTextCodeMirror_.getText(); - else if (this.ace_) - return this.ace_.getSession().getDocument().getValue(); - else - return this.monaco_.getModel().getValue(); - }; - - Firepad.prototype.setText = function(textPieces) { - this.assertReady_('setText'); - if (this.monaco_) { - return this.monaco_.getModel().setValue(textPieces); - } else if (this.ace_) { - return this.ace_.getSession().getDocument().setValue(textPieces); - } else { - // HACK: Hide CodeMirror during setText to prevent lots of extra renders. - this.codeMirror_.getWrapperElement().setAttribute('style', 'display: none'); - this.codeMirror_.setValue(""); - this.insertText(0, textPieces); - this.codeMirror_.getWrapperElement().setAttribute('style', ''); - this.codeMirror_.refresh(); - } - this.editorAdapter_.setCursor({position: 0, selectionEnd: 0}); - }; - - Firepad.prototype.insertTextAtCursor = function(textPieces) { - this.insertText(this.codeMirror_.indexFromPos(this.codeMirror_.getCursor()), textPieces); - }; - - Firepad.prototype.insertText = function(index, textPieces) { - utils.assert(!this.ace_, "Not supported for ace yet."); - utils.assert(!this.monaco_, "Not supported for monaco yet."); - this.assertReady_('insertText'); - - // Wrap it in an array if it's not already. - if(Object.prototype.toString.call(textPieces) !== '[object Array]') { - textPieces = [textPieces]; - } - - var self = this; - self.codeMirror_.operation(function() { - // HACK: We should check if we're actually at the beginning of a line; but checking for index == 0 is sufficient - // for the setText() case. - var atNewLine = index === 0; - var inserts = firepad.textPiecesToInserts(atNewLine, textPieces); - - for (var i = 0; i < inserts.length; i++) { - var string = inserts[i].string; - var attributes = inserts[i].attributes; - self.richTextCodeMirror_.insertText(index, string, attributes); - index += string.length; - } - }); - }; - - Firepad.prototype.getOperationForSpan = function(start, end) { - var text = this.richTextCodeMirror_.getRange(start, end); - var spans = this.richTextCodeMirror_.getAttributeSpans(start, end); - var pos = 0; - var op = new firepad.TextOperation(); - for(var i = 0; i < spans.length; i++) { - op.insert(text.substr(pos, spans[i].length), spans[i].attributes); - pos += spans[i].length; - } - return op; - }; - - Firepad.prototype.getHtml = function() { - return this.getHtmlFromRange(null, null); - }; - - Firepad.prototype.getHtmlFromSelection = function() { - var startPos = this.codeMirror_.getCursor('start'), endPos = this.codeMirror_.getCursor('end'); - var startIndex = this.codeMirror_.indexFromPos(startPos), endIndex = this.codeMirror_.indexFromPos(endPos); - return this.getHtmlFromRange(startIndex, endIndex); - }; - - Firepad.prototype.getHtmlFromRange = function(start, end) { - this.assertReady_('getHtmlFromRange'); - var doc = (start != null && end != null) ? - this.getOperationForSpan(start, end) : - this.getOperationForSpan(0, this.codeMirror_.getValue().length); - return firepad.SerializeHtml(doc, this.entityManager_); - }; - - Firepad.prototype.insertHtml = function (index, html) { - var lines = firepad.ParseHtml(html, this.entityManager_); - this.insertText(index, lines); - }; - - Firepad.prototype.insertHtmlAtCursor = function (html) { - this.insertHtml(this.codeMirror_.indexFromPos(this.codeMirror_.getCursor()), html); - }; - - Firepad.prototype.setHtml = function (html) { - var lines = firepad.ParseHtml(html, this.entityManager_); - this.setText(lines); - }; - - Firepad.prototype.isHistoryEmpty = function() { - this.assertReady_('isHistoryEmpty'); - return this.firebaseAdapter_.isHistoryEmpty(); - }; - - Firepad.prototype.bold = function() { - this.richTextCodeMirror_.toggleAttribute(ATTR.BOLD); - this.codeMirror_.focus(); - }; - - Firepad.prototype.italic = function() { - this.richTextCodeMirror_.toggleAttribute(ATTR.ITALIC); - this.codeMirror_.focus(); - }; - - Firepad.prototype.underline = function() { - this.richTextCodeMirror_.toggleAttribute(ATTR.UNDERLINE); - this.codeMirror_.focus(); - }; - - Firepad.prototype.strike = function() { - this.richTextCodeMirror_.toggleAttribute(ATTR.STRIKE); - this.codeMirror_.focus(); - }; - - Firepad.prototype.fontSize = function(size) { - this.richTextCodeMirror_.setAttribute(ATTR.FONT_SIZE, size); - this.codeMirror_.focus(); - }; - - Firepad.prototype.font = function(font) { - this.richTextCodeMirror_.setAttribute(ATTR.FONT, font); - this.codeMirror_.focus(); - }; - - Firepad.prototype.color = function(color) { - this.richTextCodeMirror_.setAttribute(ATTR.COLOR, color); - this.codeMirror_.focus(); - }; - - Firepad.prototype.highlight = function() { - this.richTextCodeMirror_.toggleAttribute(ATTR.BACKGROUND_COLOR, 'rgba(255,255,0,.65)'); - this.codeMirror_.focus(); - }; - - Firepad.prototype.align = function(alignment) { - if (alignment !== 'left' && alignment !== 'center' && alignment !== 'right') { - throw new Error('align() must be passed "left", "center", or "right".'); - } - this.richTextCodeMirror_.setLineAttribute(ATTR.LINE_ALIGN, alignment); - this.codeMirror_.focus(); - }; - - Firepad.prototype.orderedList = function() { - this.richTextCodeMirror_.toggleLineAttribute(ATTR.LIST_TYPE, 'o'); - this.codeMirror_.focus(); - }; - - Firepad.prototype.unorderedList = function() { - this.richTextCodeMirror_.toggleLineAttribute(ATTR.LIST_TYPE, 'u'); - this.codeMirror_.focus(); - }; - - Firepad.prototype.todo = function() { - this.richTextCodeMirror_.toggleTodo(); - this.codeMirror_.focus(); - }; - - Firepad.prototype.newline = function() { - this.richTextCodeMirror_.newline(); - }; - - Firepad.prototype.deleteLeft = function() { - this.richTextCodeMirror_.deleteLeft(); - }; - - Firepad.prototype.deleteRight = function() { - this.richTextCodeMirror_.deleteRight(); - }; - - Firepad.prototype.indent = function() { - this.richTextCodeMirror_.indent(); - this.codeMirror_.focus(); - }; - - Firepad.prototype.unindent = function() { - this.richTextCodeMirror_.unindent(); - this.codeMirror_.focus(); - }; - - Firepad.prototype.undo = function() { - this.codeMirror_.undo(); - }; - - Firepad.prototype.redo = function() { - this.codeMirror_.redo(); - }; - - Firepad.prototype.insertEntity = function(type, info, origin) { - this.richTextCodeMirror_.insertEntityAtCursor(type, info, origin); - }; - - Firepad.prototype.insertEntityAt = function(index, type, info, origin) { - this.richTextCodeMirror_.insertEntityAt(index, type, info, origin); - }; - - Firepad.prototype.registerEntity = function(type, options) { - this.entityManager_.register(type, options); - }; - - Firepad.prototype.getOption = function(option, def) { - return (option in this.options_) ? this.options_[option] : def; - }; - - Firepad.prototype.assertReady_ = function(funcName) { - if (!this.ready_) { - throw new Error('You must wait for the "ready" event before calling ' + funcName + '.'); - } - if (this.zombie_) { - throw new Error('You can\'t use a Firepad after calling dispose()! [called ' + funcName + ']'); - } - }; - - Firepad.prototype.makeImageDialog_ = function() { - this.makeDialog_('img', 'Insert image url'); - }; - - Firepad.prototype.makeDialog_ = function(id, placeholder) { - var self = this; - - var hideDialog = function() { - var dialog = document.getElementById('overlay'); - dialog.style.visibility = "hidden"; - self.firepadWrapper_.removeChild(dialog); - }; - - var cb = function() { - var dialog = document.getElementById('overlay'); - dialog.style.visibility = "hidden"; - var src = document.getElementById(id).value; - if (src !== null) - self.insertEntity(id, { 'src': src }); - self.firepadWrapper_.removeChild(dialog); - }; - - var input = utils.elt('input', null, { 'class':'firepad-dialog-input', 'id':id, 'type':'text', 'placeholder':placeholder, 'autofocus':'autofocus' }); - - var submit = utils.elt('a', 'Submit', { 'class': 'firepad-btn', 'id':'submitbtn' }); - utils.on(submit, 'click', utils.stopEventAnd(cb)); - - var cancel = utils.elt('a', 'Cancel', { 'class': 'firepad-btn' }); - utils.on(cancel, 'click', utils.stopEventAnd(hideDialog)); - - var buttonsdiv = utils.elt('div', [submit, cancel], { 'class':'firepad-btn-group' }); - - var div = utils.elt('div', [input, buttonsdiv], { 'class':'firepad-dialog-div' }); - var dialog = utils.elt('div', [div], { 'class': 'firepad-dialog', id:'overlay' }); - - this.firepadWrapper_.appendChild(dialog); - }; - - Firepad.prototype.addToolbar_ = function() { - this.toolbar = new RichTextToolbar(this.imageInsertionUI); - - this.toolbar.on('undo', this.undo, this); - this.toolbar.on('redo', this.redo, this); - this.toolbar.on('bold', this.bold, this); - this.toolbar.on('italic', this.italic, this); - this.toolbar.on('underline', this.underline, this); - this.toolbar.on('strike', this.strike, this); - this.toolbar.on('font-size', this.fontSize, this); - this.toolbar.on('font', this.font, this); - this.toolbar.on('color', this.color, this); - this.toolbar.on('left', function() { this.align('left')}, this); - this.toolbar.on('center', function() { this.align('center')}, this); - this.toolbar.on('right', function() { this.align('right')}, this); - this.toolbar.on('ordered-list', this.orderedList, this); - this.toolbar.on('unordered-list', this.unorderedList, this); - this.toolbar.on('todo-list', this.todo, this); - this.toolbar.on('indent-increase', this.indent, this); - this.toolbar.on('indent-decrease', this.unindent, this); - this.toolbar.on('insert-image', this.makeImageDialog_, this); - - this.firepadWrapper_.insertBefore(this.toolbar.element(), this.firepadWrapper_.firstChild); - }; - - Firepad.prototype.addPoweredByLogo_ = function() { - var poweredBy = utils.elt('a', null, { 'class': 'powered-by-firepad'} ); - poweredBy.setAttribute('href', 'http://www.firepad.io/'); - poweredBy.setAttribute('target', '_blank'); - this.firepadWrapper_.appendChild(poweredBy) - }; - - Firepad.prototype.initializeKeyMap_ = function() { - function binder(fn) { - return function(cm) { - // HACK: CodeMirror will often call our key handlers within a cm.operation(), and that - // can mess us up (we rely on events being triggered synchronously when we make CodeMirror - // edits). So to escape any cm.operation(), we do a setTimeout. - setTimeout(function() { - fn.call(cm.firepad); - }, 0); - } - } - - CodeMirror.keyMap["richtext"] = { - "Ctrl-B": binder(this.bold), - "Cmd-B": binder(this.bold), - "Ctrl-I": binder(this.italic), - "Cmd-I": binder(this.italic), - "Ctrl-U": binder(this.underline), - "Cmd-U": binder(this.underline), - "Ctrl-H": binder(this.highlight), - "Cmd-H": binder(this.highlight), - "Enter": binder(this.newline), - "Delete": binder(this.deleteRight), - "Backspace": binder(this.deleteLeft), - "Tab": binder(this.indent), - "Shift-Tab": binder(this.unindent), - fallthrough: ['default'] - }; - }; - - function colorFromUserId (userId) { - var a = 1; - for (var i = 0; i < userId.length; i++) { - a = 17 * (a+userId.charCodeAt(i)) % 360; - } - var hue = a/360; - - return hsl2hex(hue, 1, 0.75); - } - - function rgb2hex (r, g, b) { - function digits (n) { - var m = Math.round(255*n).toString(16); - return m.length === 1 ? '0'+m : m; - } - return '#' + digits(r) + digits(g) + digits(b); - } - - function hsl2hex (h, s, l) { - if (s === 0) { return rgb2hex(l, l, l); } - var var2 = l < 0.5 ? l * (1+s) : (l+s) - (s*l); - var var1 = 2 * l - var2; - var hue2rgb = function (hue) { - if (hue < 0) { hue += 1; } - if (hue > 1) { hue -= 1; } - if (6*hue < 1) { return var1 + (var2-var1)*6*hue; } - if (2*hue < 1) { return var2; } - if (3*hue < 2) { return var1 + (var2-var1)*6*(2/3 - hue); } - return var1; - }; - return rgb2hex(hue2rgb(h+1/3), hue2rgb(h), hue2rgb(h-1/3)); - } - - return Firepad; -})(this); - -// Export Text classes -firepad.Firepad.Formatting = firepad.Formatting; -firepad.Firepad.Text = firepad.Text; -firepad.Firepad.Entity = firepad.Entity; -firepad.Firepad.LineFormatting = firepad.LineFormatting; -firepad.Firepad.Line = firepad.Line; -firepad.Firepad.TextOperation = firepad.TextOperation; -firepad.Firepad.Headless = firepad.Headless; - -// Export adapters -firepad.Firepad.RichTextCodeMirrorAdapter = firepad.RichTextCodeMirrorAdapter; -firepad.Firepad.ACEAdapter = firepad.ACEAdapter; -firepad.Firepad.MonacoAdapter = firepad.MonacoAdapter; - -return firepad.Firepad; }, this); \ No newline at end of file diff --git a/dist/firepad.min.js b/dist/firepad.min.js deleted file mode 100644 index 8cb118ff4..000000000 --- a/dist/firepad.min.js +++ /dev/null @@ -1,16 +0,0 @@ -/*! - * Firepad is an open-source, collaborative code and text editor. It was designed - * to be embedded inside larger applications. Since it uses Firebase as a backend, - * it requires no server-side code and can be added to any web app simply by - * including a couple JavaScript files. - * - * Firepad 0.0.0 - * http://www.firepad.io/ - * License: MIT - * Copyright: 2014 Firebase - * With code from ot.js (Copyright 2012-2013 Tim Baumann) - */ -!function(a,b,c){"undefined"!=typeof module&&module.exports?module.exports=b():"function"==typeof c.define&&c.define.amd?define(b):c[a]=b()}("Firepad",function(){var a=a||{};a.utils={},a.utils.makeEventEmitter=function(a,b){a.prototype.allowedEvents_=b,a.prototype.on=function(a,b,c){this.validateEventType_(a),this.eventListeners_=this.eventListeners_||{},this.eventListeners_[a]=this.eventListeners_[a]||[],this.eventListeners_[a].push({callback:b,context:c})},a.prototype.off=function(a,b){this.validateEventType_(a),this.eventListeners_=this.eventListeners_||{};for(var c=this.eventListeners_[a]||[],d=0;da)throw new Error("retain expects a positive integer.");if(0===a)return this;this.baseLength+=a,this.targetLength+=a,b=b||{};var c=this.ops.length>0?this.ops[this.ops.length-1]:null;return c&&c.isRetain()&&c.attributesEqual(b)?c.chars+=a:this.ops.push(new e("retain",a,b)),this},b.prototype.insert=function(a,b){if("string"!=typeof a)throw new Error("insert expects a string");if(""===a)return this;b=b||{},this.targetLength+=a.length;var c=this.ops.length>0?this.ops[this.ops.length-1]:null,d=this.ops.length>1?this.ops[this.ops.length-2]:null;return c&&c.isInsert()&&c.attributesEqual(b)?c.text+=a:c&&c.isDelete()?d&&d.isInsert()&&d.attributesEqual(b)?d.text+=a:(this.ops[this.ops.length-1]=new e("insert",a,b),this.ops.push(c)):this.ops.push(new e("insert",a,b)),this},b.prototype["delete"]=function(a){if("string"==typeof a&&(a=a.length),"number"!=typeof a||0>a)throw new Error("delete expects a positive integer or a string");if(0===a)return this;this.baseLength+=a;var b=this.ops.length>0?this.ops[this.ops.length-1]:null;return b&&b.isDelete()?b.chars+=a:this.ops.push(new e("delete",a)),this},b.prototype.isNoop=function(){return 0===this.ops.length||1===this.ops.length&&this.ops[0].isRetain()&&this.ops[0].hasEmptyAttributes()},b.prototype.clone=function(){for(var a=new b,c=0;cd;d++)c[d]=a(b[d]);return c};return a.call(this.ops,function(a){return a.isRetain()?"retain "+a.chars:a.isInsert()?"insert '"+a.text+"'":"delete "+a.chars}).join(", ")},b.prototype.toJSON=function(){for(var a=[],b=0;bd;d++){var g=a[d],h={};"object"==typeof g&&(h=g,d++,g=a[d]),"number"==typeof g?g>0?c.retain(g,h):c["delete"](-g):(f.assert("string"==typeof g),c.insert(g,h))}return c},b.prototype.apply=function(a,b,c){var d=this;if(b=b||[],c=c||[],a.length!==d.baseLength)throw new Error("The operation's base length must be equal to the string's length.");for(var e,g,h=[],i=0,j=0,k=this.ops,l=0,m=k.length;m>l;l++){var n=k[l];if(n.isRetain()){if(j+n.chars>a.length)throw new Error("Operation can't retain more characters than are left in the string.");for(h[i++]=a.slice(j,j+n.chars),e=0;ef;f++){var h=e[f];h.isRetain()?(d.retain(h.chars),c+=h.chars):h.isInsert()?d["delete"](h.text.length):(d.insert(a.slice(c,c+h.chars)),c+=h.chars)}return d},b.prototype.compose=function(a){function c(a,b,c){var d,e={};for(d in a)e[d]=a[d];for(d in b)c&&b[d]===!1?delete e[d]:e[d]=b[d];return e}var d=this;if(d.targetLength!==a.baseLength)throw new Error("The base length of the second operation has to be the target length of the first operation");for(var e,f=new b,g=d.clone().ops,h=a.clone().ops,i=0,j=0,k=g[i++],l=h[j++];;){if("undefined"==typeof k&&"undefined"==typeof l)break;if(k&&k.isDelete())f["delete"](k.chars),k=g[i++];else if(l&&l.isInsert())f.insert(l.text,l.attributes),l=h[j++];else{if("undefined"==typeof k)throw new Error("Cannot compose operations: first operation is too short.");if("undefined"==typeof l)throw new Error("Cannot compose operations: first operation is too long.");if(k.isRetain()&&l.isRetain())e=c(k.attributes,l.attributes),k.chars>l.chars?(f.retain(l.chars,e),k.chars-=l.chars,l=h[j++]):k.chars===l.chars?(f.retain(k.chars,e),k=g[i++],l=h[j++]):(f.retain(k.chars,e),l.chars-=k.chars,k=g[i++]);else if(k.isInsert()&&l.isDelete())k.text.length>l.chars?(k.text=k.text.slice(l.chars),l=h[j++]):k.text.length===l.chars?(k=g[i++],l=h[j++]):(l.chars-=k.text.length,k=g[i++]);else if(k.isInsert()&&l.isRetain())e=c(k.attributes,l.attributes,!0),k.text.length>l.chars?(f.insert(k.text.slice(0,l.chars),e),k.text=k.text.slice(l.chars),l=h[j++]):k.text.length===l.chars?(f.insert(k.text,e),k=g[i++],l=h[j++]):(f.insert(k.text,e),l.chars-=k.text.length,k=g[i++]);else{if(!k.isRetain()||!l.isDelete())throw new Error("This shouldn't happen: op1: "+JSON.stringify(k)+", op2: "+JSON.stringify(l));k.chars>l.chars?(f["delete"](l.chars),k.chars-=l.chars,l=h[j++]):k.chars===l.chars?(f["delete"](l.chars),k=g[i++],l=h[j++]):(f["delete"](k.chars),l.chars-=k.chars,k=g[i++])}}}return f},b.prototype.shouldBeComposedWith=function(a){if(this.isNoop()||a.isNoop())return!0;var b=d(this),e=d(a),f=c(this),g=c(a);return f&&g?f.isInsert()&&g.isInsert()?b+f.text.length===e:f.isDelete()&&g.isDelete()?e+g.chars===b||b===e:!1:!1},b.prototype.shouldBeComposedWithInverted=function(a){if(this.isNoop()||a.isNoop())return!0;var b=d(this),e=d(a),f=c(this),g=c(a);return f&&g?f.isInsert()&&g.isInsert()?b+f.text.length===e||b===e:f.isDelete()&&g.isDelete()?e+g.chars===b:!1:!1},b.transformAttributes=function(a,b){var c,d={},e={},g={};for(c in a)g[c]=!0;for(c in b)g[c]=!0;for(c in g){var h=a[c],i=b[c];f.assert(null!=h||null!=i),null==h?e[c]=i:null==i?d[c]=h:h===i||(d[c]=h)}return[d,e]},b.transform=function(a,c){if(a.baseLength!==c.baseLength)throw new Error("Both operations have to have the same base length");for(var d=new b,e=new b,f=a.clone().ops,g=c.clone().ops,h=0,i=0,j=f[h++],k=g[i++];;){if("undefined"==typeof j&&"undefined"==typeof k)break;if(j&&j.isInsert())d.insert(j.text,j.attributes),e.retain(j.text.length),j=f[h++];else if(k&&k.isInsert())d.retain(k.text.length),e.insert(k.text,k.attributes),k=g[i++];else{if("undefined"==typeof j)throw new Error("Cannot transform operations: first operation is too short.");if("undefined"==typeof k)throw new Error("Cannot transform operations: first operation is too long.");var l;if(j.isRetain()&&k.isRetain()){var m=b.transformAttributes(j.attributes,k.attributes);j.chars>k.chars?(l=k.chars,j.chars-=k.chars,k=g[i++]):j.chars===k.chars?(l=k.chars,j=f[h++],k=g[i++]):(l=j.chars,k.chars-=j.chars,j=f[h++]),d.retain(l,m[0]),e.retain(l,m[1])}else if(j.isDelete()&&k.isDelete())j.chars>k.chars?(j.chars-=k.chars,k=g[i++]):j.chars===k.chars?(j=f[h++],k=g[i++]):(k.chars-=j.chars,j=f[h++]);else if(j.isDelete()&&k.isRetain())j.chars>k.chars?(l=k.chars,j.chars-=k.chars,k=g[i++]):j.chars===k.chars?(l=k.chars,j=f[h++],k=g[i++]):(l=j.chars,k.chars-=j.chars,j=f[h++]),d["delete"](l);else{if(!j.isRetain()||!k.isDelete())throw new Error("The two operations aren't compatible");j.chars>k.chars?(l=k.chars,j.chars-=k.chars,k=g[i++]):j.chars===k.chars?(l=j.chars,j=f[h++],k=g[i++]):(l=j.chars,k.chars-=j.chars,j=f[h++]),e["delete"](l)}}}return[d,e]},b.prototype.transform=function(a){return b.transform(this,a)},b}();var a=a||{};a.AnnotationList=function(){function b(a,b){if(!a)throw new Error("AnnotationList assertion failed"+(b?": "+b:""))}function c(a,b){this.pos=a,this.length=b.length,this.annotation=b.annotation,this.attachedObject_=b.attachedObject}function d(a,b){this.pos=a,this.length=b.length,this.annotation=b.annotation,this.node_=b}function e(a){this.head_=new f(0,h),this.changeHandler_=a}function f(a,b){this.length=a,this.annotation=b,this.attachedObject=null,this.next=null}var g=a.Span;c.prototype.getAttachedObject=function(){return this.attachedObject_},d.prototype.attachObject=function(a){this.node_.attachedObject=a};var h={equals:function(){return!1}};return e.prototype.insertAnnotatedSpan=function(a,c){this.wrapOperation_(new g(a.pos,0),function(d,e){b(!e||null===e.next);var g=new f(a.length,c);if(e){b(a.pos>d&&a.posc&&(g.next=new f(a.pos-c,d.annotation),g=g.next);a.end()>c+d.length;)c+=d.length,d=d.next;var i=c+d.length-a.end();return i>0&&(g.next=new f(i,d.annotation)),e.next})},e.prototype.updateSpan=function(a,c){0!==a.length&&this.wrapOperation_(a,function(d,e){b(null!==e);var g=new f(0,h),i=g,j=d,k=a.pos-j;for(b(k0&&(i.next=new f(k,e.annotation),i=i.next,j+=i.length);null!==e&&a.end()>=d+e.length;){var l=d+e.length-j;i.next=new f(l,c(e.annotation,l)),i=i.next,d+=e.length,e=e.next,j=d}var m=a.end()-j;return m>0&&(b(m=f+e.length;)f+=e.length,c=d,d=e,e=e.next;if(null===e&&(0!==a.length||a.pos!==f))throw new Error("Span start exceeds the bounds of the AnnotationList.");for(b.startPos=f,0===a.length&&a.pos===f?b.start=null:b.start=e,b.beforeStart=d,f===a.pos&&f>0?(b.pred=d,b.predPos=f-d.length,b.beforePred=c):b.pred=null;null!==e&&a.end()>f;)f+=e.length,d=e,e=e.next;if(a.end()>f)throw new Error("Span end exceeds the bounds of the AnnotationList.");return 0===a.length&&a.end()===f?b.end=null:b.end=d,b.succ=f===a.end()?e:null,b},e.prototype.mergeNodesWithSameAnnotations_=function(a){if(a)for(var b=null,c=a;c;)b&&b.annotation.equals(c.annotation)?(b.length+=c.length,b.next=c.next):b=c,c=c.next},e.prototype.forEach=function(a){for(var b=this.head_.next;null!==b;)a(b.length,b.annotation,b.attachedObject),b=b.next},e.prototype.getAnnotatedSpansForPos=function(a){for(var b=0,d=this.head_.next,e=null;null!==d&&b+d.length<=a;)b+=d.length,e=d,d=d.next;if(null===d&&b!==a)throw new Error("pos exceeds the bounds of the AnnotationList");var f=[];return b===a&&e&&f.push(new c(b-e.length,e)),d&&f.push(new c(b,d)),f},e.prototype.getAnnotatedSpansForSpan=function(a){if(0===a.length)return[];for(var b=[],c=this.getAffectedNodes_(a),d=c.startPos,e=c.start;null!==e&&de&&(d[e].isRetain()?a-=d[e].chars:d[e].isInsert()?c+=d[e].text.length:(c-=Math.min(a,d[e].chars),a-=d[e].chars),!(0>a));e++);return c}var d=c(this.position);return this.position===this.selectionEnd?new a(d,d):new a(d,c(this.selectionEnd))},a}();var a=a||{};a.FirebaseAdapter=function(b){function c(a,b,c){this.ref_=a,this.ready_=!1,this.firebaseCallbacks_=[],this.zombie_=!1,this.document_=new g,this.revision_=0,this.pendingReceivedRevisions_={};var d=this;if(b){this.setUserId(b),this.setColor(c);var e=a.root.child(".info/connected");this.firebaseOn_(e,"value",function(a){a.val()===!0&&d.initializeUserData_()},this),this.on("ready",function(){d.monitorCursors_()})}else this.userId_=a.push().key;setTimeout(function(){d.monitorHistory_()},0)}function d(a,b){if(!a)throw new Error(b||"assertion error")}function e(a){if(0===a)return"A0";for(var b="";a>0;){var c=a%j.length;b=j[c]+b,a-=c,a/=j.length}var d=j[b.length+9];return d+b}function f(a){d(a.length>0&&a[0]===j[a.length+8]);for(var b=0,c=1;c=0;e--){var f=d.transform(a[e],b);"function"==typeof f[0].isNoop&&f[0].isNoop()||c.push(f[0]),b=f[1]}return c.reverse()}var c="normal",d="undoing",e="redoing";return a.prototype.add=function(a,b){if(this.state===d)this.redoStack.push(a),this.dontCompose=!0;else if(this.state===e)this.undoStack.push(a),this.dontCompose=!0;else{var c=this.undoStack;!this.dontCompose&&b&&c.length>0?c.push(a.compose(c.pop())):(c.push(a),c.length>this.maxItems&&c.shift()),this.dontCompose=!1,this.redoStack=[]}},a.prototype.transform=function(a){this.undoStack=b(this.undoStack,a),this.redoStack=b(this.redoStack,a)},a.prototype.performUndo=function(a){if(this.state=d,0===this.undoStack.length)throw new Error("undo not possible");a(this.undoStack.pop()),this.state=c},a.prototype.performRedo=function(a){if(this.state=e,0===this.redoStack.length)throw new Error("redo not possible");a(this.redoStack.pop()),this.state=c},a.prototype.canUndo=function(){return 0!==this.undoStack.length},a.prototype.canRedo=function(){return 0!==this.redoStack.length},a.prototype.isUndoing=function(){return this.state===d},a.prototype.isRedoing=function(){return this.state===e},a}();var a=a||{};a.Client=function(){"use strict";function a(){this.state=e}function b(){}function c(a){this.outstanding=a}function d(a,b){this.outstanding=a,this.buffer=b}a.prototype.setState=function(a){this.state=a},a.prototype.applyClient=function(a){this.setState(this.state.applyClient(this,a))},a.prototype.applyServer=function(a){this.setState(this.state.applyServer(this,a))},a.prototype.serverAck=function(){this.setState(this.state.serverAck(this))},a.prototype.serverRetry=function(){this.setState(this.state.serverRetry(this))},a.prototype.sendOperation=function(a){throw new Error("sendOperation must be defined in child class")},a.prototype.applyOperation=function(a){throw new Error("applyOperation must be defined in child class")},a.Synchronized=b,b.prototype.applyClient=function(a,b){return a.sendOperation(b),new c(b)},b.prototype.applyServer=function(a,b){return a.applyOperation(b),this},b.prototype.serverAck=function(a){throw new Error("There is no pending operation.")},b.prototype.serverRetry=function(a){throw new Error("There is no pending operation.")};var e=new b;return a.AwaitingConfirm=c,c.prototype.applyClient=function(a,b){return new d(this.outstanding,b)},c.prototype.applyServer=function(a,b){var d=this.outstanding.transform(b);return a.applyOperation(d[1]),new c(d[0])},c.prototype.serverAck=function(a){return e},c.prototype.serverRetry=function(a){return a.sendOperation(this.outstanding),this},a.AwaitingWithBuffer=d,d.prototype.applyClient=function(a,b){var c=this.buffer.compose(b);return new d(this.outstanding,c)},d.prototype.applyServer=function(a,b){var c=this.outstanding.transform(b),e=this.buffer.transform(c[1]);return a.applyOperation(e[1]),new d(c[0],e[0])},d.prototype.serverRetry=function(a){var b=this.outstanding.compose(this.buffer);return a.sendOperation(b),new c(b)},d.prototype.serverAck=function(a){return a.sendOperation(this.buffer),new c(this.buffer)},a}();var a=a||{};a.EditorClient=function(){"use strict";function b(a,b){this.cursorBefore=a,this.cursorAfter=b}function c(a,b){this.id=a,this.editorAdapter=b}function d(a,b){g.call(this),this.serverAdapter=a,this.editorAdapter=b,this.undoManager=new i,this.clients={};var c=this;this.editorAdapter.registerCallbacks({change:function(a,b){c.onChange(a,b)},cursorActivity:function(){c.onCursorActivity()},blur:function(){c.onBlur()},focus:function(){c.onFocus()}}),this.editorAdapter.registerUndo(function(){c.undo()}),this.editorAdapter.registerRedo(function(){c.redo()}),this.serverAdapter.registerCallbacks({ack:function(){c.serverAck(),c.focused&&c.state instanceof g.Synchronized&&(c.updateCursor(),c.sendCursor(c.cursor)),c.emitStatus()},retry:function(){c.serverRetry()},operation:function(a){c.applyServer(a)},cursor:function(a,b,d){if(c.serverAdapter.userId_!==a&&c.state instanceof g.Synchronized){var e=c.getClientObject(a);b?(d&&e.setColor(d),e.updateCursor(h.fromJSON(b))):e.removeCursor(); -}}})}function e(a,b){function c(){}c.prototype=b.prototype,a.prototype=new c,a.prototype.constructor=a}function f(a){return a[a.length-1]}var g=a.Client,h=a.Cursor,i=a.UndoManager,j=a.WrappedOperation;return b.prototype.invert=function(){return new b(this.cursorAfter,this.cursorBefore)},b.prototype.compose=function(a){return new b(this.cursorBefore,a.cursorAfter)},b.prototype.transform=function(a){return new b(this.cursorBefore?this.cursorBefore.transform(a):null,this.cursorAfter?this.cursorAfter.transform(a):null)},c.prototype.setColor=function(a){this.color=a},c.prototype.updateCursor=function(a){this.removeCursor(),this.cursor=a,this.mark=this.editorAdapter.setOtherCursor(a,this.color,this.id)},c.prototype.removeCursor=function(){this.mark&&this.mark.clear()},e(d,g),d.prototype.getClientObject=function(a){var b=this.clients[a];return b?b:this.clients[a]=new c(a,this.editorAdapter)},d.prototype.applyUnredo=function(a){this.undoManager.add(this.editorAdapter.invertOperation(a)),this.editorAdapter.applyOperation(a.wrapped),this.cursor=a.meta.cursorAfter,this.cursor&&this.editorAdapter.setCursor(this.cursor),this.applyClient(a.wrapped)},d.prototype.undo=function(){var a=this;this.undoManager.canUndo()&&this.undoManager.performUndo(function(b){a.applyUnredo(b)})},d.prototype.redo=function(){var a=this;this.undoManager.canRedo()&&this.undoManager.performRedo(function(b){a.applyUnredo(b)})},d.prototype.onChange=function(a,c){var d=this.cursor;this.updateCursor();var e=this.undoManager.undoStack.length>0&&c.shouldBeComposedWithInverted(f(this.undoManager.undoStack).wrapped),g=new b(this.cursor,d);this.undoManager.add(new j(c,g),e),this.applyClient(a)},d.prototype.updateCursor=function(){this.cursor=this.editorAdapter.getCursor()},d.prototype.onCursorActivity=function(){var a=this.cursor;this.updateCursor(),!this.focused||a&&this.cursor.equals(a)||this.sendCursor(this.cursor)},d.prototype.onBlur=function(){this.cursor=null,this.sendCursor(null),this.focused=!1},d.prototype.onFocus=function(){this.focused=!0,this.onCursorActivity()},d.prototype.sendCursor=function(a){this.state instanceof g.AwaitingWithBuffer||this.serverAdapter.sendCursor(a)},d.prototype.sendOperation=function(a){this.serverAdapter.sendOperation(a),this.emitStatus()},d.prototype.applyOperation=function(a){this.editorAdapter.applyOperation(a),this.updateCursor(),this.undoManager.transform(new j(a,null))},d.prototype.emitStatus=function(){var a=this;setTimeout(function(){a.trigger("synced",a.state instanceof g.Synchronized)},0)},d}(),a.utils.makeEventEmitter(a.EditorClient,["synced"]);var a,b=function(a,b){return function(){return a.apply(b,arguments)}},c=[].slice;("undefined"==typeof a||null===a)&&(a={}),a.ACEAdapter=function(){function d(a){this.onCursorActivity=b(this.onCursorActivity,this),this.onFocus=b(this.onFocus,this),this.onBlur=b(this.onBlur,this),this.onChange=b(this.onChange,this);var c;this.ace=a,this.aceSession=this.ace.getSession(),this.aceDoc=this.aceSession.getDocument(),this.aceDoc.setNewLineMode("unix"),this.grabDocumentState(),this.ace.on("change",this.onChange),this.ace.on("blur",this.onBlur),this.ace.on("focus",this.onFocus),this.aceSession.selection.on("changeCursor",this.onCursorActivity),null==this.aceRange&&(this.aceRange=(null!=(c=ace.require)?c:require)("ace/range").Range)}return d.prototype.ignoreChanges=!1,d.prototype.grabDocumentState=function(){return this.lastDocLines=this.aceDoc.getAllLines(),this.lastCursorRange=this.aceSession.selection.getRange()},d.prototype.detach=function(){return this.ace.removeListener("change",this.onChange),this.ace.removeListener("blur",this.onBlur),this.ace.removeListener("focus",this.onFocus),this.aceSession.selection.removeListener("changeCursor",this.onCursorActivity)},d.prototype.onChange=function(a){var b;return this.ignoreChanges?void 0:(b=this.operationFromACEChange(a),this.trigger.apply(this,["change"].concat(c.call(b))),this.grabDocumentState())},d.prototype.onBlur=function(){return this.ace.selection.isEmpty()?this.trigger("blur"):void 0},d.prototype.onFocus=function(){return this.trigger("focus")},d.prototype.onCursorActivity=function(){var a=this;return setTimeout(function(){return a.trigger("cursorActivity")},0)},d.prototype.operationFromACEChange=function(b){var c,d,e,f,g,h,i,j;return b.data?(e=b.data,"insertLines"===(j=e.action)||"removeLines"===j?(i=e.lines.join("\n")+"\n",c=e.action.replace("Lines","")):(i=e.text.replace(this.aceDoc.getNewLineCharacter(),"\n"),c=e.action.replace("Text","")),h=this.indexFromPos(e.range.start)):(i=b.lines.join("\n"),h=this.indexFromPos(b.start)),g=this.lastDocLines.join("\n").length-h,"remove"===b.action&&(g-=i.length),f=(new a.TextOperation).retain(h).insert(i).retain(g),d=(new a.TextOperation).retain(h)["delete"](i).retain(g),"remove"===b.action?[d,f]:[f,d]},d.prototype.applyOperationToACE=function(a){var b,c,d,e,f,g,h,i;for(c=0,i=a.ops,g=0,h=i.length;h>g;g++)d=i[g],d.isRetain()?c+=d.chars:d.isInsert()?(this.aceDoc.insert(this.posFromIndex(c),d.text),c+=d.text.length):d.isDelete()&&(b=this.posFromIndex(c),f=this.posFromIndex(c+d.chars),e=this.aceRange.fromPoints(b,f),this.aceDoc.remove(e));return this.grabDocumentState()},d.prototype.posFromIndex=function(a){var b,c,d,e,f;for(f=this.aceDoc.$lines,c=d=0,e=f.length;e>d&&(b=f[c],!(a<=b.length));c=++d)a-=b.length+1;return{row:c,column:a}},d.prototype.indexFromPos=function(a,b){var c,d,e,f;for(null==b&&(b=this.lastDocLines),d=0,c=e=0,f=a.row;f>=0?f>e:e>f;c=f>=0?++e:--e)d+=this.lastDocLines[c].length+1;return d+=a.column},d.prototype.getValue=function(){return this.aceDoc.getValue()},d.prototype.getCursor=function(){var b,c,d,e,f,g;try{e=this.indexFromPos(this.aceSession.selection.getRange().start,this.aceDoc.$lines),d=this.indexFromPos(this.aceSession.selection.getRange().end,this.aceDoc.$lines)}catch(h){b=h;try{e=this.indexFromPos(this.lastCursorRange.start),d=this.indexFromPos(this.lastCursorRange.end)}catch(h){c=h,console.log("Couldn't figure out the cursor range:",c,"-- setting it to 0:0."),f=[0,0],e=f[0],d=f[1]}}return e>d&&(g=[d,e],e=g[0],d=g[1]),new a.Cursor(e,d)},d.prototype.setCursor=function(a){var b,c,d;return c=this.posFromIndex(a.position),b=this.posFromIndex(a.selectionEnd),a.position>a.selectionEnd&&(d=[b,c],c=d[0],b=d[1]),this.aceSession.selection.setSelectionRange(new this.aceRange(c.row,c.column,b.row,b.column))},d.prototype.setOtherCursor=function(a,b,c){var d,e,f,g,h,i,j,k,l=this;return null==this.otherCursors&&(this.otherCursors={}),f=this.otherCursors[c],f&&(f.start.detach(),f.end.detach(),this.aceSession.removeMarker(f.id)),j=this.posFromIndex(a.position),g=this.posFromIndex(a.selectionEnd),a.selectionEnd0&&(restLength-=rangeLen,text=this.lastDocLines.join("\n").slice(start,start+rangeLen)),insert_op=(new a.TextOperation).retain(start).insert(text).retain(restLength),delete_op=(new a.TextOperation).retain(start)["delete"](text).retain(restLength),ins_op_ne=(new a.TextOperation).retain(start)["delete"](text_ins).insert(text).retain(restLength),del_op_ne=(new a.TextOperation).retain(start)["delete"](text).insert(text_ins).retain(restLength),""===b.changes[0].text&&rangeLen>0?[delete_op,insert_op]:""!==b.changes[0].text&&rangeLen>0?[del_op_ne,ins_op_ne]:[insert_op,delete_op]},d.prototype.applyOperationToMonaco=function(a){var b,c,d,e,f,g,h,i,j,k;for(c=0,h=a.ops,f=0,g=h.length;g>f;f++)d=h[f],d.isRetain()?c+=d.chars:d.isInsert()?(k=new monaco.Range(this.posFromIndex(c).row,this.posFromIndex(c).column,this.posFromIndex(c).row,this.posFromIndex(c).column),i={major:1,minor:1},j={identifier:i,range:k,text:d.text,forceMoveMarkers:!0},this.monaco.executeEdits("my-source",[j]),c+=d.text.length):d.isDelete()&&(content=this.monacoModel.getLinesContent(),b=this.posFromIndex(c,content),e=this.posFromIndex(c+d.chars,content),k=new monaco.Range(b.row,b.column,e.row,e.column),i={major:1,minor:2},j={identifier:i,range:k,text:"",forceMoveMarkers:!0},this.monaco.executeEdits("my-source",[j]));return this.grabDocumentState()},d.prototype.posFromIndex=function(a,b){var c,d,e,f,g;for(g=null==b?this.lastDocLines:b,d=e=0,f=g.length;f>e&&(c=g[d],!(a<=c.length));d=++e)a-=c.length+1;return{row:d+1,column:a+1}},d.prototype.indexFromPos=function(a,b,c){var d;if(null==c&&(c=this.lastDocLines),d=0,"start"===b){for(row=1;rowd&&(g=[d,e],e=g[0],d=g[1]),new a.Cursor(e,d)},d.prototype.setCursor=function(a){var b,c,d;return c=this.posFromIndex(a.position),b=this.posFromIndex(a.selectionEnd),a.position>a.selectionEnd&&(d=[b,c],c=d[0],b=d[1]),this.monaco.setSelection(new monaco.Range(c.row,c.column,b.row,b.column))},d.prototype.setOtherCursor=function(a,b,c){return null==this.otherCursors&&(this.otherCursors={},this.otherCursors[c]=[]),this.otherCursors[c]&&(this.otherCursors[c]=this.monaco.deltaDecorations(this.otherCursors[c],[])),start=this.posFromIndex(a.position),end=this.posFromIndex(a.selectionEnd),clazz="other-client-selection-"+b.replace("#",""),start<0||end<0||start>end?void 0:(justCursor=a.position===a.selectionEnd,justCursor&&(clazz=clazz.replace("selection","cursor")),css="."+clazz+" {\n position: relative;\nbackground-color: "+(justCursor?"transparent":b)+";\nborder-left: 2px solid "+b+";\n}",this.addStyleRule(css),this.otherCursors[c]=this.monaco.deltaDecorations(this.otherCursors[c],[{range:new monaco.Range(start.row,start.column,end.row,end.column),options:{className:clazz}}]),_this=this,{clear:function(){return _this.monaco.deltaDecorations(_this.otherCursors[c],[])}})},d.prototype.addStyleRule=function(a){var b;if("undefined"!=typeof document&&null!==document&&(this.addedStyleRules||(this.addedStyleRules={},b=document.createElement("style"),document.documentElement.getElementsByTagName("head")[0].appendChild(b),this.addedStyleSheet=b.sheet),!this.addedStyleRules[a]))return this.addedStyleRules[a]=!0,this.addedStyleSheet.insertRule(a,0)},d.prototype.registerCallbacks=function(a){this.callbacks=a},d.prototype.trigger=function(a){var b=Array.prototype.slice.call(arguments,1),c=this.callbacks&&this.callbacks[a];c&&c.apply(this,b)},d.prototype.applyOperation=function(a){return a.isNoop()||(this.ignoreChanges=!0),this.applyOperationToMonaco(a),this.ignoreChanges=!1},d.prototype.registerUndo=function(a){return this.monacoModel.undo=a},d.prototype.registerRedo=function(a){return this.monacoModel.redo=a},d.prototype.invertOperation=function(a){return a.invert(this.getValue())},d}();var a=a||{};a.AttributeConstants={BOLD:"b",ITALIC:"i",UNDERLINE:"u",STRIKE:"s",FONT:"f",FONT_SIZE:"fs",COLOR:"c",BACKGROUND_COLOR:"bc",ENTITY_SENTINEL:"ent",LINE_SENTINEL:"l",LINE_INDENT:"li",LINE_ALIGN:"la",LIST_TYPE:"lt"},a.sentinelConstants={LINE_SENTINEL_CHARACTER:"",ENTITY_SENTINEL_CHARACTER:""};var a=a||{};a.EntityManager=function(){function b(){this.entities_={};var a=["src","alt","width","height","style","class"];this.register("img",{render:function(a){c.assert(a.src,"image entity should have 'src'!");for(var b=["src","alt","width","height","style","class"],d="=4?this.codeMirror.on("changes",this.onCodeMirrorChange_):this.codeMirror.on("change",this.onCodeMirrorChange_),this.codeMirror.on("beforeChange",this.onCodeMirrorBeforeChange_),this.codeMirror.on("cursorActivity",this.onCursorActivity_),this.changeId_=0,this.outstandingChanges_={},this.dirtyLines_=[]}function c(a,b){return a.line-b.line||a.ch-b.ch}function d(a,b){return c(a,b)<=0}function e(a){return a[a.length-1]}function f(a){if(0===a.length)return 0;for(var b=0,c=0;c0&&this.trigger("attributesChange",this,f)},b.prototype.computeChangedAttributes_=function(a,b,c,d){var e,f={};for(e in a)f[e]=!0;for(e in b)f[e]=!0;for(e in f)e in b?e in a?a[e]!==b[e]&&(c[e]=b[e],d[e]=a[e]):(c[e]=b[e],d[e]=!1):(c[e]=!1,d[e]=a[e])},b.prototype.toggleLineAttribute=function(a,b){var c,d=this.getCurrentLineAttributes_();c=a in d&&d[a]===b?!1:b,this.setLineAttribute(a,c)},b.prototype.setLineAttribute=function(a,b){this.updateLineAttributesForSelection(function(c){b===!1?delete c[a]:c[a]=b})},b.prototype.updateLineAttributesForSelection=function(a){var b=this.codeMirror,c=b.getCursor("start"),d=b.getCursor("end"),e=c.line,f=d.line,g=b.getLine(f),h=this.areLineSentinelCharacters_(g.substr(0,d.ch));f>e&&h&&f--,this.updateLineAttributes(e,f,a)},b.prototype.updateLineAttributes=function(a,b,c){for(var d=a;b>=d;d++){var e=this.codeMirror.getLine(d),f=this.codeMirror.indexFromPos({line:d,ch:0});if(e[0]!==r){var g={};g[m.LINE_SENTINEL]=!0,c(g),this.insertText(f,r,g)}else this.updateTextAttributes(f,f+1,c,null,!0)}},b.prototype.replaceText=function(a,b,c,d,e){this.changeId_++;var f=o+this.changeId_;this.outstandingChanges_[f]={origOrigin:e,attributes:d};var g=this.codeMirror,h=g.posFromIndex(a),i="number"==typeof b?g.posFromIndex(b):null;g.replaceRange(c,h,i,f)},b.prototype.insertText=function(a,b,c,d){var e=this.codeMirror,f=e.getCursor(),g="RTCMADAPTER"==d&&!e.somethingSelected()&&a==e.indexFromPos(f);this.replaceText(a,null,b,c,d),g&&e.setCursor(f)},b.prototype.removeText=function(a,b,c){var d=this.codeMirror;d.replaceRange("",d.posFromIndex(a),d.posFromIndex(b),c)},b.prototype.insertEntityAtCursor=function(a,b,c){var d=this.codeMirror,e=d.indexFromPos(d.getCursor("head"));this.insertEntityAt(e,a,b,c)},b.prototype.insertEntityAt=function(b,c,d,e){this.codeMirror;this.insertEntity_(b,new a.Entity(c,d),e)},b.prototype.insertEntity_=function(a,b,c){this.replaceText(a,null,s,b.toAttributes(),c)},b.prototype.getAttributeSpans=function(a,b){for(var c=[],d=this.annotationList_.getAnnotatedSpansForSpan(new k(a,b-a)),e=0;ee&&(e=a.markLineSentinelCharactersForChangedLines_(f,f))}},0)}},b.prototype.addStyleWithCSS_=function(a){var b=document.getElementsByTagName("head")[0],c=document.createElement("style");c.type="text/css",c.styleSheet?c.styleSheet.cssText=a:c.appendChild(document.createTextNode(a)),b.appendChild(c)},b.prototype.getClassNameForAttributes_=function(b){var c="";for(var d in b){var e=b[d];if(d===m.LINE_SENTINEL)a.utils.assert(e===!0,"LINE_SENTINEL attribute should be true if it exists.");else{var f=(this.options_.cssPrefix||n)+d;if(e!==!0){d===m.FONT_SIZE&&"string"!=typeof e&&(e+="px");var g=e.toString().toLowerCase().replace(/[^a-z0-9-_]/g,"-");if(f+="-"+g,p[d]&&(q[d]||(q[d]={}),!q[d][g])){q[d][g]=!0;var h=p[d],i="function"==typeof h?h(e):h+": "+e,j=d==m.LINE_INDENT?"pre."+f:"."+f;this.addStyleWithCSS_(j+" { "+i+" }")}}c=c+" "+f}}return c},b.prototype.markEntity_=function(b){for(var c=b.annotation.attributes,d=a.Entity.fromAttributes(c),e=this.codeMirror,f=this,g=[],h=0;h0){for(var n=this.annotationList_.getAnnotatedSpansForSpan(new k(i,l.length)),o=0,p=0;p0){var r;"+input"===h.origin||"paste"===h.origin?r=this.currentAttributes_||{}:m in this.outstandingChanges_?(r=this.outstandingChanges_[m].attributes,m=this.outstandingChanges_[m].origOrigin,delete this.outstandingChanges_[m]):r={},this.annotationList_.insertAnnotatedSpan(new k(i,j.length),new g(r)),e.push({start:i,end:i,removedAttributes:{},removed:"",text:j,attributes:r,origin:m})}}this.markLineSentinelCharactersForChanges_(b),e.length>0&&this.trigger("change",this,e)},b.prototype.convertCoordinateSystemForChanges_=function(a){function b(a,b){return function(c){return d(c,b.from)?a(c):d(b.to,c)?a({line:c.line+b.text.length-1-(b.to.line-b.from.line),ch:b.to.line=0;i--){var j=a[i];g=b(g,j);var k=g(j.from),l=j.removed.join("\n"),m=j.text.join("\n");h.unshift({start:k,end:k+l.length,removed:l,text:m,origin:j.origin})}return h},b.prototype.markLineSentinelCharactersForChanges_=function(a){for(var b=Number.MAX_VALUE,c=-1,d=0;d1||e.removed[0].indexOf(r)>=0)&&(b=Math.min(b,f),c=Math.max(c,f)),e.text.length>1?(b=Math.min(b,f),c=Math.max(c,f+e.text.length-1)):e.text[0].indexOf(r)>=0&&(b=Math.min(b,f),c=Math.max(c,f))}c=Math.min(c,this.codeMirror.lineCount()-1),this.markLineSentinelCharactersForChangedLines_(b,c)},b.prototype.markLineSentinelCharactersForChangedLines_=function(a,b){if(a0&&this.lineIsListItemOrIndented_(a-1);)a--;if(b>-1)for(var c=this.codeMirror.lineCount();c>b+1&&this.lineIsListItemOrIndented_(b+1);)b++;for(var d=[],e=this.codeMirror,f=a;b>=f;f++){var g=e.getLine(f),h=e.getLineHandle(f);if(e.removeLineClass(h,"text",".*"),g.length>0)for(var i=g.indexOf(r);i>=0;){for(var j=i;i=d.length;)d.push(1);"o"===j?(f=this.makeOrderedListElement_(d[k]),d[k]++):"u"===j?(f=this.makeUnorderedListElement_(),d[k]=1):"t"===j?(f=this.makeTodoListElement_(!1,h),d[k]=1):"tc"===j&&(f=this.makeTodoListElement_(!0,h),d[k]=1);var l=this.getClassNameForAttributes_(i);""!==l&&this.codeMirror.addLineClass(a,"text",l),d=d.slice(0,k+1)}var n={inclusiveLeft:!0,collapsed:!0};f&&(n.replacedWith=f);var g=e.markText({line:a,ch:b},{line:a,ch:c},n);g.isForLineSentinel=!0},b.prototype.makeOrderedListElement_=function(a){return l.elt("div",a+".",{"class":"firepad-list-left"})},b.prototype.makeUnorderedListElement_=function(){return l.elt("div","•",{"class":"firepad-list-left"})},b.prototype.toggleTodo=function(a){var b,c=m.LIST_TYPE,d=this.getCurrentLineAttributes_();c in d&&("t"===d[c]||"tc"===d[c])?"t"===d[c]?b="tc":"tc"===d[c]&&(b=a?"t":!1):b="t",this.setLineAttribute(c,b)},b.prototype.makeTodoListElement_=function(a,b){var c={type:"checkbox","class":"firepad-todo-left"};a&&(c.checked=!0);var d=l.elt("input",!1,c),e=this;return l.on(d,"click",l.stopEventAnd(function(a){e.codeMirror.setCursor({line:b(),ch:1}),e.toggleTodo(!0)})),d},b.prototype.lineIsListItemOrIndented_=function(a){var b=this.getLineAttributes_(a);return(b[m.LIST_TYPE]||!1)!==!1||0!==(b[m.LINE_INDENT]||0)},b.prototype.onCursorActivity_=function(){var a=this;setTimeout(function(){a.updateCurrentAttributes_()},1)},b.prototype.getCurrentAttributes_=function(){return this.currentAttributes_||this.updateCurrentAttributes_(),this.currentAttributes_},b.prototype.updateCurrentAttributes_=function(){var b=this.codeMirror,c=b.indexFromPos(b.getCursor("anchor")),d=b.indexFromPos(b.getCursor("head")),e=d;if(c>d){for(;e0&&(f=this.getRange(e-1,e),"\n"===f||f===r);)e--;var g=this.annotationList_.getAnnotatedSpansForPos(e);this.currentAttributes_={};var h={};g.length>0&&!(m.LINE_SENTINEL in g[0].annotation.attributes)?h=g[0].annotation.attributes:g.length>1&&(a.utils.assert(!(m.LINE_SENTINEL in g[1].annotation.attributes),"Cursor can't be between two line sentinel characters."),h=g[1].annotation.attributes);for(var i in h)"l"!==i&&"lt"!==i&&"li"!==i&&0!==i.indexOf(m.ENTITY_SENTINEL)&&(this.currentAttributes_[i]=h[i])},b.prototype.getCurrentLineAttributes_=function(){var a=this.codeMirror,b=a.getCursor("anchor"),c=a.getCursor("head"),d=c.line;return 0===c.ch&&b.line0&&d[0]===r){var e=this.codeMirror.indexFromPos({line:b,ch:0}),f=this.annotationList_.getAnnotatedSpansForSpan(new k(e,1));a.utils.assert(1===f.length);for(var g in f[0].annotation.attributes)c[g]=f[0].annotation.attributes[g]}return c},b.prototype.clearAnnotations_=function(){this.annotationList_.updateSpan(new k(0,this.end()),function(a,b){return new g({})})},b.prototype.newline=function(){var a=this.codeMirror,b=this;if(this.emptySelection_()){var c=a.getCursor("head").line,d=this.getLineAttributes_(c),e=d[m.LIST_TYPE];e&&1===a.getLine(c).length?this.updateLineAttributes(c,c,function(a){delete a[m.LIST_TYPE],delete a[m.LINE_INDENT]}):(a.replaceSelection("\n","end","+input"),this.updateLineAttributes(c+1,c+1,function(a){for(var f in d)a[f]=d[f];"tc"===e&&(a[m.LIST_TYPE]="t"),b.trigger("newLine",{line:c+1,attr:a})}))}else a.replaceSelection("\n","end","+input")},b.prototype.deleteLeft=function(){var a=this.codeMirror,b=a.getCursor("head"),c=this.getLineAttributes_(b.line),d=c[m.LIST_TYPE],e=c[m.LINE_INDENT],f=this.emptySelection_()&&1===b.ch;f&&d?this.updateLineAttributes(b.line,b.line,function(a){delete a[m.LIST_TYPE],delete a[m.LINE_INDENT]}):f&&e&&e>0?this.unindent():a.deleteH(-1,"char")},b.prototype.deleteRight=function(){var a=this.codeMirror,b=a.getCursor("head"),c=a.getLine(b.line),d=this.areLineSentinelCharacters_(c),e=b.line+11?a[m.LINE_INDENT]=b-1:(delete a[m.LIST_TYPE],delete a[m.LINE_INDENT])})},b.prototype.getText=function(){return this.codeMirror.getValue().replace(new RegExp(r,"g"),"")},b.prototype.areLineSentinelCharacters_=function(a){for(var b=0;bb.line?1:a.chb.ch?1:0}function d(a,b){return 0===c(a,b)}function e(a){var b=a.lineCount()-1;return a.indexFromPos({line:b,ch:a.getLine(b).length})}function f(a,b){if(!a)throw new Error(b||"assertion error")}function g(a,b){var c=a[b];a[b]=function(){c.apply(a,arguments)}}function h(a){for(var b in a)return!1;return!0}function i(a,b){if("string"!=typeof a)throw new TypeError("Expected a string");a=a.replace(/^#/,""),3===a.length&&(a=a[0]+a[0]+a[1]+a[1]+a[2]+a[2]);var c=parseInt(a,16),d=[c>>16,c>>8&255,255&c],e="rgb";return j(b)&&(e="rgba",d.push(b)),e+"("+d.join(",")+")"}function j(a){return null!==a&&void 0!==a}var k=a.TextOperation,l=a.WrappedOperation,m=a.Cursor;return b.prototype.detach=function(){this.rtcm.off("change",this.onChange),this.rtcm.off("attributesChange",this.onAttributesChange),this.cm.off("cursorActivity",this.onCursorActivity),this.cm.off("focus",this.onFocus),this.cm.off("blur",this.onBlur)},b.operationFromCodeMirrorChanges=function(a,b){for(var c=e(b),d=(new k).retain(c),f=(new k).retain(c),g=a.length-1;g>=0;g--){var h=a[g],i=h.start,j=c-i-h.text.length;d=(new k).retain(i)["delete"](h.removed.length).insert(h.text,h.attributes).retain(j).compose(d),f=f.compose((new k).retain(i)["delete"](h.text.length).insert(h.removed,h.removedAttributes).retain(j)),c+=h.removed.length-h.text.length}return[d,f]},b.operationFromAttributesChanges=function(a,b){for(var c=e(b),d=new k,g=new k,h=0,i=0;i=0),d.retain(l),g.retain(l);var m=j.end-j.start;d.retain(m,j.attributes),g.retain(m,j.attributesInverse),h=j.start+m}return d.retain(c-h),g.retain(c-h),[d,g]},b.prototype.registerCallbacks=function(a){this.callbacks=a},b.prototype.onChange=function(a,c){if("RTCMADAPTER"!==c[0].origin){var d=b.operationFromCodeMirrorChanges(c,this.cm);this.trigger("change",d[0],d[1])}},b.prototype.onAttributesChange=function(a,c){if("RTCMADAPTER"!==c[0].origin){var d=b.operationFromAttributesChanges(c,this.cm);this.trigger("change",d[0],d[1])}},b.prototype.onCursorActivity=function(){var a=this;setTimeout(function(){a.trigger("cursorActivity")},1)},b.prototype.onFocus=function(){this.trigger("focus")},b.prototype.onBlur=function(){this.cm.somethingSelected()||this.trigger("blur")},b.prototype.getValue=function(){return this.cm.getValue()},b.prototype.getCursor=function(){var a,b=this.cm,c=b.getCursor(),e=b.indexFromPos(c);if(b.somethingSelected()){var f=b.getCursor(!0),g=d(c,f)?b.getCursor(!1):f;a=b.indexFromPos(g)}else a=e;return new m(e,a)},b.prototype.setCursor=function(a){this.cm.setSelection(this.cm.posFromIndex(a.position),this.cm.posFromIndex(a.selectionEnd))},b.prototype.addStyleRule=function(a){if("undefined"!=typeof document&&null!==document){if(!this.addedStyleRules){this.addedStyleRules={};var b=document.createElement("style");document.documentElement.getElementsByTagName("head")[0].appendChild(b),this.addedStyleSheet=b.sheet}if(!this.addedStyleRules[a])return this.addedStyleRules[a]=!0,this.addedStyleSheet.insertRule(a,0)}},b.prototype.setOtherCursor=function(a,b,c){var d=this.cm.posFromIndex(a.position);if("string"==typeof b&&b.match(/^#[a-fA-F0-9]{3,6}$/)){var e=this.rtcm.end();if("object"==typeof a&&"number"==typeof a.position&&"number"==typeof a.selectionEnd&&!(a.position<0||a.position>e||a.selectionEnd<0||a.selectionEnd>e)){if(a.position===a.selectionEnd){var f=this.cm.cursorCoords(d),g=document.createElement("span");return g.className="other-client",g.style.borderLeftWidth="2px",g.style.borderLeftStyle="solid",g.style.borderLeftColor=b,g.style.marginLeft=g.style.marginRight="-1px",g.style.height=.9*(f.bottom-f.top)+"px",g.setAttribute("data-clientid",c),g.style.zIndex=0,this.cm.setBookmark(d,{widget:g,insertLeft:!0})}var h="selection-"+b.replace("#",""),j=.4,k="."+h+" { background: "+i(b)+";\n background: "+i(b,j)+";}";this.addStyleRule(k);var l,m;return a.selectionEnd>a.position?(l=d,m=this.cm.posFromIndex(a.selectionEnd)):(l=this.cm.posFromIndex(a.selectionEnd),m=d),this.cm.markText(l,m,{className:h})}}},b.prototype.trigger=function(a){var b=Array.prototype.slice.call(arguments,1),c=this.callbacks&&this.callbacks[a];c&&c.apply(this,b)},b.prototype.applyOperation=function(a){a.ops.length>10&&this.rtcm.codeMirror.getWrapperElement().setAttribute("style","display: none");for(var b=a.ops,c=0,d=0,e=b.length;e>d;d++){var f=b[d];f.isRetain()?(h(f.attributes)||this.rtcm.updateTextAttributes(c,c+f.chars,function(a){for(var b in f.attributes)f.attributes[b]===!1?delete a[b]:a[b]=f.attributes[b]},"RTCMADAPTER",!0),c+=f.chars):f.isInsert()?(this.rtcm.insertText(c,f.text,f.attributes,"RTCMADAPTER"),c+=f.text.length):f.isDelete()&&this.rtcm.removeText(c,c+f.chars,"RTCMADAPTER")}a.ops.length>10&&(this.rtcm.codeMirror.getWrapperElement().setAttribute("style",""),this.rtcm.codeMirror.refresh())},b.prototype.registerUndo=function(a){this.cm.undo=a},b.prototype.registerRedo=function(a){this.cm.redo=a},b.prototype.invertOperation=function(a){for(var b,c,d=0,e=this.rtcm.codeMirror,f=new k,g=0;g0)&&c.newline(b)}function h(b,c){for(var d=b.textFormatting,e=b.lineFormatting,f=c.split(";"),g=0;g=0,l=j.indexOf("line-through")>=0;d=d.underline(k).strike(l);break;case"font-weight":var m="bold"===j||parseInt(j)>=600;d=d.bold(m);break;case"font-style":var n="italic"===j||"oblique"===j;d=d.italic(n);break;case"color":d=d.color(j);break;case"background-color":d=d.backgroundColor(j);break;case"text-align":e=e.align(j);break;case"font-size":var o=null,p=["px","pt","%","em","xx-small","x-small","small","medium","large","x-large","xx-large","smaller","larger"];a.utils.stringEndsWith(j,p)?o=j:parseInt(j)&&(o=parseInt(j)+"px"),o&&(d=d.fontSize(o));break;case"font-family":var q=a.utils.trim(j.split(",")[0]);q=q.replace(/['"]/g,""),q=q.replace(/\w\S*/g,function(a){return a.charAt(0).toUpperCase()+a.substr(1).toLowerCase()}),d=d.font(q)}}}return b.withLineFormatting(e).withTextFormatting(d)}var i=a.LineFormatting.LIST_TYPE;b.prototype.withTextFormatting=function(a){return new b(this.listType,this.lineFormatting,a)},b.prototype.withLineFormatting=function(a){return new b(this.listType,a,this.textFormatting)},b.prototype.withListType=function(a){return new b(a,this.lineFormatting,this.textFormatting)},b.prototype.withIncreasedIndent=function(){var a=this.lineFormatting.indent(this.lineFormatting.getIndent()+1);return new b(this.listType,a,this.textFormatting)},b.prototype.withAlign=function(a){var c=this.lineFormatting.align(a);return new b(this.listType,c,this.textFormatting)},c.prototype.newlineIfNonEmpty=function(a){this.cleanLine_(),this.currentLine.length>0&&this.newline(a)},c.prototype.newlineIfNonEmptyOrListItem=function(a){this.cleanLine_(),(this.currentLine.length>0||null!==this.currentLineListItemType)&&this.newline(a)},c.prototype.newline=function(b){this.cleanLine_();var c=b.lineFormatting;null!==this.currentLineListItemType&&(c=c.listItem(this.currentLineListItemType),this.currentLineListItemType=null),this.lines.push(a.Line(this.currentLine,c)),this.currentLine=[]},c.prototype.makeListItem=function(a){this.currentLineListItemType=a},c.prototype.cleanLine_=function(){if(this.currentLine.length>0){var a=this.currentLine.length-1;this.currentLine[0].text=this.currentLine[0].text.replace(/^ +/,""),this.currentLine[a].text=this.currentLine[a].text.replace(/ +$/g,"");for(var b=0;b":a===i.UNORDERED?"
      ":'
        '}function c(a){return a===i.ORDERED?"":"
      "}function d(a,b){return a===b||a===i.TODO&&b===i.TODOCHECKED||a===i.TODOCHECKED&&b===i.TODO}function e(a){return a.replace(/&/g,"&").replace(/"/g,""").replace(/'/g,"'").replace(//g,">").replace(/\u00a0/g," ")}function f(f,k){for(var l="",m=!0,n=[],o=!1,p=!0,q=!0,r=0,s=f.ops[r],t=!1;s;){g.assert(s.isInsert());var u=s.attributes;if(m){m=!1;var v=0,w=null,x="left";h.LINE_SENTINEL in u&&(v=u[h.LINE_INDENT]||0,w=u[h.LIST_TYPE]||null,x=u[h.LINE_ALIGN]||"left"),w&&(v=v||1),o?(l+="",o=!1):p||(q&&(l+="
      "),l+=""),p=!1,g.assert(v>=0,"Indent must not be negative.");for(;n.length>v||v===n.length&&null!==w&&!d(w,n[n.length-1]);)l+=c(n.pop());for(;n.length",o=!0}else l+="";q=!0}if(h.LINE_SENTINEL in u)s=f.ops[++r];else if(h.ENTITY_SENTINEL in u){for(var B=0;B"),I&&(F=""+F)}var K=s.text,L=K.indexOf("\n");L>=0?(m=!0,s=L0&&(q=!1),l+=E+e(K)+F}}for(o?l+="":p||(q&&(l+=" "),l+="");n.length>0;)l+=c(n.pop());return t&&(l=j+l),l}var g=a.utils,h=a.AttributeConstants,i=a.LineFormatting.LIST_TYPE,j='\n';return f}();var a=a||{};a.textPiecesToInserts=function(b,c){function d(c,d){c instanceof a.Text&&(d=c.formatting.attributes,c=c.text),f.push({string:c,attributes:d}),b="\n"===c[c.length-1]}function e(c,e){b&&d(a.sentinelConstants.LINE_SENTINEL_CHARACTER,c.formatting.attributes);for(var f=0;f",function(c,d){return a.document?(d.close(),b()):(a.document=d.document,void b())})},b.prototype.getHtml=function(b){var c=this;if(this.zombie_)throw new Error("You can't use a firepad.Headless after calling dispose()!");c.initializeFakeDom(function(){c.getDocument(function(d){b(a.SerializeHtml(d,c.entityManager_))})})},b.prototype.setHtml=function(b,d){var e=this;if(this.zombie_)throw new Error("You can't use a firepad.Headless after calling dispose()!");e.initializeFakeDom(function(){for(var g=f(b,e.entityManager_),h=a.textPiecesToInserts(!0,g),i=new c,j=0;jc?c*(1+b):c+b-b*c,f=2*c-d,g=function(a){return 0>a&&(a+=1),a>1&&(a-=1),1>6*a?f+6*(d-f)*a:1>2*a?d:2>3*a?f+6*(d-f)*(2/3-a):f};return e(g(a+1/3),g(a),g(a-1/3))}if(!a.RichTextCodeMirrorAdapter)throw new Error("Oops! It looks like you're trying to include lib/firepad.js directly. This is actually one of many source files that make up firepad. You want dist/firepad.js instead.");var g=a.RichTextCodeMirrorAdapter,h=a.RichTextCodeMirror,i=a.RichTextToolbar,j=a.ACEAdapter,k=a.MonacoAdapter,l=a.FirebaseAdapter,m=a.EditorClient,n=a.EntityManager,o=a.AttributeConstants,p=a.utils,q=(a.LineFormatting.LIST_TYPE,b.CodeMirror),r=b.ace,s=b.monaco;return p.makeEventEmitter(c),c.fromCodeMirror=c,c.fromACE=c,c.fromMonaco=c,c.prototype.dispose=function(){this.zombie_=!0,this.codeMirror_?editorWrapper=this.codeMirror_.getWrapperElement():this.ace_?editorWrapper=this.ace_.container:editorWrapper=this.monaco_.getDomNode(),this.firepadWrapper_.removeChild(editorWrapper),this.firepadWrapper_.parentNode.replaceChild(editorWrapper,this.firepadWrapper_),this.editor_.firepad=null,this.codeMirror_&&"richtext"===this.codeMirror_.getOption("keyMap")&&this.codeMirror_.setOption("keyMap","default"),this.firebaseAdapter_.dispose(),this.editorAdapter_.detach(),this.richTextCodeMirror_&&this.richTextCodeMirror_.detach()},c.prototype.setUserId=function(a){this.firebaseAdapter_.setUserId(a)},c.prototype.setUserColor=function(a){this.firebaseAdapter_.setColor(a)},c.prototype.getText=function(){return this.assertReady_("getText"),this.codeMirror_?this.richTextCodeMirror_.getText():this.ace_?this.ace_.getSession().getDocument().getValue():this.monaco_.getModel().getValue()},c.prototype.setText=function(a){return this.assertReady_("setText"),this.monaco_?this.monaco_.getModel().setValue(a):this.ace_?this.ace_.getSession().getDocument().setValue(a):(this.codeMirror_.getWrapperElement().setAttribute("style","display: none"),this.codeMirror_.setValue(""),this.insertText(0,a),this.codeMirror_.getWrapperElement().setAttribute("style",""),this.codeMirror_.refresh(),void this.editorAdapter_.setCursor({position:0,selectionEnd:0}))},c.prototype.insertTextAtCursor=function(a){this.insertText(this.codeMirror_.indexFromPos(this.codeMirror_.getCursor()),a)},c.prototype.insertText=function(b,c){p.assert(!this.ace_,"Not supported for ace yet."),p.assert(!this.monaco_,"Not supported for monaco yet."),this.assertReady_("insertText"),"[object Array]"!==Object.prototype.toString.call(c)&&(c=[c]);var d=this;d.codeMirror_.operation(function(){for(var e=0===b,f=a.textPiecesToInserts(e,c),g=0;g