implemented version history feature in UI

This commit is contained in:
sairaj mote 2021-11-15 00:27:51 +05:30
parent ecf86ef764
commit 05480c7462
4 changed files with 184 additions and 63 deletions

View File

@ -548,9 +548,19 @@ sm-checkbox {
fill: var(--danger-color);
}
#main_page {
grid-template-columns: minmax(0, 1fr);
}
#article_wrapper {
justify-self: center;
padding: 1rem 0;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-ms-flex-direction: column;
flex-direction: column;
padding: 1rem;
gap: 1rem 0;
}
@ -574,6 +584,8 @@ sm-checkbox {
display: flex;
gap: 0.5rem;
overflow-x: auto;
-ms-flex-negative: 0;
flex-shrink: 0;
}
.article-section:not(:last-of-type) {
margin-bottom: 1.5rem;
@ -717,32 +729,24 @@ sm-checkbox {
}
#version_history_panel {
position: fixed;
top: 0;
bottom: 0;
right: 0;
margin: 1rem;
border-radius: 0.5rem;
padding: 1rem;
width: min(22rem, 100%);
width: min(24rem, 100%);
background-color: var(--foreground-color);
-webkit-box-shadow: -0.5rem 0 1rem rgba(0, 0, 0, 0.1);
box-shadow: -0.5rem 0 1rem rgba(0, 0, 0, 0.1);
overflow-y: auto;
}
#version_history_panel > :first-child {
padding: 1rem;
}
#version_timeline {
padding: 1rem;
height: 100%;
margin-top: 1.5rem;
overflow-y: auto;
}
.history-entry {
padding: 1rem;
border-radius: 0.3rem;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
.history-entry:not(:last-of-type) {
padding-bottom: 1rem;
border-bottom: thin solid rgba(var(--text-color), 0.3);
}
.history-entry:last-of-type::before {
content: "CREATED";
@ -753,8 +757,9 @@ sm-checkbox {
justify-self: flex-start;
font-weight: 500;
padding: 0.2rem 0.3rem;
font-size: 0.8rem;
background-color: rgba(var(--text-color), 0.06);
font-size: 0.7rem;
border-radius: 0.2rem;
border: solid thin rgba(var(--text-color), 0.5);
}
.entry__time,
@ -763,6 +768,13 @@ sm-checkbox {
font-weight: 500;
}
.entry__changes .added {
background-color: #00e67650;
}
.entry__changes .removed {
background-color: #ff3a4a50;
}
@media screen and (max-width: 40rem) and (any-hover: none) {
.cancel-order span {
display: none;
@ -799,6 +811,7 @@ sm-checkbox {
}
.popup__header {
grid-column: 1/-1;
padding: 1rem 1.5rem 0 0.5rem;
}
@ -813,6 +826,17 @@ sm-checkbox {
.hide-on-desktop {
display: none;
}
#main_page.active-sidebar {
height: 100%;
overflow-y: hidden;
grid-template-rows: auto 1fr;
grid-template-columns: minmax(0, 1fr) 24rem;
}
#main_page.active-sidebar #article_wrapper {
height: 100%;
overflow-y: auto;
}
}
@media (any-hover: hover) {
::-webkit-scrollbar {

2
css/main.min.css vendored

File diff suppressed because one or more lines are too long

View File

@ -395,9 +395,6 @@ button:active,
padding: 0.5rem;
cursor: pointer;
}
#main_page {
}
.logo {
display: grid;
align-items: center;
@ -484,9 +481,14 @@ sm-checkbox {
fill: var(--danger-color);
}
#main_page {
grid-template-columns: minmax(0, 1fr);
}
#article_wrapper {
justify-self: center;
padding: 1rem 0;
display: flex;
flex-direction: column;
padding: 1rem;
gap: 1rem 0;
}
@ -506,6 +508,7 @@ sm-checkbox {
display: flex;
gap: 0.5rem;
overflow-x: auto;
flex-shrink: 0;
&:not(:last-of-type) {
margin-bottom: 1.5rem;
}
@ -629,26 +632,24 @@ sm-checkbox {
}
#version_history_panel {
position: fixed;
top: 0;
bottom: 0;
right: 0;
margin: 1rem;
border-radius: 0.5rem;
padding: 1rem;
width: min(22rem, 100%);
width: min(24rem, 100%);
background-color: var(--foreground-color);
box-shadow: -0.5rem 0 1rem rgba(0, 0, 0, 0.1);
overflow-y: auto;
& > :first-child {
padding: 1rem;
}
}
#version_timeline {
padding: 1rem;
height: 100%;
margin-top: 1.5rem;
overflow-y: auto;
}
.history-entry {
padding: 1rem;
border-radius: 0.3rem;
user-select: none;
&:not(:last-of-type) {
padding-bottom: 1rem;
border-bottom: thin solid rgba(var(--text-color), 0.3);
}
&:last-of-type::before {
content: "CREATED";
letter-spacing: 0.03em;
@ -656,8 +657,9 @@ sm-checkbox {
justify-self: flex-start;
font-weight: 500;
padding: 0.2rem 0.3rem;
font-size: 0.8rem;
background-color: rgba(var(--text-color), 0.06);
font-size: 0.7rem;
border-radius: 0.2rem;
border: solid thin rgba(var(--text-color), 0.5);
}
}
.entry__time,
@ -665,6 +667,14 @@ sm-checkbox {
font-size: 0.8rem;
font-weight: 500;
}
.entry__changes {
.added {
background-color: #00e67650;
}
.removed {
background-color: #ff3a4a50;
}
}
@media screen and (max-width: 40rem) and (any-hover: none) {
.cancel-order {
@ -701,6 +711,7 @@ sm-checkbox {
font-size: 1rem;
}
.popup__header {
grid-column: 1/-1;
padding: 1rem 1.5rem 0 0.5rem;
}
#confirmation_popup {
@ -712,6 +723,18 @@ sm-checkbox {
.hide-on-desktop {
display: none;
}
#main_page {
&.active-sidebar {
height: 100%;
overflow-y: hidden;
grid-template-rows: auto 1fr;
grid-template-columns: minmax(0, 1fr) 24rem;
#article_wrapper {
height: 100%;
overflow-y: auto;
}
}
}
}
@media (any-hover: hover) {
::-webkit-scrollbar {

View File

@ -133,8 +133,8 @@
</svg>
</button>
</div>
<div id="article_wrapper" class="grid page-layout"></div>
<section class="grid page-layout">
<div id="article_wrapper"></div>
<!-- <div class="grid page-layout">
<div id="action_button_group" class="flex align-center gap-0-5">
<span>
<b>
@ -142,8 +142,8 @@
</b>
</span>
<button class="actionable-button" title="Add paragraph">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px"
fill="#000000">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24"
width="24px" fill="#000000">
<path d="M0 0h24v24H0V0z" fill="none" />
<path d="M14 17H4v2h10v-2zm6-8H4v2h16V9zM4 15h16v-2H4v2zM4 5v2h16V5H4z" />
</svg>
@ -152,7 +152,7 @@
</span>
</button>
</div>
</section>
</div> -->
<aside id="version_history_panel" class="flex direction-column hide-completely">
<div class="flex align-center space-between">
<div class="flex align-center">
@ -173,7 +173,7 @@
</svg>
</button>
</div>
<ul id="version_timeline" class="flex direction-column gap-0-5"></ul>
<ul id="version_timeline" class="flex direction-column gap-1-5"></ul>
</aside>
</article>
<sm-popup id="article_list_popup">
@ -270,7 +270,7 @@
<p><br></p>
</template>
<template id="history_entry_template">
<li class="history-entry interact grid gap-0-5">
<li class="history-entry grid gap-0-5">
<div class="flex align-center space-between">
<time class="entry__time"></time>
<span class="entry__score"></span>
@ -279,6 +279,7 @@
<div class="label">Author</div>
<span class="entry__author breakable"></span>
</div>
<div class="entry__changes"></div>
</li>
</template>
<script id="ui_utils">
@ -673,8 +674,16 @@
returns newStr
*/
function splitHTML(string) {
const el = createElement('div', {
innerHTML: string
});
return Array.from(el.childNodes).map(e => (e.outerHTML || e.innerHTML || e.nodeValue).split(' ').filter(t => t)).flat();
}
function getDiff(oldStr, newStr) {
let d = patienceDiff(oldStr.split(" "), newStr.split(" "), true);
console.log(splitHTML(oldStr), splitHTML(newStr))
let d = patienceDiff(splitHTML(oldStr), splitHTML(newStr), true);
return [d.aMoveIndex, d.bMove, d.bMoveIndex]
}
@ -732,16 +741,17 @@
contentArea.querySelectorAll('[style=""]').forEach((el) => {
el.removeAttribute('style')
})
const clean = DOMPurify.sanitize(contentArea.innerHTML);
const clean = DOMPurify.sanitize(contentArea.innerHTML.split('\n').map(v => v.trim()).filter(v => v));
if (clean.trim() === '') return
let previousVersion, contributors
if (!isUniqueEntry)
({ data: previousVersion, contributors } = getIterationDetails(uid))
const entry = {
section: contentCard.closest('.article-section').dataset.id,
section: contentCard.closest('.article-section').dataset.sectionId,
origin: isUniqueEntry ? floCrypto.randString(16, true) : uid,
data: isUniqueEntry ? clean : getDiff(previousVersion, clean),
}
console.log(entry)
floCloudAPI.sendGeneralData(entry, `${currentArticle.id}_gd`)
.then((res) => {
console.log(res)
@ -750,7 +760,10 @@
contentArea.innerHTML = ''
})
} else if (e.target.closest('.version-history-button')) {
showVersionHistory(e.target.closest('.content-card').dataset.uid)
if (isHistoryPanelOpen)
hideVersionHistory()
else
showVersionHistory(e.target.closest('.content-card').dataset.uid)
}
})
getRef('article_wrapper').addEventListener("paste", e => {
@ -776,6 +789,15 @@
})
}
})
getRef('article_wrapper').addEventListener("dblclick", e => {
if (e.target.closest('.heading')) {
const target = e.target.closest('.heading')
if (!target.isContentEditable) {
target.contentEditable = true
target.focus()
}
}
})
getRef('article_wrapper').addEventListener("focusout", e => {
if (e.target.closest('.content__area')) {
document.removeEventListener('selectionchange', detectFormatting)
@ -785,6 +807,12 @@
if (!e.relatedTarget?.closest('#text_toolbar')) {
getRef('text_toolbar').classList.add('hide-completely')
}
} else if (e.target.closest('.heading')) {
const target = e.target.closest('.heading')
if (target.isContentEditable) {
floGlobals.appObjects[currentArticle.id].sections[target.dataset.index].title = target.textContent.trim()
target.contentEditable = false
}
}
})
const childObserver = new MutationObserver((mutations, observer) => {
@ -808,10 +836,11 @@
<script>
let currentArticle = {}
const render = {
section(sectionID, { title, uniqueEntries }) {
section(sectionID, { title, uniqueEntries }, index) {
const section = getRef('section_template').content.cloneNode(true)
const frag = document.createDocumentFragment()
section.children[1].dataset.id = sectionID
section.children[0].dataset.index = index
section.children[1].dataset.sectionId = sectionID
section.querySelector('.heading').textContent = title
currentArticle.sections[sectionID].uniqueEntries.forEach(entry => {
frag.append(render.contentCard(entry))
@ -837,18 +866,33 @@
const { title } = floGlobals.appObjects.cc.articleList[id]
const { writer, sections } = currentArticle
const frag = document.createDocumentFragment()
let index = 0
for (const sectionID in sections) {
frag.append(render.section(sectionID, sections[sectionID]))
frag.append(render.section(sectionID, sections[sectionID], index))
index += 1
}
getRef('current_article_name').textContent = title
getRef('article_wrapper').innerHTML = ''
getRef('article_wrapper').append(frag)
},
historyEntry(details) {
const { editor, timestamp } = details
historyEntry(details, oldText) {
const { editor, timestamp, data } = details
const clone = getRef('history_entry_template').content.cloneNode(true).firstElementChild;
clone.querySelector('.entry__time').textContent = getFormattedTime(timestamp)
clone.querySelector('.entry__author').textContent = editor
if (Array.isArray(data)) {
console.log(data)
const [removedAt, addedWords, addedAt] = data
const changed = oldText.split(' ')
let firstAddedPlace
let startIndex, endIndex
let addedNodes
addedAt.forEach((place, index) => {
changed.splice(place, 0, `<span class="added">${addedWords[index]}</span>`)
})
removedAt.forEach(place => changed[place] = `<span class="removed">${changed[place]}</span>`)
clone.querySelector('.entry__changes').innerHTML = changed.join(' ')
}
return clone
}
}
@ -866,7 +910,7 @@
currentArticle['uniqueEntries'] = {}
for (const key in generalData) {
const { message: { section, data, origin }, senderID } = generalData[key]
if (!currentArticle.uniqueEntries.hasOwnProperty(origin)) {
if (!currentArticle.uniqueEntries.hasOwnProperty(origin)) { // check if gen data has origin that's already defined
currentArticle.uniqueEntries[origin] = {
iterations: []
}
@ -878,6 +922,9 @@
editor: senderID
})
}
for (const sectionID in currentArticle.sections) {
currentArticle.sections[sectionID].uniqueEntries = [...currentArticle.sections[sectionID].uniqueEntries].reverse()
}
for (const entry in currentArticle.uniqueEntries) {
currentArticle.uniqueEntries[entry]['iterations'].sort((a, b) => a.timestamp - b.timestamp)
}
@ -898,20 +945,47 @@
}
}
let isHistoryPanelOpen = false
function showVersionHistory(uid) {
const { iterations } = currentArticle.uniqueEntries[uid]
const frag = document.createDocumentFragment()
iterations.forEach(iter => {
frag.prepend(render.historyEntry(iter))
let mergedChanges/* , oldText */
// oldText = createElement('div', {
// innerHTML: mergedChanges
// }).textContent
iterations.forEach((iter, index) => {
// const tempText = createElement('div', {
// innerHTML: tempMergedChanges
// }).textContent
// const changes = {
// diff: getDiff(oldText, tempText),
// }
// const versions = {
// oldText: mergedChanges,
// newText: tempMergedChanges
// }
// oldText = tempText
// console.log(mergedChanges, iter.data, tempMergedChanges)
frag.prepend(render.historyEntry(iter, mergedChanges))
mergedChanges = index ? updateString(mergedChanges, iter.data) : iter.data
// mergedChanges = tempMergedChanges
})
getRef('version_timeline').innerHTML = ''
getRef('version_timeline').append(frag)
getRef('version_history_panel').classList.remove('hide-completely')
if (!isHistoryPanelOpen) {
getRef('version_history_panel').classList.remove('hide-completely')
getRef('main_page').classList.add('active-sidebar')
isHistoryPanelOpen = true
}
}
function hideVersionHistory() {
getRef('version_history_panel').classList.add('hide-completely')
getRef('version_timeline').innerHTML = ''
if (isHistoryPanelOpen) {
getRef('version_history_panel').classList.add('hide-completely')
getRef('version_timeline').innerHTML = ''
getRef('main_page').classList.remove('active-sidebar')
isHistoryPanelOpen = false
}
}
const splitAt = (string, index) => [string.slice(0, index), string.slice(index)]