registry.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450
  1. /*-----------------------------------------------------------------------------
  2. | Copyright (c) Jupyter Development Team.
  3. | Distributed under the terms of the Modified BSD License.
  4. |----------------------------------------------------------------------------*/
  5. import { Contents, Session } from '@jupyterlab/services';
  6. import { IRenderMime } from '@jupyterlab/rendermime-interfaces';
  7. import { PathExt, URLExt } from '@jupyterlab/coreutils';
  8. import {
  9. IClientSession,
  10. ISanitizer,
  11. defaultSanitizer
  12. } from '@jupyterlab/apputils';
  13. import { ReadonlyJSONObject, Token } from '@phosphor/coreutils';
  14. import { MimeModel } from './mimemodel';
  15. /* tslint:disable */
  16. /**
  17. * The rendermime token.
  18. */
  19. export const IRenderMimeRegistry = new Token<IRenderMimeRegistry>(
  20. '@jupyterlab/rendermime:IRenderMimeRegistry'
  21. );
  22. export interface IRenderMimeRegistry extends RenderMimeRegistry {}
  23. /* tslint:enable */
  24. /* tslint:disable */
  25. /**
  26. * The latex typesetter token.
  27. */
  28. export const ILatexTypesetter = new Token<IRenderMime.ILatexTypesetter>(
  29. '@jupyterlab/rendermime:ILatexTypesetter'
  30. );
  31. export interface ILatexTypesetter extends IRenderMime.ILatexTypesetter {}
  32. /* tslint:enable */
  33. /**
  34. * An object which manages mime renderer factories.
  35. *
  36. * This object is used to render mime models using registered mime
  37. * renderers, selecting the preferred mime renderer to render the
  38. * model into a widget.
  39. *
  40. * #### Notes
  41. * This class is not intended to be subclassed.
  42. */
  43. export class RenderMimeRegistry {
  44. /**
  45. * Construct a new rendermime.
  46. *
  47. * @param options - The options for initializing the instance.
  48. */
  49. constructor(options: RenderMimeRegistry.IOptions = {}) {
  50. // Parse the options.
  51. this.resolver = options.resolver || null;
  52. this.linkHandler = options.linkHandler || null;
  53. this.latexTypesetter = options.latexTypesetter || null;
  54. this.sanitizer = options.sanitizer || defaultSanitizer;
  55. // Add the initial factories.
  56. if (options.initialFactories) {
  57. for (let factory of options.initialFactories) {
  58. this.addFactory(factory);
  59. }
  60. }
  61. }
  62. /**
  63. * The sanitizer used by the rendermime instance.
  64. */
  65. readonly sanitizer: ISanitizer;
  66. /**
  67. * The object used to resolve relative urls for the rendermime instance.
  68. */
  69. readonly resolver: IRenderMime.IResolver | null;
  70. /**
  71. * The object used to handle path opening links.
  72. */
  73. readonly linkHandler: IRenderMime.ILinkHandler | null;
  74. /**
  75. * The LaTeX typesetter for the rendermime.
  76. */
  77. readonly latexTypesetter: IRenderMime.ILatexTypesetter | null;
  78. /**
  79. * The ordered list of mimeTypes.
  80. */
  81. get mimeTypes(): ReadonlyArray<string> {
  82. return this._types || (this._types = Private.sortedTypes(this._ranks));
  83. }
  84. /**
  85. * Find the preferred mime type for a mime bundle.
  86. *
  87. * @param bundle - The bundle of mime data.
  88. *
  89. * @param safe - How to consider safe/unsafe factories. If 'ensure',
  90. * it will only consider safe factories. If 'any', any factory will be
  91. * considered. If 'prefer', unsafe factories will be considered, but
  92. * only after the safe options have been exhausted.
  93. *
  94. * @returns The preferred mime type from the available factories,
  95. * or `undefined` if the mime type cannot be rendered.
  96. */
  97. preferredMimeType(
  98. bundle: ReadonlyJSONObject,
  99. safe: 'ensure' | 'prefer' | 'any' = 'ensure'
  100. ): string | undefined {
  101. // Try to find a safe factory first, if preferred.
  102. if (safe === 'ensure' || safe === 'prefer') {
  103. for (let mt of this.mimeTypes) {
  104. if (mt in bundle && this._factories[mt].safe) {
  105. return mt;
  106. }
  107. }
  108. }
  109. if (safe !== 'ensure') {
  110. // Otherwise, search for the best factory among all factories.
  111. for (let mt of this.mimeTypes) {
  112. if (mt in bundle) {
  113. return mt;
  114. }
  115. }
  116. }
  117. // Otherwise, no matching mime type exists.
  118. return undefined;
  119. }
  120. /**
  121. * Create a renderer for a mime type.
  122. *
  123. * @param mimeType - The mime type of interest.
  124. *
  125. * @returns A new renderer for the given mime type.
  126. *
  127. * @throws An error if no factory exists for the mime type.
  128. */
  129. createRenderer(mimeType: string): IRenderMime.IRenderer {
  130. // Throw an error if no factory exists for the mime type.
  131. if (!(mimeType in this._factories)) {
  132. throw new Error(`No factory for mime type: '${mimeType}'`);
  133. }
  134. // Invoke the best factory for the given mime type.
  135. return this._factories[mimeType].createRenderer({
  136. mimeType,
  137. resolver: this.resolver,
  138. sanitizer: this.sanitizer,
  139. linkHandler: this.linkHandler,
  140. latexTypesetter: this.latexTypesetter
  141. });
  142. }
  143. /**
  144. * Create a new mime model. This is a convenience method.
  145. *
  146. * @options - The options used to create the model.
  147. *
  148. * @returns A new mime model.
  149. */
  150. createModel(options: MimeModel.IOptions = {}): MimeModel {
  151. return new MimeModel(options);
  152. }
  153. /**
  154. * Create a clone of this rendermime instance.
  155. *
  156. * @param options - The options for configuring the clone.
  157. *
  158. * @returns A new independent clone of the rendermime.
  159. */
  160. clone(options: RenderMimeRegistry.ICloneOptions = {}): RenderMimeRegistry {
  161. // Create the clone.
  162. let clone = new RenderMimeRegistry({
  163. resolver: options.resolver || this.resolver || undefined,
  164. sanitizer: options.sanitizer || this.sanitizer || undefined,
  165. linkHandler: options.linkHandler || this.linkHandler || undefined,
  166. latexTypesetter: options.latexTypesetter || this.latexTypesetter
  167. });
  168. // Clone the internal state.
  169. clone._factories = { ...this._factories };
  170. clone._ranks = { ...this._ranks };
  171. clone._id = this._id;
  172. // Return the cloned object.
  173. return clone;
  174. }
  175. /**
  176. * Get the renderer factory registered for a mime type.
  177. *
  178. * @param mimeType - The mime type of interest.
  179. *
  180. * @returns The factory for the mime type, or `undefined`.
  181. */
  182. getFactory(mimeType: string): IRenderMime.IRendererFactory | undefined {
  183. return this._factories[mimeType];
  184. }
  185. /**
  186. * Add a renderer factory to the rendermime.
  187. *
  188. * @param factory - The renderer factory of interest.
  189. *
  190. * @param rank - The rank of the renderer. A lower rank indicates
  191. * a higher priority for rendering. If not given, the rank will
  192. * defer to the `defaultRank` of the factory. If no `defaultRank`
  193. * is given, it will default to 100.
  194. *
  195. * #### Notes
  196. * The renderer will replace an existing renderer for the given
  197. * mimeType.
  198. */
  199. addFactory(factory: IRenderMime.IRendererFactory, rank?: number): void {
  200. if (rank === undefined) {
  201. rank = factory.defaultRank;
  202. if (rank === undefined) {
  203. rank = 100;
  204. }
  205. }
  206. for (let mt of factory.mimeTypes) {
  207. this._factories[mt] = factory;
  208. this._ranks[mt] = { rank, id: this._id++ };
  209. }
  210. this._types = null;
  211. }
  212. /**
  213. * Remove a mime type.
  214. *
  215. * @param mimeType - The mime type of interest.
  216. */
  217. removeMimeType(mimeType: string): void {
  218. delete this._factories[mimeType];
  219. delete this._ranks[mimeType];
  220. this._types = null;
  221. }
  222. /**
  223. * Get the rank for a given mime type.
  224. *
  225. * @param mimeType - The mime type of interest.
  226. *
  227. * @returns The rank of the mime type or undefined.
  228. */
  229. getRank(mimeType: string): number | undefined {
  230. let rank = this._ranks[mimeType];
  231. return rank && rank.rank;
  232. }
  233. /**
  234. * Set the rank of a given mime type.
  235. *
  236. * @param mimeType - The mime type of interest.
  237. *
  238. * @param rank - The new rank to assign.
  239. *
  240. * #### Notes
  241. * This is a no-op if the mime type is not registered.
  242. */
  243. setRank(mimeType: string, rank: number): void {
  244. if (!this._ranks[mimeType]) {
  245. return;
  246. }
  247. let id = this._id++;
  248. this._ranks[mimeType] = { rank, id };
  249. this._types = null;
  250. }
  251. private _id = 0;
  252. private _ranks: Private.RankMap = {};
  253. private _types: string[] | null = null;
  254. private _factories: Private.FactoryMap = {};
  255. }
  256. /**
  257. * The namespace for `RenderMimeRegistry` class statics.
  258. */
  259. export namespace RenderMimeRegistry {
  260. /**
  261. * The options used to initialize a rendermime instance.
  262. */
  263. export interface IOptions {
  264. /**
  265. * Initial factories to add to the rendermime instance.
  266. */
  267. initialFactories?: ReadonlyArray<IRenderMime.IRendererFactory>;
  268. /**
  269. * The sanitizer used to sanitize untrusted html inputs.
  270. *
  271. * If not given, a default sanitizer will be used.
  272. */
  273. sanitizer?: IRenderMime.ISanitizer;
  274. /**
  275. * The initial resolver object.
  276. *
  277. * The default is `null`.
  278. */
  279. resolver?: IRenderMime.IResolver;
  280. /**
  281. * An optional path handler.
  282. */
  283. linkHandler?: IRenderMime.ILinkHandler;
  284. /**
  285. * An optional LaTeX typesetter.
  286. */
  287. latexTypesetter?: IRenderMime.ILatexTypesetter;
  288. }
  289. /**
  290. * The options used to clone a rendermime instance.
  291. */
  292. export interface ICloneOptions {
  293. /**
  294. * The new sanitizer used to sanitize untrusted html inputs.
  295. */
  296. sanitizer?: IRenderMime.ISanitizer;
  297. /**
  298. * The new resolver object.
  299. */
  300. resolver?: IRenderMime.IResolver;
  301. /**
  302. * The new path handler.
  303. */
  304. linkHandler?: IRenderMime.ILinkHandler;
  305. /**
  306. * The new LaTeX typesetter.
  307. */
  308. latexTypesetter?: IRenderMime.ILatexTypesetter;
  309. }
  310. /**
  311. * A default resolver that uses a session and a contents manager.
  312. */
  313. export class UrlResolver implements IRenderMime.IResolver {
  314. /**
  315. * Create a new url resolver for a console.
  316. */
  317. constructor(options: IUrlResolverOptions) {
  318. this._session = options.session;
  319. this._contents = options.contents;
  320. }
  321. /**
  322. * Resolve a relative url to a correct server path.
  323. */
  324. resolveUrl(url: string): Promise<string> {
  325. if (this.isLocal(url)) {
  326. let cwd = PathExt.dirname(this._session.path);
  327. url = PathExt.resolve(cwd, url);
  328. }
  329. return Promise.resolve(url);
  330. }
  331. /**
  332. * Get the download url of a given absolute server path.
  333. */
  334. getDownloadUrl(path: string): Promise<string> {
  335. if (this.isLocal(path)) {
  336. return this._contents.getDownloadUrl(path);
  337. }
  338. return Promise.resolve(path);
  339. }
  340. /**
  341. * Whether the URL should be handled by the resolver
  342. * or not.
  343. *
  344. * #### Notes
  345. * This is similar to the `isLocal` check in `URLExt`,
  346. * but it also checks whether the path points to any
  347. * of the `IDrive`s that may be registered with the contents
  348. * manager.
  349. */
  350. isLocal(url: string): boolean {
  351. return URLExt.isLocal(url) || !!this._contents.driveName(url);
  352. }
  353. private _session: Session.ISession | IClientSession;
  354. private _contents: Contents.IManager;
  355. }
  356. /**
  357. * The options used to create a UrlResolver.
  358. */
  359. export interface IUrlResolverOptions {
  360. /**
  361. * The session used by the resolver.
  362. */
  363. session: Session.ISession | IClientSession;
  364. /**
  365. * The contents manager used by the resolver.
  366. */
  367. contents: Contents.IManager;
  368. }
  369. }
  370. /**
  371. * The namespace for the module implementation details.
  372. */
  373. namespace Private {
  374. /**
  375. * A type alias for a mime rank and tie-breaking id.
  376. */
  377. export type RankPair = { readonly id: number; readonly rank: number };
  378. /**
  379. * A type alias for a mapping of mime type -> rank pair.
  380. */
  381. export type RankMap = { [key: string]: RankPair };
  382. /**
  383. * A type alias for a mapping of mime type -> ordered factories.
  384. */
  385. export type FactoryMap = { [key: string]: IRenderMime.IRendererFactory };
  386. /**
  387. * Get the mime types in the map, ordered by rank.
  388. */
  389. export function sortedTypes(map: RankMap): string[] {
  390. return Object.keys(map).sort((a, b) => {
  391. let p1 = map[a];
  392. let p2 = map[b];
  393. if (p1.rank !== p2.rank) {
  394. return p1.rank - p2.rank;
  395. }
  396. return p1.id - p2.id;
  397. });
  398. }
  399. }