diff --git a/.gitignore b/.gitignore index f9c3f4e..a7bce2e 100644 --- a/.gitignore +++ b/.gitignore @@ -16,9 +16,6 @@ public/assets/css/* !public/assets/scripts public/assets/scripts/* !public/assets/scripts/Start.js -/public/assets/images/global/rikc-logo.svg - - !public/assets/images public/assets/images/* !public/assets/images/global/ @@ -42,8 +39,4 @@ config/pages.json config/tags.json *.DS_Store config.codekit3 -/config/backups - -src/com/* -src/styles/* - +/config/backups \ No newline at end of file diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php index adf5ae8..cd8588c 100644 --- a/.php-cs-fixer.php +++ b/.php-cs-fixer.php @@ -1,6 +1,8 @@ setRiskyAllowed(true) ->setRules([ '@PSR12' => true, 'array_indentation' => true, @@ -20,7 +22,7 @@ return (new PhpCsFixer\Config()) 'multiline_whitespace_before_semicolons' => [ 'strategy' => 'no_multi_line', ], - 'single_quote' => true, + 'single_quote' => false, 'binary_operator_spaces' => [ 'default' => 'single_space', @@ -50,7 +52,6 @@ return (new PhpCsFixer\Config()) 'extra', 'parenthesis_brace_block', 'throw', - ] ], 'no_multiline_whitespace_around_double_arrow' => true, @@ -68,5 +69,6 @@ return (new PhpCsFixer\Config()) 'ordered_imports' => [ 'sort_algorithm' => 'none', ], + //Other rules here... ]) ->setLineEnding("\n"); diff --git a/README.md b/README.md index 89aadd7..b554f92 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,9 @@ -![This is Fipamo](https://playvicio.us/base-assets/images/fipamo-brand.png) - # Fipamo means to save The Fipamo project was born from a need for a simple, easy to use no data blog platform that doesn't require much effort to set up and maintain. Fipamo uses Markdown to handle posts and renders them to flat html so you can serve them from anywhere. No complicated set ups. No long list of dependencies. Just set up and go. Because nobody has time for all that. -## Check the (WIP) Docs to get you started.
+## Check the (WIP) Docs to get you started.
-[Getting Started](https://code.playvicio.us/Are0h/Fipamo/wiki/00---Start)
-[Install](https://code.playvicio.us/Are0h/Fipamo/wiki/01---Install)
-[Using Fipamo](https://code.playvicio.us/Are0h/Fipamo/wiki/02-Usage)
+[Getting Started](https://koodu.ubiqueros.com/are0h/Fipamo/wiki/00---Start)
diff --git a/brain/controller/APIControl.php b/brain/controller/APIControl.php index dd60058..6f41555 100644 --- a/brain/controller/APIControl.php +++ b/brain/controller/APIControl.php @@ -225,7 +225,6 @@ class APIControl ]; break; } - $response->getBody()->write(json_encode($result)); return $response->withHeader('Content-Type', 'application/json'); } diff --git a/brain/controller/RouteControl.php b/brain/controller/RouteControl.php index ab75386..3ceaa26 100644 --- a/brain/controller/RouteControl.php +++ b/brain/controller/RouteControl.php @@ -34,7 +34,6 @@ class RouteControl switch (isset($args['first']) ? $args['first'] : 'index') { case 'api': //$result = APIControl::post($request, $response, $args); - //var_dump($result); return APIControl::post($request, $response, $args); break; default: diff --git a/brain/utility/Maintenance.php b/brain/utility/Maintenance.php index f6a4469..c3e4f40 100644 --- a/brain/utility/Maintenance.php +++ b/brain/utility/Maintenance.php @@ -3,7 +3,6 @@ namespace brain\utility; use Carbon\Carbon; -use brain\data\Settings; class Maintenance { diff --git a/brain/utility/Sorting.php b/brain/utility/Sorting.php index 8ca60a4..aa38c14 100644 --- a/brain/utility/Sorting.php +++ b/brain/utility/Sorting.php @@ -2,12 +2,13 @@ namespace brain\utility; -use function _\filter; -use function _\find; use brain\data\Book; use brain\data\Settings; use Mni\FrontYAML\Parser; +use function _\filter; +use function _\find; + class Sorting { private static $p_tags = []; @@ -16,6 +17,7 @@ class Sorting public function __construct() { } + public static function tags() { $pages = (new Book('../content/pages'))->getContents(); @@ -36,6 +38,7 @@ class Sorting return self::$p_tags; } + private static function tagPages($tag, $pages) { $tagged = []; @@ -52,6 +55,7 @@ class Sorting return $tagged; } + public static function archive() { $pages = (new Book('../content/pages'))->getContents(); @@ -64,10 +68,13 @@ class Sorting if (!find($years, ['year' => trim($date[0])])) { $findPages = filter($pages, ['createdYear' => trim($date[0])]); // var_dump($findPages); - array_push($years, [ - 'year' => trim($date[0]), - 'count' => count($findPages), - ]); + array_push( + $years, + [ + 'year' => trim($date[0]), + 'count' => count($findPages), + ] + ); } } foreach ($years as $year) { @@ -77,12 +84,15 @@ class Sorting foreach ($filtered as $obj) { $month = date('m', date($obj['rawCreated'])); if (!find($sorted, ['month' => $month])) { - $perMonth = filter($pages, [ - 'path' => $year['year'] . '/' . $month, - 'deleted' => false, - 'published' => true, - 'layout' => 'page', - ]); + $perMonth = filter( + $pages, + [ + 'path' => $year['year'] . '/' . $month, + 'deleted' => false, + 'published' => true, + 'layout' => 'page', + ] + ); array_push($sorted, [ 'month' => $month, 'full_month' => date('F', date($obj['rawCreated'])), @@ -99,6 +109,7 @@ class Sorting return self::$p_archive; } + public static function page($page) { $config = new Settings(); diff --git a/brain/views/dash/_frame.old.twig b/brain/views/dash/_frame.old.twig new file mode 100644 index 0000000..4f96798 --- /dev/null +++ b/brain/views/dash/_frame.old.twig @@ -0,0 +1,66 @@ + + + + + + + {% block title %} + {{ title }} + {% endblock %} + + {% block stylesheets %}{% endblock %} + + +
+
+
+ +
+
+ +
+
+ +
+ +
+
+

MESSAGE TEXT

+
+ +
+
+
+
+ {% if status %} + + {% endif %} + {% apply spaceless %} + {% block mainContent %}{% endblock %} + {% endapply %} + +
+ + +
+ + + {% block javascripts %}{% endblock %} + + diff --git a/brain/views/dash/_frame.twig b/brain/views/dash/_frame.twig index 4f96798..f941c1e 100644 --- a/brain/views/dash/_frame.twig +++ b/brain/views/dash/_frame.twig @@ -30,36 +30,32 @@ -
-
- {% if status %} -
- - -
- + {% endif %} + +
+ {% apply spaceless %} + {% block mainContent %}{% endblock %} + {% endapply %} +
{% block javascripts %}{% endblock %} diff --git a/brain/views/dash/forms/login.twig b/brain/views/dash/forms/login.twig index 2a8792d..5c2b285 100644 --- a/brain/views/dash/forms/login.twig +++ b/brain/views/dash/forms/login.twig @@ -1,13 +1,13 @@ -
-
- - -
-
\ No newline at end of file +
+
+ +
+
+ + + + ? +
+
diff --git a/brain/views/dash/partials/index.twig b/brain/views/dash/partials/index.twig index 0ebb0e2..3258b12 100644 --- a/brain/views/dash/partials/index.twig +++ b/brain/views/dash/partials/index.twig @@ -1,58 +1,51 @@ -
-
-
-
- Recent -
- -
-
- {% if data["entryCount"] != 0 %} - {% for page in data['pages'] %} - {% if page.media[0].type == 'mp4' %} - - - - - {{ include("dash/partials/recent-options.twig") }} - - - {% else %} - - - - {{ include("dash/partials/recent-options.twig") }} - - {% endif %} - - {% endfor %} - {% else %} - There are no pages - {% endif %} - -
+
+
+

Recent

+
+ + + + + + + +
+
+
+ {% if data["entryCount"] != 0 %} + {% for page in data['pages'] %} + {% if page.media[0].type == 'mp4' %} + + + + + {{ include("dash/partials/recent-meta.twig") }} + + + {% else %} + + + + {{ include("dash/partials/recent-meta.twig") }} + + {% endif %} + + {% endfor %} + {% else %} + There are no pages + {% endif %} + +
diff --git a/brain/views/dash/partials/navigation.twig b/brain/views/dash/partials/navigation.twig index 24634c1..10ffc29 100644 --- a/brain/views/dash/partials/navigation.twig +++ b/brain/views/dash/partials/navigation.twig @@ -1,23 +1,25 @@ -
+
- . + + . - . + + . -
\ No newline at end of file +
diff --git a/brain/views/dash/partials/recent-meta.twig b/brain/views/dash/partials/recent-meta.twig new file mode 100644 index 0000000..0160efe --- /dev/null +++ b/brain/views/dash/partials/recent-meta.twig @@ -0,0 +1,43 @@ +{% if page.menu == 'true' %} + {% set menu = "true" %} +{% else %} + {% set menu = "false" %} +{% endif %} +{% if page.published == 'true' %} + {% set published = "true" %} +{% else %} + {% set published = "false" %} +{% endif %} +{% if page.featured == 'true' %} + {% set featured = "true" %} +{% else %} + {% set featured = "false" %} +{% endif %} + + diff --git a/brain/views/dash/partials/recent-options.twig b/brain/views/dash/partials/recent-options.twig deleted file mode 100644 index c7e8096..0000000 --- a/brain/views/dash/partials/recent-options.twig +++ /dev/null @@ -1,42 +0,0 @@ -
- {% if page.menu == 'true' %} - {% set menu = "true" %} - {% else %} - {% set menu = "false" %} - {% endif %} - {% if page.published == 'true' %} - {% set published = "true" %} - {% else %} - {% set published = "false" %} - {% endif %} - {% if page.featured == 'true' %} - {% set featured = "true" %} - {% else %} - {% set featured = "false" %} - {% endif %} -
- - - - - -
-
- - {{ page.updated }} - -
- -
\ No newline at end of file diff --git a/brain/views/dash/start.twig b/brain/views/dash/start.twig index ad9d3d0..4f822ca 100644 --- a/brain/views/dash/start.twig +++ b/brain/views/dash/start.twig @@ -5,21 +5,17 @@ {% endblock %} {% block stylesheets %} - + {% endblock %} {% block mainContent %} -
-
- {% if status %} - {% apply spaceless %} - {{ include("dash/partials/index.twig") }} - {% endapply %} - {% else %} - {{ include("dash/forms/login.twig") }} - {% endif %} -
-
+ {% if status %} + {% apply spaceless %} + {{ include("dash/partials/index.twig") }} + {% endapply %} + {% else %} + {{ include("dash/forms/login.twig") }} + {% endif %} {% endblock %} {% block javascripts %} diff --git a/package.json b/package.json index ea57ef7..4f8c99c 100644 --- a/package.json +++ b/package.json @@ -35,8 +35,8 @@ "license": "UNLICENSED", "author": "Are0h", "scripts": { - "watch": "sass --watch src/styles:public/assets/css & npx parcel watch src/com/Start.js --dist-dir public/assets/scripts --public-url /assets/scripts", - "build": "sass src/styles:public/assets/css & npx parcel build src/com/Start.js --dist-dir public/assets/scripts --public-url /assets/scripts" + "watch": "npx parcel watch src/com/Start.js --dist-dir public/assets/scripts --public-url /assets/scripts", + "build": "npx parcel build src/com/Start.js --dist-dir public/assets/scripts --public-url /assets/scripts" }, "description": "Front end script for the most chill blog framework ever.", "repository": "https://code.playvicio.us/Are0h/Fipamo" diff --git a/package.old.json b/package.old.json new file mode 100644 index 0000000..ea57ef7 --- /dev/null +++ b/package.old.json @@ -0,0 +1,43 @@ +{ + "name": "fipamo-dash", + "version": "2.5.1-beta", + "private": true, + "apidoc": { + "name": "Fipamo API", + "version": "1.0.0", + "description": "The most chill API for the most chill blog framework" + }, + "devDependencies": { + "@babel/preset-env": "^7.16.5", + "babel-cli": "^6.26.0", + "eslint": "^8.11.0", + "eslint-plugin-babel": "^5.3.1", + "parcel": "^2.0.1", + "prettier": "^2.6.0", + "stylelint": "^14.8.2", + "stylelint-config-prettier-scss": "^0.0.1", + "stylelint-config-standard-scss": "^3.0.0" + }, + "dependencies": { + "@babel/core": "^7.16.5", + "@babel/eslint-parser": "^7.16.5", + "animejs": "^3.2.1", + "babel-plugin-prismjs": "^2.1.0", + "babel-preset-env": "^1.7.0", + "bulma": "^0.9.3", + "caret-pos": "^2.0.0", + "jsdoc": "^3.6.7", + "minami": "^1.2.3", + "prismjs": "^1.25.0", + "sass": "^1.45.1", + "sortablejs": "^1.14.0" + }, + "license": "UNLICENSED", + "author": "Are0h", + "scripts": { + "watch": "sass --watch src/styles:public/assets/css & npx parcel watch src/com/Start.js --dist-dir public/assets/scripts --public-url /assets/scripts", + "build": "sass src/styles:public/assets/css & npx parcel build src/com/Start.js --dist-dir public/assets/scripts --public-url /assets/scripts" + }, + "description": "Front end script for the most chill blog framework ever.", + "repository": "https://code.playvicio.us/Are0h/Fipamo" +} diff --git a/public/assets/images/global/fipamo-logo.svg b/public/assets/images/global/fipamo-logo.svg index f8d21b5..b8504ab 100644 --- a/public/assets/images/global/fipamo-logo.svg +++ b/public/assets/images/global/fipamo-logo.svg @@ -1,33 +1,33 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/assets/images/global/rikc-logo.svg b/public/assets/images/global/rikc-logo.svg new file mode 100644 index 0000000..56bdf9d --- /dev/null +++ b/public/assets/images/global/rikc-logo.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/public/assets/scripts/Start.js b/public/assets/scripts/Start.js index 45dd849..ea79522 100644 --- a/public/assets/scripts/Start.js +++ b/public/assets/scripts/Start.js @@ -537,10 +537,10 @@ class Base { // methods //-------------------------- start() { - if (document.getElementById("dash-form") || document.getElementById("dash-init")) { + if (document.getElementById("login") || document.getElementById("dash-init")) { 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("dash-form")) document.getElementById("login-btn").addEventListener("click", (e)=>this.handleLogin(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)); diff --git a/src/com/Base.js b/src/com/Base.js new file mode 100644 index 0000000..55f3835 --- /dev/null +++ b/src/com/Base.js @@ -0,0 +1,208 @@ +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 + //-------------------------- + start() { + if (document.getElementById('login') || document.getElementById('dash-init')) { + 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) { + notify.alert(response.message, false); + } else { + notify.alert(response.message, true); + e.target.innerHTML = response.message; + setTimeout(() => { + window.location = '/dashboard'; + }, 500); + } + }) + .catch(err => { + self.processing = false; + notify.alert(err, 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; + notify.alert(response.message, false); + } else { + self.processing = false; + notify.alert(response.message, true); + 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; + notify.alert(response.message, false); + } else { + self.processing = false; + notify.alert(response.message, true); + setTimeout(() => { + window.location = '/dashboard'; + }, 1500); + } + }) + .catch(err => { + self.processing = false; + notify.alert(err, false); + }); + } + + 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.getElementById('dash-init'); + let restore = document.getElementById('dash-restore'); + if (e.target.id === 'init-switch-restore') { + init.style.display = 'none'; + init.style.visibility = 'hidden'; + + restore.style.display = 'flex'; + restore.style.visibility = 'visible'; + } else { + init.style.display = 'flex'; + init.style.visibility = 'visible'; + + restore.style.display = 'none'; + restore.style.visibility = 'hidden'; + } + } +} diff --git a/src/com/Start.js b/src/com/Start.js new file mode 100644 index 0000000..167671c --- /dev/null +++ b/src/com/Start.js @@ -0,0 +1,9 @@ +import Base from "./Base"; + +document.addEventListener( + "DOMContentLoaded", + function () { + new Base(); + }, + false +); diff --git a/src/com/actions/Mailer.js b/src/com/actions/Mailer.js new file mode 100644 index 0000000..20e1eb4 --- /dev/null +++ b/src/com/actions/Mailer.js @@ -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 + //-------------------------- +} diff --git a/src/com/actions/NavActions.js b/src/com/actions/NavActions.js new file mode 100644 index 0000000..5f6bdd9 --- /dev/null +++ b/src/com/actions/NavActions.js @@ -0,0 +1,37 @@ +export default class NavActions { + //-------------------------- + // constructor + //-------------------------- + constructor() {} + //-------------------------- + // methods + //-------------------------- + syncMenu() { + let navData = []; + let items = document.getElementById("nav-pages").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-pages") + .removeChild(document.getElementById(id)); + } + + //-------------------------- + // event handlers + //-------------------------- +} diff --git a/src/com/actions/PageActions.js b/src/com/actions/PageActions.js new file mode 100644 index 0000000..7ef79cd --- /dev/null +++ b/src/com/actions/PageActions.js @@ -0,0 +1,96 @@ +import StringUtils from '../utils/StringUtils'; +export default class PostActions { + //-------------------------- + // constructor + //-------------------------- + constructor() {} + //-------------------------- + // methods + //-------------------------- + collectInfo(files) { + return new Promise((resolve, reject) => { + let pageInfo = new FormData(); + pageInfo.enctype = 'multipart/form-data'; + 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 + pageInfo.append( + 'id', + document.getElementById('post-edit-index').getAttribute('data-index') + ); + pageInfo.append( + 'uuid', + document.getElementById('post-edit-index').getAttribute('data-uuid') + ); + pageInfo.append( + 'layout', + document.getElementById('post-edit-index').getAttribute('data-layout') + ); + pageInfo.append( + 'current_title', + document.getElementById('post-edit-index').getAttribute('data-slug') + ); + pageInfo.append('content', html); + pageInfo.append('title', document.getElementById('post-title-text').value); + pageInfo.append( + 'created', + document.getElementById('post-date').getAttribute('data-raw') + ); + pageInfo.append( + 'slug', + new StringUtils().cleanString( + document.getElementById('post-title-text').value + ) + ); + pageInfo.append('tags', document.getElementById('post-tags').value); + pageInfo.append( + 'menu', + document.getElementById('option-menu-pin').getAttribute('data-active') + ); + pageInfo.append( + 'featured', + document.getElementById('option-feature').getAttribute('data-active') + ); + pageInfo.append( + 'published', + document.getElementById('option-published').getAttribute('data-active') + ); + pageInfo.append('layout', document.getElementById('page-templates').value); + pageInfo.append('form_token', document.getElementById('form_token').value); + if (files.length > 0 && files != null) { + for (var i = 0; i < files.length; i++) { + var file = files[i]; + if ( + file.type.match('image.*') || + file.type.match('video.mp4') || + file.type.match('audio.mpeg') || + file.type.match('application.pdf') || + file.type.match('text.plain') || + file.type.match('text.rtf') + ) { + pageInfo.append('page_files[]', file, file.name); + } else { + reject('Not an image file: ' + file.type); + } + } + } else { + //check to see if image exists + if (document.getElementById('featured-image')) { + var imageURL = document.getElementById('featured-image').src; + imageURL != null || imageURL != undefined + ? pageInfo.append('feature_image', imageURL) + : pageInfo.append('feature_image', null); + } else { + //pageInfo.append("feature_image", null); + } + } + //console.log("FILES", files); + resolve(pageInfo); + }); + } + //-------------------------- + // event handlers + //-------------------------- +} diff --git a/src/com/actions/SettingsActions.js b/src/com/actions/SettingsActions.js new file mode 100644 index 0000000..07bdffc --- /dev/null +++ b/src/com/actions/SettingsActions.js @@ -0,0 +1,78 @@ +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.getElementById("background").src; + 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 + //-------------------------- +} diff --git a/src/com/controllers/DashManager.js b/src/com/controllers/DashManager.js new file mode 100644 index 0000000..6ca7adb --- /dev/null +++ b/src/com/controllers/DashManager.js @@ -0,0 +1,41 @@ +import PostIndex from './PostIndex'; +import SettingsIndex from './SettingsIndex'; +import NaviIndex from './NavIndex'; + +export default class DashManager { + //-------------------------- + // constructor + //-------------------------- + constructor() { + this.currentDisplay = ''; + this.urlPieces = document.URL.split('/'); + this.chooseDisplay(this.urlPieces[4], this.urlPieces[5]); + } + //-------------------------- + // 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 + //-------------------------- +} diff --git a/src/com/controllers/MaintenanceManager.js b/src/com/controllers/MaintenanceManager.js new file mode 100644 index 0000000..f7cc9c3 --- /dev/null +++ b/src/com/controllers/MaintenanceManager.js @@ -0,0 +1,304 @@ +//** 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'; +//** 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, progressBar = null) { + this.percentComplete = 0; //for later + this.token = null; + this.baseURL = null; + this.progressBar = progressBar; + 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, + 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, 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, 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 images [todo: change to uploading files] + * @param {string} type - type of upload + * @param {input} files - form input containing files + */ + imageUpload(type, files) { + return new Promise((resolve, reject) => { + let url = ''; + switch (type) { + case 'avatar-upload': + url = API_UPLOAD_AVATAR; + break; + case 'background-upload': + url = API_UPLOAD_BACKGROUND; + break; + default: + url = API_IMAGE_UPLOAD; + break; + } + var imageData = new FormData(); + + if (this.baseURL) { + imageData.append('key', this.key); + imageData.append('remote', true); + } else { + imageData.append('remote', false); + } + + for (var i = 0; i < files.length; i++) { + var file = files[i]; + // Check the file type. + if (!file.type.match('image.*')) { + continue; + } + if (type === 'avatar-upload') { + imageData.append('avatar_upload', file, file.name); + } else if (type === 'background-upload') { + imageData.append('background_upload', file, file.name); + } else { + imageData.append('post_image', file, file.name); + } + } + this._request( + url, + TASK_UPLOAD_FILES, + REQUEST_TYPE_POST, + CONTENT_TYPE_FORM, + imageData + ) + .then(r => { + resolve(r); + }) + .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_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 }; diff --git a/src/com/controllers/NavIndex.js b/src/com/controllers/NavIndex.js new file mode 100644 index 0000000..b1ed680 --- /dev/null +++ b/src/com/controllers/NavIndex.js @@ -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-pages'), { + 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; + } + } +} diff --git a/src/com/controllers/PageEditor.js b/src/com/controllers/PageEditor.js new file mode 100644 index 0000000..bbf6dc8 --- /dev/null +++ b/src/com/controllers/PageEditor.js @@ -0,0 +1,228 @@ +//TOOLS +import FipamoAdminAPI, { + TASK_PAGE_CREATE, + TASK_PAGE_EDIT, + TASK_PAGE_DELETE +} from '../../libraries/FipamoAdminAPI'; +import Maintenance from './MaintenanceManager'; +import * as DataEvent from '../events/DataEvent'; +import PageActions from '../actions/PageActions'; +import * as EditorEvent from '../events/EditorEvent'; +//import TinyDatePicker from 'tiny-date-picker'; TODO: Reactivate for scheduled publishing +import TextEditor from '../ui/TextEditor'; +import Notfications from '../ui/Notifications'; +import FileManager from '../ui/FileManager'; +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, document.getElementById('notify-progress')); + this.urlPieces = document.URL.split('/'); + this.post = []; + this.postID = null; + this.postUUID = null; + this.postLayout = null; + this.fm = null; + if (document.getElementById('post-edit-index').getAttribute('data-index')) { + this.postID = document + .getElementById('post-edit-index') + .getAttribute('data-index'); + this.postUUID = document + .getElementById('post-edit-index') + .getAttribute('data-uuid'); + this.postLayout = document + .getElementById('post-edit-index') + .getAttribute('data-layout'); + } + if (document.getElementById('edit')) { + this.editor = new TextEditor( + document.getElementById('edit'), + document.getElementById('header').offsetHeight + + document.getElementById('post-header').offsetHeight + + document.getElementById('post-feature').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.getElementById('page-file-drop')) { + //insert fileManager here + this.fm = new FileManager( + document.getElementById('page-file-drop'), + document.getElementById('page-files-upload'), + document.getElementById('page-images-list'), + document.getElementById('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.getFiles()).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( + document.getElementById('featured-image-upload').files[0] + ) + .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); + + self.mm + .imageUpload(type, files) + .then(r => { + if (r.type == DataEvent.POST_IMAGE_ADDED) { + self.editor.notify(EditorEvent.EDITOR_UPLOAD_POST_IMAGE, r.url); + 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); + //console.log('ERROR', err); + }); + } +} +PostEditor.uploadFiles = []; diff --git a/src/com/controllers/PostIndex.js b/src/com/controllers/PostIndex.js new file mode 100644 index 0000000..9ff06f8 --- /dev/null +++ b/src/com/controllers/PostIndex.js @@ -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 + //-------------------------- +} diff --git a/src/com/controllers/SettingsIndex.js b/src/com/controllers/SettingsIndex.js new file mode 100644 index 0000000..cacdf60 --- /dev/null +++ b/src/com/controllers/SettingsIndex.js @@ -0,0 +1,265 @@ +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.getElementById("avatar").addEventListener("click", () => { + document.getElementById("avatar-upload").click(); + }); + document.getElementById("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 = "EXTERNAL API ACCESS IS ENABLED"; + } else { + apiButton.setAttribute("data-enabled", "false"); + apiStatus.innerHTML = "EXTERNAL API ACCESS IS NOT ENABLED"; + } + }); + + //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)); + document + .getElementById("render-toggle-icon") + .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); + + this.mm + .imageUpload(type, files) + .then((r) => { + if (r.type == DataEvent.AVATAR_UPLOADED) { + notify.alert(r.message, true); + document.getElementById("avatar").src = r.url; + } else { + notify.alert(r.message, true); + document.getElementById("background").src = r.url; + } + }) + .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); + }); + } +} diff --git a/src/com/events/AuthEvent.js b/src/com/events/AuthEvent.js new file mode 100644 index 0000000..81fd0fc --- /dev/null +++ b/src/com/events/AuthEvent.js @@ -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(); diff --git a/src/com/events/DataEvent.js b/src/com/events/DataEvent.js new file mode 100644 index 0000000..fd0a8f2 --- /dev/null +++ b/src/com/events/DataEvent.js @@ -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(); diff --git a/src/com/events/EditorEvent.js b/src/com/events/EditorEvent.js new file mode 100644 index 0000000..2cd24c6 --- /dev/null +++ b/src/com/events/EditorEvent.js @@ -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(); diff --git a/src/com/events/EventEmitter.js b/src/com/events/EventEmitter.js new file mode 100644 index 0000000..56f5dfe --- /dev/null +++ b/src/com/events/EventEmitter.js @@ -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; diff --git a/src/com/ui/FileManager.js b/src/com/ui/FileManager.js new file mode 100644 index 0000000..36a37cc --- /dev/null +++ b/src/com/ui/FileManager.js @@ -0,0 +1,265 @@ +import Sortable from 'sortablejs'; +import DataUtils from '../utils/DataUtils'; +import Notfications from './Notifications.js'; +const notify = new Notfications(); + +export default class FileManager { + //-------------------------- + // constructor + //-------------------------- + constructor(upload, input, imageList, fileList) { + 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 + ); + } + } + getFiles() { + return this.files; + } + + reindexFiles(sortOrder, step) { + let count = sortOrder.length; + if (step == 0) this.files = []; + var utils = new DataUtils(); + utils.imgLoad(sortOrder[step].earl).then(blob => { + var fresh = new File([blob], sortOrder[step].fileName, { type: blob.type }); + this.files.push(fresh); + var limit = count - 1; + if (this.files.length <= limit) { + step = step + 1; + this.reindexFiles(sortOrder, step); + } else { + notify.alert('FILES READY TO UPLOAD', true); + } + }); + } + + 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'); + remove.className = 'media-remove'; + remove.innerHTML = 'X'; + remove.addEventListener( + 'click', + e => self.removeFile(e, 'media'), + false + ); + //get counts for lists + var mediaCount = self.imageList.childNodes.length; + var fileCount = self.fileList.childNodes.length; + // sort files + switch (theFile.type) { + case 'image/jpg': + case 'image/jpeg': + case 'image/gif': + case 'image/svg': + case 'image/png': + //create element and add to list + //var image = document.createElement('img'); + //image.src = f.target.result; + //image.title = escape(theFile.name); + var span = document.createElement('div'); + span.style.background = + 'url(' + + f.target.result + + ') no-repeat center center / cover'; + span.className = 'img-item'; + + //image.setAttribute('id', i); + self.storage.push([ + { + id: 'page_image' + i, + data: f.target.result, + type: theFile.type, + name: escape(theFile.name) + } + ]); + if (mediaCount < 0) mediaCount = 0; + span.setAttribute('id', mediaCount); + remove.setAttribute('id', mediaCount); + span.setAttribute('data-file-name', theFile.name); + span.appendChild(remove); + self.imageList.appendChild(span); + + break; + case 'video/mp4': + var video = document.createElement('div'); + video.className = 'video-item'; + video.setAttribute('data-source', f.target.result); + if (mediaCount < 0) mediaCount = 0; + video.setAttribute('id', mediaCount); + remove.setAttribute('id', mediaCount); + video.setAttribute('data-file-name', theFile.name); + video.appendChild(remove); + self.imageList.appendChild(video); + break; + case 'audio/mpeg': + var sound = document.createElement('div'); + sound.className = 'audio-item'; + sound.setAttribute('data-source', f.target.result); + sound.setAttribute('id', fileCount); + remove.setAttribute('id', fileCount); + sound.setAttribute('data-file-name', theFile.name); + sound.appendChild(remove); + self.fileList.appendChild(sound); + break; + case 'application/pdf': + case 'text/plain': + case 'text/rtf': + var file = document.createElement('div'); + file.className = 'file-item'; + file.setAttribute('data-source', f.target.result); + file.setAttribute('id', fileCount); + remove.setAttribute('id', fileCount); + file.setAttribute('data-file-name', theFile.name); + file.appendChild(remove); + self.fileList.appendChild(file); + break; + } + }; + })(file); + // Read in the image file as a data URL. + reader.readAsDataURL(file); + } + //give the script a beat to add the child nodes, then update it all + setTimeout(() => { + self.updateFiles(); + }, 50); + } + + //-------------------------- + // event handlers + //-------------------------- + updateFiles() { + let currentFiles = []; //store current list + let items = []; + //get files from media and files list + for (let index = 0; index < this.imageList.childNodes.length; index++) { + items.push(this.imageList.childNodes[index]); + } + + for (let index = 0; index < this.fileList.childNodes.length; index++) { + items.push(this.fileList.childNodes[index]); + } + + for (let index = 0; index < items.length; index++) { + var item = items[index]; + let url = ''; + if (item.className == 'img-item') { + url = item.style.backgroundImage.slice(4, -1).replace(/"/g, ''); + } else { + url = item.getAttribute('data-source'); + } + currentFiles.push({ + earl: url, + fileName: item.getAttribute('data-file-name') + }); + } + this.reindexFiles(currentFiles, 0); + } + + removeFile(e) { + var list = []; + switch (e.target.parentNode.className) { + case 'img-item': + case 'video-item': + list = this.imageList; + + break; + case 'audio-item': + case 'file-item': + list = this.fileList; + break; + } + for (let index = 0; index < list.childNodes.length; index++) { + let media = list.childNodes[index]; + if (media.id == e.target.id) { + list.removeChild(media); + notify.alert('REINDEXING MEDIA', null); + this.updateFiles(); + } + } + } + + 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; + } + } +} diff --git a/src/com/ui/Notifications.js b/src/com/ui/Notifications.js new file mode 100644 index 0000000..fcf42f6 --- /dev/null +++ b/src/com/ui/Notifications.js @@ -0,0 +1,101 @@ +import anime from 'animejs/lib/anime.es.js'; +const notifcation = document.getElementById('notifications'); +const notify = document.getElementById('notify-message'); +const messageText = document.getElementById('message-text'); +const notifyText = document.getElementById('notify-text'); +const notifyProgress = document.getElementById('notify-progress'); +const iconGood = document.getElementById('notify-good'); +const iconLame = document.getElementById('notify-lame'); +const iconWorking = document.getElementById('notify-working'); + +export default class Notfications { + //-------------------------- + // constructor + //-------------------------- + constructor() {} + //-------------------------- + // methods + //-------------------------- + + alert(text, status) { + iconWorking.style.display = 'none'; + iconGood.style.display = 'none'; + iconLame.style.display = 'none'; + + var color = ''; + if (status !== null) { + anime({ + targets: notifyProgress, + opacity: 0, + easing: 'easeInOutQuint', + duration: 500 + }); + if (status) { + color = '#32cd32'; + iconWorking.style.display = 'none'; + iconGood.style.display = 'block'; + } else { + color = '#F64747'; + iconWorking.style.display = 'none'; + iconLame.style.display = 'block'; + } + } else { + color = '#200317'; + iconWorking.style.display = 'block'; + anime({ + targets: notifyProgress, + opacity: 1, + easing: 'easeInOutQuint', + duration: 500 + }); + } + messageText.innerHTML = text; + + anime({ + targets: notifcation, + marginTop: '-10', + easing: 'easeInOutQuint', + duration: 10, + complete: () => { + anime({ + targets: notify, + rotateX: '0', + easing: 'easeInOutQuint', + duration: 700 + }); + anime({ + targets: notifyText, + backgroundColor: color, + easing: 'easeInOutQuint', + duration: 700, + complete: () => { + setTimeout(() => { + if (status !== null) { + anime({ + targets: notify, + rotateX: '-120', + easing: 'easeInOutQuint', + duration: 700, + complete: () => { + anime({ + targets: notifcation, + marginTop: '-55', + easing: 'easeInOutQuint', + delay: 700, + duration: 50 + }); + //notifcation.style.display = 'none'; + } + }); + } + }, 1000); + } + }); + } + }); + } + + //-------------------------- + // event handlers + //-------------------------- +} diff --git a/src/com/ui/TextEditor.js b/src/com/ui/TextEditor.js new file mode 100644 index 0000000..d59cfaa --- /dev/null +++ b/src/com/ui/TextEditor.js @@ -0,0 +1,194 @@ +import * as DataEvent from "../events/DataEvent"; +import { position } from "caret-pos"; +import EventEmitter from "../events/EventEmitter"; +import * as EditorEvent from "../events/EditorEvent"; +import Prism from "prismjs"; +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.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.getElementById("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 + window.addEventListener("scroll", () => { + var fixLimit = scrollLimit; + + if (window.pageYOffset + 5 >= fixLimit) { + document.getElementById("edit-control").style.position = "fixed"; + } else { + document.getElementById("edit-control").style.position = "relative"; + } + }); + } + //-------------------------- + // 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; diff --git a/src/com/utils/DataUtils.js b/src/com/utils/DataUtils.js new file mode 100644 index 0000000..524f323 --- /dev/null +++ b/src/com/utils/DataUtils.js @@ -0,0 +1,93 @@ +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.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 + //-------------------------- +} diff --git a/src/com/utils/StringUtils.js b/src/com/utils/StringUtils.js new file mode 100644 index 0000000..6676b91 --- /dev/null +++ b/src/com/utils/StringUtils.js @@ -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;