mirror of
https://github.com/codex-team/editor.js
synced 2024-06-26 09:20:07 +02:00
Toolbar, Toolbox, UI (#239)
* Toolbox making * Add Toolbox buttons click handler * Toolbar, Toolbox, UI * Updates * update css prefix
This commit is contained in:
parent
c84e4e6191
commit
c1afcf0205
|
@ -27,7 +27,10 @@
|
|||
"objectsInArrays": true,
|
||||
"arraysInArrays": true
|
||||
}],
|
||||
"quotes": [2, "single", "avoid-escape"],
|
||||
"quotes": [2, "single", {
|
||||
"avoidEscape": true,
|
||||
"allowTemplateLiterals": true
|
||||
}],
|
||||
"eqeqeq": 0,
|
||||
"brace-style": [2, "1tbs"],
|
||||
"comma-spacing": [2, {
|
||||
|
@ -75,7 +78,9 @@
|
|||
"RegExp": true,
|
||||
"Module": true,
|
||||
"Node": true,
|
||||
"Element": true,
|
||||
"Proxy": true,
|
||||
"Symbol": true,
|
||||
"$": true,
|
||||
"_": true
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because one or more lines are too long
24
docs/tools.md
Normal file
24
docs/tools.md
Normal file
|
@ -0,0 +1,24 @@
|
|||
# CodeX Editor Tools
|
||||
|
||||
CodeX Editor is a block-oriented editor. It means that entry composed with the list of `Blocks` of different types: `Texts`, `Headers`, `Images`, `Quotes` etc.
|
||||
|
||||
`Tool` — is a class that provide custom `Block` type. All Tools represented by `Plugins`.
|
||||
|
||||
## Tool class structure
|
||||
|
||||
### Constructor
|
||||
|
||||
### Save
|
||||
|
||||
### Render
|
||||
|
||||
### Available settings
|
||||
|
||||
| Name | Type | Default Value | Description |
|
||||
| -- | -- | -- | -- |
|
||||
| `displayInToolbox` | _Boolean_ | `false` | Pass `true` to display this `Tool` in the Editor's `Toolbox` |
|
||||
| `iconClassName` | _String_ | — | CSS class name for the `Toolbox` icon. Used when `displayInToolbox` is `true` |
|
||||
| `irreplaceable` | _Boolean_ | `false` | By default, **empty** `Blocks` can be **replaced** by other `Blocks` with the `Toolbox`. Some tools with media-content may prefer another behaviour. Pass `true` and `Toolbox` will add a new block below yours. |
|
||||
| `contentless` | _Boolean_ | `false` | Pass `true` for Tool which represents decorative empty `Blocks` |
|
||||
|
||||
### Sanitize
|
|
@ -9,11 +9,6 @@
|
|||
font-size: 14px;
|
||||
line-height: 1.5em;
|
||||
}
|
||||
|
||||
#codex-editor {
|
||||
margin: 0 auto;
|
||||
max-width: 800px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
@ -22,8 +17,8 @@
|
|||
|
||||
</body>
|
||||
|
||||
<script src="plugins/paragraph/paragraph.js?v=100"></script>
|
||||
<link rel="stylesheet" href="plugins/paragraph/paragraph.css">
|
||||
<script src="plugins/text/text.js?v=100"></script>
|
||||
<link rel="stylesheet" href="plugins/text/text.css">
|
||||
|
||||
<!--<script src="plugins/header/header.js"></script>-->
|
||||
<!--<link rel="stylesheet" href="plugins/header/header.css">-->
|
||||
|
@ -65,9 +60,9 @@
|
|||
codex.editor = 1;
|
||||
var editor = new CodexEditor({
|
||||
holderId : 'codex-editor',
|
||||
initialBlock : 'paragraph',
|
||||
initialBlock : 'text',
|
||||
tools: {
|
||||
paragraph: Paragraph,
|
||||
text: Text,
|
||||
},
|
||||
toolsConfig: {
|
||||
quote: {
|
||||
|
@ -79,13 +74,13 @@
|
|||
data: {
|
||||
items: [
|
||||
{
|
||||
type : 'paragraph',
|
||||
type : 'text',
|
||||
data : {
|
||||
text : 'Привет от CodeX'
|
||||
}
|
||||
},
|
||||
{
|
||||
type : 'paragraph',
|
||||
type : 'text',
|
||||
data : {
|
||||
text : 'Пишите нам на team@ifmo.su'
|
||||
}
|
||||
|
@ -100,17 +95,17 @@
|
|||
// });
|
||||
// codex.editor.start({
|
||||
// holderId : "codex-editor",
|
||||
// initialBlockPlugin : 'paragraph',
|
||||
// initialBlockPlugin : 'text',
|
||||
// // placeholder: 'Прошлой ночью мне приснилось...',
|
||||
// hideToolbar: false,
|
||||
// tools : {
|
||||
// paragraph: {
|
||||
// type: 'paragraph',
|
||||
// iconClassname: 'ce-icon-paragraph',
|
||||
// render: paragraph.render,
|
||||
// validate: paragraph.validate,
|
||||
// save: paragraph.save,
|
||||
// destroy: paragraph.destroy,
|
||||
// text: {
|
||||
// type: 'text',
|
||||
// iconClassname: 'ce-icon-text',
|
||||
// render: text.render,
|
||||
// validate: text.validate,
|
||||
// save: text.save,
|
||||
// destroy: text.destroy,
|
||||
// allowedToPaste: true,
|
||||
// showInlineToolbar: true,
|
||||
// allowRenderOnPaste: true
|
||||
|
@ -272,7 +267,7 @@
|
|||
// }
|
||||
// },
|
||||
// {
|
||||
// type : 'paragraph',
|
||||
// type : 'text',
|
||||
// data : {
|
||||
// text : 'Пишите нам на team@ifmo.su'
|
||||
// }
|
||||
|
|
|
@ -2,9 +2,10 @@
|
|||
* Empty paragraph placeholder
|
||||
*/
|
||||
|
||||
.ce-paragraph {
|
||||
padding: 0.7em 0 !important;
|
||||
line-height: 1.7em;
|
||||
.ce-text {
|
||||
padding: 15px 0 !important;
|
||||
line-height: 1.6em;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.ce-paragraph:empty::before,
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* @class Paragraph
|
||||
* @class Text
|
||||
* @classdesc Paragraph plugin for CodexEditor
|
||||
*
|
||||
* @author CodeX Team (team@ifmo.su)
|
||||
|
@ -8,33 +8,44 @@
|
|||
* @version 2.0.0
|
||||
*
|
||||
*
|
||||
* @typedef {Object} ParagraphData
|
||||
* @property {String} text — HTML content to insert to paragraph element
|
||||
* @typedef {Object} TextData
|
||||
* @property {String} text — HTML content to insert to text element
|
||||
*
|
||||
*/
|
||||
|
||||
class Paragraph {
|
||||
class Text {
|
||||
|
||||
/**
|
||||
* Get the name of the plugin
|
||||
* Pass true to display this tool in the Editor's Toolbox
|
||||
*
|
||||
* @returns {string} The plugin name
|
||||
* @returns {boolean}
|
||||
*/
|
||||
static get name() {
|
||||
static get displayInToolbox() {
|
||||
|
||||
return 'paragraph';
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Class for the Toolbox icon
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
static get iconClassName() {
|
||||
|
||||
return 'cdx-text-icon';
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Render plugin`s html and set initial content
|
||||
*
|
||||
* @param {ParagraphData} data — initial plugin content
|
||||
* @param {TextData} data — initial plugin content
|
||||
*/
|
||||
constructor(data = {}) {
|
||||
|
||||
this._CSS = {
|
||||
wrapper: 'ce-paragraph'
|
||||
wrapper: 'ce-text'
|
||||
};
|
||||
|
||||
this._data = {};
|
||||
|
@ -65,7 +76,7 @@ class Paragraph {
|
|||
/**
|
||||
* Check if saved text is empty
|
||||
*
|
||||
* @param {ParagraphData} savedData — data received from plugins`s element
|
||||
* @param {TextData} savedData — data received from plugins`s element
|
||||
* @returns {boolean} false if saved text is empty, true otherwise
|
||||
*/
|
||||
validate(savedData) {
|
||||
|
@ -96,7 +107,7 @@ class Paragraph {
|
|||
*
|
||||
* @todo sanitize data while saving
|
||||
*
|
||||
* @returns {ParagraphData} Current data
|
||||
* @returns {TextData} Current data
|
||||
*/
|
||||
get data() {
|
||||
|
||||
|
@ -111,7 +122,7 @@ class Paragraph {
|
|||
/**
|
||||
* Set new data for plugin
|
||||
*
|
||||
* @param {ParagraphData} data — data to set
|
||||
* @param {TextData} data — data to set
|
||||
*/
|
||||
set data(data) {
|
||||
|
195
package-lock.json
generated
195
package-lock.json
generated
|
@ -51,9 +51,9 @@
|
|||
}
|
||||
},
|
||||
"ajv": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.0.tgz",
|
||||
"integrity": "sha1-6yhAdG6dxIvV4GOjbj/UAMXqtak=",
|
||||
"version": "5.5.2",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz",
|
||||
"integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"co": "4.6.0",
|
||||
|
@ -1225,12 +1225,6 @@
|
|||
"integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=",
|
||||
"dev": true
|
||||
},
|
||||
"complex.js": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/complex.js/-/complex.js-2.0.4.tgz",
|
||||
"integrity": "sha512-Syl95HpxUTS0QjwNxencZsKukgh1zdS9uXeXX2Us0pHaqBR6kiZZi0AkZ9VpZFwHJyVIUVzI4EumjWdXP3fy6w==",
|
||||
"dev": true
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
|
@ -1543,12 +1537,6 @@
|
|||
"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
|
||||
"dev": true
|
||||
},
|
||||
"decimal.js": {
|
||||
"version": "7.2.3",
|
||||
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-7.2.3.tgz",
|
||||
"integrity": "sha512-AoFI37QS0S87Ft0r3Bdz4q9xSpm1Paa9lSeKLXgMPk/u/+QPIM5Gy4DHcZQS1seqPJH4gHLauPGn347z0HbsrA==",
|
||||
"dev": true
|
||||
},
|
||||
"deep-is": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
|
||||
|
@ -1669,12 +1657,12 @@
|
|||
}
|
||||
},
|
||||
"errno": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/errno/-/errno-0.1.4.tgz",
|
||||
"integrity": "sha1-uJbiOp5ei6M4cfyZar02NfyaHH0=",
|
||||
"version": "0.1.6",
|
||||
"resolved": "https://registry.npmjs.org/errno/-/errno-0.1.6.tgz",
|
||||
"integrity": "sha512-IsORQDpaaSwcDP4ZZnHxgE85werpo34VYn1Ud3mq+eUsF593faR8oCZNXrROVkpFu2TsbrNhHin0aUrTsQ9vNw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"prr": "0.0.0"
|
||||
"prr": "1.0.1"
|
||||
}
|
||||
},
|
||||
"error-ex": {
|
||||
|
@ -1783,12 +1771,12 @@
|
|||
}
|
||||
},
|
||||
"eslint": {
|
||||
"version": "4.12.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-4.12.1.tgz",
|
||||
"integrity": "sha512-28hOYej+NZ/R5H1yMvyKa1+bPlu+fnsIAQffK6hxXgvmXnImos2bA5XfCn5dYv2k2mrKj+/U/Z4L5ICWxC7TQw==",
|
||||
"version": "4.13.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-4.13.1.tgz",
|
||||
"integrity": "sha512-UCJVV50RtLHYzBp1DZ8CMPtRSg4iVZvjgO9IJHIKyWU/AnJVjtdRikoUPLB29n5pzMB7TnsLQWf0V6VUJfoPfw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ajv": "5.5.0",
|
||||
"ajv": "5.5.2",
|
||||
"babel-code-frame": "6.26.0",
|
||||
"chalk": "2.3.0",
|
||||
"concat-stream": "1.6.0",
|
||||
|
@ -1803,11 +1791,11 @@
|
|||
"file-entry-cache": "2.0.0",
|
||||
"functional-red-black-tree": "1.0.1",
|
||||
"glob": "7.1.2",
|
||||
"globals": "11.0.1",
|
||||
"globals": "11.1.0",
|
||||
"ignore": "3.3.7",
|
||||
"imurmurhash": "0.1.4",
|
||||
"inquirer": "3.3.0",
|
||||
"is-resolvable": "1.0.0",
|
||||
"is-resolvable": "1.0.1",
|
||||
"js-yaml": "3.10.0",
|
||||
"json-stable-stringify-without-jsonify": "1.0.1",
|
||||
"levn": "0.3.0",
|
||||
|
@ -1869,9 +1857,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"globals": {
|
||||
"version": "11.0.1",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-11.0.1.tgz",
|
||||
"integrity": "sha1-Eqh7sBDlFUOWrMU14eQ/x1Ow5eg=",
|
||||
"version": "11.1.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-11.1.0.tgz",
|
||||
"integrity": "sha512-uEuWt9mqTlPDwSqi+sHjD4nWU/1N+q0fiWI9T1mZpD2UENqX20CFD5T/ziLZvztPaBKl7ZylUi1q6Qfm7E2CiQ==",
|
||||
"dev": true
|
||||
},
|
||||
"has-flag": {
|
||||
|
@ -2200,12 +2188,6 @@
|
|||
"for-in": "1.0.2"
|
||||
}
|
||||
},
|
||||
"fraction.js": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.0.2.tgz",
|
||||
"integrity": "sha512-OswcigOSil3vYXgrPSx4NCaSyPikXqVNYN/4CyhS0ucVOJ4GVYr6KQQLLcAudvS/4bBOzxqJ3XIsFaaMjl98ZQ==",
|
||||
"dev": true
|
||||
},
|
||||
"fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
|
@ -3679,13 +3661,10 @@
|
|||
"dev": true
|
||||
},
|
||||
"is-resolvable": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.0.0.tgz",
|
||||
"integrity": "sha1-jfV8YeouPFAUCNEA+wE8+NbgzGI=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"tryit": "1.0.3"
|
||||
}
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.0.1.tgz",
|
||||
"integrity": "sha512-y5CXYbzvB3jTnWAZH1Nl7ykUWb6T3BcTs56HUruwBf8MhF56n1HWqhDWnVFo8GHrUPDgvUUNVhrc2U8W7iqz5g==",
|
||||
"dev": true
|
||||
},
|
||||
"is-stream": {
|
||||
"version": "1.1.0",
|
||||
|
@ -3729,12 +3708,6 @@
|
|||
"isarray": "1.0.0"
|
||||
}
|
||||
},
|
||||
"javascript-natural-sort": {
|
||||
"version": "0.7.1",
|
||||
"resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz",
|
||||
"integrity": "sha1-+eIwPUUH9tdDVac2ZNFED7Wg71k=",
|
||||
"dev": true
|
||||
},
|
||||
"js-base64": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.4.0.tgz",
|
||||
|
@ -3981,21 +3954,6 @@
|
|||
"integrity": "sha1-3oGf282E3M2PrlnGrreWFbnSZqw=",
|
||||
"dev": true
|
||||
},
|
||||
"mathjs": {
|
||||
"version": "3.17.0",
|
||||
"resolved": "https://registry.npmjs.org/mathjs/-/mathjs-3.17.0.tgz",
|
||||
"integrity": "sha512-bFDSjjLV3+csekog1L6z3FjZ0uSkRPFcMlbLef8KXxq68jtQQ48W2f+JKJugM9y6KxJEtt1zWFIGQnYKWR0nxg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"complex.js": "2.0.4",
|
||||
"decimal.js": "7.2.3",
|
||||
"fraction.js": "4.0.2",
|
||||
"javascript-natural-sort": "0.7.1",
|
||||
"seed-random": "2.2.0",
|
||||
"tiny-emitter": "2.0.0",
|
||||
"typed-function": "0.10.6"
|
||||
}
|
||||
},
|
||||
"md5.js": {
|
||||
"version": "1.3.4",
|
||||
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.4.tgz",
|
||||
|
@ -4033,7 +3991,7 @@
|
|||
"integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"errno": "0.1.4",
|
||||
"errno": "0.1.6",
|
||||
"readable-stream": "2.3.3"
|
||||
}
|
||||
},
|
||||
|
@ -6959,14 +6917,67 @@
|
|||
"dev": true
|
||||
},
|
||||
"postcss-sass": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-sass/-/postcss-sass-0.1.0.tgz",
|
||||
"integrity": "sha1-DSplW10kHsj0Gbs9o43lyhF0bds=",
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-sass/-/postcss-sass-0.2.0.tgz",
|
||||
"integrity": "sha512-cUmYzkP747fPCQE6d+CH2l1L4VSyIlAzZsok3HPjb5Gzsq3jE+VjpAdGlPsnQ310WKWI42sw+ar0UNN59/f3hg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"gonzales-pe": "4.2.3",
|
||||
"mathjs": "3.17.0",
|
||||
"postcss": "5.2.18"
|
||||
"postcss": "6.0.14"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-styles": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz",
|
||||
"integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-convert": "1.9.1"
|
||||
}
|
||||
},
|
||||
"chalk": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz",
|
||||
"integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-styles": "3.2.0",
|
||||
"escape-string-regexp": "1.0.5",
|
||||
"supports-color": "4.5.0"
|
||||
}
|
||||
},
|
||||
"has-flag": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz",
|
||||
"integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=",
|
||||
"dev": true
|
||||
},
|
||||
"postcss": {
|
||||
"version": "6.0.14",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.14.tgz",
|
||||
"integrity": "sha512-NJ1z0f+1offCgadPhz+DvGm5Mkci+mmV5BqD13S992o0Xk9eElxUfPPF+t2ksH5R/17gz4xVK8KWocUQ5o3Rog==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"chalk": "2.3.0",
|
||||
"source-map": "0.6.1",
|
||||
"supports-color": "4.5.0"
|
||||
}
|
||||
},
|
||||
"source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"dev": true
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz",
|
||||
"integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"has-flag": "2.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"postcss-scss": {
|
||||
|
@ -7184,16 +7195,16 @@
|
|||
}
|
||||
},
|
||||
"postcss-smart-import": {
|
||||
"version": "0.7.5",
|
||||
"resolved": "https://registry.npmjs.org/postcss-smart-import/-/postcss-smart-import-0.7.5.tgz",
|
||||
"integrity": "sha512-Bs9wAFxH5irGpenBg9a65jTcydZLh7VBTX6NYwMXvVXO6y9CQ83kRmfpQDs5lHhl6IODeIeQfJep5HBMd9UVCQ==",
|
||||
"version": "0.7.6",
|
||||
"resolved": "https://registry.npmjs.org/postcss-smart-import/-/postcss-smart-import-0.7.6.tgz",
|
||||
"integrity": "sha512-9OpXaQ1uMMHWafUh0RWIpAKa3xxUDC2yyxicUPpGffH33nzbZG4/z+nk5Ocw5gGZ+3qkXV91iDV23Cmxf2Jhew==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"babel-runtime": "6.26.0",
|
||||
"lodash": "4.17.4",
|
||||
"object-assign": "4.1.1",
|
||||
"postcss": "6.0.14",
|
||||
"postcss-sass": "0.1.0",
|
||||
"postcss-sass": "0.2.0",
|
||||
"postcss-scss": "1.0.2",
|
||||
"postcss-value-parser": "3.3.0",
|
||||
"promise-each": "2.2.0",
|
||||
|
@ -7348,9 +7359,9 @@
|
|||
}
|
||||
},
|
||||
"prr": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://registry.npmjs.org/prr/-/prr-0.0.0.tgz",
|
||||
"integrity": "sha1-GoS4WQgyVQFBGFPQCB7j+obikmo=",
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
|
||||
"integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=",
|
||||
"dev": true
|
||||
},
|
||||
"pseudomap": {
|
||||
|
@ -7823,12 +7834,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"seed-random": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/seed-random/-/seed-random-2.2.0.tgz",
|
||||
"integrity": "sha1-KpsZ4lCoFwmSMaW5mk2vgLf77VQ=",
|
||||
"dev": true
|
||||
},
|
||||
"semver": {
|
||||
"version": "5.4.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz",
|
||||
|
@ -8147,7 +8152,7 @@
|
|||
"integrity": "sha512-UUkEAPdSGxtRpiV9ozJ5cMTtYiqz7Ni1OGqLXRCynrvzdtR1p+cfOWe2RJLwvUG8hNanaSRjecIqwOjqeatDsA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ajv": "5.5.0",
|
||||
"ajv": "5.5.2",
|
||||
"ajv-keywords": "2.1.1",
|
||||
"chalk": "2.3.0",
|
||||
"lodash": "4.17.4",
|
||||
|
@ -8219,12 +8224,6 @@
|
|||
"setimmediate": "1.0.5"
|
||||
}
|
||||
},
|
||||
"tiny-emitter": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.0.0.tgz",
|
||||
"integrity": "sha1-utMnrbGAS0KiMa+nQVMr2ITNCa0=",
|
||||
"dev": true
|
||||
},
|
||||
"tmp": {
|
||||
"version": "0.0.33",
|
||||
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
|
||||
|
@ -8252,12 +8251,6 @@
|
|||
"integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=",
|
||||
"dev": true
|
||||
},
|
||||
"tryit": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tryit/-/tryit-1.0.3.tgz",
|
||||
"integrity": "sha1-OTvnMKlEb9Hq1tpZoBQwjzbCics=",
|
||||
"dev": true
|
||||
},
|
||||
"tty-browserify": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",
|
||||
|
@ -8273,12 +8266,6 @@
|
|||
"prelude-ls": "1.1.2"
|
||||
}
|
||||
},
|
||||
"typed-function": {
|
||||
"version": "0.10.6",
|
||||
"resolved": "https://registry.npmjs.org/typed-function/-/typed-function-0.10.6.tgz",
|
||||
"integrity": "sha512-PYtsDjxyW3vq7Itn2RMz0cn6CrbybIY6XC2i9c1q1o/H94QW8B1Pf3wSsbBDOCMpN1i5jDRrlDsLXFaqXBpfHQ==",
|
||||
"dev": true
|
||||
},
|
||||
"typedarray": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
|
||||
|
@ -8443,14 +8430,14 @@
|
|||
}
|
||||
},
|
||||
"webpack": {
|
||||
"version": "3.9.0",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-3.9.0.tgz",
|
||||
"integrity": "sha512-SlBO3yUIhSohW7uCA5c0v03V32DsaXU3vDyUtHB8rubgTgfwl1nv+I+BQIScuQ6exu74wWT6brF/GDXxGLStuA==",
|
||||
"version": "3.10.0",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-3.10.0.tgz",
|
||||
"integrity": "sha512-fxxKXoicjdXNUMY7LIdY89tkJJJ0m1Oo8PQutZ5rLgWbV5QVKI15Cn7+/IHnRTd3vfKfiwBx6SBqlorAuNA8LA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"acorn": "5.2.1",
|
||||
"acorn-dynamic-import": "2.0.2",
|
||||
"ajv": "5.5.0",
|
||||
"ajv": "5.5.2",
|
||||
"ajv-keywords": "2.1.1",
|
||||
"async": "2.6.0",
|
||||
"enhanced-resolve": "3.4.1",
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
"babel-preset-es2015": "^6.24.1",
|
||||
"babel-runtime": "^6.26.0",
|
||||
"css-loader": "^0.28.7",
|
||||
"eslint": "^4.11.0",
|
||||
"eslint": "^4.13.1",
|
||||
"eslint-loader": "^1.9.0",
|
||||
"extract-text-webpack-plugin": "^3.0.2",
|
||||
"html-janitor": "^2.0.2",
|
||||
|
@ -36,8 +36,8 @@
|
|||
"postcss-nested": "^2.1.2",
|
||||
"postcss-nested-ancestors": "^1.0.0",
|
||||
"postcss-nesting": "^4.2.1",
|
||||
"postcss-smart-import": "^0.7.5",
|
||||
"webpack": "^3.8.1"
|
||||
"postcss-smart-import": "^0.7.6",
|
||||
"webpack": "^3.10.0"
|
||||
},
|
||||
"dependencies": {}
|
||||
}
|
||||
|
|
63
src/codex.js
63
src/codex.js
|
@ -45,15 +45,30 @@
|
|||
|
||||
/**
|
||||
* @typedef {Object} EditorConfig
|
||||
* @property {String} holderId - Element to append Editor
|
||||
* @property {String} initialBlock - Tool name which will be initial
|
||||
* @property {Object} tools - list of tools. The object value must be function (constructor) so that CodexEditor could make an instance
|
||||
* @property {@link Tools#ToolsConfig} toolsConfig - tools configuration
|
||||
* @property {Array} data - Blocks list in JSON-format
|
||||
* @property {String} holderId - Element to append Editor
|
||||
* @property {Array} data - Blocks list in JSON-format
|
||||
* @property {Object} tools - Map for used Tools in format { name : Class, ... }
|
||||
* @property {String} initialBlock - This Tool will be added by default
|
||||
* @property {String} placeholder - First Block placeholder
|
||||
* @property {Object} sanitizer - @todo fill desc
|
||||
* @property {Boolean} hideToolbar - @todo fill desc
|
||||
* @property {Object} toolsConfig - tools configuration {@link Tools#ToolsConfig}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Dynamically imported utils
|
||||
*
|
||||
* @typedef {Dom} $ - {@link components/dom.js}
|
||||
* @typedef {Util} _ - {@link components/utils.js}
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Apply polyfills
|
||||
*/
|
||||
import 'components/polyfills';
|
||||
|
||||
/**
|
||||
* Require Editor modules places in components/modules dir
|
||||
*/
|
||||
|
@ -87,11 +102,19 @@ module.exports = class CodexEditor {
|
|||
|
||||
/**
|
||||
* Configuration object
|
||||
* @type {EditorConfig}
|
||||
*/
|
||||
this.config = {};
|
||||
|
||||
/**
|
||||
* Editor Components
|
||||
* @typedef {Object} EditorComponents
|
||||
* @property {BlockManager} BlockManager
|
||||
* @property {Tools} Tools
|
||||
* @property {Events} Events
|
||||
* @property {UI} UI
|
||||
* @property {Toolbar} Toolbar
|
||||
* @property {Toolbox} Toolbox
|
||||
* @property {Renderer} Renderer
|
||||
*/
|
||||
this.moduleInstances = {};
|
||||
|
||||
|
@ -110,7 +133,7 @@ module.exports = class CodexEditor {
|
|||
})
|
||||
.catch(error => {
|
||||
|
||||
console.log('CodeX Editor does not ready beecause of %o', error);
|
||||
console.log('CodeX Editor does not ready because of %o', error);
|
||||
|
||||
});
|
||||
|
||||
|
@ -118,9 +141,9 @@ module.exports = class CodexEditor {
|
|||
|
||||
/**
|
||||
* Setting for configuration
|
||||
* @param {Object} config
|
||||
* @param {EditorConfig} config
|
||||
*/
|
||||
set configuration(config = {}) {
|
||||
set configuration(config) {
|
||||
|
||||
this.config.holderId = config.holderId;
|
||||
this.config.placeholder = config.placeholder || 'write your story...';
|
||||
|
@ -135,11 +158,24 @@ module.exports = class CodexEditor {
|
|||
this.config.toolsConfig = config.toolsConfig || {};
|
||||
this.config.data = config.data || [];
|
||||
|
||||
/**
|
||||
* If initial Block's Tool was not passed, use the first Tool in config.tools
|
||||
*/
|
||||
if (!config.initialBlock) {
|
||||
|
||||
for (this.config.initialBlock in this.config.tools) break;
|
||||
|
||||
} else {
|
||||
|
||||
this.config.initialBlock = config.initialBlock;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns private property
|
||||
* @returns {{}|*}
|
||||
* @returns {EditorConfig}
|
||||
*/
|
||||
get configuration() {
|
||||
|
||||
|
@ -182,7 +218,6 @@ module.exports = class CodexEditor {
|
|||
* To prevent this, we use 'babel-plugin-class-display-name' plugin
|
||||
* @see https://www.npmjs.com/package/babel-plugin-class-display-name
|
||||
*/
|
||||
|
||||
this.moduleInstances[Module.displayName] = new Module({
|
||||
config : this.configuration
|
||||
});
|
||||
|
@ -250,8 +285,8 @@ module.exports = class CodexEditor {
|
|||
let prepareDecorator = module => module.prepare();
|
||||
|
||||
return Promise.resolve()
|
||||
.then(prepareDecorator(this.moduleInstances.UI))
|
||||
.then(prepareDecorator(this.moduleInstances.Tools))
|
||||
.then(prepareDecorator(this.moduleInstances.UI))
|
||||
.then(() => {
|
||||
|
||||
if (this.config.data && this.config.data.items) {
|
||||
|
@ -307,11 +342,11 @@ module.exports = class CodexEditor {
|
|||
// * holds initial settings
|
||||
// */
|
||||
// editor.settings = {
|
||||
// tools : ['paragraph', 'header', 'picture', 'list', 'quote', 'code', 'twitter', 'instagram', 'smile'],
|
||||
// tools : ['text', 'header', 'picture', 'list', 'quote', 'code', 'twitter', 'instagram', 'smile'],
|
||||
// holderId : 'codex-editor',
|
||||
//
|
||||
// // Type of block showing on empty editor
|
||||
// initialBlockPlugin: 'paragraph'
|
||||
// initialBlockPlugin: 'text'
|
||||
// };
|
||||
//
|
||||
// /**
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* @abstract
|
||||
* @class Module
|
||||
* @classdesc All modules inherites from this class.
|
||||
* @classdesc All modules inherits from this class.
|
||||
*
|
||||
* @typedef {Module} Module
|
||||
* @property {Object} config - Editor user settings
|
||||
|
@ -22,7 +22,14 @@ export default class Module {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {EditorConfig}
|
||||
*/
|
||||
this.config = config;
|
||||
|
||||
/**
|
||||
* @type {EditorComponents}
|
||||
*/
|
||||
this.Editor = null;
|
||||
|
||||
}
|
||||
|
|
|
@ -20,15 +20,24 @@ export default class Block {
|
|||
|
||||
this.tool = tool;
|
||||
|
||||
this.CSS = {
|
||||
wrapper: 'ce-block',
|
||||
content: 'ce-block__content'
|
||||
};
|
||||
|
||||
this._html = this.compose();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* CSS classes for the Block
|
||||
* @return {{wrapper: string, content: string}}
|
||||
*/
|
||||
static get CSS() {
|
||||
|
||||
return {
|
||||
wrapper: 'ce-block',
|
||||
content: 'ce-block__content',
|
||||
selected: 'ce-block--selected'
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Make default block wrappers and put tool`s content there
|
||||
*
|
||||
|
@ -37,8 +46,8 @@ export default class Block {
|
|||
*/
|
||||
compose() {
|
||||
|
||||
let wrapper = $.make('div', this.CSS.wrapper),
|
||||
content = $.make('div', this.CSS.content);
|
||||
let wrapper = $.make('div', Block.CSS.wrapper),
|
||||
content = $.make('div', Block.CSS.content);
|
||||
|
||||
content.appendChild(this.tool.html);
|
||||
wrapper.appendChild(content);
|
||||
|
@ -58,4 +67,74 @@ export default class Block {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* Check block for emptiness
|
||||
*
|
||||
* @return {Boolean}
|
||||
*/
|
||||
get isEmpty() {
|
||||
|
||||
/**
|
||||
* Allow Tool to represent decorative contentless blocks: for example "* * *"-tool
|
||||
* That Tools are not empty
|
||||
*/
|
||||
if (this.tool.contentless) {
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
let emptyText = this._html.textContent.trim().length === 0,
|
||||
emptyMedia = !this.hasMedia;
|
||||
|
||||
return emptyText && emptyMedia;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if block has a media content such as images, iframes and other
|
||||
* @return {Boolean}
|
||||
*/
|
||||
get hasMedia() {
|
||||
|
||||
/**
|
||||
* This tags represents media-content
|
||||
* @type {string[]}
|
||||
*/
|
||||
const mediaTags = [
|
||||
'img',
|
||||
'iframe',
|
||||
'video',
|
||||
'audio',
|
||||
'source',
|
||||
'input',
|
||||
'textarea',
|
||||
'twitterwidget'
|
||||
];
|
||||
|
||||
return !!this._html.querySelector(mediaTags.join(','));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Set selected state
|
||||
* @param {Boolean} state - 'true' to select, 'false' to remove selection
|
||||
*/
|
||||
set selected(state) {
|
||||
|
||||
/**
|
||||
* We don't need to mark Block as Selected when it is not empty
|
||||
*/
|
||||
if (state === true && !this.isEmpty) {
|
||||
|
||||
this._html.classList.add(Block.CSS.selected);
|
||||
|
||||
} else {
|
||||
|
||||
this._html.classList.remove(Block.CSS.selected);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -99,42 +99,6 @@ export default class Core {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* Core custom logger
|
||||
*
|
||||
* @param msg
|
||||
* @param type
|
||||
* @param args
|
||||
*/
|
||||
log(msg, type, args) {
|
||||
|
||||
type = type || 'log';
|
||||
|
||||
if (!args) {
|
||||
|
||||
args = msg || 'undefined';
|
||||
msg = '[codex-editor]: %o';
|
||||
|
||||
} else {
|
||||
|
||||
msg = '[codex-editor]: ' + msg;
|
||||
|
||||
}
|
||||
|
||||
try{
|
||||
|
||||
if ( 'console' in window && window.console[ type ] ) {
|
||||
|
||||
if ( args ) window.console[ type ]( msg, args );
|
||||
else window.console[ type ]( msg );
|
||||
|
||||
}
|
||||
|
||||
} catch(e) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Native Ajax
|
||||
|
|
|
@ -92,7 +92,7 @@ export default class Dom {
|
|||
* @param {Object} node
|
||||
* @returns {boolean}
|
||||
*/
|
||||
static isNode(node) {
|
||||
static isElement(node) {
|
||||
|
||||
return node && typeof node === 'object' && node.nodeType && node.nodeType === Node.ELEMENT_NODE;
|
||||
|
||||
|
|
|
@ -118,7 +118,7 @@ module.exports = class Content {
|
|||
*/
|
||||
getFirstLevelBlock(node) {
|
||||
|
||||
if (!$.isNode(node)) {
|
||||
if (!$.isElement(node)) {
|
||||
|
||||
node = node.parentNode;
|
||||
|
||||
|
|
|
@ -7,15 +7,20 @@
|
|||
|
||||
import Block from '../block';
|
||||
|
||||
/**
|
||||
* @typedef {BlockManager} BlockManager
|
||||
* @property {Number} currentBlockIndex - Index of current working block
|
||||
* @property {Proxy} _blocks - Proxy for Blocks instance {@link Blocks}
|
||||
*/
|
||||
export default class BlockManager extends Module {
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @param {EditorConfig} config
|
||||
*/
|
||||
constructor(config) {
|
||||
constructor({config}) {
|
||||
|
||||
super(config);
|
||||
super({config});
|
||||
|
||||
/**
|
||||
* Proxy for Blocks instance {@link Blocks}
|
||||
|
@ -78,13 +83,17 @@ export default class BlockManager extends Module {
|
|||
* @param {String} toolName — plugin name
|
||||
* @param {Object} data — plugin data
|
||||
*/
|
||||
insert(toolName, data) {
|
||||
insert(toolName, data = {}) {
|
||||
|
||||
let toolInstance = this.Editor.Tools.construct(toolName, data),
|
||||
block = new Block(toolInstance);
|
||||
|
||||
this._blocks[++this.currentBlockIndex] = block;
|
||||
|
||||
/**
|
||||
* @todo fire Tool's appendCallback
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -93,13 +102,17 @@ export default class BlockManager extends Module {
|
|||
* @param {String} toolName — plugin name
|
||||
* @param {Object} data — plugin data
|
||||
*/
|
||||
replace(toolName, data) {
|
||||
replace(toolName, data = {}) {
|
||||
|
||||
let toolInstance = this.Editor.Tools.construct(toolName, data),
|
||||
block = new Block(toolInstance);
|
||||
|
||||
this._blocks.insert(this.currentBlockIndex, block, true);
|
||||
|
||||
/**
|
||||
* @todo fire Tool's appendCallback
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -156,8 +169,23 @@ export default class BlockManager extends Module {
|
|||
|
||||
let nodes = this._blocks.nodes;
|
||||
|
||||
/**
|
||||
* Update current Block's index
|
||||
* @type {number}
|
||||
*/
|
||||
this.currentBlockIndex = nodes.indexOf(element);
|
||||
|
||||
/**
|
||||
* Remove previous selected Block's state
|
||||
*/
|
||||
this._blocks.array.forEach( block => block.selected = false);
|
||||
|
||||
/**
|
||||
* Mark current Block as selected
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.currentBlock.selected = true;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -171,6 +199,38 @@ export default class BlockManager extends Module {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* 1) Find first-level Block from passed child Node
|
||||
* 2) Mark it as current
|
||||
*
|
||||
* @param {Element|Text} childNode - look ahead from this node.
|
||||
* @throws Error - when passed Node is not included at the Block
|
||||
*/
|
||||
setCurrentBlockByChildNode(childNode) {
|
||||
|
||||
/**
|
||||
* If node is Text TextNode
|
||||
*/
|
||||
if (!$.isElement(childNode)) {
|
||||
|
||||
childNode = childNode.parentNode;
|
||||
|
||||
}
|
||||
|
||||
let parentFirstLevelBlock = childNode.closest(`.${Block.CSS.wrapper}`);
|
||||
|
||||
if (parentFirstLevelBlock) {
|
||||
|
||||
this.currentNode = parentFirstLevelBlock;
|
||||
|
||||
} else {
|
||||
|
||||
throw new Error('Can not find a Block from this child Node');
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
109
src/components/modules/caret.js
Normal file
109
src/components/modules/caret.js
Normal file
|
@ -0,0 +1,109 @@
|
|||
/**
|
||||
* @class Caret
|
||||
* @classdesc Contains methods for working Caret
|
||||
*
|
||||
* @typedef {Caret} Caret
|
||||
*/
|
||||
export default class Caret extends Module {
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*/
|
||||
constructor({config}) {
|
||||
|
||||
super({config});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Caret to the last Block
|
||||
*
|
||||
* If last block is not empty, append another empty block
|
||||
*/
|
||||
setToTheLastBlock() {
|
||||
|
||||
let blocks = this.Editor.BlockManager.blocks,
|
||||
lastBlock;
|
||||
|
||||
if (blocks.length) {
|
||||
|
||||
lastBlock = blocks[blocks.length - 1];
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* If last block is empty and it is an initialBlock, set to that.
|
||||
* Otherwise, append new empty block and set to that
|
||||
*/
|
||||
if (lastBlock.isEmpty) {
|
||||
|
||||
this.set(lastBlock.html);
|
||||
|
||||
} else {
|
||||
|
||||
this.Editor.BlockManager.insert(this.config.initialBlock);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
// * If inputs in redactor does not exits, then we put input index 0 not -1
|
||||
// */
|
||||
// var indexOfLastInput = editor.state.inputs.length > 0 ? editor.state.inputs.length - 1 : 0;
|
||||
//
|
||||
// /** If we have any inputs */
|
||||
// if (editor.state.inputs.length) {
|
||||
//
|
||||
// /** getting firstlevel parent of input */
|
||||
// firstLevelBlock = editor.content.getFirstLevelBlock(editor.state.inputs[indexOfLastInput]);
|
||||
//
|
||||
// }
|
||||
//
|
||||
// /** If input is empty, then we set caret to the last input */
|
||||
// if (editor.state.inputs.length && editor.state.inputs[indexOfLastInput].textContent === '' && firstLevelBlock.dataset.tool == editor.settings.initialBlockPlugin) {
|
||||
//
|
||||
// editor.caret.setToBlock(indexOfLastInput);
|
||||
//
|
||||
// } else {
|
||||
//
|
||||
// /** Create new input when caret clicked in redactors area */
|
||||
// var NEW_BLOCK_TYPE = editor.settings.initialBlockPlugin;
|
||||
//
|
||||
// editor.content.insertBlock({
|
||||
// type : NEW_BLOCK_TYPE,
|
||||
// block : editor.tools[NEW_BLOCK_TYPE].render()
|
||||
// });
|
||||
//
|
||||
// /** If there is no inputs except inserted */
|
||||
// if (editor.state.inputs.length === 1) {
|
||||
//
|
||||
// editor.caret.setToBlock(indexOfLastInput);
|
||||
//
|
||||
// } else {
|
||||
//
|
||||
// /** Set caret to this appended input */
|
||||
// editor.caret.setToNextBlock(indexOfLastInput);
|
||||
//
|
||||
// }
|
||||
//
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Set caret to the passed Node
|
||||
* @param {Element} node - content-editable Element
|
||||
*/
|
||||
set(node) {
|
||||
|
||||
/**
|
||||
* @todo add working with Selection
|
||||
* tmp: work with textContent
|
||||
*/
|
||||
|
||||
node.textContent += '|';
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -15,9 +15,9 @@ export default class Events extends Module {
|
|||
/**
|
||||
* @constructor
|
||||
*/
|
||||
constructor(config) {
|
||||
constructor({config}) {
|
||||
|
||||
super(config);
|
||||
super({config});
|
||||
this.subscribers = {};
|
||||
|
||||
}
|
||||
|
|
|
@ -12,9 +12,9 @@ export default class Renderer extends Module {
|
|||
* @constructor
|
||||
* @param {EditorConfig} config
|
||||
*/
|
||||
constructor(config) {
|
||||
constructor({config}) {
|
||||
|
||||
super(config);
|
||||
super({config});
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -43,9 +43,9 @@ export default class Sanitizer extends Module {
|
|||
*
|
||||
* @param {SanitizerConfig} config
|
||||
*/
|
||||
constructor(config) {
|
||||
constructor({config}) {
|
||||
|
||||
super(config);
|
||||
super({config});
|
||||
|
||||
// default config
|
||||
this.defaultConfig = null;
|
||||
|
|
|
@ -54,9 +54,9 @@ export default class Toolbar extends Module {
|
|||
/**
|
||||
* @constructor
|
||||
*/
|
||||
constructor(config) {
|
||||
constructor({config}) {
|
||||
|
||||
super(config);
|
||||
super({config});
|
||||
|
||||
this.nodes = {
|
||||
wrapper : null,
|
||||
|
@ -65,7 +65,6 @@ export default class Toolbar extends Module {
|
|||
|
||||
// Content Zone
|
||||
plusButton : null,
|
||||
toolbox : null,
|
||||
|
||||
// Actions Zone
|
||||
settingsToggler : null,
|
||||
|
@ -77,14 +76,25 @@ export default class Toolbar extends Module {
|
|||
defaultSettings: null,
|
||||
};
|
||||
|
||||
this.CSS = {
|
||||
}
|
||||
|
||||
/**
|
||||
* CSS styles
|
||||
* @return {Object}
|
||||
* @constructor
|
||||
*/
|
||||
static get CSS() {
|
||||
|
||||
return {
|
||||
toolbar: 'ce-toolbar',
|
||||
content: 'ce-toolbar__content',
|
||||
actions: 'ce-toolbar__actions',
|
||||
|
||||
toolbarOpened: 'ce-toolbar--opened',
|
||||
|
||||
// Content Zone
|
||||
toolbox: 'ce-toolbar__toolbox',
|
||||
plusButton: 'ce-toolbar__plus',
|
||||
plusButtonHidden: 'ce-toolbar__plus--hidden',
|
||||
|
||||
// Actions Zone
|
||||
settingsToggler: 'ce-toolbar__settings-btn',
|
||||
|
@ -103,14 +113,14 @@ export default class Toolbar extends Module {
|
|||
*/
|
||||
make() {
|
||||
|
||||
this.nodes.wrapper = $.make('div', this.CSS.toolbar);
|
||||
this.nodes.wrapper = $.make('div', Toolbar.CSS.toolbar);
|
||||
|
||||
/**
|
||||
* Make Content Zone and Actions Zone
|
||||
*/
|
||||
['content', 'actions'].forEach( el => {
|
||||
|
||||
this.nodes[el] = $.make('div', this.CSS[el]);
|
||||
this.nodes[el] = $.make('div', Toolbar.CSS[el]);
|
||||
$.append(this.nodes.wrapper, this.nodes[el]);
|
||||
|
||||
});
|
||||
|
@ -121,12 +131,15 @@ export default class Toolbar extends Module {
|
|||
* - Plus Button
|
||||
* - Toolbox
|
||||
*/
|
||||
['plusButton', 'toolbox'].forEach( el => {
|
||||
this.nodes.plusButton = $.make('div', Toolbar.CSS.plusButton);
|
||||
$.append(this.nodes.content, this.nodes.plusButton);
|
||||
this.nodes.plusButton.addEventListener('click', event => this.plusButtonClicked(event), false);
|
||||
|
||||
this.nodes[el] = $.make('div', this.CSS[el]);
|
||||
$.append(this.nodes.content, this.nodes[el]);
|
||||
|
||||
});
|
||||
/**
|
||||
* Make a Toolbox
|
||||
*/
|
||||
this.Editor.Toolbox.make();
|
||||
|
||||
/**
|
||||
* Fill Actions Zone:
|
||||
|
@ -134,7 +147,7 @@ export default class Toolbar extends Module {
|
|||
* - Remove Block Button
|
||||
* - Settings Panel
|
||||
*/
|
||||
this.nodes.settingsToggler = $.make('span', this.CSS.settingsToggler);
|
||||
this.nodes.settingsToggler = $.make('span', Toolbar.CSS.settingsToggler);
|
||||
this.nodes.removeBlockButton = this.makeRemoveBlockButton();
|
||||
|
||||
$.append(this.nodes.actions, [this.nodes.settingsToggler, this.nodes.removeBlockButton]);
|
||||
|
@ -158,10 +171,10 @@ export default class Toolbar extends Module {
|
|||
*/
|
||||
makeBlockSettingsPanel() {
|
||||
|
||||
this.nodes.settings = $.make('div', this.CSS.settings);
|
||||
this.nodes.settings = $.make('div', Toolbar.CSS.settings);
|
||||
|
||||
this.nodes.pluginSettings = $.make('div', this.CSS.pluginSettings);
|
||||
this.nodes.defaultSettings = $.make('div', this.CSS.defaultSettings);
|
||||
this.nodes.pluginSettings = $.make('div', Toolbar.CSS.pluginSettings);
|
||||
this.nodes.defaultSettings = $.make('div', Toolbar.CSS.defaultSettings);
|
||||
|
||||
$.append(this.nodes.settings, [this.nodes.pluginSettings, this.nodes.defaultSettings]);
|
||||
$.append(this.nodes.actions, this.nodes.settings);
|
||||
|
@ -178,7 +191,83 @@ export default class Toolbar extends Module {
|
|||
* @todo add confirmation panel and handlers
|
||||
* @see {@link settings#makeRemoveBlockButton}
|
||||
*/
|
||||
return $.make('span', this.CSS.removeBlockButton);
|
||||
return $.make('span', Toolbar.CSS.removeBlockButton);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Move Toolbar to the Current Block
|
||||
*/
|
||||
move() {
|
||||
|
||||
/** Close Toolbox when we move toolbar */
|
||||
this.Editor.Toolbox.close();
|
||||
|
||||
let currentNode = this.Editor.BlockManager.currentNode;
|
||||
|
||||
/**
|
||||
* If no one Block selected as a Current
|
||||
*/
|
||||
if (!currentNode) {
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo Compute dynamically on prepare
|
||||
* @type {number}
|
||||
*/
|
||||
const defaultToolbarHeight = 49;
|
||||
const defaultOffset = 34;
|
||||
|
||||
var newYCoordinate = currentNode.offsetTop - (defaultToolbarHeight / 2) + defaultOffset;
|
||||
|
||||
this.nodes.wrapper.style.transform = `translate3D(0, ${Math.floor(newYCoordinate)}px, 0)`;
|
||||
|
||||
/** Close trash actions */
|
||||
// editor.toolbar.settings.hideRemoveActions();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Open Toolbar with Plus Button
|
||||
*/
|
||||
open() {
|
||||
|
||||
this.nodes.wrapper.classList.add(Toolbar.CSS.toolbarOpened);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the Toolbar
|
||||
*/
|
||||
close() {
|
||||
|
||||
this.nodes.wrapper.classList.remove(Toolbar.CSS.toolbarOpened);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Plus Button public methods
|
||||
* @return {{hide: function(): void, show: function(): void}}
|
||||
*/
|
||||
get plusButton() {
|
||||
|
||||
return {
|
||||
hide: () => this.nodes.plusButton.classList.add(Toolbar.CSS.plusButtonHidden),
|
||||
show: () => this.nodes.plusButton.classList.remove(Toolbar.CSS.plusButtonHidden)
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for Plus Button
|
||||
* @param {MouseEvent} event
|
||||
*/
|
||||
plusButtonClicked(event) {
|
||||
|
||||
this.Editor.Toolbox.toggle();
|
||||
|
||||
}
|
||||
|
||||
|
|
223
src/components/modules/toolbox.js
Normal file
223
src/components/modules/toolbox.js
Normal file
|
@ -0,0 +1,223 @@
|
|||
/**
|
||||
* @class Toolbox
|
||||
* @classdesc Holder for Tools
|
||||
*
|
||||
* @typedef {Toolbox} Toolbox
|
||||
* @property {Boolean} opened - opening state
|
||||
* @property {Object} nodes - Toolbox nodes
|
||||
* @property {Object} CSS - CSS class names
|
||||
*
|
||||
*/
|
||||
export default class Toolbox extends Module {
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*/
|
||||
constructor({config}) {
|
||||
|
||||
super({config});
|
||||
|
||||
this.nodes = {
|
||||
toolbox: null,
|
||||
buttons: []
|
||||
};
|
||||
|
||||
/**
|
||||
* Opening state
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.opened = false;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* CSS styles
|
||||
* @return {{toolbox: string, toolboxButton: string, toolboxOpened: string}}
|
||||
*/
|
||||
static get CSS() {
|
||||
|
||||
return {
|
||||
toolbox: 'ce-toolbox',
|
||||
toolboxButton: 'ce-toolbox__button',
|
||||
toolboxOpened: 'ce-toolbox--opened',
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the Toolbox
|
||||
*/
|
||||
make() {
|
||||
|
||||
this.nodes.toolbox = $.make('div', Toolbox.CSS.toolbox);
|
||||
$.append(this.Editor.Toolbar.nodes.content, this.nodes.toolbox);
|
||||
|
||||
this.addTools();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates available tools and appends them to the Toolbox
|
||||
*/
|
||||
addTools() {
|
||||
|
||||
let tools = this.Editor.Tools.toolsAvailable;
|
||||
|
||||
for (let toolName in tools) {
|
||||
|
||||
this.addTool(toolName, tools[toolName]);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Append Tool to the Toolbox
|
||||
*
|
||||
* @param {string} toolName - tool name
|
||||
* @param {Tool} tool - tool class
|
||||
*/
|
||||
addTool(toolName, tool) {
|
||||
|
||||
if (tool.displayInToolbox && !tool.iconClassName) {
|
||||
|
||||
_.log('Toolbar icon class name is missed. Tool %o skipped', 'warn', toolName);
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo Add checkup for the render method
|
||||
*/
|
||||
// if (typeof tool.render !== 'function') {
|
||||
//
|
||||
// _.log('render method missed. Tool %o skipped', 'warn', tool);
|
||||
// return;
|
||||
//
|
||||
// }
|
||||
|
||||
/**
|
||||
* Skip tools that pass 'displayInToolbox=false'
|
||||
*/
|
||||
if (!tool.displayInToolbox) {
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
let button = $.make('li', [Toolbox.CSS.toolboxButton, tool.iconClassName], {
|
||||
title: toolName
|
||||
});
|
||||
|
||||
/**
|
||||
* Save tool's name in the button data-name
|
||||
*/
|
||||
button.dataset.name = toolName;
|
||||
|
||||
$.append(this.nodes.toolbox, button);
|
||||
|
||||
this.nodes.toolbox.appendChild(button);
|
||||
this.nodes.buttons.push(button);
|
||||
|
||||
/**
|
||||
* @todo add event with module Listeners
|
||||
*/
|
||||
// this.Editor.Listeners.add();
|
||||
button.addEventListener('click', event => {
|
||||
|
||||
this.buttonClicked(event);
|
||||
|
||||
}, false);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Toolbox button click listener
|
||||
* 1) if block is empty -> replace
|
||||
* 2) if block is not empty -> add new block below
|
||||
*
|
||||
* @param {MouseEvent} event
|
||||
*/
|
||||
buttonClicked(event) {
|
||||
|
||||
let toolButton = event.target,
|
||||
toolName = toolButton.dataset.name,
|
||||
tool = this.Editor.Tools.toolClasses[toolName];
|
||||
|
||||
/**
|
||||
* @type {Block}
|
||||
*/
|
||||
let currentBlock = this.Editor.BlockManager.currentBlock;
|
||||
|
||||
/**
|
||||
* We do replace if:
|
||||
* - block is empty
|
||||
* - block is not irreplaceable
|
||||
* @type {Array}
|
||||
*/
|
||||
if (!tool.irreplaceable && currentBlock.isEmpty) {
|
||||
|
||||
this.Editor.BlockManager.replace(toolName);
|
||||
|
||||
} else {
|
||||
|
||||
this.Editor.BlockManager.insert(toolName);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo set caret to the new block
|
||||
*/
|
||||
|
||||
// window.setTimeout(function () {
|
||||
|
||||
/** Set caret to current block */
|
||||
// editor.caret.setToBlock(currentInputIndex);
|
||||
|
||||
// }, 10);
|
||||
|
||||
/**
|
||||
* Move toolbar when node is changed
|
||||
*/
|
||||
this.Editor.Toolbar.move();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Open Toolbox with Tools
|
||||
*/
|
||||
open() {
|
||||
|
||||
this.nodes.toolbox.classList.add(Toolbox.CSS.toolboxOpened);
|
||||
this.opened = true;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Close Toolbox
|
||||
*/
|
||||
close() {
|
||||
|
||||
this.nodes.toolbox.classList.remove(Toolbox.CSS.toolboxOpened);
|
||||
this.opened = false;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Close Toolbox
|
||||
*/
|
||||
toggle() {
|
||||
|
||||
if (!this.opened) {
|
||||
|
||||
this.open();
|
||||
|
||||
} else {
|
||||
|
||||
this.close();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -5,10 +5,9 @@
|
|||
*/
|
||||
|
||||
/**
|
||||
* Load user defined tools
|
||||
* Tools must contain the following important objects:
|
||||
* Each Tool must contain the following important objects:
|
||||
*
|
||||
* @typedef {Object} ToolsConfig
|
||||
* @typedef {Object} ToolConfig {@link docs/tools.md}
|
||||
* @property {String} iconClassname - this a icon in toolbar
|
||||
* @property {Boolean} displayInToolbox - will be displayed in toolbox. Default value is TRUE
|
||||
* @property {Boolean} enableLineBreaks - inserts new block or break lines. Default value is FALSE
|
||||
|
@ -19,15 +18,28 @@
|
|||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Tool} Tool
|
||||
* @property {String} name - name of this module
|
||||
* @property {Object[]} toolInstances - list of tool instances
|
||||
* @property {Tools[]} available - available Tools
|
||||
* @property {Tools[]} unavailable - unavailable Tools
|
||||
* @typedef {Function} Tool {@link docs/tools.md}
|
||||
* @property {Boolean} displayInToolbox - By default, tools won't be added in the Toolbox. Pass true to add.
|
||||
* @property {String} iconClassName - CSS class name for the Toolbox button
|
||||
* @property {Boolean} irreplaceable - Toolbox behaviour: replace or add new block below
|
||||
* @property render
|
||||
* @property save
|
||||
* @property settings
|
||||
* @property validate
|
||||
*
|
||||
* @todo update according to current API
|
||||
* @todo describe Tool in the {@link docs/tools.md}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class properties:
|
||||
*
|
||||
* @typedef {Tools} Tools
|
||||
* @property {Tools[]} toolsAvailable - available Tools
|
||||
* @property {Tools[]} toolsUnavailable - unavailable Tools
|
||||
* @property {Object} toolsClasses - all classes
|
||||
* @property {EditorConfig} config - Editor config
|
||||
*/
|
||||
|
||||
export default class Tools extends Module {
|
||||
|
||||
/**
|
||||
|
@ -51,15 +63,18 @@ export default class Tools extends Module {
|
|||
}
|
||||
|
||||
/**
|
||||
* If config wasn't passed by user
|
||||
* @return {ToolsConfig}
|
||||
* Static getter for default Tool config fields
|
||||
*
|
||||
* @usage Tools.defaultConfig.displayInToolbox
|
||||
* @return {ToolConfig}
|
||||
*/
|
||||
get defaultConfig() {
|
||||
static get defaultConfig() {
|
||||
|
||||
return {
|
||||
iconClassName : 'default-icon',
|
||||
iconClassName : '',
|
||||
displayInToolbox : false,
|
||||
enableLineBreaks : false
|
||||
enableLineBreaks : false,
|
||||
irreplaceable : false
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -67,21 +82,38 @@ export default class Tools extends Module {
|
|||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @param {ToolsConfig} config
|
||||
* @param {EditorConfig} config
|
||||
*/
|
||||
constructor(config) {
|
||||
constructor({config}) {
|
||||
|
||||
super(config);
|
||||
super({config});
|
||||
|
||||
/**
|
||||
* Map {name: Class, ...} where:
|
||||
* name — block type name in JSON. Got from EditorConfig.tools keys
|
||||
* @type {Object}
|
||||
*/
|
||||
this.toolClasses = {};
|
||||
|
||||
/**
|
||||
* Available tools list
|
||||
* {name: Class, ...}
|
||||
* @type {Object}
|
||||
*/
|
||||
this.toolsAvailable = {};
|
||||
|
||||
/**
|
||||
* Tools that rejected a prepare method
|
||||
* {name: Class, ... }
|
||||
* @type {Object}
|
||||
*/
|
||||
this.toolsUnavailable = {};
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates instances via passed or default configuration
|
||||
* @return {boolean}
|
||||
* @return {Promise}
|
||||
*/
|
||||
prepare() {
|
||||
|
||||
|
@ -128,7 +160,7 @@ export default class Tools extends Module {
|
|||
|
||||
/**
|
||||
* Binds prepare function of plugins with user or default config
|
||||
* @return {Array} list of functions that needs to be fired sequently
|
||||
* @return {Array} list of functions that needs to be fired sequentially
|
||||
*/
|
||||
getListOfPrepareFunctions() {
|
||||
|
||||
|
@ -147,6 +179,13 @@ export default class Tools extends Module {
|
|||
}
|
||||
});
|
||||
|
||||
} else {
|
||||
|
||||
/**
|
||||
* If Tool hasn't a prepare method, mark it as available
|
||||
*/
|
||||
this.toolsAvailable[toolName] = toolClass;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -173,16 +212,6 @@ export default class Tools extends Module {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all tools
|
||||
* @return {Array}
|
||||
*/
|
||||
getTools() {
|
||||
|
||||
return this.toolInstances;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Return tool`a instance
|
||||
*
|
||||
|
@ -209,4 +238,15 @@ export default class Tools extends Module {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if passed Tool is an instance of Initial Block Tool
|
||||
* @param {Tool} tool - Tool to check
|
||||
* @return {Boolean}
|
||||
*/
|
||||
isInitial(tool) {
|
||||
|
||||
return tool instanceof this.available[this.config.initialBlock];
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -31,10 +31,7 @@
|
|||
// SETTINGS_ITEM : 'ce-settings__item'
|
||||
// };
|
||||
|
||||
let CSS = {
|
||||
editorWrapper : 'codex-editor',
|
||||
editorZone : 'ce-redactor'
|
||||
};
|
||||
import Block from '../block';
|
||||
|
||||
/**
|
||||
* @class
|
||||
|
@ -54,7 +51,6 @@ let CSS = {
|
|||
* @property {Element} nodes.wrapper - <codex-editor>
|
||||
* @property {Element} nodes.redactor - <ce-redactor>
|
||||
*/
|
||||
|
||||
export default class UI extends Module {
|
||||
|
||||
/**
|
||||
|
@ -62,9 +58,9 @@ export default class UI extends Module {
|
|||
*
|
||||
* @param {EditorConfig} config
|
||||
*/
|
||||
constructor(config) {
|
||||
constructor({config}) {
|
||||
|
||||
super(config);
|
||||
super({config});
|
||||
|
||||
this.nodes = {
|
||||
holder: null,
|
||||
|
@ -81,45 +77,19 @@ export default class UI extends Module {
|
|||
*/
|
||||
prepare() {
|
||||
|
||||
return new Promise( (resolve, reject) => {
|
||||
|
||||
/**
|
||||
* Element where we need to append CodeX Editor
|
||||
* @type {Element}
|
||||
*/
|
||||
this.nodes.holder = document.getElementById(this.config.holderId);
|
||||
|
||||
if (!this.nodes.holder) {
|
||||
|
||||
reject(Error("Holder wasn't found by ID: #" + this.config.holderId));
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and save main UI elements
|
||||
*/
|
||||
this.nodes.wrapper = $.make('div', CSS.editorWrapper);
|
||||
this.nodes.redactor = $.make('div', CSS.editorZone);
|
||||
|
||||
this.nodes.wrapper.appendChild(this.nodes.redactor);
|
||||
this.nodes.holder.appendChild(this.nodes.wrapper);
|
||||
|
||||
return this.make()
|
||||
/**
|
||||
* Make toolbar
|
||||
*/
|
||||
this.Editor.Toolbar.make();
|
||||
.then(() => this.Editor.Toolbar.make())
|
||||
/**
|
||||
* Load and append CSS
|
||||
*/
|
||||
this.loadStyles();
|
||||
|
||||
resolve();
|
||||
|
||||
})
|
||||
|
||||
/** Add toolbox tools */
|
||||
// .then(addTools_)
|
||||
.then(() => this.loadStyles())
|
||||
/**
|
||||
* Bind events for the UI elements
|
||||
*/
|
||||
.then(() => this.bindEvents())
|
||||
|
||||
/** Make container for inline toolbar */
|
||||
// .then(makeInlineToolbar_)
|
||||
|
@ -143,6 +113,58 @@ export default class UI extends Module {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* CodeX Editor UI CSS class names
|
||||
* @return {{editorWrapper: string, editorZone: string, block: string}}
|
||||
*/
|
||||
get CSS() {
|
||||
|
||||
return {
|
||||
editorWrapper : 'codex-editor',
|
||||
editorZone : 'codex-editor__redactor',
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes CodeX Editor interface
|
||||
* @return {Promise<any>}
|
||||
*/
|
||||
make() {
|
||||
|
||||
return new Promise( (resolve, reject) => {
|
||||
|
||||
/**
|
||||
* Element where we need to append CodeX Editor
|
||||
* @type {Element}
|
||||
*/
|
||||
this.nodes.holder = document.getElementById(this.config.holderId);
|
||||
|
||||
if (!this.nodes.holder) {
|
||||
|
||||
reject(Error("Holder wasn't found by ID: #" + this.config.holderId));
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and save main UI elements
|
||||
*/
|
||||
this.nodes.wrapper = $.make('div', this.CSS.editorWrapper);
|
||||
this.nodes.redactor = $.make('div', this.CSS.editorZone);
|
||||
|
||||
this.nodes.wrapper.appendChild(this.nodes.redactor);
|
||||
this.nodes.holder.appendChild(this.nodes.wrapper);
|
||||
|
||||
resolve();
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends CSS
|
||||
*/
|
||||
loadStyles() {
|
||||
|
||||
/**
|
||||
|
@ -164,6 +186,174 @@ export default class UI extends Module {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind events on the CodeX Editor interface
|
||||
*/
|
||||
bindEvents() {
|
||||
|
||||
/**
|
||||
* @todo bind events with the Listeners module
|
||||
*/
|
||||
this.nodes.redactor.addEventListener('click', event => this.redactorClicked(event), false );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* All clicks on the redactor zone
|
||||
*
|
||||
* @param {MouseEvent} event
|
||||
*
|
||||
* @description
|
||||
* 1. Save clicked Block as a current {@link BlockManager#currentNode}
|
||||
* it uses for the following:
|
||||
* - add CSS modifier for the selected Block
|
||||
* - on Enter press, we make a new Block under that
|
||||
*
|
||||
* 2. Move and show the Toolbar
|
||||
*
|
||||
* 3. Set a Caret
|
||||
*
|
||||
* 4. By clicks on the Editor's bottom zone:
|
||||
* - if last Block is empty, set a Caret to this
|
||||
* - otherwise, add a new empty Block and set a Caret to that
|
||||
*
|
||||
* 5. Hide the Inline Toolbar
|
||||
*
|
||||
* @see selectClickedBlock
|
||||
*
|
||||
*/
|
||||
redactorClicked(event) {
|
||||
|
||||
let clickedNode = event.target;
|
||||
|
||||
/**
|
||||
* Select clicked Block as Current
|
||||
*/
|
||||
try {
|
||||
|
||||
this.Editor.BlockManager.setCurrentBlockByChildNode(clickedNode);
|
||||
|
||||
/**
|
||||
* If clicked outside first-level Blocks, set Caret to the last empty Block
|
||||
*/
|
||||
|
||||
} catch (e) {
|
||||
|
||||
this.Editor.Caret.setToTheLastBlock();
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @todo hide the Inline Toolbar
|
||||
*/
|
||||
// var selectedText = editor.toolbar.inline.getSelectionText(),
|
||||
// firstLevelBlock;
|
||||
|
||||
/** If selection range took off, then we hide inline toolbar */
|
||||
// if (selectedText.length === 0) {
|
||||
|
||||
// editor.toolbar.inline.close();
|
||||
|
||||
// }
|
||||
|
||||
/**
|
||||
*
|
||||
|
||||
/** Update current input index in memory when caret focused into existed input */
|
||||
// if (event.target.contentEditable == 'true') {
|
||||
//
|
||||
// editor.caret.saveCurrentInputIndex();
|
||||
//
|
||||
// }
|
||||
|
||||
// if (editor.content.currentNode === null) {
|
||||
//
|
||||
// /**
|
||||
// * If inputs in redactor does not exits, then we put input index 0 not -1
|
||||
// */
|
||||
// var indexOfLastInput = editor.state.inputs.length > 0 ? editor.state.inputs.length - 1 : 0;
|
||||
//
|
||||
// /** If we have any inputs */
|
||||
// if (editor.state.inputs.length) {
|
||||
//
|
||||
// /** getting firstlevel parent of input */
|
||||
// firstLevelBlock = editor.content.getFirstLevelBlock(editor.state.inputs[indexOfLastInput]);
|
||||
//
|
||||
// }
|
||||
//
|
||||
// /** If input is empty, then we set caret to the last input */
|
||||
// if (editor.state.inputs.length && editor.state.inputs[indexOfLastInput].textContent === '' && firstLevelBlock.dataset.tool == editor.settings.initialBlockPlugin) {
|
||||
//
|
||||
// editor.caret.setToBlock(indexOfLastInput);
|
||||
//
|
||||
// } else {
|
||||
//
|
||||
// /** Create new input when caret clicked in redactors area */
|
||||
// var NEW_BLOCK_TYPE = editor.settings.initialBlockPlugin;
|
||||
//
|
||||
// editor.content.insertBlock({
|
||||
// type : NEW_BLOCK_TYPE,
|
||||
// block : editor.tools[NEW_BLOCK_TYPE].render()
|
||||
// });
|
||||
//
|
||||
// /** If there is no inputs except inserted */
|
||||
// if (editor.state.inputs.length === 1) {
|
||||
//
|
||||
// editor.caret.setToBlock(indexOfLastInput);
|
||||
//
|
||||
// } else {
|
||||
//
|
||||
// /** Set caret to this appended input */
|
||||
// editor.caret.setToNextBlock(indexOfLastInput);
|
||||
//
|
||||
// }
|
||||
//
|
||||
// }
|
||||
//
|
||||
// } else {
|
||||
//
|
||||
// /** Close all panels */
|
||||
// editor.toolbar.settings.close();
|
||||
// editor.toolbar.toolbox.close();
|
||||
//
|
||||
// }
|
||||
//
|
||||
/**
|
||||
* Move toolbar and open
|
||||
*/
|
||||
this.Editor.Toolbar.move();
|
||||
this.Editor.Toolbar.open();
|
||||
//
|
||||
// var inputIsEmpty = !editor.content.currentNode.textContent.trim(),
|
||||
// currentNodeType = editor.content.currentNode.dataset.tool,
|
||||
// isInitialType = currentNodeType == editor.settings.initialBlockPlugin;
|
||||
//
|
||||
//
|
||||
|
||||
/**
|
||||
* Hide the Plus Button
|
||||
* */
|
||||
this.Editor.Toolbar.plusButton.hide();
|
||||
|
||||
/**
|
||||
* Show the Plus Button if:
|
||||
* - Block is an initial-block (Text)
|
||||
* - Block is empty
|
||||
*/
|
||||
let isInitialBlock = this.Editor.Tools.isInitial(this.Editor.BlockManager.currentBlock.tool),
|
||||
isEmptyBlock = this.Editor.BlockManager.currentBlock.isEmpty;
|
||||
|
||||
if (isInitialBlock && isEmptyBlock) {
|
||||
|
||||
this.Editor.Toolbar.plusButton.show();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// /**
|
||||
|
@ -194,54 +384,6 @@ export default class UI extends Module {
|
|||
//
|
||||
// };
|
||||
//
|
||||
// /**
|
||||
// * @private
|
||||
// * Append tools passed in editor.tools
|
||||
// */
|
||||
// var addTools_ = function () {
|
||||
//
|
||||
// var tool,
|
||||
// toolName,
|
||||
// toolButton;
|
||||
//
|
||||
// for ( toolName in editor.settings.tools ) {
|
||||
//
|
||||
// tool = editor.settings.tools[toolName];
|
||||
//
|
||||
// editor.tools[toolName] = tool;
|
||||
//
|
||||
// if (!tool.iconClassname && tool.displayInToolbox) {
|
||||
//
|
||||
// editor.core.log('Toolbar icon classname missed. Tool %o skipped', 'warn', toolName);
|
||||
// continue;
|
||||
//
|
||||
// }
|
||||
//
|
||||
// if (typeof tool.render != 'function') {
|
||||
//
|
||||
// editor.core.log('render method missed. Tool %o skipped', 'warn', toolName);
|
||||
// continue;
|
||||
//
|
||||
// }
|
||||
//
|
||||
// if (!tool.displayInToolbox) {
|
||||
//
|
||||
// continue;
|
||||
//
|
||||
// } else {
|
||||
//
|
||||
// /** if tools is for toolbox */
|
||||
// toolButton = editor.draw.toolbarButton(toolName, tool.iconClassname);
|
||||
//
|
||||
// editor.nodes.toolbox.appendChild(toolButton);
|
||||
//
|
||||
// editor.nodes.toolbarButtons[toolName] = toolButton;
|
||||
//
|
||||
// }
|
||||
//
|
||||
// }
|
||||
//
|
||||
// };
|
||||
//
|
||||
// var addInlineToolbarTools_ = function () {
|
||||
//
|
||||
|
|
24
src/components/polyfills.js
Normal file
24
src/components/polyfills.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
/**
|
||||
* Element.closest()
|
||||
*
|
||||
* https://developer.mozilla.org/en-US/docs/Web/API/Element/closest
|
||||
*/
|
||||
if (!Element.prototype.matches)
|
||||
Element.prototype.matches = Element.prototype.msMatchesSelector ||
|
||||
Element.prototype.webkitMatchesSelector;
|
||||
|
||||
if (!Element.prototype.closest)
|
||||
Element.prototype.closest = function (s) {
|
||||
|
||||
var el = this;
|
||||
|
||||
if (!document.documentElement.contains(el)) return null;
|
||||
do {
|
||||
|
||||
if (el.matches(s)) return el;
|
||||
el = el.parentElement || el.parentNode;
|
||||
|
||||
} while (el !== null);
|
||||
return null;
|
||||
|
||||
};
|
|
@ -3,6 +3,43 @@
|
|||
*/
|
||||
export default class Util {
|
||||
|
||||
/**
|
||||
* Custom logger
|
||||
*
|
||||
* @param {string} msg - message
|
||||
* @param {string} type - logging type 'log'|'warn'|'error'|'info'
|
||||
* @param {*} args - argument to log with a message
|
||||
*/
|
||||
static log(msg, type, args) {
|
||||
|
||||
type = type || 'log';
|
||||
|
||||
if (!args) {
|
||||
|
||||
args = msg || 'undefined';
|
||||
msg = '[codex-editor]: %o';
|
||||
|
||||
} else {
|
||||
|
||||
msg = '[codex-editor]: ' + msg;
|
||||
|
||||
}
|
||||
|
||||
try{
|
||||
|
||||
if ( 'console' in window && window.console[ type ] ) {
|
||||
|
||||
if ( args ) window.console[ type ]( msg, args );
|
||||
else window.console[ type ]( msg );
|
||||
|
||||
}
|
||||
|
||||
} catch(e) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Object} ChainData
|
||||
* @property {Object} data - data that will be passed to the success or fallback
|
||||
|
|
12
src/styles/block.css
Normal file
12
src/styles/block.css
Normal file
|
@ -0,0 +1,12 @@
|
|||
.ce-block {
|
||||
border: 1px dotted #ccc;
|
||||
margin: 2px 0;
|
||||
|
||||
&--selected {
|
||||
background-color: var(--bg-light);
|
||||
}
|
||||
&__content {
|
||||
max-width: var(--content-width);
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
|
@ -1,2 +1,5 @@
|
|||
@import url('variables.css');
|
||||
@import url('ui.css');
|
||||
@import url('toolbar.css');
|
||||
@import url('toolbox.css');
|
||||
@import url('block.css');
|
||||
|
|
44
src/styles/toolbar.css
Normal file
44
src/styles/toolbar.css
Normal file
|
@ -0,0 +1,44 @@
|
|||
.ce-toolbar {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: opacity 100ms ease;
|
||||
will-change: opacity, transform;
|
||||
|
||||
&--opened {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
&__content {
|
||||
max-width: var(--content-width);
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
}
|
||||
&__plus {
|
||||
position: absolute;
|
||||
left: calc(-var(--toolbar-buttons-size) - 10px);
|
||||
display: inline-block;
|
||||
background-color: var(--bg-light);
|
||||
width: var(--toolbar-buttons-size);
|
||||
height: var(--toolbar-buttons-size);
|
||||
line-height: 34px;
|
||||
text-align: center;
|
||||
border-radius: 50%;
|
||||
|
||||
&::after {
|
||||
content: '+';
|
||||
font-size: 26px;
|
||||
display: block;
|
||||
margin-top: -2px;
|
||||
margin-right: -2px;
|
||||
}
|
||||
|
||||
&--hidden {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
34
src/styles/toolbox.css
Normal file
34
src/styles/toolbox.css
Normal file
|
@ -0,0 +1,34 @@
|
|||
.ce-toolbox {
|
||||
visibility: hidden;
|
||||
transition: opacity 100ms ease;
|
||||
will-change: opacity;
|
||||
|
||||
&--opened {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
&__button {
|
||||
display: inline-block;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
background: var(--bg-light);
|
||||
width: var(--toolbar-buttons-size);
|
||||
height: var(--toolbar-buttons-size);
|
||||
border-radius: 30px;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
line-height: var(--toolbar-buttons-size);
|
||||
|
||||
&::before {
|
||||
content: attr(title);
|
||||
font-size: 22px;
|
||||
font-weight: 500;
|
||||
letter-spacing: 1em;
|
||||
font-variant-caps: all-small-caps;
|
||||
padding-left: 11.5px;
|
||||
margin-top: -1px;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,12 +1,17 @@
|
|||
/**
|
||||
* Editor wrapper
|
||||
*/
|
||||
.codex-editor{
|
||||
.codex-editor {
|
||||
position: relative;
|
||||
border: 1px solid #ccc;
|
||||
padding: 10px;
|
||||
box-sizing: border-box;
|
||||
|
||||
.hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&__redactor {
|
||||
padding-bottom: 300px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,4 +5,14 @@
|
|||
*/
|
||||
--bg-light: #eff2f5;
|
||||
|
||||
/**
|
||||
* Block content width
|
||||
*/
|
||||
--content-width: 650px;
|
||||
|
||||
/**
|
||||
* Toolbar Plus Button and Toolbox buttons height and width
|
||||
*/
|
||||
--toolbar-buttons-size: 34px;
|
||||
|
||||
}
|
Loading…
Reference in a new issue