Feature update
- added option to mark any open article as default - added new article creation UI and flow - added feature to add section after creation of article
This commit is contained in:
parent
70b13b7f5a
commit
0bee787ccb
489
components.js
489
components.js
@ -74,11 +74,13 @@ smButton.innerHTML = `
|
||||
background-color: rgba(var(--text-color), 0.3);
|
||||
}
|
||||
@media (hover: hover){
|
||||
:host(:not([disabled])) .button:hover{
|
||||
:host(:not([disabled])) .button:hover,
|
||||
:host(:focus-within:not([disabled])) .button{
|
||||
-webkit-box-shadow: 0 0.1rem 0.1rem rgba(0, 0, 0, 0.1), 0 0.2rem 0.8rem rgba(0, 0, 0, 0.12);
|
||||
box-shadow: 0 0.1rem 0.1rem rgba(0, 0, 0, 0.1), 0 0.2rem 0.8rem rgba(0, 0, 0, 0.12);
|
||||
box-shadow: 0 0.1rem 0.1rem rgba(0, 0, 0, 0.1), 0 0.2rem 0.8rem rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
:host([variant='outlined']) .button:hover{
|
||||
:host([variant='outlined']:not([disabled])) .button:hover,
|
||||
:host(:focus-within[variant='outlined']:not([disabled])) .button:hover{
|
||||
-webkit-box-shadow: 0 0 0 1px rgba(var(--text-color), 0.2) inset, 0 0.1rem 0.1rem rgba(0, 0, 0, 0.1), 0 0.4rem 0.8rem rgba(0, 0, 0, 0.12);
|
||||
box-shadow: 0 0 0 1px rgba(var(--text-color), 0.2) inset, 0 0.1rem 0.1rem rgba(0, 0, 0, 0.1), 0 0.4rem 0.8rem rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
@ -120,6 +122,9 @@ customElements.define('sm-button',
|
||||
this.removeAttribute('disabled');
|
||||
}
|
||||
}
|
||||
focusIn() {
|
||||
this.focus();
|
||||
}
|
||||
|
||||
handleKeyDown(e) {
|
||||
if (!this.hasAttribute('disabled') && (e.key === 'Enter' || e.code === 'Space')) {
|
||||
@ -156,12 +161,11 @@ smForm.innerHTML = `
|
||||
}
|
||||
:host{
|
||||
display: flex;
|
||||
--gap: 1rem;
|
||||
width: 100%;
|
||||
}
|
||||
form{
|
||||
display: grid;
|
||||
gap: var(--gap);
|
||||
gap: var(--gap, 1.5rem);
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
@ -986,6 +990,27 @@ customElements.define('sm-notifications', class extends HTMLElement {
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
class Stack {
|
||||
constructor() {
|
||||
this.items = [];
|
||||
}
|
||||
push(element) {
|
||||
this.items.push(element);
|
||||
}
|
||||
pop() {
|
||||
if (this.items.length == 0)
|
||||
return "Underflow";
|
||||
return this.items.pop();
|
||||
}
|
||||
peek() {
|
||||
return this.items[this.items.length - 1];
|
||||
}
|
||||
}
|
||||
const popupStack = new Stack();
|
||||
|
||||
const smPopup = document.createElement('template');
|
||||
smPopup.innerHTML = `
|
||||
<style>
|
||||
@ -1019,10 +1044,6 @@ smPopup.innerHTML = `
|
||||
left: 0;
|
||||
right: 0;
|
||||
place-items: center;
|
||||
background: var(--backdrop-background);
|
||||
-webkit-transition: opacity 0.3s;
|
||||
-o-transition: opacity 0.3s;
|
||||
transition: opacity 0.3s;
|
||||
z-index: 10;
|
||||
touch-action: none;
|
||||
}
|
||||
@ -1030,6 +1051,18 @@ smPopup.innerHTML = `
|
||||
-webkit-transform: scale(0.9) translateY(-2rem) !important;
|
||||
transform: scale(0.9) translateY(-2rem) !important;
|
||||
}
|
||||
.background{
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
pointer-events: none;
|
||||
background: var(--backdrop-background);
|
||||
-webkit-transition: opacity 0.3s;
|
||||
-o-transition: opacity 0.3s;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
.popup{
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
@ -1049,17 +1082,10 @@ smPopup.innerHTML = `
|
||||
min-height: var(--min-height);
|
||||
max-height: 90vh;
|
||||
border-radius: var(--border-radius);
|
||||
-webkit-transform: scale(1) translateY(100%);
|
||||
transform: scale(1) translateY(100%);
|
||||
-webkit-transition: -webkit-transform 0.3s;
|
||||
transition: -webkit-transform 0.3s;
|
||||
-o-transition: transform 0.3s;
|
||||
transition: transform 0.3s, -webkit-transform 0.3s;
|
||||
transition: transform 0.3s;
|
||||
background: rgba(var(--background-color), 1);
|
||||
-webkit-box-shadow: 0 -1rem 2rem #00000020;
|
||||
box-shadow: 0 -1rem 2rem #00000020;
|
||||
content-visibility: auto;
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
.container-header{
|
||||
display: -webkit-box;
|
||||
@ -1090,13 +1116,11 @@ smPopup.innerHTML = `
|
||||
overflow-y: auto;
|
||||
}
|
||||
.hide{
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
visibility: none;
|
||||
display:none;
|
||||
}
|
||||
@media screen and (min-width: 640px){
|
||||
:host{
|
||||
--border-radius: 0.4rem;
|
||||
--border-radius: 0.5rem;
|
||||
}
|
||||
.popup{
|
||||
-ms-flex-item-align: center;
|
||||
@ -1104,8 +1128,6 @@ smPopup.innerHTML = `
|
||||
align-self: center;
|
||||
border-radius: var(--border-radius);
|
||||
height: var(--height);
|
||||
-webkit-transform: scale(1) translateY(3rem);
|
||||
transform: scale(1) translateY(3rem);
|
||||
-webkit-box-shadow: 0 3rem 2rem -0.5rem #00000040;
|
||||
box-shadow: 0 3rem 2rem -0.5rem #00000040;
|
||||
}
|
||||
@ -1140,7 +1162,8 @@ smPopup.innerHTML = `
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<div part="background" class="popup-container hide" role="dialog">
|
||||
<div class="popup-container hide" role="dialog">
|
||||
<div part="background" class="background"></div>
|
||||
<div part="popup" class="popup">
|
||||
<div part="popup-header" class="popup-top">
|
||||
<div class="handle"></div>
|
||||
@ -1162,7 +1185,6 @@ customElements.define('sm-popup', class extends HTMLElement {
|
||||
this.allowClosing = false;
|
||||
this.isOpen = false;
|
||||
this.pinned = false;
|
||||
this.popupStack = undefined;
|
||||
this.offset = 0;
|
||||
this.touchStartY = 0;
|
||||
this.touchEndY = 0;
|
||||
@ -1174,17 +1196,18 @@ customElements.define('sm-popup', class extends HTMLElement {
|
||||
this.mutationObserver
|
||||
|
||||
this.popupContainer = this.shadowRoot.querySelector('.popup-container');
|
||||
this.backdrop = this.shadowRoot.querySelector('.background');
|
||||
this.popup = this.shadowRoot.querySelector('.popup');
|
||||
this.popupBodySlot = this.shadowRoot.querySelector('.popup-body slot');
|
||||
this.popupHeader = this.shadowRoot.querySelector('.popup-top');
|
||||
|
||||
this.resumeScrolling = this.resumeScrolling.bind(this);
|
||||
this.setStateOpen = this.setStateOpen.bind(this);
|
||||
this.show = this.show.bind(this);
|
||||
this.hide = this.hide.bind(this);
|
||||
this.handleTouchStart = this.handleTouchStart.bind(this);
|
||||
this.handleTouchMove = this.handleTouchMove.bind(this);
|
||||
this.handleTouchEnd = this.handleTouchEnd.bind(this);
|
||||
this.movePopup = this.movePopup.bind(this);
|
||||
this.detectFocus = this.detectFocus.bind(this);
|
||||
}
|
||||
|
||||
@ -1196,86 +1219,140 @@ customElements.define('sm-popup', class extends HTMLElement {
|
||||
return this.isOpen;
|
||||
}
|
||||
|
||||
animateTo(element, keyframes, options) {
|
||||
const anime = element.animate(keyframes, { ...options, fill: 'both' })
|
||||
anime.finished.then(() => {
|
||||
anime.commitStyles()
|
||||
anime.cancel()
|
||||
})
|
||||
return anime
|
||||
}
|
||||
|
||||
resumeScrolling() {
|
||||
const scrollY = document.body.style.top;
|
||||
window.scrollTo(0, parseInt(scrollY || '0') * -1);
|
||||
setTimeout(() => {
|
||||
document.body.style.overflow = 'auto';
|
||||
document.body.style.top = 'initial';
|
||||
}, 300);
|
||||
document.body.style.overflow = 'auto';
|
||||
document.body.style.top = 'initial';
|
||||
}
|
||||
|
||||
setStateOpen() {
|
||||
const animOptions = {
|
||||
duration: 300,
|
||||
easing: 'ease'
|
||||
}
|
||||
const initialAnimation = (window.innerWidth > 640) ? 'scale(1.1)' : `translateY(${this.offset ? `${this.offset}px` : '100%'})`
|
||||
this.animateTo(this.popup, [
|
||||
{
|
||||
opacity: this.offset ? 1 : 0,
|
||||
transform: initialAnimation
|
||||
},
|
||||
{
|
||||
opacity: 1,
|
||||
transform: 'none'
|
||||
},
|
||||
], animOptions)
|
||||
}
|
||||
|
||||
show(options = {}) {
|
||||
const { pinned = false, popupStack } = options;
|
||||
if (popupStack)
|
||||
this.popupStack = popupStack;
|
||||
if (this.popupStack && !this.hasAttribute('open')) {
|
||||
this.popupStack.push({
|
||||
popup: this,
|
||||
permission: pinned
|
||||
});
|
||||
if (this.popupStack.items.length > 1) {
|
||||
this.popupStack.items[this.popupStack.items.length - 2].popup.classList.add('stacked');
|
||||
const { pinned = false } = options;
|
||||
if (!this.isOpen) {
|
||||
const animOptions = {
|
||||
duration: 300,
|
||||
easing: 'ease'
|
||||
}
|
||||
if (popupStack) {
|
||||
popupStack.push({
|
||||
popup: this,
|
||||
permission: pinned
|
||||
});
|
||||
if (popupStack.items.length > 1) {
|
||||
this.animateTo(popupStack.items[popupStack.items.length - 2].popup.shadowRoot.querySelector('.popup'), [
|
||||
{ transform: 'none' },
|
||||
{ transform: 'translateY(-1.5rem) scale(0.9)' },
|
||||
], animOptions)
|
||||
}
|
||||
}
|
||||
this.popupContainer.classList.remove('hide');
|
||||
if (!this.offset)
|
||||
this.backdrop.animate([
|
||||
{ opacity: 0 },
|
||||
{ opacity: 1 },
|
||||
], animOptions)
|
||||
this.setStateOpen()
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("popupopened", {
|
||||
bubbles: true,
|
||||
detail: {
|
||||
popup: this,
|
||||
popupStack: this.popupStack
|
||||
}
|
||||
})
|
||||
);
|
||||
this.setAttribute('open', '');
|
||||
this.pinned = pinned;
|
||||
this.isOpen = true;
|
||||
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();
|
||||
if (!this.hasAttribute('open'))
|
||||
this.setAttribute('open', '');
|
||||
}
|
||||
this.popupContainer.classList.remove('hide');
|
||||
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() {
|
||||
if (window.innerWidth < 640)
|
||||
this.popup.style.transform = 'translateY(100%)';
|
||||
else
|
||||
this.popup.style.transform = 'translateY(3rem)';
|
||||
this.popupContainer.classList.add('hide');
|
||||
this.removeAttribute('open');
|
||||
if (typeof this.popupStack !== 'undefined') {
|
||||
this.popupStack.pop();
|
||||
if (this.popupStack.items.length) {
|
||||
this.popupStack.items[this.popupStack.items.length - 1].popup.classList.remove('stacked');
|
||||
} else {
|
||||
this.resumeScrolling();
|
||||
}
|
||||
} else {
|
||||
this.resumeScrolling();
|
||||
const animOptions = {
|
||||
duration: 150,
|
||||
easing: 'ease'
|
||||
}
|
||||
this.backdrop.animate([
|
||||
{ opacity: 1 },
|
||||
{ opacity: 0 }
|
||||
], animOptions)
|
||||
this.animateTo(this.popup, [
|
||||
{
|
||||
opacity: 1,
|
||||
transform: (window.innerWidth > 640) ? 'none' : `translateY(${this.offset ? `${this.offset}px` : '0'})`
|
||||
},
|
||||
{
|
||||
opacity: 0,
|
||||
transform: (window.innerWidth > 640) ? 'scale(1.1)' : 'translateY(100%)'
|
||||
},
|
||||
], animOptions).finished
|
||||
.finally(() => {
|
||||
this.popupContainer.classList.add('hide');
|
||||
this.popup.style = ''
|
||||
this.removeAttribute('open');
|
||||
if (typeof popupStack !== 'undefined') {
|
||||
popupStack.pop();
|
||||
if (popupStack.items.length) {
|
||||
this.animateTo(popupStack.items[popupStack.items.length - 1].popup.shadowRoot.querySelector('.popup'), [
|
||||
{ transform: 'translateY(-1.5rem) scale(0.9)' },
|
||||
{ transform: 'none' },
|
||||
], animOptions)
|
||||
|
||||
if (this.forms.length) {
|
||||
setTimeout(() => {
|
||||
this.forms.forEach(form => form.reset());
|
||||
}, 300);
|
||||
}
|
||||
setTimeout(() => {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("popupclosed", {
|
||||
bubbles: true,
|
||||
detail: {
|
||||
popup: this,
|
||||
popupStack: this.popupStack
|
||||
} else {
|
||||
this.resumeScrolling();
|
||||
}
|
||||
})
|
||||
);
|
||||
this.isOpen = false;
|
||||
}, 300);
|
||||
} else {
|
||||
this.resumeScrolling();
|
||||
}
|
||||
|
||||
if (this.forms.length) {
|
||||
this.forms.forEach(form => form.reset());
|
||||
}
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("popupclosed", {
|
||||
bubbles: true,
|
||||
detail: {
|
||||
popup: this,
|
||||
}
|
||||
})
|
||||
);
|
||||
this.isOpen = false;
|
||||
})
|
||||
}
|
||||
|
||||
handleTouchStart(e) {
|
||||
this.popupHeader.addEventListener('touchmove', this.handleTouchMove, { passive: true });
|
||||
this.popupHeader.addEventListener('touchend', this.handleTouchEnd, { passive: true });
|
||||
this.touchStartY = e.changedTouches[0].clientY;
|
||||
this.popup.style.transition = 'transform 0.1s';
|
||||
this.touchStartTime = e.timeStamp;
|
||||
@ -1284,7 +1361,9 @@ 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.touchEndAnimation = window.requestAnimationFrame(() => this.movePopup());
|
||||
this.touchEndAnimation = window.requestAnimationFrame(() => {
|
||||
this.popup.style.transform = `translateY(${this.offset}px)`;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1297,27 +1376,26 @@ customElements.define('sm-popup', class extends HTMLElement {
|
||||
if (this.touchEndTime - this.touchStartTime > 200) {
|
||||
if (this.touchEndY - this.touchStartY > this.threshold) {
|
||||
if (this.pinned) {
|
||||
this.show();
|
||||
this.setStateOpen();
|
||||
return;
|
||||
} else
|
||||
this.hide();
|
||||
} else {
|
||||
this.show();
|
||||
this.setStateOpen();
|
||||
}
|
||||
} else {
|
||||
if (this.touchEndY > this.touchStartY)
|
||||
if (this.pinned) {
|
||||
this.show();
|
||||
this.setStateOpen();
|
||||
return;
|
||||
}
|
||||
else
|
||||
this.hide();
|
||||
}
|
||||
this.popupHeader.removeEventListener('touchmove', this.handleTouchMove, { passive: true });
|
||||
this.popupHeader.removeEventListener('touchend', this.handleTouchEnd, { passive: true });
|
||||
}
|
||||
|
||||
movePopup() {
|
||||
this.popup.style.transform = `translateY(${this.offset}px)`;
|
||||
}
|
||||
|
||||
detectFocus(e) {
|
||||
if (e.code === 'Tab') {
|
||||
@ -1333,14 +1411,20 @@ customElements.define('sm-popup', class extends HTMLElement {
|
||||
}
|
||||
}
|
||||
|
||||
updateFocusableList() {
|
||||
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]')
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.popupBodySlot.addEventListener('slotchange', () => {
|
||||
this.forms = this.querySelectorAll('sm-form');
|
||||
this.updateFocusableList()
|
||||
});
|
||||
this.popupContainer.addEventListener('mousedown', e => {
|
||||
if (e.target === this.popupContainer && !this.pinned) {
|
||||
if (this.pinned) {
|
||||
this.show();
|
||||
this.setStateOpen();
|
||||
} else
|
||||
this.hide();
|
||||
}
|
||||
@ -1360,25 +1444,18 @@ 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.updateFocusableList()
|
||||
})
|
||||
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()
|
||||
this.popupHeader.removeEventListener('touchstart', this.handleTouchStart, { passive: true });
|
||||
}
|
||||
attributeChangedCallback(name) {
|
||||
if (name === 'open') {
|
||||
@ -2281,7 +2358,7 @@ smSelect.innerHTML = `
|
||||
fill: rgba(var(--text-color), 0.7);
|
||||
}
|
||||
.selected-option-text{
|
||||
font-size: 0.9rem;
|
||||
font-size: inherit;
|
||||
overflow: hidden;
|
||||
-o-text-overflow: ellipsis;
|
||||
text-overflow: ellipsis;
|
||||
@ -2315,6 +2392,7 @@ smSelect.innerHTML = `
|
||||
}
|
||||
.options{
|
||||
top: 100%;
|
||||
padding: var(--options-padding, 0.3rem);
|
||||
margin-top: 0.2rem;
|
||||
overflow: hidden auto;
|
||||
position: absolute;
|
||||
@ -2330,7 +2408,7 @@ smSelect.innerHTML = `
|
||||
max-height: var(--max-height);
|
||||
background: rgba(var(--background-color), 1);
|
||||
border: solid 1px rgba(var(--text-color), 0.2);
|
||||
border-radius: 0.3rem;
|
||||
border-radius: var(--border-radius, 0.5rem);
|
||||
z-index: 2;
|
||||
-webkit-box-shadow: 0.4rem 0.8rem 1.2rem #00000030;
|
||||
box-shadow: 0.4rem 0.8rem 1.2rem #00000030;
|
||||
@ -2600,11 +2678,12 @@ smOption.innerHTML = `
|
||||
width: 100%;
|
||||
gap: 0.5rem;
|
||||
grid-template-columns: max-content minmax(0, 1fr);
|
||||
padding: 0.8rem 1.2rem;
|
||||
padding: var(--padding, 0.6rem 1rem);
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
outline: none;
|
||||
user-select: none;
|
||||
border-radius: var(--border-radius, 0.3rem);
|
||||
}
|
||||
:host(:focus){
|
||||
outline: none;
|
||||
@ -2746,6 +2825,7 @@ customElements.define('sm-checkbox', class extends HTMLElement {
|
||||
mode: 'open'
|
||||
}).append(smCheckbox.content.cloneNode(true))
|
||||
|
||||
this.defaultState
|
||||
this.checkbox = this.shadowRoot.querySelector('.checkbox');
|
||||
|
||||
this.reset = this.reset.bind(this)
|
||||
@ -2796,7 +2876,7 @@ customElements.define('sm-checkbox', class extends HTMLElement {
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.removeAttribute('checked')
|
||||
this.value = this.defaultState
|
||||
}
|
||||
|
||||
dispatch() {
|
||||
@ -2820,6 +2900,7 @@ customElements.define('sm-checkbox', class extends HTMLElement {
|
||||
this.setAttribute('tabindex', '0')
|
||||
}
|
||||
this.setAttribute('role', 'checkbox')
|
||||
this.defaultState = this.hasAttribute('checked')
|
||||
if (!this.hasAttribute('checked')) {
|
||||
this.setAttribute('aria-checked', 'false')
|
||||
}
|
||||
@ -3417,6 +3498,10 @@ customElements.define('sm-switch', class extends HTMLElement {
|
||||
}
|
||||
}
|
||||
|
||||
reset() {
|
||||
|
||||
}
|
||||
|
||||
dispatch() {
|
||||
this.dispatchEvent(new CustomEvent('change', {
|
||||
bubbles: true,
|
||||
@ -4004,4 +4089,204 @@ customElements.define('menu-option', class extends HTMLElement {
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
const smTextarea = document.createElement('template')
|
||||
smTextarea.innerHTML = `
|
||||
<style>
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
::-moz-focus-inner{
|
||||
border: none;
|
||||
}
|
||||
.hide{
|
||||
opacity: 0 !important;
|
||||
}
|
||||
:host{
|
||||
display: grid;
|
||||
--accent-color: #4d2588;
|
||||
--text-color: 17, 17, 17;
|
||||
--background-color: 255, 255, 255;
|
||||
--danger-color: red;
|
||||
--border-radius: 0.3rem;
|
||||
--background: rgba(var(--text-color), 0.06);
|
||||
--padding: initial;
|
||||
--max-height: 8rem;
|
||||
}
|
||||
:host([variant="outlined"]) .textarea {
|
||||
box-shadow: 0 0 0 0.1rem rgba(var(--text-color), 0.4) inset;
|
||||
background: rgba(var(--background-color), 1);
|
||||
}
|
||||
.textarea{
|
||||
display: grid;
|
||||
position: relative;
|
||||
cursor: text;
|
||||
min-width: 0;
|
||||
text-align: left;
|
||||
overflow: hidden auto;
|
||||
grid-template-columns: 1fr;
|
||||
align-items: stretch;
|
||||
max-height: var(--max-height);
|
||||
background: var(--background);
|
||||
border-radius: var(--border-radius);
|
||||
padding: var(--padding);
|
||||
}
|
||||
.textarea::after,
|
||||
textarea{
|
||||
padding: 0.7rem 1rem;
|
||||
width: 100%;
|
||||
min-width: 1em;
|
||||
font: inherit;
|
||||
color: inherit;
|
||||
resize: none;
|
||||
grid-area: 2/1;
|
||||
justify-self: stretch;
|
||||
background: none;
|
||||
appearance: none;
|
||||
border: none;
|
||||
outline: none;
|
||||
line-height: 1.5;
|
||||
overflow: hidden;
|
||||
}
|
||||
.textarea::after{
|
||||
content: attr(data-value) ' ';
|
||||
visibility: hidden;
|
||||
white-space: pre-wrap;
|
||||
overflow-wrap: break-word;
|
||||
word-wrap: break-word;
|
||||
hyphens: auto;
|
||||
}
|
||||
.readonly{
|
||||
pointer-events: none;
|
||||
}
|
||||
.textarea:focus-within:not(.readonly){
|
||||
box-shadow: 0 0 0 0.1rem var(--accent-color) inset;
|
||||
}
|
||||
.placeholder{
|
||||
position: absolute;
|
||||
margin: 0.7rem 1rem;
|
||||
opacity: .7;
|
||||
font-weight: inherit;
|
||||
font-size: inherit;
|
||||
line-height: 1.5;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
:host([disabled]) .textarea{
|
||||
cursor: not-allowed;
|
||||
opacity: 0.6;
|
||||
}
|
||||
@media (any-hover: hover){
|
||||
::-webkit-scrollbar{
|
||||
width: 0.5rem;
|
||||
height: 0.5rem;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb{
|
||||
background: rgba(var(--text-color), 0.3);
|
||||
border-radius: 1rem;
|
||||
&:hover{
|
||||
background: rgba(var(--text-color), 0.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<label class="textarea" part="textarea">
|
||||
<span class="placeholder"></span>
|
||||
<textarea rows="1"></textarea>
|
||||
</label>
|
||||
`;
|
||||
customElements.define('sm-textarea',
|
||||
class extends HTMLElement {
|
||||
constructor() {
|
||||
super()
|
||||
this.attachShadow({
|
||||
mode: 'open'
|
||||
}).append(smTextarea.content.cloneNode(true))
|
||||
|
||||
this.textarea = this.shadowRoot.querySelector('textarea')
|
||||
this.textareaBox = this.shadowRoot.querySelector('.textarea')
|
||||
this.placeholder = this.shadowRoot.querySelector('.placeholder')
|
||||
this.reflectedAttributes = ['disabled', 'required', 'readonly', 'rows', 'minlength', 'maxlength']
|
||||
|
||||
this.reset = this.reset.bind(this)
|
||||
this.focusIn = this.focusIn.bind(this)
|
||||
this.fireEvent = this.fireEvent.bind(this)
|
||||
this.checkInput = this.checkInput.bind(this)
|
||||
}
|
||||
static get observedAttributes() {
|
||||
return ['disabled', 'value', 'placeholder', 'required', 'readonly', 'rows', 'minlength', 'maxlength']
|
||||
}
|
||||
get value() {
|
||||
return this.textarea.value
|
||||
}
|
||||
set value(val) {
|
||||
this.setAttribute('value', val)
|
||||
this.fireEvent()
|
||||
}
|
||||
get disabled() {
|
||||
return this.hasAttribute('disabled')
|
||||
}
|
||||
set disabled(val) {
|
||||
if (val) {
|
||||
this.setAttribute('disabled', '')
|
||||
} else {
|
||||
this.removeAttribute('disabled')
|
||||
}
|
||||
}
|
||||
get isValid() {
|
||||
return this.textarea.checkValidity()
|
||||
}
|
||||
reset() {
|
||||
this.setAttribute('value', '')
|
||||
}
|
||||
focusIn() {
|
||||
this.textarea.focus()
|
||||
}
|
||||
fireEvent() {
|
||||
let event = new Event('input', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
composed: true
|
||||
});
|
||||
this.dispatchEvent(event);
|
||||
}
|
||||
checkInput() {
|
||||
if (!this.hasAttribute('placeholder') || this.getAttribute('placeholder') === '')
|
||||
return;
|
||||
if (this.textarea.value !== '') {
|
||||
this.placeholder.classList.add('hide')
|
||||
} else {
|
||||
this.placeholder.classList.remove('hide')
|
||||
}
|
||||
}
|
||||
connectedCallback() {
|
||||
this.textarea.addEventListener('input', e => {
|
||||
this.textareaBox.dataset.value = this.textarea.value
|
||||
this.checkInput()
|
||||
})
|
||||
}
|
||||
attributeChangedCallback(name, oldValue, newValue) {
|
||||
if (this.reflectedAttributes.includes(name)) {
|
||||
if (this.hasAttribute(name)) {
|
||||
this.textarea.setAttribute(name, this.getAttribute(name) ? this.getAttribute(name) : '')
|
||||
}
|
||||
else {
|
||||
this.textContent.removeAttribute(name)
|
||||
}
|
||||
}
|
||||
else if (name === 'placeholder') {
|
||||
this.placeholder.textContent = this.getAttribute('placeholder')
|
||||
}
|
||||
else if (name === 'value') {
|
||||
this.textarea.value = newValue;
|
||||
this.textareaBox.dataset.value = newValue
|
||||
this.checkInput()
|
||||
}
|
||||
}
|
||||
})
|
||||
48
css/main.css
48
css/main.css
@ -132,7 +132,8 @@ a:any-link:focus-visible {
|
||||
outline: rgba(var(--text-color), 1) 0.1rem solid;
|
||||
}
|
||||
|
||||
sm-input {
|
||||
sm-input,
|
||||
sm-textarea {
|
||||
font-size: 0.9rem;
|
||||
--border-radius: 0.3rem;
|
||||
}
|
||||
@ -346,12 +347,8 @@ ul {
|
||||
|
||||
.empty-state {
|
||||
display: grid;
|
||||
-webkit-box-pack: center;
|
||||
-ms-flex-pack: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
padding: 1.5rem;
|
||||
padding: 1.5rem 1rem;
|
||||
}
|
||||
|
||||
.observe-empty-state:empty {
|
||||
@ -581,6 +578,11 @@ menu-option {
|
||||
}
|
||||
|
||||
.article-link {
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-box-direction: normal;
|
||||
-ms-flex-direction: column;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
padding: 1rem;
|
||||
color: rgba(var(--text-color), 0.8);
|
||||
font-weight: 500;
|
||||
@ -590,15 +592,17 @@ menu-option {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.default-article {
|
||||
margin-left: auto;
|
||||
.default-article::before {
|
||||
-ms-flex-item-align: start;
|
||||
align-self: flex-start;
|
||||
content: "Actively written";
|
||||
margin-bottom: 0.3rem;
|
||||
font-size: 0.7rem;
|
||||
background-color: #00e67650;
|
||||
background-color: var(--accent-color);
|
||||
padding: 0.2rem 0.4rem;
|
||||
border-radius: 0.3rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.05em;
|
||||
color: rgba(var(--text-color), 0.8);
|
||||
border-radius: 0.2rem;
|
||||
font-weight: 500;
|
||||
color: var(--foreground-color);
|
||||
}
|
||||
|
||||
#edit_sections_popup {
|
||||
@ -614,21 +618,30 @@ menu-option {
|
||||
|
||||
.section-card {
|
||||
font-weight: 500;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
.section-card:not(.section-card--new) {
|
||||
padding: 0.8rem 0;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
.section-card--new input {
|
||||
border: none;
|
||||
font-size: inherit;
|
||||
background: inherit;
|
||||
color: inherit;
|
||||
font-weight: inherit;
|
||||
width: 100%;
|
||||
padding: 0.8rem 0;
|
||||
padding: 0.8rem 0.2rem;
|
||||
}
|
||||
.section-card--new input:focus {
|
||||
outline: var(--accent-color) solid;
|
||||
}
|
||||
.section-card .remove {
|
||||
padding: 0.3rem;
|
||||
}
|
||||
|
||||
#insert_section_button {
|
||||
-ms-flex-item-align: start;
|
||||
@ -776,7 +789,7 @@ menu-option {
|
||||
background-color: var(--accent-color);
|
||||
}
|
||||
.active .icon {
|
||||
fill: white;
|
||||
fill: var(--foreground-color);
|
||||
}
|
||||
|
||||
.quote-template {
|
||||
@ -864,8 +877,9 @@ menu-option {
|
||||
background-color: #00e67650;
|
||||
}
|
||||
.entry__changes .removed {
|
||||
-webkit-text-decoration-color: red;
|
||||
text-decoration-color: red;
|
||||
color: var(--danger-color);
|
||||
-webkit-text-decoration-color: var(--danger-color);
|
||||
text-decoration-color: var(--danger-color);
|
||||
}
|
||||
|
||||
@media screen and (max-width: 40rem) and (any-hover: none) {
|
||||
|
||||
2
css/main.min.css
vendored
2
css/main.min.css
vendored
File diff suppressed because one or more lines are too long
@ -113,7 +113,8 @@ a:any-link:focus-visible {
|
||||
outline: rgba(var(--text-color), 1) 0.1rem solid;
|
||||
}
|
||||
|
||||
sm-input {
|
||||
sm-input,
|
||||
sm-textarea {
|
||||
font-size: 0.9rem;
|
||||
--border-radius: 0.3rem;
|
||||
}
|
||||
@ -309,10 +310,8 @@ ul {
|
||||
}
|
||||
.empty-state {
|
||||
display: grid;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
padding: 1.5rem;
|
||||
padding: 1.5rem 1rem;
|
||||
}
|
||||
|
||||
.observe-empty-state:empty {
|
||||
@ -488,7 +487,6 @@ menu-option {
|
||||
#main_page {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
|
||||
#current_article_title {
|
||||
font-weight: 700;
|
||||
}
|
||||
@ -512,6 +510,8 @@ menu-option {
|
||||
}
|
||||
|
||||
.article-link {
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
padding: 1rem;
|
||||
color: rgba(var(--text-color), 0.8);
|
||||
font-weight: 500;
|
||||
@ -521,14 +521,17 @@ menu-option {
|
||||
}
|
||||
}
|
||||
.default-article {
|
||||
margin-left: auto;
|
||||
font-size: 0.7rem;
|
||||
background-color: #00e67650;
|
||||
padding: 0.2rem 0.4rem;
|
||||
border-radius: 0.3rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.05em;
|
||||
color: rgba(var(--text-color), 0.8);
|
||||
&::before {
|
||||
align-self: flex-start;
|
||||
content: "Actively written";
|
||||
margin-bottom: 0.3rem;
|
||||
font-size: 0.7rem;
|
||||
background-color: var(--accent-color);
|
||||
padding: 0.2rem 0.4rem;
|
||||
border-radius: 0.2rem;
|
||||
font-weight: 500;
|
||||
color: var(--foreground-color);
|
||||
}
|
||||
}
|
||||
|
||||
#edit_sections_popup {
|
||||
@ -542,8 +545,10 @@ menu-option {
|
||||
}
|
||||
.section-card {
|
||||
font-weight: 500;
|
||||
font-size: 0.9rem;
|
||||
&:not(.section-card--new) {
|
||||
padding: 0.8rem 0;
|
||||
user-select: none;
|
||||
}
|
||||
&--new {
|
||||
input {
|
||||
@ -551,13 +556,17 @@ menu-option {
|
||||
font-size: inherit;
|
||||
background: inherit;
|
||||
color: inherit;
|
||||
font-weight: inherit;
|
||||
width: 100%;
|
||||
padding: 0.8rem 0;
|
||||
padding: 0.8rem 0.2rem;
|
||||
&:focus {
|
||||
outline: var(--accent-color) solid;
|
||||
}
|
||||
}
|
||||
}
|
||||
.remove {
|
||||
padding: 0.3rem;
|
||||
}
|
||||
}
|
||||
#insert_section_button {
|
||||
align-self: flex-start;
|
||||
@ -676,7 +685,7 @@ menu-option {
|
||||
.active {
|
||||
background-color: var(--accent-color);
|
||||
.icon {
|
||||
fill: white;
|
||||
fill: var(--foreground-color);
|
||||
}
|
||||
}
|
||||
|
||||
@ -758,7 +767,8 @@ menu-option {
|
||||
background-color: #00e67650;
|
||||
}
|
||||
.removed {
|
||||
text-decoration-color: red;
|
||||
color: var(--danger-color);
|
||||
text-decoration-color: var(--danger-color);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
293
index.html
293
index.html
@ -101,6 +101,20 @@
|
||||
</svg>
|
||||
Edit sections
|
||||
</menu-option>
|
||||
<menu-option onclick="setDefaultArticle()">
|
||||
<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>
|
||||
<path d="M0,0h24v24H0V0z" fill="none" />
|
||||
</g>
|
||||
<g>
|
||||
<path
|
||||
d="M14,2H6C4.9,2,4.01,2.9,4.01,4L4,20c0,1.1,0.89,2,1.99,2H18c1.1,0,2-0.9,2-2V8L14,2z M18,20H6V4h7v5h5V20z M8.82,13.05 L7.4,14.46L10.94,18l5.66-5.66l-1.41-1.41l-4.24,4.24L8.82,13.05z" />
|
||||
</g>
|
||||
</svg>
|
||||
Make default article
|
||||
</menu-option>
|
||||
</sm-menu>
|
||||
</div>
|
||||
<theme-toggle></theme-toggle>
|
||||
@ -205,8 +219,8 @@
|
||||
<h3>Create article</h3>
|
||||
</header>
|
||||
<sm-form>
|
||||
<sm-input placeholder="Article title" required autofocus></sm-input>
|
||||
<sm-checkbox checked>
|
||||
<sm-input id="get_article_title" placeholder="Article title" required autofocus></sm-input>
|
||||
<sm-checkbox id="set_default_checkbox" checked>
|
||||
<div class="grid button__icon--right gap-0-5">
|
||||
Set as default
|
||||
<p style="font-size: 0.8rem;">
|
||||
@ -214,15 +228,29 @@
|
||||
</p>
|
||||
</div>
|
||||
</sm-checkbox>
|
||||
<!-- <sm-switch>
|
||||
<div slot="left">
|
||||
Make private
|
||||
<div class="grid gap-1">
|
||||
<div class="grid gap-0-5">
|
||||
<h4>Define sections (optional)</h4>
|
||||
<p>Create and name sections by writing title of each section separated by a comma (,)</p>
|
||||
</div>
|
||||
</sm-switch> -->
|
||||
<sm-button variant="primary">Create</sm-button>
|
||||
<sm-textarea id="get_section_titles" rows="4" placeholder="section title 1, section title 2, ...">
|
||||
</sm-textarea>
|
||||
</div>
|
||||
<!-- <div class="grid gap-1">
|
||||
<div class="grid gap-0-5">
|
||||
<sm-switch>
|
||||
<h4 slot="left">
|
||||
Make private
|
||||
</h4>
|
||||
</sm-switch>
|
||||
<p>Define the FLO IDs separated by a comma (,), which are allowed to contribute.</p>
|
||||
</div>
|
||||
<sm-textarea id="get_section_titles" rows="4"></sm-textarea>
|
||||
</div> -->
|
||||
<sm-button variant="primary" onclick="cc.createNewArticle()">Create</sm-button>
|
||||
</sm-form>
|
||||
</sm-popup>
|
||||
<sm-popup id="edit_sections_popup" open>
|
||||
<sm-popup id="edit_sections_popup">
|
||||
<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"
|
||||
@ -239,26 +267,29 @@
|
||||
<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 />
|
||||
<div class="flex space-between align-center">
|
||||
<sm-button id="insert_section_button" onclick="insertEmptySection()">
|
||||
<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>
|
||||
<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" />
|
||||
<rect fill="none" height="24" width="24" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
Add new section
|
||||
</button>
|
||||
<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>
|
||||
Insert section
|
||||
</sm-button>
|
||||
<sm-button variant="primary" onclick="saveSectionEdit()">Save</sm-button>
|
||||
</div>
|
||||
</sm-popup>
|
||||
<template id="section_template">
|
||||
<text-field class="heading"></text-field>
|
||||
@ -311,13 +342,20 @@
|
||||
<p><br></p>
|
||||
</template>
|
||||
<template id="history_entry_template">
|
||||
<li class="history-entry grid gap-0-5">
|
||||
<li class="history-entry grid gap-1">
|
||||
<div class="flex align-center space-between">
|
||||
<time class="entry__time"></time>
|
||||
<span class="entry__score"></span>
|
||||
</div>
|
||||
<div class="grid">
|
||||
<div class="label">Author</div>
|
||||
<div class="label flex align-center">
|
||||
<svg class="icon" style="margin-right: 0.2rem;" width="24" height="24" viewBox="0 0 24 24"
|
||||
fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M14.6243 7.99154C13.9395 8.14279 13.4165 8.69672 13.3047 9.38904C13.2248 9.88365 13.3664 10.3773 13.6735 10.7498L9.29125 15.0941C8.93718 15.4451 8.48852 15.6853 8.00011 15.7854L6.12418 16.1699L9.19811 13.1381C9.53438 12.8064 9.53812 12.265 9.20646 11.9287C8.8748 11.5924 8.33333 11.5887 7.99706 11.9203L4.97835 14.8977L5.47976 12.451C5.50451 12.3303 5.55656 12.2168 5.63191 12.1192L6.49083 11.0073C9.58386 7.00317 14.32 4.72705 19.2553 4.71054L16.3569 7.60884L14.6243 7.99154ZM2.79008 17.0559L3.8042 12.1076C3.88132 11.7313 4.0435 11.3776 4.27833 11.0736L5.13724 9.96172C8.89964 5.09105 14.861 2.53305 20.8954 3.07051C21.5971 3.133 22.2997 3.23735 23 3.38478L17.2133 9.1713L14.9932 9.66167L15.4238 9.91837C15.9039 10.2046 15.9849 10.8668 15.588 11.2603L10.4954 16.3088C9.90529 16.8938 9.15752 17.2941 8.34351 17.461L3.88965 18.3738L2.45572 19.788C2.11945 20.1197 1.57798 20.116 1.24632 19.7797C0.91466 19.4434 0.918397 18.9019 1.25467 18.5703L2.79008 17.0559Z" />
|
||||
</svg>
|
||||
Author
|
||||
</div>
|
||||
<span class="entry__author breakable"></span>
|
||||
</div>
|
||||
<div class="entry__changes"></div>
|
||||
@ -412,29 +450,12 @@
|
||||
}, delay);
|
||||
}
|
||||
|
||||
class Stack {
|
||||
constructor() {
|
||||
this.items = [];
|
||||
}
|
||||
push(element) {
|
||||
this.items.push(element);
|
||||
}
|
||||
pop() {
|
||||
if (this.items.length == 0)
|
||||
return "Underflow";
|
||||
return this.items.pop();
|
||||
}
|
||||
peek() {
|
||||
return this.items[this.items.length - 1];
|
||||
}
|
||||
}
|
||||
let popupStack = new Stack()
|
||||
let zIndex = 10
|
||||
// function required for popups or modals to appear
|
||||
function showPopup(popupId, pinned) {
|
||||
zIndex++
|
||||
getRef(popupId).setAttribute('style', `z-index: ${zIndex}`)
|
||||
popupStack = getRef(popupId).show({ pinned, popupStack })
|
||||
getRef(popupId).show({ pinned })
|
||||
return getRef(popupId);
|
||||
}
|
||||
|
||||
@ -445,6 +466,29 @@
|
||||
popupStack.peek().popup.hide()
|
||||
}
|
||||
|
||||
document.addEventListener('popupopened', e => {
|
||||
switch (e.target.id) {
|
||||
case 'edit_sections_popup':
|
||||
renderSectionList()
|
||||
break;
|
||||
case 'article_list_popup':
|
||||
renderArticleList()
|
||||
break;
|
||||
}
|
||||
})
|
||||
document.addEventListener('popupclosed', e => {
|
||||
zIndex--
|
||||
switch (e.target.id) {
|
||||
case 'edit_sections_popup':
|
||||
getRef('section_list_container').innerHTML = ''
|
||||
break;
|
||||
case 'article_list_popup':
|
||||
getRef('article_list').innerHTML = ``
|
||||
|
||||
break;
|
||||
}
|
||||
})
|
||||
|
||||
// displays a popup for asking permission. Use this instead of JS confirm
|
||||
const getConfirmation = (title, options = {}) => {
|
||||
return new Promise(resolve => {
|
||||
@ -734,35 +778,62 @@
|
||||
</script>
|
||||
<script id="cc">
|
||||
const cc = {
|
||||
createNewArticle(title) {
|
||||
const uid = floCrypto.randString(16, true)
|
||||
floGlobals.appObjects.cc['defaultArticle'] = uid
|
||||
floGlobals.appObjects.cc['articleList'][uid] = {
|
||||
title,
|
||||
timestamp: Date.now()
|
||||
}
|
||||
floCloudAPI.updateObjectData('cc')
|
||||
.then((res) => {
|
||||
console.log('created article entry', res)
|
||||
})
|
||||
.catch(err => console.error(err))
|
||||
floGlobals.appObjects[uid] = {
|
||||
public: true,
|
||||
editors: [],
|
||||
sections: [{
|
||||
id: floCrypto.randString(16, true),
|
||||
title: 'Introduction',
|
||||
}, {
|
||||
id: floCrypto.randString(16, true),
|
||||
title: 'core',
|
||||
createNewArticle() {
|
||||
if (isSubAdmin) {
|
||||
const title = getRef('get_article_title').value.trim()
|
||||
const setDefault = getRef('set_default_checkbox').checked
|
||||
const sectionTitles = getRef('get_section_titles').value.trim()
|
||||
let sections = []
|
||||
if (sectionTitles !== '') {
|
||||
sections = sectionTitles.split(',').map(title => {
|
||||
return {
|
||||
id: floCrypto.randString(16, true),
|
||||
title: title.trim(),
|
||||
}
|
||||
})
|
||||
}
|
||||
]
|
||||
const uid = floCrypto.randString(16, true)
|
||||
if (setDefault)
|
||||
floGlobals.appObjects.cc['defaultArticle'] = uid
|
||||
floGlobals.appObjects.cc['articleList'][uid] = {
|
||||
title,
|
||||
timestamp: Date.now()
|
||||
}
|
||||
floGlobals.appObjects[uid] = {
|
||||
public: true,
|
||||
editors: [],
|
||||
sections
|
||||
}
|
||||
Promise.all([
|
||||
floCloudAPI.updateObjectData('cc'),
|
||||
floCloudAPI.resetObjectData(uid)
|
||||
])
|
||||
.then((res) => {
|
||||
hidePopup()
|
||||
notify('created article', 'success')
|
||||
window.location.hash = `#/home?articleID=${uid}`
|
||||
})
|
||||
.catch(err => console.error(err))
|
||||
} else {
|
||||
notify('This action requires sub-admin privileges', 'error')
|
||||
}
|
||||
floCloudAPI.resetObjectData(uid)
|
||||
.then((res) => {
|
||||
console.log('created article', res)
|
||||
})
|
||||
.catch(err => console.error(err))
|
||||
}
|
||||
}
|
||||
|
||||
function setDefaultArticle() {
|
||||
if (isSubAdmin) {
|
||||
getConfirmation('Set as default article?').then(res => {
|
||||
if (res) {
|
||||
floGlobals.appObjects.cc['defaultArticle'] = currentArticle.id
|
||||
floCloudAPI.updateObjectData('cc')
|
||||
.then((res) => {
|
||||
notify('Set current article as default', 'success')
|
||||
})
|
||||
.catch(err => console.error(err))
|
||||
}
|
||||
})
|
||||
} else {
|
||||
notify('This action requires sub-admin privileges', 'error')
|
||||
}
|
||||
}
|
||||
|
||||
@ -837,7 +908,7 @@
|
||||
currentArticle.uniqueEntries[entry.origin] = { iterations: [{ ...entry, timestamp }] }
|
||||
parentSection.firstElementChild.after(render.contentCard(entry.origin))
|
||||
} else {
|
||||
currentArticle.uniqueEntries[entry.origin].iterations.push({ ...entry, timestamp })
|
||||
currentArticle.uniqueEntries[entry.origin].iterations.push({ ...entry, timestamp, editor: myFloID })
|
||||
}
|
||||
})
|
||||
} else {
|
||||
@ -930,7 +1001,6 @@
|
||||
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))
|
||||
@ -938,8 +1008,6 @@
|
||||
}
|
||||
if (!isSubAdmin) {
|
||||
getRef('current_article_title').setAttribute('disabled', '')
|
||||
} else {
|
||||
renderSectionList()
|
||||
}
|
||||
getRef('current_article_title').value = title
|
||||
getRef('article_wrapper').innerHTML = ''
|
||||
@ -953,22 +1021,30 @@
|
||||
className: 'article-link flex interact'
|
||||
})
|
||||
if (isDefaultArticle) {
|
||||
link.append(createElement('span', {
|
||||
className: 'default-article',
|
||||
textContent: 'Actively written'
|
||||
}))
|
||||
link.classList.add('default-article')
|
||||
}
|
||||
return link
|
||||
},
|
||||
contentCard(id, version = 0) {
|
||||
const clone = getRef('content_card_template').content.cloneNode(true).firstElementChild;
|
||||
clone.dataset.uid = id
|
||||
if (!floGlobals.subAdmins.includes(myFloID)) {
|
||||
if (!isSubAdmin) {
|
||||
clone.querySelector('.content__area').setAttribute('contentEditable', true)
|
||||
}
|
||||
const { data, contributors } = getIterationDetails(id)
|
||||
console.log(contributors)
|
||||
clone.querySelector('.content__area').innerHTML = DOMPurify.sanitize(data)
|
||||
clone.querySelector('.content__editor').textContent = `by ${contributors}`;
|
||||
if (contributors.size === 1) {
|
||||
const [contributor] = contributors
|
||||
clone.querySelector('.content__editor').textContent = `by ${contributor}`;
|
||||
} else {
|
||||
clone.querySelector('.content__editor').innerHTML = `
|
||||
<svg class="icon" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.6243 7.99154C13.9395 8.14279 13.4165 8.69672 13.3047 9.38904C13.2248 9.88365 13.3664 10.3773 13.6735 10.7498L9.29125 15.0941C8.93718 15.4451 8.48852 15.6853 8.00011 15.7854L6.12418 16.1699L9.19811 13.1381C9.53438 12.8064 9.53812 12.265 9.20646 11.9287C8.8748 11.5924 8.33333 11.5887 7.99706 11.9203L4.97835 14.8977L5.47976 12.451C5.50451 12.3303 5.55656 12.2168 5.63191 12.1192L6.49083 11.0073C9.58386 7.00317 14.32 4.72705 19.2553 4.71054L16.3569 7.60884L14.6243 7.99154ZM2.79008 17.0559L3.8042 12.1076C3.88132 11.7313 4.0435 11.3776 4.27833 11.0736L5.13724 9.96172C8.89964 5.09105 14.861 2.53305 20.8954 3.07051C21.5971 3.133 22.2997 3.23735 23 3.38478L17.2133 9.1713L14.9932 9.66167L15.4238 9.91837C15.9039 10.2046 15.9849 10.8668 15.588 11.2603L10.4954 16.3088C9.90529 16.8938 9.15752 17.2941 8.34351 17.461L3.88965 18.3738L2.45572 19.788C2.11945 20.1197 1.57798 20.116 1.24632 19.7797C0.91466 19.4434 0.918397 18.9019 1.25467 18.5703L2.79008 17.0559Z"/>
|
||||
</svg>
|
||||
|
||||
`
|
||||
}
|
||||
// clone.querySelector('.content__score').textContent = score;
|
||||
return clone
|
||||
},
|
||||
@ -1008,7 +1084,7 @@
|
||||
const frag = document.createDocumentFragment()
|
||||
section.children[0].dataset.index = index
|
||||
section.children[1].dataset.sectionId = sectionID
|
||||
if (floGlobals.subAdmins.includes(myFloID)) {
|
||||
if (isSubAdmin) {
|
||||
section.querySelector('.content-card--empty').remove()
|
||||
} else {
|
||||
section.querySelector('.heading').setAttribute('disabled', '')
|
||||
@ -1066,6 +1142,7 @@
|
||||
}
|
||||
}
|
||||
function renderSectionList() {
|
||||
if (!isSubAdmin) return
|
||||
const frag = document.createDocumentFragment()
|
||||
floGlobals.appObjects[currentArticle.id].sections.forEach(section => {
|
||||
frag.append(render.sectionCard(section))
|
||||
@ -1088,7 +1165,49 @@
|
||||
`
|
||||
})
|
||||
getRef('section_list_container').append(emptySection)
|
||||
emptySection.querySelector('input').focus()
|
||||
}
|
||||
getRef('section_list_container').addEventListener('click', e => {
|
||||
if (e.target.closest('.remove')) {
|
||||
e.target.closest('.section-card').remove()
|
||||
}
|
||||
})
|
||||
|
||||
function saveSectionEdit() {
|
||||
const newSections = []
|
||||
getRef('section_list_container').querySelectorAll('.section-card--new').forEach(section => {
|
||||
const title = section.querySelector('input').value.trim()
|
||||
if (title !== '')
|
||||
newSections.push({
|
||||
id: floCrypto.randString(16, true),
|
||||
title
|
||||
})
|
||||
})
|
||||
if (newSections.length) {
|
||||
newSections.forEach(elem => {
|
||||
floGlobals.appObjects[currentArticle.id].sections.push(elem)
|
||||
})
|
||||
floCloudAPI.updateObjectData(currentArticle.id)
|
||||
.then((res) => {
|
||||
const frag = document.createDocumentFragment()
|
||||
const currentSectionCount = getRef('section_list_container').querySelectorAll('.section-card:not(.section-card--new)').length
|
||||
let index = currentSectionCount
|
||||
newSections.forEach(elem => {
|
||||
const { title, id } = elem
|
||||
currentArticle.sections[id] = { title, uniqueEntries: [] }
|
||||
frag.append(render.section(id, currentArticle.sections[id], index))
|
||||
index += 1
|
||||
})
|
||||
getRef('article_wrapper').append(frag)
|
||||
notify('Sections updated', 'success')
|
||||
hidePopup()
|
||||
})
|
||||
.catch(err => {
|
||||
notify(err, 'error')
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function getArticleList() {
|
||||
const articleList = floGlobals.appObjects.cc.articleList
|
||||
@ -1114,7 +1233,7 @@
|
||||
const searchKey = e.target.value.trim()
|
||||
const options = {
|
||||
keys: ['title'],
|
||||
threshold: 0
|
||||
threshold: 0.3
|
||||
}
|
||||
const fuse = new Fuse(getArticleList(), options)
|
||||
renderArticleList(searchKey === '' ? undefined : fuse.search(searchKey).map(v => v.item))
|
||||
@ -1234,6 +1353,7 @@
|
||||
},
|
||||
200)
|
||||
|
||||
|
||||
const replaceBetween = (origin, startIndex, endIndex, insertion) =>
|
||||
`${origin.substring(0, startIndex)}${insertion}${origin.substring(endIndex)}`;
|
||||
const make = {
|
||||
@ -1357,6 +1477,7 @@
|
||||
}
|
||||
</script>
|
||||
<script id="onLoadStartUp">
|
||||
let isSubAdmin = false
|
||||
function onLoadStartUp() {
|
||||
|
||||
//floDapps.addStartUpFunction('Sample', Promised Function)
|
||||
@ -1364,11 +1485,11 @@
|
||||
//floDapps.setCustomPrivKeyInput( () => { FUNCTION BODY *must resolve private key* } )
|
||||
|
||||
floDapps.launchStartUp().then(async result => {
|
||||
isSubAdmin = floGlobals.subAdmins.includes(myFloID)
|
||||
await Promise.all([
|
||||
floCloudAPI.requestObjectData('cc'),
|
||||
])
|
||||
showPage(window.location.hash)
|
||||
renderArticleList()
|
||||
|
||||
console.log(result)
|
||||
// alert(`Welcome FLO_ID: ${ myFloID }`)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user