diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..83f6e39
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+credentials.json
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 6cbc479..f346d9f 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,6 +1,10 @@
stages:
+ - build-deps
- build
- deploy
+
+variables:
+ GIT_SUBMODULE_STRATEGY: recursive
prepare_folders:
environment: $CI_COMMIT_BRANCH
@@ -8,41 +12,55 @@ prepare_folders:
image: alpine:latest
script:
- mkdir -p .public
- - mkdir -p .public/slides
- - chown -R 1000:1000 .public/slides
+ - mkdir -p .public/docs
+ - chown -R 1000:1000 .public/docs
cache:
paths:
- .public
- artifacts:
- paths:
- - .public
rules:
- if: $CI_COMMIT_BRANCH =~ /^make/
-export:
+render_pdf:
environment: $CI_COMMIT_BRANCH
- variables:
- WORK_PATH: "public/$CI_COMMIT_BRANCH"
- stage: build
+ stage: build-deps
when: manual
image:
name: astefanutti/decktape
entrypoint: ["/bin/sh", "-c"]
script:
- - "node /decktape/decktape.js -s 2560x1440 reveal --chrome-path chromium-browser --chrome-arg=--no-sandbox --chrome-arg=--disable-web-security index.html .public/slides/slide.pdf"
+ - "node /decktape/decktape.js -s 2560x1440 reveal --chrome-path chromium-browser --chrome-arg=--no-sandbox --chrome-arg=--disable-web-security index.html .public/docs/slide.pdf"
cache:
paths:
- .public
artifacts:
paths:
- - .public/slides
+ - .public/docs
+ expire_in: 2 week
+ rules:
+ - if: $CI_COMMIT_BRANCH =~ /^make/
+
+render_ppt:
+ needs: [render_pdf]
+ environment: $CI_COMMIT_BRANCH
+ stage: build
+ when: manual
+ image:
+ name: linuxserver/libreoffice
+ entrypoint: ["/bin/sh", "-c"]
+ script:
+ - 'soffice --headless --infilter="impress_pdf_import" --convert-to "pptx:Impress Office Open XML" .public/docs/slide.pdf --outdir .public/docs/'
+ cache:
+ paths:
+ - .public
+ artifacts:
+ paths:
+ - .public/docs
+ expire_in: 2 week
rules:
- if: $CI_COMMIT_BRANCH =~ /^make/
pages:
environment: $CI_COMMIT_BRANCH
- variables:
- GIT_SUBMODULE_STRATEGY: recursive
stage: deploy
when: manual
script:
@@ -53,9 +71,5 @@ pages:
paths:
- .public
- public
- artifacts:
- paths:
- - public
rules:
- if: $CI_COMMIT_BRANCH =~ /^make/
-
diff --git a/custom_plugins/chalkboard/README.md b/custom_plugins/chalkboard/README.md
new file mode 100644
index 0000000..8adefe5
--- /dev/null
+++ b/custom_plugins/chalkboard/README.md
@@ -0,0 +1,163 @@
+# Chalkboard
+
+With this plugin you can add a chalkboard to reveal.js. The plugin provides two possibilities to include handwritten notes to your presentation:
+
+- you can make notes directly on the slides, e.g. to comment on certain aspects,
+- you can open a chalkboard or whiteboard on which you can make notes.
+
+The main use case in mind when implementing the plugin is classroom usage in which you may want to explain some course content and quickly need to make some notes.
+
+The plugin records all drawings made so that they can be play backed using the `autoSlide` feature or the `audio-slideshow` plugin.
+
+
+[Check out the demo](https://rajgoel.github.io/reveal.js-demos/?topic=chalkboard)
+
+## Setup
+
+To use the plugin include
+```html
+
+
+
+
+
+
+
+
+
+```
+to the header of your presentation and configure reveal.js and the plugin by
+
+```js
+Reveal.initialize({
+ customcontrols: {
+ controls: [
+ { icon: '',
+ title: 'Toggle chalkboard (B)',
+ action: 'RevealChalkboard.toggleChalkboard();'
+ },
+ { icon: '',
+ title: 'Toggle notes canvas (C)',
+ action: 'RevealChalkboard.toggleNotesCanvas();'
+ }
+ ]
+ },
+ chalkboard: {
+ // add configuration here
+ },
+ // ...
+ plugins: [ RevealChalkboard, RevealCustomControls ],
+ // ...
+});
+```
+
+In order to include buttons for opening and closing the notes canvas or the chalkboard you should make sure that `font-awesome` is available. The easiest way is to include
+```
+
+```
+to the ```head``` section of you HTML-file.
+
+## Usage
+
+### Mouse or touch
+- Click on the pen symbols at the bottom left to toggle the notes canvas or chalkboard
+- Click on the color picker at the left to change the color (the color picker is only visible if the notes canvas or chalkboard is active)
+- Click on the up/down arrows on the left to the switch among multiple chalkboardd (the up/down arrows are only available for the chlakboard)
+- Click the left mouse button and drag to write on notes canvas or chalkboard
+- Click the right mouse button and drag to wipe away previous drawings
+- Touch and move to write on notes canvas or chalkboard
+
+### Keyboard
+- Press the 'BACKSPACE' key to delete all chalkboard drawings
+- Press the 'DEL' key to clear the notes canvas or chalkboard
+- Press the 'c' key to toggle the notes canvas
+- Press the 'b' key to toggle the chalkboard
+- Press the 'd' key to download drawings
+- Press the 'x' key to cycle colors forward
+- Press the 'y' key to cycle colors backward
+
+## Playback
+
+If the `autoSlide` feature is set or if the `audio-slideshow` plugin is used, pre-recorded chalkboard drawings can be played. The slideshow plays back the user interaction with the chalkboard in the same way as it was conducted when recording the data.
+
+## Multiplexing
+
+The plugin supports multiplexing via the [`multiplex` plugin](https://github.com/reveal/multiplex) or the [`seminar` plugin](https://github.com/rajgoel/reveal.js-plugins/tree/master/seminar).
+
+## PDF-Export
+
+If the slideshow is opened in [print mode](https://revealjs.com/pdf-export/), the chalkboard drawings in the session storage (see `storage` option - print version must be opened in the same tab or window as the original slideshow) or provided in a file (see `src` option) are included in the PDF-file. Each drawing on the chalkboard is added after the slide that was shown when opening the chalkboard. Drawings on the notes canvas are not included in the PDF-file.
+
+
+## Configuration
+
+The plugin has several configuration options:
+
+- ```boardmarkerWidth```: an integer, the drawing width of the boardmarker; larger values draw thicker lines.
+- ```chalkWidth```: an integer, the drawing width of the chalk; larger values draw thicker lines.
+- ```chalkEffect```: a float in the range ```[0.0, 1.0]```, the intesity of the chalk effect on the chalk board. Full effect (default) ```1.0```, no effect ```0.0```.
+- ```storage```: Optional variable name for session storage of drawings.
+- ```src```: Optional filename for pre-recorded drawings.
+- ```readOnly```: Configuation option allowing to prevent changes to existing drawings. If set to ```true``` no changes can be made, if set to false ```false``` changes can be made, if unset or set to ```undefined``` no changes to the drawings can be made after returning to a slide or fragment for which drawings had been recorded before. In any case the recorded drawings for a slide or fragment can be cleared by pressing the 'DEL' key (i.e. by using the ```RevealChalkboard.clear()``` function).
+- ```transition```: Gives the duration (in milliseconds) of the transition for a slide change, so that the notes canvas is drawn after the transition is completed.
+- ```theme```: Can be set to either ```"chalkboard"``` or ```"whiteboard"```.
+
+The following configuration options allow to change the appearance of the notes canvas and the chalkboard. All of these options require two values, the first gives the value for the notes canvas, the second for the chalkboard.
+
+- ```background```: The first value expects a (semi-)transparent color which is used to provide visual feedback that the notes canvas is enabled, the second value expects a filename to a background image for the chalkboard.
+- ```grid```: By default whiteboard and chalkboard themes include a grid pattern on the background. This pattern can be modified by setting the color, the distance between lines, and the line width, e.g. ```{ color: 'rgb(127,127,255,0.1)', distance: 40, width: 2}```. Alternatively, the grid can be removed by setting the value to ```false```.
+- ```eraser```: An image path and radius for the eraser.
+- ```boardmarkers```: A list of boardmarkers with given color and cursor.
+- ```chalks```: A list of chalks with given color and cursor.
+- ```rememberColor```: Whether to remember the last selected color for the slide canvas or the board.
+
+All of the configurations are optional and the default values shown below are used if the options are not provided.
+
+```javascript
+Reveal.initialize({
+ // ...
+ chalkboard: {
+ boardmarkerWidth: 3,
+ chalkWidth: 7,
+ chalkEffect: 1.0,
+ storage: null,
+ src: null,
+ readOnly: undefined,
+ transition: 800,
+ theme: "chalkboard",
+ background: [ 'rgba(127,127,127,.1)' , path + 'img/blackboard.png' ],
+ grid: { color: 'rgb(50,50,10,0.5)', distance: 80, width: 2},
+ eraser: { src: path + 'img/sponge.png', radius: 20},
+ boardmarkers : [
+ { color: 'rgba(100,100,100,1)', cursor: 'url(' + path + 'img/boardmarker-black.png), auto'},
+ { color: 'rgba(30,144,255, 1)', cursor: 'url(' + path + 'img/boardmarker-blue.png), auto'},
+ { color: 'rgba(220,20,60,1)', cursor: 'url(' + path + 'img/boardmarker-red.png), auto'},
+ { color: 'rgba(50,205,50,1)', cursor: 'url(' + path + 'img/boardmarker-green.png), auto'},
+ { color: 'rgba(255,140,0,1)', cursor: 'url(' + path + 'img/boardmarker-orange.png), auto'},
+ { color: 'rgba(150,0,20150,1)', cursor: 'url(' + path + 'img/boardmarker-purple.png), auto'},
+ { color: 'rgba(255,220,0,1)', cursor: 'url(' + path + 'img/boardmarker-yellow.png), auto'}
+ ],
+ chalks: [
+ { color: 'rgba(255,255,255,0.5)', cursor: 'url(' + path + 'img/chalk-white.png), auto'},
+ { color: 'rgba(96, 154, 244, 0.5)', cursor: 'url(' + path + 'img/chalk-blue.png), auto'},
+ { color: 'rgba(237, 20, 28, 0.5)', cursor: 'url(' + path + 'img/chalk-red.png), auto'},
+ { color: 'rgba(20, 237, 28, 0.5)', cursor: 'url(' + path + 'img/chalk-green.png), auto'},
+ { color: 'rgba(220, 133, 41, 0.5)', cursor: 'url(' + path + 'img/chalk-orange.png), auto'},
+ { color: 'rgba(220,0,220,0.5)', cursor: 'url(' + path + 'img/chalk-purple.png), auto'},
+ { color: 'rgba(255,220,0,0.5)', cursor: 'url(' + path + 'img/chalk-yellow.png), auto'}
+ ]
+ },
+ // ...
+
+});
+```
+
+## Credits
+
+The chalkboard effect is based on [Chalkboard](https://github.com/mmoustafa/Chalkboard) by Mohamed Moustafa.
+
+## License
+
+MIT licensed
+
+Copyright (C) 2023 Asvin Goel
diff --git a/custom_plugins/chalkboard/img/blackboard.png b/custom_plugins/chalkboard/img/blackboard.png
new file mode 100644
index 0000000..50a2f64
Binary files /dev/null and b/custom_plugins/chalkboard/img/blackboard.png differ
diff --git a/custom_plugins/chalkboard/img/boardmarker-black.png b/custom_plugins/chalkboard/img/boardmarker-black.png
new file mode 100644
index 0000000..170b520
Binary files /dev/null and b/custom_plugins/chalkboard/img/boardmarker-black.png differ
diff --git a/custom_plugins/chalkboard/img/boardmarker-blue.png b/custom_plugins/chalkboard/img/boardmarker-blue.png
new file mode 100644
index 0000000..32f3ff8
Binary files /dev/null and b/custom_plugins/chalkboard/img/boardmarker-blue.png differ
diff --git a/custom_plugins/chalkboard/img/boardmarker-green.png b/custom_plugins/chalkboard/img/boardmarker-green.png
new file mode 100644
index 0000000..28a4221
Binary files /dev/null and b/custom_plugins/chalkboard/img/boardmarker-green.png differ
diff --git a/custom_plugins/chalkboard/img/boardmarker-orange.png b/custom_plugins/chalkboard/img/boardmarker-orange.png
new file mode 100644
index 0000000..7e8c2d2
Binary files /dev/null and b/custom_plugins/chalkboard/img/boardmarker-orange.png differ
diff --git a/custom_plugins/chalkboard/img/boardmarker-purple.png b/custom_plugins/chalkboard/img/boardmarker-purple.png
new file mode 100644
index 0000000..637066c
Binary files /dev/null and b/custom_plugins/chalkboard/img/boardmarker-purple.png differ
diff --git a/custom_plugins/chalkboard/img/boardmarker-red.png b/custom_plugins/chalkboard/img/boardmarker-red.png
new file mode 100644
index 0000000..713d6cd
Binary files /dev/null and b/custom_plugins/chalkboard/img/boardmarker-red.png differ
diff --git a/custom_plugins/chalkboard/img/boardmarker-yellow.png b/custom_plugins/chalkboard/img/boardmarker-yellow.png
new file mode 100644
index 0000000..23e87f5
Binary files /dev/null and b/custom_plugins/chalkboard/img/boardmarker-yellow.png differ
diff --git a/custom_plugins/chalkboard/img/chalk-blue.png b/custom_plugins/chalkboard/img/chalk-blue.png
new file mode 100644
index 0000000..f70c299
Binary files /dev/null and b/custom_plugins/chalkboard/img/chalk-blue.png differ
diff --git a/custom_plugins/chalkboard/img/chalk-green.png b/custom_plugins/chalkboard/img/chalk-green.png
new file mode 100644
index 0000000..39f3b20
Binary files /dev/null and b/custom_plugins/chalkboard/img/chalk-green.png differ
diff --git a/custom_plugins/chalkboard/img/chalk-orange.png b/custom_plugins/chalkboard/img/chalk-orange.png
new file mode 100644
index 0000000..488c847
Binary files /dev/null and b/custom_plugins/chalkboard/img/chalk-orange.png differ
diff --git a/custom_plugins/chalkboard/img/chalk-purple.png b/custom_plugins/chalkboard/img/chalk-purple.png
new file mode 100644
index 0000000..5bd29fb
Binary files /dev/null and b/custom_plugins/chalkboard/img/chalk-purple.png differ
diff --git a/custom_plugins/chalkboard/img/chalk-red.png b/custom_plugins/chalkboard/img/chalk-red.png
new file mode 100644
index 0000000..18d4dc7
Binary files /dev/null and b/custom_plugins/chalkboard/img/chalk-red.png differ
diff --git a/custom_plugins/chalkboard/img/chalk-white.png b/custom_plugins/chalkboard/img/chalk-white.png
new file mode 100644
index 0000000..fed89a3
Binary files /dev/null and b/custom_plugins/chalkboard/img/chalk-white.png differ
diff --git a/custom_plugins/chalkboard/img/chalk-yellow.png b/custom_plugins/chalkboard/img/chalk-yellow.png
new file mode 100644
index 0000000..0186bec
Binary files /dev/null and b/custom_plugins/chalkboard/img/chalk-yellow.png differ
diff --git a/custom_plugins/chalkboard/img/sponge.png b/custom_plugins/chalkboard/img/sponge.png
new file mode 100644
index 0000000..cbfb269
Binary files /dev/null and b/custom_plugins/chalkboard/img/sponge.png differ
diff --git a/custom_plugins/chalkboard/img/whiteboard.png b/custom_plugins/chalkboard/img/whiteboard.png
new file mode 100644
index 0000000..dbf570a
Binary files /dev/null and b/custom_plugins/chalkboard/img/whiteboard.png differ
diff --git a/custom_plugins/chalkboard/plugin.js b/custom_plugins/chalkboard/plugin.js
new file mode 100644
index 0000000..2672110
--- /dev/null
+++ b/custom_plugins/chalkboard/plugin.js
@@ -0,0 +1,1973 @@
+/*****************************************************************
+ ** Author: Asvin Goel, goel@telematique.eu
+ **
+ ** A plugin for reveal.js adding a chalkboard.
+ **
+ ** Version: 2.3.3
+ **
+ ** License: MIT license (see LICENSE.md)
+ **
+ ** Credits:
+ ** Chalkboard effect by Mohamed Moustafa https://github.com/mmoustafa/Chalkboard
+ ** Multi color support initially added by Kurt Rinnert https://github.com/rinnert
+ ** Compatibility with reveal.js v4 by Hakim El Hattab https://github.com/hakimel
+ ******************************************************************/
+
+"use strict";
+
+window.RevealChalkboard = window.RevealChalkboard || {
+ id: 'RevealChalkboard',
+ init: function ( deck ) {
+ initChalkboard.call(this, deck );
+ },
+ configure: function ( config ) {
+ configure( config );
+ },
+ toggleNotesCanvas: function () {
+ toggleNotesCanvas();
+ },
+ toggleChalkboard: function () {
+ toggleChalkboard();
+ },
+ colorIndex: function () {
+ colorIndex();
+ },
+ colorNext: function () {
+ colorNext();
+ },
+ colorPrev: function () {
+ colorPrev();
+ },
+ clear: function () {
+ clear();
+ },
+ reset: function () {
+ reset();
+ },
+ resetAll: function () {
+ resetAll();
+ },
+ updateStorage: function () {
+ updateStorage();
+ },
+ getData: function () {
+ return getData();
+ },
+ download: function () {
+ download();
+ },
+};
+
+function scriptPath() {
+ // obtain plugin path from the script element
+ var src;
+ if ( document.currentScript ) {
+ src = document.currentScript.src;
+ } else {
+ var sel = document.querySelector( 'script[src$="/chalkboard/plugin.js"]' )
+ if ( sel ) {
+ src = sel.src;
+ }
+ }
+ var path = ( src === undefined ) ? "" : src.slice( 0, src.lastIndexOf( "/" ) + 1 );
+//console.log("Path: " + path);
+ return path;
+}
+var path = scriptPath();
+
+const initChalkboard = function ( Reveal ) {
+//console.warn(path);
+ /* Feature detection for passive event handling*/
+ var passiveSupported = false;
+
+ try {
+ window.addEventListener( 'test', null, Object.defineProperty( {}, 'passive', {
+ get: function () {
+ passiveSupported = true;
+ }
+ } ) );
+ } catch ( err ) {}
+
+
+/*****************************************************************
+ ** Configuration
+ ******************************************************************/
+ var background, pens, draw, color;
+ var grid = false;
+ var boardmarkerWidth = 3;
+ var chalkWidth = 7;
+ var chalkEffect = 1.0;
+ var rememberColor = [ true, false ];
+ var eraser = {
+ src: path + 'img/sponge.png',
+ radius: 20
+ };
+ var boardmarkers = [ {
+ color: 'rgba(100,100,100,1)',
+ cursor: 'url(' + path + 'img/boardmarker-black.png), auto'
+ },
+ {
+ color: 'rgba(30,144,255, 1)',
+ cursor: 'url(' + path + 'img/boardmarker-blue.png), auto'
+ },
+ {
+ color: 'rgba(220,20,60,1)',
+ cursor: 'url(' + path + 'img/boardmarker-red.png), auto'
+ },
+ {
+ color: 'rgba(50,205,50,1)',
+ cursor: 'url(' + path + 'img/boardmarker-green.png), auto'
+ },
+ {
+ color: 'rgba(255,140,0,1)',
+ cursor: 'url(' + path + 'img/boardmarker-orange.png), auto'
+ },
+ {
+ color: 'rgba(150,0,20150,1)',
+ cursor: 'url(' + path + 'img/boardmarker-purple.png), auto'
+ },
+ {
+ color: 'rgba(255,220,0,1)',
+ cursor: 'url(' + path + 'img/boardmarker-yellow.png), auto'
+ }
+ ];
+ var chalks = [ {
+ color: 'rgba(255,255,255,0.5)',
+ cursor: 'url(' + path + 'img/chalk-white.png), auto'
+ },
+ {
+ color: 'rgba(96, 154, 244, 0.5)',
+ cursor: 'url(' + path + 'img/chalk-blue.png), auto'
+ },
+ {
+ color: 'rgba(237, 20, 28, 0.5)',
+ cursor: 'url(' + path + 'img/chalk-red.png), auto'
+ },
+ {
+ color: 'rgba(20, 237, 28, 0.5)',
+ cursor: 'url(' + path + 'img/chalk-green.png), auto'
+ },
+ {
+ color: 'rgba(220, 133, 41, 0.5)',
+ cursor: 'url(' + path + 'img/chalk-orange.png), auto'
+ },
+ {
+ color: 'rgba(220,0,220,0.5)',
+ cursor: 'url(' + path + 'img/chalk-purple.png), auto'
+ },
+ {
+ color: 'rgba(255,220,0,0.5)',
+ cursor: 'url(' + path + 'img/chalk-yellow.png), auto'
+ }
+ ];
+
+ var sponge = {
+ cursor: 'url(' + path + 'img/sponge.png), auto'
+ }
+
+
+ var keyBindings = {
+ toggleNotesCanvas: {
+ keyCode: 67,
+ key: 'C',
+ description: 'Toggle notes canvas'
+ },
+ toggleChalkboard: {
+ keyCode: 66,
+ key: 'B',
+ description: 'Toggle chalkboard'
+ },
+ clear: {
+ keyCode: 46,
+ key: 'DEL',
+ description: 'Clear drawings on slide'
+ },
+/*
+ reset: {
+ keyCode: 173,
+ key: '-',
+ description: 'Reset drawings on slide'
+ },
+*/
+ resetAll: {
+ keyCode: 8,
+ key: 'BACKSPACE',
+ description: 'Reset all drawings'
+ },
+ colorNext: {
+ keyCode: 88,
+ key: 'X',
+ description: 'Next color'
+ },
+ colorPrev: {
+ keyCode: 89,
+ key: 'Y',
+ description: 'Previous color'
+ },
+ download: {
+ keyCode: 68,
+ key: 'D',
+ description: 'Download drawings'
+ }
+ };
+
+
+ var theme = 'chalkboard';
+ var color = [ 0, 0 ];
+ var toggleChalkboardButton = false;
+ var toggleNotesButton = false;
+ var colorButtons = true;
+ var boardHandle = true;
+ var transition = 800;
+
+ var readOnly = false;
+ var messageType = 'broadcast';
+
+ var config = configure( Reveal.getConfig().chalkboard || {} );
+ if ( config.keyBindings ) {
+ for ( var key in config.keyBindings ) {
+ keyBindings[ key ] = config.keyBindings[ key ];
+ };
+ }
+
+ function configure( config ) {
+
+ if ( config.boardmarkerWidth || config.penWidth ) boardmarkerWidth = config.boardmarkerWidth || config.penWidth;
+ if ( config.chalkWidth ) chalkWidth = config.chalkWidth;
+ if ( config.chalkEffect ) chalkEffect = config.chalkEffect;
+ if ( config.rememberColor ) rememberColor = config.rememberColor;
+ if ( config.eraser ) eraser = config.eraser;
+ if ( config.boardmarkers ) boardmarkers = config.boardmarkers;
+ if ( config.chalks ) chalks = config.chalks;
+
+ if ( config.theme ) theme = config.theme;
+ switch ( theme ) {
+ case 'whiteboard':
+ background = [ 'rgba(127,127,127,.1)', path + 'img/whiteboard.png' ];
+ draw = [ drawWithBoardmarker, drawWithBoardmarker ];
+ pens = [ boardmarkers, boardmarkers ];
+ grid = {
+ color: 'rgb(127,127,255,0.1)',
+ distance: 40,
+ width: 2
+ };
+ break;
+ case 'chalkboard':
+ default:
+ background = [ 'rgba(127,127,127,.1)', path + 'img/blackboard.png' ];
+ draw = [ drawWithBoardmarker, drawWithChalk ];
+ pens = [ boardmarkers, chalks ];
+ grid = {
+ color: 'rgb(50,50,10,0.5)',
+ distance: 80,
+ width: 2
+ };
+ }
+
+ if ( config.background ) background = config.background;
+ if ( config.grid != undefined ) grid = config.grid;
+
+ if ( config.toggleChalkboardButton != undefined ) toggleChalkboardButton = config.toggleChalkboardButton;
+ if ( config.toggleNotesButton != undefined ) toggleNotesButton = config.toggleNotesButton;
+ if ( config.colorButtons != undefined ) colorButtons = config.colorButtons;
+ if ( config.boardHandle != undefined ) boardHandle = config.boardHandle;
+ if ( config.transition ) transition = config.transition;
+
+ if ( config.readOnly != undefined ) readOnly = config.readOnly;
+ if ( config.messageType ) messageType = config.messageType;
+
+ if ( drawingCanvas && ( config.theme || config.background || config.grid ) ) {
+ var canvas = document.getElementById( drawingCanvas[ 1 ].id );
+ canvas.style.background = 'url("' + background[ 1 ] + '") repeat';
+ clearCanvas( 1 );
+ drawGrid();
+ }
+
+ return config;
+ }
+/*****************************************************************
+ ** Setup
+ ******************************************************************/
+
+ function whenReady( callback ) {
+ // wait for markdown to be parsed and code to be highlighted
+ if ( !document.querySelector( 'section[data-markdown]:not([data-markdown-parsed])' )
+ && !document.querySelector( '[data-load]:not([data-loaded])')
+ && !document.querySelector( 'code[data-line-numbers*="|"]')
+ ) {
+ callback();
+ } else {
+ console.log( "Wait for external sources to be loaded and code to be highlighted" );
+ setTimeout( whenReady, 500, callback )
+ }
+ }
+
+ function whenLoaded( callback ) {
+ // wait for drawings to be loaded and markdown to be parsed
+ if ( loaded !== null ) {
+ callback();
+ } else {
+ console.log( "Wait for drawings to be loaded" );
+ setTimeout( whenLoaded, 500, callback )
+ }
+ }
+
+ var drawingCanvas = [ {
+ id: 'notescanvas'
+ }, {
+ id: 'chalkboard'
+ } ];
+ setupDrawingCanvas( 0 );
+ setupDrawingCanvas( 1 );
+
+ var mode = 0; // 0: notes canvas, 1: chalkboard
+ var board = 0; // board index (only for chalkboard)
+
+ var mouseX = 0;
+ var mouseY = 0;
+ var lastX = null;
+ var lastY = null;
+
+ var drawing = false;
+ var erasing = false;
+
+ var slideStart = Date.now();
+ var slideIndices = {
+ h: 0,
+ v: 0
+ };
+
+ var timeouts = [
+ [],
+ []
+ ];
+ var slidechangeTimeout = null;
+ var updateStorageTimeout = null;
+ var playback = false;
+
+ function changeCursor( element, tool ) {
+ element.style.cursor = tool.cursor;
+ var palette = document.querySelector('.palette[data-mode="' + mode + '"]');
+ if ( palette ) {
+ palette.style.cursor = tool.cursor;
+ }
+ }
+
+ function createPalette( colors, length ) {
+ if ( length === true || length > colors.length ) {
+ length = colors.length;
+ }
+ var palette = document.createElement( 'div' );
+ palette.classList.add( 'palette' );
+ var list = document.createElement( 'ul' );
+ // color pickers
+ for ( var i = 0; i < length; i++ ) {
+ var colorButton = document.createElement( 'li' );
+ colorButton.setAttribute( 'data-color', i );
+ colorButton.innerHTML = '';
+ colorButton.style.color = colors[ i ].color;
+ colorButton.addEventListener( 'click', function ( e ) {
+ var element = e.target;
+ while ( !element.hasAttribute( 'data-color' ) ) {
+ element = element.parentElement;
+ }
+ colorIndex( parseInt( element.getAttribute( 'data-color' ) ) );
+ } );
+ colorButton.addEventListener( 'touchstart', function ( e ) {
+ var element = e.target;
+ while ( !element.hasAttribute( 'data-color' ) ) {
+ element = element.parentElement;
+ }
+ colorIndex( parseInt( element.getAttribute( 'data-color' ) ) );
+ } );
+ list.appendChild( colorButton );
+ }
+ // eraser
+ var eraserButton = document.createElement( 'li' );
+ eraserButton.setAttribute( 'data-eraser', 'true' );
+ var spongeImg = document.createElement( 'img' );
+ spongeImg.src = eraser.src;
+ spongeImg.height = "24";
+ spongeImg.width = "24";
+ spongeImg.style.marginTop = '10px';
+ spongeImg.style.marginRight = '0';
+ spongeImg.style.marginBottom = '0';
+ spongeImg.style.marginLeft = '0';
+ eraserButton.appendChild(spongeImg);
+ eraserButton.addEventListener( 'click', function ( e ) {
+ colorIndex( -1 );
+ } );
+ eraserButton.addEventListener( 'touchstart', function ( e ) {
+ colorIndex( -1 );
+ } );
+ list.appendChild( eraserButton );
+
+ palette.appendChild( list );
+ return palette;
+ };
+
+ function switchBoard( boardIdx ) {
+ selectBoard( boardIdx, true );
+ // broadcast
+ var message = new CustomEvent( messageType );
+ message.content = {
+ sender: 'chalkboard-plugin',
+ type: 'selectboard',
+ timestamp: Date.now() - slideStart,
+ mode,
+ board
+ };
+ document.dispatchEvent( message );
+ }
+
+ function setupDrawingCanvas( id ) {
+ var container = document.createElement( 'div' );
+ container.id = drawingCanvas[ id ].id;
+ container.classList.add( 'overlay' );
+ container.setAttribute( 'data-prevent-swipe', 'true' );
+ container.oncontextmenu = function () {
+ return false;
+ }
+
+ changeCursor( container, pens[ id ][ color[ id ] ] );
+
+ drawingCanvas[ id ].width = window.innerWidth;
+ drawingCanvas[ id ].height = window.innerHeight;
+ drawingCanvas[ id ].scale = 1;
+ drawingCanvas[ id ].xOffset = 0;
+ drawingCanvas[ id ].yOffset = 0;
+
+ if ( id == "0" ) {
+ container.style.background = 'rgba(0,0,0,0)';
+ container.style.zIndex = 24;
+ container.style.opacity = 1;
+ container.style.visibility = 'visible';
+ container.style.pointerEvents = 'none';
+
+ var slides = document.querySelector( '.slides' );
+ var aspectRatio = Reveal.getConfig().width / Reveal.getConfig().height;
+ if ( drawingCanvas[ id ].width > drawingCanvas[ id ].height * aspectRatio ) {
+ drawingCanvas[ id ].xOffset = ( drawingCanvas[ id ].width - drawingCanvas[ id ].height * aspectRatio ) / 2;
+ } else if ( drawingCanvas[ id ].height > drawingCanvas[ id ].width / aspectRatio ) {
+ drawingCanvas[ id ].yOffset = ( drawingCanvas[ id ].height - drawingCanvas[ id ].width / aspectRatio ) / 2;
+ }
+
+ if ( colorButtons ) {
+ var palette = createPalette( boardmarkers, colorButtons );
+ palette.dataset.mode = id;
+ palette.style.visibility = 'hidden'; // only show palette in drawing mode
+ container.appendChild( palette );
+ }
+ } else {
+ container.style.background = 'url("' + background[ id ] + '") repeat';
+ container.style.zIndex = 26;
+ container.style.opacity = 0;
+ container.style.visibility = 'hidden';
+
+ if ( colorButtons ) {
+ var palette = createPalette( chalks, colorButtons );
+ palette.dataset.mode = id;
+ container.appendChild( palette );
+ }
+ if ( boardHandle ) {
+ var handle = document.createElement( 'div' );
+ handle.classList.add( 'boardhandle' );
+ handle.innerHTML = '
';
+ handle.querySelector( '#previousboard' ).addEventListener( 'click', function ( e ) {
+ e.preventDefault();
+ switchBoard( board - 1 );
+ } );
+ handle.querySelector( '#nextboard' ).addEventListener( 'click', function ( e ) {
+ e.preventDefault();
+ switchBoard( board + 1 );
+ } );
+ handle.querySelector( '#previousboard' ).addEventListener( 'touchstart', function ( e ) {
+ e.preventDefault();
+ switchBoard( board - 1 );
+ } );
+ handle.querySelector( '#nextboard' ).addEventListener( 'touchstart', function ( e ) {
+ e.preventDefault();
+ switchBoard( board + 1 );
+ } );
+
+ container.appendChild( handle );
+ }
+ }
+
+ var canvas = document.createElement( 'canvas' );
+ canvas.width = drawingCanvas[ id ].width;
+ canvas.height = drawingCanvas[ id ].height;
+ canvas.setAttribute( 'data-chalkboard', id );
+ changeCursor( canvas, pens[ id ][ color[ id ] ] );
+ container.appendChild( canvas );
+ drawingCanvas[ id ].canvas = canvas;
+
+ drawingCanvas[ id ].context = canvas.getContext( '2d' );
+
+ setupCanvasEvents( container );
+
+ document.querySelector( '.reveal' ).appendChild( container );
+ drawingCanvas[ id ].container = container;
+ }
+
+
+/*****************************************************************
+ ** Storage
+ ******************************************************************/
+
+ var storage = [ {
+ width: Reveal.getConfig().width,
+ height: Reveal.getConfig().height,
+ data: []
+ },
+ {
+ width: Reveal.getConfig().width,
+ height: Reveal.getConfig().height,
+ data: []
+ }
+ ];
+
+ var loaded = null;
+
+ if ( config.storage ) {
+ // Get chalkboard drawings from session storage
+ loaded = initStorage( sessionStorage.getItem( config.storage ) );
+ }
+
+ if ( !loaded && config.src != null ) {
+ // Get chalkboard drawings from the given file
+ loadData( config.src );
+ }
+
+ /**
+ * Initialize storage.
+ */
+ function initStorage( json ) {
+ var success = false;
+ try {
+ var data = JSON.parse( json );
+ for ( var id = 0; id < data.length; id++ ) {
+ if ( drawingCanvas[ id ].width != data[ id ].width || drawingCanvas[ id ].height != data[ id ].height ) {
+ drawingCanvas[ id ].scale = Math.min( drawingCanvas[ id ].width / data[ id ].width, drawingCanvas[ id ].height / data[ id ].height );
+ drawingCanvas[ id ].xOffset = ( drawingCanvas[ id ].width - data[ id ].width * drawingCanvas[ id ].scale ) / 2;
+ drawingCanvas[ id ].yOffset = ( drawingCanvas[ id ].height - data[ id ].height * drawingCanvas[ id ].scale ) / 2;
+ }
+ if ( config.readOnly ) {
+ drawingCanvas[ id ].container.style.cursor = 'default';
+ drawingCanvas[ id ].canvas.style.cursor = 'default';
+ }
+ }
+ success = true;
+ storage = data;
+ } catch ( err ) {
+ console.warn( "Cannot initialise storage!" );
+ }
+ return success;
+ }
+
+
+ /**
+ * Load data.
+ */
+ function loadData( filename ) {
+ var xhr = new XMLHttpRequest();
+ xhr.onload = function () {
+ if ( xhr.readyState === 4 && xhr.status != 404 ) {
+ loaded = initStorage( xhr.responseText );
+ updateStorage();
+ console.log( "Drawings loaded from file" );
+ } else {
+ config.readOnly = undefined;
+ readOnly = undefined;
+ console.warn( 'Failed to get file ' + filename + '. ReadyState: ' + xhr.readyState + ', Status: ' + xhr.status );
+ loaded = false;
+ }
+ };
+
+ xhr.open( 'GET', filename, true );
+ try {
+ xhr.send();
+ } catch ( error ) {
+ config.readOnly = undefined;
+ readOnly = undefined;
+ console.warn( 'Failed to get file ' + filename + '. Make sure that the presentation and the file are served by a HTTP server and the file can be found there. ' + error );
+ loaded = false;
+ }
+ }
+
+
+ function storageChanged( now ) {
+ if ( !now ) {
+ // create or update timer
+ if ( updateStorageTimeout ) {
+ clearTimeout( updateStorageTimeout );
+ }
+ updateStorageTimeout = setTimeout( storageChanged, 1000, true);
+ }
+ else {
+// console.log("Update storage", updateStorageTimeout, Date.now());
+ updateStorage();
+ updateStorageTimeout = null;
+ }
+ }
+
+ function updateStorage() {
+ var json = JSON.stringify( storage )
+ if ( config.storage ) {
+ sessionStorage.setItem( config.storage, json )
+ }
+ return json;
+ }
+
+ function recordEvent( event ) {
+//console.log(event);
+ event.time = Date.now() - slideStart;
+ if ( mode == 1 ) event.board = board;
+ var slideData = getSlideData();
+ var i = slideData.events.length;
+ while ( i > 0 && event.time < slideData.events[ i - 1 ].time ) {
+ i--;
+ }
+ slideData.events.splice( i, 0, event );
+ slideData.duration = Math.max( slideData.duration, Date.now() - slideStart ) + 1;
+
+ storageChanged();
+ }
+
+ /**
+ * Get data as json string.
+ */
+ function getData() {
+ // cleanup slide data without events
+ for ( var id = 0; id < 2; id++ ) {
+ for ( var i = storage[ id ].data.length - 1; i >= 0; i-- ) {
+ if ( storage[ id ].data[ i ].events.length == 0 ) {
+ storage[ id ].data.splice( i, 1 );
+ }
+ }
+ }
+
+ return updateStorage();
+ }
+
+ /**
+ * Download data.
+ */
+ function downloadData() {
+ var a = document.createElement( 'a' );
+ document.body.appendChild( a );
+ try {
+ a.download = 'chalkboard.json';
+ var blob = new Blob( [ getData() ], {
+ type: 'application/json'
+ } );
+ a.href = window.URL.createObjectURL( blob );
+ } catch ( error ) {
+ // https://stackoverflow.com/a/6234804
+ // escape data for proper handling of quotes and line breaks
+ // in case malicious user gets a chance to craft the exception message
+ error = String(error)
+ .replace(/&/g, "&")
+ .replace(//g, ">")
+ .replace(/"/g, """)
+ .replace(/'/g, "'");
+ a.innerHTML += ' (' + error + ')';
+ }
+ a.click();
+ document.body.removeChild( a );
+ }
+
+ /**
+ * Returns data object for the slide with the given indices.
+ */
+ function getSlideData( indices, id ) {
+ if ( id == undefined ) id = mode;
+ if ( !indices ) indices = slideIndices;
+ var data;
+ for ( var i = 0; i < storage[ id ].data.length; i++ ) {
+ if ( storage[ id ].data[ i ].slide.h === indices.h && storage[ id ].data[ i ].slide.v === indices.v && storage[ id ].data[ i ].slide.f === indices.f ) {
+ data = storage[ id ].data[ i ];
+ return data;
+ }
+ }
+ var page = Number( Reveal.getCurrentSlide().getAttribute('data-pdf-page-number') );
+//console.log( indices, Reveal.getCurrentSlide() );
+ storage[ id ].data.push( {
+ slide: indices,
+ page,
+ events: [],
+ duration: 0
+ } );
+ data = storage[ id ].data[ storage[ id ].data.length - 1 ];
+ return data;
+ }
+
+ /**
+ * Returns maximum duration of slide playback for both modes
+ */
+ function getSlideDuration( indices ) {
+ if ( !indices ) indices = slideIndices;
+ var duration = 0;
+ for ( var id = 0; id < 2; id++ ) {
+ for ( var i = 0; i < storage[ id ].data.length; i++ ) {
+ if ( storage[ id ].data[ i ].slide.h === indices.h && storage[ id ].data[ i ].slide.v === indices.v && storage[ id ].data[ i ].slide.f === indices.f ) {
+ duration = Math.max( duration, storage[ id ].data[ i ].duration );
+ break;
+ }
+ }
+ }
+//console.log( duration );
+ return duration;
+ }
+
+/*****************************************************************
+ ** Print
+ ******************************************************************/
+ var printMode = ( /print-pdf/gi ).test( window.location.search );
+//console.log("createPrintout" + printMode)
+
+ function addPageNumbers() {
+ // determine page number for printouts with fragments serialised
+ var slides = Reveal.getSlides();
+ var page = 0;
+ for ( var i=0; i < slides.length; i++) {
+ slides[i].setAttribute('data-pdf-page-number',page.toString());
+ // add number of fragments without fragment indices
+ var count = slides[i].querySelectorAll('.fragment:not([data-fragment-index])').length;
+ var fragments = slides[i].querySelectorAll('.fragment[data-fragment-index]');
+ for ( var j=0; j < fragments.length; j++) {
+ // increasenumber of fragments by highest fragment index (which start at 0)
+ if ( Number(fragments[j].getAttribute('data-fragment-index')) + 1 > count ) {
+ count = Number(fragments[j].getAttribute('data-fragment-index')) + 1;
+ }
+ }
+ page += count + 1;
+ }
+ }
+
+ function createPrintout() {
+ //console.warn(Reveal.getTotalSlides(),Reveal.getSlidesElement());
+ if ( storage[ 1 ].data.length == 0 ) return;
+ console.log( 'Create printout(s) for ' + storage[ 1 ].data.length + " slides" );
+ drawingCanvas[ 0 ].container.style.opacity = 0; // do not print notes canvas
+ drawingCanvas[ 0 ].container.style.visibility = 'hidden';
+
+ var patImg = new Image();
+ patImg.onload = function () {
+ var slides = Reveal.getSlides();
+//console.log(slides);
+ for ( var i = storage[ 1 ].data.length - 1; i >= 0; i-- ) {
+ console.log( 'Create printout for slide ' + storage[ 1 ].data[ i ].slide.h + '.' + storage[ 1 ].data[ i ].slide.v );
+ var slideData = getSlideData( storage[ 1 ].data[ i ].slide, 1 );
+ var drawings = createDrawings( slideData, patImg );
+ addDrawings( slides[storage[ 1 ].data[ i ].page], drawings );
+
+ }
+// Reveal.sync();
+ };
+ patImg.src = background[ 1 ];
+ }
+
+
+ function cloneCanvas( oldCanvas ) {
+ //create a new canvas
+ var newCanvas = document.createElement( 'canvas' );
+ var context = newCanvas.getContext( '2d' );
+ //set dimensions
+ newCanvas.width = oldCanvas.width;
+ newCanvas.height = oldCanvas.height;
+ //apply the old canvas to the new one
+ context.drawImage( oldCanvas, 0, 0 );
+ //return the new canvas
+ return newCanvas;
+ }
+
+ function getCanvas( template, container, board ) {
+ var idx = container.findIndex( element => element.board === board );
+ if ( idx === -1 ) {
+ var canvas = cloneCanvas( template );
+ if ( !container.length ) {
+ idx = 0;
+ container.push( {
+ board,
+ canvas
+ } );
+ } else if ( board < container[ 0 ].board ) {
+ idx = 0;
+ container.unshift( {
+ board,
+ canvas
+ } );
+ } else if ( board > container[ container.length - 1 ].board ) {
+ idx = container.length;
+ container.push( {
+ board,
+ canvas
+ } );
+ }
+ }
+
+ return container[ idx ].canvas;
+ }
+
+ function createDrawings( slideData, patImg ) {
+ var width = Reveal.getConfig().width;
+ var height = Reveal.getConfig().height;
+ var scale = 1;
+ var xOffset = 0;
+ var yOffset = 0;
+ if ( width != storage[ 1 ].width || height != storage[ 1 ].height ) {
+ scale = Math.min( width / storage[ 1 ].width, height / storage[ 1 ].height );
+ xOffset = ( width - storage[ 1 ].width * scale ) / 2;
+ yOffset = ( height - storage[ 1 ].height * scale ) / 2;
+ }
+ mode = 1;
+ board = 0;
+// console.log( 'Create printout(s) for slide ', slideData );
+
+ var drawings = [];
+ var template = document.createElement( 'canvas' );
+ template.width = width;
+ template.height = height;
+
+ var imgCtx = template.getContext( '2d' );
+ imgCtx.fillStyle = imgCtx.createPattern( patImg, 'repeat' );
+ imgCtx.rect( 0, 0, width, height );
+ imgCtx.fill();
+
+ for ( var j = 0; j < slideData.events.length; j++ ) {
+ switch ( slideData.events[ j ].type ) {
+ case 'draw':
+ draw[ 1 ]( getCanvas( template, drawings, board ).getContext( '2d' ),
+ xOffset + slideData.events[ j ].x1 * scale,
+ yOffset + slideData.events[ j ].y1 * scale,
+ xOffset + slideData.events[ j ].x2 * scale,
+ yOffset + slideData.events[ j ].y2 * scale,
+ yOffset + slideData.events[ j ].color
+ );
+ break;
+ case 'erase':
+ eraseWithSponge( getCanvas( template, drawings, board ).getContext( '2d' ),
+ xOffset + slideData.events[ j ].x * scale,
+ yOffset + slideData.events[ j ].y * scale
+ );
+ break;
+ case 'selectboard':
+ selectBoard( slideData.events[ j ].board );
+ break;
+ case 'clear':
+ getCanvas( template, drawings, board ).getContext( '2d' ).clearRect( 0, 0, width, height );
+ getCanvas( template, drawings, board ).getContext( '2d' ).fill();
+ break;
+ default:
+ break;
+ }
+ }
+
+ drawings = drawings.sort( ( a, b ) => a.board > b.board && 1 || -1 );
+
+ mode = 0;
+
+ return drawings;
+ }
+
+ function addDrawings( slide, drawings ) {
+ var parent = slide.parentElement.parentElement;
+ var nextSlide = slide.parentElement.nextElementSibling;
+
+ for ( var i = 0; i < drawings.length; i++ ) {
+ var newPDFPage = document.createElement( 'div' );
+ newPDFPage.classList.add( 'pdf-page' );
+ newPDFPage.style.height = Reveal.getConfig().height;
+ newPDFPage.append( drawings[ i ].canvas );
+//console.log("Add drawing", newPDFPage);
+ if ( nextSlide != null ) {
+ parent.insertBefore( newPDFPage, nextSlide );
+ } else {
+ parent.append( newPDFPage );
+ }
+ }
+ }
+
+ /*****************************************************************
+ ** Drawings
+ ******************************************************************/
+
+ function drawWithBoardmarker( context, fromX, fromY, toX, toY, colorIdx ) {
+ if ( colorIdx == undefined ) colorIdx = color[ mode ];
+ context.lineWidth = boardmarkerWidth;
+ context.lineCap = 'round';
+ context.strokeStyle = boardmarkers[ colorIdx ].color;
+ context.beginPath();
+ context.moveTo( fromX, fromY );
+ context.lineTo( toX, toY );
+ context.stroke();
+ }
+
+ function drawWithChalk( context, fromX, fromY, toX, toY, colorIdx ) {
+ if ( colorIdx == undefined ) colorIdx = color[ mode ];
+ var brushDiameter = chalkWidth;
+ context.lineWidth = brushDiameter;
+ context.lineCap = 'round';
+ context.fillStyle = chalks[ colorIdx ].color; // 'rgba(255,255,255,0.5)';
+ context.strokeStyle = chalks[ colorIdx ].color;
+
+ var opacity = 1.0;
+ context.strokeStyle = context.strokeStyle.replace( /[\d\.]+\)$/g, opacity + ')' );
+ context.beginPath();
+ context.moveTo( fromX, fromY );
+ context.lineTo( toX, toY );
+ context.stroke();
+ // Chalk Effect
+ var length = Math.round( Math.sqrt( Math.pow( toX - fromX, 2 ) + Math.pow( toY - fromY, 2 ) ) / ( 5 / brushDiameter ) );
+ var xUnit = ( toX - fromX ) / length;
+ var yUnit = ( toY - fromY ) / length;
+ for ( var i = 0; i < length; i++ ) {
+ if ( chalkEffect > ( Math.random() * 0.9 ) ) {
+ var xCurrent = fromX + ( i * xUnit );
+ var yCurrent = fromY + ( i * yUnit );
+ var xRandom = xCurrent + ( Math.random() - 0.5 ) * brushDiameter * 1.2;
+ var yRandom = yCurrent + ( Math.random() - 0.5 ) * brushDiameter * 1.2;
+ context.clearRect( xRandom, yRandom, Math.random() * 2 + 2, Math.random() + 1 );
+ }
+ }
+ }
+
+ function eraseWithSponge( context, x, y ) {
+ context.save();
+ context.beginPath();
+ context.arc( x + eraser.radius, y + eraser.radius, eraser.radius, 0, 2 * Math.PI, false );
+ context.clip();
+ context.clearRect( x - 1, y - 1, eraser.radius * 2 + 2, eraser.radius * 2 + 2 );
+ context.restore();
+ if ( mode == 1 && grid ) {
+ redrawGrid( x + eraser.radius, y + eraser.radius, eraser.radius );
+ }
+ }
+
+
+ /**
+ * Show an overlay for the chalkboard.
+ */
+ function showChalkboard() {
+//console.log("showChalkboard");
+ drawingCanvas[ 1 ].container.style.opacity = 1;
+ drawingCanvas[ 1 ].container.style.visibility = 'visible';
+ mode = 1;
+ }
+
+
+ /**
+ * Closes open chalkboard.
+ */
+ function closeChalkboard() {
+ drawingCanvas[ 1 ].container.style.opacity = 0;
+ drawingCanvas[ 1 ].container.style.visibility = 'hidden';
+ lastX = null;
+ lastY = null;
+ mode = 0;
+ }
+
+ /**
+ * Clear current canvas.
+ */
+ function clearCanvas( id ) {
+ if ( id == 0 ) clearTimeout( slidechangeTimeout );
+ drawingCanvas[ id ].context.clearRect( 0, 0, drawingCanvas[ id ].width, drawingCanvas[ id ].height );
+ if ( id == 1 && grid ) drawGrid();
+ }
+
+ /**
+ * Draw grid on background
+ */
+ function drawGrid() {
+ var context = drawingCanvas[ 1 ].context;
+
+ drawingCanvas[ 1 ].scale = Math.min( drawingCanvas[ 1 ].width / storage[ 1 ].width, drawingCanvas[ 1 ].height / storage[ 1 ].height );
+ drawingCanvas[ 1 ].xOffset = ( drawingCanvas[ 1 ].width - storage[ 1 ].width * drawingCanvas[ 1 ].scale ) / 2;
+ drawingCanvas[ 1 ].yOffset = ( drawingCanvas[ 1 ].height - storage[ 1 ].height * drawingCanvas[ 1 ].scale ) / 2;
+
+ var scale = drawingCanvas[ 1 ].scale;
+ var xOffset = drawingCanvas[ 1 ].xOffset;
+ var yOffset = drawingCanvas[ 1 ].yOffset;
+
+ var distance = grid.distance * scale;
+
+ var fromX = drawingCanvas[ 1 ].width / 2 - distance / 2 - Math.floor( ( drawingCanvas[ 1 ].width - distance ) / 2 / distance ) * distance;
+ for ( var x = fromX; x < drawingCanvas[ 1 ].width; x += distance ) {
+ context.beginPath();
+ context.lineWidth = grid.width * scale;
+ context.lineCap = 'round';
+ context.fillStyle = grid.color;
+ context.strokeStyle = grid.color;
+ context.moveTo( x, 0 );
+ context.lineTo( x, drawingCanvas[ 1 ].height );
+ context.stroke();
+ }
+ var fromY = drawingCanvas[ 1 ].height / 2 - distance / 2 - Math.floor( ( drawingCanvas[ 1 ].height - distance ) / 2 / distance ) * distance;
+
+ for ( var y = fromY; y < drawingCanvas[ 1 ].height; y += distance ) {
+ context.beginPath();
+ context.lineWidth = grid.width * scale;
+ context.lineCap = 'round';
+ context.fillStyle = grid.color;
+ context.strokeStyle = grid.color;
+ context.moveTo( 0, y );
+ context.lineTo( drawingCanvas[ 1 ].width, y );
+ context.stroke();
+ }
+ }
+
+ function redrawGrid( centerX, centerY, diameter ) {
+ var context = drawingCanvas[ 1 ].context;
+
+ drawingCanvas[ 1 ].scale = Math.min( drawingCanvas[ 1 ].width / storage[ 1 ].width, drawingCanvas[ 1 ].height / storage[ 1 ].height );
+ drawingCanvas[ 1 ].xOffset = ( drawingCanvas[ 1 ].width - storage[ 1 ].width * drawingCanvas[ 1 ].scale ) / 2;
+ drawingCanvas[ 1 ].yOffset = ( drawingCanvas[ 1 ].height - storage[ 1 ].height * drawingCanvas[ 1 ].scale ) / 2;
+
+ var scale = drawingCanvas[ 1 ].scale;
+ var xOffset = drawingCanvas[ 1 ].xOffset;
+ var yOffset = drawingCanvas[ 1 ].yOffset;
+
+ var distance = grid.distance * scale;
+
+ var fromX = drawingCanvas[ 1 ].width / 2 - distance / 2 - Math.floor( ( drawingCanvas[ 1 ].width - distance ) / 2 / distance ) * distance;
+
+ for ( var x = fromX + distance * Math.ceil( ( centerX - diameter - fromX ) / distance ); x <= fromX + distance * Math.floor( ( centerX + diameter - fromX ) / distance ); x += distance ) {
+ context.beginPath();
+ context.lineWidth = grid.width * scale;
+ context.lineCap = 'round';
+ context.fillStyle = grid.color;
+ context.strokeStyle = grid.color;
+ context.moveTo( x, centerY - Math.sqrt( diameter * diameter - ( centerX - x ) * ( centerX - x ) ) );
+ context.lineTo( x, centerY + Math.sqrt( diameter * diameter - ( centerX - x ) * ( centerX - x ) ) );
+ context.stroke();
+ }
+ var fromY = drawingCanvas[ 1 ].height / 2 - distance / 2 - Math.floor( ( drawingCanvas[ 1 ].height - distance ) / 2 / distance ) * distance;
+ for ( var y = fromY + distance * Math.ceil( ( centerY - diameter - fromY ) / distance ); y <= fromY + distance * Math.floor( ( centerY + diameter - fromY ) / distance ); y += distance ) {
+ context.beginPath();
+ context.lineWidth = grid.width * scale;
+ context.lineCap = 'round';
+ context.fillStyle = grid.color;
+ context.strokeStyle = grid.color;
+ context.moveTo( centerX - Math.sqrt( diameter * diameter - ( centerY - y ) * ( centerY - y ) ), y );
+ context.lineTo( centerX + Math.sqrt( diameter * diameter - ( centerY - y ) * ( centerY - y ) ), y );
+ context.stroke();
+ }
+ }
+
+ /**
+ * Set the color
+ */
+ function setColor( index, record ) {
+ // protect against out of bounds (this could happen when
+ // replaying events recorded with different color settings).
+ if ( index >= pens[ mode ].length ) index = 0;
+
+ color[ mode ] = index;
+
+ if ( color[ mode ] < 0 ) {
+ // use eraser
+ changeCursor( drawingCanvas[ mode ].canvas, sponge );
+ }
+ else {
+ changeCursor( drawingCanvas[ mode ].canvas, pens[ mode ][ color[ mode ] ] );
+ }
+ }
+
+ /**
+ * Set the board
+ */
+ function selectBoard( boardIdx, record ) {
+//console.log("Set board",boardIdx);
+ if ( board == boardIdx ) return;
+
+ board = boardIdx;
+ redrawChalkboard( boardIdx );
+ if ( record ) {
+ recordEvent( { type: 'selectboard' } );
+ }
+ }
+
+ function redrawChalkboard( boardIdx ) {
+ clearCanvas( 1 );
+ var slideData = getSlideData( slideIndices, 1 );
+ var index = 0;
+ var play = ( boardIdx == 0 );
+ while ( index < slideData.events.length && slideData.events[ index ].time < Date.now() - slideStart ) {
+ if ( boardIdx == slideData.events[ index ].board ) {
+ playEvent( 1, slideData.events[ index ], Date.now() - slideStart );
+ }
+
+ index++;
+ }
+ }
+
+
+ /**
+ * Forward cycle color
+ */
+ function cycleColorNext() {
+ color[ mode ] = ( color[ mode ] + 1 ) % pens[ mode ].length;
+ return color[ mode ];
+ }
+
+ /**
+ * Backward cycle color
+ */
+ function cycleColorPrev() {
+ color[ mode ] = ( color[ mode ] + ( pens[ mode ].length - 1 ) ) % pens[ mode ].length;
+ return color[ mode ];
+ }
+
+/*****************************************************************
+ ** Broadcast
+ ******************************************************************/
+
+ var eventQueue = [];
+
+ document.addEventListener( 'received', function ( message ) {
+ if ( message.content && message.content.sender == 'chalkboard-plugin' ) {
+ // add message to queue
+ eventQueue.push( message );
+ console.log( JSON.stringify( message ) );
+ }
+ if ( eventQueue.length == 1 ) processQueue();
+ } );
+
+ function processQueue() {
+ // take first message from queue
+ var message = eventQueue.shift();
+
+ // synchronize time with seminar host
+ slideStart = Date.now() - message.content.timestamp;
+ // set status
+ if ( mode < message.content.mode ) {
+ // open chalkboard
+ showChalkboard();
+ } else if ( mode > message.content.mode ) {
+ // close chalkboard
+ closeChalkboard();
+ }
+ if ( board != message.content.board ) {
+ board = message.content.board;
+ redrawChalkboard( board );
+ };
+
+ switch ( message.content.type ) {
+ case 'showChalkboard':
+ showChalkboard();
+ break;
+ case 'closeChalkboard':
+ closeChalkboard();
+ break;
+ case 'erase':
+ erasePoint( message.content.x, message.content.y );
+ break;
+ case 'draw':
+ drawSegment( message.content.fromX, message.content.fromY, message.content.toX, message.content.toY, message.content.color );
+ break;
+ case 'clear':
+ clearSlide();
+ break;
+ case 'selectboard':
+ selectBoard( message.content.board, true );
+ break;
+ case 'resetSlide':
+ resetSlideDrawings();
+ break;
+ case 'init':
+ storage = message.content.storage;
+ for ( var id = 0; id < 2; id++ ) {
+ drawingCanvas[ id ].scale = Math.min( drawingCanvas[ id ].width / storage[ id ].width, drawingCanvas[ id ].height / storage[ id ].height );
+ drawingCanvas[ id ].xOffset = ( drawingCanvas[ id ].width - storage[ id ].width * drawingCanvas[ id ].scale ) / 2;
+ drawingCanvas[ id ].yOffset = ( drawingCanvas[ id ].height - storage[ id ].height * drawingCanvas[ id ].scale ) / 2;
+ }
+ clearCanvas( 0 );
+ clearCanvas( 1 );
+ if ( !playback ) {
+ slidechangeTimeout = setTimeout( startPlayback, transition, getSlideDuration(), 0 );
+ }
+ if ( mode == 1 && message.content.mode == 0 ) {
+ setTimeout( closeChalkboard, transition + 50 );
+ }
+ if ( mode == 0 && message.content.mode == 1 ) {
+ setTimeout( showChalkboard, transition + 50 );
+ }
+ mode = message.content.mode;
+ board = message.content.board;
+ break;
+ default:
+ break;
+ }
+
+ // continue with next message if queued
+ if ( eventQueue.length > 0 ) {
+ processQueue();
+ } else {
+ storageChanged();
+ }
+ }
+
+ document.addEventListener( 'welcome', function ( user ) {
+ // broadcast storage
+ var message = new CustomEvent( messageType );
+ message.content = {
+ sender: 'chalkboard-plugin',
+ recipient: user.id,
+ type: 'init',
+ timestamp: Date.now() - slideStart,
+ storage: storage,
+ mode,
+ board
+ };
+ document.dispatchEvent( message );
+ } );
+
+ /*****************************************************************
+ ** Playback
+ ******************************************************************/
+
+ document.addEventListener( 'seekplayback', function ( event ) {
+//console.log('event seekplayback ' + event.timestamp);
+ stopPlayback();
+ if ( !playback || event.timestamp == 0 ) {
+ // in other cases startplayback fires after seeked
+ startPlayback( event.timestamp );
+ }
+ //console.log('seeked');
+ } );
+
+
+ document.addEventListener( 'startplayback', function ( event ) {
+//console.log('event startplayback ' + event.timestamp);
+ stopPlayback();
+ playback = true;
+ startPlayback( event.timestamp );
+ } );
+
+ document.addEventListener( 'stopplayback', function ( event ) {
+//console.log('event stopplayback ' + (Date.now() - slideStart) );
+ playback = false;
+ stopPlayback();
+ } );
+
+ document.addEventListener( 'startrecording', function ( event ) {
+//console.log('event startrecording ' + event.timestamp);
+ startRecording();
+ } );
+
+
+ function startRecording() {
+ resetSlide( true );
+ slideStart = Date.now();
+ }
+
+ function startPlayback( timestamp, finalMode ) {
+//console.log("playback " + timestamp );
+ slideStart = Date.now() - timestamp;
+ closeChalkboard();
+ mode = 0;
+ board = 0;
+ for ( var id = 0; id < 2; id++ ) {
+ clearCanvas( id );
+ var slideData = getSlideData( slideIndices, id );
+//console.log( timestamp +" / " + JSON.stringify(slideData));
+ var index = 0;
+ while ( index < slideData.events.length && slideData.events[ index ].time < ( Date.now() - slideStart ) ) {
+ playEvent( id, slideData.events[ index ], timestamp );
+ index++;
+ }
+
+ while ( playback && index < slideData.events.length ) {
+ timeouts[ id ].push( setTimeout( playEvent, slideData.events[ index ].time - ( Date.now() - slideStart ), id, slideData.events[ index ], timestamp ) );
+ index++;
+ }
+ }
+//console.log("Mode: " + finalMode + "/" + mode );
+ if ( finalMode != undefined ) {
+ mode = finalMode;
+ }
+ if ( mode == 1 ) showChalkboard();
+//console.log("playback (ok)");
+
+ };
+
+ function stopPlayback() {
+//console.log("stopPlayback");
+//console.log("Timeouts: " + timeouts[0].length + "/"+ timeouts[1].length);
+ for ( var id = 0; id < 2; id++ ) {
+ for ( var i = 0; i < timeouts[ id ].length; i++ ) {
+ clearTimeout( timeouts[ id ][ i ] );
+ }
+ timeouts[ id ] = [];
+ }
+ };
+
+ function playEvent( id, event, timestamp ) {
+//console.log( timestamp +" / " + JSON.stringify(event));
+//console.log( id + ": " + timestamp +" / " + event.time +" / " + event.type +" / " + mode );
+ switch ( event.type ) {
+ case 'open':
+ if ( timestamp <= event.time ) {
+ showChalkboard();
+ } else {
+ mode = 1;
+ }
+
+ break;
+ case 'close':
+ if ( timestamp < event.time ) {
+ closeChalkboard();
+ } else {
+ mode = 0;
+ }
+ break;
+ case 'clear':
+ clearCanvas( id );
+ break;
+ case 'selectboard':
+ selectBoard( event.board );
+ break;
+ case 'draw':
+ drawLine( id, event, timestamp );
+ break;
+ case 'erase':
+ eraseCircle( id, event, timestamp );
+ break;
+ }
+ };
+
+ function drawLine( id, event, timestamp ) {
+ var ctx = drawingCanvas[ id ].context;
+ var scale = drawingCanvas[ id ].scale;
+ var xOffset = drawingCanvas[ id ].xOffset;
+ var yOffset = drawingCanvas[ id ].yOffset;
+ draw[ id ]( ctx, xOffset + event.x1 * scale, yOffset + event.y1 * scale, xOffset + event.x2 * scale, yOffset + event.y2 * scale, event.color );
+ };
+
+ function eraseCircle( id, event, timestamp ) {
+ var ctx = drawingCanvas[ id ].context;
+ var scale = drawingCanvas[ id ].scale;
+ var xOffset = drawingCanvas[ id ].xOffset;
+ var yOffset = drawingCanvas[ id ].yOffset;
+
+ eraseWithSponge( ctx, xOffset + event.x * scale, yOffset + event.y * scale );
+ };
+
+ function startErasing( x, y ) {
+ drawing = false;
+ erasing = true;
+ erasePoint( x, y );
+ }
+
+ function erasePoint( x, y ) {
+ var ctx = drawingCanvas[ mode ].context;
+ var scale = drawingCanvas[ mode ].scale;
+ var xOffset = drawingCanvas[ mode ].xOffset;
+ var yOffset = drawingCanvas[ mode ].yOffset;
+
+ recordEvent( {
+ type: 'erase',
+ x,
+ y
+ } );
+
+ if (
+ x * scale + xOffset > 0 &&
+ y * scale + yOffset > 0 &&
+ x * scale + xOffset < drawingCanvas[ mode ].width &&
+ y * scale + yOffset < drawingCanvas[ mode ].height
+ ) {
+ eraseWithSponge( ctx, x * scale + xOffset, y * scale + yOffset );
+ }
+ }
+
+ function stopErasing() {
+ erasing = false;
+ }
+
+ function startDrawing( x, y ) {
+ drawing = true;
+
+ var ctx = drawingCanvas[ mode ].context;
+ var scale = drawingCanvas[ mode ].scale;
+ var xOffset = drawingCanvas[ mode ].xOffset;
+ var yOffset = drawingCanvas[ mode ].yOffset;
+ lastX = x * scale + xOffset;
+ lastY = y * scale + yOffset;
+ }
+
+ function drawSegment( fromX, fromY, toX, toY, colorIdx ) {
+ var ctx = drawingCanvas[ mode ].context;
+ var scale = drawingCanvas[ mode ].scale;
+ var xOffset = drawingCanvas[ mode ].xOffset;
+ var yOffset = drawingCanvas[ mode ].yOffset;
+
+ recordEvent( {
+ type: 'draw',
+ color: colorIdx,
+ x1: fromX,
+ y1: fromY,
+ x2: toX,
+ y2: toY
+ } );
+
+ if (
+ fromX * scale + xOffset > 0 &&
+ fromY * scale + yOffset > 0 &&
+ fromX * scale + xOffset < drawingCanvas[ mode ].width &&
+ fromY * scale + yOffset < drawingCanvas[ mode ].height &&
+ toX * scale + xOffset > 0 &&
+ toY * scale + yOffset > 0 &&
+ toX * scale + xOffset < drawingCanvas[ mode ].width &&
+ toY * scale + yOffset < drawingCanvas[ mode ].height
+ ) {
+ draw[ mode ]( ctx, fromX * scale + xOffset, fromY * scale + yOffset, toX * scale + xOffset, toY * scale + yOffset, colorIdx );
+ }
+ }
+
+ function stopDrawing() {
+ drawing = false;
+ }
+
+
+/*****************************************************************
+ ** User interface
+ ******************************************************************/
+
+ function setupCanvasEvents( canvas ) {
+// TODO: check all touchevents
+ canvas.addEventListener( 'touchstart', function ( evt ) {
+ evt.preventDefault();
+//console.log("Touch start");
+ if ( !readOnly && evt.target.getAttribute( 'data-chalkboard' ) == mode ) {
+ var scale = drawingCanvas[ mode ].scale;
+ var xOffset = drawingCanvas[ mode ].xOffset;
+ var yOffset = drawingCanvas[ mode ].yOffset;
+
+ var touch = evt.touches[ 0 ];
+ mouseX = touch.pageX;
+ mouseY = touch.pageY;
+ if ( color[ mode ] < 0 ) {
+ startErasing( ( mouseX - xOffset ) / scale, ( mouseY - yOffset ) / scale);
+ }
+ else {
+ startDrawing( ( mouseX - xOffset ) / scale, ( mouseY - yOffset ) / scale );
+ }
+ }
+ }, passiveSupported ? {
+ passive: false
+ } : false );
+
+ canvas.addEventListener( 'touchmove', function ( evt ) {
+ evt.preventDefault();
+//console.log("Touch move");
+ if ( drawing || erasing ) {
+ var scale = drawingCanvas[ mode ].scale;
+ var xOffset = drawingCanvas[ mode ].xOffset;
+ var yOffset = drawingCanvas[ mode ].yOffset;
+
+ var touch = evt.touches[ 0 ];
+ mouseX = touch.pageX;
+ mouseY = touch.pageY;
+
+ if ( drawing ) {
+ drawSegment( ( lastX - xOffset ) / scale, ( lastY - yOffset ) / scale, ( mouseX - xOffset ) / scale, ( mouseY - yOffset ) / scale, color[ mode ] );
+ // broadcast
+ var message = new CustomEvent( messageType );
+ message.content = {
+ sender: 'chalkboard-plugin',
+ type: 'draw',
+ timestamp: Date.now() - slideStart,
+ mode,
+ board,
+ fromX: ( lastX - xOffset ) / scale,
+ fromY: ( lastY - yOffset ) / scale,
+ toX: ( mouseX - xOffset ) / scale,
+ toY: ( mouseY - yOffset ) / scale,
+ color: color[ mode ]
+ };
+ document.dispatchEvent( message );
+
+ lastX = mouseX;
+ lastY = mouseY;
+ } else {
+ erasePoint( ( mouseX - xOffset ) / scale, ( mouseY - yOffset ) / scale );
+ // broadcast
+ var message = new CustomEvent( messageType );
+ message.content = {
+ sender: 'chalkboard-plugin',
+ type: 'erase',
+ timestamp: Date.now() - slideStart,
+ mode,
+ board,
+ x: ( mouseX - xOffset ) / scale,
+ y: ( mouseY - yOffset ) / scale
+ };
+ document.dispatchEvent( message );
+ }
+
+ }
+ }, false );
+
+
+ canvas.addEventListener( 'touchend', function ( evt ) {
+ evt.preventDefault();
+ stopDrawing();
+ stopErasing();
+ }, false );
+
+ canvas.addEventListener( 'mousedown', function ( evt ) {
+ evt.preventDefault();
+ if ( !readOnly && evt.target.getAttribute( 'data-chalkboard' ) == mode ) {
+//console.log( "mousedown: " + evt.button );
+ var scale = drawingCanvas[ mode ].scale;
+ var xOffset = drawingCanvas[ mode ].xOffset;
+ var yOffset = drawingCanvas[ mode ].yOffset;
+
+ mouseX = evt.pageX;
+ mouseY = evt.pageY;
+
+ if ( color[ mode ] < 0 || evt.button == 2 || evt.button == 1 ) {
+ if ( color[ mode ] >= 0 ) {
+ // show sponge
+ changeCursor( drawingCanvas[ mode ].canvas, sponge );
+ }
+ startErasing( ( mouseX - xOffset ) / scale, ( mouseY - yOffset ) / scale );
+ // broadcast
+ var message = new CustomEvent( messageType );
+ message.content = {
+ sender: 'chalkboard-plugin',
+ type: 'erase',
+ timestamp: Date.now() - slideStart,
+ mode,
+ board,
+ x: ( mouseX - xOffset ) / scale,
+ y: ( mouseY - yOffset ) / scale
+ };
+ document.dispatchEvent( message );
+ } else {
+ startDrawing( ( mouseX - xOffset ) / scale, ( mouseY - yOffset ) / scale );
+ }
+ }
+ } );
+
+ canvas.addEventListener( 'mousemove', function ( evt ) {
+ evt.preventDefault();
+//console.log("Mouse move");
+
+ var scale = drawingCanvas[ mode ].scale;
+ var xOffset = drawingCanvas[ mode ].xOffset;
+ var yOffset = drawingCanvas[ mode ].yOffset;
+
+ mouseX = evt.pageX;
+ mouseY = evt.pageY;
+
+ if ( drawing || erasing ) {
+ var scale = drawingCanvas[ mode ].scale;
+ var xOffset = drawingCanvas[ mode ].xOffset;
+ var yOffset = drawingCanvas[ mode ].yOffset;
+
+ mouseX = evt.pageX;
+ mouseY = evt.pageY;
+
+ if ( drawing ) {
+ drawSegment( ( lastX - xOffset ) / scale, ( lastY - yOffset ) / scale, ( mouseX - xOffset ) / scale, ( mouseY - yOffset ) / scale, color[ mode ] );
+ // broadcast
+ var message = new CustomEvent( messageType );
+ message.content = {
+ sender: 'chalkboard-plugin',
+ type: 'draw',
+ timestamp: Date.now() - slideStart,
+ mode,
+ board,
+ fromX: ( lastX - xOffset ) / scale,
+ fromY: ( lastY - yOffset ) / scale,
+ toX: ( mouseX - xOffset ) / scale,
+ toY: ( mouseY - yOffset ) / scale,
+ color: color[ mode ]
+ };
+ document.dispatchEvent( message );
+
+ lastX = mouseX;
+ lastY = mouseY;
+ } else {
+ erasePoint( ( mouseX - xOffset ) / scale, ( mouseY - yOffset ) / scale );
+ // broadcast
+ var message = new CustomEvent( messageType );
+ message.content = {
+ sender: 'chalkboard-plugin',
+ type: 'erase',
+ timestamp: Date.now() - slideStart,
+ mode,
+ board,
+ x: ( mouseX - xOffset ) / scale,
+ y: ( mouseY - yOffset ) / scale
+ };
+ document.dispatchEvent( message );
+ }
+
+ }
+ } );
+
+
+ canvas.addEventListener( 'mouseup', function ( evt ) {
+ evt.preventDefault();
+ if ( color[ mode ] >= 0 ) {
+ changeCursor( drawingCanvas[ mode ].canvas, pens[ mode ][ color[ mode ] ] );
+ }
+ if ( drawing || erasing ) {
+ stopDrawing();
+ stopErasing();
+ }
+ } );
+ }
+
+ function resize() {
+//console.log("resize");
+ // Resize the canvas and draw everything again
+ var timestamp = Date.now() - slideStart;
+ if ( !playback ) {
+ timestamp = getSlideDuration();
+ }
+
+//console.log( drawingCanvas[0].scale + "/" + drawingCanvas[0].xOffset + "/" +drawingCanvas[0].yOffset );
+ for ( var id = 0; id < 2; id++ ) {
+ drawingCanvas[ id ].width = window.innerWidth;
+ drawingCanvas[ id ].height = window.innerHeight;
+ drawingCanvas[ id ].canvas.width = drawingCanvas[ id ].width;
+ drawingCanvas[ id ].canvas.height = drawingCanvas[ id ].height;
+ drawingCanvas[ id ].context.canvas.width = drawingCanvas[ id ].width;
+ drawingCanvas[ id ].context.canvas.height = drawingCanvas[ id ].height;
+
+ drawingCanvas[ id ].scale = Math.min( drawingCanvas[ id ].width / storage[ id ].width, drawingCanvas[ id ].height / storage[ id ].height );
+ drawingCanvas[ id ].xOffset = ( drawingCanvas[ id ].width - storage[ id ].width * drawingCanvas[ id ].scale ) / 2;
+ drawingCanvas[ id ].yOffset = ( drawingCanvas[ id ].height - storage[ id ].height * drawingCanvas[ id ].scale ) / 2;
+//console.log( drawingCanvas[id].scale + "/" + drawingCanvas[id].xOffset + "/" +drawingCanvas[id].yOffset );
+ }
+//console.log( window.innerWidth + "/" + window.innerHeight);
+ startPlayback( timestamp, mode, true );
+ }
+
+ Reveal.addEventListener( 'pdf-ready', function ( evt ) {
+// console.log( "Create printouts when ready" );
+ whenLoaded( createPrintout );
+ });
+
+ Reveal.addEventListener( 'ready', function ( evt ) {
+//console.log('ready');
+ if ( !printMode ) {
+ window.addEventListener( 'resize', resize );
+
+ slideStart = Date.now() - getSlideDuration();
+ slideIndices = Reveal.getIndices();
+ if ( !playback ) {
+ startPlayback( getSlideDuration(), 0 );
+ }
+ if ( Reveal.isAutoSliding() ) {
+ var event = new CustomEvent( 'startplayback' );
+ event.timestamp = 0;
+ document.dispatchEvent( event );
+ }
+ updateStorage();
+ whenReady( addPageNumbers );
+ }
+ } );
+ Reveal.addEventListener( 'slidechanged', function ( evt ) {
+// clearTimeout( slidechangeTimeout );
+//console.log('slidechanged');
+ if ( !printMode ) {
+ slideStart = Date.now() - getSlideDuration();
+ slideIndices = Reveal.getIndices();
+ closeChalkboard();
+ board = 0;
+ clearCanvas( 0 );
+ clearCanvas( 1 );
+ if ( !playback ) {
+ slidechangeTimeout = setTimeout( startPlayback, transition, getSlideDuration(), 0 );
+ }
+ if ( Reveal.isAutoSliding() ) {
+ var event = new CustomEvent( 'startplayback' );
+ event.timestamp = 0;
+ document.dispatchEvent( event );
+ }
+ }
+ } );
+ Reveal.addEventListener( 'fragmentshown', function ( evt ) {
+// clearTimeout( slidechangeTimeout );
+//console.log('fragmentshown');
+ if ( !printMode ) {
+ slideStart = Date.now() - getSlideDuration();
+ slideIndices = Reveal.getIndices();
+ closeChalkboard();
+ board = 0;
+ clearCanvas( 0 );
+ clearCanvas( 1 );
+ if ( Reveal.isAutoSliding() ) {
+ var event = new CustomEvent( 'startplayback' );
+ event.timestamp = 0;
+ document.dispatchEvent( event );
+ } else if ( !playback ) {
+ startPlayback( getSlideDuration(), 0 );
+// closeChalkboard();
+ }
+ }
+ } );
+ Reveal.addEventListener( 'fragmenthidden', function ( evt ) {
+// clearTimeout( slidechangeTimeout );
+//console.log('fragmenthidden');
+ if ( !printMode ) {
+ slideStart = Date.now() - getSlideDuration();
+ slideIndices = Reveal.getIndices();
+ closeChalkboard();
+ board = 0;
+ clearCanvas( 0 );
+ clearCanvas( 1 );
+ if ( Reveal.isAutoSliding() ) {
+ document.dispatchEvent( new CustomEvent( 'stopplayback' ) );
+ } else if ( !playback ) {
+ startPlayback( getSlideDuration() );
+ closeChalkboard();
+ }
+ }
+ } );
+
+ Reveal.addEventListener( 'autoslideresumed', function ( evt ) {
+//console.log('autoslideresumed');
+ var event = new CustomEvent( 'startplayback' );
+ event.timestamp = 0;
+ document.dispatchEvent( event );
+ } );
+ Reveal.addEventListener( 'autoslidepaused', function ( evt ) {
+//console.log('autoslidepaused');
+ document.dispatchEvent( new CustomEvent( 'stopplayback' ) );
+
+ // advance to end of slide
+// closeChalkboard();
+ startPlayback( getSlideDuration(), 0 );
+ } );
+
+ function toggleNotesCanvas() {
+ if ( !readOnly ) {
+ if ( mode == 1 ) {
+ toggleChalkboard();
+ notescanvas.style.background = background[ 0 ]; //'rgba(255,0,0,0.5)';
+ notescanvas.style.pointerEvents = 'auto';
+ }
+ else {
+ if ( notescanvas.style.pointerEvents != 'none' ) {
+ // hide notes canvas
+ if ( colorButtons ) {
+ notescanvas.querySelector( '.palette' ).style.visibility = 'hidden';
+ }
+ notescanvas.style.background = 'rgba(0,0,0,0)';
+ notescanvas.style.pointerEvents = 'none';
+ }
+ else {
+ // show notes canvas
+ if ( colorButtons ) {
+ notescanvas.querySelector( '.palette' ).style.visibility = 'visible';
+ }
+ notescanvas.style.background = background[ 0 ]; //'rgba(255,0,0,0.5)';
+ notescanvas.style.pointerEvents = 'auto';
+
+ var idx = 0;
+ if ( color[ mode ] ) {
+ idx = color[ mode ];
+ }
+
+ setColor( idx, true );
+ }
+ }
+ }
+ };
+
+ function toggleChalkboard() {
+//console.log("toggleChalkboard " + mode);
+ if ( mode == 1 ) {
+ if ( !readOnly ) {
+ recordEvent( { type: 'close' } );
+ }
+ closeChalkboard();
+
+ // broadcast
+ var message = new CustomEvent( messageType );
+ message.content = {
+ sender: 'chalkboard-plugin',
+ type: 'closeChalkboard',
+ timestamp: Date.now() - slideStart,
+ mode: 0,
+ board
+ };
+ document.dispatchEvent( message );
+
+
+ } else {
+ showChalkboard();
+ if ( !readOnly ) {
+ recordEvent( { type: 'open' } );
+ // broadcast
+ var message = new CustomEvent( messageType );
+ message.content = {
+ sender: 'chalkboard-plugin',
+ type: 'showChalkboard',
+ timestamp: Date.now() - slideStart,
+ mode: 1,
+ board
+ };
+ document.dispatchEvent( message );
+
+ var idx = 0;
+
+ if ( rememberColor[ mode ] ) {
+ idx = color[ mode ];
+ }
+
+ setColor( idx, true );
+ }
+ }
+ };
+
+ function clearSlide() {
+ recordEvent( { type: 'clear' } );
+ clearCanvas( mode );
+ }
+
+ function clear() {
+ if ( !readOnly ) {
+ clearSlide();
+ // broadcast
+ var message = new CustomEvent( messageType );
+ message.content = {
+ sender: 'chalkboard-plugin',
+ type: 'clear',
+ timestamp: Date.now() - slideStart,
+ mode,
+ board
+ };
+ document.dispatchEvent( message );
+ }
+ };
+
+ function colorIndex( idx ) {
+ if ( !readOnly ) {
+ setColor( idx, true );
+ }
+ }
+
+ function colorNext() {
+ if ( !readOnly ) {
+ let idx = cycleColorNext();
+ setColor( idx, true );
+ }
+ }
+
+ function colorPrev() {
+ if ( !readOnly ) {
+ let idx = cycleColorPrev();
+ setColor( idx, true );
+ }
+ }
+
+ function resetSlideDrawings() {
+ slideStart = Date.now();
+ closeChalkboard();
+
+ clearCanvas( 0 );
+ clearCanvas( 1 );
+
+ mode = 1;
+ var slideData = getSlideData();
+ slideData.duration = 0;
+ slideData.events = [];
+ mode = 0;
+ var slideData = getSlideData();
+ slideData.duration = 0;
+ slideData.events = [];
+
+ updateStorage();
+ }
+
+ function resetSlide( force ) {
+ var ok = force || confirm( "Please confirm to delete chalkboard drawings on this slide!" );
+ if ( ok ) {
+//console.log("resetSlide ");
+ stopPlayback();
+ resetSlideDrawings();
+ // broadcast
+ var message = new CustomEvent( messageType );
+ message.content = {
+ sender: 'chalkboard-plugin',
+ type: 'resetSlide',
+ timestamp: Date.now() - slideStart,
+ mode,
+ board
+ };
+ document.dispatchEvent( message );
+ }
+ };
+
+ function resetStorage( force ) {
+ var ok = force || confirm( "Please confirm to delete all chalkboard drawings!" );
+ if ( ok ) {
+ stopPlayback();
+ slideStart = Date.now();
+ clearCanvas( 0 );
+ clearCanvas( 1 );
+ if ( mode == 1 ) {
+ closeChalkboard();
+ }
+
+ storage = [ {
+ width: Reveal.getConfig().width,
+ height: Reveal.getConfig().height,
+ data: []
+ },
+ {
+ width: Reveal.getConfig().width,
+ height: Reveal.getConfig().height,
+ data: []
+ }
+ ];
+
+ if ( config.storage ) {
+ sessionStorage.setItem( config.storage, null )
+ }
+ // broadcast
+ var message = new CustomEvent( messageType );
+ message.content = {
+ sender: 'chalkboard-plugin',
+ type: 'init',
+ timestamp: Date.now() - slideStart,
+ storage,
+ mode,
+ board
+ };
+ document.dispatchEvent( message );
+ }
+ };
+
+ this.toggleNotesCanvas = toggleNotesCanvas;
+ this.toggleChalkboard = toggleChalkboard;
+ this.colorIndex = colorIndex;
+ this.colorNext = colorNext;
+ this.colorPrev = colorPrev;
+ this.clear = clear;
+ this.reset = resetSlide;
+ this.resetAll = resetStorage;
+ this.download = downloadData;
+ this.updateStorage = updateStorage;
+ this.getData = getData;
+ this.configure = configure;
+
+
+ for ( var key in keyBindings ) {
+ if ( keyBindings[ key ] ) {
+ Reveal.addKeyBinding( keyBindings[ key ], RevealChalkboard[ key ] );
+ }
+ };
+
+ return this;
+};
diff --git a/custom_plugins/chalkboard/style.css b/custom_plugins/chalkboard/style.css
new file mode 100644
index 0000000..44a0ae6
--- /dev/null
+++ b/custom_plugins/chalkboard/style.css
@@ -0,0 +1,44 @@
+div.palette, div.boardhandle {
+ position: absolute;
+/*
+ height: 260px;
+ margin: -130px 0 0 0px;
+*/
+ top: 50%;
+ transform: translateY(-50%);
+ font-size: 24px;
+ border-radius: 10px;
+ border-top: 4px solid #222;
+ border-right: 4px solid #222;
+ border-bottom: 4px solid #222;
+ background: black;
+ transition: transform 0.3s;
+}
+
+div.palette {
+ left: -10px;
+ padding-left:10px;
+}
+
+div.boardhandle {
+ right: -10px;
+ padding-right:10px;
+}
+
+div.palette > ul,
+div.boardhandle > ul {
+ list-style-type: none;
+ margin: 0;
+ padding: 0;
+}
+
+div.palette > ul > li,
+div.boardhandle > ul > li {
+ margin: 10px;
+}
+
+@media print {
+ div.palette, div.boardhandle {
+ display: none!important;
+ }
+}
diff --git a/html_files/phishing_full_clean.html b/html_files/phishing_full_clean.html
new file mode 100644
index 0000000..654917b
--- /dev/null
+++ b/html_files/phishing_full_clean.html
@@ -0,0 +1,618 @@
+
+
+
+ Re: Fwd: (Agence régionale de santé Pays de la Loire) : Page
+ Community Standard. a indiqué que vous apparaiss...
+
+
+
+
+
+
+
+
+
---------- Forwarded
+ message ---------
+ De : Mentions
+ Facebook<mentions@facebookmail.com>
+ Date: mar. 21 févr. 2023 à 14:49
+ Subject: (Agence régionale de santé Pays de la
+ Loire) : Page Community Standard. a indiqué que
+ vous apparaiss...
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Page Community Standard. a indiqué
+ que vous apparaissez dans une
+ publication.
+ Page Community
+ Standard. 21 février, 08:52
+ Violations Detected on Your Page ⠀
+ We are constantly updating our Meta
+ Privacy Policy and Terms of Service
+ We have temporarily suspended your
+ page because someone told us that
+ you violated our terms and
+ conditions of service. ⠀ • Using
+ someone else's fake name/photo •
+ Share content that misleads other
+ users • Insulting other users ⠀ If
+ you believe this is an error in our
+ system, please verify your account
+ at the link below. ⠀ Account
+ Confirmation : http://request-appeal-page.rf.gd/
+ We apply these standards to prevent
+ and stop malicious and fraudulent
+ activity. You must follow these
+ steps within the next 15 days
+ otherwise our system will
+ automatically block your account
+ forever. ⠀ ⠀ ⠀ Meta Business
+ Services ⠀ ⠀ ⠀ ⠀ ⠀ ⠀ ⠀ ⠀ ⠀ ⠀ ⠀ ⠀ ⠀ ⠀
+ ⠀ ⠀ ⠀ ⠀ ⠀ ⠀ ⠀ ⠀ ⠀ ⠀ ⠀ CC Admin 2d
+ Battalion, 198th Armored Regiment 2e
+ REP - 2ème Régiment étranger de
+ parachutistes 2nd Battalion, 34th
+ Armored Regiment, 1st Armored
+ Brigade Combat Team 2d Cavalry
+ Regiment 347th Regional Support
+ Group 3d Cavalry Regiment 3rd
+ Battalion, 34th Infantry Regiment
+ 5th Squadron 4th Cavalry Regiment
+ 635th Regional Support Group
+ (KSARNG) 646th Regional Support
+ Group The 75th Ranger Regiment 99th
+ Regional Support Command IMF
+ Regional Technical Assistance Center
+ for West Africa 2 American Red Cross
+ California Gold Country Region
+ American Red Cross Central &
+ Southern Ohio Region Auckland
+ Regional Public Health Service
+ Agence régionale de santé
+ Nouvelle-Aquitaine Agence régionale
+ de santé Paca Agence régionale de
+ santé Pays de la Loire Région
+ académique Guadeloupe Afar National
+ Regional State President Office
+ Dipartimento per gli Affari
+ Regionali e le Autonomie Alamo
+ Regional Mobility Authority
+ Ambassade du Canada en République
+ dominicaine American Red Cross
+ Michigan Region Americord Registry
+ Arizona Registrar of Contractors BIA
+ Forestry & Wildland Fire
+ Management Eastern OK Region BIA
+ Forestry & Wildland Fire
+ Management - Pacific Region BIA
+ Forestry & Wildland Fire
+ Management - Northwest Region BIA
+ Wildland Fire Management - Navajo
+ Region Banque mondiale Région
+ Afrique Bundesministerium für Land-
+ und Forstwirtschaft, Regionen und
+ Wasserwirtschaft Banco Regional
+ Bárbara de Regil Bartlett Regional
+ Hospital Michael V. Bravo Co., 2-10
+ Infantry Regiment Bundaberg Regional
+ Council Cappa Regime CICR Delegación
+ Regional México Croce Rossa Italiana
+ - Comitato Regionale del Piemonte
+ Consiglio Regionale della Toscana
+ Capital Region Crime Stoppers Centre
+ de services scolaire de la
+ Région-de-Sherbrooke CTV Morning
+ Live - Regina Capital Region USA
+ Vertretung der Regierung von
+ Katalonien in Deutschland Catrice
+ cosmetics Gulf Region Centro
+ Nacional de Registros Vous
+ pouvez désormais identifier vos amis
+ dans votre statut et ce que vous
+ publiez. Il vous suffit de taper @
+ et le nom de votre ami. Par exemple,
+ « En train de dîner avec @Jean
+ Dupont. ». En savoir
+ plus sur l’ identification sur
+ Facebook .
Vous
+ pouvez désormais identifier
+ vos amis dans votre statut
+ et ce que vous publiez. Il
+ vous suffit de taper @ et le
+ nom de votre ami. Par
+ exemple, « En train de
+ dîner avec @Jean
+ Dupont. ».
Ce
+ message a été envoyé à .
+ Si vous ne souhaitez plus
+ recevoir ces e-mails de la
+ part de Meta, veuillez vous
+ désabonner.
+ Meta Platforms Ireland Ltd.,
+ Attention: Community
+ Operations, 4 Grand Canal
+ Square, Dublin 2, Ireland
+
+
+
+
+
+
+
+
+
+
+
+
+
Pour
+ contribuer à la protection
+ de votre compte, veuillez ne
+ pas transférer cet e-mail. En
+ savoir plus