community_engine 2.0.0.beta2 → 2.0.0.beta3

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 (64) hide show
  1. data/Gemfile +4 -0
  2. data/README.markdown +3 -3
  3. data/about.yml +1 -1
  4. data/app/assets/javascripts/tinymce/plugins/tinyautosave/editor_plugin.js +8 -0
  5. data/app/assets/javascripts/tinymce/plugins/tinyautosave/editor_plugin_src.js +1001 -0
  6. data/app/assets/javascripts/tinymce/plugins/tinyautosave/images/progress.gif +0 -0
  7. data/app/assets/javascripts/tinymce/plugins/tinyautosave/images/progress10.gif +0 -0
  8. data/app/assets/javascripts/tinymce/plugins/tinyautosave/images/progress11.gif +0 -0
  9. data/app/assets/javascripts/tinymce/plugins/tinyautosave/images/progress12.gif +0 -0
  10. data/app/assets/javascripts/tinymce/plugins/tinyautosave/images/progress2.gif +0 -0
  11. data/app/assets/javascripts/tinymce/plugins/tinyautosave/images/progress3.gif +0 -0
  12. data/app/assets/javascripts/tinymce/plugins/tinyautosave/images/progress4.gif +0 -0
  13. data/app/assets/javascripts/tinymce/plugins/tinyautosave/images/progress5.gif +0 -0
  14. data/app/assets/javascripts/tinymce/plugins/tinyautosave/images/progress6.gif +0 -0
  15. data/app/assets/javascripts/tinymce/plugins/tinyautosave/images/progress7.gif +0 -0
  16. data/app/assets/javascripts/tinymce/plugins/tinyautosave/images/progress8.gif +0 -0
  17. data/app/assets/javascripts/tinymce/plugins/tinyautosave/images/progress9.gif +0 -0
  18. data/app/assets/javascripts/tinymce/plugins/tinyautosave/images/restore.gif +0 -0
  19. data/app/assets/javascripts/tinymce/plugins/tinyautosave/images/restore.png +0 -0
  20. data/app/assets/javascripts/tinymce/plugins/tinyautosave/langs/da.js +5 -0
  21. data/app/assets/javascripts/tinymce/plugins/tinyautosave/langs/en.js +5 -0
  22. data/app/assets/{javascripts → stylesheets}/cropper.css +0 -0
  23. data/app/controllers/comments_controller.rb +1 -1
  24. data/app/controllers/forums_controller.rb +3 -6
  25. data/app/controllers/moderators_controller.rb +2 -5
  26. data/app/controllers/sb_posts_controller.rb +1 -11
  27. data/app/controllers/tags_controller.rb +1 -2
  28. data/app/controllers/topics_controller.rb +1 -2
  29. data/app/helpers/base_helper.rb +1 -1
  30. data/app/models/photo.rb +5 -5
  31. data/app/models/tag.rb +36 -0
  32. data/app/models/user.rb +2 -2
  33. data/app/views/activities/index.html.haml +1 -1
  34. data/app/views/admin/events.html.haml +1 -1
  35. data/app/views/photos/edit.html.haml +1 -1
  36. data/app/views/photos/show.html.haml +2 -2
  37. data/app/views/posts/show.html.haml +1 -0
  38. data/app/views/sb_posts/index.html.haml +10 -5
  39. data/app/views/shared/_footer_content.html.haml +1 -1
  40. data/app/views/tags/index.html.haml +1 -1
  41. data/app/views/tags/manage.html.haml +5 -3
  42. data/app/views/topics/show.html.haml +6 -1
  43. data/app/views/users/crop_profile_photo.html.haml +1 -0
  44. data/community_engine.gemspec +3 -5
  45. data/config/initializers/recaptcha_constants.rb +2 -2
  46. data/config/locales/en.yml +1 -1
  47. data/config/locales/ru-RU.yml +1656 -54
  48. data/config/locales/sv-SE.yml +212 -4
  49. data/config/routes.rb +1 -1
  50. data/lib/community_engine.rb +3 -2
  51. data/lib/community_engine/engine.rb +8 -6
  52. data/lib/community_engine/version.rb +1 -1
  53. data/test/fixtures/taggings.yml +11 -1
  54. data/test/fixtures/tags.yml +9 -1
  55. data/test/functional/admin_controller_test.rb +8 -0
  56. data/test/functional/forums_controller_test.rb +1 -1
  57. data/test/functional/sb_posts_controller_test.rb +15 -19
  58. data/test/functional/tags_controller_test.rb +21 -1
  59. data/test/test_helper.rb +1 -1
  60. data/test/testapp/db/schema.rb +1 -0
  61. data/test/unit/tag_test.rb +2 -2
  62. metadata +75 -69
  63. data/app/views/sb_posts/index.xml.builder +0 -20
  64. data/lib/tag_hacks.rb +0 -24
data/Gemfile CHANGED
@@ -1,3 +1,7 @@
1
1
  source 'http://rubygems.org'
2
2
 
3
+ group :test do
4
+ gem 'sqlite3'
5
+ end
6
+
3
7
  gemspec
data/README.markdown CHANGED
@@ -6,14 +6,14 @@ Information at: [http://www.communityengine.org](http://www.communityengine.org)
6
6
 
7
7
  Requirements:
8
8
 
9
- - RAILS VERSION 3.1.2
9
+ - RAILS VERSION 3.1.3
10
10
 
11
11
  Getting CommunityEngine Running
12
12
  --------------------------------
13
13
 
14
14
  1. Copy the following into your `Gemfile`:
15
15
 
16
- gem 'community_engine', '2.0.0.beta1'
16
+ gem 'community_engine'
17
17
 
18
18
  2. From your app's root directory run:
19
19
 
@@ -101,7 +101,7 @@ For complex strings with substitutions, Symbols respond to the `.l` method with
101
101
 
102
102
  And in your language file you'd have:
103
103
 
104
- welcome: "Welcome {{name}}"
104
+ welcome: "Welcome %{name}"
105
105
 
106
106
  To customize the language, or add a new language create a new yaml file in `Rails.root/config/locales`. The name of the file should be `LANG-LOCALE.yml` (`e.g. en-US.yml` or `es-PR`). The language only file (`es.yml`) will support all locales.
107
107
 
data/about.yml CHANGED
@@ -4,4 +4,4 @@ homepage: http://www.missingmethod.com
4
4
  summary: A social networking engine
5
5
  description: Adds basic social networking capabilities to your existing application, including users, blogs, photos, clippings, favorites, and more.
6
6
  license: MIT
7
- version: 2.0.0.beta
7
+ version: 2.0.0.beta3
@@ -0,0 +1,8 @@
1
+ /*
2
+ TinyAutoSave 2.1.3 March 19, 2011) plugin for TinyMCE
3
+ http://tinyautosave.googlecode.com/
4
+ Copyright (c) 2008-2011 Todd Northrop
5
+ http://www.speednet.biz/
6
+ Dual licensed under the MIT or GPL Version 2 licenses.
7
+ */
8
+ (function(){var p="function",e="string",i="unload",d=true,l="OK",s="TinyAutoSave",b=null,a=false,L="2.1.3",c="tinyautosave",h=a,o=a,m=a,D={"%":"%1","&":"%2",";":"%3","=":"%4","<":"%5"},C={"%1":"%","%2":"&","%3":";","%4":"=","%5":"<"},t=[],q={},u={},n="TinyAutoSave_Test_",g=b,r={dataKey:s,cookieFilter:b,saveDelegate:b,saveFinalDelegate:b,restoreDelegate:b,disposeDelegate:b,restoreImage:"",progressImage:"progress.gif",intervalSeconds:60,retentionMinutes:20,minSaveLength:50,askBeforeUnload:a,canRestore:a,busy:a,timer:b};try{localStorage.setItem(n,l);if(localStorage.getItem(n)===l){localStorage.removeItem(n);h=d}}catch(M){try{sessionStorage.setItem(n,l);if(sessionStorage.getItem(n)===l){sessionStorage.removeItem(n);o=d}}catch(M){m=tinymce.isIE}}tinymce.PluginManager.requireLangPack(c);tinymce.create("tinymce.plugins.TinyAutoSavePlugin",{editor:b,url:"",key:"",onPreSave:b,onPostSave:b,onSaveError:b,onPreRestore:b,onPostRestore:b,onRestoreError:b,showSaveProgress:d,progressDisplayTime:1200,init:function(d,l){var h="mceTinyAutoSaveRestore",f=this,o=tinymce.is,q=tinymce.resolve,a,k,n;f.editor=d;f.id=d.id;f.url=l;f.key=d.getParam(c+"_key",d.id);a=B(f);a.restoreImage=l+"/images/restore."+(tinymce.isIE6?"gif":"png");f.setProgressImage(l+"/images/"+r.progressImage);a.intervalSeconds=Math.max(1,parseInt(d.getParam(c+"_interval_seconds",b)||d.getParam(c+"_interval",a.intervalSeconds)));a.retentionMinutes=Math.max(1,parseInt(d.getParam(c+"_retention_minutes",b)||d.getParam(c+"_retention",a.retentionMinutes)));a.minSaveLength=Math.max(1,parseInt(d.getParam(c+"_minlength",a.minSaveLength)));f.showSaveProgress=d.getParam(c+"_showsaveprogress",f.showSaveProgress);a.askBeforeUnload=d.getParam(c+"_ask_beforeunload",a.askBeforeUnload);a.saveDelegate=j(f,z);a.saveFinalDelegate=j(f,x);a.restoreDelegate=j(f,y);d.addCommand("mceTinyAutoSave",a.saveDelegate);d.addCommand(h,a.restoreDelegate);d.addButton(c,{title:c+".restore_content",cmd:h,image:a.restoreImage});a.timer=window.setInterval(a.saveDelegate,a.intervalSeconds*1e3);tinymce.dom.Event.add(window,i,a.saveFinalDelegate);d.onRemove.add(a.saveFinalDelegate);a.askBeforeUnload&&tinymce.dom.Event.add(window,i,tinymce.plugins.AutoSavePlugin._beforeUnloadHandler);d.onInit.add(function(){if(m){if(!g)g=d.getElement();g.style.behavior="url('#default#userData')"}a.canRestore=f.hasSavedContent();k=d.getParam(c+"_oninit",b);if(o(k,e)){n=q(k);o(n,p)&&n.apply(f)}d.controlManager.setDisabled(c,!a.canRestore)})},getInfo:function(){return{longname:s,author:"Speednet",authorurl:"http://www.speednet.biz/",infourl:"http://tinyautosave.googlecode.com/",version:L}},clear:function(){var d=this,e=d.editor,b=f(d);if(h)localStorage.removeItem(b.dataKey);else if(o)sessionStorage.removeItem(b.dataKey);else if(m)E(d);else tinymce.util.Cookie.remove(b.dataKey);b.canRestore=a;e.controlManager.setDisabled(c,d)},hasSavedContent:function(){var g=this,b=f(g),i=new Date,c,e;try{if(h||o){c=((h?localStorage.getItem(b.dataKey):sessionStorage.getItem(b.dataKey))||"").toString(),e=c.indexOf(",");if(e>8&&e<c.length-1){if(new Date(c.slice(0,e))>i)return d;if(h)localStorage.removeItem(b.dataKey);else sessionStorage.removeItem(b.dataKey)}return a}else if(m)return(w(g)||"").length>0;return(tinymce.util.Cookie.get(b.dataKey)||"").length>0}catch(j){return a}},setProgressImage:function(a){tinymce.is(a,e)&&I(f(this).progressImage=a)},"static":{_beforeUnloadHandler:function(){var b;tinymce.each(tinyMCE.editors,function(c){if(c.getParam("fullscreen_is_enabled"))return;if(c.isDirty()){b=c.getLang("autosave.unload_msg");return a}});return b}}});function K(){var b=this,a=f(b);a.timer&&window.clearInterval(a.timer);tinymce.dom.Event.remove(window,i,a.saveFinalDelegate);a.askBeforeUnload&&tinymce.dom.Event.remove(window,i,tinymce.plugins.AutoSavePlugin._beforeUnloadHandler);b.editor.onRemove.remove(a.saveFinalDelegate);A(b)}function k(a){if(!a)return d;var c,b,f=tinymce.is;if(f(a,e)){c=u[a];if(c)b=c[a];else u[a]=b=tinymce.resolve(a)}else if(f(a,p))b=a;else return d;return b.apply(this)}function x(){var a=f(this);a.saveDelegate();a.disposeDelegate()}function z(){var g=this,q=g.editor,b=f(g),u=tinymce.is,n=a,t=new Date,i,l,r,j,p,s;if(q&&!b.busy){b.busy=d;i=q.getContent();if(u(i,e)&&i.length>=b.minSaveLength){if(!k.call(g,g.onPreSave)){b.busy=a;return a}l=new Date(t.getTime()+b.retentionMinutes*6e4);try{if(h)localStorage.setItem(b.dataKey,l.toString()+","+v(i));else if(o)sessionStorage.setItem(b.dataKey,l.toString()+","+v(i));else if(m)J(g,i,l);else{r=b.dataKey+"=";j="; expires="+l.toUTCString();document.cookie=r+H(i).slice(0,4096-r.length-j.length)+j}n=d}catch(w){k.call(g,g.onSaveError)}if(n){p=q.controlManager;b.canRestore=d;p.setDisabled(c,a);if(g.showSaveProgress){j=tinymce.DOM.get(p.get(c).id);if(j){s=b.restoreImage;j.firstChild.src=b.progressImage;window.setTimeout(function(){j.firstChild.src=s},Math.min(g.progressDisplayTime,b.intervalSeconds*1e3-100))}}k.call(g,g.onPostSave)}}b.busy=a}return n}function y(){var g=this,l=g.editor,j=f(g),i=b,q=tinymce.is,n,p;if(l&&j.canRestore&&!j.busy){j.busy=d;if(!k.call(g,g.onPreRestore)){j.busy=a;return}try{if(h||o){i=((h?localStorage.getItem(j.dataKey):sessionStorage.getItem(j.dataKey))||"").toString();n=i.indexOf(",");if(n==-1)i=b;else i=F(i.slice(n+1,i.length))}else if(m)i=w(g);else{p=j.cookieFilter.exec(document.cookie);if(p)i=G(p[1])}if(!q(i,e))l.windowManager.alert(c+".no_content");else if(l.getContent().replace(/\s|&nbsp;|<\/?p[^>]*>|<br[^>]*>/gi,"").length===0){l.setContent(i);k.call(g,g.onPostRestore)}else l.windowManager.confirm(c+".warning_message",function(b){if(b){l.setContent(i);k.call(g,g.onPostRestore)}j.busy=a},g)}catch(r){k.call(g,g.onRestoreError)}j.busy=a}}function J(a,c,b){g.setAttribute(f(a).dataKey,c);g.expires=b.toUTCString();g.save("TinyMCE")}function w(a){g.load("TinyMCE");return g.getAttribute(f(a).dataKey)}function E(a){g.removeAttribute(f(a).dataKey)}function H(a){return a.replace(/[\x00-\x1f]+|&nbsp;|&#160;/gi," ").replace(/(.)\1{5,}|[%&;=<]/g,function(a){return a.length>1?"%0"+a.charAt(0)+a.length.toString()+"%":D[a]})}function G(a){return a.replace(/%[1-5]|%0(.)(\d+)%/g,function(c,f,e){var a,b,d;if(c.length==2)return C[c];for(a=[],b=0,d=parseInt(e);b<d;b++)a.push(f);return a.join("")})}function v(a){return a.replace(/,/g,"&#44;")}function F(a){return a.replace(/&#44;/g,",")}function I(b){var a=t.length;t[a]=new Image;t[a].src=b}function j(b,a){return function(){return a.apply(b)}}function B(a){var b=a.key,c=q[b];if(!c)c=q[b]=tinymce.extend({},r,{dataKey:r.dataKey+b,saveDelegate:j(a,z),saveFinalDelegate:j(a,x),restoreDelegate:j(a,y),disposeDelegate:j(a,K),cookieFilter:new RegExp("(?:^|;\\s*)"+r.dataKey+b+"=([^;]*)(?:;|$)","i")});return c}function f(a){return q[a.key]}function A(a){delete q[a.key]}tinymce.PluginManager.add(c,tinymce.plugins.TinyAutoSavePlugin)})();
@@ -0,0 +1,1001 @@
1
+ /*
2
+ TinyAutoSave plugin for TinyMCE
3
+ Version: 2.1.3
4
+ http://tinyautosave.googlecode.com/
5
+
6
+ Copyright (c) 2008-2011 Todd Northrop
7
+ http://www.speednet.biz/
8
+
9
+ March 19, 2011
10
+
11
+ Adds auto-save capability to the TinyMCE text editor to rescue content
12
+ inadvertently lost.
13
+
14
+ Dual licensed under the MIT or GPL Version 2 licenses.
15
+ See mit-license.txt and gpl2-license.txt in the project root for details.
16
+ */
17
+
18
+
19
+ // Wrap all code in function to create true private scope and prevent
20
+ // pollution of global namespace
21
+
22
+ (function() {
23
+
24
+
25
+ //************************************************************************
26
+ // PRIVATE VARIABLES
27
+
28
+ var version = "2.1.3",
29
+
30
+ // The name of the plugin, as specified to TinyMCE
31
+ pluginName = "tinyautosave",
32
+
33
+ // Specifies if localStorage (HTML 5) is available
34
+ useLocalStorage = false,
35
+
36
+ // Specifies if sessionStorage (HTML 5) is available
37
+ useSessionStorage = false,
38
+
39
+ // Specifies if UserData (IE client storage) is available
40
+ useUserData = false,
41
+
42
+ // Translation keys for encoding/decoding cookie values
43
+ cookieEncodeKey = {"%": "%1", "&": "%2", ";": "%3", "=": "%4", "<": "%5"},
44
+ cookieDecodeKey = {"%1": "%", "%2": "&", "%3": ";", "%4": "=", "%5": "<"},
45
+
46
+ // Internal storage for preloaded images
47
+ preloadImages = [],
48
+
49
+ // Internal storage of settings for each plugin instance
50
+ instanceSettings = {},
51
+
52
+ // Cached storage of callback function resolution, for performance
53
+ callbackLookup = {},
54
+
55
+ // Unique key used to test if HTML 5 storage methods are available
56
+ testKey = "TinyAutoSave_Test_",
57
+
58
+ // The HTML element that IE's UserData will be attached to
59
+ userDataElement = null,
60
+
61
+ // Default settings for each plugin instance
62
+ settingsTemplate = {
63
+ dataKey: "TinyAutoSave",
64
+ cookieFilter: null,
65
+ saveDelegate: null,
66
+ saveFinalDelegate: null,
67
+ restoreDelegate: null,
68
+ disposeDelegate: null,
69
+ restoreImage: "",
70
+ progressImage: "progress.gif",
71
+ intervalSeconds: 60,
72
+ retentionMinutes: 20,
73
+ minSaveLength: 50,
74
+ askBeforeUnload: false,
75
+ canRestore: false,
76
+ busy: false,
77
+ timer: null
78
+ };
79
+
80
+ //************************************************************************
81
+ // TEST STORAGE METHODS
82
+ // Determine best storage method by storing and retrieving test data
83
+
84
+ try {
85
+ localStorage.setItem(testKey, "OK");
86
+
87
+ if (localStorage.getItem(testKey) === "OK") {
88
+ localStorage.removeItem(testKey);
89
+ useLocalStorage = true;
90
+ }
91
+ }
92
+ catch (e) {
93
+
94
+ try {
95
+ sessionStorage.setItem(testKey, "OK");
96
+
97
+ if (sessionStorage.getItem(testKey) === "OK") {
98
+ sessionStorage.removeItem(testKey);
99
+ useSessionStorage = true;
100
+ }
101
+ }
102
+ catch (e) {
103
+ useUserData = tinymce.isIE;
104
+ }
105
+ }
106
+
107
+
108
+ //************************************************************************
109
+ // TINYMCE INTEROP
110
+
111
+ tinymce.PluginManager.requireLangPack(pluginName);
112
+
113
+ tinymce.create("tinymce.plugins.TinyAutoSavePlugin", {
114
+ /// <summary>
115
+ /// Automatically saves the editor contents periodically and just before leaving the current page.
116
+ /// Allows the user to rescue the contents of the last autosave, in case they did not intend to
117
+ /// navigate away from the current page or the browser window was closed before posting the content.
118
+ /// </summary>
119
+ /// <field name="editor" type="Object" mayBeNull="false">
120
+ /// A reference to the TinyMCE editor instance that contains this TinyAutoSave plugin instance.
121
+ /// </field>
122
+ /// <field name="url" type="String" mayBeNull="false">
123
+ /// The URL of the folder containing the TinyAutoSave plugin. Does not include a trailing slash.
124
+ /// </field>
125
+ /// <field name="key" type="String" mayBeNull="false">
126
+ /// A string value identifying the storage and settings for the plugin, as set by tinyautosave_key.
127
+ /// </field>
128
+ /// <field name="onPreSave" type="String or Function" mayBeNull="false">
129
+ /// (String) Name of a callback function that gets called before each auto-save is performed.
130
+ /// (Function) A function that gets called before each auto-save is performed.
131
+ /// The callback function must return a Boolean value of true if the auto-save is to proceed
132
+ /// normally, or false if the auto-save is to be canceled. The editor instance is the context of the
133
+ /// callback (assigned to 'this').
134
+ /// </field>
135
+ /// <field name="onPostSave" type="String or Function" mayBeNull="false">
136
+ /// (String) Name of a callback function that gets called after each auto-save is performed.
137
+ /// (Function) A function that gets called after each auto-save is performed.
138
+ /// Any return value from the callback function is ignored. The editor instance is the context of
139
+ /// the callback (assigned to 'this').
140
+ /// </field>
141
+ /// <field name="onSaveError" type="String or Function" mayBeNull="false">
142
+ /// (String) Name of a callback function that gets called each time an auto-save fails in an error condition.
143
+ /// (Function) A function that gets called each time an auto-save fails in an error condition.
144
+ /// The editor instance is the context of the callback (assigned to 'this').
145
+ /// </field>
146
+ /// <field name="onPreRestore" type="String or Function" mayBeNull="false">
147
+ /// (String) Name of a callback function that gets called before a restore request is performed.
148
+ /// (Function) A function that gets called before a restore request is performed.
149
+ /// The callback function must return a Boolean value of true if the restore is to proceed normally,
150
+ /// or false if the restore is to be canceled. The editor instance is the context of the callback
151
+ /// (assigned to 'this').
152
+ /// </field>
153
+ /// <field name="onPostRestore" type="String or Function" mayBeNull="false">
154
+ /// (String) Name of a callback function that gets called after a restore request is performed.
155
+ /// (Function) A function that gets called after a restore request is performed.
156
+ /// Any return value from the callback function is ignored. The editor instance is the context of
157
+ /// the callback (assigned to 'this').
158
+ /// </field>
159
+ /// <field name="onRestoreError" type="String or Function" mayBeNull="false">
160
+ /// (String) Name of a callback function that gets called each time a restore request fails in an error condition.
161
+ /// (Function) A function that gets called each time a restore request fails in an error condition.
162
+ /// The editor instance is the context of the callback (assigned to 'this').
163
+ /// </field>
164
+ /// <field name="progressDisplayTime" type="Number" integer="true" mayBeNull="false">
165
+ /// Number of milliseconds that the progress image is displayed after an auto-save. The default is
166
+ /// 1200, which is the equivalent of 1.2 seconds.
167
+ /// </field>
168
+ /// <field name="showSaveProgress" type="Boolean" mayBeNull="false">
169
+ /// Receives the Boolean value specified in the tinyautosave_showsaveprogress configuration option,
170
+ /// or true if none is specified. This is a public read/write property, and the behavior of the
171
+ /// toolbar button throbber/progress can be altered dynamically by changing this property.
172
+ /// </field>
173
+ /// <remarks>
174
+ ///
175
+ /// CONFIGURATION OPTIONS:
176
+ ///
177
+ /// tinyautosave_key - (String, default = editor id) A string value used to identify the autosave
178
+ /// storage and settings to use for the plug instance. If tinyautosave_key is not specified, then
179
+ /// the editor's id property is used. If you set the tinyautosave_key for all editors to the same value,
180
+ /// that would create a single autosave storage instance and a single set of autosave settings to use
181
+ /// with all editors. Because each key maintains its own plugin settings, tinyautosave_key can also be
182
+ /// used to apply a different UI or behavior to individual editors. For example, two editors on the same
183
+ /// page could use different progress images, or they could autosave at different intervals.
184
+ ///
185
+ /// tinyautosave_interval_seconds - (Number, default = 60) The number of seconds between automatic saves.
186
+ /// When the editor is first displayed, an autosave will not occur for at least this amount of time.
187
+ ///
188
+ /// tinyautosave_minlength - (Number, default = 50) The minimum number of characters that must be in the
189
+ /// editor before an autosave will occur. The character count includes all non-visible characters,
190
+ /// such as HTML tags. Although this can be set to 0 (zero), it is not recommended. Doing so would
191
+ /// open the possibility that if the user accidentally refreshes the page, the empty editor contents
192
+ /// would overwrite the rescue content, effectively defeating the purpose of the plugin.
193
+ ///
194
+ /// tinyautosave_retention_minutes - (Number, default = 20) The number of minutes since the last autosave
195
+ /// that content will remain in the rescue storage space before it is automatically expired.
196
+ ///
197
+ /// tinyautosave_oninit - (String, default = null) The name of a function to call immediately after the
198
+ /// TinyAutoSave plugin instance is initialized. Can include dot-notation, e.g., "myObject.myFunction".
199
+ /// The context of the function call (the value of 'this') is the plugin instance. This function is
200
+ /// a good place to set any of the public properties that you want to configure.
201
+ ///
202
+ /// tinyautosave_showsaveprogress - (Boolean, default = true) When true, the toolbar button will show a
203
+ /// brief animation every time an autosave occurs.
204
+ ///
205
+ /// COMMANDS:
206
+ ///
207
+ /// Available TinyMCE commands are:
208
+ /// mceTinyAutoSave - Perform an auto-save
209
+ /// mceTinyAutoSaveRestore - Restore auto-saved content into the editor
210
+ ///
211
+ /// PUBLIC PROPERTIES:
212
+ ///
213
+ /// Available public properties of the TinyAutoSave plugin are:
214
+ /// editor (Object)
215
+ /// url (String)
216
+ /// key (String)
217
+ /// onPreSave (String)
218
+ /// onPostSave (String)
219
+ /// onSaveError (String)
220
+ /// onPreRestore (String)
221
+ /// onPostRestore (String)
222
+ /// onRestoreError (String)
223
+ /// progressDisplayTime (Number)
224
+ /// showSaveProgress (Boolean)
225
+ ///
226
+ /// See [field] definitions above for detailed descriptions of the public properties.
227
+ ///
228
+ /// PUBLIC METHODS:
229
+ ///
230
+ /// Available public methods of the TinyAutoSave plugin are:
231
+ /// init() - [Called by TinyMCE]
232
+ /// getInfo() - [Called by TinyMCE]
233
+ /// clear() - Clears any auto-saved content currently stored, and "dims" the Restore toolbar button.
234
+ /// hasSavedContent() - Returns true if there is auto-save content available to be restored, or false if not.
235
+ /// setProgressImage() - Sets the URL of the image that will be displayed every time an auto-save occurs.
236
+ ///
237
+ /// TECHNOLOGY DISCUSSION:
238
+ ///
239
+ /// The plugin attempts to use the most advanced features available in the current browser to save
240
+ /// as much content as possible. There are a total of four different methods used to autosave the
241
+ /// content. In order of preference, they are:
242
+ ///
243
+ /// 1. localStorage - A new feature of HTML 5, localStorage can store megabytes of data per domain
244
+ /// on the client computer. Data stored in the localStorage area has no expiration date, so we must
245
+ /// manage expiring the data ourselves. localStorage is fully supported by IE8, and it is supposed
246
+ /// to be working in Firefox 3 and Safari 3.2, but in reality is is flaky in those browsers. As
247
+ /// HTML 5 gets wider support, the TinyAutoSave plugin will use it automatically. In Windows Vista/7,
248
+ /// localStorage is stored in the following folder:
249
+ /// C:\Users\[username]\AppData\Local\Microsoft\Internet Explorer\DOMStore\[tempFolder]
250
+ ///
251
+ /// 2. sessionStorage - A new feature of HTML 5, sessionStorage works similarly to localStorage,
252
+ /// except it is designed to expire after a certain amount of time. Because the specification
253
+ /// around expiration date/time is very loosely-described, it is preferrable to use locaStorage and
254
+ /// manage the expiration ourselves. sessionStorage has similar storage characteristics to
255
+ /// localStorage, although it seems to have better support by Firefox 3 at the moment. (That will
256
+ /// certainly change as Firefox continues getting better at HTML 5 adoption.)
257
+ ///
258
+ /// 3. UserData - A very under-exploited feature of Microsoft Internet Explorer, UserData is a
259
+ /// way to store up to 128K of data per "document", or up to 1MB of data per domain, on the client
260
+ /// computer. The feature is available for IE 5+, which makes it available for every version of IE
261
+ /// supported by TinyMCE. The content is persistent across browser restarts and expires on the
262
+ /// date/time specified, just like a cookie. However, the data is not cleared when the user clears
263
+ /// cookies on the browser, which makes it well-suited for rescuing autosaved content. UserData,
264
+ /// like other Microsoft IE browser technologies, is implemented as a behavior attached to a
265
+ /// specific DOM object, so in this case we attach the behavior to the same DOM element that the
266
+ /// TinyMCE editor instance is attached to.
267
+ ///
268
+ /// 4. Cookies - When none of the above methods is available, the autosave content is stored in a
269
+ /// cookie. This limits the total saved content to around 4,000 characters, but we use every bit
270
+ /// of that space as we can. To maximize space utilization, before saving the content, we remove
271
+ /// all newlines and other control characters less than ASCII code 32, change &nbsp; instances to
272
+ /// a regular space character, and do some minor compression techniques. (TO-DO: add more
273
+ /// compressiion techniques.) Unfortunately, because the data is stored in a cookie, we have to
274
+ /// waste some space encoding certain characters to avoid server warnings about dangerous content
275
+ /// (as well as overcoming some browser bugs in Safari). Instead of using the built-in escape()
276
+ /// function, we do a proprietary encoding that only encodes the bare minimum characters, and uses
277
+ /// only two bytes per encoded character, rather than 3 bytes like escape() does. escape() encodes
278
+ /// most non-alpha-numeric characters because it is designed for encoding URLs, not for encoding
279
+ /// cookies. It is a huge space-waster in cookies, and in this case would have cut the amount
280
+ /// of autosaved content by at least half.
281
+ ///
282
+ /// </remarks>
283
+
284
+
285
+ //************************************************************************
286
+ // PUBLIC PROPERTIES
287
+
288
+ editor: null,
289
+ url: "",
290
+ key: "",
291
+ onPreSave: null,
292
+ onPostSave: null,
293
+ onSaveError: null,
294
+ onPreRestore: null,
295
+ onPostRestore: null,
296
+ onRestoreError: null,
297
+ showSaveProgress: true,
298
+ progressDisplayTime: 1200, // Milliseconds
299
+
300
+
301
+ //************************************************************************
302
+ // PUBLIC METHODS
303
+
304
+ init: function (ed, url) {
305
+ /// <summary>
306
+ /// Initialization function called by TinyMCE.
307
+ /// </summary>
308
+
309
+ var t = this,
310
+ is = tinymce.is,
311
+ resolve = tinymce.resolve,
312
+ s, onInit, f;
313
+
314
+ t.editor = ed;
315
+ t.id = ed.id;
316
+ t.url = url;
317
+ t.key = ed.getParam(pluginName + "_key", ed.id);
318
+
319
+ s = newInstanceSettings(t);
320
+ s.restoreImage = url + "/images/restore." + (tinymce.isIE6? "gif" : "png");
321
+ t.setProgressImage(url + "/images/" + settingsTemplate.progressImage);
322
+
323
+ // Get the auto-save interval from the TinyMCE config. (i.e., auto-save every 'x' seconds.)
324
+ // Integer value. If not specified in config, default is 60 seconds; minimum is 1 second.
325
+ // Either 'tinyautosave_interval_seconds' or 'tinyautosave_interval' can be used, but 'tinyautosave_interval_seconds' provides better clarity.
326
+ s.intervalSeconds = Math.max(1, parseInt(ed.getParam(pluginName + "_interval_seconds", null) || ed.getParam(pluginName + "_interval", s.intervalSeconds)));
327
+
328
+ // Get the rescue content retention time from the TinyMCE config. (i.e., rescue content available for 'x' minutes after navigating from page.)
329
+ // Integer value. If not specified in config, default is 20 minutes; minimum is 1 minute.
330
+ // Don't make this too long; users will get weirded out if content from long ago is still hanging around.
331
+ // Either 'tinyautosave_retention_minutes' or 'tinyautosave_retention' can be used, but 'tinyautosave_retention_minutes' provides better clarity.
332
+ s.retentionMinutes = Math.max(1, parseInt(ed.getParam(pluginName + "_retention_minutes", null) || ed.getParam(pluginName + "_retention", s.retentionMinutes)));
333
+
334
+ // Get the minimum content length from the TinyMCE config. (i.e., minimum number of characters in the editor before an auto-save can occur.)
335
+ // Integer value. If not specified in config, default is 50 characters; minimum is 1 character.
336
+ // Prevents situation where user accidentally hits Refresh, then their rescue content is wiped out when the editor auto-saves the blank editor on the refreshed page. No need to auto-save a few characters.
337
+ // Specified as 'tinyautosave_minlength' in the config.
338
+ s.minSaveLength = Math.max(1, parseInt(ed.getParam(pluginName + "_minlength", s.minSaveLength)));
339
+
340
+ // Determine if progress animation should occur by reading TinyMCE config.
341
+ // Boolean value. If not specified in config, default is true, progress animation will be displayed after each auto-save.
342
+ // Specified as 'tinyautosave_showsaveprogress' in the config.
343
+ t.showSaveProgress = ed.getParam(pluginName + "_showsaveprogress", t.showSaveProgress);
344
+ s.askBeforeUnload = ed.getParam(pluginName + "_ask_beforeunload", s.askBeforeUnload);
345
+
346
+ // Save action delegates with context
347
+ s.saveDelegate = createDelegate(t, save);
348
+ s.saveFinalDelegate = createDelegate(t, saveFinal);
349
+ s.restoreDelegate = createDelegate(t, restore);
350
+
351
+ // Register commands
352
+ ed.addCommand("mceTinyAutoSave", s.saveDelegate);
353
+ ed.addCommand("mceTinyAutoSaveRestore", s.restoreDelegate);
354
+
355
+ // Register restore button
356
+ ed.addButton(pluginName, {
357
+ title: pluginName + ".restore_content",
358
+ cmd: "mceTinyAutoSaveRestore",
359
+ image: s.restoreImage
360
+ });
361
+
362
+ // Set save interval
363
+ s.timer = window.setInterval(s.saveDelegate, s.intervalSeconds * 1000);
364
+
365
+ // Ensures content is autosaved before window closes or navigates to new page
366
+ tinymce.dom.Event.add(window, "unload", s.saveFinalDelegate);
367
+
368
+ // Save when editor is removed (may be different than window's onunload event, so we need to do both)
369
+ ed.onRemove.add(s.saveFinalDelegate);
370
+
371
+ // Add ask before unload dialog
372
+ if (s.askBeforeUnload) {
373
+ tinymce.dom.Event.add(window, "unload", tinymce.plugins.AutoSavePlugin._beforeUnloadHandler);
374
+ }
375
+
376
+ ed.onInit.add(function() {
377
+
378
+ if (useUserData) {
379
+
380
+ if (!userDataElement) {
381
+ userDataElement = ed.getElement();
382
+ }
383
+
384
+ userDataElement.style.behavior = "url('#default#userData')";
385
+ }
386
+
387
+ s.canRestore = t.hasSavedContent();
388
+
389
+ // Call tinyautosave_oninit, if specified
390
+ // This config option is a String value specifying the name of a function to call. Can include dot-notation, e.g., "myObject.myFunction".
391
+ // The context of the function call (the value of 'this') is the plugin instance.
392
+ onInit = ed.getParam(pluginName + "_oninit", null);
393
+
394
+ if (is(onInit, "string")) {
395
+ f = resolve(onInit);
396
+
397
+ if (is(f, "function")) {
398
+ f.apply(t);
399
+ }
400
+ }
401
+
402
+ // Set initial state of restore button
403
+ ed.controlManager.setDisabled(pluginName, !s.canRestore);
404
+ });
405
+ },
406
+
407
+ getInfo: function() {
408
+ /// <summary>
409
+ /// Called by TinyMCE, returns standard information about the plugin
410
+ /// to display in the About box.
411
+ /// </summary>
412
+
413
+ return {
414
+ longname: "TinyAutoSave",
415
+ author: "Speednet",
416
+ authorurl: "http://www.speednet.biz/",
417
+ infourl: "http://tinyautosave.googlecode.com/",
418
+ version: version
419
+ };
420
+ },
421
+
422
+ clear: function () {
423
+ /// <summary>
424
+ /// Removes the autosave content from storage. Disables the 'tinyautosave' toolbar button.
425
+ /// </summary>
426
+
427
+ var t = this,
428
+ ed = t.editor,
429
+ s = getInstanceSettings(t);
430
+
431
+ if (useLocalStorage) {
432
+ localStorage.removeItem(s.dataKey);
433
+ }
434
+ else if (useSessionStorage) {
435
+ sessionStorage.removeItem(s.dataKey);
436
+ }
437
+ else if (useUserData) {
438
+ removeUserData(t);
439
+ }
440
+ else {
441
+ tinymce.util.Cookie.remove(s.dataKey);
442
+ }
443
+
444
+ s.canRestore = false;
445
+ ed.controlManager.setDisabled(pluginName, t);
446
+ },
447
+
448
+ hasSavedContent: function () {
449
+ /// <summary>
450
+ /// Returns true if there is unexpired autosave content available to be restored.
451
+ /// </summary>
452
+ /// <returns type="Boolean"></returns>
453
+
454
+ var t = this,
455
+ s = getInstanceSettings(t),
456
+ now = new Date(),
457
+ content, i;
458
+
459
+ try {
460
+ if (useLocalStorage || useSessionStorage) {
461
+ content = ((useLocalStorage? localStorage.getItem(s.dataKey) : sessionStorage.getItem(s.dataKey)) || "").toString(),
462
+ i = content.indexOf(",");
463
+
464
+ if ((i > 8) && (i < content.length - 1)) {
465
+
466
+ if ((new Date(content.slice(0, i))) > now) {
467
+ return true;
468
+ }
469
+
470
+ // Remove expired content
471
+ if (useLocalStorage) {
472
+ localStorage.removeItem(s.dataKey);
473
+ }
474
+ else {
475
+ sessionStorage.removeItem(s.dataKey);
476
+ }
477
+ }
478
+
479
+ return false;
480
+ }
481
+ else if (useUserData) {
482
+ return ((getUserData(t) || "").length > 0);
483
+ }
484
+
485
+ return ((tinymce.util.Cookie.get(s.dataKey) || "").length > 0);
486
+ }
487
+ catch (e) {
488
+ return false;
489
+ }
490
+ },
491
+
492
+ setProgressImage: function (url) {
493
+ /// <summary>
494
+ /// Sets the progress image/throbber to a specified URL. The progress image
495
+ /// temporarily replaces the image on the TinyAutoSave toolbar button every
496
+ /// time an auto-save occurs. The default value is
497
+ /// "[tinymce]/plugins/tinyautosave/images/progress.gif". Can be set any time
498
+ /// after the plugin initializes. The progress image is normally an animated GIF,
499
+ /// but it can be any image type. Because the image will be displayed on a toolbar
500
+ /// button, so the recommended size is 20 x 20 (using a centered 16 x 16 image).
501
+ /// </summary>
502
+ /// <param name="url" type="String" optional="false" mayBeNull="false">
503
+ /// The URL of the image that will be displayed on the restore toolbar button
504
+ /// every time an auto-save occurs.
505
+ /// </param>
506
+
507
+ if (tinymce.is(url, "string")) {
508
+ preloadImage(getInstanceSettings(this).progressImage = url);
509
+ }
510
+ },
511
+
512
+ "static": {
513
+ _beforeUnloadHandler: function () {
514
+ var msg;
515
+
516
+ tinymce.each(tinyMCE.editors, function (ed) {
517
+
518
+ if (ed.getParam("fullscreen_is_enabled")) {
519
+ return;
520
+ }
521
+
522
+ if (ed.isDirty()) {
523
+ msg = ed.getLang("autosave.unload_msg");
524
+ return false;
525
+ }
526
+ });
527
+
528
+ return msg;
529
+ }
530
+ }
531
+ });
532
+
533
+
534
+ //************************************************************************
535
+ // PRIVATE FUNCTIONS
536
+
537
+ function dispose() {
538
+ /// <summary>
539
+ /// Called just before the current page unloads. Cleans up memory, releases
540
+ /// timers and events.
541
+ /// </summary>
542
+ /// <remarks>
543
+ /// Must be called with context ("this" keyword) set to plugin instance
544
+ /// </remarks>
545
+
546
+ var t = this,
547
+ s = getInstanceSettings(t);
548
+
549
+ if (s.timer) {
550
+ window.clearInterval(s.timer);
551
+ }
552
+
553
+ tinymce.dom.Event.remove(window, "unload", s.saveFinalDelegate);
554
+
555
+ if (s.askBeforeUnload) {
556
+ tinymce.dom.Event.remove(window, "unload", tinymce.plugins.AutoSavePlugin._beforeUnloadHandler);
557
+ }
558
+
559
+ t.editor.onRemove.remove(s.saveFinalDelegate);
560
+ removeInstanceSettings(t);
561
+ }
562
+
563
+ function execCallback(n) {
564
+ /// <summary>
565
+ /// Executes a callback function. The callback function can be specified
566
+ /// either as a string or a function.
567
+ /// </summary>
568
+ /// <remarks>
569
+ /// Must be called with context ("this" keyword) set to plugin instance
570
+ /// </remarks>
571
+
572
+ if (!n) {
573
+ return true;
574
+ }
575
+
576
+ var c, f,
577
+ is = tinymce.is;
578
+
579
+ if (is(n, "string")) {
580
+ c = callbackLookup[n];
581
+
582
+ if (c) {
583
+ f = c[n];
584
+ }
585
+ else {
586
+ callbackLookup[n] = f = tinymce.resolve(n);
587
+ }
588
+ }
589
+ else if (is(n, "function")) {
590
+ f = n;
591
+ }
592
+ else {
593
+ return true;
594
+ }
595
+
596
+ return f.apply(this);
597
+ }
598
+
599
+ function saveFinal() {
600
+ /// <summary>
601
+ /// Called just before the current page is unloaded. Performs a final save, then
602
+ /// cleans up memory to prevent leaks.
603
+ /// </summary>
604
+ /// <remarks>
605
+ /// Must be called with context ("this" keyword) set to plugin instance
606
+ /// </remarks>
607
+
608
+ var s = getInstanceSettings(this);
609
+
610
+ s.saveDelegate();
611
+ s.disposeDelegate();
612
+ }
613
+
614
+ function save() {
615
+ /// <summary>
616
+ /// Performs a single, one-time autosave. Checks to be sure there is at least the
617
+ /// specified minimum number of characters in the editor before saving. Briefly
618
+ /// animates the toolbar button. Enables the 'tinyautosave' button to indicate
619
+ /// autosave content is available.
620
+ /// </summary>
621
+ /// <returns type="Boolean">
622
+ /// Returns true if content was saved, or false if not.
623
+ /// </returns>
624
+ /// <remarks>
625
+ /// Must be called with context ("this" keyword) set to plugin instance
626
+ /// </remarks>
627
+
628
+ var t = this,
629
+ ed = t.editor,
630
+ s = getInstanceSettings(t),
631
+ is = tinymce.is,
632
+ saved = false,
633
+ now = new Date(),
634
+ content, exp, a, b, cm, img;
635
+
636
+ if ((ed) && (!s.busy)) {
637
+ s.busy = true;
638
+ content = ed.getContent();
639
+
640
+ if (is(content, "string") && (content.length >= s.minSaveLength)) {
641
+
642
+ if (!execCallback.call(t, t.onPreSave)) {
643
+ s.busy = false;
644
+ return false;
645
+ }
646
+
647
+ exp = new Date(now.getTime() + (s.retentionMinutes * 60 * 1000));
648
+
649
+ try {
650
+ if (useLocalStorage) {
651
+ localStorage.setItem(s.dataKey, exp.toString() + "," + encodeStorage(content)); // Uses local time for expiration
652
+ }
653
+ else if (useSessionStorage) {
654
+ sessionStorage.setItem(s.dataKey, exp.toString() + "," + encodeStorage(content)); // Uses local time for expiration
655
+ }
656
+ else if (useUserData) {
657
+ setUserData(t, content, exp);
658
+ }
659
+ else {
660
+ a = s.dataKey + "=";
661
+ b = "; expires=" + exp.toUTCString();
662
+
663
+ document.cookie = a + encodeCookie(content).slice(0, 4096 - a.length - b.length) + b;
664
+ }
665
+
666
+ saved = true;
667
+ }
668
+ catch (e) {
669
+ execCallback.call(t, t.onSaveError);
670
+ }
671
+
672
+ if (saved) {
673
+ cm = ed.controlManager;
674
+ s.canRestore = true;
675
+ cm.setDisabled(pluginName, false);
676
+
677
+ if (t.showSaveProgress) {
678
+ b = tinymce.DOM.get(cm.get(pluginName).id);
679
+
680
+ if (b) {
681
+ img = s.restoreImage;
682
+
683
+ b.firstChild.src = s.progressImage;
684
+
685
+ window.setTimeout(
686
+ function () {
687
+ b.firstChild.src = img;
688
+ },
689
+
690
+ Math.min(t.progressDisplayTime, s.intervalSeconds * 1000 - 100)
691
+ );
692
+ }
693
+ }
694
+
695
+ execCallback.call(t, t.onPostSave);
696
+ }
697
+ }
698
+
699
+ s.busy = false;
700
+ }
701
+
702
+ return saved;
703
+ }
704
+
705
+ function restore() {
706
+ /// <summary>
707
+ /// Called when the user clicks the 'tinyautosave' button on the toolbar.
708
+ /// Replaces the contents of the editor with the autosaved content. If the editor
709
+ /// contains more than just whitespace, the user is warned and given the option
710
+ /// to abort. The autosaved content remains in storage.
711
+ /// </summary>
712
+ /// <remarks>
713
+ /// Must be called with context ("this" keyword) set to plugin instance
714
+ /// </remarks>
715
+
716
+ var t = this,
717
+ ed = t.editor,
718
+ s = getInstanceSettings(t),
719
+ content = null,
720
+ is = tinymce.is,
721
+ i, m;
722
+
723
+ if ((ed) && (s.canRestore) && (!s.busy)) {
724
+ s.busy = true;
725
+
726
+ if (!execCallback.call(t, t.onPreRestore)) {
727
+ s.busy = false;
728
+ return;
729
+ }
730
+
731
+ try {
732
+ if (useLocalStorage || useSessionStorage) {
733
+ content = ((useLocalStorage? localStorage.getItem(s.dataKey) : sessionStorage.getItem(s.dataKey)) || "").toString();
734
+ i = content.indexOf(",");
735
+
736
+ if (i == -1) {
737
+ content = null;
738
+ }
739
+ else {
740
+ content = decodeStorage(content.slice(i + 1, content.length));
741
+ }
742
+ }
743
+ else if (useUserData) {
744
+ content = getUserData(t);
745
+ }
746
+ else {
747
+ m = s.cookieFilter.exec(document.cookie);
748
+
749
+ if (m) {
750
+ content = decodeCookie(m[1]);
751
+ }
752
+ }
753
+
754
+ if (!is(content, "string")) {
755
+ ed.windowManager.alert(pluginName + ".no_content");
756
+ }
757
+ else {
758
+
759
+ // If current contents are empty or whitespace, the confirmation is unnecessary
760
+ if (ed.getContent().replace(/\s|&nbsp;|<\/?p[^>]*>|<br[^>]*>/gi, "").length === 0) {
761
+ ed.setContent(content);
762
+ execCallback.call(t, t.onPostRestore);
763
+ }
764
+ else {
765
+ ed.windowManager.confirm(
766
+ pluginName + ".warning_message",
767
+ function (ok) {
768
+ if (ok) {
769
+ ed.setContent(content);
770
+ execCallback.call(t, t.onPostRestore);
771
+ }
772
+ s.busy = false;
773
+ },
774
+ t
775
+ );
776
+ }
777
+ }
778
+ }
779
+ catch (e) {
780
+ execCallback.call(t, t.onRestoreError);
781
+ }
782
+
783
+ s.busy = false;
784
+ }
785
+ }
786
+
787
+ function setUserData(inst, str, exp) {
788
+ /// <summary>
789
+ /// IE browsers only. Saves a string to the 'UserData' storage area.
790
+ /// </summary>
791
+ /// <param name="inst" type="Object" optional="false" mayBeNull="false">
792
+ /// Plugin instance for which to set the UserData
793
+ /// </param>
794
+ /// <param name="str" type="String" optional="false" mayBeNull="false">
795
+ /// String value to save.
796
+ /// </param>
797
+ /// <param name="exp" type="Date" optional="false" mayBeNull="false">
798
+ /// Date object specifying the expiration date of the content
799
+ /// </param>
800
+ /// <remarks>
801
+ /// Maximum size of the autosave data is 128K for regular Internet Web sites or
802
+ /// 512KB for intranet sites. Total size of all data for one domain is 1MB for
803
+ /// Internet sites and 10MB for intranet sites.
804
+ /// </remarks>
805
+
806
+ userDataElement.setAttribute(getInstanceSettings(inst).dataKey, str);
807
+ userDataElement.expires = exp.toUTCString();
808
+ userDataElement.save("TinyMCE");
809
+ }
810
+
811
+ function getUserData(inst) {
812
+ /// <summary>
813
+ /// IE browsers only. Retrieves a string from the 'UserData' storage area.
814
+ /// </summary>
815
+ /// <param name="inst" type="Object" optional="false" mayBeNull="false">
816
+ /// Plugin instance from which to get the UserData
817
+ /// </param>
818
+ /// <returns type="String"></returns>
819
+
820
+ userDataElement.load("TinyMCE");
821
+ return userDataElement.getAttribute(getInstanceSettings(inst).dataKey);
822
+ }
823
+
824
+ function removeUserData(inst) {
825
+ /// <summary>
826
+ /// IE browsers only. Removes a string from the 'UserData' storage area.
827
+ /// </summary>
828
+ /// <param name="inst" type="Object" optional="false" mayBeNull="false">
829
+ /// Plugin instance from which to remove the UserData
830
+ /// </param>
831
+
832
+ userDataElement.removeAttribute(getInstanceSettings(inst).dataKey);
833
+ }
834
+
835
+ function encodeCookie(str) {
836
+ /// <summary>
837
+ /// Encodes a string value intended for storage in a cookie. Used instead of
838
+ /// escape() to be more space-efficient and to apply some minor compression.
839
+ /// </summary>
840
+ /// <param name="str" type="String" optional="false" mayBeNull="false">
841
+ /// String to encode for cookie storage
842
+ /// </param>
843
+ /// <returns type="String"></returns>
844
+ /// <remarks>
845
+ /// Depends on the existence of the cookieEncodeKey property. Used as a lookup table.
846
+ /// TO DO: Implement additional compression techniques.
847
+ /// </remarks>
848
+
849
+ return str.replace(/[\x00-\x1f]+|&nbsp;|&#160;/gi, " ")
850
+ .replace(/(.)\1{5,}|[%&;=<]/g,
851
+ function (c) {
852
+ if (c.length > 1) {
853
+ return ("%0" + c.charAt(0) + c.length.toString() + "%");
854
+ }
855
+ return cookieEncodeKey[c];
856
+ }
857
+ );
858
+ }
859
+
860
+ function decodeCookie(str) {
861
+ /// <summary>
862
+ /// Decodes a string value that was previously encoded with encodeCookie().
863
+ /// </summary>
864
+ /// <param name="str" type="String" optional="false" mayBeNull="false">
865
+ /// String that was previously encoded with encodeCookie()
866
+ /// </param>
867
+ /// <returns type="String"></returns>
868
+ /// <remarks>
869
+ /// Depends on the existence of the cookieDecodeKey property. Used as a lookup table.
870
+ /// TO DO: Implement additional compression techniques.
871
+ /// </remarks>
872
+
873
+ return str.replace(/%[1-5]|%0(.)(\d+)%/g,
874
+ function (c, m, d) {
875
+ var a, i, l;
876
+
877
+ if (c.length == 2) {
878
+ return cookieDecodeKey[c];
879
+ }
880
+
881
+ for (a=[], i=0, l=parseInt(d); i<l; i++) {
882
+ a.push(m);
883
+ }
884
+
885
+ return a.join("");
886
+ });
887
+ }
888
+
889
+ function encodeStorage(str) {
890
+ /// <summary>
891
+ /// Encodes a string value intended for storage in either localStorage or sessionStorage.
892
+ /// </summary>
893
+ /// <param name="str" type="String" optional="false" mayBeNull="false">
894
+ /// String to encode for localStorage or sessionStorage
895
+ /// </param>
896
+ /// <returns type="String"></returns>
897
+ /// <remarks>
898
+ /// Necessary because a bug in Safari truncates the string at the first comma.
899
+ /// </remarks>
900
+
901
+ return str.replace(/,/g, "&#44;");
902
+ }
903
+
904
+ function decodeStorage(str) {
905
+ /// <summary>
906
+ /// Decodes a string value that was previously encoded with encodeStorage().
907
+ /// </summary>
908
+ /// <param name="str" type="String" optional="false" mayBeNull="false">
909
+ /// String that was previously encoded with encodeStorage()
910
+ /// </param>
911
+ /// <returns type="String"></returns>
912
+
913
+ return str.replace(/&#44;/g, ",");
914
+ }
915
+
916
+ function preloadImage(imageURL) {
917
+ /// <summary>
918
+ /// Preloads an image so it will be instantly displayed the first time it's needed.
919
+ /// </summary>
920
+
921
+ var i = preloadImages.length;
922
+
923
+ preloadImages[i] = new Image();
924
+ preloadImages[i].src = imageURL;
925
+ }
926
+
927
+ function createDelegate(t, method) {
928
+ /// <summary>
929
+ /// Returns a delegate function, used for callbacks. Ensures 'this' refers
930
+ /// to the desired object.
931
+ /// </summary>
932
+ /// <param name="t" type="Object" optional="false" mayBeNull="true">
933
+ /// Object that will be 'this' within the callback function.
934
+ /// </param>
935
+ /// <param name="method" type="Function" optional="false" mayBeNull="false">
936
+ /// Callback function
937
+ /// </param>
938
+ /// <returns type="Function"></returns>
939
+
940
+ return function () {
941
+ return method.apply(t);
942
+ };
943
+ }
944
+
945
+ function newInstanceSettings(inst) {
946
+ /// <summary>
947
+ /// Creates new settings storage for a plugin instance.
948
+ /// </summary>
949
+ /// <param name="inst" type="Object" optional="false" mayBeNull="true">
950
+ /// The plugin instance for which to create the settings storage.
951
+ /// </param>
952
+ /// <returns type="Object"></returns>
953
+
954
+ var key = inst.key,
955
+ s = instanceSettings[key];
956
+
957
+ if (!s) {
958
+ s = instanceSettings[key] = tinymce.extend({}, settingsTemplate, {
959
+ dataKey: settingsTemplate.dataKey + key,
960
+ saveDelegate: createDelegate(inst, save),
961
+ saveFinalDelegate: createDelegate(inst, saveFinal),
962
+ restoreDelegate: createDelegate(inst, restore),
963
+ disposeDelegate: createDelegate(inst, dispose),
964
+ cookieFilter: new RegExp("(?:^|;\\s*)" + settingsTemplate.dataKey + key + "=([^;]*)(?:;|$)", "i")
965
+ });
966
+ }
967
+
968
+ return s;
969
+ }
970
+
971
+ function getInstanceSettings(inst) {
972
+ /// <summary>
973
+ /// Retrieves the settings for a plugin instance.
974
+ /// </summary>
975
+ /// <param name="inst" type="Object" optional="false" mayBeNull="true">
976
+ /// The plugin instance for which to retrieve the settings.
977
+ /// </param>
978
+ /// <returns type="Object"></returns>
979
+
980
+ return instanceSettings[inst.key];
981
+ }
982
+
983
+ function removeInstanceSettings(inst) {
984
+ /// <summary>
985
+ /// Deletes the settings for a plugin instance.
986
+ /// </summary>
987
+ /// <param name="inst" type="Object" optional="false" mayBeNull="true">
988
+ /// The plugin instance for which to delete the settings.
989
+ /// </param>
990
+
991
+ delete instanceSettings[inst.key];
992
+ }
993
+
994
+
995
+ //************************************************************************
996
+ // REGISTER PLUGIN
997
+
998
+ tinymce.PluginManager.add(pluginName, tinymce.plugins.TinyAutoSavePlugin);
999
+
1000
+ })();
1001
+