diff --git a/README.md b/README.md
index cea70b1..4946504 100644
--- a/README.md
+++ b/README.md
@@ -24,13 +24,13 @@ An Open-Source JavaScript Implementation of [Bionic Reading API](https://bionic-
| [Support ESM and CommonJS](#usage) | ✅ |
| [Custom `sep` Style](#options-sep) | ✅ |
| [Fixation-Points](#options-fixationpoint) | ✅ |
+| [Ignore HTML Tags](#options-ignorehtmltag) | ✅ |
### Work in Progress
-| Feature | Issues |
-| ---------------------------------------------------- | ------------------------------------------------------- |
-| Saccade | [#21](https://github.com/Gumball12/text-vide/issues/21) |
-| Apply the Bionic Reading technique without HTML code | [#36](https://github.com/Gumball12/text-vide/issues/36) |
+| Feature | Issues |
+| ------- | ------------------------------------------------------- |
+| Saccade | [#21](https://github.com/Gumball12/text-vide/issues/21) |
## ⚙️ Install
@@ -85,6 +85,7 @@ textVide('text-vide', {
type Options = Partial<{
sep: string | string[];
fixationPoint: number;
+ ignoreHtmlTag: boolean;
}>;
```
@@ -117,6 +118,17 @@ textVide('text-vide'); // 'text-vide'
textVide('text-vide', { fixationPoint: 5 }); // 'text-vide'
```
+#### `ignoreHtmlTag`
+
+- Default Value: `true`
+
+If this option is `true`, HTML tags are not highlighted.
+
+```ts
+textVite('
abcd
efg'); // 'abcd
efg'
+textVite('abcd
efg', { ignoreHtmlTag: false }); // '<div>abcddiv>efg'
+```
+
## License
[MIT](./LICENSE) @Gumball12
diff --git a/apps/sandbox/src/App.tsx b/apps/sandbox/src/App.tsx
index 12ff33c..03f8b50 100644
--- a/apps/sandbox/src/App.tsx
+++ b/apps/sandbox/src/App.tsx
@@ -19,6 +19,7 @@ type Edits = {
firstSep: string;
secondSep: string;
fixationPoint: string;
+ ignoreHtmlTag: string;
input: string;
};
@@ -26,15 +27,23 @@ const defaultEdits: Edits = {
firstSep: '',
secondSep: '',
fixationPoint: '1',
+ ignoreHtmlTag: '1', // 1 = true, 0 = false
input: INITIAL_INPUT,
};
-const storeEdits = ({ firstSep, secondSep, fixationPoint, input }: Edits) => {
+const storeEdits = ({
+ firstSep,
+ secondSep,
+ fixationPoint,
+ input,
+ ignoreHtmlTag,
+}: Edits) => {
const search = [
`firstSep=${encodeURIComponent(firstSep)}`,
`secondSep=${encodeURIComponent(secondSep)}`,
`fixationPoint=${encodeURIComponent(fixationPoint)}`,
`input=${encodeURIComponent(input)}`,
+ `ignoreHtmlTag=${encodeURIComponent(ignoreHtmlTag)}`,
].join('&');
// eslint-disable-next-line
@@ -90,6 +99,7 @@ type Action = {
| 'INPUT'
| 'HIGHLIGHTED_TEXT'
| 'COPIED'
+ | 'TOGGLE_IGNORE_HTML_TAG'
| 'RESET';
value: string;
copied: boolean;
@@ -120,6 +130,11 @@ const reducer: Reducer = (state, { type, value, copied }) => {
return { ...state, copiedEffect: copied };
}
+ if (type === 'TOGGLE_IGNORE_HTML_TAG') {
+ const nextIgnoreHtmlTag = state.ignoreHtmlTag === '1' ? '0' : '1';
+ return { ...state, ignoreHtmlTag: nextIgnoreHtmlTag };
+ }
+
if (type === 'RESET') {
return {
...defaultEdits,
@@ -145,6 +160,7 @@ const App = () => {
fixationPoint,
copiedEffect,
highlightedText,
+ ignoreHtmlTag,
} = state;
useEffect(() => {
@@ -152,6 +168,7 @@ const App = () => {
const options = {
sep: [firstSep, secondSep],
fixationPoint: parseInt(fixationPoint),
+ ignoreHtmlTag: ignoreHtmlTag === '1',
};
const highlightedText = textVide(input, options);
@@ -167,11 +184,12 @@ const App = () => {
secondSep,
input,
fixationPoint,
+ ignoreHtmlTag,
});
}, DEBOUNCE_TIMEOUT);
return () => clearTimeout(store);
- }, [firstSep, secondSep, input, fixationPoint]);
+ }, [firstSep, secondSep, input, fixationPoint, ignoreHtmlTag]);
const copyUrl = () => {
const { href: url } = location;
@@ -198,6 +216,7 @@ const App = () => {
secondSep,
fixationPoint,
input,
+ ignoreHtmlTag,
});
return (
@@ -276,6 +295,23 @@ const App = () => {
5
+
+
+
+
+
+
diff --git a/packages/text-vide/src/__tests__/getOptions.test.ts b/packages/text-vide/src/__tests__/getOptions.test.ts
index e22090a..252aba1 100644
--- a/packages/text-vide/src/__tests__/getOptions.test.ts
+++ b/packages/text-vide/src/__tests__/getOptions.test.ts
@@ -1,48 +1,55 @@
import { describe, expect, it } from 'vitest';
import getOptions from '../getOptions';
+import { Options } from '../types';
describe('test getOptions()', () => {
it('pass empty object', () => {
- const expected = {
+ const expected: Options = {
sep: ['', ''],
fixationPoint: 1,
+ ignoreHtmlTag: true,
};
expect(getOptions({})).toEqual(expected);
});
it('pass undefined value', () => {
- const maybeOptions = {
+ const undefinedOptionValues = {
sep: undefined,
fixationPoint: undefined,
+ ignoreHtmlTag: undefined,
};
- const expected = {
+ const expected: Options = {
sep: ['', ''],
fixationPoint: 1,
+ ignoreHtmlTag: true,
};
- expect(getOptions(maybeOptions)).toEqual(expected);
+ expect(getOptions(undefinedOptionValues)).toEqual(expected);
});
it('pass empty string value', () => {
const maybeOptions = {
sep: ['', ''],
fixationPoint: undefined,
+ ignoreHtmlTag: undefined,
};
- const expected = {
+ const expected: Options = {
sep: ['', ''],
fixationPoint: 1,
+ ignoreHtmlTag: true,
};
expect(getOptions(maybeOptions)).toEqual(expected);
});
it('pass valid value', () => {
- const expected = {
+ const expected: Options = {
sep: ['a', 'b'],
fixationPoint: 0, // but it's okay
+ ignoreHtmlTag: false,
};
expect(getOptions(expected)).toEqual(expected);
diff --git a/packages/text-vide/src/__tests__/index.test.ts b/packages/text-vide/src/__tests__/index.test.ts
index fb62b5c..deef14f 100644
--- a/packages/text-vide/src/__tests__/index.test.ts
+++ b/packages/text-vide/src/__tests__/index.test.ts
@@ -226,48 +226,124 @@ describe('numbers', () => {
it('1234567890', () => {
const text = '1234567890';
const expected = '1234567890';
- expect(textVide(text), expected);
+ expect(textVide(text)).toBe(expected);
});
it('1234-567890', () => {
const text = '1234-567890';
const expected = '1234-567890';
- expect(textVide(text), expected);
+ expect(textVide(text)).toBe(expected);
});
it('a1234567890', () => {
const text = 'a1234567890';
const expected = 'a1234567890';
- expect(textVide(text), expected);
+ expect(textVide(text)).toBe(expected);
});
it('1234567890a', () => {
const text = '1234567890a';
const expected = '1234567890a';
- expect(textVide(text), expected);
+ expect(textVide(text)).toBe(expected);
});
it('1234a567890', () => {
const text = '1234a567890';
const expected = '1234a567890';
- expect(textVide(text), expected);
+ expect(textVide(text)).toBe(expected);
});
it('@1234567890', () => {
const text = '@1234567890';
const expected = '@1234567890';
- expect(textVide(text), expected);
+ expect(textVide(text)).toBe(expected);
});
it('1234567890@', () => {
const text = '1234567890@';
const expected = '1234567890@';
- expect(textVide(text), expected);
+ expect(textVide(text)).toBe(expected);
});
it('1234@567890', () => {
const text = '1234@567890';
const expected = '1234@567890';
- expect(textVide(text), expected);
+ expect(textVide(text)).toBe(expected);
+ });
+});
+
+describe('with html tags', () => {
+ it('normal text', () => {
+ const text = 'abcdefg';
+ const expected = 'abcdefg';
+ expect(textVide(text)).toBe(expected);
+ });
+
+ it('with a tag', () => {
+ const text = 'abcdefg';
+ const expected = 'abcdefg';
+ expect(textVide(text)).toBe(expected);
+ });
+
+ it('with b tag', () => {
+ const text = 'abcdefg';
+ const expected = 'abcdefg';
+ expect(textVide(text)).toBe(expected);
+ });
+
+ it('with div tag', () => {
+ const text = 'abcd
efg';
+ const expected = 'abcd
efg';
+ expect(textVide(text)).toBe(expected);
+ });
+
+ it('complex html tags', () => {
+ const text = `
+
+
+
normal text:
abcdefgwith a tag:
abcdefgwith b tag:
abcdefgwith div tag:
abcd
efg
+
+
+
+
`;
+
+ const expected = `
+
+
+
normal text:
abcdefgwith a tag:
abcdefgwith b tag:
abcdefgwith div tag:
abcd
efg
+
+
+
+
`;
+
+ expect(textVide(text)).toBe(expected);
});
});
diff --git a/packages/text-vide/src/getOptions.ts b/packages/text-vide/src/getOptions.ts
index 01823e0..ef65415 100644
--- a/packages/text-vide/src/getOptions.ts
+++ b/packages/text-vide/src/getOptions.ts
@@ -3,9 +3,11 @@ import defaults from 'utils/defaults';
const DEFAULT_SEP = ['', ''];
const DEFAULT_FIXATION_POINT = 1;
+const DEFAULT_IGNORE_HTML_TAG = true;
export default (maybeOptions: Partial): Options =>
defaults(maybeOptions, {
sep: DEFAULT_SEP,
fixationPoint: DEFAULT_FIXATION_POINT,
+ ignoreHtmlTag: DEFAULT_IGNORE_HTML_TAG,
});
diff --git a/packages/text-vide/src/index.ts b/packages/text-vide/src/index.ts
index 37f032a..2c6efce 100644
--- a/packages/text-vide/src/index.ts
+++ b/packages/text-vide/src/index.ts
@@ -2,6 +2,7 @@ import { Options } from './types';
import getOptions from './getOptions';
import getFixationLength from './getFixationLength';
import getHighlightedText from './getHighlightedText';
+import { useCheckIsHtmlTag } from './useCheckIsHtmlTag';
const CONVERTIBLE_REGEX = /(\p{L}|\p{Nd})*\p{L}(\p{L}|\p{Nd})*/gu;
@@ -10,13 +11,24 @@ export const textVide = (text: string, maybeOptions: Partial = {}) => {
return '';
}
- const { fixationPoint, sep } = getOptions(maybeOptions);
+ const { fixationPoint, sep, ignoreHtmlTag } = getOptions(maybeOptions);
const convertibleMatchList = text.matchAll(CONVERTIBLE_REGEX);
let result = '';
let lastMatchedIndex = 0;
+ let checkIsHtmlTag: ReturnType | undefined;
+
+ if (ignoreHtmlTag) {
+ checkIsHtmlTag = useCheckIsHtmlTag(text);
+ }
+
for (const match of convertibleMatchList) {
+ const isHtmlTag = checkIsHtmlTag?.(match);
+ if (isHtmlTag) {
+ continue;
+ }
+
const startIndex = match.index!;
const endIndex = startIndex + getFixationLength(match[0], fixationPoint);
diff --git a/packages/text-vide/src/types.ts b/packages/text-vide/src/types.ts
index e68254f..d045992 100644
--- a/packages/text-vide/src/types.ts
+++ b/packages/text-vide/src/types.ts
@@ -1,4 +1,5 @@
export type Options = {
- sep: string | string[];
- fixationPoint: number;
+ sep: string | string[]; // default: ['', '']
+ fixationPoint: number; // default: 1
+ ignoreHtmlTag: boolean; // default: true
};
diff --git a/packages/text-vide/src/useCheckIsHtmlTag.ts b/packages/text-vide/src/useCheckIsHtmlTag.ts
new file mode 100644
index 0000000..74e8688
--- /dev/null
+++ b/packages/text-vide/src/useCheckIsHtmlTag.ts
@@ -0,0 +1,33 @@
+const HTML_TAG_REGEX = /()|(<[^>]*>)/g;
+
+export const useCheckIsHtmlTag = (text: string) => {
+ const htmlTagMatchList = text.matchAll(HTML_TAG_REGEX);
+ const htmlTagRangeList = getHtmlTagRangeList(htmlTagMatchList);
+ const reversedHtmlTagRangeList = htmlTagRangeList.reverse();
+
+ return (match: RegExpMatchArray) => {
+ const startIndex = match.index!;
+ const tagRange = reversedHtmlTagRangeList.find(
+ ([rangeStart]) => startIndex > rangeStart,
+ );
+
+ if (!tagRange) {
+ return false;
+ }
+
+ const [, rangeEnd] = tagRange;
+ const isInclude = startIndex < rangeEnd;
+ return isInclude;
+ };
+};
+
+const getHtmlTagRangeList = (
+ htmlTagMatchList: IterableIterator,
+) =>
+ [...htmlTagMatchList].map(htmlTagMatch => {
+ const startIndex = htmlTagMatch.index!;
+ const [tag] = htmlTagMatch;
+ const { length: tagLength } = tag;
+
+ return [startIndex, startIndex + tagLength - 1];
+ });