--- /dev/null
+
+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;
\ No newline at end of file
--- /dev/null
+
+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;
\ No newline at end of file
--- /dev/null
+
+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;
\ No newline at end of file
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 = {};
--- /dev/null
+
+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;
\ No newline at end of file
--- /dev/null
+
+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;
\ No newline at end of file
function hide() {
element.fadeOut(240);
}
+ scope.hide = hide;
// Listen to confirmation of entity selections (doubleclick)
events.listen('entity-select-confirm', entity => {
}
});
window.$http = axiosInstance;
-
Vue.prototype.$http = axiosInstance;
-require("./vues/vues");
-require("./components");
-
// AngularJS - Create application and load components
const angular = require("angular");
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');
};
});
-// 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
}
}
-.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 {
}
}
-.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);
-.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;
.inset-list {
display: none;
overflow: hidden;
- margin-bottom: $-l;
}
h5 {
display: block;
border-left-color: $color-page-draft;
}
}
+ .entity-list-item {
+ margin-bottom: $-m;
+ }
hr {
margin-top: 0;
}
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;
background-color: rgba($color-chapter, 0.12);
}
}
- .chapter-toggle {
+ [chapter-toggle] {
padding-left: $-s;
}
.list-item-chapter {
h4, a {
line-height: 1.2;
}
- p {
+ .entity-item-snippet {
display: none;
+ }
+ p {
font-size: $fs-m * 0.8;
padding-top: $-xs;
margin: 0;
}
}
-// 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 {
// Back to top link
$btt-size: 40px;
-#back-to-top {
+[back-to-top] {
background-color: $primary;
position: fixed;
bottom: $-m;
@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>
<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>
- @if(isset($book->searchSnippet))
- <p class="text-muted">{!! $book->searchSnippet !!}</p>
- @else
- <p class="text-muted">{{ $book->getExcerpt() }}</p>
- @endif
+ <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>
\ No newline at end of file
<i class="zmdi zmdi-collection-bookmark"></i><span class="entity-list-item-name">{{ $chapter->name }}</span>
</a>
</h4>
- @if(isset($chapter->searchSnippet))
- <p class="text-muted">{!! $chapter->searchSnippet !!}</p>
- @else
- <p class="text-muted">{{ $chapter->getExcerpt() }}</p>
- @endif
+
+ <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>
<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">
<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">
<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">
<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>
<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>
- @if(isset($page->searchSnippet))
- <p class="text-muted">{!! $page->searchSnippet !!}</p>
- @else
- <p class="text-muted">{{ $page->getExcerpt() }}</p>
- @endif
+ <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">
</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">
<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 {
-<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>