Deploy 0.8.0

This commit is contained in:
Kevin Jahns 2016-01-15 00:01:56 +01:00
parent ff006c92d7
commit 0ec83aa431
97 changed files with 37273 additions and 35 deletions

2
.gitignore vendored
View File

@ -1,5 +1,5 @@
node_modules
Examples/bower_components
./Examples/bower_components
.directory
.codio
.settings

View File

@ -9,7 +9,15 @@
"license": "MIT",
"ignore": [],
"dependencies": {
"yjs": "../",
"y-webrtc": "~0.6.4"
"yjs": "~0.7.6",
"y-array": "~0.7.5",
"y-map": "~0.7.2",
"y-memory": "~0.7.0",
"y-richtext": "~0.7.5",
"y-webrtc": "~0.7.1",
"y-websockets-client": "~0.7.10",
"y-text": "~0.7.1",
"y-indexeddb": "~0.7.1",
"quill": "~0.20.1"
}
}

View File

@ -0,0 +1,35 @@
{
"name": "quill",
"version": "0.20.1",
"homepage": "http://quilljs.com",
"authors": [
"Jason Chen <jhchen7@gmail.com>"
],
"contributors": [
"Byron Milligan <byronner@gmail.com>",
"Keegan Poppen <keegan.poppen@gmail.com>"
],
"description": "Cross browser rich text editor",
"main": "dist/quill.js",
"license": "BSD",
"ignore": [
"**/.*",
"config",
"examples",
"src",
"test",
"Gruntfile.coffee",
"index.js",
"package.json"
],
"_release": "0.20.1",
"_resolution": {
"type": "version",
"tag": "v0.20.1",
"commit": "07b7f54dfe8997e1c8fe180be6f0817693215c48"
},
"_source": "git://github.com/quilljs/quill.git",
"_target": "~0.20.1",
"_originalSource": "quill",
"_direct": true
}

30
Examples/bower_components/quill/LICENSE vendored Normal file
View File

@ -0,0 +1,30 @@
Copyright (c) 2014, Jason Chen
Copyright (c) 2013, salesforce.com
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,136 @@
# [Quill Rich Text Editor](http://quilljs.com/) [![Build Status](https://travis-ci.org/quilljs/quill.svg?branch=master)](http://travis-ci.org/quilljs/quill)
[![Webdriver Test Status](https://saucelabs.com/browser-matrix/quill-master.svg)](https://saucelabs.com/u/quill)
Quill is a modern rich text editor built for compatibility and extensibility. It was created by [Jason Chen](https://twitter.com/jhchen) and [Byron Milligan](https://twitter.com/byronmilligan) and open sourced by [Salesforce.com](http://www.salesforce.com).
To get started, check out the [Quill Github Page](http://quilljs.com/) or jump straight into the [demo](http://quilljs.com/examples/).
## Quickstart
Instantiate a new Quill object with a css selector for the div that should become the editor.
```html
<!-- Create the toolbar container -->
<div id="toolbar">
<button class="ql-bold">Bold</button>
<button class="ql-italic">Italic</button>
</div>
<!-- Create the editor container -->
<div id="editor">
<div>Hello World!</div>
</div>
<!-- Include the Quill library -->
<script src="http://cdn.quilljs.com/latest/quill.js"></script>
<!-- Initialize Quill editor -->
<script>
var editor = new Quill('#editor');
editor.addModule('toolbar', { container: '#toolbar' });
</script>
```
## Downloading Quill
There are a number of ways to download the latest or versioned copy of Quill.
- npm: `npm install quill`
- bower: `bower install quill`
- tar: https://github.com/quilljs/quill/releases
### CDN
```html
<link rel="stylesheet" href="//cdn.quilljs.com/0.19.10/quill.snow.css" />
<script src="//cdn.quilljs.com/0.19.10/quill.min.js"></script>
```
## Local Development
Quill's source is in [Coffeescript](http://coffeescript.org/) and utilizes [Browserify](http://browserify.org/) to organize its files.
### Installation
npm install -g grunt-cli
npm install
### Building
grunt dist - compile and browserify
grunt server - starts a local server that will build and serve assets on the fly
### Examples
With the local server (`grunt server`) running you can try out some minimal examples on:
- [localhost:9000/examples/index.html](http://localhost:9000/examples/index.html)
- [localhost:9000/examples/advanced.html](http://localhost:9000/examples/advanced.html)
Quill [releases](https://github.com/quilljs/quill/releases) also contain these examples as built static files you can try without needing to run the local development server.
### Testing
grunt test:unit - runs javascript test suite with Chrome
grunt test:e2e - runs end to end tests with Webdriver + Chrome
grunt test:coverage - run tests measuring coverage with Chrome
Tests are run by [Karma](http://karma-runner.github.io/) and [Protractor](https://github.com/angular/protractor) using [Jasmine](http://jasmine.github.io/). Check out `Gruntfile.coffee` and `config/grunt/` for more testing options.
## Contributing
### Community
Get help or stay up to date.
- Follow [@quilljs](https://twitter.com/quilljs) on Twitter
- Ask questions on [Stack Overflow](http://stackoverflow.com/questions/tagged/quill) (tag with quill)
- If a private channel is required, you may also email support@quilljs.com
### Bug Reports
Search through [Github Issues](https://github.com/quilljs/quill/issues) to see if the bug has already been reported. If so, please comment with any additional information about the bug.
For new issues, create a new issue and tag with the appropriate browser tag. Include as much detail as possible such as:
- Detailed description of faulty behavior
- Affected platforms
- Steps for reproduction
- Failing test case
The more details you provide, the more likely we or someone else will be able to find and fix the bug.
### Feature Requests
We welcome feature requests. Please make sure they are within scope of Quill's goals and submit them in [Github Issues](https://github.com/quilljs/quill/issues) tagged with the 'feature' tag. The more complete and compelling the request, the more likely it will be implemented. Garnering community support will help as well!
### Pull Requests
1. Please check to make sure your plans fall within Quill's scope (likely through Github Issues).
2. Fork Quill
3. Branch off of the 'develop' branch.
4. Implement your changes.
5. Submit a Pull Request.
Pull requests will not be accepted without adhering to the following:
1. Conform to existing [coding styles](docs/style-guide.md).
2. New functionality are accompanied by tests.
3. Serve a single atomic purpose (add one feature or fix one bug)
4. Introduce only changes that further the PR's singular purpose (ex. do not tweak an unrelated config along with adding your feature).
**Important:** By issuing a Pull Request you agree to allow the project owners to license your work under the terms of the [License](https://github.com/quilljs/quill/blob/master/LICENSE).
## Thanks
[Swift](https://github.com/theycallmeswift), for providing the npm package name. If you're looking for his blogging engine see [v0.1.5-1](https://www.npmjs.org/package/quill/0.1.5-1).
## License
BSD 3-clause

View File

@ -0,0 +1,23 @@
{
"name": "quill",
"version": "0.20.1",
"homepage": "http://quilljs.com",
"authors": ["Jason Chen <jhchen7@gmail.com>"],
"contributors": [
"Byron Milligan <byronner@gmail.com>",
"Keegan Poppen <keegan.poppen@gmail.com>"
],
"description": "Cross browser rich text editor",
"main": "dist/quill.js",
"license": "BSD",
"ignore": [
"**/.*",
"config",
"examples",
"src",
"test",
"Gruntfile.coffee",
"index.js",
"package.json"
]
}

View File

@ -0,0 +1,194 @@
/*! Quill Editor v0.20.1
* https://quilljs.com/
* Copyright (c) 2014, Jason Chen
* Copyright (c) 2013, salesforce.com
*/
.ql-image-tooltip {
padding: 10px;
width: 300px;
}
.ql-image-tooltip:after {
clear: both;
content: "";
display: table;
}
.ql-image-tooltip a {
border: 1px solid #000;
box-sizing: border-box;
display: inline-block;
float: left;
padding: 5px;
text-align: center;
width: 50%;
}
.ql-image-tooltip img {
bottom: 0;
left: 0;
margin: auto;
max-height: 100%;
max-width: 100%;
position: absolute;
right: 0;
top: 0;
}
.ql-image-tooltip .input {
box-sizing: border-box;
width: 100%;
}
.ql-image-tooltip .preview {
margin: 10px 0px;
position: relative;
border: 1px dashed #000;
height: 200px;
}
.ql-image-tooltip .preview span {
display: inline-block;
position: absolute;
text-align: center;
top: 40%;
width: 100%;
}
.ql-link-tooltip {
padding: 5px 10px;
}
.ql-link-tooltip input.input {
width: 170px;
}
.ql-link-tooltip input.input,
.ql-link-tooltip a.done {
display: none;
}
.ql-link-tooltip a.change {
margin-right: 4px;
}
.ql-link-tooltip.editing input.input,
.ql-link-tooltip.editing a.done {
display: inline-block;
}
.ql-link-tooltip.editing a.url,
.ql-link-tooltip.editing a.change,
.ql-link-tooltip.editing a.remove {
display: none;
}
.ql-multi-cursor {
position: absolute;
left: 0;
top: 0;
z-index: 1000;
}
.ql-multi-cursor .cursor {
margin-left: -1px;
position: absolute;
}
.ql-multi-cursor .cursor-flag {
bottom: 100%;
position: absolute;
white-space: nowrap;
}
.ql-multi-cursor .cursor-name {
display: inline-block;
color: #fff;
padding: 2px 8px;
}
.ql-multi-cursor .cursor-caret {
height: 100%;
position: absolute;
width: 2px;
}
.ql-multi-cursor .cursor.hidden .cursor-flag {
display: none;
}
.ql-multi-cursor .cursor.top .cursor-flag {
bottom: auto;
top: 100%;
}
.ql-multi-cursor .cursor.right .cursor-flag {
right: -2px;
}
.ql-paste-manager {
left: -100000px;
position: absolute;
top: 50%;
}
.ql-toolbar {
box-sizing: border-box;
}
.ql-tooltip {
background-color: #fff;
border: 1px solid #000;
box-sizing: border-box;
position: absolute;
top: 0px;
white-space: nowrap;
z-index: 2000;
}
.ql-tooltip a {
cursor: pointer;
text-decoration: none;
}
.ql-container {
box-sizing: border-box;
cursor: text;
font-family: Helvetica, 'Arial', sans-serif;
font-size: 13px;
height: 100%;
line-height: 1.42;
margin: 0px;
overflow-x: hidden;
overflow-y: auto;
padding: 12px 15px;
position: relative;
}
.ql-editor {
box-sizing: border-box;
min-height: 100%;
outline: none;
tab-size: 4;
white-space: pre-wrap;
}
.ql-editor div {
margin: 0;
padding: 0;
}
.ql-editor a {
text-decoration: underline;
}
.ql-editor b {
font-weight: bold;
}
.ql-editor i {
font-style: italic;
}
.ql-editor s {
text-decoration: line-through;
}
.ql-editor u {
text-decoration: underline;
}
.ql-editor a,
.ql-editor b,
.ql-editor i,
.ql-editor s,
.ql-editor u,
.ql-editor span {
background-color: inherit;
}
.ql-editor img {
max-width: 100%;
}
.ql-editor blockquote,
.ql-editor ol,
.ql-editor ul {
margin: 0 0 0 2em;
padding: 0;
}
.ql-editor ol {
list-style-type: decimal;
}
.ql-editor ul {
list-style-type: disc;
}
.ql-editor.ql-ie-9 br,
.ql-editor.ql-ie-10 br {
display: none;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,917 @@
/*! Quill Editor v0.20.1
* https://quilljs.com/
* Copyright (c) 2014, Jason Chen
* Copyright (c) 2013, salesforce.com
*/
.ql-image-tooltip {
padding: 10px;
width: 300px;
}
.ql-image-tooltip:after {
clear: both;
content: "";
display: table;
}
.ql-image-tooltip a {
border: 1px solid #000;
box-sizing: border-box;
display: inline-block;
float: left;
padding: 5px;
text-align: center;
width: 50%;
}
.ql-image-tooltip img {
bottom: 0;
left: 0;
margin: auto;
max-height: 100%;
max-width: 100%;
position: absolute;
right: 0;
top: 0;
}
.ql-image-tooltip .input {
box-sizing: border-box;
width: 100%;
}
.ql-image-tooltip .preview {
margin: 10px 0px;
position: relative;
border: 1px dashed #000;
height: 200px;
}
.ql-image-tooltip .preview span {
display: inline-block;
position: absolute;
text-align: center;
top: 40%;
width: 100%;
}
.ql-link-tooltip {
padding: 5px 10px;
}
.ql-link-tooltip input.input {
width: 170px;
}
.ql-link-tooltip input.input,
.ql-link-tooltip a.done {
display: none;
}
.ql-link-tooltip a.change {
margin-right: 4px;
}
.ql-link-tooltip.editing input.input,
.ql-link-tooltip.editing a.done {
display: inline-block;
}
.ql-link-tooltip.editing a.url,
.ql-link-tooltip.editing a.change,
.ql-link-tooltip.editing a.remove {
display: none;
}
.ql-multi-cursor {
position: absolute;
left: 0;
top: 0;
z-index: 1000;
}
.ql-multi-cursor .cursor {
margin-left: -1px;
position: absolute;
}
.ql-multi-cursor .cursor-flag {
bottom: 100%;
position: absolute;
white-space: nowrap;
}
.ql-multi-cursor .cursor-name {
display: inline-block;
color: #fff;
padding: 2px 8px;
}
.ql-multi-cursor .cursor-caret {
height: 100%;
position: absolute;
width: 2px;
}
.ql-multi-cursor .cursor.hidden .cursor-flag {
display: none;
}
.ql-multi-cursor .cursor.top .cursor-flag {
bottom: auto;
top: 100%;
}
.ql-multi-cursor .cursor.right .cursor-flag {
right: -2px;
}
.ql-paste-manager {
left: -100000px;
position: absolute;
top: 50%;
}
.ql-toolbar {
box-sizing: border-box;
}
.ql-tooltip {
background-color: #fff;
border: 1px solid #000;
box-sizing: border-box;
position: absolute;
top: 0px;
white-space: nowrap;
z-index: 2000;
}
.ql-tooltip a {
cursor: pointer;
text-decoration: none;
}
.ql-container {
box-sizing: border-box;
cursor: text;
font-family: Helvetica, 'Arial', sans-serif;
font-size: 13px;
height: 100%;
line-height: 1.42;
margin: 0px;
overflow-x: hidden;
overflow-y: auto;
padding: 12px 15px;
position: relative;
}
.ql-editor {
box-sizing: border-box;
min-height: 100%;
outline: none;
tab-size: 4;
white-space: pre-wrap;
}
.ql-editor div {
margin: 0;
padding: 0;
}
.ql-editor a {
text-decoration: underline;
}
.ql-editor b {
font-weight: bold;
}
.ql-editor i {
font-style: italic;
}
.ql-editor s {
text-decoration: line-through;
}
.ql-editor u {
text-decoration: underline;
}
.ql-editor a,
.ql-editor b,
.ql-editor i,
.ql-editor s,
.ql-editor u,
.ql-editor span {
background-color: inherit;
}
.ql-editor img {
max-width: 100%;
}
.ql-editor blockquote,
.ql-editor ol,
.ql-editor ul {
margin: 0 0 0 2em;
padding: 0;
}
.ql-editor ol {
list-style-type: decimal;
}
.ql-editor ul {
list-style-type: disc;
}
.ql-editor.ql-ie-9 br,
.ql-editor.ql-ie-10 br {
display: none;
}
.ql-snow .ql-image-tooltip a {
border: 1px solid #06c;
}
.ql-snow .ql-image-tooltip a.insert {
background-color: #06c;
color: #fff;
}
.ql-snow .ql-image-tooltip .preview {
border-color: #ccc;
color: #ccc;
}
.ql-snow .ql-link-tooltip a,
.ql-snow .ql-link-tooltip span {
line-height: 25px;
}
.ql-snow .ql-multi-cursor .cursor-name {
border-radius: 4px;
font-size: 11px;
font-family: Arial;
margin-left: -50%;
padding: 4px 10px;
}
.ql-snow .ql-multi-cursor .cursor-triangle {
border-left: 4px solid transparent;
border-right: 4px solid transparent;
height: 0px;
margin-left: -3px;
width: 0px;
}
.ql-snow .ql-multi-cursor .cursor.left .cursor-name {
margin-left: -8px;
}
.ql-snow .ql-multi-cursor .cursor.right .cursor-flag {
right: auto;
}
.ql-snow .ql-multi-cursor .cursor.right .cursor-name {
margin-left: -100%;
margin-right: -8px;
}
.ql-snow .ql-multi-cursor .cursor-triangle.bottom {
border-top: 4px solid transparent;
display: block;
margin-bottom: -1px;
}
.ql-snow .ql-multi-cursor .cursor-triangle.top {
border-bottom: 4px solid transparent;
display: none;
margin-top: -1px;
}
.ql-snow .ql-multi-cursor .cursor.top .cursor-triangle.bottom {
display: none;
}
.ql-snow .ql-multi-cursor .cursor.top .cursor-triangle.top {
display: block;
}
.ql-snow.ql-toolbar {
box-sizing: border-box;
padding: 8px;
user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
}
.ql-snow.ql-toolbar .ql-format-group {
display: inline-block;
margin-right: 15px;
vertical-align: middle;
}
.ql-snow.ql-toolbar .ql-format-separator {
box-sizing: border-box;
background-color: #ddd;
display: inline-block;
height: 14px;
margin-left: 4px;
margin-right: 4px;
vertical-align: middle;
width: 1px;
}
.ql-snow.ql-toolbar .ql-format-button {
box-sizing: border-box;
display: inline-block;
height: 24px;
line-height: 24px;
vertical-align: middle;
background-position: center center;
background-repeat: no-repeat;
background-size: 18px 18px;
box-sizing: border-box;
cursor: pointer;
text-align: center;
width: 24px;
}
.ql-snow.ql-toolbar .ql-picker {
box-sizing: border-box;
color: #444;
display: inline-block;
font-family: 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif;
font-size: 14px;
font-weight: 500;
position: relative;
}
.ql-snow.ql-toolbar .ql-picker .ql-picker-label {
box-sizing: border-box;
display: inline-block;
height: 24px;
line-height: 24px;
vertical-align: middle;
background-color: #fff;
background-position: right center;
background-repeat: no-repeat;
background-size: 18px 18px;
border: 1px solid transparent;
cursor: pointer;
position: relative;
width: 100%;
}
.ql-snow.ql-toolbar .ql-picker .ql-picker-label.ql-active,
.ql-snow.ql-toolbar .ql-picker .ql-picker-label:hover {
color: #06c;
}
.ql-snow.ql-toolbar .ql-picker .ql-picker-options {
background-color: #fff;
border: 1px solid transparent;
box-sizing: border-box;
display: none;
padding: 4px 8px;
position: absolute;
width: 100%;
}
.ql-snow.ql-toolbar .ql-picker .ql-picker-options .ql-picker-item {
background-position: center center;
background-repeat: no-repeat;
background-size: 18px 18px;
box-sizing: border-box;
cursor: pointer;
display: block;
padding-bottom: 5px;
padding-top: 5px;
}
.ql-snow.ql-toolbar .ql-picker .ql-picker-options .ql-picker-item.ql-selected,
.ql-snow.ql-toolbar .ql-picker .ql-picker-options .ql-picker-item:hover {
color: #06c;
}
.ql-snow.ql-toolbar .ql-picker.ql-expanded .ql-picker-label {
border-color: #ccc;
color: #ccc;
z-index: 2;
}
.ql-snow.ql-toolbar .ql-picker.ql-expanded .ql-picker-options {
border-color: #ccc;
box-shadow: rgba(0,0,0,0.2) 0 2px 8px;
display: block;
margin-top: -1px;
z-index: 1;
}
.ql-snow.ql-toolbar .ql-picker.ql-color-picker .ql-picker-label {
background-position: center center;
width: 28px;
}
.ql-snow.ql-toolbar .ql-picker.ql-color-picker .ql-picker-options {
padding: 5px;
width: 152px;
}
.ql-snow.ql-toolbar .ql-picker.ql-color-picker .ql-picker-options .ql-picker-item {
border: 1px solid transparent;
float: left;
height: 16px;
margin: 2px;
padding: 0px;
width: 16px;
}
.ql-snow.ql-toolbar .ql-picker.ql-color-picker .ql-picker-options .ql-picker-item.ql-primary-color {
margin-bottom: 8px;
}
.ql-snow.ql-toolbar .ql-picker.ql-color-picker .ql-picker-options .ql-picker-item.ql-selected,
.ql-snow.ql-toolbar .ql-picker.ql-color-picker .ql-picker-options .ql-picker-item:hover {
border-color: #000;
}
.ql-snow.ql-toolbar .ql-picker.ql-font {
width: 105px;
}
.ql-snow.ql-toolbar .ql-picker.ql-size {
width: 80px;
}
.ql-snow.ql-toolbar .ql-picker.ql-font .ql-picker-label,
.ql-snow.ql-toolbar .ql-picker.ql-size .ql-picker-label {
padding-left: 8px;
padding-right: 8px;
}
.ql-snow.ql-toolbar .ql-picker.ql-align .ql-picker-label {
background-position: center center;
width: 28px;
}
.ql-snow.ql-toolbar .ql-picker.ql-align .ql-picker-item {
box-sizing: border-box;
display: inline-block;
height: 24px;
line-height: 24px;
vertical-align: middle;
padding: 0px;
width: 28px;
}
.ql-snow.ql-toolbar .ql-picker.ql-align .ql-picker-options {
padding: 4px 0px;
}
.ql-snow.ql-toolbar .ql-picker .ql-picker-label {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASBAMAAACk4JNkAAAAKlBMVEUAAABJSUlAQEBERERFRUVERERERERERERERERFRUVEREREREREREREREQJcW6NAAAADXRSTlMAFRzExcbLzM/Q0dLbKbcyLwAAADVJREFUCNdjYCAeMKYJQFnSdzdCWbl3r0NZvnev4tFre/cKlNV79yaUpXP3EJTFtEqBBHcAAHyoDQk0vM/lAAAAAElFTkSuQmCC");
}
.ql-snow.ql-toolbar .ql-picker.ql-expanded .ql-picker-label {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAdElEQVR42mP4//8/VfBINGjVqlUMhw4dEj148OBpEAaxQWKkGgQz5BIQ/4fiSyAxkg2CuuQ/Gj5DjkFHsRh0jJwwwooHzCCQ145g8dpRcgw6j8WgCyQbtH//fhmgxttIhtwGiZETRjDDLoIwiA0UG820FGAA5b25+qRqGXcAAAAASUVORK5CYII=");
}
.ql-snow.ql-toolbar .ql-picker.ql-active:not(.ql-expanded) .ql-picker-label,
.ql-snow.ql-toolbar:not(.ios) .ql-picker:not(.ql-expanded) .ql-picker-label:hover {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASBAMAAACk4JNkAAAAKlBMVEUAAAAAYc4AZMgAZcwAZs0AZs0AZs0AZ8wAZswAZs0AZswAZswAZswAZsx12LPhAAAADXRSTlMAFRzExcbLzM/Q0dLbKbcyLwAAADVJREFUCNdjYCAeMKYJQFnSdzdCWbl3r0NZvnev4tFre/cKlNV79yaUpXP3EJTFtEqBBHcAAHyoDQk0vM/lAAAAAElFTkSuQmCC");
}
.ql-snow.ql-toolbar .ql-format-button.ql-bold,
.ql-snow.ql-toolbar .ql-picker.ql-bold .ql-picker-label,
.ql-snow.ql-toolbar .ql-picker .ql-picker-label[data-value=bold],
.ql-snow.ql-toolbar .ql-picker .ql-picker-item[data-value=bold] {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAMAAABhEH5lAAAAYFBMVEUAAACAgIBAQEA5OTlAQEBERERAQEBERERERERERERDQ0NERERERERERERDQ0NERERERERFRUVERERERERFRUVERERERERERERERERERERERERERERERERERERERERERESN6WzHAAAAH3RSTlMAAggJDA8cQEtTWHF/i4yTpau+xMXX3O7v8/f6+/z+qN9w2AAAAFZJREFUeNqlzMcSgCAMRVEsYO+9vv//S9FhNIYld5HFmSTCqQ66dazkRzA1lPSQGRZGIsDMKMxRW7+2yCIcyf/QUyUGSnc+dkaqoFumM32pf2BqY+HUBfQaCPgVIBc1AAAAAElFTkSuQmCC");
}
.ql-snow.ql-toolbar .ql-format-button.ql-bold.ql-active,
.ql-snow.ql-toolbar .ql-picker.ql-bold .ql-picker-label.ql-active,
.ql-snow.ql-toolbar .ql-picker .ql-picker-label[data-value=bold].ql-active,
.ql-snow.ql-toolbar .ql-picker .ql-picker-item[data-value=bold].ql-selected,
.ql-snow.ql-toolbar:not(.ios) .ql-format-button.ql-bold:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker.ql-bold .ql-picker-label:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker .ql-picker-label[data-value=bold]:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker .ql-picker-item[data-value=bold]:hover {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAMAAABhEH5lAAAAYFBMVEUAAAAAgP8AYL8AccYAatUAZswAZMgAZMsAZswAZcsAZcsAZssAZssAZ80AZswAZs0AZswAZ8wAZswAZcwAZs0AZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZsxCU9XcAAAAH3RSTlMAAggJDA8cQEtTWHF/i4yTpau+xMXX3O7v8/f6+/z+qN9w2AAAAFZJREFUeNqlzMcSgCAMRVEsYO+9vv//S9FhNIYld5HFmSTCqQ66dazkRzA1lPSQGRZGIsDMKMxRW7+2yCIcyf/QUyUGSnc+dkaqoFumM32pf2BqY+HUBfQaCPgVIBc1AAAAAElFTkSuQmCC");
}
.ql-snow.ql-toolbar .ql-format-button.ql-italic,
.ql-snow.ql-toolbar .ql-picker.ql-italic .ql-picker-label,
.ql-snow.ql-toolbar .ql-picker .ql-picker-label[data-value=italic],
.ql-snow.ql-toolbar .ql-picker .ql-picker-item[data-value=italic] {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAi0lEQVR42mMYvoARl4SLi0sNkGoAYmY0qf+MjIztu3fvrkYWZGLADZhB8pS4CN1lQUBqLRDvAQJXHMqIstEISp8BEZQYZAIi/v//f5ZSg0xBBCMj4ymyDQKGjxKQEgLiV8DweUS2QUBXGEOZp0EEJV4zgdJnKDLo379/JsS6iJHSFA0DTDhT9CiAAQBbWyIY/pd4rQAAAABJRU5ErkJggg==");
}
.ql-snow.ql-toolbar .ql-format-button.ql-italic.ql-active,
.ql-snow.ql-toolbar .ql-picker.ql-italic .ql-picker-label.ql-active,
.ql-snow.ql-toolbar .ql-picker .ql-picker-label[data-value=italic].ql-active,
.ql-snow.ql-toolbar .ql-picker .ql-picker-item[data-value=italic].ql-selected,
.ql-snow.ql-toolbar:not(.ios) .ql-format-button.ql-italic:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker.ql-italic .ql-picker-label:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker .ql-picker-label[data-value=italic]:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker .ql-picker-item[data-value=italic]:hover {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAk0lEQVR42u3SsQ3CMBBA0X/2BozACMQswg4EMQMUdOyQVdggdpagZAc4ihjJjYmU66K8xpZsfdnSsVxCzTFdEW6AB0oKcqdrLhQcNaK+PLc79QfapLTDgz8cU9Tv8ibZQqIBgI8OxhexH29KPz90jltgA7zownN+6C0Nowhg+JqEvCZbSDSHNDJBLBNdctWJXv18Ad5dJL0jVfDhAAAAAElFTkSuQmCC");
}
.ql-snow.ql-toolbar .ql-format-button.ql-underline,
.ql-snow.ql-toolbar .ql-picker.ql-underline .ql-picker-label,
.ql-snow.ql-toolbar .ql-picker .ql-picker-label[data-value=underline],
.ql-snow.ql-toolbar .ql-picker .ql-picker-item[data-value=underline] {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAMAAABhEH5lAAAAM1BMVEUAAABLS0tFRUVDQ0NERERDQ0NFRUVFRUVERERDQ0NERERFRUVERERERERERERERERERESvCHKbAAAAEHRSTlMAERpMbW6Bgry9xMXh5PP51ZZfkwAAAEdJREFUeNq9yEEKgDAMRNHERDWq6dz/tFLBQUC6KfRtPnzpsh/sC2AHrcRUo0iuDXONI7gMxVW9wIQWPFb5sMgMk5YTdMmvGw2DA8yS9di7AAAAAElFTkSuQmCC");
}
.ql-snow.ql-toolbar .ql-format-button.ql-underline.ql-active,
.ql-snow.ql-toolbar .ql-picker.ql-underline .ql-picker-label.ql-active,
.ql-snow.ql-toolbar .ql-picker .ql-picker-label[data-value=underline].ql-active,
.ql-snow.ql-toolbar .ql-picker .ql-picker-item[data-value=underline].ql-selected,
.ql-snow.ql-toolbar:not(.ios) .ql-format-button.ql-underline:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker.ql-underline .ql-picker-label:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker .ql-picker-label[data-value=underline]:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker .ql-picker-item[data-value=underline]:hover {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAMAAABhEH5lAAAAM1BMVEUAAAAAadIAYs4AZc0AZcwAZswAZ84AZswAZs0AZ8wAZcwAZs0AZswAZswAZswAZswAZsycBlETAAAAEHRSTlMAERpMbW6Bgry9xMXh5PP51ZZfkwAAAEdJREFUeNq9yEEKgDAMRNHERDWq6dz/tFLBQUC6KfRtPnzpsh/sC2AHrcRUo0iuDXONI7gMxVW9wIQWPFb5sMgMk5YTdMmvGw2DA8yS9di7AAAAAElFTkSuQmCC");
}
.ql-snow.ql-toolbar .ql-format-button.ql-strike,
.ql-snow.ql-toolbar .ql-picker.ql-strike .ql-picker-label,
.ql-snow.ql-toolbar .ql-picker .ql-picker-label[data-value=strike],
.ql-snow.ql-toolbar .ql-picker .ql-picker-item[data-value=strike] {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAMAAABhEH5lAAAAn1BMVEUAAAAAAACAgIBAQEA7OztAQEBLS0tHR0dAQEBJSUlGRkZERERCQkJERERDQ0NERERERERDQ0NFRUVERERERERERERERERERERFRUVERERERERERERFRUVDQ0NFRUVERERFRUVFRUVERERFRUVFRUVFRUVERERFRUVFRUVERERERERERERERERERERERERERERERERERERERERERERERERfrjwTAAAANHRSTlMAAQIMDRAREhQVKCk6PEhLT1xkZWZ4e4CCg4SIiZucoaersLK2wcTFydLX2ODi5err8fX3BKZfrQAAAH5JREFUGBmlwOEWgTAYBuC3isgMxCYAmwRh++7/2qRzttP/HnQTZjdjilkALzhR4wBvQiaLk8WXOJwlHVHjYgxnSmbeR0swGEkpxWZ3vt7fL/w9P4/ist+KdZ7zYYiWiCnScFYiRq1HFo4mxaKIKdJw0ooaVQovkaW1pUzQyQ86Agx4yKmWPAAAAABJRU5ErkJggg==");
}
.ql-snow.ql-toolbar .ql-format-button.ql-strike.ql-active,
.ql-snow.ql-toolbar .ql-picker.ql-strike .ql-picker-label.ql-active,
.ql-snow.ql-toolbar .ql-picker .ql-picker-label[data-value=strike].ql-active,
.ql-snow.ql-toolbar .ql-picker .ql-picker-item[data-value=strike].ql-selected,
.ql-snow.ql-toolbar:not(.ios) .ql-format-button.ql-strike:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker.ql-strike .ql-picker-label:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker .ql-picker-label[data-value=strike]:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker .ql-picker-item[data-value=strike]:hover {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAMAAABhEH5lAAAAolBMVEUAAAAAAP8AgP8AatUAYsQAYM8AadIAY8YAZswAYc4AZswAZM0AZcoAZswAZ8oAZswAZMsAZ8oAZswAZcoAZ8sAZswAZssAZssAZs0AZswAZ8wAZs0AZ8wAZs0AZswAZ8wAZ8wAZs0AZ8wAZ8wAZs0AZs0AZs0AZcwAZs0AZcwAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZsyiCU+yAAAANXRSTlMAAQIMDRAREhQVKCk6PEhLT1xkZWZ4e4CAgoOEiImbnKGnq7CytsHExcnS19jg4uXq6/H190B1i7AAAAB/SURBVBgZpcDhFoEwGAbgt4pIBmImAJsEYfvu/9ZU52yn/z3oxk/vWuczD453psYRzoR0GkaLHzFYSzqhwvgY1pT0vI8WbzASQvDt/nJ7fN6ovb7P/HrYrTdZxoY+WoJEkoK14iEqPTKwFMkkCBJJClZcUqOM4USiMKYQETr5A2SVDLpJv6ZtAAAAAElFTkSuQmCC");
}
.ql-snow.ql-toolbar .ql-format-button.ql-link,
.ql-snow.ql-toolbar .ql-picker.ql-link .ql-picker-label,
.ql-snow.ql-toolbar .ql-picker .ql-picker-label[data-value=link],
.ql-snow.ql-toolbar .ql-picker .ql-picker-item[data-value=link] {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAMAAABhEH5lAAAAllBMVEUAAAD///9VVVVJSUk5OTlAQEBHR0dFRUVCQkJHR0dBQUFCQkJGRkZDQ0NGRkZFRUVCQkJDQ0NERERDQ0NERERFRUVERERFRUVDQ0NERERFRUVERERERERFRUVERERERERERERERERFRUVERERFRUVFRUVERERERERERERERERERERERERERERERERERERERERERERERETx5KUoAAAAMXRSTlMAAAYHCQwZGiMkJzIzOUJOYGNlfoCJl5ibnaCxtLa8xsfIycrQ1OHi5uvs7e/19vn8NGTYeAAAAJdJREFUeNqN0McOgkAARdGnFJWiKGBhEEFpSn3//3OGjMmQ6MK7PMuLxVe/CXDTPl5DJmk3cOTTmZE7MDQES11RyhBY5vQU9aOB2z3gWVFMsXywYx3t9Q9tXsyDjlOVLQlOyanOL1ibkqB7l5odM01QSJqK6GdXmGwUHVhowImJIr2iMI9sLUWwa5LtFjPCSjSJBUl//HoDlmQPy0DFuCkAAAAASUVORK5CYII=");
}
.ql-snow.ql-toolbar .ql-format-button.ql-link.ql-active,
.ql-snow.ql-toolbar .ql-picker.ql-link .ql-picker-label.ql-active,
.ql-snow.ql-toolbar .ql-picker .ql-picker-label[data-value=link].ql-active,
.ql-snow.ql-toolbar .ql-picker .ql-picker-item[data-value=link].ql-selected,
.ql-snow.ql-toolbar:not(.ios) .ql-format-button.ql-link:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker.ql-link .ql-picker-label:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker .ql-picker-label[data-value=link]:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker .ql-picker-item[data-value=link]:hover {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAMAAABhEH5lAAAAmVBMVEUAAAD///8AVdUAbdsAccYAatUAZswAYs4AZswAY80AacsAZswAZM0AZ8kAZM0AZcsAZcoAZMsAZcoAZcoAZssAZs0AZs0AZ8wAZs0AZswAZs0AZswAZs0AZswAZs0AZs0AZs0AZ8wAZswAZcwAZs0AZs0AZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZsy/jsjWAAAAMnRSTlMAAAYHCQwZGiMkJzIzOUJOYGNlfoCAiZeYm52gsbS2vMbHyMnK0NTh4ubr7O3v9fb5/BM/koAAAACXSURBVHjajdDbEoFQAIXhpROqiAjaSdGJSq33fzjTbDO7GS78l9/lj9lXvwnw0le8gEzSuufAhzshr2doCpaGopQhoOX0Fb0GE9fbnidFMYV2Z8c62hgfWj6Z7zqOVY4kuCXHuqBgbUmC4Z9rdsx0QSFpLGKQXWCxUbRloQNHJoqMisI6sLUVwalJtitMCHPRJDYk/fHrDdIHECSPJag6AAAAAElFTkSuQmCC");
}
.ql-snow.ql-toolbar .ql-format-button.ql-image,
.ql-snow.ql-toolbar .ql-picker.ql-image .ql-picker-label,
.ql-snow.ql-toolbar .ql-picker .ql-picker-label[data-value=image],
.ql-snow.ql-toolbar .ql-picker .ql-picker-item[data-value=image] {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASBAMAAACk4JNkAAAAElBMVEUAAABERERERERFRUVEREREREQbmEZBAAAABXRSTlMAeMTFxj7M9NAAAABBSURBVAjXY2DAD1RDQSAYyAqFABALLANmMRnAWMwODIIMUFnGUAEIS1A0NADMYgTqhLBY4SyEKXCTTcGMEAJuAgBa9RKl6Fva+wAAAABJRU5ErkJggg==");
}
.ql-snow.ql-toolbar .ql-format-button.ql-image.ql-active,
.ql-snow.ql-toolbar .ql-picker.ql-image .ql-picker-label.ql-active,
.ql-snow.ql-toolbar .ql-picker .ql-picker-label[data-value=image].ql-active,
.ql-snow.ql-toolbar .ql-picker .ql-picker-item[data-value=image].ql-selected,
.ql-snow.ql-toolbar:not(.ios) .ql-format-button.ql-image:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker.ql-image .ql-picker-label:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker .ql-picker-label[data-value=image]:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker .ql-picker-item[data-value=image]:hover {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASBAMAAACk4JNkAAAAElBMVEUAAAAAZswAZcwAZs0AZs0AZszYB6XUAAAABXRSTlMAeMTFxj7M9NAAAABBSURBVAjXY2DAD1RDQSAYyAqFABALLANmMRnAWMwODIIMUFnGUAEIS1A0NADMYgTqhLBY4SyEKXCTTcGMEAJuAgBa9RKl6Fva+wAAAABJRU5ErkJggg==");
}
.ql-snow.ql-toolbar .ql-format-button.ql-list,
.ql-snow.ql-toolbar .ql-picker.ql-list .ql-picker-label,
.ql-snow.ql-toolbar .ql-picker .ql-picker-label[data-value=list],
.ql-snow.ql-toolbar .ql-picker .ql-picker-item[data-value=list] {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAMAAABhEH5lAAAAS1BMVEUAAABCQkJFRUVGRkZFRUVCQkJFRUVDQ0NFRUVFRUVFRUVERERERERERERERERFRUVERERERERERERERERERERERERERERERERERET32eciAAAAGHRSTlMAMjRCQ0lOfYKQlJmaocTFxuHi5OXm9falfyKhAAAATElEQVR42mMgFnCKYIpJMDDwSUABP1yIHyYkABYRlBAmwngucV50IXZGIXTjmQTZ0I0XIcp4DjEedCFWFlF041mZRdCN5xDjZiAdAACXwgbrzvG+ZgAAAABJRU5ErkJggg==");
}
.ql-snow.ql-toolbar .ql-format-button.ql-list.ql-active,
.ql-snow.ql-toolbar .ql-picker.ql-list .ql-picker-label.ql-active,
.ql-snow.ql-toolbar .ql-picker .ql-picker-label[data-value=list].ql-active,
.ql-snow.ql-toolbar .ql-picker .ql-picker-item[data-value=list].ql-selected,
.ql-snow.ql-toolbar:not(.ios) .ql-format-button.ql-list:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker.ql-list .ql-picker-label:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker .ql-picker-label[data-value=list]:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker .ql-picker-item[data-value=list]:hover {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAMAAABhEH5lAAAAS1BMVEUAAAAAZswAZ8kAZM0AZ8oAZcsAZcsAZswAZswAZ80AZs0AZs0AZ80AZ8wAZcwAZs0AZs0AZswAZswAZswAZswAZswAZswAZswAZswCB3gJAAAAGHRSTlMAMjRCQ0lOfYKQlJmaocTFxuHi5OXm9falfyKhAAAATElEQVR42mMgFnCKYIpJMDDwSUABP1yIHyYkABYRlBAmwngucV50IXZGIXTjmQTZ0I0XIcp4DjEedCFWFlF041mZRdCN5xDjZiAdAACXwgbrzvG+ZgAAAABJRU5ErkJggg==");
}
.ql-snow.ql-toolbar .ql-format-button.ql-bullet,
.ql-snow.ql-toolbar .ql-picker.ql-bullet .ql-picker-label,
.ql-snow.ql-toolbar .ql-picker .ql-picker-label[data-value=bullet],
.ql-snow.ql-toolbar .ql-picker .ql-picker-item[data-value=bullet] {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASBAMAAACk4JNkAAAAD1BMVEUAAABERERFRUVERERERETRGyWnAAAABHRSTlMAxMXG4b8ciAAAABxJREFUCNdjYMAPhBhdgMAJyFJmArGcGRgGXAcA/t0ImAOSO9kAAAAASUVORK5CYII=");
}
.ql-snow.ql-toolbar .ql-format-button.ql-bullet.ql-active,
.ql-snow.ql-toolbar .ql-picker.ql-bullet .ql-picker-label.ql-active,
.ql-snow.ql-toolbar .ql-picker .ql-picker-label[data-value=bullet].ql-active,
.ql-snow.ql-toolbar .ql-picker .ql-picker-item[data-value=bullet].ql-selected,
.ql-snow.ql-toolbar:not(.ios) .ql-format-button.ql-bullet:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker.ql-bullet .ql-picker-label:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker .ql-picker-label[data-value=bullet]:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker .ql-picker-item[data-value=bullet]:hover {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASBAMAAACk4JNkAAAAD1BMVEUAAAAAZcwAZs0AZs0AZsyEYJIjAAAABHRSTlMAxMXG4b8ciAAAABxJREFUCNdjYMAPhBhdgMAJyFJmArGcGRgGXAcA/t0ImAOSO9kAAAAASUVORK5CYII=");
}
.ql-snow.ql-toolbar .ql-format-button.ql-authorship,
.ql-snow.ql-toolbar .ql-picker.ql-authorship .ql-picker-label,
.ql-snow.ql-toolbar .ql-picker .ql-picker-label[data-value=authorship],
.ql-snow.ql-toolbar .ql-picker .ql-picker-item[data-value=authorship] {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAMAAABhEH5lAAAARVBMVEUAAABFRUVFRUUAAAAAAABERERDQ0NEREQAAABERERERERERERERERERERFRUVERERERERERERERERERERERERERERERERVeSBUAAAAFnRSTlMAMDtOT1JfYmassMfN09Ta6vD4+fz9w8DTTwAAAExJREFUGBmVwEkSgCAMBMBRQUEU4zb/f6oFF5KbNLp4EQ8rkxnWQ76whBRYkYwwxo08ZijDzWJBs7La0ZysLjSJVUKXKSgOhQuKw08fJOYE1SddZQoAAAAASUVORK5CYII=");
}
.ql-snow.ql-toolbar .ql-format-button.ql-authorship.ql-active,
.ql-snow.ql-toolbar .ql-picker.ql-authorship .ql-picker-label.ql-active,
.ql-snow.ql-toolbar .ql-picker .ql-picker-label[data-value=authorship].ql-active,
.ql-snow.ql-toolbar .ql-picker .ql-picker-item[data-value=authorship].ql-selected,
.ql-snow.ql-toolbar:not(.ios) .ql-format-button.ql-authorship:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker.ql-authorship .ql-picker-label:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker .ql-picker-label[data-value=authorship]:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker .ql-picker-item[data-value=authorship]:hover {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAMAAABhEH5lAAAARVBMVEUAAAAAZcoAaMsAZc4AZ8sAZ8oAZswAZcsAZ80AZs0AZ8wAZ8wAZswAZswAZswAZs0AZswAZswAZswAZswAZswAZswAZszAoUIuAAAAFnRSTlMAMDtOT1JfYmassMfN09Ta6vD4+fz9w8DTTwAAAExJREFUGBmVwEkSgCAMBMBRQUEU4zb/f6oFF5KbNLp4EQ8rkxnWQ76whBRYkYwwxo08ZijDzWJBs7La0ZysLjSJVUKXKSgOhQuKw08fJOYE1SddZQoAAAAASUVORK5CYII=");
}
.ql-snow.ql-toolbar .ql-format-button.ql-color,
.ql-snow.ql-toolbar .ql-picker.ql-color .ql-picker-label,
.ql-snow.ql-toolbar .ql-picker .ql-picker-label[data-value=color],
.ql-snow.ql-toolbar .ql-picker .ql-picker-item[data-value=color] {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAMAAABhEH5lAAAAgVBMVEUAAAAAAACAgIBAQEBVVVVDQ0NGRkZGRkZFRUVERERDQ0NDQ0NDQ0NCQkIAAABFRUUAAABDQ0NEREREREREREQAAABDQ0NDQ0NERERFRUVERERERERERERDQ0NERERERERFRUVFRUVERERERERERERERERERERERERERERERERERERLPkdWAAAAKnRSTlMAAQIEBhMWISUtLkVMTU5OT1BTVlpmeX6OkJmdvL3GztTj5/Hy8/b3/f5utmv0AAAAX0lEQVR42pXIRQ6AQABDUdzd3bX3PyCWwAwr+Is2ecyvuKriXmQD5otKoKBFQz+sKkU5khQZKdK8yMoyiQTFOIseEbqLWv6mAPW+bAPvJmN0j/N7nfmTFRI5Jzk0fWwD4sYJPnqIyzwAAAAASUVORK5CYII=");
}
.ql-snow.ql-toolbar .ql-format-button.ql-color.ql-active,
.ql-snow.ql-toolbar .ql-picker.ql-color .ql-picker-label.ql-active,
.ql-snow.ql-toolbar .ql-picker .ql-picker-label[data-value=color].ql-active,
.ql-snow.ql-toolbar .ql-picker .ql-picker-item[data-value=color].ql-selected,
.ql-snow.ql-toolbar:not(.ios) .ql-format-button.ql-color:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker.ql-color .ql-picker-label:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker .ql-picker-label[data-value=color]:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker .ql-picker-item[data-value=color]:hover {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAMAAABhEH5lAAAAgVBMVEUAAAAAAP8AgP8AgL8AVdUAa8kAaNEAZMkAZ8gAZswAZM0AZMsAZc0AZ8oAZcsAZc4AZ8sAZswAZcsAZc0AZswAZ80AZcoAZcoAZs0AZ80AZs0AZs0AZs0AZ8wAZs0AZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZsy3JBcuAAAAKnRSTlMAAQIEBhMWISUtLkVMTU5OT1BTVlpmeX6OkJmdvL3GztTj5/Hy8/b3/f5utmv0AAAAX0lEQVR42pXIRQ6AQABDUdzd3bX3PyCWwAwr+Is2ecyvuKriXmQB5otKoKBFQz+sKkU5khQZKdK8yMoyiQTFOIseEbqLWv6mAPW+bAPvJmN0j/N7nfmTHRI5Jzk0fWwD4foJPqgJbeoAAAAASUVORK5CYII=");
}
.ql-snow.ql-toolbar .ql-format-button.ql-background,
.ql-snow.ql-toolbar .ql-picker.ql-background .ql-picker-label,
.ql-snow.ql-toolbar .ql-picker .ql-picker-label[data-value=background],
.ql-snow.ql-toolbar .ql-picker .ql-picker-item[data-value=background] {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAMAAABhEH5lAAAAnFBMVEUAAAAAAACAgIBAQEAAAABVVVUAAAAAAAAAAABDQ0MAAABGRkZGRkYAAABFRUVERERDQ0MAAAAAAAAAAAAAAABDQ0MAAABDQ0MAAABCQkJFRUVDQ0NERERERERERERDQ0NDQ0NERERFRUVERERERERERERDQ0NERERERERFRUVFRUVERERERERERERERERERERERERERERERERERETMTXVbAAAAM3RSTlMAAQIEBgYHCBMTFBYhIyUtLjE2N0JFS0xNTU5QU1ZaeX6OkJmdvL3GztTj5/Hy8/b3/f5Qd6EEAAAAf0lEQVR42o2PRw6DQBRDHVJISCUhvTd69/3vhgT6MLPDmoX15KfRR++c6mdKgVIOTRFoeJ6hE+tCnjXRgUv+oc02jJNyrYk/vj/8jhRxnheLVZHNupn1Yp3nVIgzjhoUDlvxQR/AIOBtKbNjerUB+x7vhZjARPkLyslbYIe+qQDqMQxGJwkBGwAAAABJRU5ErkJggg==");
}
.ql-snow.ql-toolbar .ql-format-button.ql-background.ql-active,
.ql-snow.ql-toolbar .ql-picker.ql-background .ql-picker-label.ql-active,
.ql-snow.ql-toolbar .ql-picker .ql-picker-label[data-value=background].ql-active,
.ql-snow.ql-toolbar .ql-picker .ql-picker-item[data-value=background].ql-selected,
.ql-snow.ql-toolbar:not(.ios) .ql-format-button.ql-background:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker.ql-background .ql-picker-label:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker .ql-picker-label[data-value=background]:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker .ql-picker-item[data-value=background]:hover {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAMAAABhEH5lAAAAllBMVEUAAAAAAP8AgP8AgL8AVdUAbbYAYL8Aa8kAZswAaNEAZMkAZswAZ8gAZswAZM0AaMsAaNAAZswAZM0AZMsAZswAZc0AZ8oAZ80AZcsAZswAZcsAZc0AZswAZcoAZcoAZs0AZ80AZs0AZs0AZs0AZ8wAZs0AZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZsy8dW5vAAAAMXRSTlMAAQIEBgcIExQWISMlLS4xNjdCRUtMTU1OUFNWWnl+jpCZnby9xs7U4+fx8vP29/3+dqGBzgAAAH5JREFUeNqNj0cOg0AUQx1CgFQS0nujd9//ckigDzM7rFlYT34afYzOuX2WFCjl0BWBRhAYOnEu5EkTPfjkH9pswzSr15r44/vDr6mI87JarKrCHmbOi22ethDPTDoUT3vxwRDAJOJtKbNjfnUB957uhVjATPkLyslbYIexaQB/ngudkm14XQAAAABJRU5ErkJggg==");
}
.ql-snow.ql-toolbar .ql-format-button.ql-left,
.ql-snow.ql-toolbar .ql-picker.ql-left .ql-picker-label,
.ql-snow.ql-toolbar .ql-picker .ql-picker-label[data-value=left],
.ql-snow.ql-toolbar .ql-picker .ql-picker-item[data-value=left] {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASBAMAAACk4JNkAAAAD1BMVEUAAABERERFRUVERERERETRGyWnAAAABHRSTlMAxMXG4b8ciAAAAClJREFUCNdjYMAPRFxcnCAsFRcXZwYiAFCHC0STCpjlTJwOJwaYDoIaAKIACBBRNsu4AAAAAElFTkSuQmCC");
}
.ql-snow.ql-toolbar .ql-format-button.ql-left.ql-active,
.ql-snow.ql-toolbar .ql-picker.ql-left .ql-picker-label.ql-active,
.ql-snow.ql-toolbar .ql-picker .ql-picker-label[data-value=left].ql-active,
.ql-snow.ql-toolbar .ql-picker .ql-picker-item[data-value=left].ql-selected,
.ql-snow.ql-toolbar:not(.ios) .ql-format-button.ql-left:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker.ql-left .ql-picker-label:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker .ql-picker-label[data-value=left]:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker .ql-picker-item[data-value=left]:hover {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASBAMAAACk4JNkAAAAD1BMVEUAAAAAZcwAZs0AZs0AZsyEYJIjAAAABHRSTlMAxMXG4b8ciAAAAClJREFUCNdjYMAPRFxcnCAsFRcXZwYiAFCHC0STCpjlTJwOJwaYDoIaAKIACBBRNsu4AAAAAElFTkSuQmCC");
}
.ql-snow.ql-toolbar .ql-format-button.ql-right,
.ql-snow.ql-toolbar .ql-picker.ql-right .ql-picker-label,
.ql-snow.ql-toolbar .ql-picker .ql-picker-label[data-value=right],
.ql-snow.ql-toolbar .ql-picker .ql-picker-item[data-value=right] {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASBAMAAACk4JNkAAAAD1BMVEUAAABERERFRUVERERERETRGyWnAAAABHRSTlMAxMXG4b8ciAAAAChJREFUCNdjYCAIRFxcnCAsFRcXZ2KUu0B0qIBZzgzEaXFigGkhpAMAmbwIEMJ9k/cAAAAASUVORK5CYII=");
}
.ql-snow.ql-toolbar .ql-format-button.ql-right.ql-active,
.ql-snow.ql-toolbar .ql-picker.ql-right .ql-picker-label.ql-active,
.ql-snow.ql-toolbar .ql-picker .ql-picker-label[data-value=right].ql-active,
.ql-snow.ql-toolbar .ql-picker .ql-picker-item[data-value=right].ql-selected,
.ql-snow.ql-toolbar:not(.ios) .ql-format-button.ql-right:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker.ql-right .ql-picker-label:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker .ql-picker-label[data-value=right]:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker .ql-picker-item[data-value=right]:hover {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASBAMAAACk4JNkAAAAD1BMVEUAAAAAZcwAZs0AZs0AZsyEYJIjAAAABHRSTlMAxMXG4b8ciAAAAChJREFUCNdjYCAIRFxcnCAsFRcXZ2KUu0B0qIBZzgzEaXFigGkhpAMAmbwIEMJ9k/cAAAAASUVORK5CYII=");
}
.ql-snow.ql-toolbar .ql-format-button.ql-center,
.ql-snow.ql-toolbar .ql-picker.ql-center .ql-picker-label,
.ql-snow.ql-toolbar .ql-picker .ql-picker-label[data-value=center],
.ql-snow.ql-toolbar .ql-picker .ql-picker-item[data-value=center] {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASBAMAAACk4JNkAAAAD1BMVEUAAABERERFRUVERERERETRGyWnAAAABHRSTlMAxMXG4b8ciAAAAC1JREFUCNdjYCAAGF1cXBTALCYgy4CBIBBxAQEnIEsFzHJmIMYKiCVMYBYhSwCyqQhMfft6AQAAAABJRU5ErkJggg==");
}
.ql-snow.ql-toolbar .ql-format-button.ql-center.ql-active,
.ql-snow.ql-toolbar .ql-picker.ql-center .ql-picker-label.ql-active,
.ql-snow.ql-toolbar .ql-picker .ql-picker-label[data-value=center].ql-active,
.ql-snow.ql-toolbar .ql-picker .ql-picker-item[data-value=center].ql-selected,
.ql-snow.ql-toolbar:not(.ios) .ql-format-button.ql-center:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker.ql-center .ql-picker-label:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker .ql-picker-label[data-value=center]:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker .ql-picker-item[data-value=center]:hover {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASBAMAAACk4JNkAAAAD1BMVEUAAAAAZcwAZs0AZs0AZsyEYJIjAAAABHRSTlMAxMXG4b8ciAAAAC1JREFUCNdjYCAAGF1cXBTALCYgy4CBIBBxAQEnIEsFzHJmIMYKiCVMYBYhSwCyqQhMfft6AQAAAABJRU5ErkJggg==");
}
.ql-snow.ql-toolbar .ql-format-button.ql-justify,
.ql-snow.ql-toolbar .ql-picker.ql-justify .ql-picker-label,
.ql-snow.ql-toolbar .ql-picker .ql-picker-label[data-value=justify],
.ql-snow.ql-toolbar .ql-picker .ql-picker-item[data-value=justify] {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASBAMAAACk4JNkAAAAD1BMVEUAAABERERFRUVERERERETRGyWnAAAABHRSTlMAxMXG4b8ciAAAABpJREFUCNdjYMAPRFxAwAnIUgGznBkYBlwHAJGzCjB/C3owAAAAAElFTkSuQmCC");
}
.ql-snow.ql-toolbar .ql-format-button.ql-justify.ql-active,
.ql-snow.ql-toolbar .ql-picker.ql-justify .ql-picker-label.ql-active,
.ql-snow.ql-toolbar .ql-picker .ql-picker-label[data-value=justify].ql-active,
.ql-snow.ql-toolbar .ql-picker .ql-picker-item[data-value=justify].ql-selected,
.ql-snow.ql-toolbar:not(.ios) .ql-format-button.ql-justify:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker.ql-justify .ql-picker-label:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker .ql-picker-label[data-value=justify]:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker .ql-picker-item[data-value=justify]:hover {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAALklEQVR42mMYvoARzko9cwTIsyZR+zGGWcZgPUwIMUZGShwyGtijgT0a2EMMAADESwwWta/i5QAAAABJRU5ErkJggg==");
}
@media (-webkit-min-device-pixel-ratio: 2) {
.ql-snow.ql-toolbar .ql-picker .ql-picker-label {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkBAMAAAATLoWrAAAAIVBMVEUAAABCQkJDQ0NDQ0NERERERERERERERERERERERERERERehmmoAAAACnRSTlMATVRbaeXo6fz+NPhZJgAAAF9JREFUKM9jYBjkQC0JXYS5a4UBmpDFqlXN6IpWrUJTprEKCJpQhLJAQsswhZaiCImDhAJp5kMxkPGJZLjLEiQ0GUWIZdaqVSsdUM33XLVqCpqVLLPQFTEwmAcP9qQAAFUgKabkwE6gAAAAAElFTkSuQmCC");
}
.ql-snow.ql-toolbar .ql-picker.ql-expanded .ql-picker-label {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkBAMAAAATLoWrAAAAJFBMVEWqqqr////AwMDAwMDAwMDBwcHBwcHBwcHBwcHBwcHBwcHBwcEexLCPAAAAC3RSTlMAAE1UW2nl6On8/tZA57EAAABxSURBVHjazc4hFkBAGMTxL3AAp+AGniYiyaLnBETHoKkknbc7l7OrzW7zhP3HX5mRxCskEsknEaZoU6VDNbAyRRugSqICpoVotnT7dBFllnpefPuHUpjGD78aSztRfAK65cUOOIQpPnXrkFSDEFFB0APtK1HCkKpz1wAAAABJRU5ErkJggg==");
}
.ql-snow.ql-toolbar .ql-picker.ql-active:not(.ql-expanded) .ql-picker-label,
.ql-snow.ql-toolbar:not(.ios) .ql-picker:not(.ql-expanded) .ql-picker-label:hover {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkBAMAAAATLoWrAAAAIVBMVEUAAAAAZ8oAZMsAZc0AZswAZswAZswAZswAZswAZswAZswhMkyGAAAACnRSTlMATVRbaeXo6fz+NPhZJgAAAF9JREFUKM9jYBjkQC0JXYS5a4UBmpDFqlXN6IpWrUJTprEKCJpQhLJAQsswhZaiCImDhAJp5kMxkPGJZLjLEiQ0GUWIZdaqVSsdUM33XLVqCpqVLLPQFTEwmAcP9qQAAFUgKabkwE6gAAAAAElFTkSuQmCC");
}
.ql-snow.ql-toolbar .ql-format-button.ql-bold,
.ql-snow.ql-toolbar .ql-picker.ql-bold .ql-picker-label,
.ql-snow.ql-toolbar .ql-picker .ql-picker-label[data-value=bold],
.ql-snow.ql-toolbar .ql-picker .ql-picker-item[data-value=bold] {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAMAAADW3miqAAAAxlBMVEUAAABVVVUzMzNVVVVJSUlGRkZAQEBJSUlAQEBAQEBAQEBHR0dCQkJGRkZAQEBGRkZCQkJERERDQ0NDQ0NGRkZERERDQ0NFRUVCQkJFRUVERERDQ0NDQ0NFRUVDQ0NERERERERERERERERERERERERERERERERERERFRUVDQ0NERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERfjmwgAAAAQXRSTlMAAwUGBwsMDhAUGBkbHSAhIykuOUJERUpNUVZYXGRne3yAi4+SmqWmq67R1tfY2dve5ujp7/Dy8/T19vf4+fv8/mUg1b0AAACrSURBVDjL5dPFDgJBEEXRxt3d3d11gPv/P8WCEAgZuno/b1WLk1TqJaWUI1Jc8852Mqz5bdHHALDK2CF+ckgYIHp/0GtypxpHYKlFSqkycJeQD7hIKADMJFQHulrkSrYs2MflCnZZgzKvo7RJmZeSAWIf1V3nihSGAG19BUq1gKmEQsBZQkHAklATmOuQN5zvP4COQQWnmIxuFfERWOTsXmrztWg8qHqUU/IEzOhNFx6Ncl4AAAAASUVORK5CYII=");
}
.ql-snow.ql-toolbar .ql-format-button.ql-bold.ql-active,
.ql-snow.ql-toolbar .ql-picker.ql-bold .ql-picker-label.ql-active,
.ql-snow.ql-toolbar .ql-picker .ql-picker-label[data-value=bold].ql-active,
.ql-snow.ql-toolbar .ql-picker .ql-picker-item[data-value=bold].ql-selected,
.ql-snow.ql-toolbar:not(.ios) .ql-format-button.ql-bold:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker.ql-bold .ql-picker-label:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker .ql-picker-label[data-value=bold]:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker .ql-picker-item[data-value=bold]:hover {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAMAAADW3miqAAAAxlBMVEUAAAAAVaoAZswAVdUAbdsAXdEAatUAbcgAYM8AZswAasoAZswAaNAAasoAaMcAZMkAZswAZM0AZM0AZ8kAZM0AZcsAZMsAZMsAZ8oAZc0AZc0AZcsAZ8oAZswAZssAZssAZcwAZssAZ80AZs0AZ8wAZ80AZswAZ8wAZ8wAZ8wAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZsyeO+aMAAAAQXRSTlMAAwUGBwsMDhAUGBkbHSAhIykuOUJERUpNUVZYXGRne3yAi4+SmqWmq67R1tfY2dve5ujp7/Dy8/T19vf4+fv8/mUg1b0AAACrSURBVDjL5dPFDgJBEEXRxt3d3d11gPv/P8WCEAgZuno/b1WLk1TqJaWUI1Jc8852Mqz5bdHHALDK2CF+ckgYIHp/0GtypxpHYKlFSqkycJeQD7hIKADMJFQHulrkSrYs2MflCnZZgzKvo7RJmZeSAWIf1V3nihSGAG19BUq1gKmEQsBZQkHAklATmOuQN5zvP4COQQWnmIxuFfERWOTsXmrztWg8qHqUU/IEzOhNFx6Ncl4AAAAASUVORK5CYII=");
}
.ql-snow.ql-toolbar .ql-format-button.ql-italic,
.ql-snow.ql-toolbar .ql-picker.ql-italic .ql-picker-label,
.ql-snow.ql-toolbar .ql-picker .ql-picker-label[data-value=italic],
.ql-snow.ql-toolbar .ql-picker .ql-picker-item[data-value=italic] {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAMAAADW3miqAAAAjVBMVEUAAAAAAACAgIBAQEBVVVVAQEBAQEBCQkJCQkJFRUVDQ0NBQUFDQ0NDQ0NDQ0NFRUVERERERERERERDQ0NERERDQ0NERERERERERERFRUVFRUVERERFRUVERERERERDQ0NERERERERERERDQ0NFRUVEREREREREREREREREREREREREREREREREREREREQUqV1+AAAALnRSTlMAAQIEBggMGyMlKisuUFhZXmJmb3R9hIiKjZGTlKWprrG0uL3BxObt8PL19/j9SqrrawAAAIJJREFUOMvl0jUOQgEQRVHc3d1dzv6XRwch+WRq4NYnmVdMKvU35RZXz+7LQiJqe6uXiDrvqJuI8vM7ALd14fOwIabR+i1agUmfUA1QGedMgJrYRZPGGEVoh0ZgMmeUAlTBMbrWwiZCEwwitEc9MNkLigGq4RBda2MVoRn6X/jfv9YDjuYgGnCpSqcAAAAASUVORK5CYII=");
}
.ql-snow.ql-toolbar .ql-format-button.ql-italic.ql-active,
.ql-snow.ql-toolbar .ql-picker.ql-italic .ql-picker-label.ql-active,
.ql-snow.ql-toolbar .ql-picker .ql-picker-label[data-value=italic].ql-active,
.ql-snow.ql-toolbar .ql-picker .ql-picker-item[data-value=italic].ql-selected,
.ql-snow.ql-toolbar:not(.ios) .ql-format-button.ql-italic:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker.ql-italic .ql-picker-label:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker .ql-picker-label[data-value=italic]:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker .ql-picker-item[data-value=italic]:hover {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAMAAADW3miqAAAAjVBMVEUAAAAAAP8AgP8AgL8AVdUAYL8AatUAaNAAZswAZ8gAZ8gAZcoAZM0AZswAZcsAZMsAZMsAZcsAZ8sAZcoAZcoAZswAZs0AZ8wAZs0AZ8wAZswAZs0AZs0AZswAZ8wAZ8wAZs0AZswAZ8wAZ8wAZs0AZcwAZswAZswAZswAZswAZswAZswAZswAZswAZsyyI9XbAAAALnRSTlMAAQIEBggMGyMlKisuUFhZXmJmb3R9hIiKjZGTlKWprrG0uL3BxObt8PL19/j9SqrrawAAAIJJREFUOMvl0jUOQgEQRVHc3d1dzv6XRwch+WRq4NYnmVdMKvU35RZXz+7LQiJqe6uXiDrvqJuI8vM7ALd14fOwIabR+i1agUmfUA1QGedMgJrYRZPGGEVoh0ZgMmeUAlTBMbrWwiZCEwwitEc9MNkLigGq4RBda2MVoRn6X/jfv9YDjuYgGnCpSqcAAAAASUVORK5CYII=");
}
.ql-snow.ql-toolbar .ql-format-button.ql-underline,
.ql-snow.ql-toolbar .ql-picker.ql-underline .ql-picker-label,
.ql-snow.ql-toolbar .ql-picker .ql-picker-label[data-value=underline],
.ql-snow.ql-toolbar .ql-picker .ql-picker-item[data-value=underline] {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAMAAADW3miqAAAAWlBMVEUAAAAAAAAzMzNAQEBGRkZERERERERCQkJERERDQ0NFRUVERERERERFRUVERERERERERERFRUVERERERERERERDQ0NFRUVERERERERERERERERERERERERERET15sOLAAAAHXRSTlMAAQUMLC04TU9UVYePkJKkxMXG2Nrf4+jz9/n6/qlZ0HQAAACUSURBVHja7Y3BDsIgEAW3UCmCFatQxLL//5uuiQ0py1EPxs5tHhMW/oMhxoF5TUSMzGuQqH2PfiO60yiLStIHi260qqKKNLDI0XouOpI6Fh1f/x9W6xOpYZHwNM/9u5lJvACGzvSQRiWlOiUkNDSwuMFCi87mkmTbQRvt18aXWwxhXFiW4IyAr3LBJtMmmtrRFT7ME0B0HEswIOSJAAAAAElFTkSuQmCC");
}
.ql-snow.ql-toolbar .ql-format-button.ql-underline.ql-active,
.ql-snow.ql-toolbar .ql-picker.ql-underline .ql-picker-label.ql-active,
.ql-snow.ql-toolbar .ql-picker .ql-picker-label[data-value=underline].ql-active,
.ql-snow.ql-toolbar .ql-picker .ql-picker-item[data-value=underline].ql-selected,
.ql-snow.ql-toolbar:not(.ios) .ql-format-button.ql-underline:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker.ql-underline .ql-picker-label:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker .ql-picker-label[data-value=underline]:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker .ql-picker-item[data-value=underline]:hover {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAMAAADW3miqAAAAWlBMVEUAAAAAAP8AZswAatUAaMsAZswAZM0AZ8oAZMsAZMsAZswAZswAZs0AZ80AZ8wAZ8wAZcwAZs0AZs0AZswAZs0AZswAZswAZswAZswAZswAZswAZswAZswAZszogqY1AAAAHXRSTlMAAQUMLC04TU9UVYePkJKkxMXG2Nrf4+jz9/n6/qlZ0HQAAACUSURBVHja7Y3BDsIgEAW3UCmCFatQxLL//5uuiQ0py1EPxs5tHhMW/oMhxoF5TUSMzGuQqH2PfiO60yiLStIHi260qqKKNLDI0XouOpI6Fh1f/x9W6xOpYZHwNM/9u5lJvACGzvSQRiWlOiUkNDSwuMFCi87mkmTbQRvt18aXWwxhXFiW4IyAr3LBJtMmmtrRFT7ME0B0HEswIOSJAAAAAElFTkSuQmCC");
}
.ql-snow.ql-toolbar .ql-format-button.ql-strike,
.ql-snow.ql-toolbar .ql-picker.ql-strike .ql-picker-label,
.ql-snow.ql-toolbar .ql-picker .ql-picker-label[data-value=strike],
.ql-snow.ql-toolbar .ql-picker .ql-picker-item[data-value=strike] {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAMAAADW3miqAAABLFBMVEUAAACAgIBVVVVAQEAzMzNVVVVAQEA5OTlNTU1JSUlERERHR0dDQ0NGRkZDQ0NAQEBCQkJAQEBGRkZAQEBGRkZERERBQUFERERGRkZCQkJGRkZERERFRUVERERDQ0NFRUVERERDQ0NFRUVCQkJDQ0NFRUVCQkJDQ0NERERDQ0NERERERERDQ0NFRUVERERERERERERERERFRUVERERDQ0NFRUVERERERERFRUVERERERERDQ0NDQ0NFRUVERERERERFRUVERERERERFRUVERERERERDQ0NERERFRUVERERERERERERFRUVERERERERERERERERFRUVERERERERERERFRUVERERERERERERERERERERERERERERERERERERERERERERERERERERERET5TTiyAAAAY3RSTlMAAgMEBQYICQoODxITFhcYGxwdICEtLzEzNjc4P0BFRkdISk1YWWBjaWtsdHZ3f4CHiImKjJGSk5SVl5ufo6Smp625uru8vb/BwsPExcbMzs/Q0dPi4+Tl6+zv8PL19vf4+/z2SQ4sAAABE0lEQVQ4y2NgGDmAV8c5PCkxxFGDE6cSDuOEZCiI0WXGroY/OBkJeHJhU8Pkm4wCXBixKFIHyUTqibJzS5lEgNhqWBT5AMWD+CFsHg8gxxuLoniguCyMIwLkxGFRBPKZDKEw8gMqCuAloEgb7HADMTZ8ijisjHTUlCSFOdgFxeVUNPXM7Z38QmJ9EApQxFFCyxeuxhtFPC7U39nBQl9LVV5CiAMpiFDEOYQlldR0jGwM8DmOVVDRLBpkpDIBr/KBXOBKKNSEgYpiMUQjgaLChBQ5A0W94AHO6wXkumEoUgY5NcpUUYCFRUDBNAqHw22T0YAdNp9bo6qxZMLqI4VAhJIgBZwelzZ0D4uLC3M3lB5B5QgAFQdgZ6NzzvYAAAAASUVORK5CYII=");
}
.ql-snow.ql-toolbar .ql-format-button.ql-strike.ql-active,
.ql-snow.ql-toolbar .ql-picker.ql-strike .ql-picker-label.ql-active,
.ql-snow.ql-toolbar .ql-picker .ql-picker-label[data-value=strike].ql-active,
.ql-snow.ql-toolbar .ql-picker .ql-picker-item[data-value=strike].ql-selected,
.ql-snow.ql-toolbar:not(.ios) .ql-format-button.ql-strike:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker.ql-strike .ql-picker-label:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker .ql-picker-label[data-value=strike]:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker .ql-picker-item[data-value=strike]:hover {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAMAAADW3miqAAABLFBMVEUAAAAAgP8AVaoAgL8AZswAVdUAYL8AccYAZswAbcgAZswAY8YAa8kAaNEAZMgAasoAaNAAZMgAasoAaMcAZMkAZswAZ8kAaMsAZM0AaMsAZswAZM0AZcoAZMsAZMsAZswAZc0AZ8oAZMsAZ8oAZcsAZMsAZcoAZMsAZswAZssAZssAZcoAZssAZcwAZssAZs0AZswAZ8wAZs0AZs0AZswAZswAZ8wAZs0AZs0AZ80AZ8wAZswAZ8wAZs0AZ8wAZ8wAZs0AZs0AZswAZ8wAZs0AZs0AZ8wAZcwAZs0AZ8wAZswAZcwAZs0AZs0AZ8wAZswAZswAZs0AZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswL5dPDAAAAY3RSTlMAAgMEBQYICQoODxITFhcYGxwdICEtLzEzNjc4P0BFRkdISk1YWWBjaWtsdHZ3f4CHiImKjJGSk5SVl5ufo6Smp625uru8vb/BwsPExcbMzs/Q0dPi4+Tl6+zv8PL19vf4+/z2SQ4sAAABE0lEQVQ4y2NgGDmAV8c5PCkxxFGDE6cSDuOEZCiI0WXGroY/OBkJeHJhU8Pkm4wCXBixKFIHyUTqibJzS5lEgNhqWBT5AMWD+CFsHg8gxxuLoniguCyMIwLkxGFRBPKZDKEw8gMqCuAloEgb7HADMTZ8ijisjHTUlCSFOdgFxeVUNPXM7Z38QmJ9EApQxFFCyxeuxhtFPC7U39nBQl9LVV5CiAMpiFDEOYQlldR0jGwM8DmOVVDRLBpkpDIBr/KBXOBKKNSEgYpiMUQjgaLChBQ5A0W94AHO6wXkumEoUgY5NcpUUYCFRUDBNAqHw22T0YAdNp9bo6qxZMLqI4VAhJIgBZwelzZ0D4uLC3M3lB5B5QgAFQdgZ6NzzvYAAAAASUVORK5CYII=");
}
.ql-snow.ql-toolbar .ql-format-button.ql-link,
.ql-snow.ql-toolbar .ql-picker.ql-link .ql-picker-label,
.ql-snow.ql-toolbar .ql-picker .ql-picker-label[data-value=link],
.ql-snow.ql-toolbar .ql-picker .ql-picker-item[data-value=link] {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAMAAADW3miqAAABDlBMVEUAAAD///8AAACAgIBVVVVAQEAzMzNVVVVAQEBNTU1HR0dAQEBJSUlGRkZDQ0NAQEBERERHR0dGRkZDQ0NBQUFGRkZERERCQkJGRkZFRUVCQkJFRUVERERDQ0NDQ0NCQkJFRUVDQ0NERERDQ0NFRUVDQ0NFRUVFRUVFRUVFRUVERERDQ0NFRUVERERFRUVERERERERDQ0NFRUVFRUVERERERERERERERERFRUVERERERERERERFRUVDQ0NERERERERFRUVERERERERERERERERERERERERERERERERERERERERFRUVERERERERERERERERERERERERERERERERERERERERERERERERERERERESFPz0UAAAAWXRSTlMAAAECAwQFBggKEhQVFhccHiQoKissLTIzNDpGR0hMTU5QUlRVW12BgoaHjI2PmJmam5ygpKWosbKztLW6vcDD0NLT2Nna3N7g4eLj5Ofo6err7u/w8vn7/A90CXkAAAFqSURBVDjLzdTHUgJREIXho8yo6JgFc0LFjAkVMZAFJYrCzP/+L+JCtJipS5U7Patbt79Vd1dr6BfRHyBJUiie6dSSiwrEh2aeAPAO7cEoUqWXdHgQirQAOh7A46gZzVQBzsfmSgAnRhR6AjiS5OQAd9aE4t9GmqoCCRPKAGe9zzhQDxlQBzpjknab9c2RD2DBgGrgzUlqQnfrHlg3oGug6Eh1oFsAEtvLVhAteUBuSjseP2lfzQf6dARQjY/s9SncY9uH7DQA7+ky/XkI+8YSfvRVC6k3AO4s34BHT90+1N2yYq8A+/5V0Wyi0ac2NJkD3KgfSaGF9QRQ9oCC5JSAiyCStA2k9jzISooCFQNaBlpWrJBdkTThQsOA7DYQ+3pbKeDWgHQFvDiSNJwEWDWheRfIOZKVBLiRCekYoBiZSAHkx83IfgDABXielhkpfAcAkJ/WICTrwAXgZlyDkRS9rDRu1wJL98/u0yeVYHcP1mwWWgAAAABJRU5ErkJggg==");
}
.ql-snow.ql-toolbar .ql-format-button.ql-link.ql-active,
.ql-snow.ql-toolbar .ql-picker.ql-link .ql-picker-label.ql-active,
.ql-snow.ql-toolbar .ql-picker .ql-picker-label[data-value=link].ql-active,
.ql-snow.ql-toolbar .ql-picker .ql-picker-item[data-value=link].ql-selected,
.ql-snow.ql-toolbar:not(.ios) .ql-format-button.ql-link:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker.ql-link .ql-picker-label:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker .ql-picker-label[data-value=link]:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker .ql-picker-item[data-value=link]:hover {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAMAAADW3miqAAABDlBMVEUAAAD///8AAP8AgP8AVaoAgL8AZswAVdUAYL8AZswAY8YAZswAYc4AaNEAZMgAZMgAZswAY80AZswAZ8gAZcoAaMsAZswAZswAZM0AZ8kAZcoAZswAZc0AZ8oAZc0AZ8oAZcsAZswAZ8oAZMsAZswAZc0AZcsAZ84AZswAZ84AZswAZswAZ8wAZs0AZs0AZs0AZ80AZswAZ8wAZswAZ8wAZswAZs0AZs0AZs0AZ8wAZswAZ8wAZ8wAZ8wAZs0AZswAZs0AZswAZswAZswAZswAZs0AZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZsxCnEEHAAAAWXRSTlMAAAECAwQFBggKEhQVFhccHiQoKissLTIzNDpGR0hMTU5QUlRVW12BgoaHjI2PmJmam5ygpKWosbKztLW6vcDD0NLT2Nna3N7g4eLj5Ofo6err7u/w8vn7/A90CXkAAAFqSURBVDjLzdTHUgJREIXho8yo6JgFc0LFjAkVMZAFJYrCzP/+L+JCtJipS5U7Patbt79Vd1dr6BfRHyBJUiie6dSSiwrEh2aeAPAO7cEoUqWXdHgQirQAOh7A46gZzVQBzsfmSgAnRhR6AjiS5OQAd9aE4t9GmqoCCRPKAGe9zzhQDxlQBzpjknab9c2RD2DBgGrgzUlqQnfrHlg3oGug6Eh1oFsAEtvLVhAteUBuSjseP2lfzQf6dARQjY/s9SncY9uH7DQA7+ky/XkI+8YSfvRVC6k3AO4s34BHT90+1N2yYq8A+/5V0Wyi0ac2NJkD3KgfSaGF9QRQ9oCC5JSAiyCStA2k9jzISooCFQNaBlpWrJBdkTThQsOA7DYQ+3pbKeDWgHQFvDiSNJwEWDWheRfIOZKVBLiRCekYoBiZSAHkx83IfgDABXielhkpfAcAkJ/WICTrwAXgZlyDkRS9rDRu1wJL98/u0yeVYHcP1mwWWgAAAABJRU5ErkJggg==");
}
.ql-snow.ql-toolbar .ql-format-button.ql-image,
.ql-snow.ql-toolbar .ql-picker.ql-image .ql-picker-label,
.ql-snow.ql-toolbar .ql-picker .ql-picker-label[data-value=image],
.ql-snow.ql-toolbar .ql-picker .ql-picker-item[data-value=image] {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkBAMAAAATLoWrAAAAFVBMVEUAAABCQkJEREREREREREREREREREQL6X1nAAAABnRSTlMATXjl6OmAFiJpAAAAZklEQVR42sXQsQ3AIAxEUeQZoKdyzwg0DALo9h8hiCYXo4R0/MbSK1ycO5EHlScVpj4Jj97p/vtJPi9U+kptXIlMIY2r1b4XIBpSoDJJFIyYtKohAWBIV8Ke9kv8X7WwtEmBKbkDXfWkWdehkaSCAAAAAElFTkSuQmCC");
}
.ql-snow.ql-toolbar .ql-format-button.ql-image.ql-active,
.ql-snow.ql-toolbar .ql-picker.ql-image .ql-picker-label.ql-active,
.ql-snow.ql-toolbar .ql-picker .ql-picker-label[data-value=image].ql-active,
.ql-snow.ql-toolbar .ql-picker .ql-picker-item[data-value=image].ql-selected,
.ql-snow.ql-toolbar:not(.ios) .ql-format-button.ql-image:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker.ql-image .ql-picker-label:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker .ql-picker-label[data-value=image]:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker .ql-picker-item[data-value=image]:hover {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkBAMAAAATLoWrAAAAFVBMVEUAAAAAZ8oAZswAZswAZswAZswAZsx4QzxlAAAABnRSTlMATXjl6OmAFiJpAAAAZklEQVR42sXQsQ3AIAxEUeQZoKdyzwg0DALo9h8hiCYXo4R0/MbSK1ycO5EHlScVpj4Jj97p/vtJPi9U+kptXIlMIY2r1b4XIBpSoDJJFIyYtKohAWBIV8Ke9kv8X7WwtEmBKbkDXfWkWdehkaSCAAAAAElFTkSuQmCC");
}
.ql-snow.ql-toolbar .ql-format-button.ql-list,
.ql-snow.ql-toolbar .ql-picker.ql-list .ql-picker-label,
.ql-snow.ql-toolbar .ql-picker .ql-picker-label[data-value=list],
.ql-snow.ql-toolbar .ql-picker .ql-picker-item[data-value=list] {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAMAAADW3miqAAAAw1BMVEUAAAAAAABVVVVAQEBERERAQEBJSUlGRkZHR0dFRUVCQkJERERAQEBGRkZDQ0NFRUVDQ0NCQkJGRkZDQ0NCQkJERERDQ0NFRUVERERFRUVERERDQ0NERERERERDQ0NFRUVERERERERERERERERERERERERERERFRUVERERERERERERFRUVERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERESFbZw4AAAAQHRSTlMAAQYIDxAVFhkaGx4gKCo0NTY3OU10fYKIiYqMj56fo6SmqKmvtLe6vr/ExcbLz9fh4uXm5+jp7O/w8vP3+vv9Z7IwDAAAAK1JREFUOMvV0scOglAQQFGwYO+oiIq9YldEFPX+/1e5cGEii2FFdNY3b/JORlF+dAqNrS1GQyDEW+9Id/gaRw9EgQacMNEhuO4caD7rlgDS/2yAVWTiia53HWeEaMLzwUKIdvt08n4TxLMptc1UEo/38YqCuGZzKknimxDi6jpa8Vjn6I4kcQNgLkSmVSvjizeeb9ITbzxXxxLETatSxRfEWwAzicC4uANN+at5AdptTQ0Ubk4LAAAAAElFTkSuQmCC");
}
.ql-snow.ql-toolbar .ql-format-button.ql-list.ql-active,
.ql-snow.ql-toolbar .ql-picker.ql-list .ql-picker-label.ql-active,
.ql-snow.ql-toolbar .ql-picker .ql-picker-label[data-value=list].ql-active,
.ql-snow.ql-toolbar .ql-picker .ql-picker-item[data-value=list].ql-selected,
.ql-snow.ql-toolbar:not(.ios) .ql-format-button.ql-list:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker.ql-list .ql-picker-label:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker .ql-picker-label[data-value=list]:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker .ql-picker-item[data-value=list]:hover {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAMAAADW3miqAAAAw1BMVEUAAAAAAP8AVdUAYL8AZswAYM8AYc4AaNEAZswAYs4AaNAAZswAaMcAZswAZ8gAZ8kAZcoAaMsAZswAZ8kAZ8oAZcoAZswAZswAZ8wAZs0AZs0AZswAZs0AZs0AZ8wAZs0AZ8wAZ8wAZs0AZ8wAZswAZswAZs0AZ8wAZswAZcwAZcwAZs0AZs0AZs0AZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZszno9YmAAAAQHRSTlMAAQYIDxAVFhkaGx4gKCo0NTY3OU10fYKIiYqMj56fo6SmqKmvtLe6vr/ExcbLz9fh4uXm5+jp7O/w8vP3+vv9Z7IwDAAAAK1JREFUOMvV0scOglAQQFGwYO+oiIq9YldEFPX+/1e5cGEii2FFdNY3b/JORlF+dAqNrS1GQyDEW+9Id/gaRw9EgQacMNEhuO4caD7rlgDS/2yAVWTiia53HWeEaMLzwUKIdvt08n4TxLMptc1UEo/38YqCuGZzKknimxDi6jpa8Vjn6I4kcQNgLkSmVSvjizeeb9ITbzxXxxLETatSxRfEWwAzicC4uANN+at5AdptTQ0Ubk4LAAAAAElFTkSuQmCC");
}
.ql-snow.ql-toolbar .ql-format-button.ql-bullet,
.ql-snow.ql-toolbar .ql-picker.ql-bullet .ql-picker-label,
.ql-snow.ql-toolbar .ql-picker .ql-picker-label[data-value=bullet],
.ql-snow.ql-toolbar .ql-picker .ql-picker-item[data-value=bullet] {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkBAMAAAATLoWrAAAAElBMVEUAAABCQkJEREREREREREREREQc4xmxAAAABXRSTlMATeXo6UtNtyIAAAAzSURBVCjPY2AYACBsyCAcCgOGYCHTYAZTuFAwRCgISSgILCSiyCACF1JkGBgw6voBcj0AFsUtDasGrUcAAAAASUVORK5CYII=");
}
.ql-snow.ql-toolbar .ql-format-button.ql-bullet.ql-active,
.ql-snow.ql-toolbar .ql-picker.ql-bullet .ql-picker-label.ql-active,
.ql-snow.ql-toolbar .ql-picker .ql-picker-label[data-value=bullet].ql-active,
.ql-snow.ql-toolbar .ql-picker .ql-picker-item[data-value=bullet].ql-selected,
.ql-snow.ql-toolbar:not(.ios) .ql-format-button.ql-bullet:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker.ql-bullet .ql-picker-label:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker .ql-picker-label[data-value=bullet]:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker .ql-picker-item[data-value=bullet]:hover {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkBAMAAAATLoWrAAAAElBMVEUAAAAAZ8oAZswAZswAZswAZsxixJGvAAAABXRSTlMATeXo6UtNtyIAAAAzSURBVCjPY2AYACBsyCAcCgOGYCHTYAZTuFAwRCgISSgILCSiyCACF1JkGBgw6voBcj0AFsUtDasGrUcAAAAASUVORK5CYII=");
}
.ql-snow.ql-toolbar .ql-format-button.ql-authorship,
.ql-snow.ql-toolbar .ql-picker.ql-authorship .ql-picker-label,
.ql-snow.ql-toolbar .ql-picker .ql-picker-label[data-value=authorship],
.ql-snow.ql-toolbar .ql-picker .ql-picker-item[data-value=authorship] {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAMAAADW3miqAAAAllBMVEUAAACAgIBAQEBCQkIAAABCQkJAQEBGRkZERERERERCQkJGRkZDQ0NDQ0NDQ0MAAAAAAAAAAABDQ0NFRUVERERFRUVERERFRUVERERFRUVERERERERERERERERERERERERERERFRUVEREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREQe3JVeAAAAMXRSTlMAAhgbHx8gIS0xMjM5VFdcXWZyd3yChImPkKy4yMrO0tPj5ebq7e7v8PLz9/j6/P3+mEwo9QAAAJxJREFUGBnVwNcOgjAYBeCj4l7FjeAGUZzn/V9O0kikSftf44c/0A+Tc9iFqHll7tKEJKAWQLKjtockpZZC8qL2hiSjlkESUYsgmVNbQtKhNoCgNrwz95w14NTe8Os2gUP9wJ8p7NYsebRg06NhAZsVDRFstjQksMlogs2Rhhg2o5glpxGqz1O+g/JQUL6TQkH5TmMUPOU7jD1U1AdG8S1kERvjygAAAABJRU5ErkJggg==");
}
.ql-snow.ql-toolbar .ql-format-button.ql-authorship.ql-active,
.ql-snow.ql-toolbar .ql-picker.ql-authorship .ql-picker-label.ql-active,
.ql-snow.ql-toolbar .ql-picker .ql-picker-label[data-value=authorship].ql-active,
.ql-snow.ql-toolbar .ql-picker .ql-picker-item[data-value=authorship].ql-selected,
.ql-snow.ql-toolbar:not(.ios) .ql-format-button.ql-authorship:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker.ql-authorship .ql-picker-label:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker .ql-picker-label[data-value=authorship]:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker .ql-picker-item[data-value=authorship]:hover {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAMAAADW3miqAAAAllBMVEUAAAAAgP8AasoAaNAAY84AaMcAZMkAZswAaMsAZswAZM0AZ8kAZMsAZ8oAZ8oAZcsAZc4AZ80AZcwAZcwAZcwAZswAZs0AZs0AZs0AZ80AZs0AZ8wAZswAZs0AZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZsyCDIYeAAAAMXRSTlMAAhgbHyAhLTEyMzlUV1xdXWZyd3yChImPkKy4yMrO0tPj5ebq7e7v8PLz9/j6/P3+PxHOPAAAAJxJREFUGBnVwNcOgjAYBeCj1j0q7oEbRHGe9385SSORJu1/jR/+QGcdn9ctiNSVmYuCZEljCcmOxh6ShEYCyYvGG5KURgpJSCOEZEpjDkmTRheCSu/OzHNSg1djw6/bCB7VA3/GcFux4FGHS5uWGVwWtIRw2dISwyWlDS5HWiK49CMWnPooP6UDD62Q04GXRk4HXgPk1DDwGCiU1AcZWy1RmD8CRQAAAABJRU5ErkJggg==");
}
.ql-snow.ql-toolbar .ql-format-button.ql-color,
.ql-snow.ql-toolbar .ql-picker.ql-color .ql-picker-label,
.ql-snow.ql-toolbar .ql-picker .ql-picker-label[data-value=color],
.ql-snow.ql-toolbar .ql-picker .ql-picker-item[data-value=color] {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAMAAADW3miqAAAAz1BMVEUAAAAAAACAgIBVVVVAQEBVVVU5OTk7OztLS0tHR0dGRkZCQkIAAABERERDQ0NDQ0NDQ0NDQ0NGRkZERERERERCQkJFRUVERERFRUVEREQAAAAAAABDQ0NFRUVEREQAAABERERFRUVERERDQ0NDQ0NERERERERERERERERERERERERERERERERERERFRUVFRUVERERERERERERERERERERDQ0NERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERbYaT1AAAARHRSTlMAAQIDBAYJDRESFhsfIiYqNUFCREtNVVZZWlxdY2RlZm1zdXZ9hI6Tl6Sws7nExcnS09XY2d/g5ejp6+zt8PP09/n9/idH/qoAAADKSURBVBgZ1cDXUsJAAIXhg2KMGruxsGoUe8cWoij1f/9nYiZDGJjsLrfwaRHEWRZrhuAXWoH8zgBO5VVpADTktU9uVz5P5B7lsdUn19+U2x3w+gbcyilsA0cnwP+qXOpAWl1pAhdyqKZAXboGvpZkdwi0Q2m9CxzI7oUJz7LaYdJgWzYPTLmXxUaPKZ01ld0A7xXllr+BK5VlwLlGLoFPlWXQCjQSduBDZfFPM9bY8V+6p7kXmcTBRCqYxMmoYBKnmgqRSRxqkebUEKsKOlxMa6IbAAAAAElFTkSuQmCC");
}
.ql-snow.ql-toolbar .ql-format-button.ql-color.ql-active,
.ql-snow.ql-toolbar .ql-picker.ql-color .ql-picker-label.ql-active,
.ql-snow.ql-toolbar .ql-picker .ql-picker-label[data-value=color].ql-active,
.ql-snow.ql-toolbar .ql-picker .ql-picker-item[data-value=color].ql-selected,
.ql-snow.ql-toolbar:not(.ios) .ql-format-button.ql-color:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker.ql-color .ql-picker-label:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker .ql-picker-label[data-value=color]:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker .ql-picker-item[data-value=color]:hover {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAMAAADW3miqAAAA0lBMVEUAAAAAAP8AgP8AVaoAgL8AVdUAccYAYsQAadIAY8YAaNEAaNAAY84AacsAZckAZ8gAZcoAZswAZM0AZcsAZswAZ8oAZswAZc0AZMsAZswAZ8oAZcsAZc4AZMsAZswAZcoAZ80AZcwAZswAZssAZssAZswAZs0AZs0AZs0AZ8wAZ8wAZ8wAZ8wAZswAZcwAZs0AZcwAZswAZswAZs0AZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswVaivDAAAARXRSTlMAAQIDBAYJDRESFhsfIiYqNUFCREtNVVZZWlxdXWNkZWZtc3V2fYSOk5eksLO5xMXJ0tPV2Nnf4OXo6evs7fDz9Pf5/f6Y2SWXAAAAy0lEQVQYGdXA11LCQACF4YNijBq7sbCWKPaOLURREPjf/5WYyRAGJrvLLXyaB3GWxZoi+IFWIL9TgBN5VRoADXntktuWzyO5B3ls9Mj11uV2C7y8AjdyCtvAwRHwtyyXOpBWl5rAuRyqKVCXroDPBdntA+1QWv0H9mT3zJgnWW0xrr8pm3sm3MlircuEzorKroG3inKLX8ClyjLgTEMXwIfKMmgFGgo78K6y+LsZa+TwN93RzItM4mAiFUziZFQwiVNNheg4cahFmlEDFzs7cwmPHM8AAAAASUVORK5CYII=");
}
.ql-snow.ql-toolbar .ql-format-button.ql-background,
.ql-snow.ql-toolbar .ql-picker.ql-background .ql-picker-label,
.ql-snow.ql-toolbar .ql-picker .ql-picker-label[data-value=background],
.ql-snow.ql-toolbar .ql-picker .ql-picker-item[data-value=background] {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAMAAADW3miqAAAA4VBMVEUAAAAAAACAgIBVVVVAQEBVVVU5OTk7OztLS0tHR0dGRkZCQkJERERDQ0NDQ0NDQ0NDQ0NERERCQkJEREQAAAADAwMGBgZDQ0NEREQODg5ERERDQ0NFRUVERERERERERERDQ0MiIiJDQ0MmJiZEREQrKytEREREREQyMjIyMjJEREREREREREQ4ODhERERERERFRUVFRUVERERERERERERERERAQEBERERERERBQUFERERERERERERBQUFERERERERERERBQUFERERERERERERDQ0NERERERERDQ0NERERERESZD8GyAAAASnRSTlMAAQIDBAYJDRESFhsiJio1QURJS01QU1RWWVpjZGVtdXZ4fYCEiI6TnZ6ksLO3ucTFydLT193g4OLl5ebn6enq6+7w8vP39/n+/rihcb4AAADbSURBVHjazZPFDsMwEERdZkpTZmbmpszd//+grhpFSaS1e+khc1jbmrG1z7KZdSXLgvo79M9ziKCkKJIeoUPJA8AxKT6H5QGVE3dlmwJqKqaLwVdRIV1fDfVEdKGXGnoFBXQtDIwnWJp8uswd/XQWy8XD7aqD9srp2uJQ5NElVuiWGKvisLFz6Bpo3ryM+R84iXO6GoFBQ5ouAka9wyRdF0waUHSBpzl09xF0dTRmNnXu2OOiTNDtAKCg7W3jYk7QnQGObu0KvVeAJUFXU9aS/h5Sp0VFtui/s6w+XSJAbiVJ3G0AAAAASUVORK5CYII=");
}
.ql-snow.ql-toolbar .ql-format-button.ql-background.ql-active,
.ql-snow.ql-toolbar .ql-picker.ql-background .ql-picker-label.ql-active,
.ql-snow.ql-toolbar .ql-picker .ql-picker-label[data-value=background].ql-active,
.ql-snow.ql-toolbar .ql-picker .ql-picker-item[data-value=background].ql-selected,
.ql-snow.ql-toolbar:not(.ios) .ql-format-button.ql-background:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker.ql-background .ql-picker-label:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker .ql-picker-label[data-value=background]:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker .ql-picker-item[data-value=background]:hover {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAMAAADW3miqAAAA5FBMVEUAAAAAAP8AgP8AVaoAgL8AZswAVdUAYL8AccYAYsQAadIAY8YAaNEAasoAZswAYsQAaNAAacsAZckAadEAZ8gAZcoAZswAZswAZMkAZM0AZcsAZ8sAZswAaM0AZ8oAZ80AZswAZc0AZMsAZswAZMsAZswAZcoAZcwAZswAZssAZssAZswAZs0AZs0AZs0AZ8wAZ8wAZ8wAZ8wAZswAZcwAZs0AZcwAZswAZswAZs0AZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZswAZsxJPDLdAAAAS3RSTlMAAQIDBAUGCAkNERIWGBkaGyImJyo1N0FCQkRFS0xNTVVWWVpjZGVtc3V2fYSOk5eksLO5xMXJ0tPV2Nnf4OXo6evs7fDz9Pf5/f60OfwzAAABG0lEQVR42s2T6VKDQBCEGyUJoqgSjcYg8dZ43/EieCUa5/3fx661qMAu7O98P4bZnq5lZlkwvXS7k1hf1BTdZFEsFpvUMU15IU7TuKiYJu9d5MODZZ8WcCBk39ZVAKcvpG+ZrgNsimIdTtV0TeBGFNewdBWORTFesUx3QcP9A8N59XT+kPWdPYavOQQVXfVYTtz6gI8jvfUsdRNWe8ApHy8z5ftgm8WhDyx8M4nKumoBd5LjVkkaAdYkz+8qpQLqtK+kwKU5XRPLP1JgNF8y3RkLjw4Us69cnMDb0qdLqR9myjEXz2brNPG2NSKQqOGPRJ5gEr8NYoT/9yHE7mfShoarovYptDw7kiWLyZTbNZBa9saK33tDWZlPK39U3ELkzhssBgAAAABJRU5ErkJggg==");
}
.ql-snow.ql-toolbar .ql-format-button.ql-left,
.ql-snow.ql-toolbar .ql-picker.ql-left .ql-picker-label,
.ql-snow.ql-toolbar .ql-picker .ql-picker-label[data-value=left],
.ql-snow.ql-toolbar .ql-picker .ql-picker-item[data-value=left] {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkBAMAAAATLoWrAAAAElBMVEUAAABCQkJEREREREREREREREQc4xmxAAAABXRSTlMATeXo6UtNtyIAAABCSURBVCjPY2AYACAcCgaGSEKmEKFgTKEgJCERiJAiw0ACqOuR/WCKLBSMKRSE7PqB9YMwuttRnBqMKRSEGvYD6HYAD8opyeJDvUUAAAAASUVORK5CYII=");
}
.ql-snow.ql-toolbar .ql-format-button.ql-left.ql-active,
.ql-snow.ql-toolbar .ql-picker.ql-left .ql-picker-label.ql-active,
.ql-snow.ql-toolbar .ql-picker .ql-picker-label[data-value=left].ql-active,
.ql-snow.ql-toolbar .ql-picker .ql-picker-item[data-value=left].ql-selected,
.ql-snow.ql-toolbar:not(.ios) .ql-format-button.ql-left:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker.ql-left .ql-picker-label:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker .ql-picker-label[data-value=left]:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker .ql-picker-item[data-value=left]:hover {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkBAMAAAATLoWrAAAAElBMVEUAAAAAZ8oAZswAZswAZswAZsxixJGvAAAABXRSTlMATeXo6UtNtyIAAABCSURBVCjPY2AYACAcCgaGSEKmEKFgTKEgJCERiJAiw0ACqOuR/WCKLBSMKRSE7PqB9YMwuttRnBqMKRSEGvYD6HYAD8opyeJDvUUAAAAASUVORK5CYII=");
}
.ql-snow.ql-toolbar .ql-format-button.ql-right,
.ql-snow.ql-toolbar .ql-picker.ql-right .ql-picker-label,
.ql-snow.ql-toolbar .ql-picker .ql-picker-label[data-value=right],
.ql-snow.ql-toolbar .ql-picker .ql-picker-item[data-value=right] {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkBAMAAAATLoWrAAAAElBMVEUAAABCQkJEREREREREREREREQc4xmxAAAABXRSTlMATeXo6UtNtyIAAABCSURBVCjPY2AYMCAcCgaGSEKmEKFgTKEgJCERiJDiwLob2fWmyELBmEJByO4eWNejuN8QNZCRw94U3fUo7h8Q1wMAuRspyVIXC2UAAAAASUVORK5CYII=");
}
.ql-snow.ql-toolbar .ql-format-button.ql-right.ql-active,
.ql-snow.ql-toolbar .ql-picker.ql-right .ql-picker-label.ql-active,
.ql-snow.ql-toolbar .ql-picker .ql-picker-label[data-value=right].ql-active,
.ql-snow.ql-toolbar .ql-picker .ql-picker-item[data-value=right].ql-selected,
.ql-snow.ql-toolbar:not(.ios) .ql-format-button.ql-right:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker.ql-right .ql-picker-label:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker .ql-picker-label[data-value=right]:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker .ql-picker-item[data-value=right]:hover {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkBAMAAAATLoWrAAAAElBMVEUAAAAAZ8oAZswAZswAZswAZsxixJGvAAAABXRSTlMATeXo6UtNtyIAAABCSURBVCjPY2AYMCAcCgaGSEKmEKFgTKEgJCERiJDiwLob2fWmyELBmEJByO4eWNejuN8QNZCRw94U3fUo7h8Q1wMAuRspyVIXC2UAAAAASUVORK5CYII=");
}
.ql-snow.ql-toolbar .ql-format-button.ql-center,
.ql-snow.ql-toolbar .ql-picker.ql-center .ql-picker-label,
.ql-snow.ql-toolbar .ql-picker .ql-picker-label[data-value=center],
.ql-snow.ql-toolbar .ql-picker .ql-picker-item[data-value=center] {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkBAMAAAATLoWrAAAAElBMVEUAAABCQkJEREREREREREREREQc4xmxAAAABXRSTlMATeXo6UtNtyIAAABCSURBVCjPY2AYGCAcCgaGSEKmEKFgTKEgJCERiJAiw4ABqNORPWCKLBSMKRSE7PQB9oAwuuNR3BqMKRSEGvID53gA5GspyQ9EElMAAAAASUVORK5CYII=");
}
.ql-snow.ql-toolbar .ql-format-button.ql-center.ql-active,
.ql-snow.ql-toolbar .ql-picker.ql-center .ql-picker-label.ql-active,
.ql-snow.ql-toolbar .ql-picker .ql-picker-label[data-value=center].ql-active,
.ql-snow.ql-toolbar .ql-picker .ql-picker-item[data-value=center].ql-selected,
.ql-snow.ql-toolbar:not(.ios) .ql-format-button.ql-center:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker.ql-center .ql-picker-label:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker .ql-picker-label[data-value=center]:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker .ql-picker-item[data-value=center]:hover {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkBAMAAAATLoWrAAAAElBMVEUAAAAAZ8oAZswAZswAZswAZsxixJGvAAAABXRSTlMATeXo6UtNtyIAAABCSURBVCjPY2AYGCAcCgaGSEKmEKFgTKEgJCERiJAiw4ABqNORPWCKLBSMKRSE7PQB9oAwuuNR3BqMKRSEGvID53gA5GspyQ9EElMAAAAASUVORK5CYII=");
}
.ql-snow.ql-toolbar .ql-format-button.ql-justify,
.ql-snow.ql-toolbar .ql-picker.ql-justify .ql-picker-label,
.ql-snow.ql-toolbar .ql-picker .ql-picker-label[data-value=justify],
.ql-snow.ql-toolbar .ql-picker .ql-picker-item[data-value=justify] {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkBAMAAAATLoWrAAAAElBMVEUAAABCQkJEREREREREREREREQc4xmxAAAABXRSTlMATeXo6UtNtyIAAAAoSURBVCjPY2AYACAcigQMwUKmyELBmEJBYCERZCFFhoEBo64fINcDAAcQNGkJNhVcAAAAAElFTkSuQmCC");
}
.ql-snow.ql-toolbar .ql-format-button.ql-justify.ql-active,
.ql-snow.ql-toolbar .ql-picker.ql-justify .ql-picker-label.ql-active,
.ql-snow.ql-toolbar .ql-picker .ql-picker-label[data-value=justify].ql-active,
.ql-snow.ql-toolbar .ql-picker .ql-picker-item[data-value=justify].ql-selected,
.ql-snow.ql-toolbar:not(.ios) .ql-format-button.ql-justify:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker.ql-justify .ql-picker-label:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker .ql-picker-label[data-value=justify]:hover,
.ql-snow.ql-toolbar:not(.ios) .ql-picker .ql-picker-item[data-value=justify]:hover {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkBAMAAAATLoWrAAAAElBMVEUAAAAAZ8oAZswAZswAZswAZsxixJGvAAAABXRSTlMATeXo6UtNtyIAAAAoSURBVCjPY2AYACAcigQMwUKmyELBmEJBYCERZCFFhoEBo64fINcDAAcQNGkJNhVcAAAAAElFTkSuQmCC");
}
}
.ql-snow .ql-tooltip {
border: 1px solid #ccc;
box-shadow: 0px 0px 5px #ddd;
color: #222;
}
.ql-snow .ql-tooltip a {
color: #06c;
}
.ql-snow .ql-tooltip .input {
border: 1px solid #ccc;
margin: 0px;
padding: 5px;
}
.ql-snow a {
color: #06c;
}

View File

@ -0,0 +1,50 @@
# Style Guide
Code style is very subjective but consistency is very important for a healthy codebase. Quill strives to follow good programming practices and the language specific guidelines so those will not be reproduced here. However some less obvious guidelines are listed below.
If there is ever uncertainty, please look at other parts of the codebase and mimic that style.
### General
- Use two spaces for tabs
- No trailing whitespace on any lines
### Operators
- Always use parenthesis for function calls
- Use brackets for one line object definitions
```coffeescript
console.log('Yes') # Yes
console.log 'No' # No
config = { attack: 10, defense: 10 } # Yes
config = attack: 10, defense: 10 # No
# Okay
config =
attack: 10
defense: 10
```
### Classes
- Use an explicit `this` when referencing methods
- Use `@` when referencing instance variables
```coffeescript
class Tower
@constructor: (strength, toughness) ->
@strength = strength # Yes
this.toughness = toughness # No
this.attack() # Yes
@defend() # No
attack: ->
defend: ->
```

View File

@ -0,0 +1,15 @@
{
"name": "y-array",
"homepage": "https://github.com/y-js/y-array",
"version": "0.7.5",
"_release": "0.7.5",
"_resolution": {
"type": "version",
"tag": "v0.7.5",
"commit": "616d21127c3aece7c1c3619b9f7ffe2f20f03f23"
},
"_source": "git://github.com/y-js/y-array.git",
"_target": "~0.7.5",
"_originalSource": "y-array",
"_direct": true
}

View File

@ -0,0 +1,3 @@
{
"directory": "../"
}

View File

@ -0,0 +1,6 @@
/node_modules/
bower_components
.directory
.c9
.codio
.settings

View File

@ -0,0 +1,11 @@
language: node_js
before_install:
- "npm install -g bower coffee-script"
- "bower install"
node_js:
- "0.12"
- "0.11"
- "0.10"
branches:
only:
- master

View File

@ -0,0 +1,92 @@
# List Type for [Yjs](https://github.com/y-js/yjs)
Manage list-like data with this shareable list type. You can insert and delete arbitrary objects (also custom types for Yjs) in the list type.
## Use it!
Retrieve this with bower or npm.
##### Bower
```
bower install y-list --save
```
and include the js library.
```
<script src="./bower_components/y-list/y-list.js"></script>
```
##### NPM
```
npm install y-list --save
```
and put it on the `Y` object.
```
Y.List = require("y-list");
```
### List Object
##### Reference
* Create
```
var ylist = new Y.List()
```
* .insert(position, content)
* Insert content at a position
* .insertContents(position, contents)
* Insert a set of content at a position. This expects that contents is an array of content.
* .push(content)
* Insert content at the end of the list
* .delete(position, length)
* Delete content. The *length* parameter is optional and defaults to 1
* .val()
* Retrieve all content as an Array Object
* .val(position)
* Retrieve content from a position
* .ref(position)
* Retrieve a reference to the element on a *position*.
* You can call `ref.getNext()` and `ref.getPrev()` to get the next/previous reference
* You can call `ref.getNext(i)` and `ref.getPrev(i)` to get the i-th next/previous reference
* You can call `ref.val()` to get the element, to which the reference points (`y.ref(1).val() === y.val(1)`)
* .observe(f)
* The observer is called whenever something on this list changed. (throws insert, and delete events)
* .unobserve(f)
* Delete an observer
# A note on intention preservation
If two users insert something at the same position concurrently, the content that was inserted by the user with the higher user-id will be to the right of the other content. In the OT world we often speak of *intention preservation*, which is very loosely defined in most cases. This type has the following notion of intention preservation: When a user inserts content *c* after a set of content *C_left*, and before a set of content *C_right*, then *C_left* will be always to the left of c, and *C_right* will be always to the right of *c*. This property will also hold when content is deleted or when a deletion is undone.
# A note on time complexities
* .insert(position, content)
* O(position)
* .insertContents(position, contents)
* O(position + |contents|)
* .push(content)
* O(1)
* .delete(position, length)
* O(position)
* .val()
* O(|ylist|)
* .val(position)
* O(position|)
* Apply a delete operation from another user
* O(1)
* Apply an insert operation from another user
* Yjs does not transform against operations that do not conflict with each other.
* An operation conflicts with another operation if it intends to be inserted at the same position.
* Overall worst case complexety: O(|conflicts|!)
# Issues
* Support moving of objects
* Create a polymer element
## License
Yjs is licensed under the [MIT License](./LICENSE.txt).
<kevin.jahns@rwth-aachen.de>

View File

@ -0,0 +1,197 @@
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
/* global Y */
'use strict'
function extend (Y) {
class YArray {
constructor (os, _model, idArray, valArray) {
this.os = os
this._model = _model
// Array of all the operation id's
this.idArray = idArray
// Array of all the values
this.valArray = valArray
this.eventHandler = new Y.utils.EventHandler(ops => {
var userEvents = []
for (var i in ops) {
var op = ops[i]
if (op.struct === 'Insert') {
let pos
// we check op.left only!,
// because op.right might not be defined when this is called
if (op.left === null) {
pos = 0
} else {
var sid = JSON.stringify(op.left)
pos = this.idArray.indexOf(sid) + 1
if (pos <= 0) {
throw new Error('Unexpected operation!')
}
}
this.idArray.splice(pos, 0, JSON.stringify(op.id))
this.valArray.splice(pos, 0, op.content)
userEvents.push({
type: 'insert',
object: this,
index: pos,
value: op.content,
length: 1
})
} else if (op.struct === 'Delete') {
let pos = this.idArray.indexOf(JSON.stringify(op.target))
if (pos >= 0) {
var val = this.valArray[pos]
this.idArray.splice(pos, 1)
this.valArray.splice(pos, 1)
userEvents.push({
type: 'delete',
object: this,
index: pos,
value: val,
length: 1
})
}
} else {
throw new Error('Unexpected struct!')
}
}
this.eventHandler.callEventListeners(userEvents)
})
}
get length () {
return this.idArray.length
}
get (pos) {
if (pos == null || typeof pos !== 'number') {
throw new Error('pos must be a number!')
}
return this.valArray[pos]
}
toArray () {
return this.valArray.slice()
}
push (contents) {
this.insert(this.idArray.length, contents)
}
insert (pos, contents) {
if (typeof pos !== 'number') {
throw new Error('pos must be a number!')
}
if (!(contents instanceof Array)) {
throw new Error('contents must be an Array of objects!')
}
if (contents.length === 0) {
return
}
if (pos > this.idArray.length || pos < 0) {
throw new Error('This position exceeds the range of the array!')
}
var mostLeft = pos === 0 ? null : JSON.parse(this.idArray[pos - 1])
var ops = []
var prevId = mostLeft
for (var i = 0; i < contents.length; i++) {
var op = {
left: prevId,
origin: prevId,
// right: mostRight,
// NOTE: I intentionally do not define right here, because it could be deleted
// at the time of creating this operation, and is therefore not defined in idArray
parent: this._model,
content: contents[i],
struct: 'Insert',
id: this.os.getNextOpId()
}
ops.push(op)
prevId = op.id
}
var eventHandler = this.eventHandler
eventHandler.awaitAndPrematurelyCall(ops)
this.os.requestTransaction(function *() {
// now we can set the right reference.
var mostRight
if (mostLeft != null) {
mostRight = (yield* this.getOperation(mostLeft)).right
} else {
mostRight = (yield* this.getOperation(ops[0].parent)).start
}
for (var j in ops) {
ops[j].right = mostRight
}
yield* this.applyCreatedOperations(ops)
eventHandler.awaitedInserts(ops.length)
})
}
delete (pos, length) {
if (length == null) { length = 1 }
if (typeof length !== 'number') {
throw new Error('pos must be a number!')
}
if (typeof pos !== 'number') {
throw new Error('pos must be a number!')
}
if (pos + length > this.idArray.length || pos < 0 || length < 0) {
throw new Error('The deletion range exceeds the range of the array!')
}
if (length === 0) {
return
}
var eventHandler = this.eventHandler
var newLeft = pos > 0 ? JSON.parse(this.idArray[pos - 1]) : null
var dels = []
for (var i = 0; i < length; i++) {
dels.push({
target: JSON.parse(this.idArray[pos + i]),
struct: 'Delete'
})
}
eventHandler.awaitAndPrematurelyCall(dels)
this.os.requestTransaction(function *() {
yield* this.applyCreatedOperations(dels)
eventHandler.awaitedDeletes(dels.length, newLeft)
})
}
observe (f) {
this.eventHandler.addEventListener(f)
}
* _changed (transaction, op) {
if (!op.deleted) {
if (op.struct === 'Insert') {
var l = op.left
var left
while (l != null) {
left = yield* transaction.getOperation(l)
if (!left.deleted) {
break
}
l = left.left
}
op.left = l
}
this.eventHandler.receivedOp(op)
}
}
}
Y.extend('Array', new Y.utils.CustomType({
name: 'Array', // TODO: copy the name when extending the object.. (see one line above)
class: YArray,
struct: 'List',
initType: function * YArrayInitializer (os, model) {
var valArray = []
var idArray = yield* Y.Struct.List.map.call(this, model, function (c) {
valArray.push(c.content)
return JSON.stringify(c.id)
})
return new YArray(os, model.id, idArray, valArray)
}
}))
}
module.exports = extend
if (typeof Y !== 'undefined') {
extend(Y)
}
},{}]},{},[1])

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,2 @@
!function e(r,t,n){function a(s,o){if(!t[s]){if(!r[s]){var u="function"==typeof require&&require;if(!o&&u)return u(s,!0);if(i)return i(s,!0);var l=new Error("Cannot find module '"+s+"'");throw l.code="MODULE_NOT_FOUND",l}var c=t[s]={exports:{}};r[s][0].call(c.exports,function(e){var t=r[s][1][e];return a(t?t:e)},c,c.exports,e,r,t,n)}return t[s].exports}for(var i="function"==typeof require&&require,s=0;s<n.length;s++)a(n[s]);return a}({1:[function(e,r,t){"use strict";function n(e,r){if(!(e instanceof r))throw new TypeError("Cannot call a class as a function")}function a(e){var r=function(){function r(t,a,i,s){var o=this;n(this,r),this.os=t,this._model=a,this.idArray=i,this.valArray=s,this.eventHandler=new e.utils.EventHandler(function(e){var r=[];for(var t in e){var n=e[t];if("Insert"===n.struct){var a=void 0;if(null===n.left)a=0;else{var i=JSON.stringify(n.left);if(a=o.idArray.indexOf(i)+1,0>=a)throw new Error("Unexpected operation!")}o.idArray.splice(a,0,JSON.stringify(n.id)),o.valArray.splice(a,0,n.content),r.push({type:"insert",object:o,index:a,value:n.content,length:1})}else{if("Delete"!==n.struct)throw new Error("Unexpected struct!");var a=o.idArray.indexOf(JSON.stringify(n.target));if(a>=0){var s=o.valArray[a];o.idArray.splice(a,1),o.valArray.splice(a,1),r.push({type:"delete",object:o,index:a,value:s,length:1})}}}o.eventHandler.callEventListeners(r)})}return i(r,[{key:"get",value:function(e){if(null==e||"number"!=typeof e)throw new Error("pos must be a number!");return this.valArray[e]}},{key:"toArray",value:function(){return this.valArray.slice()}},{key:"push",value:function(e){this.insert(this.idArray.length,e)}},{key:"insert",value:function(e,r){if("number"!=typeof e)throw new Error("pos must be a number!");if(!(r instanceof Array))throw new Error("contents must be an Array of objects!");if(0!==r.length){if(e>this.idArray.length||0>e)throw new Error("This position exceeds the range of the array!");for(var t=0===e?null:JSON.parse(this.idArray[e-1]),n=[],a=t,i=0;i<r.length;i++){var s={left:a,origin:a,parent:this._model,content:r[i],struct:"Insert",id:this.os.getNextOpId()};n.push(s),a=s.id}var o=this.eventHandler;o.awaitAndPrematurelyCall(n),this.os.requestTransaction(regeneratorRuntime.mark(function u(){var e,r;return regeneratorRuntime.wrap(function(a){for(;;)switch(a.prev=a.next){case 0:if(null==t){a.next=5;break}return a.delegateYield(this.getOperation(t),"t0",2);case 2:e=a.t0.right,a.next=7;break;case 5:return a.delegateYield(this.getOperation(n[0].parent),"t1",6);case 6:e=a.t1.start;case 7:for(r in n)n[r].right=e;return a.delegateYield(this.applyCreatedOperations(n),"t2",9);case 9:o.awaitedInserts(n.length);case 10:case"end":return a.stop()}},u,this)}))}}},{key:"delete",value:function(e,r){if(null==r&&(r=1),"number"!=typeof r)throw new Error("pos must be a number!");if("number"!=typeof e)throw new Error("pos must be a number!");if(e+r>this.idArray.length||0>e||0>r)throw new Error("The deletion range exceeds the range of the array!");if(0!==r){for(var t=this.eventHandler,n=e>0?JSON.parse(this.idArray[e-1]):null,a=[],i=0;r>i;i++)a.push({target:JSON.parse(this.idArray[e+i]),struct:"Delete"});t.awaitAndPrematurelyCall(a),this.os.requestTransaction(regeneratorRuntime.mark(function s(){return regeneratorRuntime.wrap(function(e){for(;;)switch(e.prev=e.next){case 0:return e.delegateYield(this.applyCreatedOperations(a),"t0",1);case 1:t.awaitedDeletes(a.length,n);case 2:case"end":return e.stop()}},s,this)}))}}},{key:"observe",value:function(e){this.eventHandler.addEventListener(e)}},{key:"_changed",value:regeneratorRuntime.mark(function t(e,r){var n,a;return regeneratorRuntime.wrap(function(t){for(;;)switch(t.prev=t.next){case 0:if(r.deleted){t.next=13;break}if("Insert"!==r.struct){t.next=12;break}n=r.left;case 3:if(null==n){t.next=11;break}return t.delegateYield(e.getOperation(n),"t0",5);case 5:if(a=t.t0,a.deleted){t.next=8;break}return t.abrupt("break",11);case 8:n=a.left,t.next=3;break;case 11:r.left=n;case 12:this.eventHandler.receivedOp(r);case 13:case"end":return t.stop()}},t,this)})},{key:"length",get:function(){return this.idArray.length}}]),r}();e.extend("Array",new e.utils.CustomType({name:"Array","class":r,struct:"List",initType:regeneratorRuntime.mark(function t(n,a){var i,s;return regeneratorRuntime.wrap(function(t){for(;;)switch(t.prev=t.next){case 0:return i=[],t.delegateYield(e.Struct.List.map.call(this,a,function(e){return i.push(e.content),JSON.stringify(e.id)}),"t0",2);case 2:return s=t.t0,t.abrupt("return",new r(n,a.id,s,i));case 4:case"end":return t.stop()}},t,this)})}))}var i=function(){function e(e,r){for(var t=0;t<r.length;t++){var n=r[t];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(e,n.key,n)}}return function(r,t,n){return t&&e(r.prototype,t),n&&e(r,n),r}}();r.exports=a,"undefined"!=typeof Y&&a(Y)},{}]},{},[1]);
//# sourceMappingURL=y-array.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,37 @@
{
"name": "y-indexeddb",
"main": "y-indexeddb.js",
"version": "0.7.1",
"homepage": "http://y-js.org",
"authors": [
"Kevin Jahns <kevin.jahns@rwth-aachen.de>"
],
"description": "IndexedDB database adapter for Yjs",
"moduleType": [
"globals",
"node"
],
"keywords": [
"Yjs",
"connector",
"webrtc"
],
"license": "MIT",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests"
],
"_release": "0.7.1",
"_resolution": {
"type": "version",
"tag": "v0.7.1",
"commit": "f84233467fbf2f15a0bb74f34902d33f2790b9e3"
},
"_source": "git://github.com/y-js/y-indexeddb.git",
"_target": "~0.7.1",
"_originalSource": "y-indexeddb",
"_direct": true
}

View File

@ -0,0 +1,23 @@
The MIT License (MIT)
Copyright (c) 2014
- Kevin Jahns <kevin.jahns@rwth-aachen.de>.
- Chair of Computer Science 5 (Databases & Information Systems), RWTH Aachen University, Germany
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1 @@
# IndexedDB database adapter for Yjs

View File

@ -0,0 +1,27 @@
{
"name": "y-indexeddb",
"main": "y-indexeddb.js",
"version": "0.7.1",
"homepage": "http://y-js.org",
"authors": [
"Kevin Jahns <kevin.jahns@rwth-aachen.de>"
],
"description": "IndexedDB database adapter for Yjs",
"moduleType": [
"globals",
"node"
],
"keywords": [
"Yjs",
"connector",
"webrtc"
],
"license": "MIT",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests"
]
}

View File

@ -0,0 +1,189 @@
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
/* global Y */
'use strict'
function extend (Y) {
class Store {
constructor (transaction, name) {
this.store = transaction.objectStore(name)
}
* find (id) {
return yield this.store.get(id)
}
* put (v) {
yield this.store.put(v)
}
* delete (id) {
yield this.store.delete(id)
}
* findWithLowerBound (start) {
return yield this.store.openCursor(window.IDBKeyRange.lowerBound(start))
}
* findWithUpperBound (end) {
return yield this.store.openCursor(window.IDBKeyRange.upperBound(end), 'prev')
}
* findNext (id) {
return yield* this.findWithLowerBound([id[0], id[1] + 1])
}
* findPrev (id) {
return yield* this.findWithUpperBound([id[0], id[1] - 1])
}
* iterate (t, start, end, gen) {
var range = null
if (start != null && end != null) {
range = window.IDBKeyRange.bound(start, end)
} else if (start != null) {
range = window.IDBKeyRange.lowerBound(start)
} else if (end != null) {
range = window.IDBKeyRange.upperBound(end)
}
var cursorResult = this.store.openCursor(range)
while ((yield cursorResult) != null) {
yield* gen.call(t, cursorResult.result.value)
cursorResult.result.continue()
}
}
}
class Transaction extends Y.Transaction {
constructor (store) {
super(store)
var transaction = store.db.transaction(['OperationStore', 'StateStore', 'DeleteStore'], 'readwrite')
this.store = store
this.ss = new Store(transaction, 'StateStore')
this.os = new Store(transaction, 'OperationStore')
this.ds = new Store(transaction, 'DeleteStore')
}
}
class OperationStore extends Y.AbstractDatabase {
constructor (y, opts) {
super(y, opts)
if (opts == null) {
opts = {}
}
if (opts.namespace == null || typeof opts.namespace !== 'string') {
throw new Error('IndexedDB: expect a string (opts.namespace)!')
} else {
this.namespace = opts.namespace
}
if (opts.idbVersion != null) {
this.idbVersion = opts.idbVersion
} else {
this.idbVersion = 5
}
var store = this
// initialize database!
this.requestTransaction(function * () {
store.db = yield window.indexedDB.open(opts.namespace, store.idbVersion)
})
if (opts.cleanStart) {
this.requestTransaction(function * () {
yield this.os.store.clear()
yield this.ds.store.clear()
yield this.ss.store.clear()
})
}
var operationsToAdd = []
window.addEventListener('storage', function (event) {
if (event.key === '__YJS__' + store.namespace) {
operationsToAdd.push(event.newValue)
if (operationsToAdd.length === 1) {
store.requestTransaction(function * () {
var add = operationsToAdd
operationsToAdd = []
for (var i in add) {
// don't call the localStorage event twice..
var op = JSON.parse(add[i])
if (op.struct !== 'Delete') {
op = yield* this.getOperation(op.id)
}
yield* this.store.operationAdded(this, op, true)
}
})
}
}
}, false)
}
* operationAdded (transaction, op, noAdd) {
yield* super.operationAdded(transaction, op)
if (!noAdd) {
window.localStorage['__YJS__' + this.namespace] = JSON.stringify(op)
}
}
transact (makeGen) {
var transaction = this.db != null ? new Transaction(this) : null
var store = this
var gen = makeGen.call(transaction)
handleTransactions(gen.next())
function handleTransactions (result) {
var request = result.value
if (result.done) {
makeGen = store.getNextRequest()
if (makeGen != null) {
if (transaction == null && store.db != null) {
transaction = new Transaction(store)
}
gen = makeGen.call(transaction)
handleTransactions(gen.next())
} // else no transaction in progress!
return
}
if (request.constructor === window.IDBRequest) {
request.onsuccess = function () {
var res = request.result
if (res != null && res.constructor === window.IDBCursorWithValue) {
res = res.value
}
handleTransactions(gen.next(res))
}
request.onerror = function (err) {
gen.throw(err)
}
} else if (request.constructor === window.IDBCursor) {
request.onsuccess = function () {
handleTransactions(gen.next(request.result != null ? request.result.value : null))
}
request.onerror = function (err) {
gen.throw(err)
}
} else if (request.constructor === window.IDBOpenDBRequest) {
request.onsuccess = function (event) {
var db = event.target.result
handleTransactions(gen.next(db))
}
request.onerror = function () {
gen.throw("Couldn't open IndexedDB database!")
}
request.onupgradeneeded = function (event) {
var db = event.target.result
try {
db.createObjectStore('OperationStore', {keyPath: 'id'})
db.createObjectStore('DeleteStore', {keyPath: 'id'})
db.createObjectStore('StateStore', {keyPath: 'id'})
} catch (e) {
console.log('Store already exists!')
}
}
} else {
gen.throw('You must not yield this type!')
}
}
}
// TODO: implement "free"..
* destroy () {
this.db.close()
yield window.indexedDB.deleteDatabase(this.namespace)
}
}
Y.extend('indexeddb', OperationStore)
}
module.exports = extend
if (typeof Y !== 'undefined') {
extend(Y)
}
},{}]},{},[1])

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,39 @@
{
"name": "y-map",
"version": "0.7.2",
"homepage": "http://y-js.org",
"authors": [
"Kevin Jahns <kevin.jahns@rwth-aachen.de>"
],
"description": "Map type for Yjs",
"main": [
"./y-list.js",
"./build/node/y-list.js"
],
"keywords": [
"OT",
"collaboration",
"synchronization",
"ShareJS",
"Coweb",
"concurrency"
],
"license": "MIT",
"ignore": [
"node_modules",
"bower_components",
"test",
"extras",
"test"
],
"_release": "0.7.2",
"_resolution": {
"type": "version",
"tag": "v0.7.2",
"commit": "1d85b5890ce8d76a37c5f8ad2587ca8ec7e82cbc"
},
"_source": "git://github.com/y-js/y-map.git",
"_target": "~0.7.2",
"_originalSource": "y-map",
"_direct": true
}

View File

View File

@ -0,0 +1,29 @@
{
"name": "y-map",
"version": "0.7.3",
"homepage": "http://y-js.org",
"authors": [
"Kevin Jahns <kevin.jahns@rwth-aachen.de>"
],
"description": "Map type for Yjs",
"main": [
"./y-list.js",
"./build/node/y-list.js"
],
"keywords": [
"OT",
"collaboration",
"synchronization",
"ShareJS",
"Coweb",
"concurrency"
],
"license": "MIT",
"ignore": [
"node_modules",
"bower_components",
"test",
"extras",
"test"
]
}

View File

@ -0,0 +1,56 @@
{
"name": "map",
"version": "0.7.3",
"description": "Map Type for Yjs",
"main": "./src/Map.js",
"scripts": {
"test": "node --harmony ./node_modules/.bin/gulp test",
"lint": "./node_modules/.bin/standard"
},
"pre-commit": [
"lint",
"test"
],
"standard": {
"parser": "babel-eslint",
"ignore": [
"build/**",
"dist/**",
"./y.js",
"./y.js.map"
]
},
"repository": {
"type": "git",
"url": "https://github.com/y-js/map"
},
"keywords": [
"Yjs",
"OT",
"List",
"Array",
"collaboration",
"synchronization",
"ShareJs",
"Coweb",
"concurrency"
],
"author": "Kevin Jahns <kevin.jahns@rwth-aachen.de>",
"license": "MIT",
"bugs": {
"url": "https://github.com/y-js/yjs/issues"
},
"homepage": "http://y-js.org",
"devDependencies": {
"babel-eslint": "^4.1.4",
"babel-plugin-transform-runtime": "^6.1.18",
"babel-preset-es2015": "^6.1.18",
"babelify": "^7.2.0",
"gulp": "^3.9.0",
"gulp-load-plugins": "^1.1.0",
"gulp-prompt": "^0.1.2",
"pre-commit": "^1.1.2",
"run-sequence": "^1.1.4",
"standard": "^5.3.1"
}
}

View File

@ -0,0 +1,315 @@
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
/* global Y */
'use strict'
function extend (Y /* :any */) {
class YMap {
/* ::
_model: Id;
os: Y.AbstractDatabase;
map: Object;
contents: any;
opContents: Object;
eventHandler: Function;
*/
constructor (os, model, contents, opContents) {
this._model = model.id
this.os = os
this.map = Y.utils.copyObject(model.map)
this.contents = contents
this.opContents = opContents
this.eventHandler = new Y.utils.EventHandler(ops => {
var userEvents = []
for (var i in ops) {
var op = ops[i]
var oldValue
// key is the name to use to access (op)content
var key = op.struct === 'Delete' ? op.key : op.parentSub
// compute oldValue
if (this.opContents[key] != null) {
let prevType = this.opContents[key]
oldValue = () => {// eslint-disable-line
return new Promise((resolve) => {
this.os.requestTransaction(function *() {// eslint-disable-line
var type = yield* this.getType(prevType)
resolve(type)
})
})
}
} else {
oldValue = this.contents[key]
}
// compute op event
if (op.struct === 'Insert') {
if (op.left === null) {
if (op.opContent != null) {
delete this.contents[key]
if (op.deleted) {
delete this.opContents[key]
} else {
this.opContents[key] = op.opContent
}
} else {
delete this.opContents[key]
if (op.deleted) {
delete this.contents[key]
} else {
this.contents[key] = op.content
}
}
this.map[key] = op.id
var insertEvent
if (oldValue === undefined) {
insertEvent = {
name: key,
object: this,
type: 'add'
}
} else {
insertEvent = {
name: key,
object: this,
oldValue: oldValue,
type: 'update'
}
}
userEvents.push(insertEvent)
}
} else if (op.struct === 'Delete') {
if (Y.utils.compareIds(this.map[key], op.target)) {
delete this.opContents[key]
delete this.contents[key]
var deleteEvent = {
name: key,
object: this,
oldValue: oldValue,
type: 'delete'
}
userEvents.push(deleteEvent)
}
} else {
throw new Error('Unexpected Operation!')
}
}
if (userEvents.length > 0) {
this.eventHandler.callEventListeners(userEvents)
}
})
}
get (key) {
// return property.
// if property does not exist, return null
// if property is a type, return a promise
if (key == null) {
throw new Error('You must specify key!')
}
if (this.opContents[key] == null) {
return this.contents[key]
} else {
return new Promise((resolve) => {
var oid = this.opContents[key]
this.os.requestTransaction(function *() {
var type = yield* this.getType(oid)
resolve(type)
})
})
}
}
/*
If there is a primitive (not a custom type), then return it.
Returns all primitive values, if propertyName is specified!
Note: modifying the return value could result in inconsistencies!
-- so make sure to copy it first!
*/
getPrimitive (key) {
if (key == null) {
return Y.utils.copyObject(this.contents)
} else {
return this.contents[key]
}
}
delete (key) {
var right = this.map[key]
if (right != null) {
var del = {
target: right,
struct: 'Delete'
}
var eventHandler = this.eventHandler
var modDel = Y.utils.copyObject(del)
modDel.key = key
eventHandler.awaitAndPrematurelyCall([modDel])
this.os.requestTransaction(function *() {
yield* this.applyCreatedOperations([del])
eventHandler.awaitedDeletes(1)
})
}
}
set (key, value) {
// set property.
// if property is a type, return a promise
// if not, apply immediately on this type an call event
var right = this.map[key] || null
var insert /* :any */ = {
left: null,
right: right,
origin: null,
parent: this._model,
parentSub: key,
struct: 'Insert'
}
return new Promise((resolve) => {
if (value instanceof Y.utils.CustomType) {
// construct a new type
this.os.requestTransaction(function *() {
var type = yield* this.createType(value)
insert.opContent = type._model
insert.id = this.store.getNextOpId()
yield* this.applyCreatedOperations([insert])
resolve(type)
})
} else {
insert.content = value
insert.id = this.os.getNextOpId()
var eventHandler = this.eventHandler
eventHandler.awaitAndPrematurelyCall([insert])
this.os.requestTransaction(function *() {
yield* this.applyCreatedOperations([insert])
eventHandler.awaitedInserts(1)
})
resolve(value)
}
})
}
observe (f) {
this.eventHandler.addEventListener(f)
}
unobserve (f) {
this.eventHandler.removeEventListener(f)
}
/*
Observe a path.
E.g.
```
o.set('textarea', Y.TextBind)
o.observePath(['textarea'], function(t){
// is called whenever textarea is replaced
t.bind(textarea)
})
returns a Promise that contains a function that removes the observer from the path.
*/
observePath (path, f) {
var self = this
function observeProperty (events) {
// call f whenever path changes
for (var i = 0; i < events.length; i++) {
var event = events[i]
if (event.name === propertyName) {
// call this also for delete events!
var property = self.get(propertyName)
if (property instanceof Promise) {
property.then(f)
} else {
f(property)
}
}
}
}
if (path.length < 1) {
throw new Error('Path must contain at least one element!')
} else if (path.length === 1) {
var propertyName = path[0]
var property = self.get(propertyName)
if (property instanceof Promise) {
property.then(f)
} else {
f(property)
}
this.observe(observeProperty)
return Promise.resolve(function () {
self.unobserve(f)
})
} else {
var deleteChildObservers
var resetObserverPath = function () {
var promise = self.get(path[0])
if (!promise instanceof Promise) {
// its either not defined or a primitive value
promise = self.set(path[0], Y.Map)
}
return promise.then(function (map) {
return map.observePath(path.slice(1), f)
}).then(function (_deleteChildObservers) {
// update deleteChildObservers
deleteChildObservers = _deleteChildObservers
return Promise.resolve() // Promise does not return anything
})
}
var observer = function (events) {
for (var e in events) {
var event = events[e]
if (event.name === path[0]) {
if (deleteChildObservers != null) {
deleteChildObservers()
}
if (event.type === 'add' || event.type === 'update') {
resetObserverPath()
}
// TODO: what about the delete events?
}
}
}
self.observe(observer)
return resetObserverPath().then(
// this promise contains a function that deletes all the child observers
// and how to unobserve the observe from this object
new Promise.resolve(function () { // eslint-disable-line
if (deleteChildObservers != null) {
deleteChildObservers()
}
self.unobserve(observer)
})
)
}
}
* _changed (transaction, op) {
if (op.struct === 'Delete') {
var target = yield* transaction.getOperation(op.target)
op.key = target.parentSub
}
this.eventHandler.receivedOp(op)
}
}
Y.extend('Map', new Y.utils.CustomType({
name: 'Map',
class: YMap,
struct: 'Map',
initType: function * YMapInitializer (os, model) {
var contents = {}
var opContents = {}
var map = model.map
for (var name in map) {
var op = yield* this.getOperation(map[name])
if (op.opContent != null) {
opContents[name] = op.opContent
} else {
contents[name] = op.content
}
}
return new YMap(os, model, contents, opContents)
}
}))
}
module.exports = extend
if (typeof Y !== 'undefined') {
extend(Y)
}
},{}]},{},[1])

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,37 @@
{
"name": "y-memory",
"main": "y-memory.js",
"version": "0.7.0",
"homepage": "http://y-js.org",
"authors": [
"Kevin Jahns <kevin.jahns@rwth-aachen.de>"
],
"description": "In-Memory database adapter for Yjs",
"moduleType": [
"globals",
"node"
],
"keywords": [
"Yjs",
"connector",
"webrtc"
],
"license": "MIT",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests"
],
"_release": "0.7.0",
"_resolution": {
"type": "version",
"tag": "v0.7.0",
"commit": "04c97da664d857328524e8a873e4f40cc066125d"
},
"_source": "git://github.com/y-js/y-memory.git",
"_target": "~0.7.0",
"_originalSource": "y-memory",
"_direct": true
}

View File

@ -0,0 +1,23 @@
The MIT License (MIT)
Copyright (c) 2014
- Kevin Jahns <kevin.jahns@rwth-aachen.de>.
- Chair of Computer Science 5 (Databases & Information Systems), RWTH Aachen University, Germany
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1 @@
# IndexedDB database adapter for Yjs

View File

@ -0,0 +1,27 @@
{
"name": "y-memory",
"main": "y-memory.js",
"version": "0.7.1",
"homepage": "http://y-js.org",
"authors": [
"Kevin Jahns <kevin.jahns@rwth-aachen.de>"
],
"description": "In-Memory database adapter for Yjs",
"moduleType": [
"globals",
"node"
],
"keywords": [
"Yjs",
"connector",
"webrtc"
],
"license": "MIT",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests"
]
}

View File

@ -0,0 +1,564 @@
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
/* global Y */
'use strict'
function extend (Y) {
require('./RedBlackTree.js')(Y)
class Transaction extends Y.Transaction {
constructor (store) {
super(store)
this.store = store
this.ss = store.ss
this.os = store.os
this.ds = store.ds
}
}
class Database extends Y.AbstractDatabase {
constructor (y, opts) {
super(y, opts)
this.os = new Y.utils.RBTree()
this.ds = new Y.utils.RBTree()
this.ss = new Y.utils.RBTree()
}
logTable () {
var self = this
self.requestTransaction(function * () {
console.log('User: ', this.store.y.connector.userId, "==============================") // eslint-disable-line
console.log("State Set (SS):", yield* this.getStateSet()) // eslint-disable-line
console.log("Operation Store (OS):") // eslint-disable-line
yield* this.os.logTable() // eslint-disable-line
console.log("Deletion Store (DS):") //eslint-disable-line
yield* this.ds.logTable() // eslint-disable-line
if (this.store.gc1.length > 0 || this.store.gc2.length > 0) {
console.warn('GC1|2 not empty!', this.store.gc1, this.store.gc2)
}
if (JSON.stringify(this.store.listenersById) !== '{}') {
console.warn('listenersById not empty!')
}
if (JSON.stringify(this.store.listenersByIdExecuteNow) !== '[]') {
console.warn('listenersByIdExecuteNow not empty!')
}
if (this.store.transactionInProgress) {
console.warn('Transaction still in progress!')
}
}, true)
}
transact (makeGen) {
var t = new Transaction(this)
while (makeGen !== null) {
var gen = makeGen.call(t)
var res = gen.next()
while (!res.done) {
res = gen.next(res.value)
}
makeGen = this.getNextRequest()
}
}
* destroy () {
super.destroy()
delete this.os
delete this.ss
delete this.ds
}
}
Y.extend('memory', Database)
}
module.exports = extend
if (typeof Y !== 'undefined') {
extend(Y)
}
},{"./RedBlackTree.js":2}],2:[function(require,module,exports){
'use strict'
/*
This file contains a not so fancy implemantion of a Red Black Tree.
*/
module.exports = function (Y) {
class N {
// A created node is always red!
constructor (val) {
this.val = val
this.color = true
this._left = null
this._right = null
this._parent = null
if (val.id === null) {
throw new Error('You must define id!')
}
}
isRed () { return this.color }
isBlack () { return !this.color }
redden () { this.color = true; return this }
blacken () { this.color = false; return this }
get grandparent () {
return this.parent.parent
}
get parent () {
return this._parent
}
get sibling () {
return (this === this.parent.left)
? this.parent.right : this.parent.left
}
get left () {
return this._left
}
get right () {
return this._right
}
set left (n) {
if (n !== null) {
n._parent = this
}
this._left = n
}
set right (n) {
if (n !== null) {
n._parent = this
}
this._right = n
}
rotateLeft (tree) {
var parent = this.parent
var newParent = this.right
var newRight = this.right.left
newParent.left = this
this.right = newRight
if (parent === null) {
tree.root = newParent
newParent._parent = null
} else if (parent.left === this) {
parent.left = newParent
} else if (parent.right === this) {
parent.right = newParent
} else {
throw new Error('The elements are wrongly connected!')
}
}
next () {
if (this.right !== null) {
// search the most left node in the right tree
var o = this.right
while (o.left !== null) {
o = o.left
}
return o
} else {
var p = this
while (p.parent !== null && p !== p.parent.left) {
p = p.parent
}
return p.parent
}
}
prev () {
if (this.left !== null) {
// search the most right node in the left tree
var o = this.left
while (o.right !== null) {
o = o.right
}
return o
} else {
var p = this
while (p.parent !== null && p !== p.parent.right) {
p = p.parent
}
return p.parent
}
}
rotateRight (tree) {
var parent = this.parent
var newParent = this.left
var newLeft = this.left.right
newParent.right = this
this.left = newLeft
if (parent === null) {
tree.root = newParent
newParent._parent = null
} else if (parent.left === this) {
parent.left = newParent
} else if (parent.right === this) {
parent.right = newParent
} else {
throw new Error('The elements are wrongly connected!')
}
}
getUncle () {
// we can assume that grandparent exists when this is called!
if (this.parent === this.parent.parent.left) {
return this.parent.parent.right
} else {
return this.parent.parent.left
}
}
}
class RBTree {
constructor () {
this.root = null
this.length = 0
}
* findNext (id) {
return yield* this.findWithLowerBound([id[0], id[1] + 1])
}
* findPrev (id) {
return yield* this.findWithUpperBound([id[0], id[1] - 1])
}
findNodeWithLowerBound (from) {
if (from === void 0) {
throw new Error('You must define from!')
}
var o = this.root
if (o === null) {
return null
} else {
while (true) {
if ((from === null || Y.utils.smaller(from, o.val.id)) && o.left !== null) {
// o is included in the bound
// try to find an element that is closer to the bound
o = o.left
} else if (from !== null && Y.utils.smaller(o.val.id, from)) {
// o is not within the bound, maybe one of the right elements is..
if (o.right !== null) {
o = o.right
} else {
// there is no right element. Search for the next bigger element,
// this should be within the bounds
return o.next()
}
} else {
return o
}
}
}
}
findNodeWithUpperBound (to) {
if (to === void 0) {
throw new Error('You must define from!')
}
var o = this.root
if (o === null) {
return null
} else {
while (true) {
if ((to === null || Y.utils.smaller(o.val.id, to)) && o.right !== null) {
// o is included in the bound
// try to find an element that is closer to the bound
o = o.right
} else if (to !== null && Y.utils.smaller(to, o.val.id)) {
// o is not within the bound, maybe one of the left elements is..
if (o.left !== null) {
o = o.left
} else {
// there is no left element. Search for the prev smaller element,
// this should be within the bounds
return o.prev()
}
} else {
return o
}
}
}
}
* findWithLowerBound (from) {
var n = this.findNodeWithLowerBound(from)
return n == null ? null : n.val
}
* findWithUpperBound (to) {
var n = this.findNodeWithUpperBound(to)
return n == null ? null : n.val
}
* iterate (t, from, to, f) {
var o = this.findNodeWithLowerBound(from)
while (o !== null && (to === null || Y.utils.smaller(o.val.id, to) || Y.utils.compareIds(o.val.id, to))) {
yield* f.call(t, o.val)
o = o.next()
}
return true
}
* logTable (from, to, filter) {
if (filter == null) {
filter = function () {
return true
}
}
if (from == null) { from = null }
if (to == null) { to = null }
var os = []
yield* this.iterate(this, from, to, function * (o) {
if (filter(o)) {
var o_ = {}
for (var key in o) {
if (typeof o[key] === 'object') {
o_[key] = JSON.stringify(o[key])
} else {
o_[key] = o[key]
}
}
os.push(o_)
}
})
if (console.table != null) {
console.table(os)
}
}
* find (id) {
var n
return (n = this.findNode(id)) ? n.val : null
}
findNode (id) {
if (id == null || id.constructor !== Array) {
throw new Error('Expect id to be an array!')
}
var o = this.root
if (o === null) {
return false
} else {
while (true) {
if (o === null) {
return false
}
if (Y.utils.smaller(id, o.val.id)) {
o = o.left
} else if (Y.utils.smaller(o.val.id, id)) {
o = o.right
} else {
return o
}
}
}
}
* delete (id) {
if (id == null || id.constructor !== Array) {
throw new Error('id is expected to be an Array!')
}
var d = this.findNode(id)
if (d == null) {
throw new Error('Element does not exist!')
}
this.length--
if (d.left !== null && d.right !== null) {
// switch d with the greates element in the left subtree.
// o should have at most one child.
var o = d.left
// find
while (o.right !== null) {
o = o.right
}
// switch
d.val = o.val
d = o
}
// d has at most one child
// let n be the node that replaces d
var isFakeChild
var child = d.left || d.right
if (child === null) {
isFakeChild = true
child = new N({id: 0})
child.blacken()
d.right = child
} else {
isFakeChild = false
}
if (d.parent === null) {
if (!isFakeChild) {
this.root = child
child.blacken()
child._parent = null
} else {
this.root = null
}
return
} else if (d.parent.left === d) {
d.parent.left = child
} else if (d.parent.right === d) {
d.parent.right = child
} else {
throw new Error('Impossible!')
}
if (d.isBlack()) {
if (child.isRed()) {
child.blacken()
} else {
this._fixDelete(child)
}
}
this.root.blacken()
if (isFakeChild) {
if (child.parent.left === child) {
child.parent.left = null
} else if (child.parent.right === child) {
child.parent.right = null
} else {
throw new Error('Impossible #3')
}
}
}
_fixDelete (n) {
function isBlack (node) {
return node !== null ? node.isBlack() : true
}
function isRed (node) {
return node !== null ? node.isRed() : false
}
if (n.parent === null) {
// this can only be called after the first iteration of fixDelete.
return
}
// d was already replaced by the child
// d is not the root
// d and child are black
var sibling = n.sibling
if (isRed(sibling)) {
// make sibling the grandfather
n.parent.redden()
sibling.blacken()
if (n === n.parent.left) {
n.parent.rotateLeft(this)
} else if (n === n.parent.right) {
n.parent.rotateRight(this)
} else {
throw new Error('Impossible #2')
}
sibling = n.sibling
}
// parent, sibling, and children of n are black
if (n.parent.isBlack() &&
sibling.isBlack() &&
isBlack(sibling.left) &&
isBlack(sibling.right)
) {
sibling.redden()
this._fixDelete(n.parent)
} else if (n.parent.isRed() &&
sibling.isBlack() &&
isBlack(sibling.left) &&
isBlack(sibling.right)
) {
sibling.redden()
n.parent.blacken()
} else {
if (n === n.parent.left &&
sibling.isBlack() &&
isRed(sibling.left) &&
isBlack(sibling.right)
) {
sibling.redden()
sibling.left.blacken()
sibling.rotateRight(this)
sibling = n.sibling
} else if (n === n.parent.right &&
sibling.isBlack() &&
isRed(sibling.right) &&
isBlack(sibling.left)
) {
sibling.redden()
sibling.right.blacken()
sibling.rotateLeft(this)
sibling = n.sibling
}
sibling.color = n.parent.color
n.parent.blacken()
if (n === n.parent.left) {
sibling.right.blacken()
n.parent.rotateLeft(this)
} else {
sibling.left.blacken()
n.parent.rotateRight(this)
}
}
}
* put (v) {
if (v == null || v.id == null || v.id.constructor !== Array) {
throw new Error('v is expected to have an id property which is an Array!')
}
var node = new N(v)
if (this.root !== null) {
var p = this.root // p abbrev. parent
while (true) {
if (Y.utils.smaller(node.val.id, p.val.id)) {
if (p.left === null) {
p.left = node
break
} else {
p = p.left
}
} else if (Y.utils.smaller(p.val.id, node.val.id)) {
if (p.right === null) {
p.right = node
break
} else {
p = p.right
}
} else {
p.val = node.val
return p
}
}
this._fixInsert(node)
} else {
this.root = node
}
this.length++
this.root.blacken()
return node
}
_fixInsert (n) {
if (n.parent === null) {
n.blacken()
return
} else if (n.parent.isBlack()) {
return
}
var uncle = n.getUncle()
if (uncle !== null && uncle.isRed()) {
// Note: parent: red, uncle: red
n.parent.blacken()
uncle.blacken()
n.grandparent.redden()
this._fixInsert(n.grandparent)
} else {
// Note: parent: red, uncle: black or null
// Now we transform the tree in such a way that
// either of these holds:
// 1) grandparent.left.isRed
// and grandparent.left.left.isRed
// 2) grandparent.right.isRed
// and grandparent.right.right.isRed
if (n === n.parent.right && n.parent === n.grandparent.left) {
n.parent.rotateLeft(this)
// Since we rotated and want to use the previous
// cases, we need to set n in such a way that
// n.parent.isRed again
n = n.left
} else if (n === n.parent.left && n.parent === n.grandparent.right) {
n.parent.rotateRight(this)
// see above
n = n.right
}
// Case 1) or 2) hold from here on.
// Now traverse grandparent, make parent a black node
// on the highest level which holds two red nodes.
n.parent.blacken()
n.grandparent.redden()
if (n === n.parent.left) {
// Case 1
n.grandparent.rotateRight(this)
} else {
// Case 2
n.grandparent.rotateLeft(this)
}
}
}
}
Y.utils.RBTree = RBTree
}
},{}]},{},[1])

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,36 @@
{
"name": "y-richtext",
"version": "0.7.5",
"authors": [
"corentin.cadiou@linagora.com",
"kevin.jahns@rwth-aachen.de"
],
"description": "Rich Text type for Yjs",
"keywords": [
"webrtc",
"text",
"edition",
"collaborative",
"rich text"
],
"license": "MIT",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests"
],
"dependencies": {},
"homepage": "https://github.com/y-js/y-richtext",
"_release": "0.7.5",
"_resolution": {
"type": "version",
"tag": "v0.7.5",
"commit": "5a6056fa835fab93e6add70537dde4cb67e8428c"
},
"_source": "git://github.com/y-js/y-richtext.git",
"_target": "~0.7.5",
"_originalSource": "y-richtext",
"_direct": true
}

View File

@ -0,0 +1,25 @@
The MIT License (MIT)
Copyright (c) 2015
- Corentin Cadiou <corentin.cadiou@linagora.com>
- Kevin Jahns <kevin.jahns@rwth-aachen.de>
- Linagora
- Veeting.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,59 @@
# Rich Text type for [Yjs](https://github.com/y-js/richtext)
This type strongly resembles the [rich text](https://github.com/ottypes/rich-text) format for operational transformation. Under the hood, however, several mechanisms ensure that the intentions of your changes are preserved. Furthermore, you can transform the actions on the document in the rich text format back and forth, and, therefore, you can bind this type to any rich text editor that supports the widely used rich text format.
## Use it!
Retrieve this with bower or npm.
##### Bower
```
bower install y-richtext --save
```
and include the js library.
```
<script src="./bower_components/y-richtext/y-richtext.js"></script>
```
##### NPM
```
npm install y-richtext --save
```
and put it on the `Y` object.
```
Y.RichText = require("y-richtext");
```
### RichText Object
##### Reference
* Create
```
var yrichtext = new Y.RichText()
```
* Create
```
var yrichtext = new Y.RichText(ot_delta)
```
* .bind(editor)
* Bind this type to an rich text editor. (Currently, only QuillJs is supported)
# A note on intention preservation
This type has several mechanisms to ensure that the intention of your actions are preserved. For example:
* If two users fix a word concurrently, only one change will prevail. A classical example is that two users want to correct the word "missplled". If two users correct it at the same time (or they merge after they corrected it offline), the result in operation transformation algorithms would be "misspeelled". This type will ensure that the result is "misspelled"
* When a user inserts content *c* after a set of content *C_left*, and before a set of content *C_right*, then *C_left* will be always to the left of c, and *C_right* will be always to the right of *c*. This property will also hold when content is deleted or when a deletion is undone.
## Contribution
We thank [Veeting](https://www.veeting.com/) and [Linagora](https://www.linagora.com/) who sponsored this work, and agreed to publish it as Open Source.
## License
Yjs and the RichText type are licensed under the [MIT License](./LICENSE.txt).
- Corentin Cadiou <corentin.cadiou@linagora.com>
- Kevin Jahns <kevin.jahns@rwth-aachen.de>

View File

@ -0,0 +1,25 @@
{
"name": "y-richtext",
"version": "0.7.6",
"authors": [
"corentin.cadiou@linagora.com",
"kevin.jahns@rwth-aachen.de"
],
"description": "Rich Text type for Yjs",
"keywords": [
"webrtc",
"text",
"edition",
"collaborative",
"rich text"
],
"license": "MIT",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests"
],
"dependencies": {}
}

View File

@ -0,0 +1,503 @@
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
/* global Y */
'use strict'
function extend (Y) {
Y.requestModules(['Array']).then(function () {
class YRichtext extends Y.Array['class'] {
constructor (os, _model, idArray, valArray) {
super(os, _model, idArray, valArray)
this._length = 0
this.instances = []
for (var i = 0, v = valArray[i]; i < valArray.length; i++) {
if (typeof v === 'string') {
this._length++
}
}
var self = this
this.observe(function (events) {
for (var i = 0, event = events[i]; i < events.length; i++) {
if (event.type === 'insert') {
if (typeof event.value === 'string') {
self._length++
}
} else if (event.type === 'delete') {
if (typeof event.value === 'string') {
self._length--
}
}
}
})
}
get length () {
return this._length
}
toString () {
return this.valArray.map(function (v) {
if (typeof v === 'string') {
return v
}
}).join('')
}
toOTOps () {
var ops = []
var op = {
insert: [],
attributes: {}
}
function createNewOp () {
var attrs = {}
// copy attributes
for (var name in op.attributes) {
attrs[name] = op.attributes[name]
}
op = {
insert: [],
attributes: attrs
}
}
var i = 0
for (; i < this.valArray.length; i++) {
let v = this.valArray[i]
if (v.constructor === Array) {
if (op.insert.length > 0) {
op.insert = op.insert.join('')
ops.push(op)
createNewOp()
}
if (v[1] === null) {
delete op.attributes[v[0]]
} else {
op.attributes[v[0]] = v[1]
}
} else {
op.insert.push(v)
}
}
if (op.insert.length > 0) {
op.insert = op.insert.join('')
ops.push(op)
}
return ops
}
insert (pos, content) {
var curPos = 0
var selection = {}
for (var i = 0; i < this.valArray.length; i++) {
if (curPos === pos) {
break
}
var v = this.valArray[i]
if (typeof v === 'string') {
curPos++
} else if (v.constructor === Array) {
if (v[1] === null) {
delete selection[v[0]]
} else {
selection[v[0]] = v[1]
}
}
}
super.insert(i, content.split(''))
return selection
}
delete (pos, length) {
/*
let x = to be deleted string
let s = some string
let * = some selection
E.g.
sss*s***x*xxxxx***xx*x**ss*s
|---delete-range--|
delStart delEnd
We'll check the following
* is it possible to delete some of the selections?
1. a dominating selection to the right could be the same as the selection (curSel) to delStart
2. a selections could be overwritten by another selection to the right
*/
var curPos = 0
var curSel = {}
var endPos = pos + length
if (length <= 0) return
var delStart // relative to valArray
var delEnd // ..
var v, i // helper variable for elements of valArray
for (delStart = 0, v = this.valArray[delStart]; curPos < pos && delStart < this.valArray.length; v = this.valArray[++delStart]) {
if (typeof v === 'string') {
curPos++
} else if (v.constructor === Array) {
curSel[v[0]] = v[1]
}
}
for (delEnd = delStart, v = this.valArray[delEnd]; curPos < endPos && delEnd < this.valArray.length; v = this.valArray[++delEnd]) {
if (typeof v === 'string') {
curPos++
}
}
if (delEnd === this.valArray.length) {
// yay, you can delete everything without checking
for (i = delEnd - 1, v = this.valArray[i]; i >= delStart; v = this.valArray[--i]) {
super.delete(i, 1)
}
} else {
if (typeof v === 'string') {
delEnd--
}
var rightSel = {}
for (i = delEnd, v = this.valArray[i]; i >= delStart; v = this.valArray[--i]) {
if (v.constructor === Array) {
if (rightSel[v[0]] === undefined) {
if (v[1] === curSel[v[0]]) {
// case 1.
super.delete(i, 1)
}
rightSel[v[0]] = v[1]
} else {
// case 2.
super.delete(i, 1)
}
} else if (typeof v === 'string') {
// always delete the strings
super.delete(i, 1)
}
}
}
}
/*
1. get selection attributes from position $from
(name it antiAttrs, and we'll use it to make sure that selection ends in antiAttrs)
2. Insert selection $attr, if necessary
3. Between from and to, we'll delete all selections that do not match $attr.
Furthermore, we'll update antiAttrs, if necessary
4. In the end well insert a selection that makes sure that selection($to) ends in antiAttrs
*/
select (from, to, attrName, attrValue) {
if (from == null || to == null || attrName == null || attrValue === undefined) {
throw new Error('You must define four parameters')
} else {
var step2i
var step2sel
var antiAttrs = [attrName, null]
var curPos = 0
var i = 0
// 1. compute antiAttrs
for (; i < this.valArray.length; i++) {
let v = this.valArray[i]
if (curPos === from) {
break
}
if (v.constructor === Array) {
if (v[0] === attrName) {
antiAttrs[1] = v[1]
}
} else if (typeof v === 'string') {
curPos++
}
}
// 2. Insert attr
if (antiAttrs[1] !== attrValue) {
// we'll execute this later
step2i = i
step2sel = [attrName, attrValue]
}
// 3. update antiAttrs, modify selection
var deletes = []
for (; i < this.valArray.length; i++) {
let v = this.valArray[i]
if (curPos === to) {
break
}
if (v.constructor === Array) {
if (v[0] === attrName) {
antiAttrs[1] = v[1]
deletes.push(i)
}
} else if (typeof v === 'string') {
curPos++
}
}
// actually delete the found selections
// also.. we have to delete from right to left (so that the positions dont change)
for (var j = deletes.length - 1; j >= 0; j--) {
var del = deletes[j]
super.delete(del, 1)
// update i, rel. to
if (del < i) {
i--
}
if (del < step2i) {
step2i--
}
}
// 4. Update selection to match antiAttrs
// never insert, if not necessary
// 1. when it is the last position ~ i < valArray.length)
// 2. when a similar attrName already exists between i and the next character
if (antiAttrs[1] !== attrValue && i < this.valArray.length) { // check 1.
var performStep4 = true
var v
for (j = i, v = this.valArray[j]; j < this.valArray.length && v.constructor === Array; v = this.valArray[++j]) {
if (v[0] === attrName) {
performStep4 = false // check 2.
if (v[1] === attrValue) {
super.delete(j, 1)
}
break
}
}
if (performStep4) {
var sel = [attrName, antiAttrs[1]]
super.insert(i, [sel])
}
}
if (step2i != null) {
super.insert(step2i, [step2sel])
// if there are some selections to the left of step2sel, delete them if possible
// * have same attribute name
// * no insert between step2sel and selection
for (j = step2i - 1, v = this.valArray[j]; j >= 0 && v.constructor === Array; v = this.valArray[--j]) {
if (v[0] === attrName) {
super.delete(j, 1)
}
}
}
}
}
bind (quill) {
this.instances.push(quill)
var self = this
// this function makes sure that either the
// quill event is executed, or the yjs observer is executed
var token = true
function mutualExcluse (f) {
if (token) {
token = false
try {
f()
} catch (e) {
token = true
throw new Error(e)
}
token = true
}
}
quill.setContents(this.toOTOps())
quill.on('text-change', function (delta) {
mutualExcluse(function () {
var pos = 0
var name // helper variable
for (var i = 0; i < delta.ops.length; i++) {
var op = delta.ops[i]
if (op.insert != null) {
var attrs = self.insert(pos, op.insert)
// create new selection
for (name in op.attributes) {
if (op.attributes[name] !== attrs[name]) {
self.select(pos, pos + op.insert.length, name, op.attributes[name])
}
}
// not-existence of an attribute in op.attributes denotes
// that we have to unselect (set to null)
for (name in attrs) {
if (op.attributes == null || attrs[name] !== op.attributes[name]) {
self.select(pos, pos + op.insert.length, name, null)
}
}
pos += op.insert.length
}
if (op.delete != null) {
self.delete(pos, op.delete)
}
if (op.retain != null) {
var afterRetain = pos + op.retain
if (afterRetain > self.length) {
var diff = afterRetain - self.length
var enters = ''
while (diff !== 0) {
diff--
enters += '\n'
}
for (name in op.attributes) {
quill.formatText(self.length, self.length + op.retain, name, null)
// quill.deleteText(self.length, self.length + op.retain)
}
quill.insertText(self.length, enters, op.attributes)
self.insert(self.length, enters)
}
for (name in op.attributes) {
self.select(pos, pos + op.retain, name, op.attributes[name])
}
pos = afterRetain
}
}
})
})
this.observe(function (events) {
mutualExcluse(function () {
var v // helper variable
var curSel // helper variable (current selection)
for (var i = 0; i < events.length; i++) {
var event = events[i]
if (event.type === 'insert') {
if (typeof event.value === 'string') {
var position = 0
var insertSel = {}
for (var l = event.index - 1; l >= 0; l--) {
v = self.valArray[l]
if (typeof v === 'string') {
position++
} else if (v.constructor === Array && typeof insertSel[v[0]] === 'undefined') {
insertSel[v[0]] = v[1]
}
}
quill.insertText(position, event.value, insertSel)
} else if (event.value.constructor === Array) {
// a new selection is created
// find left selection that matches newSel[0]
curSel = null
var newSel = event.value
// denotes the start position of the selection
// (without the selection objects)
var selectionStart = 0
for (var j = event.index - 1; j >= 0; j--) {
v = self.valArray[j]
if (v.constructor === Array) {
// check if v matches newSel
if (newSel[0] === v[0]) {
// found a selection
// update curSel and go to next step
curSel = v[1]
break
}
} else if (typeof v === 'string') {
selectionStart++
}
}
// make sure to decrement j, so we correctly compute selectionStart
for (; j >= 0; j--) {
v = self.valArray[j]
if (typeof v === 'string') {
selectionStart++
}
}
// either a selection was found {then curSel was updated}, or not (then curSel = null)
if (newSel[1] === curSel) {
// both are the same. not necessary to do anything
return
}
// now find out the range over which newSel has to be created
var selectionEnd = selectionStart
for (var k = event.index + 1; k < self.valArray.length; k++) {
v = self.valArray[k]
if (v.constructor === Array) {
if (v[0] === newSel[0]) {
// found another selection with same attr name
break
}
} else if (typeof v === 'string') {
selectionEnd++
}
}
// create a selection from selectionStart to selectionEnd
if (selectionStart !== selectionEnd) {
quill.formatText(selectionStart, selectionEnd, newSel[0], newSel[1])
}
}
} else if (event.type === 'delete') {
if (typeof event.value === 'string') { // TODO: see button. add || `event.length > 1`
// only if these conditions are true, we have to actually check if we have to delete sth.
// Then we have to check if between pos and pos + event.length are selections:
// delete till pos + (event.length - number of selections)
var pos = 0
for (var u = 0; u < event.index; u++) {
v = self.valArray[u]
if (typeof v === 'string') {
pos++
}
}
var delLength = event.length
/* TODO!!
they do not exist anymore.. so i can't query. you have to query over event.value(s) - but that not yet implemented
for (; i < event.index + event.length; i++) {
if (self.valArray[i].constructor === Array) {
delLength--
}
}*/
quill.deleteText(pos, pos + delLength)
} else if (event.value.constructor === Array) {
curSel = null
var from = 0
var x
for (x = event.index - 1; x >= 0; x--) {
v = self.valArray[x]
if (v.constructor === Array) {
if (v[0] === event.value[0]) {
curSel = v[1]
break
}
} else if (typeof v === 'string') {
from++
}
}
for (; x >= 0; v = self.valArray[--x]) {
if (typeof v === 'string') {
from++
}
}
var to = from
for (x = event.index; x < self.valArray.length; x++) {
v = self.valArray[x]
if (v.constructor === Array) {
if (v[0] === event.value[0]) {
break
}
} else if (typeof v === 'string') {
to++
}
}
if (curSel !== event.value[1] && from !== to) {
quill.formatText(from, to, event.value[0], curSel)
}
}
}
}
quill.editor.checkUpdate()
})
})
}
* _changed () {
this.instances.forEach(function (quill) {
quill.editor.checkUpdate()
})
yield* Y.Array.class.prototype._changed.apply(this, arguments)
}
}
Y.extend('Richtext', new Y.utils.CustomType({
name: 'Richtext',
class: YRichtext,
struct: 'List',
initType: function * YTextInitializer (os, model) {
var valArray = []
var idArray = yield* Y.Struct.List.map.call(this, model, function (c) {
valArray.push(c.content)
return JSON.stringify(c.id)
})
return new YRichtext(os, model.id, idArray, valArray)
}
}))
})
}
module.exports = extend
if (typeof Y !== 'undefined') {
extend(Y)
}
},{}]},{},[1])

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,37 @@
{
"name": "y-text",
"version": "0.7.1",
"homepage": "http://y-js.org",
"authors": [
"Kevin Jahns <kevin.jahns@rwth-aachen.de>"
],
"description": "Text type for Yjs",
"main": "./src/Text.js",
"keywords": [
"OT",
"collaboration",
"synchronization",
"ShareJS",
"Coweb",
"concurrency"
],
"license": "MIT",
"ignore": [
"node_modules",
"bower_components",
"test",
"extras",
"test"
],
"devDependencies": {},
"_release": "0.7.1",
"_resolution": {
"type": "version",
"tag": "v0.7.1",
"commit": "bdb6553994226703504875431c56436704b3af57"
},
"_source": "git://github.com/y-js/y-text.git",
"_target": "~0.7.1",
"_originalSource": "y-text",
"_direct": true
}

View File

@ -0,0 +1,3 @@
{
"directory": "../"
}

View File

@ -0,0 +1,6 @@
/node_modules/
bower_components
.directory
.c9
.codio
.settings

View File

@ -0,0 +1,11 @@
language: node_js
before_install:
- "npm install -g bower coffee-script"
- "bower install"
node_js:
- "0.12"
- "0.11"
- "0.10"
branches:
only:
- master

View File

@ -0,0 +1,27 @@
{
"name": "y-text",
"version": "0.7.2",
"homepage": "http://y-js.org",
"authors": [
"Kevin Jahns <kevin.jahns@rwth-aachen.de>"
],
"description": "Text type for Yjs",
"main": "./src/Text.js",
"keywords": [
"OT",
"collaboration",
"synchronization",
"ShareJS",
"Coweb",
"concurrency"
],
"license": "MIT",
"ignore": [
"node_modules",
"bower_components",
"test",
"extras",
"test"
],
"devDependencies": {}
}

View File

@ -0,0 +1,291 @@
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
/* global Y */
'use strict'
function extend (Y) {
Y.requestModules(['Array']).then(function () {
class YText extends Y.Array['class'] {
constructor (os, _model, idArray, valArray) {
super(os, _model, idArray, valArray)
this.textfields = []
}
toString () {
return this.valArray.join('')
}
insert (pos, content) {
super.insert(pos, content.split(''))
}
bind (textfield, domRoot) {
domRoot = domRoot || window; // eslint-disable-line
if (domRoot.getSelection == null) {
domRoot = window;// eslint-disable-line
}
// don't duplicate!
for (var t in this.textfields) {
if (this.textfields[t] === textfield) {
return
}
}
var creatorToken = false
var word = this
textfield.value = this.toString()
this.textfields.push(textfield)
var createRange, writeRange, writeContent
if (textfield.selectionStart != null && textfield.setSelectionRange != null) {
createRange = function (fix) {
var left = textfield.selectionStart
var right = textfield.selectionEnd
if (fix != null) {
left = fix(left)
right = fix(right)
}
return {
left: left,
right: right
}
}
writeRange = function (range) {
writeContent(word.toString())
textfield.setSelectionRange(range.left, range.right)
}
writeContent = function (content) {
textfield.value = content
}
} else {
createRange = function (fix) {
var range = {}
var s = domRoot.getSelection()
var clength = textfield.textContent.length
range.left = Math.min(s.anchorOffset, clength)
range.right = Math.min(s.focusOffset, clength)
if (fix != null) {
range.left = fix(range.left)
range.right = fix(range.right)
}
var editedElement = s.focusNode
if (editedElement === textfield || editedElement === textfield.childNodes[0]) {
range.isReal = true
} else {
range.isReal = false
}
return range
}
writeRange = function (range) {
writeContent(word.toString())
var textnode = textfield.childNodes[0]
if (range.isReal && textnode != null) {
if (range.left < 0) {
range.left = 0
}
range.right = Math.max(range.left, range.right)
if (range.right > textnode.length) {
range.right = textnode.length
}
range.left = Math.min(range.left, range.right)
var r = document.createRange(); // eslint-disable-line
r.setStart(textnode, range.left)
r.setEnd(textnode, range.right)
var s = window.getSelection(); // eslint-disable-line
s.removeAllRanges()
s.addRange(r)
}
}
writeContent = function (content) {
var contentArray = content.replace(new RegExp('\n', 'g'), ' ').split(' ');// eslint-disable-line
textfield.innerText = ''
for (var i in contentArray) {
var c = contentArray[i]
textfield.innerText += c
if (i !== contentArray.length - 1) {
textfield.innerHTML += '&nbsp;'
}
}
}
}
writeContent(this.toString())
this.observe(function (events) {
for (var e in events) {
var event = events[e]
if (!creatorToken) {
var oPos, fix
if (event.type === 'insert') {
oPos = event.index
fix = function (cursor) {// eslint-disable-line
if (cursor <= oPos) {
return cursor
} else {
cursor += 1
return cursor
}
}
var r = createRange(fix)
writeRange(r)
} else if (event.type === 'delete') {
oPos = event.index
fix = function (cursor) {// eslint-disable-line
if (cursor < oPos) {
return cursor
} else {
cursor -= 1
return cursor
}
}
r = createRange(fix)
writeRange(r)
}
}
}
})
// consume all text-insert changes.
textfield.onkeypress = function (event) {
if (word.is_deleted) {
// if word is deleted, do not do anything ever again
textfield.onkeypress = null
return true
}
creatorToken = true
var char
if (event.keyCode === 13) {
char = '\n'
} else if (event.key != null) {
if (event.charCode === 32) {
char = ' '
} else {
char = event.key
}
} else {
char = window.String.fromCharCode(event.keyCode); // eslint-disable-line
}
if (char.length > 1) {
return true
} else if (char.length > 0) {
var r = createRange()
var pos = Math.min(r.left, r.right, word.length)
var diff = Math.abs(r.right - r.left)
word.delete(pos, diff)
word.insert(pos, char)
r.left = pos + char.length
r.right = r.left
writeRange(r)
}
event.preventDefault()
creatorToken = false
return false
}
textfield.onpaste = function (event) {
if (word.is_deleted) {
// if word is deleted, do not do anything ever again
textfield.onpaste = null
return true
}
event.preventDefault()
}
textfield.oncut = function (event) {
if (word.is_deleted) {
// if word is deleted, do not do anything ever again
textfield.oncut = null
return true
}
event.preventDefault()
}
//
// consume deletes. Note that
// chrome: won't consume deletions on keypress event.
// keyCode is deprecated. BUT: I don't see another way.
// since event.key is not implemented in the current version of chrome.
// Every browser supports keyCode. Let's stick with it for now..
//
textfield.onkeydown = function (event) {
creatorToken = true
if (word.is_deleted) {
// if word is deleted, do not do anything ever again
textfield.onkeydown = null
return true
}
var r = createRange()
var pos = Math.min(r.left, r.right, word.toString().length)
var diff = Math.abs(r.left - r.right)
if (event.keyCode != null && event.keyCode === 8) { // Backspace
if (diff > 0) {
word.delete(pos, diff)
r.left = pos
r.right = pos
writeRange(r)
} else {
if (event.ctrlKey != null && event.ctrlKey) {
var val = word.toString()
var newPos = pos
var delLength = 0
if (pos > 0) {
newPos--
delLength++
}
while (newPos > 0 && val[newPos] !== ' ' && val[newPos] !== '\n') {
newPos--
delLength++
}
word.delete(newPos, pos - newPos)
r.left = newPos
r.right = newPos
writeRange(r)
} else {
if (pos > 0) {
word.delete(pos - 1, 1)
r.left = pos - 1
r.right = pos - 1
writeRange(r)
}
}
}
event.preventDefault()
creatorToken = false
return false
} else if (event.keyCode != null && event.keyCode === 46) { // Delete
if (diff > 0) {
word.delete(pos, diff)
r.left = pos
r.right = pos
writeRange(r)
} else {
word.delete(pos, 1)
r.left = pos
r.right = pos
writeRange(r)
}
event.preventDefault()
creatorToken = false
return false
} else {
creatorToken = false
return true
}
}
}
}
Y.extend('Text', new Y.utils.CustomType({
name: 'Text',
class: YText,
struct: 'List',
initType: function * YTextInitializer (os, model) {
var valArray = []
var idArray = yield* Y.Struct.List.map.call(this, model, function (c) {
valArray.push(c.content)
return JSON.stringify(c.id)
})
return new YText(os, model.id, idArray, valArray)
}
}))
})
}
module.exports = extend
if (typeof Y !== 'undefined') {
extend(Y)
}
},{}]},{},[1])

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,37 @@
{
"name": "y-webrtc",
"main": "y-webrtc.js",
"version": "0.7.1",
"homepage": "http://y-js.org",
"authors": [
"Kevin Jahns <kevin.jahns@rwth-aachen.de>"
],
"description": "WebRTC connector for Yjs",
"moduleType": [
"globals",
"node"
],
"keywords": [
"Yjs",
"connector",
"webrtc"
],
"license": "MIT",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests"
],
"_release": "0.7.1",
"_resolution": {
"type": "version",
"tag": "v0.7.1",
"commit": "8620167b9feceaf4940b6fd0a7be60243e362420"
},
"_source": "git://github.com/y-js/y-webrtc.git",
"_target": "~0.7.1",
"_originalSource": "y-webrtc",
"_direct": true
}

View File

@ -0,0 +1,70 @@
# WebRTC Connector for [Yjs](https://github.com/y-js/yjs)
It propagates document updates directly to all users via WebRTC. While WebRTC is not the most reliable connector, messages are propagated with almost no delay.
* Very fast message propagation (not noticeable)
* Very easy to use
* Very little server load (you still have to set up a [signaling server](http://www.html5rocks.com/en/tutorials/webrtc/infrastructure/))
* Not suited for a large amount of collaborators
* WebRTC is not supported in all browsers, and some have troubles communicating with each other
We provide you with a free signaling server (it is used by default), but in production you should set up your own signaling server. You could use the [signalmaster](https://github.com/andyet/signalmaster) from &yet, which is very easy to set up.
## Use it!
Retrieve this with bower or npm, and use it as a js library or as a custom polymer element.
##### NPM
```
npm install y-webrtc --save
```
and put it on the `Y` object.
```
Y.WebRTC = require("y-webrtc");
```
##### Bower
```
bower install y-webrtc --save
```
##### Polymer
On the website you find a bunch of examples on how you can use Yjs as polymer element.
```
<link rel="import" href="../y-webrtc/y-webrtc.html">
<y-webrtc connector={{connector}} room="my-room-name"></y-webrtc>
```
### Create the connection object
This connector uses [SimpleWebRTC](https://simplewebrtc.com/) as an underlaying WebRTC framework, which supports the concept of rooms.
```
var options = {};
var conn = new Y.WebRTC("my_room_name", options); // will connect to the default signaling server
```
On the options object you can put the following properties:
* url (optional)
* Set the url of your signaling server. E.g. url = "https://yatta.ninja:8888" (which is the default endpoint)
* debug (optional)
* Whether to enable debugging mode (defaults to false)
# Start Hacking
This connector is also a nice starting point to build your own connector. The only 75 SLOCs of code are pretty well documented and understandable. If you have any troubles, don't hesitate to ask me for help!
### Directory Structure
* lib/
* Source files
* build/browser
* Unminified, but [browserified](http://browserify.org/) source files
* build/node
* npm modules
## License
Yjs is licensed under the [MIT License](./LICENSE.txt).
<kevin.jahns@rwth-aachen.de>

View File

@ -0,0 +1,27 @@
{
"name": "y-webrtc",
"main": "y-webrtc.js",
"version": "0.7.2",
"homepage": "http://y-js.org",
"authors": [
"Kevin Jahns <kevin.jahns@rwth-aachen.de>"
],
"description": "WebRTC connector for Yjs",
"moduleType": [
"globals",
"node"
],
"keywords": [
"Yjs",
"connector",
"webrtc"
],
"license": "MIT",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests"
]
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,37 @@
{
"name": "y-websockets-client",
"main": "y-websockets-client.js",
"version": "0.7.10",
"homepage": "http://y-js.org",
"authors": [
"Kevin Jahns <kevin.jahns@rwth-aachen.de>"
],
"description": "Websockets connector for Yjs (browser/node client)",
"moduleType": [
"globals",
"node"
],
"keywords": [
"Yjs",
"connector",
"websockets"
],
"license": "MIT",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests"
],
"_release": "0.7.10",
"_resolution": {
"type": "version",
"tag": "v0.7.10",
"commit": "b5ce42502dae6ad3cb0bf7b1d5a615af91876f1c"
},
"_source": "git://github.com/y-js/y-websockets-client.git",
"_target": "~0.7.10",
"_originalSource": "y-websockets-client",
"_direct": true
}

View File

@ -0,0 +1 @@
# Websockets Connector for [Yjs](https://github.com/y-js/yjs)

View File

@ -0,0 +1,27 @@
{
"name": "y-websockets-client",
"main": "y-websockets-client.js",
"version": "0.7.11",
"homepage": "http://y-js.org",
"authors": [
"Kevin Jahns <kevin.jahns@rwth-aachen.de>"
],
"description": "Websockets connector for Yjs (browser/node client)",
"moduleType": [
"globals",
"node"
],
"keywords": [
"Yjs",
"connector",
"websockets"
],
"license": "MIT",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests"
]
}

View File

@ -0,0 +1,54 @@
{
"name": "y-websockets-client",
"version": "0.7.1",
"description": "Websockets connector for Yjs (browser/node client)",
"main": "./src/Websockets-client.js",
"scripts": {
"test": "node --harmony ./node_modules/.bin/gulp test",
"lint": "./node_modules/.bin/standard"
},
"pre-commit": [
"lint",
"test"
],
"standard": {
"parser": "babel-eslint",
"ignore": [
"build/**",
"dist/**"
]
},
"repository": {
"type": "git",
"url": "https://github.com/y-js/y-websockets-client.git"
},
"keywords": [
"Yjs",
"Connector"
],
"author": "Kevin Jahns <kevin.jahns@rwth-aachen.de>",
"license": "MIT",
"bugs": {
"url": "https://github.com/y-js/y-websockets-client/issues"
},
"homepage": "http://y-js.org",
"dependencies": {
"socket.io-client": "^1.3.7"
},
"devDependencies": {
"babel-eslint": "^4.1.4",
"babel-plugin-transform-runtime": "^6.1.18",
"babel-preset-es2015": "^6.1.18",
"babel-runtime": "^6.1.18",
"babelify": "^7.2.0",
"fs": "0.0.2",
"gulp": "^3.9.0",
"gulp-load-plugins": "^1.1.0",
"gulp-prompt": "^0.1.2",
"http": "0.0.0",
"pre-commit": "^1.1.2",
"run-sequence": "^1.1.4",
"socket.io": "^1.3.7",
"standard": "^5.3.1"
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,29 @@
{
"name": "yjs",
"version": "0.7.6",
"homepage": "y-js.org",
"authors": [
"Kevin Jahns <kevin.jahns@rwth-aachen.de>"
],
"description": "A Framework that enables Real-Time collaboration on arbitrary data structures.",
"main": "y.js",
"keywords": [
"OT",
"collaboration",
"synchronization",
"sharejs",
"coweb",
"concurrency"
],
"license": "MIT",
"_release": "0.7.6",
"_resolution": {
"type": "version",
"tag": "v0.7.6",
"commit": "eee80e1755379a2f39e9d0ed780bf29975256575"
},
"_source": "git://github.com/y-js/yjs.git",
"_target": "~0.7.6",
"_originalSource": "yjs",
"_direct": true
}

View File

@ -0,0 +1,8 @@
node_modules
Examples/bower_components
.directory
.codio
.settings
.jshintignore
.jshintrc
.validate.json

View File

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html>
<body>
<style>
#chat p span {
color: blue;
}
</style>
<div id="chat"></div>
<form id="chatform">
<input name="username" type="text" style="width:15%;">
<input name="message" type="text" style="width:60%;">
<input type="submit" value="Send">
</form>
<script src="../bower_components/yjs/y.js"></script>
<script src="./index.js"></script>
</body>
</html>

View File

@ -0,0 +1,75 @@
/* @flow */
/* global Y */
// initialize a shared object. This function call returns a promise!
Y({
db: {
name: 'memory'
},
connector: {
name: 'websockets-client',
room: 'chat-example'
// debug: true,
// url: 'http://127.0.0.1:2345'
},
sourceDir: '/bower_components',
share: {
chat: 'Array'
}
}).then(function (y) {
window.y = y
// This functions inserts a message at the specified position in the DOM
function appendMessage(message, position) {
var p = document.createElement('p')
var uname = document.createElement('span')
uname.appendChild(document.createTextNode(message.username + ": "))
p.appendChild(uname)
p.appendChild(document.createTextNode(message.message))
document.querySelector('#chat').insertBefore(p, chat.children[position] || null)
}
// This function makes sure that only 7 messages exist in the chat history.
// The rest is deleted
function cleanupChat () {
var len
while ((len = y.share.chat.length) > 7) {
y.share.chat.delete(0)
}
}
// Insert the initial content
y.share.chat.toArray().forEach(appendMessage)
cleanupChat()
// whenever content changes, make sure to reflect the changes in the DOM
y.share.chat.observe(function (events) {
for (var i = 0; i < events.length; i++) {
if (events[i].type === 'insert') {
appendMessage(events[i].value, events[i].index)
} else if (events[i].type === 'delete') {
chat.children[events[i].index].remove()
}
}
// concurrent insertions may result in a history > 7, so cleanup here
cleanupChat()
})
document.querySelector('#chatform').onsubmit = function (event) {
// the form is submitted
var message = {
username: this.querySelector("[name=username]").value,
message: this.querySelector("[name=message]").value
}
if (message.username.length > 0 && message.message.length > 0) {
if (y.share.chat.length > 6) {
// If we are goint to insert the 8th element, make sure to delete first.
y.share.chat.delete(0)
}
// Here we insert a message in the shared chat type.
// This will call the observe function (see line 40)
// and reflect the change in the DOM
y.share.chat.push([message])
this.querySelector("[name=message]").value = ""
}
// Do not send this form!
event.preventDefault()
return false
}
})

View File

@ -0,0 +1,23 @@
<!DOCTYPE html>
<html>
<head>
<style type="text/css">
.draggable {
cursor: move;
}
</style>
</head>
<body>
<svg id="puzzle-example" width="100%" viewBox="0 0 800 800">
<g>
<path d="M 311.76636,154.23389 C 312.14136,171.85693 318.14087,184.97998 336.13843,184.23047 C 354.13647,183.48047 351.88647,180.48096 354.88599,178.98096 C 357.8855,177.48096 368.38452,170.35693 380.00806,169.98193 C 424.61841,168.54297 419.78296,223.6001 382.25757,223.6001 C 377.75806,223.6001 363.51001,219.10107 356.38599,211.97656 C 349.26196,204.85254 310.64185,207.10254 314.76636,236.34863 C 316.34888,247.5708 324.08374,267.90723 324.84595,286.23486 C 325.29321,296.99414 323.17603,307.00635 321.58911,315.6377 C 360.11353,305.4585 367.73462,304.30518 404.00513,312.83936 C 410.37915,314.33887 436.62573,310.21436 421.25269,290.3418 C 405.87964,270.46924 406.25464,248.34717 417.12817,240.84814 C 428.00171,233.34912 446.74976,228.84961 457.99829,234.09912 C 469.24683,239.34814 484.61987,255.84619 475.24585,271.59424 C 465.87231,287.34229 452.74878,290.7168 456.49829,303.84033 C 460.2478,316.96387 479.74536,320.33838 500.74292,321.83789 C 509.70142,322.47803 527.97192,323.28467 542.10864,320.12939 C 549.91821,318.38672 556.92212,315.89502 562.46753,313.56396 C 561.40796,277.80664 560.84888,245.71729 560.3606,241.97314 C 558.85278,230.41455 542.49536,217.28564 525.86499,223.2251 C 520.61548,225.1001 519.86548,231.84912 505.24243,232.59912 C 444.92798,235.69238 462.06958,143.26709 525.86499,180.48096 C 539.52759,188.45068 575.19409,190.7583 570.10913,156.85889 C 567.85962,141.86035 553.98608,102.86523 553.98608,102.86523 C 553.98608,102.86523 477.23755,111.82227 451.99878,91.991699 C 441.50024,83.74292 444.87476,69.494629 449.37427,61.245605 C 453.87378,52.996582 465.12231,46.622559 464.74731,36.123779 C 463.02563,-12.086426 392.96704,-10.902832 396.5061,36.873535 C 397.25562,46.997314 406.62964,52.621582 410.75415,60.495605 C 420.00757,78.161377 405.50024,96.073486 384.50757,99.490723 C 377.36206,100.65381 349.17505,102.65332 320.39429,102.23486 C 319.677,102.22461 318.95923,102.21143 318.24194,102.19775 C 315.08423,120.9751 311.55688,144.39697 311.76636,154.23389 z " style="fill:#f2c569;stroke:#000000" id="path2502"/>
<path d="M 500.74292,321.83789 C 479.74536,320.33838 460.2478,316.96387 456.49829,303.84033 C 452.74878,290.7168 465.87231,287.34229 475.24585,271.59424 C 484.61987,255.84619 469.24683,239.34814 457.99829,234.09912 C 446.74976,228.84961 428.00171,233.34912 417.12817,240.84814 C 406.25464,248.34717 405.87964,270.46924 421.25269,290.3418 C 436.62573,310.21436 410.37915,314.33887 404.00513,312.83936 C 367.73462,304.30518 360.11353,305.4585 321.58911,315.6377 C 320.56372,321.21484 319.75854,326.2207 320.01538,330.46191 C 320.76538,342.83545 329.3894,385.95508 327.8894,392.7041 C 326.3894,399.45312 313.64136,418.20117 297.89331,407.32715 C 282.14526,396.45361 276.52075,393.4541 265.27222,394.5791 C 254.02368,395.70361 239.77563,402.07812 239.77563,419.32568 C 239.77563,436.57373 250.27417,449.69727 268.64673,447.82227 C 287.36353,445.9126 317.92163,423.11035 325.63989,452.69678 C 330.1394,469.94434 330.51392,487.19238 330.1394,498.44092 C 329.95825,503.87646 326.09985,518.06592 322.16089,531.28125 C 353.2854,532.73682 386.47095,531.26611 394.2561,529.93701 C 430.30933,523.78174 429.31909,496.09766 412.62866,477.44385 C 406.25464,470.31934 401.75513,455.32129 405.87964,444.82275 C 414.07056,423.97314 458.8064,422.17773 473.37134,438.82324 C 483.86987,450.82178 475.99585,477.44385 468.49683,482.69287 C 453.52222,493.17529 457.22485,516.83008 473.37134,528.06201 C 504.79126,549.91943 572.35913,535.56152 572.35913,535.56152 C 572.35913,535.56152 567.85962,498.06592 567.48462,471.81934 C 567.10962,445.57275 589.60669,450.07227 593.3562,450.07227 C 597.10571,450.07227 604.22974,455.32129 609.47925,459.4458 C 614.72876,463.57031 618.85327,469.94434 630.85181,470.69434 C 677.43726,473.60596 674.58813,420.7373 631.97632,413.32666 C 623.35229,411.82666 614.72876,416.32617 603.10522,424.57519 C 591.48169,432.82422 577.23315,425.32519 570.10913,417.45117 C 566.07788,412.99561 563.8479,360.16406 562.46753,313.56396 C 556.92212,315.89502 549.91821,318.38672 542.10864,320.12939 C 527.97192,323.28467 509.70142,322.47803 500.74292,321.83789 z " style="fill:#f3f3d6;stroke:#000000" id="path2504"/>
<path d="M 240.52563,141.86035 C 257.60327,159.6499 243.94507,188.68799 214.65356,190.22949 C 185.09448,191.78516 164.66675,157.17822 190.28589,136.61621 C 200.49585,128.42139 198.05786,114.12158 179.78296,106.98975 C 154.4187,97.091553 90.54419,107.73975 90.54419,107.73975 C 90.54419,107.73975 100.88794,135.11328 101.41772,168.48242 C 101.79272,192.104 68.796875,189.47949 63.172607,186.85498 C 57.54834,184.23047 45.924805,173.73145 37.675781,173.73145 C -14.411865,173.73145 -10.013184,245.84375 39.925537,232.22412 C 48.174316,229.97461 56.42334,220.97559 68.796875,222.47559 C 81.17041,223.9751 87.544434,232.59912 87.544434,246.09766 C 87.544434,252.51709 87.0354,281.24268 86.340576,312.87012 C 119.15894,313.67676 160.60962,314.46582 170.03442,313.58887 C 186.15698,312.08936 195.90601,301.59033 188.40698,293.3418 C 180.90796,285.09277 156.16089,256.59619 179.03296,239.34814 C 201.90503,222.10059 235.65112,231.84912 239.77563,247.22217 C 243.90015,262.59521 240.52563,273.46924 234.90112,279.09326 C 229.27661,284.71777 210.52905,298.96582 221.40259,308.71484 C 232.27661,318.46338 263.77222,330.83691 302.39282,320.71338 C 309.58862,318.82715 315.92114,317.13525 321.58911,315.6377 C 323.17603,307.00635 325.29321,296.99414 324.84595,286.23486 C 324.08374,267.90723 316.34888,247.5708 314.76636,236.34863 C 310.64185,207.10254 349.26196,204.85254 356.38599,211.97656 C 363.51001,219.10107 377.75806,223.6001 382.25757,223.6001 C 419.78296,223.6001 424.61841,168.54297 380.00806,169.98193 C 368.38452,170.35693 357.8855,177.48096 354.88599,178.98096 C 351.88647,180.48096 354.13647,183.48047 336.13843,184.23047 C 318.14087,184.97998 312.14136,171.85693 311.76636,154.23389 C 311.55688,144.39697 315.08423,120.9751 318.24194,102.19775 C 290.37524,101.67725 262.46069,98.968262 254.39868,97.991211 C 233.38013,95.443359 217.17456,117.53662 240.52563,141.86035 z " style="fill:#bebcdb;stroke:#000000" id="path2506"/>
<path d="M 325.63989,452.69678 C 317.92163,423.11035 287.36353,445.9126 268.64673,447.82227 C 250.27417,449.69727 239.77563,436.57373 239.77563,419.32568 C 239.77563,402.07812 254.02368,395.70361 265.27222,394.5791 C 276.52075,393.4541 282.14526,396.45361 297.89331,407.32715 C 313.64136,418.20117 326.3894,399.45313 327.8894,392.7041 C 329.3894,385.95508 320.76538,342.83545 320.01538,330.46191 C 319.75855,326.2207 320.56372,321.21484 321.58911,315.6377 C 315.92114,317.13525 309.58862,318.82715 302.39282,320.71338 C 263.77222,330.83691 232.27661,318.46338 221.40259,308.71484 C 210.52905,298.96582 229.27661,284.71777 234.90112,279.09326 C 240.52563,273.46924 243.90015,262.59521 239.77563,247.22217 C 235.65112,231.84912 201.90503,222.10059 179.03296,239.34814 C 156.16089,256.59619 180.90796,285.09277 188.40698,293.3418 C 195.90601,301.59033 186.15698,312.08936 170.03442,313.58887 C 160.60962,314.46582 119.15894,313.67676 86.340576,312.87012 C 85.573975,347.74561 84.581299,386.15088 83.794922,402.07812 C 82.295166,432.44922 109.29175,422.32568 115.66577,420.82568 C 122.04028,419.32568 126.16479,409.57715 143.03735,408.45215 C 185.9231,405.59326 186.09985,466.69629 144.16235,467.69482 C 128.41431,468.06982 113.79126,451.19678 108.16675,447.44727 C 102.54272,443.69775 87.919433,442.94775 83.794922,457.9458 C 82.01709,464.41113 78.118652,481.65137 78.098144,496.18994 C 78.071045,515.38037 82.295166,531.81201 82.295166,531.81201 C 82.295166,531.81201 105.54224,526.5625 149.41187,526.5625 C 193.28149,526.5625 199.65552,547.93506 194.78101,558.80859 C 189.90649,569.68213 181.28296,568.93213 179.40796,583.18066 C 172.7063,634.11133 253.34106,631.08203 249.14917,584.68018 C 247.96948,571.62354 237.16528,571.66699 232.27661,557.68359 C 222.17944,528.80273 244.64966,523.56299 257.39819,524.68799 C 263.59351,525.23437 290.95679,529.73389 320.75757,531.21582 C 321.22437,531.23877 321.69312,531.25928 322.16089,531.28125 C 326.09985,518.06592 329.95825,503.87646 330.1394,498.44092 C 330.51392,487.19238 330.1394,469.94434 325.63989,452.69678 z " style="fill:#d3ea9d;stroke:#000000" id="path2508"/>
</g>
</svg>
<script src="../bower_components/yjs/y.js"></script>
<script src="../bower_components/d3/d3.js"></script>
<script src="./index.js"></script>
</body>
</html>

View File

@ -0,0 +1,70 @@
/* @flow */
/* global Y, d3 */
// initialize a shared object. This function call returns a promise!
Y({
db: {
name: 'memory'
},
connector: {
name: 'websockets-client',
room: 'Puzzle-example2'
// debug: true,
// url: 'http://127.0.0.1:2345'
},
sourceDir: '/bower_components',
share: {
piece1: 'Map',
piece2: 'Map',
piece3: 'Map',
piece4: 'Map'
}
}).then(function (y) {
var origin // mouse start position - translation of piece
var drag = d3.behavior.drag()
.on('dragstart', function (params) {
// get the translation of the element
var translation = d3.select(this).attr('transform').slice(10,-1).split(',').map(Number)
// mouse coordinates
var mouse = d3.mouse(this.parentNode)
origin = {
x: mouse[0] - translation[0],
y: mouse[1] - translation[1]
}
})
.on("drag", function(){
var mouse = d3.mouse(this.parentNode)
var x = mouse[0] - origin.x // =^= mouse - mouse at dragstart + translation at dragstart
var y = mouse[1] - origin.y
d3.select(this).attr("transform", "translate(" + x + "," + y + ")")
})
.on('dragend', function (piece, i) {
// save the current translation of the puzzle piece
var mouse = d3.mouse(this.parentNode)
var x = mouse[0] - origin.x
var y = mouse[1] - origin.y
piece.set('translation', {x: x, y: y})
})
var data = [y.share.piece1, y.share.piece2, y.share.piece3, y.share.piece4]
var pieces = d3.select(document.querySelector("#puzzle-example")).selectAll("path").data(data)
pieces
.classed('draggable', true)
.attr("transform", function (piece) {
var translation = piece.get('translation') || {x: 0, y: 0}
return "translate(" + translation.x + "," + translation.y + ")"
}).call(drag)
data.forEach(function(piece){
piece.observe(function () {
// whenever a property of a piece changes, update the translation of the pieces
pieces
.transition()
.attr("transform", function (piece) {
var translation = piece.get('translation') || {x: 0, y: 0}
return "translate(" + translation.x + "," + translation.y + ")"
})
})
})
})

View File

@ -0,0 +1,145 @@
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="../bower_components/quill/dist/quill.snow.css" />
<style>
#quill {
border: 1px solid gray;
box-shadow: 0px 0px 10px gray;
}
#toolbar {
border-bottom: 1px solid gray;
}
</style>
</head>
<body>
<div id="quill">
<!-- Create the toolbar container -->
<div id="toolbar" class="toolbar">
<span class="ql-format-group">
<select title="Font" class="ql-font">
<option value="sans-serif" selected="">Sans Serif</option>
<option value="serif">Serif</option>
<option value="monospace">Monospace</option>
</select>
<select title="Size" class="ql-size">
<option value="10px">Small</option>
<option value="13px" selected="">Normal</option>
<option value="18px">Large</option>
<option value="32px">Huge</option>
</select>
</span>
<span class="ql-format-group">
<span title="Bold" class="ql-format-button ql-bold"></span>
<span class="ql-format-separator"></span>
<span title="Italic" class="ql-format-button ql-italic"></span>
<span class="ql-format-separator"></span>
<span title="Underline" class="ql-format-button ql-underline"></span>
<span class="ql-format-separator"></span>
<span title="Strikethrough" class="ql-format-button ql-strike"></span>
</span>
<span class="ql-format-group">
<select title="Text Color" class="ql-color">
<option value="rgb(0, 0, 0)" label="rgb(0, 0, 0)" selected=""></option>
<option value="rgb(230, 0, 0)" label="rgb(230, 0, 0)"></option>
<option value="rgb(255, 153, 0)" label="rgb(255, 153, 0)"></option>
<option value="rgb(255, 255, 0)" label="rgb(255, 255, 0)"></option>
<option value="rgb(0, 138, 0)" label="rgb(0, 138, 0)"></option>
<option value="rgb(0, 102, 204)" label="rgb(0, 102, 204)"></option>
<option value="rgb(153, 51, 255)" label="rgb(153, 51, 255)"></option>
<option value="rgb(255, 255, 255)" label="rgb(255, 255, 255)"></option>
<option value="rgb(250, 204, 204)" label="rgb(250, 204, 204)"></option>
<option value="rgb(255, 235, 204)" label="rgb(255, 235, 204)"></option>
<option value="rgb(255, 255, 204)" label="rgb(255, 255, 204)"></option>
<option value="rgb(204, 232, 204)" label="rgb(204, 232, 204)"></option>
<option value="rgb(204, 224, 245)" label="rgb(204, 224, 245)"></option>
<option value="rgb(235, 214, 255)" label="rgb(235, 214, 255)"></option>
<option value="rgb(187, 187, 187)" label="rgb(187, 187, 187)"></option>
<option value="rgb(240, 102, 102)" label="rgb(240, 102, 102)"></option>
<option value="rgb(255, 194, 102)" label="rgb(255, 194, 102)"></option>
<option value="rgb(255, 255, 102)" label="rgb(255, 255, 102)"></option>
<option value="rgb(102, 185, 102)" label="rgb(102, 185, 102)"></option>
<option value="rgb(102, 163, 224)" label="rgb(102, 163, 224)"></option>
<option value="rgb(194, 133, 255)" label="rgb(194, 133, 255)"></option>
<option value="rgb(136, 136, 136)" label="rgb(136, 136, 136)"></option>
<option value="rgb(161, 0, 0)" label="rgb(161, 0, 0)"></option>
<option value="rgb(178, 107, 0)" label="rgb(178, 107, 0)"></option>
<option value="rgb(178, 178, 0)" label="rgb(178, 178, 0)"></option>
<option value="rgb(0, 97, 0)" label="rgb(0, 97, 0)"></option>
<option value="rgb(0, 71, 178)" label="rgb(0, 71, 178)"></option>
<option value="rgb(107, 36, 178)" label="rgb(107, 36, 178)"></option>
<option value="rgb(68, 68, 68)" label="rgb(68, 68, 68)"></option>
<option value="rgb(92, 0, 0)" label="rgb(92, 0, 0)"></option>
<option value="rgb(102, 61, 0)" label="rgb(102, 61, 0)"></option>
<option value="rgb(102, 102, 0)" label="rgb(102, 102, 0)"></option>
<option value="rgb(0, 55, 0)" label="rgb(0, 55, 0)"></option>
<option value="rgb(0, 41, 102)" label="rgb(0, 41, 102)"></option>
<option value="rgb(61, 20, 102)" label="rgb(61, 20, 102)"></option>
</select>
<span class="ql-format-separator"></span>
<select title="Background Color" class="ql-background">
<option value="rgb(0, 0, 0)" label="rgb(0, 0, 0)"></option>
<option value="rgb(230, 0, 0)" label="rgb(230, 0, 0)"></option>
<option value="rgb(255, 153, 0)" label="rgb(255, 153, 0)"></option>
<option value="rgb(255, 255, 0)" label="rgb(255, 255, 0)"></option>
<option value="rgb(0, 138, 0)" label="rgb(0, 138, 0)"></option>
<option value="rgb(0, 102, 204)" label="rgb(0, 102, 204)"></option>
<option value="rgb(153, 51, 255)" label="rgb(153, 51, 255)"></option>
<option value="rgb(255, 255, 255)" label="rgb(255, 255, 255)" selected=""></option>
<option value="rgb(250, 204, 204)" label="rgb(250, 204, 204)"></option>
<option value="rgb(255, 235, 204)" label="rgb(255, 235, 204)"></option>
<option value="rgb(255, 255, 204)" label="rgb(255, 255, 204)"></option>
<option value="rgb(204, 232, 204)" label="rgb(204, 232, 204)"></option>
<option value="rgb(204, 224, 245)" label="rgb(204, 224, 245)"></option>
<option value="rgb(235, 214, 255)" label="rgb(235, 214, 255)"></option>
<option value="rgb(187, 187, 187)" label="rgb(187, 187, 187)"></option>
<option value="rgb(240, 102, 102)" label="rgb(240, 102, 102)"></option>
<option value="rgb(255, 194, 102)" label="rgb(255, 194, 102)"></option>
<option value="rgb(255, 255, 102)" label="rgb(255, 255, 102)"></option>
<option value="rgb(102, 185, 102)" label="rgb(102, 185, 102)"></option>
<option value="rgb(102, 163, 224)" label="rgb(102, 163, 224)"></option>
<option value="rgb(194, 133, 255)" label="rgb(194, 133, 255)"></option>
<option value="rgb(136, 136, 136)" label="rgb(136, 136, 136)"></option>
<option value="rgb(161, 0, 0)" label="rgb(161, 0, 0)"></option>
<option value="rgb(178, 107, 0)" label="rgb(178, 107, 0)"></option>
<option value="rgb(178, 178, 0)" label="rgb(178, 178, 0)"></option>
<option value="rgb(0, 97, 0)" label="rgb(0, 97, 0)"></option>
<option value="rgb(0, 71, 178)" label="rgb(0, 71, 178)"></option>
<option value="rgb(107, 36, 178)" label="rgb(107, 36, 178)"></option>
<option value="rgb(68, 68, 68)" label="rgb(68, 68, 68)"></option>
<option value="rgb(92, 0, 0)" label="rgb(92, 0, 0)"></option>
<option value="rgb(102, 61, 0)" label="rgb(102, 61, 0)"></option>
<option value="rgb(102, 102, 0)" label="rgb(102, 102, 0)"></option>
<option value="rgb(0, 55, 0)" label="rgb(0, 55, 0)"></option>
<option value="rgb(0, 41, 102)" label="rgb(0, 41, 102)"></option>
<option value="rgb(61, 20, 102)" label="rgb(61, 20, 102)"></option>
</select>
</span>
<span class="ql-format-group">
<span title="List" class="ql-format-button ql-list"></span>
<span class="ql-format-separator"></span>
<span title="Bullet" class="ql-format-button ql-bullet"></span>
<span class="ql-format-separator"></span>
<select title="Text Alignment" class="ql-align">
<option value="left" label="Left" selected=""></option>
<option value="center" label="Center"></option>
<option value="right" label="Right"></option>
<option value="justify" label="Justify"></option>
</select>
</span>
<span class="ql-format-group">
<span title="Link" class="ql-format-button ql-link"></span>
</span>
</div>
<!-- Create the editor container -->
<div id="editor">
</div>
</div>
<!-- Include the Quill library -->
<script src="../bower_components/quill/dist/quill.js"></script>
<script src="../bower_components/yjs/y.es6"></script>
<script src="./index.js"></script>
</body>
</html>

View File

@ -0,0 +1,32 @@
/* global Y, Quill */
// initialize a shared object. This function call returns a promise!
Y({
db: {
name: 'memory'
},
connector: {
name: 'websockets-client',
room: 'richtext-example18',
debug: true
//url: 'http://127.0.0.1:2345'
},
sourceDir: '/bower_components',
share: {
richtext: 'Richtext' // y.share.richtext is of type Y.Richtext
}
}).then(function (y) {
window.yquill = y
// create quill element
window.quill = new Quill('#editor', {
modules: {
'toolbar': { container: '#toolbar' },
'link-tooltip': true
},
theme: 'snow'
})
// bind quill to richtext type
y.share.richtext.bind(window.quill)
})

View File

@ -0,0 +1,8 @@
<!DOCTYPE html>
<html>
<body>
<textarea style="width:80%;" rows=40 id="textfield" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"></textarea>
<script src="../bower_components/yjs/y.js"></script>
<script src="./index.js"></script>
</body>
</html>

View File

@ -0,0 +1,24 @@
/* global Y */
// initialize a shared object. This function call returns a promise!
Y({
db: {
name: 'memory'
},
connector: {
name: 'websockets-client',
room: 'Textarea-example-dev'
// debug: true
// url: 'http://127.0.0.1:2345'
},
sourceDir: '/bower_components',
share: {
textarea: 'Text' // y.share.textarea is of type Y.Text
}
}).then(function (y) {
window.y = y
// bind the textarea to a shared text element
y.share.textarea.bind(document.getElementById('textfield'))
// thats it..
})

View File

@ -0,0 +1,15 @@
{
"name": "yjs-examples",
"version": "0.0",
"homepage": "y-js.org",
"authors": [
"Kevin Jahns <kevin.jahns@rwth-aachen.de>"
],
"description": "Examples for yjs",
"license": "MIT",
"ignore": [],
"dependencies": {
"yjs": "../",
"y-webrtc": "~0.6.4"
}
}

23
Examples/bower_components/yjs/LICENSE vendored Normal file
View File

@ -0,0 +1,23 @@
The MIT License (MIT)
Copyright (c) 2014
- Kevin Jahns <kevin.jahns@rwth-aachen.de>.
- Chair of Computer Science 5 (Databases & Information Systems), RWTH Aachen University, Germany
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

147
Examples/bower_components/yjs/README.md vendored Normal file
View File

@ -0,0 +1,147 @@
# ![Yjs](http://y-js.org/images/yjs.png)
Yjs is a framework for optimistic concurrency control and automatic conflict resolution on shared data types. The framework implements a new OT-like concurrency algorithm and provides similar functionality as [ShareJs] and [OpenCoweb]. Yjs was designed to handle concurrent actions on arbitrary complex data types like Text, Json, and XML. We provide a tutorial and some applications for this framework on our [homepage](http://y-js.org/).
**NOTE** This project is currently migrating. So there may exist some information that is not true anymore..
You can create you own shared types easily. Therefore, you can take matters into your own hand by defining the meaning of the shared types and ensure that it is valid, while Yjs ensures data consistency (everyone will eventually end up with the same data). We already provide data types for
| Name | Description |
|----------|-------------------|
|[map](https://github.com/y-js/y-map) | Add, update, and remove properties of an object. Included in Yjs|
|[array](https://github.com/y-js/y-array) | A shared linked list implementation |
|[selections](https://github.com/y-js/y-selections) | Manages selections on types that use linear structures (e.g. the y-array type). Select a range of elements, and assign meaning to them.|
|[xml](https://github.com/y-js/y-xml) | An implementation of the DOM. You can create a two way binding to Browser DOM objects|
|[text](https://github.com/y-js/y-text) | Collaborate on text. Supports two way binding to textareas, input elements, or HTML elements (e.g. *h1*, or *p*)|
|[richtext](https://github.com/y-js/y-richtext) | Collaborate on rich text. Supports two way binding to several editors|
Unlike other frameworks, Yjs supports P2P message propagation and is not bound to a specific communication protocol. Therefore, Yjs is extremely scalable and can be used in a wide range of application scenarios.
We support several communication protocols as so called *Connectors*. You can create your own connector too - read [this wiki page](https://github.com/y-js/yjs/wiki/Custom-Connectors). Currently, we support the following communication protocols:
|Name | Description |
|----------------|-----------------------------------|
|[xmpp](https://github.com/y-js/y-xmpp) | Propagate updates in a XMPP multi-user-chat room ([XEP-0045](http://xmpp.org/extensions/xep-0045.html))|
|[webrtc](https://github.com/y-js/y-webrtc) | Propagate updates Browser2Browser via WebRTC|
|[test](https://github.com/y-js/y-test) | A Connector for testing purposes. It is designed to simulate delays that happen in worst case scenarios|
You can use Yjs client-, and server- side. You can get it as via npm, and bower. We even provide polymer elements for Yjs!
The advantages over similar frameworks are support for
* .. P2P message propagation and arbitrary communication protocols
* .. arbitrary complex data types
* .. offline editing: Changes are stored persistently and only relevant changes are propagated on rejoin
* .. AnyUndo: Undo *any* action that was executed in constant time (coming..)
* .. Intention Preservation: When working on Text, the intention of your changes are preserved. This is particularily important when working offline. Every type has a notion on how we define Intention Preservation on it.
## Use it!
You can find a tutorial, and examples on the [website](http://y-js.org). Furthermore, the [github wiki](https://github.com/y-js/yjs/wiki) offers more information about how you can use Yjs in your application.
Either clone this git repository, install it with [bower](http://bower.io/), or install it with [npm](https://www.npmjs.org/package/yjs).
### Bower
```
bower install y-js/yjs
```
Then you include the libraries directly from the installation folder.
```
<script src="./bower_components/yjs/y.js"></script>
```
### Npm
```
npm install yjs --save
```
And use it like this with *npm*:
```
Y = require("yjs");
```
# Y()
In order to create an instance of Y, you need to have a connection object (instance of a Connector). Then, you can create a shared data type like this:
```
var y = new Y(connector);
```
# Y.Map
Yjs includes only one type by default - the Y.Map type. It mimics the behaviour of a javascript Object. You can create, update, and remove properies on the Y.Map type. Furthermore, you can observe changes on this type as you can observe changes on Javascript Objects with [Object.observe](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/observe) - an ECMAScript 7 proposal which is likely to become accepted by the committee. Until then, we have our own implementation.
##### Reference
* Create
```
var map = y.set("new_map", Y.Map).then(function(map){
map // is my map type
});
```
* Every instance of Y is an Y.Map
```
var y = new Y(options);
```
* .get(name)
* Retrieve the value of a property. If the value is a type, `.get(name)` returns a promise
* .set(name, value)
* Set/update a property. `value` may be a primitive type, or a custom type definition (e.g. `Y.Map`)
* .delete(name)
* Delete a property
* .observe(observer)
* The `observer` is called whenever something on this object changes. Throws *add*, *update*, and *delete* events
* .observePath(path, observer)
* `path` is an array of property names. `observer` is called when the property under `path` is set, deleted, or updated
* .unobserve(f)
* Delete an observer
# A note on intention preservation
When users create/update/delete the same property concurrently, only one change will prevail. Changes on different properties do not conflict with each other.
# A note on time complexities
* .get(name)
* O(1)
* .set(name, value)
* O(1)
* .delete(name)
* O(1)
* Apply a delete operation from another user
* O(1)
* Apply an update operation from another user (set/update a property)
* Yjs does not transform against operations that do not conflict with each other.
* An operation conflicts with another operation if it changes the same property.
* Overall worst case complexety: O(|conflicts|!)
# Status
Yjs is a work in progress. Different versions of the *y-* repositories may not work together. Just drop me a line if you run into troubles.
## Get help
There are some friendly people on [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/y-js/yjs?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) who may help you with your problem, and answer your questions.
Please report _any_ issues to the [Github issue page](https://github.com/y-js/yjs/issues)! I try to fix them very soon, if possible.
## Changelog
### 1.0.0
This is a complete rewrite of the 0.5 version of Yjs. Since Yjs 1.0 it is possible to work asynchronously on a persistent database, which enables offline support.
* Switched to semver versioning
* Requires a promise implementation in environment (es6 promises suffice, included in all the major browsers). Otherwise you have to include a polyfill
* Y.Object has been renamed to Y.Map
* Y.Map exchanges `.val(name [, value])` in favor of `.set(name, value)` and `.get(name)`
* Y.Map `.get(name)` returns a promise, if the value is a custom type
* The Connector definition slightly changed (I'll update the wiki)
* The Type definitions completely changed, so you have to rewrite them (I'll rewrite the article in the wiki)
* Support for several packaging systems
* Flowtype
## Contribution
I created this framework during my bachelor thesis at the chair of computer science 5 [(i5)](http://dbis.rwth-aachen.de/cms), RWTH University. Since December 2014 I'm working on Yjs as a part of my student worker job at the i5.
## License
Yjs is licensed under the [MIT License](./LICENSE.txt).
<yjs@dbis.rwth-aachen.de>
[ShareJs]: https://github.com/share/ShareJS
[OpenCoweb]: https://github.com/opencoweb/coweb/wiki

View File

@ -0,0 +1,19 @@
{
"name": "yjs",
"version": "0.7.7",
"homepage": "y-js.org",
"authors": [
"Kevin Jahns <kevin.jahns@rwth-aachen.de>"
],
"description": "A Framework that enables Real-Time collaboration on arbitrary data structures.",
"main": "y.js",
"keywords": [
"OT",
"collaboration",
"synchronization",
"sharejs",
"coweb",
"concurrency"
],
"license": "MIT"
}

2386
Examples/bower_components/yjs/y.es6 vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

3
Examples/bower_components/yjs/y.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,6 @@
{
"name": "yjs",
"version": "0.7.7",
"version": "0.8.1",
"homepage": "y-js.org",
"authors": [
"Kevin Jahns <kevin.jahns@rwth-aachen.de>"

114
y.es6
View File

@ -1909,34 +1909,8 @@ module.exports = function (Y/* :any */) {
})
return ss
}
* getOperations (startSS) {
// TODO: use bounds here!
if (startSS == null) {
startSS = {}
}
var ops = []
var endSV = yield* this.getStateVector()
for (var endState of endSV) {
var user = endState.user
if (user === '_') {
continue
}
var startPos = startSS[user] || 0
yield* this.os.iterate(this, [user, startPos], [user, Number.MAX_VALUE], function * (op) {
ops.push(op)
})
}
var res = []
for (var op of ops) {
var o = yield* this.makeOperationReady(startSS, op)
res.push(o)
}
return res
}
/*
Here, we make op executable for the receiving user.
Here, we make all missing operations executable for the receiving user.
Notes:
startSS: denotes to the SV that the remote user sent
@ -1971,7 +1945,92 @@ module.exports = function (Y/* :any */) {
(startSS or currSS.. ?)
-> Could be necessary when I turn GC again.
-> Is a bad(ish) idea because it requires more computation
What we do:
* Iterate over all missing operations.
* When there is an operation, where the right op is known, send this op all missing ops to the left to the user
* I explained above what we have to do with each operation. Here is how we do it efficiently:
1. Go to the left until you find either op.origin, or a known operation (let o denote current operation in the iteration)
2. Found a known operation -> set op.left = o, and send it to the user. stop
3. Found o = op.origin -> set op.left = op.origin, and send it to the user. start again from 1. (set op = o)
4. Found some o -> set o.right = op, o.left = o.origin, send it to the user, continue
*/
* getOperations (startSS) {
// TODO: use bounds here!
if (startSS == null) {
startSS = {}
}
var send = []
var endSV = yield* this.getStateVector()
for (var endState of endSV) {
var user = endState.user
if (user === '_') {
continue
}
var startPos = startSS[user] || 0
yield* this.os.iterate(this, [user, startPos], [user, Number.MAX_VALUE], function * (op) {
op = Y.Struct[op.struct].encode(op)
if (op.struct !== 'Insert') {
send.push(op)
} else if (op.right == null || op.right[1] < (startSS[op.right[0]] || 0)) {
// case 1. op.right is known
var o = op
// Remember: ?
// -> set op.right
// 1. to the first operation that is known (according to startSS)
// 2. or to the first operation that has an origin that is not to the
// right of op.
// For this we maintain a list of ops which origins are not found yet.
var missing_origins = [op]
var newright = op.right
while (true) {
if (o.left == null) {
op.left = null
send.push(op)
if (!Y.utils.compareIds(o.id, op.id)) {
o = Y.Struct[op.struct].encode(o)
o.right = missing_origins[missing_origins.length - 1].id
send.push(o)
}
break
}
o = yield* this.getOperation(o.left)
// we set another o, check if we can reduce $missing_origins
while (missing_origins.length > 0 && Y.utils.compareIds(missing_origins[missing_origins.length - 1].origin, o.id)) {
missing_origins.pop()
}
if (o.id[1] < (startSS[o.id[0]] || 0)) {
// case 2. o is known
op.left = o.id
send.push(op)
break
} else if (Y.utils.compareIds(o.id, op.origin)) {
// case 3. o is op.origin
op.left = op.origin
send.push(op)
op = Y.Struct[op.struct].encode(o)
op.right = newright
if (missing_origins.length > 0) {
console.log('This should not happen .. :( please report this')
}
missing_origins = [op]
} else {
// case 4. send o, continue to find op.origin
var s = Y.Struct[op.struct].encode(o)
s.right = missing_origins[missing_origins.length - 1].id
s.left = s.origin
send.push(s)
missing_origins.push(o)
}
}
}
})
}
return send
}
/* this is what we used before.. use this as a reference..
* makeOperationReady (startSS, op) {
op = Y.Struct[op.struct].encode(op)
op = Y.utils.copyObject(op)
@ -1995,6 +2054,7 @@ module.exports = function (Y/* :any */) {
op.left = op.origin
return op
}
*/
}
Y.Transaction = TransactionInterface
}

File diff suppressed because one or more lines are too long

4
y.js

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long