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
* 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
* OS: _____
* diff2html version: _____
* Using diff2html directly or using diff2html-ui helper: _____
* Extra flags: _____
- OS: **\_**
- diff2html version: **\_**
- Using diff2html directly or using diff2html-ui helper: **\_**
- Extra flags: **\_**
### Step 1: Describe the problem:
#### Steps to reproduce:
1. _____
2. _____
3. _____
1. ***
2. ***
3. ***
#### diff example:
```diff
diff --git describe.c
index fabadb8,cc95eb0..4866510
--- a/describe.c
+++ b/describe.c
@@@ -98,20 -98,12 +98,20 @@@
```diff
diff --git describe.c
index fabadb8,cc95eb0..4866510
--- a/describe.c
+++ b/describe.c
@@@ -98,20 -98,12 +98,20 @@@
return (a_date > b_date) ? -1 : (a_date == b_date) ? 0 : 1;
}
```
```
#### Observed Results:
* What happened? This could be a description, log output, etc.
- What happened? This could be a description, log output, etc.
#### Expected Results:
* What did you expect to happen?
- What did you expect to happen?
#### 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**
A clear and concise description of what the bug is.
name: Bug report about: Create a report to help us improve title: "" labels: "" assignees: "" ---**Describe the bug** A
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 '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Expected behavior** A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Screenshots** If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. Windows, Linux, Mac]
- Browser [e.g. Firefox, Chrome, Safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.
- OS: [e.g. Windows, Linux, Mac]
- 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.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
name: Feature request about: Suggest an idea for this project title: "" labels: "" assignees: "" ---**Is your feature
request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always
frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe the solution you'd like** A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Describe alternatives you've considered** A clear and concise description of any alternative solutions or features
you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
**Additional context** Add any other context or screenshots about the feature request here.

View file

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

View file

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

View file

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

4
.gitignore vendored
View file

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

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

View file

@ -2,31 +2,33 @@
### 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)
and that [Codacy](https://www.codacy.com/app/Codacy/diff2html) is also confident in the code quality.
- After creating your pull request make sure the build is passing on
[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
Writing good commit logs is important. A commit log should describe what changed and why.
Follow these guidelines when writing one:
Writing good commit logs is important. A commit log should describe what changed and why. Follow these guidelines when
writing one:
1. The first line should be 50 characters or less and contain a short
description of the change prefixed with the name of the changed
subsystem (e.g. "net: add localAddress and localPort to Socket").
1. The first line should be 50 characters or less and contain a short description of the change prefixed with the name
of the changed subsystem (e.g. "net: add localAddress and localPort to Socket").
2. Keep the second line blank.
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:
* (a) The contribution was created in whole or in part by me and I
have the right to submit it under the open source license indicated
in the file; or
* (b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate open source license
and I have the right under that license to submit that work with
modifications, whether created in whole or in part by me, under the
same open source license (unless I am permitted to submit under a
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.
- (a) The contribution was created in whole or in part by me and I have the right to submit it under the open source
license indicated in the file; or
- (b) The contribution is based upon previous work that, to the best of my knowledge, is covered under an appropriate
open source license and I have the right under that license to submit that work with modifications, whether created in
whole or in part by me, under the same open source license (unless I am permitted to submit under a 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
This is the list of all the kind people that have contributed to the diff2html project.
This list is ordered by first contribution.
This is the list of all the kind people that have contributed to the diff2html project. This list is ordered by first
contribution.
Thanks,
@rtfpessoa
Thanks, @rtfpessoa
----------
---
Rodrigo Fernandes, [@rtfpessoa](https://github.com/rtfpessoa)

View file

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

241
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 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)
[![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)
[![cdnjs](https://img.shields.io/cdnjs/v/diff2html)](https://cdnjs.com/libraries/diff2html)
[![node](https://img.shields.io/node/v/diff2html.svg)]()
[![npm](https://img.shields.io/npm/l/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/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-)
[![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/)
@ -29,7 +28,7 @@ diff2html generates pretty HTML diffs from git or unified diff output.
- Inserted and removed lines
- GitHub like style
- GitHub like visual style
- Code syntax highlight
@ -44,17 +43,39 @@ diff2html generates pretty HTML diffs from git or unified diff output.
## Distributions
- [WebJar](http://www.webjars.org/)
- [Node Module](https://www.npmjs.org/package/diff2html)
- [Node CLI](https://www.npmjs.org/package/diff2html-cli)
- [Node Library](https://www.npmjs.org/package/diff2html)
- [NPM CLI](https://www.npmjs.org/package/diff2html-cli)
- Manually download and import:
- Browser
- Browser / Bundle
- Parser and HTML Generator
- [bundles/js/diff2html.min.js](./bundles/js/diff2html.min.js) - includes the diff 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
- Node.js
- [lib/diff2html.js](./lib/diff2html.js) - targeted for es5, includes the diff parser and html generator
- [lib-esm/diff2html.js](./lib-esm/diff2html.js) - targeted for es6 - includes the diff parser and html generator
- Wrapper and helper adding syntax highlight, synchronized scroll, and other nice features
- [bundles/js/diff2html-ui.min.js](./bundles/js/diff2html-ui.min.js) - includes the wrapper of diff2html with
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.
@ -67,7 +88,41 @@ Import the stylesheet
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
@ -82,32 +137,34 @@ Import the stylesheet and the library code
It will now be available as a global variable named `Diff2Html`.
```js
document.addEventListener("DOMContentLoaded", () => {
var diffHtml = global.Diff2Html.html("<Unified Diff String>", {
document.addEventListener('DOMContentLoaded', () => {
var diffHtml = global.Diff2Html.html('<Unified Diff String>', {
drawFileList: true,
matching: "lines",
outputFormat: "side-by-side"
matching: 'lines',
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
const Diff2html = require("diff2html");
const diffJson = Diff2html.parse("<Unified Diff String>");
const Diff2html = require('diff2html');
const diffJson = Diff2html.parse('<Unified Diff String>');
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
import * as Diff2Html from "diff2html";
import { Component, OnInit } from "@angular/core";
import * as Diff2Html from 'diff2html';
import { Component, OnInit } from '@angular/core';
export class AppDiffComponent implements OnInit {
outputHtml: string;
@ -119,8 +176,8 @@ export class AppDiffComponent implements OnInit {
init() {
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";
let outputHtml = Diff2Html.html(strInput, { drawFileList: true, matching: "lines" });
'--- 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' });
this.outputHtml = outputHtml;
}
}
@ -148,7 +205,7 @@ export class AppDiffComponent implements OnInit {
]
```
### Vue.js
#### Diff2Html Vue.js Example
```vue
<template>
@ -156,67 +213,30 @@ export class AppDiffComponent implements OnInit {
</template>
<script>
import * as Diff2Html from "diff2html";
import "diff2html/bundles/css/diff2html.min.css";
import * as Diff2Html from 'diff2html';
import 'diff2html/bundles/css/diff2html.min.css';
export default {
data() {
return {
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: {
prettyHtml() {
return Diff2Html.getPrettyHtml(this.diffs, {
inputFormat: "diff",
return Diff2Html.html(this.diffs, {
drawFileList: true,
matching: "lines",
outputFormat: "side-by-side"
matching: 'lines',
outputFormat: 'side-by-side',
});
}
}
},
},
};
</script>
```
## API
> 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
## Diff2HtmlUI
> 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 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
@ -240,8 +294,8 @@ The HTML output accepts a Javascript object with configuration. Possible options
#### Init
```js
const targetElement = document.getElementById("destination-elem-id");
const configuration = { drawFileList: true, matching: "lines" };
const targetElement = document.getElementById('destination-elem-id');
const configuration = { drawFileList: true, matching: 'lines' };
const diff2htmlUi = new Diff2HtmlUI(diffString, targetElement, configuration);
// or
@ -268,7 +322,7 @@ diff2htmlUi.draw();
> Pass the option `highlight` with value true or invoke `diff2htmlUi.highlightCode()` after `diff2htmlUi.draw()`.
```js
document.addEventListener("DOMContentLoaded", () => {
document.addEventListener('DOMContentLoaded', () => {
const diffString = `diff --git a/sample.js b/sample.js
index 0000001..0ddf2ba
--- a/sample.js
@ -276,8 +330,8 @@ index 0000001..0ddf2ba
@@ -1 +1 @@
-console.log("Hello World!")
+console.log("Hello from Diff2Html!")`;
const targetElement = document.getElementById("myDiffElement");
const configuration = { inputFormat: "json", drawFileList: true, matching: "lines", highlight: true };
const targetElement = document.getElementById('myDiffElement');
const configuration = { inputFormat: 'json', drawFileList: true, matching: 'lines', highlight: true };
const diff2htmlUi = new Diff2HtmlUI(diffString, targetElement, configuration);
diff2htmlUi.draw();
diff2htmlUi.highlightCode();
@ -293,19 +347,19 @@ index 0000001..0ddf2ba
<script type="text/javascript" src="bundles/js/diff2html-ui.min.js"></script>
```
> Invoke the Diff2HtmlUI helper
> Pass the option `fileListToggle` with value true or invoke `diff2htmlUi.fileListToggle()` after `diff2htmlUi.draw()`.
> Invoke the Diff2HtmlUI helper Pass the option `fileListToggle` with value true or invoke
> `diff2htmlUi.fileListToggle()` after `diff2htmlUi.draw()`.
```js
document.addEventListener("DOMContentLoaded", () => {
const targetElement = document.getElementById("myDiffElement");
var diff2htmlUi = new Diff2HtmlUI(lineDiffExample, targetElement, { drawFileList: true, matching: "lines" });
document.addEventListener('DOMContentLoaded', () => {
const targetElement = document.getElementById('myDiffElement');
var diff2htmlUi = new Diff2HtmlUI(lineDiffExample, targetElement, { drawFileList: true, matching: 'lines' });
diff2htmlUi.draw();
diff2htmlUi.fileListToggle(false);
});
```
# Troubleshooting
## Troubleshooting
### 1. Out of memory or Slow execution
@ -320,9 +374,8 @@ document.addEventListener("DOMContentLoaded", () => {
## Contributions
This is a developer friendly project, all the contributions are welcome.
To contribute just send a pull request with your changes following the guidelines described in `CONTRIBUTING.md`.
I will try to review them as soon as possible.
This is a developer friendly project, all the contributions are welcome. To contribute just send a pull request with
your changes following the guidelines described in `CONTRIBUTING.md`. I will try to review them as soon as possible.
## Contributors ✨
@ -369,14 +422,16 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<!-- 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
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
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
We take all security bugs in `diff2html` seriously.
Thank you for the help improving the security of `diff2html`.
We appreciate your efforts and responsible disclosure and
will make every effort to acknowledge your contributions.
We take all security bugs in `diff2html` seriously. Thank you for the help improving the security of `diff2html`. 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`.
The lead maintainer will acknowledge your email within 48 hours, and will send a
more detailed response within 48 hours indicating the next steps in handling
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 information or guidance.
The lead maintainer will acknowledge your email within 48 hours, and will send a more detailed response within 48 hours
indicating the next steps in handling 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
information or guidance.
Report security bugs in third-party modules to the person or team maintaining
the module.
Report security bugs in third-party modules to the person or team maintaining the module.
## Disclosure Policy
When the security team receives a security bug report, they will assign it to a
primary handler. This person will coordinate the fix and release process,
involving the following steps:
When the security team receives a security bug report, they will assign it to a primary handler. This person will
coordinate the fix and release process, involving the following steps:
* Confirm the problem and determine the affected versions.
* Audit code to find any potential similar problems.
* Prepare fixes for all releases still under maintenance. These fixes will be
released as fast as possible.
- Confirm the problem and determine the affected versions.
- Audit code to find any potential similar problems.
- Prepare fixes for all releases still under maintenance. These fixes will be released as fast as possible.
## Comments on this Policy
If you have suggestions on how this process could be improved please submit a
pull request.
If you have suggestions on how this process could be improved please submit a pull request.

View file

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

View file

@ -35,26 +35,51 @@
"node": ">=10.13"
},
"scripts": {
"lint": "eslint '*/**/*.{js,jsx,ts,tsx}'",
"style": "yarn run lint",
"test": "yarn run build-templates && jest",
"coverage": "jest --collectCoverage",
"coverage-html": "yarn run coverage && open ./coverage/index.html",
"codacy": "cat ./coverage/lcov.info | codacy-coverage",
"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-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-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-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",
"start-website": "WEBPACK_MINIFY=false NODE_ENV=dev webpack-dev-server --mode dev --config webpack.website.ts",
"preversion": "yarn run lint && yarn run build && yarn test",
"eslint": "eslint --ignore-path .gitignore \"**/*.{js,jsx,ts,tsx,json}\"",
"lint:check": "yarn run eslint",
"lint:fix": "yarn run eslint --fix",
"prettier": "prettier --ignore-path .gitignore '**/*.+(js|jsx|ts|tsx|json|css|html|md|mdx)'",
"format:check": "yarn run prettier --check",
"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: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:bundles": "rm -rf ./bundles/js; webpack ---display-reasons --display-modules --mode production --config webpack.bundles.ts",
"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:website": "rm -rf docs; webpack ---display-reasons --display-modules --mode production --config webpack.website.ts",
"test": "is-ci 'test:coverage' 'test:watch'",
"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"
},
"main": "./lib/diff2html.js",
"module": "./lib-esm/diff2html.js",
"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": {
"diff": "4.0.1",
"hogan.js": "3.0.2"
@ -63,21 +88,20 @@
"highlight.js": "9.17.1"
},
"devDependencies": {
"@types/autoprefixer": "9.6.1",
"@types/clipboard": "2.0.1",
"@types/copy-webpack-plugin": "5.0.0",
"@types/diff": "4.0.2",
"@types/highlight.js": "9.12.3",
"@types/hogan.js": "3.0.0",
"@types/html-webpack-plugin": "3.2.1",
"@types/jest": "24.0.24",
"@types/mini-css-extract-plugin": "0.8.0",
"@types/jest": "24.0.25",
"@types/mini-css-extract-plugin": "0.9.0",
"@types/mkdirp": "0.5.2",
"@types/node": "12.12.21",
"@types/node": "13.1.1",
"@types/nopt": "3.0.29",
"@types/webpack": "4.41.0",
"@typescript-eslint/eslint-plugin": "2.12.0",
"@typescript-eslint/parser": "2.12.0",
"@typescript-eslint/eslint-plugin": "2.13.0",
"@typescript-eslint/parser": "2.13.0",
"autoprefixer": "9.7.3",
"bootstrap": "3.4.1",
"clipboard": "2.0.4",
@ -86,37 +110,38 @@
"css-loader": "3.4.0",
"cssnano": "4.1.10",
"eslint": "6.8.0",
"eslint-config-prettier": "6.7.0",
"eslint-config-standard": "14.1.0",
"eslint-config-prettier": "6.9.0",
"eslint-plugin-import": "2.19.1",
"eslint-plugin-jest": "23.1.1",
"eslint-plugin-node": "10.0.0",
"eslint-plugin-prettier": "3.1.2",
"eslint-plugin-jest": "23.2.0",
"eslint-plugin-json": "2.0.1",
"eslint-plugin-node": "11.0.0",
"eslint-plugin-optimize-regex": "1.1.7",
"eslint-plugin-promise": "4.2.1",
"eslint-plugin-standard": "4.0.1",
"fast-html-parser": "1.0.1",
"eslint-plugin-sonarjs": "0.5.0",
"file-loader": "5.0.2",
"handlebars": "4.5.3",
"handlebars-loader": "1.7.1",
"html-webpack-plugin": "3.2.0",
"husky": "3.1.0",
"image-webpack-loader": "6.0.0",
"is-ci-cli": "2.0.0",
"jest": "24.9.0",
"lint-staged": "9.5.0",
"mini-css-extract-plugin": "0.9.0",
"mkdirp": "0.5.1",
"nopt": "4.0.1",
"postcss": "7.0.25",
"postcss-cli": "6.1.3",
"postcss-import": "12.0.1",
"postcss-loader": "3.0.0",
"postcss-normalize": "8.0.1",
"postcss-preset-env": "6.7.0",
"prettier": "1.19.1",
"style-loader": "1.1.1",
"ts-jest": "24.2.0",
"ts-loader": "6.2.1",
"ts-node": "8.5.4",
"typescript": "3.7.4",
"url-loader": "3.0.0",
"webpack": "4.41.4",
"webpack": "4.41.5",
"webpack-cli": "3.3.10",
"webpack-dev-server": "3.10.1",
"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.
* Licensed under the Apache License, Version 2.0 (the "License");
@ -15,12 +13,12 @@
* limitations under the License.
*/
import * as path from "path";
import * as fs from "fs";
import * as path from 'path';
import * as fs from 'fs';
import * as hogan from "hogan.js";
import nopt from "nopt";
import * as mkderp from "mkdirp";
import * as hogan from 'hogan.js';
import nopt from 'nopt';
import * as mkderp from 'mkdirp';
const options = nopt(
{
@ -29,47 +27,47 @@ const options = nopt(
variable: String,
wrapper: String,
version: true,
help: true
help: true,
},
{
n: ["--namespace"],
o: ["--outputdir"],
vn: ["--variable"],
w: ["--wrapper"],
h: ["--help"],
v: ["--version"]
}
n: ['--namespace'],
o: ['--outputdir'],
vn: ['--variable'],
w: ['--wrapper'],
h: ['--help'],
v: ['--version'],
},
);
const specials = ["/", ".", "*", "+", "?", "|", "(", ")", "[", "]", "{", "}", "\\"];
const specialsRegExp = new RegExp("(\\" + specials.join("|\\") + ")", "g");
const specials = ['/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\'];
const specialsRegExp = new RegExp('(\\' + specials.join('|\\') + ')', 'g');
function escape(text: string): string {
return text.replace(specialsRegExp, "\\$1");
return text.replace(specialsRegExp, '\\$1');
}
function cyan(text: string): string {
return "\x1B[36m" + text + "\x1B[39m";
return '\x1B[36m' + text + '\x1B[39m';
}
function extractFiles(files: string[]): string[] {
const usage = `${cyan(
"USAGE:"
'USAGE:',
)} 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
[-n, --namespace] :: prepend string to template names
[-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) {
console.log(require("../package.json").version);
console.log(require('../package.json').version);
process.exit(0);
}
@ -78,20 +76,18 @@ function extractFiles(files: string[]): string[] {
process.exit(0);
}
const templateFiles = files
return files
.map((fileGlob: string) => {
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);
if (new RegExp(`${escape(fileGlobSuffix)}$`).test(relativeFilePath) && fs.statSync(file).isFile()) {
previousFiles.push(file);
}
return previousFiles;
}, []);
return files;
} else if (fs.statSync(fileGlob).isFile()) {
return [fileGlob];
} else {
@ -99,8 +95,6 @@ function extractFiles(files: string[]): string[] {
}
})
.reduce((previous, current) => previous.concat(current), []);
return templateFiles;
}
// 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 {
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 objectStmt = `${objectAccessor} = ${hoganTemplateString};`;
switch (options.wrapper) {
case "amd":
case 'amd':
return `define(${
!options.outputdir ? `"${path.join(path.dirname(file), name)}", ` : ""
!options.outputdir ? `"${path.join(path.dirname(file), name)}", ` : ''
}["hogan.js"], function(Hogan) { return ${hoganTemplateString}; });`;
case "node":
case 'node':
// If we have a template per file the export will expose the template directly
return options.outputdir ? `global.${objectStmt};\nmodule.exports = ${objectAccessor};` : `global.${objectStmt}`;
case "ts":
case 'ts':
return `// @ts-ignore\n${objectStmt}`;
default:
return objectStmt;
@ -137,25 +131,25 @@ function wrap(file: string, name: string, openedFile: string): string {
}
function prepareOutput(content: string): string {
const variableName = options.variable || "templates";
const variableName = options.variable || 'templates';
switch (options.wrapper) {
case "amd":
case 'amd':
return content;
case "node":
case 'node':
return `(function() {
if (!!!global.${variableName}) global.${variableName} = {};
var Hogan = require("hogan.js");
${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";
type CompiledTemplates = { [name: string]: Hogan.Template };
export const ${variableName}: CompiledTemplates = {};
${content}`;
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
function namespace(name: string): string {
return (options.namespace || "") + name;
return (options.namespace || '') + name;
}
// Write a template foreach file that matches template extension
const templates = extractFiles(options.argv.remain)
.map(file => {
const timmedFileContents = fs.readFileSync(file, "utf8").trim();
const timmedFileContents = fs.readFileSync(file, 'utf8').trim();
if (!timmedFileContents) return;
const name = namespace(path.basename(file).replace(/\..*$/, ""));
const name = namespace(path.basename(file).replace(/\..*$/, ''));
const cleanFileContents = wrap(file, name, removeByteOrderMark(timmedFileContents));
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));
})
.filter(templateContents => typeof templateContents !== "undefined");
.filter(templateContents => typeof templateContents !== 'undefined');
// Output templates
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("generateDiffJson", () => {
describe('DiffParser', () => {
describe('generateDiffJson', () => {
// eslint-disable-next-line jest/expect-expect
it("should parse unix with \n diff", () => {
it('should parse unix with \n diff', () => {
const diff =
"diff --git a/sample b/sample\n" +
"index 0000001..0ddf2ba\n" +
"--- a/sample\n" +
"+++ b/sample\n" +
"@@ -1 +1 @@\n" +
"-test\n" +
"+test1r\n";
'diff --git a/sample b/sample\n' +
'index 0000001..0ddf2ba\n' +
'--- a/sample\n' +
'+++ b/sample\n' +
'@@ -1 +1 @@\n' +
'-test\n' +
'+test1r\n';
const result = parse(diff);
expect(result).toMatchInlineSnapshot(`
Array [
@ -53,15 +53,15 @@ describe("DiffParser", () => {
});
// 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 =
"diff --git a/sample b/sample\r\n" +
"index 0000001..0ddf2ba\r\n" +
"--- a/sample\r\n" +
"+++ b/sample\r\n" +
"@@ -1 +1 @@\r\n" +
"-test\r\n" +
"+test1r\r\n";
'diff --git a/sample b/sample\r\n' +
'index 0000001..0ddf2ba\r\n' +
'--- a/sample\r\n' +
'+++ b/sample\r\n' +
'@@ -1 +1 @@\r\n' +
'-test\r\n' +
'+test1r\r\n';
const result = parse(diff);
expect(result).toMatchInlineSnapshot(`
Array [
@ -103,15 +103,15 @@ describe("DiffParser", () => {
});
// 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 =
"diff --git a/sample b/sample\r" +
"index 0000001..0ddf2ba\r" +
"--- a/sample\r" +
"+++ b/sample\r" +
"@@ -1 +1 @@\r" +
"-test\r" +
"+test1r\r";
'diff --git a/sample b/sample\r' +
'index 0000001..0ddf2ba\r' +
'--- a/sample\r' +
'+++ b/sample\r' +
'@@ -1 +1 @@\r' +
'-test\r' +
'+test1r\r';
const result = parse(diff);
expect(result).toMatchInlineSnapshot(`
Array [
@ -153,15 +153,15 @@ describe("DiffParser", () => {
});
// eslint-disable-next-line jest/expect-expect
it("should parse mixed eols diff", () => {
it('should parse mixed eols diff', () => {
const diff =
"diff --git a/sample b/sample\n" +
"index 0000001..0ddf2ba\r\n" +
"--- a/sample\r" +
"+++ b/sample\r\n" +
"@@ -1 +1 @@\n" +
"-test\r" +
"+test1r\n";
'diff --git a/sample b/sample\n' +
'index 0000001..0ddf2ba\r\n' +
'--- a/sample\r' +
'+++ b/sample\r\n' +
'@@ -1 +1 @@\n' +
'-test\r' +
'+test1r\n';
const result = parse(diff);
expect(result).toMatchInlineSnapshot(`
Array [
@ -202,16 +202,16 @@ describe("DiffParser", () => {
`);
});
it("should parse diff with special characters", () => {
it('should parse diff with special characters', () => {
const diff =
'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' +
'+++ "b/bla with \ttab.scala"\n' +
"@@ -1 +1,2 @@\n" +
"-cenas\n" +
"+cenas com ananas\n" +
"+bananas";
'@@ -1 +1,2 @@\n' +
'-cenas\n' +
'+cenas com ananas\n' +
'+bananas';
const result = parse(diff);
expect(result).toMatchInlineSnapshot(`
Array [
@ -259,16 +259,16 @@ describe("DiffParser", () => {
`);
});
it("should parse diff with prefix", () => {
it('should parse diff with prefix', () => {
const diff =
'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' +
"@@ -1 +1,2 @@\n" +
"-cenas\n" +
"+cenas com ananas\n" +
"+bananas";
'@@ -1 +1,2 @@\n' +
'-cenas\n' +
'+cenas com ananas\n' +
'+bananas';
const result = parse(diff);
expect(result).toMatchInlineSnapshot(`
Array [
@ -316,17 +316,17 @@ describe("DiffParser", () => {
`);
});
it("should parse diff with deleted file", () => {
it('should parse diff with deleted file', () => {
const diff =
"diff --git a/src/var/strundefined.js b/src/var/strundefined.js\n" +
"deleted file mode 100644\n" +
"index 04e16b0..0000000\n" +
"--- a/src/var/strundefined.js\n" +
"+++ /dev/null\n" +
"@@ -1,3 +0,0 @@\n" +
"-define(() => {\n" +
"- return typeof undefined;\n" +
"-});\n";
'diff --git a/src/var/strundefined.js b/src/var/strundefined.js\n' +
'deleted file mode 100644\n' +
'index 04e16b0..0000000\n' +
'--- a/src/var/strundefined.js\n' +
'+++ /dev/null\n' +
'@@ -1,3 +0,0 @@\n' +
'-define(() => {\n' +
'- return typeof undefined;\n' +
'-});\n';
const result = parse(diff);
expect(result).toMatchInlineSnapshot(`
Array [
@ -375,19 +375,19 @@ describe("DiffParser", () => {
`);
});
it("should parse diff with new file", () => {
it('should parse diff with new file', () => {
const diff =
"diff --git a/test.js b/test.js\n" +
"new file mode 100644\n" +
"index 0000000..e1e22ec\n" +
"--- /dev/null\n" +
"+++ b/test.js\n" +
"@@ -0,0 +1,5 @@\n" +
'diff --git a/test.js b/test.js\n' +
'new file mode 100644\n' +
'index 0000000..e1e22ec\n' +
'--- /dev/null\n' +
'+++ b/test.js\n' +
'@@ -0,0 +1,5 @@\n' +
"+var parser = require('./source/git-parser');\n" +
"+\n" +
"+var patchLineList = [ false, false, false, false ];\n" +
"+\n" +
"+console.log(parser.parsePatchDiffResult(text, patchLineList));\n";
'+\n' +
'+var patchLineList = [ false, false, false, false ];\n' +
'+\n' +
'+console.log(parser.parsePatchDiffResult(text, patchLineList));\n';
const result = parse(diff);
expect(result).toMatchInlineSnapshot(`
Array [
@ -448,19 +448,19 @@ describe("DiffParser", () => {
`);
});
it("should parse diff with nested diff", () => {
it('should parse diff with nested diff', () => {
const diff =
"diff --git a/src/offset.js b/src/offset.js\n" +
"index cc6ffb4..fa51f18 100644\n" +
"--- a/src/offset.js\n" +
"+++ b/src/offset.js\n" +
"@@ -1,6 +1,5 @@\n" +
'diff --git a/src/offset.js b/src/offset.js\n' +
'index cc6ffb4..fa51f18 100644\n' +
'--- a/src/offset.js\n' +
'+++ b/src/offset.js\n' +
'@@ -1,6 +1,5 @@\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 patchLineList = [ false, false, false, false ];\n" +
"+\n" +
"+console.log(parser.parsePatchDiffResult(text, patchLineList));\n";
'+var patchLineList = [ false, false, false, false ];\n' +
'+\n' +
'+console.log(parser.parsePatchDiffResult(text, patchLineList));\n';
const result = parse(diff);
expect(result).toMatchInlineSnapshot(`
Array [
@ -526,32 +526,32 @@ describe("DiffParser", () => {
`);
});
it("should parse diff with multiple blocks", () => {
it('should parse diff with multiple blocks', () => {
const diff =
"diff --git a/src/attributes/classes.js b/src/attributes/classes.js\n" +
"index c617824..c8d1393 100644\n" +
"--- a/src/attributes/classes.js\n" +
"+++ b/src/attributes/classes.js\n" +
"@@ -1,10 +1,9 @@\n" +
" define([\n" +
'diff --git a/src/attributes/classes.js b/src/attributes/classes.js\n' +
'index c617824..c8d1393 100644\n' +
'--- a/src/attributes/classes.js\n' +
'+++ b/src/attributes/classes.js\n' +
'@@ -1,10 +1,9 @@\n' +
' define([\n' +
' "../core",\n' +
' "../var/rnotwhite",\n' +
'- "../var/strundefined",\n' +
' "../data/var/dataPriv",\n' +
' "../core/init"\n' +
"-], function( jQuery, rnotwhite, strundefined, dataPriv ) {\n" +
"+], function( jQuery, rnotwhite, dataPriv ) {\n" +
" \n" +
" var rclass = /[\\t\\r\\n\\f]/g;\n" +
" \n" +
"@@ -128,7 +127,7 @@ jQuery.fn.extend({\n" +
" }\n" +
" \n" +
" // Toggle whole class name\n" +
'-], function( jQuery, rnotwhite, strundefined, dataPriv ) {\n' +
'+], function( jQuery, rnotwhite, dataPriv ) {\n' +
' \n' +
' var rclass = /[\\t\\r\\n\\f]/g;\n' +
' \n' +
'@@ -128,7 +127,7 @@ jQuery.fn.extend({\n' +
' }\n' +
' \n' +
' // Toggle whole class name\n' +
'- } else if ( type === strundefined || type === "boolean" ) {\n' +
'+ } else if ( value === undefined || type === "boolean" ) {\n' +
" if ( this.className ) {\n" +
" // store className if set\n" +
' if ( this.className ) {\n' +
' // store className if set\n' +
' dataPriv.set( this, "__className__", this.className );\n';
const result = parse(diff);
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 =
"diff --git a/src/core/init.js b/src/core/init.js\n" +
"index e49196a..50f310c 100644\n" +
"--- a/src/core/init.js\n" +
"+++ b/src/core/init.js\n" +
"@@ -101,7 +101,7 @@ var rootjQuery,\n" +
" // HANDLE: $(function)\n" +
" // Shortcut for document ready\n" +
" } else if ( jQuery.isFunction( selector ) ) {\n" +
'diff --git a/src/core/init.js b/src/core/init.js\n' +
'index e49196a..50f310c 100644\n' +
'--- a/src/core/init.js\n' +
'+++ b/src/core/init.js\n' +
'@@ -101,7 +101,7 @@ var rootjQuery,\n' +
' // HANDLE: $(function)\n' +
' // Shortcut for document ready\n' +
' } else if ( jQuery.isFunction( selector ) ) {\n' +
'- return typeof rootjQuery.ready !== "undefined" ?\n' +
"+ return rootjQuery.ready !== undefined ?\n" +
" rootjQuery.ready( selector ) :\n" +
" // Execute immediately if ready is not present\n" +
" selector( jQuery );\n" +
"diff --git a/src/event.js b/src/event.js\n" +
"index 7336f4d..6183f70 100644\n" +
"--- a/src/event.js\n" +
"+++ b/src/event.js\n" +
"@@ -1,6 +1,5 @@\n" +
" define([\n" +
'+ return rootjQuery.ready !== undefined ?\n' +
' rootjQuery.ready( selector ) :\n' +
' // Execute immediately if ready is not present\n' +
' selector( jQuery );\n' +
'diff --git a/src/event.js b/src/event.js\n' +
'index 7336f4d..6183f70 100644\n' +
'--- a/src/event.js\n' +
'+++ b/src/event.js\n' +
'@@ -1,6 +1,5 @@\n' +
' define([\n' +
' "./core",\n' +
'- "./var/strundefined",\n' +
' "./var/rnotwhite",\n' +
@ -865,35 +865,35 @@ describe("DiffParser", () => {
`);
});
it("should parse combined diff", () => {
it('should parse combined diff', () => {
const diff =
"diff --combined describe.c\n" +
"index fabadb8,cc95eb0..4866510\n" +
"--- a/describe.c\n" +
"+++ b/describe.c\n" +
"@@@ -98,20 -98,12 +98,20 @@@\n" +
" return (a_date > b_date) ? -1 : (a_date == b_date) ? 0 : 1;\n" +
" }\n" +
" \n" +
"- static void describe(char *arg)\n" +
" -static void describe(struct commit *cmit, int last_one)\n" +
"++static void describe(char *arg, int last_one)\n" +
" {\n" +
" + unsigned char sha1[20];\n" +
" + struct commit *cmit;\n" +
" struct commit_list *list;\n" +
" static int initialized = 0;\n" +
" struct commit_name *n;\n" +
" \n" +
" + if (get_sha1(arg, sha1) < 0)\n" +
" + usage(describe_usage);\n" +
" + cmit = lookup_commit_reference(sha1);\n" +
" + if (!cmit)\n" +
" + usage(describe_usage);\n" +
" +\n" +
" if (!initialized) {\n" +
" initialized = 1;\n" +
" for_each_ref(get_name);\n";
'diff --combined describe.c\n' +
'index fabadb8,cc95eb0..4866510\n' +
'--- a/describe.c\n' +
'+++ b/describe.c\n' +
'@@@ -98,20 -98,12 +98,20 @@@\n' +
' return (a_date > b_date) ? -1 : (a_date == b_date) ? 0 : 1;\n' +
' }\n' +
' \n' +
'- static void describe(char *arg)\n' +
' -static void describe(struct commit *cmit, int last_one)\n' +
'++static void describe(char *arg, int last_one)\n' +
' {\n' +
' + unsigned char sha1[20];\n' +
' + struct commit *cmit;\n' +
' struct commit_list *list;\n' +
' static int initialized = 0;\n' +
' struct commit_name *n;\n' +
' \n' +
' + if (get_sha1(arg, sha1) < 0)\n' +
' + usage(describe_usage);\n' +
' + cmit = lookup_commit_reference(sha1);\n' +
' + if (!cmit)\n' +
' + usage(describe_usage);\n' +
' +\n' +
' if (!initialized) {\n' +
' initialized = 1;\n' +
' for_each_ref(get_name);\n';
const result = parse(diff);
expect(result).toMatchInlineSnapshot(`
Array [
@ -1057,12 +1057,12 @@ describe("DiffParser", () => {
`);
});
it("should parse diffs with copied files", () => {
it('should parse diffs with copied files', () => {
const diff =
"diff --git a/index.js b/more-index.js\n" +
"dissimilarity index 5%\n" +
"copy from index.js\n" +
"copy to more-index.js\n";
'diff --git a/index.js b/more-index.js\n' +
'dissimilarity index 5%\n' +
'copy from index.js\n' +
'copy to more-index.js\n';
const result = parse(diff);
expect(result).toMatchInlineSnapshot(`
Array [
@ -1080,12 +1080,12 @@ describe("DiffParser", () => {
`);
});
it("should parse diffs with moved files", () => {
it('should parse diffs with moved files', () => {
const diff =
"diff --git a/more-index.js b/other-index.js\n" +
"similarity index 86%\n" +
"rename from more-index.js\n" +
"rename to other-index.js\n";
'diff --git a/more-index.js b/other-index.js\n' +
'similarity index 86%\n' +
'rename from more-index.js\n' +
'rename to other-index.js\n';
const result = parse(diff);
expect(result).toMatchInlineSnapshot(`
Array [
@ -1103,15 +1103,15 @@ describe("DiffParser", () => {
`);
});
it("should parse diffs correct line numbers", () => {
it('should parse diffs correct line numbers', () => {
const diff =
"diff --git a/sample b/sample\n" +
"index 0000001..0ddf2ba\n" +
"--- a/sample\n" +
"+++ b/sample\n" +
"@@ -1 +1,2 @@\n" +
"-test\n" +
"+test1r\n";
'diff --git a/sample b/sample\n' +
'index 0000001..0ddf2ba\n' +
'--- a/sample\n' +
'+++ b/sample\n' +
'@@ -1 +1,2 @@\n' +
'-test\n' +
'+test1r\n';
const result = parse(diff);
expect(result).toMatchInlineSnapshot(`
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 = [
// 2 hours ahead of GMT
"--- a/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" +
"-test\n" +
"+test1r\n" +
"+test2r",
'--- a/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' +
'-test\n' +
'+test1r\n' +
'+test2r',
// 2 hours behind GMT
"--- a/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" +
"-test\n" +
"+test1r\n" +
"+test2r"
].join("\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' +
'@@ -1 +1,2 @@\n' +
'-test\n' +
'+test1r\n' +
'+test2r',
].join('\n');
const result = parse(diffs);
expect(result).toMatchInlineSnapshot(`
Array [
@ -1248,9 +1248,9 @@ describe("DiffParser", () => {
`);
});
it("should parse unified non git diff", () => {
it('should parse unified non git 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);
expect(result).toMatchInlineSnapshot(`
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 =
"--- sample.js\n" +
"+++ sample.js\n" +
"@@ -1 +1,2 @@\n" +
"-test\n" +
"@@ -10 +20,2 @@\n" +
"+test\n" +
"--- sample1.js\n" +
"+++ sample1.js\n" +
"@@ -1 +1,2 @@\n" +
"+test1";
'--- sample.js\n' +
'+++ sample.js\n' +
'@@ -1 +1,2 @@\n' +
'-test\n' +
'@@ -10 +20,2 @@\n' +
'+test\n' +
'--- sample1.js\n' +
'+++ sample1.js\n' +
'@@ -1 +1,2 @@\n' +
'+test1';
const result = parse(diff);
expect(result).toMatchInlineSnapshot(`
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 =
"--- sample.js\n" +
"+++ sample.js\n" +
"@@ -1,8 +1,8 @@\n" +
" test\n" +
" \n" +
"-- 1\n" +
"--- 1\n" +
"---- 1\n" +
" \n" +
"++ 2\n" +
"+++ 2\n" +
"++++ 2";
'--- sample.js\n' +
'+++ sample.js\n' +
'@@ -1,8 +1,8 @@\n' +
' test\n' +
' \n' +
'-- 1\n' +
'--- 1\n' +
'---- 1\n' +
' \n' +
'++ 2\n' +
'+++ 2\n' +
'++++ 2';
const result = parse(diff);
expect(result).toMatchInlineSnapshot(`
Array [
@ -1468,8 +1468,8 @@ describe("DiffParser", () => {
`);
});
it("should parse diff without proper hunk headers", () => {
const diff = "--- sample.js\n" + "+++ sample.js\n" + "@@ @@\n" + " test";
it('should parse diff without proper hunk headers', () => {
const diff = '--- sample.js\n' + '+++ sample.js\n' + '@@ @@\n' + ' test';
const result = parse(diff);
expect(result).toMatchInlineSnapshot(`
Array [
@ -1501,13 +1501,13 @@ describe("DiffParser", () => {
`);
});
it("should parse binary file diff", () => {
it('should parse binary file diff', () => {
const diff =
"diff --git a/last-changes-config.png b/last-changes-config.png\n" +
"index 322248b..56fc1f2 100644\n" +
"--- a/last-changes-config.png\n" +
"+++ b/last-changes-config.png\n" +
"Binary files differ";
'diff --git a/last-changes-config.png b/last-changes-config.png\n' +
'index 322248b..56fc1f2 100644\n' +
'--- a/last-changes-config.png\n' +
'+++ b/last-changes-config.png\n' +
'Binary files differ';
const result = parse(diff);
expect(result).toMatchInlineSnapshot(`
Array [
@ -1536,21 +1536,21 @@ describe("DiffParser", () => {
`);
});
it("should parse diff with --find-renames", () => {
it('should parse diff with --find-renames', () => {
const diff =
"diff --git a/src/test-bar.js b/src/test-baz.js\n" +
"similarity index 98%\n" +
"rename from src/test-bar.js\n" +
"rename to src/test-baz.js\n" +
"index e01513b..f14a870 100644\n" +
"--- a/src/test-bar.js\n" +
"+++ b/src/test-baz.js\n" +
"@@ -1,4 +1,32 @@\n" +
" function foo() {\n" +
'diff --git a/src/test-bar.js b/src/test-baz.js\n' +
'similarity index 98%\n' +
'rename from src/test-bar.js\n' +
'rename to src/test-baz.js\n' +
'index e01513b..f14a870 100644\n' +
'--- a/src/test-bar.js\n' +
'+++ b/src/test-baz.js\n' +
'@@ -1,4 +1,32 @@\n' +
' function foo() {\n' +
'-var bar = "Whoops!";\n' +
'+var baz = "Whoops!";\n' +
" }\n" +
" ";
' }\n' +
' ';
const result = parse(diff);
expect(result).toMatchInlineSnapshot(`
Array [
@ -1612,49 +1612,49 @@ describe("DiffParser", () => {
`);
});
it("should parse diff with prefix 2", () => {
it('should parse diff with prefix 2', () => {
const diff =
'diff --git "\tTest.scala" "\tScalaTest.scala"\n' +
"similarity index 88%\n" +
"rename from Test.scala\n" +
"rename to ScalaTest.scala\n" +
"index 7d1f9bf..8b13271 100644\n" +
'similarity index 88%\n' +
'rename from Test.scala\n' +
'rename to ScalaTest.scala\n' +
'index 7d1f9bf..8b13271 100644\n' +
'--- "\tTest.scala"\n' +
'+++ "\tScalaTest.scala"\n' +
"@@ -1,6 +1,8 @@\n" +
" class Test {\n" +
" \n" +
" def method1 = ???\n" +
"+\n" +
"+ def method2 = ???\n" +
" \n" +
" def myMethod = ???\n" +
" \n" +
"@@ -10,7 +12,6 @@ class Test {\n" +
" \n" +
" def + = ???\n" +
" \n" +
"- def |> = ???\n" +
" \n" +
" }\n" +
" \n" +
'@@ -1,6 +1,8 @@\n' +
' class Test {\n' +
' \n' +
' def method1 = ???\n' +
'+\n' +
'+ def method2 = ???\n' +
' \n' +
' def myMethod = ???\n' +
' \n' +
'@@ -10,7 +12,6 @@ class Test {\n' +
' \n' +
' def + = ???\n' +
' \n' +
'- def |> = ???\n' +
' \n' +
' }\n' +
' \n' +
'diff --git "\ttardis.png" "\ttardis.png"\n' +
"new file mode 100644\n" +
"index 0000000..d503a29\n" +
'new file mode 100644\n' +
'index 0000000..d503a29\n' +
'Binary files /dev/null and "\ttardis.png" differ\n' +
"diff --git a/src/test-bar.js b/src/test-baz.js\n" +
"similarity index 98%\n" +
"rename from src/test-bar.js\n" +
"rename to src/test-baz.js\n" +
"index e01513b..f14a870 100644\n" +
"--- a/src/test-bar.js\n" +
"+++ b/src/test-baz.js\n" +
"@@ -1,4 +1,32 @@\n" +
" function foo() {\n" +
'diff --git a/src/test-bar.js b/src/test-baz.js\n' +
'similarity index 98%\n' +
'rename from src/test-bar.js\n' +
'rename to src/test-baz.js\n' +
'index e01513b..f14a870 100644\n' +
'--- a/src/test-bar.js\n' +
'+++ b/src/test-baz.js\n' +
'@@ -1,4 +1,32 @@\n' +
' function foo() {\n' +
'-var bar = "Whoops!";\n' +
'+var baz = "Whoops!";\n' +
" }\n" +
" ";
' }\n' +
' ';
const result = parse(diff);
expect(result).toMatchInlineSnapshot(`
Array [
@ -1860,39 +1860,39 @@ describe("DiffParser", () => {
`);
});
it("should parse binary with content", () => {
it('should parse binary with content', () => {
const diff =
"diff --git a/favicon.png b/favicon.png\n" +
"deleted file mode 100644\n" +
"index 2a9d516a5647205d7be510dd0dff93a3663eff6f..0000000000000000000000000000000000000000\n" +
"GIT binary patch\n" +
"literal 0\n" +
"HcmV?d00001\n" +
"\n" +
"literal 471\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" +
"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+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" +
"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{&h9@9n8F^?|qusoPy{k#%tVHzu7H$t26CR`BJZk*Ixf&u36WuS=?6m2^ho-p00i_\n" +
"I>zopr0Nz-&lmGw#\n" +
"diff --git a/src/test-bar.js b/src/test-baz.js\n" +
"similarity index 98%\n" +
"rename from src/test-bar.js\n" +
"rename to src/test-baz.js\n" +
"index e01513b..f14a870 100644\n" +
"--- a/src/test-bar.js\n" +
"+++ b/src/test-baz.js\n" +
"@@ -1,4 +1,32 @@\n" +
" function foo() {\n" +
'diff --git a/favicon.png b/favicon.png\n' +
'deleted file mode 100644\n' +
'index 2a9d516a5647205d7be510dd0dff93a3663eff6f..0000000000000000000000000000000000000000\n' +
'GIT binary patch\n' +
'literal 0\n' +
'HcmV?d00001\n' +
'\n' +
'literal 471\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' +
'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+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' +
'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{&h9@9n8F^?|qusoPy{k#%tVHzu7H$t26CR`BJZk*Ixf&u36WuS=?6m2^ho-p00i_\n' +
'I>zopr0Nz-&lmGw#\n' +
'diff --git a/src/test-bar.js b/src/test-baz.js\n' +
'similarity index 98%\n' +
'rename from src/test-bar.js\n' +
'rename to src/test-baz.js\n' +
'index e01513b..f14a870 100644\n' +
'--- a/src/test-bar.js\n' +
'+++ b/src/test-baz.js\n' +
'@@ -1,4 +1,32 @@\n' +
' function foo() {\n' +
'-var bar = "Whoops!";\n' +
'+var baz = "Whoops!";\n' +
" }\n" +
" ";
' }\n' +
' ';
const result = parse(diff);
expect(result).toMatchInlineSnapshot(`
Array [

View file

@ -1,14 +1,14 @@
import { parse, html } from "../diff2html";
import { DiffFile, LineType, OutputFormatType } from "../types";
import { parse, html } from '../diff2html';
import { DiffFile, LineType, OutputFormatType } from '../types';
const diffExample1 =
"diff --git a/sample b/sample\n" +
"index 0000001..0ddf2ba\n" +
"--- a/sample\n" +
"+++ b/sample\n" +
"@@ -1 +1 @@\n" +
"-test\n" +
"+test1\n";
'diff --git a/sample b/sample\n' +
'index 0000001..0ddf2ba\n' +
'--- a/sample\n' +
'+++ b/sample\n' +
'@@ -1 +1 @@\n' +
'-test\n' +
'+test1\n';
const jsonExample1: DiffFile[] = [
{
@ -16,47 +16,47 @@ const jsonExample1: DiffFile[] = [
{
lines: [
{
content: "-test",
content: '-test',
type: LineType.DELETE,
oldNumber: 1,
newNumber: undefined
newNumber: undefined,
},
{
content: "+test1",
content: '+test1',
type: LineType.INSERT,
oldNumber: undefined,
newNumber: 1
}
newNumber: 1,
},
],
oldStartLine: 1,
oldStartLine2: undefined,
newStartLine: 1,
header: "@@ -1 +1 @@"
}
header: '@@ -1 +1 @@',
},
],
deletedLines: 1,
addedLines: 1,
checksumBefore: "0000001",
checksumAfter: "0ddf2ba",
oldName: "sample",
newName: "sample",
language: "",
checksumBefore: '0000001',
checksumAfter: '0ddf2ba',
oldName: 'sample',
newName: 'sample',
language: '',
isCombined: false,
isGitDiff: true
}
isGitDiff: true,
},
];
describe("Diff2Html", () => {
describe("getJsonFromDiff", () => {
it("should parse simple diff to json", () => {
describe('Diff2Html', () => {
describe('getJsonFromDiff', () => {
it('should parse simple diff to json', () => {
const diff =
"diff --git a/sample b/sample\n" +
"index 0000001..0ddf2ba\n" +
"--- a/sample\n" +
"+++ b/sample\n" +
"@@ -1 +1 @@\n" +
"-test\n" +
"+test1\n";
'diff --git a/sample b/sample\n' +
'index 0000001..0ddf2ba\n' +
'--- a/sample\n' +
'+++ b/sample\n' +
'@@ -1 +1 @@\n' +
'-test\n' +
'+test1\n';
const result = parse(diff);
expect(result).toMatchInlineSnapshot(`
Array [
@ -98,23 +98,23 @@ describe("Diff2Html", () => {
});
// Test case for issue #49
it("should parse diff with added EOF", () => {
it('should parse diff with added EOF', () => {
const diff =
"diff --git a/sample.scala b/sample.scala\n" +
"index b583263..8b2fc3e 100644\n" +
"--- a/b583263..8b2fc3e\n" +
"+++ b/8b2fc3e\n" +
"@@ -50,5 +50,7 @@ case class Response[+A](value: Option[A],\n" +
" object ResponseErrorCode extends JsonEnumeration {\n" +
" val NoError, ServiceError, JsonError,\n" +
" InvalidPermissions, MissingPermissions, GenericError,\n" +
"- TokenRevoked, MissingToken = Value\n" +
"-}\n" +
"\\ No newline at end of file\n" +
"+ TokenRevoked, MissingToken,\n" +
"+ IndexLock, RepositoryError, NotValidRepo, PullRequestNotMergeable, BranchError,\n" +
"+ PluginError, CodeParserError, EngineError = Value\n" +
"+}\n";
'diff --git a/sample.scala b/sample.scala\n' +
'index b583263..8b2fc3e 100644\n' +
'--- a/b583263..8b2fc3e\n' +
'+++ b/8b2fc3e\n' +
'@@ -50,5 +50,7 @@ case class Response[+A](value: Option[A],\n' +
' object ResponseErrorCode extends JsonEnumeration {\n' +
' val NoError, ServiceError, JsonError,\n' +
' InvalidPermissions, MissingPermissions, GenericError,\n' +
'- TokenRevoked, MissingToken = Value\n' +
'-}\n' +
'\\ No newline at end of file\n' +
'+ TokenRevoked, MissingToken,\n' +
'+ IndexLock, RepositoryError, NotValidRepo, PullRequestNotMergeable, BranchError,\n' +
'+ PluginError, CodeParserError, EngineError = Value\n' +
'+}\n';
const result = parse(diff);
expect(result).toMatchInlineSnapshot(`
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 });
expect(result).toMatchInlineSnapshot(`
"<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 });
expect(result).toMatchInlineSnapshot(`
"<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 });
expect(result).toMatchInlineSnapshot(`
"<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 });
expect(result).toMatchInlineSnapshot(`
"<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 });
expect(result).toMatchInlineSnapshot(`
"<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 });
expect(result).toMatchInlineSnapshot(`
"<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 =
"diff --git a/CHANGELOG.md b/CHANGELOG.md\n" +
"index fc3e3f4..b486d10 100644\n" +
"--- a/CHANGELOG.md\n" +
"+++ b/CHANGELOG.md\n" +
"@@ -1,7 +1,6 @@\n" +
" # Change Log\n" +
" All notable changes to this project will be documented in this file.\n" +
" This project adheres to [Semantic Versioning](http://semver.org/).\n" +
'diff --git a/CHANGELOG.md b/CHANGELOG.md\n' +
'index fc3e3f4..b486d10 100644\n' +
'--- a/CHANGELOG.md\n' +
'+++ b/CHANGELOG.md\n' +
'@@ -1,7 +1,6 @@\n' +
' # Change Log\n' +
' All notable changes to this project will be documented in this file.\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>\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" +
"@@ -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" +
" - 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 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" +
' $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' +
'@@ -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' +
' - 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 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.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" +
" \n";
' - 1.1.4: [patch] Promise refactoring\n' +
' \n';
const result = html(diffExample2, { drawFileList: false });
expect(result).toMatchInlineSnapshot(`
"<div class=\\"d2h-wrapper\\">

View file

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

View file

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

View file

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

View file

@ -1,158 +1,151 @@
import { escapeForHtml, getHtmlId, filenameDiff, diffHighlight } from "../render-utils";
import { DiffStyleType, LineMatchingType } from "../types";
import { escapeForHtml, getHtmlId, filenameDiff, diffHighlight } from '../render-utils';
import { DiffStyleType, LineMatchingType } from '../types';
describe("Utils", () => {
describe("escapeForHtml", () => {
it("should escape & with &amp;", () => {
const result = escapeForHtml("&");
expect(result).toEqual("&amp;");
describe('Utils', () => {
describe('escapeForHtml', () => {
it('should escape & with &amp;', () => {
const result = escapeForHtml('&');
expect(result).toEqual('&amp;');
});
it("should escape < with &lt;", () => {
const result = escapeForHtml("<");
expect(result).toEqual("&lt;");
it('should escape < with &lt;', () => {
const result = escapeForHtml('<');
expect(result).toEqual('&lt;');
});
it("should escape > with &gt;", () => {
const result = escapeForHtml(">");
expect(result).toEqual("&gt;");
it('should escape > with &gt;', () => {
const result = escapeForHtml('>');
expect(result).toEqual('&gt;');
});
it('should escape " with &quot;', () => {
const result = escapeForHtml('"');
expect(result).toEqual("&quot;");
expect(result).toEqual('&quot;');
});
it("should escape ' with &#x27;", () => {
const result = escapeForHtml("'");
expect(result).toEqual("&#x27;");
expect(result).toEqual('&#x27;');
});
it("should escape / with &#x2F;", () => {
const result = escapeForHtml("/");
expect(result).toEqual("&#x2F;");
it('should escape / with &#x2F;', () => {
const result = escapeForHtml('/');
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>`);
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", () => {
it("should generate file unique id", () => {
describe('getHtmlId', () => {
it('should generate file unique id', () => {
const result = getHtmlId({
oldName: "sample.js",
newName: "sample.js"
oldName: 'sample.js',
newName: 'sample.js',
});
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");
expect(result).toEqual('d2h-960013');
});
});
describe("getDiffName", () => {
it("should generate the file name for a changed file", () => {
describe('getDiffName', () => {
it('should generate the file name for a changed file', () => {
const result = filenameDiff({
oldName: "sample.js",
newName: "sample.js"
oldName: '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({
oldName: "sample1.js",
newName: "sample2.js"
oldName: 'sample1.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({
oldName: "src/path/sample.js",
newName: "source/path/sample.js"
oldName: 'src/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({
oldName: "src/path/sample1.js",
newName: "src/path/sample2.js"
oldName: 'src/path/sample1.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({
oldName: "src/really/big/path/sample.js",
newName: "src/small/path/sample.js"
oldName: 'src/really/big/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({
oldName: "src/my/file.js",
newName: "/dev/null"
oldName: 'src/my/file.js',
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({
oldName: "/dev/null",
newName: "src/my/file.js"
oldName: '/dev/null',
newName: 'src/my/file.js',
});
expect(result).toEqual("src/my/file.js");
expect(result).toEqual('src/my/file.js');
});
});
describe("diffHighlight", () => {
it("should highlight two lines", () => {
const result = diffHighlight("-var myVar = 2;", "+var myVariable = 3;", false, {
matching: LineMatchingType.WORDS
describe('diffHighlight', () => {
it('should highlight two lines', () => {
const result = diffHighlight('-var myVar = 2;', '+var myVariable = 3;', false, {
matching: LineMatchingType.WORDS,
});
expect(result).toEqual({
oldLine: {
prefix: "-",
content: "var <del>myVar</del> = <del>2</del>;"
prefix: '-',
content: 'var <del>myVar</del> = <del>2</del>;',
},
newLine: {
prefix: "+",
content: "var <ins>myVariable</ins> = <ins>3</ins>;"
}
prefix: '+',
content: 'var <ins>myVariable</ins> = <ins>3</ins>;',
},
});
});
it("should highlight two lines char by char", () => {
const result = diffHighlight("-var myVar = 2;", "+var myVariable = 3;", false, {
diffStyle: DiffStyleType.CHAR
it('should highlight two lines char by char', () => {
const result = diffHighlight('-var myVar = 2;', '+var myVariable = 3;', false, {
diffStyle: DiffStyleType.CHAR,
});
expect(result).toEqual({
oldLine: {
prefix: "-",
content: "var myVar = <del>2</del>;"
prefix: '-',
content: 'var myVar = <del>2</del>;',
},
newLine: {
prefix: "+",
content: "var myVar<ins>iable</ins> = <ins>3</ins>;"
}
prefix: '+',
content: 'var myVar<ins>iable</ins> = <ins>3</ins>;',
},
});
});
it("should highlight combined diff lines", () => {
const result = diffHighlight(" -var myVar = 2;", " +var myVariable = 3;", true, {
it('should highlight combined diff lines', () => {
const result = diffHighlight(' -var myVar = 2;', ' +var myVariable = 3;', true, {
diffStyle: DiffStyleType.WORD,
matching: LineMatchingType.WORDS,
matchWordsThreshold: 1.0
matchWordsThreshold: 1.0,
});
expect(result).toEqual({
oldLine: {
prefix: " -",
content: 'var <del class="d2h-change">myVar</del> = <del class="d2h-change">2</del>;'
prefix: ' -',
content: 'var <del class="d2h-change">myVar</del> = <del class="d2h-change">2</del>;',
},
newLine: {
prefix: " +",
content: 'var <ins class="d2h-change">myVariable</ins> = <ins class="d2h-change">3</ins>;'
}
prefix: ' +',
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 HoganJsUtils from "../hoganjs-utils";
import { LineType, DiffLine, DiffFile, LineMatchingType } from "../types";
import { CSSLineClass } from "../render-utils";
import SideBySideRenderer from '../side-by-side-renderer';
import HoganJsUtils from '../hoganjs-utils';
import { LineType, DiffLine, DiffFile, LineMatchingType } from '../types';
import { CSSLineClass } from '../render-utils';
describe("SideBySideRenderer", () => {
describe("generateEmptyDiff", () => {
it("should return an empty diff", () => {
describe('SideBySideRenderer', () => {
describe('generateEmptyDiff', () => {
it('should return an empty diff', () => {
const hoganUtils = new HoganJsUtils({});
const sideBySideRenderer = new SideBySideRenderer(hoganUtils, {});
const fileHtml = sideBySideRenderer.generateEmptyDiff();
@ -24,8 +24,8 @@ describe("SideBySideRenderer", () => {
});
});
describe("generateSideBySideFileHtml", () => {
it("should generate lines with the right prefixes", () => {
describe('generateSideBySideFileHtml', () => {
it('should generate lines with the right prefixes', () => {
const hoganUtils = new HoganJsUtils({});
const sideBySideRenderer = new SideBySideRenderer(hoganUtils, {});
@ -35,44 +35,44 @@ describe("SideBySideRenderer", () => {
{
lines: [
{
content: " context",
content: ' context',
type: LineType.CONTEXT,
oldNumber: 19,
newNumber: 19
newNumber: 19,
},
{
content: "-removed",
content: '-removed',
type: LineType.DELETE,
oldNumber: 20,
newNumber: undefined
newNumber: undefined,
},
{
content: "+added",
content: '+added',
type: LineType.INSERT,
oldNumber: undefined,
newNumber: 20
newNumber: 20,
},
{
content: "+another added",
content: '+another added',
type: LineType.INSERT,
oldNumber: undefined,
newNumber: 21
}
newNumber: 21,
},
],
oldStartLine: 19,
newStartLine: 19,
header: "@@ -19,7 +19,7 @@"
}
header: '@@ -19,7 +19,7 @@',
},
],
deletedLines: 1,
addedLines: 2,
checksumBefore: "fc56817",
checksumAfter: "e8e7e49",
mode: "100644",
oldName: "coverage.init",
language: "init",
newName: "coverage.init",
isCombined: false
checksumBefore: 'fc56817',
checksumAfter: 'e8e7e49',
mode: '100644',
oldName: 'coverage.init',
language: 'init',
newName: 'coverage.init',
isCombined: false,
};
const fileHtml = sideBySideRenderer.generateFileHtml(file);
@ -156,15 +156,15 @@ describe("SideBySideRenderer", () => {
});
});
describe("generateSingleLineHtml", () => {
it("should work for insertions", () => {
describe('generateSingleLineHtml', () => {
it('should work for insertions', () => {
const hoganUtils = new HoganJsUtils({});
const sideBySideRenderer = new SideBySideRenderer(hoganUtils, {});
const fileHtml = sideBySideRenderer.generateLineHtml(undefined, {
type: CSSLineClass.INSERTS,
prefix: "+",
content: "test",
number: 30
prefix: '+',
content: 'test',
number: 30,
});
expect(fileHtml).toMatchInlineSnapshot(`
@ -194,17 +194,17 @@ describe("SideBySideRenderer", () => {
}
`);
});
it("should work for deletions", () => {
it('should work for deletions', () => {
const hoganUtils = new HoganJsUtils({});
const sideBySideRenderer = new SideBySideRenderer(hoganUtils, {});
const fileHtml = sideBySideRenderer.generateLineHtml(
{
type: CSSLineClass.DELETES,
prefix: "-",
content: "test",
number: 30
prefix: '-',
content: 'test',
number: 30,
},
undefined
undefined,
);
expect(fileHtml).toMatchInlineSnapshot(`
@ -236,42 +236,42 @@ describe("SideBySideRenderer", () => {
});
});
describe("generateSideBySideJsonHtml", () => {
it("should work for list of files", () => {
describe('generateSideBySideJsonHtml', () => {
it('should work for list of files', () => {
const exampleJson: DiffFile[] = [
{
blocks: [
{
lines: [
{
content: "-test",
content: '-test',
type: LineType.DELETE,
oldNumber: 1,
newNumber: undefined
newNumber: undefined,
},
{
content: "+test1r",
content: '+test1r',
type: LineType.INSERT,
oldNumber: undefined,
newNumber: 1
}
newNumber: 1,
},
],
oldStartLine: 1,
oldStartLine2: undefined,
newStartLine: 1,
header: "@@ -1 +1 @@"
}
header: '@@ -1 +1 @@',
},
],
deletedLines: 1,
addedLines: 1,
checksumBefore: "0000001",
checksumAfter: "0ddf2ba",
oldName: "sample",
language: "txt",
newName: "sample",
checksumBefore: '0000001',
checksumAfter: '0ddf2ba',
oldName: 'sample',
language: 'txt',
newName: 'sample',
isCombined: false,
isGitDiff: true
}
isGitDiff: true,
},
];
const hoganUtils = new HoganJsUtils({});
@ -341,18 +341,18 @@ describe("SideBySideRenderer", () => {
</div>"
`);
});
it("should work for files without blocks", () => {
it('should work for files without blocks', () => {
const exampleJson: DiffFile[] = [
{
blocks: [],
oldName: "sample",
language: "js",
newName: "sample",
oldName: 'sample',
language: 'js',
newName: 'sample',
isCombined: false,
addedLines: 0,
deletedLines: 0,
isGitDiff: false
}
isGitDiff: false,
},
];
const hoganUtils = new HoganJsUtils({});
@ -400,24 +400,24 @@ describe("SideBySideRenderer", () => {
});
});
describe("processLines", () => {
it("should process file lines", () => {
describe('processLines', () => {
it('should process file lines', () => {
const oldLines: DiffLine[] = [
{
content: "-test",
content: '-test',
type: LineType.DELETE,
oldNumber: 1,
newNumber: undefined
}
newNumber: undefined,
},
];
const newLines: DiffLine[] = [
{
content: "+test1r",
content: '+test1r',
type: LineType.INSERT,
oldNumber: undefined,
newNumber: 1
}
newNumber: 1,
},
];
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("escapeForRegExp", () => {
it("should escape markdown link text", () => {
const result = escapeForRegExp("[Link](https://diff2html.xyz)");
expect(result).toEqual("\\[Link\\]\\(https:\\/\\/diff2html\\.xyz\\)");
describe('Utils', () => {
describe('escapeForRegExp', () => {
it('should escape markdown link text', () => {
const result = escapeForRegExp('[Link](https://diff2html.xyz)');
expect(result).toEqual('\\[Link\\]\\(https:\\/\\/diff2html\\.xyz\\)');
});
it("should escape all dangerous characters", () => {
const result = escapeForRegExp("-[]/{}()*+?.\\^$|");
expect(result).toEqual("\\-\\[\\]\\/\\{\\}\\(\\)\\*\\+\\?\\.\\\\\\^\\$\\|");
it('should escape all dangerous characters', () => {
const result = escapeForRegExp('-[]/{}()*+?.\\^$|');
expect(result).toEqual('\\-\\[\\]\\/\\{\\}\\(\\)\\*\\+\\?\\.\\\\\\^\\$\\|');
});
});
describe("unifyPath", () => {
it("should unify windows style path", () => {
const result = unifyPath("\\Users\\Downloads\\diff.html");
expect(result).toEqual("/Users/Downloads/diff.html");
describe('unifyPath', () => {
it('should unify windows style path', () => {
const result = unifyPath('\\Users\\Downloads\\diff.html');
expect(result).toEqual('/Users/Downloads/diff.html');
});
});
describe("hashCode", () => {
it("should create consistent hash for a text piece", () => {
const string = "/home/diff2html/diff.html";
describe('hashCode', () => {
it('should create consistent hash for a text piece', () => {
const string = '/home/diff2html/diff.html';
expect(hashCode(string)).toEqual(hashCode(string));
});
it("should create different hash for different text pieces", () => {
expect(hashCode("/home/diff2html/diff1.html")).not.toEqual(hashCode("/home/diff2html/diff2.html"));
it('should create different hash for different text pieces', () => {
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 { escapeForRegExp } from "./utils";
import { DiffFile, DiffBlock, DiffLine, LineType } from './types';
import { escapeForRegExp } from './utils';
export interface DiffParserConfig {
srcPrefix?: string;
@ -7,7 +7,7 @@ export interface DiffParserConfig {
}
function getExtension(filename: string, language: string): string {
const filenameParts = filename.split(".");
const filenameParts = filename.split('.');
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);
}
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 {
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('^"?(.+?)"?$');
const [, filename = ""] = FilenameRegExp.exec(line) || [];
const [, filename = ''] = FilenameRegExp.exec(line) || [];
const matchingPrefix = prefixes.find(p => filename.indexOf(p) === 0);
const fnameWithoutPrefix = matchingPrefix ? filename.slice(matchingPrefix.length) : filename;
// Cleanup timestamps generated by the unified diff (diff command) as specified in
// https://www.gnu.org/software/diffutils/manual/html_node/Detailed-Unified.html
// 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 {
return getFilename(line, "---", srcPrefix);
return getFilename(line, '---', srcPrefix);
}
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;
/* Diff Header */
const oldFileNameHeader = "--- ";
const newFileNameHeader = "+++ ";
const hunkHeaderPrefix = "@@";
const oldFileNameHeader = '--- ';
const newFileNameHeader = '+++ ';
const hunkHeaderPrefix = '@@';
/* Diff */
const oldMode = /^old mode (\d{6})/;
@ -79,21 +79,21 @@ export function parse(diffInput: string, config: DiffParserConfig = {}): DiffFil
const similarityIndex = /^similarity 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 binaryDiff = /^GIT binary patch/;
/* 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 combinedNewFile = /^new file mode (\d{6})/;
const combinedDeletedFile = /^deleted file mode (\d{6}),(\d{6})/;
const diffLines = diffInput
.replace(/\\ No newline at end of file/g, "")
.replace(/\r\n?/g, "\n")
.split("\n");
.replace(/\\ No newline at end of file/g, '')
.replace(/\r\n?/g, '\n')
.split('\n');
/* Add previous block(if exists) before start a new file */
function saveBlock(): void {
@ -137,7 +137,7 @@ export function parse(diffInput: string, config: DiffParserConfig = {}): DiffFil
currentFile = {
blocks: [],
deletedLines: 0,
addedLines: 0
addedLines: 0,
};
}
@ -172,7 +172,7 @@ export function parse(diffInput: string, config: DiffParserConfig = {}): DiffFil
newLine = parseInt(values[3], 10);
} else {
if (line.startsWith(hunkHeaderPrefix)) {
console.error("Failed to parse lines, starting in 0!");
console.error('Failed to parse lines, starting in 0!');
}
oldLine = 0;
@ -189,7 +189,7 @@ export function parse(diffInput: string, config: DiffParserConfig = {}): DiffFil
oldStartLine: oldLine,
oldStartLine2: oldLine2,
newStartLine: newLine,
header: line
header: line,
};
}
@ -199,11 +199,11 @@ export function parse(diffInput: string, config: DiffParserConfig = {}): DiffFil
// eslint-disable-next-line
// @ts-ignore
const currentLine: DiffLine = {
content: line
content: line,
};
const addedPrefixes = currentFile.isCombined ? ["+ ", " +", "++"] : ["+"];
const deletedPrefixes = currentFile.isCombined ? ["- ", " -", "--"] : ["-"];
const addedPrefixes = currentFile.isCombined ? ['+ ', ' +', '++'] : ['+'];
const deletedPrefixes = currentFile.isCombined ? ['- ', ' -', '--'] : ['-'];
if (startsWithAny(line, addedPrefixes)) {
currentFile.addedLines++;
@ -232,7 +232,7 @@ export function parse(diffInput: string, config: DiffParserConfig = {}): DiffFil
let idx = lineIdx;
while (idx < diffLines.length - 3) {
if (line.startsWith("diff")) {
if (line.startsWith('diff')) {
return false;
}
@ -254,7 +254,7 @@ export function parse(diffInput: string, config: DiffParserConfig = {}): DiffFil
// Unmerged paths, and possibly other non-diffable files
// https://github.com/scottgonzalez/pretty-diff/issues/11
// Also, remove some useless lines
if (!line || line.startsWith("*")) {
if (!line || line.startsWith('*')) {
return;
}
@ -265,7 +265,7 @@ export function parse(diffInput: string, config: DiffParserConfig = {}): DiffFil
const nxtLine = diffLines[lineIndex + 1];
const afterNxtLine = diffLines[lineIndex + 2];
if (line.startsWith("diff")) {
if (line.startsWith('diff')) {
startFile();
// 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) {
throw new Error("Where is my file !!!");
throw new Error('Where is my file !!!');
}
currentFile.isGitDiff = true;
@ -311,7 +311,7 @@ export function parse(diffInput: string, config: DiffParserConfig = {}): DiffFil
if (
currentFile &&
!currentFile.oldName &&
line.startsWith("--- ") &&
line.startsWith('--- ') &&
(values = getSrcFilename(line, config.srcPrefix))
) {
currentFile.oldName = values;
@ -326,7 +326,7 @@ export function parse(diffInput: string, config: DiffParserConfig = {}): DiffFil
if (
currentFile &&
!currentFile.newName &&
line.startsWith("+++ ") &&
line.startsWith('+++ ') &&
(values = getDstFilename(line, config.dstPrefix))
) {
currentFile.newName = values;
@ -350,7 +350,7 @@ export function parse(diffInput: string, config: DiffParserConfig = {}): DiffFil
* 2. Old line starts with: -
* 3. Context line starts with: <SPACE>
*/
if (currentBlock && (line.startsWith("+") || line.startsWith("-") || line.startsWith(" "))) {
if (currentBlock && (line.startsWith('+') || line.startsWith('-') || line.startsWith(' '))) {
createLine(line);
return;
}
@ -358,7 +358,7 @@ export function parse(diffInput: string, config: DiffParserConfig = {}): DiffFil
const doesNotExistHunkHeader = !existHunkHeader(line, lineIndex);
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.oldName = getFilename(values[1], undefined, config.srcPrefix);
currentFile.newName = getFilename(values[2], undefined, config.dstPrefix);
startBlock("Binary file");
startBlock('Binary file');
} else if (binaryDiff.test(line)) {
currentFile.isBinary = true;
startBlock(line);
@ -417,9 +417,11 @@ export function parse(diffInput: string, config: DiffParserConfig = {}): DiffFil
} else if ((values = combinedMode.exec(line))) {
currentFile.oldMode = [values[2], values[3]];
currentFile.newMode = values[1];
// eslint-disable-next-line sonarjs/no-duplicated-branches
} else if ((values = combinedNewFile.exec(line))) {
currentFile.newFileMode = values[1];
currentFile.isNew = true;
// eslint-disable-next-line sonarjs/no-duplicated-branches
} else if ((values = combinedDeletedFile.exec(line))) {
currentFile.deletedFileMode = values[1];
currentFile.isDeleted = true;

View file

@ -1,9 +1,9 @@
import * as DiffParser from "./diff-parser";
import * as fileListPrinter from "./file-list-renderer";
import LineByLineRenderer, { LineByLineRendererConfig, defaultLineByLineRendererConfig } from "./line-by-line-renderer";
import SideBySideRenderer, { SideBySideRendererConfig, defaultSideBySideRendererConfig } from "./side-by-side-renderer";
import { DiffFile, OutputFormatType } from "./types";
import HoganJsUtils, { HoganJsUtilsConfig } from "./hoganjs-utils";
import * as DiffParser from './diff-parser';
import * as fileListPrinter from './file-list-renderer';
import LineByLineRenderer, { LineByLineRendererConfig, defaultLineByLineRendererConfig } from './line-by-line-renderer';
import SideBySideRenderer, { SideBySideRendererConfig, defaultSideBySideRendererConfig } from './side-by-side-renderer';
import { DiffFile, OutputFormatType } from './types';
import HoganJsUtils, { HoganJsUtilsConfig } from './hoganjs-utils';
export interface Diff2HtmlConfig
extends DiffParser.DiffParserConfig,
@ -18,7 +18,7 @@ export const defaultDiff2HtmlConfig = {
...defaultLineByLineRendererConfig,
...defaultSideBySideRendererConfig,
outputFormat: OutputFormatType.LINE_BY_LINE,
drawFileList: true
drawFileList: true,
};
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 {
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 fileList = config.drawFileList ? fileListPrinter.render(diffJson, hoganUtils) : "";
const fileList = config.drawFileList ? fileListPrinter.render(diffJson, hoganUtils) : '';
const diffOutput =
config.outputFormat === "side-by-side"
config.outputFormat === 'side-by-side'
? new SideBySideRenderer(hoganUtils, config).render(diffJson)
: new LineByLineRenderer(hoganUtils, config).render(diffJson);

View file

@ -1,33 +1,33 @@
import * as renderUtils from "./render-utils";
import HoganJsUtils from "./hoganjs-utils";
import { DiffFile } from "./types";
import * as renderUtils from './render-utils';
import HoganJsUtils from './hoganjs-utils';
import { DiffFile } from './types';
const baseTemplatesPath = "file-summary";
const iconsBaseTemplatesPath = "icon";
const baseTemplatesPath = 'file-summary';
const iconsBaseTemplatesPath = 'icon';
export function render(diffFiles: DiffFile[], hoganUtils: HoganJsUtils): string {
const files = diffFiles
.map(file =>
hoganUtils.render(
baseTemplatesPath,
"line",
'line',
{
fileHtmlId: renderUtils.getHtmlId(file),
oldName: file.oldName,
newName: file.newName,
fileName: renderUtils.filenameDiff(file),
deletedLines: "-" + file.deletedLines,
addedLines: "+" + file.addedLines
deletedLines: '-' + file.deletedLines,
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,
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 {
[name: string]: string;
@ -24,7 +24,7 @@ export default class HoganJsUtils {
const compiledTemplate: Hogan.Template = Hogan.compile(templateString, { asString: false });
return { ...previousTemplates, [name]: compiledTemplate };
},
{}
{},
);
this.preCompiledTemplates = { ...defaultTemplates, ...compiledTemplates, ...compiledRawTemplates };

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -51,7 +51,7 @@
-ms-flex-align: center;
align-items: center;
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;
}
@ -70,7 +70,7 @@
.d2h-diff-table {
width: 100%;
border-collapse: collapse;
font-family: "Menlo", "Consolas", monospace;
font-family: 'Menlo', 'Consolas', monospace;
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 * as HighlightJSInternals from "./highlight.js-internals";
import { html, Diff2HtmlConfig, defaultDiff2HtmlConfig } from "../../diff2html";
import { DiffFile } from "../../types";
import hljs from 'highlight.js';
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;
targetElement: HTMLElement;
currentSelectionColumnId = -1;
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 = {}) {
this.config = { ...defaultDiff2HtmlUIConfig, ...config };
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;
super(diffInput, target, config, hljs);
}
}
export { Diff2HtmlUIConfig, defaultDiff2HtmlUIConfig };

View file

@ -1,15 +1,15 @@
/*
* Copied from Highlight.js Private API
* Will be removed when this part of the API is exposed
* Adapted Highlight.js Internal APIs
* Used to highlight selected html elements using context
*/
/* Utility functions */
function escape(value: string): string {
return value
.replace(/&/gm, "&amp;")
.replace(/</gm, "&lt;")
.replace(/>/gm, "&gt;");
.replace(/&/gm, '&amp;')
.replace(/</gm, '&lt;')
.replace(/>/gm, '&gt;');
}
function tag(node: Node): string {
@ -19,7 +19,7 @@ function tag(node: Node): string {
/* Stream merging */
type NodeEvent = {
event: "start" | "stop";
event: 'start' | 'stop';
offset: number;
node: Node;
};
@ -33,9 +33,9 @@ export function nodeStream(node: Node): NodeEvent[] {
offset += child.nodeValue.length;
} else if (child.nodeType === 1) {
result.push({
event: "start",
event: 'start',
offset: offset,
node: child
node: child,
});
offset = nodeStream(child, offset);
// 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.
if (!tag(child).match(/br|hr|img|input/)) {
result.push({
event: "stop",
event: 'stop',
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 {
let processed = 0;
let result = "";
let result = '';
const nodeStack = [];
function selectStream(): NodeEvent[] {
@ -84,22 +84,22 @@ export function mergeStreams(original: NodeEvent[], highlighted: NodeEvent[], va
return highlighted;
... which is collapsed to:
*/
return highlighted[0].event === "start" ? original : highlighted;
return highlighted[0].event === 'start' ? original : highlighted;
}
function open(node: Node): void {
const htmlNode = node as HTMLElement;
result += `<${tag(node)} ${[].map
.call(htmlNode.attributes, (attr: Attr) => `${attr.nodeName}="${escape(attr.value)}"`)
.join(" ")}>`;
.join(' ')}>`;
}
function close(node: Node): void {
result += "</" + tag(node) + ">";
result += '</' + tag(node) + '>';
}
function render(event: NodeEvent): void {
(event.event === "start" ? open : close)(event.node);
(event.event === 'start' ? open : close)(event.node);
}
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);
nodeStack.reverse().forEach(open);
} else {
if (stream[0].event === "start") {
if (stream[0].event === 'start') {
nodeStack.push(stream[0].node);
} else {
nodeStack.pop();
@ -130,5 +130,3 @@ export function mergeStreams(original: NodeEvent[], highlighted: NodeEvent[], va
}
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 = [
// Order matters for these
"-",
"[",
"]",
'-',
'[',
']',
// Order doesn't matter for any of these
"/",
"{",
"}",
"(",
")",
"*",
"+",
"?",
".",
"\\",
"^",
"$",
"|"
'/',
'{',
'}',
'(',
')',
'*',
'+',
'?',
'.',
'\\',
'^',
'$',
'|',
];
// All characters will be escaped with '\'
// 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
*/
export function escapeForRegExp(str: string): string {
return str.replace(regex, "\\$&");
return str.replace(regex, '\\$&');
}
/**
* Converts all '\' in @path to unix style '/'
*/
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;
}

View file

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

View file

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

View file

@ -17,11 +17,11 @@
}
.m-b-md {
margin-bottom: 23px !important
margin-bottom: 23px !important;
}
.p-t {
padding-top: 15px !important
padding-top: 15px !important;
}
@media (min-width: 768px) {
@ -34,8 +34,8 @@
.btn {
display: inline-block;
color: #fff;
background: #26A65B;
font-weight: 400
background: #26a65b;
font-weight: 400;
}
.btn:hover {
@ -67,19 +67,19 @@
padding: 40px 0;
text-align: center;
font-size: 14px;
border-top: 1px solid #dcdfe4
border-top: 1px solid #dcdfe4;
}
.footer p {
margin-bottom: 5px
margin-bottom: 5px;
}
.footer a {
color: #26A65B;
color: #26a65b;
}
.container a {
color: #26A65B;
color: #26a65b;
}
.container a.btn {
@ -87,11 +87,11 @@
}
.footer-list-item {
display: inline-block
display: inline-block;
}
.footer-list-item:not(:last-child):after {
content: "\b7"
content: '\b7';
}
.footer > ul {
@ -116,7 +116,7 @@
}
.row-bordered {
position: relative
position: relative;
}
.row-bordered:before {
@ -129,14 +129,14 @@
margin-left: -40%;
height: 1px;
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 {
position: relative;
text-align: center;
padding: 80px 0;
border-bottom: 1px solid #dcdfe4
border-bottom: 1px solid #dcdfe4;
}
.hero-booticon {
@ -159,7 +159,7 @@
}
.hero-homepage > .btn {
margin-top: 20px
margin-top: 20px;
}
.swag-line:before {
@ -171,9 +171,9 @@
right: 0;
height: 5px;
z-index: 2;
background-color: #26A65B;
background: -webkit-linear-gradient(45deg, #28a142, #26A65B);
background: linear-gradient(45deg, #28a142, #26A65B)
background-color: #26a65b;
background: -webkit-linear-gradient(45deg, #28a142, #26a65b);
background: linear-gradient(45deg, #28a142, #26a65b);
}
.navbar {
@ -182,7 +182,7 @@
}
.navbar-header {
text-align: center
text-align: center;
}
.navbar-brand {
@ -192,21 +192,26 @@
display: inline-block;
float: none;
text-align: center;
margin: 5px 0 0
margin: 5px 0 0;
}
.navbar-nav {
margin-right: -15px
margin-right: -15px;
}
.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;
color: #293a46;
font-weight: 300
font-weight: 300;
}
.navbar-default .navbar-toggle {
@ -215,88 +220,94 @@
top: 7px;
border-color: #fff;
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;
border-color: #f9f9f9
border-color: #f9f9f9;
}
@media (min-width: 768px) {
.navbar-full .navbar-brand {
margin-left: -25px
margin-left: -25px;
}
.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;
text-align: left
text-align: left;
}
.navbar-brand {
float: none;
display: inline-block;
text-align: left;
margin: 0
margin: 0;
}
.navbar-nav > li > a {
display: inline-block;
margin-left: 13px
margin-left: 13px;
}
.navbar-nav > li:first-child > a {
margin-left: 0
margin-left: 0;
}
}
body {
font-size: 16px;
font-family: Roboto, sans-serif;
font-weight: 300;
line-height: 1.6
line-height: 1.6;
}
h1 {
font-size: 26px;
font-weight: 300
font-weight: 300;
}
h2 {
font-size: 18px;
font-weight: 300
font-weight: 300;
}
h3 {
font-size: 26px;
font-weight: 300
font-weight: 300;
}
h4 {
font-size: 16px;
font-weight: 300
font-weight: 300;
}
h5 {
font-size: 16px;
font-weight: 400
font-weight: 400;
}
h1, h2, h3, h4, h5 {
line-height: 1.4
h1,
h2,
h3,
h4,
h5 {
line-height: 1.4;
}
h1, h2 {
margin: 10px 0
h1,
h2 {
margin: 10px 0;
}
h5 {
margin: 6px 0
margin: 6px 0;
}
@media (min-width: 768px) {
@ -304,33 +315,33 @@ h5 {
font-size: 16px;
font-family: Roboto, sans-serif;
font-weight: 300;
line-height: 1.6
line-height: 1.6;
}
h1 {
font-size: 38px;
font-weight: 300
font-weight: 300;
}
h2 {
font-size: 26px;
font-weight: 300;
line-height: 1.4
line-height: 1.4;
}
h3 {
font-size: 26px;
font-weight: 300
font-weight: 300;
}
h4 {
font-size: 18px;
font-weight: 300
font-weight: 300;
}
h5 {
font-size: 16px;
font-weight: 400
font-weight: 400;
}
}
@ -343,7 +354,8 @@ a {
color: inherit;
}
a:hover, a:focus {
a:hover,
a:focus {
text-decoration: underline;
}
@ -357,40 +369,42 @@ a:hover, a:focus {
}
.text-muted {
color: #697176
color: #697176;
}
.template-index h3 {
font-size: 21px;
margin-bottom: 12px
margin-bottom: 12px;
}
.template-index h4 {
color: #697176;
line-height: 1.6
line-height: 1.6;
}
.template-index h4 a, .template-index p a {
color: #26A65B;
.template-index h4 a,
.template-index p a {
color: #26a65b;
}
.template-index h5 {
font-size: 17px;
margin-bottom: 8px
margin-bottom: 8px;
}
.homepage-terminal-example, .homepage-code-example {
.homepage-terminal-example,
.homepage-code-example {
position: relative;
font-family: monospace;
background: #272b38;
color: #48d8a0;
border-radius: 8px;
padding: 30px
padding: 30px;
}
.homepage-terminal-example .text-muted,
.homepage-code-example .text-muted {
color: #6a7490
color: #6a7490;
}
@media (min-width: 768px) {
@ -408,7 +422,7 @@ a:hover, a:focus {
}
.hero-green {
color: #26A65B;
color: #26a65b;
}
.hero-black {
@ -416,7 +430,7 @@ a:hover, a:focus {
}
.hero-red {
color: #CB2C37;
color: #cb2c37;
}
.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> => {
let partial = handlebars.partials[name];
if (typeof partial === "string") {
if (typeof partial === 'string') {
partial = handlebars.compile(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 => {
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.css";
import "./demo.css";
import "../../../../src/ui/css/diff2html.css";
import '../../../main.ts';
import '../../../main.css';
import 'highlight.js/styles/github.css';
import '../../../../src/ui/css/diff2html.css';
/*
* Example URLs:
@ -23,18 +23,18 @@ type URLParams = {
[key: string]: string | boolean | number | undefined;
};
const searchParam = "diff";
const searchParam = 'diff';
function getParamsFromSearch(search: string): URLParams {
try {
return search
.split("?")[1]
.split("&")
.split('?')[1]
.split('&')
.reduce((urlParams, e) => {
const values = e.split("=");
const values = e.split('=');
return {
...urlParams,
[values[0]]: values[1]
[values[0]]: values[1],
};
}, {});
} catch (_ignore) {
@ -43,8 +43,8 @@ function getParamsFromSearch(search: string): URLParams {
}
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(
url
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,
);
}
@ -55,7 +55,7 @@ type Request = {
function prepareRequest(url: string): Request {
if (!validateUrl(url)) {
const errorMsg = "Invalid url provided!";
const errorMsg = 'Invalid url provided!';
console.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 {
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 {
headers.append("Accept", "application/vnd.github.v3.diff");
return "https://api.github.com/repos/" + userName + "/" + projectName + "/" + type + "/" + value;
headers.append('Accept', 'application/vnd.github.v3.diff');
return 'https://api.github.com/repos/' + userName + '/' + projectName + '/' + type + '/' + value;
}
function bitbucketUrlGen(userName: string, projectName: string, type: string, value: string): string {
const baseUrl = "https://bitbucket.org/api/2.0/repositories/";
if (type === "pullrequests") {
return baseUrl + userName + "/" + projectName + "/pullrequests/" + value + "/diff";
const baseUrl = 'https://bitbucket.org/api/2.0/repositories/';
if (type === 'pullrequests') {
return baseUrl + userName + '/' + projectName + '/pullrequests/' + value + '/diff';
}
return baseUrl + userName + "/" + projectName + "/diff/" + value;
return baseUrl + userName + '/' + projectName + '/diff/' + value;
}
let values;
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))) {
fetchUrl = gitHubUrlGen(values[1], values[2], "pulls", values[3]);
fetchUrl = gitHubUrlGen(values[1], values[2], 'pulls', values[3]);
} 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))) {
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))) {
fetchUrl = bitbucketUrlGen(values[1], values[2], "commit", values[3]);
fetchUrl = bitbucketUrlGen(values[1], values[2], 'commit', values[3]);
} 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 {
console.info("Could not parse url, using the provided url.");
fetchUrl = "https://crossorigin.me/" + url;
console.info('Could not parse url, using the provided url.');
fetchUrl = 'https://crossorigin.me/' + url;
}
return {
url: fetchUrl,
headers: headers
headers: headers,
};
}
@ -121,13 +121,13 @@ function getConfiguration(urlParams: URLParams): Diff2HtmlUIConfig {
const { diff, ...urlParamsRest } = urlParams;
const config: URLParams = {
...defaultDiff2HtmlUIConfig,
...urlParamsRest
...urlParamsRest,
};
return Object.entries(config).reduce((object, [k, v]) => {
const newObject = !Number.isNaN(Number(v))
? { [k]: Number(v) }
: v === "true" || v === "false"
: v === 'true' || v === 'false'
? { [k]: Boolean(v) }
: { [k]: v };
return { ...object, ...newObject };
@ -137,14 +137,14 @@ function getConfiguration(urlParams: URLParams): Diff2HtmlUIConfig {
async function getDiff(request: Request): Promise<string> {
try {
const result = await fetch(request.url, {
method: "GET",
method: 'GET',
headers: request.headers,
mode: "cors",
cache: "default"
mode: 'cors',
cache: 'default',
});
return result.text();
} catch (error) {
console.error("Failed to retrieve diff", error);
console.error('Failed to retrieve diff', error);
throw error;
}
}
@ -152,10 +152,10 @@ async function getDiff(request: Request): Promise<string> {
function draw(diffString: string, config: Diff2HtmlUIConfig, elements: Elements): void {
const diff2htmlUi = new Diff2HtmlUI(diffString, elements.structure.diffTarget, config);
if (config.outputFormat === "side-by-side") {
elements.structure.container.style.width = "100%";
if (config.outputFormat === 'side-by-side') {
elements.structure.container.style.width = '100%';
} else {
elements.structure.container.style.width = "";
elements.structure.container.style.width = '';
}
diff2htmlUi.draw();
@ -163,7 +163,7 @@ function draw(diffString: string, config: Diff2HtmlUIConfig, elements: Elements)
async function prepareInitialState(elements: Elements): Promise<[Diff2HtmlUIConfig, string]> {
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;
@ -178,20 +178,20 @@ async function prepareInitialState(elements: Elements): Promise<[Diff2HtmlUIConf
function updateBrowserUrl(config: Diff2HtmlUIConfig, newDiffUrl: string): void {
if (history.pushState) {
const paramString = Object.entries(config)
.map(([k, v]) => k + "=" + v)
.join("&");
.map(([k, v]) => k + '=' + v)
.join('&');
const newPageUrl =
window.location.protocol +
"//" +
'//' +
window.location.host +
window.location.pathname +
"?" +
'?' +
paramString +
"&" +
'&' +
searchParam +
"=" +
'=' +
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
require("whatwg-fetch");
require('whatwg-fetch');
const drawAndUpdateUrl = async (
diffUrl: string,
diffString: string,
config: Diff2HtmlUIConfig,
elements: Elements
elements: Elements,
): Promise<void> => {
updateBrowserUrl(config, diffUrl);
const newRequest = prepareRequest(diffUrl);
@ -233,22 +233,22 @@ document.addEventListener("DOMContentLoaded", async () => {
const elements: Elements = {
structure: {
container: document.getElementsByClassName("container")[0] as HTMLElement,
diffTarget: document.getElementById("url-diff-container") as HTMLElement
container: document.getElementsByClassName('container')[0] as HTMLElement,
diffTarget: document.getElementById('url-diff-container') as HTMLElement,
},
url: {
input: document.getElementById("url") as HTMLInputElement,
button: document.getElementById("url-btn") as HTMLElement
input: document.getElementById('url') as HTMLInputElement,
button: document.getElementById('url-btn') as HTMLElement,
},
options: {
outputFormat: document.getElementById("diff-url-options-output-format") as HTMLInputElement,
matching: document.getElementById("diff-url-options-matching") as HTMLInputElement,
wordsThreshold: document.getElementById("diff-url-options-match-words-threshold") as HTMLInputElement,
matchingMaxComparisons: document.getElementById("diff-url-options-matching-max-comparisons") as HTMLInputElement
outputFormat: document.getElementById('diff-url-options-output-format') as HTMLInputElement,
matching: document.getElementById('diff-url-options-matching') as HTMLInputElement,
wordsThreshold: document.getElementById('diff-url-options-match-words-threshold') as HTMLInputElement,
matchingMaxComparisons: document.getElementById('diff-url-options-matching-max-comparisons') as HTMLInputElement,
},
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);
@ -262,20 +262,20 @@ document.addEventListener("DOMContentLoaded", async () => {
(elements.options.matchingMaxComparisons.value = config.matchingMaxComparisons.toString());
Object.entries(elements.options).forEach(([option, element]) =>
element.addEventListener("change", () => {
element.addEventListener('change', () => {
config = { ...config, [option]: element.value };
drawAndUpdateUrl(elements.url.input.value, diffString, config, elements);
})
}),
);
Object.entries(elements.checkboxes).forEach(([option, checkbox]) =>
checkbox.addEventListener("change", () => {
checkbox.addEventListener('change', () => {
config = { ...config, [option]: checkbox.checked };
drawAndUpdateUrl(elements.url.input.value, diffString, config, elements);
})
}),
);
elements.url.button.addEventListener("click", async e => {
elements.url.button.addEventListener('click', async e => {
e.preventDefault();
const newDiffUrl = elements.url.input.value;
const newRequest = prepareRequest(newDiffUrl);

View file

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

View file

@ -1,8 +1,8 @@
import Clipboard from "clipboard";
import Clipboard from 'clipboard';
import "../../../main.ts";
import "../../../main.css";
import "./index.css";
import '../../../main.ts';
import '../../../main.css';
import './index.css';
// 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