mirror of
https://github.com/codex-team/editor.js
synced 2026-03-16 15:45:47 +01:00
Compare commits
4 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
530ec56bb8 |
||
|
|
b69aa1ed25 |
||
|
|
a89f3d0eda |
||
|
|
cc624cad2e |
9 changed files with 170 additions and 24 deletions
|
|
@ -1,5 +1,17 @@
|
||||||
# Changelog
|
# 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
|
### 2.31.2
|
||||||
|
|
||||||
- `Fix` - Prevent link removal when applying bold to linked text
|
- `Fix` - Prevent link removal when applying bold to linked text
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,6 @@
|
||||||
<script src="https://cdn.jsdelivr.net/npm/@editorjs/image@latest"></script><!-- Image -->
|
<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/delimiter@latest"></script><!-- Delimiter -->
|
||||||
<script src="https://cdn.jsdelivr.net/npm/@editorjs/list@latest"></script><!-- List -->
|
<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/quote@latest"></script><!-- Quote -->
|
||||||
<script src="https://cdn.jsdelivr.net/npm/@editorjs/code@latest"></script><!-- Code -->
|
<script src="https://cdn.jsdelivr.net/npm/@editorjs/code@latest"></script><!-- Code -->
|
||||||
<script src="https://cdn.jsdelivr.net/npm/@editorjs/embed@latest"></script><!-- Embed -->
|
<script src="https://cdn.jsdelivr.net/npm/@editorjs/embed@latest"></script><!-- Embed -->
|
||||||
|
|
@ -112,11 +111,6 @@
|
||||||
shortcut: 'CMD+SHIFT+L'
|
shortcut: 'CMD+SHIFT+L'
|
||||||
},
|
},
|
||||||
|
|
||||||
checklist: {
|
|
||||||
class: Checklist,
|
|
||||||
inlineToolbar: true,
|
|
||||||
},
|
|
||||||
|
|
||||||
quote: {
|
quote: {
|
||||||
class: Quote,
|
class: Quote,
|
||||||
inlineToolbar: true,
|
inlineToolbar: true,
|
||||||
|
|
@ -202,7 +196,7 @@
|
||||||
/**
|
/**
|
||||||
* Translation of "Convert To" at the Block Tunes Popover
|
* Translation of "Convert To" at the Block Tunes Popover
|
||||||
*/
|
*/
|
||||||
"Convert to": "Конвертировать в"
|
"Convert to": "Конвертировать в",
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -212,7 +206,8 @@
|
||||||
"toolNames": {
|
"toolNames": {
|
||||||
"Text": "Параграф",
|
"Text": "Параграф",
|
||||||
"Heading": "Заголовок",
|
"Heading": "Заголовок",
|
||||||
"List": "Список",
|
"Ordered List": "Нумерованный список",
|
||||||
|
"Unordered List": "Маркированный список",
|
||||||
"Warning": "Примечание",
|
"Warning": "Примечание",
|
||||||
"Checklist": "Чеклист",
|
"Checklist": "Чеклист",
|
||||||
"Quote": "Цитата",
|
"Quote": "Цитата",
|
||||||
|
|
@ -270,7 +265,12 @@
|
||||||
"Wrong response format from the server": "Неполадки на сервере",
|
"Wrong response format from the server": "Неполадки на сервере",
|
||||||
},
|
},
|
||||||
"header": {
|
"header": {
|
||||||
"Header": "Заголовок",
|
"Heading 1": "Заголовок 1",
|
||||||
|
"Heading 2": "Заголовок 2",
|
||||||
|
"Heading 3": "Заголовок 3",
|
||||||
|
"Heading 4": "Заголовок 4",
|
||||||
|
"Heading 5": "Заголовок 5",
|
||||||
|
"Heading 6": "Заголовок 6",
|
||||||
},
|
},
|
||||||
"paragraph": {
|
"paragraph": {
|
||||||
"Enter something": "Введите текст"
|
"Enter something": "Введите текст"
|
||||||
|
|
@ -278,6 +278,7 @@
|
||||||
"list": {
|
"list": {
|
||||||
"Ordered": "Нумерованный",
|
"Ordered": "Нумерованный",
|
||||||
"Unordered": "Маркированный",
|
"Unordered": "Маркированный",
|
||||||
|
"Checklist": "Чеклист",
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Translation of "Convert To" at the Inline Toolbar hint
|
* Translation of "Convert To" at the Inline Toolbar hint
|
||||||
|
|
@ -298,7 +299,8 @@
|
||||||
* Also, there are few internal block tunes: "delete", "moveUp" and "moveDown"
|
* Also, there are few internal block tunes: "delete", "moveUp" and "moveDown"
|
||||||
*/
|
*/
|
||||||
"delete": {
|
"delete": {
|
||||||
"Delete": "Удалить"
|
"Delete": "Удалить",
|
||||||
|
"Click to delete": "Подтвердить удаление"
|
||||||
},
|
},
|
||||||
"moveUp": {
|
"moveUp": {
|
||||||
"Move up": "Переместить вверх"
|
"Move up": "Переместить вверх"
|
||||||
|
|
|
||||||
|
|
@ -120,12 +120,6 @@
|
||||||
inlineToolbar: ['link'],
|
inlineToolbar: ['link'],
|
||||||
},
|
},
|
||||||
|
|
||||||
list: {
|
|
||||||
class: List,
|
|
||||||
inlineToolbar: true,
|
|
||||||
shortcut: 'CMD+SHIFT+L'
|
|
||||||
},
|
|
||||||
|
|
||||||
checklist: {
|
checklist: {
|
||||||
class: Checklist,
|
class: Checklist,
|
||||||
inlineToolbar: true,
|
inlineToolbar: true,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@editorjs/editorjs",
|
"name": "@editorjs/editorjs",
|
||||||
"version": "2.31.2",
|
"version": "2.31.5",
|
||||||
"description": "Editor.js — open source block-style WYSIWYG editor with JSON output",
|
"description": "Editor.js — open source block-style WYSIWYG editor with JSON output",
|
||||||
"main": "dist/editorjs.umd.js",
|
"main": "dist/editorjs.umd.js",
|
||||||
"module": "dist/editorjs.mjs",
|
"module": "dist/editorjs.mjs",
|
||||||
|
|
|
||||||
|
|
@ -566,6 +566,16 @@ export default class Dom {
|
||||||
return element.tagName.toLowerCase() === 'a';
|
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
|
* Return element's offset related to the document
|
||||||
*
|
*
|
||||||
|
|
@ -623,6 +633,9 @@ export default class Dom {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If no node found or last node is empty, return null
|
* 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) {
|
if (!lastTextNode) {
|
||||||
return {
|
return {
|
||||||
|
|
@ -633,6 +646,14 @@ export default class Dom {
|
||||||
|
|
||||||
const textContent = lastTextNode.textContent;
|
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) {
|
if (textContent === null || textContent.length === 0) {
|
||||||
return {
|
return {
|
||||||
node: null,
|
node: null,
|
||||||
|
|
|
||||||
|
|
@ -212,7 +212,7 @@ export default class LinkInlineTool implements InlineTool {
|
||||||
*/
|
*/
|
||||||
const hrefAttr = anchorTag.getAttribute('href');
|
const hrefAttr = anchorTag.getAttribute('href');
|
||||||
|
|
||||||
this.nodes.input.value = hrefAttr !== 'null' ? hrefAttr : '';
|
this.nodes.input.defaultValue = hrefAttr !== 'null' ? hrefAttr : '';
|
||||||
|
|
||||||
this.selection.save();
|
this.selection.save();
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -773,12 +773,13 @@ export default class UI extends Module<UINodes> {
|
||||||
*/
|
*/
|
||||||
const element = event.target as Element;
|
const element = event.target as Element;
|
||||||
const ctrlKey = event.metaKey || event.ctrlKey;
|
const ctrlKey = event.metaKey || event.ctrlKey;
|
||||||
|
const anchor = $.getClosestAnchor(element);
|
||||||
if ($.isAnchor(element) && ctrlKey) {
|
|
||||||
|
if (anchor && ctrlKey) {
|
||||||
event.stopImmediatePropagation();
|
event.stopImmediatePropagation();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
||||||
const href = element.getAttribute('href');
|
const href = anchor.getAttribute('href');
|
||||||
const validUrl = _.getValidUrl(href);
|
const validUrl = _.getValidUrl(href);
|
||||||
|
|
||||||
_.openTab(validUrl);
|
_.openTab(validUrl);
|
||||||
|
|
|
||||||
|
|
@ -57,10 +57,9 @@ export default class SelectionUtils {
|
||||||
public isFakeBackgroundEnabled = false;
|
public isFakeBackgroundEnabled = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Native Document's commands for fake background
|
* Native Document's command for fake background
|
||||||
*/
|
*/
|
||||||
private readonly commandBackground: string = 'backColor';
|
private readonly commandBackground: string = 'backColor';
|
||||||
private readonly commandRemoveFormat: string = 'removeFormat';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Editor styles
|
* Editor styles
|
||||||
|
|
@ -416,9 +415,9 @@ export default class SelectionUtils {
|
||||||
if (!this.isFakeBackgroundEnabled) {
|
if (!this.isFakeBackgroundEnabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
document.execCommand(this.commandBackground, false, 'transparent');
|
||||||
|
|
||||||
this.isFakeBackgroundEnabled = false;
|
this.isFakeBackgroundEnabled = false;
|
||||||
document.execCommand(this.commandRemoveFormat);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -121,4 +121,121 @@ describe('Inline Tool Link', () => {
|
||||||
.should('exist')
|
.should('exist')
|
||||||
.should('contain', 'Text with link');
|
.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