Render the git graph on the server (#12333)

Rendering the git graph on the server means that we can properly track flows and switch from the Canvas implementation to a SVG implementation.

* This implementation provides a 16 limited color selection
* The uniqued color numbers are also provided
* And there is also a monochrome version
*In addition is a hover highlight that allows users to highlight commits on the same flow.

Closes #12209

Signed-off-by: Andrew Thornton art27@cantab.net
Co-authored-by: silverwind <me@silverwind.io>
This commit is contained in:
zeripath 2020-08-06 09:04:08 +01:00 committed by GitHub
parent f1a42f5d5e
commit 2c1ae6c82d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 1666 additions and 696 deletions

View file

@ -1,568 +1,81 @@
// Although inspired by the https://github.com/bluef/gitgraph.js/blob/master/gitgraph.js
// this has been completely rewritten with almost no remaining code
// GitGraphCanvas is a canvas for drawing gitgraphs on to
class GitGraphCanvas {
constructor(canvas, widthUnits, heightUnits, config) {
this.ctx = canvas.getContext('2d');
const width = widthUnits * config.unitSize;
this.height = heightUnits * config.unitSize;
const ratio = window.devicePixelRatio || 1;
canvas.width = width * ratio;
canvas.height = this.height * ratio;
canvas.style.width = `${width}px`;
canvas.style.height = `${this.height}px`;
this.ctx.lineWidth = config.lineWidth;
this.ctx.lineJoin = 'round';
this.ctx.lineCap = 'round';
this.ctx.scale(ratio, ratio);
this.config = config;
}
drawLine(moveX, moveY, lineX, lineY, color) {
this.ctx.strokeStyle = color;
this.ctx.beginPath();
this.ctx.moveTo(moveX, moveY);
this.ctx.lineTo(lineX, lineY);
this.ctx.stroke();
}
drawLineRight(x, y, color) {
this.drawLine(
x - 0.5 * this.config.unitSize,
y + this.config.unitSize / 2,
x + 0.5 * this.config.unitSize,
y + this.config.unitSize / 2,
color
);
}
drawLineUp(x, y, color) {
this.drawLine(
x,
y + this.config.unitSize / 2,
x,
y - this.config.unitSize / 2,
color
);
}
drawNode(x, y, color) {
this.ctx.strokeStyle = color;
this.drawLineUp(x, y, color);
this.ctx.beginPath();
this.ctx.arc(x, y, this.config.nodeRadius, 0, Math.PI * 2, true);
this.ctx.fillStyle = color;
this.ctx.fill();
}
drawLineIn(x, y, color) {
this.drawLine(
x + 0.5 * this.config.unitSize,
y + this.config.unitSize / 2,
x - 0.5 * this.config.unitSize,
y - this.config.unitSize / 2,
color
);
}
drawLineOut(x, y, color) {
this.drawLine(
x - 0.5 * this.config.unitSize,
y + this.config.unitSize / 2,
x + 0.5 * this.config.unitSize,
y - this.config.unitSize / 2,
color
);
}
drawSymbol(symbol, columnNumber, rowNumber, color) {
const y = this.height - this.config.unitSize * (rowNumber + 0.5);
const x = this.config.unitSize * 0.5 * (columnNumber + 1);
switch (symbol) {
case '-':
if (columnNumber % 2 === 1) {
this.drawLineRight(x, y, color);
}
break;
case '_':
this.drawLineRight(x, y, color);
break;
case '*':
this.drawNode(x, y, color);
break;
case '|':
this.drawLineUp(x, y, color);
break;
case '/':
this.drawLineOut(x, y, color);
break;
case '\\':
this.drawLineIn(x, y, color);
break;
case '.':
case ' ':
break;
default:
console.error('Unknown symbol', symbol, color);
}
}
}
class GitGraph {
constructor(canvas, rawRows, config) {
this.rows = [];
let maxWidth = 0;
for (let i = 0; i < rawRows.length; i++) {
const rowStr = rawRows[i];
maxWidth = Math.max(rowStr.replace(/([_\s.-])/g, '').length, maxWidth);
const rowArray = rowStr.split('');
this.rows.unshift(rowArray);
}
this.currentFlows = [];
this.previousFlows = [];
this.gitGraphCanvas = new GitGraphCanvas(
canvas,
maxWidth,
this.rows.length,
config
);
}
generateNewFlow(column) {
let newId;
do {
newId = generateRandomColorString();
} while (this.hasFlow(newId, column));
return {id: newId, color: `#${newId}`};
}
hasFlow(id, column) {
// We want to find the flow with the current ID
// Possible flows are those in the currentFlows
// Or flows in previousFlows[column-2:...]
for (
let idx = column - 2 < 0 ? 0 : column - 2;
idx < this.previousFlows.length;
idx++
) {
if (this.previousFlows[idx] && this.previousFlows[idx].id === id) {
return true;
}
}
for (let idx = 0; idx < this.currentFlows.length; idx++) {
if (this.currentFlows[idx] && this.currentFlows[idx].id === id) {
return true;
}
}
return false;
}
takePreviousFlow(column) {
if (column < this.previousFlows.length && this.previousFlows[column]) {
const flow = this.previousFlows[column];
this.previousFlows[column] = null;
return flow;
}
return this.generateNewFlow(column);
}
draw() {
if (this.rows.length === 0) {
return;
}
this.currentFlows = new Array(this.rows[0].length);
// Generate flows for the first row - I do not believe that this can contain '_', '-', '.'
for (let column = 0; column < this.rows[0].length; column++) {
if (this.rows[0][column] === ' ') {
continue;
}
this.currentFlows[column] = this.generateNewFlow(column);
}
// Draw the first row
for (let column = 0; column < this.rows[0].length; column++) {
const symbol = this.rows[0][column];
const color = this.currentFlows[column] ? this.currentFlows[column].color : '';
this.gitGraphCanvas.drawSymbol(symbol, column, 0, color);
}
for (let row = 1; row < this.rows.length; row++) {
// Done previous row - step up the row
const currentRow = this.rows[row];
const previousRow = this.rows[row - 1];
this.previousFlows = this.currentFlows;
this.currentFlows = new Array(currentRow.length);
// Set flows for this row
for (let column = 0; column < currentRow.length; column++) {
column = this.setFlowFor(column, currentRow, previousRow);
}
// Draw this row
for (let column = 0; column < currentRow.length; column++) {
const symbol = currentRow[column];
const color = this.currentFlows[column] ? this.currentFlows[column].color : '';
this.gitGraphCanvas.drawSymbol(symbol, column, row, color);
}
}
}
setFlowFor(column, currentRow, previousRow) {
const symbol = currentRow[column];
switch (symbol) {
case '|':
case '*':
return this.setUpFlow(column, currentRow, previousRow);
case '/':
return this.setOutFlow(column, currentRow, previousRow);
case '\\':
return this.setInFlow(column, currentRow, previousRow);
case '_':
return this.setRightFlow(column, currentRow, previousRow);
case '-':
return this.setLeftFlow(column, currentRow, previousRow);
case ' ':
// In space no one can hear you flow ... (?)
return column;
default:
// Unexpected so let's generate a new flow and wait for bug-reports
this.currentFlows[column] = this.generateNewFlow(column);
return column;
}
}
// setUpFlow handles '|' or '*' - returns the last column that was set
// generally we prefer to take the left most flow from the previous row
setUpFlow(column, currentRow, previousRow) {
// If ' |/' or ' |_'
// '/|' '/|' -> Take the '|' flow directly beneath us
if (
column + 1 < currentRow.length &&
(currentRow[column + 1] === '/' || currentRow[column + 1] === '_') &&
column < previousRow.length &&
(previousRow[column] === '|' || previousRow[column] === '*') &&
previousRow[column - 1] === '/'
) {
this.currentFlows[column] = this.takePreviousFlow(column);
return column;
}
// If ' |/' or ' |_'
// '/ ' '/ ' -> Take the '/' flow from the preceding column
if (
column + 1 < currentRow.length &&
(currentRow[column + 1] === '/' || currentRow[column + 1] === '_') &&
column - 1 < previousRow.length &&
previousRow[column - 1] === '/'
) {
this.currentFlows[column] = this.takePreviousFlow(column - 1);
return column;
}
// If ' |'
// '/' -> Take the '/' flow - (we always prefer the left-most flow)
if (
column > 0 &&
column - 1 < previousRow.length &&
previousRow[column - 1] === '/'
) {
this.currentFlows[column] = this.takePreviousFlow(column - 1);
return column;
}
// If '|' OR '|' take the '|' flow
// '|' '*'
if (
column < previousRow.length &&
(previousRow[column] === '|' || previousRow[column] === '*')
) {
this.currentFlows[column] = this.takePreviousFlow(column);
return column;
}
// If '| ' keep the '\' flow
// ' \'
if (column + 1 < previousRow.length && previousRow[column + 1] === '\\') {
this.currentFlows[column] = this.takePreviousFlow(column + 1);
return column;
}
// Otherwise just create a new flow - probably this is an error...
this.currentFlows[column] = this.generateNewFlow(column);
return column;
}
// setOutFlow handles '/' - returns the last column that was set
// generally we prefer to take the left most flow from the previous row
setOutFlow(column, currentRow, previousRow) {
// If '_/' -> keep the '_' flow
if (column > 0 && currentRow[column - 1] === '_') {
this.currentFlows[column] = this.currentFlows[column - 1];
return column;
}
// If '_|/' -> keep the '_' flow
if (
column > 1 &&
(currentRow[column - 1] === '|' || currentRow[column - 1] === '*') &&
currentRow[column - 2] === '_'
) {
this.currentFlows[column] = this.currentFlows[column - 2];
return column;
}
// If '|/'
// '/' -> take the '/' flow (if it is still available)
if (
column > 1 &&
currentRow[column - 1] === '|' &&
column - 2 < previousRow.length &&
previousRow[column - 2] === '/'
) {
this.currentFlows[column] = this.takePreviousFlow(column - 2);
return column;
}
// If ' /'
// '/' -> take the '/' flow, but transform the symbol to '|' due to our spacing
// This should only happen if there are 3 '/' - in a row so we don't need to be cleverer here
if (
column > 0 &&
currentRow[column - 1] === ' ' &&
column - 1 < previousRow.length &&
previousRow[column - 1] === '/'
) {
this.currentFlows[column] = this.takePreviousFlow(column - 1);
currentRow[column] = '|';
return column;
}
// If ' /'
// '|' -> take the '|' flow
if (
column > 0 &&
currentRow[column - 1] === ' ' &&
column - 1 < previousRow.length &&
(previousRow[column - 1] === '|' || previousRow[column - 1] === '*')
) {
this.currentFlows[column] = this.takePreviousFlow(column - 1);
return column;
}
// If '/' <- Not sure this ever happens... but take the '\' flow
// '\'
if (column < previousRow.length && previousRow[column] === '\\') {
this.currentFlows[column] = this.takePreviousFlow(column);
return column;
}
// Otherwise just generate a new flow and wait for bug-reports...
this.currentFlows[column] = this.generateNewFlow(column);
return column;
}
// setInFlow handles '\' - returns the last column that was set
// generally we prefer to take the left most flow from the previous row
setInFlow(column, currentRow, previousRow) {
// If '\?'
// '/?' -> take the '/' flow
if (column < previousRow.length && previousRow[column] === '/') {
this.currentFlows[column] = this.takePreviousFlow(column);
return column;
}
// If '\?'
// ' \' -> take the '\' flow and reassign to '|'
// This should only happen if there are 3 '\' - in a row so we don't need to be cleverer here
if (column + 1 < previousRow.length && previousRow[column + 1] === '\\') {
this.currentFlows[column] = this.takePreviousFlow(column + 1);
currentRow[column] = '|';
return column;
}
// If '\?'
// ' |' -> take the '|' flow
if (
column + 1 < previousRow.length &&
(previousRow[column + 1] === '|' || previousRow[column + 1] === '*')
) {
this.currentFlows[column] = this.takePreviousFlow(column + 1);
return column;
}
// Otherwise just generate a new flow and wait for bug-reports if we're wrong...
this.currentFlows[column] = this.generateNewFlow(column);
return column;
}
// setRightFlow handles '_' - returns the last column that was set
// generally we prefer to take the left most flow from the previous row
setRightFlow(column, currentRow, previousRow) {
// if '__' keep the '_' flow
if (column > 0 && currentRow[column - 1] === '_') {
this.currentFlows[column] = this.currentFlows[column - 1];
return column;
}
// if '_|_' -> keep the '_' flow
if (
column > 1 &&
currentRow[column - 1] === '|' &&
currentRow[column - 2] === '_'
) {
this.currentFlows[column] = this.currentFlows[column - 2];
return column;
}
// if ' _' -> take the '/' flow
// '/ '
if (
column > 0 &&
column - 1 < previousRow.length &&
previousRow[column - 1] === '/'
) {
this.currentFlows[column] = this.takePreviousFlow(column - 1);
return column;
}
// if ' |_'
// '/? ' -> take the '/' flow (this may cause generation...)
// we can do this because we know that git graph
// doesn't create compact graphs like: ' |_'
// '//'
if (
column > 1 &&
column - 2 < previousRow.length &&
previousRow[column - 2] === '/'
) {
this.currentFlows[column] = this.takePreviousFlow(column - 2);
return column;
}
// There really shouldn't be another way of doing this - generate and wait for bug-reports...
this.currentFlows[column] = this.generateNewFlow(column);
return column;
}
// setLeftFlow handles '----.' - returns the last column that was set
// generally we prefer to take the left most flow from the previous row that terminates this left recursion
setLeftFlow(column, currentRow, previousRow) {
// This is: '----------.' or the like
// ' \ \ /|\'
// Find the end of the '-' or nearest '/|\' in the previousRow :
let originalColumn = column;
let flow;
for (; column < currentRow.length && currentRow[column] === '-'; column++) {
if (column > 0 && column - 1 < previousRow.length && previousRow[column - 1] === '/') {
flow = this.takePreviousFlow(column - 1);
break;
} else if (column < previousRow.length && previousRow[column] === '|') {
flow = this.takePreviousFlow(column);
break;
} else if (
column + 1 < previousRow.length &&
previousRow[column + 1] === '\\'
) {
flow = this.takePreviousFlow(column + 1);
break;
}
}
// if we have a flow then we found a '/|\' in the previousRow
if (flow) {
for (; originalColumn < column + 1; originalColumn++) {
this.currentFlows[originalColumn] = flow;
}
return column;
}
// If the symbol in the column is not a '.' then there's likely an error
if (currentRow[column] !== '.') {
// It really should end in a '.' but this one doesn't...
// 1. Step back - we don't want to eat this column
column--;
// 2. Generate a new flow and await bug-reports...
this.currentFlows[column] = this.generateNewFlow(column);
// 3. Assign all of the '-' to the same flow.
for (; originalColumn < column; originalColumn++) {
this.currentFlows[originalColumn] = this.currentFlows[column];
}
return column;
}
// We have a terminal '.' eg. the current row looks like '----.'
// the previous row should look like one of '/|\' eg. ' \'
if (column > 0 && column - 1 < previousRow.length && previousRow[column - 1] === '/') {
flow = this.takePreviousFlow(column - 1);
} else if (column < previousRow.length && previousRow[column] === '|') {
flow = this.takePreviousFlow(column);
} else if (
column + 1 < previousRow.length &&
previousRow[column + 1] === '\\'
) {
flow = this.takePreviousFlow(column + 1);
} else {
// Again unexpected so let's generate and wait the bug-report
flow = this.generateNewFlow(column);
}
// Assign all of the rest of the ----. to this flow.
for (; originalColumn < column + 1; originalColumn++) {
this.currentFlows[originalColumn] = flow;
}
return column;
}
}
function generateRandomColorString() {
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;
}
export default async function initGitGraph() {
const graphCanvas = document.getElementById('graph-canvas');
if (!graphCanvas || !graphCanvas.getContext) return;
const graphContainer = document.getElementById('git-graph-container');
if (!graphContainer) return;
// Grab the raw graphList
const graphList = [];
$('#graph-raw-list li span.node-relation').each(function () {
graphList.push($(this).text());
$('#flow-color-monochrome').on('click', () => {
$('#flow-color-monochrome').addClass('active');
$('#flow-color-colored').removeClass('active');
$('#git-graph-container').removeClass('colored').addClass('monochrome');
const params = new URLSearchParams(window.location.search);
params.set('mode', 'monochrome');
const queryString = params.toString();
if (queryString) {
window.history.replaceState({}, '', `?${queryString}`);
} else {
window.history.replaceState({}, '', window.location.pathname);
}
$('.pagination a').each((_, that) => {
const href = $(that).attr('href');
if (!href) return;
const url = new URL(href, window.location);
const params = url.searchParams;
params.set('mode', 'monochrome');
url.search = `?${params.toString()}`;
$(that).attr('href', url.href);
});
});
$('#flow-color-colored').on('click', () => {
$('#flow-color-colored').addClass('active');
$('#flow-color-monochrome').removeClass('active');
$('#git-graph-container').addClass('colored').removeClass('monochrome');
$('.pagination a').each((_, that) => {
const href = $(that).attr('href');
if (!href) return;
const url = new URL(href, window.location);
const params = url.searchParams;
params.delete('mode');
url.search = `?${params.toString()}`;
$(that).attr('href', url.href);
});
const params = new URLSearchParams(window.location.search);
params.delete('mode');
const queryString = params.toString();
if (queryString) {
window.history.replaceState({}, '', `?${queryString}`);
} else {
window.history.replaceState({}, '', window.location.pathname);
}
});
$('#git-graph-container').on('mouseenter', '#rev-list li', (e) => {
const flow = $(e.currentTarget).data('flow');
if (flow === 0) return;
$(`#flow-${flow}`).addClass('highlight');
$(e.currentTarget).addClass('hover');
$(`#rev-list li[data-flow='${flow}']`).addClass('highlight');
});
$('#git-graph-container').on('mouseleave', '#rev-list li', (e) => {
const flow = $(e.currentTarget).data('flow');
if (flow === 0) return;
$(`#flow-${flow}`).removeClass('highlight');
$(e.currentTarget).removeClass('hover');
$(`#rev-list li[data-flow='${flow}']`).removeClass('highlight');
});
$('#git-graph-container').on('mouseenter', '#rel-container .flow-group', (e) => {
$(e.currentTarget).addClass('highlight');
const flow = $(e.currentTarget).data('flow');
$(`#rev-list li[data-flow='${flow}']`).addClass('highlight');
});
$('#git-graph-container').on('mouseleave', '#rel-container .flow-group', (e) => {
$(e.currentTarget).removeClass('highlight');
const flow = $(e.currentTarget).data('flow');
$(`#rev-list li[data-flow='${flow}']`).removeClass('highlight');
});
$('#git-graph-container').on('mouseenter', '#rel-container .flow-commit', (e) => {
const rev = $(e.currentTarget).data('rev');
$(`#rev-list li#commit-${rev}`).addClass('hover');
});
$('#git-graph-container').on('mouseleave', '#rel-container .flow-commit', (e) => {
const rev = $(e.currentTarget).data('rev');
$(`#rev-list li#commit-${rev}`).removeClass('hover');
});
// Define some drawing parameters
const config = {
unitSize: 20,
lineWidth: 3,
nodeRadius: 4
};
const gitGraph = new GitGraph(graphCanvas, graphList, config);
gitGraph.draw();
graphCanvas.closest('#git-graph-container').classList.add('in');
}

View file

@ -3082,11 +3082,3 @@ tbody.commit-list {
}
}
}
#git-graph-container {
display: none;
}
#git-graph-container.in {
display: block;
}

View file

@ -0,0 +1,256 @@
#git-graph-container {
float: left;
display: block;
overflow-x: auto;
width: 100%;
.color-buttons {
margin-right: 0;
}
.ui.header.dividing {
padding-bottom: 10px;
}
li {
list-style-type: none;
height: 20px;
line-height: 20px;
white-space: nowrap;
.node-relation {
font-family: "Bitstream Vera Sans Mono", "Courier", monospace;
}
.author {
color: #666666;
}
.time {
color: #999999;
font-size: 80%;
}
a {
color: #000000;
}
a:hover {
text-decoration: underline;
}
a em {
color: #bb0000;
border-bottom: 1px dotted #bbbbbb;
text-decoration: none;
font-style: normal;
}
}
#rel-container {
max-width: 30%;
overflow-x: auto;
float: left;
}
#rev-container {
width: 100%;
}
#rev-list {
margin: 0;
padding: 0 5px;
min-width: 95%;
li.highlight,
li.hover {
background-color: rgba(0, 0, 0, .05);
}
li.highlight.hover {
background-color: rgba(0, 0, 0, .1);
}
}
#graph-raw-list {
margin: 0;
}
&.monochrome #rel-container {
.flow-group {
stroke: grey;
fill: grey;
}
.flow-group.highlight {
stroke: black;
fill: black;
}
}
&:not(.monochrome) #rel-container {
.flow-group {
&.flow-color-16-1 {
stroke: #499a37;
fill: #499a37;
}
&.flow-color-16-2 {
stroke: hsl(356, 58%, 54%);
fill: #ce4751;
}
&.flow-color-16-3 {
stroke: #8f9121;
fill: #8f9121;
}
&.flow-color-16-4 {
stroke: #ac32a6;
fill: #ac32a6;
}
&.flow-color-16-5 {
stroke: #3d27aa;
fill: #3d27aa;
}
&.flow-color-16-6 {
stroke: #c67d28;
fill: #c67d28;
}
&.flow-color-16-7 {
stroke: #4db392;
fill: #4db392;
}
&.flow-color-16-8 {
stroke: #aa4d30;
fill: #aa4d30;
}
&.flow-color-16-9 {
stroke: #2a6f84;
fill: #2a6f84;
}
&.flow-color-16-10 {
stroke: #c45327;
fill: #c45327;
}
&.flow-color-16-11 {
stroke: #3d965c;
fill: #3d965c;
}
&.flow-color-16-12 {
stroke: #792a93;
fill: #792a93;
}
&.flow-color-16-13 {
stroke: #439d73;
fill: #439d73;
}
&.flow-color-16-14 {
stroke: #103aad;
fill: #103aad;
}
&.flow-color-16-15 {
stroke: #982e85;
fill: #982e85;
}
&.flow-color-16-0 {
stroke: #7db233;
fill: #7db233;
}
}
.flow-group.highlight {
&.flow-color-16-1 {
stroke: #5ac144;
fill: #5ac144;
}
&.flow-color-16-2 {
stroke: #ed5a8b;
fill: #ed5a8b;
}
&.flow-color-16-3 {
stroke: #ced049;
fill: #ced048;
}
&.flow-color-16-4 {
stroke: #db61d7;
fill: #db62d6;
}
&.flow-color-16-5 {
stroke: #4e33d1;
fill: #4f35d1;
}
&.flow-color-16-6 {
stroke: #e6a151;
fill: #e6a151;
}
&.flow-color-16-7 {
stroke: #44daaa;
fill: #44daaa;
}
&.flow-color-16-8 {
stroke: #dd7a5c;
fill: #dd7a5c;
}
&.flow-color-16-9 {
stroke: #38859c;
fill: #38859c;
}
&.flow-color-16-10 {
stroke: #d95520;
fill: #d95520;
}
&.flow-color-16-11 {
stroke: #42ae68;
fill: #42ae68;
}
&.flow-color-16-12 {
stroke: #9126b5;
fill: #9126b5;
}
&.flow-color-16-13 {
stroke: #4ab080;
fill: #4ab080;
}
&.flow-color-16-14 {
stroke: #284fb8;
fill: #284fb8;
}
&.flow-color-16-15 {
stroke: #971c80;
fill: #971c80;
}
&.flow-color-16-0 {
stroke: #87ca28;
fill: #87ca28;
}
}
}
}

View file

@ -1,5 +1,6 @@
@import "~font-awesome/css/font-awesome.css";
@import "./vendor/gitGraph.css";
@import "./features/gitgraph.less";
@import "./features/animations.less";
@import "./markdown/mermaid.less";

View file

@ -919,11 +919,17 @@ a.ui.basic.green.label:hover {
.ui.active.button:active,
.ui.button:active,
.ui.button:focus {
.ui.button:focus,
.ui.active.button {
background-color: #2e3e4e;
color: #dbdbdb;
}
.ui.active.button:hover {
background-color: #475e75;
color: #dbdbdb;
}
.ui.dropdown .menu .selected.item,
.ui.dropdown.selected {
color: #dbdbdb;
@ -1921,6 +1927,45 @@ footer .container .links > * {
color: #2a2e3a;
}
#git-graph-container.monochrome #rel-container .flow-group {
stroke: dimgrey;
fill: dimgrey;
}
#git-graph-container.monochrome #rel-container .flow-group.highlight {
stroke: darkgrey;
fill: darkgrey;
}
#git-graph-container:not(.monochrome) #rel-container .flow-group {
&.flow-color-16-5 {
stroke: #5543b1;
fill: #5543b1;
}
}
#git-graph-container:not(.monochrome) #rel-container .flow-group.highlight {
&.flow-color-16-5 {
stroke: #7058e6;
fill: #7058e6;
}
}
#git-graph-container #rev-list li.highlight,
#git-graph-container #rev-list li.hover {
background-color: rgba(255, 255, 255, .05);
}
#git-graph-container #rev-list li.highlight.hover {
background-color: rgba(255, 255, 255, .1);
}
#git-graph-container .ui.buttons button#flow-color-monochrome.ui.button {
border-left-color: rgb(76, 80, 92);
border-left-style: solid;
border-left-width: 1px;
}
.mermaid-chart {
filter: invert(84%) hue-rotate(180deg);
}

View file

@ -1,15 +0,0 @@
/* This is a customized version of https://github.com/bluef/gitgraph.js/blob/master/gitgraph.css
Changes include the removal of `body` and `em` styles */
#git-graph-container, #rel-container {float:left;}
#rel-container {max-width:30%; overflow-x:auto;}
#git-graph-container {overflow-x:auto; width:100%}
#git-graph-container li {list-style-type:none;height:20px;line-height:20px; white-space:nowrap;}
#git-graph-container li .node-relation {font-family:'Bitstream Vera Sans Mono', 'Courier', monospace;}
#git-graph-container li .author {color:#666666;}
#git-graph-container li .time {color:#999999;font-size:80%}
#git-graph-container li a {color:#000000;}
#git-graph-container li a:hover {text-decoration:underline;}
#git-graph-container li a em {color:#BB0000;border-bottom:1px dotted #BBBBBB;text-decoration:none;font-style:normal;}
#rev-container {width:100%}
#rev-list {margin:0;padding:0 5px 0 5px;min-width:95%}
#graph-raw-list {margin:0px;}

View file

@ -0,0 +1 @@
<svg viewBox="0 0 768 768"><path d="M384 627V163.5l-135 135c-36 36-57 85.5-57 136.5 0 103.19 88.8 192 192 192zm181.5-373.5C666 354 666 514.5 565.5 615 516 664.5 450 690 384 690s-132-25.5-181.5-75C102 514.5 102 354 202.5 253.5L384 72z"/></svg>

After

Width:  |  Height:  |  Size: 242 B

View file

@ -0,0 +1 @@
<svg viewBox="0 0 768 768"><path d="M559.5 384c27 0 48-21 48-48s-21-48-48-48-48 21-48 48 21 48 48 48zm-96-127.5c27 0 48-21 48-48s-21-48-48-48-48 21-48 48 21 48 48 48zm-159 0c27 0 48-21 48-48s-21-48-48-48-48 21-48 48 21 48 48 48zm-96 127.5c27 0 48-21 48-48s-21-48-48-48-48 21-48 48 21 48 48 48zM384 96c159 0 288 115.5 288 256.5 0 88.5-72 159-160.5 159H456c-27 0-48 21-48 48 0 12 4.5 22.5 12 31.5s12 21 12 33c0 27-21 48-48 48-159 0-288-129-288-288S225 96 384 96z"/></svg>

After

Width:  |  Height:  |  Size: 469 B