Replaced Moment with Carbon #84

Merged
Ghost merged 148 commits from develop into beta 2022-09-22 05:53:36 +02:00
36 changed files with 6997 additions and 7465 deletions
Showing only changes of commit 1e37580869 - Show all commits

3
.gitignore vendored
View file

@ -39,9 +39,6 @@ public/assets/*
public/assets/css/*
!public/assets/css/dash
!public/assets/scripts
public/assets/scripts/*
!public/assets/scripts/dash.js
!public/assets/scripts/dash.js.map
!public/assets/images
public/assets/images/*
!public/assets/images/global/

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,211 @@
import FipamoAdminAPI from '../libraries/FipamoAdminAPI';
import Maintenance from './controllers/MaintenanceManager';
import DataUitls from './utils/DataUtils';
import * as DataEvent from './events/DataEvent';
import DashManager from './controllers/DashManager';
import Notfications from './ui/Notifications';
const data = new DataUitls();
const notify = new Notfications();
export default class Base {
//--------------------------
// constructor
//--------------------------
constructor() {
this.processing = false;
this.start();
}
//--------------------------
// methods
//--------------------------
//TODO: Move init functions and set up to their own class
start() {
if (
document.getElementById('login') ||
document.querySelector('[role="site-restore"]')
) {
var options = document.getElementsByClassName('init-option');
for (let index = 0; index < options.length; index++) {
options[index].addEventListener('click', e => this.handleOptions(e));
}
if (document.getElementById('login')) {
document
.getElementById('login-btn')
.addEventListener('click', e => this.handleLogin(e));
} else {
document
.getElementById('init-blog')
.addEventListener('click', e => this.handleSetup(e));
document
.getElementById('blog-restore')
.addEventListener('click', e => this.handleRestore(e));
}
} else if (document.getElementById('dash-reset')) {
document
.getElementById('get-secret-btn')
.addEventListener('click', e => this.handleReset(e));
document
.getElementById('reset-btn')
.addEventListener('click', e => this.handleReset(e));
} else {
new DashManager();
}
}
//--------------------------
// event handlers
//--------------------------
handleLogin(e) {
if (this.processing) return;
let self = this;
e.preventDefault();
let authForm = data.formDataToJSON(document.getElementById('login'));
//notify.alert('Looking, hold up', null);
let api = new FipamoAdminAPI();
this.processing = true;
api.login(authForm)
.then(response => {
self.processing = false;
if (response.type === DataEvent.REQUEST_LAME) {
e.target.innerHTML = response.message;
} else {
e.target.innerHTML = response.message;
setTimeout(() => {
window.location = '/dashboard';
}, 500);
}
})
.catch(err => {
self.processing = false;
});
}
handleSetup(e) {
if (this.processing) return;
let self = this;
e.stopPropagation();
e.preventDefault();
let setUpForm = data.formDataToJSON(document.getElementById('init-form'));
let mm = new Maintenance();
this.processing = true;
mm.create(setUpForm)
.then(response => {
if (response.type === DataEvent.API_INIT_LAME) {
self.processing = false;
e.target.innerHTML = response.message;
} else {
self.processing = false;
e.target.innerHTML = response.message;
setTimeout(() => {
window.location = '/dashboard';
}, 700);
}
})
.catch(err => {
self.processing = false;
//notify.alert(err, false);
});
}
handleRestore(e) {
if (this.processing) return;
let self = this;
e.stopPropagation();
e.preventDefault();
let mm = new Maintenance();
var form = document.getElementById('init-restore');
this.processing = true;
mm.restore(form)
.then(response => {
if (response.type === DataEvent.REQUEST_LAME) {
self.processing = false;
e.target.innerHTML = response.message;
} else {
self.processing = false;
e.target.innerHTML = response.message;
setTimeout(() => {
window.location = '/dashboard';
}, 1500);
}
})
.catch(err => {
self.processing = false;
e.target.innerHTML = err;
});
}
handleReset(e) {
e.stopPropagation();
e.preventDefault();
let self = this;
let mm = new Maintenance();
if (e.target.id == 'get-secret-btn') {
let data = {
email: document.getElementById('email').value,
task: 'retrieveSecret'
};
this.processing = true;
mm.secret(data)
.then(response => {
self.processing = false;
if (response.secret) {
document.getElementById('secret').value = response.secret;
notify.alert(response.message, true);
} else {
if (response.type == 'mailSent') {
notify.alert(response.message, true);
} else {
notify.alert(response.message, false);
}
}
})
.catch(err => {
self.processing = false;
notify.alert(err, false);
});
} else {
let data = {
newPass: document.getElementById('new_password').value,
newPassConfirm: document.getElementById('new_password2').value,
secret: document.getElementById('secret').value
};
mm.newPass(data)
.then(response => {
self.processing = false;
if (response.type == 'passNotCreated') {
notify.alert(response.message, false);
} else {
notify.alert(response.message, true);
setTimeout(() => {
window.location = '/dashboard';
}, 1000);
}
})
.catch(err => {
self.processing = false;
notify.alert(err, false);
});
}
}
handleOptions(e) {
e.stopPropagation();
e.preventDefault();
let init = document.querySelector('[role="restore-fresh"]');
let restore = document.querySelector('[role="restore-backup"]');
if (e.target.id === 'init-switch-restore') {
init.style.display = 'none';
init.style.visibility = 'hidden';
restore.style.display = 'grid';
restore.style.visibility = 'visible';
} else {
init.style.display = 'grid';
init.style.visibility = 'visible';
restore.style.display = 'none';
restore.style.visibility = 'hidden';
}
}
}

View file

@ -0,0 +1,9 @@
import Editor from './controllers/PageEditor.js';
document.addEventListener(
'DOMContentLoaded',
function () {
new Editor();
},
false
);

View file

@ -0,0 +1,9 @@
import Base from './Base';
document.addEventListener(
'DOMContentLoaded',
function () {
new Base();
},
false
);

View file

@ -0,0 +1,44 @@
import FipamoAdminAPI from "../../libraries/FipamoAdminAPI";
import Notficaton from "../ui/Notifications";
const notify = new Notficaton();
export default class Mailer {
//--------------------------
// constructor
//--------------------------
constructor() {}
//--------------------------
// methods
//--------------------------
sendMail() {
let mailData = {
content: "This is a test email"
};
let admin = new FipamoAdminAPI();
admin
.sendMail(mailData)
.then((result) => {
notify.alert(result.message, true);
})
.catch((err) => {
notify.alert(err.message, false);
});
}
testMail() {
let mailData = {
content: "This is a test email",
mail_task: "TESTING"
};
let admin = new FipamoAdminAPI();
admin
.sendMail(mailData)
.then((result) => {
notify.alert(result.message, true);
})
.catch((err) => {
notify.alert(err.message, false);
});
}
//--------------------------
// event handlers
//--------------------------
}

View file

@ -0,0 +1,35 @@
export default class NavActions {
//--------------------------
// constructor
//--------------------------
constructor() {}
//--------------------------
// methods
//--------------------------
syncMenu() {
let navData = [];
let items = document.getElementById('nav-items').children;
for (let index = 0; index < items.length; index++) {
navData.push({
title: items[index].getElementsByTagName('label')[0].innerHTML,
id: items[index].id,
slug: items[index].getAttribute('data-slug'),
uuid: items[index].getAttribute('data-uuid'),
path: items[index].getAttribute('data-path')
});
}
let data = { menu: navData, remove: null };
return new Promise(function (resolve) {
resolve(data);
});
}
removeItem(id) {
document.getElementById('nav-items').removeChild(document.getElementById(id));
}
//--------------------------
// event handlers
//--------------------------
}

View file

@ -0,0 +1,53 @@
import StringUtils from '../utils/StringUtils.js';
export default class PostActions {
//--------------------------
// constructor
//--------------------------
constructor() {}
//--------------------------
// methods
//--------------------------
collectInfo(files) {
return new Promise((resolve, reject) => {
let pageInfo = [];
let pageRef = document.querySelector('[role="file-manager"]');
//process html content for storage
let txt = document.createElement('textarea');
txt.innerHTML = document.getElementById('highlight-content').innerHTML;
let html = txt.value;
html = html.replace(/<\/?span[^>]*>/g, ''); //removes prism styling
html = html.replace(/<\/?br[^>]*>/g, '\n'); //convert back to encoded line break for storage
//build data object
pageInfo = {
id: pageRef.getAttribute('data-index'),
uuid: pageRef.getAttribute('data-uuid'),
layout: document.getElementById('page-templates').value,
current_title: pageRef.getAttribute('data-slug'),
content: html,
title: document.getElementById('post-title-text').value,
created: document.getElementById('post-date').getAttribute('data-raw'),
slug: new StringUtils().cleanString(
document.getElementById('post-title-text').value
),
tags: document.getElementById('post-tags').value,
menu: document
.getElementById('option-menu-pin')
.getAttribute('data-active'),
featured: document
.getElementById('option-feature')
.getAttribute('data-active'),
published: document
.getElementById('option-published')
.getAttribute('data-active'),
form_token: document.getElementById('form_token').value,
imageList: files.images,
fileList: files.files
};
resolve(pageInfo);
});
}
//--------------------------
// event handlers
//--------------------------
}

View file

@ -0,0 +1,79 @@
export default class SettingsActions {
//--------------------------
// constructor
//--------------------------
constructor() {}
//--------------------------
// methods
//--------------------------
getInfo() {
let handle = document.getElementById('settings-handle').value;
let email = document.getElementById('settings-email').value;
let url = document.getElementById('settings-url').value;
let title = document.getElementById('settings-title').value;
let desc = document.getElementById('settings-desc').value;
//let privacy = document.getElementById('privacy-toggle').getAttribute('data-private');
let render = document.getElementById('render-toggle').getAttribute('data-render');
let background = document
.querySelector('[role="background"]')
.style.backgroundImage.slice(4, -1)
.replace(/"/g, '');
let selected = '';
let selects = document.querySelectorAll('.theme-select');
let smtpDomain = document.getElementById('smtp-domain').value;
let smtpEmail = document.getElementById('smtp-email').value;
let smtpPass = document.getElementById('smtp-pass').value;
let mgDomain = document.getElementById('mg-domain').value;
let mgKey = document.getElementById('mg-key').value;
let mailActive = '';
let mailOptions = document.querySelectorAll('.mail-option');
let apiStatus = document
.getElementById('api-access-toggle')
.getAttribute('data-enabled');
let dynamicRenderStatus = document
.getElementById('dynamic-render-toggle')
.getAttribute('data-enabled');
var i, count;
for (i = 0, count = selects.length; i < count; i++) {
if (selects[i].getAttribute('data-enabled') == 'true')
selected = selects[i].id;
}
for (i = 0, count = mailOptions.length; i < count; i++) {
if (mailOptions[i].getAttribute('data-enabled') == 'true')
mailActive = mailOptions[i].id;
}
let settingsData = {
global: {
base_url: url,
title: title,
descriptions: desc,
background: background,
private: false,
renderOnSave: render,
theme: selected,
externalAPI: apiStatus,
dynamicRender: dynamicRenderStatus
},
member: { handle: handle, email: email },
email: {
active: mailActive,
smtp: {
domain: smtpDomain,
email: smtpEmail,
password: smtpPass
},
mailgun: {
domain: mgDomain,
key: mgKey
}
}
};
return new Promise(function (resolve) {
resolve(settingsData);
});
}
//--------------------------
// event handlers
//--------------------------
}

View file

@ -0,0 +1,44 @@
import PostIndex from './PostIndex';
import SettingsIndex from './SettingsIndex';
import NaviIndex from './NavIndex';
import Menu from '../ui/Menu';
export default class DashManager {
//--------------------------
// constructor
//--------------------------
constructor() {
this.currentDisplay = '';
this.urlPieces = document.URL.split('/');
this.chooseDisplay(this.urlPieces[4], this.urlPieces[5]);
//start main menu handler
new Menu();
}
//--------------------------
// methods
//--------------------------
start() {}
chooseDisplay(section, page) {
this.currentDisplay = '';
switch (section) {
case 'page':
this.currentDisplay = new PostIndex(page);
break;
case 'settings':
this.currentDisplay = new SettingsIndex();
break;
case 'navigation':
this.currentDisplay = new NaviIndex();
break;
default:
//just chill
break;
}
this.start();
}
//--------------------------
// event handlers
//--------------------------
}

View file

@ -0,0 +1,292 @@
//** REQUEST TYPES **//
export const REQUEST_TYPE_POST = 'POST';
export const REQUEST_TYPE_GET = 'GET';
export const REQUEST_TYPE_PUT = 'PUT';
export const REQUEST_TYPE_DELETE = 'DELETE';
//** POST CONTENT TYPES **//
export const CONTENT_TYPE_JSON = 'json';
export const CONTENT_TYPE_FORM = 'x-www-form-urlencoded';
//** API URLS **//
export const API_STATUS = '/api/v1/status';
export const API_INIT = '/api/v1/init';
export const API_RESTORE = '/api/v1/restore';
export const API_GET_SECRET = '/api/v1/get-secret';
export const API_RESET_PASS = '/api/v1/reset-password';
export const API_CREATE_BACKUP = '/api/v1/backup';
export const API_DOWNLOAD_BACKUP = '/api/v1/backup/download';
export const API_RESTORE_BACKUP = '/api/v1/backup/restore';
export const API_UPLOAD_AVATAR = '/api/v1/settings/add-avatar';
export const API_UPLOAD_BACKGROUND = '/api/v1/settings/add-feature-background';
export const API_IMAGE_UPLOAD = '/api/v1/page/add-entry-image';
export const API_FILES_UPLOAD = '/api/v1/files';
//** API TASKS **//
export const TASK_SITE_INIT = 'blogInit';
export const TASK_BACKUP_RESTORE = 'restoreBackup';
export const TASK_BACKUP_CREATE = 'createBackup';
export const TASK_GET_SECRET = 'retrieveSecret';
export const TASK_RESET_PASS = 'resetPassword';
export const TASK_UPLOAD_FILES = 'uploadFiles';
//** API STATUS **//
export const API_ACCESS_GOOD = 'apiUseAuthorized';
export const API_ACCESS_BAD = 'apiUseNotAuthorized';
/**
* A tub of methods for creating/restoring installs, resetting passwords and uploading images.
*/
class MaintenanceManager {
/**
* @constructor
* @param {string} baseURL - url of site; uses local when empty
* @param {string} key - user api key
*/
constructor(baseURL = null, key = null) {
this.accetableFiles = [
'image/jpeg',
'image/gif',
'image/png',
'image/svg',
'audio/mpeg',
'video/mp4',
'application/pdf',
'text/plain',
'text/rtf'
];
this.percentComplete = 0; //for later
this.token = null;
this.baseURL = null;
this.key = null;
if (key) this.key = key;
if (baseURL) this.baseURL = baseURL;
//if key is valid, checks to see if a session is active and returns
this._request(
this.baseURL
? this.baseURL + API_STATUS + '?key=' + this.key
: API_STATUS + '?key=' + this.key
).then(response => {
if (response.type === API_ACCESS_GOOD) {
this.token = response.token;
} else {
//don't set token
//console.log("NO TOKEN");
}
});
}
/**
* Promise method used create new site from scratch. For local use only.
* @param {object} data - json object that contains data for set up
* @property {string} new_member_handle - handle for new user
* @property {string} new_member_email - email for new user
* @property {string} new_member_pass - password for new user
* @property {string} new_member_title - title for new user
*/
create(data) {
return new Promise((resolve, reject) => {
this._request(
API_INIT,
null,
TASK_SITE_INIT,
REQUEST_TYPE_POST,
CONTENT_TYPE_JSON,
data
)
.then(result => {
resolve(result);
})
.catch(err => {
reject(err);
});
});
}
/**
* Promise method for restoring site from a previous back up. For local use only.
* @param {object} form - form object that contains restore data and files
* @property {string} restore_member_handle - handle for site user
* @property {string} restore_member_pass - password for site user
* @property {file} backup-upload - backup zip file
*/
restore(form) {
return new Promise((resolve, reject) => {
var url, event, method, type, data;
url = API_RESTORE;
event = TASK_BACKUP_RESTORE;
method = REQUEST_TYPE_POST;
type = CONTENT_TYPE_FORM;
data = new FormData(form);
this._request(url, null, event, method, type, data)
.then(result => {
resolve(result);
})
.catch(err => {
reject(err);
});
});
}
/**
* Promise method for creating a zip back up of current site. For local use only.
*/
backup() {
return new Promise((resolve, reject) => {
var url, event, method, type, data;
url = API_CREATE_BACKUP;
event = TASK_BACKUP_CREATE;
method = REQUEST_TYPE_POST;
type = CONTENT_TYPE_JSON;
data = { task: 'create_backup' };
this._request(url, null, event, method, type, data)
.then(result => {
resolve(result);
})
.catch(err => {
reject(err);
});
});
}
/**
* Promise method for retrieving user secret key. For local use only.
* @param {object} data - json object that contains data for set up
* @property {string} email - email for site user
*/
secret(data) {
return new Promise((resolve, reject) => {
this._request(
API_GET_SECRET,
TASK_GET_SECRET,
REQUEST_TYPE_POST,
CONTENT_TYPE_JSON,
data
)
.then(result => {
resolve(result);
})
.catch(err => {
reject(err);
});
});
}
/**
* Promise method for resetting password for user. For local use only.
* @param {object} data - json object that contains data for set up
* @property {string} new_password - password for user
* @property {string} new_password2 - confirm password for user
* @property {string} secret - secret key for user
*/
newPass(data) {
return new Promise((resolve, reject) => {
this._request(
API_RESET_PASS,
TASK_RESET_PASS,
REQUEST_TYPE_POST,
CONTENT_TYPE_JSON,
data
)
.then(result => {
resolve(result);
})
.catch(err => {
reject(err);
});
});
}
/**
* Promise method for uploading files [todo: change to uploading files]
* @param {string} type - type of upload
* @param {input} files - form input containing files
*/
filesUpload(type, files, progress = null) {
return new Promise((resolve, reject) => {
let url = API_FILES_UPLOAD;
if (this.baseURL) {
files.append('key', this.key);
files.append('remote', true);
} else {
files.append('remote', false);
}
this._request(
url,
progress,
TASK_UPLOAD_FILES,
REQUEST_TYPE_POST,
CONTENT_TYPE_FORM,
files
)
.then(r => {
resolve(r);
})
.catch(err => {
reject(err);
});
});
}
//--------------------------
// private
//--------------------------
_request(
requestURL,
progressBar = null,
eventType,
requestType = REQUEST_TYPE_GET,
contentType = CONTENT_TYPE_JSON,
requestData = null
) {
var self = this;
return new Promise(function (resolve, reject) {
var request = new XMLHttpRequest();
request.upload.addEventListener('progress', e =>
self.handleLoadProgress(e, progressBar)
);
request.open(requestType, requestURL, true);
request.onload = () => {
if (request.status == 200) {
let response = JSON.parse(request['response']);
resolve(response);
} else {
let error = JSON.parse(request['response']);
reject(error);
}
};
if (requestType == REQUEST_TYPE_PUT || requestType == REQUEST_TYPE_POST) {
if (eventType === TASK_UPLOAD_FILES)
request.setRequestHeader('fipamo-access-token', self.token);
switch (contentType) {
case CONTENT_TYPE_JSON:
request.setRequestHeader(
'Content-type',
'application/' + contentType
);
request.send(JSON.stringify(requestData));
break;
case CONTENT_TYPE_FORM:
request.send(requestData);
break;
}
} else {
request.send();
}
});
}
//--------------------------
// event handlers
//--------------------------
handleLoadProgress(e, progressBar) {
let percent = Math.ceil((e.loaded / e.total) * 100);
//if a progress bar element is present, talk to it
if (progressBar != null) {
progressBar.style.width = percent + '%';
}
}
}
export { MaintenanceManager as default };

View file

@ -0,0 +1,74 @@
import FipamoAdminAPI, { TASK_SYNC_NAV } from '../../libraries/FipamoAdminAPI';
import NavActions from '../actions/NavActions';
import * as DataEvent from '../events/DataEvent';
import Notifications from '../ui/Notifications';
import Sortable from 'sortablejs';
const notify = new Notifications();
export default class NavIndex {
//--------------------------
// constructor
//--------------------------
constructor() {
this.processing = false;
this.admin = new FipamoAdminAPI(null);
this.start();
}
//--------------------------
// methods
//--------------------------
start() {
//grabs elements and makes them sortables
let self = this;
Sortable.create(document.getElementById('nav-items'), {
onUpdate: () => {
new NavActions().syncMenu().then(data => {
notify.alert('Updating Menu', null);
self.admin.sync(TASK_SYNC_NAV, data).then(r => {
if (r.type == DataEvent.MENU_UPDATED) {
notify.alert(r.message, true);
} else {
notify.alert(r.message, true);
}
});
});
}
});
var nav = document.querySelectorAll('.nav-btn');
for (var i = 0, length = nav.length; i < length; i++) {
nav[i].addEventListener('click', e => this.handleNavButton(e), false);
}
}
//--------------------------
// event handlers
//--------------------------
handleNavButton(e) {
if (this.processing) return;
let id = '';
let self = this;
switch (e.target.id) {
case 'remove-item':
id = e.target.getAttribute('data-id');
new NavActions().removeItem(id);
new NavActions().syncMenu().then(data => {
data.remove = e.target.getAttribute('data-uuid');
notify.alert('Editing Menu', null);
self.processing = true;
self.admin.sync(TASK_SYNC_NAV, data).then(r => {
self.processing = false;
if (r.type == DataEvent.MENU_UPDATED) {
notify.alert(r.message, true);
} else {
notify.alert(r.message, true);
}
});
});
break;
case 'edit-item':
self.processing = false;
window.location =
'/dashboard/page/edit/' + e.target.getAttribute('data-id');
break;
}
}
}

View file

@ -0,0 +1,228 @@
//TOOLS
import FipamoAdminAPI, {
TASK_PAGE_CREATE,
TASK_PAGE_EDIT,
TASK_PAGE_DELETE
} from '../../libraries/FipamoAdminAPI.js';
import Maintenance from './MaintenanceManager.js';
import * as DataEvent from '../events/DataEvent.js';
import PageActions from '../actions/PageActions.js';
import * as EditorEvent from '../events/EditorEvent.js';
//import TinyDatePicker from 'tiny-date-picker'; TODO: Reactivate for scheduled publishing
import TextEditor from '../ui/TextEditor.js';
import Notfications from '../ui/Notifications.js';
import FileManager from '../ui/FileManager.js';
const notify = new Notfications();
export default class PostEditor {
//--------------------------
// constructor
//--------------------------
constructor() {
this.processing = false;
let self = 'this';
this.admin = new FipamoAdminAPI(null, document.getElementById('notify-progress'));
this.mm = new Maintenance(null, null);
this.urlPieces = document.URL.split('/');
this.post = [];
this.postID = null;
this.postUUID = null;
this.postLayout = null;
this.fm = null;
if (document.querySelector('[role="file-manager"]').getAttribute('data-index')) {
this.postID = document
.querySelector('[role="file-manager"]')
.getAttribute('data-index');
this.postUUID = document
.querySelector('[role="file-manager"]')
.getAttribute('data-uuid');
this.postLayout = document
.querySelector('[role="file-manager"]')
.getAttribute('data-layout');
}
if (document.getElementById('edit')) {
this.editor = new TextEditor(
document.getElementById('edit'),
document.querySelector('[role="file-manager"]').offsetHeight +
document.querySelector('[role="page-meta"]').offsetHeight +
document.querySelector('[role="text-editor"]').offsetHeight
);
this.editor.addListener(
EditorEvent.EDITOR_DELETE,
() => this.handleEditorOptions(EditorEvent.EDITOR_DELETE),
false
);
this.editor.addListener(
EditorEvent.EDITOR_UPLOAD_POST_IMAGE,
() => this.handleEditorOptions(EditorEvent.EDITOR_UPLOAD_POST_IMAGE),
false
);
this.editor.addListener(
EditorEvent.EDITOR_UPDATE,
() => this.handleEditorOptions(EditorEvent.EDITOR_UPDATE),
false
);
this.editor.addListener(
EditorEvent.EDITOR_SAVE,
() => this.handleEditorOptions(EditorEvent.EDITOR_SAVE),
false
);
document.getElementById('post-image-upload').addEventListener(
'change',
e => {
this.handleImageUpload(e.target.id, e.target.files);
},
false
);
/*
TinyDatePicker(document.getElementById('post-date'), {
mode: 'dp-below',
format() {
//return self.dateUtils.getDate('origin', date);
}
});
*/
this.start();
}
}
//--------------------------
// methods
//--------------------------
start() {
if (document.querySelector('[role="file-drop"]')) {
//insert fileManager here
this.fm = new FileManager(
document.querySelector('[role="file-drop"]'),
document.getElementById('page-files-upload'),
document.querySelector('[role="page-images-list"]'),
document.querySelector('[role="page-files-list"]')
);
var optionButtons = document.querySelectorAll('.post-option-btn');
for (var i = 0, length = optionButtons.length; i < length; i++) {
optionButtons[i].addEventListener(
'click',
e => this.handlePostOptions(e),
false
);
}
}
}
//--------------------------
// event handlers
//--------------------------
handlePostOptions(e) {
let currentOption = null;
switch (e.target.id) {
case 'option-page-icon':
case 'option-menu-pin':
currentOption = document.getElementById('option-menu-pin');
break;
case 'option-feature-icon':
case 'option-feature':
currentOption = document.getElementById('option-feature');
break;
case 'option-published-icon':
case 'option-published':
currentOption = document.getElementById('option-published');
break;
}
if (currentOption != null) {
let active = currentOption.getAttribute('data-active');
active == 'false'
? currentOption.setAttribute('data-active', 'true')
: currentOption.setAttribute('data-active', 'false');
}
}
handleEditorOptions(e) {
if (this.processing) return;
let self = this;
switch (e) {
case EditorEvent.EDITOR_SAVE:
case EditorEvent.EDITOR_UPDATE:
var task = '';
e === EditorEvent.EDITOR_SAVE
? (task = TASK_PAGE_CREATE)
: (task = TASK_PAGE_EDIT);
new PageActions().collectInfo(this.fm.getFileOrder()).then(page => {
self.processing = true;
notify.alert('Writing down changes', null);
self.admin
.pageActions(task, page)
.then(r => {
self.processing = false;
if (
r.type === DataEvent.PAGE_ERROR ||
r.type === DataEvent.API_REQUEST_LAME
) {
notify.alert(r.message, false);
} else {
if (r.type === DataEvent.PAGE_UPDATED) {
notify.alert(r.message, true);
} else {
notify.alert(r.message, true);
window.location = '/dashboard/page/edit/' + r.id;
}
}
})
.catch(err => {
self.processing = false;
notify.alert(err, false);
});
});
break;
case EditorEvent.EDITOR_DELETE:
if (this.postLayout === 'index') {
notify.alert('Index cannot be deleted', false);
return;
}
if (confirm("AYE! You know you're deleting this post, right?")) {
new PageActions()
.collectInfo(this.fm.getFileOrder())
.then(page => {
self.processing = true;
this.admin
.pageActions(TASK_PAGE_DELETE, page)
.then(() => {
self.processing = false;
window.location = '/dashboard/pages';
})
.catch(err => {
self.processing = false;
notify.alert(err, false);
});
})
.catch(() => {});
} else {
// Do nothing!
}
break;
case EditorEvent.EDITOR_UPLOAD_POST_IMAGE:
document.getElementById('post-image-upload').click();
break;
}
}
handleImageUpload(type, files) {
let self = this;
notify.alert('Uploading Image', null);
let upload = new FormData();
upload.enctype = 'multipart/form-data';
upload.append('upload_files[]', files[0], files[0].name);
this.mm
.filesUpload(files[0].type, upload)
.then(result => {
if (result.message == 'File Uploaded. Great!') {
self.editor.notify(
EditorEvent.EDITOR_UPLOAD_POST_IMAGE,
result.filePath
);
notify.alert('Image Added to Entry', true);
} else {
notify.alert('Uh oh. Image not added', false);
}
})
.catch(() => {
notify.alert('Uh oh. Image not added', false);
});
}
}

View file

@ -0,0 +1,30 @@
import PageEditor from "./PageEditor";
export default class PostIndex {
//--------------------------
// constructor
//--------------------------
constructor(page) {
this.currentPage = null;
this.choosePage(page);
this.start();
}
//--------------------------
// methods
//--------------------------
start() {}
choosePage(page) {
this.currentPage = "";
switch (page) {
case "edit":
case "add":
this.currentPage = new PageEditor();
break;
default:
//just chill
break;
}
}
//--------------------------
// event handlers
//--------------------------
}

View file

@ -0,0 +1,268 @@
import SettingsActions from '../actions/SettingsActions';
import Maintenance from './MaintenanceManager';
import FipamoAdminAPI, { TASK_SYNC_SETTNIGS } from '../../libraries/FipamoAdminAPI';
import * as DataEvent from '../../../src/com/events/DataEvent';
import Mailer from '../actions/Mailer';
import Notifications from '../ui/Notifications';
const notify = new Notifications();
export default class SettingsIndex {
//--------------------------
// constructor
//--------------------------
constructor() {
this.processing = false;
this.start();
this.admin = new FipamoAdminAPI(null);
this.mm = new Maintenance(null, null);
}
//--------------------------
// methods
//--------------------------
start() {
let self = this;
//handle save button
document.getElementById('save-toggle').addEventListener('click', () =>
new SettingsActions()
.getInfo()
.then(data => {
notify.alert('Saving Settings', null);
self.admin.sync(TASK_SYNC_SETTNIGS, data).then(r => {
if (r.type == DataEvent.SETTINGS_UPDATED) {
notify.alert(r.message, true);
} else {
notify.alert(r.message, true);
}
});
})
.catch(() => {
//console.log(err);
})
);
//handle set up image uploads
document.querySelector('.avatar').addEventListener('click', () => {
document.getElementById('avatar-upload').click();
});
document.querySelector('.background').addEventListener('click', () => {
document.getElementById('background-upload').click();
});
document.getElementById('avatar-upload').addEventListener(
'change',
e => {
self.handleImageUpload(e.target.id, e.target.files);
},
false
);
document.getElementById('background-upload').addEventListener(
'change',
e => {
self.handleImageUpload(e.target.id, e.target.files);
},
false
);
//handle api access toggle
var apiButton = document.getElementById('api-access-toggle');
var apiStatus = document.getElementById('api-status');
apiButton.addEventListener('click', e => {
e.stopPropagation();
e.preventDefault();
if (apiButton.getAttribute('data-enabled') == 'false') {
apiButton.setAttribute('data-enabled', 'true');
apiStatus.innerHTML = 'API ACCESS IS ENABLED';
} else {
apiButton.setAttribute('data-enabled', 'false');
apiStatus.innerHTML = 'API ACCESS IS DISABLED';
}
});
//handle dynamic page rendering
var dynamicRenderButton = document.getElementById('dynamic-render-toggle');
var dynamicRenderStatus = document.getElementById('dynamic-render-status');
dynamicRenderButton.addEventListener('click', e => {
e.stopPropagation();
e.preventDefault();
if (dynamicRenderButton.getAttribute('data-enabled') == 'false') {
dynamicRenderButton.setAttribute('data-enabled', 'true');
dynamicRenderStatus.innerHTML = 'DYNAMIC PAGE RENDERING';
} else {
dynamicRenderButton.setAttribute('data-enabled', 'false');
dynamicRenderStatus.innerHTML = 'STATIC PAGE RENDERING';
}
});
document
.getElementById('send-mail')
.addEventListener('click', e => this.handleMailer(e));
document
.getElementById('publish-pages')
.addEventListener('click', e => this.handlePublished(e));
//handle page render on save toggle
document
.getElementById('render-toggle')
.addEventListener('click', e => this.toggleRender(e));
//handle theme toggle
let themeBtns = document.querySelectorAll('.theme-select');
for (var i = 0, length = themeBtns.length; i < length; i++) {
themeBtns[i].addEventListener('click', e => this.handleThemes(e));
}
//handle mail options
let mailBtn = document.querySelectorAll('.mail-option');
for (i = 0, length = mailBtn.length; i < length; i++) {
mailBtn[i].addEventListener('click', e => this.handleMailOptions(e));
}
//handle backup from settings [disabled]
document
.getElementById('create-backup')
.addEventListener('click', e => this.handleBackup(e));
/*
document
.getElementById("reindex-pages")
.addEventListener("click", (e) => this.handleReindex(e));
*/
}
//--------------------------
// event handlers
//--------------------------
togglePrivacy(e) {
e.stopPropagation();
e.preventDefault();
if (e.target.getAttribute('data-private') == 'false') {
e.target.setAttribute('data-private', 'true');
e.target.innerHTML = 'SITE IS PUBLIC';
} else {
e.target.setAttribute('data-private', 'false');
e.target.innerHTML = 'SITE IS PRIVATE';
}
}
toggleRender(e) {
e.stopPropagation();
e.preventDefault();
let button = document.getElementById('render-toggle');
if (button.getAttribute('data-render') == 'false') {
button.setAttribute('data-render', 'true');
//e.target.innerHTML = 'RENDER PAGES ON SAVE';
} else {
button.setAttribute('data-render', 'false');
//e.target.innerHTML = "DON'T RENDER PAGES ON SAVE";
}
}
handleMailer() {
let mailer = new Mailer();
mailer.testMail();
//mailer.sendMail();
}
handleThemes(e) {
e.stopPropagation();
e.preventDefault();
let themes = document.querySelectorAll('.theme-select');
for (var i = 0, length = themes.length; i < length; i++) {
e.target.id == themes[i].id
? themes[i].setAttribute('data-enabled', 'true')
: themes[i].setAttribute('data-enabled', 'false');
}
}
handleMailOptions(e) {
e.preventDefault();
e.stopPropagation();
let smtp = document.getElementById('mail-smtp');
let mailgun = document.getElementById('mail-mg');
let mail = document.querySelectorAll('.mail-option');
for (var i = 0, length = mail.length; i < length; i++) {
if (e.target.id == mail[i].id) {
mail[i].setAttribute('data-enabled', 'true');
if (e.target.id == 'option-smtp') {
smtp.setAttribute('data-enabled', 'true');
mailgun.setAttribute('data-enabled', 'false');
} else if (e.target.id == 'option-none') {
smtp.setAttribute('data-enabled', 'false');
mailgun.setAttribute('data-enabled', 'false');
} else {
smtp.setAttribute('data-enabled', 'false');
mailgun.setAttribute('data-enabled', 'true');
}
} else {
mail[i].setAttribute('data-enabled', 'false');
}
}
}
handleImageUpload(type, files) {
notify.alert('Uploading Image... ', null);
let self = this;
notify.alert('Uploading Image', null);
let upload = new FormData();
upload.enctype = 'multipart/form-data';
upload.append('source', type);
upload.append('upload_files[]', files[0], files[0].name);
this.mm
.filesUpload(files[0].type, upload)
.then(r => {
if (type == 'avatar-upload') {
notify.alert(r.message, true);
document.querySelector('[role="avatar"]').style.background =
'url(' + r.filePath + ') no-repeat center center / cover';
} else {
notify.alert(r.message, true);
document.querySelector('[role="background"]').style.background =
'url(' + r.filePath + ') no-repeat center center / cover';
}
})
.catch(() => {
//console.log(err)
});
}
handlePublished(e) {
if (this.processing) return;
e.preventDefault();
e.stopPropagation();
let self = this;
let task = { task: 'PUBLISH_ALL' };
this.processing = true;
notify.alert('Publishing site...', null);
this.admin
.publish(task)
.then(r => {
self.processing = false;
notify.alert(r.message, true);
})
.catch(err => {
self.processing = false;
notify.alert(err, false);
});
}
handleBackup(e) {
e.preventDefault();
e.stopPropagation();
notify.alert('Creating backup', null);
this.mm
.backup()
.then(r => {
notify.alert(r.message, true);
})
.catch(err => {
notify.alert(err, false);
});
}
handleReindex(e) {
if (this.processing) return;
let self = this;
e.preventDefault();
e.stopPropagation();
let task = { task: 'cleanup pages indexes' };
this.processing = true;
notify.alert('Cleaning up page indexes', null);
this.admin
.handleReindex(task)
.then(r => {
self.processing = false;
notify.alert(r.message, true);
})
.catch(err => {
self.processing = false;
notify.alert(err, false);
});
}
}

View file

@ -0,0 +1,21 @@
export const MEMBER_STATUS = 'memberStatus';
export const LOGIN_STATUS = 'loginStatus';
export const SUPPORTER_FOUND = 'SUPPORTER FOUND';
export const SUPPORTER_LISTED = 'SUPPORTER LISTED';
export const SUPPORTER_NOT_FOUND = 'SUPPORTER NOT FOUND';
export const MEMBER_ADDED = 'MEMBER ADDED';
export const MEMBER_NOT_ADDED = 'MEMBER NOT ADDED';
export const MEMBER_LOGIN_GOOD = 'MEMBER LOGIN GOOD';
export const MEMBER_LOGIN_LAME = 'MEMBER LOGIN LAME';
export const MEMBER_EXISTS = 'USER ALREADY EXISTS';
export const MEMBER_LOGIN_MISSING = 'Missing credentials';
class AuthEvent {
//--------------------------
// methods
//--------------------------
//--------------------------
// event handlers
//--------------------------
}
export default new AuthEvent();

View file

@ -0,0 +1,51 @@
export const AUTH_STATUS = "getAuthStatus";
export const REQUEST_GOOD = "requestGood";
export const REQUEST_LAME = "requestLame";
export const API_REQUEST_GOOD = "apiUseAuthorized";
export const API_REQUEST_LAME = "apiUseNotAuthorized";
export const IMG_REQUEST_GOOD = "imgRequestGood";
export const IMG_REQUEST_LAME = "imgRequestLame";
export const SETTINGS_LOADED = "settingsLoaded";
export const POST_IMAGE_ADDED = "postImageAdded";
export const FEATURE_IMAGE_ADDED = "featureImageAdded";
export const PAGE_ERROR = "postError";
export const PAGE_ADDED = "postAdded";
export const PAGE_UPDATED = "postUpdated";
export const PAGE_DELETED = "postImageAdded";
export const PAGES_RENDERED = "pagesRendered";
export const PAGES_NOT_RENDERED = "pagesNotRendered";
export const TAG_PAGES_RENDERED = "tagPagesRendered";
export const TAG_PAGES_NOT_RENDERED = "tagPagesNotRendered";
export const SETTINGS_UPDATED = "settingsUpdated";
export const SETTINGS_NOT_UPDATED = "settingsNotUpdated";
export const MENU_ADD_ITEM = "menuAddItem";
export const MENU_DELETE_ITEM = "menuDeleteItem";
export const MENU_UPDATED = "menuUpdated";
export const AVATAR_UPLOADED = "avatarUploaded";
export const SITE_BACKGROUND_UPLOADED = "siteBackgroundUploaded";
export const UPLOAD_PROGRESS = "uploadProgress";
export const API_PAGE_WRITE = "writingItDown";
export const API_PAGE_CREATE = "writingNewEntry";
export const API_PAGE_DELETE = "erasingPage";
export const API_SETTINGS_WRITE = "savingSettings";
export const API_BACKUP_CREATE = "createBackup";
export const API_BACKUP_DOWNLOAD = "downloadBackup";
export const API_BACKUP_RESTORE = "downloadBackup";
export const API_IMAGES_UPLOAD = "uploadProfileImages";
export const API_RENDER_PAGES = "renderPages";
export const API_REINDEX_PAGES = "reindexPages";
export const API_INIT = "blogInit";
export const API_INIT_GOOD = "blogInitGood";
export const API_INIT_LAME = "blogInitLame";
export const API_GET_SECRET = "retrieveSecret";
export const API_RESET_PASS = "resetPassword";
export const SEND_MAIL = "sendMail";
class DataEvent {
//--------------------------
// methods
//--------------------------
//--------------------------
// event handlers
//--------------------------
}
export default new DataEvent();

View file

@ -0,0 +1,14 @@
export const EDITOR_DELETE = 'editorDelete';
export const EDITOR_UPLOAD_POST_IMAGE = 'editorUploadImage';
export const EDITOR_SAVE = 'editorSave';
export const EDITOR_UPDATE = 'editorUpdate';
class EditorEvent {
//--------------------------
// methods
//--------------------------
//--------------------------
// event handlers
//--------------------------
}
export default new EditorEvent();

View file

@ -0,0 +1,52 @@
class EventEmitter {
//--------------------------
// constructor
//--------------------------
constructor() {
this.listeners = new Map();
}
//--------------------------
// methods
//--------------------------
addListener(label, callback) {
this.listeners.has(label) || this.listeners.set(label, []);
this.listeners.get(label).push(callback);
}
removeListener(label, callback) {
var isFunction = function(obj) {
return typeof obj == 'function' || false;
};
var listeners = this.listeners.get(label),
index;
if (listeners && listeners.length) {
index = listeners.reduce((i, listener, index) => {
return isFunction(listener) && listener === callback ? (i = index) : i;
}, -1);
if (index > -1) {
listeners.splice(index, 1);
this.listeners.set(label, listeners);
return true;
}
}
return false;
}
emitEvent(label, ...args) {
var listeners = this.listeners.get(label);
if (listeners && listeners.length) {
listeners.forEach(listener => {
listener(...args);
});
return true;
}
return false;
}
//--------------------------
// event handlers
//--------------------------
}
export default EventEmitter;

View file

@ -0,0 +1,296 @@
import Sortable from '../vendor/sortable.js';
import anime from '../vendor/anime.es.js';
import DataUtils from '../utils/DataUtils.js';
import Notfications from './Notifications.js';
import Maintenance from '../controllers/MaintenanceManager.js';
const notify = new Notfications();
export default class FileManager {
//--------------------------
// constructor
//--------------------------
constructor(upload, input, imageList, fileList) {
this.mm = new Maintenance(null, null, document.getElementById('notify-progress'));
this.upload = upload;
this.input = input;
this.imageList = imageList;
this.fileList = fileList;
this.accetableFiles = [
'image/jpeg',
'image/gif',
'image/png',
'image/svg',
'audio/mpeg',
'video/mp4',
'application/pdf',
'text/plain',
'text/rtf'
];
this.files = [];
this.sortedFiles = [];
this.storage = [];
this.mediaSort = Sortable.create(this.imageList, {
animation: 150,
onUpdate: () => {
//notify.alert('REINDEXING MEDIA', null);
//this.updateFiles();
}
});
this.fileSort = Sortable.create(this.fileList, {
animation: 150,
onUpdate: () => {
//notify.alert('REINDEXING FILES', null);
//this.updateFiles();
}
});
this.start();
}
//--------------------------
// methods
//--------------------------
start() {
this.upload.addEventListener('dragover', e => this.handleFileActions(e), false);
this.upload.addEventListener('drop', e => this.handleFileActions(e), false);
this.input.addEventListener('change', e => this.handleFileActions(e), false);
var removeMedia = document.querySelectorAll('.media-remove');
for (var i = 0, length = removeMedia.length; i < length; i++) {
removeMedia[i].addEventListener(
'click',
e => this.removeFile(e, 'media'),
false
);
}
}
getFileOrder() {
let imgList = '';
let fileList = '';
for (var i = 0, length = this.imageList.childNodes.length; i < length; i++) {
let div = this.imageList.childNodes[i];
imgList = imgList + div.getAttribute('data-source') + ',';
}
for (var i = 0, length = this.fileList.childNodes.length; i < length; i++) {
let div = this.fileList.childNodes[i];
fileList = fileList + div.getAttribute('data-source') + ',';
}
let media = { images: imgList, files: fileList };
return media;
}
sortFiles(files) {
var self = this;
for (var i = 0, file; (file = files[i]); i++) {
var reader = new FileReader();
// Closure to capture the file information
reader.onload = (theFile => {
return function (f) {
//create remove button object
var remove = document.createElement('button');
var removeIcon = document.createElement('i');
removeIcon.classList.add('ti', 'ti-x');
remove.className = 'media-remove';
remove.appendChild(removeIcon);
//remove.setAttribute('id', mediaCount);
remove.addEventListener(
'click',
e => self.removeFile(e, 'media'),
false
);
//upload the file
let upload = new FormData();
upload.enctype = 'multipart/form-data';
upload.append('upload_files[]', theFile, theFile.name);
let item = null;
let progress = null;
// sort files
switch (theFile.type) {
case 'image/jpg':
case 'image/jpeg':
case 'image/gif':
case 'image/svg':
case 'image/png':
item = self.itemFactory('img-item');
progress = document.getElementById(
'pgs' + item.getAttribute('id')
);
self.mm
.filesUpload(theFile.type, upload, progress)
.then(result => {
item.setAttribute('data-source', result.filePath);
item.style.background =
'url(' +
f.target.result +
') no-repeat center center / cover';
anime({
targets: progress,
width: 0,
easing: 'easeInOutQuint',
duration: 1000,
complete: () => {
item.removeChild(progress);
item.appendChild(remove);
}
});
});
break;
case 'video/mp4':
item = self.itemFactory('video-item');
progress = document.getElementById(
'pgs' + item.getAttribute('id')
);
self.mm
.filesUpload(theFile.type, upload, progress)
.then(result => {
item.setAttribute('data-source', result.filePath);
let video = document.createElement('video');
let source = document.createElement('source');
source.src = f.target.result;
video.appendChild(source);
item.appendChild(video);
anime({
targets: progress,
width: 0,
easing: 'easeInOutQuint',
duration: 1000,
complete: () => {
item.removeChild(progress);
item.appendChild(remove);
}
});
});
break;
case 'audio/mpeg':
item = self.itemFactory('audio-item');
progress = document.getElementById(
'pgs' + item.getAttribute('id')
);
self.mm
.filesUpload(theFile.type, upload, progress)
.then(result => {
item.setAttribute('data-source', result.filePath);
let audio = document.createElement('audio');
audio.setAttribute('controls', true);
let source = document.createElement('source');
source.src = f.target.result;
audio.appendChild(source);
item.appendChild(audio);
anime({
targets: progress,
width: 0,
easing: 'easeInOutQuint',
duration: 1000,
complete: () => {
item.removeChild(progress);
item.appendChild(remove);
}
});
});
break;
case 'application/pdf':
case 'text/plain':
case 'text/rtf':
item = self.itemFactory('file-item');
progress = document.getElementById(
'pgs' + item.getAttribute('id')
);
self.mm
.filesUpload(theFile.type, upload, progress)
.then(result => {
item.setAttribute('data-source', result.filePath);
let link = document.createElement('a');
link.href = result.filePath;
link.innerHTML = result.fileName;
item.appendChild(link);
anime({
targets: progress,
width: 0,
easing: 'easeInOutQuint',
duration: 1000,
complete: () => {
item.removeChild(progress);
item.appendChild(remove);
}
});
});
break;
}
};
})(file);
// Read in the image file as a data URL.
reader.readAsDataURL(file);
}
}
itemFactory(type = null) {
//get counts for lists
var mediaCount = this.imageList.childNodes.length;
var fileCount = this.fileList.childNodes.length;
if (mediaCount < 0) mediaCount = 0;
if (fileCount < 0) fileCount = 0;
var item = document.createElement('div');
item.className = type;
var progress = document.createElement('div');
progress.className = 'item-progress';
item.appendChild(progress);
if (type == 'img-item' || type == 'video-item') {
this.imageList.appendChild(item);
progress.setAttribute('id', 'pgs' + mediaCount);
item.setAttribute('id', mediaCount);
} else {
this.fileList.appendChild(item);
progress.setAttribute('id', 'pgs' + fileCount);
item.setAttribute('id', fileCount);
}
return item;
}
//--------------------------
// event handlers
//--------------------------
removeFile(e) {
var item = e.target.parentNode.parentNode;
switch (item.className) {
case 'img-item':
case 'video-item':
this.imageList.removeChild(item);
break;
case 'audio-item':
case 'file-item':
this.fileList.removeChild(item);
break;
}
notify.alert('File Removed!', true);
}
handleFileActions(e) {
e.stopPropagation();
e.preventDefault();
let self = this;
let rawList = [];
let sortedList = [];
let notOnTheList = [];
switch (e.type) {
case 'dragover':
e.dataTransfer.dropEffect = 'copy'; // Explicitly show this is a copy.
break;
case 'change':
case 'drop':
e.type == 'drop'
? (rawList = e.dataTransfer.files)
: (rawList = e.target.files);
for (var i = 0, f; (f = rawList[i]); i++) {
// check witch files are cool to upload
if (this.accetableFiles.includes(f.type)) {
sortedList.push(f);
} else {
notOnTheList.push(f);
}
}
//send for sorting
self.sortFiles(sortedList);
break;
}
}
}

View file

@ -0,0 +1,28 @@
export default class Menu {
//--------------------------
// constructor
//--------------------------
constructor() {
this.mobile = false;
this.mobileMenu = document.querySelector('.mobile-menu');
document
.querySelector('.menu-toggle')
.addEventListener('click', e => this.handleMobile(e));
}
//--------------------------
// methods
//--------------------------
start() {}
//--------------------------
// event handlers
//--------------------------
handleMobile(e) {
if (this.mobile) {
this.mobile = false;
this.mobileMenu.style.display = 'none';
} else {
this.mobile = true;
this.mobileMenu.style.display = 'inline';
}
}
}

View file

@ -0,0 +1,109 @@
import anime from '../vendor/anime.es.js';
const notifcation = document.querySelector('.notify-message');
const notify = document.getElementById('notify-message');
const responseText = document.querySelector('.response-text');
const notifyText = document.querySelector('.notify-text');
const notifyIcons = document.querySelector('.notify-icons');
//const notifyProgress = document.getElementById('notify-progress');
const iconGood = document.querySelector('.notify-good');
const iconNotGood = document.querySelector('.notify-notgood');
const iconWorking = document.querySelector('.notify-working');
export default class Notfications {
//--------------------------
// constructor
//--------------------------
constructor() {}
//--------------------------
// methods
//--------------------------
alert(text, status) {
iconWorking.style.display = 'none';
iconGood.style.display = 'none';
iconNotGood.style.display = 'none';
var color = '';
responseText.innerHTML = text;
if (status !== null) {
if (status) {
color = '#32cd32';
iconWorking.style.display = 'none';
iconGood.style.display = 'block';
} else {
color = '#F64747';
iconWorking.style.display = 'none';
iconNotGood.style.display = 'block';
}
} else {
color = '#200317';
iconWorking.style.display = 'block';
}
new anime({
targets: document.querySelector('.top-nav'),
rotateX: '180deg',
easing: 'easeOutQuint'
});
new anime({
targets: document.querySelector('.notify'),
rotateX: '10deg',
easing: 'easeOutQuint',
complete: () => {
new anime({
targets: notifyIcons,
width: 39,
opacity: 1,
easing: 'easeInQuint',
duration: 300
});
new anime({
targets: notifyText,
backgroundColor: color,
opacity: 1,
easing: 'easeInOutQuad',
duration: 400,
complete: () => {
setTimeout(() => {
if (status !== null) {
anime({
targets: notifyText,
backgroundColor: color,
opacity: 0,
easing: 'easeInOutQuad',
duration: 400
});
anime({
targets: notifyIcons,
width: 0,
opacity: 0,
easing: 'easeOutQuint',
duration: 350
});
new anime({
targets: document.querySelector('.top-nav'),
rotateX: '0deg',
easing: 'easeOutQuint'
});
new anime({
targets: document.querySelector('.notify'),
rotateX: '180deg',
easing: 'easeOutQuint'
});
}
}, 2500);
}
});
}
});
}
//--------------------------
// event handlers
//--------------------------
}

View file

@ -0,0 +1,206 @@
import * as DataEvent from '../events/DataEvent.js';
import EventEmitter from '../events/EventEmitter.js';
import * as EditorEvent from '../events/EditorEvent.js';
import Prism from '../vendor/prism.js';
//import MarkdownLang from '../vendor/lang/prism-markdown.js';
//Prism.language.md = '../vendor/lang/prism-markdown.js';
//Prism.languauges['markdown'];
class TextEditor extends EventEmitter {
/**
* Text Editor UI Component
* @constructor
* @param {object} textEditor - Text area that will edit text
* @param {number} scrollLimit - YPos where editor position will become fixed
*/
//--------------------------
// constructor
//--------------------------
constructor(textEditor, scrollLimit) {
super();
document
.querySelector('[role="text-editor-control"]')
.addEventListener('scroll', e => {
console.log('HERE');
});
document.body.addEventListener('scroll', e => {
var fixLimit = scrollLimit;
//console.log('POSITION', document.body.scrollTop + ' : ' + fixLimit);
if (document.body.scrollTop + 5 >= fixLimit) {
document
.querySelector('[role="text-editor-control"]')
.classList.add('control-freeze');
} else {
document
.querySelector('[role="text-editor-control"]')
.classList.remove('control-freeze');
}
});
document.getElementById('edit').addEventListener('input', e => {
let result_element = document.querySelector('#highlight-content');
this.textEditor = textEditor;
// Update code
let text = e.target.value;
result_element.innerHTML = text
.replace(new RegExp('&', 'g'), '&amp;')
.replace(new RegExp('<', 'g'), '&lt;');
let editorHeight = document.getElementById('highlight').offsetHeight;
document.querySelector('[role="edit-post-wrapper"]').style.height =
editorHeight + 'px';
e.target.style.height = editorHeight + 30 + 'px'; //TODO: yeah, it's ugly but it works for now, fix soon
// Syntax Highlight
Prism.highlightElement(result_element);
});
document.getElementById('edit').addEventListener('scroll', e => {
/* Scroll result to scroll coords of event - sync with textarea */
let result_element = document.querySelector('#highlight');
// Get and set x and y
result_element.scrollTop = e.scrollTop;
result_element.scrollLeft = e.scrollLeft;
});
document.getElementById('edit').dispatchEvent(new Event('input'));
this.setInputs();
//freeze editor formatting so it doesn't scroll off screen
}
//--------------------------
// methods
//--------------------------
setInputs() {
var editorButtons = document.querySelectorAll('.editor-button');
for (var i = 0, length = editorButtons.length; i < length; i++) {
editorButtons[i].addEventListener(
'click',
e => this.handleEditorOption(e),
false
);
}
}
notify(type, data) {
switch (type) {
case DataEvent.PAGE_UPDATED:
document.getElementById('submit-update').classList.add('icon-hide');
document.getElementById('submit-good').classList.remove('icon-hide');
document.getElementById('edit-update').classList.remove('submit-start');
document.getElementById('edit-update').classList.add('submit-cool');
setTimeout(() => {
document
.getElementById('submit-update')
.classList.remove('icon-hide');
document.getElementById('submit-good').classList.add('icon-hide');
document.getElementById('edit-update').classList.add('submit-start');
document
.getElementById('edit-update')
.classList.remove('submit-cool');
}, 2000);
break;
case DataEvent.PAGE_ADDED:
// do nothing
break;
case EditorEvent.EDITOR_UPLOAD_POST_IMAGE: {
let len = this.textEditor.value.length;
let start = this.textEditor.selectionStart;
let end = this.textEditor.selectionEnd;
let insert = '![image alt text](' + data + ')';
this.textEditor.value =
this.textEditor.value.substring(0, start) +
insert +
this.textEditor.value.substring(end, len);
document.getElementById('edit').dispatchEvent(new Event('input'));
break;
}
}
}
//--------------------------
// event handlers
//--------------------------
handleEditorOption(e) {
e.preventDefault();
let len = this.textEditor.value.length;
let start = this.textEditor.selectionStart;
let end = this.textEditor.selectionEnd;
let selectedText = this.textEditor.value.substring(start, end);
let insert = '';
switch (e.target.id) {
case 'edit-bold':
insert = '**' + selectedText + '**';
this.textEditor.value =
this.textEditor.value.substring(0, start) +
insert +
this.textEditor.value.substring(end, len);
break;
case 'edit-italic':
insert = '*' + selectedText + '*';
//console.log(this.textEditor);
this.textEditor.value =
this.textEditor.value.substring(0, start) +
insert +
this.textEditor.value.substring(end, len);
break;
case 'edit-strikethrough':
insert = '~~' + selectedText + '~~';
this.textEditor.value =
this.textEditor.value.substring(0, start) +
insert +
this.textEditor.value.substring(end, len);
break;
case 'edit-header1':
insert = '# ' + selectedText + '\n';
this.textEditor.value =
this.textEditor.value.substring(0, start) +
insert +
this.textEditor.value.substring(end, len);
break;
case 'edit-header2':
insert = '## ' + selectedText + '\n';
this.textEditor.value =
this.textEditor.value.substring(0, start) +
insert +
this.textEditor.value.substring(end, len);
break;
case 'edit-header3':
insert = '### ' + selectedText + '\n';
this.textEditor.value =
this.textEditor.value.substring(0, start) +
insert +
this.textEditor.value.substring(end, len);
break;
case 'edit-link':
{
let url = prompt("Let's get that url, boss");
let link = url.toLowerCase();
insert = '[' + selectedText + '](' + link + ')';
this.textEditor.value =
this.textEditor.value.substring(0, start) +
insert +
this.textEditor.value.substring(end, len);
}
break;
case 'edit-image':
//this.caretPos = position(this.textEditor).pos;
this.emitEvent(EditorEvent.EDITOR_UPLOAD_POST_IMAGE);
break;
case 'submit-save':
case 'edit-save':
this.emitEvent(EditorEvent.EDITOR_SAVE);
break;
case 'submit-update':
case 'edit-update':
this.emitEvent(EditorEvent.EDITOR_UPDATE);
break;
case 'edit-delete':
this.emitEvent(EditorEvent.EDITOR_DELETE);
break;
default:
//do stuff
break;
}
document.getElementById('edit').dispatchEvent(new Event('input'));
}
}
export default TextEditor;

View file

@ -0,0 +1,95 @@
export default class DataUtils {
//--------------------------
// constructor
//--------------------------
constructor() {}
//--------------------------
// methods
//--------------------------
imgLoad(url) {
'use strict';
// Create new promise with the Promise() constructor;
// This has as its argument a function with two parameters, resolve and reject
return new Promise(function (resolve, reject) {
// Standard XHR to load an image
var request = new XMLHttpRequest();
request.open('GET', url);
request.responseType = 'blob';
// When the request loads, check whether it was successful
request.onload = function () {
if (request.status === 200) {
// If successful, resolve the promise by passing back the request response
resolve(request.response);
} else {
// If it fails, reject the promise with a error message
reject(
new Error(
"Image didn't load successfully; error code: " +
request.status +
' ' +
request.statusText
)
);
}
};
request.onerror = function () {
// Also deal with the case when the entire request fails to begin with
// This is probably a network error, so reject the promise with an appropriate message
reject(new Error('There was a network error.'));
};
// Send the request
request.send();
});
}
loadImage(src) {
'use strict';
let self = this;
return new Promise(function (resolve, reject) {
// Get a reference to the body element, and create a new image object
var myImage = new Image();
myImage.crossOrigin = ''; // or "anonymous"
// Call the function with the URL we want to load, but then chain the
// promise then() method on to the end of it. This contains two callbacks
self.imgLoad(src).then(
function (response) {
// The first runs when the promise resolves, with the request.reponse specified within the resolve() method.
var imageURL = window.URL.createObjectURL(response);
resolve(imageURL);
//$('background-content').setStyle('background-image', 'url('+imageURL+')') //myImage.src = imageURL;
//console.log(imageURL);
//body.appendChild(myImage);
// The second runs when the promise is rejected, and logs the Error specified with the reject() method.
},
function (Error) {
reject(Error);
}
);
});
}
/**
* Create a function to convert the serialize and convert the form data to JSON
* @param : $('#form_example');
* @return a JSON Stringify
*/
formDataToJSON(form) {
let object = {};
let formData = new FormData(form);
formData.forEach((value, key) => {
if (!object.hasOwnProperty(key)) {
object[key] = value;
return;
}
if (!Array.isArray(object[key])) {
object[key] = [object[key]];
}
object[key].push(value);
});
//let json = JSON.stringify(object);
return object;
}
//--------------------------
// event handlers
//--------------------------
}

View file

@ -0,0 +1,68 @@
class StringUtils {
//--------------------------
// constructor
//--------------------------
constructor() {}
//--------------------------
// methods
//--------------------------
cleanString(string) {
var clean = string
.replace(/(^\-+|[^a-zA-Z0-9\/_| -]+|\-+$)/g, '')
.toLowerCase()
.replace(/[\/_| -]+/g, '-');
return clean;
}
decodeHTML(string, quote_style) {
var optTemp = 0,
i = 0,
noquotes = false;
if (typeof quote_style === 'undefined') {
quote_style = 2;
}
string = string
.toString()
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>');
var OPTS = {
ENT_NOQUOTES: 0,
ENT_HTML_QUOTE_SINGLE: 1,
ENT_HTML_QUOTE_DOUBLE: 2,
ENT_COMPAT: 2,
ENT_QUOTES: 3,
ENT_IGNORE: 4
};
if (quote_style === 0) {
noquotes = true;
}
if (typeof quote_style !== 'number') {
// Allow for a single string or an array of string flags
quote_style = [].concat(quote_style);
for (i = 0; i < quote_style.length; i++) {
// Resolve string input to bitwise e.g. 'PATHINFO_EXTENSION' becomes 4
if (OPTS[quote_style[i]] === 0) {
noquotes = true;
} else if (OPTS[quote_style[i]]) {
optTemp = optTemp | OPTS[quote_style[i]];
}
}
quote_style = optTemp;
}
if (quote_style & OPTS.ENT_HTML_QUOTE_SINGLE) {
string = string.replace(/&#0*39;/g, "'"); // PHP doesn't currently escape if more than one 0, but it should
// string = string.replace(/&apos;|&#x0*27;/g, "'"); // This would also be useful here, but not a part of PHP
}
if (!noquotes) {
string = string.replace(/&quot;/g, '"');
}
// Put this in last place to avoid escape being double-decoded
string = string.replace(/&amp;/g, '&');
return string;
}
//--------------------------
// event handlers
//--------------------------
}
export default StringUtils;

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,227 @@
Prism.languages.markdown = Prism.languages.extend('markup', {});
Prism.languages.insertBefore('markdown', 'prolog', {
'blockquote': {
// > ...
pattern: /^>(?:[\t ]*>)*/m,
alias: 'punctuation'
},
'code': [
{
// Prefixed by 4 spaces or 1 tab
pattern: /^(?: {4}|\t).+/m,
alias: 'keyword'
},
{
// `code`
// ``code``
pattern: /``.+?``|`[^`\n]+`/,
alias: 'keyword'
},
{
// ```optional language
// code block
// ```
pattern: /^```[\s\S]*?^```$/m,
greedy: true,
inside: {
'code-block': {
pattern: /^(```.*(?:\r?\n|\r))[\s\S]+?(?=(?:\r?\n|\r)^```$)/m,
lookbehind: true
},
'code-language': {
pattern: /^(```).+/,
lookbehind: true
},
'punctuation': /```/
}
}
],
'title': [
{
// title 1
// =======
// title 2
// -------
pattern: /\S.*(?:\r?\n|\r)(?:==+|--+)/,
alias: 'important',
inside: {
punctuation: /==+$|--+$/
}
},
{
// # title 1
// ###### title 6
pattern: /(^\s*)#+.+/m,
lookbehind: true,
alias: 'important',
inside: {
punctuation: /^#+|#+$/
}
}
],
'hr': {
// ***
// ---
// * * *
// -----------
pattern: /(^\s*)([*-])(?:[\t ]*\2){2,}(?=\s*$)/m,
lookbehind: true,
alias: 'punctuation'
},
'list': {
// * item
// + item
// - item
// 1. item
pattern: /(^\s*)(?:[*+-]|\d+\.)(?=[\t ].)/m,
lookbehind: true,
alias: 'punctuation'
},
'url-reference': {
// [id]: http://example.com "Optional title"
// [id]: http://example.com 'Optional title'
// [id]: http://example.com (Optional title)
// [id]: <http://example.com> "Optional title"
pattern: /!?\[[^\]]+\]:[\t ]+(?:\S+|<(?:\\.|[^>\\])+>)(?:[\t ]+(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\((?:\\.|[^)\\])*\)))?/,
inside: {
'variable': {
pattern: /^(!?\[)[^\]]+/,
lookbehind: true
},
'string': /(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\((?:\\.|[^)\\])*\))$/,
'punctuation': /^[\[\]!:]|[<>]/
},
alias: 'url'
},
'bold': {
// **strong**
// __strong__
// Allow only one line break
pattern: /(^|[^\\])(\*\*|__)(?:(?:\r?\n|\r)(?!\r?\n|\r)|.)+?\2/,
lookbehind: true,
greedy: true,
inside: {
'punctuation': /^\*\*|^__|\*\*$|__$/
}
},
'italic': {
// *em*
// _em_
// Allow only one line break
pattern: /(^|[^\\])([*_])(?:(?:\r?\n|\r)(?!\r?\n|\r)|.)+?\2/,
lookbehind: true,
greedy: true,
inside: {
'punctuation': /^[*_]|[*_]$/
}
},
'strike': {
// ~~strike through~~
// ~strike~
// Allow only one line break
pattern: /(^|[^\\])(~~?)(?:(?:\r?\n|\r)(?!\r?\n|\r)|.)+?\2/,
lookbehind: true,
greedy: true,
inside: {
'punctuation': /^~~?|~~?$/
}
},
'url': {
// [example](http://example.com "Optional title")
// [example] [id]
pattern: /!?\[[^\]]+\](?:\([^\s)]+(?:[\t ]+"(?:\\.|[^"\\])*")?\)| ?\[[^\]\n]*\])/,
inside: {
'variable': {
pattern: /(!?\[)[^\]]+(?=\]$)/,
lookbehind: true
},
'string': {
pattern: /"(?:\\.|[^"\\])*"(?=\)$)/
}
}
}
});
['bold', 'italic', 'strike'].forEach(function (token) {
['url', 'bold', 'italic', 'strike'].forEach(function (inside) {
if (token !== inside) {
Prism.languages.markdown[token].inside[inside] = Prism.languages.markdown[inside];
}
});
});
Prism.hooks.add('after-tokenize', function (env) {
if (env.language !== 'markdown' && env.language !== 'md') {
return;
}
function walkTokens(tokens) {
if (!tokens || typeof tokens === 'string') {
return;
}
for (var i = 0, l = tokens.length; i < l; i++) {
var token = tokens[i];
if (token.type !== 'code') {
walkTokens(token.content);
continue;
}
var codeLang = token.content[1];
var codeBlock = token.content[3];
if (codeLang && codeBlock &&
codeLang.type === 'code-language' && codeBlock.type === 'code-block' &&
typeof codeLang.content === 'string') {
// this might be a language that Prism does not support
var alias = 'language-' + codeLang.content.trim().split(/\s+/)[0].toLowerCase();
// add alias
if (!codeBlock.alias) {
codeBlock.alias = [alias];
} else if (typeof codeBlock.alias === 'string') {
codeBlock.alias = [codeBlock.alias, alias];
} else {
codeBlock.alias.push(alias);
}
}
}
}
walkTokens(env.tokens);
});
Prism.hooks.add('wrap', function (env) {
if (env.type !== 'code-block') {
return;
}
var codeLang = '';
for (var i = 0, l = env.classes.length; i < l; i++) {
var cls = env.classes[i];
var match = /language-(.+)/.exec(cls);
if (match) {
codeLang = match[1];
break;
}
}
var grammar = Prism.languages[codeLang];
if (!grammar) {
return;
}
// reverse Prism.util.encode
var code = env.content.replace(/&lt;/g, '<').replace(/&amp;/g, '&');
env.content = Prism.highlight(code, grammar, codeLang);
});
Prism.languages.md = Prism.languages.markdown;

View file

@ -0,0 +1,100 @@
Prism.languages.markup = {
'comment': /<!--[\s\S]*?-->/,
'prolog': /<\?[\s\S]+?\?>/,
'doctype': /<!DOCTYPE[\s\S]+?>/i,
'cdata': /<!\[CDATA\[[\s\S]*?]]>/i,
'tag': {
pattern: /<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/i,
greedy: true,
inside: {
'tag': {
pattern: /^<\/?[^\s>\/]+/i,
inside: {
'punctuation': /^<\/?/,
'namespace': /^[^\s>\/:]+:/
}
},
'attr-value': {
pattern: /=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/i,
inside: {
'punctuation': [
/^=/,
{
pattern: /^(\s*)["']|["']$/,
lookbehind: true
}
]
}
},
'punctuation': /\/?>/,
'attr-name': {
pattern: /[^\s>\/]+/,
inside: {
'namespace': /^[^\s>\/:]+:/
}
}
}
},
'entity': /&#?[\da-z]{1,8};/i
};
Prism.languages.markup['tag'].inside['attr-value'].inside['entity'] =
Prism.languages.markup['entity'];
// Plugin to make entity title show the real entity, idea by Roman Komarov
Prism.hooks.add('wrap', function(env) {
if (env.type === 'entity') {
env.attributes['title'] = env.content.replace(/&amp;/, '&');
}
});
Object.defineProperty(Prism.languages.markup.tag, 'addInlined', {
/**
* Adds an inlined language to markup.
*
* An example of an inlined language is CSS with `<style>` tags.
*
* @param {string} tagName The name of the tag that contains the inlined language. This name will be treated as
* case insensitive.
* @param {string} lang The language key.
* @example
* addInlined('style', 'css');
*/
value: function addInlined(tagName, lang) {
var includedCdataInside = {};
includedCdataInside['language-' + lang] = {
pattern: /(^<!\[CDATA\[)[\s\S]+?(?=\]\]>$)/i,
lookbehind: true,
inside: Prism.languages[lang]
};
includedCdataInside['cdata'] = /^<!\[CDATA\[|\]\]>$/i;
var inside = {
'included-cdata': {
pattern: /<!\[CDATA\[[\s\S]*?\]\]>/i,
inside: includedCdataInside
}
};
inside['language-' + lang] = {
pattern: /[\s\S]+/,
inside: Prism.languages[lang]
};
var def = {};
def[tagName] = {
pattern: RegExp(/(<__[\s\S]*?>)(?:<!\[CDATA\[[\s\S]*?\]\]>\s*|[\s\S])*?(?=<\/__>)/.source.replace(/__/g, tagName), 'i'),
lookbehind: true,
greedy: true,
inside: inside
};
Prism.languages.insertBefore('markup', 'cdata', def);
}
});
Prism.languages.xml = Prism.languages.extend('markup', {});
Prism.languages.html = Prism.languages.markup;
Prism.languages.mathml = Prism.languages.markup;
Prism.languages.svg = Prism.languages.markup;

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,770 @@
/* eslint-disable */
/* **********************************************
Begin prism-core.js
********************************************** */
let _self = {};
/**
* Prism: Lightweight, robust, elegant syntax highlighting
* MIT license http://www.opensource.org/licenses/mit-license.php/
* @author Lea Verou http://lea.verou.me
*/
let Prism = (function () {
// Private helper vars
let lang = /\blang(?:uage)?-([\w-]+)\b/i;
let uniqueId = 0;
var _ = _self.Prism = {
manual: _self.Prism && _self.Prism.manual,
disableWorkerMessageHandler: _self.Prism && _self.Prism.disableWorkerMessageHandler,
util: {
encode (tokens) {
if (tokens instanceof Token) {
return new Token(tokens.type, _.util.encode(tokens.content), tokens.alias);
} else if (_.util.type(tokens) === 'Array') {
return tokens.map(_.util.encode);
} else {
return tokens.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/\u00a0/g, ' ');
}
},
type (o) {
return Object.prototype.toString.call(o).match(/\[object (\w+)\]/)[1];
},
objId (obj) {
if (!obj['__id']) {
Object.defineProperty(obj, '__id', { value: ++uniqueId });
}
return obj['__id'];
},
// Deep clone a language definition (e.g. to extend it)
clone (o, visited) {
var type = _.util.type(o);
visited = visited || {};
switch (type) {
case 'Object':
if (visited[_.util.objId(o)]) {
return visited[_.util.objId(o)];
}
var clone = {};
visited[_.util.objId(o)] = clone;
for (var key in o) {
if (o.hasOwnProperty(key)) {
clone[key] = _.util.clone(o[key], visited);
}
}
return clone;
case 'Array':
if (visited[_.util.objId(o)]) {
return visited[_.util.objId(o)];
}
var clone = [];
visited[_.util.objId(o)] = clone;
o.forEach(function (v, i) {
clone[i] = _.util.clone(v, visited);
});
return clone;
}
return o;
},
},
languages: {
extend (id, redef) {
var lang = _.util.clone(_.languages[id]);
for (var key in redef) {
lang[key] = redef[key];
}
return lang;
},
/**
* Insert a token before another token in a language literal
* As this needs to recreate the object (we cannot actually insert before keys in object literals),
* we cannot just provide an object, we need anobject and a key.
* @param inside The key (or language id) of the parent
* @param before The key to insert before. If not provided, the function appends instead.
* @param insert Object with the key/value pairs to insert
* @param root The object that contains `inside`. If equal to Prism.languages, it can be omitted.
*/
insertBefore (inside, before, insert, root) {
root = root || _.languages;
var grammar = root[inside];
if (arguments.length == 2) {
insert = arguments[1];
for (var newToken in insert) {
if (insert.hasOwnProperty(newToken)) {
grammar[newToken] = insert[newToken];
}
}
return grammar;
}
var ret = {};
for (var token in grammar) {
if (grammar.hasOwnProperty(token)) {
if (token == before) {
for (var newToken in insert) {
if (insert.hasOwnProperty(newToken)) {
ret[newToken] = insert[newToken];
}
}
}
ret[token] = grammar[token];
}
}
// Update references in other language definitions
_.languages.DFS(_.languages, function(key, value) {
if (value === root[inside] && key != inside) {
this[key] = ret;
}
});
return root[inside] = ret;
},
// Traverse a language definition with Depth First Search
DFS(o, callback, type, visited) {
visited = visited || {};
for (var i in o) {
if (o.hasOwnProperty(i)) {
callback.call(o, i, o[i], type || i);
if (_.util.type(o[i]) === 'Object' && !visited[_.util.objId(o[i])]) {
visited[_.util.objId(o[i])] = true;
_.languages.DFS(o[i], callback, null, visited);
}
else if (_.util.type(o[i]) === 'Array' && !visited[_.util.objId(o[i])]) {
visited[_.util.objId(o[i])] = true;
_.languages.DFS(o[i], callback, i, visited);
}
}
}
},
},
plugins: {},
highlightAll(async, callback) {
_.highlightAllUnder(document, async, callback);
},
highlightAllUnder(container, async, callback) {
var env = {
callback: callback,
selector: 'code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code'
};
_.hooks.run("before-highlightall", env);
var elements = env.elements || container.querySelectorAll(env.selector);
for (var i=0, element; element = elements[i++];) {
_.highlightElement(element, async === true, env.callback);
}
},
highlightElement(element, async, callback) {
// Find language
var language, grammar, parent = element;
while (parent && !lang.test(parent.className)) {
parent = parent.parentNode;
}
if (parent) {
language = (parent.className.match(lang) || [,''])[1].toLowerCase();
grammar = _.languages[language];
}
// Set language on the element, if not present
element.className = element.className.replace(lang, '').replace(/\s+/g, ' ') + ' language-' + language;
if (element.parentNode) {
// Set language on the parent, for styling
parent = element.parentNode;
if (/pre/i.test(parent.nodeName)) {
parent.className = parent.className.replace(lang, '').replace(/\s+/g, ' ') + ' language-' + language;
}
}
var code = element.textContent;
var env = {
element: element,
language: language,
grammar: grammar,
code: code
};
_.hooks.run('before-sanity-check', env);
if (!env.code || !env.grammar) {
if (env.code) {
_.hooks.run('before-highlight', env);
env.element.textContent = env.code;
_.hooks.run('after-highlight', env);
}
_.hooks.run('complete', env);
return;
}
_.hooks.run('before-highlight', env);
if (async && _self.Worker) {
var worker = new Worker(_.filename);
worker.onmessage = function(evt) {
env.highlightedCode = evt.data;
_.hooks.run('before-insert', env);
env.element.innerHTML = env.highlightedCode;
callback && callback.call(env.element);
_.hooks.run('after-highlight', env);
_.hooks.run('complete', env);
};
worker.postMessage(JSON.stringify({
language: env.language,
code: env.code,
immediateClose: true
}));
}
else {
env.highlightedCode = _.highlight(env.code, env.grammar, env.language);
_.hooks.run('before-insert', env);
env.element.innerHTML = env.highlightedCode;
callback && callback.call(element);
_.hooks.run('after-highlight', env);
_.hooks.run('complete', env);
}
},
highlight (text, grammar, language) {
var env = {
code: text,
grammar: grammar,
language: language
};
_.hooks.run('before-tokenize', env);
env.tokens = _.tokenize(env.code, env.grammar);
_.hooks.run('after-tokenize', env);
return Token.stringify(_.util.encode(env.tokens), env.language);
},
matchGrammar (text, strarr, grammar, index, startPos, oneshot, target) {
var Token = _.Token;
for (var token in grammar) {
if(!grammar.hasOwnProperty(token) || !grammar[token]) {
continue;
}
if (token == target) {
return;
}
var patterns = grammar[token];
patterns = (_.util.type(patterns) === "Array") ? patterns : [patterns];
for (var j = 0; j < patterns.length; ++j) {
var pattern = patterns[j],
inside = pattern.inside,
lookbehind = !!pattern.lookbehind,
greedy = !!pattern.greedy,
lookbehindLength = 0,
alias = pattern.alias;
if (greedy && !pattern.pattern.global) {
// Without the global flag, lastIndex won't work
var flags = pattern.pattern.toString().match(/[imuy]*$/)[0];
pattern.pattern = RegExp(pattern.pattern.source, flags + "g");
}
pattern = pattern.pattern || pattern;
// Dont cache length as it changes during the loop
for (var i = index, pos = startPos; i < strarr.length; pos += strarr[i].length, ++i) {
var str = strarr[i];
if (strarr.length > text.length) {
// Something went terribly wrong, ABORT, ABORT!
return;
}
if (str instanceof Token) {
continue;
}
if (greedy && i != strarr.length - 1) {
pattern.lastIndex = pos;
var match = pattern.exec(text);
if (!match) {
break;
}
var from = match.index + (lookbehind ? match[1].length : 0),
to = match.index + match[0].length,
k = i,
p = pos;
for (var len = strarr.length; k < len && (p < to || (!strarr[k].type && !strarr[k - 1].greedy)); ++k) {
p += strarr[k].length;
// Move the index i to the element in strarr that is closest to from
if (from >= p) {
++i;
pos = p;
}
}
// If strarr[i] is a Token, then the match starts inside another Token, which is invalid
if (strarr[i] instanceof Token) {
continue;
}
// Number of tokens to delete and replace with the new match
delNum = k - i;
str = text.slice(pos, p);
match.index -= pos;
} else {
pattern.lastIndex = 0;
var match = pattern.exec(str),
delNum = 1;
}
if (!match) {
if (oneshot) {
break;
}
continue;
}
if(lookbehind) {
lookbehindLength = match[1] ? match[1].length : 0;
}
var from = match.index + lookbehindLength,
match = match[0].slice(lookbehindLength),
to = from + match.length,
before = str.slice(0, from),
after = str.slice(to);
var args = [i, delNum];
if (before) {
++i;
pos += before.length;
args.push(before);
}
var wrapped = new Token(token, inside? _.tokenize(match, inside) : match, alias, match, greedy);
args.push(wrapped);
if (after) {
args.push(after);
}
Array.prototype.splice.apply(strarr, args);
if (delNum != 1)
_.matchGrammar(text, strarr, grammar, i, pos, true, token);
if (oneshot)
break;
}
}
}
},
tokenize(text, grammar, language) {
var strarr = [text];
var rest = grammar.rest;
if (rest) {
for (var token in rest) {
grammar[token] = rest[token];
}
delete grammar.rest;
}
_.matchGrammar(text, strarr, grammar, 0, 0, false);
return strarr;
},
hooks: {
all: {},
add (name, callback) {
var hooks = _.hooks.all;
hooks[name] = hooks[name] || [];
hooks[name].push(callback);
},
run (name, env) {
var callbacks = _.hooks.all[name];
if (!callbacks || !callbacks.length) {
return;
}
for (var i=0, callback; callback = callbacks[i++];) {
callback(env);
}
},
},
};
var Token = _.Token = function (type, content, alias, matchedStr, greedy) {
this.type = type;
this.content = content;
this.alias = alias;
// Copy of the full string this token was created from
this.length = (matchedStr || '').length | 0;
this.greedy = !!greedy;
};
Token.stringify = function (o, language, parent) {
if (typeof o === 'string') {
return o;
}
if (_.util.type(o) === 'Array') {
return o.map((element) => {
return Token.stringify(element, language, o);
}).join('');
}
let env = {
type: o.type,
content: Token.stringify(o.content, language, parent),
tag: 'span',
classes: ['token', o.type],
attributes: {},
language,
parent,
};
if (o.alias) {
let aliases = _.util.type(o.alias) === 'Array' ? o.alias : [o.alias];
Array.prototype.push.apply(env.classes, aliases);
}
_.hooks.run('wrap', env);
let attributes = Object.keys(env.attributes).map((name) => {
return name + '="' + (env.attributes[name] || '').replace(/"/g, '&quot;') + '"';
}).join(' ');
return `<${ env.tag } class="${ env.classes.join(' ') }"${ attributes ? ' ' + attributes : '' }>${ env.content }</${ env.tag }>`;
};
if (!_self.document) {
if (!_self.addEventListener) {
// in Node.js
return _self.Prism;
}
if (!_.disableWorkerMessageHandler) {
// In worker
_self.addEventListener('message', (evt) => {
var message = JSON.parse(evt.data),
lang = message.language,
code = message.code,
immediateClose = message.immediateClose;
_self.postMessage(_.highlight(code, _.languages[lang], lang));
if (immediateClose) {
_self.close();
}
}, false);
}
return _self.Prism;
}
// Get current script and highlight
// let script = document.currentScript || [].slice.call(document.getElementsByTagName('script')).pop();
// if (script) {
// _.filename = script.src;
// if (!_.manual && !script.hasAttribute('data-manual')) {
// if (document.readyState !== 'loading') {
// if (window.requestAnimationFrame) {
// window.requestAnimationFrame(_.highlightAll);
// } else {
// window.setTimeout(_.highlightAll, 16);
// }
// } else {
// document.addEventListener('DOMContentLoaded', _.highlightAll);
// }
// }
// }
return _self.Prism;
}());
if (typeof module !== 'undefined' && module.exports) {
module.exports = Prism;
}
// hack for components to work correctly in node.js
if (typeof global !== 'undefined') {
global.Prism = Prism;
}
/* **********************************************
Begin prism-markup.js
********************************************** */
Prism.languages.markup = {
comment: /<!--[\s\S]*?-->/,
prolog: /<\?[\s\S]+?\?>/,
doctype: /<!DOCTYPE[\s\S]+?>/i,
cdata: /<!\[CDATA\[[\s\S]*?]]>/i,
tag: {
pattern: /<\/?(?!\d)[^\s>\/=$<%]+(?:\s+[^\s>\/=]+(?:=(?:("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|[^\s'">=]+))?)*\s*\/?>/i,
greedy: true,
inside: {
tag: {
pattern: /^<\/?[^\s>\/]+/i,
inside: {
punctuation: /^<\/?/,
namespace: /^[^\s>\/:]+:/,
},
},
'attr-value': {
pattern: /=(?:("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|[^\s'">=]+)/i,
inside: {
punctuation: [
/^=/,
{
pattern: /(^|[^\\])["']/,
lookbehind: true,
},
],
},
},
punctuation: /\/?>/,
'attr-name': {
pattern: /[^\s>\/]+/,
inside: {
namespace: /^[^\s>\/:]+:/,
},
},
},
},
entity: /&#?[\da-z]{1,8};/i,
};
Prism.languages.markup.tag.inside['attr-value'].inside.entity = Prism.languages.markup.entity;
// Plugin to make entity title show the real entity, idea by Roman Komarov
Prism.hooks.add('wrap', (env) => {
if (env.type === 'entity') {
env.attributes['title'] = env.content.replace(/&amp;/, '&');
}
});
Prism.languages.xml = Prism.languages.markup;
Prism.languages.html = Prism.languages.markup;
Prism.languages.mathml = Prism.languages.markup;
Prism.languages.svg = Prism.languages.markup;
/* **********************************************
Begin prism-css.js
********************************************** */
Prism.languages.css = {
comment: /\/\*[\s\S]*?\*\//,
atrule: {
pattern: /@[\w-]+?.*?(?:;|(?=\s*\{))/i,
inside: {
rule: /@[\w-]+/,
// See rest below
},
},
url: /url\((?:(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1|.*?)\)/i,
selector: /[^{}\s][^{};]*?(?=\s*\{)/,
string: {
pattern: /("|')(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,
greedy: true,
},
property: /[-_a-z\xA0-\uFFFF][-\w\xA0-\uFFFF]*(?=\s*:)/i,
important: /\B!important\b/i,
function: /[-a-z0-9]+(?=\()/i,
punctuation: /[(){};:]/,
};
Prism.languages.css.atrule.inside.rest = Prism.languages.css;
if (Prism.languages.markup) {
Prism.languages.insertBefore('markup', 'tag', {
style: {
pattern: /(<style[\s\S]*?>)[\s\S]*?(?=<\/style>)/i,
lookbehind: true,
inside: Prism.languages.css,
alias: 'language-css',
greedy: true,
},
});
Prism.languages.insertBefore('inside', 'attr-value', {
'style-attr': {
pattern: /\s*style=("|')(?:\\[\s\S]|(?!\1)[^\\])*\1/i,
inside: {
'attr-name': {
pattern: /^\s*style/i,
inside: Prism.languages.markup.tag.inside,
},
punctuation: /^\s*=\s*['"]|['"]\s*$/,
'attr-value': {
pattern: /.+/i,
inside: Prism.languages.css,
},
},
alias: 'language-css',
},
}, Prism.languages.markup.tag);
}
/* **********************************************
Begin prism-clike.js
********************************************** */
Prism.languages.clike = {
comment: [
{
pattern: /(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,
lookbehind: true,
},
{
pattern: /(^|[^\\:])\/\/.*/,
lookbehind: true,
greedy: true,
},
],
string: {
pattern: /(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,
greedy: true,
},
'class-name': {
pattern: /((?:\b(?:class|interface|extends|implements|trait|instanceof|new)\s+)|(?:catch\s+\())[\w.\\]+/i,
lookbehind: true,
inside: {
punctuation: /[.\\]/,
},
},
keyword: /\b(?:if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,
boolean: /\b(?:true|false)\b/,
function: /[a-z0-9_]+(?=\()/i,
number: /\b0x[\da-f]+\b|(?:\b\d+\.?\d*|\B\.\d+)(?:e[+-]?\d+)?/i,
operator: /--?|\+\+?|!=?=?|<=?|>=?|==?=?|&&?|\|\|?|\?|\*|\/|~|\^|%/,
punctuation: /[{}[\];(),.:]/,
};
/* **********************************************
Begin prism-javascript.js
********************************************** */
Prism.languages.javascript = Prism.languages.extend('clike', {
keyword: /\b(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|var|void|while|with|yield)\b/,
number: /\b(?:0[xX][\dA-Fa-f]+|0[bB][01]+|0[oO][0-7]+|NaN|Infinity)\b|(?:\b\d+\.?\d*|\B\.\d+)(?:[Ee][+-]?\d+)?/,
// Allow for all non-ASCII characters (See http://stackoverflow.com/a/2008444)
function: /[_$a-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\s*\()/i,
operator: /-[-=]?|\+[+=]?|!=?=?|<<?=?|>>?>?=?|=(?:==?|>)?|&[&=]?|\|[|=]?|\*\*?=?|\/=?|~|\^=?|%=?|\?|\.{3}/,
});
Prism.languages.insertBefore('javascript', 'keyword', {
regex: {
pattern: /((?:^|[^$\w\xA0-\uFFFF."'\])\s])\s*)\/(\[[^\]\r\n]+]|\\.|[^/\\\[\r\n])+\/[gimyu]{0,5}(?=\s*($|[\r\n,.;})\]]))/,
lookbehind: true,
greedy: true,
},
// This must be declared before keyword because we use "function" inside the look-forward
'function-variable': {
pattern: /[_$a-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\s*=\s*(?:function\b|(?:\([^()]*\)|[_$a-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*)\s*=>))/i,
alias: 'function',
},
constant: /\b[A-Z][A-Z\d_]*\b/,
});
Prism.languages.insertBefore('javascript', 'string', {
'template-string': {
pattern: /`(?:\\[\s\S]|\${[^}]+}|[^\\`])*`/,
greedy: true,
inside: {
interpolation: {
pattern: /\${[^}]+}/,
inside: {
'interpolation-punctuation': {
pattern: /^\${|}$/,
alias: 'punctuation',
},
rest: null, // See below
},
},
string: /[\s\S]+/,
},
},
});
Prism.languages.javascript['template-string'].inside.interpolation.inside.rest = Prism.languages.javascript;
if (Prism.languages.markup) {
Prism.languages.insertBefore('markup', 'tag', {
script: {
pattern: /(<script[\s\S]*?>)[\s\S]*?(?=<\/script>)/i,
lookbehind: true,
inside: Prism.languages.javascript,
alias: 'language-javascript',
greedy: true,
},
});
}
Prism.languages.js = Prism.languages.javascript;
export default Prism;

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,432 @@
//** REQUEST TYPES **//
export const REQUEST_TYPE_POST = 'POST';
export const REQUEST_TYPE_GET = 'GET';
export const REQUEST_TYPE_PUT = 'PUT';
export const REQUEST_TYPE_DELETE = 'DELETE';
//** POST CONTENT TYPES **//
export const CONTENT_TYPE_JSON = 'json';
export const CONTENT_TYPE_FORM = 'x-www-form-urlencoded';
//** API URLS **//
export const API_STATUS = '/api/v1/status';
export const API_GET_SETTINGS = '/api/v1/settings/site';
export const API_GET_MEMBER_INFO = '/api/v1/settings/member';
export const API_NEW_PAGE = '/api/v1/page/create';
export const API_EDIT_PAGE = '/api/v1/page/write';
export const API_DELETE_PAGE = '/api/v1/page/delete';
export const API_SETTINGS_SYNC = '/api/v1/settings/sync';
export const API_PUBLISH_PAGES = '/api/v1/settings/publish';
export const API_NAV_SYNC = '/api/v1/settings/nav-sync';
export const API_REINDEX_PAGES = '/api/v1/settings/reindex';
export const API_SEND_MAIL = '/api/v1/mailer';
export const API_LOGIN = '/api/v1/login';
//** API TASKS **//
export const AUTH_STATUS = 'getAuthStatus';
export const TASK_SETTINGS_WRITE = 'writeSettings';
export const TASK_PUBLISH_SITE = 'publishSite';
export const TASK_PAGE_CREATE = 'createNewPage';
export const TASK_PAGE_EDIT = 'editPage';
export const TASK_PAGE_DELETE = 'deletePage';
export const TASK_SEND_MAIL = 'sendMail';
export const TASK_REINDEX_PAGE = 'reIndexPages';
export const TASK_SYNC_SETTNIGS = 'syncSite';
export const TASK_SYNC_NAV = 'syncNav';
export const TASK_GET_SETTINGS = 'getSiteSettings';
export const TASK_GET_MEMBER_INFO = 'getMemberInfo';
//** API STATUS **//
export const API_ACCESS_GOOD = 'apiUseAuthorized';
export const API_ACCESS_BAD = 'apiUseNotAuthorized';
/**
* A can of methods used to edit install settings, navigation pages and content pages
*/
class FipamoAdminAPI {
/**
* @constructor
* @param {string} baseURL - url of site; uses local when empty
* @param {object} progressBar - element to be used to display upload progress
*/
constructor(baseURL = null, progressBar = null) {
this.percentComplete = 0; //for later
this.baseURL = null;
this.progressBar = progressBar;
this.status = false;
if (baseURL) this.baseURL = baseURL;
//asks server if a session is active
this._request(this.baseURL ? this.baseURL + API_STATUS : API_STATUS).then(
response => {
if (response.type === API_ACCESS_GOOD) {
this.token = response.token;
} else {
//don't set token
//console.log("NO TOKEN");
}
}
);
}
/**
* Promise method for authenticating and starting a session\
* **POST**`/api/v1/login`
* @param {Object[]} data - json object that contains data for set up
* @param {string} data[].handle - handle for site user
* @param {string} data[].password - password for site user
* @example
* api.login(data).then(response=>{
* console.log("RESPONSE", response);
* })
* @returns {object} json object that contains type and status of login request
```
{
"message":"Example Message of Affirmation or what you're missing!",
"type":"exampleType",
}
```
*/
login(data) {
return new Promise((resolve, reject) => {
this.baseURL ? (data.remote = true) : (data.remote = false);
this.key ? (data.key = this.key) : (data.key = null);
this._request(
this.baseURL ? this.baseURL + API_LOGIN : API_LOGIN,
AUTH_STATUS,
REQUEST_TYPE_POST,
CONTENT_TYPE_JSON,
data
)
.then(result => {
resolve(result);
})
.catch(err => {
reject(err);
});
});
}
/**
* Method for saving site and navigation settings\
* **POST**`/api/v1/settings/:task`
* @param {string} task - settings being synced `config | navigation`
* @param {object[]} data - json object that contains settings data for [site] or [nav]
* @param {string} data[].global.base_url - base url for site [site]
* @param {string} data[].global.title - site title [site]
* @param {string} data[].global.descriptions - brief site summary [site]
* @param {string} data[].global.background - url for site feature image for header [site]
* @param {boolean} data[].global.private - privacy state for site [disabled] [site]
* @param {boolean} data[].global.renderOnSave - property for publishing site when page saved [disabled] [site]
* @param {string} data[].global.theme - current theme for site [site]
* @param {boolean} data[].global.externalAPI - toggle for external API access [site]
* @param {string} data[].member.handle - current member handle [site]
* @param {string} data[].member.email - current member email [site]
* @param {string} data[].email.active - current email protocol being used [site]
* @param {string} data[].email.smtp.domain - url of smtp service being [site]
* @param {string} data[].email.smtp.email - email account of smtp service [site]
* @param {string} data[].email.smtp.password - password for email of smtp service [site]
* @param {string} data[].email.mailgun.domain - mailgun domain url [site]
* @param {string} data[].email.mailgun.key - mailgun key [site]
*
* @param {string} data[].item.title - page title [nav]
* @param {string} data[].item.slug - url safe title [nav]
* @param {string} data[].item.uuid - unique identifier [nav]
* @param {string} data[].item.path - directory path to associated markdown file [nav]
* @example
* api.sync(TASK, data).then(response=>{
* console.log("RESPONSE", response);
* })
* @returns {object} json object that contains type and status of sync request
```
*
{
"message":"Example Message of Affirmation!",
"type":"exampleType",
}
```
*/
sync(task, data) {
return new Promise((resolve, reject) => {
let url = '';
switch (task) {
case 'syncSite':
url = API_SETTINGS_SYNC;
break;
case 'syncNav':
url = API_NAV_SYNC;
break;
}
this._request(
this.baseURL ? this.baseURL + url : url,
TASK_SETTINGS_WRITE,
REQUEST_TYPE_POST,
CONTENT_TYPE_JSON,
data
)
.then(result => {
resolve(result);
})
.catch(err => {
reject(err);
});
});
}
/**
* Method for retrieving user authorizing user login
* @param {object[]} data - json object that contains task
* @param {string} data[].task - publishing task
* @example
* api.publish(TASK).then(response=>{
* console.log("RESPONSE", response);
* })
* @returns {object} json object that contains type and status of publis request
* ```
{
"message":"Example Message of Affirmation!",
"type":"exampleType",
}
* ```
*/
publish(data) {
return new Promise((resolve, reject) => {
this._request(
//API_PUBLISH_PAGES,
this.baseURL ? this.baseURL + API_PUBLISH_PAGES : API_PUBLISH_PAGES,
TASK_PUBLISH_SITE,
REQUEST_TYPE_POST,
CONTENT_TYPE_JSON,
data
)
.then(result => {
resolve(result);
})
.catch(err => {
reject(err);
});
});
}
/**
* Method for handling page creating and editing\
* **POST**`/api/v1/page/:task`
* @param {string} task - current page action
* @param {object[]} form - form object that contains info for current page being edited/created
* @param {string} form[].id - sequence id for page, leave empty for new page
* @param {string} form[].uuid - unique identifier for page, leave empty for new page
* @param {string} form[].layout - current page layout
* @param {string} form[].current_title - saved url save title for persistence when changing title, leave empty for new page
* @param {string} form[].content - markdown body of page
* @param {string} form[].title - current title of page
* @param {string} form[].created - date page was created, leave empty for new page
* @param {string} form[].slug - url safe string of page title
* @param {string} form[].tags - comma separated list of tags
* @param {boolean} form[].menu - property that indicates page is included in site menu
* @param {boolean} form[].featured - property that indicates page is featured
* @param {boolean} form[].published - property that indicates page is public
* @param {string} form[].form_token - hidden property to authenticate form submission
* @param {input} form[].feature_image - main image for page
* @example
* api.pageActions(TASK, data).then(response=>{
* console.log("RESPONSE", response);
* })
* @returns {object} json object that contains type and status of page edit request
```
{
"message":"Example Message of Affirmation!",
"type":"exampleType",
}
```
*/
pageActions(task, data) {
let url, event, content;
switch (task) {
case TASK_PAGE_CREATE:
url = API_NEW_PAGE;
event = TASK_PAGE_CREATE;
content = CONTENT_TYPE_JSON;
break;
case TASK_PAGE_EDIT:
url = API_EDIT_PAGE;
event = TASK_PAGE_EDIT;
content = CONTENT_TYPE_JSON;
break;
case TASK_PAGE_DELETE:
url = API_DELETE_PAGE;
event = TASK_PAGE_DELETE;
content = CONTENT_TYPE_JSON;
break;
default:
break;
}
if (this.baseURL) {
//data.key = this.key;
data.remote = true;
} else {
data.remote = false;
}
return new Promise((resolve, reject) => {
this._request(
this.baseURL ? this.baseURL + url : url,
event,
REQUEST_TYPE_POST,
content,
data
)
.then(result => {
resolve(result);
})
.catch(err => {
reject(err);
});
});
}
/**
* Method for sending mail (if completed in settings)\
* **POST**`/api/v1/mailer`
* @param {object[]} message - json object that contains items to be included in main site navigation
* @param {string} message[].content - message to send
* @example
* api.sendMail(message).then(response=>{
* console.log("RESPONSE", response);
* })
* @returns {object} json object that contains type and status of page edit request
```
{
"message":"Example Message of Affirmation!",
"type":"exampleType"
}
```
*/
sendMail(message) {
return new Promise((resolve, reject) => {
this._request(
this.baseURL ? this.baseURL + API_SEND_MAIL : API_SEND_MAIL,
TASK_SEND_MAIL,
REQUEST_TYPE_POST,
CONTENT_TYPE_JSON,
message
)
.then(result => {
resolve(result);
})
.catch(err => {
reject(err);
});
});
}
/**
* *Promise method for retrieving site and member info*\
* **GET** `/api/v1/settings/:type`
* @param {string} type - type of info requested ['site'|'member'];
* @example
* api.getInfo("type").then(data=>{
console.log("Info Object", data);
* })
* @returns {object} json object that contains data for requested information
*
* *info object example*
* ```
{
"message":"message of affirmation!",
"task":"type of info request",
"data":json-data-object,
}
* ```
*/
getInfo(type) {
let url, task;
if (type == 'site') {
url = API_GET_SETTINGS;
task = TASK_GET_SETTINGS;
} else {
url = API_GET_MEMBER_INFO;
task = TASK_GET_MEMBER_INFO;
}
return new Promise((resolve, reject) => {
this._request(this.baseURL ? this.baseURL + url : url, task)
.then(result => {
resolve(result);
})
.catch(err => {
reject(err);
});
});
}
//--------------------------
// private
//--------------------------
_request(
requestURL,
eventType,
requestType = REQUEST_TYPE_GET,
contentType = CONTENT_TYPE_JSON,
requestData = null
) {
var self = this;
return new Promise(function (resolve, reject) {
var request = new XMLHttpRequest();
request.upload.addEventListener('progress', e =>
self.handleLoadProgress(e, self.progressBar)
);
request.open(requestType, requestURL, true);
request.onload = () => {
if (request.status == 200) {
let response = JSON.parse(request['response']);
resolve(response);
} else {
let error = JSON.parse(request['response']);
reject(error);
}
};
if (requestType == REQUEST_TYPE_PUT || requestType == REQUEST_TYPE_POST) {
if (
eventType === TASK_SETTINGS_WRITE ||
eventType === TASK_PAGE_EDIT ||
eventType === TASK_PAGE_CREATE ||
eventType === TASK_PAGE_DELETE ||
eventType === TASK_PUBLISH_SITE ||
eventType === TASK_REINDEX_PAGE
)
request.setRequestHeader('fipamo-access-token', self.token);
switch (contentType) {
case CONTENT_TYPE_JSON:
request.setRequestHeader(
'Content-type',
'application/' + contentType
);
request.send(JSON.stringify(requestData));
break;
case CONTENT_TYPE_FORM:
request.send(requestData);
break;
}
} else {
if (
eventType === TASK_GET_SETTINGS ||
eventType === TASK_GET_MEMBER_INFO
) {
request.setRequestHeader('fipamo-access-token', self.token);
}
request.send();
}
});
}
//--------------------------
// event handlers
//--------------------------
handleLoadProgress(e, progressBar) {
let percent = Math.ceil((e.loaded / e.total) * 100);
//if a progress bar element is present, talk to it
if (progressBar != null) {
progressBar.style.width = percent + '%';
}
}
}
export { FipamoAdminAPI as default };

View file

@ -0,0 +1,294 @@
//** REQUEST TYPES **//
export const REQUEST_TYPE_POST = "POST";
export const REQUEST_TYPE_GET = "GET";
export const REQUEST_TYPE_PUT = "PUT";
export const REQUEST_TYPE_DELETE = "DELETE";
//** POST CONTENT TYPES **//
export const CONTENT_TYPE_JSON = "json";
export const CONTENT_TYPE_FORM = "x-www-form-urlencoded";
//** API URLS **//
export const API_GET_PAGES = "/api/v1/page/published";
export const API_GET_FEATURED = "/api/v1/page/featured";
export const API_GET_PAGE = "/api/v1/page/single";
export const API_GET_MENU = "/api/v1/page/menu";
export const API_GET_TAGS = "/api/v1/page/tags";
//** API TASKS **//
export const TASK_GET_CONTENT = "retrieveContent";
/**
* A bag of methods for getting content from an install.
*/
class FipamoContentAPI {
/**
* @constructor
* @param {string} baseURL - url of install, defaults to local
* @param {string} key - user api key found in Settings
* @author Ro
*/
constructor(baseURL = null, key = null) {
this.baseURL = null;
this.key = null;
if (key) this.key = key;
if (baseURL) this.baseURL = baseURL;
}
/**
* *Promise method for retrieving page data*\
* **GET**`/api/v1/page/:type`
* @param {string} type - type of pages (`published | menu | featured`) being retrieved; null value defaults to `published`
* @example
* api.pages('published').then(pages=>{
* console.log("Pages Object", pages);
* })
* @returns {object} json object that contains pages of requested type
*
* *pages object example*
* ```
{
"pages":
[
{
"id":1,
"uuid":"uuid-for-entry",
"title":"Entry Title",
"feature":"/path/to/image.jpg",
"path":"2020/09",
"layout":"page",
"tags":"these, are, tags",
"author":"your-name",
"created":"2020 Sep Tue 01",
"updated":"2020 Sep Tue 01",
"deleted":false,
"menu":false,
"featured":false,
"published":true,
"slug":"entry-title",
"content":"Premium Content"
},
{
"id":2,
"uuid":"uuid-for-entry",
"title":"Another Title",
"feature":"/path/to/image.jpg",
"path":"2020/09",
"layout":"page",
"tags":"these, are, tags",
"author":"your-name",
"created":"2020 Sep Tue 01",
"updated":"2020 Sep Tue 01",
"deleted":false,
"menu":false,
"featured":false,
"published":true,
"slug":"another-title",
"content":"Premium Content"
}
],
"totalItems":2
}
* ```
*
*/
pages(type = null) {
//set url based on request type
let requestURL = "";
switch (type) {
default:
case "published":
requestURL = API_GET_PAGES + "?key=" + this.key;
break;
case "featured":
requestURL = API_GET_FEATURED + "?key=" + this.key;
break;
case "menu":
requestURL = API_GET_MENU + "?key=" + this.key;
break;
}
return new Promise((resolve, reject) => {
this._request(
this.baseURL ? this.baseURL + requestURL : requestURL,
TASK_GET_CONTENT
)
.then((result) => {
resolve(result);
})
.catch((err) => {
reject(err);
});
});
}
/**
* *Promise method for retrieving single page*\
* **GET** `/api/v1/page/single/:id`
* @param {string} id - uuid of desired page
* @example
* api.page("a-uuid-for-a-page").then(page=>{
console.log("Page Object", page);
* })
* @returns {object} json object that contains data for requested page
*
* *page object example*
* ```
{
"id":1,
"uuid":"uuid-for-entry",
"title":"Entry Title",
"feature":"/path/to/image.jpg",
"path":"2020/09",
"layout":"page",
"tags":"these, are, tags",
"author":"your-name",
"created":"2020 Sep Tue 01",
"updated":"2020 Sep Tue 01",
"deleted":false,
"menu":false,
"featured":false,
"published":true,
"slug":"entry-title",
"content":"Premium Content"
}
* ```
*/
page(id) {
return new Promise((resolve, reject) => {
this._request(
this.baseURL
? this.baseURL + API_GET_PAGE + "/" + id + "?key=" + this.key
: API_GET_PAGE + "/" + id + "?key=" + this.key,
TASK_GET_CONTENT,
REQUEST_TYPE_GET
)
.then((result) => {
resolve(result);
})
.catch((err) => {
reject(err);
});
});
}
/**
* *Promise method for retrieving all tags used by pages*\
* **GET** `/api/v1/page/tags`
* @example
* api.tags().then(tags=>{
console.log("Tags Object", tags);
* })
* @returns {object} json object that contains site tags and page stubs associated with said tag
*
* *tags object example*
* ```
[
{
"tag_name":"this is a tag",
"slug":"this-is-a-tag",
"pages":
[
{
"title":"This is a title",
"slug":"this-is-a-title",
"path":"2021/04"
},
{
"title":"This is another title",
"slug":"this-is-another-title",
"path":"2020/10"
}
]
},
{
"tag_name":"this is another tag",
"slug":"this-is-another-tag",
"pages":
[
{
"title":"This is a title",
"slug":"this-is-a-title",
"path":"2021/04"
},
{
"title":"This is another title",
"slug":"this-is-another-title",
"path":"2020/10"
}
]
}
]
* ```
*/
tags() {
return new Promise((resolve, reject) => {
this._request(
this.baseURL
? this.baseURL + API_GET_TAGS + "?key=" + this.key
: API_GET_TAGS + "?key=" + this.key,
TASK_GET_CONTENT,
REQUEST_TYPE_GET
)
.then((result) => {
resolve(result);
})
.catch((err) => {
reject(err);
});
});
}
//--------------------------
// private
//--------------------------
_request(
requestURL,
eventType,
requestType = REQUEST_TYPE_GET,
contentType = CONTENT_TYPE_JSON,
requestData = null
) {
var self = this;
return new Promise(function (resolve, reject) {
var request = new XMLHttpRequest();
request.upload.onprogress = self.handleLoadProgress;
request.open(requestType, requestURL, true);
request.onload = () => {
if (request.status == 200) {
let response = JSON.parse(request["response"]);
resolve(response);
} else {
let error = JSON.parse(request["response"]);
reject(error);
}
};
if (requestType == REQUEST_TYPE_PUT || requestType == REQUEST_TYPE_POST) {
switch (contentType) {
case CONTENT_TYPE_JSON:
request.setRequestHeader(
"Content-type",
"application/" + contentType
);
request.send(JSON.stringify(requestData));
break;
case CONTENT_TYPE_FORM:
request.send(requestData);
break;
}
} else {
request.send();
}
});
}
//--------------------------
// event handlers
//--------------------------
handleLoadProgress(e) {
this.percentComplete = Math.ceil((e.loaded / e.total) * 100);
//pass element to display request progress
}
}
export { FipamoContentAPI as default };

View file

@ -194,3 +194,6 @@
</div>
</section>
@endsection
@section('scripting')
<script type="module" src="/assets/scripts/dash/app/EditPage.js"></script>
@endsection

View file

@ -11,41 +11,41 @@
<link rel="stylesheet" type="text/css" href="/assets/css/dash/start.css">
</head>
<body>
<body>
<header>
@if($status)
<nav class="top-nav">
<a href="/dashboard"><img id="the-logo" src="/assets/images/global/fipamo-logo.svg"/></a>
<h1>{{ $title }}</h1>
<div class="nav-right">
@if($status)
@include('includes.nav')
@endif
<header>
@if($status)
<nav class="top-nav">
<a href="/dashboard"><img id="the-logo" src="/assets/images/global/fipamo-logo.svg"/></a>
<h1>{{ $title }}</h1>
<div class="nav-right">
@if($status)
@include('includes.nav')
@endif
</div>
</nav>
<div class="notify" role="note">
@include('includes.notifications')
</div>
</nav>
<div class="notify" role="note">
@include('includes.notifications')
@endif
</header>
@if(session('message'))
<div class="system-notice-message" role="status">
{!! session('message') !!}
</div>
@endif
</header>
@if(session('message'))
<div class="system-notice-message" role="status">
{!! session('message') !!}
</div>
@endif
@endif
<main>
@spaceless
@section('main-content')
@show
@endspaceless
</main>
<footer>
<main>
@spaceless
@section('main-content')
@show
@endspaceless
</main>
<footer>
</footer>
<script type="module" src="/assets/scripts/dash.js"></script>
</body>
</footer>
@section('scripting')
@show
</body>
</html>