mirror of
https://github.com/codex-team/editor.js
synced 2024-05-10 10:26:46 +02:00
feat(toolbar): toolbar refactored and ui improved (#1815)
* chore(block-tune-toggler): toggler moved to the left (draft) * toolbox ui updated * fixd caret jumpling, improved some styles * toolbar moving by block-hover - UI module triggers 'block-hovered' event - Toolbar uses 'block-hovered' for appearing - `currentBlock` setter added to the BlockManager - (reactangle-selection): the throttling added to the mousemove and scroll handlers - `getBlockIndex` method added to the Api - (api-blocks): toolbar moving logic removed from `blocks.move()` and `blocks.swap()` methods. Instead, MoveUp and MoveDown tunes uses Toolbar API * the dark-theme to the example-dev.html * positioning improved * fix(rectangle-selection): first click after RS does not clears selection state * toolbox position fixed * the toolbox module became a standalone class - Toolbox became a standalone class from the editor module. It can be accessed only via the owner (the Toolbar module) - (api.blocks) the insert() method now has the `replace` param. Also, it returns inserted Block API now. * new(api.listeners): `on()` now returns the listener id. The new `offById()` method added * fix bug with Tab pressing on hovered but not focused block * mobile version improved * upd example dev * small updaets * add nested-list * linting * (api.toolbar): `toggleBlockSettings` now fires toggling event with the same state * EventDispatcher used instead of callbacks for the Toolbox * UIApi added * fix ci * git submodules removed from the ci flow * add paragraph submodule to the ci flow * Update CHANGELOG.md * Update package.json * use ubuntu-latest for chrome ci
This commit is contained in:
parent
acdd1f5b4e
commit
ff91466b14
8
.github/workflows/cypress.yml
vendored
8
.github/workflows/cypress.yml
vendored
|
@ -8,17 +8,17 @@ jobs:
|
|||
options: --user 1001
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- run: yarn pull_tools && yarn tools:update
|
||||
- run: yarn ci:pull_paragraph
|
||||
- uses: cypress-io/github-action@v2
|
||||
with:
|
||||
config: video=false
|
||||
browser: firefox
|
||||
build: yarn build
|
||||
chrome:
|
||||
runs-on: ubuntu-16.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- run: yarn pull_tools && yarn tools:update
|
||||
- run: yarn ci:pull_paragraph
|
||||
- uses: cypress-io/github-action@v2
|
||||
with:
|
||||
config: video=false
|
||||
|
@ -28,7 +28,7 @@ jobs:
|
|||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- run: yarn pull_tools && yarn tools:update
|
||||
- run: yarn ci:pull_paragraph
|
||||
- uses: cypress-io/github-action@v2
|
||||
with:
|
||||
config: video=false
|
||||
|
|
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -49,3 +49,6 @@
|
|||
[submodule "example/tools/underline"]
|
||||
path = example/tools/underline
|
||||
url = https://github.com/editor-js/underline
|
||||
[submodule "example/tools/nested-list"]
|
||||
path = example/tools/nested-list
|
||||
url = https://github.com/editor-js/nested-list
|
||||
|
|
|
@ -251,4 +251,4 @@ CodeX is a team of digital specialists around the world interested in building h
|
|||
|
||||
| 🌐 | Join 👋 | Twitter | Instagram |
|
||||
| -- | -- | -- | -- |
|
||||
| [codex.so](https://codex.so) | [codex.so/join](https://codex.so/join) |[@codex_team](http://twitter.com/codex_team) | [@codex_team](http://instagram.com/codex_team) |
|
||||
| [codex.so](https://codex.so) | [codex.so/join](https://codex.so/join) |[@codex_team](http://twitter.com/codex_team) | [@codex_team](http://instagram.com/codex_team/) |
|
||||
|
|
|
@ -2,8 +2,24 @@
|
|||
|
||||
### 2.23.0
|
||||
|
||||
- `Improvement` — The `onChange` callback now accepts two arguments: EditorJS API and the CustomEvent with `type` and `detail` allowing to determine what happened with a Block
|
||||
- `New` *Block API* — The new `dispatchChange()` method allows to manually trigger the 'onChange' callback. Useful when Tool made a state mutation that is invisible for editor core.
|
||||
- `Improvement` — *EditorConfig* — The `onChange` callback now accepts two arguments: EditorJS API and the CustomEvent with `type` and `detail` allowing to determine what happened with a Block
|
||||
- `New` — *Block API* — The new `dispatchChange()` method allows to manually trigger the 'onChange' callback. Useful when Tool made a state mutation that is invisible for editor core.
|
||||
- `Improvement` — *UI* — Block Tunes toggler moved to the left
|
||||
- `Improvement` — *UI* — Block Actions (BT toggler + Plus Button) will appear on block hovering instead of click
|
||||
- `Improvement` — *UI* — Block Tunes toggler icon and Plus button icon updated
|
||||
- `Improvement` — *Dev Example Page* — The menu with helpful buttons added to the bottom of the screen
|
||||
- `Improvement` — *Dev Example Page* — The 'dark' theme added. Now we can code at night more comfortably.
|
||||
- `Improvement` — *Rectangle Selection* — paint optimized
|
||||
- `Fix` — *Rectangle Selection* — the first click after RS was not clear selection state. Now does.
|
||||
- `Improvement` — *Blocks API* — toolbar moving logic removed from `blocks.move()` and `blocks.swap()` methods. Instead, you should use Toolbar API (it was used by MoveUp and MoveDown tunes, they were updated).
|
||||
- `New` — *Blocks API* — The `getBlockIndex()` method added
|
||||
- `New` — *Blocks API* — the `insert()` method now has the `replace: boolean` parameter
|
||||
- `New` — *Blocks API* — the `insert()` method now returns the inserted `Block API`
|
||||
- `New` — *Listeners API* — the `on()` method now returns the listener id.
|
||||
- `New` — *Listeners API* — the new `offById()` method added
|
||||
- `New` — `API` — The new `UiApi` section was added. It allows accessing some editor UI nodes and methods.
|
||||
- `Refactoring` — Toolbox became a standalone class instead of a Module. It can be accessed only through the Toolbar module.
|
||||
- `Refactoring` — CI flow optimized.
|
||||
|
||||
### 2.22.3
|
||||
|
||||
|
|
|
@ -1,11 +1,27 @@
|
|||
/**
|
||||
* Styles for the example page
|
||||
*/
|
||||
|
||||
:root {
|
||||
--color-bg-main: #fff;
|
||||
--color-border-light: #E8E8EB;
|
||||
--color-text-main: #000;
|
||||
}
|
||||
|
||||
.dark-mode {
|
||||
--color-border-light: rgba(255, 255, 255,.08);
|
||||
--color-bg-main: #1c1e24;
|
||||
--color-text-main: #737886;
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
background: var(--color-bg-main);
|
||||
color: var(--color-text-main);
|
||||
}
|
||||
|
||||
.ce-example {
|
||||
|
@ -13,7 +29,7 @@ body {
|
|||
}
|
||||
|
||||
.ce-example__header {
|
||||
border-bottom: 1px solid #E8E8EB;
|
||||
border-bottom: 1px solid var(--color-border-light);
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
display: flex;
|
||||
|
@ -62,23 +78,17 @@ body {
|
|||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.ce-example__content--small {
|
||||
.thin-mode .ce-example__content {
|
||||
max-width: 500px;
|
||||
border-left: 1px solid #eee;
|
||||
border-right: 1px solid #eee;
|
||||
padding: 0 15px;
|
||||
}
|
||||
|
||||
.ce-example__content--with-bg {
|
||||
background: #f4f4f4;
|
||||
max-width: none;
|
||||
margin-top: -30px;
|
||||
}
|
||||
|
||||
.ce-example__output {
|
||||
background: #1B202B;
|
||||
overflow-x: auto;
|
||||
padding: 0 30px;
|
||||
padding: 0 30px 80px;
|
||||
}
|
||||
|
||||
.ce-example__output-content {
|
||||
|
@ -127,29 +137,94 @@ body {
|
|||
}
|
||||
|
||||
.ce-example__statusbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: fixed;
|
||||
bottom: 10px;
|
||||
right: 10px;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.18);
|
||||
font-size: 12px;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
background: var(--color-bg-main);
|
||||
border-radius: 8px 8px 0 0;
|
||||
border-top: 1px solid var(--color-border-light);
|
||||
box-shadow: 0 2px 6px var(--color-border-light);
|
||||
font-size: 13px;
|
||||
padding: 8px 15px;
|
||||
z-index: 1;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.ce-example__statusbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.ce-example__statusbar-item:not(:last-of-type)::after {
|
||||
content: '|';
|
||||
color: #ddd;
|
||||
margin: 0 15px 0 12px;
|
||||
}
|
||||
|
||||
.ce-example__statusbar-item--right {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.ce-example__statusbar-button {
|
||||
display: inline-flex;
|
||||
margin-left: 10px;
|
||||
background: #4A9DF8;
|
||||
padding: 6px 12px;
|
||||
box-shadow: 0 7px 8px -4px rgba(137, 207, 255, 0.77);
|
||||
display: inline-block;
|
||||
padding: 3px 12px;
|
||||
transition: all 150ms ease;
|
||||
cursor: pointer;
|
||||
border-radius: 31px;
|
||||
color: #fff;
|
||||
font-family: 'PT Mono', Menlo, Monaco, Consolas, Courier New, monospace;
|
||||
background: #eff1f4;
|
||||
text-align: center;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.ce-example__statusbar-button:hover {
|
||||
background: #e0e4eb;
|
||||
}
|
||||
|
||||
.ce-example__statusbar-button-primary {
|
||||
background: #4A9DF8;
|
||||
color: #fff;
|
||||
box-shadow: 0 7px 8px -4px rgba(137, 207, 255, 0.77);
|
||||
font-family: 'PT Mono', Menlo, Monaco, Consolas, Courier New, monospace;
|
||||
}
|
||||
|
||||
.ce-example__statusbar {
|
||||
--toggler-size: 20px;
|
||||
}
|
||||
|
||||
.ce-example__statusbar-toggler {
|
||||
position: relative;
|
||||
background: #7b8799;
|
||||
border-radius: 20px;
|
||||
padding: 2px;
|
||||
width: calc(var(--toggler-size) * 2.2);
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.ce-example__statusbar-toggler::before {
|
||||
display: block;
|
||||
content: '';
|
||||
width: var(--toggler-size);
|
||||
height: var(--toggler-size);
|
||||
background: #fff;
|
||||
border-radius: 50%;
|
||||
transition: transform 100ms ease-in;
|
||||
}
|
||||
|
||||
.ce-example__statusbar-toggler::after {
|
||||
--moon-size: calc(var(--toggler-size) * 0.5);
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
height: var(--moon-size);
|
||||
width: var(--moon-size);
|
||||
box-shadow: calc(var(--moon-size) * 0.25 * -1) calc(var(--moon-size) * 0.18) 0 calc(var(--moon-size) * 0.05) white;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
@media all and (max-width: 730px){
|
||||
|
@ -178,22 +253,10 @@ body {
|
|||
color: rgb(247, 60, 173);
|
||||
}
|
||||
|
||||
.ce-example .ce-block:first-of-type h2.ce-header{
|
||||
.ce-example .ce-block:first-of-type h1.ce-header{
|
||||
font-size: 50px;
|
||||
}
|
||||
|
||||
.ce-example h2.ce-header{
|
||||
font-size: 30px;
|
||||
}
|
||||
|
||||
.ce-example h3.ce-header {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.ce-example h4.ce-header {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.ce-example-multiple {
|
||||
display: grid;
|
||||
grid-template-columns: calc(50% - 15px) calc(50% - 15px);
|
||||
|
@ -206,3 +269,101 @@ body {
|
|||
border-radius: 7px;
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
.show-block-boundaries .ce-block {
|
||||
box-shadow: inset 0 0 0 1px #eff2f5;
|
||||
}
|
||||
|
||||
.show-block-boundaries .ce-block__content {
|
||||
box-shadow: 0 0 0 1px rgba(224, 231, 241, 0.61) inset;
|
||||
}
|
||||
.show-block-boundaries #showBlocksBoundariesButton span,
|
||||
.thin-mode #enableThinModeButton span {
|
||||
font-size: 0;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
.show-block-boundaries #showBlocksBoundariesButton span::before,
|
||||
.thin-mode #enableThinModeButton span::before {
|
||||
content: attr(data-toggled-text);
|
||||
display: inline;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Dark theme overrides
|
||||
*/
|
||||
.dark-mode img {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.dark-mode .cdx-simple-image__picture--with-border,
|
||||
.dark-mode .cdx-input {
|
||||
border-color: var(--color-border-light);
|
||||
}
|
||||
|
||||
.dark-mode .ce-example__button {
|
||||
box-shadow: 0 24px 18px -14px rgba(4, 154, 255, 0.24);
|
||||
}
|
||||
|
||||
.dark-mode .ce-example__output {
|
||||
background-color: #17191f;
|
||||
}
|
||||
|
||||
.dark-mode .inline-code {
|
||||
background-color: rgba(53, 56, 68, 0.62);
|
||||
color: #727683;
|
||||
}
|
||||
|
||||
.dark-mode a {
|
||||
color: #959ba8;
|
||||
}
|
||||
|
||||
.dark-mode .ce-example__statusbar-toggler,
|
||||
.dark-mode .ce-example__statusbar-button {
|
||||
background-color: #343842;
|
||||
}
|
||||
|
||||
.dark-mode .ce-example__statusbar-toggler::before {
|
||||
transform: translateX(calc(var(--toggler-size) * 2.2 - var(--toggler-size)));
|
||||
}
|
||||
|
||||
.dark-mode .ce-example__statusbar-toggler::after {
|
||||
content: '*';
|
||||
right: auto;
|
||||
left: 6px;
|
||||
top: 7px;
|
||||
color: #fff;
|
||||
box-shadow: none;
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
.dark-mode.show-block-boundaries .ce-block,
|
||||
.dark-mode.show-block-boundaries .ce-block__content {
|
||||
box-shadow: 0 0 0 1px rgba(128, 144, 159, 0.09) inset;
|
||||
}
|
||||
|
||||
.dark-mode.thin-mode .ce-example__content{
|
||||
border-color: var(--color-border-light);
|
||||
}
|
||||
|
||||
.dark-mode .ce-example__statusbar-item:not(:last-of-type)::after {
|
||||
color: var(--color-border-light);
|
||||
}
|
||||
|
||||
.dark-mode .ce-block--selected .ce-block__content,
|
||||
.dark-mode ::selection{
|
||||
background-color: rgba(57, 68, 84, 0.57);
|
||||
}
|
||||
|
||||
.dark-mode .ce-toolbox__button,
|
||||
.dark-mode .ce-toolbar__settings-btn,
|
||||
.dark-mode .ce-toolbar__plus {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.dark-mode .ce-stub {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<!--
|
||||
Use this page for debugging purposes.
|
||||
Use this page is for debugging purposes.
|
||||
|
||||
Editor Tools are loaded as git-submodules.
|
||||
You can pull modules by running `yarn pull_tools` and start experimenting.
|
||||
|
@ -15,6 +15,11 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
if (localStorage.getItem('theme') === 'dark') {
|
||||
document.body.classList.add("dark-mode");
|
||||
}
|
||||
</script>
|
||||
<div class="ce-example">
|
||||
<div class="ce-example__header">
|
||||
<a class="ce-example__header-logo" href="https://codex.so/editor">Editor.js 🤩🧦🤨</a>
|
||||
|
@ -26,7 +31,7 @@
|
|||
<a href="https://editorjs.io/creating-a-block-tool" target="_blank">API</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ce-example__content _ce-example__content--small">
|
||||
<div class="ce-example__content">
|
||||
<div id="editorjs"></div>
|
||||
<div id="hint-core" style="text-align: center;">
|
||||
No core bundle file found. Run <code class="inline-code">yarn build</code>
|
||||
|
@ -38,12 +43,31 @@
|
|||
editor.save()
|
||||
</div>
|
||||
<div class="ce-example__statusbar">
|
||||
Readonly:
|
||||
<b id="readonly-state">
|
||||
Off
|
||||
</b>
|
||||
<div class="ce-example__statusbar-button" id="toggleReadOnlyButton">
|
||||
toggle
|
||||
<div class="ce-example__statusbar-item">
|
||||
Readonly:
|
||||
<b id="readonly-state">
|
||||
Off
|
||||
</b>
|
||||
|
||||
<div class="ce-example__statusbar-button" id="toggleReadOnlyButton">
|
||||
toggle
|
||||
</div>
|
||||
</div>
|
||||
<div class="ce-example__statusbar-item">
|
||||
<div class="ce-example__statusbar-button" id="showBlocksBoundariesButton">
|
||||
<span data-toggled-text="Hide">Show</span>
|
||||
blocks boundaries
|
||||
</div>
|
||||
</div>
|
||||
<div class="ce-example__statusbar-item">
|
||||
<div class="ce-example__statusbar-button" id="enableThinModeButton">
|
||||
<span data-toggled-text="Disable">Enable</span>
|
||||
thin mode
|
||||
</div>
|
||||
</div>
|
||||
<div class="ce-example__statusbar-item ce-example__statusbar-item--right">
|
||||
<div class="ce-example__statusbar-toggler" id="darkThemeToggler">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -68,7 +92,7 @@
|
|||
<script src="./tools/header/dist/bundle.js" onload="document.getElementById('hint-tools').hidden = true"></script><!-- Header -->
|
||||
<script src="./tools/simple-image/dist/bundle.js"></script><!-- Image -->
|
||||
<script src="./tools/delimiter/dist/bundle.js"></script><!-- Delimiter -->
|
||||
<!-- <script src="./tools/list/dist/bundle.js"></script> List -->
|
||||
<!-- <script src="./tools/list/dist/bundle.js"></script> List-->
|
||||
<script src="./tools/nested-list/dist/nested-list.js"></script><!-- Nested List -->
|
||||
<script src="./tools/checklist/dist/bundle.js"></script><!-- Checklist -->
|
||||
<script src="./tools/quote/dist/bundle.js"></script><!-- Quote -->
|
||||
|
@ -88,10 +112,10 @@
|
|||
<!-- Initialization -->
|
||||
<script>
|
||||
/**
|
||||
* To initialize the Editor, create a new instance with configuration object
|
||||
* @see docs/installation.md for mode details
|
||||
* Editor init config
|
||||
* @see https://editorjs.io/configuration
|
||||
*/
|
||||
var editor = new EditorJS({
|
||||
const editorConfig = {
|
||||
/**
|
||||
* Enable/Disable the read only mode
|
||||
*/
|
||||
|
@ -200,7 +224,7 @@
|
|||
type: "header",
|
||||
data: {
|
||||
text: "Editor.js",
|
||||
level: 2
|
||||
level: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -215,7 +239,7 @@
|
|||
id: "7ItVl5biRo",
|
||||
data: {
|
||||
text: "Key features",
|
||||
level: 3
|
||||
level: 2
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -244,7 +268,7 @@
|
|||
id: "QZFox1m_ul",
|
||||
data: {
|
||||
text: "What does it mean «block-styled editor»",
|
||||
level: 3
|
||||
level: 2
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -266,7 +290,7 @@
|
|||
id: "1sYMhUrznu",
|
||||
data: {
|
||||
text: "What does it mean clean data output",
|
||||
level: 3
|
||||
level: 2
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -321,7 +345,12 @@
|
|||
onChange: function(api, event) {
|
||||
console.log('something changed', event);
|
||||
},
|
||||
});
|
||||
}
|
||||
/**
|
||||
* To initialize the Editor, create a new instance with configuration object
|
||||
* @see docs/installation.md for mode details
|
||||
*/
|
||||
var editor = new EditorJS(editorConfig);
|
||||
|
||||
/**
|
||||
* Saving button
|
||||
|
@ -355,6 +384,39 @@
|
|||
|
||||
readOnlyIndicator.textContent = readOnlyState ? 'On' : 'Off';
|
||||
});
|
||||
|
||||
/**
|
||||
* Button for displaying blocks borders. Useful for UI development
|
||||
*/
|
||||
const showBlocksBoundariesButton = document.getElementById("showBlocksBoundariesButton");
|
||||
|
||||
showBlocksBoundariesButton.addEventListener('click', () => {
|
||||
document.body.classList.toggle("show-block-boundaries")
|
||||
})
|
||||
|
||||
/**
|
||||
* Button for enabling the 'Thin' mode
|
||||
*/
|
||||
const enableThinModeButton = document.getElementById("enableThinModeButton");
|
||||
|
||||
enableThinModeButton.addEventListener('click', () => {
|
||||
document.body.classList.toggle("thin-mode")
|
||||
|
||||
editor.destroy();
|
||||
|
||||
editor = new EditorJS(editorConfig);
|
||||
})
|
||||
|
||||
/**
|
||||
* Toggler for toggling the dark mode
|
||||
*/
|
||||
const darkThemeToggler = document.getElementById("darkThemeToggler");
|
||||
|
||||
darkThemeToggler.addEventListener('click', () => {
|
||||
document.body.classList.toggle("dark-mode");
|
||||
|
||||
localStorage.setItem('theme', document.body.classList.contains("dark-mode") ? 'dark' : 'default');
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit f511dc066aab6bf82b4ffcd4cdee35ad71db8d36
|
||||
Subproject commit 491ada2f1653de52cfaac6130244736f57f7afde
|
|
@ -1 +1 @@
|
|||
Subproject commit e26b3e7c106486d2d776219e18cd125469991a25
|
||||
Subproject commit e3df500fc62a88d3490fa4ba4030c07f0cd79d64
|
1
example/tools/nested-list
Submodule
1
example/tools/nested-list
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 8375ae17756fa2677d57e716e12096437d01e8f8
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@editorjs/editorjs",
|
||||
"version": "2.23.0-rc.0",
|
||||
"version": "2.23.0-rc.1",
|
||||
"description": "Editor.js — Native JS, based on API and Open Source",
|
||||
"main": "dist/editor.js",
|
||||
"types": "./types/index.d.ts",
|
||||
|
@ -22,8 +22,9 @@
|
|||
"lint:fix": "eslint src/ --ext .ts --fix",
|
||||
"lint:tests": "eslint test/ --ext .ts",
|
||||
"svg": "svg-sprite-generate -d src/assets/ -o dist/sprite.svg",
|
||||
"ci:pull_paragraph": "git submodule update --init ./src/tools/paragraph",
|
||||
"pull_tools": "git submodule update --init --recursive",
|
||||
"_tools:checkout": "git submodule foreach git checkout master",
|
||||
"_tools:checkout": "git submodule foreach 'git checkout master || git checkout main'",
|
||||
"_tools:pull": "git submodule foreach git pull",
|
||||
"_tools:yarn": "git submodule foreach yarn",
|
||||
"_tools:build": "git submodule foreach yarn build",
|
||||
|
@ -49,6 +50,8 @@
|
|||
"@codexteam/shortcuts": "^1.1.1",
|
||||
"@cypress/code-coverage": "^3.9.2",
|
||||
"@cypress/webpack-preprocessor": "^5.6.0",
|
||||
"@editorjs/header": "^2.6.1",
|
||||
"@editorjs/simple-image": "^1.4.1",
|
||||
"@types/node": "^14.14.35",
|
||||
"@types/webpack": "^4.41.12",
|
||||
"@types/webpack-env": "^1.15.2",
|
||||
|
@ -93,7 +96,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"codex-notifier": "^1.1.2",
|
||||
"codex-tooltip": "^1.0.2",
|
||||
"codex-tooltip": "^1.0.4",
|
||||
"nanoid": "^3.1.22"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 8 8">
|
||||
<circle cx="6.5" cy="1.5" r="1.5"/>
|
||||
<circle cx="6.5" cy="6.5" r="1.5"/>
|
||||
<circle cx="1.5" cy="1.5" r="1.5"/>
|
||||
<circle cx="1.5" cy="6.5" r="1.5"/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<g transform="translate(4 1.5)" fill-rule="evenodd">
|
||||
<circle cx="1.3" cy="1.3" r="1.3"/>
|
||||
<circle cx="6.5" cy="1.3" r="1.3"/>
|
||||
<circle cx="6.5" cy="6.5" r="1.3"/>
|
||||
<circle cx="1.3" cy="6.5" r="1.3"/>
|
||||
<circle cx="6.5" cy="11.7" r="1.3"/>
|
||||
<circle cx="1.3" cy="11.7" r="1.3"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 218 B After Width: | Height: | Size: 372 B |
|
@ -1,3 +1,6 @@
|
|||
<svg width="14" height="14" viewBox="0 0 14 14" xmlns="http://www.w3.org/2000/svg">
|
||||
<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"/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<g transform="translate(1 1.5)" fill-rule="evenodd">
|
||||
<rect x="6" width="2" height="13" rx="1"/>
|
||||
<rect x=".5" y="5.5" width="13" height="2" rx="1"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 260 B After Width: | Height: | Size: 233 B |
|
@ -109,6 +109,8 @@ export default class MoveDownTune implements BlockTune {
|
|||
/** Change blocks positions */
|
||||
this.api.blocks.move(currentBlockIndex + 1);
|
||||
|
||||
this.api.toolbar.toggleBlockSettings(true);
|
||||
|
||||
/** Hide the Tooltip */
|
||||
this.api.tooltip.hide();
|
||||
}
|
||||
|
|
|
@ -117,6 +117,8 @@ export default class MoveUpTune implements BlockTune {
|
|||
/** Change blocks positions */
|
||||
this.api.blocks.move(currentBlockIndex - 1);
|
||||
|
||||
this.api.toolbar.toggleBlockSettings(true);
|
||||
|
||||
/** Hide the Tooltip */
|
||||
this.api.tooltip.hide();
|
||||
}
|
||||
|
|
|
@ -347,7 +347,7 @@ export default class Core {
|
|||
eventsDispatcher: this.eventsDispatcher,
|
||||
});
|
||||
} catch (e) {
|
||||
_.log(`Module ${Module.displayName} skipped because`, 'warn', e);
|
||||
_.log(`Module ${Module.displayName} skipped because`, 'error', e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -612,4 +612,26 @@ export default class Dom {
|
|||
public static isAnchor(element: Element): element is HTMLAnchorElement {
|
||||
return element.tagName.toLowerCase() === 'a';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return element's offset related to the document
|
||||
*
|
||||
* @todo handle case when editor initialized in scrollable popup
|
||||
* @param el - element to compute offset
|
||||
*/
|
||||
public static offset(el): {top: number; left: number; right: number; bottom: number} {
|
||||
const rect = el.getBoundingClientRect();
|
||||
const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
|
||||
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
|
||||
|
||||
const top = rect.top + scrollTop;
|
||||
const left = rect.left + scrollLeft;
|
||||
|
||||
return {
|
||||
top,
|
||||
left,
|
||||
bottom: top + rect.height,
|
||||
right: left + rect.width,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ export default class BlocksAPI extends Module {
|
|||
getBlockByIndex: (index: number): BlockAPIInterface | void => this.getBlockByIndex(index),
|
||||
getById: (id: string): BlockAPIInterface | null => this.getById(id),
|
||||
getCurrentBlockIndex: (): number => this.getCurrentBlockIndex(),
|
||||
getBlockIndex: (id: string): number => this.getBlockIndex(id),
|
||||
getBlocksCount: (): number => this.getBlocksCount(),
|
||||
stretchBlock: (index: number, status = true): void => this.stretchBlock(index, status),
|
||||
insertNewBlock: (): void => this.insertNewBlock(),
|
||||
|
@ -51,6 +52,24 @@ export default class BlocksAPI extends Module {
|
|||
return this.Editor.BlockManager.currentBlockIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index of Block by id;
|
||||
*
|
||||
* @param id - block id
|
||||
* @returns {number}
|
||||
*/
|
||||
public getBlockIndex(id: string): number | undefined {
|
||||
const block = this.Editor.BlockManager.getBlockById(id);
|
||||
|
||||
if (!block) {
|
||||
_.logLabeled('There is no block with id `' + id + '`', 'warn');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
return this.Editor.BlockManager.getBlockIndex(block);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns BlockAPI object by Block index
|
||||
*
|
||||
|
@ -100,12 +119,6 @@ export default class BlocksAPI extends Module {
|
|||
);
|
||||
|
||||
this.Editor.BlockManager.swap(fromIndex, toIndex);
|
||||
|
||||
/**
|
||||
* Move toolbar
|
||||
* DO not close the settings
|
||||
*/
|
||||
this.Editor.Toolbar.move(false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -116,12 +129,6 @@ export default class BlocksAPI extends Module {
|
|||
*/
|
||||
public move(toIndex: number, fromIndex?: number): void {
|
||||
this.Editor.BlockManager.move(toIndex, fromIndex);
|
||||
|
||||
/**
|
||||
* Move toolbar
|
||||
* DO not close the settings
|
||||
*/
|
||||
this.Editor.Toolbar.move(false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -212,27 +219,32 @@ export default class BlocksAPI extends Module {
|
|||
}
|
||||
|
||||
/**
|
||||
* Insert new Block
|
||||
* Insert new Block and returns it's API
|
||||
*
|
||||
* @param {string} type — Tool name
|
||||
* @param {BlockToolData} data — Tool data to insert
|
||||
* @param {ToolConfig} config — Tool config
|
||||
* @param {number?} index — index where to insert new Block
|
||||
* @param {boolean?} needToFocus - flag to focus inserted Block
|
||||
* @param replace - pass true to replace the Block existed under passed index
|
||||
*/
|
||||
public insert = (
|
||||
type: string = this.config.defaultBlock,
|
||||
data: BlockToolData = {},
|
||||
config: ToolConfig = {},
|
||||
index?: number,
|
||||
needToFocus?: boolean
|
||||
): void => {
|
||||
this.Editor.BlockManager.insert({
|
||||
needToFocus?: boolean,
|
||||
replace?: boolean
|
||||
): BlockAPIInterface => {
|
||||
const insertedBlock = this.Editor.BlockManager.insert({
|
||||
tool: type,
|
||||
data,
|
||||
index,
|
||||
needToFocus,
|
||||
replace,
|
||||
});
|
||||
|
||||
return new BlockAPI(insertedBlock);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -32,6 +32,7 @@ export default class API extends Module {
|
|||
tooltip: this.Editor.TooltipAPI.methods,
|
||||
i18n: this.Editor.I18nAPI.methods,
|
||||
readOnly: this.Editor.ReadOnlyAPI.methods,
|
||||
ui: this.Editor.UiAPI.methods,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -13,21 +13,22 @@ export default class ListenersAPI extends Module {
|
|||
*/
|
||||
public get methods(): Listeners {
|
||||
return {
|
||||
on: (element: HTMLElement, eventType, handler, useCapture): void => this.on(element, eventType, handler, useCapture),
|
||||
on: (element: HTMLElement, eventType, handler, useCapture): string => this.on(element, eventType, handler, useCapture),
|
||||
off: (element, eventType, handler, useCapture): void => this.off(element, eventType, handler, useCapture),
|
||||
offById: (id): void => this.offById(id),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* adds DOM event listener
|
||||
* Ads a DOM event listener. Return it's id.
|
||||
*
|
||||
* @param {HTMLElement} element - Element to set handler to
|
||||
* @param {string} eventType - event type
|
||||
* @param {() => void} handler - event handler
|
||||
* @param {boolean} useCapture - capture event or not
|
||||
*/
|
||||
public on(element: HTMLElement, eventType: string, handler: () => void, useCapture?: boolean): void {
|
||||
this.listeners.on(element, eventType, handler, useCapture);
|
||||
public on(element: HTMLElement, eventType: string, handler: () => void, useCapture?: boolean): string {
|
||||
return this.listeners.on(element, eventType, handler, useCapture);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -41,4 +42,13 @@ export default class ListenersAPI extends Module {
|
|||
public off(element: Element, eventType: string, handler: () => void, useCapture?: boolean): void {
|
||||
this.listeners.off(element, eventType, handler, useCapture);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes DOM listener by the listener id
|
||||
*
|
||||
* @param id - id of the listener to remove
|
||||
*/
|
||||
public offById(id: string): void {
|
||||
this.listeners.offById(id);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ export default class ToolbarAPI extends Module {
|
|||
* Open toolbar
|
||||
*/
|
||||
public open(): void {
|
||||
this.Editor.Toolbar.open();
|
||||
this.Editor.Toolbar.moveAndOpen();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -48,16 +48,8 @@ export default class ToolbarAPI extends Module {
|
|||
/** Check that opening state is set or not */
|
||||
const canOpenBlockSettings = openingState ?? !this.Editor.BlockSettings.opened;
|
||||
|
||||
/** Check if state same as current state */
|
||||
if (openingState === this.Editor.BlockSettings.opened) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (canOpenBlockSettings) {
|
||||
if (!this.Editor.Toolbar.opened) {
|
||||
this.Editor.Toolbar.open(true, false);
|
||||
this.Editor.Toolbar.plusButton.hide();
|
||||
}
|
||||
this.Editor.Toolbar.moveAndOpen();
|
||||
this.Editor.BlockSettings.open();
|
||||
} else {
|
||||
this.Editor.BlockSettings.close();
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
import { Tooltip as ITooltip } from '../../../../types/api';
|
||||
import { TooltipContent, TooltipOptions } from 'codex-tooltip';
|
||||
import type { TooltipOptions, TooltipContent } from 'codex-tooltip/types';
|
||||
import Module from '../../__module';
|
||||
import { ModuleConfig } from '../../../types-internal/module-config';
|
||||
import Tooltip from '../../utils/tooltip';
|
||||
import EventsDispatcher from '../../utils/events';
|
||||
import { EditorConfig } from '../../../../types';
|
||||
/**
|
||||
* @class TooltipAPI
|
||||
* @classdesc Tooltip API
|
||||
|
@ -16,9 +14,9 @@ export default class TooltipAPI extends Module {
|
|||
private tooltip: Tooltip;
|
||||
/**
|
||||
* @class
|
||||
* @param {object} moduleConfiguration - Module Configuration
|
||||
* @param {EditorConfig} moduleConfiguration.config - Editor's config
|
||||
* @param {EventsDispatcher} moduleConfiguration.eventsDispatcher - Editor's event dispatcher
|
||||
* @param moduleConfiguration - Module Configuration
|
||||
* @param moduleConfiguration.config - Editor's config
|
||||
* @param moduleConfiguration.eventsDispatcher - Editor's event dispatcher
|
||||
*/
|
||||
constructor({ config, eventsDispatcher }: ModuleConfig) {
|
||||
super({
|
||||
|
|
36
src/components/modules/api/ui.ts
Normal file
36
src/components/modules/api/ui.ts
Normal file
|
@ -0,0 +1,36 @@
|
|||
import Module from '../../__module';
|
||||
import { Ui, UiNodes } from '../../../../types/api';
|
||||
|
||||
/**
|
||||
* API module allowing to access some Editor UI elements
|
||||
*/
|
||||
export default class UiAPI extends Module {
|
||||
/**
|
||||
* Available methods / getters
|
||||
*/
|
||||
public get methods(): Ui {
|
||||
return {
|
||||
nodes: this.editorNodes,
|
||||
/**
|
||||
* There can be added some UI methods, like toggleThinMode() etc
|
||||
*/
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Exported classes
|
||||
*/
|
||||
private get editorNodes(): UiNodes {
|
||||
return {
|
||||
/**
|
||||
* Top-level editor instance wrapper
|
||||
*/
|
||||
wrapper: this.Editor.UI.nodes.wrapper,
|
||||
|
||||
/**
|
||||
* Element that holds all the Blocks
|
||||
*/
|
||||
redactor: this.Editor.UI.nodes.redactor,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -125,9 +125,10 @@ export default class BlockEvents extends Module {
|
|||
return;
|
||||
}
|
||||
|
||||
const canOpenToolbox = currentBlock.tool.isDefault && currentBlock.isEmpty;
|
||||
const conversionToolbarOpened = !currentBlock.isEmpty && ConversionToolbar.opened;
|
||||
const inlineToolbarOpened = !currentBlock.isEmpty && !SelectionUtils.isCollapsed && InlineToolbar.opened;
|
||||
const isEmptyBlock = currentBlock.isEmpty;
|
||||
const canOpenToolbox = currentBlock.tool.isDefault && isEmptyBlock;
|
||||
const conversionToolbarOpened = !isEmptyBlock && ConversionToolbar.opened;
|
||||
const inlineToolbarOpened = !isEmptyBlock && !SelectionUtils.isCollapsed && InlineToolbar.opened;
|
||||
|
||||
/**
|
||||
* For empty Blocks we show Plus button via Toolbox only for default Blocks
|
||||
|
@ -255,19 +256,9 @@ export default class BlockEvents extends Module {
|
|||
this.Editor.Caret.setToBlock(newCurrent);
|
||||
|
||||
/**
|
||||
* If new Block is empty
|
||||
* Show Toolbar
|
||||
*/
|
||||
if (newCurrent.tool.isDefault && newCurrent.isEmpty) {
|
||||
/**
|
||||
* Show Toolbar
|
||||
*/
|
||||
this.Editor.Toolbar.open(false);
|
||||
|
||||
/**
|
||||
* Show Plus Button
|
||||
*/
|
||||
this.Editor.Toolbar.plusButton.show();
|
||||
}
|
||||
this.Editor.Toolbar.moveAndOpen(newCurrent);
|
||||
|
||||
event.preventDefault();
|
||||
}
|
||||
|
@ -505,7 +496,7 @@ export default class BlockEvents extends Module {
|
|||
* @param {KeyboardEvent} event - keyboard event
|
||||
*/
|
||||
private needToolbarClosing(event: KeyboardEvent): boolean {
|
||||
const toolboxItemSelected = (event.keyCode === _.keyCodes.ENTER && this.Editor.Toolbox.opened),
|
||||
const toolboxItemSelected = (event.keyCode === _.keyCodes.ENTER && this.Editor.Toolbar.toolbox.opened),
|
||||
blockSettingsItemSelected = (event.keyCode === _.keyCodes.ENTER && this.Editor.BlockSettings.opened),
|
||||
inlineToolbarItemSelected = (event.keyCode === _.keyCodes.ENTER && this.Editor.InlineToolbar.opened),
|
||||
conversionToolbarItemSelected = (event.keyCode === _.keyCodes.ENTER && this.Editor.ConversionToolbar.opened),
|
||||
|
@ -531,11 +522,10 @@ export default class BlockEvents extends Module {
|
|||
*/
|
||||
private activateToolbox(): void {
|
||||
if (!this.Editor.Toolbar.opened) {
|
||||
this.Editor.Toolbar.open(false, false);
|
||||
this.Editor.Toolbar.plusButton.show();
|
||||
}
|
||||
this.Editor.Toolbar.moveAndOpen();
|
||||
} // else Flipper will leaf through it
|
||||
|
||||
this.Editor.Toolbox.open();
|
||||
this.Editor.Toolbar.toolbox.open();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -544,8 +534,7 @@ export default class BlockEvents extends Module {
|
|||
private activateBlockSettings(): void {
|
||||
if (!this.Editor.Toolbar.opened) {
|
||||
this.Editor.BlockManager.currentBlock.focused = true;
|
||||
this.Editor.Toolbar.open(true, false);
|
||||
this.Editor.Toolbar.plusButton.hide();
|
||||
this.Editor.Toolbar.moveAndOpen();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -553,6 +542,11 @@ export default class BlockEvents extends Module {
|
|||
* Next Tab press will leaf Settings Buttons
|
||||
*/
|
||||
if (!this.Editor.BlockSettings.opened) {
|
||||
/**
|
||||
* @todo Debug the case when we set caret to some block, hovering another block
|
||||
* — wrong settings will be opened.
|
||||
* To fix it, we should refactor the Block Settings module — make it a standalone class, like the Toolbox
|
||||
*/
|
||||
this.Editor.BlockSettings.open();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -75,6 +75,15 @@ export default class BlockManager extends Module {
|
|||
return this._blocks[this.currentBlockIndex];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set passed Block as a current
|
||||
*
|
||||
* @param block - block to set as a current
|
||||
*/
|
||||
public set currentBlock(block: Block) {
|
||||
this.currentBlockIndex = this.getBlockIndex(block);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns next Block instance
|
||||
*
|
||||
|
@ -549,11 +558,15 @@ export default class BlockManager extends Module {
|
|||
/**
|
||||
* Returns Block by passed index
|
||||
*
|
||||
* @param {number} index - index to get
|
||||
* @param {number} index - index to get. -1 to get last
|
||||
*
|
||||
* @returns {Block}
|
||||
*/
|
||||
public getBlockByIndex(index): Block {
|
||||
if (index === -1) {
|
||||
index = this._blocks.length - 1;
|
||||
}
|
||||
|
||||
return this._blocks[index];
|
||||
}
|
||||
|
||||
|
@ -746,7 +759,7 @@ export default class BlockManager extends Module {
|
|||
|
||||
/**
|
||||
* Sets current Block Index -1 which means unknown
|
||||
* and clear highlightings
|
||||
* and clear highlights
|
||||
*/
|
||||
public dropPointer(): void {
|
||||
this.currentBlockIndex = -1;
|
||||
|
|
|
@ -10,6 +10,7 @@ import $ from '../dom';
|
|||
|
||||
import SelectionUtils from '../selection';
|
||||
import Block from '../block';
|
||||
import * as _ from '../utils';
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -185,17 +186,21 @@ export default class RectangleSelection extends Module {
|
|||
this.processMouseDown(mouseEvent);
|
||||
}, false);
|
||||
|
||||
this.listeners.on(document.body, 'mousemove', (mouseEvent: MouseEvent) => {
|
||||
this.listeners.on(document.body, 'mousemove', _.throttle((mouseEvent: MouseEvent) => {
|
||||
this.processMouseMove(mouseEvent);
|
||||
}, false);
|
||||
}, 10), {
|
||||
passive: true,
|
||||
});
|
||||
|
||||
this.listeners.on(document.body, 'mouseleave', () => {
|
||||
this.processMouseLeave();
|
||||
});
|
||||
|
||||
this.listeners.on(window, 'scroll', (mouseEvent: MouseEvent) => {
|
||||
this.listeners.on(window, 'scroll', _.throttle((mouseEvent: MouseEvent) => {
|
||||
this.processScroll(mouseEvent);
|
||||
}, false);
|
||||
}, 10), {
|
||||
passive: true,
|
||||
});
|
||||
|
||||
this.listeners.on(document.body, 'mouseup', () => {
|
||||
this.processMouseUp();
|
||||
|
@ -252,6 +257,7 @@ export default class RectangleSelection extends Module {
|
|||
* Handle mouse up
|
||||
*/
|
||||
private processMouseUp(): void {
|
||||
this.clearSelection();
|
||||
this.endSelection();
|
||||
}
|
||||
|
||||
|
@ -356,6 +362,11 @@ export default class RectangleSelection extends Module {
|
|||
|
||||
this.updateRectangleSize();
|
||||
|
||||
/**
|
||||
* Hide Block Settings Toggler (along with the Toolbar) (if showed) when the Rectangle Selection is activated
|
||||
*/
|
||||
this.Editor.Toolbar.close();
|
||||
|
||||
if (index === undefined) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ import $ from '../../dom';
|
|||
import Flipper, { FlipperOptions } from '../../flipper';
|
||||
import * as _ from '../../utils';
|
||||
import SelectionUtils from '../../selection';
|
||||
import Block from '../../block';
|
||||
|
||||
/**
|
||||
* HTML Elements that used for BlockSettings
|
||||
|
@ -23,6 +24,8 @@ interface BlockSettingsNodes {
|
|||
* | . Default Settings . |
|
||||
* | ...................... |
|
||||
* |________________________|
|
||||
*
|
||||
* @todo Make Block Settings no-module but a standalone class, like Toolbox
|
||||
*/
|
||||
export default class BlockSettings extends Module<BlockSettingsNodes> {
|
||||
/**
|
||||
|
@ -120,8 +123,10 @@ export default class BlockSettings extends Module<BlockSettingsNodes> {
|
|||
|
||||
/**
|
||||
* Open Block Settings pane
|
||||
*
|
||||
* @param targetBlock - near which Block we should open BlockSettings
|
||||
*/
|
||||
public open(): void {
|
||||
public open(targetBlock: Block = this.Editor.BlockManager.currentBlock): void {
|
||||
this.nodes.wrapper.classList.add(this.CSS.wrapperOpened);
|
||||
|
||||
/**
|
||||
|
@ -133,18 +138,18 @@ export default class BlockSettings extends Module<BlockSettingsNodes> {
|
|||
/**
|
||||
* Highlight content of a Block we are working with
|
||||
*/
|
||||
this.Editor.BlockManager.currentBlock.selected = true;
|
||||
targetBlock.selected = true;
|
||||
this.Editor.BlockSelection.clearCache();
|
||||
|
||||
/**
|
||||
* Fill Tool's settings
|
||||
*/
|
||||
this.addToolSettings();
|
||||
this.addToolSettings(targetBlock);
|
||||
|
||||
/**
|
||||
* Add default settings that presents for all Blocks
|
||||
*/
|
||||
this.addTunes();
|
||||
this.addTunes(targetBlock);
|
||||
|
||||
/** Tell to subscribers that block settings is opened */
|
||||
this.eventsDispatcher.emit(this.events.opened);
|
||||
|
@ -227,9 +232,11 @@ export default class BlockSettings extends Module<BlockSettingsNodes> {
|
|||
|
||||
/**
|
||||
* Add Tool's settings
|
||||
*
|
||||
* @param targetBlock - Block to render settings
|
||||
*/
|
||||
private addToolSettings(): void {
|
||||
const settingsElement = this.Editor.BlockManager.currentBlock.renderSettings();
|
||||
private addToolSettings(targetBlock): void {
|
||||
const settingsElement = targetBlock.renderSettings();
|
||||
|
||||
if (settingsElement) {
|
||||
$.append(this.nodes.toolSettings, settingsElement);
|
||||
|
@ -238,9 +245,11 @@ export default class BlockSettings extends Module<BlockSettingsNodes> {
|
|||
|
||||
/**
|
||||
* Add tunes: provided by user and default ones
|
||||
*
|
||||
* @param targetBlock - Block to render its Tunes set
|
||||
*/
|
||||
private addTunes(): void {
|
||||
const [toolTunes, defaultTunes] = this.Editor.BlockManager.currentBlock.renderTunes();
|
||||
private addTunes(targetBlock): void {
|
||||
const [toolTunes, defaultTunes] = targetBlock.renderTunes();
|
||||
|
||||
$.append(this.nodes.toolSettings, toolTunes);
|
||||
$.append(this.nodes.defaultSettings, defaultTunes);
|
||||
|
|
|
@ -17,6 +17,8 @@ interface ConversionToolbarNodes {
|
|||
|
||||
/**
|
||||
* Block Converter
|
||||
*
|
||||
* @todo Make the Conversion Toolbar no-module but a standalone class, like Toolbox
|
||||
*/
|
||||
export default class ConversionToolbar extends Module<ConversionToolbarNodes> {
|
||||
/**
|
||||
|
|
|
@ -5,8 +5,25 @@ import I18n from '../../i18n';
|
|||
import { I18nInternalNS } from '../../i18n/namespace-internal';
|
||||
import Tooltip from '../../utils/tooltip';
|
||||
import { ModuleConfig } from '../../../types-internal/module-config';
|
||||
import { EditorConfig } from '../../../../types';
|
||||
import SelectionUtils from '../../selection';
|
||||
import { BlockAPI } from '../../../../types';
|
||||
import Block from '../../block';
|
||||
import Toolbox, { ToolboxEvent } from '../../ui/toolbox';
|
||||
|
||||
/**
|
||||
* @todo Tab on non-empty block should open Block Settings of the hoveredBlock (not where caret is set)
|
||||
* - make Block Settings a standalone module
|
||||
*
|
||||
* @todo TESTCASE - show toggler after opening and closing the Inline Toolbar
|
||||
* @todo TESTCASE - Click outside Editor holder should close Toolbar and Clear Focused blocks
|
||||
* @todo TESTCASE - Click inside Editor holder should close Toolbar and Clear Focused blocks
|
||||
* @todo TESTCASE - Click inside Redactor zone when Block Settings are opened:
|
||||
* - should close Block Settings
|
||||
* - should not close Toolbar
|
||||
* - should move Toolbar to the clicked Block
|
||||
* @todo TESTCASE - Toolbar should be closed on the Cross Block Selection
|
||||
* @todo TESTCASE - Toolbar should be closed on the Rectangle Selection
|
||||
* @todo TESTCASE - If Block Settings or Toolbox are opened, the Toolbar should not be moved by Bocks hovering
|
||||
*/
|
||||
|
||||
/**
|
||||
* HTML Elements used for Toolbar UI
|
||||
|
@ -80,11 +97,22 @@ export default class Toolbar extends Module<ToolbarNodes> {
|
|||
* Tooltip utility Instance
|
||||
*/
|
||||
private tooltip: Tooltip;
|
||||
|
||||
/**
|
||||
* Block near which we display the Toolbox
|
||||
*/
|
||||
private hoveredBlock: Block;
|
||||
|
||||
/**
|
||||
* Toolbox class instance
|
||||
*/
|
||||
private toolboxInstance: Toolbox;
|
||||
|
||||
/**
|
||||
* @class
|
||||
* @param {object} moduleConfiguration - Module Configuration
|
||||
* @param {EditorConfig} moduleConfiguration.config - Editor's config
|
||||
* @param {EventsDispatcher} moduleConfiguration.eventsDispatcher - Editor's event dispatcher
|
||||
* @param moduleConfiguration - Module Configuration
|
||||
* @param moduleConfiguration.config - Editor's config
|
||||
* @param moduleConfiguration.eventsDispatcher - Editor's event dispatcher
|
||||
*/
|
||||
constructor({ config, eventsDispatcher }: ModuleConfig) {
|
||||
super({
|
||||
|
@ -107,6 +135,7 @@ export default class Toolbar extends Module<ToolbarNodes> {
|
|||
actionsOpened: 'ce-toolbar__actions--opened',
|
||||
|
||||
toolbarOpened: 'ce-toolbar--opened',
|
||||
openedToolboxHolderModifier: 'codex-editor--toolbox-opened',
|
||||
|
||||
// Content Zone
|
||||
plusButton: 'ce-toolbar__plus',
|
||||
|
@ -137,7 +166,7 @@ export default class Toolbar extends Module<ToolbarNodes> {
|
|||
return {
|
||||
hide: (): void => this.nodes.plusButton.classList.add(this.CSS.plusButtonHidden),
|
||||
show: (): void => {
|
||||
if (this.Editor.Toolbox.isEmpty) {
|
||||
if (this.toolboxInstance.isEmpty) {
|
||||
return;
|
||||
}
|
||||
this.nodes.plusButton.classList.remove(this.CSS.plusButtonHidden);
|
||||
|
@ -145,6 +174,32 @@ export default class Toolbar extends Module<ToolbarNodes> {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Public interface for accessing the Toolbox
|
||||
*/
|
||||
public get toolbox(): {
|
||||
opened: boolean;
|
||||
close: () => void;
|
||||
open: () => void;
|
||||
toggle: () => void;
|
||||
flipperHasFocus: boolean;
|
||||
} {
|
||||
return {
|
||||
opened: this.toolboxInstance.opened,
|
||||
close: (): void => this.toolboxInstance.close(),
|
||||
open: (): void => {
|
||||
/**
|
||||
* Set current block to cover the case when the Toolbar showed near hovered Block but caret is set to another Block.
|
||||
*/
|
||||
this.Editor.BlockManager.currentBlock = this.hoveredBlock;
|
||||
|
||||
this.toolboxInstance.open();
|
||||
},
|
||||
toggle: (): void => this.toolboxInstance.toggle(),
|
||||
flipperHasFocus: this.toolboxInstance.flipperHasFocus,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Block actions appearance manipulations
|
||||
*
|
||||
|
@ -172,54 +227,80 @@ export default class Toolbar extends Module<ToolbarNodes> {
|
|||
this.enableModuleBindings();
|
||||
} else {
|
||||
this.destroy();
|
||||
this.Editor.Toolbox.destroy();
|
||||
this.toolboxInstance.destroy();
|
||||
this.Editor.BlockSettings.destroy();
|
||||
this.disableModuleBindings();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Move Toolbar to the Current Block
|
||||
* Move Toolbar to the passed (or current) Block
|
||||
*
|
||||
* @param {boolean} forceClose - force close Toolbar Settings and Toolbar
|
||||
* @param block - block to move Toolbar near it
|
||||
*/
|
||||
public move(forceClose = true): void {
|
||||
if (forceClose) {
|
||||
/** Close Toolbox when we move toolbar */
|
||||
this.Editor.Toolbox.close();
|
||||
this.Editor.BlockSettings.close();
|
||||
}
|
||||
|
||||
const currentBlock = this.Editor.BlockManager.currentBlock.holder;
|
||||
public moveAndOpen(block: Block = this.Editor.BlockManager.currentBlock): void {
|
||||
/**
|
||||
* Close Toolbox when we move toolbar
|
||||
*/
|
||||
this.toolboxInstance.close();
|
||||
this.Editor.BlockSettings.close();
|
||||
|
||||
/**
|
||||
* If no one Block selected as a Current
|
||||
*/
|
||||
if (!currentBlock) {
|
||||
if (!block) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.hoveredBlock = block;
|
||||
|
||||
const targetBlockHolder = block.holder;
|
||||
const { isMobile } = this.Editor.UI;
|
||||
const blockHeight = currentBlock.offsetHeight;
|
||||
let toolbarY = currentBlock.offsetTop;
|
||||
const renderedContent = block.pluginsContent;
|
||||
const renderedContentStyle = window.getComputedStyle(renderedContent);
|
||||
const blockRenderedElementPaddingTop = parseInt(renderedContentStyle.paddingTop, 10);
|
||||
const blockHeight = targetBlockHolder.offsetHeight;
|
||||
|
||||
let toolbarY;
|
||||
|
||||
/**
|
||||
* 1) On desktop — Toolbar at the top of Block, Plus/Toolbox moved the center of Block
|
||||
* 2) On mobile — Toolbar at the bottom of Block
|
||||
* On mobile — Toolbar at the bottom of Block
|
||||
* On Desktop — Toolbar should be moved to the first line of block text
|
||||
* To do that, we compute the block offset and the padding-top of the plugin content
|
||||
*/
|
||||
if (!isMobile) {
|
||||
const contentOffset = Math.floor(blockHeight / 2);
|
||||
|
||||
this.nodes.plusButton.style.transform = `translate3d(0, calc(${contentOffset}px - 50%), 0)`;
|
||||
this.Editor.Toolbox.nodes.toolbox.style.transform = `translate3d(0, calc(${contentOffset}px - 50%), 0)`;
|
||||
if (isMobile) {
|
||||
toolbarY = targetBlockHolder.offsetTop + blockHeight;
|
||||
} else {
|
||||
toolbarY += blockHeight;
|
||||
toolbarY = targetBlockHolder.offsetTop + blockRenderedElementPaddingTop;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move Toolbar to the Top coordinate of Block
|
||||
*/
|
||||
this.nodes.wrapper.style.transform = `translate3D(0, ${Math.floor(toolbarY)}px, 0)`;
|
||||
|
||||
/**
|
||||
* Plus Button should be shown only for __empty__ __default__ block
|
||||
*/
|
||||
if (block.tool.isDefault && block.isEmpty) {
|
||||
this.plusButton.show();
|
||||
} else {
|
||||
this.plusButton.hide();
|
||||
}
|
||||
|
||||
this.open();
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the Toolbar
|
||||
*/
|
||||
public close(): void {
|
||||
this.nodes.wrapper.classList.remove(this.CSS.toolbarOpened);
|
||||
|
||||
/** Close components */
|
||||
this.blockActions.hide();
|
||||
this.toolboxInstance.close();
|
||||
this.Editor.BlockSettings.close();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -232,9 +313,8 @@ export default class Toolbar extends Module<ToolbarNodes> {
|
|||
* with closing Toolbox and Block Settings
|
||||
* This flag allows to open Toolbar with Toolbox
|
||||
*/
|
||||
public open(withBlockActions = true, needToCloseToolbox = true): void {
|
||||
private open(withBlockActions = true, needToCloseToolbox = true): void {
|
||||
_.delay(() => {
|
||||
this.move(needToCloseToolbox);
|
||||
this.nodes.wrapper.classList.add(this.CSS.toolbarOpened);
|
||||
|
||||
if (withBlockActions) {
|
||||
|
@ -245,18 +325,6 @@ export default class Toolbar extends Module<ToolbarNodes> {
|
|||
}, 50)();
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the Toolbar
|
||||
*/
|
||||
public close(): void {
|
||||
this.nodes.wrapper.classList.remove(this.CSS.toolbarOpened);
|
||||
|
||||
/** Close components */
|
||||
this.blockActions.hide();
|
||||
this.Editor.Toolbox.close();
|
||||
this.Editor.BlockSettings.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws Toolbar elements
|
||||
*/
|
||||
|
@ -282,10 +350,11 @@ export default class Toolbar extends Module<ToolbarNodes> {
|
|||
* - Toolbox
|
||||
*/
|
||||
this.nodes.plusButton = $.make('div', this.CSS.plusButton);
|
||||
$.append(this.nodes.plusButton, $.svg('plus', 14, 14));
|
||||
$.append(this.nodes.content, this.nodes.plusButton);
|
||||
$.append(this.nodes.plusButton, $.svg('plus', 16, 16));
|
||||
$.append(this.nodes.actions, this.nodes.plusButton);
|
||||
|
||||
this.readOnlyMutableListeners.on(this.nodes.plusButton, 'click', () => {
|
||||
this.tooltip.hide(true);
|
||||
this.plusButtonClicked();
|
||||
}, false);
|
||||
|
||||
|
@ -299,7 +368,9 @@ export default class Toolbar extends Module<ToolbarNodes> {
|
|||
textContent: '⇥ Tab',
|
||||
}));
|
||||
|
||||
this.tooltip.onHover(this.nodes.plusButton, tooltipContent);
|
||||
this.tooltip.onHover(this.nodes.plusButton, tooltipContent, {
|
||||
hidingDelay: 400,
|
||||
});
|
||||
|
||||
/**
|
||||
* Fill Actions Zone:
|
||||
|
@ -309,7 +380,7 @@ export default class Toolbar extends Module<ToolbarNodes> {
|
|||
*/
|
||||
this.nodes.blockActionsButtons = $.make('div', this.CSS.blockActionsButtons);
|
||||
this.nodes.settingsToggler = $.make('span', this.CSS.settingsToggler);
|
||||
const settingsIcon = $.svg('dots', 8, 8);
|
||||
const settingsIcon = $.svg('dots', 16, 16);
|
||||
|
||||
$.append(this.nodes.settingsToggler, settingsIcon);
|
||||
$.append(this.nodes.blockActionsButtons, this.nodes.settingsToggler);
|
||||
|
@ -319,14 +390,14 @@ export default class Toolbar extends Module<ToolbarNodes> {
|
|||
this.nodes.settingsToggler,
|
||||
I18n.ui(I18nInternalNS.ui.blockTunes.toggler, 'Click to tune'),
|
||||
{
|
||||
placement: 'top',
|
||||
hidingDelay: 400,
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Appending Toolbar components to itself
|
||||
*/
|
||||
$.append(this.nodes.content, this.Editor.Toolbox.nodes.toolbox);
|
||||
$.append(this.nodes.content, this.makeToolbox());
|
||||
$.append(this.nodes.actions, this.Editor.BlockSettings.nodes.wrapper);
|
||||
|
||||
/**
|
||||
|
@ -335,11 +406,57 @@ export default class Toolbar extends Module<ToolbarNodes> {
|
|||
$.append(this.Editor.UI.nodes.wrapper, this.nodes.wrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the Toolbox instance and return it's rendered element
|
||||
*/
|
||||
private makeToolbox(): Element {
|
||||
/**
|
||||
* Make the Toolbox
|
||||
*/
|
||||
this.toolboxInstance = new Toolbox({
|
||||
api: this.Editor.API.methods,
|
||||
tools: this.Editor.Tools.blockTools,
|
||||
});
|
||||
|
||||
this.toolboxInstance.on(ToolboxEvent.Opened, () => {
|
||||
this.Editor.UI.nodes.wrapper.classList.add(this.CSS.openedToolboxHolderModifier);
|
||||
});
|
||||
|
||||
this.toolboxInstance.on(ToolboxEvent.Closed, () => {
|
||||
this.Editor.UI.nodes.wrapper.classList.remove(this.CSS.openedToolboxHolderModifier);
|
||||
});
|
||||
|
||||
this.toolboxInstance.on(ToolboxEvent.BlockAdded, ({ block }: {block: BlockAPI }) => {
|
||||
const { BlockManager, Caret } = this.Editor;
|
||||
const newBlock = BlockManager.getBlockById(block.id);
|
||||
|
||||
/**
|
||||
* If the new block doesn't contain inputs, insert the new paragraph below
|
||||
*/
|
||||
if (newBlock.inputs.length === 0) {
|
||||
if (newBlock === BlockManager.lastBlock) {
|
||||
BlockManager.insertAtEnd();
|
||||
Caret.setToBlock(BlockManager.lastBlock);
|
||||
} else {
|
||||
Caret.setToBlock(BlockManager.nextBlock);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return this.toolboxInstance.make();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for Plus Button
|
||||
*/
|
||||
private plusButtonClicked(): void {
|
||||
this.Editor.Toolbox.toggle();
|
||||
/**
|
||||
* We need to update Current Block because user can click on the Plus Button (thanks to appearing by hover) without any clicks on editor
|
||||
* In this case currentBlock will point last block
|
||||
*/
|
||||
this.Editor.BlockManager.currentBlock = this.hoveredBlock;
|
||||
|
||||
this.toolboxInstance.toggle();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -360,7 +477,25 @@ export default class Toolbar extends Module<ToolbarNodes> {
|
|||
e.stopPropagation();
|
||||
|
||||
this.settingsTogglerClicked();
|
||||
|
||||
this.toolboxInstance.close();
|
||||
|
||||
this.tooltip.hide(true);
|
||||
}, true);
|
||||
|
||||
/**
|
||||
* Subscribe to the 'block-hovered' event
|
||||
*/
|
||||
this.eventsDispatcher.on(this.Editor.UI.events.blockHovered, (data: {block: Block}) => {
|
||||
/**
|
||||
* Do not move toolbar if Block Settings or Toolbox opened
|
||||
*/
|
||||
if (this.Editor.BlockSettings.opened || this.toolboxInstance.opened) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.moveAndOpen(data.block);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -374,10 +509,16 @@ export default class Toolbar extends Module<ToolbarNodes> {
|
|||
* Clicks on the Block Settings toggler
|
||||
*/
|
||||
private settingsTogglerClicked(): void {
|
||||
/**
|
||||
* We need to update Current Block because user can click on toggler (thanks to appearing by hover) without any clicks on editor
|
||||
* In this case currentBlock will point last block
|
||||
*/
|
||||
this.Editor.BlockManager.currentBlock = this.hoveredBlock;
|
||||
|
||||
if (this.Editor.BlockSettings.opened) {
|
||||
this.Editor.BlockSettings.close();
|
||||
} else {
|
||||
this.Editor.BlockSettings.open();
|
||||
this.Editor.BlockSettings.open(this.hoveredBlock);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -385,7 +526,7 @@ export default class Toolbar extends Module<ToolbarNodes> {
|
|||
* Draws Toolbar UI
|
||||
*
|
||||
* Toolbar contains BlockSettings and Toolbox.
|
||||
* Thats why at first we draw its components and then Toolbar itself
|
||||
* That's why at first we draw its components and then Toolbar itself
|
||||
*
|
||||
* Steps:
|
||||
* - Make Toolbar dependent components like BlockSettings, Toolbox and so on
|
||||
|
@ -398,11 +539,6 @@ export default class Toolbar extends Module<ToolbarNodes> {
|
|||
*/
|
||||
this.Editor.BlockSettings.make();
|
||||
|
||||
/**
|
||||
* Make Toolbox
|
||||
*/
|
||||
this.Editor.Toolbox.make();
|
||||
|
||||
/**
|
||||
* Make Toolbar
|
||||
*/
|
||||
|
@ -415,6 +551,7 @@ export default class Toolbar extends Module<ToolbarNodes> {
|
|||
*/
|
||||
private destroy(): void {
|
||||
this.removeAllNodes();
|
||||
this.toolboxInstance.destroy();
|
||||
this.tooltip.destroy();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import Module from '../../__module';
|
|||
import $ from '../../dom';
|
||||
import SelectionUtils from '../../selection';
|
||||
import * as _ from '../../utils';
|
||||
import { InlineTool as IInlineTool, EditorConfig } from '../../../../types';
|
||||
import { InlineTool as IInlineTool } from '../../../../types';
|
||||
import Flipper from '../../flipper';
|
||||
import I18n from '../../i18n';
|
||||
import { I18nInternalNS } from '../../i18n/namespace-internal';
|
||||
|
@ -100,9 +100,9 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
|
|||
private tooltip: Tooltip;
|
||||
/**
|
||||
* @class
|
||||
* @param {object} moduleConfiguration - Module Configuration
|
||||
* @param {EditorConfig} moduleConfiguration.config - Editor's config
|
||||
* @param {EventsDispatcher} moduleConfiguration.eventsDispatcher - Editor's event dispatcher
|
||||
* @param moduleConfiguration - Module Configuration
|
||||
* @param moduleConfiguration.config - Editor's config
|
||||
* @param moduleConfiguration.eventsDispatcher - Editor's event dispatcher
|
||||
*/
|
||||
constructor({ config, eventsDispatcher }: ModuleConfig) {
|
||||
super({
|
||||
|
|
|
@ -46,6 +46,15 @@ interface UINodes {
|
|||
* @property {Element} nodes.redactor - <ce-redactor>
|
||||
*/
|
||||
export default class UI extends Module<UINodes> {
|
||||
/**
|
||||
* Events could be emitted by this module.
|
||||
*/
|
||||
public get events(): { blockHovered: string } {
|
||||
return {
|
||||
blockHovered: 'block-hovered',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Editor.js UI CSS class names
|
||||
*
|
||||
|
@ -209,15 +218,23 @@ export default class UI extends Module<UINodes> {
|
|||
* @returns {boolean}
|
||||
*/
|
||||
public get someToolbarOpened(): boolean {
|
||||
const { Toolbox, BlockSettings, InlineToolbar, ConversionToolbar } = this.Editor;
|
||||
const { Toolbar, BlockSettings, InlineToolbar, ConversionToolbar } = this.Editor;
|
||||
|
||||
return BlockSettings.opened || InlineToolbar.opened || ConversionToolbar.opened || Toolbox.opened;
|
||||
return BlockSettings.opened || InlineToolbar.opened || ConversionToolbar.opened || Toolbar.toolbox.opened;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for some Flipper-buttons is under focus
|
||||
*/
|
||||
public get someFlipperButtonFocused(): boolean {
|
||||
/**
|
||||
* Toolbar has internal module (Toolbox) that has own Flipper,
|
||||
* so we check it manually
|
||||
*/
|
||||
if (this.Editor.Toolbar.toolbox.flipperHasFocus) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return Object.entries(this.Editor).filter(([moduleName, moduleClass]) => {
|
||||
return moduleClass.flipper instanceof Flipper;
|
||||
})
|
||||
|
@ -237,12 +254,12 @@ export default class UI extends Module<UINodes> {
|
|||
* Close all Editor's toolbars
|
||||
*/
|
||||
public closeAllToolbars(): void {
|
||||
const { Toolbox, BlockSettings, InlineToolbar, ConversionToolbar } = this.Editor;
|
||||
const { Toolbar, BlockSettings, InlineToolbar, ConversionToolbar } = this.Editor;
|
||||
|
||||
BlockSettings.close();
|
||||
InlineToolbar.close();
|
||||
ConversionToolbar.close();
|
||||
Toolbox.close();
|
||||
Toolbar.toolbox.close();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -356,6 +373,48 @@ export default class UI extends Module<UINodes> {
|
|||
}, {
|
||||
passive: true,
|
||||
});
|
||||
|
||||
/**
|
||||
* Start watching 'block-hovered' events that is used by Toolbar for moving
|
||||
*/
|
||||
this.watchBlockHoveredEvents();
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen redactor mousemove to emit 'block-hovered' event
|
||||
*/
|
||||
private watchBlockHoveredEvents(): void {
|
||||
/**
|
||||
* Used to not to emit the same block multiple times to the 'block-hovered' event on every mousemove
|
||||
*/
|
||||
let blockHoveredEmitted;
|
||||
|
||||
this.readOnlyMutableListeners.on(this.nodes.redactor, 'mousemove', _.throttle((event: MouseEvent | TouchEvent) => {
|
||||
const hoveredBlock = (event.target as Element).closest('.ce-block');
|
||||
|
||||
/**
|
||||
* Do not trigger 'block-hovered' for cross-block selection
|
||||
*/
|
||||
if (this.Editor.BlockSelection.anyBlockSelected) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hoveredBlock) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (blockHoveredEmitted === hoveredBlock) {
|
||||
return;
|
||||
}
|
||||
|
||||
blockHoveredEmitted = hoveredBlock;
|
||||
|
||||
this.eventsDispatcher.emit(this.events.blockHovered, {
|
||||
block: this.Editor.BlockManager.getBlockByChildNode(hoveredBlock),
|
||||
});
|
||||
}, 20), {
|
||||
passive: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -483,8 +542,8 @@ export default class UI extends Module<UINodes> {
|
|||
*/
|
||||
this.Editor.BlockSelection.clearSelection(event);
|
||||
|
||||
if (this.Editor.Toolbox.opened) {
|
||||
this.Editor.Toolbox.close();
|
||||
if (this.Editor.Toolbar.toolbox.opened) {
|
||||
this.Editor.Toolbar.toolbox.close();
|
||||
} else if (this.Editor.BlockSettings.opened) {
|
||||
this.Editor.BlockSettings.close();
|
||||
} else if (this.Editor.ConversionToolbar.opened) {
|
||||
|
@ -548,8 +607,7 @@ export default class UI extends Module<UINodes> {
|
|||
/**
|
||||
* Move toolbar and show plus button because new Block is empty
|
||||
*/
|
||||
this.Editor.Toolbar.move();
|
||||
this.Editor.Toolbar.plusButton.show();
|
||||
this.Editor.Toolbar.moveAndOpen(newBlock);
|
||||
}
|
||||
|
||||
this.Editor.BlockSelection.clearSelection(event);
|
||||
|
@ -577,15 +635,31 @@ export default class UI extends Module<UINodes> {
|
|||
|
||||
if (!clickedInsideOfEditor) {
|
||||
/**
|
||||
* Clear highlightings and pointer on BlockManager
|
||||
* Clear highlights and pointer on BlockManager
|
||||
*
|
||||
* Current page might contain several instances
|
||||
* Click between instances MUST clear focus, pointers and close toolbars
|
||||
*/
|
||||
this.Editor.BlockManager.dropPointer();
|
||||
this.Editor.InlineToolbar.close();
|
||||
this.Editor.Toolbar.close();
|
||||
this.Editor.ConversionToolbar.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* If Block Settings opened, close them by click on document.
|
||||
*
|
||||
* But allow clicking inside Block Settings.
|
||||
* Also, do not process clicks on the Block Settings Toggler, because it has own click listener
|
||||
*/
|
||||
const isClickedInsideBlockSettings = this.Editor.BlockSettings.nodes.wrapper.contains(target);
|
||||
const isClickedInsideBlockSettingsToggler = this.Editor.Toolbar.nodes.settingsToggler.contains(target);
|
||||
const doNotProcess = isClickedInsideBlockSettings || isClickedInsideBlockSettingsToggler;
|
||||
|
||||
if (this.Editor.BlockSettings.opened && !doNotProcess) {
|
||||
this.Editor.BlockSettings.close();
|
||||
|
||||
const clickedBlock = this.Editor.BlockManager.getBlockByChildNode(target);
|
||||
|
||||
this.Editor.Toolbar.moveAndOpen(clickedBlock);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -609,7 +683,7 @@ export default class UI extends Module<UINodes> {
|
|||
let clickedNode = event.target as HTMLElement;
|
||||
|
||||
/**
|
||||
* If click was fired is on Editor`s wrapper, try to get clicked node by elementFromPoint method
|
||||
* If click was fired on Editor`s wrapper, try to get clicked node by elementFromPoint method
|
||||
*/
|
||||
if (clickedNode === this.nodes.redactor) {
|
||||
const clientX = event instanceof MouseEvent ? event.clientX : event.touches[0].clientX;
|
||||
|
@ -642,13 +716,9 @@ export default class UI extends Module<UINodes> {
|
|||
|
||||
/**
|
||||
* Move and open toolbar
|
||||
* (used for showing Block Settings toggler after opening and closing Inline Toolbar)
|
||||
*/
|
||||
this.Editor.Toolbar.open();
|
||||
|
||||
/**
|
||||
* Hide the Plus Button
|
||||
*/
|
||||
this.Editor.Toolbar.plusButton.hide();
|
||||
this.Editor.Toolbar.moveAndOpen();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -691,12 +761,21 @@ export default class UI extends Module<UINodes> {
|
|||
return;
|
||||
}
|
||||
|
||||
const lastBlock = this.Editor.BlockManager.getBlockByIndex(-1);
|
||||
const lastBlockBottomCoord = $.offset(lastBlock.holder).bottom;
|
||||
const clickedCoord = event.pageY;
|
||||
|
||||
const isClickedBottom = event.target instanceof Element &&
|
||||
event.target.isEqualNode(this.nodes.redactor) &&
|
||||
/**
|
||||
* If there is cross block selection started, target will be equal to redactor so we need additional check
|
||||
*/
|
||||
!BlockSelection.anyBlockSelected;
|
||||
!BlockSelection.anyBlockSelected &&
|
||||
|
||||
/**
|
||||
* Prevent caret jumping (to last block) when clicking between blocks
|
||||
*/
|
||||
lastBlockBottomCoord < clickedCoord;
|
||||
|
||||
if (isClickedBottom) {
|
||||
stopPropagation();
|
||||
|
@ -717,27 +796,7 @@ export default class UI extends Module<UINodes> {
|
|||
* Set the caret and toolbar to empty Block
|
||||
*/
|
||||
Caret.setToTheLastBlock();
|
||||
Toolbar.move();
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the Plus Button if:
|
||||
* - Block is an default-block (Text)
|
||||
* - Block is empty
|
||||
*/
|
||||
const isDefaultBlock = this.Editor.BlockManager.currentBlock.tool.isDefault;
|
||||
|
||||
if (isDefaultBlock) {
|
||||
stopPropagation();
|
||||
|
||||
/**
|
||||
* Check isEmpty only for paragraphs to prevent unnecessary tree-walking on Tools with many nodes (for ex. Table)
|
||||
*/
|
||||
const isEmptyBlock = this.Editor.BlockManager.currentBlock.isEmpty;
|
||||
|
||||
if (isEmptyBlock) {
|
||||
this.Editor.Toolbar.plusButton.show();
|
||||
}
|
||||
Toolbar.moveAndOpen(BlockManager.lastBlock);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,61 +1,44 @@
|
|||
import Module from '../../__module';
|
||||
import $ from '../../dom';
|
||||
import * as _ from '../../utils';
|
||||
import Flipper from '../../flipper';
|
||||
import { BlockToolAPI } from '../../block';
|
||||
import I18n from '../../i18n';
|
||||
import { I18nInternalNS } from '../../i18n/namespace-internal';
|
||||
import Shortcuts from '../../utils/shortcuts';
|
||||
import Tooltip from '../../utils/tooltip';
|
||||
import { ModuleConfig } from '../../../types-internal/module-config';
|
||||
import EventsDispatcher from '../../utils/events';
|
||||
import BlockTool from '../../tools/block';
|
||||
import $ from '../dom';
|
||||
import * as _ from '../utils';
|
||||
import Flipper from '../flipper';
|
||||
import { BlockToolAPI } from '../block';
|
||||
import I18n from '../i18n';
|
||||
import { I18nInternalNS } from '../i18n/namespace-internal';
|
||||
import Shortcuts from '../utils/shortcuts';
|
||||
import Tooltip from '../utils/tooltip';
|
||||
import BlockTool from '../tools/block';
|
||||
import ToolsCollection from '../tools/collection';
|
||||
import { API } from '../../../types';
|
||||
import EventsDispatcher from '../utils/events';
|
||||
|
||||
/**
|
||||
* HTMLElements used for Toolbox UI
|
||||
* Event that can be triggered by the Toolbox
|
||||
*/
|
||||
interface ToolboxNodes {
|
||||
toolbox: HTMLElement;
|
||||
buttons: HTMLElement[];
|
||||
export enum ToolboxEvent {
|
||||
/**
|
||||
* When the Toolbox is opened
|
||||
*/
|
||||
Opened = 'toolbox-opened',
|
||||
|
||||
/**
|
||||
* When the Toolbox is closed
|
||||
*/
|
||||
Closed = 'toolbox-closed',
|
||||
|
||||
/**
|
||||
* When the new Block added by Toolbox
|
||||
*/
|
||||
BlockAdded = 'toolbox-block-added',
|
||||
}
|
||||
|
||||
/**
|
||||
* @class Toolbox
|
||||
* @classdesc Holder for Tools
|
||||
*
|
||||
* @typedef {Toolbox} Toolbox
|
||||
* @property {boolean} opened - opening state
|
||||
* @property {object} nodes - Toolbox nodes
|
||||
* @property {object} CSS - CSS class names
|
||||
* Toolbox
|
||||
* This UI element contains list of Block Tools available to be inserted
|
||||
* It appears after click on the Plus Button
|
||||
*
|
||||
* @implements {EventsDispatcher} with some events, see {@link ToolboxEvent}
|
||||
*/
|
||||
export default class Toolbox extends Module<ToolboxNodes> {
|
||||
/**
|
||||
* Current module HTML Elements
|
||||
*/
|
||||
public nodes = {
|
||||
toolbox: null,
|
||||
buttons: [],
|
||||
}
|
||||
|
||||
/**
|
||||
* CSS styles
|
||||
*
|
||||
* @returns {object.<string, string>}
|
||||
*/
|
||||
public get CSS(): { [name: string]: string } {
|
||||
return {
|
||||
toolbox: 'ce-toolbox',
|
||||
toolboxButton: 'ce-toolbox__button',
|
||||
toolboxButtonActive: 'ce-toolbox__button--active',
|
||||
toolboxOpened: 'ce-toolbox--opened',
|
||||
openedToolbarHolderModifier: 'codex-editor--toolbox-opened',
|
||||
|
||||
buttonTooltip: 'ce-toolbox-button-tooltip',
|
||||
buttonShortcut: 'ce-toolbox-button-tooltip__shortcut',
|
||||
};
|
||||
}
|
||||
|
||||
export default class Toolbox extends EventsDispatcher<ToolboxEvent> {
|
||||
/**
|
||||
* Returns True if Toolbox is Empty and nothing to show
|
||||
*
|
||||
|
@ -72,6 +55,44 @@ export default class Toolbox extends Module<ToolboxNodes> {
|
|||
*/
|
||||
public opened = false;
|
||||
|
||||
/**
|
||||
* Editor API
|
||||
*/
|
||||
private api: API;
|
||||
|
||||
/**
|
||||
* List of Tools available. Some of them will be shown in the Toolbox
|
||||
*/
|
||||
private tools: ToolsCollection<BlockTool>;
|
||||
|
||||
/**
|
||||
* Current module HTML Elements
|
||||
*/
|
||||
private nodes: {
|
||||
toolbox: HTMLElement;
|
||||
buttons: HTMLElement[];
|
||||
} = {
|
||||
toolbox: null,
|
||||
buttons: [],
|
||||
}
|
||||
|
||||
/**
|
||||
* CSS styles
|
||||
*
|
||||
* @returns {object.<string, string>}
|
||||
*/
|
||||
private static get CSS(): { [name: string]: string } {
|
||||
return {
|
||||
toolbox: 'ce-toolbox',
|
||||
toolboxButton: 'ce-toolbox__button',
|
||||
toolboxButtonActive: 'ce-toolbox__button--active',
|
||||
toolboxOpened: 'ce-toolbox--opened',
|
||||
|
||||
buttonTooltip: 'ce-toolbox-button-tooltip',
|
||||
buttonShortcut: 'ce-toolbox-button-tooltip__shortcut',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* How many tools displayed in Toolbox
|
||||
*
|
||||
|
@ -90,34 +111,53 @@ export default class Toolbox extends Module<ToolboxNodes> {
|
|||
* Tooltip utility Instance
|
||||
*/
|
||||
private tooltip: Tooltip;
|
||||
|
||||
/**
|
||||
* @class
|
||||
* @param {object} moduleConfiguration - Module Configuration
|
||||
* @param {EditorConfig} moduleConfiguration.config - Editor's config
|
||||
* @param {EventsDispatcher} moduleConfiguration.eventsDispatcher - Editor's event dispatcher
|
||||
* Id of listener added used to remove it on destroy()
|
||||
*/
|
||||
constructor({ config, eventsDispatcher }: ModuleConfig) {
|
||||
super({
|
||||
config,
|
||||
eventsDispatcher,
|
||||
});
|
||||
private clickListenerId: string = null;
|
||||
|
||||
/**
|
||||
* Toolbox constructor
|
||||
*
|
||||
* @param options - available parameters
|
||||
* @param options.api - Editor API methods
|
||||
* @param options.tools - Tools available to check whether some of them should be displayed at the Toolbox or not
|
||||
*/
|
||||
constructor({ api, tools }) {
|
||||
super();
|
||||
|
||||
this.api = api;
|
||||
this.tools = tools;
|
||||
|
||||
this.tooltip = new Tooltip();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the Toolbox has the Flipper activated and the Flipper has selected button
|
||||
*/
|
||||
public get flipperHasFocus(): boolean {
|
||||
return this.flipper && this.flipper.currentItem !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the Toolbox
|
||||
*/
|
||||
public make(): void {
|
||||
this.nodes.toolbox = $.make('div', this.CSS.toolbox);
|
||||
public make(): Element {
|
||||
this.nodes.toolbox = $.make('div', Toolbox.CSS.toolbox);
|
||||
|
||||
this.addTools();
|
||||
this.enableFlipper();
|
||||
|
||||
return this.nodes.toolbox;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy Module
|
||||
*/
|
||||
public destroy(): void {
|
||||
super.destroy();
|
||||
|
||||
/**
|
||||
* Sometimes (in read-only mode) there is no Flipper
|
||||
*/
|
||||
|
@ -126,7 +166,14 @@ export default class Toolbox extends Module<ToolboxNodes> {
|
|||
this.flipper = null;
|
||||
}
|
||||
|
||||
this.removeAllNodes();
|
||||
if (this.nodes && this.nodes.toolbox) {
|
||||
this.nodes.toolbox.remove();
|
||||
this.nodes.toolbox = null;
|
||||
this.nodes.buttons = [];
|
||||
}
|
||||
|
||||
this.api.listeners.offById(this.clickListenerId);
|
||||
|
||||
this.removeAllShortcuts();
|
||||
this.tooltip.destroy();
|
||||
}
|
||||
|
@ -149,8 +196,9 @@ export default class Toolbox extends Module<ToolboxNodes> {
|
|||
return;
|
||||
}
|
||||
|
||||
this.Editor.UI.nodes.wrapper.classList.add(this.CSS.openedToolbarHolderModifier);
|
||||
this.nodes.toolbox.classList.add(this.CSS.toolboxOpened);
|
||||
this.emit(ToolboxEvent.Opened);
|
||||
|
||||
this.nodes.toolbox.classList.add(Toolbox.CSS.toolboxOpened);
|
||||
|
||||
this.opened = true;
|
||||
this.flipper.activate();
|
||||
|
@ -160,8 +208,9 @@ export default class Toolbox extends Module<ToolboxNodes> {
|
|||
* Close Toolbox
|
||||
*/
|
||||
public close(): void {
|
||||
this.nodes.toolbox.classList.remove(this.CSS.toolboxOpened);
|
||||
this.Editor.UI.nodes.wrapper.classList.remove(this.CSS.openedToolbarHolderModifier);
|
||||
this.emit(ToolboxEvent.Closed);
|
||||
|
||||
this.nodes.toolbox.classList.remove(Toolbox.CSS.toolboxOpened);
|
||||
|
||||
this.opened = false;
|
||||
this.flipper.deactivate();
|
||||
|
@ -182,10 +231,8 @@ export default class Toolbox extends Module<ToolboxNodes> {
|
|||
* Iterates available tools and appends them to the Toolbox
|
||||
*/
|
||||
private addTools(): void {
|
||||
const tools = this.Editor.Tools.blockTools;
|
||||
|
||||
Array
|
||||
.from(tools.values())
|
||||
.from(this.tools.values())
|
||||
.forEach((tool) => this.addTool(tool));
|
||||
}
|
||||
|
||||
|
@ -218,7 +265,7 @@ export default class Toolbox extends Module<ToolboxNodes> {
|
|||
// return;
|
||||
// }
|
||||
|
||||
const button = $.make('li', [ this.CSS.toolboxButton ]);
|
||||
const button = $.make('li', [ Toolbox.CSS.toolboxButton ]);
|
||||
|
||||
button.dataset.tool = tool.name;
|
||||
button.innerHTML = toolToolboxSettings.icon;
|
||||
|
@ -231,7 +278,7 @@ export default class Toolbox extends Module<ToolboxNodes> {
|
|||
/**
|
||||
* Add click listener
|
||||
*/
|
||||
this.listeners.on(button, 'click', (event: KeyboardEvent|MouseEvent) => {
|
||||
this.clickListenerId = this.api.listeners.on(button, 'click', (event: KeyboardEvent|MouseEvent) => {
|
||||
this.toolButtonActivate(event, tool.name);
|
||||
});
|
||||
|
||||
|
@ -267,7 +314,7 @@ export default class Toolbox extends Module<ToolboxNodes> {
|
|||
|
||||
let shortcut = tool.shortcut;
|
||||
|
||||
const tooltip = $.make('div', this.CSS.buttonTooltip);
|
||||
const tooltip = $.make('div', Toolbox.CSS.buttonTooltip);
|
||||
const hint = document.createTextNode(_.capitalize(name));
|
||||
|
||||
tooltip.appendChild(hint);
|
||||
|
@ -275,7 +322,7 @@ export default class Toolbox extends Module<ToolboxNodes> {
|
|||
if (shortcut) {
|
||||
shortcut = _.beautifyShortcut(shortcut);
|
||||
|
||||
tooltip.appendChild($.make('div', this.CSS.buttonShortcut, {
|
||||
tooltip.appendChild($.make('div', Toolbox.CSS.buttonShortcut, {
|
||||
textContent: shortcut,
|
||||
}));
|
||||
}
|
||||
|
@ -292,11 +339,11 @@ export default class Toolbox extends Module<ToolboxNodes> {
|
|||
private enableShortcut(toolName: string, shortcut: string): void {
|
||||
Shortcuts.add({
|
||||
name: shortcut,
|
||||
on: this.api.ui.nodes.redactor,
|
||||
handler: (event: KeyboardEvent) => {
|
||||
event.preventDefault();
|
||||
this.insertNewBlock(toolName);
|
||||
},
|
||||
on: this.Editor.UI.nodes.redactor,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -305,15 +352,13 @@ export default class Toolbox extends Module<ToolboxNodes> {
|
|||
* Fired when the Read-Only mode is activated
|
||||
*/
|
||||
private removeAllShortcuts(): void {
|
||||
const tools = this.Editor.Tools.blockTools;
|
||||
|
||||
Array
|
||||
.from(tools.values())
|
||||
.from(this.tools.values())
|
||||
.forEach((tool) => {
|
||||
const shortcut = tool.shortcut;
|
||||
|
||||
if (shortcut) {
|
||||
Shortcuts.remove(this.Editor.UI.nodes.redactor, shortcut);
|
||||
Shortcuts.remove(this.api.ui.nodes.redactor, shortcut);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -326,7 +371,7 @@ export default class Toolbox extends Module<ToolboxNodes> {
|
|||
|
||||
this.flipper = new Flipper({
|
||||
items: tools,
|
||||
focusedItemClass: this.CSS.toolboxButtonActive,
|
||||
focusedItemClass: Toolbox.CSS.toolboxButtonActive,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -337,34 +382,42 @@ export default class Toolbox extends Module<ToolboxNodes> {
|
|||
* @param {string} toolName - Tool name
|
||||
*/
|
||||
private insertNewBlock(toolName: string): void {
|
||||
const { BlockManager, Caret } = this.Editor;
|
||||
const { currentBlock } = BlockManager;
|
||||
const currentBlockIndex = this.api.blocks.getCurrentBlockIndex();
|
||||
const currentBlock = this.api.blocks.getBlockByIndex(currentBlockIndex);
|
||||
|
||||
const newBlock = BlockManager.insert({
|
||||
tool: toolName,
|
||||
replace: currentBlock.isEmpty,
|
||||
});
|
||||
if (!currentBlock) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* On mobile version, we see the Plus Button even near non-empty blocks,
|
||||
* so if current block is not empty, add the new block below the current
|
||||
*/
|
||||
const index = currentBlock.isEmpty ? currentBlockIndex : currentBlockIndex + 1;
|
||||
|
||||
const newBlock = this.api.blocks.insert(
|
||||
toolName,
|
||||
undefined,
|
||||
undefined,
|
||||
index,
|
||||
undefined,
|
||||
currentBlock.isEmpty
|
||||
);
|
||||
|
||||
/**
|
||||
* Apply callback before inserting html
|
||||
*/
|
||||
newBlock.call(BlockToolAPI.APPEND_CALLBACK);
|
||||
|
||||
this.Editor.Caret.setToBlock(newBlock);
|
||||
this.api.caret.setToBlock(index);
|
||||
|
||||
/** If new block doesn't contain inpus, insert new paragraph above */
|
||||
if (newBlock.inputs.length === 0) {
|
||||
if (newBlock === BlockManager.lastBlock) {
|
||||
BlockManager.insertAtEnd();
|
||||
Caret.setToBlock(BlockManager.lastBlock);
|
||||
} else {
|
||||
Caret.setToBlock(BlockManager.nextBlock);
|
||||
}
|
||||
}
|
||||
this.emit(ToolboxEvent.BlockAdded, {
|
||||
block: newBlock,
|
||||
});
|
||||
|
||||
/**
|
||||
* close toolbar when node is changed
|
||||
*/
|
||||
this.Editor.Toolbar.close();
|
||||
this.api.toolbar.close();
|
||||
}
|
||||
}
|
|
@ -454,6 +454,68 @@ export function debounce(func: (...args: unknown[]) => void, wait?: number, imme
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a function, that, when invoked, will only be triggered at most once during a given window of time.
|
||||
*
|
||||
* @param func - function to throttle
|
||||
* @param wait - function will be called only once for that period
|
||||
* @param options - Normally, the throttled function will run as much as it can
|
||||
* without ever going more than once per `wait` duration;
|
||||
* but if you'd like to disable the execution on the leading edge, pass
|
||||
* `{leading: false}`. To disable execution on the trailing edge, ditto.
|
||||
*/
|
||||
export function throttle(func, wait, options: {leading?: boolean; trailing?: boolean} = undefined): () => void {
|
||||
let context, args, result;
|
||||
let timeout = null;
|
||||
let previous = 0;
|
||||
|
||||
if (!options) {
|
||||
options = {};
|
||||
}
|
||||
|
||||
const later = function (): void {
|
||||
previous = options.leading === false ? 0 : Date.now();
|
||||
timeout = null;
|
||||
result = func.apply(context, args);
|
||||
|
||||
if (!timeout) {
|
||||
context = args = null;
|
||||
}
|
||||
};
|
||||
|
||||
return function (): unknown {
|
||||
const now = Date.now();
|
||||
|
||||
if (!previous && options.leading === false) {
|
||||
previous = now;
|
||||
}
|
||||
|
||||
const remaining = wait - (now - previous);
|
||||
|
||||
context = this;
|
||||
|
||||
// eslint-disable-next-line prefer-rest-params
|
||||
args = arguments;
|
||||
|
||||
if (remaining <= 0 || remaining > wait) {
|
||||
if (timeout) {
|
||||
clearTimeout(timeout);
|
||||
timeout = null;
|
||||
}
|
||||
previous = now;
|
||||
result = func.apply(context, args);
|
||||
|
||||
if (!timeout) {
|
||||
context = args = null;
|
||||
}
|
||||
} else if (!timeout && options.trailing !== false) {
|
||||
timeout = setTimeout(later, remaining);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies passed text to the clipboard
|
||||
*
|
||||
|
|
|
@ -19,7 +19,7 @@ export default class EventsDispatcher<Events extends string = string> {
|
|||
*
|
||||
* @type {{}}
|
||||
*/
|
||||
private subscribers: {[name: string]: Array<(data?: object) => object>} = {};
|
||||
private subscribers: {[name: string]: Array<(data?: object) => unknown>} = {};
|
||||
|
||||
/**
|
||||
* Subscribe any event on callback
|
||||
|
@ -27,7 +27,7 @@ export default class EventsDispatcher<Events extends string = string> {
|
|||
* @param {string} eventName - event name
|
||||
* @param {Function} callback - subscriber
|
||||
*/
|
||||
public on(eventName: Events, callback: (data: object) => object): void {
|
||||
public on(eventName: Events, callback: (data: object) => unknown): void {
|
||||
if (!(eventName in this.subscribers)) {
|
||||
this.subscribers[eventName] = [];
|
||||
}
|
||||
|
@ -42,12 +42,12 @@ export default class EventsDispatcher<Events extends string = string> {
|
|||
* @param {string} eventName - event name
|
||||
* @param {Function} callback - subscriber
|
||||
*/
|
||||
public once(eventName: Events, callback: (data: object) => object): void {
|
||||
public once(eventName: Events, callback: (data: object) => unknown): void {
|
||||
if (!(eventName in this.subscribers)) {
|
||||
this.subscribers[eventName] = [];
|
||||
}
|
||||
|
||||
const wrappedCallback = (data: object): object => {
|
||||
const wrappedCallback = (data: object): unknown => {
|
||||
const result = callback(data);
|
||||
|
||||
const indexOfHandler = this.subscribers[eventName].indexOf(wrappedCallback);
|
||||
|
@ -87,7 +87,7 @@ export default class EventsDispatcher<Events extends string = string> {
|
|||
* @param {string} eventName - event name
|
||||
* @param {Function} callback - event handler
|
||||
*/
|
||||
public off(eventName: Events, callback: (data: object) => object): void {
|
||||
public off(eventName: Events, callback: (data: object) => unknown): void {
|
||||
for (let i = 0; i < this.subscribers[eventName].length; i++) {
|
||||
if (this.subscribers[eventName][i] === callback) {
|
||||
delete this.subscribers[eventName][i];
|
||||
|
@ -98,7 +98,7 @@ export default class EventsDispatcher<Events extends string = string> {
|
|||
|
||||
/**
|
||||
* Destroyer
|
||||
* clears subsribers list
|
||||
* clears subscribers list
|
||||
*/
|
||||
public destroy(): void {
|
||||
this.subscribers = null;
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
/**
|
||||
* Use external module CodeX Tooltip
|
||||
*/
|
||||
import CodeXTooltips, { TooltipContent, TooltipOptions } from 'codex-tooltip';
|
||||
import CodeXTooltips from 'codex-tooltip';
|
||||
import type { TooltipOptions, TooltipContent } from 'codex-tooltip/types';
|
||||
|
||||
/**
|
||||
* Tooltip
|
||||
|
@ -28,8 +29,8 @@ export default class Tooltip {
|
|||
* Shows tooltip on element with passed HTML content
|
||||
*
|
||||
* @param {HTMLElement} element - any HTML element in DOM
|
||||
* @param {TooltipContent} content - tooltip's content
|
||||
* @param {TooltipOptions} options - showing settings
|
||||
* @param content - tooltip's content
|
||||
* @param options - showing settings
|
||||
*/
|
||||
public show(element: HTMLElement, content: TooltipContent, options?: TooltipOptions): void {
|
||||
this.lib.show(element, content, options);
|
||||
|
@ -37,17 +38,19 @@ export default class Tooltip {
|
|||
|
||||
/**
|
||||
* Hides tooltip
|
||||
*
|
||||
* @param skipHidingDelay — pass true to immediately hide the tooltip
|
||||
*/
|
||||
public hide(): void {
|
||||
this.lib.hide();
|
||||
public hide(skipHidingDelay = false): void {
|
||||
this.lib.hide(skipHidingDelay);
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds 'mouseenter' and 'mouseleave' events that shows/hides the Tooltip
|
||||
*
|
||||
* @param {HTMLElement} element - any HTML element in DOM
|
||||
* @param {TooltipContent} content - tooltip's content
|
||||
* @param {TooltipOptions} options - showing settings
|
||||
* @param content - tooltip's content
|
||||
* @param options - showing settings
|
||||
*/
|
||||
public onHover(element: HTMLElement, content: TooltipContent, options?: TooltipOptions): void {
|
||||
this.lib.onHover(element, content, options);
|
||||
|
|
|
@ -2,7 +2,11 @@
|
|||
* Block Tool wrapper
|
||||
*/
|
||||
.cdx-block {
|
||||
padding: 0.4em 0;
|
||||
padding: var(--block-padding-vertical) 0;
|
||||
|
||||
&::-webkit-input-placeholder {
|
||||
line-height:normal!important;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
@media (--mobile){
|
||||
bottom: 40px;
|
||||
right: -11px;
|
||||
right: auto;
|
||||
top: auto;
|
||||
}
|
||||
|
||||
|
|
|
@ -3,8 +3,6 @@
|
|||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
/*opacity: 0;*/
|
||||
/*visibility: hidden;*/
|
||||
transition: opacity 100ms ease;
|
||||
will-change: opacity, transform;
|
||||
display: none;
|
||||
|
@ -32,15 +30,12 @@
|
|||
display: flex;
|
||||
align-content: center;
|
||||
margin: 0;
|
||||
max-width: calc(100% - 35px);
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&__plus {
|
||||
@apply --toolbox-button;
|
||||
|
||||
position: absolute;
|
||||
left: calc(var(--toolbox-buttons-size) * -1);
|
||||
flex-shrink: 0;
|
||||
|
||||
&-shortcut {
|
||||
|
@ -60,25 +55,19 @@
|
|||
}
|
||||
}
|
||||
|
||||
&__plus,
|
||||
.ce-toolbox {
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
/**
|
||||
* Block actions Zone
|
||||
* -------------------------
|
||||
*/
|
||||
&__actions {
|
||||
position: absolute;
|
||||
right: -30px;
|
||||
top: 5px;
|
||||
right: 100%;
|
||||
opacity: 0;
|
||||
display: flex;
|
||||
|
||||
@media (--mobile){
|
||||
position: absolute;
|
||||
right: -28px;
|
||||
right: auto;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
display: flex;
|
||||
|
@ -95,26 +84,19 @@
|
|||
}
|
||||
|
||||
&__settings-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@apply --toolbox-button;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
color: var(--grayText);
|
||||
margin: 0 5px;
|
||||
|
||||
cursor: pointer;
|
||||
background: var(--bg-light);
|
||||
user-select: none;
|
||||
|
||||
&:hover {
|
||||
color: var(--color-dark);
|
||||
}
|
||||
|
||||
@media (--mobile){
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.codex-editor--toolbox-opened .ce-toolbar__actions {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Styles for Narrow mode
|
||||
*/
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
&__button {
|
||||
@apply --toolbox-button;
|
||||
flex-shrink: 0;
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
/**
|
||||
* Gray icons hover
|
||||
*/
|
||||
--color-dark: #1D202B;
|
||||
--color-dark: #1D202B;
|
||||
|
||||
/**
|
||||
* Blue icons
|
||||
|
@ -52,7 +52,14 @@
|
|||
/**
|
||||
* Toolbar Plus Button and Toolbox buttons height and width
|
||||
*/
|
||||
--toolbox-buttons-size: 34px;
|
||||
--toolbox-buttons-size: 26px;
|
||||
--toolbox-buttons-size--mobile: 36px;
|
||||
|
||||
/**
|
||||
* The main `.cdx-block` wrapper has such vertical paddings
|
||||
* And the Block Actions toggler too
|
||||
*/
|
||||
--block-padding-vertical: 0.4em;
|
||||
|
||||
/**
|
||||
* Confirm deletion bg
|
||||
|
@ -62,14 +69,14 @@
|
|||
--overlay-pane: {
|
||||
position: absolute;
|
||||
background-color: #FFFFFF;
|
||||
border: 1px solid #EAEAEA;
|
||||
border: 1px solid #E8E8EB;
|
||||
box-shadow: 0 3px 15px -3px rgba(13,20,33,0.13);
|
||||
border-radius: 4px;
|
||||
border-radius: 6px;
|
||||
z-index: 2;
|
||||
|
||||
@media (--mobile){
|
||||
box-shadow: 0 13px 7px -5px rgba(26, 38, 49, 0.09),6px 15px 34px -6px rgba(33, 48, 73, 0.29);
|
||||
border-bottom-color: #d5d7db;
|
||||
box-shadow: 0 8px 6px -6px rgb(33 48 73 / 19%);
|
||||
border-bottom-color: #c7c7c7;
|
||||
}
|
||||
|
||||
&--left-oriented {
|
||||
|
@ -92,17 +99,23 @@
|
|||
* Styles for Toolbox Buttons and Plus Button
|
||||
*/
|
||||
--toolbox-button: {
|
||||
color: var(--grayText);
|
||||
color: var(--color-dark);
|
||||
cursor: pointer;
|
||||
width: var(--toolbox-buttons-size);
|
||||
height: var(--toolbox-buttons-size);
|
||||
border-radius: 3px;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
@media (--mobile){
|
||||
width: var(--toolbox-buttons-size--mobile);
|
||||
height: var(--toolbox-buttons-size--mobile);
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&--active {
|
||||
color: var(--color-active-icon);
|
||||
background-color: var(--bg-light);
|
||||
}
|
||||
|
||||
&--active{
|
||||
|
@ -152,3 +165,4 @@
|
|||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
|
4
src/types-internal/editor-modules.d.ts
vendored
4
src/types-internal/editor-modules.d.ts
vendored
|
@ -2,7 +2,6 @@ import UI from '../components/modules/ui';
|
|||
import BlockEvents from '../components/modules/blockEvents';
|
||||
import Toolbar from '../components/modules/toolbar/index';
|
||||
import InlineToolbar from '../components/modules/toolbar/inline';
|
||||
import Toolbox from '../components/modules/toolbar/toolbox';
|
||||
import BlockSettings from '../components/modules/toolbar/blockSettings';
|
||||
import Paste from '../components/modules/paste';
|
||||
import DragNDrop from '../components/modules/dragNDrop';
|
||||
|
@ -31,6 +30,7 @@ import TooltipAPI from '../components/modules/api/tooltip';
|
|||
import ReadOnly from '../components/modules/readonly';
|
||||
import ReadOnlyAPI from '../components/modules/api/readonly';
|
||||
import I18nAPI from '../components/modules/api/i18n';
|
||||
import UiAPI from '../components/modules/api/ui';
|
||||
import ModificationsObserver from '../components/modules/modificationsObserver';
|
||||
|
||||
export interface EditorModules {
|
||||
|
@ -40,7 +40,6 @@ export interface EditorModules {
|
|||
RectangleSelection: RectangleSelection;
|
||||
Toolbar: Toolbar;
|
||||
InlineToolbar: InlineToolbar;
|
||||
Toolbox: Toolbox;
|
||||
BlockSettings: BlockSettings;
|
||||
ConversionToolbar: ConversionToolbar;
|
||||
Paste: Paste;
|
||||
|
@ -67,5 +66,6 @@ export interface EditorModules {
|
|||
ReadOnly: ReadOnly;
|
||||
ReadOnlyAPI: ReadOnlyAPI;
|
||||
I18nAPI: I18nAPI;
|
||||
UiAPI: UiAPI;
|
||||
ModificationsObserver: ModificationsObserver;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import Header from '../../../example/tools/header';
|
||||
import Header from '@editorjs/header';
|
||||
import { nanoid } from 'nanoid';
|
||||
|
||||
describe.only('Block ids', () => {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import Header from '../../../example/tools/header';
|
||||
import Image from '../../../example/tools/simple-image';
|
||||
import Header from '@editorjs/header';
|
||||
import Image from '@editorjs/simple-image';
|
||||
import * as _ from '../../../src/components/utils';
|
||||
|
||||
describe('Copy pasting from Editor', () => {
|
||||
|
|
|
@ -96,7 +96,10 @@ describe('Tools module', () => {
|
|||
|
||||
await module.prepare();
|
||||
|
||||
expect(WithSuccessfulPrepare.prepare).to.be.calledWithExactly({ toolName: 'withSuccessfulPrepare', config });
|
||||
expect(WithSuccessfulPrepare.prepare).to.be.calledWithExactly({
|
||||
toolName: 'withSuccessfulPrepare',
|
||||
config,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import Header from '../../../example/tools/header';
|
||||
import Header from '@editorjs/header';
|
||||
import { BlockMutationType } from '../../../types/events/block/mutation-type';
|
||||
|
||||
/**
|
||||
|
|
11
types/api/blocks.d.ts
vendored
11
types/api/blocks.d.ts
vendored
|
@ -66,6 +66,11 @@ export interface Blocks {
|
|||
*/
|
||||
getCurrentBlockIndex(): number;
|
||||
|
||||
/**
|
||||
* Returns the index of Block by id;
|
||||
*/
|
||||
getBlockIndex(blockId: string): number;
|
||||
|
||||
/**
|
||||
* Mark Block as stretched
|
||||
* @param {number} index - Block to mark
|
||||
|
@ -89,13 +94,14 @@ export interface Blocks {
|
|||
insertNewBlock(): void;
|
||||
|
||||
/**
|
||||
* Insert new Block
|
||||
* Insert new Block and return inserted Block API
|
||||
*
|
||||
* @param {string} type — Tool name
|
||||
* @param {BlockToolData} data — Tool data to insert
|
||||
* @param {ToolConfig} config — Tool config
|
||||
* @param {number?} index — index where to insert new Block
|
||||
* @param {boolean?} needToFocus - flag to focus inserted Block
|
||||
* @param {boolean?} replace - should the existed Block on that index be replaced or not
|
||||
*/
|
||||
insert(
|
||||
type?: string,
|
||||
|
@ -103,7 +109,8 @@ export interface Blocks {
|
|||
config?: ToolConfig,
|
||||
index?: number,
|
||||
needToFocus?: boolean,
|
||||
): void;
|
||||
replace?: boolean,
|
||||
): BlockAPI;
|
||||
|
||||
|
||||
/**
|
||||
|
|
1
types/api/index.d.ts
vendored
1
types/api/index.d.ts
vendored
|
@ -13,3 +13,4 @@ export * from './inline-toolbar';
|
|||
export * from './block';
|
||||
export * from './readonly';
|
||||
export * from './i18n';
|
||||
export * from './ui';
|
||||
|
|
12
types/api/listeners.d.ts
vendored
12
types/api/listeners.d.ts
vendored
|
@ -3,14 +3,14 @@
|
|||
*/
|
||||
export interface Listeners {
|
||||
/**
|
||||
* Subscribe to event dispatched on passed element
|
||||
* Subscribe to event dispatched on passed element. Returns listener id.
|
||||
*
|
||||
* @param {Element} element
|
||||
* @param {string} eventType
|
||||
* @param {(event: Event) => void}handler
|
||||
* @param {boolean} useCapture
|
||||
*/
|
||||
on(element: Element, eventType: string, handler: (event?: Event) => void, useCapture?: boolean): void;
|
||||
on(element: Element, eventType: string, handler: (event?: Event) => void, useCapture?: boolean): string;
|
||||
|
||||
/**
|
||||
* Unsubscribe from event dispatched on passed element
|
||||
|
@ -21,4 +21,12 @@ export interface Listeners {
|
|||
* @param {boolean} useCapture
|
||||
*/
|
||||
off(element: Element, eventType: string, handler: (event?: Event) => void, useCapture?: boolean): void;
|
||||
|
||||
|
||||
/**
|
||||
* Unsubscribe from event dispatched by the listener id
|
||||
*
|
||||
* @param id - id of the listener to remove
|
||||
*/
|
||||
offById(id: string): void;
|
||||
}
|
||||
|
|
24
types/api/ui.d.ts
vendored
Normal file
24
types/api/ui.d.ts
vendored
Normal file
|
@ -0,0 +1,24 @@
|
|||
/**
|
||||
* Describes API module allowing to access some Editor UI elements and methods
|
||||
*/
|
||||
export interface Ui {
|
||||
/**
|
||||
* Allows accessing some Editor UI elements
|
||||
*/
|
||||
nodes: UiNodes,
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows accessing some Editor UI elements
|
||||
*/
|
||||
export interface UiNodes {
|
||||
/**
|
||||
* Top-level editor instance wrapper
|
||||
*/
|
||||
wrapper: HTMLElement,
|
||||
|
||||
/**
|
||||
* Element that holds all the Blocks
|
||||
*/
|
||||
redactor: HTMLElement,
|
||||
}
|
2
types/index.d.ts
vendored
2
types/index.d.ts
vendored
|
@ -27,6 +27,7 @@ import {
|
|||
Toolbar,
|
||||
Tooltip,
|
||||
I18n,
|
||||
Ui,
|
||||
} from './api';
|
||||
|
||||
import { OutputData } from './data-formats';
|
||||
|
@ -92,6 +93,7 @@ export interface API {
|
|||
tooltip: Tooltip;
|
||||
i18n: I18n;
|
||||
readOnly: ReadOnly;
|
||||
ui: Ui;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in a new issue