suvii 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (150) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/.travis.yml +10 -0
  4. data/.yardopts +1 -0
  5. data/CHANGELOG.md +1 -0
  6. data/CONTRIBUTING.md +47 -0
  7. data/Gemfile +6 -0
  8. data/LICENSE.txt +22 -0
  9. data/README.md +51 -0
  10. data/Rakefile +7 -0
  11. data/lib/suvii.rb +40 -0
  12. data/lib/suvii/cache.rb +24 -0
  13. data/lib/suvii/extract.rb +58 -0
  14. data/lib/suvii/extract/targz.rb +49 -0
  15. data/lib/suvii/extract/zip.rb +19 -0
  16. data/lib/suvii/http.rb +31 -0
  17. data/suvii.gemspec +23 -0
  18. data/test/acceptance_test.rb +29 -0
  19. data/test/cache_test.rb +42 -0
  20. data/test/extract/targz_test.rb +28 -0
  21. data/test/extract/zip_test.rb +28 -0
  22. data/test/extract_test.rb +26 -0
  23. data/test/fixtures/results/Trumbowyg-1.1.7/.gitattributes +6 -0
  24. data/test/fixtures/results/Trumbowyg-1.1.7/.jshintrc +15 -0
  25. data/test/fixtures/results/Trumbowyg-1.1.7/Gulpfile.js +167 -0
  26. data/test/fixtures/results/Trumbowyg-1.1.7/LICENSE +21 -0
  27. data/test/fixtures/results/Trumbowyg-1.1.7/README.md +29 -0
  28. data/test/fixtures/results/Trumbowyg-1.1.7/bower.json +36 -0
  29. data/test/fixtures/results/Trumbowyg-1.1.7/dist/langs/ca.min.js +11 -0
  30. data/test/fixtures/results/Trumbowyg-1.1.7/dist/langs/da.min.js +9 -0
  31. data/test/fixtures/results/Trumbowyg-1.1.7/dist/langs/de.min.js +9 -0
  32. data/test/fixtures/results/Trumbowyg-1.1.7/dist/langs/es.min.js +9 -0
  33. data/test/fixtures/results/Trumbowyg-1.1.7/dist/langs/es_ar.min.js +9 -0
  34. data/test/fixtures/results/Trumbowyg-1.1.7/dist/langs/fa.min.js +10 -0
  35. data/test/fixtures/results/Trumbowyg-1.1.7/dist/langs/fi.min.js +9 -0
  36. data/test/fixtures/results/Trumbowyg-1.1.7/dist/langs/fr.min.js +10 -0
  37. data/test/fixtures/results/Trumbowyg-1.1.7/dist/langs/he.min.js +9 -0
  38. data/test/fixtures/results/Trumbowyg-1.1.7/dist/langs/id.min.js +11 -0
  39. data/test/fixtures/results/Trumbowyg-1.1.7/dist/langs/it.min.js +8 -0
  40. data/test/fixtures/results/Trumbowyg-1.1.7/dist/langs/ko.min.js +10 -0
  41. data/test/fixtures/results/Trumbowyg-1.1.7/dist/langs/pl.min.js +9 -0
  42. data/test/fixtures/results/Trumbowyg-1.1.7/dist/langs/pt.min.js +11 -0
  43. data/test/fixtures/results/Trumbowyg-1.1.7/dist/langs/ro.min.js +13 -0
  44. data/test/fixtures/results/Trumbowyg-1.1.7/dist/langs/ru.min.js +8 -0
  45. data/test/fixtures/results/Trumbowyg-1.1.7/dist/langs/tr.min.js +10 -0
  46. data/test/fixtures/results/Trumbowyg-1.1.7/dist/langs/zh_cn.min.js +10 -0
  47. data/test/fixtures/results/Trumbowyg-1.1.7/dist/plugins/base64/trumbowyg.base64.js +74 -0
  48. data/test/fixtures/results/Trumbowyg-1.1.7/dist/plugins/base64/trumbowyg.base64.min.js +1 -0
  49. data/test/fixtures/results/Trumbowyg-1.1.7/dist/plugins/colors/trumbowyg.colors.js +69 -0
  50. data/test/fixtures/results/Trumbowyg-1.1.7/dist/plugins/colors/trumbowyg.colors.min.js +1 -0
  51. data/test/fixtures/results/Trumbowyg-1.1.7/dist/plugins/colors/ui/images/icons-2x.png +0 -0
  52. data/test/fixtures/results/Trumbowyg-1.1.7/dist/plugins/colors/ui/images/icons.png +0 -0
  53. data/test/fixtures/results/Trumbowyg-1.1.7/dist/plugins/colors/ui/trumbowyg.colors.css +55 -0
  54. data/test/fixtures/results/Trumbowyg-1.1.7/dist/plugins/colors/ui/trumbowyg.colors.min.css +2 -0
  55. data/test/fixtures/results/Trumbowyg-1.1.7/dist/plugins/upload/trumbowyg.upload.js +140 -0
  56. data/test/fixtures/results/Trumbowyg-1.1.7/dist/plugins/upload/trumbowyg.upload.min.js +1 -0
  57. data/test/fixtures/results/Trumbowyg-1.1.7/dist/trumbowyg.js +1124 -0
  58. data/test/fixtures/results/Trumbowyg-1.1.7/dist/trumbowyg.min.js +2 -0
  59. data/test/fixtures/results/Trumbowyg-1.1.7/dist/ui/images/icons-2x.png +0 -0
  60. data/test/fixtures/results/Trumbowyg-1.1.7/dist/ui/images/icons.png +0 -0
  61. data/test/fixtures/results/Trumbowyg-1.1.7/dist/ui/trumbowyg.css +471 -0
  62. data/test/fixtures/results/Trumbowyg-1.1.7/dist/ui/trumbowyg.min.css +2 -0
  63. data/test/fixtures/results/Trumbowyg-1.1.7/examples/css/main.css +14 -0
  64. data/test/fixtures/results/Trumbowyg-1.1.7/examples/index.html +41 -0
  65. data/test/fixtures/results/Trumbowyg-1.1.7/examples/plugins/colors.html +49 -0
  66. data/test/fixtures/results/Trumbowyg-1.1.7/index.html +252 -0
  67. data/test/fixtures/results/Trumbowyg-1.1.7/package.json +48 -0
  68. data/test/fixtures/results/Trumbowyg-1.1.7/plugins/base64/trumbowyg.base64.js +74 -0
  69. data/test/fixtures/results/Trumbowyg-1.1.7/plugins/colors/Gulpfile.js +101 -0
  70. data/test/fixtures/results/Trumbowyg-1.1.7/plugins/colors/trumbowyg.colors.js +69 -0
  71. data/test/fixtures/results/Trumbowyg-1.1.7/plugins/colors/ui/images/icons-2x/backcolor.png +0 -0
  72. data/test/fixtures/results/Trumbowyg-1.1.7/plugins/colors/ui/images/icons-2x/forecolor.png +0 -0
  73. data/test/fixtures/results/Trumbowyg-1.1.7/plugins/colors/ui/images/icons/backcolor.png +0 -0
  74. data/test/fixtures/results/Trumbowyg-1.1.7/plugins/colors/ui/images/icons/forecolor.png +0 -0
  75. data/test/fixtures/results/Trumbowyg-1.1.7/plugins/colors/ui/sass/trumbowyg.colors.scss +65 -0
  76. data/test/fixtures/results/Trumbowyg-1.1.7/plugins/upload/trumbowyg.upload.js +140 -0
  77. data/test/fixtures/results/Trumbowyg-1.1.7/plugins/upload/trumbowyg.upload.php +49 -0
  78. data/test/fixtures/results/Trumbowyg-1.1.7/src/langs/ca.js +58 -0
  79. data/test/fixtures/results/Trumbowyg-1.1.7/src/langs/da.js +56 -0
  80. data/test/fixtures/results/Trumbowyg-1.1.7/src/langs/de.js +56 -0
  81. data/test/fixtures/results/Trumbowyg-1.1.7/src/langs/en.js +14 -0
  82. data/test/fixtures/results/Trumbowyg-1.1.7/src/langs/es.js +56 -0
  83. data/test/fixtures/results/Trumbowyg-1.1.7/src/langs/es_ar.js +56 -0
  84. data/test/fixtures/results/Trumbowyg-1.1.7/src/langs/fa.js +57 -0
  85. data/test/fixtures/results/Trumbowyg-1.1.7/src/langs/fi.js +56 -0
  86. data/test/fixtures/results/Trumbowyg-1.1.7/src/langs/fr.js +57 -0
  87. data/test/fixtures/results/Trumbowyg-1.1.7/src/langs/he.js +58 -0
  88. data/test/fixtures/results/Trumbowyg-1.1.7/src/langs/id.js +58 -0
  89. data/test/fixtures/results/Trumbowyg-1.1.7/src/langs/it.js +55 -0
  90. data/test/fixtures/results/Trumbowyg-1.1.7/src/langs/ko.js +57 -0
  91. data/test/fixtures/results/Trumbowyg-1.1.7/src/langs/pl.js +56 -0
  92. data/test/fixtures/results/Trumbowyg-1.1.7/src/langs/pt.js +58 -0
  93. data/test/fixtures/results/Trumbowyg-1.1.7/src/langs/ro.js +60 -0
  94. data/test/fixtures/results/Trumbowyg-1.1.7/src/langs/ru.js +55 -0
  95. data/test/fixtures/results/Trumbowyg-1.1.7/src/langs/tr.js +57 -0
  96. data/test/fixtures/results/Trumbowyg-1.1.7/src/langs/zh_cn.js +57 -0
  97. data/test/fixtures/results/Trumbowyg-1.1.7/src/trumbowyg.js +1113 -0
  98. data/test/fixtures/results/Trumbowyg-1.1.7/src/ui/images/icons-2x/attachement.png +0 -0
  99. data/test/fixtures/results/Trumbowyg-1.1.7/src/ui/images/icons-2x/barre.png +0 -0
  100. data/test/fixtures/results/Trumbowyg-1.1.7/src/ui/images/icons-2x/bold.png +0 -0
  101. data/test/fixtures/results/Trumbowyg-1.1.7/src/ui/images/icons-2x/center-align.png +0 -0
  102. data/test/fixtures/results/Trumbowyg-1.1.7/src/ui/images/icons-2x/close.png +0 -0
  103. data/test/fixtures/results/Trumbowyg-1.1.7/src/ui/images/icons-2x/formatting.png +0 -0
  104. data/test/fixtures/results/Trumbowyg-1.1.7/src/ui/images/icons-2x/fullscreen-exit.png +0 -0
  105. data/test/fixtures/results/Trumbowyg-1.1.7/src/ui/images/icons-2x/fullscreen.png +0 -0
  106. data/test/fixtures/results/Trumbowyg-1.1.7/src/ui/images/icons-2x/gras.png +0 -0
  107. data/test/fixtures/results/Trumbowyg-1.1.7/src/ui/images/icons-2x/horizontal-rule.png +0 -0
  108. data/test/fixtures/results/Trumbowyg-1.1.7/src/ui/images/icons-2x/image.png +0 -0
  109. data/test/fixtures/results/Trumbowyg-1.1.7/src/ui/images/icons-2x/italic.png +0 -0
  110. data/test/fixtures/results/Trumbowyg-1.1.7/src/ui/images/icons-2x/justify-align.png +0 -0
  111. data/test/fixtures/results/Trumbowyg-1.1.7/src/ui/images/icons-2x/left-align.png +0 -0
  112. data/test/fixtures/results/Trumbowyg-1.1.7/src/ui/images/icons-2x/link.png +0 -0
  113. data/test/fixtures/results/Trumbowyg-1.1.7/src/ui/images/icons-2x/ordered-list.png +0 -0
  114. data/test/fixtures/results/Trumbowyg-1.1.7/src/ui/images/icons-2x/right-align.png +0 -0
  115. data/test/fixtures/results/Trumbowyg-1.1.7/src/ui/images/icons-2x/souligne.png +0 -0
  116. data/test/fixtures/results/Trumbowyg-1.1.7/src/ui/images/icons-2x/strikethrough.png +0 -0
  117. data/test/fixtures/results/Trumbowyg-1.1.7/src/ui/images/icons-2x/underline.png +0 -0
  118. data/test/fixtures/results/Trumbowyg-1.1.7/src/ui/images/icons-2x/unordered-list.png +0 -0
  119. data/test/fixtures/results/Trumbowyg-1.1.7/src/ui/images/icons-2x/video.png +0 -0
  120. data/test/fixtures/results/Trumbowyg-1.1.7/src/ui/images/icons-2x/view-html.png +0 -0
  121. data/test/fixtures/results/Trumbowyg-1.1.7/src/ui/images/icons/attachement.png +0 -0
  122. data/test/fixtures/results/Trumbowyg-1.1.7/src/ui/images/icons/barre.png +0 -0
  123. data/test/fixtures/results/Trumbowyg-1.1.7/src/ui/images/icons/bold.png +0 -0
  124. data/test/fixtures/results/Trumbowyg-1.1.7/src/ui/images/icons/center-align.png +0 -0
  125. data/test/fixtures/results/Trumbowyg-1.1.7/src/ui/images/icons/close.png +0 -0
  126. data/test/fixtures/results/Trumbowyg-1.1.7/src/ui/images/icons/formatting.png +0 -0
  127. data/test/fixtures/results/Trumbowyg-1.1.7/src/ui/images/icons/fullscreen-exit.png +0 -0
  128. data/test/fixtures/results/Trumbowyg-1.1.7/src/ui/images/icons/fullscreen.png +0 -0
  129. data/test/fixtures/results/Trumbowyg-1.1.7/src/ui/images/icons/gras.png +0 -0
  130. data/test/fixtures/results/Trumbowyg-1.1.7/src/ui/images/icons/horizontal-rule.png +0 -0
  131. data/test/fixtures/results/Trumbowyg-1.1.7/src/ui/images/icons/image.png +0 -0
  132. data/test/fixtures/results/Trumbowyg-1.1.7/src/ui/images/icons/italic.png +0 -0
  133. data/test/fixtures/results/Trumbowyg-1.1.7/src/ui/images/icons/justify-align.png +0 -0
  134. data/test/fixtures/results/Trumbowyg-1.1.7/src/ui/images/icons/left-align.png +0 -0
  135. data/test/fixtures/results/Trumbowyg-1.1.7/src/ui/images/icons/link.png +0 -0
  136. data/test/fixtures/results/Trumbowyg-1.1.7/src/ui/images/icons/ordered-list.png +0 -0
  137. data/test/fixtures/results/Trumbowyg-1.1.7/src/ui/images/icons/right-align.png +0 -0
  138. data/test/fixtures/results/Trumbowyg-1.1.7/src/ui/images/icons/souligne.png +0 -0
  139. data/test/fixtures/results/Trumbowyg-1.1.7/src/ui/images/icons/strikethrough.png +0 -0
  140. data/test/fixtures/results/Trumbowyg-1.1.7/src/ui/images/icons/underline.png +0 -0
  141. data/test/fixtures/results/Trumbowyg-1.1.7/src/ui/images/icons/unordered-list.png +0 -0
  142. data/test/fixtures/results/Trumbowyg-1.1.7/src/ui/images/icons/video.png +0 -0
  143. data/test/fixtures/results/Trumbowyg-1.1.7/src/ui/images/icons/view-html.png +0 -0
  144. data/test/fixtures/results/Trumbowyg-1.1.7/src/ui/sass/mixins/_sprite-pos.scss +7 -0
  145. data/test/fixtures/results/Trumbowyg-1.1.7/src/ui/sass/trumbowyg.scss +564 -0
  146. data/test/fixtures/trumbowyg.tar.gz +0 -0
  147. data/test/fixtures/trumbowyg.zip +0 -0
  148. data/test/http_test.rb +61 -0
  149. data/test/test_helper.rb +24 -0
  150. metadata +409 -0
@@ -0,0 +1,57 @@
1
+ /* ===========================================================
2
+ * zh_cn.js
3
+ * Simplified Chinese translation for Trumbowyg
4
+ * http://alex-d.github.com/Trumbowyg
5
+ * ===========================================================
6
+ * Author : Liu Kai (akai)
7
+ * Twitter : @akai404
8
+ * Github : https://github.com/akai
9
+ */
10
+
11
+ jQuery.trumbowyg.langs.zh_cn = {
12
+ viewHTML: "源代码",
13
+
14
+ formatting: "格式",
15
+ p: "段落",
16
+ blockquote: "引用",
17
+ code: "代码",
18
+ header: "标题",
19
+
20
+ bold: "加粗",
21
+ italic: "斜体",
22
+ strikethrough: "删除线",
23
+ underline: "下划线",
24
+
25
+ strong: "加粗",
26
+ em: "斜体",
27
+ del: "删除线",
28
+
29
+ unorderedList: "无序列表",
30
+ orderedList: "有序列表",
31
+
32
+ insertImage: "插入图片",
33
+ insertVideo: "插入视频",
34
+ link: "超链接",
35
+ createLink: "插入链接",
36
+ unlink: "取消链接",
37
+
38
+ justifyLeft: "居左对齐",
39
+ justifyCenter: "居中对齐",
40
+ justifyRight: "居右对齐",
41
+ justifyFull: "两端对齐",
42
+
43
+ horizontalRule: "插入分隔线",
44
+
45
+ fullscreen: "全屏",
46
+
47
+ close: "关闭",
48
+
49
+ submit: "确定",
50
+ reset: "取消",
51
+
52
+ invalidUrl: "无效的 URL",
53
+ required: "必需的",
54
+ description: "描述",
55
+ title: "标题",
56
+ text: "文字"
57
+ };
@@ -0,0 +1,1113 @@
1
+ jQuery.trumbowyg = {
2
+ langs: {
3
+ en: {
4
+ viewHTML: "View HTML",
5
+
6
+ formatting: "Formatting",
7
+ p: "Paragraph",
8
+ blockquote: "Quote",
9
+ code: "Code",
10
+ header: "Header",
11
+
12
+ bold: "Bold",
13
+ italic: "Italic",
14
+ strikethrough: "Stroke",
15
+ underline: "Underline",
16
+
17
+ strong: "Strong",
18
+ em: "Emphasis",
19
+ del: "Deleted",
20
+
21
+ unorderedList: "Unordered list",
22
+ orderedList: "Ordered list",
23
+
24
+ insertImage: "Insert Image",
25
+ insertVideo: "Insert Video",
26
+ link: "Link",
27
+ createLink: "Insert link",
28
+ unlink: "Remove link",
29
+
30
+ justifyLeft: "Align Left",
31
+ justifyCenter: "Align Center",
32
+ justifyRight: "Align Right",
33
+ justifyFull: "Align Justify",
34
+
35
+ horizontalRule: "Insert horizontal rule",
36
+
37
+ fullscreen: "fullscreen",
38
+
39
+ close: "Close",
40
+
41
+ submit: "Confirm",
42
+ reset: "Cancel",
43
+
44
+ invalidUrl: "Invalid URL",
45
+ required: "Required",
46
+ description: "Description",
47
+ title: "Title",
48
+ text: "Text"
49
+ }
50
+ },
51
+
52
+ // User default options
53
+ opts: {},
54
+
55
+ btnsGrps: {
56
+ design: ['bold', 'italic', 'underline', 'strikethrough'],
57
+ semantic: ['strong', 'em', 'del'],
58
+ justify: ['justifyLeft', 'justifyCenter', 'justifyRight', 'justifyFull'],
59
+ lists: ['unorderedList', 'orderedList']
60
+ }
61
+ };
62
+
63
+
64
+
65
+ (function(window, document, $, undefined){
66
+ 'use strict';
67
+
68
+ // @param : o are options
69
+ // @param : p are params
70
+ $.fn.trumbowyg = function(o, p){
71
+ if(o === Object(o) || !o){
72
+ return this.each(function(){
73
+ if(!$(this).data('trumbowyg'))
74
+ $(this).data('trumbowyg', new Trumbowyg(this, o));
75
+ });
76
+ } else if(this.length === 1){
77
+ try {
78
+ var t = $(this).data('trumbowyg');
79
+ switch(o){
80
+ // Modal box
81
+ case 'openModal':
82
+ return t.openModal(p.title, p.content);
83
+ case 'closeModal':
84
+ return t.closeModal();
85
+ case 'openModalInsert':
86
+ return t.openModalInsert(p.title, p.fields, p.callback);
87
+
88
+ // Selection
89
+ case 'saveSelection':
90
+ return t.saveSelection();
91
+ case 'getSelection':
92
+ return t.selection;
93
+ case 'getSelectedText':
94
+ return t.selection+'';
95
+ case 'restoreSelection':
96
+ return t.restoreSelection();
97
+
98
+ // Destroy
99
+ case 'destroy':
100
+ return t.destroy();
101
+
102
+ // Empty
103
+ case 'empty':
104
+ return t.empty();
105
+
106
+ // Public options
107
+ case 'lang':
108
+ return t.lang;
109
+ case 'duration':
110
+ return t.o.duration;
111
+
112
+ // HTML
113
+ case 'html':
114
+ return t.html(p);
115
+ }
116
+ } catch(e){}
117
+ }
118
+
119
+ return false;
120
+ };
121
+
122
+ var Trumbowyg = function(editorElem, opts){
123
+ var t = this;
124
+ // Get the document of the element. It use to makes the plugin
125
+ // compatible on iframes.
126
+ t.doc = editorElem.ownerDocument || document;
127
+ // jQuery object of the editor
128
+ t.$e = $(editorElem);
129
+ t.$creator = $(editorElem);
130
+
131
+ // Extend with options
132
+ opts = $.extend(true, {}, opts, $.trumbowyg.opts);
133
+
134
+ // Localization management
135
+ if(typeof opts.lang === 'undefined' || typeof $.trumbowyg.langs[opts.lang] === 'undefined')
136
+ t.lang = $.trumbowyg.langs.en;
137
+ else
138
+ t.lang = $.extend(true, {}, $.trumbowyg.langs.en, $.trumbowyg.langs[opts.lang]);
139
+
140
+ // Defaults Options
141
+ t.o = $.extend(true, {}, {
142
+ lang: 'en',
143
+ dir: 'ltr',
144
+ duration: 200, // Duration of modal box animations
145
+
146
+ mobile: false,
147
+ tablet: true,
148
+ closable: false,
149
+ fullscreenable: true,
150
+ fixedBtnPane: false,
151
+ fixedFullWidth: false,
152
+ autogrow: false,
153
+
154
+ prefix: 'trumbowyg-',
155
+
156
+ // WYSIWYG only
157
+ convertLink: true, // TODO
158
+ semantic: false,
159
+ resetCss: false,
160
+
161
+ btns: [
162
+ 'viewHTML',
163
+ '|', 'formatting',
164
+ '|', $.trumbowyg.btnsGrps.design,
165
+ '|', 'link',
166
+ '|', 'insertImage',
167
+ '|', $.trumbowyg.btnsGrps.justify,
168
+ '|', $.trumbowyg.btnsGrps.lists,
169
+ '|', 'horizontalRule'
170
+ ],
171
+ btnsAdd: [],
172
+
173
+ /**
174
+ * When the button is associated to a empty object
175
+ * func and title attributs are defined from the button key value
176
+ *
177
+ * For example
178
+ * foo: {}
179
+ * is equivalent to :
180
+ * foo: {
181
+ * func: 'foo',
182
+ * title: this.lang.foo
183
+ * }
184
+ */
185
+ btnsDef: {
186
+ viewHTML: {
187
+ func: 'toggle'
188
+ },
189
+
190
+ p: {
191
+ func: 'formatBlock'
192
+ },
193
+ blockquote: {
194
+ func: 'formatBlock'
195
+ },
196
+ h1: {
197
+ func: 'formatBlock',
198
+ title: t.lang.header + ' 1'
199
+ },
200
+ h2: {
201
+ func: 'formatBlock',
202
+ title: t.lang.header + ' 2'
203
+ },
204
+ h3: {
205
+ func: 'formatBlock',
206
+ title: t.lang.header + ' 3'
207
+ },
208
+ h4: {
209
+ func: 'formatBlock',
210
+ title: t.lang.header + ' 4'
211
+ },
212
+
213
+ bold: {},
214
+ italic: {},
215
+ underline: {},
216
+ strikethrough: {},
217
+
218
+ strong: {
219
+ func: 'bold'
220
+ },
221
+ em: {
222
+ func: 'italic'
223
+ },
224
+ del: {
225
+ func: 'strikethrough'
226
+ },
227
+
228
+ createLink: {},
229
+ unlink: {},
230
+
231
+ insertImage: {},
232
+
233
+ justifyLeft: {},
234
+ justifyCenter: {},
235
+ justifyRight: {},
236
+ justifyFull: {},
237
+
238
+ unorderedList: {
239
+ func: 'insertUnorderedList'
240
+ },
241
+ orderedList: {
242
+ func: 'insertOrderedList'
243
+ },
244
+
245
+ horizontalRule: {
246
+ func: 'insertHorizontalRule'
247
+ },
248
+
249
+ // Dropdowns
250
+ formatting: {
251
+ dropdown: ['p', 'blockquote', 'h1', 'h2', 'h3', 'h4']
252
+ },
253
+ link: {
254
+ dropdown: ['createLink', 'unlink']
255
+ }
256
+ }
257
+ }, opts);
258
+
259
+ if(t.o.semantic && !opts.btns)
260
+ t.o.btns = [
261
+ 'viewHTML',
262
+ '|', 'formatting',
263
+ '|', $.trumbowyg.btnsGrps.semantic,
264
+ '|', 'link',
265
+ '|', 'insertImage',
266
+ '|', $.trumbowyg.btnsGrps.justify,
267
+ '|', $.trumbowyg.btnsGrps.lists,
268
+ '|', 'horizontalRule'
269
+ ];
270
+ else if(opts && opts.btns)
271
+ t.o.btns = opts.btns;
272
+
273
+ t.init();
274
+ };
275
+
276
+ Trumbowyg.prototype = {
277
+ init: function(){
278
+ var t = this;
279
+ t.height = t.$e.css('height');
280
+
281
+ if(t.isEnabled()){
282
+ t.buildEditor(true);
283
+ return;
284
+ }
285
+
286
+ t.buildEditor();
287
+ t.buildBtnPane();
288
+
289
+ t.fixedBtnPaneEvents();
290
+
291
+ t.buildOverlay();
292
+ },
293
+
294
+ buildEditor: function(disable){
295
+ var t = this,
296
+ pfx = t.o.prefix,
297
+ html = '';
298
+
299
+
300
+ if(disable === true){
301
+ if(!t.$e.is('textarea')){
302
+ var textarea = t.buildTextarea().val(t.$e.val());
303
+ t.$e.hide().after(textarea);
304
+ }
305
+ return;
306
+ }
307
+
308
+
309
+ t.$box = $('<div/>', {
310
+ class: pfx + 'box ' + pfx + t.o.lang + ' trumbowyg'
311
+ });
312
+
313
+ t.isTextarea = true;
314
+ if(t.$e.is('textarea'))
315
+ t.$editor = $('<div/>');
316
+ else {
317
+ t.$editor = t.$e;
318
+ t.$e = t.buildTextarea().val(t.$e.val());
319
+ t.isTextarea = false;
320
+ }
321
+
322
+ if(t.$creator.is('[placeholder]'))
323
+ t.$editor.attr('placeholder', t.$creator.attr('placeholder'));
324
+
325
+ t.$e.hide()
326
+ .addClass(pfx + 'textarea');
327
+
328
+
329
+ if(t.isTextarea){
330
+ html = t.$e.val();
331
+ t.$box.insertAfter(t.$e)
332
+ .append(t.$editor)
333
+ .append(t.$e);
334
+ } else {
335
+ html = t.$editor.html();
336
+ t.$box.insertAfter(t.$editor)
337
+ .append(t.$e)
338
+ .append(t.$editor);
339
+ t.syncCode();
340
+ }
341
+
342
+ t.$editor.addClass(pfx + 'editor')
343
+ .attr('contenteditable', true)
344
+ .attr('dir', t.lang._dir || t.o.dir)
345
+ .html(html);
346
+
347
+ if(t.o.resetCss)
348
+ t.$editor.addClass(pfx + 'reset-css');
349
+
350
+ if(!t.o.autogrow){
351
+ $.each([t.$editor, t.$e], function(i, $el){
352
+ $el.css({
353
+ height: t.height,
354
+ overflow: 'auto'
355
+ });
356
+ });
357
+ }
358
+
359
+ if(t.o.semantic){
360
+ t.$editor.html(
361
+ t.$editor.html()
362
+ .replace('<br>', '</p><p>')
363
+ .replace('&nbsp;', '')
364
+ );
365
+ t.semanticCode();
366
+ }
367
+
368
+
369
+
370
+ t.$editor
371
+ .on('dblclick', 'img', function(e){
372
+ var $img = $(this);
373
+ t.openModalInsert(t.lang.insertImage, {
374
+ url: {
375
+ label: 'URL',
376
+ value: $img.attr('src'),
377
+ required: true
378
+ },
379
+ alt: {
380
+ label: 'description',
381
+ value: $img.attr('alt')
382
+ }
383
+ }, function(v){
384
+ $img.attr({
385
+ src: v.url,
386
+ alt: v.alt
387
+ });
388
+ });
389
+ e.stopPropagation();
390
+ })
391
+ .on('keyup', function(e){
392
+ t.semanticCode(false, e.which === 13);
393
+ })
394
+ .on('focus', function(){
395
+ t.$creator.trigger('tbwfocus');
396
+ })
397
+ .on('blur', function(){
398
+ t.syncCode();
399
+ t.$creator.trigger('tbwblur');
400
+ });
401
+ },
402
+
403
+
404
+ // Build the Textarea which contain HTML generated code
405
+ buildTextarea: function(){
406
+ return $('<textarea/>', {
407
+ name: this.$e.attr('id'),
408
+ height: this.height
409
+ });
410
+ },
411
+
412
+
413
+ // Build button pane, use o.btns and o.btnsAdd options
414
+ buildBtnPane: function(){
415
+ var t = this,
416
+ pfx = t.o.prefix;
417
+
418
+ if(t.o.btns === false)
419
+ return;
420
+
421
+ t.$btnPane = $('<ul/>', {
422
+ class: pfx + 'button-pane'
423
+ });
424
+
425
+ $.each(t.o.btns.concat(t.o.btnsAdd), function(i, btn){
426
+ // Managment of group of buttons
427
+ try {
428
+ var b = btn.split('btnGrp-');
429
+ if(b[1] !== undefined)
430
+ btn = $.trumbowyg.btnsGrps[b[1]];
431
+ } catch(e){}
432
+
433
+ if(!$.isArray(btn))
434
+ btn = [btn];
435
+
436
+ $.each(btn, function(i, b){
437
+ try { // Prevent buildBtn error
438
+ var $li = $('<li/>');
439
+
440
+ if(b === '|') // It's a separator
441
+ $li.addClass(pfx + 'separator');
442
+ else if(t.isSupportedBtn(b)) // It's a supported button
443
+ $li.append(t.buildBtn(b));
444
+
445
+ t.$btnPane.append($li);
446
+ } catch(e){}
447
+ });
448
+ });
449
+
450
+ // Build right li for fullscreen and close buttons
451
+ var $liRight = $('<li/>', {
452
+ class: pfx + 'not-disable ' + pfx + 'buttons-right'
453
+ });
454
+
455
+ // Add the fullscreen button
456
+ if(t.o.fullscreenable)
457
+ $liRight.append(
458
+ t.buildRightBtn('fullscreen')
459
+ .on('click', function(){
460
+ var cssClass = pfx + 'fullscreen';
461
+ t.$box.toggleClass(cssClass);
462
+
463
+ if(t.$box.hasClass(cssClass)){
464
+ $('body').css('overflow', 'hidden');
465
+ $.each([t.$editor, t.$e], function(){
466
+ $(this).css({
467
+ height: 'calc(100% - 35px)',
468
+ overflow: 'auto'
469
+ });
470
+ });
471
+ t.$btnPane.css('width', '100%');
472
+ } else {
473
+ $('body').css('overflow', 'auto');
474
+ t.$box.removeAttr('style');
475
+ if(!t.o.autogrow)
476
+ $.each([t.$editor, t.$e], function(){
477
+ $(this).css('height', t.height);
478
+ });
479
+ }
480
+ $(window).trigger('scroll');
481
+ })
482
+ );
483
+
484
+ // Build and add close button
485
+ if(t.o.closable)
486
+ $liRight
487
+ .append(
488
+ t.buildRightBtn('close')
489
+ .on('click', function(){
490
+ if(t.$box.hasClass(pfx + 'fullscreen'))
491
+ $('body').css('overflow', 'auto');
492
+ t.destroy();
493
+ })
494
+ );
495
+
496
+
497
+ // Add right li only if isn't empty
498
+ if($liRight.not(':empty'))
499
+ t.$btnPane.append($liRight);
500
+
501
+ t.$box.prepend(t.$btnPane);
502
+ },
503
+
504
+
505
+ // Build a button and his action
506
+ buildBtn: function(n){ // n is name of the button
507
+ var t = this,
508
+ pfx = t.o.prefix,
509
+ btn = t.o.btnsDef[n],
510
+ d = btn.dropdown,
511
+ textDef = t.lang[n] || n,
512
+
513
+ $btn = $('<button/>', {
514
+ type: 'button',
515
+ class: pfx + n +'-button' + (btn.ico ? ' '+ pfx + btn.ico +'-button' : ''),
516
+ text: btn.text || btn.title || textDef,
517
+ title: btn.title || btn.text || textDef,
518
+ mousedown: function(e){
519
+ if(!d || t.$box.find('.'+n+'-'+pfx + 'dropdown').is(':hidden'))
520
+ $('body', t.doc).trigger('mousedown');
521
+
522
+ if(t.$btnPane.hasClass(pfx + 'disable') && !$(this).hasClass(pfx + 'active') && !$(this).parent().hasClass(pfx + 'not-disable'))
523
+ return false;
524
+
525
+ t.execCmd((d ? 'dropdown' : false) || btn.func || n,
526
+ btn.param || n);
527
+
528
+ e.stopPropagation();
529
+ e.preventDefault();
530
+ }
531
+ });
532
+
533
+
534
+
535
+ if(d){
536
+ $btn.addClass(pfx + 'open-dropdown');
537
+ var c = pfx + 'dropdown',
538
+ dd = $('<div/>', { // the dropdown
539
+ class: n + '-' + c + ' ' + c + ' ' + pfx + 'fixed-top'
540
+ });
541
+ $.each(d, function(i, def){
542
+ if(t.o.btnsDef[def] && t.isSupportedBtn(def))
543
+ dd.append(t.buildSubBtn(def));
544
+ });
545
+ t.$box.append(dd.hide());
546
+ }
547
+
548
+ return $btn;
549
+ },
550
+ // Build a button for dropdown menu
551
+ // @param n : name of the subbutton
552
+ buildSubBtn: function(n){
553
+ var t = this,
554
+ btnDef = t.o.btnsDef[n];
555
+ return $('<button/>', {
556
+ type: 'button',
557
+ text: btnDef.text || btnDef.title || t.lang[n] || n,
558
+ style: btnDef.style || null,
559
+ mousedown: function(e){
560
+ $('body', t.doc).trigger('mousedown');
561
+
562
+ t.execCmd(btnDef.func || n,
563
+ btnDef.param || n);
564
+
565
+ e.stopPropagation();
566
+ }
567
+ });
568
+ },
569
+ // Build a button for right li
570
+ // @param n : name of the right button
571
+ buildRightBtn: function(n){
572
+ return $('<button/>', {
573
+ type: 'button',
574
+ class: this.o.prefix + n + '-button',
575
+ title: this.lang[n],
576
+ text: this.lang[n]
577
+ });
578
+ },
579
+ // Check if button is supported
580
+ isSupportedBtn: function(btn){
581
+ var def = this.o.btnsDef[btn];
582
+ return typeof def.isSupported !== 'function' || def.isSupported();
583
+ },
584
+
585
+ // Build overlay for modal box
586
+ buildOverlay: function(){
587
+ var t = this;
588
+ t.$overlay = $('<div/>', {
589
+ class: t.o.prefix + 'overlay'
590
+ }).css({
591
+ top: t.$btnPane.outerHeight(),
592
+ height: (parseInt(t.$editor.outerHeight()) + 1) + 'px'
593
+ }).appendTo(t.$box);
594
+ return t.$overlay;
595
+ },
596
+ showOverlay: function(){
597
+ var t = this;
598
+ $(window).trigger('scroll');
599
+ t.$overlay.fadeIn(t.o.duration);
600
+ t.$box.addClass(t.o.prefix + 'box-blur');
601
+ },
602
+ hideOverlay: function(){
603
+ var t = this;
604
+ t.$overlay.fadeOut(t.o.duration/4);
605
+ t.$box.removeClass(t.o.prefix + 'box-blur');
606
+ },
607
+
608
+ // Management of fixed button pane
609
+ fixedBtnPaneEvents: function(){
610
+ var t = this,
611
+ ffw = t.o.fixedFullWidth;
612
+ if(!t.o.fixedBtnPane)
613
+ return;
614
+
615
+ t.isFixed = false;
616
+
617
+ $(window)
618
+ .on('scroll resize', function(){
619
+ if(!t.$box)
620
+ return;
621
+
622
+ t.syncCode();
623
+
624
+ var s = $(window).scrollTop(), // s is top scroll
625
+ o = t.$box.offset().top + 1, // o is offset
626
+ toFixed = (s - o > 0) && ((s - o - parseInt(t.height)) < 0),
627
+ bp = t.$btnPane,
628
+ mt = bp.css('height'),
629
+ oh = bp.outerHeight();
630
+
631
+ if(toFixed){
632
+ if(!t.isFixed){
633
+ t.isFixed = true;
634
+ bp.css({
635
+ position: 'fixed',
636
+ top: 0,
637
+ left: ffw ? '0' : 'auto',
638
+ zIndex: 7
639
+ });
640
+ $([t.$editor, t.$e]).css({ marginTop: mt });
641
+ }
642
+ bp.css({
643
+ width: ffw ? '100%' : ((parseInt(t.$box.css('width'))-1) + 'px')
644
+ });
645
+
646
+ $('.' + t.o.prefix + 'fixed-top', t.$box).css({
647
+ position: ffw ? 'fixed' : 'absolute',
648
+ top: ffw ? oh : parseInt(oh) + (s - o) + 'px',
649
+ zIndex: 15
650
+ });
651
+ } else if(t.isFixed) {
652
+ t.isFixed = false;
653
+ bp.removeAttr('style');
654
+ $([t.$editor, t.$e]).css({ marginTop: 0 });
655
+ $('.' + t.o.prefix + 'fixed-top', t.$box).css({
656
+ position: 'absolute',
657
+ top: oh
658
+ });
659
+ }
660
+ });
661
+ },
662
+
663
+
664
+
665
+ // Destroy the editor
666
+ destroy: function(){
667
+ var t = this,
668
+ pfx = t.o.prefix,
669
+ h = t.height,
670
+ html = t.html();
671
+
672
+ if(t.isTextarea)
673
+ t.$box.after(
674
+ t.$e.css({ height: h })
675
+ .val(html)
676
+ .removeClass(pfx + 'textarea')
677
+ .show()
678
+ );
679
+ else
680
+ t.$box.after(
681
+ t.$editor
682
+ .css({ height: h })
683
+ .removeClass(pfx + 'editor')
684
+ .removeAttr('contenteditable')
685
+ .html(html)
686
+ .show()
687
+ );
688
+
689
+ t.$box.remove();
690
+ t.$creator.removeData('trumbowyg');
691
+ },
692
+
693
+
694
+
695
+ // Empty the editor
696
+ empty: function(){
697
+ this.$e.val('');
698
+ this.syncCode(true);
699
+ },
700
+
701
+
702
+
703
+ // Function call when click on viewHTML button
704
+ toggle: function(){
705
+ var t = this,
706
+ pfx = t.o.prefix;
707
+ t.semanticCode(false, true);
708
+ t.$editor.toggle();
709
+ t.$e.toggle();
710
+ t.$btnPane.toggleClass(pfx + 'disable');
711
+ t.$btnPane.find('.'+pfx + 'viewHTML-button').toggleClass(pfx + 'active');
712
+ },
713
+
714
+ // Open dropdown when click on a button which open that
715
+ dropdown: function(name){
716
+ var t = this,
717
+ d = t.doc,
718
+ pfx = t.o.prefix,
719
+ $dropdown = t.$box.find('.'+name+'-'+pfx + 'dropdown'),
720
+ $btn = t.$btnPane.find('.'+pfx+name+'-button');
721
+
722
+ if($dropdown.is(':hidden')){
723
+ var o = $btn.offset().left;
724
+ $btn.addClass(pfx + 'active');
725
+
726
+ $dropdown.css({
727
+ position: 'absolute',
728
+ top: t.$btnPane.outerHeight(),
729
+ left: (t.o.fixedFullWidth && t.isFixed) ? o+'px' : (o - t.$btnPane.offset().left)+'px'
730
+ }).show();
731
+
732
+ $(window).trigger('scroll');
733
+
734
+ $('body', d).on('mousedown', function(){
735
+ $('.' + pfx + 'dropdown', d).hide();
736
+ $('.' + pfx + 'active', d).removeClass(pfx + 'active');
737
+ $('body', d).off('mousedown');
738
+ });
739
+ } else
740
+ $('body', d).trigger('mousedown');
741
+ },
742
+
743
+
744
+
745
+
746
+ // HTML Code management
747
+ html: function(html){
748
+ var t = this;
749
+ if(html){
750
+ t.$e.val(html);
751
+ t.syncCode(true);
752
+ return t;
753
+ } else
754
+ return t.$e.val();
755
+ },
756
+ syncCode: function(force){
757
+ var t = this;
758
+ if(!force && t.$editor.is(':visible'))
759
+ t.$e.val(t.$editor.html());
760
+ else
761
+ t.$editor.html(t.$e.val());
762
+
763
+ if(t.o.autogrow){
764
+ t.height = t.$editor.css('height');
765
+ t.$e.css({ height: t.height });
766
+ }
767
+ },
768
+
769
+ // Analyse and update to semantic code
770
+ // @param force : force to sync code from textarea
771
+ // @param full : wrap text nodes in <p>
772
+ semanticCode: function(force, full){
773
+ var t = this;
774
+ t.syncCode(force);
775
+
776
+ if(t.o.semantic){
777
+ t.semanticTag('b', 'strong');
778
+ t.semanticTag('i', 'em');
779
+ t.semanticTag('strike', 'del');
780
+
781
+ if(full){
782
+ // Wrap text nodes in p
783
+ t.$editor.contents()
784
+ .filter(function(){
785
+ // Only non-empty text nodes
786
+ return this.nodeType === 3 && $.trim(this.nodeValue).length > 0;
787
+ }).wrap('<p></p>').end()
788
+
789
+ // Remove all br
790
+ .filter('br').remove();
791
+
792
+ t.saveSelection();
793
+ t.semanticTag('div', 'p');
794
+ t.restoreSelection();
795
+ }
796
+
797
+ t.$e.val(t.$editor.html());
798
+ }
799
+ },
800
+ semanticTag: function(oldTag, newTag){
801
+ $(oldTag, this.$editor).each(function(){
802
+ $(this).replaceWith(function(){
803
+ return '<'+newTag+'>' + $(this).html() + '</'+newTag+'>';
804
+ });
805
+ });
806
+ },
807
+
808
+
809
+ // Function call when user click on "Insert Link"
810
+ createLink: function(){
811
+ var t = this;
812
+ t.saveSelection();
813
+ t.openModalInsert(t.lang.createLink, {
814
+ url: {
815
+ label: 'URL',
816
+ value: 'http://',
817
+ required: true
818
+ },
819
+ title: {
820
+ label: t.lang.title,
821
+ value: t.selection
822
+ },
823
+ text: {
824
+ label: t.lang.text,
825
+ value: t.selection
826
+ }
827
+ }, function(v){ // v is value
828
+ t.execCmd('createLink', v.url);
829
+ var l = $('a[href="'+v.url+'"]:not([title])', t.$box);
830
+ if(v.text.length > 0)
831
+ l.text(v.text);
832
+
833
+ if(v.title.length > 0)
834
+ l.attr('title', v.title);
835
+
836
+ return true;
837
+ });
838
+ },
839
+ insertImage: function(){
840
+ var t = this;
841
+ t.saveSelection();
842
+ t.openModalInsert(t.lang.insertImage, {
843
+ url: {
844
+ label: 'URL',
845
+ value: 'http://',
846
+ required: true
847
+ },
848
+ alt: {
849
+ label: t.lang.description,
850
+ value: t.selection
851
+ }
852
+ }, function(v){ // v are values
853
+ t.execCmd('insertImage', v.url);
854
+ $('img[src="'+v.url+'"]:not([alt])', t.$box).attr('alt', v.alt);
855
+ return true;
856
+ });
857
+ },
858
+
859
+
860
+ /*
861
+ * Call method of trumbowyg if exist
862
+ * else try to call anonymous function
863
+ * and finaly native execCommand
864
+ */
865
+ execCmd: function(cmd, param){
866
+ var t = this;
867
+ if(cmd != 'dropdown')
868
+ t.$editor.focus();
869
+
870
+ try {
871
+ t[cmd](param);
872
+ } catch(e){
873
+ try {
874
+ cmd(param, t);
875
+ } catch(e2){
876
+ //t.$editor.focus();
877
+ if(cmd == 'insertHorizontalRule')
878
+ param = null;
879
+ else if(cmd == 'formatBlock' && (navigator.userAgent.indexOf('MSIE') !== -1 || navigator.appVersion.indexOf('Trident/') > 0))
880
+ param = '<' + param + '>';
881
+
882
+ t.doc.execCommand(cmd, false, param);
883
+ }
884
+ }
885
+ t.syncCode();
886
+ },
887
+
888
+
889
+ // Open a modal box
890
+ openModal: function(title, content){
891
+ var t = this,
892
+ pfx = t.o.prefix;
893
+
894
+ // No open a modal box when exist other modal box
895
+ if($('.' + pfx + 'modal-box', t.$box).size() > 0)
896
+ return false;
897
+
898
+ t.saveSelection();
899
+ t.showOverlay();
900
+
901
+ // Disable all btnPane btns
902
+ t.$btnPane.addClass(pfx + 'disable');
903
+
904
+ // Build out of ModalBox, it's the mask for animations
905
+ var $modal = $('<div/>', {
906
+ class: pfx + 'modal ' + pfx + 'fixed-top'
907
+ }).css({
908
+ top: (parseInt(t.$btnPane.css('height')) + 1) + 'px'
909
+ }).appendTo(t.$box);
910
+
911
+ // Click on overflay close modal by cancelling them
912
+ t.$overlay.one('click', function(e){
913
+ e.preventDefault();
914
+ $modal.trigger(pfx + 'cancel');
915
+ });
916
+
917
+ // Build the form
918
+ var $form = $('<form/>', {
919
+ action: '',
920
+ html: content
921
+ })
922
+ .on('submit', function(e){
923
+ e.preventDefault();
924
+ $modal.trigger(pfx + 'confirm');
925
+ })
926
+ .on('reset', function(e){
927
+ e.preventDefault();
928
+ $modal.trigger(pfx + 'cancel');
929
+ });
930
+
931
+
932
+ // Build ModalBox and animate to show them
933
+ var $box = $('<div/>', {
934
+ class: pfx + 'modal-box',
935
+ html: $form
936
+ })
937
+ .css({
938
+ top: '-' + parseInt(t.$btnPane.outerHeight()) + 'px',
939
+ opacity: 0
940
+ })
941
+ .appendTo($modal)
942
+ .animate({
943
+ top: 0,
944
+ opacity: 1
945
+ }, t.o.duration / 2);
946
+
947
+
948
+ // Append title
949
+ $('<span/>', {
950
+ text: title,
951
+ class: pfx + 'modal-title'
952
+ }).prependTo($box);
953
+
954
+
955
+ // Focus in modal box
956
+ $box.find('input:first').focus();
957
+
958
+
959
+ // Append Confirm and Cancel buttons
960
+ t.buildModalBtn('submit', $box);
961
+ t.buildModalBtn('reset', $box);
962
+
963
+
964
+ $(window).trigger('scroll');
965
+
966
+ return $modal;
967
+ },
968
+ // @param n is name of modal
969
+ buildModalBtn: function(n, modal){
970
+ var t = this,
971
+ pfx = t.o.prefix;
972
+
973
+ return $('<button/>', {
974
+ class: pfx + 'modal-button ' + pfx + 'modal-' + n,
975
+ type: n,
976
+ text: t.lang[n] || n
977
+ }).appendTo(modal.find('form'));
978
+ },
979
+ // close current modal box
980
+ closeModal: function(){
981
+ var t = this,
982
+ pfx = t.o.prefix;
983
+
984
+ t.$btnPane.removeClass(pfx + 'disable');
985
+ t.$overlay.off();
986
+
987
+ var $modalBox = $('.' + pfx + 'modal-box', t.$box);
988
+
989
+ $modalBox.animate({
990
+ top: '-' + $modalBox.css('height')
991
+ }, t.o.duration/2, function(){
992
+ $(this).parent().remove();
993
+ t.hideOverlay();
994
+ });
995
+ },
996
+ // Preformated build and management modal
997
+ openModalInsert: function(title, fields, cmd){
998
+ var t = this,
999
+ pfx = t.o.prefix,
1000
+ lg = t.lang,
1001
+ html = '';
1002
+
1003
+ for(var f in fields){
1004
+ var fd = fields[f], // field definition
1005
+ label = (fd.label === undefined) ? (lg[f] ? lg[f] : f) : (lg[fd.label] ? lg[fd.label] : fd.label);
1006
+
1007
+ if(fd.name === undefined)
1008
+ fd.name = f;
1009
+
1010
+ if(!fd.pattern && f === 'url'){
1011
+ fd.pattern = /^(http|https):\/\/([\w~#!:.?+=&%@!\-\/]+)$/;
1012
+ fd.patternError = lg.invalidUrl;
1013
+ }
1014
+
1015
+ html += '<label><input type="'+(fd.type || 'text')+'" name="'+fd.name+'" value="'+(fd.value || '')+'"><span class="'+pfx+'input-infos"><span>'+label+'</span></span></label>';
1016
+ }
1017
+
1018
+ return t.openModal(title, html)
1019
+ .on(pfx + 'confirm', function(){
1020
+ var $form = $(this).find('form'),
1021
+ valid = true,
1022
+ v = {}; // values
1023
+
1024
+ for(var f in fields){
1025
+ var $field = $('input[name="'+f+'"]', $form);
1026
+
1027
+ v[f] = $.trim($field.val());
1028
+
1029
+ // Validate value
1030
+ if(fields[f].required && v[f] === ''){
1031
+ valid = false;
1032
+ t.addErrorOnModalField($field, t.lang.required);
1033
+ } else if(fields[f].pattern && !fields[f].pattern.test(v[f])){
1034
+ valid = false;
1035
+ t.addErrorOnModalField($field, fields[f].patternError);
1036
+ }
1037
+ }
1038
+
1039
+ if(valid){
1040
+ t.restoreSelection();
1041
+
1042
+ if(cmd(v, fields)){
1043
+ t.syncCode();
1044
+ t.closeModal();
1045
+ $(this).off(pfx + 'confirm');
1046
+ }
1047
+ }
1048
+ })
1049
+ .one(pfx + 'cancel', function(){
1050
+ $(this).off(pfx + 'confirm');
1051
+ t.closeModal();
1052
+ t.restoreSelection();
1053
+ });
1054
+ },
1055
+ addErrorOnModalField: function($field, err){
1056
+ var pfx = this.o.prefix,
1057
+ $label = $field.parent();
1058
+
1059
+ $field.on('change keyup', function(){
1060
+ $label.removeClass(pfx + 'input-error');
1061
+ });
1062
+ $label
1063
+ .addClass(pfx + 'input-error')
1064
+ .find('input+span').append(
1065
+ $('<span/>', {
1066
+ class: pfx +'msg-error',
1067
+ text: err
1068
+ })
1069
+ );
1070
+ },
1071
+
1072
+
1073
+
1074
+
1075
+ // Selection management
1076
+ saveSelection: function(){
1077
+ var t = this,
1078
+ d = t.doc;
1079
+
1080
+ t.selection = null;
1081
+ if(window.getSelection){
1082
+ var s = window.getSelection();
1083
+ if(s.getRangeAt && s.rangeCount)
1084
+ t.selection = s.getRangeAt(0);
1085
+ } else if(d.selection && d.selection.createRange)
1086
+ t.selection = d.selection.createRange();
1087
+ },
1088
+ restoreSelection: function(){
1089
+ var t = this,
1090
+ range = t.selection;
1091
+
1092
+ if(range){
1093
+ if(window.getSelection){
1094
+ var s = window.getSelection();
1095
+ s.removeAllRanges();
1096
+ s.addRange(range);
1097
+ } else if(t.doc.selection && range.select)
1098
+ range.select();
1099
+ }
1100
+ },
1101
+
1102
+
1103
+
1104
+ // Return true if must enable Trumbowyg on this mobile device
1105
+ isEnabled: function(){
1106
+ var exprTablet = new RegExp("(iPad|webOS)"),
1107
+ exprMobile = new RegExp("(iPhone|iPod|Android|BlackBerry|Windows Phone|ZuneWP7)"),
1108
+ ua = navigator.userAgent;
1109
+
1110
+ return (this.o.tablet === true && exprTablet.test(ua)) || (this.o.mobile === true && exprMobile.test(ua));
1111
+ }
1112
+ };
1113
+ })(window, document, jQuery);