verlog
Version:
Generate Version and Changelog for git repo
361 lines (352 loc) • 10.1 kB
HTML
<title>{{TITLE}}</title>
<style>
body {
word-break: break-all;
overflow-wrap: break-word;
}
</style>
<div style="display: flex;">
<div style="flex:3; position: relative;" id="left"></div>
<div style="flex:7; position: relative; font-size: 14px;">{{DIFF}}</div>
</div>
<div id="footer" style="position: relative; display: flex;">
<button onclick="fetch('/add').then(r=>r.json()).then(data=>{
data.ok && location.reload()
data.message && alert(data.message)
})">add</button>
<input id="additional-message" placeholder="additional message">
<button onclick="submit()">commit</button>
<label>amend? <input type="checkbox" onclick="submitArgs = (this.checked ? ['--amend'] : [])"></label>
</div>
<dialog id="result"></dialog>
<dialog id="error"></dialog>
<template id="input-template"><style>
:host {
font-size: 12px;
height: 100%;
min-height: 100px;
display: flex;
flex-direction: column;
}
input {
width: 100%;
border: 1px solid #eee;
line-height: 1.5em;
color: #0048ff;
}
select {
width: 70px;
}
span {
padding: 0 10px;
}
.item {
cursor: pointer;
}
.title {
display: flex;
}
</style>
<div class="title">
<span>1</span>
<select tabindex="-1">
<option value="">none</option>
<option value="fix">fix</option>
<option value="feat">feat</option>
<option value="chore">chore</option>
<option value="docs">docs</option>
<option value="style">style</option>
<option value="refactor">refactor</option>
<option value="perf">perf</option>
<option value="test">test</option>
<option value="BREAKING CHANGE">BREAKING CHANGE</option>
</select>
<input>
</div>
<div></div></template>
<script>
const dialog = document.getElementById('result')
const allBlockNodes = []
const store = {
lastInput: '',
inputs: new Set()
}
function addToArray(arr, value) {
if(arr.indexOf(value) < 0) {
arr.push(value)
}
return arr.length
}
function arrayUnique(arr){
return Array.from(new Set(arr))
}
var source = new EventSource("/events/")
source.onerror = function(e) {
source.close()
document.title = 'OFFLINE:' + document.title
const errDialog = document.getElementById('error')
errDialog.close()
errDialog.innerHTML = `<h4>OFFLINE? You can close this window now.</h4>`
errDialog.showModal()
}
var submitArgs = []
function getAllMessage (){
const additionalMessage = document.getElementById('additional-message').value
const messageList = [...document.querySelectorAll('commit-input')].map(input=>input.getValue())
const values = arrayUnique([additionalMessage].concat(messageList).filter(Boolean))
return {additionalMessage, messageList, values}
}
function submit(){
const {values} = getAllMessage()
console.log(values)
if(values.length && !confirm('Confirm commit message:\n'+ values.join('\n'))) {
return
}
if(dialog.open) dialog.close()
fetch('/commit', {
body: JSON.stringify({
commits: values,
args: submitArgs
}),
method: 'POST',
headers:{
'Content-Type':'application/json',
Accept: 'application/json'
}
}).then(r=>r.json()).then(ret=>{
const command = ret.command ? `<pre>$ ${ret.command}</pre>` : ''
const actions = ret.ok
? `<button onclick="window.location.reload()">Reload Page</button>`
: `<button onclick="dialog.close()">Close Dialog</button>`
dialog.innerHTML = `${ret.ok ? 'OK' : 'Fail'}${command}<pre>${ret.message || ret.all}</pre>${actions}`
dialog.showModal()
}).catch(err=>{
dialog.innerHTML = err.message + `<div><button onclick="dialog.close()">Close Dialog</button></div>`
dialog.showModal()
})
}
function parseCommitMessage (message) {
let value = message
let prefix = ''
const match = /^(.*?): (.*)$/.exec(message)
if(match) {
value = match[2]
prefix = match[1]
}
return {value, prefix}
}
function setInputValue(item){
const root = item.getRootNode().host
const valueToSet = item.textContent
addToArray(root.history.values, valueToSet)
const {value, prefix} = parseCommitMessage(valueToSet)
const con = item.parentNode.previousElementSibling
const input = con.lastElementChild
const select = input.previousElementSibling
input.value = value
select.value = prefix
}
function getList(){
const html = `<div style="overflow: scroll;">`+Array.from(store.inputs).map(value=>(
`<div
class="item"
onclick="setInputValue(this)">${value}</div>`
)).join('')+`</div>`
return createElementFromHTML(html)
}
function restoreMessage(){
const additionalMessage = localStorage.getItem('additionalMessage') || ''
let messageList = []
try{
messageList = JSON.parse(localStorage.getItem('messageList')) || []
}catch(e){}
if(Array.isArray(messageList)){
allBlockNodes.map(div=>div.querySelector('commit-input')).slice(0, messageList.length).map((v,i)=>{
v.setValue(messageList[i])
})
}
document.getElementById('additional-message').value = additionalMessage
}
function updateAllList() {
const {additionalMessage, messageList, values} = getAllMessage()
localStorage.setItem('additionalMessage', additionalMessage || '')
localStorage.setItem('messageList', JSON.stringify(arrayUnique(messageList)))
allBlockNodes.map(div=>div.querySelector('commit-input')).map(v=>v.updateList())
}
class CommitInput extends HTMLElement {
constructor(){
super()
const root = this.root = this.attachShadow({ 'mode': 'open' })
root.appendChild(document.getElementById('input-template').content.cloneNode(true))
this.input = root.querySelector('input')
this.span = root.querySelector('span')
this.select = root.querySelector('select')
this.span.innerHTML = (this.dataset.index | 0) + 1;
this.history = {
index: 0,
values: [],
getNext() {
if(this.values.length < 1) return
const nextIndex = ()=>{
this.index = (this.index - 1) % this.values.length
if(this.index < 0) {
this.index = this.values.length + this.index
}
}
nextIndex()
while(this.index<0 || this.index >= this.values.length) nextIndex()
return this.values[this.index]
}
}
this.focus = () => this.input.focus()
this.updateList = ()=>{
root.removeChild(root.lastElementChild)
root.appendChild(getList())
}
this.input.onfocus = e=>{
const el = e.target
const {lastInput} = store
this.setValue(lastInput, true)
}
this.setValue = (messageValue, isFocus)=>{
if(messageValue) {
const {value, prefix} = parseCommitMessage(messageValue)
addToArray(this.history.values, messageValue)
this.input.value = value
this.select.value = prefix
if(isFocus){
this.input.select()
}
}
}
this.getValue = ()=>{
let {value} = this.input
if(value.trim()){
if(this.select.value){
value = this.select.value + ': ' + value
}
}
return value
}
this.updateMessage = ()=>{
let {value} = this.input
if(value.trim()){
if(this.select.value){
value = this.select.value + ': ' + value
}
store.lastInput = value
store.inputs.add(value)
updateAllList()
}
}
this.input.onblur = e=>this.updateMessage()
this.select.oninput = e=>{
this.updateMessage()
this.focus()
}
this.onKeyDown = (e) => {
const hasCtrl = e.metaKey || e.ctrlKey
switch(e.key) {
case 'Enter':
if(hasCtrl) {
submit()
break
}
case 'Tab':
{
e.preventDefault()
this.updateMessage()
const index = allBlockNodes.indexOf(this.parentElement)
const next = allBlockNodes[e.shiftKey ? index - 1 : index + 1]
if(next) {
const nextInput = next.querySelector('commit-input')
nextInput.focus()
nextInput.scrollIntoView({behavior: 'smooth', block: 'center', inline: 'center'})
}
break
}
case 'z': {
e.preventDefault()
const message = this.history.getNext()
if(!message) return
const oldValue = this.getValue()
addToArray(this.history.values, oldValue)
const {value, prefix} = parseCommitMessage(message)
this.input.value = value
this.select.value = prefix
this.input.select()
break
}
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
{
if(hasCtrl){
e.preventDefault()
this.select.value = this.select.options[e.key].value
}
break
}
}
}
}
connectedCallback() {
this.input.addEventListener('keydown', this.onKeyDown)
}
disconnectedCallback() {
this.input.removeEventListener('keydown', this.onKeyDown)
}
}
customElements.define('commit-input', CommitInput);
</script>
<script>
function info(node) {
return {
node,
top: node.offsetTop,
height: node.offsetHeight
}
}
function last(arr) {
return arr[arr.length-1]
}
const blocks = []
let list = [
...document.querySelectorAll('span[style="color:#A00"],span[style="color:#0A0"],div[style="color:red"]'),
].map(info)
let block = []
blocks.push(block)
list.forEach((info,i)=>{
block.push(info)
const next = list[i+1]
if(!next) return
if(next.top == info.top) return
if(next.top != info.top + info.height) {
block = []
blocks.push(block)
}
})
const div = document.getElementById('left')
blocks.filter(block=>block.length).forEach((block, i)=>{
const top = block[0].top
const bottom = last(block).top + last(block).height
const blockNode = createElementFromHTML(`<div style="background:#66d7ff;width:100%; position: absolute; left:0; top: ${top}px; height:${bottom-top}px">
<commit-input data-index=${i} id="block${i}"></commit-input></dvi>`);
allBlockNodes.push(blockNode)
div.appendChild(blockNode)
})
// div.querySelector('commit-input').focus()
restoreMessage()
function createElementFromHTML(htmlString) {
var div = document.createElement('div');
div.innerHTML = htmlString.trim();
// Change this to div.childNodes to support multiple top-level nodes
return div.firstChild;
}
</script>