- moved gitgraph.js to web_src and made it importable and es6-compatible - created new webpack chunk for gitgraph - enabled CSS loader in webpack - enabled async/await syntax via regenerator-runtime - added script to ensure webpack chunks are loaded correctly - disable terser's comment extraction to prevent .LICENCE files gitgraph.js has many issues: 1. it is incompatible with ES6 because of strict-mode violations 1. it does not export anything 1. it's css has weird styles like for `body` 1. it is not available on npm I fixed points 1-3 in our version so it's now loadable in webpack. We should eventually consider alternatives.tags/v1.11.0-rc1
/public/js/semantic.dropdown.custom.js | /public/js/semantic.dropdown.custom.js | ||||
/web_src/js/vendor/** |
node: true | node: true | ||||
globals: | globals: | ||||
__webpack_public_path__: true | |||||
Clipboard: false | Clipboard: false | ||||
CodeMirror: false | CodeMirror: false | ||||
Dropzone: false | Dropzone: false |
}, | }, | ||||
"devDependencies": { | "devDependencies": { | ||||
"@babel/core": "7.7.2", | "@babel/core": "7.7.2", | ||||
"@babel/plugin-transform-runtime": "7.6.2", | |||||
"@babel/preset-env": "7.7.1", | "@babel/preset-env": "7.7.1", | ||||
"@babel/runtime": "7.7.2", | |||||
"autoprefixer": "9.7.1", | "autoprefixer": "9.7.1", | ||||
"babel-loader": "8.0.6", | "babel-loader": "8.0.6", | ||||
"core-js": "3.4.1", | "core-js": "3.4.1", | ||||
"css-loader": "3.2.0", | |||||
"eslint": "6.6.0", | "eslint": "6.6.0", | ||||
"eslint-config-airbnb-base": "14.0.0", | "eslint-config-airbnb-base": "14.0.0", | ||||
"eslint-plugin-import": "2.18.2", | "eslint-plugin-import": "2.18.2", | ||||
"less": "3.10.3", | "less": "3.10.3", | ||||
"less-plugin-clean-css": "1.5.1", | "less-plugin-clean-css": "1.5.1", | ||||
"postcss-cli": "6.1.3", | "postcss-cli": "6.1.3", | ||||
"style-loader": "1.0.0", | |||||
"stylelint": "11.1.1", | "stylelint": "11.1.1", | ||||
"stylelint-config-standard": "19.0.0", | "stylelint-config-standard": "19.0.0", | ||||
"terser-webpack-plugin": "2.2.1", | "terser-webpack-plugin": "2.2.1", |
File(s): /vendor/plugins/clipboard/clipboard.min.js | File(s): /vendor/plugins/clipboard/clipboard.min.js | ||||
Version: 1.5.9 | Version: 1.5.9 | ||||
File(s): /vendor/plugins/gitgraph/gitgraph.js | |||||
Version: 745f604212e2abfe2f0a59169ea530857b46625c | |||||
File(s): /vendor/plugins/vue/vue.min.js | File(s): /vendor/plugins/vue/vue.min.js | ||||
Version: 2.1.10 | Version: 2.1.10 | ||||
<td><a href="https://github.com/zenorocha/clipboard.js/archive/v1.5.9.tar.gz">clipboard-1.5.9.tar.gz</a></td> | <td><a href="https://github.com/zenorocha/clipboard.js/archive/v1.5.9.tar.gz">clipboard-1.5.9.tar.gz</a></td> | ||||
</tr> | </tr> | ||||
<tr> | <tr> | ||||
<td><a href="./plugins/gitgraph/gitgraph.js">gitgraph.js</a></td> | |||||
<td><a href="../js/gitgraph.js">gitgraph.js</a></td> | |||||
<td><a href="https://github.com/bluef/gitgraph.js/blob/master/LICENSE">BSD 3-Clause</a></td> | <td><a href="https://github.com/bluef/gitgraph.js/blob/master/LICENSE">BSD 3-Clause</a></td> | ||||
<td><a href="https://github.com/bluef/gitgraph.js">gitgraph.js-latest</a></td> | <td><a href="https://github.com/bluef/gitgraph.js">gitgraph.js-latest</a></td> | ||||
</tr> | </tr> |
/* | |||||
* @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD 3-Clause | |||||
* Copyright (c) 2011, Terrence Lee <kill889@gmail.com> | |||||
* All rights reserved. | |||||
* | |||||
* Redistribution and use in source and binary forms, with or without | |||||
* modification, are permitted provided that the following conditions are met: | |||||
* * Redistributions of source code must retain the above copyright | |||||
* notice, this list of conditions and the following disclaimer. | |||||
* * Redistributions in binary form must reproduce the above copyright | |||||
* notice, this list of conditions and the following disclaimer in the | |||||
* documentation and/or other materials provided with the distribution. | |||||
* * Neither the name of the <organization> nor the | |||||
* names of its contributors may be used to endorse or promote products | |||||
* derived from this software without specific prior written permission. | |||||
* | |||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | |||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |||||
* DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY | |||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | |||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | |||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | |||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||||
*/ | |||||
var gitGraph = function (canvas, rawGraphList, config) { | |||||
if (!canvas.getContext) { | |||||
return; | |||||
} | |||||
if (typeof config === "undefined") { | |||||
config = { | |||||
unitSize: 20, | |||||
lineWidth: 3, | |||||
nodeRadius: 4 | |||||
}; | |||||
} | |||||
var flows = []; | |||||
var graphList = []; | |||||
var ctx = canvas.getContext("2d"); | |||||
var devicePixelRatio = window.devicePixelRatio || 1; | |||||
var backingStoreRatio = ctx.webkitBackingStorePixelRatio || | |||||
ctx.mozBackingStorePixelRatio || | |||||
ctx.msBackingStorePixelRatio || | |||||
ctx.oBackingStorePixelRatio || | |||||
ctx.backingStorePixelRatio || 1; | |||||
var ratio = devicePixelRatio / backingStoreRatio; | |||||
var init = function () { | |||||
var maxWidth = 0; | |||||
var i; | |||||
var l = rawGraphList.length; | |||||
var row; | |||||
var midStr; | |||||
for (i = 0; i < l; i++) { | |||||
midStr = rawGraphList[i].replace(/\s+/g, " ").replace(/^\s+|\s+$/g, ""); | |||||
maxWidth = Math.max(midStr.replace(/(\_|\s)/g, "").length, maxWidth); | |||||
row = midStr.split(""); | |||||
graphList.unshift(row); | |||||
} | |||||
var width = maxWidth * config.unitSize; | |||||
var height = graphList.length * config.unitSize; | |||||
canvas.width = width * ratio; | |||||
canvas.height = height * ratio; | |||||
canvas.style.width = width + 'px'; | |||||
canvas.style.height = height + 'px'; | |||||
ctx.lineWidth = config.lineWidth; | |||||
ctx.lineJoin = "round"; | |||||
ctx.lineCap = "round"; | |||||
ctx.scale(ratio, ratio); | |||||
}; | |||||
var genRandomStr = function () { | |||||
var chars = "0123456789ABCDEF"; | |||||
var stringLength = 6; | |||||
var randomString = '', rnum, i; | |||||
for (i = 0; i < stringLength; i++) { | |||||
rnum = Math.floor(Math.random() * chars.length); | |||||
randomString += chars.substring(rnum, rnum + 1); | |||||
} | |||||
return randomString; | |||||
}; | |||||
var findFlow = function (id) { | |||||
var i = flows.length; | |||||
while (i-- && flows[i].id !== id) {} | |||||
return i; | |||||
}; | |||||
var findColomn = function (symbol, row) { | |||||
var i = row.length; | |||||
while (i-- && row[i] !== symbol) {} | |||||
return i; | |||||
}; | |||||
var findBranchOut = function (row) { | |||||
if (!row) { | |||||
return -1 | |||||
} | |||||
var i = row.length; | |||||
while (i-- && | |||||
!(row[i - 1] && row[i] === "/" && row[i - 1] === "|") && | |||||
!(row[i - 2] && row[i] === "_" && row[i - 2] === "|")) {} | |||||
return i; | |||||
}; | |||||
var findLineBreak = function (row) { | |||||
if (!row) { | |||||
return -1 | |||||
} | |||||
var i = row.length; | |||||
while (i-- && | |||||
!(row[i - 1] && row[i - 2] && row[i] === " " && row[i - 1] === "|" && row[i - 2] === "_")) {} | |||||
return i; | |||||
}; | |||||
var genNewFlow = function () { | |||||
var newId; | |||||
do { | |||||
newId = genRandomStr(); | |||||
} while (findFlow(newId) !== -1); | |||||
return {id:newId, color:"#" + newId}; | |||||
}; | |||||
//Draw methods | |||||
var drawLine = function (moveX, moveY, lineX, lineY, color) { | |||||
ctx.strokeStyle = color; | |||||
ctx.beginPath(); | |||||
ctx.moveTo(moveX, moveY); | |||||
ctx.lineTo(lineX, lineY); | |||||
ctx.stroke(); | |||||
}; | |||||
var drawLineRight = function (x, y, color) { | |||||
drawLine(x, y + config.unitSize / 2, x + config.unitSize, y + config.unitSize / 2, color); | |||||
}; | |||||
var drawLineUp = function (x, y, color) { | |||||
drawLine(x, y + config.unitSize / 2, x, y - config.unitSize / 2, color); | |||||
}; | |||||
var drawNode = function (x, y, color) { | |||||
ctx.strokeStyle = color; | |||||
drawLineUp(x, y, color); | |||||
ctx.beginPath(); | |||||
ctx.arc(x, y, config.nodeRadius, 0, Math.PI * 2, true); | |||||
ctx.fill(); | |||||
}; | |||||
var drawLineIn = function (x, y, color) { | |||||
drawLine(x + config.unitSize, y + config.unitSize / 2, x, y - config.unitSize / 2, color); | |||||
}; | |||||
var drawLineOut = function (x, y, color) { | |||||
drawLine(x, y + config.unitSize / 2, x + config.unitSize, y - config.unitSize / 2, color); | |||||
}; | |||||
var draw = function (graphList) { | |||||
var colomn, colomnIndex, prevColomn, condenseIndex, breakIndex = -1; | |||||
var x, y; | |||||
var color; | |||||
var nodePos; | |||||
var tempFlow; | |||||
var prevRowLength = 0; | |||||
var flowSwapPos = -1; | |||||
var lastLinePos; | |||||
var i, l; | |||||
var condenseCurrentLength, condensePrevLength = 0, condenseNextLength = 0; | |||||
var inlineIntersect = false; | |||||
//initiate color array for first row | |||||
for (i = 0, l = graphList[0].length; i < l; i++) { | |||||
if (graphList[0][i] !== "_" && graphList[0][i] !== " ") { | |||||
flows.push(genNewFlow()); | |||||
} | |||||
} | |||||
y = (canvas.height / ratio) - config.unitSize * 0.5; | |||||
//iterate | |||||
for (i = 0, l = graphList.length; i < l; i++) { | |||||
x = config.unitSize * 0.5; | |||||
currentRow = graphList[i]; | |||||
nextRow = graphList[i + 1]; | |||||
prevRow = graphList[i - 1]; | |||||
flowSwapPos = -1; | |||||
condenseCurrentLength = currentRow.filter(function (val) { | |||||
return (val !== " " && val !== "_") | |||||
}).length; | |||||
if (nextRow) { | |||||
condenseNextLength = nextRow.filter(function (val) { | |||||
return (val !== " " && val !== "_") | |||||
}).length; | |||||
} else { | |||||
condenseNextLength = 0; | |||||
} | |||||
//pre process begin | |||||
//use last row for analysing | |||||
if (prevRow) { | |||||
if (!inlineIntersect) { | |||||
//intersect might happen | |||||
for (colomnIndex = 0; colomnIndex < prevRowLength; colomnIndex++) { | |||||
if (prevRow[colomnIndex + 1] && | |||||
(prevRow[colomnIndex] === "/" && prevRow[colomnIndex + 1] === "|") || | |||||
((prevRow[colomnIndex] === "_" && prevRow[colomnIndex + 1] === "|") && | |||||
(prevRow[colomnIndex + 2] === "/"))) { | |||||
flowSwapPos = colomnIndex; | |||||
//swap two flow | |||||
tempFlow = {id:flows[flowSwapPos].id, color:flows[flowSwapPos].color}; | |||||
flows[flowSwapPos].id = flows[flowSwapPos + 1].id; | |||||
flows[flowSwapPos].color = flows[flowSwapPos + 1].color; | |||||
flows[flowSwapPos + 1].id = tempFlow.id; | |||||
flows[flowSwapPos + 1].color = tempFlow.color; | |||||
} | |||||
} | |||||
} | |||||
if (condensePrevLength < condenseCurrentLength && | |||||
((nodePos = findColomn("*", currentRow)) !== -1 && | |||||
(findColomn("_", currentRow) === -1))) { | |||||
flows.splice(nodePos - 1, 0, genNewFlow()); | |||||
} | |||||
if (prevRowLength > currentRow.length && | |||||
(nodePos = findColomn("*", prevRow)) !== -1) { | |||||
if (findColomn("_", currentRow) === -1 && | |||||
findColomn("/", currentRow) === -1 && | |||||
findColomn("\\", currentRow) === -1) { | |||||
flows.splice(nodePos + 1, 1); | |||||
} | |||||
} | |||||
} //done with the previous row | |||||
prevRowLength = currentRow.length; //store for next round | |||||
colomnIndex = 0; //reset index | |||||
condenseIndex = 0; | |||||
condensePrevLength = 0; | |||||
breakIndex = -1; //reset break index | |||||
while (colomnIndex < currentRow.length) { | |||||
colomn = currentRow[colomnIndex]; | |||||
if (colomn !== " " && colomn !== "_") { | |||||
++condensePrevLength; | |||||
} | |||||
//check and fix line break in next row | |||||
if (colomn === "/" && currentRow[colomnIndex - 1] && currentRow[colomnIndex - 1] === "|") { | |||||
if ((breakIndex = findLineBreak(nextRow)) !== -1) { | |||||
nextRow.splice(breakIndex, 1); | |||||
} | |||||
} | |||||
//if line break found replace all '/' with '|' after breakIndex in previous row | |||||
if (breakIndex !== - 1 && colomn === "/" && colomnIndex > breakIndex) { | |||||
currentRow[colomnIndex] = "|"; | |||||
colomn = "|"; | |||||
} | |||||
if (colomn === " " && | |||||
currentRow[colomnIndex + 1] && | |||||
currentRow[colomnIndex + 1] === "_" && | |||||
currentRow[colomnIndex - 1] && | |||||
currentRow[colomnIndex - 1] === "|") { | |||||
currentRow.splice(colomnIndex, 1); | |||||
currentRow[colomnIndex] = "/"; | |||||
colomn = "/"; | |||||
} | |||||
//create new flow only when no intersect happened | |||||
if (flowSwapPos === -1 && | |||||
colomn === "/" && | |||||
currentRow[colomnIndex - 1] && | |||||
currentRow[colomnIndex - 1] === "|") { | |||||
flows.splice(condenseIndex, 0, genNewFlow()); | |||||
} | |||||
//change \ and / to | when it's in the last position of the whole row | |||||
if (colomn === "/" || colomn === "\\") { | |||||
if (!(colomn === "/" && findBranchOut(nextRow) === -1)) { | |||||
if ((lastLinePos = Math.max(findColomn("|", currentRow), | |||||
findColomn("*", currentRow))) !== -1 && | |||||
(lastLinePos < colomnIndex - 1)) { | |||||
while (currentRow[++lastLinePos] === " ") {} | |||||
if (lastLinePos === colomnIndex) { | |||||
currentRow[colomnIndex] = "|"; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
if (colomn === "*" && | |||||
prevRow && | |||||
prevRow[condenseIndex + 1] === "\\") { | |||||
flows.splice(condenseIndex + 1, 1); | |||||
} | |||||
if (colomn !== " ") { | |||||
++condenseIndex; | |||||
} | |||||
++colomnIndex; | |||||
} | |||||
condenseCurrentLength = currentRow.filter(function (val) { | |||||
return (val !== " " && val !== "_") | |||||
}).length; | |||||
//do some clean up | |||||
if (flows.length > condenseCurrentLength) { | |||||
flows.splice(condenseCurrentLength, flows.length - condenseCurrentLength); | |||||
} | |||||
colomnIndex = 0; | |||||
//a little inline analysis and draw process | |||||
while (colomnIndex < currentRow.length) { | |||||
colomn = currentRow[colomnIndex]; | |||||
prevColomn = currentRow[colomnIndex - 1]; | |||||
if (currentRow[colomnIndex] === " ") { | |||||
currentRow.splice(colomnIndex, 1); | |||||
x += config.unitSize; | |||||
continue; | |||||
} | |||||
//inline interset | |||||
if ((colomn === "_" || colomn === "/") && | |||||
currentRow[colomnIndex - 1] === "|" && | |||||
currentRow[colomnIndex - 2] === "_") { | |||||
inlineIntersect = true; | |||||
tempFlow = flows.splice(colomnIndex - 2, 1)[0]; | |||||
flows.splice(colomnIndex - 1, 0, tempFlow); | |||||
currentRow.splice(colomnIndex - 2, 1); | |||||
colomnIndex = colomnIndex - 1; | |||||
} else { | |||||
inlineIntersect = false; | |||||
} | |||||
color = flows[colomnIndex].color; | |||||
switch (colomn) { | |||||
case "_" : | |||||
drawLineRight(x, y, color); | |||||
x += config.unitSize; | |||||
break; | |||||
case "*" : | |||||
drawNode(x, y, color); | |||||
break; | |||||
case "|" : | |||||
drawLineUp(x, y, color); | |||||
break; | |||||
case "/" : | |||||
if (prevColomn && | |||||
(prevColomn === "/" || | |||||
prevColomn === " ")) { | |||||
x -= config.unitSize; | |||||
} | |||||
drawLineOut(x, y, color); | |||||
x += config.unitSize; | |||||
break; | |||||
case "\\" : | |||||
drawLineIn(x, y, color); | |||||
break; | |||||
} | |||||
++colomnIndex; | |||||
} | |||||
y -= config.unitSize; | |||||
} | |||||
}; | |||||
init(); | |||||
draw(graphList); | |||||
}; | |||||
// @end-license |
ctx.Data["Reponame"] = ctx.Repo.Repository.Name | ctx.Data["Reponame"] = ctx.Repo.Repository.Name | ||||
ctx.Data["CommitCount"] = commitsCount | ctx.Data["CommitCount"] = commitsCount | ||||
ctx.Data["Branch"] = ctx.Repo.BranchName | ctx.Data["Branch"] = ctx.Repo.BranchName | ||||
ctx.Data["RequireGitGraph"] = true | |||||
ctx.Data["Page"] = context.NewPagination(int(allCommitsCount), setting.UI.GraphMaxCommitNum, page, 5) | ctx.Data["Page"] = context.NewPagination(int(allCommitsCount), setting.UI.GraphMaxCommitNum, page, 5) | ||||
ctx.HTML(200, tplGraph) | ctx.HTML(200, tplGraph) | ||||
} | } |
CodeMirror.modeURL = "{{StaticUrlPrefix}}/vendor/plugins/codemirror/mode/%N/%N.js"; | CodeMirror.modeURL = "{{StaticUrlPrefix}}/vendor/plugins/codemirror/mode/%N/%N.js"; | ||||
</script> | </script> | ||||
{{end}} | {{end}} | ||||
{{if .RequireGitGraph}} | |||||
<!-- graph --> | |||||
<script src="{{StaticUrlPrefix}}/vendor/plugins/gitgraph/gitgraph.js"></script> | |||||
{{end}} | |||||
<!-- Third-party libraries --> | <!-- Third-party libraries --> | ||||
{{if .RequireHighlightJS}} | {{if .RequireHighlightJS}} |
<link rel="stylesheet" href="{{StaticUrlPrefix}}/vendor/plugins/simplemde/simplemde.min.css"> | <link rel="stylesheet" href="{{StaticUrlPrefix}}/vendor/plugins/simplemde/simplemde.min.css"> | ||||
{{end}} | {{end}} | ||||
{{if .RequireGitGraph}} | |||||
<!-- graph --> | |||||
<link rel="stylesheet" href="{{StaticUrlPrefix}}/vendor/plugins/gitgraph/gitgraph.css"> | |||||
{{end}} | |||||
{{if .RequireTribute}} | {{if .RequireTribute}} | ||||
<link rel="stylesheet" href="{{StaticUrlPrefix}}/vendor/plugins/tribute/tribute.css"> | <link rel="stylesheet" href="{{StaticUrlPrefix}}/vendor/plugins/tribute/tribute.css"> | ||||
{{end}} | {{end}} |
'{{StaticUrlPrefix}}/vendor/plugins/jquery-migrate/jquery-migrate.min.js?v=3.0.1', | '{{StaticUrlPrefix}}/vendor/plugins/jquery-migrate/jquery-migrate.min.js?v=3.0.1', | ||||
'{{StaticUrlPrefix}}/vendor/plugins/semantic/semantic.min.js', | '{{StaticUrlPrefix}}/vendor/plugins/semantic/semantic.min.js', | ||||
'{{StaticUrlPrefix}}/js/index.js?v={{MD5 AppVer}}', | '{{StaticUrlPrefix}}/js/index.js?v={{MD5 AppVer}}', | ||||
'{{StaticUrlPrefix}}/js/gitgraph.js?v={{MD5 AppVer}}', | |||||
'{{StaticUrlPrefix}}/js/semantic.dropdown.custom.js?v={{MD5 AppVer}}', | '{{StaticUrlPrefix}}/js/semantic.dropdown.custom.js?v={{MD5 AppVer}}', | ||||
'{{StaticUrlPrefix}}/vendor/plugins/clipboard/clipboard.min.js', | '{{StaticUrlPrefix}}/vendor/plugins/clipboard/clipboard.min.js', | ||||
'{{StaticUrlPrefix}}/vendor/plugins/gitgraph/gitgraph.js', | |||||
'{{StaticUrlPrefix}}/vendor/plugins/vue/vue.min.js', | '{{StaticUrlPrefix}}/vendor/plugins/vue/vue.min.js', | ||||
'{{StaticUrlPrefix}}/vendor/plugins/emojify/emojify.custom.js', | '{{StaticUrlPrefix}}/vendor/plugins/emojify/emojify.custom.js', | ||||
'{{StaticUrlPrefix}}/vendor/plugins/cssrelpreload/loadCSS.min.js', | '{{StaticUrlPrefix}}/vendor/plugins/cssrelpreload/loadCSS.min.js', | ||||
'{{StaticUrlPrefix}}/vendor/assets/font-awesome/css/font-awesome.min.css', | '{{StaticUrlPrefix}}/vendor/assets/font-awesome/css/font-awesome.min.css', | ||||
'{{StaticUrlPrefix}}/vendor/assets/octicons/octicons.min.css', | '{{StaticUrlPrefix}}/vendor/assets/octicons/octicons.min.css', | ||||
'{{StaticUrlPrefix}}/vendor/plugins/simplemde/simplemde.min.css', | '{{StaticUrlPrefix}}/vendor/plugins/simplemde/simplemde.min.css', | ||||
'{{StaticUrlPrefix}}/vendor/plugins/gitgraph/gitgraph.css', | |||||
'{{StaticUrlPrefix}}/vendor/plugins/tribute/tribute.css', | '{{StaticUrlPrefix}}/vendor/plugins/tribute/tribute.css', | ||||
'{{StaticUrlPrefix}}/vendor/plugins/semantic/semantic.min.css', | '{{StaticUrlPrefix}}/vendor/plugins/semantic/semantic.min.css', | ||||
'{{StaticUrlPrefix}}/css/index.css?v={{MD5 AppVer}}', | '{{StaticUrlPrefix}}/css/index.css?v={{MD5 AppVer}}', |
/* globals gitGraph */ | |||||
$(() => { | |||||
const graphList = []; | |||||
if (!document.getElementById('graph-canvas')) { | |||||
return; | |||||
} | |||||
$('#graph-raw-list li span.node-relation').each(function () { | |||||
graphList.push($(this).text()); | |||||
}); | |||||
gitGraph(document.getElementById('graph-canvas'), graphList); | |||||
}); |
$(async () => { | |||||
const graphCanvas = document.getElementById('graph-canvas'); | |||||
if (!graphCanvas) return; | |||||
const [{ default: gitGraph }] = await Promise.all([ | |||||
import(/* webpackChunkName: "gitgraph" */'../vendor/gitgraph.js/gitgraph.custom.js'), | |||||
import(/* webpackChunkName: "gitgraph" */'../vendor/gitgraph.js/gitgraph.custom.css'), | |||||
]); | |||||
const graphList = []; | |||||
$('#graph-raw-list li span.node-relation').each(function () { | |||||
graphList.push($(this).text()); | |||||
}); | |||||
gitGraph(graphCanvas, graphList); | |||||
}); |
/* exported timeAddManual, toggleStopwatch, cancelStopwatch, initHeatmap */ | /* exported timeAddManual, toggleStopwatch, cancelStopwatch, initHeatmap */ | ||||
/* exported toggleDeadlineForm, setDeadline, updateDeadline, deleteDependencyModal, cancelCodeComment, onOAuthLoginClick */ | /* exported toggleDeadlineForm, setDeadline, updateDeadline, deleteDependencyModal, cancelCodeComment, onOAuthLoginClick */ | ||||
import './publicPath'; | |||||
import './gitGraph'; | |||||
function htmlEncode(text) { | function htmlEncode(text) { | ||||
return jQuery('<div />').text(text).html(); | return jQuery('<div />').text(text).html(); | ||||
} | } |
/* This sets up webpack's chunk loading to load resources from the same | |||||
directory where it loaded index.js from. This file must be imported | |||||
before any lazy-loading is being attempted. */ | |||||
if (document.currentScript && document.currentScript.src) { | |||||
const url = new URL(document.currentScript.src); | |||||
__webpack_public_path__ = `${url.pathname.replace(/\/[^/]*$/, '')}/`; | |||||
} else { | |||||
// compat: IE11 | |||||
const script = document.querySelector('script[src*="/index.js"]'); | |||||
__webpack_public_path__ = `${script.getAttribute('src').replace(/\/[^/]*$/, '')}/`; | |||||
} |
body {font:13.34px/1.4 helvetica,arial,freesans,clean,sans-serif;} | |||||
em {font-style:normal;} | |||||
#git-graph-container, #rel-container {float:left;} | #git-graph-container, #rel-container {float:left;} | ||||
#rel-container {max-width:30%; overflow-x:auto;} | #rel-container {max-width:30%; overflow-x:auto;} | ||||
#git-graph-container {overflow-x:auto; width:100%} | #git-graph-container {overflow-x:auto; width:100%} | ||||
#git-graph-container li a em {color:#BB0000;border-bottom:1px dotted #BBBBBB;text-decoration:none;font-style:normal;} | #git-graph-container li a em {color:#BB0000;border-bottom:1px dotted #BBBBBB;text-decoration:none;font-style:normal;} | ||||
#rev-container {width:100%} | #rev-container {width:100%} | ||||
#rev-list {margin:0;padding:0 5px 0 5px;min-width:95%} | #rev-list {margin:0;padding:0 5px 0 5px;min-width:95%} | ||||
#graph-raw-list {margin:0px;} | |||||
#graph-raw-list {margin:0px;} |
/* | |||||
* @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD 3-Clause | |||||
* Copyright (c) 2011, Terrence Lee <kill889@gmail.com> | |||||
* All rights reserved. | |||||
* | |||||
* Redistribution and use in source and binary forms, with or without | |||||
* modification, are permitted provided that the following conditions are met: | |||||
* * Redistributions of source code must retain the above copyright | |||||
* notice, this list of conditions and the following disclaimer. | |||||
* * Redistributions in binary form must reproduce the above copyright | |||||
* notice, this list of conditions and the following disclaimer in the | |||||
* documentation and/or other materials provided with the distribution. | |||||
* * Neither the name of the <organization> nor the | |||||
* names of its contributors may be used to endorse or promote products | |||||
* derived from this software without specific prior written permission. | |||||
* | |||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | |||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |||||
* DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY | |||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | |||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | |||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | |||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||||
*/ | |||||
export default function gitGraph(canvas, rawGraphList, config) { | |||||
if (!canvas.getContext) { | |||||
return; | |||||
} | |||||
if (typeof config === 'undefined') { | |||||
config = { | |||||
unitSize: 20, | |||||
lineWidth: 3, | |||||
nodeRadius: 4 | |||||
}; | |||||
} | |||||
const flows = []; | |||||
const graphList = []; | |||||
const ctx = canvas.getContext('2d'); | |||||
const devicePixelRatio = window.devicePixelRatio || 1; | |||||
const backingStoreRatio = ctx.webkitBackingStorePixelRatio | |||||
|| ctx.mozBackingStorePixelRatio | |||||
|| ctx.msBackingStorePixelRatio | |||||
|| ctx.oBackingStorePixelRatio | |||||
|| ctx.backingStorePixelRatio || 1; | |||||
const ratio = devicePixelRatio / backingStoreRatio; | |||||
const init = function () { | |||||
let maxWidth = 0; | |||||
let i; | |||||
const l = rawGraphList.length; | |||||
let row; | |||||
let midStr; | |||||
for (i = 0; i < l; i++) { | |||||
midStr = rawGraphList[i].replace(/\s+/g, ' ').replace(/^\s+|\s+$/g, ''); | |||||
maxWidth = Math.max(midStr.replace(/(_|\s)/g, '').length, maxWidth); | |||||
row = midStr.split(''); | |||||
graphList.unshift(row); | |||||
} | |||||
const width = maxWidth * config.unitSize; | |||||
const height = graphList.length * config.unitSize; | |||||
canvas.width = width * ratio; | |||||
canvas.height = height * ratio; | |||||
canvas.style.width = `${width}px`; | |||||
canvas.style.height = `${height}px`; | |||||
ctx.lineWidth = config.lineWidth; | |||||
ctx.lineJoin = 'round'; | |||||
ctx.lineCap = 'round'; | |||||
ctx.scale(ratio, ratio); | |||||
}; | |||||
const genRandomStr = function () { | |||||
const chars = '0123456789ABCDEF'; | |||||
const stringLength = 6; | |||||
let randomString = '', rnum, i; | |||||
for (i = 0; i < stringLength; i++) { | |||||
rnum = Math.floor(Math.random() * chars.length); | |||||
randomString += chars.substring(rnum, rnum + 1); | |||||
} | |||||
return randomString; | |||||
}; | |||||
const findFlow = function (id) { | |||||
let i = flows.length; | |||||
while (i-- && flows[i].id !== id); | |||||
return i; | |||||
}; | |||||
const findColomn = function (symbol, row) { | |||||
let i = row.length; | |||||
while (i-- && row[i] !== symbol); | |||||
return i; | |||||
}; | |||||
const findBranchOut = function (row) { | |||||
if (!row) { | |||||
return -1; | |||||
} | |||||
let i = row.length; | |||||
while (i-- | |||||
&& !(row[i - 1] && row[i] === '/' && row[i - 1] === '|') | |||||
&& !(row[i - 2] && row[i] === '_' && row[i - 2] === '|')); | |||||
return i; | |||||
}; | |||||
const findLineBreak = function (row) { | |||||
if (!row) { | |||||
return -1; | |||||
} | |||||
let i = row.length; | |||||
while (i-- | |||||
&& !(row[i - 1] && row[i - 2] && row[i] === ' ' && row[i - 1] === '|' && row[i - 2] === '_')); | |||||
return i; | |||||
}; | |||||
const genNewFlow = function () { | |||||
let newId; | |||||
do { | |||||
newId = genRandomStr(); | |||||
} while (findFlow(newId) !== -1); | |||||
return { id: newId, color: `#${newId}` }; | |||||
}; | |||||
// Draw methods | |||||
const drawLine = function (moveX, moveY, lineX, lineY, color) { | |||||
ctx.strokeStyle = color; | |||||
ctx.beginPath(); | |||||
ctx.moveTo(moveX, moveY); | |||||
ctx.lineTo(lineX, lineY); | |||||
ctx.stroke(); | |||||
}; | |||||
const drawLineRight = function (x, y, color) { | |||||
drawLine(x, y + config.unitSize / 2, x + config.unitSize, y + config.unitSize / 2, color); | |||||
}; | |||||
const drawLineUp = function (x, y, color) { | |||||
drawLine(x, y + config.unitSize / 2, x, y - config.unitSize / 2, color); | |||||
}; | |||||
const drawNode = function (x, y, color) { | |||||
ctx.strokeStyle = color; | |||||
drawLineUp(x, y, color); | |||||
ctx.beginPath(); | |||||
ctx.arc(x, y, config.nodeRadius, 0, Math.PI * 2, true); | |||||
ctx.fill(); | |||||
}; | |||||
const drawLineIn = function (x, y, color) { | |||||
drawLine(x + config.unitSize, y + config.unitSize / 2, x, y - config.unitSize / 2, color); | |||||
}; | |||||
const drawLineOut = function (x, y, color) { | |||||
drawLine(x, y + config.unitSize / 2, x + config.unitSize, y - config.unitSize / 2, color); | |||||
}; | |||||
const draw = function (graphList) { | |||||
let colomn, colomnIndex, prevColomn, condenseIndex, breakIndex = -1; | |||||
let x, y; | |||||
let color; | |||||
let nodePos; | |||||
let tempFlow; | |||||
let prevRowLength = 0; | |||||
let flowSwapPos = -1; | |||||
let lastLinePos; | |||||
let i, l; | |||||
let condenseCurrentLength, condensePrevLength = 0; | |||||
let inlineIntersect = false; | |||||
// initiate color array for first row | |||||
for (i = 0, l = graphList[0].length; i < l; i++) { | |||||
if (graphList[0][i] !== '_' && graphList[0][i] !== ' ') { | |||||
flows.push(genNewFlow()); | |||||
} | |||||
} | |||||
y = (canvas.height / ratio) - config.unitSize * 0.5; | |||||
// iterate | |||||
for (i = 0, l = graphList.length; i < l; i++) { | |||||
x = config.unitSize * 0.5; | |||||
const currentRow = graphList[i]; | |||||
const nextRow = graphList[i + 1]; | |||||
const prevRow = graphList[i - 1]; | |||||
flowSwapPos = -1; | |||||
condenseCurrentLength = currentRow.filter((val) => { | |||||
return (val !== ' ' && val !== '_'); | |||||
}).length; | |||||
// pre process begin | |||||
// use last row for analysing | |||||
if (prevRow) { | |||||
if (!inlineIntersect) { | |||||
// intersect might happen | |||||
for (colomnIndex = 0; colomnIndex < prevRowLength; colomnIndex++) { | |||||
if (prevRow[colomnIndex + 1] | |||||
&& (prevRow[colomnIndex] === '/' && prevRow[colomnIndex + 1] === '|') | |||||
|| ((prevRow[colomnIndex] === '_' && prevRow[colomnIndex + 1] === '|') | |||||
&& (prevRow[colomnIndex + 2] === '/'))) { | |||||
flowSwapPos = colomnIndex; | |||||
// swap two flow | |||||
tempFlow = { id: flows[flowSwapPos].id, color: flows[flowSwapPos].color }; | |||||
flows[flowSwapPos].id = flows[flowSwapPos + 1].id; | |||||
flows[flowSwapPos].color = flows[flowSwapPos + 1].color; | |||||
flows[flowSwapPos + 1].id = tempFlow.id; | |||||
flows[flowSwapPos + 1].color = tempFlow.color; | |||||
} | |||||
} | |||||
} | |||||
if (condensePrevLength < condenseCurrentLength | |||||
&& ((nodePos = findColomn('*', currentRow)) !== -1 | |||||
&& (findColomn('_', currentRow) === -1))) { | |||||
flows.splice(nodePos - 1, 0, genNewFlow()); | |||||
} | |||||
if (prevRowLength > currentRow.length | |||||
&& (nodePos = findColomn('*', prevRow)) !== -1) { | |||||
if (findColomn('_', currentRow) === -1 | |||||
&& findColomn('/', currentRow) === -1 | |||||
&& findColomn('\\', currentRow) === -1) { | |||||
flows.splice(nodePos + 1, 1); | |||||
} | |||||
} | |||||
} // done with the previous row | |||||
prevRowLength = currentRow.length; // store for next round | |||||
colomnIndex = 0; // reset index | |||||
condenseIndex = 0; | |||||
condensePrevLength = 0; | |||||
breakIndex = -1; // reset break index | |||||
while (colomnIndex < currentRow.length) { | |||||
colomn = currentRow[colomnIndex]; | |||||
if (colomn !== ' ' && colomn !== '_') { | |||||
++condensePrevLength; | |||||
} | |||||
// check and fix line break in next row | |||||
if (colomn === '/' && currentRow[colomnIndex - 1] && currentRow[colomnIndex - 1] === '|') { | |||||
if ((breakIndex = findLineBreak(nextRow)) !== -1) { | |||||
nextRow.splice(breakIndex, 1); | |||||
} | |||||
} | |||||
// if line break found replace all '/' with '|' after breakIndex in previous row | |||||
if (breakIndex !== -1 && colomn === '/' && colomnIndex > breakIndex) { | |||||
currentRow[colomnIndex] = '|'; | |||||
colomn = '|'; | |||||
} | |||||
if (colomn === ' ' | |||||
&& currentRow[colomnIndex + 1] | |||||
&& currentRow[colomnIndex + 1] === '_' | |||||
&& currentRow[colomnIndex - 1] | |||||
&& currentRow[colomnIndex - 1] === '|') { | |||||
currentRow.splice(colomnIndex, 1); | |||||
currentRow[colomnIndex] = '/'; | |||||
colomn = '/'; | |||||
} | |||||
// create new flow only when no intersect happened | |||||
if (flowSwapPos === -1 | |||||
&& colomn === '/' | |||||
&& currentRow[colomnIndex - 1] | |||||
&& currentRow[colomnIndex - 1] === '|') { | |||||
flows.splice(condenseIndex, 0, genNewFlow()); | |||||
} | |||||
// change \ and / to | when it's in the last position of the whole row | |||||
if (colomn === '/' || colomn === '\\') { | |||||
if (!(colomn === '/' && findBranchOut(nextRow) === -1)) { | |||||
if ((lastLinePos = Math.max(findColomn('|', currentRow), | |||||
findColomn('*', currentRow))) !== -1 | |||||
&& (lastLinePos < colomnIndex - 1)) { | |||||
while (currentRow[++lastLinePos] === ' '); | |||||
if (lastLinePos === colomnIndex) { | |||||
currentRow[colomnIndex] = '|'; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
if (colomn === '*' | |||||
&& prevRow | |||||
&& prevRow[condenseIndex + 1] === '\\') { | |||||
flows.splice(condenseIndex + 1, 1); | |||||
} | |||||
if (colomn !== ' ') { | |||||
++condenseIndex; | |||||
} | |||||
++colomnIndex; | |||||
} | |||||
condenseCurrentLength = currentRow.filter((val) => { | |||||
return (val !== ' ' && val !== '_'); | |||||
}).length; | |||||
// do some clean up | |||||
if (flows.length > condenseCurrentLength) { | |||||
flows.splice(condenseCurrentLength, flows.length - condenseCurrentLength); | |||||
} | |||||
colomnIndex = 0; | |||||
// a little inline analysis and draw process | |||||
while (colomnIndex < currentRow.length) { | |||||
colomn = currentRow[colomnIndex]; | |||||
prevColomn = currentRow[colomnIndex - 1]; | |||||
if (currentRow[colomnIndex] === ' ') { | |||||
currentRow.splice(colomnIndex, 1); | |||||
x += config.unitSize; | |||||
continue; | |||||
} | |||||
// inline interset | |||||
if ((colomn === '_' || colomn === '/') | |||||
&& currentRow[colomnIndex - 1] === '|' | |||||
&& currentRow[colomnIndex - 2] === '_') { | |||||
inlineIntersect = true; | |||||
tempFlow = flows.splice(colomnIndex - 2, 1)[0]; | |||||
flows.splice(colomnIndex - 1, 0, tempFlow); | |||||
currentRow.splice(colomnIndex - 2, 1); | |||||
colomnIndex -= 1; | |||||
} else { | |||||
inlineIntersect = false; | |||||
} | |||||
color = flows[colomnIndex].color; | |||||
switch (colomn) { | |||||
case '_': | |||||
drawLineRight(x, y, color); | |||||
x += config.unitSize; | |||||
break; | |||||
case '*': | |||||
drawNode(x, y, color); | |||||
break; | |||||
case '|': | |||||
drawLineUp(x, y, color); | |||||
break; | |||||
case '/': | |||||
if (prevColomn | |||||
&& (prevColomn === '/' | |||||
|| prevColomn === ' ')) { | |||||
x -= config.unitSize; | |||||
} | |||||
drawLineOut(x, y, color); | |||||
x += config.unitSize; | |||||
break; | |||||
case '\\': | |||||
drawLineIn(x, y, color); | |||||
break; | |||||
} | |||||
++colomnIndex; | |||||
} | |||||
y -= config.unitSize; | |||||
} | |||||
}; | |||||
init(); | |||||
draw(graphList); | |||||
} | |||||
// @end-license |
module.exports = { | module.exports = { | ||||
mode: 'production', | mode: 'production', | ||||
entry: { | entry: { | ||||
index: ['./web_src/js/index', './web_src/js/draw'] | |||||
index: ['./web_src/js/index'] | |||||
}, | }, | ||||
devtool: 'source-map', | devtool: 'source-map', | ||||
output: { | output: { | ||||
path: path.resolve(__dirname, 'public/js'), | path: path.resolve(__dirname, 'public/js'), | ||||
filename: 'index.js' | |||||
filename: 'index.js', | |||||
chunkFilename: '[name].js', | |||||
}, | }, | ||||
optimization: { | optimization: { | ||||
minimize: true, | minimize: true, | ||||
minimizer: [new TerserPlugin({ | minimizer: [new TerserPlugin({ | ||||
sourceMap: true, | sourceMap: true, | ||||
extractComments: false, | |||||
terserOptions: { | |||||
output: { | |||||
comments: false, | |||||
}, | |||||
}, | |||||
})], | })], | ||||
}, | }, | ||||
module: { | module: { | ||||
corejs: 3, | corejs: 3, | ||||
} | } | ||||
] | ] | ||||
] | |||||
], | |||||
plugins: [ | |||||
[ | |||||
'@babel/plugin-transform-runtime', | |||||
{ | |||||
regenerator: true, | |||||
} | |||||
] | |||||
], | |||||
} | } | ||||
} | } | ||||
} | |||||
}, | |||||
{ | |||||
test: /\.css$/i, | |||||
use: ['style-loader', 'css-loader'], | |||||
}, | |||||
] | ] | ||||
} | } | ||||
}; | }; |