Feature update and UX improvements
-- added sorting options for article explorer popup (sort alphabetically or chronologically ) - added page routing with article ID to enable history navigation components - added focus trapping for sm-popup (UX)
This commit is contained in:
parent
e155bfc341
commit
70b13b7f5a
363
components.js
363
components.js
@ -1007,7 +1007,6 @@ smPopup.innerHTML = `
|
||||
--height: auto;
|
||||
--min-width: auto;
|
||||
--min-height: auto;
|
||||
--body-padding: 1.5rem;
|
||||
--backdrop-background: rgba(0, 0, 0, 0.6);
|
||||
--border-radius: 0.8rem 0.8rem 0 0;
|
||||
}
|
||||
@ -1087,13 +1086,13 @@ smPopup.innerHTML = `
|
||||
-ms-flex: 1;
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
padding: var(--body-padding);
|
||||
padding: var(--body-padding, 1.5rem);
|
||||
overflow-y: auto;
|
||||
}
|
||||
.hide{
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
visiblity: none;
|
||||
visibility: none;
|
||||
}
|
||||
@media screen and (min-width: 640px){
|
||||
:host{
|
||||
@ -1169,7 +1168,10 @@ customElements.define('sm-popup', class extends HTMLElement {
|
||||
this.touchEndY = 0;
|
||||
this.touchStartTime = 0;
|
||||
this.touchEndTime = 0;
|
||||
this.touchEndAnimataion = undefined;
|
||||
this.touchEndAnimation = undefined;
|
||||
this.focusable
|
||||
this.autoFocus
|
||||
this.mutationObserver
|
||||
|
||||
this.popupContainer = this.shadowRoot.querySelector('.popup-container');
|
||||
this.popup = this.shadowRoot.querySelector('.popup');
|
||||
@ -1183,6 +1185,7 @@ customElements.define('sm-popup', class extends HTMLElement {
|
||||
this.handleTouchMove = this.handleTouchMove.bind(this);
|
||||
this.handleTouchEnd = this.handleTouchEnd.bind(this);
|
||||
this.movePopup = this.movePopup.bind(this);
|
||||
this.detectFocus = this.detectFocus.bind(this);
|
||||
}
|
||||
|
||||
static get observedAttributes() {
|
||||
@ -1231,6 +1234,8 @@ customElements.define('sm-popup', class extends HTMLElement {
|
||||
this.popup.style.transform = 'none';
|
||||
document.body.style.overflow = 'hidden';
|
||||
document.body.style.top = `-${window.scrollY}px`;
|
||||
const elementToFocus = this.autoFocus || this.focusable[0];
|
||||
elementToFocus.tagName.includes('SM-') ? elementToFocus.focusIn() : elementToFocus.focus();
|
||||
return this.popupStack;
|
||||
}
|
||||
hide() {
|
||||
@ -1279,13 +1284,13 @@ customElements.define('sm-popup', class extends HTMLElement {
|
||||
handleTouchMove(e) {
|
||||
if (this.touchStartY < e.changedTouches[0].clientY) {
|
||||
this.offset = e.changedTouches[0].clientY - this.touchStartY;
|
||||
this.touchEndAnimataion = window.requestAnimationFrame(() => this.movePopup());
|
||||
this.touchEndAnimation = window.requestAnimationFrame(() => this.movePopup());
|
||||
}
|
||||
}
|
||||
|
||||
handleTouchEnd(e) {
|
||||
this.touchEndTime = e.timeStamp;
|
||||
cancelAnimationFrame(this.touchEndAnimataion);
|
||||
cancelAnimationFrame(this.touchEndAnimation);
|
||||
this.touchEndY = e.changedTouches[0].clientY;
|
||||
this.popup.style.transition = 'transform 0.3s';
|
||||
this.threshold = this.popup.getBoundingClientRect().height * 0.3;
|
||||
@ -1314,6 +1319,20 @@ customElements.define('sm-popup', class extends HTMLElement {
|
||||
this.popup.style.transform = `translateY(${this.offset}px)`;
|
||||
}
|
||||
|
||||
detectFocus(e) {
|
||||
if (e.code === 'Tab') {
|
||||
const lastElement = this.focusable[this.focusable.length - 1];
|
||||
const firstElement = this.focusable[0];
|
||||
if (e.shiftKey && document.activeElement === firstElement) {
|
||||
e.preventDefault();
|
||||
lastElement.tagName.includes('SM-') ? lastElement.focusIn() : lastElement.focus();
|
||||
} else if (!e.shiftKey && document.activeElement === lastElement) {
|
||||
e.preventDefault();
|
||||
firstElement.tagName.includes('SM-') ? firstElement.focusIn() : firstElement.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.popupBodySlot.addEventListener('slotchange', () => {
|
||||
this.forms = this.querySelectorAll('sm-form');
|
||||
@ -1340,16 +1359,26 @@ customElements.define('sm-popup', class extends HTMLElement {
|
||||
});
|
||||
resizeObserver.observe(this);
|
||||
|
||||
this.mutationObserver = new MutationObserver(entries => {
|
||||
entries.forEach(mutation => {
|
||||
this.focusable = this.querySelectorAll('sm-button:not([disabled]), button:not([disabled]), [href], sm-input, input, sm-select, select, sm-checkbox, sm-textarea, textarea, [tabindex]:not([tabindex="-1"])')
|
||||
this.autoFocus = this.querySelector('[autofocus]')
|
||||
})
|
||||
})
|
||||
this.mutationObserver.observe(this, { attributes: true, childList: true, subtree: true })
|
||||
|
||||
this.addEventListener('keydown', this.detectFocus);
|
||||
this.popupHeader.addEventListener('touchstart', this.handleTouchStart, { passive: true });
|
||||
this.popupHeader.addEventListener('touchmove', this.handleTouchMove, { passive: true });
|
||||
this.popupHeader.addEventListener('touchend', this.handleTouchEnd, { passive: true });
|
||||
}
|
||||
disconnectedCallback() {
|
||||
this.removeEventListener('keydown', this.detectFocus);
|
||||
this.popupHeader.removeEventListener('touchstart', this.handleTouchStart, { passive: true });
|
||||
this.popupHeader.removeEventListener('touchmove', this.handleTouchMove, { passive: true });
|
||||
this.popupHeader.removeEventListener('touchend', this.handleTouchEnd, { passive: true });
|
||||
resizeObserver.unobserve();
|
||||
this.mutationObserver.disconnect()
|
||||
}
|
||||
attributeChangedCallback(name) {
|
||||
if (name === 'open') {
|
||||
@ -2329,7 +2358,7 @@ smSelect.innerHTML = `
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<div class="select" >
|
||||
<div class="select">
|
||||
<div class="selection">
|
||||
<div class="selected-option-text"></div>
|
||||
<svg class="icon toggle" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M12 13.172l4.95-4.95 1.414 1.414L12 16 5.636 9.636 7.05 8.222z"/></svg>
|
||||
@ -2345,6 +2374,7 @@ customElements.define('sm-select', class extends HTMLElement {
|
||||
mode: 'open'
|
||||
}).append(smSelect.content.cloneNode(true))
|
||||
|
||||
this.focusIn = this.focusIn.bind(this)
|
||||
this.reset = this.reset.bind(this)
|
||||
this.open = this.open.bind(this)
|
||||
this.collapse = this.collapse.bind(this)
|
||||
@ -2413,6 +2443,10 @@ customElements.define('sm-select', class extends HTMLElement {
|
||||
}
|
||||
}
|
||||
|
||||
focusIn() {
|
||||
this.selection.focus()
|
||||
}
|
||||
|
||||
open() {
|
||||
this.optionList.classList.remove('hide')
|
||||
this.optionList.animate(this.slideDown, this.animationOptions)
|
||||
@ -2757,6 +2791,10 @@ customElements.define('sm-checkbox', class extends HTMLElement {
|
||||
return this.getAttribute('value')
|
||||
}
|
||||
|
||||
focusIn() {
|
||||
this.focus()
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.removeAttribute('checked')
|
||||
}
|
||||
@ -3447,7 +3485,6 @@ textField.innerHTML = `
|
||||
align-items: center;
|
||||
}
|
||||
.text{
|
||||
padding: 0.6rem 0;
|
||||
transition: background-color 0.3s;
|
||||
border-bottom: 0.15rem solid transparent;
|
||||
overflow-wrap: break-word;
|
||||
@ -3657,4 +3694,314 @@ customElements.define('text-field', class extends HTMLElement {
|
||||
this.editButton.removeEventListener('click', this.setEditable)
|
||||
this.saveButton.removeEventListener('click', this.setNonEditable)
|
||||
}
|
||||
})
|
||||
const smMenu = document.createElement('template')
|
||||
smMenu.innerHTML = `
|
||||
<style>
|
||||
*{
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
:host{
|
||||
display: -webkit-inline-box;
|
||||
display: -ms-inline-flexbox;
|
||||
display: inline-flex;
|
||||
|
||||
}
|
||||
.menu{
|
||||
display: -ms-grid;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
height: 2rem;
|
||||
width: 2rem;
|
||||
outline: none;
|
||||
}
|
||||
.icon {
|
||||
position: absolute;
|
||||
fill: rgba(var(--text-color), 0.7);
|
||||
height: 2.4rem;
|
||||
width: 2.4rem;
|
||||
padding: 0.5rem;
|
||||
border-radius: 2rem;
|
||||
-webkit-transition: background 0.3s;
|
||||
-o-transition: background 0.3s;
|
||||
transition: background 0.3s;
|
||||
}
|
||||
.select{
|
||||
position: relative;
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-box-direction: normal;
|
||||
-ms-flex-direction: column;
|
||||
flex-direction: column;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
.menu:focus .icon,
|
||||
.focused{
|
||||
background: rgba(var(--text-color), 0.1);
|
||||
}
|
||||
:host([align-options="left"]) .options{
|
||||
left: 0;
|
||||
}
|
||||
:host([align-options="right"]) .options{
|
||||
right: 0;
|
||||
}
|
||||
.options{
|
||||
top: 100%;
|
||||
padding: 0.3rem;
|
||||
overflow: hidden auto;
|
||||
position: absolute;
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
min-width: -webkit-max-content;
|
||||
min-width: -moz-max-content;
|
||||
min-width: max-content;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-box-direction: normal;
|
||||
-ms-flex-direction: column;
|
||||
flex-direction: column;
|
||||
background: var(--background, rgba(var(--background-color), 1));
|
||||
border-radius: var(--border-radius, 0.5rem);
|
||||
z-index: 1;
|
||||
-webkit-box-shadow: 0 0.5rem 1.5rem -0.5rem rgba(0,0,0,0.3);
|
||||
box-shadow: 0 0.5rem 1.5rem -0.5rem rgba(0,0,0,0.3);
|
||||
bottom: auto;
|
||||
}
|
||||
.hide{
|
||||
display: none;
|
||||
}
|
||||
@media screen and (max-width: 640px){
|
||||
.options{
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
top: auto;
|
||||
border-radius: 0.5rem 0.5rem 0 0;
|
||||
}
|
||||
}
|
||||
@media (hover: hover){
|
||||
.menu:hover .icon{
|
||||
background: rgba(var(--text-color), 0.1);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<div class="select">
|
||||
<div class="menu" tabindex="0">
|
||||
<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 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"/></svg>
|
||||
</div>
|
||||
<div class="options hide">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>`;
|
||||
customElements.define('sm-menu', class extends HTMLElement {
|
||||
constructor() {
|
||||
super()
|
||||
this.attachShadow({
|
||||
mode: 'open'
|
||||
}).append(smMenu.content.cloneNode(true))
|
||||
|
||||
this.isOpen = false;
|
||||
this.availableOptions
|
||||
this.containerDimensions
|
||||
this.animOptions = {
|
||||
duration: 200,
|
||||
easing: 'ease'
|
||||
}
|
||||
|
||||
this.optionList = this.shadowRoot.querySelector('.options')
|
||||
this.menu = this.shadowRoot.querySelector('.menu')
|
||||
this.icon = this.shadowRoot.querySelector('.icon')
|
||||
|
||||
this.expand = this.expand.bind(this)
|
||||
this.collapse = this.collapse.bind(this)
|
||||
this.toggle = this.toggle.bind(this)
|
||||
this.handleKeyDown = this.handleKeyDown.bind(this)
|
||||
this.handleClickOutside = this.handleClickOutside.bind(this)
|
||||
|
||||
}
|
||||
static get observedAttributes() {
|
||||
return ['value']
|
||||
}
|
||||
get value() {
|
||||
return this.getAttribute('value')
|
||||
}
|
||||
set value(val) {
|
||||
this.setAttribute('value', val)
|
||||
}
|
||||
expand() {
|
||||
if (!this.isOpen) {
|
||||
this.optionList.classList.remove('hide')
|
||||
this.optionList.animate([
|
||||
{
|
||||
transform: window.innerWidth < 640 ? 'translateY(1.5rem)' : 'translateY(-1rem)',
|
||||
opacity: '0'
|
||||
},
|
||||
{
|
||||
transform: 'none',
|
||||
opacity: '1'
|
||||
},
|
||||
], this.animOptions)
|
||||
.onfinish = () => {
|
||||
this.isOpen = true
|
||||
this.icon.classList.add('focused')
|
||||
}
|
||||
}
|
||||
}
|
||||
collapse() {
|
||||
if (this.isOpen) {
|
||||
this.optionList.animate([
|
||||
{
|
||||
transform: 'none',
|
||||
opacity: '1'
|
||||
},
|
||||
{
|
||||
transform: window.innerWidth < 640 ? 'translateY(1.5rem)' : 'translateY(-1rem)',
|
||||
opacity: '0'
|
||||
},
|
||||
], this.animOptions)
|
||||
.onfinish = () => {
|
||||
this.isOpen = false
|
||||
this.icon.classList.remove('focused')
|
||||
this.optionList.classList.add('hide')
|
||||
}
|
||||
}
|
||||
}
|
||||
toggle() {
|
||||
if (!this.isOpen) {
|
||||
this.expand()
|
||||
} else {
|
||||
this.collapse()
|
||||
}
|
||||
}
|
||||
handleKeyDown(e) {
|
||||
// If key is pressed on menu button
|
||||
if (e.target === this) {
|
||||
if (e.code === 'ArrowDown') {
|
||||
e.preventDefault()
|
||||
this.availableOptions[0].focus()
|
||||
}
|
||||
else if (e.code === 'Enter' || e.code === 'Space') {
|
||||
e.preventDefault()
|
||||
this.toggle()
|
||||
}
|
||||
} else { // If key is pressed over menu options
|
||||
if (e.code === 'ArrowUp') {
|
||||
e.preventDefault()
|
||||
if (document.activeElement.previousElementSibling) {
|
||||
document.activeElement.previousElementSibling.focus()
|
||||
} else {
|
||||
this.availableOptions[this.availableOptions.length - 1].focus()
|
||||
}
|
||||
}
|
||||
else if (e.code === 'ArrowDown') {
|
||||
e.preventDefault()
|
||||
if (document.activeElement.nextElementSibling) {
|
||||
document.activeElement.nextElementSibling.focus()
|
||||
} else {
|
||||
this.availableOptions[0].focus()
|
||||
}
|
||||
}
|
||||
else if (e.code === 'Enter' || e.code === 'Space') {
|
||||
e.preventDefault()
|
||||
e.target.click()
|
||||
}
|
||||
}
|
||||
}
|
||||
handleClickOutside(e) {
|
||||
if (!this.contains(e.target) && e.button !== 2) {
|
||||
this.collapse()
|
||||
}
|
||||
}
|
||||
connectedCallback() {
|
||||
this.setAttribute('role', 'listbox')
|
||||
this.setAttribute('aria-label', 'dropdown menu')
|
||||
const slot = this.shadowRoot.querySelector('.options slot')
|
||||
slot.addEventListener('slotchange', e => {
|
||||
this.availableOptions = e.target.assignedElements()
|
||||
this.containerDimensions = this.optionList.getBoundingClientRect()
|
||||
});
|
||||
this.addEventListener('click', this.toggle)
|
||||
this.addEventListener('keydown', this.handleKeyDown)
|
||||
document.addEventListener('mousedown', this.handleClickOutside)
|
||||
}
|
||||
disconnectedCallback() {
|
||||
this.removeEventListener('click', this.toggle)
|
||||
this.removeEventListener('keydown', this.handleKeyDown)
|
||||
document.removeEventListener('mousedown', this.handleClickOutside)
|
||||
}
|
||||
})
|
||||
|
||||
// option
|
||||
const menuOption = document.createElement('template')
|
||||
menuOption.innerHTML = `
|
||||
<style>
|
||||
*{
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
:host{
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
}
|
||||
.option{
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
min-width: 100%;
|
||||
padding: var(--padding, 0.6rem 1rem);
|
||||
cursor: pointer;
|
||||
overflow-wrap: break-word;
|
||||
white-space: nowrap;
|
||||
outline: none;
|
||||
user-select: none;
|
||||
border-radius: 0.3rem;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
}
|
||||
:host(:focus){
|
||||
outline: none;
|
||||
background: rgba(var(--text-color), 0.1);
|
||||
}
|
||||
@media (any-hover: hover){
|
||||
.option{
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
.option:hover{
|
||||
background: rgba(var(--text-color), 0.1);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<div class="option">
|
||||
<slot></slot>
|
||||
</div>`;
|
||||
customElements.define('menu-option', class extends HTMLElement {
|
||||
constructor() {
|
||||
super()
|
||||
this.attachShadow({
|
||||
mode: 'open'
|
||||
}).append(menuOption.content.cloneNode(true))
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.setAttribute('role', 'option')
|
||||
this.setAttribute('tabindex', '0')
|
||||
this.addEventListener('keyup', e => {
|
||||
if (e.code === 'Enter' || e.code === 'Space') {
|
||||
e.preventDefault()
|
||||
this.click()
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
102
css/main.css
102
css/main.css
@ -22,8 +22,7 @@ body {
|
||||
}
|
||||
body,
|
||||
body * {
|
||||
--accent-color: #504dff;
|
||||
--accent-color--light: #eeeeff;
|
||||
--accent-color: rgb(0, 156, 78);
|
||||
--text-color: 36, 36, 36;
|
||||
--background-color: 248, 248, 248;
|
||||
--foreground-color: rgb(255, 255, 255);
|
||||
@ -36,8 +35,7 @@ body * {
|
||||
|
||||
body[data-theme=dark],
|
||||
body[data-theme=dark] * {
|
||||
--accent-color: #a3a1ff;
|
||||
--accent-color--light: rgba(142, 140, 255, 0.06);
|
||||
--accent-color: rgb(14, 230, 122);
|
||||
--text-color: 230, 230, 230;
|
||||
--text-color-light: 170, 170, 170;
|
||||
--background-color: 10, 10, 10;
|
||||
@ -501,6 +499,14 @@ sm-checkbox {
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
sm-menu {
|
||||
--background: var(--foreground-color);
|
||||
}
|
||||
|
||||
menu-option {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.warning {
|
||||
background-color: khaki;
|
||||
color: rgba(0, 0, 0, 0.7);
|
||||
@ -524,11 +530,11 @@ sm-checkbox {
|
||||
#main_header {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
padding: 1rem 1.5rem;
|
||||
padding: 1rem;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
grid-template-columns: auto 1fr auto auto;
|
||||
grid-template-columns: 1fr auto auto;
|
||||
grid-column: 1/-1;
|
||||
background-color: var(--foreground-color);
|
||||
}
|
||||
@ -556,6 +562,24 @@ sm-checkbox {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
#article_list_popup {
|
||||
--width: min(64rem, 100%);
|
||||
--min-height: 70vh;
|
||||
}
|
||||
#article_list_popup::part(popup-header), #article_list_popup::part(popup-body) {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
#article_list_popup .popup__header {
|
||||
padding: 1rem;
|
||||
gap: 1rem;
|
||||
padding-bottom: 0;
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
|
||||
#article_list {
|
||||
grid-template-columns: repeat(auto-fill, minmax(30ch, 1fr));
|
||||
}
|
||||
|
||||
.article-link {
|
||||
padding: 1rem;
|
||||
color: rgba(var(--text-color), 0.8);
|
||||
@ -577,6 +601,40 @@ sm-checkbox {
|
||||
color: rgba(var(--text-color), 0.8);
|
||||
}
|
||||
|
||||
#edit_sections_popup {
|
||||
--body-padding: 1.2rem;
|
||||
}
|
||||
|
||||
#section_list_container {
|
||||
background-color: rgba(var(--text-color), 0.06);
|
||||
margin: 1rem 0;
|
||||
padding: 0 0.8rem;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
.section-card {
|
||||
font-weight: 500;
|
||||
}
|
||||
.section-card:not(.section-card--new) {
|
||||
padding: 0.8rem 0;
|
||||
}
|
||||
.section-card--new input {
|
||||
border: none;
|
||||
font-size: inherit;
|
||||
background: inherit;
|
||||
color: inherit;
|
||||
width: 100%;
|
||||
padding: 0.8rem 0;
|
||||
}
|
||||
.section-card--new input:focus {
|
||||
outline: var(--accent-color) solid;
|
||||
}
|
||||
|
||||
#insert_section_button {
|
||||
-ms-flex-item-align: start;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
#article_wrapper {
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
@ -608,7 +666,7 @@ sm-checkbox {
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
gap: 1rem;
|
||||
overflow-x: auto;
|
||||
-ms-flex-negative: 0;
|
||||
flex-shrink: 0;
|
||||
@ -623,7 +681,7 @@ sm-checkbox {
|
||||
}
|
||||
|
||||
.content-card {
|
||||
scroll-snap-align: center;
|
||||
scroll-snap-align: start;
|
||||
width: min(46ch, 100%);
|
||||
-ms-flex-negative: 0;
|
||||
flex-shrink: 0;
|
||||
@ -806,7 +864,8 @@ sm-checkbox {
|
||||
background-color: #00e67650;
|
||||
}
|
||||
.entry__changes .removed {
|
||||
background-color: #ff3a4a50;
|
||||
-webkit-text-decoration-color: red;
|
||||
text-decoration-color: red;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 40rem) and (any-hover: none) {
|
||||
@ -819,6 +878,17 @@ sm-checkbox {
|
||||
--padding: 0.9rem 1.6rem;
|
||||
}
|
||||
|
||||
#article_name_wrapper {
|
||||
grid-row: 2/3;
|
||||
grid-column: 1/-1;
|
||||
-webkit-box-pack: start;
|
||||
-ms-flex-pack: start;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
#article_name_wrapper sm-menu {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.hide-on-mobile {
|
||||
display: none;
|
||||
}
|
||||
@ -861,6 +931,11 @@ sm-checkbox {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#main_header {
|
||||
padding: 1rem 1.5rem;
|
||||
grid-template-columns: auto 1fr auto auto;
|
||||
}
|
||||
|
||||
#main_page.active-sidebar {
|
||||
height: 100%;
|
||||
overflow-y: hidden;
|
||||
@ -871,6 +946,15 @@ sm-checkbox {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
#article_list_popup .popup__header {
|
||||
grid-template-columns: auto 1fr auto;
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
#article_list_search {
|
||||
width: 16rem;
|
||||
}
|
||||
}
|
||||
@media (any-hover: hover) {
|
||||
::-webkit-scrollbar {
|
||||
|
||||
2
css/main.min.css
vendored
2
css/main.min.css
vendored
File diff suppressed because one or more lines are too long
@ -18,8 +18,7 @@ body {
|
||||
body {
|
||||
&,
|
||||
* {
|
||||
--accent-color: #504dff;
|
||||
--accent-color--light: #eeeeff;
|
||||
--accent-color: rgb(0, 156, 78);
|
||||
--text-color: 36, 36, 36;
|
||||
--background-color: 248, 248, 248;
|
||||
--foreground-color: rgb(255, 255, 255);
|
||||
@ -37,8 +36,7 @@ body {
|
||||
body[data-theme="dark"] {
|
||||
&,
|
||||
* {
|
||||
--accent-color: #a3a1ff;
|
||||
--accent-color--light: rgba(142, 140, 255, 0.06);
|
||||
--accent-color: rgb(14, 230, 122);
|
||||
--text-color: 230, 230, 230;
|
||||
--text-color-light: 170, 170, 170;
|
||||
--background-color: 10, 10, 10;
|
||||
@ -440,6 +438,12 @@ sm-checkbox {
|
||||
--width: 1rem;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
sm-menu {
|
||||
--background: var(--foreground-color);
|
||||
}
|
||||
menu-option {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
.warning {
|
||||
background-color: khaki;
|
||||
color: rgba(0, 0, 0, 0.7);
|
||||
@ -461,9 +465,9 @@ sm-checkbox {
|
||||
#main_header {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
padding: 1rem 1.5rem;
|
||||
padding: 1rem;
|
||||
align-items: center;
|
||||
grid-template-columns: auto 1fr auto auto;
|
||||
grid-template-columns: 1fr auto auto;
|
||||
grid-column: 1/-1;
|
||||
background-color: var(--foreground-color);
|
||||
}
|
||||
@ -489,6 +493,24 @@ sm-checkbox {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
#article_list_popup {
|
||||
--width: min(64rem, 100%);
|
||||
--min-height: 70vh;
|
||||
&::part(popup-header),
|
||||
&::part(popup-body) {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
.popup__header {
|
||||
padding: 1rem;
|
||||
gap: 1rem;
|
||||
padding-bottom: 0;
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
}
|
||||
#article_list {
|
||||
grid-template-columns: repeat(auto-fill, minmax(30ch, 1fr));
|
||||
}
|
||||
|
||||
.article-link {
|
||||
padding: 1rem;
|
||||
color: rgba(var(--text-color), 0.8);
|
||||
@ -509,6 +531,38 @@ sm-checkbox {
|
||||
color: rgba(var(--text-color), 0.8);
|
||||
}
|
||||
|
||||
#edit_sections_popup {
|
||||
--body-padding: 1.2rem;
|
||||
}
|
||||
#section_list_container {
|
||||
background-color: rgba(var(--text-color), 0.06);
|
||||
margin: 1rem 0;
|
||||
padding: 0 0.8rem;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
.section-card {
|
||||
font-weight: 500;
|
||||
&:not(.section-card--new) {
|
||||
padding: 0.8rem 0;
|
||||
}
|
||||
&--new {
|
||||
input {
|
||||
border: none;
|
||||
font-size: inherit;
|
||||
background: inherit;
|
||||
color: inherit;
|
||||
width: 100%;
|
||||
padding: 0.8rem 0;
|
||||
&:focus {
|
||||
outline: var(--accent-color) solid;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#insert_section_button {
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
#article_wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -531,7 +585,7 @@ sm-checkbox {
|
||||
|
||||
.article-section {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
gap: 1rem;
|
||||
overflow-x: auto;
|
||||
flex-shrink: 0;
|
||||
scroll-snap-type: x mandatory;
|
||||
@ -543,7 +597,7 @@ sm-checkbox {
|
||||
}
|
||||
}
|
||||
.content-card {
|
||||
scroll-snap-align: center;
|
||||
scroll-snap-align: start;
|
||||
width: min(46ch, 100%);
|
||||
flex-shrink: 0;
|
||||
border-radius: 0.5rem;
|
||||
@ -704,7 +758,7 @@ sm-checkbox {
|
||||
background-color: #00e67650;
|
||||
}
|
||||
.removed {
|
||||
background-color: #ff3a4a50;
|
||||
text-decoration-color: red;
|
||||
}
|
||||
}
|
||||
|
||||
@ -719,6 +773,14 @@ sm-checkbox {
|
||||
sm-button {
|
||||
--padding: 0.9rem 1.6rem;
|
||||
}
|
||||
#article_name_wrapper {
|
||||
grid-row: 2/3;
|
||||
grid-column: 1/-1;
|
||||
justify-content: flex-start;
|
||||
sm-menu {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
.hide-on-mobile {
|
||||
display: none;
|
||||
}
|
||||
@ -755,6 +817,10 @@ sm-checkbox {
|
||||
.hide-on-desktop {
|
||||
display: none;
|
||||
}
|
||||
#main_header {
|
||||
padding: 1rem 1.5rem;
|
||||
grid-template-columns: auto 1fr auto auto;
|
||||
}
|
||||
#main_page {
|
||||
&.active-sidebar {
|
||||
height: 100%;
|
||||
@ -767,6 +833,15 @@ sm-checkbox {
|
||||
}
|
||||
}
|
||||
}
|
||||
#article_list_popup {
|
||||
.popup__header {
|
||||
grid-template-columns: auto 1fr auto;
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
}
|
||||
#article_list_search {
|
||||
width: 16rem;
|
||||
}
|
||||
}
|
||||
@media (any-hover: hover) {
|
||||
::-webkit-scrollbar {
|
||||
|
||||
259
index.html
259
index.html
@ -12,6 +12,7 @@
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400..700&display=swap" rel="stylesheet">
|
||||
<script src="purify.min.js" defer></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/fuse.js@6.4.6" defer></script>
|
||||
<script id="floGlobals">
|
||||
/* Constants for FLO blockchain operations !!Make sure to add this at begining!! */
|
||||
const floGlobals = {
|
||||
@ -71,23 +72,36 @@
|
||||
<h4>Content Collab</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div id="article_name_wrapper" class="flex align-center justify-center">
|
||||
<button title="Create new article" class="button__icon--left"
|
||||
onclick="showPopup('create_article_popup')">
|
||||
<div id="article_name_wrapper" class="flex gap-0-5 align-center justify-center">
|
||||
<button title="Show all articles list" onclick="showPopup('article_list_popup')">
|
||||
<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="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" />
|
||||
<path
|
||||
d="M4 8h4V4H4v4zm6 12h4v-4h-4v4zm-6 0h4v-4H4v4zm0-6h4v-4H4v4zm6 0h4v-4h-4v4zm6-10v4h4V4h-4zm-6 4h4V4h-4v4zm6 6h4v-4h-4v4zm0 6h4v-4h-4v4z" />
|
||||
</svg>
|
||||
</button>
|
||||
<text-field id="current_article_title"></text-field>
|
||||
<button class="button__icon--right" onclick="showPopup('article_list_popup')">
|
||||
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px"
|
||||
fill="#000000">
|
||||
<path d="M24 24H0V0h24v24z" fill="none" opacity=".87" />
|
||||
<path d="M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6-1.41-1.41z" />
|
||||
</svg>
|
||||
</button>
|
||||
<sm-menu align-options="right">
|
||||
<menu-option onclick="showPopup('create_article_popup')">
|
||||
<svg class="icon button__icon--left" 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="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" />
|
||||
</svg>
|
||||
Create new article
|
||||
</menu-option>
|
||||
<menu-option onclick="showPopup('edit_sections_popup')">
|
||||
<svg class="icon button__icon--left" title="edit" xmlns="http://www.w3.org/2000/svg"
|
||||
height="24px" viewBox="0 0 24 24" width="24px" fill="#000000">
|
||||
<path d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path
|
||||
d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z">
|
||||
</path>
|
||||
</svg>
|
||||
Edit sections
|
||||
</menu-option>
|
||||
</sm-menu>
|
||||
</div>
|
||||
<theme-toggle></theme-toggle>
|
||||
<button onclick="showPopup('user_popup')">
|
||||
@ -134,25 +148,6 @@
|
||||
</button>
|
||||
</div>
|
||||
<div id="article_wrapper"></div>
|
||||
<!-- <div class="grid page-layout">
|
||||
<div id="action_button_group" class="flex align-center gap-0-5">
|
||||
<span>
|
||||
<b>
|
||||
Add
|
||||
</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">
|
||||
<path d="M0 0h24v24H0V0z" fill="none" />
|
||||
<path d="M14 17H4v2h10v-2zm6-8H4v2h16V9zM4 15h16v-2H4v2zM4 5v2h16V5H4z" />
|
||||
</svg>
|
||||
<span class="actionable-button__title">
|
||||
Section
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div> -->
|
||||
<aside id="version_history_panel" class="flex direction-column hide-completely">
|
||||
<div class="flex align-center space-between">
|
||||
<div class="flex align-center">
|
||||
@ -178,18 +173,24 @@
|
||||
</article>
|
||||
<sm-popup id="article_list_popup">
|
||||
<header slot="header" class="popup__header">
|
||||
<div class="grid">
|
||||
<div class="flex align-center space-between">
|
||||
<h3>All articles</h3>
|
||||
<sm-select label="Sort by:">
|
||||
<sm-option value="time">Date created</sm-option>
|
||||
<sm-option value="az">A-Z</sm-option>
|
||||
</sm-select>
|
||||
</div>
|
||||
<sm-input placeholder="Search articles" type="search"></sm-input>
|
||||
</div>
|
||||
<h3>All articles</h3>
|
||||
<sm-input id="article_list_search" placeholder="Search articles" type="search" autofocus>
|
||||
<svg slot="icon" class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24"
|
||||
width="24px" fill="#000000">
|
||||
<path d="M0 0h24v24H0z" fill="none" />
|
||||
<path
|
||||
d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z" />
|
||||
</svg>
|
||||
</sm-input>
|
||||
<sm-select id="sort_article_list" label="Sort by:">
|
||||
<sm-option value="time">Most recent</sm-option>
|
||||
<sm-option value="az">A-Z</sm-option>
|
||||
</sm-select>
|
||||
</header>
|
||||
<div id="article_list"></div>
|
||||
<div id="article_list" class="observe-empty-state grid"></div>
|
||||
<h4 class="empty-state">
|
||||
No related article
|
||||
</h4>
|
||||
</sm-popup>
|
||||
<sm-popup id="create_article_popup">
|
||||
<header slot="header" class="popup__header">
|
||||
@ -204,7 +205,7 @@
|
||||
<h3>Create article</h3>
|
||||
</header>
|
||||
<sm-form>
|
||||
<sm-input placeholder="Article title" required></sm-input>
|
||||
<sm-input placeholder="Article title" required autofocus></sm-input>
|
||||
<sm-checkbox checked>
|
||||
<div class="grid button__icon--right gap-0-5">
|
||||
Set as default
|
||||
@ -221,6 +222,44 @@
|
||||
<sm-button variant="primary">Create</sm-button>
|
||||
</sm-form>
|
||||
</sm-popup>
|
||||
<sm-popup id="edit_sections_popup" open>
|
||||
<header slot="header" class="popup__header">
|
||||
<button class="popup__header__close" onclick="hidePopup()">
|
||||
<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="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z" />
|
||||
</svg>
|
||||
</button>
|
||||
<h3>Edit sections</h3>
|
||||
</header>
|
||||
<h5 class="label">Existing sections</h5>
|
||||
<div id="section_list_container" class="observe-empty-state"></div>
|
||||
<p class="empty-state">
|
||||
There are no sections so far, you can add section with button below.
|
||||
</p>
|
||||
<button id="insert_section_button" onclick="insertEmptySection()" class="button">
|
||||
<svg class="icon button__icon--left" 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" />
|
||||
</g>
|
||||
<g>
|
||||
<g />
|
||||
<g>
|
||||
<path
|
||||
d="M17,19.22H5V7h7V5H5C3.9,5,3,5.9,3,7v12c0,1.1,0.9,2,2,2h12c1.1,0,2-0.9,2-2v-7h-2V19.22z" />
|
||||
<path d="M19,2h-2v3h-3c0.01,0.01,0,2,0,2h3v2.99c0.01,0.01,2,0,2,0V7h3V5h-3V2z" />
|
||||
<rect height="2" width="8" x="7" y="9" />
|
||||
<polygon points="7,12 7,14 15,14 15,12 12,12" />
|
||||
<rect height="2" width="8" x="7" y="15" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
Add new section
|
||||
</button>
|
||||
</sm-popup>
|
||||
<template id="section_template">
|
||||
<text-field class="heading"></text-field>
|
||||
<section class="article-section">
|
||||
@ -263,10 +302,11 @@
|
||||
<template id="quote_template">
|
||||
<figure class="quote-template grid gap-0-5" contenteditable="false">
|
||||
<blockquote class="flex">
|
||||
<p class="quote" contenteditable="true"></p>
|
||||
<p class="quote" contenteditable="true">Words can be like X-rays, if you use them properly—they’ll go
|
||||
through anything. You read and you’re pierced.</p>
|
||||
</blockquote>
|
||||
<figcaption class="flex"><span class="by" contenteditable="true"></span><cite class="citation"
|
||||
contenteditable="true"></cite></figcaption>
|
||||
<figcaption class="flex"><span class="by" contenteditable="true">Aldous Huxley</span><cite class="citation"
|
||||
contenteditable="true">Brave New World</cite></figcaption>
|
||||
</figure>
|
||||
<p><br></p>
|
||||
</template>
|
||||
@ -504,7 +544,6 @@
|
||||
|
||||
window.addEventListener('hashchange', e => showPage(window.location.hash))
|
||||
window.addEventListener("load", () => {
|
||||
showPage(window.location.hash)
|
||||
document.body.classList.remove('hide-completely')
|
||||
document.querySelectorAll('sm-input[data-private-key]').forEach(input => input.customValidation = floCrypto.getPubKeyHex)
|
||||
document.addEventListener('keyup', (e) => {
|
||||
@ -525,7 +564,8 @@
|
||||
async function showPage(targetPage, options = {}) {
|
||||
const { firstLoad, hashChange } = options
|
||||
let pageId
|
||||
let params
|
||||
let params = {}
|
||||
let searchParams
|
||||
if (targetPage === '') {
|
||||
if (typeof myFloID === "undefined") {
|
||||
pageId = 'landing'
|
||||
@ -549,8 +589,26 @@
|
||||
pageId = targetPage
|
||||
}
|
||||
}
|
||||
if (searchParams) {
|
||||
const urlSearchParams = new URLSearchParams('?' + searchParams);
|
||||
params = Object.fromEntries(urlSearchParams.entries());
|
||||
pagesData.params = params
|
||||
}
|
||||
switch (pageId) {
|
||||
case 'home':
|
||||
if (!params.articleID)
|
||||
params['articleID'] = floGlobals.appObjects.cc.defaultArticle
|
||||
Promise.all([
|
||||
floCloudAPI.requestObjectData(params.articleID),
|
||||
floCloudAPI.requestGeneralData(`${params.articleID}_gd`)
|
||||
]).then(() => {
|
||||
hidePopup()
|
||||
render.article(params.articleID)
|
||||
window.history.replaceState('', '', `#/home?articleID=${params.articleID}`)
|
||||
})
|
||||
break
|
||||
}
|
||||
if (pagesData.lastPage !== pageId) {
|
||||
document.querySelectorAll('.mobile-page').forEach(elem => elem.classList.add('hide-on-mobile'))
|
||||
pagesData.lastPage = pageId
|
||||
if (!pagesData.openedPages.includes(pageId)) {
|
||||
pagesData.openedPages.push(pageId)
|
||||
@ -872,20 +930,23 @@
|
||||
const { title } = floGlobals.appObjects.cc.articleList[id]
|
||||
const { writer, sections } = currentArticle
|
||||
const frag = document.createDocumentFragment()
|
||||
const isSubAdmin = floGlobals.subAdmins.includes(myFloID)
|
||||
let index = 0
|
||||
for (const sectionID in sections) {
|
||||
frag.append(render.section(sectionID, sections[sectionID], index))
|
||||
index += 1
|
||||
}
|
||||
if (!floGlobals.subAdmins.includes(myFloID)) {
|
||||
if (!isSubAdmin) {
|
||||
getRef('current_article_title').setAttribute('disabled', '')
|
||||
} else {
|
||||
renderSectionList()
|
||||
}
|
||||
getRef('current_article_title').value = title
|
||||
getRef('article_wrapper').innerHTML = ''
|
||||
getRef('article_wrapper').append(frag)
|
||||
},
|
||||
articleLink(uid, details, isDefaultArticle) {
|
||||
const { timestamp, title } = details
|
||||
articleLink(details, isDefaultArticle) {
|
||||
const { uid, timestamp, title } = details
|
||||
const link = createElement('a', {
|
||||
textContent: title,
|
||||
attributes: { href: `#/home?articleID=${uid}` },
|
||||
@ -894,7 +955,7 @@
|
||||
if (isDefaultArticle) {
|
||||
link.append(createElement('span', {
|
||||
className: 'default-article',
|
||||
textContent: 'Active'
|
||||
textContent: 'Actively written'
|
||||
}))
|
||||
}
|
||||
return link
|
||||
@ -933,7 +994,7 @@
|
||||
}
|
||||
if (i - index > 1)
|
||||
changed.splice((index + 1), (i - index - 1))
|
||||
return `<span class="${type ? 'added' : 'removed'}">${consecutiveWords.join(' ')}</span>`
|
||||
return `<s${type ? 'pan' : ''} class="${type ? 'added' : 'removed'}">${consecutiveWords.join(' ')}</s${type ? 'pan' : ''}>`
|
||||
} else return word
|
||||
})
|
||||
clone.querySelector('.entry__changes').innerHTML = DOMPurify.sanitize(final.join(' '))
|
||||
@ -959,6 +1020,16 @@
|
||||
section.querySelector('.article-section').append(frag)
|
||||
return section
|
||||
},
|
||||
sectionCard(details) {
|
||||
const { title } = details
|
||||
return createElement('div', {
|
||||
className: 'section-card flex align-center',
|
||||
innerHTML: `
|
||||
<svg class="icon button__icon--left" 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"/></g><g><path d="M9,18h12v-2H9V18z M3,6v2h18V6H3z M9,13h12v-2H9V13z"/></g></svg>
|
||||
${title}
|
||||
`
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function parseArticleData() {
|
||||
@ -994,18 +1065,74 @@
|
||||
currentArticle.uniqueEntries[entry]['iterations'].sort((a, b) => a.timestamp - b.timestamp)
|
||||
}
|
||||
}
|
||||
function renderSectionList() {
|
||||
const frag = document.createDocumentFragment()
|
||||
floGlobals.appObjects[currentArticle.id].sections.forEach(section => {
|
||||
frag.append(render.sectionCard(section))
|
||||
})
|
||||
getRef('section_list_container').innerHTML = ''
|
||||
getRef('section_list_container').append(frag)
|
||||
}
|
||||
function insertEmptySection() {
|
||||
const emptySection = createElement('div', {
|
||||
className: 'section-card section-card--new flex align-center',
|
||||
innerHTML: `
|
||||
<svg class="icon button__icon--left" 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"/></g><g><path d="M9,18h12v-2H9V18z M3,6v2h18V6H3z M9,13h12v-2H9V13z"/></g></svg>
|
||||
<input placeholder="New section title"/>
|
||||
<button class="remove">
|
||||
<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>
|
||||
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
`
|
||||
})
|
||||
getRef('section_list_container').append(emptySection)
|
||||
}
|
||||
|
||||
function renderArticleList() {
|
||||
function getArticleList() {
|
||||
const articleList = floGlobals.appObjects.cc.articleList
|
||||
const sortBy = getRef('sort_article_list').value
|
||||
const arrayOfObject = []
|
||||
for (const key in articleList) {
|
||||
const { timestamp, title } = articleList[key]
|
||||
arrayOfObject.push({
|
||||
uid: key,
|
||||
timestamp,
|
||||
title
|
||||
})
|
||||
}
|
||||
if (sortBy === 'az') {
|
||||
arrayOfObject.sort((a, b) => a.title.localeCompare(b.title, 'en', { numeric: true, sensitivity: 'base' }))
|
||||
} else {
|
||||
arrayOfObject.sort((a, b) => b.timestamp - a.timestamp)
|
||||
}
|
||||
return arrayOfObject
|
||||
}
|
||||
|
||||
getRef('article_list_search').addEventListener('input', e => {
|
||||
const searchKey = e.target.value.trim()
|
||||
const options = {
|
||||
keys: ['title'],
|
||||
threshold: 0
|
||||
}
|
||||
const fuse = new Fuse(getArticleList(), options)
|
||||
renderArticleList(searchKey === '' ? undefined : fuse.search(searchKey).map(v => v.item))
|
||||
})
|
||||
getRef('sort_article_list').addEventListener('change', e => { renderArticleList() })
|
||||
|
||||
function renderArticleList(articleList) {
|
||||
getRef('article_list').innerHTML = ``
|
||||
const frag = document.createDocumentFragment()
|
||||
const { articleList, defaultArticle } = floGlobals.appObjects.cc
|
||||
for (articleKey in articleList) {
|
||||
const isDefaultArticle = defaultArticle === articleKey
|
||||
frag.prepend(render.articleLink(articleKey, articleList[articleKey], isDefaultArticle))
|
||||
}
|
||||
const defaultArticle = floGlobals.appObjects.cc.defaultArticle;
|
||||
(articleList || getArticleList()).forEach((article) => {
|
||||
const isDefaultArticle = defaultArticle === article.uid
|
||||
frag.append(render.articleLink(article, isDefaultArticle))
|
||||
})
|
||||
getRef('article_list').append(frag)
|
||||
}
|
||||
|
||||
|
||||
function getIterationDetails(uid, targetIndex) {
|
||||
let merged
|
||||
const contributors = new Set()
|
||||
@ -1111,7 +1238,7 @@
|
||||
`${origin.substring(0, startIndex)}${insertion}${origin.substring(endIndex)}`;
|
||||
const make = {
|
||||
bold() {
|
||||
document.execCommand('bold')
|
||||
replaceSelectedText(createElement('strong'))
|
||||
},
|
||||
italic() {
|
||||
document.execCommand('italic')
|
||||
@ -1221,7 +1348,7 @@
|
||||
function createQuote() {
|
||||
const quote = getRef('quote_template').content.cloneNode(true)
|
||||
quote.querySelector('.quote').textContent = '"Words can be like X-rays, if you use them properly—they’ll go through anything. You read and you’re pierced."'
|
||||
quote.querySelector('.by').textContent = `- Author`
|
||||
quote.querySelector('.by').textContent = `- Aldous Huxley,`
|
||||
quote.querySelector('.citation').textContent = 'Brave New World'
|
||||
return quote
|
||||
}
|
||||
@ -1240,14 +1367,8 @@
|
||||
await Promise.all([
|
||||
floCloudAPI.requestObjectData('cc'),
|
||||
])
|
||||
Promise.all([
|
||||
floCloudAPI.requestObjectData(floGlobals.appObjects.cc.defaultArticle),
|
||||
floCloudAPI.requestGeneralData(`${floGlobals.appObjects.cc.defaultArticle}_gd`)
|
||||
])
|
||||
.then(() => {
|
||||
render.article(floGlobals.appObjects.cc.defaultArticle)
|
||||
renderArticleList()
|
||||
})
|
||||
showPage(window.location.hash)
|
||||
renderArticleList()
|
||||
|
||||
console.log(result)
|
||||
// alert(`Welcome FLO_ID: ${ myFloID }`)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user