@iebh/tera-fy 1.0.9 → 1.0.11

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 (37) hide show
  1. package/README.md +9 -4
  2. package/api.md +445 -431
  3. package/dist/terafy.js +2 -2
  4. package/dist/terafy.js.map +4 -4
  5. package/docs/assets/anchor.js +350 -0
  6. package/docs/assets/bass-addons.css +12 -0
  7. package/docs/assets/bass.css +544 -0
  8. package/docs/assets/fonts/EOT/SourceCodePro-Bold.eot +0 -0
  9. package/docs/assets/fonts/EOT/SourceCodePro-Regular.eot +0 -0
  10. package/docs/assets/fonts/LICENSE.txt +93 -0
  11. package/docs/assets/fonts/OTF/SourceCodePro-Bold.otf +0 -0
  12. package/docs/assets/fonts/OTF/SourceCodePro-Regular.otf +0 -0
  13. package/docs/assets/fonts/TTF/SourceCodePro-Bold.ttf +0 -0
  14. package/docs/assets/fonts/TTF/SourceCodePro-Regular.ttf +0 -0
  15. package/docs/assets/fonts/WOFF/OTF/SourceCodePro-Bold.otf.woff +0 -0
  16. package/docs/assets/fonts/WOFF/OTF/SourceCodePro-Regular.otf.woff +0 -0
  17. package/docs/assets/fonts/WOFF/TTF/SourceCodePro-Bold.ttf.woff +0 -0
  18. package/docs/assets/fonts/WOFF/TTF/SourceCodePro-Regular.ttf.woff +0 -0
  19. package/docs/assets/fonts/WOFF2/OTF/SourceCodePro-Bold.otf.woff2 +0 -0
  20. package/docs/assets/fonts/WOFF2/OTF/SourceCodePro-Regular.otf.woff2 +0 -0
  21. package/docs/assets/fonts/WOFF2/TTF/SourceCodePro-Bold.ttf.woff2 +0 -0
  22. package/docs/assets/fonts/WOFF2/TTF/SourceCodePro-Regular.ttf.woff2 +0 -0
  23. package/docs/assets/fonts/source-code-pro.css +23 -0
  24. package/docs/assets/github.css +123 -0
  25. package/docs/assets/site.js +168 -0
  26. package/docs/assets/split.css +15 -0
  27. package/docs/assets/split.js +782 -0
  28. package/docs/assets/style.css +147 -0
  29. package/docs/index.html +3636 -0
  30. package/{index.html → docs/playground.html} +48 -12
  31. package/documentation.yml +12 -0
  32. package/lib/terafy.client.js +294 -8
  33. package/lib/terafy.server.js +229 -13
  34. package/package.json +13 -7
  35. package/plugins/vue2.js +185 -0
  36. package/plugins/{vue.js → vue3.js} +35 -13
  37. package/utils/mixin.js +18 -0
@@ -20,7 +20,7 @@
20
20
  </style>
21
21
 
22
22
  <script type="module">
23
- import TeraFy from './dist/terafy.js';
23
+ import TeraFy from 'https://esm.run/@iebh/tera-fy';
24
24
 
25
25
  Vue.createApp({
26
26
  data() { return {
@@ -97,6 +97,32 @@
97
97
  </head>
98
98
  <body>
99
99
  <div id="app">
100
+ <!-- Nav header {{{ -->
101
+ <nav class="navbar navbar-expand-lg navbar-light bg-light px-4">
102
+ <a href="https://github.com/IEBH/TERA-fy" class="navbar-brand">TERA-fy</a>
103
+ <button type="button" data-toggle="collapse" data-target="#navbarAreas" class="navbar-toggler">
104
+ <span class="navbar-toggler-icon"></span>
105
+ </button>
106
+ <div id="navbarAreas" class="collapse navbar-collapse">
107
+ <ul class="navbar-nav">
108
+ <li class="navbar-item me-1">
109
+ <a href="https://iebh.github.io/TERA-fy" class="btn btn-light">API docs</a>
110
+ </li>
111
+ <li class="navbar-item mr-1">
112
+ <a href="https://iebh.github.io/TERA-fy/playground.html" class="btn btn-primary text-white">Playground</a>
113
+ </li>
114
+ </ul>
115
+ </div>
116
+ <ul class="navbar-nav">
117
+ <li class="navbar-item">
118
+ <a href="https://github.com/IEBH/TERA-fy" class="btn btn-light">
119
+ GitHub
120
+ </a>
121
+ </li>
122
+ </ul>
123
+ </nav>
124
+ <!-- }}} -->
125
+
100
126
  <div class="container pt-4">
101
127
  <div class="row">
102
128
  <div class="col-sm-12 col-md-6">
@@ -161,12 +187,6 @@
161
187
  <div class="card-header">Projects</div>
162
188
  <div class="card-body">
163
189
  <div class="list-group">
164
- <a
165
- @click="run('bindProject')"
166
- class="list-group-item list-group-item-action disabled"
167
- >
168
- terafy.bindProject()
169
- </a>
170
190
  <a
171
191
  @click="run('getProject')"
172
192
  class="list-group-item list-group-item-action"
@@ -186,7 +206,7 @@
186
206
  >
187
207
  <div>terafy.setActiveProject(</div>
188
208
  <select
189
- v-if="projects"
209
+ v-if="projects && projects.length > 0"
190
210
  v-model="project"
191
211
  class="form-control"
192
212
  placeholder="Select project..."
@@ -231,16 +251,32 @@
231
251
  terafy.getProjectState()
232
252
  </a>
233
253
  <a
234
- @click="run('bindProjectState')"
254
+ @click="run('applyProjectStatePatch')"
235
255
  class="list-group-item list-group-item-action disabled"
236
256
  >
237
- terafy.bindProjectState()
257
+ terafy.applyProjectStatePatch()
238
258
  </a>
239
259
  <a
240
- @click="run('applyProjectStatePatch')"
260
+ @click="run('subscribeProjectState')"
241
261
  class="list-group-item list-group-item-action disabled"
242
262
  >
243
- terafy.applyProjectStatePatch()
263
+ terafy.subscribeProjectState()
264
+ </a>
265
+ </div>
266
+ </div>
267
+ </div>
268
+ <!-- }}} -->
269
+
270
+ <!-- Project Files {{{ -->
271
+ <div class="card mb-2">
272
+ <div class="card-header">Project Files</div>
273
+ <div class="card-body">
274
+ <div class="list-group">
275
+ <a
276
+ @click="run('getProjectFiles')"
277
+ class="list-group-item list-group-item-action"
278
+ >
279
+ terafy.getProjectFiles()
244
280
  </a>
245
281
  </div>
246
282
  </div>
@@ -0,0 +1,12 @@
1
+ toc:
2
+ - name: TeraFy Playground
3
+ description: |
4
+ See [the TeraFy Playground](./playground.html) to expriment with the various TeraFy API calls.
5
+ - name: Data entities
6
+ children:
7
+ - User
8
+ - Project
9
+ - ProjectFile
10
+ - name: TeraFy
11
+ description: |
12
+ API reference for all methods exposed by the TeraFy client library.
@@ -1,4 +1,6 @@
1
+ import {diff, jsonPatchPathConverter as jsPatchConverter} from 'just-diff';
1
2
  import {cloneDeep} from 'lodash-es';
3
+ import Mitt from 'mitt';
2
4
  import {nanoid} from 'nanoid';
3
5
 
4
6
  /* globals globalThis */
@@ -29,6 +31,13 @@ export default class TeraFy {
29
31
  };
30
32
 
31
33
 
34
+ /**
35
+ * Event emitter subscription endpoint
36
+ * @type {Mitt}
37
+ */
38
+ events = Mitt();
39
+
40
+
32
41
  /**
33
42
  * DOMElements for this TeraFy instance
34
43
  *
@@ -60,9 +69,12 @@ export default class TeraFy {
60
69
  // Projects
61
70
  'bindProject', 'getProject', 'getProjects', 'setActiveProject', 'requireProject', 'selectProject',
62
71
 
63
- // Project state
64
- 'getProjectState', 'applyProjectStatePatch',
65
- // bindProjectState() - See below
72
+ // Project State
73
+ 'getProjectState', 'setProjectState', 'saveProjectState', 'replaceProjectState',
74
+
75
+ // Project State Patching + Subscribing
76
+ 'applyProjectStatePatch', 'subscribeProjectState',
77
+ // bindProjectState() - See individual plugins
66
78
 
67
79
  // Project files
68
80
  'getProjectFiles',
@@ -189,6 +201,13 @@ export default class TeraFy {
189
201
  response: e.toString(),
190
202
  });
191
203
  })
204
+ } else if (message?.action == 'event') {
205
+ return Promise.resolve()
206
+ .then(()=> this.events.emit(message.event, ...message.payload))
207
+ .catch(e => {
208
+ console.warn(`TERA-FY client threw while handling emitted event "${message.event}"`, {message});
209
+ throw e;
210
+ })
192
211
  } else if (message?.id) {
193
212
  this.debug(`Ignoring message ID ${message.id} - was meant for someone else?`);
194
213
  } else {
@@ -204,6 +223,39 @@ export default class TeraFy {
204
223
 
205
224
  // }}}
206
225
 
226
+ // Project state - createProjectStatePatch(), applyProjectStatePatchLocal() {{{
227
+ /**
228
+ * Create + transmit a new project state patch base on the current and previous states
229
+ * The transmitted patch follows the [JSPatch](http://jsonpatch.com) standard
230
+ * This function accepts an entire projectState instance, computes the delta and transmits that to the server for merging
231
+ *
232
+ * @param {Object} newState The local projectState to accept
233
+ * @param {Object} oldState The previous projectState to examine against
234
+ *
235
+ * @returns {Promise} A promise which will resolve when the operation has completed
236
+ */
237
+ createProjectStatePatch(newState, oldState) {
238
+ let patch = diff(oldState, newState, jsPatchConverter);
239
+ this.debug('INFO', 'Created project patch', {newState, oldState});
240
+ return this.applyProjectStatePatch(patch);
241
+ }
242
+
243
+
244
+ /**
245
+ * Client function which accepts a patch from the server and applies it to local project state
246
+ * The patch should follow the [JSPatch](http://jsonpatch.com) standard
247
+ * This function is expected to be sub-classed by a plugin
248
+ *
249
+ * @param {Array} patch A JSPatch patch to apply
250
+ *
251
+ * @returns {Promise} A promise which will resolve when the operation has completed
252
+ */
253
+ applyProjectStatePatchLocal(patch) {
254
+ this.debug('WARN', 'Accepted project patch', patch, 'but applyProjectStatePatchLocal() has not been sub-classed');
255
+ return Promise.resolve();
256
+ }
257
+ // }}}
258
+
207
259
  // Init - constructor(), init(), inject*() {{{
208
260
 
209
261
  /**
@@ -220,13 +272,16 @@ export default class TeraFy {
220
272
  * Initalize the TERA client singleton
221
273
  * This function can only be called once and will return the existing init() worker Promise if its called againt
222
274
  *
275
+ * @param {Object} [options] Additional options to merge into `settings` via `set`
223
276
  * @returns {Promise<TeraFy>} An eventual promise which will resovle with this terafy instance
224
277
  */
225
- init() {
278
+ init(options) {
279
+ if (options) this.set(options);
226
280
  if (this.init.promise) return this.init.promise; // Aleady been called - return init promise
227
281
 
228
282
  window.addEventListener('message', this.acceptMessage.bind(this));
229
283
 
284
+ const context = this;
230
285
  return this.init.promise = Promise.resolve()
231
286
  .then(()=> this.detectMode())
232
287
  .then(mode => this.settings.mode = mode)
@@ -235,11 +290,17 @@ export default class TeraFy {
235
290
  this.injectComms(),
236
291
  this.injectStylesheet(),
237
292
  this.injectMethods(),
238
-
239
- // Init all plugins
240
- ...this.plugins
241
- .map(plugin => plugin.init()),
242
293
  ]))
294
+ .then(()=> this.rpc('setServerMode', // Tell server what mode its in
295
+ this.settings.mode == 'child' ? 'embedded'
296
+ : this.settings.mode == 'parent' ? 'window'
297
+ : (()=> { throw(`Unknown server mode "${this.settings.mode}"`) })()
298
+ ))
299
+ .then(()=> Promise.all( // Init all plugins (with this outer module as the context)
300
+ this.plugins.map(plugin =>
301
+ plugin.init.call(context)
302
+ )
303
+ ))
243
304
  }
244
305
 
245
306
 
@@ -279,6 +340,8 @@ export default class TeraFy {
279
340
  injectComms() { return new Promise(resolve => {
280
341
  switch (this.settings.mode) {
281
342
  case 'child':
343
+ this.debug('Injecting TERA site as iFrame child');
344
+
282
345
  this.dom.el = document.createElement('div')
283
346
  this.dom.el.id = 'tera-fy';
284
347
  this.dom.el.classList.toggle('dev-mode', this.settings.devMode);
@@ -298,6 +361,7 @@ export default class TeraFy {
298
361
  this.dom.el.append(this.dom.iframe);
299
362
  break;
300
363
  case 'parent':
364
+ this.debug('Using TERA site stack parent');
301
365
  resolve();
302
366
  break;
303
367
  default:
@@ -453,6 +517,7 @@ export default class TeraFy {
453
517
  mixin(target, source) {
454
518
  Object.getOwnPropertyNames(Object.getPrototypeOf(source))
455
519
  .filter(prop => !['constructor', 'prototype', 'name'].includes(prop))
520
+ .filter(prop => prop != 'init') // Don't allow plugin init() to override our version - these all get called during init() anyway
456
521
  .forEach((prop) => {
457
522
  Object.defineProperty(
458
523
  target,
@@ -496,4 +561,225 @@ export default class TeraFy {
496
561
  globalThis.document.body.classList.toggle('tera-fy-focus', isFocused === 'toggle' ? undefined : isFocused);
497
562
  }
498
563
  // }}}
564
+
565
+ // Stub documentation carried over from ./terafy.server.js {{{
566
+ /**
567
+ * Return basic server information as a form of validation
568
+ *
569
+ * @function handshake
570
+ * @returns {Promise<Object>} Basic promise result
571
+ * @property {Date} date Server date
572
+ */
573
+
574
+
575
+ /**
576
+ * User / active session within TERA
577
+ *
578
+ * @name User
579
+ * @property {String} id Unique identifier of the user
580
+ * @property {String} email The email address of the current user
581
+ * @property {String} name The provided full name of the user
582
+ * @property {Boolean} isSubscribed Whether the active user has a TERA subscription
583
+ */
584
+
585
+
586
+ /**
587
+ * Fetch the current session user
588
+ *
589
+ * @function getUser
590
+ * @returns {Promise<User>} The current logged in user or null if none
591
+ */
592
+
593
+
594
+ /**
595
+ * Project entry within TERA
596
+ *
597
+ * @name Project
598
+ * @url https://tera-tools.com/help/api/schema
599
+ */
600
+
601
+
602
+ /**
603
+ * Get the currently active project, if any
604
+ *
605
+ * @function getProject
606
+ * @returns {Promise<Project|null>} The currently active project, if any
607
+ */
608
+
609
+
610
+ /**
611
+ * Get a list of projects the current session user has access to
612
+ *
613
+ * @function getProjects
614
+ * @returns {Promise<Array<Project>>} Collection of projects the user has access to
615
+ */
616
+
617
+
618
+ /**
619
+ * Set the currently active project within TERA
620
+ *
621
+ * @function setActiveProject
622
+ * @param {Object|String} project The project to set as active - either the full Project object or its ID
623
+ */
624
+
625
+
626
+ /**
627
+ * Ask the user to select a project from those available - if one isn't already active
628
+ * Note that this function will percist in asking the uesr even if they try to cancel
629
+ *
630
+ * @function requireProject
631
+ * @param {Object} [options] Additional options to mutate behaviour
632
+ * @param {Boolean} [options.autoSetActiveProject=true] After selecting a project set that project as active in TERA
633
+ * @param {String} [options.title="Select a project to work with"] The title of the dialog to display
634
+ * @param {String} [options.noSelectTitle='Select project'] Dialog title when warning the user they need to select something
635
+ * @param {String} [options.noSelectBody='A project needs to be selected to continue'] Dialog body when warning the user they need to select something
636
+ *
637
+ * @returns {Promise<Project>} The active project
638
+ */
639
+
640
+
641
+ /**
642
+ * Prompt the user to select a project from those available
643
+ *
644
+ * @function selectProject
645
+ * @param {Object} [options] Additional options to mutate behaviour
646
+ * @param {String} [options.title="Select a project to work with"] The title of the dialog to display
647
+ * @param {Boolean} [options.allowCancel=true] Advertise cancelling the operation, the dialog can still be cancelled by closing it
648
+ * @param {Boolean} [options.setActive=false] Also set the project as active when selected
649
+ *
650
+ * @returns {Promise<Project>} The active project
651
+ */
652
+
653
+
654
+ /**
655
+ * Return the current, full snapshot state of the active project
656
+ *
657
+ * @function getProjectState
658
+ * @param {Object} [options] Additional options to mutate behaviour
659
+ * @param {Boolean} [options.autoRequire=true] Run `requireProject()` automatically before continuing
660
+ * @param {Array<String>} Paths to subscribe to e.g. ['/users/'],
661
+ *
662
+ * @returns {Promise<Object>} The current project state snapshot
663
+ */
664
+
665
+
666
+ /**
667
+ * Set a nested value within the project state
668
+ * Paths can be any valid Lodash.set() value such as:
669
+ *
670
+ * - Dotted notation - e.g. `foo.bar.1.baz`
671
+ * - Array path segments e.g. `['foo', 'bar', 1, 'baz']`
672
+ *
673
+ *
674
+ * @function setProjectState
675
+ * @param {String|Array<String>} path The sub-path within the project state to set
676
+ * @param {*} value The value to set
677
+ *
678
+ * @param {Object} [options] Additional options to mutate behaviour
679
+ * @param {Boolean} [options.save=true] Save the changes to the server immediately, disable to queue up multiple writes
680
+ * @param {Boolean} [options.sync=false] Wait for the server to acknowledge the write, you almost never need to do this
681
+ *
682
+ * @returns {Promise} A promise which resolves when the operation has synced with the server
683
+ */
684
+
685
+
686
+ /**
687
+ * Force-Save the currently active project state
688
+ *
689
+ * @function saveProjectState
690
+ * @returns {Promise} A promise which resolves when the operation has completed
691
+ */
692
+
693
+
694
+ /**
695
+ * Overwrite the entire project state with a new object
696
+ * You almost never want to use this function directly, see `setProjectState(path, value)` for a nicer wrapper
697
+ *
698
+ * @function replaceProjectState
699
+ * @see setProjectState()
700
+ * @param {Object} newState The new state to replace the current state with
701
+ * @returns {Promise} A promise which resolves when the operation has completed
702
+ */
703
+
704
+
705
+ /**
706
+ * Apply a computed `just-diff` patch to the current project state
707
+ *
708
+ * @function applyProjectStatePatch
709
+ * @param {Object} Patch to apply
710
+ * @returns {Promise} A promise which resolves when the operation has completed
711
+ */
712
+
713
+
714
+ /**
715
+ * Subscribe to project state changes
716
+ * This will dispatch an RPC call to the source object `applyProjectStatePatchLocal()` function with the patch
717
+ * If the above call fails the subscriber is assumed as dead and unsubscribed from the polling list
718
+ *
719
+ * @function subscribeProjectState
720
+ * @returns {Promise<Function>} A promise which resolves when a subscription has been created, call the resulting function to unsubscribe
721
+ */
722
+
723
+
724
+ /**
725
+ * Data structure for a project file
726
+ *
727
+ * @name ProjectFile
728
+ *
729
+ * @property {String} id A UUID string representing the unique ID of the file
730
+ * @property {String} name Relative name path (can contain prefix directories) for the human readable file name
731
+ * @property {Object} parsedName An object representing meta file parts of a file name
732
+ * @property {String} parsedName.basename The filename + extention (i.e. everything without directory name)
733
+ * @property {String} parsedName.filename The file portion of the name (basename without the extension)
734
+ * @property {String} parsedName.ext The extension portion of the name (always lower case)
735
+ * @property {String} parsedName.dirName The directory path portion of the name
736
+ * @property {Date} created A date representing when the file was created
737
+ * @property {Date} modified A date representing when the file was created
738
+ * @property {Date} accessed A date representing when the file was last accessed
739
+ * @property {Number} size Size, in bytes, of the file
740
+ * @property {String} mime The associated mime type for the file
741
+ */
742
+
743
+
744
+ /**
745
+ * Fetch the files associated with a given project
746
+ *
747
+ * @function getProjectFiles
748
+ * @param {Object} options Options which mutate behaviour
749
+ * @param {Boolean} [options.autoRequire=true] Run `requireProject()` automatically before continuing
750
+ * @param {Boolean} [options.meta=true] Pull meta information for each file entity
751
+ *
752
+ * @returns {Promise<ProjectFile>} A collection of project files for the given project
753
+ */
754
+
755
+
756
+ /**
757
+ * Fetch the active projects citation library
758
+ *
759
+ * @function getProjectLibrary
760
+ * @param {String} [path] Optional file path to use, if omitted the contents of `options` are used to guess at a suitable file
761
+ * @param {Object} [options] Additional options to mutate behaviour
762
+ * @param {Boolean} [options.autoRequire=true] Run `requireProject()` automatically before continuing
763
+ * @param {Boolean} [options.multiple=false] Allow selection of multiple libraries
764
+ * @param {Function} [options.filter] Optional async file filter, called each time as `(File:ProjectFile)`
765
+ * @param {Function} [options.find] Optional async final stage file filter to reduce all candidates down to one subject file
766
+ * @param {String|Array<String>} [options.hint] Hints to identify the library to select in array order of preference. Generally corresponds to the previous stage - e.g. 'deduped', 'review1', 'review2', 'dedisputed'
767
+ *
768
+ * @returns {Promise<Array<ProjectFile>>} Collection of references for the selected library matching the given hint + filter, this could be a zero length array
769
+ */
770
+
771
+
772
+ /**
773
+ * Save back a projects citation library
774
+ *
775
+ * @function setProjectLibrary
776
+ * @param {Array<RefLibRef>} Collection of references for the selected library
777
+ *
778
+ * @param {Object} [options] Additional options to mutate behaviour
779
+ * @param {Boolean} [options.autoRequire=true] Run `requireProject()` automatically before continuing
780
+ * @param {String} [options.hint] Hint to store against the library. Generally corresponds to the current operation being performed - e.g. 'deduped'
781
+ *
782
+ * @returns {Promise} A promise which resolves when the save operation has completed
783
+ */
784
+ // }}}
499
785
  }