leksy-editor 2.2.3 → 2.3.0
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/README.md +5 -0
- package/contribution.md +57 -0
- package/index.js +5 -4
- package/package.json +1 -1
- package/utilities.js +21 -7
package/README.md
CHANGED
|
@@ -109,6 +109,7 @@ const app = createApp({
|
|
|
109
109
|
| `onFullScreen` | To open custom preview |
|
|
110
110
|
| `showTabs` | Enables or disables the display of tabs. |
|
|
111
111
|
| `enableFindAndReplace` | To enabled find and replace feature|
|
|
112
|
+
| `customTooltip` | Customize tooltip according to your need, set using key-value pair|
|
|
112
113
|
|
|
113
114
|
### CSS Customization
|
|
114
115
|
|
|
@@ -203,6 +204,10 @@ Similarly, Pexels and Tenor can be integrated using `pexels` and `tenor` plugins
|
|
|
203
204
|
|
|
204
205
|
MIT License. Free to use and modify.
|
|
205
206
|
|
|
207
|
+
## Contributing
|
|
208
|
+
|
|
209
|
+
Contributions are welcome. Please review the [Contribution Guide](./contribution.md) before opening a pull request.
|
|
210
|
+
|
|
206
211
|
## Author
|
|
207
212
|
|
|
208
213
|
Leksy Editor is developed and maintained by **Agami Technologies**.
|
package/contribution.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Contribution Guide
|
|
2
|
+
|
|
3
|
+
Thank you for contributing to Leksy Editor.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
Leksy Editor is a JavaScript editor package with example integrations for React and Vue. The main source files live in the repository root, and example apps are available in the `examples/` directory.
|
|
8
|
+
|
|
9
|
+
## Getting Started
|
|
10
|
+
|
|
11
|
+
1. Fork the repository.
|
|
12
|
+
2. Clone your fork locally.
|
|
13
|
+
3. Install dependencies:
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
npm install
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
4. Review the example projects if your change affects framework integrations:
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
cd examples/react && npm install
|
|
23
|
+
cd ../vue-cdn && npm install
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Contribution Workflow
|
|
27
|
+
|
|
28
|
+
1. Create a feature branch for your work.
|
|
29
|
+
2. Keep changes focused and avoid unrelated refactors.
|
|
30
|
+
3. Update documentation when behavior, APIs, or examples change.
|
|
31
|
+
4. Verify the impacted flows manually before opening a pull request.
|
|
32
|
+
|
|
33
|
+
## Code Guidelines
|
|
34
|
+
|
|
35
|
+
- Follow the existing code style and file structure.
|
|
36
|
+
- Prefer small, readable changes over large rewrites.
|
|
37
|
+
- Preserve backward compatibility unless the change is intentional and documented.
|
|
38
|
+
- If you add or change a plugin-related behavior, confirm the editor still works in the example apps.
|
|
39
|
+
|
|
40
|
+
## Testing
|
|
41
|
+
|
|
42
|
+
This repository does not currently include an automated test suite. Before submitting a change:
|
|
43
|
+
|
|
44
|
+
- Run the package locally and validate the affected editor behavior.
|
|
45
|
+
- Check the relevant example app when applicable.
|
|
46
|
+
- Confirm there are no obvious console errors or broken interactions.
|
|
47
|
+
|
|
48
|
+
## Pull Request Checklist
|
|
49
|
+
|
|
50
|
+
- The change is limited to the intended scope.
|
|
51
|
+
- Documentation has been updated if needed.
|
|
52
|
+
- Example usage has been reviewed if the public API changed.
|
|
53
|
+
- Manual verification has been completed.
|
|
54
|
+
|
|
55
|
+
## Contributor Reference
|
|
56
|
+
|
|
57
|
+
Profile: [Chetan Gupta](https://github.com/chetanguptamrt)
|
package/index.js
CHANGED
|
@@ -178,7 +178,7 @@ class LeksyEditor {
|
|
|
178
178
|
syncRemoteChangesDebounce(core)
|
|
179
179
|
updateOutlineItems(core, options)
|
|
180
180
|
},
|
|
181
|
-
onSave:() => {
|
|
181
|
+
onSave: () => {
|
|
182
182
|
if (editorRef.onSave instanceof Function) editorRef.onSave()
|
|
183
183
|
},
|
|
184
184
|
onBlur: (html) => {
|
|
@@ -416,6 +416,7 @@ class LeksyEditor {
|
|
|
416
416
|
}
|
|
417
417
|
|
|
418
418
|
const _plugin = PLUGINS[plugin] ?? options.customPlugins?.[plugin];
|
|
419
|
+
_plugin.pluginId = plugin
|
|
419
420
|
/******************************* validations ******************************* */
|
|
420
421
|
if (!_plugin) throw Error(ERRORS.INVALID_PLUGIN);
|
|
421
422
|
if (_plugin.title === 'GIPHY' && !options.giphyApiKey) throw Error(ERRORS.GIPHY_KEY_NOT_FOUND);
|
|
@@ -700,9 +701,9 @@ class LeksyEditor {
|
|
|
700
701
|
applyTextFormat(core, 'underline')
|
|
701
702
|
} else if (event.key === 'i') {
|
|
702
703
|
applyTextFormat(core, 'italic')
|
|
703
|
-
} else if (event.key === 'f' || event.key === 'h'){
|
|
704
|
+
} else if (event.key === 'f' || event.key === 'h') {
|
|
704
705
|
findAndReplace(core, options, event)
|
|
705
|
-
} else if (event.key === 's'){
|
|
706
|
+
} else if (event.key === 's') {
|
|
706
707
|
core.onSave();
|
|
707
708
|
}
|
|
708
709
|
}
|
|
@@ -855,7 +856,7 @@ class LeksyEditor {
|
|
|
855
856
|
range.setStart(textNode, textNode.length);
|
|
856
857
|
range.collapse(true);
|
|
857
858
|
}
|
|
858
|
-
|
|
859
|
+
|
|
859
860
|
handleBackspaceInList(event, core);
|
|
860
861
|
|
|
861
862
|
if (!isLinkCreated) core.elements.lastCreatedLink = null
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "leksy-editor",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "Leksy Editor is an alternative to traditional WYSIWYG editors, designed primarily for creating mail templates, blogs, and documents without any content manipulation.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"directories": {
|
package/utilities.js
CHANGED
|
@@ -184,13 +184,16 @@ const showTooltip = (options) => {
|
|
|
184
184
|
});
|
|
185
185
|
};
|
|
186
186
|
|
|
187
|
+
const getTooltip = (plugin, options) => {
|
|
188
|
+
return options?.customTooltip?.[plugin.pluginId] ?? plugin.title
|
|
189
|
+
}
|
|
187
190
|
|
|
188
191
|
const makeToolbarButton = (_plugin, options, core) => {
|
|
189
192
|
const pluginButton = document.createElement('button');
|
|
190
193
|
pluginButton.type = "button"
|
|
191
194
|
pluginButton.className = `${options.classPrefix}${CLASSES.TOOLBAR_ITEM} ${_plugin.type === 'reset-color' ? 'reset-color' : ''}`
|
|
192
195
|
|
|
193
|
-
pluginButton.dataset.title = _plugin
|
|
196
|
+
pluginButton.dataset.title = getTooltip(_plugin, options)
|
|
194
197
|
pluginButton.dataset.type = 'button'
|
|
195
198
|
pluginButton.onclick = (e) => {
|
|
196
199
|
e.preventDefault();
|
|
@@ -209,7 +212,7 @@ const makeToolbarButtonSelect = (_plugin, options, core) => {
|
|
|
209
212
|
pluginButton.type = "button"
|
|
210
213
|
pluginButton.className = `${options.classPrefix}${CLASSES.TOOLBAR_ITEM} ${_plugin.type === 'button-select' ? CLASSES.TOOLBAR_ITEM_BUTTON_SELECT : ''}`
|
|
211
214
|
|
|
212
|
-
pluginButton.dataset.title = _plugin
|
|
215
|
+
pluginButton.dataset.title = getTooltip(_plugin, options)
|
|
213
216
|
pluginButton.dataset.type = 'button'
|
|
214
217
|
pluginButton.onclick = (e) => {
|
|
215
218
|
e.preventDefault();
|
|
@@ -233,7 +236,7 @@ const makeToolbarButtonSelect = (_plugin, options, core) => {
|
|
|
233
236
|
const makeToolbarColor = (_plugin, options, core) => {
|
|
234
237
|
const pluginContainer = document.createElement('div');
|
|
235
238
|
pluginContainer.className = `${options.classPrefix}${CLASSES.TOOLBAR_ITEM} ${CLASSES.TOOLBAR_ITEM_COLOR}`
|
|
236
|
-
pluginContainer.dataset.title = _plugin
|
|
239
|
+
pluginContainer.dataset.title = getTooltip(_plugin, options)
|
|
237
240
|
pluginContainer.dataset.type = 'color'
|
|
238
241
|
|
|
239
242
|
const pluginButton = document.createElement('input');
|
|
@@ -326,7 +329,7 @@ const makeToolbarDropdown = (_plugin, options, core) => {
|
|
|
326
329
|
const dropdownButton = document.createElement('button');
|
|
327
330
|
dropdownButton.type = "button"
|
|
328
331
|
dropdownButton.className = `${options.classPrefix}${CLASSES.TOOLBAR_ITEM}`
|
|
329
|
-
dropdownButton.setAttribute('data-title', _plugin
|
|
332
|
+
dropdownButton.setAttribute('data-title', getTooltip(_plugin, options))
|
|
330
333
|
|
|
331
334
|
const pluginIcon = document.createElement('div');
|
|
332
335
|
pluginIcon.innerHTML = _plugin.icon;
|
|
@@ -569,7 +572,7 @@ const makeToolbarSelect = (_plugin, options, core) => {
|
|
|
569
572
|
const dropdownButton = document.createElement('button');
|
|
570
573
|
dropdownButton.type = "button"
|
|
571
574
|
dropdownButton.className = `${options.classPrefix}${CLASSES.TOOLBAR_ITEM} ${CLASSES.TOOLBAR_ITEM_SELECT} ${_plugin.type === 'button-select' ? CLASSES.TOOLBAR_ITEM_BUTTON_SELECT : ''}`
|
|
572
|
-
dropdownButton.dataset.title = _plugin.type == 'button-select' ? `Options` : _plugin
|
|
575
|
+
dropdownButton.dataset.title = _plugin.type == 'button-select' ? `Options` : getTooltip(_plugin, options)
|
|
573
576
|
|
|
574
577
|
const pluginIcon = document.createElement('div');
|
|
575
578
|
pluginIcon.style.width = _plugin.width
|
|
@@ -3219,6 +3222,12 @@ const outdentListItem = (li, core) => {
|
|
|
3219
3222
|
}
|
|
3220
3223
|
};
|
|
3221
3224
|
|
|
3225
|
+
const isEmptyLi = (li) => {
|
|
3226
|
+
const text = li.textContent.replace(/\u200B/g, '').trim();
|
|
3227
|
+
const hasOnlySublist = li.children.length === 1 && li.querySelector('ul, ol');
|
|
3228
|
+
return text === '' && !hasOnlySublist;
|
|
3229
|
+
}
|
|
3230
|
+
|
|
3222
3231
|
const makeSublist = (event, core) => {
|
|
3223
3232
|
if (event.key !== 'Tab') return;
|
|
3224
3233
|
|
|
@@ -3247,6 +3256,11 @@ const makeSublist = (event, core) => {
|
|
|
3247
3256
|
|
|
3248
3257
|
if (!selectedLis.length) return;
|
|
3249
3258
|
|
|
3259
|
+
if (!event.shiftKey) {
|
|
3260
|
+
const hasContent = selectedLis.some(li => !isEmptyLi(li));
|
|
3261
|
+
if (hasContent) return;
|
|
3262
|
+
}
|
|
3263
|
+
|
|
3250
3264
|
event.preventDefault();
|
|
3251
3265
|
|
|
3252
3266
|
selectedLis.forEach(li => {
|
|
@@ -3943,7 +3957,7 @@ const handleBackspaceInList = (event, core) => {
|
|
|
3943
3957
|
const nestedList = li.querySelector('ul, ol');
|
|
3944
3958
|
if (!nestedList) return;
|
|
3945
3959
|
|
|
3946
|
-
|
|
3960
|
+
|
|
3947
3961
|
if (!isCaretAtStart(range, li)) return;
|
|
3948
3962
|
|
|
3949
3963
|
event.preventDefault();
|
|
@@ -3981,7 +3995,7 @@ const handleBackspaceInList = (event, core) => {
|
|
|
3981
3995
|
|
|
3982
3996
|
selection.removeAllRanges();
|
|
3983
3997
|
selection.addRange(newRange);
|
|
3984
|
-
}
|
|
3998
|
+
}
|
|
3985
3999
|
// Case 2: first list item
|
|
3986
4000
|
else {
|
|
3987
4001
|
const span = document.createElement('span');
|