suneditor 3.0.0-beta.2 → 3.0.0-beta.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +186 -184
- package/LICENSE +21 -21
- package/README.md +157 -180
- package/dist/suneditor.min.css +1 -1
- package/dist/suneditor.min.js +1 -1
- package/package.json +126 -123
- package/src/assets/design/color.css +131 -121
- package/src/assets/design/index.css +3 -3
- package/src/assets/design/size.css +37 -35
- package/src/assets/design/typography.css +37 -37
- package/src/assets/icons/defaultIcons.js +247 -232
- package/src/assets/suneditor-contents.css +779 -778
- package/src/assets/suneditor.css +43 -35
- package/src/core/base/eventHandlers/handler_toolbar.js +135 -135
- package/src/core/base/eventHandlers/handler_ww_clipboard.js +56 -56
- package/src/core/base/eventHandlers/handler_ww_dragDrop.js +115 -113
- package/src/core/base/eventHandlers/handler_ww_key_input.js +1200 -1200
- package/src/core/base/eventHandlers/handler_ww_mouse.js +194 -194
- package/src/core/base/eventManager.js +1550 -1484
- package/src/core/base/history.js +355 -355
- package/src/core/class/char.js +163 -162
- package/src/core/class/component.js +856 -842
- package/src/core/class/format.js +3433 -3422
- package/src/core/class/html.js +1927 -1890
- package/src/core/class/menu.js +357 -346
- package/src/core/class/nodeTransform.js +424 -424
- package/src/core/class/offset.js +858 -891
- package/src/core/class/selection.js +710 -620
- package/src/core/class/shortcuts.js +98 -98
- package/src/core/class/toolbar.js +438 -430
- package/src/core/class/ui.js +424 -422
- package/src/core/class/viewer.js +750 -750
- package/src/core/editor.js +1810 -1708
- package/src/core/section/actives.js +268 -241
- package/src/core/section/constructor.js +1348 -1661
- package/src/core/section/context.js +102 -102
- package/src/core/section/documentType.js +582 -561
- package/src/core/section/options.js +367 -0
- package/src/core/util/instanceCheck.js +59 -0
- package/src/editorInjector/_classes.js +36 -36
- package/src/editorInjector/_core.js +92 -92
- package/src/editorInjector/index.js +75 -75
- package/src/events.js +634 -622
- package/src/helper/clipboard.js +59 -59
- package/src/helper/converter.js +586 -564
- package/src/helper/dom/domCheck.js +304 -304
- package/src/helper/dom/domQuery.js +677 -669
- package/src/helper/dom/domUtils.js +618 -557
- package/src/helper/dom/index.js +12 -12
- package/src/helper/env.js +249 -240
- package/src/helper/index.js +25 -25
- package/src/helper/keyCodeMap.js +183 -183
- package/src/helper/numbers.js +72 -72
- package/src/helper/unicode.js +47 -47
- package/src/langs/ckb.js +231 -231
- package/src/langs/cs.js +231 -231
- package/src/langs/da.js +231 -231
- package/src/langs/de.js +231 -231
- package/src/langs/en.js +230 -230
- package/src/langs/es.js +231 -231
- package/src/langs/fa.js +231 -231
- package/src/langs/fr.js +231 -231
- package/src/langs/he.js +231 -231
- package/src/langs/hu.js +230 -230
- package/src/langs/index.js +28 -28
- package/src/langs/it.js +231 -231
- package/src/langs/ja.js +230 -230
- package/src/langs/km.js +230 -230
- package/src/langs/ko.js +230 -230
- package/src/langs/lv.js +231 -231
- package/src/langs/nl.js +231 -231
- package/src/langs/pl.js +231 -231
- package/src/langs/pt_br.js +231 -231
- package/src/langs/ro.js +231 -231
- package/src/langs/ru.js +231 -231
- package/src/langs/se.js +231 -231
- package/src/langs/tr.js +231 -231
- package/src/langs/uk.js +231 -231
- package/src/langs/ur.js +231 -231
- package/src/langs/zh_cn.js +231 -231
- package/src/modules/ApiManager.js +191 -191
- package/src/modules/Browser.js +669 -667
- package/src/modules/ColorPicker.js +364 -362
- package/src/modules/Controller.js +474 -454
- package/src/modules/Figure.js +1620 -1617
- package/src/modules/FileManager.js +359 -359
- package/src/modules/HueSlider.js +577 -565
- package/src/modules/Modal.js +346 -346
- package/src/modules/ModalAnchorEditor.js +643 -643
- package/src/modules/SelectMenu.js +549 -549
- package/src/modules/_DragHandle.js +17 -17
- package/src/modules/index.js +14 -14
- package/src/plugins/browser/audioGallery.js +83 -83
- package/src/plugins/browser/fileBrowser.js +103 -103
- package/src/plugins/browser/fileGallery.js +83 -83
- package/src/plugins/browser/imageGallery.js +81 -81
- package/src/plugins/browser/videoGallery.js +103 -103
- package/src/plugins/command/blockquote.js +61 -60
- package/src/plugins/command/exportPDF.js +134 -134
- package/src/plugins/command/fileUpload.js +456 -456
- package/src/plugins/command/list_bulleted.js +149 -148
- package/src/plugins/command/list_numbered.js +152 -151
- package/src/plugins/dropdown/align.js +157 -155
- package/src/plugins/dropdown/backgroundColor.js +108 -104
- package/src/plugins/dropdown/font.js +141 -137
- package/src/plugins/dropdown/fontColor.js +109 -105
- package/src/plugins/dropdown/formatBlock.js +170 -178
- package/src/plugins/dropdown/hr.js +152 -152
- package/src/plugins/dropdown/layout.js +83 -83
- package/src/plugins/dropdown/lineHeight.js +131 -130
- package/src/plugins/dropdown/list.js +123 -122
- package/src/plugins/dropdown/paragraphStyle.js +138 -138
- package/src/plugins/dropdown/table.js +4110 -4000
- package/src/plugins/dropdown/template.js +83 -83
- package/src/plugins/dropdown/textStyle.js +149 -149
- package/src/plugins/field/mention.js +242 -242
- package/src/plugins/index.js +120 -120
- package/src/plugins/input/fontSize.js +414 -410
- package/src/plugins/input/pageNavigator.js +71 -70
- package/src/plugins/modal/audio.js +677 -677
- package/src/plugins/modal/drawing.js +537 -531
- package/src/plugins/modal/embed.js +886 -886
- package/src/plugins/modal/image.js +1377 -1376
- package/src/plugins/modal/link.js +248 -240
- package/src/plugins/modal/math.js +563 -563
- package/src/plugins/modal/video.js +1226 -1226
- package/src/plugins/popup/anchor.js +224 -222
- package/src/suneditor.js +114 -107
- package/src/themes/dark.css +132 -122
- package/src/typedef.js +132 -130
- package/types/assets/icons/defaultIcons.d.ts +8 -0
- package/types/core/base/eventManager.d.ts +29 -4
- package/types/core/class/char.d.ts +2 -1
- package/types/core/class/component.d.ts +1 -2
- package/types/core/class/format.d.ts +8 -1
- package/types/core/class/html.d.ts +8 -0
- package/types/core/class/menu.d.ts +8 -0
- package/types/core/class/offset.d.ts +24 -26
- package/types/core/class/selection.d.ts +2 -0
- package/types/core/class/toolbar.d.ts +6 -0
- package/types/core/class/ui.d.ts +1 -1
- package/types/core/editor.d.ts +34 -12
- package/types/core/section/constructor.d.ts +5 -638
- package/types/core/section/documentType.d.ts +12 -2
- package/types/core/section/options.d.ts +740 -0
- package/types/core/util/instanceCheck.d.ts +50 -0
- package/types/editorInjector/_core.d.ts +5 -5
- package/types/editorInjector/index.d.ts +2 -2
- package/types/events.d.ts +2 -0
- package/types/helper/converter.d.ts +9 -0
- package/types/helper/dom/domQuery.d.ts +5 -5
- package/types/helper/dom/domUtils.d.ts +8 -0
- package/types/helper/env.d.ts +6 -1
- package/types/helper/index.d.ts +4 -1
- package/types/index.d.ts +122 -120
- package/types/langs/_Lang.d.ts +194 -194
- package/types/modules/ColorPicker.d.ts +5 -1
- package/types/modules/Controller.d.ts +8 -4
- package/types/modules/Figure.d.ts +2 -1
- package/types/modules/HueSlider.d.ts +4 -1
- package/types/modules/SelectMenu.d.ts +1 -1
- package/types/plugins/command/blockquote.d.ts +1 -0
- package/types/plugins/command/list_bulleted.d.ts +1 -0
- package/types/plugins/command/list_numbered.d.ts +1 -0
- package/types/plugins/dropdown/align.d.ts +1 -0
- package/types/plugins/dropdown/backgroundColor.d.ts +1 -0
- package/types/plugins/dropdown/font.d.ts +1 -0
- package/types/plugins/dropdown/fontColor.d.ts +1 -0
- package/types/plugins/dropdown/formatBlock.d.ts +3 -2
- package/types/plugins/dropdown/lineHeight.d.ts +1 -0
- package/types/plugins/dropdown/list.d.ts +1 -0
- package/types/plugins/dropdown/table.d.ts +6 -0
- package/types/plugins/input/fontSize.d.ts +1 -0
- package/types/plugins/modal/drawing.d.ts +4 -0
- package/types/plugins/modal/link.d.ts +32 -15
- package/types/suneditor.d.ts +13 -9
- package/types/typedef.d.ts +8 -0
|
@@ -1,643 +1,643 @@
|
|
|
1
|
-
import EditorInjector from '../editorInjector';
|
|
2
|
-
import SelectMenu from './SelectMenu';
|
|
3
|
-
import FileManager from './FileManager';
|
|
4
|
-
import { dom, numbers, env, unicode } from '../helper';
|
|
5
|
-
const { NO_EVENT } = env;
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* @typedef {{default?: string, check_new_window?: string, check_bookmark?: string}} RELAttr
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* @typedef {Object} ModalAnchorEditorParams
|
|
13
|
-
* @property {boolean} [title=false] - Modal title display.
|
|
14
|
-
* @property {boolean} [textToDisplay=''] - Create Text to display input.
|
|
15
|
-
* @property {boolean} [openNewWindow=false] - Default checked value of the "Open in new window" checkbox.
|
|
16
|
-
* @property {boolean} [noAutoPrefix=false] - If true, disables the automatic prefixing of the host URL to the value of the link.
|
|
17
|
-
* @property {Array<string>} [relList=[]] - The "rel" attribute list of anchor tag.
|
|
18
|
-
* @property {RELAttr} [defaultRel={}] - Default "rel" attributes of anchor tag.
|
|
19
|
-
* @property {string=} uploadUrl - File upload URL.
|
|
20
|
-
* @property {Object<string, string>=} uploadHeaders - File upload headers.
|
|
21
|
-
* @property {number=} uploadSizeLimit - File upload size limit.
|
|
22
|
-
* @property {number=} uploadSingleSizeLimit - File upload single size limit.
|
|
23
|
-
* @property {string=} acceptedFormats - File upload accepted formats.
|
|
24
|
-
* @property {boolean=} enableFileUpload - If true, enables file upload.
|
|
25
|
-
* @example "REL" structure
|
|
26
|
-
{
|
|
27
|
-
default: 'nofollow', // Default rel
|
|
28
|
-
check_new_window: 'noreferrer noopener', // When "open new window" is checked
|
|
29
|
-
check_bookmark: 'bookmark' // When "bookmark" is checked
|
|
30
|
-
}
|
|
31
|
-
If true, disables the automatic prefixing of the host URL to the value of the link.
|
|
32
|
-
*/
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* @class
|
|
36
|
-
* @description Modal form Anchor tag editor
|
|
37
|
-
* - Use it by inserting it into Modal in a plugin that uses Modal.
|
|
38
|
-
*/
|
|
39
|
-
class ModalAnchorEditor extends EditorInjector {
|
|
40
|
-
/**
|
|
41
|
-
* @constructor
|
|
42
|
-
* @param {*} inst The instance object that called the constructor.
|
|
43
|
-
* @param {Node} modalForm The modal form element
|
|
44
|
-
* @param {ModalAnchorEditorParams} params ModalAnchorEditor options
|
|
45
|
-
*/
|
|
46
|
-
constructor(inst, modalForm, params) {
|
|
47
|
-
// plugin bisic properties
|
|
48
|
-
super(inst.editor);
|
|
49
|
-
|
|
50
|
-
// params
|
|
51
|
-
this.openNewWindow = !!params.openNewWindow;
|
|
52
|
-
this.relList = Array.isArray(params.relList) ? params.relList : [];
|
|
53
|
-
this.defaultRel = params.defaultRel || {};
|
|
54
|
-
this.noAutoPrefix = !!params.noAutoPrefix;
|
|
55
|
-
// file upload
|
|
56
|
-
if (params.enableFileUpload) {
|
|
57
|
-
this.uploadUrl = typeof params.uploadUrl === 'string' ? params.uploadUrl : null;
|
|
58
|
-
this.uploadHeaders = params.uploadHeaders || null;
|
|
59
|
-
this.uploadSizeLimit = numbers.get(params.uploadSizeLimit, 0) || null;
|
|
60
|
-
this.uploadSingleSizeLimit = numbers.get(params.uploadSingleSizeLimit, 0) || null;
|
|
61
|
-
this.input = dom.utils.createElement('input', { type: 'file', accept: params.acceptedFormats || '*' });
|
|
62
|
-
this.eventManager.addEvent(this.input, 'change', this.#OnChangeFile.bind(this));
|
|
63
|
-
// file manager
|
|
64
|
-
this.fileManager = new FileManager(this, {
|
|
65
|
-
query: 'a[download]:not([data-se-file-download])',
|
|
66
|
-
loadHandler: this.events.onFileLoad,
|
|
67
|
-
eventHandler: this.events.onFileAction
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// create HTML
|
|
72
|
-
const forms = CreatetModalForm(inst.editor, params, this.relList);
|
|
73
|
-
|
|
74
|
-
// members
|
|
75
|
-
this.kink = inst.constructor.key || inst.constructor.name;
|
|
76
|
-
this.inst = inst;
|
|
77
|
-
this.modalForm = /** @type {HTMLElement} */ (modalForm);
|
|
78
|
-
this.host = (this._w.location.origin + this._w.location.pathname).replace(/\/$/, '');
|
|
79
|
-
|
|
80
|
-
/** @type {HTMLInputElement} */
|
|
81
|
-
this.urlInput = forms.querySelector('.se-input-url');
|
|
82
|
-
/** @type {HTMLInputElement} */
|
|
83
|
-
this.displayInput = forms.querySelector('._se_display_text');
|
|
84
|
-
/** @type {HTMLInputElement} */
|
|
85
|
-
this.titleInput = forms.querySelector('._se_title');
|
|
86
|
-
/** @type {HTMLInputElement} */
|
|
87
|
-
this.newWindowCheck = forms.querySelector('._se_anchor_check');
|
|
88
|
-
/** @type {HTMLInputElement} */
|
|
89
|
-
this.downloadCheck = forms.querySelector('._se_anchor_download');
|
|
90
|
-
/** @type {HTMLElement} */
|
|
91
|
-
this.download = forms.querySelector('._se_anchor_download_icon');
|
|
92
|
-
/** @type {HTMLElement} */
|
|
93
|
-
this.preview = forms.querySelector('.se-link-preview');
|
|
94
|
-
/** @type {HTMLElement} */
|
|
95
|
-
this.bookmark = forms.querySelector('._se_anchor_bookmark_icon');
|
|
96
|
-
/** @type {HTMLButtonElement} */
|
|
97
|
-
this.bookmarkButton = forms.querySelector('._se_bookmark_button');
|
|
98
|
-
|
|
99
|
-
this.currentRel = [];
|
|
100
|
-
this.currentTarget = null;
|
|
101
|
-
this.linkValue = '';
|
|
102
|
-
this._change = false;
|
|
103
|
-
this._isRel = this.relList.length > 0;
|
|
104
|
-
// members - rel
|
|
105
|
-
if (this._isRel) {
|
|
106
|
-
/** @type {HTMLButtonElement} */
|
|
107
|
-
this.relButton = forms.querySelector('.se-anchor-rel-btn');
|
|
108
|
-
/** @type {HTMLElement} */
|
|
109
|
-
this.relPreview = forms.querySelector('.se-anchor-rel-preview');
|
|
110
|
-
|
|
111
|
-
const relList = this.relList;
|
|
112
|
-
const defaultRel = (this.defaultRel.default || '').split(' ');
|
|
113
|
-
const list = [];
|
|
114
|
-
for (let i = 0, len = relList.length, rel; i < len; i++) {
|
|
115
|
-
rel = relList[i];
|
|
116
|
-
list.push(
|
|
117
|
-
dom.utils.createElement(
|
|
118
|
-
'BUTTON',
|
|
119
|
-
{
|
|
120
|
-
type: 'button',
|
|
121
|
-
class: 'se-btn-list' + (defaultRel.includes(rel) ? ' se-checked' : ''),
|
|
122
|
-
'data-command': rel,
|
|
123
|
-
title: rel,
|
|
124
|
-
'aria-label': rel
|
|
125
|
-
},
|
|
126
|
-
rel + '<span class="se-svg">' + this.icons.checked + '</span>'
|
|
127
|
-
)
|
|
128
|
-
);
|
|
129
|
-
}
|
|
130
|
-
this.selectMenu_rel = new SelectMenu(this, { checkList: true, position: 'right-middle', dir: 'ltr' });
|
|
131
|
-
this.selectMenu_rel.on(this.relButton, this.#SetRelItem.bind(this));
|
|
132
|
-
this.selectMenu_rel.create(list);
|
|
133
|
-
this.eventManager.addEvent(this.relButton, 'click', this.#OnClick_relbutton.bind(this));
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// init
|
|
137
|
-
this.modalForm.querySelector('.se-anchor-editor').appendChild(forms);
|
|
138
|
-
this.selectMenu_bookmark = new SelectMenu(this, { checkList: false, position: 'bottom-left', dir: 'ltr' });
|
|
139
|
-
this.selectMenu_bookmark.on(this.urlInput, this.#SetHeaderBookmark.bind(this));
|
|
140
|
-
this.eventManager.addEvent(this.newWindowCheck, 'change', this.#OnChange_newWindowCheck.bind(this));
|
|
141
|
-
this.eventManager.addEvent(this.downloadCheck, 'change', this.#OnChange_downloadCheck.bind(this));
|
|
142
|
-
this.eventManager.addEvent(this.displayInput, 'input', this.#OnChange_displayInput.bind(this));
|
|
143
|
-
this.eventManager.addEvent(this.urlInput, 'input', this.#OnChange_urlInput.bind(this));
|
|
144
|
-
this.eventManager.addEvent(this.urlInput, 'focus', this.#OnFocus_urlInput.bind(this));
|
|
145
|
-
this.eventManager.addEvent(this.bookmarkButton, 'click', this.#OnClick_bookmarkButton.bind(this));
|
|
146
|
-
this.eventManager.addEvent(forms.querySelector('._se_upload_button'), 'click', () => this.input.click());
|
|
147
|
-
}
|
|
148
|
-
/**
|
|
149
|
-
* @description Initialize.
|
|
150
|
-
* - Sets the current anchor element to be edited.
|
|
151
|
-
* @param {Node} element Modal target element
|
|
152
|
-
*/
|
|
153
|
-
set(element) {
|
|
154
|
-
this.currentTarget = /** @type {HTMLAnchorElement} */ (element);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* @description Opens the anchor editor modal and populates it with data.
|
|
159
|
-
* @param {boolean} isUpdate - Indicates whether an existing anchor is being updated (`true`) or a new one is being created (`false`).
|
|
160
|
-
*/
|
|
161
|
-
on(isUpdate) {
|
|
162
|
-
if (!isUpdate) {
|
|
163
|
-
this.init();
|
|
164
|
-
this.displayInput.value = this.selection.get().toString().trim();
|
|
165
|
-
this.newWindowCheck.checked = this.openNewWindow;
|
|
166
|
-
this.titleInput.value = '';
|
|
167
|
-
} else if (this.currentTarget) {
|
|
168
|
-
const href = this.currentTarget.href;
|
|
169
|
-
this.linkValue = this.preview.textContent = this.urlInput.value = this._selfPathBookmark(href) ? href.substring(href.lastIndexOf('#')) : href;
|
|
170
|
-
this.displayInput.value = this.currentTarget.textContent;
|
|
171
|
-
this.titleInput.value = this.currentTarget.title;
|
|
172
|
-
this.newWindowCheck.checked = /_blank/i.test(this.currentTarget.target) ? true : false;
|
|
173
|
-
this.downloadCheck.checked = !!this.currentTarget.download;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
this._setRel(isUpdate && this.currentTarget ? this.currentTarget.rel : this.defaultRel.default || '');
|
|
177
|
-
this._setLinkPreview(this.linkValue);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* @description Creates an anchor (`<a>`) element with the specified attributes.
|
|
182
|
-
* @param {boolean} notText - If `true`, the anchor will not contain text content.
|
|
183
|
-
* @returns {HTMLElement|null} - The newly created anchor element, or `null` if the URL is empty.
|
|
184
|
-
*/
|
|
185
|
-
create(notText) {
|
|
186
|
-
if (this.linkValue.length === 0) return null;
|
|
187
|
-
|
|
188
|
-
const url = this.linkValue;
|
|
189
|
-
const displayText = this.displayInput.value.length === 0 ? url : this.displayInput.value;
|
|
190
|
-
|
|
191
|
-
const oA = /** @type {HTMLAnchorElement} */ (this.currentTarget || dom.utils.createElement('A'));
|
|
192
|
-
this._updateAnchor(oA, url, displayText, this.titleInput.value, notText);
|
|
193
|
-
this.linkValue = this.preview.textContent = this.urlInput.value = this.displayInput.value = '';
|
|
194
|
-
|
|
195
|
-
return oA;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* @description Resets the ModalAnchorEditor to its initial state.
|
|
200
|
-
*/
|
|
201
|
-
init() {
|
|
202
|
-
this.currentTarget = null;
|
|
203
|
-
this.linkValue = this.preview.textContent = this.urlInput.value = '';
|
|
204
|
-
this.displayInput.value = '';
|
|
205
|
-
this.newWindowCheck.checked = false;
|
|
206
|
-
this.downloadCheck.checked = false;
|
|
207
|
-
this._change = false;
|
|
208
|
-
this._setRel(this.defaultRel.default || '');
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* @private
|
|
213
|
-
* @description Updates the anchor element with new attributes.
|
|
214
|
-
* @param {HTMLAnchorElement} anchor - The anchor (`<a>`) element to update.
|
|
215
|
-
* @param {string} url - The URL for the anchor's `href` attribute.
|
|
216
|
-
* @param {string} displayText - The text to be displayed inside the anchor.
|
|
217
|
-
* @param {string} title - The tooltip text (title attribute).
|
|
218
|
-
* @param {boolean} notText - If `true`, the anchor will not contain text content.
|
|
219
|
-
*/
|
|
220
|
-
_updateAnchor(anchor, url, displayText, title, notText) {
|
|
221
|
-
// download
|
|
222
|
-
if (!this._selfPathBookmark(url) && this.downloadCheck.checked) {
|
|
223
|
-
anchor.setAttribute('download', displayText || url);
|
|
224
|
-
} else {
|
|
225
|
-
anchor.removeAttribute('download');
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// new window
|
|
229
|
-
if (this.newWindowCheck.checked) anchor.target = '_blank';
|
|
230
|
-
else anchor.removeAttribute('target');
|
|
231
|
-
|
|
232
|
-
// rel
|
|
233
|
-
const rel = this.currentRel.join(' ');
|
|
234
|
-
if (!rel) anchor.removeAttribute('rel');
|
|
235
|
-
else anchor.rel = rel;
|
|
236
|
-
|
|
237
|
-
// set url
|
|
238
|
-
anchor.href = url;
|
|
239
|
-
if (title) anchor.title = title;
|
|
240
|
-
else anchor.removeAttribute('title');
|
|
241
|
-
|
|
242
|
-
if (notText) {
|
|
243
|
-
if (anchor.children.length === 0) anchor.textContent = '';
|
|
244
|
-
} else {
|
|
245
|
-
anchor.textContent = displayText;
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
/**
|
|
250
|
-
* @private
|
|
251
|
-
* @description Checks if the given path is an internal bookmark.
|
|
252
|
-
* @param {string} path - The URL or anchor link.
|
|
253
|
-
* @returns {boolean} - `true` if the path is an internal bookmark, otherwise `false`.
|
|
254
|
-
*/
|
|
255
|
-
_selfPathBookmark(path) {
|
|
256
|
-
const href = this._w.location.href.replace(/\/$/, '');
|
|
257
|
-
return path.indexOf('#') === 0 || (path.indexOf(href) === 0 && path.indexOf('#') === (!href.includes('#') ? href.length : href.substring(0, href.indexOf('#')).length));
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
/**
|
|
261
|
-
* @private
|
|
262
|
-
* @description Updates the `rel` attribute list in the modal and preview.
|
|
263
|
-
* @param {string} relAttr - The `rel` attribute string to set.
|
|
264
|
-
*/
|
|
265
|
-
_setRel(relAttr) {
|
|
266
|
-
if (!this._isRel) return;
|
|
267
|
-
|
|
268
|
-
const rels = (this.currentRel = !relAttr ? [] : relAttr.split(' '));
|
|
269
|
-
const checkedRel = this.selectMenu_rel.form.querySelectorAll('button');
|
|
270
|
-
for (let i = 0, len = checkedRel.length, cmd; i < len; i++) {
|
|
271
|
-
cmd = checkedRel[i].getAttribute('data-command');
|
|
272
|
-
if (rels.includes(cmd)) {
|
|
273
|
-
dom.utils.addClass(checkedRel[i], 'se-checked');
|
|
274
|
-
} else {
|
|
275
|
-
dom.utils.removeClass(checkedRel[i], 'se-checked');
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
this.relPreview.title = this.relPreview.textContent = rels.join(' ');
|
|
280
|
-
if (rels.length > 0) {
|
|
281
|
-
dom.utils.addClass(this.relButton, 'on');
|
|
282
|
-
} else {
|
|
283
|
-
dom.utils.removeClass(this.relButton, 'on');
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
/**
|
|
288
|
-
* @private
|
|
289
|
-
* @description Generates a list of bookmark headers within the editor.
|
|
290
|
-
* @param {string} urlValue - The current URL input value.
|
|
291
|
-
*/
|
|
292
|
-
_createBookmarkList(urlValue) {
|
|
293
|
-
const headers = dom.query.getListChildren(this.editor.frameContext.get('wysiwyg'), (current) => /h[1-6]/i.test(current.nodeName) || (dom.check.isAnchor(current) && !!current.id));
|
|
294
|
-
if (headers.length === 0) return;
|
|
295
|
-
|
|
296
|
-
const valueRegExp = new RegExp(`^${urlValue.replace(/^#/, '')}`, 'i');
|
|
297
|
-
const list = [];
|
|
298
|
-
const menus = [];
|
|
299
|
-
for (let i = 0, len = headers.length, v; i < len; i++) {
|
|
300
|
-
v = headers[i];
|
|
301
|
-
if (!valueRegExp.test(v.textContent)) continue;
|
|
302
|
-
list.push(v);
|
|
303
|
-
menus.push(dom.check.isAnchor(v) ? `<div><span class="se-text-prefix-icon">${this.icons.bookmark_anchor}</span>${v.id}</div>` : `<div style="${v.style.cssText}">${v.textContent}</div>`);
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
if (list.length === 0) {
|
|
307
|
-
this.selectMenu_bookmark.close();
|
|
308
|
-
} else {
|
|
309
|
-
this.selectMenu_bookmark.create(list, menus);
|
|
310
|
-
this.selectMenu_bookmark.open(this.options.get('_rtl') ? 'bottom-right' : '');
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
/**
|
|
315
|
-
* @private
|
|
316
|
-
* @description Updates the preview of the anchor link.
|
|
317
|
-
* @param {string} value - The current URL value.
|
|
318
|
-
*/
|
|
319
|
-
_setLinkPreview(value) {
|
|
320
|
-
const preview = this.preview;
|
|
321
|
-
const protocol = this.options.get('defaultUrlProtocol');
|
|
322
|
-
const noPrefix = this.noAutoPrefix;
|
|
323
|
-
const reservedProtocol = /^(mailto:|tel:|sms:|https*:\/\/|#)/.test(value) || value.indexOf(protocol) === 0;
|
|
324
|
-
const sameProtocol = !protocol ? false : RegExp('^' + unicode.escapeStringRegexp(value.substring(0, protocol.length))).test(protocol);
|
|
325
|
-
|
|
326
|
-
value =
|
|
327
|
-
this.linkValue =
|
|
328
|
-
preview.textContent =
|
|
329
|
-
!value ? '' : noPrefix ? value : protocol && !reservedProtocol && !sameProtocol ? protocol + value : reservedProtocol ? value : /^www\./.test(value) ? 'http://' + value : this.host + (/^\//.test(value) ? '' : '/') + value;
|
|
330
|
-
|
|
331
|
-
if (this._selfPathBookmark(value)) {
|
|
332
|
-
this.bookmark.style.display = 'block';
|
|
333
|
-
dom.utils.addClass(this.bookmarkButton, 'active');
|
|
334
|
-
} else {
|
|
335
|
-
this.bookmark.style.display = 'none';
|
|
336
|
-
dom.utils.removeClass(this.bookmarkButton, 'active');
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
if (!this._selfPathBookmark(value) && this.downloadCheck.checked) {
|
|
340
|
-
this.download.style.display = 'block';
|
|
341
|
-
} else {
|
|
342
|
-
this.download.style.display = 'none';
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
/**
|
|
347
|
-
* @private
|
|
348
|
-
* @description Merges the given `rel` attribute value with the current list.
|
|
349
|
-
* @param {string} relAttr - The `rel` attribute to merge.
|
|
350
|
-
* @returns {string} - The updated `rel` attribute string.
|
|
351
|
-
*/
|
|
352
|
-
_relMerge(relAttr) {
|
|
353
|
-
const current = this.currentRel;
|
|
354
|
-
if (!relAttr) return current.join(' ');
|
|
355
|
-
|
|
356
|
-
if (/^only:/.test(relAttr)) {
|
|
357
|
-
relAttr = relAttr.replace(/^only:/, '').trim();
|
|
358
|
-
this.currentRel = relAttr.split(' ');
|
|
359
|
-
return relAttr;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
const rels = relAttr.split(' ');
|
|
363
|
-
for (let i = 0, len = rels.length; i < len; i++) {
|
|
364
|
-
if (!current.includes(rels[i])) current.push(rels[i]);
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
return current.join(' ');
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
/**
|
|
371
|
-
* @private
|
|
372
|
-
* @description Removes the specified `rel` attribute from the current list.
|
|
373
|
-
* @param {string} relAttr - The `rel` attribute to remove.
|
|
374
|
-
* @returns {string} - The updated `rel` attribute string.
|
|
375
|
-
*/
|
|
376
|
-
_relDelete(relAttr) {
|
|
377
|
-
if (!relAttr) return this.currentRel.join(' ');
|
|
378
|
-
if (/^only:/.test(relAttr)) relAttr = relAttr.replace(/^only:/, '').trim();
|
|
379
|
-
|
|
380
|
-
const rels = this.currentRel.join(' ').replace(RegExp(relAttr + '\\s*'), '');
|
|
381
|
-
this.currentRel = rels.split(' ');
|
|
382
|
-
return rels;
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
/**
|
|
386
|
-
* @private
|
|
387
|
-
* @description Registers a newly uploaded file and sets its URL in the modal form.
|
|
388
|
-
* @param {Object<string, *>} response - The response object from the file upload request.
|
|
389
|
-
*/
|
|
390
|
-
_register(response) {
|
|
391
|
-
const file = response.result[0];
|
|
392
|
-
this.linkValue = this.preview.textContent = this.urlInput.value = file.url;
|
|
393
|
-
this.displayInput.value = file.name;
|
|
394
|
-
this.downloadCheck.checked = true;
|
|
395
|
-
this.download.style.display = 'block';
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
/**
|
|
399
|
-
* @private
|
|
400
|
-
* @description Handles file upload errors.
|
|
401
|
-
* @param {Object<string, *>} response - The error response object.
|
|
402
|
-
* @returns {Promise<void>}
|
|
403
|
-
*/
|
|
404
|
-
async _error(response) {
|
|
405
|
-
const message = await this.triggerEvent('onFileUploadError', { error: response });
|
|
406
|
-
if (message === false) return;
|
|
407
|
-
const err = message === NO_EVENT ? response.errorMessage : message || response.errorMessage;
|
|
408
|
-
this.ui.alertOpen(err, 'error');
|
|
409
|
-
console.error('[SUNEDITOR.plugin.fileUpload.error]', err);
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
/**
|
|
413
|
-
* @description Handles the callback after a file upload completes.
|
|
414
|
-
* @param {XMLHttpRequest} xmlHttp - The XMLHttpRequest object containing the response.
|
|
415
|
-
*/
|
|
416
|
-
_uploadCallBack(xmlHttp) {
|
|
417
|
-
const response = JSON.parse(xmlHttp.responseText);
|
|
418
|
-
if (response.errorMessage) {
|
|
419
|
-
this._error(response);
|
|
420
|
-
} else {
|
|
421
|
-
this._register(response);
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
/**
|
|
426
|
-
* @description Handles file input change events.
|
|
427
|
-
* @param {InputEvent} e - The change event object.
|
|
428
|
-
*/
|
|
429
|
-
async #OnChangeFile(e) {
|
|
430
|
-
/** @type {HTMLInputElement} */
|
|
431
|
-
const eventTarget = dom.query.getEventTarget(e);
|
|
432
|
-
const files = eventTarget.files;
|
|
433
|
-
if (!files[0]) return;
|
|
434
|
-
|
|
435
|
-
const fileInfo = {
|
|
436
|
-
url: this.uploadUrl,
|
|
437
|
-
uploadHeaders: this.uploadHeaders,
|
|
438
|
-
files
|
|
439
|
-
};
|
|
440
|
-
|
|
441
|
-
const handler = async function (infos, newInfos) {
|
|
442
|
-
infos = newInfos || infos;
|
|
443
|
-
const xmlHttp = await this.fileManager.asyncUpload(infos.url, infos.uploadHeaders, infos.files);
|
|
444
|
-
this._uploadCallBack(xmlHttp);
|
|
445
|
-
}.bind(this, fileInfo);
|
|
446
|
-
// se-ts-ignore
|
|
447
|
-
void this._uploadCallBack;
|
|
448
|
-
|
|
449
|
-
const result = await this.triggerEvent('onFileUploadBefore', {
|
|
450
|
-
info: fileInfo,
|
|
451
|
-
handler
|
|
452
|
-
});
|
|
453
|
-
|
|
454
|
-
if (result === undefined) return true;
|
|
455
|
-
if (result === false) return false;
|
|
456
|
-
if (result !== null && typeof result === 'object') handler(result);
|
|
457
|
-
|
|
458
|
-
if (result === true || result === NO_EVENT) handler(null);
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
/**
|
|
462
|
-
* @description Opens the `rel` attribute selection menu.
|
|
463
|
-
*/
|
|
464
|
-
#OnClick_relbutton() {
|
|
465
|
-
this.selectMenu_rel.open(this.options.get('_rtl') ? 'left-middle' : '');
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
/**
|
|
469
|
-
* @description Sets the selected bookmark as the URL.
|
|
470
|
-
* @param {HTMLElement} item - The selected bookmark element.
|
|
471
|
-
*/
|
|
472
|
-
#SetHeaderBookmark(item) {
|
|
473
|
-
const id = item.id || 'h_' + Math.random().toString().replace(/.+\./, '');
|
|
474
|
-
item.id = id;
|
|
475
|
-
this.urlInput.value = '#' + id;
|
|
476
|
-
|
|
477
|
-
this._setLinkPreview(this.urlInput.value);
|
|
478
|
-
this.selectMenu_bookmark.close();
|
|
479
|
-
this.urlInput.focus();
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
/**
|
|
483
|
-
* @param {HTMLElement} item - The selected `rel` attribute element.
|
|
484
|
-
*/
|
|
485
|
-
#SetRelItem(item) {
|
|
486
|
-
const cmd = item.getAttribute('data-command');
|
|
487
|
-
if (!cmd) return;
|
|
488
|
-
|
|
489
|
-
const current = this.currentRel;
|
|
490
|
-
const index = current.indexOf(cmd);
|
|
491
|
-
if (index === -1) current.push(cmd);
|
|
492
|
-
else current.splice(index, 1);
|
|
493
|
-
|
|
494
|
-
this.relPreview.title = this.relPreview.textContent = current.join(', ');
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
/**
|
|
498
|
-
* @param {InputEvent} e - Event object
|
|
499
|
-
*/
|
|
500
|
-
#OnChange_displayInput(e) {
|
|
501
|
-
/** @type {HTMLInputElement} */
|
|
502
|
-
const eventTarget = dom.query.getEventTarget(e);
|
|
503
|
-
this._change = !!eventTarget.value.trim();
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
/**
|
|
507
|
-
* @param {InputEvent} e - Event object
|
|
508
|
-
*/
|
|
509
|
-
#OnChange_urlInput(e) {
|
|
510
|
-
/** @type {HTMLInputElement} */
|
|
511
|
-
const eventTarget = dom.query.getEventTarget(e);
|
|
512
|
-
const value = eventTarget.value.trim();
|
|
513
|
-
this._setLinkPreview(value);
|
|
514
|
-
if (this._selfPathBookmark(value)) this._createBookmarkList(value);
|
|
515
|
-
else this.selectMenu_bookmark.close();
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
#OnFocus_urlInput() {
|
|
519
|
-
const value = this.urlInput.value;
|
|
520
|
-
if (this._selfPathBookmark(value)) this._createBookmarkList(value);
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
#OnClick_bookmarkButton() {
|
|
524
|
-
let url = this.urlInput.value;
|
|
525
|
-
if (this._selfPathBookmark(url)) {
|
|
526
|
-
url = url.substring(1);
|
|
527
|
-
this.bookmark.style.display = 'none';
|
|
528
|
-
dom.utils.removeClass(this.bookmarkButton, 'active');
|
|
529
|
-
} else {
|
|
530
|
-
url = '#' + url;
|
|
531
|
-
this.bookmark.style.display = 'block';
|
|
532
|
-
dom.utils.addClass(this.bookmarkButton, 'active');
|
|
533
|
-
this.downloadCheck.checked = false;
|
|
534
|
-
this.download.style.display = 'none';
|
|
535
|
-
this._createBookmarkList(url);
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
this.urlInput.value = url;
|
|
539
|
-
this._setLinkPreview(url);
|
|
540
|
-
this.urlInput.focus();
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
/**
|
|
544
|
-
* @param {InputEvent} e - Event object
|
|
545
|
-
*/
|
|
546
|
-
#OnChange_newWindowCheck(e) {
|
|
547
|
-
if (typeof this.defaultRel.check_new_window !== 'string') return;
|
|
548
|
-
/** @type {HTMLInputElement} */
|
|
549
|
-
const eventTarget = dom.query.getEventTarget(e);
|
|
550
|
-
if (eventTarget.checked) {
|
|
551
|
-
this._setRel(this._relMerge(this.defaultRel.check_new_window));
|
|
552
|
-
} else {
|
|
553
|
-
this._setRel(this._relDelete(this.defaultRel.check_new_window));
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
/**
|
|
558
|
-
* @param {InputEvent} e - Event object
|
|
559
|
-
*/
|
|
560
|
-
#OnChange_downloadCheck(e) {
|
|
561
|
-
/** @type {HTMLInputElement} */
|
|
562
|
-
const eventTarget = dom.query.getEventTarget(e);
|
|
563
|
-
if (eventTarget.checked) {
|
|
564
|
-
this.download.style.display = 'block';
|
|
565
|
-
this.bookmark.style.display = 'none';
|
|
566
|
-
dom.utils.removeClass(this.bookmarkButton, 'active');
|
|
567
|
-
this.linkValue = this.preview.textContent = this.urlInput.value = this.urlInput.value.replace(/^#+/, '');
|
|
568
|
-
if (typeof this.defaultRel.check_bookmark === 'string') {
|
|
569
|
-
this._setRel(this._relMerge(this.defaultRel.check_bookmark));
|
|
570
|
-
}
|
|
571
|
-
} else {
|
|
572
|
-
this.download.style.display = 'none';
|
|
573
|
-
if (typeof this.defaultRel.check_bookmark === 'string') {
|
|
574
|
-
this._setRel(this._relDelete(this.defaultRel.check_bookmark));
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
/**
|
|
581
|
-
* @private
|
|
582
|
-
* @param {__se__EditorCore} editor - Editor instance
|
|
583
|
-
* @param {ModalAnchorEditorParams} params - ModalAnchorEditor options
|
|
584
|
-
* @param {Array<string>} relList - REL attribute list
|
|
585
|
-
* @returns {HTMLElement} - Modal form element
|
|
586
|
-
*/
|
|
587
|
-
function CreatetModalForm(editor, params, relList) {
|
|
588
|
-
const lang = editor.lang;
|
|
589
|
-
const icons = editor.icons;
|
|
590
|
-
const textDisplayShow = params.textToDisplay ? '' : 'style="display: none;"';
|
|
591
|
-
const titleShow = params.title ? '' : 'style="display: none;"';
|
|
592
|
-
|
|
593
|
-
let html = /*html*/ `
|
|
594
|
-
<div class="se-modal-body">
|
|
595
|
-
<div class="se-modal-form">
|
|
596
|
-
<label>${lang.link_modal_url}</label>
|
|
597
|
-
<div class="se-modal-form-files">
|
|
598
|
-
<input data-focus class="se-input-form se-input-url" type="text" placeholder="${editor.options.get('protocol') || ''}" />
|
|
599
|
-
${
|
|
600
|
-
params.enableFileUpload
|
|
601
|
-
? `<button type="button" class="se-btn se-tooltip se-modal-files-edge-button _se_upload_button" aria-label="${lang.fileUpload}">
|
|
602
|
-
${icons.file_upload}
|
|
603
|
-
${dom.utils.createTooltipInner(lang.fileUpload)}
|
|
604
|
-
</button>`
|
|
605
|
-
: ''
|
|
606
|
-
}
|
|
607
|
-
<button type="button" class="se-btn se-tooltip se-modal-files-edge-button _se_bookmark_button" aria-label="${lang.link_modal_bookmark}">
|
|
608
|
-
${icons.bookmark}
|
|
609
|
-
${dom.utils.createTooltipInner(lang.link_modal_bookmark)}
|
|
610
|
-
</button>
|
|
611
|
-
</div>
|
|
612
|
-
<div class="se-anchor-preview-form">
|
|
613
|
-
<span class="se-svg se-anchor-preview-icon _se_anchor_bookmark_icon">${icons.bookmark}</span>
|
|
614
|
-
<span class="se-svg se-anchor-preview-icon _se_anchor_download_icon">${icons.download}</span>
|
|
615
|
-
<pre class="se-link-preview"></pre>
|
|
616
|
-
</div>
|
|
617
|
-
<label ${textDisplayShow}>${lang.link_modal_text}</label>
|
|
618
|
-
<input class="se-input-form _se_display_text" type="text" ${textDisplayShow} />
|
|
619
|
-
<label ${titleShow}>${lang.title}</label>
|
|
620
|
-
<input class="se-input-form _se_title" type="text" ${titleShow} />
|
|
621
|
-
</div>
|
|
622
|
-
<div class="se-modal-form-footer">
|
|
623
|
-
<label><input type="checkbox" class="se-modal-btn-check _se_anchor_check" /> ${lang.link_modal_newWindowCheck}</label>
|
|
624
|
-
<label><input type="checkbox" class="se-modal-btn-check _se_anchor_download" /> ${lang.link_modal_downloadLinkCheck}</label>`;
|
|
625
|
-
|
|
626
|
-
if (relList.length > 0) {
|
|
627
|
-
html += /*html*/ `
|
|
628
|
-
<div class="se-anchor-rel">
|
|
629
|
-
<button type="button" class="se-btn se-tooltip se-anchor-rel-btn" title="${lang.link_modal_relAttribute}" aria-label="${lang.link_modal_relAttribute}">
|
|
630
|
-
${icons.link_rel}
|
|
631
|
-
${dom.utils.createTooltipInner(lang.link_modal_relAttribute)}
|
|
632
|
-
</button>
|
|
633
|
-
<div class="se-anchor-rel-wrapper"><pre class="se-link-preview se-anchor-rel-preview"></pre></div>
|
|
634
|
-
</div>
|
|
635
|
-
</div>`;
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
html += '</div></div>';
|
|
639
|
-
|
|
640
|
-
return dom.utils.createElement('DIV', null, html);
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
export default ModalAnchorEditor;
|
|
1
|
+
import EditorInjector from '../editorInjector';
|
|
2
|
+
import SelectMenu from './SelectMenu';
|
|
3
|
+
import FileManager from './FileManager';
|
|
4
|
+
import { dom, numbers, env, unicode } from '../helper';
|
|
5
|
+
const { NO_EVENT } = env;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {{default?: string, check_new_window?: string, check_bookmark?: string}} RELAttr
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @typedef {Object} ModalAnchorEditorParams
|
|
13
|
+
* @property {boolean} [title=false] - Modal title display.
|
|
14
|
+
* @property {boolean} [textToDisplay=''] - Create Text to display input.
|
|
15
|
+
* @property {boolean} [openNewWindow=false] - Default checked value of the "Open in new window" checkbox.
|
|
16
|
+
* @property {boolean} [noAutoPrefix=false] - If true, disables the automatic prefixing of the host URL to the value of the link.
|
|
17
|
+
* @property {Array<string>} [relList=[]] - The "rel" attribute list of anchor tag.
|
|
18
|
+
* @property {RELAttr} [defaultRel={}] - Default "rel" attributes of anchor tag.
|
|
19
|
+
* @property {string=} uploadUrl - File upload URL.
|
|
20
|
+
* @property {Object<string, string>=} uploadHeaders - File upload headers.
|
|
21
|
+
* @property {number=} uploadSizeLimit - File upload size limit.
|
|
22
|
+
* @property {number=} uploadSingleSizeLimit - File upload single size limit.
|
|
23
|
+
* @property {string=} acceptedFormats - File upload accepted formats.
|
|
24
|
+
* @property {boolean=} enableFileUpload - If true, enables file upload.
|
|
25
|
+
* @example "REL" structure
|
|
26
|
+
{
|
|
27
|
+
default: 'nofollow', // Default rel
|
|
28
|
+
check_new_window: 'noreferrer noopener', // When "open new window" is checked
|
|
29
|
+
check_bookmark: 'bookmark' // When "bookmark" is checked
|
|
30
|
+
}
|
|
31
|
+
If true, disables the automatic prefixing of the host URL to the value of the link.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @class
|
|
36
|
+
* @description Modal form Anchor tag editor
|
|
37
|
+
* - Use it by inserting it into Modal in a plugin that uses Modal.
|
|
38
|
+
*/
|
|
39
|
+
class ModalAnchorEditor extends EditorInjector {
|
|
40
|
+
/**
|
|
41
|
+
* @constructor
|
|
42
|
+
* @param {*} inst The instance object that called the constructor.
|
|
43
|
+
* @param {Node} modalForm The modal form element
|
|
44
|
+
* @param {ModalAnchorEditorParams} params ModalAnchorEditor options
|
|
45
|
+
*/
|
|
46
|
+
constructor(inst, modalForm, params) {
|
|
47
|
+
// plugin bisic properties
|
|
48
|
+
super(inst.editor);
|
|
49
|
+
|
|
50
|
+
// params
|
|
51
|
+
this.openNewWindow = !!params.openNewWindow;
|
|
52
|
+
this.relList = Array.isArray(params.relList) ? params.relList : [];
|
|
53
|
+
this.defaultRel = params.defaultRel || {};
|
|
54
|
+
this.noAutoPrefix = !!params.noAutoPrefix;
|
|
55
|
+
// file upload
|
|
56
|
+
if (params.enableFileUpload) {
|
|
57
|
+
this.uploadUrl = typeof params.uploadUrl === 'string' ? params.uploadUrl : null;
|
|
58
|
+
this.uploadHeaders = params.uploadHeaders || null;
|
|
59
|
+
this.uploadSizeLimit = numbers.get(params.uploadSizeLimit, 0) || null;
|
|
60
|
+
this.uploadSingleSizeLimit = numbers.get(params.uploadSingleSizeLimit, 0) || null;
|
|
61
|
+
this.input = dom.utils.createElement('input', { type: 'file', accept: params.acceptedFormats || '*' });
|
|
62
|
+
this.eventManager.addEvent(this.input, 'change', this.#OnChangeFile.bind(this));
|
|
63
|
+
// file manager
|
|
64
|
+
this.fileManager = new FileManager(this, {
|
|
65
|
+
query: 'a[download]:not([data-se-file-download])',
|
|
66
|
+
loadHandler: this.events.onFileLoad,
|
|
67
|
+
eventHandler: this.events.onFileAction
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// create HTML
|
|
72
|
+
const forms = CreatetModalForm(inst.editor, params, this.relList);
|
|
73
|
+
|
|
74
|
+
// members
|
|
75
|
+
this.kink = inst.constructor.key || inst.constructor.name;
|
|
76
|
+
this.inst = inst;
|
|
77
|
+
this.modalForm = /** @type {HTMLElement} */ (modalForm);
|
|
78
|
+
this.host = (this._w.location.origin + this._w.location.pathname).replace(/\/$/, '');
|
|
79
|
+
|
|
80
|
+
/** @type {HTMLInputElement} */
|
|
81
|
+
this.urlInput = forms.querySelector('.se-input-url');
|
|
82
|
+
/** @type {HTMLInputElement} */
|
|
83
|
+
this.displayInput = forms.querySelector('._se_display_text');
|
|
84
|
+
/** @type {HTMLInputElement} */
|
|
85
|
+
this.titleInput = forms.querySelector('._se_title');
|
|
86
|
+
/** @type {HTMLInputElement} */
|
|
87
|
+
this.newWindowCheck = forms.querySelector('._se_anchor_check');
|
|
88
|
+
/** @type {HTMLInputElement} */
|
|
89
|
+
this.downloadCheck = forms.querySelector('._se_anchor_download');
|
|
90
|
+
/** @type {HTMLElement} */
|
|
91
|
+
this.download = forms.querySelector('._se_anchor_download_icon');
|
|
92
|
+
/** @type {HTMLElement} */
|
|
93
|
+
this.preview = forms.querySelector('.se-link-preview');
|
|
94
|
+
/** @type {HTMLElement} */
|
|
95
|
+
this.bookmark = forms.querySelector('._se_anchor_bookmark_icon');
|
|
96
|
+
/** @type {HTMLButtonElement} */
|
|
97
|
+
this.bookmarkButton = forms.querySelector('._se_bookmark_button');
|
|
98
|
+
|
|
99
|
+
this.currentRel = [];
|
|
100
|
+
this.currentTarget = null;
|
|
101
|
+
this.linkValue = '';
|
|
102
|
+
this._change = false;
|
|
103
|
+
this._isRel = this.relList.length > 0;
|
|
104
|
+
// members - rel
|
|
105
|
+
if (this._isRel) {
|
|
106
|
+
/** @type {HTMLButtonElement} */
|
|
107
|
+
this.relButton = forms.querySelector('.se-anchor-rel-btn');
|
|
108
|
+
/** @type {HTMLElement} */
|
|
109
|
+
this.relPreview = forms.querySelector('.se-anchor-rel-preview');
|
|
110
|
+
|
|
111
|
+
const relList = this.relList;
|
|
112
|
+
const defaultRel = (this.defaultRel.default || '').split(' ');
|
|
113
|
+
const list = [];
|
|
114
|
+
for (let i = 0, len = relList.length, rel; i < len; i++) {
|
|
115
|
+
rel = relList[i];
|
|
116
|
+
list.push(
|
|
117
|
+
dom.utils.createElement(
|
|
118
|
+
'BUTTON',
|
|
119
|
+
{
|
|
120
|
+
type: 'button',
|
|
121
|
+
class: 'se-btn-list' + (defaultRel.includes(rel) ? ' se-checked' : ''),
|
|
122
|
+
'data-command': rel,
|
|
123
|
+
title: rel,
|
|
124
|
+
'aria-label': rel
|
|
125
|
+
},
|
|
126
|
+
rel + '<span class="se-svg">' + this.icons.checked + '</span>'
|
|
127
|
+
)
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
this.selectMenu_rel = new SelectMenu(this, { checkList: true, position: 'right-middle', dir: 'ltr' });
|
|
131
|
+
this.selectMenu_rel.on(this.relButton, this.#SetRelItem.bind(this));
|
|
132
|
+
this.selectMenu_rel.create(list);
|
|
133
|
+
this.eventManager.addEvent(this.relButton, 'click', this.#OnClick_relbutton.bind(this));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// init
|
|
137
|
+
this.modalForm.querySelector('.se-anchor-editor').appendChild(forms);
|
|
138
|
+
this.selectMenu_bookmark = new SelectMenu(this, { checkList: false, position: 'bottom-left', dir: 'ltr' });
|
|
139
|
+
this.selectMenu_bookmark.on(this.urlInput, this.#SetHeaderBookmark.bind(this));
|
|
140
|
+
this.eventManager.addEvent(this.newWindowCheck, 'change', this.#OnChange_newWindowCheck.bind(this));
|
|
141
|
+
this.eventManager.addEvent(this.downloadCheck, 'change', this.#OnChange_downloadCheck.bind(this));
|
|
142
|
+
this.eventManager.addEvent(this.displayInput, 'input', this.#OnChange_displayInput.bind(this));
|
|
143
|
+
this.eventManager.addEvent(this.urlInput, 'input', this.#OnChange_urlInput.bind(this));
|
|
144
|
+
this.eventManager.addEvent(this.urlInput, 'focus', this.#OnFocus_urlInput.bind(this));
|
|
145
|
+
this.eventManager.addEvent(this.bookmarkButton, 'click', this.#OnClick_bookmarkButton.bind(this));
|
|
146
|
+
this.eventManager.addEvent(forms.querySelector('._se_upload_button'), 'click', () => this.input.click());
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* @description Initialize.
|
|
150
|
+
* - Sets the current anchor element to be edited.
|
|
151
|
+
* @param {Node} element Modal target element
|
|
152
|
+
*/
|
|
153
|
+
set(element) {
|
|
154
|
+
this.currentTarget = /** @type {HTMLAnchorElement} */ (element);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* @description Opens the anchor editor modal and populates it with data.
|
|
159
|
+
* @param {boolean} isUpdate - Indicates whether an existing anchor is being updated (`true`) or a new one is being created (`false`).
|
|
160
|
+
*/
|
|
161
|
+
on(isUpdate) {
|
|
162
|
+
if (!isUpdate) {
|
|
163
|
+
this.init();
|
|
164
|
+
this.displayInput.value = this.selection.get().toString().trim();
|
|
165
|
+
this.newWindowCheck.checked = this.openNewWindow;
|
|
166
|
+
this.titleInput.value = '';
|
|
167
|
+
} else if (this.currentTarget) {
|
|
168
|
+
const href = this.currentTarget.href;
|
|
169
|
+
this.linkValue = this.preview.textContent = this.urlInput.value = this._selfPathBookmark(href) ? href.substring(href.lastIndexOf('#')) : href;
|
|
170
|
+
this.displayInput.value = this.currentTarget.textContent;
|
|
171
|
+
this.titleInput.value = this.currentTarget.title;
|
|
172
|
+
this.newWindowCheck.checked = /_blank/i.test(this.currentTarget.target) ? true : false;
|
|
173
|
+
this.downloadCheck.checked = !!this.currentTarget.download;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
this._setRel(isUpdate && this.currentTarget ? this.currentTarget.rel : this.defaultRel.default || '');
|
|
177
|
+
this._setLinkPreview(this.linkValue);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* @description Creates an anchor (`<a>`) element with the specified attributes.
|
|
182
|
+
* @param {boolean} notText - If `true`, the anchor will not contain text content.
|
|
183
|
+
* @returns {HTMLElement|null} - The newly created anchor element, or `null` if the URL is empty.
|
|
184
|
+
*/
|
|
185
|
+
create(notText) {
|
|
186
|
+
if (this.linkValue.length === 0) return null;
|
|
187
|
+
|
|
188
|
+
const url = this.linkValue;
|
|
189
|
+
const displayText = this.displayInput.value.length === 0 ? url : this.displayInput.value;
|
|
190
|
+
|
|
191
|
+
const oA = /** @type {HTMLAnchorElement} */ (this.currentTarget || dom.utils.createElement('A'));
|
|
192
|
+
this._updateAnchor(oA, url, displayText, this.titleInput.value, notText);
|
|
193
|
+
this.linkValue = this.preview.textContent = this.urlInput.value = this.displayInput.value = '';
|
|
194
|
+
|
|
195
|
+
return oA;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* @description Resets the ModalAnchorEditor to its initial state.
|
|
200
|
+
*/
|
|
201
|
+
init() {
|
|
202
|
+
this.currentTarget = null;
|
|
203
|
+
this.linkValue = this.preview.textContent = this.urlInput.value = '';
|
|
204
|
+
this.displayInput.value = '';
|
|
205
|
+
this.newWindowCheck.checked = false;
|
|
206
|
+
this.downloadCheck.checked = false;
|
|
207
|
+
this._change = false;
|
|
208
|
+
this._setRel(this.defaultRel.default || '');
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* @private
|
|
213
|
+
* @description Updates the anchor element with new attributes.
|
|
214
|
+
* @param {HTMLAnchorElement} anchor - The anchor (`<a>`) element to update.
|
|
215
|
+
* @param {string} url - The URL for the anchor's `href` attribute.
|
|
216
|
+
* @param {string} displayText - The text to be displayed inside the anchor.
|
|
217
|
+
* @param {string} title - The tooltip text (title attribute).
|
|
218
|
+
* @param {boolean} notText - If `true`, the anchor will not contain text content.
|
|
219
|
+
*/
|
|
220
|
+
_updateAnchor(anchor, url, displayText, title, notText) {
|
|
221
|
+
// download
|
|
222
|
+
if (!this._selfPathBookmark(url) && this.downloadCheck.checked) {
|
|
223
|
+
anchor.setAttribute('download', displayText || url);
|
|
224
|
+
} else {
|
|
225
|
+
anchor.removeAttribute('download');
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// new window
|
|
229
|
+
if (this.newWindowCheck.checked) anchor.target = '_blank';
|
|
230
|
+
else anchor.removeAttribute('target');
|
|
231
|
+
|
|
232
|
+
// rel
|
|
233
|
+
const rel = this.currentRel.join(' ');
|
|
234
|
+
if (!rel) anchor.removeAttribute('rel');
|
|
235
|
+
else anchor.rel = rel;
|
|
236
|
+
|
|
237
|
+
// set url
|
|
238
|
+
anchor.href = url;
|
|
239
|
+
if (title) anchor.title = title;
|
|
240
|
+
else anchor.removeAttribute('title');
|
|
241
|
+
|
|
242
|
+
if (notText) {
|
|
243
|
+
if (anchor.children.length === 0) anchor.textContent = '';
|
|
244
|
+
} else {
|
|
245
|
+
anchor.textContent = displayText;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* @private
|
|
251
|
+
* @description Checks if the given path is an internal bookmark.
|
|
252
|
+
* @param {string} path - The URL or anchor link.
|
|
253
|
+
* @returns {boolean} - `true` if the path is an internal bookmark, otherwise `false`.
|
|
254
|
+
*/
|
|
255
|
+
_selfPathBookmark(path) {
|
|
256
|
+
const href = this._w.location.href.replace(/\/$/, '');
|
|
257
|
+
return path.indexOf('#') === 0 || (path.indexOf(href) === 0 && path.indexOf('#') === (!href.includes('#') ? href.length : href.substring(0, href.indexOf('#')).length));
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* @private
|
|
262
|
+
* @description Updates the `rel` attribute list in the modal and preview.
|
|
263
|
+
* @param {string} relAttr - The `rel` attribute string to set.
|
|
264
|
+
*/
|
|
265
|
+
_setRel(relAttr) {
|
|
266
|
+
if (!this._isRel) return;
|
|
267
|
+
|
|
268
|
+
const rels = (this.currentRel = !relAttr ? [] : relAttr.split(' '));
|
|
269
|
+
const checkedRel = this.selectMenu_rel.form.querySelectorAll('button');
|
|
270
|
+
for (let i = 0, len = checkedRel.length, cmd; i < len; i++) {
|
|
271
|
+
cmd = checkedRel[i].getAttribute('data-command');
|
|
272
|
+
if (rels.includes(cmd)) {
|
|
273
|
+
dom.utils.addClass(checkedRel[i], 'se-checked');
|
|
274
|
+
} else {
|
|
275
|
+
dom.utils.removeClass(checkedRel[i], 'se-checked');
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
this.relPreview.title = this.relPreview.textContent = rels.join(' ');
|
|
280
|
+
if (rels.length > 0) {
|
|
281
|
+
dom.utils.addClass(this.relButton, 'on');
|
|
282
|
+
} else {
|
|
283
|
+
dom.utils.removeClass(this.relButton, 'on');
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* @private
|
|
289
|
+
* @description Generates a list of bookmark headers within the editor.
|
|
290
|
+
* @param {string} urlValue - The current URL input value.
|
|
291
|
+
*/
|
|
292
|
+
_createBookmarkList(urlValue) {
|
|
293
|
+
const headers = dom.query.getListChildren(this.editor.frameContext.get('wysiwyg'), (current) => /h[1-6]/i.test(current.nodeName) || (dom.check.isAnchor(current) && !!current.id));
|
|
294
|
+
if (headers.length === 0) return;
|
|
295
|
+
|
|
296
|
+
const valueRegExp = new RegExp(`^${urlValue.replace(/^#/, '')}`, 'i');
|
|
297
|
+
const list = [];
|
|
298
|
+
const menus = [];
|
|
299
|
+
for (let i = 0, len = headers.length, v; i < len; i++) {
|
|
300
|
+
v = headers[i];
|
|
301
|
+
if (!valueRegExp.test(v.textContent)) continue;
|
|
302
|
+
list.push(v);
|
|
303
|
+
menus.push(dom.check.isAnchor(v) ? `<div><span class="se-text-prefix-icon">${this.icons.bookmark_anchor}</span>${v.id}</div>` : `<div style="${v.style.cssText}">${v.textContent}</div>`);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (list.length === 0) {
|
|
307
|
+
this.selectMenu_bookmark.close();
|
|
308
|
+
} else {
|
|
309
|
+
this.selectMenu_bookmark.create(list, menus);
|
|
310
|
+
this.selectMenu_bookmark.open(this.options.get('_rtl') ? 'bottom-right' : '');
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* @private
|
|
316
|
+
* @description Updates the preview of the anchor link.
|
|
317
|
+
* @param {string} value - The current URL value.
|
|
318
|
+
*/
|
|
319
|
+
_setLinkPreview(value) {
|
|
320
|
+
const preview = this.preview;
|
|
321
|
+
const protocol = this.options.get('defaultUrlProtocol');
|
|
322
|
+
const noPrefix = this.noAutoPrefix;
|
|
323
|
+
const reservedProtocol = /^(mailto:|tel:|sms:|https*:\/\/|#)/.test(value) || value.indexOf(protocol) === 0;
|
|
324
|
+
const sameProtocol = !protocol ? false : RegExp('^' + unicode.escapeStringRegexp(value.substring(0, protocol.length))).test(protocol);
|
|
325
|
+
|
|
326
|
+
value =
|
|
327
|
+
this.linkValue =
|
|
328
|
+
preview.textContent =
|
|
329
|
+
!value ? '' : noPrefix ? value : protocol && !reservedProtocol && !sameProtocol ? protocol + value : reservedProtocol ? value : /^www\./.test(value) ? 'http://' + value : this.host + (/^\//.test(value) ? '' : '/') + value;
|
|
330
|
+
|
|
331
|
+
if (this._selfPathBookmark(value)) {
|
|
332
|
+
this.bookmark.style.display = 'block';
|
|
333
|
+
dom.utils.addClass(this.bookmarkButton, 'active');
|
|
334
|
+
} else {
|
|
335
|
+
this.bookmark.style.display = 'none';
|
|
336
|
+
dom.utils.removeClass(this.bookmarkButton, 'active');
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (!this._selfPathBookmark(value) && this.downloadCheck.checked) {
|
|
340
|
+
this.download.style.display = 'block';
|
|
341
|
+
} else {
|
|
342
|
+
this.download.style.display = 'none';
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* @private
|
|
348
|
+
* @description Merges the given `rel` attribute value with the current list.
|
|
349
|
+
* @param {string} relAttr - The `rel` attribute to merge.
|
|
350
|
+
* @returns {string} - The updated `rel` attribute string.
|
|
351
|
+
*/
|
|
352
|
+
_relMerge(relAttr) {
|
|
353
|
+
const current = this.currentRel;
|
|
354
|
+
if (!relAttr) return current.join(' ');
|
|
355
|
+
|
|
356
|
+
if (/^only:/.test(relAttr)) {
|
|
357
|
+
relAttr = relAttr.replace(/^only:/, '').trim();
|
|
358
|
+
this.currentRel = relAttr.split(' ');
|
|
359
|
+
return relAttr;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const rels = relAttr.split(' ');
|
|
363
|
+
for (let i = 0, len = rels.length; i < len; i++) {
|
|
364
|
+
if (!current.includes(rels[i])) current.push(rels[i]);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
return current.join(' ');
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* @private
|
|
372
|
+
* @description Removes the specified `rel` attribute from the current list.
|
|
373
|
+
* @param {string} relAttr - The `rel` attribute to remove.
|
|
374
|
+
* @returns {string} - The updated `rel` attribute string.
|
|
375
|
+
*/
|
|
376
|
+
_relDelete(relAttr) {
|
|
377
|
+
if (!relAttr) return this.currentRel.join(' ');
|
|
378
|
+
if (/^only:/.test(relAttr)) relAttr = relAttr.replace(/^only:/, '').trim();
|
|
379
|
+
|
|
380
|
+
const rels = this.currentRel.join(' ').replace(RegExp(relAttr + '\\s*'), '');
|
|
381
|
+
this.currentRel = rels.split(' ');
|
|
382
|
+
return rels;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* @private
|
|
387
|
+
* @description Registers a newly uploaded file and sets its URL in the modal form.
|
|
388
|
+
* @param {Object<string, *>} response - The response object from the file upload request.
|
|
389
|
+
*/
|
|
390
|
+
_register(response) {
|
|
391
|
+
const file = response.result[0];
|
|
392
|
+
this.linkValue = this.preview.textContent = this.urlInput.value = file.url;
|
|
393
|
+
this.displayInput.value = file.name;
|
|
394
|
+
this.downloadCheck.checked = true;
|
|
395
|
+
this.download.style.display = 'block';
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* @private
|
|
400
|
+
* @description Handles file upload errors.
|
|
401
|
+
* @param {Object<string, *>} response - The error response object.
|
|
402
|
+
* @returns {Promise<void>}
|
|
403
|
+
*/
|
|
404
|
+
async _error(response) {
|
|
405
|
+
const message = await this.triggerEvent('onFileUploadError', { error: response });
|
|
406
|
+
if (message === false) return;
|
|
407
|
+
const err = message === NO_EVENT ? response.errorMessage : message || response.errorMessage;
|
|
408
|
+
this.ui.alertOpen(err, 'error');
|
|
409
|
+
console.error('[SUNEDITOR.plugin.fileUpload.error]', err);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* @description Handles the callback after a file upload completes.
|
|
414
|
+
* @param {XMLHttpRequest} xmlHttp - The XMLHttpRequest object containing the response.
|
|
415
|
+
*/
|
|
416
|
+
_uploadCallBack(xmlHttp) {
|
|
417
|
+
const response = JSON.parse(xmlHttp.responseText);
|
|
418
|
+
if (response.errorMessage) {
|
|
419
|
+
this._error(response);
|
|
420
|
+
} else {
|
|
421
|
+
this._register(response);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* @description Handles file input change events.
|
|
427
|
+
* @param {InputEvent} e - The change event object.
|
|
428
|
+
*/
|
|
429
|
+
async #OnChangeFile(e) {
|
|
430
|
+
/** @type {HTMLInputElement} */
|
|
431
|
+
const eventTarget = dom.query.getEventTarget(e);
|
|
432
|
+
const files = eventTarget.files;
|
|
433
|
+
if (!files[0]) return;
|
|
434
|
+
|
|
435
|
+
const fileInfo = {
|
|
436
|
+
url: this.uploadUrl,
|
|
437
|
+
uploadHeaders: this.uploadHeaders,
|
|
438
|
+
files
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
const handler = async function (infos, newInfos) {
|
|
442
|
+
infos = newInfos || infos;
|
|
443
|
+
const xmlHttp = await this.fileManager.asyncUpload(infos.url, infos.uploadHeaders, infos.files);
|
|
444
|
+
this._uploadCallBack(xmlHttp);
|
|
445
|
+
}.bind(this, fileInfo);
|
|
446
|
+
// se-ts-ignore
|
|
447
|
+
void this._uploadCallBack;
|
|
448
|
+
|
|
449
|
+
const result = await this.triggerEvent('onFileUploadBefore', {
|
|
450
|
+
info: fileInfo,
|
|
451
|
+
handler
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
if (result === undefined) return true;
|
|
455
|
+
if (result === false) return false;
|
|
456
|
+
if (result !== null && typeof result === 'object') handler(result);
|
|
457
|
+
|
|
458
|
+
if (result === true || result === NO_EVENT) handler(null);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* @description Opens the `rel` attribute selection menu.
|
|
463
|
+
*/
|
|
464
|
+
#OnClick_relbutton() {
|
|
465
|
+
this.selectMenu_rel.open(this.options.get('_rtl') ? 'left-middle' : '');
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* @description Sets the selected bookmark as the URL.
|
|
470
|
+
* @param {HTMLElement} item - The selected bookmark element.
|
|
471
|
+
*/
|
|
472
|
+
#SetHeaderBookmark(item) {
|
|
473
|
+
const id = item.id || 'h_' + Math.random().toString().replace(/.+\./, '');
|
|
474
|
+
item.id = id;
|
|
475
|
+
this.urlInput.value = '#' + id;
|
|
476
|
+
|
|
477
|
+
this._setLinkPreview(this.urlInput.value);
|
|
478
|
+
this.selectMenu_bookmark.close();
|
|
479
|
+
this.urlInput.focus();
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* @param {HTMLElement} item - The selected `rel` attribute element.
|
|
484
|
+
*/
|
|
485
|
+
#SetRelItem(item) {
|
|
486
|
+
const cmd = item.getAttribute('data-command');
|
|
487
|
+
if (!cmd) return;
|
|
488
|
+
|
|
489
|
+
const current = this.currentRel;
|
|
490
|
+
const index = current.indexOf(cmd);
|
|
491
|
+
if (index === -1) current.push(cmd);
|
|
492
|
+
else current.splice(index, 1);
|
|
493
|
+
|
|
494
|
+
this.relPreview.title = this.relPreview.textContent = current.join(', ');
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* @param {InputEvent} e - Event object
|
|
499
|
+
*/
|
|
500
|
+
#OnChange_displayInput(e) {
|
|
501
|
+
/** @type {HTMLInputElement} */
|
|
502
|
+
const eventTarget = dom.query.getEventTarget(e);
|
|
503
|
+
this._change = !!eventTarget.value.trim();
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* @param {InputEvent} e - Event object
|
|
508
|
+
*/
|
|
509
|
+
#OnChange_urlInput(e) {
|
|
510
|
+
/** @type {HTMLInputElement} */
|
|
511
|
+
const eventTarget = dom.query.getEventTarget(e);
|
|
512
|
+
const value = eventTarget.value.trim();
|
|
513
|
+
this._setLinkPreview(value);
|
|
514
|
+
if (this._selfPathBookmark(value)) this._createBookmarkList(value);
|
|
515
|
+
else this.selectMenu_bookmark.close();
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
#OnFocus_urlInput() {
|
|
519
|
+
const value = this.urlInput.value;
|
|
520
|
+
if (this._selfPathBookmark(value)) this._createBookmarkList(value);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
#OnClick_bookmarkButton() {
|
|
524
|
+
let url = this.urlInput.value;
|
|
525
|
+
if (this._selfPathBookmark(url)) {
|
|
526
|
+
url = url.substring(1);
|
|
527
|
+
this.bookmark.style.display = 'none';
|
|
528
|
+
dom.utils.removeClass(this.bookmarkButton, 'active');
|
|
529
|
+
} else {
|
|
530
|
+
url = '#' + url;
|
|
531
|
+
this.bookmark.style.display = 'block';
|
|
532
|
+
dom.utils.addClass(this.bookmarkButton, 'active');
|
|
533
|
+
this.downloadCheck.checked = false;
|
|
534
|
+
this.download.style.display = 'none';
|
|
535
|
+
this._createBookmarkList(url);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
this.urlInput.value = url;
|
|
539
|
+
this._setLinkPreview(url);
|
|
540
|
+
this.urlInput.focus();
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* @param {InputEvent} e - Event object
|
|
545
|
+
*/
|
|
546
|
+
#OnChange_newWindowCheck(e) {
|
|
547
|
+
if (typeof this.defaultRel.check_new_window !== 'string') return;
|
|
548
|
+
/** @type {HTMLInputElement} */
|
|
549
|
+
const eventTarget = dom.query.getEventTarget(e);
|
|
550
|
+
if (eventTarget.checked) {
|
|
551
|
+
this._setRel(this._relMerge(this.defaultRel.check_new_window));
|
|
552
|
+
} else {
|
|
553
|
+
this._setRel(this._relDelete(this.defaultRel.check_new_window));
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* @param {InputEvent} e - Event object
|
|
559
|
+
*/
|
|
560
|
+
#OnChange_downloadCheck(e) {
|
|
561
|
+
/** @type {HTMLInputElement} */
|
|
562
|
+
const eventTarget = dom.query.getEventTarget(e);
|
|
563
|
+
if (eventTarget.checked) {
|
|
564
|
+
this.download.style.display = 'block';
|
|
565
|
+
this.bookmark.style.display = 'none';
|
|
566
|
+
dom.utils.removeClass(this.bookmarkButton, 'active');
|
|
567
|
+
this.linkValue = this.preview.textContent = this.urlInput.value = this.urlInput.value.replace(/^#+/, '');
|
|
568
|
+
if (typeof this.defaultRel.check_bookmark === 'string') {
|
|
569
|
+
this._setRel(this._relMerge(this.defaultRel.check_bookmark));
|
|
570
|
+
}
|
|
571
|
+
} else {
|
|
572
|
+
this.download.style.display = 'none';
|
|
573
|
+
if (typeof this.defaultRel.check_bookmark === 'string') {
|
|
574
|
+
this._setRel(this._relDelete(this.defaultRel.check_bookmark));
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* @private
|
|
582
|
+
* @param {__se__EditorCore} editor - Editor instance
|
|
583
|
+
* @param {ModalAnchorEditorParams} params - ModalAnchorEditor options
|
|
584
|
+
* @param {Array<string>} relList - REL attribute list
|
|
585
|
+
* @returns {HTMLElement} - Modal form element
|
|
586
|
+
*/
|
|
587
|
+
function CreatetModalForm(editor, params, relList) {
|
|
588
|
+
const lang = editor.lang;
|
|
589
|
+
const icons = editor.icons;
|
|
590
|
+
const textDisplayShow = params.textToDisplay ? '' : 'style="display: none;"';
|
|
591
|
+
const titleShow = params.title ? '' : 'style="display: none;"';
|
|
592
|
+
|
|
593
|
+
let html = /*html*/ `
|
|
594
|
+
<div class="se-modal-body">
|
|
595
|
+
<div class="se-modal-form">
|
|
596
|
+
<label>${lang.link_modal_url}</label>
|
|
597
|
+
<div class="se-modal-form-files">
|
|
598
|
+
<input data-focus class="se-input-form se-input-url" type="text" placeholder="${editor.options.get('protocol') || ''}" />
|
|
599
|
+
${
|
|
600
|
+
params.enableFileUpload
|
|
601
|
+
? `<button type="button" class="se-btn se-tooltip se-modal-files-edge-button _se_upload_button" aria-label="${lang.fileUpload}">
|
|
602
|
+
${icons.file_upload}
|
|
603
|
+
${dom.utils.createTooltipInner(lang.fileUpload)}
|
|
604
|
+
</button>`
|
|
605
|
+
: ''
|
|
606
|
+
}
|
|
607
|
+
<button type="button" class="se-btn se-tooltip se-modal-files-edge-button _se_bookmark_button" aria-label="${lang.link_modal_bookmark}">
|
|
608
|
+
${icons.bookmark}
|
|
609
|
+
${dom.utils.createTooltipInner(lang.link_modal_bookmark)}
|
|
610
|
+
</button>
|
|
611
|
+
</div>
|
|
612
|
+
<div class="se-anchor-preview-form">
|
|
613
|
+
<span class="se-svg se-anchor-preview-icon _se_anchor_bookmark_icon">${icons.bookmark}</span>
|
|
614
|
+
<span class="se-svg se-anchor-preview-icon _se_anchor_download_icon">${icons.download}</span>
|
|
615
|
+
<pre class="se-link-preview"></pre>
|
|
616
|
+
</div>
|
|
617
|
+
<label ${textDisplayShow}>${lang.link_modal_text}</label>
|
|
618
|
+
<input class="se-input-form _se_display_text" type="text" ${textDisplayShow} />
|
|
619
|
+
<label ${titleShow}>${lang.title}</label>
|
|
620
|
+
<input class="se-input-form _se_title" type="text" ${titleShow} />
|
|
621
|
+
</div>
|
|
622
|
+
<div class="se-modal-form-footer">
|
|
623
|
+
<label><input type="checkbox" class="se-modal-btn-check _se_anchor_check" /> ${lang.link_modal_newWindowCheck}</label>
|
|
624
|
+
<label><input type="checkbox" class="se-modal-btn-check _se_anchor_download" /> ${lang.link_modal_downloadLinkCheck}</label>`;
|
|
625
|
+
|
|
626
|
+
if (relList.length > 0) {
|
|
627
|
+
html += /*html*/ `
|
|
628
|
+
<div class="se-anchor-rel">
|
|
629
|
+
<button type="button" class="se-btn se-tooltip se-anchor-rel-btn" title="${lang.link_modal_relAttribute}" aria-label="${lang.link_modal_relAttribute}">
|
|
630
|
+
${icons.link_rel}
|
|
631
|
+
${dom.utils.createTooltipInner(lang.link_modal_relAttribute)}
|
|
632
|
+
</button>
|
|
633
|
+
<div class="se-anchor-rel-wrapper"><pre class="se-link-preview se-anchor-rel-preview"></pre></div>
|
|
634
|
+
</div>
|
|
635
|
+
</div>`;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
html += '</div></div>';
|
|
639
|
+
|
|
640
|
+
return dom.utils.createElement('DIV', null, html);
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
export default ModalAnchorEditor;
|