+@import 'edumed/edumed.less';
+
.canvas-widget-footnote-handle {
display: inline;
outline: 0px solid transparent;
plugin = {name: 'core', actions: [], canvas: {}, documentExtension: {textNode: {}, documentNode: {}}},
Dialog = require('views/dialog/dialog'),
canvasElements = require('plugins/core/canvasElements'),
- metadataEditor = require('plugins/core/metadataEditor/metadataEditor');
-
+ metadataEditor = require('plugins/core/metadataEditor/metadataEditor'),
+ edumed = require('plugins/core/edumed/edumed');
plugin.documentExtension.textNode.transformations = {
createWrapTextAction({name: 'cite', klass: 'cite', wrapDescription: gettext('Mark as citation'), unwrapDescription: gettext('Remove citation')}),
linkAction,
metadataEditor.action(metadataParams)
-].concat(plugin.actions, templates.actions, footnote.actions, switchTo.actions, lists.actions);
-
+].concat(plugin.actions, templates.actions, footnote.actions, switchTo.actions, lists.actions, edumed.actions);
plugin.config = function(config) {
});
};
-plugin.canvasElements = canvasElements;
+plugin.canvasElements = canvasElements.concat(edumed.canvasElements);
return plugin;
--- /dev/null
+define(function() {
+
+'use strict';
+
+/* globals gettext */
+
+
+var createAction = function(actionConfig) {
+
+ return {
+ name: actionConfig.name,
+ params: {
+ fragment: {type: 'context', name: 'fragment'}
+ },
+ stateDefaults: {
+ icon: actionConfig.icon || null,
+ execute: function(callback, params) {
+ /* globals Node */
+ void(callback);
+
+ var node = params.fragment.node;
+ if(node.nodeType === Node.TEXT_NODE) {
+ node = node.parent();
+ }
+
+ node.document.transaction(function() {
+ var exerciseNode = node.after(node.document.edumedCreateExerciseNode(actionConfig.exercise));
+
+ //@@
+ if(actionConfig.exercise === 'order') {
+ exerciseNode.object.addItem('first item');
+ exerciseNode.contents('.p')[0].append({text: 'Write here...'});
+ }
+ });
+
+ }
+ },
+ getState: function(params) {
+ return {
+ allowed: params.fragment && params.fragment.isValid() && params.fragment instanceof params.fragment.NodeFragment && !params.fragment.node.isInside('exercise'),
+ description: gettext('Insert exercise: ' + actionConfig.exerciseTitle)
+ };
+ }
+ };
+
+};
+
+return [
+ createAction({name: 'insertOrderExercise', icon: 'random', exercise: 'order', exerciseTitle: gettext('Order') })
+];
+
+});
\ No newline at end of file
--- /dev/null
+define(function(require) {
+
+'use strict';
+
+var actions = require('./actions'),
+ orderExerciseElement = require('./order/element');
+
+return {
+ actions: actions,
+ canvasElements: [orderExerciseElement]
+};
+
+});
\ No newline at end of file
--- /dev/null
+@import 'order/order.less';
+
+.edumed-exercise {
+
+ border: 1px lighten(#000, 35%) dashed;
+ margin: 15px 0;
+ padding: 10px;
+
+ .header {
+ font-weight: bold;
+ font-variant: small-caps;
+ margin-bottom: 10px;
+ }
+
+ .btn {
+ line-height: 12px;
+ }
+}
\ No newline at end of file
--- /dev/null
+define(function(require) {
+
+'use strict';
+
+/* globals gettext */
+
+var _ = require('libs/underscore');
+
+var documentElement = require('modules/documentCanvas/canvas/documentElement'),
+ OrderExerciseView = require('./view');
+
+var OrderExerciceElement = Object.create(documentElement.DocumentNodeElement.prototype);
+_.extend(OrderExerciceElement, {
+ init: function() {
+ documentElement.DocumentNodeElement.prototype.init.call(this);
+
+ this.view = new OrderExerciseView(this, this.wlxmlNode);
+ this._container().append(this.view.dom);
+
+ this.view.on('newItemRequested', function() {
+ this.wlxmlNode.document.transaction(function() {
+ var textNode = this.wlxmlNode.object.addItem('');
+ var doc = this.wlxmlNode.document;
+ return doc.createFragment(doc.CaretFragment, {node: textNode, offset:0});
+ }.bind(this), {
+ metadata: {
+ description: gettext('Add item to exercise')
+ },
+ success: function(ret) {
+ this.canvas.select(ret);
+ }.bind(this)
+ });
+ }.bind(this));
+
+ this.view.on('moveAnswer', function(sourceItem, targetItem, where) {
+ this.wlxmlNode.document.transaction(function() {
+ sourceItem.setAnswer(targetItem.getAnswer() + (where === 'before' ? 0 : 1));
+ }, {
+ metadata: {
+ description: gettext('Change solution')
+ }
+ });
+ }.bind(this));
+
+ this.view.on('moveItem', function(sourceItem, targetItem, where) {
+ this.wlxmlNode.document.transaction(function() {
+ targetItem.node[where](sourceItem.node);
+ }, {
+ metadata: {
+ description: gettext('Change order')
+ }
+ });
+
+ }.bind(this));
+
+ var exerciseNode = this.wlxmlNode;
+ this.createContainer(this.wlxmlNode.object.getDescription(), {
+ manages: function(node, removedFrom) {
+ if(node.is('list.orderable')) {
+ return false;
+ }
+ return exerciseNode.sameNode(node.parent() || removedFrom); //!n.hasFollowingSibing(this.params.listnode);
+ },
+ dom: this.view.dom.find('.description')
+ });
+
+ this.reloadView();
+ },
+ onNodeAdded: function(event) {
+ var node = event.meta.node;
+ if(this.wlxmlNode.object.isItemNode(node)) {
+ this.reloadView();
+ }
+ },
+ onNodeAttrChange: function(event) {
+ var node = event.meta.node;
+ if(node.is('item.answer') && node.parent() && node.parent().is('list.orderable')) {
+ this.reloadView();
+ }
+ },
+ reloadView: function() {
+ this.view.clearItems();
+ this.wlxmlNode.object.getItems().forEach(function(item) {
+ this.view.addItem(item);
+ }.bind(this));
+ },
+ getVerticallyFirstTextElement: function() {
+ var toret;
+ this.containers.some(function(container) {
+ toret = container.getVerticallyFirstTextElement();
+ return !!toret;
+ });
+ return toret;
+ }
+});
+
+return {tag: 'div', klass: 'exercise.order', prototype: OrderExerciceElement};
+
+});
--- /dev/null
+.exercise-order {
+
+ overflow-x: hidden;
+
+ .description {
+ margin-bottom: 20px;
+ }
+
+ .wrapper {
+ display: table;
+ width: 100%;
+ border-radius: 5px;
+ background-color: #ddd;
+ -webkit-margin-before: -20px; // compensate for Chrome behavior for ol > li > table
+ }
+
+ .content {
+ display: table-cell;
+ width: calc(~'100% - 40px');
+ padding: 10px 10px;
+ cursor: default;
+
+ .dragEnter {
+ opacity: 0.5;
+ }
+ }
+
+ .handle {
+ vertical-align: middle;
+ display: table-cell;
+ width: 20px;
+ background-color: darken(#ddd, 15%);
+ text-align: center;
+ border-radius: 0 5px 5px 0;
+ padding: 10px 10px;
+ pointer-events: none;
+
+ .dragEnter {
+ opacity: 0.5;
+ }
+ }
+
+ .placeholder {
+ height: 10px;
+ transition: height 0.05s;
+ background-color: white;
+ &.active {
+ height: 60px;
+ transition: height 0.05s;
+ background-color: lighten(@blue,40%);
+ border: 1px dashed @blue;
+ }
+ width:100%;
+ }
+
+ .placeholder-top {
+ margin-left: 25px;
+ }
+
+ li {
+ cursor: move;
+ .dragged {
+ opacity: 0.6;
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+<div class="edumed-exercise exercise-order">
+ <div class="header"><%= gettext('Exercise') %></div>
+ <div class="description"></div>
+ <ul class="nav nav-tabs modePills">
+ <li class="active" mode="initial"><a href="#"><%= gettext('Initial') %></a></li>
+ <li mode="correct"><a href="#"><%= gettext('Solution') %></a></li>
+ </ul>
+ <div class="placeholder placeholder-top"></div>
+ <ol></ol>
+</div>
\ No newline at end of file
--- /dev/null
+define(function(require) {
+
+'use strict';
+var $ = require('libs/jquery');
+
+var _ = require('libs/underscore'),
+ Backbone = require('libs/backbone'),
+ viewTemplate = require('libs/text!./view.html'),
+ viewItemTemplate = require('libs/text!./viewItem.html');
+
+
+var OrderExerciseView = function(element) {
+ this.element = element;
+ this.dom = $(_.template(viewTemplate)());
+ this.modePills = this.dom.find('.modePills');
+ this.list = this.dom.find('ol');
+ this.addButton = this.dom.find('button.add');
+ this.description = this.dom.find('.description');
+ this.itemViews = [];
+
+ this.addButton.on('click', function(e) {
+ e.stopPropagation();
+ e.preventDefault();
+ this.trigger('newItemRequested');
+ //_.last(this.itemViews).editStart();
+ }.bind(this));
+
+ this.modePills.find('a').on('click', function(e) {
+ e.stopPropagation();
+ e.preventDefault();
+ this.setMode($(e.target).parent().attr('mode'));
+ }.bind(this));
+
+ this.mode = 'initial';
+
+ var dropTargets = this.dom.find('.placeholder-top');
+
+ dropTargets.on('dragend', function() {
+ dropTargets.removeClass('dragged');
+ });
+
+ dropTargets.on('dragenter', function() {
+ var first = this.itemViews[0];
+ if(this.mode === 'correct') {
+ first = this.itemViews.slice(0)
+ .sort(function(view1, view2) {
+ if(view1.item.getAnswer() > view2.item.getAnswer()) {
+ return 1;
+ }
+ return -1;
+ })[0];
+ }
+ if(!this.allowDropAt(first, true)) {
+ return;
+ }
+ dropTargets.addClass('active');
+ }.bind(this));
+
+ dropTargets.on('dragleave', function() {
+ dropTargets.removeClass('active');
+ }.bind(this));
+
+ dropTargets.on('dragover', function(e) {
+ e.preventDefault();
+ e.originalEvent.dataTransfer.dropEffect = 'move';
+ });
+
+ dropTargets.on('drop', function(e) {
+ var vid = e.originalEvent.dataTransfer.getData('text');
+ var droppedItem = $('[vid='+vid+']').data('viewInstance');
+
+ var first = this.itemViews[0];
+ if(this.mode === 'correct') {
+ first = this.itemViews.slice(0)
+ .sort(function(view1, view2) {
+ if(view1.item.getAnswer() > view2.item.getAnswer()) {
+ return 1;
+ }
+ return -1;
+ })[0];
+ }
+
+ this.trigger(this.mode === 'initial' ? 'moveItem' : 'moveAnswer', droppedItem.item, first.item, 'before');
+ dropTargets.removeClass('active');
+ }.bind(this));
+};
+_.extend(OrderExerciseView.prototype, Backbone.Events, {
+ addItem: function(item) {
+ var view = new ItemView(item, this);
+ view.on('edit', function(text) {
+ this.trigger('itemEdited', item, text);
+ }.bind(this));
+ view.on('receivedDrop', function(droppedItem) {
+ this.trigger(this.mode === 'initial' ? 'moveItem' : 'moveAnswer', droppedItem.item, item, 'after');
+ }.bind(this));
+ view.on('dragStarted', function(view) {
+ this.draggedView = view;
+ }.bind(this));
+ this.list.append(view.dom);
+ this.itemViews.push(view);
+
+ if(this.mode === 'correct') {
+ this.setMode(this.mode);
+ }
+ },
+ clearItems: function() {
+ this.list.empty();
+ this.itemViews.forEach(function(view) {
+ view.remove();
+ });
+ this.itemViews = [];
+ },
+ setMode: function(mode) {
+ this.modePills.find('li').removeClass('active');
+ this.modePills.find('[mode='+mode+']').addClass('active');
+ this.mode = mode;
+ this.list.children().detach();
+
+ if(this.mode === 'initial') {
+ this.itemViews.forEach(function(itemView) {
+ this.list.append(itemView.dom);
+ }.bind(this));
+ } else {
+ this.itemViews.slice(0)
+ .sort(function(view1, view2) {
+ if(view1.item.getAnswer() > view2.item.getAnswer()) {
+ return 1;
+ }
+ return -1;
+ })
+ .forEach(function(itemView) {
+ this.list.append(itemView.dom);
+ }.bind(this));
+ }
+ },
+ allowDropAt: function(view, up) {
+ var arr = [this.draggedView.dom[0]];
+ if(!up) {
+ arr.push(this.draggedView.dom.prev()[0]);
+ }
+ return !_.contains(arr, view.dom[0]);
+ }
+});
+
+var ItemView = function(item, exerciseView) {
+ this.item = item;
+ this.exerciseView = exerciseView;
+ this.dom = $(_.template(viewItemTemplate)());
+ this.content = this.dom.find('.content');
+
+
+ var dropTargets = this.dom.find('.placeholder'),
+ dragSources = this.dom.find('.wrapper');
+
+ dragSources.on('dragstart', function(e) {
+ this.dom.addClass('dragged');
+ e.originalEvent.dataTransfer.setData('text', this.dom.attr('vid'));
+ e.originalEvent.effectAllowed = 'move';
+ this.trigger('dragStarted', this);
+
+ }.bind(this));
+
+ dropTargets.on('dragend', function() {
+ this.dom.removeClass('dragged');
+ });
+
+ dropTargets.on('dragenter', function() {
+ if(!this.exerciseView.allowDropAt(this)) {
+ return;
+ }
+ dropTargets.addClass('active');
+ }.bind(this));
+
+ dropTargets.on('dragleave', function() {
+ dropTargets.removeClass('active');
+ }.bind(this));
+
+ dropTargets.on('dragover', function(e) {
+ e.preventDefault();
+ e.originalEvent.dataTransfer.dropEffect = 'move';
+ });
+
+ dropTargets.on('drop', function(e) {
+ var vid = e.originalEvent.dataTransfer.getData('text');
+ var droppedItem = $('[vid='+vid+']').data('viewInstance');
+ e.preventDefault();
+ this.trigger('receivedDrop', droppedItem);
+ }.bind(this));
+
+ var content = this.content;
+ this.container = exerciseView.element.createContainer(item.node.contents(), {
+ manages: function(node, originaParent) {
+ return item.node.sameNode(node.parent() || originaParent);
+ },
+ dom: content
+ });
+
+ this.dom.data('viewInstance', this);
+ this.dom.attr('vid', _.uniqueId());
+};
+
+_.extend(ItemView.prototype, Backbone.Events, {
+ remove: function() {
+ this.container.remove();
+ }
+});
+
+return OrderExerciseView;
+
+});
+
+
--- /dev/null
+<li>
+ <div class="wrapper" draggable="true">
+ <div class="content"></div>
+ <div class="handle">
+ <i class="icon-resize-vertical icon-white"></i>
+ </div>
+ </div>
+ <div class="placeholder"></div>
+</li>
\ No newline at end of file