Implement text formatting features (Superscript , Subscript and link insertion)

This commit is contained in:
sairaj mote 2021-12-20 21:06:29 +05:30
parent d6b8dc1746
commit ae4e6116d5
4 changed files with 208 additions and 73 deletions

View File

@ -59,17 +59,13 @@ strong:not(:last-of-type) {
margin-bottom: 1.5rem;
}
a:where([class]) {
color: inherit;
text-decoration: none;
}
a:where([class]):focus-visible {
-webkit-box-shadow: 0 0 0 0.1rem rgba(var(--text-color), 1) inset;
box-shadow: 0 0 0 0.1rem rgba(var(--text-color), 1) inset;
}
a {
color: var(--accent-color);
text-decoration: none;
}
a:focus-visible {
-webkit-box-shadow: 0 0 0 0.1rem rgba(var(--text-color), 1) inset;
box-shadow: 0 0 0 0.1rem rgba(var(--text-color), 1) inset;
}
button,
@ -538,6 +534,7 @@ menu-option {
grid-column: 1/-1;
background-color: var(--foreground-color);
overflow-x: auto;
min-height: 3.2rem;
}
.outline-button {
@ -760,6 +757,10 @@ menu-option {
padding: 0.4rem 0.8rem;
color: var(--accent-color);
}
.content-card .submit-entry sm-spinner {
--height: 0.8rem;
--width: .8rem;
}
.content__header {
padding: 0.5rem;
@ -888,6 +889,9 @@ menu-option {
overflow-y: auto;
}
.history-entry {
grid-template-columns: minmax(0, 1fr);
}
.history-entry:not(:last-of-type) {
padding-bottom: 1rem;
border-bottom: thin solid rgba(var(--text-color), 0.3);
@ -916,6 +920,7 @@ menu-option {
font-size: 0.9rem;
line-height: 1.7;
color: rgba(var(--text-color), 0.8);
overflow-wrap: break-word;
}
.entry__changes .added > *,
.entry__changes .removed > * {
@ -923,10 +928,12 @@ menu-option {
}
.entry__changes .added,
.entry__changes .added > * {
overflow-wrap: break-word;
background-color: #00e67650;
}
.entry__changes .removed,
.entry__changes .removed > * {
overflow-wrap: break-word;
color: var(--danger-color);
-webkit-text-decoration-color: var(--danger-color);
text-decoration-color: var(--danger-color);

2
css/main.min.css vendored

File diff suppressed because one or more lines are too long

View File

@ -61,19 +61,15 @@ strong {
}
}
a:where([class]) {
color: inherit;
a {
color: var(--accent-color);
text-decoration: none;
&:focus-visible {
box-shadow: 0 0 0 0.1rem rgba(var(--text-color), 1) inset;
}
}
a {
color: var(--accent-color);
}
button,
.button {
user-select: none;
@ -469,6 +465,7 @@ menu-option {
grid-column: 1/-1;
background-color: var(--foreground-color);
overflow-x: auto;
min-height: 3.2rem;
}
.outline-button{
position: relative;
@ -659,6 +656,10 @@ menu-option {
border-radius: 0.2rem;
padding: 0.4rem 0.8rem;
color: var(--accent-color);
sm-spinner{
--height: 0.8rem;
--width: .8rem;
}
}
}
.content__header {
@ -773,6 +774,7 @@ menu-option {
overflow-y: auto;
}
.history-entry {
grid-template-columns: minmax(0, 1fr);
&:not(:last-of-type) {
padding-bottom: 1rem;
border-bottom: thin solid rgba(var(--text-color), 0.3);
@ -798,16 +800,19 @@ menu-option {
font-size: 0.9rem;
line-height: 1.7;
color: rgba(var(--text-color), 0.8);
overflow-wrap: break-word;
.added > *,
.removed > * {
background-color: transparent;
}
.added,
.added > * {
overflow-wrap: break-word;
background-color: #00e67650;
}
.removed,
.removed > * {
overflow-wrap: break-word;
color: var(--danger-color);
text-decoration-color: var(--danger-color);
}

View File

@ -176,43 +176,87 @@
</strip-select>
</div>
<div id="article_outline_panel" class="flex align-center gap-1 hide-completely">
<h4>Sections</h4>
<h5>Sections</h5>
<ul id="article_outline" class="flex gap-1"></ul>
</div>
</section>
<div id="text_toolbar" class="hide-completely">
<button id="strong_button" title="Bold (ctrl+b)" class="formatting-button" onclick="formatDoc('bold')">
<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="M15.6 10.79c.97-.67 1.65-1.77 1.65-2.79 0-2.26-1.75-4-4-4H7v14h7.04c2.09 0 3.71-1.7 3.71-3.79 0-1.52-.86-2.82-2.15-3.42zM10 6.5h3c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5h-3v-3zm3.5 9H10v-3h3.5c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5z" />
</svg>
</button>
<button id="em_button" title="Italic (ctrl+i)" class="formatting-button" onclick="formatDoc('italic');">
<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="M10 4v3h2.21l-3.42 8H6v3h8v-3h-2.21l3.42-8H18V4h-8z" />
</svg>
</button>
<button id="u_button" title="Underline (ctrl+u)" class="formatting-button"
onclick="formatDoc('underline');">
<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="M12 17c3.31 0 6-2.69 6-6V3h-2.5v8c0 1.93-1.57 3.5-3.5 3.5S8.5 12.93 8.5 11V3H6v8c0 3.31 2.69 6 6 6zm-7 2v2h14v-2H5z" />
</svg>
</button>
<!-- <button class="formatting-button" title="Add quote" onclick="insertBlockquote()">
<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="M18.62 18h-5.24l2-4H13V6h8v7.24L18.62 18zm-2-2h.76L19 12.76V8h-4v4h3.62l-2 4zm-8 2H3.38l2-4H3V6h8v7.24L8.62 18zm-2-2h.76L9 12.76V8H5v4h3.62l-2 4z" />
</svg>
</button> -->
<div id="formatting_options">
<button id="strong_button" title="Bold (ctrl+b)" class="formatting-button" onclick="formatDoc('bold')">
<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="M15.6 10.79c.97-.67 1.65-1.77 1.65-2.79 0-2.26-1.75-4-4-4H7v14h7.04c2.09 0 3.71-1.7 3.71-3.79 0-1.52-.86-2.82-2.15-3.42zM10 6.5h3c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5h-3v-3zm3.5 9H10v-3h3.5c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5z" />
</svg>
</button>
<button id="em_button" title="Italic (ctrl+i)" class="formatting-button" onclick="formatDoc('italic');">
<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="M10 4v3h2.21l-3.42 8H6v3h8v-3h-2.21l3.42-8H18V4h-8z" />
</svg>
</button>
<button id="u_button" title="Underline (ctrl+u)" class="formatting-button"
onclick="formatDoc('underline');">
<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="M12 17c3.31 0 6-2.69 6-6V3h-2.5v8c0 1.93-1.57 3.5-3.5 3.5S8.5 12.93 8.5 11V3H6v8c0 3.31 2.69 6 6 6zm-7 2v2h14v-2H5z" />
</svg>
</button>
<button id="sup_button" class="formatting-button" title="Superscript"
onclick="formatDoc('superscript')">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px"
viewBox="0 0 24 24" width="24px" fill="#000000">
<g>
<rect fill="none" height="24" width="24" x="0" y="0" />
<path
d="M22,7h-2v1h3v1h-4V7c0-0.55,0.45-1,1-1h2V5h-3V4h3c0.55,0,1,0.45,1,1v1C23,6.55,22.55,7,22,7z M5.88,20h2.66l3.4-5.42h0.12 l3.4,5.42h2.66l-4.65-7.27L17.81,6h-2.68l-3.07,4.99h-0.12L8.85,6H6.19l4.32,6.73L5.88,20z" />
</g>
</svg>
</button>
<button id="sub_button" class="formatting-button" title="Subscript" onclick="formatDoc('subscript')">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px"
viewBox="0 0 24 24" width="24px" fill="#000000">
<g>
<rect fill="none" height="24" width="24" />
<path
d="M22,18h-2v1h3v1h-4v-2c0-0.55,0.45-1,1-1h2v-1h-3v-1h3c0.55,0,1,0.45,1,1v1C23,17.55,22.55,18,22,18z M5.88,18h2.66 l3.4-5.42h0.12l3.4,5.42h2.66l-4.65-7.27L17.81,4h-2.68l-3.07,4.99h-0.12L8.85,4H6.19l4.32,6.73L5.88,18z" />
</g>
</svg>
</button>
<button id="a_button" class="formatting-button" title="Create link" onclick="toggleLinkPanel(this)">
<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="M17 7h-4v2h4c1.65 0 3 1.35 3 3s-1.35 3-3 3h-4v2h4c2.76 0 5-2.24 5-5s-2.24-5-5-5zm-6 8H7c-1.65 0-3-1.35-3-3s1.35-3 3-3h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-2zm-3-4h8v2H8z" />
</svg>
</button>
<!-- <button class="formatting-button" title="Add quote" onclick="insertBlockquote()">
<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="M18.62 18h-5.24l2-4H13V6h8v7.24L18.62 18zm-2-2h.76L19 12.76V8h-4v4h3.62l-2 4zm-8 2H3.38l2-4H3V6h8v7.24L8.62 18zm-2-2h.76L9 12.76V8H5v4h3.62l-2 4z" />
</svg>
</button> -->
</div>
<div id="link_panel" class="hide-completely">
<sm-form style="--gap: 0.5rem">
<button class="icon-only justify-self-start" onclick="toggleLinkPanel()">
<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="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z" />
</svg>
</button>
<sm-input id="create_link_href" placeholder="Link address" required hiderequired></sm-input>
<sm-button variant="primary" onclick="createLink()">Add link</sm-button>
</sm-form>
</div>
</div>
<div id="article_wrapper"></div>
<aside id="version_history_panel" class="flex direction-column hide-completely">
@ -1116,6 +1160,7 @@
})
getRef('article_wrapper').addEventListener('click', e => {
if (e.target.closest('.submit-entry')) {
const submitButton = e.target.closest('.submit-entry')
const contentCard = e.target.closest('.content-card')
const contentArea = contentCard.querySelector('.content__area')
const uid = contentCard.dataset.uid
@ -1125,7 +1170,7 @@
contentArea.querySelectorAll('[style=""]').forEach((el) => {
el.removeAttribute('style')
})
const clean = DOMPurify.sanitize(contentArea.innerHTML.split('\n').map(v => v.trim()).filter(v => v));
const clean = DOMPurify.sanitize(contentArea.innerHTML.split('\n').map(v => v.trim()).filter(v => v).join('\n'));
if (clean === '') return
const hash = Crypto.SHA256(clean)
@ -1143,10 +1188,12 @@
data: isUniqueEntry ? clean : getDiff(previousVersion, clean),
hash
}
submitButton.innerHTML = `<sm-spinner></sm-spinner>`
submitButton.disabled = true
floCloudAPI.sendGeneralData(entry, `${floGlobals.currentArticle.id}_gd`)
.then((res) => {
console.log(res)
e.target.closest('.submit-entry').classList.add('hide-completely')
submitButton.classList.add('hide-completely')
notify('sent data', 'success')
const iterationData = { ...entry, timestamp, editor: myFloID }
if (isUniqueEntry) {
@ -1189,6 +1236,11 @@
floGlobals.currentArticle.uniqueEntries[entry.origin].iterations.push(iterationData)
}
})
.catch(err => console.log(err))
.finally(() => {
submitButton.textContent = 'Submit'
submitButton.disabled = false
})
} else {
notify("Duplicate entry!", 'error')
}
@ -2096,39 +2148,105 @@
getRef('text_toolbar').classList.add('hide-completely')
document.querySelectorAll('.formatting-button').forEach(elem => elem.classList.remove('active'))
} else {
getRef('create_link_href').value = ''
const pos = selection.isCollapsed ? selection.anchorNode.getBoundingClientRect() : selection.getRangeAt(0).getBoundingClientRect()
getRef('text_toolbar').style.transform = `translate(${pos.left}px, calc(${pos.bottom + window.pageYOffset}px + 1rem))`
getRef('text_toolbar').classList.remove('hide-completely')
getRef('text_toolbar').style.transform = `translate(${pos.left}px, calc(${pos.bottom + window.pageYOffset}px + 0.3rem))`
getRef('formatting_options').classList.remove('hide-completely')
getRef('link_panel').classList.add('hide-completely')
}
}
const detectFormatting = debounce((e) => {
const selection = window.getSelection();
manageFormattingOptions()
if (!selection.isCollapsed) {
const isBold = document.queryCommandState('bold')
const isItalic = document.queryCommandState('italic')
const isUnderlined = document.queryCommandState('underline')
if (isBold) {
getRef(`strong_button`).classList.add('active')
} else {
getRef(`strong_button`).classList.remove('active')
if (!window.getSelection().isCollapsed) {
[
['bold', 'strong_button'],
['italic', 'em_button'],
['underline', 'u_button'],
['superscript', 'sup_button'],
['subscript', 'sub_button'],
['link', 'a_button'],
].forEach(([state, id]) => {
if (state === 'link' ? isLink() : document.queryCommandState(state)) {
getRef(id).classList.add('active')
} else {
getRef(id).classList.remove('active')
}
})
}
}, 100)
function saveSelection() {
if (window.getSelection) {
sel = window.getSelection();
if (sel.getRangeAt && sel.rangeCount) {
var ranges = [];
for (var i = 0, len = sel.rangeCount; i < len; ++i) {
ranges.push(sel.getRangeAt(i));
}
return ranges;
}
if (isItalic) {
getRef(`em_button`).classList.add('active')
} else {
getRef(`em_button`).classList.remove('active')
}
if (isUnderlined) {
getRef(`u_button`).classList.add('active')
} else {
getRef(`u_button`).classList.remove('active')
} else if (document.selection && document.selection.createRange) {
return document.selection.createRange();
}
return null;
}
function restoreSelection(savedSel) {
if (savedSel) {
if (window.getSelection) {
sel = window.getSelection();
sel.removeAllRanges();
for (var i = 0, len = savedSel.length; i < len; ++i) {
sel.addRange(savedSel[i]);
}
} else if (document.selection && savedSel.select) {
savedSel.select();
}
}
},
200)
}
let currentSelection
function toggleLinkPanel(elem) {
if (elem && elem.classList.contains('active')) {
const selection = window.getSelection()
const startA = selection.anchorNode.parentNode
const endA = selection.focusNode.parentNode
getRef('create_link_href').value = (startA || endA).getAttribute('href')
} else {
getRef('create_link_href').value = ''
}
if (getRef('link_panel').classList.contains('hide-completely')) {
currentSelection = saveSelection()
} else {
}
getRef('formatting_options').classList.toggle('hide-completely')
getRef('link_panel').classList.toggle('hide-completely')
}
function isLink() {
const selection = window.getSelection()
const startA = selection.anchorNode.parentNode.tagName === 'A'
const endA = selection.focusNode.parentNode.tagName === 'A'
return startA || endA
}
function createLink() {
restoreSelection(currentSelection)
const url = getRef('create_link_href').value
if (isLink()) {
const selection = window.getSelection()
const startA = selection.anchorNode.parentNode
const endA = selection.focusNode.parentNode;
(startA || endA).setAttribute('href', url)
} else {
replaceSelectedText(createElement('a', {
attributes: { href: url }
}))
}
}
const replaceBetween = (origin, startIndex, endIndex, insertion) =>
`${origin.substring(0, startIndex)}${insertion}${origin.substring(endIndex)}`;
@ -2144,7 +2262,7 @@
},
link() {
replaceSelectedText(createElement('a', {
attributes: { href: 'google.com', target: '_blank' }
attributes: { href: 'google.com' }
}))
}
}
@ -11814,6 +11932,11 @@
font-family: inherit
}
a {
text-decoration: none;
color: var(--accent-color);
}
.flex {
display: -webkit-box;
display: -ms-flexbox;