mirror of
https://github.com/codex-team/editor.js
synced 2026-03-15 15:15:47 +01:00
Compare commits
No commits in common. "next" and "v2.31.0-rc.10" have entirely different histories.
next
...
v2.31.0-rc
18 changed files with 44 additions and 298 deletions
|
|
@ -17,7 +17,7 @@ jobs:
|
|||
# If pull request was merged then we should check for a package version update
|
||||
check-for-no-version-changing:
|
||||
if: github.event.pull_request.merged == true
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-20.04
|
||||
permissions:
|
||||
actions: write
|
||||
steps:
|
||||
|
|
|
|||
2
.github/workflows/create-a-release-draft.yml
vendored
2
.github/workflows/create-a-release-draft.yml
vendored
|
|
@ -17,7 +17,7 @@ jobs:
|
|||
# If pull request was merged then we should check for a package version update
|
||||
check-version-changing:
|
||||
if: github.event.pull_request.merged == true
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-20.04
|
||||
permissions:
|
||||
actions: write
|
||||
steps:
|
||||
|
|
|
|||
11
.github/workflows/cypress.yml
vendored
11
.github/workflows/cypress.yml
vendored
|
|
@ -8,19 +8,12 @@ jobs:
|
|||
matrix:
|
||||
browser: [firefox, chrome, edge]
|
||||
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
- name: Setup Firefox
|
||||
if: matrix.browser == 'firefox'
|
||||
uses: browser-actions/setup-firefox@v1
|
||||
with:
|
||||
firefox-version: '115.0esr'
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
- uses: cypress-io/github-action@v6
|
||||
with:
|
||||
config: video=false
|
||||
|
|
|
|||
6
.github/workflows/eslint.yml
vendored
6
.github/workflows/eslint.yml
vendored
|
|
@ -5,14 +5,10 @@ on: [pull_request]
|
|||
jobs:
|
||||
lint:
|
||||
name: ESlint
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
|
|
|
|||
2
.github/workflows/publish-package-to-npm.yml
vendored
2
.github/workflows/publish-package-to-npm.yml
vendored
|
|
@ -7,7 +7,7 @@ on:
|
|||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
# Checkout to target branch
|
||||
- uses: actions/checkout@v4
|
||||
|
|
|
|||
1
.nvmrc
1
.nvmrc
|
|
@ -1 +0,0 @@
|
|||
v18.20.1
|
||||
|
|
@ -1,25 +1,5 @@
|
|||
# Changelog
|
||||
|
||||
### 2.31.5
|
||||
|
||||
- `Fix` - Handle __Ctrl + click__ on links with inline styles applied (e.g., bold, italic)
|
||||
|
||||
### 2.31.4
|
||||
|
||||
- `Fix` - Prevent inline-toolbar re-renders when linked text is selected
|
||||
|
||||
### 2.31.3
|
||||
|
||||
- `Fix` - Prevent text formatting removal when applying link
|
||||
|
||||
### 2.31.2
|
||||
|
||||
- `Fix` - Prevent link removal when applying bold to linked text
|
||||
|
||||
### 2.31.1
|
||||
|
||||
- `Fix` - Prevent the warning from appearing when `readOnly` mode is initially set to `true`
|
||||
|
||||
### 2.31.0
|
||||
|
||||
- `New` - Inline tools (those with `isReadOnlySupported` specified) can now be used in read-only mode
|
||||
|
|
@ -36,11 +16,6 @@
|
|||
- `DX` - Tools submodules removed from the repository
|
||||
- `Improvement` - Shift + Down/Up will allow to select next/previous line instead of Inline Toolbar flipping
|
||||
- `Improvement` - The API `caret.setToBlock()` offset now works across the entire block content, not just the first or last node.
|
||||
- `Improvement` - The API `blocks.renderFromHTML()` became async and now can be awaited.
|
||||
- `Fix` - `blocks.renderFromHTML()` — Error "Can't find a Block to remove." fixed
|
||||
- `Fix` - The API `.clear()` index invalidation fixed
|
||||
|
||||
|
||||
|
||||
### 2.30.7
|
||||
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@
|
|||
<script src="https://cdn.jsdelivr.net/npm/@editorjs/image@latest"></script><!-- Image -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/@editorjs/delimiter@latest"></script><!-- Delimiter -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/@editorjs/list@latest"></script><!-- List -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/@editorjs/checklist@latest"></script><!-- Checklist -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/@editorjs/quote@latest"></script><!-- Quote -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/@editorjs/code@latest"></script><!-- Code -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/@editorjs/embed@latest"></script><!-- Embed -->
|
||||
|
|
@ -106,11 +107,16 @@
|
|||
image: ImageTool,
|
||||
|
||||
list: {
|
||||
class: EditorjsList,
|
||||
class: List,
|
||||
inlineToolbar: true,
|
||||
shortcut: 'CMD+SHIFT+L'
|
||||
},
|
||||
|
||||
checklist: {
|
||||
class: Checklist,
|
||||
inlineToolbar: true,
|
||||
},
|
||||
|
||||
quote: {
|
||||
class: Quote,
|
||||
inlineToolbar: true,
|
||||
|
|
@ -192,11 +198,7 @@
|
|||
},
|
||||
"popover": {
|
||||
"Filter": "Поиск",
|
||||
"Nothing found": "Ничего не найдено",
|
||||
/**
|
||||
* Translation of "Convert To" at the Block Tunes Popover
|
||||
*/
|
||||
"Convert to": "Конвертировать в",
|
||||
"Nothing found": "Ничего не найдено"
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -206,8 +208,7 @@
|
|||
"toolNames": {
|
||||
"Text": "Параграф",
|
||||
"Heading": "Заголовок",
|
||||
"Ordered List": "Нумерованный список",
|
||||
"Unordered List": "Маркированный список",
|
||||
"List": "Список",
|
||||
"Warning": "Примечание",
|
||||
"Checklist": "Чеклист",
|
||||
"Quote": "Цитата",
|
||||
|
|
@ -220,7 +221,7 @@
|
|||
"Bold": "Полужирный",
|
||||
"Italic": "Курсив",
|
||||
"InlineCode": "Моноширинный",
|
||||
"Image": "Картинка",
|
||||
"Image": "Картинка"
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
@ -265,12 +266,7 @@
|
|||
"Wrong response format from the server": "Неполадки на сервере",
|
||||
},
|
||||
"header": {
|
||||
"Heading 1": "Заголовок 1",
|
||||
"Heading 2": "Заголовок 2",
|
||||
"Heading 3": "Заголовок 3",
|
||||
"Heading 4": "Заголовок 4",
|
||||
"Heading 5": "Заголовок 5",
|
||||
"Heading 6": "Заголовок 6",
|
||||
"Header": "Заголовок",
|
||||
},
|
||||
"paragraph": {
|
||||
"Enter something": "Введите текст"
|
||||
|
|
@ -278,14 +274,7 @@
|
|||
"list": {
|
||||
"Ordered": "Нумерованный",
|
||||
"Unordered": "Маркированный",
|
||||
"Checklist": "Чеклист",
|
||||
},
|
||||
/**
|
||||
* Translation of "Convert To" at the Inline Toolbar hint
|
||||
*/
|
||||
"convertTo": {
|
||||
"Convert to": "Конвертировать в"
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
@ -299,15 +288,14 @@
|
|||
* Also, there are few internal block tunes: "delete", "moveUp" and "moveDown"
|
||||
*/
|
||||
"delete": {
|
||||
"Delete": "Удалить",
|
||||
"Click to delete": "Подтвердить удаление"
|
||||
"Delete": "Удалить"
|
||||
},
|
||||
"moveUp": {
|
||||
"Move up": "Переместить вверх"
|
||||
},
|
||||
"moveDown": {
|
||||
"Move down": "Переместить вниз"
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -120,6 +120,12 @@
|
|||
inlineToolbar: ['link'],
|
||||
},
|
||||
|
||||
list: {
|
||||
class: List,
|
||||
inlineToolbar: true,
|
||||
shortcut: 'CMD+SHIFT+L'
|
||||
},
|
||||
|
||||
checklist: {
|
||||
class: Checklist,
|
||||
inlineToolbar: true,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@editorjs/editorjs",
|
||||
"version": "2.31.5",
|
||||
"version": "2.31.0-rc.10",
|
||||
"description": "Editor.js — open source block-style WYSIWYG editor with JSON output",
|
||||
"main": "dist/editorjs.umd.js",
|
||||
"module": "dist/editorjs.mjs",
|
||||
|
|
|
|||
|
|
@ -566,16 +566,6 @@ export default class Dom {
|
|||
return element.tagName.toLowerCase() === 'a';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the closest ancestor anchor (A tag) of the given element (including itself)
|
||||
*
|
||||
* @param element - element to check
|
||||
* @returns {HTMLAnchorElement | null}
|
||||
*/
|
||||
public static getClosestAnchor(element: Element): HTMLAnchorElement | null {
|
||||
return element.closest("a");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return element's offset related to the document
|
||||
*
|
||||
|
|
@ -633,9 +623,6 @@ export default class Dom {
|
|||
|
||||
/**
|
||||
* If no node found or last node is empty, return null
|
||||
* - The root node has no text nodes at all
|
||||
* - The TreeWalker couldn't find any text nodes in the DOM tree
|
||||
* - The root node itself is null or invalid
|
||||
*/
|
||||
if (!lastTextNode) {
|
||||
return {
|
||||
|
|
@ -646,14 +633,6 @@ export default class Dom {
|
|||
|
||||
const textContent = lastTextNode.textContent;
|
||||
|
||||
/**
|
||||
* - The text node exists but has no content (textContent is null)
|
||||
* - The text node exists but has empty content (textContent.length === 0)
|
||||
* This could be due to:
|
||||
* - Empty text nodes (<span></span>)
|
||||
* - Nodes with only whitespace
|
||||
* - Nodes that were cleared but not removed
|
||||
*/
|
||||
if (textContent === null || textContent.length === 0) {
|
||||
return {
|
||||
node: null,
|
||||
|
|
|
|||
|
|
@ -172,21 +172,11 @@ export default class LinkInlineTool implements InlineTool {
|
|||
* Unlink icon pressed
|
||||
*/
|
||||
if (parentAnchor) {
|
||||
/**
|
||||
* If input is not opened, treat click as explicit unlink action.
|
||||
* If input is opened (e.g., programmatic close when switching tools), avoid unlinking.
|
||||
*/
|
||||
if (!this.inputOpened) {
|
||||
this.selection.expandToTag(parentAnchor);
|
||||
this.unlink();
|
||||
this.closeActions();
|
||||
this.checkState();
|
||||
this.toolbar.close();
|
||||
} else {
|
||||
/** Only close actions without clearing saved selection to preserve user state */
|
||||
this.closeActions(false);
|
||||
this.checkState();
|
||||
}
|
||||
this.selection.expandToTag(parentAnchor);
|
||||
this.unlink();
|
||||
this.closeActions();
|
||||
this.checkState();
|
||||
this.toolbar.close();
|
||||
|
||||
return;
|
||||
}
|
||||
|
|
@ -212,7 +202,7 @@ export default class LinkInlineTool implements InlineTool {
|
|||
*/
|
||||
const hrefAttr = anchorTag.getAttribute('href');
|
||||
|
||||
this.nodes.input.defaultValue = hrefAttr !== 'null' ? hrefAttr : '';
|
||||
this.nodes.input.value = hrefAttr !== 'null' ? hrefAttr : '';
|
||||
|
||||
this.selection.save();
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -224,8 +224,8 @@ export default class BlocksAPI extends Module {
|
|||
* @param {string} data - HTML string to render
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public async renderFromHTML(data: string): Promise<void> {
|
||||
await this.Editor.BlockManager.clear();
|
||||
public renderFromHTML(data: string): Promise<void> {
|
||||
this.Editor.BlockManager.clear();
|
||||
|
||||
return this.Editor.Paste.processText(data, true);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -533,8 +533,8 @@ export default class BlockManager extends Module {
|
|||
throw new Error('Can\'t find a Block to remove');
|
||||
}
|
||||
|
||||
this._blocks.remove(index);
|
||||
block.destroy();
|
||||
this._blocks.remove(index);
|
||||
|
||||
/**
|
||||
* Force call of didMutated event on Block removal
|
||||
|
|
@ -894,10 +894,7 @@ export default class BlockManager extends Module {
|
|||
public async clear(needToAddDefaultBlock = false): Promise<void> {
|
||||
const queue = new PromiseQueue();
|
||||
|
||||
// Create a copy of the blocks array to avoid issues with array modification during iteration
|
||||
const blocksToRemove = [...this.blocks];
|
||||
|
||||
blocksToRemove.forEach((block) => {
|
||||
this.blocks.forEach((block) => {
|
||||
queue.add(async () => {
|
||||
await this.removeBlock(block, false);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -68,11 +68,6 @@ export default class BlockSettings extends Module<BlockSettingsNodes> {
|
|||
return 'flipper' in this.popover ? this.popover?.flipper : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flag that indicates whether the `EditorMobileLayoutToggled` event listener is attached.
|
||||
*/
|
||||
private hasMobileLayoutToggleListener = false;
|
||||
|
||||
/**
|
||||
* Page selection utils
|
||||
*/
|
||||
|
|
@ -97,7 +92,6 @@ export default class BlockSettings extends Module<BlockSettingsNodes> {
|
|||
}
|
||||
|
||||
this.eventsDispatcher.on(EditorMobileLayoutToggled, this.close);
|
||||
this.hasMobileLayoutToggleListener = true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -106,11 +100,7 @@ export default class BlockSettings extends Module<BlockSettingsNodes> {
|
|||
public destroy(): void {
|
||||
this.removeAllNodes();
|
||||
this.listeners.destroy();
|
||||
|
||||
if (this.hasMobileLayoutToggleListener) {
|
||||
this.eventsDispatcher.off(EditorMobileLayoutToggled, this.close);
|
||||
this.hasMobileLayoutToggleListener = false;
|
||||
}
|
||||
this.eventsDispatcher.off(EditorMobileLayoutToggled, this.close);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -773,13 +773,12 @@ export default class UI extends Module<UINodes> {
|
|||
*/
|
||||
const element = event.target as Element;
|
||||
const ctrlKey = event.metaKey || event.ctrlKey;
|
||||
const anchor = $.getClosestAnchor(element);
|
||||
|
||||
if (anchor && ctrlKey) {
|
||||
|
||||
if ($.isAnchor(element) && ctrlKey) {
|
||||
event.stopImmediatePropagation();
|
||||
event.stopPropagation();
|
||||
|
||||
const href = anchor.getAttribute('href');
|
||||
const href = element.getAttribute('href');
|
||||
const validUrl = _.getValidUrl(href);
|
||||
|
||||
_.openTab(validUrl);
|
||||
|
|
|
|||
|
|
@ -57,9 +57,10 @@ export default class SelectionUtils {
|
|||
public isFakeBackgroundEnabled = false;
|
||||
|
||||
/**
|
||||
* Native Document's command for fake background
|
||||
* Native Document's commands for fake background
|
||||
*/
|
||||
private readonly commandBackground: string = 'backColor';
|
||||
private readonly commandRemoveFormat: string = 'removeFormat';
|
||||
|
||||
/**
|
||||
* Editor styles
|
||||
|
|
@ -415,9 +416,9 @@ export default class SelectionUtils {
|
|||
if (!this.isFakeBackgroundEnabled) {
|
||||
return;
|
||||
}
|
||||
document.execCommand(this.commandBackground, false, 'transparent');
|
||||
|
||||
this.isFakeBackgroundEnabled = false;
|
||||
document.execCommand(this.commandRemoveFormat);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -71,171 +71,4 @@ describe('Inline Tool Link', () => {
|
|||
.find('.ce-paragraph span[style]')
|
||||
.should('not.exist');
|
||||
});
|
||||
|
||||
it('should preserve link when applying bold to linked text', () => {
|
||||
cy.createEditor({
|
||||
data: {
|
||||
blocks: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
data: {
|
||||
text: 'Text with link',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
cy.get('[data-cy=editorjs]')
|
||||
.find('.ce-paragraph')
|
||||
.selectText('Text with link');
|
||||
|
||||
cy.get('[data-cy=editorjs]')
|
||||
.find('[data-item-name=link]')
|
||||
.click();
|
||||
|
||||
cy.get('[data-cy=editorjs]')
|
||||
.find('.ce-inline-tool-input')
|
||||
.type('https://editorjs.io')
|
||||
.type('{enter}');
|
||||
|
||||
cy.get('[data-cy=editorjs]')
|
||||
.find('div.ce-block')
|
||||
.find('a')
|
||||
.should('have.attr', 'href', 'https://editorjs.io');
|
||||
|
||||
cy.get('[data-cy=editorjs]')
|
||||
.find('div.ce-block')
|
||||
.find('a')
|
||||
.selectText('Text with link');
|
||||
|
||||
cy.get('[data-cy=editorjs]')
|
||||
.find('[data-item-name=bold]')
|
||||
.click();
|
||||
|
||||
cy.get('[data-cy=editorjs]')
|
||||
.find('div.ce-block')
|
||||
.find('a')
|
||||
.should('have.attr', 'href', 'https://editorjs.io')
|
||||
.find('b')
|
||||
.should('exist')
|
||||
.should('contain', 'Text with link');
|
||||
});
|
||||
|
||||
it('should preserve bold and italic when applying link', () => {
|
||||
cy.createEditor({
|
||||
data: {
|
||||
blocks: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
data: {
|
||||
text: 'Bold and italic text',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
cy.get('[data-cy=editorjs]')
|
||||
.find('.ce-paragraph')
|
||||
.selectText('Bold and italic text');
|
||||
|
||||
cy.get('[data-cy=editorjs]')
|
||||
.find('[data-item-name=bold]')
|
||||
.click();
|
||||
|
||||
cy.get('[data-cy=editorjs]')
|
||||
.find('div.ce-block')
|
||||
.find('b')
|
||||
.should('exist')
|
||||
.should('contain', 'Bold and italic text');
|
||||
|
||||
cy.get('[data-cy=editorjs]')
|
||||
.find('div.ce-block')
|
||||
.find('b')
|
||||
.selectText('Bold and italic text');
|
||||
|
||||
cy.get('[data-cy=editorjs]')
|
||||
.find('[data-item-name=italic]')
|
||||
.click();
|
||||
|
||||
cy.get('[data-cy=editorjs]')
|
||||
.find('div.ce-block')
|
||||
.find('b')
|
||||
.should('exist')
|
||||
.find('i')
|
||||
.should('exist')
|
||||
.should('contain', 'Bold and italic text');
|
||||
|
||||
cy.get('[data-cy=editorjs]')
|
||||
.find('div.ce-block')
|
||||
.find('b')
|
||||
.find('i')
|
||||
.selectText('Bold and italic text');
|
||||
|
||||
cy.get('[data-cy=editorjs]')
|
||||
.find('[data-item-name=link]')
|
||||
.click();
|
||||
|
||||
cy.get('[data-cy=editorjs]')
|
||||
.find('.ce-inline-tool-input')
|
||||
.type('https://editorjs.io')
|
||||
.type('{enter}');
|
||||
|
||||
cy.get('[data-cy=editorjs]')
|
||||
.find('div.ce-block')
|
||||
.find('b')
|
||||
.should('exist')
|
||||
.find('i')
|
||||
.should('exist')
|
||||
.find('a')
|
||||
.should('have.attr', 'href', 'https://editorjs.io')
|
||||
.should('contain', 'Bold and italic text');
|
||||
});
|
||||
|
||||
it('should open a link if it is wrapped in another formatting', () => {
|
||||
cy.createEditor({
|
||||
data: {
|
||||
blocks: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
data: {
|
||||
text: 'Link text',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
cy.get('[data-cy=editorjs]')
|
||||
.find('.ce-paragraph')
|
||||
.selectText('Link text');
|
||||
|
||||
cy.get('[data-cy=editorjs]')
|
||||
.find('[data-item-name=link]')
|
||||
.click();
|
||||
|
||||
cy.get('[data-cy=editorjs]')
|
||||
.find('.ce-inline-tool-input')
|
||||
.type('https://test.io/')
|
||||
.type('{enter}');
|
||||
|
||||
cy.get('[data-cy=editorjs]')
|
||||
.find('div.ce-block')
|
||||
.find('a')
|
||||
.selectText('Link text');
|
||||
|
||||
cy.get('[data-cy=editorjs]')
|
||||
.find('[data-item-name=italic]')
|
||||
.click();
|
||||
|
||||
cy.window().then((win) => {
|
||||
cy.stub(win, 'open').as('windowOpen');
|
||||
});
|
||||
|
||||
cy.contains('[data-cy=editorjs] div.ce-block i', 'Link text')
|
||||
.click({ ctrlKey: true });
|
||||
|
||||
cy.get('@windowOpen').should('be.calledWith', 'https://test.io/');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue