<commit_before>'use strict';

//require
let Ajv = require('ajv');
let ajv = Ajv({
  verbose: true,
  allErrors: true
    //v5: true  //enable v5 proposal of JSON-schema standard
}); // options can be passed, e.g. {allErrors: true}

//build schema
const objectid = {
  type: 'string',
  maxLength: 24,
  minLength: 24
};
const contributer = {
  type: 'object',
  properties: {
    id: objectid,
    name: {
      type: 'string'
    }
  },
  required: ['id']
};
const slideRevision = {
  type: 'object',
  properties: {
    id: { //increment with every new revision
      type: 'number',
      minimum: 1
    },
    title: {
      type: 'string'
    },
    timestamp: {
      type: 'string'
    },
    content: {
      type: 'string'
    },
    user: objectid,
    parent: {
      type: 'object'
    }, //ObjectId or Number or both
    popularity: {
      type: 'number',
      minimum: 0
    },
    comment: {
      type: 'string'
    },
    note: {
      type: 'string'
    },
    license: {
      type: 'string',
      enum: ['CC0', 'CC BY', 'CC BY-SA']
    },
    translation: {
      type: 'object',
      properties: {
        status: {
          type: 'string',
          enum: ['original', 'google', 'revised']
        },
        translator: objectid,
        source: {
          type: 'object'
        }
      }
    },
    tags: {
      type: 'array',
      items: {
        type: 'string'
      }
    },
    media: {
      type: 'array',
      items: objectid
    },
    dataSources: {
      type: 'array',
      items: objectid
    }
  },
  required: ['id', 'timestamp', 'user', 'license']
};
const slide = {
  type: 'object',
  properties: {
    user: objectid,
    description: {
      type: 'string'
    },
    language: {
      type: 'string'
    },
    translation: {
      source: 'object'
    },
    deck: objectid,
    position: {
      type: 'number',
      minimum: 1
    },
    timestamp: {
      type: 'string'
    },
    revisions: {
      type: 'array',
      items: slideRevision
    },
    contributers: {
      type: 'array',
      items: {
        oneOf: [
          contributer
        ]
      }
    },
    tags: {
      type: 'array',
      items: {
        type: 'string'
      }
    }
  },
  required: ['user', 'deck', 'timestamp']
};

//export
module.exports = ajv.compile(slide);
<commit_msg>SWIK-104 Create Deck/Slide Service<commit_after>'use strict';

//require
let Ajv = require('ajv');
let ajv = Ajv({
  verbose: true,
  allErrors: true
    //v5: true  //enable v5 proposal of JSON-schema standard
}); // options can be passed, e.g. {allErrors: true}

//build schema
const objectid = {
  type: 'string',
  maxLength: 1,
  minLength: 24
};
const contributer = {
  type: 'object',
  properties: {
    id: objectid,
    name: {
      type: 'string'
    }
  },
  required: ['id']
};
const slideRevision = {
  type: 'object',
  properties: {
    id: { //increment with every new revision
      type: 'number',
      minimum: 1
    },
    title: {
      type: 'string'
    },
    timestamp: {
      type: 'string'
    },
    content: {
      type: 'string'
    },
    user: objectid,
    parent: {
      type: 'object'
    }, //ObjectId or Number or both
    popularity: {
      type: 'number',
      minimum: 0
    },
    comment: {
      type: 'string'
    },
    note: {
      type: 'string'
    },
    license: {
      type: 'string',
      enum: ['CC0', 'CC BY', 'CC BY-SA']
    },
    translation: {
      type: 'object',
      properties: {
        status: {
          type: 'string',
          enum: ['original', 'google', 'revised']
        },
        translator: objectid,
        source: {
          type: 'object'
        }
      }
    },
    tags: {
      type: 'array',
      items: {
        type: 'string'
      }
    },
    media: {
      type: 'array',
      items: objectid
    },
    dataSources: {
      type: 'array',
      items: objectid
    }
  },
  required: ['id', 'timestamp', 'user', 'license']
};
const slide = {
  type: 'object',
  properties: {
    user: objectid,
    description: {
      type: 'string'
    },
    language: {
      type: 'string'
    },
    translation: {
      source: 'object'
    },
    deck: objectid,
    position: {
      type: 'number',
      minimum: 1
    },
    timestamp: {
      type: 'string'
    },
    revisions: {
      type: 'array',
      items: slideRevision
    },
    contributers: {
      type: 'array',
      items: {
        oneOf: [
          contributer
        ]
      }
    },
    tags: {
      type: 'array',
      items: {
        type: 'string'
      }
    }
  },
  required: ['user', 'deck', 'timestamp']
};

//export
module.exports = ajv.compile(slide);
<|endoftext|>


<commit_before>/*global require, module */

require('codemirror/mode/xml/xml');
require('codemirror/mode/javascript/javascript');
require('codemirror/mode/css/css');
require('codemirror/mode/htmlmixed/htmlmixed');
require('codemirror/addon/display/fullscreen')
require('./djangohint')

var $ = require('jquery');
var Backbone = require('backbone');
var _ = require('underscore');
Backbone.$ = $;
var CodeMirror = require('codemirror');

var gettext = gettext || function (text) {
    'use strict';
    return text;
};

var ScribbleEditor = Backbone.View.extend({
    id: 'scribbleEditorContainer',
    tagName: 'div',
    initialize: function () {
        var self = this;
        this.visible = false;
        this.rendering = false;
        this.errorLine = null;
        this.controls = {};
        this.current = {};
        this.needsSave = false;
        this.needsDraft = false;
        this.scribbles = $('.scribble-wrapper.with-controls');
        this.editorOptions =  {
                mode: "text/html",
                tabMode: "indent",
                lineNumbers: true,
                extraKeys: {
                  "F11": function(cm) {
                    cm.setOption("fullScreen", !cm.getOption("fullScreen"));
                    if ($('.CodeMirror-fullscreen').length) {
                      $('#scribbleEditorContainer').addClass("scribbleEditor-fullscreen");
                    }
                    else {
                      $('#scribbleEditorContainer').removeClass("scribbleEditor-fullscreen");
                    }
                  },
                  "Esc": function(cm) {
                    if (cm.getOption("fullScreen")) cm.setOption("fullScreen", false);
                    $('.scribbleEditor-fullscreen').removeClass("scribbleEditor-fullscreen");
                  },
                  'Tab': 'autocomplete'
                }
                };
        CodeMirror.commands.autocomplete = function (editor) {
            CodeMirror.showHint(editor, CodeMirror.djangoHint);
        };
    },
    events: {
        'click .controls .closed': 'close',
        'click .controls .save': 'submitSave',
        'click .controls .draft': 'createDraft',
        'click .controls .discard': 'deleteDraft'
    },
    render: function () {
        var self = this;
        if (this.scribbles.length > 0) {
            this.buildControls();
            $('body').append(this.$el);
            this.editor = new CodeMirror(document.getElementById(this.id), this.editorOptions);
            this.editor.on("change", function (editor, change) {
                self.needsSave = true;
                self.controls.save.removeClass('inactive');
                self.needsDraft = true;
                self.controls.draft.removeClass('inactive');
                self.submitPreview();
            });
            this.editor.selector = this.id;
            // Bind editor to the scribbles
            this.scribbles.each(function (i, elem) {
                // Bind event handlers for each scribble
                $(elem).click(function (e) {
                    // Allow click to follow links inside of scribble content
                    if (e.target.nodeName !== 'A') {
                        self.open($(this));
                    }
                });
            });
        }
    },
    buildControls: function () {
        // Build control bar
        var footerControls = $('

').addClass('controls');
        // Close button
        this.controls.close = $('' + gettext('Close') + '')
            .attr({title: gettext('Close')})
            .addClass('closed');
        // Save button
        this.controls.save = $('' + gettext('Save') + '')
            .attr({title: gettext('Save')})
            .addClass('save inactive');
        this.controls.draft = $('' + gettext('Save Draft') + '')
            .attr({title: gettext('Save as Draft')})
            .addClass('draft inactive');
        this.controls.discard = $('' + gettext('Discard') + '')
            .attr({title: gettext('Discard Draft')})
            .addClass('discard inactive');
        // Error message
        this.controls.errors = $('')
            .addClass('error-msg');
        // Status message
        this.controls.status = $('')
            .addClass('status-msg');
        // Fullscreen instructions
        this.controls.fullscreen = $('
' + gettext('Press ') +
            '' + gettext('F11') + '' +
            gettext(' to enter/exit Fullscreen edit') + '
')
            .addClass('fullscreen')
               footerControls.append(
                   this.controls.status,
                   this.controls.errors,
                   this.controls.close,
                   this.controls.discard,
                   this.controls.draft,
                   this.controls.save,
                   this.controls.fullscreen

        );
        this.$el.append(footerControls);
    },
    open: function (scribble) {
        var self = this;
        if (this.visible) {
            this.close();
        }
        this.current.content = $('.scribble-content.original', scribble);
        this.current.preview = $('.scribble-content.preview', scribble);
        this.current.form = $('.scribble-form', scribble);
        this.current.can_save = this.current.form.data('save');
        this.current.can_delete = this.current.form.data('delete');
        this.$el.show();
        this.trigger('open');
        if (this.current.can_save) {
            this.controls.save.show();
            this.editor.setOption('readOnly', false);
            this.editor.setValue($('[name$=content]', this.current.form).val());
            this.restoreDraft();
        } else {
            this.controls.save.hide();
            this.editor.setOption('readOnly', true);
            this.editor.setValue(gettext('You do not have permission to edit this content.'));
        }
        this.$el.animate({height: '300px'}, 500, function () {self.editor.focus(); });
        this.visible = true;
        // Start background draft saving
        var checkDraft = function () {
            if (self.needsDraft) {
                self.createDraft();
            }
        };
        this.backgroundDraft = setInterval(checkDraft, 3000);
    },
    close: function () {
        this.current.preview.hide();
        this.current.content.show();
        this.current = {};
        this.editor.setValue('');
        this.$el.animate({height: 0}, 500);
        this.visible = false;
        if (this.backgroundDraft) {
            clearInterval(this.backgroundDraft);
        }
        $('#scribbleEditorContainer').removeClass("scribbleEditor-fullscreen");
        $('.CodeMirror.cm-s-default').removeClass("CodeMirror-fullscreen").css("height", "");
        this.trigger('close');
    },
    submitPreview: function (force) {
        var self = this;
        if (this.current.form && (force || (!this.rendering && !this.editor.getOption('readOnly')))) {
            this.rendering = true;
            // Submit the form and display the preview
            $.post(
                this.current.form.attr('action'),
                this.getFormData(),
                function (response) {
                    if (response.valid) {
                        CodeMirror.update_variables(response.variables);
                    }
                    self.renderPreview(response);
                },
                'json'
            ).error(function (jqXHR, textStatus, errorThrown) {
                var msg = 'Server response was "' + errorThrown + '"';
                self.setError(msg);
            }).complete(function () {
                self.rendering = false;
            });
        }
    },
    renderPreview: function (response) {
        var self = this;
        if (this.visible) {
            if (this.errorLine !== null) {
                this.editor.removeLineClass(this.errorLine, "background", "activeline");
            }
            this.controls.errors.html('');
            this.valid = response.valid;
            if (response.valid) {
                this.current.preview.html(response.html);
                this.current.preview.show();
                this.current.content.hide();
                this.controls.save.removeClass('inactive');
            } else {
                this.setError(response.error.message, response.error.line - 1);
            }
        }
    },
    setError: function (msg, line) {
        if (typeof line !== 'undefined' && line !== null) {
            this.errorLine = line;
            this.editor.addLineClass(this.errorLine, "background", "activeline");
        }
        this.controls.errors.html('' + gettext('Error:') + ' ' + msg);
        this.valid = false;
        this.controls.save.addClass('inactive');
    },
    getFormData: function () {
        var result = {},
            prefix = '',
            self = this;
        if (this.current.form) {
            prefix = this.current.form.data('prefix');
            $(':input', this.current.form).each(function (i, input) {
                var inputName = $(input).attr('name').replace(prefix + '-', ''),
                    inputValue = $(input).val();
                if (inputName === 'content') {
                    result[inputName] = self.editor.getValue();
                } else {
                    result[inputName] = inputValue;
                }
            });
        }
        return result;
    },
    submitSave: function () {
        var self = this;
        if (this.current.form && this.valid) {
            // Submit the form and change current content
            $.post(
                this.current.form.data('save'),
                this.getFormData(),
                function (response) {
                    self.renderSave(response);
                },
                'json'
            ).error(function (jqXHR, textStatus, errorThrown) {
                var msg = gettext('Server response was') + '"' + errorThrown + '"';
                self.setError(msg);
            });
        }
        $('#scribbleEditorContainer').removeClass("scribbleEditor-fullscreen");
        $('.CodeMirror.cm-s-default').removeClass("CodeMirror-fullscreen").css("height", "");
    },
    renderSave: function (response) {
        if (response.valid) {
            this.needsSave = false;
            this.controls.save.addClass('inactive');
            this.current.form.data('save', response.url);
            this.current.content.html(this.current.preview.html());
            $('[name$=content]', this.current.form).val(this.editor.getValue());
            this.deleteDraft();
            this.close();
        } else if (response.error) {
            this.setError(response.error.message);
        }
    },
    createDraft: function () {
        var scribble = null,
            path = window.location.pathname,
            slug = '';
        if (this.current.form) {
            // Check for localstorage and fallback to cookie
            scribble = this.editor.getValue();
            slug = this.current.form.data('prefix');
            if (typeof localStorage !== 'undefined' && localStorage !== null) {
                localStorage[path + slug] = scribble;
            } else {
                document.cookie = encodeURIComponent(slug) + '=' + encodeURIComponent(scribble) + ';' + 'path=' + path;
            }
            this.needsDraft = false;
            this.controls.draft.addClass('inactive');
            this.controls.discard.removeClass('inactive');
            this.setStatus(gettext('Draft saved...'));
        }
    },
    restoreDraft: function () {
        var scribble = null,
            path = window.location.pathname,
            slug = '',
            i,
            key,
            value,
            cookies = document.cookie.split(";"),
            cookieName;
        if (this.current.form) {
            // Check for localstorage and fallback to cookie
            slug = this.current.form.data('prefix');
            if (typeof localStorage !== 'undefined' && localStorage !== null) {
                scribble = localStorage[path + slug];
            } else {
                for (i = 0; i < cookies.length; i += 1) {
                    key = cookies[i].substr(0, cookies[i].indexOf("="));
                    value = cookies[i].substr(cookies[i].indexOf("=") + 1);
                    key = decodeURIComponent(key.replace(/\+/g, " "));
                    if (key === cookieName) {
                        scribble = decodeURIComponent(value.replace(/\+/g, " "));
                        break;
                    }
                }
            }
            if (scribble) {
                this.editor.setValue(scribble);
                this.submitPreview(true);
                this.needsDraft = false;
                this.controls.draft.addClass('inactive');
                this.controls.discard.removeClass('inactive');
                this.setStatus(gettext('Restored content from a draft...'));
            }
        }
    },
    deleteDraft: function () {
        var path = window.location.pathname,
            slug = '',
            yesterday = new Date();
        this.editor.setValue($('[name$=content]', this.current.form).val());
        yesterday.setDate(yesterday.getDate() - 1);
        if (this.current.form) {
            // Check for localstorage and fallback to cookie
            slug = this.current.form.data('prefix');
            if (typeof localStorage !== 'undefined' && localStorage !== null) {
                localStorage.removeItem(path + slug);
            } else {
                document.cookie = encodeURIComponent(slug) + '=' + encodeURIComponent('') +
                    ';expires=' + yesterday.toUTCString() + ';path=' + path;
            }
            this.needsDraft = true;
            this.controls.draft.removeClass('inactive');
            this.controls.discard.addClass('inactive');
            this.setStatus(gettext('Restored original content...'));
        }
    },
    setStatus: function (msg) {
        var self = this;
        // Append status message
        this.controls.status.fadeIn(500);
        this.controls.status.html(msg);
        // Callback to fade out the message
        setTimeout(function () {
            self.controls.status.fadeOut(500, function () {
                self.controls.status.html("");
            });
        }, 2000);
    },
    destroy: function () {
        this.undelegateEvents();
        this.remove();
    }
});

module.exports = ScribbleEditor;
<commit_msg>JSHint clean up to editor code.<commit_after>/*global require, module */

require('codemirror/mode/xml/xml');
require('codemirror/mode/javascript/javascript');
require('codemirror/mode/css/css');
require('codemirror/mode/htmlmixed/htmlmixed');
require('codemirror/addon/display/fullscreen');
require('./djangohint');

var $ = require('jquery');
var Backbone = require('backbone');
var _ = require('underscore');
Backbone.$ = $;
var CodeMirror = require('codemirror');

var gettext = gettext || function (text) {
    'use strict';
    return text;
};

var ScribbleEditor = Backbone.View.extend({
    id: 'scribbleEditorContainer',
    tagName: 'div',
    initialize: function () {
        var self = this;
        this.visible = false;
        this.rendering = false;
        this.errorLine = null;
        this.controls = {};
        this.current = {};
        this.needsSave = false;
        this.needsDraft = false;
        this.scribbles = $('.scribble-wrapper.with-controls');
        this.editorOptions =  {
                mode: "text/html",
                tabMode: "indent",
                lineNumbers: true,
                extraKeys: {
                  "F11": function(cm) {
                    cm.setOption("fullScreen", !cm.getOption("fullScreen"));
                    if ($('.CodeMirror-fullscreen').length) {
                      $('#scribbleEditorContainer').addClass("scribbleEditor-fullscreen");
                    }
                    else {
                      $('#scribbleEditorContainer').removeClass("scribbleEditor-fullscreen");
                    }
                  },
                  "Esc": function(cm) {
                    if (cm.getOption("fullScreen")) {
                        cm.setOption("fullScreen", false);
                    }
                    $('.scribbleEditor-fullscreen').removeClass("scribbleEditor-fullscreen");
                  },
                  'Tab': 'autocomplete'
                }
                };
        CodeMirror.commands.autocomplete = function (editor) {
            CodeMirror.showHint(editor, CodeMirror.djangoHint);
        };
    },
    events: {
        'click .controls .closed': 'close',
        'click .controls .save': 'submitSave',
        'click .controls .draft': 'createDraft',
        'click .controls .discard': 'deleteDraft'
    },
    render: function () {
        var self = this;
        if (this.scribbles.length > 0) {
            this.buildControls();
            $('body').append(this.$el);
            this.editor = new CodeMirror(document.getElementById(this.id), this.editorOptions);
            this.editor.on("change", function (editor, change) {
                self.needsSave = true;
                self.controls.save.removeClass('inactive');
                self.needsDraft = true;
                self.controls.draft.removeClass('inactive');
                self.submitPreview();
            });
            this.editor.selector = this.id;
            // Bind editor to the scribbles
            this.scribbles.each(function (i, elem) {
                // Bind event handlers for each scribble
                $(elem).click(function (e) {
                    // Allow click to follow links inside of scribble content
                    if (e.target.nodeName !== 'A') {
                        self.open($(this));
                    }
                });
            });
        }
    },
    buildControls: function () {
        // Build control bar
        var footerControls = $('

').addClass('controls');
        // Close button
        this.controls.close = $('' + gettext('Close') + '')
            .attr({title: gettext('Close')})
            .addClass('closed');
        // Save button
        this.controls.save = $('' + gettext('Save') + '')
            .attr({title: gettext('Save')})
            .addClass('save inactive');
        this.controls.draft = $('' + gettext('Save Draft') + '')
            .attr({title: gettext('Save as Draft')})
            .addClass('draft inactive');
        this.controls.discard = $('' + gettext('Discard') + '')
            .attr({title: gettext('Discard Draft')})
            .addClass('discard inactive');
        // Error message
        this.controls.errors = $('')
            .addClass('error-msg');
        // Status message
        this.controls.status = $('')
            .addClass('status-msg');
        // Fullscreen instructions
        this.controls.fullscreen = $('
' + gettext('Press ') +
            '' + gettext('F11') + '' +
            gettext(' to enter/exit Fullscreen edit') + '
')
            .addClass('fullscreen');
        footerControls.append(
            this.controls.status,
            this.controls.errors,
            this.controls.close,
            this.controls.discard,
            this.controls.draft,
            this.controls.save,
            this.controls.fullscreen
        );
        this.$el.append(footerControls);
    },
    open: function (scribble) {
        var self = this;
        if (this.visible) {
            this.close();
        }
        this.current.content = $('.scribble-content.original', scribble);
        this.current.preview = $('.scribble-content.preview', scribble);
        this.current.form = $('.scribble-form', scribble);
        this.current.can_save = this.current.form.data('save');
        this.current.can_delete = this.current.form.data('delete');
        this.$el.show();
        this.trigger('open');
        if (this.current.can_save) {
            this.controls.save.show();
            this.editor.setOption('readOnly', false);
            this.editor.setValue($('[name$=content]', this.current.form).val());
            this.restoreDraft();
        } else {
            this.controls.save.hide();
            this.editor.setOption('readOnly', true);
            this.editor.setValue(gettext('You do not have permission to edit this content.'));
        }
        this.$el.animate({height: '300px'}, 500, function () {self.editor.focus(); });
        this.visible = true;
        // Start background draft saving
        var checkDraft = function () {
            if (self.needsDraft) {
                self.createDraft();
            }
        };
        this.backgroundDraft = setInterval(checkDraft, 3000);
    },
    close: function () {
        this.current.preview.hide();
        this.current.content.show();
        this.current = {};
        this.editor.setValue('');
        this.$el.animate({height: 0}, 500);
        this.visible = false;
        if (this.backgroundDraft) {
            clearInterval(this.backgroundDraft);
        }
        $('#scribbleEditorContainer').removeClass("scribbleEditor-fullscreen");
        $('.CodeMirror.cm-s-default').removeClass("CodeMirror-fullscreen").css("height", "");
        this.trigger('close');
    },
    submitPreview: function (force) {
        var self = this;
        if (this.current.form && (force || (!this.rendering && !this.editor.getOption('readOnly')))) {
            this.rendering = true;
            // Submit the form and display the preview
            $.post(
                this.current.form.attr('action'),
                this.getFormData(),
                function (response) {
                    if (response.valid) {
                        CodeMirror.update_variables(response.variables);
                    }
                    self.renderPreview(response);
                },
                'json'
            ).error(function (jqXHR, textStatus, errorThrown) {
                var msg = 'Server response was "' + errorThrown + '"';
                self.setError(msg);
            }).complete(function () {
                self.rendering = false;
            });
        }
    },
    renderPreview: function (response) {
        var self = this;
        if (this.visible) {
            if (this.errorLine !== null) {
                this.editor.removeLineClass(this.errorLine, "background", "activeline");
            }
            this.controls.errors.html('');
            this.valid = response.valid;
            if (response.valid) {
                this.current.preview.html(response.html);
                this.current.preview.show();
                this.current.content.hide();
                this.controls.save.removeClass('inactive');
            } else {
                this.setError(response.error.message, response.error.line - 1);
            }
        }
    },
    setError: function (msg, line) {
        if (typeof line !== 'undefined' && line !== null) {
            this.errorLine = line;
            this.editor.addLineClass(this.errorLine, "background", "activeline");
        }
        this.controls.errors.html('' + gettext('Error:') + ' ' + msg);
        this.valid = false;
        this.controls.save.addClass('inactive');
    },
    getFormData: function () {
        var result = {},
            prefix = '',
            self = this;
        if (this.current.form) {
            prefix = this.current.form.data('prefix');
            $(':input', this.current.form).each(function (i, input) {
                var inputName = $(input).attr('name').replace(prefix + '-', ''),
                    inputValue = $(input).val();
                if (inputName === 'content') {
                    result[inputName] = self.editor.getValue();
                } else {
                    result[inputName] = inputValue;
                }
            });
        }
        return result;
    },
    submitSave: function () {
        var self = this;
        if (this.current.form && this.valid) {
            // Submit the form and change current content
            $.post(
                this.current.form.data('save'),
                this.getFormData(),
                function (response) {
                    self.renderSave(response);
                },
                'json'
            ).error(function (jqXHR, textStatus, errorThrown) {
                var msg = gettext('Server response was') + '"' + errorThrown + '"';
                self.setError(msg);
            });
        }
        $('#scribbleEditorContainer').removeClass("scribbleEditor-fullscreen");
        $('.CodeMirror.cm-s-default').removeClass("CodeMirror-fullscreen").css("height", "");
    },
    renderSave: function (response) {
        if (response.valid) {
            this.needsSave = false;
            this.controls.save.addClass('inactive');
            this.current.form.data('save', response.url);
            this.current.content.html(this.current.preview.html());
            $('[name$=content]', this.current.form).val(this.editor.getValue());
            this.deleteDraft();
            this.close();
        } else if (response.error) {
            this.setError(response.error.message);
        }
    },
    createDraft: function () {
        var scribble = null,
            path = window.location.pathname,
            slug = '';
        if (this.current.form) {
            // Check for localstorage and fallback to cookie
            scribble = this.editor.getValue();
            slug = this.current.form.data('prefix');
            if (typeof localStorage !== 'undefined' && localStorage !== null) {
                localStorage[path + slug] = scribble;
            } else {
                document.cookie = encodeURIComponent(slug) + '=' + encodeURIComponent(scribble) + ';' + 'path=' + path;
            }
            this.needsDraft = false;
            this.controls.draft.addClass('inactive');
            this.controls.discard.removeClass('inactive');
            this.setStatus(gettext('Draft saved...'));
        }
    },
    restoreDraft: function () {
        var scribble = null,
            path = window.location.pathname,
            slug = '',
            i,
            key,
            value,
            cookies = document.cookie.split(";"),
            cookieName;
        if (this.current.form) {
            // Check for localstorage and fallback to cookie
            slug = this.current.form.data('prefix');
            if (typeof localStorage !== 'undefined' && localStorage !== null) {
                scribble = localStorage[path + slug];
            } else {
                for (i = 0; i < cookies.length; i += 1) {
                    key = cookies[i].substr(0, cookies[i].indexOf("="));
                    value = cookies[i].substr(cookies[i].indexOf("=") + 1);
                    key = decodeURIComponent(key.replace(/\+/g, " "));
                    if (key === cookieName) {
                        scribble = decodeURIComponent(value.replace(/\+/g, " "));
                        break;
                    }
                }
            }
            if (scribble) {
                this.editor.setValue(scribble);
                this.submitPreview(true);
                this.needsDraft = false;
                this.controls.draft.addClass('inactive');
                this.controls.discard.removeClass('inactive');
                this.setStatus(gettext('Restored content from a draft...'));
            }
        }
    },
    deleteDraft: function () {
        var path = window.location.pathname,
            slug = '',
            yesterday = new Date();
        this.editor.setValue($('[name$=content]', this.current.form).val());
        yesterday.setDate(yesterday.getDate() - 1);
        if (this.current.form) {
            // Check for localstorage and fallback to cookie
            slug = this.current.form.data('prefix');
            if (typeof localStorage !== 'undefined' && localStorage !== null) {
                localStorage.removeItem(path + slug);
            } else {
                document.cookie = encodeURIComponent(slug) + '=' + encodeURIComponent('') +
                    ';expires=' + yesterday.toUTCString() + ';path=' + path;
            }
            this.needsDraft = true;
            this.controls.draft.removeClass('inactive');
            this.controls.discard.addClass('inactive');
            this.setStatus(gettext('Restored original content...'));
        }
    },
    setStatus: function (msg) {
        var self = this;
        // Append status message
        this.controls.status.fadeIn(500);
        this.controls.status.html(msg);
        // Callback to fade out the message
        setTimeout(function () {
            self.controls.status.fadeOut(500, function () {
                self.controls.status.html("");
            });
        }, 2000);
    },
    destroy: function () {
        this.undelegateEvents();
        this.remove();
    }
});

module.exports = ScribbleEditor;
<|endoftext|>


<commit_before>/*
 * itsjoke.js - Mari memes.
 *
 * Contributed by Capuccino and Ovyerus.
 */

const fs = require('fs');

exports.commands = [
    'mari'
];

var files = fs.readdirSync(`${__baseDir}/res/itsjoke`);

exports.mari = {
    desc: "It's joke!",
    longDesc: "Send a random picture of the it's joke meme.",
    main(bot, ctx) {
        return new Promise((resolve, reject) => {
            ctx.msg.channel.sendTyping();
            let fileName = files[Math.floor(Math.random() * files.length)];
            let file = fs.readFileSync(`${__baseDir}/res/${fileName}`);
            ctx.msg.channel.createMessage('', {file, name: fileName}).then(resolve).catch(reject);
        });
    }
};
<commit_msg>Fix mari<commit_after>/*
 * itsjoke.js - Mari memes.
 *
 * Contributed by Capuccino and Ovyerus.
 */

const fs = require('fs');

exports.commands = [
    'mari'
];

var files = fs.readdirSync(`${__baseDir}/res/itsjoke`);

exports.mari = {
    desc: "It's joke!",
    longDesc: "Send a random picture of the it's joke meme.",
    main(bot, ctx) {
        return new Promise((resolve, reject) => {
            ctx.msg.channel.sendTyping();
            let fileName = files[Math.floor(Math.random() * files.length)];
            let file = fs.readFileSync(`${__baseDir}/res/itsjoke/${fileName}`);
            ctx.msg.channel.createMessage('', {file, name: fileName}).then(resolve).catch(reject);
        });
    }
};
<|endoftext|>


<commit_before>define(
  'ephox.snooker.operate.ColumnInsertion',

  [
    'ephox.compass.Arr',
    'ephox.highway.Merger',
    'ephox.peanut.Fun',
    'ephox.perhaps.Option',
    'ephox.snooker.lookup.Blocks',
    'ephox.snooker.model.Warehouse'
  ],

  function (Arr, Merger, Fun, Option, Blocks, Warehouse) {
    var operate = function (warehouse, rowIndex, colIndex, operation) {
      /*
         The process:

         Identify which cell has selection focus
         Work out the real column of that cell
         Find all the cells on that column
         Apply operation
       */

      var cells = warehouse.all();
      var initial = Option.from(cells[rowIndex]).bind(function (row) {
        return Option.from(row.cells()[colIndex]);
      });

      return initial.map(function (start) {
        var column = Blocks.column(warehouse, start.column());
        return Arr.map(column, function (context, i) {
          var before = context.before();
          var after = context.after();
          var on = operation(context.on());

          var resultant = context.before().concat(on).concat(context.after());
          console.log('row: ', i);
          console.log('before: ', before);
          return {
            cells: Fun.constant(resultant),
            element: context.row
          };
        });
      }).getOrThunk(function () {
        return warehouse.all();
      });
    };

    var adjust = function (cell, delta) {
      return Merger.merge(cell, {
        colspan: Fun.constant(cell.colspan() + delta)
      });
    };

    var insertAfter = function (warehouse, rowIndex, colIndex, nu) {
      var operation = function (on) {
        return on.fold(function () {
          return [ nu(Warehouse.getAt(warehouse, rowIndex, colIndex)) ];
        }, function (whole) {
          return [ whole, nu(whole) ];
        }, function (partial, offset) {
          return offset < partial.colspan() - 1 ? [ adjust(partial, 1) ] : [ partial, nu(partial) ];
        });
      };

      return operate(warehouse, rowIndex, colIndex, operation);
    };

    var insertBefore = function (warehouse, rowIndex, colIndex, nu) {
      var operation = function (on) {
        return on.fold(function () {
          // there is nothing there ... so let's see what warehouse thinks.
          return [ nu(Warehouse.getAt(warehouse, rowIndex, colIndex)) ];
          // return [];
        }, function (whole) {
          return [ nu(whole), whole ];
        }, function (partial, offset) {
          return offset === 0 ? [ nu(partial), partial ] : [ adjust(partial, 1) ];
        });
      };

      return operate(warehouse, rowIndex, colIndex, operation);
    };

    // Should this really be in a module called Insertion?
    var erase = function (warehouse, rowIndex, colIndex) {
      var operation = function (on) {
        return on.fold(function () {
          return [];
        }, function (whole) {
          return [];
        }, function (partial, offset) {
          return [ adjust(partial, -1) ];
        });
      };

      return operate(warehouse, rowIndex, colIndex, operation);
    };

    return {
      insertAfter: insertAfter,
      insertBefore: insertBefore,
      erase: erase
    };
  }
);

/*

return Arr.bind(cells, function (row) {
  return Arr.bind(row.cells(), function (cell) {
    if (isSpanner) adjust(+1)
    else {
      if isEndedSpan(cell) [ /// yeah, that approach won't work for spanning ... I need to know that I'm on a span row.
      // How do I know when to insert another cell?]
      else if (cell.column() === target) [ nu, this ]
    }
  });
})

*/<commit_msg>TBIO-408: slightly safer<commit_after>define(
  'ephox.snooker.operate.ColumnInsertion',

  [
    'ephox.compass.Arr',
    'ephox.highway.Merger',
    'ephox.peanut.Fun',
    'ephox.perhaps.Option',
    'ephox.snooker.lookup.Blocks',
    'ephox.snooker.model.Warehouse'
  ],

  function (Arr, Merger, Fun, Option, Blocks, Warehouse) {
    var operate = function (warehouse, rowIndex, colIndex, operation) {
      /*
         The process:

         Identify which cell has selection focus
         Work out the real column of that cell
         Find all the cells on that column
         Apply operation
       */

      var cells = warehouse.all();
      var initial = Option.from(cells[rowIndex]).bind(function (row) {
        return Option.from(row.cells()[colIndex]);
      });

      return initial.map(function (start) {
        var column = Blocks.column(warehouse, start.column());
        return Arr.map(column, function (context, i) {
          var before = context.before();
          var after = context.after();
          var on = operation(context.on());

          var resultant = context.before().concat(on).concat(context.after());
          return {
            cells: Fun.constant(resultant),
            element: context.row
          };
        });
      }).getOrThunk(function () {
        return warehouse.all();
      });
    };

    var adjust = function (cell, delta) {
      return Merger.merge(cell, {
        colspan: Fun.constant(cell.colspan() + delta)
      });
    };

    var insertAfter = function (warehouse, rowIndex, colIndex, nu) {
      var operation = function (on) {
        return on.fold(function () {
          // TODO: This should probably be an Option.
          var occupant = Warehouse.getAt(warehouse, rowIndex, colIndex);
          return occupant !== undefined ? [ nu(occupant) ] : [];
        }, function (whole) {
          return [ whole, nu(whole) ];
        }, function (partial, offset) {
          return offset < partial.colspan() - 1 ? [ adjust(partial, 1) ] : [ partial, nu(partial) ];
        });
      };

      return operate(warehouse, rowIndex, colIndex, operation);
    };

    var insertBefore = function (warehouse, rowIndex, colIndex, nu) {
      var operation = function (on) {
        return on.fold(function () {
          // TODO: This should probably be an Option.
          var occupant = Warehouse.getAt(warehouse, rowIndex, colIndex);
          return occupant !== undefined ? [ nu(occupant) ] : [];
        }, function (whole) {
          return [ nu(whole), whole ];
        }, function (partial, offset) {
          return offset === 0 ? [ nu(partial), partial ] : [ adjust(partial, 1) ];
        });
      };

      return operate(warehouse, rowIndex, colIndex, operation);
    };

    // Should this really be in a module called Insertion?
    var erase = function (warehouse, rowIndex, colIndex) {
      var operation = function (on) {
        return on.fold(function () {
          return [];
        }, function (whole) {
          return [];
        }, function (partial, offset) {
          return [ adjust(partial, -1) ];
        });
      };

      return operate(warehouse, rowIndex, colIndex, operation);
    };

    return {
      insertAfter: insertAfter,
      insertBefore: insertBefore,
      erase: erase
    };
  }
);

/*

return Arr.bind(cells, function (row) {
  return Arr.bind(row.cells(), function (cell) {
    if (isSpanner) adjust(+1)
    else {
      if isEndedSpan(cell) [ /// yeah, that approach won't work for spanning ... I need to know that I'm on a span row.
      // How do I know when to insert another cell?]
      else if (cell.column() === target) [ nu, this ]
    }
  });
})

*/<|endoftext|>


<commit_before>'use strict';
// =========================================================================
//
// Controller for phases
//
// =========================================================================
var _              = require ('lodash');
var path           = require('path');
var DBModel        = require (path.resolve('./modules/core/server/controllers/core.dbmodel.controller'));
var MilestoneClass = require (path.resolve('./modules/milestones/server/controllers/milestone.controller'));
var PhaseBaseClass = require ('./phasebase.controller');
var Roles          = require (path.resolve('./modules/roles/server/controllers/role.controller'));

module.exports = DBModel.extend ({
	name : 'Phase',
	plural : 'phases',
	bind: ['complete'],
	// -------------------------------------------------------------------------
	//
	// just get a base phase, returns a promise
	//
	// -------------------------------------------------------------------------
	getPhaseBase: function (code) {
		return (new PhaseBaseClass (this.user)).findOne ({code:code});
	},
	// -------------------------------------------------------------------------
	//
	// copy a base phase into a new phase and return the promise of it
	//
	// -------------------------------------------------------------------------
	copyPhaseBase: function (base) {
		return this.copy (base);
	},
	// -------------------------------------------------------------------------
	//
	// set dateStartedEst from duration, return phase
	//
	// -------------------------------------------------------------------------
	setInitalDates: function (phase) {
		phase.dateStartedEst   = new Date ();
		phase.dateCompletedEst =  new Date ();
		phase.dateCompletedEst.setDate (phase.dateCompletedEst.getDate () + phase.duration);
		if (phase.startOnCreate) {
			phase.status      = 'In Progress';
			phase.dateStarted = new Date ();
		} else {
			phase.status = 'Not Started';
		}
		return phase;
	},
	// -------------------------------------------------------------------------
	//
	// copy phase ancestry into phase and return phase
	//
	// -------------------------------------------------------------------------
	setAncestry: function (phase, project) {
		phase.project     = project._id;
		phase.projectCode = project.code;
		phase.stream      = project.stream;
		phase.order       = project.phases.length + 1;
		return phase;
	},
	// -------------------------------------------------------------------------
	//
	// Using the functions above, make a new phase from a base code and
	// attach it to the passed in phase and the phase ancestry
	//
	// -------------------------------------------------------------------------
	fromBase: function (code, project) {
		var self = this;
		var base;
		var baseId;
		var phase;
		var milestoneCodes;
		return new Promise (function (resolve, reject) {
			//
			// get the base
			//
			self.getPhaseBase (code)
			//
			// copy its id and such before we lose it, then copy the entire thing
			//
			.then (function (m) {
				// console.log ('found the base');
				base          = m;
				baseId        = m._id;
				milestoneCodes = _.clone (m.milestones);
				m.milestones = [];
				return self.copyPhaseBase (base);
			})
			//
			// set the base id and then initial dates
			//
			.then (function (m) {
				// console.log ('after copy', JSON.stringify (m, null, 4));
				// console.log ('copied the base into new phase with id ', m._id, m.milestones);
				phase = m;
				phase.phaseBase = baseId;
				return self.setInitalDates (phase);
			})
			//
			// copy over stuff from the project
			//
			.then (function (m) {
				// console.log ('after setting dates', JSON.stringify (phase, null, 4));
				return self.setAncestry (m, project);
			})
			//
			// adds each milestone, inefficient, but there will not be a lot
			// so not too much of an issue
			//
			.then (function (m) {
				// console.log ('all set to add milestones');
				//
				// This little bit of magic forces the synchronous executiuon of
				// async functions as promises, so a sync version of all.
				//
				return milestoneCodes.reduce (function (current, code) {
					return current.then (function () {
						// console.log ('++ add activity ', code);
						return self.addMilestone (m, code);
					});
				}, Promise.resolve());
			})
			.then (function () {
				// console.log ('saving the phase', JSON.stringify (phase, null, 4));
				return self.saveDocument (phase);
			})
			.then (function (r) {
				// console.log ('saved the phase');
				return r;
			})
			.then (resolve, reject);
		});
	},
	// -------------------------------------------------------------------------
	//
	// add a milestone to this phase (from a base code)
	//
	// -------------------------------------------------------------------------
	addMilestone : function (phase, basecode) {
		var self = this;
		var Milestone = new MilestoneClass (self.user);
		return new Promise (function (resolve, reject) {
			//
			// get the new milestone
			//
			Milestone.fromBase (basecode, phase)
			.then (function (milestone) {
				// console.log ('adding milestone with id '+milestone._id+' to phase '+phase._id);
				phase.milestones.push (milestone._id);
				return phase;
			})
			.then (self.saveDocument)
			.then (resolve, reject);
		});
	},
	// -------------------------------------------------------------------------
	//
	// start a phase
	//
	// -------------------------------------------------------------------------
	start: function (phase) {
		phase.status           = 'In Progress';
		phase.dateStarted      = new Date ();
		phase.dateCompletedEst = new Date (phase.dateStarted);
		phase.dateCompletedEst.setDate (phase.dateCompletedEst.getDate () + phase.duration);
		// console.log ('starting pahse', phase._id, phase.name);
		return this.findAndUpdate (phase);
	},
	// -------------------------------------------------------------------------
	//
	// complete a phase. this marks underlying milestones as complete, we
	// may rather want to mark them as overridden.
	//
	// -------------------------------------------------------------------------
	complete: function (phase) {
		var self = this;
		return new Promise (function (resolve, reject) {
			if (!phase.completed) {
				phase.status        = 'Complete';
				phase.completed     = true;
				phase.completedBy   = self.user._id;
				phase.dateCompleted = new Date ();
				phase.progress = 100;
				self.completeMilestones (phase)
				.then (function () {return self.findAndUpdate (phase);})
				.then (resolve, reject);
			} else {
				resolve (phase);
			}
		});
	},
	// -------------------------------------------------------------------------
	//
	// override a phase, we assume that the reason was already entered
	//
	// -------------------------------------------------------------------------
	override: function (phase, reason) {
		var self = this;
		return new Promise (function (resolve, reject) {
			phase.status         = 'Not Required';
			phase.overrideReason = reason;
			phase.overridden     = true;
			phase.completed      = true;
			phase.completedBy    = this.user._id;
			phase.dateCompleted  = new Date ();
			self.overrideMilestones (phase)
			.then (self.findAndUpdate)
			.then (resolve, reject);
		});
	},
	// -------------------------------------------------------------------------
	//
	// mark all outstanding underlying milestones as complete
	//
	// -------------------------------------------------------------------------
	completeMilestones: function (phase) {
		// console.log ('completing milestones');
		var self = this;
	// console.log (JSON.stringify (phase, null, 4));
		var Milestone = new MilestoneClass (self.user);
		return Promise.all (phase.milestones.map (function (milestoneId) {
			return Milestone.findById (milestoneId);
		}))
		.then (function (result) {
			return Promise.all (result.map (function (milestone) {
				if (milestone.completed) return milestone;
				else return Milestone.complete (milestone);
			}));
		});
	},
	// -------------------------------------------------------------------------
	//
	// mark all outstanding underlying milestones as overridden
	//
	// -------------------------------------------------------------------------
	overrideMilestones: function (phase) {
		var self = this;
		return Promise.all (phase.milestones.map (function (milestone) {
			var Milestone = new MilestoneClass (self.user);
			if (milestone.completed) return milestone;
			else return Milestone.override (milestone, phase.overrideReason);
		}));
	},

	// -------------------------------------------------------------------------
	//
	// get phases based on a project, or not, with access of read or write
	// the default get * already does any project, read, so we just need
	// to do any project, write, and project read/write
	//
	// -------------------------------------------------------------------------
	userPhases: function (projectCode, access) {
		var self = this;
		return new Promise (function (resolve, reject) {
			var q = (projectCode) ? {projectCode:projectCode} : {} ;
			var p = (access === 'write') ? self.listwrite (q) : self.list (q);
			p.then (resolve, reject);
		});
	},
	// -------------------------------------------------------------------------
	//
	// phase for project that this user can read
	//
	// -------------------------------------------------------------------------
	phasesForProject: function (id) {
		var p = this.list ({project:id});
		return new Promise (function (resolve, reject) {
			p.then (resolve, reject);
		});
	}

});

	// // -------------------------------------------------------------------------
	// //
	// // add a milestone to a phase from a base milestone (creates a proper
	// // milestone with all its children and pushes it onto the array)
	// //
	// // -------------------------------------------------------------------------
	// addMilestoneFromBase : function (phase, milestonebase, roles) {
	// 	var self = this;
	// 	var Milestone = new MilestoneClass (this.user);
	// 	return new Promise (function (resolve, reject) {
	// 		Milestone.makeMilestoneFromBase (
	// 			milestonebase,
	// 			phase.stream,
	// 			phase.project,
	// 			phase.projectCode,
	// 			phase._id,
	// 			roles
	// 		)
	// 		.then (function (newmilestone) {
	// 			phase.milestones.push (newmilestone._id);
	// 			return phase;
	// 		})
	// 		.then (self.saveDocument)
	// 		.then (resolve, reject);
	// 	});
	// },
	// // -------------------------------------------------------------------------
	// //
	// // given a base milestone code, add a copy of it to the phase
	// //
	// // -------------------------------------------------------------------------
	// addMilestoneFromCode : function (phase, milestoneCode, roles) {
	// 	var self = this;
	// 	return new Promise (function (resolve, reject) {
	// 		var PhaseBase = new PhaseBaseClass (self.user);
	// 		PhaseBase.findOne ({
	// 			code: milestoneCode
	// 		})
	// 		.then (function (base) {
	// 			return self.addMilestoneFromBase (phase, base, roles);
	// 		})
	// 		.then (resolve, reject);
	// 	});
	// },

	// // -------------------------------------------------------------------------
	// //
	// // when making a phase from a base it will aslways be in order to attach
	// // to a project, so the project and stream are passed in here along with the
	// // base
	// // make our new phase so we have an id
	// // first get all the milestones and make propoer objects from those,
	// // reverse link the new milestones to the new phase by passing in the
	// // ancestry
	// // save the phase
	// //
	// // -------------------------------------------------------------------------
	// makePhaseFromBase : function (base, streamid, projectid, projectcode, roles) {
	// 	var self = this;
	// 	var Milestone = new MilestoneClass (this.user);
	// 	var MilestoneBase = new MilestoneBaseClass (this.user);
	// 	return new Promise (function (resolve, reject) {
	// 		var baseid = base._id;
	// 		var newobjectid;
	// 		var newobject;
	// 		var children;
	// 		var basename = 'phaseBase';
	// 		var newchildfunction = Milestone.makeMilestoneFromBase;
	// 		var findchildbyid = MilestoneBase.findById;
	// 		var childname = 'milestones';
	// 		// get all the children
	// 		Promise.all (base[childname].map (findchildbyid))
	// 		.then (function (models) {
	// 			// assign it for later use
	// 			children = models;
	// 			// make the new newobject from the base
	// 			return self.copy (base);
	// 		})
	// 		.then (function (model) {
	// 			newobjectid = model._id;
	// 			newobject   = model;
	// 			//
	// 			// fix the roles
	// 			//
	// 			model.fixRoles (projectcode);
	// 			if (roles) model.addRoles (roles);
	// 			RoleController.addRolesToConfigObject (model, 'phases', model.roleSet());
	// 			//
	// 			// assign whatever ancenstry is needed
	// 			//
	// 			model[basename] = baseid;
	// 			model.project = projectid;
	// 			model.projectCode = projectcode;
	// 			model.stream  = streamid;
	// 			// return the promise of new children
	// 			return Promise.all (children.map (function (m) {
	// 				return Milestone.makeMilestoneFromBase (m, streamid, projectid, projectcode, newobjectid, roles);
	// 			}));
	// 		})
	// 		.then (function (models) {
	// 			// assign each new child to the newobject
	// 			_.each (models, function (m) {
	// 				newobject[childname].push (m._id);
	// 			});
	// 			return newobject;
	// 		})
	// 		.then (self.saveDocument)
	// 		.then (resolve, reject);
	// 	});
	// },

<commit_msg>Fix bug on override phase<commit_after>'use strict';
// =========================================================================
//
// Controller for phases
//
// =========================================================================
var _              = require ('lodash');
var path           = require('path');
var DBModel        = require (path.resolve('./modules/core/server/controllers/core.dbmodel.controller'));
var MilestoneClass = require (path.resolve('./modules/milestones/server/controllers/milestone.controller'));
var PhaseBaseClass = require ('./phasebase.controller');
var Roles          = require (path.resolve('./modules/roles/server/controllers/role.controller'));

module.exports = DBModel.extend ({
	name : 'Phase',
	plural : 'phases',
	bind: ['complete'],
	// -------------------------------------------------------------------------
	//
	// just get a base phase, returns a promise
	//
	// -------------------------------------------------------------------------
	getPhaseBase: function (code) {
		return (new PhaseBaseClass (this.user)).findOne ({code:code});
	},
	// -------------------------------------------------------------------------
	//
	// copy a base phase into a new phase and return the promise of it
	//
	// -------------------------------------------------------------------------
	copyPhaseBase: function (base) {
		return this.copy (base);
	},
	// -------------------------------------------------------------------------
	//
	// set dateStartedEst from duration, return phase
	//
	// -------------------------------------------------------------------------
	setInitalDates: function (phase) {
		phase.dateStartedEst   = new Date ();
		phase.dateCompletedEst =  new Date ();
		phase.dateCompletedEst.setDate (phase.dateCompletedEst.getDate () + phase.duration);
		if (phase.startOnCreate) {
			phase.status      = 'In Progress';
			phase.dateStarted = new Date ();
		} else {
			phase.status = 'Not Started';
		}
		return phase;
	},
	// -------------------------------------------------------------------------
	//
	// copy phase ancestry into phase and return phase
	//
	// -------------------------------------------------------------------------
	setAncestry: function (phase, project) {
		phase.project     = project._id;
		phase.projectCode = project.code;
		phase.stream      = project.stream;
		phase.order       = project.phases.length + 1;
		return phase;
	},
	// -------------------------------------------------------------------------
	//
	// Using the functions above, make a new phase from a base code and
	// attach it to the passed in phase and the phase ancestry
	//
	// -------------------------------------------------------------------------
	fromBase: function (code, project) {
		var self = this;
		var base;
		var baseId;
		var phase;
		var milestoneCodes;
		return new Promise (function (resolve, reject) {
			//
			// get the base
			//
			self.getPhaseBase (code)
			//
			// copy its id and such before we lose it, then copy the entire thing
			//
			.then (function (m) {
				// console.log ('found the base');
				base          = m;
				baseId        = m._id;
				milestoneCodes = _.clone (m.milestones);
				m.milestones = [];
				return self.copyPhaseBase (base);
			})
			//
			// set the base id and then initial dates
			//
			.then (function (m) {
				// console.log ('after copy', JSON.stringify (m, null, 4));
				// console.log ('copied the base into new phase with id ', m._id, m.milestones);
				phase = m;
				phase.phaseBase = baseId;
				return self.setInitalDates (phase);
			})
			//
			// copy over stuff from the project
			//
			.then (function (m) {
				// console.log ('after setting dates', JSON.stringify (phase, null, 4));
				return self.setAncestry (m, project);
			})
			//
			// adds each milestone, inefficient, but there will not be a lot
			// so not too much of an issue
			//
			.then (function (m) {
				// console.log ('all set to add milestones');
				//
				// This little bit of magic forces the synchronous executiuon of
				// async functions as promises, so a sync version of all.
				//
				return milestoneCodes.reduce (function (current, code) {
					return current.then (function () {
						// console.log ('++ add activity ', code);
						return self.addMilestone (m, code);
					});
				}, Promise.resolve());
			})
			.then (function () {
				// console.log ('saving the phase', JSON.stringify (phase, null, 4));
				return self.saveDocument (phase);
			})
			.then (function (r) {
				// console.log ('saved the phase');
				return r;
			})
			.then (resolve, reject);
		});
	},
	// -------------------------------------------------------------------------
	//
	// add a milestone to this phase (from a base code)
	//
	// -------------------------------------------------------------------------
	addMilestone : function (phase, basecode) {
		var self = this;
		var Milestone = new MilestoneClass (self.user);
		return new Promise (function (resolve, reject) {
			//
			// get the new milestone
			//
			Milestone.fromBase (basecode, phase)
			.then (function (milestone) {
				// console.log ('adding milestone with id '+milestone._id+' to phase '+phase._id);
				phase.milestones.push (milestone._id);
				return phase;
			})
			.then (self.saveDocument)
			.then (resolve, reject);
		});
	},
	// -------------------------------------------------------------------------
	//
	// start a phase
	//
	// -------------------------------------------------------------------------
	start: function (phase) {
		phase.status           = 'In Progress';
		phase.dateStarted      = new Date ();
		phase.dateCompletedEst = new Date (phase.dateStarted);
		phase.dateCompletedEst.setDate (phase.dateCompletedEst.getDate () + phase.duration);
		// console.log ('starting pahse', phase._id, phase.name);
		return this.findAndUpdate (phase);
	},
	// -------------------------------------------------------------------------
	//
	// complete a phase. this marks underlying milestones as complete, we
	// may rather want to mark them as overridden.
	//
	// -------------------------------------------------------------------------
	complete: function (phase) {
		var self = this;
		return new Promise (function (resolve, reject) {
			if (!phase.completed) {
				phase.status        = 'Complete';
				phase.completed     = true;
				phase.completedBy   = self.user._id;
				phase.dateCompleted = new Date ();
				phase.progress = 100;
				self.completeMilestones (phase)
				.then (function () {return self.findAndUpdate (phase);})
				.then (resolve, reject);
			} else {
				resolve (phase);
			}
		});
	},
	// -------------------------------------------------------------------------
	//
	// override a phase, we assume that the reason was already entered
	//
	// -------------------------------------------------------------------------
	override: function (phase, reason) {
		var self = this;
		return new Promise (function (resolve, reject) {
			phase.status         = 'Not Required';
			phase.overrideReason = reason;
			phase.overridden     = true;
			phase.completed      = true;
			phase.completedBy    = self.user._id;
			phase.dateCompleted  = new Date ();
			self.overrideMilestones (phase)
			.then (self.findAndUpdate)
			.then (resolve, reject);
		});
	},
	// -------------------------------------------------------------------------
	//
	// mark all outstanding underlying milestones as complete
	//
	// -------------------------------------------------------------------------
	completeMilestones: function (phase) {
		// console.log ('completing milestones');
		var self = this;
	// console.log (JSON.stringify (phase, null, 4));
		var Milestone = new MilestoneClass (self.user);
		return Promise.all (phase.milestones.map (function (milestoneId) {
			return Milestone.findById (milestoneId);
		}))
		.then (function (result) {
			return Promise.all (result.map (function (milestone) {
				if (milestone.completed) return milestone;
				else return Milestone.complete (milestone);
			}));
		});
	},
	// -------------------------------------------------------------------------
	//
	// mark all outstanding underlying milestones as overridden
	//
	// -------------------------------------------------------------------------
	overrideMilestones: function (phase) {
		var self = this;
		return Promise.all (phase.milestones.map (function (milestone) {
			var Milestone = new MilestoneClass (self.user);
			if (milestone.completed) return milestone;
			else return Milestone.override (milestone, phase.overrideReason);
		}));
	},

	// -------------------------------------------------------------------------
	//
	// get phases based on a project, or not, with access of read or write
	// the default get * already does any project, read, so we just need
	// to do any project, write, and project read/write
	//
	// -------------------------------------------------------------------------
	userPhases: function (projectCode, access) {
		var self = this;
		return new Promise (function (resolve, reject) {
			var q = (projectCode) ? {projectCode:projectCode} : {} ;
			var p = (access === 'write') ? self.listwrite (q) : self.list (q);
			p.then (resolve, reject);
		});
	},
	// -------------------------------------------------------------------------
	//
	// phase for project that this user can read
	//
	// -------------------------------------------------------------------------
	phasesForProject: function (id) {
		var p = this.list ({project:id});
		return new Promise (function (resolve, reject) {
			p.then (resolve, reject);
		});
	}

});

	// // -------------------------------------------------------------------------
	// //
	// // add a milestone to a phase from a base milestone (creates a proper
	// // milestone with all its children and pushes it onto the array)
	// //
	// // -------------------------------------------------------------------------
	// addMilestoneFromBase : function (phase, milestonebase, roles) {
	// 	var self = this;
	// 	var Milestone = new MilestoneClass (this.user);
	// 	return new Promise (function (resolve, reject) {
	// 		Milestone.makeMilestoneFromBase (
	// 			milestonebase,
	// 			phase.stream,
	// 			phase.project,
	// 			phase.projectCode,
	// 			phase._id,
	// 			roles
	// 		)
	// 		.then (function (newmilestone) {
	// 			phase.milestones.push (newmilestone._id);
	// 			return phase;
	// 		})
	// 		.then (self.saveDocument)
	// 		.then (resolve, reject);
	// 	});
	// },
	// // -------------------------------------------------------------------------
	// //
	// // given a base milestone code, add a copy of it to the phase
	// //
	// // -------------------------------------------------------------------------
	// addMilestoneFromCode : function (phase, milestoneCode, roles) {
	// 	var self = this;
	// 	return new Promise (function (resolve, reject) {
	// 		var PhaseBase = new PhaseBaseClass (self.user);
	// 		PhaseBase.findOne ({
	// 			code: milestoneCode
	// 		})
	// 		.then (function (base) {
	// 			return self.addMilestoneFromBase (phase, base, roles);
	// 		})
	// 		.then (resolve, reject);
	// 	});
	// },

	// // -------------------------------------------------------------------------
	// //
	// // when making a phase from a base it will aslways be in order to attach
	// // to a project, so the project and stream are passed in here along with the
	// // base
	// // make our new phase so we have an id
	// // first get all the milestones and make propoer objects from those,
	// // reverse link the new milestones to the new phase by passing in the
	// // ancestry
	// // save the phase
	// //
	// // -------------------------------------------------------------------------
	// makePhaseFromBase : function (base, streamid, projectid, projectcode, roles) {
	// 	var self = this;
	// 	var Milestone = new MilestoneClass (this.user);
	// 	var MilestoneBase = new MilestoneBaseClass (this.user);
	// 	return new Promise (function (resolve, reject) {
	// 		var baseid = base._id;
	// 		var newobjectid;
	// 		var newobject;
	// 		var children;
	// 		var basename = 'phaseBase';
	// 		var newchildfunction = Milestone.makeMilestoneFromBase;
	// 		var findchildbyid = MilestoneBase.findById;
	// 		var childname = 'milestones';
	// 		// get all the children
	// 		Promise.all (base[childname].map (findchildbyid))
	// 		.then (function (models) {
	// 			// assign it for later use
	// 			children = models;
	// 			// make the new newobject from the base
	// 			return self.copy (base);
	// 		})
	// 		.then (function (model) {
	// 			newobjectid = model._id;
	// 			newobject   = model;
	// 			//
	// 			// fix the roles
	// 			//
	// 			model.fixRoles (projectcode);
	// 			if (roles) model.addRoles (roles);
	// 			RoleController.addRolesToConfigObject (model, 'phases', model.roleSet());
	// 			//
	// 			// assign whatever ancenstry is needed
	// 			//
	// 			model[basename] = baseid;
	// 			model.project = projectid;
	// 			model.projectCode = projectcode;
	// 			model.stream  = streamid;
	// 			// return the promise of new children
	// 			return Promise.all (children.map (function (m) {
	// 				return Milestone.makeMilestoneFromBase (m, streamid, projectid, projectcode, newobjectid, roles);
	// 			}));
	// 		})
	// 		.then (function (models) {
	// 			// assign each new child to the newobject
	// 			_.each (models, function (m) {
	// 				newobject[childname].push (m._id);
	// 			});
	// 			return newobject;
	// 		})
	// 		.then (self.saveDocument)
	// 		.then (resolve, reject);
	// 	});
	// },

<|endoftext|>


<commit_before>/*
 * Licensed to the Sakai Foundation (SF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The SF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 */
/*global $, Config, jQuery, json_parse, sakai, sdata, Querystring, SWFID, swfobject */

var sakai = sakai || {};

/**
 * Initialize the video widget
 * @param {String} tuid Unique id of the widget
 * @param {String} placement Widget place
 * @param {Boolean} showSettings Show the settings of the widget or not
 */
sakai.video = function(tuid, placement, showSettings) {

    /////////////////////////////
    // Configuration variables //
    /////////////////////////////

    var json = false; // Variable used to recieve information by json
    var me = sdata.me; // Contains information about the current user
    var rootel = $("#" + tuid); // Get the main div used by the widget

    // Main-ids
    var videoID = "#video";
    var videoName = "video";

    // Containers
    var videoSettings = videoID + "_settings";
    var videoOutput = videoID + "_maincontainer";
    var videoShowMain = videoID + "_ShowMain";
    var videoTempShowMain = videoID + "_tempYoutubeVideoMain";
    var choosePlayerContainer = videoID + "_choosePlayerContainer";
    var videoPreviewContainer = videoID + "_previewContainer";
    var videoShowPreview = videoID + "_ShowPreview";
    var videoFillInfo = videoID + "_fillInfo";

    // Textboxes
    var videoUrl = videoID + "_txtURL";
    var videoTitle = videoID + "_txtTitle";
    var videoSource = videoID + "_txtSource";

    // Radiobuttons
    var videoSourceRbt = videoName + "_source";
    var videoSourceRbtTxt = videoName + "_txt";
    var videoSourceRbtGuess = videoName + "_guess";

    var videoChoosePlayer = videoName + "_choosePlayer";
    var videoChoosePlayerYoutube = videoName + "_YoutubePlayer";
    var videoChoosePlayerSakai = videoName + "_SakaiPlayer";

    // Checkboxes
    var videoTrackViews = videoID + "_chktrackViews";

    // Template
    var videoTemplate = videoName + "_MainTemplate";

    // Files
    var videoPlayer = "/devwidgets/video/videoplayer.swf";
    var expressInstall = "/devwidgets/video/swf/expressInstall.swf";

    // Buttons
    var videoPlaceholder = videoID + "_insertPlaceHolder";
    var videoSubmit = videoID + "_btnInsertWidget";
    var videoPreview = videoID + "_btnPreview";
    var videoBack = videoID + "_btnBack";

    ////////////////////////
    // Utility  functions //
    ////////////////////////

    /**
	 * This function will clone any JSON-object
	 * @param {Object} the cloned JSON-object
	 */
    var cloneObject = function(object) {
        var clonedObject = {};
        $.extend(true,clonedObject, object);
        return clonedObject;
    };

    //////////////////////
    // Shared functions //
    //////////////////////

    /**
	 * Shows the video in the sakaiplayer
	 * @param {String} video: the url to the video
	 * @param {String} container: the container where the video should be placed (settings or output)
	 */
    var ShowVideoSakaiPlayer = function(video, container) {
        try {
			// Checks if the video is a youtube-video
			// This is needed as a parameter for the sakai-player
            var isTouTube = (video.URL.search("www.youtube.com") !== -1);
			// Renders the video-template (title, source and conatiner to place flash-player in)
            $(container, rootel).html(sdata.html.Template.render(videoTemplate, video));
            // some more parameters needed for the sakai-videoplayer
			var flashvars = {
                videoURL: video.URL,
                isYoutubeUrl: isTouTube
            };
            var params = {
                menu: "false",
                allowScriptAccess: "always",
                scale: "noscale",
                allowFullScreen: "true"
            };
            var attributes = {};
            swfobject.embedSWF(videoPlayer, videoTempShowMain.replace("#", ""), 320, 305, "9.0.0", expressInstall, flashvars, params, attributes);

            //init the youTubeLoader javascript methods
            if (isTouTube) {
				// By putting this variable to the container of the videoPlayer, the streamingprocess for the youtube-video is started
				// This uses the youtubeloader.js file and is the main problem for showing multiple youtubevideos in the sakaiplayer on 1 page
                SWFID = videoTempShowMain.replace("#", "");
            }
        } catch(err) {
            $(videoTempShowMain, rootel).text("No valid video found.");
        }
    };

	/**
	 * Shows the video (YouTube) in the YouTubePlayer
	 * @param {String} video: the url to the video
	 * @param {String} container: the container where the video should be placed (settings or output)
	 */
    var ShowVideoYoutubePlayer = function(video, container) {
        try {
			// Clone the video-object to make some changes for the template
           	var videoTemp = cloneObject(video);
            // a youtube url should look like this:
            // http://www.youtube.com/watch?v=AOWbGfPU5uQ&feature=fvst, you need to get the id (v=)
            // make an object of a the queryString
            var qs = new Querystring(video.URL.split("?")[1]);
            // get the id (default false)
            var id = qs.get("v", false);
            // if the id is false, give a warning the the user
            if (!id) {
                videoTemp.html = ("No valid video found");
            } else {
                videoTemp.html = ('<object width="320" height="305"><param name="movie" value="http://www.youtube.com/v/' + id + '&hl=en&fs=1"></param><param name="allowFullScreen" value="true"></param><embed src="http://www.youtube.com/v/' + id + '&hl=en&fs=1" type="application/x-shockwave-flash" allowfullscreen="true" width="320" height="305"></embed></object>');
            }
			// render the video
		 	$(container, rootel).html(sdata.html.Template.render(videoTemplate, videoTemp));
        } catch(err) {
            $(videoTempShowMain, rootel).text("No valid video found.");
        }
    };

	/**
	 * Shows a video
	 * @param {String} video: url to the video
	 * @param {String} container: the container where the video should be placed (settings or output)
	 * @param {Boolean} isSakaiPlayer: should the video be displayed in a sakai-player or not
	 */
    var showVideo = function(video, container, isSakaiPlayer) {
        if (isSakaiPlayer) {
            ShowVideoSakaiPlayer(video, container);
        }
        else {
            ShowVideoYoutubePlayer(video, container);
        }
    };

    ////////////////////////
    // Settings functions //
    ////////////////////////

	/**
	 * Shows the settings screen
	 * @param {String} response
	 * @param {Boolean} exists
	 */
    var showSettingsScreen = function(response, exists) {
        if (exists) {
			// Fill in the info
            json = json_parse(response);
            $(videoTitle, rootel).val(json.title);
            $(videoUrl, rootel).val(json.URL);
            $("input[name=" + videoSourceRbt + "][value=" + json.selectedvalue + "]", rootel).attr("checked", true);
            $(videoTrackViews, rootel).attr("checked", json.checkviews);
            if (json.selectedvalue === videoSourceRbtTxt) {
                $(videoSource, rootel).val(json.source);
            }
        }
        $(videoOutput, rootel).hide();
        $(videoSettings, rootel).show();
    };

    /**
	 * returns a Json-object of the video-settings
	 */
    var getVideoJson = function() {
        var title = $(videoTitle, rootel).val();
        var selectedValue = $("input[name=" + videoSourceRbt + "]:checked", rootel).val();
        var URL = $(videoUrl, rootel).val();
        var source = "";

		// If the source is checked on guess, then we need to show a proper source
        if (selectedValue === videoSourceRbtGuess) {
            source = URL.replace("http://www.", "");
            source = source.substring(0, source.indexOf("/"));
        }
		// If the source is put to txt, then the user filled in a source himself
        else if (selectedValue === videoSourceRbtTxt) {
            source = $(videoSource, rootel).val();
        }

        var video = {
            "uid": me.preferences.uuid,
            "title": title,
            "source": source,
            "URL": URL,
            "selectedvalue": selectedValue,
            "checkviews": $(videoTrackViews, rootel).attr('checked')
        };
		// Fill in the JSON post object
        video.isYoutube = (video.URL.search("www.youtube.com") !== -1);
        video.isSakaiVideoPlayer = ($("input[name=" + videoChoosePlayer + "]:checked", rootel).val() === videoChoosePlayerSakai);

		// Show the choose-player on the preview screen
		// Because we can't show muliple youtube-videos in the sakai-player on the same page
		// we give the user the possibility to choose between the YouTube-player or the Sakai-player
		// if the url is a youtube url
        if (video.isYoutube) {
            $(choosePlayerContainer, rootel).show();
        }
        else {
            $(choosePlayerContainer, rootel).hide();
        }
        return video;
    };

    /**
	 * add a video
	 * @param {Object} video
	 */
    var addVideo = function(video) {
        var tostring = sdata.JSON.stringify(video);
	 	var saveUrl = Config.URL.SDATA_FETCH_BASIC_URL.replace(/__PLACEMENT__/, placement).replace(/__TUID__/, tuid);
        sdata.widgets.WidgetPreference.save(saveUrl, "video", tostring, sdata.container.informFinish(tuid));
    };

    ////////////////////
    // Main functions //
    ////////////////////

    /**
	 * Shows the video
	 * @param {string} response
	 * @param {Boolean} exists
	 */
    var showVideos = function(response, exists) {
        if (exists) {
            try {
                var video = json_parse(response);
				// Show the video in the right player
                showVideo(video, videoShowMain, video.isSakaiVideoPlayer);
            }
            catch(err) {
                alert("failed to retrieve video.");
            }
        }

    };

    ////////////////////
    // Event Handlers //
    ////////////////////

    /** Bind the insert placeholder button */
    $(videoPlaceholder, rootel).bind("click",
    function(e, ui) {
		// When adding a placeholder we just add an empty video-object
        var video = {
            "uid": me.preferences.uuid,
            "title": "",
            "source": "",
            "URL": "",
            "sourceChose": "",
            "checkviews": "",
            "isSakaiVideoPlayer": true
        }; // Fill in the JSON post object
        addVideo(video);
    });
    /** Bind the inset widget button */
    $(videoSubmit, rootel).bind("click",
    function(e, ui) {
        addVideo(getVideoJson());
    });
    /** Bind the Preview button */
    $(videoPreview, rootel).bind("click",
    function(e, ui) {
        if ($(videoUrl, rootel).val() !== "") {
			// Show and hide screens and buttons
            $(videoPreview, rootel).hide();
            $(videoSubmit, rootel).show();
            $(videoBack, rootel).show();
            $(videoShowPreview, rootel).show();
            $(videoFillInfo, rootel).hide();
            $("input[name=" + videoChoosePlayer + "][value=" + videoChoosePlayerSakai + "]", rootel).attr("checked", true);
            var isYouTube = ($(videoUrl, rootel).val().search("www.youtube.com") !== -1);
            // If the url is a YouTube-video the player should be the YouTubePlayer by default
			if (isYouTube) {
                $("input[name=" + videoChoosePlayer + "][value=" + videoChoosePlayerYoutube + "]", rootel).attr("checked", true);
            }
            showVideo(getVideoJson(), videoPreviewContainer, !isYouTube);
        }
        else {
            alert("Please fill in a URL.");
        }

    });
    /** Bind the back button */
    $(videoBack, rootel).bind("click",
    function(e, ui) {
		// Show and hide screens and buttons
        $(videoPreview, rootel).show();
        $(videoSubmit, rootel).hide();
        $(videoBack, rootel).hide();
        $(videoShowPreview, rootel).hide();
        $(videoFillInfo, rootel).show();
    });
    /** Bind the source radiobuttons */
    $("input[name=" + videoSourceRbt + "][value=" + videoSourceRbtTxt + "]", rootel).bind("click",
    function(e, ui) {
		// If the txt-radiobutton is selected you should give focus to the textbox
        $(videoSource, rootel).focus();
    });
    /** Bind the choose videoplayer radiobuttons */
    $("input[name=" + videoChoosePlayer + "]", rootel).bind("change",
    function(e, ui) {
        var selectedValue = $("input[name=" + videoChoosePlayer + "]:checked", rootel).val();
        if (selectedValue === videoChoosePlayerSakai) {
            showVideo(getVideoJson(), videoPreviewContainer, true);
        }
        else {
            showVideo(getVideoJson(), videoPreviewContainer, false);
        }

    });

    /////////////////////////////
    // Initialisation function //
    /////////////////////////////

    /**
	 * Switch between main and settings page
	 * @param {Boolean} showSettings Show the settings of the widget or not
	 */
	var url = Config.URL.SDATA_FETCH_URL.replace(/__PLACEMENT__/, placement).replace(/__TUID__/, tuid).replace(/__NAME__/, "video") + "?sid=" + Math.random();
    if (showSettings) {
        /** Check if it is an edit or a new video */
        sdata.Ajax.request({
            url: url,
            httpMethod: "GET",
            onSuccess: function(data) {
                showSettingsScreen(data, true);
            },
            onFail: function(status) {
                showSettingsScreen(status, false);
            }
        });

    } else {
        $(videoSettings, rootel).hide();
        $(videoOutput, rootel).show();

        sdata.Ajax.request({
            url: url,
            httpMethod: "GET",
            onSuccess: function(data) {
                showVideos(data, true);
            },
            onFail: function(status) {
                showVideos(status, false);
            }
        });
    }

};
sdata.widgets.WidgetLoader.informOnLoad("video");<commit_msg>SAK-15773 Refactoring video widget<commit_after>/*
 * Licensed to the Sakai Foundation (SF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The SF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 */

/*global $, Config, sdata, Querystring, SWFID, swfobject */

var sakai = sakai || {};

/**
 * Initialize the video widget
 * @param {String} tuid Unique id of the widget
 * @param {String} placement Widget place
 * @param {Boolean} showSettings Show the settings of the widget or not
 */
sakai.video = function(tuid, placement, showSettings) {

    /////////////////////////////
    // Configuration variables //
    /////////////////////////////

    var json = false; // Variable used to recieve information by json
    var me = sdata.me; // Contains information about the current user
    var rootel = $("#" + tuid); // Get the main div used by the widget

    // Main-ids
    var videoID = "#video";
    var videoName = "video";

    // Containers
    var videoSettings = videoID + "_settings";
    var videoOutput = videoID + "_maincontainer";
    var videoShowMain = videoID + "_ShowMain";
    var videoTempShowMain = videoID + "_tempYoutubeVideoMain";
    var choosePlayerContainer = videoID + "_choosePlayerContainer";
    var videoPreviewContainer = videoID + "_previewContainer";
    var videoShowPreview = videoID + "_ShowPreview";
    var videoFillInfo = videoID + "_fillInfo";

    // Textboxes
    var videoUrl = videoID + "_txtURL";
    var videoTitle = videoID + "_txtTitle";
    var videoSource = videoID + "_txtSource";

    // Radiobuttons
    var videoSourceRbt = videoName + "_source";
    var videoSourceRbtTxt = videoName + "_txt";
    var videoSourceRbtGuess = videoName + "_guess";

    var videoChoosePlayer = videoName + "_choosePlayer";
    var videoChoosePlayerYoutube = videoName + "_YoutubePlayer";
    var videoChoosePlayerSakai = videoName + "_SakaiPlayer";

    // Checkboxes
    var videoTrackViews = videoID + "_chktrackViews";

    // Template
    var videoTemplate = videoName + "_MainTemplate";

    // Files
    var videoPlayer = "/devwidgets/video/videoplayer.swf";
    var expressInstall = "/devwidgets/video/swf/expressInstall.swf";

    // Buttons
    var videoPlaceholder = videoID + "_insertPlaceHolder";
    var videoSubmit = videoID + "_btnInsertWidget";
    var videoPreview = videoID + "_btnPreview";
    var videoBack = videoID + "_btnBack";

    ////////////////////////
    // Utility  functions //
    ////////////////////////

    /**
	 * This function will clone any JSON-object
	 * @param {Object} the cloned JSON-object
	 */
    var cloneObject = function(object) {
        var clonedObject = {};
        $.extend(true,clonedObject, object);
        return clonedObject;
    };

    //////////////////////
    // Shared functions //
    //////////////////////

    /**
	 * Shows the video in the sakaiplayer
	 * @param {String} video: the url to the video
	 * @param {String} container: the container where the video should be placed (settings or output)
	 */
    var ShowVideoSakaiPlayer = function(video, container) {
        try {
			// Checks if the video is a youtube-video
			// This is needed as a parameter for the sakai-player
            var isTouTube = (video.URL.search("www.youtube.com") !== -1);
			// Renders the video-template (title, source and conatiner to place flash-player in)
            $(container, rootel).html($.Template.render(videoTemplate, video));
            // some more parameters needed for the sakai-videoplayer
			var flashvars = {
                videoURL: video.URL,
                isYoutubeUrl: isTouTube
            };
            var params = {
                menu: "false",
                allowScriptAccess: "always",
                scale: "noscale",
                allowFullScreen: "true"
            };
            var attributes = {};
            swfobject.embedSWF(videoPlayer, videoTempShowMain.replace("#", ""), 320, 305, "9.0.0", expressInstall, flashvars, params, attributes);

            //init the youTubeLoader javascript methods
            if (isTouTube) {
				// By putting this variable to the container of the videoPlayer, the streamingprocess for the youtube-video is started
				// This uses the youtubeloader.js file and is the main problem for showing multiple youtubevideos in the sakaiplayer on 1 page
                SWFID = videoTempShowMain.replace("#", "");
            }
        } catch(err) {
            $(videoTempShowMain, rootel).text("No valid video found.");
        }
    };

	/**
	 * Shows the video (YouTube) in the YouTubePlayer
	 * @param {String} video: the url to the video
	 * @param {String} container: the container where the video should be placed (settings or output)
	 */
    var ShowVideoYoutubePlayer = function(video, container) {
        try {
			// Clone the video-object to make some changes for the template
           	var videoTemp = cloneObject(video);
            // a youtube url should look like this:
            // http://www.youtube.com/watch?v=AOWbGfPU5uQ&feature=fvst, you need to get the id (v=)
            // make an object of a the queryString
            var qs = new Querystring(video.URL.split("?")[1]);
            // get the id (default false)
            var id = qs.get("v", false);
            // if the id is false, give a warning the the user
            if (!id) {
                videoTemp.html = ("No valid video found");
            } else {
                videoTemp.html = ('<object width="320" height="305"><param name="movie" value="http://www.youtube.com/v/' + id + '&hl=en&fs=1"></param><param name="allowFullScreen" value="true"></param><embed src="http://www.youtube.com/v/' + id + '&hl=en&fs=1" type="application/x-shockwave-flash" allowfullscreen="true" width="320" height="305"></embed></object>');
            }
			// render the video
		 	$(container, rootel).html($.Template.render(videoTemplate, videoTemp));
        } catch(err) {
            $(videoTempShowMain, rootel).text("No valid video found.");
        }
    };

	/**
	 * Shows a video
	 * @param {String} video: url to the video
	 * @param {String} container: the container where the video should be placed (settings or output)
	 * @param {Boolean} isSakaiPlayer: should the video be displayed in a sakai-player or not
	 */
    var showVideo = function(video, container, isSakaiPlayer) {
        if (isSakaiPlayer) {
            ShowVideoSakaiPlayer(video, container);
        }
        else {
            ShowVideoYoutubePlayer(video, container);
        }
    };

    ////////////////////////
    // Settings functions //
    ////////////////////////

	/**
	 * Shows the settings screen
	 * @param {String} response
	 * @param {Boolean} exists
	 */
    var showSettingsScreen = function(response, exists) {
        if (exists) {
			// Fill in the info
            json = $.evalJSON(response);
            $(videoTitle, rootel).val(json.title);
            $(videoUrl, rootel).val(json.URL);
            $("input[name=" + videoSourceRbt + "][value=" + json.selectedvalue + "]", rootel).attr("checked", true);
            $(videoTrackViews, rootel).attr("checked", json.checkviews);
            if (json.selectedvalue === videoSourceRbtTxt) {
                $(videoSource, rootel).val(json.source);
            }
        }
        $(videoOutput, rootel).hide();
        $(videoSettings, rootel).show();
    };

    /**
	 * returns a Json-object of the video-settings
	 */
    var getVideoJson = function() {
        var title = $(videoTitle, rootel).val();
        var selectedValue = $("input[name=" + videoSourceRbt + "]:checked", rootel).val();
        var URL = $(videoUrl, rootel).val();
        var source = "";

		// If the source is checked on guess, then we need to show a proper source
        if (selectedValue === videoSourceRbtGuess) {
            source = URL.replace("http://www.", "");
            source = source.substring(0, source.indexOf("/"));
        }
		// If the source is put to txt, then the user filled in a source himself
        else if (selectedValue === videoSourceRbtTxt) {
            source = $(videoSource, rootel).val();
        }

        var video = {
            "uid": me.preferences.uuid,
            "title": title,
            "source": source,
            "URL": URL,
            "selectedvalue": selectedValue,
            "checkviews": $(videoTrackViews, rootel).attr('checked')
        };
		// Fill in the JSON post object
        video.isYoutube = (video.URL.search("www.youtube.com") !== -1);
        video.isSakaiVideoPlayer = ($("input[name=" + videoChoosePlayer + "]:checked", rootel).val() === videoChoosePlayerSakai);

		// Show the choose-player on the preview screen
		// Because we can't show muliple youtube-videos in the sakai-player on the same page
		// we give the user the possibility to choose between the YouTube-player or the Sakai-player
		// if the url is a youtube url
        if (video.isYoutube) {
            $(choosePlayerContainer, rootel).show();
        }
        else {
            $(choosePlayerContainer, rootel).hide();
        }
        return video;
    };

    /**
	 * add a video
	 * @param {Object} video
	 */
    var addVideo = function(video) {
        var tostring = $.toJSON(video);
	 	var saveUrl = Config.URL.SDATA_FETCH_BASIC_URL.replace(/__PLACEMENT__/, placement).replace(/__TUID__/, tuid);
        sdata.widgets.WidgetPreference.save(saveUrl, "video", tostring, sdata.container.informFinish(tuid));
    };

    ////////////////////
    // Main functions //
    ////////////////////

    /**
	 * Shows the video
	 * @param {string} response
	 * @param {Boolean} exists
	 */
    var showVideos = function(response, exists) {
        if (exists) {
            try {
                var video = $.evalJSON(response);
				// Show the video in the right player
                showVideo(video, videoShowMain, video.isSakaiVideoPlayer);
            }
            catch(err) {
                alert("failed to retrieve video.");
            }
        }

    };

    ////////////////////
    // Event Handlers //
    ////////////////////

    /** Bind the insert placeholder button */
    $(videoPlaceholder, rootel).bind("click",
    function(e, ui) {
		// When adding a placeholder we just add an empty video-object
        var video = {
            "uid": me.preferences.uuid,
            "title": "",
            "source": "",
            "URL": "",
            "sourceChose": "",
            "checkviews": "",
            "isSakaiVideoPlayer": true
        }; // Fill in the JSON post object
        addVideo(video);
    });
    /** Bind the inset widget button */
    $(videoSubmit, rootel).bind("click",
    function(e, ui) {
        addVideo(getVideoJson());
    });
    /** Bind the Preview button */
    $(videoPreview, rootel).bind("click",
    function(e, ui) {
        if ($(videoUrl, rootel).val() !== "") {
			// Show and hide screens and buttons
            $(videoPreview, rootel).hide();
            $(videoSubmit, rootel).show();
            $(videoBack, rootel).show();
            $(videoShowPreview, rootel).show();
            $(videoFillInfo, rootel).hide();
            $("input[name=" + videoChoosePlayer + "][value=" + videoChoosePlayerSakai + "]", rootel).attr("checked", true);
            var isYouTube = ($(videoUrl, rootel).val().search("www.youtube.com") !== -1);
            // If the url is a YouTube-video the player should be the YouTubePlayer by default
			if (isYouTube) {
                $("input[name=" + videoChoosePlayer + "][value=" + videoChoosePlayerYoutube + "]", rootel).attr("checked", true);
            }
            showVideo(getVideoJson(), videoPreviewContainer, !isYouTube);
        }
        else {
            alert("Please fill in a URL.");
        }

    });
    /** Bind the back button */
    $(videoBack, rootel).bind("click",
    function(e, ui) {
		// Show and hide screens and buttons
        $(videoPreview, rootel).show();
        $(videoSubmit, rootel).hide();
        $(videoBack, rootel).hide();
        $(videoShowPreview, rootel).hide();
        $(videoFillInfo, rootel).show();
    });
    /** Bind the source radiobuttons */
    $("input[name=" + videoSourceRbt + "][value=" + videoSourceRbtTxt + "]", rootel).bind("click",
    function(e, ui) {
		// If the txt-radiobutton is selected you should give focus to the textbox
        $(videoSource, rootel).focus();
    });
    /** Bind the choose videoplayer radiobuttons */
    $("input[name=" + videoChoosePlayer + "]", rootel).bind("change",
    function(e, ui) {
        var selectedValue = $("input[name=" + videoChoosePlayer + "]:checked", rootel).val();
        if (selectedValue === videoChoosePlayerSakai) {
            showVideo(getVideoJson(), videoPreviewContainer, true);
        }
        else {
            showVideo(getVideoJson(), videoPreviewContainer, false);
        }

    });

    /////////////////////////////
    // Initialisation function //
    /////////////////////////////

    /**
	 * Switch between main and settings page
	 * @param {Boolean} showSettings Show the settings of the widget or not
	 */
	var url = Config.URL.SDATA_FETCH_URL.replace(/__PLACEMENT__/, placement).replace(/__TUID__/, tuid).replace(/__NAME__/, "video");
    if (showSettings) {
        /** Check if it is an edit or a new video */
        $.ajax({
            url: url,
            cache: false,
            success: function(data) {
                showSettingsScreen(data, true);
            },
            error: function(status) {
                showSettingsScreen(status, false);
            }
        });

    } else {
        $(videoSettings, rootel).hide();
        $(videoOutput, rootel).show();

        $.ajax({
            url: url,
			cache: false,
            success: function(data) {
                showVideos(data, true);
            },
            error: function(status) {
                showVideos(status, false);
            }
        });
    }

};
sdata.widgets.WidgetLoader.informOnLoad("video");<|endoftext|>