Files
mythoughts/scripts/mythoughts.js
2020-03-22 17:29:50 +01:00

568 lines
15 KiB
JavaScript
Executable File

function MyThoughts(asGlobals)
{
self = this;
this.consts = asGlobals.consts;
this.consts.hash_sep = '-';
this.consts.default_page = 'home';
this.consts.title = 'My Thoughts';
this.consts.root = location.protocol+'//'+location.host+location.pathname;
this.init = function()
{
//Variables & constants from php
self.vars('tmp', 'object');
self.vars('page', 'string');
self.vars('template', 'boolean');
self.updateVars(asGlobals.vars);
//page elem
self.elem = {};
self.elem.$Container = $('#container');
//on window resize
$(window).resize(self.onResize).resize();
//Hash management
self.resetTmpFunctions();
$(window)
.bind('hashchange', self.onHashChange)
.trigger('hashchange');
$('html').on('keydown', self.onKeydown);
};
this.updateVars = function(asVars)
{
$.each(asVars, function(sKey, oValue){self.vars(sKey, oValue)});
};
/* Scrollbar */
/*this.scrollbar = function(sPos)
{
var $Cv = $('#main');
if(!self.vars('mobile'))
{
if(typeof self.vars('scrollbar') === 'undefined')
{
$Cv.tinyscrollbar({axis:'y', thumbSizeMin:'20', thumbSize:'20'});
self.vars('scrollbar', $Cv.data("plugin_tinyscrollbar"));
}
else self.vars('scrollbar').update(sPos);
}
else $Cv.unbind("tinyscrollbar");
}*/
/* Menu */
this.initMenu = function()
{
self.elem.$Menu.show('fast');
//Thoughts on side menu
self.updateSideMenu();
};
this.updateSideMenu = function() {
Tools.ajax(
'thoughts',
function(asData){
self.elem.$Side.find('.tag:not(.write)').remove();
$.each(asData, function(iKey, asThought) {
$Tile = $('<div>', {'class': 'tag'}).appendTo(self.elem.$Side);
var $Link = $('<a>', {'href': '#read-'+asThought.id_thought, title:asThought.created_d+', '+asThought.created_h})
.html(asThought.created_d.replace(' ', '<br />'))
.appendTo($Tile);
});
self.elem.$Side.slideDown('fast', self.setSideElemVisibility);
}
);
};
this.setSideElemVisibility = function() {
if(self.elem.$Side) {
var iHeight = 0;
var iMaxHeight = self.elem.$Side.height();
self.elem.$Side.children().each(function(){
$(this).toggle(iMaxHeight - iHeight > 0);
iHeight += $(this).outerHeight(true);
});
}
};
/* Events */
this.onResize = function()
{
self.vars('mobile', $('body').css('min-width')=='120px');
self.setSideElemVisibility();
//self.scrollbar();
};
this.onHashChange = function()
{
var asHash = self.getHash();
self.switchPage(asHash);
};
this.resetTmpFunctions = function()
{
self.pageInit = function(asHash, bFirstPage){console.log('no init for the page: '+asHash.page)};
self.onSamePageMove = function(asHash){return false};
self.onQuitPage = function(){return true};
self.onFeedback = function(sType, sMsg){Tools.feedback(sType, sMsg);};
self.onKeydown = function(oEvent){};
};
this.feedback = function(sType, sMsg) {
self.onFeedback(sType, sMsg);
};
/* Hash Handling */
this.getHash = function()
{
var sHash = self.hash();
var asHash = sHash.split(self.consts.hash_sep);
var sPage = asHash.shift() || '';
return {hash:sHash, page:sPage, items:asHash};
};
this.setHash = function(sPage, asItems, bReboot)
{
bReboot = bReboot || false;
sPage = sPage || '';
asItems = asItems || [];
if(typeof asItems == 'string') asItems = [asItems];
if(sPage != '')
{
var sItems = (asItems.length > 0)?self.consts.hash_sep+asItems.join(self.consts.hash_sep):'';
self.hash(sPage+sItems, bReboot);
}
};
this.hash = function(hash, bReboot)
{
bReboot = bReboot || false;
if(!hash) return window.location.hash.slice(1);
else window.location.hash = '#'+hash;
if(bReboot) location.reload();
};
this.getVars = function(fOnSuccess)
{
fOnSuccess = fOnSuccess || function(){};
getInfo
(
'vars',
function(asData)
{
self.updateVars(asData.vars);
fOnSuccess();
},
{}
);
};
/* Page Switching */
this.loadHome = function(asData) {
self.vars('log_in', asData.log_in);
self.vars('id', asData.id);
if(self.vars('log_in')) {
//Setup menu
self.initMenu();
//Swap to default page
self.setHash('write');
}
};
this.getActionLink = function(sAction, oVars)
{
if(!oVars) oVars = {};
sVars = '';
for(i in oVars)
{
sVars += '&'+i+'='+oVars[i];
}
return self.consts.process_page+'?a='+sAction+sVars;
};
this.switchPage = function(asHash)
{
var sCurrPage = self.vars('page');
var sNextPage = asHash.page;
var bLoggedIn = self.vars('log_in');
var sDefaultPage = bLoggedIn?'write':'logon';
if( asHash.hash == '' ||
sNextPage == '' ||
!bLoggedIn && sNextPage != sDefaultPage ||
bLoggedIn && sNextPage == 'logon'
)
{
self.setHash(sDefaultPage); //force first page
}
else
{
var bSamePage = (sCurrPage == sNextPage);
if(self.onQuitPage(bSamePage) && !bSamePage || self.onSamePageMove(asHash))
{
//Delete tmp variables
self.vars('tmp', {});
//disable tmp functions
self.resetTmpFunctions();
//Officially a new page
var bFirstPage = (sCurrPage == '');
self.vars('page', sNextPage);
//Update Page Title
var sDetail = asHash.items[0] || '';
document.title = self.consts.title+' - '+sNextPage+' '+sDetail;
//Replacing DOM
var $Dom = $(self.consts.pages[sNextPage]);
if(bFirstPage)
{
self.elem.$Container.html($(self.consts.pages['template']));
self.elem.$Main = self.elem.$Container.find('#main');
self.elem.$Menu = self.elem.$Container.find('#menu');
self.elem.$Side = self.elem.$Container.find('#side');
if(self.vars.log_in) self.initMenu();
self.splash(self.elem.$Main, $Dom, asHash, bFirstPage); //first page
}
else
{
self.elem.$Main.stop().fadeTo('fast', 0, function(){self.splash(self.elem.$Main, $Dom, asHash, bFirstPage);}); //Switching page
}
}
}
};
this.splash = function($FadeInElem, $Dom, asHash, bFirstPage)
{
//Switch main content
$FadeInElem.empty();
$FadeInElem.html($Dom);
//Page Bootstrap
self.elem.$Main.removeClass('no_frame');
//Show main
$FadeInElem.fadeTo('fast', 1, function(){self.pageInit(asHash, bFirstPage);});
};
/* Variables Handling */
this.vars = function(sVarName, oValue)
{
var asTypes = {boolean:false, string:'', integer:0, array:[], object:{}};
var asVarName = (typeof sVarName == 'object')?sVarName:[sVarName];
var sFirstVarName = asVarName[0];
//Get, only name parameter
if(typeof oValue == 'undefined')
{
return getElem(self.vars, asVarName);
}
//Init, name & type / default value
else if(typeof oValue !== 'undefined' && typeof self.vars[sFirstVarName] === 'undefined')
{
self.vars[sFirstVarName] = (typeof asTypes[oValue] !== 'undefined')?asTypes[oValue]:oValue;
return self.vars[sFirstVarName];
}
//Set, name & value
else if(typeof oValue !== 'undefined' && typeof self.vars[sFirstVarName] !== 'undefined')
{
setElem(self.vars, asVarName, oValue);
return getElem(self.vars, asVarName);
}
return null;
};
this.tmp = function(sVarName, oValue)
{
var asVarName = (typeof sVarName == 'object')?sVarName:[sVarName];
asVarName.unshift('tmp');
return self.vars(asVarName, oValue);
};
}
//Date format
const Inline = Quill.import('blots/inline');
class ThoughtDate extends Inline {
static create(value) {
let node = super.create();
node.setAttribute('class', 'edi_thought_date');
return node;
}
}
ThoughtDate.blotName = 'thought_date';
ThoughtDate.tagName = 'div';
//Time format
class ThoughtTime extends Inline {
static create(value) {
let node = super.create();
node.setAttribute('class', 'edi_thought_time');
return node;
}
}
ThoughtTime.blotName = 'thought_time';
ThoughtTime.tagName = 'div';
Quill.register({'blots/thought_date':ThoughtDate, 'blots/thought_time':ThoughtTime});
class Editor {
constructor(sContainerId, bReadOnly) {
this.id = 0;
this.prevId = 0;
this.nextId = 0;
this.thoughts = [];
this.page = 0;
this.keystrokes = 0;
this.line = false;
this.readOnly = bReadOnly || false;
this.sCursorPos = '';
//DOM Elements
var sEditorId = 'edi'+Math.floor(Math.random() * 1000);
this.$Container = $(sContainerId).addClass('editor').append(self.consts.pages['editor']);
this.$EditorBox = this.$Container.find('.edi_container');
this.$Editor = this.$Container.find('.edi_table').attr('id', sEditorId);
this.$PrevBtn = $('.prev');
this.$NextBtn = $('.next');
this.onKeyStroke = function(e){};
this.oQuill = new Quill('#'+sEditorId, {
theme: 'bubble',
placeholder: 'What\'s on your mind?',
readOnly: bReadOnly,
modules: {
toolbar: [['bold', 'italic', 'underline'], [{ 'list': 'ordered'}, { 'list': 'bullet' }], ['clean']]
}
});
this._initEvents();
this._postInit();
}
_initEvents() {
//Key strokes
this.$Editor.keydown((e) => {
if($.inArray(e.which, [13, 37, 38, 39, 40]) != -1) this._onChange('', '', 'user', e);
else if(e.which == 33) this.prevPage();
else if(e.which == 34) this.nextPage();
else this.onKeyStroke(e);
});
//On text modification
this.oQuill.on('text-change', (delta, oldDelta, sSource) => {this._onChange(delta, oldDelta, sSource);});
//Mouse wheel events
$(window).mousewheel((turn, iDelta) => {
var iNewPage = this.page + ((iDelta > 0)?-1:1);
this.moveToPage(iNewPage);
return false;
});
//Page buttons
this.$PrevBtn.click(() => {this.moveToPage(this.page - 1);});
this.$NextBtn.click(() => {this.moveToPage(this.page + 1);});
}
_postInit(iPage) {
iPage = iPage || 0;
this._setPageHeight();
this.moveToPage(iPage);
if(!this.readOnly) this.oQuill.focus();
}
prevPage() {
this.$PrevBtn.click();
}
nextPage() {
this.$NextBtn.click();
}
open(iThoughtId) {
if(!(iThoughtId in this.thoughts)) {
Tools.ajax(
'load',
(asData) => {
this.prevId = this.id;
this.id = asData.id;
this.nextId = asData.next_id;
this.thoughts[asData.id] = asData;
asData.ops.unshift({
'attributes': {'thought_time': true},
'insert': asData.created_h+"\n"
});
if(this.prevId==0 || asData.created_d != this.thoughts[this.prevId].created_d) {
asData.ops.unshift({
'attributes': {'thought_date': true},
'insert': 'Thoughts on '+asData.created_d
});
}
if(!this.isEmpty()) asData.ops = this.getContent().concat(asData.ops);
this.oQuill.setContents(asData.ops);
this._postInit(this.page);
},
{id: iThoughtId}
);
}
}
_setPageHeight() {
var iHeight = this.$EditorBox.height();
var iLineHeight = parseInt(this.$Editor.find('p').css('line-height'));
var iMaxHeight = Math.floor(iHeight / iLineHeight) * iLineHeight;
this.$EditorBox.height(iMaxHeight+'px');
this.lineHeight = iLineHeight;
this.pageHeight = iMaxHeight;
}
_onChange(delta, oldDelta, sSource, e) {
if(sSource == 'user')
{
var range = this.oQuill.getSelection();
if(range)
{
var bSelection = (typeof e != 'undefined')?e.shiftKey:false;
var oSelBound = this.oQuill.getBounds(range.index, range.length);
var oEditorCurBound = { top: this.pageHeight * this.page,
bottom: this.pageHeight * (this.page + 1)};
//console.log('oEditorCurBound: top='+oEditorCurBound.top+' bottom='+oEditorCurBound.bottom);
//console.log('---------------------');
//console.log('range.length = '+range.length);
//console.log('oSelBound: top='+oSelBound.top+' bottom='+oSelBound.bottom);
//Detecting new selection & saving original line
if(!this.line && bSelection) this.line = $.extend({}, oSelBound);
else if(!bSelection) this.line = false;
//Detecting navigating back to original line
var bReset = (this.line && this.line.top == oSelBound.top && this.line.bottom == oSelBound.bottom);
//Anticipating arrows (downside of using keydown event)
if(e)
{
switch(e.which)
{
case 13: //Enter
case 40: //Down
if(bSelection) {
if(bReset) this.sCursorPos = 'last';
if(this.sCursorPos == 'last') { //Downwards selection, expanding
oSelBound.bottom += this.lineHeight;
}
else { //Upwards selection, reducing
oSelBound.top += this.lineHeight;
}
}
else oSelBound.bottom += this.lineHeight;
break;
case 38: //Up
if(bSelection) {
if(bReset) this.sCursorPos = 'first';
if(this.sCursorPos == 'last') { //Downwards selection, reducing
oSelBound.bottom -= this.lineHeight;
}
else { //Upwards selection, expanding
oSelBound.top -= this.lineHeight;
}
}
else oSelBound.top = Math.max(0, oSelBound.top - this.lineHeight);
break;
case 37: //Left
if(bReset && bSelection) this.sCursorPos = 'first';
oSelBound = this.oQuill.getBounds(Math.max(0, range.index - 1), range.length);
break;
case 39: //Right
if(bReset && bSelection) this.sCursorPos = 'last';
oSelBound = this.oQuill.getBounds(range.index + 1, range.length);
break;
}
}
else this._incKeyStrokes();
//console.log('oSelBound: top='+oSelBound.top+' bottom='+oSelBound.bottom);
var sNewPage = this.page;
if( oSelBound.top < oEditorCurBound.top && (!bSelection || this.sCursorPos == 'first') ||
oSelBound.bottom < oEditorCurBound.top && (!bSelection || this.sCursorPos == 'last')) {
sNewPage--;
}
else if(oSelBound.bottom > oEditorCurBound.bottom && (!bSelection || this.sCursorPos == 'last') ||
oSelBound.top > oEditorCurBound.bottom && (!bSelection || this.sCursorPos == 'first')) {
sNewPage++;
}
this.moveToPage(sNewPage);
}
}
}
moveToPage(iNewPage) {
var iContentHeight = this.$Editor.height();
var iLastPage = Math.floor(iContentHeight / this.pageHeight);
if(iNewPage == 'last') iNewPage = iLastPage;
if(iNewPage >= 0 && iNewPage <= iLastPage)
{
if(iNewPage != this.page)
{
var iOldPage = this.page;
this.page = iNewPage;
//Page Position
var iOffset = this.page * this.pageHeight * -1;
this.$Editor.css('top', iOffset);
}
//Page Number
//$('.curr').text(self.vars('quill_page') + 1);
//Detect First/Last Page
this.$PrevBtn.toggleClass('visible', this.page != 0);
this.$NextBtn.toggleClass('visible', this.page != iLastPage);
if(this.readOnly && this.page == iLastPage && this.nextId > 0) {
this.open(this.nextId);
}
}
}
_incKeyStrokes() {
this.keystrokes += 1;
}
getContent() {
return this.oQuill.getContents().ops;
}
isEmpty() {
const rEmpty = /^(<p>(<br\s?\/?>|\s+|)<\/p>|)$/gm;
return rEmpty.test(this.oQuill.getText().trim());
}
quill2Html(asDelta) {
var tempCont = document.createElement('div');
(new Quill(tempCont)).setContents(asDelta);
return tempCont.getElementsByClassName('ql-editor')[0].innerHTML;
}
}