@tiddlywiki/mws 0.0.21 → 0.1.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 (202) hide show
  1. package/README.md +42 -9
  2. package/dist/metafile-esm.json +1 -0
  3. package/dist/mws.d.ts +1 -0
  4. package/dist/mws.js +34710 -0
  5. package/dist/mws.js.map +1 -0
  6. package/editions/mws-docs/tiddlers/$__StoryList.tid +1 -2
  7. package/editions/mws-docs/tiddlers/2025-02-18 - User Management.md.meta +2 -2
  8. package/editions/mws-docs/tiddlers/2025-04-14 - client plugins /342/234/224/357/270/217.md" +31 -0
  9. package/editions/mws-docs/tiddlers/2025-04-14 - client plugins /342/234/224/357/270/217.md.meta" +5 -0
  10. package/editions/mws-docs/tiddlers/{2025-05-04.md → 2025-05-04 - event hooks /342/234/224/357/270/217.md" } +7 -1
  11. package/editions/mws-docs/tiddlers/2025-05-04 - event hooks /342/234/224/357/270/217.md.meta" +5 -0
  12. package/editions/mws-docs/tiddlers/{2025-05-06.md → 2025-05-06 - syncer design.md } +0 -4
  13. package/editions/mws-docs/tiddlers/2025-05-06 - syncer design.md.meta +5 -0
  14. package/editions/mws-docs/tiddlers/Allowed Methods.tid +8 -0
  15. package/editions/mws-docs/tiddlers/Application Access & Security.md +14 -84
  16. package/editions/mws-docs/tiddlers/Application Access & Security.md.meta +1 -1
  17. package/editions/mws-docs/tiddlers/Body Format.md +19 -0
  18. package/editions/mws-docs/tiddlers/Body Format.md.meta +4 -0
  19. package/editions/mws-docs/tiddlers/Checklists and Features.md +0 -3
  20. package/editions/mws-docs/tiddlers/Checklists and Features.md.meta +1 -1
  21. package/editions/mws-docs/tiddlers/Client Plugins.md +17 -0
  22. package/editions/mws-docs/tiddlers/Client Plugins.md.meta +5 -0
  23. package/editions/mws-docs/tiddlers/HTTP API.md +4 -3
  24. package/editions/mws-docs/tiddlers/HTTP API.md.meta +1 -1
  25. package/editions/mws-docs/tiddlers/HelloThere.tid +2 -2
  26. package/editions/mws-docs/tiddlers/Installation.tid +2 -3
  27. package/editions/mws-docs/tiddlers/MWS Architecture.tid +6 -1
  28. package/editions/mws-docs/tiddlers/Multi-user Trust.md +12 -0
  29. package/editions/mws-docs/tiddlers/Multi-user Trust.md.meta +5 -0
  30. package/editions/mws-docs/tiddlers/Notes.tid +5 -3
  31. package/editions/mws-docs/tiddlers/Oracle Attacks.md +29 -0
  32. package/editions/mws-docs/tiddlers/Oracle Attacks.md.meta +5 -0
  33. package/editions/mws-docs/tiddlers/Reference.md +1 -0
  34. package/editions/mws-docs/tiddlers/Reference.md.meta +1 -1
  35. package/editions/mws-docs/tiddlers/Request Handling.md +53 -0
  36. package/editions/mws-docs/tiddlers/Request Handling.md.meta +5 -0
  37. package/editions/mws-docs/tiddlers/Roadmap.tid +7 -0
  38. package/editions/mws-docs/tiddlers/Route Definers.md +206 -0
  39. package/editions/mws-docs/tiddlers/Route Definers.md.meta +5 -0
  40. package/editions/mws-docs/tiddlers/Security.md +75 -0
  41. package/editions/mws-docs/tiddlers/Security.md.meta +5 -0
  42. package/editions/mws-docs/tiddlers/Server Plugins.md +7 -0
  43. package/editions/mws-docs/tiddlers/Server Plugins.md.meta +5 -0
  44. package/editions/mws-docs/tiddlers/Startup Cache.md +28 -0
  45. package/editions/mws-docs/tiddlers/Startup Cache.md.meta +5 -0
  46. package/editions/mws-docs/tiddlers/TiddlerFields.md +5 -0
  47. package/editions/mws-docs/tiddlers/TiddlerFields.md.meta +4 -0
  48. package/editions/mws-docs/tiddlers/Usage.tid +2 -2
  49. package/editions/mws-docs/tiddlers/system/$__mws_cssvar-macro.tid +10 -0
  50. package/editions/mws-docs/tiddlers/system/$__palettes_MUI +143 -0
  51. package/editions/mws-docs/tiddlers/system/$__palettes_MUI.meta +7 -0
  52. package/editions/mws-docs/tiddlers/system/$__palettes_MUI_styles.css +567 -0
  53. package/editions/mws-docs/tiddlers/system/$__palettes_MUI_styles.css.meta +6 -0
  54. package/editions/mws-docs/tiddlers/system/$__theme.tid +6 -0
  55. package/editions/mws-docs/tiddlers/system/SplashScreen.tid +21 -0
  56. package/editions/mws-docs/tiddlers/system/StaticBanner.tid +1 -1
  57. package/editions/mws-docs/tiddlers/system/Styles.tid +13 -0
  58. package/editions/mws-docs/tiddlers/system/palette.tid +4 -1
  59. package/editions/mws-docs/tiddlers/system/sidebarlayout.tid +4 -1
  60. package/editions/mws-docs/tiddlers/x-mws-tiddler.md +35 -0
  61. package/editions/mws-docs/tiddlers/x-mws-tiddler.md.meta +5 -0
  62. package/mws.run.mjs +30 -0
  63. package/package.json +47 -36
  64. package/plugins/client/package-lock.json +46 -83
  65. package/plugins/client/package.json +1 -6
  66. package/plugins/client/plugin.info +2 -2
  67. package/plugins/client/src/fflate.d.ts +1501 -0
  68. package/plugins/client/src/multiwikiclientadaptor.ts +768 -317
  69. package/plugins/client/src/mws.d.ts +4 -0
  70. package/plugins/client/src/types.d.ts +3928 -0
  71. package/plugins/client/tiddlers/en-US.multids +3 -0
  72. package/plugins/client/tiddlers/status/SaveWikiButtonTemplate.tid +3 -0
  73. package/plugins/client/tiddlers/status/icon-cloud.tid +4 -0
  74. package/plugins/client/tiddlers/status/info-segment.tid +8 -0
  75. package/plugins/client/tiddlers/status/readonly-styles.tid +27 -0
  76. package/plugins/client/tiddlers/status/save-offline.tid +7 -0
  77. package/plugins/client/tiddlers/status/save-wiki-button.tid +26 -0
  78. package/plugins/client/tiddlers/status/styles.tid +44 -0
  79. package/plugins/client/tiddlers/status/syncer-actions-copy-logs.tid +6 -0
  80. package/plugins/client/tiddlers/status/syncer-actions-login-status.tid +9 -0
  81. package/plugins/client/tiddlers/status/syncer-actions-login.tid +8 -0
  82. package/plugins/client/tiddlers/status/syncer-actions-logout.tid +8 -0
  83. package/plugins/client/tiddlers/status/syncer-actions-refresh.tid +9 -0
  84. package/plugins/client/tiddlers/status/syncer-actions-save-snapshot.tid +9 -0
  85. package/plugins/client/tiddlers/status/tags-syncerdropdown.tid +2 -0
  86. package/plugins/client/tiddlers/syncer/multiwikiclientadaptor.js +640 -0
  87. package/plugins/client/tiddlers/syncer/syncer.js +831 -0
  88. package/plugins/client/tsconfig.json +19 -16
  89. package/plugins/filter-ast-to-sql/example-run-series.ts +146 -0
  90. package/plugins/server/SplashScreen.tid +150 -0
  91. package/plugins/server/fflate.js +9 -0
  92. package/plugins/server/plugin.info +8 -0
  93. package/plugins/server/readme +1 -0
  94. package/plugins/server/url-polyfill.js +294 -0
  95. package/prisma/client/client.d.ts +1 -0
  96. package/prisma/client/client.js +3 -0
  97. package/prisma/client/default.d.ts +1 -0
  98. package/prisma/client/default.js +3 -0
  99. package/prisma/client/edge.d.ts +1 -0
  100. package/prisma/client/edge.js +297 -0
  101. package/prisma/client/index-browser.js +275 -0
  102. package/prisma/client/index.d.ts +17770 -0
  103. package/prisma/client/index.js +324 -0
  104. package/prisma/client/package.json +173 -0
  105. package/prisma/client/query_engine_bg.js +811 -0
  106. package/prisma/client/query_engine_bg.wasm +0 -0
  107. package/prisma/client/runtime/edge-esm.js +10000 -0
  108. package/prisma/client/runtime/edge.js +10036 -0
  109. package/prisma/client/runtime/index-browser.d.ts +370 -0
  110. package/prisma/client/runtime/index-browser.js +2300 -0
  111. package/prisma/client/runtime/library.d.ts +3681 -0
  112. package/prisma/client/runtime/library.js +10604 -0
  113. package/prisma/client/runtime/react-native.js +10629 -0
  114. package/prisma/client/runtime/wasm-compiler-edge.js +11238 -0
  115. package/prisma/client/runtime/wasm-engine-edge.js +7801 -0
  116. package/prisma/client/schema.prisma +156 -0
  117. package/prisma/client/wasm-edge-light-loader.mjs +4 -0
  118. package/prisma/client/wasm-worker-loader.mjs +4 -0
  119. package/prisma/client/wasm.d.ts +1 -0
  120. package/prisma/client/wasm.js +304 -0
  121. package/prisma/global.ts +53 -0
  122. package/prisma/migrations/{20250406213424_init → 20250606001949_init}/migration.sql +46 -48
  123. package/prisma/migrations/20250613000526_remove_group/migration.sql +22 -0
  124. package/prisma/migrations/20250620014502_settings/migration.sql +5 -0
  125. package/prisma/migrations/20250707205229_recipes/migration.sql +20 -0
  126. package/prisma/migrations/migration_lock.toml +1 -1
  127. package/prisma/notes.md +1 -2
  128. package/prisma/schema.prisma +52 -49
  129. package/prisma/test.sqlite +0 -0
  130. package/prisma/test.sqlite-journal +0 -0
  131. package/public/react-admin/android-chrome-192x192.png +0 -0
  132. package/public/react-admin/android-chrome-512x512.png +0 -0
  133. package/public/react-admin/apple-touch-icon.png +0 -0
  134. package/{react-user-mgmt/public/esm-QKDFEP3B.js → public/react-admin/esm-L4WLVWJN.js} +1 -1
  135. package/public/react-admin/esm-L4WLVWJN.js.map +7 -0
  136. package/public/react-admin/esm-QMNGI3LF.js +3 -0
  137. package/public/react-admin/favicon-16x16.png +0 -0
  138. package/public/react-admin/favicon-32x32.png +0 -0
  139. package/public/react-admin/favicon.ico +0 -0
  140. package/public/react-admin/index.html +36 -0
  141. package/public/react-admin/main.js +268 -0
  142. package/public/react-admin/main.js.map +7 -0
  143. package/public/react-admin/site.webmanifest +20 -0
  144. package/public/react-admin/stats.json +1 -0
  145. package/scripts.mjs +134 -25
  146. package/dist/server.d.ts +0 -945
  147. package/dist/server.js +0 -20775
  148. package/dist/server.js.map +0 -1
  149. package/editions/mws-docs/tiddlers/2025-04-03 - Phases of Development.md +0 -64
  150. package/editions/mws-docs/tiddlers/2025-04-03 - Phases of Development.md.meta +0 -5
  151. package/editions/mws-docs/tiddlers/2025-04-13 - Plugins.md +0 -18
  152. package/editions/mws-docs/tiddlers/2025-04-13 - Plugins.md.meta +0 -5
  153. package/editions/mws-docs/tiddlers/2025-05-04.md.meta +0 -5
  154. package/editions/mws-docs/tiddlers/2025-05-06.md.meta +0 -5
  155. package/editions/mws-docs/tiddlers/Moduler Design.md +0 -29
  156. package/editions/mws-docs/tiddlers/Moduler Design.md.meta +0 -5
  157. package/editions/mws-docs/tiddlers/Request Server.md +0 -20
  158. package/editions/mws-docs/tiddlers/Request Server.md.meta +0 -5
  159. package/editions/mws-docs/tiddlers/system/$__palettes_MUI-Dark +0 -173
  160. package/editions/mws-docs/tiddlers/system/$__palettes_MUI-Dark.meta +0 -6
  161. package/editions/mws-docs/tiddlers/system/$__palettes_MUI-Light +0 -172
  162. package/editions/mws-docs/tiddlers/system/$__palettes_MUI-Light.meta +0 -6
  163. package/plugins/client/tiddlers/config-tiddlers-filter.tid +0 -2
  164. package/plugins/client/tiddlers/config-use-server-sent-events.tid +0 -2
  165. package/plugins/client/tiddlers/info-segment.tid +0 -8
  166. package/plugins/client/tiddlers/multiwikiclientadaptor.js +0 -444
  167. package/prisma/migrations/20250407002306_disconnect_user_tables/migration.sql +0 -52
  168. package/prisma/old-db.sql +0 -144
  169. package/react-user-mgmt/README.md +0 -50
  170. package/react-user-mgmt/eslint.config.js +0 -28
  171. package/react-user-mgmt/package-lock.json +0 -2738
  172. package/react-user-mgmt/package.json +0 -31
  173. package/react-user-mgmt/public/index.html +0 -1
  174. package/react-user-mgmt/public/main.js +0 -295
  175. package/react-user-mgmt/public/main.js.map +0 -7
  176. package/react-user-mgmt/public/stats.json +0 -1
  177. package/react-user-mgmt/tsconfig.json +0 -40
  178. package/react-user-mgmt/types.d.ts +0 -2
  179. package/tiddlywiki-types/boot/boot.d.ts +0 -378
  180. package/tiddlywiki-types/boot/bootprefix.d.ts +0 -110
  181. package/tiddlywiki-types/core/modules/filters.d.ts +0 -107
  182. package/tiddlywiki-types/core/modules/tiddler.d.ts +0 -47
  183. package/tiddlywiki-types/core/modules/wiki-bulkops.d.ts +0 -36
  184. package/tiddlywiki-types/core/modules/wiki.d.ts +0 -208
  185. package/tiddlywiki-types/index.d.ts +0 -7
  186. package/tiddlywiki-types/package.json +0 -3
  187. /package/plugins/client/tiddlers/{sidebarsegment.tid → status/sidebarsegment.tid} +0 -0
  188. /package/plugins/client/tiddlers/{GettingStarted.tid → syncer/GettingStarted.tid} +0 -0
  189. /package/plugins/client/tiddlers/{config-incoming-updates-filter.tid → syncer/config-incoming-updates-filter.tid} +0 -0
  190. /package/plugins/client/tiddlers/{readme.tid → syncer/readme.tid} +0 -0
  191. /package/{react-user-mgmt/public → public/react-admin}/chunk-YALWN5OT.js +0 -0
  192. /package/{react-user-mgmt/public → public/react-admin}/chunk-YALWN5OT.js.map +0 -0
  193. /package/{react-user-mgmt/public/esm-QKDFEP3B.js.map → public/react-admin/esm-QMNGI3LF.js.map} +0 -0
  194. /package/{react-user-mgmt/public → public/react-admin}/favicon.png +0 -0
  195. /package/{react-user-mgmt/public → public/react-admin}/missing-favicon.png +0 -0
  196. /package/{react-user-mgmt/public → public/react-admin}/motovun-jack.jpg +0 -0
  197. /package/{react-user-mgmt/public → public/react-admin}/mws-icon.png +0 -0
  198. /package/{react-user-mgmt/public → public/react-admin}/stats.css +0 -0
  199. /package/{react-user-mgmt/public → public/react-admin}/stats.html +0 -0
  200. /package/{react-user-mgmt/public → public/react-admin}/stats.js +0 -0
  201. /package/{react-user-mgmt/public → public/react-admin}/stats.svg +0 -0
  202. /package/{react-user-mgmt/public → public/react-admin}/vite.svg +0 -0
@@ -0,0 +1,831 @@
1
+ /*\
2
+ title: $:/core/modules/syncer.js
3
+ type: application/javascript
4
+ module-type: global
5
+
6
+ The syncer tracks changes to the store and synchronises them to a remote data store represented as a "sync adaptor"
7
+
8
+ \*/
9
+
10
+ "use strict";
11
+
12
+ /*
13
+ Defaults
14
+ */
15
+ Syncer.prototype.titleIsLoggedIn = "$:/status/IsLoggedIn";
16
+ Syncer.prototype.titleIsAnonymous = "$:/status/IsAnonymous";
17
+ Syncer.prototype.titleIsReadOnly = "$:/status/IsReadOnly";
18
+ Syncer.prototype.titleUserName = "$:/status/UserName";
19
+ Syncer.prototype.titleSyncFilter = "$:/config/SyncFilter";
20
+ Syncer.prototype.titleSyncPollingInterval = "$:/config/SyncPollingInterval";
21
+ Syncer.prototype.titleSyncDisableLazyLoading = "$:/config/SyncDisableLazyLoading";
22
+ Syncer.prototype.titleSavedNotification = "$:/language/Notifications/Save/Done";
23
+ Syncer.prototype.titleSyncThrottleInterval = "$:/config/SyncThrottleInterval";
24
+ Syncer.prototype.taskTimerInterval = 0.25 * 1000; // Interval for sync timer
25
+ Syncer.prototype.throttleInterval = 1 * 1000; // Defer saving tiddlers if they've changed in the last 1s...
26
+ Syncer.prototype.errorRetryInterval = 5 * 1000; // Interval to retry after an error
27
+ Syncer.prototype.fallbackInterval = 10 * 1000; // Unless the task is older than 10s
28
+ Syncer.prototype.pollTimerInterval = 60 * 1000; // Interval for polling for changes from the adaptor
29
+
30
+ /*
31
+ Instantiate the syncer with the following options:
32
+ syncadaptor: reference to syncadaptor to be used
33
+ wiki: wiki to be synced
34
+ */
35
+ function Syncer(options) {
36
+ var self = this;
37
+ this.wiki = options.wiki;
38
+ // Save parameters
39
+ this.syncadaptor = options.syncadaptor;
40
+ this.disableUI = !!options.disableUI;
41
+ this.titleIsLoggedIn = options.titleIsLoggedIn || this.titleIsLoggedIn;
42
+ this.titleUserName = options.titleUserName || this.titleUserName;
43
+ this.titleSyncFilter = options.titleSyncFilter || this.titleSyncFilter;
44
+ this.titleSavedNotification = options.titleSavedNotification || this.titleSavedNotification;
45
+ this.taskTimerInterval = options.taskTimerInterval || this.taskTimerInterval;
46
+ this.throttleInterval = options.throttleInterval || parseInt(this.wiki.getTiddlerText(this.titleSyncThrottleInterval,""),10) || this.throttleInterval;
47
+ this.errorRetryInterval = options.errorRetryInterval || this.errorRetryInterval;
48
+ this.fallbackInterval = options.fallbackInterval || this.fallbackInterval;
49
+ this.pollTimerInterval = options.pollTimerInterval || parseInt(this.wiki.getTiddlerText(this.titleSyncPollingInterval,""),10) || this.pollTimerInterval;
50
+ this.logging = "logging" in options ? options.logging : true;
51
+ // Make a logger
52
+ this.logger = new $tw.utils.Logger("syncer" + ($tw.browser ? "-browser" : "") + ($tw.node ? "-server" : "") + (this.syncadaptor.name ? ("-" + this.syncadaptor.name) : ""),{
53
+ colour: "cyan",
54
+ enable: this.logging,
55
+ saveHistory: true
56
+ });
57
+ // Make another logger for connection errors
58
+ this.loggerConnection = new $tw.utils.Logger("syncer" + ($tw.browser ? "-browser" : "") + ($tw.node ? "-server" : "") + (this.syncadaptor.name ? ("-" + this.syncadaptor.name) : "") + "-connection",{
59
+ colour: "cyan",
60
+ enable: this.logging
61
+ });
62
+ // Ask the syncadaptor to use the main logger
63
+ if(this.syncadaptor.setLoggerSaveBuffer) {
64
+ this.syncadaptor.setLoggerSaveBuffer(this.logger);
65
+ }
66
+ // Compile the dirty tiddler filter
67
+ this.filterFn = this.wiki.compileFilter(this.wiki.getTiddlerText(this.titleSyncFilter));
68
+ // Record information for known tiddlers
69
+ this.readTiddlerInfo();
70
+ this.titlesToBeLoaded = {}; // Hashmap of titles of tiddlers that need loading from the server
71
+ this.titlesHaveBeenLazyLoaded = {}; // Hashmap of titles of tiddlers that have already been lazily loaded from the server
72
+ // Timers
73
+ this.taskTimerId = null; // Timer for task dispatch
74
+ // Number of outstanding requests
75
+ this.numTasksInProgress = 0;
76
+ // True when we want to force an immediate sync from the server
77
+ this.forceSyncFromServer = false;
78
+ this.timestampLastSyncFromServer = new Date();
79
+ // Listen out for changes to tiddlers
80
+ this.wiki.addEventListener("change",function(changes) {
81
+ // Filter the changes to just include ones that are being synced
82
+ var filteredChanges = self.getSyncedTiddlers(function(callback) {
83
+ $tw.utils.each(changes,function(change,title) {
84
+ var tiddler = self.wiki.tiddlerExists(title) && self.wiki.getTiddler(title);
85
+ callback(tiddler,title);
86
+ });
87
+ });
88
+ if(filteredChanges.length > 0) {
89
+ self.processTaskQueue();
90
+ } else {
91
+ // Look for deletions of tiddlers we're already syncing
92
+ var outstandingDeletion = false
93
+ $tw.utils.each(changes,function(change,title,object) {
94
+ if(change.deleted && $tw.utils.hop(self.tiddlerInfo,title)) {
95
+ outstandingDeletion = true;
96
+ }
97
+ });
98
+ if(outstandingDeletion) {
99
+ self.processTaskQueue();
100
+ }
101
+ }
102
+ });
103
+ // Browser event handlers
104
+ if($tw.browser && !this.disableUI) {
105
+ // Set up our beforeunload handler
106
+ $tw.addUnloadTask(function(event) {
107
+ var confirmationMessage;
108
+ if(self.isDirty()) {
109
+ confirmationMessage = $tw.language.getString("UnsavedChangesWarning");
110
+ event.returnValue = confirmationMessage; // Gecko
111
+ }
112
+ return confirmationMessage;
113
+ });
114
+ // Listen out for login/logout/refresh events in the browser
115
+ $tw.rootWidget.addEventListener("tm-login",function(event) {
116
+ var username = event && event.paramObject && event.paramObject.username,
117
+ password = event && event.paramObject && event.paramObject.password;
118
+ if(username && password) {
119
+ // Login with username and password
120
+ self.login(username,password,function() {});
121
+ } else {
122
+ // No username and password, so we display a prompt
123
+ self.handleLoginEvent();
124
+ }
125
+ });
126
+ $tw.rootWidget.addEventListener("tm-logout",function() {
127
+ self.handleLogoutEvent();
128
+ });
129
+ $tw.rootWidget.addEventListener("tm-server-refresh",function() {
130
+ self.handleRefreshEvent();
131
+ });
132
+ $tw.rootWidget.addEventListener("tm-copy-syncer-logs-to-clipboard",function() {
133
+ $tw.utils.copyToClipboard($tw.utils.getSystemInfo() + "\n\nLog:\n" + self.logger.getBuffer());
134
+ });
135
+ }
136
+ // Listen out for lazyLoad events
137
+ if(!this.disableUI && this.wiki.getTiddlerText(this.titleSyncDisableLazyLoading) !== "yes") {
138
+ this.wiki.addEventListener("lazyLoad",function(title) {
139
+ self.handleLazyLoadEvent(title);
140
+ });
141
+ }
142
+ if(this.syncadaptor.subscribe) this.syncadaptor.subscribe(function(){
143
+ self.syncFromServer();
144
+ });
145
+
146
+ // Get the login status
147
+ this.getStatus(function(err,isLoggedIn) {
148
+ // Do a sync from the server
149
+ self.syncFromServer();
150
+ });
151
+
152
+ }
153
+
154
+ /*
155
+ Show a generic network error alert
156
+ */
157
+ Syncer.prototype.displayError = function(msg,err) {
158
+ if(err === ($tw.language.getString("Error/XMLHttpRequest") + ": 0")) {
159
+ this.loggerConnection.alert($tw.language.getString("Error/NetworkErrorAlert"));
160
+ this.logger.log(msg + ":",err);
161
+ } else {
162
+ this.logger.alert(msg + ":",err);
163
+ }
164
+ };
165
+
166
+ /*
167
+ Return an array of the tiddler titles that are subjected to syncing
168
+ */
169
+ Syncer.prototype.getSyncedTiddlers = function(source) {
170
+ return this.filterFn.call(this.wiki,source);
171
+ };
172
+
173
+ /*
174
+ Return an array of the tiddler titles that are subjected to syncing
175
+ */
176
+ Syncer.prototype.getTiddlerRevision = function(title) {
177
+ if(this.syncadaptor && this.syncadaptor.getTiddlerRevision) {
178
+ return this.syncadaptor.getTiddlerRevision(title);
179
+ } else {
180
+ return this.wiki.getTiddler(title).fields.revision;
181
+ }
182
+ };
183
+
184
+ /*
185
+ Read (or re-read) the latest tiddler info from the store
186
+ */
187
+ Syncer.prototype.readTiddlerInfo = function() {
188
+ // Hashmap by title of {revision:,changeCount:,adaptorInfo:}
189
+ // "revision" is the revision of the tiddler last seen on the server, and "changecount" is the corresponding local changecount
190
+ this.tiddlerInfo = {};
191
+ // Record information for known tiddlers
192
+ var self = this,
193
+ tiddlers = this.getSyncedTiddlers();
194
+ $tw.utils.each(tiddlers,function(title) {
195
+ var tiddler = self.wiki.getTiddler(title);
196
+ if(tiddler) {
197
+ self.tiddlerInfo[title] = {
198
+ revision: self.getTiddlerRevision(title),
199
+ adaptorInfo: self.syncadaptor && self.syncadaptor.getTiddlerInfo(tiddler),
200
+ changeCount: self.wiki.getChangeCount(title)
201
+ };
202
+ }
203
+ });
204
+ };
205
+
206
+ /*
207
+ Checks whether the wiki is dirty (ie the window shouldn't be closed)
208
+ */
209
+ Syncer.prototype.isDirty = function() {
210
+ var self = this;
211
+ function checkIsDirty() {
212
+ // Check tiddlers that are in the store and included in the filter function
213
+ var titles = self.getSyncedTiddlers();
214
+ for(var index=0; index<titles.length; index++) {
215
+ var title = titles[index],
216
+ tiddlerInfo = self.tiddlerInfo[title];
217
+ if(self.wiki.tiddlerExists(title)) {
218
+ if(tiddlerInfo) {
219
+ // If the tiddler is known on the server and has been modified locally then it needs to be saved to the server
220
+ if(self.wiki.getChangeCount(title) > tiddlerInfo.changeCount) {
221
+ return true;
222
+ }
223
+ } else {
224
+ // If the tiddler isn't known on the server then it needs to be saved to the server
225
+ return true;
226
+ }
227
+ }
228
+ }
229
+ // Check tiddlers that are known from the server but not currently in the store
230
+ titles = Object.keys(self.tiddlerInfo);
231
+ for(index=0; index<titles.length; index++) {
232
+ if(!self.wiki.tiddlerExists(titles[index])) {
233
+ // There must be a pending delete
234
+ return true;
235
+ }
236
+ }
237
+ return false;
238
+ }
239
+ var dirtyStatus = checkIsDirty();
240
+ return dirtyStatus;
241
+ };
242
+
243
+ /*
244
+ Update the document body with the class "tc-dirty" if the wiki has unsaved/unsynced changes
245
+ */
246
+ Syncer.prototype.updateDirtyStatus = function() {
247
+ if($tw.browser && !this.disableUI) {
248
+ var dirty = this.isDirty();
249
+ $tw.utils.toggleClass(document.body,"tc-dirty",dirty);
250
+ if(!dirty) {
251
+ this.loggerConnection.clearAlerts();
252
+ }
253
+ }
254
+ };
255
+
256
+ /*
257
+ Save an incoming tiddler in the store, and updates the associated tiddlerInfo
258
+ */
259
+ Syncer.prototype.storeTiddler = function(tiddlerFields) {
260
+ // Save the tiddler
261
+ var tiddler = new $tw.Tiddler(tiddlerFields);
262
+ this.wiki.addTiddler(tiddler);
263
+ // Save the tiddler revision and changeCount details
264
+ this.tiddlerInfo[tiddlerFields.title] = {
265
+ revision: this.getTiddlerRevision(tiddlerFields.title),
266
+ adaptorInfo: this.syncadaptor.getTiddlerInfo(tiddler),
267
+ changeCount: this.wiki.getChangeCount(tiddlerFields.title)
268
+ };
269
+ };
270
+
271
+ Syncer.prototype.getStatus = function(callback) {
272
+ var self = this;
273
+ // Check if the adaptor supports getStatus()
274
+ if(this.syncadaptor && this.syncadaptor.getStatus) {
275
+ // Mark us as not logged in
276
+ this.wiki.addTiddler({title: this.titleIsLoggedIn,text: "no"});
277
+ // Get login status
278
+ this.syncadaptor.getStatus(function(err,isLoggedIn,username,isReadOnly,isAnonymous) {
279
+ if(err) {
280
+ self.displayError("Get Status Error",err);
281
+ } else {
282
+ // Set the various status tiddlers
283
+ self.wiki.addTiddler({title: self.titleIsReadOnly,text: isReadOnly ? "yes" : "no"});
284
+ self.wiki.addTiddler({title: self.titleIsAnonymous,text: isAnonymous ? "yes" : "no"});
285
+ self.wiki.addTiddler({title: self.titleIsLoggedIn,text: isLoggedIn ? "yes" : "no"});
286
+ if(isLoggedIn) {
287
+ self.wiki.addTiddler({title: self.titleUserName,text: username || ""});
288
+ }
289
+ }
290
+ // Invoke the callback
291
+ if(callback) {
292
+ callback(err,isLoggedIn,username);
293
+ }
294
+ });
295
+ } else {
296
+ callback(null,true,"UNAUTHENTICATED");
297
+ }
298
+ };
299
+
300
+ /*
301
+ Synchronise from the server by reading the skinny tiddler list and queuing up loads for any tiddlers that we don't already have up to date
302
+ */
303
+ Syncer.prototype.syncFromServer = function() {
304
+ if(this.canSyncFromServer()) {
305
+ this.forceSyncFromServer = true;
306
+ this.processTaskQueue();
307
+ }
308
+ };
309
+
310
+ Syncer.prototype.canSyncFromServer = function() {
311
+ return !!this.syncadaptor.getUpdatedTiddlers || !!this.syncadaptor.getSkinnyTiddlers;
312
+ }
313
+
314
+ /*
315
+ Force load a tiddler from the server
316
+ */
317
+ Syncer.prototype.enqueueLoadTiddler = function(title) {
318
+ this.titlesToBeLoaded[title] = true;
319
+ this.processTaskQueue();
320
+ };
321
+
322
+ /*
323
+ Lazily load a skinny tiddler if we can
324
+ */
325
+ Syncer.prototype.handleLazyLoadEvent = function(title) {
326
+ // Ignore if the syncadaptor doesn't handle it
327
+ if(!this.syncadaptor.supportsLazyLoading) {
328
+ return;
329
+ }
330
+ // Don't lazy load the same tiddler twice
331
+ if(!this.titlesHaveBeenLazyLoaded[title]) {
332
+ // Don't lazy load if the tiddler isn't included in the sync filter
333
+ if(this.getSyncedTiddlers().indexOf(title) !== -1) {
334
+ // Mark the tiddler as needing loading, and having already been lazily loaded
335
+ this.titlesToBeLoaded[title] = true;
336
+ this.titlesHaveBeenLazyLoaded[title] = true;
337
+ this.processTaskQueue();
338
+ }
339
+ }
340
+ };
341
+
342
+ /*
343
+ Dispay a password prompt and allow the user to login
344
+ */
345
+ Syncer.prototype.handleLoginEvent = function() {
346
+ var self = this;
347
+ this.getStatus(function(err,isLoggedIn,username) {
348
+ if(!err && !isLoggedIn) {
349
+ if(self.syncadaptor && self.syncadaptor.displayLoginPrompt) {
350
+ self.syncadaptor.displayLoginPrompt(self);
351
+ } else {
352
+ self.displayLoginPrompt();
353
+ }
354
+ }
355
+ });
356
+ };
357
+
358
+ /*
359
+ Dispay a password prompt
360
+ */
361
+ Syncer.prototype.displayLoginPrompt = function() {
362
+ var self = this;
363
+ var promptInfo = $tw.passwordPrompt.createPrompt({
364
+ serviceName: $tw.language.getString("LoginToTiddlySpace"),
365
+ callback: function(data) {
366
+ self.login(data.username,data.password,function(err,isLoggedIn) {
367
+ self.syncFromServer();
368
+ });
369
+ return true; // Get rid of the password prompt
370
+ }
371
+ });
372
+ };
373
+
374
+ /*
375
+ Attempt to login to TiddlyWeb.
376
+ username: username
377
+ password: password
378
+ callback: invoked with arguments (err,isLoggedIn)
379
+ */
380
+ Syncer.prototype.login = function(username,password,callback) {
381
+ this.logger.log("Attempting to login as",username);
382
+ var self = this;
383
+ if(this.syncadaptor.login) {
384
+ this.syncadaptor.login(username,password,function(err) {
385
+ if(err) {
386
+ return callback(err);
387
+ }
388
+ self.getStatus(function(err,isLoggedIn,username) {
389
+ if(callback) {
390
+ callback(err,isLoggedIn);
391
+ }
392
+ });
393
+ });
394
+ } else {
395
+ callback(null,true);
396
+ }
397
+ };
398
+
399
+ /*
400
+ Attempt to log out of TiddlyWeb
401
+ */
402
+ Syncer.prototype.handleLogoutEvent = function() {
403
+ this.logger.log("Attempting to logout");
404
+ var self = this;
405
+ if(this.syncadaptor.logout) {
406
+ this.syncadaptor.logout(function(err) {
407
+ if(err) {
408
+ self.displayError("Logout Error",err);
409
+ } else {
410
+ self.getStatus();
411
+ }
412
+ });
413
+ }
414
+ };
415
+
416
+ /*
417
+ Immediately refresh from the server
418
+ */
419
+ Syncer.prototype.handleRefreshEvent = function() {
420
+ this.syncFromServer();
421
+ };
422
+
423
+ /*
424
+ Process the next task
425
+ */
426
+ Syncer.prototype.processTaskQueue = function() {
427
+ var self = this;
428
+ // Only process a task if the sync adaptor is fully initialised and we're not already performing
429
+ // a task. If we are already performing a task then we'll dispatch the next one when it completes
430
+ if((!this.syncadaptor.isReady || this.syncadaptor.isReady()) && this.numTasksInProgress === 0) {
431
+ // Choose the next task to perform
432
+ var task = this.chooseNextTask();
433
+ // Perform the task if we had one
434
+ if(typeof task === "object" && task !== null) {
435
+ this.numTasksInProgress += 1;
436
+ task.run(function(err) {
437
+ self.numTasksInProgress -= 1;
438
+ if(err) {
439
+ self.displayError("Sync error while processing " + task.type + " of '" + task.title + "'",err);
440
+ self.updateDirtyStatus();
441
+ self.triggerTimeout(self.errorRetryInterval);
442
+ } else {
443
+ self.updateDirtyStatus();
444
+ // Process the next task
445
+ self.processTaskQueue.call(self);
446
+ }
447
+ });
448
+ } else {
449
+ // No task is ready so update the status
450
+ this.updateDirtyStatus();
451
+ // And trigger a timeout if there is a pending task
452
+ if(task === true) {
453
+ this.triggerTimeout(this.taskTimerInterval);
454
+ } else if(this.canSyncFromServer()) {
455
+ this.triggerTimeout(this.pollTimerInterval);
456
+ }
457
+ }
458
+ } else {
459
+ this.updateDirtyStatus();
460
+ this.triggerTimeout(this.taskTimerInterval);
461
+ }
462
+ };
463
+
464
+ Syncer.prototype.triggerTimeout = function(interval) {
465
+ var self = this;
466
+ if(this.taskTimerId) {
467
+ clearTimeout(this.taskTimerId);
468
+ }
469
+ this.taskTimerId = setTimeout(function() {
470
+ self.taskTimerId = null;
471
+ self.processTaskQueue.call(self);
472
+ },interval || self.taskTimerInterval);
473
+ };
474
+
475
+ /*
476
+ Choose the next sync task. We prioritise saves to the server, then getting updates from the server, then deletes to the server, then loads from the server
477
+
478
+ Returns either:
479
+ * a task object
480
+ * the boolean true if there are pending sync tasks that aren't yet due
481
+ * null if there's no pending sync tasks (just the next poll)
482
+ */
483
+
484
+ Syncer.prototype.chooseNextTask = function() {
485
+ var pending = this.getPendingTitles();
486
+
487
+ if(pending.savePending.length){
488
+ if(this.syncadaptor.saveTiddlers){
489
+ return new SaveAllTiddlersTask(this, pending.savePending);
490
+ } else {
491
+ return new SaveTiddlerTask(this, pending.savePending[0]);
492
+ }
493
+ }
494
+ if(pending.syncServer){
495
+ return new SyncFromServerTask(this);
496
+ }
497
+ if(pending.deletePending.length){
498
+ if(this.syncadaptor.deleteTiddlers){
499
+ return new DeleteAllTiddlersTask(this, pending.deletePending);
500
+ } else {
501
+ return new DeleteTiddlerTask(this, pending.deletePending[0]);
502
+ }
503
+ }
504
+ if(pending.loadPending.length){
505
+ if(this.syncadaptor.loadTiddlers){
506
+ var self = this;
507
+ pending.loadPending.forEach(function(e){ delete self.titlesToBeLoaded[e]; });
508
+ return new LoadAllTiddlersTask(this, pending.loadPending);
509
+ } else {
510
+ delete this.titlesToBeLoaded[pending.loadPending[0]];
511
+ return new LoadTiddlerTask(this, pending.loadPending[0]);
512
+ }
513
+ }
514
+ return pending.havePending;
515
+ };
516
+
517
+ Syncer.prototype.getPendingTitles = function() {
518
+ var now = new Date(),
519
+ thresholdLastSaved = now - this.throttleInterval,
520
+ havePending = null,
521
+ savePending = [],
522
+ syncServer = false,
523
+ deletePending = [],
524
+ loadPending = [];
525
+ // First we look for tiddlers that have been modified locally and need saving back to the server
526
+ var titles = this.getSyncedTiddlers();
527
+ for(var index=0; index<titles.length; index++) {
528
+ var title = titles[index],
529
+ tiddler = this.wiki.tiddlerExists(title) && this.wiki.getTiddler(title),
530
+ tiddlerInfo = this.tiddlerInfo[title];
531
+ if(tiddler) {
532
+ // If the tiddler is not known on the server, or has been modified locally no more recently than the threshold then it needs to be saved to the server
533
+ var hasChanged = !tiddlerInfo || this.wiki.getChangeCount(title) > tiddlerInfo.changeCount,
534
+ isReadyToSave = !tiddlerInfo || !tiddlerInfo.timestampLastSaved || tiddlerInfo.timestampLastSaved < thresholdLastSaved;
535
+ if(hasChanged) {
536
+ if(isReadyToSave) {
537
+ // return new SaveTiddlerTask(this,title);
538
+ savePending.push(title);
539
+ } else {
540
+ havePending = true;
541
+ }
542
+ }
543
+ }
544
+ }
545
+ // Second we check for an outstanding sync from server
546
+ if(this.forceSyncFromServer || (this.timestampLastSyncFromServer && (now.valueOf() >= (this.timestampLastSyncFromServer.valueOf() + this.pollTimerInterval)))) {
547
+ // return new SyncFromServerTask(this);
548
+ syncServer = true;
549
+ }
550
+ // Third, we check tiddlers that are known from the server but not currently in the store, and so need deleting on the server
551
+ titles = Object.keys(this.tiddlerInfo);
552
+ for(index=0; index<titles.length; index++) {
553
+ title = titles[index];
554
+ tiddlerInfo = this.tiddlerInfo[title];
555
+ tiddler = this.wiki.tiddlerExists(title) && this.wiki.getTiddler(title);
556
+ if(!tiddler) {
557
+ // return new DeleteTiddlerTask(this,title);
558
+ deletePending.push(title);
559
+ }
560
+ }
561
+ // Finally, check for tiddlers that need loading
562
+ // title = Object.keys(this.titlesToBeLoaded)[0];
563
+ // if(title) {
564
+ // delete this.titlesToBeLoaded[title];
565
+ // return new LoadTiddlerTask(this,title);
566
+ // }
567
+ loadPending.push(...Object.keys(this.titlesToBeLoaded));
568
+ // No tasks are ready now, but might be in the future
569
+ return {havePending: havePending, loadPending: loadPending, savePending: savePending, deletePending: deletePending, syncServer: syncServer};
570
+ };
571
+
572
+ function SaveTiddlerTask(syncer,title) {
573
+ this.syncer = syncer;
574
+ this.title = title;
575
+ this.type = "save";
576
+ }
577
+
578
+ SaveTiddlerTask.prototype.toString = function() {
579
+ return "SAVE " + this.title;
580
+ }
581
+
582
+ SaveTiddlerTask.prototype.run = function(callback) {
583
+ var self = this,
584
+ changeCount = this.syncer.wiki.getChangeCount(this.title),
585
+ tiddler = this.syncer.wiki.tiddlerExists(this.title) && this.syncer.wiki.getTiddler(this.title);
586
+ this.syncer.logger.log("Dispatching 'save' task:",this.title);
587
+ if(tiddler) {
588
+ this.syncer.syncadaptor.saveTiddler(tiddler,function(err,adaptorInfo,revision) {
589
+ // If there's an error, exit without changing any internal state
590
+ if(err) {
591
+ return callback(err);
592
+ }
593
+ // Adjust the info stored about this tiddler
594
+ self.syncer.tiddlerInfo[self.title] = {
595
+ changeCount: changeCount,
596
+ adaptorInfo: adaptorInfo,
597
+ revision: revision,
598
+ timestampLastSaved: new Date()
599
+ };
600
+ // Invoke the callback
601
+ callback(null);
602
+ },{
603
+ tiddlerInfo: self.syncer.tiddlerInfo[self.title]
604
+ });
605
+ } else {
606
+ $tw.utils.nextTick(callback(null));
607
+ }
608
+ };
609
+
610
+ function SaveAllTiddlersTask(syncer,titles) {
611
+ this.syncer = syncer;
612
+ this.titles = titles;
613
+ this.type = "save all";
614
+ }
615
+
616
+ SaveAllTiddlersTask.prototype.toString = function() {
617
+ return "SAVE ALL " + this.titles;
618
+ }
619
+
620
+ SaveAllTiddlersTask.prototype.run = function(callback) {
621
+ var self = this;
622
+ this.syncer.logger.log("Dispatching 'save all' task:",this.titles);
623
+ var changeCounts = this.titles.reduce(function (r,title) { r[title] = self.syncer.wiki.getChangeCount(title); return r; }, {});
624
+ var tiddlers = this.titles
625
+ .map(function(title){return self.syncer.wiki.tiddlerExists(title) && self.syncer.wiki.getTiddler(title);})
626
+ .filter(function(title){return title;});
627
+ if(!tiddlers.length) return void $tw.utils.nextTick(callback(null));
628
+ this.syncer.syncadaptor.saveTiddlers({
629
+ syncer: this.syncer,
630
+ tiddlers: tiddlers,
631
+ onNext: function(title,adaptorInfo,revision){
632
+ self.syncer.tiddlerInfo[title] = {
633
+ changeCount: changeCounts[title],
634
+ adaptorInfo: adaptorInfo,
635
+ revision: revision,
636
+ timestampLastSaved: new Date()
637
+ };
638
+ },
639
+ onDone: function(){callback(null);},
640
+ onError: function(err){callback(err);},
641
+ });
642
+ };
643
+
644
+ function DeleteTiddlerTask(syncer,title) {
645
+ this.syncer = syncer;
646
+ this.title = title;
647
+ this.type = "delete";
648
+ }
649
+
650
+ DeleteTiddlerTask.prototype.toString = function() {
651
+ return "DELETE " + this.title;
652
+ }
653
+
654
+ DeleteTiddlerTask.prototype.run = function(callback) {
655
+ var self = this;
656
+ this.syncer.logger.log("Dispatching 'delete' task:",this.title);
657
+ this.syncer.syncadaptor.deleteTiddler(this.title,function(err) {
658
+ // If there's an error, exit without changing any internal state
659
+ if(err) {
660
+ return callback(err);
661
+ }
662
+ // Remove the info stored about this tiddler
663
+ delete self.syncer.tiddlerInfo[self.title];
664
+ // Invoke the callback
665
+ callback(null);
666
+ },{
667
+ tiddlerInfo: self.syncer.tiddlerInfo[this.title]
668
+ });
669
+ };
670
+
671
+ function DeleteAllTiddlersTask(syncer,titles) {
672
+ this.syncer = syncer;
673
+ this.titles = titles;
674
+ this.type = "delete all";
675
+ }
676
+
677
+ DeleteAllTiddlersTask.prototype.toString = function() {
678
+ return "DELETE ALL " + this.titles;
679
+ }
680
+
681
+ DeleteAllTiddlersTask.prototype.run = function(callback) {
682
+ var self = this;
683
+ this.syncer.logger.log("Dispatching 'delete all' task:",this.titles);
684
+ this.syncer.syncadaptor.deleteTiddlers({
685
+ syncer: this.syncer,
686
+ titles: this.titles,
687
+ onNext: function(title){
688
+ delete self.syncer.tiddlerInfo[title];
689
+ },
690
+ onDone: function(){callback(null);},
691
+ onError: function(err){callback(err);},
692
+ });
693
+ };
694
+
695
+ function LoadTiddlerTask(syncer,title) {
696
+ this.syncer = syncer;
697
+ this.title = title;
698
+ this.type = "load";
699
+ }
700
+
701
+ LoadTiddlerTask.prototype.toString = function() {
702
+ return "LOAD " + this.title;
703
+ }
704
+
705
+ LoadTiddlerTask.prototype.run = function(callback) {
706
+ var self = this;
707
+ this.syncer.logger.log("Dispatching 'load' task:",this.title);
708
+ this.syncer.syncadaptor.loadTiddler(this.title,function(err,tiddlerFields) {
709
+ // If there's an error, exit without changing any internal state
710
+ if(err) {
711
+ return callback(err);
712
+ }
713
+ // Update the info stored about this tiddler
714
+ if(tiddlerFields) {
715
+ self.syncer.storeTiddler(tiddlerFields);
716
+ }
717
+ // Invoke the callback
718
+ callback(null);
719
+ });
720
+ };
721
+
722
+ function LoadAllTiddlersTask(syncer,titles) {
723
+ this.syncer = syncer;
724
+ this.titles = titles;
725
+ this.type = "load all";
726
+ }
727
+
728
+ LoadAllTiddlersTask.prototype.toString = function() {
729
+ return "LOAD ALL" + this.titles;
730
+ }
731
+
732
+ LoadAllTiddlersTask.prototype.run = function(callback) {
733
+ var self = this;
734
+ this.syncer.logger.log("Dispatching 'load all' task:",this.titles);
735
+ this.syncer.syncadaptor.loadTiddlers({
736
+ syncer: this.syncer,
737
+ titles: this.titles,
738
+ onNext: function(tiddlerFields){
739
+ self.syncer.storeTiddler(tiddlerFields);
740
+ },
741
+ onDone: function(){callback(null);},
742
+ onError: function(err){callback(err);},
743
+ });
744
+ };
745
+
746
+ function SyncFromServerTask(syncer) {
747
+ this.syncer = syncer;
748
+ this.type = "syncfromserver";
749
+ }
750
+
751
+ SyncFromServerTask.prototype.toString = function() {
752
+ return "SYNCFROMSERVER";
753
+ }
754
+
755
+ SyncFromServerTask.prototype.run = function(callback) {
756
+ var self = this;
757
+ var syncSystemFromServer = (self.syncer.wiki.getTiddlerText("$:/config/SyncSystemTiddlersFromServer") === "yes" ? true : false);
758
+ var successCallback = function() {
759
+ self.syncer.forceSyncFromServer = false;
760
+ self.syncer.timestampLastSyncFromServer = new Date();
761
+ callback(null);
762
+ };
763
+ if(this.syncer.syncadaptor.getUpdatedTiddlers) {
764
+ this.syncer.syncadaptor.getUpdatedTiddlers(self.syncer,function(err,updates) {
765
+ if(err) {
766
+ self.syncer.displayError($tw.language.getString("Error/RetrievingSkinny"),err);
767
+ return callback(err);
768
+ }
769
+ if(updates) {
770
+ $tw.utils.each(updates.modifications,function(title) {
771
+ self.syncer.titlesToBeLoaded[title] = true;
772
+ });
773
+ $tw.utils.each(updates.deletions,function(title) {
774
+ if(syncSystemFromServer || !self.syncer.wiki.isSystemTiddler(title)) {
775
+ delete self.syncer.tiddlerInfo[title];
776
+ self.syncer.logger.log("Deleting tiddler missing from server:",title);
777
+ self.syncer.wiki.deleteTiddler(title);
778
+ }
779
+ });
780
+ }
781
+ return successCallback();
782
+ });
783
+ } else if(this.syncer.syncadaptor.getSkinnyTiddlers) {
784
+ this.syncer.syncadaptor.getSkinnyTiddlers(function(err,tiddlers) {
785
+ // Check for errors
786
+ if(err) {
787
+ self.syncer.displayError($tw.language.getString("Error/RetrievingSkinny"),err);
788
+ return callback(err);
789
+ }
790
+ // Keep track of which tiddlers we already know about have been reported this time
791
+ var previousTitles = Object.keys(self.syncer.tiddlerInfo);
792
+ // Process each incoming tiddler
793
+ for(var t=0; t<tiddlers.length; t++) {
794
+ // Get the incoming tiddler fields, and the existing tiddler
795
+ var tiddlerFields = tiddlers[t],
796
+ incomingRevision = tiddlerFields.revision + "",
797
+ tiddler = self.syncer.wiki.tiddlerExists(tiddlerFields.title) && self.syncer.wiki.getTiddler(tiddlerFields.title),
798
+ tiddlerInfo = self.syncer.tiddlerInfo[tiddlerFields.title],
799
+ currRevision = tiddlerInfo ? tiddlerInfo.revision : null,
800
+ indexInPreviousTitles = previousTitles.indexOf(tiddlerFields.title);
801
+ if(indexInPreviousTitles !== -1) {
802
+ previousTitles.splice(indexInPreviousTitles,1);
803
+ }
804
+ // Ignore the incoming tiddler if it's the same as the revision we've already got
805
+ if(currRevision !== incomingRevision) {
806
+ // Only load the skinny version if we don't already have a fat version of the tiddler
807
+ if(!tiddler || tiddler.fields.text === undefined) {
808
+ self.syncer.storeTiddler(tiddlerFields);
809
+ }
810
+ // Do a full load of this tiddler
811
+ self.syncer.titlesToBeLoaded[tiddlerFields.title] = true;
812
+ }
813
+ }
814
+ // Delete any tiddlers that were previously reported but missing this time
815
+ $tw.utils.each(previousTitles,function(title) {
816
+ if(syncSystemFromServer || !self.syncer.wiki.isSystemTiddler(title)) {
817
+ delete self.syncer.tiddlerInfo[title];
818
+ self.syncer.logger.log("Deleting tiddler missing from server:",title);
819
+ self.syncer.wiki.deleteTiddler(title);
820
+ }
821
+ });
822
+ self.syncer.forceSyncFromServer = false;
823
+ self.syncer.timestampLastSyncFromServer = new Date();
824
+ return successCallback();
825
+ });
826
+ } else {
827
+ return successCallback();
828
+ }
829
+ };
830
+
831
+ exports.Syncer = Syncer;