Updated to nearly fully functional. Pre-release 1. Still needs some UI changes in the slideshow and admin pages (move the save button & fix the save detection for the internship list). Customer had one more page change request which I need to re-define and handle.

This commit is contained in:
Wynne Crisman
2018-12-12 11:04:00 -08:00
parent c0e971774e
commit 3fc374eda3
108 changed files with 3472 additions and 628 deletions

View File

@@ -1,7 +0,0 @@
<template name="AppreciationEditor">
<div id="appreciationEditor">
<h1>Appreciation Editor</h1>
<div class="editor"></div>
<button id="save">Save</button>
</div>
</template>

View File

@@ -1,6 +0,0 @@
#appreciationEditor
display: block
.ck.ck-editor__editable_inline
border-color: rgba(0,0,0,.2)
.ck.ck-editor__editable_inline.ck-focused
border-color: rgba(0,0,0,1)

View File

@@ -1,87 +0,0 @@
import './AppreciationEditor.html';
import swal from "sweetalert2";
let originalData = "";
Tracker.autorun(function() {
Meteor.subscribe("pages");
});
Template.AppreciationEditor.onRendered(function() {
let _this = this;
//#appreciationEditor'
// CKEditor.create(document.querySelector('#editor'), {}).then(editor => {
// _this.ckEditor = editor;
//
// Tracker.autorun(function() {
// let doc = Meteor.collections.Pages.findOne({name: 'Appreciation'});
//
// originalData = (doc === undefined ? "" : doc.html);
// editor.setData(originalData);
// });
// }).catch(err => {
// console.error(err);
// });
$('.editor').tinymce({
inline: true
});
Tracker.autorun(function() {
let doc = Meteor.collections.Pages.findOne({name: 'Appreciation'});
originalData = (doc === undefined ? "" : doc.html);
$('.editor').html(originalData);
});
});
Template.AppreciationEditor.onDestroyed(function() {
// let data = this.ckEditor.getData();
let data = $('.editor').html();
if(data != originalData) {
swal({
title: "Save Changes",
text: "Would you like to save any changes you have made to this sheet?",
type: "question",
showCancelButton: true,
confirmButtonColor: "#7cdd7f",
confirmButtonText: "Yes",
cancelButtonText: "No"
}).then(
function(isConfirm) {
if(isConfirm) {
Meteor.call('updatePage', 'Appreciation', data, function (error, result) {
if (error) sAlert.error(error);
else sAlert.success("Content Saved Successfully");
});
}
},
function(dismiss) {}
);
}
});
Template.AppreciationEditor.helpers({
// html: function() {
// let doc = Meteor.collections.Pages.findOne({name: 'Appreciation'});
//
// return doc == undefined ? "" : doc.html;
// }
});
Template.AppreciationEditor.events({
'click #save': function(event, template) {
// let data = template.ckEditor.getData();
let data = $('.editor').html();
if(data != originalData) {
Meteor.call('updatePage', 'Appreciation', data, function (error, result) {
if (error) sAlert.error(error);
else sAlert.success("Content Saved Successfully");
});
}
else {
sAlert.success("Data has not changed!");
}
}
});

View File

@@ -1,7 +0,0 @@
<template name="BoardEditor">
<div id="boardEditor">
<h1>Current Board Editor</h1>
<div id="editor"></div>
<button id="save">Save</button>
</div>
</template>

View File

@@ -1,68 +0,0 @@
import './BoardEditor.html';
import swal from "sweetalert2";
let originalData = "";
Tracker.autorun(function() {
Meteor.subscribe("pages");
});
Template.BoardEditor.onRendered(function() {
let _this = this;
CKEditor.create(document.querySelector('#editor'), {}).then(editor => {
_this.ckEditor = editor;
Tracker.autorun(function() {
let doc = Meteor.collections.Pages.findOne({name: 'Board'});
originalData = (doc === undefined ? "" : doc.html);
editor.setData(originalData);
});
}).catch(err => {
console.error(err);
});
});
Template.BoardEditor.onDestroyed(function() {
let data = this.ckEditor.getData();
if(data != originalData) {
swal({
title: "Save Changes",
text: "Would you like to save any changes you have made to this sheet?",
type: "question",
showCancelButton: true,
confirmButtonColor: "#7cdd7f",
confirmButtonText: "Yes",
cancelButtonText: "No"
}).then(
function(isConfirm) {
if(isConfirm) {
Meteor.call('updatePage', 'Board', data, function (error, result) {
if (error) sAlert.error(error);
else sAlert.success("Content Saved Successfully");
});
}
},
function(dismiss) {}
);
}
});
Template.BoardEditor.events({
'click #save': function(event, template) {
let data = template.ckEditor.getData();
if(data != originalData) {
Meteor.call('updatePage', 'Board', data, function (error, result) {
if (error) sAlert.error(error);
else sAlert.success("Content Saved Successfully");
});
}
else {
sAlert.success("Data has not changed!");
}
}
});

View File

@@ -1,7 +0,0 @@
<template name="DatesEditor">
<div id="datesEditor">
<h1>Important Dates Editor</h1>
<div id="editor"></div>
<button id="save">Save</button>
</div>
</template>

View File

@@ -1,6 +0,0 @@
#datesEditor
display: block
.ck.ck-editor__editable_inline
border-color: rgba(0,0,0,.2)
.ck.ck-editor__editable_inline.ck-focused
border-color: rgba(0,0,0,1)

View File

@@ -1,69 +0,0 @@
import './DatesEditor.html';
import swal from "sweetalert2";
let originalData = "";
Tracker.autorun(function() {
Meteor.subscribe("pages");
});
Template.DatesEditor.onRendered(function() {
let _this = this;
CKEditor.create(document.querySelector('#editor'), {}).then(editor => {
_this.ckEditor = editor;
Tracker.autorun(function() {
let doc = Meteor.collections.Pages.findOne({name: 'Dates'});
originalData = (doc === undefined ? "" : doc.html);
editor.setData(originalData);
});
}).catch(err => {
console.error(err);
});
});
Template.DatesEditor.onDestroyed(function() {
let data = this.ckEditor.getData();
if(data != originalData) {
swal({
title: "Save Changes",
text: "Would you like to save any changes you have made to this sheet?",
type: "question",
showCancelButton: true,
confirmButtonColor: "#7cdd7f",
confirmButtonText: "Yes",
cancelButtonText: "No"
}).then(
function(isConfirm) {
if(isConfirm) {
Meteor.call('updatePage', 'Dates', data, function (error, result) {
if (error) sAlert.error(error);
else sAlert.success("Content Saved Successfully");
});
}
},
function(dismiss) {}
);
}
});
Template.DatesEditor.events({
'click #save': function(event, template) {
let data = template.ckEditor.getData();
if(data != originalData) {
Meteor.call('updatePage', 'Dates', data, function (error, result) {
if (error) sAlert.error(error);
else sAlert.success("Content Saved Successfully");
});
}
else {
sAlert.success("Data has not changed!");
}
}
});
s

View File

@@ -0,0 +1,33 @@
<template name="InternshipEditor">
<div id="internshipEditor">
<div class="internshipNavigation">
<div class="addInternship"><input type="text" name="newInternshipName" class="newInternshipName form-control"/><i class="fa fa-plus-circle createInternship noselect clickable" aria-hidden="true"></i></div><span class="editPageText noselect clickable"><i class="fa fa-pencil" aria-hidden="true"></i><br/>Header</span>
<ul class="internshipList">
{{#each internships}}
<li class="internshipListItem noselect clickable" data-id="{{_id}}"><span class="name">{{name}}</span><i class="fa fa-times-circle deleteInternship" aria-hidden="true"></i></li>
{{/each}}
</ul>
</div>
<div class="internshipHtml">
{{> InternshipHtmlEditor internship=selectedInternship}}
</div>
</div>
</template>
<template name="InternshipHtmlEditor">
{{#if showInstructions}}
<h2>Instructions</h2>
<p>Add internships using the +. Type in the internship's name as it will appear in the list to the user, then click the + a second time to add it.
Edit the internships by selecting them in the list. The contents will replace these instructions, and you can edit the internship HTML directly.</p>
{{/if}}
{{#if showSelectImageDialog}}
<div class="modalBackground">
{{> SelectImageDialog (selectImageDialogArgs)}}
</div>
{{/if}}
<div class="editorContainer {{#if showInstructions}}hidden{{/if}}">
<div class="editor textView"></div>
<button id="save">Save</button>
</div>
</template>

View File

@@ -0,0 +1,118 @@
#internshipEditor
display: block
.internshipNavigation
float: left
clear: none
display: inline-block
min-width: 200px
width: 20%
padding-right: 20px
position: relative
padding-top: 50px
.addInternship
margin-bottom: 10px
position: absolute
left: 0
top: 0
right: 60px
height: 40px
input[name="newInternshipName"]
display: inline-block
width: 0
transition: all .75s ease
border: 0
opacity: 0
font-size: 1.2em
input[name="newInternshipName"].show
opacity: 1
border: 1px solid #ccc
border-radius: 4px
width: 70%
transform: translateX(4px)
.createInternship
display: inline-block
position: relative
top: 2px
padding: 2px 6px
margin: 0 4px
//width: 33px
text-align: center
font-size: 1.5em
line-height: 1.5em
border-radius: 8px
//border: 1px solid rgba(0, 0, 0, 0)
box-sizing: border-box
.createInternship:hover
//border: 1px inset #b100d1
//-webkit-box-shadow: inset 0px 0px 20px 0px #de7cff
//-moz-box-shadow: inset 0px 0px 20px 0px #de7cff
//box-shadow: inset 0px 0px 20px 0px #de7cff
color: #5c8744
.createInternship
transform: translateX(-25px) rotate(0deg)
transition: all .75s ease
.createInternship.move
transform: translateX(6px) rotate(720deg)
.editPageText
position: absolute
right: 0
top: 0
width: 60px
font-size: 14px
text-align: center
line-height: 14px
font-weight: 800
border: 1.5px solid gray
border-radius: 6px
padding: 6px
margin-right: 5px
.editPageText:hover
background-color: #c6c6c6
.editPageText:active
border-color: black
.internshipListItem
display: block
background: white
padding: 2px 0
white-space: nowrap
position: relative
height: 24px
.name
position: absolute
left: 0
top: 0
bottom: 0
right: 24px
overflow: hidden
.deleteInternship
position: absolute
right: 0
top: 0
bottom: 0
width: 24px
padding-top: 0px
padding-left: 2px
color: #7c0000
line-height: 24px
font-size: 18px
.deleteInternship:hover
font-size: 22px
top: 0
right: 2px
color: #ab0000
.deleteInternship:active
color: #613434
.internshipListItem:hover
background: #e2ddc0
.internshipListItem:active
background: rgba(254, 255, 0, 0.56)
.internshipListItem.selected
background: rgba(254, 255, 0, 0.56)
.internshipHtml
display: inline-block
max-width: 80%
min-width: 200px
width: 100%

View File

@@ -0,0 +1,228 @@
import './InternshipEditor.html';
import '/imports/ui/dialogs/SelectImageDialog.js';
import swal from "sweetalert2";
const PREFIX = "InternshipEditor_";
Tracker.autorun(function() {
Meteor.subscribe("Internship");
});
Template.InternshipEditor.onCreated(function() {
this.internships = Meteor.collections.Internship.find({}, {sort: {name: 1}});
Session.set(PREFIX + 'selectedInternship', null);
});
Template.InternshipEditor.helpers({
internships: function() {
return Template.instance().internships;
},
selectedInternship: function() {
return Session.get(PREFIX + "selectedInternship");
}
});
Template.InternshipEditor.events({
'click .deleteInternship': function(event, template) {
let $li = template.$(event.target).parent();
Meteor.call('removeInternship', $li.data('id'), function(error, id) {
if(error) sAlert.error("Failed to create the internship!\n" + error);
});
},
'click .internshipListItem': function(event, template) {
let $li = template.$(event.currentTarget);
$li.addClass('selected');
$li.siblings().removeClass('selected');
Session.set(PREFIX + "selectedInternship", Meteor.collections.Internship.findOne($li.data('id')));
},
'click .editPageText': function(event,template) {
},
"keyup input[name='newInternshipName']" : function(event, template) {
if(event.keyCode === 13) {
event.preventDefault();
$('.createInternship').trigger('click');
}
},
"click .createInternship": function(event, template) {
let $input = template.$('input[name="newInternshipName"]');
if($input.hasClass('show')) {
let name = $input.val();
name = name ? name.trim() : undefined;
name = name && name.length > 0 ? name : undefined;
if(name) {
let content = "<p><strong>Job Title:&nbsp;</strong></p>\n<p><strong>Supervisor / Sponsor:&nbsp;</strong></p>\n<p><strong>Location of Internship:&nbsp;</strong></p>\n<p><strong>Dates &amp; hours:&nbsp;</strong></p>\n<p><strong>Duties &amp; Activities:&nbsp;</strong></p>\n<p><strong>Desirable Qualities / Skills:&nbsp;</strong></p>";
//Meteor.collections.Internship.insert({name, content});
Meteor.call('addInternship', name, content, function(error, id) {
if(error) sAlert.error("Failed to create the internship!\n" + error);
else {
//Clear the text editor.
$input.val("");
//Quick hack to attempt to allow the internship we created to be added to the list before we try to select it and edit it.
let count = 0;
let interval = setInterval(function() {
let selected = Meteor.collections.Internship.findOne(id);
if(selected) {
//Select the sheet in the drop down.
let $li = template.$('ul.internshipList li[data-id="' + id + '"]');
$li.addClass("selected");
$li.siblings().removeClass("selected");
Session.set(PREFIX + "selectedInternship", selected);
clearInterval(interval);
}
else count++;
//Avoid infinite loop that should never happen.
if(count > 100) clearInterval(interval);
}, 100);
}
});
}
$input.removeClass('show');
$(event.target).toggleClass('move');
}
else {
$input.addClass('show');
$(event.target).toggleClass('move');
$input.focus();
}
}
});
Template.InternshipHtmlEditor.onCreated(function() {
let template = this;
this.showSelectImageDialog = new ReactiveVar(false);
this.saveChanges = function(ask) {
let data = this.$('.editor').val();
let template = this;
//Only ask the user if they want to update their changes if they actually have changes that have not yet been saved.
if(data !== template.currentHtml) {
const changedData = data;
//Ensure this does not get run twice.
template.currentHtml = changedData;
if(ask) {
//Ask the user if they want to update the repository with their changes.
swal({
title: "Save Changes",
text: "Would you like to save any changes you have made to this page?",
type: "question",
showCancelButton: true,
confirmButtonColor: "#7cdd7f",
confirmButtonText: "Yes",
cancelButtonText: "No"
}).then(
function(isConfirm) {
if(isConfirm) {
Meteor.call('updateInternship', template.data.internship._id, changedData, function (error, result) {
if (error) sAlert.error(error);
else sAlert.success("Content Saved Successfully");
});
}
},
function(dismiss) {}
);
}
else {
Meteor.call('updateInternship', template.data.internship._id, changedData, function (error, result) {
if (error) sAlert.error(error);
else sAlert.success("Content Saved Successfully");
});
}
}
};
});
Template.InternshipHtmlEditor.onRendered(function() {
let template = this;
$('.editor').tinymce({
inline: true,
menubar: false,
theme: 'inlite',
plugins: [
'autolink',
'contextmenu',
'link',
'lists',
'table',
'textcolor'
],
toolbar: [
'undo redo | bold italic underline | fontselect fontsizeselect',
'forecolor backcolor | alignleft aligncenter alignright alignfull | link unlink | numlist bullist outdent indent'
],
table_default_attributes: {
border: 0,
cellpadding: 4
},
table_default_styles: {
borderCollapse: "collapse"
},
insert_toolbar: 'quicktable', //image
selection_toolbar: 'bold italic | h1 h2 h3 | bullist numlist outdent indent | blockquote quicklink',
contextmenu: 'InsertImage | inserttable | cell row column deletetable', //image
contextmenu_never_use_native: false,
setup: function(editor) {
editor.addMenuItem('InsertImage', {
text: "Insert Image",
context: 'tools',
onclick: function() {
template.showSelectImageDialog.set(true);
}
});
}
});
Tracker.autorun(function() {
//let data = Template.currentData().data; //Note: Calling Template.currentData() is a reactive way to get the template parameters.
let data = Blaze.getData(template.view).internship;
if(data) {
template.currentHtml = data.content;
$('.editor').html(data.content && data.content.length > 0 ? data.content : "Change Me!");
}
});
});
Template.InternshipHtmlEditor.onDestroyed(function() {
this.saveChanges(true);
});
Template.InternshipHtmlEditor.events({
'click #save': function(event, template) {
template.saveChanges(false);
}
});
Template.InternshipHtmlEditor.helpers({
showSelectImageDialog() {
return Template.instance().showSelectImageDialog.get();
},
selectImageDialogArgs() {
let template = Template.instance();
return {
onApply(value) {
tinymce.activeEditor.insertContent('<img src="' + value + '"/>');
template.showSelectImageDialog.set(false);
},
onClose() {
template.showSelectImageDialog.set(false);
}
};
},
showInstructions() {
return !Template.currentData().internship;
}
});

View File

@@ -1,4 +0,0 @@
<template name="InternshipsEditor">
<div id="internshipsEditor">
</div>
</template>

View File

@@ -1,2 +0,0 @@
#internshipsEditor
display: block

View File

@@ -1,2 +0,0 @@
import './InternshipsEditor.html';

View File

@@ -1,7 +0,0 @@
<template name="NewsEditor">
<div id="newsEditor">
<h1>News Editor</h1>
<div class="editor"></div>
<button id="save">Save</button>
</div>
</template>

View File

@@ -1,6 +0,0 @@
#newsEditor
display: block
.ck.ck-editor__editable_inline
border-color: rgba(0,0,0,.2)
.ck.ck-editor__editable_inline.ck-focused
border-color: rgba(0,0,0,1)

View File

@@ -1,86 +0,0 @@
import './NewsEditor.html';
import swal from "sweetalert2";
let originalData = "";
Tracker.autorun(function() {
Meteor.subscribe("pages");
});
Template.NewsEditor.onRendered(function() {
let _this = this;
//#appreciationEditor'
// CKEditor.create(document.querySelector('#editor'), {}).then(editor => {
// _this.ckEditor = editor;
//
// Tracker.autorun(function() {
// let doc = Meteor.collections.Pages.findOne({name: 'Appreciation'});
//
// originalData = (doc === undefined ? "" : doc.html);
// editor.setData(originalData);
// });
// }).catch(err => {
// console.error(err);
// });
$('.editor').tinymce({
inline: true
});
Tracker.autorun(function() {
let doc = Meteor.collections.Pages.findOne({name: 'News'});
originalData = (doc === undefined ? "" : doc.html);
$('.editor').html(originalData);
});
});
Template.NewsEditor.onDestroyed(function() {
let data = this.ckEditor.getData();
if(data != originalData) {
swal({
title: "Save Changes",
text: "Would you like to save any changes you have made to this sheet?",
type: "question",
showCancelButton: true,
confirmButtonColor: "#7cdd7f",
confirmButtonText: "Yes",
cancelButtonText: "No"
}).then(
function(isConfirm) {
if(isConfirm) {
Meteor.call('updatePage', 'News', data, function (error, result) {
if (error) sAlert.error(error);
else sAlert.success("Content Saved Successfully");
});
}
},
function(dismiss) {}
);
}
});
Template.NewsEditor.helpers({
// html: function() {
// let doc = Meteor.collections.Pages.findOne({name: 'News'});
//
// return doc == undefined ? "" : doc.html;
// }
});
Template.NewsEditor.events({
'click #save': function(event, template) {
let data = template.ckEditor.getData();
if(data != originalData) {
Meteor.call('updatePage', 'News', data, function (error, result) {
if (error) sAlert.error(error);
else sAlert.success("Content Saved Successfully");
});
}
else {
sAlert.success("Data has not changed!");
}
}
});

View File

@@ -0,0 +1,13 @@
<template name="PageEditor">
<div id="pageEditor">
{{#if showSelectImageDialog}}
<div class="modalBackground">
{{> SelectImageDialog (selectImageDialogArgs)}}
</div>
{{/if}}
<h1>{{editorName}} Editor</h1>
<div class="editor"></div>
<button id="save">Save</button>
</div>
</template>

View File

@@ -0,0 +1,226 @@
import './PageEditor.html';
import '/imports/ui/dialogs/SelectImageDialog.js';
import swal from "sweetalert2";
let currentHtml = "";
let currentPath = "";
let routeData = {
AppreciationEditor: {title: "Appreciation", name: "Appreciation"},
NewsEditor: {title: "News", name: "News"},
DatesEditor: {title: "Dates", name: "Dates"},
BoardEditor: {title: "Current Board", name: "Board"},
SlideshowPageEditor: {title: "Slideshow Page", name: "Slideshow"}
};
Tracker.autorun(function() {
//let name = routeData[FlowRouter.getRouteName()] ? routeData[FlowRouter.getRouteName()].name : "";
//if(name) {
//TODO: Filter the page by the page name.
Meteor.subscribe("pages");
//}
});
Template.PageEditor.onCreated(function() {
let template = this;
this.showSelectImageDialog = new ReactiveVar(false);
this.pageName = new ReactiveVar();
this.saveChanges = function() {
if(currentPath && currentPath !== FlowRouter.getRouteName()) {
let data = this.$('.editor').val();
let template = this;
currentPath = FlowRouter.getRouteName();
//Only ask the user if they want to update their changes if they actually have changes that have not yet been saved.
if(data !== currentHtml) {
const changedData = data;
//Ensure this does not get run twice.
currentHtml = data;
//Ask the user if they want to update the repository with their changes.
swal({
title: "Save Changes",
text: "Would you like to save any changes you have made to this page?",
type: "question",
showCancelButton: true,
confirmButtonColor: "#7cdd7f",
confirmButtonText: "Yes",
cancelButtonText: "No"
}).then(
function(isConfirm) {
if(isConfirm) {
Meteor.call('updatePage', template.pageName.get(), changedData, function (error, result) {
if (error) sAlert.error(error);
else sAlert.success("Content Saved Successfully");
});
}
},
function(dismiss) {}
);
}
}
};
Tracker.autorun(function() {
if(routeData[FlowRouter.getRouteName()]) {
template.saveChanges();
//Save the page's name (indexes the page HTML in the collection) to the reactive variable so that all the content changes automatically.
template.pageName.set(routeData[FlowRouter.getRouteName()].name);
}
});
});
Template.PageEditor.onRendered(function() {
let template = this;
$('.editor').tinymce({
// inline: true,
// menubar: false,
// theme: 'inlite', //inlite
// //skin: 'light',
// plugins: [
// 'autolink',
// 'contextmenu',
// 'link',
//// 'linkchecker', Broken
// 'lists',
//// 'powerpaste', Broken
// 'table',
// //'image',
// 'textcolor'
// ],
// toolbar: [
// 'undo redo | bold italic underline | fontselect fontsizeselect',
// 'forecolor backcolor | alignleft aligncenter alignright alignfull | link unlink | numlist bullist outdent indent | InsertImage | inserttable | cell row column deletetable'
// ],
// table_default_attributes: {
// border: 0,
// cellpadding: 4
// },
// table_default_styles: {
// borderCollapse: "collapse"
// },
// insert_toolbar: 'quicktable', //image
// selection_toolbar: 'bold italic | h1 h2 h3 | blockquote quicklink',
// contextmenu: 'InsertImage | inserttable | cell row column deletetable', //image
// contextmenu_never_use_native: false,
//image_advtab: true,
//image_description: true,
//image_dimensions: true,
//image_title: true,
//image_list: function(success) {
// //Expects an array of objects containing title:String and value:String properties where the value is a path to the image.
// Meteor.call('getGeneralImages', function(data, err) {
// if(err) {
// sAlert.error(err);
// }
// else {
// success(data);
// }
// });
//}
//powerpaste_word_import: 'clean',
//powerpaste_html_import: 'clean'
inline: true,
menubar: false,
theme: 'inlite',
plugins: [
'autolink',
'contextmenu',
'link',
'lists',
'table',
'textcolor'
],
toolbar: [
'undo redo | bold italic underline | fontselect fontsizeselect',
'forecolor backcolor | alignleft aligncenter alignright alignfull | link unlink | numlist bullist outdent indent'
],
table_default_attributes: {
border: 0,
cellpadding: 4
},
table_default_styles: {
borderCollapse: "collapse"
},
insert_toolbar: 'quicktable', //image
selection_toolbar: 'bold italic | h1 h2 h3 | blockquote quicklink',
contextmenu: 'InsertImage | inserttable | cell row column deletetable', //image
contextmenu_never_use_native: false,
setup: function(editor) {
editor.addMenuItem('InsertImage', {
text: "Insert Image",
context: 'tools',
onclick: function() {
template.showSelectImageDialog.set(true);
}
});
}
});
Tracker.autorun(function() {
let doc = Meteor.collections.Pages.findOne({name: template.pageName.get()});
currentHtml = (doc === undefined ? "" : doc.html);
$('.editor').html(currentHtml);
});
});
Template.PageEditor.onDestroyed(function() {
this.saveChanges();
});
Template.PageEditor.events({
'click #save': function(event, template) {
let data = template.$('.editor').val();
if(data !== currentHtml) {
Meteor.call('updatePage', template.pageName.get(), data, function (error, result) {
if (error) sAlert.error(error);
else {
sAlert.success("Content Saved Successfully");
//Ensure we recognize things were saved later.
currentHtml = data;
}
});
}
else {
sAlert.success("Data has not changed!");
}
}
});
Template.PageEditor.helpers({
editorName() {
return routeData[FlowRouter.getRouteName()].title; //FlowRouter.getRouteName() is reactive allowing this to trigger when the content changes.
},
showSelectImageDialog() {
return Template.instance().showSelectImageDialog.get();
},
selectImageDialogArgs() {
let template = Template.instance();
return {
onApply(value) {
tinymce.activeEditor.insertContent('<img src="' + value + '"/>');
template.showSelectImageDialog.set(false);
},
onClose() {
template.showSelectImageDialog.set(false);
}
};
}
});

View File

@@ -0,0 +1,41 @@
<template name="SlideshowEditor">
<div id="slideshowEditor">
<h1>Slideshow Editor</h1>
<div class="slideshowsGroup" style="vertical-align: bottom">
<label class='controlLabel'>Selected Slideshow: </label>
<select name="slideshows">
{{#each slideshows}}
<option value="{{_id}}">{{name}}</option>
{{/each}}
</select>
<input type="text" name="newSlideshowName" class="newSlideshowName form-control"/><i class="fa fa-plus-circle createSlideshow noselect clickable" aria-hidden="true"></i>{{#if selectedSlideshow}}<i class="fa fa-trash deleteSlideshow noselect clickable" aria-hidden="true"></i>{{/if}}
</div>
{{#if selectedSlideshow}}
{{> SlideshowContentEditor selectedSlideshow._id}}
{{/if}}
</div>
</template>
<template name="SlideshowContentEditor">
<div id="slideshowContentEditor">
{{#if showSelectImageDialog}}
<div class="modalBackground">
{{> SelectImageDialog (selectImageDialogArgs)}}
</div>
{{/if}}
{{#each slides}}
<div class="slideThumbnail" style="background-image: url('/slideshow-image/{{this}}')" data-image-id="{{this}}">
<div class="controls">
<i class="fa fa-angle-double-left moveLeft noselect clickable" aria-hidden="true">
</i><i class="fa fa-times removeThumbnail noselect clickable" aria-hidden="true">
</i><i class="fa fa-angle-double-right moveRight noselect clickable" aria-hidden="true"></i>
</div>
</div>
<!--<p>{{this}}</p>-->
{{/each}}
<div class="addSlide noselect clickable" style='background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMzAwcHgiIHdpZHRoPSIzMDBweCIgZmlsbD0iIzAwMDAwMCIgdmVyc2lvbj0iMS4xIiB4PSIwcHgiIHk9IjBweCIgdmlld0JveD0iMCAwIDEzOSAxMzkiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDEzOSAxMzkiIHhtbDpzcGFjZT0icHJlc2VydmUiIGlkPSJzdmcxMCI+IDxkZWZzIGlkPSJkZWZzMTQiIC8+PHBhdGggZD0iTTY5LjQ5OSw4LjYxOWMtMzMuNTQzLDAtNjAuODMzLDI3LjI5LTYwLjgzMyw2MC44MzRjMCwzMy41NDUsMjcuMjksNjAuODM1LDYwLjgzMyw2MC44MzUgIGMzMy41NDUsMCw2MC44MzYtMjcuMjksNjAuODM2LTYwLjgzNUMxMzAuMzM1LDM1LjkwOSwxMDMuMDQ0LDguNjE5LDY5LjQ5OSw4LjYxOXogTTY5LjQ5OSwxMjQuMjg4ICBjLTMwLjIzNSwwLTU0LjgzMy0yNC41OTktNTQuODMzLTU0LjgzNWMwLTMwLjIzNSwyNC41OTgtNTQuODM0LDU0LjgzMy01NC44MzRjMzAuMjM2LDAsNTQuODM2LDI0LjU5OSw1NC44MzYsNTQuODM0ICBDMTI0LjMzNSw5OS42ODksOTkuNzM1LDEyNC4yODgsNjkuNDk5LDEyNC4yODh6IiBpZD0icGF0aDIiIC8+PHBhdGggZD0iTSA4NS42NCw5NS42OCA4NC4xMTk3MTgsOTIuNTM1ODQ4IDQyLjYzNiw5Mi42Mzk5NjYgNDIuNjM2LDQ2LjI1NiBIIDk2LjMwMyBWIDcyLjMyIGggMi4zNyA0LjE2MiBWIDQyLjYgYyAwLC0xLjUzNCAtMS4yNDMsLTIuNzc3IC0yLjc3OCwtMi43NzcgSCAzOC45NDUgYyAtMS41MzUsMCAtMi43NzgsMS4yNDMgLTIuNzc4LDIuNzc3IHYgNTMuNzA1IGMgMCwxLjUzNCAxLjI0MywyLjc3NyAyLjc3OCwyLjc3NyBIIDk0LjY3MyBWIDk1LjY4IEggODkuNjQgWiIgaWQ9InBhdGg0Ii8+PHBvbHlnb24gcG9pbnRzPSIxMDQuOTk5LDc2LjMyIDEwMi44MzUsNzYuMzIgOTguNjczLDc2LjMyIDk4LjY3Myw4NS4zNTMgODkuNjQsODUuMzUzIDg5LjY0LDkxLjY4IDk4LjY3Myw5MS42OCA5OC42NzMsOTkuMDgzIDk4LjY3MywxMDAuNzEyIDEwNC45OTksMTAwLjcxMiAxMDQuOTk5LDkxLjY4IDExNC4wMzEsOTEuNjggMTE0LjAzMSw4NS4zNTMgMTA0Ljk5OSw4NS4zNTMgIiBpZD0icG9seWdvbjYiIHRyYW5zZm9ybT0ibWF0cml4KDEuMDM0MTQ4NiwwLDAsMS4wMzQxNDg2LC01LjcwMDk4OCwtMS45NzI4ODk2KSIgLz48Y2lyY2xlIGN4PSI2Mi42MTM1NzkiIGN5PSI2NC42NDAyMjgiIHI9IjUuNzQ4MDAwMSIgaWQ9ImNpcmNsZTgiIHN0eWxlPSJmaWxsOiNmZWM5NDE7ZmlsbC1vcGFjaXR5OjEiIC8+PHBhdGggc3R5bGU9ImZpbGw6IzU0NDgwMDtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6ZXZlbm9kZDtzdHJva2U6bm9uZTtzdHJva2Utd2lkdGg6MC40NjMzMzMzNHB4O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO3N0cm9rZS1vcGFjaXR5OjEiIGQ9Im0gNDcuMDYxNTQ5LDg3LjE0ODk5OSBjIDAsMCA1LjU2NTY4OSwtMjQuMzkzMzg5IDcuODA4ODg2LC0yNC41NzE5NjQgMi4yNDMxOTcsLTAuMTc4NTc1IDcuMzkyNDE0LDkuODkxMjU5IDcuMzkyNDE0LDkuODkxMjU5IDAsMCAyLjI5MTI1MiwtMy44OTYwMTYgMy41NDAwMjksLTMuNTQwMDI5IDEuMjQ4Nzc3LDAuMzU1OTg3IDYuOTcwMTMzLDcuOTQ5ODkgOC4xMjEyNDEsOC42NDE4MzQgMS4xNTExMDgsMC42OTE5NDQgMy41NzYxMjksLTIuNzc5NjQ0IDUuNzI2NTE0LC0zLjEyMzU1NiAyLjE1MDM4NSwtMC4zNDM5MTIgNy40OTY1MzMsOC4wMTcxMjUgNy40OTY1MzMsOC4wMTcxMjUgaCAtMS44NzcyIGwgLTEuNzY2OTQ5LDQuNzg5NDQ2IHoiIGlkPSJwYXRoODIzIi8+PC9zdmc+");'></div>
</div>
</template>

View File

@@ -0,0 +1,108 @@
#slideshowEditor
display: block
select[name='slideshows']
font-size: 1.5em
min-width: 100px
input[name="newSlideshowName"]
display: inline-block
transition: all .75s ease
width: 0
border: 0
opacity: 0
font-size: 1.2em
input[name="newSlideshowName"].show
opacity: 1
border: 1px solid #ccc
border-radius: 4px
width: 200px
transform: translateX(4px)
.createSlideshow
display: inline-block
padding: 2px 6px
margin: 0 4px
//width: 33px
text-align: center
font-size: 1.5em
line-height: 1.5em
border-radius: 8px
//border: 1px solid rgba(0, 0, 0, 0)
box-sizing: border-box
.createSlideshow:hover
//border: 1px inset #b100d1
//-webkit-box-shadow: inset 0px 0px 20px 0px #de7cff
//-moz-box-shadow: inset 0px 0px 20px 0px #de7cff
//box-shadow: inset 0px 0px 20px 0px #de7cff
color: #5c8744
.createSlideshow
transform: translateX(-25px) rotate(0deg)
transition: all .75s ease
.createSlideshow.move
transform: translateX(6px) rotate(720deg)
.deleteSlideshow
display: inline-block
padding: 2px 3px
margin: 0 4px
width: 1.5em
line-height: 1.5em
//width: 33px
text-align: center
font-size: 1.5em
border-radius: 50%
//border: 1px solid #8c0000
box-sizing: border-box
color: black
.deleteSlideshow:hover
color: #8c2353
.addSlide
display:inline-block
width: 200px
height: 200px
background-size: contain
background-repeat: no-repeat
border-radius: 6px
border 3px dashed black
.addSlide:hover
border-color: #5c8744
.slideThumbnail
position: relative
width: 200px
height: 200px
background-position: 50% 50%
background-repeat:no-repeat
background-size: contain
display: inline-block
.controls
position: absolute
bottom: 50%
left: 50%
right: 50%
top: 50%
height: 22px
width: 90px
margin-left: -45px
margin-top: -10px
background-color: rgba(0, 0, 0, .8)
display: none
text-align: center
font-size: 20px
line-height: 20px
border-radius: 8px
border: 1px solid black
.slideThumbnail:hover > .controls, .controls:hover
display: block
.moveLeft, .moveRight, .removeThumbnail
color: white
padding: 0 4px
.moveLeft
padding-right: 14px
.moveRight
padding-left: 14px
.moveLeft:hover, .moveRight:hover
color: #00ff55
.moveLeft:active, .moveRight:active
color: #006e27
.removeThumbnail:hover
color: #ff0019
.removeThumbnail:active
color: #57000c

View File

@@ -0,0 +1,213 @@
import './SlideshowEditor.html';
import '/imports/ui/dialogs/SelectImageDialog.js';
import swal from "sweetalert2";
const PREFIX = "SlideshowEditor_";
Tracker.autorun(function() {
Meteor.subscribe("slideshow");
});
Template.SlideshowEditor.onCreated(function() {
this.slideshows = Meteor.collections.Slideshow.find({}, {sort: {name: 1}});
Session.set(PREFIX + 'selectedSlideshow', null);
});
Template.SlideshowEditor.onRendered(function() {
let _this = this;
Tracker.autorun(function() {
//This is a reactive call, allowing this function to be re-run when the cursor changes.
let slideshows = _this.slideshows.fetch();
if(slideshows.length > 0 && !Session.get(PREFIX + "selectedSlideshow")) {
//Mark the first slideshow as selected because it will be selected in the UI by default.
Session.set(PREFIX + "selectedSlideshow", slideshows[0]);
}
});
});
Template.SlideshowEditor.onDestroyed(function() {
});
Template.SlideshowEditor.helpers({
slideshows: function() {
return Template.instance().slideshows;
},
selectedSlideshow: function(){
return Session.get(PREFIX + "selectedSlideshow");
},
isSlideshowSelected: function() {
let selectedSlideshow = Session.get(PREFIX + "selectedSlideshow");
return selectedSlideshow == this ? "selected" : "";
},
hasSelectedSlideshow: function() {
return Session.get(PREFIX + "selectedSlideshow");
}
});
Template.SlideshowEditor.events({
'change select[name="slideshows"]': function(event, template) {
let slideshowId = $(event.target).val();
let slideshow = Meteor.collections.Slideshow.findOne(slideshowId);
Session.set(PREFIX + 'selectedSlideshow', slideshow);
},
"keyup input[name='newSlideshowName']" : function(event, template) {
if(event.keyCode === 13) {
event.preventDefault();
$('.createSlideshow').trigger('click');
}
},
"click .createSlideshow": function(event, template) {
let $input = template.$('input[name="newSlideshowName"]');
if($input.hasClass('show')) {
let name = $input.val();
name = name ? name.trim() : undefined;
name = name && name.length > 0 ? name : undefined;
if(name) {
Meteor.call('addSlideshow', name, function(error, id) {
if(error) sAlert.error("Failed to create the slideshow!\n" + error);
else {
//Clear the text editor.
$input.val("");
//Quick hack to attempt to allow the slideshow we created to be added to the select box before we try to select it and edit it.
let count = 0;
let interval = setInterval(function() {
let selected = Meteor.collections.Slideshow.findOne(id);
if(selected) {
//Select the sheet in the drop down.
template.$('select[name="slideshows"]').val(id);
Session.set(PREFIX + "selectedSlideshow", selected);
clearInterval(interval);
}
else count++;
//Avoid infinite loop that should never happen.
if(count > 100) clearInterval(interval);
}, 100);
}
});
}
$input.removeClass('show');
$(event.target).toggleClass('move');
template.$('.deleteSlideshow').show();
}
else {
$input.addClass('show');
$(event.target).toggleClass('move');
$input.focus();
template.$('.deleteSlideshow').hide();
}
},
"click .deleteSlideshow": function(event, template) {
let $combo = template.$('select[name="slideshows"]');
let slideshow = Session.get(PREFIX + 'selectedSlideshow');
if(slideshow) {
swal({
title: 'Are you sure?',
text: "You won't be able to revert this!",
type: 'warning',
showCancelButton: true,
confirmButtonColor: '#3085d6',
cancelButtonColor: '#d33',
confirmButtonText: 'Yes, delete it!'
}).then((result) => {
Meteor.call('removeSlideshow', slideshow._id, function(error, id) {
if(error) sAlert.error("Failed to remove the slideshow!\n" + error);
else {
sAlert.success("Slideshow removed.");
//Fire the event so that our selection changes once the slideshow is removed from the combo.
$combo.trigger('change');
}
});
});
}
}
});
Template.SlideshowContentEditor.onCreated(function() {
this.showSelectImageDialog = new ReactiveVar(false);
});
Template.SlideshowContentEditor.helpers({
showSelectImageDialog: function() {
return Template.instance().showSelectImageDialog.get();
},
slides: function() {
return Meteor.collections.Slideshow.findOne(Template.instance().data).images;
},
selectImageDialogArgs() {
let template = Template.instance();
return {
onApply(data) {
//tinymce.activeEditor.insertContent('<img src="' + value + '"/>');
template.showSelectImageDialog.set(false);
//Send the slide image as a base64 string.
Meteor.call('addSlideshowImage', template.data, data, function(error, id) {
if(error) sAlert.error("Failed to add the slide.\n" + error);
else {
//TODO: ?
}
});
},
onClose() {
template.showSelectImageDialog.set(false);
}
};
}
//getImage(id) {
// Meteor.call('getSlideshowImage', id, function(error, base64)) {
// if(error) sAlert.error("Failed to get the slide image.\n" + error);
// else {
//
// }
// }
//}
});
Template.SlideshowContentEditor.events({
"click .addSlide": function(event, template) {
template.showSelectImageDialog.set(true);
},
'click .moveLeft': function(event, template) {
let $slideThumbnail = $(event.target).parent().parent();
let index = $slideThumbnail.index();
//If we can move it to the right then do so.
if(index - 1 >= 0) {
Meteor.call('swapSlideshowImages', template.data, index, index - 1, function(error) {
if(error) sAlert.error("Failed to move the slide.\n" + error);
});
}
},
'click .moveRight': function(event, template) {
let $slideThumbnail = $(event.target).parent().parent();
let index = $slideThumbnail.index();
let thumbnailCount = $slideThumbnail.siblings().length; //Should give us the number of siblings including the add image button (we don't want to count it), and excluding this slide thumbnail (we do want to count it).
//If we can move it to the right then do so.
if(index + 1 < thumbnailCount) {
Meteor.call('swapSlideshowImages', template.data, index, index + 1, function(error) {
if(error) sAlert.error("Failed to move the slide.\n" + error);
});
}
},
'click .removeThumbnail': function(event, template) {
let $slideThumbnail = $(event.target).parent().parent();
let imageId = $slideThumbnail.data("image-id");
Meteor.call('removeSlideshowImage', template.data, imageId, function(error) {
if(error) sAlert.error("Failed to remove the slide.\n" + error);
});
}
});