From 8f64ed8c78df2316b641fba418322c29b88ade45 Mon Sep 17 00:00:00 2001 From: HampusM Date: Thu, 20 May 2021 15:57:06 +0200 Subject: Added commit-page --- package-lock.json | 5 +- package.json | 1 + src/js/app.js | 323 ++++++++++++++++++++++++++++++++++++++-- src/scss/abstracts/_colors.scss | 2 + src/scss/style.scss | 122 ++++++++++++++- 5 files changed, 433 insertions(+), 20 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8e3b71a..6844b84 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "bootstrap": "^5.0.0-beta3", "date-fns": "^2.21.3", "express": "^5.0.0-alpha.8", + "highlight.js": "^10.4.1", "js-yaml": "^4.1.0", "nodegit": "^0.27.0" }, @@ -7051,7 +7052,6 @@ "version": "10.4.1", "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.4.1.tgz", "integrity": "sha512-yR5lWvNz7c85OhVAEAeFhVCc/GV4C30Fjzc/rCP0aCWzc1UUOPUk55dK/qdwTZHBvMZo+eZ2jpk62ndX/xMFlg==", - "dev": true, "engines": { "node": "*" } @@ -21447,8 +21447,7 @@ "highlight.js": { "version": "10.4.1", "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.4.1.tgz", - "integrity": "sha512-yR5lWvNz7c85OhVAEAeFhVCc/GV4C30Fjzc/rCP0aCWzc1UUOPUk55dK/qdwTZHBvMZo+eZ2jpk62ndX/xMFlg==", - "dev": true + "integrity": "sha512-yR5lWvNz7c85OhVAEAeFhVCc/GV4C30Fjzc/rCP0aCWzc1UUOPUk55dK/qdwTZHBvMZo+eZ2jpk62ndX/xMFlg==" }, "hmac-drbg": { "version": "1.0.1", diff --git a/package.json b/package.json index 2d788a0..5c50a05 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "bootstrap": "^5.0.0-beta3", "date-fns": "^2.21.3", "express": "^5.0.0-alpha.8", + "highlight.js": "^10.4.1", "js-yaml": "^4.1.0", "nodegit": "^0.27.0" }, diff --git a/src/js/app.js b/src/js/app.js index 3c3d7b9..a853ae5 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -1,4 +1,5 @@ import { format } from "date-fns"; +import hljs from 'highlight.js'; function request(method, source, data = null) { @@ -160,10 +161,10 @@ async function buildProjects(container) link.appendChild(document.createTextNode(key)); repo_title.appendChild(link); - const repo_last_updated = createElement("span", null, ["repo-last-updated", "fs-4"]); + const repo_last_updated = createElement("span", null, ["repo-last-updated", "fs-5"]); repo_last_updated.appendChild(document.createTextNode(`Last updated about ${value["last_updated"]} ago`)); - const repo_desc = createElement("span", null, ["fs-4"]); + const repo_desc = createElement("span", null, ["fs-5"]); repo_desc.appendChild(document.createTextNode(value["description"])); repo_div.appendChild(repo_title) @@ -202,7 +203,7 @@ function buildRepoNavbar(container, repo, page) nav_items.forEach(item => { const item_li = createElement("li", null, ["nav-item"]); - const item_link = createElement("a", null, ["nav-link", "fs-3"], { "href": `/${repo}/${item}` }); + const item_link = createElement("a", null, ["nav-link", "fs-4"], { "href": `/${repo}/${item}` }); if(item === page) { item_link.classList.add("active"); @@ -229,7 +230,7 @@ async function buildLog(container, repo) const row_div = createElement("div", null, ["row", "mx-0"], null); const col_div = createElement("div", null, ["col", "ms-4", "ps-4", "ps-sm-5", "mt-3"], null); - const table = createElement("table", "log", ["table", "table-dark", "fs-4"]); + const table = createElement("table", "log", ["table", "table-dark", "fs-5"]); const log = JSON.parse(await request("GET", `http://localhost:1337/api/v1/repos/${repo}/log`))["data"]; @@ -251,14 +252,16 @@ async function buildLog(container, repo) log.forEach(commit => { const tr = createElement("tr"); - const subject = createElement("td"); - subject.appendChild(document.createTextNode(commit["subject"])); + const message = createElement("td"); + const message_link = createElement("a", null, null, { "href": `log/${commit["commit"]}` }); + message_link.appendChild(document.createTextNode(commit["message"])); + message.appendChild(message_link); const author = createElement("td"); - author.appendChild(document.createTextNode(commit["author"])); + author.appendChild(document.createTextNode(commit["author_name"])); const date = createElement("td"); - date.appendChild(document.createTextNode(format(new Date(commit["date"] * 1000), "yyyy-MM-dd hh:mm"))); + date.appendChild(document.createTextNode(format(new Date(commit["date"]), "yyyy-MM-dd hh:mm"))); const files_changed = createElement("td"); files_changed.appendChild(document.createTextNode(commit["files_changed"])); @@ -275,7 +278,7 @@ async function buildLog(container, repo) del_add.appendChild(document.createTextNode(" / ")) del_add.appendChild(insertions); - tr.appendChild(subject); + tr.appendChild(message); tr.appendChild(author); tr.appendChild(date); tr.appendChild(files_changed); @@ -290,6 +293,286 @@ async function buildLog(container, repo) container.appendChild(row_div); } +const languages = [ + { "name": "arduino", "extensions": [".ino"]}, + { "name": "actionscript", "extensions": [".as"]}, + { "name": "bash", "extensions": [".sh", ".zsh"]}, + { "name": "csharp", "extensions": [".cs"]}, + { "name": "c", "extensions": [".c", ".h"]}, + { "name": "cpp", "extensions": [".cpp", ".hpp"]}, + { "name": "cmake", "extensions": ["cmake.in"]}, + { "name": "css", "extensions": [".css"]}, + { "name": "d", "extensions": [".d"]}, + { "name": "dos", "extensions": [".bat", ".cmd"]}, + { "name": "dockerfile", "extensions": ["dockerfile", "Dockerfile"]}, + { "name": "go", "extensions": [".go"]}, + { "name": "gradle", "extensions": [".gradle"]}, + { "name": "xml", "extensions": [".xml", ".html", ".xhtml", ".rss", ".atom", ".xjb", ".xsd", ".xsl", ".plist", ".svg"]}, + { "name": "haskell", "extensions": [".hs"]}, + { "name": "ini", "extensions": [".ini", ".toml"]}, + { "name": "json", "extensions": [".json"]}, + { "name": "java", "extensions": [".java", ".jsp"]}, + { "name": "javascript", "extensions": [".js", ".jsx"]}, + { "name": "kotlin", "extensions": [".kt"]}, + { "name": "lua", "extensions": [".lua"]}, + { "name": "makefile", "extensions": ["makefile", "Makefile"]}, + { "name": "markdown", "extensions": [".md"]}, + { "name": "objectivec", "extensions": [".m", ".mm", ".M"]}, + { "name": "php", "extensions": [".php"]}, + { "name": "perl", "extensions": [".pl", ".pm"]}, + { "name": "plaintext", "extensions": [".txt"]}, + { "name": "pgsql", "extensions": [".pgsql"]}, + { "name": "powershell", "extensions": [".ps", ".ps1"]}, + { "name": "python", "extensions": [".py"]}, + { "name": "ruby", "extensions": [".rb"]}, + { "name": "rust", "extensions": [".rs"]}, + { "name": "scss", "extensions": [".scss"]}, + { "name": "sql", "extensions": [".sql"]}, + { "name": "swift", "extensions": [".swift"]}, + { "name": "typescript", "extensions": [".ts"]}, + { "name": "vbnet", "extensions": [".vb"]}, + { "name": "vba", "extensions": [".vba"]}, + { "name": "vbscript", "extensions": [".vbs"]}, + { "name": "vim", "extensions": [".vim"]}, + { "name": "yml", "extensions": [".yml"]} +]; + +async function buildCommit(container, repo, hash) +{ + const row_div = createElement("div", null, ["row", "mx-0"], null); + const col_div = createElement("div", null, ["col", "ms-2", "ps-4", "ps-sm-5", "fs-5"], null); + + const breadcrumb = createElement("nav", null, null, { "aria-label": "breadcrumb" }); + const breadcrumb_ol = createElement("ol", null, ["breadcrumb"]); + const breadcrumb_item_log = createElement("li", null, ["breadcrumb-item"], { "aria-current": "page" }); + const breadcrumb_item_log_link = createElement("a", null, null, { "href": ".." }); + breadcrumb_item_log_link.appendChild(document.createTextNode("Log")); + breadcrumb_item_log.appendChild(breadcrumb_item_log_link); + const breadcrumb_item_commit = createElement("li", null, ["breadcrumb-item", "active"], { "aria-current": "page" }); + breadcrumb_item_commit.appendChild(document.createTextNode(hash)); + + breadcrumb_ol.appendChild(breadcrumb_item_log); + breadcrumb_ol.appendChild(breadcrumb_item_commit); + breadcrumb.appendChild(breadcrumb_ol); + col_div.appendChild(breadcrumb); + + const commit = JSON.parse(await request("GET", `http://localhost:1337/api/v1/repos/${repo}/log/${hash}`)); + + const commit_info = createElement("table", "commit-info", ["table", "table-dark"]); + const tbody = createElement("tbody"); + + ["author", "date", "message"].forEach((subject) => + { + const info = createElement("tr"); + const title = createElement("td", null, ["commit-info-title"]); + title.appendChild(document.createTextNode(subject.charAt(0).toUpperCase() + subject.slice(1))); + const content = createElement("td", null); + if(subject === "date") { + content.appendChild(document.createTextNode(format(new Date(commit["data"]["date"]), "yyyy-MM-dd hh:mm"))); + } + else { + content.appendChild(document.createTextNode(commit["data"][subject])); + } + info.appendChild(title); + info.appendChild(content); + tbody.appendChild(info); + }); + + commit_info.appendChild(tbody); + col_div.appendChild(commit_info); + + commit["data"]["patches"].forEach((patch) => + { + const file_div = createElement("div", null, ["commit-file"]); + + // Header + const file_header = createElement("div", null, ["commit-file-header"]); + const file_name = createElement("span", null, ["fw-bold"]); + const file_deleted = createElement("span"); + + if(patch["to"] === "/dev/null") { + file_name.appendChild(document.createTextNode(patch["from"])); + file_deleted.appendChild(document.createTextNode("Deleted")); + } + else { + file_name.appendChild(document.createTextNode(patch["to"])); + file_deleted.appendChild(document.createTextNode("")); + } + file_header.appendChild(file_name); + file_header.appendChild(file_deleted); + + const file_add_del = createElement("div", null, ["commit-file-add-del"]); + const file_add = createElement("span"); + const file_del = createElement("span"); + + file_add.appendChild(document.createTextNode(`+${patch["additions"]}`)); + file_add_del.appendChild(file_add); + + file_del.appendChild(document.createTextNode(`-${patch["deletions"]}`)); + file_add_del.appendChild(file_del); + + file_header.appendChild(file_add_del); + file_div.appendChild(file_header); + + console.log(patch); + + // The diff + if(patch["too_large"] === false) { + let full_patch = ""; + + patch["hunks"].forEach((hunk) => + { + full_patch = `${full_patch}${hunk["hunk"]}\n`; + }); + + const patch_table = createElement("table", null, null, { "cellspacing": "0px" }); + const patch_tbody = createElement("tbody"); + + const language = languages.find((lang) => lang["extensions"].some((extension) => patch["to"].endsWith(extension))); + + const highlighted = language ? hljs.highlight(language["name"], full_patch) : hljs.highlightAuto(full_patch); + const highlighted_patch = highlighted["value"].split('\n'); + + let index = 0; + patch["hunks"].forEach((hunk) => + { + const hunk_length = hunk["hunk"].split('\n').length; + const end = index + hunk_length; + + const unhighlighted_hunk = hunk["hunk"].split('\n'); + hunk["hunk"] = highlighted_patch.slice(index, end); + + index = end; + + let new_offset = 0; + let deleted_offset = 0; + const multiline_tags = []; + hunk["hunk"].forEach((line, line_index) => + { + //console.log(line_index + " " + line); + const line_tr = createElement("tr"); + + const old_line_num = createElement("td"); + const line_num = createElement("td"); + const line_change = createElement("td"); + + const line_content = createElement("td"); + const line_code = createElement("code"); + + if(/^@@\ -[0-9,]+\ \+[0-9,]+\ @@/.test(unhighlighted_hunk[line_index])) { + line_tr.classList.add("commit-file-pos-change"); + + for(let i = 0; i < 3; i++) { + const triple_dot = createElement("td"); + triple_dot.appendChild(document.createTextNode("...")); + line_tr.appendChild(triple_dot); + } + + line_code.innerHTML = unhighlighted_hunk[line_index]; + line_content.appendChild(line_code) + line_tr.appendChild(line_content) + + new_offset++; + deleted_offset++; + } + else if(/^\\\ No\ newline\ at\ end\ of\ file$/.test(unhighlighted_hunk[line_index])) { + line_tr.classList.add("commit-file-no-newline"); + + for(let i = 0; i < 3; i++) { + const empty = createElement("td"); + empty.appendChild(document.createTextNode("")); + line_tr.appendChild(empty); + } + + line_code.innerHTML = unhighlighted_hunk[line_index]; + line_content.appendChild(line_code) + line_tr.appendChild(line_content) + + new_offset++; + deleted_offset++; + } + else { + if(hunk["new"].includes(line_index)) { + deleted_offset++; + line_num.appendChild(document.createTextNode(Number(hunk["new_start"]) + line_index - new_offset)); + line_num.classList.add("line-highlight-new"); + + line_change.appendChild(document.createTextNode("+")); + line_change.classList.add("line-new"); + } + else if(hunk["deleted"].includes(line_index)) { + new_offset++; + old_line_num.appendChild(document.createTextNode(Number(hunk["old_start"]) + line_index - deleted_offset)); + line_num.classList.add("line-highlight-deleted"); + line_change.appendChild(document.createTextNode("-")); + line_change.classList.add("line-deleted"); + + } + else { + old_line_num.appendChild(document.createTextNode(Number(hunk["old_start"]) + line_index - deleted_offset)); + old_line_num.classList.add("line-unchanged"); + line_num.appendChild(document.createTextNode(Number(hunk["new_start"]) + line_index - new_offset)); + line_num.classList.add("line-unchanged"); + + line_change.appendChild(document.createTextNode(" ")); + } + + line_tr.appendChild(old_line_num); + line_tr.appendChild(line_num); + line_tr.appendChild(line_change); + + let comment_open = line.match(//g); + const comment_open_cnt = (comment_open !== null) ? comment_open.length : 0; + comment_open = (comment_open !== null) ? comment_open[0] : ""; + + let comment_close = line.match(/<\/span>/g); + const comment_close_cnt = (comment_close !== null) ? comment_close.length : 0; + comment_close = (comment_close !== null) ? comment_close[0] : ""; + + if(comment_open_cnt > comment_close_cnt) { + line_code.innerHTML = line + ""; + console.log("Öppning " + line); + multiline_tags.push(comment_open); + } + else if(comment_open_cnt < comment_close_cnt && multiline_tags.length !== 0) { + line_code.innerHTML = multiline_tags[multiline_tags.length - 1] + line; + console.log("Stängning " + line + " " + multiline_tags[multiline_tags.length - 1]); + multiline_tags.pop(); + } + else if(multiline_tags.length !== 0) { + line_code.innerHTML = multiline_tags[multiline_tags.length - 1] + line + ""; + console.log("Mitt i " + line); + } + else { + line_code.innerHTML = line; + } + + line_content.appendChild(line_code); + line_tr.appendChild(line_content) + } + + patch_tbody.appendChild(line_tr); + }); + }) + + patch_table.appendChild(patch_tbody); + file_div.appendChild(patch_table); + } + else { + const patch_too_large_div = createElement("div", null, ["ps-3", "pt-3", "patch-too-large"]); + const patch_too_large = createElement("span"); + patch_too_large.appendChild(document.createTextNode("Patch is too large to display.")); + patch_too_large_div.appendChild(patch_too_large); + file_div.appendChild(patch_too_large_div); + } + + col_div.appendChild(file_div); + }); + + row_div.appendChild(col_div); + container.appendChild(row_div); +} + function createBackButtonSVG() { const xmlns = "http://www.w3.org/2000/svg"; @@ -325,16 +608,28 @@ document.addEventListener("DOMContentLoaded", async function () await buildHeader(container, "info", "title", "about"); buildProjectsHeader(container); buildProjects(container); + return } - const path_valid_and_split = /\/([a-zA-Z0-9\.\-_]+)\/([a-z]+)$/; + const path_valid_and_split = /^\/([a-zA-Z0-9\.\-_]+)\/([a-z]+)(?:\/([0-9a-f]+))?$/; + if(path_valid_and_split.test(path)) { path = path_valid_and_split.exec(path); const repo = path[1]; const page = path[2]; - - await buildHeader(container, `repos/${repo}`, "name", "description", true); - buildRepoNavbar(container, repo, page); - buildLog(container, repo); + const sub_page = path[3]; + + console.log("Tjena!"); + + if(page === "log") { + await buildHeader(container, `repos/${repo}`, "name", "description", true); + buildRepoNavbar(container, repo, page); + + if(sub_page) { + buildCommit(container, repo, sub_page); + return; + } + buildLog(container, repo); + } } }); \ No newline at end of file diff --git a/src/scss/abstracts/_colors.scss b/src/scss/abstracts/_colors.scss index f49c464..d7c43f5 100644 --- a/src/scss/abstracts/_colors.scss +++ b/src/scss/abstracts/_colors.scss @@ -2,6 +2,8 @@ $primary: #023E8A; $primary-light: #0096C7; $secondary: #F48C06; $success: #40916C; +$new: #06d6a0; $danger: #D00000; $text: #ffffff; +$text-gray: #6c757d; $background: #121212; \ No newline at end of file diff --git a/src/scss/style.scss b/src/scss/style.scss index d45ad56..89ea349 100644 --- a/src/scss/style.scss +++ b/src/scss/style.scss @@ -21,7 +21,7 @@ $font-size-base: 0.75rem; $h1-font-size: $font-size-base * 2.5; $h2-font-size: $font-size-base * 2; $h3-font-size: $font-size-base * 1.75; -$h4-font-size: $font-size-base * 1.4; +$h4-font-size: $font-size-base * 1.5; $h5-font-size: $font-size-base * 1.125; $h6-font-size: $font-size-base; @@ -36,6 +36,7 @@ $font-sizes: ( $navbar-nav-link-padding-x: 0.5rem; +@import "../../node_modules/bootstrap/scss/breadcrumb"; @import "../../node_modules/bootstrap/scss/utilities"; @import "../../node_modules/bootstrap/scss/utilities/api"; @import "../../node_modules/bootstrap/scss/nav"; @@ -60,10 +61,13 @@ $table-variants: ( @import "../../node_modules/bootstrap/scss/containers"; @import "../../node_modules/bootstrap/scss/grid"; +@import "../../node_modules/highlight.js/scss/srcery.scss"; + body { background-color: colors.$background; color: colors.$text; font-family: fonts.$primary; + margin: 0px; } ul { @@ -134,6 +138,95 @@ a { } } +.breadcrumb { + li { + margin-bottom: 0.5rem; + } +} + +#commit-info { + margin-bottom: 2rem; + tbody tr { + td { + padding: 0px; + padding-right: 10px; + } + } +} + +.commit-file { + margin-bottom: 50px; + table { + padding-top: 15px; + tbody tr td { + padding: 0px; + padding-left: 8px; + vertical-align: top; + &:nth-child(2) { + padding-right: 7px; + } + &:nth-child(3) { + padding-right: 15px; + } + } + } +} + +.commit-file-add-del { + margin-left: auto; + margin-right: 23px; + span { + margin-right: 10px !important; + font-weight: 700; + &:nth-child(1) { + color: colors.$new; + } + } +} + +.commit-file-pos-change { + color: colors.$text-gray; +} + +.commit-file-no-newline { + color: colors.$text-gray; +} + +.line-new { + color: colors.$new; +} +.line-deleted { + color: colors.$danger; +} + +.line-unchanged { + color: colors.$text-gray; +} + +.line-highlight-new { + border-right: 1px solid colors.$new; +} +.line-highlight-deleted { + border-right: 1px solid colors.$danger; +} + +code { + white-space: pre-wrap; + word-wrap: anywhere; +} + +.commit-file-header { + display: flex; + background-color: rgba($color: #ffffff, $alpha: 0.08); + padding: 10px; + span { + margin-right: 30px; + &:nth-child(2) { + color: colors.$danger; + } + } +} + #back:hover { fill: colors.$primary-light; } @@ -146,9 +239,29 @@ th { text-align: start; } +.commit-info-title { + color: colors.$secondary; + padding-right: 30px; + width: 20px; +} + +.patch-too-large { + font-weight: 600; +} + + @include media-breakpoint-down(sm) { + .commit-file table tbody tr td { + padding-left: 4px; + &:nth-child(2) { + padding-right: 4px; + } + &:nth-child(3) { + padding-right: 5px; + } + } .table > :not(caption) > * > * { - padding: 0.5rem; + padding: 0.1rem; } } @@ -163,6 +276,9 @@ th { font-size: calc(1.3rem + 0.017vw) !important; } .fs-4 { - font-size: calc(0.75rem + 0.4vw) !important; + font-size: calc(0.82rem + 0.4vw) !important; + } + .fs-5 { + font-size: calc(0.65rem + 0.25vw) !important; } } \ No newline at end of file -- cgit v1.2.3-18-g5258