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.
- data/Gemfile +4 -0
- data/README.markdown +3 -3
- data/about.yml +1 -1
- data/app/assets/javascripts/tinymce/plugins/tinyautosave/editor_plugin.js +8 -0
- data/app/assets/javascripts/tinymce/plugins/tinyautosave/editor_plugin_src.js +1001 -0
- data/app/assets/javascripts/tinymce/plugins/tinyautosave/images/progress.gif +0 -0
- data/app/assets/javascripts/tinymce/plugins/tinyautosave/images/progress10.gif +0 -0
- data/app/assets/javascripts/tinymce/plugins/tinyautosave/images/progress11.gif +0 -0
- data/app/assets/javascripts/tinymce/plugins/tinyautosave/images/progress12.gif +0 -0
- data/app/assets/javascripts/tinymce/plugins/tinyautosave/images/progress2.gif +0 -0
- data/app/assets/javascripts/tinymce/plugins/tinyautosave/images/progress3.gif +0 -0
- data/app/assets/javascripts/tinymce/plugins/tinyautosave/images/progress4.gif +0 -0
- data/app/assets/javascripts/tinymce/plugins/tinyautosave/images/progress5.gif +0 -0
- data/app/assets/javascripts/tinymce/plugins/tinyautosave/images/progress6.gif +0 -0
- data/app/assets/javascripts/tinymce/plugins/tinyautosave/images/progress7.gif +0 -0
- data/app/assets/javascripts/tinymce/plugins/tinyautosave/images/progress8.gif +0 -0
- data/app/assets/javascripts/tinymce/plugins/tinyautosave/images/progress9.gif +0 -0
- data/app/assets/javascripts/tinymce/plugins/tinyautosave/images/restore.gif +0 -0
- data/app/assets/javascripts/tinymce/plugins/tinyautosave/images/restore.png +0 -0
- data/app/assets/javascripts/tinymce/plugins/tinyautosave/langs/da.js +5 -0
- data/app/assets/javascripts/tinymce/plugins/tinyautosave/langs/en.js +5 -0
- data/app/assets/{javascripts → stylesheets}/cropper.css +0 -0
- data/app/controllers/comments_controller.rb +1 -1
- data/app/controllers/forums_controller.rb +3 -6
- data/app/controllers/moderators_controller.rb +2 -5
- data/app/controllers/sb_posts_controller.rb +1 -11
- data/app/controllers/tags_controller.rb +1 -2
- data/app/controllers/topics_controller.rb +1 -2
- data/app/helpers/base_helper.rb +1 -1
- data/app/models/photo.rb +5 -5
- data/app/models/tag.rb +36 -0
- data/app/models/user.rb +2 -2
- data/app/views/activities/index.html.haml +1 -1
- data/app/views/admin/events.html.haml +1 -1
- data/app/views/photos/edit.html.haml +1 -1
- data/app/views/photos/show.html.haml +2 -2
- data/app/views/posts/show.html.haml +1 -0
- data/app/views/sb_posts/index.html.haml +10 -5
- data/app/views/shared/_footer_content.html.haml +1 -1
- data/app/views/tags/index.html.haml +1 -1
- data/app/views/tags/manage.html.haml +5 -3
- data/app/views/topics/show.html.haml +6 -1
- data/app/views/users/crop_profile_photo.html.haml +1 -0
- data/community_engine.gemspec +3 -5
- data/config/initializers/recaptcha_constants.rb +2 -2
- data/config/locales/en.yml +1 -1
- data/config/locales/ru-RU.yml +1656 -54
- data/config/locales/sv-SE.yml +212 -4
- data/config/routes.rb +1 -1
- data/lib/community_engine.rb +3 -2
- data/lib/community_engine/engine.rb +8 -6
- data/lib/community_engine/version.rb +1 -1
- data/test/fixtures/taggings.yml +11 -1
- data/test/fixtures/tags.yml +9 -1
- data/test/functional/admin_controller_test.rb +8 -0
- data/test/functional/forums_controller_test.rb +1 -1
- data/test/functional/sb_posts_controller_test.rb +15 -19
- data/test/functional/tags_controller_test.rb +21 -1
- data/test/test_helper.rb +1 -1
- data/test/testapp/db/schema.rb +1 -0
- data/test/unit/tag_test.rb +2 -2
- metadata +75 -69
- data/app/views/sb_posts/index.xml.builder +0 -20
- data/lib/tag_hacks.rb +0 -24
data/Gemfile
CHANGED
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.
|
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'
|
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 {
|
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.
|
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| |<\/?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]+| | /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,",")}function F(a){return a.replace(/,/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 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| |<\/?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]+| | /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, ",");
|
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(/,/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
|
+
|