clean: Improve build configurations

This commit is contained in:
Rodrigo Fernandes 2019-12-29 22:31:32 +00:00
parent 236347eaec
commit dc9c866041
No known key found for this signature in database
GPG key ID: 67157D2E3D4258B4
59 changed files with 2980 additions and 2325 deletions

View file

@ -1,10 +0,0 @@
# Skip build results
_target/**
coverage/**
bundles/**
docs/**
lib/**
lib-esm/**
node_modules/**
src/diff2html-templates.*
typings/**

58
.eslintrc.js Normal file
View file

@ -0,0 +1,58 @@
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 2018,
sourceType: 'module',
},
env: {
browser: true,
es6: true,
node: true,
},
globals: {
Atomics: 'readonly',
SharedArrayBuffer: 'readonly',
document: 'readonly',
navigator: 'readonly',
window: 'readonly',
},
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended',
'plugin:json/recommended',
'plugin:promise/recommended',
'plugin:import/errors',
'plugin:import/warnings',
'plugin:import/typescript',
'plugin:node/recommended',
'plugin:sonarjs/recommended',
'plugin:jest/recommended',
'plugin:jest/style',
'prettier',
'prettier/@typescript-eslint',
'prettier/babel',
],
plugins: ['@typescript-eslint', 'json', 'promise', 'import', 'node', 'sonarjs', 'jest', 'optimize-regex'],
rules: {
// Enable
'optimize-regex/optimize-regex': 'error',
// Hack: For some reason we need pass again the extensions
'node/no-missing-import': [
'error',
{
tryExtensions: ['.js', '.jsx', '.ts', '.tsx', '.json'],
},
],
// Disable
// https://github.com/benmosher/eslint-plugin-import/issues/1446
'import/named': 'off',
// We don't need this since we are using transpilation
'node/no-unsupported-features/es-syntax': 'off',
'no-process-exit': 'off',
// Too verbose
'sonarjs/no-duplicate-string': 'off',
// Too verbose
'sonarjs/cognitive-complexity': 'off',
},
};

View file

@ -1,30 +0,0 @@
{
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 2018,
"sourceType": "module",
"ecmaFeatures": {
"experimentalObjectRestSpread": true,
"jsx": true
}
},
"env": {
"es6": true,
"node": true,
"browser": true,
"commonjs": true
},
"plugins": ["standard", "node", "import", "promise", "@typescript-eslint", "jest"],
"globals": {
"document": false,
"navigator": false,
"window": false
},
"extends": [
"plugin:@typescript-eslint/recommended",
"plugin:jest/recommended",
"plugin:promise/recommended",
"standard",
"plugin:prettier/recommended"
]
}

View file

@ -1,44 +1,44 @@
### Step -1: Before filling an issue check out troubleshooting section ### Step -1: Before filling an issue check out troubleshooting section
* Go to [README.md#Troubleshooting](https://github.com/rtfpessoa/diff2html#troubleshooting) - Go to [README.md#Troubleshooting](https://github.com/rtfpessoa/diff2html#troubleshooting)
### Step 0: Describe your environment ### Step 0: Describe your environment
* OS: _____ - OS: **\_**
* diff2html version: _____ - diff2html version: **\_**
* Using diff2html directly or using diff2html-ui helper: _____ - Using diff2html directly or using diff2html-ui helper: **\_**
* Extra flags: _____ - Extra flags: **\_**
### Step 1: Describe the problem: ### Step 1: Describe the problem:
#### Steps to reproduce: #### Steps to reproduce:
1. _____ 1. ***
2. _____ 2. ***
3. _____ 3. ***
#### diff example: #### diff example:
```diff ```diff
diff --git describe.c diff --git describe.c
index fabadb8,cc95eb0..4866510 index fabadb8,cc95eb0..4866510
--- a/describe.c --- a/describe.c
+++ b/describe.c +++ b/describe.c
@@@ -98,20 -98,12 +98,20 @@@ @@@ -98,20 -98,12 +98,20 @@@
return (a_date > b_date) ? -1 : (a_date == b_date) ? 0 : 1; return (a_date > b_date) ? -1 : (a_date == b_date) ? 0 : 1;
} }
``` ```
#### Observed Results: #### Observed Results:
* What happened? This could be a description, log output, etc. - What happened? This could be a description, log output, etc.
#### Expected Results: #### Expected Results:
* What did you expect to happen? - What did you expect to happen?
#### Relevant Code: #### Relevant Code:
``` ```
// TODO(you): code here to reproduce the problem // TODO(you): code here to reproduce the problem
``` ```

View file

@ -1,32 +1,23 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
--- ---
**Describe the bug** name: Bug report about: Create a report to help us improve title: "" labels: "" assignees: "" ---**Describe the bug** A
A clear and concise description of what the bug is. clear and concise description of what the bug is.
**To Reproduce** Steps to reproduce the behavior:
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...' 1. Go to '...'
2. Click on '....' 2. Click on '....'
3. Scroll down to '....' 3. Scroll down to '....'
4. See error 4. See error
**Expected behavior** **Expected behavior** A clear and concise description of what you expected to happen.
A clear and concise description of what you expected to happen.
**Screenshots** **Screenshots** If applicable, add screenshots to help explain your problem.
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):** **Desktop (please complete the following information):**
- OS: [e.g. Windows, Linux, Mac]
- Browser [e.g. Firefox, Chrome, Safari]
- Version [e.g. 22]
**Additional context** - OS: [e.g. Windows, Linux, Mac]
Add any other context about the problem here. - Browser [e.g. Firefox, Chrome, Safari]
- Version [e.g. 22]
**Additional context** Add any other context about the problem here.

View file

@ -1,20 +1,12 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
--- ---
**Is your feature request related to a problem? Please describe.** name: Feature request about: Suggest an idea for this project title: "" labels: "" assignees: "" ---**Is your feature
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 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** **Describe the solution you'd like** A clear and concise description of what you want to happen.
A clear and concise description of what you want to happen.
**Describe alternatives you've considered** **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features
A clear and concise description of any alternative solutions or features you've considered. you've considered.
**Additional context** **Additional context** Add any other context or screenshots about the feature request here.
Add any other context or screenshots about the feature request here.

View file

@ -31,17 +31,13 @@ jobs:
${{ runner.os }}-yarn- ${{ runner.os }}-yarn-
- name: Install dependencies - name: Install dependencies
run: yarn install run: yarn install
- name: Lint - name: Validate
run: yarn run lint run: yarn run validate
- name: Build
run: yarn run build
- name: Test
run: yarn run coverage
- name: Push coverage to Codacy - name: Push coverage to Codacy
if: matrix.node-version == '13.x' if: matrix.node-version == '13.x'
env: env:
CODACY_PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }} CODACY_PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }}
run: yarn run codacy run: yarn run coverage:push
- name: Save coverage report - name: Save coverage report
if: matrix.node-version == '13.x' if: matrix.node-version == '13.x'
uses: actions/upload-artifact@v1 uses: actions/upload-artifact@v1

View file

@ -1,7 +1,8 @@
name: Release name: Release
on: on:
push: pull_request:
types: [closed]
branches: branches:
- master - master
@ -12,6 +13,7 @@ jobs:
image: codacy/git-version image: codacy/git-version
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v1
if: github.event.pull_request.merged
- name: Configure Git - name: Configure Git
run: | run: |
git checkout -f "${GITHUB_REF#refs/heads/}" git checkout -f "${GITHUB_REF#refs/heads/}"
@ -46,7 +48,7 @@ jobs:
path: tag.txt path: tag.txt
publish: publish:
needs: [version, build] needs: [version]
runs-on: ubuntu-18.04 runs-on: ubuntu-18.04
env: env:
CI: true CI: true
@ -84,7 +86,7 @@ jobs:
git config user.email "$GITHUB_ACTOR@users.noreply.github.com" git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
git config user.name "$GITHUB_ACTOR" git config user.name "$GITHUB_ACTOR"
- name: Install dependencies - name: Install dependencies
run: yarn install run: yarn
- name: Prepare version - name: Prepare version
env: env:
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}

View file

@ -1,7 +1,8 @@
name: Website name: Website
on: on:
push: pull_request:
types: [closed]
branches: branches:
- master - master
@ -15,6 +16,7 @@ jobs:
node-version: [13.x] node-version: [13.x]
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v1
if: github.event.pull_request.merged
- name: Setup Node.js ${{ matrix.node-version }} - name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1 uses: actions/setup-node@v1
with: with:
@ -31,13 +33,11 @@ jobs:
restore-keys: | restore-keys: |
${{ runner.os }}-yarn- ${{ runner.os }}-yarn-
- name: Install dependencies - name: Install dependencies
run: yarn install run: yarn
- name: Lint - name: Build website
run: yarn run lint run: |
- name: Build yarn run build:templates
run: yarn run build yarn run build:website
- name: Test
run: yarn run coverage
- name: Save website artifact - name: Save website artifact
if: matrix.node-version == '13.x' if: matrix.node-version == '13.x'
uses: actions/upload-artifact@v1 uses: actions/upload-artifact@v1
@ -59,5 +59,5 @@ jobs:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
run: | run: |
cd docs cd website
aws s3 sync . s3://diff2html.xyz --region eu-west-1 aws s3 sync . s3://diff2html.xyz --region eu-west-1

4
.gitignore vendored
View file

@ -20,7 +20,7 @@ node_modules/
npm-debug.log npm-debug.log
yarn-error.log yarn-error.log
# Istanbul # Coverage
coverage/ coverage/
# Bower # Bower
@ -29,7 +29,7 @@ bower_components/
# Terraform # Terraform
/terraform/.terraform /terraform/.terraform
/_target /_target/
/src/diff2html-templates.* /src/diff2html-templates.*
/docs/ /docs/
/bundles/ /bundles/

17
.prettierrc.json Normal file
View file

@ -0,0 +1,17 @@
{
"arrowParens": "avoid",
"bracketSpacing": true,
"htmlWhitespaceSensitivity": "css",
"insertPragma": false,
"jsxBracketSameLine": false,
"jsxSingleQuote": false,
"printWidth": 120,
"proseWrap": "always",
"quoteProps": "as-needed",
"requirePragma": false,
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "all",
"useTabs": false
}

View file

@ -1,3 +1,3 @@
{ {
"typescript.tsdk": "node_modules/typescript/lib" "typescript.tsdk": "node_modules/typescript/lib"
} }

View file

@ -2,75 +2,60 @@
## Our Pledge ## Our Pledge
In the interest of fostering an open and welcoming environment, we as In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making
contributors and maintainers pledge to making participation in our project and participation in our project and our community a harassment-free experience for everyone, regardless of age, body size,
our community a harassment-free experience for everyone, regardless of age, body disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education,
size, disability, ethnicity, sex characteristics, gender identity and expression, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards ## Our Standards
Examples of behavior that contributes to creating a positive environment Examples of behavior that contributes to creating a positive environment include:
include:
* Using welcoming and inclusive language - Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences - Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism - Gracefully accepting constructive criticism
* Focusing on what is best for the community - Focusing on what is best for the community
* Showing empathy towards other community members - Showing empathy towards other community members
Examples of unacceptable behavior by participants include: Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or - The use of sexualized language or imagery and unwelcome sexual attention or advances
advances - Trolling, insulting/derogatory comments, and personal or political attacks
* Trolling, insulting/derogatory comments, and personal or political attacks - Public or private harassment
* Public or private harassment - Publishing others' private information, such as a physical or electronic address, without explicit permission
* Publishing others' private information, such as a physical or electronic - Other conduct which could reasonably be considered inappropriate in a professional setting
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities ## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take
behavior and are expected to take appropriate and fair corrective action in appropriate and fair corrective action in response to any instances of unacceptable behavior.
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits,
reject comments, commits, code, wiki edits, issues, and other contributions issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any
that are not aligned to this Code of Conduct, or to ban temporarily or contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope ## Scope
This Code of Conduct applies both within project spaces and in public spaces This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the
when an individual is representing the project or its community. Examples of project or its community. Examples of representing a project or community include using an official project e-mail
representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline
address, posting via an official social media account, or acting as an appointed event. Representation of a project may be further defined and clarified by project maintainers.
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement ## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at
reported by contacting the project team at rtfrodrigo [at] gmail [dot] com. All rtfrodrigo [at] gmail [dot] com. All complaints will be reviewed and investigated and will result in a response that is
complaints will be reviewed and investigated and will result in a response that deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with
is deemed necessary and appropriate to the circumstances. The project team is regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent
faith may face temporary or permanent repercussions as determined by other repercussions as determined by other members of the project's leadership.
members of the project's leadership.
## Attribution ## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org [homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq
https://www.contributor-covenant.org/faq

View file

@ -2,31 +2,33 @@
### Main rules ### Main rules
* Before you open a ticket or send a pull request, [search](https://github.com/rtfpessoa/diff2html/issues) for previous discussions about the same feature or issue. Add to the earlier ticket if you find one. - Before you open a ticket or send a pull request, [search](https://github.com/rtfpessoa/diff2html/issues) for previous
discussions about the same feature or issue. Add to the earlier ticket if you find one.
* If you're proposing a new feature, make sure you create an issue to let other contributors know what you are working on. - If you're proposing a new feature, make sure you create an issue to let other contributors know what you are working
on.
* Before sending a pull request make sure your code is tested. - Before sending a pull request make sure your code is tested.
* Before sending a pull request for a feature, be sure to run tests with `yarn test`. - Before sending a pull request for a feature, be sure to run tests with `yarn test`.
* Use the same coding style as the rest of the codebase, most of the check can be performed with `yarn run lint`. - Use the same coding style as the rest of the codebase, most of the check can be performed with `yarn run lint`.
* Use `git rebase` (not `git merge`) to sync your work from time to time with the master branch. - Use `git rebase` (not `git merge`) to sync your work from time to time with the master branch.
* After creating your pull request make sure the build is passing on [CircleCI](https://circleci.com/gh/rtfpessoa/diff2html) - After creating your pull request make sure the build is passing on
and that [Codacy](https://www.codacy.com/app/Codacy/diff2html) is also confident in the code quality. [CircleCI](https://circleci.com/gh/rtfpessoa/diff2html) and that [Codacy](https://www.codacy.com/app/Codacy/diff2html)
is also confident in the code quality.
* In your pull request, do not commit the `dist` or `build` folder if you needed to build the release files. - In your pull request, do not commit the `dist` or `build` folder if you needed to build the release files.
### Commit Style ### Commit Style
Writing good commit logs is important. A commit log should describe what changed and why. Writing good commit logs is important. A commit log should describe what changed and why. Follow these guidelines when
Follow these guidelines when writing one: writing one:
1. The first line should be 50 characters or less and contain a short 1. The first line should be 50 characters or less and contain a short description of the change prefixed with the name
description of the change prefixed with the name of the changed of the changed subsystem (e.g. "net: add localAddress and localPort to Socket").
subsystem (e.g. "net: add localAddress and localPort to Socket").
2. Keep the second line blank. 2. Keep the second line blank.
3. Wrap all other lines at 72 columns. 3. Wrap all other lines at 72 columns.
@ -49,14 +51,11 @@ nicely even when it is indented.
By making a contribution to this project, I certify that: By making a contribution to this project, I certify that:
* (a) The contribution was created in whole or in part by me and I - (a) The contribution was created in whole or in part by me and I have the right to submit it under the open source
have the right to submit it under the open source license indicated license indicated in the file; or
in the file; or - (b) The contribution is based upon previous work that, to the best of my knowledge, is covered under an appropriate
* (b) The contribution is based upon previous work that, to the best open source license and I have the right under that license to submit that work with modifications, whether created in
of my knowledge, is covered under an appropriate open source license whole or in part by me, under the same open source license (unless I am permitted to submit under a different
and I have the right under that license to submit that work with license), as indicated in the file; or
modifications, whether created in whole or in part by me, under the - (c) The contribution was provided directly to me by some other person who certified (a), (b) or (c) and I have not
same open source license (unless I am permitted to submit under a modified it.
different license), as indicated in the file; or
* (c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified it.

View file

@ -1,12 +1,11 @@
# Credits # Credits
This is the list of all the kind people that have contributed to the diff2html project. This is the list of all the kind people that have contributed to the diff2html project. This list is ordered by first
This list is ordered by first contribution. contribution.
Thanks, Thanks, @rtfpessoa
@rtfpessoa
---------- ---
Rodrigo Fernandes, [@rtfpessoa](https://github.com/rtfpessoa) Rodrigo Fernandes, [@rtfpessoa](https://github.com/rtfpessoa)

View file

@ -1,20 +1,14 @@
Copyright 2014-2016 Rodrigo Fernandes https://rtfpessoa.github.io/ Copyright 2014-2016 Rodrigo Fernandes https://rtfpessoa.github.io/
Permission is hereby granted, free of charge, to any person obtaining Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
a copy of this software and associated documentation files (the documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
"Software"), to deal in the Software without restriction, including rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
without limitation the rights to use, copy, modify, merge, publish, persons to whom the Software is furnished to do so, subject to the following conditions:
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 The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
included in all copies or substantial portions of the Software. Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
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.

243
README.md
View file

@ -2,20 +2,19 @@
[![Codacy Quality Badge](https://api.codacy.com/project/badge/Grade/06412dc3f5a14f568778d0db8a1f7dc8)](https://www.codacy.com/app/rtfpessoa/diff2html?utm_source=github.com&utm_medium=referral&utm_content=rtfpessoa/diff2html&utm_campaign=Badge_Grade) [![Codacy Quality Badge](https://api.codacy.com/project/badge/Grade/06412dc3f5a14f568778d0db8a1f7dc8)](https://www.codacy.com/app/rtfpessoa/diff2html?utm_source=github.com&utm_medium=referral&utm_content=rtfpessoa/diff2html&utm_campaign=Badge_Grade)
[![Codacy Coverage Badge](https://api.codacy.com/project/badge/Coverage/06412dc3f5a14f568778d0db8a1f7dc8)](https://www.codacy.com/app/rtfpessoa/diff2html?utm_source=github.com&utm_medium=referral&utm_content=rtfpessoa/diff2html&utm_campaign=Badge_Coverage) [![Codacy Coverage Badge](https://api.codacy.com/project/badge/Coverage/06412dc3f5a14f568778d0db8a1f7dc8)](https://www.codacy.com/app/rtfpessoa/diff2html?utm_source=github.com&utm_medium=referral&utm_content=rtfpessoa/diff2html&utm_campaign=Badge_Coverage)
![GitHub CI](https://github.com/rtfpessoa/diff2html/workflows/CI/badge.svg) [![GitHub CI](https://github.com/rtfpessoa/diff2html/workflows/CI/badge.svg?branch=master)](https://github.com/rtfpessoa/diff2html/actions?query=branch%3Amaster)
[![npm](https://img.shields.io/npm/v/diff2html.svg)](https://www.npmjs.com/package/diff2html) [![npm](https://img.shields.io/npm/v/diff2html.svg)](https://www.npmjs.com/package/diff2html)
[![Dependency Status](https://david-dm.org/rtfpessoa/diff2html.svg)](https://david-dm.org/rtfpessoa/diff2html) [![Dependency Status](https://david-dm.org/rtfpessoa/diff2html.svg)](https://david-dm.org/rtfpessoa/diff2html)
[![devDependency Status](https://david-dm.org/rtfpessoa/diff2html/dev-status.svg)](https://david-dm.org/rtfpessoa/diff2html#info=devDependencies) [![devDependency Status](https://david-dm.org/rtfpessoa/diff2html/dev-status.svg)](https://david-dm.org/rtfpessoa/diff2html#info=devDependencies)
[![cdnjs](https://img.shields.io/cdnjs/v/diff2html)](https://cdnjs.com/libraries/diff2html) [![cdnjs](https://img.shields.io/cdnjs/v/diff2html)](https://cdnjs.com/libraries/diff2html)
[![node](https://img.shields.io/node/v/diff2html.svg)]() [![node](https://img.shields.io/node/v/diff2html.svg)]() [![npm](https://img.shields.io/npm/l/diff2html.svg)]()
[![npm](https://img.shields.io/npm/l/diff2html.svg)]()
[![npm](https://img.shields.io/npm/dm/diff2html.svg)](https://www.npmjs.com/package/diff2html) [![npm](https://img.shields.io/npm/dm/diff2html.svg)](https://www.npmjs.com/package/diff2html)
[![All Contributors](https://img.shields.io/badge/all_contributors-22-orange.svg?style=flat-square)](#contributors-) [![All Contributors](https://img.shields.io/badge/all_contributors-22-orange.svg?style=flat-square)](#contributors-)
[![Gitter](https://badges.gitter.im/rtfpessoa/diff2html.svg)](https://gitter.im/rtfpessoa/diff2html?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![Gitter](https://badges.gitter.im/rtfpessoa/diff2html.svg)](https://gitter.im/rtfpessoa/diff2html?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
diff2html generates pretty HTML diffs from git or unified diff output. diff2html generates pretty HTML diffs from git diff or unified diff output.
[![NPM](https://nodei.co/npm/diff2html.png?downloads=true&downloadRank=true&stars=true)](https://nodei.co/npm/diff2html/) [![NPM](https://nodei.co/npm/diff2html.png?downloads=true&downloadRank=true&stars=true)](https://nodei.co/npm/diff2html/)
@ -29,7 +28,7 @@ diff2html generates pretty HTML diffs from git or unified diff output.
- Inserted and removed lines - Inserted and removed lines
- GitHub like style - GitHub like visual style
- Code syntax highlight - Code syntax highlight
@ -44,17 +43,39 @@ diff2html generates pretty HTML diffs from git or unified diff output.
## Distributions ## Distributions
- [WebJar](http://www.webjars.org/) - [WebJar](http://www.webjars.org/)
- [Node Module](https://www.npmjs.org/package/diff2html) - [Node Library](https://www.npmjs.org/package/diff2html)
- [Node CLI](https://www.npmjs.org/package/diff2html-cli) - [NPM CLI](https://www.npmjs.org/package/diff2html-cli)
- Manually download and import: - Manually download and import:
- Browser - Browser / Bundle
- [bundles/js/diff2html.min.js](./bundles/js/diff2html.min.js) - includes the diff parser and html generator - Parser and HTML Generator
- [bundles/js/diff2html-ui.min.js](./bundles/js/diff2html-ui.min.js) - includes the wrapper of diff2html that adds highlight, synchronized scroll, and other nice features - [bundles/js/diff2html.min.js](./bundles/js/diff2html.min.js) - includes the diff parser and html generator
- Node.js - Wrapper and helper adding syntax highlight, synchronized scroll, and other nice features
- [lib/diff2html.js](./lib/diff2html.js) - targeted for es5, includes the diff parser and html generator - [bundles/js/diff2html-ui.min.js](./bundles/js/diff2html-ui.min.js) - includes the wrapper of diff2html with
- [lib-esm/diff2html.js](./lib-esm/diff2html.js) - targeted for es6 - includes the diff parser and html generator highlight for all `highlight.js` supported languages
- [bundles/js/diff2html-ui-slim.min.js](./bundles/js/diff2html-ui-slim.min.js) - includes the wrapper of diff2html
with "the most common" `highlight.js` supported languages
- [bundles/js/diff2html-ui-base.min.js](./bundles/js/diff2html-ui-base.min.js) - includes the wrapper of diff2html
without including a `highlight.js` implementation. You can use it without syntax highlight or by passing your
own implementation with the languages you prefer
- NPM / Node.js library
- ES5
- [lib/diff2html.js](./lib/diff2html.js) - includes the diff parser and html generator
- [lib/ui/js/diff2html-ui.js](./lib/ui/js/diff2html-ui.js) - includes the wrapper of diff2html with highlight for
all `highlight.js` supported languages
- [lib/ui/js/diff2html-ui-slim.js](./lib/ui/js/diff2html-ui-slim.js) - includes the wrapper of diff2html with "the
most common" `highlight.js` supported languages
- [lib/ui/js/diff2html-ui-base.js](./lib/ui/js/diff2html-ui-base.js)
- ES6
- [lib-esm/diff2html.js](./lib-esm/diff2html.js) - includes the diff parser and html generator
- [lib/ui/js/diff2html-ui.js](./lib/ui/js/diff2html-ui.js) - includes the wrapper of diff2html with highlight for
all `highlight.js` supported languages
- [lib/ui/js/diff2html-ui-slim.js](./lib/ui/js/diff2html-ui-slim.js) - includes the wrapper of diff2html with "the
most common" `highlight.js` supported languages
- [lib/ui/js/diff2html-ui-base.js](./lib/ui/js/diff2html-ui-base.js) - includes the wrapper of diff2html without
including a `highlight.js` implementation. You can use it without syntax highlight or by passing your own
implementation with the languages you prefer
## How to use ## Diff2Html Usage
To load correctly in the Browser you always need to include the stylesheet in the final HTML. To load correctly in the Browser you always need to include the stylesheet in the final HTML.
@ -67,7 +88,41 @@ Import the stylesheet
You can also refer to it from a CDN like [CDNJS](https://cdnjs.com/libraries/diff2html). You can also refer to it from a CDN like [CDNJS](https://cdnjs.com/libraries/diff2html).
### Browser Library ### Diff2Html API
> JSON representation of the diff
function parse(diffInput: string, configuration: Diff2HtmlConfig = {}): DiffFile[]
> Pretty HTML representation of the diff
function html(diffInput: string | DiffFile[], configuration: Diff2HtmlConfig = {}): string
> Check out the [docs/demo.html](./docs/demo.html) for a demo example.
### Diff2Html Configuration
The HTML output accepts a Javascript object with configuration. Possible options:
- `outputFormat`: the format of the output data: `'line-by-line'` or `'side-by-side'`, default is `'line-by-line'`
- `drawFileList`: show a file list before the diff: `true` or `false`, default is `false`
- `diffStyle`: show differences level in each line: `word` or `char`, default is `word`
- `matching`: matching level: `'lines'` for matching lines, `'words'` for matching lines and words or `'none'`, default
is `none`
- `matchWordsThreshold`: similarity threshold for word matching, default is 0.25
- `matchingMaxComparisons`: perform at most this much comparisons for line matching a block of changes, default is
`2500`
- `maxLineSizeInBlockForComparison`: maximum number os characters of the bigger line in a block to apply comparison,
default is `200`
- `maxLineLengthHighlight`: only perform diff changes highlight if lines are smaller than this, default is `10000`
- `renderNothingWhenEmpty`: render nothing if the diff shows no change in its comparison: `true` or `false`, default is
`false`
- `compiledTemplates`: object with previously compiled templates to replace parts of the html
- `rawTemplates`: object with raw not compiled templates to replace parts of the html
> For more information regarding the possible templates look into
> [src/templates](https://github.com/rtfpessoa/diff2html/tree/master/src/templates)
### Diff2Html Browser
Import the stylesheet and the library code Import the stylesheet and the library code
@ -82,32 +137,34 @@ Import the stylesheet and the library code
It will now be available as a global variable named `Diff2Html`. It will now be available as a global variable named `Diff2Html`.
```js ```js
document.addEventListener("DOMContentLoaded", () => { document.addEventListener('DOMContentLoaded', () => {
var diffHtml = global.Diff2Html.html("<Unified Diff String>", { var diffHtml = global.Diff2Html.html('<Unified Diff String>', {
drawFileList: true, drawFileList: true,
matching: "lines", matching: 'lines',
outputFormat: "side-by-side" outputFormat: 'side-by-side',
}); });
document.getElementById("destination-elem-id").innerHTML = diffHtml; document.getElementById('destination-elem-id').innerHTML = diffHtml;
}); });
``` ```
### Node Module ### Diff2Html NPM / Node.js Library
```js ```js
const Diff2html = require("diff2html"); const Diff2html = require('diff2html');
const diffJson = Diff2html.parse("<Unified Diff String>"); const diffJson = Diff2html.parse('<Unified Diff String>');
const diffHtml = Diff2html.html(diffJson, { drawFileList: true }); const diffHtml = Diff2html.html(diffJson, { drawFileList: true });
document.getElementById("destination-elem-id").innerHTML = diffHtml; document.getElementById('destination-elem-id').innerHTML = diffHtml;
``` ```
### Angular ### Diff2Html Examples
#### Diff2Html Angular Example
- Typescript - Typescript
```typescript ```typescript
import * as Diff2Html from "diff2html"; import * as Diff2Html from 'diff2html';
import { Component, OnInit } from "@angular/core"; import { Component, OnInit } from '@angular/core';
export class AppDiffComponent implements OnInit { export class AppDiffComponent implements OnInit {
outputHtml: string; outputHtml: string;
@ -119,8 +176,8 @@ export class AppDiffComponent implements OnInit {
init() { init() {
let strInput = let strInput =
"--- a/server/vendor/golang.org/x/sys/unix/zsyscall_linux_mipsle.go\n+++ b/server/vendor/golang.org/x/sys/unix/zsyscall_linux_mipsle.go\n@@ -1035,6 +1035,17 @@ func Prctl(option int, arg2 uintptr, arg3 uintptr, arg4 uintptr, arg5 uintptr) (\n \n // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT\n \n+func Pselect(nfd int, r *FdSet, w *FdSet, e *FdSet, timeout *Timespec, sigmask *Sigset_t) (n int, err error) {\n+\tr0, _, e1 := Syscall6(SYS_PSELECT6, uintptr(nfd), uintptr(unsafe.Pointer(r)), uintptr(unsafe.Pointer(w)), uintptr(unsafe.Pointer(e)), uintptr(unsafe.Pointer(timeout)), uintptr(unsafe.Pointer(sigmask)))\n+\tn = int(r0)\n+\tif e1 != 0 {\n+\t\terr = errnoErr(e1)\n+\t}\n+\treturn\n+}\n+\n+// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT\n+\n func read(fd int, p []byte) (n int, err error) {\n \tvar _p0 unsafe.Pointer\n \tif len(p) > 0 {\n"; '--- a/server/vendor/golang.org/x/sys/unix/zsyscall_linux_mipsle.go\n+++ b/server/vendor/golang.org/x/sys/unix/zsyscall_linux_mipsle.go\n@@ -1035,6 +1035,17 @@ func Prctl(option int, arg2 uintptr, arg3 uintptr, arg4 uintptr, arg5 uintptr) (\n \n // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT\n \n+func Pselect(nfd int, r *FdSet, w *FdSet, e *FdSet, timeout *Timespec, sigmask *Sigset_t) (n int, err error) {\n+\tr0, _, e1 := Syscall6(SYS_PSELECT6, uintptr(nfd), uintptr(unsafe.Pointer(r)), uintptr(unsafe.Pointer(w)), uintptr(unsafe.Pointer(e)), uintptr(unsafe.Pointer(timeout)), uintptr(unsafe.Pointer(sigmask)))\n+\tn = int(r0)\n+\tif e1 != 0 {\n+\t\terr = errnoErr(e1)\n+\t}\n+\treturn\n+}\n+\n+// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT\n+\n func read(fd int, p []byte) (n int, err error) {\n \tvar _p0 unsafe.Pointer\n \tif len(p) > 0 {\n';
let outputHtml = Diff2Html.html(strInput, { drawFileList: true, matching: "lines" }); let outputHtml = Diff2Html.html(strInput, { drawFileList: true, matching: 'lines' });
this.outputHtml = outputHtml; this.outputHtml = outputHtml;
} }
} }
@ -148,7 +205,7 @@ export class AppDiffComponent implements OnInit {
] ]
``` ```
### Vue.js #### Diff2Html Vue.js Example
```vue ```vue
<template> <template>
@ -156,67 +213,30 @@ export class AppDiffComponent implements OnInit {
</template> </template>
<script> <script>
import * as Diff2Html from "diff2html"; import * as Diff2Html from 'diff2html';
import "diff2html/bundles/css/diff2html.min.css"; import 'diff2html/bundles/css/diff2html.min.css';
export default { export default {
data() { data() {
return { return {
diffs: diffs:
"--- a/server/vendor/golang.org/x/sys/unix/zsyscall_linux_mipsle.go\n+++ b/server/vendor/golang.org/x/sys/unix/zsyscall_linux_mipsle.go\n@@ -1035,6 +1035,17 @@ func Prctl(option int, arg2 uintptr, arg3 uintptr, arg4 uintptr, arg5 uintptr) (\n \n // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT\n \n+func Pselect(nfd int, r *FdSet, w *FdSet, e *FdSet, timeout *Timespec, sigmask *Sigset_t) (n int, err error) {\n+\tr0, _, e1 := Syscall6(SYS_PSELECT6, uintptr(nfd), uintptr(unsafe.Pointer(r)), uintptr(unsafe.Pointer(w)), uintptr(unsafe.Pointer(e)), uintptr(unsafe.Pointer(timeout)), uintptr(unsafe.Pointer(sigmask)))\n+\tn = int(r0)\n+\tif e1 != 0 {\n+\t\terr = errnoErr(e1)\n+\t}\n+\treturn\n+}\n+\n+// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT\n+\n func read(fd int, p []byte) (n int, err error) {\n \tvar _p0 unsafe.Pointer\n \tif len(p) > 0 {\n" '--- a/server/vendor/golang.org/x/sys/unix/zsyscall_linux_mipsle.go\n+++ b/server/vendor/golang.org/x/sys/unix/zsyscall_linux_mipsle.go\n@@ -1035,6 +1035,17 @@ func Prctl(option int, arg2 uintptr, arg3 uintptr, arg4 uintptr, arg5 uintptr) (\n \n // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT\n \n+func Pselect(nfd int, r *FdSet, w *FdSet, e *FdSet, timeout *Timespec, sigmask *Sigset_t) (n int, err error) {\n+\tr0, _, e1 := Syscall6(SYS_PSELECT6, uintptr(nfd), uintptr(unsafe.Pointer(r)), uintptr(unsafe.Pointer(w)), uintptr(unsafe.Pointer(e)), uintptr(unsafe.Pointer(timeout)), uintptr(unsafe.Pointer(sigmask)))\n+\tn = int(r0)\n+\tif e1 != 0 {\n+\t\terr = errnoErr(e1)\n+\t}\n+\treturn\n+}\n+\n+// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT\n+\n func read(fd int, p []byte) (n int, err error) {\n \tvar _p0 unsafe.Pointer\n \tif len(p) > 0 {\n',
}; };
}, },
computed: { computed: {
prettyHtml() { prettyHtml() {
return Diff2Html.getPrettyHtml(this.diffs, { return Diff2Html.html(this.diffs, {
inputFormat: "diff",
drawFileList: true, drawFileList: true,
matching: "lines", matching: 'lines',
outputFormat: "side-by-side" outputFormat: 'side-by-side',
}); });
} },
} },
}; };
</script> </script>
``` ```
## API ## Diff2HtmlUI
> Intermediate Json From Git Word Diff Output
getJsonFromDiff(input: string, configuration?: Options): Result[]
> Pretty HTML diff
getPrettyHtml(input: any, configuration?: Options): string
> Check out the [docs/demo.html](./docs/demo.html) for a demo example.
## Configuration
The HTML output accepts a Javascript object with configuration. Possible options:
- `inputFormat`: the format of the input data: `'diff'` or `'json'`, default is `'diff'`
- `outputFormat`: the format of the output data: `'line-by-line'` or `'side-by-side'`, default is `'line-by-line'`
- `drawFileList`: show a file list before the diff: `true` or `false`, default is `false`
- `diffStyle`: show differences level in each line: `word` or `char`, default is `word`
- `matching`: matching level: `'lines'` for matching lines, `'words'` for matching lines and words or `'none'`, default is `none`
- `matchWordsThreshold`: similarity threshold for word matching, default is 0.25
- `matchingMaxComparisons`: perform at most this much comparisons for line matching a block of changes, default is `2500`
- `maxLineSizeInBlockForComparison`: maximum number os characters of the bigger line in a block to apply comparison, default is `200`
- `maxLineLengthHighlight`: only perform diff changes highlight if lines are smaller than this, default is `10000`
- `compiledTemplates`: object with previously compiled templates to replace parts of the html
- `rawTemplates`: object with raw not compiled templates to replace parts of the html
- `renderNothingWhenEmpty`: render nothing if the diff shows no change in its comparison: `true` or `false`, default is `false`
> For more information regarding the possible templates look into [src/templates](https://github.com/rtfpessoa/diff2html/tree/master/src/templates)
** Diff2HtmlUI Helper Options **
- `synchronisedScroll`: scroll both panes in side-by-side mode: `true` or `false`, default is `false`
> For more information regarding the possible templates look into [src/templates](https://github.com/rtfpessoa/diff2html/tree/master/src/templates)
## Diff2HtmlUI Helper
> Simple wrapper to ease simple tasks in the browser such as: code highlight and js effects > Simple wrapper to ease simple tasks in the browser such as: code highlight and js effects
@ -225,7 +245,41 @@ The HTML output accepts a Javascript object with configuration. Possible options
- Enable collapsible file summary list - Enable collapsible file summary list
- Enable syntax highlight of the code in the diffs - Enable syntax highlight of the code in the diffs
### How to use ### Diff2HtmlUI API
> Create a Diff2HtmlUI instance
```ts
constructor(diffInput: string | DiffFile[], target: HTMLElement) // diff2html-ui, diff2html-ui-slim
constructor(diffInput: string | DiffFile[], target: HTMLElement, config: Diff2HtmlUIConfig = {}, hljs?: HighlightJS) // diff2html-ui-base
```
> Generate and inject in the document the Pretty HTML representation of the diff
```ts
draw(): void
```
> Enable extra features
```ts
synchronisedScroll(): void
fileListToggle(startVisible: boolean): void
highlightCode(): void
```
> Check out the [docs/demo.html](./docs/demo.html) for a demo example.
### Diff2HtmlUI Configuration
- `synchronisedScroll`: scroll both panes in side-by-side mode: `true` or `false`, default is `true`
- `highlight`: syntax highlight the code on the diff: `true` or `false`, default is `true`
- `fileListToggle`: allow the file summary list to be toggled: `true` or `false`, default is `true`
- `fileListStartVisible`: choose if the file summary list starts visible: `true` or `false`, default is `false`
- `smartSelection`: allow selection of the code without including line numbers of line prefixes: `true` or `false`,
default is `true`
### Diff2HtmlUI Browser
#### Mandatory HTML resource imports #### Mandatory HTML resource imports
@ -240,8 +294,8 @@ The HTML output accepts a Javascript object with configuration. Possible options
#### Init #### Init
```js ```js
const targetElement = document.getElementById("destination-elem-id"); const targetElement = document.getElementById('destination-elem-id');
const configuration = { drawFileList: true, matching: "lines" }; const configuration = { drawFileList: true, matching: 'lines' };
const diff2htmlUi = new Diff2HtmlUI(diffString, targetElement, configuration); const diff2htmlUi = new Diff2HtmlUI(diffString, targetElement, configuration);
// or // or
@ -268,7 +322,7 @@ diff2htmlUi.draw();
> Pass the option `highlight` with value true or invoke `diff2htmlUi.highlightCode()` after `diff2htmlUi.draw()`. > Pass the option `highlight` with value true or invoke `diff2htmlUi.highlightCode()` after `diff2htmlUi.draw()`.
```js ```js
document.addEventListener("DOMContentLoaded", () => { document.addEventListener('DOMContentLoaded', () => {
const diffString = `diff --git a/sample.js b/sample.js const diffString = `diff --git a/sample.js b/sample.js
index 0000001..0ddf2ba index 0000001..0ddf2ba
--- a/sample.js --- a/sample.js
@ -276,8 +330,8 @@ index 0000001..0ddf2ba
@@ -1 +1 @@ @@ -1 +1 @@
-console.log("Hello World!") -console.log("Hello World!")
+console.log("Hello from Diff2Html!")`; +console.log("Hello from Diff2Html!")`;
const targetElement = document.getElementById("myDiffElement"); const targetElement = document.getElementById('myDiffElement');
const configuration = { inputFormat: "json", drawFileList: true, matching: "lines", highlight: true }; const configuration = { inputFormat: 'json', drawFileList: true, matching: 'lines', highlight: true };
const diff2htmlUi = new Diff2HtmlUI(diffString, targetElement, configuration); const diff2htmlUi = new Diff2HtmlUI(diffString, targetElement, configuration);
diff2htmlUi.draw(); diff2htmlUi.draw();
diff2htmlUi.highlightCode(); diff2htmlUi.highlightCode();
@ -293,19 +347,19 @@ index 0000001..0ddf2ba
<script type="text/javascript" src="bundles/js/diff2html-ui.min.js"></script> <script type="text/javascript" src="bundles/js/diff2html-ui.min.js"></script>
``` ```
> Invoke the Diff2HtmlUI helper > Invoke the Diff2HtmlUI helper Pass the option `fileListToggle` with value true or invoke
> Pass the option `fileListToggle` with value true or invoke `diff2htmlUi.fileListToggle()` after `diff2htmlUi.draw()`. > `diff2htmlUi.fileListToggle()` after `diff2htmlUi.draw()`.
```js ```js
document.addEventListener("DOMContentLoaded", () => { document.addEventListener('DOMContentLoaded', () => {
const targetElement = document.getElementById("myDiffElement"); const targetElement = document.getElementById('myDiffElement');
var diff2htmlUi = new Diff2HtmlUI(lineDiffExample, targetElement, { drawFileList: true, matching: "lines" }); var diff2htmlUi = new Diff2HtmlUI(lineDiffExample, targetElement, { drawFileList: true, matching: 'lines' });
diff2htmlUi.draw(); diff2htmlUi.draw();
diff2htmlUi.fileListToggle(false); diff2htmlUi.fileListToggle(false);
}); });
``` ```
# Troubleshooting ## Troubleshooting
### 1. Out of memory or Slow execution ### 1. Out of memory or Slow execution
@ -320,9 +374,8 @@ document.addEventListener("DOMContentLoaded", () => {
## Contributions ## Contributions
This is a developer friendly project, all the contributions are welcome. This is a developer friendly project, all the contributions are welcome. To contribute just send a pull request with
To contribute just send a pull request with your changes following the guidelines described in `CONTRIBUTING.md`. your changes following the guidelines described in `CONTRIBUTING.md`. I will try to review them as soon as possible.
I will try to review them as soon as possible.
## Contributors ✨ ## Contributors ✨
@ -369,14 +422,16 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<!-- ALL-CONTRIBUTORS-LIST:END --> <!-- ALL-CONTRIBUTORS-LIST:END -->
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification.
Contributions of any kind welcome!
## License ## License
Copyright 2014-2016 Rodrigo Fernandes. Released under the terms of the MIT license. Copyright 2014-present Rodrigo Fernandes. Released under the terms of the MIT license.
## Thanks ## Thanks
This project is inspired in [pretty-diff](https://github.com/scottgonzalez/pretty-diff) by [Scott González](https://github.com/scottgonzalez). This project is inspired in [pretty-diff](https://github.com/scottgonzalez/pretty-diff) by
[Scott González](https://github.com/scottgonzalez).
--- ---

View file

@ -9,34 +9,27 @@
## Reporting a Vulnerability ## Reporting a Vulnerability
We take all security bugs in `diff2html` seriously. We take all security bugs in `diff2html` seriously. Thank you for the help improving the security of `diff2html`. We
Thank you for the help improving the security of `diff2html`. appreciate your efforts and responsible disclosure and will make every effort to acknowledge your contributions.
We appreciate your efforts and responsible disclosure and
will make every effort to acknowledge your contributions.
Report security bugs by emailing the lead maintainer at `rtfrodrigo [at] gmail [dot] com`. Report security bugs by emailing the lead maintainer at `rtfrodrigo [at] gmail [dot] com`.
The lead maintainer will acknowledge your email within 48 hours, and will send a The lead maintainer will acknowledge your email within 48 hours, and will send a more detailed response within 48 hours
more detailed response within 48 hours indicating the next steps in handling indicating the next steps in handling your report. After the initial reply to your report, the security team will
your report. After the initial reply to your report, the security team will endeavor to keep you informed of the progress towards a fix and full announcement, and may ask for additional
endeavor to keep you informed of the progress towards a fix and full information or guidance.
announcement, and may ask for additional information or guidance.
Report security bugs in third-party modules to the person or team maintaining Report security bugs in third-party modules to the person or team maintaining the module.
the module.
## Disclosure Policy ## Disclosure Policy
When the security team receives a security bug report, they will assign it to a When the security team receives a security bug report, they will assign it to a primary handler. This person will
primary handler. This person will coordinate the fix and release process, coordinate the fix and release process, involving the following steps:
involving the following steps:
* Confirm the problem and determine the affected versions. - Confirm the problem and determine the affected versions.
* Audit code to find any potential similar problems. - Audit code to find any potential similar problems.
* Prepare fixes for all releases still under maintenance. These fixes will be - Prepare fixes for all releases still under maintenance. These fixes will be released as fast as possible.
released as fast as possible.
## Comments on this Policy ## Comments on this Policy
If you have suggestions on how this process could be improved please submit a If you have suggestions on how this process could be improved please submit a pull request.
pull request.

View file

@ -1,15 +1,22 @@
module.exports = { module.exports = {
verbose: true, verbose: true,
preset: "ts-jest", preset: 'ts-jest',
testEnvironment: "node", testEnvironment: 'node',
coverageDirectory: "./coverage", coverageDirectory: './coverage',
coverageReporters: ["lcov", "text", "html"], coverageReporters: ['lcov', 'text', 'html'],
collectCoverageFrom: [
'src/**/*.ts',
'!src/ui/**',
'!src/diff2html-templates.ts',
'!src/__tests__/**',
'!node_modules/**',
],
coverageThreshold: { coverageThreshold: {
global: { global: {
statements: 94, statements: 93,
branches: 85, branches: 86,
functions: 98, functions: 98,
lines: 93 lines: 93,
} },
} },
}; };

View file

@ -35,26 +35,51 @@
"node": ">=10.13" "node": ">=10.13"
}, },
"scripts": { "scripts": {
"lint": "eslint '*/**/*.{js,jsx,ts,tsx}'", "eslint": "eslint --ignore-path .gitignore \"**/*.{js,jsx,ts,tsx,json}\"",
"style": "yarn run lint", "lint:check": "yarn run eslint",
"test": "yarn run build-templates && jest", "lint:fix": "yarn run eslint --fix",
"coverage": "jest --collectCoverage", "prettier": "prettier --ignore-path .gitignore '**/*.+(js|jsx|ts|tsx|json|css|html|md|mdx)'",
"coverage-html": "yarn run coverage && open ./coverage/index.html", "format:check": "yarn run prettier --check",
"codacy": "cat ./coverage/lcov.info | codacy-coverage", "format:fix": "yarn run prettier --write",
"build": "yarn run build-css && yarn run build-templates && yarn run build-es5 && yarn run build-esm && yarn run build-bundles && yarn run build-website", "build": "yarn run build:css && yarn run build:templates && yarn run build:es5 && yarn run build:esm && yarn run build:bundles && yarn run build:website",
"build-es5": "rm -rf lib; tsc -p tsconfig.json --outDir lib", "build:es5": "rm -rf lib; tsc -p tsconfig.json --outDir lib",
"build-esm": "rm -rf lib-esm; tsc -p tsconfig.json -m es6 --outDir lib-esm", "build:esm": "rm -rf lib-esm; tsc -p tsconfig.json -m es6 --outDir lib-esm",
"build-bundles": "rm -rf ./bundles/js; NODE_ENV=production WEBPACK_MINIMIZE=true webpack --mode production --config webpack.bundles.ts", "build:bundles": "rm -rf ./bundles/js; webpack ---display-reasons --display-modules --mode production --config webpack.bundles.ts",
"build-css": "rm -rf ./bundles/css; postcss --use autoprefixer postcss-import postcss-preset-env cssnano -o ./bundles/css/diff2html.min.css ./src/ui/css/diff2html.css", "build:css": "rm -rf ./bundles/css; postcss --config ./postcss.config.js --no-map -o ./bundles/css/diff2html.min.css ./src/ui/css/diff2html.css",
"build-templates": "ts-node ./scripts/hulk.ts --wrapper ts --variable 'defaultTemplates' ./src/templates/*.mustache > ./src/diff2html-templates.ts", "build:templates": "ts-node ./scripts/hulk.ts --wrapper ts --variable 'defaultTemplates' ./src/templates/*.mustache > ./src/diff2html-templates.ts",
"build-website": "rm -rf docs; NODE_ENV=production WEBPACK_MINIMIZE=true webpack --mode production --config webpack.website.ts", "build:website": "rm -rf docs; webpack ---display-reasons --display-modules --mode production --config webpack.website.ts",
"start-website": "WEBPACK_MINIFY=false NODE_ENV=dev webpack-dev-server --mode dev --config webpack.website.ts", "test": "is-ci 'test:coverage' 'test:watch'",
"preversion": "yarn run lint && yarn run build && yarn test", "test:coverage": "jest --coverage",
"test:watch": "jest --watch",
"test:debug": "node --inspect-brk ./node_modules/jest/bin/jest.js --runInBand --watch",
"coverage:open": "yarn run test:coverage && open ./coverage/index.html",
"coverage:push": "cat ./coverage/lcov.info | codacy-coverage",
"validate": "yarn run build:templates && yarn run format:check && yarn run lint:check && yarn run build && yarn run test:coverage",
"fix": "yarn run format:fix && yarn run lint:fix",
"start": "yarn run start:website",
"start:website": "webpack-dev-server --mode development --config webpack.website.ts",
"preversion": "yarn run validate",
"version": "git add -A package.json" "version": "git add -A package.json"
}, },
"main": "./lib/diff2html.js", "main": "./lib/diff2html.js",
"module": "./lib-esm/diff2html.js", "module": "./lib-esm/diff2html.js",
"types": "./lib/diff2html.d.ts", "types": "./lib/diff2html.d.ts",
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"**/*.+(js|jsx|ts|tsx|json)": [
"prettier --write",
"eslint --fix",
"git add"
],
"**/*.+(css|html|md|mdx)": [
"prettier --write",
"git add"
]
},
"dependencies": { "dependencies": {
"diff": "4.0.1", "diff": "4.0.1",
"hogan.js": "3.0.2" "hogan.js": "3.0.2"
@ -63,21 +88,20 @@
"highlight.js": "9.17.1" "highlight.js": "9.17.1"
}, },
"devDependencies": { "devDependencies": {
"@types/autoprefixer": "9.6.1",
"@types/clipboard": "2.0.1", "@types/clipboard": "2.0.1",
"@types/copy-webpack-plugin": "5.0.0", "@types/copy-webpack-plugin": "5.0.0",
"@types/diff": "4.0.2", "@types/diff": "4.0.2",
"@types/highlight.js": "9.12.3", "@types/highlight.js": "9.12.3",
"@types/hogan.js": "3.0.0", "@types/hogan.js": "3.0.0",
"@types/html-webpack-plugin": "3.2.1", "@types/html-webpack-plugin": "3.2.1",
"@types/jest": "24.0.24", "@types/jest": "24.0.25",
"@types/mini-css-extract-plugin": "0.8.0", "@types/mini-css-extract-plugin": "0.9.0",
"@types/mkdirp": "0.5.2", "@types/mkdirp": "0.5.2",
"@types/node": "12.12.21", "@types/node": "13.1.1",
"@types/nopt": "3.0.29", "@types/nopt": "3.0.29",
"@types/webpack": "4.41.0", "@types/webpack": "4.41.0",
"@typescript-eslint/eslint-plugin": "2.12.0", "@typescript-eslint/eslint-plugin": "2.13.0",
"@typescript-eslint/parser": "2.12.0", "@typescript-eslint/parser": "2.13.0",
"autoprefixer": "9.7.3", "autoprefixer": "9.7.3",
"bootstrap": "3.4.1", "bootstrap": "3.4.1",
"clipboard": "2.0.4", "clipboard": "2.0.4",
@ -86,37 +110,38 @@
"css-loader": "3.4.0", "css-loader": "3.4.0",
"cssnano": "4.1.10", "cssnano": "4.1.10",
"eslint": "6.8.0", "eslint": "6.8.0",
"eslint-config-prettier": "6.7.0", "eslint-config-prettier": "6.9.0",
"eslint-config-standard": "14.1.0",
"eslint-plugin-import": "2.19.1", "eslint-plugin-import": "2.19.1",
"eslint-plugin-jest": "23.1.1", "eslint-plugin-jest": "23.2.0",
"eslint-plugin-node": "10.0.0", "eslint-plugin-json": "2.0.1",
"eslint-plugin-prettier": "3.1.2", "eslint-plugin-node": "11.0.0",
"eslint-plugin-optimize-regex": "1.1.7",
"eslint-plugin-promise": "4.2.1", "eslint-plugin-promise": "4.2.1",
"eslint-plugin-standard": "4.0.1", "eslint-plugin-sonarjs": "0.5.0",
"fast-html-parser": "1.0.1",
"file-loader": "5.0.2", "file-loader": "5.0.2",
"handlebars": "4.5.3", "handlebars": "4.5.3",
"handlebars-loader": "1.7.1", "handlebars-loader": "1.7.1",
"html-webpack-plugin": "3.2.0", "html-webpack-plugin": "3.2.0",
"husky": "3.1.0",
"image-webpack-loader": "6.0.0", "image-webpack-loader": "6.0.0",
"is-ci-cli": "2.0.0",
"jest": "24.9.0", "jest": "24.9.0",
"lint-staged": "9.5.0",
"mini-css-extract-plugin": "0.9.0", "mini-css-extract-plugin": "0.9.0",
"mkdirp": "0.5.1", "mkdirp": "0.5.1",
"nopt": "4.0.1", "nopt": "4.0.1",
"postcss": "7.0.25",
"postcss-cli": "6.1.3", "postcss-cli": "6.1.3",
"postcss-import": "12.0.1", "postcss-import": "12.0.1",
"postcss-loader": "3.0.0", "postcss-loader": "3.0.0",
"postcss-normalize": "8.0.1",
"postcss-preset-env": "6.7.0", "postcss-preset-env": "6.7.0",
"prettier": "1.19.1", "prettier": "1.19.1",
"style-loader": "1.1.1",
"ts-jest": "24.2.0", "ts-jest": "24.2.0",
"ts-loader": "6.2.1", "ts-loader": "6.2.1",
"ts-node": "8.5.4", "ts-node": "8.5.4",
"typescript": "3.7.4", "typescript": "3.7.4",
"url-loader": "3.0.0", "url-loader": "3.0.0",
"webpack": "4.41.4", "webpack": "4.41.5",
"webpack-cli": "3.3.10", "webpack-cli": "3.3.10",
"webpack-dev-server": "3.10.1", "webpack-dev-server": "3.10.1",
"whatwg-fetch": "3.0.0" "whatwg-fetch": "3.0.0"

10
postcss.config.js Normal file
View file

@ -0,0 +1,10 @@
module.exports = {
sourceMap: false,
plugins: {
'postcss-import': {},
'postcss-preset-env': {
browsers: 'last 2 versions',
},
cssnano: {},
},
};

View file

@ -1,5 +1,3 @@
#!/usr/bin/env node
/* /*
* Copyright 2011 Twitter, Inc. * Copyright 2011 Twitter, Inc.
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -15,12 +13,12 @@
* limitations under the License. * limitations under the License.
*/ */
import * as path from "path"; import * as path from 'path';
import * as fs from "fs"; import * as fs from 'fs';
import * as hogan from "hogan.js"; import * as hogan from 'hogan.js';
import nopt from "nopt"; import nopt from 'nopt';
import * as mkderp from "mkdirp"; import * as mkderp from 'mkdirp';
const options = nopt( const options = nopt(
{ {
@ -29,47 +27,47 @@ const options = nopt(
variable: String, variable: String,
wrapper: String, wrapper: String,
version: true, version: true,
help: true help: true,
}, },
{ {
n: ["--namespace"], n: ['--namespace'],
o: ["--outputdir"], o: ['--outputdir'],
vn: ["--variable"], vn: ['--variable'],
w: ["--wrapper"], w: ['--wrapper'],
h: ["--help"], h: ['--help'],
v: ["--version"] v: ['--version'],
} },
); );
const specials = ["/", ".", "*", "+", "?", "|", "(", ")", "[", "]", "{", "}", "\\"]; const specials = ['/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\'];
const specialsRegExp = new RegExp("(\\" + specials.join("|\\") + ")", "g"); const specialsRegExp = new RegExp('(\\' + specials.join('|\\') + ')', 'g');
function escape(text: string): string { function escape(text: string): string {
return text.replace(specialsRegExp, "\\$1"); return text.replace(specialsRegExp, '\\$1');
} }
function cyan(text: string): string { function cyan(text: string): string {
return "\x1B[36m" + text + "\x1B[39m"; return '\x1B[36m' + text + '\x1B[39m';
} }
function extractFiles(files: string[]): string[] { function extractFiles(files: string[]): string[] {
const usage = `${cyan( const usage = `${cyan(
"USAGE:" 'USAGE:',
)} hulk [--wrapper wrapper] [--outputdir outputdir] [--namespace namespace] [--variable variable] FILES )} hulk [--wrapper wrapper] [--outputdir outputdir] [--namespace namespace] [--variable variable] FILES
${cyan("OPTIONS:")} [-w, --wrapper] :: wraps the template (i.e. amd) ${cyan('OPTIONS:')} [-w, --wrapper] :: wraps the template (i.e. amd)
[-o, --outputdir] :: outputs the templates as individual files to a directory [-o, --outputdir] :: outputs the templates as individual files to a directory
[-n, --namespace] :: prepend string to template names [-n, --namespace] :: prepend string to template names
[-vn, --variable] :: variable name for non-amd wrapper [-vn, --variable] :: variable name for non-amd wrapper
${cyan("EXAMPLE:")} hulk --wrapper amd ./templates/*.mustache ${cyan('EXAMPLE:')} hulk --wrapper amd ./templates/*.mustache
${cyan("NOTE:")} hulk supports the "*" wildcard and allows you to target specific extensions too ${cyan('NOTE:')} hulk supports the "*" wildcard and allows you to target specific extensions too
`; `;
if (options.version) { if (options.version) {
console.log(require("../package.json").version); console.log(require('../package.json').version);
process.exit(0); process.exit(0);
} }
@ -78,20 +76,18 @@ function extractFiles(files: string[]): string[] {
process.exit(0); process.exit(0);
} }
const templateFiles = files return files
.map((fileGlob: string) => { .map((fileGlob: string) => {
if (/\*/.test(fileGlob)) { if (/\*/.test(fileGlob)) {
const [fileGlobPrefix, fileGlobSuffix] = fileGlob.split("*"); const [fileGlobPrefix, fileGlobSuffix] = fileGlob.split('*');
const files = fs.readdirSync(fileGlobPrefix || ".").reduce<string[]>((previousFiles, relativeFilePath) => { return fs.readdirSync(fileGlobPrefix || '.').reduce<string[]>((previousFiles, relativeFilePath) => {
const file = path.join(fileGlobPrefix, relativeFilePath); const file = path.join(fileGlobPrefix, relativeFilePath);
if (new RegExp(`${escape(fileGlobSuffix)}$`).test(relativeFilePath) && fs.statSync(file).isFile()) { if (new RegExp(`${escape(fileGlobSuffix)}$`).test(relativeFilePath) && fs.statSync(file).isFile()) {
previousFiles.push(file); previousFiles.push(file);
} }
return previousFiles; return previousFiles;
}, []); }, []);
return files;
} else if (fs.statSync(fileGlob).isFile()) { } else if (fs.statSync(fileGlob).isFile()) {
return [fileGlob]; return [fileGlob];
} else { } else {
@ -99,8 +95,6 @@ function extractFiles(files: string[]): string[] {
} }
}) })
.reduce((previous, current) => previous.concat(current), []); .reduce((previous, current) => previous.concat(current), []);
return templateFiles;
} }
// Remove utf-8 byte order mark, http://en.wikipedia.org/wiki/Byte_order_mark // Remove utf-8 byte order mark, http://en.wikipedia.org/wiki/Byte_order_mark
@ -115,21 +109,21 @@ function removeByteOrderMark(text: string): string {
function wrap(file: string, name: string, openedFile: string): string { function wrap(file: string, name: string, openedFile: string): string {
const hoganTemplateString = `new Hogan.Template(${hogan.compile(openedFile, { asString: true })})`; const hoganTemplateString = `new Hogan.Template(${hogan.compile(openedFile, { asString: true })})`;
const objectName = options.variable || "templates"; const objectName = options.variable || 'templates';
const objectAccessor = `${objectName}["${name}"]`; const objectAccessor = `${objectName}["${name}"]`;
const objectStmt = `${objectAccessor} = ${hoganTemplateString};`; const objectStmt = `${objectAccessor} = ${hoganTemplateString};`;
switch (options.wrapper) { switch (options.wrapper) {
case "amd": case 'amd':
return `define(${ return `define(${
!options.outputdir ? `"${path.join(path.dirname(file), name)}", ` : "" !options.outputdir ? `"${path.join(path.dirname(file), name)}", ` : ''
}["hogan.js"], function(Hogan) { return ${hoganTemplateString}; });`; }["hogan.js"], function(Hogan) { return ${hoganTemplateString}; });`;
case "node": case 'node':
// If we have a template per file the export will expose the template directly // If we have a template per file the export will expose the template directly
return options.outputdir ? `global.${objectStmt};\nmodule.exports = ${objectAccessor};` : `global.${objectStmt}`; return options.outputdir ? `global.${objectStmt};\nmodule.exports = ${objectAccessor};` : `global.${objectStmt}`;
case "ts": case 'ts':
return `// @ts-ignore\n${objectStmt}`; return `// @ts-ignore\n${objectStmt}`;
default: default:
return objectStmt; return objectStmt;
@ -137,25 +131,25 @@ function wrap(file: string, name: string, openedFile: string): string {
} }
function prepareOutput(content: string): string { function prepareOutput(content: string): string {
const variableName = options.variable || "templates"; const variableName = options.variable || 'templates';
switch (options.wrapper) { switch (options.wrapper) {
case "amd": case 'amd':
return content; return content;
case "node": case 'node':
return `(function() { return `(function() {
if (!!!global.${variableName}) global.${variableName} = {}; if (!!!global.${variableName}) global.${variableName} = {};
var Hogan = require("hogan.js"); var Hogan = require("hogan.js");
${content} ${content}
${!options.outputdir ? `module.exports = global.${variableName};\n` : ""})();`; ${!options.outputdir ? `module.exports = global.${variableName};\n` : ''})();`;
case "ts": case 'ts':
return `import * as Hogan from "hogan.js"; return `import * as Hogan from "hogan.js";
type CompiledTemplates = { [name: string]: Hogan.Template }; type CompiledTemplates = { [name: string]: Hogan.Template };
export const ${variableName}: CompiledTemplates = {}; export const ${variableName}: CompiledTemplates = {};
${content}`; ${content}`;
default: default:
return "if (!!!" + variableName + ") var " + variableName + " = {};\n" + content; return 'if (!!!' + variableName + ') var ' + variableName + ' = {};\n' + content;
} }
} }
@ -166,28 +160,28 @@ if (options.outputdir) {
// Prepend namespace to template name // Prepend namespace to template name
function namespace(name: string): string { function namespace(name: string): string {
return (options.namespace || "") + name; return (options.namespace || '') + name;
} }
// Write a template foreach file that matches template extension // Write a template foreach file that matches template extension
const templates = extractFiles(options.argv.remain) const templates = extractFiles(options.argv.remain)
.map(file => { .map(file => {
const timmedFileContents = fs.readFileSync(file, "utf8").trim(); const timmedFileContents = fs.readFileSync(file, 'utf8').trim();
if (!timmedFileContents) return; if (!timmedFileContents) return;
const name = namespace(path.basename(file).replace(/\..*$/, "")); const name = namespace(path.basename(file).replace(/\..*$/, ''));
const cleanFileContents = wrap(file, name, removeByteOrderMark(timmedFileContents)); const cleanFileContents = wrap(file, name, removeByteOrderMark(timmedFileContents));
if (!options.outputdir) return cleanFileContents; if (!options.outputdir) return cleanFileContents;
const fileExtension = options.wrapper === "ts" ? "ts" : "js"; const fileExtension = options.wrapper === 'ts' ? 'ts' : 'js';
return fs.writeFileSync(path.join(options.outputdir, `${name}.${fileExtension}`), prepareOutput(cleanFileContents)); return fs.writeFileSync(path.join(options.outputdir, `${name}.${fileExtension}`), prepareOutput(cleanFileContents));
}) })
.filter(templateContents => typeof templateContents !== "undefined"); .filter(templateContents => typeof templateContents !== 'undefined');
// Output templates // Output templates
if (!templates.length || options.outputdir) process.exit(0); if (!templates.length || options.outputdir) process.exit(0);
console.log(prepareOutput(templates.join("\n"))); console.log(prepareOutput(templates.join('\n')));

View file

@ -1,17 +1,17 @@
import { parse } from "../diff-parser"; import { parse } from '../diff-parser';
describe("DiffParser", () => { describe('DiffParser', () => {
describe("generateDiffJson", () => { describe('generateDiffJson', () => {
// eslint-disable-next-line jest/expect-expect // eslint-disable-next-line jest/expect-expect
it("should parse unix with \n diff", () => { it('should parse unix with \n diff', () => {
const diff = const diff =
"diff --git a/sample b/sample\n" + 'diff --git a/sample b/sample\n' +
"index 0000001..0ddf2ba\n" + 'index 0000001..0ddf2ba\n' +
"--- a/sample\n" + '--- a/sample\n' +
"+++ b/sample\n" + '+++ b/sample\n' +
"@@ -1 +1 @@\n" + '@@ -1 +1 @@\n' +
"-test\n" + '-test\n' +
"+test1r\n"; '+test1r\n';
const result = parse(diff); const result = parse(diff);
expect(result).toMatchInlineSnapshot(` expect(result).toMatchInlineSnapshot(`
Array [ Array [
@ -53,15 +53,15 @@ describe("DiffParser", () => {
}); });
// eslint-disable-next-line jest/expect-expect // eslint-disable-next-line jest/expect-expect
it("should parse windows with \r\n diff", () => { it('should parse windows with \r\n diff', () => {
const diff = const diff =
"diff --git a/sample b/sample\r\n" + 'diff --git a/sample b/sample\r\n' +
"index 0000001..0ddf2ba\r\n" + 'index 0000001..0ddf2ba\r\n' +
"--- a/sample\r\n" + '--- a/sample\r\n' +
"+++ b/sample\r\n" + '+++ b/sample\r\n' +
"@@ -1 +1 @@\r\n" + '@@ -1 +1 @@\r\n' +
"-test\r\n" + '-test\r\n' +
"+test1r\r\n"; '+test1r\r\n';
const result = parse(diff); const result = parse(diff);
expect(result).toMatchInlineSnapshot(` expect(result).toMatchInlineSnapshot(`
Array [ Array [
@ -103,15 +103,15 @@ describe("DiffParser", () => {
}); });
// eslint-disable-next-line jest/expect-expect // eslint-disable-next-line jest/expect-expect
it("should parse old os x with \r diff", () => { it('should parse old os x with \r diff', () => {
const diff = const diff =
"diff --git a/sample b/sample\r" + 'diff --git a/sample b/sample\r' +
"index 0000001..0ddf2ba\r" + 'index 0000001..0ddf2ba\r' +
"--- a/sample\r" + '--- a/sample\r' +
"+++ b/sample\r" + '+++ b/sample\r' +
"@@ -1 +1 @@\r" + '@@ -1 +1 @@\r' +
"-test\r" + '-test\r' +
"+test1r\r"; '+test1r\r';
const result = parse(diff); const result = parse(diff);
expect(result).toMatchInlineSnapshot(` expect(result).toMatchInlineSnapshot(`
Array [ Array [
@ -153,15 +153,15 @@ describe("DiffParser", () => {
}); });
// eslint-disable-next-line jest/expect-expect // eslint-disable-next-line jest/expect-expect
it("should parse mixed eols diff", () => { it('should parse mixed eols diff', () => {
const diff = const diff =
"diff --git a/sample b/sample\n" + 'diff --git a/sample b/sample\n' +
"index 0000001..0ddf2ba\r\n" + 'index 0000001..0ddf2ba\r\n' +
"--- a/sample\r" + '--- a/sample\r' +
"+++ b/sample\r\n" + '+++ b/sample\r\n' +
"@@ -1 +1 @@\n" + '@@ -1 +1 @@\n' +
"-test\r" + '-test\r' +
"+test1r\n"; '+test1r\n';
const result = parse(diff); const result = parse(diff);
expect(result).toMatchInlineSnapshot(` expect(result).toMatchInlineSnapshot(`
Array [ Array [
@ -202,16 +202,16 @@ describe("DiffParser", () => {
`); `);
}); });
it("should parse diff with special characters", () => { it('should parse diff with special characters', () => {
const diff = const diff =
'diff --git "a/bla with \ttab.scala" "b/bla with \ttab.scala"\n' + 'diff --git "a/bla with \ttab.scala" "b/bla with \ttab.scala"\n' +
"index 4c679d7..e9bd385 100644\n" + 'index 4c679d7..e9bd385 100644\n' +
'--- "a/bla with \ttab.scala"\n' + '--- "a/bla with \ttab.scala"\n' +
'+++ "b/bla with \ttab.scala"\n' + '+++ "b/bla with \ttab.scala"\n' +
"@@ -1 +1,2 @@\n" + '@@ -1 +1,2 @@\n' +
"-cenas\n" + '-cenas\n' +
"+cenas com ananas\n" + '+cenas com ananas\n' +
"+bananas"; '+bananas';
const result = parse(diff); const result = parse(diff);
expect(result).toMatchInlineSnapshot(` expect(result).toMatchInlineSnapshot(`
Array [ Array [
@ -259,16 +259,16 @@ describe("DiffParser", () => {
`); `);
}); });
it("should parse diff with prefix", () => { it('should parse diff with prefix', () => {
const diff = const diff =
'diff --git "\tbla with \ttab.scala" "\tbla with \ttab.scala"\n' + 'diff --git "\tbla with \ttab.scala" "\tbla with \ttab.scala"\n' +
"index 4c679d7..e9bd385 100644\n" + 'index 4c679d7..e9bd385 100644\n' +
'--- "\tbla with \ttab.scala"\n' + '--- "\tbla with \ttab.scala"\n' +
'+++ "\tbla with \ttab.scala"\n' + '+++ "\tbla with \ttab.scala"\n' +
"@@ -1 +1,2 @@\n" + '@@ -1 +1,2 @@\n' +
"-cenas\n" + '-cenas\n' +
"+cenas com ananas\n" + '+cenas com ananas\n' +
"+bananas"; '+bananas';
const result = parse(diff); const result = parse(diff);
expect(result).toMatchInlineSnapshot(` expect(result).toMatchInlineSnapshot(`
Array [ Array [
@ -316,17 +316,17 @@ describe("DiffParser", () => {
`); `);
}); });
it("should parse diff with deleted file", () => { it('should parse diff with deleted file', () => {
const diff = const diff =
"diff --git a/src/var/strundefined.js b/src/var/strundefined.js\n" + 'diff --git a/src/var/strundefined.js b/src/var/strundefined.js\n' +
"deleted file mode 100644\n" + 'deleted file mode 100644\n' +
"index 04e16b0..0000000\n" + 'index 04e16b0..0000000\n' +
"--- a/src/var/strundefined.js\n" + '--- a/src/var/strundefined.js\n' +
"+++ /dev/null\n" + '+++ /dev/null\n' +
"@@ -1,3 +0,0 @@\n" + '@@ -1,3 +0,0 @@\n' +
"-define(() => {\n" + '-define(() => {\n' +
"- return typeof undefined;\n" + '- return typeof undefined;\n' +
"-});\n"; '-});\n';
const result = parse(diff); const result = parse(diff);
expect(result).toMatchInlineSnapshot(` expect(result).toMatchInlineSnapshot(`
Array [ Array [
@ -375,19 +375,19 @@ describe("DiffParser", () => {
`); `);
}); });
it("should parse diff with new file", () => { it('should parse diff with new file', () => {
const diff = const diff =
"diff --git a/test.js b/test.js\n" + 'diff --git a/test.js b/test.js\n' +
"new file mode 100644\n" + 'new file mode 100644\n' +
"index 0000000..e1e22ec\n" + 'index 0000000..e1e22ec\n' +
"--- /dev/null\n" + '--- /dev/null\n' +
"+++ b/test.js\n" + '+++ b/test.js\n' +
"@@ -0,0 +1,5 @@\n" + '@@ -0,0 +1,5 @@\n' +
"+var parser = require('./source/git-parser');\n" + "+var parser = require('./source/git-parser');\n" +
"+\n" + '+\n' +
"+var patchLineList = [ false, false, false, false ];\n" + '+var patchLineList = [ false, false, false, false ];\n' +
"+\n" + '+\n' +
"+console.log(parser.parsePatchDiffResult(text, patchLineList));\n"; '+console.log(parser.parsePatchDiffResult(text, patchLineList));\n';
const result = parse(diff); const result = parse(diff);
expect(result).toMatchInlineSnapshot(` expect(result).toMatchInlineSnapshot(`
Array [ Array [
@ -448,19 +448,19 @@ describe("DiffParser", () => {
`); `);
}); });
it("should parse diff with nested diff", () => { it('should parse diff with nested diff', () => {
const diff = const diff =
"diff --git a/src/offset.js b/src/offset.js\n" + 'diff --git a/src/offset.js b/src/offset.js\n' +
"index cc6ffb4..fa51f18 100644\n" + 'index cc6ffb4..fa51f18 100644\n' +
"--- a/src/offset.js\n" + '--- a/src/offset.js\n' +
"+++ b/src/offset.js\n" + '+++ b/src/offset.js\n' +
"@@ -1,6 +1,5 @@\n" + '@@ -1,6 +1,5 @@\n' +
"+var parser = require('./source/git-parser');\n" + "+var parser = require('./source/git-parser');\n" +
"+\n" + '+\n' +
"+var text = 'diff --git a/components/app/app.html b/components/app/app.html\\nindex ecb7a95..027bd9b 100644\\n--- a/components/app/app.html\\n+++ b/components/app/app.html\\n@@ -52,0 +53,3 @@\\n+\\n+\\n+\\n@@ -56,0 +60,3 @@\\n+\\n+\\n+\\n'\n" + "+var text = 'diff --git a/components/app/app.html b/components/app/app.html\\nindex ecb7a95..027bd9b 100644\\n--- a/components/app/app.html\\n+++ b/components/app/app.html\\n@@ -52,0 +53,3 @@\\n+\\n+\\n+\\n@@ -56,0 +60,3 @@\\n+\\n+\\n+\\n'\n" +
"+var patchLineList = [ false, false, false, false ];\n" + '+var patchLineList = [ false, false, false, false ];\n' +
"+\n" + '+\n' +
"+console.log(parser.parsePatchDiffResult(text, patchLineList));\n"; '+console.log(parser.parsePatchDiffResult(text, patchLineList));\n';
const result = parse(diff); const result = parse(diff);
expect(result).toMatchInlineSnapshot(` expect(result).toMatchInlineSnapshot(`
Array [ Array [
@ -526,32 +526,32 @@ describe("DiffParser", () => {
`); `);
}); });
it("should parse diff with multiple blocks", () => { it('should parse diff with multiple blocks', () => {
const diff = const diff =
"diff --git a/src/attributes/classes.js b/src/attributes/classes.js\n" + 'diff --git a/src/attributes/classes.js b/src/attributes/classes.js\n' +
"index c617824..c8d1393 100644\n" + 'index c617824..c8d1393 100644\n' +
"--- a/src/attributes/classes.js\n" + '--- a/src/attributes/classes.js\n' +
"+++ b/src/attributes/classes.js\n" + '+++ b/src/attributes/classes.js\n' +
"@@ -1,10 +1,9 @@\n" + '@@ -1,10 +1,9 @@\n' +
" define([\n" + ' define([\n' +
' "../core",\n' + ' "../core",\n' +
' "../var/rnotwhite",\n' + ' "../var/rnotwhite",\n' +
'- "../var/strundefined",\n' + '- "../var/strundefined",\n' +
' "../data/var/dataPriv",\n' + ' "../data/var/dataPriv",\n' +
' "../core/init"\n' + ' "../core/init"\n' +
"-], function( jQuery, rnotwhite, strundefined, dataPriv ) {\n" + '-], function( jQuery, rnotwhite, strundefined, dataPriv ) {\n' +
"+], function( jQuery, rnotwhite, dataPriv ) {\n" + '+], function( jQuery, rnotwhite, dataPriv ) {\n' +
" \n" + ' \n' +
" var rclass = /[\\t\\r\\n\\f]/g;\n" + ' var rclass = /[\\t\\r\\n\\f]/g;\n' +
" \n" + ' \n' +
"@@ -128,7 +127,7 @@ jQuery.fn.extend({\n" + '@@ -128,7 +127,7 @@ jQuery.fn.extend({\n' +
" }\n" + ' }\n' +
" \n" + ' \n' +
" // Toggle whole class name\n" + ' // Toggle whole class name\n' +
'- } else if ( type === strundefined || type === "boolean" ) {\n' + '- } else if ( type === strundefined || type === "boolean" ) {\n' +
'+ } else if ( value === undefined || type === "boolean" ) {\n' + '+ } else if ( value === undefined || type === "boolean" ) {\n' +
" if ( this.className ) {\n" + ' if ( this.className ) {\n' +
" // store className if set\n" + ' // store className if set\n' +
' dataPriv.set( this, "__className__", this.className );\n'; ' dataPriv.set( this, "__className__", this.className );\n';
const result = parse(diff); const result = parse(diff);
expect(result).toMatchInlineSnapshot(` expect(result).toMatchInlineSnapshot(`
@ -704,27 +704,27 @@ describe("DiffParser", () => {
`); `);
}); });
it("should parse diff with multiple files", () => { it('should parse diff with multiple files', () => {
const diff = const diff =
"diff --git a/src/core/init.js b/src/core/init.js\n" + 'diff --git a/src/core/init.js b/src/core/init.js\n' +
"index e49196a..50f310c 100644\n" + 'index e49196a..50f310c 100644\n' +
"--- a/src/core/init.js\n" + '--- a/src/core/init.js\n' +
"+++ b/src/core/init.js\n" + '+++ b/src/core/init.js\n' +
"@@ -101,7 +101,7 @@ var rootjQuery,\n" + '@@ -101,7 +101,7 @@ var rootjQuery,\n' +
" // HANDLE: $(function)\n" + ' // HANDLE: $(function)\n' +
" // Shortcut for document ready\n" + ' // Shortcut for document ready\n' +
" } else if ( jQuery.isFunction( selector ) ) {\n" + ' } else if ( jQuery.isFunction( selector ) ) {\n' +
'- return typeof rootjQuery.ready !== "undefined" ?\n' + '- return typeof rootjQuery.ready !== "undefined" ?\n' +
"+ return rootjQuery.ready !== undefined ?\n" + '+ return rootjQuery.ready !== undefined ?\n' +
" rootjQuery.ready( selector ) :\n" + ' rootjQuery.ready( selector ) :\n' +
" // Execute immediately if ready is not present\n" + ' // Execute immediately if ready is not present\n' +
" selector( jQuery );\n" + ' selector( jQuery );\n' +
"diff --git a/src/event.js b/src/event.js\n" + 'diff --git a/src/event.js b/src/event.js\n' +
"index 7336f4d..6183f70 100644\n" + 'index 7336f4d..6183f70 100644\n' +
"--- a/src/event.js\n" + '--- a/src/event.js\n' +
"+++ b/src/event.js\n" + '+++ b/src/event.js\n' +
"@@ -1,6 +1,5 @@\n" + '@@ -1,6 +1,5 @@\n' +
" define([\n" + ' define([\n' +
' "./core",\n' + ' "./core",\n' +
'- "./var/strundefined",\n' + '- "./var/strundefined",\n' +
' "./var/rnotwhite",\n' + ' "./var/rnotwhite",\n' +
@ -865,35 +865,35 @@ describe("DiffParser", () => {
`); `);
}); });
it("should parse combined diff", () => { it('should parse combined diff', () => {
const diff = const diff =
"diff --combined describe.c\n" + 'diff --combined describe.c\n' +
"index fabadb8,cc95eb0..4866510\n" + 'index fabadb8,cc95eb0..4866510\n' +
"--- a/describe.c\n" + '--- a/describe.c\n' +
"+++ b/describe.c\n" + '+++ b/describe.c\n' +
"@@@ -98,20 -98,12 +98,20 @@@\n" + '@@@ -98,20 -98,12 +98,20 @@@\n' +
" return (a_date > b_date) ? -1 : (a_date == b_date) ? 0 : 1;\n" + ' return (a_date > b_date) ? -1 : (a_date == b_date) ? 0 : 1;\n' +
" }\n" + ' }\n' +
" \n" + ' \n' +
"- static void describe(char *arg)\n" + '- static void describe(char *arg)\n' +
" -static void describe(struct commit *cmit, int last_one)\n" + ' -static void describe(struct commit *cmit, int last_one)\n' +
"++static void describe(char *arg, int last_one)\n" + '++static void describe(char *arg, int last_one)\n' +
" {\n" + ' {\n' +
" + unsigned char sha1[20];\n" + ' + unsigned char sha1[20];\n' +
" + struct commit *cmit;\n" + ' + struct commit *cmit;\n' +
" struct commit_list *list;\n" + ' struct commit_list *list;\n' +
" static int initialized = 0;\n" + ' static int initialized = 0;\n' +
" struct commit_name *n;\n" + ' struct commit_name *n;\n' +
" \n" + ' \n' +
" + if (get_sha1(arg, sha1) < 0)\n" + ' + if (get_sha1(arg, sha1) < 0)\n' +
" + usage(describe_usage);\n" + ' + usage(describe_usage);\n' +
" + cmit = lookup_commit_reference(sha1);\n" + ' + cmit = lookup_commit_reference(sha1);\n' +
" + if (!cmit)\n" + ' + if (!cmit)\n' +
" + usage(describe_usage);\n" + ' + usage(describe_usage);\n' +
" +\n" + ' +\n' +
" if (!initialized) {\n" + ' if (!initialized) {\n' +
" initialized = 1;\n" + ' initialized = 1;\n' +
" for_each_ref(get_name);\n"; ' for_each_ref(get_name);\n';
const result = parse(diff); const result = parse(diff);
expect(result).toMatchInlineSnapshot(` expect(result).toMatchInlineSnapshot(`
Array [ Array [
@ -1057,12 +1057,12 @@ describe("DiffParser", () => {
`); `);
}); });
it("should parse diffs with copied files", () => { it('should parse diffs with copied files', () => {
const diff = const diff =
"diff --git a/index.js b/more-index.js\n" + 'diff --git a/index.js b/more-index.js\n' +
"dissimilarity index 5%\n" + 'dissimilarity index 5%\n' +
"copy from index.js\n" + 'copy from index.js\n' +
"copy to more-index.js\n"; 'copy to more-index.js\n';
const result = parse(diff); const result = parse(diff);
expect(result).toMatchInlineSnapshot(` expect(result).toMatchInlineSnapshot(`
Array [ Array [
@ -1080,12 +1080,12 @@ describe("DiffParser", () => {
`); `);
}); });
it("should parse diffs with moved files", () => { it('should parse diffs with moved files', () => {
const diff = const diff =
"diff --git a/more-index.js b/other-index.js\n" + 'diff --git a/more-index.js b/other-index.js\n' +
"similarity index 86%\n" + 'similarity index 86%\n' +
"rename from more-index.js\n" + 'rename from more-index.js\n' +
"rename to other-index.js\n"; 'rename to other-index.js\n';
const result = parse(diff); const result = parse(diff);
expect(result).toMatchInlineSnapshot(` expect(result).toMatchInlineSnapshot(`
Array [ Array [
@ -1103,15 +1103,15 @@ describe("DiffParser", () => {
`); `);
}); });
it("should parse diffs correct line numbers", () => { it('should parse diffs correct line numbers', () => {
const diff = const diff =
"diff --git a/sample b/sample\n" + 'diff --git a/sample b/sample\n' +
"index 0000001..0ddf2ba\n" + 'index 0000001..0ddf2ba\n' +
"--- a/sample\n" + '--- a/sample\n' +
"+++ b/sample\n" + '+++ b/sample\n' +
"@@ -1 +1,2 @@\n" + '@@ -1 +1,2 @@\n' +
"-test\n" + '-test\n' +
"+test1r\n"; '+test1r\n';
const result = parse(diff); const result = parse(diff);
expect(result).toMatchInlineSnapshot(` expect(result).toMatchInlineSnapshot(`
Array [ Array [
@ -1152,23 +1152,23 @@ describe("DiffParser", () => {
`); `);
}); });
it("should parse unified non git diff and strip timestamps off the headers", () => { it('should parse unified non git diff and strip timestamps off the headers', () => {
const diffs = [ const diffs = [
// 2 hours ahead of GMT // 2 hours ahead of GMT
"--- a/sample.js 2016-10-25 11:37:14.000000000 +0200\n" + '--- a/sample.js 2016-10-25 11:37:14.000000000 +0200\n' +
"+++ b/sample.js 2016-10-25 11:37:14.000000000 +0200\n" + '+++ b/sample.js 2016-10-25 11:37:14.000000000 +0200\n' +
"@@ -1 +1,2 @@\n" + '@@ -1 +1,2 @@\n' +
"-test\n" + '-test\n' +
"+test1r\n" + '+test1r\n' +
"+test2r", '+test2r',
// 2 hours behind GMT // 2 hours behind GMT
"--- a/sample.js 2016-10-25 11:37:14.000000000 -0200\n" + '--- a/sample.js 2016-10-25 11:37:14.000000000 -0200\n' +
"+++ b/sample.js 2016-10-25 11:37:14.000000000 -0200\n" + '+++ b/sample.js 2016-10-25 11:37:14.000000000 -0200\n' +
"@@ -1 +1,2 @@\n" + '@@ -1 +1,2 @@\n' +
"-test\n" + '-test\n' +
"+test1r\n" + '+test1r\n' +
"+test2r" '+test2r',
].join("\n"); ].join('\n');
const result = parse(diffs); const result = parse(diffs);
expect(result).toMatchInlineSnapshot(` expect(result).toMatchInlineSnapshot(`
Array [ Array [
@ -1248,9 +1248,9 @@ describe("DiffParser", () => {
`); `);
}); });
it("should parse unified non git diff", () => { it('should parse unified non git diff', () => {
const diff = const diff =
"--- a/sample.js\n" + "+++ b/sample.js\n" + "@@ -1 +1,2 @@\n" + "-test\n" + "+test1r\n" + "+test2r\n"; '--- a/sample.js\n' + '+++ b/sample.js\n' + '@@ -1 +1,2 @@\n' + '-test\n' + '+test1r\n' + '+test2r\n';
const result = parse(diff); const result = parse(diff);
expect(result).toMatchInlineSnapshot(` expect(result).toMatchInlineSnapshot(`
Array [ Array [
@ -1294,18 +1294,18 @@ describe("DiffParser", () => {
`); `);
}); });
it("should parse unified diff with multiple hunks and files", () => { it('should parse unified diff with multiple hunks and files', () => {
const diff = const diff =
"--- sample.js\n" + '--- sample.js\n' +
"+++ sample.js\n" + '+++ sample.js\n' +
"@@ -1 +1,2 @@\n" + '@@ -1 +1,2 @@\n' +
"-test\n" + '-test\n' +
"@@ -10 +20,2 @@\n" + '@@ -10 +20,2 @@\n' +
"+test\n" + '+test\n' +
"--- sample1.js\n" + '--- sample1.js\n' +
"+++ sample1.js\n" + '+++ sample1.js\n' +
"@@ -1 +1,2 @@\n" + '@@ -1 +1,2 @@\n' +
"+test1"; '+test1';
const result = parse(diff); const result = parse(diff);
expect(result).toMatchInlineSnapshot(` expect(result).toMatchInlineSnapshot(`
Array [ Array [
@ -1375,20 +1375,20 @@ describe("DiffParser", () => {
`); `);
}); });
it("should parse diff with --- and +++ in the context lines", () => { it('should parse diff with --- and +++ in the context lines', () => {
const diff = const diff =
"--- sample.js\n" + '--- sample.js\n' +
"+++ sample.js\n" + '+++ sample.js\n' +
"@@ -1,8 +1,8 @@\n" + '@@ -1,8 +1,8 @@\n' +
" test\n" + ' test\n' +
" \n" + ' \n' +
"-- 1\n" + '-- 1\n' +
"--- 1\n" + '--- 1\n' +
"---- 1\n" + '---- 1\n' +
" \n" + ' \n' +
"++ 2\n" + '++ 2\n' +
"+++ 2\n" + '+++ 2\n' +
"++++ 2"; '++++ 2';
const result = parse(diff); const result = parse(diff);
expect(result).toMatchInlineSnapshot(` expect(result).toMatchInlineSnapshot(`
Array [ Array [
@ -1468,8 +1468,8 @@ describe("DiffParser", () => {
`); `);
}); });
it("should parse diff without proper hunk headers", () => { it('should parse diff without proper hunk headers', () => {
const diff = "--- sample.js\n" + "+++ sample.js\n" + "@@ @@\n" + " test"; const diff = '--- sample.js\n' + '+++ sample.js\n' + '@@ @@\n' + ' test';
const result = parse(diff); const result = parse(diff);
expect(result).toMatchInlineSnapshot(` expect(result).toMatchInlineSnapshot(`
Array [ Array [
@ -1501,13 +1501,13 @@ describe("DiffParser", () => {
`); `);
}); });
it("should parse binary file diff", () => { it('should parse binary file diff', () => {
const diff = const diff =
"diff --git a/last-changes-config.png b/last-changes-config.png\n" + 'diff --git a/last-changes-config.png b/last-changes-config.png\n' +
"index 322248b..56fc1f2 100644\n" + 'index 322248b..56fc1f2 100644\n' +
"--- a/last-changes-config.png\n" + '--- a/last-changes-config.png\n' +
"+++ b/last-changes-config.png\n" + '+++ b/last-changes-config.png\n' +
"Binary files differ"; 'Binary files differ';
const result = parse(diff); const result = parse(diff);
expect(result).toMatchInlineSnapshot(` expect(result).toMatchInlineSnapshot(`
Array [ Array [
@ -1536,21 +1536,21 @@ describe("DiffParser", () => {
`); `);
}); });
it("should parse diff with --find-renames", () => { it('should parse diff with --find-renames', () => {
const diff = const diff =
"diff --git a/src/test-bar.js b/src/test-baz.js\n" + 'diff --git a/src/test-bar.js b/src/test-baz.js\n' +
"similarity index 98%\n" + 'similarity index 98%\n' +
"rename from src/test-bar.js\n" + 'rename from src/test-bar.js\n' +
"rename to src/test-baz.js\n" + 'rename to src/test-baz.js\n' +
"index e01513b..f14a870 100644\n" + 'index e01513b..f14a870 100644\n' +
"--- a/src/test-bar.js\n" + '--- a/src/test-bar.js\n' +
"+++ b/src/test-baz.js\n" + '+++ b/src/test-baz.js\n' +
"@@ -1,4 +1,32 @@\n" + '@@ -1,4 +1,32 @@\n' +
" function foo() {\n" + ' function foo() {\n' +
'-var bar = "Whoops!";\n' + '-var bar = "Whoops!";\n' +
'+var baz = "Whoops!";\n' + '+var baz = "Whoops!";\n' +
" }\n" + ' }\n' +
" "; ' ';
const result = parse(diff); const result = parse(diff);
expect(result).toMatchInlineSnapshot(` expect(result).toMatchInlineSnapshot(`
Array [ Array [
@ -1612,49 +1612,49 @@ describe("DiffParser", () => {
`); `);
}); });
it("should parse diff with prefix 2", () => { it('should parse diff with prefix 2', () => {
const diff = const diff =
'diff --git "\tTest.scala" "\tScalaTest.scala"\n' + 'diff --git "\tTest.scala" "\tScalaTest.scala"\n' +
"similarity index 88%\n" + 'similarity index 88%\n' +
"rename from Test.scala\n" + 'rename from Test.scala\n' +
"rename to ScalaTest.scala\n" + 'rename to ScalaTest.scala\n' +
"index 7d1f9bf..8b13271 100644\n" + 'index 7d1f9bf..8b13271 100644\n' +
'--- "\tTest.scala"\n' + '--- "\tTest.scala"\n' +
'+++ "\tScalaTest.scala"\n' + '+++ "\tScalaTest.scala"\n' +
"@@ -1,6 +1,8 @@\n" + '@@ -1,6 +1,8 @@\n' +
" class Test {\n" + ' class Test {\n' +
" \n" + ' \n' +
" def method1 = ???\n" + ' def method1 = ???\n' +
"+\n" + '+\n' +
"+ def method2 = ???\n" + '+ def method2 = ???\n' +
" \n" + ' \n' +
" def myMethod = ???\n" + ' def myMethod = ???\n' +
" \n" + ' \n' +
"@@ -10,7 +12,6 @@ class Test {\n" + '@@ -10,7 +12,6 @@ class Test {\n' +
" \n" + ' \n' +
" def + = ???\n" + ' def + = ???\n' +
" \n" + ' \n' +
"- def |> = ???\n" + '- def |> = ???\n' +
" \n" + ' \n' +
" }\n" + ' }\n' +
" \n" + ' \n' +
'diff --git "\ttardis.png" "\ttardis.png"\n' + 'diff --git "\ttardis.png" "\ttardis.png"\n' +
"new file mode 100644\n" + 'new file mode 100644\n' +
"index 0000000..d503a29\n" + 'index 0000000..d503a29\n' +
'Binary files /dev/null and "\ttardis.png" differ\n' + 'Binary files /dev/null and "\ttardis.png" differ\n' +
"diff --git a/src/test-bar.js b/src/test-baz.js\n" + 'diff --git a/src/test-bar.js b/src/test-baz.js\n' +
"similarity index 98%\n" + 'similarity index 98%\n' +
"rename from src/test-bar.js\n" + 'rename from src/test-bar.js\n' +
"rename to src/test-baz.js\n" + 'rename to src/test-baz.js\n' +
"index e01513b..f14a870 100644\n" + 'index e01513b..f14a870 100644\n' +
"--- a/src/test-bar.js\n" + '--- a/src/test-bar.js\n' +
"+++ b/src/test-baz.js\n" + '+++ b/src/test-baz.js\n' +
"@@ -1,4 +1,32 @@\n" + '@@ -1,4 +1,32 @@\n' +
" function foo() {\n" + ' function foo() {\n' +
'-var bar = "Whoops!";\n' + '-var bar = "Whoops!";\n' +
'+var baz = "Whoops!";\n' + '+var baz = "Whoops!";\n' +
" }\n" + ' }\n' +
" "; ' ';
const result = parse(diff); const result = parse(diff);
expect(result).toMatchInlineSnapshot(` expect(result).toMatchInlineSnapshot(`
Array [ Array [
@ -1860,39 +1860,39 @@ describe("DiffParser", () => {
`); `);
}); });
it("should parse binary with content", () => { it('should parse binary with content', () => {
const diff = const diff =
"diff --git a/favicon.png b/favicon.png\n" + 'diff --git a/favicon.png b/favicon.png\n' +
"deleted file mode 100644\n" + 'deleted file mode 100644\n' +
"index 2a9d516a5647205d7be510dd0dff93a3663eff6f..0000000000000000000000000000000000000000\n" + 'index 2a9d516a5647205d7be510dd0dff93a3663eff6f..0000000000000000000000000000000000000000\n' +
"GIT binary patch\n" + 'GIT binary patch\n' +
"literal 0\n" + 'literal 0\n' +
"HcmV?d00001\n" + 'HcmV?d00001\n' +
"\n" + '\n' +
"literal 471\n" + 'literal 471\n' +
"zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`EX7WqAsj$Z!;#Vf<Z~8yL>4nJ\n" + 'zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`EX7WqAsj$Z!;#Vf<Z~8yL>4nJ\n' +
"za0`Jj<E6WGe}IBwC9V-A&PAz-C7Jno3L%-fsSJk3`UaNzMkcGzh!g=;$beJ?=ckpF\n" + 'za0`Jj<E6WGe}IBwC9V-A&PAz-C7Jno3L%-fsSJk3`UaNzMkcGzh!g=;$beJ?=ckpF\n' +
"zCl;kLIHu$$r7E~(7NwTw7iAYKI0u`(*t4mJfq_xq)5S5wqIc=!hrWj$cv|<b{x!c(\n" + 'zCl;kLIHu$$r7E~(7NwTw7iAYKI0u`(*t4mJfq_xq)5S5wqIc=!hrWj$cv|<b{x!c(\n' +
"z;3r#y;31Y&=1q>qPVOAS4ANVKzqmCp=Cty@U^(7zk!jHsvT~YI{F^=Ex6g|gox78w\n" + 'z;3r#y;31Y&=1q>qPVOAS4ANVKzqmCp=Cty@U^(7zk!jHsvT~YI{F^=Ex6g|gox78w\n' +
"z+Sn2Du3GS9U7qU`1*NYYlJi3u-!<?H-eky}wyIIL;8VU@wCDrb0``&v(jQ*DWSR4K\n" + 'z+Sn2Du3GS9U7qU`1*NYYlJi3u-!<?H-eky}wyIIL;8VU@wCDrb0``&v(jQ*DWSR4K\n' +
"zPq(3;isEyho{emNa=%%!jDPE`l3u;5d=q=<+v8kO-=C`*G#t-*AiE-D>-_B#8k9H0\n" + 'zPq(3;isEyho{emNa=%%!jDPE`l3u;5d=q=<+v8kO-=C`*G#t-*AiE-D>-_B#8k9H0\n' +
"zGl{FnZs<2$wz5^=Q2h-1XI^s{LQL1#T4epqNPC%Orl(tD_@!*EY++~^Lt2<2&!&%=\n" + 'zGl{FnZs<2$wz5^=Q2h-1XI^s{LQL1#T4epqNPC%Orl(tD_@!*EY++~^Lt2<2&!&%=\n' +
"z`m>(TYj6uS7jDdt=eH>iOyQg(QMR<-Fw8)Dk^ZG)XQTuzEgl{`GpS?Cfq9818R9~=\n" + 'z`m>(TYj6uS7jDdt=eH>iOyQg(QMR<-Fw8)Dk^ZG)XQTuzEgl{`GpS?Cfq9818R9~=\n' +
"z{&h9@9n8F^?|qusoPy{k#%tVHzu7H$t26CR`BJZk*Ixf&u36WuS=?6m2^ho-p00i_\n" + 'z{&h9@9n8F^?|qusoPy{k#%tVHzu7H$t26CR`BJZk*Ixf&u36WuS=?6m2^ho-p00i_\n' +
"I>zopr0Nz-&lmGw#\n" + 'I>zopr0Nz-&lmGw#\n' +
"diff --git a/src/test-bar.js b/src/test-baz.js\n" + 'diff --git a/src/test-bar.js b/src/test-baz.js\n' +
"similarity index 98%\n" + 'similarity index 98%\n' +
"rename from src/test-bar.js\n" + 'rename from src/test-bar.js\n' +
"rename to src/test-baz.js\n" + 'rename to src/test-baz.js\n' +
"index e01513b..f14a870 100644\n" + 'index e01513b..f14a870 100644\n' +
"--- a/src/test-bar.js\n" + '--- a/src/test-bar.js\n' +
"+++ b/src/test-baz.js\n" + '+++ b/src/test-baz.js\n' +
"@@ -1,4 +1,32 @@\n" + '@@ -1,4 +1,32 @@\n' +
" function foo() {\n" + ' function foo() {\n' +
'-var bar = "Whoops!";\n' + '-var bar = "Whoops!";\n' +
'+var baz = "Whoops!";\n' + '+var baz = "Whoops!";\n' +
" }\n" + ' }\n' +
" "; ' ';
const result = parse(diff); const result = parse(diff);
expect(result).toMatchInlineSnapshot(` expect(result).toMatchInlineSnapshot(`
Array [ Array [

View file

@ -1,14 +1,14 @@
import { parse, html } from "../diff2html"; import { parse, html } from '../diff2html';
import { DiffFile, LineType, OutputFormatType } from "../types"; import { DiffFile, LineType, OutputFormatType } from '../types';
const diffExample1 = const diffExample1 =
"diff --git a/sample b/sample\n" + 'diff --git a/sample b/sample\n' +
"index 0000001..0ddf2ba\n" + 'index 0000001..0ddf2ba\n' +
"--- a/sample\n" + '--- a/sample\n' +
"+++ b/sample\n" + '+++ b/sample\n' +
"@@ -1 +1 @@\n" + '@@ -1 +1 @@\n' +
"-test\n" + '-test\n' +
"+test1\n"; '+test1\n';
const jsonExample1: DiffFile[] = [ const jsonExample1: DiffFile[] = [
{ {
@ -16,47 +16,47 @@ const jsonExample1: DiffFile[] = [
{ {
lines: [ lines: [
{ {
content: "-test", content: '-test',
type: LineType.DELETE, type: LineType.DELETE,
oldNumber: 1, oldNumber: 1,
newNumber: undefined newNumber: undefined,
}, },
{ {
content: "+test1", content: '+test1',
type: LineType.INSERT, type: LineType.INSERT,
oldNumber: undefined, oldNumber: undefined,
newNumber: 1 newNumber: 1,
} },
], ],
oldStartLine: 1, oldStartLine: 1,
oldStartLine2: undefined, oldStartLine2: undefined,
newStartLine: 1, newStartLine: 1,
header: "@@ -1 +1 @@" header: '@@ -1 +1 @@',
} },
], ],
deletedLines: 1, deletedLines: 1,
addedLines: 1, addedLines: 1,
checksumBefore: "0000001", checksumBefore: '0000001',
checksumAfter: "0ddf2ba", checksumAfter: '0ddf2ba',
oldName: "sample", oldName: 'sample',
newName: "sample", newName: 'sample',
language: "", language: '',
isCombined: false, isCombined: false,
isGitDiff: true isGitDiff: true,
} },
]; ];
describe("Diff2Html", () => { describe('Diff2Html', () => {
describe("getJsonFromDiff", () => { describe('getJsonFromDiff', () => {
it("should parse simple diff to json", () => { it('should parse simple diff to json', () => {
const diff = const diff =
"diff --git a/sample b/sample\n" + 'diff --git a/sample b/sample\n' +
"index 0000001..0ddf2ba\n" + 'index 0000001..0ddf2ba\n' +
"--- a/sample\n" + '--- a/sample\n' +
"+++ b/sample\n" + '+++ b/sample\n' +
"@@ -1 +1 @@\n" + '@@ -1 +1 @@\n' +
"-test\n" + '-test\n' +
"+test1\n"; '+test1\n';
const result = parse(diff); const result = parse(diff);
expect(result).toMatchInlineSnapshot(` expect(result).toMatchInlineSnapshot(`
Array [ Array [
@ -98,23 +98,23 @@ describe("Diff2Html", () => {
}); });
// Test case for issue #49 // Test case for issue #49
it("should parse diff with added EOF", () => { it('should parse diff with added EOF', () => {
const diff = const diff =
"diff --git a/sample.scala b/sample.scala\n" + 'diff --git a/sample.scala b/sample.scala\n' +
"index b583263..8b2fc3e 100644\n" + 'index b583263..8b2fc3e 100644\n' +
"--- a/b583263..8b2fc3e\n" + '--- a/b583263..8b2fc3e\n' +
"+++ b/8b2fc3e\n" + '+++ b/8b2fc3e\n' +
"@@ -50,5 +50,7 @@ case class Response[+A](value: Option[A],\n" + '@@ -50,5 +50,7 @@ case class Response[+A](value: Option[A],\n' +
" object ResponseErrorCode extends JsonEnumeration {\n" + ' object ResponseErrorCode extends JsonEnumeration {\n' +
" val NoError, ServiceError, JsonError,\n" + ' val NoError, ServiceError, JsonError,\n' +
" InvalidPermissions, MissingPermissions, GenericError,\n" + ' InvalidPermissions, MissingPermissions, GenericError,\n' +
"- TokenRevoked, MissingToken = Value\n" + '- TokenRevoked, MissingToken = Value\n' +
"-}\n" + '-}\n' +
"\\ No newline at end of file\n" + '\\ No newline at end of file\n' +
"+ TokenRevoked, MissingToken,\n" + '+ TokenRevoked, MissingToken,\n' +
"+ IndexLock, RepositoryError, NotValidRepo, PullRequestNotMergeable, BranchError,\n" + '+ IndexLock, RepositoryError, NotValidRepo, PullRequestNotMergeable, BranchError,\n' +
"+ PluginError, CodeParserError, EngineError = Value\n" + '+ PluginError, CodeParserError, EngineError = Value\n' +
"+}\n"; '+}\n';
const result = parse(diff); const result = parse(diff);
expect(result).toMatchInlineSnapshot(` expect(result).toMatchInlineSnapshot(`
Array [ Array [
@ -198,7 +198,7 @@ describe("Diff2Html", () => {
`); `);
}); });
it("should generate pretty line by line html from diff", () => { it('should generate pretty line by line html from diff', () => {
const result = html(diffExample1, { drawFileList: false }); const result = html(diffExample1, { drawFileList: false });
expect(result).toMatchInlineSnapshot(` expect(result).toMatchInlineSnapshot(`
"<div class=\\"d2h-wrapper\\"> "<div class=\\"d2h-wrapper\\">
@ -251,7 +251,7 @@ describe("Diff2Html", () => {
`); `);
}); });
it("should generate pretty line by line html from json", () => { it('should generate pretty line by line html from json', () => {
const result = html(jsonExample1, { drawFileList: false }); const result = html(jsonExample1, { drawFileList: false });
expect(result).toMatchInlineSnapshot(` expect(result).toMatchInlineSnapshot(`
"<div class=\\"d2h-wrapper\\"> "<div class=\\"d2h-wrapper\\">
@ -304,7 +304,7 @@ describe("Diff2Html", () => {
`); `);
}); });
it("should generate pretty diff with files summary", () => { it('should generate pretty diff with files summary', () => {
const result = html(diffExample1, { drawFileList: true }); const result = html(diffExample1, { drawFileList: true });
expect(result).toMatchInlineSnapshot(` expect(result).toMatchInlineSnapshot(`
"<div class=\\"d2h-file-list-wrapper\\"> "<div class=\\"d2h-file-list-wrapper\\">
@ -377,7 +377,7 @@ describe("Diff2Html", () => {
`); `);
}); });
it("should generate pretty side by side html from diff", () => { it('should generate pretty side by side html from diff', () => {
const result = html(diffExample1, { outputFormat: OutputFormatType.SIDE_BY_SIDE, drawFileList: false }); const result = html(diffExample1, { outputFormat: OutputFormatType.SIDE_BY_SIDE, drawFileList: false });
expect(result).toMatchInlineSnapshot(` expect(result).toMatchInlineSnapshot(`
"<div class=\\"d2h-wrapper\\"> "<div class=\\"d2h-wrapper\\">
@ -444,7 +444,7 @@ describe("Diff2Html", () => {
`); `);
}); });
it("should generate pretty side by side html from json", () => { it('should generate pretty side by side html from json', () => {
const result = html(jsonExample1, { outputFormat: OutputFormatType.SIDE_BY_SIDE, drawFileList: false }); const result = html(jsonExample1, { outputFormat: OutputFormatType.SIDE_BY_SIDE, drawFileList: false });
expect(result).toMatchInlineSnapshot(` expect(result).toMatchInlineSnapshot(`
"<div class=\\"d2h-wrapper\\"> "<div class=\\"d2h-wrapper\\">
@ -511,7 +511,7 @@ describe("Diff2Html", () => {
`); `);
}); });
it("should generate pretty side by side html from diff 2", () => { it('should generate pretty side by side html from diff 2', () => {
const result = html(diffExample1, { outputFormat: OutputFormatType.SIDE_BY_SIDE, drawFileList: true }); const result = html(diffExample1, { outputFormat: OutputFormatType.SIDE_BY_SIDE, drawFileList: true });
expect(result).toMatchInlineSnapshot(` expect(result).toMatchInlineSnapshot(`
"<div class=\\"d2h-file-list-wrapper\\"> "<div class=\\"d2h-file-list-wrapper\\">
@ -598,30 +598,30 @@ describe("Diff2Html", () => {
`); `);
}); });
it("should generate pretty side by side html from diff with html on headers", () => { it('should generate pretty side by side html from diff with html on headers', () => {
const diffExample2 = const diffExample2 =
"diff --git a/CHANGELOG.md b/CHANGELOG.md\n" + 'diff --git a/CHANGELOG.md b/CHANGELOG.md\n' +
"index fc3e3f4..b486d10 100644\n" + 'index fc3e3f4..b486d10 100644\n' +
"--- a/CHANGELOG.md\n" + '--- a/CHANGELOG.md\n' +
"+++ b/CHANGELOG.md\n" + '+++ b/CHANGELOG.md\n' +
"@@ -1,7 +1,6 @@\n" + '@@ -1,7 +1,6 @@\n' +
" # Change Log\n" + ' # Change Log\n' +
" All notable changes to this project will be documented in this file.\n" + ' All notable changes to this project will be documented in this file.\n' +
" This project adheres to [Semantic Versioning](http://semver.org/).\n" + ' This project adheres to [Semantic Versioning](http://semver.org/).\n' +
'-$a="<table><tr><td>Use the following format for additions: ` - VERSION: [feature/patch (if applicable)] Short description of change. Links to relevant issues/PRs.`\n' + '-$a="<table><tr><td>Use the following format for additions: ` - VERSION: [feature/patch (if applicable)] Short description of change. Links to relevant issues/PRs.`\n' +
' $a="<table><tr><td>\n' + ' $a="<table><tr><td>\n' +
" $a=\"<table><tr><td>- 1.1.9: Fix around ubuntu's inability to cache promises. [#877](https://github.com/FredrikNoren/ungit/pull/878)\n" + ' $a="<table><tr><td>- 1.1.9: Fix around ubuntu\'s inability to cache promises. [#877](https://github.com/FredrikNoren/ungit/pull/878)\n' +
" - 1.1.8:\n" + ' - 1.1.8:\n' +
"@@ -11,7 +10,7 @@ $a=&quot;&lt;table&gt;&lt;tr&gt;&lt;td&gt;- 1.1.9: Fix around ubuntu&#x27;s inability to cache promises. [#8\n" + '@@ -11,7 +10,7 @@ $a=&quot;&lt;table&gt;&lt;tr&gt;&lt;td&gt;- 1.1.9: Fix around ubuntu&#x27;s inability to cache promises. [#8\n' +
" - 1.1.7:\n" + ' - 1.1.7:\n' +
" - Fix diff flickering issue and optimization [#865](https://github.com/FredrikNoren/ungit/pull/865)\n" + ' - Fix diff flickering issue and optimization [#865](https://github.com/FredrikNoren/ungit/pull/865)\n' +
" - Fix credential dialog issue [#864](https://github.com/FredrikNoren/ungit/pull/864)\n" + ' - Fix credential dialog issue [#864](https://github.com/FredrikNoren/ungit/pull/864)\n' +
"- - Fix HEAD branch order when redraw [#858](https://github.com/FredrikNoren/ungit/issues/858)\n" + '- - Fix HEAD branch order when redraw [#858](https://github.com/FredrikNoren/ungit/issues/858)\n' +
"+4 - Fix HEAD branch order when redraw [#858](https://github.com/FredrikNoren/ungit/issues/858)\n" + '+4 - Fix HEAD branch order when redraw [#858](https://github.com/FredrikNoren/ungit/issues/858)\n' +
" - 1.1.6: Fix path auto complete [#861](https://github.com/FredrikNoren/ungit/issues/861)\n" + ' - 1.1.6: Fix path auto complete [#861](https://github.com/FredrikNoren/ungit/issues/861)\n' +
' - 1.1.5: Update "Toggle all" button after commit or changing selected files [#859](https://github.com/FredrikNoren/ungit/issues/859)\n' + ' - 1.1.5: Update "Toggle all" button after commit or changing selected files [#859](https://github.com/FredrikNoren/ungit/issues/859)\n' +
" - 1.1.4: [patch] Promise refactoring\n" + ' - 1.1.4: [patch] Promise refactoring\n' +
" \n"; ' \n';
const result = html(diffExample2, { drawFileList: false }); const result = html(diffExample2, { drawFileList: false });
expect(result).toMatchInlineSnapshot(` expect(result).toMatchInlineSnapshot(`
"<div class=\\"d2h-wrapper\\"> "<div class=\\"d2h-wrapper\\">

View file

@ -1,14 +1,14 @@
import { render } from "../file-list-renderer"; import { render } from '../file-list-renderer';
import HoganJsUtils from "../hoganjs-utils"; import HoganJsUtils from '../hoganjs-utils';
describe("FileListPrinter", () => { describe('FileListPrinter', () => {
describe("generateFileList", () => { describe('generateFileList', () => {
it("should expose old and new files to templates", () => { it('should expose old and new files to templates', () => {
const hoganUtils = new HoganJsUtils({ const hoganUtils = new HoganJsUtils({
rawTemplates: { rawTemplates: {
"file-summary-wrapper": "{{{files}}}", 'file-summary-wrapper': '{{{files}}}',
"file-summary-line": "{{oldName}}, {{newName}}, {{fileName}}" 'file-summary-line': '{{oldName}}, {{newName}}, {{fileName}}',
} },
}); });
const files = [ const files = [
{ {
@ -17,9 +17,9 @@ describe("FileListPrinter", () => {
blocks: [], blocks: [],
addedLines: 12, addedLines: 12,
deletedLines: 41, deletedLines: 41,
language: "js", language: 'js',
oldName: "my/file/name.js", oldName: 'my/file/name.js',
newName: "my/file/name.js" newName: 'my/file/name.js',
}, },
{ {
isCombined: false, isCombined: false,
@ -27,9 +27,9 @@ describe("FileListPrinter", () => {
blocks: [], blocks: [],
addedLines: 12, addedLines: 12,
deletedLines: 41, deletedLines: 41,
language: "js", language: 'js',
oldName: "my/file/name1.js", oldName: 'my/file/name1.js',
newName: "my/file/name2.js" newName: 'my/file/name2.js',
}, },
{ {
isCombined: false, isCombined: false,
@ -37,10 +37,10 @@ describe("FileListPrinter", () => {
blocks: [], blocks: [],
addedLines: 12, addedLines: 12,
deletedLines: 0, deletedLines: 0,
language: "js", language: 'js',
oldName: "dev/null", oldName: 'dev/null',
newName: "my/file/name.js", newName: 'my/file/name.js',
isNew: true isNew: true,
}, },
{ {
isCombined: false, isCombined: false,
@ -48,11 +48,11 @@ describe("FileListPrinter", () => {
blocks: [], blocks: [],
addedLines: 0, addedLines: 0,
deletedLines: 41, deletedLines: 41,
language: "js", language: 'js',
oldName: "my/file/name.js", oldName: 'my/file/name.js',
newName: "dev/null", newName: 'dev/null',
isDeleted: true isDeleted: true,
} },
]; ];
const fileHtml = render(files, hoganUtils); const fileHtml = render(files, hoganUtils);
@ -65,7 +65,7 @@ describe("FileListPrinter", () => {
`); `);
}); });
it("should work for all kinds of files", () => { it('should work for all kinds of files', () => {
const hoganUtils = new HoganJsUtils({}); const hoganUtils = new HoganJsUtils({});
const files = [ const files = [
{ {
@ -74,9 +74,9 @@ describe("FileListPrinter", () => {
blocks: [], blocks: [],
addedLines: 12, addedLines: 12,
deletedLines: 41, deletedLines: 41,
language: "js", language: 'js',
oldName: "my/file/name.js", oldName: 'my/file/name.js',
newName: "my/file/name.js" newName: 'my/file/name.js',
}, },
{ {
isCombined: false, isCombined: false,
@ -84,9 +84,9 @@ describe("FileListPrinter", () => {
blocks: [], blocks: [],
addedLines: 12, addedLines: 12,
deletedLines: 41, deletedLines: 41,
language: "js", language: 'js',
oldName: "my/file/name1.js", oldName: 'my/file/name1.js',
newName: "my/file/name2.js" newName: 'my/file/name2.js',
}, },
{ {
isCombined: false, isCombined: false,
@ -94,10 +94,10 @@ describe("FileListPrinter", () => {
blocks: [], blocks: [],
addedLines: 12, addedLines: 12,
deletedLines: 0, deletedLines: 0,
language: "js", language: 'js',
oldName: "dev/null", oldName: 'dev/null',
newName: "my/file/name.js", newName: 'my/file/name.js',
isNew: true isNew: true,
}, },
{ {
isCombined: false, isCombined: false,
@ -105,11 +105,11 @@ describe("FileListPrinter", () => {
blocks: [], blocks: [],
addedLines: 0, addedLines: 0,
deletedLines: 41, deletedLines: 41,
language: "js", language: 'js',
oldName: "my/file/name.js", oldName: 'my/file/name.js',
newName: "dev/null", newName: 'dev/null',
isDeleted: true isDeleted: true,
} },
]; ];
const fileHtml = render(files, hoganUtils); const fileHtml = render(files, hoganUtils);
expect(fileHtml).toMatchInlineSnapshot(` expect(fileHtml).toMatchInlineSnapshot(`

View file

@ -1,13 +1,13 @@
import HoganJsUtils from "../hoganjs-utils"; import HoganJsUtils from '../hoganjs-utils';
import { CSSLineClass } from "../render-utils"; import { CSSLineClass } from '../render-utils';
describe("HoganJsUtils", () => { describe('HoganJsUtils', () => {
describe("render", () => { describe('render', () => {
it("should render view", () => { it('should render view', () => {
const hoganJsUtils = new HoganJsUtils({}); const hoganJsUtils = new HoganJsUtils({});
const result = hoganJsUtils.render("generic", "empty-diff", { const result = hoganJsUtils.render('generic', 'empty-diff', {
contentClass: "d2h-code-line", contentClass: 'd2h-code-line',
CSSLineClass: CSSLineClass CSSLineClass: CSSLineClass,
}); });
expect(result).toMatchInlineSnapshot(` expect(result).toMatchInlineSnapshot(`
"<tr> "<tr>
@ -20,53 +20,36 @@ describe("HoganJsUtils", () => {
`); `);
}); });
it("should render view without cache", () => { it('should throw exception if template is missing', () => {
const hoganJsUtils = new HoganJsUtils({}); const hoganJsUtils = new HoganJsUtils({});
const result = hoganJsUtils.render("generic", "empty-diff", { expect(() => hoganJsUtils.render('generic', 'missing-template', {})).toThrow(Error);
contentClass: "d2h-code-line",
CSSLineClass: CSSLineClass
});
expect(result).toMatchInlineSnapshot(`
"<tr>
<td class=\\"d2h-info\\">
<div class=\\"d2h-code-line d2h-info\\">
File without changes
</div>
</td>
</tr>"
`);
}); });
it("should throw exception if template is missing", () => { it('should allow templates to be overridden with compiled templates', () => {
const hoganJsUtils = new HoganJsUtils({}); const emptyDiffTemplate = HoganJsUtils.compile('<p>{{myName}}</p>');
expect(() => hoganJsUtils.render("generic", "missing-template", {})).toThrow(Error); const hoganJsUtils = new HoganJsUtils({ compiledTemplates: { 'generic-empty-diff': emptyDiffTemplate } });
});
it("should allow templates to be overridden with compiled templates", () => { const result = hoganJsUtils.render('generic', 'empty-diff', { myName: 'Rodrigo Fernandes' });
const emptyDiffTemplate = HoganJsUtils.compile("<p>{{myName}}</p>");
const hoganJsUtils = new HoganJsUtils({ compiledTemplates: { "generic-empty-diff": emptyDiffTemplate } });
const result = hoganJsUtils.render("generic", "empty-diff", { myName: "Rodrigo Fernandes" });
expect(result).toMatchInlineSnapshot(`"<p>Rodrigo Fernandes</p>"`); expect(result).toMatchInlineSnapshot(`"<p>Rodrigo Fernandes</p>"`);
}); });
it("should allow templates to be overridden with uncompiled templates", () => { it('should allow templates to be overridden with uncompiled templates', () => {
const emptyDiffTemplate = "<p>{{myName}}</p>"; const emptyDiffTemplate = '<p>{{myName}}</p>';
const hoganJsUtils = new HoganJsUtils({ rawTemplates: { "generic-empty-diff": emptyDiffTemplate } }); const hoganJsUtils = new HoganJsUtils({ rawTemplates: { 'generic-empty-diff': emptyDiffTemplate } });
const result = hoganJsUtils.render("generic", "empty-diff", { myName: "Rodrigo Fernandes" }); const result = hoganJsUtils.render('generic', 'empty-diff', { myName: 'Rodrigo Fernandes' });
expect(result).toMatchInlineSnapshot(`"<p>Rodrigo Fernandes</p>"`); expect(result).toMatchInlineSnapshot(`"<p>Rodrigo Fernandes</p>"`);
}); });
it("should allow templates to be overridden giving priority to raw templates", () => { it('should allow templates to be overridden giving priority to raw templates', () => {
const emptyDiffTemplate = HoganJsUtils.compile("<p>Not used!</p>"); const emptyDiffTemplate = HoganJsUtils.compile('<p>Not used!</p>');
const emptyDiffTemplateUncompiled = "<p>{{myName}}</p>"; const emptyDiffTemplateUncompiled = '<p>{{myName}}</p>';
const hoganJsUtils = new HoganJsUtils({ const hoganJsUtils = new HoganJsUtils({
compiledTemplates: { "generic-empty-diff": emptyDiffTemplate }, compiledTemplates: { 'generic-empty-diff': emptyDiffTemplate },
rawTemplates: { "generic-empty-diff": emptyDiffTemplateUncompiled } rawTemplates: { 'generic-empty-diff': emptyDiffTemplateUncompiled },
}); });
const result = hoganJsUtils.render("generic", "empty-diff", { myName: "Rodrigo Fernandes" }); const result = hoganJsUtils.render('generic', 'empty-diff', { myName: 'Rodrigo Fernandes' });
expect(result).toMatchInlineSnapshot(`"<p>Rodrigo Fernandes</p>"`); expect(result).toMatchInlineSnapshot(`"<p>Rodrigo Fernandes</p>"`);
}); });
}); });

View file

@ -1,11 +1,11 @@
import LineByLineRenderer from "../line-by-line-renderer"; import LineByLineRenderer from '../line-by-line-renderer';
import HoganJsUtils from "../hoganjs-utils"; import HoganJsUtils from '../hoganjs-utils';
import { LineType, DiffFile, LineMatchingType } from "../types"; import { LineType, DiffFile, LineMatchingType } from '../types';
import { CSSLineClass } from "../render-utils"; import { CSSLineClass } from '../render-utils';
describe("LineByLineRenderer", () => { describe('LineByLineRenderer', () => {
describe("_generateEmptyDiff", () => { describe('_generateEmptyDiff', () => {
it("should return an empty diff", () => { it('should return an empty diff', () => {
const hoganUtils = new HoganJsUtils({}); const hoganUtils = new HoganJsUtils({});
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {}); const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
const fileHtml = lineByLineRenderer.generateEmptyDiff(); const fileHtml = lineByLineRenderer.generateEmptyDiff();
@ -21,16 +21,16 @@ describe("LineByLineRenderer", () => {
}); });
}); });
describe("makeLineHtml", () => { describe('makeLineHtml', () => {
it("should work for insertions", () => { it('should work for insertions', () => {
const hoganUtils = new HoganJsUtils({}); const hoganUtils = new HoganJsUtils({});
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {}); const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
const fileHtml = lineByLineRenderer.generateSingleLineHtml({ const fileHtml = lineByLineRenderer.generateSingleLineHtml({
type: CSSLineClass.INSERTS, type: CSSLineClass.INSERTS,
prefix: "+", prefix: '+',
content: "test", content: 'test',
oldNumber: undefined, oldNumber: undefined,
newNumber: 30 newNumber: 30,
}); });
expect(fileHtml).toMatchInlineSnapshot(` expect(fileHtml).toMatchInlineSnapshot(`
"<tr> "<tr>
@ -48,15 +48,15 @@ describe("LineByLineRenderer", () => {
`); `);
}); });
it("should work for deletions", () => { it('should work for deletions', () => {
const hoganUtils = new HoganJsUtils({}); const hoganUtils = new HoganJsUtils({});
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {}); const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
const fileHtml = lineByLineRenderer.generateSingleLineHtml({ const fileHtml = lineByLineRenderer.generateSingleLineHtml({
type: CSSLineClass.DELETES, type: CSSLineClass.DELETES,
prefix: "-", prefix: '-',
content: "test", content: 'test',
oldNumber: 30, oldNumber: 30,
newNumber: undefined newNumber: undefined,
}); });
expect(fileHtml).toMatchInlineSnapshot(` expect(fileHtml).toMatchInlineSnapshot(`
"<tr> "<tr>
@ -74,15 +74,15 @@ describe("LineByLineRenderer", () => {
`); `);
}); });
it("should convert indents into non breakin spaces (2 white spaces)", () => { it('should convert indents into non breakin spaces (2 white spaces)', () => {
const hoganUtils = new HoganJsUtils({}); const hoganUtils = new HoganJsUtils({});
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {}); const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
const fileHtml = lineByLineRenderer.generateSingleLineHtml({ const fileHtml = lineByLineRenderer.generateSingleLineHtml({
type: CSSLineClass.INSERTS, type: CSSLineClass.INSERTS,
prefix: "+", prefix: '+',
content: " test", content: ' test',
oldNumber: undefined, oldNumber: undefined,
newNumber: 30 newNumber: 30,
}); });
expect(fileHtml).toMatchInlineSnapshot(` expect(fileHtml).toMatchInlineSnapshot(`
"<tr> "<tr>
@ -100,15 +100,15 @@ describe("LineByLineRenderer", () => {
`); `);
}); });
it("should convert indents into non breakin spaces (4 white spaces)", () => { it('should convert indents into non breakin spaces (4 white spaces)', () => {
const hoganUtils = new HoganJsUtils({}); const hoganUtils = new HoganJsUtils({});
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {}); const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
const fileHtml = lineByLineRenderer.generateSingleLineHtml({ const fileHtml = lineByLineRenderer.generateSingleLineHtml({
type: CSSLineClass.INSERTS, type: CSSLineClass.INSERTS,
prefix: "+", prefix: '+',
content: " test", content: ' test',
oldNumber: undefined, oldNumber: undefined,
newNumber: 30 newNumber: 30,
}); });
expect(fileHtml).toMatchInlineSnapshot(` expect(fileHtml).toMatchInlineSnapshot(`
"<tr> "<tr>
@ -126,15 +126,15 @@ describe("LineByLineRenderer", () => {
`); `);
}); });
it("should preserve tabs", () => { it('should preserve tabs', () => {
const hoganUtils = new HoganJsUtils({}); const hoganUtils = new HoganJsUtils({});
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {}); const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
const fileHtml = lineByLineRenderer.generateSingleLineHtml({ const fileHtml = lineByLineRenderer.generateSingleLineHtml({
type: CSSLineClass.INSERTS, type: CSSLineClass.INSERTS,
prefix: "+", prefix: '+',
content: "\ttest", content: '\ttest',
oldNumber: undefined, oldNumber: undefined,
newNumber: 30 newNumber: 30,
}); });
expect(fileHtml).toMatchInlineSnapshot(` expect(fileHtml).toMatchInlineSnapshot(`
"<tr> "<tr>
@ -153,22 +153,22 @@ describe("LineByLineRenderer", () => {
}); });
}); });
describe("makeFileDiffHtml", () => { describe('makeFileDiffHtml', () => {
it("should work for simple file", () => { it('should work for simple file', () => {
const hoganUtils = new HoganJsUtils({}); const hoganUtils = new HoganJsUtils({});
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {}); const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
const file = { const file = {
addedLines: 12, addedLines: 12,
deletedLines: 41, deletedLines: 41,
language: "js", language: 'js',
oldName: "my/file/name.js", oldName: 'my/file/name.js',
newName: "my/file/name.js", newName: 'my/file/name.js',
isCombined: false, isCombined: false,
isGitDiff: false, isGitDiff: false,
blocks: [] blocks: [],
}; };
const diffs = "<span>Random Html</span>"; const diffs = '<span>Random Html</span>';
const fileHtml = lineByLineRenderer.makeFileDiffHtml(file, diffs); const fileHtml = lineByLineRenderer.makeFileDiffHtml(file, diffs);
@ -193,22 +193,22 @@ describe("LineByLineRenderer", () => {
</div>" </div>"
`); `);
}); });
it("should work for simple added file", () => { it('should work for simple added file', () => {
const hoganUtils = new HoganJsUtils({}); const hoganUtils = new HoganJsUtils({});
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {}); const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
const file = { const file = {
addedLines: 12, addedLines: 12,
deletedLines: 0, deletedLines: 0,
language: "js", language: 'js',
oldName: "dev/null", oldName: 'dev/null',
newName: "my/file/name.js", newName: 'my/file/name.js',
isNew: true, isNew: true,
isCombined: false, isCombined: false,
isGitDiff: false, isGitDiff: false,
blocks: [] blocks: [],
}; };
const diffs = "<span>Random Html</span>"; const diffs = '<span>Random Html</span>';
const fileHtml = lineByLineRenderer.makeFileDiffHtml(file, diffs); const fileHtml = lineByLineRenderer.makeFileDiffHtml(file, diffs);
@ -233,22 +233,22 @@ describe("LineByLineRenderer", () => {
</div>" </div>"
`); `);
}); });
it("should work for simple deleted file", () => { it('should work for simple deleted file', () => {
const hoganUtils = new HoganJsUtils({}); const hoganUtils = new HoganJsUtils({});
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {}); const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
const file = { const file = {
addedLines: 0, addedLines: 0,
deletedLines: 41, deletedLines: 41,
language: "js", language: 'js',
oldName: "my/file/name.js", oldName: 'my/file/name.js',
newName: "dev/null", newName: 'dev/null',
isDeleted: true, isDeleted: true,
isCombined: false, isCombined: false,
isGitDiff: false, isGitDiff: false,
blocks: [] blocks: [],
}; };
const diffs = "<span>Random Html</span>"; const diffs = '<span>Random Html</span>';
const fileHtml = lineByLineRenderer.makeFileDiffHtml(file, diffs); const fileHtml = lineByLineRenderer.makeFileDiffHtml(file, diffs);
@ -273,22 +273,22 @@ describe("LineByLineRenderer", () => {
</div>" </div>"
`); `);
}); });
it("should work for simple renamed file", () => { it('should work for simple renamed file', () => {
const hoganUtils = new HoganJsUtils({}); const hoganUtils = new HoganJsUtils({});
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {}); const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
const file = { const file = {
addedLines: 12, addedLines: 12,
deletedLines: 41, deletedLines: 41,
language: "js", language: 'js',
oldName: "my/file/name1.js", oldName: 'my/file/name1.js',
newName: "my/file/name2.js", newName: 'my/file/name2.js',
isRename: true, isRename: true,
isCombined: false, isCombined: false,
isGitDiff: false, isGitDiff: false,
blocks: [] blocks: [],
}; };
const diffs = "<span>Random Html</span>"; const diffs = '<span>Random Html</span>';
const fileHtml = lineByLineRenderer.makeFileDiffHtml(file, diffs); const fileHtml = lineByLineRenderer.makeFileDiffHtml(file, diffs);
@ -313,25 +313,25 @@ describe("LineByLineRenderer", () => {
</div>" </div>"
`); `);
}); });
it("should return empty when option renderNothingWhenEmpty is true and file blocks not present", () => { it('should return empty when option renderNothingWhenEmpty is true and file blocks not present', () => {
const hoganUtils = new HoganJsUtils({}); const hoganUtils = new HoganJsUtils({});
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, { const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {
renderNothingWhenEmpty: true renderNothingWhenEmpty: true,
}); });
const file = { const file = {
addedLines: 0, addedLines: 0,
deletedLines: 0, deletedLines: 0,
language: "js", language: 'js',
oldName: "my/file/name1.js", oldName: 'my/file/name1.js',
newName: "my/file/name2.js", newName: 'my/file/name2.js',
isRename: true, isRename: true,
isCombined: false, isCombined: false,
isGitDiff: false, isGitDiff: false,
blocks: [] blocks: [],
}; };
const diffs = "<span>Random Html</span>"; const diffs = '<span>Random Html</span>';
const fileHtml = lineByLineRenderer.makeFileDiffHtml(file, diffs); const fileHtml = lineByLineRenderer.makeFileDiffHtml(file, diffs);
@ -339,47 +339,47 @@ describe("LineByLineRenderer", () => {
}); });
}); });
describe("generateLineByLineJsonHtml", () => { describe('generateLineByLineJsonHtml', () => {
it("should work for list of files", () => { it('should work for list of files', () => {
const exampleJson: DiffFile[] = [ const exampleJson: DiffFile[] = [
{ {
blocks: [ blocks: [
{ {
lines: [ lines: [
{ {
content: "-test", content: '-test',
type: LineType.DELETE, type: LineType.DELETE,
oldNumber: 1, oldNumber: 1,
newNumber: undefined newNumber: undefined,
}, },
{ {
content: "+test1r", content: '+test1r',
type: LineType.INSERT, type: LineType.INSERT,
oldNumber: undefined, oldNumber: undefined,
newNumber: 1 newNumber: 1,
} },
], ],
oldStartLine: 1, oldStartLine: 1,
oldStartLine2: undefined, oldStartLine2: undefined,
newStartLine: 1, newStartLine: 1,
header: "@@ -1 +1 @@" header: '@@ -1 +1 @@',
} },
], ],
deletedLines: 1, deletedLines: 1,
addedLines: 1, addedLines: 1,
checksumBefore: "0000001", checksumBefore: '0000001',
checksumAfter: "0ddf2ba", checksumAfter: '0ddf2ba',
oldName: "sample", oldName: 'sample',
newName: "sample", newName: 'sample',
language: "txt", language: 'txt',
isCombined: false, isCombined: false,
isGitDiff: true isGitDiff: true,
} },
]; ];
const hoganUtils = new HoganJsUtils({}); const hoganUtils = new HoganJsUtils({});
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, { const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {
matching: LineMatchingType.LINES matching: LineMatchingType.LINES,
}); });
const html = lineByLineRenderer.render(exampleJson); const html = lineByLineRenderer.render(exampleJson);
expect(html).toMatchInlineSnapshot(` expect(html).toMatchInlineSnapshot(`
@ -433,23 +433,23 @@ describe("LineByLineRenderer", () => {
`); `);
}); });
it("should work for empty blocks", () => { it('should work for empty blocks', () => {
const exampleJson = [ const exampleJson = [
{ {
blocks: [], blocks: [],
deletedLines: 0, deletedLines: 0,
addedLines: 0, addedLines: 0,
oldName: "sample", oldName: 'sample',
language: "js", language: 'js',
newName: "sample", newName: 'sample',
isCombined: false, isCombined: false,
isGitDiff: false isGitDiff: false,
} },
]; ];
const hoganUtils = new HoganJsUtils({}); const hoganUtils = new HoganJsUtils({});
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, { const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {
renderNothingWhenEmpty: false renderNothingWhenEmpty: false,
}); });
const html = lineByLineRenderer.render(exampleJson); const html = lineByLineRenderer.render(exampleJson);
expect(html).toMatchInlineSnapshot(` expect(html).toMatchInlineSnapshot(`
@ -483,8 +483,8 @@ describe("LineByLineRenderer", () => {
}); });
}); });
describe("_generateFileHtml", () => { describe('_generateFileHtml', () => {
it("should work for simple file", () => { it('should work for simple file', () => {
const hoganUtils = new HoganJsUtils({}); const hoganUtils = new HoganJsUtils({});
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {}); const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
const file: DiffFile = { const file: DiffFile = {
@ -492,45 +492,45 @@ describe("LineByLineRenderer", () => {
{ {
lines: [ lines: [
{ {
content: " one context line", content: ' one context line',
type: LineType.CONTEXT, type: LineType.CONTEXT,
oldNumber: 1, oldNumber: 1,
newNumber: 1 newNumber: 1,
}, },
{ {
content: "-test", content: '-test',
type: LineType.DELETE, type: LineType.DELETE,
oldNumber: 2, oldNumber: 2,
newNumber: undefined newNumber: undefined,
}, },
{ {
content: "+test1r", content: '+test1r',
type: LineType.INSERT, type: LineType.INSERT,
oldNumber: undefined, oldNumber: undefined,
newNumber: 2 newNumber: 2,
}, },
{ {
content: "+test2r", content: '+test2r',
type: LineType.INSERT, type: LineType.INSERT,
oldNumber: undefined, oldNumber: undefined,
newNumber: 3 newNumber: 3,
} },
], ],
oldStartLine: 1, oldStartLine: 1,
oldStartLine2: undefined, oldStartLine2: undefined,
newStartLine: 1, newStartLine: 1,
header: "@@ -1 +1 @@" header: '@@ -1 +1 @@',
} },
], ],
deletedLines: 1, deletedLines: 1,
addedLines: 1, addedLines: 1,
checksumBefore: "0000001", checksumBefore: '0000001',
checksumAfter: "0ddf2ba", checksumAfter: '0ddf2ba',
oldName: "sample", oldName: 'sample',
language: "txt", language: 'txt',
newName: "sample", newName: 'sample',
isCombined: false, isCombined: false,
isGitDiff: true isGitDiff: true,
}; };
const html = lineByLineRenderer.generateFileHtml(file); const html = lineByLineRenderer.generateFileHtml(file);

View file

@ -1,158 +1,151 @@
import { escapeForHtml, getHtmlId, filenameDiff, diffHighlight } from "../render-utils"; import { escapeForHtml, getHtmlId, filenameDiff, diffHighlight } from '../render-utils';
import { DiffStyleType, LineMatchingType } from "../types"; import { DiffStyleType, LineMatchingType } from '../types';
describe("Utils", () => { describe('Utils', () => {
describe("escapeForHtml", () => { describe('escapeForHtml', () => {
it("should escape & with &amp;", () => { it('should escape & with &amp;', () => {
const result = escapeForHtml("&"); const result = escapeForHtml('&');
expect(result).toEqual("&amp;"); expect(result).toEqual('&amp;');
}); });
it("should escape < with &lt;", () => { it('should escape < with &lt;', () => {
const result = escapeForHtml("<"); const result = escapeForHtml('<');
expect(result).toEqual("&lt;"); expect(result).toEqual('&lt;');
}); });
it("should escape > with &gt;", () => { it('should escape > with &gt;', () => {
const result = escapeForHtml(">"); const result = escapeForHtml('>');
expect(result).toEqual("&gt;"); expect(result).toEqual('&gt;');
}); });
it('should escape " with &quot;', () => { it('should escape " with &quot;', () => {
const result = escapeForHtml('"'); const result = escapeForHtml('"');
expect(result).toEqual("&quot;"); expect(result).toEqual('&quot;');
}); });
it("should escape ' with &#x27;", () => { it("should escape ' with &#x27;", () => {
const result = escapeForHtml("'"); const result = escapeForHtml("'");
expect(result).toEqual("&#x27;"); expect(result).toEqual('&#x27;');
}); });
it("should escape / with &#x2F;", () => { it('should escape / with &#x2F;', () => {
const result = escapeForHtml("/"); const result = escapeForHtml('/');
expect(result).toEqual("&#x2F;"); expect(result).toEqual('&#x2F;');
}); });
it("should escape a string containing HTML code", () => { it('should escape a string containing HTML code', () => {
const result = escapeForHtml(`<a href="/search?q=diff2html">Search 'Diff2Html'</a>`); const result = escapeForHtml(`<a href="/search?q=diff2html">Search 'Diff2Html'</a>`);
expect(result).toEqual( expect(result).toEqual(
"&lt;a href=&quot;&#x2F;search?q=diff2html&quot;&gt;Search &#x27;Diff2Html&#x27;&lt;&#x2F;a&gt;" '&lt;a href=&quot;&#x2F;search?q=diff2html&quot;&gt;Search &#x27;Diff2Html&#x27;&lt;&#x2F;a&gt;',
); );
}); });
}); });
describe("getHtmlId", () => { describe('getHtmlId', () => {
it("should generate file unique id", () => { it('should generate file unique id', () => {
const result = getHtmlId({ const result = getHtmlId({
oldName: "sample.js", oldName: 'sample.js',
newName: "sample.js" newName: 'sample.js',
}); });
expect(result).toEqual("d2h-960013"); expect(result).toEqual('d2h-960013');
});
it("should generate file unique id for empty hashes", () => {
const result = getHtmlId({
oldName: "sample.js",
newName: "sample.js"
});
expect(result).toEqual("d2h-960013");
}); });
}); });
describe("getDiffName", () => { describe('getDiffName', () => {
it("should generate the file name for a changed file", () => { it('should generate the file name for a changed file', () => {
const result = filenameDiff({ const result = filenameDiff({
oldName: "sample.js", oldName: 'sample.js',
newName: "sample.js" newName: 'sample.js',
}); });
expect(result).toEqual("sample.js"); expect(result).toEqual('sample.js');
}); });
it("should generate the file name for a changed file and full rename", () => { it('should generate the file name for a changed file and full rename', () => {
const result = filenameDiff({ const result = filenameDiff({
oldName: "sample1.js", oldName: 'sample1.js',
newName: "sample2.js" newName: 'sample2.js',
}); });
expect(result).toEqual("sample1.js → sample2.js"); expect(result).toEqual('sample1.js → sample2.js');
}); });
it("should generate the file name for a changed file and prefix rename", () => { it('should generate the file name for a changed file and prefix rename', () => {
const result = filenameDiff({ const result = filenameDiff({
oldName: "src/path/sample.js", oldName: 'src/path/sample.js',
newName: "source/path/sample.js" newName: 'source/path/sample.js',
}); });
expect(result).toEqual("{src → source}/path/sample.js"); expect(result).toEqual('{src → source}/path/sample.js');
}); });
it("should generate the file name for a changed file and suffix rename", () => { it('should generate the file name for a changed file and suffix rename', () => {
const result = filenameDiff({ const result = filenameDiff({
oldName: "src/path/sample1.js", oldName: 'src/path/sample1.js',
newName: "src/path/sample2.js" newName: 'src/path/sample2.js',
}); });
expect(result).toEqual("src/path/{sample1.js → sample2.js}"); expect(result).toEqual('src/path/{sample1.js → sample2.js}');
}); });
it("should generate the file name for a changed file and middle rename", () => { it('should generate the file name for a changed file and middle rename', () => {
const result = filenameDiff({ const result = filenameDiff({
oldName: "src/really/big/path/sample.js", oldName: 'src/really/big/path/sample.js',
newName: "src/small/path/sample.js" newName: 'src/small/path/sample.js',
}); });
expect(result).toEqual("src/{really/big → small}/path/sample.js"); expect(result).toEqual('src/{really/big → small}/path/sample.js');
}); });
it("should generate the file name for a deleted file", () => { it('should generate the file name for a deleted file', () => {
const result = filenameDiff({ const result = filenameDiff({
oldName: "src/my/file.js", oldName: 'src/my/file.js',
newName: "/dev/null" newName: '/dev/null',
}); });
expect(result).toEqual("src/my/file.js"); expect(result).toEqual('src/my/file.js');
}); });
it("should generate the file name for a new file", () => { it('should generate the file name for a new file', () => {
const result = filenameDiff({ const result = filenameDiff({
oldName: "/dev/null", oldName: '/dev/null',
newName: "src/my/file.js" newName: 'src/my/file.js',
}); });
expect(result).toEqual("src/my/file.js"); expect(result).toEqual('src/my/file.js');
}); });
}); });
describe("diffHighlight", () => { describe('diffHighlight', () => {
it("should highlight two lines", () => { it('should highlight two lines', () => {
const result = diffHighlight("-var myVar = 2;", "+var myVariable = 3;", false, { const result = diffHighlight('-var myVar = 2;', '+var myVariable = 3;', false, {
matching: LineMatchingType.WORDS matching: LineMatchingType.WORDS,
}); });
expect(result).toEqual({ expect(result).toEqual({
oldLine: { oldLine: {
prefix: "-", prefix: '-',
content: "var <del>myVar</del> = <del>2</del>;" content: 'var <del>myVar</del> = <del>2</del>;',
}, },
newLine: { newLine: {
prefix: "+", prefix: '+',
content: "var <ins>myVariable</ins> = <ins>3</ins>;" content: 'var <ins>myVariable</ins> = <ins>3</ins>;',
} },
}); });
}); });
it("should highlight two lines char by char", () => { it('should highlight two lines char by char', () => {
const result = diffHighlight("-var myVar = 2;", "+var myVariable = 3;", false, { const result = diffHighlight('-var myVar = 2;', '+var myVariable = 3;', false, {
diffStyle: DiffStyleType.CHAR diffStyle: DiffStyleType.CHAR,
}); });
expect(result).toEqual({ expect(result).toEqual({
oldLine: { oldLine: {
prefix: "-", prefix: '-',
content: "var myVar = <del>2</del>;" content: 'var myVar = <del>2</del>;',
}, },
newLine: { newLine: {
prefix: "+", prefix: '+',
content: "var myVar<ins>iable</ins> = <ins>3</ins>;" content: 'var myVar<ins>iable</ins> = <ins>3</ins>;',
} },
}); });
}); });
it("should highlight combined diff lines", () => { it('should highlight combined diff lines', () => {
const result = diffHighlight(" -var myVar = 2;", " +var myVariable = 3;", true, { const result = diffHighlight(' -var myVar = 2;', ' +var myVariable = 3;', true, {
diffStyle: DiffStyleType.WORD, diffStyle: DiffStyleType.WORD,
matching: LineMatchingType.WORDS, matching: LineMatchingType.WORDS,
matchWordsThreshold: 1.0 matchWordsThreshold: 1.0,
}); });
expect(result).toEqual({ expect(result).toEqual({
oldLine: { oldLine: {
prefix: " -", prefix: ' -',
content: 'var <del class="d2h-change">myVar</del> = <del class="d2h-change">2</del>;' content: 'var <del class="d2h-change">myVar</del> = <del class="d2h-change">2</del>;',
}, },
newLine: { newLine: {
prefix: " +", prefix: ' +',
content: 'var <ins class="d2h-change">myVariable</ins> = <ins class="d2h-change">3</ins>;' content: 'var <ins class="d2h-change">myVariable</ins> = <ins class="d2h-change">3</ins>;',
} },
}); });
}); });
}); });

View file

@ -1,11 +1,11 @@
import SideBySideRenderer from "../side-by-side-renderer"; import SideBySideRenderer from '../side-by-side-renderer';
import HoganJsUtils from "../hoganjs-utils"; import HoganJsUtils from '../hoganjs-utils';
import { LineType, DiffLine, DiffFile, LineMatchingType } from "../types"; import { LineType, DiffLine, DiffFile, LineMatchingType } from '../types';
import { CSSLineClass } from "../render-utils"; import { CSSLineClass } from '../render-utils';
describe("SideBySideRenderer", () => { describe('SideBySideRenderer', () => {
describe("generateEmptyDiff", () => { describe('generateEmptyDiff', () => {
it("should return an empty diff", () => { it('should return an empty diff', () => {
const hoganUtils = new HoganJsUtils({}); const hoganUtils = new HoganJsUtils({});
const sideBySideRenderer = new SideBySideRenderer(hoganUtils, {}); const sideBySideRenderer = new SideBySideRenderer(hoganUtils, {});
const fileHtml = sideBySideRenderer.generateEmptyDiff(); const fileHtml = sideBySideRenderer.generateEmptyDiff();
@ -24,8 +24,8 @@ describe("SideBySideRenderer", () => {
}); });
}); });
describe("generateSideBySideFileHtml", () => { describe('generateSideBySideFileHtml', () => {
it("should generate lines with the right prefixes", () => { it('should generate lines with the right prefixes', () => {
const hoganUtils = new HoganJsUtils({}); const hoganUtils = new HoganJsUtils({});
const sideBySideRenderer = new SideBySideRenderer(hoganUtils, {}); const sideBySideRenderer = new SideBySideRenderer(hoganUtils, {});
@ -35,44 +35,44 @@ describe("SideBySideRenderer", () => {
{ {
lines: [ lines: [
{ {
content: " context", content: ' context',
type: LineType.CONTEXT, type: LineType.CONTEXT,
oldNumber: 19, oldNumber: 19,
newNumber: 19 newNumber: 19,
}, },
{ {
content: "-removed", content: '-removed',
type: LineType.DELETE, type: LineType.DELETE,
oldNumber: 20, oldNumber: 20,
newNumber: undefined newNumber: undefined,
}, },
{ {
content: "+added", content: '+added',
type: LineType.INSERT, type: LineType.INSERT,
oldNumber: undefined, oldNumber: undefined,
newNumber: 20 newNumber: 20,
}, },
{ {
content: "+another added", content: '+another added',
type: LineType.INSERT, type: LineType.INSERT,
oldNumber: undefined, oldNumber: undefined,
newNumber: 21 newNumber: 21,
} },
], ],
oldStartLine: 19, oldStartLine: 19,
newStartLine: 19, newStartLine: 19,
header: "@@ -19,7 +19,7 @@" header: '@@ -19,7 +19,7 @@',
} },
], ],
deletedLines: 1, deletedLines: 1,
addedLines: 2, addedLines: 2,
checksumBefore: "fc56817", checksumBefore: 'fc56817',
checksumAfter: "e8e7e49", checksumAfter: 'e8e7e49',
mode: "100644", mode: '100644',
oldName: "coverage.init", oldName: 'coverage.init',
language: "init", language: 'init',
newName: "coverage.init", newName: 'coverage.init',
isCombined: false isCombined: false,
}; };
const fileHtml = sideBySideRenderer.generateFileHtml(file); const fileHtml = sideBySideRenderer.generateFileHtml(file);
@ -156,15 +156,15 @@ describe("SideBySideRenderer", () => {
}); });
}); });
describe("generateSingleLineHtml", () => { describe('generateSingleLineHtml', () => {
it("should work for insertions", () => { it('should work for insertions', () => {
const hoganUtils = new HoganJsUtils({}); const hoganUtils = new HoganJsUtils({});
const sideBySideRenderer = new SideBySideRenderer(hoganUtils, {}); const sideBySideRenderer = new SideBySideRenderer(hoganUtils, {});
const fileHtml = sideBySideRenderer.generateLineHtml(undefined, { const fileHtml = sideBySideRenderer.generateLineHtml(undefined, {
type: CSSLineClass.INSERTS, type: CSSLineClass.INSERTS,
prefix: "+", prefix: '+',
content: "test", content: 'test',
number: 30 number: 30,
}); });
expect(fileHtml).toMatchInlineSnapshot(` expect(fileHtml).toMatchInlineSnapshot(`
@ -194,17 +194,17 @@ describe("SideBySideRenderer", () => {
} }
`); `);
}); });
it("should work for deletions", () => { it('should work for deletions', () => {
const hoganUtils = new HoganJsUtils({}); const hoganUtils = new HoganJsUtils({});
const sideBySideRenderer = new SideBySideRenderer(hoganUtils, {}); const sideBySideRenderer = new SideBySideRenderer(hoganUtils, {});
const fileHtml = sideBySideRenderer.generateLineHtml( const fileHtml = sideBySideRenderer.generateLineHtml(
{ {
type: CSSLineClass.DELETES, type: CSSLineClass.DELETES,
prefix: "-", prefix: '-',
content: "test", content: 'test',
number: 30 number: 30,
}, },
undefined undefined,
); );
expect(fileHtml).toMatchInlineSnapshot(` expect(fileHtml).toMatchInlineSnapshot(`
@ -236,42 +236,42 @@ describe("SideBySideRenderer", () => {
}); });
}); });
describe("generateSideBySideJsonHtml", () => { describe('generateSideBySideJsonHtml', () => {
it("should work for list of files", () => { it('should work for list of files', () => {
const exampleJson: DiffFile[] = [ const exampleJson: DiffFile[] = [
{ {
blocks: [ blocks: [
{ {
lines: [ lines: [
{ {
content: "-test", content: '-test',
type: LineType.DELETE, type: LineType.DELETE,
oldNumber: 1, oldNumber: 1,
newNumber: undefined newNumber: undefined,
}, },
{ {
content: "+test1r", content: '+test1r',
type: LineType.INSERT, type: LineType.INSERT,
oldNumber: undefined, oldNumber: undefined,
newNumber: 1 newNumber: 1,
} },
], ],
oldStartLine: 1, oldStartLine: 1,
oldStartLine2: undefined, oldStartLine2: undefined,
newStartLine: 1, newStartLine: 1,
header: "@@ -1 +1 @@" header: '@@ -1 +1 @@',
} },
], ],
deletedLines: 1, deletedLines: 1,
addedLines: 1, addedLines: 1,
checksumBefore: "0000001", checksumBefore: '0000001',
checksumAfter: "0ddf2ba", checksumAfter: '0ddf2ba',
oldName: "sample", oldName: 'sample',
language: "txt", language: 'txt',
newName: "sample", newName: 'sample',
isCombined: false, isCombined: false,
isGitDiff: true isGitDiff: true,
} },
]; ];
const hoganUtils = new HoganJsUtils({}); const hoganUtils = new HoganJsUtils({});
@ -341,18 +341,18 @@ describe("SideBySideRenderer", () => {
</div>" </div>"
`); `);
}); });
it("should work for files without blocks", () => { it('should work for files without blocks', () => {
const exampleJson: DiffFile[] = [ const exampleJson: DiffFile[] = [
{ {
blocks: [], blocks: [],
oldName: "sample", oldName: 'sample',
language: "js", language: 'js',
newName: "sample", newName: 'sample',
isCombined: false, isCombined: false,
addedLines: 0, addedLines: 0,
deletedLines: 0, deletedLines: 0,
isGitDiff: false isGitDiff: false,
} },
]; ];
const hoganUtils = new HoganJsUtils({}); const hoganUtils = new HoganJsUtils({});
@ -400,24 +400,24 @@ describe("SideBySideRenderer", () => {
}); });
}); });
describe("processLines", () => { describe('processLines', () => {
it("should process file lines", () => { it('should process file lines', () => {
const oldLines: DiffLine[] = [ const oldLines: DiffLine[] = [
{ {
content: "-test", content: '-test',
type: LineType.DELETE, type: LineType.DELETE,
oldNumber: 1, oldNumber: 1,
newNumber: undefined newNumber: undefined,
} },
]; ];
const newLines: DiffLine[] = [ const newLines: DiffLine[] = [
{ {
content: "+test1r", content: '+test1r',
type: LineType.INSERT, type: LineType.INSERT,
oldNumber: undefined, oldNumber: undefined,
newNumber: 1 newNumber: 1,
} },
]; ];
const hoganUtils = new HoganJsUtils({}); const hoganUtils = new HoganJsUtils({});

View file

@ -1,31 +1,31 @@
import { escapeForRegExp, unifyPath, hashCode } from "../utils"; import { escapeForRegExp, unifyPath, hashCode } from '../utils';
describe("Utils", () => { describe('Utils', () => {
describe("escapeForRegExp", () => { describe('escapeForRegExp', () => {
it("should escape markdown link text", () => { it('should escape markdown link text', () => {
const result = escapeForRegExp("[Link](https://diff2html.xyz)"); const result = escapeForRegExp('[Link](https://diff2html.xyz)');
expect(result).toEqual("\\[Link\\]\\(https:\\/\\/diff2html\\.xyz\\)"); expect(result).toEqual('\\[Link\\]\\(https:\\/\\/diff2html\\.xyz\\)');
}); });
it("should escape all dangerous characters", () => { it('should escape all dangerous characters', () => {
const result = escapeForRegExp("-[]/{}()*+?.\\^$|"); const result = escapeForRegExp('-[]/{}()*+?.\\^$|');
expect(result).toEqual("\\-\\[\\]\\/\\{\\}\\(\\)\\*\\+\\?\\.\\\\\\^\\$\\|"); expect(result).toEqual('\\-\\[\\]\\/\\{\\}\\(\\)\\*\\+\\?\\.\\\\\\^\\$\\|');
}); });
}); });
describe("unifyPath", () => { describe('unifyPath', () => {
it("should unify windows style path", () => { it('should unify windows style path', () => {
const result = unifyPath("\\Users\\Downloads\\diff.html"); const result = unifyPath('\\Users\\Downloads\\diff.html');
expect(result).toEqual("/Users/Downloads/diff.html"); expect(result).toEqual('/Users/Downloads/diff.html');
}); });
}); });
describe("hashCode", () => { describe('hashCode', () => {
it("should create consistent hash for a text piece", () => { it('should create consistent hash for a text piece', () => {
const string = "/home/diff2html/diff.html"; const string = '/home/diff2html/diff.html';
expect(hashCode(string)).toEqual(hashCode(string)); expect(hashCode(string)).toEqual(hashCode(string));
}); });
it("should create different hash for different text pieces", () => { it('should create different hash for different text pieces', () => {
expect(hashCode("/home/diff2html/diff1.html")).not.toEqual(hashCode("/home/diff2html/diff2.html")); expect(hashCode('/home/diff2html/diff1.html')).not.toEqual(hashCode('/home/diff2html/diff2.html'));
}); });
}); });
}); });

View file

@ -1,5 +1,5 @@
import { DiffFile, DiffBlock, DiffLine, LineType } from "./types"; import { DiffFile, DiffBlock, DiffLine, LineType } from './types';
import { escapeForRegExp } from "./utils"; import { escapeForRegExp } from './utils';
export interface DiffParserConfig { export interface DiffParserConfig {
srcPrefix?: string; srcPrefix?: string;
@ -7,7 +7,7 @@ export interface DiffParserConfig {
} }
function getExtension(filename: string, language: string): string { function getExtension(filename: string, language: string): string {
const filenameParts = filename.split("."); const filenameParts = filename.split('.');
return filenameParts.length > 1 ? filenameParts[filenameParts.length - 1] : language; return filenameParts.length > 1 ? filenameParts[filenameParts.length - 1] : language;
} }
@ -15,7 +15,7 @@ function startsWithAny(str: string, prefixes: string[]): boolean {
return prefixes.reduce<boolean>((startsWith, prefix) => startsWith || str.startsWith(prefix), false); return prefixes.reduce<boolean>((startsWith, prefix) => startsWith || str.startsWith(prefix), false);
} }
const baseDiffFilenamePrefixes = ["a/", "b/", "i/", "w/", "c/", "o/"]; const baseDiffFilenamePrefixes = ['a/', 'b/', 'i/', 'w/', 'c/', 'o/'];
function getFilename(line: string, linePrefix?: string, extraPrefix?: string): string { function getFilename(line: string, linePrefix?: string, extraPrefix?: string): string {
const prefixes = extraPrefix !== undefined ? [...baseDiffFilenamePrefixes, extraPrefix] : baseDiffFilenamePrefixes; const prefixes = extraPrefix !== undefined ? [...baseDiffFilenamePrefixes, extraPrefix] : baseDiffFilenamePrefixes;
@ -23,22 +23,22 @@ function getFilename(line: string, linePrefix?: string, extraPrefix?: string): s
? new RegExp(`^${escapeForRegExp(linePrefix)} "?(.+?)"?$`) ? new RegExp(`^${escapeForRegExp(linePrefix)} "?(.+?)"?$`)
: new RegExp('^"?(.+?)"?$'); : new RegExp('^"?(.+?)"?$');
const [, filename = ""] = FilenameRegExp.exec(line) || []; const [, filename = ''] = FilenameRegExp.exec(line) || [];
const matchingPrefix = prefixes.find(p => filename.indexOf(p) === 0); const matchingPrefix = prefixes.find(p => filename.indexOf(p) === 0);
const fnameWithoutPrefix = matchingPrefix ? filename.slice(matchingPrefix.length) : filename; const fnameWithoutPrefix = matchingPrefix ? filename.slice(matchingPrefix.length) : filename;
// Cleanup timestamps generated by the unified diff (diff command) as specified in // Cleanup timestamps generated by the unified diff (diff command) as specified in
// https://www.gnu.org/software/diffutils/manual/html_node/Detailed-Unified.html // https://www.gnu.org/software/diffutils/manual/html_node/Detailed-Unified.html
// Ie: 2016-10-25 11:37:14.000000000 +0200 // Ie: 2016-10-25 11:37:14.000000000 +0200
return fnameWithoutPrefix.replace(/\s+\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(?:\.\d+)? [-+]\d{4}.*$/, ""); return fnameWithoutPrefix.replace(/\s+\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(?:\.\d+)? [+-]\d{4}.*$/, '');
} }
function getSrcFilename(line: string, srcPrefix?: string): string | undefined { function getSrcFilename(line: string, srcPrefix?: string): string | undefined {
return getFilename(line, "---", srcPrefix); return getFilename(line, '---', srcPrefix);
} }
function getDstFilename(line: string, dstPrefix?: string): string | undefined { function getDstFilename(line: string, dstPrefix?: string): string | undefined {
return getFilename(line, "+++", dstPrefix); return getFilename(line, '+++', dstPrefix);
} }
/** /**
@ -61,9 +61,9 @@ export function parse(diffInput: string, config: DiffParserConfig = {}): DiffFil
let possibleNewName: string | null = null; let possibleNewName: string | null = null;
/* Diff Header */ /* Diff Header */
const oldFileNameHeader = "--- "; const oldFileNameHeader = '--- ';
const newFileNameHeader = "+++ "; const newFileNameHeader = '+++ ';
const hunkHeaderPrefix = "@@"; const hunkHeaderPrefix = '@@';
/* Diff */ /* Diff */
const oldMode = /^old mode (\d{6})/; const oldMode = /^old mode (\d{6})/;
@ -79,21 +79,21 @@ export function parse(diffInput: string, config: DiffParserConfig = {}): DiffFil
const similarityIndex = /^similarity index (\d+)%/; const similarityIndex = /^similarity index (\d+)%/;
const dissimilarityIndex = /^dissimilarity index (\d+)%/; const dissimilarityIndex = /^dissimilarity index (\d+)%/;
const index = /^index ([0-9a-z]+)\.\.([0-9a-z]+)\s*(\d{6})?/; const index = /^index ([\da-z]+)\.\.([\da-z]+)\s*(\d{6})?/;
const binaryFiles = /^Binary files (.*) and (.*) differ/; const binaryFiles = /^Binary files (.*) and (.*) differ/;
const binaryDiff = /^GIT binary patch/; const binaryDiff = /^GIT binary patch/;
/* Combined Diff */ /* Combined Diff */
const combinedIndex = /^index ([0-9a-z]+),([0-9a-z]+)\.\.([0-9a-z]+)/; const combinedIndex = /^index ([\da-z]+),([\da-z]+)\.\.([\da-z]+)/;
const combinedMode = /^mode (\d{6}),(\d{6})\.\.(\d{6})/; const combinedMode = /^mode (\d{6}),(\d{6})\.\.(\d{6})/;
const combinedNewFile = /^new file mode (\d{6})/; const combinedNewFile = /^new file mode (\d{6})/;
const combinedDeletedFile = /^deleted file mode (\d{6}),(\d{6})/; const combinedDeletedFile = /^deleted file mode (\d{6}),(\d{6})/;
const diffLines = diffInput const diffLines = diffInput
.replace(/\\ No newline at end of file/g, "") .replace(/\\ No newline at end of file/g, '')
.replace(/\r\n?/g, "\n") .replace(/\r\n?/g, '\n')
.split("\n"); .split('\n');
/* Add previous block(if exists) before start a new file */ /* Add previous block(if exists) before start a new file */
function saveBlock(): void { function saveBlock(): void {
@ -137,7 +137,7 @@ export function parse(diffInput: string, config: DiffParserConfig = {}): DiffFil
currentFile = { currentFile = {
blocks: [], blocks: [],
deletedLines: 0, deletedLines: 0,
addedLines: 0 addedLines: 0,
}; };
} }
@ -172,7 +172,7 @@ export function parse(diffInput: string, config: DiffParserConfig = {}): DiffFil
newLine = parseInt(values[3], 10); newLine = parseInt(values[3], 10);
} else { } else {
if (line.startsWith(hunkHeaderPrefix)) { if (line.startsWith(hunkHeaderPrefix)) {
console.error("Failed to parse lines, starting in 0!"); console.error('Failed to parse lines, starting in 0!');
} }
oldLine = 0; oldLine = 0;
@ -189,7 +189,7 @@ export function parse(diffInput: string, config: DiffParserConfig = {}): DiffFil
oldStartLine: oldLine, oldStartLine: oldLine,
oldStartLine2: oldLine2, oldStartLine2: oldLine2,
newStartLine: newLine, newStartLine: newLine,
header: line header: line,
}; };
} }
@ -199,11 +199,11 @@ export function parse(diffInput: string, config: DiffParserConfig = {}): DiffFil
// eslint-disable-next-line // eslint-disable-next-line
// @ts-ignore // @ts-ignore
const currentLine: DiffLine = { const currentLine: DiffLine = {
content: line content: line,
}; };
const addedPrefixes = currentFile.isCombined ? ["+ ", " +", "++"] : ["+"]; const addedPrefixes = currentFile.isCombined ? ['+ ', ' +', '++'] : ['+'];
const deletedPrefixes = currentFile.isCombined ? ["- ", " -", "--"] : ["-"]; const deletedPrefixes = currentFile.isCombined ? ['- ', ' -', '--'] : ['-'];
if (startsWithAny(line, addedPrefixes)) { if (startsWithAny(line, addedPrefixes)) {
currentFile.addedLines++; currentFile.addedLines++;
@ -232,7 +232,7 @@ export function parse(diffInput: string, config: DiffParserConfig = {}): DiffFil
let idx = lineIdx; let idx = lineIdx;
while (idx < diffLines.length - 3) { while (idx < diffLines.length - 3) {
if (line.startsWith("diff")) { if (line.startsWith('diff')) {
return false; return false;
} }
@ -254,7 +254,7 @@ export function parse(diffInput: string, config: DiffParserConfig = {}): DiffFil
// Unmerged paths, and possibly other non-diffable files // Unmerged paths, and possibly other non-diffable files
// https://github.com/scottgonzalez/pretty-diff/issues/11 // https://github.com/scottgonzalez/pretty-diff/issues/11
// Also, remove some useless lines // Also, remove some useless lines
if (!line || line.startsWith("*")) { if (!line || line.startsWith('*')) {
return; return;
} }
@ -265,7 +265,7 @@ export function parse(diffInput: string, config: DiffParserConfig = {}): DiffFil
const nxtLine = diffLines[lineIndex + 1]; const nxtLine = diffLines[lineIndex + 1];
const afterNxtLine = diffLines[lineIndex + 2]; const afterNxtLine = diffLines[lineIndex + 2];
if (line.startsWith("diff")) { if (line.startsWith('diff')) {
startFile(); startFile();
// diff --git a/blocked_delta_results.png b/blocked_delta_results.png // diff --git a/blocked_delta_results.png b/blocked_delta_results.png
@ -276,7 +276,7 @@ export function parse(diffInput: string, config: DiffParserConfig = {}): DiffFil
} }
if (currentFile === null) { if (currentFile === null) {
throw new Error("Where is my file !!!"); throw new Error('Where is my file !!!');
} }
currentFile.isGitDiff = true; currentFile.isGitDiff = true;
@ -311,7 +311,7 @@ export function parse(diffInput: string, config: DiffParserConfig = {}): DiffFil
if ( if (
currentFile && currentFile &&
!currentFile.oldName && !currentFile.oldName &&
line.startsWith("--- ") && line.startsWith('--- ') &&
(values = getSrcFilename(line, config.srcPrefix)) (values = getSrcFilename(line, config.srcPrefix))
) { ) {
currentFile.oldName = values; currentFile.oldName = values;
@ -326,7 +326,7 @@ export function parse(diffInput: string, config: DiffParserConfig = {}): DiffFil
if ( if (
currentFile && currentFile &&
!currentFile.newName && !currentFile.newName &&
line.startsWith("+++ ") && line.startsWith('+++ ') &&
(values = getDstFilename(line, config.dstPrefix)) (values = getDstFilename(line, config.dstPrefix))
) { ) {
currentFile.newName = values; currentFile.newName = values;
@ -350,7 +350,7 @@ export function parse(diffInput: string, config: DiffParserConfig = {}): DiffFil
* 2. Old line starts with: - * 2. Old line starts with: -
* 3. Context line starts with: <SPACE> * 3. Context line starts with: <SPACE>
*/ */
if (currentBlock && (line.startsWith("+") || line.startsWith("-") || line.startsWith(" "))) { if (currentBlock && (line.startsWith('+') || line.startsWith('-') || line.startsWith(' '))) {
createLine(line); createLine(line);
return; return;
} }
@ -358,7 +358,7 @@ export function parse(diffInput: string, config: DiffParserConfig = {}): DiffFil
const doesNotExistHunkHeader = !existHunkHeader(line, lineIndex); const doesNotExistHunkHeader = !existHunkHeader(line, lineIndex);
if (currentFile === null) { if (currentFile === null) {
throw new Error("Where is my file !!!"); throw new Error('Where is my file !!!');
} }
/* /*
@ -399,7 +399,7 @@ export function parse(diffInput: string, config: DiffParserConfig = {}): DiffFil
currentFile.isBinary = true; currentFile.isBinary = true;
currentFile.oldName = getFilename(values[1], undefined, config.srcPrefix); currentFile.oldName = getFilename(values[1], undefined, config.srcPrefix);
currentFile.newName = getFilename(values[2], undefined, config.dstPrefix); currentFile.newName = getFilename(values[2], undefined, config.dstPrefix);
startBlock("Binary file"); startBlock('Binary file');
} else if (binaryDiff.test(line)) { } else if (binaryDiff.test(line)) {
currentFile.isBinary = true; currentFile.isBinary = true;
startBlock(line); startBlock(line);
@ -417,9 +417,11 @@ export function parse(diffInput: string, config: DiffParserConfig = {}): DiffFil
} else if ((values = combinedMode.exec(line))) { } else if ((values = combinedMode.exec(line))) {
currentFile.oldMode = [values[2], values[3]]; currentFile.oldMode = [values[2], values[3]];
currentFile.newMode = values[1]; currentFile.newMode = values[1];
// eslint-disable-next-line sonarjs/no-duplicated-branches
} else if ((values = combinedNewFile.exec(line))) { } else if ((values = combinedNewFile.exec(line))) {
currentFile.newFileMode = values[1]; currentFile.newFileMode = values[1];
currentFile.isNew = true; currentFile.isNew = true;
// eslint-disable-next-line sonarjs/no-duplicated-branches
} else if ((values = combinedDeletedFile.exec(line))) { } else if ((values = combinedDeletedFile.exec(line))) {
currentFile.deletedFileMode = values[1]; currentFile.deletedFileMode = values[1];
currentFile.isDeleted = true; currentFile.isDeleted = true;

View file

@ -1,9 +1,9 @@
import * as DiffParser from "./diff-parser"; import * as DiffParser from './diff-parser';
import * as fileListPrinter from "./file-list-renderer"; import * as fileListPrinter from './file-list-renderer';
import LineByLineRenderer, { LineByLineRendererConfig, defaultLineByLineRendererConfig } from "./line-by-line-renderer"; import LineByLineRenderer, { LineByLineRendererConfig, defaultLineByLineRendererConfig } from './line-by-line-renderer';
import SideBySideRenderer, { SideBySideRendererConfig, defaultSideBySideRendererConfig } from "./side-by-side-renderer"; import SideBySideRenderer, { SideBySideRendererConfig, defaultSideBySideRendererConfig } from './side-by-side-renderer';
import { DiffFile, OutputFormatType } from "./types"; import { DiffFile, OutputFormatType } from './types';
import HoganJsUtils, { HoganJsUtilsConfig } from "./hoganjs-utils"; import HoganJsUtils, { HoganJsUtilsConfig } from './hoganjs-utils';
export interface Diff2HtmlConfig export interface Diff2HtmlConfig
extends DiffParser.DiffParserConfig, extends DiffParser.DiffParserConfig,
@ -18,7 +18,7 @@ export const defaultDiff2HtmlConfig = {
...defaultLineByLineRendererConfig, ...defaultLineByLineRendererConfig,
...defaultSideBySideRendererConfig, ...defaultSideBySideRendererConfig,
outputFormat: OutputFormatType.LINE_BY_LINE, outputFormat: OutputFormatType.LINE_BY_LINE,
drawFileList: true drawFileList: true,
}; };
export function parse(diffInput: string, configuration: Diff2HtmlConfig = {}): DiffFile[] { export function parse(diffInput: string, configuration: Diff2HtmlConfig = {}): DiffFile[] {
@ -28,14 +28,14 @@ export function parse(diffInput: string, configuration: Diff2HtmlConfig = {}): D
export function html(diffInput: string | DiffFile[], configuration: Diff2HtmlConfig = {}): string { export function html(diffInput: string | DiffFile[], configuration: Diff2HtmlConfig = {}): string {
const config = { ...defaultDiff2HtmlConfig, ...configuration }; const config = { ...defaultDiff2HtmlConfig, ...configuration };
const diffJson = typeof diffInput === "string" ? DiffParser.parse(diffInput, config) : diffInput; const diffJson = typeof diffInput === 'string' ? DiffParser.parse(diffInput, config) : diffInput;
const hoganUtils = new HoganJsUtils(config); const hoganUtils = new HoganJsUtils(config);
const fileList = config.drawFileList ? fileListPrinter.render(diffJson, hoganUtils) : ""; const fileList = config.drawFileList ? fileListPrinter.render(diffJson, hoganUtils) : '';
const diffOutput = const diffOutput =
config.outputFormat === "side-by-side" config.outputFormat === 'side-by-side'
? new SideBySideRenderer(hoganUtils, config).render(diffJson) ? new SideBySideRenderer(hoganUtils, config).render(diffJson)
: new LineByLineRenderer(hoganUtils, config).render(diffJson); : new LineByLineRenderer(hoganUtils, config).render(diffJson);

View file

@ -1,33 +1,33 @@
import * as renderUtils from "./render-utils"; import * as renderUtils from './render-utils';
import HoganJsUtils from "./hoganjs-utils"; import HoganJsUtils from './hoganjs-utils';
import { DiffFile } from "./types"; import { DiffFile } from './types';
const baseTemplatesPath = "file-summary"; const baseTemplatesPath = 'file-summary';
const iconsBaseTemplatesPath = "icon"; const iconsBaseTemplatesPath = 'icon';
export function render(diffFiles: DiffFile[], hoganUtils: HoganJsUtils): string { export function render(diffFiles: DiffFile[], hoganUtils: HoganJsUtils): string {
const files = diffFiles const files = diffFiles
.map(file => .map(file =>
hoganUtils.render( hoganUtils.render(
baseTemplatesPath, baseTemplatesPath,
"line", 'line',
{ {
fileHtmlId: renderUtils.getHtmlId(file), fileHtmlId: renderUtils.getHtmlId(file),
oldName: file.oldName, oldName: file.oldName,
newName: file.newName, newName: file.newName,
fileName: renderUtils.filenameDiff(file), fileName: renderUtils.filenameDiff(file),
deletedLines: "-" + file.deletedLines, deletedLines: '-' + file.deletedLines,
addedLines: "+" + file.addedLines addedLines: '+' + file.addedLines,
}, },
{ {
fileIcon: hoganUtils.template(iconsBaseTemplatesPath, renderUtils.getFileIcon(file)) fileIcon: hoganUtils.template(iconsBaseTemplatesPath, renderUtils.getFileIcon(file)),
} },
) ),
) )
.join("\n"); .join('\n');
return hoganUtils.render(baseTemplatesPath, "wrapper", { return hoganUtils.render(baseTemplatesPath, 'wrapper', {
filesNumber: diffFiles.length, filesNumber: diffFiles.length,
files: files files: files,
}); });
} }

View file

@ -1,6 +1,6 @@
import * as Hogan from "hogan.js"; import * as Hogan from 'hogan.js';
import { defaultTemplates } from "./diff2html-templates"; import { defaultTemplates } from './diff2html-templates';
export interface RawTemplates { export interface RawTemplates {
[name: string]: string; [name: string]: string;
@ -24,7 +24,7 @@ export default class HoganJsUtils {
const compiledTemplate: Hogan.Template = Hogan.compile(templateString, { asString: false }); const compiledTemplate: Hogan.Template = Hogan.compile(templateString, { asString: false });
return { ...previousTemplates, [name]: compiledTemplate }; return { ...previousTemplates, [name]: compiledTemplate };
}, },
{} {},
); );
this.preCompiledTemplates = { ...defaultTemplates, ...compiledTemplates, ...compiledRawTemplates }; this.preCompiledTemplates = { ...defaultTemplates, ...compiledTemplates, ...compiledRawTemplates };

View file

@ -1,6 +1,6 @@
import HoganJsUtils from "./hoganjs-utils"; import HoganJsUtils from './hoganjs-utils';
import * as Rematch from "./rematch"; import * as Rematch from './rematch';
import * as renderUtils from "./render-utils"; import * as renderUtils from './render-utils';
import { import {
DiffFile, DiffFile,
DiffLine, DiffLine,
@ -9,8 +9,8 @@ import {
DiffLineDeleted, DiffLineDeleted,
DiffLineContent, DiffLineContent,
DiffLineContext, DiffLineContext,
DiffLineInserted DiffLineInserted,
} from "./types"; } from './types';
export interface LineByLineRendererConfig extends renderUtils.RenderConfig { export interface LineByLineRendererConfig extends renderUtils.RenderConfig {
renderNothingWhenEmpty?: boolean; renderNothingWhenEmpty?: boolean;
@ -22,13 +22,13 @@ export const defaultLineByLineRendererConfig = {
...renderUtils.defaultRenderConfig, ...renderUtils.defaultRenderConfig,
renderNothingWhenEmpty: false, renderNothingWhenEmpty: false,
matchingMaxComparisons: 2500, matchingMaxComparisons: 2500,
maxLineSizeInBlockForComparison: 200 maxLineSizeInBlockForComparison: 200,
}; };
const genericTemplatesPath = "generic"; const genericTemplatesPath = 'generic';
const baseTemplatesPath = "line-by-line"; const baseTemplatesPath = 'line-by-line';
const iconsBaseTemplatesPath = "icon"; const iconsBaseTemplatesPath = 'icon';
const tagsBaseTemplatesPath = "tag"; const tagsBaseTemplatesPath = 'tag';
export default class LineByLineRenderer { export default class LineByLineRenderer {
private readonly hoganUtils: HoganJsUtils; private readonly hoganUtils: HoganJsUtils;
@ -50,17 +50,17 @@ export default class LineByLineRenderer {
} }
return this.makeFileDiffHtml(file, diffs); return this.makeFileDiffHtml(file, diffs);
}) })
.join("\n"); .join('\n');
return this.hoganUtils.render(genericTemplatesPath, "wrapper", { content: diffsHtml }); return this.hoganUtils.render(genericTemplatesPath, 'wrapper', { content: diffsHtml });
} }
makeFileDiffHtml(file: DiffFile, diffs: string): string { makeFileDiffHtml(file: DiffFile, diffs: string): string {
if (this.config.renderNothingWhenEmpty && Array.isArray(file.blocks) && file.blocks.length === 0) return ""; if (this.config.renderNothingWhenEmpty && Array.isArray(file.blocks) && file.blocks.length === 0) return '';
const fileDiffTemplate = this.hoganUtils.template(baseTemplatesPath, "file-diff"); const fileDiffTemplate = this.hoganUtils.template(baseTemplatesPath, 'file-diff');
const filePathTemplate = this.hoganUtils.template(genericTemplatesPath, "file-path"); const filePathTemplate = this.hoganUtils.template(genericTemplatesPath, 'file-path');
const fileIconTemplate = this.hoganUtils.template(iconsBaseTemplatesPath, "file"); const fileIconTemplate = this.hoganUtils.template(iconsBaseTemplatesPath, 'file');
const fileTagTemplate = this.hoganUtils.template(tagsBaseTemplatesPath, renderUtils.getFileIcon(file)); const fileTagTemplate = this.hoganUtils.template(tagsBaseTemplatesPath, renderUtils.getFileIcon(file));
return fileDiffTemplate.render({ return fileDiffTemplate.render({
@ -69,35 +69,35 @@ export default class LineByLineRenderer {
diffs: diffs, diffs: diffs,
filePath: filePathTemplate.render( filePath: filePathTemplate.render(
{ {
fileDiffName: renderUtils.filenameDiff(file) fileDiffName: renderUtils.filenameDiff(file),
}, },
{ {
fileIcon: fileIconTemplate, fileIcon: fileIconTemplate,
fileTag: fileTagTemplate fileTag: fileTagTemplate,
} },
) ),
}); });
} }
generateEmptyDiff(): string { generateEmptyDiff(): string {
return this.hoganUtils.render(genericTemplatesPath, "empty-diff", { return this.hoganUtils.render(genericTemplatesPath, 'empty-diff', {
contentClass: "d2h-code-line", contentClass: 'd2h-code-line',
CSSLineClass: renderUtils.CSSLineClass CSSLineClass: renderUtils.CSSLineClass,
}); });
} }
generateFileHtml(file: DiffFile): string { generateFileHtml(file: DiffFile): string {
const matcher = Rematch.newMatcherFn( const matcher = Rematch.newMatcherFn(
Rematch.newDistanceFn((e: DiffLine) => renderUtils.deconstructLine(e.content, file.isCombined).content) Rematch.newDistanceFn((e: DiffLine) => renderUtils.deconstructLine(e.content, file.isCombined).content),
); );
return file.blocks return file.blocks
.map(block => { .map(block => {
let lines = this.hoganUtils.render(genericTemplatesPath, "block-header", { let lines = this.hoganUtils.render(genericTemplatesPath, 'block-header', {
CSSLineClass: renderUtils.CSSLineClass, CSSLineClass: renderUtils.CSSLineClass,
blockHeader: block.header, blockHeader: block.header,
lineClass: "d2h-code-linenumber", lineClass: 'd2h-code-linenumber',
contentClass: "d2h-code-line" contentClass: 'd2h-code-line',
}); });
this.applyLineGroupping(block).forEach(([contextLines, oldLines, newLines]) => { this.applyLineGroupping(block).forEach(([contextLines, oldLines, newLines]) => {
@ -115,7 +115,7 @@ export default class LineByLineRenderer {
prefix: prefix, prefix: prefix,
content: content, content: content,
oldNumber: line.oldNumber, oldNumber: line.oldNumber,
newNumber: line.newNumber newNumber: line.newNumber,
}); });
}); });
} else if (oldLines.length || newLines.length) { } else if (oldLines.length || newLines.length) {
@ -123,13 +123,13 @@ export default class LineByLineRenderer {
lines += left; lines += left;
lines += right; lines += right;
} else { } else {
console.error("Unknown state reached while processing groups of lines", contextLines, oldLines, newLines); console.error('Unknown state reached while processing groups of lines', contextLines, oldLines, newLines);
} }
}); });
return lines; return lines;
}) })
.join("\n"); .join('\n');
} }
applyLineGroupping(block: DiffBlock): DiffLineGroups { applyLineGroupping(block: DiffBlock): DiffLineGroups {
@ -173,27 +173,25 @@ export default class LineByLineRenderer {
applyRematchMatching( applyRematchMatching(
oldLines: DiffLine[], oldLines: DiffLine[],
newLines: DiffLine[], newLines: DiffLine[],
matcher: Rematch.MatcherFn<DiffLine> matcher: Rematch.MatcherFn<DiffLine>,
): DiffLine[][][] { ): DiffLine[][][] {
const comparisons = oldLines.length * newLines.length; const comparisons = oldLines.length * newLines.length;
const maxLineSizeInBlock = Math.max.apply( const maxLineSizeInBlock = Math.max.apply(
null, null,
[0].concat(oldLines.concat(newLines).map(elem => elem.content.length)) [0].concat(oldLines.concat(newLines).map(elem => elem.content.length)),
); );
const doMatching = const doMatching =
comparisons < this.config.matchingMaxComparisons && comparisons < this.config.matchingMaxComparisons &&
maxLineSizeInBlock < this.config.maxLineSizeInBlockForComparison && maxLineSizeInBlock < this.config.maxLineSizeInBlockForComparison &&
(this.config.matching === "lines" || this.config.matching === "words"); (this.config.matching === 'lines' || this.config.matching === 'words');
const matches = doMatching ? matcher(oldLines, newLines) : [[oldLines, newLines]]; return doMatching ? matcher(oldLines, newLines) : [[oldLines, newLines]];
return matches;
} }
processChangedLines(isCombined: boolean, oldLines: DiffLine[], newLines: DiffLine[]): FileHtml { processChangedLines(isCombined: boolean, oldLines: DiffLine[], newLines: DiffLine[]): FileHtml {
const fileHtml = { const fileHtml = {
right: "", right: '',
left: "" left: '',
}; };
const maxLinesNumber = Math.max(oldLines.length, newLines.length); const maxLinesNumber = Math.max(oldLines.length, newLines.length);
@ -213,14 +211,14 @@ export default class LineByLineRenderer {
? { ? {
prefix: diff.oldLine.prefix, prefix: diff.oldLine.prefix,
content: diff.oldLine.content, content: diff.oldLine.content,
type: renderUtils.CSSLineClass.DELETE_CHANGES type: renderUtils.CSSLineClass.DELETE_CHANGES,
} }
: { : {
...renderUtils.deconstructLine(oldLine.content, isCombined), ...renderUtils.deconstructLine(oldLine.content, isCombined),
type: renderUtils.toCSSClass(oldLine.type) type: renderUtils.toCSSClass(oldLine.type),
}), }),
oldNumber: oldLine.oldNumber, oldNumber: oldLine.oldNumber,
newNumber: oldLine.newNumber newNumber: oldLine.newNumber,
} }
: undefined; : undefined;
@ -231,14 +229,14 @@ export default class LineByLineRenderer {
? { ? {
prefix: diff.newLine.prefix, prefix: diff.newLine.prefix,
content: diff.newLine.content, content: diff.newLine.content,
type: renderUtils.CSSLineClass.INSERT_CHANGES type: renderUtils.CSSLineClass.INSERT_CHANGES,
} }
: { : {
...renderUtils.deconstructLine(newLine.content, isCombined), ...renderUtils.deconstructLine(newLine.content, isCombined),
type: renderUtils.toCSSClass(newLine.type) type: renderUtils.toCSSClass(newLine.type),
}), }),
oldNumber: newLine.oldNumber, oldNumber: newLine.oldNumber,
newNumber: newLine.newNumber newNumber: newLine.newNumber,
} }
: undefined; : undefined;
@ -253,25 +251,25 @@ export default class LineByLineRenderer {
generateLineHtml(oldLine?: DiffPreparedLine, newLine?: DiffPreparedLine): FileHtml { generateLineHtml(oldLine?: DiffPreparedLine, newLine?: DiffPreparedLine): FileHtml {
return { return {
left: this.generateSingleLineHtml(oldLine), left: this.generateSingleLineHtml(oldLine),
right: this.generateSingleLineHtml(newLine) right: this.generateSingleLineHtml(newLine),
}; };
} }
generateSingleLineHtml(line?: DiffPreparedLine): string { generateSingleLineHtml(line?: DiffPreparedLine): string {
if (line === undefined) return ""; if (line === undefined) return '';
const lineNumberHtml = this.hoganUtils.render(baseTemplatesPath, "numbers", { const lineNumberHtml = this.hoganUtils.render(baseTemplatesPath, 'numbers', {
oldNumber: line.oldNumber || "", oldNumber: line.oldNumber || '',
newNumber: line.newNumber || "" newNumber: line.newNumber || '',
}); });
return this.hoganUtils.render(genericTemplatesPath, "line", { return this.hoganUtils.render(genericTemplatesPath, 'line', {
type: line.type, type: line.type,
lineClass: "d2h-code-linenumber", lineClass: 'd2h-code-linenumber',
contentClass: "d2h-code-line", contentClass: 'd2h-code-line',
prefix: line.prefix === " " ? "&nbsp;" : line.prefix, prefix: line.prefix === ' ' ? '&nbsp;' : line.prefix,
content: line.content, content: line.content,
lineNumber: lineNumberHtml lineNumber: lineNumberHtml,
}); });
} }
} }
@ -279,7 +277,7 @@ export default class LineByLineRenderer {
type DiffLineGroups = [ type DiffLineGroups = [
(DiffLineContext & DiffLineContent)[], (DiffLineContext & DiffLineContent)[],
(DiffLineDeleted & DiffLineContent)[], (DiffLineDeleted & DiffLineContent)[],
(DiffLineInserted & DiffLineContent)[] (DiffLineInserted & DiffLineContent)[],
][]; ][];
type DiffPreparedLine = { type DiffPreparedLine = {

View file

@ -53,8 +53,8 @@ export function levenshtein(a: string, b: string): number {
matrix[i - 1][j - 1] + 1, // Substitution matrix[i - 1][j - 1] + 1, // Substitution
Math.min( Math.min(
matrix[i][j - 1] + 1, // Insertion matrix[i][j - 1] + 1, // Insertion
matrix[i - 1][j] + 1 matrix[i - 1][j] + 1,
) ),
); // Deletion ); // Deletion
} }
} }
@ -70,9 +70,7 @@ export function newDistanceFn<T>(str: (value: T) => string): DistanceFn<T> {
const xValue = str(x).trim(); const xValue = str(x).trim();
const yValue = str(y).trim(); const yValue = str(y).trim();
const lev = levenshtein(xValue, yValue); const lev = levenshtein(xValue, yValue);
const score = lev / (xValue.length + yValue.length); return lev / (xValue.length + yValue.length);
return score;
}; };
} }

View file

@ -1,16 +1,16 @@
import * as jsDiff from "diff"; import * as jsDiff from 'diff';
import { unifyPath, hashCode } from "./utils"; import { unifyPath, hashCode } from './utils';
import * as rematch from "./rematch"; import * as rematch from './rematch';
import { LineMatchingType, DiffStyleType, LineType, DiffLineParts, DiffFile, DiffFileName } from "./types"; import { LineMatchingType, DiffStyleType, LineType, DiffLineParts, DiffFile, DiffFileName } from './types';
export enum CSSLineClass { export enum CSSLineClass {
INSERTS = "d2h-ins", INSERTS = 'd2h-ins',
DELETES = "d2h-del", DELETES = 'd2h-del',
CONTEXT = "d2h-cntx", CONTEXT = 'd2h-cntx',
INFO = "d2h-info", INFO = 'd2h-info',
INSERT_CHANGES = "d2h-ins d2h-change", INSERT_CHANGES = 'd2h-ins d2h-change',
DELETE_CHANGES = "d2h-del d2h-change" DELETE_CHANGES = 'd2h-del d2h-change',
} }
export type HighlightedLines = { export type HighlightedLines = {
@ -35,23 +35,23 @@ export const defaultRenderConfig = {
matching: LineMatchingType.NONE, matching: LineMatchingType.NONE,
matchWordsThreshold: 0.25, matchWordsThreshold: 0.25,
maxLineLengthHighlight: 10000, maxLineLengthHighlight: 10000,
diffStyle: DiffStyleType.WORD diffStyle: DiffStyleType.WORD,
}; };
const separator = "/"; const separator = '/';
const distance = rematch.newDistanceFn((change: jsDiff.Change) => change.value); const distance = rematch.newDistanceFn((change: jsDiff.Change) => change.value);
const matcher = rematch.newMatcherFn(distance); const matcher = rematch.newMatcherFn(distance);
function isDevNullName(name: string): boolean { function isDevNullName(name: string): boolean {
return name.indexOf("dev/null") !== -1; return name.indexOf('dev/null') !== -1;
} }
function removeInsElements(line: string): string { function removeInsElements(line: string): string {
return line.replace(/(<ins[^>]*>((.|\n)*?)<\/ins>)/g, ""); return line.replace(/(<ins[^>]*>((.|\n)*?)<\/ins>)/g, '');
} }
function removeDelElements(line: string): string { function removeDelElements(line: string): string {
return line.replace(/(<del[^>]*>((.|\n)*?)<\/del>)/g, ""); return line.replace(/(<del[^>]*>((.|\n)*?)<\/del>)/g, '');
} }
/** /**
@ -82,12 +82,12 @@ function prefixLength(isCombined: boolean): number {
export function escapeForHtml(str: string): string { export function escapeForHtml(str: string): string {
return str return str
.slice(0) .slice(0)
.replace(/&/g, "&amp;") .replace(/&/g, '&amp;')
.replace(/</g, "&lt;") .replace(/</g, '&lt;')
.replace(/>/g, "&gt;") .replace(/>/g, '&gt;')
.replace(/"/g, "&quot;") .replace(/"/g, '&quot;')
.replace(/'/g, "&#x27;") .replace(/'/g, '&#x27;')
.replace(/\//g, "&#x2F;"); .replace(/\//g, '&#x2F;');
} }
/** /**
@ -97,7 +97,7 @@ export function deconstructLine(line: string, isCombined: boolean): DiffLinePart
const indexToSplit = prefixLength(isCombined); const indexToSplit = prefixLength(isCombined);
return { return {
prefix: line.substring(0, indexToSplit), prefix: line.substring(0, indexToSplit),
content: escapeForHtml(line.substring(indexToSplit)) content: escapeForHtml(line.substring(indexToSplit)),
}; };
} }
@ -158,15 +158,15 @@ export function filenameDiff(file: DiffFileName): string {
if (finalPrefix.length && finalSuffix.length) { if (finalPrefix.length && finalSuffix.length) {
return ( return (
finalPrefix + separator + "{" + oldRemainingPath + " → " + newRemainingPath + "}" + separator + finalSuffix finalPrefix + separator + '{' + oldRemainingPath + ' → ' + newRemainingPath + '}' + separator + finalSuffix
); );
} else if (finalPrefix.length) { } else if (finalPrefix.length) {
return finalPrefix + separator + "{" + oldRemainingPath + " → " + newRemainingPath + "}"; return finalPrefix + separator + '{' + oldRemainingPath + ' → ' + newRemainingPath + '}';
} else if (finalSuffix.length) { } else if (finalSuffix.length) {
return "{" + oldRemainingPath + " → " + newRemainingPath + "}" + separator + finalSuffix; return '{' + oldRemainingPath + ' → ' + newRemainingPath + '}' + separator + finalSuffix;
} }
return oldFilename + " → " + newFilename; return oldFilename + ' → ' + newFilename;
} else if (!isDevNullName(newFilename)) { } else if (!isDevNullName(newFilename)) {
return newFilename; return newFilename;
} else { } else {
@ -187,19 +187,19 @@ export function getHtmlId(file: DiffFileName): string {
* Selects the correct icon name for the file * Selects the correct icon name for the file
*/ */
export function getFileIcon(file: DiffFile): string { export function getFileIcon(file: DiffFile): string {
let templateName = "file-changed"; let templateName = 'file-changed';
if (file.isRename) { if (file.isRename) {
templateName = "file-renamed"; templateName = 'file-renamed';
} else if (file.isCopy) { } else if (file.isCopy) {
templateName = "file-renamed"; templateName = 'file-renamed';
} else if (file.isNew) { } else if (file.isNew) {
templateName = "file-added"; templateName = 'file-added';
} else if (file.isDeleted) { } else if (file.isDeleted) {
templateName = "file-deleted"; templateName = 'file-deleted';
} else if (file.newName !== file.oldName) { } else if (file.newName !== file.oldName) {
// If file is not Added, not Deleted and the names changed it must be a rename :) // If file is not Added, not Deleted and the names changed it must be a rename :)
templateName = "file-renamed"; templateName = 'file-renamed';
} }
return templateName; return templateName;
@ -212,7 +212,7 @@ export function diffHighlight(
diffLine1: string, diffLine1: string,
diffLine2: string, diffLine2: string,
isCombined: boolean, isCombined: boolean,
config: RenderConfig = {} config: RenderConfig = {},
): HighlightedLines { ): HighlightedLines {
const { matching, maxLineLengthHighlight, matchWordsThreshold, diffStyle } = { ...defaultRenderConfig, ...config }; const { matching, maxLineLengthHighlight, matchWordsThreshold, diffStyle } = { ...defaultRenderConfig, ...config };
@ -223,22 +223,22 @@ export function diffHighlight(
return { return {
oldLine: { oldLine: {
prefix: line1.prefix, prefix: line1.prefix,
content: line1.content content: line1.content,
}, },
newLine: { newLine: {
prefix: line2.prefix, prefix: line2.prefix,
content: line2.content content: line2.content,
} },
}; };
} }
const diff = const diff =
diffStyle === "char" diffStyle === 'char'
? jsDiff.diffChars(line1.content, line2.content) ? jsDiff.diffChars(line1.content, line2.content)
: jsDiff.diffWordsWithSpace(line1.content, line2.content); : jsDiff.diffWordsWithSpace(line1.content, line2.content);
const changedWords: jsDiff.Change[] = []; const changedWords: jsDiff.Change[] = [];
if (diffStyle === "word" && matching === "words") { if (diffStyle === 'word' && matching === 'words') {
const removed = diff.filter(element => element.removed); const removed = diff.filter(element => element.removed);
const added = diff.filter(element => element.added); const added = diff.filter(element => element.added);
const chunks = matcher(added, removed); const chunks = matcher(added, removed);
@ -254,22 +254,22 @@ export function diffHighlight(
} }
const highlightedLine = diff.reduce((highlightedLine, part) => { const highlightedLine = diff.reduce((highlightedLine, part) => {
const elemType = part.added ? "ins" : part.removed ? "del" : null; const elemType = part.added ? 'ins' : part.removed ? 'del' : null;
const addClass = changedWords.indexOf(part) > -1 ? ' class="d2h-change"' : ""; const addClass = changedWords.indexOf(part) > -1 ? ' class="d2h-change"' : '';
return elemType !== null return elemType !== null
? `${highlightedLine}<${elemType}${addClass}>${part.value}</${elemType}>` ? `${highlightedLine}<${elemType}${addClass}>${part.value}</${elemType}>`
: `${highlightedLine}${part.value}`; : `${highlightedLine}${part.value}`;
}, ""); }, '');
return { return {
oldLine: { oldLine: {
prefix: line1.prefix, prefix: line1.prefix,
content: removeInsElements(highlightedLine) content: removeInsElements(highlightedLine),
}, },
newLine: { newLine: {
prefix: line2.prefix, prefix: line2.prefix,
content: removeDelElements(highlightedLine) content: removeDelElements(highlightedLine),
} },
}; };
} }

View file

@ -1,6 +1,6 @@
import HoganJsUtils from "./hoganjs-utils"; import HoganJsUtils from './hoganjs-utils';
import * as Rematch from "./rematch"; import * as Rematch from './rematch';
import * as renderUtils from "./render-utils"; import * as renderUtils from './render-utils';
import { import {
DiffLine, DiffLine,
LineType, LineType,
@ -9,8 +9,8 @@ import {
DiffLineContext, DiffLineContext,
DiffLineDeleted, DiffLineDeleted,
DiffLineInserted, DiffLineInserted,
DiffLineContent DiffLineContent,
} from "./types"; } from './types';
export interface SideBySideRendererConfig extends renderUtils.RenderConfig { export interface SideBySideRendererConfig extends renderUtils.RenderConfig {
renderNothingWhenEmpty?: boolean; renderNothingWhenEmpty?: boolean;
@ -22,13 +22,13 @@ export const defaultSideBySideRendererConfig = {
...renderUtils.defaultRenderConfig, ...renderUtils.defaultRenderConfig,
renderNothingWhenEmpty: false, renderNothingWhenEmpty: false,
matchingMaxComparisons: 2500, matchingMaxComparisons: 2500,
maxLineSizeInBlockForComparison: 200 maxLineSizeInBlockForComparison: 200,
}; };
const genericTemplatesPath = "generic"; const genericTemplatesPath = 'generic';
const baseTemplatesPath = "side-by-side"; const baseTemplatesPath = 'side-by-side';
const iconsBaseTemplatesPath = "icon"; const iconsBaseTemplatesPath = 'icon';
const tagsBaseTemplatesPath = "tag"; const tagsBaseTemplatesPath = 'tag';
export default class SideBySideRenderer { export default class SideBySideRenderer {
private readonly hoganUtils: HoganJsUtils; private readonly hoganUtils: HoganJsUtils;
@ -50,17 +50,17 @@ export default class SideBySideRenderer {
} }
return this.makeFileDiffHtml(file, diffs); return this.makeFileDiffHtml(file, diffs);
}) })
.join("\n"); .join('\n');
return this.hoganUtils.render(genericTemplatesPath, "wrapper", { content: diffsHtml }); return this.hoganUtils.render(genericTemplatesPath, 'wrapper', { content: diffsHtml });
} }
makeFileDiffHtml(file: DiffFile, diffs: FileHtml): string { makeFileDiffHtml(file: DiffFile, diffs: FileHtml): string {
if (this.config.renderNothingWhenEmpty && Array.isArray(file.blocks) && file.blocks.length === 0) return ""; if (this.config.renderNothingWhenEmpty && Array.isArray(file.blocks) && file.blocks.length === 0) return '';
const fileDiffTemplate = this.hoganUtils.template(baseTemplatesPath, "file-diff"); const fileDiffTemplate = this.hoganUtils.template(baseTemplatesPath, 'file-diff');
const filePathTemplate = this.hoganUtils.template(genericTemplatesPath, "file-path"); const filePathTemplate = this.hoganUtils.template(genericTemplatesPath, 'file-path');
const fileIconTemplate = this.hoganUtils.template(iconsBaseTemplatesPath, "file"); const fileIconTemplate = this.hoganUtils.template(iconsBaseTemplatesPath, 'file');
const fileTagTemplate = this.hoganUtils.template(tagsBaseTemplatesPath, renderUtils.getFileIcon(file)); const fileTagTemplate = this.hoganUtils.template(tagsBaseTemplatesPath, renderUtils.getFileIcon(file));
return fileDiffTemplate.render({ return fileDiffTemplate.render({
@ -69,36 +69,36 @@ export default class SideBySideRenderer {
diffs: diffs, diffs: diffs,
filePath: filePathTemplate.render( filePath: filePathTemplate.render(
{ {
fileDiffName: renderUtils.filenameDiff(file) fileDiffName: renderUtils.filenameDiff(file),
}, },
{ {
fileIcon: fileIconTemplate, fileIcon: fileIconTemplate,
fileTag: fileTagTemplate fileTag: fileTagTemplate,
} },
) ),
}); });
} }
generateEmptyDiff(): FileHtml { generateEmptyDiff(): FileHtml {
return { return {
right: "", right: '',
left: this.hoganUtils.render(genericTemplatesPath, "empty-diff", { left: this.hoganUtils.render(genericTemplatesPath, 'empty-diff', {
contentClass: "d2h-code-side-line", contentClass: 'd2h-code-side-line',
CSSLineClass: renderUtils.CSSLineClass CSSLineClass: renderUtils.CSSLineClass,
}) }),
}; };
} }
generateFileHtml(file: DiffFile): FileHtml { generateFileHtml(file: DiffFile): FileHtml {
const matcher = Rematch.newMatcherFn( const matcher = Rematch.newMatcherFn(
Rematch.newDistanceFn((e: DiffLine) => renderUtils.deconstructLine(e.content, file.isCombined).content) Rematch.newDistanceFn((e: DiffLine) => renderUtils.deconstructLine(e.content, file.isCombined).content),
); );
return file.blocks return file.blocks
.map(block => { .map(block => {
const fileHtml = { const fileHtml = {
left: this.makeHeaderHtml(block.header), left: this.makeHeaderHtml(block.header),
right: this.makeHeaderHtml("") right: this.makeHeaderHtml(''),
}; };
this.applyLineGroupping(block).forEach(([contextLines, oldLines, newLines]) => { this.applyLineGroupping(block).forEach(([contextLines, oldLines, newLines]) => {
@ -116,14 +116,14 @@ export default class SideBySideRenderer {
type: renderUtils.CSSLineClass.CONTEXT, type: renderUtils.CSSLineClass.CONTEXT,
prefix: prefix, prefix: prefix,
content: content, content: content,
number: line.oldNumber number: line.oldNumber,
}, },
{ {
type: renderUtils.CSSLineClass.CONTEXT, type: renderUtils.CSSLineClass.CONTEXT,
prefix: prefix, prefix: prefix,
content: content, content: content,
number: line.newNumber number: line.newNumber,
} },
); );
fileHtml.left += left; fileHtml.left += left;
fileHtml.right += right; fileHtml.right += right;
@ -133,7 +133,7 @@ export default class SideBySideRenderer {
fileHtml.left += left; fileHtml.left += left;
fileHtml.right += right; fileHtml.right += right;
} else { } else {
console.error("Unknown state reached while processing groups of lines", contextLines, oldLines, newLines); console.error('Unknown state reached while processing groups of lines', contextLines, oldLines, newLines);
} }
}); });
@ -143,7 +143,7 @@ export default class SideBySideRenderer {
(accomulated, html) => { (accomulated, html) => {
return { left: accomulated.left + html.left, right: accomulated.right + html.right }; return { left: accomulated.left + html.left, right: accomulated.right + html.right };
}, },
{ left: "", right: "" } { left: '', right: '' },
); );
} }
@ -188,36 +188,34 @@ export default class SideBySideRenderer {
applyRematchMatching( applyRematchMatching(
oldLines: DiffLine[], oldLines: DiffLine[],
newLines: DiffLine[], newLines: DiffLine[],
matcher: Rematch.MatcherFn<DiffLine> matcher: Rematch.MatcherFn<DiffLine>,
): DiffLine[][][] { ): DiffLine[][][] {
const comparisons = oldLines.length * newLines.length; const comparisons = oldLines.length * newLines.length;
const maxLineSizeInBlock = Math.max.apply( const maxLineSizeInBlock = Math.max.apply(
null, null,
[0].concat(oldLines.concat(newLines).map(elem => elem.content.length)) [0].concat(oldLines.concat(newLines).map(elem => elem.content.length)),
); );
const doMatching = const doMatching =
comparisons < this.config.matchingMaxComparisons && comparisons < this.config.matchingMaxComparisons &&
maxLineSizeInBlock < this.config.maxLineSizeInBlockForComparison && maxLineSizeInBlock < this.config.maxLineSizeInBlockForComparison &&
(this.config.matching === "lines" || this.config.matching === "words"); (this.config.matching === 'lines' || this.config.matching === 'words');
const matches = doMatching ? matcher(oldLines, newLines) : [[oldLines, newLines]]; return doMatching ? matcher(oldLines, newLines) : [[oldLines, newLines]];
return matches;
} }
makeHeaderHtml(blockHeader: string): string { makeHeaderHtml(blockHeader: string): string {
return this.hoganUtils.render(genericTemplatesPath, "block-header", { return this.hoganUtils.render(genericTemplatesPath, 'block-header', {
CSSLineClass: renderUtils.CSSLineClass, CSSLineClass: renderUtils.CSSLineClass,
blockHeader: blockHeader, blockHeader: blockHeader,
lineClass: "d2h-code-side-linenumber", lineClass: 'd2h-code-side-linenumber',
contentClass: "d2h-code-side-line" contentClass: 'd2h-code-side-line',
}); });
} }
processChangedLines(isCombined: boolean, oldLines: DiffLine[], newLines: DiffLine[]): FileHtml { processChangedLines(isCombined: boolean, oldLines: DiffLine[], newLines: DiffLine[]): FileHtml {
const fileHtml = { const fileHtml = {
right: "", right: '',
left: "" left: '',
}; };
const maxLinesNumber = Math.max(oldLines.length, newLines.length); const maxLinesNumber = Math.max(oldLines.length, newLines.length);
@ -237,13 +235,13 @@ export default class SideBySideRenderer {
? { ? {
prefix: diff.oldLine.prefix, prefix: diff.oldLine.prefix,
content: diff.oldLine.content, content: diff.oldLine.content,
type: renderUtils.CSSLineClass.DELETE_CHANGES type: renderUtils.CSSLineClass.DELETE_CHANGES,
} }
: { : {
...renderUtils.deconstructLine(oldLine.content, isCombined), ...renderUtils.deconstructLine(oldLine.content, isCombined),
type: renderUtils.toCSSClass(oldLine.type) type: renderUtils.toCSSClass(oldLine.type),
}), }),
number: oldLine.oldNumber number: oldLine.oldNumber,
} }
: undefined; : undefined;
@ -254,13 +252,13 @@ export default class SideBySideRenderer {
? { ? {
prefix: diff.newLine.prefix, prefix: diff.newLine.prefix,
content: diff.newLine.content, content: diff.newLine.content,
type: renderUtils.CSSLineClass.INSERT_CHANGES type: renderUtils.CSSLineClass.INSERT_CHANGES,
} }
: { : {
...renderUtils.deconstructLine(newLine.content, isCombined), ...renderUtils.deconstructLine(newLine.content, isCombined),
type: renderUtils.toCSSClass(newLine.type) type: renderUtils.toCSSClass(newLine.type),
}), }),
number: newLine.newNumber number: newLine.newNumber,
} }
: undefined; : undefined;
@ -275,21 +273,21 @@ export default class SideBySideRenderer {
generateLineHtml(oldLine?: DiffPreparedLine, newLine?: DiffPreparedLine): FileHtml { generateLineHtml(oldLine?: DiffPreparedLine, newLine?: DiffPreparedLine): FileHtml {
return { return {
left: this.generateSingleHtml(oldLine), left: this.generateSingleHtml(oldLine),
right: this.generateSingleHtml(newLine) right: this.generateSingleHtml(newLine),
}; };
} }
generateSingleHtml(line?: DiffPreparedLine): string { generateSingleHtml(line?: DiffPreparedLine): string {
const lineClass = "d2h-code-side-linenumber"; const lineClass = 'd2h-code-side-linenumber';
const contentClass = "d2h-code-side-line"; const contentClass = 'd2h-code-side-line';
return this.hoganUtils.render(genericTemplatesPath, "line", { return this.hoganUtils.render(genericTemplatesPath, 'line', {
type: line?.type || `${renderUtils.CSSLineClass.CONTEXT} d2h-emptyplaceholder`, type: line?.type || `${renderUtils.CSSLineClass.CONTEXT} d2h-emptyplaceholder`,
lineClass: line !== undefined ? lineClass : `${lineClass} d2h-code-side-emptyplaceholder`, lineClass: line !== undefined ? lineClass : `${lineClass} d2h-code-side-emptyplaceholder`,
contentClass: line !== undefined ? contentClass : `${contentClass} d2h-code-side-emptyplaceholder`, contentClass: line !== undefined ? contentClass : `${contentClass} d2h-code-side-emptyplaceholder`,
prefix: line?.prefix === " " ? "&nbsp;" : line?.prefix || "&nbsp;", prefix: line?.prefix === ' ' ? '&nbsp;' : line?.prefix || '&nbsp;',
content: line?.content || "&nbsp;", content: line?.content || '&nbsp;',
lineNumber: line?.number lineNumber: line?.number,
}); });
} }
} }
@ -297,7 +295,7 @@ export default class SideBySideRenderer {
type DiffLineGroups = [ type DiffLineGroups = [
(DiffLineContext & DiffLineContent)[], (DiffLineContext & DiffLineContent)[],
(DiffLineDeleted & DiffLineContent)[], (DiffLineDeleted & DiffLineContent)[],
(DiffLineInserted & DiffLineContent)[] (DiffLineInserted & DiffLineContent)[],
][]; ][];
type DiffPreparedLine = { type DiffPreparedLine = {

View file

@ -4,9 +4,9 @@ export type DiffLineParts = {
}; };
export enum LineType { export enum LineType {
INSERT = "insert", INSERT = 'insert',
DELETE = "delete", DELETE = 'delete',
CONTEXT = "context" CONTEXT = 'context',
} }
export interface DiffLineDeleted { export interface DiffLineDeleted {
@ -70,17 +70,17 @@ export interface DiffFile extends DiffFileName {
} }
export enum OutputFormatType { export enum OutputFormatType {
LINE_BY_LINE = "line-by-line", LINE_BY_LINE = 'line-by-line',
SIDE_BY_SIDE = "side-by-side" SIDE_BY_SIDE = 'side-by-side',
} }
export enum LineMatchingType { export enum LineMatchingType {
LINES = "lines", LINES = 'lines',
WORDS = "words", WORDS = 'words',
NONE = "none" NONE = 'none',
} }
export enum DiffStyleType { export enum DiffStyleType {
WORD = "word", WORD = 'word',
CHAR = "char" CHAR = 'char',
} }

View file

@ -51,7 +51,7 @@
-ms-flex-align: center; -ms-flex-align: center;
align-items: center; align-items: center;
width: 100%; width: 100%;
font-family: "Source Sans Pro", "Helvetica Neue", Helvetica, Arial, sans-serif; font-family: 'Source Sans Pro', 'Helvetica Neue', Helvetica, Arial, sans-serif;
font-size: 15px; font-size: 15px;
} }
@ -70,7 +70,7 @@
.d2h-diff-table { .d2h-diff-table {
width: 100%; width: 100%;
border-collapse: collapse; border-collapse: collapse;
font-family: "Menlo", "Consolas", monospace; font-family: 'Menlo', 'Consolas', monospace;
font-size: 13px; font-size: 13px;
} }

View file

@ -0,0 +1,228 @@
import { HighlightJS, ICompiledMode, IHighlightResult, IAutoHighlightResult } from './highlight.js-interface';
import { nodeStream, mergeStreams } from './highlight.js-helpers';
import { html, Diff2HtmlConfig, defaultDiff2HtmlConfig } from '../../diff2html';
import { DiffFile } from '../../types';
export interface Diff2HtmlUIConfig extends Diff2HtmlConfig {
synchronisedScroll?: boolean;
highlight?: boolean;
fileListToggle?: boolean;
fileListStartVisible?: boolean;
smartSelection?: boolean;
}
export const defaultDiff2HtmlUIConfig = {
...defaultDiff2HtmlConfig,
synchronisedScroll: true,
highlight: true,
fileListToggle: true,
fileListStartVisible: false,
smartSelection: true,
};
export class Diff2HtmlUI {
readonly config: typeof defaultDiff2HtmlUIConfig;
readonly diffHtml: string;
readonly targetElement: HTMLElement;
readonly hljs: HighlightJS | null = null;
currentSelectionColumnId = -1;
constructor(diffInput: string | DiffFile[], target: HTMLElement, config: Diff2HtmlUIConfig = {}, hljs?: HighlightJS) {
this.config = { ...defaultDiff2HtmlUIConfig, ...config };
this.diffHtml = html(diffInput, this.config);
this.targetElement = target;
if (hljs !== undefined) this.hljs = hljs;
}
draw(): void {
this.targetElement.innerHTML = this.diffHtml;
if (this.config.smartSelection) this.initSelection();
if (this.config.synchronisedScroll) this.synchronisedScroll();
if (this.config.highlight) this.highlightCode();
if (this.config.fileListToggle) this.fileListToggle(this.config.fileListStartVisible);
}
synchronisedScroll(): void {
this.targetElement.querySelectorAll('.d2h-file-wrapper').forEach(wrapper => {
const [left, right] = [].slice.call(wrapper.querySelectorAll('.d2h-file-side-diff')) as HTMLElement[];
if (left === undefined || right === undefined) return;
const onScroll = (event: Event): void => {
if (event === null || event.target === null) return;
if (event.target === left) {
right.scrollTop = left.scrollTop;
right.scrollLeft = left.scrollLeft;
} else {
left.scrollTop = right.scrollTop;
left.scrollLeft = right.scrollLeft;
}
};
left.addEventListener('scroll', onScroll);
right.addEventListener('scroll', onScroll);
});
}
fileListToggle(startVisible: boolean): void {
const hashTag = this.getHashTag();
const showBtn = this.targetElement.querySelector('.d2h-show') as HTMLElement;
const hideBtn = this.targetElement.querySelector('.d2h-hide') as HTMLElement;
const fileList = this.targetElement.querySelector('.d2h-file-list') as HTMLElement;
if (showBtn === null || hideBtn === null || fileList === null) return;
function show(): void {
showBtn.style.display = 'none';
hideBtn.style.display = 'inline';
fileList.style.display = 'block';
}
function hide(): void {
showBtn.style.display = 'inline';
hideBtn.style.display = 'none';
fileList.style.display = 'none';
}
showBtn.addEventListener('click', () => show());
hideBtn.addEventListener('click', () => hide());
if (hashTag === 'files-summary-show') show();
else if (hashTag === 'files-summary-hide') hide();
else if (startVisible) show();
else hide();
}
highlightCode(): void {
if (this.hljs === null) {
throw new Error('Missing a `highlight.js` implementation. Please provide one when instantiating Diff2HtmlUI.');
}
// Collect all the diff files and execute the highlight on their lines
const files = this.targetElement.querySelectorAll('.d2h-file-wrapper');
files.forEach(file => {
let oldLinesState: ICompiledMode;
let newLinesState: ICompiledMode;
// Collect all the code lines and execute the highlight on them
const codeLines = file.querySelectorAll('.d2h-code-line-ctn');
codeLines.forEach(line => {
// HACK: help Typescript know that `this.hljs` is defined since we already checked it
if (this.hljs === null) return;
const text = line.textContent;
const lineParent = line.parentNode as HTMLElement;
if (lineParent === null || text === null) return;
const lineState = lineParent.className.indexOf('d2h-del') !== -1 ? oldLinesState : newLinesState;
const language = file.getAttribute('data-lang');
const result =
language && this.hljs.getLanguage(language)
? this.hljs.highlight(language, text, true, lineState)
: this.hljs.highlightAuto(text);
if (this.instanceOfIHighlightResult(result)) {
if (lineParent.className.indexOf('d2h-del') !== -1) {
oldLinesState = result.top;
} else if (lineParent.className.indexOf('d2h-ins') !== -1) {
newLinesState = result.top;
} else {
oldLinesState = result.top;
newLinesState = result.top;
}
}
const originalStream = nodeStream(line);
if (originalStream.length) {
const resultNode = document.createElementNS('http://www.w3.org/1999/xhtml', 'div');
resultNode.innerHTML = result.value;
result.value = mergeStreams(originalStream, nodeStream(resultNode), text);
}
line.classList.add('hljs');
line.classList.add('result.language');
line.innerHTML = result.value;
});
});
}
private instanceOfIHighlightResult(object: IHighlightResult | IAutoHighlightResult): object is IHighlightResult {
return 'top' in object;
}
private getHashTag(): string | null {
const docUrl = document.URL;
const hashTagIndex = docUrl.indexOf('#');
let hashTag = null;
if (hashTagIndex !== -1) {
hashTag = docUrl.substr(hashTagIndex + 1);
}
return hashTag;
}
private initSelection(): void {
const body = document.getElementsByTagName('body')[0];
const diffTable = body.getElementsByClassName('d2h-diff-table')[0];
diffTable.addEventListener('mousedown', event => {
if (event === null || event.target === null) return;
const mouseEvent = event as MouseEvent;
const target = mouseEvent.target as HTMLElement;
const table = target.closest('.d2h-diff-table');
if (table !== null) {
if (target.closest('.d2h-code-line,.d2h-code-side-line') !== null) {
table.classList.remove('selecting-left');
table.classList.add('selecting-right');
this.currentSelectionColumnId = 1;
} else if (target.closest('.d2h-code-linenumber,.d2h-code-side-linenumber') !== null) {
table.classList.remove('selecting-right');
table.classList.add('selecting-left');
this.currentSelectionColumnId = 0;
}
}
});
diffTable.addEventListener('copy', event => {
const clipboardEvent = event as ClipboardEvent;
const clipboardData = clipboardEvent.clipboardData;
const text = this.getSelectedText();
if (clipboardData === null || text === undefined) return;
clipboardData.setData('text', text);
event.preventDefault();
});
}
private getSelectedText(): string | undefined {
const sel = window.getSelection();
if (sel === null) return;
const range = sel.getRangeAt(0);
const doc = range.cloneContents();
const nodes = doc.querySelectorAll('tr');
const idx = this.currentSelectionColumnId;
let text = '';
if (nodes.length === 0) {
text = doc.textContent || '';
} else {
nodes.forEach((tr, i) => {
const td = tr.cells[tr.cells.length === 1 ? 0 : idx];
if (td === undefined || td.textContent === null) return;
text += (i ? '\n' : '') + td.textContent.replace(/\r\n|\r|\n/g, '');
});
}
return text;
}
}

View file

@ -0,0 +1,12 @@
import { hljs } from './highlight.js-slim';
import { DiffFile } from '../../types';
import { Diff2HtmlUI as Diff2HtmlUIBase, Diff2HtmlUIConfig, defaultDiff2HtmlUIConfig } from './diff2html-ui-base';
export class Diff2HtmlUI extends Diff2HtmlUIBase {
constructor(diffInput: string | DiffFile[], target: HTMLElement, config: Diff2HtmlUIConfig = {}) {
super(diffInput, target, config, hljs);
}
}
export { Diff2HtmlUIConfig, defaultDiff2HtmlUIConfig };

View file

@ -1,223 +1,12 @@
import HighlightJS from "highlight.js"; import hljs from 'highlight.js';
import * as HighlightJSInternals from "./highlight.js-internals";
import { html, Diff2HtmlConfig, defaultDiff2HtmlConfig } from "../../diff2html";
import { DiffFile } from "../../types";
export interface Diff2HtmlUIConfig extends Diff2HtmlConfig { import { DiffFile } from '../../types';
synchronisedScroll?: boolean; import { Diff2HtmlUI as Diff2HtmlUIBase, Diff2HtmlUIConfig, defaultDiff2HtmlUIConfig } from './diff2html-ui-base';
highlight?: boolean;
fileListToggle?: boolean;
fileListStartVisible?: boolean;
smartSelection?: boolean;
}
export const defaultDiff2HtmlUIConfig = {
...defaultDiff2HtmlConfig,
synchronisedScroll: true,
highlight: true,
fileListToggle: true,
fileListStartVisible: false,
smartSelection: true
};
export class Diff2HtmlUI {
readonly config: typeof defaultDiff2HtmlUIConfig;
readonly diffHtml: string;
targetElement: HTMLElement;
currentSelectionColumnId = -1;
export class Diff2HtmlUI extends Diff2HtmlUIBase {
constructor(diffInput: string | DiffFile[], target: HTMLElement, config: Diff2HtmlUIConfig = {}) { constructor(diffInput: string | DiffFile[], target: HTMLElement, config: Diff2HtmlUIConfig = {}) {
this.config = { ...defaultDiff2HtmlUIConfig, ...config }; super(diffInput, target, config, hljs);
this.diffHtml = html(diffInput, this.config);
this.targetElement = target;
}
draw(): void {
this.targetElement.innerHTML = this.diffHtml;
if (this.config.smartSelection) this.initSelection();
if (this.config.synchronisedScroll) this.synchronisedScroll();
if (this.config.highlight) this.highlightCode();
if (this.config.fileListToggle) this.fileListToggle(this.config.fileListStartVisible);
}
synchronisedScroll(): void {
this.targetElement.querySelectorAll(".d2h-file-wrapper").forEach(wrapper => {
const [left, right] = [].slice.call(wrapper.querySelectorAll(".d2h-file-side-diff")) as HTMLElement[];
if (left === undefined || right === undefined) return;
const onScroll = (event: Event): void => {
if (event === null || event.target === null) return;
if (event.target === left) {
right.scrollTop = left.scrollTop;
right.scrollLeft = left.scrollLeft;
} else {
left.scrollTop = right.scrollTop;
left.scrollLeft = right.scrollLeft;
}
};
left.addEventListener("scroll", onScroll);
right.addEventListener("scroll", onScroll);
});
}
fileListToggle(startVisible: boolean): void {
const hashTag = this.getHashTag();
const showBtn = this.targetElement.querySelector(".d2h-show") as HTMLElement;
const hideBtn = this.targetElement.querySelector(".d2h-hide") as HTMLElement;
const fileList = this.targetElement.querySelector(".d2h-file-list") as HTMLElement;
if (showBtn === null || hideBtn === null || fileList === null) return;
function show(): void {
showBtn.style.display = "none";
hideBtn.style.display = "inline";
fileList.style.display = "block";
}
function hide(): void {
showBtn.style.display = "inline";
hideBtn.style.display = "none";
fileList.style.display = "none";
}
showBtn.addEventListener("click", () => show());
hideBtn.addEventListener("click", () => hide());
if (hashTag === "files-summary-show") show();
else if (hashTag === "files-summary-hide") hide();
else if (startVisible) show();
else hide();
}
highlightCode(): void {
// Collect all the diff files and execute the highlight on their lines
const files = this.targetElement.querySelectorAll(".d2h-file-wrapper");
files.forEach(file => {
let oldLinesState: HighlightJS.ICompiledMode;
let newLinesState: HighlightJS.ICompiledMode;
// Collect all the code lines and execute the highlight on them
const codeLines = file.querySelectorAll(".d2h-code-line-ctn");
codeLines.forEach(line => {
const text = line.textContent;
const lineParent = line.parentNode as HTMLElement;
if (lineParent === null || text === null) return;
const lineState = lineParent.className.indexOf("d2h-del") !== -1 ? oldLinesState : newLinesState;
const language = file.getAttribute("data-lang");
const result =
language && HighlightJS.getLanguage(language)
? HighlightJS.highlight(language, text, true, lineState)
: HighlightJS.highlightAuto(text);
if (this.instanceOfIHighlightResult(result)) {
if (lineParent.className.indexOf("d2h-del") !== -1) {
oldLinesState = result.top;
} else if (lineParent.className.indexOf("d2h-ins") !== -1) {
newLinesState = result.top;
} else {
oldLinesState = result.top;
newLinesState = result.top;
}
}
const originalStream = HighlightJSInternals.nodeStream(line);
if (originalStream.length) {
const resultNode = document.createElementNS("http://www.w3.org/1999/xhtml", "div");
resultNode.innerHTML = result.value;
result.value = HighlightJSInternals.mergeStreams(
originalStream,
HighlightJSInternals.nodeStream(resultNode),
text
);
}
line.classList.add("hljs");
line.classList.add("result.language");
line.innerHTML = result.value;
});
});
}
private instanceOfIHighlightResult(
object: HighlightJS.IHighlightResult | HighlightJS.IAutoHighlightResult
): object is HighlightJS.IHighlightResult {
return "top" in object;
}
private getHashTag(): string | null {
const docUrl = document.URL;
const hashTagIndex = docUrl.indexOf("#");
let hashTag = null;
if (hashTagIndex !== -1) {
hashTag = docUrl.substr(hashTagIndex + 1);
}
return hashTag;
}
private initSelection(): void {
const body = document.getElementsByTagName("body")[0];
const diffTable = body.getElementsByClassName("d2h-diff-table")[0];
diffTable.addEventListener("mousedown", event => {
if (event === null || event.target === null) return;
const mouseEvent = event as MouseEvent;
const target = mouseEvent.target as HTMLElement;
const table = target.closest(".d2h-diff-table");
if (table !== null) {
if (target.closest(".d2h-code-line,.d2h-code-side-line") !== null) {
table.classList.remove("selecting-left");
table.classList.add("selecting-right");
this.currentSelectionColumnId = 1;
} else if (target.closest(".d2h-code-linenumber,.d2h-code-side-linenumber") !== null) {
table.classList.remove("selecting-right");
table.classList.add("selecting-left");
this.currentSelectionColumnId = 0;
}
}
});
diffTable.addEventListener("copy", event => {
const clipboardEvent = event as ClipboardEvent;
const clipboardData = clipboardEvent.clipboardData;
const text = this.getSelectedText();
if (clipboardData === null || text === undefined) return;
clipboardData.setData("text", text);
event.preventDefault();
});
}
private getSelectedText(): string | undefined {
const sel = window.getSelection();
if (sel === null) return;
const range = sel.getRangeAt(0);
const doc = range.cloneContents();
const nodes = doc.querySelectorAll("tr");
const idx = this.currentSelectionColumnId;
let text = "";
if (nodes.length === 0) {
text = doc.textContent || "";
} else {
nodes.forEach((tr, i) => {
const td = tr.cells[tr.cells.length === 1 ? 0 : idx];
if (td === undefined || td.textContent === null) return;
text += (i ? "\n" : "") + td.textContent.replace(/(?:\r\n|\r|\n)/g, "");
});
}
return text;
} }
} }
export { Diff2HtmlUIConfig, defaultDiff2HtmlUIConfig };

View file

@ -1,15 +1,15 @@
/* /*
* Copied from Highlight.js Private API * Adapted Highlight.js Internal APIs
* Will be removed when this part of the API is exposed * Used to highlight selected html elements using context
*/ */
/* Utility functions */ /* Utility functions */
function escape(value: string): string { function escape(value: string): string {
return value return value
.replace(/&/gm, "&amp;") .replace(/&/gm, '&amp;')
.replace(/</gm, "&lt;") .replace(/</gm, '&lt;')
.replace(/>/gm, "&gt;"); .replace(/>/gm, '&gt;');
} }
function tag(node: Node): string { function tag(node: Node): string {
@ -19,7 +19,7 @@ function tag(node: Node): string {
/* Stream merging */ /* Stream merging */
type NodeEvent = { type NodeEvent = {
event: "start" | "stop"; event: 'start' | 'stop';
offset: number; offset: number;
node: Node; node: Node;
}; };
@ -33,9 +33,9 @@ export function nodeStream(node: Node): NodeEvent[] {
offset += child.nodeValue.length; offset += child.nodeValue.length;
} else if (child.nodeType === 1) { } else if (child.nodeType === 1) {
result.push({ result.push({
event: "start", event: 'start',
offset: offset, offset: offset,
node: child node: child,
}); });
offset = nodeStream(child, offset); offset = nodeStream(child, offset);
// Prevent void elements from having an end tag that would actually // Prevent void elements from having an end tag that would actually
@ -43,9 +43,9 @@ export function nodeStream(node: Node): NodeEvent[] {
// but we list only those realistically expected in code display. // but we list only those realistically expected in code display.
if (!tag(child).match(/br|hr|img|input/)) { if (!tag(child).match(/br|hr|img|input/)) {
result.push({ result.push({
event: "stop", event: 'stop',
offset: offset, offset: offset,
node: child node: child,
}); });
} }
} }
@ -60,7 +60,7 @@ export function nodeStream(node: Node): NodeEvent[] {
export function mergeStreams(original: NodeEvent[], highlighted: NodeEvent[], value: string): string { export function mergeStreams(original: NodeEvent[], highlighted: NodeEvent[], value: string): string {
let processed = 0; let processed = 0;
let result = ""; let result = '';
const nodeStack = []; const nodeStack = [];
function selectStream(): NodeEvent[] { function selectStream(): NodeEvent[] {
@ -84,22 +84,22 @@ export function mergeStreams(original: NodeEvent[], highlighted: NodeEvent[], va
return highlighted; return highlighted;
... which is collapsed to: ... which is collapsed to:
*/ */
return highlighted[0].event === "start" ? original : highlighted; return highlighted[0].event === 'start' ? original : highlighted;
} }
function open(node: Node): void { function open(node: Node): void {
const htmlNode = node as HTMLElement; const htmlNode = node as HTMLElement;
result += `<${tag(node)} ${[].map result += `<${tag(node)} ${[].map
.call(htmlNode.attributes, (attr: Attr) => `${attr.nodeName}="${escape(attr.value)}"`) .call(htmlNode.attributes, (attr: Attr) => `${attr.nodeName}="${escape(attr.value)}"`)
.join(" ")}>`; .join(' ')}>`;
} }
function close(node: Node): void { function close(node: Node): void {
result += "</" + tag(node) + ">"; result += '</' + tag(node) + '>';
} }
function render(event: NodeEvent): void { function render(event: NodeEvent): void {
(event.event === "start" ? open : close)(event.node); (event.event === 'start' ? open : close)(event.node);
} }
while (original.length || highlighted.length) { while (original.length || highlighted.length) {
@ -120,7 +120,7 @@ export function mergeStreams(original: NodeEvent[], highlighted: NodeEvent[], va
} while (stream === original && stream.length && stream[0].offset === processed); } while (stream === original && stream.length && stream[0].offset === processed);
nodeStack.reverse().forEach(open); nodeStack.reverse().forEach(open);
} else { } else {
if (stream[0].event === "start") { if (stream[0].event === 'start') {
nodeStack.push(stream[0].node); nodeStack.push(stream[0].node);
} else { } else {
nodeStack.pop(); nodeStack.pop();
@ -130,5 +130,3 @@ export function mergeStreams(original: NodeEvent[], highlighted: NodeEvent[], va
} }
return result + escape(value.substr(processed)); return result + escape(value.substr(processed));
} }
/* **** Highlight.js Private API **** */

View file

@ -0,0 +1,69 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/ban-types */
/* eslint-disable @typescript-eslint/interface-name-prefix */
/* eslint-disable @typescript-eslint/camelcase */
export interface HighlightJS {
highlight(name: string, value: string, ignore_illegals?: boolean, continuation?: ICompiledMode): IHighlightResult;
highlightAuto(value: string, languageSubset?: string[]): IAutoHighlightResult;
getLanguage(name: string): IMode;
}
export interface IAutoHighlightResult extends IHighlightResultBase {
second_best?: IAutoHighlightResult;
}
export interface IHighlightResultBase {
relevance: number;
language: string;
value: string;
}
export interface IHighlightResult extends IHighlightResultBase {
top: ICompiledMode;
}
export interface IMode extends IModeBase {
keywords?: any;
contains?: IMode[];
}
// Reference:
// https://github.com/isagalaev/highlight.js/blob/master/docs/reference.rst
export interface IModeBase {
className?: string;
aliases?: string[];
begin?: string | RegExp;
end?: string | RegExp;
case_insensitive?: boolean;
beginKeyword?: string;
endsWithParent?: boolean;
lexems?: string;
illegal?: string;
excludeBegin?: boolean;
excludeEnd?: boolean;
returnBegin?: boolean;
returnEnd?: boolean;
starts?: string;
subLanguage?: string;
subLanguageMode?: string;
relevance?: number;
variants?: IMode[];
}
export interface ICompiledMode extends IModeBase {
compiled: boolean;
contains?: ICompiledMode[];
keywords?: Object;
terminators: RegExp;
terminator_end?: string;
}
export interface IOptions {
classPrefix?: string;
tabReplace?: string;
useBR?: boolean;
languages?: string[];
}

View file

@ -0,0 +1,203 @@
import { HighlightJS } from './highlight.js-interface';
/* eslint-disable @typescript-eslint/camelcase */
/* eslint-disable @typescript-eslint/no-var-requires */
/* eslint-disable @typescript-eslint/interface-name-prefix */
/* eslint-disable @typescript-eslint/ban-types */
/* eslint-disable @typescript-eslint/no-explicit-any */
/*
* Adapted Highlight.js External APIs
* Used to avoid importing all the languages
*/
// Require the highlight.js library without languages
const highlightJS = require('highlight.js/lib/highlight.js');
// Separately require languages
// highlightJS.registerLanguage('1c', require('highlight.js/lib/languages/1c'));
// highlightJS.registerLanguage('abnf', require('highlight.js/lib/languages/abnf'));
// highlightJS.registerLanguage('accesslog', require('highlight.js/lib/languages/accesslog'));
// highlightJS.registerLanguage('actionscript', require('highlight.js/lib/languages/actionscript'));
// highlightJS.registerLanguage('ada', require('highlight.js/lib/languages/ada'));
// highlightJS.registerLanguage('angelscript', require('highlight.js/lib/languages/angelscript'));
// highlightJS.registerLanguage('apache', require('highlight.js/lib/languages/apache'));
// highlightJS.registerLanguage('applescript', require('highlight.js/lib/languages/applescript'));
// highlightJS.registerLanguage('arcade', require('highlight.js/lib/languages/arcade'));
highlightJS.registerLanguage('cpp', require('highlight.js/lib/languages/cpp'));
// highlightJS.registerLanguage('arduino', require('highlight.js/lib/languages/arduino'));
// highlightJS.registerLanguage('armasm', require('highlight.js/lib/languages/armasm'));
highlightJS.registerLanguage('xml', require('highlight.js/lib/languages/xml'));
// highlightJS.registerLanguage('asciidoc', require('highlight.js/lib/languages/asciidoc'));
// highlightJS.registerLanguage('aspectj', require('highlight.js/lib/languages/aspectj'));
// highlightJS.registerLanguage('autohotkey', require('highlight.js/lib/languages/autohotkey'));
// highlightJS.registerLanguage('autoit', require('highlight.js/lib/languages/autoit'));
// highlightJS.registerLanguage('avrasm', require('highlight.js/lib/languages/avrasm'));
highlightJS.registerLanguage('awk', require('highlight.js/lib/languages/awk'));
// highlightJS.registerLanguage('axapta', require('highlight.js/lib/languages/axapta'));
highlightJS.registerLanguage('bash', require('highlight.js/lib/languages/bash'));
// highlightJS.registerLanguage('basic', require('highlight.js/lib/languages/basic'));
// highlightJS.registerLanguage('bnf', require('highlight.js/lib/languages/bnf'));
// highlightJS.registerLanguage('brainfuck', require('highlight.js/lib/languages/brainfuck'));
// highlightJS.registerLanguage('cal', require('highlight.js/lib/languages/cal'));
// highlightJS.registerLanguage('capnproto', require('highlight.js/lib/languages/capnproto'));
// highlightJS.registerLanguage('ceylon', require('highlight.js/lib/languages/ceylon'));
// highlightJS.registerLanguage('clean', require('highlight.js/lib/languages/clean'));
highlightJS.registerLanguage('clojure', require('highlight.js/lib/languages/clojure'));
highlightJS.registerLanguage('clojure-repl', require('highlight.js/lib/languages/clojure-repl'));
highlightJS.registerLanguage('cmake', require('highlight.js/lib/languages/cmake'));
highlightJS.registerLanguage('coffeescript', require('highlight.js/lib/languages/coffeescript'));
// highlightJS.registerLanguage('coq', require('highlight.js/lib/languages/coq'));
// highlightJS.registerLanguage('cos', require('highlight.js/lib/languages/cos'));
// highlightJS.registerLanguage('crmsh', require('highlight.js/lib/languages/crmsh'));
highlightJS.registerLanguage('crystal', require('highlight.js/lib/languages/crystal'));
highlightJS.registerLanguage('cs', require('highlight.js/lib/languages/cs'));
highlightJS.registerLanguage('csp', require('highlight.js/lib/languages/csp'));
highlightJS.registerLanguage('css', require('highlight.js/lib/languages/css'));
highlightJS.registerLanguage('d', require('highlight.js/lib/languages/d'));
highlightJS.registerLanguage('markdown', require('highlight.js/lib/languages/markdown'));
highlightJS.registerLanguage('dart', require('highlight.js/lib/languages/dart'));
// highlightJS.registerLanguage('delphi', require('highlight.js/lib/languages/delphi'));
highlightJS.registerLanguage('diff', require('highlight.js/lib/languages/diff'));
highlightJS.registerLanguage('django', require('highlight.js/lib/languages/django'));
// highlightJS.registerLanguage('dns', require('highlight.js/lib/languages/dns'));
highlightJS.registerLanguage('dockerfile', require('highlight.js/lib/languages/dockerfile'));
// highlightJS.registerLanguage('dos', require('highlight.js/lib/languages/dos'));
// highlightJS.registerLanguage('dsconfig', require('highlight.js/lib/languages/dsconfig'));
// highlightJS.registerLanguage('dts', require('highlight.js/lib/languages/dts'));
// highlightJS.registerLanguage('dust', require('highlight.js/lib/languages/dust'));
// highlightJS.registerLanguage('ebnf', require('highlight.js/lib/languages/ebnf'));
highlightJS.registerLanguage('elixir', require('highlight.js/lib/languages/elixir'));
highlightJS.registerLanguage('elm', require('highlight.js/lib/languages/elm'));
highlightJS.registerLanguage('ruby', require('highlight.js/lib/languages/ruby'));
highlightJS.registerLanguage('erb', require('highlight.js/lib/languages/erb'));
highlightJS.registerLanguage('erlang-repl', require('highlight.js/lib/languages/erlang-repl'));
highlightJS.registerLanguage('erlang', require('highlight.js/lib/languages/erlang'));
highlightJS.registerLanguage('excel', require('highlight.js/lib/languages/excel'));
// highlightJS.registerLanguage('fix', require('highlight.js/lib/languages/fix'));
// highlightJS.registerLanguage('flix', require('highlight.js/lib/languages/flix'));
// highlightJS.registerLanguage('fortran', require('highlight.js/lib/languages/fortran'));
highlightJS.registerLanguage('fsharp', require('highlight.js/lib/languages/fsharp'));
// highlightJS.registerLanguage('gams', require('highlight.js/lib/languages/gams'));
// highlightJS.registerLanguage('gauss', require('highlight.js/lib/languages/gauss'));
// highlightJS.registerLanguage('gcode', require('highlight.js/lib/languages/gcode'));
// highlightJS.registerLanguage('gherkin', require('highlight.js/lib/languages/gherkin'));
// highlightJS.registerLanguage('glsl', require('highlight.js/lib/languages/glsl'));
// highlightJS.registerLanguage('gml', require('highlight.js/lib/languages/gml'));
highlightJS.registerLanguage('go', require('highlight.js/lib/languages/go'));
// highlightJS.registerLanguage('golo', require('highlight.js/lib/languages/golo'));
highlightJS.registerLanguage('gradle', require('highlight.js/lib/languages/gradle'));
highlightJS.registerLanguage('groovy', require('highlight.js/lib/languages/groovy'));
// highlightJS.registerLanguage('haml', require('highlight.js/lib/languages/haml'));
highlightJS.registerLanguage('handlebars', require('highlight.js/lib/languages/handlebars'));
highlightJS.registerLanguage('haskell', require('highlight.js/lib/languages/haskell'));
// highlightJS.registerLanguage('haxe', require('highlight.js/lib/languages/haxe'));
// highlightJS.registerLanguage('hsp', require('highlight.js/lib/languages/hsp'));
highlightJS.registerLanguage('htmlbars', require('highlight.js/lib/languages/htmlbars'));
highlightJS.registerLanguage('http', require('highlight.js/lib/languages/http'));
// highlightJS.registerLanguage('hy', require('highlight.js/lib/languages/hy'));
// highlightJS.registerLanguage('inform7', require('highlight.js/lib/languages/inform7'));
highlightJS.registerLanguage('ini', require('highlight.js/lib/languages/ini'));
// highlightJS.registerLanguage('irpf90', require('highlight.js/lib/languages/irpf90'));
// highlightJS.registerLanguage('isbl', require('highlight.js/lib/languages/isbl'));
highlightJS.registerLanguage('java', require('highlight.js/lib/languages/java'));
highlightJS.registerLanguage('javascript', require('highlight.js/lib/languages/javascript'));
// highlightJS.registerLanguage('jboss-cli', require('highlight.js/lib/languages/jboss-cli'));
highlightJS.registerLanguage('json', require('highlight.js/lib/languages/json'));
highlightJS.registerLanguage('julia', require('highlight.js/lib/languages/julia'));
highlightJS.registerLanguage('julia-repl', require('highlight.js/lib/languages/julia-repl'));
highlightJS.registerLanguage('kotlin', require('highlight.js/lib/languages/kotlin'));
// highlightJS.registerLanguage('lasso', require('highlight.js/lib/languages/lasso'));
// highlightJS.registerLanguage('ldif', require('highlight.js/lib/languages/ldif'));
// highlightJS.registerLanguage('leaf', require('highlight.js/lib/languages/leaf'));
highlightJS.registerLanguage('less', require('highlight.js/lib/languages/less'));
highlightJS.registerLanguage('lisp', require('highlight.js/lib/languages/lisp'));
// highlightJS.registerLanguage('livecodeserver', require('highlight.js/lib/languages/livecodeserver'));
// highlightJS.registerLanguage('livescript', require('highlight.js/lib/languages/livescript'));
highlightJS.registerLanguage('llvm', require('highlight.js/lib/languages/llvm'));
// highlightJS.registerLanguage('lsl', require('highlight.js/lib/languages/lsl'));
highlightJS.registerLanguage('lua', require('highlight.js/lib/languages/lua'));
highlightJS.registerLanguage('makefile', require('highlight.js/lib/languages/makefile'));
highlightJS.registerLanguage('mathematica', require('highlight.js/lib/languages/mathematica'));
highlightJS.registerLanguage('matlab', require('highlight.js/lib/languages/matlab'));
// highlightJS.registerLanguage('maxima', require('highlight.js/lib/languages/maxima'));
// highlightJS.registerLanguage('mel', require('highlight.js/lib/languages/mel'));
// highlightJS.registerLanguage('mercury', require('highlight.js/lib/languages/mercury'));
// highlightJS.registerLanguage('mipsasm', require('highlight.js/lib/languages/mipsasm'));
// highlightJS.registerLanguage('mizar', require('highlight.js/lib/languages/mizar'));
highlightJS.registerLanguage('perl', require('highlight.js/lib/languages/perl'));
// highlightJS.registerLanguage('mojolicious', require('highlight.js/lib/languages/mojolicious'));
// highlightJS.registerLanguage('monkey', require('highlight.js/lib/languages/monkey'));
// highlightJS.registerLanguage('moonscript', require('highlight.js/lib/languages/moonscript'));
// highlightJS.registerLanguage('n1ql', require('highlight.js/lib/languages/n1ql'));
highlightJS.registerLanguage('nginx', require('highlight.js/lib/languages/nginx'));
// highlightJS.registerLanguage('nimrod', require('highlight.js/lib/languages/nimrod'));
highlightJS.registerLanguage('nix', require('highlight.js/lib/languages/nix'));
// highlightJS.registerLanguage('nsis', require('highlight.js/lib/languages/nsis'));
highlightJS.registerLanguage('objectivec', require('highlight.js/lib/languages/objectivec'));
highlightJS.registerLanguage('ocaml', require('highlight.js/lib/languages/ocaml'));
// highlightJS.registerLanguage('openscad', require('highlight.js/lib/languages/openscad'));
// highlightJS.registerLanguage('oxygene', require('highlight.js/lib/languages/oxygene'));
// highlightJS.registerLanguage('parser3', require('highlight.js/lib/languages/parser3'));
// highlightJS.registerLanguage('pf', require('highlight.js/lib/languages/pf'));
highlightJS.registerLanguage('pgsql', require('highlight.js/lib/languages/pgsql'));
highlightJS.registerLanguage('php', require('highlight.js/lib/languages/php'));
highlightJS.registerLanguage('plaintext', require('highlight.js/lib/languages/plaintext'));
// highlightJS.registerLanguage('pony', require('highlight.js/lib/languages/pony'));
highlightJS.registerLanguage('powershell', require('highlight.js/lib/languages/powershell'));
// highlightJS.registerLanguage('processing', require('highlight.js/lib/languages/processing'));
// highlightJS.registerLanguage('profile', require('highlight.js/lib/languages/profile'));
// highlightJS.registerLanguage('prolog', require('highlight.js/lib/languages/prolog'));
highlightJS.registerLanguage('properties', require('highlight.js/lib/languages/properties'));
highlightJS.registerLanguage('protobuf', require('highlight.js/lib/languages/protobuf'));
highlightJS.registerLanguage('puppet', require('highlight.js/lib/languages/puppet'));
// highlightJS.registerLanguage('purebasic', require('highlight.js/lib/languages/purebasic'));
highlightJS.registerLanguage('python', require('highlight.js/lib/languages/python'));
// highlightJS.registerLanguage('q', require('highlight.js/lib/languages/q'));
// highlightJS.registerLanguage('qml', require('highlight.js/lib/languages/qml'));
highlightJS.registerLanguage('r', require('highlight.js/lib/languages/r'));
highlightJS.registerLanguage('reasonml', require('highlight.js/lib/languages/reasonml'));
// highlightJS.registerLanguage('rib', require('highlight.js/lib/languages/rib'));
// highlightJS.registerLanguage('roboconf', require('highlight.js/lib/languages/roboconf'));
// highlightJS.registerLanguage('routeros', require('highlight.js/lib/languages/routeros'));
// highlightJS.registerLanguage('rsl', require('highlight.js/lib/languages/rsl'));
// highlightJS.registerLanguage('ruleslanguage', require('highlight.js/lib/languages/ruleslanguage'));
highlightJS.registerLanguage('rust', require('highlight.js/lib/languages/rust'));
// highlightJS.registerLanguage('sas', require('highlight.js/lib/languages/sas'));
highlightJS.registerLanguage('scala', require('highlight.js/lib/languages/scala'));
highlightJS.registerLanguage('scheme', require('highlight.js/lib/languages/scheme'));
// highlightJS.registerLanguage('scilab', require('highlight.js/lib/languages/scilab'));
highlightJS.registerLanguage('scss', require('highlight.js/lib/languages/scss'));
highlightJS.registerLanguage('shell', require('highlight.js/lib/languages/shell'));
// highlightJS.registerLanguage('smali', require('highlight.js/lib/languages/smali'));
// highlightJS.registerLanguage('smalltalk', require('highlight.js/lib/languages/smalltalk'));
// highlightJS.registerLanguage('sml', require('highlight.js/lib/languages/sml'));
// highlightJS.registerLanguage('sqf', require('highlight.js/lib/languages/sqf'));
highlightJS.registerLanguage('sql', require('highlight.js/lib/languages/sql'));
// highlightJS.registerLanguage('stan', require('highlight.js/lib/languages/stan'));
// highlightJS.registerLanguage('stata', require('highlight.js/lib/languages/stata'));
// highlightJS.registerLanguage('step21', require('highlight.js/lib/languages/step21'));
highlightJS.registerLanguage('stylus', require('highlight.js/lib/languages/stylus'));
// highlightJS.registerLanguage('subunit', require('highlight.js/lib/languages/subunit'));
highlightJS.registerLanguage('swift', require('highlight.js/lib/languages/swift'));
// highlightJS.registerLanguage('taggerscript', require('highlight.js/lib/languages/taggerscript'));
highlightJS.registerLanguage('yaml', require('highlight.js/lib/languages/yaml'));
// highlightJS.registerLanguage('tap', require('highlight.js/lib/languages/tap'));
// highlightJS.registerLanguage('tcl', require('highlight.js/lib/languages/tcl'));
highlightJS.registerLanguage('tex', require('highlight.js/lib/languages/tex'));
// highlightJS.registerLanguage('thrift', require('highlight.js/lib/languages/thrift'));
// highlightJS.registerLanguage('tp', require('highlight.js/lib/languages/tp'));
// highlightJS.registerLanguage('twig', require('highlight.js/lib/languages/twig'));
highlightJS.registerLanguage('typescript', require('highlight.js/lib/languages/typescript'));
// highlightJS.registerLanguage('vala', require('highlight.js/lib/languages/vala'));
// highlightJS.registerLanguage('vbnet', require('highlight.js/lib/languages/vbnet'));
// highlightJS.registerLanguage('vbscript', require('highlight.js/lib/languages/vbscript'));
// highlightJS.registerLanguage('vbscript-html', require('highlight.js/lib/languages/vbscript-html'));
// highlightJS.registerLanguage('verilog', require('highlight.js/lib/languages/verilog'));
// highlightJS.registerLanguage('vhdl', require('highlight.js/lib/languages/vhdl'));
highlightJS.registerLanguage('vim', require('highlight.js/lib/languages/vim'));
// highlightJS.registerLanguage('x86asm', require('highlight.js/lib/languages/x86asm'));
// highlightJS.registerLanguage('xl', require('highlight.js/lib/languages/xl'));
// highlightJS.registerLanguage('xquery', require('highlight.js/lib/languages/xquery'));
// highlightJS.registerLanguage('zephir', require('highlight.js/lib/languages/zephir'));
export const hljs: HighlightJS = highlightJS as HighlightJS;

View file

@ -1,40 +1,40 @@
const specials = [ const specials = [
// Order matters for these // Order matters for these
"-", '-',
"[", '[',
"]", ']',
// Order doesn't matter for any of these // Order doesn't matter for any of these
"/", '/',
"{", '{',
"}", '}',
"(", '(',
")", ')',
"*", '*',
"+", '+',
"?", '?',
".", '.',
"\\", '\\',
"^", '^',
"$", '$',
"|" '|',
]; ];
// All characters will be escaped with '\' // All characters will be escaped with '\'
// even though only some strictly require it when inside of [] // even though only some strictly require it when inside of []
const regex = RegExp("[" + specials.join("\\") + "]", "g"); const regex = RegExp('[' + specials.join('\\') + ']', 'g');
/** /**
* Escapes all required characters for safe usage inside a RegExp * Escapes all required characters for safe usage inside a RegExp
*/ */
export function escapeForRegExp(str: string): string { export function escapeForRegExp(str: string): string {
return str.replace(regex, "\\$&"); return str.replace(regex, '\\$&');
} }
/** /**
* Converts all '\' in @path to unix style '/' * Converts all '\' in @path to unix style '/'
*/ */
export function unifyPath(path: string): string { export function unifyPath(path: string): string {
return path ? path.replace(/\\/g, "/") : path; return path ? path.replace(/\\/g, '/') : path;
} }
/** /**

2
typings/merge.d.ts vendored
View file

@ -1,3 +1,3 @@
declare module "merge" { declare module 'merge' {
export function recursive(clone: boolean, ...items: object[]): object; export function recursive(clone: boolean, ...items: object[]): object;
} }

View file

@ -1,60 +1,61 @@
import path from "path"; import path from 'path';
import webpack from "webpack"; import webpack from 'webpack';
const minimize = process.env.WEBPACK_MINIFY === "true";
const diff2htmlBrowserConfig: webpack.Configuration = { const diff2htmlBrowserConfig: webpack.Configuration = {
mode: "production",
optimization: { minimize: minimize },
module: { module: {
rules: [ rules: [
{ {
test: /\.tsx?$/, test: /\.tsx?$/,
use: "ts-loader", use: 'ts-loader',
exclude: /node_modules/ exclude: /node_modules/,
} },
] ],
}, },
resolve: { resolve: {
extensions: [".tsx", ".ts", ".jsx", ".js"] extensions: ['.tsx', '.ts', '.jsx', '.js'],
}, },
entry: "./src/diff2html.ts", entry: './src/diff2html.ts',
output: { output: {
path: path.resolve(__dirname, "bundles/js"), path: path.resolve(__dirname, 'bundles/js'),
libraryTarget: "umd", libraryTarget: 'umd',
globalObject: "this", globalObject: 'this',
library: "Diff2Html", library: 'Diff2Html',
filename: `diff2html${minimize ? ".min" : ""}.js`, filename: 'diff2html.min.js',
umdNamedDefine: true umdNamedDefine: true,
} },
}; };
const diff2htmlUIBrowserConfig: webpack.Configuration = { function diff2htmlUIBrowserConfig(entrypointName: string): webpack.Configuration {
mode: "production", return {
optimization: { minimize: minimize }, module: {
module: { rules: [
rules: [ {
{ test: /\.tsx?$/,
test: /\.tsx?$/, use: 'ts-loader',
use: "ts-loader", exclude: /node_modules/,
exclude: /node_modules/ },
} ],
] },
}, resolve: {
resolve: { extensions: ['.tsx', '.ts', '.jsx', '.js'],
extensions: [".tsx", ".ts", ".jsx", ".js"] },
}, entry: `./src/ui/js/${entrypointName}.ts`,
entry: "./src/ui/js/diff2html-ui.ts", output: {
output: { path: path.resolve(__dirname, 'bundles/js'),
path: path.resolve(__dirname, "bundles/js"), libraryTarget: 'umd',
libraryTarget: "umd", globalObject: 'this',
globalObject: "this", filename: `${entrypointName}.min.js`,
filename: `diff2html-ui${minimize ? ".min" : ""}.js`, umdNamedDefine: true,
umdNamedDefine: true },
} };
}; }
const config: webpack.Configuration[] = [diff2htmlBrowserConfig, diff2htmlUIBrowserConfig]; const config: webpack.Configuration[] = [
diff2htmlBrowserConfig,
diff2htmlUIBrowserConfig('diff2html-ui'),
diff2htmlUIBrowserConfig('diff2html-ui-slim'),
diff2htmlUIBrowserConfig('diff2html-ui-base'),
];
export default config; export default config;

View file

@ -1,140 +1,116 @@
import path from "path"; import path from 'path';
import webpack from "webpack"; import webpack from 'webpack';
import { Plugin } from "postcss"; import HtmlWebpackPlugin from 'html-webpack-plugin';
import HtmlWebpackPlugin from "html-webpack-plugin"; import MiniCssExtractPlugin from 'mini-css-extract-plugin';
import MiniCssExtractPlugin from "mini-css-extract-plugin"; import CopyWebpackPlugin from 'copy-webpack-plugin';
import autoprefixer from "autoprefixer";
import CopyWebpackPlugin from "copy-webpack-plugin";
const isDevelopment = process.env.NODE_ENV !== "production"; const pages = ['index', 'demo'];
const minimize = process.env.WEBPACK_MINIFY === "true";
const pages = ["index", "demo"];
const config: webpack.Configuration[] = pages.map(page => { const config: webpack.Configuration[] = pages.map(page => {
return { return {
devServer: { devServer: {
port: 3000, port: 3000,
open: true, open: true,
contentBase: path.join(__dirname, "./website") contentBase: path.join(__dirname, './website'),
}, },
entry: { entry: {
[page]: `./website/templates/pages/${page}/${page}.ts` [page]: `./website/templates/pages/${page}/${page}.ts`,
}, },
output: { output: {
path: path.resolve(__dirname, "./docs") path: path.resolve(__dirname, './docs'),
}, },
resolve: { resolve: {
extensions: [".tsx", ".ts", ".jsx", ".js"] extensions: ['.tsx', '.ts', '.jsx', '.js'],
}, },
optimization: { minimize },
module: { module: {
rules: [ rules: [
{ {
test: /\.tsx?$/, test: /\.tsx?$/,
use: "ts-loader", use: 'ts-loader',
exclude: /node_modules/ exclude: /node_modules/,
}, },
{ {
test: /\.handlebars$/, test: /\.handlebars$/,
loader: "handlebars-loader", loader: 'handlebars-loader',
options: { options: {
precompileOptions: { precompileOptions: {
knownHelpersOnly: false knownHelpersOnly: false,
}, },
helperDirs: [path.join(__dirname, "website/templates/helpers")], helperDirs: [path.join(__dirname, 'website/templates/helpers')],
partialDirs: [path.join(__dirname, "website/templates")] partialDirs: [path.join(__dirname, 'website/templates')],
} },
}, },
{ {
test: /\.(css)$/, test: /\.(css)$/,
use: [ use: [MiniCssExtractPlugin.loader, { loader: 'css-loader', options: { importLoaders: 1 } }, 'postcss-loader'],
MiniCssExtractPlugin.loader,
{
loader: "css-loader",
options: {
sourceMap: isDevelopment
}
},
{
loader: "postcss-loader",
options: {
autoprefixer: {
browsers: ["last 2 versions"]
},
sourceMap: isDevelopment,
plugins: (): Plugin<object>[] => [autoprefixer]
}
}
]
}, },
{ {
test: /\.(html)$/, test: /\.(html)$/,
use: { use: {
loader: "html-loader", loader: 'html-loader',
options: { options: {
attrs: ["img:src"] attrs: ['img:src'],
} },
} },
}, },
{ {
test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, test: /\.woff(2)?(\?v=\d\.\d\.\d)?$/,
use: [ use: [
{ {
loader: "url-loader", loader: 'url-loader',
options: { options: {
limit: 1000, limit: 1000,
mimetype: "application/font-woff" mimetype: 'application/font-woff',
} },
} },
] ],
}, },
{ {
test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/, test: /\.(ttf|eot|svg)(\?v=\d\.\d\.\d)?$/,
loader: "file-loader" loader: 'file-loader',
}, },
{ {
test: /\.(jpeg|jpg|png|gif)$/, test: /\.(jpeg|jpg|png|gif)$/,
use: [ use: [
{ {
loader: "file-loader", loader: 'file-loader',
options: { options: {
name: "[name].[ext]", name: '[name].[ext]',
outputPath: "images/", outputPath: 'images/',
useRelativePath: true useRelativePath: true,
} },
}, },
{ {
loader: "image-webpack-loader", loader: 'image-webpack-loader',
options: { options: {
mozjpeg: { mozjpeg: {
progressive: true, progressive: true,
quality: 65 quality: 65,
}, },
optipng: { optipng: {
enabled: true enabled: true,
}, },
pngquant: { pngquant: {
quality: [0.65, 0.9], quality: [0.65, 0.9],
speed: 4 speed: 4,
}, },
gifsicle: { gifsicle: {
interlaced: false interlaced: false,
}, },
webp: { webp: {
quality: 75 quality: 75,
} },
} },
} },
] ],
} },
] ],
}, },
plugins: [ plugins: [
new MiniCssExtractPlugin({ new MiniCssExtractPlugin({
filename: "[name].css", filename: '[name].css',
chunkFilename: "[id].css" chunkFilename: '[id].css',
}), }),
new HtmlWebpackPlugin({ new HtmlWebpackPlugin({
hash: true, hash: true,
@ -142,7 +118,7 @@ const config: webpack.Configuration[] = pages.map(page => {
title: `${page} page`, title: `${page} page`,
filename: `${page}.html`, filename: `${page}.html`,
template: `./website/templates/pages/${page}/${page}.handlebars`, template: `./website/templates/pages/${page}/${page}.handlebars`,
minify: !isDevelopment && { minify: {
html5: true, html5: true,
collapseWhitespace: true, collapseWhitespace: true,
caseSensitive: true, caseSensitive: true,
@ -155,15 +131,15 @@ const config: webpack.Configuration[] = pages.map(page => {
keepClosingSlash: true, keepClosingSlash: true,
minifyJS: true, minifyJS: true,
minifyCSS: true, minifyCSS: true,
minifyURLs: true minifyURLs: true,
} },
}), }),
new CopyWebpackPlugin([ new CopyWebpackPlugin([
{ from: "website/favicon.ico", to: "favicon.ico" }, { from: 'website/favicon.ico', to: 'favicon.ico' },
{ from: "website/robots.txt", to: "robots.txt" }, { from: 'website/robots.txt', to: 'robots.txt' },
{ from: "website/sitemap.xml", to: "sitemap.xml" } { from: 'website/sitemap.xml', to: 'sitemap.xml' },
]) ]),
] ],
}; };
}); });

View file

@ -17,11 +17,11 @@
} }
.m-b-md { .m-b-md {
margin-bottom: 23px !important margin-bottom: 23px !important;
} }
.p-t { .p-t {
padding-top: 15px !important padding-top: 15px !important;
} }
@media (min-width: 768px) { @media (min-width: 768px) {
@ -34,8 +34,8 @@
.btn { .btn {
display: inline-block; display: inline-block;
color: #fff; color: #fff;
background: #26A65B; background: #26a65b;
font-weight: 400 font-weight: 400;
} }
.btn:hover { .btn:hover {
@ -67,19 +67,19 @@
padding: 40px 0; padding: 40px 0;
text-align: center; text-align: center;
font-size: 14px; font-size: 14px;
border-top: 1px solid #dcdfe4 border-top: 1px solid #dcdfe4;
} }
.footer p { .footer p {
margin-bottom: 5px margin-bottom: 5px;
} }
.footer a { .footer a {
color: #26A65B; color: #26a65b;
} }
.container a { .container a {
color: #26A65B; color: #26a65b;
} }
.container a.btn { .container a.btn {
@ -87,11 +87,11 @@
} }
.footer-list-item { .footer-list-item {
display: inline-block display: inline-block;
} }
.footer-list-item:not(:last-child):after { .footer-list-item:not(:last-child):after {
content: "\b7" content: '\b7';
} }
.footer > ul { .footer > ul {
@ -116,7 +116,7 @@
} }
.row-bordered { .row-bordered {
position: relative position: relative;
} }
.row-bordered:before { .row-bordered:before {
@ -129,14 +129,14 @@
margin-left: -40%; margin-left: -40%;
height: 1px; height: 1px;
background: -webkit-radial-gradient(ellipse at center, rgba(0, 0, 0, 0.2) 0, rgba(255, 255, 255, 0) 75%); background: -webkit-radial-gradient(ellipse at center, rgba(0, 0, 0, 0.2) 0, rgba(255, 255, 255, 0) 75%);
background: radial-gradient(ellipse at center, rgba(0, 0, 0, 0.2) 0, rgba(255, 255, 255, 0) 75%) background: radial-gradient(ellipse at center, rgba(0, 0, 0, 0.2) 0, rgba(255, 255, 255, 0) 75%);
} }
.hero { .hero {
position: relative; position: relative;
text-align: center; text-align: center;
padding: 80px 0; padding: 80px 0;
border-bottom: 1px solid #dcdfe4 border-bottom: 1px solid #dcdfe4;
} }
.hero-booticon { .hero-booticon {
@ -159,7 +159,7 @@
} }
.hero-homepage > .btn { .hero-homepage > .btn {
margin-top: 20px margin-top: 20px;
} }
.swag-line:before { .swag-line:before {
@ -171,9 +171,9 @@
right: 0; right: 0;
height: 5px; height: 5px;
z-index: 2; z-index: 2;
background-color: #26A65B; background-color: #26a65b;
background: -webkit-linear-gradient(45deg, #28a142, #26A65B); background: -webkit-linear-gradient(45deg, #28a142, #26a65b);
background: linear-gradient(45deg, #28a142, #26A65B) background: linear-gradient(45deg, #28a142, #26a65b);
} }
.navbar { .navbar {
@ -182,7 +182,7 @@
} }
.navbar-header { .navbar-header {
text-align: center text-align: center;
} }
.navbar-brand { .navbar-brand {
@ -192,21 +192,26 @@
display: inline-block; display: inline-block;
float: none; float: none;
text-align: center; text-align: center;
margin: 5px 0 0 margin: 5px 0 0;
} }
.navbar-nav { .navbar-nav {
margin-right: -15px margin-right: -15px;
} }
.navbar-nav > li > a { .navbar-nav > li > a {
font-size: 14px font-size: 14px;
} }
.navbar-default .navbar-brand, .navbar-default .navbar-brand:focus, .navbar-default .navbar-brand:hover, .navbar-default .navbar-nav > li > a, .navbar-default .navbar-nav > li > a:focus, .navbar-default .navbar-nav > li > a:hover { .navbar-default .navbar-brand,
.navbar-default .navbar-brand:focus,
.navbar-default .navbar-brand:hover,
.navbar-default .navbar-nav > li > a,
.navbar-default .navbar-nav > li > a:focus,
.navbar-default .navbar-nav > li > a:hover {
background: transparent; background: transparent;
color: #293a46; color: #293a46;
font-weight: 300 font-weight: 300;
} }
.navbar-default .navbar-toggle { .navbar-default .navbar-toggle {
@ -215,88 +220,94 @@
top: 7px; top: 7px;
border-color: #fff; border-color: #fff;
color: #293a46; color: #293a46;
margin-right: 0 margin-right: 0;
} }
.navbar-default .navbar-toggle:hover, .navbar-default .navbar-toggle:focus { .navbar-default .navbar-toggle:hover,
.navbar-default .navbar-toggle:focus {
background: #f9f9f9; background: #f9f9f9;
border-color: #f9f9f9 border-color: #f9f9f9;
} }
@media (min-width: 768px) { @media (min-width: 768px) {
.navbar-full .navbar-brand { .navbar-full .navbar-brand {
margin-left: -25px margin-left: -25px;
} }
.navbar-tall { .navbar-tall {
height: 125px height: 125px;
} }
.navbar-tall .navbar-header, .navbar-tall .navbar-nav { .navbar-tall .navbar-header,
.navbar-tall .navbar-nav {
line-height: 125px; line-height: 125px;
text-align: left text-align: left;
} }
.navbar-brand { .navbar-brand {
float: none; float: none;
display: inline-block; display: inline-block;
text-align: left; text-align: left;
margin: 0 margin: 0;
} }
.navbar-nav > li > a { .navbar-nav > li > a {
display: inline-block; display: inline-block;
margin-left: 13px margin-left: 13px;
} }
.navbar-nav > li:first-child > a { .navbar-nav > li:first-child > a {
margin-left: 0 margin-left: 0;
} }
} }
body { body {
font-size: 16px; font-size: 16px;
font-family: Roboto, sans-serif; font-family: Roboto, sans-serif;
font-weight: 300; font-weight: 300;
line-height: 1.6 line-height: 1.6;
} }
h1 { h1 {
font-size: 26px; font-size: 26px;
font-weight: 300 font-weight: 300;
} }
h2 { h2 {
font-size: 18px; font-size: 18px;
font-weight: 300 font-weight: 300;
} }
h3 { h3 {
font-size: 26px; font-size: 26px;
font-weight: 300 font-weight: 300;
} }
h4 { h4 {
font-size: 16px; font-size: 16px;
font-weight: 300 font-weight: 300;
} }
h5 { h5 {
font-size: 16px; font-size: 16px;
font-weight: 400 font-weight: 400;
} }
h1, h2, h3, h4, h5 { h1,
line-height: 1.4 h2,
h3,
h4,
h5 {
line-height: 1.4;
} }
h1, h2 { h1,
margin: 10px 0 h2 {
margin: 10px 0;
} }
h5 { h5 {
margin: 6px 0 margin: 6px 0;
} }
@media (min-width: 768px) { @media (min-width: 768px) {
@ -304,33 +315,33 @@ h5 {
font-size: 16px; font-size: 16px;
font-family: Roboto, sans-serif; font-family: Roboto, sans-serif;
font-weight: 300; font-weight: 300;
line-height: 1.6 line-height: 1.6;
} }
h1 { h1 {
font-size: 38px; font-size: 38px;
font-weight: 300 font-weight: 300;
} }
h2 { h2 {
font-size: 26px; font-size: 26px;
font-weight: 300; font-weight: 300;
line-height: 1.4 line-height: 1.4;
} }
h3 { h3 {
font-size: 26px; font-size: 26px;
font-weight: 300 font-weight: 300;
} }
h4 { h4 {
font-size: 18px; font-size: 18px;
font-weight: 300 font-weight: 300;
} }
h5 { h5 {
font-size: 16px; font-size: 16px;
font-weight: 400 font-weight: 400;
} }
} }
@ -343,7 +354,8 @@ a {
color: inherit; color: inherit;
} }
a:hover, a:focus { a:hover,
a:focus {
text-decoration: underline; text-decoration: underline;
} }
@ -357,40 +369,42 @@ a:hover, a:focus {
} }
.text-muted { .text-muted {
color: #697176 color: #697176;
} }
.template-index h3 { .template-index h3 {
font-size: 21px; font-size: 21px;
margin-bottom: 12px margin-bottom: 12px;
} }
.template-index h4 { .template-index h4 {
color: #697176; color: #697176;
line-height: 1.6 line-height: 1.6;
} }
.template-index h4 a, .template-index p a { .template-index h4 a,
color: #26A65B; .template-index p a {
color: #26a65b;
} }
.template-index h5 { .template-index h5 {
font-size: 17px; font-size: 17px;
margin-bottom: 8px margin-bottom: 8px;
} }
.homepage-terminal-example, .homepage-code-example { .homepage-terminal-example,
.homepage-code-example {
position: relative; position: relative;
font-family: monospace; font-family: monospace;
background: #272b38; background: #272b38;
color: #48d8a0; color: #48d8a0;
border-radius: 8px; border-radius: 8px;
padding: 30px padding: 30px;
} }
.homepage-terminal-example .text-muted, .homepage-terminal-example .text-muted,
.homepage-code-example .text-muted { .homepage-code-example .text-muted {
color: #6a7490 color: #6a7490;
} }
@media (min-width: 768px) { @media (min-width: 768px) {
@ -408,7 +422,7 @@ a:hover, a:focus {
} }
.hero-green { .hero-green {
color: #26A65B; color: #26a65b;
} }
.hero-black { .hero-black {
@ -416,7 +430,7 @@ a:hover, a:focus {
} }
.hero-red { .hero-red {
color: #CB2C37; color: #cb2c37;
} }
.svg-icon-large { .svg-icon-large {

View file

@ -1 +1 @@
import "bootstrap/dist/css/bootstrap.css"; import 'bootstrap/dist/css/bootstrap.css';

View file

@ -1,8 +1,8 @@
import handlebars, { HelperOptions } from "handlebars"; import handlebars, { HelperOptions } from 'handlebars';
const loadPartial = <T>(name: string): handlebars.Template<T> => { const loadPartial = <T>(name: string): handlebars.Template<T> => {
let partial = handlebars.partials[name]; let partial = handlebars.partials[name];
if (typeof partial === "string") { if (typeof partial === 'string') {
partial = handlebars.compile(partial); partial = handlebars.compile(partial);
handlebars.partials[name] = partial; handlebars.partials[name] = partial;
} }

View file

@ -1,4 +1,4 @@
import handlebars, { HelperOptions } from "handlebars"; import handlebars, { HelperOptions } from 'handlebars';
export default (name: string, options: HelperOptions): void => { export default (name: string, options: HelperOptions): void => {
handlebars.registerPartial(name, options.fn); handlebars.registerPartial(name, options.fn);

View file

@ -1 +0,0 @@
@import url("https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.13.1/styles/github.min.css");

View file

@ -1,9 +1,9 @@
import { Diff2HtmlUI, defaultDiff2HtmlUIConfig, Diff2HtmlUIConfig } from "../../../../src/ui/js/diff2html-ui"; import { Diff2HtmlUI, defaultDiff2HtmlUIConfig, Diff2HtmlUIConfig } from '../../../../src/ui/js/diff2html-ui-slim';
import "../../../main.ts"; import '../../../main.ts';
import "../../../main.css"; import '../../../main.css';
import "./demo.css"; import 'highlight.js/styles/github.css';
import "../../../../src/ui/css/diff2html.css"; import '../../../../src/ui/css/diff2html.css';
/* /*
* Example URLs: * Example URLs:
@ -23,18 +23,18 @@ type URLParams = {
[key: string]: string | boolean | number | undefined; [key: string]: string | boolean | number | undefined;
}; };
const searchParam = "diff"; const searchParam = 'diff';
function getParamsFromSearch(search: string): URLParams { function getParamsFromSearch(search: string): URLParams {
try { try {
return search return search
.split("?")[1] .split('?')[1]
.split("&") .split('&')
.reduce((urlParams, e) => { .reduce((urlParams, e) => {
const values = e.split("="); const values = e.split('=');
return { return {
...urlParams, ...urlParams,
[values[0]]: values[1] [values[0]]: values[1],
}; };
}, {}); }, {});
} catch (_ignore) { } catch (_ignore) {
@ -43,8 +43,8 @@ function getParamsFromSearch(search: string): URLParams {
} }
function validateUrl(url: string): boolean { function validateUrl(url: string): boolean {
return /^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})).?)(?::\d{2,5})?(?:[/?#]\S*)?$/i.test( return /^(?:(?:https?|ftp):)?\/\/(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[01])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4])|(?:[\da-z\u00a1-\uffff]-*)*[\da-z\u00a1-\uffff]+(?:\.(?:[\da-z\u00a1-\uffff]-*)*[\da-z\u00a1-\uffff]+)*\.[a-z\u00a1-\uffff]{2,}.?)(?::\d{2,5})?(?:[#/?]\S*)?$/i.test(
url url,
); );
} }
@ -55,7 +55,7 @@ type Request = {
function prepareRequest(url: string): Request { function prepareRequest(url: string): Request {
if (!validateUrl(url)) { if (!validateUrl(url)) {
const errorMsg = "Invalid url provided!"; const errorMsg = 'Invalid url provided!';
console.error(errorMsg); console.error(errorMsg);
throw new Error(errorMsg); throw new Error(errorMsg);
} }
@ -74,44 +74,44 @@ function prepareRequest(url: string): Request {
function gitLabUrlGen(userName: string, projectName: string, type: string, value: string): string { function gitLabUrlGen(userName: string, projectName: string, type: string, value: string): string {
return ( return (
"https://crossorigin.me/https://gitlab.com/" + userName + "/" + projectName + "/" + type + "/" + value + ".diff" 'https://crossorigin.me/https://gitlab.com/' + userName + '/' + projectName + '/' + type + '/' + value + '.diff'
); );
} }
function gitHubUrlGen(userName: string, projectName: string, type: string, value: string): string { function gitHubUrlGen(userName: string, projectName: string, type: string, value: string): string {
headers.append("Accept", "application/vnd.github.v3.diff"); headers.append('Accept', 'application/vnd.github.v3.diff');
return "https://api.github.com/repos/" + userName + "/" + projectName + "/" + type + "/" + value; return 'https://api.github.com/repos/' + userName + '/' + projectName + '/' + type + '/' + value;
} }
function bitbucketUrlGen(userName: string, projectName: string, type: string, value: string): string { function bitbucketUrlGen(userName: string, projectName: string, type: string, value: string): string {
const baseUrl = "https://bitbucket.org/api/2.0/repositories/"; const baseUrl = 'https://bitbucket.org/api/2.0/repositories/';
if (type === "pullrequests") { if (type === 'pullrequests') {
return baseUrl + userName + "/" + projectName + "/pullrequests/" + value + "/diff"; return baseUrl + userName + '/' + projectName + '/pullrequests/' + value + '/diff';
} }
return baseUrl + userName + "/" + projectName + "/diff/" + value; return baseUrl + userName + '/' + projectName + '/diff/' + value;
} }
let values; let values;
if ((values = githubCommitUrl.exec(url))) { if ((values = githubCommitUrl.exec(url))) {
fetchUrl = gitHubUrlGen(values[1], values[2], "commits", values[3]); fetchUrl = gitHubUrlGen(values[1], values[2], 'commits', values[3]);
} else if ((values = githubPrUrl.exec(url))) { } else if ((values = githubPrUrl.exec(url))) {
fetchUrl = gitHubUrlGen(values[1], values[2], "pulls", values[3]); fetchUrl = gitHubUrlGen(values[1], values[2], 'pulls', values[3]);
} else if ((values = gitlabCommitUrl.exec(url))) { } else if ((values = gitlabCommitUrl.exec(url))) {
fetchUrl = gitLabUrlGen(values[1], values[2], "commit", values[3]); fetchUrl = gitLabUrlGen(values[1], values[2], 'commit', values[3]);
} else if ((values = gitlabPrUrl.exec(url))) { } else if ((values = gitlabPrUrl.exec(url))) {
fetchUrl = gitLabUrlGen(values[1], values[2], "merge_requests", values[3]); fetchUrl = gitLabUrlGen(values[1], values[2], 'merge_requests', values[3]);
} else if ((values = bitbucketCommitUrl.exec(url))) { } else if ((values = bitbucketCommitUrl.exec(url))) {
fetchUrl = bitbucketUrlGen(values[1], values[2], "commit", values[3]); fetchUrl = bitbucketUrlGen(values[1], values[2], 'commit', values[3]);
} else if ((values = bitbucketPrUrl.exec(url))) { } else if ((values = bitbucketPrUrl.exec(url))) {
fetchUrl = bitbucketUrlGen(values[1], values[2], "pullrequests", values[3]); fetchUrl = bitbucketUrlGen(values[1], values[2], 'pullrequests', values[3]);
} else { } else {
console.info("Could not parse url, using the provided url."); console.info('Could not parse url, using the provided url.');
fetchUrl = "https://crossorigin.me/" + url; fetchUrl = 'https://crossorigin.me/' + url;
} }
return { return {
url: fetchUrl, url: fetchUrl,
headers: headers headers: headers,
}; };
} }
@ -121,13 +121,13 @@ function getConfiguration(urlParams: URLParams): Diff2HtmlUIConfig {
const { diff, ...urlParamsRest } = urlParams; const { diff, ...urlParamsRest } = urlParams;
const config: URLParams = { const config: URLParams = {
...defaultDiff2HtmlUIConfig, ...defaultDiff2HtmlUIConfig,
...urlParamsRest ...urlParamsRest,
}; };
return Object.entries(config).reduce((object, [k, v]) => { return Object.entries(config).reduce((object, [k, v]) => {
const newObject = !Number.isNaN(Number(v)) const newObject = !Number.isNaN(Number(v))
? { [k]: Number(v) } ? { [k]: Number(v) }
: v === "true" || v === "false" : v === 'true' || v === 'false'
? { [k]: Boolean(v) } ? { [k]: Boolean(v) }
: { [k]: v }; : { [k]: v };
return { ...object, ...newObject }; return { ...object, ...newObject };
@ -137,14 +137,14 @@ function getConfiguration(urlParams: URLParams): Diff2HtmlUIConfig {
async function getDiff(request: Request): Promise<string> { async function getDiff(request: Request): Promise<string> {
try { try {
const result = await fetch(request.url, { const result = await fetch(request.url, {
method: "GET", method: 'GET',
headers: request.headers, headers: request.headers,
mode: "cors", mode: 'cors',
cache: "default" cache: 'default',
}); });
return result.text(); return result.text();
} catch (error) { } catch (error) {
console.error("Failed to retrieve diff", error); console.error('Failed to retrieve diff', error);
throw error; throw error;
} }
} }
@ -152,10 +152,10 @@ async function getDiff(request: Request): Promise<string> {
function draw(diffString: string, config: Diff2HtmlUIConfig, elements: Elements): void { function draw(diffString: string, config: Diff2HtmlUIConfig, elements: Elements): void {
const diff2htmlUi = new Diff2HtmlUI(diffString, elements.structure.diffTarget, config); const diff2htmlUi = new Diff2HtmlUI(diffString, elements.structure.diffTarget, config);
if (config.outputFormat === "side-by-side") { if (config.outputFormat === 'side-by-side') {
elements.structure.container.style.width = "100%"; elements.structure.container.style.width = '100%';
} else { } else {
elements.structure.container.style.width = ""; elements.structure.container.style.width = '';
} }
diff2htmlUi.draw(); diff2htmlUi.draw();
@ -163,7 +163,7 @@ function draw(diffString: string, config: Diff2HtmlUIConfig, elements: Elements)
async function prepareInitialState(elements: Elements): Promise<[Diff2HtmlUIConfig, string]> { async function prepareInitialState(elements: Elements): Promise<[Diff2HtmlUIConfig, string]> {
const urlParams = getParamsFromSearch(window.location.search); const urlParams = getParamsFromSearch(window.location.search);
const currentUrl = (urlParams && urlParams[searchParam]) || "https://github.com/rtfpessoa/diff2html/pull/106"; const currentUrl = (urlParams && urlParams[searchParam]) || 'https://github.com/rtfpessoa/diff2html/pull/106';
if (currentUrl !== elements.url.input.value) elements.url.input.value = currentUrl; if (currentUrl !== elements.url.input.value) elements.url.input.value = currentUrl;
@ -178,20 +178,20 @@ async function prepareInitialState(elements: Elements): Promise<[Diff2HtmlUIConf
function updateBrowserUrl(config: Diff2HtmlUIConfig, newDiffUrl: string): void { function updateBrowserUrl(config: Diff2HtmlUIConfig, newDiffUrl: string): void {
if (history.pushState) { if (history.pushState) {
const paramString = Object.entries(config) const paramString = Object.entries(config)
.map(([k, v]) => k + "=" + v) .map(([k, v]) => k + '=' + v)
.join("&"); .join('&');
const newPageUrl = const newPageUrl =
window.location.protocol + window.location.protocol +
"//" + '//' +
window.location.host + window.location.host +
window.location.pathname + window.location.pathname +
"?" + '?' +
paramString + paramString +
"&" + '&' +
searchParam + searchParam +
"=" + '=' +
newDiffUrl; newDiffUrl;
window.history.pushState({ path: newPageUrl }, "", newPageUrl); window.history.pushState({ path: newPageUrl }, '', newPageUrl);
} }
} }
@ -215,15 +215,15 @@ type Elements = {
}; };
}; };
document.addEventListener("DOMContentLoaded", async () => { document.addEventListener('DOMContentLoaded', async () => {
// Improves browser compatibility // Improves browser compatibility
require("whatwg-fetch"); require('whatwg-fetch');
const drawAndUpdateUrl = async ( const drawAndUpdateUrl = async (
diffUrl: string, diffUrl: string,
diffString: string, diffString: string,
config: Diff2HtmlUIConfig, config: Diff2HtmlUIConfig,
elements: Elements elements: Elements,
): Promise<void> => { ): Promise<void> => {
updateBrowserUrl(config, diffUrl); updateBrowserUrl(config, diffUrl);
const newRequest = prepareRequest(diffUrl); const newRequest = prepareRequest(diffUrl);
@ -233,22 +233,22 @@ document.addEventListener("DOMContentLoaded", async () => {
const elements: Elements = { const elements: Elements = {
structure: { structure: {
container: document.getElementsByClassName("container")[0] as HTMLElement, container: document.getElementsByClassName('container')[0] as HTMLElement,
diffTarget: document.getElementById("url-diff-container") as HTMLElement diffTarget: document.getElementById('url-diff-container') as HTMLElement,
}, },
url: { url: {
input: document.getElementById("url") as HTMLInputElement, input: document.getElementById('url') as HTMLInputElement,
button: document.getElementById("url-btn") as HTMLElement button: document.getElementById('url-btn') as HTMLElement,
}, },
options: { options: {
outputFormat: document.getElementById("diff-url-options-output-format") as HTMLInputElement, outputFormat: document.getElementById('diff-url-options-output-format') as HTMLInputElement,
matching: document.getElementById("diff-url-options-matching") as HTMLInputElement, matching: document.getElementById('diff-url-options-matching') as HTMLInputElement,
wordsThreshold: document.getElementById("diff-url-options-match-words-threshold") as HTMLInputElement, wordsThreshold: document.getElementById('diff-url-options-match-words-threshold') as HTMLInputElement,
matchingMaxComparisons: document.getElementById("diff-url-options-matching-max-comparisons") as HTMLInputElement matchingMaxComparisons: document.getElementById('diff-url-options-matching-max-comparisons') as HTMLInputElement,
}, },
checkboxes: { checkboxes: {
drawFileList: document.getElementById("diff-url-options-show-files") as HTMLInputElement drawFileList: document.getElementById('diff-url-options-show-files') as HTMLInputElement,
} },
}; };
let [config, diffString] = await prepareInitialState(elements); let [config, diffString] = await prepareInitialState(elements);
@ -262,20 +262,20 @@ document.addEventListener("DOMContentLoaded", async () => {
(elements.options.matchingMaxComparisons.value = config.matchingMaxComparisons.toString()); (elements.options.matchingMaxComparisons.value = config.matchingMaxComparisons.toString());
Object.entries(elements.options).forEach(([option, element]) => Object.entries(elements.options).forEach(([option, element]) =>
element.addEventListener("change", () => { element.addEventListener('change', () => {
config = { ...config, [option]: element.value }; config = { ...config, [option]: element.value };
drawAndUpdateUrl(elements.url.input.value, diffString, config, elements); drawAndUpdateUrl(elements.url.input.value, diffString, config, elements);
}) }),
); );
Object.entries(elements.checkboxes).forEach(([option, checkbox]) => Object.entries(elements.checkboxes).forEach(([option, checkbox]) =>
checkbox.addEventListener("change", () => { checkbox.addEventListener('change', () => {
config = { ...config, [option]: checkbox.checked }; config = { ...config, [option]: checkbox.checked };
drawAndUpdateUrl(elements.url.input.value, diffString, config, elements); drawAndUpdateUrl(elements.url.input.value, diffString, config, elements);
}) }),
); );
elements.url.button.addEventListener("click", async e => { elements.url.button.addEventListener('click', async e => {
e.preventDefault(); e.preventDefault();
const newDiffUrl = elements.url.input.value; const newDiffUrl = elements.url.input.value;
const newRequest = prepareRequest(newDiffUrl); const newRequest = prepareRequest(newDiffUrl);

View file

@ -1,93 +1,97 @@
.screenshot { .screenshot {
display: block; display: block;
overflow: hidden; overflow: hidden;
} }
.screenshot > img { .screenshot > img {
width: 100% width: 100%;
} }
.screenshots-fan {
margin-top: 50px;
}
.screenshots-fan .screenshot {
position: relative;
width: auto;
display: inline-block;
text-align: center;
}
.screenshots-fan .screenshot:last-child,
.screenshots-fan .screenshot:first-child {
z-index: 2;
}
.screenshots-fan .screenshot {
z-index: 3;
}
@media (min-width: 768px) {
.screenshots-fan { .screenshots-fan {
margin-top: 50px
}
.screenshots-fan .screenshot {
position: relative; position: relative;
width: auto; overflow: hidden;
display: inline-block; margin-top: 60px;
text-align: center; height: 200px;
}
.screenshots-fan .screenshot:last-child, .screenshots-fan .screenshot:first-child {
z-index: 2
} }
.screenshots-fan .screenshot { .screenshots-fan .screenshot {
z-index: 3 height: auto;
top: 10px;
width: 350px;
} }
@media (min-width: 768px) { .screenshots-fan .screenshot:first-child,
.screenshots-fan { .screenshots-fan .screenshot:last-child {
position: relative; width: 250px;
overflow: hidden; position: absolute;
margin-top: 60px; top: 65px;
height: 200px
}
.screenshots-fan .screenshot {
height: auto;
top: 10px;
width: 350px
}
.screenshots-fan .screenshot:first-child, .screenshots-fan .screenshot:last-child {
width: 250px;
position: absolute;
top: 65px
}
.screenshots-fan .screenshot:first-child {
left: 10px
}
.screenshots-fan .screenshot:last-child {
left: auto;
right: 10px
}
} }
@media (min-width: 992px) { .screenshots-fan .screenshot:first-child {
.screenshots-fan { left: 10px;
margin-top: 60px;
height: 240px
}
.screenshots-fan .screenshot {
width: 400px
}
.screenshots-fan .screenshot:first-child, .screenshots-fan .screenshot:last-child {
width: 300px
}
} }
@media (min-width: 1200px) { .screenshots-fan .screenshot:last-child {
.screenshots-fan { left: auto;
margin-top: 80px; right: 10px;
height: 380px
}
.screenshots-fan .screenshot {
width: 550px
}
.screenshots-fan .screenshot:first-child, .screenshots-fan .screenshot:last-child {
width: 450px
}
} }
}
@media (min-width: 992px) {
.screenshots-fan {
margin-top: 60px;
height: 240px;
}
.screenshots-fan .screenshot {
width: 400px;
}
.screenshots-fan .screenshot:first-child,
.screenshots-fan .screenshot:last-child {
width: 300px;
}
}
@media (min-width: 1200px) {
.screenshots-fan {
margin-top: 80px;
height: 380px;
}
.screenshots-fan .screenshot {
width: 550px;
}
.screenshots-fan .screenshot:first-child,
.screenshots-fan .screenshot:last-child {
width: 450px;
}
}
.img-snapshot1 { .img-snapshot1 {
background-image: url("./images/snapshot-1.png"); background-image: url('./images/snapshot-1.png');
background-repeat: no-repeat; background-repeat: no-repeat;
/* height: 50px; /* height: 50px;
width: 50px; */ width: 50px; */
@ -95,7 +99,7 @@
} }
.img-snapshot2 { .img-snapshot2 {
background-image: url("./images/snapshot-2.png"); background-image: url('./images/snapshot-2.png');
background-repeat: no-repeat; background-repeat: no-repeat;
/* height: 50px; /* height: 50px;
width: 50px; */ width: 50px; */
@ -103,7 +107,7 @@
} }
.img-snapshot3 { .img-snapshot3 {
background-image: url("./images/snapshot-3.png"); background-image: url('./images/snapshot-3.png');
background-repeat: no-repeat; background-repeat: no-repeat;
/* height: 50px; /* height: 50px;
width: 50px; */ width: 50px; */

View file

@ -1,8 +1,8 @@
import Clipboard from "clipboard"; import Clipboard from 'clipboard';
import "../../../main.ts"; import '../../../main.ts';
import "../../../main.css"; import '../../../main.css';
import "./index.css"; import './index.css';
// eslint-disable-next-line no-new // eslint-disable-next-line no-new
new Clipboard(document.getElementsByClassName("btn-clipboard")[0]); new Clipboard(document.getElementsByClassName('btn-clipboard')[0]);

1031
yarn.lock

File diff suppressed because it is too large Load diff