added front end scripting to recreate API
dropped in js from the old site to begin the process of wiring up the API, but this time around, scripts will be served directly in browswer rather than being transpiled through NPM/Babel, eliminating the need for NPM. also scripting will new modularized and served specifically for the requirements of the page loading it. no more front loading everything. only script that is needed for that page will be retrieved. if no scripting is needed, none will be loaded. The only casualty so far has been syntax highlighting due to prismjs still being a common js module, but either this will be replaced with another library or a custom syntax enginge will be created at a later date
This commit is contained in:
parent
b527884c51
commit
1e37580869
36 changed files with 6997 additions and 7465 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -39,9 +39,6 @@ public/assets/*
|
||||||
public/assets/css/*
|
public/assets/css/*
|
||||||
!public/assets/css/dash
|
!public/assets/css/dash
|
||||||
!public/assets/scripts
|
!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/*
|
public/assets/images/*
|
||||||
!public/assets/images/global/
|
!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
211
public/assets/scripts/dash/app/Base.js
Normal file
211
public/assets/scripts/dash/app/Base.js
Normal 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';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
9
public/assets/scripts/dash/app/EditPage.js
Normal file
9
public/assets/scripts/dash/app/EditPage.js
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import Editor from './controllers/PageEditor.js';
|
||||||
|
|
||||||
|
document.addEventListener(
|
||||||
|
'DOMContentLoaded',
|
||||||
|
function () {
|
||||||
|
new Editor();
|
||||||
|
},
|
||||||
|
false
|
||||||
|
);
|
9
public/assets/scripts/dash/app/Start.js
Normal file
9
public/assets/scripts/dash/app/Start.js
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import Base from './Base';
|
||||||
|
|
||||||
|
document.addEventListener(
|
||||||
|
'DOMContentLoaded',
|
||||||
|
function () {
|
||||||
|
new Base();
|
||||||
|
},
|
||||||
|
false
|
||||||
|
);
|
44
public/assets/scripts/dash/app/actions/Mailer.js
Normal file
44
public/assets/scripts/dash/app/actions/Mailer.js
Normal 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
|
||||||
|
//--------------------------
|
||||||
|
}
|
35
public/assets/scripts/dash/app/actions/NavActions.js
Normal file
35
public/assets/scripts/dash/app/actions/NavActions.js
Normal 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
|
||||||
|
//--------------------------
|
||||||
|
}
|
53
public/assets/scripts/dash/app/actions/PageActions.js
Normal file
53
public/assets/scripts/dash/app/actions/PageActions.js
Normal 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
|
||||||
|
//--------------------------
|
||||||
|
}
|
79
public/assets/scripts/dash/app/actions/SettingsActions.js
Normal file
79
public/assets/scripts/dash/app/actions/SettingsActions.js
Normal 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
|
||||||
|
//--------------------------
|
||||||
|
}
|
44
public/assets/scripts/dash/app/controllers/DashManager.js
Normal file
44
public/assets/scripts/dash/app/controllers/DashManager.js
Normal 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
|
||||||
|
//--------------------------
|
||||||
|
}
|
292
public/assets/scripts/dash/app/controllers/MaintenanceManager.js
Normal file
292
public/assets/scripts/dash/app/controllers/MaintenanceManager.js
Normal 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 };
|
74
public/assets/scripts/dash/app/controllers/NavIndex.js
Normal file
74
public/assets/scripts/dash/app/controllers/NavIndex.js
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
228
public/assets/scripts/dash/app/controllers/PageEditor.js
Normal file
228
public/assets/scripts/dash/app/controllers/PageEditor.js
Normal 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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
30
public/assets/scripts/dash/app/controllers/PostIndex.js
Normal file
30
public/assets/scripts/dash/app/controllers/PostIndex.js
Normal 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
|
||||||
|
//--------------------------
|
||||||
|
}
|
268
public/assets/scripts/dash/app/controllers/SettingsIndex.js
Normal file
268
public/assets/scripts/dash/app/controllers/SettingsIndex.js
Normal 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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
21
public/assets/scripts/dash/app/events/AuthEvent.js
Normal file
21
public/assets/scripts/dash/app/events/AuthEvent.js
Normal 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();
|
51
public/assets/scripts/dash/app/events/DataEvent.js
Normal file
51
public/assets/scripts/dash/app/events/DataEvent.js
Normal 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();
|
14
public/assets/scripts/dash/app/events/EditorEvent.js
Normal file
14
public/assets/scripts/dash/app/events/EditorEvent.js
Normal 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();
|
52
public/assets/scripts/dash/app/events/EventEmitter.js
Normal file
52
public/assets/scripts/dash/app/events/EventEmitter.js
Normal 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;
|
296
public/assets/scripts/dash/app/ui/FileManager.js
Normal file
296
public/assets/scripts/dash/app/ui/FileManager.js
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
28
public/assets/scripts/dash/app/ui/Menu.js
Normal file
28
public/assets/scripts/dash/app/ui/Menu.js
Normal 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';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
109
public/assets/scripts/dash/app/ui/Notifications.js
Normal file
109
public/assets/scripts/dash/app/ui/Notifications.js
Normal 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
|
||||||
|
//--------------------------
|
||||||
|
}
|
206
public/assets/scripts/dash/app/ui/TextEditor.js
Normal file
206
public/assets/scripts/dash/app/ui/TextEditor.js
Normal 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'), '&')
|
||||||
|
.replace(new RegExp('<', 'g'), '<');
|
||||||
|
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;
|
95
public/assets/scripts/dash/app/utils/DataUtils.js
Normal file
95
public/assets/scripts/dash/app/utils/DataUtils.js
Normal 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
|
||||||
|
//--------------------------
|
||||||
|
}
|
68
public/assets/scripts/dash/app/utils/StringUtils.js
Normal file
68
public/assets/scripts/dash/app/utils/StringUtils.js
Normal 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(/</g, '<')
|
||||||
|
.replace(/>/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(/�*39;/g, "'"); // PHP doesn't currently escape if more than one 0, but it should
|
||||||
|
// string = string.replace(/'|�*27;/g, "'"); // This would also be useful here, but not a part of PHP
|
||||||
|
}
|
||||||
|
if (!noquotes) {
|
||||||
|
string = string.replace(/"/g, '"');
|
||||||
|
}
|
||||||
|
// Put this in last place to avoid escape being double-decoded
|
||||||
|
string = string.replace(/&/g, '&');
|
||||||
|
return string;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------
|
||||||
|
// event handlers
|
||||||
|
//--------------------------
|
||||||
|
}
|
||||||
|
export default StringUtils;
|
1310
public/assets/scripts/dash/app/vendor/anime.es.js
vendored
Normal file
1310
public/assets/scripts/dash/app/vendor/anime.es.js
vendored
Normal file
File diff suppressed because it is too large
Load diff
227
public/assets/scripts/dash/app/vendor/lang/prism-markdown.js
vendored
Normal file
227
public/assets/scripts/dash/app/vendor/lang/prism-markdown.js
vendored
Normal 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(/</g, '<').replace(/&/g, '&');
|
||||||
|
|
||||||
|
env.content = Prism.highlight(code, grammar, codeLang);
|
||||||
|
});
|
||||||
|
|
||||||
|
Prism.languages.md = Prism.languages.markdown;
|
100
public/assets/scripts/dash/app/vendor/lang/prism-markup.js
vendored
Normal file
100
public/assets/scripts/dash/app/vendor/lang/prism-markup.js
vendored
Normal 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(/&/, '&');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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;
|
1
public/assets/scripts/dash/app/vendor/prism-markdown.min.js
vendored
Normal file
1
public/assets/scripts/dash/app/vendor/prism-markdown.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
770
public/assets/scripts/dash/app/vendor/prism.js
vendored
Normal file
770
public/assets/scripts/dash/app/vendor/prism.js
vendored
Normal 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, '&').replace(/</g, '<').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;
|
||||||
|
|
||||||
|
// Don’t 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, '"') + '"';
|
||||||
|
}).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(/&/, '&');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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;
|
1513
public/assets/scripts/dash/app/vendor/sortable.js
vendored
Normal file
1513
public/assets/scripts/dash/app/vendor/sortable.js
vendored
Normal file
File diff suppressed because it is too large
Load diff
432
public/assets/scripts/dash/libraries/FipamoAdminAPI.js
Normal file
432
public/assets/scripts/dash/libraries/FipamoAdminAPI.js
Normal 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 };
|
294
public/assets/scripts/dash/libraries/FipamoContentAPI.js
Normal file
294
public/assets/scripts/dash/libraries/FipamoContentAPI.js
Normal 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 };
|
|
@ -194,3 +194,6 @@
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@endsection
|
@endsection
|
||||||
|
@section('scripting')
|
||||||
|
<script type="module" src="/assets/scripts/dash/app/EditPage.js"></script>
|
||||||
|
@endsection
|
||||||
|
|
|
@ -11,8 +11,7 @@
|
||||||
<link rel="stylesheet" type="text/css" href="/assets/css/dash/start.css">
|
<link rel="stylesheet" type="text/css" href="/assets/css/dash/start.css">
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
|
<body>
|
||||||
<body>
|
|
||||||
|
|
||||||
<header>
|
<header>
|
||||||
@if($status)
|
@if($status)
|
||||||
|
@ -45,7 +44,8 @@
|
||||||
<footer>
|
<footer>
|
||||||
|
|
||||||
</footer>
|
</footer>
|
||||||
<script type="module" src="/assets/scripts/dash.js"></script>
|
@section('scripting')
|
||||||
</body>
|
@show
|
||||||
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
Loading…
Reference in a new issue