git-changelog-tool
Version:
A comprehensive collection of tools to generate, format, and visualize changelogs from git commit history
1,274 lines (1,085 loc) • 142 kB
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Changelog JSON Parser</title>
<style>
body {
font-family: Arial, sans-serif;
font-size: 14px;
margin: 0 auto;
padding: 20px;
background-color: #f5f5f5;
}
.container {
background: white;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.main-layout {
display: flex;
gap: 20px;
align-items: flex-start;
}
.content-area {
flex: 1;
min-width: 0;
}
.sidebar {
width: 300px;
font-size: 14px;
flex-shrink: 0;
}
h1 {
color: #333;
text-align: center;
margin-bottom: 30px;
}
.file-input-section {
border: 2px dashed #ccc;
border-radius: 8px;
font-size: 14px;
padding: 40px;
text-align: center;
margin-bottom: 30px;
transition: all 0.3s ease;
position: relative;
}
.file-input-section.dragover {
border-color: #007bff;
background-color: #f8f9fa;
}
.file-input-section.collapsed {
padding: 15px 40px;
border-style: solid;
background-color: #f8f9fa;
}
.file-input-section.collapsed .file-input-content {
display: none;
}
.file-input-section.collapsed .file-input-collapsed {
display: block;
}
.file-input-collapsed {
display: none;
font-size: 14px;
color: #6c757d;
}
.loaded-file-item {
display: flex;
justify-content: space-between;
align-items: center;
background: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 4px;
padding: 8px 12px;
margin-bottom: 5px;
font-size: 0.9em;
}
.loaded-file-info {
flex: 1;
margin-right: 10px;
}
.loaded-file-name {
font-weight: bold;
color: #495057;
}
.loaded-file-details {
font-size: 0.8em;
color: #6c757d;
margin-top: 2px;
}
.remove-file-btn {
background: #dc3545;
color: white;
border: none;
padding: 4px 8px;
border-radius: 3px;
cursor: pointer;
font-size: 0.8em;
flex-shrink: 0;
}
.remove-file-btn:hover {
background: #c82333;
}
.collapse-toggle {
position: absolute;
top: 10px;
right: 15px;
background: none;
border: none;
font-size: 18px;
cursor: pointer;
color: #6c757d;
padding: 5px;
border-radius: 3px;
transition: all 0.2s ease;
}
.collapse-toggle:hover {
background-color: #e9ecef;
color: #495057;
}
.file-input {
margin: 10px;
padding: 8px 16px;
border: 1px solid #ddd;
border-radius: 4px;
cursor: pointer;
}
.changelog-info {
background: #e9ecef;
padding: 15px;
border-radius: 4px;
margin-bottom: 20px;
}
.component-section {
margin-bottom: 30px;
}
.component-title {
background: #007bff;
color: white;
padding: 10px 15px;
border-radius: 4px 4px 0 0;
font-weight: bold;
font-size: 1.1em;
}
.component-title.type-fix {
background: #dc3545;
}
.component-title.type-feat {
background: #28a745;
}
.component-title.type-style {
background: #6f42c1;
}
.component-title.type-refactor {
background: #fd7e14;
}
.component-title.type-other {
background: #6c757d;
}
.commits-table {
width: 100%;
border-collapse: collapse;
border: 1px solid #ddd;
border-top: none;
table-layout: fixed;
}
.commits-table th,
.commits-table td {
padding: 12px;
text-align: left;
border-bottom: 1px solid #ddd;
word-wrap: break-word;
overflow-wrap: break-word;
}
/* Fixed column widths - default (component grouping) */
.commits-table th:nth-child(1),
.commits-table td:nth-child(1) {
width: 120px; /* Type/Component column */
}
.commits-table th:nth-child(2),
.commits-table td:nth-child(2) {
width: auto; /* Commit message - takes remaining space */
min-width: 200px;
}
.commits-table th:nth-child(3),
.commits-table td:nth-child(3) {
width: 100px; /* Hash column */
}
.commits-table th:nth-child(4),
.commits-table td:nth-child(4) {
width: 180px; /* Date column */
}
.commits-table th:nth-child(5),
.commits-table td:nth-child(5) {
width: 150px; /* Author column */
}
.commits-table th:nth-child(6),
.commits-table td:nth-child(6) {
width: 200px; /* Branches column */
}
/* Column widths when grouping by type (with bugs column) */
.commits-table.type-grouping th:nth-child(1),
.commits-table.type-grouping td:nth-child(1) {
width: 120px; /* Component column */
}
.commits-table.type-grouping th:nth-child(2),
.commits-table.type-grouping td:nth-child(2) {
width: 150px; /* Bugs column */
}
.commits-table.type-grouping th:nth-child(3),
.commits-table.type-grouping td:nth-child(3) {
width: auto; /* Commit message - takes remaining space */
min-width: 200px;
}
.commits-table.type-grouping th:nth-child(4),
.commits-table.type-grouping td:nth-child(4) {
width: 100px; /* Hash column */
}
.commits-table.type-grouping th:nth-child(5),
.commits-table.type-grouping td:nth-child(5) {
width: 180px; /* Date column */
}
.commits-table.type-grouping th:nth-child(6),
.commits-table.type-grouping td:nth-child(6) {
width: 150px; /* Author column */
}
.commits-table.type-grouping th:nth-child(7),
.commits-table.type-grouping td:nth-child(7) {
width: 200px; /* Branches column */
}
/* Text overflow handling for specific columns - default (component grouping) */
.commits-table td:nth-child(5) {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.commits-table td:nth-child(6) {
white-space: nowrap;
overflow: hidden;
line-clamp: 2;
-webkit-line-clamp: 2;
text-overflow: ellipsis;
}
/* Commit message column styling - default */
.commits-table td:nth-child(2) {
line-height: 1.4;
}
/* Text overflow handling when grouping by type */
.commits-table.type-grouping td:nth-child(6) {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.commits-table.type-grouping td:nth-child(7) {
white-space: nowrap;
overflow: hidden;
line-clamp: 2;
-webkit-line-clamp: 2;
text-overflow: ellipsis;
}
/* Commit message column styling when grouping by type */
.commits-table.type-grouping td:nth-child(3) {
line-height: 1.4;
}
/* Bugs column styling */
.commits-table.type-grouping td:nth-child(2) {
line-height: 1.2;
overflow: hidden;
}
.commits-table th {
background-color: #f8f9fa;
font-weight: bold;
}
.commits-table tr:hover {
background-color: #f8f9fa;
}
.commit-type {
padding: 4px 8px;
border-radius: 4px;
font-size: 0.85em;
font-weight: bold;
text-transform: uppercase;
}
.commit-type.fix {
background-color: #dc3545;
color: white;
}
.commit-type.feat {
background-color: #28a745;
color: white;
}
.commit-type.style {
background-color: #6f42c1;
color: white;
}
.commit-type.refactor {
background-color: #fd7e14;
color: white;
}
.commit-type.other {
background-color: #6c757d;
color: white;
}
.commit-type.bugs {
background-color: #e74c3c;
color: white;
}
.bug-tag {
display: inline-block;
background-color: #f8f9fa;
color: #495057;
border: 1px solid #dee2e6;
border-radius: 3px;
padding: 2px 6px;
font-size: 0.7em;
margin-right: 4px;
margin-bottom: 2px;
}
.editable {
cursor: pointer;
position: relative;
}
.editable:hover {
background-color: #f8f9fa;
border-radius: 3px;
}
.editable-input {
width: 100%;
border: 2px solid #007bff;
border-radius: 3px;
padding: 4px;
font-size: inherit;
font-family: inherit;
}
.editable-textarea {
width: 100%;
min-height: 40px;
border: 2px solid #007bff;
border-radius: 3px;
padding: 4px;
font-size: inherit;
font-family: inherit;
resize: vertical;
}
.edit-buttons {
margin-top: 5px;
}
.edit-btn {
background: #007bff;
color: white;
border: none;
padding: 4px 8px;
border-radius: 3px;
cursor: pointer;
font-size: 0.8em;
margin-right: 5px;
}
.edit-btn:hover {
background: #0056b3;
}
.edit-btn.cancel {
background: #6c757d;
}
.edit-btn.cancel:hover {
background: #545b62;
}
.add-bug-btn {
background: #28a745;
color: white;
border: none;
padding: 2px 6px;
border-radius: 3px;
cursor: pointer;
font-size: 0.7em;
margin-left: 5px;
}
.add-bug-btn:hover {
background: #1e7e34;
}
.bug-tag.editable {
background-color: #e9ecef;
cursor: pointer;
}
.bug-tag.editable:hover {
background-color: #dee2e6;
}
.bug-tag.readonly {
background-color: #e9ecef;
color: #6c757d;
border-color: #ced4da;
opacity: 0.7;
}
.additional-commit-log {
margin-top: 8px;
padding: 6px 8px;
background-color: #e3f2fd;
border-left: 3px solid #2196f3;
border-radius: 3px;
font-size: 0.9em;
font-style: italic;
}
.additional-commit-log strong {
color: #1976d2;
font-weight: bold;
}
.commit-log-container {
position: relative;
}
.commit-log-preview {
max-height: 4.2em; /* Roughly 3 lines */
overflow: hidden;
line-height: 1.4;
position: relative;
}
.commit-log-preview.has-overflow::after {
content: "";
position: absolute;
bottom: 0;
right: 0;
width: 50px;
height: 1.4em;
background: linear-gradient(to right, transparent, white 50%);
pointer-events: none;
}
.commit-log-full {
line-height: 1.4;
}
.commit-log-toggle {
display: inline-block;
margin-top: 5px;
color: #007bff;
cursor: pointer;
font-size: 0.9em;
text-decoration: underline;
user-select: none;
}
.commit-log-toggle:hover {
color: #0056b3;
}
.commit-body {
margin-top: 8px;
padding-top: 8px;
border-top: 1px solid #e9ecef;
color: #6c757d;
font-size: 0.95em;
line-height: 1.4;
}
.commit-body-container {
position: relative;
}
.commit-body-preview {
max-height: 3.6em; /* Roughly 3 lines for body content */
overflow: hidden;
position: relative;
}
.commit-body-preview.has-overflow::after {
content: "";
position: absolute;
bottom: 0;
right: 0;
width: 50px;
height: 1.4em;
background: linear-gradient(to right, transparent, white 50%);
pointer-events: none;
}
.commit-body-full {
/* No height restrictions for full view */
}
.original-commit-log {
padding: 6px 8px;
background-color: #f8f9fa;
border-left: 3px solid #6c757d;
border-radius: 3px;
font-size: 0.9em;
margin-bottom: 8px;
}
.original-commit-log strong {
color: #495057;
font-weight: bold;
}
.remove-bug {
margin-left: 4px;
color: #dc3545;
cursor: pointer;
font-weight: bold;
}
.remove-bug:hover {
color: #c82333;
}
.commit-hash {
font-family: monospace;
background: #f8f9fa;
padding: 2px 6px;
border-radius: 3px;
font-size: 0.9em;
display: block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.error {
color: #dc3545;
background: #f8d7da;
padding: 10px;
border-radius: 4px;
margin: 10px 0;
}
.no-data {
text-align: center;
color: #6c757d;
font-style: italic;
padding: 40px;
}
.filters-section {
background: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 8px;
padding: 20px;
display: none;
position: sticky;
top: 20px;
overflow-y: auto;
}
.filters-section.show {
display: block;
}
.filters-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.filters-header h4 {
margin: 0;
color: #495057;
}
.clear-filters-btn {
background: #6c757d;
color: white;
border: none;
padding: 6px 12px;
border-radius: 4px;
cursor: pointer;
font-size: 0.9em;
}
.clear-filters-btn:hover {
background: #5a6268;
}
.layout-section {
border-bottom: 1px solid #dee2e6;
padding-bottom: 15px;
margin-bottom: 15px;
}
.layout-section h5 {
margin: 0 0 10px 0;
color: #495057;
font-size: 0.9em;
}
.layout-options {
display: flex;
flex-direction: column;
gap: 8px;
}
.layout-option {
display: flex;
align-items: center;
gap: 8px;
}
.layout-option input[type="radio"] {
margin: 0;
}
.layout-option label {
margin: 0;
font-weight: normal;
cursor: pointer;
font-size: 0.9em;
}
.search-section {
border-bottom: 1px solid #dee2e6;
padding-bottom: 15px;
margin-bottom: 15px;
}
.search-section h5 {
margin: 0 0 10px 0;
color: #495057;
font-size: 0.9em;
}
.search-input {
width: 100%;
padding: 8px 12px;
border: 1px solid #ced4da;
border-radius: 4px;
font-size: 0.9em;
box-sizing: border-box;
}
.search-input:focus {
outline: none;
border-color: #007bff;
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
}
.search-clear {
position: absolute;
right: 8px;
top: 50%;
transform: translateY(-50%);
background: none;
border: none;
color: #6c757d;
cursor: pointer;
font-size: 1.2em;
padding: 0;
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
}
.search-container {
position: relative;
}
.search-highlight {
background-color: yellow;
padding: 1px 2px;
border-radius: 2px;
}
.filters-grid {
display: flex;
flex-direction: column;
gap: 20px;
}
.filter-group {
display: flex;
flex-direction: column;
}
.filter-group label {
font-weight: bold;
margin-bottom: 5px;
color: #495057;
}
.filter-group select,
.filter-group input {
padding: 8px;
border: 1px solid #ced4da;
font-size: 0.9em;
}
.filter-group select[multiple] {
min-height: 80px;
}
.filter-container {
border: 1px solid #ced4da;
border-radius: 4px;
background: white;
font-size: 0.9em;
}
.filter-search {
width: 100%;
padding: 6px 8px;
border: none;
border-bottom: 1px solid #e9ecef;
font-size: 0.85em;
box-sizing: border-box;
background: #f8f9fa;
}
.filter-search:focus {
outline: none;
background: white;
border-bottom-color: #007bff;
}
.filter-search::placeholder {
color: #6c757d;
font-style: italic;
}
.filter-items {
max-height: 160px;
overflow-y: auto;
padding: 8px;
}
.filter-item {
display: flex;
align-items: center;
padding: 4px 0;
cursor: pointer;
border-radius: 3px;
transition: background-color 0.2s;
}
.filter-item:hover {
background-color: #f8f9fa;
}
.filter-item input[type="checkbox"] {
margin-right: 8px;
cursor: pointer;
}
.filter-item label {
cursor: pointer;
margin: 0;
flex: 1;
font-size: 0.9em;
word-break: break-word;
}
/* Special styling for files filter */
#filesFilter .filter-item label {
font-family: monospace;
font-size: 0.85em;
word-break: break-all;
}
.date-range-inputs {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
}
.active-filters {
margin-top: 15px;
padding-top: 15px;
border-top: 1px solid #dee2e6;
}
.active-filters h5 {
margin: 0 0 10px 0;
color: #495057;
font-size: 0.9em;
}
.filter-tags {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.filter-tag {
background: #007bff;
color: white;
padding: 4px 8px;
border-radius: 12px;
font-size: 0.8em;
display: flex;
align-items: center;
gap: 5px;
}
.filter-tag .remove {
cursor: pointer;
font-weight: bold;
}
.filter-tag .remove:hover {
color: #ffcccb;
}
.results-summary {
background: #e3f2fd;
padding: 10px 15px;
border-radius: 4px;
margin-bottom: 20px;
font-size: 0.9em;
color: #1565c0;
}
/* Responsive design */
@media (max-width: 768px) {
.main-layout {
flex-direction: column;
}
.sidebar {
width: 100%;
order: -1;
}
.filters-section {
position: static;
max-height: none;
margin-bottom: 20px;
}
.filters-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
}
}
</style>
</head>
<body>
<div class="container">
<h1>Changelog JSON Parser</h1>
<div class="file-input-section" id="dropZone">
<button class="collapse-toggle" id="collapseToggle" title="Collapse file input" style="display: none">−</button>
<div class="file-input-content">
<p>Drag and drop JSON files here or select files:</p>
<input type="file" id="fileInput" class="file-input" accept=".json" multiple />
<p><small>Supports multiple changelog JSON files. Commits will be merged by hash.</small></p>
<!-- Loaded Files List -->
<div id="loadedFilesList" style="display: none; margin-top: 15px; border-top: 1px solid #ddd; padding-top: 15px">
<h4 style="margin: 0 0 10px 0; color: #495057; font-size: 0.9em">Loaded Files:</h4>
<div id="loadedFilesContainer"></div>
<div style="margin-top: 10px; text-align: center">
<button id="resetDataBtn" style="background: #dc3545; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; font-size: 0.9em">Reset All Data</button>
</div>
</div>
</div>
<div class="file-input-collapsed">
<span id="loadedFileName">No file loaded</span>
•
<button style="background: none; border: none; color: #007bff; cursor: pointer; text-decoration: underline" onclick="document.getElementById('fileInput').click()">Load new file</button>
</div>
</div>
<div id="error" class="error" style="display: none"></div>
<div id="content" style="display: none">
<div id="changelogInfo" class="changelog-info"></div>
<div class="main-layout">
<div class="content-area">
<div id="resultsSummary" class="results-summary" style="display: none"></div>
<div id="componentsContainer" style="height: 100%; overflow-y: auto"></div>
</div>
<div class="sidebar">
<div id="filtersSection" class="filters-section">
<div class="filters-header">
<h4>Controls</h4>
<div>
<div style="display: flex; flex-direction: column; gap: 5px; margin-right: 10px">
<button id="exportJsonBtn" class="clear-filters-btn" style="background: #28a745">Export All</button>
<button id="exportModificationsBtn" class="clear-filters-btn" style="background: #17a2b8; font-size: 0.8em">Export Modifications Only</button>
</div>
</div>
</div>
<div class="search-section">
<h5>Search:</h5>
<div class="search-container">
<input type="text" id="searchInput" class="search-input" placeholder="Search commits, authors, components..." />
<button id="searchClear" class="search-clear" style="display: none" title="Clear search">×</button>
</div>
</div>
<div class="layout-section">
<h5>Group By:</h5>
<div class="layout-options">
<div class="layout-option">
<input type="radio" id="groupByComponent" name="groupBy" value="component" checked />
<label for="groupByComponent">Component</label>
</div>
<div class="layout-option">
<input type="radio" id="groupByType" name="groupBy" value="type" />
<label for="groupByType">Commit Type</label>
</div>
</div>
</div>
<div class="filters-grid">
<div class="filter-group">
<label>Filters:</label>
</div>
<div id="activeFilters" class="active-filters" style="display: none">
<div style="margin-bottom: 10px; display: flex; justify-content: space-between; align-items: center">
<h5>Active Filters:</h5>
<button id="clearFiltersBtn" class="clear-filters-btn">Clear All</button>
</div>
<div id="filterTags" class="filter-tags"></div>
</div>
<div class="filter-group">
<label>Date Range:</label>
<div class="date-range-inputs">
<input type="date" id="dateFromFilter" placeholder="From" />
<input type="date" id="dateToFilter" placeholder="To" />
</div>
</div>
<div class="filter-group">
<label for="typeFilter">Commit Types:</label>
<div id="typeFilter" class="filter-container">
<!-- Type checkboxes will be populated dynamically -->
</div>
</div>
<div class="filter-group">
<label for="authorFilter">Authors:</label>
<div id="authorFilter" class="filter-container">
<!-- Author checkboxes will be populated dynamically -->
</div>
</div>
<div class="filter-group">
<label for="branchFilter">Include Branches:</label>
<div id="branchFilter" class="filter-container">
<!-- Branch checkboxes will be populated dynamically -->
</div>
</div>
<div class="filter-group">
<label for="excludeBranchFilter">Exclude Branches:</label>
<div id="excludeBranchFilter" class="filter-container">
<!-- Branch checkboxes will be populated dynamically -->
</div>
</div>
<div class="filter-group">
<label for="bugsFilter">Bug Types:</label>
<div id="bugsFilter" class="filter-container">
<!-- Bug type checkboxes will be populated dynamically -->
</div>
</div>
<div class="filter-group">
<label for="filesFilter">Source Files:</label>
<div id="filesFilter" class="filter-container">
<!-- File checkboxes will be populated dynamically -->
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
class ChangelogParser {
constructor() {
this.fileInput = document.getElementById("fileInput");
this.dropZone = document.getElementById("dropZone");
this.errorDiv = document.getElementById("error");
this.contentDiv = document.getElementById("content");
this.changelogInfoDiv = document.getElementById("changelogInfo");
this.componentsContainer = document.getElementById("componentsContainer");
// Filter elements
this.filtersSection = document.getElementById("filtersSection");
this.typeFilter = document.getElementById("typeFilter");
this.authorFilter = document.getElementById("authorFilter");
this.branchFilter = document.getElementById("branchFilter");
this.excludeBranchFilter = document.getElementById("excludeBranchFilter");
this.bugsFilter = document.getElementById("bugsFilter");
this.filesFilter = document.getElementById("filesFilter");
this.dateFromFilter = document.getElementById("dateFromFilter");
this.dateToFilter = document.getElementById("dateToFilter");
this.clearFiltersBtn = document.getElementById("clearFiltersBtn");
this.exportJsonBtn = document.getElementById("exportJsonBtn");
this.exportModificationsBtn = document.getElementById("exportModificationsBtn");
this.resetDataBtn = document.getElementById("resetDataBtn");
this.activeFiltersDiv = document.getElementById("activeFilters");
this.filterTagsDiv = document.getElementById("filterTags");
this.resultsSummaryDiv = document.getElementById("resultsSummary");
// Layout elements
this.groupByComponent = document.getElementById("groupByComponent");
this.groupByType = document.getElementById("groupByType");
// Search elements
this.searchInput = document.getElementById("searchInput");
this.searchClear = document.getElementById("searchClear");
// Collapse elements
this.collapseToggle = document.getElementById("collapseToggle");
this.loadedFileName = document.getElementById("loadedFileName");
// Loaded files elements
this.loadedFilesList = document.getElementById("loadedFilesList");
this.loadedFilesContainer = document.getElementById("loadedFilesContainer");
// Data storage
this.originalData = null;
this.allCommits = [];
this.loadedFiles = [];
this.commitMap = new Map(); // For merging commits by hash
this.currentFilters = {
types: [],
authors: [],
branches: [],
excludeBranches: [],
bugs: [],
files: [],
dateFrom: null,
dateTo: null,
};
this.currentGroupBy = "component";
this.currentSearchTerm = "";
this.initializeEventListeners();
}
initializeEventListeners() {
// File input change
this.fileInput.addEventListener("change", (e) => {
if (e.target.files.length > 0) {
this.handleMultipleFiles(Array.from(e.target.files));
}
});
// Drag and drop events
this.dropZone.addEventListener("dragover", (e) => {
e.preventDefault();
this.dropZone.classList.add("dragover");
});
this.dropZone.addEventListener("dragleave", (e) => {
e.preventDefault();
this.dropZone.classList.remove("dragover");
});
this.dropZone.addEventListener("drop", (e) => {
e.preventDefault();
this.dropZone.classList.remove("dragover");
const files = Array.from(e.dataTransfer.files);
if (files.length > 0) {
this.handleMultipleFiles(files);
}
});
// Filter event listeners are now handled in populateFilter method
this.dateFromFilter.addEventListener("change", () => this.handleFilterChange());
this.dateToFilter.addEventListener("change", () => this.handleFilterChange());
this.clearFiltersBtn.addEventListener("click", () => this.clearAllFilters());
this.exportJsonBtn.addEventListener("click", () => this.exportModifiedJson());
this.exportModificationsBtn.addEventListener("click", () => this.exportModificationsOnly());
// Layout event listeners
this.groupByComponent.addEventListener("change", () => this.handleLayoutChange());
this.groupByType.addEventListener("change", () => this.handleLayoutChange());
// Search event listeners
this.searchInput.addEventListener("input", () => this.handleSearchChange());
this.searchInput.addEventListener("keyup", (e) => {
if (e.key === "Escape") {
this.clearSearch();
}
});
this.searchClear.addEventListener("click", () => this.clearSearch());
// Collapse event listener
this.collapseToggle.addEventListener("click", () => this.toggleFileInputCollapse());
}
handleMultipleFiles(files) {
// Don't reset data - add to existing data instead
// this.loadedFiles = []; // Keep existing files
// this.commitMap.clear(); // Keep existing commits
// this.allCommits = []; // Keep existing commits
// Filter for JSON files only
const jsonFiles = files.filter((file) => file.name.toLowerCase().endsWith(".json"));
if (jsonFiles.length === 0) {
this.showError("Please select at least one JSON file.");
return;
}
if (jsonFiles.length !== files.length) {
console.warn(`${files.length - jsonFiles.length} non-JSON files were ignored.`);
}
this.processFiles(jsonFiles);
}
async processFiles(files) {
let processedCount = 0;
let modificationsCount = 0;
let hasErrors = false;
const errors = [];
for (const file of files) {
try {
const fileData = await this.readFile(file);
const jsonData = JSON.parse(fileData);
if (jsonData.type === "changelog_modifications") {
// This is a modifications file
this.storeModificationsFile(jsonData, file.name);
modificationsCount++;
} else {
// This is a regular changelog file
this.mergeFileData(jsonData, file.name);
processedCount++;
}
} catch (error) {
hasErrors = true;
errors.push(`${file.name}: ${error.message}`);
}
}
// Allow loading of modifications files even without changelog files
if (processedCount === 0 && modificationsCount === 0) {
this.showError(`No valid files could be processed:\n${errors.join("\n")}`);
return;
}
if (hasErrors) {
console.warn("Some files had errors:", errors);
}
// Only finalize data if we have actual changelog data
if (processedCount > 0) {
this.finalizeData();
} else if (modificationsCount > 0) {
// Check if we already have changelog data and just added modifications
if (this.allCommits && this.allCommits.length > 0) {
// We already have data, just update the loaded files list
this.updateLoadedFilesList();
} else {
// Only modifications loaded - update the UI to show pending modifications
this.updateLoadedFilesList();
this.showPendingModificationsInfo();
}
}
// Show loading feedback
const loadingMsg = document.createElement("div");
loadingMsg.style.cssText = `
position: fixed; top: 10px; left: 50%; transform: translateX(-50%);
background: #28a745; color: white; padding: 10px 20px;
border-radius: 4px; z-index: 1001; font-size: 0.9em;
`;
if (processedCount > 0 && modificationsCount > 0) {
loadingMsg.textContent = `✅ Added ${processedCount} changelog files + ${modificationsCount} modification files (auto-applying...)`;
} else if (processedCount > 0) {
const hasPending = this.pendingModifications && this.pendingModifications.modifications && this.pendingModifications.modifications.length > 0;
if (hasPending) {
loadingMsg.textContent = `✅ Added ${processedCount} changelog files (auto-applying pending modifications...)`;
} else {
loadingMsg.textContent = `✅ Added ${processedCount} changelog files`;
}
} else if (modificationsCount > 0) {
// Check if we already have changelog data loaded
if (this.allCommits && this.allCommits.length > 0) {
loadingMsg.textContent = `✅ Added ${modificationsCount} modification files (auto-applying...)`;
} else {
loadingMsg.textContent = `✅ Added ${modificationsCount} modification files (waiting for changelog data)`;
}
}
if (processedCount > 0 || modificationsCount > 0) {