widget.ts 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921
  1. /*-----------------------------------------------------------------------------
  2. | Copyright (c) Jupyter Development Team.
  3. | Distributed under the terms of the Modified BSD License.
  4. |----------------------------------------------------------------------------*/
  5. import {
  6. KernelMessage
  7. } from '@jupyterlab/services';
  8. import {
  9. JSONValue, PromiseDelegate
  10. } from '@phosphor/coreutils';
  11. import {
  12. Message
  13. } from '@phosphor/messaging';
  14. import {
  15. PanelLayout, Panel, Widget
  16. } from '@phosphor/widgets';
  17. import {
  18. IClientSession
  19. } from '@jupyterlab/apputils';
  20. import {
  21. IChangedArgs, ActivityMonitor
  22. } from '@jupyterlab/coreutils';
  23. import {
  24. CodeEditor, CodeEditorWrapper
  25. } from '@jupyterlab/codeeditor';
  26. import {
  27. IRenderMime, MimeModel, RenderMime
  28. } from '@jupyterlab/rendermime';
  29. import {
  30. IObservableMap
  31. } from '@jupyterlab/coreutils';
  32. import {
  33. OutputArea, IOutputPrompt, OutputPrompt, IStdin, Stdin
  34. } from '@jupyterlab/outputarea';
  35. import {
  36. ICellModel, ICodeCellModel,
  37. IMarkdownCellModel, IRawCellModel
  38. } from './model';
  39. import {
  40. InputCollapser, OutputCollapser
  41. } from './collapser';
  42. import {
  43. InputArea, IInputPrompt, InputPrompt
  44. } from './inputarea';
  45. import {
  46. InputPlaceholder, OutputPlaceholder
  47. } from './placeholder';
  48. import {
  49. CellHeader, CellFooter, ICellHeader, ICellFooter
  50. } from './headerfooter';
  51. /**
  52. * The CSS class added to cell widgets.
  53. */
  54. const CELL_CLASS = 'jp-Cell';
  55. /**
  56. * The CSS class added to the cell header.
  57. */
  58. const CELL_HEADER_CLASS = 'jp-Cell-header';
  59. /**
  60. * The CSS class added to the cell footer.
  61. */
  62. const CELL_FOOTER_CLASS = 'jp-Cell-footer';
  63. /**
  64. * The CSS class added to the cell input wrapper.
  65. */
  66. const CELL_INPUT_WRAPPER_CLASS = 'jp-Cell-inputWrapper';
  67. /**
  68. * The CSS class added to the cell output wrapper.
  69. */
  70. const CELL_OUTPUT_WRAPPER_CLASS = 'jp-Cell-outputWrapper';
  71. /**
  72. * The CSS class added to the cell input area.
  73. */
  74. const CELL_INPUT_AREA_CLASS = 'jp-Cell-inputArea';
  75. /**
  76. * The CSS class added to the cell output area.
  77. */
  78. const CELL_OUTPUT_AREA_CLASS = 'jp-Cell-outputArea';
  79. /**
  80. * The CSS class added to the cell input collapser.
  81. */
  82. const CELL_INPUT_COLLAPSER_CLASS = 'jp-Cell-inputCollapser';
  83. /**
  84. * The CSS class added to the cell output collapser.
  85. */
  86. const CELL_OUTPUT_COLLAPSER_CLASS = 'jp-Cell-outputCollapser';
  87. /**
  88. * The class name added to the cell when collapsed.
  89. */
  90. const COLLAPSED_CLASS = 'jp-mod-collapsed';
  91. /**
  92. * The class name added to the cell when readonly.
  93. */
  94. const READONLY_CLASS = 'jp-mod-readOnly';
  95. /**
  96. * The class name added to code cells.
  97. */
  98. const CODE_CELL_CLASS = 'jp-CodeCell';
  99. /**
  100. * The class name added to markdown cells.
  101. */
  102. const MARKDOWN_CELL_CLASS = 'jp-MarkdownCell';
  103. /**
  104. * The class name added to rendered markdown output widgets.
  105. */
  106. const MARKDOWN_OUTPUT_CLASS = 'jp-MarkdownOutput';
  107. /**
  108. * The class name added to raw cells.
  109. */
  110. const RAW_CELL_CLASS = 'jp-RawCell';
  111. /**
  112. * The class name added to a rendered input area.
  113. */
  114. const RENDERED_CLASS = 'jp-mod-rendered';
  115. const NO_OUTPUTS_CLASS = 'jp-mod-noOutputs';
  116. /**
  117. * The text applied to an empty markdown cell.
  118. */
  119. const DEFAULT_MARKDOWN_TEXT = 'Type Markdown and LaTeX: $ α^2 $';
  120. /**
  121. * The timeout to wait for change activity to have ceased before rendering.
  122. */
  123. const RENDER_TIMEOUT = 1000;
  124. /******************************************************************************
  125. * Cell
  126. ******************************************************************************/
  127. /**
  128. * A base cell widget.
  129. */
  130. export
  131. class Cell extends Widget {
  132. /**
  133. * Construct a new base cell widget.
  134. */
  135. constructor(options: Cell.IOptions) {
  136. super();
  137. this.addClass(CELL_CLASS);
  138. let model = this._model = options.model;
  139. let contentFactory = this.contentFactory = (
  140. options.contentFactory || Cell.defaultContentFactory
  141. );
  142. this.layout = new PanelLayout();
  143. // Header
  144. let header = this._header = contentFactory.createCellHeader();
  145. header.addClass(CELL_HEADER_CLASS);
  146. (this.layout as PanelLayout).addWidget(header);
  147. // Input
  148. let inputWrapper = this._inputWrapper = new Panel();
  149. inputWrapper.addClass(CELL_INPUT_WRAPPER_CLASS);
  150. let inputCollapser = this._inputCollapser = new InputCollapser();
  151. inputCollapser.addClass(CELL_INPUT_COLLAPSER_CLASS);
  152. let input = this._input = new InputArea({model, contentFactory });
  153. input.addClass(CELL_INPUT_AREA_CLASS);
  154. inputWrapper.addWidget(inputCollapser);
  155. inputWrapper.addWidget(input);
  156. (this.layout as PanelLayout).addWidget(inputWrapper);
  157. this._inputPlaceholder = new InputPlaceholder();
  158. // Footer
  159. let footer = this._footer = this.contentFactory.createCellFooter();
  160. footer.addClass(CELL_FOOTER_CLASS);
  161. (this.layout as PanelLayout).addWidget(footer);
  162. }
  163. /**
  164. * The content factory used by the widget.
  165. */
  166. readonly contentFactory: Cell.IContentFactory;
  167. /**
  168. * Get the prompt node used by the cell.
  169. */
  170. get promptNode(): HTMLElement {
  171. if (!this._inputHidden) {
  172. return this._input.promptNode;
  173. } else {
  174. return ((this._inputPlaceholder.node as HTMLElement).firstElementChild as HTMLElement);
  175. }
  176. }
  177. /**
  178. * Get the CodeEditorWrapper used by the cell.
  179. */
  180. get editorWidget(): CodeEditorWrapper {
  181. return this._input.editorWidget;
  182. }
  183. /**
  184. * Get the CodeEditor used by the cell.
  185. */
  186. get editor(): CodeEditor.IEditor {
  187. return this._input.editor;
  188. }
  189. /**
  190. * Get the model used by the cell.
  191. */
  192. get model(): ICellModel {
  193. return this._model;
  194. }
  195. /**
  196. * Get the input area for the cell.
  197. */
  198. get inputArea(): InputArea {
  199. return this._input;
  200. }
  201. /**
  202. * The read only state of the cell.
  203. */
  204. get readOnly(): boolean {
  205. return this._readOnly;
  206. }
  207. set readOnly(value: boolean) {
  208. if (value === this._readOnly) {
  209. return;
  210. }
  211. this._readOnly = value;
  212. this.update();
  213. }
  214. /**
  215. * A promise that resolves when the widget renders for the first time.
  216. */
  217. get ready(): Promise<void> {
  218. return Promise.resolve(undefined);
  219. }
  220. /**
  221. * Set the prompt for the widget.
  222. */
  223. setPrompt(value: string): void {
  224. this._input.setPrompt(value);
  225. }
  226. /**
  227. * The view state of input being hidden.
  228. */
  229. get inputHidden(): boolean {
  230. return this._inputHidden;
  231. }
  232. set inputHidden(value: boolean) {
  233. if (this._inputHidden === value) {
  234. return;
  235. }
  236. let layout = this._inputWrapper.layout as PanelLayout;
  237. if (value) {
  238. this._input.parent = null;
  239. layout.addWidget(this._inputPlaceholder);
  240. } else {
  241. this._inputPlaceholder.parent = null;
  242. layout.addWidget(this._input);
  243. }
  244. this._inputHidden = value;
  245. this.handleInputHidden(value);
  246. }
  247. /**
  248. * Handle the input being hidden.
  249. *
  250. * #### Notes
  251. * This is called by the `inputHidden` setter so that subclasses
  252. * can perform actions upon the input being hidden without accessing
  253. * private state.
  254. */
  255. protected handleInputHidden(value: boolean): void {
  256. return;
  257. }
  258. /**
  259. * Dispose of the resources held by the widget.
  260. */
  261. dispose() {
  262. // Do nothing if already disposed.
  263. if (this.isDisposed) {
  264. return;
  265. }
  266. this._input = null;
  267. this._model = null;
  268. this._header = null;
  269. this._footer = null;
  270. this._inputCollapser = null;
  271. this._inputWrapper = null;
  272. this._inputPlaceholder = null;
  273. super.dispose();
  274. }
  275. /**
  276. * Handle `after-attach` messages.
  277. */
  278. protected onAfterAttach(msg: Message): void {
  279. this.update();
  280. }
  281. /**
  282. * Handle `'activate-request'` messages.
  283. */
  284. protected onActivateRequest(msg: Message): void {
  285. this.editor.focus();
  286. }
  287. /**
  288. * Handle `update-request` messages.
  289. */
  290. protected onUpdateRequest(msg: Message): void {
  291. if (!this._model) {
  292. return;
  293. }
  294. // Handle read only state.
  295. this.editor.setOption('readOnly', this._readOnly);
  296. this.toggleClass(READONLY_CLASS, this._readOnly);
  297. }
  298. private _readOnly = false;
  299. private _model: ICellModel = null;
  300. private _header: ICellHeader = null;
  301. private _footer: ICellFooter = null;
  302. private _inputHidden = false;
  303. private _input: InputArea = null;
  304. private _inputCollapser: InputCollapser = null;
  305. private _inputWrapper: Widget = null;
  306. private _inputPlaceholder: InputPlaceholder = null;
  307. }
  308. /**
  309. * The namespace for the `Cell` class statics.
  310. */
  311. export
  312. namespace Cell {
  313. /**
  314. * An options object for initializing a cell widget.
  315. */
  316. export
  317. interface IOptions {
  318. /**
  319. * The model used by the cell.
  320. */
  321. model: ICellModel;
  322. /**
  323. * The factory object for customizable cell children.
  324. */
  325. contentFactory?: IContentFactory;
  326. }
  327. /**
  328. * The factory object for customizable cell children.
  329. *
  330. * This is used to allow users of cells to customize child content.
  331. *
  332. * This inherits from `OutputArea.IContentFactory` to avoid needless nesting and
  333. * provide a single factory object for all notebook/cell/outputarea related
  334. * widgets.
  335. */
  336. export
  337. interface IContentFactory extends OutputArea.IContentFactory, InputArea.IContentFactory {
  338. /**
  339. * Create a new cell header for the parent widget.
  340. */
  341. createCellHeader(): ICellHeader;
  342. /**
  343. * Create a new cell header for the parent widget.
  344. */
  345. createCellFooter(): ICellFooter;
  346. }
  347. /**
  348. * The default implementation of an `IContentFactory`.
  349. *
  350. * This includes a CodeMirror editor factory to make it easy to use out of the box.
  351. */
  352. export
  353. class ContentFactory implements IContentFactory {
  354. /**
  355. * Create a content factory for a cell.
  356. */
  357. constructor(options: ContentFactory.IOptions = {}) {
  358. this._editorFactory = (options.editorFactory || InputArea.defaultEditorFactory);
  359. }
  360. /**
  361. * The readonly editor factory that create code editors
  362. */
  363. get editorFactory(): CodeEditor.Factory {
  364. return this._editorFactory;
  365. }
  366. /**
  367. * Create a new cell header for the parent widget.
  368. */
  369. createCellHeader(): ICellHeader {
  370. return new CellHeader();
  371. }
  372. /**
  373. * Create a new cell header for the parent widget.
  374. */
  375. createCellFooter(): ICellFooter {
  376. return new CellFooter();
  377. }
  378. /**
  379. * Create an input prompt.
  380. */
  381. createInputPrompt(): IInputPrompt {
  382. return new InputPrompt();
  383. }
  384. /**
  385. * Create the output prompt for the widget.
  386. */
  387. createOutputPrompt(): IOutputPrompt {
  388. return new OutputPrompt();
  389. }
  390. /**
  391. * Create an stdin widget.
  392. */
  393. createStdin(options: Stdin.IOptions): IStdin {
  394. return new Stdin(options);
  395. }
  396. private _editorFactory: CodeEditor.Factory = null;
  397. }
  398. /**
  399. * A namespace for cell content factory.
  400. */
  401. export
  402. namespace ContentFactory {
  403. /**
  404. * Options for the content factory.
  405. */
  406. export
  407. interface IOptions {
  408. /**
  409. * The editor factory used by the content factory.
  410. *
  411. * If this is not passed, a default CodeMirror editor factory
  412. * will be used.
  413. */
  414. editorFactory?: CodeEditor.Factory;
  415. }
  416. }
  417. /**
  418. * The default content factory for cells.
  419. */
  420. export
  421. const defaultContentFactory = new ContentFactory();
  422. }
  423. /******************************************************************************
  424. * CodeCell
  425. ******************************************************************************/
  426. /**
  427. * A widget for a code cell.
  428. */
  429. export
  430. class CodeCell extends Cell {
  431. /**
  432. * Construct a code cell widget.
  433. */
  434. constructor(options: CodeCell.IOptions) {
  435. super(options);
  436. this.addClass(CODE_CELL_CLASS);
  437. // Only save options not handled by parent constructor.
  438. let rendermime = this._rendermime = options.rendermime;
  439. let contentFactory = this.contentFactory;
  440. let model = this.model;
  441. // Code cells should not wrap lines.
  442. this.editor.setOption('lineWrap', false);
  443. // Insert the output before the cell footer.
  444. let outputWrapper = this._outputWrapper = new Panel();
  445. outputWrapper.addClass(CELL_OUTPUT_WRAPPER_CLASS);
  446. let outputCollapser = this._outputCollapser = new OutputCollapser();
  447. outputCollapser.addClass(CELL_OUTPUT_COLLAPSER_CLASS);
  448. let output = this._output = new OutputArea({
  449. model: model.outputs,
  450. rendermime,
  451. contentFactory: contentFactory
  452. });
  453. output.addClass(CELL_OUTPUT_AREA_CLASS);
  454. // Set a CSS if there are no outputs, and connect a signal for future
  455. // changes to the number of outputs. This is for conditional styling
  456. // if there are no outputs.
  457. if (model.outputs.length === 0) {
  458. this.addClass(NO_OUTPUTS_CLASS);
  459. }
  460. output.outputLengthChanged.connect(this._outputLengthHandler, this);
  461. outputWrapper.addWidget(outputCollapser);
  462. outputWrapper.addWidget(output);
  463. (this.layout as PanelLayout).insertWidget(2, outputWrapper);
  464. this._outputPlaceholder = new OutputPlaceholder();
  465. // Modify state
  466. this.setPrompt(`${model.executionCount || ''}`);
  467. model.stateChanged.connect(this.onStateChanged, this);
  468. model.metadata.changed.connect(this.onMetadataChanged, this);
  469. }
  470. /**
  471. * The model used by the widget.
  472. */
  473. readonly model: ICodeCellModel;
  474. /**
  475. * Get the output area for the cell.
  476. */
  477. get outputArea(): OutputArea {
  478. return this._output;
  479. }
  480. /**
  481. * The view state of output being collapsed.
  482. */
  483. get outputHidden(): boolean {
  484. return this._outputHidden;
  485. }
  486. set outputHidden(value: boolean) {
  487. if (this._outputHidden === value) {
  488. return;
  489. }
  490. let layout = this._outputWrapper.layout as PanelLayout;
  491. if (value) {
  492. layout.removeWidget(this._output);
  493. layout.addWidget(this._outputPlaceholder);
  494. if (this.inputHidden && !this._outputWrapper.isHidden) {
  495. this._outputWrapper.hide();
  496. }
  497. } else {
  498. if (this._outputWrapper.isHidden) {
  499. this._outputWrapper.show();
  500. }
  501. layout.removeWidget(this._outputPlaceholder);
  502. layout.addWidget(this._output);
  503. }
  504. this._outputHidden = value;
  505. }
  506. /**
  507. * Handle the input being hidden.
  508. *
  509. * #### Notes
  510. * This method is called by the case cell implementation and is
  511. * subclasses here so the code cell can watch to see when input
  512. * is hidden without accessing private state.
  513. */
  514. protected handleInputHidden(value: boolean): void {
  515. if (!value && this._outputWrapper.isHidden) {
  516. this._outputWrapper.show();
  517. } else if (value && !this._outputWrapper.isHidden && this._outputHidden) {
  518. this._outputWrapper.hide();
  519. }
  520. }
  521. /**
  522. * Dispose of the resources used by the widget.
  523. */
  524. dispose(): void {
  525. if (this.isDisposed) {
  526. return;
  527. }
  528. this._output.outputLengthChanged.disconnect(this._outputLengthHandler, this);
  529. this._rendermime = null;
  530. this._output = null;
  531. this._outputWrapper = null;
  532. this._outputCollapser = null;
  533. this._outputPlaceholder = null;
  534. super.dispose();
  535. }
  536. /**
  537. * Handle `update-request` messages.
  538. */
  539. protected onUpdateRequest(msg: Message): void {
  540. let value = this.model.metadata.get('collapsed') as boolean;
  541. this.toggleClass(COLLAPSED_CLASS, value);
  542. if (this._output) {
  543. // TODO: handle scrolled state.
  544. }
  545. super.onUpdateRequest(msg);
  546. }
  547. /**
  548. * Handle changes in the model.
  549. */
  550. protected onStateChanged(model: ICellModel, args: IChangedArgs<any>): void {
  551. switch (args.name) {
  552. case 'executionCount':
  553. this.setPrompt(`${(model as ICodeCellModel).executionCount || ''}`);
  554. break;
  555. default:
  556. break;
  557. }
  558. }
  559. /**
  560. * Handle changes in the metadata.
  561. */
  562. protected onMetadataChanged(model: IObservableMap<JSONValue>, args: IObservableMap.IChangedArgs<JSONValue>): void {
  563. switch (args.key) {
  564. case 'collapsed':
  565. case 'scrolled':
  566. this.update();
  567. break;
  568. default:
  569. break;
  570. }
  571. }
  572. /**
  573. * Handle changes in the number of outputs in the output area.
  574. */
  575. private _outputLengthHandler(sender: OutputArea, args: number) {
  576. let force = args === 0 ? true : false;
  577. this.toggleClass(NO_OUTPUTS_CLASS, force);
  578. }
  579. private _rendermime: RenderMime = null;
  580. private _outputHidden = false;
  581. private _outputWrapper: Widget = null;
  582. private _outputCollapser: OutputCollapser = null;
  583. private _outputPlaceholder: OutputPlaceholder = null;
  584. private _output: OutputArea = null;
  585. }
  586. /**
  587. * The namespace for the `CodeCell` class statics.
  588. */
  589. export
  590. namespace CodeCell {
  591. /**
  592. * An options object for initializing a base cell widget.
  593. */
  594. export
  595. interface IOptions extends Cell.IOptions {
  596. /**
  597. * The model used by the cell.
  598. */
  599. model: ICodeCellModel;
  600. /**
  601. * The mime renderer for the cell widget.
  602. */
  603. rendermime: RenderMime;
  604. }
  605. /**
  606. * Execute a cell given a client session.
  607. */
  608. export
  609. function execute(cell: CodeCell, session: IClientSession): Promise<KernelMessage.IExecuteReplyMsg> {
  610. let model = cell.model;
  611. let code = model.value.text;
  612. if (!code.trim() || !session.kernel) {
  613. model.executionCount = null;
  614. model.outputs.clear();
  615. return Promise.resolve(void 0);
  616. }
  617. model.executionCount = null;
  618. cell.outputHidden = false;
  619. cell.setPrompt('*');
  620. model.trusted = true;
  621. return OutputArea.execute(code, cell.outputArea, session).then(msg => {
  622. model.executionCount = msg.content.execution_count;
  623. return msg;
  624. });
  625. }
  626. }
  627. /******************************************************************************
  628. * MarkdownCell
  629. ******************************************************************************/
  630. /**
  631. * A widget for a Markdown cell.
  632. *
  633. * #### Notes
  634. * Things get complicated if we want the rendered text to update
  635. * any time the text changes, the text editor model changes,
  636. * or the input area model changes. We don't support automatically
  637. * updating the rendered text in all of these cases.
  638. */
  639. export
  640. class MarkdownCell extends Cell {
  641. /**
  642. * Construct a Markdown cell widget.
  643. */
  644. constructor(options: MarkdownCell.IOptions) {
  645. super(options);
  646. this.addClass(MARKDOWN_CELL_CLASS);
  647. this._rendermime = options.rendermime;
  648. // Throttle the rendering rate of the widget.
  649. this._monitor = new ActivityMonitor({
  650. signal: this.model.contentChanged,
  651. timeout: RENDER_TIMEOUT
  652. });
  653. this._monitor.activityStopped.connect(() => {
  654. if (this._rendered) {
  655. this.update();
  656. }
  657. }, this);
  658. this._updateRenderedInput().then(() => {
  659. this._ready.resolve(void 0);
  660. });
  661. }
  662. /**
  663. * The model used by the widget.
  664. */
  665. readonly model: IMarkdownCellModel;
  666. /**
  667. * A promise that resolves when the widget renders for the first time.
  668. */
  669. get ready(): Promise<void> {
  670. return this._ready.promise;
  671. }
  672. /**
  673. * Whether the cell is rendered.
  674. */
  675. get rendered(): boolean {
  676. return this._rendered;
  677. }
  678. set rendered(value: boolean) {
  679. if (value === this._rendered) {
  680. return;
  681. }
  682. this._rendered = value;
  683. this._handleRendered();
  684. }
  685. /**
  686. * Render an input instead of the text editor.
  687. */
  688. protected renderInput(widget: Widget): void {
  689. this.addClass(RENDERED_CLASS);
  690. this.inputArea.renderInput(widget);
  691. }
  692. /**
  693. * Show the text editor instead of rendered input.
  694. */
  695. protected showEditor(): void {
  696. this.removeClass(RENDERED_CLASS);
  697. this.inputArea.showEditor();
  698. }
  699. /*
  700. * Handle `update-request` messages.
  701. */
  702. protected onUpdateRequest(msg: Message): void {
  703. // Make sure we are properly rendered.
  704. this._handleRendered();
  705. super.onUpdateRequest(msg);
  706. }
  707. /**
  708. * Handle the rendered state.
  709. */
  710. private _handleRendered(): void {
  711. if (!this._rendered) {
  712. this.showEditor();
  713. } else {
  714. this._updateRenderedInput();
  715. this.renderInput(this._renderer);
  716. }
  717. }
  718. /**
  719. * Update the rendered input.
  720. */
  721. private _updateRenderedInput(): Promise<void> {
  722. let model = this.model;
  723. let text = model && model.value.text || DEFAULT_MARKDOWN_TEXT;
  724. // Do not re-render if the text has not changed.
  725. if (text !== this._prevText) {
  726. let mimeModel = new MimeModel({ data: { 'text/markdown': text }});
  727. if (!this._renderer) {
  728. this._renderer = this._rendermime.createRenderer(mimeModel);
  729. this._renderer.addClass(MARKDOWN_OUTPUT_CLASS);
  730. }
  731. this._prevText = text;
  732. return this._renderer.renderModel(mimeModel);
  733. }
  734. return Promise.resolve(void 0);
  735. }
  736. private _monitor: ActivityMonitor<any, any> = null;
  737. private _renderer: IRenderMime.IRenderer = null;
  738. private _rendermime: RenderMime;
  739. private _rendered = true;
  740. private _prevText = '';
  741. private _ready = new PromiseDelegate<void>();
  742. }
  743. /**
  744. * The namespace for the `CodeCell` class statics.
  745. */
  746. export
  747. namespace MarkdownCell {
  748. /**
  749. * An options object for initializing a base cell widget.
  750. */
  751. export
  752. interface IOptions extends Cell.IOptions {
  753. /**
  754. * The model used by the cell.
  755. */
  756. model: IMarkdownCellModel;
  757. /**
  758. * The mime renderer for the cell widget.
  759. */
  760. rendermime: RenderMime;
  761. }
  762. }
  763. /******************************************************************************
  764. * RawCell
  765. ******************************************************************************/
  766. /**
  767. * A widget for a raw cell.
  768. */
  769. export
  770. class RawCell extends Cell {
  771. /**
  772. * Construct a raw cell widget.
  773. */
  774. constructor(options: Cell.IOptions) {
  775. super(options);
  776. this.addClass(RAW_CELL_CLASS);
  777. }
  778. /**
  779. * The model used by the widget.
  780. */
  781. readonly model: IRawCellModel;
  782. }
  783. /**
  784. * The namespace for the `RawCell` class statics.
  785. */
  786. export
  787. namespace RawCell {
  788. /**
  789. * An options object for initializing a base cell widget.
  790. */
  791. export
  792. interface IOptions extends Cell.IOptions {
  793. /**
  794. * The model used by the cell.
  795. */
  796. model: IRawCellModel;
  797. }
  798. }