notes.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. /**
  2. * Handles opening of and synchronization with the reveal.js
  3. * notes window.
  4. *
  5. * Handshake process:
  6. * 1. This window posts 'connect' to notes window
  7. * - Includes URL of presentation to show
  8. * 2. Notes window responds with 'connected' when it is available
  9. * 3. This window proceeds to send the current presentation state
  10. * to the notes window
  11. */
  12. var RevealNotes = (function () {
  13. var notesPopup = null;
  14. function openNotes(notesFilePath) {
  15. if (notesPopup && !notesPopup.closed) {
  16. notesPopup.focus();
  17. return;
  18. }
  19. if (!notesFilePath) {
  20. var jsFileLocation = document.querySelector('script[src$="notes.js"]').src; // this js file path
  21. jsFileLocation = jsFileLocation.replace(/notes\.js(\?.*)?$/, ''); // the js folder path
  22. notesFilePath = jsFileLocation + 'notes.html';
  23. }
  24. notesPopup = window.open(notesFilePath, 'reveal.js - Notes', 'width=1100,height=700');
  25. if (!notesPopup) {
  26. alert('Speaker view popup failed to open. Please make sure popups are allowed and reopen the speaker view.');
  27. return;
  28. }
  29. /**
  30. * Connect to the notes window through a postmessage handshake.
  31. * Using postmessage enables us to work in situations where the
  32. * origins differ, such as a presentation being opened from the
  33. * file system.
  34. */
  35. function connect() {
  36. // Keep trying to connect until we get a 'connected' message back
  37. var connectInterval = setInterval(function () {
  38. notesPopup.postMessage(
  39. JSON.stringify({
  40. namespace: 'reveal-notes',
  41. type: 'connect',
  42. url:
  43. window.location.protocol +
  44. '//' +
  45. window.location.host +
  46. window.location.pathname +
  47. window.location.search,
  48. state: Reveal.getState(),
  49. }),
  50. '*',
  51. );
  52. }, 500);
  53. window.addEventListener('message', function (event) {
  54. var data = JSON.parse(event.data);
  55. if (data && data.namespace === 'reveal-notes' && data.type === 'connected') {
  56. clearInterval(connectInterval);
  57. onConnected();
  58. }
  59. if (data && data.namespace === 'reveal-notes' && data.type === 'call') {
  60. callRevealApi(data.methodName, data.arguments, data.callId);
  61. }
  62. });
  63. }
  64. /**
  65. * Calls the specified Reveal.js method with the provided argument
  66. * and then pushes the result to the notes frame.
  67. */
  68. function callRevealApi(methodName, methodArguments, callId) {
  69. var result = Reveal[methodName].apply(Reveal, methodArguments);
  70. notesPopup.postMessage(
  71. JSON.stringify({
  72. namespace: 'reveal-notes',
  73. type: 'return',
  74. result: result,
  75. callId: callId,
  76. }),
  77. '*',
  78. );
  79. }
  80. /**
  81. * Posts the current slide data to the notes window
  82. */
  83. function post(event) {
  84. var slideElement = Reveal.getCurrentSlide(),
  85. notesElement = slideElement.querySelector('aside.notes'),
  86. fragmentElement = slideElement.querySelector('.current-fragment');
  87. var messageData = {
  88. namespace: 'reveal-notes',
  89. type: 'state',
  90. notes: '',
  91. markdown: false,
  92. whitespace: 'normal',
  93. state: Reveal.getState(),
  94. };
  95. // Look for notes defined in a slide attribute
  96. if (slideElement.hasAttribute('data-notes')) {
  97. messageData.notes = slideElement.getAttribute('data-notes');
  98. messageData.whitespace = 'pre-wrap';
  99. }
  100. // Look for notes defined in a fragment
  101. if (fragmentElement) {
  102. var fragmentNotes = fragmentElement.querySelector('aside.notes');
  103. if (fragmentNotes) {
  104. notesElement = fragmentNotes;
  105. } else if (fragmentElement.hasAttribute('data-notes')) {
  106. messageData.notes = fragmentElement.getAttribute('data-notes');
  107. messageData.whitespace = 'pre-wrap';
  108. // In case there are slide notes
  109. notesElement = null;
  110. }
  111. }
  112. // Look for notes defined in an aside element
  113. if (notesElement) {
  114. messageData.notes = notesElement.innerHTML;
  115. messageData.markdown = typeof notesElement.getAttribute('data-markdown') === 'string';
  116. }
  117. notesPopup.postMessage(JSON.stringify(messageData), '*');
  118. }
  119. /**
  120. * Called once we have established a connection to the notes
  121. * window.
  122. */
  123. function onConnected() {
  124. // Monitor events that trigger a change in state
  125. Reveal.addEventListener('slidechanged', post);
  126. Reveal.addEventListener('fragmentshown', post);
  127. Reveal.addEventListener('fragmenthidden', post);
  128. Reveal.addEventListener('overviewhidden', post);
  129. Reveal.addEventListener('overviewshown', post);
  130. Reveal.addEventListener('paused', post);
  131. Reveal.addEventListener('resumed', post);
  132. // Post the initial state
  133. post();
  134. }
  135. connect();
  136. }
  137. return {
  138. init: function () {
  139. if (!/receiver/i.test(window.location.search)) {
  140. // If the there's a 'notes' query set, open directly
  141. if (window.location.search.match(/(\?|\&)notes/gi) !== null) {
  142. openNotes();
  143. }
  144. // Open the notes when the 's' key is hit
  145. Reveal.addKeyBinding({keyCode: 83, key: 'S', description: 'Speaker notes view'}, function () {
  146. openNotes();
  147. });
  148. }
  149. },
  150. open: openNotes,
  151. };
  152. })();
  153. Reveal.registerPlugin('notes', RevealNotes);