UNPKG

textchecker-element

Version:
308 lines 13.8 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.attachToTextArea = void 0; const text_checker_element_1 = require("./text-checker-element"); const text_checker_popup_element_1 = require("./text-checker-popup-element"); const p_debounce_1 = __importDefault(require("p-debounce")); const logger_1 = require("./util/logger"); const createCompositionHandler = () => { let onComposition = false; return { onComposition, handleEvent: (event) => { if (event.type === "compositionend") { onComposition = false; } else if (event.type === "compositionstart") { onComposition = true; } } }; }; const createTextCheckerPopupElement = (args) => { const textCheckerPopup = new text_checker_popup_element_1.TextCheckerPopupElement(args); document.body.append(textCheckerPopup); return textCheckerPopup; }; /** * Return true if the element in viewport * @param element */ function isVisibleInViewport(element) { const style = window.getComputedStyle(element); if (style.display === "none" || style.visibility === "hidden") { return false; } const rect = element.getBoundingClientRect(); if (rect.height === 0 || rect.width === 0) { return false; } return (rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth)); } /** * Dismiss all popup * * - Update text(includes fixed) * - Scroll textarea/Scroll window * - Focus on textarea * - Click out of textarea/popup * - popup → textarea → other → dismiss * - textarea → popup → other → dismiss */ /** * Dismiss a single popup(500ms delay) * * - Leave from popup * - Leave from RectItem * - Focus on textarea * */ /** * Show popup condition * - onUpdate */ /** * Attach text-checker component to `<textarea>` element */ const attachToTextArea = ({ textAreaElement, lintingDebounceMs, lintEngine }) => { if (!textAreaElement) { (0, logger_1.debug)("Can not attach. No textarea", textAreaElement); return () => { }; } if (textAreaElement.readOnly) { (0, logger_1.debug)("Can not attach textarea that is readonly", textAreaElement); return () => { }; } if (textAreaElement.dataset.attachedTextCheckerElement === "true") { (0, logger_1.debug)("Can not attach textarea that is already attached", textAreaElement); return () => { }; } const dismissCards = () => { (0, logger_1.debug)("dismissCards", { textCheckerPopup: textCheckerPopup.isHovering, textChecker: textChecker.isHovering, textCheckerF: textChecker.isFocus }); if (!textCheckerPopup.isHovering && !textChecker.isHovering && !textChecker.isFocus) { textCheckerPopup.dismissCards(); textChecker.resetHoverState(); } }; const textCheckerPopup = createTextCheckerPopupElement({ onLeave() { dismissCards(); } }); const textChecker = new text_checker_element_1.TextCheckerElement({ targetElement: textAreaElement, hoverPadding: 20, onLeave() { dismissCards(); } }); textAreaElement.before(textChecker); const compositionHandler = createCompositionHandler(); const update = (0, p_debounce_1.default)(() => __awaiter(void 0, void 0, void 0, function* () { if (!isVisibleInViewport(textAreaElement)) { return; } // stop lint on IME composition if (compositionHandler.onComposition) { return; } // dismiss card before update annotations // dismissCards(); const text = textAreaElement.value; const results = yield lintEngine.lintText({ text }); (0, logger_1.debug)("lint results", results); const updateText = (newText, card) => __awaiter(void 0, void 0, void 0, function* () { const currentText = textAreaElement.value; if (currentText === text && currentText !== newText) { textAreaElement.value = newText; yield updateOrClearAnnotationsIfFailed(); textCheckerPopup.dismissCard(card); } }); const annotations = results.flatMap((result) => { return result.messages.map((message) => { var _a, _b, _c; // message.range is introduced in textlint@12.2.0 // https://github.com/textlint/textlint/releases/tag/v12.2.0 const range = (_c = (_a = message.range) !== null && _a !== void 0 ? _a : (_b = message.fix) === null || _b === void 0 ? void 0 : _b.range) !== null && _c !== void 0 ? _c : [message.index, message.index + 1]; const card = { id: `${message.ruleId}::${range[0]}-${range[1]}`, message: message.message, messageRuleId: message.ruleId, fixable: Boolean(message.fix) }; let dismissTimerId = null; return { id: `${message.ruleId}::${range[0]}-${range[1]}`, start: range[0], end: range[1], onMouseEnter: ({ rectItem }) => { (0, logger_1.debug)("annotation - onMouseEnter"); if (dismissTimerId) { clearTimeout(dismissTimerId); } textCheckerPopup.updateCard({ card: card, rect: { top: rectItem.boxBorderWidth + rectItem.boxMarginTop + rectItem.boxPaddingTop + rectItem.boxAbsoluteY + rectItem.top + rectItem.height, left: rectItem.boxAbsoluteX + rectItem.left, width: rectItem.width }, handlers: { onFixText() { return __awaiter(this, void 0, void 0, function* () { const fixResults = yield lintEngine.fixText({ text, messages: [message] }); yield updateText(fixResults.output, card); }); }, onFixAll() { return __awaiter(this, void 0, void 0, function* () { const fixResults = yield lintEngine.fixText({ text, messages: result.messages }); yield updateText(fixResults.output, card); }); }, onFixRule() { return __awaiter(this, void 0, void 0, function* () { const messages = result.messages.filter((aMessage) => aMessage.ruleId === message.ruleId); const fixResults = yield lintEngine.fixText({ text, messages }); yield updateText(fixResults.output, card); }); }, onIgnore() { return __awaiter(this, void 0, void 0, function* () { yield lintEngine.ignoreText({ text, message }); yield updateOrClearAnnotationsIfFailed(); }); }, onSeeDocument() { const id = message.ruleId.includes("/") ? message.ruleId.split("/")[1] : message.ruleId; window.open(`https://github.com/search?q=textlint ${encodeURIComponent(id)}`, "_blank", "noopener"); } } }); }, onMouseLeave(_a) { return __awaiter(this, arguments, void 0, function* ({ rectItem }) { try { (0, logger_1.debug)("annotation - onMouseLeave"); dismissTimerId = setTimeout(() => { const isHover = textChecker.isHoverRectItem(rectItem); (0, logger_1.debug)("dismiss", { textCheckerPopup: textCheckerPopup.isHovering, isRectElementHover: isHover }); if (textCheckerPopup.isHovering || isHover) { return; } textCheckerPopup.dismissCard(card); }, 500); } catch (error) { (0, logger_1.debug)("Abort dismiss popup", error); } }); } }; }); }); (0, logger_1.debug)("annotations", annotations); textChecker.updateAnnotations(annotations); }), lintingDebounceMs); const updateOrClearAnnotationsIfFailed = () => __awaiter(void 0, void 0, void 0, function* () { try { yield update(); } catch (error) { (0, logger_1.debug)("update error", error); textChecker.updateAnnotations([]); } }); // Events // when resize element, update annotation const resizeObserver = new ResizeObserver(() => { (0, logger_1.debug)("ResizeObserver do update"); textCheckerPopup.dismissCards(); textChecker.resetAnnotations(); updateOrClearAnnotationsIfFailed(); }); resizeObserver.observe(textAreaElement); // when scroll window, update annotation const onScroll = () => { textCheckerPopup.dismissCards(); textChecker.resetAnnotations(); updateOrClearAnnotationsIfFailed(); }; const onFocus = () => { textCheckerPopup.dismissCards(); updateOrClearAnnotationsIfFailed(); }; const onBlur = (event) => { // does not dismiss on click popup items(require tabindex) if (event.relatedTarget === textChecker || event.relatedTarget === textCheckerPopup) { return; } textCheckerPopup.dismissCards(); }; textAreaElement.addEventListener("compositionstart", compositionHandler); textAreaElement.addEventListener("compositionend", compositionHandler); textAreaElement.addEventListener("input", update); textAreaElement.addEventListener("focus", onFocus); textAreaElement.addEventListener("blur", onBlur); textAreaElement.addEventListener("focusout", dismissCards); window.addEventListener("scroll", onScroll); // when scroll the element, update annotation textAreaElement.addEventListener("scroll", onScroll); updateOrClearAnnotationsIfFailed(); return () => { window.removeEventListener("scroll", onScroll); textAreaElement.removeEventListener("scroll", onScroll); textAreaElement.removeEventListener("compositionstart", compositionHandler); textAreaElement.removeEventListener("compositionend", compositionHandler); textAreaElement.removeEventListener("input", update); textAreaElement.removeEventListener("focus", onFocus); textAreaElement.removeEventListener("blur", onBlur); resizeObserver.disconnect(); }; }; exports.attachToTextArea = attachToTextArea; //# sourceMappingURL=attach-to-text-area.js.map