diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d734f5b..9e4f2f2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,12 @@ > - :house: [Internal] > - :nail_care: [Polish] +## 4.2.35 + +### :bug: Bug Fix + +- [Edit Link bugg when there is a iframe #1176](https://github.com/xdan/jodit/issues/1176) + ## 4.2.34 ### :bug: Bug Fix @@ -2841,11 +2847,11 @@ Related with https://github.com/xdan/jodit/issues/574. In some cases need to lim - @property {IUIOption[]} link.selectOptionsClassName=[] The list of the option for the select (to use with modeClassName="select") - ex: [ -- { value: "", text: "" }, -- { value: "val1", text: "text1" }, -- { value: "val2", text: "text2" }, -- { value: "val3", text: "text3" } -- ] +- { value: "", text: "" }, +- { value: "val1", text: "text1" }, +- { value: "val2", text: "text2" }, +- { value: "val3", text: "text3" } +- ] PR: https://github.com/xdan/jodit/pull/577 Thanks @s-renier-taonix-fr ##### New option `statusbar: boolean = true` diff --git a/src/modules/history/snapshot.ts b/src/modules/history/snapshot.ts index 1d8bf589..619c44b1 100644 --- a/src/modules/history/snapshot.ts +++ b/src/modules/history/snapshot.ts @@ -10,8 +10,8 @@ import type { IJodit, ISnapshot, Nullable, SnapshotType } from 'jodit/types'; import { ViewComponent } from 'jodit/core/component'; -import { IS_PROD, TEMP_ATTR } from 'jodit/core/constants'; -import { Dom } from 'jodit/core/dom'; +import { IS_PROD } from 'jodit/core/constants'; +import { Dom } from 'jodit/core/dom/dom'; /** * Module for creating snapshot of editor which includes html content and the current selection @@ -154,14 +154,16 @@ export class Snapshot extends ViewComponent implements ISnapshot { } }; - snapshot.html = this.removeJoditSelection(this.j.editor); + snapshot.html = this.__getCleanedEditorValue(this.j.editor); const sel = this.j.s.sel; if (sel && sel.rangeCount) { - const range = sel.getRangeAt(0), - startContainer = this.calcHierarchyLadder(range.startContainer), - endContainer = this.calcHierarchyLadder(range.endContainer); + const range = sel.getRangeAt(0); + const startContainer = this.calcHierarchyLadder( + range.startContainer + ); + const endContainer = this.calcHierarchyLadder(range.endContainer); let startOffset = Snapshot.strokeOffset( range.startContainer, @@ -204,8 +206,8 @@ export class Snapshot extends ViewComponent implements ISnapshot { this.transaction(() => { const scroll = this.storeScrollState(); - const value = this.j.getNativeEditorValue(); - if (value !== snapshot.html) { + const html = this.__getCleanedEditorValue(this.j.editor); + if (html !== snapshot.html) { this.j.value = snapshot.html; } @@ -270,10 +272,10 @@ export class Snapshot extends ViewComponent implements ISnapshot { return (Dom.isText(node) && !node.nodeValue) || Dom.isTemporary(node); } - private removeJoditSelection(node: HTMLElement): string { + private __getCleanedEditorValue(node: HTMLElement): string { const clone = node.cloneNode(true) as HTMLElement; - clone.querySelectorAll(`[${TEMP_ATTR}]`).forEach(Dom.unwrap); + Dom.temporaryList(clone).forEach(Dom.unwrap); return clone.innerHTML; } diff --git a/src/plugins/link/link.test.js b/src/plugins/link/link.test.js index 5bd57387..783ca524 100644 --- a/src/plugins/link/link.test.js +++ b/src/plugins/link/link.test.js @@ -4,10 +4,10 @@ * Copyright (c) 2013-2024 Valeriy Chupurnov. All rights reserved. https://xdsoft.net */ -describe('Link plugin', function () { - describe('Insert link', function () { - describe('Insert simple link', function () { - it('Should insert as simple link', function () { +describe('Link plugin', () => { + describe('Insert link', () => { + describe('Insert simple link', () => { + it('Should insert as simple link', () => { const editor = getJodit(); simulatePaste( @@ -21,8 +21,8 @@ describe('Link plugin', function () { ); }); - describe('For not collapsed selection', function () { - it('Should insert as link with selected content', function () { + describe('For not collapsed selection', () => { + it('Should insert as link with selected content', () => { const editor = getJodit(); editor.value = '

test |test|

'; @@ -40,9 +40,9 @@ describe('Link plugin', function () { }); }); - describe('Disable', function () { - describe('Disable any convert', function () { - it('Should not change source link', function () { + describe('Disable', () => { + describe('Disable any convert', () => { + it('Should not change source link', () => { const editor = getJodit({ link: { processPastedLink: false @@ -58,7 +58,7 @@ describe('Link plugin', function () { }); }); - describe('Insert youtube link', function () { + describe('Insert youtube link', () => { [ [ 'https://www.youtube.com/watch?v=Cy1qd16VDhM&ab_channel=КонстантинСёмин', @@ -69,29 +69,26 @@ describe('Link plugin', function () { 'https://www.youtube.com/embed/8Qn_spdM5Zg' ] ].forEach(function (lnk) { - describe('Insert link ' + lnk[0], function () { - it( - 'Should insert iframe with video ' + lnk[1], - function () { - const editor = getJodit(); + describe('Insert link ' + lnk[0], () => { + it('Should insert iframe with video ' + lnk[1], () => { + const editor = getJodit(); - simulatePaste(editor.editor, lnk[0]); + simulatePaste(editor.editor, lnk[0]); - expect(sortAttributes(editor.value)).equal( - sortAttributes( - '' - ) - ); - } - ); + expect(sortAttributes(editor.value)).equal( + sortAttributes( + '' + ) + ); + }); }); }); - describe('Disable', function () { - describe('Disable any convert', function () { - it('Should not change source link', function () { + describe('Disable', () => { + describe('Disable any convert', () => { + it('Should not change source link', () => { const editor = getJodit({ link: { processPastedLink: false, @@ -109,8 +106,8 @@ describe('Link plugin', function () { }); }); - describe('Disable video convert', function () { - it('Should insert video link as simple link', function () { + describe('Disable video convert', () => { + it('Should insert video link as simple link', () => { const editor = getJodit({ link: { processVideoLink: false @@ -130,11 +127,11 @@ describe('Link plugin', function () { }); }); - describe('Toolbar link', function () { - describe('Click link button', function () { - describe('Edit exists link', function () { - describe('Content input was not changed', function () { - it('Should save link content', function () { + describe('Toolbar link', () => { + describe('Click link button', () => { + describe('Edit exists link', () => { + describe('Content input was not changed', () => { + it('Should save link content', () => { const editor = getJodit(); editor.value = @@ -180,8 +177,8 @@ describe('Link plugin', function () { ); }); - describe('For relative link', function () { - it('Should work same way', function () { + describe('For relative link', () => { + it('Should work same way', () => { const editor = getJodit(); editor.value = @@ -216,8 +213,8 @@ describe('Link plugin', function () { }); }); - describe('Content input was changed', function () { - it('Should replace link content', function () { + describe('Content input was changed', () => { + it('Should replace link content', () => { const editor = getJodit(); editor.value = @@ -255,8 +252,8 @@ describe('Link plugin', function () { ); }); - describe('Content stay clear', function () { - it('Should replace link content to url', function () { + describe('Content stay clear', () => { + it('Should replace link content to url', () => { const editor = getJodit(); editor.value = @@ -300,9 +297,9 @@ describe('Link plugin', function () { }); }); - describe('Select some text inside link', function () { - describe('Content input was not changed', function () { - it("Should open edit popup with full link's content", function () { + describe('Select some text inside link', () => { + describe('Content input was not changed', () => { + it("Should open edit popup with full link's content", () => { const editor = getJodit(); editor.value = @@ -354,8 +351,8 @@ describe('Link plugin', function () { }); }); - describe('Content input was changed', function () { - it("Should open edit popup with full link's content and after submit should replace full link's content", function () { + describe('Content input was changed', () => { + it("Should open edit popup with full link's content and after submit should replace full link's content", () => { const editor = getJodit(); editor.value = @@ -404,9 +401,9 @@ describe('Link plugin', function () { }); }); - describe('Press unlink button', function () { - describe('In inline popup', function () { - it('Should unwrap existing link', function () { + describe('Press unlink button', () => { + describe('In inline popup', () => { + it('Should unwrap existing link', () => { const editor = getJodit(); editor.value = @@ -426,8 +423,8 @@ describe('Link plugin', function () { }); }); - describe('In toolbar popup', function () { - it('Should unwrap existing link', function () { + describe('In toolbar popup', () => { + it('Should unwrap existing link', () => { const editor = getJodit(); editor.value = @@ -492,10 +489,10 @@ describe('Link plugin', function () { }); }); - describe('In dialog', function () { - describe('Edit exists link', function () { - describe('Content input was not changed', function () { - it('Should save link content', function () { + describe('In dialog', () => { + describe('Edit exists link', () => { + describe('Content input was not changed', () => { + it('Should save link content', () => { const editor = getJodit(); editor.value = @@ -541,16 +538,16 @@ describe('Link plugin', function () { }); }); - describe('Open LINK insert dialog and insert new link', function () { - it('Should insert new link', function () { + describe('Open LINK insert dialog and insert new link', () => { + it('Should insert new link', () => { let popup_opened = 0; const editor = getJodit({ events: { - beforeLinkOpenPopup: function () { + beforeLinkOpenPopup: () => { popup_opened += 1; }, - afterLinkOpenPopup: function () { + afterLinkOpenPopup: () => { popup_opened += 1; } }, @@ -601,14 +598,14 @@ describe('Link plugin', function () { expect(list.parentNode).is.null; }); - it('Should fire change event', function () { + it('Should fire change event', () => { let change = 0; const editor = getJodit(); editor.value = '

|

'; setCursorToChar(editor); - editor.events.on('change', function () { + editor.events.on('change', () => { change += 1; }); @@ -626,14 +623,14 @@ describe('Link plugin', function () { expect(change).equals(1); }); - describe('Set custom popup template', function () { - it('Should show this template inside popup', function () { + describe('Set custom popup template', () => { + it('Should show this template inside popup', () => { const tpl = '
'; const editor = getJodit({ link: { - formTemplate: function () { + formTemplate: () => { return tpl; } } @@ -665,14 +662,14 @@ describe('Link plugin', function () { ); }); - describe('Use data-ref instead ref', function () { - it('Should show this template inside popup', function () { + describe('Use data-ref instead ref', () => { + it('Should show this template inside popup', () => { const tpl = '
'; const editor = getJodit({ link: { - formTemplate: function () { + formTemplate: () => { return tpl; } } @@ -711,8 +708,8 @@ describe('Link plugin', function () { }); }); - describe('Add class name in form', function () { - it('Should show form with this class', function () { + describe('Add class name in form', () => { + it('Should show form with this class', () => { const editor = getJodit({ link: { formClassName: 'bootstrap_form' @@ -731,9 +728,9 @@ describe('Link plugin', function () { }); }); - describe('On selected content', function () { - describe('Selected text', function () { - it('Should wrap selected text in link', function () { + describe('On selected content', () => { + describe('Selected text', () => { + it('Should wrap selected text in link', () => { const editor = getJodit({ toolbarAdaptive: false }); @@ -797,9 +794,9 @@ describe('Link plugin', function () { }); }); - describe('Selected image', function () { - describe('On open popup', function () { - it('Should hide text input', function () { + describe('Selected image', () => { + describe('On open popup', () => { + it('Should hide text input', () => { const editor = getJodit({ toolbarAdaptive: false, history: { @@ -829,7 +826,7 @@ describe('Link plugin', function () { }); }); - it('Should wrap selected image in link', function () { + it('Should wrap selected image in link', () => { const editor = getJodit({ toolbarAdaptive: false, history: { @@ -878,7 +875,7 @@ describe('Link plugin', function () { }); }); - it('Should restore source text after user clicked on Unlink button', function () { + it('Should restore source text after user clicked on Unlink button', () => { const editor = getJodit({ history: { timeout: 0 @@ -927,8 +924,8 @@ describe('Link plugin', function () { }); }); - describe('Was selected part of text', function () { - it('Should show dialog form with this text', function () { + describe('Was selected part of text', () => { + it('Should show dialog form with this text', () => { const editor = getJodit(); editor.value = '

one green bottle hanging under wall

'; @@ -950,8 +947,8 @@ describe('Link plugin', function () { }); }); - describe('Was selected part of html', function () { - it('Should show dialog form with selection text content from this HTML', function () { + describe('Was selected part of html', () => { + it('Should show dialog form with selection text content from this HTML', () => { const editor = getJodit(); editor.value = @@ -977,10 +974,10 @@ describe('Link plugin', function () { ); }); - describe('Was selected image', function () { - describe('Image was inside the Table', function () { - describe('Edit with contect menu', function () { - it('Should wrap selected image inside the link', function () { + describe('Was selected image', () => { + describe('Image was inside the Table', () => { + describe('Edit with contect menu', () => { + it('Should wrap selected image inside the link', () => { const editor = getJodit({ popup: { img: ['link', 'unlink'] @@ -1051,8 +1048,8 @@ describe('Link plugin', function () { }); }); - describe('Image had not anchor parent', function () { - it('Should show dialog without content input and after submit wrap this image', function () { + describe('Image had not anchor parent', () => { + it('Should show dialog without content input and after submit wrap this image', () => { const editor = getJodit(); editor.value = @@ -1095,8 +1092,8 @@ describe('Link plugin', function () { }); }); - describe('Image had anchor parent', function () { - it('Should show dialog without content input and after submit wrap this image', function () { + describe('Image had anchor parent', () => { + it('Should show dialog without content input and after submit wrap this image', () => { const editor = getJodit(); editor.value = @@ -1143,8 +1140,8 @@ describe('Link plugin', function () { }); }); - describe('After submit this part', function () { - it('should be wrapped inside anchor', function () { + describe('After submit this part', () => { + it('should be wrapped inside anchor', () => { const editor = getJodit(); editor.value = @@ -1183,9 +1180,9 @@ describe('Link plugin', function () { }); }); - describe('Link with class name (modeClassName=input/default)', function () { - describe('Add class name on link', function () { - it('Should insert new link with a class name', function () { + describe('Link with class name (modeClassName=input/default)', () => { + describe('Add class name on link', () => { + it('Should insert new link with a class name', () => { const editor = getJodit(); editor.value = @@ -1227,8 +1224,8 @@ describe('Link plugin', function () { }); }); - describe('Vérify class name on link', function () { - it('Should have link with a class name', function () { + describe('Vérify class name on link', () => { + it('Should have link with a class name', () => { const editor = getJodit(); editor.value = @@ -1256,8 +1253,8 @@ describe('Link plugin', function () { }); }); - describe('Modify class name on link', function () { - it('Should modify link with a new class name', function () { + describe('Modify class name on link', () => { + it('Should modify link with a new class name', () => { const editor = getJodit(); editor.value = @@ -1293,8 +1290,8 @@ describe('Link plugin', function () { }); }); - describe('Delete class name on link', function () { - it('Should modify link witout class name', function () { + describe('Delete class name on link', () => { + it('Should modify link witout class name', () => { const editor = getJodit(); editor.value = @@ -1331,9 +1328,9 @@ describe('Link plugin', function () { }); }); - describe('Link with class name (modeClassName=select)', function () { - describe('Add class name on link', function () { - it('Should insert new link with a class name', function () { + describe('Link with class name (modeClassName=select)', () => { + describe('Add class name on link', () => { + it('Should insert new link with a class name', () => { const editor = getJodit({ link: { modeClassName: 'select', @@ -1388,8 +1385,8 @@ describe('Link plugin', function () { }); }); - describe('Vérify class name on link', function () { - it('Should have link with a class name', function () { + describe('Vérify class name on link', () => { + it('Should have link with a class name', () => { const editor = getJodit({ link: { modeClassName: 'select', @@ -1431,8 +1428,8 @@ describe('Link plugin', function () { }); }); - describe('Modify class name on link', function () { - it('Should modify link with a new class name', function () { + describe('Modify class name on link', () => { + it('Should modify link with a new class name', () => { const editor = getJodit({ link: { modeClassName: 'select', @@ -1481,8 +1478,8 @@ describe('Link plugin', function () { }); }); - describe('Delete class name on link', function () { - it('Should modify link witout class name', function () { + describe('Delete class name on link', () => { + it('Should modify link witout class name', () => { const editor = getJodit({ link: { modeClassName: 'select', @@ -1532,9 +1529,9 @@ describe('Link plugin', function () { }); }); - describe('Link with class name (modeClassName="select", selectMultipleClassName=true)', function () { - describe('Add class name on link', function () { - it('Should insert new link with a class name', function () { + describe('Link with class name (modeClassName="select", selectMultipleClassName=true)', () => { + describe('Add class name on link', () => { + it('Should insert new link with a class name', () => { const editor = getJodit({ link: { modeClassName: 'select', @@ -1591,8 +1588,8 @@ describe('Link plugin', function () { }); }); - describe('Vérify class name on link', function () { - it('Should have link with a class name', function () { + describe('Vérify class name on link', () => { + it('Should have link with a class name', () => { const editor = getJodit({ link: { modeClassName: 'select', @@ -1637,8 +1634,8 @@ describe('Link plugin', function () { }); }); - describe('Modify class name on link', function () { - it('Should modify link with a new class name', function () { + describe('Modify class name on link', () => { + it('Should modify link with a new class name', () => { const editor = getJodit({ link: { modeClassName: 'select', @@ -1691,8 +1688,8 @@ describe('Link plugin', function () { }); }); - describe('Delete class name on link', function () { - it('Should modify link witout class name', function () { + describe('Delete class name on link', () => { + it('Should modify link witout class name', () => { const editor = getJodit({ link: { modeClassName: 'select', @@ -1742,4 +1739,35 @@ describe('Link plugin', function () { }); }); }); + + describe('Edit link with iframe', () => { + it('Should work fine', () => { + const editor = getJodit(); + editor.value = `

— + |The Wonderful Wizard of Oz by L. Frank Baum. +

+
`; + setCursorToChar(editor); + clickButton('link', editor); + const popup = getOpenedPopup(editor); + const form = popup.querySelector('.jodit-ui-form'); + expect(form).is.not.null; + + const input = form.querySelector('input[ref=url_input]'); + + expect(input).is.not.null; + expect(input.value).equals( + 'https://www.gutenberg.org/cache/epub/55/pg55-images.html' + ); + input.value = 'https://xdsoft.net/jodit/'; + + simulateEvent('submit', form); + + expect( + sortAttributes(editor.value).replace(/[\t\n\s]+/g, ' ') + ).equals( + '

The Wonderful Wizard of Oz by L. Frank Baum.

' + ); + }); + }); }); diff --git a/src/plugins/link/link.ts b/src/plugins/link/link.ts index 0e47dbbd..6d85caac 100644 --- a/src/plugins/link/link.ts +++ b/src/plugins/link/link.ts @@ -145,21 +145,21 @@ export class link extends Plugin { modeClassName } = jodit.o.link; - const html = formTemplate(jodit), - form = isString(html) - ? (jodit.c.fromHTML(html, { - target_checkbox_box: openInNewTabCheckbox, - nofollow_checkbox_box: noFollowCheckbox - }) as HTMLFormElement) - : html, - htmlForm = Dom.isElement(form) ? form : form.container; - - const elements = refs(htmlForm), - { insert, unlink, content_input_box } = elements, - { target_checkbox, nofollow_checkbox, url_input } = - elements as IDictionary, - currentElement = current, - isImageContent = Dom.isImage(currentElement); + const html = formTemplate(jodit); + const form = isString(html) + ? (jodit.c.fromHTML(html, { + target_checkbox_box: openInNewTabCheckbox, + nofollow_checkbox_box: noFollowCheckbox + }) as HTMLFormElement) + : html; + const htmlForm = Dom.isElement(form) ? form : form.container; + + const elements = refs(htmlForm); + const { insert, unlink, content_input_box } = elements; + const { target_checkbox, nofollow_checkbox, url_input } = + elements as IDictionary; + const currentElement = current; + const isImageContent = Dom.isImage(currentElement); let { content_input } = elements as IDictionary; @@ -261,7 +261,7 @@ export class link extends Plugin { const ci = jodit.createInside; - if (!link) { + if (!link || !Dom.isOrContains(jodit.editor, link)) { if (!jodit.s.isCollapsed()) { const node = jodit.s.current();