diff --git a/.meteor/packages b/.meteor/packages
index a4a0d5e..405bbcf 100644
--- a/.meteor/packages
+++ b/.meteor/packages
@@ -6,19 +6,19 @@
meteor-base@1.4.0 # Packages every Meteor app needs to have
mobile-experience@1.0.5 # Packages for a great mobile UX
-mongo@1.5.0 # The database Meteor supports right now
+mongo@1.6.0 # The database Meteor supports right now
blaze-html-templates@1.0.4 # Compile .html files into Meteor Blaze views
reactive-var@1.0.11 # Reactive variable for tracker
-reactive-dict@1.2.0 # ???
+reactive-dict@1.2.1 # ???
tracker@1.2.0 # Meteor's client-side reactive programming library
tomwasd:history-polyfill # Adds IE 8/9 support for HTML5 history.
email@1.2.3 # Adds the Meteor/Email package for sending lost password emails
-standard-minifier-css@1.4.1 # CSS minifier run for production mode
-standard-minifier-js@2.3.4 # JS minifier run for production mode
+standard-minifier-css@1.5.2 # CSS minifier run for production mode
+standard-minifier-js@2.4.0 # JS minifier run for production mode
es5-shim@4.8.0 # ECMAScript 5 compatibility for older browsers.
poorvavyas:es6-shim
-ecmascript@0.11.1 # Enable ECMAScript2015+ syntax in app code
+ecmascript@0.12.3 # Enable ECMAScript2015+ syntax in app code
#accounts-ui
#accounts-base
@@ -31,11 +31,11 @@ alanning:roles # Adds roles to the user mix. https://atmospherejs.com/alanni
kadira:flow-router
arillo:flow-router-helpers # Provides various template helpers such as {{pathFor 'templateName'}}
#tomwasd:flow-router-seo
-kadira:blaze-layout
+kadira:blaze-layout #TODO: Remove?
-shell-server@0.3.1 # ???
+shell-server@0.4.0 # ???
meteortoys:allthings
-session@1.1.7
+session@1.2.0
##browser-policy # Adds support for specifying browser level security rules related to content and what's allowed to laod on the page.
check@1.3.1 # Allows for checking the structure and types of arguments passed to Meteor methods and publications.
#audit-argument-checks # Used in combination with the Check package for checking the structure and types of arguments passed to Meteor methods and publications. Automatically alerts when a method or publication does not use a check() call.
@@ -60,10 +60,16 @@ juliancwirko:s-alert # Client error/alert handling
jcbernack:reactive-aggregate # Allows us to create a new client collection (from the server) with the contents being an aggregate of server data. Note that aggregation can only be done on the server currently as mini-mongo does not support it.
ostrio:logger
ostrio:loggermongo
-dynamic-import@0.4.2
+dynamic-import@0.5.0
markdown@1.0.12
wcrisman:jquery-custom-scrollbar
+wcrisman:server-side-seo # A custom plugin to take all HTML from the app, and place the templates in a map in a file in the private folder for use at runtime to generate search engine readable html. See code in the /packages folder for this project.
stylus@=2.513.14 # This package is no longer supported, but it still works and is available. It provides support for reading .styl files on the client and converting them to CSS on the fly. The alternative would be to compile the styl files to CSS on the server ahead of time.
underscore@1.0.10
aldeed:schema-index
+ostrio:files
+meteorhacks:picker
+meteorhacks:ssr
+ostrio:meteor-root
+manuel:reactivearray
diff --git a/.meteor/release b/.meteor/release
index 4c71956..2299ae7 100644
--- a/.meteor/release
+++ b/.meteor/release
@@ -1 +1 @@
-METEOR@1.7.0.5
+METEOR@1.8.0.1
diff --git a/.meteor/versions b/.meteor/versions
index 102064c..6d2e0ef 100644
--- a/.meteor/versions
+++ b/.meteor/versions
@@ -1,21 +1,21 @@
-accounts-base@1.4.2
+accounts-base@1.4.3
accounts-password@1.5.1
alanning:roles@1.2.16
-aldeed:collection2@3.0.0
+aldeed:collection2@3.0.1
aldeed:schema-index@3.0.0
aldeed:template-extension@4.1.0
allow-deny@1.1.0
arillo:flow-router-helpers@0.5.2
-autoupdate@1.4.1
-babel-compiler@7.1.1
-babel-runtime@1.2.5
+autoupdate@1.5.0
+babel-compiler@7.2.3
+babel-runtime@1.3.0
base64@1.0.11
-binary-heap@1.0.10
+binary-heap@1.0.11
blaze@2.3.3
blaze-html-templates@1.1.2
blaze-tools@1.0.10
-boilerplate-generator@1.5.0
-caching-compiler@1.1.12
+boilerplate-generator@1.6.0
+caching-compiler@1.2.1
caching-html-compiler@1.1.3
callback-hook@1.1.0
check@1.3.1
@@ -26,22 +26,24 @@ ddp-common@1.4.0
ddp-rate-limiter@1.0.7
ddp-server@2.2.0
deps@1.0.12
-diff-sequence@1.1.0
-dynamic-import@0.4.2
-ecmascript@0.11.1
+diff-sequence@1.1.1
+dynamic-import@0.5.1
+ecmascript@0.12.3
ecmascript-runtime@0.7.0
-ecmascript-runtime-client@0.7.2
+ecmascript-runtime-client@0.8.0
ecmascript-runtime-server@0.7.1
ejson@1.1.0
email@1.2.3
es5-shim@4.8.0
+fetch@0.1.0
fortawesome:fontawesome@4.7.0
geojson-utils@1.0.10
hot-code-push@1.0.4
html-tools@1.0.11
htmljs@1.0.11
-http@1.4.1
+http@1.4.2
id-map@1.1.0
+inter-process-messaging@0.1.0
jcbernack:reactive-aggregate@1.0.0
jquery@1.11.11
juliancwirko:s-alert@3.2.0
@@ -51,9 +53,12 @@ launch-screen@1.1.1
livedata@1.0.18
localstorage@1.2.0
logging@1.1.20
+manuel:reactivearray@1.0.9
markdown@1.0.12
meteor@1.9.2
meteor-base@1.4.0
+meteorhacks:picker@1.0.3
+meteorhacks:ssr@2.2.0
meteortoys:allthings@7.0.1
meteortoys:authenticate@4.0.0
meteortoys:autopub@4.0.0
@@ -71,27 +76,31 @@ meteortoys:sub@4.0.0
meteortoys:throttle@4.0.0
meteortoys:toggle@4.0.0
meteortoys:toykit@4.0.2
-minifier-css@1.3.1
-minifier-js@2.3.5
-minimongo@1.4.4
+minifier-css@1.4.1
+minifier-js@2.4.0
+minimongo@1.4.5
mizzao:bootboxjs@4.4.0
mobile-experience@1.0.5
mobile-status-bar@1.0.14
-modern-browsers@0.1.2
-modules@0.12.2
-modules-runtime@0.10.2
+modern-browsers@0.1.3
+modules@0.13.0
+modules-runtime@0.10.3
momentjs:moment@2.22.2
-mongo@1.5.1
+mongo@1.6.0
+mongo-decimal@0.1.0
mongo-dev-server@1.1.0
mongo-id@1.0.7
msavin:jetsetter@4.0.0
msavin:mongol@7.0.1
npm-bcrypt@0.9.3
-npm-mongo@3.0.11
+npm-mongo@3.1.1
observe-sequence@1.0.16
ordered-dict@1.1.0
+ostrio:cookies@2.3.0
+ostrio:files@1.10.2
ostrio:logger@2.0.7
ostrio:loggermongo@2.0.4
+ostrio:meteor-root@1.0.7
poorvavyas:es6-shim@0.21.1
promise@0.11.1
raix:eventemitter@0.1.3
@@ -101,18 +110,18 @@ reactive-dict@1.2.1
reactive-var@1.0.11
reload@1.2.0
retry@1.1.0
-routepolicy@1.0.13
+routepolicy@1.1.0
service-configuration@1.0.11
-session@1.1.8
+session@1.2.0
sha@1.0.9
-shell-server@0.3.1
+shell-server@0.4.0
socket-stream-client@0.2.2
softwarerero:accounts-t9n@1.3.11
spacebars@1.0.15
spacebars-compiler@1.1.3
srp@1.0.12
-standard-minifier-css@1.4.1
-standard-minifier-js@2.3.4
+standard-minifier-css@1.5.2
+standard-minifier-js@2.4.0
stylus@2.513.14
templating@1.3.2
templating-compiler@1.3.3
@@ -128,6 +137,7 @@ useraccounts:core@1.14.2
useraccounts:flow-routing@1.14.2
useraccounts:unstyled@1.14.2
wcrisman:jquery-custom-scrollbar@3.0.0
-webapp@1.6.2
+wcrisman:server-side-seo@1.0.0
+webapp@1.7.2
webapp-hashing@1.0.9
zimme:active-route@2.3.2
diff --git a/client/client.js b/client/client.js
index e402767..a8e94c6 100644
--- a/client/client.js
+++ b/client/client.js
@@ -3,7 +3,6 @@ import '/imports/startup/client';
import '/imports/startup/both';
import '/imports/api';
import '/imports/ui/helpers.js';
-// import '/imports/util/normalize.css';
import '/imports/util/validator.js';
import '/imports/util/polyfills/blaze.js';
import '/imports/util/polyfills/regex.js';
@@ -22,17 +21,29 @@ import '/imports/util/select2/select2.full.js';
import 'sweetalert2/dist/sweetalert2.min.css';
import '/imports/util/simplegrid.css';
import 'dragula/dist/dragula.css';
-//import 'malihu-custom-scrollbar-plugin/jquery.mCustomScrollbar.css';
-
+//Import all the tinyMCE code, including all the plugins we are using. If one plugin is forgotten it may result in a unexpected '>' error in the console, or it may just not function at all.
import '/imports/util/tinymce/tinymce.min.js';
import '/imports/util/tinymce/jquery.tinymce.min.js';
import '/imports/util/tinymce/themes/modern/theme.min.js';
+import '/imports/util/tinymce/themes/inlite/theme.min.js';
+import '/imports/util/tinymce/plugins/autolink/plugin.min.js';
+import '/imports/util/tinymce/plugins/contextmenu/plugin.min.js';
+import '/imports/util/tinymce/plugins/link/plugin.min.js';
+import '/imports/util/tinymce/plugins/lists/plugin.min.js';
+import '/imports/util/tinymce/plugins/table/plugin.min.js';
+import '/imports/util/tinymce/plugins/textcolor/plugin.min.js';
+//Replaces the standard toDataURL() function in canvas to support PNG compression.
+import 'canvas-png-compression/dist/bundle.js';
+//Set some tinyMCE basic settings specific to our Meteor project structure.
tinymce.baseURL='/tinymce';
tinymce.suddix='.min';
Blaze._allowJavascriptUrls();
+//Turn on the canvas toDataURL() conversion to allow PNG compression.
+CanvasPngCompression.replaceToDataURL();
+
Meteor.startup(function () {
sAlert.config({
diff --git a/client/main.styl b/client/main.styl
index ef21330..c1a19de 100644
--- a/client/main.styl
+++ b/client/main.styl
@@ -44,9 +44,38 @@ h3
.textView
border: 1px solid #DDD
- padding: 6px 20px
+ padding: 10px
+ font-family: Palatino, "Palatino Linotype", "Palatino LT STD", "Book Antiqua", Georgia, serif
+ font-size: 16px
+ line-height: 27.4286px
+ max-width: 960px
+ ul
+ list-style-type: disc
+ margin-block-end: 14px
+ margin-block-start: 14px
+ margin-inline-end: 0
+ margin-inline-start: 0
+ padding-inline-start: 40px
+ li
+ font-family: Palatino, "Palatino Linotype", "Palatino LT STD", "Book Antiqua", Georgia, serif
+ font-size: 16px
+ line-height: 27.4286px
+ ol
+ list-style-type: decimal
+ margin-block-end: 14px
+ margin-block-start: 14px
+ margin-inline-end: 0
+ margin-inline-start: 0
+ padding-inline-start: 40px
+ li
+ font-family: Palatino, "Palatino Linotype", "Palatino LT STD", "Book Antiqua", Georgia, serif
+ font-size: 16px
+ line-height: 27.4286px
+ padding-left: 8px
//Standard Stylings
+.hidden
+ display: none
.noselect
-webkit-touch-callout: none; /* iOS Safari */
-webkit-user-select: none; /* Chrome/Safari/Opera */
@@ -152,6 +181,7 @@ h3
@import "../imports/ui/styles/maxHeightLayout.import.styl"
@import "../imports/ui/styles/tabs.import.styl"
@import "../imports/ui/styles/forms.import.styl"
+@import "../imports/ui/styles/modal.import.styl"
@import "../imports/util/de.combo.import.styl"
@import "../imports/util/bootstrap-like-btn.import.styl"
@@ -162,14 +192,12 @@ h3
@import "../imports/ui/Home.import.styl"
-@import "../imports/ui/InternshipJobs.import.styl"
@import "../imports/ui/Admin/UserManagement.import.styl"
-@import "../imports/ui/Admin/AppreciationEditor.import.styl"
-@import "../imports/ui/Admin/DatesEditor.import.styl"
-@import "../imports/ui/Admin/InternshipsEditor.import.styl"
-@import "../imports/ui/Admin/NewsEditor.import.styl"
-@import "../imports/ui/Admin/BoardEditor.import.styl"
+@import "../imports/ui/Admin/InternshipEditor.import.styl"
+@import "../imports/ui/Admin/PageEditor.import.styl"
+@import "../imports/ui/Admin/SlideshowEditor.import.styl"
+@import "../imports/ui/InternshipJobs.import.styl"
@import "../imports/ui/Grants.import.styl"
@import "../imports/ui/Internships.import.styl"
@import "../imports/ui/Scholarships.import.styl"
@@ -184,3 +212,7 @@ h3
@import "../imports/ui/Contact.import.styl"
@import "../imports/ui/Programs.import.styl"
@import "../imports/ui/ImportantDates.import.styl"
+
+@import "../imports/ui/EditablePage.import.styl"
+
+@import "../imports/ui/dialogs/SelectImageDialog.import.styl"
diff --git a/imports/api/Images.js b/imports/api/Images.js
new file mode 100644
index 0000000..6e6d77a
--- /dev/null
+++ b/imports/api/Images.js
@@ -0,0 +1,11 @@
+import { Meteor } from 'meteor/meteor';
+import { Mongo } from 'meteor/mongo';
+import { check } from 'meteor/check';
+import SimpleSchema from 'simpl-schema';
+import {FilesCollection} from 'meteor/ostrio:files';
+
+const ImageUploads = new FilesCollection({
+ storagePath: 'public/uploads'
+});
+
+export default ImageUploads;
diff --git a/imports/api/Internship.js b/imports/api/Internship.js
new file mode 100644
index 0000000..0e1435b
--- /dev/null
+++ b/imports/api/Internship.js
@@ -0,0 +1,74 @@
+import { Meteor } from 'meteor/meteor';
+import { Mongo } from 'meteor/mongo';
+import { check } from 'meteor/check';
+import SimpleSchema from 'simpl-schema';
+
+let Internship = new Mongo.Collection('Internship');
+
+Internship.attachSchema(new SimpleSchema({
+ name: {
+ type: String,
+ label: "Name",
+ optional: false,
+ trim: true,
+ index: 1,
+ unique: true
+ },
+ content: {
+ type: String,
+ label: "Content",
+ optional: false,
+ trim: false
+ },
+ createdAt: {
+ type: Date,
+ label: "Created On",
+ optional: true
+ },
+ updatedAt: {
+ type: Date,
+ label: "Updated On",
+ optional: true
+ }
+}));
+
+if(Meteor.isServer) Meteor.publish('Internship', function() {
+ return Internship.find({});
+});
+
+if(Meteor.isServer) {
+ Meteor.methods({
+ addInternship: function(name, content) {
+ check(name, String);
+ check(content, String);
+ if(Roles.userIsInRole(this.userId, [Meteor.UserRoles.ROLE_UPDATE])) {
+ const createdAt = new Date();
+ const updatedAt = createdAt;
+
+ //Returns the id of the object created.
+ return Internship.insert({name, content, createdAt, updatedAt});
+ }
+ else throw new Meteor.Error(403, "Not authorized.");
+ },
+ removeInternship: function(_id) {
+ check(_id, String);
+
+ if(Roles.userIsInRole(this.userId, [Meteor.UserRoles.ROLE_UPDATE])) {
+ Internship.remove(_id);
+ }
+ else throw new Meteor.Error(403, "Not authorized.");
+ },
+ updateInternship: function(_id, content) {
+ check(_id, String);
+ check(content, String);
+ if(Roles.userIsInRole(this.userId, [Meteor.UserRoles.ROLE_UPDATE])) {
+ const updatedAt = new Date();
+
+ return Internship.update(_id, {$set: {content, updatedAt}});
+ }
+ else throw new Meteor.Error(403, "Not authorized.");
+ }
+ });
+}
+
+export default Internship;
diff --git a/imports/api/Slideshow.js b/imports/api/Slideshow.js
new file mode 100644
index 0000000..ea00a21
--- /dev/null
+++ b/imports/api/Slideshow.js
@@ -0,0 +1,137 @@
+import { Meteor } from 'meteor/meteor';
+import { Mongo } from 'meteor/mongo';
+import { check } from 'meteor/check';
+import SimpleSchema from 'simpl-schema';
+
+let Slideshow = new Mongo.Collection('Slideshow');
+let SlideshowImage = new Mongo.Collection("SlideshowImage");
+
+Slideshow.attachSchema(new SimpleSchema({
+ name: {
+ type: String,
+ label: "Name",
+ optional: false,
+ trim: true,
+ index: 1,
+ unique: true
+ },
+ images: { //A JSON array of image URL's.
+ type: Array,
+ label: "Images",
+ optional: false,
+ defaultValue: []
+ },
+ 'images.$': {
+ type: String
+ },
+ createdAt: {
+ type: Date,
+ label: "Created On",
+ optional: true
+ },
+ updatedAt: {
+ type: Date,
+ label: "Updated On",
+ optional: true
+ }
+}));
+
+SlideshowImage.attachSchema(new SimpleSchema({
+ image: {
+ type: String,
+ label: "Image",
+ optional: false,
+ trim: false
+ }
+}));
+
+if(Meteor.isServer) Meteor.publish('slideshow', function() {
+ return Slideshow.find({});
+});
+
+if(Meteor.isServer) {
+ WebApp.connectHandlers.use("/slideshow-image/", (req, res, next) => {
+ try {
+ let id = req.url.substr(1);
+
+ let slide = SlideshowImage.findOne(id);
+
+ if(slide) {
+ let i = slide.image.indexOf(";base64,");
+ let header = slide.image.substr(5, i);
+ let content = slide.image.substr(i + 8);
+ let buffer = new Buffer(content, 'base64');
+
+ res.writeHead(200, {'Content-Type': header});
+ res.write(buffer);
+ res.end();
+ }
+ else {
+ //TODO: Write out a bad image picture.
+ }
+ } catch(err) {
+ console.log(err);
+ res.end();
+ }
+ });
+
+ Meteor.methods({
+ addSlideshowImage: function(slideshowId, image) {
+ check(slideshowId, String);
+ check(image, String);
+
+ if(Roles.userIsInRole(this.userId, [Meteor.UserRoles.ROLE_UPDATE])) {
+ let id = SlideshowImage.insert({image});
+
+ if(id) {
+ Slideshow.update(slideshowId, {$push: {images: id}});
+ }
+ else {
+ throw new Meteor.Error(400, "Image storage failed.");
+ }
+ }
+ else throw new Meteor.Error(403, "Not authorized.");
+ },
+ removeSlideshowImage: function(slideshowId, imageId) {
+ check(slideshowId, String);
+ check(imageId, String);
+
+ //Note: there is currently no way in mongo to remove an element at an index.
+ Slideshow.update(slideshowId, {$pull: {images: imageId}});
+ },
+ swapSlideshowImages: function(slideshowId, firstIndex, secondIndex) {
+ check(slideshowId, String);
+ check(firstIndex, Number);
+ check(secondIndex, Number);
+
+ let slideshow = Slideshow.findOne(slideshowId);
+ let temp = slideshow.images[firstIndex];
+
+ slideshow.images[firstIndex] = slideshow.images[secondIndex];
+ slideshow.images[secondIndex] = temp;
+
+ Slideshow.update(slideshowId, {$set: {images: slideshow.images}});
+ },
+ addSlideshow: function(name) {
+ check(name, String);
+ if(Roles.userIsInRole(this.userId, [Meteor.UserRoles.ROLE_UPDATE])) {
+ const createdAt = new Date();
+ const updatedAt = createdAt;
+ const images = [];
+
+ //Returns the id of the object created.
+ return Slideshow.insert({name, images, createdAt, updatedAt});
+ }
+ else throw new Meteor.Error(403, "Not authorized.");
+ },
+ removeSlideshow: function(_id) {
+ check(_id, String);
+ if(Roles.userIsInRole(this.userId, [Meteor.UserRoles.ROLE_UPDATE])) {
+ Slideshow.remove({_id});
+ }
+ else throw new Meteor.Error(403, "Not authorized.");
+ }
+ });
+}
+
+export default Slideshow;
diff --git a/imports/api/index.js b/imports/api/index.js
index d94247c..a7d62e5 100644
--- a/imports/api/index.js
+++ b/imports/api/index.js
@@ -2,10 +2,13 @@
import Users from "./User.js";
import UserRoles from "./Roles.js";
import Pages from "./Page.js";
+import ImageUploads from "./Images.js";
+import Slideshow from "./Slideshow.js";
+import Internship from "./Internship.js";
import ContactUsMessages from "./ContactUsMessages.js";
//Save the collections in the Meteor.collections property for easy access without name conflicts.
-Meteor.collections = {Users, UserRoles, Pages, ContactUsMessages};
+Meteor.collections = {Users, UserRoles, Pages, Slideshow, Internship, ContactUsMessages, ImageUploads};
//If this is the server then setup the default admin user if none exist.
if(Meteor.isServer) {
diff --git a/imports/startup/both/accounts.js b/imports/startup/both/accounts.js
index e64837c..49f82c9 100644
--- a/imports/startup/both/accounts.js
+++ b/imports/startup/both/accounts.js
@@ -11,7 +11,7 @@ AccountsTemplates.configure({
// defaultLayout: 'Body',
// defaultContentRegion: 'content',
// defaultLayoutRegions: {}
- homeRoutePath: '/Admin/InternshipJobs',
+ homeRoutePath: '/Admin/InternshipJobs', //The path where the user is taken after logging in successfully.
texts: {
title: {
signIn: ""
diff --git a/imports/startup/client/routes.js b/imports/startup/client/routes.js
index 2731f01..557dc73 100644
--- a/imports/startup/client/routes.js
+++ b/imports/startup/client/routes.js
@@ -31,43 +31,57 @@ AccountsTemplates.configureRoute('forgotPwd', {
pri.route("/Admin/Internships", {
name: "InternshipsEditor",
action: function(params, queryParams) {
- require("/imports/ui/Admin/InternshipsEditor.js");
- BlazeLayout.render("Admin", {content: "InternshipsEditor"})
+ require("/imports/ui/Admin/InternshipEditor.js");
+ BlazeLayout.render("Admin", {content: "InternshipEditor"});
}
});
pri.route("/Admin/Board", {
name: "BoardEditor",
action: function(params, queryParams) {
- require("/imports/ui/Admin/BoardEditor.js");
- BlazeLayout.render("Admin", {content: "BoardEditor"})
+ require("/imports/ui/Admin/PageEditor.js");
+ BlazeLayout.render("Admin", {content: "PageEditor"});
}
});
pri.route("/Admin/Dates", {
name: "DatesEditor",
action: function(params, queryParams) {
- require("/imports/ui/Admin/DatesEditor.js");
- BlazeLayout.render("Admin", {content: "DatesEditor"})
+ require("/imports/ui/Admin/PageEditor.js");
+ BlazeLayout.render("Admin", {content: "PageEditor"});
}
});
pri.route("/Admin/UserManagement", {
name: "UserManagement",
action: function(params, queryParams) {
require("/imports/ui/Admin/UserManagement.js");
- BlazeLayout.render("Admin", {content: "UserManagement"})
+ BlazeLayout.render("Admin", {content: "UserManagement"});
}
});
pri.route("/Admin/Appreciation", {
name: "AppreciationEditor",
action: function(params, queryParams) {
- require("/imports/ui/Admin/AppreciationEditor.js");
- BlazeLayout.render("Admin", {content: "AppreciationEditor"})
+ require("/imports/ui/Admin/PageEditor.js");
+ BlazeLayout.render("Admin", {content: "PageEditor"});
}
});
pri.route("/Admin/NewsEditor", {
name: "NewsEditor",
action: function(params, queryParams) {
- require("/imports/ui/Admin/NewsEditor.js");
- BlazeLayout.render("Admin", {content: "NewsEditor"})
+ require("/imports/ui/Admin/PageEditor.js");
+ BlazeLayout.render("Admin", {content: "PageEditor"});
+ }
+});
+pri.route("/Admin/SlideshowEditor", {
+ name: "SlideshowEditor",
+ action: function(params, queryParams) {
+ require("/imports/ui/Admin/SlideshowEditor.js");
+ BlazeLayout.render("Admin", {content: "SlideshowEditor"});
+ }
+});
+pri.route("/Admin/SlideshowPageEditor", {
+ name: "SlideshowPageEditor",
+ action: function(params, queryParams) {
+ require("/imports/ui/Admin/PageEditor.js");
+ BlazeLayout.render("Admin", {content: "PageEditor"});
}
});
@@ -85,8 +99,8 @@ pub.route("/Home", {
pub.route("/ImportantDates", {
name: 'ImportantDates',
action: function(params, queryParams) {
- require("/imports/ui/ImportantDates.js");
- BlazeLayout.render("Public", {content: "ImportantDates"});
+ require("/imports/ui/EditablePage.js");
+ BlazeLayout.render("Public", {content: "EditablePage"});
}
});
pub.route("/Support", {
@@ -131,6 +145,20 @@ pub.route("/Internships", {
BlazeLayout.render("Public", {content: "Internships"});
}
});
+pub.route("/Internship-Job-List", {
+ name: 'Internships Job List',
+ action: function(params, queryParams) {
+ require("/imports/ui/InternshipJobs.js");
+ BlazeLayout.render("Public", {content: "InternshipJobs"});
+ }
+});
+pub.route("/Internship-Job/:_id", {
+ name: 'Internship',
+ action: function(params, queryParams) {
+ require("/imports/ui/InternshipJobs.js");
+ BlazeLayout.render("Public", {content: "InternshipJob"});
+ }
+});
pub.route("/Scholarships", {
name: 'Scholarships',
action: function(params, queryParams) {
@@ -145,11 +173,11 @@ pub.route("/Fellowships", {
BlazeLayout.render("Public", {content: "Fellowships"});
}
});
-pub.route("/News&Notices", {
- name: 'News&Notices',
+pub.route("/News", {
+ name: 'News',
action: function(params, queryParams) {
- require("/imports/ui/News&Notices.js");
- BlazeLayout.render("Public", {content: "NewsNotices"});
+ require("/imports/ui/EditablePage.js");
+ BlazeLayout.render("Public", {content: "EditablePage"});
}
});
pub.route("/PhotoGallery", {
@@ -162,8 +190,8 @@ pub.route("/PhotoGallery", {
pub.route("/Appreciation", {
name: 'Appreciation',
action: function(params, queryParams) {
- require("/imports/ui/Appreciation.js");
- BlazeLayout.render("Public", {content: "Appreciation"});
+ require("/imports/ui/EditablePage.js");
+ BlazeLayout.render("Public", {content: "EditablePage"});
}
});
pub.route("/HowCanYouHelp", {
@@ -176,7 +204,7 @@ pub.route("/HowCanYouHelp", {
pub.route("/CurrentBoard", {
name: 'CurrentBoard',
action: function(params, queryParams) {
- require("/imports/ui/CurrentBoard.js");
- BlazeLayout.render("Public", {content: "CurrentBoard"});
+ require("/imports/ui/EditablePage.js");
+ BlazeLayout.render("Public", {content: "EditablePage"});
}
-});
+});
\ No newline at end of file
diff --git a/imports/ui/Admin/AppreciationEditor.html b/imports/ui/Admin/AppreciationEditor.html
deleted file mode 100644
index 2cee5f1..0000000
--- a/imports/ui/Admin/AppreciationEditor.html
+++ /dev/null
@@ -1,7 +0,0 @@
-
- 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.Appreciation Editor
-
-
- Current Board Editor
-
-
- Important Dates Editor
-
-
- Instructions
+
Job Title:
\nSupervisor / Sponsor:
\nLocation of Internship:
\nDates & hours:
\nDuties & Activities:
\nDesirable Qualities / Skills:
"; + //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('
+
High School Juniors looking for an exciting and challenging summer experience may be motivated to apply for an AVEF Summer Fellowship. AVEF will support fees, transportation, living expenses and incidentals for a program selected by the student. Students should begin by analyzing their interests, aptitudes and passions to guide them in finding a summer program. The program of interest can be located within the United States or even abroad. The program of interest must have an educational component.
Students must complete the AVEF application before the deadline and interview with our selection committee. This program is competitive and open to current juniors only. The Fellowship Application Form is available below.
@@ -26,7 +26,7 @@
+
Every year AVEF provides financial support to teachers, students, parents, mentors or community groups for a wide variety of projects. The purpose of this program is to enhance the educational opportunities available to the youth of Anderson Valley. All applications for grants must be received in writing. Guidelines for grant requests are below. The AVEF board reviews and responds to these requests at its monthly meeting.
Any community member - teacher, student, parent, mentor or community group - may submit a proposal for an AVEF grant. Grant applications are accepted throughout the year. Grant applications can be mailed to AVEF Box 242, Boonville, CA 95415 or can be given to any board member to present at our meeting.
diff --git a/imports/ui/Home.html b/imports/ui/Home.html index a34a4ec..38adcca 100644 --- a/imports/ui/Home.html +++ b/imports/ui/Home.html @@ -21,7 +21,7 @@ - + diff --git a/imports/ui/InternshipJobs.html b/imports/ui/InternshipJobs.html index a0bf318..343c9f0 100644 --- a/imports/ui/InternshipJobs.html +++ b/imports/ui/InternshipJobs.html @@ -1,5 +1,16 @@ -
+
This is a chance for high school students to try something new and gain job experience. Every year AVEF supports a number of summer work opportunities for current high school students. Local businesses and individuals who agree to mentor a student supply job descriptions for their specific needs. These job descriptions will be available for students to review in March of each year. Students are selected by the mentor on the basis of the student's application and interview. The Internship Job List and Internship Student Application Form are available below. Students who successfully complete the required 80 hours of work are paid.
- The purpose of this Photo Gallery is to share and celebrate the activities of Anderson Valley's students and the community that supports them. Since many student events occur either within school classrooms or outside of the valley during field trips, they are often known only to those who are directly involved. Through this Photo Gallery we hope to overcome this barrier and enhance the connections between all members of our community. The photos included in the gallery will change periodically. The goal is to capture current or recent events. We encourage all members of the community to contact us with ideas and images for this section of the web site. The photos have been divided into three categories. Selection of one of the following links will launch a slide show. Arrows on the page provide control of the show.
+
+
Each June AVEF awards scholarships to AVHS graduating seniors who have applied to attend a college, university or vocational school. Scholarships are awarded on the basis of GPA, SAT scores, financial need, educational and personal goals and quality of the essay submitted with an application. In addition references, awards, work experience, community service, and other criteria are taken into account. The Scholarship Application Form is available below. The Scholarship Selection Criteria form used by the Scholarship Committee to evaluate applicants is also available below. In order to qualify a student's application and all supporting documents and letters must be received by AVEF on or before the deadline listed under the "Important Dates" tab on this web site.