Implement text formatting features (Superscript , Subscript and link insertion)
This commit is contained in:
parent
d6b8dc1746
commit
ae4e6116d5
25
css/main.css
25
css/main.css
@ -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
2
css/main.min.css
vendored
File diff suppressed because one or more lines are too long
@ -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);
|
||||
}
|
||||
|
||||
235
index.html
235
index.html
@ -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;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user