import * as React from 'react'; import { useRef, forwardRef, useImperativeHandle, useCallback } from 'react'; const { themeStyle } = require('../theme.js'); const Mark = require('mark.js/dist/mark.min.js'); const markJsUtils = require('lib/markJsUtils'); const Note = require('lib/models/Note'); const { replaceRegexDiacritics, pregQuote } = require('lib/string-utils'); interface NoteListItemProps { theme: number, width: number, style: any, dragItemIndex: number, highlightedWords: string[], index: number, isProvisional: boolean, isSelected: boolean, isWatched: boolean item: any, itemCount: number, onCheckboxClick: any, onDragStart: any, onNoteDragOver: any, onNoteDrop: any, onTitleClick: any, } function NoteListItem(props:NoteListItemProps, ref:any) { const item = props.item; const theme = themeStyle(props.theme); const hPadding = 10; const anchorRef = useRef(null); useImperativeHandle(ref, () => { return { focus: function() { if (anchorRef.current) anchorRef.current.focus(); }, }; }); let rootStyle = Object.assign({ width: props.width, opacity: props.isProvisional ? 0.5 : 1 }, props.style.listItem); if (props.isSelected) rootStyle = Object.assign(rootStyle, props.style.listItemSelected); if (props.dragItemIndex === props.index) { rootStyle.borderTop = `2px solid ${theme.color}`; } else if (props.index === props.itemCount - 1 && props.dragItemIndex >= props.itemCount) { rootStyle.borderBottom = `2px solid ${theme.color}`; } const onTitleClick = useCallback((event) => { props.onTitleClick(event, props.item); }, [props.onTitleClick, props.item]); const onCheckboxClick = useCallback((event) => { props.onCheckboxClick(event, props.item); }, [props.onCheckboxClick, props.item]); // Setting marginBottom = 1 because it makes the checkbox looks more centered, at least on Windows // but don't know how it will look in other OSes. function renderCheckbox() { if (!item.is_todo) return null; return (
); } let listItemTitleStyle = Object.assign({}, props.style.listItemTitle); listItemTitleStyle.paddingLeft = !item.is_todo ? hPadding : 4; if (item.is_todo && !!item.todo_completed) listItemTitleStyle = Object.assign(listItemTitleStyle, props.style.listItemTitleCompleted); const displayTitle = Note.displayTitle(item); let titleComp = null; if (props.highlightedWords.length) { const titleElement = document.createElement('span'); titleElement.textContent = displayTitle; const mark = new Mark(titleElement, { exclude: ['img'], acrossElements: true, }); mark.unmark(); for (let i = 0; i < props.highlightedWords.length; i++) { const w = props.highlightedWords[i]; markJsUtils.markKeyword(mark, w, { pregQuote: pregQuote, replaceRegexDiacritics: replaceRegexDiacritics, }); } // Note: in this case it is safe to use dangerouslySetInnerHTML because titleElement // is a span tag that we created and that contains data that's been inserted as plain text // with `textContent` so it cannot contain any XSS attacks. We use this feature because // mark.js can only deal with DOM elements. // https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml titleComp = ; } else { titleComp = {displayTitle}; } const watchedIconStyle = { paddingRight: 4, color: theme.color, }; const watchedIcon = props.isWatched ? null : ; // key={`${item.id}_${item.todo_completed}`} // Need to include "todo_completed" in key so that checkbox is updated when // item is changed via sync. return (
{renderCheckbox()} this.itemContextMenu(event)} href="#" draggable={true} style={listItemTitleStyle} onClick={onTitleClick} onDragStart={props.onDragStart} data-id={item.id} > {watchedIcon} {titleComp}
); } export default forwardRef(NoteListItem);