Converted jQuery bits into raw JS components

This commit is contained in:
Dan Brown 2017-08-06 21:08:03 +01:00
parent 5e5928a8a6
commit 2a069880cd
No known key found for this signature in database
GPG Key ID: 46D9F943C24A2EF9
23 changed files with 387 additions and 209 deletions

View File

@ -0,0 +1,53 @@
class BackToTop {
constructor(elem) {
this.elem = elem;
this.targetElem = document.getElementById('header');
this.showing = false;
this.breakPoint = 1200;
this.elem.addEventListener('click', this.scrollToTop.bind(this));
window.addEventListener('scroll', this.onPageScroll.bind(this));
}
onPageScroll() {
let scrollTopPos = document.documentElement.scrollTop || document.body.scrollTop || 0;
if (!this.showing && scrollTopPos > this.breakPoint) {
this.elem.style.display = 'block';
this.showing = true;
setTimeout(() => {
this.elem.style.opacity = 0.4;
}, 1);
} else if (this.showing && scrollTopPos < this.breakPoint) {
this.elem.style.opacity = 0;
this.showing = false;
setTimeout(() => {
this.elem.style.display = 'none';
}, 500);
}
}
scrollToTop() {
let targetTop = this.targetElem.getBoundingClientRect().top;
let scrollElem = document.documentElement.scrollTop ? document.documentElement : document.body;
let duration = 300;
let start = Date.now();
let scrollStart = this.targetElem.getBoundingClientRect().top;
function setPos() {
let percentComplete = (1-((Date.now() - start) / duration));
let target = Math.abs(percentComplete * scrollStart);
if (percentComplete > 0) {
scrollElem.scrollTop = target;
requestAnimationFrame(setPos.bind(this));
} else {
scrollElem.scrollTop = targetTop;
}
}
requestAnimationFrame(setPos.bind(this));
}
}
module.exports = BackToTop;

View File

@ -0,0 +1,67 @@
class ChapterToggle {
constructor(elem) {
this.elem = elem;
this.isOpen = elem.classList.contains('open');
elem.addEventListener('click', this.click.bind(this));
}
open() {
let list = this.elem.parentNode.querySelector('.inset-list');
this.elem.classList.add('open');
list.style.display = 'block';
list.style.height = '';
let height = list.getBoundingClientRect().height;
list.style.height = '0px';
list.style.overflow = 'hidden';
list.style.transition = 'height ease-in-out 240ms';
let transitionEndBound = onTransitionEnd.bind(this);
function onTransitionEnd() {
list.style.overflow = '';
list.style.height = '';
list.style.transition = '';
list.removeEventListener('transitionend', transitionEndBound);
}
setTimeout(() => {
list.style.height = `${height}px`;
list.addEventListener('transitionend', transitionEndBound)
}, 1);
}
close() {
let list = this.elem.parentNode.querySelector('.inset-list');
this.elem.classList.remove('open');
list.style.display = 'block';
list.style.height = list.getBoundingClientRect().height + 'px';
list.style.overflow = 'hidden';
list.style.transition = 'height ease-in-out 240ms';
let transitionEndBound = onTransitionEnd.bind(this);
function onTransitionEnd() {
list.style.overflow = '';
list.style.height = '';
list.style.transition = '';
list.style.display = 'none';
list.removeEventListener('transitionend', transitionEndBound);
}
setTimeout(() => {
list.style.height = `0px`;
list.addEventListener('transitionend', transitionEndBound)
}, 1);
}
click(event) {
event.preventDefault();
this.isOpen ? this.close() : this.open();
this.isOpen = !this.isOpen;
}
}
module.exports = ChapterToggle;

View File

@ -0,0 +1,65 @@
class ExpandToggle {
constructor(elem) {
this.elem = elem;
this.isOpen = false;
this.selector = elem.getAttribute('expand-toggle');
elem.addEventListener('click', this.click.bind(this));
}
open(elemToToggle) {
elemToToggle.style.display = 'block';
elemToToggle.style.height = '';
let height = elemToToggle.getBoundingClientRect().height;
elemToToggle.style.height = '0px';
elemToToggle.style.overflow = 'hidden';
elemToToggle.style.transition = 'height ease-in-out 240ms';
let transitionEndBound = onTransitionEnd.bind(this);
function onTransitionEnd() {
elemToToggle.style.overflow = '';
elemToToggle.style.height = '';
elemToToggle.style.transition = '';
elemToToggle.removeEventListener('transitionend', transitionEndBound);
}
setTimeout(() => {
elemToToggle.style.height = `${height}px`;
elemToToggle.addEventListener('transitionend', transitionEndBound)
}, 1);
}
close(elemToToggle) {
elemToToggle.style.display = 'block';
elemToToggle.style.height = elemToToggle.getBoundingClientRect().height + 'px';
elemToToggle.style.overflow = 'hidden';
elemToToggle.style.transition = 'all ease-in-out 240ms';
let transitionEndBound = onTransitionEnd.bind(this);
function onTransitionEnd() {
elemToToggle.style.overflow = '';
elemToToggle.style.height = '';
elemToToggle.style.transition = '';
elemToToggle.style.display = 'none';
elemToToggle.removeEventListener('transitionend', transitionEndBound);
}
setTimeout(() => {
elemToToggle.style.height = `0px`;
elemToToggle.addEventListener('transitionend', transitionEndBound)
}, 1);
}
click(event) {
event.preventDefault();
let matchingElems = document.querySelectorAll(this.selector);
for (let i = 0, len = matchingElems.length; i < len; i++) {
this.isOpen ? this.close(matchingElems[i]) : this.open(matchingElems[i]);
}
this.isOpen = !this.isOpen;
}
}
module.exports = ExpandToggle;

View File

@ -1,6 +1,11 @@
let componentMapping = {
'dropdown': require('./dropdown'),
'overlay': require('./overlay'),
'back-to-top': require('./back-top-top'),
'notification': require('./notification'),
'chapter-toggle': require('./chapter-toggle'),
'expand-toggle': require('./expand-toggle'),
};
window.components = {};

View File

@ -0,0 +1,40 @@
class Notification {
constructor(elem) {
this.elem = elem;
this.type = elem.getAttribute('notification');
this.textElem = elem.querySelector('span');
this.autohide = this.elem.hasAttribute('data-autohide');
window.Events.listen(this.type, text => {
console.log('show', text);
this.show(text);
});
elem.addEventListener('click', this.hide.bind(this));
if (elem.hasAttribute('data-show')) this.show(this.textElem.textContent);
}
show(textToShow = '') {
this.textElem.textContent = textToShow;
this.elem.style.display = 'block';
setTimeout(() => {
this.elem.classList.add('showing');
}, 1);
if (this.autohide) setTimeout(this.hide.bind(this), 2000);
}
hide() {
this.elem.classList.remove('showing');
function transitionEnd() {
this.elem.style.display = 'none';
this.elem.removeEventListener('transitionend', transitionEnd);
}
this.elem.addEventListener('transitionend', transitionEnd.bind(this));
}
}
module.exports = Notification;

View File

@ -0,0 +1,39 @@
class Overlay {
constructor(elem) {
this.container = elem;
elem.addEventListener('click', event => {
if (event.target === elem) return this.hide();
});
let closeButtons = elem.querySelectorAll('.overlay-close');
for (let i=0; i < closeButtons.length; i++) {
closeButtons[i].addEventListener('click', this.hide.bind(this));
}
}
toggle(show = true) {
let start = Date.now();
let duration = 240;
function setOpacity() {
let elapsedTime = (Date.now() - start);
let targetOpacity = show ? (elapsedTime / duration) : 1-(elapsedTime / duration);
this.container.style.opacity = targetOpacity;
if (elapsedTime > duration) {
this.container.style.display = show ? 'display' : 'none';
this.container.style.opacity = '';
} else {
requestAnimationFrame(setOpacity.bind(this));
}
}
requestAnimationFrame(setOpacity.bind(this));
}
hide() { this.toggle(false); }
show() { this.toggle(true); }
}
module.exports = Overlay;

View File

@ -713,6 +713,7 @@ module.exports = function (ngApp, events) {
function hide() {
element.fadeOut(240);
}
scope.hide = hide;
// Listen to confirmation of entity selections (doubleclick)
events.listen('entity-select-confirm', entity => {

View File

@ -19,12 +19,8 @@ let axiosInstance = axios.create({
}
});
window.$http = axiosInstance;
Vue.prototype.$http = axiosInstance;
require("./vues/vues");
require("./components");
// AngularJS - Create application and load components
const angular = require("angular");
@ -67,6 +63,9 @@ class EventManager {
window.Events = new EventManager();
Vue.prototype.$events = window.Events;
require("./vues/vues");
require("./components");
// Load in angular specific items
const Services = require('./services');
const Directives = require('./directives');
@ -93,83 +92,11 @@ jQuery.expr[":"].contains = $.expr.createPseudo(function (arg) {
};
});
// Global jQuery Elements
let notifications = $('.notification');
let successNotification = notifications.filter('.pos');
let errorNotification = notifications.filter('.neg');
let warningNotification = notifications.filter('.warning');
// Notification Events
window.Events.listen('success', function (text) {
successNotification.hide();
successNotification.find('span').text(text);
setTimeout(() => {
successNotification.show();
}, 1);
});
window.Events.listen('warning', function (text) {
warningNotification.find('span').text(text);
warningNotification.show();
});
window.Events.listen('error', function (text) {
errorNotification.find('span').text(text);
errorNotification.show();
});
// Notification hiding
notifications.click(function () {
$(this).fadeOut(100);
});
// Chapter page list toggles
$('.chapter-toggle').click(function (e) {
e.preventDefault();
$(this).toggleClass('open');
$(this).closest('.chapter').find('.inset-list').slideToggle(180);
});
// Back to top button
$('#back-to-top').click(function() {
$('#header').smoothScrollTo();
});
let scrollTopShowing = false;
let scrollTop = document.getElementById('back-to-top');
let scrollTopBreakpoint = 1200;
window.addEventListener('scroll', function() {
let scrollTopPos = document.documentElement.scrollTop || document.body.scrollTop || 0;
if (!scrollTopShowing && scrollTopPos > scrollTopBreakpoint) {
scrollTop.style.display = 'block';
scrollTopShowing = true;
setTimeout(() => {
scrollTop.style.opacity = 0.4;
}, 1);
} else if (scrollTopShowing && scrollTopPos < scrollTopBreakpoint) {
scrollTop.style.opacity = 0;
scrollTopShowing = false;
setTimeout(() => {
scrollTop.style.display = 'none';
}, 500);
}
});
// Common jQuery actions
$('[data-action="expand-entity-list-details"]').click(function() {
$('.entity-list.compact').find('p').not('.empty-text').slideToggle(240);
});
// Popup close
$('.popup-close').click(function() {
$(this).closest('.overlay').fadeOut(240);
});
$('.overlay').click(function(event) {
if (!$(event.target).hasClass('overlay')) return;
$(this).fadeOut(240);
});
// Detect IE for css
if(navigator.userAgent.indexOf('MSIE')!==-1
|| navigator.appVersion.indexOf('Trident/') > 0
|| navigator.userAgent.indexOf('Safari') !== -1){
$('body').addClass('flexbox-support');
document.body.classList.add('flexbox-support');
}
// Page specific items

View File

@ -36,41 +36,12 @@
}
}
.anim.notification {
transform: translate3d(580px, 0, 0);
animation-name: notification;
animation-duration: 3s;
animation-timing-function: ease-in-out;
animation-fill-mode: forwards;
&.stopped {
animation-name: notificationStopped;
}
}
@keyframes notification {
0% {
transform: translate3d(580px, 0, 0);
}
10% {
transform: translate3d(0, 0, 0);
}
90% {
transform: translate3d(0, 0, 0);
}
100% {
transform: translate3d(580px, 0, 0);
}
}
@keyframes notificationStopped {
0% {
transform: translate3d(580px, 0, 0);
}
10% {
transform: translate3d(0, 0, 0);
}
100% {
transform: translate3d(0, 0, 0);
}
.anim.menuIn {
transform-origin: 100% 0%;
animation-name: menuIn;
animation-duration: 120ms;
animation-delay: 0s;
animation-timing-function: cubic-bezier(.62, .28, .23, .99);
}
@keyframes menuIn {
@ -85,14 +56,6 @@
}
}
.anim.menuIn {
transform-origin: 100% 0%;
animation-name: menuIn;
animation-duration: 120ms;
animation-delay: 0s;
animation-timing-function: cubic-bezier(.62, .28, .23, .99);
}
@keyframes loadingBob {
0% {
transform: translate3d(0, 0, 0);

View File

@ -1,4 +1,65 @@
.overlay {
// System wide notifications
[notification] {
position: fixed;
top: 0;
right: 0;
margin: $-xl*2 $-xl;
padding: $-l $-xl;
background-color: #EEE;
border-radius: 3px;
box-shadow: $bs-med;
z-index: 999999;
display: block;
cursor: pointer;
max-width: 480px;
transition: transform ease-in-out 360ms;
transform: translate3d(580px, 0, 0);
i, span {
display: table-cell;
}
i {
font-size: 2em;
padding-right: $-l;
}
span {
vertical-align: middle;
}
&.pos {
background-color: $positive;
color: #EEE;
}
&.neg {
background-color: $negative;
color: #EEE;
}
&.warning {
background-color: $secondary;
color: #EEE;
}
&.showing {
transform: translate3d(0, 0, 0);
}
}
[chapter-toggle] {
cursor: pointer;
margin: 0;
transition: all ease-in-out 180ms;
user-select: none;
i.zmdi-caret-right {
transition: all ease-in-out 180ms;
transform: rotate(0deg);
transform-origin: 25% 50%;
}
&.open {
//margin-bottom: 0;
}
&.open i.zmdi-caret-right {
transform: rotate(90deg);
}
}
[overlay] {
background-color: rgba(0, 0, 0, 0.333);
position: fixed;
z-index: 95536;

View File

@ -9,7 +9,6 @@
.inset-list {
display: none;
overflow: hidden;
margin-bottom: $-l;
}
h5 {
display: block;
@ -22,6 +21,9 @@
border-left-color: $color-page-draft;
}
}
.entity-list-item {
margin-bottom: $-m;
}
hr {
margin-top: 0;
}
@ -51,23 +53,6 @@
margin-right: $-s;
}
}
.chapter-toggle {
cursor: pointer;
margin: 0 0 $-l 0;
transition: all ease-in-out 180ms;
user-select: none;
i.zmdi-caret-right {
transition: all ease-in-out 180ms;
transform: rotate(0deg);
transform-origin: 25% 50%;
}
&.open {
margin-bottom: 0;
}
&.open i.zmdi-caret-right {
transform: rotate(90deg);
}
}
.sidebar-page-nav {
$nav-indent: $-s;
@ -171,7 +156,7 @@
background-color: rgba($color-chapter, 0.12);
}
}
.chapter-toggle {
[chapter-toggle] {
padding-left: $-s;
}
.list-item-chapter {
@ -336,8 +321,10 @@ ul.pagination {
h4, a {
line-height: 1.2;
}
p {
.entity-item-snippet {
display: none;
}
p {
font-size: $fs-m * 0.8;
padding-top: $-xs;
margin: 0;

View File

@ -66,44 +66,6 @@ body.dragging, body.dragging * {
}
}
// System wide notifications
.notification {
position: fixed;
top: 0;
right: 0;
margin: $-xl*2 $-xl;
padding: $-l $-xl;
background-color: #EEE;
border-radius: 3px;
box-shadow: $bs-med;
z-index: 999999;
display: block;
cursor: pointer;
max-width: 480px;
i, span {
display: table-cell;
}
i {
font-size: 2em;
padding-right: $-l;
}
span {
vertical-align: middle;
}
&.pos {
background-color: $positive;
color: #EEE;
}
&.neg {
background-color: $negative;
color: #EEE;
}
&.warning {
background-color: $secondary;
color: #EEE;
}
}
// Loading icon
$loadingSize: 10px;
.loading-container {
@ -151,7 +113,7 @@ $loadingSize: 10px;
// Back to top link
$btt-size: 40px;
#back-to-top {
[back-to-top] {
background-color: $primary;
position: fixed;
bottom: $-m;

View File

@ -77,7 +77,7 @@
@yield('content')
</section>
<div id="back-to-top">
<div back-to-top>
<div class="inner">
<i class="zmdi zmdi-chevron-up"></i> <span>{{ trans('common.back_to_top') }}</span>
</div>

View File

@ -1,8 +1,10 @@
<div class="book entity-list-item" data-entity-type="book" data-entity-id="{{$book->id}}">
<h4 class="text-book"><a class="text-book entity-list-item-link" href="{{$book->getUrl()}}"><i class="zmdi zmdi-book"></i><span class="entity-list-item-name">{{$book->name}}</span></a></h4>
<div class="entity-item-snippet">
@if(isset($book->searchSnippet))
<p class="text-muted">{!! $book->searchSnippet !!}</p>
@else
<p class="text-muted">{{ $book->getExcerpt() }}</p>
@endif
</div>
</div>

View File

@ -10,14 +10,18 @@
<i class="zmdi zmdi-collection-bookmark"></i><span class="entity-list-item-name">{{ $chapter->name }}</span>
</a>
</h4>
<div class="entity-item-snippet">
@if(isset($chapter->searchSnippet))
<p class="text-muted">{!! $chapter->searchSnippet !!}</p>
@else
<p class="text-muted">{{ $chapter->getExcerpt() }}</p>
@endif
</div>
@if(!isset($hidePages) && count($chapter->pages) > 0)
<p class="text-muted chapter-toggle"><i class="zmdi zmdi-caret-right"></i> <i class="zmdi zmdi-file-text"></i> <span>{{ trans('entities.x_pages', ['count' => $chapter->pages->count()]) }}</span></p>
<p chapter-toggle class="text-muted"><i class="zmdi zmdi-caret-right"></i> <i class="zmdi zmdi-file-text"></i> <span>{{ trans('entities.x_pages', ['count' => $chapter->pages->count()]) }}</span></p>
<div class="inset-list">
@foreach($chapter->pages as $page)
<h5 class="@if($page->draft) draft @endif"><a href="{{ $page->getUrl() }}" class="text-page @if($page->draft) draft @endif"><i class="zmdi zmdi-file-text"></i>{{$page->name}}</a></h5>

View File

@ -1,10 +1,10 @@
<div id="code-editor">
<div class="overlay" ref="overlay" v-cloak @click="hide()">
<div overlay ref="overlay" v-cloak @click="hide()">
<div class="popup-body" @click.stop>
<div class="popup-header primary-background">
<div class="popup-title">{{ trans('components.code_editor') }}</div>
<button class="popup-close neg corner-button button" @click="hide()">x</button>
<button class="overlay-close neg corner-button button" @click="hide()">x</button>
</div>
<div class="padded">

View File

@ -1,9 +1,9 @@
<div id="entity-selector-wrap">
<div class="overlay" entity-link-selector>
<div overlay entity-link-selector>
<div class="popup-body small flex-child">
<div class="popup-header primary-background">
<div class="popup-title">{{ trans('entities.entity_select') }}</div>
<button type="button" class="corner-button neg button popup-close">x</button>
<button type="button" class="corner-button neg button overlay-close">x</button>
</div>
@include('components.entity-selector', ['name' => 'entity-selector'])
<div class="popup-footer">

View File

@ -1,10 +1,10 @@
<div id="image-manager" image-type="{{ $imageType }}" ng-controller="ImageManagerController" uploaded-to="{{ $uploaded_to or 0 }}">
<div class="overlay" ng-cloak ng-click="hide()">
<div overlay ng-cloak ng-click="hide()">
<div class="popup-body" ng-click="$event.stopPropagation()">
<div class="popup-header primary-background">
<div class="popup-title">{{ trans('components.image_select') }}</div>
<button class="popup-close neg corner-button button">x</button>
<button class="overlay-close neg corner-button button">x</button>
</div>
<div class="flex-fill image-manager-body">

View File

@ -7,7 +7,7 @@
<div class="row">
<div class="col-sm-6 faded">
<div class="action-buttons text-left">
<a data-action="expand-entity-list-details" class="text-primary text-button"><i class="zmdi zmdi-wrap-text"></i>{{ trans('common.toggle_details') }}</a>
<a expand-toggle=".entity-list.compact .entity-item-snippet" class="text-primary text-button"><i class="zmdi zmdi-wrap-text"></i>{{ trans('common.toggle_details') }}</a>
</div>
</div>
</div>

View File

@ -15,11 +15,13 @@
<a href="{{ $page->getUrl() }}" class="text-page entity-list-item-link"><i class="zmdi zmdi-file-text"></i><span class="entity-list-item-name">{{ $page->name }}</span></a>
</h4>
<div class="entity-item-snippet">
@if(isset($page->searchSnippet))
<p class="text-muted">{!! $page->searchSnippet !!}</p>
@else
<p class="text-muted">{{ $page->getExcerpt() }}</p>
@endif
</div>
@if(isset($style) && $style === 'detailed')
<div class="row meta text-muted text-small">

View File

@ -51,7 +51,7 @@
</a>
@if($bookChild->isA('chapter') && count($bookChild->pages) > 0)
<p class="text-muted chapter-toggle @if($bookChild->matchesOrContains($current)) open @endif">
<p chapter-toggle class="text-muted @if($bookChild->matchesOrContains($current)) open @endif">
<i class="zmdi zmdi-caret-right"></i> <i class="zmdi zmdi-file-text"></i> <span>{{ trans('entities.x_pages', ['count' => $bookChild->pages->count()]) }}</span>
</p>
<ul class="menu sub-menu inset-list @if($bookChild->matchesOrContains($current)) open @endif">

View File

@ -1,5 +1,5 @@
<style id="custom-styles" data-color="{{ setting('app-color') }}" data-color-light="{{ setting('app-color-light') }}">
header, #back-to-top, .primary-background {
header, [back-to-top], .primary-background {
background-color: {{ setting('app-color') }} !important;
}
.faded-small, .primary-background-light {

View File

@ -1,12 +1,12 @@
<div class="notification anim pos" @if(!session()->has('success')) style="display:none;" @endif>
<div notification="success" data-autohide class="pos" @if(session()->has('success')) data-show @endif>
<i class="zmdi zmdi-check-circle"></i> <span>{!! nl2br(htmlentities(session()->get('success'))) !!}</span>
</div>
<div class="notification anim warning stopped" @if(!session()->has('warning')) style="display:none;" @endif>
<div notification="warning" class="warning" @if(session()->has('warning')) data-show @endif>
<i class="zmdi zmdi-info"></i> <span>{!! nl2br(htmlentities(session()->get('warning'))) !!}</span>
</div>
<div class="notification anim neg stopped" @if(!session()->has('error')) style="display:none;" @endif>
<div notification="error" class="neg" @if(session()->has('error')) data-show @endif>
<i class="zmdi zmdi-alert-circle"></i> <span>{!! nl2br(htmlentities(session()->get('error'))) !!}</span>
</div>