pinstripe 0.23.0 → 0.24.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/lib/app.js +35 -0
  2. package/lib/apps/_file_importer.js +1 -0
  3. package/lib/apps/docs.js +6 -0
  4. package/lib/apps/main.js +6 -0
  5. package/lib/command.js +3 -1
  6. package/lib/commands/generate_app.js +41 -0
  7. package/lib/commands/generate_component.js +22 -8
  8. package/lib/commands/list_apps.js +15 -0
  9. package/lib/commands/list_views.js +25 -2
  10. package/lib/commands/start_server.js +26 -4
  11. package/lib/component.js +48 -28
  12. package/lib/components/a.js +12 -6
  13. package/lib/components/document.js +17 -8
  14. package/lib/components/helpers.js +11 -14
  15. package/lib/components/pinstripe_frame.js +23 -10
  16. package/lib/components/pinstripe_markdown_editor.js +3 -4
  17. package/lib/components/pinstripe_modal.js +77 -0
  18. package/lib/components/pinstripe_overlay.js +9 -14
  19. package/lib/components/pinstripe_skeleton.js +55 -0
  20. package/lib/components/script.js +11 -0
  21. package/lib/database/client.js +37 -6
  22. package/lib/database/constants.js +1 -1
  23. package/lib/database/row.js +3 -5
  24. package/lib/index.js +1 -0
  25. package/lib/lru_cache.js +53 -0
  26. package/lib/lru_cache.test.js +45 -0
  27. package/lib/markdown.js +58 -0
  28. package/lib/project.js +8 -7
  29. package/lib/registry.js +7 -10
  30. package/lib/services/app.js +11 -0
  31. package/lib/services/client_builder.js +2 -0
  32. package/lib/services/config.js +7 -4
  33. package/lib/services/fetch.js +1 -2
  34. package/lib/services/render_form.js +58 -54
  35. package/lib/services/render_markdown.js +2 -36
  36. package/lib/services/render_view.js +1 -3
  37. package/lib/services/send_mail.js +1 -0
  38. package/lib/services/server.js +41 -26
  39. package/lib/services/view.js +6 -0
  40. package/lib/view.js +39 -3
  41. package/lib/views/{assets → main/assets}/stylesheets/all.css.js +1 -1
  42. package/package.json +4 -4
  43. package/lib/extensions/multi-app/index.js +0 -4
  44. package/lib/extensions/multi-app/views/_file_importer.js +0 -2
  45. package/lib/extensions/multi-app/views/apps/default.js +0 -6
  46. package/lib/extensions/multi-app/views/apps/guard.js +0 -7
  47. package/lib/extensions/multi-app/views/guard.js +0 -21
  48. package/lib/services/view_names.js +0 -8
  49. package/lib/view_file_importers/ejs.js +0 -82
  50. package/lib/views/assets/stylesheets/components/modal.css +0 -62
  51. /package/lib/views/{assets → main/assets}/javascripts/all.js.js +0 -0
  52. /package/lib/views/{assets → main/assets}/javascripts/all.js.map.js +0 -0
  53. /package/lib/views/{assets → main/assets}/stylesheets/components/button.css +0 -0
  54. /package/lib/views/{assets → main/assets}/stylesheets/components/card.css +0 -0
  55. /package/lib/views/{assets → main/assets}/stylesheets/components/form.css +0 -0
  56. /package/lib/views/{assets → main/assets}/stylesheets/components/frame.css +0 -0
  57. /package/lib/views/{assets → main/assets}/stylesheets/components/input.css +0 -0
  58. /package/lib/views/{assets → main/assets}/stylesheets/components/label.css +0 -0
  59. /package/lib/views/{assets → main/assets}/stylesheets/components/overlay.css +0 -0
  60. /package/lib/views/{assets → main/assets}/stylesheets/components/pagination.css +0 -0
  61. /package/lib/views/{assets → main/assets}/stylesheets/components/progress_bar.css +0 -0
  62. /package/lib/views/{assets → main/assets}/stylesheets/components/textarea.css +0 -0
  63. /package/lib/views/{assets → main/assets}/stylesheets/global.css +0 -0
  64. /package/lib/views/{assets → main/assets}/stylesheets/reset.css +0 -0
  65. /package/lib/views/{assets → main/assets}/stylesheets/vars.css +0 -0
package/lib/app.js ADDED
@@ -0,0 +1,35 @@
1
+
2
+ import { Class } from './class.js';
3
+ import { Registry } from './registry.js';
4
+ import { View } from './view.js';
5
+ import { ServiceConsumer } from './service_consumer.js';
6
+
7
+ export const App = Class.extend().include({
8
+ meta(){
9
+ this.include(Registry);
10
+ this.include(ServiceConsumer);
11
+ },
12
+
13
+ compose(){
14
+ return [];
15
+ },
16
+
17
+ get viewMapper(){
18
+ if(!this._viewMapper){
19
+ this._viewMapper = View.mapperFor(this.compose());
20
+ }
21
+ return this._viewMapper;
22
+ },
23
+
24
+ renderView(...args){
25
+ return this.viewMapper.renderView(this.context, ...args);
26
+ },
27
+
28
+ isView(...args){
29
+ return this.viewMapper.isView(...args);
30
+ },
31
+
32
+ get viewNames(){
33
+ return this.viewMapper.viewNames;
34
+ }
35
+ });
@@ -0,0 +1 @@
1
+ export { App as default } from 'pinstripe';
@@ -0,0 +1,6 @@
1
+
2
+ export default {
3
+ compose(){
4
+ return ['shared', 'docs'];
5
+ }
6
+ };
@@ -0,0 +1,6 @@
1
+
2
+ export default {
3
+ compose(){
4
+ return ['shared', 'main'];
5
+ }
6
+ };
package/lib/command.js CHANGED
@@ -10,7 +10,9 @@ export const Command = Class.extend().include({
10
10
  this.include(ServiceConsumer);
11
11
 
12
12
  this.assignProps({
13
- normalizeName: (...args) => inflector.dasherize(...args),
13
+ normalizeName(name){
14
+ return inflector.dasherize(name);
15
+ },
14
16
 
15
17
  get schedules(){
16
18
  if(!this.hasOwnProperty('_schedules')){
@@ -0,0 +1,41 @@
1
+
2
+
3
+ export default {
4
+ async run(){
5
+ const [ name = '' ] = this.args;
6
+ const normalizedName = this.inflector.snakeify(name);
7
+ if(normalizedName == ''){
8
+ console.error('An app name must be given.');
9
+ process.exit();
10
+ }
11
+
12
+ const { inProjectRootDir, generateFile, line, indent } = this.fsBuilder;
13
+
14
+ await inProjectRootDir(async () => {
15
+
16
+ await generateFile(`lib/apps/_file_importer.js`, { skipIfExists: true }, () => {
17
+ line();
18
+ line(`export { App as default } from 'pinstripe';`);
19
+ line();
20
+ });
21
+
22
+ await generateFile(`lib/apps/${normalizedName}.js`, () => {
23
+ line();
24
+ line(`export default {`);
25
+ indent(() => {
26
+ line('compose(){');
27
+ indent(() => {
28
+ line(`return ['shared', '${this.inflector.dasherize(normalizedName)}'];`);
29
+ });
30
+ line('}');
31
+ });
32
+ line('};');
33
+ line();
34
+ });
35
+
36
+ });
37
+
38
+ await this.runCommand('generate-view', `${normalizedName}/index`);
39
+ }
40
+ }
41
+
@@ -1,10 +1,10 @@
1
1
 
2
2
  export default {
3
3
  async run(){
4
- const [ name = '' ] = this.args;
5
- const normalizedName = this.inflector.snakeify(name);
6
- if(normalizedName == ''){
7
- console.error('A node wrapper name must be given.');
4
+ const { extractArg } = this.cliUtils;
5
+ const name = this.inflector.snakeify(extractArg(''));
6
+ if(name == ''){
7
+ console.error('A component name must be given.');
8
8
  process.exit();
9
9
  }
10
10
 
@@ -17,21 +17,35 @@ export default {
17
17
  line(`export { Component as default } from 'pinstripe';`);
18
18
  line();
19
19
  });
20
-
21
- await generateFile(`lib/components/${normalizedName}.js`, () => {
20
+
21
+ await generateFile(`lib/components/${name}.js`, () => {
22
22
  line();
23
23
  line(`export default {`);
24
24
  indent(() => {
25
- line(`initialize(){`);
25
+ line(`initialize(...args){`);
26
26
  indent(() => {
27
27
  line(`this.constructor.parent.prototype.initialize.call(this, ...args);`);
28
+ line();
29
+ line('this.shadow.patch(`');
30
+ indent(() => {
31
+ line(`<style>`);
32
+ indent(() => {
33
+ line(`.root {`);
34
+ indent(() => {
35
+ line(`background: yellow;`)
36
+ })
37
+ line(`}`);
38
+ });
39
+ line(`</style>`);
40
+ line(`<div class="root"><slot></div>`);
41
+ });
42
+ line('`);');
28
43
  });
29
44
  line(`}`);
30
45
  });
31
46
  line('};');
32
47
  line();
33
48
  });
34
-
35
49
  });
36
50
  }
37
51
  }
@@ -0,0 +1,15 @@
1
+
2
+ import chalk from 'chalk';
3
+ import { App } from 'pinstripe';
4
+
5
+ export default {
6
+ run(){
7
+ console.log('');
8
+ console.log('The following apps are available:');
9
+ console.log('');
10
+ App.names.forEach(appName => {
11
+ console.log(` * ${chalk.green(appName)} (composed of ${JSON.stringify(App.create(appName, this.context).compose())} views)`);
12
+ });
13
+ console.log('');
14
+ }
15
+ };
@@ -1,11 +1,34 @@
1
1
 
2
2
  import chalk from 'chalk';
3
- import { View } from 'pinstripe';
3
+ import { App, View } from 'pinstripe';
4
4
 
5
5
  export default {
6
6
  run(){
7
+ const { extractOptions } = this.cliUtils;
8
+
9
+ const { app } = extractOptions();
10
+
11
+ if(app){
12
+ this.listComposedViews(typeof app == 'string' ? app : 'main');
13
+ } else {
14
+ this.listAllViews();
15
+ }
16
+ },
17
+
18
+ listComposedViews(appName){
19
+ const { viewNames, resolveView } = View.mapperFor(App.create(appName, this.context).compose());
20
+ console.log('');
21
+ console.log(`The following views have been composed for app "${appName}":`);
22
+ console.log('');
23
+ viewNames.forEach(viewName => {
24
+ console.log(` * ${chalk.green(viewName)} -> ${chalk.green(resolveView(viewName))}`);
25
+ });
26
+ console.log('');
27
+ },
28
+
29
+ listAllViews(){
7
30
  console.log('');
8
- console.log('The following views are available:');
31
+ console.log(`The following views are available:`);
9
32
  console.log('');
10
33
  View.names.forEach(viewName => {
11
34
  console.log(` * ${chalk.green(viewName)}`);
@@ -1,9 +1,31 @@
1
1
 
2
2
  export default {
3
3
  run(){
4
- this.server.start();
5
-
6
- //TODO: be able to pass a --without-bot option
7
- this.bot.start();
4
+ const { extractOptions } = this.cliUtils;
5
+
6
+ const { app, withoutBot } = extractOptions({
7
+ app: `main:${process.env.HOST || '127.0.0.1'}:${parseInt(process.env.PORT || '3000')}`,
8
+ withoutBot: false
9
+ });
10
+
11
+
12
+ const apps = [];
13
+ let currentPort = 3000;
14
+
15
+ app.trim().split(/\s+/).forEach((app) => {
16
+ const [name, ...serverConfig] = app.split(/:/);
17
+ const [ port, host = '127.0.0.1'] = serverConfig.reverse();
18
+
19
+ if(port){
20
+ apps.push({ name, port: parseInt(port), host });
21
+ } else {
22
+ while(apps.filter(app => app.host == host).map(({ port }) => port).includes(currentPort)) currentPort++;
23
+ apps.push({ name, port: currentPort, host });
24
+ }
25
+ });
26
+
27
+ this.server.start(apps);
28
+
29
+ // if(!withoutBot) this.bot.start();
8
30
  }
9
31
  };
package/lib/component.js CHANGED
@@ -13,8 +13,6 @@ export const Component = Class.extend().include({
13
13
  meta(){
14
14
  this.include(Registry);
15
15
 
16
- const { importFile } = this;
17
-
18
16
  this.assignProps({
19
17
  instanceFor(node){
20
18
  if(!node._component){
@@ -30,24 +28,32 @@ export const Component = Class.extend().include({
30
28
 
31
29
  normalizeName(name){
32
30
  return Inflector.instance.dasherize(name);
33
- },
34
-
35
- async importFile(params){
36
- const { filePath, relativeFilePathWithoutExtension } = params;
37
- if((await import(filePath)).default){
38
- Client.instance.addModule(`
39
- import { Component } from ${JSON.stringify(fileURLToPath(`${import.meta.url}/../index.js`))};
40
- import include from ${JSON.stringify(filePath)};
41
- Component.register(${JSON.stringify(relativeFilePathWithoutExtension)}, include);
42
- `);
43
- } else {
44
- Client.instance.addModule(`
45
- import ${JSON.stringify(filePath)};
46
- `);
47
- }
48
- return importFile.call(this, params);
49
31
  }
50
32
  });
33
+
34
+ this.FileImporter.register('js', {
35
+ meta(){
36
+ const { importFile } = this.prototype;
37
+
38
+ this.include({
39
+ async importFile(params){
40
+ const { filePath, relativeFilePathWithoutExtension } = params;
41
+ if((await import(filePath)).default){
42
+ Client.instance.addModule(`
43
+ import { Component } from ${JSON.stringify(fileURLToPath(`${import.meta.url}/../index.js`))};
44
+ import include from ${JSON.stringify(filePath)};
45
+ Component.register(${JSON.stringify(relativeFilePathWithoutExtension)}, include);
46
+ `);
47
+ } else {
48
+ Client.instance.addModule(`
49
+ import ${JSON.stringify(filePath)};
50
+ `);
51
+ }
52
+ return importFile.call(this, params);
53
+ }
54
+ })
55
+ }
56
+ })
51
57
  },
52
58
 
53
59
  initialize(node, skipInit = false){
@@ -70,8 +76,9 @@ export const Component = Class.extend().include({
70
76
  this.setTimeout(() => this.node.focus());
71
77
  }
72
78
 
73
- const { autosubmit, trigger } = this.data;
79
+ const { autosubmit, trigger, decorate } = this.data;
74
80
 
81
+ // replace with pinstripe-autosubmitter?
75
82
  if(autosubmit){
76
83
  let hash = JSON.stringify(this.values);
77
84
  this.setInterval(() => {
@@ -83,6 +90,7 @@ export const Component = Class.extend().include({
83
90
  }, 100);
84
91
  }
85
92
 
93
+ // replace with script?
86
94
  if(trigger){
87
95
  this.setTimeout(() => {
88
96
  this.trigger(trigger);
@@ -148,6 +156,10 @@ export const Component = Class.extend().include({
148
156
  return out;
149
157
  },
150
158
 
159
+ get parentsIncludingThis(){
160
+ return [this, ...this.parents];
161
+ },
162
+
151
163
  get children(){
152
164
  return [...this.node.childNodes].map(
153
165
  node => this.constructor.instanceFor(node)
@@ -267,7 +279,7 @@ export const Component = Class.extend().include({
267
279
  },
268
280
 
269
281
  get document(){
270
- return this.parents.find(({ isDocument }) => isDocument) || this;
282
+ return this.find('parentsIncludingThis', ({ isDocument }) => isDocument);
271
283
  },
272
284
 
273
285
  get overlay(){
@@ -277,6 +289,7 @@ export const Component = Class.extend().include({
277
289
  get shadow(){
278
290
  if(!this.node.shadowRoot){
279
291
  this.node.attachShadow({ mode: 'open' });
292
+ this.shadow.observe({ add: true }, component => component.descendants);
280
293
  this.shadow.patch(`<slot>`);
281
294
  }
282
295
  return Component.instanceFor(this.node.shadowRoot);
@@ -351,8 +364,8 @@ export const Component = Class.extend().include({
351
364
 
352
365
  remove(){
353
366
  if(this.realParent){
354
- clearTimers.call(this);
355
- if(this.realParent) this.realParent.node.removeChild(this.node);
367
+ clean.call(this);
368
+ this.realParent.node.removeChild(this.node);
356
369
  }
357
370
  return this;
358
371
  },
@@ -440,20 +453,25 @@ export const Component = Class.extend().include({
440
453
  },
441
454
 
442
455
  async fetch(url, options = {}){
456
+ const { minimumDelay = 0, ...otherOptions } = options;
443
457
  const { progressBar } = this.document;
444
458
  const frame = this.frame || this;
445
459
  const normalizedUrl = new URL(url, frame.url);
446
460
  const abortController = new AbortController();
447
461
  this._registeredAbortControllers.push(abortController);
448
462
  progressBar.start();
463
+ let minimumDelayTimeout;
449
464
  const cleanUp = () => {
465
+ clearTimeout(minimumDelayTimeout);
450
466
  this._registeredAbortControllers = this._registeredAbortControllers.filter(item => item !== abortController);
451
467
  progressBar.stop();
452
468
  };
453
469
  try {
454
- const out = await fetch(normalizedUrl, Object.assign({
455
- signal: abortController.signal
456
- }, options));
470
+ const promises = [
471
+ fetch(normalizedUrl, { signal: abortController.signal, ...otherOptions }),
472
+ new Promise(resolve => minimumDelayTimeout = setTimeout(resolve, minimumDelay))
473
+ ];
474
+ const [ out ] = await Promise.all(promises);
457
475
  cleanUp();
458
476
  return out;
459
477
  } catch(e){
@@ -480,7 +498,6 @@ export const Component = Class.extend().include({
480
498
  const [ collection, selector ] = args;
481
499
  return this[collection].filter(item => item.is(selector));
482
500
  }
483
-
484
501
  });
485
502
 
486
503
  const matchesSelector = (() => {
@@ -497,6 +514,8 @@ function cleanChildren(){
497
514
  }
498
515
 
499
516
  function clean(){
517
+ this.trigger('clean', { bubbles: false });
518
+
500
519
  [...this.node.childNodes].forEach(node => node._component && clean.call(node._component));
501
520
 
502
521
  while(this._registeredEventListeners.length){
@@ -572,11 +591,12 @@ function patchAttributes(attributes){
572
591
  const currentAttributes = this.attributes;
573
592
  Object.keys(currentAttributes).forEach((key) => {
574
593
  if(attributes[key] === undefined){
575
- this.node.removeAttribute(key);
594
+ Element.prototype.removeAttribute.call(this.node, key); // work around for https://github.com/cypress-io/cypress/issues/26206
595
+ // this.node.removeAttribute(key);
576
596
  }
577
597
  })
578
598
  Object.keys(attributes).forEach((key) => {
579
- if(currentAttributes[key] != attributes[key]){
599
+ if(!currentAttributes.hasOwnProperty(key) || currentAttributes[key] != attributes[key]){
580
600
  this.node.setAttribute(key, attributes[key]);
581
601
  if(key == 'value'){
582
602
  this.node.value = attributes[key];
@@ -1,5 +1,5 @@
1
1
 
2
- import { loadFrame, removeFrame } from "./helpers.js";
2
+ import { loadFrame, getFrame } from "./helpers.js";
3
3
 
4
4
  export default {
5
5
  initialize(...args){
@@ -8,14 +8,20 @@ export default {
8
8
  const { ignoreEventsFromChildren = false } = this.data;
9
9
  this.on('click', (event) => {
10
10
  if(ignoreEventsFromChildren && event.target != this) return;
11
- const { action = 'load', confirm, target = '_self', method = 'GET', href } = { ...this.attributes, ...this.data };
11
+ const { confirm, target = '_self', method = 'GET', href, placeholder } = { ...this.attributes, ...this.data };
12
12
  if(new URL(href, window.location.href).host != window.location.host) return;
13
13
  event.preventDefault();
14
14
  event.stopPropagation();
15
- if(action == 'load') loadFrame.call(this, confirm, target, method, href);
16
- if(action == 'remove') removeFrame.call(this, confirm, target);
15
+ loadFrame.call(this, confirm, target, method, href, placeholder);
17
16
  });
18
-
17
+
18
+ const { target = '_self', method = 'GET', href, preload, placeholder } = { ...this.attributes, ...this.data };
19
+ if(method == 'GET' && target != '_blank'){
20
+ const frame = target == '_overlay' ? this.frame : getFrame.call(this, target);
21
+ if(preload != undefined) this.document.preload(new URL(href, frame.url));
22
+ if(placeholder != undefined) this.document.preload(new URL(placeholder, frame.url));
23
+ }
24
+
19
25
  if(this.is('input, textarea')) this.on('keyup', (event) => this.trigger('click'));
20
26
  }
21
- };
27
+ };
@@ -1,4 +1,8 @@
1
1
 
2
+ import { loadCache } from "./helpers.js";
3
+
4
+ const preloading = {};
5
+
2
6
  export default {
3
7
  meta(){
4
8
  this.include('pinstripe-frame');
@@ -25,8 +29,8 @@ export default {
25
29
  return this.body.progressBar;
26
30
  },
27
31
 
28
- async load(url, options = {}){
29
- const { replace, method = 'GET', headers = {}, ...otherOptions } = options;
32
+ async load(url = this.url.toString(), options = {}){
33
+ const { replace, method = 'GET' } = options;
30
34
  const previousUrl = this.url.toString();
31
35
  const normalizedUrl = new URL(url, previousUrl).toString();
32
36
 
@@ -39,11 +43,16 @@ export default {
39
43
  }
40
44
  }
41
45
 
42
- return this.constructor.for('pinstripe-frame').prototype.load.call(this, url, Object.assign({
43
- method,
44
- headers: Object.assign({
45
- 'x-pinstripe-frame-type': 'document'
46
- }, headers)
47
- }, otherOptions));
46
+ return this.constructor.for('pinstripe-frame').prototype.load.call(this, url, options);
47
+ },
48
+
49
+ async preload(url){
50
+ if(loadCache.get(url.toString())) return;
51
+ if(preloading[url.toString()]) return;
52
+ preloading[url.toString()] = true;
53
+ const response = await fetch(url);
54
+ const html = await response.text();
55
+ loadCache.put(url.toString(), html);
56
+ delete preloading[url.toString()];
48
57
  }
49
58
  };
@@ -1,5 +1,9 @@
1
1
 
2
- export function loadFrame(confirm, target, method, url){
2
+ import { LruCache } from '../lru_cache.js';
3
+
4
+ export const loadCache = LruCache.new();
5
+
6
+ export function loadFrame(confirm, target, method, url, placeholderUrl){
3
7
  if(confirm && !window.confirm(confirm)){
4
8
  return;
5
9
  }
@@ -21,31 +25,24 @@ export function loadFrame(confirm, target, method, url){
21
25
  return;
22
26
  }
23
27
 
28
+ if(placeholderUrl) placeholderUrl = new URL(placeholderUrl, this.frame.url);
29
+
24
30
  if(method.match(/POST|PUT|PATCH/i)){
25
31
  const formData = new FormData();
26
32
  const values = this.values;
27
33
  Object.keys(values).forEach((name) => formData.append(name, values[name]));
28
- frame.load(url, { method, body: formData });
34
+ frame.load(url, { method, body: formData, placeholderUrl });
29
35
  } else {
30
- frame.load(url, { method });
36
+ frame.load(url, { method, placeholderUrl });
31
37
  }
32
38
  }
33
39
 
34
- export function removeFrame(confirm, target){
35
- if(confirm && !window.confirm(confirm)){
36
- return;
37
- }
38
-
39
- const frame = getFrame.call(this, target);
40
- if(frame) frame.remove();
41
- }
42
-
43
- function getFrame(target){
40
+ export function getFrame(target){
44
41
  if(target == '_self') return this.frame;
45
42
  if(target == '_top') return this.document;
46
43
  if(target.match(/^_parent/)){
47
44
  const index = target.split(/_/).length - 1;
48
45
  return this.parents.filter(n => n.isFrame)[index];
49
46
  }
50
- return this.frame.decendants.find(n => n.isFrame && n.data.name == target);
47
+ return this.frame.descendants.find(n => n.isFrame && n.data.name == target);
51
48
  }
@@ -1,4 +1,6 @@
1
1
 
2
+ import { loadCache } from "./helpers.js";
3
+
2
4
  export default {
3
5
  initialize(...args){
4
6
  this.constructor.parent.prototype.initialize.call(this, ...args);
@@ -30,18 +32,29 @@ export default {
30
32
  );
31
33
  },
32
34
 
35
+ loading: false,
36
+
33
37
  async load(url = this.url, options = {}){
38
+ if(this.loading) return;
39
+ this.loading = true;
34
40
  this.abort();
35
-
41
+ const { method = 'GET', placeholderUrl } = options;
42
+ const cachedHtml = method == 'GET' ? loadCache.get(url.toString()) : undefined;
43
+ const out = cachedHtml ? this.patch(cachedHtml) : undefined;
44
+ let minimumDelay = 0;
45
+ if(!cachedHtml && placeholderUrl){
46
+ const placeholderHtml = loadCache.get(placeholderUrl.toString());
47
+ if(placeholderHtml) {
48
+ this.patch(placeholderHtml);
49
+ minimumDelay = 300;
50
+ }
51
+ }
36
52
  this.url = url;
37
-
38
- const { headers = {}, ...otherOptions } = options;
39
- const response = await this.fetch(url, Object.assign({
40
- headers: Object.assign({
41
- 'x-pinstripe-frame-type': 'basic'
42
- }, headers)
43
- }, otherOptions));
44
-
45
- return this.patch(await response.text());
53
+ const response = await this.fetch(url, { minimumDelay, ...options });
54
+ const html = await response.text();
55
+ this.loading = false;
56
+ if(html == cachedHtml) return out;
57
+ if(method == 'GET') loadCache.put(url.toString(), html);
58
+ this.patch(html);
46
59
  }
47
60
  };
@@ -1,5 +1,6 @@
1
1
 
2
- import { Component } from '../component.js'
2
+ import { Component } from '../component.js';
3
+ import { Markdown } from '../markdown.js';
3
4
 
4
5
  export default {
5
6
  initialize(...args){
@@ -24,9 +25,7 @@ export default {
24
25
 
25
26
  this.on('submit', () => {
26
27
  const { value } = this.values;
27
- const formData = new FormData();
28
- formData.append('value', value);
29
- previewFrame.load(previewFrame.url, { method: 'POST', body: formData });
28
+ previewFrame.patch(Markdown.render(value).toString());
30
29
  anchorTextarea.value = value;
31
30
  });
32
31
  },