community_engine 2.0.0.beta2 → 2.0.0.beta3

Sign up to get free protection for your applications and to get access to all the features.
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
+