@taze-editor/taze-plugin-search-highlight
Version:
Search and highlight plugin for Taze Editor
326 lines (313 loc) • 9.14 kB
JavaScript
;
Object.defineProperty(exports, '__esModule', { value: true });
var React = require('react');
var tazeCore = require('@taze-editor/taze-core');
var slate = require('slate');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
function _extends() {
_extends = Object.assign ? Object.assign.bind() : function (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
return _extends.apply(this, arguments);
}
const SearchHighlightComponent = ({
attributes,
children,
text,
leaf,
editor,
nodeProps,
...props
}) => {
return /*#__PURE__*/React__default["default"].createElement("mark", _extends({}, props, attributes, {
style: {
backgroundColor: (props === null || props === void 0 ? void 0 : props["data-taze-leaf-search-highlight-current"]) === true ? "#ffd386" : "#e2e2e2"
}
}), children);
};
const useSearchHighlightPluginStore = tazeCore.createPluginStore((set, get) => ({
searchParams: {
query: "",
caseSensitive: false
},
replaceQuery: "",
searchMatchedRanges: [],
searchStep: 0,
setSearchParams: params => set(state => ({
...state,
searchParams: params
})),
setReplaceQuery: value => set(state => ({
...state,
replaceQuery: value
})),
setSearchMatchedRanges: ranges => set(state => ({
...state,
searchMatchedRanges: ranges
})),
setSearchStep: value => set(state => ({
...state,
searchStep: value
})),
moveNextSearchStep: editor => {
const {
searchStep,
searchMatchedRanges
} = get();
const nextStep = searchStep >= searchMatchedRanges.length - 1 ? 0 : searchStep + 1;
set(state => ({
...state,
searchStep: nextStep
}));
editor.apply({
type: "set_selection",
properties: searchMatchedRanges[nextStep],
newProperties: searchMatchedRanges[nextStep]
});
},
movePrevSearchStep: editor => {
const {
searchStep,
searchMatchedRanges
} = get();
const prevStep = searchStep === 0 ? searchMatchedRanges.length - 1 : searchStep - 1;
set(state => ({
...state,
searchStep: prevStep
}));
editor.apply({
type: "set_selection",
properties: searchMatchedRanges[prevStep],
newProperties: searchMatchedRanges[prevStep]
});
},
getSearchRanges: (node, path, searchParams, focusedRange) => {
const ranges = [];
if (!slate.Text.isText(node) || searchParams.query.length === 0) {
return ranges;
}
let {
text
} = node;
let search = searchParams.query;
if (!searchParams.caseSensitive) {
text = text.toLowerCase();
search = searchParams.query.toLowerCase();
}
const parts = text.split(search);
let offset = 0;
parts.forEach((part, index) => {
const isCurrent = focusedRange && slate.Path.equals(focusedRange.anchor.path, path) && focusedRange.focus.offset === offset;
if (index !== 0) {
ranges.push({
anchor: {
path,
offset: offset - search.length
},
focus: {
path,
offset
},
"search-highlight": true,
"search-highlight-current": !!isCurrent
});
}
offset = offset + part.length + search.length;
});
return ranges;
},
getAllSearchRanges: (editor, searchParams) => {
const {
getSearchRanges
} = get();
if (!editor.children.length || searchParams.query.length === 0) {
return [];
}
const matchingNodes = slate.Editor.nodes(editor, {
at: [],
match: node => slate.Text.isText(node) && node.text.toLowerCase().includes(searchParams.query.toLowerCase())
});
let nodeMatch = matchingNodes.next();
let ranges = [];
while (!nodeMatch.done) {
const [node, path] = nodeMatch.value;
ranges.push(...getSearchRanges(node, path, searchParams));
nodeMatch = matchingNodes.next();
}
return ranges;
},
replaceOne: editor => {
const {
searchMatchedRanges,
searchStep,
replaceQuery,
setSearchStep
} = get();
const focusedRange = searchMatchedRanges[searchStep];
slate.Transforms.insertText(editor, replaceQuery, {
at: {
anchor: focusedRange.anchor,
focus: focusedRange.focus
}
});
if (searchMatchedRanges.length >= searchStep) {
setSearchStep(0);
}
},
replaceAll: editor => {
const {
searchMatchedRanges,
replaceQuery
} = get();
if (!searchMatchedRanges.length) {
return;
}
const originalWordLength = Math.abs(searchMatchedRanges[0].anchor.offset - searchMatchedRanges[0].focus.offset);
let sameNodeAdjustment = 0;
let prevNodePath;
searchMatchedRanges.forEach(range => {
const currentPath = range.anchor.path;
if (prevNodePath && slate.Path.equals(currentPath, prevNodePath)) {
sameNodeAdjustment = sameNodeAdjustment + (replaceQuery.length - originalWordLength);
} else {
sameNodeAdjustment = 0;
}
slate.Transforms.insertText(editor, replaceQuery, {
at: {
anchor: {
...range.anchor,
offset: range.anchor.offset + sameNodeAdjustment
},
focus: {
...range.focus,
offset: range.focus.offset + sameNodeAdjustment
}
}
});
prevNodePath = range.anchor.path;
});
},
getNextSearchMatchStep: (editor, ranges) => {
let step = 0;
if (editor.selection) {
const {
anchor: selectionAnchor
} = editor.selection;
const found = ranges.some(r => {
const pathCompare = slate.Path.compare(r.anchor.path, selectionAnchor.path);
if (pathCompare === -1) {
++step;
} else if (pathCompare === 0) {
if (r.anchor.offset <= selectionAnchor.offset) {
++step;
} else {
return true;
}
} else {
return true;
}
return false;
});
if (!found) {
step = 0;
}
}
return step;
}
}));
const useSearchHighlightEffect = ({
editor
}) => {
const {
searchParams,
getAllSearchRanges,
setSearchMatchedRanges,
getNextSearchMatchStep,
setSearchStep
} = useSearchHighlightPluginStore();
React.useEffect(() => {
const ranges = getAllSearchRanges(editor, searchParams);
setSearchMatchedRanges(ranges);
const step = getNextSearchMatchStep(editor, ranges);
setSearchStep(step);
editor.apply({
type: "set_selection",
properties: ranges[0],
newProperties: ranges[0]
});
}, [editor, searchParams]);
};
const SearchHighlightEffect = ({
editor
}) => {
if (!editor) {
console.warn("[@taze-editor/taze-plugin-search-highlight] editor is not defined @renderAfterEditable");
return null;
}
useSearchHighlightEffect({
editor
});
return null;
};
const decorateSearchHighlight = (editor, plugin) => ([node, path]) => {
const {
store
} = plugin;
if (!store) {
console.warn("[@taze-editor/taze-plugin-search-highlight] store is not defined @decorate");
return;
}
const {
getSearchRanges,
searchParams,
searchMatchedRanges,
searchStep
} = store.getState();
return getSearchRanges(node, path, searchParams, searchMatchedRanges[searchStep]);
};
const MARK_SEARCH_HIGHLIGHT = "search-highlight";
const createSearchHighlightPlugin = tazeCore.createPluginFactory({
key: MARK_SEARCH_HIGHLIGHT,
type: MARK_SEARCH_HIGHLIGHT,
isLeaf: true,
renderAfterEditable: SearchHighlightEffect,
component: SearchHighlightComponent,
store: useSearchHighlightPluginStore,
decorate: decorateSearchHighlight,
onChange: (editor, store) => _value => {
if (!store) {
console.warn("[@taze-editor/taze-plugin-search-highlight] store is not defined @onChange");
return;
}
const {
operations
} = editor;
const firstOperation = operations[0];
if (firstOperation && (firstOperation.type === "insert_text" || firstOperation.type === "remove_text")) {
const {
getAllSearchRanges,
setSearchMatchedRanges,
searchParams
} = store.getState();
const ranges = getAllSearchRanges(editor, searchParams);
setSearchMatchedRanges(ranges);
}
}
});
exports.MARK_SEARCH_HIGHLIGHT = MARK_SEARCH_HIGHLIGHT;
exports.SearchHighlightComponent = SearchHighlightComponent;
exports.SearchHighlightEffect = SearchHighlightEffect;
exports.createSearchHighlightPlugin = createSearchHighlightPlugin;
exports.decorateSearchHighlight = decorateSearchHighlight;
exports.useSearchHighlightEffect = useSearchHighlightEffect;
exports.useSearchHighlightPluginStore = useSearchHighlightPluginStore;
//# sourceMappingURL=index.js.map