Compare commits
No commits in common. "master" and "1.3.0" have entirely different histories.
126 changed files with 5540 additions and 31777 deletions
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
44
.github/ISSUE_TEMPLATE.md
vendored
44
.github/ISSUE_TEMPLATE.md
vendored
|
|
@ -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
|
||||
```
|
||||
23
.github/ISSUE_TEMPLATE/bug_report.md
vendored
23
.github/ISSUE_TEMPLATE/bug_report.md
vendored
|
|
@ -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.
|
||||
12
.github/ISSUE_TEMPLATE/feature_request.md
vendored
12
.github/ISSUE_TEMPLATE/feature_request.md
vendored
|
|
@ -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.
|
||||
12
.github/workflows/ci.yml
vendored
12
.github/workflows/ci.yml
vendored
|
|
@ -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
|
||||
35
.github/workflows/release.yml
vendored
35
.github/workflows/release.yml
vendored
|
|
@ -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
|
||||
148
.github/workflows/test-and-publish.yml
vendored
148
.github/workflows/test-and-publish.yml
vendored
|
|
@ -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/
|
||||
23
.gitignore
vendored
23
.gitignore
vendored
|
|
@ -18,29 +18,6 @@ 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
1
.husky/.gitignore
vendored
|
|
@ -1 +0,0 @@
|
|||
_
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
npm run lint:staged
|
||||
51
.jscsrc
Normal file
51
.jscsrc
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
"disallowKeywords": [
|
||||
"with"
|
||||
],
|
||||
"disallowKeywordsOnNewLine": [
|
||||
"else"
|
||||
],
|
||||
"disallowMixedSpacesAndTabs": true,
|
||||
"disallowMultipleVarDecl": {
|
||||
"allExcept": [
|
||||
"undefined"
|
||||
]
|
||||
},
|
||||
"disallowNewlineBeforeBlockStatements": true,
|
||||
"disallowSpaceAfterObjectKeys": true,
|
||||
"disallowSpaceAfterPrefixUnaryOperators": true,
|
||||
"disallowSpacesInFunction": {
|
||||
"beforeOpeningRoundBrace": true
|
||||
},
|
||||
"disallowSpacesInsideParentheses": true,
|
||||
"disallowTrailingWhitespace": true,
|
||||
"maximumLineLength": 130,
|
||||
"requireCamelCaseOrUpperCaseIdentifiers": true,
|
||||
"requireCapitalizedComments": true,
|
||||
"requireCapitalizedConstructors": true,
|
||||
"requireCurlyBraces": true,
|
||||
"requireSpaceAfterKeywords": [
|
||||
"if",
|
||||
"else",
|
||||
"for",
|
||||
"while",
|
||||
"do",
|
||||
"switch",
|
||||
"case",
|
||||
"return",
|
||||
"try",
|
||||
"catch",
|
||||
"typeof"
|
||||
],
|
||||
"requireSpaceAfterLineComment": true,
|
||||
"requireSpaceAfterBinaryOperators": true,
|
||||
"requireSpaceBeforeBinaryOperators": true,
|
||||
"requireSpaceBeforeBlockStatements": true,
|
||||
"requireSpaceBeforeObjectValues": true,
|
||||
"requireSpacesInFunction": {
|
||||
"beforeOpeningCurlyBrace": true
|
||||
},
|
||||
"requireTrailingComma": null,
|
||||
"validateIndentation": 2,
|
||||
"validateLineBreaks": "LF"
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
|
|
@ -1,3 +0,0 @@
|
|||
{
|
||||
"typescript.tsdk": "node_modules/typescript/lib"
|
||||
}
|
||||
1
CNAME
1
CNAME
|
|
@ -1 +0,0 @@
|
|||
diff2html.xyz
|
||||
|
|
@ -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
|
||||
|
|
@ -2,33 +2,31 @@
|
|||
|
||||
### Main rules
|
||||
|
||||
- Before you open a ticket or send a pull request, [search](https://github.com/rtfpessoa/diff2html/issues) for previous
|
||||
discussions about the same feature or issue. Add to the earlier ticket if you find one.
|
||||
* Before you open a ticket or send a pull request, [search](https://github.com/rtfpessoa/diff2html/issues) for previous discussions about the same feature or issue. Add to the earlier ticket if you find one.
|
||||
|
||||
- If you're proposing a new feature, make sure you create an issue to let other contributors know what you are working
|
||||
on.
|
||||
* If you're proposing a new feature, make sure you create an issue to let other contributors know what you are working on.
|
||||
|
||||
- Before sending a pull request make sure your code is tested.
|
||||
* Before sending a pull request make sure your code is tested.
|
||||
|
||||
- Before sending a pull request for a feature, be sure to run tests with `npm run test`.
|
||||
* Before sending a pull request for a feature, be sure to run tests with `npm 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 the same coding style as the rest of the codebase, most of the check can be performed with `npm run style`.
|
||||
|
||||
- Use `git rebase` (not `git merge`) to sync your work from time to time with the master branch.
|
||||
* Use `git rebase` (not `git merge`) to sync your work from time to time with the master branch.
|
||||
|
||||
- After creating your pull request make sure the build is passing on
|
||||
[CircleCI](https://circleci.com/gh/rtfpessoa/diff2html) and that [Codacy](https://www.codacy.com/app/Codacy/diff2html)
|
||||
is also confident in the code quality.
|
||||
* After creating your pull request make sure the build is passing on [CircleCI](https://circleci.com/gh/rtfpessoa/diff2html)
|
||||
and that [Codacy](https://www.codacy.com/app/Codacy/diff2html) is also confident in the code quality.
|
||||
|
||||
- In your pull request, do not commit the `dist` or `build` folder if you needed to build the release files.
|
||||
* In your pull request, do not commit the `dist` or `build` folder if you needed to build the release files.
|
||||
|
||||
### Commit Style
|
||||
|
||||
Writing good commit logs is important. A commit log should describe what changed and why. Follow these guidelines when
|
||||
writing one:
|
||||
Writing good commit logs is important. A commit log should describe what changed and why.
|
||||
Follow these guidelines when writing one:
|
||||
|
||||
1. The first line should be 50 characters or less and contain a short description of the change prefixed with the name
|
||||
of the changed subsystem (e.g. "net: add localAddress and localPort to Socket").
|
||||
1. The first line should be 50 characters or less and contain a short
|
||||
description of the change prefixed with the name of the changed
|
||||
subsystem (e.g. "net: add localAddress and localPort to Socket").
|
||||
2. Keep the second line blank.
|
||||
3. Wrap all other lines at 72 columns.
|
||||
|
||||
|
|
@ -51,11 +49,14 @@ nicely even when it is indented.
|
|||
|
||||
By making a contribution to this project, I certify that:
|
||||
|
||||
- (a) The contribution was created in whole or in part by me and I have the right to submit it under the open source
|
||||
license indicated in the file; or
|
||||
- (b) The contribution is based upon previous work that, to the best of my knowledge, is covered under an appropriate
|
||||
open source license and I have the right under that license to submit that work with modifications, whether created in
|
||||
whole or in part by me, under the same open source license (unless I am permitted to submit under a different
|
||||
license), as indicated in the file; or
|
||||
- (c) The contribution was provided directly to me by some other person who certified (a), (b) or (c) and I have not
|
||||
modified it.
|
||||
* (a) The contribution was created in whole or in part by me and I
|
||||
have the right to submit it under the open source license indicated
|
||||
in the file; or
|
||||
* (b) The contribution is based upon previous work that, to the best
|
||||
of my knowledge, is covered under an appropriate open source license
|
||||
and I have the right under that license to submit that work with
|
||||
modifications, whether created in whole or in part by me, under the
|
||||
same open source license (unless I am permitted to submit under a
|
||||
different license), as indicated in the file; or
|
||||
* (c) The contribution was provided directly to me by some other
|
||||
person who certified (a), (b) or (c) and I have not modified it.
|
||||
|
|
|
|||
15
CREDITS.md
15
CREDITS.md
|
|
@ -1,11 +1,12 @@
|
|||
# Credits
|
||||
|
||||
This is the list of all the kind people that have contributed to the diff2html project. This list is ordered by first
|
||||
contribution.
|
||||
This is the list of all the kind people that have contributed to the diff2html project.
|
||||
This list is ordered by first contribution.
|
||||
|
||||
Thanks, @rtfpessoa
|
||||
Thanks,
|
||||
@rtfpessoa
|
||||
|
||||
---
|
||||
----------
|
||||
|
||||
Rodrigo Fernandes, [@rtfpessoa](https://github.com/rtfpessoa)
|
||||
|
||||
|
|
@ -18,9 +19,3 @@ 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
20
LICENSE
Normal 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.
|
||||
14
LICENSE.md
14
LICENSE.md
|
|
@ -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.
|
||||
629
README.md
629
README.md
|
|
@ -1,613 +1,164 @@
|
|||
# diff2html
|
||||
|
||||
[](https://www.npmjs.com/package/diff2html)
|
||||
[](https://www.npmjs.com/package/diff2html)
|
||||
[](https://www.npmjs.com/package/diff2html)
|
||||
[](https://github.com/rtfpessoa/diff2html/actions/workflows/release.yml)
|
||||
[](https://circleci.com/gh/rtfpessoa/diff2html)
|
||||
[](https://www.codacy.com/app/Codacy/diff2html)
|
||||
[](https://www.codacy.com/app/Codacy/diff2html)
|
||||
|
||||
[](https://www.npmjs.com/package/diff2html)
|
||||
[](https://www.npmjs.com/package/diff2html)
|
||||
[](https://www.npmjs.com/package/diff2html)
|
||||
[](https://www.npmjs.com/package/diff2html)
|
||||
[](https://www.npmjs.com/package/diff2html)
|
||||
[](https://david-dm.org/rtfpessoa/diff2html)
|
||||
[](https://david-dm.org/rtfpessoa/diff2html)
|
||||
|
||||
[](https://www.jsdelivr.com/package/npm/diff2html)
|
||||
[](#contributors)
|
||||
[]()
|
||||
[]()
|
||||
[](https://www.npmjs.com/package/diff2html)
|
||||
[](https://gitter.im/rtfpessoa/diff2html?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
||||
|
||||
diff2html generates pretty HTML diffs from git diff or unified diff output.
|
||||
diff2html generates pretty HTML diffs from git 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 -->
|
||||
[](https://nodei.co/npm/diff2html/)
|
||||
|
||||
## Features
|
||||
|
||||
- Supports git and unified diffs
|
||||
* `line-by-line` and `side-by-side` diff
|
||||
|
||||
- Line by line and Side by side diff
|
||||
* new and old line numbers
|
||||
|
||||
- New and old line numbers
|
||||
* inserted and removed lines
|
||||
|
||||
- Inserted and removed lines
|
||||
* GitHub like style
|
||||
|
||||
- GitHub like visual style
|
||||
* Code syntax highlight
|
||||
|
||||
- Code syntax highlight
|
||||
|
||||
- Line similarity matching
|
||||
|
||||
- Easy code selection
|
||||
* Line similarity matching
|
||||
|
||||
## Online Example
|
||||
|
||||
> Go to [diff2html](https://diff2html.xyz/demo.html)
|
||||
> Go to [diff2html](http://rtfpessoa.github.io/diff2html/)
|
||||
|
||||
## Distributions
|
||||
|
||||
- [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
|
||||
* [WebJar](http://www.webjars.org/)
|
||||
|
||||
## Usage
|
||||
* [Node Module](https://www.npmjs.org/package/diff2html)
|
||||
|
||||
Diff2Html can be used in various ways as listed in the [distributions](#distributions) section. The two main ways are:
|
||||
* [Bower Package](http://bower.io/search/?q=diff2html)
|
||||
|
||||
- [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.
|
||||
* [Node CLI](https://www.npmjs.org/package/diff2html-cli)
|
||||
|
||||
Below you can find more details and examples about each option.
|
||||
* Manually download and import `dist/diff2html.min.js` into your page
|
||||
|
||||
## Diff Text Input
|
||||
## How to use
|
||||
|
||||
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).
|
||||
> Pretty HTML diff
|
||||
|
||||
## Diff2HtmlUI Usage
|
||||
Diff2Html.getPrettyHtml(exInput, configuration)
|
||||
|
||||
> Intermediate Json From Git Word Diff Output
|
||||
|
||||
Diff2Html.getJsonFromDiff(exInput)
|
||||
|
||||
> Check out the `index.html` for a complete example.
|
||||
|
||||
## Configuration
|
||||
The HTML output accepts a Javascript object with configuration. Possible options:
|
||||
|
||||
- `inputFormat`: the format of the input data: `'diff'` or `'json'`, default is `'diff'`
|
||||
- `outputFormat`: the format of the output data: `'line-by-line'` or `'side-by-side'`, default is `'line-by-line'`
|
||||
- `showFiles`: show a file list before the diff: `true` or `false`, default is `false`
|
||||
- `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
|
||||
|
||||
## Diff2HtmlUI Helper
|
||||
|
||||
> 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
|
||||
### How to use
|
||||
|
||||
### 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
|
||||
> Init
|
||||
|
||||
```js
|
||||
const targetElement = document.getElementById('destination-elem-id');
|
||||
const configuration = { drawFileList: true, matching: 'lines' };
|
||||
|
||||
const diff2htmlUi = new Diff2HtmlUI(targetElement, diffString, configuration);
|
||||
var diff2htmlUi = new Diff2HtmlUI({diff: diffString});
|
||||
// or
|
||||
const diff2htmlUi = new Diff2HtmlUI(targetElement, diffJson, configuration);
|
||||
var diff2htmlUi = new Diff2HtmlUI({json: diffJson});
|
||||
```
|
||||
|
||||
#### Draw
|
||||
> Draw
|
||||
|
||||
```js
|
||||
diff2htmlUi.draw();
|
||||
diff2htmlUi.draw('html-target-elem', {inputFormat: 'json', showFiles: true, matching: 'lines'});
|
||||
```
|
||||
|
||||
#### Syntax Highlight
|
||||
> Highlight Code
|
||||
|
||||
**NOTE:** The highlight.js css should come before the diff2html css
|
||||
```js
|
||||
diff2htmlUi.highlightCode('html-target-elem');
|
||||
```
|
||||
|
||||
> Collapse File Summary List
|
||||
|
||||
```js
|
||||
diff2htmlUi.fileListCloseable('html-target-elem', false);
|
||||
```
|
||||
|
||||
## Syntax Highlight
|
||||
|
||||
> Add the dependencies.
|
||||
Choose one color scheme, and add the main highlight code.
|
||||
If your favourite language is not included in the default package also add its javascript highlight file.
|
||||
|
||||
```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" />
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/8.9.1/styles/github.min.css">
|
||||
|
||||
<!-- Javascripts -->
|
||||
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/diff2html/bundles/js/diff2html-ui.min.js"></script>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/8.9.1/highlight.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/8.9.1/languages/scala.min.js"></script>
|
||||
<script type="text/javascript" src="dist/diff2html-ui.js"></script>
|
||||
```
|
||||
|
||||
> Pass the option `highlight` with value true or invoke `diff2htmlUi.highlightCode()` after `diff2htmlUi.draw()`.
|
||||
> Invoke the Diff2HtmlUI helper
|
||||
|
||||
```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();
|
||||
$(document).ready(function() {
|
||||
var diff2htmlUi = new Diff2HtmlUI({diff: lineDiffExample});
|
||||
diff2htmlUi.draw('#line-by-line', {inputFormat: 'json', showFiles: true, matching: 'lines'});
|
||||
diff2htmlUi.highlightCode('#line-by-line');
|
||||
});
|
||||
```
|
||||
|
||||
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
|
||||
## 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>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="dist/diff2html-ui.js"></script>
|
||||
```
|
||||
|
||||
> Invoke the Diff2HtmlUI helper Pass the option `fileListToggle` with value true or invoke
|
||||
> `diff2htmlUi.fileListToggle()` after `diff2htmlUi.draw()`.
|
||||
> Invoke the Diff2HtmlUI helper
|
||||
|
||||
```js
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const targetElement = document.getElementById('myDiffElement');
|
||||
var diff2htmlUi = new Diff2HtmlUI(targetElement, lineDiffExample, { drawFileList: true, matching: 'lines' });
|
||||
diff2htmlUi.draw();
|
||||
diff2htmlUi.fileListToggle(false);
|
||||
$(document).ready(function() {
|
||||
var diff2htmlUi = new Diff2HtmlUI({diff: lineDiffExample});
|
||||
diff2htmlUi.draw('#line-by-line', {inputFormat: 'json', showFiles: true, matching: 'lines'});
|
||||
diff2htmlUi.fileListCloseable('#line-by-line', false);
|
||||
});
|
||||
```
|
||||
|
||||
### Diff2HtmlUI Examples
|
||||
## Contributions
|
||||
|
||||
#### 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!
|
||||
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.
|
||||
|
||||
## 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).
|
||||
|
||||
---
|
||||
|
|
|
|||
35
SECURITY.md
35
SECURITY.md
|
|
@ -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.
|
||||
49
bower.json
Normal file
49
bower.json
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
{
|
||||
"name": "diff2html",
|
||||
"version": "1.3.0",
|
||||
"homepage": "http://rtfpessoa.github.io/diff2html/",
|
||||
"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"
|
||||
],
|
||||
"authors": [
|
||||
"Rodrigo Fernandes <rtfrodrigo@gmail.com>"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/rtfpessoa/diff2html.git"
|
||||
},
|
||||
"main": [
|
||||
"./dist/diff2html.js",
|
||||
"./dist/diff2html-ui.js",
|
||||
"./dist/diff2html.css"
|
||||
],
|
||||
"license": "MIT",
|
||||
"moduleType": [
|
||||
"globals",
|
||||
"node"
|
||||
],
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"package.json",
|
||||
"release.sh",
|
||||
"circle.yml",
|
||||
"css",
|
||||
"src",
|
||||
"test"
|
||||
]
|
||||
}
|
||||
9
circle.yml
Normal file
9
circle.yml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
test:
|
||||
override:
|
||||
- nvm install 0.12 && npm test
|
||||
- nvm install 4 && npm test
|
||||
post:
|
||||
- npm install
|
||||
- istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -R spec
|
||||
- cat ./coverage/lcov.info | ./node_modules/.bin/codacy-coverage
|
||||
- rm -rf ./coverage
|
||||
150
dist/diff2html-ui.js
vendored
Normal file
150
dist/diff2html-ui.js
vendored
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
/******/ (function(modules) { // webpackBootstrap
|
||||
/******/ // The module cache
|
||||
/******/ var installedModules = {};
|
||||
|
||||
/******/ // The require function
|
||||
/******/ function __webpack_require__(moduleId) {
|
||||
|
||||
/******/ // Check if module is in cache
|
||||
/******/ if(installedModules[moduleId])
|
||||
/******/ return installedModules[moduleId].exports;
|
||||
|
||||
/******/ // Create a new module (and put it into the cache)
|
||||
/******/ var module = installedModules[moduleId] = {
|
||||
/******/ exports: {},
|
||||
/******/ id: moduleId,
|
||||
/******/ loaded: false
|
||||
/******/ };
|
||||
|
||||
/******/ // Execute the module function
|
||||
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
|
||||
|
||||
/******/ // Flag the module as loaded
|
||||
/******/ module.loaded = true;
|
||||
|
||||
/******/ // Return the exports of the module
|
||||
/******/ return module.exports;
|
||||
/******/ }
|
||||
|
||||
|
||||
/******/ // expose the modules object (__webpack_modules__)
|
||||
/******/ __webpack_require__.m = modules;
|
||||
|
||||
/******/ // expose the module cache
|
||||
/******/ __webpack_require__.c = installedModules;
|
||||
|
||||
/******/ // __webpack_public_path__
|
||||
/******/ __webpack_require__.p = "";
|
||||
|
||||
/******/ // Load entry module and return exports
|
||||
/******/ return __webpack_require__(0);
|
||||
/******/ })
|
||||
/************************************************************************/
|
||||
/******/ ([
|
||||
/* 0 */
|
||||
/***/ function(module, exports) {
|
||||
|
||||
/* WEBPACK VAR INJECTION */(function(global) {/*
|
||||
*
|
||||
* Diff to HTML (diff2html-ui.js)
|
||||
* Author: rtfpessoa
|
||||
*
|
||||
* Depends on: [ jQuery ]
|
||||
* Optional dependencies on: [ highlight.js ]
|
||||
*
|
||||
*/
|
||||
|
||||
/*global $, hljs*/
|
||||
|
||||
(function() {
|
||||
|
||||
var diffJson = null;
|
||||
var defaultTarget = "body";
|
||||
|
||||
function Diff2HtmlUI(config) {
|
||||
var cfg = config || {};
|
||||
|
||||
if (cfg.diff) {
|
||||
diffJson = Diff2Html.getJsonFromDiff(cfg.diff);
|
||||
} else if (cfg.json) {
|
||||
diffJson = cfg.json;
|
||||
}
|
||||
}
|
||||
|
||||
Diff2HtmlUI.prototype.draw = function(targetId, config) {
|
||||
var cfg = config || {};
|
||||
var $target = this._getTarget(targetId);
|
||||
$target.html(Diff2Html.getPrettyHtml(diffJson, cfg));
|
||||
};
|
||||
|
||||
Diff2HtmlUI.prototype.fileListCloseable = function(targetId, startVisible) {
|
||||
var $target = this._getTarget(targetId);
|
||||
|
||||
var $showBtn = $target.find(".d2h-show");
|
||||
var $hideBtn = $target.find(".d2h-hide");
|
||||
var $fileList = $target.find(".d2h-file-list");
|
||||
|
||||
if (startVisible) show(); else hide();
|
||||
|
||||
$showBtn.click(show);
|
||||
$hideBtn.click(hide);
|
||||
|
||||
function show() {
|
||||
$showBtn.hide();
|
||||
$hideBtn.show();
|
||||
$fileList.show();
|
||||
}
|
||||
|
||||
function hide() {
|
||||
$hideBtn.hide();
|
||||
$showBtn.show();
|
||||
$fileList.hide();
|
||||
}
|
||||
};
|
||||
|
||||
Diff2HtmlUI.prototype.highlightCode = function(targetId) {
|
||||
var that = this;
|
||||
|
||||
// collect all the file extensions in the json
|
||||
var allFileLanguages = diffJson.map(function(line) {
|
||||
return line.language;
|
||||
});
|
||||
|
||||
// remove duplicated languages
|
||||
var distinctLanguages = allFileLanguages.filter(function(v, i) {
|
||||
return allFileLanguages.indexOf(v) === i;
|
||||
});
|
||||
|
||||
// pass the languages to the highlightjs plugin
|
||||
hljs.configure({languages: distinctLanguages});
|
||||
|
||||
// collect all the code lines and execute the highlight on them
|
||||
var $target = that._getTarget(targetId);
|
||||
var $codeLines = $target.find(".d2h-code-line-ctn");
|
||||
$codeLines.map(function(i, line) {
|
||||
hljs.highlightBlock(line);
|
||||
});
|
||||
};
|
||||
|
||||
Diff2HtmlUI.prototype._getTarget = function(targetId) {
|
||||
var $target;
|
||||
if (targetId) {
|
||||
$target = $(targetId);
|
||||
} else {
|
||||
$target = $(defaultTarget);
|
||||
}
|
||||
|
||||
return $target;
|
||||
};
|
||||
|
||||
module.exports.Diff2HtmlUI = Diff2HtmlUI;
|
||||
|
||||
// Expose diff2html in the browser
|
||||
global.Diff2HtmlUI = Diff2HtmlUI;
|
||||
|
||||
})();
|
||||
|
||||
/* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }())))
|
||||
|
||||
/***/ }
|
||||
/******/ ]);
|
||||
1
dist/diff2html-ui.min.js
vendored
Normal file
1
dist/diff2html-ui.min.js
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
!function(modules){function __webpack_require__(moduleId){if(installedModules[moduleId])return installedModules[moduleId].exports;var module=installedModules[moduleId]={exports:{},id:moduleId,loaded:!1};return modules[moduleId].call(module.exports,module,module.exports,__webpack_require__),module.loaded=!0,module.exports}var installedModules={};return __webpack_require__.m=modules,__webpack_require__.c=installedModules,__webpack_require__.p="",__webpack_require__(0)}([function(module,exports){(function(global){!function(){function Diff2HtmlUI(config){var cfg=config||{};cfg.diff?diffJson=Diff2Html.getJsonFromDiff(cfg.diff):cfg.json&&(diffJson=cfg.json)}var diffJson=null,defaultTarget="body";Diff2HtmlUI.prototype.draw=function(targetId,config){var cfg=config||{},$target=this._getTarget(targetId);$target.html(Diff2Html.getPrettyHtml(diffJson,cfg))},Diff2HtmlUI.prototype.fileListCloseable=function(targetId,startVisible){function show(){$showBtn.hide(),$hideBtn.show(),$fileList.show()}function hide(){$hideBtn.hide(),$showBtn.show(),$fileList.hide()}var $target=this._getTarget(targetId),$showBtn=$target.find(".d2h-show"),$hideBtn=$target.find(".d2h-hide"),$fileList=$target.find(".d2h-file-list");startVisible?show():hide(),$showBtn.click(show),$hideBtn.click(hide)},Diff2HtmlUI.prototype.highlightCode=function(targetId){var that=this,allFileLanguages=diffJson.map(function(line){return line.language}),distinctLanguages=allFileLanguages.filter(function(v,i){return allFileLanguages.indexOf(v)===i});hljs.configure({languages:distinctLanguages});var $target=that._getTarget(targetId),$codeLines=$target.find(".d2h-code-line-ctn");$codeLines.map(function(i,line){hljs.highlightBlock(line)})},Diff2HtmlUI.prototype._getTarget=function(targetId){var $target;return $target=$(targetId?targetId:defaultTarget)},module.exports.Diff2HtmlUI=Diff2HtmlUI,global.Diff2HtmlUI=Diff2HtmlUI}()}).call(exports,function(){return this}())}]);
|
||||
278
dist/diff2html.css
vendored
Normal file
278
dist/diff2html.css
vendored
Normal file
|
|
@ -0,0 +1,278 @@
|
|||
/*
|
||||
*
|
||||
* Diff to HTML (diff2html.css)
|
||||
* Author: rtfpessoa
|
||||
*
|
||||
*/
|
||||
|
||||
.d2h-wrapper {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.d2h-file-wrapper {
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 3px;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.d2h-file-header {
|
||||
padding: 5px 10px;
|
||||
border-bottom: 1px solid #d8d8d8;
|
||||
background-color: #f7f7f7;
|
||||
font: 13px Helvetica, arial, freesans, clean, sans-serif, "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
}
|
||||
|
||||
.d2h-file-stats {
|
||||
display: inline;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
max-width: 15%;
|
||||
}
|
||||
|
||||
.d2h-lines-added {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.d2h-lines-added > * {
|
||||
background-color: #ceffce;
|
||||
border: 1px solid #b4e2b4;
|
||||
color: #399839;
|
||||
border-radius: 5px 0 0 5px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.d2h-lines-deleted {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.d2h-lines-deleted > * {
|
||||
background-color: #f7c8c8;
|
||||
border: 1px solid #e9aeae;
|
||||
color: #c33;
|
||||
border-radius: 0 5px 5px 0;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.d2h-file-name {
|
||||
display: inline;
|
||||
line-height: 33px;
|
||||
max-width: 80%;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.d2h-diff-table {
|
||||
border-collapse: collapse;
|
||||
font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
|
||||
font-size: 12px;
|
||||
height: 18px;
|
||||
line-height: 18px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.d2h-files-diff {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.d2h-file-diff {
|
||||
overflow-x: scroll;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.d2h-file-side-diff {
|
||||
display: inline-block;
|
||||
overflow-x: scroll;
|
||||
overflow-y: hidden;
|
||||
width: 50%;
|
||||
margin-right: -4px;
|
||||
}
|
||||
|
||||
.d2h-code-line {
|
||||
display: block;
|
||||
white-space: pre;
|
||||
padding: 0 10px;
|
||||
height: 18px;
|
||||
line-height: 18px;
|
||||
margin-left: 80px;
|
||||
/* Override HighlightJS */
|
||||
color: inherit;
|
||||
overflow-x: inherit;
|
||||
background: none;
|
||||
/* ******************** */
|
||||
}
|
||||
|
||||
.d2h-code-side-line {
|
||||
display: block;
|
||||
white-space: pre;
|
||||
padding: 0 10px;
|
||||
height: 18px;
|
||||
line-height: 18px;
|
||||
margin-left: 50px;
|
||||
/* Override HighlightJS */
|
||||
color: inherit;
|
||||
overflow-x: inherit;
|
||||
background: none;
|
||||
/* ******************** */
|
||||
}
|
||||
|
||||
.d2h-code-line del,
|
||||
.d2h-code-side-line del {
|
||||
display: inline-block;
|
||||
margin-top: -1px;
|
||||
text-decoration: none;
|
||||
background-color: #ffb6ba;
|
||||
border-radius: 0.2em;
|
||||
}
|
||||
|
||||
.d2h-code-line ins,
|
||||
.d2h-code-side-line ins {
|
||||
display: inline-block;
|
||||
margin-top: -1px;
|
||||
text-decoration: none;
|
||||
background-color: #97f295;
|
||||
border-radius: 0.2em;
|
||||
}
|
||||
|
||||
.d2h-code-line-prefix {
|
||||
float: left;
|
||||
background: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.d2h-code-line-ctn {
|
||||
background: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.line-num1 {
|
||||
box-sizing: border-box;
|
||||
float: left;
|
||||
width: 32px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
padding-left: 3px;
|
||||
}
|
||||
|
||||
.line-num2 {
|
||||
box-sizing: border-box;
|
||||
float: right;
|
||||
width: 32px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
padding-left: 3px;
|
||||
}
|
||||
|
||||
.d2h-code-linenumber {
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
width: 82px;
|
||||
height: 18px;
|
||||
padding-left: 2px;
|
||||
padding-right: 2px;
|
||||
line-height: 18px;
|
||||
background-color: #fff;
|
||||
color: rgba(0, 0, 0, 0.3);
|
||||
text-align: right;
|
||||
border: solid #eeeeee;
|
||||
border-width: 0 1px 0 1px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.d2h-code-side-linenumber {
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
width: 52px;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
height: 18px;
|
||||
line-height: 18px;
|
||||
background-color: #fff;
|
||||
color: rgba(0, 0, 0, 0.3);
|
||||
text-align: right;
|
||||
border: solid #eeeeee;
|
||||
border-width: 0 1px 0 1px;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.d2h-del {
|
||||
background-color: #fee8e9;
|
||||
border-color: #e9aeae;
|
||||
}
|
||||
|
||||
.d2h-ins {
|
||||
background-color: #dfd;
|
||||
border-color: #b4e2b4;
|
||||
}
|
||||
|
||||
.d2h-info {
|
||||
background-color: #f8fafd;
|
||||
color: rgba(0, 0, 0, 0.3);
|
||||
border-color: #d5e4f2;
|
||||
}
|
||||
|
||||
.d2h-file-list-wrapper {
|
||||
margin-bottom: 10px;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.d2h-file-list-wrapper a {
|
||||
text-decoration: none;
|
||||
color: #3572b0;
|
||||
}
|
||||
|
||||
.d2h-file-list-wrapper a:visited {
|
||||
color: #3572b0;
|
||||
}
|
||||
|
||||
.d2h-file-list-header {
|
||||
font-weight: bold;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.d2h-file-list-line {
|
||||
text-align: left;
|
||||
font: 13px Helvetica, arial, freesans, clean, sans-serif, "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
}
|
||||
|
||||
.d2h-file-list-line .d2h-file-name {
|
||||
line-height: 21px;
|
||||
}
|
||||
|
||||
.d2h-file-list {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.d2h-clear {
|
||||
display: block;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.d2h-del.d2h-change, .d2h-ins.d2h-change {
|
||||
background-color: #ffc;
|
||||
}
|
||||
|
||||
ins.d2h-change, del.d2h-change {
|
||||
background-color: #fad771;
|
||||
}
|
||||
|
||||
.d2h-file-diff .d2h-del.d2h-change {
|
||||
background-color: #fae1af;
|
||||
}
|
||||
|
||||
.d2h-file-diff .d2h-ins.d2h-change {
|
||||
background-color: #ded;
|
||||
}
|
||||
|
||||
.d2h-file-switch {
|
||||
display: none;
|
||||
float: left;
|
||||
font-size: 10px;
|
||||
cursor: pointer;
|
||||
margin-top: 3px;
|
||||
}
|
||||
2587
dist/diff2html.js
vendored
Normal file
2587
dist/diff2html.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dist/diff2html.min.css
vendored
Normal file
1
dist/diff2html.min.css
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
.d2h-code-line-prefix,.line-num1{float:left}.d2h-wrapper{display:block;margin:0 auto;text-align:left;width:100%}.d2h-file-wrapper{border:1px solid #ddd;border-radius:3px;margin-bottom:1em}.d2h-file-header{padding:5px 10px;border-bottom:1px solid #d8d8d8;background-color:#f7f7f7;font:13px Helvetica,arial,freesans,clean,sans-serif,"Segoe UI Emoji","Segoe UI Symbol"}.d2h-file-stats{display:inline;font-size:12px;text-align:center;max-width:15%}.d2h-lines-added{text-align:right}.d2h-lines-added>*{background-color:#ceffce;border:1px solid #b4e2b4;color:#399839;border-radius:5px 0 0 5px;padding:2px}.d2h-lines-deleted{text-align:left}.d2h-lines-deleted>*{background-color:#f7c8c8;border:1px solid #e9aeae;color:#c33;border-radius:0 5px 5px 0;padding:2px}.d2h-file-name{display:inline;line-height:33px;max-width:80%;white-space:nowrap;text-overflow:ellipsis;overflow:hidden}.d2h-file-diff,.d2h-file-side-diff{overflow-x:scroll;overflow-y:hidden}.d2h-diff-table{border-collapse:collapse;font-family:Consolas,"Liberation Mono",Menlo,Courier,monospace;font-size:12px;height:18px;line-height:18px;width:100%}.d2h-files-diff{width:100%}.d2h-file-side-diff{display:inline-block;width:50%;margin-right:-4px}.d2h-code-line,.d2h-code-side-line{white-space:pre;padding:0 10px;height:18px;line-height:18px;color:inherit;overflow-x:inherit;background:0 0;display:block}.d2h-code-line{margin-left:80px}.d2h-code-side-line{margin-left:50px}.d2h-code-line del,.d2h-code-side-line del{display:inline-block;margin-top:-1px;text-decoration:none;background-color:#ffb6ba;border-radius:.2em}.d2h-code-line ins,.d2h-code-side-line ins{display:inline-block;margin-top:-1px;text-decoration:none;background-color:#97f295;border-radius:.2em}.d2h-code-line-ctn,.d2h-code-line-prefix{background:0 0;padding:0}.d2h-code-linenumber,.d2h-code-side-linenumber{position:absolute;height:18px;line-height:18px;background-color:#fff;text-align:right;color:rgba(0,0,0,.3);cursor:pointer}.line-num1,.line-num2{width:32px;padding-left:3px;box-sizing:border-box;overflow:hidden;text-overflow:ellipsis}.line-num2{float:right}.d2h-code-linenumber{box-sizing:border-box;width:82px;padding-left:2px;padding-right:2px;border:solid #eee;border-width:0 1px}.d2h-code-side-linenumber{box-sizing:border-box;width:52px;padding-left:10px;padding-right:10px;border:solid #eee;border-width:0 1px;overflow:hidden;text-overflow:ellipsis}.d2h-del{background-color:#fee8e9;border-color:#e9aeae}.d2h-ins{background-color:#dfd;border-color:#b4e2b4}.d2h-info{background-color:#f8fafd;color:rgba(0,0,0,.3);border-color:#d5e4f2}.d2h-file-list-wrapper{margin-bottom:10px;padding:0 10px}.d2h-file-list-wrapper a{text-decoration:none;color:#3572b0}.d2h-file-list-wrapper a:visited{color:#3572b0}.d2h-file-list-header{font-weight:700;float:left}.d2h-file-list-line{text-align:left;font:13px Helvetica,arial,freesans,clean,sans-serif,"Segoe UI Emoji","Segoe UI Symbol"}.d2h-file-list-line .d2h-file-name{line-height:21px}.d2h-file-list{display:block}.d2h-clear{display:block;clear:both}.d2h-del.d2h-change,.d2h-ins.d2h-change{background-color:#ffc}del.d2h-change,ins.d2h-change{background-color:#fad771}.d2h-file-diff .d2h-del.d2h-change{background-color:#fae1af}.d2h-file-diff .d2h-ins.d2h-change{background-color:#ded}.d2h-file-switch{display:none;float:left;font-size:10px;cursor:pointer;margin-top:3px}
|
||||
3
dist/diff2html.min.js
vendored
Normal file
3
dist/diff2html.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -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}'],
|
||||
},
|
||||
];
|
||||
|
|
@ -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
18121
package-lock.json
generated
File diff suppressed because it is too large
Load diff
139
package.json
139
package.json
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "diff2html",
|
||||
"version": "3.0.0-beta.1",
|
||||
"homepage": "https://diff2html.xyz",
|
||||
"version": "1.3.0",
|
||||
"homepage": "http://rtfpessoa.github.io/diff2html/",
|
||||
"description": "Fast Diff to colorized HTML",
|
||||
"keywords": [
|
||||
"git",
|
||||
|
|
@ -26,135 +26,38 @@
|
|||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/rtfpessoa/diff2html.git"
|
||||
"url": "https://www.github.com/rtfpessoa/diff2html.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://www.github.com/rtfpessoa/diff2html/issues"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=0.10"
|
||||
},
|
||||
"preferGlobal": "true",
|
||||
"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"
|
||||
]
|
||||
"release": "bash release.sh",
|
||||
"test": "mocha",
|
||||
"style": "jscs src test",
|
||||
"codacy": "istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -R spec && cat ./coverage/lcov.info | ./node_modules/.bin/codacy-coverage && rm -rf ./coverage"
|
||||
},
|
||||
"main": "./src/diff2html.js",
|
||||
"dependencies": {
|
||||
"diff": "^7.0.0",
|
||||
"hogan.js": "3.0.2"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"highlight.js": "11.9.0"
|
||||
"diff": "^2.2.1"
|
||||
},
|
||||
"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"
|
||||
"codacy-coverage": "^1.1.3",
|
||||
"clean-css": "^3.4.9",
|
||||
"fast-html-parser": "^1.0.1",
|
||||
"istanbul": "^0.4.1",
|
||||
"jscs": "^2.9.0",
|
||||
"mocha": "^2.4.5",
|
||||
"uglifyjs": "^2.4.10",
|
||||
"webpack": "^1.12.13"
|
||||
},
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
"bundles",
|
||||
"lib",
|
||||
"lib-esm"
|
||||
"src",
|
||||
"css"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +0,0 @@
|
|||
module.exports = {
|
||||
sourceMap: false,
|
||||
plugins: {
|
||||
'postcss-import': {},
|
||||
'postcss-preset-env': {
|
||||
browsers: 'last 2 versions',
|
||||
},
|
||||
cssnano: {},
|
||||
},
|
||||
};
|
||||
54
release.sh
Executable file
54
release.sh
Executable file
|
|
@ -0,0 +1,54 @@
|
|||
#!/bin/bash
|
||||
|
||||
#
|
||||
# diff2html release script
|
||||
# by rtfpessoa
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
INPUT_DIR=src
|
||||
INPUT_UI_DIR=${INPUT_DIR}/ui
|
||||
INPUT_JS_FILE=${INPUT_DIR}/diff2html.js
|
||||
INPUT_JS_UI_FILE=${INPUT_UI_DIR}/js/diff2html-ui.js
|
||||
INPUT_CSS_FILE=${INPUT_UI_DIR}/css/diff2html.css
|
||||
|
||||
OUTPUT_DIR=dist
|
||||
OUTPUT_JS_FILE=${OUTPUT_DIR}/diff2html.js
|
||||
OUTPUT_MIN_JS_FILE=${OUTPUT_DIR}/diff2html.min.js
|
||||
OUTPUT_JS_UI_FILE=${OUTPUT_DIR}/diff2html-ui.js
|
||||
OUTPUT_MIN_JS_UI_FILE=${OUTPUT_DIR}/diff2html-ui.min.js
|
||||
OUTPUT_CSS_FILE=${OUTPUT_DIR}/diff2html.css
|
||||
OUTPUT_MIN_CSS_FILE=${OUTPUT_DIR}/diff2html.min.css
|
||||
|
||||
echo "Creating diff2html release ..."
|
||||
|
||||
echo "Cleaning previous versions ..."
|
||||
rm -rf ${OUTPUT_DIR}
|
||||
mkdir -p ${OUTPUT_DIR}
|
||||
|
||||
echo "Generating js aggregation file in ${OUTPUT_JS_FILE}"
|
||||
|
||||
webpack ${INPUT_JS_FILE} ${OUTPUT_JS_FILE}
|
||||
|
||||
echo "Minifying ${OUTPUT_JS_FILE} to ${OUTPUT_MIN_JS_FILE}"
|
||||
|
||||
uglifyjs ${OUTPUT_JS_FILE} -c -o ${OUTPUT_MIN_JS_FILE}
|
||||
|
||||
echo "Generating js ui aggregation file in ${OUTPUT_JS_UI_FILE}"
|
||||
|
||||
webpack ${INPUT_JS_UI_FILE} ${OUTPUT_JS_UI_FILE}
|
||||
|
||||
echo "Minifying ${OUTPUT_JS_UI_FILE} to ${OUTPUT_MIN_JS_UI_FILE}"
|
||||
|
||||
uglifyjs ${OUTPUT_JS_UI_FILE} -c -o ${OUTPUT_MIN_JS_UI_FILE}
|
||||
|
||||
echo "Copying css file to ${OUTPUT_CSS_FILE}"
|
||||
|
||||
cp -f ${INPUT_CSS_FILE} ${OUTPUT_CSS_FILE}
|
||||
|
||||
echo "Minifying ${OUTPUT_CSS_FILE} to ${OUTPUT_MIN_CSS_FILE}"
|
||||
|
||||
cleancss --advanced --compatibility=ie8 -o ${OUTPUT_MIN_CSS_FILE} ${OUTPUT_CSS_FILE}
|
||||
|
||||
echo "diff2html release created successfully!"
|
||||
265
sample/index.html
Normal file
265
sample/index.html
Normal file
|
|
@ -0,0 +1,265 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Diff to HTML by rtfpessoa</title>
|
||||
|
||||
<!--
|
||||
Diff to HTML (index.html)
|
||||
Author: rtfpessoa
|
||||
-->
|
||||
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/8.9.1/styles/github.min.css">
|
||||
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/8.9.1/highlight.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/8.9.1/languages/scala.min.js"></script>
|
||||
|
||||
<!-- diff2html -->
|
||||
<link rel="stylesheet" type="text/css" href="../dist/diff2html.min.css">
|
||||
<script type="text/javascript" src="../dist/diff2html.js"></script>
|
||||
<script type="text/javascript" src="../dist/diff2html-ui.js"></script>
|
||||
<!-- -->
|
||||
|
||||
<script>
|
||||
var lineDiffExample =
|
||||
'diff --git a/coverage.init b/coverage.init\n' +
|
||||
'index fc56817..e8e7e49 100644\n' +
|
||||
'--- a/coverage.init\n' +
|
||||
'+++ b/coverage.init\n' +
|
||||
'@@ -19,7 +19,7 @@\n' +
|
||||
' -opt "\-nostart"\n' +
|
||||
' \n' +
|
||||
' # skip stopenv\n' +
|
||||
'--do "runbvt,stopenv,getlogs,pullcoveragedata"\n' +
|
||||
'+-do "runbvt,getlogs,pullcoveragedata"\n' +
|
||||
' \n' +
|
||||
' ##########################################\n' +
|
||||
' # logs files to bring back to base\n' +
|
||||
'diff --git a/src/attributes/attr.js b/src/attributes/attr.js\n' +
|
||||
'index facdd41..b627fe8 100644\n' +
|
||||
'--- a/src/attributes/attr.js\n' +
|
||||
'+++ b/src/attributes/attr.js\n' +
|
||||
'@@ -1,11 +1,10 @@\n' +
|
||||
' define([\n' +
|
||||
' "../core",\n' +
|
||||
' "../var/rnotwhite",\n' +
|
||||
'- "../var/strundefined",\n' +
|
||||
' "../core/access",\n' +
|
||||
' "./support",\n' +
|
||||
' "../selector"\n' +
|
||||
'-], function( jQuery, rnotwhite, strundefined, access, support ) {\n' +
|
||||
'+], function( jQuery, rnotwhite, access, support ) {\n' +
|
||||
' \n' +
|
||||
' var nodeHook, boolHook,\n' +
|
||||
' attrHandle = jQuery.expr.attrHandle;\n' +
|
||||
'@@ -33,7 +32,7 @@ jQuery.extend({\n' +
|
||||
' }\n' +
|
||||
' \n' +
|
||||
' // Fallback to prop when attributes are not supported\n' +
|
||||
'- if ( typeof elem.getAttribute === strundefined ) {\n' +
|
||||
'+ if ( !elem.getAttribute ) {\n' +
|
||||
' return jQuery.prop( elem, name, value );\n' +
|
||||
' }\n' +
|
||||
' \n' +
|
||||
'diff --git a/src/attributes/classes.js b/src/attributes/classes.js\n' +
|
||||
'index c617824..c8d1393 100644\n' +
|
||||
'--- a/src/attributes/classes.js\n' +
|
||||
'+++ b/src/attributes/classes.js\n' +
|
||||
'@@ -1,10 +1,9 @@\n' +
|
||||
' define([\n' +
|
||||
' "../core",\n' +
|
||||
' "../var/rnotwhite",\n' +
|
||||
'- "../var/strundefined",\n' +
|
||||
' "../data/var/dataPriv",\n' +
|
||||
' "../core/init"\n' +
|
||||
'-], function( jQuery, rnotwhite, strundefined, dataPriv ) {\n' +
|
||||
'+], function( jQuery, rnotwhite, dataPriv ) {\n' +
|
||||
' \n' +
|
||||
' var rclass = /[\\t\\r\\n\\f]/g;\n' +
|
||||
' \n' +
|
||||
'@@ -128,7 +127,7 @@ jQuery.fn.extend({\n' +
|
||||
' }\n' +
|
||||
' \n' +
|
||||
' // Toggle whole class name\n' +
|
||||
'- } else if ( type === strundefined || type === "boolean" ) {\n' +
|
||||
'+ } else if ( value === undefined || type === "boolean" ) {\n' +
|
||||
' if ( this.className ) {\n' +
|
||||
' // store className if set\n' +
|
||||
' dataPriv.set( this, "__className__", this.className );\n' +
|
||||
'diff --git a/src/core/init.js b/src/core/init.js\n' +
|
||||
'index e49196a..50f310c 100644\n' +
|
||||
'--- a/src/core/init.js\n' +
|
||||
'+++ b/src/core/init.js\n' +
|
||||
'@@ -101,7 +101,7 @@ var rootjQuery,\n' +
|
||||
' // HANDLE: $(function)\n' +
|
||||
' // Shortcut for document ready\n' +
|
||||
' } else if ( jQuery.isFunction( selector ) ) {\n' +
|
||||
'- return typeof rootjQuery.ready !== "undefined" ?\n' +
|
||||
'+ return rootjQuery.ready !== undefined ?\n' +
|
||||
' rootjQuery.ready( selector ) :\n' +
|
||||
' // Execute immediately if ready is not present\n' +
|
||||
' selector( jQuery );\n' +
|
||||
'diff --git a/src/event.js b/src/event.js\n' +
|
||||
'index 7336f4d..6183f70 100644\n' +
|
||||
'--- a/src/event.js\n' +
|
||||
'+++ b/src/event.js\n' +
|
||||
'@@ -1,6 +1,5 @@\n' +
|
||||
' define([\n' +
|
||||
' "./core",\n' +
|
||||
'- "./var/strundefined",\n' +
|
||||
' "./var/rnotwhite",\n' +
|
||||
' "./var/hasOwn",\n' +
|
||||
' "./var/slice",\n' +
|
||||
'@@ -10,7 +9,7 @@ define([\n' +
|
||||
' "./core/init",\n' +
|
||||
' "./data/accepts",\n' +
|
||||
' "./selector"\n' +
|
||||
'-], function( jQuery, strundefined, rnotwhite, hasOwn, slice, support, dataPriv ) {\n' +
|
||||
'+], function( jQuery, rnotwhite, hasOwn, slice, support, dataPriv ) {\n' +
|
||||
' \n' +
|
||||
' var\n' +
|
||||
' rkeyEvent = /^key/,\n' +
|
||||
'@@ -72,7 +71,7 @@ jQuery.event = {\n' +
|
||||
' eventHandle = elemData.handle = function( e ) {\n' +
|
||||
' // Discard the second event of a jQuery.event.trigger() and\n' +
|
||||
' // when an event is called after a page has unloaded\n' +
|
||||
'- return typeof jQuery !== strundefined && jQuery.event.triggered !== e.type ?\n' +
|
||||
'+ return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ?\n' +
|
||||
' jQuery.event.dispatch.apply( elem, arguments ) : undefined;\n' +
|
||||
' };\n' +
|
||||
' }\n' +
|
||||
'diff --git a/src/exports/global.js b/src/exports/global.js\n' +
|
||||
'index 6513287..1db4144 100644\n' +
|
||||
'--- a/src/exports/global.js\n' +
|
||||
'+++ b/src/exports/global.js\n' +
|
||||
'@@ -1,7 +1,9 @@\n' +
|
||||
' define([\n' +
|
||||
'- "../core",\n' +
|
||||
'- "../var/strundefined"\n' +
|
||||
'-], function( jQuery, strundefined ) {\n' +
|
||||
'+ "../core"\n' +
|
||||
'+], function( jQuery ) {\n' +
|
||||
'+\n' +
|
||||
'+/* exported noGlobal */\n' +
|
||||
'+/* global noGlobal: false */\n' +
|
||||
' \n' +
|
||||
' var\n' +
|
||||
' // Map over jQuery in case of overwrite\n' +
|
||||
'@@ -25,7 +27,7 @@ jQuery.noConflict = function( deep ) {\n' +
|
||||
' // Expose jQuery and $ identifiers, even in AMD\n' +
|
||||
' // (#7102#comment:10, https://github.com/jquery/jquery/pull/557)\n' +
|
||||
' // and CommonJS for browser emulators (#13566)\n' +
|
||||
'-if ( typeof noGlobal === strundefined ) {\n' +
|
||||
'+if ( !noGlobal ) {\n' +
|
||||
' window.jQuery = window.$ = jQuery;\n' +
|
||||
' }\n' +
|
||||
' \n' +
|
||||
'diff --git a/src/offset.js b/src/offset.js\n' +
|
||||
'index cc6ffb4..fa51f18 100644\n' +
|
||||
'--- a/src/offset.js\n' +
|
||||
'+++ b/src/offset.js\n' +
|
||||
'@@ -1,6 +1,5 @@\n' +
|
||||
' define([\n' +
|
||||
' "./core",\n' +
|
||||
'- "./var/strundefined",\n' +
|
||||
' "./core/access",\n' +
|
||||
' "./css/var/rnumnonpx",\n' +
|
||||
' "./css/curCSS",\n' +
|
||||
'@@ -10,7 +9,7 @@ define([\n' +
|
||||
' "./core/init",\n' +
|
||||
' "./css",\n' +
|
||||
' "./selector" // contains\n' +
|
||||
'-], function( jQuery, strundefined, access, rnumnonpx, curCSS, addGetHookIf, support ) {\n' +
|
||||
'+], function( jQuery, access, rnumnonpx, curCSS, addGetHookIf, support ) {\n' +
|
||||
' \n' +
|
||||
' var docElem = window.document.documentElement;\n' +
|
||||
' \n' +
|
||||
'@@ -99,7 +98,7 @@ jQuery.fn.extend({\n' +
|
||||
' \n' +
|
||||
' // Support: BlackBerry 5, iOS 3 (original iPhone)\n' +
|
||||
' // If we dont have gBCR, just use 0,0 rather than error\n' +
|
||||
'- if ( typeof elem.getBoundingClientRect !== strundefined ) {\n' +
|
||||
'+ if ( elem.getBoundingClientRect !== undefined ) {\n' +
|
||||
' box = elem.getBoundingClientRect();\n' +
|
||||
' }\n' +
|
||||
' win = getWindow( doc );\n' +
|
||||
'diff --git a/src/var/strundefined.js b/src/var/strundefined.js\n' +
|
||||
'deleted file mode 100644\n' +
|
||||
'index 04e16b0..0000000\n' +
|
||||
'--- a/src/var/strundefined.js\n' +
|
||||
'+++ /dev/null\n' +
|
||||
'@@ -1,3 +0,0 @@\n' +
|
||||
'-define(function() {\n' +
|
||||
'- return typeof undefined;\n' +
|
||||
'-});\n' +
|
||||
'diff --git a/components/textdiff/textdiff.html b/components/textdiff/textdiff.html\n' +
|
||||
'index a3484bf..82209af 100644\n' +
|
||||
'--- a/components/textdiff/textdiff.html\n' +
|
||||
'+++ b/components/textdiff/textdiff.html\n' +
|
||||
'@@ -1,6 +1,8 @@\n' +
|
||||
' <!-- ko if: isShowingDiffs -->\n' +
|
||||
' <div>\n' +
|
||||
'- <div data-bind="event: { load: setDom($element) }"></div>\n' +
|
||||
'+ <!-- ko if: isParsed -->\n' +
|
||||
'+ <div data-bind="template: {nodes: ko.utils.parseHtmlFragment(htmlSrc())}"></div>\n' +
|
||||
'+ <!-- /ko -->\n' +
|
||||
' <div class="btn-load-more" data-bind="visible: loadMoreCount() > 0">\n' +
|
||||
' </div>\n' +
|
||||
'diff --git a/test.js b/test.js\n' +
|
||||
'new file mode 100644\n' +
|
||||
'index 0000000..e1e22ec\n' +
|
||||
'--- /dev/null\n' +
|
||||
'+++ b/test.js\n' +
|
||||
'@@ -0,0 +1,6 @@\n' +
|
||||
"+var parser = require('./source/git-parser');\n" +
|
||||
'+\n' +
|
||||
"+var text = 'diff --git a/components/app/app.html b/components/app/app.html\\nindex ecb7a95..027bd9b 100644\\n--- a/components/app/app.html\\n+++ b/components/app/app.html\\n@@ -52,0 +53,3 @@\\n+\\n+\\n+\\n@@ -56,0 +60,3 @@\\n+\\n+\\n+\\n'\n" +
|
||||
'+var patchLineList = [ false, false, false, false ];\n' +
|
||||
'+\n' +
|
||||
'+console.log(parser.parsePatchDiffResult(text, patchLineList));\n' +
|
||||
"diff --git a/a.xml b/b.xml\n" +
|
||||
"index e54317e..82a9a56 100644\n" +
|
||||
"--- a/a.xml\n" +
|
||||
"+++ b/b.xml\n" +
|
||||
"@@ -242,4 +242,6 @@ need to create a new job for native server java api and move these tests to a ne\n" +
|
||||
" </packages>\n" +
|
||||
" </test>\n" +
|
||||
" -->\n" +
|
||||
"+\n" +
|
||||
"+\n";
|
||||
|
||||
$(document).ready(function() {
|
||||
var diff2htmlUi = new Diff2HtmlUI({diff: lineDiffExample});
|
||||
|
||||
diff2htmlUi.draw('#line-by-line', {inputFormat: 'json', showFiles: true, matching: 'lines'});
|
||||
diff2htmlUi.fileListCloseable('#line-by-line', false);
|
||||
diff2htmlUi.highlightCode('#line-by-line');
|
||||
|
||||
diff2htmlUi.draw('#side-by-side', {
|
||||
inputFormat: 'json',
|
||||
showFiles: true,
|
||||
matching: 'lines',
|
||||
outputFormat: 'side-by-side'
|
||||
});
|
||||
diff2htmlUi.fileListCloseable('#side-by-side', false);
|
||||
diff2htmlUi.highlightCode('#side-by-side');
|
||||
});
|
||||
</script>
|
||||
|
||||
</head>
|
||||
<body style="text-align: center; font-family: 'Source Sans Pro',sans-serif;">
|
||||
<h1>Diff to HTML by <a href="https://github.com/rtfpessoa">rtfpessoa</a></h1>
|
||||
|
||||
<h2>Line by Line</h2>
|
||||
|
||||
<div id="line-by-line" style="margin: 0 auto; max-width: 900px;">
|
||||
</div>
|
||||
|
||||
<h2>Side by Side</h2>
|
||||
|
||||
<div id="side-by-side" style="margin: 0 auto;">
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
188
scripts/hulk.ts
188
scripts/hulk.ts
|
|
@ -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
|
|
@ -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>"
|
||||
`);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -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>"`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -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"> </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>"
|
||||
`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -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 &', () => {
|
||||
const result = escapeForHtml('&');
|
||||
expect(result).toBe('&');
|
||||
});
|
||||
it('should escape < with <', () => {
|
||||
const result = escapeForHtml('<');
|
||||
expect(result).toBe('<');
|
||||
});
|
||||
it('should escape > with >', () => {
|
||||
const result = escapeForHtml('>');
|
||||
expect(result).toBe('>');
|
||||
});
|
||||
it('should escape " with "', () => {
|
||||
const result = escapeForHtml('"');
|
||||
expect(result).toBe('"');
|
||||
});
|
||||
it("should escape ' with '", () => {
|
||||
const result = escapeForHtml("'");
|
||||
expect(result).toBe(''');
|
||||
});
|
||||
it('should escape / with /', () => {
|
||||
const result = escapeForHtml('/');
|
||||
expect(result).toBe('/');
|
||||
});
|
||||
it('should escape a string containing HTML code', () => {
|
||||
const result = escapeForHtml(`<a href="/search?q=diff2html">Search 'Diff2Html'</a>`);
|
||||
expect(result).toBe(
|
||||
'<a href="/search?q=diff2html">Search 'Diff2Html'</a>',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
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>;',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -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"> </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"> </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"> </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"> </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"> </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"> </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"> </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"> </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>",
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -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'));
|
||||
});
|
||||
});
|
||||
});
|
||||
231
src/diff-parser.js
Normal file
231
src/diff-parser.js
Normal file
|
|
@ -0,0 +1,231 @@
|
|||
/*
|
||||
*
|
||||
* Diff Parser (diff-parser.js)
|
||||
* Author: rtfpessoa
|
||||
*
|
||||
*/
|
||||
|
||||
(function() {
|
||||
|
||||
var utils = require('./utils.js').Utils;
|
||||
|
||||
var LINE_TYPE = {
|
||||
INSERTS: 'd2h-ins',
|
||||
DELETES: 'd2h-del',
|
||||
INSERT_CHANGES: 'd2h-ins d2h-change',
|
||||
DELETE_CHANGES: 'd2h-del d2h-change',
|
||||
CONTEXT: 'd2h-cntx',
|
||||
INFO: 'd2h-info'
|
||||
};
|
||||
|
||||
function DiffParser() {
|
||||
}
|
||||
|
||||
DiffParser.prototype.LINE_TYPE = LINE_TYPE;
|
||||
|
||||
DiffParser.prototype.generateDiffJson = function(diffInput) {
|
||||
var files = [];
|
||||
var currentFile = null;
|
||||
var currentBlock = null;
|
||||
var oldLine = null;
|
||||
var 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 it has name (to avoid binary files errors)
|
||||
*/
|
||||
if (currentFile && currentFile.newName) {
|
||||
files.push(currentFile);
|
||||
currentFile = null;
|
||||
}
|
||||
};
|
||||
|
||||
var startFile = function() {
|
||||
saveBlock();
|
||||
saveFile();
|
||||
|
||||
/* Create file structure */
|
||||
currentFile = {};
|
||||
currentFile.blocks = [];
|
||||
currentFile.deletedLines = 0;
|
||||
currentFile.addedLines = 0;
|
||||
};
|
||||
|
||||
var startBlock = function(line) {
|
||||
saveBlock();
|
||||
|
||||
var values;
|
||||
|
||||
if (values = /^@@ -(\d+),\d+ \+(\d+),\d+ @@.*/.exec(line)) {
|
||||
currentFile.isCombined = false;
|
||||
} else if (values = /^@@@ -(\d+),\d+ -\d+,\d+ \+(\d+),\d+ @@@.*/.exec(line)) {
|
||||
currentFile.isCombined = true;
|
||||
} else {
|
||||
values = [0, 0];
|
||||
currentFile.isCombined = false;
|
||||
}
|
||||
|
||||
oldLine = values[1];
|
||||
newLine = values[2];
|
||||
|
||||
/* Create block metadata */
|
||||
currentBlock = {};
|
||||
currentBlock.lines = [];
|
||||
currentBlock.oldStartLine = oldLine;
|
||||
currentBlock.newStartLine = newLine;
|
||||
currentBlock.header = line;
|
||||
};
|
||||
|
||||
var createLine = function(line) {
|
||||
var currentLine = {};
|
||||
currentLine.content = line;
|
||||
|
||||
var newLinePrefixes = !currentFile.isCombined ? ['+'] : ['+', ' +'];
|
||||
var delLinePrefixes = !currentFile.isCombined ? ['-'] : ['-', ' -'];
|
||||
|
||||
/* Fill the line data */
|
||||
if (utils.startsWith(line, newLinePrefixes)) {
|
||||
currentFile.addedLines++;
|
||||
|
||||
currentLine.type = LINE_TYPE.INSERTS;
|
||||
currentLine.oldNumber = null;
|
||||
currentLine.newNumber = newLine++;
|
||||
|
||||
currentBlock.lines.push(currentLine);
|
||||
|
||||
} else if (utils.startsWith(line, delLinePrefixes)) {
|
||||
currentFile.deletedLines++;
|
||||
|
||||
currentLine.type = LINE_TYPE.DELETES;
|
||||
currentLine.oldNumber = oldLine++;
|
||||
currentLine.newNumber = null;
|
||||
|
||||
currentBlock.lines.push(currentLine);
|
||||
|
||||
} else {
|
||||
currentLine.type = LINE_TYPE.CONTEXT;
|
||||
currentLine.oldNumber = oldLine++;
|
||||
currentLine.newNumber = newLine++;
|
||||
|
||||
currentBlock.lines.push(currentLine);
|
||||
}
|
||||
};
|
||||
|
||||
var diffLines =
|
||||
diffInput.replace(/\\ No newline at end of file/g, '')
|
||||
.replace(/\r\n?/g, '\n')
|
||||
.split('\n');
|
||||
|
||||
/* Diff */
|
||||
var oldMode = /^old mode (\d{6})/;
|
||||
var newMode = /^new mode (\d{6})/;
|
||||
var deletedFileMode = /^deleted file mode (\d{6})/;
|
||||
var newFileMode = /^new file mode (\d{6})/;
|
||||
|
||||
var copyFrom = /^copy from (.+)/;
|
||||
var copyTo = /^copy to (.+)/;
|
||||
|
||||
var renameFrom = /^rename from (.+)/;
|
||||
var renameTo = /^rename to (.+)/;
|
||||
|
||||
var similarityIndex = /^similarity index (\d+)%/;
|
||||
var dissimilarityIndex = /^dissimilarity index (\d+)%/;
|
||||
var index = /^index ([0-9a-z]+)..([0-9a-z]+) (\d{6})?/;
|
||||
|
||||
/* Combined Diff */
|
||||
var combinedIndex = /^index ([0-9a-z]+),([0-9a-z]+)..([0-9a-z]+)/;
|
||||
var combinedMode = /^mode (\d{6}),(\d{6})..(\d{6})/;
|
||||
var combinedNewFile = /^new file mode (\d{6})/;
|
||||
var combinedDeletedFile = /^deleted file mode (\d{6}),(\d{6})/;
|
||||
|
||||
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 || utils.startsWith(line, '*')) {
|
||||
return;
|
||||
}
|
||||
|
||||
var values = [];
|
||||
if (utils.startsWith(line, 'diff')) {
|
||||
startFile();
|
||||
} else if (currentFile && !currentFile.oldName && (values = /^--- [aiwco]\/(.+)$/.exec(line))) {
|
||||
currentFile.oldName = values[1];
|
||||
currentFile.language = getExtension(currentFile.oldName, currentFile.language);
|
||||
} else if (currentFile && !currentFile.newName && (values = /^\+\+\+ [biwco]?\/(.+)$/.exec(line))) {
|
||||
currentFile.newName = values[1];
|
||||
currentFile.language = getExtension(currentFile.newName, currentFile.language);
|
||||
} else if (currentFile && utils.startsWith(line, '@@')) {
|
||||
startBlock(line);
|
||||
} else 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];
|
||||
} else if ((values = newFileMode.exec(line))) {
|
||||
currentFile.newFileMode = values[1];
|
||||
} else if ((values = copyFrom.exec(line))) {
|
||||
currentFile.oldName = values[1];
|
||||
currentFile.isCopy = true;
|
||||
} else if ((values = copyTo.exec(line))) {
|
||||
currentFile.newName = values[1];
|
||||
currentFile.isCopy = true;
|
||||
} else if ((values = renameFrom.exec(line))) {
|
||||
currentFile.oldName = values[1];
|
||||
currentFile.isRename = true;
|
||||
} else if ((values = renameTo.exec(line))) {
|
||||
currentFile.newName = values[1];
|
||||
currentFile.isRename = true;
|
||||
} else if ((values = similarityIndex.exec(line))) {
|
||||
currentFile.unchangedPercentage = values[1];
|
||||
} else if ((values = dissimilarityIndex.exec(line))) {
|
||||
currentFile.changedPercentage = values[1];
|
||||
} else if ((values = index.exec(line))) {
|
||||
currentFile.checksumBefore = values[1];
|
||||
currentFile.checksumAfter = values[2];
|
||||
values[2] && (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];
|
||||
} else if ((values = combinedDeletedFile.exec(line))) {
|
||||
currentFile.deletedFileMode = values[1];
|
||||
} else if (currentBlock) {
|
||||
createLine(line);
|
||||
}
|
||||
});
|
||||
|
||||
saveBlock();
|
||||
saveFile();
|
||||
|
||||
return files;
|
||||
};
|
||||
|
||||
function getExtension(filename, language) {
|
||||
var nameSplit = filename.split('.');
|
||||
if (nameSplit.length > 1) {
|
||||
return nameSplit[nameSplit.length - 1];
|
||||
}
|
||||
|
||||
return language;
|
||||
}
|
||||
|
||||
module.exports.DiffParser = new DiffParser();
|
||||
|
||||
})();
|
||||
|
|
@ -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;
|
||||
}
|
||||
110
src/diff2html.js
Normal file
110
src/diff2html.js
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
*
|
||||
* Diff to HTML (diff2html.js)
|
||||
* Author: rtfpessoa
|
||||
*
|
||||
*/
|
||||
|
||||
(function() {
|
||||
|
||||
var diffParser = require('./diff-parser.js').DiffParser;
|
||||
var fileLister = require('./file-list-printer.js').FileListPrinter;
|
||||
var htmlPrinter = require('./html-printer.js').HtmlPrinter;
|
||||
|
||||
function Diff2Html() {
|
||||
}
|
||||
|
||||
/*
|
||||
* Line diff type configuration
|
||||
var config = {
|
||||
'wordByWord': true, // (default)
|
||||
// OR
|
||||
'charByChar': true
|
||||
};
|
||||
*/
|
||||
|
||||
/*
|
||||
* Generates json object from string diff input
|
||||
*/
|
||||
Diff2Html.prototype.getJsonFromDiff = function(diffInput) {
|
||||
return diffParser.generateDiffJson(diffInput);
|
||||
};
|
||||
|
||||
/*
|
||||
* Generates the html diff. The config parameter configures the output/input formats and other options
|
||||
*/
|
||||
Diff2Html.prototype.getPrettyHtml = function(diffInput, config) {
|
||||
var configOrEmpty = config || {};
|
||||
|
||||
var diffJson = diffInput;
|
||||
if (!configOrEmpty.inputFormat || configOrEmpty.inputFormat === 'diff') {
|
||||
diffJson = diffParser.generateDiffJson(diffInput);
|
||||
}
|
||||
|
||||
var fileList = '';
|
||||
if (configOrEmpty.showFiles === true) {
|
||||
fileList = fileLister.generateFileList(diffJson, configOrEmpty);
|
||||
}
|
||||
|
||||
var diffOutput = '';
|
||||
if (configOrEmpty.outputFormat === 'side-by-side') {
|
||||
diffOutput = htmlPrinter.generateSideBySideJsonHtml(diffJson, configOrEmpty);
|
||||
} else {
|
||||
diffOutput = htmlPrinter.generateLineByLineJsonHtml(diffJson, configOrEmpty);
|
||||
}
|
||||
|
||||
return fileList + diffOutput;
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Deprecated methods - The following methods exist only to maintain compatibility with previous versions
|
||||
*/
|
||||
|
||||
/*
|
||||
* Generates pretty html from string diff input
|
||||
*/
|
||||
Diff2Html.prototype.getPrettyHtmlFromDiff = function(diffInput, config) {
|
||||
var configOrEmpty = config || {};
|
||||
configOrEmpty.inputFormat = 'diff';
|
||||
configOrEmpty.outputFormat = 'line-by-line';
|
||||
return this.getPrettyHtml(diffInput, configOrEmpty);
|
||||
};
|
||||
|
||||
/*
|
||||
* Generates pretty html from a json object
|
||||
*/
|
||||
Diff2Html.prototype.getPrettyHtmlFromJson = function(diffJson, config) {
|
||||
var configOrEmpty = config || {};
|
||||
configOrEmpty.inputFormat = 'json';
|
||||
configOrEmpty.outputFormat = 'line-by-line';
|
||||
return this.getPrettyHtml(diffJson, configOrEmpty);
|
||||
};
|
||||
|
||||
/*
|
||||
* Generates pretty side by side html from string diff input
|
||||
*/
|
||||
Diff2Html.prototype.getPrettySideBySideHtmlFromDiff = function(diffInput, config) {
|
||||
var configOrEmpty = config || {};
|
||||
configOrEmpty.inputFormat = 'diff';
|
||||
configOrEmpty.outputFormat = 'side-by-side';
|
||||
return this.getPrettyHtml(diffInput, configOrEmpty);
|
||||
};
|
||||
|
||||
/*
|
||||
* Generates pretty side by side html from a json object
|
||||
*/
|
||||
Diff2Html.prototype.getPrettySideBySideHtmlFromJson = function(diffJson, config) {
|
||||
var configOrEmpty = config || {};
|
||||
configOrEmpty.inputFormat = 'json';
|
||||
configOrEmpty.outputFormat = 'side-by-side';
|
||||
return this.getPrettyHtml(diffJson, configOrEmpty);
|
||||
};
|
||||
|
||||
var diffObject = new Diff2Html();
|
||||
module.exports.Diff2Html = diffObject;
|
||||
|
||||
// Expose diff2html in the browser
|
||||
global.Diff2Html = diffObject;
|
||||
|
||||
})();
|
||||
|
|
@ -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;
|
||||
}
|
||||
41
src/file-list-printer.js
Normal file
41
src/file-list-printer.js
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
*
|
||||
* FileListPrinter (file-list-printer.js)
|
||||
* Author: nmatpt
|
||||
*
|
||||
*/
|
||||
|
||||
(function() {
|
||||
|
||||
var printerUtils = require('./printer-utils.js').PrinterUtils;
|
||||
|
||||
function FileListPrinter() {
|
||||
}
|
||||
|
||||
FileListPrinter.prototype.generateFileList = function(diffFiles) {
|
||||
return '<div class="d2h-file-list-wrapper">\n' +
|
||||
' <div class="d2h-file-list-header">Files changed (' + diffFiles.length + ')  </div>\n' +
|
||||
' <a class="d2h-file-switch d2h-hide">hide</a>\n' +
|
||||
' <a class="d2h-file-switch d2h-show">show</a>\n' +
|
||||
' <div class="d2h-clear"></div>\n' +
|
||||
' <table class="d2h-file-list">\n' +
|
||||
|
||||
|
||||
diffFiles.map(function(file) {
|
||||
return ' <tr class="d2h-file-list-line">\n' +
|
||||
' <td class="d2h-lines-added">\n' +
|
||||
' <span>+' + file.addedLines + '</span>\n' +
|
||||
' </td>\n' +
|
||||
' <td class="d2h-lines-deleted">\n' +
|
||||
' <span>-' + file.deletedLines + '</span>\n' +
|
||||
' </td>\n' +
|
||||
' <td class="d2h-file-name"><a href="#' + printerUtils.getHtmlId(file) + '">' +
|
||||
' ' + printerUtils.getDiffName(file) + '</a></td>\n' +
|
||||
' </tr>\n';
|
||||
}).join('\n') +
|
||||
'</table></div>\n';
|
||||
};
|
||||
|
||||
module.exports.FileListPrinter = new FileListPrinter();
|
||||
|
||||
})();
|
||||
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -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}`;
|
||||
}
|
||||
}
|
||||
28
src/html-printer.js
Normal file
28
src/html-printer.js
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
*
|
||||
* HtmlPrinter (html-printer.js)
|
||||
* Author: rtfpessoa
|
||||
*
|
||||
*/
|
||||
|
||||
(function() {
|
||||
|
||||
var LineByLinePrinter = require('./line-by-line-printer.js').LineByLinePrinter;
|
||||
var SideBySidePrinter = require('./side-by-side-printer.js').SideBySidePrinter;
|
||||
|
||||
function HtmlPrinter() {
|
||||
}
|
||||
|
||||
HtmlPrinter.prototype.generateLineByLineJsonHtml = function(diffFiles, config) {
|
||||
var lineByLinePrinter = new LineByLinePrinter(config);
|
||||
return lineByLinePrinter.generateLineByLineJsonHtml(diffFiles);
|
||||
};
|
||||
|
||||
HtmlPrinter.prototype.generateSideBySideJsonHtml = function(diffFiles, config) {
|
||||
var sideBySidePrinter = new SideBySidePrinter(config);
|
||||
return sideBySidePrinter.generateSideBySideJsonHtml(diffFiles);
|
||||
};
|
||||
|
||||
module.exports.HtmlPrinter = new HtmlPrinter();
|
||||
|
||||
})();
|
||||
218
src/line-by-line-printer.js
Normal file
218
src/line-by-line-printer.js
Normal file
|
|
@ -0,0 +1,218 @@
|
|||
/*
|
||||
*
|
||||
* LineByLinePrinter (line-by-line-printer.js)
|
||||
* Author: rtfpessoa
|
||||
*
|
||||
*/
|
||||
|
||||
(function() {
|
||||
|
||||
var diffParser = require('./diff-parser.js').DiffParser;
|
||||
var printerUtils = require('./printer-utils.js').PrinterUtils;
|
||||
var utils = require('./utils.js').Utils;
|
||||
var Rematch = require('./rematch.js').Rematch;
|
||||
|
||||
function LineByLinePrinter(config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
LineByLinePrinter.prototype.makeFileDiffHtml = function(file, diffs) {
|
||||
return '<div id="' + printerUtils.getHtmlId(file) + '" class="d2h-file-wrapper" data-lang="' + file.language + '">\n' +
|
||||
' <div class="d2h-file-header">\n' +
|
||||
' <div class="d2h-file-stats">\n' +
|
||||
' <span class="d2h-lines-added">' +
|
||||
' <span>+' + file.addedLines + '</span>\n' +
|
||||
' </span>\n' +
|
||||
' <span class="d2h-lines-deleted">' +
|
||||
' <span>-' + file.deletedLines + '</span>\n' +
|
||||
' </span>\n' +
|
||||
' </div>\n' +
|
||||
' <div class="d2h-file-name">' + printerUtils.getDiffName(file) + '</div>\n' +
|
||||
' </div>\n' +
|
||||
' <div class="d2h-file-diff">\n' +
|
||||
' <div class="d2h-code-wrapper">\n' +
|
||||
' <table class="d2h-diff-table">\n' +
|
||||
' <tbody class="d2h-diff-tbody">\n' +
|
||||
' ' + diffs +
|
||||
' </tbody>\n' +
|
||||
' </table>\n' +
|
||||
' </div>\n' +
|
||||
' </div>\n' +
|
||||
' </div>\n';
|
||||
};
|
||||
|
||||
LineByLinePrinter.prototype.generateLineByLineJsonHtml = function(diffFiles) {
|
||||
var that = this;
|
||||
var htmlDiffs = diffFiles.map(function(file) {
|
||||
var diffs;
|
||||
if (file.blocks.length) {
|
||||
diffs = that._generateFileHtml(file);
|
||||
} else {
|
||||
diffs = that._generateEmptyDiff();
|
||||
}
|
||||
return that.makeFileDiffHtml(file, diffs);
|
||||
});
|
||||
|
||||
return '<div class="d2h-wrapper">\n' + htmlDiffs.join('\n') + '</div>\n';
|
||||
};
|
||||
|
||||
var matcher = Rematch.rematch(function(a, b) {
|
||||
var amod = a.content.substr(1);
|
||||
var bmod = b.content.substr(1);
|
||||
|
||||
return Rematch.distance(amod, bmod);
|
||||
});
|
||||
|
||||
LineByLinePrinter.prototype.makeColumnLineNumberHtml = function(block) {
|
||||
return '<tr>\n' +
|
||||
' <td class="d2h-code-linenumber ' + diffParser.LINE_TYPE.INFO + '"></td>\n' +
|
||||
' <td class="' + diffParser.LINE_TYPE.INFO + '">' +
|
||||
' <div class="d2h-code-line ' + diffParser.LINE_TYPE.INFO + '">' + utils.escape(block.header) + '</div>' +
|
||||
' </td>\n' +
|
||||
'</tr>\n';
|
||||
};
|
||||
|
||||
LineByLinePrinter.prototype._generateFileHtml = function(file) {
|
||||
var that = this;
|
||||
return file.blocks.map(function(block) {
|
||||
|
||||
var lines = that.makeColumnLineNumberHtml(block);
|
||||
var oldLines = [];
|
||||
var newLines = [];
|
||||
|
||||
function processChangeBlock() {
|
||||
var matches;
|
||||
var insertType;
|
||||
var deleteType;
|
||||
|
||||
var doMatching = that.config.matching === 'lines' || that.config.matching === 'words';
|
||||
|
||||
if (doMatching) {
|
||||
matches = matcher(oldLines, newLines);
|
||||
insertType = diffParser.LINE_TYPE.INSERT_CHANGES;
|
||||
deleteType = diffParser.LINE_TYPE.DELETE_CHANGES;
|
||||
} else {
|
||||
matches = [[oldLines, newLines]];
|
||||
insertType = diffParser.LINE_TYPE.INSERTS;
|
||||
deleteType = diffParser.LINE_TYPE.DELETES;
|
||||
}
|
||||
|
||||
matches.forEach(function(match) {
|
||||
oldLines = match[0];
|
||||
newLines = match[1];
|
||||
|
||||
var processedOldLines = [];
|
||||
var processedNewLines = [];
|
||||
|
||||
var common = Math.min(oldLines.length, newLines.length);
|
||||
|
||||
var oldLine, newLine;
|
||||
for (var j = 0; j < common; j++) {
|
||||
oldLine = oldLines[j];
|
||||
newLine = newLines[j];
|
||||
|
||||
that.config.isCombined = file.isCombined;
|
||||
var diff = printerUtils.diffHighlight(oldLine.content, newLine.content, that.config);
|
||||
|
||||
processedOldLines +=
|
||||
that._generateLineHtml(deleteType, oldLine.oldNumber, oldLine.newNumber,
|
||||
diff.first.line, diff.first.prefix);
|
||||
processedNewLines +=
|
||||
that._generateLineHtml(insertType, newLine.oldNumber, newLine.newNumber,
|
||||
diff.second.line, diff.second.prefix);
|
||||
}
|
||||
|
||||
lines += processedOldLines + processedNewLines;
|
||||
lines += that._processLines(oldLines.slice(common), newLines.slice(common));
|
||||
});
|
||||
|
||||
oldLines = [];
|
||||
newLines = [];
|
||||
}
|
||||
|
||||
for (var i = 0; i < block.lines.length; i++) {
|
||||
var line = block.lines[i];
|
||||
var escapedLine = utils.escape(line.content);
|
||||
|
||||
if (line.type !== diffParser.LINE_TYPE.INSERTS &&
|
||||
(newLines.length > 0 || (line.type !== diffParser.LINE_TYPE.DELETES && oldLines.length > 0))) {
|
||||
processChangeBlock();
|
||||
}
|
||||
|
||||
if (line.type === diffParser.LINE_TYPE.CONTEXT) {
|
||||
lines += that._generateLineHtml(line.type, line.oldNumber, line.newNumber, escapedLine);
|
||||
} else if (line.type === diffParser.LINE_TYPE.INSERTS && !oldLines.length) {
|
||||
lines += that._generateLineHtml(line.type, line.oldNumber, line.newNumber, escapedLine);
|
||||
} else if (line.type === diffParser.LINE_TYPE.DELETES) {
|
||||
oldLines.push(line);
|
||||
} else if (line.type === diffParser.LINE_TYPE.INSERTS && Boolean(oldLines.length)) {
|
||||
newLines.push(line);
|
||||
} else {
|
||||
console.error('Unknown state in html line-by-line generator');
|
||||
processChangeBlock();
|
||||
}
|
||||
}
|
||||
|
||||
processChangeBlock();
|
||||
|
||||
return lines;
|
||||
}).join('\n');
|
||||
};
|
||||
|
||||
LineByLinePrinter.prototype._processLines = function(oldLines, newLines) {
|
||||
var lines = '';
|
||||
|
||||
for (var i = 0; i < oldLines.length; i++) {
|
||||
var oldLine = oldLines[i];
|
||||
var oldEscapedLine = utils.escape(oldLine.content);
|
||||
lines += this._generateLineHtml(oldLine.type, oldLine.oldNumber, oldLine.newNumber, oldEscapedLine);
|
||||
}
|
||||
|
||||
for (var j = 0; j < newLines.length; j++) {
|
||||
var newLine = newLines[j];
|
||||
var newEscapedLine = utils.escape(newLine.content);
|
||||
lines += this._generateLineHtml(newLine.type, newLine.oldNumber, newLine.newNumber, newEscapedLine);
|
||||
}
|
||||
|
||||
return lines;
|
||||
};
|
||||
|
||||
LineByLinePrinter.prototype.makeLineHtml = function(type, oldNumber, newNumber, htmlPrefix, htmlContent) {
|
||||
return '<tr>\n' +
|
||||
' <td class="d2h-code-linenumber ' + type + '">' +
|
||||
' <div class="line-num1">' + utils.valueOrEmpty(oldNumber) + '</div>' +
|
||||
' <div class="line-num2">' + utils.valueOrEmpty(newNumber) + '</div>' +
|
||||
' </td>\n' +
|
||||
' <td class="' + type + '">' +
|
||||
' <div class="d2h-code-line ' + type + '">' + htmlPrefix + htmlContent + '</div>' +
|
||||
' </td>\n' +
|
||||
'</tr>\n';
|
||||
};
|
||||
|
||||
LineByLinePrinter.prototype._generateLineHtml = function(type, oldNumber, newNumber, content, prefix) {
|
||||
var htmlPrefix = '';
|
||||
if (prefix) {
|
||||
htmlPrefix = '<span class="d2h-code-line-prefix">' + prefix + '</span>';
|
||||
}
|
||||
|
||||
var htmlContent = '';
|
||||
if (content) {
|
||||
htmlContent = '<span class="d2h-code-line-ctn">' + content + '</span>';
|
||||
}
|
||||
|
||||
return this.makeLineHtml(type, oldNumber, newNumber, htmlPrefix, htmlContent);
|
||||
};
|
||||
|
||||
LineByLinePrinter.prototype._generateEmptyDiff = function() {
|
||||
return '<tr>\n' +
|
||||
' <td class="' + diffParser.LINE_TYPE.INFO + '">' +
|
||||
' <div class="d2h-code-line ' + diffParser.LINE_TYPE.INFO + '">' +
|
||||
'File without changes' +
|
||||
' </div>' +
|
||||
' </td>\n' +
|
||||
'</tr>\n';
|
||||
};
|
||||
|
||||
module.exports.LineByLinePrinter = LineByLinePrinter;
|
||||
|
||||
})();
|
||||
|
|
@ -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 === ' ' ? ' ' : 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;
|
||||
};
|
||||
149
src/printer-utils.js
Normal file
149
src/printer-utils.js
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
/*
|
||||
*
|
||||
* PrinterUtils (printer-utils.js)
|
||||
* Author: rtfpessoa
|
||||
*
|
||||
*/
|
||||
|
||||
(function() {
|
||||
|
||||
var jsDiff = require('diff');
|
||||
var utils = require('./utils.js').Utils;
|
||||
var Rematch = require('./rematch.js').Rematch;
|
||||
|
||||
function PrinterUtils() {
|
||||
}
|
||||
|
||||
PrinterUtils.prototype.getHtmlId = function(file) {
|
||||
var hashCode = function(text) {
|
||||
var i, chr, len;
|
||||
var hash = 0;
|
||||
|
||||
if (text.length === 0) {
|
||||
return hash;
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
return 'd2h-' + hashCode(this.getDiffName(file)).toString().slice(-6);
|
||||
};
|
||||
|
||||
PrinterUtils.prototype.getDiffName = function(file) {
|
||||
var oldFilename = file.oldName;
|
||||
var newFilename = file.newName;
|
||||
|
||||
if (oldFilename && newFilename && oldFilename !== newFilename && !isDeletedName(newFilename)) {
|
||||
return oldFilename + ' -> ' + newFilename;
|
||||
} else if (newFilename && !isDeletedName(newFilename)) {
|
||||
return newFilename;
|
||||
} else if (oldFilename) {
|
||||
return oldFilename;
|
||||
}
|
||||
|
||||
return 'Unknown filename';
|
||||
};
|
||||
|
||||
PrinterUtils.prototype.diffHighlight = function(diffLine1, diffLine2, config) {
|
||||
var linePrefix1, linePrefix2, unprefixedLine1, unprefixedLine2;
|
||||
|
||||
var prefixSize = 1;
|
||||
|
||||
if (config.isCombined) {
|
||||
prefixSize = 2;
|
||||
}
|
||||
|
||||
linePrefix1 = diffLine1.substr(0, prefixSize);
|
||||
linePrefix2 = diffLine2.substr(0, prefixSize);
|
||||
unprefixedLine1 = diffLine1.substr(prefixSize);
|
||||
unprefixedLine2 = diffLine2.substr(prefixSize);
|
||||
|
||||
var diff;
|
||||
if (config.charByChar) {
|
||||
diff = jsDiff.diffChars(unprefixedLine1, unprefixedLine2);
|
||||
} else {
|
||||
diff = jsDiff.diffWordsWithSpace(unprefixedLine1, unprefixedLine2);
|
||||
}
|
||||
|
||||
var highlightedLine = '';
|
||||
|
||||
var changedWords = [];
|
||||
if (!config.charByChar && config.matching === 'words') {
|
||||
var treshold = 0.25;
|
||||
|
||||
if (typeof (config.matchWordsThreshold) !== 'undefined') {
|
||||
treshold = config.matchWordsThreshold;
|
||||
}
|
||||
|
||||
var matcher = Rematch.rematch(function(a, b) {
|
||||
var amod = a.value;
|
||||
var bmod = b.value;
|
||||
|
||||
return Rematch.distance(amod, bmod);
|
||||
});
|
||||
|
||||
var removed = diff.filter(function isRemoved(element) {
|
||||
return element.removed;
|
||||
});
|
||||
|
||||
var added = diff.filter(function isAdded(element) {
|
||||
return element.added;
|
||||
});
|
||||
|
||||
var chunks = matcher(added, removed);
|
||||
chunks.forEach(function(chunk) {
|
||||
if (chunk[0].length === 1 && chunk[1].length === 1) {
|
||||
var dist = Rematch.distance(chunk[0][0].value, chunk[1][0].value);
|
||||
if (dist < treshold) {
|
||||
changedWords.push(chunk[0][0]);
|
||||
changedWords.push(chunk[1][0]);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
diff.forEach(function(part) {
|
||||
var addClass = changedWords.indexOf(part) > -1 ? ' class="d2h-change"' : '';
|
||||
var elemType = part.added ? 'ins' : part.removed ? 'del' : null;
|
||||
var escapedValue = utils.escape(part.value);
|
||||
|
||||
if (elemType !== null) {
|
||||
highlightedLine += '<' + elemType + addClass + '>' + escapedValue + '</' + elemType + '>';
|
||||
} else {
|
||||
highlightedLine += escapedValue;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
first: {
|
||||
prefix: linePrefix1,
|
||||
line: removeIns(highlightedLine)
|
||||
},
|
||||
second: {
|
||||
prefix: linePrefix2,
|
||||
line: removeDel(highlightedLine)
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
function isDeletedName(name) {
|
||||
return name === 'dev/null';
|
||||
}
|
||||
|
||||
function removeIns(line) {
|
||||
return line.replace(/(<ins[^>]*>((.|\n)*?)<\/ins>)/g, '');
|
||||
}
|
||||
|
||||
function removeDel(line) {
|
||||
return line.replace(/(<del[^>]*>((.|\n)*?)<\/del>)/g, '');
|
||||
}
|
||||
|
||||
module.exports.PrinterUtils = new PrinterUtils();
|
||||
|
||||
})();
|
||||
156
src/rematch.js
Normal file
156
src/rematch.js
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
/*
|
||||
*
|
||||
* Rematch (rematch.js)
|
||||
* Matching two sequences of objects by similarity
|
||||
* Author: W. Illmeyer, Nexxar GmbH
|
||||
*
|
||||
*/
|
||||
|
||||
(function() {
|
||||
|
||||
var Rematch = {};
|
||||
Rematch.arrayToString = function arrayToString(a) {
|
||||
if (Object.prototype.toString.apply(a, []) === "[object Array]") {
|
||||
return "[" + a.map(arrayToString).join(", ") + "]";
|
||||
} else {
|
||||
return a;
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
function levenshtein(a, b) {
|
||||
if (a.length == 0) {
|
||||
return b.length;
|
||||
}
|
||||
if (b.length == 0) {
|
||||
return a.length;
|
||||
}
|
||||
|
||||
var matrix = [];
|
||||
|
||||
// Increment along the first column of each row
|
||||
var i;
|
||||
for (i = 0; i <= b.length; i++) {
|
||||
matrix[i] = [i];
|
||||
}
|
||||
|
||||
// Increment each column in the first row
|
||||
var 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];
|
||||
}
|
||||
|
||||
Rematch.levenshtein = levenshtein;
|
||||
|
||||
Rematch.distance = function distance(x, y) {
|
||||
x = x.trim();
|
||||
y = y.trim();
|
||||
var lev = levenshtein(x, y);
|
||||
var score = lev / (x.length + y.length);
|
||||
|
||||
return score;
|
||||
};
|
||||
|
||||
Rematch.rematch = function rematch(distanceFunction) {
|
||||
function findBestMatch(a, b, cache) {
|
||||
var cachecount = 0;
|
||||
|
||||
for (var key in cache) {
|
||||
cachecount++;
|
||||
}
|
||||
|
||||
var bestMatchDist = Infinity;
|
||||
var bestMatch;
|
||||
for (var i = 0; i < a.length; ++i) {
|
||||
for (var j = 0; j < b.length; ++j) {
|
||||
var cacheKey = JSON.stringify([a[i], b[j]]);
|
||||
var md;
|
||||
if (cache.hasOwnProperty(cacheKey)) {
|
||||
md = cache[cacheKey];
|
||||
} else {
|
||||
md = distanceFunction(a[i], b[j]);
|
||||
cache[cacheKey] = md;
|
||||
}
|
||||
if (md < bestMatchDist) {
|
||||
bestMatchDist = md;
|
||||
bestMatch = {indexA: i, indexB: j, score: bestMatchDist};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bestMatch;
|
||||
}
|
||||
|
||||
function group(a, b, level, cache) {
|
||||
if (typeof (cache) === "undefined") {
|
||||
cache = {};
|
||||
}
|
||||
|
||||
var bm = findBestMatch(a, b, cache);
|
||||
|
||||
if (!level) {
|
||||
level = 0;
|
||||
}
|
||||
|
||||
if (!bm || (a.length + b.length < 3)) {
|
||||
return [[a, b]];
|
||||
}
|
||||
|
||||
var a1 = a.slice(0, bm.indexA);
|
||||
var b1 = b.slice(0, bm.indexB);
|
||||
var aMatch = [a[bm.indexA]];
|
||||
var bMatch = [b[bm.indexB]];
|
||||
var tailA = bm.indexA + 1;
|
||||
var tailB = bm.indexB + 1;
|
||||
var a2 = a.slice(tailA);
|
||||
var b2 = b.slice(tailB);
|
||||
|
||||
var group1 = group(a1, b1, level + 1, cache);
|
||||
var groupMatch = group(aMatch, bMatch, level + 1, cache);
|
||||
var group2 = group(a2, b2, level + 1, cache);
|
||||
var 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;
|
||||
};
|
||||
|
||||
module.exports.Rematch = Rematch;
|
||||
|
||||
})();
|
||||
135
src/rematch.ts
135
src/rematch.ts
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''')
|
||||
.replace(/\//g, '/');
|
||||
}
|
||||
|
||||
/**
|
||||
* 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),
|
||||
},
|
||||
};
|
||||
}
|
||||
269
src/side-by-side-printer.js
Normal file
269
src/side-by-side-printer.js
Normal file
|
|
@ -0,0 +1,269 @@
|
|||
/*
|
||||
*
|
||||
* HtmlPrinter (html-printer.js)
|
||||
* Author: rtfpessoa
|
||||
*
|
||||
*/
|
||||
|
||||
(function() {
|
||||
|
||||
var diffParser = require('./diff-parser.js').DiffParser;
|
||||
var printerUtils = require('./printer-utils.js').PrinterUtils;
|
||||
var utils = require('./utils.js').Utils;
|
||||
var Rematch = require('./rematch.js').Rematch;
|
||||
|
||||
var matcher = Rematch.rematch(function(a, b) {
|
||||
var amod = a.content.substr(1);
|
||||
var bmod = b.content.substr(1);
|
||||
|
||||
return Rematch.distance(amod, bmod);
|
||||
});
|
||||
|
||||
function SideBySidePrinter(config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
SideBySidePrinter.prototype.makeDiffHtml = function(file, diffs) {
|
||||
return '<div id="' + printerUtils.getHtmlId(file) + '" class="d2h-file-wrapper" data-lang="' + file.language + '">\n' +
|
||||
' <div class="d2h-file-header">\n' +
|
||||
' <div class="d2h-file-stats">\n' +
|
||||
' <span class="d2h-lines-added">' +
|
||||
' <span>+' + file.addedLines + '</span>\n' +
|
||||
' </span>\n' +
|
||||
' <span class="d2h-lines-deleted">' +
|
||||
' <span>-' + file.deletedLines + '</span>\n' +
|
||||
' </span>\n' +
|
||||
' </div>\n' +
|
||||
' <div class="d2h-file-name">' + printerUtils.getDiffName(file) + '</div>\n' +
|
||||
' </div>\n' +
|
||||
' <div class="d2h-files-diff">\n' +
|
||||
' <div class="d2h-file-side-diff">\n' +
|
||||
' <div class="d2h-code-wrapper">\n' +
|
||||
' <table class="d2h-diff-table">\n' +
|
||||
' <tbody class="d2h-diff-tbody">\n' +
|
||||
' ' + diffs.left +
|
||||
' </tbody>\n' +
|
||||
' </table>\n' +
|
||||
' </div>\n' +
|
||||
' </div>\n' +
|
||||
' <div class="d2h-file-side-diff">\n' +
|
||||
' <div class="d2h-code-wrapper">\n' +
|
||||
' <table class="d2h-diff-table">\n' +
|
||||
' <tbody class="d2h-diff-tbody">\n' +
|
||||
' ' + diffs.right +
|
||||
' </tbody>\n' +
|
||||
' </table>\n' +
|
||||
' </div>\n' +
|
||||
' </div>\n' +
|
||||
' </div>\n' +
|
||||
' </div>\n';
|
||||
};
|
||||
|
||||
SideBySidePrinter.prototype.generateSideBySideJsonHtml = function(diffFiles) {
|
||||
var that = this;
|
||||
return '<div class="d2h-wrapper">\n' +
|
||||
diffFiles.map(function(file) {
|
||||
|
||||
var diffs;
|
||||
if (file.blocks.length) {
|
||||
diffs = that.generateSideBySideFileHtml(file);
|
||||
} else {
|
||||
diffs = that.generateEmptyDiff();
|
||||
}
|
||||
|
||||
return that.makeDiffHtml(file, diffs);
|
||||
}).join('\n') +
|
||||
'</div>\n';
|
||||
};
|
||||
|
||||
SideBySidePrinter.prototype.makeSideHtml = function(blockHeader) {
|
||||
return '<tr>\n' +
|
||||
' <td class="d2h-code-side-linenumber ' + diffParser.LINE_TYPE.INFO + '"></td>\n' +
|
||||
' <td class="' + diffParser.LINE_TYPE.INFO + '">\n' +
|
||||
' <div class="d2h-code-side-line ' + diffParser.LINE_TYPE.INFO + '">' + blockHeader + '</div>\n' +
|
||||
' </td>\n' +
|
||||
'</tr>\n';
|
||||
};
|
||||
|
||||
SideBySidePrinter.prototype.generateSideBySideFileHtml = function(file) {
|
||||
var that = this;
|
||||
var fileHtml = {};
|
||||
fileHtml.left = '';
|
||||
fileHtml.right = '';
|
||||
|
||||
file.blocks.forEach(function(block) {
|
||||
|
||||
fileHtml.left += that.makeSideHtml(utils.escape(block.header));
|
||||
fileHtml.right += that.makeSideHtml('');
|
||||
|
||||
var oldLines = [];
|
||||
var newLines = [];
|
||||
|
||||
function processChangeBlock() {
|
||||
var matches;
|
||||
var insertType;
|
||||
var deleteType;
|
||||
var doMatching = that.config.matching === 'lines' || that.config.matching === 'words';
|
||||
|
||||
if (doMatching) {
|
||||
matches = matcher(oldLines, newLines);
|
||||
insertType = diffParser.LINE_TYPE.INSERT_CHANGES;
|
||||
deleteType = diffParser.LINE_TYPE.DELETE_CHANGES;
|
||||
} else {
|
||||
matches = [[oldLines, newLines]];
|
||||
insertType = diffParser.LINE_TYPE.INSERTS;
|
||||
deleteType = diffParser.LINE_TYPE.DELETES;
|
||||
}
|
||||
|
||||
matches.forEach(function(match) {
|
||||
oldLines = match[0];
|
||||
newLines = match[1];
|
||||
|
||||
var common = Math.min(oldLines.length, newLines.length);
|
||||
var max = Math.max(oldLines.length, newLines.length);
|
||||
|
||||
for (var j = 0; j < common; j++) {
|
||||
var oldLine = oldLines[j];
|
||||
var newLine = newLines[j];
|
||||
|
||||
that.config.isCombined = file.isCombined;
|
||||
|
||||
var diff = printerUtils.diffHighlight(oldLine.content, newLine.content, that.config);
|
||||
|
||||
fileHtml.left +=
|
||||
that.generateSingleLineHtml(deleteType, oldLine.oldNumber,
|
||||
diff.first.line, diff.first.prefix);
|
||||
fileHtml.right +=
|
||||
that.generateSingleLineHtml(insertType, newLine.newNumber,
|
||||
diff.second.line, diff.second.prefix);
|
||||
}
|
||||
|
||||
if (max > common) {
|
||||
var oldSlice = oldLines.slice(common);
|
||||
var newSlice = newLines.slice(common);
|
||||
|
||||
var tmpHtml = that.processLines(oldSlice, newSlice);
|
||||
fileHtml.left += tmpHtml.left;
|
||||
fileHtml.right += tmpHtml.right;
|
||||
}
|
||||
});
|
||||
|
||||
oldLines = [];
|
||||
newLines = [];
|
||||
}
|
||||
|
||||
for (var i = 0; i < block.lines.length; i++) {
|
||||
var line = block.lines[i];
|
||||
var prefix = line.content[0];
|
||||
var escapedLine = utils.escape(line.content.substr(1));
|
||||
|
||||
if (line.type !== diffParser.LINE_TYPE.INSERTS &&
|
||||
(newLines.length > 0 || (line.type !== diffParser.LINE_TYPE.DELETES && oldLines.length > 0))) {
|
||||
processChangeBlock();
|
||||
}
|
||||
|
||||
if (line.type === diffParser.LINE_TYPE.CONTEXT) {
|
||||
fileHtml.left += that.generateSingleLineHtml(line.type, line.oldNumber, escapedLine, prefix);
|
||||
fileHtml.right += that.generateSingleLineHtml(line.type, line.newNumber, escapedLine, prefix);
|
||||
} else if (line.type === diffParser.LINE_TYPE.INSERTS && !oldLines.length) {
|
||||
fileHtml.left += that.generateSingleLineHtml(diffParser.LINE_TYPE.CONTEXT, '', '', '');
|
||||
fileHtml.right += that.generateSingleLineHtml(line.type, line.newNumber, escapedLine, prefix);
|
||||
} else if (line.type === diffParser.LINE_TYPE.DELETES) {
|
||||
oldLines.push(line);
|
||||
} else if (line.type === diffParser.LINE_TYPE.INSERTS && Boolean(oldLines.length)) {
|
||||
newLines.push(line);
|
||||
} else {
|
||||
console.error('unknown state in html side-by-side generator');
|
||||
processChangeBlock();
|
||||
}
|
||||
}
|
||||
|
||||
processChangeBlock();
|
||||
});
|
||||
|
||||
return fileHtml;
|
||||
};
|
||||
|
||||
SideBySidePrinter.prototype.processLines = function(oldLines, newLines) {
|
||||
var that = this;
|
||||
var fileHtml = {};
|
||||
fileHtml.left = '';
|
||||
fileHtml.right = '';
|
||||
|
||||
var maxLinesNumber = Math.max(oldLines.length, newLines.length);
|
||||
for (var i = 0; i < maxLinesNumber; i++) {
|
||||
var oldLine = oldLines[i];
|
||||
var newLine = newLines[i];
|
||||
var oldContent;
|
||||
var newContent;
|
||||
var oldPrefix;
|
||||
var newPrefix;
|
||||
|
||||
if (oldLine) {
|
||||
oldContent = utils.escape(oldLine.content.substr(1));
|
||||
oldPrefix = oldLine.content[0];
|
||||
}
|
||||
|
||||
if (newLine) {
|
||||
newContent = utils.escape(newLine.content.substr(1));
|
||||
newPrefix = newLine.content[0];
|
||||
}
|
||||
|
||||
if (oldLine && newLine) {
|
||||
fileHtml.left += that.generateSingleLineHtml(oldLine.type, oldLine.oldNumber, oldContent, oldPrefix);
|
||||
fileHtml.right += that.generateSingleLineHtml(newLine.type, newLine.newNumber, newContent, newPrefix);
|
||||
} else if (oldLine) {
|
||||
fileHtml.left += that.generateSingleLineHtml(oldLine.type, oldLine.oldNumber, oldContent, oldPrefix);
|
||||
fileHtml.right += that.generateSingleLineHtml(diffParser.LINE_TYPE.CONTEXT, '', '', '');
|
||||
} else if (newLine) {
|
||||
fileHtml.left += that.generateSingleLineHtml(diffParser.LINE_TYPE.CONTEXT, '', '', '');
|
||||
fileHtml.right += that.generateSingleLineHtml(newLine.type, newLine.newNumber, newContent, newPrefix);
|
||||
} else {
|
||||
console.error('How did it get here?');
|
||||
}
|
||||
}
|
||||
|
||||
return fileHtml;
|
||||
};
|
||||
|
||||
SideBySidePrinter.prototype.makeSingleLineHtml = function(type, number, htmlContent, htmlPrefix) {
|
||||
return '<tr>\n' +
|
||||
' <td class="d2h-code-side-linenumber ' + type + '">' + number + '</td>\n' +
|
||||
' <td class="' + type + '">' +
|
||||
' <div class="d2h-code-side-line ' + type + '">' + htmlPrefix + htmlContent + '</div>' +
|
||||
' </td>\n' +
|
||||
' </tr>\n';
|
||||
};
|
||||
|
||||
SideBySidePrinter.prototype.generateSingleLineHtml = function(type, number, content, prefix) {
|
||||
var htmlPrefix = '';
|
||||
if (prefix) {
|
||||
htmlPrefix = '<span class="d2h-code-line-prefix">' + prefix + '</span>';
|
||||
}
|
||||
|
||||
var htmlContent = '';
|
||||
if (content) {
|
||||
htmlContent = '<span class="d2h-code-line-ctn">' + content + '</span>';
|
||||
}
|
||||
|
||||
return this.makeSingleLineHtml(type, number, htmlContent, htmlPrefix);
|
||||
};
|
||||
|
||||
SideBySidePrinter.prototype.generateEmptyDiff = function() {
|
||||
var fileHtml = {};
|
||||
fileHtml.right = '';
|
||||
|
||||
fileHtml.left = '<tr>\n' +
|
||||
' <td class="' + diffParser.LINE_TYPE.INFO + '">' +
|
||||
' <div class="d2h-code-side-line ' + diffParser.LINE_TYPE.INFO + '">' +
|
||||
'File without changes' +
|
||||
' </div>' +
|
||||
' </td>\n' +
|
||||
'</tr>\n';
|
||||
|
||||
return fileHtml;
|
||||
};
|
||||
|
||||
module.exports.SideBySidePrinter = SideBySidePrinter;
|
||||
|
||||
})();
|
||||
|
|
@ -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 === ' ' ? ' ' : 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;
|
||||
};
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
<tr>
|
||||
<td class="{{lineClass}} {{CSSLineClass.INFO}}"></td>
|
||||
<td class="{{CSSLineClass.INFO}}">
|
||||
<div class="{{contentClass}}">{{#blockHeader}}{{{blockHeader}}}{{/blockHeader}}{{^blockHeader}} {{/blockHeader}}</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
<tr>
|
||||
<td class="{{CSSLineClass.INFO}}">
|
||||
<div class="{{contentClass}}">
|
||||
File without changes
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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"> </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>
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
<div class="d2h-wrapper {{colorScheme}}">
|
||||
{{{content}}}
|
||||
</div>
|
||||
|
|
@ -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 |
|
|
@ -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 |
|
|
@ -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 |
|
|
@ -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 |
|
|
@ -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 |
|
|
@ -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>
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
<div class="line-num1">{{oldNumber}}</div>
|
||||
<div class="line-num2">{{newNumber}}</div>
|
||||
|
|
@ -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>
|
||||
|
|
@ -1 +0,0 @@
|
|||
<span class="d2h-tag d2h-added d2h-added-tag">ADDED</span>
|
||||
|
|
@ -1 +0,0 @@
|
|||
<span class="d2h-tag d2h-changed d2h-changed-tag">CHANGED</span>
|
||||
|
|
@ -1 +0,0 @@
|
|||
<span class="d2h-tag d2h-deleted d2h-deleted-tag">DELETED</span>
|
||||
|
|
@ -1 +0,0 @@
|
|||
<span class="d2h-tag d2h-moved d2h-moved-tag">RENAMED</span>
|
||||
99
src/types.ts
99
src/types.ts
|
|
@ -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',
|
||||
}
|
||||
|
|
@ -5,225 +5,118 @@
|
|||
*
|
||||
*/
|
||||
|
||||
: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 {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
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: 1px solid #ddd;
|
||||
border-radius: 3px;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.d2h-file-collapse {
|
||||
justify-content: flex-end;
|
||||
display: none;
|
||||
cursor: pointer;
|
||||
.d2h-file-header {
|
||||
padding: 5px 10px;
|
||||
border-bottom: 1px solid #d8d8d8;
|
||||
background-color: #f7f7f7;
|
||||
font: 13px Helvetica, arial, freesans, clean, sans-serif, "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
}
|
||||
|
||||
.d2h-file-stats {
|
||||
display: inline;
|
||||
font-size: 12px;
|
||||
align-items: center;
|
||||
border-radius: 3px;
|
||||
border: 1px solid var(--d2h-border-color);
|
||||
padding: 4px 8px;
|
||||
text-align: center;
|
||||
max-width: 15%;
|
||||
}
|
||||
|
||||
.d2h-file-collapse.d2h-selected {
|
||||
background-color: var(--d2h-selected-color);
|
||||
.d2h-lines-added {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.d2h-file-collapse-input {
|
||||
margin: 0 4px 0 0;
|
||||
.d2h-lines-added > * {
|
||||
background-color: #ceffce;
|
||||
border: 1px solid #b4e2b4;
|
||||
color: #399839;
|
||||
border-radius: 5px 0 0 5px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.d2h-lines-deleted {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.d2h-lines-deleted > * {
|
||||
background-color: #f7c8c8;
|
||||
border: 1px solid #e9aeae;
|
||||
color: #c33;
|
||||
border-radius: 0 5px 5px 0;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.d2h-file-name {
|
||||
display: inline;
|
||||
line-height: 33px;
|
||||
max-width: 80%;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.d2h-diff-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-family: 'Menlo', 'Consolas', monospace;
|
||||
font-size: 13px;
|
||||
font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
|
||||
font-size: 12px;
|
||||
height: 18px;
|
||||
line-height: 18px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.d2h-files-diff {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.d2h-file-diff {
|
||||
overflow-x: scroll;
|
||||
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%;
|
||||
margin-right: -4px;
|
||||
}
|
||||
|
||||
.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;
|
||||
display: block;
|
||||
white-space: pre;
|
||||
padding: 0 10px;
|
||||
height: 18px;
|
||||
line-height: 18px;
|
||||
margin-left: 80px;
|
||||
/* Override HighlightJS */
|
||||
color: inherit;
|
||||
overflow-x: inherit;
|
||||
background: none;
|
||||
/* ******************** */
|
||||
}
|
||||
|
||||
.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;
|
||||
display: block;
|
||||
white-space: pre;
|
||||
user-select: text;
|
||||
width: 100%;
|
||||
vertical-align: middle;
|
||||
padding: 0 10px;
|
||||
height: 18px;
|
||||
line-height: 18px;
|
||||
margin-left: 50px;
|
||||
/* Override HighlightJS */
|
||||
color: inherit;
|
||||
overflow-x: inherit;
|
||||
background: none;
|
||||
/* ******************** */
|
||||
}
|
||||
|
||||
.d2h-code-line del,
|
||||
|
|
@ -231,7 +124,7 @@
|
|||
display: inline-block;
|
||||
margin-top: -1px;
|
||||
text-decoration: none;
|
||||
background-color: var(--d2h-del-highlight-bg-color);
|
||||
background-color: #ffb6ba;
|
||||
border-radius: 0.2em;
|
||||
}
|
||||
|
||||
|
|
@ -240,502 +133,146 @@
|
|||
display: inline-block;
|
||||
margin-top: -1px;
|
||||
text-decoration: none;
|
||||
background-color: var(--d2h-ins-highlight-bg-color);
|
||||
background-color: #97f295;
|
||||
border-radius: 0.2em;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.d2h-code-line-prefix {
|
||||
display: inline;
|
||||
float: left;
|
||||
background: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.d2h-code-line-ctn {
|
||||
background: none;
|
||||
padding: 0;
|
||||
word-wrap: normal;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.line-num1 {
|
||||
box-sizing: border-box;
|
||||
float: left;
|
||||
width: 3.5em;
|
||||
width: 32px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
padding: 0 0.5em 0 0.5em;
|
||||
padding-left: 3px;
|
||||
}
|
||||
|
||||
.line-num2 {
|
||||
box-sizing: border-box;
|
||||
float: right;
|
||||
width: 3.5em;
|
||||
width: 32px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
padding: 0 0.5em 0 0.5em;
|
||||
padding-left: 3px;
|
||||
}
|
||||
|
||||
.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);
|
||||
width: 82px;
|
||||
height: 18px;
|
||||
padding-left: 2px;
|
||||
padding-right: 2px;
|
||||
line-height: 18px;
|
||||
background-color: #fff;
|
||||
color: rgba(0, 0, 0, 0.3);
|
||||
text-align: right;
|
||||
border: solid var(--d2h-line-border-color);
|
||||
border: solid #eeeeee;
|
||||
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);
|
||||
position: absolute;
|
||||
width: 52px;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
height: 18px;
|
||||
line-height: 18px;
|
||||
background-color: #fff;
|
||||
color: rgba(0, 0, 0, 0.3);
|
||||
text-align: right;
|
||||
border: solid var(--d2h-line-border-color);
|
||||
border: solid #eeeeee;
|
||||
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);
|
||||
background-color: #fee8e9;
|
||||
border-color: #e9aeae;
|
||||
}
|
||||
|
||||
.d2h-ins {
|
||||
background-color: var(--d2h-ins-bg-color);
|
||||
border-color: var(--d2h-ins-border-color);
|
||||
background-color: #dfd;
|
||||
border-color: #b4e2b4;
|
||||
}
|
||||
|
||||
.d2h-info {
|
||||
background-color: var(--d2h-info-bg-color);
|
||||
color: var(--d2h-dim-color);
|
||||
border-color: var(--d2h-info-border-color);
|
||||
background-color: #f8fafd;
|
||||
color: rgba(0, 0, 0, 0.3);
|
||||
border-color: #d5e4f2;
|
||||
}
|
||||
|
||||
.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;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.d2h-file-list-wrapper a {
|
||||
text-decoration: none;
|
||||
color: var(--d2h-moved-label-color);
|
||||
color: #3572b0;
|
||||
}
|
||||
|
||||
.d2h-file-list-wrapper a:visited {
|
||||
color: var(--d2h-moved-label-color);
|
||||
color: #3572b0;
|
||||
}
|
||||
|
||||
.d2h-file-list-header {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.d2h-file-list-title {
|
||||
font-weight: bold;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.d2h-file-list-line {
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
text-align: left;
|
||||
font: 13px Helvetica, arial, freesans, clean, sans-serif, "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
}
|
||||
|
||||
.d2h-file-list-line .d2h-file-name {
|
||||
line-height: 21px;
|
||||
}
|
||||
|
||||
.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-clear {
|
||||
display: block;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.d2h-file-list > li:last-child {
|
||||
border-bottom: none;
|
||||
.d2h-del.d2h-change, .d2h-ins.d2h-change {
|
||||
background-color: #ffc;
|
||||
}
|
||||
|
||||
ins.d2h-change, del.d2h-change {
|
||||
background-color: #fad771;
|
||||
}
|
||||
|
||||
.d2h-file-diff .d2h-del.d2h-change {
|
||||
background-color: #fae1af;
|
||||
}
|
||||
|
||||
.d2h-file-diff .d2h-ins.d2h-change {
|
||||
background-color: #ded;
|
||||
}
|
||||
|
||||
.d2h-file-switch {
|
||||
display: none;
|
||||
float: left;
|
||||
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;
|
||||
}
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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 };
|
||||
99
src/ui/js/diff2html-ui.js
Normal file
99
src/ui/js/diff2html-ui.js
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
*
|
||||
* Diff to HTML (diff2html-ui.js)
|
||||
* Author: rtfpessoa
|
||||
*
|
||||
* Depends on: [ jQuery ]
|
||||
* Optional dependencies on: [ highlight.js ]
|
||||
*
|
||||
*/
|
||||
|
||||
/*global $, hljs*/
|
||||
|
||||
(function() {
|
||||
|
||||
var diffJson = null;
|
||||
var defaultTarget = "body";
|
||||
|
||||
function Diff2HtmlUI(config) {
|
||||
var cfg = config || {};
|
||||
|
||||
if (cfg.diff) {
|
||||
diffJson = Diff2Html.getJsonFromDiff(cfg.diff);
|
||||
} else if (cfg.json) {
|
||||
diffJson = cfg.json;
|
||||
}
|
||||
}
|
||||
|
||||
Diff2HtmlUI.prototype.draw = function(targetId, config) {
|
||||
var cfg = config || {};
|
||||
var $target = this._getTarget(targetId);
|
||||
$target.html(Diff2Html.getPrettyHtml(diffJson, cfg));
|
||||
};
|
||||
|
||||
Diff2HtmlUI.prototype.fileListCloseable = function(targetId, startVisible) {
|
||||
var $target = this._getTarget(targetId);
|
||||
|
||||
var $showBtn = $target.find(".d2h-show");
|
||||
var $hideBtn = $target.find(".d2h-hide");
|
||||
var $fileList = $target.find(".d2h-file-list");
|
||||
|
||||
if (startVisible) show(); else hide();
|
||||
|
||||
$showBtn.click(show);
|
||||
$hideBtn.click(hide);
|
||||
|
||||
function show() {
|
||||
$showBtn.hide();
|
||||
$hideBtn.show();
|
||||
$fileList.show();
|
||||
}
|
||||
|
||||
function hide() {
|
||||
$hideBtn.hide();
|
||||
$showBtn.show();
|
||||
$fileList.hide();
|
||||
}
|
||||
};
|
||||
|
||||
Diff2HtmlUI.prototype.highlightCode = function(targetId) {
|
||||
var that = this;
|
||||
|
||||
// collect all the file extensions in the json
|
||||
var allFileLanguages = diffJson.map(function(line) {
|
||||
return line.language;
|
||||
});
|
||||
|
||||
// remove duplicated languages
|
||||
var distinctLanguages = allFileLanguages.filter(function(v, i) {
|
||||
return allFileLanguages.indexOf(v) === i;
|
||||
});
|
||||
|
||||
// pass the languages to the highlightjs plugin
|
||||
hljs.configure({languages: distinctLanguages});
|
||||
|
||||
// collect all the code lines and execute the highlight on them
|
||||
var $target = that._getTarget(targetId);
|
||||
var $codeLines = $target.find(".d2h-code-line-ctn");
|
||||
$codeLines.map(function(i, line) {
|
||||
hljs.highlightBlock(line);
|
||||
});
|
||||
};
|
||||
|
||||
Diff2HtmlUI.prototype._getTarget = function(targetId) {
|
||||
var $target;
|
||||
if (targetId) {
|
||||
$target = $(targetId);
|
||||
} else {
|
||||
$target = $(defaultTarget);
|
||||
}
|
||||
|
||||
return $target;
|
||||
};
|
||||
|
||||
module.exports.Diff2HtmlUI = Diff2HtmlUI;
|
||||
|
||||
// Expose diff2html in the browser
|
||||
global.Diff2HtmlUI = Diff2HtmlUI;
|
||||
|
||||
})();
|
||||
|
|
@ -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 };
|
||||
|
|
@ -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, '&').replace(/</gm, '<').replace(/>/gm, '>');
|
||||
}
|
||||
|
||||
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, '"')}"`)
|
||||
.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';
|
||||
}
|
||||
|
|
@ -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;
|
||||
42
src/utils.js
Normal file
42
src/utils.js
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
*
|
||||
* Utils (utils.js)
|
||||
* Author: rtfpessoa
|
||||
*
|
||||
*/
|
||||
|
||||
(function() {
|
||||
|
||||
function Utils() {
|
||||
}
|
||||
|
||||
Utils.prototype.escape = function(str) {
|
||||
return str.slice(0)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/\t/g, ' ');
|
||||
};
|
||||
|
||||
Utils.prototype.startsWith = function(str, start) {
|
||||
if (typeof start === 'object') {
|
||||
var result = false;
|
||||
start.forEach(function(s) {
|
||||
if (str.indexOf(s) === 0) {
|
||||
result = true;
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return str.indexOf(start) === 0;
|
||||
};
|
||||
|
||||
Utils.prototype.valueOrEmpty = function(value) {
|
||||
return value ? value : '';
|
||||
};
|
||||
|
||||
module.exports.Utils = new Utils();
|
||||
|
||||
})();
|
||||
54
src/utils.ts
54
src/utils.ts
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
output "route53_domain" {
|
||||
value = "${aws_route53_record.root_domain.fqdn}"
|
||||
}
|
||||
|
||||
output "cdn_domain" {
|
||||
value = "${aws_cloudfront_distribution.cdn.domain_name}"
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
68
test/diff-parser-tests.js
Normal file
68
test/diff-parser-tests.js
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
var assert = require('assert');
|
||||
|
||||
var DiffParser = require('../src/diff-parser.js').DiffParser;
|
||||
|
||||
describe('DiffParser', function() {
|
||||
describe('generateDiffJson', function() {
|
||||
|
||||
it('should parse unix with \n diff', function() {
|
||||
var diff =
|
||||
'diff --git a/sample b/sample\n' +
|
||||
'index 0000001..0ddf2ba\n' +
|
||||
'--- a/sample\n' +
|
||||
'+++ b/sample\n' +
|
||||
'@@ -1 +1 @@\n' +
|
||||
'-test\n' +
|
||||
'+test1r\n';
|
||||
checkDiffSample(diff)
|
||||
});
|
||||
|
||||
it('should parse windows with \r\n diff', function() {
|
||||
var diff =
|
||||
'diff --git a/sample b/sample\r\n' +
|
||||
'index 0000001..0ddf2ba\r\n' +
|
||||
'--- a/sample\r\n' +
|
||||
'+++ b/sample\r\n' +
|
||||
'@@ -1 +1 @@\r\n' +
|
||||
'-test\r\n' +
|
||||
'+test1r\r\n';
|
||||
checkDiffSample(diff)
|
||||
});
|
||||
|
||||
it('should parse old os x with \r diff', function() {
|
||||
var diff =
|
||||
'diff --git a/sample b/sample\r' +
|
||||
'index 0000001..0ddf2ba\r' +
|
||||
'--- a/sample\r' +
|
||||
'+++ b/sample\r' +
|
||||
'@@ -1 +1 @@\r' +
|
||||
'-test\r' +
|
||||
'+test1r\r';
|
||||
checkDiffSample(diff)
|
||||
});
|
||||
|
||||
it('should parse mixed eols diff', function() {
|
||||
var diff =
|
||||
'diff --git a/sample b/sample\n' +
|
||||
'index 0000001..0ddf2ba\r\n' +
|
||||
'--- a/sample\r' +
|
||||
'+++ b/sample\r\n' +
|
||||
'@@ -1 +1 @@\n' +
|
||||
'-test\r' +
|
||||
'+test1r\n';
|
||||
checkDiffSample(diff)
|
||||
});
|
||||
|
||||
function checkDiffSample(diff) {
|
||||
var result = Diff2Html.getJsonFromDiff(diff);
|
||||
var file1 = result[0];
|
||||
assert.equal(1, result.length);
|
||||
assert.equal(1, file1.addedLines);
|
||||
assert.equal(1, file1.deletedLines);
|
||||
assert.equal('sample', file1.oldName);
|
||||
assert.equal('sample', file1.newName);
|
||||
assert.equal(1, file1.blocks.length);
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
75
test/diff2html-tests.js
Normal file
75
test/diff2html-tests.js
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
var assert = require('assert');
|
||||
|
||||
var Diff2Html = require('../src/diff2html.js').Diff2Html;
|
||||
|
||||
describe('Diff2Html', function() {
|
||||
describe('getJsonFromDiff', function() {
|
||||
it('should parse simple diff to json', function() {
|
||||
var diff =
|
||||
'diff --git a/sample b/sample\n' +
|
||||
'index 0000001..0ddf2ba\n' +
|
||||
'--- a/sample\n' +
|
||||
'+++ b/sample\n' +
|
||||
'@@ -1 +1 @@\n' +
|
||||
'-test\n' +
|
||||
'+test1\n';
|
||||
var result = Diff2Html.getJsonFromDiff(diff);
|
||||
var file1 = result[0];
|
||||
assert.equal(1, result.length);
|
||||
assert.equal(1, file1.addedLines);
|
||||
assert.equal(1, file1.deletedLines);
|
||||
assert.equal('sample', file1.oldName);
|
||||
assert.equal('sample', file1.newName);
|
||||
assert.equal(1, file1.blocks.length);
|
||||
});
|
||||
|
||||
// Test case for issue #49
|
||||
it('should parse diff with added EOF', function() {
|
||||
var diff =
|
||||
'diff --git a/sample.scala b/sample.scala\n' +
|
||||
'index b583263..8b2fc3e 100644\n' +
|
||||
'--- a/b583263..8b2fc3e\n' +
|
||||
'+++ b/8b2fc3e\n' +
|
||||
'@@ -50,5 +50,7 @@ case class Response[+A](value: Option[A],\n' +
|
||||
' object ResponseErrorCode extends JsonEnumeration {\n' +
|
||||
' val NoError, ServiceError, JsonError,\n' +
|
||||
' InvalidPermissions, MissingPermissions, GenericError,\n' +
|
||||
'- TokenRevoked, MissingToken = Value\n' +
|
||||
'-}\n' +
|
||||
'\\ No newline at end of file\n' +
|
||||
'+ TokenRevoked, MissingToken,\n' +
|
||||
'+ IndexLock, RepositoryError, NotValidRepo, PullRequestNotMergeable, BranchError,\n' +
|
||||
'+ PluginError, CodeParserError, EngineError = Value\n' +
|
||||
'+}\n';
|
||||
var result = Diff2Html.getJsonFromDiff(diff);
|
||||
|
||||
assert.equal(50, result[0].blocks[0].lines[0].oldNumber);
|
||||
assert.equal(50, result[0].blocks[0].lines[0].newNumber);
|
||||
|
||||
assert.equal(51, result[0].blocks[0].lines[1].oldNumber);
|
||||
assert.equal(51, result[0].blocks[0].lines[1].newNumber);
|
||||
|
||||
assert.equal(52, result[0].blocks[0].lines[2].oldNumber);
|
||||
assert.equal(52, result[0].blocks[0].lines[2].newNumber);
|
||||
|
||||
assert.equal(53, result[0].blocks[0].lines[3].oldNumber);
|
||||
assert.equal(null, result[0].blocks[0].lines[3].newNumber);
|
||||
|
||||
assert.equal(54, result[0].blocks[0].lines[4].oldNumber);
|
||||
assert.equal(null, result[0].blocks[0].lines[4].newNumber);
|
||||
|
||||
assert.equal(null, result[0].blocks[0].lines[5].oldNumber);
|
||||
assert.equal(53, result[0].blocks[0].lines[5].newNumber);
|
||||
|
||||
assert.equal(null, result[0].blocks[0].lines[6].oldNumber);
|
||||
assert.equal(54, result[0].blocks[0].lines[6].newNumber);
|
||||
|
||||
|
||||
assert.equal(null, result[0].blocks[0].lines[7].oldNumber);
|
||||
assert.equal(55, result[0].blocks[0].lines[7].newNumber);
|
||||
|
||||
assert.equal(null, result[0].blocks[0].lines[8].oldNumber);
|
||||
assert.equal(56, result[0].blocks[0].lines[8].newNumber);
|
||||
});
|
||||
});
|
||||
});
|
||||
64
test/line-by-line-tests.js
Normal file
64
test/line-by-line-tests.js
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
var assert = require('assert');
|
||||
|
||||
var LineByLinePrinter = require('../src/line-by-line-printer.js').LineByLinePrinter;
|
||||
|
||||
describe('LineByLinePrinter', function() {
|
||||
describe('_generateEmptyDiff', function() {
|
||||
it('should return an empty diff', function() {
|
||||
|
||||
var lineByLinePrinter = new LineByLinePrinter({});
|
||||
var fileHtml = lineByLinePrinter._generateEmptyDiff();
|
||||
var expected = '<tr>\n' +
|
||||
' <td class="d2h-info">' +
|
||||
' <div class="d2h-code-line d2h-info">' +
|
||||
'File without changes' +
|
||||
' </div>' +
|
||||
' </td>\n' +
|
||||
'</tr>\n';
|
||||
|
||||
assert.equal(expected, fileHtml);
|
||||
});
|
||||
});
|
||||
describe('_generateLineHtml', function() {
|
||||
it('should work for insertions', function() {
|
||||
|
||||
var diffParser = require('../src/diff-parser.js').DiffParser;
|
||||
var lineByLinePrinter = new LineByLinePrinter({});
|
||||
var fileHtml = lineByLinePrinter._generateLineHtml(
|
||||
diffParser.LINE_TYPE.INSERTS, '', 30, '+', 'test');
|
||||
var expected = '<tr>\n' +
|
||||
' <td class="d2h-code-linenumber d2h-ins">' +
|
||||
' <div class="line-num1"></div>' +
|
||||
' <div class="line-num2">30</div>' +
|
||||
' </td>\n' +
|
||||
' <td class="d2h-ins">' +
|
||||
' <div class="d2h-code-line d2h-ins">' +
|
||||
'<span class="d2h-code-line-prefix">test</span>' +
|
||||
'<span class="d2h-code-line-ctn">+</span></div>' +
|
||||
' </td>\n' +
|
||||
'</tr>\n';
|
||||
|
||||
assert.equal(expected, fileHtml);
|
||||
});
|
||||
it('should work for deletions', function() {
|
||||
|
||||
var diffParser = require('../src/diff-parser.js').DiffParser;
|
||||
var lineByLinePrinter = new LineByLinePrinter({});
|
||||
var fileHtml = lineByLinePrinter._generateLineHtml(
|
||||
diffParser.LINE_TYPE.DELETES, 30, '', '-', 'test');
|
||||
var expected = '<tr>\n' +
|
||||
' <td class="d2h-code-linenumber d2h-del">' +
|
||||
' <div class="line-num1">30</div>' +
|
||||
' <div class="line-num2"></div>' +
|
||||
' </td>\n' +
|
||||
' <td class="d2h-del">' +
|
||||
' <div class="d2h-code-line d2h-del">' +
|
||||
'<span class="d2h-code-line-prefix">test</span>' +
|
||||
'<span class="d2h-code-line-ctn">-</span></div>' +
|
||||
' </td>\n' +
|
||||
'</tr>\n';
|
||||
|
||||
assert.equal(expected, fileHtml);
|
||||
});
|
||||
});
|
||||
});
|
||||
156
test/side-by-side-printer-tests.js
Normal file
156
test/side-by-side-printer-tests.js
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
var assert = require('assert');
|
||||
|
||||
var SideBySidePrinter = require('../src/side-by-side-printer.js').SideBySidePrinter;
|
||||
|
||||
describe('SideBySidePrinter', function() {
|
||||
describe('generateEmptyDiff', function() {
|
||||
it('should return an empty diff', function() {
|
||||
|
||||
var sideBySidePrinter = new SideBySidePrinter({});
|
||||
var fileHtml = sideBySidePrinter.generateEmptyDiff();
|
||||
var expectedRight = '';
|
||||
var expectedLeft = '<tr>\n' +
|
||||
' <td class="d2h-info">' +
|
||||
' <div class="d2h-code-side-line d2h-info">' +
|
||||
'File without changes' +
|
||||
' </div>' +
|
||||
' </td>\n' +
|
||||
'</tr>\n';
|
||||
|
||||
assert.equal(expectedRight, fileHtml.right);
|
||||
assert.equal(expectedLeft, fileHtml.left);
|
||||
});
|
||||
});
|
||||
|
||||
describe('generateSideBySideFileHtml', function() {
|
||||
it('should generate lines with the right prefixes', function() {
|
||||
var sideBySidePrinter = new SideBySidePrinter({});
|
||||
|
||||
var file = {
|
||||
"blocks": [{
|
||||
"lines": [{
|
||||
"content": " context",
|
||||
"type": "d2h-cntx",
|
||||
"oldNumber": 19,
|
||||
"newNumber": 19
|
||||
}, {"content": "-removed", "type": "d2h-del", "oldNumber": 20, "newNumber": null}, {
|
||||
"content": "+added",
|
||||
"type": "d2h-ins",
|
||||
"oldNumber": null,
|
||||
"newNumber": 20
|
||||
}], "oldStartLine": "19", "newStartLine": "19", "header": "@@ -19,7 +19,7 @@"
|
||||
}],
|
||||
"deletedLines": 1,
|
||||
"addedLines": 1,
|
||||
"checksumBefore": "fc56817",
|
||||
"checksumAfter": "e8e7e49",
|
||||
"mode": "100644",
|
||||
"oldName": "coverage.init",
|
||||
"language": "init",
|
||||
"newName": "coverage.init",
|
||||
"isCombined": false
|
||||
};
|
||||
|
||||
var fileHtml = sideBySidePrinter.generateSideBySideFileHtml(file);
|
||||
|
||||
var expectedRight = '<tr>' +
|
||||
' <td class="d2h-code-side-linenumber d2h-info"></td>' +
|
||||
' <td class="d2h-info">' +
|
||||
' <div class="d2h-code-side-line d2h-info"></div>' +
|
||||
' </td>' +
|
||||
'</tr>' +
|
||||
'<tr>' +
|
||||
' <td class="d2h-code-side-linenumber d2h-cntx">19</td>' +
|
||||
' <td class="d2h-cntx">' +
|
||||
' <div class="d2h-code-side-line d2h-cntx">' +
|
||||
' <span class="d2h-code-line-prefix"> </span>' +
|
||||
' <span class="d2h-code-line-ctn">context</span>' +
|
||||
' </div>' +
|
||||
' </td>' +
|
||||
'</tr>' +
|
||||
'<tr>' +
|
||||
' <td class="d2h-code-side-linenumber d2h-ins">20</td>' +
|
||||
' <td class="d2h-ins">' +
|
||||
' <div class="d2h-code-side-line d2h-ins">' +
|
||||
' <span class="d2h-code-line-prefix">+</span>' +
|
||||
' <span class="d2h-code-line-ctn"><ins>added</ins></span>' +
|
||||
' </div>' +
|
||||
' </td>' +
|
||||
'</tr>';
|
||||
|
||||
var expectedLeft = '<tr>' +
|
||||
' <td class="d2h-code-side-linenumber d2h-info"></td>' +
|
||||
' <td class="d2h-info">' +
|
||||
' <div class="d2h-code-side-line d2h-info"> @@ -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 d2h-cntx">' +
|
||||
' <span class="d2h-code-line-prefix"> </span>' +
|
||||
' <span class="d2h-code-line-ctn">context</span>' +
|
||||
' </div>' +
|
||||
' </td>' +
|
||||
'</tr>' +
|
||||
'<tr>' +
|
||||
' <td class="d2h-code-side-linenumber d2h-del">20</td>' +
|
||||
' <td class="d2h-del">' +
|
||||
' <div class="d2h-code-side-line d2h-del">' +
|
||||
' <span class="d2h-code-line-prefix">-</span>' +
|
||||
' <span class="d2h-code-line-ctn"><del>removed</del></span>' +
|
||||
' </div>' +
|
||||
' </td>' +
|
||||
'</tr>';
|
||||
|
||||
var HTMLParser = require('fast-html-parser');
|
||||
|
||||
var prefixTag = '.d2h-code-line-prefix';
|
||||
|
||||
var parsedExpectedRight = HTMLParser.parse(expectedRight);
|
||||
var parsedFileRight = HTMLParser.parse(fileHtml.right);
|
||||
assert.equal(parsedExpectedRight.querySelectorAll(prefixTag).length > 0, true);
|
||||
assert.equal(parsedExpectedRight.querySelectorAll(prefixTag).length,
|
||||
parsedFileRight.querySelectorAll(prefixTag).length);
|
||||
|
||||
var parsedExpectedLeft = HTMLParser.parse(expectedLeft);
|
||||
var parsedFileLeft = HTMLParser.parse(fileHtml.left);
|
||||
assert.equal(parsedExpectedLeft.querySelectorAll(prefixTag).length > 0, true);
|
||||
assert.equal(parsedExpectedLeft.querySelectorAll(prefixTag).length,
|
||||
parsedFileLeft.querySelectorAll(prefixTag).length);
|
||||
});
|
||||
});
|
||||
|
||||
describe('generateSingleLineHtml', function() {
|
||||
it('should work for insertions', function() {
|
||||
|
||||
var diffParser = require('../src/diff-parser.js').DiffParser;
|
||||
var sideBySidePrinter = new SideBySidePrinter({});
|
||||
var fileHtml = sideBySidePrinter.generateSingleLineHtml(
|
||||
diffParser.LINE_TYPE.INSERTS, 30, 'test', '+');
|
||||
var expected = '<tr>\n' +
|
||||
' <td class="d2h-code-side-linenumber d2h-ins">30</td>\n' +
|
||||
' <td class="d2h-ins">' +
|
||||
' <div class="d2h-code-side-line d2h-ins"><span class="d2h-code-line-prefix">+</span><span class="d2h-code-line-ctn">test</span></div>' +
|
||||
' </td>\n' +
|
||||
' </tr>\n';
|
||||
|
||||
assert.equal(expected, fileHtml);
|
||||
});
|
||||
it('should work for deletions', function() {
|
||||
|
||||
var diffParser = require('../src/diff-parser.js').DiffParser;
|
||||
var sideBySidePrinter = new SideBySidePrinter({});
|
||||
var fileHtml = sideBySidePrinter.generateSingleLineHtml(
|
||||
diffParser.LINE_TYPE.DELETES, 30, 'test', '-');
|
||||
var expected = '<tr>\n' +
|
||||
' <td class="d2h-code-side-linenumber d2h-del">30</td>\n' +
|
||||
' <td class="d2h-del">' +
|
||||
' <div class="d2h-code-side-line d2h-del"><span class="d2h-code-line-prefix">-</span><span class="d2h-code-line-ctn">test</span></div>' +
|
||||
' </td>\n' +
|
||||
' </tr>\n';
|
||||
|
||||
assert.equal(expected, fileHtml);
|
||||
});
|
||||
});
|
||||
});
|
||||
25
test/utils-tests.js
Normal file
25
test/utils-tests.js
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
var assert = require('assert');
|
||||
|
||||
var Utils = require('../src/utils.js').Utils;
|
||||
|
||||
describe('Utils', function() {
|
||||
describe('escape', function() {
|
||||
it('should escape & with &', function() {
|
||||
var result = Utils.escape('&');
|
||||
assert.equal('&', result);
|
||||
});
|
||||
it('should escape < with <', function() {
|
||||
var result = Utils.escape('<');
|
||||
assert.equal('<', result);
|
||||
});
|
||||
it('should escape > with >', function() {
|
||||
var result = Utils.escape('>');
|
||||
assert.equal('>', result);
|
||||
});
|
||||
it('should escape a string with multiple problematic characters', function() {
|
||||
var result = Utils.escape('<a href="#">\tlink text</a>');
|
||||
var expected = '<a href="#"> link text</a>';
|
||||
assert.equal(expected, result);
|
||||
});
|
||||
});
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue