Skip to content

Commit

Permalink
Lots of work
Browse files Browse the repository at this point in the history
  • Loading branch information
muratcakir committed Dec 10, 2024
1 parent a4f1d51 commit 8bc92b1
Show file tree
Hide file tree
Showing 21 changed files with 620 additions and 99 deletions.
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"@babel/plugin-external-helpers": "^7.8.3",
"@babel/preset-env": "^7.9.5",
"@popperjs/core": "^2.9.2",
"@rollup/plugin-replace": "^6.0.1",
"@vitest/browser": "^1.6.0",
"adm-zip": "^0.5.16",
"autoprefixer": "^10.2.4",
Expand Down Expand Up @@ -70,13 +71,13 @@
"sass-loader": "^14.2.1",
"serve-static": "^1.14.1",
"uglify-js": "^3.9.1",
"underscore": "^1.13.1",
"vite": "^5.4.9",
"vite-plugin-banner": "^0.8.0",
"vitest": "^1.6.0",
"webdriverio": "^8.35.1",
"webfont": "^11.1.1",
"webpack-sources": "^3.2.3",
"underscore": "^1.13.1"
"webpack-sources": "^3.2.3"
},
"dependencies": {
"popper.js": "1.16.1"
Expand Down
7 changes: 7 additions & 0 deletions src/js/Context.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ export default class Context {
$.summernote.ui = $.summernote.ui_template(this.options);
this.ui = $.summernote.ui;

// Special handling for change event: trigger only all 10 ms.
this.triggerChangeEvent = func.throttle(this.triggerChangeEvent.bind(this), 10, { trailing: false });

this.initialize();
}

Expand Down Expand Up @@ -139,6 +142,10 @@ export default class Context {
this.triggerEvent('disable', true);
}

triggerChangeEvent($editable) {
this.triggerEvent('change', $editable);
}

triggerEvent() {
const name = lists.head(arguments);
const args = lists.tail(lists.from(arguments));
Expand Down
2 changes: 2 additions & 0 deletions src/js/core/dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ const isBodyContainer = (node) => isCell(node) || isBlockquote(node) || isEditab
const isBookmarkNode = func.and(matchNodeName('SPAN'), matchAttributeValue('data-note-type', 'bookmark'))
const isVoid = (node) => node && schema.isVoid(node.nodeName);
const isEmptyAnchor = (node) => isAnchor(node) && isEmpty(node);
const isNonEmptyText = (node) => isText(node) && !isEmpty(node);

const matches = (node, selector) => matchSelector(selector)(node);

Expand Down Expand Up @@ -1186,6 +1187,7 @@ export default {
isControlSizing,
isNode,
isText,
isNonEmptyText,
isElement,
isBookmarkNode,
isCData,
Expand Down
8 changes: 6 additions & 2 deletions src/js/editing/Selection.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,17 +65,21 @@ export default class Selection {
let rng;
if (sel.rangeCount > 0) {
rng = range.createFromNativeRange(sel.getRangeAt(0));
} else {
}
else {
rng = createRootRange(this.editor);
}

this.context.triggerEvent('selectionchange', rng);

return rng;
};

const throttledHandler = func.throttle(e => {
if (e.type === 'blur') {
this.hasFocus = false;
}

if (e.type === 'focus') {
this.hasFocus = true;
this.bookmark = createBookmarkFromSelection();
Expand Down Expand Up @@ -303,7 +307,7 @@ export default class Selection {
}
}

// If the anchor node is a element instead of a text node then return this element
// If the anchor node is an element instead of a text node then return this element
// if (isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1)
// return anchorNode.childNodes[sel.anchorOffset];

Expand Down
27 changes: 19 additions & 8 deletions src/js/editing/Style.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import lists from '../core/lists';
import dom from '../core/dom';

export default class Style {
constructor(formatter) {
this.formatter = formatter;
}

/**
* @method jQueryCSS
*
Expand Down Expand Up @@ -79,7 +83,7 @@ export default class Style {
}

let pred = dom.matchNodeNames(nodeName);
const nodes = rng.nodes(dom.isText, {
const nodes = rng.nodes(dom.isNonEmptyText, {
fullyContains: true,
}).map((text) => {
return dom.closestSingleParent(text, pred) || dom.wrap(text, nodeName);
Expand Down Expand Up @@ -123,13 +127,20 @@ export default class Style {
// [workaround] prevent Firefox nsresult: "0x80004005 (NS_ERROR_FAILURE)"
try {
styleInfo = $.extend(styleInfo, {
'font-bold': document.queryCommandState('bold') ? 'bold' : 'normal',
'font-italic': document.queryCommandState('italic') ? 'italic' : 'normal',
'font-underline': document.queryCommandState('underline') ? 'underline' : 'normal',
'font-subscript': document.queryCommandState('subscript') ? 'subscript' : 'normal',
'font-superscript': document.queryCommandState('superscript') ? 'superscript' : 'normal',
'font-strikethrough': document.queryCommandState('strikethrough') ? 'strikethrough' : 'normal',
'font-family': document.queryCommandValue('fontname') || styleInfo['font-family'],
// 'font-bold': document.queryCommandState('bold') ? 'bold' : 'normal',
// 'font-italic': document.queryCommandState('italic') ? 'italic' : 'normal',
// 'font-underline': document.queryCommandState('underline') ? 'underline' : 'normal',
// 'font-subscript': document.queryCommandState('subscript') ? 'subscript' : 'normal',
// 'font-superscript': document.queryCommandState('superscript') ? 'superscript' : 'normal',
// 'font-strikethrough': document.queryCommandState('strikethrough') ? 'strikethrough' : 'normal',
// 'font-family': document.queryCommandValue('fontname') || styleInfo['font-family'],
'font-bold': this.formatter.match('bold') ? 'bold' : 'normal',
'font-italic': this.formatter.match('italic') ? 'italic' : 'normal',
'font-underline': this.formatter.match('underline') ? 'underline' : 'normal',
'font-subscript': this.formatter.match('subscript') ? 'subscript' : 'normal',
'font-superscript': this.formatter.match('superscript') ? 'superscript' : 'normal',
'font-strikethrough': this.formatter.match('strikethrough') ? 'strikethrough' : 'normal',
'font-family': this.formatter.match('fontname') || styleInfo['font-family'],
});
} catch (e) {
// eslint-disable-next-line
Expand Down
25 changes: 25 additions & 0 deletions src/js/fmt/ApplyFormat2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import Type from '../core/Type';
import lists from '../core/lists';
import range from '../core/range';
import dom from '../core/dom';
import Point from '../core/Point';
import MatchFormat from './MatchFormat2';
import Str from '../core/Str';

const each = lists.each;

const applyFormatInternal = (editor, name, vars = null, node = null) => {
const sel = editor.selection;
let rng = sel.getRange();
const formatList = editor.formatter.get(name);
const format = formatList[0];
const isCollapsed = !node && rng.collapsed;
};

const applyFormat = (editor, name, vars = null, node = null) => {
applyFormatInternal(editor, name, vars, node);
};

export default {
applyFormat
}
185 changes: 185 additions & 0 deletions src/js/fmt/FormatUtils2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
import Type from '../core/Type';
import Obj from '../core/Obj';
import Convert from '../core/Convert';
import dom from '../core/dom';
import schema from '../core/schema';
import range from '../core/range';

const isElementNode = (node) =>
dom.isElement(node) && !dom.isBookmarkNode(node);

const isBlockFormat = (format) =>
format.block === true || Type.isString(format.block);

const isWrappingBlockFormat = (format) =>
isBlockFormat(format) && format.wrapper === true;

const isNonWrappingBlockFormat = (format) =>
isBlockFormat(format) && format.wrapper !== true;

const isSelectorFormat = (format) =>
Type.isString(format.selector) || Type.isFunction(format.selector);

const isInlineFormat = (format) =>
format.inline === true || Type.isString(format.inline);

const isMixedFormat = (format) =>
isSelectorFormat(format) && isInlineFormat(format) && Obj.valueOrDefault(format.mixed, true);

const shouldExpandToSelector = (format) =>
isSelectorFormat(format) && format.expand !== false && !isInlineFormat(format);

const isEmptyTextNode = (node) =>
node && dom.isText(node) && node.length === 0;

const isTextBlock = (node) =>
!!schema.getTextBlockElements()[node.nodeName.toLowerCase()];

/**
* Returns the next/previous non whitespace node.
*
* @private
* @param {Node} node Node to start at.
* @param {Boolean} [next] (Optional) Include next or previous node defaults to previous.
* @param {Boolean} [inc] (Optional) Include the current node in checking. Defaults to false.
* @return {Node} Next or previous node or undefined if it wasn't found.
*/
const getNonWhiteSpaceSibling = (node, next, inc) => {
if (node) {
const nextName = next ? 'nextSibling' : 'previousSibling';

for (node = inc ? node : node[nextName]; node; node = node[nextName]) {
if (dom.isElement(node) || !dom.isWhiteSpace(node)) {
return node;
}
}
}
return undefined;
};

const replaceVars = (value, vars = null) => {
if (Type.isFunction(value)) {
return value(vars);
}
else if (vars) {
value = value.replace(/%(\w+)/g, (str, name) => {
return vars[name] || str;
});
}
return value;
};

const getTextDecoration = (node) => {
let decoration;

dom.closest(node, (n) => {
if (dom.isElement(n)) {
decoration = dom.getStyle(n, 'text-decoration');
return !!decoration && decoration !== 'none';
}
else {
return false;
}
});

return decoration;
};

/**
* Compares two string/nodes regardless of their case.
*
* @private
* @param {String/Node} str1 Node or string to compare.
* @param {String/Node} str2 Node or string to compare.
* @return {Boolean} True/false if they match.
*/
const isEq = (str1, str2) => {
str1 = str1 || '';
str2 = str2 || '';

str1 = '' + (str1?.nodeName || str1);
str2 = '' + (str2?.nodeName || str2);

return str1.toLowerCase() === str2.toLowerCase();
};

const normalizeStyleValue = (value, name) => {
if (Type.isNullOrUndefined(value)) {
return null;
}
else {
let strValue = String(value);

// Force the format to hex
if (name === 'color' || name === 'backgroundColor') {
strValue = Convert.rgbaToHexString(strValue);
}

// Opera/Chrome will return bold as 700
if (name === 'fontWeight') {
const numValue = parseInt(value);
if (!isNaN(numValue)) {
if (numValue >= 500) strValue = 'bold';
else if (numValue >= 400) strValue = 'normal';
else strValue = 'light';
}
}

// Normalize fontFamily so "'Font name', Font" becomes: "Font name,Font"
if (name === 'fontFamily') {
strValue = strValue.replace(/[\'\"]/g, '').replace(/,\s+/g, ',');
}

return strValue;
}
};

const getStyle = (node, name) => {
const style = dom.getStyle(node, name);
return normalizeStyleValue(style, name);
};

const preserveSelection = (editor, rng, action) => {
// Remember selection points before applying format
const pts = rng.getPoints();

// Apply stuff to range
action();

let rngAfter;
if (pts.sc == pts.ec) {
rngAfter = range.createFromNode(pts.sc);
}
else {
rngAfter = range.create(pts.sc, pts.so, pts.ec, pts.eo);
}

editor.selection.setRange(rngAfter);
};

const afterFormat = (editor) => {
editor.normalizeContent();
editor.history.recordUndo();
editor.context.triggerEvent('change', editor.$editable);
};

export default {
isElementNode,
isInlineFormat,
isBlockFormat,
isMixedFormat,
shouldExpandToSelector,
isWrappingBlockFormat,
isNonWrappingBlockFormat,
isSelectorFormat,
isEmptyTextNode,
isTextBlock,
getNonWhiteSpaceSibling,
replaceVars,
isEq,
normalizeStyleValue,
getTextDecoration,
getStyle,
preserveSelection,
afterFormat
}
Loading

0 comments on commit 8bc92b1

Please sign in to comment.