Release 2.0-beta (#387)

* update
* Toolbar, Toolbox, UI (#239)
* Toolbox making
* Add Toolbox buttons click handler
* Toolbar, Toolbox, UI
* Updates
* update css prefix
* trying to write docs
* append callback behaviour
* update request
* update
* empty initial data
* initial saver
* some requested changes
* update request
* requested changes
* upgrade saver
* new improvements
* update
* Caret module: initial
* improvements
* moving caret initial
* small improvements
* last changes, added docs
* requested changes
* implement getters instead of functions in block cursors
* last requested changes
* caret module docs and last improvements
* update docs
* upgrade request
* update docs
* upd
* todo on delays
* Sanitizer docs
* split func upd
* split blocks update
* up docs
* Listeners Module: initial
* listener module updates
* split is ready
* update
* start to make merge
* upd
* split general upd split is ready
* ups
* keyboard module update
* BlockManager removed keyboard handler
* commit before merging rewriting2.0
* general upd split
* Documentation upd
* document + listener upd
* upd doc
* documentation upd
* doc upd
* listener upd
* update algh extract fragm
* upd extractRangeContent
* upd dom.js
* keyboard upd (shift + enter & enableLineBreaks)
* upd enter pressed
* keyboard.js upd
* enter pressed upd
* documenation added
* documentation upd
* Toolbar: settings zone added. (#252)
* Toolbar: settings zone added.
* update some comments
* Making a Toolbar
* delete block
* dom improvements and merging blocks
* merge and split improvements
* fix merging
* do not remove block if block contains media
* optimize code
* caret behaviour improved
* up
* up
* merging blocks. Now plugins handles this cases
* mergeable getter
* save
* up
* dom getdeepestnode improvements
* improve getDeepest node method one more time
* upd
* Deal with it
* improve isAtStart
* improve docs
* use smart isAtStart and isAtEnt method in navigateNext/navigatePrevious
* improve docs
* fix bug in $.isEmpty, improve keydown
* fix isAtEnd
* rollback setCaret code duplication
* improve backspace
* Debug tree walker
* fix tree walker
* small caret fix
* queue ordering
* update bundle
* improve first letter checkup
* doc upd
* update current block index setter
* TypeScript support, Webpack 4, Inline Toolbar beginning (#257)
* Create UI
* Support TypeScript Modules
* remove tmp files
* migrate to 2-spaced tabs
* Add TS Linter
* Inline Toolbar moving (#258)
* Inline Toolbar moving
* simplify code
* Check is need to show Inline Toolbar
* remove duplicate from doc
* fix doc
* open/close IT
* Close IT by clicks on Redactor
* @guryn going strange
* default settings initial
* add move up button to default tunes area
* need to figure out with assets
* Inline Toolbar Tools base example (#260)
* Inline Toolbar Tools base example
* texts fixed
* imrpove texts
* little fixes
* save
* tunes with interface
* add tool settings
* initial api methods
* api is ready
* started writing docs
* Create svg sprite (#261)
* API
* requested changes
* fix conflicts
* add docs
* doc fixes and interface improvements
* update
* API scopes improved
* Deleting block: Initial
* Delete block with confirmation
* Event subscription&unsubscription
* deletion trigger improvements
* small improvements
* Link Inline Tool (#264)
* Link Inline Tool
* api injected
* text improved
* Clear input on the reselection
* little improvements
* Delete tune fixes
* UI: Block Settings, show Plus after Enter keydown (#265)
* Some UI improvements: icons settigns
* Show plus button after split
* decrease autoprefixer
* rename variable
* Revert "Merge branch 'delete-tune-fixes' into rewriting-version2.0"
* Delete Tune improvements
* upd
* upd comments
* actualize API docs
* Allow to connect external Inline Tools (#269)
* Allow to connect external Inline Tools
* unhardcode tool's api settings
* Italic inline tool
* update icon size
* upgrade findParentTag function
* add interface selection
* fix cs
* save marker
* bundles
* add todo
* removing wrapper
* update styles
* market -> term
* add comments
* improve code
* descrease margin
* add text block to example
* add line brakes
* remove space
* fix bugs
* fix bug
* umd as a library target
* background -> background-color
* Clear API (#274)
* blockManager.clear
* upd
* api bez ebanoj knopki api
* fix assignment
* insert empty block with clear method
* clear and render methods improved
* open saver.save()
* add comments
* update comments
* fix data returned by editor
* rename plugin name field in data object (#276)
* Text tool refactored (#277) Now it returns strict data format.
* do not add block if tool is not exist (#278)
* do not add block if tool is not exist
* show warning
* add todo
* update warning message
* put message into variable
* Revert "put message into variable"
* update comment
* Module Keyboard rewrited to BlockEvents (#279)
* Module Keydown rewrited to BlockEvents
* move keyup and mouseup to the Block Events
* Move-up tune (#268)
* Move up tune initial
* move up tune initial behaviour* moving up formula, docs and code cleaned
* do not close the toolbar if moving block up* move nagivate methods to Caret Module* navigations returns boolean if caret is set
* code improved
* update comments
* Eslint --fix for project files (#280)
* Header plugin (#281)
* header initial
* fix styles
* eslint fix
* add appendCallback
* add comments
* update styles
* add svgs
* highlight settings buttons
* do not show text plugin in the toolbar
* remove svg
* Fixing caret behaviour. (#282) Plugins can change their state so that affect on Block's pluginsContent property which is in memory.
* remove useless code
* fix merge
* "MoveDown" tune (#283)
* move down initial Swap current block with next block and scroll window screen
* check if block is last added new method to the blocks API that returns blocks count
* fix comments
* animate tune
* add animation when tune is disabled
* requested changes
* remove unused css
* Fix merge function and rename Block's wrapper (#284)
* Fix merge function and rename wrapper
* update
* renew condition
* update
* upd
* Merging blocks: Restore caret position 🤟🤟💪 (#286)
* Merging blocks: Restore caret position 🤟🤟💪
* requested changes
* update removing shadow caret
* hide toolbar and selection on typing (#289)
* Editor Instance config Interface (#285)
* create interface for editor config
* use IEditorConfig
* create some interfaces
* add comments
* editor interface
* updates
* update editor interface (#293)
* При перемещении по стрелочкам убирать выделение блока (#296)
* При перемещении по стрелочкам убирать выделение блока
* Add comments
* update comments
* update comment
* update toolbar design (#301)
* Set caret at the end if clicked outsite the block (#305)
* Set caret at last block or create new block at end
* update comment
* fix comments
* Insert new Block when enter pressed on editor area (#307)
* insert new block when enter pressed on editor zone
* extra conditions. Enter must be handled on editors area
* move at editor condition to the Selection method
* closes can return null
* fixing editor area
* do not create new block
* clean example
* updates due to the requested changes
* Add placeholder to contentEditable elements (#306)
* add placeholder to contentEditable elements
* store selection color in a variable
* add placeholder to header block
* Add placeholder to contenteditable only if attribute data-placeholder exists
* remove tool config
* Close toolbar after block is removed (#314)
* makeSettings -> renderSettings (#315)
* Term: new icon, new style. + margin between settings buttons (#316)
* remove useless span wrapper
* update linters
* update styles
* process click on svg by closest
* remove commented code
* rename function: svgIcon -> toolboxIcon
* add toolboxIcon to docs
* Paste (#259)
* Paste module
* Rewrite paste logic
* Update due comments
* Docs
* Add all block elements
* Sanitize content on paste
* Remove logs and add header handler
* Add comment to dom.js
* Add comment to tools.js
* Split block if paste not at the end
* Update docs
* Update docs
* Take Tool name from config
* Update docs
* Label onPaste handler as private
* Resolve conflict
* Replace current block if it is empty (#320)
* Improve Header line-height (#321)
* Fix typo (#324)
* CodeX Editor 2.0
* Module Shortcuts (#317)
* import shortcuts
* node modules works
* enable shortcut for inline tools
* check shortcut existance
* enable shortcuts to Block tools
* set caret to block if block replaced
* enable shortcuts on inline-tools
* improve code
* last changes
* update
* fix
* insert returns block so we can set caret
* use Map instead own structure
* disable shortcut if iniline-toolbar disabled
* update code styles
* remove todo
* upd
* update
* remove settings from insert function
* create interface
* code improvements
* use const instead of let
* upd
* Simple Image Tool (#326)
* Simple Image
* fix pattern
* show tunes` state
* update code
* update code
* upd
* Fix toolbox appearance, tools boxed are clickable (#331)
* Remove toolsConfig from Editor's config (#327)
* fix linters
* remove toolsConfig
* update tool's interfaces
* add comments
* bundles
* remove test headers
* restore commented code
* update tool's interface
* toolConfig -> toolSetting
* fix typos
* update code comments
* update installation doc
* update docs
* update dev dep packages (#333)
* Check is paste handler a function only if it exists (#328)
* Toolbar with tab (#330)
* toolbar tabs initial
* leaf initial
* save state
* flip back toolbox items
* enter on toolbox item
* update
* requested changes
* new condfition
* update
* improve animation on leaf
* fix shift+tab flip
* up
* update
* updates
* Consecutive blank lines are forbidden
* Correct choosing next toolbox item
* update
* update comment
* Validate editor's config before initing (#341)
* validate editor's config before initing
* update readme
* @
* update comments
* add function _.isClass
* Styles API (#343)
* StylesAPI
* use styles api in plugins
* add inline styles
* List Tool [new] (#344)
* list tool initial
* list class with settings
* make tool reactive
* final List improvements
* reorder
* tmp update
* unhadrcode enter handler
* updates
* enableLineBreaks also checks
* skip empty items
* select LI by CMD+A, fix backspace in the last item
* improve check for emptiness
* Example page improved (#347)
* Update new example
* imrpove example.html
* updates
* improve code
* Header plugin (#348)
* isFunction function
* use header from cdn
* Improve paste behaviour (#346)
* Improve paste behaviour
* Done
* Don't pass empty items
* Update comment
* move public up
* Remove disallow paste option
* Quote Tool (#329)
* Quote Tool
* Add icon
* Upd
* fix ENTER on quote
* Remove useless code
* items -> blocks (#351)
* use SimpleImage from cdn (#355)
* use simpleimage from cdn
* add comments
* fix spaces
* fix comments
* remove comments
* update simple-image script
* Update text on the example.html (#356)
* use Paragraph Tool from CDN (#357)
* use Paragraph Tool from CDN
* add line brakes
* rename block: paragraph -> text
* Remove _callbacks.js (#358)
* Clear unused files (#359)
* TOOLBAR_ICON_CLASS -> TOOLBAR_ICON (#360)
* TOOLBAR_ICON_CLASS -> TOOLBAR_ICON
* remove defaultConfig
* Delimiter tool (#362)
* Delimiter added
* ашч
* use delimiter from cdn
* Enter on editor (#363)
* Enterpress on editor
* use additional property
* check enter on body
* update
* fix toolbar behaviour
* upd
* update bundle
* remove useless ui property
* update comment
* add Element.prepend() function (#365)
* add Element.prepend() function
* allow to pass element of array to prepend() polyfill
* use List Tool from cdn (#366)
* use List Tool from cdn
* add missing </div> in example.html
* Pass "config" from Tool's settings to Tool's constructor (#367)
* pass config from Tool's settings to Tool's constructor
* reorder elements
* add apiSettings.CONFIG property
* use string as a object's key 😔 (#368)
* update placeholder's styles (#369)
* Add shortcuts for internal tools (#370)
* Add shortcuts for internal tools
* upd doc
* remove articles
* ☹️ guryn asked
* use quote from cdn (#371)
* Add cache to the inline tools (#372)
* use Inline Code Tool from cdn (#375)
* Issue 354 inline tools filter (#376)
* Allow to filter inline tools.
* update example
* update header
* fix endless cycle (#378)
* Destructured options for Inline Tools (#379)
* add destructured options for inline-tools
* temporary disable inlineCode
* remove term sources
* Fix toolbar moving after arrow navigation (#380)
* Fix toolbar moving after arrow navigation
* move before open
* fix error on Enter after block removing
* add example Tools as submodules (#381)
* add destructured options for inline-tools
* temporary disable inlineCode
* remove term sources
* add inline code Tool as a submodule
* update version of inline-code package
* add Tools as submodules
* update Tools and use destructured params object
* Add constructor() docs
* update package Paragraph Tool
* update installation docs
* Input navigation (#339)
* Quote Tool
* Add icon
* Upd
* Initial setup
* Save changes
* Add scroll and fix input focus
* Add comments
* Rebuild bundle
* fix ENTER on quote
* Fix split behaviour
* Fix
* Navigate only to contentful blocks
* add comments
* Fix backspace on last block
* Remove log
* It works
* Resolve comments
* Use constants
* New readme 🦅 (#386)
* New readme 🦅
* upd text
* Issue 374 (#385)
* Fix issue #374
* Set current block index to -1 if there is no blocks left
* Insert new block if first one was removed by default
* Paragraph as a default Tool in editor; Zero-conf (#389)
* git commit -m "Removed submodule Paragraph"
* add paragraph to core + zero-config
* update bundle
* update comment
* remove log
* enable minifying (#390)
* Drop current block index only if there is no selection at the Editor (#388)
* Drop current block index only if there is no selection at the Editor
* Set current node if click ended out of editor
* Small backspace behaviour improvement (#391)
* Small backspace behaviour improvement
* fix caret position
* update from base branch
* Update webpack config
* Migrate to Yarn (#393)
* Migrate to yarn
* Update scripts
* Rewrite helpers classes to TypeScript (#396)
* Add docs and isReady promise (#394)
* set default holderId value (#404)
* Destroyer (#392)
* Initial destroy method
* Add destroy method to api docs
* Export isReady promise in CodexEditor constructor

👨‍🎓

Co-authored-by: Murod Khaydarov <murod.haydarov@gmail.com>
Co-authored-by: George Berezhnoy <gohabereg@gmail.com>
Co-authored-by: Taly <vitalik7tv@yandex.ru>
This commit is contained in:
Peter Savchenko 2018-08-05 14:51:58 +03:00 committed by GitHub
parent dd83578f8d
commit 61242ab6a6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
179 changed files with 18561 additions and 16022 deletions

9
.editorconfig Normal file
View File

@ -0,0 +1,9 @@
root = false
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

View File

@ -1,7 +1,8 @@
{
/** Enable ES6 features */
"parserOptions": {
"ecmaVersion": 6,
"ecmaVersion": 2017,
"sourceType": "module"
},
"rules": {
@ -18,7 +19,7 @@
"no-shadow": 2,
"no-undef-init": 2,
"no-undef": 2,
"no-unused-vars": 1,
"no-unused-vars": 0,
/** Style */
"array-bracket-spacing": [2, "never", {
@ -26,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, {
@ -38,7 +42,7 @@
"no-nested-ternary": 1,
"no-trailing-spaces": 2,
"no-mixed-spaces-and-tabs": 2,
"padded-blocks": [2, "always"],
"padded-blocks": [2, "never"],
"space-before-blocks": 1,
"space-before-function-paren": [1, {
"anonymous": "always",
@ -49,7 +53,7 @@
"markers": ["=", "!"]
}],
"semi": [2, "always"],
"indent": [2, 4, {
"indent": [2, 2, {
"SwitchCase": 1
}],
"camelcase": [2, {
@ -71,7 +75,14 @@
"FormData": true,
"XMLHttpRequest": true,
"ActiveXObject": true,
"RegExp": true
"RegExp": true,
"Module": true,
"Node": true,
"Element": true,
"Proxy": true,
"Symbol": true,
"$": true,
"_": true,
"setTimeout": true
}
}
}

4
.gitignore vendored
View File

@ -6,7 +6,5 @@ Thumbs.db
/*.sublime-workspace
node_modules/*
/server/
/uploads/
plugins/personality/
npm-debug.log

21
.gitmodules vendored Normal file
View File

@ -0,0 +1,21 @@
[submodule "example/tools/inline-code"]
path = example/tools/inline-code
url = https://github.com/codex-editor/inline-code
[submodule "example/tools/header"]
path = example/tools/header
url = https://github.com/codex-editor/header
[submodule "example/tools/delimiter"]
path = example/tools/delimiter
url = https://github.com/codex-editor/delimiter
[submodule "example/tools/list"]
path = example/tools/list
url = https://github.com/codex-editor/list
[submodule "example/tools/quote"]
path = example/tools/quote
url = https://github.com/codex-editor/quote
[submodule "example/tools/simple-image"]
path = example/tools/simple-image
url = https://github.com/codex-editor/simple-image
[submodule "src/components/tools/paragraph"]
path = src/components/tools/paragraph
url = https://github.com/codex-editor/paragraph

View File

@ -12,11 +12,15 @@
// Define globals exposed by CodeX Team
"predef": [
"codex"
"codex",
"_",
"$",
"editorModules",
"Module"
],
// Allow ES6.
"esversion": 6,
"esversion": 2017,
/*
* ENFORCING OPTIONS
@ -61,4 +65,4 @@
// Suppress warnings about == null comparisons.
"eqnull": true
}
}

16
.postcssrc Normal file
View File

@ -0,0 +1,16 @@
plugins:
postcss-smart-import: {}
postcss-custom-properties: {}
postcss-apply: {}
postcss-custom-media: {}
postcss-media-minmax: {}
postcss-custom-selectors: {}
postcss-nested-ancestors: {}
postcss-nesting: {}
postcss-nested: {}
postcss-color-mod-function: {}
postcss-color-hex-alpha: {}
postcss-font-variant: {}
postcss-font-family-system-ui: {}
autoprefixer:
browsers: ['last 2 versions', '> 1%']

View File

@ -1,12 +1,17 @@
{
"rules": {
"at-rule-empty-line-before": [ "always", {
except: [
"blockless-after-same-name-blockless",
"first-nested",
],
ignore: ["after-comment"],
} ],
"at-rule-empty-line-before": [
"always",
{
except: [
"blockless-after-same-name-blockless",
"first-nested",
],
ignore: [
"after-comment"
],
}
],
"at-rule-name-case": "lower",
"at-rule-name-space-after": "always-single-line",
"at-rule-semicolon-newline-after": "always",
@ -21,27 +26,42 @@
"color-hex-case": "lower",
"color-hex-length": "short",
"color-no-invalid-hex": true,
"comment-empty-line-before": [ "always", {
except: ["first-nested"],
ignore: ["stylelint-commands"],
} ],
"comment-empty-line-before": [
"always",
{
except: [
"first-nested"
],
ignore: [
"stylelint-commands"
],
}
],
"comment-no-empty": true,
"comment-whitespace-inside": "always",
"custom-property-empty-line-before": [ "always", {
except: [
"after-custom-property",
"first-nested",
],
ignore: [
"after-comment",
"inside-single-line-block",
],
} ],
"custom-property-empty-line-before": [
"always",
{
except: [
"after-custom-property",
"first-nested",
],
ignore: [
"after-comment",
"inside-single-line-block",
],
}
],
"declaration-bang-space-after": "never",
"declaration-bang-space-before": "always",
"declaration-block-no-duplicate-properties": [ true, {
ignore: ["consecutive-duplicates-with-different-values"],
} ],
"declaration-block-no-duplicate-properties": [
true,
{
ignore: [
"consecutive-duplicates-with-different-values"
],
}
],
"declaration-block-no-redundant-longhand-properties": true,
"declaration-block-no-shorthand-property-overrides": true,
"declaration-block-semicolon-newline-after": "always-multi-line",
@ -52,16 +72,19 @@
"declaration-colon-newline-after": "always-multi-line",
"declaration-colon-space-after": "always-single-line",
"declaration-colon-space-before": "never",
"declaration-empty-line-before": [ "always", {
except: [
"after-declaration",
"first-nested",
],
ignore: [
"after-comment",
"inside-single-line-block",
],
} ],
"declaration-empty-line-before": [
"always",
{
except: [
"after-declaration",
"first-nested",
],
ignore: [
"after-comment",
"inside-single-line-block",
],
}
],
"font-family-no-duplicate-names": true,
"function-calc-no-unspaced-operator": true,
"function-comma-newline-after": "always-multi-line",
@ -96,13 +119,17 @@
"number-no-trailing-zeros": true,
"property-case": "lower",
"property-no-unknown": true,
"rule-nested-empty-line-before": [ "always-multi-line", {
except: ["first-nested"],
ignore: ["after-comment"],
} ],
"rule-non-nested-empty-line-before": [ "always-multi-line", {
ignore: ["after-comment"],
} ],
"rule-empty-line-before": [
"always-multi-line",
{
except: [
"first-nested"
],
ignore: [
"after-comment"
],
}
],
"selector-attribute-brackets-space-inside": "never",
"selector-attribute-operator-space-after": "never",
"selector-attribute-operator-space-before": "never",
@ -129,4 +156,4 @@
"value-list-comma-space-before": "never",
"value-list-max-empty-lines": 0,
},
}
}

235
README.md
View File

@ -1,49 +1,222 @@
### NOTE
<p align="center"><img src="https://capella.pics/3c0b525b-50d9-4720-8aad-9148114cfa6e.jpg"></p>
At the moment we working hard on the CodeX Editor 2.0 with completely new core, new API and great new documentation. Stay tuned and subsribe to us on the Twitter. Be in the front.
![](https://flat.badgen.net/badge/CodeX%20Editor/v2.0.0/blue?icon=npm)
[http://twitter.com/codex_team](http://twitter.com/codex_team)
## Version 2.0-beta is here!
We glad to introduce the next version of CodeX Editor. Totally new core, structure and plugins — that was an impressive adventure 🤓.
Welcome to testing stage. Please, join a [public Telegram-chat](//t.me/codex_editor) where you always find a support.
## Documentation
While we develop the new Documentation Site with all stuff, you can check some available docs at the [docs/](docs/) dir.
- [Installation](docs/installation.md)
- [How to use](docs/usage.md)
- [How to create a Block Tool Plugin](docs/tools.md)
- [How to create an Inline Tool Plugin](docs/tools-inline.md)
- [API for Tools](src/components/interfaces/api.ts)
Sorry if we missed something. You can join a [Telegram-chat](//t.me/codex_editor) and ask a question.
---
# CodeX Editor
Next generation Editor for modern web applications.
# So how to use CodeX Editor
### Block-styled
## Basics
Entry composed by different and extended list of Blocks
CodeX Editor is a Block-Styled editor. Blocks is a structural units, of which the Entry is composed.
For example, `Paragraph`, `Heading`, `Image`, `Video`, `List` are Blocks. Each Block is represented by a Plugin.
We have [many](http://github.com/codex-editor) ready-to-use Plugins and the [simple API](tools.md) for creation new ones.
### JSON as output
So how to use the Editor after [Installation](installation.md).
Outputs clear Blocks data via JSON
- Create new Blocks by Enter or with the Plus Button
- Press `TAB` or click on the Plus Button to view the Toolbox
- Press `TAB` again to leaf Toolbox and select a Block you need. Then press Enter.
```json
{
"blocks": [
{
"type": "paragraph",
"data": {
"text": "There is a first paragraph"
}
![](https://github.com/codex-editor/list/raw/master/assets/example.gif)
- Select text fragment and apply a style or insert a link from the Inline Toolbar
![](https://capella.pics/7ccbcfcd-1c49-4674-bea7-71021468a1bd.jpg)
- Use «three-dots» button on the right to open Block Settings. From here, you can move and delete a Block
or apply Tool's settings, if it provided. For example, set a Heading level or List style.
![](https://capella.pics/01a55381-46cd-47c7-b92e-34765434f2ca.jpg)
## Shortcuts
We really appreciate shortcuts. So there are few presets.
Action | Shortcut | Restrictions
-- | -- | --
`TAB` | Show/leaf a Toolbox. | On empty block
`SHIFT+TAB` | Leaf back a Toolbox. | While Toolbox is opened
`ENTER` | Create a Block | While Toolbox is opened and some Tool is selected
`CMD+B` | Bold style | On selection
`CMD+I` | Italic style | On selection
`CMD+K` | Insert a link | On selection
Also we support shortcuts on the all type of Tools. Specify a shortcut with the Tools configuration. For example:
```js
var editor = CodexEditor({
//...
tools: {
header: {
class: Header,
shortcut: 'CMD+SHIFT+H'
},
{
"type": "header",
"data": {
"text": "Aaand here is a Header",
"type": "H2"
}
},
...
]
}
list: {
class: List,
shortcut: 'CMD+SHIFT+L'
}
}
//...
});
```
### API oriented
Focused on the Plugins API that allows to create amazing Blocks
# Installation Guide
There are few steps to run CodeX Editor on your site.
1. [Load Editor's core](#load-editors-core)
2. [Load Tools](#load-tools)
3. [Initialize Editor's instance](#create-editor-instance)
## Load Editor's core
Firstly you need to get CodeX Editor itself. It is a [minified script](../build/codex-editor.js) with minimal available
Choose the most usable method of getting Editor for you.
- Node package
- Source from CDN
- Local file from project
### Node.js
Install the package via NPM or Yarn
```shell
npm i codex.editor --save-dev
```
Include module at your application
```javascript
const CodexEditor = require('codex.editor');
```
### Use from CDN
You can load specific version of package from [jsDelivr CDN](https://www.jsdelivr.com/package/npm/codex.editor).
`https://cdn.jsdelivr.net/npm/codex.editor@2.0.0`
Then require this script.
```html
<script src="..."></script>
```
### Save sources to project
Copy [codex-editor.js](../build/codex-editor.js) file to your project and load it.
```html
<script src="codex-editor.js"></script>
```
## Load Tools
Each Block at the CodeX Editor represented by [Tools](tools.md). There are simple external scripts with own logic. To start using the Editor, you should connect at least one Block Tool.
For example check out our [Paragraph](https://github.com/codex-editor/paragraph) Tool that represents simple text block.
Each Tool should have an installation guide. You can install Paragraph Tool via the same ways as an Editor (Node.js, CDN, local file).
Check [CodeX Editor's community](https://github.com/codex-editor) to see Tools examples.
**Example:** use Paragragh from CDN
```html
<script src="https://cdn.jsdelivr.net/npm/codex.editor.paragraph@2.0.3/dist/bundle.js"></script>
```
## Create Editor instance
Create an instance of CodeX Editor and pass [Configuration Object](../src/components/interfaces/editor-config.ts).
Minimal params is a `holderId`, `tools` list and `initialBlock` marker.
```html
<div id="codex-editor"></div>
```
You can create a simple Editor only with a default Paragraph Tool by passing a string with element's Id (wrapper for Editor) as a configuration param or use default `codex-editor`.
```javascript
var editor = new CodexEditor();
// equals
var editor = new CodexEditor('codex-editor');
````
Or pass a whole settings object.
```javascript
var editor = new CodexEditor({
/**
* Create a holder for the Editor and pass its ID
*/
holderId : 'codex-editor',
/**
* Available Tools list.
* Pass Tool's class or Settings object for each Tool you want to use
*/
tools: {
paragraph: {
class: Paragraph,
inlineToolbar : true
},
// ...
},
/**
* What Block will be inserted by default
*/
initialBlock : 'paragraph',
/**
* Previously saved data that should be rendered
*/
data: {}
});
```
## Saving Data
Call `editor.saver.save()` and handle returned Promise with saved data.
```javascript
editor.saver.save()
.then((savedData) => {
console.log(savedData);
});
```
## Example
Take a look at the [example.html](../example/example.html) to view more detailed examples.
### Native JS
Zero-dependency — use everywhere you want

1540
build/codex-editor.js Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

42
build/sprite.svg Normal file
View File

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg">
<symbol id="arrow-down" viewBox="0 0 14 14">
<path transform="matrix(1 0 0 -1 0 14)" d="M8.024 4.1v8.6a1.125 1.125 0 0 1-2.25 0V4.1L2.18 7.695A1.125 1.125 0 1 1 .59 6.104L6.103.588c.44-.439 1.151-.439 1.59 0l5.516 5.516a1.125 1.125 0 0 1-1.59 1.59L8.023 4.1z"/>
</symbol>
<symbol id="arrow-up" viewBox="0 0 14 14">
<path d="M8.024 4.1v8.6a1.125 1.125 0 0 1-2.25 0V4.1L2.18 7.695A1.125 1.125 0 1 1 .59 6.104L6.103.588c.44-.439 1.151-.439 1.59 0l5.516 5.516a1.125 1.125 0 0 1-1.59 1.59L8.023 4.1z"/>
</symbol>
<symbol id="bold" viewBox="0 0 13 15">
<path d="M5.996 13.9H1.752c-.613 0-1.05-.137-1.312-.412-.262-.275-.393-.712-.393-1.312V1.737C.047 1.125.18.684.449.416.718.147 1.152.013 1.752.013h4.5a10.5 10.5 0 0 1 1.723.123c.487.082.922.24 1.308.474a3.43 3.43 0 0 1 1.449 1.738c.132.363.199.747.199 1.151 0 1.39-.695 2.406-2.084 3.05 1.825.581 2.737 1.712 2.737 3.391 0 .777-.199 1.477-.596 2.099a3.581 3.581 0 0 1-1.61 1.378c-.424.177-.91.301-1.46.374-.549.073-1.19.109-1.922.109zm-.209-6.167H2.86v4.055h3.022c1.9 0 2.851-.686 2.851-2.056 0-.7-.246-1.21-.739-1.525-.492-.316-1.228-.474-2.207-.474zM2.86 2.125v3.59h2.577c.7 0 1.242-.066 1.624-.198a1.55 1.55 0 0 0 .876-.758c.158-.265.237-.562.237-.89 0-.702-.25-1.167-.748-1.398-.499-.23-1.26-.346-2.283-.346H2.86z"/>
</symbol>
<symbol id="cross" viewBox="0 0 237 237">
<path transform="rotate(45 280.675 51.325)" d="M191 191V73c0-5.523 4.477-10 10-10h25c5.523 0 10 4.477 10 10v118h118c5.523 0 10 4.477 10 10v25c0 5.523-4.477 10-10 10H236v118c0 5.523-4.477 10-10 10h-25c-5.523 0-10-4.477-10-10V236H73c-5.523 0-10-4.477-10-10v-25c0-5.523 4.477-10 10-10h118z"/>
</symbol>
<symbol id="dots" viewBox="0 0 18 4">
<g fill-rule="evenodd">
<circle cx="9" cy="2" r="2"/>
<circle cx="2" cy="2" r="2"/>
<circle cx="16" cy="2" r="2"/>
</g>
</symbol>
<symbol id="italic" viewBox="0 0 6 15">
<path d="M4 5.2l-1.368 7.474c-.095.518-.29.91-.585 1.175a1.468 1.468 0 0 1-1.01.398c-.379 0-.662-.136-.85-.407-.186-.272-.234-.66-.141-1.166L1.4 5.276c.093-.511.282-.896.567-1.155a1.43 1.43 0 0 1 .994-.389c.38 0 .668.13.867.389.199.259.256.618.172 1.08zm-.79-2.67c-.36 0-.648-.111-.863-.332-.215-.221-.286-.534-.212-.938.067-.366.253-.668.559-.905A1.57 1.57 0 0 1 3.673 0c.334 0 .612.107.831.322.22.215.292.527.217.938-.073.398-.256.709-.55.933a1.55 1.55 0 0 1-.961.336z"/>
</symbol>
<symbol id="link" viewBox="0 0 15 14">
<path transform="rotate(-45 11.83 6.678)" d="M11.332 4.013a51.07 51.07 0 0 1-2.28.001A1.402 1.402 0 0 0 7.7 2.25H3.65a1.4 1.4 0 1 0 0 2.8h.848c.206.86.693 1.61 1.463 2.25H3.65a3.65 3.65 0 1 1 0-7.3H7.7a3.65 3.65 0 0 1 3.632 4.013zM10.9 0h2a3.65 3.65 0 0 1 0 7.3H8.85a3.65 3.65 0 0 1-3.632-4.011A62.68 62.68 0 0 1 7.5 3.273 1.401 1.401 0 0 0 8.85 5.05h4.05a1.4 1.4 0 0 0 0-2.8h-.48C12.274 1.664 11.694.785 10.9 0z"/>
</symbol>
<symbol id="plus" viewBox="0 0 14 14">
<path d="M8.05 5.8h4.625a1.125 1.125 0 0 1 0 2.25H8.05v4.625a1.125 1.125 0 0 1-2.25 0V8.05H1.125a1.125 1.125 0 0 1 0-2.25H5.8V1.125a1.125 1.125 0 0 1 2.25 0V5.8z"/>
</symbol>
<symbol id="unlink" viewBox="0 0 16 18">
<path transform="rotate(-45 8.358 11.636)" d="M9.14 9.433c.008-.12-.087-.686-.112-.81a1.4 1.4 0 0 0-1.64-1.106l-3.977.772a1.4 1.4 0 0 0 .535 2.749l.935-.162s.019 1.093.592 2.223l-1.098.148A3.65 3.65 0 1 1 2.982 6.08l3.976-.773c1.979-.385 3.838.919 4.28 2.886.51 2.276-1.084 2.816-1.073 2.935.011.12-.394-1.59-1.026-1.696zm3.563-.875l2.105 3.439a3.65 3.65 0 0 1-6.19 3.868L6.47 12.431c-1.068-1.71-.964-2.295-.49-3.07.067-.107 1.16-1.466 1.48-.936-.12.036.9 1.33.789 1.398-.656.41-.28.76.13 1.415l2.145 3.435a1.4 1.4 0 0 0 2.375-1.484l-1.132-1.941c.42-.435 1.237-1.054.935-2.69zm1.88-2.256h3.4a1.125 1.125 0 0 1 0 2.25h-3.4a1.125 1.125 0 0 1 0-2.25zM11.849.038c.62 0 1.125.503 1.125 1.125v3.4a1.125 1.125 0 0 1-2.25 0v-3.4c0-.622.503-1.125 1.125-1.125z"/>
</symbol></svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

146
codex.js
View File

@ -1,146 +0,0 @@
/**
*
* Codex Editor
*
* @author Codex Team
*/
module.exports = (function (editor) {
'use strict';
editor.version = VERSION;
editor.scriptPrefix = 'cdx-script-';
var init = function () {
editor.core = require('./modules/core');
editor.tools = require('./modules/tools');
editor.ui = require('./modules/ui');
editor.transport = require('./modules/transport');
editor.renderer = require('./modules/renderer');
editor.saver = require('./modules/saver');
editor.content = require('./modules/content');
editor.toolbar = require('./modules/toolbar/toolbar');
editor.callback = require('./modules/callbacks');
editor.draw = require('./modules/draw');
editor.caret = require('./modules/caret');
editor.notifications = require('./modules/notifications');
editor.parser = require('./modules/parser');
editor.sanitizer = require('./modules/sanitizer');
editor.listeners = require('./modules/listeners');
editor.destroyer = require('./modules/destroyer');
editor.paste = require('./modules/paste');
};
/**
* @public
* holds initial settings
*/
editor.settings = {
tools : ['paragraph', 'header', 'picture', 'list', 'quote', 'code', 'twitter', 'instagram', 'smile'],
holderId : 'codex-editor',
// Type of block showing on empty editor
initialBlockPlugin: 'paragraph'
};
/**
* public
*
* Static nodes
*/
editor.nodes = {
holder : null,
wrapper : null,
toolbar : null,
inlineToolbar : {
wrapper : null,
buttons : null,
actions : null
},
toolbox : null,
notifications : null,
plusButton : null,
showSettingsButton: null,
showTrashButton : null,
blockSettings : null,
pluginSettings : null,
defaultSettings : null,
toolbarButtons : {}, // { type : DomEl, ... }
redactor : null
};
/**
* @public
*
* Output state
*/
editor.state = {
jsonOutput : [],
blocks : [],
inputs : []
};
/**
* @public
* Editor plugins
*/
editor.tools = {};
/**
* Initialization
* @uses Promise cEditor.core.prepare
* @param {Object} userSettings
* @param {Array} userSettings.tools list of plugins
* @param {String} userSettings.holderId Element's id to append editor
*
* Load user defined tools
* Tools must contain this important objects :
* @param {String} type - this is a type of plugin. It can be used as plugin name
* @param {String} iconClassname - this a icon in toolbar
* @param {Object} make - what should plugin do, when it is clicked
* @param {Object} appendCallback - callback after clicking
* @param {Element} settings - what settings does it have
* @param {Object} render - plugin get JSON, and should return HTML
* @param {Object} save - plugin gets HTML content, returns JSON
* @param {Boolean} displayInToolbox - will be displayed in toolbox. Default value is TRUE
* @param {Boolean} enableLineBreaks - inserts new block or break lines. Default value is FALSE
*
* @example
* - type : 'header',
* - iconClassname : 'ce-icon-header',
* - make : headerTool.make,
* - appendCallback : headerTool.appendCallback,
* - settings : headerTool.makeSettings(),
* - render : headerTool.render,
* - save : headerTool.save,
* - displayInToolbox : true,
* - enableLineBreaks : false
*/
editor.start = function (userSettings) {
init();
editor.core.prepare(userSettings)
// If all ok, make UI, bind events and parse initial-content
.then(editor.ui.prepare)
.then(editor.tools.prepare)
.then(editor.sanitizer.prepare)
.then(editor.paste.prepare)
.then(editor.transport.prepare)
.then(editor.renderer.makeBlocksFromData)
.then(editor.ui.saveInputs)
.catch(function (error) {
editor.core.log('Initialization failed with error: %o', 'warn', error);
});
};
return editor;
})({});

91
docs/api.md Normal file
View File

@ -0,0 +1,91 @@
# CodeX Editor API
Blocks have access to the public methods provided by CodeX Editor API Module. Plugin and Tune Developers
can use Editor API as they want.
## Api object description
Common API interface.
```js
export interface IAPI {
blocks: IBlocksAPI;
caret: ICaretAPI;
sanitizer: ISanitizerAPI;
toolbar: IToolbarAPI;
}
```
#### IBlocksAPI
Methods that working with Blocks
```swap(fromIndex, toIndex)``` - swaps two Blocks by their positions
```delete(blockIndex?: Number)``` - deletes Block with passed index
```getCurrentBlockIndex()``` - current Block index
```getBlockByIndex(index: Number)``` - returns Block with passed index
```getBlocksCount()``` - returns Blocks count
```stretchBlock(index: number, status: boolean)``` - make Block stretched
```insertNewBlock()``` - insert new Block after working place
#### ISanitizerAPI
```clean(taintString, config)``` - method uses HTMLJanitor to clean taint string.
CodeX Editor provides basic config without attributes, but you can inherit by passing your own config.
Usage:
```js
let taintString = '<div><p style="font-size: 5em;"><b></b>BlockWithText<a onclick="void(0)"></div>'
let customConfig = {
b: true,
p: {
style: true,
},
}
this.api.sanitizer.clean(taintString, customConfig);
```
### IToolbarAPI
Methods that working with Toolbar
```open()``` - Opens toolbar
```close()``` - Closes toolbar, toolbox and blockSettings if they are opened
### IEventsAPI
Methods that allows to subscribe on CodeX Editor events
```on(eventName: string, callback: Function)``` - subscribe callback on event
```off(eventName: string, callback: Function)``` - unsubscribe callback from event
```emit(eventName: string, data: object)``` - fires all subscribed callbacks with passed data
### IListenerAPI
Methods that allows to work with DOM listener. Useful when you forgot to remove listener.
Module collects all listeners and destroys automatically
```on(element: HTMLElement, eventType: string, handler: Function, useCapture?: boolean)``` - add event listener to HTML element
```off(element: HTMLElement, eventType: string, handler: Function)``` - remove event handler from HTML element
### Destroy API
If there are necessity to remove CodeX Editor instance from the page you can use `destroy()` method.
It makes following steps:
1. Clear the holder element by setting it\`s innerHTML to empty string
2. Remove all event listeners related to CodeX Editor
3. Delete all properties from instance object and set it\`s prototype to `null`
After executing the `destroy` method, editor inctance becomes an empty object. This way you will free occupied JS Heap on your page.

37
docs/caret.md Normal file
View File

@ -0,0 +1,37 @@
# CodeX Editor Caret Module
The `Caret` module contains methods working with caret. Uses [Range](https://developer.mozilla.org/en-US/docs/Web/API/Range) methods to navigate caret
between blocks.
Caret class implements basic Module class that holds User configuration
and default CodeX Editor instances
## Properties
## Methods
### setToBlock
```javascript
Caret.setToBlock(block, position, offset)
```
> Method gets Block instance and puts caret to the text node with offset
#### params
| Param | Type | Description|
| -------------|------ |:-------------:|
| block | Object | Block instance that BlockManager created|
| position | String | Can be 'start', 'end' or 'default'. Other values will be treated as 'default'. Shows position of the caret regarding to the Block.|
| offset | Number | caret offset regarding to the text node (Default: 0)|
### setToTheLastBlock
```javascript
Caret.setToTheLastBlock()
```
> sets Caret at the end of last Block
If last block is not empty, inserts another empty Block which is passed as initial

50
docs/events.md Normal file
View File

@ -0,0 +1,50 @@
# CodeX Editor Events Module
Module allows Developers to subscribe on events or trigger own events
## Methods
### On
```javascript
Events.on(eventName, callback)
```
> Method subscribes callback on event. It will be called when CodeX Editor emits this event
#### params
| Param | Type | Description|
| -------------|------ |:-------------:|
| eventName | String | event name|
| callback | Function | event callback|
### Off
```javascript
Events.off(eventName, callback)
```
> Method unsubscribes callback on event
#### params
| Param | Type | Description|
| -------------|------ |:-------------:|
| eventName | String | event name|
| callback | Function | event callback|
### Emit
```javascript
Events.emit(eventName, data)
```
> Method emits data to all subscribed callbacks
#### params
| Param | Type | Description|
| -------------|------ |:-------------:|
| eventName | String | event name|
| data | Object | any data|

185
docs/installation.md Normal file
View File

@ -0,0 +1,185 @@
# Installation Guide
There are few steps to run CodeX Editor on your site.
1. [Load Editor's core](#load-editors-core)
2. [Load Tools](#load-tools)
3. [Initialize Editor's instance](#create-editor-instance)
## Load Editor's core
Firstly you need to get CodeX Editor itself. It is a [minified script](../build/codex-editor.js) with minimal available
Choose the most usable method of getting Editor for you.
- Node package
- Source from CDN
- Local file from project
### Node.js
Install the package via NPM or Yarn
```shell
npm i codex.editor --save-dev
```
Include module at your application
```javascript
const CodexEditor = require('codex.editor');
```
### Use from CDN
You can load specific version of package from [jsDelivr CDN](https://www.jsdelivr.com/package/npm/codex.editor).
`https://cdn.jsdelivr.net/npm/codex.editor@2.0.0`
Then require this script.
```html
<script src="..."></script>
```
### Save sources to project
Copy [codex-editor.js](../build/codex-editor.js) file to your project and load it.
```html
<script src="codex-editor.js"></script>
```
## Load Tools
Each Block at the CodeX Editor represented by [Tools](tools.md). There are simple external scripts with own logic. For example check out our [Paragraph](https://github.com/codex-editor/paragraph) Tool that represents simple text block.
Each Tool should have an installation guide. You can install Paragraph Tool via the same ways as an Editor (Node.js, CDN, local file).
Check [CodeX Editor's community](https://github.com/codex-editor) to see Tools examples.
**Example:** use Paragragh from CDN
```html
<script src="https://cdn.jsdelivr.net/npm/codex.editor.paragraph@2.0.3/dist/bundle.js"></script>
```
## Create Editor instance
Create an instance of CodeX Editor and pass [Configuration Object](../src/components/interfaces/editor-config.ts).
Minimal params is a `holderId`, `tools` list and `initialBlock` marker.
```html
<div id="codex-editor"></div>
```
You can create a simple Editor only with a default Paragraph Tool by passing a string with element's Id (wrapper for Editor) as a configuration param or use default `codex-editor`.
```javascript
var editor = new CodexEditor();
// equals
var editor = new CodexEditor('codex-editor');
````
Or pass a whole settings object.
```javascript
var editor = new CodexEditor({
/**
* Create a holder for the Editor and pass its ID
*/
holderId : 'codex-editor',
/**
* Available Tools list.
* Pass Tool's class or Settings object for each Tool you want to use
*/
tools: {
paragraph: {
class: Paragraph,
inlineToolbar : true
},
// ...
},
/**
* What Block will be inserted by default
*/
initialBlock : 'paragraph',
/**
* Previously saved data that should be rendered
*/
data: {}
});
```
## Ready callback
CodeX Editor needs a bit time to initialize. It is an asynchronous action so it won't block execution of your main script.
If you need to know when editor instance is ready you can use one of following ways:
##### Pass `onReady` property to the configuration object.
It must be a function:
```javascript
var editor = new CodexEditor({
// Other configuration properties
/**
* onReady callback
*/
onReady: () => {console.log('CodeX Editor is ready to work!')}
});
```
#### Use `isReady` promise.
After you create new `CodexEditor` object it contains `isReady` property.
It is a Promise object resolved when editor is ready to work and rejected otherwise.
If there is an error during initialization `isReady` promise will be rejected with error message.
```javascript
var editor = new CodexEditor();
editor.isReady
.then(() => {
/** Do anything you need after editor initialization */
})
.catch((reason) => {
console.log(`CodeX Editor initialization failed because of ${reason}`)
});
```
You can use `async/await` to keep your code looking synchronous:
```javascript
var editor = new CodexEditor();
try {
await editor.isReady;
/** Do anything you need after editor initialization */
} catch (reason) {
console.log(`CodeX Editor initialization failed because of ${reason}`)
}
```
## Saving Data
Call `editor.saver.save()` and handle returned Promise with saved data.
```javascript
editor.saver.save()
.then((savedData) => {
console.log(savedData);
});
```
## Example
Take a look at the [example.html](../example/example.html) to view more detailed examples.

45
docs/sanitizer.md Normal file
View File

@ -0,0 +1,45 @@
# CodeX Editor Sanitizer Module
The `Sanitizer` module represents a set of methods that clears taint strings.
Uses lightweight npm package with simple API [html-janitor](https://www.npmjs.com/package/html-janitor)
Sanitizer class implements basic Module class that holds User configuration
and default CodeX Editor instances
You can read more about Module class [here]()
## Properties
Default Editor Sanitizer configuration according to the html-janitor API
```javascript
defaultConfig
```
Custom User configuration which passed on Editor initialization. Data type must be according to the html-janitor API
```javascript
sanitizerConfig
```
Property that holds an instance used in Module
```javascript
sanitizerInstance
```
## Methods
### clean
```javascript
clean(taintString, customConfig)
```
> Cleans up the passed taint string
#### params
| Param | Type | Description|
| -------------|------ |:-------------:|
| taintString | String | string that needs to be cleaned|
| customConfig | Object | Can be passed new config per usage (Default: uses default configuration)|

91
docs/toolbar-settings.md Normal file
View File

@ -0,0 +1,91 @@
# CodeX Editor Toolbar Block Settings Module
Toolbar Module has space for Block settings. Settings divided into:
- space for plugin's settings, that is described by «Plugin»'s Developer
- space for default settings. This option is also can be implemented and expanded
They difference between zones is that the first option is specified by plugin
and each Block can have different options, when second option is for every Block
regardless to the plugin's option.
### Let's look the examples:
«Plugin»'s Developers need to expand «renderSettings» method that returns HTML.
Every user action will be handled by itself. So, you can easily write
callbacks that switches your content or makes better. For more information
read [Tools](tools.md).
---
«Tune»'s Developers need to implement core-provided interface to develop
tunes that will be appeared in Toolbar default settings zone.
Tunes must expand two important methods:
- `render()` - returns HTML and it is appended to the default settings zone
- `save()` - extracts important information to be saved
No restrictions. Handle user action by yourself
Create Class that implements block-tune.ts
Your Tune's constructor gets argument as object and it includes:
- {Object} api - object contains public methods from modules. @see [API](api.md)
- {Object} settings - settings contains block default state.
This object could have information about cover, anchor and so on.
Example on TypeScript:
```js
import IBlockTune from './block-tune';
export default class YourCustomTune implements IBlockTune {
public constructor({api, settings}) {
this.api = api;
this.settings = settings;
}
render() {
let someHTML = '...';
return someHTML;
}
save() {
// Return the important data that needs to be saved
return object
}
someMethod() {
// moves current block down
this.api.blocks.moveDown();
}
}
```
Example on ES6
```js
export default class YourCustomTune {
constructor({api, settings}) {
this.api = api;
this.settings = settings;
}
render() {
let someHTML = '...';
return someHTML;
}
save() {
// Return the important data that needs to be saved
return object
}
someMethod() {
// moves current block down
this.api.blocks.moveDown();
}
}
```

105
docs/tools-inline.md Normal file
View File

@ -0,0 +1,105 @@
# Tools for the Inline Toolbar
Similar with [Tools](tools.md) represented Blocks, you can create Tools for the Inline Toolbar. It will work with
selected fragment of text. The simplest example is `bold` or `italic` Tools.
## Base structure
First of all, Tool's class should have a `isInline` property (static getter) set as `true`.
After that Inline Tool should implement next methods.
- `render()` — create a button
- `surround()` — works with selected range
- `checkState()` — get Tool's activated state by selected range
Also, you can provide optional methods
- `renderActions()` — create additional element below the buttons
- `clear()` — clear Tool's stuff on opening/closing of Inline Toolbar
- `shortcut()` — shortcut that handles Tool
At the constructor of Tool's class exemplar you will accept an object with the [API](api.md) as a parameter.
---
### render()
Method that returns button to append at the Inline Toolbar
#### Parameters
Method does not accept any parameters
#### Return value
type | description |
-- | -- |
`HTMLElement` | element that will be added to the Inline Toolbar |
---
### surround(range: Range)
Method that accepts selected range and wrap it somehow
#### Parameters
name | type | description |
-- |-- | -- |
range | Range | first range of current Selection |
#### Return value
There is no return value
---
### checkState(selection: Selection)
Get Selection and detect if Tool was applied. For example, after that Tool can highlight button or show some details.
#### Parameters
name | type | description |
-- |-- | -- |
selection | Selection | current Selection |
#### Return value
type | description |
-- | -- |
`Boolean` | `true` if Tool is active, otherwise `false` |
---
### renderActions()
Optional method that returns additional Element with actions.
For example, input for the 'link' tool or textarea for the 'comment' tool.
It will be places below the buttons list at Inline Toolbar.
#### Parameters
Method does not accept any parameters
#### Return value
type | description |
-- | -- |
`HTMLElement` | element that will be added to the Inline Toolbar |
---
### clear()
Optional method that will be called on opening/closing of Inline Toolbar.
Can contain logic for clearing Tool's stuff, such as inputs, states and other.
#### Parameters
Method does not accept any parameters
#### Return value
Method should not return a value.

164
docs/tools.md Normal file
View File

@ -0,0 +1,164 @@
# 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()
Each Tool's instance called with an params object.
| Param | Type | Description |
| ------ | ------------------- | ----------------------------------------------- |
| api | [`IAPI`][iapi-link] | CodeX Editor's API methods |
| config | `object` | Special configuration params passed in «config» |
| data | `object` | Data to be rendered in this Tool |
[iapi-link]: ../src/components/interfaces/api.ts
#### Example
```javascript
constructor({data, config, api}) {
this.data = data;
this.api = api;
this.config = config;
// ...
}
```
### render()
### save()
### validate() _optional_
### merge() _optional_
Method that specifies how to merge two `Blocks` of the same type, for example on `Backspace` keypress.
Method does accept data object in same format as the `Render` and it should provide logic how to combine new
data with the currently stored value.
### Internal Tool Settings
Options that Tool can specify. All settings should be passed as static properties of Tool's class.
| Name | Type | Default Value | Description |
| -- | -- | -- | -- |
| `displayInToolbox` | _Boolean_ | `false` | Pass `true` to display this `Tool` in the Editor's `Toolbox` |
| `toolboxIcon` | _String_ | — | Icon for Toolbox HTML string |
| `enableLineBreaks` | _Boolean_ | `false` | With this option, CodeX Editor won't handle Enter keydowns. Can be helpful for Tools like `<code>` where line breaks should be handled by default behaviour. |
| `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` |
| `isInline` | _Boolean_ | `false` | Describes Tool as a [Tool for the Inline Toolbar](tools-inline.md) |
### User configuration
All Tools can be configured by users. You can set up some of available settings along with Tool's class
to the `tools` property of Editor Config.
```javascript
var editor = new CodexEditor({
holderId : 'codex-editor',
tools: {
text: {
class: Text,
inlineToolbar : true,
// other settings..
},
header: Header
},
initialBlock : 'text',
});
```
There are few options available by CodeX Editor.
| Name | Type | Default Value | Description |
| -- | -- | -- | -- |
| `inlineToolbar` | _Boolean/Array_ | `false` | Pass `true` to enable the Inline Toolbar with all Tools, or pass an array with specified Tools list |
| `config` | _Object_ | `null` | User's configuration for Plugin.
### Paste handling
CodeX Editor handles paste on Blocks and provides API for Tools to process the pasted data.
When user pastes content into Editor, pasted content is splitted into blocks.
1. If plain text has been pasted, it is split by new line characters
2. If HTML string has been pasted, it is split by block tags
Also Editor API allows you to define RegExp patterns to substitute them by your data.
To provide paste handling for your Tool you need to define static getter `onPaste` in Tool class.
`onPaste` getter should return object with fields described below.
##### HTML tags handling
To handle pasted HTML elements object returned from `onPaste` getter should contain following fields:
| Name | Type | Description |
| -- | -- | -- |
| `handler(content: HTMLElement)` | `Function` | _Optional_. Pasted HTML elements handler. Gets one argument `content`. `content` is HTML element extracted from pasted data. Handler should return the same object as Tool's `save` method |
| `tags` | `String[]` | _Optional_. Should contain all tag names you want to be extracted from pasted data and be passed to your `handler` method |
For correct work you MUST provide `onPaste.handler` at least for `initialBlock` Tool.
> Example
Header tool can handle `H1`-`H6` tags using paste handling API
```javascript
static get onPaste() {
return {
tags: ['H1', 'H2', 'H3', 'H4', 'H5', 'H6'],
handler: (element) => ({
type: element.tagName,
text: element.innerHTML
})
}
}
```
> One tag can be handled by one Tool only.
##### Patterns handling
Your Tool can analyze text by RegExp patterns to substitute pasted string with data you want. Object returned from `onPaste` getter should contain following fields to use patterns:
| Name | Type | Description |
| -- | -- | -- |
| `patterns` | `Object` | _Optional_. `patterns` object contains RegExp patterns with their names as object's keys |
| `patternHandler(text: string, key: string)` | `Function` | _Optional_. Gets pasted string and pattern name. Should return the same object as Tool `save` method |
**Note** Editor will check pattern's full match, so don't forget to handle all available chars in there.
Pattern will be processed only if paste was on `initialBlock` Tool and pasted string length is less than 450 characters.
> Example
You can handle youtube links and insert embeded video instead:
```javascript
static get onPaste() {
return {
patterns: {
youtube: /http(?:s?):\/\/(?:www\.)?youtu(?:be\.com\/watch\?v=|\.be\/)([\w\-\_]*)(&(amp;)?[\w\?=]*)?/
},
patternHandler: (text, key) => {
const urlData = Youtube.onPaste.patterns[key].exec(text);
return {
iframe: Youtube.makeEmbededFromURL(urlData)
};
}
}
}
```
> Both `onPaste.handler` and `onPaste.patternHandler` can be `async` or return a `Promise`.
### Sanitize

58
docs/usage.md Normal file
View File

@ -0,0 +1,58 @@
# So how to use CodeX Editor
## Basics
CodeX Editor is a Block-Styled editor. Blocks is a structural units, of which the Entry is composed.
For example, `Paragraph`, `Heading`, `Image`, `Video`, `List` are Blocks. Each Block is represented by a Plugin.
We have [many](http://github.com/codex-editor) ready-to-use Plugins and the [simple API](tools.md) for creation new ones.
So how to use the Editor after [Installation](installation.md).
- Create new Blocks by Enter or with the Plus Button
- Press `TAB` or click on the Plus Button to view the Toolbox
- Press `TAB` again to leaf Toolbox and select a Block you need. Then press Enter.
![](https://github.com/codex-editor/list/raw/master/assets/example.gif)
- Select text fragment and apply a style or insert a link from the Inline Toolbar
![](https://capella.pics/7ccbcfcd-1c49-4674-bea7-71021468a1bd.jpg)
- Use «three-dots» button on the right to open Block Settings. From here, you can move and delete a Block
or apply Tool's settings, if it provided. For example, set a Heading level or List style.
![](https://capella.pics/01a55381-46cd-47c7-b92e-34765434f2ca.jpg)
## Shortcuts
We really appreciate shortcuts. So there are few presets.
Action | Shortcut | Restrictions
-- | -- | --
`TAB` | Show/leaf a Toolbox. | On empty block
`SHIFT+TAB` | Leaf back a Toolbox. | While Toolbox is opened
`ENTER` | Create a Block | While Toolbox is opened and some Tool is selected
`CMD+B` | Bold style | On selection
`CMD+I` | Italic style | On selection
`CMD+K` | Insert a link | On selection
Also we support shortcuts on the all type of Tools. Specify a shortcut with the Tools configuration. For example:
```js
var editor = CodexEditor({
//...
tools: {
header: {
class: Header,
shortcut: 'CMD+SHIFT+H'
},
list: {
class: List,
shortcut: 'CMD+SHIFT+L'
}
}
//...
});
```

View File

@ -1,244 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>CodeX Editor example</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
font-size: 14px;
line-height: 1.5em;
}
</style>
</head>
<body>
<div id="codex-editor"></div>
</body>
<script src="codex-editor.js?v=108"></script>
<link rel="stylesheet" href="codex-editor.css?v=11000">
<script src="plugins/paragraph/paragraph.js?v=100"></script>
<link rel="stylesheet" href="plugins/paragraph/paragraph.css">
<script src="plugins/header/header.js"></script>
<link rel="stylesheet" href="plugins/header/header.css">
<script src="plugins/code/code.js"></script>
<link rel="stylesheet" href="plugins/code/code.css">
<script src="plugins/link/link.js"></script>
<link rel="stylesheet" href="plugins/link/link.css">
<script src="plugins/quote/quote.js"></script>
<link rel="stylesheet" href="plugins/quote/quote.css">
<script src="plugins/list/list.js"></script>
<link rel="stylesheet" href="plugins/list/list.css">
<script src="plugins/image/image.js"></script>
<link rel="stylesheet" href="plugins/image/image.css">
<script src="plugins/instagram/instagram.js"></script>
<link rel="stylesheet" href="plugins/instagram/instagram.css">
<script src="plugins/twitter/twitter.js"></script>
<link rel="stylesheet" href="plugins/twitter/twitter.css">
<script src="plugins/embed/embed.js"></script>
<link rel="stylesheet" href="plugins/embed/embed.css">
<script src="plugins/raw/raw.js"></script>
<link rel="stylesheet" href="plugins/raw/raw.css">
<script src="plugins/attaches/attaches.js"></script>
<link rel="stylesheet" href="plugins/attaches/attaches.css">
<script>
codex.editor.start({
holderId : "codex-editor",
initialBlockPlugin : 'paragraph',
// placeholder: 'Прошлой ночью мне приснилось...',
hideToolbar: false,
tools : {
paragraph: {
type: 'paragraph',
iconClassname: 'ce-icon-paragraph',
render: paragraph.render,
validate: paragraph.validate,
save: paragraph.save,
destroy: paragraph.destroy,
allowedToPaste: true,
showInlineToolbar: true,
allowRenderOnPaste: true
},
header: {
type: 'header',
iconClassname: 'ce-icon-header',
appendCallback: header.appendCallback,
makeSettings: header.makeSettings,
render: header.render,
validate: header.validate,
save: header.save,
destroy: header.destroy,
displayInToolbox: true
},
code: {
type: 'code',
iconClassname: 'ce-icon-code',
make: code.make,
appendCallback: null,
settings: null,
render: code.render,
validate: code.validate,
save: code.save,
destroy: code.destroy,
displayInToolbox: true,
enableLineBreaks: true
},
link: {
type: 'link',
iconClassname: 'ce-icon-link',
prepare: link.prepare,
make: link.makeNewBlock,
appendCallback: link.appendCallback,
render: link.render,
validate: link.validate,
save: link.save,
destroy: link.destroy,
displayInToolbox: true,
enableLineBreaks: true
},
list: {
type: 'list',
iconClassname: 'ce-icon-list-bullet',
make: list.make,
appendCallback: null,
makeSettings: list.makeSettings,
render: list.render,
validate: list.validate,
save: list.save,
destroy: list.destroy,
displayInToolbox: true,
showInlineToolbar: true,
enableLineBreaks: true,
allowedToPaste: true,
},
quote: {
type: 'quote',
iconClassname: 'ce-icon-quote',
makeSettings: quote.makeSettings,
prepare: quote.prepare,
render: quote.render,
validate: quote.validate,
save: quote.save,
destroy: quote.destroy,
displayInToolbox: true,
enableLineBreaks: true,
showInlineToolbar: true,
allowedToPaste: true,
config : {
defaultStyle : 'withPhoto'
}
},
image_extended: {
type: 'image_extended',
iconClassname: 'ce-icon-picture',
appendCallback: image.appendCallback,
prepare: image.prepare,
makeSettings: image.makeSettings,
render: image.render,
save: image.save,
destroy: image.destroy,
isStretched: true,
showInlineToolbar: true,
displayInToolbox: true,
renderOnPastePatterns: image.pastePatterns,
config: {
uploadImage : '/writing/uploadImage',
uploadFromUrl : '/club/fetch'
}
},
instagram: {
type: 'instagram',
iconClassname: 'ce-icon-instagram',
prepare: instagram.prepare,
render: instagram.render,
validate: instagram.validate,
save: instagram.save,
destroy: instagram.destroy,
renderOnPastePatterns: instagram.pastePatterns,
},
tweet: {
type: 'tweet',
iconClassname: 'ce-icon-twitter',
prepare: twitter.prepare,
render: twitter.render,
validate: twitter.validate,
save: twitter.save,
destroy: twitter.destroy,
showInlineToolbar : true,
renderOnPastePatterns: twitter.pastePatterns,
config : {
fetchUrl : ''
}
},
video_extended: {
type: 'video_extended',
make: embed.make,
render: embed.render,
save: embed.save,
destroy: embed.destroy,
validate: embed.validate,
renderOnPastePatterns: embed.pastePatterns,
},
raw: {
type: 'raw',
displayInToolbox: true,
iconClassname: 'raw-plugin-icon',
render: rawPlugin.render,
save: rawPlugin.save,
validate: rawPlugin.validate,
destroy: rawPlugin.destroy,
enableLineBreaks: true,
allowPasteHTML: true
},
attaches: {
type: 'attaches',
displayInToolbox: true,
iconClassname: 'cdx-attaches__icon',
prepare: cdxAttaches.prepare,
render: cdxAttaches.render,
save: cdxAttaches.save,
validate: cdxAttaches.validate,
destroy: cdxAttaches.destroy,
appendCallback: cdxAttaches.appendCallback,
config: {
fetchUrl: '/test',
maxSize: 50000,
}
},
},
data : {
id: +new Date(),
items: [
{
type : 'header',
data : {
text : 'Привет от CodeX'
}
},
{
type : 'paragraph',
data : {
text : 'Пишите нам на team@ifmo.su'
}
},
],
count: 3
}
});
</script>
</html>

118
example/assets/demo.css Normal file
View File

@ -0,0 +1,118 @@
/**
* Styles for the example page
*/
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
font-size: 14px;
line-height: 1.5em;
margin: 0;
}
.ce-example {
font-size: 15px;
}
.ce-example__header {
border-bottom: 1px solid #E8E8EB;
height: 50px;
line-height: 50px;
display: flex;
padding: 0 30px;
margin-bottom: 30px;
}
.ce-example__header a {
color: inherit;
text-decoration: none;
}
.ce-example__header-logo {
font-weight: bold;
}
.ce-example__header-menu {
margin-left: auto;
}
.ce-example__header-menu a {
margin-left: 20px;
}
.ce-example__content {
max-width: 1100px;
margin: 0 auto;
}
.ce-example__output {
background: #1B202B;
overflow-x: auto;
padding: 0 30px;
}
.ce-example__output-content {
max-width: 650px;
margin: 30px auto;
color: #ABADC3;
font-family: 'PT Mono', Menlo, Monaco, Consolas, Courier New, monospace;
font-size: 13.3px;
}
.ce-example__output-content:empty {
display: none;
}
.ce-example__button {
display: block;
margin: 50px auto;
max-width: 180px;
background: #4A9DF8;
padding: 17px 30px;
box-shadow: 0 6px 4px -4px rgba(137, 207, 255, 0.77);
cursor: pointer;
border-radius: 31px;
color: #fff;
font-family: 'PT Mono', Menlo, Monaco, Consolas, Courier New, monospace;
text-align: center;
}
.ce-example__button:hover {
background: #3D8DE5;
}
.ce-example__output-footer {
padding: 30px 0;
font-size: 14.2px;
letter-spacing: 0.3px;
text-align: center;
}
.ce-example__output-footer a {
color: #fff;
text-decoration: none;
}
@media all and (max-width: 680px){
.ce-example__header,
.ce-example__content{
padding: 0 20px;
}
}
/**
* JSON highlighter
*/
.sc_attr {
color: rgb(148, 162, 192);
}
.sc_key {
color: rgb(190, 213, 255);
}
.sc_toolname {
color: rgb(15, 205, 251);
}
.sc_tag {
color: rgb(4, 131, 216);
}
.sc_bool {
color: rgb(247, 60, 173);
}

View File

@ -0,0 +1,45 @@
/**
* Module to compose output JSON preview
*/
const cPreview = (function (module) {
/**
* Shows JSON in pretty preview
* @param {object} output - what to show
* @param {Element} holder - where to show
*/
module.show = function(output, holder) {
/** Make JSON pretty */
output = JSON.stringify( output, null, 4 );
/** Encode HTML entities */
output = encodeHTMLEntities( output );
/** Stylize! */
output = stylize( output );
holder.innerHTML = output;
};
/**
* Converts '>', '<', '&' symbols to entities
*/
function encodeHTMLEntities(string) {
return string.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
}
/**
* Some styling magic
*/
function stylize(string) {
/** Stylize JSON keys */
string = string.replace( /"(\w+)"\s?:/g, '"<span class=sc_key>$1</span>" :');
/** Stylize tool names */
string = string.replace( /"(text|quote|list|header|link|code|image|delimiter)"/g, '"<span class=sc_toolname>$1</span>"');
/** Stylize HTML tags */
string = string.replace( /(&lt;[\/a-z]+(&gt;)?)/gi, '<span class=sc_tag>$1</span>' );
/** Stylize strings */
string = string.replace( /"([^"]+)"/gi, '"<span class=sc_attr>$1</span>"' );
/** Boolean/Null */
string = string.replace( /\b(true|false|null)\b/gi, '<span class=sc_bool>$1</span>' );
return string;
}
return module;
})({});

231
example/example.html Normal file
View File

@ -0,0 +1,231 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>CodeX Editor 🤩🧦🤨 example</title>
<link href="https://fonts.googleapis.com/css?family=PT+Mono" rel="stylesheet">
<link href="assets/demo.css" rel="stylesheet">
<script src="assets/json-preview.js"></script>
</head>
<body>
<div class="ce-example">
<div class="ce-example__header">
<a class="ce-example__header-logo" href="https://ifmo.su/editor">CodeX Editor 🤩🧦🤨</a>
<div class="ce-example__header-menu">
<a href="https://github.com/codex-editor" target="_blank">Plugins</a>
</div>
</div>
<div class="ce-example__content">
<div id="codex-editor"></div>
<div class="ce-example__button" id="saveButton">
editor.saver.save()
</div>
</div>
<div class="ce-example__output">
<pre class="ce-example__output-content" id="output"></pre>
<div class="ce-example__output-footer">
<a href="https://ifmo.su" style="font-weight: bold">Made by CodeX</a>
</div>
</div>
</div>
<!-- Load Tools -->
<!--
You can upload Tools to your project's directory and use as in example below.
Also you can load each Tool from CDN or use NPM/Yarn packages.
Read more in Tool's README file. For example:
https://github.com/codex-editor/header#installation
-->
<script src="./tools/header/dist/bundle.js"></script><!-- Header -->
<script src="./tools/simple-image/dist/bundle.js"></script><!-- Simple Image -->
<script src="./tools/delimiter/dist/bundle.js"></script><!-- Delimiter -->
<script src="./tools/list/dist/bundle.js"></script><!-- List -->
<script src="./tools/quote/dist/bundle.js"></script><!-- Quote -->
<script src="./tools/inline-code/dist/bundle.js"></script><!-- Inline Code -->
<!-- Load CodeX Editor's Core -->
<script src="../build/codex-editor.js"></script>
<!-- Initialization -->
<script>
/**
* Saving button
*/
const saveButton = document.getElementById('saveButton');
/**
* To initialize the Editor, create a new instance with configuration object
* @see docs/installation.md for mode details
*/
var editor = new CodexEditor({
/**
* Wrapper of Editor
*/
holderId: 'codex-editor',
/**
* Tools list
*/
tools: {
/**
* Each Tool is a Plugin. Pass them via 'class' option with necessary settings {@link docs/tools.md}
*/
header: {
class: Header,
inlineToolbar: ['link'],
config: {
placeholder: 'Header'
}
},
/**
* Or pass class directly without any configuration
*/
image: SimpleImage,
list: {
class: List,
inlineToolbar: true
},
quote: {
class: Quote,
inlineToolbar: true,
config: {
quotePlaceholder: 'Enter a quote',
captionPlaceholder: 'Quote\'s author',
},
},
delimiter: Delimiter,
inlineCode: {
class: InlineCode,
shortcut: 'CMD+SHIFT+M'
},
},
/**
* This Tool will be used as default
*/
// initialBlock: 'paragraph',
/**
* Initial Editor data
*/
data: {
blocks: [
{
type: "header",
data: {
text: "CodeX Editor",
level: 2
}
},
{
type : 'paragraph',
data : {
text : 'Привет. Перед вами наш обновленный редактор. На этой странице вы можете проверить его в действии — попробуйте отредактировать или дополнить материал. Код страницы содержит пример подключения и простейшей настройки.'
}
},
{
type: "header",
data: {
text: "О редакторе",
level: 3
}
},
{
type : 'list',
data : {
items : [
'Это блочный редактор',
'На выводе отдает чистые данные',
'Имеет гибкие настройки и простой API',
],
style: 'unordered'
}
},
{
type: "header",
data: {
text: "Что значит «блочный» редактор",
level: 3
}
},
{
type : 'paragraph',
data : {
text : 'Блоки — это структурные элементы, из которых состоит статья. Например <span class="inline-code">Параграф</span>, <span class="inline-code">Заголовок</span>, <span class="inline-code">Изображение</span>, <span class="inline-code">Видео</span> — это все Блоки. В CodeX Editor каждый Блок определяется плагином. Есть много готовых Блоков и простой API для создания новых. Например, вы можете создать Блок для Твиттера, Инстаграма, Опроса, Игры или CTA-кнопки.'
}
},
{
type: "header",
data: {
text: "Что значит «чистые данные»",
level: 3
}
},
{
type : 'paragraph',
data : {
text : 'В отличие от WYSIWYG-редакторов, CodeX Editor возвращает не сгенерированный HTML-код, включающий и содержание и оформление статьи, а JSON с данными о каждом Блоке. Пример таких данных находится внизу этой страницы.'
}
},
{
type : 'paragraph',
data : {
text : 'Полученные данные можно использовать как угодно: выводить в вебе, рендерить в нативных мобильных приложениях, передавать в Instant Articles или Google AMP, использовать для генерации аудио-версии и тд.'
}
},
{
type : 'paragraph',
data : {
text : 'Помимо этого, данные о Блоках удобно очищать, фильтровать и обрабатывать на бэкенде.'
}
},
{
type: 'image',
data: {
url : 'https://ifmo.su/upload/redactor_images/o_e48549d1855c7fc1807308dd14990126.jpg',
caption: '',
stretched: false,
withBorder: true,
withBackground: false,
}
},
{
type : 'delimiter',
data : {}
},
{
type : 'paragraph',
data : {
text : 'Мы работали над этим редактором более двух лет. В отладке принимали участие известные медиа-проекты: vc.ru, TJ, DTF — с их помощью удалось найти и исправить много ошибок, стабилизировать ядро. Вместе с этим мы развивали API, который позволяет создавать плагины под любые задачи. Надеемся, вам понравится 😏'
}
},
]
},
onReady: function(){
saveButton.click();
}
});
/**
* Saving example
*/
saveButton.addEventListener('click', function () {
editor.saver.save().then((savedData) => {
cPreview.show(savedData, document.getElementById("output"));
});
});
</script>
</body>
</html>

@ -0,0 +1 @@
Subproject commit 489b87499ad9c60100e3dd0d832c78e3127536a5

1
example/tools/header Submodule

@ -0,0 +1 @@
Subproject commit 92e5373cf8826492a96b6ca5070552ddf6aa749d

@ -0,0 +1 @@
Subproject commit 286b1e1a0d37e17175ebc28b00f8d4e1c68497a9

1
example/tools/list Submodule

@ -0,0 +1 @@
Subproject commit 7c5fb17b11056171d121b7ce040b86b091b483bc

@ -0,0 +1 @@
Subproject commit 67af23696c591b6dc922b3c6c1b43070293281ea

1
example/tools/quote Submodule

@ -0,0 +1 @@
Subproject commit 293149cac0f352fc4debdd62558ca14e0448889a

@ -0,0 +1 @@
Subproject commit b3ba0e5de15bed3d4354b4bf23f63158bd4167dd

Binary file not shown.

View File

@ -1,72 +0,0 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>Copyright (C) 2016 by original authors @ fontello.com</metadata>
<defs>
<font id="codex-editor" horiz-adv-x="1000" >
<font-face font-family="codex-editor" font-weight="400" font-stretch="normal" units-per-em="1000" ascent="850" descent="-150" />
<missing-glyph horiz-adv-x="1000" />
<glyph glyph-name="instagram" unicode="&#xe800;" d="M690 350q0 26-6 50l176 0 0-344q0-56-39-96t-95-40l-592 0q-56 0-95 40t-39 96l0 344 174 0q-4-32-4-50 0-106 76-183t184-77q106 0 183 77t77 183z m36 430q56 0 95-39t39-95l0-146-218 0q-78 110-212 110-138 0-212-110l-218 0 0 146q0 56 39 95t95 39l592 0z m64-166l0 72q0 24-24 24l-72 0q-24 0-24-24l0-72q0-8 7-16t17-8l72 0q24 0 24 24z m-200-264q0-66-47-113t-113-47-113 47-47 113q0 68 47 114t113 46 113-46 47-114z" horiz-adv-x="860" />
<glyph glyph-name="picture" unicode="&#xe801;" d="M357 529q0-45-31-76t-76-32-76 32-31 76 31 75 76 32 76-32 31-75z m572-215v-250h-786v107l178 179 90-89 285 285z m53 393h-893q-7 0-12-5t-6-13v-678q0-7 6-13t12-5h893q7 0 13 5t5 13v678q0 8-5 13t-13 5z m89-18v-678q0-37-26-63t-63-27h-893q-36 0-63 27t-26 63v678q0 37 26 63t63 27h893q37 0 63-27t26-63z" horiz-adv-x="1071.4" />
<glyph glyph-name="cog" unicode="&#xe802;" d="M911 295l-133-56q-8-22-12-31l55-133-79-79-135 53q-9-4-31-12l-55-134-112 0-56 133q-11 4-33 13l-132-55-78 79 53 134q-1 3-4 9t-6 12-4 11l-131 55 0 112 131 56 14 33-54 132 78 79 133-54q22 9 33 13l55 132 112 0 56-132q14-5 31-13l133 55 80-79-54-135q6-12 12-30l133-56 0-112z m-447-111q69 0 118 48t49 118-49 119-118 50-119-50-49-119 49-118 119-48z" horiz-adv-x="928" />
<glyph glyph-name="link" unicode="&#xe803;" d="M812 171q0 23-15 38l-116 116q-16 16-38 16-24 0-40-18 1-1 10-10t12-12 9-11 7-14 2-15q0-23-16-38t-38-16q-8 0-15 2t-14 7-11 9-12 12-10 10q-19-17-19-40 0-23 16-38l115-116q15-15 38-15 22 0 38 15l82 81q15 16 15 37z m-392 394q0 22-15 38l-115 115q-16 16-38 16-22 0-38-15l-82-82q-16-15-16-37 0-22 16-38l116-116q15-15 38-15 23 0 40 17-2 2-11 11t-12 12-8 10-7 14-2 16q0 22 15 38t38 15q9 0 16-2t14-7 10-8 12-12 11-11q18 17 18 41z m500-394q0-67-48-113l-82-81q-46-47-113-47-68 0-114 48l-115 115q-46 47-46 114 0 68 49 116l-49 49q-48-49-116-49-67 0-114 47l-116 116q-47 47-47 114t47 113l82 82q47 46 114 46 67 0 114-47l114-116q47-46 47-113 0-69-49-117l49-49q48 49 116 49 67 0 114-47l116-116q47-47 47-114z" horiz-adv-x="928.6" />
<glyph glyph-name="unlink" unicode="&#xe804;" d="M245 141l-143-143q-5-5-13-5-6 0-13 5-5 5-5 13t5 13l143 142q6 5 13 5t13-5q5-5 5-12t-5-13z m94-23v-179q0-8-5-13t-13-5-12 5-5 13v179q0 8 5 13t12 5 13-5 5-13z m-125 125q0-8-5-13t-13-5h-178q-8 0-13 5t-5 13 5 13 13 5h178q8 0 13-5t5-13z m706-72q0-67-48-113l-82-81q-46-47-113-47-68 0-114 48l-186 187q-12 11-24 31l134 10 152-153q15-15 38-15t38 15l82 81q15 16 15 37 0 23-15 38l-153 154 10 133q20-12 31-23l188-188q47-48 47-114z m-345 404l-133-10-152 153q-16 16-38 16-22 0-38-15l-82-82q-16-15-16-37 0-22 16-38l153-153-10-134q-20 12-32 24l-187 187q-47 48-47 114 0 67 47 113l82 82q47 46 114 46 67 0 114-47l186-187q12-12 23-32z m354-46q0-8-5-13t-13-5h-179q-8 0-13 5t-5 13 5 12 13 5h179q8 0 13-5t5-12z m-304 303v-178q0-8-5-13t-13-5-13 5-5 13v178q0 8 5 13t13 5 13-5 5-13z m227-84l-143-143q-6-5-13-5t-12 5q-5 6-5 13t5 13l142 143q6 5 13 5t13-5q5-6 5-13t-5-13z" horiz-adv-x="928.6" />
<glyph glyph-name="code" unicode="&#xe805;" d="M344 69l-28-28q-5-5-12-5t-13 5l-260 260q-6 6-6 13t6 13l260 260q5 6 13 6t12-6l28-28q6-5 6-13t-6-12l-219-220 219-219q6-6 6-13t-6-13z m330 596l-208-721q-2-7-9-11t-13-1l-34 9q-8 3-11 9t-2 14l208 720q3 8 9 11t13 2l35-10q7-2 11-9t1-13z m367-364l-260-260q-6-5-13-5t-13 5l-28 28q-5 6-5 13t5 13l219 219-219 220q-5 5-5 12t5 13l28 28q6 6 13 6t13-6l260-260q5-5 5-13t-5-13z" horiz-adv-x="1071.4" />
<glyph glyph-name="quote" unicode="&#xe806;" d="M146 680q146 0 184-146 38-140-40-302-80-168-224-204-32-8-66-8l0 70q112 0 182 108 54 86 26 146-16 36-62 36-60 0-103 44t-43 106 43 106 103 44z m420 0q146 0 184-146 38-140-40-302-80-168-224-204-32-8-66-8l0 70q112 0 182 108 54 86 26 146-16 36-62 36-60 0-103 44t-43 106 43 106 103 44z" horiz-adv-x="762" />
<glyph glyph-name="trash" unicode="&#xe807;" d="M50 458q122-70 330-70t330 70l-54-486q-2-14-35-36t-100-43-141-21-140 21-100 43-36 36z m488 300q94-18 158-55t64-71l0-10q0-58-112-99t-268-41-268 41-112 99l0 10q0 34 64 71t158 55l42 48q22 26 70 26l92 0q52 0 70-26z m-54-112l84 0q-92 110-104 126-14 16-32 16l-102 0q-22 0-32-16l-106-126 84 0 64 66 82 0z" horiz-adv-x="760" />
<glyph glyph-name="down-big" unicode="&#xe808;" d="M899 386q0-30-21-50l-363-364q-22-21-51-21-29 0-50 21l-363 364q-21 20-21 50 0 29 21 51l41 41q22 21 51 21 29 0 50-21l164-164v393q0 29 21 50t51 22h71q29 0 50-22t21-50v-393l164 164q21 21 51 21 29 0 50-21l42-41q21-22 21-51z" horiz-adv-x="928.6" />
<glyph glyph-name="up-big" unicode="&#xe809;" d="M899 308q0-28-21-50l-42-42q-21-21-50-21-30 0-51 21l-164 164v-393q0-29-20-47t-51-19h-71q-30 0-51 19t-21 47v393l-164-164q-20-21-50-21t-50 21l-42 42q-21 21-21 50 0 30 21 51l363 363q20 21 50 21 30 0 51-21l363-363q21-22 21-51z" horiz-adv-x="928.6" />
<glyph glyph-name="header" unicode="&#xe80a;" d="M939-79q-25 0-74 2t-75 2q-24 0-73-2t-74-2q-14 0-21 12t-7 25q0 17 9 26t22 9 29 4 25 9q18 11 18 78l0 218q0 12-1 17-7 3-28 3h-376q-22 0-29-3 0-5 0-17l-1-207q0-79 21-92 9-5 26-7t32-2 25-8 11-26q0-14-7-26t-20-13q-26 0-78 2t-77 2q-24 0-71-2t-71-2q-13 0-20 12t-7 25q0 17 9 25t20 10 26 4 24 9q18 13 18 80l-1 31v454q0 2 1 14t0 21-1 21-2 24-4 20-6 18-9 10q-8 5-25 6t-29 2-23 7-10 26q0 14 6 26t20 13q26 0 78-2t77-2q23 0 71 2t70 2q14 0 21-13t7-26q0-17-9-25t-22-8-28-2-24-7q-19-12-19-90l1-178q0-12 0-18 7-2 22-2h390q14 0 21 2 1 6 1 18l0 178q0 78-19 90-10 6-33 7t-37 7-14 28q0 14 7 26t21 13q24 0 74-2t73-2q24 0 72 2t72 2q14 0 21-13t7-26q0-17-10-25t-22-8-29-2-24-7q-20-13-20-90l1-526q0-66 19-78 9-6 25-8t30-2 23-9 10-25q0-14-6-26t-20-13z" horiz-adv-x="1000" />
<glyph glyph-name="paragraph" unicode="&#xe80b;" d="M713 745v-41q0-16-10-34t-24-18q-28 0-30-1-15-3-18-17-2-6-2-36v-643q0-14-10-24t-24-10h-60q-14 0-24 10t-10 24v680h-80v-680q0-14-9-24t-25-10h-60q-14 0-24 10t-10 24v277q-82 7-137 33-70 33-107 100-36 65-36 145 0 92 50 159 49 66 116 89 62 21 233 21h267q14 0 24-10t10-24z" horiz-adv-x="714.3" />
<glyph glyph-name="align-left" unicode="&#xe80c;" d="M1000 100v-71q0-15-11-25t-25-11h-928q-15 0-25 11t-11 25v71q0 15 11 25t25 11h928q15 0 25-11t11-25z m-214 214v-71q0-15-11-25t-25-11h-714q-15 0-25 11t-11 25v71q0 15 11 25t25 11h714q15 0 25-11t11-25z m143 215v-72q0-14-11-25t-25-11h-857q-15 0-25 11t-11 25v72q0 14 11 25t25 10h857q14 0 25-10t11-25z m-215 214v-72q0-14-10-25t-25-10h-643q-15 0-25 10t-11 25v72q0 14 11 25t25 11h643q14 0 25-11t10-25z" horiz-adv-x="1000" />
<glyph glyph-name="align-center" unicode="&#xe80d;" d="M1000 100v-71q0-15-11-25t-25-11h-928q-15 0-25 11t-11 25v71q0 15 11 25t25 11h928q15 0 25-11t11-25z m-214 214v-71q0-15-11-25t-25-11h-500q-14 0-25 11t-11 25v71q0 15 11 25t25 11h500q15 0 25-11t11-25z m143 215v-72q0-14-11-25t-25-11h-786q-14 0-25 11t-11 25v72q0 14 11 25t25 10h786q14 0 25-10t11-25z m-215 214v-72q0-14-10-25t-25-10h-358q-14 0-25 10t-10 25v72q0 14 10 25t25 11h358q14 0 25-11t10-25z" horiz-adv-x="1000" />
<glyph glyph-name="align-right" unicode="&#xe80e;" d="M1000 100v-71q0-15-11-25t-25-11h-928q-15 0-25 11t-11 25v71q0 15 11 25t25 11h928q15 0 25-11t11-25z m0 214v-71q0-15-11-25t-25-11h-714q-14 0-25 11t-11 25v71q0 15 11 25t25 11h714q15 0 25-11t11-25z m0 215v-72q0-14-11-25t-25-11h-857q-14 0-25 11t-11 25v72q0 14 11 25t25 10h857q15 0 25-10t11-25z m0 214v-72q0-14-11-25t-25-10h-643q-14 0-25 10t-10 25v72q0 14 10 25t25 11h643q15 0 25-11t11-25z" horiz-adv-x="1000" />
<glyph glyph-name="font" unicode="&#xe80f;" d="M405 538l-95-251q18 0 76-1t89-1q11 0 32 1-48 141-102 252z m-405-617l1 44q13 4 31 7t32 6 28 8 25 17 17 28l132 344 156 404h72q4-8 6-12l114-268q19-44 60-144t63-153q9-19 33-80t40-95q11-25 19-31 11-9 49-17t47-11q4-22 4-32 0-2-1-7t0-8q-35 0-106 5t-107 4q-42 0-120-4t-99-4q0 24 2 43l73 16q1 0 7 1t9 2 8 3 9 4 6 4 5 6 1 8q0 9-17 54t-40 99-24 56l-251 1q-14-32-43-109t-28-91q0-12 8-21t24-14 27-7 32-5 23-2q1-11 1-32 0-5-2-16-32 0-97 6t-97 6q-5 0-15-3t-12-2q-45-8-105-8z" horiz-adv-x="928.6" />
<glyph glyph-name="bold" unicode="&#xe810;" d="M310 1q41-18 78-18 210 0 210 187 0 64-23 101-15 24-35 41t-37 26-45 14-47 6-53 1q-40 0-56-6 0-29 0-88t-1-89q0-4 0-37t0-54 2-47 7-37z m-8 417q23-4 61-4 46 0 80 7t61 25 41 50 15 79q0 39-16 68t-45 46-60 24-69 8q-28 0-73-7 0-28 3-84t2-85q0-15 0-45t-1-44q0-26 1-38z m-302-497l1 53q9 2 48 9t59 15q4 7 7 15t4 19 4 18 1 21 0 19v36q0 548-12 572-2 5-12 8t-25 6-28 4-27 3-17 2l-2 46q55 1 190 6t208 6q13 0 38-1t38 0q39 0 76-7t72-24 60-39 41-59 16-76q0-29-9-54t-22-40-36-32-41-25-47-22q86-20 144-75t57-138q0-56-20-101t-52-72-77-48-91-27-98-8q-25 0-74 2t-74 1q-59 0-171-6t-129-7z" horiz-adv-x="785.7" />
<glyph glyph-name="medium" unicode="&#xe811;" d="M1000 107v-117h-358v117h75v516h-4l-175-633h-136l-173 633h-4v-516h75v-117h-300v117h39q11 0 24 11t12 21v491q0 10-12 22t-24 13h-39v116h375l123-458h4l124 458h374v-116h-40q-10 0-23-13t-12-22v-491q0-11 12-21t23-11h40z" horiz-adv-x="1000" />
<glyph glyph-name="italic" unicode="&#xe812;" d="M0-78l10 48q3 1 45 12t62 21q16 19 23 56 1 4 35 162t63 303 29 165v14q-13 7-30 11t-39 4-32 3l10 58q19-2 67-4t84-4 67-1q27 0 55 1t67 4 55 4q-2-22-10-50-17-6-57-16t-60-19q-5-10-8-23t-5-23-4-25-4-24q-15-82-49-234t-43-198q-1-5-7-32t-11-51-9-46-4-32l1-10q9-3 103-18-2-24-9-55-6 0-18-1t-18-1q-16 0-49 6t-48 6q-77 1-115 1-28 0-79-5t-68-7z" horiz-adv-x="571.4" />
<glyph glyph-name="list-bullet" unicode="&#xe813;" d="M214 64q0-44-31-76t-76-31-76 31-31 76 31 76 76 31 76-31 31-76z m0 286q0-45-31-76t-76-31-76 31-31 76 31 76 76 31 76-31 31-76z m786-232v-107q0-7-5-13t-13-5h-678q-8 0-13 5t-5 13v107q0 7 5 12t13 6h678q7 0 13-6t5-12z m-786 518q0-45-31-76t-76-31-76 31-31 76 31 76 76 31 76-31 31-76z m786-232v-108q0-7-5-12t-13-5h-678q-8 0-13 5t-5 12v108q0 7 5 12t13 5h678q7 0 13-5t5-12z m0 285v-107q0-7-5-12t-13-6h-678q-8 0-13 6t-5 12v107q0 8 5 13t13 5h678q7 0 13-5t5-13z" horiz-adv-x="1000" />
<glyph glyph-name="list-numbered" unicode="&#xe814;" d="M213-54q0-45-31-70t-75-26q-60 0-96 37l31 49q28-25 60-25 16 0 28 8t12 24q0 35-59 31l-14 31q4 6 18 24t24 31 20 21v1q-9 0-27-1t-27 0v-30h-59v85h186v-49l-53-65q28-6 45-27t17-49z m1 350v-89h-202q-4 20-4 30 0 29 14 52t31 38 37 27 31 24 14 25q0 14-9 22t-22 7q-25 0-45-32l-47 33q13 28 40 44t59 16q40 0 68-23t28-63q0-28-19-51t-42-36-42-28-20-30h71v34h59z m786-178v-107q0-8-5-13t-13-5h-678q-8 0-13 5t-5 13v107q0 8 5 13t13 5h678q7 0 13-6t5-12z m-786 502v-56h-187v56h60q0 22 0 68t1 67v7h-1q-5-10-28-30l-40 42 76 71h59v-225h60z m786-216v-108q0-7-5-12t-13-5h-678q-8 0-13 5t-5 12v108q0 7 5 12t13 5h678q7 0 13-5t5-12z m0 285v-107q0-7-5-12t-13-6h-678q-8 0-13 6t-5 12v107q0 8 5 13t13 5h678q7 0 13-5t5-13z" horiz-adv-x="1000" />
<glyph glyph-name="strike" unicode="&#xe815;" d="M982 350q8 0 13-5t5-13v-36q0-7-5-12t-13-5h-964q-8 0-13 5t-5 12v36q0 8 5 13t13 5h964z m-712 36q-16 19-29 44-27 54-27 105 0 101 75 173 74 71 219 71 28 0 94-11 36-7 98-27 6-21 12-66 8-68 8-102 0-10-3-25l-7-2-46 4-8 1q-28 83-58 114-49 51-117 51-64 0-102-33-37-32-37-81 0-41 37-78t156-72q38-12 96-37 33-16 53-29h-414z m282-143h230q4-22 4-51 0-62-23-119-13-30-40-58-20-19-61-45-44-27-85-37-45-12-113-12-64 0-109 13l-78 23q-32 8-40 15-5 5-5 12v8q0 60-1 87 0 17 0 38l1 20v25l57 1q8-19 17-40t12-31 7-15q20-32 45-52 24-20 59-32 33-12 73-12 36 0 78 15 43 14 68 48 26 34 26 72 0 47-45 87-19 16-77 40z" horiz-adv-x="1000" />
<glyph glyph-name="underline" unicode="&#xe816;" d="M27 726q-21 1-25 2l-2 49q7 0 22 0 34 0 63-2 74-4 92-4 48 0 94 2 65 2 82 3 31 0 48 1l-1-8 1-36v-5q-33-5-69-5-33 0-44-14-7-7-7-73 0-8 0-18t0-15l1-127 8-157q3-69 28-112 20-33 54-52 49-26 98-26 58 0 107 16 31 10 55 28 27 20 37 36 20 31 29 63 12 41 12 128 0 44-2 72t-6 68-8 89l-2 33q-3 37-13 49-19 20-43 19l-56-1-8 2 1 48h47l114-6q43-2 110 6l10-2q3-21 3-28 0-4-2-17-25-7-47-8-41-6-44-9-8-8-8-23 0-4 0-15t1-17q5-11 13-221 3-109-9-170-8-42-23-68-21-36-62-69-42-31-102-49-61-19-142-19-93 0-159 26-66 26-99 68-34 42-47 109-9 45-9 132v186q0 105-9 119-14 20-82 22z m830-787v36q0 8-5 13t-13 5h-821q-8 0-13-5t-5-13v-36q0-8 5-13t13-5h821q8 0 13 5t5 13z" horiz-adv-x="857.1" />
<glyph glyph-name="table" unicode="&#xe817;" d="M286 82v107q0 8-5 13t-13 5h-179q-7 0-13-5t-5-13v-107q0-8 5-13t13-5h179q8 0 13 5t5 13z m0 214v108q0 7-5 12t-13 5h-179q-7 0-13-5t-5-12v-108q0-7 5-12t13-5h179q8 0 13 5t5 12z m285-214v107q0 8-5 13t-12 5h-179q-8 0-13-5t-5-13v-107q0-8 5-13t13-5h179q7 0 12 5t5 13z m-285 429v107q0 8-5 13t-13 5h-179q-7 0-13-5t-5-13v-107q0-8 5-13t13-5h179q8 0 13 5t5 13z m285-215v108q0 7-5 12t-12 5h-179q-8 0-13-5t-5-12v-108q0-7 5-12t13-5h179q7 0 12 5t5 12z m286-214v107q0 8-5 13t-13 5h-178q-8 0-13-5t-5-13v-107q0-8 5-13t13-5h178q8 0 13 5t5 13z m-286 429v107q0 8-5 13t-12 5h-179q-8 0-13-5t-5-13v-107q0-8 5-13t13-5h179q7 0 12 5t5 13z m286-215v108q0 7-5 12t-13 5h-178q-8 0-13-5t-5-12v-108q0-7 5-12t13-5h178q8 0 13 5t5 12z m0 215v107q0 8-5 13t-13 5h-178q-8 0-13-5t-5-13v-107q0-8 5-13t13-5h178q8 0 13 5t5 13z m72 178v-607q0-37-27-63t-63-26h-750q-36 0-63 26t-26 63v607q0 37 26 63t63 27h750q37 0 63-27t27-63z" horiz-adv-x="928.6" />
<glyph glyph-name="ellipsis-vert" unicode="&#xe818;" d="M214 154v-108q0-22-15-37t-38-16h-107q-23 0-38 16t-16 37v108q0 22 16 38t38 15h107q22 0 38-15t15-38z m0 285v-107q0-22-15-38t-38-15h-107q-23 0-38 15t-16 38v107q0 23 16 38t38 16h107q22 0 38-16t15-38z m0 286v-107q0-22-15-38t-38-16h-107q-23 0-38 16t-16 38v107q0 22 16 38t38 16h107q22 0 38-16t15-38z" horiz-adv-x="214.3" />
<glyph glyph-name="columns" unicode="&#xe819;" d="M89-7h340v643h-358v-625q0-8 6-13t12-5z m768 18v625h-357v-643h339q8 0 13 5t5 13z m72 678v-678q0-37-27-63t-63-27h-750q-36 0-63 27t-26 63v678q0 37 26 63t63 27h750q37 0 63-27t27-63z" horiz-adv-x="928.6" />
<glyph glyph-name="smile" unicode="&#xe81a;" d="M633 250q-21-67-77-109t-127-41-128 41-77 109q-4 14 3 27t21 18q14 4 27-2t17-22q14-44 52-72t85-28 84 28 52 72q4 15 18 22t27 2 21-18 2-27z m-276 243q0-30-21-51t-50-21-51 21-21 51 21 50 51 21 50-21 21-50z m286 0q0-30-21-51t-51-21-50 21-21 51 21 50 50 21 51-21 21-50z m143-143q0 73-29 139t-76 114-114 76-138 28-139-28-114-76-76-114-29-139 29-139 76-113 114-77 139-28 138 28 114 77 76 113 29 139z m71 0q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" />
<glyph glyph-name="newspaper" unicode="&#xe81b;" d="M571 564h-214v-214h214v214z m72-357v-71h-357v71h357z m0 429v-357h-357v357h357z m357-429v-71h-286v71h286z m0 143v-71h-286v71h286z m0 143v-72h-286v72h286z m0 143v-72h-286v72h286z m-857-536v536h-72v-536q0-14 11-25t25-11 25 11 11 25z m928 0v607h-857v-607q0-18-6-36h828q14 0 25 11t10 25z m72 679v-679q0-45-31-76t-76-31h-929q-44 0-76 31t-31 76v607h143v72h1000z" horiz-adv-x="1142.9" />
<glyph glyph-name="twitter" unicode="&#xe81c;" d="M904 622q-37-54-90-93 0-8 0-23 0-73-21-145t-64-139-103-117-144-82-181-30q-151 0-276 81 19-2 43-2 126 0 224 77-59 1-105 36t-64 89q19-3 34-3 24 0 48 6-63 13-104 62t-41 115v2q38-21 82-23-37 25-59 64t-22 86q0 49 25 91 68-83 164-133t208-55q-5 21-5 41 0 75 53 127t127 53q79 0 132-57 61 12 114 44-20-64-79-100 52 6 104 28z" horiz-adv-x="928.6" />
<glyph glyph-name="facebook-squared" unicode="&#xe81d;" d="M696 779q67 0 114-48t47-113v-536q0-66-47-113t-114-48h-104v332h111l16 130h-127v83q0 31 13 46t51 16l68 1v115q-35 5-100 5-75 0-121-45t-45-126v-95h-112v-130h112v-332h-297q-67 0-114 48t-47 113v536q0 66 47 113t114 48h535z" horiz-adv-x="857.1" />
<glyph glyph-name="vkontakte" unicode="&#xe81e;" d="M1070 560q13-36-84-164-13-18-36-48-44-55-50-73-10-23 7-45 10-12 46-46h0l1 0 0-1 1-1q79-73 107-123 2-3 4-7t3-15 0-19-14-15-33-7l-143-3q-13-2-31 3t-29 13l-11 6q-17 12-39 36t-38 43-34 33-32 8q-1 0-4-2t-10-8-12-16-9-29-4-44q0-8-2-15t-4-10l-2-3q-10-11-30-12h-64q-40-3-81 9t-74 29-57 37-40 32l-14 14q-5 5-15 17t-40 50-59 85-68 117-73 152q-4 9-4 15t2 9l2 3q9 11 32 11l153 1q7-1 13-4t9-4l3-2q9-6 13-18 11-28 26-58t23-45l9-16q16-34 31-58t27-38 23-22 19-8 15 3q1 1 3 3t7 12 7 26 5 46 0 69q-1 23-5 41t-7 26l-4 6q-14 19-47 24-8 2 3 14 9 10 21 17 29 14 133 13 46-1 75-7 12-3 19-8t12-13 5-18 2-26 0-30-2-40 0-46q0-6-1-23t0-27 2-22 6-22 13-14q4-1 9-2t15 6 21 19 29 38 38 60q33 58 60 125 2 6 5 10t6 6l2 2 3 1t8 2 11 0l160 1q22 3 36-1t17-10z" horiz-adv-x="1071.4" />
</font>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Binary file not shown.

View File

@ -1,6 +0,0 @@
<?xml version="1.0" ?>
<!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'><svg height="32px" style="enable-background:new 0 0 28 32;" version="1.1" viewBox="0 0 28 32" width="28px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g id="Layer_1"/>
<g id="hash">
<path d="M28,12V8h-5.004l1-8h-4l-1,8h-7.998l1-8h-4l-1,8H0v4h6.498L5.5,20H0v4h5l-1,8h4l1-8h8l-1.002,8H20 l1-8h7v-4h-6.5l0.996-8H28z M17.5,20h-8l0.998-8h7.998L17.5,20z" style="fill:#707684;"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 569 B

View File

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="12px" height="12px" viewBox="0 0 12 12" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 3.7 (28169) - http://www.bohemiancoding.com/sketch -->
<title>Combined Shape</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="redactor-toolbar-opened" transform="translate(-84.000000, -472.000000)" fill="#2b304a">
<g id="+-copy-hovered" transform="translate(76.000000, 265.000000)">
<g id="PLUS" transform="translate(0.000000, 199.000000)">
<path d="M15.125,12.875 L15.125,9.12893087 C15.125,8.50034732 14.6213203,8 14,8 C13.3743479,8 12.875,8.50543957 12.875,9.12893087 L12.875,12.875 L9.12893087,12.875 C8.50034732,12.875 8,13.3786797 8,14 C8,14.6256521 8.50543957,15.125 9.12893087,15.125 L12.875,15.125 L12.875,18.8710691 C12.875,19.4996527 13.3786797,20 14,20 C14.6256521,20 15.125,19.4945604 15.125,18.8710691 L15.125,15.125 L18.8710691,15.125 C19.4996527,15.125 20,14.6213203 20,14 C20,13.3743479 19.4945604,12.875 18.8710691,12.875 L15.125,12.875 Z" id="Combined-Shape" transform="translate(14.000000, 14.000000) rotate(-270.000000) translate(-14.000000, -14.000000) "></path>
</g>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -1,63 +0,0 @@
@font-face {
font-family: 'codex_editor';
src: url('fonts/codex_editor/codex-editor.eot?20895205');
src: url('fonts/codex_editor/codex-editor.eot?20895205#iefix') format('embedded-opentype'),
url('fonts/codex_editor/codex-editor.woff?20895205') format('woff'),
url('fonts/codex_editor/codex-editor.ttf?20895205') format('truetype'),
url('fonts/codex_editor/codex-editor.svg?20895205#codex_editor') format('svg');
font-weight: normal;
font-style: normal;
}
[class^="ce-icon-"]:before,
[class*="ce-icon-"]:before {
font-family: "codex_editor";
font-style: normal;
font-weight: normal;
speak: none;
display: inline-block;
text-decoration: inherit;
width: 1em;
margin-right: .2em;
text-align: center;
font-variant: normal;
text-transform: none;
line-height: 1em;
/* Animation center compensation - margins should be symmetric */
margin-left: .2em;
-moz-osx-font-smoothing: grayscale;
}
.ce-icon-instagram:before { content: '\e800'; } /* '' */
.ce-icon-picture:before { content: '\e801'; } /* '' */
.ce-icon-cog:before { content: '\e802'; } /* '' */
.ce-icon-link:before { content: '\e803'; } /* '' */
.ce-icon-unlink:before { content: '\e804'; } /* '' */
.ce-icon-code:before { content: '\e805'; } /* '' */
.ce-icon-quote:before { content: '\e806'; } /* '' */
.ce-icon-trash:before { content: '\e807'; } /* '' */
.ce-icon-down-big:before { content: '\e808'; } /* '' */
.ce-icon-up-big:before { content: '\e809'; } /* '' */
.ce-icon-header:before { content: '\e80a'; } /* '' */
.ce-icon-paragraph:before { content: '\e80b'; } /* '' */
.ce-icon-align-left:before { content: '\e80c'; } /* '' */
.ce-icon-align-center:before { content: '\e80d'; } /* '' */
.ce-icon-align-right:before { content: '\e80e'; } /* '' */
.ce-icon-font:before { content: '\e80f'; } /* '' */
.ce-icon-bold:before { content: '\e810'; } /* '' */
.ce-icon-medium:before { content: '\e811'; } /* '' */
.ce-icon-italic:before { content: '\e812'; } /* '' */
.ce-icon-list-bullet:before { content: '\e813'; } /* '' */
.ce-icon-list-numbered:before { content: '\e814'; } /* '' */
.ce-icon-strike:before { content: '\e815'; } /* '' */
.ce-icon-underline:before { content: '\e816'; } /* '' */
.ce-icon-table:before { content: '\e817'; } /* '' */
.ce-icon-ellipsis-vert:before { content: '\e818'; } /* '' */
.ce-icon-columns:before { content: '\e819'; } /* '' */
.ce-icon-smile:before { content: '\e81a'; } /* '' */
.ce-icon-newspaper:before { content: '\e81b'; } /* '' */
.ce-icon-twitter:before { content: '\e81c'; } /* '' */
.ce-icon-facebook-squared:before { content: '\e81d'; } /* '' */
.ce-icon-vkontakte:before { content: '\e81e'; } /* '' */

View File

@ -1,92 +0,0 @@
/**
* Codex Editor Anchors module
*
* @author Codex Team
* @version 1.0
*/
module.exports = function (anchors) {
let editor = codex.editor;
anchors.input = null;
anchors.currentNode = null;
anchors.settingsOpened = function (currentBlock) {
anchors.currentNode = currentBlock;
anchors.input.value = anchors.currentNode.dataset.anchor || '';
};
anchors.anchorChanged = function (e) {
var newAnchor = e.target.value = anchors.rusToTranslit(e.target.value);
anchors.currentNode.dataset.anchor = newAnchor;
if (newAnchor.trim() !== '') {
anchors.currentNode.classList.add(editor.ui.className.BLOCK_WITH_ANCHOR);
} else {
anchors.currentNode.classList.remove(editor.ui.className.BLOCK_WITH_ANCHOR);
}
};
anchors.keyDownOnAnchorInput = function (e) {
if (e.keyCode == editor.core.keys.ENTER) {
e.preventDefault();
e.stopPropagation();
e.target.blur();
editor.toolbar.settings.close();
}
};
anchors.keyUpOnAnchorInput = function (e) {
if (e.keyCode >= editor.core.keys.LEFT && e.keyCode <= editor.core.keys.DOWN) {
e.stopPropagation();
}
};
anchors.rusToTranslit = function (string) {
var ru = [
'А', 'Б', 'В', 'Г', 'Д', 'Е', 'Ё', 'Ж', 'З', 'И', 'Й',
'К', 'Л', 'М', 'Н', 'О', 'П', 'Р', 'С', 'Т', 'У', 'Ф',
'Х', 'Ц', 'Ч', 'Ш', 'Щ', 'Ь', 'Ы', 'Ь', 'Э', 'Ю', 'Я'
],
en = [
'A', 'B', 'V', 'G', 'D', 'E', 'E', 'Zh', 'Z', 'I', 'Y',
'K', 'L', 'M', 'N', 'O', 'P', 'R', 'S', 'T', 'U', 'F',
'H', 'C', 'Ch', 'Sh', 'Sch', '', 'Y', '', 'E', 'Yu', 'Ya'
];
for (var i = 0; i < ru.length; i++) {
string = string.split(ru[i]).join(en[i]);
string = string.split(ru[i].toLowerCase()).join(en[i].toLowerCase());
}
string = string.replace(/[^0-9a-zA-Z_]+/g, '-');
return string;
};
return anchors;
}({});

View File

@ -1,911 +0,0 @@
/**
* @module Codex Editor Callbacks module
* @description Module works with editor added Elements
*
* @author Codex Team
* @version 1.4.0
*/
module.exports = (function (callbacks) {
let editor = codex.editor;
/**
* used by UI module
* @description Routes all keydowns on document
* @param {Object} event
*/
callbacks.globalKeydown = function (event) {
switch (event.keyCode) {
case editor.core.keys.ENTER : enterKeyPressed_(event); break;
}
};
/**
* used by UI module
* @description Routes all keydowns on redactors area
* @param {Object} event
*/
callbacks.redactorKeyDown = function (event) {
switch (event.keyCode) {
case editor.core.keys.TAB : tabKeyPressedOnRedactorsZone_(event); break;
case editor.core.keys.ENTER : enterKeyPressedOnRedactorsZone_(event); break;
case editor.core.keys.ESC : escapeKeyPressedOnRedactorsZone_(event); break;
default : defaultKeyPressedOnRedactorsZone_(event); break;
}
};
/**
* used by UI module
* @description Routes all keyup events
* @param {Object} event
*/
callbacks.globalKeyup = function (event) {
switch (event.keyCode) {
case editor.core.keys.UP :
case editor.core.keys.LEFT :
case editor.core.keys.RIGHT :
case editor.core.keys.DOWN : arrowKeyPressed_(event); break;
}
};
/**
* @param {Object} event
* @private
*
* Handles behaviour when tab pressed
* @description if Content is empty show toolbox (if it is closed) or leaf tools
* uses Toolbars toolbox module to handle the situation
*/
var tabKeyPressedOnRedactorsZone_ = function (event) {
/**
* Wait for solution. Would like to know the behaviour
* @todo Add spaces
*/
event.preventDefault();
if (!editor.core.isBlockEmpty(editor.content.currentNode)) {
return;
}
if ( !editor.toolbar.opened ) {
editor.toolbar.open();
}
if (editor.toolbar.opened && !editor.toolbar.toolbox.opened) {
editor.toolbar.toolbox.open();
} else {
editor.toolbar.toolbox.leaf();
}
};
/**
* Handles global EnterKey Press
* @see enterPressedOnBlock_
* @param {Object} event
*/
var enterKeyPressed_ = function () {
if (editor.content.editorAreaHightlighted) {
/**
* it means that we lose input index, saved index before is not correct
* therefore we need to set caret when we insert new block
*/
editor.caret.inputIndex = -1;
enterPressedOnBlock_();
}
};
/**
* Callback for enter key pressing in first-level block area
*
* @param {Event} event
* @private
*
* @description Inserts new block with initial type from settings
*/
var enterPressedOnBlock_ = function () {
var NEW_BLOCK_TYPE = editor.settings.initialBlockPlugin;
editor.content.insertBlock({
type : NEW_BLOCK_TYPE,
block : editor.tools[NEW_BLOCK_TYPE].render()
}, true );
editor.toolbar.move();
editor.toolbar.open();
};
/**
* ENTER key handler
*
* @param {Object} event
* @private
*
* @description Makes new block with initial type from settings
*/
var enterKeyPressedOnRedactorsZone_ = function (event) {
if (event.target.contentEditable == 'true') {
/** Update input index */
editor.caret.saveCurrentInputIndex();
}
var currentInputIndex = editor.caret.getCurrentInputIndex() || 0,
workingNode = editor.content.currentNode,
tool = workingNode.dataset.tool,
isEnterPressedOnToolbar = editor.toolbar.opened &&
editor.toolbar.current &&
event.target == editor.state.inputs[currentInputIndex];
/** The list of tools which needs the default browser behaviour */
var enableLineBreaks = editor.tools[tool].enableLineBreaks;
/** This type of block creates when enter is pressed */
var NEW_BLOCK_TYPE = editor.settings.initialBlockPlugin;
/**
* When toolbar is opened, select tool instead of making new paragraph
*/
if ( isEnterPressedOnToolbar ) {
event.preventDefault();
editor.toolbar.toolbox.toolClicked(event);
editor.toolbar.close();
/**
* Stop other listeners callback executions
*/
event.stopPropagation();
event.stopImmediatePropagation();
return;
}
/**
* Allow paragraph lineBreaks with shift enter
* Or if shiftkey pressed and enter and enabledLineBreaks, the let new block creation
*/
if ( event.shiftKey || enableLineBreaks ) {
event.stopPropagation();
event.stopImmediatePropagation();
return;
}
var currentSelection = window.getSelection(),
currentSelectedNode = currentSelection.anchorNode,
caretAtTheEndOfText = editor.caret.position.atTheEnd(),
isTextNodeHasParentBetweenContenteditable = false;
/**
* Allow making new <p> in same block by SHIFT+ENTER and forbids to prevent default browser behaviour
*/
if ( event.shiftKey && !enableLineBreaks ) {
editor.callback.enterPressedOnBlock(editor.content.currentBlock, event);
event.preventDefault();
return;
}
/**
* Workaround situation when caret at the Text node that has some wrapper Elements
* Split block cant handle this.
* We need to save default behavior
*/
isTextNodeHasParentBetweenContenteditable = currentSelectedNode && currentSelectedNode.parentNode.contentEditable != 'true';
/**
* Split blocks when input has several nodes and caret placed in textNode
*/
if (
currentSelectedNode.nodeType == editor.core.nodeTypes.TEXT &&
!isTextNodeHasParentBetweenContenteditable &&
!caretAtTheEndOfText
) {
event.preventDefault();
editor.core.log('Splitting Text node...');
editor.content.splitBlock(currentInputIndex);
/** Show plus button when next input after split is empty*/
if (!editor.state.inputs[currentInputIndex + 1].textContent.trim()) {
editor.toolbar.showPlusButton();
}
} else {
var islastNode = editor.content.isLastNode(currentSelectedNode);
if ( islastNode && caretAtTheEndOfText ) {
event.preventDefault();
event.stopPropagation();
event.stopImmediatePropagation();
editor.core.log('ENTER clicked in last textNode. Create new BLOCK');
editor.content.insertBlock({
type: NEW_BLOCK_TYPE,
block: editor.tools[NEW_BLOCK_TYPE].render()
}, true);
editor.toolbar.move();
editor.toolbar.open();
/** Show plus button with empty block */
editor.toolbar.showPlusButton();
}
}
/** get all inputs after new appending block */
editor.ui.saveInputs();
};
/**
* Escape behaviour
* @param event
* @private
*
* @description Closes toolbox and toolbar. Prevents default behaviour
*/
var escapeKeyPressedOnRedactorsZone_ = function (event) {
/** Close all toolbar */
editor.toolbar.close();
/** Close toolbox */
editor.toolbar.toolbox.close();
event.preventDefault();
};
/**
* @param {Event} event
* @private
*
* closes and moves toolbar
*/
var arrowKeyPressed_ = function (event) {
editor.content.workingNodeChanged();
/* Closing toolbar */
editor.toolbar.close();
editor.toolbar.move();
};
/**
* @private
* @param {Event} event
*
* @description Closes all opened bars from toolbar.
* If block is mark, clears highlightning
*/
var defaultKeyPressedOnRedactorsZone_ = function () {
editor.toolbar.close();
if (!editor.toolbar.inline.actionsOpened) {
editor.toolbar.inline.close();
editor.content.clearMark();
}
};
/**
* Handler when clicked on redactors area
*
* @protected
* @param event
*
* @description Detects clicked area. If it is first-level block area, marks as detected and
* on next enter press will be inserted new block
* Otherwise, save carets position (input index) and put caret to the editable zone.
*
* @see detectWhenClickedOnFirstLevelBlockArea_
*
*/
callbacks.redactorClicked = function (event) {
detectWhenClickedOnFirstLevelBlockArea_();
editor.content.workingNodeChanged(event.target);
editor.ui.saveInputs();
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
*/
editor.toolbar.move();
editor.toolbar.open();
var inputIsEmpty = !editor.content.currentNode.textContent.trim(),
currentNodeType = editor.content.currentNode.dataset.tool,
isInitialType = currentNodeType == editor.settings.initialBlockPlugin;
/** Hide plus buttons */
editor.toolbar.hidePlusButton();
if (!inputIsEmpty) {
/** Mark current block */
editor.content.markBlock();
}
if ( isInitialType && inputIsEmpty ) {
/** Show plus button */
editor.toolbar.showPlusButton();
}
};
/**
* This method allows to define, is caret in contenteditable element or not.
*
* @private
*
* @description Otherwise, if we get TEXT node from range container, that will means we have input index.
* In this case we use default browsers behaviour (if plugin allows that) or overwritten action.
* Therefore, to be sure that we've clicked first-level block area, we should have currentNode, which always
* specifies to the first-level block. Other cases we just ignore.
*/
var detectWhenClickedOnFirstLevelBlockArea_ = function () {
var selection = window.getSelection(),
anchorNode = selection.anchorNode,
flag = false;
if (selection.rangeCount === 0) {
editor.content.editorAreaHightlighted = true;
} else {
if (!editor.core.isDomNode(anchorNode)) {
anchorNode = anchorNode.parentNode;
}
/** Already founded, without loop */
if (anchorNode.contentEditable == 'true') {
flag = true;
}
while (anchorNode.contentEditable != 'true') {
anchorNode = anchorNode.parentNode;
if (anchorNode.contentEditable == 'true') {
flag = true;
}
if (anchorNode == document.body) {
break;
}
}
/** If editable element founded, flag is "TRUE", Therefore we return "FALSE" */
editor.content.editorAreaHightlighted = !flag;
}
};
/**
* Toolbar button click handler
*
* @param {Object} event - cursor to the button
* @protected
*
* @description gets current tool and calls render method
*/
callbacks.toolbarButtonClicked = function (event) {
var button = this;
editor.toolbar.current = button.dataset.type;
editor.toolbar.toolbox.toolClicked(event);
editor.toolbar.close();
};
/**
* Show or Hide toolbox when plus button is clicked
*/
callbacks.plusButtonClicked = function () {
if (!editor.nodes.toolbox.classList.contains('opened')) {
editor.toolbar.toolbox.open();
} else {
editor.toolbar.toolbox.close();
}
};
/**
* Block handlers for KeyDown events
*
* @protected
* @param {Object} event
*
* Handles keydowns on block
* @see blockRightOrDownArrowPressed_
* @see backspacePressed_
* @see blockLeftOrUpArrowPressed_
*/
callbacks.blockKeydown = function (event) {
let block = event.target; // event.target is input
switch (event.keyCode) {
case editor.core.keys.DOWN:
case editor.core.keys.RIGHT:
blockRightOrDownArrowPressed_(event);
break;
case editor.core.keys.BACKSPACE:
backspacePressed_(block, event);
break;
case editor.core.keys.UP:
case editor.core.keys.LEFT:
blockLeftOrUpArrowPressed_(event);
break;
}
};
/**
* RIGHT or DOWN keydowns on block
*
* @param {Object} event
* @private
*
* @description watches the selection and gets closest editable element.
* Uses method getDeepestTextNodeFromPosition to get the last node of next block
* Sets caret if it is contenteditable
*/
var blockRightOrDownArrowPressed_ = function (event) {
var selection = window.getSelection(),
inputs = editor.state.inputs,
focusedNode = selection.anchorNode,
focusedNodeHolder;
/** Check for caret existance */
if (!focusedNode) {
return false;
}
/** Looking for closest (parent) contentEditable element of focused node */
while (focusedNode.contentEditable != 'true') {
focusedNodeHolder = focusedNode.parentNode;
focusedNode = focusedNodeHolder;
}
/** Input index in DOM level */
var editableElementIndex = 0;
while (focusedNode != inputs[editableElementIndex]) {
editableElementIndex ++;
}
/**
* Founded contentEditable element doesn't have childs
* Or maybe New created block
*/
if (!focusedNode.textContent) {
editor.caret.setToNextBlock(editableElementIndex);
return;
}
/**
* Do nothing when caret doesn not reaches the end of last child
*/
var caretInLastChild = false,
caretAtTheEndOfText = false;
var lastChild,
deepestTextnode;
lastChild = focusedNode.childNodes[focusedNode.childNodes.length - 1 ];
if (editor.core.isDomNode(lastChild)) {
deepestTextnode = editor.content.getDeepestTextNodeFromPosition(lastChild, lastChild.childNodes.length);
} else {
deepestTextnode = lastChild;
}
caretInLastChild = selection.anchorNode == deepestTextnode;
caretAtTheEndOfText = deepestTextnode.length == selection.anchorOffset;
if ( !caretInLastChild || !caretAtTheEndOfText ) {
editor.core.log('arrow [down|right] : caret does not reached the end');
return false;
}
editor.caret.setToNextBlock(editableElementIndex);
};
/**
* LEFT or UP keydowns on block
*
* @param {Object} event
* @private
*
* watches the selection and gets closest editable element.
* Uses method getDeepestTextNodeFromPosition to get the last node of previous block
* Sets caret if it is contenteditable
*
*/
var blockLeftOrUpArrowPressed_ = function (event) {
var selection = window.getSelection(),
inputs = editor.state.inputs,
focusedNode = selection.anchorNode,
focusedNodeHolder;
/** Check for caret existance */
if (!focusedNode) {
return false;
}
/**
* LEFT or UP not at the beginning
*/
if ( selection.anchorOffset !== 0) {
return false;
}
/** Looking for parent contentEditable block */
while (focusedNode.contentEditable != 'true') {
focusedNodeHolder = focusedNode.parentNode;
focusedNode = focusedNodeHolder;
}
/** Input index in DOM level */
var editableElementIndex = 0;
while (focusedNode != inputs[editableElementIndex]) {
editableElementIndex ++;
}
/**
* Do nothing if caret is not at the beginning of first child
*/
var caretInFirstChild = false,
caretAtTheBeginning = false;
var firstChild,
deepestTextnode;
/**
* Founded contentEditable element doesn't have childs
* Or maybe New created block
*/
if (!focusedNode.textContent) {
editor.caret.setToPreviousBlock(editableElementIndex);
return;
}
firstChild = focusedNode.childNodes[0];
if (editor.core.isDomNode(firstChild)) {
deepestTextnode = editor.content.getDeepestTextNodeFromPosition(firstChild, 0);
} else {
deepestTextnode = firstChild;
}
caretInFirstChild = selection.anchorNode == deepestTextnode;
caretAtTheBeginning = selection.anchorOffset === 0;
if ( caretInFirstChild && caretAtTheBeginning ) {
editor.caret.setToPreviousBlock(editableElementIndex);
}
};
/**
* Handles backspace keydown
*
* @param {Element} block
* @param {Object} event
* @private
*
* @description if block is empty, delete the block and set caret to the previous block
* If block is not empty, try to merge two blocks - current and previous
* But it we try'n to remove first block, then we should set caret to the next block, not previous.
* If we removed the last block, create new one
*/
var backspacePressed_ = function (block, event) {
var currentInputIndex = editor.caret.getCurrentInputIndex(),
range,
selectionLength,
firstLevelBlocksCount;
if (editor.core.isNativeInput(event.target)) {
/** If input value is empty - remove block */
if (event.target.value.trim() == '') {
block.remove();
} else {
return;
}
}
if (block.textContent.trim()) {
range = editor.content.getRange();
selectionLength = range.endOffset - range.startOffset;
if (editor.caret.position.atStart() && !selectionLength && editor.state.inputs[currentInputIndex - 1]) {
editor.content.mergeBlocks(currentInputIndex);
} else {
return;
}
}
if (!selectionLength) {
block.remove();
}
firstLevelBlocksCount = editor.nodes.redactor.childNodes.length;
/**
* If all blocks are removed
*/
if (firstLevelBlocksCount === 0) {
/** update currentNode variable */
editor.content.currentNode = null;
/** Inserting new empty initial block */
editor.ui.addInitialBlock();
/** Updating inputs state after deleting last block */
editor.ui.saveInputs();
/** Set to current appended block */
window.setTimeout(function () {
editor.caret.setToPreviousBlock(1);
}, 10);
} else {
if (editor.caret.inputIndex !== 0) {
/** Target block is not first */
editor.caret.setToPreviousBlock(editor.caret.inputIndex);
} else {
/** If we try to delete first block */
editor.caret.setToNextBlock(editor.caret.inputIndex);
}
}
editor.toolbar.move();
if (!editor.toolbar.opened) {
editor.toolbar.open();
}
/** Updating inputs state */
editor.ui.saveInputs();
/** Prevent default browser behaviour */
event.preventDefault();
};
/**
* used by UI module
* Clicks on block settings button
*
* @param {Object} event
* @protected
* @description Opens toolbar settings
*/
callbacks.showSettingsButtonClicked = function (event) {
/**
* Get type of current block
* It uses to append settings from tool.settings property.
* ...
* Type is stored in data-type attribute on block
*/
var currentToolType = editor.content.currentNode.dataset.tool;
editor.toolbar.settings.toggle(currentToolType);
/** Close toolbox when settings button is active */
editor.toolbar.toolbox.close();
editor.toolbar.settings.hideRemoveActions();
};
return callbacks;
})({});

View File

@ -1,305 +0,0 @@
/**
* Codex Editor Caret Module
*
* @author Codex Team
* @version 1.0
*/
module.exports = (function (caret) {
let editor = codex.editor;
/**
* @var {int} InputIndex - editable element in DOM
*/
caret.inputIndex = null;
/**
* @var {int} offset - caret position in a text node.
*/
caret.offset = null;
/**
* @var {int} focusedNodeIndex - we get index of child node from first-level block
*/
caret.focusedNodeIndex = null;
/**
* Creates Document Range and sets caret to the element.
* @protected
* @uses caret.save if you need to save caret position
* @param {Element} el - Changed Node.
*/
caret.set = function ( el, index, offset) {
offset = offset || caret.offset || 0;
index = index || caret.focusedNodeIndex || 0;
var childs = el.childNodes,
nodeToSet;
if ( childs.length === 0 ) {
nodeToSet = el;
} else {
nodeToSet = childs[index];
}
/** If Element is INPUT */
if (el.contentEditable != 'true') {
el.focus();
return;
}
if (editor.core.isDomNode(nodeToSet)) {
nodeToSet = editor.content.getDeepestTextNodeFromPosition(nodeToSet, nodeToSet.childNodes.length);
}
var range = document.createRange(),
selection = window.getSelection();
window.setTimeout(function () {
range.setStart(nodeToSet, offset);
range.setEnd(nodeToSet, offset);
selection.removeAllRanges();
selection.addRange(range);
editor.caret.saveCurrentInputIndex();
}, 20);
};
/**
* @protected
* Updates index of input and saves it in caret object
*/
caret.saveCurrentInputIndex = function () {
/** Index of Input that we paste sanitized content */
var selection = window.getSelection(),
inputs = editor.state.inputs,
focusedNode = selection.anchorNode,
focusedNodeHolder;
if (!focusedNode) {
return;
}
/** Looking for parent contentEditable block */
while (focusedNode && focusedNode.contentEditable != 'true') {
focusedNodeHolder = focusedNode.parentNode;
focusedNode = focusedNodeHolder;
}
/** Input index in DOM level */
var editableElementIndex = 0;
while (focusedNode != inputs[editableElementIndex]) {
editableElementIndex ++;
}
caret.inputIndex = editableElementIndex;
};
/**
* Returns current input index (caret object)
*/
caret.getCurrentInputIndex = function () {
return caret.inputIndex;
};
/**
* @param {int} index - index of first-level block after that we set caret into next input
*/
caret.setToNextBlock = function (index) {
var inputs = editor.state.inputs,
nextInput = inputs[index + 1];
if (!nextInput) {
editor.core.log('We are reached the end');
return;
}
/**
* When new Block created or deleted content of input
* We should add some text node to set caret
*/
if (!nextInput.childNodes.length) {
var emptyTextElement = document.createTextNode('');
nextInput.appendChild(emptyTextElement);
}
editor.caret.inputIndex = index + 1;
editor.caret.set(nextInput, 0, 0);
editor.content.workingNodeChanged(nextInput);
};
/**
* @param {int} index - index of target input.
* Sets caret to input with this index
*/
caret.setToBlock = function (index) {
var inputs = editor.state.inputs,
targetInput = inputs[index];
if ( !targetInput ) {
return;
}
/**
* When new Block created or deleted content of input
* We should add some text node to set caret
*/
if (!targetInput.childNodes.length) {
var emptyTextElement = document.createTextNode('');
targetInput.appendChild(emptyTextElement);
}
editor.caret.inputIndex = index;
editor.caret.set(targetInput, 0, 0);
editor.content.workingNodeChanged(targetInput);
};
/**
* @param {int} index - index of input
*/
caret.setToPreviousBlock = function (index) {
index = index || 0;
var inputs = editor.state.inputs,
previousInput = inputs[index - 1],
lastChildNode,
lengthOfLastChildNode,
emptyTextElement;
if (!previousInput) {
editor.core.log('We are reached first node');
return;
}
lastChildNode = editor.content.getDeepestTextNodeFromPosition(previousInput, previousInput.childNodes.length);
lengthOfLastChildNode = lastChildNode.length;
/**
* When new Block created or deleted content of input
* We should add some text node to set caret
*/
if (!previousInput.childNodes.length) {
emptyTextElement = document.createTextNode('');
previousInput.appendChild(emptyTextElement);
}
editor.caret.inputIndex = index - 1;
editor.caret.set(previousInput, previousInput.childNodes.length - 1, lengthOfLastChildNode);
editor.content.workingNodeChanged(inputs[index - 1]);
};
caret.position = {
atStart : function () {
var selection = window.getSelection(),
anchorOffset = selection.anchorOffset,
anchorNode = selection.anchorNode,
firstLevelBlock = editor.content.getFirstLevelBlock(anchorNode),
pluginsRender = firstLevelBlock.childNodes[0];
if (!editor.core.isDomNode(anchorNode)) {
anchorNode = anchorNode.parentNode;
}
var isFirstNode = anchorNode === pluginsRender.childNodes[0],
isOffsetZero = anchorOffset === 0;
return isFirstNode && isOffsetZero;
},
atTheEnd : function () {
var selection = window.getSelection(),
anchorOffset = selection.anchorOffset,
anchorNode = selection.anchorNode;
/** Caret is at the end of input */
return !anchorNode || !anchorNode.length || anchorOffset === anchorNode.length;
}
};
/**
* Inserts node at the caret location
* @param {HTMLElement|DocumentFragment} node
*/
caret.insertNode = function (node) {
var selection, range,
lastNode = node;
if (node.nodeType == editor.core.nodeTypes.DOCUMENT_FRAGMENT) {
lastNode = node.lastChild;
}
selection = window.getSelection();
range = selection.getRangeAt(0);
range.deleteContents();
range.insertNode(node);
range.setStartAfter(lastNode);
range.collapse(true);
selection.removeAllRanges();
selection.addRange(range);
};
return caret;
})({});

View File

@ -1,805 +0,0 @@
/**
* Codex Editor Content Module
* Works with DOM
*
* @module Codex Editor content module
*
* @author Codex Team
* @version 1.3.13
*
* @description Module works with Elements that have been appended to the main DOM
*/
module.exports = (function (content) {
let editor = codex.editor;
/**
* Links to current active block
* @type {null | Element}
*/
content.currentNode = null;
/**
* clicked in redactor area
* @type {null | Boolean}
*/
content.editorAreaHightlighted = null;
/**
* @deprecated
* Synchronizes redactor with original textarea
*/
content.sync = function () {
editor.core.log('syncing...');
/**
* Save redactor content to editor.state
*/
editor.state.html = editor.nodes.redactor.innerHTML;
};
/**
* Appends background to the block
*
* @description add CSS class to highlight visually first-level block area
*/
content.markBlock = function () {
editor.content.currentNode.classList.add(editor.ui.className.BLOCK_HIGHLIGHTED);
};
/**
* Clear background
*
* @description clears styles that highlights block
*/
content.clearMark = function () {
if (editor.content.currentNode) {
editor.content.currentNode.classList.remove(editor.ui.className.BLOCK_HIGHLIGHTED);
}
};
/**
* Finds first-level block
*
* @param {Element} node - selected or clicked in redactors area node
* @protected
*
* @description looks for first-level block.
* gets parent while node is not first-level
*/
content.getFirstLevelBlock = function (node) {
if (!editor.core.isDomNode(node)) {
node = node.parentNode;
}
if (node === editor.nodes.redactor || node === document.body) {
return null;
} else {
while(!node.classList.contains(editor.ui.className.BLOCK_CLASSNAME)) {
node = node.parentNode;
}
return node;
}
};
/**
* Trigger this event when working node changed
* @param {Element} targetNode - first-level of this node will be current
* @protected
*
* @description If targetNode is first-level then we set it as current else we look for parents to find first-level
*/
content.workingNodeChanged = function (targetNode) {
/** Clear background from previous marked block before we change */
editor.content.clearMark();
if (!targetNode) {
return;
}
content.currentNode = content.getFirstLevelBlock(targetNode);
};
/**
* Replaces one redactor block with another
* @protected
* @param {Element} targetBlock - block to replace. Mostly currentNode.
* @param {Element} newBlock
* @param {string} newBlockType - type of new block; we need to store it to data-attribute
*
* [!] Function does not saves old block content.
* You can get it manually and pass with newBlock.innerHTML
*/
content.replaceBlock = function (targetBlock, newBlock) {
if (!targetBlock || !newBlock) {
editor.core.log('replaceBlock: missed params');
return;
}
/** If target-block is not a frist-level block, then we iterate parents to find it */
while(!targetBlock.classList.contains(editor.ui.className.BLOCK_CLASSNAME)) {
targetBlock = targetBlock.parentNode;
}
/** Replacing */
editor.nodes.redactor.replaceChild(newBlock, targetBlock);
/**
* Set new node as current
*/
editor.content.workingNodeChanged(newBlock);
/**
* Add block handlers
*/
editor.ui.addBlockHandlers(newBlock);
/**
* Save changes
*/
editor.ui.saveInputs();
};
/**
* @protected
*
* Inserts new block to redactor
* Wrapps block into a DIV with BLOCK_CLASSNAME class
*
* @param blockData {object}
* @param blockData.block {Element} element with block content
* @param blockData.type {string} block plugin
* @param needPlaceCaret {bool} pass true to set caret in new block
*
*/
content.insertBlock = function ( blockData, needPlaceCaret ) {
var workingBlock = editor.content.currentNode,
newBlockContent = blockData.block,
blockType = blockData.type,
isStretched = blockData.stretched;
var newBlock = composeNewBlock_(newBlockContent, blockType, isStretched);
if (workingBlock) {
editor.core.insertAfter(workingBlock, newBlock);
} else {
/**
* If redactor is empty, append as first child
*/
editor.nodes.redactor.appendChild(newBlock);
}
/**
* Block handler
*/
editor.ui.addBlockHandlers(newBlock);
/**
* Set new node as current
*/
editor.content.workingNodeChanged(newBlock);
/**
* Save changes
*/
editor.ui.saveInputs();
if ( needPlaceCaret ) {
/**
* If we don't know input index then we set default value -1
*/
var currentInputIndex = editor.caret.getCurrentInputIndex() || -1;
if (currentInputIndex == -1) {
var editableElement = newBlock.querySelector('[contenteditable]'),
emptyText = document.createTextNode('');
editableElement.appendChild(emptyText);
editor.caret.set(editableElement, 0, 0);
editor.toolbar.move();
editor.toolbar.showPlusButton();
} else {
if (currentInputIndex === editor.state.inputs.length - 1)
return;
/** Timeout for browsers execution */
window.setTimeout(function () {
/** Setting to the new input */
editor.caret.setToNextBlock(currentInputIndex);
editor.toolbar.move();
editor.toolbar.open();
}, 10);
}
}
/**
* Block is inserted, wait for new click that defined focusing on editors area
* @type {boolean}
*/
content.editorAreaHightlighted = false;
};
/**
* Replaces blocks with saving content
* @protected
* @param {Element} noteToReplace
* @param {Element} newNode
* @param {Element} blockType
*/
content.switchBlock = function (blockToReplace, newBlock, tool) {
tool = tool || editor.content.currentNode.dataset.tool;
var newBlockComposed = composeNewBlock_(newBlock, tool);
/** Replacing */
editor.content.replaceBlock(blockToReplace, newBlockComposed);
/** Save new Inputs when block is changed */
editor.ui.saveInputs();
};
/**
* Iterates between child noted and looking for #text node on deepest level
* @protected
*
* @param {Element} block - node where find
* @param {int} postiton - starting postion
* Example: childNodex.length to find from the end
* or 0 to find from the start
* @return {Text} block
* @uses DFS
*/
content.getDeepestTextNodeFromPosition = function (block, position) {
/**
* Clear Block from empty and useless spaces with trim.
* Such nodes we should remove
*/
var blockChilds = block.childNodes,
index,
node,
text;
for(index = 0; index < blockChilds.length; index++) {
node = blockChilds[index];
if (node.nodeType == editor.core.nodeTypes.TEXT) {
text = node.textContent.trim();
/** Text is empty. We should remove this child from node before we start DFS
* decrease the quantity of childs.
*/
if (text === '') {
block.removeChild(node);
position--;
}
}
}
if (block.childNodes.length === 0) {
return document.createTextNode('');
}
/** Setting default position when we deleted all empty nodes */
if ( position < 0 )
position = 1;
var lookingFromStart = false;
/** For looking from START */
if (position === 0) {
lookingFromStart = true;
position = 1;
}
while ( position ) {
/** initial verticle of node. */
if ( lookingFromStart ) {
block = block.childNodes[0];
} else {
block = block.childNodes[position - 1];
}
if ( block.nodeType == editor.core.nodeTypes.TAG ) {
position = block.childNodes.length;
} else if (block.nodeType == editor.core.nodeTypes.TEXT ) {
position = 0;
}
}
return block;
};
/**
* @private
* @param {Element} block - current plugins render
* @param {String} tool - plugins name
* @param {Boolean} isStretched - make stretched block or not
*
* @description adds necessary information to wrap new created block by first-level holder
*/
var composeNewBlock_ = function (block, tool, isStretched) {
var newBlock = editor.draw.node('DIV', editor.ui.className.BLOCK_CLASSNAME, {}),
blockContent = editor.draw.node('DIV', editor.ui.className.BLOCK_CONTENT, {});
blockContent.appendChild(block);
newBlock.appendChild(blockContent);
if (isStretched) {
blockContent.classList.add(editor.ui.className.BLOCK_STRETCHED);
}
newBlock.dataset.tool = tool;
return newBlock;
};
/**
* Returns Range object of current selection
* @protected
*/
content.getRange = function () {
var selection = window.getSelection().getRangeAt(0);
return selection;
};
/**
* Divides block in two blocks (after and before caret)
*
* @protected
* @param {int} inputIndex - target input index
*
* @description splits current input content to the separate blocks
* When enter is pressed among the words, that text will be splited.
*/
content.splitBlock = function (inputIndex) {
var selection = window.getSelection(),
anchorNode = selection.anchorNode,
anchorNodeText = anchorNode.textContent,
caretOffset = selection.anchorOffset,
textBeforeCaret,
textNodeBeforeCaret,
textAfterCaret,
textNodeAfterCaret;
var currentBlock = editor.content.currentNode.querySelector('[contentEditable]');
textBeforeCaret = anchorNodeText.substring(0, caretOffset);
textAfterCaret = anchorNodeText.substring(caretOffset);
textNodeBeforeCaret = document.createTextNode(textBeforeCaret);
if (textAfterCaret) {
textNodeAfterCaret = document.createTextNode(textAfterCaret);
}
var previousChilds = [],
nextChilds = [],
reachedCurrent = false;
if (textNodeAfterCaret) {
nextChilds.push(textNodeAfterCaret);
}
for ( var i = 0, child; !!(child = currentBlock.childNodes[i]); i++) {
if ( child != anchorNode ) {
if ( !reachedCurrent ) {
previousChilds.push(child);
} else {
nextChilds.push(child);
}
} else {
reachedCurrent = true;
}
}
/** Clear current input */
editor.state.inputs[inputIndex].innerHTML = '';
/**
* Append all childs founded before anchorNode
*/
var previousChildsLength = previousChilds.length;
for(i = 0; i < previousChildsLength; i++) {
editor.state.inputs[inputIndex].appendChild(previousChilds[i]);
}
editor.state.inputs[inputIndex].appendChild(textNodeBeforeCaret);
/**
* Append text node which is after caret
*/
var nextChildsLength = nextChilds.length,
newNode = document.createElement('div');
for(i = 0; i < nextChildsLength; i++) {
newNode.appendChild(nextChilds[i]);
}
newNode = newNode.innerHTML;
/** This type of block creates when enter is pressed */
var NEW_BLOCK_TYPE = editor.settings.initialBlockPlugin;
/**
* Make new paragraph with text after caret
*/
editor.content.insertBlock({
type : NEW_BLOCK_TYPE,
block : editor.tools[NEW_BLOCK_TYPE].render({
text : newNode
})
}, true );
};
/**
* Merges two blocks current and target
* If target index is not exist, then previous will be as target
*
* @protected
* @param {int} currentInputIndex
* @param {int} targetInputIndex
*
* @description gets two inputs indexes and merges into one
*/
content.mergeBlocks = function (currentInputIndex, targetInputIndex) {
/** If current input index is zero, then prevent method execution */
if (currentInputIndex === 0) {
return;
}
var targetInput,
currentInputContent = editor.state.inputs[currentInputIndex].innerHTML;
if (!targetInputIndex) {
targetInput = editor.state.inputs[currentInputIndex - 1];
} else {
targetInput = editor.state.inputs[targetInputIndex];
}
targetInput.innerHTML += currentInputContent;
};
/**
* Iterates all right siblings and parents, which has right siblings
* while it does not reached the first-level block
*
* @param {Element} node
* @return {boolean}
*/
content.isLastNode = function (node) {
// console.log('погнали перебор родителей');
var allChecked = false;
while ( !allChecked ) {
// console.log('Смотрим на %o', node);
// console.log('Проверим, пустые ли соседи справа');
if ( !allSiblingsEmpty_(node) ) {
// console.log('Есть непустые соседи. Узел не последний. Выходим.');
return false;
}
node = node.parentNode;
/**
* Проверяем родителей до тех пор, пока не найдем блок первого уровня
*/
if ( node.classList.contains(editor.ui.className.BLOCK_CONTENT) ) {
allChecked = true;
}
}
return true;
};
/**
* Checks if all element right siblings is empty
* @param node
*/
var allSiblingsEmpty_ = function (node) {
/**
* Нужно убедиться, что после пустого соседа ничего нет
*/
var sibling = node.nextSibling;
while ( sibling ) {
if (sibling.textContent.length) {
return false;
}
sibling = sibling.nextSibling;
}
return true;
};
/**
* @public
*
* @param {string} htmlData - html content as string
* @param {string} plainData - plain text
* @return {string} - html content as string
*/
content.wrapTextWithParagraphs = function (htmlData, plainData) {
if (!htmlData.trim()) {
return wrapPlainTextWithParagraphs(plainData);
}
var wrapper = document.createElement('DIV'),
newWrapper = document.createElement('DIV'),
i,
paragraph,
firstLevelBlocks = ['DIV', 'P'],
blockTyped,
node;
/**
* Make HTML Element to Wrap Text
* It allows us to work with input data as HTML content
*/
wrapper.innerHTML = htmlData;
paragraph = document.createElement('P');
for (i = 0; i < wrapper.childNodes.length; i++) {
node = wrapper.childNodes[i];
blockTyped = firstLevelBlocks.indexOf(node.tagName) != -1;
/**
* If node is first-levet
* we add this node to our new wrapper
*/
if ( blockTyped ) {
/**
* If we had splitted inline nodes to paragraph before
*/
if ( paragraph.childNodes.length ) {
newWrapper.appendChild(paragraph.cloneNode(true));
/** empty paragraph */
paragraph = null;
paragraph = document.createElement('P');
}
newWrapper.appendChild(node.cloneNode(true));
} else {
/** Collect all inline nodes to one as paragraph */
paragraph.appendChild(node.cloneNode(true));
/** if node is last we should append this node to paragraph and paragraph to new wrapper */
if ( i == wrapper.childNodes.length - 1 ) {
newWrapper.appendChild(paragraph.cloneNode(true));
}
}
}
return newWrapper.innerHTML;
};
/**
* Splits strings on new line and wraps paragraphs with <p> tag
* @param plainText
* @returns {string}
*/
var wrapPlainTextWithParagraphs = function (plainText) {
if (!plainText) return '';
return '<p>' + plainText.split('\n\n').join('</p><p>') + '</p>';
};
/**
* Finds closest Contenteditable parent from Element
* @param {Element} node element looking from
* @return {Element} node contenteditable
*/
content.getEditableParent = function (node) {
while (node && node.contentEditable != 'true') {
node = node.parentNode;
}
return node;
};
/**
* Clear editors content
*
* @param {Boolean} all if true, delete all article data (content, id, etc.)
*/
content.clear = function (all) {
editor.nodes.redactor.innerHTML = '';
editor.content.sync();
editor.ui.saveInputs();
if (all) {
editor.state.blocks = {};
} else if (editor.state.blocks) {
editor.state.blocks.items = [];
}
editor.content.currentNode = null;
};
/**
*
* Load new data to editor
* If editor is not empty, just append articleData.items
*
* @param articleData.items
*/
content.load = function (articleData) {
var currentContent = Object.assign({}, editor.state.blocks);
editor.content.clear();
if (!Object.keys(currentContent).length) {
editor.state.blocks = articleData;
} else if (!currentContent.items) {
currentContent.items = articleData.items;
editor.state.blocks = currentContent;
} else {
currentContent.items = currentContent.items.concat(articleData.items);
editor.state.blocks = currentContent;
}
editor.renderer.makeBlocksFromData();
};
return content;
})({});

View File

@ -1,389 +0,0 @@
/**
* Codex Editor Core
*
* @author Codex Team
* @version 1.1.3
*/
module.exports = (function (core) {
let editor = codex.editor;
/**
* @public
*
* Editor preparing method
* @return Promise
*/
core.prepare = function (userSettings) {
return new Promise(function (resolve, reject) {
if ( userSettings ) {
editor.settings.tools = userSettings.tools || editor.settings.tools;
}
if (userSettings.data) {
editor.state.blocks = userSettings.data;
}
if (userSettings.initialBlockPlugin) {
editor.settings.initialBlockPlugin = userSettings.initialBlockPlugin;
}
if (userSettings.sanitizer) {
editor.settings.sanitizer = userSettings.sanitizer;
}
editor.hideToolbar = userSettings.hideToolbar;
editor.settings.placeholder = userSettings.placeholder || '';
editor.nodes.holder = document.getElementById(userSettings.holderId || editor.settings.holderId);
if (typeof editor.nodes.holder === undefined || editor.nodes.holder === null) {
reject(Error("Holder wasn't found by ID: #" + userSettings.holderId));
} else {
resolve();
}
});
};
/**
* Logging method
* @param type = ['log', 'info', 'warn']
*/
core.log = function (msg, type, arg) {
type = type || 'log';
if (!arg) {
arg = msg || 'undefined';
msg = '[codex-editor]: %o';
} else {
msg = '[codex-editor]: ' + msg;
}
try{
if ( 'console' in window && window.console[ type ] ) {
if ( arg ) window.console[ type ]( msg, arg );
else window.console[ type ]( msg );
}
}catch(e) {}
};
/**
* @protected
*
* Helper for insert one element after another
*/
core.insertAfter = function (target, element) {
target.parentNode.insertBefore(element, target.nextSibling);
};
/**
* @const
*
* Readable DOM-node types map
*/
core.nodeTypes = {
TAG : 1,
TEXT : 3,
COMMENT : 8,
DOCUMENT_FRAGMENT: 11
};
/**
* @const
* Readable keys map
*/
core.keys = { BACKSPACE: 8, TAB: 9, ENTER: 13, SHIFT: 16, CTRL: 17, ALT: 18, ESC: 27, SPACE: 32, LEFT: 37, UP: 38, DOWN: 40, RIGHT: 39, DELETE: 46, META: 91 };
/**
* @protected
*
* Check object for DOM node
*/
core.isDomNode = function (el) {
return el && typeof el === 'object' && el.nodeType && el.nodeType == this.nodeTypes.TAG;
};
/**
* Checks passed object for emptiness
* @require ES5 - Object.keys
* @param {object}
*/
core.isEmpty = function ( obj ) {
return Object.keys(obj).length === 0;
};
/**
* Native Ajax
* @param {String} settings.url - request URL
* @param {function} settings.beforeSend - returned value will be passed as context to the Success, Error and Progress callbacks
* @param {function} settings.success
* @param {function} settings.progress
*/
core.ajax = function (settings) {
if (!settings || !settings.url) {
return;
}
var XMLHTTP = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP'),
encodedString,
isFormData,
prop;
settings.async = true;
settings.type = settings.type || 'GET';
settings.data = settings.data || '';
settings['content-type'] = settings['content-type'] || 'application/json; charset=utf-8';
if (settings.type == 'GET' && settings.data) {
settings.url = /\?/.test(settings.url) ? settings.url + '&' + settings.data : settings.url + '?' + settings.data;
} else {
encodedString = '';
for(prop in settings.data) {
encodedString += (prop + '=' + encodeURIComponent(settings.data[prop]) + '&');
}
}
if (settings.withCredentials) {
XMLHTTP.withCredentials = true;
}
/**
* Value returned in beforeSend funtion will be passed as context to the other response callbacks
* If beforeSend returns false, AJAX will be blocked
*/
let responseContext,
beforeSendResult;
if (typeof settings.beforeSend === 'function') {
beforeSendResult = settings.beforeSend.call();
if (beforeSendResult === false) {
return;
}
}
XMLHTTP.open( settings.type, settings.url, settings.async );
/**
* If we send FormData, we need no content-type header
*/
isFormData = isFormData_(settings.data);
if (!isFormData) {
if (settings.type !== 'POST') {
XMLHTTP.setRequestHeader('Content-type', settings['content-type']);
} else {
XMLHTTP.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
}
}
XMLHTTP.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
responseContext = beforeSendResult || XMLHTTP;
if (typeof settings.progress === 'function') {
XMLHTTP.upload.onprogress = settings.progress.bind(responseContext);
}
XMLHTTP.onreadystatechange = function () {
if (XMLHTTP.readyState === 4) {
if (XMLHTTP.status === 200) {
if (typeof settings.success === 'function') {
settings.success.call(responseContext, XMLHTTP.responseText);
}
} else {
if (typeof settings.error === 'function') {
settings.error.call(responseContext, XMLHTTP.responseText, XMLHTTP.status);
}
}
}
};
if (isFormData) {
// Sending FormData
XMLHTTP.send(settings.data);
} else {
// POST requests
XMLHTTP.send(encodedString);
}
return XMLHTTP;
};
/**
* Appends script to head of document
* @return Promise
*/
core.importScript = function (scriptPath, instanceName) {
return new Promise(function (resolve, reject) {
let script;
/** Script is already loaded */
if ( !instanceName ) {
reject('Instance name is missed');
} else if ( document.getElementById(editor.scriptPrefix + instanceName) ) {
resolve(scriptPath);
}
script = document.createElement('SCRIPT');
script.async = true;
script.defer = true;
script.id = editor.scriptPrefix + instanceName;
script.onload = function () {
resolve(scriptPath);
};
script.onerror = function () {
reject(scriptPath);
};
script.src = scriptPath;
document.head.appendChild(script);
});
};
/**
* Function for checking is it FormData object to send.
* @param {Object} object to check
* @return boolean
*/
var isFormData_ = function (object) {
return object instanceof FormData;
};
/**
* Check block
* @param target
* @description Checks target is it native input
*/
core.isNativeInput = function (target) {
var nativeInputAreas = ['INPUT', 'TEXTAREA'];
return nativeInputAreas.indexOf(target.tagName) != -1;
};
/**
* Check if block is empty
* We should check block textContent, child native inputs and some exceptions like IMG and IFRAME
*
* @param block
* @returns {boolean}
*/
core.isBlockEmpty = function (block) {
const EXCEPTION_TAGS = ['IMG', 'IFRAME'];
var nativeInputs = block.querySelectorAll('textarea, input'),
nativeInputsAreEmpty = true,
textContentIsEmpty = !block.textContent.trim();
Array.prototype.forEach.call(nativeInputs, function (input) {
if (input.type == 'textarea' || input.type == 'text') {
nativeInputsAreEmpty = nativeInputsAreEmpty && !input.value.trim();
}
});
return textContentIsEmpty && nativeInputsAreEmpty && !EXCEPTION_TAGS.includes(block.tagName);
};
return core;
})({});

View File

@ -1,98 +0,0 @@
/**
* Codex Editor Destroyer module
*
* @auhor Codex Team
* @version 1.0
*/
module.exports = function (destroyer) {
let editor = codex.editor;
destroyer.removeNodes = function () {
editor.nodes.wrapper.remove();
editor.nodes.notifications.remove();
};
destroyer.destroyPlugins = function () {
for (var tool in editor.tools) {
if (typeof editor.tools[tool].destroy === 'function') {
editor.tools[tool].destroy();
}
}
};
destroyer.destroyScripts = function () {
var scripts = document.getElementsByTagName('SCRIPT');
for (var i = 0; i < scripts.length; i++) {
if (scripts[i].id.indexOf(editor.scriptPrefix) + 1) {
scripts[i].remove();
i--;
}
}
};
/**
* Delete editor data from webpage.
* You should send settings argument with boolean flags:
* @param settings.ui- remove redactor event listeners and DOM nodes
* @param settings.scripts - remove redactor scripts from DOM
* @param settings.plugins - remove plugin's objects
* @param settings.core - remove editor core. You can remove core only if UI and scripts flags is true
* }
*
*/
destroyer.destroy = function (settings) {
if (!settings || typeof settings !== 'object') {
return;
}
if (settings.ui) {
destroyer.removeNodes();
editor.listeners.removeAll();
}
if (settings.scripts) {
destroyer.destroyScripts();
}
if (settings.plugins) {
destroyer.destroyPlugins();
}
if (settings.ui && settings.scripts && settings.core) {
delete codex.editor;
}
};
return destroyer;
}({});

View File

@ -1,316 +0,0 @@
/**
* Codex Editor Draw module
*
* @author Codex Team
* @version 1.0.
*/
module.exports = (function (draw) {
/**
* Base editor wrapper
*/
draw.wrapper = function () {
var wrapper = document.createElement('div');
wrapper.className += 'codex-editor';
return wrapper;
};
/**
* Content-editable holder
*/
draw.redactor = function () {
var redactor = document.createElement('div');
redactor.className += 'ce-redactor';
return redactor;
};
draw.ceBlock = function () {
var block = document.createElement('DIV');
block.className += 'ce_block';
return block;
};
/**
* Empty toolbar with toggler
*/
draw.toolbar = function () {
var bar = document.createElement('div');
bar.className += 'ce-toolbar';
return bar;
};
draw.toolbarContent = function () {
var wrapper = document.createElement('DIV');
wrapper.classList.add('ce-toolbar__content');
return wrapper;
};
/**
* Inline toolbar
*/
draw.inlineToolbar = function () {
var bar = document.createElement('DIV');
bar.className += 'ce-toolbar-inline';
return bar;
};
/**
* Wrapper for inline toobar buttons
*/
draw.inlineToolbarButtons = function () {
var wrapper = document.createElement('DIV');
wrapper.className += 'ce-toolbar-inline__buttons';
return wrapper;
};
/**
* For some actions
*/
draw.inlineToolbarActions = function () {
var wrapper = document.createElement('DIV');
wrapper.className += 'ce-toolbar-inline__actions';
return wrapper;
};
draw.inputForLink = function () {
var input = document.createElement('INPUT');
input.type = 'input';
input.className += 'inputForLink';
input.placeholder = 'Вставьте ссылку ...';
input.setAttribute('form', 'defaultForm');
input.setAttribute('autofocus', 'autofocus');
return input;
};
/**
* @todo Desc
*/
draw.blockButtons = function () {
var block = document.createElement('div');
block.className += 'ce-toolbar__actions';
return block;
};
/**
* Block settings panel
*/
draw.blockSettings = function () {
var settings = document.createElement('div');
settings.className += 'ce-settings';
return settings;
};
draw.defaultSettings = function () {
var div = document.createElement('div');
div.classList.add('ce-settings_default');
return div;
};
draw.pluginsSettings = function () {
var div = document.createElement('div');
div.classList.add('ce-settings_plugin');
return div;
};
draw.plusButton = function () {
var button = document.createElement('span');
button.className = 'ce-toolbar__plus';
// button.innerHTML = '<i class="ce-icon-plus"></i>';
return button;
};
/**
* Settings button in toolbar
*/
draw.settingsButton = function () {
var toggler = document.createElement('span');
toggler.className = 'ce-toolbar__settings-btn';
/** Toggler button*/
toggler.innerHTML = '<i class="ce-icon-cog"></i>';
return toggler;
};
/**
* Redactor tools wrapper
*/
draw.toolbox = function () {
var wrapper = document.createElement('div');
wrapper.className = 'ce-toolbar__tools';
return wrapper;
};
/**
* @protected
*
* Draws tool buttons for toolbox
*
* @param {String} type
* @param {String} classname
* @returns {Element}
*/
draw.toolbarButton = function (type, classname) {
var button = document.createElement('li'),
toolIcon = document.createElement('i'),
toolTitle = document.createElement('span');
button.dataset.type = type;
button.setAttribute('title', type);
toolIcon.classList.add(classname);
toolTitle.classList.add('ce_toolbar_tools--title');
button.appendChild(toolIcon);
button.appendChild(toolTitle);
return button;
};
/**
* @protected
*
* Draws tools for inline toolbar
*
* @param {String} type
* @param {String} classname
*/
draw.toolbarButtonInline = function (type, classname) {
var button = document.createElement('BUTTON'),
toolIcon = document.createElement('I');
button.type = 'button';
button.dataset.type = type;
toolIcon.classList.add(classname);
button.appendChild(toolIcon);
return button;
};
/**
* Redactor block
*/
draw.block = function (tagName, content) {
var node = document.createElement(tagName);
node.innerHTML = content || '';
return node;
};
/**
* Creates Node with passed tagName and className
* @param {string} tagName
* @param {string} className
* @param {object} properties - allow to assign properties
*/
draw.node = function ( tagName, className, properties ) {
var el = document.createElement( tagName );
if ( className ) el.className = className;
if ( properties ) {
for (var name in properties) {
el[name] = properties[name];
}
}
return el;
};
/**
* Unavailable plugin block
*/
draw.unavailableBlock = function () {
var wrapper = document.createElement('DIV');
wrapper.classList.add('cdx-unavailable-block');
return wrapper;
};
return draw;
})({});

View File

@ -1,192 +0,0 @@
/**
* Codex Editor Listeners module
*
* @author Codex Team
* @version 1.0
*/
/**
* Module-decorator for event listeners assignment
*/
module.exports = function (listeners) {
var allListeners = [];
/**
* Search methods
*
* byElement, byType and byHandler returns array of suitable listeners
* one and all takes element, eventType, and handler and returns first (all) suitable listener
*
*/
listeners.search = function () {
var byElement = function (element, context) {
var listenersOnElement = [];
context = context || allListeners;
for (var i = 0; i < context.length; i++) {
var listener = context[i];
if (listener.element === element) {
listenersOnElement.push(listener);
}
}
return listenersOnElement;
};
var byType = function (eventType, context) {
var listenersWithType = [];
context = context || allListeners;
for (var i = 0; i < context.length; i++) {
var listener = context[i];
if (listener.type === eventType) {
listenersWithType.push(listener);
}
}
return listenersWithType;
};
var byHandler = function (handler, context) {
var listenersWithHandler = [];
context = context || allListeners;
for (var i = 0; i < context.length; i++) {
var listener = context[i];
if (listener.handler === handler) {
listenersWithHandler.push(listener);
}
}
return listenersWithHandler;
};
var one = function (element, eventType, handler) {
var result = allListeners;
if (element)
result = byElement(element, result);
if (eventType)
result = byType(eventType, result);
if (handler)
result = byHandler(handler, result);
return result[0];
};
var all = function (element, eventType, handler) {
var result = allListeners;
if (element)
result = byElement(element, result);
if (eventType)
result = byType(eventType, result);
if (handler)
result = byHandler(handler, result);
return result;
};
return {
byElement : byElement,
byType : byType,
byHandler : byHandler,
one : one,
all : all
};
}();
listeners.add = function (element, eventType, handler, isCapture) {
element.addEventListener(eventType, handler, isCapture);
var data = {
element: element,
type: eventType,
handler: handler
};
var alreadyAddedListener = listeners.search.one(element, eventType, handler);
if (!alreadyAddedListener) {
allListeners.push(data);
}
};
listeners.remove = function (element, eventType, handler) {
element.removeEventListener(eventType, handler);
var existingListeners = listeners.search.all(element, eventType, handler);
for (var i = 0; i < existingListeners.length; i++) {
var index = allListeners.indexOf(existingListeners[i]);
if (index > 0) {
allListeners.splice(index, 1);
}
}
};
listeners.removeAll = function () {
allListeners.map(function (current) {
listeners.remove(current.element, current.type, current.handler);
});
};
listeners.get = function (element, eventType, handler) {
return listeners.search.all(element, eventType, handler);
};
return listeners;
}({});

View File

@ -1,231 +0,0 @@
/**
* Codex Editor Notification Module
*
* @author Codex Team
* @version 1.0
*/
module.exports = (function (notifications) {
let editor = codex.editor;
var queue = [];
var addToQueue = function (settings) {
queue.push(settings);
var index = 0;
while ( index < queue.length && queue.length > 5) {
if (queue[index].type == 'confirm' || queue[index].type == 'prompt') {
index++;
continue;
}
queue[index].close();
queue.splice(index, 1);
}
};
notifications.createHolder = function () {
var holder = editor.draw.node('DIV', 'cdx-notifications-block');
editor.nodes.notifications = document.body.appendChild(holder);
return holder;
};
/**
* Error notificator. Shows block with message
* @protected
*/
notifications.errorThrown = function (errorMsg, event) {
editor.notifications.notification({message: 'This action is not available currently', type: event.type});
};
/**
*
* Appends notification
*
* settings = {
* type - notification type (reserved types: alert, confirm, prompt). Just add class 'cdx-notification-'+type
* message - notification message
* okMsg - confirm button text (default - 'Ok')
* cancelBtn - cancel button text (default - 'Cancel'). Only for confirm and prompt types
* confirm - function-handler for ok button click
* cancel - function-handler for cancel button click. Only for confirm and prompt types
* time - time (in seconds) after which notification will close (default - 10s)
* }
*
* @param settings
*/
notifications.notification = function (constructorSettings) {
/** Private vars and methods */
var notification = null,
cancel = null,
type = null,
confirm = null,
inputField = null;
var confirmHandler = function () {
close();
if (typeof confirm !== 'function' ) {
return;
}
if (type == 'prompt') {
confirm(inputField.value);
return;
}
confirm();
};
var cancelHandler = function () {
close();
if (typeof cancel !== 'function' ) {
return;
}
cancel();
};
/** Public methods */
function create(settings) {
if (!(settings && settings.message)) {
editor.core.log('Can\'t create notification. Message is missed');
return;
}
settings.type = settings.type || 'alert';
settings.time = settings.time*1000 || 10000;
var wrapper = editor.draw.node('DIV', 'cdx-notification'),
message = editor.draw.node('DIV', 'cdx-notification__message'),
input = editor.draw.node('INPUT', 'cdx-notification__input'),
okBtn = editor.draw.node('SPAN', 'cdx-notification__ok-btn'),
cancelBtn = editor.draw.node('SPAN', 'cdx-notification__cancel-btn');
message.textContent = settings.message;
okBtn.textContent = settings.okMsg || 'ОК';
cancelBtn.textContent = settings.cancelMsg || 'Отмена';
editor.listeners.add(okBtn, 'click', confirmHandler);
editor.listeners.add(cancelBtn, 'click', cancelHandler);
wrapper.appendChild(message);
if (settings.type == 'prompt') {
wrapper.appendChild(input);
}
wrapper.appendChild(okBtn);
if (settings.type == 'prompt' || settings.type == 'confirm') {
wrapper.appendChild(cancelBtn);
}
wrapper.classList.add('cdx-notification-' + settings.type);
wrapper.dataset.type = settings.type;
notification = wrapper;
type = settings.type;
confirm = settings.confirm;
cancel = settings.cancel;
inputField = input;
if (settings.type != 'prompt' && settings.type != 'confirm') {
window.setTimeout(close, settings.time);
}
};
/**
* Show notification block
*/
function send() {
editor.nodes.notifications.appendChild(notification);
inputField.focus();
editor.nodes.notifications.classList.add('cdx-notification__notification-appending');
window.setTimeout(function () {
editor.nodes.notifications.classList.remove('cdx-notification__notification-appending');
}, 100);
addToQueue({type: type, close: close});
};
/**
* Remove notification block
*/
function close() {
notification.remove();
};
if (constructorSettings) {
create(constructorSettings);
send();
}
return {
create: create,
send: send,
close: close
};
};
notifications.clear = function () {
editor.nodes.notifications.innerHTML = '';
queue = [];
};
return notifications;
})({});

View File

@ -1,36 +0,0 @@
/**
* Codex Editor Parser Module
*
* @author Codex Team
* @version 1.1
*/
module.exports = (function (parser) {
let editor = codex.editor;
/** inserting text */
parser.insertPastedContent = function (blockType, tag) {
editor.content.insertBlock({
type : blockType.type,
block : blockType.render({
text : tag.innerHTML
})
});
};
/**
* Check DOM node for display style: separated block or child-view
*/
parser.isFirstLevelBlock = function (node) {
return node.nodeType == editor.core.nodeTypes.TAG &&
node.classList.contains(editor.ui.className.BLOCK_CLASSNAME);
};
return parser;
})({});

View File

@ -1,278 +0,0 @@
/**
* Codex Editor Paste module
*
* @author Codex Team
* @version 1.1.1
*/
module.exports = function (paste) {
let editor = codex.editor;
var patterns = [];
paste.prepare = function () {
var tools = editor.tools;
for (var tool in tools) {
if (!tools[tool].renderOnPastePatterns || !Array.isArray(tools[tool].renderOnPastePatterns)) {
continue;
}
tools[tool].renderOnPastePatterns.map(function (pattern) {
patterns.push(pattern);
});
}
return Promise.resolve();
};
/**
* Saves data
* @param event
*/
paste.pasted = function (event) {
var clipBoardData = event.clipboardData || window.clipboardData,
content = clipBoardData.getData('Text');
var result = analize(content);
if (result) {
event.preventDefault();
event.stopImmediatePropagation();
}
return result;
};
/**
* Analizes pated string and calls necessary method
*/
var analize = function (string) {
var result = false,
content = editor.content.currentNode,
plugin = content.dataset.tool;
patterns.map( function (pattern) {
var execArray = pattern.regex.exec(string),
match = execArray && execArray[0];
if ( match && match === string.trim()) {
/** current block is not empty */
if ( content.textContent.trim() && plugin == editor.settings.initialBlockPlugin ) {
pasteToNewBlock_();
}
pattern.callback(string, pattern);
result = true;
}
});
return result;
};
var pasteToNewBlock_ = function () {
/** Create new initial block */
editor.content.insertBlock({
type : editor.settings.initialBlockPlugin,
block : editor.tools[editor.settings.initialBlockPlugin].render({
text : ''
})
}, false);
};
/**
* This method prevents default behaviour.
*
* @param {Object} event
* @protected
*
* @description We get from clipboard pasted data, sanitize, make a fragment that contains of this sanitized nodes.
* Firstly, we need to memorize the caret position. We can do that by getting the range of selection.
* After all, we insert clear fragment into caret placed position. Then, we should move the caret to the last node
*/
paste.blockPasteCallback = function (event) {
if (!needsToHandlePasteEvent(event.target)) {
return;
}
/** Prevent default behaviour */
event.preventDefault();
/** get html pasted data - dirty data */
var htmlData = event.clipboardData.getData('text/html'),
plainData = event.clipboardData.getData('text/plain');
/** Temporary DIV that is used to work with text's paragraphs as DOM-elements*/
var paragraphs = editor.draw.node('DIV', '', {}),
cleanData,
wrappedData;
/** Create fragment, that we paste to range after proccesing */
cleanData = editor.sanitizer.clean(htmlData);
/**
* We wrap pasted text with <p> tags to split it logically
* @type {string}
*/
wrappedData = editor.content.wrapTextWithParagraphs(cleanData, plainData);
paragraphs.innerHTML = wrappedData;
/**
* If there only one paragraph, just insert in at the caret location
*/
if (paragraphs.childNodes.length == 1) {
emulateUserAgentBehaviour(paragraphs.firstChild);
return;
}
insertPastedParagraphs(paragraphs.childNodes);
};
/**
* Checks if we should handle paste event on block
* @param block
*
* @return {boolean}
*/
var needsToHandlePasteEvent = function (block) {
/** If area is input or textarea then allow default behaviour */
if ( editor.core.isNativeInput(block) ) {
return false;
}
var editableParent = editor.content.getEditableParent(block);
/** Allow paste when event target placed in Editable element */
if (!editableParent) {
return false;
}
return true;
};
/**
* Inserts new initial plugin blocks with data in paragraphs
*
* @param {Array} paragraphs - array of paragraphs (<p></p>) whit content, that should be inserted
*/
var insertPastedParagraphs = function (paragraphs) {
var NEW_BLOCK_TYPE = editor.settings.initialBlockPlugin,
currentNode = editor.content.currentNode;
paragraphs.forEach(function (paragraph) {
/** Don't allow empty paragraphs */
if (editor.core.isBlockEmpty(paragraph)) {
return;
}
editor.content.insertBlock({
type : NEW_BLOCK_TYPE,
block : editor.tools[NEW_BLOCK_TYPE].render({
text : paragraph.innerHTML
})
});
editor.caret.inputIndex++;
});
editor.caret.setToPreviousBlock(editor.caret.getCurrentInputIndex() + 1);
/**
* If there was no data in working node, remove it
*/
if (editor.core.isBlockEmpty(currentNode)) {
currentNode.remove();
editor.ui.saveInputs();
}
};
/**
* Inserts node content at the caret position
*
* @param {Node} node - DOM node (could be DocumentFragment), that should be inserted at the caret location
*/
var emulateUserAgentBehaviour = function (node) {
var newNode;
if (node.childElementCount) {
newNode = document.createDocumentFragment();
node.childNodes.forEach(function (current) {
if (!editor.core.isDomNode(current) && current.data.trim() === '') {
return;
}
newNode.appendChild(current.cloneNode(true));
});
} else {
newNode = document.createTextNode(node.textContent);
}
editor.caret.insertNode(newNode);
};
return paste;
}({});

View File

@ -1,203 +0,0 @@
/**
* Codex Editor Renderer Module
*
* @author Codex Team
* @version 1.0
*/
module.exports = (function (renderer) {
let editor = codex.editor;
/**
* Asyncronously parses input JSON to redactor blocks
*/
renderer.makeBlocksFromData = function () {
/**
* If redactor is empty, add first paragraph to start writing
*/
if (editor.core.isEmpty(editor.state.blocks) || !editor.state.blocks.items.length) {
editor.ui.addInitialBlock();
return;
}
Promise.resolve()
/** First, get JSON from state */
.then(function () {
return editor.state.blocks;
})
/** Then, start to iterate they */
.then(editor.renderer.appendBlocks)
/** Write log if something goes wrong */
.catch(function (error) {
editor.core.log('Error while parsing JSON: %o', 'error', error);
});
};
/**
* Parses JSON to blocks
* @param {object} data
* @return Primise -> nodeList
*/
renderer.appendBlocks = function (data) {
var blocks = data.items;
/**
* Sequence of one-by-one blocks appending
* Uses to save blocks order after async-handler
*/
var nodeSequence = Promise.resolve();
for (var index = 0; index < blocks.length ; index++ ) {
/** Add node to sequence at specified index */
editor.renderer.appendNodeAtIndex(nodeSequence, blocks, index);
}
};
/**
* Append node at specified index
*/
renderer.appendNodeAtIndex = function (nodeSequence, blocks, index) {
/** We need to append node to sequence */
nodeSequence
/** first, get node async-aware */
.then(function () {
return editor.renderer.getNodeAsync(blocks, index);
})
/**
* second, compose editor-block from JSON object
*/
.then(editor.renderer.createBlockFromData)
/**
* now insert block to redactor
*/
.then(function (blockData) {
/**
* blockData has 'block', 'type' and 'stretched' information
*/
editor.content.insertBlock(blockData);
/** Pass created block to next step */
return blockData.block;
})
/** Log if something wrong with node */
.catch(function (error) {
editor.core.log('Node skipped while parsing because %o', 'error', error);
});
};
/**
* Asynchronously returns block data from blocksList by index
* @return Promise to node
*/
renderer.getNodeAsync = function (blocksList, index) {
return Promise.resolve().then(function () {
return {
tool : blocksList[index],
position : index
};
});
};
/**
* Creates editor block by JSON-data
*
* @uses render method of each plugin
*
* @param {Object} toolData.tool
* { header : {
* text: '',
* type: 'H3', ...
* }
* }
* @param {Number} toolData.position - index in input-blocks array
* @return {Object} with type and Element
*/
renderer.createBlockFromData = function ( toolData ) {
/** New parser */
var block,
tool = toolData.tool,
pluginName = tool.type;
/** Get first key of object that stores plugin name */
// for (var pluginName in blockData) break;
/** Check for plugin existance */
if (!editor.tools[pluginName]) {
throw Error(`Plugin «${pluginName}» not found`);
}
/** Check for plugin having render method */
if (typeof editor.tools[pluginName].render != 'function') {
throw Error(`Plugin «${pluginName}» must have «render» method`);
}
if ( editor.tools[pluginName].available === false ) {
block = editor.draw.unavailableBlock();
block.innerHTML = editor.tools[pluginName].loadingMessage;
/**
* Saver will extract data from initial block data by position in array
*/
block.dataset.inputPosition = toolData.position;
} else {
/** New Parser */
block = editor.tools[pluginName].render(tool.data);
}
/** is first-level block stretched */
var stretched = editor.tools[pluginName].isStretched || false;
/** Retrun type and block */
return {
type : pluginName,
block : block,
stretched : stretched
};
};
return renderer;
})({});

View File

@ -1,80 +0,0 @@
/**
* Codex Sanitizer
*/
module.exports = (function (sanitizer) {
/** HTML Janitor library */
let janitor = require('html-janitor');
/** Codex Editor */
let editor = codex.editor;
sanitizer.prepare = function () {
if (editor.settings.sanitizer && !editor.core.isEmpty(editor.settings.sanitizer)) {
Config.CUSTOM = editor.settings.sanitizer;
}
};
/**
* Basic config
*/
var Config = {
/** User configuration */
CUSTOM : null,
BASIC : {
tags: {
p: {},
a: {
href: true,
target: '_blank',
rel: 'nofollow'
}
}
}
};
sanitizer.Config = Config;
/**
*
* @param userCustomConfig
* @returns {*}
* @private
*
* @description If developer uses editor's API, then he can customize sane restrictions.
* Or, sane config can be defined globally in editors initialization. That config will be used everywhere
* At least, if there is no config overrides, that API uses BASIC Default configation
*/
let init_ = function (userCustomConfig) {
let configuration = userCustomConfig || Config.CUSTOM || Config.BASIC;
return new janitor(configuration);
};
/**
* Cleans string from unwanted tags
* @protected
* @param {String} dirtyString - taint string
* @param {Object} customConfig - allowed tags
*/
sanitizer.clean = function (dirtyString, customConfig) {
let janitorInstance = init_(customConfig);
return janitorInstance.clean(dirtyString);
};
return sanitizer;
})({});

View File

@ -1,165 +0,0 @@
/**
* Codex Editor Saver
*
* @author Codex Team
* @version 1.1.0
*/
module.exports = (function (saver) {
let editor = codex.editor;
/**
* @public
* Save blocks
*/
saver.save = function () {
/** Save html content of redactor to memory */
editor.state.html = editor.nodes.redactor.innerHTML;
/** Clean jsonOutput state */
editor.state.jsonOutput = [];
return saveBlocks(editor.nodes.redactor.childNodes);
};
/**
* @private
* Save each block data
*
* @param blocks
* @returns {Promise.<TResult>}
*/
let saveBlocks = function (blocks) {
let data = [];
for(let index = 0; index < blocks.length; index++) {
data.push(getBlockData(blocks[index]));
}
return Promise.all(data)
.then(makeOutput)
.catch(editor.core.log);
};
/** Save and validate block data */
let getBlockData = function (block) {
return saveBlockData(block)
.then(validateBlockData)
.catch(editor.core.log);
};
/**
* @private
* Call block`s plugin save method and return saved data
*
* @param block
* @returns {Object}
*/
let saveBlockData = function (block) {
let pluginName = block.dataset.tool;
/** Check for plugin existence */
if (!editor.tools[pluginName]) {
editor.core.log(`Plugin «${pluginName}» not found`, 'error');
return {data: null, pluginName: null};
}
/** Check for plugin having save method */
if (typeof editor.tools[pluginName].save !== 'function') {
editor.core.log(`Plugin «${pluginName}» must have save method`, 'error');
return {data: null, pluginName: null};
}
/** Result saver */
let blockContent = block.childNodes[0],
pluginsContent = blockContent.childNodes[0],
position = pluginsContent.dataset.inputPosition;
/** If plugin wasn't available then return data from cache */
if ( editor.tools[pluginName].available === false ) {
return Promise.resolve({data: codex.editor.state.blocks.items[position].data, pluginName});
}
return Promise.resolve(pluginsContent)
.then(editor.tools[pluginName].save)
.then(data => Object({data, pluginName}));
};
/**
* Call plugin`s validate method. Return false if validation failed
*
* @param data
* @param pluginName
* @returns {Object|Boolean}
*/
let validateBlockData = function ({data, pluginName}) {
if (!data || !pluginName) {
return false;
}
if (editor.tools[pluginName].validate) {
let result = editor.tools[pluginName].validate(data);
/**
* Do not allow invalid data
*/
if (!result) {
return false;
}
}
return {data, pluginName};
};
/**
* Compile article output
*
* @param savedData
* @returns {{time: number, version, items: (*|Array)}}
*/
let makeOutput = function (savedData) {
savedData = savedData.filter(blockData => blockData);
let items = savedData.map(blockData => Object({type: blockData.pluginName, data: blockData.data}));
editor.state.jsonOutput = items;
return {
id: editor.state.blocks.id || null,
time: +new Date(),
version: editor.version,
items
};
};
return saver;
})({});

View File

@ -1,599 +0,0 @@
/**
* Inline toolbar
*
* Contains from tools:
* Bold, Italic, Underline and Anchor
*
* @author Codex Team
* @version 1.0
*/
module.exports = (function (inline) {
let editor = codex.editor;
inline.buttonsOpened = null;
inline.actionsOpened = null;
inline.wrappersOffset = null;
/**
* saving selection that need for execCommand for styling
*
*/
inline.storedSelection = null;
/**
* @protected
*
* Open inline toobar
*/
inline.show = function () {
var currentNode = editor.content.currentNode,
tool = currentNode.dataset.tool,
plugin;
/**
* tool allowed to open inline toolbar
*/
plugin = editor.tools[tool];
if (!plugin.showInlineToolbar)
return;
var selectedText = inline.getSelectionText(),
toolbar = editor.nodes.inlineToolbar.wrapper;
if (selectedText.length > 0) {
/** Move toolbar and open */
editor.toolbar.inline.move();
/** Open inline toolbar */
toolbar.classList.add('opened');
/** show buttons of inline toolbar */
editor.toolbar.inline.showButtons();
}
};
/**
* @protected
*
* Closes inline toolbar
*/
inline.close = function () {
var toolbar = editor.nodes.inlineToolbar.wrapper;
toolbar.classList.remove('opened');
};
/**
* @private
*
* Moving toolbar
*/
inline.move = function () {
if (!this.wrappersOffset) {
this.wrappersOffset = this.getWrappersOffset();
}
var coords = this.getSelectionCoords(),
defaultOffset = 0,
toolbar = editor.nodes.inlineToolbar.wrapper,
newCoordinateX,
newCoordinateY;
if (!coords) {
return;
}
if (toolbar.offsetHeight === 0) {
defaultOffset = 40;
}
newCoordinateX = coords.x - this.wrappersOffset.left;
newCoordinateY = coords.y + window.scrollY - this.wrappersOffset.top - defaultOffset - toolbar.offsetHeight;
toolbar.style.transform = `translate3D(${Math.floor(newCoordinateX)}px, ${Math.floor(newCoordinateY)}px, 0)`;
/** Close everything */
editor.toolbar.inline.closeButtons();
editor.toolbar.inline.closeAction();
};
/**
* @private
*
* Tool Clicked
*/
inline.toolClicked = function (event, type) {
/**
* For simple tools we use default browser function
* For more complicated tools, we should write our own behavior
*/
switch (type) {
case 'createLink' : editor.toolbar.inline.createLinkAction(event, type); break;
default : editor.toolbar.inline.defaultToolAction(type); break;
}
/**
* highlight buttons
* after making some action
*/
editor.nodes.inlineToolbar.buttons.childNodes.forEach(editor.toolbar.inline.hightlight);
};
/**
* @private
*
* Saving wrappers offset in DOM
*/
inline.getWrappersOffset = function () {
var wrapper = editor.nodes.wrapper,
offset = this.getOffset(wrapper);
this.wrappersOffset = offset;
return offset;
};
/**
* @private
*
* Calculates offset of DOM element
*
* @param el
* @returns {{top: number, left: number}}
*/
inline.getOffset = function ( el ) {
var _x = 0;
var _y = 0;
while( el && !isNaN( el.offsetLeft ) && !isNaN( el.offsetTop ) ) {
_x += (el.offsetLeft + el.clientLeft);
_y += (el.offsetTop + el.clientTop);
el = el.offsetParent;
}
return { top: _y, left: _x };
};
/**
* @private
*
* Calculates position of selected text
* @returns {{x: number, y: number}}
*/
inline.getSelectionCoords = function () {
var sel = document.selection, range;
var x = 0, y = 0;
if (sel) {
if (sel.type != 'Control') {
range = sel.createRange();
range.collapse(true);
x = range.boundingLeft;
y = range.boundingTop;
}
} else if (window.getSelection) {
sel = window.getSelection();
if (sel.rangeCount) {
range = sel.getRangeAt(0).cloneRange();
if (range.getClientRects) {
range.collapse(true);
var rect = range.getClientRects()[0];
if (!rect) {
return;
}
x = rect.left;
y = rect.top;
}
}
}
return { x: x, y: y };
};
/**
* @private
*
* Returns selected text as String
* @returns {string}
*/
inline.getSelectionText = function () {
var selectedText = '';
// all modern browsers and IE9+
if (window.getSelection) {
selectedText = window.getSelection().toString();
}
return selectedText;
};
/** Opens buttons block */
inline.showButtons = function () {
var buttons = editor.nodes.inlineToolbar.buttons;
buttons.classList.add('opened');
editor.toolbar.inline.buttonsOpened = true;
/** highlight buttons */
editor.nodes.inlineToolbar.buttons.childNodes.forEach(editor.toolbar.inline.hightlight);
};
/** Makes buttons disappear */
inline.closeButtons = function () {
var buttons = editor.nodes.inlineToolbar.buttons;
buttons.classList.remove('opened');
editor.toolbar.inline.buttonsOpened = false;
};
/** Open buttons defined action if exist */
inline.showActions = function () {
var action = editor.nodes.inlineToolbar.actions;
action.classList.add('opened');
editor.toolbar.inline.actionsOpened = true;
};
/** Close actions block */
inline.closeAction = function () {
var action = editor.nodes.inlineToolbar.actions;
action.innerHTML = '';
action.classList.remove('opened');
editor.toolbar.inline.actionsOpened = false;
};
/**
* Callback for keydowns in inline toolbar "Insert link..." input
*/
let inlineToolbarAnchorInputKeydown_ = function (event) {
if (event.keyCode != editor.core.keys.ENTER) {
return;
}
let editable = editor.content.currentNode,
storedSelection = editor.toolbar.inline.storedSelection;
editor.toolbar.inline.restoreSelection(editable, storedSelection);
editor.toolbar.inline.setAnchor(this.value);
/**
* Preventing events that will be able to happen
*/
event.preventDefault();
event.stopImmediatePropagation();
editor.toolbar.inline.clearRange();
};
/** Action for link creation or for setting anchor */
inline.createLinkAction = function (event) {
var isActive = this.isLinkActive();
var editable = editor.content.currentNode,
storedSelection = editor.toolbar.inline.saveSelection(editable);
/** Save globally selection */
editor.toolbar.inline.storedSelection = storedSelection;
if (isActive) {
/**
* Changing stored selection. if we want to remove anchor from word
* we should remove anchor from whole word, not only selected part.
* The solution is than we get the length of current link
* Change start position to - end of selection minus length of anchor
*/
editor.toolbar.inline.restoreSelection(editable, storedSelection);
editor.toolbar.inline.defaultToolAction('unlink');
} else {
/** Create input and close buttons */
var action = editor.draw.inputForLink();
editor.nodes.inlineToolbar.actions.appendChild(action);
editor.toolbar.inline.closeButtons();
editor.toolbar.inline.showActions();
/**
* focus to input
* Solution: https://developer.mozilla.org/ru/docs/Web/API/HTMLElement/focus
* Prevents event after showing input and when we need to focus an input which is in unexisted form
*/
action.focus();
event.preventDefault();
/** Callback to link action */
editor.listeners.add(action, 'keydown', inlineToolbarAnchorInputKeydown_, false);
}
};
inline.isLinkActive = function () {
var isActive = false;
editor.nodes.inlineToolbar.buttons.childNodes.forEach(function (tool) {
var dataType = tool.dataset.type;
if (dataType == 'link' && tool.classList.contains('hightlighted')) {
isActive = true;
}
});
return isActive;
};
/** default action behavior of tool */
inline.defaultToolAction = function (type) {
document.execCommand(type, false, null);
};
/**
* @private
*
* Sets URL
*
* @param {String} url - URL
*/
inline.setAnchor = function (url) {
document.execCommand('createLink', false, url);
/** Close after URL inserting */
editor.toolbar.inline.closeAction();
};
/**
* @private
*
* Saves selection
*/
inline.saveSelection = function (containerEl) {
var range = window.getSelection().getRangeAt(0),
preSelectionRange = range.cloneRange(),
start;
preSelectionRange.selectNodeContents(containerEl);
preSelectionRange.setEnd(range.startContainer, range.startOffset);
start = preSelectionRange.toString().length;
return {
start: start,
end: start + range.toString().length
};
};
/**
* @private
*
* Sets to previous selection (Range)
*
* @param {Element} containerEl - editable element where we restore range
* @param {Object} savedSel - range basic information to restore
*/
inline.restoreSelection = function (containerEl, savedSel) {
var range = document.createRange(),
charIndex = 0;
range.setStart(containerEl, 0);
range.collapse(true);
var nodeStack = [ containerEl ],
node,
foundStart = false,
stop = false,
nextCharIndex;
while (!stop && (node = nodeStack.pop())) {
if (node.nodeType == 3) {
nextCharIndex = charIndex + node.length;
if (!foundStart && savedSel.start >= charIndex && savedSel.start <= nextCharIndex) {
range.setStart(node, savedSel.start - charIndex);
foundStart = true;
}
if (foundStart && savedSel.end >= charIndex && savedSel.end <= nextCharIndex) {
range.setEnd(node, savedSel.end - charIndex);
stop = true;
}
charIndex = nextCharIndex;
} else {
var i = node.childNodes.length;
while (i--) {
nodeStack.push(node.childNodes[i]);
}
}
}
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
};
/**
* @private
*
* Removes all ranges from window selection
*/
inline.clearRange = function () {
var selection = window.getSelection();
selection.removeAllRanges();
};
/**
* @private
*
* sets or removes hightlight
*/
inline.hightlight = function (tool) {
var dataType = tool.dataset.type;
if (document.queryCommandState(dataType)) {
editor.toolbar.inline.setButtonHighlighted(tool);
} else {
editor.toolbar.inline.removeButtonsHighLight(tool);
}
/**
*
* hightlight for anchors
*/
var selection = window.getSelection(),
tag = selection.anchorNode.parentNode;
if (tag.tagName == 'A' && dataType == 'link') {
editor.toolbar.inline.setButtonHighlighted(tool);
}
};
/**
* @private
*
* Mark button if text is already executed
*/
inline.setButtonHighlighted = function (button) {
button.classList.add('hightlighted');
/** At link tool we also change icon */
if (button.dataset.type == 'link') {
var icon = button.childNodes[0];
icon.classList.remove('ce-icon-link');
icon.classList.add('ce-icon-unlink');
}
};
/**
* @private
*
* Removes hightlight
*/
inline.removeButtonsHighLight = function (button) {
button.classList.remove('hightlighted');
/** At link tool we also change icon */
if (button.dataset.type == 'link') {
var icon = button.childNodes[0];
icon.classList.remove('ce-icon-unlink');
icon.classList.add('ce-icon-link');
}
};
return inline;
})({});

View File

@ -1,172 +0,0 @@
/**
* Toolbar settings
*
* @version 1.0.5
*/
module.exports = (function (settings) {
let editor = codex.editor;
settings.opened = false;
settings.setting = null;
settings.actions = null;
/**
* Append and open settings
*/
settings.open = function (toolType) {
/**
* Append settings content
* It's stored in tool.settings
*/
if ( !editor.tools[toolType] || !editor.tools[toolType].makeSettings ) {
return;
}
/**
* Draw settings block
*/
var settingsBlock = editor.tools[toolType].makeSettings();
editor.nodes.pluginSettings.appendChild(settingsBlock);
/** Open settings block */
editor.nodes.blockSettings.classList.add('opened');
this.opened = true;
};
/**
* Close and clear settings
*/
settings.close = function () {
editor.nodes.blockSettings.classList.remove('opened');
editor.nodes.pluginSettings.innerHTML = '';
this.opened = false;
};
/**
* @param {string} toolType - plugin type
*/
settings.toggle = function ( toolType ) {
if ( !this.opened ) {
this.open(toolType);
} else {
this.close();
}
};
/**
* Here we will draw buttons and add listeners to components
*/
settings.makeRemoveBlockButton = function () {
var removeBlockWrapper = editor.draw.node('SPAN', 'ce-toolbar__remove-btn', {}),
settingButton = editor.draw.node('SPAN', 'ce-toolbar__remove-setting', { innerHTML : '<i class="ce-icon-trash"></i>' }),
actionWrapper = editor.draw.node('DIV', 'ce-toolbar__remove-confirmation', {}),
confirmAction = editor.draw.node('DIV', 'ce-toolbar__remove-confirm', { textContent : 'Удалить блок' }),
cancelAction = editor.draw.node('DIV', 'ce-toolbar__remove-cancel', { textContent : 'Отмена' });
editor.listeners.add(settingButton, 'click', editor.toolbar.settings.removeButtonClicked, false);
editor.listeners.add(confirmAction, 'click', editor.toolbar.settings.confirmRemovingRequest, false);
editor.listeners.add(cancelAction, 'click', editor.toolbar.settings.cancelRemovingRequest, false);
actionWrapper.appendChild(confirmAction);
actionWrapper.appendChild(cancelAction);
removeBlockWrapper.appendChild(settingButton);
removeBlockWrapper.appendChild(actionWrapper);
/** Save setting */
editor.toolbar.settings.setting = settingButton;
editor.toolbar.settings.actions = actionWrapper;
return removeBlockWrapper;
};
settings.removeButtonClicked = function () {
var action = editor.toolbar.settings.actions;
if (action.classList.contains('opened')) {
editor.toolbar.settings.hideRemoveActions();
} else {
editor.toolbar.settings.showRemoveActions();
}
editor.toolbar.toolbox.close();
editor.toolbar.settings.close();
};
settings.cancelRemovingRequest = function () {
editor.toolbar.settings.actions.classList.remove('opened');
};
settings.confirmRemovingRequest = function () {
var currentBlock = editor.content.currentNode,
firstLevelBlocksCount;
currentBlock.remove();
firstLevelBlocksCount = editor.nodes.redactor.childNodes.length;
/**
* If all blocks are removed
*/
if (firstLevelBlocksCount === 0) {
/** update currentNode variable */
editor.content.currentNode = null;
/** Inserting new empty initial block */
editor.ui.addInitialBlock();
}
editor.ui.saveInputs();
editor.toolbar.close();
};
settings.showRemoveActions = function () {
editor.toolbar.settings.actions.classList.add('opened');
};
settings.hideRemoveActions = function () {
editor.toolbar.settings.actions.classList.remove('opened');
};
return settings;
})({});

View File

@ -1,133 +0,0 @@
/**
* Codex Editor toolbar module
*
* Contains:
* - Inline toolbox
* - Toolbox within plus button
* - Settings section
*
* @author Codex Team
* @version 1.0
*/
module.exports = (function (toolbar) {
let editor = codex.editor;
toolbar.settings = require('./settings');
toolbar.inline = require('./inline');
toolbar.toolbox = require('./toolbox');
/**
* Margin between focused node and toolbar
*/
toolbar.defaultToolbarHeight = 49;
toolbar.defaultOffset = 34;
toolbar.opened = false;
toolbar.current = null;
/**
* @protected
*/
toolbar.open = function () {
if (editor.hideToolbar) {
return;
}
let toolType = editor.content.currentNode.dataset.tool;
if (!editor.tools[toolType] || !editor.tools[toolType].makeSettings ) {
editor.nodes.showSettingsButton.classList.add('hide');
} else {
editor.nodes.showSettingsButton.classList.remove('hide');
}
editor.nodes.toolbar.classList.add('opened');
this.opened = true;
};
/**
* @protected
*/
toolbar.close = function () {
editor.nodes.toolbar.classList.remove('opened');
toolbar.opened = false;
toolbar.current = null;
for (var button in editor.nodes.toolbarButtons) {
editor.nodes.toolbarButtons[button].classList.remove('selected');
}
/** Close toolbox when toolbar is not displayed */
editor.toolbar.toolbox.close();
editor.toolbar.settings.close();
};
toolbar.toggle = function () {
if ( !this.opened ) {
this.open();
} else {
this.close();
}
};
toolbar.hidePlusButton = function () {
editor.nodes.plusButton.classList.add('hide');
};
toolbar.showPlusButton = function () {
editor.nodes.plusButton.classList.remove('hide');
};
/**
* Moving toolbar to the specified node
*/
toolbar.move = function () {
/** Close Toolbox when we move toolbar */
editor.toolbar.toolbox.close();
if (!editor.content.currentNode) {
return;
}
var newYCoordinate = editor.content.currentNode.offsetTop - (editor.toolbar.defaultToolbarHeight / 2) + editor.toolbar.defaultOffset;
editor.nodes.toolbar.style.transform = `translate3D(0, ${Math.floor(newYCoordinate)}px, 0)`;
/** Close trash actions */
editor.toolbar.settings.hideRemoveActions();
};
return toolbar;
})({});

View File

@ -1,191 +0,0 @@
/**
* Codex Editor toolbox
*
* All tools be able to appended here
*
* @author Codex Team
* @version 1.0
*/
module.exports = (function (toolbox) {
let editor = codex.editor;
toolbox.opened = false;
toolbox.openedOnBlock = null;
/** Shows toolbox */
toolbox.open = function () {
/** Close setting if toolbox is opened */
if (editor.toolbar.settings.opened) {
editor.toolbar.settings.close();
}
/** Add 'toolbar-opened' class for current block **/
toolbox.openedOnBlock = editor.content.currentNode;
toolbox.openedOnBlock.classList.add('toolbar-opened');
/** display toolbox */
editor.nodes.toolbox.classList.add('opened');
/** Animate plus button */
editor.nodes.plusButton.classList.add('clicked');
/** toolbox state */
editor.toolbar.toolbox.opened = true;
};
/** Closes toolbox */
toolbox.close = function () {
/** Remove 'toolbar-opened' class from current block **/
if (toolbox.openedOnBlock) toolbox.openedOnBlock.classList.remove('toolbar-opened');
toolbox.openedOnBlock = null;
/** Makes toolbox disappear */
editor.nodes.toolbox.classList.remove('opened');
/** Rotate plus button */
editor.nodes.plusButton.classList.remove('clicked');
/** toolbox state */
editor.toolbar.toolbox.opened = false;
editor.toolbar.current = null;
};
toolbox.leaf = function () {
let currentTool = editor.toolbar.current,
tools = Object.keys(editor.tools),
barButtons = editor.nodes.toolbarButtons,
nextToolIndex = 0,
toolToSelect,
visibleTool,
tool;
if ( !currentTool ) {
/** Get first tool from object*/
for(tool in editor.tools) {
if (editor.tools[tool].displayInToolbox) {
break;
}
nextToolIndex ++;
}
} else {
nextToolIndex = (tools.indexOf(currentTool) + 1) % tools.length;
visibleTool = tools[nextToolIndex];
while (!editor.tools[visibleTool].displayInToolbox) {
nextToolIndex = (nextToolIndex + 1) % tools.length;
visibleTool = tools[nextToolIndex];
}
}
toolToSelect = tools[nextToolIndex];
for ( var button in barButtons ) {
barButtons[button].classList.remove('selected');
}
barButtons[toolToSelect].classList.add('selected');
editor.toolbar.current = toolToSelect;
};
/**
* Transforming selected node type into selected toolbar element type
* @param {event} event
*/
toolbox.toolClicked = function (event) {
/**
* UNREPLACEBLE_TOOLS this types of tools are forbidden to replace even they are empty
*/
var UNREPLACEBLE_TOOLS = ['image', 'link', 'list', 'instagram', 'twitter', 'embed'],
tool = editor.tools[editor.toolbar.current],
workingNode = editor.content.currentNode,
currentInputIndex = editor.caret.inputIndex,
newBlockContent,
appendCallback,
blockData;
/** Make block from plugin */
newBlockContent = tool.render();
/** information about block */
blockData = {
block : newBlockContent,
type : tool.type,
stretched : false
};
if (
workingNode &&
UNREPLACEBLE_TOOLS.indexOf(workingNode.dataset.tool) === -1 &&
workingNode.textContent.trim() === ''
) {
/** Replace current block */
editor.content.switchBlock(workingNode, newBlockContent, tool.type);
} else {
/** Insert new Block from plugin */
editor.content.insertBlock(blockData);
/** increase input index */
currentInputIndex++;
}
/** Fire tool append callback */
appendCallback = tool.appendCallback;
if (appendCallback && typeof appendCallback == 'function') {
appendCallback.call(event);
}
window.setTimeout(function () {
/** Set caret to current block */
editor.caret.setToBlock(currentInputIndex);
}, 10);
/**
* Changing current Node
*/
editor.content.workingNodeChanged();
/**
* Move toolbar when node is changed
*/
editor.toolbar.move();
};
return toolbox;
})({});

View File

@ -1,153 +0,0 @@
/**
* Module working with plugins
*/
module.exports = (function () {
let editor = codex.editor;
/**
* Initialize plugins before using
* Ex. Load scripts or call some internal methods
* @return Promise
*/
function prepare() {
return new Promise(function (resolve_, reject_) {
Promise.resolve()
/**
* Compose a sequence of plugins that requires preparation
*/
.then(function () {
let pluginsRequiresPreparation = [],
allPlugins = editor.tools;
for ( let pluginName in allPlugins ) {
let plugin = allPlugins[pluginName];
if (plugin.prepare && typeof plugin.prepare != 'function' || !plugin.prepare) {
continue;
}
pluginsRequiresPreparation.push(plugin);
}
/**
* If no one passed plugins requires preparation, finish prepare() and go ahead
*/
if (!pluginsRequiresPreparation.length) {
resolve_();
}
return pluginsRequiresPreparation;
})
/** Wait plugins while they prepares */
.then(waitAllPluginsPreparation_)
.then(function () {
editor.core.log('Plugins loaded', 'info');
resolve_();
}).catch(function (error) {
reject_(error);
});
});
}
/**
* @param {array} plugins - list of tools that requires preparation
* @return {Promise} resolved while all plugins will be ready or failed
*/
function waitAllPluginsPreparation_(plugins) {
/**
* @calls allPluginsProcessed__ when all plugins prepared or failed
*/
return new Promise (function (allPluginsProcessed__) {
/**
* pluck each element from queue
* First, send resolved Promise as previous value
* Each plugins "prepare" method returns a Promise, that's why
* reduce current element will not be able to continue while can't get
* a resolved Promise
*
* If last plugin is "prepared" then go to the next stage of initialization
*/
plugins.reduce(function (previousValue, plugin, iteration) {
return previousValue.then(function () {
/**
* Wait till plugins prepared
* @calls pluginIsReady__ when plugin is ready or failed
*/
return new Promise ( function (pluginIsReady__) {
callPluginsPrepareMethod_( plugin )
.then( pluginIsReady__ )
.then( function () {
plugin.available = true;
})
.catch(function (error) {
editor.core.log(`Plugin «${plugin.type}» was not loaded. Preparation failed because %o`, 'warn', error);
plugin.available = false;
plugin.loadingMessage = error;
/** Go ahead even some plugin has problems */
pluginIsReady__();
})
.then(function () {
/** If last plugin has problems then just ignore and continue */
if (iteration == plugins.length - 1) {
allPluginsProcessed__();
}
});
});
});
}, Promise.resolve() );
});
}
var callPluginsPrepareMethod_ = function (plugin) {
return plugin.prepare( plugin.config || {} );
};
return {
prepare: prepare
};
}());

View File

@ -1,136 +0,0 @@
/**
*
* Codex.Editor Transport Module
*
* @copyright 2017 Codex-Team
* @version 1.2.0
*/
module.exports = (function (transport) {
let editor = codex.editor;
/**
* @private {Object} current XmlHttpRequest instance
*/
var currentRequest = null;
/**
* @type {null} | {DOMElement} input - keeps input element in memory
*/
transport.input = null;
/**
* @property {Object} arguments - keep plugin settings and defined callbacks
*/
transport.arguments = null;
/**
* Prepares input element where will be files
*/
transport.prepare = function () {
let input = editor.draw.node( 'INPUT', '', { type : 'file' } );
editor.listeners.add(input, 'change', editor.transport.fileSelected);
editor.transport.input = input;
};
/** Clear input when files is uploaded */
transport.clearInput = function () {
/** Remove old input */
transport.input = null;
/** Prepare new one */
transport.prepare();
};
/**
* Callback for file selection
* @param {Event} event
*/
transport.fileSelected = function () {
var input = this,
i,
files = input.files,
formData = new FormData();
if (editor.transport.arguments.multiple === true) {
for ( i = 0; i < files.length; i++) {
formData.append('files[]', files[i], files[i].name);
}
} else {
formData.append('files', files[0], files[0].name);
}
currentRequest = editor.core.ajax({
type : 'POST',
data : formData,
url : editor.transport.arguments.url,
beforeSend : editor.transport.arguments.beforeSend,
success : editor.transport.arguments.success,
error : editor.transport.arguments.error,
progress : editor.transport.arguments.progress
});
/** Clear input */
transport.clearInput();
};
/**
* Use plugin callbacks
* @protected
*
* @param {Object} args - can have :
* @param {String} args.url - fetch URL
* @param {Function} args.beforeSend - function calls before sending ajax
* @param {Function} args.success - success callback
* @param {Function} args.error - on error handler
* @param {Function} args.progress - xhr onprogress handler
* @param {Boolean} args.multiple - allow select several files
* @param {String} args.accept - adds accept attribute
*/
transport.selectAndUpload = function (args) {
transport.arguments = args;
if ( args.multiple === true) {
transport.input.setAttribute('multiple', 'multiple');
}
if ( args.accept ) {
transport.input.setAttribute('accept', args.accept);
}
transport.input.click();
};
transport.abort = function () {
currentRequest.abort();
currentRequest = null;
};
return transport;
})({});

View File

@ -1,431 +0,0 @@
/**
* Codex Editor UI module
*
* @author Codex Team
* @version 1.2.0
*/
module.exports = (function (ui) {
let editor = codex.editor;
/**
* Basic editor classnames
*/
ui.className = {
/**
* @const {string} BLOCK_CLASSNAME - redactor blocks name
*/
BLOCK_CLASSNAME : 'ce-block',
/**
* @const {String} wrapper for plugins content
*/
BLOCK_CONTENT : 'ce-block__content',
/**
* @const {String} BLOCK_STRETCHED - makes block stretched
*/
BLOCK_STRETCHED : 'ce-block--stretched',
/**
* @const {String} BLOCK_HIGHLIGHTED - adds background
*/
BLOCK_HIGHLIGHTED : 'ce-block--focused',
/**
* @const {String} - for all default settings
*/
SETTINGS_ITEM : 'ce-settings__item'
};
/**
* @protected
*
* Making main interface
*/
ui.prepare = function () {
return new Promise(function (resolve) {
let wrapper = editor.draw.wrapper(),
redactor = editor.draw.redactor(),
toolbar = makeToolBar_();
wrapper.appendChild(toolbar);
wrapper.appendChild(redactor);
/** Save created ui-elements to static nodes state */
editor.nodes.wrapper = wrapper;
editor.nodes.redactor = redactor;
/** Append editor wrapper with redactor zone into holder */
editor.nodes.holder.appendChild(wrapper);
resolve();
})
/** Add toolbox tools */
.then(addTools_)
/** Make container for inline toolbar */
.then(makeInlineToolbar_)
/** Add inline toolbar tools */
.then(addInlineToolbarTools_)
/** Draw wrapper for notifications */
.then(makeNotificationHolder_)
/** Add eventlisteners to redactor elements */
.then(bindEvents_)
.catch( function () {
editor.core.log("Can't draw editor interface");
});
};
/**
* @private
* Draws inline toolbar zone
*/
var makeInlineToolbar_ = function () {
var container = editor.draw.inlineToolbar();
/** Append to redactor new inline block */
editor.nodes.inlineToolbar.wrapper = container;
/** Draw toolbar buttons */
editor.nodes.inlineToolbar.buttons = editor.draw.inlineToolbarButtons();
/** Buttons action or settings */
editor.nodes.inlineToolbar.actions = editor.draw.inlineToolbarActions();
/** Append to inline toolbar buttons as part of it */
editor.nodes.inlineToolbar.wrapper.appendChild(editor.nodes.inlineToolbar.buttons);
editor.nodes.inlineToolbar.wrapper.appendChild(editor.nodes.inlineToolbar.actions);
editor.nodes.wrapper.appendChild(editor.nodes.inlineToolbar.wrapper);
};
var makeToolBar_ = function () {
let toolbar = editor.draw.toolbar(),
blockButtons = makeToolbarSettings_(),
toolbarContent = makeToolbarContent_();
/** Appending first-level block buttons */
toolbar.appendChild(blockButtons);
/** Append toolbarContent to toolbar */
toolbar.appendChild(toolbarContent);
/** Make toolbar global */
editor.nodes.toolbar = toolbar;
return toolbar;
};
var makeToolbarContent_ = function () {
let toolbarContent = editor.draw.toolbarContent(),
toolbox = editor.draw.toolbox(),
plusButton = editor.draw.plusButton();
/** Append plus button */
toolbarContent.appendChild(plusButton);
/** Appending toolbar tools */
toolbarContent.appendChild(toolbox);
/** Make Toolbox and plusButton global */
editor.nodes.toolbox = toolbox;
editor.nodes.plusButton = plusButton;
return toolbarContent;
};
var makeToolbarSettings_ = function () {
let blockSettings = editor.draw.blockSettings(),
blockButtons = editor.draw.blockButtons(),
defaultSettings = editor.draw.defaultSettings(),
showSettingsButton = editor.draw.settingsButton(),
showTrashButton = editor.toolbar.settings.makeRemoveBlockButton(),
pluginSettings = editor.draw.pluginsSettings();
/** Add default and plugins settings */
blockSettings.appendChild(pluginSettings);
blockSettings.appendChild(defaultSettings);
/**
* Make blocks buttons
* This block contains settings button and remove block button
*/
blockButtons.appendChild(showSettingsButton);
blockButtons.appendChild(showTrashButton);
blockButtons.appendChild(blockSettings);
/** Make BlockSettings, PluginSettings, DefaultSettings global */
editor.nodes.blockSettings = blockSettings;
editor.nodes.pluginSettings = pluginSettings;
editor.nodes.defaultSettings = defaultSettings;
editor.nodes.showSettingsButton = showSettingsButton;
editor.nodes.showTrashButton = showTrashButton;
return blockButtons;
};
/** Draw notifications holder */
var makeNotificationHolder_ = function () {
/** Append block with notifications to the document */
editor.nodes.notifications = editor.notifications.createHolder();
};
/**
* @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 () {
var tools = {
bold: {
icon : 'ce-icon-bold',
command : 'bold'
},
italic: {
icon : 'ce-icon-italic',
command : 'italic'
},
link: {
icon : 'ce-icon-link',
command : 'createLink'
}
};
var toolButton,
tool;
for(var name in tools) {
tool = tools[name];
toolButton = editor.draw.toolbarButtonInline(name, tool.icon);
editor.nodes.inlineToolbar.buttons.appendChild(toolButton);
/**
* Add callbacks to this buttons
*/
editor.ui.setInlineToolbarButtonBehaviour(toolButton, tool.command);
}
};
/**
* @private
* Bind editor UI events
*/
var bindEvents_ = function () {
editor.core.log('ui.bindEvents fired', 'info');
// window.addEventListener('error', function (errorMsg, url, lineNumber) {
// editor.notifications.errorThrown(errorMsg, event);
// }, false );
/** All keydowns on Document */
editor.listeners.add(document, 'keydown', editor.callback.globalKeydown, false);
/** All keydowns on Redactor zone */
editor.listeners.add(editor.nodes.redactor, 'keydown', editor.callback.redactorKeyDown, false);
/** All keydowns on Document */
editor.listeners.add(document, 'keyup', editor.callback.globalKeyup, false );
/**
* Mouse click to radactor
*/
editor.listeners.add(editor.nodes.redactor, 'click', editor.callback.redactorClicked, false );
/**
* Clicks to the Plus button
*/
editor.listeners.add(editor.nodes.plusButton, 'click', editor.callback.plusButtonClicked, false);
/**
* Clicks to SETTINGS button in toolbar
*/
editor.listeners.add(editor.nodes.showSettingsButton, 'click', editor.callback.showSettingsButtonClicked, false );
/** Bind click listeners on toolbar buttons */
for (var button in editor.nodes.toolbarButtons) {
editor.listeners.add(editor.nodes.toolbarButtons[button], 'click', editor.callback.toolbarButtonClicked, false);
}
};
ui.addBlockHandlers = function (block) {
if (!block) return;
/**
* Block keydowns
*/
editor.listeners.add(block, 'keydown', editor.callback.blockKeydown, false);
/**
* Pasting content from another source
* We have two type of sanitization
* First - uses deep-first search algorithm to get sub nodes,
* sanitizes whole Block_content and replaces cleared nodes
* This method is deprecated
* Method is used in editor.callback.blockPaste(event)
*
* Secont - uses Mutation observer.
* Observer "observe" DOM changes and send changings to callback.
* Callback gets changed node, not whole Block_content.
* Inserted or changed node, which we've gotten have been cleared and replaced with diry node
*
* Method is used in editor.callback.blockPasteViaSanitize(event)
*
* @uses html-janitor
* @example editor.callback.blockPasteViaSanitize(event), the second method.
*
*/
editor.listeners.add(block, 'paste', editor.paste.blockPasteCallback, false);
/**
* Show inline toolbar for selected text
*/
editor.listeners.add(block, 'mouseup', editor.toolbar.inline.show, false);
editor.listeners.add(block, 'keyup', editor.toolbar.inline.show, false);
};
/** getting all contenteditable elements */
ui.saveInputs = function () {
var redactor = editor.nodes.redactor;
editor.state.inputs = [];
/** Save all inputs in global variable state */
var inputs = redactor.querySelectorAll('[contenteditable], input, textarea');
Array.prototype.map.call(inputs, function (current) {
if (!current.type || current.type == 'text' || current.type == 'textarea') {
editor.state.inputs.push(current);
}
});
};
/**
* Adds first initial block on empty redactor
*/
ui.addInitialBlock = function () {
var initialBlockType = editor.settings.initialBlockPlugin,
initialBlock;
if ( !editor.tools[initialBlockType] ) {
editor.core.log('Plugin %o was not implemented and can\'t be used as initial block', 'warn', initialBlockType);
return;
}
initialBlock = editor.tools[initialBlockType].render();
initialBlock.setAttribute('data-placeholder', editor.settings.placeholder);
editor.content.insertBlock({
type : initialBlockType,
block : initialBlock
});
editor.content.workingNodeChanged(initialBlock);
};
ui.setInlineToolbarButtonBehaviour = function (button, type) {
editor.listeners.add(button, 'mousedown', function (event) {
editor.toolbar.inline.toolClicked(event, type);
}, false);
};
return ui;
})({});

5018
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,25 +1,59 @@
{
"name": "codex.editor",
"version": "1.7.9",
"version": "2.0.0",
"description": "Codex Editor. Native JS, based on API and Open Source",
"main": "index.js",
"main": "build/codex-editor.js",
"scripts": {
"build": "webpack"
"build": "rimraf dist && yarn svg && yarn build:dev",
"svg": "svg-sprite-generate -d src/assets/ -o build/sprite.svg",
"build:dev": "webpack --mode development --progress --display-error-details --display-entrypoints"
},
"author": "Codex Team",
"author": "CodeX",
"license": "ISC",
"repository": {
"type": "git",
"url": "git+https://github.com/codex-team/codex.editor.git"
},
"devDependencies": {
"babel-core": "^6.21.0",
"babel-loader": "^6.2.10",
"babel-polyfill": "^6.20.0",
"babel-preset-es2015": "^6.22.0",
"babel-runtime": "^6.20.0",
"css-loader": "^0.26.1",
"eslint": "^3.12.2",
"eslint-loader": "^1.6.1",
"extract-text-webpack-plugin": "^1.0.1",
"@codexteam/shortcuts": "^1.0.0",
"babel-core": "^6.26.3",
"babel-loader": "^7.1.5",
"babel-plugin-add-module-exports": "^0.2.1",
"babel-plugin-class-display-name": "^2.1.0",
"babel-polyfill": "^6.26.0",
"babel-preset-env": "^1.7.0",
"babel-preset-stage-0": "^6.24.1",
"babel-runtime": "^6.26.0",
"css-loader": "^0.28.11",
"eslint": "^4.19.1",
"eslint-loader": "^2.1.0",
"extract-text-webpack-plugin": "^3.0.2",
"html-janitor": "^2.0.2",
"path": "^0.12.7",
"webpack": "^1.14.0"
"postcss-apply": "^0.10.0",
"postcss-color-hex-alpha": "^3.0.0",
"postcss-color-mod-function": "^2.4.3",
"postcss-cssnext": "^3.1.0",
"postcss-custom-media": "^6.0.0",
"postcss-custom-properties": "^7.0.0",
"postcss-custom-selectors": "^4.0.1",
"postcss-font-family-system-ui": "^3.0.0",
"postcss-font-variant": "^3.0.0",
"postcss-loader": "^2.1.6",
"postcss-media-minmax": "^3.0.0",
"postcss-nested": "^3.0.0",
"postcss-nested-ancestors": "^2.0.0",
"postcss-nesting": "^6.0.0",
"postcss-smart-import": "^0.7.6",
"raw-loader": "^0.5.1",
"rimraf": "^2.6.2",
"stylelint": "^9.3.0",
"svg-sprite-generator": "0.0.7",
"ts-loader": "^4.4.2",
"tslint": "^5.11.0",
"tslint-loader": "^3.6.0",
"typescript": "^2.9.2",
"webpack": "^4.16.2",
"webpack-cli": "^3.1.0"
}
}

View File

@ -1,130 +0,0 @@
.cdx-attaches__default-wrapper {
margin: 15px 0;
padding: 15px;
background: #fff;
border: 1px solid #ebecec;
box-shadow: 0 1px 2px 0 rgba(34, 36, 44, 0.03);
border-radius: 3px;
text-align: center;
}
.cdx-attaches__default-button {
color: #8990aa;
cursor: pointer;
}
.cdx-attaches__default-button:hover {
color: #393f52;
}
.cdx-attaches__wrapper {
display: -ms-flexbox;
display: flex;
-ms-flex-flow: row nowrap;
flex-flow: row nowrap;
-ms-flex-pack: start;
justify-content: flex-start;
-ms-flex-align: center;
align-items: center;
margin: 10px 0;
padding: 15px 20px;
background: #fff;
border: 1px solid #ebecec;
box-shadow: 0 1px 2px 0 rgba(34, 36, 44, 0.03);
border-radius: 3px;
font-size: 15px;
}
.cdx-attaches__file-name {
-ms-flex-positive: 8;
flex-grow: 8;
width: 100%;
outline: none;
border: 0;
font-size: inherit;
}
.cdx-attaches__file-name--collapsed {
width: 30%;
overflow: hidden;
text-overflow: ellipsis;
}
.cdx-attaches__extension,
.cdx-attaches__size {
color: #8f9298;
white-space: nowrap;
}
.cdx-attaches__extension::after {
content: ',';
margin-right: 0.2em;
}
.cdx-attaches__size::after {
content: 'KB';
margin-left: 0.2em;
}
.cdx-attaches__icon {
display: inline-block;
width: 16px;
height: 32px;
background: url(file-icon-black.svg) no-repeat center center;
background-size: contain;
}
li:hover .cdx-attaches__icon,
.selected .cdx-attaches__icon {
background: url(file-icon-white.svg) no-repeat center center;
background-size: contain;
}
.cdx-attaches__icon--inline {
height: 16px;
vertical-align: text-bottom;
}
.cdx-attaches__loader {
background-color: transparent;
background-image: repeating-linear-gradient(-45deg, transparent, transparent 4px, #f5f9ff 4px, #eaedef 8px) !important;
background-size: 56px 56px;
animation: loading-bar 5s infinite linear;
}
@keyframes loading-bar {
100% { background-position: -56% 0; }
}
.cdx-attaches__progress-bar {
width: 100%;
height: 3px;
margin: 0 15px;
background: #e1e3eb;
border: 0;
border-radius: 5px;
}
.cdx-attaches__progress-bar::-webkit-progress-bar {
background: #e0e1e3;
border-radius: 5px;
}
.cdx-attaches__progress-bar::-webkit-progress-value {
background: #414957;
border-radius: 5px;
transition: all 100ms ease-in;
}
progress::-moz-progress-bar {
background: #414957;
border-radius: 5px;
}
.cdx-attaches__cross-button {
width: 18px;
height: 18px;
background: url(cross.svg) no-repeat center center;
background-size: contain;
cursor: pointer;
}

View File

@ -1,430 +0,0 @@
/**
* Attache-file Plugin for CodeX Editor
*
* @param {String} config.fetchUrl - Route for file uploding
* @param {Nubmer} config.maxSize - Maximum allowed file size in KB
* @param {String} config.accept - Accepted MIME-types. By default, accepts all
*
* Backend should return response with
* 'url' - Full path to the uploaded file
* 'title' - File title,
* 'name' - File name without extension,
* 'extension' - File extension,
* 'size' - File size
*
* @author @gohabereg
* @version 1.0.0
*/
var cdxAttaches = function () {
/**
* Private methods and props
*/
var KBYTE = 1024,
fileWrapper = null;
/**
* Default config
* Can be redefined with prepare method
*
* @var sting config.fetchUrl -- url to your fetch script
* @var int config.maxSize -- max size of file in kilobytes
* @var accept config.accept -- valid MIME-types. By default, accepts all
*
*/
var config = {
fetchUrl: '',
maxSize: 2,
accept: ''
};
var elementsClasses = {
defaultFormWrapper : 'cdx-attaches__default-wrapper',
defaultFormButton : 'cdx-attaches__default-button',
progressBar : 'cdx-attaches__progress-bar',
wrapper : 'cdx-attaches__wrapper',
loader : 'cdx-attaches__loader',
crossButton : 'cdx-attaches__cross-button',
file: {
title : 'cdx-attaches__file-name',
collapsedName : 'cdx-attaches__file-name--collapsed',
extension : 'cdx-attaches__extension',
size : 'cdx-attaches__size'
}
};
var ui = {
defaultForm: function () {
var wrapper = codex.editor.draw.node('div', elementsClasses.defaultFormWrapper),
button = codex.editor.draw.node('div', elementsClasses.defaultFormButton);
button.addEventListener('click', upload.fire);
button.innerHTML = '<i class="cdx-attaches__icon cdx-attaches__icon--inline"></i> Загрузить файл';
wrapper.appendChild(button);
return wrapper;
},
uploadedFile: function (data) {
var wrapper = codex.editor.draw.node('div', elementsClasses.wrapper),
name = codex.editor.draw.node('input', elementsClasses.file.title),
extension = codex.editor.draw.node('span', elementsClasses.file.extension),
size = codex.editor.draw.node('span', elementsClasses.file.size);
wrapper.dataset.url = data.url;
wrapper.dataset.name = data.name;
name.value = data.title || '';
extension.textContent = data.extension.toUpperCase();
size.textContent = data.size;
wrapper.appendChild(name);
wrapper.appendChild(extension);
wrapper.appendChild(size);
return wrapper;
},
progressBar: {
bar: null,
draw: function () {
var wrapper = codex.editor.draw.node('div', elementsClasses.wrapper),
progress = codex.editor.draw.node('progress', elementsClasses.progressBar),
name = codex.editor.draw.node('span', elementsClasses.file.title),
crossButton = codex.editor.draw.node('span', elementsClasses.crossButton);
progress.max = 100;
progress.value = 0;
name.textContent = codex.editor.transport.input.files[0].name;
name.classList.add(elementsClasses.file.collapsedName);
crossButton.addEventListener('click', upload.abort);
ui.progressBar.bar = progress;
wrapper.appendChild(name);
wrapper.appendChild(progress);
wrapper.appendChild(crossButton);
return wrapper;
},
change: function (value) {
console.assert( !isNaN(value), 'CodeX Editor Attaches: passed value is not a Number');
ui.progressBar.bar.value = value;
}
}
};
/**
* Notify about upload errors via codex.editor.notifications
*
* @param Object error can have `message` property with error message
*/
var notifyError = function (error) {
error = error || {};
codex.editor.notifications.notification({
type: 'error',
message: 'Ошибка во время загрузки файла' + ( error.message ? ': ' + error.message : '' )
});
};
/**
* Contains validation methods
*
* TODO: MIME-type validation
*
*/
var validation = {
size: function () {
var file = codex.editor.transport.input.files[0];
return Math.ceil(file.size / KBYTE) <= config.maxSize;
},
};
var upload = {
current: null,
aborted: false,
/**
* Fired codex.editor.transport selectAndUpload methods
*/
fire: function () {
codex.editor.transport.selectAndUpload({
url: config.fetchUrl,
success: upload.success,
beforeSend: upload.start,
progress: upload.progress,
error: upload.error,
accept: config.accept
});
},
/**
* Will be called before upload
* Draws load animation and progress bar
*/
start: function () {
if (!validation.size()) {
notifyError({message: 'Файл слишком большой'});
return false;
}
if (upload.current) {
notifyError({message: 'Дождитесь окончания предыдущей загрузки'});
return;
}
var progress = ui.progressBar.draw();
upload.current = progress;
codex.editor.content.switchBlock(fileWrapper, progress, 'attaches');
},
/**
* Handler for XmlHttpRequest.upload.onprogress event
* Changes progress bar status
*
* @param event
*/
progress: function (event) {
/** Prevents isNaN value assignment */
if (!event.total) {
return;
}
var value = parseInt(event.loaded / event.total * 100);
ui.progressBar.change(value);
},
/**
* Will be called after success upload
* Try to decode JSON response and draws ui or fires error handler
*
* @param response
*/
success: function (response) {
var data,
uploadedFile;
try {
response = JSON.parse(response);
if (response.success) {
data = response.data;
data.size = Math.ceil(data.size / KBYTE) || 1;
uploadedFile = ui.uploadedFile(data);
codex.editor.content.switchBlock(upload.current, uploadedFile, 'attaches');
uploadedFile.querySelector('input').focus();
} else {
upload.error(response);
}
} catch (e) {
upload.error();
}
upload.current = null;
},
/**
* Upload errors handler
*
* @param error
*/
error: function (error) {
var defaultFrom = ui.defaultForm();
codex.editor.content.switchBlock(upload.current, defaultFrom, 'attaches');
if (!upload.aborted) {
notifyError(error);
}
upload.aborted = false;
upload.current = null;
},
abort: function () {
codex.editor.transport.abort();
upload.aborted = true;
upload.current = null;
}
};
/*
* Public methods
* @param {String} _config.fetchUrl Required
*/
var prepare = function (_config) {
return new Promise(function(resolve, reject){
if ( !_config.fetchUrl ){
reject(Error('fetchUrl is missed'));
return;
}
config.fetchUrl = _config.fetchUrl;
config.accept = _config.accept || config.accept;
if ( !isNaN(_config.maxSize)){
config.maxSize = _config.maxSize;
}
resolve();
});
};
var render = function (data) {
if (!data) {
fileWrapper = ui.defaultForm();
return fileWrapper;
}
return ui.uploadedFile(data);
};
var save = function (block) {
var data = {
url: block.dataset.url,
name: block.dataset.name,
title: block.querySelector('.' + elementsClasses.file.title).value,
extension: block.querySelector('.' + elementsClasses.file.extension).textContent,
size: block.querySelector('.' + elementsClasses.file.size).textContent,
};
return data;
};
var validate = function (data) {
if (!data.url || !data.url.trim()) {
return false;
}
if (!data.title || !data.title.trim()) {
return false;
}
if (!data.extension || !data.extension.trim()) {
return false;
}
if (!data.size || !data.size.trim()) {
return false;
}
return true;
};
var destroy = function () {
cdxAttaches = null;
};
var appendCallback = function () {
upload.fire();
};
return {
prepare: prepare,
render: render,
save: save,
validate: validate,
destroy: destroy,
appendCallback: appendCallback
};
}();

View File

@ -1,7 +0,0 @@
<svg width="13px" height="13px" viewBox="0 0 13 13" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="icon/cross" transform="translate(-5.000000, -5.000000)" fill="#575D67">
<path d="M10.4350288,10.7279221 L4.4359005,10.7279221 C3.8796597,10.7279221 3.43502884,11.1756373 3.43502884,11.7279221 C3.43502884,12.2840572 3.88313435,12.7279221 4.4359005,12.7279221 L10.4350288,12.7279221 L10.4350288,18.7270504 C10.4350288,19.2832912 10.8827441,19.7279221 11.4350288,19.7279221 C11.991164,19.7279221 12.4350288,19.2798166 12.4350288,18.7270504 L12.4350288,12.7279221 L18.4341572,12.7279221 C18.990398,12.7279221 19.4350288,12.2802068 19.4350288,11.7279221 C19.4350288,11.1717869 18.9869233,10.7279221 18.4341572,10.7279221 L12.4350288,10.7279221 L12.4350288,4.72879372 C12.4350288,4.17255292 11.9873136,3.72792206 11.4350288,3.72792206 C10.8788937,3.72792206 10.4350288,4.17602757 10.4350288,4.72879372 L10.4350288,10.7279221 Z" transform="translate(11.435029, 11.727922) rotate(45.000000) translate(-11.435029, -11.727922) "></path>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -1 +0,0 @@
<svg enable-background="new 0 0 48 48" height="48px" id="Layer_1" version="1.1" viewBox="0 0 48 48" width="48px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path clip-rule="evenodd" d="M37,47H11c-2.209,0-4-1.791-4-4V5c0-2.209,1.791-4,4-4h18.973 c0.002,0,0.005,0,0.007,0h0.02H30c0.32,0,0.593,0.161,0.776,0.395l9.829,9.829C40.84,11.407,41,11.68,41,12l0,0v0.021 c0,0.002,0,0.003,0,0.005V43C41,45.209,39.209,47,37,47z M31,4.381V11h6.619L31,4.381z M39,13h-9c-0.553,0-1-0.448-1-1V3H11 C9.896,3,9,3.896,9,5v38c0,1.104,0.896,2,2,2h26c1.104,0,2-0.896,2-2V13z M33,39H15c-0.553,0-1-0.447-1-1c0-0.552,0.447-1,1-1h18 c0.553,0,1,0.448,1,1C34,38.553,33.553,39,33,39z M33,31H15c-0.553,0-1-0.447-1-1c0-0.552,0.447-1,1-1h18c0.553,0,1,0.448,1,1 C34,30.553,33.553,31,33,31z M33,23H15c-0.553,0-1-0.447-1-1c0-0.552,0.447-1,1-1h18c0.553,0,1,0.448,1,1C34,22.553,33.553,23,33,23 z" fill-rule="evenodd" fill="#000"/></svg>

Before

Width:  |  Height:  |  Size: 958 B

View File

@ -1 +0,0 @@
<svg enable-background="new 0 0 48 48" height="48px" id="Layer_1" version="1.1" viewBox="0 0 48 48" width="48px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path clip-rule="evenodd" d="M37,47H11c-2.209,0-4-1.791-4-4V5c0-2.209,1.791-4,4-4h18.973 c0.002,0,0.005,0,0.007,0h0.02H30c0.32,0,0.593,0.161,0.776,0.395l9.829,9.829C40.84,11.407,41,11.68,41,12l0,0v0.021 c0,0.002,0,0.003,0,0.005V43C41,45.209,39.209,47,37,47z M31,4.381V11h6.619L31,4.381z M39,13h-9c-0.553,0-1-0.448-1-1V3H11 C9.896,3,9,3.896,9,5v38c0,1.104,0.896,2,2,2h26c1.104,0,2-0.896,2-2V13z M33,39H15c-0.553,0-1-0.447-1-1c0-0.552,0.447-1,1-1h18 c0.553,0,1,0.448,1,1C34,38.553,33.553,39,33,39z M33,31H15c-0.553,0-1-0.447-1-1c0-0.552,0.447-1,1-1h18c0.553,0,1,0.448,1,1 C34,30.553,33.553,31,33,31z M33,23H15c-0.553,0-1-0.447-1-1c0-0.552,0.447-1,1-1h18c0.553,0,1,0.448,1,1C34,22.553,33.553,23,33,23 z" fill-rule="evenodd" fill="#fff"/></svg>

Before

Width:  |  Height:  |  Size: 958 B

View File

@ -1,30 +0,0 @@
.ce-code {
display: block;
width: 100%;
min-height: 100px;
border: 1px solid #ebeef3;
border-radius: 3px;
background: #fdfdff !important;
margin: 1em 0 !important;
padding: .5em .8em;
box-sizing: border-box;
white-space: pre-wrap;
font-family: 'monospace', 'monaco', 'consolas', 'courier';
line-height: 1.5em;
color: #325179;
font-size: .8em;
resize: vertical;
outline: none;
}
/**
* CodeX Editor styles overlay
* @todo change for ce-tool-wrapper__code
*/
.ce_block[data-type="code"]{
padding: 1em 0 !important;
}

View File

@ -1,82 +0,0 @@
/**
* Code Plugin\
* Creates code tag and adds content to this tag
*/
var code = (function(code_plugin) {
var baseClass = "ce-code";
/**
* Make initial header block
* @param {object} JSON with block data
* @return {Element} element to append
*/
var make_ = function (data) {
var tag = codex.editor.draw.node('TEXTAREA', [baseClass], {});
if (data && data.text) {
tag.value = data.text;
}
return tag;
};
/**
* Escapes HTML chars
*
* @param {string} input
* @return {string} escaped string
*/
var escapeHTML_ = function (input) {
var div = document.createElement('DIV'),
text = document.createTextNode(input);
div.appendChild(text);
return div.innerHTML;
};
/**
* Method to render HTML block from JSON
*/
code_plugin.render = function (data) {
return make_(data);
};
/**
* Method to extract JSON data from HTML block
*/
code_plugin.save = function (blockContent) {
var escaped = escapeHTML_(blockContent.value),
data = {
text : escaped
};
return data;
};
code_plugin.validate = function (data) {
if (data.text.trim() == '')
return;
return true;
};
code_plugin.destroy = function () {
code = null;
};
return code_plugin;
})({});

View File

@ -1,17 +0,0 @@
.ce-redactor .embed {
max-width: 600px;
margin: 15px 0;
background: #fff;
}
.ce-redactor .embed iframe {
width: 100% !important;
border: 0 !important;
}
.embed__loader {
background-image: url("loading.gif") !important;
background-repeat: no-repeat;
background-position: center center;
opacity: 0.5;
}

View File

@ -1,211 +0,0 @@
/**
* Embed plugin by gohabereg
* @version 1.0.0
*/
var embed = function(embed_plugin){
var methods = {
addInternal: function (content) {
codex.editor.content.switchBlock(codex.editor.content.currentNode, content);
var blockContent = codex.editor.content.currentNode.childNodes[0];
blockContent.classList.add('embed__loader');
setTimeout(function(){
blockContent.classList.remove('embed__loader');
}, 1000);
},
getHtmlWithEmbedId: function (type, id) {
return services[type].html.replace(/<\%\= remote\_id \%\>/g, id);
},
makeElementFromHtml: function(html) {
var wrapper = document.createElement('DIV');
wrapper.innerHTML = html;
return wrapper;
},
getRemoteId: function(source, execArray) {
switch(source) {
case 'yandex-music-track':
id = execArray[2]+'/'+execArray[1];
break;
case 'yandex-music-playlist':
id = execArray[1]+'/'+execArray[2];
break;
default:
id = execArray[1];
}
return id;
}
};
var services = {
youtube: {
regex: /^.*(?:(?:youtu\.be\/)|(?:youtube\.com)\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*)(?:[\?\&]t\=(\d*)|)/,
html: "<iframe src=\"https://www.youtube.com/embed/<%= remote_id %>\" style=\"width:100%;\" height=\"320\" frameborder=\"0\" allowfullscreen></iframe>",
height: 320,
width: 580
}
};
embed_plugin.make = function(data, isInternal) {
if (!data.remote_id)
return;
var html = methods.getHtmlWithEmbedId(data.source, data.remote_id),
block = methods.makeElementFromHtml(html);
block.dataset.remoteId = data.remote_id;
block.dataset.source = data.source;
block.dataset.thumbnailUrl = data.thumbnailUrl;
block.classList.add('embed');
// var sidePadding = (600 - services[data.source].width) / 2 + 'px';
// block.style.padding = '30px ' + sidePadding;
if (isInternal) {
methods.addInternal(block);
}
return block;
};
/**
* Saving JSON output.
* Upload data via ajax
*/
embed_plugin.save = function(blockContent) {
if (!blockContent)
return;
var data,
source = blockContent.dataset.source;
data = {
source: source,
remote_id: blockContent.dataset.remoteId,
thumbnailUrl: blockContent.dataset.thumbnailUrl,
height: services[source].height,
width: services[source].width
};
return data;
};
/**
* Render data
*/
embed_plugin.render = function(data) {
return embed_plugin.make(data);
};
embed_plugin.urlPastedCallback = function(url, pattern) {
var execArray = pattern.regex.exec(url),
id = methods.getRemoteId(pattern.type, execArray);
var data = {
source: pattern.type,
remote_id: id,
thumbnailUrl: url
};
embed_plugin.make(data, true);
};
embed_plugin.validate = function(savedData) {
var source = savedData.source,
execArray = services[source].regex.exec(savedData.thumbnailUrl),
remoteId = methods.getRemoteId(source, execArray);
return remoteId == savedData.remote_id;
};
embed_plugin.pastePatterns = [
{
type: 'vk',
regex: /https?:\/\/vk\.com\/.*(?:video)([-0-9]+_[0-9]+)/, ///https?.+vk?.com\/feed\?w=wall\d+_\d+/,
callback: embed_plugin.urlPastedCallback
},
{
type: 'youtube',
regex: /(?:https?:\/{2})?(?:w{3}\.)?youtu(?:be)?\.(?:com|be)(?:\/watch\?v=|\/)([^\s&]+)/,
callback: embed_plugin.urlPastedCallback
},
{
type: 'vimeo',
regex: /(?:http[s]?:\/\/)?(?:www.)?vimeo\.co(?:.+\/([^\/]\d+)(?:#t=[\d]+)?s?$)/,
callback: embed_plugin.urlPastedCallback
},
{
type: 'coub',
regex: /https?:\/\/coub\.com\/view\/([^\/\?\&]+)/,
callback: embed_plugin.urlPastedCallback
},
{
type: 'vine',
regex: /https?:\/\/vine\.co\/v\/([^\/\?\&]+)/,
callback: embed_plugin.urlPastedCallback
},
{
type: 'imgur',
regex: /https?:\/\/(?:i\.)?imgur\.com.*\/([a-zA-Z0-9]+)(?:\.gifv)?/,
callback: embed_plugin.urlPastedCallback
},
{
type: 'gfycat',
regex: /https?:\/\/gfycat\.com(?:\/detail)?\/([a-zA-Z]+)/,
callback: embed_plugin.urlPastedCallback
},
{
type: 'twitch-channel',
regex: /https?:\/\/www.twitch.tv\/([^\/\?\&]*)/,
callback: embed_plugin.urlPastedCallback
},
{
type: 'twitch-video',
regex: /https?:\/\/www.twitch.tv\/(?:[^\/\?\&]*\/v|videos)\/([0-9]*)/,
callback: embed_plugin.urlPastedCallback
},
{
type: 'yandex-music-album',
regex: /https?:\/\/music.yandex.ru\/album\/([0-9]*)/,
callback: embed_plugin.urlPastedCallback
},
{
type: 'yandex-music-track',
regex: /https?:\/\/music.yandex.ru\/album\/([0-9]*)\/track\/([0-9]*)/,
callback: embed_plugin.urlPastedCallback
},
{
type: 'yandex-music-playlist',
regex: /https?:\/\/music.yandex.ru\/users\/([^\/\?\&]*)\/playlists\/([0-9]*)/,
callback: embed_plugin.urlPastedCallback
} ];
embed_plugin.destroy = function () {
embed = null;
};
return embed_plugin;
}({});

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -1,41 +0,0 @@
/**
* Plugin styles
*/
.ce-header {
padding: .7em 0;
margin: 0;
line-height: 1.4em;
}
.ce-header p,
.ce-header div{
padding: 0 !important;
margin: 0 !important;
}
/** H e a d e r - settings */
.ce_plugin_header--select_button{
display: block;
color: #306ac7;
cursor: pointer;
line-height: 1.3em;
}
.ce_plugin_header--select_button:not(:last-of-type){
margin-bottom: 1.5em;
}
.ce_plugin_header--select_button:hover{
color: #a1b4ec;
}
/**
* Empty header placeholder
*/
.ce-header:empty::before{
content : attr(data-placeholder);
color: #818BA1;
opacity: .7;
transition: opacity 200ms ease;
}
.ce-header:focus::before{
opacity: .1;
}

View File

@ -1,169 +0,0 @@
/**
* Example of making plugin
* H e a d e r
*/
var header = (function(header_plugin) {
/**
* @private
*/
var methods_ = {
/**
* Binds click event to passed button
*/
addSelectTypeClickListener : function (el, type) {
el.addEventListener('click', function () {
methods_.selectTypeClicked(type);
}, false);
},
/**
* Replaces old header with new type
* @params {string} type - new header tagName: H1H6
*/
selectTypeClicked : function (type) {
var old_header, new_header;
/** Now current header stored as a currentNode */
old_header = codex.editor.content.currentNode.querySelector('[contentEditable]');
/** Making new header */
new_header = codex.editor.draw.node(type, ['ce-header'], { innerHTML : old_header.innerHTML });
new_header.contentEditable = true;
new_header.setAttribute('data-placeholder', 'Заголовок');
new_header.dataset.headerData = type;
codex.editor.content.switchBlock(old_header, new_header, 'header');
/** Close settings after replacing */
codex.editor.toolbar.settings.close();
}
};
/**
* @private
*
* Make initial header block
* @param {object} JSON with block data
* @return {Element} element to append
*/
var make_ = function (data) {
var availableTypes = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
tag,
headerType = 'h2';
if ( data && data['heading-styles'] && availableTypes.includes(data['heading-styles']) ) {
headerType = data['heading-styles'];
}
tag = document.createElement(headerType);
/**
* Save header type in data-attr.
* We need it in save method to extract type from HTML to JSON
*/
tag.dataset.headerData = headerType;
if (data && data.text) {
tag.textContent = data.text;
}
if (!tag.dataset.headerData) {
tag.dataset.headerData = 'h2';
}
tag.classList.add('ce-header');
tag.setAttribute('data-placeholder', 'Заголовок');
tag.contentEditable = true;
return tag;
};
header_plugin.prepareDataForSave = function(data) {
};
/**
* Method to render HTML block from JSON
*/
header_plugin.render = function (data) {
return make_(data);
};
/**
* Method to extract JSON data from HTML block
*/
header_plugin.save = function (blockContent) {
var data = {
"heading-styles": blockContent.dataset.headerData,
"format": "html",
"text": blockContent.textContent || ''
};
return data;
};
/**
* Settings panel content
* - - - - - - - - - - - - -
* | настройки H1 H2 H3 |
* - - - - - - - - - - - - -
* @return {Element} element contains all settings
*/
header_plugin.makeSettings = function () {
var holder = codex.editor.draw.node('DIV', ['cdx-plugin-settings--horisontal'], {} ),
types = {
h2: 'H2',
h3: 'H3',
h4: 'H4'
},
selectTypeButton;
/** Now add type selectors */
for (var type in types){
selectTypeButton = codex.editor.draw.node('SPAN', ['cdx-plugin-settings__item'], { textContent : types[type] });
methods_.addSelectTypeClickListener(selectTypeButton, type);
holder.appendChild(selectTypeButton);
}
return holder;
};
header_plugin.validate = function(data) {
if (data.text.trim() === '' || data['heading-styles'].trim() === ''){
return false;
}
return true;
};
header_plugin.destroy = function () {
header = null;
}
return header_plugin;
})({});

View File

@ -1,180 +0,0 @@
/**
* Image plugin for codex-editor
* @author CodeX Team <team@ifmo.su>
*
* @version 0.0.1
*/
.ce-image__wrapper img {
transition: all 500ms ease-in;
will-change: opacity, filter;
}
.ce-image__preview img {
opacity: .5;
filter: blur(1.7px) grayscale(1);
}
/** upload image form */
.ce-plugin-image__holder{
position: relative;
background: #FEFEFE;
border: 2px solid #edeff5;
text-align: center;
margin: 30px 0;
padding: 15px;
}
.ce-plugin-image__holder input{
border: 0;
background: transparent;
outline: none;
-webkit-appearance: none;
font-size: 1.2em;
color: #A5ABBC;
}
/* Placeholder color for Chrome */
.ce-plugin-image__holder input::-webkit-input-placeholder {
color: #A5ABBC;
}
/* Placeholder color for IE 10+ */
.ce-plugin-image__holder input:-ms-input-placeholder {
color: #A5ABBC;
}
/* Placeholder color for Firefox 19+ */
.ce-plugin-image__holder input::-moz-placeholder {
color: #A5ABBC;
opacity: 1;
}
.ce-plugin-image__loader {
background-color: transparent;
background-image: repeating-linear-gradient(-45deg, transparent, transparent 4px, #f5f9ff 4px, #eaedef 8px) !important;
background-size: 56px 56px;
animation: loading-bar 5s infinite linear;
}
@keyframes loading-bar {
100% { background-position: -56% 0 }
}
.ce-plugin-image__button{
cursor: pointer;
font-size: 1;
color: #8990AA;
}
.ce-plugin-image__button:hover{
color: #393F52;
}
/** Uploaded image */
.ce-plugin-image__wrapper {
padding: 1em 0;
}
.ce-image__wrapper--bordered {
border: 1px solid #eee;
box-sizing: border-box;
}
.ce-plugin-image__uploaded--centered {
display: block;
max-width: 600px;
margin: 0 auto;
}
.ce-plugin-image__uploaded--stretched {
width: 100%;
}
.ce-plugin-image__firstlevel--stretch {
margin: 0 !important;
max-width: none !important;
padding: 0 !important;
}
.ce-plugin-image__caption {
max-width: 600px;
margin: .5em auto 0;
padding: .5em;
text-align: center;
color: #898a8c;
background: #fff;
border: 1px solid #ebeef3;
border-radius: 3px;
box-sizing: border-box;
}
.ce-plugin-image__caption:empty::before {
content: 'Введите подпись';
text-align: center;
font-weight: normal;
color: #a1a5b3;;
opacity: .9;
transition: opacity 200ms ease;
}
.ce-plugin-image__caption:focus::before {
opacity: .1;
text-align: center;
}
/** Settings */
.ce_plugin_image--select_button{
display: block;
color: #306ac7;
cursor: pointer;
line-height: 1.3em;
}
.ce_plugin_image--select_button:not(:last-of-type){
margin-bottom: 1.5em;
}
.ce_plugin_image--select_button:hover{
color: #a1b4ec;
}
.ce-image-settings {
padding: 7px 0
}
.ce-image-settings__item {
padding: 7px 15px;
cursor: pointer;
}
.ce-settings-checkbox {
display: inline-block;
width: 17px;
background: #494361;
line-height: 0px;
border-radius: 14px;
padding: 2px;
vertical-align: text-top;
margin-right: 16px;
transition: background-color 200ms ease-out;
will-change: background-color;
}
.ce-settings-checkbox__toggler {
display: inline-block;
width: 12px;
height: 12px;
border-radius: 50%;
background: #fff;
transition: margin 200ms ease-out;
}
/**
* Setting is active
*/
.ce-image-settings__item--toggled {
color: #1FAA7E;
}
.ce-image-settings__item--toggled .ce-settings-checkbox {
background-color: #14DC9E;
}
.ce-image-settings__item--toggled .ce-settings-checkbox__toggler {
margin-left: 6px;
}

View File

@ -1,701 +0,0 @@
/**
* Image plugin for codex-editor
* @author CodeX Team <team@ifmo.su>
*
* @version 1.3.0
*/
var image = (function(image_plugin) {
/**
* @private
*
* CSS classNames
*/
var elementClasses_ = {
ce_image : 'ce-image',
loading : 'ce-plugin-image__loader',
blockStretched: 'ce-block--stretched',
uploadedImage : {
centered : 'ce-plugin-image__uploaded--centered',
stretched : 'ce-plugin-image__uploaded--stretched'
},
imageCaption : 'ce-plugin-image__caption',
imageWrapper : 'ce-plugin-image__wrapper',
formHolder : 'ce-plugin-image__holder',
uploadButton : 'ce-plugin-image__button',
imagePreview : 'ce-image__preview',
selectorHolder: 'ce-settings-checkbox',
selectorButton: 'ce-settings-checkbox__toggler',
settingsItem: 'ce-image-settings__item',
imageWrapperBordered : 'ce-image__wrapper--bordered',
toggled : 'ce-image-settings__item--toggled'
};
/**
*
* @private
*
* UI methods
*/
var ui_ = {
holder : function(){
var element = document.createElement('DIV');
element.classList.add(elementClasses_.formHolder);
element.classList.add(elementClasses_.ce_image);
return element;
},
uploadButton : function(){
var button = document.createElement('SPAN');
button.classList.add(elementClasses_.uploadButton);
button.innerHTML = '<i class="ce-icon-picture"> </i>';
button.innerHTML += 'Загрузить фотографию';
return button;
},
/**
* @param {object} file - file path
* @param {string} style - css class
* @return {object} image - document IMG tag
*/
image : function(file, styles) {
var image = document.createElement('IMG');
styles.map(function(item) {
image.classList.add(item);
});
image.src = file.url;
image.dataset.bigUrl = file.bigUrl;
return image;
},
wrapper : function() {
var div = document.createElement('div');
div.classList.add(elementClasses_.imageWrapper);
return div;
},
caption : function() {
var div = document.createElement('div');
div.classList.add(elementClasses_.imageCaption);
div.contentEditable = true;
return div;
},
/**
* Draws form for image upload
*/
makeForm : function() {
var holder = ui_.holder(),
uploadButton = ui_.uploadButton();
holder.appendChild(uploadButton);
uploadButton.addEventListener('click', uploadButtonClicked_, false );
image.holder = holder;
return holder;
},
/**
* wraps image and caption
* @param {object} data - image information
* @param {string} imageTypeClass - plugin's style
* @param {boolean} stretched - stretched or not
* @return wrapped block with image and caption
*/
makeImage : function(data, imageTypeClasses, stretched, bordered) {
var file = data,
text = data.caption,
type = data.type,
image = ui_.image(file, imageTypeClasses),
caption = ui_.caption(),
wrapper = ui_.wrapper();
caption.innerHTML = text || '';
wrapper.dataset.stretched = stretched;
wrapper.dataset.bordered = bordered;
/** Appeding to the wrapper */
wrapper.appendChild(image);
wrapper.appendChild(caption);
return wrapper;
},
/**
* @param {HTML} data - Rendered block with image
*/
getImage : function(data) {
var image = data.querySelector('.' + elementClasses_.uploadedImage.centered) ||
data.querySelector('.' + elementClasses_.uploadedImage.stretched);
return image;
},
/**
* wraps image and caption
* @deprecated
* @param {object} data - image information
* @return wrapped block with image and caption
*/
centeredImage : function(data) {
var file = data.file,
text = data.caption,
type = data.type,
image = ui_.image(file, elementClasses_.uploadedImage.centered),
caption = ui_.caption(),
wrapper = ui_.wrapper();
caption.textContent = text;
wrapper.dataset.stretched = 'false';
/** Appeding to the wrapper */
wrapper.appendChild(image);
wrapper.appendChild(caption);
return wrapper;
},
/**
* wraps image and caption
* @deprecated
* @param {object} data - image information
* @return stretched image
*/
stretchedImage : function(data) {
var file = data.file,
text = data.caption,
type = data.type,
image = ui_.image(file, elementClasses_.uploadedImage.stretched),
caption = ui_.caption(),
wrapper = ui_.wrapper();
caption.textContent = text;
wrapper.dataset.stretched = 'true';
/** Appeding to the wrapper */
wrapper.appendChild(image);
wrapper.appendChild(caption);
return wrapper;
}
};
/**
* @private
*
* After render callback
*/
var uploadButtonClicked_ = function(event) {
var url = image_plugin.config.uploadImage,
beforeSend = uploadingCallbacks_.ByClick.beforeSend,
success = uploadingCallbacks_.ByClick.success,
error = uploadingCallbacks_.ByClick.error;
/** Define callbacks */
codex.editor.transport.selectAndUpload({
url : url,
multiple : false,
accept : 'image/*',
beforeSend : beforeSend,
success : success,
error : error
});
};
var methods_ = {
addSelectTypeClickListener : function(el, type) {
el.addEventListener('click', function() {
// el - settings element
switch (type) {
case 'bordered':
methods_.toggleBordered(type, this); break;
case 'stretched':
methods_.toggleStretched(type, this); break;
}
}, false);
},
toggleBordered : function(type, clickedSettingsItem ) {
var current = codex.editor.content.currentNode,
blockContent = current.childNodes[0],
img = ui_.getImage(current),
wrapper = current.querySelector('.' + elementClasses_.imageWrapper);
if (!img) {
return;
}
/**
* Add classes to the IMG tag and to the Settings element
*/
img.classList.toggle(elementClasses_.imageWrapperBordered);
clickedSettingsItem.classList.toggle(elementClasses_.toggled);
/**
* Save settings in dataset
*/
wrapper.dataset.bordered = img.classList.contains(elementClasses_.imageWrapperBordered);
setTimeout(function() {
codex.editor.toolbar.settings.close();
}, 200);
},
toggleStretched : function( type, clickedSettingsItem ) {
var current = codex.editor.content.currentNode,
blockContent = current.childNodes[0],
img = ui_.getImage(current),
wrapper = current.querySelector('.' + elementClasses_.imageWrapper);
if (!img) {
return;
}
/** Clear classList */
blockContent.classList.add(elementClasses_.blockStretched);
img.classList.toggle(elementClasses_.uploadedImage.stretched);
img.classList.toggle(elementClasses_.uploadedImage.centered);
clickedSettingsItem.classList.toggle(elementClasses_.toggled);
wrapper.dataset.stretched = img.classList.contains(elementClasses_.uploadedImage.stretched);
setTimeout(function() {
codex.editor.toolbar.settings.close();
}, 1000);
}
};
/**
* @private
* Callbacks
*/
var uploadingCallbacks_ = {
ByClick : {
/**
* Before sending ajax request
*/
beforeSend : function() {
var input = codex.editor.transport.input,
files = input.files;
var validFileExtensions = ["jpg", "jpeg", "bmp", "gif", "png"];
var type = files[0].type.split('/');
var result = validFileExtensions.some(function(ext) {
return ext == type[1];
});
if (!result) {
return;
}
var reader = new FileReader();
reader.readAsDataURL(files[0]);
reader.onload = function(e) {
var data = {
background : false,
border : false,
isstretch : false,
url : e.target.result,
bigUrl : null,
width : null,
height : null,
additionalData : null,
caption : '',
cover : null
};
var newImage = make_(data);
codex.editor.content.switchBlock(image.holder, newImage, 'image');
newImage.classList.add(elementClasses_.imagePreview);
/**
* Change holder to image
*/
image.holder = newImage;
};
},
/** Photo was uploaded successfully */
success : function(result) {
var parsed = JSON.parse(result),
data,
currentBlock = codex.editor.content.currentNode;
/**
* Preparing {Object} data to draw an image
* @uses ceImage.make method
*/
data = parsed.data;
image.holder.classList.remove(elementClasses_.imagePreview);
/**
* Change src of image
*/
var newImage = image.holder.getElementsByTagName('IMG')[0];
newImage.src = parsed.data.url;
newImage.dataset.bigUrl = parsed.data.bigUrl;
newImage.dataset.width = parsed.data.width;
newImage.dataset.height = parsed.data.height;
newImage.dataset.additionalData = parsed.data.additionalData;
},
/** Error callback. Sends notification to user that something happend or plugin doesn't supports method */
error : function(result) {
var oldHolder = image.holder;
var form = ui_.makeForm();
codex.editor.content.switchBlock(oldHolder, form, 'image');
}
},
ByPaste : {
/**
* Direct upload
* Any URL that contains image extension
* @param url
*/
uploadImageFromUrl : function(path) {
var image,
current = codex.editor.content.currentNode,
beforeSend,
success_callback;
/** When image is uploaded to redactors folder */
success_callback = function(data) {
var imageInfo = JSON.parse(data);
var newImage = image.getElementsByTagName('IMG')[0];
newImage.dataset.stretched = false;
newImage.dataset.src = imageInfo.url;
newImage.dataset.bigUrl = imageInfo.bigUrl;
newImage.dataset.width = imageInfo.width;
newImage.dataset.height = imageInfo.height;
newImage.dataset.additionalData = imageInfo.additionalData;
image.classList.remove(elementClasses_.imagePreview);
};
/** Before sending XMLHTTP request */
beforeSend = function() {
var content = current.querySelector('.ce-block__content');
var data = {
background: false,
border: false,
isStretch: false,
file: {
url: path,
bigUrl: null,
width: null,
height: null,
additionalData: null
},
caption: '',
cover: null
};
image = codex.editor.tools.image_extended.render(data);
image.classList.add(elementClasses_.imagePreview);
var img = image.querySelector('img');
codex.editor.content.switchBlock(codex.editor.content.currentNode, image, 'image');
};
/** Preparing data for XMLHTTP */
var data = {
url: image_plugin.config.uploadFromUrl,
type: "POST",
data : {
url: path
},
beforeSend : beforeSend,
success : success_callback
};
codex.editor.core.ajax(data);
}
}
};
/**
* Image path
* @type {null}
*/
image_plugin.path = null;
/**
* Plugin configuration
*/
image_plugin.config = null;
/**
*
* @private
*
* @param data
* @return {*}
*
*/
var make_ = function ( data ) {
var holder,
classes = [];
if (data) {
if (data.border) {
classes.push(elementClasses_.imageWrapperBordered);
}
if ( data.isstretch || data.isstretch === 'true') {
classes.push(elementClasses_.uploadedImage.stretched);
holder = ui_.makeImage(data, classes, 'true', data.border);
} else {
classes.push(elementClasses_.uploadedImage.centered);
holder = ui_.makeImage(data, classes, 'false', data.border);
}
return holder;
} else {
holder = ui_.makeForm();
return holder;
}
};
/**
* @private
*
* Prepare clear data before save
*
* @param data
*/
var prepareDataForSave_ = function(data) {
};
/**
* @public
* @param config
*/
image_plugin.prepare = function(config) {
image_plugin.config = config;
return Promise.resolve();
};
/**
* @public
*
* this tool works when tool is clicked in toolbox
*/
image_plugin.appendCallback = function(event) {
/** Upload image and call success callback*/
uploadButtonClicked_(event);
};
/**
* @public
*
* @param data
* @return {*}
*/
image_plugin.render = function( data ) {
return make_(data);
};
/**
* @public
*
* @param block
* @return {{background: boolean, border: boolean, isstretch: boolean, file: {url: (*|string|Object), bigUrl: (null|*), width: *, height: *, additionalData: null}, caption: (string|*|string), cover: null}}
*/
image_plugin.save = function ( block ) {
var content = block,
image = ui_.getImage(content),
caption = content.querySelector('.' + elementClasses_.imageCaption);
var data = {
background : false,
border : content.dataset.bordered === 'true' ? true : false,
isstretch : content.dataset.stretched === 'true' ? true : false,
// file : {
url : image.dataset.src || image.src,
bigUrl : image.dataset.bigUrl,
width : image.width,
height : image.height,
additionalData :null,
// },
caption : caption.innerHTML || '',
cover : null
};
return data;
};
/**
* @public
*
* Settings panel content
* @return {Element} element contains all settings
*/
image_plugin.makeSettings = function () {
var currentNode = codex.editor.content.currentNode,
wrapper = currentNode.querySelector('.' + elementClasses_.imageWrapper),
holder = document.createElement('DIV'),
types = {
stretched : "На всю ширину",
bordered : "Добавить рамку"
},
currentImageWrapper = currentNode.querySelector('.' + elementClasses_.imageWrapper ),
currentImageSettings = currentImageWrapper.dataset;
/** Add holder classname */
holder.className = 'ce-image-settings';
/** Now add type selectors */
for (var type in types){
/**
* Settings template
*/
var settingsItem = document.createElement('DIV'),
selectorsHolder = document.createElement('SPAN'),
selectorsButton = document.createElement('SPAN');
settingsItem.classList.add(elementClasses_.settingsItem);
selectorsHolder.classList.add(elementClasses_.selectorHolder);
selectorsButton.classList.add(elementClasses_.selectorButton);
selectorsHolder.appendChild(selectorsButton);
settingsItem.appendChild(selectorsHolder);
selectTypeButton = document.createTextNode(types[type]);
settingsItem.appendChild(selectTypeButton);
/**
* Activate previously selected settings
*/
if ( currentImageSettings[type] == 'true' ){
settingsItem.classList.add(elementClasses_.toggled);
}
methods_.addSelectTypeClickListener(settingsItem, type);
holder.appendChild(settingsItem);
}
return holder;
};
/**
* Share as API
*/
image_plugin.uploadImageFromUri = uploadingCallbacks_.ByPaste.uploadImageFromUrl;
image_plugin.pastePatterns = [
{
type: 'image',
regex: /(?:([^:\/?#]+):)?(?:\/\/([^\/?#]*))?([^?#]*\.(?:jpe?g|gif|png))(?:\?([^#]*))?(?:#(.*))?/i,
callback: image_plugin.uploadImageFromUri
},
{
type: 'uploadCare',
regex: /^https:\/\/(uploadcare\.cmtt\.ru|ucarecdn\.com|static[0-9]+\.siliconrus\.cmtt\.ru|static[0-9]+\.cmtt\.ru)/i,
callback: image_plugin.uploadImageFromUri
} ];
image_plugin.destroy = function () {
image = null;
};
return image_plugin;
})({});

View File

@ -1,10 +0,0 @@
.ce-redactor .instagram {
width: 100%;
max-width: 650px;
margin: 10px auto;
}
.instagram__loader {
background: url("loading.gif") !important;
opacity: 0.1;
}

View File

@ -1,162 +0,0 @@
/**
* Instagram plugin
* @version 1.0.0
*/
var instagram = (function(instagram_plugin) {
var methods = {
render : function(content) {
codex.editor.content.switchBlock(codex.editor.content.currentNode, content);
setTimeout(function() {
window.instgrm.Embeds.process();
}, 200);
},
/**
* Drawing html content.
*
* @param url
* @returns {Element} blockquote - HTML template for Instagram Embed JS
*/
instagramBlock : function(url) {
var blockquote = codex.editor.draw.node('BLOCKQUOTE', 'instagram-media instagram', {}),
div = codex.editor.draw.node('DIV', '', {}),
paragraph = codex.editor.draw.node('P', 'ce-paste__instagram--p', {}),
anchor = codex.editor.draw.node('A', '', { href : url });
blockquote.dataset.instgrmVersion = 4;
paragraph.appendChild(anchor);
div.appendChild(paragraph);
blockquote.appendChild(div);
return blockquote;
}
};
/**
* Prepare before usage
* Load important scripts to render embed
*/
instagram_plugin.prepare = function() {
return new Promise(function(resolve, reject){
codex.editor.core.importScript("https://platform.instagram.com/en_US/embeds.js", 'instagram-api').then(function(){
resolve();
}).catch(function(){
reject(Error('Instagram API was not loaded'));
});
});
};
/**
* @private
*
* Make instagram embed via Widgets method
*/
var make_ = function(data, isInternal) {
if (!data.instagram_url)
return;
var block = methods.instagramBlock(data.instagram_url);
if (isInternal) {
setTimeout(function() {
/** Render block */
methods.render(block);
}, 200);
}
if (!isInternal) {
methods.render(block);
}
return block;
};
instagram_plugin.validate = function(data) {
return true;
};
/**
* Saving JSON output.
* Upload data via ajax
*/
instagram_plugin.save = function(blockContent) {
var data;
if (!blockContent)
return;
/** Example */
data = {
instagram_url: blockContent.src
};
return data;
};
instagram_plugin.validate = function(data) {
var checkUrl = new RegExp("http?.+instagram.com\/p?.");
if (!data.instagram_url || checkUrl.exec(data.instagram_url).length == 0)
return;
return true;
};
/**
* Render data
*/
instagram_plugin.render = function(data) {
return make_(data);
};
/**
* callback for instagram url's coming from pasteTool
* Using instagram Embed Widgete to render
* @param url
*/
instagram_plugin.urlPastedCallback = function(url) {
var data = {
instagram_url: url
};
make_(data, true);
};
instagram_plugin.pastePatterns = [
{
type: 'instagram',
regex: /http?.+instagram.com\/p\/([a-zA-Z0-9]*)\S*/,
callback: instagram_plugin.urlPastedCallback
}
];
instagram_plugin.destroy = function () {
instagram = null;
delete window.instgrm
};
return instagram_plugin;
})({});

Binary file not shown.

Before

Width:  |  Height:  |  Size: 329 B

View File

@ -1,86 +0,0 @@
.ce-link {
padding: 0.7em 0 !important;
border-radius: 3px;
}
.clearfix:after {
visibility: hidden;
display: block;
font-size: 0;
content: " ";
clear: both;
height: 0;
}
.ceditor-tool-link-input {
outline: none;
border: 0;
width: 100%;
background: transparent;
font-size: 1em;
padding: 8px 25px;
transition: background 200ms;
border-left: 3px solid #65d8b3;
}
.tool-link-panel {
position: relative;
margin: 5px 0;
background: #f8f7ef;
border: 1px solid transparent;
padding: 25px 30px;
}
.tool-link-image {
float:right;
width: 75px;
border-radius: 3px;
}
.tool-link-title {
display: block;
width: 340px;
margin-bottom: 4px;
line-height: 1.2em;
font-size: 20px;
font-weight: 700;
color: #000;
}
.tool-link-description {
display: block;
margin-top: 10px;
font-size: 14px;
color: #000;
}
.tool-link-link {
width: 360px;
font-size: 10px;
margin-bottom: 4px;
letter-spacing: 1px;
overflow: hidden;
text-transform: uppercase;
text-decoration: none;
color: rgba(165,156,86,.8);
}
.tool-link-loader {
background-color: transparent;
background-image: repeating-linear-gradient(-45deg, transparent, transparent 4px, #f5f9ff 4px, #eaedef 8px) !important;
background-size: 56px 56px;
animation: loading-bar 5s infinite linear;
}
@keyframes loading-bar {
100% { background-position: -56% 0 }
}
.tool-link-error {
background: rgb(255, 241, 241);
color: #bf4747;
}
.tool-link-error .ceditor-tool-link-input {
border-left-color: #d86b6b
}

View File

@ -1,350 +0,0 @@
/**
* Created by nostr on 29.06.16.
*/
/**
* Link tool plugin
*/
var link = (function(link_plugin) {
var settings = {
defaultText : 'Вставьте ссылку ...',
ENTER_KEY : 13,
currentBlock : null,
currentInput : null,
elementClasses : {
link: "tool-link-link",
image: "tool-link-image",
title: "tool-link-title",
description: "tool-link-description",
loader: "tool-link-loader",
error: "tool-link-error"
}
};
var ui = {
make : function (json) {
var wrapper = ui.wrapper(),
siteImage = ui.image(json.image, settings.elementClasses.image),
siteTitle = ui.title(json.title),
siteDescription = ui.description(json.description),
siteLink = ui.link(json.url, json.url);
wrapper.appendChild(siteImage);
wrapper.appendChild(siteTitle);
wrapper.appendChild(siteLink);
wrapper.appendChild(siteDescription);
siteTitle.contentEditable = true;
siteDescription.contentEditable = true;
return wrapper;
},
mainBlock : function () {
var wrapper = document.createElement('div');
wrapper.classList.add("ceditor-tool-link");
return wrapper;
},
input : function () {
var inputTag = document.createElement('input');
inputTag.classList.add("ceditor-tool-link-input");
inputTag.placeholder = settings.defaultText;
inputTag.contentEditable = false;
return inputTag;
},
wrapper : function () {
var wrapper = document.createElement('div');
wrapper.classList.add('tool-link-panel', 'clearfix');
return wrapper;
},
image : function (imageSrc, imageClass) {
var imageTag = document.createElement('img');
imageTag.classList.add(imageClass);
imageTag.setAttribute('src', imageSrc);
return imageTag;
},
link : function (linkUrl, linkText) {
var linkTag = document.createElement('a');
linkTag.classList.add(settings.elementClasses.link);
linkTag.href = linkUrl;
linkTag.target = "_blank";
linkTag.innerText = linkText;
return linkTag;
},
title : function (titleText) {
var titleTag = document.createElement('div');
titleTag.classList.add("tool-link-content", settings.elementClasses.title);
titleTag.innerHTML = titleText;
return titleTag;
},
description : function (descriptionText) {
var descriptionTag = document.createElement('div');
descriptionTag.classList.add("tool-link-content", settings.elementClasses.description);
descriptionTag.innerHTML = descriptionText;
return descriptionTag;
}
};
var methods = {
blockPasteCallback : function (event) {
var clipboardData = event.clipboardData || window.clipboardData,
pastedData = clipboardData.getData('Text'),
block = event.target.parentNode;
methods.renderLink(pastedData, block);
event.stopPropagation();
},
blockKeyDownCallback : function (event) {
var inputTag = event.target,
block = inputTag.parentNode,
url;
if ( block.classList.contains(settings.elementClasses.error) ) {
block.classList.remove(settings.elementClasses.error);
}
if (event.keyCode == settings.ENTER_KEY) {
url = inputTag.value;
methods.renderLink(url, block);
event.preventDefault();
}
},
renderLink : function (url, block) {
Promise.resolve()
.then(function () {
return methods.urlify(url);
})
.then(function (url) {
/* Show loader gif **/
block.classList.add(settings.elementClasses.loader);
return fetch( link_plugin.config.fetchUrl + '?url=' + encodeURI(url) );
})
.then(function (response) {
if (response.status == "200"){
return response.json();
} else {
return Error("Invalid response status: %o", response);
}
})
.then(function (json) {
methods.composeLinkPreview(json, block);
})
.catch(function(error) {
/* Hide loader gif **/
block.classList.remove(settings.elementClasses.loader);
block.classList.add(settings.elementClasses.error);
codex.editor.core.log('Error while doing things with link paste: %o', 'error', error);
});
},
urlify : function (text) {
var urlRegex = /(https?:\/\/\S+)/g;
var links = text.match(urlRegex);
if (links) {
return links[0];
}
return Promise.reject(Error("Url is not matched"));
},
composeLinkPreview : function (json, currentBlock) {
if (json == {}) {
return;
}
var previewBlock = ui.make(json);
settings.currentInput.remove();
currentBlock.appendChild(previewBlock);
currentBlock.classList.remove(settings.elementClasses.loader);
}
};
link_plugin.prepare = function (config) {
link_plugin.config = config;
return Promise.resolve();
};
/**
* Make initial header block
* @param {object} JSON with block data
* @return {Element} element to append
*/
link_plugin.makeNewBlock = function (data) {
var wrapper = ui.mainBlock(),
tag = ui.input();
settings.currentInput = tag;
wrapper.appendChild(tag);
wrapper.classList.add('ce-link');
/**
* Bind callbacks
**/
tag.addEventListener('paste', methods.blockPasteCallback, false);
tag.addEventListener('keydown', methods.blockKeyDownCallback, false);
return wrapper;
};
/**
* Method to render HTML block from JSON
*/
link_plugin.render = function (json) {
if ( json ) {
var block = ui.mainBlock(),
tag = ui.make(json);
block.classList.add('ce-link');
block.appendChild(tag);
return block;
} else {
var wrapper = ui.mainBlock(),
tag = ui.input();
settings.currentInput = tag;
wrapper.appendChild(tag);
wrapper.classList.add('ce-link');
/**
* Bind callbacks
**/
tag.addEventListener('paste', methods.blockPasteCallback, false);
tag.addEventListener('keydown', methods.blockKeyDownCallback, false);
return wrapper;
}
};
link_plugin.validate = function (data) {
if (data.url.trim() == '' || data.title.trim() == '' || data.description.trim() == '')
return;
return true;
};
/**
* Method to extract JSON data from HTML block
*/
link_plugin.save = function (blockContent){
var linkElement = settings.elementClasses.link;
var data = {
url : blockContent.querySelector("." + linkElement).href,
shortLink : blockContent.querySelector("." + linkElement).textContent || '',
image : blockContent.querySelector("." + settings.elementClasses.image).src || '',
title : blockContent.querySelector("." + settings.elementClasses.title).textContent || '',
description : blockContent.querySelector("." + settings.elementClasses.description).textContent || ''
};
return data;
};
link_plugin.destroy = function () {
link = null;
};
return link_plugin;
})({});

View File

@ -1,45 +0,0 @@
.cdx-plugin-list {
margin: 0;
padding: .5em 0;
line-height: 1.7em;
}
ul.cdx-plugin-list {
list-style-type: disc;
}
ol.cdx-plugin-list {
list-style-type: decimal;
}
.cdx-plugin-list__li {
display: list-item;
background: #fff;
border: 1px solid #ebeef3;
border-radius: 3px;
margin: .5em;
padding: .5em;
list-style-position: outside;
list-style-type: inherit;
margin-left: 1.1em;
}
.cdx-plugin-list p {
margin: 0.6em 0;
}
.cdx-plugin-list p:first-of-type {
margin-top: 0;
}
.cdx-plugin-list p:last-of-type {
margin-bottom: 0;
}
/**
* List style settigns icons
*/
.ce_plugin_list--select_button i{
margin-right: 1.3em;
}

View File

@ -1,241 +0,0 @@
/**
* Code Plugin\
* Creates code tag and adds content to this tag
*/
var list = (function(list_plugin) {
/**
* CSS class names
*/
var elementClasses_ = {
pluginWrapper: 'cdx-plugin-list',
li: 'cdx-plugin-list__li',
settings: 'cdx-plugin-list__settings',
settingsItem: 'cdx-plugin-settings__item'
};
var LIST_ITEM_TAG = 'LI';
var ui = {
make: function (blockType) {
var wrapper = this.block(blockType || 'UL', elementClasses_.pluginWrapper);
wrapper.dataset.type = blockType;
wrapper.contentEditable = true;
wrapper.addEventListener('keydown', methods_.keyDown);
return wrapper;
},
block: function (blockType, blockClass) {
var block = document.createElement(blockType);
if (blockClass) block.classList.add(blockClass);
return block;
},
button: function (buttonType) {
var types = {
unordered: '<i class="ce-icon-list-bullet"></i>Обычный',
ordered: '<i class="ce-icon-list-numbered"></i>Нумерованный'
},
button = document.createElement('DIV');
button.innerHTML = types[buttonType];
button.classList.add(elementClasses_.settingsItem);
return button;
}
};
var methods_ = {
/**
* Changes block type => OL or UL
* @param event
* @param blockType
*/
changeBlockStyle : function (event, blockType) {
var currentBlock = codex.editor.content.currentNode,
newEditable = ui.make(blockType),
oldEditable = currentBlock.querySelector("[contenteditable]");
newEditable.dataset.type = blockType;
newEditable.innerHTML = oldEditable.innerHTML;
newEditable.classList.add(elementClasses_.pluginWrapper);
codex.editor.content.switchBlock(currentBlock, newEditable, 'list');
},
keyDown: function (e) {
var controlKeyPressed = e.ctrlKey || e.metaKey,
keyCodeForA = 65;
/**
* If CTRL+A (CMD+A) was pressed, we should select only one list item,
* not all <OL> or <UI>
*/
if (controlKeyPressed && e.keyCode == keyCodeForA) {
e.preventDefault();
/**
* Select <LI> content
*/
methods_.selectListItem();
}
},
/**
* Select all content of <LI> with caret
*/
selectListItem : function () {
var selection = window.getSelection(),
currentSelectedNode = selection.anchorNode.parentNode,
range = new Range();
/**
* Search for <LI> element
*/
while ( currentSelectedNode && currentSelectedNode.tagName != LIST_ITEM_TAG ) {
currentSelectedNode = currentSelectedNode.parentNode;
}
range.selectNodeContents(currentSelectedNode);
selection.removeAllRanges();
selection.addRange(range);
}
};
/**
* Method to render HTML block from JSON
*/
list_plugin.render = function (data) {
var type = data && (data.type == 'ordered' || data.type == 'OL') ? 'OL' : 'UL',
tag = ui.make(type),
newLi;
if (data && data.items) {
data.items.forEach(function (element, index, array) {
newLi = ui.block('li', elementClasses_.li);
newLi.innerHTML = element || '';
tag.appendChild(newLi);
});
} else {
newLi = ui.block('li', elementClasses_.li);
tag.appendChild(newLi);
}
return tag;
};
list_plugin.validate = function(data) {
var isEmpty = data.items.every(function(item){
return item.trim() === '';
});
if (isEmpty){
return;
}
if (data.type != 'UL' && data.type != 'OL'){
console.warn('CodeX Editor List-tool: wrong list type passed %o', data.type);
return;
}
return true;
};
/**
* Method to extract JSON data from HTML block
*/
list_plugin.save = function (blockContent){
var data = {
type : null,
items : []
},
litsItemContent = '',
isEmptyItem = false;
for (var index = 0; index < blockContent.childNodes.length; index++){
litsItemContent = blockContent.childNodes[index].innerHTML;
isEmptyItem = !blockContent.childNodes[index].textContent.trim();
if (!isEmptyItem) {
data.items.push(litsItemContent);
}
}
data.type = blockContent.dataset.type;
return data;
};
list_plugin.makeSettings = function () {
var holder = document.createElement('DIV');
/** Add holder classname */
holder.className = elementClasses_.settings;
var orderedButton = ui.button("ordered"),
unorderedButton = ui.button("unordered");
orderedButton.addEventListener('click', function (event) {
methods_.changeBlockStyle(event, 'OL');
codex.editor.toolbar.settings.close();
});
unorderedButton.addEventListener('click', function (event) {
methods_.changeBlockStyle(event, 'UL');
codex.editor.toolbar.settings.close();
});
holder.appendChild(orderedButton);
holder.appendChild(unorderedButton);
return holder;
};
list_plugin.destroy = function () {
list = null;
};
return list_plugin;
})({});

View File

@ -1,34 +0,0 @@
/**
* Empty paragraph placeholder
*/
.ce-paragraph {
padding: 0.7em 0 !important;
line-height: 1.7em;
}
.ce-paragraph:empty::before,
.ce-paragraph p:empty::before{
content : attr(data-placeholder);
color: #818BA1;
opacity: .7;
transition: opacity 200ms ease;
}
.ce-paragraph:focus::before{
opacity: .1;
}
.toolbar-opened .ce-paragraph::before {
display: none;
}
.ce-paragraph p {
margin: 1.2em 0;
}
.ce-paragraph p:first-of-type{
margin-top: 0;
}
.ce-paragraph p:last-of-type{
margin-bottom: 0;
}

View File

@ -1,132 +0,0 @@
/**
* Paragraph Plugin
* Creates DIV tag and adds content to this tag
*/
var paragraph = (function(paragraph_plugin) {
/**
* @private
*
* Make initial paragraph block
* @param {object} JSON with block data
* @return {Element} element to append
*/
var make_ = function (data) {
/** Create Empty DIV */
var tag = codex.editor.draw.node('DIV', ['ce-paragraph'], {});
if (data && data.text) {
tag.innerHTML = data.text;
}
tag.contentEditable = true;
return tag;
};
/**
* @private
*
* Handles input data for save
* @param data
*/
var prepareDataForSave_ = function(data) {
};
/**
* @public
*
* Plugins should have prepare method
* @param config
*/
paragraph_plugin.prepare = function(config) {
};
/*
* @public
*
* Method to render HTML block from JSON
*/
paragraph_plugin.render = function (data) {
return make_(data);
};
/**
* @public
*
* Check output data for validity.
* Should be defined by developer
*/
paragraph_plugin.validate = function(output) {
let text = output.text;
text = text.replace('&nbsp;', ' ');
text = text.replace(/\s/g, ' ');
text = text.trim();
/**
* Check for empty <p>:
* <p> </p>
*/
let div = document.createElement('div');
div.innerHTML = text;
text = div.textContent.trim();
if (!text) {
return false;
}
return output;
};
/**
* @public
*
* Method to extract JSON data from HTML block
*/
paragraph_plugin.save = function (blockContent){
var wrappedText = codex.editor.content.wrapTextWithParagraphs(blockContent.innerHTML),
sanitizerConfig = {
tags : {
p : {},
a: {
href: true,
target: '_blank',
rel: 'nofollow'
},
i: {},
b: {},
}
};
var data = {
"text": codex.editor.sanitizer.clean(wrappedText, sanitizerConfig),
"format": "html",
"introText": '<<same>>'
};
return data;
};
paragraph_plugin.destroy = function () {
paragraph = null;
};
return paragraph_plugin;
})({});

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -1,210 +0,0 @@
.ce_plugin_quote--select_button{
display: block;
color: #306ac7;
cursor: pointer;
line-height: 1.3em;
}
.ce_plugin_quote--select_button:not(:last-of-type){
margin-bottom: 1.5em;
}
.ce_plugin_quote--select_button:hover{
color: #a1b4ec;
}
/** Quote Styles */
.quoteStyle-withCaption--author:empty {
direction: rtl;
}
.quoteStyle-withCaption--author {
margin: 5px 0;
background: #fff;
border: 1px solid #ebeef3;
padding: 7px;
text-align: right;
font-size: 1em;
font-weight: bold;
line-height: 1.5em;
}
.quoteStyle-simple--text,
.quoteStyle-withCaption--blockquote,
.quoteStyle-withPhoto--quote {
/*font-size: 1.04em;
line-height: 1.9em;*/
}
.quoteStyle-simple--text {
padding: 1.2em 2.1em;
margin: 2.5em 2em;
background: #FBFBFB;
}
.quoteStyle-withCaption--blockquote {
margin: 0;
padding: 1.5em 2.0em !important;
border: 1px solid #ebeef3;
background: #fff;
}
.quoteStyle-withCaption--blockquote:focus,
.quoteStyle-withCaption--author:focus {
outline: none;
}
.quoteStyle-simple--text:empty::before,
.quoteStyle-withCaption--blockquote:empty::before,
.quoteStyle-withPhoto--quote:empty::before
{
content : 'Введите цитату';
color: #818BA1;
opacity: 0.7;
transition: opacity 200ms ease;
}
.quoteStyle-withCaption--author:empty::before
{
content : 'Введите имя автора';
font-weight: normal;
color: #a1a5b3;;
opacity: 1;
transition: opacity 200ms ease;
}
.quoteStyle-withPhoto--author:empty::before {
content : 'Введите имя автора';
color: #000;
opacity: .8;
transition: opacity 200ms ease;
}
.quoteStyle-simple--text:focus::before,
.quoteStyle-withCaption--blockquote:focus::before,
.quoteStyle-withPhoto--quote:focus::before
{
opacity: .1;
}
.quoteStyle-withCaption--author:focus::before,
.quoteStyle-withPhoto--author:focus::before
{
opacity: .1;
}
/** Quote with photo */
.ce_redactor .quoteStyle-withPhoto--wrapper {
margin: 0;
padding: 4.5em 3.6em !important;
}
.quoteStyle-withPhoto--photo {
width: 94px;
height: 94px;
overflow: hidden;
text-align: center;
line-height: 94px;
border-radius: 3px;
float: left;
margin-right: 10px;
border: 1px solid #ebeef3;
box-sizing: border-box;
background: #fff;
}
.quoteStyle-withPhoto--photo:hover {
cursor: pointer;
background: #F0F3F6;
}
.quoteStyle-withPhoto--photo:hover::after {
display: block;
}
.authorsPhoto {
height: 110%;
vertical-align: top;
}
.authorsPhoto-wrapper {
border: 0 !important;
transition: all 20ms ease-in;
will-change: opacity, filter;
}
.authorsPhotoWrapper_preview {
opacity: .5;
filter: blur(1.7px) grayscale(1);
}
.quoteStyle-withPhoto--photo .ce-icon-picture {
font-family: "codex_editor";
font-size: 1.5em;
color: #d5dbea;
}
.quoteStyle-withPhoto--photo:hover{
background: #fdfeff;
border-color: #c1c4cc;
}
.quoteStyle-withPhoto--photo:hover .ce-icon-picture {
color: #2b3142;
}
.quoteStyle-withPhoto--author {
outline: none;
overflow: hidden;
margin-bottom: 10px;
font-weight: bold;
color: #000;
}
.quoteStyle-withPhoto--job {
outline: none;
overflow: hidden;
}
.quoteStyle-withPhoto--job:empty::before {
content : 'Введите должность автора';
color: #A5ABBC;
opacity: 1;
transition: opacity 200ms ease;
}
.quoteStyle-withPhoto--job:focus::before {
opacity: .1;
}
.quoteStyle-withPhoto--quote {
margin-bottom: 10px;
line-height: 1.7em;
outline: none;
overflow: hidden;
}
/**
* New styles
*/
.ce-quote{
margin: 0 !important;
padding: 15px 0;
}
.quoteStyle-withPhoto--author,
.quoteStyle-withPhoto--job,
.quoteStyle-withPhoto--quote{
background: #fff;
border: 1px solid #ebeef3;
border-radius: 3px;
padding: 10px 15px;
}
.quoteStyle-withPhoto--author,
.quoteStyle-withPhoto--job {
line-height: 20px;
}
.quoteStyle-withPhoto--quote{
min-height: 200px;
}
/**
* Current select-type button
*/
.ce-quote-settings--selected{
font-weight: bold;
}

View File

@ -1,585 +0,0 @@
/**
*
* Quote plugin
*/
var quote = (function(quote_plugin) {
/**
* @private
*
* CSS styles
*/
var elementClasses_ = {
ce_quote : 'ce-quote',
quoteText : 'ce_quote--text',
quoteAuthor : 'ce_quote--author',
authorsJob : 'ce_quote--job',
authorsPhoto : 'authorsPhoto',
authorsPhotoWrapper : 'authorsPhoto-wrapper',
authorsPhotoWrapper_preview : 'authorsPhotoWrapper_preview',
simple : {
text : 'quoteStyle-simple--text'
},
withCaption : {
blockquote : 'quoteStyle-withCaption--blockquote',
author : 'quoteStyle-withCaption--author'
},
withPhoto : {
photo : 'quoteStyle-withPhoto--photo',
author : 'quoteStyle-withPhoto--author',
job : 'quoteStyle-withPhoto--job',
quote : 'quoteStyle-withPhoto--quote',
wrapper : 'quoteStyle-withPhoto--wrapper',
authorHolder : 'quoteStyle-withPhoto--authorWrapper'
},
settings : {
holder : 'cdx-plugin-settings--horisontal',
caption : 'ce_plugin_quote--caption',
buttons : 'cdx-plugin-settings__item',
selectedType : 'ce-quote-settings--selected'
}
};
/**
* @private
*
*
*/
var methods_ = {
changeStyleClicked : function() {
var changeStyleButton = this,
quote = codex.editor.content.currentNode.querySelector('.' + elementClasses_.ce_quote),
newStyle = changeStyleButton.dataset.style,
styleSelectors = this.parentNode.childNodes;
quote.dataset.quoteStyle = newStyle;
/**
* Mark selected style button
*/
for (var i = styleSelectors.length - 1; i >= 0; i--) {
styleSelectors[i].classList.remove(elementClasses_.settings.selectedType);
}
this.classList.add(elementClasses_.settings.selectedType);
},
/**
* @deprecated
*/
selectTypeQuoteStyle : function(type) {
var quoteStyleFunction;
/**
* Choose Quote style to replace
*/
switch (type) {
case 'simple':
quoteStyleFunction = methods_.makeSimpleQuote;
break;
case 'withCaption':
quoteStyleFunction = methods_.makeQuoteWithCaption;
break;
case 'withPhoto':
quoteStyleFunction = methods_.makeQuoteWithPhoto;
break;
}
return quoteStyleFunction;
},
/**
* @deprecated
*/
addSelectTypeClickListener : function(el, quoteStyle) {
el.addEventListener('click', function () {
/**
* Parsing currentNode to JSON.
*/
var parsedOldQuote = methods_.parseBlockQuote(),
newStyledQuote = quoteStyle(parsedOldQuote);
var wrapper = codex.editor.content.composeNewBlock(newStyledQuote, 'quote');
wrapper.appendChild(newStyledQuote);
codex.editor.content.switchBlock(codex.editor.content.currentNode, newStyledQuote, 'quote');
/** Close settings after replacing */
codex.editor.toolbar.settings.close();
}, false);
},
/**
* @deprecated
*/
makeSimpleQuote : function(data) {
var wrapper = ui_.makeBlock('BLOCKQUOTE', [elementClasses_.simple.text, elementClasses_.quoteText]);
wrapper.innerHTML = data.text || '';
wrapper.dataset.quoteStyle = 'simple';
wrapper.classList.add(elementClasses_.ce_quote);
wrapper.contentEditable = 'true';
return wrapper;
},
/**
* @deprecated
*/
makeQuoteWithCaption : function(data) {
var wrapper = ui_.blockquote(),
text = ui_.makeBlock('DIV', [elementClasses_.withCaption.blockquote, elementClasses_.quoteText]),
author = ui_.makeBlock('DIV', [elementClasses_.withCaption.author, elementClasses_.quoteAuthor]);
/* make text block ontentEditable */
text.contentEditable = 'true';
text.innerHTML = data.text || '';
/* make Author contentEditable */
author.contentEditable = 'true';
author.innerHTML = data.cite || '';
/* Appending created components */
wrapper.dataset.quoteStyle = 'withCaption';
wrapper.classList.add(elementClasses_.ce_quote);
wrapper.appendChild(text);
wrapper.appendChild(author);
return wrapper;
},
makeQuoteWithPhoto : function(data) {
var wrapper = ui_.blockquote(),
photo = ui_.makeBlock('DIV', [elementClasses_.withPhoto.photo]),
author = ui_.makeBlock('DIV', [elementClasses_.withPhoto.author, elementClasses_.quoteAuthor]),
job = ui_.makeBlock('DIV', [elementClasses_.withPhoto.job, elementClasses_.authorsJob]),
quote = ui_.makeBlock('DIV', [elementClasses_.withPhoto.quote, elementClasses_.quoteText]);
/* Default Image src */
if (!data.image) {
var icon = ui_.makeBlock('SPAN', ['ce-icon-picture']);
photo.appendChild(icon);
} else {
var authorsPhoto = ui_.img(elementClasses_.authorsPhoto);
authorsPhoto.src = data.image;
authorsPhoto.dataset.bigUrl = data.image;
photo.classList.add(elementClasses_.authorsPhotoWrapper);
photo.appendChild(authorsPhoto);
}
photo.addEventListener('click', fileUploadClicked_, false);
/* make author block contentEditable */
author.contentEditable = 'true';
author.innerHTML = data.cite || '';
/* Author's position and job */
job.contentEditable = 'true';
job.innerHTML = data.caption || '';
var authorsWrapper = ui_.makeBlock('DIV', [elementClasses_.withPhoto.authorHolder]);
authorsWrapper.appendChild(author);
authorsWrapper.appendChild(job);
/* make quote text contentEditable */
quote.contentEditable = 'true';
quote.innerHTML = data.text || '';
wrapper.classList.add(elementClasses_.ce_quote);
wrapper.classList.add(elementClasses_.withPhoto.wrapper);
wrapper.dataset.quoteStyle = 'withPhoto';
wrapper.appendChild(quote);
wrapper.appendChild(photo);
wrapper.appendChild(authorsWrapper);
return wrapper;
},
parseBlockQuote : function(block) {
var currentNode = block || codex.editor.content.currentNode,
photo = currentNode.getElementsByTagName('img')[0],
author = currentNode.querySelector('.' + elementClasses_.quoteAuthor),
job = currentNode.querySelector('.' + elementClasses_.authorsJob),
quote ;
/** Simple quote text placed in Blockquote tag*/
if ( currentNode.dataset.quoteStyle == 'simple' ){
quote = currentNode.innerHTML || '';
} else {
quote = currentNode.querySelector('.' + elementClasses_.quoteText).innerHTML;
}
if (job){
job = job.innerHTML || '';
}
if (author){
author = author.innerHTML || '';
}
if (photo){
photo = photo.dataset.bigUrl;
}
var data = {
style : currentNode.dataset.quoteStyle,
text : quote,
author : author,
job : job,
photo : photo
};
return data;
}
};
/**
* @private
*
* Author image Uploader
*/
var fileUploadClicked_ = function() {
var beforeSend = photoUploadingCallbacks_.beforeSend,
success = photoUploadingCallbacks_.success,
error = photoUploadingCallbacks_.error;
codex.editor.transport.selectAndUpload({
beforeSend: beforeSend,
success: success,
error: error
});
};
/**
* @private
*
*/
var ui_ = {
wrapper : function($classList) {
var el = document.createElement('DIV');
el.classList.add($classList);
return el;
},
blockquote : function() {
var el = document.createElement('BLOCKQUOTE');
return el;
},
img : function(attribute) {
var imageTag = document.createElement('IMG');
imageTag.classList.add(attribute);
return imageTag;
},
makeBlock : function(tag, classList) {
var el = document.createElement(tag);
if ( classList ) {
for( var i = 0; i < classList.length; i++)
el.className += ' ' + classList[i];
}
return el;
}
};
/**
* @private
*
* Callbacks
*/
var photoUploadingCallbacks_ = {
preview_ : function(e) {
var uploadImageWrapper = codex.editor.content.currentNode.querySelector('.' + elementClasses_.withPhoto.photo),
authorsPhoto = ui_.img(elementClasses_.authorsPhoto);
/** Appending uploaded image */
uploadImageWrapper.classList.add(elementClasses_.authorsPhotoWrapper, elementClasses_.authorsPhotoWrapper_preview);
authorsPhoto.src = e.target.result;
/** Remove icon from image wrapper */
uploadImageWrapper.innerHTML = '';
uploadImageWrapper.appendChild(authorsPhoto);
},
beforeSend : function() {
var input = codex.editor.transport.input,
files = input.files,
file = files[0],
fileReader = new FileReader();
fileReader.readAsDataURL(file);
fileReader.onload = photoUploadingCallbacks_.preview_;
},
/**
* Success callbacks for uploaded photo.
* Replace upload icon with uploaded photo
*/
success : function(result) {
var parsed = JSON.parse(result),
filename = parsed.filename,
uploadImageWrapper = codex.editor.content.currentNode.querySelector('.' + elementClasses_.withPhoto.photo);
var img = uploadImageWrapper.querySelector('IMG');
img.src = parsed.data.file.bigUrl;
img.dataset.bigUrl = parsed.data.file.bigUrl;
uploadImageWrapper.classList.remove(elementClasses_.authorsPhotoWrapper_preview);
},
/** Error callback. Sends notification to user that something happend or plugin doesn't supports method */
error : function(result) {
console.log('Can\'t upload an image');
}
};
/**
* @private
*
* Make Quote from JSON datasets
*/
var make_ = function(data) {
var tag;
if (data && data.size) {
data.style = quote_plugin.config.defaultStyle;
/**
* Supported types
*/
switch (data.style) {
case 'simple':
tag = methods_.makeSimpleQuote(data);
break;
case 'withCaption':
tag = methods_.makeQuoteWithCaption(data);
break;
case 'withPhoto':
tag = methods_.makeQuoteWithPhoto(data);
break;
}
tag.dataset.quoteStyle = data.size;
} else {
var settings = {
"text" : null,
"format" : "html",
"cite" : null,
"caption": null,
"size" : null,
"image" : null
};
tag = methods_.makeQuoteWithPhoto(settings);
}
return tag;
};
var prepareDataForSave_ = function(data) {
if (data.size == 'withPhoto') {
data.size = 'small';
}
/** Make paragraphs */
data.text = codex.editor.content.wrapTextWithParagraphs(data.text);
return data;
};
/**
* @public
*
* Renderer
*
* @param data
*/
quote_plugin.render = function(data) {
return make_(data);
};
quote_plugin.validate = function(output) {
if (typeof output.text != "string") {
return;
}
return output;
};
quote_plugin.save = function(blockContent) {
/**
* Extracts JSON quote data from HTML block
* @param {Text} text, {Text} author, {Object} photo
*/
var parsedblock = methods_.parseBlockQuote(blockContent);
var outputData = {
"text" : parsedblock.text,
"format" : "html",
"cite" : parsedblock.author || '',
"caption": parsedblock.job || '',
"size" : parsedblock.style,
"image" : parsedblock.photo
};
return prepareDataForSave_(outputData);
};
/**
* @public
*
* Draws settings
*/
quote_plugin.makeSettings = function(data) {
var holder = document.createElement('DIV'),
types = {
big : 'По центру',
small : 'Врезка'
},
selectTypeButton;
/** Add holder classname */
holder.className = elementClasses_.settings.holder;
/** Now add type selectors */
for (var type in types){
selectTypeButton = document.createElement('SPAN');
selectTypeButton.textContent = types[type];
selectTypeButton.className = elementClasses_.settings.buttons;
selectTypeButton.dataset.style = type;
if ( type == quote_plugin.config.defaultStyle ){
selectTypeButton.classList.add(quoteTools.styles.settings.selectedType);
}
// var quoteStyle = quoteTools.selectTypeQuoteStyle(type);
selectTypeButton.addEventListener('click', methods_.changeStyleClicked, false);
// quoteTools.addSelectTypeClickListener(selectTypeButton, quoteStyle);
holder.appendChild(selectTypeButton);
}
return holder;
};
/**
* @public
* Default path to redactors images
* @type {null}
*/
quote_plugin.path = null;
/**
* @public
*
* @type {null}
*/
quote_plugin.config = null;
/**
* @public
*
* @param config
*/
quote_plugin.prepare = function(config) {
quote_plugin.config = config;
return Promise.resolve();
};
quote_plugin.destroy = function () {
quote = null;
};
return quote_plugin;
})({});

View File

@ -1,7 +0,0 @@
<svg class="raw-plugin-icon-svg" width="19px" height="6px" viewBox="0 0 19 6" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>RAW</title>
<defs></defs>
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<path d="M3.17724609,0.00146484375 C4.53955078,0.00146484375 5.31298828,0.766113281 5.31298828,1.93505859 C5.31298828,2.84033203 4.75048828,3.42480469 4.22753906,3.62255859 L5.43164062,6 L4.01660156,6 L2.97509766,3.81591797 L1.98632812,3.81591797 L1.98632812,6 L0.733886719,6 L0.733886719,0.00146484375 L3.17724609,0.00146484375 Z M1.98632812,2.84912109 L2.97509766,2.84912109 C3.59912109,2.84912109 4.00341797,2.55029297 4.00341797,1.95263672 C4.00341797,1.34179688 3.5859375,1.01660156 2.99267578,1.01660156 L1.98632812,1.01660156 L1.98632812,2.84912109 Z M6.85976562,6 L5.58095703,6 L7.56728515,0.00146484375 L9.0790039,0.00146484375 L11.056543,6 L9.70302734,6 L9.26357421,4.54541016 L7.30800781,4.54541016 L6.85976562,6 Z M8.31874999,1.22753906 L8.26162109,1.22753906 L7.55849609,3.57421875 L9.01748046,3.57421875 L8.31874999,1.22753906 Z M13.7107422,6 L12.5374023,6 L10.9641601,0.00146484375 L12.3308594,0.00146484375 L13.187793,4.20263672 L13.2493164,4.20263672 L14.2820312,0.00146484375 L15.3542969,0.00146484375 L16.3914062,4.20263672 L16.4529297,4.20263672 L17.3054687,0.00146484375 L18.6677734,0.00146484375 L17.0989258,6 L15.9255859,6 L14.8445312,2.00537109 L14.7961914,2.00537109 L13.7107422,6 Z" id="RAW" fill="#000000"></path>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -1,7 +0,0 @@
<svg class="raw-plugin-icon-svg" width="19px" height="6px" viewBox="0 0 19 6" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>RAW</title>
<defs></defs>
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<path d="M3.17724609,0.00146484375 C4.53955078,0.00146484375 5.31298828,0.766113281 5.31298828,1.93505859 C5.31298828,2.84033203 4.75048828,3.42480469 4.22753906,3.62255859 L5.43164062,6 L4.01660156,6 L2.97509766,3.81591797 L1.98632812,3.81591797 L1.98632812,6 L0.733886719,6 L0.733886719,0.00146484375 L3.17724609,0.00146484375 Z M1.98632812,2.84912109 L2.97509766,2.84912109 C3.59912109,2.84912109 4.00341797,2.55029297 4.00341797,1.95263672 C4.00341797,1.34179688 3.5859375,1.01660156 2.99267578,1.01660156 L1.98632812,1.01660156 L1.98632812,2.84912109 Z M6.85976562,6 L5.58095703,6 L7.56728515,0.00146484375 L9.0790039,0.00146484375 L11.056543,6 L9.70302734,6 L9.26357421,4.54541016 L7.30800781,4.54541016 L6.85976562,6 Z M8.31874999,1.22753906 L8.26162109,1.22753906 L7.55849609,3.57421875 L9.01748046,3.57421875 L8.31874999,1.22753906 Z M13.7107422,6 L12.5374023,6 L10.9641601,0.00146484375 L12.3308594,0.00146484375 L13.187793,4.20263672 L13.2493164,4.20263672 L14.2820312,0.00146484375 L15.3542969,0.00146484375 L16.3914062,4.20263672 L16.4529297,4.20263672 L17.3054687,0.00146484375 L18.6677734,0.00146484375 L17.0989258,6 L15.9255859,6 L14.8445312,2.00537109 L14.7961914,2.00537109 L13.7107422,6 Z" id="RAW" fill="#ffffff"></path>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -1,36 +0,0 @@
.raw-plugin__input {
display: block;
width: 100%;
min-height: 200px;
border: 0;
background: #fdfdfd;
box-shadow: inset 0 2px 8px rgba(23, 32, 74, 0.06);
border-radius: 3px;
margin: 1em auto;
padding: 1em;
box-sizing: border-box;
white-space: pre;
outline: none;
resize: vertical;
font-family: 'monospace', 'monaco', 'consolas', 'courier';
line-height: 1.7em;
font-size: 12px;
color: #152b48;
overflow: auto;
letter-spacing: 0.015em;
}
.raw-plugin-icon {
display: inline-block;
width: 16px;
height: 32px;
background: url(raw-icon-black.svg) no-repeat center center;
background-size: contain;
}
li:hover .raw-plugin-icon,
.selected .raw-plugin-icon{
background: url(raw-icon-white.svg) no-repeat center center;
background-size: contain;
}

View File

@ -1,51 +0,0 @@
/**
* Plugin for CodeX.Editor
* Implements RAW-data block
*/
var rawPlugin = function (plugin) {
var editor = codex.editor;
plugin.render = function (data) {
var input = editor.draw.node('TEXTAREA', 'raw-plugin__input', {});
input.placeholder = 'Вставьте HTML код';
if (data && data.raw) {
input.value = data.raw;
}
return input;
};
plugin.save = function (block) {
return {
raw: block.value
};
};
plugin.validate = function (data) {
if (data.raw.trim() === '') {
return;
}
return true;
};
plugin.destroy = function () {
rawPlugin = null;
};
return plugin;
}({});

View File

@ -1,20 +0,0 @@
.ce-redactor .twitter-tweet {
margin: 15px auto !important;
width: 100% !important;
}
.ce-twitter__caption {
max-width: 520px;
margin-bottom: 15px;
background: #fff;
border: 1px solid #ebeef3;
border-radius: 3px;
padding: .6em .8em;
box-sizing: border-box;
}
.ce-twitter__caption:empty::before {
content : 'Подпись';
opacity: .5;
}

View File

@ -1,281 +0,0 @@
/**
* Twitter plugin
* @version 1.0.0
*/
var twitter = (function(twitter_plugin) {
/**
* User's configuration object
*/
var config_ = {};
/**
* CSS classes
*/
var css_ = {
pluginWrapper : 'cdx-tweet'
};
var methods = {
/**
* Twitter render method appends content after block
* @param tweetId
*/
twitter : function(data, twitterBlock) {
var tweet = methods.drawTwitterHolder(),
twittersCaption = methods.drawTwittersCaptionBlock();
if (data.caption) {
twittersCaption.innerHTML = data.caption;
}
/**
* add created tweet to holder
*/
tweet.appendChild(twitterBlock);
// setTimeout(function() {
window.twttr.widgets.createTweet(data.id_str, twitterBlock).then(tweetInsertedCallback_);
// }, 1000);
tweet.classList.add('ce-redactor__loader');
if (codex.editor.content.currentNode) {
tweet.dataset.statusUrl = data.status_url;
codex.editor.content.switchBlock(codex.editor.content.currentNode, tweet, 'tweet');
}
/**
* in case if we need extra data
*/
if ( !data.user ) {
codex.editor.core.ajax({
url : config_.fetchUrl + '?tweetId=' + data.id_str,
type: "GET",
success: function(result) {
methods.saveTwitterData(result, tweet);
}
});
} else {
tweet.dataset.profileImageUrl = data.user.profile_image_url;
tweet.dataset.profileImageUrlHttps = data.user.profile_image_url_https;
tweet.dataset.screenName = data.user.screen_name;
tweet.dataset.name = data.user.name;
tweet.dataset.id = +data.id;
tweet.dataset.idStr = data.id_str;
tweet.dataset.text = data.text;
tweet.dataset.createdAt = data.created_at;
tweet.dataset.statusUrl = data.status_url;
tweet.dataset.media = data.media;
tweet.classList.remove('ce-redactor__loader');
}
/**
* add caption to tweet
*/
setTimeout(function() {
tweet.appendChild(twittersCaption);
}, 1000);
return tweet;
},
drawTwitterHolder : function() {
var block = document.createElement('DIV');
block.classList.add(css_.pluginWrapper);
return block;
},
drawTwitterBlock : function() {
var block = codex.editor.draw.node('DIV', '', { height: "20px" });
return block;
},
drawTwittersCaptionBlock : function() {
var block = codex.editor.draw.node('DIV', ['ce-twitter__caption'], { contentEditable : true });
return block;
},
saveTwitterData : function(result, tweet) {
var data = JSON.parse(result),
twitterContent = tweet;
setTimeout(function() {
/**
* Save twitter data via data-attributes
*/
twitterContent.dataset.profileImageUrl = data.user.profile_image_url;
twitterContent.dataset.profileImageUrlHttps = data.user.profile_image_url_https;
twitterContent.dataset.screenName = data.user.screen_name;
twitterContent.dataset.name = data.user.name;
twitterContent.dataset.id = +data.id;
twitterContent.dataset.idStr = data.id_str;
twitterContent.dataset.text = data.text;
twitterContent.dataset.createdAt = data.created_at;
twitterContent.dataset.media = data.entities.urls.length > 0 ? "false" : "true";
}, 50);
}
};
/**
* @private
* Fires after tweet widget rendered
*/
function tweetInsertedCallback_(widget) {
var pluginWrapper = findParent_( widget , css_.pluginWrapper );
pluginWrapper.classList.remove('ce-redactor__loader');
}
/**
* @private
* Find closiest parent Element with CSS class
*/
function findParent_ (el, cls) {
while ((el = el.parentElement) && !el.classList.contains(cls));
return el;
}
/**
* Prepare twitter scripts
* @param {object} config
*/
twitter_plugin.prepare = function(config) {
/**
* Save configs
*/
config_ = config;
return new Promise(function(resolve, reject){
codex.editor.core.importScript("https://platform.twitter.com/widgets.js", 'twitter-api').then(function(){
resolve();
}).catch(function(){
reject(Error('Twitter API was not loaded'));
});
});
};
/**
* @private
*
* @param data
* @returns {*}
*/
make_ = function(data) {
if (!data.id || !data.status_url)
return;
if (!data.id_str) {
data.id_str = data.status_url.match(/[^\/]+$/)[0];
}
var twitterBlock = methods.drawTwitterBlock();
var tweet = methods.twitter(data, twitterBlock);
return tweet;
};
twitter_plugin.validate = function(data) {
return true;
};
twitter_plugin.save = function(blockContent) {
var data,
caption = blockContent.querySelector('.ce-twitter__caption');
data = {
media:blockContent.dataset.media,
conversation:false,
user:{
profile_image_url: blockContent.dataset.profileImageUrl,
profile_image_url_https: blockContent.dataset.profileImageUrlHttps,
screen_name: blockContent.dataset.screenName,
name: blockContent.dataset.name
},
id: blockContent.dataset.id || blockContent.dataset.tweetId,
id_str : blockContent.dataset.idStr,
text: blockContent.dataset.text,
created_at: blockContent.dataset.createdAt,
status_url: blockContent.dataset.statusUrl,
caption: caption.innerHTML || ''
};
return data;
};
twitter_plugin.render = function(data) {
return make_(data);
};
twitter_plugin.urlPastedCallback = function(url) {
var tweetId,
arr,
data;
arr = url.split('/');
tweetId = arr.pop();
/** Example */
data = {
"media" : true,
"conversation" : false,
"user" : null,
"id" : +tweetId,
"text" : null,
"created_at" : null,
"status_url" : url,
"caption" : null
};
make_(data);
};
twitter_plugin.pastePatterns = [
{
type: 'twitter',
regex: /http?.+twitter.com?.+\//,
callback: twitter_plugin.urlPastedCallback
}
];
twitter_plugin.destroy = function () {
twitter = null;
delete window.twttr;
};
return twitter_plugin;
})({});

View File

@ -1,95 +0,0 @@
<?php
function file_get_contents_curl($url)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 8);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
curl_setopt($ch,CURLOPT_USERAGENT,'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36');
$data = curl_exec($ch);
curl_close($ch);
return $data;
}
function get_url()
{
if (!isset($_GET['url']))
{
return false;
}
$url = $_GET['url'];
if (filter_var($url, FILTER_VALIDATE_URL) === FALSE) {
return false;
}
return $url;
}
function get_meta_from_html($html)
{
$doc = new DOMDocument();
@$doc->loadHTML($html);
$nodes = $doc->getElementsByTagName('title');
$title = $nodes->item(0)->nodeValue;
$description = "";
$keywords = "";
$image = "";
$metas = $doc->getElementsByTagName('meta');
for ($i = 0; $i < $metas->length; $i++)
{
$meta = $metas->item($i);
if($meta->getAttribute('name') == 'description')
$description = $meta->getAttribute('content');
if($meta->getAttribute('name') == 'keywords')
$keywords = $meta->getAttribute('content');
if($meta->getAttribute('property')=='og:image'){
$image = $meta->getAttribute('content');
}
}
return [
'image' => $image,
'title' => $title,
'description' => $description
];
}
$url = get_url();
$url_params = parse_url($url);
if (!$url)
{
exit(0);
}
$html = file_get_contents_curl($url);
$result = get_meta_from_html($html);
$result = array_merge(
get_meta_from_html($html),
array(
'linkUrl' => $url,
'linkText' => $url_params["host"] . isset($url_params["path"])?$url_params["path"]:"",
)
);
echo json_encode($result);
?>

View File

@ -0,0 +1,3 @@
<svg width="14" height="14" viewBox="0 0 14 14" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<path transform="matrix(1 0 0 -1 0 14)" d="M8.024 4.1v8.6a1.125 1.125 0 0 1-2.25 0V4.1L2.18 7.695A1.125 1.125 0 1 1 .59 6.104L6.103.588c.44-.439 1.151-.439 1.59 0l5.516 5.516a1.125 1.125 0 0 1-1.59 1.59L8.023 4.1z"/>
</svg>

After

Width:  |  Height:  |  Size: 353 B

Some files were not shown because too many files have changed in this diff Show More