Skip to content

Commit ea4325f

Browse files
authored
Merge pull request #1206 from jcb91/exercise
[exercise,exercise2]
2 parents cd14861 + f1cbfda commit ea4325f

File tree

3 files changed

+200
-314
lines changed

3 files changed

+200
-314
lines changed

src/jupyter_contrib_nbextensions/nbextensions/exercise/main.js

Lines changed: 90 additions & 148 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,15 @@
77
December 6, 2017 @jcb91: use bootstrap 'hidden' class to play nicely with collapsible_headings
88
December 30, 2015: update to 4.1
99
Update december 22, 2015:
10-
Added the metadata solution_first to mark the beginning of an exercise. It is now possible to have several consecutive exercises.
11-
Update october 21-27,2015:
10+
Added the metadata solution_first to mark the beginning of an exercise. It is now possible to have several consecutive exercises.
11+
Update october 21-27,2015:
1212
1- the extension now works with the multicell API, that is
1313
- several cells can be selected either via the rubberband extension
1414
- or via Shift-J (select next) or Shift-K (select previous) keyboard shortcuts
15-
(probably Shit-up and down will work in a near future)
15+
(probably Shit-up and down will work in a near future)
1616
Note: previously, the extension required the selected cells to be marked with a "selected" key in metadata. This is no more necessary with the new API.
1717
Then clicking on the toolbar button transforms these cells into a "solution" which is hidden by default
18-
** Do not forget to keep the Shift key pressed down while clicking on the menu button
18+
** Do not forget to keep the Shift key pressed down while clicking on the menu button
1919
(otherwise selected cells will be lost)**
2020
2- the "state" of solutions, hidden or shown, is saved and restored at reload/restart. We use the "solution" metadata to store the current state.
2121
3- A small issue (infinite loop when a solution was defined at the bottom edge of the notebook have been corrected)
@@ -27,181 +27,123 @@ define([
2727
'jquery',
2828
'require',
2929
'base/js/events',
30-
'nbextensions/rubberband/main'
31-
], function(IPython, $, requirejs, events, rubberband) {
30+
], function(IPython, $, requirejs, events) {
3231
"use strict";
3332

3433
/**
3534
* handle click event
3635
*
3736
* @method click_solution_lock
38-
* @param ev {Event} jquery event
37+
* @param evt {Event} jquery event
3938
*/
40-
function click_solution_lock(ev) {
41-
var cell=IPython.notebook.get_selected_cell();
42-
var cell_index = IPython.notebook.get_selected_index();
43-
var ncells = IPython.notebook.ncells();
44-
var is_locked = cell.element.find('#lock').hasClass('fa-plus-square-o');
45-
if (is_locked == true) {
46-
cell.element.find('#lock').removeClass('fa-plus-square-o');
47-
cell.element.find('#lock').addClass('fa-minus-square-o');
48-
cell.metadata.solution = "shown";
49-
IPython.notebook.select_next();
50-
cell = IPython.notebook.get_selected_cell();
51-
while (cell_index++<ncells & cell.metadata.solution !=undefined & cell.metadata.solution_first !=true) {
52-
cell.element.removeClass('hidden');
53-
cell.metadata.solution = "shown";
54-
IPython.notebook.select_next();
55-
cell = IPython.notebook.get_selected_cell();
56-
}
57-
} else {
58-
cell.element.find('#lock').removeClass('fa-minus-square-o');
59-
cell.element.find('#lock').addClass('fa-plus-square-o');
60-
cell.metadata.solution = "hidden"
61-
IPython.notebook.select_next();
62-
cell = IPython.notebook.get_selected_cell();
63-
while (cell_index++<ncells & cell.metadata.solution !=undefined & cell.metadata.solution_first !=true) {
64-
cell.element.addClass('hidden');
65-
cell.metadata.solution = "hidden"
66-
IPython.notebook.select_next();
67-
cell = IPython.notebook.get_selected_cell();
68-
}
39+
function click_solution_lock(evt) {
40+
var cell = IPython.notebook.get_selected_cell();
41+
var is_locked = cell.metadata.solution === 'hidden';
42+
cell.metadata.solution = is_locked ? 'shown' : 'hidden';
43+
element_set_locked(cell, !is_locked);
44+
cell = IPython.notebook.get_next_cell(cell);
45+
while (cell !== null && cell.metadata.solution !== undefined && !cell.metadata.solution_first) {
46+
cell.element.toggleClass('hidden', !is_locked);
47+
cell.metadata.solution = is_locked ? 'shown' : 'hidden';
48+
cell = IPython.notebook.get_next_cell(cell);
6949
}
70-
}
50+
}
7151

7252
/**
73-
* Hide solutions
53+
* Create or Remove an exercise in selected cells
7454
*
75-
* @method hide_solutions
55+
* @method create_remove_exercise
7656
*
7757
*/
78-
function hide_solutions() {
79-
// first check if lock symbol is already present in selected cell, if yes, remove it
80-
var lcells=IPython.notebook.get_selected_cells(); //list of selected cells
81-
if (typeof IPython.notebook.get_selected_indices == "undefined") { //noteboox 4.1.x
82-
var icells=IPython.notebook.get_selected_cells_indices(); // corresponding indices 4.1.x version
83-
}
84-
else { //notebook 4.0.x
85-
var icells=IPython.notebook.get_selected_indices(); // corresponding indices
86-
}
58+
function create_remove_exercise() {
59+
var lcells = IPython.notebook.get_selected_cells();
8760
// It is possible that no cell is selected
88-
if (lcells.length==0) {alert("Exercise extension: \nPlease select some cells..."); return};
61+
if (lcells.length < 1) {
62+
alert("Exercise extension: \nPlease select some cells...");
63+
return;
64+
}
8965

90-
var cell=lcells[0];
91-
var has_lock = cell.element.find('#lock').is('div');
92-
if (has_lock === true) {
93-
cell.element.find('#lock').remove();
66+
var cell = lcells[0];
67+
if (cell.metadata.solution_first) {
68+
remove_element(cell);
9469
delete cell.metadata.solution_first;
95-
while (cell.metadata.solution != undefined & cell.metadata.solution_first !=true ) {
70+
while (cell !== null && cell.metadata.solution !== undefined && !cell.metadata.solution_first) {
9671
delete cell.metadata.solution;
9772
cell.element.removeClass('hidden');
98-
IPython.notebook.select_next();
99-
cell = IPython.notebook.get_selected_cell()
100-
}
101-
} else {
102-
/*(jfb) --- I do not understand this part... --- It looks for the first selected cell, but we already have the list of selected cells lcells
103-
// find first cell with solution
104-
var start_cell_i; // = undefined
105-
var cells = IPython.notebook.get_cells();
106-
for(var i in cells){
107-
var cell = cells[i];
108-
if (typeof cell.metadata.selected != undefined && cell.metadata.selected === true) {
109-
start_cell_i = i;
110-
console.log("selected start cell:", i);
111-
break
73+
cell = IPython.notebook.get_next_cell(cell);
11274
}
11375
}
114-
IPython.notebook.select(start_cell_i);
115-
*/
116-
// if (cell.metadata.selected == true) { // (jfb) no metadata "selected"
117-
var el = $('<div id="lock" class="fa fa-plus-square-o">');
118-
cell.element.prepend(el);
119-
cell.metadata.solution_first=true;
120-
cell.metadata.solution = "hidden";
121-
cell.element.css({"background-color": "#ffffff"});
122-
el.click(click_solution_lock);
123-
for (var k = 1; k < lcells.length; k++){
124-
cell = lcells[k];
125-
//console.log("new cell:", icells[k]);
126-
cell.element.css({"background-color": "#ffffff"});
127-
cell.element.addClass('hidden');
128-
cell.metadata.solution = "hidden";
129-
}
76+
else {
77+
cell.metadata.solution_first = true;
78+
cell.metadata.solution = 'hidden';
79+
add_element(cell);
80+
for (var k = 1; k < lcells.length; k++) {
81+
cell = lcells[k];
82+
cell.element.addClass('hidden');
83+
cell.metadata.solution = 'hidden';
13084
}
131-
IPython.notebook.select(icells[0]); //select first cell in the list
13285
}
133-
134-
function load_ipython_extension(){
135-
IPython.toolbar.add_buttons_group([
136-
IPython.keyboard_manager.actions.register ({
137-
help : 'Exercise: Create/Remove solutions',
138-
icon : 'fa-mortar-board',
139-
handler : function () {
140-
//console.log(IPython.notebook.get_selected_cells())
141-
hide_solutions();
142-
}
143-
}, 'hide_solutions', 'exercise')
144-
]);
86+
}
14587

146-
/**
147-
* load css file and append to document
148-
*
149-
* @method load_css
150-
* @param name {String} filenaame of CSS file
151-
*
88+
/**
89+
* Add a lock control to the given cell
15290
*/
153-
var load_css = function (name) {
154-
var link = document.createElement("link");
155-
link.type = "text/css";
156-
link.rel = "stylesheet";
157-
link.href = requirejs.toUrl(name);
158-
document.getElementsByTagName("head")[0].appendChild(link);
159-
};
91+
function add_element(cell) {
92+
var ctrl = cell.element.find('.exercise');
93+
if (ctrl.length > 0) return ctrl;
94+
var locked = cell.metadata.solution === 'hidden';
95+
ctrl = $('<div class="exercise fa">')
96+
.prependTo(cell.element)
97+
.on('click', click_solution_lock);
98+
element_set_locked(cell, locked);
99+
return ctrl;
100+
}
160101

161-
load_css('./main.css');
162-
var exercise_wrapper = $('<div id="dragmask" class="highlight-drag"></div>');
163-
$("#header").append(exercise_wrapper);
102+
function remove_element(cell) {
103+
cell.element.find('.exercise').remove();
104+
}
164105

165-
// ***************** Keyboard shortcuts ******************************
166-
var add_cmd_shortcuts = {
167-
'Alt-S': {
168-
help: 'Define Solution (Exercise)',
169-
help_index: 'ht',
170-
handler: function(event) {
171-
hide_solutions();
172-
return false;
173-
}
174-
}
106+
function element_set_locked(cell, locked) {
107+
return cell.element.find('.exercise')
108+
.toggleClass('fa-plus-square-o', locked)
109+
.toggleClass('fa-minus-square-o', !locked);
175110
}
176-
IPython.keyboard_manager.command_shortcuts.add_shortcuts(add_cmd_shortcuts);
177-
178-
/**
179-
* Display existing solutions at startup
180-
*
181-
*/
182-
var cells = IPython.notebook.get_cells();
183-
var found_solution = false;
184-
for(var i in cells){
185-
var cell = cells[i];
186-
if (found_solution == true && typeof cell.metadata.solution != "undefined" && cell.metadata.solution_first !=true) {
187-
cell.element.toggleClass('hidden', cell.metadata.solution === 'hidden');
188-
} else {
189-
found_solution = false
190-
}
191111

192-
if (found_solution == false && typeof cell.metadata.solution != "undefined") {
193-
if (cell.metadata.solution=="hidden") var el = $('<div id="lock" class="fa fa-plus-square-o">');
194-
else var el = $('<div id="lock" class="fa fa-minus-square-o">');
195-
cell.element.prepend(el);
196-
el.click( click_solution_lock);
197-
found_solution = true;
198-
}
112+
function refresh_exercises() {
113+
var in_exercise = false;
114+
IPython.notebook.get_cells().forEach(function(cell) {
115+
if (in_exercise && cell.metadata.solution !== undefined && !cell.metadata.solution_first) {
116+
cell.element.toggleClass('hidden', cell.metadata.solution === 'hidden');
117+
} else {
118+
in_exercise = false;
119+
}
120+
if (!in_exercise && cell.metadata.solution !== undefined) {
121+
in_exercise = true;
122+
add_element(cell);
123+
}
124+
});
199125
}
200126

201-
console.log("Executing rubberband load_ipython")
202-
rubberband.load_ipython_extension();
203-
}
127+
function load_ipython_extension() {
128+
// add css
129+
$('<link rel="stylesheet" type="text/css">')
130+
.attr('href', requirejs.toUrl('./main.css'))
131+
.appendTo('head');
132+
133+
// Hide/display existing solutions at startup
134+
events.on('notebook_loaded.Notebook', refresh_exercises);
135+
if (IPython.notebook._fully_loaded) refresh_exercises();
204136

137+
var action_name = IPython.keyboard_manager.actions.register({
138+
help : 'Exercise: Create/Remove exercise',
139+
help_index: 'ht',
140+
icon : 'fa-mortar-board',
141+
handler : create_remove_exercise
142+
}, 'create_remove_exercise', 'exercise');
143+
144+
IPython.toolbar.add_buttons_group([action_name]);
145+
IPython.keyboard_manager.command_shortcuts.add_shortcuts({'Alt-S': action_name}});
146+
}
205147

206148
return {
207149
load_ipython_extension: load_ipython_extension,

src/jupyter_contrib_nbextensions/nbextensions/exercise2/main.css

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
.exercise2 {
2+
display: flex;
3+
width: 100%;
4+
flex-direction: row;
5+
align-content: flex-end;
6+
}
17

28
.onoffswitch {
39
display: inline;

0 commit comments

Comments
 (0)