UNPKG

@taze-editor/taze-plugin-search-highlight

Version:

Search and highlight plugin for Taze Editor

326 lines (313 loc) 9.14 kB
'use strict'; 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