Skip to content

Commit

Permalink
feat: add ignoreHtmlEntity option (#46)
Browse files Browse the repository at this point in the history
  • Loading branch information
Gumball12 authored Nov 9, 2023
1 parent fb9d282 commit 6fbef3d
Show file tree
Hide file tree
Showing 10 changed files with 137 additions and 47 deletions.
18 changes: 15 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ It does not require any additional licenses, except for MIT. ([#38](https://gith
| [Custom `sep` Style](#options-sep) ||
| [Fixation-Points](#options-fixationpoint) ||
| [Ignore HTML Tags](#options-ignorehtmltag) ||
| [Ignore HTML Entity](#options-ignorehtmlentity) ||
| [Saccade](https://github.com/Gumball12/text-vide/issues/21) ||

### Benchmark
Expand Down Expand Up @@ -123,7 +124,7 @@ type Options = Partial<{

#### `sep`<a id="options-sep"></a>

- Default Value: `['<b>', '</b>']`
- Default: `['<b>', '</b>']`

Passing a string allows you to specify the Beginning and End of the highlighted word at once.

Expand All @@ -139,7 +140,7 @@ textVide('text-vide', { sep: ['<strong>', '</strong>'] }); // '<strong>tex</stro

#### `fixationPoint`<a id="options-fixationpoint"></a>

- Default Value: `1`
- Default: `1`
- Range: `[1, 5]`

```ts
Expand All @@ -152,7 +153,7 @@ textVide('text-vide', { fixationPoint: 5 }); // '<b>t</b>ext-<b>v</b>ide'

#### `ignoreHtmlTag`<a id="options-ignorehtmltag"></a>

- Default Value: `true`
- Default: `true`

If this option is `true`, HTML tags are not highlighted.

Expand All @@ -161,6 +162,17 @@ textVite('<div>abcd</div>efg'); // '<div><b>abc</b>d</div><b>ef</b>g'
textVite('<div>abcd</div>efg', { ignoreHtmlTag: false }); // '<<b>di</b>v><b>abc</b>d</<b>di</b>v><b>ef</b>g'
```

#### `ignoreHtmlEntity`<a id="options-ignorehtmlentity"></a>

- Default: `true`

If this option is `true`, HTML entities are not highlighted.

```ts
textVide('&nbsp;abcd&gt;'); // '&nbsp;<b>abc</b>d&gt;'
textVide('&nbsp;abcd&gt;', { ignoreHtmlEntity: false }); // &<b>nbs</b>p;<b>abc</b>d&<b>g</b>t;
```

## License

[MIT](./LICENSE) @Gumball12
Expand Down
67 changes: 38 additions & 29 deletions apps/sandbox/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ type Edits = {
secondSep: string;
fixationPoint: string;
ignoreHtmlTag: string;
ignoreHtmlEntity: string;
input: string;
};

Expand All @@ -28,6 +29,7 @@ const defaultEdits: Edits = {
secondSep: '</b>',
fixationPoint: '1',
ignoreHtmlTag: '1', // 1 = true, 0 = false
ignoreHtmlEntity: '1', // 1 = true, 0 = false
input: INITIAL_INPUT,
};

Expand All @@ -37,13 +39,15 @@ const storeEdits = ({
fixationPoint,
input,
ignoreHtmlTag,
ignoreHtmlEntity,
}: Edits) => {
const search = [
`firstSep=${encodeURIComponent(firstSep)}`,
`secondSep=${encodeURIComponent(secondSep)}`,
`fixationPoint=${encodeURIComponent(fixationPoint)}`,
`input=${encodeURIComponent(input)}`,
`ignoreHtmlTag=${encodeURIComponent(ignoreHtmlTag)}`,
`ignoreHtmlEntity=${encodeURIComponent(ignoreHtmlEntity)}`,
].join('&');

// eslint-disable-next-line
Expand Down Expand Up @@ -100,6 +104,7 @@ type Action = {
| 'HIGHLIGHTED_TEXT'
| 'COPIED'
| 'TOGGLE_IGNORE_HTML_TAG'
| 'TOGGLE_IGNORE_HTML_ENTITY'
| 'RESET';
value: string;
copied: boolean;
Expand Down Expand Up @@ -135,6 +140,11 @@ const reducer: Reducer<State, Action> = (state, { type, value, copied }) => {
return { ...state, ignoreHtmlTag: nextIgnoreHtmlTag };
}

if (type === 'TOGGLE_IGNORE_HTML_ENTITY') {
const nextIgnoreHtmlEntity = state.ignoreHtmlEntity === '1' ? '0' : '1';
return { ...state, ignoreHtmlEntity: nextIgnoreHtmlEntity };
}

if (type === 'RESET') {
return {
...defaultEdits,
Expand All @@ -161,6 +171,7 @@ const App = () => {
copiedEffect,
highlightedText,
ignoreHtmlTag,
ignoreHtmlEntity,
} = state;

useEffect(() => {
Expand All @@ -169,6 +180,7 @@ const App = () => {
sep: [firstSep, secondSep],
fixationPoint: parseInt(fixationPoint),
ignoreHtmlTag: ignoreHtmlTag === '1',
ignoreHtmlEntity: ignoreHtmlEntity === '1',
};

const highlightedText = textVide(input, options);
Expand All @@ -185,11 +197,19 @@ const App = () => {
input,
fixationPoint,
ignoreHtmlTag,
ignoreHtmlEntity,
});
}, DEBOUNCE_TIMEOUT);

return () => clearTimeout(store);
}, [firstSep, secondSep, input, fixationPoint, ignoreHtmlTag]);
}, [
firstSep,
secondSep,
input,
fixationPoint,
ignoreHtmlTag,
ignoreHtmlEntity,
]);

const copyUrl = () => {
const { href: url } = location;
Expand Down Expand Up @@ -217,6 +237,7 @@ const App = () => {
fixationPoint,
input,
ignoreHtmlTag,
ignoreHtmlEntity,
});

return (
Expand Down Expand Up @@ -265,19 +286,9 @@ const App = () => {
InputLabelProps={{ shrink: true }}
/>
</div>

<Button
className="!hidden sm:!block"
variant="outlined"
color="error"
onClick={reset}
disabled={isResetDisabled}
>
reset
</Button>
</section>

<section className="flex justify-between">
<section className="flex gap-2 flex-wrap">
<ToggleButtonGroup
size="small"
exclusive
Expand All @@ -296,7 +307,6 @@ const App = () => {
</ToggleButtonGroup>

<Button
className="!hidden sm:!block"
variant="outlined"
color="success"
onClick={() =>
Expand All @@ -313,31 +323,30 @@ const App = () => {
</Button>

<Button
className="!block sm:!hidden"
variant="outlined"
color="error"
onClick={reset}
>
reset
</Button>
</section>

<section>
<Button
className="!block sm:!hidden"
variant="outlined"
color="success"
onClick={() =>
dispatchState({
type: 'TOGGLE_IGNORE_HTML_TAG',
type: 'TOGGLE_IGNORE_HTML_ENTITY',
value: '',
copied: false,
})
}
>
{ignoreHtmlTag === '1'
? 'not ignore html tags'
: 'ignore html tags'}
{ignoreHtmlEntity === '1'
? 'not ignore html entities'
: 'ignore html entities'}
</Button>
</section>

<section className="flex gap-2">
<Button
variant="outlined"
color="error"
onClick={reset}
disabled={isResetDisabled}
>
reset
</Button>
</section>
</section>
Expand Down
6 changes: 6 additions & 0 deletions packages/text-vide/src/__tests__/getOptions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ describe('test getOptions()', () => {
sep: ['<b>', '</b>'],
fixationPoint: 1,
ignoreHtmlTag: true,
ignoreHtmlEntity: true,
};

expect(getOptions({})).toEqual(expected);
Expand All @@ -18,12 +19,14 @@ describe('test getOptions()', () => {
sep: undefined,
fixationPoint: undefined,
ignoreHtmlTag: undefined,
ignoreHtmlEntity: undefined,
};

const expected: Options = {
sep: ['<b>', '</b>'],
fixationPoint: 1,
ignoreHtmlTag: true,
ignoreHtmlEntity: true,
};

expect(getOptions(undefinedOptionValues)).toEqual(expected);
Expand All @@ -34,12 +37,14 @@ describe('test getOptions()', () => {
sep: ['', ''],
fixationPoint: undefined,
ignoreHtmlTag: undefined,
ignoreHtmlEntity: undefined,
};

const expected: Options = {
sep: ['', ''],
fixationPoint: 1,
ignoreHtmlTag: true,
ignoreHtmlEntity: true,
};

expect(getOptions(maybeOptions)).toEqual(expected);
Expand All @@ -50,6 +55,7 @@ describe('test getOptions()', () => {
sep: ['a', 'b'],
fixationPoint: 0, // but it's okay
ignoreHtmlTag: false,
ignoreHtmlEntity: false,
};

expect(getOptions(expected)).toEqual(expected);
Expand Down
24 changes: 24 additions & 0 deletions packages/text-vide/src/__tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,30 @@ describe('test options', () => {

expect(textVide(text)).toBe(expected);
});

it('ignoreHtmlTag: true', () => {
const text = '<div>abcd</div>efg';
const expectedText = '<div><b>abc</b>d</div><b>ef</b>g';
expect(textVide(text, { ignoreHtmlTag: true })).toBe(expectedText);
});

it('ignoreHtmlTag: false', () => {
const text = '<div>abcd</div>efg';
const expected = '<<b>di</b>v><b>abc</b>d</<b>di</b>v><b>ef</b>g';
expect(textVide(text, { ignoreHtmlTag: false })).toBe(expected);
});

it('ignoreHtmlEntity: true', () => {
const text = '&nbsp;abcd&gt;';
const expectedText = '&nbsp;<b>abc</b>d&gt;';
expect(textVide(text, { ignoreHtmlEntity: true })).toBe(expectedText);
});

it('ignoreHtmlEntity: false', () => {
const text = '&nbsp;abcd&gt;';
const expected = '&<b>nbs</b>p;<b>abc</b>d&<b>g</b>t;';
expect(textVide(text, { ignoreHtmlEntity: false })).toBe(expected);
});
});

describe('fixation point ([2, 5])', () => {
Expand Down
2 changes: 2 additions & 0 deletions packages/text-vide/src/getOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import defaults from 'utils/defaults';
const DEFAULT_SEP = ['<b>', '</b>'];
const DEFAULT_FIXATION_POINT = 1;
const DEFAULT_IGNORE_HTML_TAG = true;
const DEFAULT_IGNORE_HTML_ENTITY = true;

export default (maybeOptions: Partial<Options>): Options =>
defaults(maybeOptions, {
sep: DEFAULT_SEP,
fixationPoint: DEFAULT_FIXATION_POINT,
ignoreHtmlTag: DEFAULT_IGNORE_HTML_TAG,
ignoreHtmlEntity: DEFAULT_IGNORE_HTML_ENTITY,
});
17 changes: 14 additions & 3 deletions packages/text-vide/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import getOptions from './getOptions';
import getFixationLength from './getFixationLength';
import getHighlightedText from './getHighlightedText';
import { useCheckIsHtmlTag } from './useCheckIsHtmlTag';
import { useCheckIsHtmlEntity } from './useCheckIsHtmlEntity';

const CONVERTIBLE_REGEX = /(\p{L}|\p{Nd})*\p{L}(\p{L}|\p{Nd})*/gu;

Expand All @@ -11,24 +12,34 @@ export const textVide = (text: string, maybeOptions: Partial<Options> = {}) => {
return '';
}

const { fixationPoint, sep, ignoreHtmlTag } = getOptions(maybeOptions);
const convertibleMatchList = text.matchAll(CONVERTIBLE_REGEX);
const { fixationPoint, sep, ignoreHtmlTag, ignoreHtmlEntity } =
getOptions(maybeOptions);
const convertibleMatchList = Array.from(text.matchAll(CONVERTIBLE_REGEX));

let result = '';
let lastMatchedIndex = 0;

let checkIsHtmlTag: ReturnType<typeof useCheckIsHtmlTag> | undefined;

if (ignoreHtmlTag) {
checkIsHtmlTag = useCheckIsHtmlTag(text);
}

let checkIsHtmlEntity: ReturnType<typeof useCheckIsHtmlEntity> | undefined;
if (ignoreHtmlEntity) {
checkIsHtmlEntity = useCheckIsHtmlEntity(text);
}

for (const match of convertibleMatchList) {
const isHtmlTag = checkIsHtmlTag?.(match);
if (isHtmlTag) {
continue;
}

const isHtmlEntity = checkIsHtmlEntity?.(match);
if (isHtmlEntity) {
continue;
}

const [matchedWord] = match;
const startIndex = match.index!;
const endIndex = startIndex + getFixationLength(matchedWord, fixationPoint);
Expand Down
1 change: 1 addition & 0 deletions packages/text-vide/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ export type Options = {
sep: string | string[]; // default: ['<b>', '</b>']
fixationPoint: number; // default: 1
ignoreHtmlTag: boolean; // default: true
ignoreHtmlEntity: boolean; // default: true
};
24 changes: 24 additions & 0 deletions packages/text-vide/src/useCheckIsHtmlEntity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { extractMatchRangeList } from './utils';

const HTML_ENTITY_REGEX = /(&[\w#]+;)/g;

export const useCheckIsHtmlEntity = (text: string) => {
const htmlEntityMatchList = text.matchAll(HTML_ENTITY_REGEX);
const htmlEntityRangeList = extractMatchRangeList(htmlEntityMatchList);
const reversedHtmlEntityRangeList = htmlEntityRangeList.reverse();

return (match: RegExpMatchArray) => {
const startIndex = match.index!;
const entityRange = reversedHtmlEntityRangeList.find(
([rangeStart]) => startIndex > rangeStart,
);

if (!entityRange) {
return false;
}

const [, rangeEnd] = entityRange;
const isInclude = startIndex < rangeEnd;
return isInclude;
};
};
Loading

0 comments on commit 6fbef3d

Please sign in to comment.