1 | /* -----------------------------------------------------------------------------
|
2 | | Copyright (c) Jupyter Development Team.
|
3 | | Distributed under the terms of the Modified BSD License.
|
4 | |----------------------------------------------------------------------------*/
|
5 | import { defaultSanitizer } from '@jupyterlab/apputils';
|
6 | import { PathExt, URLExt } from '@jupyterlab/coreutils';
|
7 | import { nullTranslator } from '@jupyterlab/translation';
|
8 | import { MimeModel } from './mimemodel';
|
9 | /**
|
10 | * An object which manages mime renderer factories.
|
11 | *
|
12 | * This object is used to render mime models using registered mime
|
13 | * renderers, selecting the preferred mime renderer to render the
|
14 | * model into a widget.
|
15 | *
|
16 | * #### Notes
|
17 | * This class is not intended to be subclassed.
|
18 | */
|
19 | export class RenderMimeRegistry {
|
20 | /**
|
21 | * Construct a new rendermime.
|
22 | *
|
23 | * @param options - The options for initializing the instance.
|
24 | */
|
25 | constructor(options = {}) {
|
26 | this._id = 0;
|
27 | this._ranks = {};
|
28 | this._types = null;
|
29 | this._factories = {};
|
30 | // Parse the options.
|
31 | this.translator = options.translator || nullTranslator;
|
32 | this.resolver = options.resolver || null;
|
33 | this.linkHandler = options.linkHandler || null;
|
34 | this.latexTypesetter = options.latexTypesetter || null;
|
35 | this.sanitizer = options.sanitizer || defaultSanitizer;
|
36 | // Add the initial factories.
|
37 | if (options.initialFactories) {
|
38 | for (const factory of options.initialFactories) {
|
39 | this.addFactory(factory);
|
40 | }
|
41 | }
|
42 | }
|
43 | /**
|
44 | * The ordered list of mimeTypes.
|
45 | */
|
46 | get mimeTypes() {
|
47 | return this._types || (this._types = Private.sortedTypes(this._ranks));
|
48 | }
|
49 | /**
|
50 | * Find the preferred mime type for a mime bundle.
|
51 | *
|
52 | * @param bundle - The bundle of mime data.
|
53 | *
|
54 | * @param safe - How to consider safe/unsafe factories. If 'ensure',
|
55 | * it will only consider safe factories. If 'any', any factory will be
|
56 | * considered. If 'prefer', unsafe factories will be considered, but
|
57 | * only after the safe options have been exhausted.
|
58 | *
|
59 | * @returns The preferred mime type from the available factories,
|
60 | * or `undefined` if the mime type cannot be rendered.
|
61 | */
|
62 | preferredMimeType(bundle, safe = 'ensure') {
|
63 | // Try to find a safe factory first, if preferred.
|
64 | if (safe === 'ensure' || safe === 'prefer') {
|
65 | for (const mt of this.mimeTypes) {
|
66 | if (mt in bundle && this._factories[mt].safe) {
|
67 | return mt;
|
68 | }
|
69 | }
|
70 | }
|
71 | if (safe !== 'ensure') {
|
72 | // Otherwise, search for the best factory among all factories.
|
73 | for (const mt of this.mimeTypes) {
|
74 | if (mt in bundle) {
|
75 | return mt;
|
76 | }
|
77 | }
|
78 | }
|
79 | // Otherwise, no matching mime type exists.
|
80 | return undefined;
|
81 | }
|
82 | /**
|
83 | * Create a renderer for a mime type.
|
84 | *
|
85 | * @param mimeType - The mime type of interest.
|
86 | *
|
87 | * @returns A new renderer for the given mime type.
|
88 | *
|
89 | * @throws An error if no factory exists for the mime type.
|
90 | */
|
91 | createRenderer(mimeType) {
|
92 | // Throw an error if no factory exists for the mime type.
|
93 | if (!(mimeType in this._factories)) {
|
94 | throw new Error(`No factory for mime type: '${mimeType}'`);
|
95 | }
|
96 | // Invoke the best factory for the given mime type.
|
97 | return this._factories[mimeType].createRenderer({
|
98 | mimeType,
|
99 | resolver: this.resolver,
|
100 | sanitizer: this.sanitizer,
|
101 | linkHandler: this.linkHandler,
|
102 | latexTypesetter: this.latexTypesetter,
|
103 | translator: this.translator
|
104 | });
|
105 | }
|
106 | /**
|
107 | * Create a new mime model. This is a convenience method.
|
108 | *
|
109 | * @options - The options used to create the model.
|
110 | *
|
111 | * @returns A new mime model.
|
112 | */
|
113 | createModel(options = {}) {
|
114 | return new MimeModel(options);
|
115 | }
|
116 | /**
|
117 | * Create a clone of this rendermime instance.
|
118 | *
|
119 | * @param options - The options for configuring the clone.
|
120 | *
|
121 | * @returns A new independent clone of the rendermime.
|
122 | */
|
123 | clone(options = {}) {
|
124 | // Create the clone.
|
125 | const clone = new RenderMimeRegistry({
|
126 | resolver: options.resolver || this.resolver || undefined,
|
127 | sanitizer: options.sanitizer || this.sanitizer || undefined,
|
128 | linkHandler: options.linkHandler || this.linkHandler || undefined,
|
129 | latexTypesetter: options.latexTypesetter || this.latexTypesetter || undefined,
|
130 | translator: this.translator
|
131 | });
|
132 | // Clone the internal state.
|
133 | clone._factories = Object.assign({}, this._factories);
|
134 | clone._ranks = Object.assign({}, this._ranks);
|
135 | clone._id = this._id;
|
136 | // Return the cloned object.
|
137 | return clone;
|
138 | }
|
139 | /**
|
140 | * Get the renderer factory registered for a mime type.
|
141 | *
|
142 | * @param mimeType - The mime type of interest.
|
143 | *
|
144 | * @returns The factory for the mime type, or `undefined`.
|
145 | */
|
146 | getFactory(mimeType) {
|
147 | return this._factories[mimeType];
|
148 | }
|
149 | /**
|
150 | * Add a renderer factory to the rendermime.
|
151 | *
|
152 | * @param factory - The renderer factory of interest.
|
153 | *
|
154 | * @param rank - The rank of the renderer. A lower rank indicates
|
155 | * a higher priority for rendering. If not given, the rank will
|
156 | * defer to the `defaultRank` of the factory. If no `defaultRank`
|
157 | * is given, it will default to 100.
|
158 | *
|
159 | * #### Notes
|
160 | * The renderer will replace an existing renderer for the given
|
161 | * mimeType.
|
162 | */
|
163 | addFactory(factory, rank) {
|
164 | if (rank === undefined) {
|
165 | rank = factory.defaultRank;
|
166 | if (rank === undefined) {
|
167 | rank = 100;
|
168 | }
|
169 | }
|
170 | for (const mt of factory.mimeTypes) {
|
171 | this._factories[mt] = factory;
|
172 | this._ranks[mt] = { rank, id: this._id++ };
|
173 | }
|
174 | this._types = null;
|
175 | }
|
176 | /**
|
177 | * Remove a mime type.
|
178 | *
|
179 | * @param mimeType - The mime type of interest.
|
180 | */
|
181 | removeMimeType(mimeType) {
|
182 | delete this._factories[mimeType];
|
183 | delete this._ranks[mimeType];
|
184 | this._types = null;
|
185 | }
|
186 | /**
|
187 | * Get the rank for a given mime type.
|
188 | *
|
189 | * @param mimeType - The mime type of interest.
|
190 | *
|
191 | * @returns The rank of the mime type or undefined.
|
192 | */
|
193 | getRank(mimeType) {
|
194 | const rank = this._ranks[mimeType];
|
195 | return rank && rank.rank;
|
196 | }
|
197 | /**
|
198 | * Set the rank of a given mime type.
|
199 | *
|
200 | * @param mimeType - The mime type of interest.
|
201 | *
|
202 | * @param rank - The new rank to assign.
|
203 | *
|
204 | * #### Notes
|
205 | * This is a no-op if the mime type is not registered.
|
206 | */
|
207 | setRank(mimeType, rank) {
|
208 | if (!this._ranks[mimeType]) {
|
209 | return;
|
210 | }
|
211 | const id = this._id++;
|
212 | this._ranks[mimeType] = { rank, id };
|
213 | this._types = null;
|
214 | }
|
215 | }
|
216 | /**
|
217 | * The namespace for `RenderMimeRegistry` class statics.
|
218 | */
|
219 | (function (RenderMimeRegistry) {
|
220 | /**
|
221 | * A default resolver that uses a given reference path and a contents manager.
|
222 | */
|
223 | class UrlResolver {
|
224 | /**
|
225 | * Create a new url resolver.
|
226 | */
|
227 | constructor(options) {
|
228 | if (options.path) {
|
229 | this._path = options.path;
|
230 | }
|
231 | else if (options.session) {
|
232 | this._session = options.session;
|
233 | }
|
234 | else {
|
235 | throw new Error("Either 'path' or 'session' must be given as a constructor option");
|
236 | }
|
237 | this._contents = options.contents;
|
238 | }
|
239 | /**
|
240 | * The path of the object, from which local urls can be derived.
|
241 | */
|
242 | get path() {
|
243 | var _a;
|
244 | return (_a = this._path) !== null && _a !== void 0 ? _a : this._session.path;
|
245 | }
|
246 | set path(value) {
|
247 | this._path = value;
|
248 | }
|
249 | /**
|
250 | * Resolve a relative url to an absolute url path.
|
251 | */
|
252 | async resolveUrl(url) {
|
253 | if (this.isLocal(url)) {
|
254 | const cwd = encodeURI(PathExt.dirname(this.path));
|
255 | url = PathExt.resolve(cwd, url);
|
256 | }
|
257 | return url;
|
258 | }
|
259 | /**
|
260 | * Get the download url of a given absolute url path.
|
261 | *
|
262 | * #### Notes
|
263 | * The returned URL may include a query parameter.
|
264 | */
|
265 | async getDownloadUrl(urlPath) {
|
266 | if (this.isLocal(urlPath)) {
|
267 | // decode url->path before passing to contents api
|
268 | return this._contents.getDownloadUrl(decodeURIComponent(urlPath));
|
269 | }
|
270 | return urlPath;
|
271 | }
|
272 | /**
|
273 | * Whether the URL should be handled by the resolver
|
274 | * or not.
|
275 | *
|
276 | * #### Notes
|
277 | * This is similar to the `isLocal` check in `URLExt`,
|
278 | * but it also checks whether the path points to any
|
279 | * of the `IDrive`s that may be registered with the contents
|
280 | * manager.
|
281 | */
|
282 | isLocal(url) {
|
283 | if (this.isMalformed(url)) {
|
284 | return false;
|
285 | }
|
286 | return URLExt.isLocal(url) || !!this._contents.driveName(decodeURI(url));
|
287 | }
|
288 | /**
|
289 | * Whether the URL can be decoded using `decodeURI`.
|
290 | */
|
291 | isMalformed(url) {
|
292 | try {
|
293 | decodeURI(url);
|
294 | return false;
|
295 | }
|
296 | catch (error) {
|
297 | if (error instanceof URIError) {
|
298 | return true;
|
299 | }
|
300 | throw error;
|
301 | }
|
302 | }
|
303 | }
|
304 | RenderMimeRegistry.UrlResolver = UrlResolver;
|
305 | })(RenderMimeRegistry || (RenderMimeRegistry = {}));
|
306 | /**
|
307 | * The namespace for the module implementation details.
|
308 | */
|
309 | var Private;
|
310 | (function (Private) {
|
311 | /**
|
312 | * Get the mime types in the map, ordered by rank.
|
313 | */
|
314 | function sortedTypes(map) {
|
315 | return Object.keys(map).sort((a, b) => {
|
316 | const p1 = map[a];
|
317 | const p2 = map[b];
|
318 | if (p1.rank !== p2.rank) {
|
319 | return p1.rank - p2.rank;
|
320 | }
|
321 | return p1.id - p2.id;
|
322 | });
|
323 | }
|
324 | Private.sortedTypes = sortedTypes;
|
325 | function sessionConnection(s) {
|
326 | return s.sessionChanged
|
327 | ? s.session
|
328 | : s;
|
329 | }
|
330 | Private.sessionConnection = sessionConnection;
|
331 | })(Private || (Private = {}));
|
332 | //# sourceMappingURL=registry.js.map |
\ | No newline at end of file |