Repository: Gumball12/text-vide
Branch: main
Commit: 8adb3593acc8
Files: 58
Total size: 106.9 KB
Directory structure:
gitextract_vz2xfrmz/
├── .eslintignore
├── .eslintrc.js
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ ├── feature_request.md
│ │ └── question.md
│ └── workflows/
│ ├── ci.yaml
│ ├── publish-sandbox.yaml
│ └── publish.yaml
├── .gitignore
├── .husky/
│ └── pre-commit
├── .nvmrc
├── .prettierrc
├── ABOUT_READABILITY.md
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── HOW.md
├── LICENSE
├── README.md
├── apps/
│ ├── benchmark/
│ │ ├── index.js
│ │ └── package.json
│ └── sandbox/
│ ├── index.html
│ ├── package.json
│ ├── src/
│ │ ├── App.tsx
│ │ ├── index.css
│ │ ├── main.tsx
│ │ └── vite-env.d.ts
│ ├── tsconfig.json
│ ├── tsconfig.node.json
│ └── vite.config.ts
├── package.json
├── packages/
│ ├── text-vide/
│ │ ├── .versionrc.json
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── __tests__/
│ │ │ │ ├── getOptions.test.ts
│ │ │ │ └── index.test.ts
│ │ │ ├── getFixationLength.ts
│ │ │ ├── getHighlightedText.ts
│ │ │ ├── getOptions.ts
│ │ │ ├── index.ts
│ │ │ ├── types.ts
│ │ │ ├── useCheckIsHtmlEntity.ts
│ │ │ ├── useCheckIsHtmlTag.ts
│ │ │ └── utils.ts
│ │ ├── tsconfig.json
│ │ ├── vite.config.ts
│ │ └── vitest.config.ts
│ ├── tsconfig/
│ │ ├── base.json
│ │ ├── package.json
│ │ └── vite.json
│ └── utils/
│ ├── __tests__/
│ │ ├── defaults.test.ts
│ │ ├── isEmpty.test.ts
│ │ └── omitBy.test.ts
│ ├── defaults.ts
│ ├── isEmpty.ts
│ ├── omitBy.ts
│ └── package.json
├── pnpm-workspace.yaml
└── turbo.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .eslintignore
================================================
apps/benchmark
================================================
FILE: .eslintrc.js
================================================
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
sourceType: 'module',
},
extends: [
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
'prettier',
],
rules: {
'no-unused-vars': ['error', { varsIgnorePattern: '.*', args: 'none' }],
'no-restricted-syntax': ['error', 'ObjectPattern > RestElement'],
'@typescript-eslint/no-non-null-assertion': 'off',
},
overrides: [
{
files: ['./**/__tests__/**'],
rules: {
'no-restricted-globals': 'off',
'no-restricted-syntax': 'off',
},
},
],
};
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: Gumball12
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: Gumball12
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
================================================
FILE: .github/ISSUE_TEMPLATE/question.md
================================================
---
name: Question
about: Ask and answer
title: ''
labels: question
assignees: Gumball12
---
**Please write questions about the project**
================================================
FILE: .github/workflows/ci.yaml
================================================
name: ci
on:
push:
pull_request:
branches:
- main
jobs:
ci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: pnpm/action-setup@v4.2.0
- uses: actions/setup-node@v6
with:
node-version: 22
registry-url: 'https://registry.npmjs.org'
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- run: pnpm install --frozen-lockfile
- run: pnpm run lint
- run: pnpm run build
- run: pnpm run test
- uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
directory: ./packages/text-vide/coverage
fail_ci_if_error: true
verbose: true
================================================
FILE: .github/workflows/publish-sandbox.yaml
================================================
name: publish-sandbox
on:
push:
branches:
- main
jobs:
publish-sandbox:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: pnpm/action-setup@v4.2.0
- uses: actions/setup-node@v6
with:
node-version: 22
registry-url: 'https://registry.npmjs.org'
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- run: pnpm install --frozen-lockfile
- run: pnpm run build
- name: Deploy
uses: peaceiris/actions-gh-pages@v3.9.3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./apps/sandbox/dist
================================================
FILE: .github/workflows/publish.yaml
================================================
name: publish
on:
release:
types: [created]
permissions:
id-token: write
contents: read
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: pnpm/action-setup@v4.2.0
- uses: actions/setup-node@v6
with:
node-version: 22
registry-url: 'https://registry.npmjs.org'
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- run: npm i -g npm@latest
- run: pnpm install --frozen-lockfile
- run: pnpm run lint
- run: pnpm run test
- run: pnpm run build
- run: |
cp ./README.md ./packages/text-vide/README.md &&
cd ./packages/text-vide &&
npm publish
================================================
FILE: .gitignore
================================================
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.DS_STORE
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
# Next.js build output
.next
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and *not* Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# turbo
.turbo
================================================
FILE: .husky/pre-commit
================================================
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx lint-staged
================================================
FILE: .nvmrc
================================================
v22.12.0
================================================
FILE: .prettierrc
================================================
{
"trailingComma": "all",
"singleQuote": true,
"printWidth": 80,
"arrowParens": "avoid",
"endOfLine": "auto"
}
================================================
FILE: ABOUT_READABILITY.md
================================================
# About Readability
Bionic-Reading is a technique still under study (see `Statements` at the top of [BR Document](https://bionic-reading.com/) or the photo below). So it seems to some people that it doesn't work (or rather it's less readability), but I think it's going to improve over time.

And... I'm not the designer of Bionic-Reading, nor am I the researcher involved. So there's nothing I can say about this.
I'm sorry if I didn't help you. Thank you for reading it.
================================================
FILE: CHANGELOG.md
================================================
# Changelog
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
### [1.8.5](https://github.com/Gumball12/text-vide/compare/v1.8.4...v1.8.5) (2026-03-29)
### Bug Fixes
* **security:** upgrade handlebars to 4.7.9 via pnpm overrides ([304c78d](https://github.com/Gumball12/text-vide/commit/304c78dec05ae2725178d378eff4772cc16a8341))
### [1.8.2](https://github.com/Gumball12/text-vide/compare/v1.8.1...v1.8.2) (2025-03-17)
### [1.8.1](https://github.com/Gumball12/text-vide/compare/v1.8.0...v1.8.1) (2023-11-09)
## [1.8.0](https://github.com/Gumball12/text-vide/compare/v1.7.0...v1.8.0) (2023-11-09)
### Features
* add ignoreHtmlEntity option ([#46](https://github.com/Gumball12/text-vide/issues/46)) ([6fbef3d](https://github.com/Gumball12/text-vide/commit/6fbef3d367cbb6e8b0a3f1028ce2c2a8b91c19b2))
## [1.7.0](https://github.com/Gumball12/text-vide/compare/v1.6.2...v1.7.0) (2023-04-03)
### [1.6.2](https://github.com/Gumball12/text-vide/compare/v1.6.1...v1.6.2) (2023-03-27)
### [1.6.1](https://github.com/Gumball12/text-vide/compare/v1.6.0...v1.6.1) (2023-02-13)
## [1.6.0](https://github.com/Gumball12/text-vide/compare/v1.5.0...v1.6.0) (2022-08-06)
### Features
* add benchmark tests ([977e008](https://github.com/Gumball12/text-vide/commit/977e0087b4670de2196e68574d697f13683d4fb1))
* add ignoreHtmlTag option ([c209f8c](https://github.com/Gumball12/text-vide/commit/c209f8cfffff4f61c12727ccbe02fb19d4f95c67))
* add mb-2px to Input paragraph ([f3f7160](https://github.com/Gumball12/text-vide/commit/f3f71605ea735ac1111c46ee1ff5cff2566cb8d7))
* make the utils directory into the utils package ([45e34a3](https://github.com/Gumball12/text-vide/commit/45e34a37eb1517114e12f1553eedf902c107b335))
## [1.5.0](https://github.com/Gumball12/text-vide/compare/v1.4.0...v1.5.0) (2022-06-04)
### Features
* add logo ([befd0d6](https://github.com/Gumball12/text-vide/commit/befd0d6029b88361765bdc12d1d3d17dda89b96d))
## [1.4.0](https://github.com/Gumball12/bionic-reading/compare/v1.3.2...v1.4.0) (2022-05-28)
### Features
* implement fixationPoint option field ([53697f4](https://github.com/Gumball12/bionic-reading/commit/53697f43d8c291890f1d496e2a7fb2ba9c160106))
* implement getOptions() ([100cb94](https://github.com/Gumball12/bionic-reading/commit/100cb94f83ecf181d04c1e4f67fc8ca0f07cd766))
* implement isEmpty util function ([9450ac9](https://github.com/Gumball12/bionic-reading/commit/9450ac9a67b23248d2254e6120de8ae6047acf57))
* implement isNumberString util function ([928c11b](https://github.com/Gumball12/bionic-reading/commit/928c11bf1e7df2fda56f74dbfcaa0190cc1444be))
* improve readability ([5b2789a](https://github.com/Gumball12/bionic-reading/commit/5b2789ab42997ef52cb06a4a7b1e78a65fbed9c1))
* improve sandbox ([2add790](https://github.com/Gumball12/bionic-reading/commit/2add7906eb040bbe81d90d6be88f29b1993247e2))
* migrate to react ([1f17c36](https://github.com/Gumball12/bionic-reading/commit/1f17c369cdf2cfc558beada554242ec7d24adab7))
### Bug Fixes
* fix footer style ([f9aa436](https://github.com/Gumball12/bionic-reading/commit/f9aa43689e86f102ba31978fced6a397e44f08ec))
* fix incorrect rendering for numbers ([2308eea](https://github.com/Gumball12/bionic-reading/commit/2308eeae27c6fca9f5ba5f8bb609b5e98b1e3ff1))
* use br tag instead of linefeed char ([f8ba04f](https://github.com/Gumball12/bionic-reading/commit/f8ba04f2dd8971d2817e4c0cdbe89416a7b13acb))
### [1.3.2](https://github.com/Gumball12/bionic-reading/compare/v1.3.1...v1.3.2) (2022-05-27)
### Bug Fixes
* copy README for npm publishing ([6cea4c8](https://github.com/Gumball12/bionic-reading/commit/6cea4c877fe8420b9a1ae9afa03ad51a67346027))
### [1.3.1](https://github.com/Gumball12/bionic-reading/compare/v1.3.0...v1.3.1) (2022-05-27)
### Bug Fixes
* fix npm publishing process ([13a7278](https://github.com/Gumball12/bionic-reading/commit/13a7278addc6d3599007610e8cea76acf16f09e0))
## [1.3.0](https://github.com/Gumball12/bionic-reading/compare/v1.2.3...v1.3.0) (2022-05-27)
### Features
* support multiple languages ([5c9c200](https://github.com/Gumball12/bionic-reading/commit/5c9c2002f3ae501554f54e26e56ff33ea3ec1823))
### Bug Fixes
* fix actions/cache key ([139171f](https://github.com/Gumball12/bionic-reading/commit/139171f9c5cd752dacd728f9b27e1eb299119460))
* fix npm script ([f6c6706](https://github.com/Gumball12/bionic-reading/commit/f6c6706570ed1b724add21c5fd2262a7ae123ca8))
* set cache field for test and release pipelines to false ([673f8fb](https://github.com/Gumball12/bionic-reading/commit/673f8fb0e3c89b037daa161c0e7b3138fd6ddf8e))
### [1.2.3](https://github.com/Gumball12/bionic-reading/compare/v1.2.2...v1.2.3) (2022-05-26)
### Features
* implement splitMap util function ([2b1db41](https://github.com/Gumball12/bionic-reading/commit/2b1db41ebc957a75d0bd52a4bebbbbe2085275c3))
* improve to perform the same behavior as the real bionic-reading API ([4e8d6bd](https://github.com/Gumball12/bionic-reading/commit/4e8d6bd93d72c21c12d054521b4b9da8cabd8cee)), closes [#12](https://github.com/Gumball12/bionic-reading/issues/12)
### [1.2.2](https://github.com/Gumball12/bionic-reading/compare/v1.2.1...v1.2.2) (2022-05-23)
### Features
* define default options value ([f71cc5d](https://github.com/Gumball12/bionic-reading/commit/f71cc5dd35ece8e6a8bb05fac33e17e485c9d19b))
* define Options type ([d632ee7](https://github.com/Gumball12/bionic-reading/commit/d632ee747df61154257d6cde084a41ceb9c0a6a7))
* implement getBionicWordConvertor ([b184fb4](https://github.com/Gumball12/bionic-reading/commit/b184fb4956a218f18a9d87d505e033f9d02e401f))
* implement isEmptyString util ([1ffa4e3](https://github.com/Gumball12/bionic-reading/commit/1ffa4e3de4d0b722b621da26e46fb9a3ad39b9f7))
* implement omitBy util ([05c4579](https://github.com/Gumball12/bionic-reading/commit/05c4579c4bbc39f248f201f9a8ea02cfccdbe81a))
* implement publish-sandbox action ([12888db](https://github.com/Gumball12/bionic-reading/commit/12888db0edbe07348c10610e3825c1971f3bccd3))
* implement sandbox ([d56cb82](https://github.com/Gumball12/bionic-reading/commit/d56cb82ae3ca6e6ee69a5ca5109d6846ea205bfc))
* supports strings with newlines ([bce07f7](https://github.com/Gumball12/bionic-reading/commit/bce07f71fde302794e13124bd5e8ff0617d56a37))
### Bug Fixes
* add root field to test only src directory ([f9e00fd](https://github.com/Gumball12/bionic-reading/commit/f9e00fdf698981395e8b0592691be33b2bff8f35))
* handle if passing an empty style string ([7b60e96](https://github.com/Gumball12/bionic-reading/commit/7b60e9672e06a611990c95d726479b6ebd14c82e))
* use base field instead of publicDir ([09cc18c](https://github.com/Gumball12/bionic-reading/commit/09cc18cf545474db5c92cefeeb7e1148f159da1a))
### [1.2.1](https://github.com/Gumball12/bionic-reading/compare/v1.2.0...v1.2.1) (2022-05-22)
## [1.2.0](https://github.com/Gumball12/bionic-reading/compare/v1.1.0...v1.2.0) (2022-05-22)
### Features
* ensure valid text & options object ([a79ac05](https://github.com/Gumball12/bionic-reading/commit/a79ac055fa8afa3666c8f4bc67c31bf4e40df988))
* implement bionicConvertor ([c6ddb43](https://github.com/Gumball12/bionic-reading/commit/c6ddb432d9f140c2ff611191b40219abaf186266))
* implement defaults util function ([58a16d5](https://github.com/Gumball12/bionic-reading/commit/58a16d5577c05971184419aea7adccdf08a3540e))
* implement splitWord util function ([6b84b32](https://github.com/Gumball12/bionic-reading/commit/6b84b3232e380f0b379ad04f2b71893eb3096e45))
* support markdown ([d4f9892](https://github.com/Gumball12/bionic-reading/commit/d4f9892dc15ea58c39e1a3e3d7fc1f4e9e6985be)), closes [#2](https://github.com/Gumball12/bionic-reading/issues/2)
## [1.1.0](https://github.com/Gumball12/bionic-reading/compare/v1.0.2...v1.1.0) (2022-05-21)
### Bug Fixes
* do not use deafult export ([fdc32b2](https://github.com/Gumball12/bionic-reading/commit/fdc32b23bccf1e540812dd0515745ffbb68fd866))
* fix release scripts ([b5495c3](https://github.com/Gumball12/bionic-reading/commit/b5495c3353a29087e2b5c8e219807063fb1490fc))
### [1.0.2](https://github.com/Gumball12/bionic-reading/compare/v1.0.1...v1.0.2) (2022-05-21)
### Bug Fixes
* add registry-url field to publish workflow ([0dbbb1d](https://github.com/Gumball12/bionic-reading/commit/0dbbb1deaffc1324a583d88f2250069066dbb2d4))
### [1.0.1](https://github.com/Gumball12/bionic-reading/compare/v1.0.0...v1.0.1) (2022-05-21)
## 1.0.0 (2022-05-21)
### Features
* implement bionicReading module ([1813a8c](https://github.com/Gumball12/bionic-reading/commit/1813a8c649cb3fcce15099573b3bd46095a2e80c))
* implement ci & publish workflows ([984bfda](https://github.com/Gumball12/bionic-reading/commit/984bfdae1bc04ae423aa06a2154a202bf2b678a7))
================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
to@shj.rip.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.
================================================
FILE: CONTRIBUTING.md
================================================
# Contributing
## Setup
```bash
git clone https://github.com/<username>/text-vide.git
cd text-vide
pnpm install
pnpm dev # run sandbox application (:5173)
pnpm test
pnpm lint
pnpm build
```
## Project structure
```
.
├── apps/
│ └── sandbox/
└── packages/
├── text-vide/ <-- core
├── tsconfig/
└── utils/
```
```mermaid
graph LR;
utils --> TV["Text Vide"]
tsconfig --> TV
TV --> NPM(("Publish to<br>NPM"))
TV --> Sandbox
```
================================================
FILE: HOW.md
================================================
# How was this made?
This module was developed by referring to the official [BR API](https://rapidapi.com/bionic-reading-bionic-reading-default/api/bionic-reading1/). However, there is a difference in that it is implemented with a slightly different logic, such as not highlighting special characters. I'm going to talk about this here.
## The Rules
First, I found the rule below through the BR API call result.
- [Rules for the Number of Characters](#char-length-rules)
- [Rules for Special Characters](#special-chars-rules)
- [Rules for Numbers](#number-rules)
### Rules for the Number of Characters<a id="char-length-rules"></a>
At first, I tried to find a Pattern by adding characters one by one. Below is the result:
| Length (Min) | Length (Max) | Max - Min | Number of Non-Highlighted Chars |
| ------------ | ------------ | --------- | ------------------------------- |
| 0 | 4 | 4 | 1 |
| 5 | 12 | 7 | 2 |
| 13 | 16 | 3 | 3 |
| 17 | 24 | 7 | 4 |
| 25 | 29 | 4 | 5 |
| 30 | 35 | 5 | 6 |
| 36 | 42 | 6 | 7 |
| 43 | 48 | 5 | 8 |
I couldn't find a specific pattern here but thought I could use a Heuristic instead.
> [Pneumonoultramicroscopicsilicovolcanoconiosis](https://en.wikipedia.org/wiki/Longest_word_in_English#cite_note-p45-6) (45 Letters)
The above word is the longest word that exists in the dictionary. That means, the number of characters will never exceed 46. So, I implemented the logic that I don't care about characters longer than a specific number of characters.
Below are the final rules for character count:
| Length (Min) | Length (Max) | Max - Min | Number of Non-Bold Chars |
| ------------ | ------------ | --------- | ------------------------ |
| 0 | 4 | 4 | 1 |
| 5 | 12 | 7 | 2 |
| 13 | 16 | 3 | 3 |
| 17 | 24 | 7 | 4 |
| 25 | 29 | 4 | 5 |
| 30 | 35 | 5 | 6 |
| 36 | 42 | 6 | 7 |
| 43 | 48 | 5 | 8 |
| 49 | infinity | infinity | 9 |
The above is a description of the `fixationPoint` option value of `1`, and the same is done for other cases. [See here](https://docs.google.com/spreadsheets/d/1nG8OoYUK6rXsWdi-L8pWihx9i_aSn9V0eYfLKy9-B-U/edit?usp=sharing) for a complete list of test results for Fixation Points.
### Rules for Special Characters<a id="special-chars-rules"></a>
Special characters at the Beginning or End of a word are **not** highlighted.
```ts
';apple;' -> ';<b>app</b>le;'
```
Special characters placed **inside words** are treated the same as Regular characters.
```ts
'a;ppl;e' -> '<b>a;ppl</b>;e'
```
However, a Dash (`-`) among special characters located inside a word is treated the same as a Space.
```ts
'app-le' -> '<b>ap</b>p-<b>l</b>e'
```
> I thought it was awkward to highlight special characters, so I implemented it to divide them based on special characters.
>
> ```ts
> // Origin
> 'a;ppl;e' -> '<b>a;ppl</b>;e'
> 'app-le' -> '<b>ap</b>p-<b>l</b>e'
>
> // This module
> 'a;ppl;e' -> 'a;<b>pp</b>l;e'
> 'app-le' -> '<b>ap</b>p-<b>l</b>e'
> ```
### Rules for numbers<a id="number-rules"></a>
If there are only numbers, highlight nothing.
```ts
'1234567890' -> '1234567890'
```
If there is a dash between the numbers, it is also not highlighted.
```ts
'1234-567890' -> '1234-567890'
```
When numbers and letters are used together, they are treated as regular characters.
```ts
'a1234567890' -> '<b>a12345678</b>90'
'1234567890a' -> '<b>123456789</b>0a'
'1234a567890' -> '<b>1234a5678</b>90'
```
If a special character other than a dash is between numbers, treat it like a regular character.
```ts
'1234!567890' -> '<b>1234!5678</b>90'
```
Otherwise, it doesn't highlight anything.
```ts
'!1234567890' -> '!1234567890'
'1234567890!' -> '1234567890!'
```
Note: Emojis are treated as special characters, not dashes.
> This module does not highlight even if there are special characters between numbers.
>
> ```ts
> // Origin
> '1234!567890' -> '<b>1234!5678</b>90'
>
> // This module
> '1234!567890' -> '1234!567890'
> ```
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2022 shj
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# TextVide (vide; Latin for "see")
[](https://www.npmjs.com/package/text-vide) )](https://img.shields.io/npm/dm/text-vide)   [](./CHANGELOG.md)
[](https://github.com/Gumball12/text-vide/actions/workflows/ci.yaml) [](https://codecov.io/gh/Gumball12/text-vide)

> **Support all languages that separate words with spaces**
> [Try on Runkit](https://npm.runkit.com/text-vide) or [Online Sandbox](https://gumball12.github.io/text-vide/)
This module is an open source implementation that mimics the behavior of the [Bionic Reading API](https://bionic-reading.com/).
It does not require any additional licenses, except for MIT. ([#38](https://github.com/Gumball12/text-vide/issues/38))
- _[How was this made?](./HOW.md)_
- _[I don't think it's going to be more readable](./ABOUT_READABILITY.md)_
- _[CONTRIBUTING.md](./CONTRIBUTING.md)_
## 💫 Features
| Feature | State |
| ----------------------------------------------------------------------- | ----- |
| [Support all languages](https://github.com/Gumball12/text-vide/pull/16) | ✅ |
| [Support ESM and CommonJS](#usage) | ✅ |
| [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
```
Sun Aug 07 2022 01:33:40 GM +0900
length of normal text: 590
length of text with html tags: 1579
normal#ignoreHtmlTags x 46,106 ops/sec ±4.22% (86 runs sampled)
normal#notIgnoreHtmlTags x 53,200 ops/sec ±0.93% (89 runs sampled)
withHtmlTags#ignoreHtmlTags x 3,213 ops/sec ±0.92% (87 runs sampled)
withHtmlTags#notIgnoreHtmlTags x 3,605 ops/sec ±1.59% (87 runs sampled)
```
[code](./apps/benchmark/index.js)
## ⚙️ Install
```bash
npm i text-vide
yarn add text-vide
pnpm add text-vide
```
### CDN
```html
<script src="https://cdn.jsdelivr.net/npm/text-vide/dist/index.iife.js"></script>
```
## 📖 Usage<a id="usage"></a>
### Browser
```html
<script src="https://cdn.jsdelivr.net/npm/text-vide/dist/index.iife.js"></script>
<script>
const text =
'Bionic Reading is a new method facilitating the reading process by guiding the eyes through text with artificial fixation points. As a result, the reader is only focusing on the highlighted initial letters and lets the brain center complete the word. In a digital world dominated by shallow forms of reading, Bionic Reading aims to encourage a more in-depth reading and understanding of written content.';
const highlightedText = textVide.textVide(text);
console.log(highlightedText); // '<b>Bion</b>ic <b>Readi</b>ng ... <b>writt</b>en <b>conte</b>nt.'
</script>
```
### ESM
```ts
import { textVide } from 'text-vide';
const text =
'Bionic Reading is a new method facilitating the reading process by guiding the eyes through text with artificial fixation points. As a result, the reader is only focusing on the highlighted initial letters and lets the brain center complete the word. In a digital world dominated by shallow forms of reading, Bionic Reading aims to encourage a more in-depth reading and understanding of written content.';
const highlightedText = textVide(text);
console.log(highlightedText); // '<b>Bion</b>ic <b>Readi</b>ng ... <b>writt</b>en <b>conte</b>nt.'
```
### CommonJS
```ts
const { textVide } = require('text-vide');
const text =
'Bionic Reading is a new method facilitating the reading process by guiding the eyes through text with artificial fixation points. As a result, the reader is only focusing on the highlighted initial letters and lets the brain center complete the word. In a digital world dominated by shallow forms of reading, Bionic Reading aims to encourage a more in-depth reading and understanding of written content.';
const highlightedText = textVide(text);
console.log(highlightedText); // '<b>Bion</b>ic <b>Readi</b>ng ... <b>writt</b>en <b>conte</b>nt.'
```
## 📚 API
### `textVide(text: string, options?: Options)`
```ts
textVide('text-vide');
textVide('text-vide', {
// ... Options
});
```
### Options
```ts
type Options = Partial<{
sep: string | string[];
fixationPoint: number;
ignoreHtmlTag: boolean;
ignoreHtmlEntity: boolean;
}>;
```
#### `sep`<a id="options-sep"></a>
- Default: `['<b>', '</b>']`
Passing a string allows you to specify the Beginning and End of the highlighted word at once.
```ts
textVide('text-vide', { sep: '**' }); // '**tex**t-**vid**e'
```
It can also set them up by passing a list of length 2.
```ts
textVide('text-vide', { sep: ['<strong>', '</strong>'] }); // '<strong>tex</strong>t-<strong>vid</strong>e'
```
#### `fixationPoint`<a id="options-fixationpoint"></a>
- Default: `1`
- Range: `[1, 5]`
```ts
// Default fixation-point: 1
textVide('text-vide'); // '<b>tex</b>t-<b>vid</b>e'
// Changed fixation-point: 5
textVide('text-vide', { fixationPoint: 5 }); // '<b>t</b>ext-<b>v</b>ide'
```
#### `ignoreHtmlTag`<a id="options-ignorehtmltag"></a>
- Default: `true`
If this option is `true`, HTML tags are not highlighted.
```ts
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(' abcd>'); // ' <b>abc</b>d>'
textVide(' abcd>', { ignoreHtmlEntity: false }); // &<b>nbs</b>p;<b>abc</b>d&<b>g</b>t;
```
## License
[MIT](./LICENSE) @Gumball12
It does not require any additional licenses, except for MIT. ([#38](https://github.com/Gumball12/text-vide/issues/38))
## Why Monorepo?
I acknowledge that the current monorepo structure might seem complex for the project's requirements. Here's why I chose this approach:
- This project served as a learning experience for implementing monorepo architecture, as I was preparing to introduce it at my workplace
- I intentionally used this small project as a practical exercise to understand monorepo concepts and best practices
- While the current structure might be over-engineered, I plan to maintain it since:
- The project requirements are relatively stable
- Major changes or additions are unlikely
- However, I'm open to simplifying the architecture if there's a compelling reason to do so
================================================
FILE: apps/benchmark/index.js
================================================
const { textVide } = require('text-vide');
const Benchmark = require('benchmark');
const suite = new Benchmark.Suite();
const text =
'orem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.';
const textWithHtmlTags = `<div class="bionic-reader-container">
<span class="w bionic"><b class="b bionic">nor</b>mal </span><span class="w bionic"><b class="b bionic">te</b>xt</span>: <span class="w bionic"><b class="b bionic">abcd</b>efg</span><br><span class="w bionic"><b class="b bionic">wi</b>th </span><span class="w bionic"><b class="b bionic">a</b> </span><span class="w bionic"><b class="b bionic">t</b>ag</span>: <a target="_blank"><span class="w bionic"><b class="b bionic">ab</b>cd</span></a><span class="w bionic"><b class="b bionic">e</b>fg</span><br><span class="w bionic"><b class="b bionic">wi</b>th </span><span class="w bionic"><b class="b bionic">b</b> </span><span class="w bionic"><b class="b bionic">t</b>ag</span>: <b><span class="w bionic"><b class="b bionic">ab</b>cd</span></b><span class="w bionic"><b class="b bionic">e</b>fg</span><br><span class="w bionic"><b class="b bionic">wi</b>th </span><span class="w bionic"><b class="b bionic">d</b>iv </span><span class="w bionic"><b class="b bionic">t</b>ag</span>: <div><span class="w bionic"><b class="b bionic">ab</b>cd</span></div><span class="w bionic"><b class="b bionic">e</b>fg</span><br>
<!-- <div class="br-foot-node">
<p style="margin: 32px 0 32px 70px; font-weight: 700; font-size: 26px; line-height: 1.6em;">
—
</p>
<p>
Bionic Reading<sup>®</sup><br>
A higher dimension of reading.<br>
<a href="https://bionic-reading.com">bionic-reading.com</a>
</p>
<br/>
<br/>
<p>
</p>
</div> -->
</div>`;
suite
.add('normal#ignoreHtmlTags', () => textVide(text))
.add('normal#notIgnoreHtmlTags', () =>
textVide(text, { ignoreHtmlTag: false }),
)
.add('withHtmlTags#ignoreHtmlTags', () => textVide(textWithHtmlTags))
.add('withHtmlTags#notIgnoreHtmlTags', () =>
textVide(textWithHtmlTags, { ignoreHtmlTag: false }),
)
.on('start', () =>
console.log(
[
new Date().toString(),
`length of normal text: ${text.length}`,
`length of text with html tags: ${textWithHtmlTags.length}`,
'',
].join('\n'),
),
)
.on('cycle', evt => {
console.log(evt.target.toString());
})
.run({ async: true });
================================================
FILE: apps/benchmark/package.json
================================================
{
"name": "benchmark",
"version": "0.0.0",
"scripts": {
"benchmark": "node index.js"
},
"devDependencies": {
"benchmark": "^2.1.4",
"text-vide": "workspace:*"
}
}
================================================
FILE: apps/sandbox/index.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Text Vide Sandbox</title>
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"
/>
<link
rel="stylesheet"
href="https://fonts.googleapis.com/icon?family=Material+Icons"
/>
<link
href="https://cdn.jsdelivr.net/gh/sunn-us/SUIT/fonts/static/woff2/SUIT.css"
rel="stylesheet"
/>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
================================================
FILE: apps/sandbox/package.json
================================================
{
"name": "sandbox",
"version": "0.0.0",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview"
},
"dependencies": {
"react": "^18.0.0",
"react-dom": "^18.0.0"
},
"devDependencies": {
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
"@mui/material": "^5.8.1",
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"@unocss/preset-wind": "^66.5.4",
"@unocss/reset": "^66.5.4",
"@vitejs/plugin-react": "^5.0.4",
"text-vide": "workspace:*",
"tsconfig": "workspace:*",
"unocss": "^66.5.4"
}
}
================================================
FILE: apps/sandbox/src/App.tsx
================================================
import {
Box,
Button,
TextField,
ToggleButton,
ToggleButtonGroup,
} from '@mui/material';
import { Reducer, useEffect, useReducer } from 'react';
import { textVide } from 'text-vide';
import logo from './logo.png';
const DEBOUNCE_TIMEOUT = 400;
const COPIED_EFFECT_DEBOUNCE_TIMEOUT = 1200;
const INITIAL_INPUT =
'Bionic Reading is a new method facilitating the reading process by guiding the eyes through text with artificial fixation points. As a result, the reader is only focusing on the highlighted initial letters and lets the brain center complete the word. In a digital world dominated by shallow forms of reading, Bionic Reading aims to encourage a more in-depth reading and understanding of written content.';
type Edits = {
firstSep: string;
secondSep: string;
fixationPoint: string;
ignoreHtmlTag: string;
ignoreHtmlEntity: string;
input: string;
};
const defaultEdits: Edits = {
firstSep: '<b>',
secondSep: '</b>',
fixationPoint: '1',
ignoreHtmlTag: '1', // 1 = true, 0 = false
ignoreHtmlEntity: '1', // 1 = true, 0 = false
input: INITIAL_INPUT,
};
const storeEdits = ({
firstSep,
secondSep,
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
// @ts-ignore
history.replaceState(null, null, `?${search}`);
};
const getEdits = (): Edits => {
const maybeEditsString = location.search.slice(1);
if (!maybeEditsString?.length) {
return defaultEdits;
}
const maybeEdits = maybeEditsString.split('&').reduce((maybeEdits, str) => {
const [maybeKey, maybeValue] = str.split('=');
if (!maybeKey.length) {
return maybeEdits;
}
const key = maybeKey as keyof Edits;
const value = maybeValue ?? defaultEdits[key];
if (!value) {
return maybeEdits;
}
// eslint-disable-next-line
// @ts-ignore
maybeEdits[key] = decodeURIComponent(value);
return maybeEdits;
}, {} as Partial<Edits>);
return {
...defaultEdits,
...maybeEdits,
};
};
const initialEdits = getEdits();
type State = Edits & {
highlightedText: string;
copiedEffect: boolean;
};
type Action = {
type:
| 'FIRST_SEP'
| 'SECOND_SEP'
| 'FIXATION_POINT'
| 'INPUT'
| 'HIGHLIGHTED_TEXT'
| 'COPIED'
| 'TOGGLE_IGNORE_HTML_TAG'
| 'TOGGLE_IGNORE_HTML_ENTITY'
| 'RESET';
value: string;
copied: boolean;
};
const reducer: Reducer<State, Action> = (state, { type, value, copied }) => {
if (type === 'FIRST_SEP') {
return { ...state, firstSep: value };
}
if (type === 'SECOND_SEP') {
return { ...state, secondSep: value };
}
if (type === 'FIXATION_POINT') {
return { ...state, fixationPoint: value };
}
if (type === 'INPUT') {
return { ...state, input: value };
}
if (type === 'HIGHLIGHTED_TEXT') {
return { ...state, highlightedText: value };
}
if (type === 'COPIED') {
return { ...state, copiedEffect: copied };
}
if (type === 'TOGGLE_IGNORE_HTML_TAG') {
const nextIgnoreHtmlTag = state.ignoreHtmlTag === '1' ? '0' : '1';
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,
highlightedText: '',
copiedEffect: false,
};
}
return state;
};
const App = () => {
const [state, dispatchState] = useReducer(reducer, {
...initialEdits,
highlightedText: '',
copiedEffect: false,
});
const {
firstSep,
secondSep,
input,
fixationPoint,
copiedEffect,
highlightedText,
ignoreHtmlTag,
ignoreHtmlEntity,
} = state;
useEffect(() => {
const store = setTimeout(() => {
const options = {
sep: [firstSep, secondSep],
fixationPoint: parseInt(fixationPoint),
ignoreHtmlTag: ignoreHtmlTag === '1',
ignoreHtmlEntity: ignoreHtmlEntity === '1',
};
const highlightedText = textVide(input, options);
dispatchState({
type: 'HIGHLIGHTED_TEXT',
value: highlightedText,
copied: false,
});
storeEdits({
firstSep,
secondSep,
input,
fixationPoint,
ignoreHtmlTag,
ignoreHtmlEntity,
});
}, DEBOUNCE_TIMEOUT);
return () => clearTimeout(store);
}, [
firstSep,
secondSep,
input,
fixationPoint,
ignoreHtmlTag,
ignoreHtmlEntity,
]);
const copyUrl = () => {
const { href: url } = location;
navigator.clipboard.writeText(url);
dispatchState({ type: 'COPIED', value: '', copied: true });
};
useEffect(() => {
const store = setTimeout(
() => dispatchState({ type: 'COPIED', value: '', copied: false }),
COPIED_EFFECT_DEBOUNCE_TIMEOUT,
);
return () => clearTimeout(store);
}, [copiedEffect]);
const reset = () =>
dispatchState({ type: 'RESET', value: '', copied: false });
const isResetDisabled =
JSON.stringify(defaultEdits) ===
JSON.stringify({
firstSep,
secondSep,
fixationPoint,
input,
ignoreHtmlTag,
ignoreHtmlEntity,
});
return (
<div className="max-w-4xl m-auto sm:px-8 px-4 py-4 leading-tight">
<header>
<section>
<img src={logo} className="h-20" />
</section>
<section className="flex justify-between mb-3">
<h1 className="text-2xl">Text Vide Sandbox</h1>
<Button variant="outlined" onClick={copyUrl}>
Copy URL
</Button>
</section>
</header>
<main className="flex flex-col gap-y-8">
<section className="flex flex-col gap-y-4">
<section className="flex justify-between">
<div className="flex gap-x-2">
<TextField
size="small"
label="first sep"
InputLabelProps={{ shrink: true }}
value={firstSep}
onInput={({ target }) =>
dispatchState({
type: 'FIRST_SEP',
value: (target as HTMLInputElement).value,
copied: false,
})
}
required
/>
<TextField
size="small"
label="second sep"
value={secondSep}
onInput={({ target }) =>
dispatchState({
type: 'SECOND_SEP',
value: (target as HTMLInputElement).value,
copied: false,
})
}
InputLabelProps={{ shrink: true }}
/>
</div>
</section>
<section className="flex gap-2 flex-wrap">
<ToggleButtonGroup
size="small"
exclusive
color="primary"
value={fixationPoint}
onChange={(_, value) =>
value &&
dispatchState({ type: 'FIXATION_POINT', value, copied: false })
}
>
<ToggleButton value="1">fixation - 1</ToggleButton>
<ToggleButton value="2">2</ToggleButton>
<ToggleButton value="3">3</ToggleButton>
<ToggleButton value="4">4</ToggleButton>
<ToggleButton value="5">5</ToggleButton>
</ToggleButtonGroup>
<Button
variant="outlined"
color="success"
onClick={() =>
dispatchState({
type: 'TOGGLE_IGNORE_HTML_TAG',
value: '',
copied: false,
})
}
>
{ignoreHtmlTag === '1'
? 'not ignore html tags'
: 'ignore html tags'}
</Button>
<Button
variant="outlined"
color="success"
onClick={() =>
dispatchState({
type: 'TOGGLE_IGNORE_HTML_ENTITY',
value: '',
copied: false,
})
}
>
{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>
<section>
<p className="section-name mb-2px">Input</p>
<textarea
className="border-gray-100 resize-y w-full rounded outline-none p-2 text-lg shadow-md focus:shadow-lg transition-shadow-300 min-h-48"
value={input}
onInput={({ currentTarget: { value } }) =>
dispatchState({ type: 'INPUT', value, copied: false })
}
/>
</section>
<section>
<p className="section-name">Rendered</p>
<Box className="rendered-box">
<pre
className="whitespace-pre-wrap"
dangerouslySetInnerHTML={{ __html: highlightedText }}
/>
</Box>
</section>
<section>
<p className="section-name">Raw Data</p>
<Box className="rendered-box">
<pre className="whitespace-pre-wrap text-sm">{highlightedText}</pre>
</Box>
</section>
</main>
<footer className="fixed right-4 bottom-4 bg-white px-2 py-1">
<a
className="text-gray-400 underline-gray-300"
href="https://github.com/Gumball12/text-vide"
target="_blank"
>
GitHub
</a>
</footer>
</div>
);
};
export default App;
================================================
FILE: apps/sandbox/src/index.css
================================================
* {
font-family: 'SUIT', sans-serif !important;
box-sizing: border-box;
}
pre {
font-weight: 300;
}
pre b {
font-weight: 800;
}
================================================
FILE: apps/sandbox/src/main.tsx
================================================
import ReactDOM from 'react-dom/client';
import App from './App';
import 'uno.css';
import '@unocss/reset/eric-meyer.css';
import './index.css';
// eslint-disable-next-line
ReactDOM.createRoot(document.getElementById('root')!).render(<App />);
================================================
FILE: apps/sandbox/src/vite-env.d.ts
================================================
/// <reference types="vite/client" />
================================================
FILE: apps/sandbox/tsconfig.json
================================================
{
"extends": "tsconfig/vite.json",
"compilerOptions": {
"lib": [
"DOM",
"DOM.Iterable",
"ESNext"
],
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"jsx": "react-jsx"
},
"include": [
"src"
],
"references": [
{
"path": "./tsconfig.node.json"
}
]
}
================================================
FILE: apps/sandbox/tsconfig.node.json
================================================
{
"compilerOptions": {
"composite": true,
"module": "esnext",
"moduleResolution": "node",
},
"include": [
"vite.config.ts"
]
}
================================================
FILE: apps/sandbox/vite.config.ts
================================================
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import Unocss from 'unocss/vite';
import presetWind from '@unocss/preset-wind';
export default defineConfig({
base: '/text-vide/',
plugins: [
Unocss({
presets: [presetWind()],
shortcuts: {
'section-name': 'text-sm text-neutral-600',
'transition-shadow-300': 'transition-shadow duration-300',
'rendered-box':
'w-full min-h-24 max-h-48 rounded bg-light-200 p-2 text-lg shadow-md hover:shadow-lg transition-shadow-300 overflow-y-scroll',
},
}),
react(),
],
});
================================================
FILE: package.json
================================================
{
"packageManager": "pnpm@10.19.0+sha512.c9fc7236e92adf5c8af42fd5bf1612df99c2ceb62f27047032f4720b33f8eacdde311865e91c411f2774f618d82f320808ecb51718bfa82c060c4ba7c76a32b8",
"engines": {
"node": ">=22.12.0"
},
"private": true,
"workspaces": [
"apps/*",
"packages/*"
],
"scripts": {
"build": "turbo run build",
"dev": "turbo run dev --no-cache --parallel --continue",
"preview": "turbo run preview",
"test": "turbo run test",
"benchmark": "turbo run benchmark",
"release": "turbo run release",
"lint": "eslint --ext .ts ./**/src/**.ts",
"prepare": "husky install"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.25.0",
"@typescript-eslint/parser": "^5.25.0",
"eslint": "^8.16.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-prettier": "^4.0.0",
"husky": "^8.0.1",
"lint-staged": "^12.4.1",
"prettier": "^2.6.2",
"turbo": "^1.2.14",
"typescript": "^4.6.4",
"vite": "^7.1.11",
"vitest": "^4.0.1"
},
"lint-staged": {
"*.{js,ts,tsx}": [
"eslint --fix",
"prettier --parser=typescript --write"
]
},
"pnpm": {
"overrides": {
"handlebars": ">=4.7.9"
}
}
}
================================================
FILE: packages/text-vide/.versionrc.json
================================================
{
"infile": "../../CHANGELOG.md"
}
================================================
FILE: packages/text-vide/package.json
================================================
{
"name": "text-vide",
"version": "1.8.5",
"description": "An Open-Source JavaScript Implementation of Bionic Reading.",
"repository": {
"type": "git",
"url": "git+https://github.com/Gumball12/text-vide.git"
},
"author": {
"name": "Gumball12",
"email": "to@shj.rip",
"url": "https://github.com/Gumball12"
},
"license": "MIT",
"bugs": {
"url": "https://github.com/Gumball12/text-vide/issues"
},
"homepage": "https://github.com/Gumball12/text-vide#readme",
"keywords": [
"bionic-reading",
"bionic reading",
"bionic",
"reading",
"readable",
"text",
"highlighted",
"fixation-points"
],
"main": "dist/index.js",
"module": "dist/index.mjs",
"types": "dist/packages/text-vide/src/index.d.ts",
"files": [
"dist"
],
"scripts": {
"test": "vitest run --coverage",
"build": "vite build",
"release": "standard-version"
},
"devDependencies": {
"@vitest/coverage-v8": "^4.0.1",
"standard-version": "^9.5.0",
"tsconfig": "workspace:*",
"utils": "workspace:*",
"vite-plugin-dts": "^2.1.0"
}
}
================================================
FILE: packages/text-vide/src/__tests__/getOptions.test.ts
================================================
import { describe, expect, it } from 'vitest';
import getOptions from '../getOptions';
import { Options } from '../types';
describe('test getOptions()', () => {
it('pass empty object', () => {
const expected: Options = {
sep: ['<b>', '</b>'],
fixationPoint: 1,
ignoreHtmlTag: true,
ignoreHtmlEntity: true,
};
expect(getOptions({})).toEqual(expected);
});
it('pass undefined value', () => {
const undefinedOptionValues = {
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);
});
it('pass empty string value', () => {
const maybeOptions = {
sep: ['', ''],
fixationPoint: undefined,
ignoreHtmlTag: undefined,
ignoreHtmlEntity: undefined,
};
const expected: Options = {
sep: ['', ''],
fixationPoint: 1,
ignoreHtmlTag: true,
ignoreHtmlEntity: true,
};
expect(getOptions(maybeOptions)).toEqual(expected);
});
it('pass valid value', () => {
const expected: Options = {
sep: ['a', 'b'],
fixationPoint: 0, // but it's okay
ignoreHtmlTag: false,
ignoreHtmlEntity: false,
};
expect(getOptions(expected)).toEqual(expected);
});
});
================================================
FILE: packages/text-vide/src/__tests__/index.test.ts
================================================
import { textVide } from '..';
import { describe, expect, it } from 'vitest';
describe('test textVide module', () => {
it('test paragraph 1', () => {
const text =
'orem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.';
const expectedText =
'<b>ore</b>m <b>ips</b>um <b>dol</b>or <b>si</b>t <b>ame</b>t, <b>consetet</b>ur <b>sadipsci</b>ng <b>eli</b>tr, <b>se</b>d <b>dia</b>m <b>nonu</b>my <b>eirm</b>od <b>temp</b>or <b>invidu</b>nt <b>u</b>t <b>labo</b>re <b>e</b>t <b>dolo</b>re <b>mag</b>na <b>aliquy</b>am <b>era</b>t, <b>se</b>d <b>dia</b>m <b>volupt</b>ua. <b>A</b>t <b>ver</b>o <b>eo</b>s <b>e</b>t <b>accus</b>am <b>e</b>t <b>jus</b>to <b>du</b>o <b>dolor</b>es <b>e</b>t <b>e</b>a <b>reb</b>um. <b>Ste</b>t <b>cli</b>ta <b>kas</b>d <b>gubergr</b>en, <b>n</b>o <b>se</b>a <b>takima</b>ta <b>sanct</b>us <b>es</b>t <b>Lor</b>em <b>ips</b>um <b>dol</b>or <b>si</b>t <b>ame</b>t. <b>Lor</b>em <b>ips</b>um <b>dol</b>or <b>si</b>t <b>ame</b>t, <b>consetet</b>ur <b>sadipsci</b>ng <b>eli</b>tr, <b>se</b>d <b>dia</b>m <b>nonu</b>my <b>eirm</b>od <b>temp</b>or <b>invidu</b>nt <b>u</b>t <b>labo</b>re <b>e</b>t <b>dolo</b>re <b>mag</b>na <b>aliquy</b>am <b>era</b>t, <b>se</b>d <b>dia</b>m <b>volupt</b>ua. <b>A</b>t <b>ver</b>o <b>eo</b>s <b>e</b>t <b>accus</b>am <b>e</b>t <b>jus</b>to <b>du</b>o <b>dolor</b>es <b>e</b>t <b>e</b>a <b>reb</b>um. <b>Ste</b>t <b>cli</b>ta <b>kas</b>d <b>gubergr</b>en, <b>n</b>o <b>se</b>a <b>takima</b>ta <b>sanct</b>us <b>es</b>t <b>Lor</b>em <b>ips</b>um <b>dol</b>or <b>si</b>t <b>ame</b>t.';
expect(textVide(text)).toBe(expectedText);
});
it('test paragraph 2', () => {
const text =
'Bionic Reading is a new method facilitating the reading process by guiding the eyes through text with artificial fixation points. As a result, the reader is only focusing on the highlighted initial letters and lets the brain center complete the word. In a digital world dominated by shallow forms of reading, Bionic Reading aims to encourage a more in-depth reading and understanding of written content.';
const expectedText =
'<b>Bion</b>ic <b>Readi</b>ng <b>i</b>s a <b>ne</b>w <b>meth</b>od <b>facilitati</b>ng <b>th</b>e <b>readi</b>ng <b>proce</b>ss <b>b</b>y <b>guidi</b>ng <b>th</b>e <b>eye</b>s <b>throu</b>gh <b>tex</b>t <b>wit</b>h <b>artifici</b>al <b>fixati</b>on <b>poin</b>ts. <b>A</b>s a <b>resu</b>lt, <b>th</b>e <b>read</b>er <b>i</b>s <b>onl</b>y <b>focusi</b>ng <b>o</b>n <b>th</b>e <b>highlight</b>ed <b>initi</b>al <b>lette</b>rs <b>an</b>d <b>let</b>s <b>th</b>e <b>bra</b>in <b>cent</b>er <b>comple</b>te <b>th</b>e <b>wor</b>d. <b>I</b>n a <b>digit</b>al <b>wor</b>ld <b>dominat</b>ed <b>b</b>y <b>shall</b>ow <b>for</b>ms <b>o</b>f <b>readi</b>ng, <b>Bion</b>ic <b>Readi</b>ng <b>aim</b>s <b>t</b>o <b>encoura</b>ge a <b>mor</b>e <b>i</b>n-<b>dep</b>th <b>readi</b>ng <b>an</b>d <b>understand</b>ing <b>o</b>f <b>writt</b>en <b>conte</b>nt.';
expect(textVide(text)).toBe(expectedText);
});
it('test paragraph 3 (with number)', () => {
const text = `Pan Am Flight 7 was a westbound round-the-world flight operated by Pan American World Airways that crashed in the Pacific Ocean on November 8, 1957, while flying from San Francisco International Airport to Honolulu International Airport. The crash of the Boeing 377 Stratocruiser 10-29 (example pictured) killed all thirty-six passengers and eight crew members. The flight's fate was not known until about nine hours after its last radio transmission. No emergency radio reports were received.`;
const expectedText = `<b>Pa</b>n <b>A</b>m <b>Flig</b>ht 7 <b>wa</b>s a <b>westbou</b>nd <b>rou</b>nd-<b>th</b>e-<b>wor</b>ld <b>flig</b>ht <b>operat</b>ed <b>b</b>y <b>Pa</b>n <b>Americ</b>an <b>Wor</b>ld <b>Airwa</b>ys <b>tha</b>t <b>crash</b>ed <b>i</b>n <b>th</b>e <b>Pacif</b>ic <b>Oce</b>an <b>o</b>n <b>Novemb</b>er 8, 1957, <b>whi</b>le <b>flyi</b>ng <b>fro</b>m <b>Sa</b>n <b>Francis</b>co <b>Internatio</b>nal <b>Airpo</b>rt <b>t</b>o <b>Honolu</b>lu <b>Internatio</b>nal <b>Airpo</b>rt. <b>Th</b>e <b>cra</b>sh <b>o</b>f <b>th</b>e <b>Boei</b>ng 377 <b>Stratocrui</b>ser 10-29 (<b>examp</b>le <b>pictur</b>ed) <b>kill</b>ed <b>al</b>l <b>thir</b>ty-<b>si</b>x <b>passenge</b>rs <b>an</b>d <b>eig</b>ht <b>cre</b>w <b>membe</b>rs. <b>Th</b>e <b>flig</b>ht's <b>fat</b>e <b>wa</b>s <b>no</b>t <b>kno</b>wn <b>unt</b>il <b>abo</b>ut <b>nin</b>e <b>hou</b>rs <b>aft</b>er <b>it</b>s <b>las</b>t <b>rad</b>io <b>transmissi</b>on. <b>N</b>o <b>emergen</b>cy <b>rad</b>io <b>repor</b>ts <b>wer</b>e <b>receiv</b>ed.`;
expect(textVide(text)).toBe(expectedText);
});
it('special char (dash)', () => {
const text = '-----';
const expected = '-----';
expect(textVide(text)).toBe(expected);
});
it('test Korean', () => {
const text =
'바이오닉 리딩은 인위적인 fixation point를 사용하여 문장을 읽기 쉽게 만들어줍니다. 눈은 강조된 단어만 따라가며 뇌를 의존해 문장을 완성합니다. 얕고 넓은 디지털 정보 시대에 바이오닉 리딩은 콘텐츠를 깊게 음미할 수 있도록 도와줍니다.';
const expectedText =
'<b>바이오</b>닉 <b>리딩</b>은 <b>인위적</b>인 <b>fixati</b>on <b>poin</b>t를 <b>사용하</b>여 <b>문장</b>을 <b>읽</b>기 <b>쉽</b>게 <b>만들어줍</b>니다. <b>눈</b>은 <b>강조</b>된 <b>단어</b>만 <b>따라가</b>며 <b>뇌</b>를 <b>의존</b>해 <b>문장</b>을 <b>완성합</b>니다. <b>얕</b>고 <b>넓</b>은 <b>디지</b>털 <b>정</b>보 <b>시대</b>에 <b>바이오</b>닉 <b>리딩</b>은 <b>콘텐츠</b>를 <b>깊</b>게 <b>음미</b>할 수 <b>있도</b>록 <b>도와줍</b>니다.';
expect(textVide(text)).toBe(expectedText);
});
it('test Russian', () => {
const text = `Конституция СФРЮ 1974 года - третья и последняя конституция Социалистической Федеративной Республики Югославии. Вступила в силу 21 февраля 1974 года, действие окончательно прекратилось в результате начавшегося распада Югославии в 1992 году.`;
const expected = `<b>Конституц</b>ия <b>СФР</b>Ю 1974 <b>год</b>а - <b>трет</b>ья и <b>последн</b>яя <b>конституц</b>ия <b>Социалистичес</b>кой <b>Федеративн</b>ой <b>Республи</b>ки <b>Югослав</b>ии. <b>Вступи</b>ла в <b>сил</b>у 21 <b>февра</b>ля 1974 <b>год</b>а, <b>действ</b>ие <b>окончатель</b>но <b>прекратило</b>сь в <b>результа</b>те <b>начавшего</b>ся <b>распа</b>да <b>Югослав</b>ии в 1992 <b>год</b>у.`;
expect(textVide(text)).toBe(expected);
});
it('pass empty string', () => {
const text = '';
const expected = '';
expect(textVide(text)).toBe(expected);
});
it('pass len 1 string', () => {
const text = 'a';
const expected = 'a';
expect(textVide(text)).toBe(expected);
});
it('pass strings with line break', () => {
const text = `
a
b
c
`;
const expected = `
a
b
c
`;
expect(textVide(text)).toBe(expected);
});
it('very long word', () => {
const text =
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
const expected =
'<b>aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa</b>aaaaaaaaa';
expect(textVide(text)).toBe(expected);
});
});
describe('test options', () => {
it('pass empty options object', () => {
expect(textVide('aaaa', {})).toBe('<b>aaa</b>a');
});
it('options.sep :: [<strong>, </strong>]', () => {
const text =
'orem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.';
const expectedText =
'<strong>ore</strong>m <strong>ips</strong>um <strong>dol</strong>or <strong>si</strong>t <strong>ame</strong>t, <strong>consetet</strong>ur <strong>sadipsci</strong>ng <strong>eli</strong>tr, <strong>se</strong>d <strong>dia</strong>m <strong>nonu</strong>my <strong>eirm</strong>od <strong>temp</strong>or <strong>invidu</strong>nt <strong>u</strong>t <strong>labo</strong>re <strong>e</strong>t <strong>dolo</strong>re <strong>mag</strong>na <strong>aliquy</strong>am <strong>era</strong>t, <strong>se</strong>d <strong>dia</strong>m <strong>volupt</strong>ua. <strong>A</strong>t <strong>ver</strong>o <strong>eo</strong>s <strong>e</strong>t <strong>accus</strong>am <strong>e</strong>t <strong>jus</strong>to <strong>du</strong>o <strong>dolor</strong>es <strong>e</strong>t <strong>e</strong>a <strong>reb</strong>um. <strong>Ste</strong>t <strong>cli</strong>ta <strong>kas</strong>d <strong>gubergr</strong>en, <strong>n</strong>o <strong>se</strong>a <strong>takima</strong>ta <strong>sanct</strong>us <strong>es</strong>t <strong>Lor</strong>em <strong>ips</strong>um <strong>dol</strong>or <strong>si</strong>t <strong>ame</strong>t. <strong>Lor</strong>em <strong>ips</strong>um <strong>dol</strong>or <strong>si</strong>t <strong>ame</strong>t, <strong>consetet</strong>ur <strong>sadipsci</strong>ng <strong>eli</strong>tr, <strong>se</strong>d <strong>dia</strong>m <strong>nonu</strong>my <strong>eirm</strong>od <strong>temp</strong>or <strong>invidu</strong>nt <strong>u</strong>t <strong>labo</strong>re <strong>e</strong>t <strong>dolo</strong>re <strong>mag</strong>na <strong>aliquy</strong>am <strong>era</strong>t, <strong>se</strong>d <strong>dia</strong>m <strong>volupt</strong>ua. <strong>A</strong>t <strong>ver</strong>o <strong>eo</strong>s <strong>e</strong>t <strong>accus</strong>am <strong>e</strong>t <strong>jus</strong>to <strong>du</strong>o <strong>dolor</strong>es <strong>e</strong>t <strong>e</strong>a <strong>reb</strong>um. <strong>Ste</strong>t <strong>cli</strong>ta <strong>kas</strong>d <strong>gubergr</strong>en, <strong>n</strong>o <strong>se</strong>a <strong>takima</strong>ta <strong>sanct</strong>us <strong>es</strong>t <strong>Lor</strong>em <strong>ips</strong>um <strong>dol</strong>or <strong>si</strong>t <strong>ame</strong>t.';
expect(textVide(text, { sep: ['<strong>', '</strong>'] })).toBe(
expectedText,
);
});
it('options.sep :: `**`', () => {
const text =
'orem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.';
const expectedText =
'**ore**m **ips**um **dol**or **si**t **ame**t, **consetet**ur **sadipsci**ng **eli**tr, **se**d **dia**m **nonu**my **eirm**od **temp**or **invidu**nt **u**t **labo**re **e**t **dolo**re **mag**na **aliquy**am **era**t, **se**d **dia**m **volupt**ua. **A**t **ver**o **eo**s **e**t **accus**am **e**t **jus**to **du**o **dolor**es **e**t **e**a **reb**um. **Ste**t **cli**ta **kas**d **gubergr**en, **n**o **se**a **takima**ta **sanct**us **es**t **Lor**em **ips**um **dol**or **si**t **ame**t. **Lor**em **ips**um **dol**or **si**t **ame**t, **consetet**ur **sadipsci**ng **eli**tr, **se**d **dia**m **nonu**my **eirm**od **temp**or **invidu**nt **u**t **labo**re **e**t **dolo**re **mag**na **aliquy**am **era**t, **se**d **dia**m **volupt**ua. **A**t **ver**o **eo**s **e**t **accus**am **e**t **jus**to **du**o **dolor**es **e**t **e**a **reb**um. **Ste**t **cli**ta **kas**d **gubergr**en, **n**o **se**a **takima**ta **sanct**us **es**t **Lor**em **ips**um **dol**or **si**t **ame**t.';
expect(textVide(text, { sep: '**' })).toBe(expectedText);
});
it('options.sep :: `__`', () => {
const text =
'orem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.';
const expectedText =
'__ore__m __ips__um __dol__or __si__t __ame__t, __consetet__ur __sadipsci__ng __eli__tr, __se__d __dia__m __nonu__my __eirm__od __temp__or __invidu__nt __u__t __labo__re __e__t __dolo__re __mag__na __aliquy__am __era__t, __se__d __dia__m __volupt__ua. __A__t __ver__o __eo__s __e__t __accus__am __e__t __jus__to __du__o __dolor__es __e__t __e__a __reb__um. __Ste__t __cli__ta __kas__d __gubergr__en, __n__o __se__a __takima__ta __sanct__us __es__t __Lor__em __ips__um __dol__or __si__t __ame__t. __Lor__em __ips__um __dol__or __si__t __ame__t, __consetet__ur __sadipsci__ng __eli__tr, __se__d __dia__m __nonu__my __eirm__od __temp__or __invidu__nt __u__t __labo__re __e__t __dolo__re __mag__na __aliquy__am __era__t, __se__d __dia__m __volupt__ua. __A__t __ver__o __eo__s __e__t __accus__am __e__t __jus__to __du__o __dolor__es __e__t __e__a __reb__um. __Ste__t __cli__ta __kas__d __gubergr__en, __n__o __se__a __takima__ta __sanct__us __es__t __Lor__em __ips__um __dol__or __si__t __ame__t.';
expect(textVide(text, { sep: '__' })).toBe(expectedText);
});
it('undefeined options.sep', () => {
const text =
'orem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.';
const expectedText =
'<b>ore</b>m <b>ips</b>um <b>dol</b>or <b>si</b>t <b>ame</b>t, <b>consetet</b>ur <b>sadipsci</b>ng <b>eli</b>tr, <b>se</b>d <b>dia</b>m <b>nonu</b>my <b>eirm</b>od <b>temp</b>or <b>invidu</b>nt <b>u</b>t <b>labo</b>re <b>e</b>t <b>dolo</b>re <b>mag</b>na <b>aliquy</b>am <b>era</b>t, <b>se</b>d <b>dia</b>m <b>volupt</b>ua. <b>A</b>t <b>ver</b>o <b>eo</b>s <b>e</b>t <b>accus</b>am <b>e</b>t <b>jus</b>to <b>du</b>o <b>dolor</b>es <b>e</b>t <b>e</b>a <b>reb</b>um. <b>Ste</b>t <b>cli</b>ta <b>kas</b>d <b>gubergr</b>en, <b>n</b>o <b>se</b>a <b>takima</b>ta <b>sanct</b>us <b>es</b>t <b>Lor</b>em <b>ips</b>um <b>dol</b>or <b>si</b>t <b>ame</b>t. <b>Lor</b>em <b>ips</b>um <b>dol</b>or <b>si</b>t <b>ame</b>t, <b>consetet</b>ur <b>sadipsci</b>ng <b>eli</b>tr, <b>se</b>d <b>dia</b>m <b>nonu</b>my <b>eirm</b>od <b>temp</b>or <b>invidu</b>nt <b>u</b>t <b>labo</b>re <b>e</b>t <b>dolo</b>re <b>mag</b>na <b>aliquy</b>am <b>era</b>t, <b>se</b>d <b>dia</b>m <b>volupt</b>ua. <b>A</b>t <b>ver</b>o <b>eo</b>s <b>e</b>t <b>accus</b>am <b>e</b>t <b>jus</b>to <b>du</b>o <b>dolor</b>es <b>e</b>t <b>e</b>a <b>reb</b>um. <b>Ste</b>t <b>cli</b>ta <b>kas</b>d <b>gubergr</b>en, <b>n</b>o <b>se</b>a <b>takima</b>ta <b>sanct</b>us <b>es</b>t <b>Lor</b>em <b>ips</b>um <b>dol</b>or <b>si</b>t <b>ame</b>t.';
expect(textVide(text, { sep: undefined })).toBe(expectedText);
});
it('pass strings with line break (w/ set options.sep to `__`)', () => {
const text = `
orem ipsum dolor sit amet, consetetur sadipscing elitr,
sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat,
sed diam voluptua.`;
const expected = `
__ore__m __ips__um __dol__or __si__t __ame__t, __consetet__ur __sadipsci__ng __eli__tr,
__se__d __dia__m __nonu__my __eirm__od __temp__or __invidu__nt __u__t __labo__re __e__t __dolo__re __mag__na __aliquy__am __era__t,
__se__d __dia__m __volupt__ua.`;
expect(textVide(text, { sep: '__' })).toBe(expected);
});
it('invalid fixation point', () => {
const text =
'orem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.';
const expectedText =
'<b>ore</b>m <b>ips</b>um <b>dol</b>or <b>si</b>t <b>ame</b>t, <b>consetet</b>ur <b>sadipsci</b>ng <b>eli</b>tr, <b>se</b>d <b>dia</b>m <b>nonu</b>my <b>eirm</b>od <b>temp</b>or <b>invidu</b>nt <b>u</b>t <b>labo</b>re <b>e</b>t <b>dolo</b>re <b>mag</b>na <b>aliquy</b>am <b>era</b>t, <b>se</b>d <b>dia</b>m <b>volupt</b>ua. <b>A</b>t <b>ver</b>o <b>eo</b>s <b>e</b>t <b>accus</b>am <b>e</b>t <b>jus</b>to <b>du</b>o <b>dolor</b>es <b>e</b>t <b>e</b>a <b>reb</b>um. <b>Ste</b>t <b>cli</b>ta <b>kas</b>d <b>gubergr</b>en, <b>n</b>o <b>se</b>a <b>takima</b>ta <b>sanct</b>us <b>es</b>t <b>Lor</b>em <b>ips</b>um <b>dol</b>or <b>si</b>t <b>ame</b>t. <b>Lor</b>em <b>ips</b>um <b>dol</b>or <b>si</b>t <b>ame</b>t, <b>consetet</b>ur <b>sadipsci</b>ng <b>eli</b>tr, <b>se</b>d <b>dia</b>m <b>nonu</b>my <b>eirm</b>od <b>temp</b>or <b>invidu</b>nt <b>u</b>t <b>labo</b>re <b>e</b>t <b>dolo</b>re <b>mag</b>na <b>aliquy</b>am <b>era</b>t, <b>se</b>d <b>dia</b>m <b>volupt</b>ua. <b>A</b>t <b>ver</b>o <b>eo</b>s <b>e</b>t <b>accus</b>am <b>e</b>t <b>jus</b>to <b>du</b>o <b>dolor</b>es <b>e</b>t <b>e</b>a <b>reb</b>um. <b>Ste</b>t <b>cli</b>ta <b>kas</b>d <b>gubergr</b>en, <b>n</b>o <b>se</b>a <b>takima</b>ta <b>sanct</b>us <b>es</b>t <b>Lor</b>em <b>ips</b>um <b>dol</b>or <b>si</b>t <b>ame</b>t.';
expect(textVide(text, { fixationPoint: -1 })).toBe(expectedText);
});
it('emojis', () => {
const text = '👆 h👆el👆lo there, this is s👆ome dummy text';
const expected =
'👆 h👆<b>e</b>l👆<b>l</b>o <b>the</b>re, <b>thi</b>s <b>i</b>s s👆<b>om</b>e <b>dum</b>my <b>tex</b>t';
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 = ' abcd>';
const expectedText = ' <b>abc</b>d>';
expect(textVide(text, { ignoreHtmlEntity: true })).toBe(expectedText);
});
it('ignoreHtmlEntity: false', () => {
const text = ' abcd>';
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])', () => {
it('fixation 2 - 1', () => {
const text =
'Bionic Reading is a new method facilitating the reading process by guiding the eyes through text with artificial fixation points. As a result, the reader is only focusing on the highlighted initial letters and lets the brain center complete the word. In a digital world dominated by shallow forms of reading, Bionic Reading aims to encourage a more in-depth reading and understanding of written content.';
const expected =
'<b>Bion</b>ic <b>Readi</b>ng <b>i</b>s <b>a</b> <b>n</b>ew <b>meth</b>od <b>facilita</b>ting <b>t</b>he <b>readi</b>ng <b>proce</b>ss <b>b</b>y <b>guidi</b>ng <b>t</b>he <b>ey</b>es <b>throu</b>gh <b>te</b>xt <b>wi</b>th <b>artific</b>ial <b>fixat</b>ion <b>poin</b>ts. <b>A</b>s <b>a</b> <b>resu</b>lt, <b>t</b>he <b>read</b>er <b>i</b>s <b>on</b>ly <b>focus</b>ing <b>o</b>n <b>t</b>he <b>highlig</b>hted <b>initi</b>al <b>lette</b>rs <b>a</b>nd <b>le</b>ts <b>t</b>he <b>bra</b>in <b>cent</b>er <b>compl</b>ete <b>t</b>he <b>wo</b>rd. <b>I</b>n <b>a</b> <b>digit</b>al <b>wor</b>ld <b>domina</b>ted <b>b</b>y <b>shall</b>ow <b>for</b>ms <b>o</b>f <b>readi</b>ng, <b>Bion</b>ic <b>Readi</b>ng <b>ai</b>ms <b>t</b>o <b>encour</b>age <b>a</b> <b>mo</b>re <b>i</b>n-<b>dep</b>th <b>readi</b>ng <b>a</b>nd <b>understan</b>ding <b>o</b>f <b>writt</b>en <b>conte</b>nt.';
expect(textVide(text, { fixationPoint: 2 })).toBe(expected);
});
it('fixation 2 - 2', () => {
const text =
'__ore__m __ips__um __dol__or __si__t __ame__t, __consetet__ur __sadipsci__ng __eli__tr, __se__d __dia__m __nonu__my __eirm__od __temp__or __invidu__nt __u__t __labo__re __e__t __dolo__re __mag__na __aliquy__am __era__t, __se__d __dia__m __volupt__ua. __A__t __ver__o __eo__s __e__t __accus__am __e__t __jus__to __du__o __dolor__es __e__t __e__a __reb__um. __Ste__t __cli__ta __kas__d __gubergr__en, __n__o __se__a __takima__ta __sanct__us __es__t __Lor__em __ips__um __dol__or __si__t __ame__t. __Lor__em __ips__um __dol__or __si__t __ame__t, __consetet__ur __sadipsci__ng __eli__tr, __se__d __dia__m __nonu__my __eirm__od __temp__or __invidu__nt __u__t __labo__re __e__t __dolo__re __mag__na __aliquy__am __era__t, __se__d __dia__m __volupt__ua. __A__t __ver__o __eo__s __e__t __accus__am __e__t __jus__to __du__o __dolor__es __e__t __e__a __reb__um. __Ste__t __cli__ta __kas__d __gubergr__en, __n__o __se__a __takima__ta __sanct__us __es__t __Lor__em __ips__um __dol__or __si__t __ame__t.';
const expected =
'__<b>o</b>re__<b>m</b> __<b>i</b>ps__<b>u</b>m __<b>d</b>ol__<b>o</b>r __<b>s</b>i__<b>t</b> __<b>a</b>me__<b>t</b>, __<b>conse</b>tet__<b>u</b>r __<b>sadip</b>sci__<b>n</b>g __<b>e</b>li__<b>t</b>r, __<b>s</b>e__<b>d</b> __<b>d</b>ia__<b>m</b> __<b>no</b>nu__<b>m</b>y __<b>ei</b>rm__<b>o</b>d __<b>te</b>mp__<b>o</b>r __<b>invi</b>du__<b>n</b>t __<b>u</b>__<b>t</b> __<b>la</b>bo__<b>r</b>e __<b>e</b>__<b>t</b> __<b>do</b>lo__<b>r</b>e __<b>m</b>ag__<b>n</b>a __<b>aliq</b>uy__<b>a</b>m __<b>e</b>ra__<b>t</b>, __<b>s</b>e__<b>d</b> __<b>d</b>ia__<b>m</b> __<b>volu</b>pt__<b>u</b>a. __<b>A</b>__<b>t</b> __<b>v</b>er__<b>o</b> __<b>e</b>o__<b>s</b> __<b>e</b>__<b>t</b> __<b>acc</b>us__<b>a</b>m __<b>e</b>__<b>t</b> __<b>j</b>us__<b>t</b>o __<b>d</b>u__<b>o</b> __<b>dol</b>or__<b>e</b>s __<b>e</b>__<b>t</b> __<b>e</b>__<b>a</b> __<b>r</b>eb__<b>u</b>m. __<b>S</b>te__<b>t</b> __<b>c</b>li__<b>t</b>a __<b>k</b>as__<b>d</b> __<b>guber</b>gr__<b>e</b>n, __<b>n</b>__<b>o</b> __<b>s</b>e__<b>a</b> __<b>taki</b>ma__<b>t</b>a __<b>san</b>ct__<b>u</b>s __<b>e</b>s__<b>t</b> __<b>L</b>or__<b>e</b>m __<b>i</b>ps__<b>u</b>m __<b>d</b>ol__<b>o</b>r __<b>s</b>i__<b>t</b> __<b>a</b>me__<b>t</b>. __<b>L</b>or__<b>e</b>m __<b>i</b>ps__<b>u</b>m __<b>d</b>ol__<b>o</b>r __<b>s</b>i__<b>t</b> __<b>a</b>me__<b>t</b>, __<b>conse</b>tet__<b>u</b>r __<b>sadip</b>sci__<b>n</b>g __<b>e</b>li__<b>t</b>r, __<b>s</b>e__<b>d</b> __<b>d</b>ia__<b>m</b> __<b>no</b>nu__<b>m</b>y __<b>ei</b>rm__<b>o</b>d __<b>te</b>mp__<b>o</b>r __<b>invi</b>du__<b>n</b>t __<b>u</b>__<b>t</b> __<b>la</b>bo__<b>r</b>e __<b>e</b>__<b>t</b> __<b>do</b>lo__<b>r</b>e __<b>m</b>ag__<b>n</b>a __<b>aliq</b>uy__<b>a</b>m __<b>e</b>ra__<b>t</b>, __<b>s</b>e__<b>d</b> __<b>d</b>ia__<b>m</b> __<b>volu</b>pt__<b>u</b>a. __<b>A</b>__<b>t</b> __<b>v</b>er__<b>o</b> __<b>e</b>o__<b>s</b> __<b>e</b>__<b>t</b> __<b>acc</b>us__<b>a</b>m __<b>e</b>__<b>t</b> __<b>j</b>us__<b>t</b>o __<b>d</b>u__<b>o</b> __<b>dol</b>or__<b>e</b>s __<b>e</b>__<b>t</b> __<b>e</b>__<b>a</b> __<b>r</b>eb__<b>u</b>m. __<b>S</b>te__<b>t</b> __<b>c</b>li__<b>t</b>a __<b>k</b>as__<b>d</b> __<b>guber</b>gr__<b>e</b>n, __<b>n</b>__<b>o</b> __<b>s</b>e__<b>a</b> __<b>taki</b>ma__<b>t</b>a __<b>san</b>ct__<b>u</b>s __<b>e</b>s__<b>t</b> __<b>L</b>or__<b>e</b>m __<b>i</b>ps__<b>u</b>m __<b>d</b>ol__<b>o</b>r __<b>s</b>i__<b>t</b> __<b>a</b>me__<b>t</b>.';
expect(textVide(text, { fixationPoint: 2 })).toBe(expected);
});
it('fixation 3 - 1', () => {
const text =
'Bionic Reading is a new method facilitating the reading process by guiding the eyes through text with artificial fixation points. As a result, the reader is only focusing on the highlighted initial letters and lets the brain center complete the word. In a digital world dominated by shallow forms of reading, Bionic Reading aims to encourage a more in-depth reading and understanding of written content.';
const expected =
'<b>Bio</b>nic <b>Read</b>ing <b>i</b>s <b>a</b> <b>n</b>ew <b>met</b>hod <b>facili</b>tating <b>t</b>he <b>read</b>ing <b>proc</b>ess <b>b</b>y <b>guid</b>ing <b>t</b>he <b>ey</b>es <b>thro</b>ugh <b>te</b>xt <b>wi</b>th <b>artif</b>icial <b>fixa</b>tion <b>poi</b>nts. <b>A</b>s <b>a</b> <b>res</b>ult, <b>t</b>he <b>rea</b>der <b>i</b>s <b>on</b>ly <b>focu</b>sing <b>o</b>n <b>t</b>he <b>highli</b>ghted <b>init</b>ial <b>lett</b>ers <b>a</b>nd <b>le</b>ts <b>t</b>he <b>bra</b>in <b>cen</b>ter <b>comp</b>lete <b>t</b>he <b>wo</b>rd. <b>I</b>n <b>a</b> <b>digi</b>tal <b>wor</b>ld <b>domin</b>ated <b>b</b>y <b>shal</b>low <b>for</b>ms <b>o</b>f <b>read</b>ing, <b>Bio</b>nic <b>Read</b>ing <b>ai</b>ms <b>t</b>o <b>encou</b>rage <b>a</b> <b>mo</b>re <b>i</b>n-<b>dep</b>th <b>read</b>ing <b>a</b>nd <b>underst</b>anding <b>o</b>f <b>writ</b>ten <b>cont</b>ent.';
expect(textVide(text, { fixationPoint: 3 })).toBe(expected);
});
it('fixation 3 - 2', () => {
const text =
'__ore__m __ips__um __dol__or __si__t __ame__t, __consetet__ur __sadipsci__ng __eli__tr, __se__d __dia__m __nonu__my __eirm__od __temp__or __invidu__nt __u__t __labo__re __e__t __dolo__re __mag__na __aliquy__am __era__t, __se__d __dia__m __volupt__ua. __A__t __ver__o __eo__s __e__t __accus__am __e__t __jus__to __du__o __dolor__es __e__t __e__a __reb__um. __Ste__t __cli__ta __kas__d __gubergr__en, __n__o __se__a __takima__ta __sanct__us __es__t __Lor__em __ips__um __dol__or __si__t __ame__t. __Lor__em __ips__um __dol__or __si__t __ame__t, __consetet__ur __sadipsci__ng __eli__tr, __se__d __dia__m __nonu__my __eirm__od __temp__or __invidu__nt __u__t __labo__re __e__t __dolo__re __mag__na __aliquy__am __era__t, __se__d __dia__m __volupt__ua. __A__t __ver__o __eo__s __e__t __accus__am __e__t __jus__to __du__o __dolor__es __e__t __e__a __reb__um. __Ste__t __cli__ta __kas__d __gubergr__en, __n__o __se__a __takima__ta __sanct__us __es__t __Lor__em __ips__um __dol__or __si__t __ame__t.';
const expected =
'__<b>o</b>re__<b>m</b> __<b>i</b>ps__<b>u</b>m __<b>d</b>ol__<b>o</b>r __<b>s</b>i__<b>t</b> __<b>a</b>me__<b>t</b>, __<b>cons</b>etet__<b>u</b>r __<b>sadi</b>psci__<b>n</b>g __<b>e</b>li__<b>t</b>r, __<b>s</b>e__<b>d</b> __<b>d</b>ia__<b>m</b> __<b>no</b>nu__<b>m</b>y __<b>ei</b>rm__<b>o</b>d __<b>te</b>mp__<b>o</b>r __<b>inv</b>idu__<b>n</b>t __<b>u</b>__<b>t</b> __<b>la</b>bo__<b>r</b>e __<b>e</b>__<b>t</b> __<b>do</b>lo__<b>r</b>e __<b>m</b>ag__<b>n</b>a __<b>ali</b>quy__<b>a</b>m __<b>e</b>ra__<b>t</b>, __<b>s</b>e__<b>d</b> __<b>d</b>ia__<b>m</b> __<b>vol</b>upt__<b>u</b>a. __<b>A</b>__<b>t</b> __<b>v</b>er__<b>o</b> __<b>e</b>o__<b>s</b> __<b>e</b>__<b>t</b> __<b>acc</b>us__<b>a</b>m __<b>e</b>__<b>t</b> __<b>j</b>us__<b>t</b>o __<b>d</b>u__<b>o</b> __<b>dol</b>or__<b>e</b>s __<b>e</b>__<b>t</b> __<b>e</b>__<b>a</b> __<b>r</b>eb__<b>u</b>m. __<b>S</b>te__<b>t</b> __<b>c</b>li__<b>t</b>a __<b>k</b>as__<b>d</b> __<b>gube</b>rgr__<b>e</b>n, __<b>n</b>__<b>o</b> __<b>s</b>e__<b>a</b> __<b>tak</b>ima__<b>t</b>a __<b>san</b>ct__<b>u</b>s __<b>e</b>s__<b>t</b> __<b>L</b>or__<b>e</b>m __<b>i</b>ps__<b>u</b>m __<b>d</b>ol__<b>o</b>r __<b>s</b>i__<b>t</b> __<b>a</b>me__<b>t</b>. __<b>L</b>or__<b>e</b>m __<b>i</b>ps__<b>u</b>m __<b>d</b>ol__<b>o</b>r __<b>s</b>i__<b>t</b> __<b>a</b>me__<b>t</b>, __<b>cons</b>etet__<b>u</b>r __<b>sadi</b>psci__<b>n</b>g __<b>e</b>li__<b>t</b>r, __<b>s</b>e__<b>d</b> __<b>d</b>ia__<b>m</b> __<b>no</b>nu__<b>m</b>y __<b>ei</b>rm__<b>o</b>d __<b>te</b>mp__<b>o</b>r __<b>inv</b>idu__<b>n</b>t __<b>u</b>__<b>t</b> __<b>la</b>bo__<b>r</b>e __<b>e</b>__<b>t</b> __<b>do</b>lo__<b>r</b>e __<b>m</b>ag__<b>n</b>a __<b>ali</b>quy__<b>a</b>m __<b>e</b>ra__<b>t</b>, __<b>s</b>e__<b>d</b> __<b>d</b>ia__<b>m</b> __<b>vol</b>upt__<b>u</b>a. __<b>A</b>__<b>t</b> __<b>v</b>er__<b>o</b> __<b>e</b>o__<b>s</b> __<b>e</b>__<b>t</b> __<b>acc</b>us__<b>a</b>m __<b>e</b>__<b>t</b> __<b>j</b>us__<b>t</b>o __<b>d</b>u__<b>o</b> __<b>dol</b>or__<b>e</b>s __<b>e</b>__<b>t</b> __<b>e</b>__<b>a</b> __<b>r</b>eb__<b>u</b>m. __<b>S</b>te__<b>t</b> __<b>c</b>li__<b>t</b>a __<b>k</b>as__<b>d</b> __<b>gube</b>rgr__<b>e</b>n, __<b>n</b>__<b>o</b> __<b>s</b>e__<b>a</b> __<b>tak</b>ima__<b>t</b>a __<b>san</b>ct__<b>u</b>s __<b>e</b>s__<b>t</b> __<b>L</b>or__<b>e</b>m __<b>i</b>ps__<b>u</b>m __<b>d</b>ol__<b>o</b>r __<b>s</b>i__<b>t</b> __<b>a</b>me__<b>t</b>.';
expect(textVide(text, { fixationPoint: 3 })).toBe(expected);
});
it('fixation 4 - 1', () => {
const text =
'Bionic Reading is a new method facilitating the reading process by guiding the eyes through text with artificial fixation points. As a result, the reader is only focusing on the highlighted initial letters and lets the brain center complete the word. In a digital world dominated by shallow forms of reading, Bionic Reading aims to encourage a more in-depth reading and understanding of written content.';
const expected =
'<b>Bi</b>onic <b>Re</b>ading <b>i</b>s a <b>n</b>ew <b>me</b>thod <b>faci</b>litating <b>t</b>he <b>re</b>ading <b>pr</b>ocess <b>b</b>y <b>gu</b>iding <b>t</b>he <b>ey</b>es <b>th</b>rough <b>te</b>xt <b>wi</b>th <b>art</b>ificial <b>fix</b>ation <b>po</b>ints. <b>A</b>s a <b>re</b>sult, <b>t</b>he <b>re</b>ader <b>i</b>s <b>on</b>ly <b>foc</b>using <b>o</b>n <b>t</b>he <b>high</b>lighted <b>in</b>itial <b>le</b>tters <b>a</b>nd <b>le</b>ts <b>t</b>he <b>br</b>ain <b>ce</b>nter <b>com</b>plete <b>t</b>he <b>wo</b>rd. <b>I</b>n a <b>di</b>gital <b>wo</b>rld <b>dom</b>inated <b>b</b>y <b>sh</b>allow <b>fo</b>rms <b>o</b>f <b>re</b>ading, <b>Bi</b>onic <b>Re</b>ading <b>ai</b>ms <b>t</b>o <b>enc</b>ourage a <b>mo</b>re <b>i</b>n-<b>de</b>pth <b>re</b>ading <b>a</b>nd <b>under</b>standing <b>o</b>f <b>wr</b>itten <b>co</b>ntent.';
expect(textVide(text, { fixationPoint: 4 })).toBe(expected);
});
it('fixation 4 - 2', () => {
const text =
'__ore__m __ips__um __dol__or __si__t __ame__t, __consetet__ur __sadipsci__ng __eli__tr, __se__d __dia__m __nonu__my __eirm__od __temp__or __invidu__nt __u__t __labo__re __e__t __dolo__re __mag__na __aliquy__am __era__t, __se__d __dia__m __volupt__ua. __A__t __ver__o __eo__s __e__t __accus__am __e__t __jus__to __du__o __dolor__es __e__t __e__a __reb__um. __Ste__t __cli__ta __kas__d __gubergr__en, __n__o __se__a __takima__ta __sanct__us __es__t __Lor__em __ips__um __dol__or __si__t __ame__t. __Lor__em __ips__um __dol__or __si__t __ame__t, __consetet__ur __sadipsci__ng __eli__tr, __se__d __dia__m __nonu__my __eirm__od __temp__or __invidu__nt __u__t __labo__re __e__t __dolo__re __mag__na __aliquy__am __era__t, __se__d __dia__m __volupt__ua. __A__t __ver__o __eo__s __e__t __accus__am __e__t __jus__to __du__o __dolor__es __e__t __e__a __reb__um. __Ste__t __cli__ta __kas__d __gubergr__en, __n__o __se__a __takima__ta __sanct__us __es__t __Lor__em __ips__um __dol__or __si__t __ame__t.';
const expected =
'__<b>o</b>re__m __<b>i</b>ps__<b>u</b>m __<b>d</b>ol__<b>o</b>r __<b>s</b>i__t __<b>a</b>me__t, __<b>con</b>setet__<b>u</b>r __<b>sad</b>ipsci__<b>n</b>g __<b>e</b>li__<b>t</b>r, __<b>s</b>e__d __<b>d</b>ia__m __<b>no</b>nu__<b>m</b>y __<b>ei</b>rm__<b>o</b>d __<b>te</b>mp__<b>o</b>r __<b>in</b>vidu__<b>n</b>t __u__t __<b>la</b>bo__<b>r</b>e __e__t __<b>do</b>lo__<b>r</b>e __<b>m</b>ag__<b>n</b>a __<b>al</b>iquy__<b>a</b>m __<b>e</b>ra__t, __<b>s</b>e__d __<b>d</b>ia__m __<b>vo</b>lupt__<b>u</b>a. __A__t __<b>v</b>er__o __<b>e</b>o__s __e__t __<b>ac</b>cus__<b>a</b>m __e__t __<b>j</b>us__<b>t</b>o __<b>d</b>u__o __<b>do</b>lor__<b>e</b>s __e__t __e__a __<b>r</b>eb__<b>u</b>m. __<b>S</b>te__t __<b>c</b>li__<b>t</b>a __<b>k</b>as__d __<b>gu</b>bergr__<b>e</b>n, __n__o __<b>s</b>e__a __<b>ta</b>kima__<b>t</b>a __<b>sa</b>nct__<b>u</b>s __<b>e</b>s__t __<b>L</b>or__<b>e</b>m __<b>i</b>ps__<b>u</b>m __<b>d</b>ol__<b>o</b>r __<b>s</b>i__t __<b>a</b>me__t. __<b>L</b>or__<b>e</b>m __<b>i</b>ps__<b>u</b>m __<b>d</b>ol__<b>o</b>r __<b>s</b>i__t __<b>a</b>me__t, __<b>con</b>setet__<b>u</b>r __<b>sad</b>ipsci__<b>n</b>g __<b>e</b>li__<b>t</b>r, __<b>s</b>e__d __<b>d</b>ia__m __<b>no</b>nu__<b>m</b>y __<b>ei</b>rm__<b>o</b>d __<b>te</b>mp__<b>o</b>r __<b>in</b>vidu__<b>n</b>t __u__t __<b>la</b>bo__<b>r</b>e __e__t __<b>do</b>lo__<b>r</b>e __<b>m</b>ag__<b>n</b>a __<b>al</b>iquy__<b>a</b>m __<b>e</b>ra__t, __<b>s</b>e__d __<b>d</b>ia__m __<b>vo</b>lupt__<b>u</b>a. __A__t __<b>v</b>er__o __<b>e</b>o__s __e__t __<b>ac</b>cus__<b>a</b>m __e__t __<b>j</b>us__<b>t</b>o __<b>d</b>u__o __<b>do</b>lor__<b>e</b>s __e__t __e__a __<b>r</b>eb__<b>u</b>m. __<b>S</b>te__t __<b>c</b>li__<b>t</b>a __<b>k</b>as__d __<b>gu</b>bergr__<b>e</b>n, __n__o __<b>s</b>e__a __<b>ta</b>kima__<b>t</b>a __<b>sa</b>nct__<b>u</b>s __<b>e</b>s__t __<b>L</b>or__<b>e</b>m __<b>i</b>ps__<b>u</b>m __<b>d</b>ol__<b>o</b>r __<b>s</b>i__t __<b>a</b>me__t.';
expect(textVide(text, { fixationPoint: 4 })).toBe(expected);
});
it('fixation 5 - 1', () => {
const text =
'Bionic Reading is a new method facilitating the reading process by guiding the eyes through text with artificial fixation points. As a result, the reader is only focusing on the highlighted initial letters and lets the brain center complete the word. In a digital world dominated by shallow forms of reading, Bionic Reading aims to encourage a more in-depth reading and understanding of written content.';
const expected =
'<b>Bi</b>onic <b>Re</b>ading <b>i</b>s a <b>n</b>ew <b>me</b>thod <b>fac</b>ilitating <b>t</b>he <b>re</b>ading <b>pr</b>ocess <b>b</b>y <b>gu</b>iding <b>t</b>he <b>e</b>yes <b>th</b>rough <b>t</b>ext <b>w</b>ith <b>art</b>ificial <b>fi</b>xation <b>po</b>ints. <b>A</b>s a <b>re</b>sult, <b>t</b>he <b>re</b>ader <b>i</b>s <b>o</b>nly <b>fo</b>cusing <b>o</b>n <b>t</b>he <b>hig</b>hlighted <b>in</b>itial <b>le</b>tters <b>a</b>nd <b>l</b>ets <b>t</b>he <b>br</b>ain <b>ce</b>nter <b>co</b>mplete <b>t</b>he <b>w</b>ord. <b>I</b>n a <b>di</b>gital <b>wo</b>rld <b>do</b>minated <b>b</b>y <b>sh</b>allow <b>fo</b>rms <b>o</b>f <b>re</b>ading, <b>Bi</b>onic <b>Re</b>ading <b>a</b>ims <b>t</b>o <b>en</b>courage a <b>m</b>ore <b>i</b>n-<b>de</b>pth <b>re</b>ading <b>a</b>nd <b>und</b>erstanding <b>o</b>f <b>wr</b>itten <b>co</b>ntent.';
expect(textVide(text, { fixationPoint: 5 })).toBe(expected);
});
it('fixation 5 - 2', () => {
const text =
'__ore__m __ips__um __dol__or __si__t __ame__t, __consetet__ur __sadipsci__ng __eli__tr, __se__d __dia__m __nonu__my __eirm__od __temp__or __invidu__nt __u__t __labo__re __e__t __dolo__re __mag__na __aliquy__am __era__t, __se__d __dia__m __volupt__ua. __A__t __ver__o __eo__s __e__t __accus__am __e__t __jus__to __du__o __dolor__es __e__t __e__a __reb__um. __Ste__t __cli__ta __kas__d __gubergr__en, __n__o __se__a __takima__ta __sanct__us __es__t __Lor__em __ips__um __dol__or __si__t __ame__t. __Lor__em __ips__um __dol__or __si__t __ame__t, __consetet__ur __sadipsci__ng __eli__tr, __se__d __dia__m __nonu__my __eirm__od __temp__or __invidu__nt __u__t __labo__re __e__t __dolo__re __mag__na __aliquy__am __era__t, __se__d __dia__m __volupt__ua. __A__t __ver__o __eo__s __e__t __accus__am __e__t __jus__to __du__o __dolor__es __e__t __e__a __reb__um. __Ste__t __cli__ta __kas__d __gubergr__en, __n__o __se__a __takima__ta __sanct__us __es__t __Lor__em __ips__um __dol__or __si__t __ame__t.';
const expected =
'__<b>o</b>re__m __<b>i</b>ps__<b>u</b>m __<b>d</b>ol__<b>o</b>r __<b>s</b>i__t __<b>a</b>me__t, __<b>co</b>nsetet__<b>u</b>r __<b>sa</b>dipsci__<b>n</b>g __<b>e</b>li__<b>t</b>r, __<b>s</b>e__d __<b>d</b>ia__m __<b>n</b>onu__<b>m</b>y __<b>e</b>irm__<b>o</b>d __<b>t</b>emp__<b>o</b>r __<b>in</b>vidu__<b>n</b>t __u__t __<b>l</b>abo__<b>r</b>e __e__t __<b>d</b>olo__<b>r</b>e __<b>m</b>ag__<b>n</b>a __<b>al</b>iquy__<b>a</b>m __<b>e</b>ra__t, __<b>s</b>e__d __<b>d</b>ia__m __<b>vo</b>lupt__<b>u</b>a. __A__t __<b>v</b>er__o __<b>e</b>o__s __e__t __<b>ac</b>cus__<b>a</b>m __e__t __<b>j</b>us__<b>t</b>o __<b>d</b>u__o __<b>do</b>lor__<b>e</b>s __e__t __e__a __<b>r</b>eb__<b>u</b>m. __<b>S</b>te__t __<b>c</b>li__<b>t</b>a __<b>k</b>as__d __<b>gu</b>bergr__<b>e</b>n, __n__o __<b>s</b>e__a __<b>ta</b>kima__<b>t</b>a __<b>sa</b>nct__<b>u</b>s __<b>e</b>s__t __<b>L</b>or__<b>e</b>m __<b>i</b>ps__<b>u</b>m __<b>d</b>ol__<b>o</b>r __<b>s</b>i__t __<b>a</b>me__t. __<b>L</b>or__<b>e</b>m __<b>i</b>ps__<b>u</b>m __<b>d</b>ol__<b>o</b>r __<b>s</b>i__t __<b>a</b>me__t, __<b>co</b>nsetet__<b>u</b>r __<b>sa</b>dipsci__<b>n</b>g __<b>e</b>li__<b>t</b>r, __<b>s</b>e__d __<b>d</b>ia__m __<b>n</b>onu__<b>m</b>y __<b>e</b>irm__<b>o</b>d __<b>t</b>emp__<b>o</b>r __<b>in</b>vidu__<b>n</b>t __u__t __<b>l</b>abo__<b>r</b>e __e__t __<b>d</b>olo__<b>r</b>e __<b>m</b>ag__<b>n</b>a __<b>al</b>iquy__<b>a</b>m __<b>e</b>ra__t, __<b>s</b>e__d __<b>d</b>ia__m __<b>vo</b>lupt__<b>u</b>a. __A__t __<b>v</b>er__o __<b>e</b>o__s __e__t __<b>ac</b>cus__<b>a</b>m __e__t __<b>j</b>us__<b>t</b>o __<b>d</b>u__o __<b>do</b>lor__<b>e</b>s __e__t __e__a __<b>r</b>eb__<b>u</b>m. __<b>S</b>te__t __<b>c</b>li__<b>t</b>a __<b>k</b>as__d __<b>gu</b>bergr__<b>e</b>n, __n__o __<b>s</b>e__a __<b>ta</b>kima__<b>t</b>a __<b>sa</b>nct__<b>u</b>s __<b>e</b>s__t __<b>L</b>or__<b>e</b>m __<b>i</b>ps__<b>u</b>m __<b>d</b>ol__<b>o</b>r __<b>s</b>i__t __<b>a</b>me__t.';
expect(textVide(text, { fixationPoint: 5 })).toBe(expected);
});
});
describe('numbers', () => {
it('1234567890', () => {
const text = '1234567890';
const expected = '1234567890';
expect(textVide(text)).toBe(expected);
});
it('1234-567890', () => {
const text = '1234-567890';
const expected = '1234-567890';
expect(textVide(text)).toBe(expected);
});
it('a1234567890', () => {
const text = 'a1234567890';
const expected = '<b>a12345678</b>90';
expect(textVide(text)).toBe(expected);
});
it('1234567890a', () => {
const text = '1234567890a';
const expected = '<b>123456789</b>0a';
expect(textVide(text)).toBe(expected);
});
it('1234a567890', () => {
const text = '1234a567890';
const expected = '<b>1234a5678</b>90';
expect(textVide(text)).toBe(expected);
});
it('@1234567890', () => {
const text = '@1234567890';
const expected = '@1234567890';
expect(textVide(text)).toBe(expected);
});
it('1234567890@', () => {
const text = '1234567890@';
const expected = '1234567890@';
expect(textVide(text)).toBe(expected);
});
it('1234@567890', () => {
const text = '1234@567890';
const expected = '1234@567890';
expect(textVide(text)).toBe(expected);
});
});
describe('with html tags', () => {
it('normal text', () => {
const text = 'abcdefg';
const expected = '<b>abcde</b>fg';
expect(textVide(text)).toBe(expected);
});
it('with a tag', () => {
const text = '<a>abcd</a>efg';
const expected = '<a><b>abc</b>d</a><b>ef</b>g';
expect(textVide(text)).toBe(expected);
});
it('with b tag', () => {
const text = '<b>abcd</b>efg';
const expected = '<b><b>abc</b>d</b><b>ef</b>g';
expect(textVide(text)).toBe(expected);
});
it('with div tag', () => {
const text = '<div>abcd</div>efg';
const expected = '<div><b>abc</b>d</div><b>ef</b>g';
expect(textVide(text)).toBe(expected);
});
it('complex html tags', () => {
const text = `<div class="bionic-reader-container">
<span class="w bionic"><b class="b bionic">nor</b>mal </span><span class="w bionic"><b class="b bionic">te</b>xt</span>: <span class="w bionic"><b class="b bionic">abcd</b>efg</span><br><span class="w bionic"><b class="b bionic">wi</b>th </span><span class="w bionic"><b class="b bionic">a</b> </span><span class="w bionic"><b class="b bionic">t</b>ag</span>: <a target="_blank"><span class="w bionic"><b class="b bionic">ab</b>cd</span></a><span class="w bionic"><b class="b bionic">e</b>fg</span><br><span class="w bionic"><b class="b bionic">wi</b>th </span><span class="w bionic"><b class="b bionic">b</b> </span><span class="w bionic"><b class="b bionic">t</b>ag</span>: <b><span class="w bionic"><b class="b bionic">ab</b>cd</span></b><span class="w bionic"><b class="b bionic">e</b>fg</span><br><span class="w bionic"><b class="b bionic">wi</b>th </span><span class="w bionic"><b class="b bionic">d</b>iv </span><span class="w bionic"><b class="b bionic">t</b>ag</span>: <div><span class="w bionic"><b class="b bionic">ab</b>cd</span></div><span class="w bionic"><b class="b bionic">e</b>fg</span><br>
<!-- <div class="br-foot-node">
<p style="margin: 32px 0 32px 70px; font-weight: 700; font-size: 26px; line-height: 1.6em;">
—
</p>
<p>
Bionic Reading<sup>®</sup><br>
A higher dimension of reading.<br>
<a href="https://bionic-reading.com">bionic-reading.com</a>
</p>
<br/>
<br/>
<p>
</p>
</div> -->
</div>`;
const expected = `<div class="bionic-reader-container">
<span class="w bionic"><b class="b bionic"><b>no</b>r</b><b>ma</b>l </span><span class="w bionic"><b class="b bionic"><b>t</b>e</b><b>x</b>t</span>: <span class="w bionic"><b class="b bionic"><b>abc</b>d</b><b>ef</b>g</span><br><span class="w bionic"><b class="b bionic"><b>w</b>i</b><b>t</b>h </span><span class="w bionic"><b class="b bionic">a</b> </span><span class="w bionic"><b class="b bionic">t</b><b>a</b>g</span>: <a target="_blank"><span class="w bionic"><b class="b bionic"><b>a</b>b</b><b>c</b>d</span></a><span class="w bionic"><b class="b bionic">e</b><b>f</b>g</span><br><span class="w bionic"><b class="b bionic"><b>w</b>i</b><b>t</b>h </span><span class="w bionic"><b class="b bionic">b</b> </span><span class="w bionic"><b class="b bionic">t</b><b>a</b>g</span>: <b><span class="w bionic"><b class="b bionic"><b>a</b>b</b><b>c</b>d</span></b><span class="w bionic"><b class="b bionic">e</b><b>f</b>g</span><br><span class="w bionic"><b class="b bionic"><b>w</b>i</b><b>t</b>h </span><span class="w bionic"><b class="b bionic">d</b><b>i</b>v </span><span class="w bionic"><b class="b bionic">t</b><b>a</b>g</span>: <div><span class="w bionic"><b class="b bionic"><b>a</b>b</b><b>c</b>d</span></div><span class="w bionic"><b class="b bionic">e</b><b>f</b>g</span><br>
<!-- <div class="br-foot-node">
<p style="margin: 32px 0 32px 70px; font-weight: 700; font-size: 26px; line-height: 1.6em;">
—
</p>
<p>
Bionic Reading<sup>®</sup><br>
A higher dimension of reading.<br>
<a href="https://bionic-reading.com">bionic-reading.com</a>
</p>
<br/>
<br/>
<p>
</p>
</div> -->
</div>`;
expect(textVide(text)).toBe(expected);
});
});
================================================
FILE: packages/text-vide/src/getFixationLength.ts
================================================
const FIXATION_BOUNDARY_LIST = [
[0, 4, 12, 17, 24, 29, 35, 42, 48],
[1, 2, 7, 10, 13, 14, 19, 22, 25, 28, 31, 34, 37, 40, 43, 46, 49],
[
1, 2, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39,
41, 43, 45, 47, 49,
],
[
0, 2, 4, 5, 6, 8, 9, 11, 14, 15, 17, 18, 20, 0, 21, 23, 24, 26, 27, 29, 30,
32, 33, 35, 36, 38, 39, 41, 42, 44, 45, 47, 48,
],
[
0, 2, 3, 5, 6, 7, 8, 10, 11, 12, 14, 15, 17, 19, 20, 21, 23, 24, 25, 26, 28,
29, 30, 32, 33, 34, 35, 37, 38, 39, 41, 42, 43, 44, 46, 47, 48,
],
];
// TODO: caching
export default (word: string, fixationPoint: number) => {
const { length: wordLength } = word;
const fixationBoundary =
FIXATION_BOUNDARY_LIST[fixationPoint - 1] ?? FIXATION_BOUNDARY_LIST[0];
const fixationLengthFromLast = fixationBoundary.findIndex(
boundary => wordLength <= boundary,
);
let fixationLength = wordLength - fixationLengthFromLast;
if (fixationLengthFromLast === -1) {
fixationLength = wordLength - fixationBoundary.length;
}
return Math.max(fixationLength, 0);
};
================================================
FILE: packages/text-vide/src/getHighlightedText.ts
================================================
export default (text: string, sep: string | string[]) => {
if (typeof sep === 'string') {
return `${sep}${text}${sep}`;
}
return `${sep[0]}${text}${sep[1]}`;
};
================================================
FILE: packages/text-vide/src/getOptions.ts
================================================
import { Options } from './types';
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,
});
================================================
FILE: packages/text-vide/src/index.ts
================================================
import { Options } from './types';
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;
export const textVide = (text: string, maybeOptions: Partial<Options> = {}) => {
if (!text?.length) {
return '';
}
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);
const plainText = text.slice(lastMatchedIndex, startIndex);
result += plainText;
if (startIndex !== endIndex) {
result += getHighlightedText(text.slice(startIndex, endIndex), sep);
}
lastMatchedIndex = endIndex;
}
const remainText = text.slice(lastMatchedIndex);
return result + remainText;
};
================================================
FILE: packages/text-vide/src/types.ts
================================================
export type Options = {
sep: string | string[]; // default: ['<b>', '</b>']
fixationPoint: number; // default: 1
ignoreHtmlTag: boolean; // default: true
ignoreHtmlEntity: boolean; // default: true
};
================================================
FILE: packages/text-vide/src/useCheckIsHtmlEntity.ts
================================================
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;
};
};
================================================
FILE: packages/text-vide/src/useCheckIsHtmlTag.ts
================================================
import { extractMatchRangeList } from './utils';
const HTML_TAG_REGEX = /<!--[^]*?-->|<[^>]+>/g;
export const useCheckIsHtmlTag = (text: string) => {
const htmlTagMatchList = text.matchAll(HTML_TAG_REGEX);
const htmlTagRangeList = extractMatchRangeList(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;
};
};
================================================
FILE: packages/text-vide/src/utils.ts
================================================
export const extractMatchRangeList = (
matchList: IterableIterator<RegExpMatchArray>,
) =>
Array.from(matchList).map(match => {
const startIndex = match.index!;
const [matchedWord] = match;
const { length: matchedWordLength } = matchedWord;
return [startIndex, startIndex + matchedWordLength - 1];
});
================================================
FILE: packages/text-vide/tsconfig.json
================================================
{
"extends": "tsconfig/base.json",
"include": [
"src"
]
}
================================================
FILE: packages/text-vide/vite.config.ts
================================================
import { defineConfig } from 'vite';
import { resolve } from 'path';
import dts from 'vite-plugin-dts';
export default defineConfig({
build: {
lib: {
entry: resolve(__dirname, './src/index.ts'),
formats: ['es', 'cjs', 'iife'],
name: 'textVide',
fileName: 'index',
},
},
plugins: [dts()],
});
================================================
FILE: packages/text-vide/vitest.config.ts
================================================
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
coverage: {
reporter: ['text', 'lcov', 'html'],
},
},
});
================================================
FILE: packages/tsconfig/base.json
================================================
{
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"target": "ESNext",
"module": "esnext",
"moduleResolution": "node",
"allowJs": false,
"strict": true,
"noUnusedLocals": true,
"experimentalDecorators": true,
"isolatedModules": true,
"esModuleInterop": true,
"removeComments": false,
"noLib": false
},
"exclude": ["node_modules"]
}
================================================
FILE: packages/tsconfig/package.json
================================================
{
"name": "tsconfig",
"version": "0.0.0",
"private": true,
"files": [
"base.json",
"vite.json"
]
}
================================================
FILE: packages/tsconfig/vite.json
================================================
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "./base.json",
"compilerOptions": {
"useDefineForClassFields": true,
"lib": ["ESNext", "DOM"],
"sourceMap": true,
"resolveJsonModule": true,
"noEmit": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"skipLibCheck": true
}
}
================================================
FILE: packages/utils/__tests__/defaults.test.ts
================================================
import { describe, expect, it } from 'vitest';
import defaults from '../defaults';
describe('test defaultValue util function', () => {
it('tests including default values', () => {
const defaultValue = {
a: 1,
b: '2',
c: false,
};
const value = {
a: 3,
c: true,
};
const expected = {
a: 3,
b: '2',
c: true,
};
expect(defaults(value, defaultValue)).toEqual(expected);
});
it('pass empty object', () => {
expect(defaults({}, {})).toEqual({});
});
it('pass empty default object', () => {
expect(defaults({ a: 1 }, {})).toEqual({ a: 1 });
});
it('pass empty value object', () => {
expect(defaults({}, { a: 1 })).toEqual({ a: 1 });
});
});
================================================
FILE: packages/utils/__tests__/isEmpty.test.ts
================================================
import { describe, expect, it } from 'vitest';
import isEmpty from '../isEmpty';
describe('test isEmptyString util', () => {
it('pass not empty string', () => {
expect(isEmpty('NOT_EMPTY')).toBeFalsy();
});
it('pass empty string', () => {
expect(isEmpty('')).toBeTruthy();
});
it('pass null value', () => {
expect(isEmpty(null)).toBeTruthy();
});
it('pass undefined value', () => {
expect(isEmpty(undefined)).toBeTruthy();
});
});
================================================
FILE: packages/utils/__tests__/omitBy.test.ts
================================================
import { describe, expect, it } from 'vitest';
import omitBy from '../omitBy';
describe('test omitBy util function', () => {
it('omit number values', () => {
const symbolValue = Symbol();
const obj = {
a: 1,
b: '2',
c: false,
d: undefined,
e: null,
f: symbolValue,
};
const omitted = omitBy(obj, value => typeof value === 'number');
expect(omitted).toEqual({
b: '2',
c: false,
d: undefined,
e: null,
f: symbolValue,
});
});
it('omit empty string', () => {
const obj = {
a: '1',
b: '2',
c: '',
d: undefined,
e: null,
};
const omitted = omitBy(obj, value => value === '');
expect(omitted).toEqual({
a: '1',
b: '2',
d: undefined,
e: null,
});
});
});
================================================
FILE: packages/utils/defaults.ts
================================================
import isEmpty from './isEmpty';
import omitBy from './omitBy';
export default <T>(origin: Partial<T>, defaultValue: T): T => ({
...defaultValue,
...omitBy(origin, isEmpty),
});
================================================
FILE: packages/utils/isEmpty.ts
================================================
export default (value: unknown) =>
value === undefined || value === null || value === '';
================================================
FILE: packages/utils/omitBy.ts
================================================
export default <T extends object>(
obj: T,
omitFilter: (value: T[keyof typeof obj]) => boolean,
) => {
const keyList = Object.keys(obj) as (keyof typeof obj)[];
return keyList.reduce((obj, key) => {
if (omitFilter(obj[key])) {
delete obj[key];
}
return obj;
}, obj);
};
================================================
FILE: packages/utils/package.json
================================================
{
"name": "utils",
"version": "0.0.0",
"private": true,
"scripts": {
"test": "vitest run"
},
"files": [
"./"
]
}
================================================
FILE: pnpm-workspace.yaml
================================================
packages:
- 'apps/*'
- 'packages/*'
================================================
FILE: turbo.json
================================================
{
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
},
"dev": {
"cache": false
},
"sandbox#dev": {
"dependsOn": ["text-vide#build"],
"outputs": []
},
"preview": {
"dependsOn": ["build"]
},
"test": {
"dependsOn": ["text-vide#build"],
"cache": false
},
"benchmark": {
"outputs": []
},
"release": {
"cache": false
}
}
}
gitextract_vz2xfrmz/ ├── .eslintignore ├── .eslintrc.js ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ ├── feature_request.md │ │ └── question.md │ └── workflows/ │ ├── ci.yaml │ ├── publish-sandbox.yaml │ └── publish.yaml ├── .gitignore ├── .husky/ │ └── pre-commit ├── .nvmrc ├── .prettierrc ├── ABOUT_READABILITY.md ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── HOW.md ├── LICENSE ├── README.md ├── apps/ │ ├── benchmark/ │ │ ├── index.js │ │ └── package.json │ └── sandbox/ │ ├── index.html │ ├── package.json │ ├── src/ │ │ ├── App.tsx │ │ ├── index.css │ │ ├── main.tsx │ │ └── vite-env.d.ts │ ├── tsconfig.json │ ├── tsconfig.node.json │ └── vite.config.ts ├── package.json ├── packages/ │ ├── text-vide/ │ │ ├── .versionrc.json │ │ ├── package.json │ │ ├── src/ │ │ │ ├── __tests__/ │ │ │ │ ├── getOptions.test.ts │ │ │ │ └── index.test.ts │ │ │ ├── getFixationLength.ts │ │ │ ├── getHighlightedText.ts │ │ │ ├── getOptions.ts │ │ │ ├── index.ts │ │ │ ├── types.ts │ │ │ ├── useCheckIsHtmlEntity.ts │ │ │ ├── useCheckIsHtmlTag.ts │ │ │ └── utils.ts │ │ ├── tsconfig.json │ │ ├── vite.config.ts │ │ └── vitest.config.ts │ ├── tsconfig/ │ │ ├── base.json │ │ ├── package.json │ │ └── vite.json │ └── utils/ │ ├── __tests__/ │ │ ├── defaults.test.ts │ │ ├── isEmpty.test.ts │ │ └── omitBy.test.ts │ ├── defaults.ts │ ├── isEmpty.ts │ ├── omitBy.ts │ └── package.json ├── pnpm-workspace.yaml └── turbo.json
SYMBOL INDEX (15 symbols across 7 files)
FILE: apps/sandbox/src/App.tsx
constant DEBOUNCE_TIMEOUT (line 12) | const DEBOUNCE_TIMEOUT = 400;
constant COPIED_EFFECT_DEBOUNCE_TIMEOUT (line 13) | const COPIED_EFFECT_DEBOUNCE_TIMEOUT = 1200;
constant INITIAL_INPUT (line 15) | const INITIAL_INPUT =
type Edits (line 18) | type Edits = {
type State (line 93) | type State = Edits & {
type Action (line 98) | type Action = {
FILE: packages/text-vide/src/getFixationLength.ts
constant FIXATION_BOUNDARY_LIST (line 1) | const FIXATION_BOUNDARY_LIST = [
FILE: packages/text-vide/src/getOptions.ts
constant DEFAULT_SEP (line 4) | const DEFAULT_SEP = ['<b>', '</b>'];
constant DEFAULT_FIXATION_POINT (line 5) | const DEFAULT_FIXATION_POINT = 1;
constant DEFAULT_IGNORE_HTML_TAG (line 6) | const DEFAULT_IGNORE_HTML_TAG = true;
constant DEFAULT_IGNORE_HTML_ENTITY (line 7) | const DEFAULT_IGNORE_HTML_ENTITY = true;
FILE: packages/text-vide/src/index.ts
constant CONVERTIBLE_REGEX (line 8) | const CONVERTIBLE_REGEX = /(\p{L}|\p{Nd})*\p{L}(\p{L}|\p{Nd})*/gu;
FILE: packages/text-vide/src/types.ts
type Options (line 1) | type Options = {
FILE: packages/text-vide/src/useCheckIsHtmlEntity.ts
constant HTML_ENTITY_REGEX (line 3) | const HTML_ENTITY_REGEX = /&[\w#]+;/g;
FILE: packages/text-vide/src/useCheckIsHtmlTag.ts
constant HTML_TAG_REGEX (line 3) | const HTML_TAG_REGEX = /<!--[^]*?-->|<[^>]+>/g;
Condensed preview — 58 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (117K chars).
[
{
"path": ".eslintignore",
"chars": 14,
"preview": "apps/benchmark"
},
{
"path": ".eslintrc.js",
"chars": 614,
"preview": "module.exports = {\n parser: '@typescript-eslint/parser',\n parserOptions: {\n sourceType: 'module',\n },\n extends: ["
},
{
"path": ".github/ISSUE_TEMPLATE/bug_report.md",
"chars": 842,
"preview": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: bug\nassignees: Gumball12\n\n---\n\n**Descri"
},
{
"path": ".github/ISSUE_TEMPLATE/feature_request.md",
"chars": 611,
"preview": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: enhancement\nassignees: Gumball12\n\n--"
},
{
"path": ".github/ISSUE_TEMPLATE/question.md",
"chars": 140,
"preview": "---\nname: Question\nabout: Ask and answer\ntitle: ''\nlabels: question\nassignees: Gumball12\n\n---\n\n**Please write questions "
},
{
"path": ".github/workflows/ci.yaml",
"chars": 726,
"preview": "name: ci\n\non:\n push:\n pull_request:\n branches:\n - main\n\njobs:\n ci:\n runs-on: ubuntu-latest\n steps:\n "
},
{
"path": ".github/workflows/publish-sandbox.yaml",
"chars": 652,
"preview": "name: publish-sandbox\n\non:\n push:\n branches:\n - main\n\njobs:\n publish-sandbox:\n runs-on: ubuntu-latest\n s"
},
{
"path": ".github/workflows/publish.yaml",
"chars": 724,
"preview": "name: publish\n\non:\n release:\n types: [created]\n\npermissions:\n id-token: write\n contents: read\n\njobs:\n publish:\n "
},
{
"path": ".gitignore",
"chars": 1636,
"preview": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\nlerna-debug.log*\n.DS_STORE\n\n# Diagnostic reports (https"
},
{
"path": ".husky/pre-commit",
"chars": 69,
"preview": "#!/usr/bin/env sh\n. \"$(dirname -- \"$0\")/_/husky.sh\"\n\nnpx lint-staged\n"
},
{
"path": ".nvmrc",
"chars": 8,
"preview": "v22.12.0"
},
{
"path": ".prettierrc",
"chars": 120,
"preview": "{\n \"trailingComma\": \"all\",\n \"singleQuote\": true,\n \"printWidth\": 80,\n \"arrowParens\": \"avoid\",\n \"endOfLine\": \"auto\"\n}"
},
{
"path": "ABOUT_READABILITY.md",
"chars": 519,
"preview": "# About Readability\n\nBionic-Reading is a technique still under study (see `Statements` at the top of [BR Document](https"
},
{
"path": "CHANGELOG.md",
"chars": 8823,
"preview": "# Changelog\n\nAll notable changes to this project will be documented in this file. See [standard-version](https://github."
},
{
"path": "CODE_OF_CONDUCT.md",
"chars": 5212,
"preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participa"
},
{
"path": "CONTRIBUTING.md",
"chars": 456,
"preview": "# Contributing\n\n## Setup\n\n```bash\ngit clone https://github.com/<username>/text-vide.git\ncd text-vide\n\npnpm install\npnpm "
},
{
"path": "HOW.md",
"chars": 4834,
"preview": "# How was this made?\n\nThis module was developed by referring to the official [BR API](https://rapidapi.com/bionic-readin"
},
{
"path": "LICENSE",
"chars": 1060,
"preview": "MIT License\n\nCopyright (c) 2022 shj\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof thi"
},
{
"path": "README.md",
"chars": 7322,
"preview": "# TextVide (vide; Latin for \"see\")\n\n[](https://w"
},
{
"path": "apps/benchmark/index.js",
"chars": 3006,
"preview": "const { textVide } = require('text-vide');\nconst Benchmark = require('benchmark');\nconst suite = new Benchmark.Suite();\n"
},
{
"path": "apps/benchmark/package.json",
"chars": 187,
"preview": "{\n \"name\": \"benchmark\",\n \"version\": \"0.0.0\",\n \"scripts\": {\n \"benchmark\": \"node index.js\"\n },\n \"devDependencies\":"
},
{
"path": "apps/sandbox/index.html",
"chars": 665,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-w"
},
{
"path": "apps/sandbox/package.json",
"chars": 614,
"preview": "{\n \"name\": \"sandbox\",\n \"version\": \"0.0.0\",\n \"scripts\": {\n \"dev\": \"vite\",\n \"build\": \"tsc && vite build\",\n \"pr"
},
{
"path": "apps/sandbox/src/App.tsx",
"chars": 10387,
"preview": "import {\n Box,\n Button,\n TextField,\n ToggleButton,\n ToggleButtonGroup,\n} from '@mui/material';\nimport { Reducer, us"
},
{
"path": "apps/sandbox/src/index.css",
"chars": 138,
"preview": "* {\n font-family: 'SUIT', sans-serif !important;\n box-sizing: border-box;\n}\n\npre {\n font-weight: 300;\n}\n\npre b {\n fo"
},
{
"path": "apps/sandbox/src/main.tsx",
"chars": 245,
"preview": "import ReactDOM from 'react-dom/client';\nimport App from './App';\nimport 'uno.css';\nimport '@unocss/reset/eric-meyer.css"
},
{
"path": "apps/sandbox/src/vite-env.d.ts",
"chars": 38,
"preview": "/// <reference types=\"vite/client\" />\n"
},
{
"path": "apps/sandbox/tsconfig.json",
"chars": 378,
"preview": "{\n \"extends\": \"tsconfig/vite.json\",\n \"compilerOptions\": {\n \"lib\": [\n \"DOM\",\n \"DOM.Iterable\",\n \"ESNex"
},
{
"path": "apps/sandbox/tsconfig.node.json",
"chars": 150,
"preview": "{\n \"compilerOptions\": {\n \"composite\": true,\n \"module\": \"esnext\",\n \"moduleResolution\": \"node\",\n },\n \"include\""
},
{
"path": "apps/sandbox/vite.config.ts",
"chars": 608,
"preview": "import { defineConfig } from 'vite';\nimport react from '@vitejs/plugin-react';\nimport Unocss from 'unocss/vite';\nimport "
},
{
"path": "package.json",
"chars": 1222,
"preview": "{\n \"packageManager\": \"pnpm@10.19.0+sha512.c9fc7236e92adf5c8af42fd5bf1612df99c2ceb62f27047032f4720b33f8eacdde311865e91c4"
},
{
"path": "packages/text-vide/.versionrc.json",
"chars": 37,
"preview": "{\n \"infile\": \"../../CHANGELOG.md\"\n}\n"
},
{
"path": "packages/text-vide/package.json",
"chars": 1112,
"preview": "{\n \"name\": \"text-vide\",\n \"version\": \"1.8.5\",\n \"description\": \"An Open-Source JavaScript Implementation of Bionic Read"
},
{
"path": "packages/text-vide/src/__tests__/getOptions.test.ts",
"chars": 1479,
"preview": "import { describe, expect, it } from 'vitest';\nimport getOptions from '../getOptions';\nimport { Options } from '../types"
},
{
"path": "packages/text-vide/src/__tests__/index.test.ts",
"chars": 44731,
"preview": "import { textVide } from '..';\nimport { describe, expect, it } from 'vitest';\n\ndescribe('test textVide module', () => {\n"
},
{
"path": "packages/text-vide/src/getFixationLength.ts",
"chars": 1085,
"preview": "const FIXATION_BOUNDARY_LIST = [\n [0, 4, 12, 17, 24, 29, 35, 42, 48],\n [1, 2, 7, 10, 13, 14, 19, 22, 25, 28, 31, 34, 3"
},
{
"path": "packages/text-vide/src/getHighlightedText.ts",
"chars": 172,
"preview": "export default (text: string, sep: string | string[]) => {\n if (typeof sep === 'string') {\n return `${sep}${text}${s"
},
{
"path": "packages/text-vide/src/getOptions.ts",
"chars": 478,
"preview": "import { Options } from './types';\nimport defaults from 'utils/defaults';\n\nconst DEFAULT_SEP = ['<b>', '</b>'];\nconst DE"
},
{
"path": "packages/text-vide/src/index.ts",
"chars": 1742,
"preview": "import { Options } from './types';\nimport getOptions from './getOptions';\nimport getFixationLength from './getFixationLe"
},
{
"path": "packages/text-vide/src/types.ts",
"chars": 209,
"preview": "export type Options = {\n sep: string | string[]; // default: ['<b>', '</b>']\n fixationPoint: number; // default: 1\n i"
},
{
"path": "packages/text-vide/src/useCheckIsHtmlEntity.ts",
"chars": 710,
"preview": "import { extractMatchRangeList } from './utils';\n\nconst HTML_ENTITY_REGEX = /&[\\w#]+;/g;\n\nexport const useCheckIsHtmlEnt"
},
{
"path": "packages/text-vide/src/useCheckIsHtmlTag.ts",
"chars": 686,
"preview": "import { extractMatchRangeList } from './utils';\n\nconst HTML_TAG_REGEX = /<!--[^]*?-->|<[^>]+>/g;\n\nexport const useCheck"
},
{
"path": "packages/text-vide/src/utils.ts",
"chars": 325,
"preview": "export const extractMatchRangeList = (\n matchList: IterableIterator<RegExpMatchArray>,\n) =>\n Array.from(matchList).map"
},
{
"path": "packages/text-vide/tsconfig.json",
"chars": 67,
"preview": "{\n \"extends\": \"tsconfig/base.json\",\n \"include\": [\n \"src\"\n ]\n}"
},
{
"path": "packages/text-vide/vite.config.ts",
"chars": 331,
"preview": "import { defineConfig } from 'vite';\nimport { resolve } from 'path';\nimport dts from 'vite-plugin-dts';\n\nexport default "
},
{
"path": "packages/text-vide/vitest.config.ts",
"chars": 161,
"preview": "import { defineConfig } from 'vitest/config';\n\nexport default defineConfig({\n test: {\n coverage: {\n reporter: ["
},
{
"path": "packages/tsconfig/base.json",
"chars": 408,
"preview": "{\n \"$schema\": \"https://json.schemastore.org/tsconfig\",\n \"compilerOptions\": {\n \"target\": \"ESNext\",\n \"module\": \"es"
},
{
"path": "packages/tsconfig/package.json",
"chars": 117,
"preview": "{\n \"name\": \"tsconfig\",\n \"version\": \"0.0.0\",\n \"private\": true,\n \"files\": [\n \"base.json\",\n \"vite.json\"\n ]\n}\n"
},
{
"path": "packages/tsconfig/vite.json",
"chars": 342,
"preview": "{\n \"$schema\": \"https://json.schemastore.org/tsconfig\",\n \"extends\": \"./base.json\",\n \"compilerOptions\": {\n \"useDefin"
},
{
"path": "packages/utils/__tests__/defaults.test.ts",
"chars": 745,
"preview": "import { describe, expect, it } from 'vitest';\nimport defaults from '../defaults';\n\ndescribe('test defaultValue util fun"
},
{
"path": "packages/utils/__tests__/isEmpty.test.ts",
"chars": 467,
"preview": "import { describe, expect, it } from 'vitest';\nimport isEmpty from '../isEmpty';\n\ndescribe('test isEmptyString util', ()"
},
{
"path": "packages/utils/__tests__/omitBy.test.ts",
"chars": 827,
"preview": "import { describe, expect, it } from 'vitest';\nimport omitBy from '../omitBy';\n\ndescribe('test omitBy util function', ()"
},
{
"path": "packages/utils/defaults.ts",
"chars": 183,
"preview": "import isEmpty from './isEmpty';\nimport omitBy from './omitBy';\n\nexport default <T>(origin: Partial<T>, defaultValue: T)"
},
{
"path": "packages/utils/isEmpty.ts",
"chars": 92,
"preview": "export default (value: unknown) =>\n value === undefined || value === null || value === '';\n"
},
{
"path": "packages/utils/omitBy.ts",
"chars": 299,
"preview": "export default <T extends object>(\n obj: T,\n omitFilter: (value: T[keyof typeof obj]) => boolean,\n) => {\n const keyLi"
},
{
"path": "packages/utils/package.json",
"chars": 135,
"preview": "{\n \"name\": \"utils\",\n \"version\": \"0.0.0\",\n \"private\": true,\n \"scripts\": {\n \"test\": \"vitest run\"\n },\n \"files\": [\n"
},
{
"path": "pnpm-workspace.yaml",
"chars": 40,
"preview": "packages:\n - 'apps/*'\n - 'packages/*'\n"
},
{
"path": "turbo.json",
"chars": 460,
"preview": "{\n \"pipeline\": {\n \"build\": {\n \"dependsOn\": [\"^build\"],\n \"outputs\": [\"dist/**\"]\n },\n \"dev\": {\n \""
}
]
About this extraction
This page contains the full source code of the Gumball12/text-vide GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 58 files (106.9 KB), approximately 40.9k tokens, and a symbol index with 15 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.