Compare commits

..

No commits in common. "master" and "v0.0.1" have entirely different histories.

102 changed files with 579 additions and 32018 deletions

View file

@ -1,342 +0,0 @@
{
"projectName": "diff2html",
"projectOwner": "rtfpessoa",
"repoType": "github",
"repoHost": "https://github.com",
"files": [
"README.md"
],
"imageSize": 100,
"commit": true,
"commitConvention": "none",
"contributors": [
{
"login": "rtfpessoa",
"name": "Rodrigo Fernandes",
"avatar_url": "https://avatars0.githubusercontent.com/u/902384?v=4",
"profile": "https://rtfpessoa.xyz",
"contributions": [
"code"
]
},
{
"login": "stockmind",
"name": "stockmind",
"avatar_url": "https://avatars3.githubusercontent.com/u/5653847?v=4",
"profile": "https://github.com/stockmind",
"contributions": [
"code"
]
},
{
"login": "lantian",
"name": "Ivan Vorontsov",
"avatar_url": "https://avatars3.githubusercontent.com/u/535545?v=4",
"profile": "https://github.com/lantian",
"contributions": [
"code"
]
},
{
"login": "brewern",
"name": "Nick Brewer",
"avatar_url": "https://avatars1.githubusercontent.com/u/129300?v=4",
"profile": "http://www.nick-brewer.com",
"contributions": [
"code"
]
},
{
"login": "romellem",
"name": "Matt Wade",
"avatar_url": "https://avatars0.githubusercontent.com/u/8504000?v=4",
"profile": "http://heyitsmattwade.com",
"contributions": [
"bug",
"code"
]
},
{
"login": "mrfyda",
"name": "Rafael Cortês",
"avatar_url": "https://avatars1.githubusercontent.com/u/593860?v=4",
"profile": "http://mrfyda.github.io",
"contributions": [
"code"
]
},
{
"login": "nmatpt",
"name": "Nuno Teixeira",
"avatar_url": "https://avatars2.githubusercontent.com/u/5034733?v=4",
"profile": "https://github.com/nmatpt",
"contributions": [
"code"
]
},
{
"login": "kaishuu0123",
"name": "Koki Oyatsu",
"avatar_url": "https://avatars0.githubusercontent.com/u/1567423?v=4",
"profile": "https://saino.me/",
"contributions": [
"bug",
"code"
]
},
{
"login": "Jameskmonger",
"name": "James Monger",
"avatar_url": "https://avatars2.githubusercontent.com/u/2037007?v=4",
"profile": "http://www.jamesmonger.com",
"contributions": [
"doc"
]
},
{
"login": "wesssel",
"name": "Wessel van der Pal",
"avatar_url": "https://avatars2.githubusercontent.com/u/7767299?v=4",
"profile": "http://wesssel.github.io/",
"contributions": [
"security",
"code"
]
},
{
"login": "jung-kim",
"name": "jk-kim",
"avatar_url": "https://avatars2.githubusercontent.com/u/5281068?v=4",
"profile": "https://jung-kim.github.io",
"contributions": [
"code"
]
},
{
"login": "sss0791",
"name": "Sergey Semenov",
"avatar_url": "https://avatars1.githubusercontent.com/u/1446970?v=4",
"profile": "https://github.com/sss0791",
"contributions": [
"bug",
"code"
]
},
{
"login": "starpit",
"name": "Nick Mitchell",
"avatar_url": "https://avatars3.githubusercontent.com/u/4741620?v=4",
"profile": "http://researcher.watson.ibm.com/researcher/view.php?person=us-nickm",
"contributions": [
"bug",
"code"
]
},
{
"login": "samiraguiar",
"name": "Samir Aguiar",
"avatar_url": "https://avatars0.githubusercontent.com/u/13439135?v=4",
"profile": "https://github.com/samiraguiar",
"contributions": [
"doc"
]
},
{
"login": "pubkey",
"name": "pubkey",
"avatar_url": "https://avatars3.githubusercontent.com/u/8926560?v=4",
"profile": "https://twitter.com/pubkeypubkey",
"contributions": [
"doc",
"code"
]
},
{
"login": "iliyaZelenko",
"name": "Илья",
"avatar_url": "https://avatars1.githubusercontent.com/u/13103045?v=4",
"profile": "https://github.com/iliyaZelenko",
"contributions": [
"doc"
]
},
{
"login": "mohd-akram",
"name": "Mohamed Akram",
"avatar_url": "https://avatars0.githubusercontent.com/u/1823771?v=4",
"profile": "https://akr.am",
"contributions": [
"bug",
"doc",
"code"
]
},
{
"login": "emarcotte",
"name": "Eugene Marcotte",
"avatar_url": "https://avatars0.githubusercontent.com/u/249390?v=4",
"profile": "https://github.com/emarcotte",
"contributions": [
"code"
]
},
{
"login": "dsabanin",
"name": "Dima Sabanin",
"avatar_url": "https://avatars0.githubusercontent.com/u/8316?v=4",
"profile": "http://twitter.com/dimasabanin",
"contributions": [
"maintenance",
"code"
]
},
{
"login": "benabbottnz",
"name": "Ben Abbott",
"avatar_url": "https://avatars2.githubusercontent.com/u/2616473?v=4",
"profile": "https://github.com/benabbottnz",
"contributions": [
"doc"
]
},
{
"login": "dickeylth",
"name": "弘树@阿里",
"avatar_url": "https://avatars1.githubusercontent.com/u/2196373?v=4",
"profile": "http://webminer.js.org",
"contributions": [
"bug",
"doc"
]
},
{
"login": "Rantanen",
"name": "Mikko Rantanen",
"avatar_url": "https://avatars0.githubusercontent.com/u/385385?v=4",
"profile": "https://github.com/Rantanen",
"contributions": [
"bug"
]
},
{
"login": "extend1994",
"name": "Ann",
"avatar_url": "https://avatars2.githubusercontent.com/u/13430892?v=4",
"profile": "https://github.com/extend1994",
"contributions": [
"doc"
]
},
{
"login": "escitalopram",
"name": "escitalopram",
"avatar_url": "https://avatars0.githubusercontent.com/u/1155220?v=4",
"profile": "https://github.com/escitalopram",
"contributions": [
"code"
]
},
{
"login": "dependabot[bot]",
"name": "dependabot[bot]",
"avatar_url": "https://avatars0.githubusercontent.com/in/29110?v=4",
"profile": "https://github.com/apps/dependabot",
"contributions": [
"security",
"maintenance"
]
},
{
"login": "JoshuaKGoldberg",
"name": "Josh Goldberg",
"avatar_url": "https://avatars1.githubusercontent.com/u/3335181?v=4",
"profile": "http://www.joshuakgoldberg.com",
"contributions": [
"code"
]
},
{
"login": "apeckham",
"name": "Aaron",
"avatar_url": "https://avatars.githubusercontent.com/u/14110?v=4",
"profile": "https://github.com/apeckham",
"contributions": [
"doc"
]
},
{
"login": "pgrimaud",
"name": "Pierre Grimaud",
"avatar_url": "https://avatars.githubusercontent.com/u/1866496?v=4",
"profile": "https://github.com/pgrimaud",
"contributions": [
"doc"
]
},
{
"login": "domdomegg",
"name": "Adam Jones",
"avatar_url": "https://avatars.githubusercontent.com/u/4953590?v=4",
"profile": "https://domdomegg.github.io/",
"contributions": [
"doc"
]
},
{
"login": "charguer",
"name": "Arthur Charguéraud",
"avatar_url": "https://avatars.githubusercontent.com/u/1830652?v=4",
"profile": "https://github.com/charguer",
"contributions": [
"doc"
]
},
{
"login": "Pierrci",
"name": "Pierric Cistac",
"avatar_url": "https://avatars.githubusercontent.com/u/5020707?v=4",
"profile": "https://twitter.com/pierrci",
"contributions": [
"doc",
"code"
]
},
{
"login": "xlith",
"name": "Civan Yavuzşen",
"avatar_url": "https://avatars.githubusercontent.com/u/510560?v=4",
"profile": "https://github.com/xlith",
"contributions": [
"code"
]
},
{
"login": "timgates42",
"name": "Tim Gates",
"avatar_url": "https://avatars.githubusercontent.com/u/47873678?v=4",
"profile": "https://github.com/timgates42",
"contributions": [
"doc"
]
},
{
"login": "campersau",
"name": "campersau",
"avatar_url": "https://avatars.githubusercontent.com/u/4009570?v=4",
"profile": "https://github.com/campersau",
"contributions": [
"code"
]
},
{
"login": "dependabot-preview[bot]",
"name": "dependabot-preview[bot]",
"avatar_url": "https://avatars.githubusercontent.com/in/2141?v=4",
"profile": "https://github.com/apps/dependabot-preview",
"contributions": [
"code"
]
}
],
"contributorsPerLine": 7
}

View file

@ -1,16 +0,0 @@
# This file is for unifying the coding style for different editors and IDEs
# editorconfig.org
root = true
[*]
end_of_line = lf
charset = utf-8
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
indent_size = 2
max_line_length = 120
[Makefile]
indent_style = tab

View file

@ -1,44 +0,0 @@
### Step -1: Before filling an issue check out troubleshooting section
- 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: **\_**
### Step 1: Describe the problem:
#### Steps to reproduce:
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 @@@
return (a_date > b_date) ? -1 : (a_date == b_date) ? 0 : 1;
}
```
#### Observed Results:
- What happened? This could be a description, log output, etc.
#### Expected Results:
- What did you expect to happen?
#### Relevant Code:
```
// TODO(you): code here to reproduce the problem
```

View file

@ -1,23 +0,0 @@
---
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:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior** A clear and concise description of what you expected to happen.
**Screenshots** If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. 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,12 +0,0 @@
---
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 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.

View file

@ -1,12 +0,0 @@
name: ci
on:
pull_request:
branches: [master]
jobs:
test-and-publish:
uses: ./.github/workflows/test-and-publish.yml
with:
environment: dev
secrets: inherit

View file

@ -1,35 +0,0 @@
name: release
on:
push:
branches: [master]
jobs:
test-and-publish:
uses: ./.github/workflows/test-and-publish.yml
with:
environment: production
secrets: inherit
publish-website:
runs-on: ubuntu-latest
container:
image: amazon/aws-cli
needs: [test-and-publish]
environment: 'production'
steps:
- name: Download docs
uses: actions/download-artifact@v4
with:
name: docs
path: docs/
- name: Publish to S3
working-directory: docs
env:
AWS_CF_DISTRIBUTION_ID: ${{ secrets.AWS_CF_DISTRIBUTION_ID }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }}
run: |
aws s3 sync --region eu-west-1 --delete . s3://diff2html.xyz --metadata-directive REPLACE --cache-control max-age=31557600
aws cloudfront create-invalidation --region eu-west-1 --distribution-id $AWS_CF_DISTRIBUTION_ID --paths /index.html /demo.html /sitemap.xml /robots.txt

View file

@ -1,148 +0,0 @@
name: test-and-publish
on:
workflow_call:
inputs:
environment:
required: true
type: string
jobs:
version:
runs-on: ubuntu-latest
container:
image: codacy/git-version
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Fix tar access
run: apk add --update --no-progress tar
- name: Fix git access
run: |
git config --global --add safe.directory /__w/diff2html/diff2html
- name: Get next version
run: |
export NEXT_VERSION="$(/bin/git-version --folder=$PWD --release-branch=master)"
echo "Next version is ${NEXT_VERSION}"
echo "${NEXT_VERSION}" > next-version.txt
echo "version=${NEXT_VERSION}" >> $GITHUB_ENV
- name: Get next npm tag name
run: |
if [ "${GITHUB_REF_NAME}" = "master" ]; then
export PUBLISH_TAG="latest"
elif [ "${GITHUB_REF_NAME}" = "next" ]; then
export PUBLISH_TAG="next"
else
export PUBLISH_TAG="pr"
fi
echo "Next tag is ${PUBLISH_TAG}"
echo "${PUBLISH_TAG}" > publish-tag.txt
- name: Upload versions
uses: actions/upload-artifact@v4
with:
name: versions
if-no-files-found: error
path: |
next-version.txt
publish-tag.txt
build:
runs-on: ubuntu-latest
needs: [version]
strategy:
matrix:
node-version: [16.x, 18.x, 20.x, 22.x]
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- name: Log environment setup
run: |
node -v
npm -v
- name: Install dependencies
run: npm install
- name: Build templates
run: npm run build:templates
- name: Build library
run: npm run build
publish:
runs-on: ubuntu-latest
needs: [build]
environment: ${{ inputs.environment }}
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Download versions
uses: actions/download-artifact@v4
with:
name: versions
- name: Store version
run: echo "version=$(cat next-version.txt)" >> $GITHUB_ENV
- name: Configure Git
run: |
git config user.email "gh-actions@users.noreply.github.com"
git config user.name "GitHub Actions"
- name: Tag commit
uses: tvdias/github-tagger@v0.0.1
with:
repo-token: '${{ secrets.GITHUB_TOKEN }}'
tag: '${{ env.version }}'
- name: Install dependencies
run: npm install
- uses: actions/setup-node@v3
with:
registry-url: 'https://registry.npmjs.org'
node-version: '22.x'
- name: Configure NPM version
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: |
rm -f .npmrc
touch .npmrc
echo "//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}" >> .npmrc
echo "registry=https://registry.npmjs.org/" >> .npmrc
echo "access=public" >> .npmrc
echo "save-exact=true" >> .npmrc
- name: Version package
run: |
# Update version in packages to publish
npm version $(cat next-version.txt) -m "Release version %s"
- name: Publish to NPM
run: npm publish --tag $(cat publish-tag.txt)
- uses: actions/setup-node@v3
with:
node-version: '22.x'
registry-url: 'https://npm.pkg.github.com'
- name: Configure NPM version
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
rm -f .npmrc
touch .npmrc
echo "//npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}" >> .npmrc
echo "@rtfpessoa:registry=https://npm.pkg.github.com/" >> .npmrc
echo "access=public" >> .npmrc
echo "save-exact=true" >> .npmrc
- name: Publish to GPR
run: |
# HACK: Override npm package name to be able to publish in GitHub
sed -i 's/^ "name":.*/ "name": "@rtfpessoa\/diff2html",/g' package.json
echo "Going to publish version $(cat next-version.txt) to GitHub"
npm publish --tag $(cat publish-tag.txt)
# HACK: Restore npm package name
sed -i 's/^ "name":.*/ "name": "diff2html",/g' package.json
- name: Upload docs
uses: actions/upload-artifact@v4
with:
name: docs
if-no-files-found: error
path: docs/

30
.gitignore vendored
View file

@ -14,33 +14,3 @@
# Maven
log/
target/
# Node
node_modules/
npm-debug.log
yarn-error.log
# Coverage
coverage/
# Bower
bower_components/
# Terraform
/terraform/.terraform
# Compiled templates
/src/diff2html-templates.*
# Compiled website
/docs/
# Bundles temporary typescript files compiled by webpack
/bundles-out/
# Web bundles
/bundles/
# CommonJS library
/lib/
# ESNext library
/lib-esm/

1
.husky/.gitignore vendored
View file

@ -1 +0,0 @@
_

View file

@ -1,3 +0,0 @@
#!/bin/sh
npm run lint:staged

View file

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

View file

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

1
CNAME
View file

@ -1 +0,0 @@
diff2html.xyz

View file

@ -1,61 +0,0 @@
# Contributor Covenant Code of Conduct
## 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.
## Our Standards
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
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
## 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 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.
## 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.
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
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq

View file

@ -1,61 +0,0 @@
## How to contribute to diff2html
### 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.
- 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 for a feature, be sure to run tests with `npm run test`.
- Use the same coding style as the rest of the codebase, most of the check can be performed with `npm run run lint`.
- 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.
- 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:
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.
A good commit log can look something like this:
```
subsystem: explaining the commit in one line
Body of commit message is a few lines of text, explaining things
in more detail, possibly giving some background about the issue
being fixed, etc. etc.
The body of the commit message can be several paragraphs, and
please do proper word-wrap and keep columns shorter than about
72 characters or so. That way `git log` will show things
nicely even when it is indented.
```
### Developer's Certificate of Origin 1.0
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.

View file

@ -1,26 +0,0 @@
# Credits
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
---
Rodrigo Fernandes, [@rtfpessoa](https://github.com/rtfpessoa)
JK Kim, [@codingtwinky](https://github.com/codingtwinky)
Paulo Bu, [@pbu88](https://github.com/pbu88)
Nuno Teixeira, [@nmatpt](https://github.com/nmatpt)
Mikko Rantanen, [@Rantanen](https://github.com/Rantanen)
Wolfgang Illmeyer, [@escitalopram](https://github.com/escitalopram)
Jameskmonger, [@Jameskmonger](https://github.com/Jameskmonger)
Rafael Cortês, [@mrfyda](https://github.com/mrfyda)
Ivan Vorontsov, [@lantian](https://github.com/lantian)

20
LICENSE Normal file
View file

@ -0,0 +1,20 @@
Copyright 2014 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:
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.

View file

@ -1,14 +0,0 @@
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:
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.

606
README.md
View file

@ -1,613 +1,49 @@
# diff2html
# Diff to Html by [rtfpessoa](https://github.com/rtfpessoa)
[![npm](https://img.shields.io/npm/v/diff2html)](https://www.npmjs.com/package/diff2html)
[![node](https://img.shields.io/node/v/diff2html)](https://www.npmjs.com/package/diff2html)
[![npm](https://img.shields.io/npm/l/diff2html)](https://www.npmjs.com/package/diff2html)
[![GitHub Actions](https://github.com/rtfpessoa/diff2html/actions/workflows/release.yml/badge.svg)](https://github.com/rtfpessoa/diff2html/actions/workflows/release.yml)
[![npm weekly downloads](https://img.shields.io/npm/dw/diff2html)](https://www.npmjs.com/package/diff2html)
[![npm monthly downloads](https://img.shields.io/npm/dm/diff2html)](https://www.npmjs.com/package/diff2html)
[![npm yearly downloads](https://img.shields.io/npm/dy/diff2html)](https://www.npmjs.com/package/diff2html)
[![npm downloads](https://img.shields.io/npm/dt/diff2html)](https://www.npmjs.com/package/diff2html)
[![jsdelivr CDN Downloads](https://data.jsdelivr.com/v1/package/npm/diff2html/badge)](https://www.jsdelivr.com/package/npm/diff2html)
[![All Contributors](https://img.shields.io/badge/all_contributors-22-orange)](#contributors)
diff2html generates pretty HTML diffs from git diff or unified diff output.
## Table of Contents
<!-- toc -->
- [Features](#features)
- [Online Example](#online-example)
- [Distributions](#distributions)
- [Usage](#usage)
- [Diff Text Input](#diff-text-input)
- [Diff2HtmlUI Usage](#diff2htmlui-usage)
- [Diff2HtmlUI API](#diff2htmlui-api)
- [Diff2HtmlUI Configuration](#diff2htmlui-configuration)
- [Diff2HtmlUI Browser](#diff2htmlui-browser)
- [Diff2HtmlUI Examples](#diff2htmlui-examples)
- [Diff2Html Usage](#diff2html-usage)
- [Diff2Html API](#diff2html-api)
- [Diff2Html Configuration](#diff2html-configuration)
- [Diff2Html Browser](#diff2html-browser)
- [Diff2Html NPM / Node.js Library](#diff2html-npm--nodejs-library)
- [Diff2Html Examples](#diff2html-examples)
- [Troubleshooting](#troubleshooting)
- [1. Out of memory or Slow execution](#1-out-of-memory-or-slow-execution)
- [Contribute](#contribute)
- [Contributors](#contributors)
- [License](#license)
- [Thanks](#thanks)
<!-- tocstop -->
Diff to Html generates pretty HTML diffs from git word diff output.
## Features
- Supports git and unified diffs
* line-by-line diff (not side-by-side)
- Line by line and Side by side diff
* char-by-char highlight
- New and old line numbers
* new and old line numbers
- Inserted and removed lines
* inserted and removed lines
- GitHub like visual style
* GitHub like style
- Code syntax highlight
## Setup
- Line similarity matching
Import the diff2html.js in your html.
- Easy code selection
> Pretty Html From Git Word Diff Output
## Online Example
Diff2Html.getPrettyHtmlFromDiff(exInput);
> Go to [diff2html](https://diff2html.xyz/demo.html)
> Intermediate Json From Git Word Diff Output
## Distributions
Diff2Html.getJsonFromDiff(exInput);
- [jsdelivr CDN](https://www.jsdelivr.com/package/npm/diff2html)
- [WebJar](http://www.webjars.org/)
- [Node Library](https://www.npmjs.org/package/diff2html)
- [NPM CLI](https://www.npmjs.org/package/diff2html-cli)
- Manually use from jsdelivr or build the project:
- Browser / Bundle
- Parser and HTML Generator
- [bundles/js/diff2html.min.js](https://cdn.jsdelivr.net/npm/diff2html/bundles/js/diff2html.min.js) - 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](https://cdn.jsdelivr.net/npm/diff2html/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](https://cdn.jsdelivr.net/npm/diff2html/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](https://cdn.jsdelivr.net/npm/diff2html/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](https://cdn.jsdelivr.net/npm/diff2html/lib/diff2html.js) - includes the diff parser and html
generator
- [lib/ui/js/diff2html-ui.js](https://cdn.jsdelivr.net/npm/diff2html/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](https://cdn.jsdelivr.net/npm/diff2html/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](https://cdn.jsdelivr.net/npm/diff2html/lib/ui/js/diff2html-ui-base.js)
- ES6
- [lib-esm/diff2html.js](https://cdn.jsdelivr.net/npm/diff2html/lib-esm/diff2html.js) - includes the diff parser
and html generator
- [lib/ui/js/diff2html-ui.js](https://cdn.jsdelivr.net/npm/diff2html/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](https://cdn.jsdelivr.net/npm/diff2html/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](https://cdn.jsdelivr.net/npm/diff2html/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
> Pretty Html From Json
## Usage
Diff2Html.getPrettyHtmlFromJson(exInput);
Diff2Html can be used in various ways as listed in the [distributions](#distributions) section. The two main ways are:
> Check out the `template.html` for a complete example.
- [Diff2HtmlUI](#diff2htmlui-usage): using this wrapper makes it easy to inject the html in the DOM and adds some nice
features to the diff, like syntax highlight.
- [Diff2Html](#diff2html-usage): using the parser and html generator directly from the library gives you complete
control about what you can do with the json or html generated.
## Contributions
Below you can find more details and examples about each option.
All the contributions are welcome.
## Diff Text Input
diff2html accepts the text contents of a
[unified diff](https://www.gnu.org/software/diffutils/manual/html_node/Unified-Format.html) or the superset format git
diff (https://git-scm.com/docs/git-diff) (not combined or word diff). To provide multiples files as input just
concatenate the diffs (just like the output of git diff).
## Diff2HtmlUI Usage
> Simple wrapper to ease simple tasks in the browser such as: code highlight and js effects
- Invoke Diff2html
- Inject output in DOM element
- Enable collapsible file summary list
- Enable syntax highlight of the code in the diffs
### Diff2HtmlUI API
> Create a Diff2HtmlUI instance
```ts
constructor(target: HTMLElement, diffInput?: string | DiffFile[]) // diff2html-ui, diff2html-ui-slim
constructor(target: HTMLElement, diffInput?: string | DiffFile[], 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
stickyFileHeaders(): void
```
### 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`
- `fileContentToggle`: allow each file contents to be toggled: `true` or `false`, default is `true`
- `stickyFileHeaders`: make file headers sticky: `true` or `false`, default is `true`
- [All the options](#diff2html-configuration) from Diff2Html are also valid configurations in Diff2HtmlUI
### Diff2HtmlUI Browser
#### Mandatory HTML resource imports
```html
<!-- CSS -->
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/diff2html/bundles/css/diff2html.min.css" />
<!-- Javascripts -->
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/diff2html/bundles/js/diff2html-ui.min.js"></script>
```
#### Init
```js
const targetElement = document.getElementById('destination-elem-id');
const configuration = { drawFileList: true, matching: 'lines' };
const diff2htmlUi = new Diff2HtmlUI(targetElement, diffString, configuration);
// or
const diff2htmlUi = new Diff2HtmlUI(targetElement, diffJson, configuration);
```
#### Draw
```js
diff2htmlUi.draw();
```
#### Syntax Highlight
**NOTE:** The highlight.js css should come before the diff2html css
```html
<!-- Stylesheet -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/styles/github.min.css" />
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/diff2html/bundles/css/diff2html.min.css" />
<!-- Javascripts -->
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/diff2html/bundles/js/diff2html-ui.min.js"></script>
```
> Pass the option `highlight` with value true or invoke `diff2htmlUi.highlightCode()` after `diff2htmlUi.draw()`.
```js
document.addEventListener('DOMContentLoaded', () => {
const diffString = `diff --git a/sample.js b/sample.js
index 0000001..0ddf2ba
--- a/sample.js
+++ b/sample.js
@@ -1 +1 @@
-console.log("Hello World!")
+console.log("Hello from Diff2Html!")`;
const targetElement = document.getElementById('myDiffElement');
const configuration = { drawFileList: true, matching: 'lines', highlight: true };
const diff2htmlUi = new Diff2HtmlUI(targetElement, diffString, configuration);
diff2htmlUi.draw();
diff2htmlUi.highlightCode();
});
```
When using the `auto` color scheme, you will need to specify both the light and dark themes for highlight.js to use.
```html
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/styles/github.min.css"
media="screen and (prefers-color-scheme: light)"
/>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/styles/github-dark.min.css"
media="screen and (prefers-color-scheme: dark)"
/>
```
#### Collapsable File Summary List
> Add the dependencies.
```html
<!-- Javascripts -->
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/diff2html/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()`.
```js
document.addEventListener('DOMContentLoaded', () => {
const targetElement = document.getElementById('myDiffElement');
var diff2htmlUi = new Diff2HtmlUI(targetElement, lineDiffExample, { drawFileList: true, matching: 'lines' });
diff2htmlUi.draw();
diff2htmlUi.fileListToggle(false);
});
```
### Diff2HtmlUI Examples
#### Example with plain HTML+CSS+JS
```html
<!doctype html>
<html lang="en-us">
<head>
<meta charset="utf-8" />
<!-- Make sure to load the highlight.js CSS file before the Diff2Html CSS file -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.7.1/styles/github.min.css" />
<link
rel="stylesheet"
type="text/css"
href="https://cdn.jsdelivr.net/npm/diff2html/bundles/css/diff2html.min.css"
/>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/diff2html/bundles/js/diff2html-ui.min.js"></script>
</head>
<script>
const diffString = `diff --git a/sample.js b/sample.js
index 0000001..0ddf2ba
--- a/sample.js
+++ b/sample.js
@@ -1 +1 @@
-console.log("Hello World!")
+console.log("Hello from Diff2Html!")`;
document.addEventListener('DOMContentLoaded', function () {
var targetElement = document.getElementById('myDiffElement');
var configuration = {
drawFileList: true,
fileListToggle: false,
fileListStartVisible: false,
fileContentToggle: false,
matching: 'lines',
outputFormat: 'side-by-side',
synchronisedScroll: true,
highlight: true,
renderNothingWhenEmpty: false,
};
var diff2htmlUi = new Diff2HtmlUI(targetElement, diffString, configuration);
diff2htmlUi.draw();
diff2htmlUi.highlightCode();
});
</script>
<body>
<div id="myDiffElement"></div>
</body>
</html>
```
#### StimulusJS with TypeScript
```ts
import { Controller } from '@hotwired/stimulus';
import { Diff2HtmlUI, Diff2HtmlUIConfig } from 'diff2html/lib/ui/js/diff2html-ui-slim.js';
// Requires `npm install highlight.js`
import 'highlight.js/styles/github.css';
import 'diff2html/bundles/css/diff2html.min.css';
export default class extends Controller {
connect(): void {
const diff2htmlUi = new Diff2HtmlUI(this.diffElement, this.unifiedDiff, this.diffConfiguration);
diff2htmlUi.draw();
}
get unifiedDiff(): string {
return this.data.get('unifiedDiff') || '';
}
get diffElement(): HTMLElement {
return this.element as HTMLElement;
}
get diffConfiguration(): Diff2HtmlUIConfig {
return {
drawFileList: true,
matching: 'lines',
};
}
}
```
## Diff2Html Usage
### Diff2Html API
> JSON representation of the diff
```ts
function parse(diffInput: string, configuration: Diff2HtmlConfig = {}): DiffFile[];
```
> Pretty HTML representation of the diff
```ts
function html(diffInput: string | DiffFile[], configuration: Diff2HtmlConfig = {}): string;
```
### 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 `true`
- `srcPrefix`: add a prefix to all source (before changes) filepaths, default is `''`. Should match the prefix used when
[generating the diff](https://git-scm.com/docs/git-diff#Documentation/git-diff.txt---src-prefixltprefixgt).
- `dstPrefix`: add a prefix to all destination (after changes) filepaths, default is `''`. Should match the prefix used
when [generating the diff](https://git-scm.com/docs/git-diff#Documentation/git-diff.txt---dst-prefixltprefixgt)
- `diffMaxChanges`: number of changed lines after which a file diff is deemed as too big and not displayed, default is
`undefined`
- `diffMaxLineLength`: number of characters in a diff line after which a file diff is deemed as too big and not
displayed, default is `undefined`
- `diffTooBigMessage`: function allowing to customize the message in case of file diff too big (if `diffMaxChanges` or
`diffMaxLineLength` is set). Will be given a file index as a number and should return a string.
- `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`
- `maxLineLengthHighlight`: only perform diff changes highlight if lines are smaller than this, default is `10000`
- `diffStyle`: show differences level in each line: `'word'` or `'char'`, default is `'word'`
- `renderNothingWhenEmpty`: render nothing if the diff shows no change in its comparison: `true` or `false`, default is
`false`
- `matchingMaxComparisons`: perform at most this much comparisons for line matching a block of changes, default is
`2500`
- `maxLineSizeInBlockForComparison`: maximum number of characters of the bigger line in a block to apply comparison,
default is `200`
- `compiledTemplates`: object ([Hogan.js](https://github.com/twitter/hogan.js/) template values) with previously
compiled templates to replace parts of the html, default is `{}`. For example:
`{ "tag-file-changed": Hogan.compile("<span class="d2h-tag d2h-changed d2h-changed-tag">MODIFIED</span>") }`
- `rawTemplates`: object (string values) with raw not compiled templates to replace parts of the html, default is `{}`.
For example: `{ "tag-file-changed": "<span class="d2h-tag d2h-changed d2h-changed-tag">MODIFIED</span>" }`
> For more information regarding the possible templates look into
> [src/templates](https://github.com/rtfpessoa/diff2html/tree/master/src/templates)
- `highlightLanguages`: Map of extension to language name, used for highlighting. This overrides the default language
detection based on file extensions.
- `colorScheme`: color scheme to use for the diff, default is `light`. Possible values are `light`, `dark`, and `auto`
which will use the browser's preferred color scheme.
### Diff2Html Browser
Import the stylesheet and the library code.
To load correctly in the Browser you need to include the stylesheet in the final HTML.
```html
<!-- CSS -->
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/diff2html/bundles/css/diff2html.min.css" />
<!-- Javascripts -->
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/diff2html/bundles/js/diff2html.min.js"></script>
```
It will now be available as a global variable named `Diff2Html`.
```js
document.addEventListener('DOMContentLoaded', () => {
var diffHtml = Diff2Html.html('<Unified Diff String>', {
drawFileList: true,
matching: 'lines',
outputFormat: 'side-by-side',
});
document.getElementById('destination-elem-id').innerHTML = diffHtml;
});
```
### Diff2Html NPM / Node.js Library
```js
const Diff2html = require('diff2html');
const diffJson = Diff2html.parse('<Unified Diff String>');
const diffHtml = Diff2html.html(diffJson, { drawFileList: true });
console.log(diffHtml);
```
### Diff2Html Examples
#### Example with Angular
- Typescript
```typescript
import * as Diff2Html from 'diff2html';
import { Component, OnInit } from '@angular/core';
export class AppDiffComponent implements OnInit {
outputHtml: string;
constructor() {
this.init();
}
ngOnInit() {}
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' });
this.outputHtml = outputHtml;
}
}
```
- HTML
```html
<!doctype html>
<html>
<head>
<title>diff2html</title>
</head>
<body>
<div [innerHtml]="outputHtml"></div>
</body>
</html>
```
- `.angular-cli.json` - Add styles
```json
"styles": [
"diff2html.min.css"
]
```
#### Example with Vue.js
```vue
<template>
<div v-html="prettyHtml" />
</template>
<script>
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',
};
},
computed: {
prettyHtml() {
return Diff2Html.html(this.diffs, {
drawFileList: true,
matching: 'lines',
outputFormat: 'side-by-side',
});
},
},
};
</script>
```
## Troubleshooting
### 1. Out of memory or Slow execution
#### Causes:
- Big files
- Big lines
#### Fix:
- Disable the line matching algorithm, by setting the option `{"matching": "none"}` when invoking diff2html
## Contribute
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
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<table>
<tr>
<td align="center"><a href="https://rtfpessoa.xyz"><img src="https://avatars0.githubusercontent.com/u/902384?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Rodrigo Fernandes</b></sub></a><br /><a href="https://github.com/rtfpessoa/diff2html/commits?author=rtfpessoa" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/stockmind"><img src="https://avatars3.githubusercontent.com/u/5653847?v=4?s=100" width="100px;" alt=""/><br /><sub><b>stockmind</b></sub></a><br /><a href="https://github.com/rtfpessoa/diff2html/commits?author=stockmind" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/lantian"><img src="https://avatars3.githubusercontent.com/u/535545?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ivan Vorontsov</b></sub></a><br /><a href="https://github.com/rtfpessoa/diff2html/commits?author=lantian" title="Code">💻</a></td>
<td align="center"><a href="http://www.nick-brewer.com"><img src="https://avatars1.githubusercontent.com/u/129300?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Nick Brewer</b></sub></a><br /><a href="https://github.com/rtfpessoa/diff2html/commits?author=brewern" title="Code">💻</a></td>
<td align="center"><a href="http://heyitsmattwade.com"><img src="https://avatars0.githubusercontent.com/u/8504000?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Matt Wade</b></sub></a><br /><a href="https://github.com/rtfpessoa/diff2html/issues?q=author%3Aromellem" title="Bug reports">🐛</a> <a href="https://github.com/rtfpessoa/diff2html/commits?author=romellem" title="Code">💻</a></td>
<td align="center"><a href="http://mrfyda.github.io"><img src="https://avatars1.githubusercontent.com/u/593860?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Rafael Cortês</b></sub></a><br /><a href="https://github.com/rtfpessoa/diff2html/commits?author=mrfyda" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/nmatpt"><img src="https://avatars2.githubusercontent.com/u/5034733?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Nuno Teixeira</b></sub></a><br /><a href="https://github.com/rtfpessoa/diff2html/commits?author=nmatpt" title="Code">💻</a></td>
</tr>
<tr>
<td align="center"><a href="https://saino.me/"><img src="https://avatars0.githubusercontent.com/u/1567423?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Koki Oyatsu</b></sub></a><br /><a href="https://github.com/rtfpessoa/diff2html/issues?q=author%3Akaishuu0123" title="Bug reports">🐛</a> <a href="https://github.com/rtfpessoa/diff2html/commits?author=kaishuu0123" title="Code">💻</a></td>
<td align="center"><a href="http://www.jamesmonger.com"><img src="https://avatars2.githubusercontent.com/u/2037007?v=4?s=100" width="100px;" alt=""/><br /><sub><b>James Monger</b></sub></a><br /><a href="https://github.com/rtfpessoa/diff2html/commits?author=Jameskmonger" title="Documentation">📖</a></td>
<td align="center"><a href="http://wesssel.github.io/"><img src="https://avatars2.githubusercontent.com/u/7767299?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Wessel van der Pal</b></sub></a><br /><a href="#security-wesssel" title="Security">🛡️</a> <a href="https://github.com/rtfpessoa/diff2html/commits?author=wesssel" title="Code">💻</a></td>
<td align="center"><a href="https://jung-kim.github.io"><img src="https://avatars2.githubusercontent.com/u/5281068?v=4?s=100" width="100px;" alt=""/><br /><sub><b>jk-kim</b></sub></a><br /><a href="https://github.com/rtfpessoa/diff2html/commits?author=jung-kim" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/sss0791"><img src="https://avatars1.githubusercontent.com/u/1446970?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Sergey Semenov</b></sub></a><br /><a href="https://github.com/rtfpessoa/diff2html/issues?q=author%3Asss0791" title="Bug reports">🐛</a> <a href="https://github.com/rtfpessoa/diff2html/commits?author=sss0791" title="Code">💻</a></td>
<td align="center"><a href="http://researcher.watson.ibm.com/researcher/view.php?person=us-nickm"><img src="https://avatars3.githubusercontent.com/u/4741620?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Nick Mitchell</b></sub></a><br /><a href="https://github.com/rtfpessoa/diff2html/issues?q=author%3Astarpit" title="Bug reports">🐛</a> <a href="https://github.com/rtfpessoa/diff2html/commits?author=starpit" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/samiraguiar"><img src="https://avatars0.githubusercontent.com/u/13439135?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Samir Aguiar</b></sub></a><br /><a href="https://github.com/rtfpessoa/diff2html/commits?author=samiraguiar" title="Documentation">📖</a></td>
</tr>
<tr>
<td align="center"><a href="https://twitter.com/pubkeypubkey"><img src="https://avatars3.githubusercontent.com/u/8926560?v=4?s=100" width="100px;" alt=""/><br /><sub><b>pubkey</b></sub></a><br /><a href="https://github.com/rtfpessoa/diff2html/commits?author=pubkey" title="Documentation">📖</a> <a href="https://github.com/rtfpessoa/diff2html/commits?author=pubkey" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/iliyaZelenko"><img src="https://avatars1.githubusercontent.com/u/13103045?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Илья</b></sub></a><br /><a href="https://github.com/rtfpessoa/diff2html/commits?author=iliyaZelenko" title="Documentation">📖</a></td>
<td align="center"><a href="https://akr.am"><img src="https://avatars0.githubusercontent.com/u/1823771?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Mohamed Akram</b></sub></a><br /><a href="https://github.com/rtfpessoa/diff2html/issues?q=author%3Amohd-akram" title="Bug reports">🐛</a> <a href="https://github.com/rtfpessoa/diff2html/commits?author=mohd-akram" title="Documentation">📖</a> <a href="https://github.com/rtfpessoa/diff2html/commits?author=mohd-akram" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/emarcotte"><img src="https://avatars0.githubusercontent.com/u/249390?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Eugene Marcotte</b></sub></a><br /><a href="https://github.com/rtfpessoa/diff2html/commits?author=emarcotte" title="Code">💻</a></td>
<td align="center"><a href="http://twitter.com/dimasabanin"><img src="https://avatars0.githubusercontent.com/u/8316?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Dima Sabanin</b></sub></a><br /><a href="#maintenance-dsabanin" title="Maintenance">🚧</a> <a href="https://github.com/rtfpessoa/diff2html/commits?author=dsabanin" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/benabbottnz"><img src="https://avatars2.githubusercontent.com/u/2616473?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ben Abbott</b></sub></a><br /><a href="https://github.com/rtfpessoa/diff2html/commits?author=benabbottnz" title="Documentation">📖</a></td>
<td align="center"><a href="http://webminer.js.org"><img src="https://avatars1.githubusercontent.com/u/2196373?v=4?s=100" width="100px;" alt=""/><br /><sub><b>弘树@阿里</b></sub></a><br /><a href="https://github.com/rtfpessoa/diff2html/issues?q=author%3Adickeylth" title="Bug reports">🐛</a> <a href="https://github.com/rtfpessoa/diff2html/commits?author=dickeylth" title="Documentation">📖</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/Rantanen"><img src="https://avatars0.githubusercontent.com/u/385385?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Mikko Rantanen</b></sub></a><br /><a href="https://github.com/rtfpessoa/diff2html/issues?q=author%3ARantanen" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/extend1994"><img src="https://avatars2.githubusercontent.com/u/13430892?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ann</b></sub></a><br /><a href="https://github.com/rtfpessoa/diff2html/commits?author=extend1994" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/escitalopram"><img src="https://avatars0.githubusercontent.com/u/1155220?v=4?s=100" width="100px;" alt=""/><br /><sub><b>escitalopram</b></sub></a><br /><a href="https://github.com/rtfpessoa/diff2html/commits?author=escitalopram" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/apps/dependabot"><img src="https://avatars0.githubusercontent.com/in/29110?v=4?s=100" width="100px;" alt=""/><br /><sub><b>dependabot[bot]</b></sub></a><br /><a href="#security-dependabot[bot]" title="Security">🛡️</a> <a href="#maintenance-dependabot[bot]" title="Maintenance">🚧</a></td>
<td align="center"><a href="http://www.joshuakgoldberg.com"><img src="https://avatars1.githubusercontent.com/u/3335181?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Josh Goldberg</b></sub></a><br /><a href="https://github.com/rtfpessoa/diff2html/commits?author=JoshuaKGoldberg" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/apeckham"><img src="https://avatars.githubusercontent.com/u/14110?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Aaron</b></sub></a><br /><a href="https://github.com/rtfpessoa/diff2html/commits?author=apeckham" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/pgrimaud"><img src="https://avatars.githubusercontent.com/u/1866496?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Pierre Grimaud</b></sub></a><br /><a href="https://github.com/rtfpessoa/diff2html/commits?author=pgrimaud" title="Documentation">📖</a></td>
</tr>
<tr>
<td align="center"><a href="https://domdomegg.github.io/"><img src="https://avatars.githubusercontent.com/u/4953590?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Adam Jones</b></sub></a><br /><a href="https://github.com/rtfpessoa/diff2html/commits?author=domdomegg" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/charguer"><img src="https://avatars.githubusercontent.com/u/1830652?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Arthur Charguéraud</b></sub></a><br /><a href="https://github.com/rtfpessoa/diff2html/commits?author=charguer" title="Documentation">📖</a></td>
<td align="center"><a href="https://twitter.com/pierrci"><img src="https://avatars.githubusercontent.com/u/5020707?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Pierric Cistac</b></sub></a><br /><a href="https://github.com/rtfpessoa/diff2html/commits?author=Pierrci" title="Documentation">📖</a> <a href="https://github.com/rtfpessoa/diff2html/commits?author=Pierrci" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/xlith"><img src="https://avatars.githubusercontent.com/u/510560?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Civan Yavuzşen</b></sub></a><br /><a href="https://github.com/rtfpessoa/diff2html/commits?author=xlith" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/timgates42"><img src="https://avatars.githubusercontent.com/u/47873678?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Tim Gates</b></sub></a><br /><a href="https://github.com/rtfpessoa/diff2html/commits?author=timgates42" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/campersau"><img src="https://avatars.githubusercontent.com/u/4009570?v=4?s=100" width="100px;" alt=""/><br /><sub><b>campersau</b></sub></a><br /><a href="https://github.com/rtfpessoa/diff2html/commits?author=campersau" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/apps/dependabot-preview"><img src="https://avatars.githubusercontent.com/in/2141?v=4?s=100" width="100px;" alt=""/><br /><sub><b>dependabot-preview[bot]</b></sub></a><br /><a href="https://github.com/rtfpessoa/diff2html/commits?author=dependabot-preview[bot]" title="Code">💻</a></td>
</tr>
</table>
<!-- markdownlint-restore -->
<!-- prettier-ignore-end -->
<!-- ALL-CONTRIBUTORS-LIST:END -->
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification.
Contributions of any kind welcome!
To contribute just send a pull request with your feature,fix,... and it will be reviewed asap.
## License
Copyright 2014-present Rodrigo Fernandes. Released under the terms of the MIT license.
Copyright 2014 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

@ -1,35 +0,0 @@
# Security Policy
## Supported Versions
| Version | Supported |
| ------- | ------------------ |
| 2.9.x | :white_check_mark: |
| < 2.9 | :x: |
## 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.
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.
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:
- 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.

292
diff2html.js Normal file
View file

@ -0,0 +1,292 @@
/*
*
* Diff to HTML (diff2html.js)
* Author: rtfpessoa
* Date: Friday 29 August 2014
* Last Update: Saturday 30 August 2014
*
* Diff command:
* git diff --word-diff-regex=. HEAD~1
*/
(function (window) {
var ClassVariable;
ClassVariable = (function () {
var CSS_STYLES = {
INFO: "info",
CONTEXT: "context",
NEW: "insert",
DELETED: "delete"
};
var BLOCK_HEADER_LINE = "...";
function Diff2Html() {
}
/*
* Generates pretty html from string diff input
*/
Diff2Html.prototype.getPrettyHtmlFromDiff = function (diffInput) {
var diffJson = generateDiffJson(diffInput);
return generateJsonHtml(diffJson);
};
/*
* Generates json object from string diff input
*/
Diff2Html.prototype.getJsonFromDiff = function (diffInput) {
return generateDiffJson(diffInput);
};
/*
* Generates pretty html from a json object
*/
Diff2Html.prototype.getPrettyHtmlFromJson = function (diffJson) {
return generateJsonHtml(diffJson);
};
var generateDiffJson = function (diffInput) {
var files = [],
currentFile = null,
currentBlock = null,
oldLine = null,
newLine = null;
var saveBlock = function () {
/* add previous block(if exists) before start a new file */
if (currentBlock) {
currentFile.blocks.push(currentBlock);
currentBlock = null;
}
};
var saveFile = function () {
/* add previous file(if exists) before start a new one */
if (currentFile) {
files.push(currentFile);
currentFile = null;
}
};
var startFile = function (line) {
saveBlock();
saveFile();
/* create file structure */
currentFile = {};
currentFile.blocks = [];
currentFile.deletedLines = 0;
currentFile.addedLines = 0;
/* save file paths, before and after the diff */
var values = /^diff --git a\/(\S+) b\/(\S+).*$/.exec(line);
currentFile.oldName = values[1];
currentFile.newName = values[2];
};
var startBlock = function (line) {
saveBlock();
var values = /^(@@ -(\d+),(\d+) \+(\d+),(\d+) @@).*/.exec(line);
/* create block metadata */
currentBlock = {};
currentBlock.lines = [];
currentBlock.oldStartLine = oldLine = values[2];
currentBlock.newStartLine = newLine = values[4];
/* create block header line */
var currentLine = {};
currentLine.type = CSS_STYLES.INFO;
currentLine.content = line;
currentLine.oldNumber = BLOCK_HEADER_LINE;
currentLine.newNumber = BLOCK_HEADER_LINE;
/* add line to block */
currentBlock.lines.push(currentLine);
};
var createLine = function (line) {
var isLineWithInserts = /{\+.*?\+}/.exec(line);
var isLineWithDeletes = /\[-.*?-\]/.exec(line);
var isNewLine = /^{\+.*?\+}$/.exec(line);
var isContextLine = !isLineWithInserts && !isLineWithDeletes;
var currentLine = {};
if (isContextLine) {
currentLine = {};
currentLine.type = CSS_STYLES.CONTEXT;
currentLine.oldNumber = oldLine++;
currentLine.newNumber = newLine++;
currentLine.content = line;
currentBlock.lines.push(currentLine);
} else {
if (isLineWithDeletes) {
currentFile.deletedLines++;
currentLine = {};
currentLine.type = CSS_STYLES.DELETED;
currentLine.oldNumber = oldLine++;
currentLine.newNumber = null;
currentLine.content = line;
currentBlock.lines.push(currentLine);
}
if (isLineWithInserts) {
currentFile.addedLines++;
currentLine = {};
currentLine.type = CSS_STYLES.NEW;
currentLine.oldNumber = null;
currentLine.newNumber = newLine++;
currentLine.content = line;
/* fix line numbers when new chars but no deletes and no whole new line */
if (isLineWithInserts && !isLineWithDeletes && !isNewLine) {
currentFile.deletedLines++;
currentLine.oldNumber = oldLine++;
}
currentBlock.lines.push(currentLine);
}
}
};
var diffLines = diffInput.split("\n");
diffLines.forEach(function (line) {
// Unmerged paths, and possibly other non-diffable files
// https://github.com/scottgonzalez/pretty-diff/issues/11
// Also, remove some useless lines
if (!line || startsWith(line, "*") ||
startsWith(line, "new") || startsWith(line, "index") ||
startsWith(line, "---") || startsWith(line, "+++")) {
return;
}
if (startsWith(line, "diff")) {
startFile(line);
} else if (currentFile && startsWith(line, "@@")) {
startBlock(line);
} else if (currentBlock) {
createLine(line);
}
});
saveBlock();
saveFile();
return files;
};
var generateJsonHtml = function (diffFiles) {
return "<div id=\"wrapper\">\n" +
diffFiles.map(function (file) {
return "<div class=\"file-wrapper\">\n" +
" <div class=\"file-header\">\n" +
" <div class=\"file-stats\">\n" +
" <span class=\"lines-added\">+" + file.addedLines + "</span>\n" +
" <span class=\"lines-deleted\">-" + file.deletedLines + "</span>\n" +
" </div>\n" +
" <div class=\"file-name\">" + getDiffName(file.oldName, file.newName) + "</div>\n" +
" </div>\n" +
" <div class=\"file-diff\">\n" +
" <div class=\"code-wrapper\">\n" +
" <table class=\"diff-table\">\n" +
" <tbody>\n" +
" " + generateFileHtml(file) +
" </tbody>\n" +
" </table>\n" +
" </div>\n" +
" </div>\n" +
" </div>\n";
}).join("\n") +
"</div>\n";
};
var getDiffName = function (oldFilename, newFilename) {
return oldFilename === newFilename ? newFilename : oldFilename + " -> " + newFilename;
};
var generateFileHtml = function (file) {
return file.blocks.map(function (block) {
return block.lines.map(function (line) {
var oldLine = valueOrEmpty(line.oldNumber);
var newLine = valueOrEmpty(line.newNumber);
var escapedLine = escape(line.content);
if (line.type === CSS_STYLES.NEW) {
escapedLine = generateLineInsertions(escapedLine);
} else if (line.type === CSS_STYLES.DELETED) {
escapedLine = generateLineDeletions(escapedLine);
}
return "<tr>\n" +
" <td class=\"code-linenumber " + line.type + "\">" + oldLine + "</td>\n" +
" <td class=\"code-linenumber " + line.type + "\">" + newLine + "</td>\n" +
" <td class=\"code-line " + line.type + "\"><pre class=\"" + line.type + "\">" + escapedLine + "</pre></td>\n" +
"</tr>\n";
}).join("\n");
}).join("\n");
};
var generateLineInsertions = function (line) {
return line.replace(/(\[-.*?-\])/g, "").
replace(/({\+(.*?)\+})/g, "<ins>$2</ins>");
};
var generateLineDeletions = function (line) {
return line.replace(/({\+.*?\+})/g, "").
replace(/(\[-(.*?)-\])/g, "<del>$2</del>");
};
/*
* Utils
*/
function escape(str) {
return str
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/\t/g, " ");
}
function startsWith(str, start) {
return str.indexOf(start) === 0;
}
function valueOrEmpty(value) {
return value ? value : "";
}
/* singleton pattern */
var instance;
return {
getInstance: function () {
if (instance === undefined) {
instance = new Diff2Html();
/* Hide the constructor so the returned objected can't be new'd */
instance.constructor = null;
}
return instance;
}
};
})();
window.Diff2Html = ClassVariable.getInstance();
return window.Diff2Html;
})(window);

View file

@ -1,77 +0,0 @@
import globals from 'globals';
import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';
import pluginJest from 'eslint-plugin-jest';
import json from '@eslint/json';
import pluginPromise from 'eslint-plugin-promise';
export default [
{ ...eslint.configs.recommended, files: ['src/**/*.{js,mjs,cjs,ts}'] },
...tseslint.configs.recommended,
// ...tseslint.configs.recommendedTypeChecked,
// ...tseslint.configs.strict,
// ...tseslint.configs.stylistic,
// ...tseslint.configs.strictTypeChecked,
// ...tseslint.configs.stylisticTypeChecked,
{
rules: {
'@typescript-eslint/no-unused-vars': [
'error',
{
args: 'all',
argsIgnorePattern: '^_',
caughtErrors: 'all',
caughtErrorsIgnorePattern: '^_',
destructuredArrayIgnorePattern: '^_',
varsIgnorePattern: '^_',
ignoreRestSiblings: true,
},
],
},
},
pluginPromise.configs['flat/recommended'],
{
plugins: {
json,
},
files: ['**/*.json'],
language: 'json/json',
rules: {
'json/no-duplicate-keys': 'error',
},
},
{
...pluginJest.configs['flat/recommended'],
...pluginJest.configs['flat/style'],
files: ['src/__tests__/**/*tests.ts'],
plugins: { jest: pluginJest },
languageOptions: {
globals: pluginJest.environments.globals.globals,
},
},
{
languageOptions: {
globals: {
...globals.browser,
...globals.node,
...globals.es2025,
Atomics: 'readonly',
SharedArrayBuffer: 'readonly',
document: 'readonly',
navigator: 'readonly',
window: 'readonly',
},
parserOptions: {
project: './tsconfig.eslint.json',
tsconfigRootDir: './',
},
},
},
{
ignores: ['src/diff2html-templates.*', 'coverage/', 'docs/', 'bundles-out/', 'bundles/', 'lib/', 'lib-esm/'],
},
{
...tseslint.configs.disableTypeChecked,
files: ['**/*.{js,mjs,cjs}'],
},
];

View file

@ -1,23 +0,0 @@
module.exports = {
verbose: true,
preset: 'ts-jest',
testEnvironment: 'node',
coverageDirectory: './coverage',
coverageReporters: ['lcov', 'text', 'html', 'json', 'cobertura', 'clover'],
collectCoverageFrom: [
'src/**/*.ts',
'!src/ui/**',
'!src/diff2html-templates.ts',
'!src/__tests__/**',
'!node_modules/**',
],
coverageThreshold: {
global: {
statements: 93,
branches: 86,
functions: 98,
lines: 93,
},
},
prettierPath: require.resolve('prettier-2'),
};

18121
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,160 +0,0 @@
{
"name": "diff2html",
"version": "3.0.0-beta.1",
"homepage": "https://diff2html.xyz",
"description": "Fast Diff to colorized HTML",
"keywords": [
"git",
"diff",
"pretty",
"side",
"line",
"side-by-side",
"line-by-line",
"character",
"highlight",
"pretty",
"color",
"html",
"diff2html",
"difftohtml",
"colorized"
],
"author": {
"name": "Rodrigo Fernandes",
"email": "rtfrodrigo@gmail.com"
},
"repository": {
"type": "git",
"url": "git://github.com/rtfpessoa/diff2html.git"
},
"bugs": {
"url": "https://www.github.com/rtfpessoa/diff2html/issues"
},
"engines": {
"node": ">=12"
},
"scripts": {
"lint:staged": "lint-staged",
"lint:check": "eslint",
"lint:fix": "eslint --fix",
"prettier": "prettier --ignore-path .gitignore '**/*.+(js|jsx|ts|tsx|json|css|html|md|mdx)'",
"format:check": "npm run prettier --check",
"format:fix": "npm run prettier --write",
"build": "npm run build:css && npm run build:templates && npm run build:commonjs && npm run build:esm && npm run build:bundles && npm run build:website",
"build:commonjs": "rm -rf lib; tsc -p tsconfig.json -m CommonJS --outDir lib",
"build:esm": "rm -rf lib-esm; tsc -p tsconfig.json -m ESNext --outDir lib-esm",
"build:bundles": "rm -rf ./bundles/js; webpack --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 --mode production --config webpack.website.ts",
"gen": "npm run gen:toc",
"gen:toc-base": "markdown-toc --maxdepth 3 --bullets='-' -i",
"gen:toc": "npm run gen:toc-base README.md",
"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": "npm run test:coverage && open ./coverage/index.html",
"coverage:push": "curl -Ls https://coverage.codacy.com/get.sh | bash",
"validate": "npm run build:templates && npm run format:check && npm run lint:check && npm run build && npm run test:coverage",
"fix": "npm run format:fix && npm run lint:fix",
"start": "npm run start:website",
"start:website": "webpack serve --mode development --config webpack.website.ts",
"preversion": "npm run validate",
"version": "git add -A package.json",
"prepare": "husky"
},
"main": "./lib/diff2html.js",
"module": "./lib-esm/diff2html.js",
"types": "./lib/diff2html.d.ts",
"lint-staged": {
"**/*.+(js|jsx|ts|tsx|json)": [
"prettier --write",
"eslint --fix"
],
"**/*.+(css|html|md|mdx)": [
"prettier --write"
],
"README.md": [
"npm run gen:toc-base"
]
},
"dependencies": {
"diff": "^7.0.0",
"hogan.js": "3.0.2"
},
"optionalDependencies": {
"highlight.js": "11.9.0"
},
"devDependencies": {
"prettier-2": "npm:prettier@^2",
"@eslint/js": "^9.17.0",
"@eslint/json": "^0.9.0",
"@types/diff": "^6.0.0",
"@types/hogan.js": "3.0.5",
"@types/jest": "^29.5.14",
"@types/node": "^22.10.2",
"@types/nopt": "3.0.32",
"all-contributors-cli": "^6.24.0",
"autoprefixer": "^10.4.20",
"bulma": "^1.0.2",
"clipboard": "2.0.11",
"copy-webpack-plugin": "^12.0.2",
"css-loader": "^7.1.2",
"cssnano": "^7.0.6",
"eslint": "^9.17.0",
"eslint-plugin-jest": "28.10.0",
"eslint-plugin-promise": "^7.2.1",
"file-loader": "6.2.0",
"globals": "^15.14.0",
"handlebars": "4.7.8",
"handlebars-loader": "1.7.3",
"html-webpack-plugin": "^5.6.3",
"husky": "^9.1.7",
"image-webpack-loader": "8.1.0",
"is-ci-cli": "2.2.0",
"jest": "29.7.0",
"lint-staged": "^15.2.11",
"markdown-toc": "^1.2.0",
"mini-css-extract-plugin": "^2.9.2",
"mkdirp": "3.0.1",
"nopt": "^8.0.0",
"postcss": "^8.4.49",
"postcss-cli": "11.0.0",
"postcss-import": "^16.1.0",
"postcss-loader": "^8.1.1",
"postcss-preset-env": "^10.1.2",
"prettier": "^3.4.2",
"ts-jest": "^29.2.5",
"ts-loader": "9.5.1",
"ts-node": "10.9.2",
"typescript": "^5.7.2",
"typescript-eslint": "^8.18.2",
"url-loader": "4.1.1",
"webpack": "^5.97.1",
"webpack-cli": "^6.0.1",
"webpack-dev-server": "^5.2.0",
"whatwg-fetch": "3.6.20"
},
"resolutions": {
"lodash": ">=4.17.20",
"minimist": ">=1.2.5",
"acorn": ">=7.4.0",
"autolinker": ">=3.14.1",
"bl": ">=2.2.1",
"decompress": ">=4.2.1",
"node-forge": ">=0.10.0",
"trim-newlines": ">=3.0.1",
"async": ">=2.6.4",
"terser": ">=5.14.2",
"semver-regex": ">=4.0.5",
"http-cache-semantics": ">=4.1.1"
},
"license": "MIT",
"files": [
"bundles",
"lib",
"lib-esm"
]
}

View file

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

View file

@ -1,188 +0,0 @@
/*
* Copyright 2011 Twitter, Inc.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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';
const options = nopt(
{
namespace: String,
outputdir: path,
variable: String,
wrapper: String,
version: true,
help: true,
},
{
n: ['--namespace'],
o: ['--outputdir'],
vn: ['--variable'],
w: ['--wrapper'],
h: ['--help'],
v: ['--version'],
},
);
const specials = ['/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\'];
const specialsRegExp = new RegExp('(\\' + specials.join('|\\') + ')', 'g');
function escape(text: string): string {
return text.replace(specialsRegExp, '\\$1');
}
function cyan(text: string): string {
return '\x1B[36m' + text + '\x1B[39m';
}
function extractFiles(files: string[]): string[] {
const usage = `${cyan(
'USAGE:',
)} hulk [--wrapper wrapper] [--outputdir outputdir] [--namespace namespace] [--variable variable] FILES
${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('NOTE:')} hulk supports the "*" wildcard and allows you to target specific extensions too
`;
if (options.version) {
// eslint-disable-next-line @typescript-eslint/no-require-imports
console.log(require('../package.json').version);
process.exit(0);
}
if (!files.length || options.help) {
console.log(usage);
process.exit(0);
}
return files
.map((fileGlob: string) => {
if (/\*/.test(fileGlob)) {
const [fileGlobPrefix, fileGlobSuffix] = fileGlob.split('*');
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;
}, []);
} else if (fs.statSync(fileGlob).isFile()) {
return [fileGlob];
} else {
return [];
}
})
.reduce((previous, current) => previous.concat(current), []);
}
// Remove utf-8 byte order mark, http://en.wikipedia.org/wiki/Byte_order_mark
function removeByteOrderMark(text: string): string {
if (text.charCodeAt(0) === 0xfeff) {
return text.substring(1);
}
return text;
}
// Wrap templates
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 objectAccessor = `${objectName}["${name}"]`;
const objectStmt = `${objectAccessor} = ${hoganTemplateString};`;
switch (options.wrapper) {
case 'amd':
return `define(${
!options.outputdir ? `"${path.join(path.dirname(file), name)}", ` : ''
}["hogan.js"], function(Hogan) { return ${hoganTemplateString}; });`;
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':
return `// @ts-ignore\n${objectStmt}`;
default:
return objectStmt;
}
}
function prepareOutput(content: string): string {
const variableName = options.variable || 'templates';
switch (options.wrapper) {
case 'amd':
return content;
case 'node':
return `(function() {
if (!!!global.${variableName}) global.${variableName} = {};
var Hogan = require("hogan.js");
${content}
${!options.outputdir ? `module.exports = global.${variableName};\n` : ''})();`;
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;
}
}
// Write the directory
if (options.outputdir) {
mkderp.sync(options.outputdir);
}
// Prepend namespace to template name
function namespace(name: string): string {
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();
if (!timmedFileContents) return;
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';
return fs.writeFileSync(path.join(options.outputdir, `${name}.${fileExtension}`), prepareOutput(cleanFileContents));
})
.filter(templateContents => typeof templateContents !== 'undefined');
// Output templates
if (!templates.length || options.outputdir) process.exit(0);
console.log(prepareOutput(templates.join('\n')));

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,272 +0,0 @@
import { FileListRenderer } from '../file-list-renderer';
import HoganJsUtils from '../hoganjs-utils';
import { ColorSchemeType } from '../types';
describe('FileListRenderer', () => {
describe('render', () => {
it('should expose old and new files to templates', () => {
const hoganUtils = new HoganJsUtils({
rawTemplates: {
'file-summary-wrapper': '{{{files}}}',
'file-summary-line': '{{oldName}}, {{newName}}, {{fileName}}',
},
});
const fileListRenderer = new FileListRenderer(hoganUtils);
const files = [
{
isCombined: false,
isGitDiff: false,
blocks: [],
addedLines: 12,
deletedLines: 41,
language: 'js',
oldName: 'my/file/name.js',
newName: 'my/file/name.js',
},
{
isCombined: false,
isGitDiff: false,
blocks: [],
addedLines: 12,
deletedLines: 41,
language: 'js',
oldName: 'my/file/name1.js',
newName: 'my/file/name2.js',
},
{
isCombined: false,
isGitDiff: false,
blocks: [],
addedLines: 12,
deletedLines: 0,
language: 'js',
oldName: 'dev/null',
newName: 'my/file/name.js',
isNew: true,
},
{
isCombined: false,
isGitDiff: false,
blocks: [],
addedLines: 0,
deletedLines: 41,
language: 'js',
oldName: 'my/file/name.js',
newName: 'dev/null',
isDeleted: true,
},
];
const fileHtml = fileListRenderer.render(files);
expect(fileHtml).toMatchInlineSnapshot(`
"my/file/name.js, my/file/name.js, my/file/name.js
my/file/name1.js, my/file/name2.js, my/file/{name1.js name2.js}
dev/null, my/file/name.js, my/file/name.js
my/file/name.js, dev/null, my/file/name.js"
`);
});
it('should work for all kinds of files', () => {
const hoganUtils = new HoganJsUtils({});
const fileListRenderer = new FileListRenderer(hoganUtils);
const files = [
{
isCombined: false,
isGitDiff: false,
blocks: [],
addedLines: 12,
deletedLines: 41,
language: 'js',
oldName: 'my/file/name.js',
newName: 'my/file/name.js',
},
{
isCombined: false,
isGitDiff: false,
blocks: [],
addedLines: 12,
deletedLines: 41,
language: 'js',
oldName: 'my/file/name1.js',
newName: 'my/file/name2.js',
},
{
isCombined: false,
isGitDiff: false,
blocks: [],
addedLines: 12,
deletedLines: 0,
language: 'js',
oldName: 'dev/null',
newName: 'my/file/name.js',
isNew: true,
},
{
isCombined: false,
isGitDiff: false,
blocks: [],
addedLines: 0,
deletedLines: 41,
language: 'js',
oldName: 'my/file/name.js',
newName: 'dev/null',
isDeleted: true,
},
];
const fileHtml = fileListRenderer.render(files);
expect(fileHtml).toMatchInlineSnapshot(`
"<div class="d2h-file-list-wrapper d2h-light-color-scheme">
<div class="d2h-file-list-header">
<span class="d2h-file-list-title">Files changed (4)</span>
<a class="d2h-file-switch d2h-hide">hide</a>
<a class="d2h-file-switch d2h-show">show</a>
</div>
<ol class="d2h-file-list">
<li class="d2h-file-list-line">
<span class="d2h-file-name-wrapper">
<svg aria-hidden="true" class="d2h-icon d2h-changed" height="16" title="modified" version="1.1"
viewBox="0 0 14 16" width="14">
<path d="M13 1H1C0.45 1 0 1.45 0 2v12c0 0.55 0.45 1 1 1h12c0.55 0 1-0.45 1-1V2c0-0.55-0.45-1-1-1z m0 13H1V2h12v12zM4 8c0-1.66 1.34-3 3-3s3 1.34 3 3-1.34 3-3 3-3-1.34-3-3z"></path>
</svg> <a href="#d2h-781444" class="d2h-file-name">my/file/name.js</a>
<span class="d2h-file-stats">
<span class="d2h-lines-added">+12</span>
<span class="d2h-lines-deleted">-41</span>
</span>
</span>
</li>
<li class="d2h-file-list-line">
<span class="d2h-file-name-wrapper">
<svg aria-hidden="true" class="d2h-icon d2h-moved" height="16" title="renamed" version="1.1"
viewBox="0 0 14 16" width="14">
<path d="M6 9H3V7h3V4l5 4-5 4V9z m8-7v12c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1V2c0-0.55 0.45-1 1-1h12c0.55 0 1 0.45 1 1z m-1 0H1v12h12V2z"></path>
</svg> <a href="#d2h-662683" class="d2h-file-name">my/file/{name1.js name2.js}</a>
<span class="d2h-file-stats">
<span class="d2h-lines-added">+12</span>
<span class="d2h-lines-deleted">-41</span>
</span>
</span>
</li>
<li class="d2h-file-list-line">
<span class="d2h-file-name-wrapper">
<svg aria-hidden="true" class="d2h-icon d2h-added" height="16" title="added" version="1.1" viewBox="0 0 14 16"
width="14">
<path d="M13 1H1C0.45 1 0 1.45 0 2v12c0 0.55 0.45 1 1 1h12c0.55 0 1-0.45 1-1V2c0-0.55-0.45-1-1-1z m0 13H1V2h12v12zM6 9H3V7h3V4h2v3h3v2H8v3H6V9z"></path>
</svg> <a href="#d2h-781444" class="d2h-file-name">my/file/name.js</a>
<span class="d2h-file-stats">
<span class="d2h-lines-added">+12</span>
<span class="d2h-lines-deleted">-0</span>
</span>
</span>
</li>
<li class="d2h-file-list-line">
<span class="d2h-file-name-wrapper">
<svg aria-hidden="true" class="d2h-icon d2h-deleted" height="16" title="removed" version="1.1"
viewBox="0 0 14 16" width="14">
<path d="M13 1H1C0.45 1 0 1.45 0 2v12c0 0.55 0.45 1 1 1h12c0.55 0 1-0.45 1-1V2c0-0.55-0.45-1-1-1z m0 13H1V2h12v12zM11 9H3V7h8v2z"></path>
</svg> <a href="#d2h-781444" class="d2h-file-name">my/file/name.js</a>
<span class="d2h-file-stats">
<span class="d2h-lines-added">+0</span>
<span class="d2h-lines-deleted">-41</span>
</span>
</span>
</li>
</ol>
</div>"
`);
});
describe('with dark colorScheme', () => {
it('should include dark colorScheme', () => {
const hoganUtils = new HoganJsUtils({});
const fileListRenderer = new FileListRenderer(hoganUtils, {
colorScheme: ColorSchemeType.DARK,
});
const files = [
{
isCombined: false,
isGitDiff: false,
blocks: [],
addedLines: 12,
deletedLines: 41,
language: 'js',
oldName: 'my/file/name.js',
newName: 'my/file/name.js',
},
];
const fileHtml = fileListRenderer.render(files);
expect(fileHtml).toMatchInlineSnapshot(`
"<div class="d2h-file-list-wrapper d2h-dark-color-scheme">
<div class="d2h-file-list-header">
<span class="d2h-file-list-title">Files changed (1)</span>
<a class="d2h-file-switch d2h-hide">hide</a>
<a class="d2h-file-switch d2h-show">show</a>
</div>
<ol class="d2h-file-list">
<li class="d2h-file-list-line">
<span class="d2h-file-name-wrapper">
<svg aria-hidden="true" class="d2h-icon d2h-changed" height="16" title="modified" version="1.1"
viewBox="0 0 14 16" width="14">
<path d="M13 1H1C0.45 1 0 1.45 0 2v12c0 0.55 0.45 1 1 1h12c0.55 0 1-0.45 1-1V2c0-0.55-0.45-1-1-1z m0 13H1V2h12v12zM4 8c0-1.66 1.34-3 3-3s3 1.34 3 3-1.34 3-3 3-3-1.34-3-3z"></path>
</svg> <a href="#d2h-781444" class="d2h-file-name">my/file/name.js</a>
<span class="d2h-file-stats">
<span class="d2h-lines-added">+12</span>
<span class="d2h-lines-deleted">-41</span>
</span>
</span>
</li>
</ol>
</div>"
`);
});
});
describe('with auto colorScheme', () => {
it('should include auto colorScheme', () => {
const hoganUtils = new HoganJsUtils({});
const fileListRenderer = new FileListRenderer(hoganUtils, {
colorScheme: ColorSchemeType.AUTO,
});
const files = [
{
isCombined: false,
isGitDiff: false,
blocks: [],
addedLines: 12,
deletedLines: 41,
language: 'js',
oldName: 'my/file/name.js',
newName: 'my/file/name.js',
},
];
const fileHtml = fileListRenderer.render(files);
expect(fileHtml).toMatchInlineSnapshot(`
"<div class="d2h-file-list-wrapper d2h-auto-color-scheme">
<div class="d2h-file-list-header">
<span class="d2h-file-list-title">Files changed (1)</span>
<a class="d2h-file-switch d2h-hide">hide</a>
<a class="d2h-file-switch d2h-show">show</a>
</div>
<ol class="d2h-file-list">
<li class="d2h-file-list-line">
<span class="d2h-file-name-wrapper">
<svg aria-hidden="true" class="d2h-icon d2h-changed" height="16" title="modified" version="1.1"
viewBox="0 0 14 16" width="14">
<path d="M13 1H1C0.45 1 0 1.45 0 2v12c0 0.55 0.45 1 1 1h12c0.55 0 1-0.45 1-1V2c0-0.55-0.45-1-1-1z m0 13H1V2h12v12zM4 8c0-1.66 1.34-3 3-3s3 1.34 3 3-1.34 3-3 3-3-1.34-3-3z"></path>
</svg> <a href="#d2h-781444" class="d2h-file-name">my/file/name.js</a>
<span class="d2h-file-stats">
<span class="d2h-lines-added">+12</span>
<span class="d2h-lines-deleted">-41</span>
</span>
</span>
</li>
</ol>
</div>"
`);
});
});
});
});

View file

@ -1,56 +0,0 @@
import HoganJsUtils from '../hoganjs-utils';
import { CSSLineClass } from '../render-utils';
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,
});
expect(result).toMatchInlineSnapshot(`
"<tr>
<td class="d2h-info">
<div class="d2h-code-line">
File without changes
</div>
</td>
</tr>"
`);
});
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 } });
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 } });
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>';
const hoganJsUtils = new HoganJsUtils({
compiledTemplates: { 'generic-empty-diff': emptyDiffTemplate },
rawTemplates: { 'generic-empty-diff': emptyDiffTemplateUncompiled },
});
const result = hoganJsUtils.render('generic', 'empty-diff', { myName: 'Rodrigo Fernandes' });
expect(result).toMatchInlineSnapshot(`"<p>Rodrigo Fernandes</p>"`);
});
});
});

View file

@ -1,725 +0,0 @@
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', () => {
const hoganUtils = new HoganJsUtils({});
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
const fileHtml = lineByLineRenderer.generateEmptyDiff();
expect(fileHtml).toMatchInlineSnapshot(`
"<tr>
<td class="d2h-info">
<div class="d2h-code-line">
File without changes
</div>
</td>
</tr>"
`);
});
});
describe('makeLineHtml', () => {
it('should work for insertions', () => {
const file = {
addedLines: 12,
deletedLines: 41,
language: 'js',
oldName: 'my/file/name.js',
newName: 'my/file/name.js',
isCombined: false,
isGitDiff: false,
blocks: [],
};
const hoganUtils = new HoganJsUtils({});
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
const fileHtml = lineByLineRenderer.generateSingleLineHtml(file, {
type: CSSLineClass.INSERTS,
prefix: '+',
content: 'test',
oldNumber: undefined,
newNumber: 30,
});
expect(fileHtml).toMatchInlineSnapshot(`
"<tr>
<td class="d2h-code-linenumber d2h-ins">
<div class="line-num1"></div>
<div class="line-num2">30</div>
</td>
<td class="d2h-ins">
<div class="d2h-code-line">
<span class="d2h-code-line-prefix">+</span>
<span class="d2h-code-line-ctn">test</span>
</div>
</td>
</tr>"
`);
});
it('should work for deletions', () => {
const file = {
addedLines: 12,
deletedLines: 41,
language: 'js',
oldName: 'my/file/name.js',
newName: 'my/file/name.js',
isCombined: false,
isGitDiff: false,
blocks: [],
};
const hoganUtils = new HoganJsUtils({});
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
const fileHtml = lineByLineRenderer.generateSingleLineHtml(file, {
type: CSSLineClass.DELETES,
prefix: '-',
content: 'test',
oldNumber: 30,
newNumber: undefined,
});
expect(fileHtml).toMatchInlineSnapshot(`
"<tr>
<td class="d2h-code-linenumber d2h-del">
<div class="line-num1">30</div>
<div class="line-num2"></div>
</td>
<td class="d2h-del">
<div class="d2h-code-line">
<span class="d2h-code-line-prefix">-</span>
<span class="d2h-code-line-ctn">test</span>
</div>
</td>
</tr>"
`);
});
it('should convert indents into non breakin spaces (2 white spaces)', () => {
const file = {
addedLines: 12,
deletedLines: 41,
language: 'js',
oldName: 'my/file/name.js',
newName: 'my/file/name.js',
isCombined: false,
isGitDiff: false,
blocks: [],
};
const hoganUtils = new HoganJsUtils({});
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
const fileHtml = lineByLineRenderer.generateSingleLineHtml(file, {
type: CSSLineClass.INSERTS,
prefix: '+',
content: ' test',
oldNumber: undefined,
newNumber: 30,
});
expect(fileHtml).toMatchInlineSnapshot(`
"<tr>
<td class="d2h-code-linenumber d2h-ins">
<div class="line-num1"></div>
<div class="line-num2">30</div>
</td>
<td class="d2h-ins">
<div class="d2h-code-line">
<span class="d2h-code-line-prefix">+</span>
<span class="d2h-code-line-ctn"> test</span>
</div>
</td>
</tr>"
`);
});
it('should convert indents into non breakin spaces (4 white spaces)', () => {
const file = {
addedLines: 12,
deletedLines: 41,
language: 'js',
oldName: 'my/file/name.js',
newName: 'my/file/name.js',
isCombined: false,
isGitDiff: false,
blocks: [],
};
const hoganUtils = new HoganJsUtils({});
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
const fileHtml = lineByLineRenderer.generateSingleLineHtml(file, {
type: CSSLineClass.INSERTS,
prefix: '+',
content: ' test',
oldNumber: undefined,
newNumber: 30,
});
expect(fileHtml).toMatchInlineSnapshot(`
"<tr>
<td class="d2h-code-linenumber d2h-ins">
<div class="line-num1"></div>
<div class="line-num2">30</div>
</td>
<td class="d2h-ins">
<div class="d2h-code-line">
<span class="d2h-code-line-prefix">+</span>
<span class="d2h-code-line-ctn"> test</span>
</div>
</td>
</tr>"
`);
});
it('should preserve tabs', () => {
const file = {
addedLines: 12,
deletedLines: 41,
language: 'js',
oldName: 'my/file/name.js',
newName: 'my/file/name.js',
isCombined: false,
isGitDiff: false,
blocks: [],
};
const hoganUtils = new HoganJsUtils({});
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
const fileHtml = lineByLineRenderer.generateSingleLineHtml(file, {
type: CSSLineClass.INSERTS,
prefix: '+',
content: '\ttest',
oldNumber: undefined,
newNumber: 30,
});
expect(fileHtml).toMatchInlineSnapshot(`
"<tr>
<td class="d2h-code-linenumber d2h-ins">
<div class="line-num1"></div>
<div class="line-num2">30</div>
</td>
<td class="d2h-ins">
<div class="d2h-code-line">
<span class="d2h-code-line-prefix">+</span>
<span class="d2h-code-line-ctn"> test</span>
</div>
</td>
</tr>"
`);
});
});
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',
isCombined: false,
isGitDiff: false,
blocks: [],
};
const diffs = '<span>Random Html</span>';
const fileHtml = lineByLineRenderer.makeFileDiffHtml(file, diffs);
expect(fileHtml).toMatchInlineSnapshot(`
"<div id="d2h-781444" class="d2h-file-wrapper" data-lang="js">
<div class="d2h-file-header">
<span class="d2h-file-name-wrapper">
<svg aria-hidden="true" class="d2h-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12">
<path d="M6 5H2v-1h4v1zM2 8h7v-1H2v1z m0 2h7v-1H2v1z m0 2h7v-1H2v1z m10-7.5v9.5c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1V2c0-0.55 0.45-1 1-1h7.5l3.5 3.5z m-1 0.5L8 2H1v12h10V5z"></path>
</svg> <span class="d2h-file-name">my/file/name.js</span>
<span class="d2h-tag d2h-changed d2h-changed-tag">CHANGED</span></span>
<label class="d2h-file-collapse">
<input class="d2h-file-collapse-input" type="checkbox" name="viewed" value="viewed">
Viewed
</label>
</div>
<div class="d2h-file-diff">
<div class="d2h-code-wrapper">
<table class="d2h-diff-table">
<tbody class="d2h-diff-tbody">
<span>Random Html</span>
</tbody>
</table>
</div>
</div>
</div>"
`);
});
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',
isNew: true,
isCombined: false,
isGitDiff: false,
blocks: [],
};
const diffs = '<span>Random Html</span>';
const fileHtml = lineByLineRenderer.makeFileDiffHtml(file, diffs);
expect(fileHtml).toMatchInlineSnapshot(`
"<div id="d2h-781444" class="d2h-file-wrapper" data-lang="js">
<div class="d2h-file-header">
<span class="d2h-file-name-wrapper">
<svg aria-hidden="true" class="d2h-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12">
<path d="M6 5H2v-1h4v1zM2 8h7v-1H2v1z m0 2h7v-1H2v1z m0 2h7v-1H2v1z m10-7.5v9.5c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1V2c0-0.55 0.45-1 1-1h7.5l3.5 3.5z m-1 0.5L8 2H1v12h10V5z"></path>
</svg> <span class="d2h-file-name">my/file/name.js</span>
<span class="d2h-tag d2h-added d2h-added-tag">ADDED</span></span>
<label class="d2h-file-collapse">
<input class="d2h-file-collapse-input" type="checkbox" name="viewed" value="viewed">
Viewed
</label>
</div>
<div class="d2h-file-diff">
<div class="d2h-code-wrapper">
<table class="d2h-diff-table">
<tbody class="d2h-diff-tbody">
<span>Random Html</span>
</tbody>
</table>
</div>
</div>
</div>"
`);
});
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',
isDeleted: true,
isCombined: false,
isGitDiff: false,
blocks: [],
};
const diffs = '<span>Random Html</span>';
const fileHtml = lineByLineRenderer.makeFileDiffHtml(file, diffs);
expect(fileHtml).toMatchInlineSnapshot(`
"<div id="d2h-781444" class="d2h-file-wrapper" data-lang="js">
<div class="d2h-file-header">
<span class="d2h-file-name-wrapper">
<svg aria-hidden="true" class="d2h-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12">
<path d="M6 5H2v-1h4v1zM2 8h7v-1H2v1z m0 2h7v-1H2v1z m0 2h7v-1H2v1z m10-7.5v9.5c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1V2c0-0.55 0.45-1 1-1h7.5l3.5 3.5z m-1 0.5L8 2H1v12h10V5z"></path>
</svg> <span class="d2h-file-name">my/file/name.js</span>
<span class="d2h-tag d2h-deleted d2h-deleted-tag">DELETED</span></span>
<label class="d2h-file-collapse">
<input class="d2h-file-collapse-input" type="checkbox" name="viewed" value="viewed">
Viewed
</label>
</div>
<div class="d2h-file-diff">
<div class="d2h-code-wrapper">
<table class="d2h-diff-table">
<tbody class="d2h-diff-tbody">
<span>Random Html</span>
</tbody>
</table>
</div>
</div>
</div>"
`);
});
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',
isRename: true,
isCombined: false,
isGitDiff: false,
blocks: [],
};
const diffs = '<span>Random Html</span>';
const fileHtml = lineByLineRenderer.makeFileDiffHtml(file, diffs);
expect(fileHtml).toMatchInlineSnapshot(`
"<div id="d2h-662683" class="d2h-file-wrapper" data-lang="js">
<div class="d2h-file-header">
<span class="d2h-file-name-wrapper">
<svg aria-hidden="true" class="d2h-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12">
<path d="M6 5H2v-1h4v1zM2 8h7v-1H2v1z m0 2h7v-1H2v1z m0 2h7v-1H2v1z m10-7.5v9.5c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1V2c0-0.55 0.45-1 1-1h7.5l3.5 3.5z m-1 0.5L8 2H1v12h10V5z"></path>
</svg> <span class="d2h-file-name">my/file/{name1.js name2.js}</span>
<span class="d2h-tag d2h-moved d2h-moved-tag">RENAMED</span></span>
<label class="d2h-file-collapse">
<input class="d2h-file-collapse-input" type="checkbox" name="viewed" value="viewed">
Viewed
</label>
</div>
<div class="d2h-file-diff">
<div class="d2h-code-wrapper">
<table class="d2h-diff-table">
<tbody class="d2h-diff-tbody">
<span>Random Html</span>
</tbody>
</table>
</div>
</div>
</div>"
`);
});
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,
});
const file = {
addedLines: 0,
deletedLines: 0,
language: 'js',
oldName: 'my/file/name1.js',
newName: 'my/file/name2.js',
isRename: true,
isCombined: false,
isGitDiff: false,
blocks: [],
};
const diffs = '<span>Random Html</span>';
const fileHtml = lineByLineRenderer.makeFileDiffHtml(file, diffs);
expect(fileHtml).toMatchInlineSnapshot(`""`);
});
});
describe('generateLineByLineJsonHtml', () => {
it('should work for list of files', () => {
const exampleJson: DiffFile[] = [
{
blocks: [
{
lines: [
{
content: '-test',
type: LineType.DELETE,
oldNumber: 1,
newNumber: undefined,
},
{
content: '+test1r',
type: LineType.INSERT,
oldNumber: undefined,
newNumber: 1,
},
],
oldStartLine: 1,
oldStartLine2: undefined,
newStartLine: 1,
header: '@@ -1 +1 @@',
},
],
deletedLines: 1,
addedLines: 1,
checksumBefore: '0000001',
checksumAfter: '0ddf2ba',
oldName: 'sample',
newName: 'sample',
language: 'txt',
isCombined: false,
isGitDiff: true,
},
];
const hoganUtils = new HoganJsUtils({});
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {
matching: LineMatchingType.LINES,
});
const html = lineByLineRenderer.render(exampleJson);
expect(html).toMatchInlineSnapshot(`
"<div class="d2h-wrapper d2h-light-color-scheme">
<div id="d2h-675094" class="d2h-file-wrapper" data-lang="txt">
<div class="d2h-file-header">
<span class="d2h-file-name-wrapper">
<svg aria-hidden="true" class="d2h-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12">
<path d="M6 5H2v-1h4v1zM2 8h7v-1H2v1z m0 2h7v-1H2v1z m0 2h7v-1H2v1z m10-7.5v9.5c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1V2c0-0.55 0.45-1 1-1h7.5l3.5 3.5z m-1 0.5L8 2H1v12h10V5z"></path>
</svg> <span class="d2h-file-name">sample</span>
<span class="d2h-tag d2h-changed d2h-changed-tag">CHANGED</span></span>
<label class="d2h-file-collapse">
<input class="d2h-file-collapse-input" type="checkbox" name="viewed" value="viewed">
Viewed
</label>
</div>
<div class="d2h-file-diff">
<div class="d2h-code-wrapper">
<table class="d2h-diff-table">
<tbody class="d2h-diff-tbody">
<tr>
<td class="d2h-code-linenumber d2h-info"></td>
<td class="d2h-info">
<div class="d2h-code-line">@@ -1 +1 @@</div>
</td>
</tr><tr>
<td class="d2h-code-linenumber d2h-del d2h-change">
<div class="line-num1">1</div>
<div class="line-num2"></div>
</td>
<td class="d2h-del d2h-change">
<div class="d2h-code-line">
<span class="d2h-code-line-prefix">-</span>
<span class="d2h-code-line-ctn"><del>test</del></span>
</div>
</td>
</tr><tr>
<td class="d2h-code-linenumber d2h-ins d2h-change">
<div class="line-num1"></div>
<div class="line-num2">1</div>
</td>
<td class="d2h-ins d2h-change">
<div class="d2h-code-line">
<span class="d2h-code-line-prefix">+</span>
<span class="d2h-code-line-ctn"><ins>test1r</ins></span>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>"
`);
});
it('should work for empty blocks', () => {
const exampleJson = [
{
blocks: [],
deletedLines: 0,
addedLines: 0,
oldName: 'sample',
language: 'js',
newName: 'sample',
isCombined: false,
isGitDiff: false,
},
];
const hoganUtils = new HoganJsUtils({});
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {
renderNothingWhenEmpty: false,
});
const html = lineByLineRenderer.render(exampleJson);
expect(html).toMatchInlineSnapshot(`
"<div class="d2h-wrapper d2h-light-color-scheme">
<div id="d2h-675094" class="d2h-file-wrapper" data-lang="js">
<div class="d2h-file-header">
<span class="d2h-file-name-wrapper">
<svg aria-hidden="true" class="d2h-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12">
<path d="M6 5H2v-1h4v1zM2 8h7v-1H2v1z m0 2h7v-1H2v1z m0 2h7v-1H2v1z m10-7.5v9.5c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1V2c0-0.55 0.45-1 1-1h7.5l3.5 3.5z m-1 0.5L8 2H1v12h10V5z"></path>
</svg> <span class="d2h-file-name">sample</span>
<span class="d2h-tag d2h-changed d2h-changed-tag">CHANGED</span></span>
<label class="d2h-file-collapse">
<input class="d2h-file-collapse-input" type="checkbox" name="viewed" value="viewed">
Viewed
</label>
</div>
<div class="d2h-file-diff">
<div class="d2h-code-wrapper">
<table class="d2h-diff-table">
<tbody class="d2h-diff-tbody">
<tr>
<td class="d2h-info">
<div class="d2h-code-line">
File without changes
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>"
`);
});
it('should work for too big file diff', () => {
const exampleJson = [
{
blocks: [
{
header: '<a href="http://example.com">Custom link to render</a>',
lines: [],
newStartLine: 0,
oldStartLine: 0,
oldStartLine2: undefined,
},
],
deletedLines: 0,
addedLines: 0,
oldName: 'sample',
language: 'js',
newName: 'sample',
isCombined: false,
isGitDiff: false,
isTooBig: true,
},
];
const hoganUtils = new HoganJsUtils({});
const lineByLineRenderer = new LineByLineRenderer(hoganUtils);
const html = lineByLineRenderer.render(exampleJson);
expect(html).toMatchInlineSnapshot(`
"<div class="d2h-wrapper d2h-light-color-scheme">
<div id="d2h-675094" class="d2h-file-wrapper" data-lang="js">
<div class="d2h-file-header">
<span class="d2h-file-name-wrapper">
<svg aria-hidden="true" class="d2h-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12">
<path d="M6 5H2v-1h4v1zM2 8h7v-1H2v1z m0 2h7v-1H2v1z m0 2h7v-1H2v1z m10-7.5v9.5c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1V2c0-0.55 0.45-1 1-1h7.5l3.5 3.5z m-1 0.5L8 2H1v12h10V5z"></path>
</svg> <span class="d2h-file-name">sample</span>
<span class="d2h-tag d2h-changed d2h-changed-tag">CHANGED</span></span>
<label class="d2h-file-collapse">
<input class="d2h-file-collapse-input" type="checkbox" name="viewed" value="viewed">
Viewed
</label>
</div>
<div class="d2h-file-diff">
<div class="d2h-code-wrapper">
<table class="d2h-diff-table">
<tbody class="d2h-diff-tbody">
<tr>
<td class="d2h-code-linenumber d2h-info"></td>
<td class="d2h-info">
<div class="d2h-code-line"><a href="http://example.com">Custom link to render</a></div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>"
`);
});
});
describe('_generateFileHtml', () => {
it('should work for simple file', () => {
const hoganUtils = new HoganJsUtils({});
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
const file: DiffFile = {
blocks: [
{
lines: [
{
content: ' one context line',
type: LineType.CONTEXT,
oldNumber: 1,
newNumber: 1,
},
{
content: '-test',
type: LineType.DELETE,
oldNumber: 2,
newNumber: undefined,
},
{
content: '+test1r',
type: LineType.INSERT,
oldNumber: undefined,
newNumber: 2,
},
{
content: '+test2r',
type: LineType.INSERT,
oldNumber: undefined,
newNumber: 3,
},
],
oldStartLine: 1,
oldStartLine2: undefined,
newStartLine: 1,
header: '@@ -1 +1 @@',
},
],
deletedLines: 1,
addedLines: 1,
checksumBefore: '0000001',
checksumAfter: '0ddf2ba',
oldName: 'sample',
language: 'txt',
newName: 'sample',
isCombined: false,
isGitDiff: true,
};
const html = lineByLineRenderer.generateFileHtml(file);
expect(html).toMatchInlineSnapshot(`
"<tr>
<td class="d2h-code-linenumber d2h-info"></td>
<td class="d2h-info">
<div class="d2h-code-line">@@ -1 +1 @@</div>
</td>
</tr><tr>
<td class="d2h-code-linenumber d2h-cntx">
<div class="line-num1">1</div>
<div class="line-num2">1</div>
</td>
<td class="d2h-cntx">
<div class="d2h-code-line">
<span class="d2h-code-line-prefix">&nbsp;</span>
<span class="d2h-code-line-ctn">one context line</span>
</div>
</td>
</tr><tr>
<td class="d2h-code-linenumber d2h-del d2h-change">
<div class="line-num1">2</div>
<div class="line-num2"></div>
</td>
<td class="d2h-del d2h-change">
<div class="d2h-code-line">
<span class="d2h-code-line-prefix">-</span>
<span class="d2h-code-line-ctn"><del>test</del></span>
</div>
</td>
</tr><tr>
<td class="d2h-code-linenumber d2h-ins d2h-change">
<div class="line-num1"></div>
<div class="line-num2">2</div>
</td>
<td class="d2h-ins d2h-change">
<div class="d2h-code-line">
<span class="d2h-code-line-prefix">+</span>
<span class="d2h-code-line-ctn"><ins>test1r</ins></span>
</div>
</td>
</tr><tr>
<td class="d2h-code-linenumber d2h-ins">
<div class="line-num1"></div>
<div class="line-num2">3</div>
</td>
<td class="d2h-ins">
<div class="d2h-code-line">
<span class="d2h-code-line-prefix">+</span>
<span class="d2h-code-line-ctn">test2r</span>
</div>
</td>
</tr>"
`);
});
});
});

View file

@ -1,152 +0,0 @@
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).toBe('&amp;');
});
it('should escape < with &lt;', () => {
const result = escapeForHtml('<');
expect(result).toBe('&lt;');
});
it('should escape > with &gt;', () => {
const result = escapeForHtml('>');
expect(result).toBe('&gt;');
});
it('should escape " with &quot;', () => {
const result = escapeForHtml('"');
expect(result).toBe('&quot;');
});
it("should escape ' with &#x27;", () => {
const result = escapeForHtml("'");
expect(result).toBe('&#x27;');
});
it('should escape / with &#x2F;', () => {
const result = escapeForHtml('/');
expect(result).toBe('&#x2F;');
});
it('should escape a string containing HTML code', () => {
const result = escapeForHtml(`<a href="/search?q=diff2html">Search 'Diff2Html'</a>`);
expect(result).toBe(
'&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', () => {
const result = getHtmlId({
oldName: 'sample.js',
newName: 'sample.js',
});
expect(result).toBe('d2h-960013');
});
});
describe('getDiffName', () => {
it('should generate the file name for a changed file', () => {
const result = filenameDiff({
oldName: 'sample.js',
newName: 'sample.js',
});
expect(result).toBe('sample.js');
});
it('should generate the file name for a changed file and full rename', () => {
const result = filenameDiff({
oldName: 'sample1.js',
newName: 'sample2.js',
});
expect(result).toBe('sample1.js → sample2.js');
});
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',
});
expect(result).toBe('{src → source}/path/sample.js');
});
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',
});
expect(result).toBe('src/path/{sample1.js → sample2.js}');
});
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',
});
expect(result).toBe('src/{really/big → small}/path/sample.js');
});
it('should generate the file name for a deleted file', () => {
const result = filenameDiff({
oldName: 'src/my/file.js',
newName: '/dev/null',
});
expect(result).toBe('src/my/file.js');
});
it('should generate the file name for a new file', () => {
const result = filenameDiff({
oldName: '/dev/null',
newName: 'src/my/file.js',
});
expect(result).toBe('src/my/file.js');
});
});
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>;',
},
newLine: {
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,
});
expect(result).toEqual({
oldLine: {
prefix: '-',
content: 'var myVar = <del>2</del>;',
},
newLine: {
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, {
diffStyle: DiffStyleType.WORD,
matching: LineMatchingType.WORDS,
matchWordsThreshold: 1.0,
});
expect(result).toEqual({
oldLine: {
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>;',
},
});
});
});
});

View file

@ -1,538 +0,0 @@
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', () => {
const hoganUtils = new HoganJsUtils({});
const sideBySideRenderer = new SideBySideRenderer(hoganUtils, {});
const fileHtml = sideBySideRenderer.generateEmptyDiff();
expect(fileHtml).toMatchInlineSnapshot(`
{
"left": "<tr>
<td class="d2h-info">
<div class="d2h-code-side-line">
File without changes
</div>
</td>
</tr>",
"right": "",
}
`);
});
});
describe('generateSideBySideFileHtml', () => {
it('should generate lines with the right prefixes', () => {
const hoganUtils = new HoganJsUtils({});
const sideBySideRenderer = new SideBySideRenderer(hoganUtils, {});
const file: DiffFile = {
isGitDiff: true,
blocks: [
{
lines: [
{
content: ' context',
type: LineType.CONTEXT,
oldNumber: 19,
newNumber: 19,
},
{
content: '-removed',
type: LineType.DELETE,
oldNumber: 20,
newNumber: undefined,
},
{
content: '+added',
type: LineType.INSERT,
oldNumber: undefined,
newNumber: 20,
},
{
content: '+another added',
type: LineType.INSERT,
oldNumber: undefined,
newNumber: 21,
},
],
oldStartLine: 19,
newStartLine: 19,
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,
};
const fileHtml = sideBySideRenderer.generateFileHtml(file);
expect(fileHtml).toMatchInlineSnapshot(`
{
"left": "<tr>
<td class="d2h-code-side-linenumber d2h-info"></td>
<td class="d2h-info">
<div class="d2h-code-side-line">@@ -19,7 +19,7 @@</div>
</td>
</tr><tr>
<td class="d2h-code-side-linenumber d2h-cntx">
19
</td>
<td class="d2h-cntx">
<div class="d2h-code-side-line">
<span class="d2h-code-line-prefix">&nbsp;</span>
<span class="d2h-code-line-ctn">context</span>
</div>
</td>
</tr><tr>
<td class="d2h-code-side-linenumber d2h-del d2h-change">
20
</td>
<td class="d2h-del d2h-change">
<div class="d2h-code-side-line">
<span class="d2h-code-line-prefix">-</span>
<span class="d2h-code-line-ctn"><del>removed</del></span>
</div>
</td>
</tr><tr>
<td class="d2h-code-side-linenumber d2h-code-side-emptyplaceholder d2h-cntx d2h-emptyplaceholder">
</td>
<td class="d2h-cntx d2h-emptyplaceholder">
<div class="d2h-code-side-line d2h-code-side-emptyplaceholder">
<span class="d2h-code-line-prefix">&nbsp;</span>
<span class="d2h-code-line-ctn"><br></span>
</div>
</td>
</tr>",
"right": "<tr>
<td class="d2h-code-side-linenumber d2h-info"></td>
<td class="d2h-info">
<div class="d2h-code-side-line">&nbsp;</div>
</td>
</tr><tr>
<td class="d2h-code-side-linenumber d2h-cntx">
19
</td>
<td class="d2h-cntx">
<div class="d2h-code-side-line">
<span class="d2h-code-line-prefix">&nbsp;</span>
<span class="d2h-code-line-ctn">context</span>
</div>
</td>
</tr><tr>
<td class="d2h-code-side-linenumber d2h-ins d2h-change">
20
</td>
<td class="d2h-ins d2h-change">
<div class="d2h-code-side-line">
<span class="d2h-code-line-prefix">+</span>
<span class="d2h-code-line-ctn"><ins>added</ins></span>
</div>
</td>
</tr><tr>
<td class="d2h-code-side-linenumber d2h-ins">
21
</td>
<td class="d2h-ins">
<div class="d2h-code-side-line">
<span class="d2h-code-line-prefix">+</span>
<span class="d2h-code-line-ctn">another added</span>
</div>
</td>
</tr>",
}
`);
});
});
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,
});
expect(fileHtml).toMatchInlineSnapshot(`
{
"left": "<tr>
<td class="d2h-code-side-linenumber d2h-code-side-emptyplaceholder d2h-cntx d2h-emptyplaceholder">
</td>
<td class="d2h-cntx d2h-emptyplaceholder">
<div class="d2h-code-side-line d2h-code-side-emptyplaceholder">
<span class="d2h-code-line-prefix">&nbsp;</span>
<span class="d2h-code-line-ctn"><br></span>
</div>
</td>
</tr>",
"right": "<tr>
<td class="d2h-code-side-linenumber d2h-ins">
30
</td>
<td class="d2h-ins">
<div class="d2h-code-side-line">
<span class="d2h-code-line-prefix">+</span>
<span class="d2h-code-line-ctn">test</span>
</div>
</td>
</tr>",
}
`);
});
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,
},
undefined,
);
expect(fileHtml).toMatchInlineSnapshot(`
{
"left": "<tr>
<td class="d2h-code-side-linenumber d2h-del">
30
</td>
<td class="d2h-del">
<div class="d2h-code-side-line">
<span class="d2h-code-line-prefix">-</span>
<span class="d2h-code-line-ctn">test</span>
</div>
</td>
</tr>",
"right": "<tr>
<td class="d2h-code-side-linenumber d2h-code-side-emptyplaceholder d2h-cntx d2h-emptyplaceholder">
</td>
<td class="d2h-cntx d2h-emptyplaceholder">
<div class="d2h-code-side-line d2h-code-side-emptyplaceholder">
<span class="d2h-code-line-prefix">&nbsp;</span>
<span class="d2h-code-line-ctn"><br></span>
</div>
</td>
</tr>",
}
`);
});
});
describe('generateSideBySideJsonHtml', () => {
it('should work for list of files', () => {
const exampleJson: DiffFile[] = [
{
blocks: [
{
lines: [
{
content: '-test',
type: LineType.DELETE,
oldNumber: 1,
newNumber: undefined,
},
{
content: '+test1r',
type: LineType.INSERT,
oldNumber: undefined,
newNumber: 1,
},
],
oldStartLine: 1,
oldStartLine2: undefined,
newStartLine: 1,
header: '@@ -1 +1 @@',
},
],
deletedLines: 1,
addedLines: 1,
checksumBefore: '0000001',
checksumAfter: '0ddf2ba',
oldName: 'sample',
language: 'txt',
newName: 'sample',
isCombined: false,
isGitDiff: true,
},
];
const hoganUtils = new HoganJsUtils({});
const sideBySideRenderer = new SideBySideRenderer(hoganUtils, { matching: LineMatchingType.LINES });
const html = sideBySideRenderer.render(exampleJson);
expect(html).toMatchInlineSnapshot(`
"<div class="d2h-wrapper d2h-light-color-scheme">
<div id="d2h-675094" class="d2h-file-wrapper" data-lang="txt">
<div class="d2h-file-header">
<span class="d2h-file-name-wrapper">
<svg aria-hidden="true" class="d2h-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12">
<path d="M6 5H2v-1h4v1zM2 8h7v-1H2v1z m0 2h7v-1H2v1z m0 2h7v-1H2v1z m10-7.5v9.5c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1V2c0-0.55 0.45-1 1-1h7.5l3.5 3.5z m-1 0.5L8 2H1v12h10V5z"></path>
</svg> <span class="d2h-file-name">sample</span>
<span class="d2h-tag d2h-changed d2h-changed-tag">CHANGED</span></span>
<label class="d2h-file-collapse">
<input class="d2h-file-collapse-input" type="checkbox" name="viewed" value="viewed">
Viewed
</label>
</div>
<div class="d2h-files-diff">
<div class="d2h-file-side-diff">
<div class="d2h-code-wrapper">
<table class="d2h-diff-table">
<tbody class="d2h-diff-tbody">
<tr>
<td class="d2h-code-side-linenumber d2h-info"></td>
<td class="d2h-info">
<div class="d2h-code-side-line">@@ -1 +1 @@</div>
</td>
</tr><tr>
<td class="d2h-code-side-linenumber d2h-del d2h-change">
1
</td>
<td class="d2h-del d2h-change">
<div class="d2h-code-side-line">
<span class="d2h-code-line-prefix">-</span>
<span class="d2h-code-line-ctn"><del>test</del></span>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="d2h-file-side-diff">
<div class="d2h-code-wrapper">
<table class="d2h-diff-table">
<tbody class="d2h-diff-tbody">
<tr>
<td class="d2h-code-side-linenumber d2h-info"></td>
<td class="d2h-info">
<div class="d2h-code-side-line">&nbsp;</div>
</td>
</tr><tr>
<td class="d2h-code-side-linenumber d2h-ins d2h-change">
1
</td>
<td class="d2h-ins d2h-change">
<div class="d2h-code-side-line">
<span class="d2h-code-line-prefix">+</span>
<span class="d2h-code-line-ctn"><ins>test1r</ins></span>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>"
`);
});
it('should work for files without blocks', () => {
const exampleJson: DiffFile[] = [
{
blocks: [],
oldName: 'sample',
language: 'js',
newName: 'sample',
isCombined: false,
addedLines: 0,
deletedLines: 0,
isGitDiff: false,
},
];
const hoganUtils = new HoganJsUtils({});
const sideBySideRenderer = new SideBySideRenderer(hoganUtils, {});
const html = sideBySideRenderer.render(exampleJson);
expect(html).toMatchInlineSnapshot(`
"<div class="d2h-wrapper d2h-light-color-scheme">
<div id="d2h-675094" class="d2h-file-wrapper" data-lang="js">
<div class="d2h-file-header">
<span class="d2h-file-name-wrapper">
<svg aria-hidden="true" class="d2h-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12">
<path d="M6 5H2v-1h4v1zM2 8h7v-1H2v1z m0 2h7v-1H2v1z m0 2h7v-1H2v1z m10-7.5v9.5c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1V2c0-0.55 0.45-1 1-1h7.5l3.5 3.5z m-1 0.5L8 2H1v12h10V5z"></path>
</svg> <span class="d2h-file-name">sample</span>
<span class="d2h-tag d2h-changed d2h-changed-tag">CHANGED</span></span>
<label class="d2h-file-collapse">
<input class="d2h-file-collapse-input" type="checkbox" name="viewed" value="viewed">
Viewed
</label>
</div>
<div class="d2h-files-diff">
<div class="d2h-file-side-diff">
<div class="d2h-code-wrapper">
<table class="d2h-diff-table">
<tbody class="d2h-diff-tbody">
<tr>
<td class="d2h-info">
<div class="d2h-code-side-line">
File without changes
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="d2h-file-side-diff">
<div class="d2h-code-wrapper">
<table class="d2h-diff-table">
<tbody class="d2h-diff-tbody">
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>"
`);
});
it('should work for too big file diff', () => {
const exampleJson = [
{
blocks: [
{
header: '<a href="http://example.com">Custom link to render</a>',
lines: [],
newStartLine: 0,
oldStartLine: 0,
oldStartLine2: undefined,
},
],
deletedLines: 0,
addedLines: 0,
oldName: 'sample',
language: 'js',
newName: 'sample',
isCombined: false,
isGitDiff: false,
isTooBig: true,
},
];
const hoganUtils = new HoganJsUtils({});
const sideBySideRenderer = new SideBySideRenderer(hoganUtils);
const html = sideBySideRenderer.render(exampleJson);
expect(html).toMatchInlineSnapshot(`
"<div class="d2h-wrapper d2h-light-color-scheme">
<div id="d2h-675094" class="d2h-file-wrapper" data-lang="js">
<div class="d2h-file-header">
<span class="d2h-file-name-wrapper">
<svg aria-hidden="true" class="d2h-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12">
<path d="M6 5H2v-1h4v1zM2 8h7v-1H2v1z m0 2h7v-1H2v1z m0 2h7v-1H2v1z m10-7.5v9.5c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1V2c0-0.55 0.45-1 1-1h7.5l3.5 3.5z m-1 0.5L8 2H1v12h10V5z"></path>
</svg> <span class="d2h-file-name">sample</span>
<span class="d2h-tag d2h-changed d2h-changed-tag">CHANGED</span></span>
<label class="d2h-file-collapse">
<input class="d2h-file-collapse-input" type="checkbox" name="viewed" value="viewed">
Viewed
</label>
</div>
<div class="d2h-files-diff">
<div class="d2h-file-side-diff">
<div class="d2h-code-wrapper">
<table class="d2h-diff-table">
<tbody class="d2h-diff-tbody">
<tr>
<td class="d2h-code-side-linenumber d2h-info"></td>
<td class="d2h-info">
<div class="d2h-code-side-line"><a href="http://example.com">Custom link to render</a></div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="d2h-file-side-diff">
<div class="d2h-code-wrapper">
<table class="d2h-diff-table">
<tbody class="d2h-diff-tbody">
<tr>
<td class="d2h-code-side-linenumber d2h-info"></td>
<td class="d2h-info">
<div class="d2h-code-side-line">&nbsp;</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>"
`);
});
});
describe('processLines', () => {
it('should process file lines', () => {
const oldLines: DiffLine[] = [
{
content: '-test',
type: LineType.DELETE,
oldNumber: 1,
newNumber: undefined,
},
];
const newLines: DiffLine[] = [
{
content: '+test1r',
type: LineType.INSERT,
oldNumber: undefined,
newNumber: 1,
},
];
const hoganUtils = new HoganJsUtils({});
const sideBySideRenderer = new SideBySideRenderer(hoganUtils, { matching: LineMatchingType.LINES });
const html = sideBySideRenderer.processChangedLines(false, oldLines, newLines);
expect(html).toMatchInlineSnapshot(`
{
"left": "<tr>
<td class="d2h-code-side-linenumber d2h-del d2h-change">
1
</td>
<td class="d2h-del d2h-change">
<div class="d2h-code-side-line">
<span class="d2h-code-line-prefix">-</span>
<span class="d2h-code-line-ctn"><del>test</del></span>
</div>
</td>
</tr>",
"right": "<tr>
<td class="d2h-code-side-linenumber d2h-ins d2h-change">
1
</td>
<td class="d2h-ins d2h-change">
<div class="d2h-code-side-line">
<span class="d2h-code-line-prefix">+</span>
<span class="d2h-code-line-ctn"><ins>test1r</ins></span>
</div>
</td>
</tr>",
}
`);
});
});
});

View file

@ -1,31 +0,0 @@
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).toBe('\\[Link\\]\\(https:\\/\\/diff2html\\.xyz\\)');
});
it('should escape all dangerous characters', () => {
const result = escapeForRegExp('-[]/{}()*+?.\\^$|');
expect(result).toBe('\\-\\[\\]\\/\\{\\}\\(\\)\\*\\+\\?\\.\\\\\\^\\$\\|');
});
});
describe('unifyPath', () => {
it('should unify windows style path', () => {
const result = unifyPath('\\Users\\Downloads\\diff.html');
expect(result).toBe('/Users/Downloads/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'));
});
});
});

View file

@ -1,481 +0,0 @@
import { DiffFile, DiffBlock, DiffLine, LineType } from './types';
import { escapeForRegExp } from './utils';
export interface DiffParserConfig {
srcPrefix?: string;
dstPrefix?: string;
diffMaxChanges?: number;
diffMaxLineLength?: number;
diffTooBigMessage?: (fileIndex: number) => string;
}
function getExtension(filename: string, language: string): string {
const filenameParts = filename.split('.');
return filenameParts.length > 1 ? filenameParts[filenameParts.length - 1] : language;
}
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/'];
function getFilename(line: string, linePrefix?: string, extraPrefix?: string): string {
const prefixes = extraPrefix !== undefined ? [...baseDiffFilenamePrefixes, extraPrefix] : baseDiffFilenamePrefixes;
const FilenameRegExp = linePrefix
? new RegExp(`^${escapeForRegExp(linePrefix)} "?(.+?)"?$`)
: new RegExp('^"?(.+?)"?$');
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}.*$/, '');
}
function getSrcFilename(line: string, srcPrefix?: string): string | undefined {
return getFilename(line, '---', srcPrefix);
}
function getDstFilename(line: string, dstPrefix?: string): string | undefined {
return getFilename(line, '+++', dstPrefix);
}
/**
*
* Docs:
* - Unified: https://www.gnu.org/software/diffutils/manual/html_node/Unified-Format.html
* - Git Diff: https://git-scm.com/docs/git-diff-tree#_raw_output_format
* - Git Combined Diff: https://git-scm.com/docs/git-diff-tree#_combined_diff_format
*
*/
export function parse(diffInput: string, config: DiffParserConfig = {}): DiffFile[] {
const files: DiffFile[] = [];
let currentFile: DiffFile | null = null;
let currentBlock: DiffBlock | null = null;
let oldLine: number | null = null;
let oldLine2: number | null = null; // Used for combined diff
let newLine: number | null = null;
let possibleOldName: string | null = null;
let possibleNewName: string | null = null;
/* Diff Header */
const oldFileNameHeader = '--- ';
const newFileNameHeader = '+++ ';
const hunkHeaderPrefix = '@@';
/* Diff */
const oldMode = /^old mode (\d{6})/;
const newMode = /^new mode (\d{6})/;
const deletedFileMode = /^deleted file mode (\d{6})/;
const newFileMode = /^new file mode (\d{6})/;
const copyFrom = /^copy from "?(.+)"?/;
const copyTo = /^copy to "?(.+)"?/;
const renameFrom = /^rename from "?(.+)"?/;
const renameTo = /^rename to "?(.+)"?/;
const similarityIndex = /^similarity index (\d+)%/;
const dissimilarityIndex = /^dissimilarity index (\d+)%/;
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 ([\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');
/* Add previous block(if exists) before start a new file */
function saveBlock(): void {
if (currentBlock !== null && currentFile !== null) {
currentFile.blocks.push(currentBlock);
currentBlock = null;
}
}
/*
* Add previous file(if exists) before start a new one
* if it has name (to avoid binary files errors)
*/
function saveFile(): void {
if (currentFile !== null) {
if (!currentFile.oldName && possibleOldName !== null) {
currentFile.oldName = possibleOldName;
}
if (!currentFile.newName && possibleNewName !== null) {
currentFile.newName = possibleNewName;
}
if (currentFile.newName) {
files.push(currentFile);
currentFile = null;
}
}
possibleOldName = null;
possibleNewName = null;
}
/* Create file structure */
function startFile(): void {
saveBlock();
saveFile();
// eslint-disable-next-line
// @ts-ignore
currentFile = {
blocks: [],
deletedLines: 0,
addedLines: 0,
};
}
function startBlock(line: string): void {
saveBlock();
let values;
/**
* From Range:
* -<start line>[,<number of lines>]
*
* To Range:
* +<start line>[,<number of lines>]
*
* @@ from-file-range to-file-range @@
*
* @@@ from-file-range from-file-range to-file-range @@@
*
* number of lines is optional, if omited consider 0
*/
if (currentFile !== null) {
if ((values = /^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@.*/.exec(line))) {
currentFile.isCombined = false;
oldLine = parseInt(values[1], 10);
newLine = parseInt(values[2], 10);
} else if ((values = /^@@@ -(\d+)(?:,\d+)? -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@@.*/.exec(line))) {
currentFile.isCombined = true;
oldLine = parseInt(values[1], 10);
oldLine2 = parseInt(values[2], 10);
newLine = parseInt(values[3], 10);
} else {
if (line.startsWith(hunkHeaderPrefix)) {
console.error('Failed to parse lines, starting in 0!');
}
oldLine = 0;
newLine = 0;
currentFile.isCombined = false;
}
}
/* Create block metadata */
currentBlock = {
lines: [],
// eslint-disable-next-line
// @ts-ignore
oldStartLine: oldLine,
// eslint-disable-next-line
// @ts-ignore
oldStartLine2: oldLine2,
// eslint-disable-next-line
// @ts-ignore
newStartLine: newLine,
header: line,
};
}
function createLine(line: string): void {
if (currentFile === null || currentBlock === null || oldLine === null || newLine === null) return;
// eslint-disable-next-line
// @ts-ignore
const currentLine: DiffLine = {
content: line,
};
const addedPrefixes = currentFile.isCombined ? ['+ ', ' +', '++'] : ['+'];
const deletedPrefixes = currentFile.isCombined ? ['- ', ' -', '--'] : ['-'];
if (startsWithAny(line, addedPrefixes)) {
currentFile.addedLines++;
currentLine.type = LineType.INSERT;
currentLine.oldNumber = undefined;
currentLine.newNumber = newLine++;
} else if (startsWithAny(line, deletedPrefixes)) {
currentFile.deletedLines++;
currentLine.type = LineType.DELETE;
currentLine.oldNumber = oldLine++;
currentLine.newNumber = undefined;
} else {
currentLine.type = LineType.CONTEXT;
currentLine.oldNumber = oldLine++;
currentLine.newNumber = newLine++;
}
currentBlock.lines.push(currentLine);
}
/*
* Checks if there is a hunk header coming before a new file starts
*
* Hunk header is a group of three lines started by ( `--- ` , `+++ ` , `@@` )
*/
function existHunkHeader(line: string, lineIdx: number): boolean {
let idx = lineIdx;
while (idx < diffLines.length - 3) {
if (line.startsWith('diff')) {
return false;
}
if (
diffLines[idx].startsWith(oldFileNameHeader) &&
diffLines[idx + 1].startsWith(newFileNameHeader) &&
diffLines[idx + 2].startsWith(hunkHeaderPrefix)
) {
return true;
}
idx++;
}
return false;
}
diffLines.forEach((line, lineIndex) => {
// 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('*')) {
return;
}
// Used to store regex capture groups
let values;
const prevLine = diffLines[lineIndex - 1];
const nxtLine = diffLines[lineIndex + 1];
const afterNxtLine = diffLines[lineIndex + 2];
if (line.startsWith('diff --git') || line.startsWith('diff --combined')) {
startFile();
// diff --git a/blocked_delta_results.png b/blocked_delta_results.png
const gitDiffStart = /^diff --git "?([a-ciow]\/.+)"? "?([a-ciow]\/.+)"?/;
if ((values = gitDiffStart.exec(line))) {
possibleOldName = getFilename(values[1], undefined, config.dstPrefix);
possibleNewName = getFilename(values[2], undefined, config.srcPrefix);
}
if (currentFile === null) {
throw new Error('Where is my file !!!');
}
currentFile.isGitDiff = true;
return;
}
if (line.startsWith('Binary files') && !currentFile?.isGitDiff) {
startFile();
const unixDiffBinaryStart = /^Binary files "?([a-ciow]\/.+)"? and "?([a-ciow]\/.+)"? differ/;
if ((values = unixDiffBinaryStart.exec(line))) {
possibleOldName = getFilename(values[1], undefined, config.dstPrefix);
possibleNewName = getFilename(values[2], undefined, config.srcPrefix);
}
if (currentFile === null) {
throw new Error('Where is my file !!!');
}
currentFile.isBinary = true;
return;
}
if (
!currentFile || // If we do not have a file yet, we should crete one
(!currentFile.isGitDiff &&
currentFile && // If we already have some file in progress and
line.startsWith(oldFileNameHeader) && // If we get to an old file path header line
// And is followed by the new file path header line and the hunk header line
nxtLine.startsWith(newFileNameHeader) &&
afterNxtLine.startsWith(hunkHeaderPrefix))
) {
startFile();
}
// Ignore remaining diff for current file if marked as too big
if (currentFile?.isTooBig) {
return;
}
if (
currentFile &&
((typeof config.diffMaxChanges === 'number' &&
currentFile.addedLines + currentFile.deletedLines > config.diffMaxChanges) ||
(typeof config.diffMaxLineLength === 'number' && line.length > config.diffMaxLineLength))
) {
currentFile.isTooBig = true;
currentFile.addedLines = 0;
currentFile.deletedLines = 0;
currentFile.blocks = [];
currentBlock = null;
const message =
typeof config.diffTooBigMessage === 'function'
? config.diffTooBigMessage(files.length)
: 'Diff too big to be displayed';
startBlock(message);
return;
}
/*
* We need to make sure that we have the three lines of the header.
* This avoids cases like the ones described in:
* - https://github.com/rtfpessoa/diff2html/issues/87
*/
if (
(line.startsWith(oldFileNameHeader) && nxtLine.startsWith(newFileNameHeader)) ||
(line.startsWith(newFileNameHeader) && prevLine.startsWith(oldFileNameHeader))
) {
/*
* --- Date Timestamp[FractionalSeconds] TimeZone
* --- 2002-02-21 23:30:39.942229878 -0800
*/
if (
currentFile &&
!currentFile.oldName &&
line.startsWith('--- ') &&
(values = getSrcFilename(line, config.srcPrefix))
) {
currentFile.oldName = values;
currentFile.language = getExtension(currentFile.oldName, currentFile.language);
return;
}
/*
* +++ Date Timestamp[FractionalSeconds] TimeZone
* +++ 2002-02-21 23:30:39.942229878 -0800
*/
if (
currentFile &&
!currentFile.newName &&
line.startsWith('+++ ') &&
(values = getDstFilename(line, config.dstPrefix))
) {
currentFile.newName = values;
currentFile.language = getExtension(currentFile.newName, currentFile.language);
return;
}
}
if (
currentFile &&
(line.startsWith(hunkHeaderPrefix) ||
(currentFile.isGitDiff && currentFile.oldName && currentFile.newName && !currentBlock))
) {
startBlock(line);
return;
}
/*
* There are three types of diff lines. These lines are defined by the way they start.
* 1. New line starts with: +
* 2. Old line starts with: -
* 3. Context line starts with: <SPACE>
*/
if (currentBlock && (line.startsWith('+') || line.startsWith('-') || line.startsWith(' '))) {
createLine(line);
return;
}
const doesNotExistHunkHeader = !existHunkHeader(line, lineIndex);
if (currentFile === null) {
throw new Error('Where is my file !!!');
}
/*
* Git diffs provide more information regarding files modes, renames, copies,
* commits between changes and similarity indexes
*/
if ((values = oldMode.exec(line))) {
currentFile.oldMode = values[1];
} else if ((values = newMode.exec(line))) {
currentFile.newMode = values[1];
} else if ((values = deletedFileMode.exec(line))) {
currentFile.deletedFileMode = values[1];
currentFile.isDeleted = true;
} else if ((values = newFileMode.exec(line))) {
currentFile.newFileMode = values[1];
currentFile.isNew = true;
} else if ((values = copyFrom.exec(line))) {
if (doesNotExistHunkHeader) {
currentFile.oldName = values[1];
}
currentFile.isCopy = true;
} else if ((values = copyTo.exec(line))) {
if (doesNotExistHunkHeader) {
currentFile.newName = values[1];
}
currentFile.isCopy = true;
} else if ((values = renameFrom.exec(line))) {
if (doesNotExistHunkHeader) {
currentFile.oldName = values[1];
}
currentFile.isRename = true;
} else if ((values = renameTo.exec(line))) {
if (doesNotExistHunkHeader) {
currentFile.newName = values[1];
}
currentFile.isRename = true;
} else if ((values = binaryFiles.exec(line))) {
currentFile.isBinary = true;
currentFile.oldName = getFilename(values[1], undefined, config.srcPrefix);
currentFile.newName = getFilename(values[2], undefined, config.dstPrefix);
startBlock('Binary file');
} else if (binaryDiff.test(line)) {
currentFile.isBinary = true;
startBlock(line);
} else if ((values = similarityIndex.exec(line))) {
currentFile.unchangedPercentage = parseInt(values[1], 10);
} else if ((values = dissimilarityIndex.exec(line))) {
currentFile.changedPercentage = parseInt(values[1], 10);
} else if ((values = index.exec(line))) {
currentFile.checksumBefore = values[1];
currentFile.checksumAfter = values[2];
if (values[3]) currentFile.mode = values[3];
} else if ((values = combinedIndex.exec(line))) {
currentFile.checksumBefore = [values[2], values[3]];
currentFile.checksumAfter = values[1];
} else if ((values = combinedMode.exec(line))) {
currentFile.oldMode = [values[2], values[3]];
currentFile.newMode = values[1];
} else if ((values = combinedNewFile.exec(line))) {
currentFile.newFileMode = values[1];
currentFile.isNew = true;
} else if ((values = combinedDeletedFile.exec(line))) {
currentFile.deletedFileMode = values[1];
currentFile.isDeleted = true;
}
});
saveBlock();
saveFile();
return files;
}

View file

@ -1,46 +0,0 @@
import * as DiffParser from './diff-parser';
import { FileListRenderer } 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,
LineByLineRendererConfig,
SideBySideRendererConfig,
HoganJsUtilsConfig {
outputFormat?: OutputFormatType;
drawFileList?: boolean;
}
export const defaultDiff2HtmlConfig = {
...defaultLineByLineRendererConfig,
...defaultSideBySideRendererConfig,
outputFormat: OutputFormatType.LINE_BY_LINE,
drawFileList: true,
};
export function parse(diffInput: string, configuration: Diff2HtmlConfig = {}): DiffFile[] {
return DiffParser.parse(diffInput, { ...defaultDiff2HtmlConfig, ...configuration });
}
export function html(diffInput: string | DiffFile[], configuration: Diff2HtmlConfig = {}): string {
const config = { ...defaultDiff2HtmlConfig, ...configuration };
const diffJson = typeof diffInput === 'string' ? DiffParser.parse(diffInput, config) : diffInput;
const hoganUtils = new HoganJsUtils(config);
const { colorScheme } = config;
const fileListRendererConfig = { colorScheme };
const fileList = config.drawFileList ? new FileListRenderer(hoganUtils, fileListRendererConfig).render(diffJson) : '';
const diffOutput =
config.outputFormat === 'side-by-side'
? new SideBySideRenderer(hoganUtils, config).render(diffJson)
: new LineByLineRenderer(hoganUtils, config).render(diffJson);
return fileList + diffOutput;
}

View file

@ -1,52 +0,0 @@
import * as renderUtils from './render-utils';
import HoganJsUtils from './hoganjs-utils';
import { ColorSchemeType, DiffFile } from './types';
const baseTemplatesPath = 'file-summary';
const iconsBaseTemplatesPath = 'icon';
export interface FileListRendererConfig {
colorScheme?: ColorSchemeType;
}
export const defaultFileListRendererConfig = {
colorScheme: renderUtils.defaultRenderConfig.colorScheme,
};
export class FileListRenderer {
private readonly hoganUtils: HoganJsUtils;
private readonly config: typeof defaultFileListRendererConfig;
constructor(hoganUtils: HoganJsUtils, config: FileListRendererConfig = {}) {
this.hoganUtils = hoganUtils;
this.config = { ...defaultFileListRendererConfig, ...config };
}
render(diffFiles: DiffFile[]): string {
const files = diffFiles
.map(file =>
this.hoganUtils.render(
baseTemplatesPath,
'line',
{
fileHtmlId: renderUtils.getHtmlId(file),
oldName: file.oldName,
newName: file.newName,
fileName: renderUtils.filenameDiff(file),
deletedLines: '-' + file.deletedLines,
addedLines: '+' + file.addedLines,
},
{
fileIcon: this.hoganUtils.template(iconsBaseTemplatesPath, renderUtils.getFileIcon(file)),
},
),
)
.join('\n');
return this.hoganUtils.render(baseTemplatesPath, 'wrapper', {
colorScheme: renderUtils.colorSchemeToCss(this.config.colorScheme),
filesNumber: diffFiles.length,
files: files,
});
}
}

View file

@ -1,54 +0,0 @@
import * as Hogan from 'hogan.js';
import { defaultTemplates } from './diff2html-templates';
export interface RawTemplates {
[name: string]: string;
}
export interface CompiledTemplates {
[name: string]: Hogan.Template;
}
export interface HoganJsUtilsConfig {
compiledTemplates?: CompiledTemplates;
rawTemplates?: RawTemplates;
}
export default class HoganJsUtils {
private preCompiledTemplates: CompiledTemplates;
constructor({ compiledTemplates = {}, rawTemplates = {} }: HoganJsUtilsConfig) {
const compiledRawTemplates = Object.entries(rawTemplates).reduce<CompiledTemplates>(
(previousTemplates, [name, templateString]) => {
const compiledTemplate: Hogan.Template = Hogan.compile(templateString, { asString: false });
return { ...previousTemplates, [name]: compiledTemplate };
},
{},
);
this.preCompiledTemplates = { ...defaultTemplates, ...compiledTemplates, ...compiledRawTemplates };
}
static compile(templateString: string): Hogan.Template {
return Hogan.compile(templateString, { asString: false });
}
render(namespace: string, view: string, params: Hogan.Context, partials?: Hogan.Partials, indent?: string): string {
const templateKey = this.templateKey(namespace, view);
try {
const template = this.preCompiledTemplates[templateKey];
return template.render(params, partials, indent);
} catch (_e) {
throw new Error(`Could not find template to render '${templateKey}'`);
}
}
template(namespace: string, view: string): Hogan.Template {
return this.preCompiledTemplates[this.templateKey(namespace, view)];
}
private templateKey(namespace: string, view: string): string {
return `${namespace}-${view}`;
}
}

View file

@ -1,299 +0,0 @@
import HoganJsUtils from './hoganjs-utils';
import * as Rematch from './rematch';
import * as renderUtils from './render-utils';
import {
DiffFile,
DiffLine,
LineType,
DiffBlock,
DiffLineDeleted,
DiffLineContent,
DiffLineContext,
DiffLineInserted,
} from './types';
export interface LineByLineRendererConfig extends renderUtils.RenderConfig {
renderNothingWhenEmpty?: boolean;
matchingMaxComparisons?: number;
maxLineSizeInBlockForComparison?: number;
}
export const defaultLineByLineRendererConfig = {
...renderUtils.defaultRenderConfig,
renderNothingWhenEmpty: false,
matchingMaxComparisons: 2500,
maxLineSizeInBlockForComparison: 200,
};
const genericTemplatesPath = 'generic';
const baseTemplatesPath = 'line-by-line';
const iconsBaseTemplatesPath = 'icon';
const tagsBaseTemplatesPath = 'tag';
export default class LineByLineRenderer {
private readonly hoganUtils: HoganJsUtils;
private readonly config: typeof defaultLineByLineRendererConfig;
constructor(hoganUtils: HoganJsUtils, config: LineByLineRendererConfig = {}) {
this.hoganUtils = hoganUtils;
this.config = { ...defaultLineByLineRendererConfig, ...config };
}
render(diffFiles: DiffFile[]): string {
const diffsHtml = diffFiles
.map(file => {
let diffs;
if (file.blocks.length) {
diffs = this.generateFileHtml(file);
} else {
diffs = this.generateEmptyDiff();
}
return this.makeFileDiffHtml(file, diffs);
})
.join('\n');
return this.hoganUtils.render(genericTemplatesPath, 'wrapper', {
colorScheme: renderUtils.colorSchemeToCss(this.config.colorScheme),
content: diffsHtml,
});
}
makeFileDiffHtml(file: DiffFile, diffs: string): string {
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 fileTagTemplate = this.hoganUtils.template(tagsBaseTemplatesPath, renderUtils.getFileIcon(file));
return fileDiffTemplate.render({
file: file,
fileHtmlId: renderUtils.getHtmlId(file),
diffs: diffs,
filePath: filePathTemplate.render(
{
fileDiffName: renderUtils.filenameDiff(file),
},
{
fileIcon: fileIconTemplate,
fileTag: fileTagTemplate,
},
),
});
}
generateEmptyDiff(): string {
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),
);
return file.blocks
.map(block => {
let lines = this.hoganUtils.render(genericTemplatesPath, 'block-header', {
CSSLineClass: renderUtils.CSSLineClass,
blockHeader: file.isTooBig ? block.header : renderUtils.escapeForHtml(block.header),
lineClass: 'd2h-code-linenumber',
contentClass: 'd2h-code-line',
});
this.applyLineGroupping(block).forEach(([contextLines, oldLines, newLines]) => {
if (oldLines.length && newLines.length && !contextLines.length) {
this.applyRematchMatching(oldLines, newLines, matcher).map(([oldLines, newLines]) => {
const { left, right } = this.processChangedLines(file, file.isCombined, oldLines, newLines);
lines += left;
lines += right;
});
} else if (contextLines.length) {
contextLines.forEach(line => {
const { prefix, content } = renderUtils.deconstructLine(line.content, file.isCombined);
lines += this.generateSingleLineHtml(file, {
type: renderUtils.CSSLineClass.CONTEXT,
prefix: prefix,
content: content,
oldNumber: line.oldNumber,
newNumber: line.newNumber,
});
});
} else if (oldLines.length || newLines.length) {
const { left, right } = this.processChangedLines(file, file.isCombined, oldLines, newLines);
lines += left;
lines += right;
} else {
console.error('Unknown state reached while processing groups of lines', contextLines, oldLines, newLines);
}
});
return lines;
})
.join('\n');
}
applyLineGroupping(block: DiffBlock): DiffLineGroups {
const blockLinesGroups: DiffLineGroups = [];
let oldLines: (DiffLineDeleted & DiffLineContent)[] = [];
let newLines: (DiffLineInserted & DiffLineContent)[] = [];
for (let i = 0; i < block.lines.length; i++) {
const diffLine = block.lines[i];
if (
(diffLine.type !== LineType.INSERT && newLines.length) ||
(diffLine.type === LineType.CONTEXT && oldLines.length > 0)
) {
blockLinesGroups.push([[], oldLines, newLines]);
oldLines = [];
newLines = [];
}
if (diffLine.type === LineType.CONTEXT) {
blockLinesGroups.push([[diffLine], [], []]);
} else if (diffLine.type === LineType.INSERT && oldLines.length === 0) {
blockLinesGroups.push([[], [], [diffLine]]);
} else if (diffLine.type === LineType.INSERT && oldLines.length > 0) {
newLines.push(diffLine);
} else if (diffLine.type === LineType.DELETE) {
oldLines.push(diffLine);
}
}
if (oldLines.length || newLines.length) {
blockLinesGroups.push([[], oldLines, newLines]);
oldLines = [];
newLines = [];
}
return blockLinesGroups;
}
applyRematchMatching(
oldLines: DiffLine[],
newLines: 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)),
);
const doMatching =
comparisons < this.config.matchingMaxComparisons &&
maxLineSizeInBlock < this.config.maxLineSizeInBlockForComparison &&
(this.config.matching === 'lines' || this.config.matching === 'words');
return doMatching ? matcher(oldLines, newLines) : [[oldLines, newLines]];
}
processChangedLines(file: DiffFile, isCombined: boolean, oldLines: DiffLine[], newLines: DiffLine[]): FileHtml {
const fileHtml = {
right: '',
left: '',
};
const maxLinesNumber = Math.max(oldLines.length, newLines.length);
for (let i = 0; i < maxLinesNumber; i++) {
const oldLine = oldLines[i];
const newLine = newLines[i];
const diff =
oldLine !== undefined && newLine !== undefined
? renderUtils.diffHighlight(oldLine.content, newLine.content, isCombined, this.config)
: undefined;
const preparedOldLine =
oldLine !== undefined && oldLine.oldNumber !== undefined
? {
...(diff !== undefined
? {
prefix: diff.oldLine.prefix,
content: diff.oldLine.content,
type: renderUtils.CSSLineClass.DELETE_CHANGES,
}
: {
...renderUtils.deconstructLine(oldLine.content, isCombined),
type: renderUtils.toCSSClass(oldLine.type),
}),
oldNumber: oldLine.oldNumber,
newNumber: oldLine.newNumber,
}
: undefined;
const preparedNewLine =
newLine !== undefined && newLine.newNumber !== undefined
? {
...(diff !== undefined
? {
prefix: diff.newLine.prefix,
content: diff.newLine.content,
type: renderUtils.CSSLineClass.INSERT_CHANGES,
}
: {
...renderUtils.deconstructLine(newLine.content, isCombined),
type: renderUtils.toCSSClass(newLine.type),
}),
oldNumber: newLine.oldNumber,
newNumber: newLine.newNumber,
}
: undefined;
const { left, right } = this.generateLineHtml(file, preparedOldLine, preparedNewLine);
fileHtml.left += left;
fileHtml.right += right;
}
return fileHtml;
}
generateLineHtml(file: DiffFile, oldLine?: DiffPreparedLine, newLine?: DiffPreparedLine): FileHtml {
return {
left: this.generateSingleLineHtml(file, oldLine),
right: this.generateSingleLineHtml(file, newLine),
};
}
generateSingleLineHtml(file: DiffFile, line?: DiffPreparedLine): string {
if (line === undefined) return '';
const lineNumberHtml = this.hoganUtils.render(baseTemplatesPath, 'numbers', {
oldNumber: line.oldNumber || '',
newNumber: line.newNumber || '',
});
return this.hoganUtils.render(genericTemplatesPath, 'line', {
type: line.type,
lineClass: 'd2h-code-linenumber',
contentClass: 'd2h-code-line',
prefix: line.prefix === ' ' ? '&nbsp;' : line.prefix,
content: line.content,
lineNumber: lineNumberHtml,
line,
file,
});
}
}
type DiffLineGroups = [
(DiffLineContext & DiffLineContent)[],
(DiffLineDeleted & DiffLineContent)[],
(DiffLineInserted & DiffLineContent)[],
][];
type DiffPreparedLine = {
type: renderUtils.CSSLineClass;
prefix: string;
content: string;
oldNumber?: number;
newNumber?: number;
};
type FileHtml = {
left: string;
right: string;
};

View file

@ -1,135 +0,0 @@
/*
* Matching two sequences of objects by similarity
* Author: W. Illmeyer, Nexxar GmbH
*/
export type BestMatch = {
indexA: number;
indexB: number;
score: number;
};
/*
Copyright (c) 2011 Andrei Mackenzie
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
export function levenshtein(a: string, b: string): number {
if (a.length === 0) {
return b.length;
}
if (b.length === 0) {
return a.length;
}
const matrix = [];
// Increment along the first column of each row
let i;
for (i = 0; i <= b.length; i++) {
matrix[i] = [i];
}
// Increment each column in the first row
let j;
for (j = 0; j <= a.length; j++) {
matrix[0][j] = j;
}
// Fill in the rest of the matrix
for (i = 1; i <= b.length; i++) {
for (j = 1; j <= a.length; j++) {
if (b.charAt(i - 1) === a.charAt(j - 1)) {
matrix[i][j] = matrix[i - 1][j - 1];
} else {
matrix[i][j] = Math.min(
matrix[i - 1][j - 1] + 1, // Substitution
Math.min(
matrix[i][j - 1] + 1, // Insertion
matrix[i - 1][j] + 1,
),
); // Deletion
}
}
}
return matrix[b.length][a.length];
}
export type DistanceFn<T> = (x: T, y: T) => number;
export function newDistanceFn<T>(str: (value: T) => string): DistanceFn<T> {
return (x: T, y: T): number => {
const xValue = str(x).trim();
const yValue = str(y).trim();
const lev = levenshtein(xValue, yValue);
return lev / (xValue.length + yValue.length);
};
}
export type MatcherFn<T> = (a: T[], b: T[], level?: number, cache?: Map<string, number>) => T[][][];
export function newMatcherFn<T>(distance: (x: T, y: T) => number): MatcherFn<T> {
function findBestMatch(a: T[], b: T[], cache: Map<string, number> = new Map()): BestMatch | undefined {
let bestMatchDist = Infinity;
let bestMatch;
for (let i = 0; i < a.length; ++i) {
for (let j = 0; j < b.length; ++j) {
const cacheKey = JSON.stringify([a[i], b[j]]);
let md;
if (!(cache.has(cacheKey) && (md = cache.get(cacheKey)))) {
md = distance(a[i], b[j]);
cache.set(cacheKey, md);
}
if (md < bestMatchDist) {
bestMatchDist = md;
bestMatch = { indexA: i, indexB: j, score: bestMatchDist };
}
}
}
return bestMatch;
}
function group(a: T[], b: T[], level = 0, cache: Map<string, number> = new Map()): T[][][] {
const bm = findBestMatch(a, b, cache);
if (!bm || a.length + b.length < 3) {
return [[a, b]];
}
const a1 = a.slice(0, bm.indexA);
const b1 = b.slice(0, bm.indexB);
const aMatch = [a[bm.indexA]];
const bMatch = [b[bm.indexB]];
const tailA = bm.indexA + 1;
const tailB = bm.indexB + 1;
const a2 = a.slice(tailA);
const b2 = b.slice(tailB);
const group1 = group(a1, b1, level + 1, cache);
const groupMatch = group(aMatch, bMatch, level + 1, cache);
const group2 = group(a2, b2, level + 1, cache);
let result = groupMatch;
if (bm.indexA > 0 || bm.indexB > 0) {
result = group1.concat(result);
}
if (a.length > tailA || b.length > tailB) {
result = result.concat(group2);
}
return result;
}
return group;
}

View file

@ -1,304 +0,0 @@
import * as jsDiff from 'diff';
import { unifyPath, hashCode } from './utils';
import * as rematch from './rematch';
import {
ColorSchemeType,
DiffFile,
DiffFileName,
DiffLineParts,
DiffStyleType,
LineMatchingType,
LineType,
} from './types';
export type CSSLineClass =
| 'd2h-ins'
| 'd2h-del'
| 'd2h-cntx'
| 'd2h-info'
| 'd2h-ins d2h-change'
| 'd2h-del d2h-change';
export const CSSLineClass: { [_: string]: 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',
};
export type HighlightedLines = {
oldLine: {
prefix: string;
content: string;
};
newLine: {
prefix: string;
content: string;
};
};
export interface RenderConfig {
matching?: LineMatchingType;
matchWordsThreshold?: number;
maxLineLengthHighlight?: number;
diffStyle?: DiffStyleType;
colorScheme?: ColorSchemeType;
}
export const defaultRenderConfig = {
matching: LineMatchingType.NONE,
matchWordsThreshold: 0.25,
maxLineLengthHighlight: 10000,
diffStyle: DiffStyleType.WORD,
colorScheme: ColorSchemeType.LIGHT,
};
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;
}
function removeInsElements(line: string): string {
return line.replace(/(<ins[^>]*>((.|\n)*?)<\/ins>)/g, '');
}
function removeDelElements(line: string): string {
return line.replace(/(<del[^>]*>((.|\n)*?)<\/del>)/g, '');
}
/**
* Convert from LineType to CSSLineClass
*/
export function toCSSClass(lineType: LineType): CSSLineClass {
switch (lineType) {
case LineType.CONTEXT:
return CSSLineClass.CONTEXT;
case LineType.INSERT:
return CSSLineClass.INSERTS;
case LineType.DELETE:
return CSSLineClass.DELETES;
}
}
export function colorSchemeToCss(colorScheme: ColorSchemeType): string {
switch (colorScheme) {
case ColorSchemeType.DARK:
return 'd2h-dark-color-scheme';
case ColorSchemeType.AUTO:
return 'd2h-auto-color-scheme';
case ColorSchemeType.LIGHT:
default:
return 'd2h-light-color-scheme';
}
}
/**
* Prefix length of the hunk lines in the diff
*/
function prefixLength(isCombined: boolean): number {
return isCombined ? 2 : 1;
}
/**
* Escapes all required characters for safe HTML rendering
*/
// TODO: Test this method inside deconstructLine since it should not be used anywhere else
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;');
}
/**
* Deconstructs diff @line by separating the content from the prefix type
*/
export function deconstructLine(line: string, isCombined: boolean, escape = true): DiffLineParts {
const indexToSplit = prefixLength(isCombined);
return {
prefix: line.substring(0, indexToSplit),
content: escape ? escapeForHtml(line.substring(indexToSplit)) : line.substring(indexToSplit),
};
}
/**
* Generates pretty filename diffs
*
* e.g.:
* 1. file = { oldName: "my/path/to/file.js", newName: "my/path/to/new-file.js" }
* returns "my/path/to/{file.js → new-file.js}"
* 2. file = { oldName: "my/path/to/file.js", newName: "very/new/path/to/new-file.js" }
* returns "my/path/to/file.js → very/new/path/to/new-file.js"
* 3. file = { oldName: "my/path/to/file.js", newName: "my/path/for/file.js" }
* returns "my/path/{to → for}/file.js"
*/
export function filenameDiff(file: DiffFileName): string {
// TODO: Move unify path to parsing
const oldFilename = unifyPath(file.oldName);
const newFilename = unifyPath(file.newName);
if (oldFilename !== newFilename && !isDevNullName(oldFilename) && !isDevNullName(newFilename)) {
const prefixPaths = [];
const suffixPaths = [];
const oldFilenameParts = oldFilename.split(separator);
const newFilenameParts = newFilename.split(separator);
const oldFilenamePartsSize = oldFilenameParts.length;
const newFilenamePartsSize = newFilenameParts.length;
let i = 0;
let j = oldFilenamePartsSize - 1;
let k = newFilenamePartsSize - 1;
while (i < j && i < k) {
if (oldFilenameParts[i] === newFilenameParts[i]) {
prefixPaths.push(newFilenameParts[i]);
i += 1;
} else {
break;
}
}
while (j > i && k > i) {
if (oldFilenameParts[j] === newFilenameParts[k]) {
suffixPaths.unshift(newFilenameParts[k]);
j -= 1;
k -= 1;
} else {
break;
}
}
const finalPrefix = prefixPaths.join(separator);
const finalSuffix = suffixPaths.join(separator);
const oldRemainingPath = oldFilenameParts.slice(i, j + 1).join(separator);
const newRemainingPath = newFilenameParts.slice(i, k + 1).join(separator);
if (finalPrefix.length && finalSuffix.length) {
return (
finalPrefix + separator + '{' + oldRemainingPath + ' → ' + newRemainingPath + '}' + separator + finalSuffix
);
} else if (finalPrefix.length) {
return finalPrefix + separator + '{' + oldRemainingPath + ' → ' + newRemainingPath + '}';
} else if (finalSuffix.length) {
return '{' + oldRemainingPath + ' → ' + newRemainingPath + '}' + separator + finalSuffix;
}
return oldFilename + ' → ' + newFilename;
} else if (!isDevNullName(newFilename)) {
return newFilename;
} else {
return oldFilename;
}
}
/**
* Generates a unique string numerical identifier based on the names of the file diff
*/
export function getHtmlId(file: DiffFileName): string {
return `d2h-${hashCode(filenameDiff(file)).toString().slice(-6)}`;
}
/**
* Selects the correct icon name for the file
*/
export function getFileIcon(file: DiffFile): string {
let templateName = 'file-changed';
if (file.isRename) {
templateName = 'file-renamed';
} else if (file.isCopy) {
templateName = 'file-renamed';
} else if (file.isNew) {
templateName = 'file-added';
} else if (file.isDeleted) {
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';
}
return templateName;
}
/**
* Highlight differences between @diffLine1 and @diffLine2 using <ins> and <del> tags
*/
export function diffHighlight(
diffLine1: string,
diffLine2: string,
isCombined: boolean,
config: RenderConfig = {},
): HighlightedLines {
const { matching, maxLineLengthHighlight, matchWordsThreshold, diffStyle } = { ...defaultRenderConfig, ...config };
const line1 = deconstructLine(diffLine1, isCombined, false);
const line2 = deconstructLine(diffLine2, isCombined, false);
if (line1.content.length > maxLineLengthHighlight || line2.content.length > maxLineLengthHighlight) {
return {
oldLine: {
prefix: line1.prefix,
content: escapeForHtml(line1.content),
},
newLine: {
prefix: line2.prefix,
content: escapeForHtml(line2.content),
},
};
}
const diff =
diffStyle === 'char'
? jsDiff.diffChars(line1.content, line2.content)
: jsDiff.diffWordsWithSpace(line1.content, line2.content);
const changedWords: jsDiff.Change[] = [];
if (diffStyle === 'word' && matching === 'words') {
const removed = diff.filter(element => element.removed);
const added = diff.filter(element => element.added);
const chunks = matcher(added, removed);
chunks.forEach(chunk => {
if (chunk[0].length === 1 && chunk[1].length === 1) {
const dist = distance(chunk[0][0], chunk[1][0]);
if (dist < matchWordsThreshold) {
changedWords.push(chunk[0][0]);
changedWords.push(chunk[1][0]);
}
}
});
}
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 escapedValue = escapeForHtml(part.value);
return elemType !== null
? `${highlightedLine}<${elemType}${addClass}>${escapedValue}</${elemType}>`
: `${highlightedLine}${escapedValue}`;
}, '');
return {
oldLine: {
prefix: line1.prefix,
content: removeInsElements(highlightedLine),
},
newLine: {
prefix: line2.prefix,
content: removeDelElements(highlightedLine),
},
};
}

View file

@ -1,314 +0,0 @@
import HoganJsUtils from './hoganjs-utils';
import * as Rematch from './rematch';
import * as renderUtils from './render-utils';
import {
DiffLine,
LineType,
DiffFile,
DiffBlock,
DiffLineContext,
DiffLineDeleted,
DiffLineInserted,
DiffLineContent,
} from './types';
export interface SideBySideRendererConfig extends renderUtils.RenderConfig {
renderNothingWhenEmpty?: boolean;
matchingMaxComparisons?: number;
maxLineSizeInBlockForComparison?: number;
}
export const defaultSideBySideRendererConfig = {
...renderUtils.defaultRenderConfig,
renderNothingWhenEmpty: false,
matchingMaxComparisons: 2500,
maxLineSizeInBlockForComparison: 200,
};
const genericTemplatesPath = 'generic';
const baseTemplatesPath = 'side-by-side';
const iconsBaseTemplatesPath = 'icon';
const tagsBaseTemplatesPath = 'tag';
export default class SideBySideRenderer {
private readonly hoganUtils: HoganJsUtils;
private readonly config: typeof defaultSideBySideRendererConfig;
constructor(hoganUtils: HoganJsUtils, config: SideBySideRendererConfig = {}) {
this.hoganUtils = hoganUtils;
this.config = { ...defaultSideBySideRendererConfig, ...config };
}
render(diffFiles: DiffFile[]): string {
const diffsHtml = diffFiles
.map(file => {
let diffs;
if (file.blocks.length) {
diffs = this.generateFileHtml(file);
} else {
diffs = this.generateEmptyDiff();
}
return this.makeFileDiffHtml(file, diffs);
})
.join('\n');
return this.hoganUtils.render(genericTemplatesPath, 'wrapper', {
colorScheme: renderUtils.colorSchemeToCss(this.config.colorScheme),
content: diffsHtml,
});
}
makeFileDiffHtml(file: DiffFile, diffs: FileHtml): string {
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 fileTagTemplate = this.hoganUtils.template(tagsBaseTemplatesPath, renderUtils.getFileIcon(file));
return fileDiffTemplate.render({
file: file,
fileHtmlId: renderUtils.getHtmlId(file),
diffs: diffs,
filePath: filePathTemplate.render(
{
fileDiffName: renderUtils.filenameDiff(file),
},
{
fileIcon: fileIconTemplate,
fileTag: fileTagTemplate,
},
),
});
}
generateEmptyDiff(): FileHtml {
return {
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),
);
return file.blocks
.map(block => {
const fileHtml = {
left: this.makeHeaderHtml(block.header, file),
right: this.makeHeaderHtml(''),
};
this.applyLineGroupping(block).forEach(([contextLines, oldLines, newLines]) => {
if (oldLines.length && newLines.length && !contextLines.length) {
this.applyRematchMatching(oldLines, newLines, matcher).map(([oldLines, newLines]) => {
const { left, right } = this.processChangedLines(file.isCombined, oldLines, newLines);
fileHtml.left += left;
fileHtml.right += right;
});
} else if (contextLines.length) {
contextLines.forEach(line => {
const { prefix, content } = renderUtils.deconstructLine(line.content, file.isCombined);
const { left, right } = this.generateLineHtml(
{
type: renderUtils.CSSLineClass.CONTEXT,
prefix: prefix,
content: content,
number: line.oldNumber,
},
{
type: renderUtils.CSSLineClass.CONTEXT,
prefix: prefix,
content: content,
number: line.newNumber,
},
);
fileHtml.left += left;
fileHtml.right += right;
});
} else if (oldLines.length || newLines.length) {
const { left, right } = this.processChangedLines(file.isCombined, oldLines, newLines);
fileHtml.left += left;
fileHtml.right += right;
} else {
console.error('Unknown state reached while processing groups of lines', contextLines, oldLines, newLines);
}
});
return fileHtml;
})
.reduce(
(accomulated, html) => {
return { left: accomulated.left + html.left, right: accomulated.right + html.right };
},
{ left: '', right: '' },
);
}
applyLineGroupping(block: DiffBlock): DiffLineGroups {
const blockLinesGroups: DiffLineGroups = [];
let oldLines: (DiffLineDeleted & DiffLineContent)[] = [];
let newLines: (DiffLineInserted & DiffLineContent)[] = [];
for (let i = 0; i < block.lines.length; i++) {
const diffLine = block.lines[i];
if (
(diffLine.type !== LineType.INSERT && newLines.length) ||
(diffLine.type === LineType.CONTEXT && oldLines.length > 0)
) {
blockLinesGroups.push([[], oldLines, newLines]);
oldLines = [];
newLines = [];
}
if (diffLine.type === LineType.CONTEXT) {
blockLinesGroups.push([[diffLine], [], []]);
} else if (diffLine.type === LineType.INSERT && oldLines.length === 0) {
blockLinesGroups.push([[], [], [diffLine]]);
} else if (diffLine.type === LineType.INSERT && oldLines.length > 0) {
newLines.push(diffLine);
} else if (diffLine.type === LineType.DELETE) {
oldLines.push(diffLine);
}
}
if (oldLines.length || newLines.length) {
blockLinesGroups.push([[], oldLines, newLines]);
oldLines = [];
newLines = [];
}
return blockLinesGroups;
}
applyRematchMatching(
oldLines: DiffLine[],
newLines: 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)),
);
const doMatching =
comparisons < this.config.matchingMaxComparisons &&
maxLineSizeInBlock < this.config.maxLineSizeInBlockForComparison &&
(this.config.matching === 'lines' || this.config.matching === 'words');
return doMatching ? matcher(oldLines, newLines) : [[oldLines, newLines]];
}
makeHeaderHtml(blockHeader: string, file?: DiffFile): string {
return this.hoganUtils.render(genericTemplatesPath, 'block-header', {
CSSLineClass: renderUtils.CSSLineClass,
blockHeader: file?.isTooBig ? blockHeader : renderUtils.escapeForHtml(blockHeader),
lineClass: 'd2h-code-side-linenumber',
contentClass: 'd2h-code-side-line',
});
}
processChangedLines(isCombined: boolean, oldLines: DiffLine[], newLines: DiffLine[]): FileHtml {
const fileHtml = {
right: '',
left: '',
};
const maxLinesNumber = Math.max(oldLines.length, newLines.length);
for (let i = 0; i < maxLinesNumber; i++) {
const oldLine = oldLines[i];
const newLine = newLines[i];
const diff =
oldLine !== undefined && newLine !== undefined
? renderUtils.diffHighlight(oldLine.content, newLine.content, isCombined, this.config)
: undefined;
const preparedOldLine =
oldLine !== undefined && oldLine.oldNumber !== undefined
? {
...(diff !== undefined
? {
prefix: diff.oldLine.prefix,
content: diff.oldLine.content,
type: renderUtils.CSSLineClass.DELETE_CHANGES,
}
: {
...renderUtils.deconstructLine(oldLine.content, isCombined),
type: renderUtils.toCSSClass(oldLine.type),
}),
number: oldLine.oldNumber,
}
: undefined;
const preparedNewLine =
newLine !== undefined && newLine.newNumber !== undefined
? {
...(diff !== undefined
? {
prefix: diff.newLine.prefix,
content: diff.newLine.content,
type: renderUtils.CSSLineClass.INSERT_CHANGES,
}
: {
...renderUtils.deconstructLine(newLine.content, isCombined),
type: renderUtils.toCSSClass(newLine.type),
}),
number: newLine.newNumber,
}
: undefined;
const { left, right } = this.generateLineHtml(preparedOldLine, preparedNewLine);
fileHtml.left += left;
fileHtml.right += right;
}
return fileHtml;
}
generateLineHtml(oldLine?: DiffPreparedLine, newLine?: DiffPreparedLine): FileHtml {
return {
left: this.generateSingleHtml(oldLine),
right: this.generateSingleHtml(newLine),
};
}
generateSingleHtml(line?: DiffPreparedLine): string {
const lineClass = 'd2h-code-side-linenumber';
const contentClass = 'd2h-code-side-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,
content: line?.content,
lineNumber: line?.number,
});
}
}
type DiffLineGroups = [
(DiffLineContext & DiffLineContent)[],
(DiffLineDeleted & DiffLineContent)[],
(DiffLineInserted & DiffLineContent)[],
][];
type DiffPreparedLine = {
type: renderUtils.CSSLineClass;
prefix: string;
content: string;
number: number;
};
type FileHtml = {
left: string;
right: string;
};

View file

@ -1,10 +0,0 @@
<li class="d2h-file-list-line">
<span class="d2h-file-name-wrapper">
{{>fileIcon}}
<a href="#{{fileHtmlId}}" class="d2h-file-name">{{fileName}}</a>
<span class="d2h-file-stats">
<span class="d2h-lines-added">{{addedLines}}</span>
<span class="d2h-lines-deleted">{{deletedLines}}</span>
</span>
</span>
</li>

View file

@ -1,10 +0,0 @@
<div class="d2h-file-list-wrapper {{colorScheme}}">
<div class="d2h-file-list-header">
<span class="d2h-file-list-title">Files changed ({{filesNumber}})</span>
<a class="d2h-file-switch d2h-hide">hide</a>
<a class="d2h-file-switch d2h-show">show</a>
</div>
<ol class="d2h-file-list">
{{{files}}}
</ol>
</div>

View file

@ -1,6 +0,0 @@
<tr>
<td class="{{lineClass}} {{CSSLineClass.INFO}}"></td>
<td class="{{CSSLineClass.INFO}}">
<div class="{{contentClass}}">{{#blockHeader}}{{{blockHeader}}}{{/blockHeader}}{{^blockHeader}}&nbsp;{{/blockHeader}}</div>
</td>
</tr>

View file

@ -1,7 +0,0 @@
<tr>
<td class="{{CSSLineClass.INFO}}">
<div class="{{contentClass}}">
File without changes
</div>
</td>
</tr>

View file

@ -1,9 +0,0 @@
<span class="d2h-file-name-wrapper">
{{>fileIcon}}
<span class="d2h-file-name">{{fileDiffName}}</span>
{{>fileTag}}
</span>
<label class="d2h-file-collapse">
<input class="d2h-file-collapse-input" type="checkbox" name="viewed" value="viewed">
Viewed
</label>

View file

@ -1,21 +0,0 @@
<tr>
<td class="{{lineClass}} {{type}}">
{{{lineNumber}}}
</td>
<td class="{{type}}">
<div class="{{contentClass}}">
{{#prefix}}
<span class="d2h-code-line-prefix">{{{prefix}}}</span>
{{/prefix}}
{{^prefix}}
<span class="d2h-code-line-prefix">&nbsp;</span>
{{/prefix}}
{{#content}}
<span class="d2h-code-line-ctn">{{{content}}}</span>
{{/content}}
{{^content}}
<span class="d2h-code-line-ctn"><br></span>
{{/content}}
</div>
</td>
</tr>

View file

@ -1,3 +0,0 @@
<div class="d2h-wrapper {{colorScheme}}">
{{{content}}}
</div>

View file

@ -1,4 +0,0 @@
<svg aria-hidden="true" class="d2h-icon d2h-added" height="16" title="added" version="1.1" viewBox="0 0 14 16"
width="14">
<path d="M13 1H1C0.45 1 0 1.45 0 2v12c0 0.55 0.45 1 1 1h12c0.55 0 1-0.45 1-1V2c0-0.55-0.45-1-1-1z m0 13H1V2h12v12zM6 9H3V7h3V4h2v3h3v2H8v3H6V9z"></path>
</svg>

Before

Width:  |  Height:  |  Size: 292 B

View file

@ -1,4 +0,0 @@
<svg aria-hidden="true" class="d2h-icon d2h-changed" height="16" title="modified" version="1.1"
viewBox="0 0 14 16" width="14">
<path d="M13 1H1C0.45 1 0 1.45 0 2v12c0 0.55 0.45 1 1 1h12c0.55 0 1-0.45 1-1V2c0-0.55-0.45-1-1-1z m0 13H1V2h12v12zM4 8c0-1.66 1.34-3 3-3s3 1.34 3 3-1.34 3-3 3-3-1.34-3-3z"></path>
</svg>

Before

Width:  |  Height:  |  Size: 324 B

View file

@ -1,4 +0,0 @@
<svg aria-hidden="true" class="d2h-icon d2h-deleted" height="16" title="removed" version="1.1"
viewBox="0 0 14 16" width="14">
<path d="M13 1H1C0.45 1 0 1.45 0 2v12c0 0.55 0.45 1 1 1h12c0.55 0 1-0.45 1-1V2c0-0.55-0.45-1-1-1z m0 13H1V2h12v12zM11 9H3V7h8v2z"></path>
</svg>

Before

Width:  |  Height:  |  Size: 281 B

View file

@ -1,4 +0,0 @@
<svg aria-hidden="true" class="d2h-icon d2h-moved" height="16" title="renamed" version="1.1"
viewBox="0 0 14 16" width="14">
<path d="M6 9H3V7h3V4l5 4-5 4V9z m8-7v12c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1V2c0-0.55 0.45-1 1-1h12c0.55 0 1 0.45 1 1z m-1 0H1v12h12V2z"></path>
</svg>

Before

Width:  |  Height:  |  Size: 289 B

View file

@ -1,3 +0,0 @@
<svg aria-hidden="true" class="d2h-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12">
<path d="M6 5H2v-1h4v1zM2 8h7v-1H2v1z m0 2h7v-1H2v1z m0 2h7v-1H2v1z m10-7.5v9.5c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1V2c0-0.55 0.45-1 1-1h7.5l3.5 3.5z m-1 0.5L8 2H1v12h10V5z"></path>
</svg>

Before

Width:  |  Height:  |  Size: 294 B

View file

@ -1,14 +0,0 @@
<div id="{{fileHtmlId}}" class="d2h-file-wrapper" data-lang="{{file.language}}">
<div class="d2h-file-header">
{{{filePath}}}
</div>
<div class="d2h-file-diff">
<div class="d2h-code-wrapper">
<table class="d2h-diff-table">
<tbody class="d2h-diff-tbody">
{{{diffs}}}
</tbody>
</table>
</div>
</div>
</div>

View file

@ -1,2 +0,0 @@
<div class="line-num1">{{oldNumber}}</div>
<div class="line-num2">{{newNumber}}</div>

View file

@ -1,25 +0,0 @@
<div id="{{fileHtmlId}}" class="d2h-file-wrapper" data-lang="{{file.language}}">
<div class="d2h-file-header">
{{{filePath}}}
</div>
<div class="d2h-files-diff">
<div class="d2h-file-side-diff">
<div class="d2h-code-wrapper">
<table class="d2h-diff-table">
<tbody class="d2h-diff-tbody">
{{{diffs.left}}}
</tbody>
</table>
</div>
</div>
<div class="d2h-file-side-diff">
<div class="d2h-code-wrapper">
<table class="d2h-diff-table">
<tbody class="d2h-diff-tbody">
{{{diffs.right}}}
</tbody>
</table>
</div>
</div>
</div>
</div>

View file

@ -1 +0,0 @@
<span class="d2h-tag d2h-added d2h-added-tag">ADDED</span>

View file

@ -1 +0,0 @@
<span class="d2h-tag d2h-changed d2h-changed-tag">CHANGED</span>

View file

@ -1 +0,0 @@
<span class="d2h-tag d2h-deleted d2h-deleted-tag">DELETED</span>

View file

@ -1 +0,0 @@
<span class="d2h-tag d2h-moved d2h-moved-tag">RENAMED</span>

View file

@ -1,99 +0,0 @@
export type DiffLineParts = {
prefix: string;
content: string;
};
export enum LineType {
INSERT = 'insert',
DELETE = 'delete',
CONTEXT = 'context',
}
export interface DiffLineDeleted {
type: LineType.DELETE;
oldNumber: number;
newNumber: undefined;
}
export interface DiffLineInserted {
type: LineType.INSERT;
oldNumber: undefined;
newNumber: number;
}
export interface DiffLineContext {
type: LineType.CONTEXT;
oldNumber: number;
newNumber: number;
}
export type DiffLineContent = {
content: string;
};
export type DiffLine = (DiffLineDeleted | DiffLineInserted | DiffLineContext) & DiffLineContent;
export interface DiffBlock {
oldStartLine: number;
oldStartLine2?: number;
newStartLine: number;
header: string;
lines: DiffLine[];
}
export interface DiffFileName {
oldName: string;
newName: string;
}
export interface DiffFile extends DiffFileName {
addedLines: number;
deletedLines: number;
isCombined: boolean;
isGitDiff: boolean;
language: string;
blocks: DiffBlock[];
oldMode?: string | string[];
newMode?: string;
deletedFileMode?: string;
newFileMode?: string;
isDeleted?: boolean;
isNew?: boolean;
isCopy?: boolean;
isRename?: boolean;
isBinary?: boolean;
isTooBig?: boolean;
unchangedPercentage?: number;
changedPercentage?: number;
checksumBefore?: string | string[];
checksumAfter?: string;
mode?: string;
}
export type OutputFormatType = 'line-by-line' | 'side-by-side';
export const OutputFormatType: { [_: string]: OutputFormatType } = {
LINE_BY_LINE: 'line-by-line',
SIDE_BY_SIDE: 'side-by-side',
};
export type LineMatchingType = 'lines' | 'words' | 'none';
export const LineMatchingType: { [_: string]: LineMatchingType } = {
LINES: 'lines',
WORDS: 'words',
NONE: 'none',
};
export type DiffStyleType = 'word' | 'char';
export const DiffStyleType: { [_: string]: DiffStyleType } = {
WORD: 'word',
CHAR: 'char',
};
export enum ColorSchemeType {
AUTO = 'auto',
DARK = 'dark',
LIGHT = 'light',
}

View file

@ -1,741 +0,0 @@
/*
*
* Diff to HTML (diff2html.css)
* Author: rtfpessoa
*
*/
:root,
:host {
--d2h-bg-color: #fff;
--d2h-border-color: #ddd;
--d2h-dim-color: rgba(0, 0, 0, 0.3);
--d2h-line-border-color: #eeeeee;
--d2h-file-header-bg-color: #f7f7f7;
--d2h-file-header-border-color: #d8d8d8;
--d2h-empty-placeholder-bg-color: #f1f1f1;
--d2h-empty-placeholder-border-color: #e1e1e1;
--d2h-selected-color: #c8e1ff;
--d2h-ins-bg-color: #dfd;
--d2h-ins-border-color: #b4e2b4;
--d2h-ins-highlight-bg-color: #97f295;
--d2h-ins-label-color: #399839;
--d2h-del-bg-color: #fee8e9;
--d2h-del-border-color: #e9aeae;
--d2h-del-highlight-bg-color: #ffb6ba;
--d2h-del-label-color: #c33;
--d2h-change-del-color: #fdf2d0;
--d2h-change-ins-color: #ded;
--d2h-info-bg-color: #f8fafd;
--d2h-info-border-color: #d5e4f2;
--d2h-change-label-color: #d0b44c;
--d2h-moved-label-color: #3572b0;
/**
* Dark Color Scheme
*/
--d2h-dark-color: rgb(230, 237, 243);
--d2h-dark-bg-color: rgb(13, 17, 23);
--d2h-dark-border-color: rgb(48, 54, 61);
--d2h-dark-dim-color: rgb(110, 118, 129);
--d2h-dark-line-border-color: rgb(33, 38, 45);
--d2h-dark-file-header-bg-color: rgb(22, 27, 34);
--d2h-dark-file-header-border-color: rgb(48, 54, 61);
--d2h-dark-empty-placeholder-bg-color: rgba(110, 118, 129, 0.1);
--d2h-dark-empty-placeholder-border-color: rgb(48, 54, 61);
--d2h-dark-selected-color: rgba(56, 139, 253, 0.1);
--d2h-dark-ins-bg-color: rgba(46, 160, 67, 0.15);
--d2h-dark-ins-border-color: rgba(46, 160, 67, 0.4);
--d2h-dark-ins-highlight-bg-color: rgba(46, 160, 67, 0.4);
--d2h-dark-ins-label-color: rgb(63, 185, 80);
--d2h-dark-del-bg-color: rgba(248, 81, 73, 0.1);
--d2h-dark-del-border-color: rgba(248, 81, 73, 0.4);
--d2h-dark-del-highlight-bg-color: rgba(248, 81, 73, 0.4);
--d2h-dark-del-label-color: rgb(248, 81, 73);
--d2h-dark-change-del-color: rgba(210, 153, 34, 0.2);
--d2h-dark-change-ins-color: rgba(46, 160, 67, 0.25);
--d2h-dark-info-bg-color: rgba(56, 139, 253, 0.1);
--d2h-dark-info-border-color: rgba(56, 139, 253, 0.4);
--d2h-dark-change-label-color: rgb(210, 153, 34);
--d2h-dark-moved-label-color: #3572b0;
}
.d2h-wrapper {
text-align: left;
}
.d2h-file-header {
display: flex;
height: 35px;
padding: 5px 10px;
border-bottom: 1px solid var(--d2h-file-header-border-color);
background-color: var(--d2h-file-header-bg-color);
font-family: 'Source Sans Pro', 'Helvetica Neue', Helvetica, Arial, sans-serif;
}
.d2h-file-header.d2h-sticky-header {
position: sticky;
top: 0;
z-index: 1;
}
.d2h-file-stats {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
margin-left: auto;
font-size: 14px;
}
.d2h-lines-added {
text-align: right;
border: 1px solid var(--d2h-ins-border-color);
border-radius: 5px 0 0 5px;
color: var(--d2h-ins-label-color);
padding: 2px;
vertical-align: middle;
}
.d2h-lines-deleted {
text-align: left;
border: 1px solid var(--d2h-del-border-color);
border-radius: 0 5px 5px 0;
color: var(--d2h-del-label-color);
padding: 2px;
vertical-align: middle;
margin-left: 1px;
}
.d2h-file-name-wrapper {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
width: 100%;
font-size: 15px;
}
.d2h-file-name {
white-space: nowrap;
text-overflow: ellipsis;
overflow-x: hidden;
}
.d2h-file-wrapper {
border: 1px solid var(--d2h-border-color);
border-radius: 3px;
margin-bottom: 1em;
}
.d2h-file-collapse {
justify-content: flex-end;
display: none;
cursor: pointer;
font-size: 12px;
align-items: center;
border-radius: 3px;
border: 1px solid var(--d2h-border-color);
padding: 4px 8px;
}
.d2h-file-collapse.d2h-selected {
background-color: var(--d2h-selected-color);
}
.d2h-file-collapse-input {
margin: 0 4px 0 0;
}
.d2h-diff-table {
width: 100%;
border-collapse: collapse;
font-family: 'Menlo', 'Consolas', monospace;
font-size: 13px;
}
.d2h-files-diff {
display: flex;
width: 100%;
}
.d2h-file-diff {
overflow-y: hidden;
}
.d2h-files-diff.d2h-d-none,
.d2h-file-diff.d2h-d-none {
display: none;
}
.d2h-file-side-diff {
display: inline-block;
overflow-x: scroll;
overflow-y: hidden;
width: 50%;
}
.d2h-code-line {
display: inline-block;
white-space: nowrap;
user-select: none;
width: calc(100% - 16em);
/* Compensate for the absolute positioning of the line numbers */
padding: 0 8em;
}
.d2h-code-side-line {
display: inline-block;
white-space: nowrap;
user-select: none;
width: calc(100% - 9em);
/* Compensate for the absolute positioning of the line numbers */
padding: 0 4.5em;
}
.d2h-code-line-ctn {
display: inline-block;
background: none;
padding: 0;
word-wrap: normal;
white-space: pre;
user-select: text;
width: 100%;
vertical-align: middle;
}
.d2h-code-line del,
.d2h-code-side-line del {
display: inline-block;
margin-top: -1px;
text-decoration: none;
background-color: var(--d2h-del-highlight-bg-color);
border-radius: 0.2em;
}
.d2h-code-line ins,
.d2h-code-side-line ins {
display: inline-block;
margin-top: -1px;
text-decoration: none;
background-color: var(--d2h-ins-highlight-bg-color);
border-radius: 0.2em;
text-align: left;
}
.d2h-code-line-prefix {
display: inline;
background: none;
padding: 0;
word-wrap: normal;
white-space: pre;
}
.line-num1 {
box-sizing: border-box;
float: left;
width: 3.5em;
overflow: hidden;
text-overflow: ellipsis;
padding: 0 0.5em 0 0.5em;
}
.line-num2 {
box-sizing: border-box;
float: right;
width: 3.5em;
overflow: hidden;
text-overflow: ellipsis;
padding: 0 0.5em 0 0.5em;
}
.d2h-code-linenumber {
box-sizing: border-box;
width: 7.5em;
/* Keep the numbers fixed on line contents scroll */
position: absolute;
display: inline-block;
background-color: var(--d2h-bg-color);
color: var(--d2h-dim-color);
text-align: right;
border: solid var(--d2h-line-border-color);
border-width: 0 1px 0 1px;
cursor: pointer;
}
.d2h-code-linenumber:after {
content: '\200b';
}
.d2h-code-side-linenumber {
/* Keep the numbers fixed on line contents scroll */
position: absolute;
display: inline-block;
box-sizing: border-box;
width: 4em;
background-color: var(--d2h-bg-color);
color: var(--d2h-dim-color);
text-align: right;
border: solid var(--d2h-line-border-color);
border-width: 0 1px 0 1px;
cursor: pointer;
overflow: hidden;
text-overflow: ellipsis;
padding: 0 0.5em 0 0.5em;
}
.d2h-code-side-linenumber:after {
content: '\200b';
}
.d2h-code-side-emptyplaceholder,
.d2h-emptyplaceholder {
background-color: var(--d2h-empty-placeholder-bg-color);
border-color: var(--d2h-empty-placeholder-border-color);
}
.d2h-code-linenumber,
.d2h-code-side-linenumber,
.d2h-code-line-prefix,
.d2h-emptyplaceholder {
user-select: none;
}
.d2h-code-linenumber,
.d2h-code-side-linenumber {
direction: rtl;
}
/*
* Changes Highlight
*/
.d2h-del {
background-color: var(--d2h-del-bg-color);
border-color: var(--d2h-del-border-color);
}
.d2h-ins {
background-color: var(--d2h-ins-bg-color);
border-color: var(--d2h-ins-border-color);
}
.d2h-info {
background-color: var(--d2h-info-bg-color);
color: var(--d2h-dim-color);
border-color: var(--d2h-info-border-color);
}
.d2h-file-diff .d2h-del.d2h-change {
background-color: var(--d2h-change-del-color);
}
.d2h-file-diff .d2h-ins.d2h-change {
background-color: var(--d2h-change-ins-color);
}
/*
* File Summary List
*/
.d2h-file-list-wrapper {
margin-bottom: 10px;
}
.d2h-file-list-wrapper a {
text-decoration: none;
color: var(--d2h-moved-label-color);
}
.d2h-file-list-wrapper a:visited {
color: var(--d2h-moved-label-color);
}
.d2h-file-list-header {
text-align: left;
}
.d2h-file-list-title {
font-weight: bold;
}
.d2h-file-list-line {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
text-align: left;
}
.d2h-file-list {
display: block;
list-style: none;
padding: 0;
margin: 0;
}
.d2h-file-list > li {
border-bottom: var(--d2h-border-color) solid 1px;
padding: 5px 10px;
margin: 0;
}
.d2h-file-list > li:last-child {
border-bottom: none;
}
.d2h-file-switch {
display: none;
font-size: 10px;
cursor: pointer;
}
.d2h-icon {
vertical-align: middle;
margin-right: 10px;
fill: currentColor;
}
.d2h-deleted {
color: var(--d2h-del-label-color);
}
.d2h-added {
color: var(--d2h-ins-label-color);
}
.d2h-changed {
color: var(--d2h-change-label-color);
}
.d2h-moved {
color: var(--d2h-moved-label-color);
}
.d2h-tag {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
font-size: 10px;
margin-left: 5px;
padding: 0 2px;
background-color: var(--d2h-bg-color);
}
.d2h-deleted-tag {
border: var(--d2h-del-label-color) 1px solid;
}
.d2h-added-tag {
border: var(--d2h-ins-label-color) 1px solid;
}
.d2h-changed-tag {
border: var(--d2h-change-label-color) 1px solid;
}
.d2h-moved-tag {
border: var(--d2h-moved-label-color) 1px solid;
}
/**
* Dark Mode Colors
*/
.d2h-dark-color-scheme {
color: var(--d2h-dark-color);
background-color: var(--d2h-dark-bg-color);
}
.d2h-dark-color-scheme .d2h-file-header {
background-color: var(--d2h-dark-file-header-bg-color);
border-bottom: var(--d2h-dark-file-header-border-color);
}
.d2h-dark-color-scheme .d2h-lines-added {
border: 1px solid var(--d2h-dark-ins-border-color);
color: var(--d2h-dark-ins-label-color);
}
.d2h-dark-color-scheme .d2h-lines-deleted {
border: 1px solid var(--d2h-dark-del-border-color);
color: var(--d2h-dark-del-label-color);
}
.d2h-dark-color-scheme .d2h-code-line del,
.d2h-dark-color-scheme .d2h-code-side-line del {
background-color: var(--d2h-dark-del-highlight-bg-color);
}
.d2h-dark-color-scheme .d2h-code-line ins,
.d2h-dark-color-scheme .d2h-code-side-line ins {
background-color: var(--d2h-dark-ins-highlight-bg-color);
}
.d2h-dark-color-scheme .d2h-diff-tbody {
border-color: var(--d2h-dark-border-color);
}
.d2h-dark-color-scheme .d2h-code-side-linenumber {
background-color: var(--d2h-dark-bg-color);
color: var(--d2h-dark-dim-color);
border-color: var(--d2h-dark-line-border-color);
}
.d2h-dark-color-scheme .d2h-files-diff .d2h-code-side-emptyplaceholder,
.d2h-dark-color-scheme .d2h-files-diff .d2h-emptyplaceholder {
background-color: var(--d2h-dark-empty-placeholder-bg-color);
border-color: var(--d2h-dark-empty-placeholder-border-color);
}
.d2h-dark-color-scheme .d2h-code-linenumber {
background-color: var(--d2h-dark-bg-color);
color: var(--d2h-dark-dim-color);
border-color: var(--d2h-dark-line-border-color);
}
.d2h-dark-color-scheme .d2h-del {
background-color: var(--d2h-dark-del-bg-color);
border-color: var(--d2h-dark-del-border-color);
}
.d2h-dark-color-scheme .d2h-ins {
background-color: var(--d2h-dark-ins-bg-color);
border-color: var(--d2h-dark-ins-border-color);
}
.d2h-dark-color-scheme .d2h-info {
background-color: var(--d2h-dark-info-bg-color);
color: var(--d2h-dark-dim-color);
border-color: var(--d2h-dark-info-border-color);
}
.d2h-dark-color-scheme .d2h-file-diff .d2h-del.d2h-change {
background-color: var(--d2h-dark-change-del-color);
}
.d2h-dark-color-scheme .d2h-file-diff .d2h-ins.d2h-change {
background-color: var(--d2h-dark-change-ins-color);
}
.d2h-dark-color-scheme .d2h-file-wrapper {
border: 1px solid var(--d2h-dark-border-color);
}
.d2h-dark-color-scheme .d2h-file-collapse {
border: 1px solid var(--d2h-dark-bg-color);
}
.d2h-dark-color-scheme .d2h-file-collapse.d2h-selected {
background-color: var(--d2h-dark-selected-color);
}
.d2h-dark-color-scheme .d2h-file-list-wrapper a {
color: var(--d2h-dark-moved-label-color);
}
.d2h-dark-color-scheme .d2h-file-list-wrapper a:visited {
color: var(--d2h-dark-moved-label-color);
}
.d2h-dark-color-scheme .d2h-file-list > li {
border-bottom: var(--d2h-dark-bg-color) solid 1px;
}
.d2h-dark-color-scheme .d2h-deleted {
color: var(--d2h-dark-del-label-color);
}
.d2h-dark-color-scheme .d2h-added {
color: var(--d2h-dark-ins-label-color);
}
.d2h-dark-color-scheme .d2h-changed {
color: var(--d2h-dark-change-label-color);
}
.d2h-dark-color-scheme .d2h-moved {
color: var(--d2h-dark-moved-label-color);
}
.d2h-dark-color-scheme .d2h-tag {
background-color: var(--d2h-dark-bg-color);
}
.d2h-dark-color-scheme .d2h-deleted-tag {
border: var(--d2h-dark-del-label-color) 1px solid;
}
.d2h-dark-color-scheme .d2h-added-tag {
border: var(--d2h-dark-ins-label-color) 1px solid;
}
.d2h-dark-color-scheme .d2h-changed-tag {
border: var(--d2h-dark-change-label-color) 1px solid;
}
.d2h-dark-color-scheme .d2h-moved-tag {
border: var(--d2h-dark-moved-label-color) 1px solid;
}
/**
* Auto Mode Colors
*/
@media (prefers-color-scheme: dark) {
.d2h-auto-color-scheme {
background-color: var(--d2h-dark-bg-color);
color: var(--d2h-dark-color);
}
.d2h-auto-color-scheme .d2h-file-header {
background-color: var(--d2h-dark-file-header-bg-color);
border-bottom: var(--d2h-dark-file-header-border-color);
}
.d2h-auto-color-scheme .d2h-lines-added {
border: 1px solid var(--d2h-dark-ins-border-color);
color: var(--d2h-dark-ins-label-color);
}
.d2h-auto-color-scheme .d2h-lines-deleted {
border: 1px solid var(--d2h-dark-del-border-color);
color: var(--d2h-dark-del-label-color);
}
.d2h-auto-color-scheme .d2h-code-line del,
.d2h-auto-color-scheme .d2h-code-side-line del {
background-color: var(--d2h-dark-del-highlight-bg-color);
}
.d2h-auto-color-scheme .d2h-code-line ins,
.d2h-auto-color-scheme .d2h-code-side-line ins {
background-color: var(--d2h-dark-ins-highlight-bg-color);
}
.d2h-auto-color-scheme .d2h-diff-tbody {
border-color: var(--d2h-dark-border-color);
}
.d2h-auto-color-scheme .d2h-code-side-linenumber {
background-color: var(--d2h-dark-bg-color);
color: var(--d2h-dark-dim-color);
border-color: var(--d2h-dark-line-border-color);
}
.d2h-auto-color-scheme .d2h-files-diff .d2h-code-side-emptyplaceholder,
.d2h-auto-color-scheme .d2h-files-diff .d2h-emptyplaceholder {
background-color: var(--d2h-dark-empty-placeholder-bg-color);
border-color: var(--d2h-dark-empty-placeholder-border-color);
}
.d2h-auto-color-scheme .d2h-code-linenumber {
background-color: var(--d2h-dark-bg-color);
color: var(--d2h-dark-dim-color);
border-color: var(--d2h-dark-line-border-color);
}
.d2h-auto-color-scheme .d2h-del {
background-color: var(--d2h-dark-del-bg-color);
border-color: var(--d2h-dark-del-border-color);
}
.d2h-auto-color-scheme .d2h-ins {
background-color: var(--d2h-dark-ins-bg-color);
border-color: var(--d2h-dark-ins-border-color);
}
.d2h-auto-color-scheme .d2h-info {
background-color: var(--d2h-dark-info-bg-color);
color: var(--d2h-dark-dim-color);
border-color: var(--d2h-dark-info-border-color);
}
.d2h-auto-color-scheme .d2h-file-diff .d2h-del.d2h-change {
background-color: var(--d2h-dark-change-del-color);
}
.d2h-auto-color-scheme .d2h-file-diff .d2h-ins.d2h-change {
background-color: var(--d2h-dark-change-ins-color);
}
.d2h-auto-color-scheme .d2h-file-wrapper {
border: 1px solid var(--d2h-dark-border-color);
}
.d2h-auto-color-scheme .d2h-file-collapse {
border: 1px solid var(--d2h-dark-bg-color);
}
.d2h-auto-color-scheme .d2h-file-collapse.d2h-selected {
background-color: var(--d2h-dark-selected-color);
}
.d2h-auto-color-scheme .d2h-file-list-wrapper a {
color: var(--d2h-dark-moved-label-color);
}
.d2h-auto-color-scheme .d2h-file-list-wrapper a:visited {
color: var(--d2h-dark-moved-label-color);
}
.d2h-auto-color-scheme .d2h-file-list > li {
border-bottom: var(--d2h-dark-bg-color) solid 1px;
}
.d2h-dark-color-scheme .d2h-deleted {
color: var(--d2h-dark-del-label-color);
}
.d2h-auto-color-scheme .d2h-added {
color: var(--d2h-dark-ins-label-color);
}
.d2h-auto-color-scheme .d2h-changed {
color: var(--d2h-dark-change-label-color);
}
.d2h-auto-color-scheme .d2h-moved {
color: var(--d2h-dark-moved-label-color);
}
.d2h-auto-color-scheme .d2h-tag {
background-color: var(--d2h-dark-bg-color);
}
.d2h-auto-color-scheme .d2h-deleted-tag {
border: var(--d2h-dark-del-label-color) 1px solid;
}
.d2h-auto-color-scheme .d2h-added-tag {
border: var(--d2h-dark-ins-label-color) 1px solid;
}
.d2h-auto-color-scheme .d2h-changed-tag {
border: var(--d2h-dark-change-label-color) 1px solid;
}
.d2h-auto-color-scheme .d2h-moved-tag {
border: var(--d2h-dark-moved-label-color) 1px solid;
}
}

View file

@ -1,225 +0,0 @@
import { closeTags, nodeStream, mergeStreams, getLanguage } from './highlight.js-helpers';
import { html, Diff2HtmlConfig, defaultDiff2HtmlConfig } from '../../diff2html';
import { DiffFile } from '../../types';
import { HighlightResult, HLJSApi } from 'highlight.js';
export interface Diff2HtmlUIConfig extends Diff2HtmlConfig {
synchronisedScroll?: boolean;
highlight?: boolean;
fileListToggle?: boolean;
fileListStartVisible?: boolean;
highlightLanguages?: Map<string, string>;
/**
* @deprecated since version 3.1.0
* Smart selection is now enabled by default with vanilla CSS
*/
smartSelection?: boolean;
fileContentToggle?: boolean;
stickyFileHeaders?: boolean;
}
export const defaultDiff2HtmlUIConfig = {
...defaultDiff2HtmlConfig,
synchronisedScroll: true,
highlight: true,
fileListToggle: true,
fileListStartVisible: false,
highlightLanguages: new Map<string, string>(),
/**
* @deprecated since version 3.1.0
* Smart selection is now enabled by default with vanilla CSS
*/
smartSelection: true,
fileContentToggle: true,
stickyFileHeaders: true,
};
export class Diff2HtmlUI {
readonly config: typeof defaultDiff2HtmlUIConfig;
readonly diffHtml: string;
readonly targetElement: HTMLElement;
readonly hljs: HLJSApi | null = null;
currentSelectionColumnId = -1;
constructor(target: HTMLElement, diffInput?: string | DiffFile[], config: Diff2HtmlUIConfig = {}, hljs?: HLJSApi) {
this.config = { ...defaultDiff2HtmlUIConfig, ...config };
this.diffHtml = diffInput !== undefined ? html(diffInput, this.config) : target.innerHTML;
this.targetElement = target;
if (hljs !== undefined) this.hljs = hljs;
}
draw(): void {
this.targetElement.innerHTML = this.diffHtml;
if (this.config.synchronisedScroll) this.synchronisedScroll();
if (this.config.highlight) this.highlightCode();
if (this.config.fileListToggle) this.fileListToggle(this.config.fileListStartVisible);
if (this.config.fileContentToggle) this.fileContentToggle();
if (this.config.stickyFileHeaders) this.stickyFileHeaders();
}
synchronisedScroll(): void {
this.targetElement.querySelectorAll('.d2h-file-wrapper').forEach(wrapper => {
const [left, right] = Array<Element>().slice.call(wrapper.querySelectorAll('.d2h-file-side-diff'));
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 showBtn: HTMLElement | null = this.targetElement.querySelector('.d2h-show');
const hideBtn: HTMLElement | null = this.targetElement.querySelector('.d2h-hide');
const fileList: HTMLElement | null = this.targetElement.querySelector('.d2h-file-list');
if (showBtn === null || hideBtn === null || fileList === null) return;
const show: () => void = () => {
showBtn.style.display = 'none';
hideBtn.style.display = 'inline';
fileList.style.display = 'block';
};
const hide: () => void = () => {
showBtn.style.display = 'inline';
hideBtn.style.display = 'none';
fileList.style.display = 'none';
};
showBtn.addEventListener('click', () => show());
hideBtn.addEventListener('click', () => hide());
const hashTag = this.getHashTag();
if (hashTag === 'files-summary-show') show();
else if (hashTag === 'files-summary-hide') hide();
else if (startVisible) show();
else hide();
}
fileContentToggle(): void {
this.targetElement.querySelectorAll<HTMLElement>('.d2h-file-collapse').forEach(fileContentToggleBtn => {
fileContentToggleBtn.style.display = 'flex';
const toggleFileContents: (selector: string) => void = selector => {
const fileContents: HTMLElement | null | undefined = fileContentToggleBtn
.closest('.d2h-file-wrapper')
?.querySelector(selector);
if (fileContents !== null && fileContents !== undefined) {
fileContentToggleBtn.classList.toggle('d2h-selected');
fileContents.classList.toggle('d2h-d-none');
}
};
const toggleHandler: (e: Event) => void = e => {
if (fileContentToggleBtn === e.target) return;
toggleFileContents('.d2h-file-diff');
toggleFileContents('.d2h-files-diff');
};
fileContentToggleBtn.addEventListener('click', e => toggleHandler(e));
});
}
highlightCode(): void {
const hljs = this.hljs;
if (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 => {
const language = file.getAttribute('data-lang');
if (!(this.config.highlightLanguages instanceof Map)) {
this.config.highlightLanguages = new Map(Object.entries(this.config.highlightLanguages));
}
let hljsLanguage =
language && this.config.highlightLanguages.has(language)
? this.config.highlightLanguages.get(language)!
: language
? getLanguage(language)
: 'plaintext';
// Fallback to plaintext in case language is not loaded
if (hljs.getLanguage(hljsLanguage) === undefined) {
hljsLanguage = 'plaintext';
}
// 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;
if (text === null || lineParent === null || !this.isElement(lineParent)) return;
const result: HighlightResult = closeTags(
hljs.highlight(text, {
language: hljsLanguage,
ignoreIllegals: true,
}),
);
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');
if (result.language) {
line.classList.add(result.language);
}
line.innerHTML = result.value;
});
});
}
stickyFileHeaders(): void {
this.targetElement.querySelectorAll('.d2h-file-header').forEach(header => {
header.classList.add('d2h-sticky-header');
});
}
/**
* @deprecated since version 3.1.0
*/
smartSelection(): void {
console.warn('Smart selection is now enabled by default with CSS. No need to call this method anymore.');
}
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 isElement(arg?: unknown): arg is Element {
return arg !== null && (arg as Element)?.classList !== undefined;
}
}

View file

@ -1,13 +0,0 @@
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(target: HTMLElement, diffInput?: string | DiffFile[], config: Diff2HtmlUIConfig = {}) {
super(target, diffInput, config, hljs);
}
}
export { defaultDiff2HtmlUIConfig };
export type { Diff2HtmlUIConfig };

View file

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

View file

@ -1,655 +0,0 @@
/*
* Adapted Highlight.js Internal APIs
* Used to highlight selected html elements using context
*/
import { HighlightResult } from 'highlight.js';
/* Utility functions */
function escapeHTML(value: string): string {
return value.replace(/&/gm, '&amp;').replace(/</gm, '&lt;').replace(/>/gm, '&gt;');
}
function tag(node: Node): string {
return node.nodeName.toLowerCase();
}
/* Stream merging */
type NodeEvent = {
event: 'start' | 'stop';
offset: number;
node: Node;
};
export function nodeStream(node: Node): NodeEvent[] {
const result: NodeEvent[] = [];
const nodeStream = (node: Node, offset: number): number => {
for (let child = node.firstChild; child; child = child.nextSibling) {
if (child.nodeType === 3 && child.nodeValue !== null) {
offset += child.nodeValue.length;
} else if (child.nodeType === 1) {
result.push({
event: 'start',
offset: offset,
node: child,
});
offset = nodeStream(child, offset);
// Prevent void elements from having an end tag that would actually
// double them in the output. There are more void elements in HTML
// but we list only those realistically expected in code display.
if (!tag(child).match(/br|hr|img|input/)) {
result.push({
event: 'stop',
offset: offset,
node: child,
});
}
}
}
return offset;
};
nodeStream(node, 0);
return result;
}
export function mergeStreams(original: NodeEvent[], highlighted: NodeEvent[], value: string): string {
let processed = 0;
let result = '';
const nodeStack = [];
function isElement(arg?: unknown): arg is Element {
return arg !== null && (arg as Element)?.attributes !== undefined;
}
function selectStream(): NodeEvent[] {
if (!original.length || !highlighted.length) {
return original.length ? original : highlighted;
}
if (original[0].offset !== highlighted[0].offset) {
return original[0].offset < highlighted[0].offset ? original : highlighted;
}
/*
To avoid starting the stream just before it should stop the order is
ensured that original always starts first and closes last:
if (event1 == 'start' && event2 == 'start')
return original;
if (event1 == 'start' && event2 == 'stop')
return highlighted;
if (event1 == 'stop' && event2 == 'start')
return original;
if (event1 == 'stop' && event2 == 'stop')
return highlighted;
... which is collapsed to:
*/
return highlighted[0].event === 'start' ? original : highlighted;
}
function open(node: Node): void {
if (!isElement(node)) {
throw new Error('Node is not an Element');
}
result += `<${tag(node)} ${Array<Attr>()
.map.call(node.attributes, attr => `${attr.nodeName}="${escapeHTML(attr.value).replace(/"/g, '&quot;')}"`)
.join(' ')}>`;
}
function close(node: Node): void {
result += '</' + tag(node) + '>';
}
function render(event: NodeEvent): void {
(event.event === 'start' ? open : close)(event.node);
}
while (original.length || highlighted.length) {
let stream = selectStream();
result += escapeHTML(value.substring(processed, stream[0].offset));
processed = stream[0].offset;
if (stream === original) {
/*
On any opening or closing tag of the original markup we first close
the entire highlighted node stack, then render the original tag along
with all the following original tags at the same offset and then
reopen all the tags on the highlighted stack.
*/
nodeStack.reverse().forEach(close);
do {
render(stream.splice(0, 1)[0]);
stream = selectStream();
} while (stream === original && stream.length && stream[0].offset === processed);
nodeStack.reverse().forEach(open);
} else {
if (stream[0].event === 'start') {
nodeStack.push(stream[0].node);
} else {
nodeStack.pop();
}
render(stream.splice(0, 1)[0]);
}
}
return result + escapeHTML(value.substr(processed));
}
// https://github.com/hexojs/hexo-util/blob/979873b63a725377c2bd6ad834d790023496130d/lib/highlight.js#L123
export function closeTags(res: HighlightResult): HighlightResult {
const tokenStack = new Array<string>();
res.value = res.value
.split('\n')
.map(line => {
const prepend = tokenStack.map(token => `<span class="${token}">`).join('');
const matches = line.matchAll(/(<span class="(.*?)">|<\/span>)/g);
Array.from(matches).forEach(match => {
if (match[0] === '</span>') tokenStack.shift();
else tokenStack.unshift(match[2]);
});
const append = '</span>'.repeat(tokenStack.length);
return prepend + line + append;
})
.join('\n');
return res;
}
// Sourced from https://github.com/highlightjs/highlight.js/blob/main/SUPPORTED_LANGUAGES.md and
// https://github.com/exercism/v2-website/blob/main/config/initializers/prism.rb#L187-L315
const languagesToExt: { [_: string]: string } = {
'1c': '1c',
abnf: 'abnf',
accesslog: 'accesslog',
as: 'actionscript',
adb: 'ada',
ada: 'ada',
ads: 'ada',
angelscript: 'angelscript',
// asc: 'angelscript',
apache: 'apache',
applescript: 'applescript',
scpt: 'applescript',
arcade: 'arcade',
cpp: 'cpp',
hpp: 'cpp',
arduino: 'arduino',
ino: 'arduino',
armasm: 'armasm',
arm: 'armasm',
xml: 'xml',
html: 'xml',
xhtml: 'xml',
rss: 'xml',
atom: 'xml',
xjb: 'xml',
xsd: 'xml',
xsl: 'xml',
plist: 'xml',
svg: 'xml',
asciidoc: 'asciidoc',
adoc: 'asciidoc',
asc: 'asciidoc',
aspectj: 'aspectj',
ahk: 'autohotkey',
ahkl: 'autohotkey',
au3: 'autoit',
avrasm: 'avrasm',
awk: 'awk',
axapta: 'axapta',
'x++': 'axapta',
bash: 'bash',
sh: 'bash',
zsh: 'bash',
b: 'basic',
bnf: 'bnf',
bf: 'brainfuck',
c: 'c',
h: 'c',
cats: 'c',
idc: 'c',
cal: 'cal',
capnproto: 'capnproto',
capnp: 'capnproto',
ceylon: 'ceylon',
clean: 'clean',
clj: 'clojure',
boot: 'clojure',
cl2: 'clojure',
cljc: 'clojure',
cljs: 'clojure',
'cljs.hl': 'clojure',
cljscm: 'clojure',
cljx: 'clojure',
hic: 'clojure',
'clojure-repl': 'clojure-repl',
cmake: 'cmake',
'cmake.in': 'cmake',
coffee: 'coffeescript',
_coffee: 'coffeescript',
cake: 'coffeescript',
cjsx: 'coffeescript',
iced: 'coffeescript',
cson: 'coffeescript',
coq: 'coq',
cos: 'cos',
cls: 'cos',
crmsh: 'crmsh',
crm: 'crmsh',
pcmk: 'crmsh',
cr: 'crystal',
cs: 'csharp',
csx: 'csharp',
csp: 'csp',
css: 'css',
d: 'd',
di: 'd',
md: 'markdown',
markdown: 'markdown',
mdown: 'markdown',
mdwn: 'markdown',
mkd: 'markdown',
mkdn: 'markdown',
mkdown: 'markdown',
ronn: 'markdown',
workbook: 'markdown',
dart: 'dart',
dpr: 'delphi',
dfm: 'delphi',
pas: 'delphi',
pascal: 'delphi',
diff: 'diff',
patch: 'diff',
django: 'django',
jinja: 'django',
dns: 'dns',
zone: 'dns',
bind: 'dns',
dockerfile: 'dockerfile',
docker: 'dockerfile',
dos: 'dos',
bat: 'dos',
cmd: 'dos',
dsconfig: 'dsconfig',
dts: 'dts',
dust: 'dust',
dst: 'dust',
ebnf: 'ebnf',
ex: 'elixir',
exs: 'elixir',
elm: 'elm',
rb: 'ruby',
builder: 'ruby',
eye: 'ruby',
gemspec: 'ruby',
god: 'ruby',
jbuilder: 'ruby',
mspec: 'ruby',
pluginspec: 'ruby',
podspec: 'ruby',
rabl: 'ruby',
rake: 'ruby',
rbuild: 'ruby',
rbw: 'ruby',
rbx: 'ruby',
ru: 'ruby',
ruby: 'ruby',
spec: 'ruby',
thor: 'ruby',
watchr: 'ruby',
erb: 'erb',
'erlang-repl': 'erlang-repl',
erl: 'erlang',
'app.src': 'erlang',
escript: 'erlang',
hrl: 'erlang',
xrl: 'erlang',
yrl: 'erlang',
excel: 'excel',
xls: 'excel',
xlsx: 'excel',
fix: 'fix',
flix: 'flix',
f90: 'fortran',
f: 'fortran',
f03: 'fortran',
f08: 'fortran',
f77: 'fortran',
f95: 'fortran',
for: 'fortran',
fpp: 'fortran',
fs: 'fsharp',
fsx: 'fsharp',
gams: 'gams',
gms: 'gams',
gauss: 'gauss',
gss: 'gauss',
gcode: 'gcode',
nc: 'gcode',
gherkin: 'gherkin',
glsl: 'glsl',
fp: 'glsl',
frag: 'glsl',
frg: 'glsl',
fsh: 'glsl',
fshader: 'glsl',
geo: 'glsl',
geom: 'glsl',
glslv: 'glsl',
gshader: 'glsl',
shader: 'glsl',
tesc: 'glsl',
tese: 'glsl',
vert: 'glsl',
vrx: 'glsl',
vsh: 'glsl',
vshader: 'glsl',
gml: 'gml',
go: 'go',
bal: 'go',
golo: 'golo',
gololang: 'golo',
gradle: 'gradle',
groovy: 'groovy',
grt: 'groovy',
gtpl: 'groovy',
gvy: 'groovy',
haml: 'haml',
'haml.deface': 'haml',
handlebars: 'handlebars',
hbs: 'handlebars',
'html.hbs': 'handlebars',
'html.handlebars': 'handlebars',
hs: 'haskell',
hsc: 'haskell',
idr: 'haskell',
purs: 'haskell',
hx: 'haxe',
hxsl: 'haxe',
hsp: 'hsp',
htmlbars: 'htmlbars',
http: 'http',
https: 'http',
hy: 'hy',
inform7: 'inform7',
i7: 'inform7',
ini: 'ini',
toml: 'ini',
cfg: 'ini',
prefs: 'ini',
// properties: 'ini',
irpf90: 'irpf90',
isbl: 'isbl',
java: 'java',
jsp: 'java',
js: 'javascript',
jsx: 'javascript',
_js: 'javascript',
bones: 'javascript',
es: 'javascript',
es6: 'javascript',
gs: 'javascript',
jake: 'javascript',
jsb: 'javascript',
jscad: 'javascript',
jsfl: 'javascript',
jsm: 'javascript',
jss: 'javascript',
mjs: 'javascript',
njs: 'javascript',
pac: 'javascript',
sjs: 'javascript',
ssjs: 'javascript',
xsjs: 'javascript',
xsjslib: 'javascript',
cfc: 'javascript',
'jboss-cli': 'jboss-cli',
json: 'json',
avsc: 'json',
geojson: 'json',
gltf: 'json',
'JSON-tmLanguage': 'json',
jsonl: 'json',
tfstate: 'json',
'tfstate.backup': 'json',
topojson: 'json',
webapp: 'json',
webmanifest: 'json',
jl: 'julia',
'julia-repl': 'julia-repl',
kt: 'kotlin',
ktm: 'kotlin',
kts: 'kotlin',
lasso: 'lasso',
// ls: 'lasso',
lassoscript: 'lasso',
tex: 'latex',
ldif: 'ldif',
leaf: 'leaf',
less: 'less',
lisp: 'lisp',
factor: 'lisp',
livecodeserver: 'livecodeserver',
ls: 'livescript',
_ls: 'livescript',
llvm: 'llvm',
lsl: 'lsl',
lua: 'lua',
nse: 'lua',
p8: 'lua',
pd_lua: 'lua',
rbxs: 'lua',
wlua: 'lua',
mak: 'makefile',
make: 'makefile',
mk: 'makefile',
mkfile: 'makefile',
mathematica: 'mathematica',
mma: 'mathematica',
wl: 'mathematica',
matlab: 'matlab',
maxima: 'maxima',
mel: 'mel',
mercury: 'mercury',
mipsasm: 'mipsasm',
miz: 'mizar',
voc: 'mizar',
al: 'perl',
cgi: 'perl',
fcgi: 'perl',
perl: 'perl',
ph: 'perl',
plx: 'perl',
pl: 'perl',
pm: 'perl',
psgi: 'perl',
t: 'perl',
mojolicious: 'mojolicious',
monkey: 'monkey',
monkey2: 'monkey',
moonscript: 'moonscript',
moon: 'moonscript',
n1ql: 'n1ql',
nginxconf: 'nginx',
nim: 'nim',
nimrod: 'nim',
nix: 'nix',
nsi: 'nsis',
nsh: 'nsis',
m: 'objectivec',
objc: 'objectivec',
mm: 'objectivec',
'obj-c': 'objectivec',
'obj-c++': 'objectivec',
'objective-c++': 'objectivec',
fun: 'ocaml',
sig: 'ocaml',
// sml: 'ocaml',
ml: 'ocaml',
mli: 'ocaml',
eliom: 'ocaml',
eliomi: 'ocaml',
ml4: 'ocaml',
mll: 'ocaml',
mly: 'ocaml',
openscad: 'openscad',
oxygene: 'oxygene',
parser3: 'parser3',
pf: 'pf',
'pf.conf': 'pf',
pgsql: 'pgsql',
postgres: 'pgsql',
postgresql: 'pgsql',
php: 'php',
aw: 'php',
ctp: 'php',
inc: 'php',
php3: 'php',
php4: 'php',
php5: 'php',
phps: 'php',
phpt: 'php',
'php-template': 'php-template',
plaintext: 'plaintext',
txt: 'plaintext',
text: 'plaintext',
pony: 'pony',
ps: 'powershell',
ps1: 'powershell',
psd1: 'powershell',
psm1: 'powershell',
pde: 'processing',
profile: 'profile',
pro: 'prolog',
prolog: 'prolog',
yap: 'prolog',
properties: 'properties',
proto: 'protobuf',
puppet: 'puppet',
pp: 'puppet',
purebasic: 'purebasic',
py: 'python',
bzl: 'python',
gyp: 'python',
gypi: 'python',
lmi: 'python',
py3: 'python',
pyde: 'python',
pyi: 'python',
pyp: 'python',
pyt: 'python',
pyw: 'python',
rpy: 'python',
tac: 'python',
wsgi: 'python',
xpy: 'python',
'python-repl': 'python-repl',
pycon: 'python-repl',
q: 'q',
k: 'q',
kdb: 'q',
qml: 'qml',
r: 'r',
rd: 'r',
rsx: 'r',
reasonml: 'reasonml',
re: 'reasonml',
rib: 'rib',
roboconf: 'roboconf',
graph: 'roboconf',
instances: 'roboconf',
routeros: 'routeros',
rsl: 'rsl',
ruleslanguage: 'ruleslanguage',
rs: 'rust',
'rs.in': 'rust',
sas: 'sas',
// pony: 'scala',
scala: 'scala',
kojo: 'scala',
sbt: 'scala',
sc: 'scala',
scm: 'scheme',
sch: 'scheme',
sld: 'scheme',
sls: 'scheme',
sps: 'scheme',
ss: 'scheme',
rkt: 'scheme',
scilab: 'scilab',
scss: 'scss',
shell: 'shell',
smali: 'smali',
st: 'smalltalk',
sml: 'sml',
sqf: 'sqf',
sql: 'sql',
cql: 'sql',
ddl: 'sql',
mysql: 'sql',
prc: 'sql',
tab: 'sql',
udf: 'sql',
viw: 'sql',
stan: 'stan',
stanfuncs: 'stan',
stata: 'stata',
step21: 'step21',
step: 'step21',
stp: 'step21',
styl: 'stylus',
subunit: 'subunit',
swift: 'swift',
taggerscript: 'taggerscript',
yml: 'yaml',
mir: 'yaml',
reek: 'yaml',
rviz: 'yaml',
'sublime-syntax': 'yaml',
syntax: 'yaml',
yaml: 'yaml',
'yaml-tmlanguage': 'yaml',
'yml.mysql': 'yaml',
tap: 'tap',
tcl: 'tcl',
adp: 'tcl',
tm: 'tcl',
thrift: 'thrift',
tp: 'tp',
twig: 'twig',
craftcms: 'twig',
ts: 'typescript',
tsx: 'typescript',
vala: 'vala',
vbnet: 'vbnet',
vb: 'vbnet',
vbscript: 'vbscript',
vbs: 'vbscript',
'vbscript-html': 'vbscript-html',
v: 'verilog',
veo: 'verilog',
vhdl: 'vhdl',
vhd: 'vhdl',
vhf: 'vhdl',
vhi: 'vhdl',
vho: 'vhdl',
vhs: 'vhdl',
vht: 'vhdl',
vhw: 'vhdl',
vim: 'vim',
x86asm: 'x86asm',
xl: 'xl',
xquery: 'xquery',
xpath: 'xquery',
xq: 'xquery',
zephir: 'zephir',
zep: 'zephir',
};
export function getLanguage(fileExtension: string): string {
return languagesToExt[fileExtension] ?? 'plaintext';
}

View file

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

View file

@ -1,54 +0,0 @@
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');
/**
* Escapes all required characters for safe usage inside a RegExp
*/
export function escapeForRegExp(str: string): string {
return str.replace(regex, '\\$&');
}
/**
* Converts all '\' in @path to unix style '/'
*/
export function unifyPath(path: string): string {
return path ? path.replace(/\\/g, '/') : path;
}
/**
* Create unique number identifier for @text
*/
export function hashCode(text: string): number {
let i, chr, len;
let hash = 0;
for (i = 0, len = text.length; i < len; i++) {
chr = text.charCodeAt(i);
hash = (hash << 5) - hash + chr;
hash |= 0; // Convert to 32bit integer
}
return hash;
}

185
style.css Normal file
View file

@ -0,0 +1,185 @@
/*
*
* Diff to HTML (style.css)
* Author: rtfpessoa
* Date: Friday 29 August 2014
* Last Update: Saturday 30 August 2014
*
*/
body {
text-align: center;
}
#wrapper {
display: inline-block;
margin-top: 1em;
text-align: left;
width: 920px;
}
.file-wrapper {
border: 1px solid #ddd;
border-radius: 3px;
margin-bottom: 1em;
}
.file-header {
padding: 5px 10px;
text-shadow: 0 1px 0 #fff;
border-bottom: 1px solid #d8d8d8;
background-color: #f7f7f7;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
box-sizing: border-box;
}
.file-name {
display: inline-block;
font: 13px Helvetica, arial, freesans, clean, sans-serif, "Segoe UI Emoji", "Segoe UI Symbol";
height: 33px;
line-height: 33px;
}
.file-stats {
display: inline-block;
font-family: monospace, monospace;
font-size: 12px;
vertical-align: middle;
}
.lines-added {
background-color: #ceffce;
border: 1px solid #b4e2b4;
color: #399839;
border-radius: 5px 0 0 5px;
padding: 5px;
}
.lines-deleted {
background-color: #f7c8c8;
border: 1px solid #e9aeae;
color: #c33;
border-radius: 0 5px 5px 0;
padding: 5px;
}
.file-diff {
overflow: auto;
}
.file-diff > div {
width: 100%;
}
pre {
margin: 0;
font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
font-size: 12px;
line-height: 18px;
text-indent: 5px;
color: #333;
border: solid #eeeeee;
border-width: 0 1px 0 0;
cursor: pointer;
}
.code-wrapper {
overflow-x: auto;
overflow-y: hidden;
border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px;
}
.diff-table {
border-collapse: separate;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
tbody {
display: table-row-group;
vertical-align: middle;
border-color: inherit;
}
tr {
display: table-row;
vertical-align: inherit;
border-color: inherit;
}
td, th {
padding: 0;
}
pre {
text-indent: 0;
border: none;
}
.code-linenumber {
width: 1%;
min-width: 25px;
padding-left: 10px;
padding-right: 10px;
font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
font-size: 12px;
line-height: 18px;
color: rgba(0, 0, 0, 0.3);
vertical-align: top;
text-align: right;
border: solid #eeeeee;
border-width: 0 1px 0 0;
cursor: pointer;
}
.code-line {
position: relative;
padding-left: 10px;
padding-right: 10px;
font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
font-size: 12px;
color: #333;
vertical-align: top;
white-space: pre;
overflow: visible;
}
.delete {
vertical-align: middle;
background-color: #f7c8c8;
border-color: #e9aeae;
}
.insert {
vertical-align: middle;
background-color: #ceffce;
border-color: #b4e2b4;
}
.info {
background-color: #f8fafd;
vertical-align: middle;
color: rgba(0, 0, 0, 0.3);
border-color: #d5e4f2;
}
.code-line del {
display: inline-block;
margin-top: -1px;
text-decoration: none;
background-color: #ffb6ba;
font-weight: 700;
}
.code-line ins {
display: inline-block;
margin-top: -1px;
text-decoration: none;
background-color: #97f295;
font-weight: 700;
}

61
template.html Normal file
View file

@ -0,0 +1,61 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Diff to HTML by rtfpessoa</title>
<!--
Diff to HTML (template.html)
Author: rtfpessoa
Date: Friday 29 August 2014
Last Update: Saturday 30 August 2014
-->
<link rel="stylesheet" type="text/css" href="style.css">
<script src="http://code.jquery.com/jquery-latest.min.js" type="text/javascript"></script>
<script type="text/javascript" src="diff2html.js"></script>
<script>
var exInput = 'diff --git a/components/enginePlugins/src/main/scala/metrics/Tools.scala b/components/enginePlugins/src/main/scala/metrics/Tools.scala\n' +
'index c68cfb8..a1edc93 100644\n' +
'--- a/components/enginePlugins/src/main/scala/metrics/Tools.scala\n' +
'+++ b/components/enginePlugins/src/main/scala/metrics/Tools.scala\n' +
'@@ -1,6 +1,7 @@\n' +
'package metrics\n' +
' \n' +
'import java.io\n' +
'{+import java.nio.file.Files._+}\n' +
' \n' +
'import framework.common.Logger.LoggerSystem\n' +
'import org.apache.commons.io.FileUtils\n' +
'@@ -43,7 +44,7 @@ object Tools {\n' +
' }\n' +
' \n' +
' def withTempDir[A](block: io.File => A): A = {\n' +
' val dir = [-io.File.-]createTemp[-F-]{+D+}i[-l-]{+r+}e{+ctory+}("codacy"[-, "-]{+)+}.t[-mp")-]{+oFile+}\n' +
' \n' +
' val res = block(dir)\n' +
' FileUtils.deleteDirectory(dir)\n' +
'diff --git a/components/enginePlugins/src/main/scala/metrics/js/Plato.scala b/components/enginePlugins/src/main/scala/metrics/js/Plato.scala\n' +
'index 5935d36..4e7e085 100644\n' +
'--- a/components/enginePlugins/src/main/scala/metrics/js/Plato.scala\n' +
'+++ b/components/enginePlugins/src/main/scala/metrics/js/Plato.scala\n' +
'@@ -24,7 +24,7 @@ object Plato extends PlatoReads {\n' +
' withTempDir {\n' +
' outputDirectory =>\n' +
' runTool(directory, files, outputDirectory) match {\n' +
' case ({+Some(+}_{+)+}, tmpFileMapper) =>\n' +
' val fileFolders = new io.File(outputDirectory, "files")\n' +
' \n' +
' fileFolders.list().flatMap {';
$(document).ready(function () {
var content = Diff2Html.getPrettyHtmlFromDiff(exInput);
$("body").html(content);
});
</script>
</head>
<body>
</body>
</html>

View file

@ -1,217 +0,0 @@
# Inspired by https://gist.github.com/danihodovic/a51eb0d9d4b29649c2d094f4251827dd
provider "aws" {
profile = "${var.aws_profile}"
region = "${var.aws_region}"
}
provider "aws" {
alias = "nvirginia"
profile = "${var.aws_profile}"
region = "us-east-1"
}
terraform {
backend "s3" {
region = "us-east-1"
encrypt = true
bucket = "terraform-state-bucket.rtfpessoa.xyz"
dynamodb_table = "terraform-state-table"
key = "diff2html.xyz"
}
}
resource "aws_acm_certificate" "cert" {
provider = "aws.nvirginia"
domain_name = "${var.domain}"
subject_alternative_names = ["*.${var.domain}"]
validation_method = "DNS"
lifecycle {
create_before_destroy = true
}
}
resource "aws_route53_record" "root_domain" {
zone_id = "${var.hosted_zone_id}"
name = "${var.domain}"
type = "A"
alias {
name = "${aws_cloudfront_distribution.cdn.domain_name}"
zone_id = "${aws_cloudfront_distribution.cdn.hosted_zone_id}"
evaluate_target_health = false
}
}
resource "aws_route53_record" "www_domain" {
zone_id = "${var.hosted_zone_id}"
name = "${local.www_domain}"
type = "A"
alias {
name = "${aws_cloudfront_distribution.www_cdn.domain_name}"
zone_id = "${aws_cloudfront_distribution.www_cdn.hosted_zone_id}"
evaluate_target_health = false
}
}
resource "aws_route53_record" "cert_validation" {
zone_id = "${var.hosted_zone_id}"
name = "${aws_acm_certificate.cert.domain_validation_options.0.resource_record_name}"
type = "${aws_acm_certificate.cert.domain_validation_options.0.resource_record_type}"
records = ["${aws_acm_certificate.cert.domain_validation_options.0.resource_record_value}"]
ttl = 60
}
resource "aws_acm_certificate_validation" "cert" {
provider = "aws.nvirginia"
certificate_arn = "${aws_acm_certificate.cert.arn}"
validation_record_fqdns = ["${aws_route53_record.cert_validation.fqdn}"]
}
resource "aws_cloudfront_origin_access_identity" "origin_access_identity" {
comment = "${var.domain} origin access identity"
}
locals {
s3_origin_id = "S3-${var.domain}"
s3_www_origin_id = "S3-www-${var.domain}"
www_domain = "www.${var.domain}"
}
resource "aws_s3_bucket" "site" {
bucket = "${var.domain}"
acl = "private"
policy = <<EOF
{
"Version": "2008-10-17",
"Statement": [{
"Sid": "AllowCloudFrontRead",
"Effect": "Allow",
"Principal": { "AWS": "${aws_cloudfront_origin_access_identity.origin_access_identity.iam_arn}" },
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::${var.domain}/*"
}]
}
EOF
}
resource "aws_cloudfront_distribution" "cdn" {
origin {
domain_name = "${aws_s3_bucket.site.bucket_regional_domain_name}"
origin_id = "${local.s3_origin_id}"
s3_origin_config {
origin_access_identity = "${aws_cloudfront_origin_access_identity.origin_access_identity.cloudfront_access_identity_path}"
}
}
# If using route53 aliases for DNS we need to declare it here too, otherwise we'll get 403s.
aliases = ["${var.domain}"]
enabled = true
is_ipv6_enabled = true
default_root_object = "index.html"
default_cache_behavior {
allowed_methods = ["GET", "HEAD", "OPTIONS"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "${local.s3_origin_id}"
forwarded_values {
query_string = true
cookies {
forward = "none"
}
}
min_ttl = 0
default_ttl = 86400
max_ttl = 31536000
compress = true
viewer_protocol_policy = "redirect-to-https"
}
price_class = "PriceClass_All"
restrictions {
geo_restriction {
restriction_type = "none"
locations = []
}
}
viewer_certificate {
acm_certificate_arn = "${aws_acm_certificate_validation.cert.certificate_arn}"
minimum_protocol_version = "TLSv1.1_2016"
ssl_support_method = "sni-only"
}
}
resource "aws_s3_bucket" "www_site" {
bucket = "${local.www_domain}"
acl = "public-read"
website {
redirect_all_requests_to = "https://${var.domain}"
}
}
resource "aws_cloudfront_distribution" "www_cdn" {
origin {
origin_id = "${local.s3_www_origin_id}"
domain_name = "${aws_s3_bucket.www_site.website_endpoint}"
custom_origin_config {
http_port = 80
https_port = 443
origin_protocol_policy = "http-only"
origin_ssl_protocols = ["TLSv1.1", "TLSv1.2"]
}
}
# If using route53 aliases for DNS we need to declare it here too, otherwise we'll get 403s.
aliases = ["${local.www_domain}"]
enabled = true
is_ipv6_enabled = true
default_cache_behavior {
allowed_methods = ["GET", "HEAD", "OPTIONS"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "${local.s3_www_origin_id}"
forwarded_values {
query_string = true
cookies {
forward = "none"
}
}
min_ttl = 0
default_ttl = 86400
max_ttl = 31536000
compress = true
viewer_protocol_policy = "redirect-to-https"
}
price_class = "PriceClass_All"
restrictions {
geo_restriction {
restriction_type = "none"
locations = []
}
}
viewer_certificate {
acm_certificate_arn = "${aws_acm_certificate_validation.cert.certificate_arn}"
minimum_protocol_version = "TLSv1.1_2016"
ssl_support_method = "sni-only"
}
}

View file

@ -1,7 +0,0 @@
output "route53_domain" {
value = "${aws_route53_record.root_domain.fqdn}"
}
output "cdn_domain" {
value = "${aws_cloudfront_distribution.cdn.domain_name}"
}

View file

@ -1,19 +0,0 @@
variable "aws_region" {
description = "The aws region to deploy"
default = "eu-west-1"
}
variable "aws_profile" {
description = "The aws profile to use"
default = "personal"
}
variable "domain" {
description = "The domain to deploy this page"
default = "diff2html.xyz"
}
variable "hosted_zone_id" {
description = "The hosted zone id where the domain will be created"
default = "Z2T76N7UKY0XQI"
}

View file

@ -1,5 +0,0 @@
{
"extends": "./tsconfig.json",
"exclude": ["node_modules"],
"include": ["./**/*"]
}

View file

@ -1,37 +0,0 @@
{
"include": ["src/**/*", "typings/**/*"],
"exclude": ["node_modules", "src/__tests__/**"],
"compilerOptions": {
"outDir": "bundles-out",
"module": "CommonJS",
"moduleResolution": "Node",
"target": "ES6",
"lib": ["ES6", "DOM"],
"jsx": "preserve",
"allowJs": false,
"checkJs": false,
"importHelpers": false,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"skipLibCheck": true,
"resolveJsonModule": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"noEmit": false,
"forceConsistentCasingInFileNames": true,
"isolatedModules": true,
"incremental": false,
"strictNullChecks": true,
"removeComments": true,
"preserveConstEnums": true,
"alwaysStrict": true,
"noImplicitAny": true,
"noImplicitThis": true
}
}

3
typings/merge.d.ts vendored
View file

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

View file

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

View file

@ -1,150 +0,0 @@
import path from 'path';
import webpack from 'webpack';
import HtmlWebpackPlugin from 'html-webpack-plugin';
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
import CopyPlugin from 'copy-webpack-plugin';
const pages = ['index', 'demo'];
type Plugin = ((this: webpack.Compiler, compiler: webpack.Compiler) => void) | webpack.WebpackPluginInstance;
function plugins(page: string): Plugin[] {
return [
new MiniCssExtractPlugin({
filename: '[name].css',
chunkFilename: '[id].css',
}),
new HtmlWebpackPlugin({
hash: true,
inject: true,
title: `${page} page`,
filename: `${page}.html`,
template: `./website/templates/pages/${page}/${page}.handlebars`,
minify: {
html5: true,
collapseWhitespace: true,
caseSensitive: true,
removeEmptyElements: false,
removeComments: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true,
},
}),
new CopyPlugin({
patterns: [
{ from: 'website/favicon.ico', to: 'favicon.ico' },
{ from: 'website/robots.txt', to: 'robots.txt' },
{ from: 'website/sitemap.xml', to: 'sitemap.xml' },
],
}),
];
}
const config: webpack.Configuration[] = pages.map(page => {
return {
entry: {
[page]: `./website/templates/pages/${page}/${page}.ts`,
},
output: {
path: path.resolve(__dirname, './docs'),
},
resolve: {
extensions: ['.tsx', '.ts', '.jsx', '.js'],
},
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/,
},
{
test: /\.(html)$/,
use: {
loader: 'html-loader',
options: {
attrs: ['img:src'],
},
},
},
{
test: /\.handlebars$/,
loader: 'handlebars-loader',
options: {
inlineRequires: '/images/',
precompileOptions: {
knownHelpersOnly: false,
},
helperDirs: [path.join(__dirname, 'website/templates/helpers')],
partialDirs: [path.join(__dirname, 'website/templates')],
},
},
{
test: /\.(gif|png|jpe?g|webp)$/i,
use: [
{
loader: 'file-loader',
options: {
name: '[name].[ext]?[hash]',
outputPath: 'images',
esModule: false,
},
},
{
loader: 'image-webpack-loader',
options: {
mozjpeg: {
progressive: true,
quality: 65,
},
optipng: {
enabled: true,
},
pngquant: {
quality: [0.65, 0.9],
speed: 4,
},
gifsicle: {
interlaced: false,
},
webp: {
quality: 75,
},
},
},
],
},
{
test: /\.(css)$/,
use: [MiniCssExtractPlugin.loader, { loader: 'css-loader', options: { importLoaders: 1 } }, 'postcss-loader'],
},
{
test: /\.woff(2)?(\?v=\d\.\d\.\d)?$/,
use: [
{
loader: 'url-loader',
options: {
limit: 1000,
mimetype: 'application/font-woff',
},
},
],
},
{
test: /\.(ttf|eot|svg)(\?v=\d\.\d\.\d)?$/,
loader: 'file-loader',
},
],
},
plugins: plugins(page),
};
});
export default config;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

View file

@ -1,22 +0,0 @@
.hero-green {
color: #26a65b;
}
.hero-black {
color: #353535;
}
.hero-red {
color: #cb2c37;
}
.button.is-green {
color: #fff;
background: #26a65b;
font-weight: 400;
}
.button.is-green:hover {
color: #fff;
background: #5dbe5d;
}

View file

@ -1,13 +0,0 @@
import 'bulma/css/bulma.css';
document.addEventListener('DOMContentLoaded', () => {
const $navbarBurgers = Array.prototype.slice.call(document.querySelectorAll('.navbar-burger'), 0);
$navbarBurgers.forEach((el: HTMLElement) => {
el.addEventListener('click', () => {
el.classList.toggle('is-active');
if (el.dataset.target !== undefined) {
document.getElementById(el.dataset.target)?.classList.toggle('is-active');
}
});
});
});

View file

@ -1 +0,0 @@
User-agent: *

View file

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset
xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9
http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
<url>
<loc>https://diff2html.xyz/</loc>
</url>
<url>
<loc>https://diff2html.xyz/index.html</loc>
</url>
<url>
<loc>https://diff2html.xyz/demo.html</loc>
</url>
</urlset>

View file

@ -1,15 +0,0 @@
import handlebars, { HelperOptions } from 'handlebars';
const loadPartial = <T>(name: string): handlebars.Template<T> => {
let partial = handlebars.partials[name];
if (typeof partial === 'string') {
partial = handlebars.compile(partial);
handlebars.partials[name] = partial;
}
return partial;
};
export default (name: string, options: HelperOptions): string => {
const partial = loadPartial(name) || options.fn;
return partial(this, { data: options.hash });
};

View file

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

View file

@ -1,147 +0,0 @@
<section class="hero">
<div class="hero-body">
<div class="container">
<h1 class="title is-size-1 is-spaced">Demo<a href="#help">
<svg fill="currentColor" height="32" class="octicon octicon-unverified" viewBox="0 0 16 16" version="1.1" width="64"
aria-hidden="true">
<path
d="M15.67 7.06l-1.08-1.34c-.17-.22-.28-.48-.31-.77l-.19-1.7a1.51 1.51 0 0 0-1.33-1.33l-1.7-.19c-.3-.03-.56-.16-.78-.33L8.94.32c-.55-.44-1.33-.44-1.88 0L5.72 1.4c-.22.17-.48.28-.77.31l-1.7.19c-.7.08-1.25.63-1.33 1.33l-.19 1.7c-.03.3-.16.56-.33.78L.32 7.05c-.44.55-.44 1.33 0 1.88l1.08 1.34c.17.22.28.48.31.77l.19 1.7c.08.7.63 1.25 1.33 1.33l1.7.19c.3.03.56.16.78.33l1.34 1.08c.55.44 1.33.44 1.88 0l1.34-1.08c.22-.17.48-.28.77-.31l1.7-.19c.7-.08 1.25-.63 1.33-1.33l.19-1.7c.03-.3.16-.56.33-.78l1.08-1.34c.44-.55.44-1.33 0-1.88zM9 11.5c0 .28-.22.5-.5.5h-1c-.27 0-.5-.22-.5-.5v-1c0-.28.23-.5.5-.5h1c.28 0 .5.22.5.5v1zm1.56-4.89c-.06.17-.17.33-.3.47-.13.16-.14.19-.33.38-.16.17-.31.3-.52.45-.11.09-.2.19-.28.27-.08.08-.14.17-.19.27-.05.1-.08.19-.11.3-.03.11-.03.13-.03.25H7.13c0-.22 0-.31.03-.48.03-.19.08-.36.14-.52.06-.14.14-.28.25-.42.11-.13.23-.25.41-.38.27-.19.36-.3.48-.52.12-.22.2-.38.2-.59 0-.27-.06-.45-.2-.58-.13-.13-.31-.19-.58-.19-.09 0-.19.02-.3.05-.11.03-.17.09-.25.16-.08.07-.14.11-.2.2a.41.41 0 0 0-.09.28h-2c0-.38.13-.56.27-.83.16-.27.36-.5.61-.67.25-.17.55-.3.88-.38.33-.08.7-.13 1.09-.13.44 0 .83.05 1.17.13.34.09.63.22.88.39.23.17.41.38.55.63.13.25.19.55.19.88 0 .22 0 .42-.08.59l-.02-.01z">
</path>
</svg>
</a>
</h1>
<p>Introduce the GitHub, Bitbucket or GitLab commit, pull request, merge request url
or any other git or unified compatible diff.</p>
<p>Review the code in a pretty HTML representation of the diff with code syntax highlight, line similarity
matching and many other improvements.
</p>
</div>
</div>
</section>
<section class="section">
<div class="container">
<h2 class="title is-size-3 is-spaced">Options</h2>
<div class="columns">
<div class="column">
<input id="url" class="diff-url-input is-size-5" type="text" name="url" placeholder="URL" />
</div>
<div class="column is-narrow">
<a id="url-btn" class="diff-url-btn button is-green is-small" href="#">Load</a>
</div>
</div>
<div class="columns is-desktop is-multiline">
<div class="column is-one-sixth-widescreen">
<label title="Output format of the HTML, either line by line or side by side">
<p>Output Format</p>
<select class="options-label-value" id="diff-url-options-output-format" name="outputFormat">
<option value="line-by-line" selected>Line by Line</option>
<option value="side-by-side">Side by Side</option>
</select>
</label>
</div>
<div class="column is-one-sixth-widescreen">
<label title="Color scheme to render. Auto uses user preference.">
<p>Color Scheme</p>
<select class="options-label-value" id="diff-url-options-color-scheme" name="colorScheme">
<option value="light" selected>Light</option>
<option value="dark">Dark</option>
<option value="auto">Auto</option>
</select>
</label>
</div>
<div class="column is-one-sixth-widescreen">
<label title="Show the file list summary before the diff">
<p>File Summary</p>
<input class="options-label-value" id="diff-url-options-show-files" type="checkbox" name="drawFileList"
checked />
</label>
</div>
<div class="column is-one-sixth-widescreen">
<label title="Level of matching for the comparison algorithm">
<p>Matching Type</p>
<select class="options-label-value" id="diff-url-options-matching" name="matching">
<option value="lines">Lines</option>
<option value="words" selected>Words</option>
<option value="none">None</option>
</select>
</label>
</div>
<div class="column is-one-sixth-widescreen">
<label title="Similarity threshold for the matching algorithm">
<p>Words Threshold</p>
<input class="options-label-value" id="diff-url-options-match-words-threshold" type="number"
name="matchWordsThreshold" value="0.25" step="0.05" min="0" max="1" />
</label>
</div>
<div class="column is-one-sixth-widescreen">
<label title="Maximum number of comparison performed by the matching algorithm in a block of changes">
<p>Max Comparisons</p>
<input class="options-label-value" id="diff-url-options-matching-max-comparisons" type="number"
name="matchingMaxComparisons" value="2500" step="100" min="0" />
</label>
</div>
</div>
</div>
</section>
<section class="section">
<div class="container">
<div id="url-diff-container" style="margin: 0 auto;"></div>
</div>
</section>
<section class="section">
<div class="container block">
<h3 id="help" class="title is-size-3 is-spaced">Help</h3>
<ul>
<li class="block">
<p class="has-text-weight-bold">Why should I use this instead of GitHub, Bitbucket or GitLab?</p>
<p>There are multiple advantages to using diff2html, but they can be divided in two categories: portability and
features.
</p>
<p>
Regarding portability, diff2html makes it easy for people to share static html representations of diffs with
anyone.
</p>
<p>
Regarding features you can have code syntax highlight, line similarity matching (similar lines are together),
line by Line and side by side diffs and easy code selection.
</p>
<p>All of this is completely independent of the provider you use to version the code.</p>
</li>
<li class="block">
<p class="has-text-weight-bold">What urls are supported in this demo?</p>
<p>Any GitHub, Bitbucket or GitLab commit, pull/merge request urls.</p>
<p>Any Git or Unified Raw Diff or Patch urls.</p>
</li>
<li class="block">
<p class="has-text-weight-bold">Can I share this demo with a friend, colleague or co-worker?</p>
<p>Just copy the url from the page, which should contain all the customizations and reference for the diff you
introduced.</p>
<p>ex: <a
href="demo.html?matching=none&matchWordsThreshold=0.25&maxLineLengthHighlight=10000&diffStyle=word&renderNothingWhenEmpty=0&matchingMaxComparisons=2500&maxLineSizeInBlockForComparison=200&outputFormat=line-by-line&drawFileList=1&synchronisedScroll=1&highlight=1&fileListToggle=1&fileListStartVisible=0&diff=https://github.com/rtfpessoa/diff2html/pull/106">https://diff2html.xyz/demo.html?matching=none&matchWordsThreshold=0.25&maxLineLengthHighlight=10000&diffStyle=word&renderNothingWhenEmpty=0&matchingMaxComparisons=2500&maxLineSizeInBlockForComparison=200&outputFormat=line-by-line&drawFileList=1&synchronisedScroll=1&highlight=1&fileListToggle=1&fileListStartVisible=0&diff=https://github.com/rtfpessoa/diff2html/pull/106</a>
</p>
</li>
<li class="block">
<p class="has-text-weight-bold">How can I paste the diff output directly in a page?</p>
<p><a href="https://diffy.org/">diffy.org</a> is an amazing tool created by <a
href="https://github.com/pbu88">pbu88</a>
to share your diffs and uses diff2html under the hood.</p>
<p>Also, <a href="https://www.npmjs.com/package/diff2html-cli">diff2html-cli</a> can directly publish diffs to
<a href="https://diffy.org/">diffy.org</a></p>
</li>
</ul>
</div>
</section>
<section class="section">
<div class="container">
<h3 class="title is-size-3 is-spaced">Thank you</h3>
<p>I want to thank <a href="https://github.com/kevinsimper">kevinsimper</a> for this great idea,
providing better diff support for existing online services.
</p>
</div>
</section>

View file

@ -1,25 +0,0 @@
.diff-url-input {
width: 100%;
}
.diff-url-btn.is-small {
font-size: 0.85rem;
}
.d2h-dark-color-scheme .title {
color: #fff;
}
.d2h-dark-color-scheme .footer {
background-color: #2d2d2d;
}
@media (prefers-color-scheme: dark) {
.d2h-auto-color-scheme .title {
color: #fff;
}
.d2h-auto-color-scheme .footer {
background-color: #2d2d2d;
}
}

View file

@ -1,4 +0,0 @@
{{#partial "content"}}
{{> content}}
{{/partial}}
{{> ../../template}}

View file

@ -1,323 +0,0 @@
import { Diff2HtmlUI, defaultDiff2HtmlUIConfig, Diff2HtmlUIConfig } from '../../../../src/ui/js/diff2html-ui-slim';
import '../../../main.ts';
import '../../../main.css';
import './github-highlights.css';
import '../../../../src/ui/css/diff2html.css';
import './demo.css';
import { colorSchemeToCss } from '../../../../src/render-utils';
import { ColorSchemeType } from '../../../../src/types';
/*
* Example URLs:
*
* https://github.com/rtfpessoa/diff2html/commit/7d02e67f3b3386ac5d804f974d025cd7a1165839
* https://github.com/rtfpessoa/diff2html/pull/106
*
* https://gitlab.com/gitlab-org/gitlab-ce/commit/4e963fed42ad518caa7353d361a38a1250c99c41
* https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6763
*
* https://bitbucket.org/atlassian/amps/commits/52c38116f12475f75af4a147b7a7685478b83eca
* https://bitbucket.org/atlassian/amps/pull-requests/236
*/
type URLParams = {
diff?: string;
diffTooBigMessage?: string;
[key: string]: string | boolean | number | Map<string, string> | undefined;
};
const searchParam = 'diff';
function getParamsFromSearch(search: string): URLParams {
try {
return search
.split('?')[1]
.split('&')
.reduce((urlParams, e) => {
const values = e.split('=');
return {
...urlParams,
[values[0]]: values[1],
};
}, {});
} catch (_ignore) {
return {};
}
}
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[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,
);
}
type Request = {
url: string;
headers: Headers;
};
function prepareRequest(url: string): Request {
if (!validateUrl(url)) {
const errorMsg = 'Invalid url provided!';
console.error(errorMsg);
throw new Error(errorMsg);
}
let fetchUrl;
const headers = new Headers();
const githubCommitUrl =
/^https?:\/\/(?:www\.)?github\.com\/(.*?)\/(.*?)\/commit\/(.*?)(?:\.diff)?(?:\.patch)?(?:\/.*)?$/;
const githubPrUrl = /^https?:\/\/(?:www\.)?github\.com\/(.*?)\/(.*?)\/pull\/(.*?)(?:\.diff)?(?:\.patch)?(?:\/.*)?$/;
const gitlabCommitUrl =
/^https?:\/\/(?:www\.)?gitlab\.com\/(.*?)\/(.*?)\/commit\/(.*?)(?:\.diff)?(?:\.patch)?(?:\/.*)?$/;
const gitlabPrUrl =
/^https?:\/\/(?:www\.)?gitlab\.com\/(.*?)\/(.*?)\/merge_requests\/(.*?)(?:\.diff)?(?:\.patch)?(?:\/.*)?$/;
const bitbucketCommitUrl = /^https?:\/\/(?:www\.)?bitbucket\.org\/(.*?)\/(.*?)\/commits\/(.*?)(?:\/raw)?(?:\/.*)?$/;
const bitbucketPrUrl = /^https?:\/\/(?:www\.)?bitbucket\.org\/(.*?)\/(.*?)\/pull-requests\/(.*?)(?:\/.*)?$/;
function gitLabUrlGen(userName: string, projectName: string, type: string, value: string): string {
return (
'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;
}
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';
}
return baseUrl + userName + '/' + projectName + '/diff/' + value;
}
let values;
if ((values = githubCommitUrl.exec(url))) {
fetchUrl = gitHubUrlGen(values[1], values[2], 'commits', values[3]);
} else if ((values = githubPrUrl.exec(url))) {
fetchUrl = gitHubUrlGen(values[1], values[2], 'pulls', values[3]);
} else if ((values = gitlabCommitUrl.exec(url))) {
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]);
} else if ((values = bitbucketCommitUrl.exec(url))) {
fetchUrl = bitbucketUrlGen(values[1], values[2], 'commit', values[3]);
} else if ((values = bitbucketPrUrl.exec(url))) {
fetchUrl = bitbucketUrlGen(values[1], values[2], 'pullrequests', values[3]);
} else {
fetchUrl = url;
}
return {
url: fetchUrl,
headers: headers,
};
}
function getConfiguration(urlParams: URLParams): Diff2HtmlUIConfig {
// Removing `diff` and `diffTooBigMessage` form `urlParams` to avoid being inserted
const { diff, diffTooBigMessage, ...urlParamsRest } = urlParams;
const defaultColorScheme: ColorSchemeType =
window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
? ColorSchemeType.DARK
: ColorSchemeType.LIGHT;
const config: URLParams = {
...defaultDiff2HtmlUIConfig,
colorScheme: defaultColorScheme,
...urlParamsRest,
};
return Object.entries(config).reduce((object, [k, v]) => {
const newObject = !Number.isNaN(Number(v))
? { [k]: Number(v) }
: v === 'true' || v === 'false'
? { [k]: Boolean(v) }
: { [k]: v };
return { ...object, ...newObject };
}, {});
}
async function getDiff(request: Request): Promise<string> {
try {
const result = await fetch(request.url, {
method: 'GET',
headers: request.headers,
mode: 'cors',
cache: 'default',
});
return result.text();
} catch (error) {
console.error('Failed to retrieve diff', error);
throw error;
}
}
function draw(diffString: string, config: Diff2HtmlUIConfig, elements: Elements): void {
const diff2htmlUi = new Diff2HtmlUI(elements.structure.diffTarget, diffString, config);
setBodyColorScheme(diff2htmlUi.config.colorScheme);
diff2htmlUi.draw();
}
function setBodyColorScheme(colorScheme: ColorSchemeType): void {
document.body.className = colorSchemeToCss(colorScheme);
}
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';
if (currentUrl !== elements.url.input.value) elements.url.input.value = currentUrl;
const request = prepareRequest(currentUrl);
const initialConfiguration = getConfiguration(urlParams);
const initialDiff = await getDiff(request);
return [initialConfiguration, initialDiff];
}
function updateBrowserUrl(config: Diff2HtmlUIConfig, newDiffUrl: string): void {
const paramString = Object.entries(config)
.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);
}
type Elements = {
structure: {
diffTarget: HTMLElement;
};
url: {
input: HTMLInputElement;
button: HTMLElement;
};
options: {
outputFormat: HTMLInputElement;
colorScheme: HTMLInputElement;
matching: HTMLInputElement;
wordsThreshold: HTMLInputElement;
matchingMaxComparisons: HTMLInputElement;
};
checkboxes: {
drawFileList: HTMLInputElement;
};
};
function isHTMLInputElement(arg?: unknown): arg is HTMLInputElement {
return arg !== null && (arg as HTMLInputElement)?.value !== undefined;
}
function getHTMLInputElementById(id: string): HTMLInputElement {
const element = document.getElementById(id);
if (!isHTMLInputElement(element)) {
throw new Error(`Could not find html input element with id '${id}'`);
}
return element;
}
function getHTMLElementById(id: string): HTMLElement {
const element = document.getElementById(id);
if (element === null) {
throw new Error(`Could not find html element with id '${id}'`);
}
return element;
}
document.addEventListener('DOMContentLoaded', async () => {
// Improves browser compatibility
// eslint-disable-next-line @typescript-eslint/no-require-imports
require('whatwg-fetch');
const drawAndUpdateUrl = async (
diffUrl: string,
diffString: string,
config: Diff2HtmlUIConfig,
elements: Elements,
): Promise<void> => {
updateBrowserUrl(config, diffUrl);
const newRequest = prepareRequest(diffUrl);
diffString = await getDiff(newRequest);
draw(diffString, config, elements);
};
const elements: Elements = {
structure: {
diffTarget: getHTMLElementById('url-diff-container'),
},
url: {
input: getHTMLInputElementById('url'),
button: getHTMLElementById('url-btn'),
},
options: {
outputFormat: getHTMLInputElementById('diff-url-options-output-format'),
colorScheme: getHTMLInputElementById('diff-url-options-color-scheme'),
matching: getHTMLInputElementById('diff-url-options-matching'),
wordsThreshold: getHTMLInputElementById('diff-url-options-match-words-threshold'),
matchingMaxComparisons: getHTMLInputElementById('diff-url-options-matching-max-comparisons'),
},
checkboxes: {
drawFileList: getHTMLInputElementById('diff-url-options-show-files'),
},
};
let [config, diffString] = await prepareInitialState(elements);
// Update HTML inputs from any changes in URL
if (config.outputFormat) elements.options.outputFormat.value = config.outputFormat;
if (config.colorScheme) elements.options.colorScheme.value = config.colorScheme;
if (config.drawFileList) elements.checkboxes.drawFileList.checked = config.drawFileList;
if (config.matching) elements.options.matching.value = config.matching;
if (config.matchWordsThreshold) elements.options.wordsThreshold.value = config.matchWordsThreshold.toString();
if (config.matchingMaxComparisons)
elements.options.matchingMaxComparisons.value = config.matchingMaxComparisons.toString();
Object.entries(elements.options).forEach(([option, element]) =>
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', () => {
config = { ...config, [option]: checkbox.checked };
drawAndUpdateUrl(elements.url.input.value, diffString, config, elements);
}),
);
elements.url.button.addEventListener('click', async e => {
e.preventDefault();
const newDiffUrl = elements.url.input.value;
const newRequest = prepareRequest(newDiffUrl);
diffString = await getDiff(newRequest);
drawAndUpdateUrl(newDiffUrl, diffString, config, elements);
});
return drawAndUpdateUrl(elements.url.input.value, diffString, config, elements);
});

View file

@ -1,332 +0,0 @@
/*!
Theme Adapted from highlight.js github theme
Theme: Modified GitHub Dark
Description: Dark theme as seen on github.com, modified for diff2html demo
Author: github.com
Maintainer: @Hirse
Updated: 2021-05-15
Outdated base version: https://github.com/primer/github-syntax-dark
Current colors taken from GitHub's CSS
*/
pre code.hljs {
display: block;
overflow-x: auto;
padding: 1em;
}
code.hljs {
padding: 3px 5px;
}
.hljs {
color: #24292e;
}
.hljs-doctag,
.hljs-keyword,
.hljs-meta .hljs-keyword,
.hljs-template-tag,
.hljs-template-variable,
.hljs-type,
.hljs-variable.language_ {
color: #d73a49;
}
.hljs-title,
.hljs-title.class_,
.hljs-title.class_.inherited__,
.hljs-title.function_ {
color: #6f42c1;
}
.hljs-attr,
.hljs-attribute,
.hljs-literal,
.hljs-meta,
.hljs-number,
.hljs-operator,
.hljs-selector-attr,
.hljs-selector-class,
.hljs-selector-id,
.hljs-variable {
color: #005cc5;
}
.hljs-meta .hljs-string,
.hljs-regexp,
.hljs-string {
color: #032f62;
}
.hljs-built_in,
.hljs-symbol {
color: #e36209;
}
.hljs-code,
.hljs-comment,
.hljs-formula {
color: #6a737d;
}
.hljs-name,
.hljs-quote,
.hljs-selector-pseudo,
.hljs-selector-tag {
color: #22863a;
}
.hljs-subst {
color: #24292e;
}
.hljs-section {
color: #005cc5;
font-weight: 700;
}
.hljs-bullet {
color: #735c0f;
}
.hljs-emphasis {
color: #24292e;
font-style: italic;
}
.hljs-strong {
color: #24292e;
font-weight: 700;
}
.hljs-addition {
color: #22863a;
background-color: #f0fff4;
}
.hljs-deletion {
color: #b31d28;
background-color: #ffeef0;
}
.d2h-dark-color-scheme .hljs {
color: #c9d1d9;
}
.d2h-dark-color-scheme .hljs-doctag,
.d2h-dark-color-scheme .hljs-keyword,
.d2h-dark-color-scheme .hljs-meta .hljs-keyword,
.d2h-dark-color-scheme .hljs-template-tag,
.d2h-dark-color-scheme .hljs-template-variable,
.d2h-dark-color-scheme .hljs-type,
.d2h-dark-color-scheme .hljs-variable.language_ {
color: #ff7b72;
}
.d2h-dark-color-scheme .hljs-title,
.d2h-dark-color-scheme .hljs-title.class_,
.d2h-dark-color-scheme .hljs-title.class_.inherited__,
.d2h-dark-color-scheme .hljs-title.function_ {
color: #d2a8ff;
}
.d2h-dark-color-scheme .hljs-attr,
.d2h-dark-color-scheme .hljs-attribute,
.d2h-dark-color-scheme .hljs-literal,
.d2h-dark-color-scheme .hljs-meta,
.d2h-dark-color-scheme .hljs-number,
.d2h-dark-color-scheme .hljs-operator,
.d2h-dark-color-scheme .hljs-selector-attr,
.d2h-dark-color-scheme .hljs-selector-class,
.d2h-dark-color-scheme .hljs-selector-id,
.d2h-dark-color-scheme .hljs-variable {
color: #79c0ff;
}
.d2h-dark-color-scheme .hljs-meta .hljs-string,
.d2h-dark-color-scheme .hljs-regexp,
.d2h-dark-color-scheme .hljs-string {
color: #a5d6ff;
}
.d2h-dark-color-scheme .hljs-built_in,
.d2h-dark-color-scheme .hljs-symbol {
color: #ffa657;
}
.d2h-dark-color-scheme .hljs-code,
.d2h-dark-color-scheme .hljs-comment,
.d2h-dark-color-scheme .hljs-formula {
color: #8b949e;
}
.d2h-dark-color-scheme .hljs-name,
.d2h-dark-color-scheme .hljs-quote,
.d2h-dark-color-scheme .hljs-selector-pseudo,
.d2h-dark-color-scheme .hljs-selector-tag {
color: #7ee787;
}
.d2h-dark-color-scheme .hljs-subst {
color: #c9d1d9;
}
.d2h-dark-color-scheme .hljs-section {
color: #1f6feb;
font-weight: 700;
}
.d2h-dark-color-scheme .hljs-bullet {
color: #f2cc60;
}
.d2h-dark-color-scheme .hljs-emphasis {
color: #c9d1d9;
font-style: italic;
}
.d2h-dark-color-scheme .hljs-strong {
color: #c9d1d9;
font-weight: 700;
}
.d2h-dark-color-scheme .hljs-addition {
color: #aff5b4;
background-color: #033a16;
}
.d2h-dark-color-scheme .hljs-deletion {
color: #ffdcd7;
background-color: #67060c;
}
@media (prefers-color-scheme: dark) {
.d2h-auto-color-scheme .hljs {
color: #c9d1d9;
}
.d2h-auto-color-scheme .hljs-doctag,
.d2h-auto-color-scheme .hljs-keyword,
.d2h-auto-color-scheme .hljs-meta .hljs-keyword,
.d2h-auto-color-scheme .hljs-template-tag,
.d2h-auto-color-scheme .hljs-template-variable,
.d2h-auto-color-scheme .hljs-type,
.d2h-auto-color-scheme .hljs-variable.language_ {
color: #ff7b72;
}
.d2h-auto-color-scheme .hljs-title,
.d2h-auto-color-scheme .hljs-title.class_,
.d2h-auto-color-scheme .hljs-title.class_.inherited__,
.d2h-auto-color-scheme .hljs-title.function_ {
color: #d2a8ff;
}
.d2h-auto-color-scheme .hljs-attr,
.d2h-auto-color-scheme .hljs-attribute,
.d2h-auto-color-scheme .hljs-literal,
.d2h-auto-color-scheme .hljs-meta,
.d2h-auto-color-scheme .hljs-number,
.d2h-auto-color-scheme .hljs-operator,
.d2h-auto-color-scheme .hljs-selector-attr,
.d2h-auto-color-scheme .hljs-selector-class,
.d2h-auto-color-scheme .hljs-selector-id,
.d2h-auto-color-scheme .hljs-variable {
color: #79c0ff;
}
.d2h-auto-color-scheme .hljs-meta .hljs-string,
.d2h-auto-color-scheme .hljs-regexp,
.d2h-auto-color-scheme .hljs-string {
color: #a5d6ff;
}
.d2h-auto-color-scheme .hljs-built_in,
.d2h-auto-color-scheme .hljs-symbol {
color: #ffa657;
}
.d2h-auto-color-scheme .hljs-code,
.d2h-auto-color-scheme .hljs-comment,
.d2h-auto-color-scheme .hljs-formula {
color: #8b949e;
}
.d2h-auto-color-scheme .hljs-name,
.d2h-auto-color-scheme .hljs-quote,
.d2h-auto-color-scheme .hljs-selector-pseudo,
.d2h-auto-color-scheme .hljs-selector-tag {
color: #7ee787;
}
.d2h-auto-color-scheme .hljs-subst {
color: #c9d1d9;
}
.d2h-auto-color-scheme .hljs-section {
color: #1f6feb;
font-weight: 700;
}
.d2h-auto-color-scheme .hljs-bullet {
color: #f2cc60;
}
.d2h-auto-color-scheme .hljs-emphasis {
color: #c9d1d9;
font-style: italic;
}
.d2h-auto-color-scheme .hljs-strong {
color: #c9d1d9;
font-weight: 700;
}
.d2h-auto-color-scheme .hljs-addition {
color: #aff5b4;
background-color: #033a16;
}
.d2h-auto-color-scheme .hljs-deletion {
color: #ffdcd7;
background-color: #67060c;
}
}
@media (prefers-color-scheme: light) {
.d2h-auto-color-scheme .hljs {
color: #24292e;
}
.d2h-auto-color-scheme .hljs-doctag,
.d2h-auto-color-scheme .hljs-keyword,
.d2h-auto-color-scheme .hljs-meta .hljs-keyword,
.d2h-auto-color-scheme .hljs-template-tag,
.d2h-auto-color-scheme .hljs-template-variable,
.d2h-auto-color-scheme .hljs-type,
.d2h-auto-color-scheme .hljs-variable.language_ {
color: #d73a49;
}
.d2h-auto-color-scheme .hljs-title,
.d2h-auto-color-scheme .hljs-title.class_,
.d2h-auto-color-scheme .hljs-title.class_.inherited__,
.d2h-auto-color-scheme .hljs-title.function_ {
color: #6f42c1;
}
.d2h-auto-color-scheme .hljs-attr,
.d2h-auto-color-scheme .hljs-attribute,
.d2h-auto-color-scheme .hljs-literal,
.d2h-auto-color-scheme .hljs-meta,
.d2h-auto-color-scheme .hljs-number,
.d2h-auto-color-scheme .hljs-operator,
.d2h-auto-color-scheme .hljs-selector-attr,
.d2h-auto-color-scheme .hljs-selector-class,
.d2h-auto-color-scheme .hljs-selector-id,
.d2h-auto-color-scheme .hljs-variable {
color: #005cc5;
}
.d2h-auto-color-scheme .hljs-meta .hljs-string,
.d2h-auto-color-scheme .hljs-regexp,
.d2h-auto-color-scheme .hljs-string {
color: #032f62;
}
.d2h-auto-color-scheme .hljs-built_in,
.d2h-auto-color-scheme .hljs-symbol {
color: #e36209;
}
.d2h-auto-color-scheme .hljs-code,
.d2h-auto-color-scheme .hljs-comment,
.d2h-auto-color-scheme .hljs-formula {
color: #6a737d;
}
.d2h-auto-color-scheme .hljs-name,
.d2h-auto-color-scheme .hljs-quote,
.d2h-auto-color-scheme .hljs-selector-pseudo,
.d2h-auto-color-scheme .hljs-selector-tag {
color: #22863a;
}
.d2h-auto-color-scheme .hljs-subst {
color: #24292e;
}
.d2h-auto-color-scheme .hljs-section {
color: #005cc5;
font-weight: 700;
}
.d2h-auto-color-scheme .hljs-bullet {
color: #735c0f;
}
.d2h-auto-color-scheme .hljs-emphasis {
color: #24292e;
font-style: italic;
}
.d2h-auto-color-scheme .hljs-strong {
color: #24292e;
font-weight: 700;
}
.d2h-auto-color-scheme .hljs-addition {
color: #22863a;
background-color: #f0fff4;
}
.d2h-auto-color-scheme .hljs-deletion {
color: #b31d28;
background-color: #ffeef0;
}
}

View file

@ -1,518 +0,0 @@
<section class="hero">
<div class="hero-body">
<div class="container has-text-centered">
<span class="hero-booticon">
<span class="hero-green">diff</span><span class="hero-black">2</span><span class="hero-red">html</span>
</span>
<h1 class="title">Diff parser and pretty html generator</h1>
<h2 class="subtitle">Better diffs, unmatched reviews</h2>
<a class="button is-green is-medium" href="demo.html">Demo</a>
</div>
<div class="container has-text-centered">
<figure class="image">
<img class="hero-img" src="./images/snapshot-3.png"
alt="Diff2Html pretty html side-by-side example render">
</figure>
</div>
</div>
</section>
<section class="section">
<div class="container has-text-centered">
<h4 class="title is-size-4 is-spaced">Each diff provides a comprehensive visualization of the code changes,
helping developers identify problems and better understand the changes.</h4>
<div class="columns">
<div class="column">
<svg aria-hidden="true" class="octicon octicon-diff" height="50" width="50" version="1.1"
viewBox="0 0 14 16">
<path
d="M6 7h2v1H6v2h-1V8H3v-1h2V5h1v2zM3 13h5v-1H3v1z m4.5-11l3.5 3.5v9.5c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1V3c0-0.55 0.45-1 1-1h6.5z m2.5 4L7 3H1v12h9V6zM8.5 0S3 0 3 0v1h5l4 4v8h1V4.5L8.5 0z">
</path>
</svg>
<p class="is-size-6"><strong>Line by Line and Side by Side changes</strong></p>
<p>Each diff features a line by line and side by side preview of your
changes.</p>
</div>
<div class="column">
<svg aria-hidden="true" class="octicon octicon-tasklist" height="50" width="50" version="1.1"
viewBox="0 0 16 16">
<path
d="M15.41 9H7.59c-0.59 0-0.59-0.41-0.59-1s0-1 0.59-1h7.81c0.59 0 0.59 0.41 0.59 1s0 1-0.59 1zM9.59 4c-0.59 0-0.59-0.41-0.59-1s0-1 0.59-1h5.81c0.59 0 0.59 0.41 0.59 1s0 1-0.59 1H9.59zM0 3.91l1.41-1.3 1.59 1.59L7.09 0l1.41 1.41-5.5 5.5L0 3.91z m7.59 8.09h7.81c0.59 0 0.59 0.41 0.59 1s0 1-0.59 1H7.59c-0.59 0-0.59-0.41-0.59-1s0-1 0.59-1z">
</path>
</svg>
<p class="is-size-6"><strong>Code syntax highlight</strong></p>
<p>All the code changes are syntax highlighted using <a
href="https://highlightjs.org/">highlight.js</a>,
providing more readability.</p>
</div>
<div class="column">
<svg aria-hidden="true" class="octicon octicon-clippy" height="50" width="50" version="1.1"
viewBox="0 0 14 16">
<path
d="M2 12h4v1H2v-1z m5-6H2v1h5v-1z m2 3V7L6 10l3 3V11h5V9H9z m-4.5-1H2v1h2.5v-1zM2 11h2.5v-1H2v1z m9 1h1v2c-0.02 0.28-0.11 0.52-0.3 0.7s-0.42 0.28-0.7 0.3H1c-0.55 0-1-0.45-1-1V3c0-0.55 0.45-1 1-1h3C4 0.89 4.89 0 6 0s2 0.89 2 2h3c0.55 0 1 0.45 1 1v5h-1V5H1v9h10V12zM2 4h8c0-0.55-0.45-1-1-1h-1c-0.55 0-1-0.45-1-1s-0.45-1-1-1-1 0.45-1 1-0.45 1-1 1h-1c-0.55 0-1 0.45-1 1z">
</path>
</svg>
<p class="is-size-6"><strong>Line similarity matching</strong></p>
<p>Similar lines are paired, allowing for easier change tracking.</p>
</div>
</div>
</div>
<hr>
<div id="install" class="container">
<h4 class="title is-size-4 is-spaced"><strong>Install with npm</strong></h4>
<div class="code-example">
<span class="is-unselectable">&gt; $ </span><span class="has-text-grey-light">npm install diff2html</span>
<span class="button clipboard" data-clipboard-text="npm install diff2html" title="Copy">Copy</span>
</div>
<p>
<a href="https://github.com/rtfpessoa/diff2html#usage" target="_blank" rel="noopener" rel="noreferrer">
Find usage examples in the Docs
</a>
</p>
</div>
<hr>
<div id="cli" class="container">
<div class="columns">
<div class="column">
<h4 class="title is-size-4 is-spaced"><strong>With command line integration</strong></h4>
<p>We work hard to make sure you can have your diffs in a simple and flexible
way. Go here for the <a href="https://github.com/rtfpessoa/diff2html-cli#readme" target="_blank"
rel="noopener" rel="noreferrer">full
documentation</a>.
</p>
</div>
<div class="column">
<div class="code-example">
<p>
<span class="is-unselectable">&gt; $ </span>
<span class="has-text-grey-light">npm install -g diff2html-cli</span>
</p>
<p class="is-unselectable">diff2html cli installed!</p>
<p>
<span class="is-unselectable">&gt; $ </span>
<span class="has-text-grey-light">diff2html</span>
</p>
<p class="is-unselectable">Check previous commit changes on your browser</p>
<p>
<span class="is-unselectable">&gt; $ </span>
<span class="has-text-grey-light">is that it?</span>
</p>
<p class="is-unselectable">Yup, it's that simple.</p>
</div>
</div>
</div>
</div>
<hr>
<div id="users" class="container">
<h4 class="title is-size-4 is-spaced"><strong>Sponsor</strong></h4>
<div class="columns is-desktop is-multiline">
<div class="column is-half-widescreen is-flex">
<div class="box is-flex is-fullwidth is-vertical">
<header>
<div class="flex">
<div class="flex">
<img src="https://huggingface.co/front/assets/huggingface_logo-noborder.svg" style="height: 30px;">
<div>
<div style="margin-left: 5px;" class="is-size-5 has-text-weight-bold flex">Hugging Face</div>
</div>
</header>
<section class="media-content">
<p>The AI community building the future. Build, train and deploy state of the art models powered by the reference open source in natural language processing.</p>
</section>
<footer>
<a href="https://huggingface.co/" target="_blank" rel="noopener"
rel="noreferrer">Website</a>
</footer>
</div>
</div>
</div>
<h4 class="title is-size-4 is-spaced"><strong>Other projects using diff2html</strong></h4>
<div class="columns is-desktop is-multiline">
<div class="column is-one-quarter-widescreen is-flex" style="min-height: 166px;">
<div class="box is-flex is-fullwidth is-vertical">
<header>
<p class="is-size-5 has-text-weight-bold">Exercism</p>
</header>
<section class="media-content">
<p>Get really good at programming.</p>
</section>
<footer>
<a href="https://exercism.org/" target="_blank" rel="noopener"
rel="noreferrer">Website</a>
</footer>
</div>
</div>
<div class="column is-one-quarter-widescreen is-flex">
<div class="box is-flex is-fullwidth is-vertical">
<header>
<p class="is-size-5 has-text-weight-bold">diff2html-cli</p>
</header>
<section class="media-content">
<p>diff2html from your terminal to the browser.</p>
</section>
<footer>
<a href="https://github.com/rtfpessoa/diff2html-cli" target="_blank" rel="noopener"
rel="noreferrer">View GitHub</a>
</footer>
</div>
</div>
<div class="column is-one-quarter-widescreen is-flex">
<div class="box is-flex is-fullwidth is-vertical">
<header>
<p class="is-size-5 has-text-weight-bold">Codacy</p>
</header>
<section class="media-content">
<p>Automate code reviews on your commits and pull requests.</p>
</section>
<footer>
<a href="https://www.codacy.com" target="_blank" rel="noopener" rel="noreferrer">Website</a>
</footer>
</div>
</div>
<div class="column is-one-quarter-widescreen is-flex">
<div class="box is-flex is-fullwidth is-vertical">
<header>
<p class="is-size-5 has-text-weight-bold">Ungit</p>
</header>
<section class="media-content">
<p>The easiest way to use git. On any platform. Anywhere.</p>
</section>
<footer>
<a href="https://github.com/FredrikNoren/ungit" target="_blank" rel="noopener"
rel="noreferrer">View GitHub</a>
</footer>
</div>
</div>
<div class="column is-one-quarter-widescreen is-flex">
<div class="box is-flex is-fullwidth is-vertical">
<header>
<p class="is-size-5 has-text-weight-bold">Diffy</p>
</header>
<section class="media-content">
<p>Share your diffs and explain your ideas without committing.</p>
</section>
<footer>
<a href="https://diffy.org/" target="_blank" rel="noopener" rel="noreferrer">Website</a>
</footer>
</div>
</div>
<div class="column is-one-quarter-widescreen is-flex">
<div class="box is-flex is-fullwidth is-vertical">
<header>
<p class="is-size-5 has-text-weight-bold">Jenkins</p>
</header>
<section class="media-content">
<p>Show diffs between builds</p>
</section>
<footer>
<a href="https://plugins.jenkins.io/last-changes/" target="_blank"
rel="noopener" rel="noreferrer">Website</a>
</footer>
</div>
</div>
<div class="column is-one-quarter-widescreen is-flex">
<div class="box is-flex is-fullwidth is-vertical">
<header>
<p class="is-size-5 has-text-weight-bold">code-annotation</p>
</header>
<section class="media-content">
<p>Code Annotation Tool.</p>
</section>
<footer>
<a href="https://github.com/src-d/code-annotation" target="_blank" rel="noopener"
rel="noreferrer">View GitHub</a>
</footer>
</div>
</div>
<div class="column is-one-quarter-widescreen is-flex">
<div class="box is-flex is-fullwidth is-vertical">
<header>
<p class="is-size-5 has-text-weight-bold">Chef Automate</p>
</header>
<section class="media-content">
<p>DevOps Dashboard for Complete Operational Visibility.</p>
</section>
<footer>
<a href="https://automate.chef.io/" target="_blank" rel="noopener" rel="noreferrer">Website</a>
</footer>
</div>
</div>
<div class="column is-one-quarter-widescreen is-flex">
<div class="box is-flex is-fullwidth is-vertical">
<header>
<p class="is-size-5 has-text-weight-bold">GraphQL Schema Diff</p>
</header>
<section class="media-content">
<p>Detects dangerous and breaking changes in GraphQL schemas.</p>
</section>
<footer>
<a href="https://github.com/fabsrc/graphql-schema-diff" target="_blank" rel="noopener"
rel="noreferrer">View GitHub</a>
</footer>
</div>
</div>
<div class="column is-one-quarter-widescreen is-flex">
<div class="box is-flex is-fullwidth is-vertical">
<header>
<p class="is-size-5 has-text-weight-bold">cypress-plugin-snapshots</p>
</header>
<section class="media-content">
<p>Plugin for snapshot tests in Cypress.io.</p>
</section>
<footer>
<a href="https://github.com/meinaart/cypress-plugin-snapshots" target="_blank" rel="noopener"
rel="noreferrer">View GitHub</a>
</footer>
</div>
</div>
<div class="column is-one-quarter-widescreen is-flex">
<div class="box is-flex is-fullwidth is-vertical">
<header>
<p class="is-size-5 has-text-weight-bold">git-explorer</p>
</header>
<section class="media-content">
<p>Offline-first support for previewing local git repositories.</p>
</section>
<footer>
<a href="https://github.com/thescientist13/git-explorer" target="_blank" rel="noopener"
rel="noreferrer">View GitHub</a>
</footer>
</div>
</div>
<div class="column is-one-quarter-widescreen is-flex">
<div class="box is-flex is-fullwidth is-vertical">
<header>
<p class="is-size-5 has-text-weight-bold">edgar-monitor</p>
</header>
<section class="media-content">
<p>A module that processes new Edgar filings and sends out notifications.</p>
</section>
<footer>
<a href="https://github.com/buzzfeed-openlab/edgar-monitor" target="_blank" rel="noopener"
rel="noreferrer">View GitHub</a>
</footer>
</div>
</div>
<div class="column is-one-quarter-widescreen is-flex">
<div class="box is-flex is-fullwidth is-vertical">
<header>
<p class="is-size-5 has-text-weight-bold">Simple Git</p>
</header>
<section class="media-content">
<p>A simple package to be able to drive GIT.</p>
</section>
<footer>
<a href="https://github.com/mauricioszabo/simple-git" target="_blank" rel="noopener"
rel="noreferrer">View GitHub</a>
</footer>
</div>
</div>
<div class="column is-one-quarter-widescreen is-flex">
<div class="box is-flex is-fullwidth is-vertical">
<header>
<p class="is-size-5 has-text-weight-bold">jsreport</p>
</header>
<section class="media-content">
<p>javascript based business reporting platform</p>
</section>
<footer>
<a href="https://jsreport.net" target="_blank" rel="noopener"
rel="noreferrer">Website</a>
</footer>
</div>
</div>
<div class="column is-one-quarter-widescreen is-flex">
<div class="box is-flex is-fullwidth is-vertical">
<header>
<p class="is-size-5 has-text-weight-bold">dendron</p>
</header>
<section class="media-content">
<p>Knowledge Management. Redefined.</p>
</section>
<footer>
<a href="https://www.dendron.so" target="_blank" rel="noopener"
rel="noreferrer">Website</a>
</footer>
</div>
</div>
<div class="column is-one-quarter-widescreen is-flex">
<div class="box is-flex is-fullwidth is-vertical">
<header>
<p class="is-size-5 has-text-weight-bold">jest-stare</p>
</header>
<section class="media-content">
<p>Jest HTML Reporter and Results Processor</p>
</section>
<footer>
<a href="https://dkelosky.github.io/jest-stare/" target="_blank" rel="noopener"
rel="noreferrer">Website</a>
</footer>
</div>
</div>
<div class="column is-one-quarter-widescreen is-flex">
<div class="box is-flex is-fullwidth is-vertical">
<header>
<p class="is-size-5 has-text-weight-bold">Wiki.js</p>
</header>
<section class="media-content">
<p>The most powerful and extensible open source Wiki software</p>
</section>
<footer>
<a href="https://js.wiki/" target="_blank" rel="noopener"
rel="noreferrer">Website</a>
</footer>
</div>
</div>
<div class="column is-one-quarter-widescreen is-flex">
<div class="box is-flex is-fullwidth is-vertical">
<header>
<p class="is-size-5 has-text-weight-bold">Salto</p>
</header>
<section class="media-content">
<p>Understand Your Business Apps' Configuration</p>
</section>
<footer>
<a href="https://www.salto.io/" target="_blank" rel="noopener"
rel="noreferrer">Website</a>
</footer>
</div>
</div>
<div class="column is-one-quarter-widescreen is-flex">
<div class="box is-flex is-fullwidth is-vertical">
<header>
<p class="is-size-5 has-text-weight-bold">intuit Design Systems CLI</p>
</header>
<section class="media-content">
<p>A CLI toolbox for creating design systems</p>
</section>
<footer>
<a href="https://intuit.github.io/design-systems-cli/#/" target="_blank" rel="noopener"
rel="noreferrer">Website</a>
</footer>
</div>
</div>
<div class="column is-one-quarter-widescreen is-flex">
<div class="box is-flex is-fullwidth is-vertical">
<header>
<p class="is-size-5 has-text-weight-bold">Camunda Modeler</p>
</header>
<section class="media-content">
<p>Automate processes and decisions in a modern, standards-based way</p>
</section>
<footer>
<a href="https://camunda.com/download/modeler/" target="_blank" rel="noopener"
rel="noreferrer">Website</a>
</footer>
</div>
</div>
<div class="column is-one-quarter-widescreen is-flex">
<div class="box is-flex is-fullwidth is-vertical">
<header>
<p class="is-size-5 has-text-weight-bold">igit</p>
</header>
<section class="media-content">
<p>Tool for managing pull requests</p>
</section>
<footer>
<a href="https://igit.dev/" target="_blank" rel="noopener"
rel="noreferrer">Website</a>
</footer>
</div>
</div>
<div class="column is-one-quarter-widescreen is-flex">
<div class="box is-flex is-fullwidth is-vertical">
<header>
<p class="is-size-5 has-text-weight-bold">Crowi</p>
</header>
<section class="media-content">
<p>Empower the team with sharing your knowledge</p>
</section>
<footer>
<a href="https://site.crowi.wiki/" target="_blank" rel="noopener"
rel="noreferrer">Website</a>
</footer>
</div>
</div>
<div class="column is-one-quarter-widescreen is-flex">
<div class="box is-flex is-fullwidth is-vertical">
<header>
<p class="is-size-5 has-text-weight-bold">Robusta</p>
</header>
<section class="media-content">
<p>Open source Kubernetes troubleshooting and automation platform</p>
</section>
<footer>
<a href="https://docs.robusta.dev/master/" target="_blank" rel="noopener"
rel="noreferrer">Website</a>
</footer>
</div>
</div>
<div class="column is-one-quarter-widescreen is-flex">
<div class="box is-flex is-fullwidth is-vertical">
<header>
<p class="is-size-5 has-text-weight-bold">git-tabular-diff</p>
</header>
<section class="media-content">
<p>Displays a tabular difference of a csv file or a split difference of any file in an Atom pane</p>
</section>
<footer>
<a href="https://github.com/jstritch/git-tabular-diff" target="_blank" rel="noopener"
rel="noreferrer">View GitHub</a>
</footer>
</div>
</div>
</div>
</div>
<hr>
<div class="container">
<h4 class="title is-size-4 is-spaced"><strong>Open Source</strong></h4>
<p>diff2html is open source.
If you'd like to be part of the diff2html community or help improve it,
find more information on <a href="https://github.com/rtfpessoa/diff2html" target="_blank" rel="noopener"
rel="noreferrer">GitHub</a> and
<a href="https://gitter.im/rtfpessoa/diff2html" target="_blank" rel="noopener" rel="noreferrer">Gitter</a>.
Need any help?
</p>
<a href="https://github.com/rtfpessoa/diff2html#usage" target="_blank" rel="noopener" rel="noreferrer">
Read more in the Docs
</a>
</div>
</section>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 121 KiB

View file

@ -1,65 +0,0 @@
.image img.hero-img {
display: block;
height: auto;
width: 70%;
max-width: 1100px;
margin: 0 auto;
margin-top: 1.5rem;
}
.hero-booticon {
font-family:
Helvetica Neue,
Helvetica,
Arial,
sans-serif;
margin: 0 auto 30px;
width: 100%;
font-size: 8vw;
display: block;
font-weight: 500;
text-align: center;
cursor: default;
}
.code-example {
position: relative;
font-family: monospace;
background: #272b38;
color: #48d8a0;
border-radius: 8px;
padding: 1.5rem;
}
.button.clipboard {
position: absolute;
top: 0;
right: 0;
z-index: 10;
display: block;
padding: 5px 8px;
font-size: 12px;
color: #fff;
background-color: #767676;
border-color: #767676;
border-radius: 0 4px 0 4px;
cursor: pointer;
}
.flex {
display: flex;
flex-direction: row;
justify-content: flex-start;
}
.is-fullwidth {
flex-grow: 1;
}
.is-vertical {
flex-direction: column;
}
.align-middle {
vertical-align: middle;
}

View file

@ -1,4 +0,0 @@
{{#partial "content"}}
{{> content}}
{{/partial}}
{{> ../../template}}

Some files were not shown because too many files have changed in this diff Show more