index.ts 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. // Copyright (c) Jupyter Development Team.
  2. // Distributed under the terms of the Modified BSD License.
  3. import {
  4. ILayoutRestorer, JupyterLab, JupyterLabPlugin
  5. } from '@jupyterlab/application';
  6. import {
  7. Dialog, ICommandPalette, IFrame, IMainMenu, InstanceTracker, showDialog
  8. } from '@jupyterlab/apputils';
  9. import {
  10. PageConfig, URLExt
  11. } from '@jupyterlab/coreutils';
  12. import {
  13. Message
  14. } from '@phosphor/messaging';
  15. import {
  16. h
  17. } from '@phosphor/virtualdom';
  18. import {
  19. Menu, PanelLayout, Widget
  20. } from '@phosphor/widgets';
  21. import '../style/index.css';
  22. /**
  23. * The command IDs used by the help plugin.
  24. */
  25. namespace CommandIDs {
  26. export
  27. const open = 'help:open';
  28. export
  29. const about = 'help:about';
  30. export
  31. const activate = 'help:activate';
  32. export
  33. const close = 'help:close';
  34. export
  35. const show = 'help:show';
  36. export
  37. const hide = 'help:hide';
  38. export
  39. const toggle = 'help:toggle';
  40. export
  41. const launchClassic = 'help:launch-classic-notebook';
  42. };
  43. /**
  44. * A flag denoting whether the application is loaded over HTTPS.
  45. */
  46. const LAB_IS_SECURE = window.location.protocol === 'https:';
  47. /**
  48. * The class name added to the help widget.
  49. */
  50. const HELP_CLASS = 'jp-Help';
  51. /**
  52. * A list of help resources.
  53. */
  54. const RESOURCES = [
  55. {
  56. text: 'Notebook Reference',
  57. url: 'https://jupyter-notebook.readthedocs.io/en/latest/'
  58. },
  59. {
  60. text: 'IPython Reference',
  61. url: 'https://ipython.readthedocs.io/en/stable/'
  62. },
  63. {
  64. text: 'Numpy Reference',
  65. url: 'https://docs.scipy.org/doc/numpy/reference/'
  66. },
  67. {
  68. text: 'Scipy Reference',
  69. url: 'https://docs.scipy.org/doc/scipy/reference/'
  70. },
  71. {
  72. text: 'Python Reference',
  73. url: 'https://docs.python.org/3.5/'
  74. },
  75. {
  76. text: 'Matplotlib Reference',
  77. url: 'https://matplotlib.org/contents.html?v=20160707164940'
  78. },
  79. {
  80. text: 'SymPy Reference',
  81. url: 'http://docs.sympy.org/latest/index.html?v=20160707164940'
  82. },
  83. {
  84. text: 'Pandas Reference',
  85. url: 'https://pandas.pydata.org/pandas-docs/stable/?v=20160707164940'
  86. },
  87. {
  88. text: 'Markdown Reference',
  89. url: 'https://help.github.com/articles/' +
  90. 'getting-started-with-writing-and-formatting-on-github/'
  91. }
  92. ];
  93. RESOURCES.sort((a: any, b: any) => {
  94. return a.text.localeCompare(b.text);
  95. });
  96. /**
  97. * The help handler extension.
  98. */
  99. const plugin: JupyterLabPlugin<void> = {
  100. activate,
  101. id: 'jupyter.extensions.help-handler',
  102. requires: [IMainMenu, ICommandPalette, ILayoutRestorer],
  103. autoStart: true
  104. };
  105. /**
  106. * Export the plugin as default.
  107. */
  108. export default plugin;
  109. /*
  110. * An IFrame the disposes itself when closed.
  111. *
  112. * This is needed to clear the state restoration db when IFrames are closed.
  113. */
  114. class HelpWidget extends Widget {
  115. /**
  116. * Construct a new help widget.
  117. */
  118. constructor(url: string) {
  119. super();
  120. let layout = this.layout = new PanelLayout();
  121. let iframe = new IFrame();
  122. this.url = iframe.url = url;
  123. layout.addWidget(iframe);
  124. }
  125. /**
  126. * The url of the widget.
  127. */
  128. readonly url: string;
  129. /**
  130. * Handle activate requests for the widget.
  131. */
  132. protected onActivateRequest(msg: Message): void {
  133. this.node.tabIndex = -1;
  134. this.node.focus();
  135. }
  136. /**
  137. * Dispose of the IFrame when closing.
  138. */
  139. protected onCloseRequest(msg: Message): void {
  140. this.dispose();
  141. }
  142. }
  143. /**
  144. * Activate the help handler extension.
  145. *
  146. * @param app - The phosphide application object.
  147. *
  148. * returns A promise that resolves when the extension is activated.
  149. */
  150. function activate(app: JupyterLab, mainMenu: IMainMenu, palette: ICommandPalette, restorer: ILayoutRestorer): void {
  151. let counter = 0;
  152. const category = 'Help';
  153. const namespace = 'help-doc';
  154. const menu = createMenu();
  155. const { commands, shell, info} = app;
  156. const tracker = new InstanceTracker<HelpWidget>({ namespace });
  157. // Handle state restoration.
  158. restorer.restore(tracker, {
  159. command: CommandIDs.open,
  160. args: widget => ({ url: widget.url, text: widget.title.label }),
  161. name: widget => widget.url
  162. });
  163. /**
  164. * Create a new HelpWidget widget.
  165. */
  166. function newClosableIFrame(url: string, text: string): HelpWidget {
  167. let iframe = new HelpWidget(url);
  168. iframe.addClass(HELP_CLASS);
  169. iframe.title.label = text;
  170. iframe.title.closable = true;
  171. iframe.id = `${namespace}-${++counter}`;
  172. tracker.add(iframe);
  173. return iframe;
  174. }
  175. /**
  176. * Create a menu for the help plugin.
  177. */
  178. function createMenu(): Menu {
  179. let { commands } = app;
  180. let menu = new Menu({ commands });
  181. menu.title.label = category;
  182. menu.addItem({ command: CommandIDs.about });
  183. menu.addItem({ command: 'faq-jupyterlab:open' });
  184. menu.addItem({ command: CommandIDs.launchClassic });
  185. menu.addItem({ type: 'separator' });
  186. RESOURCES.forEach(args => {
  187. menu.addItem({ args, command: CommandIDs.open });
  188. });
  189. menu.addItem({ type: 'separator' });
  190. menu.addItem({ command: 'apputils:clear-statedb' });
  191. return menu;
  192. }
  193. commands.addCommand(CommandIDs.about, {
  194. label: `About ${info.name}`,
  195. execute: () => {
  196. // Create the header of the about dialog
  197. let headerLogo = h.div({className: 'jp-About-header-logo'});
  198. let headerWordmark = h.div({className: 'jp-About-header-wordmark'});
  199. let release = 'alpha release';
  200. let versionNumber = `version: ${info.version}`;
  201. let versionInfo = h.span({className: 'jp-About-version-info'},
  202. h.span({className: 'jp-About-release'}, release),
  203. h.span({className: 'jp-About-version'}, versionNumber)
  204. );
  205. let title = h.span({className: 'jp-About-header'},
  206. headerLogo,
  207. h.div({className: 'jp-About-header-info'},
  208. headerWordmark,
  209. versionInfo
  210. )
  211. );
  212. // Create the body of the about dialog
  213. let jupyterURL = 'https://jupyter.org/about.html';
  214. let contributorsURL = 'https://github.com/jupyterlab/jupyterlab/graphs/contributors';
  215. let externalLinks = h.span({className: 'jp-About-externalLinks'},
  216. h.a({href: contributorsURL, target: '_blank', className: 'jp-Button-flat'}, 'CONTRIBUTOR LIST'),
  217. h.a({href: jupyterURL, target: '_blank', className: 'jp-Button-flat'}, 'ABOUT PROJECT JUPYTER')
  218. );
  219. let copyright = h.span({className: 'jp-About-copyright'}, '© 2017 Project Jupyter');
  220. let body = h.div({ className: 'jp-About-body' },
  221. externalLinks,
  222. copyright
  223. );
  224. showDialog({
  225. title,
  226. body,
  227. buttons: [Dialog.createButton({label: 'DISMISS', className: 'jp-About-button jp-mod-reject jp-mod-styled'})]
  228. });
  229. }
  230. });
  231. commands.addCommand(CommandIDs.open, {
  232. label: args => args['text'] as string,
  233. execute: args => {
  234. const url = args['url'] as string;
  235. const text = args['text'] as string;
  236. // If help resource will generate a mixed content error, load externally.
  237. if (LAB_IS_SECURE && URLExt.parse(url).protocol !== 'https:') {
  238. window.open(url);
  239. return;
  240. }
  241. let iframe = newClosableIFrame(url, text);
  242. shell.addToMainArea(iframe);
  243. shell.activateById(iframe.id);
  244. }
  245. });
  246. commands.addCommand(CommandIDs.launchClassic, {
  247. label: 'Launch Classic Notebook',
  248. execute: () => { window.open(PageConfig.getBaseUrl() + 'tree'); }
  249. });
  250. RESOURCES.forEach(args => {
  251. palette.addItem({ args, command: CommandIDs.open, category });
  252. });
  253. palette.addItem({ command: 'apputils:clear-statedb', category });
  254. palette.addItem({ command: CommandIDs.launchClassic, category });
  255. mainMenu.addMenu(menu);
  256. }