pages_core 3.10.2 → 3.11.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -6,6 +6,7 @@ module PagesCore
6
6
  include PagesCore::Admin::ContentTabsHelper
7
7
  include PagesCore::Admin::DateRangeHelper
8
8
  include PagesCore::Admin::ImageUploadsHelper
9
+ include PagesCore::Admin::LocalesHelper
9
10
  include PagesCore::Admin::PageJsonHelper
10
11
  include PagesCore::Admin::LabelledFieldHelper
11
12
  include PagesCore::Admin::TagEditorHelper
@@ -4,12 +4,13 @@ module PagesCore
4
4
  module Admin
5
5
  class FormBuilder < PagesCore::FormBuilder
6
6
  include DynamicImage::Helper
7
+ include PagesCore::Admin::LocalizedFormBuilder
7
8
 
8
9
  def rich_text_area(attr, options = {})
9
10
  @template.rich_text_area_tag(
10
11
  "#{object_name}[#{attr}]",
11
12
  object.send(attr),
12
- options
13
+ localized_form_field_options(attr).merge(options)
13
14
  )
14
15
  end
15
16
 
@@ -39,7 +39,7 @@ module PagesCore
39
39
  width: width,
40
40
  caption: caption,
41
41
  locale: locale || I18n.default_locale,
42
- locales: PagesCore.config.locales
42
+ locales: locales_with_dir
43
43
  )
44
44
  end
45
45
  end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PagesCore
4
+ module Admin
5
+ module LocalesHelper
6
+ def locales_with_dir
7
+ locales = PagesCore.config.locales || {}
8
+ locales.each_with_object({}) do |(key, name), hash|
9
+ hash[key] = { name: name, dir: locale_direction(key) }
10
+ end
11
+ end
12
+
13
+ def locale_direction(locale)
14
+ rtl_locale?(locale) ? "rtl" : "ltr"
15
+ end
16
+
17
+ def rtl_locale?(locale)
18
+ rtl_locales.include?(locale.to_s)
19
+ end
20
+
21
+ def rtl_locales
22
+ %w[ar arc dv fa ha he khw ks ku ps ur yi]
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PagesCore
4
+ module Admin
5
+ module LocalizedFormBuilder
6
+ include PagesCore::Admin::LocalesHelper
7
+
8
+ def text_area(method, options = {})
9
+ super(method, localized_form_field_options(method).merge(options))
10
+ end
11
+
12
+ def text_field(method, options = {})
13
+ super(method, localized_form_field_options(method).merge(options))
14
+ end
15
+
16
+ private
17
+
18
+ def localized_form_field_options(method)
19
+ unless object.is_a?(LocalizableModel::InstanceMethods) &&
20
+ object.class.localized_attributes.include?(method.to_sym)
21
+ return {}
22
+ end
23
+
24
+ { dir: rtl_locale?(object.locale) ? "rtl" : "ltr",
25
+ lang: object.locale }
26
+ end
27
+ end
28
+ end
29
+ end
@@ -3,12 +3,7 @@
3
3
  module PagesCore
4
4
  class FormBuilder < ActionView::Helpers::FormBuilder
5
5
  include ActionView::Helpers::TagHelper
6
-
7
- def field_with_label(attr, str, label = nil, class_name = nil)
8
- classes = ["field", class_name]
9
- classes << "field-with-errors" if object.errors[attr].any?
10
- tag.div(label_for(attr, label) + str, class: classes.compact.join(" "))
11
- end
6
+ include PagesCore::LabelledFormBuilder
12
7
 
13
8
  def image_file_preview(attribute)
14
9
  return "" unless object.send(attribute) &&
@@ -22,103 +17,5 @@ module PagesCore
22
17
  def image_file_field(attribute, options = {})
23
18
  safe_join [image_file_preview(attribute), file_field(attribute, options)]
24
19
  end
25
-
26
- def label_and_errors(attribute, label_text)
27
- return label_text unless object.errors[attribute].any?
28
-
29
- error = tag.span(object.errors[attribute].first, class: "error")
30
- safe_join([label_text, error], " ")
31
- end
32
-
33
- def label_for(attribute, label_text = nil)
34
- label_text ||= object.class.human_attribute_name(attribute)
35
- tag.label(label_and_errors(attribute, label_text),
36
- for: [object.class.to_s.underscore, attribute].join("_"))
37
- end
38
-
39
- def labelled_check_box(
40
- attr, label = nil, options = {}, checked = "1", unchecked = "0"
41
- )
42
- labelled_field(attr, label, options) do |opts|
43
- check_box(attr, opts, checked, unchecked)
44
- end
45
- end
46
-
47
- def labelled_country_select(
48
- attr, label = nil, priority = {}, opts = {}, html_opts = {}
49
- )
50
- if priority.is_a?(Hash)
51
- return labelled_field(attr, label, priority) do |options|
52
- country_select(attr, options, opts, html_opts)
53
- end
54
- end
55
- labelled_field(attr, label, opts) do |options|
56
- country_select(attr, priority, options, html_opts)
57
- end
58
- end
59
-
60
- def labelled_date_select(attribute, label_text = nil, options = {})
61
- labelled_field(attribute, label_text, options) do |opts|
62
- date_select(attribute, opts)
63
- end
64
- end
65
-
66
- def labelled_datetime_select(attribute, label_text = nil, options = {})
67
- labelled_field(attribute, label_text, options) do |opts|
68
- datetime_select(attribute, opts)
69
- end
70
- end
71
-
72
- def labelled_file_field(attribute, label_text = nil, options = {})
73
- labelled_field(attribute, label_text, options) do |opts|
74
- file_field(attribute, opts)
75
- end
76
- end
77
-
78
- def labelled_image_file_field(attribute, label_text = nil, options = {})
79
- labelled_field(attribute, label_text, options) do |opts|
80
- image_file_field(attribute, opts)
81
- end
82
- end
83
-
84
- def labelled_password_field(attribute, label_text = nil, options = {})
85
- labelled_field(attribute, label_text, options, "text-field") do |opts|
86
- password_field(attribute, opts)
87
- end
88
- end
89
-
90
- def labelled_select(attribute, choices, label_text = nil, options = {})
91
- labelled_field(attribute, label_text, options) do |opts|
92
- select(attribute, choices, opts)
93
- end
94
- end
95
-
96
- def labelled_text_area(attribute, label_text = nil, options = {})
97
- labelled_field(attribute, label_text, options, "text-area") do |opts|
98
- text_area(attribute, opts)
99
- end
100
- end
101
-
102
- def labelled_text_field(attribute, label_text = nil, options = {})
103
- labelled_field(attribute, label_text, options, "text-field") do |opts|
104
- text_field(attribute, opts)
105
- end
106
- end
107
-
108
- def labelled_time_select(attribute, label_text = nil, options = {})
109
- labelled_field(attribute, label_text, options) do |opts|
110
- time_select(attribute, opts)
111
- end
112
- end
113
-
114
- protected
115
-
116
- def labelled_field(attr, label_text = nil, options = {}, class_name = nil)
117
- if label_text.is_a?(Hash) && options == {}
118
- options = label_text
119
- label_text = nil
120
- end
121
- field_with_label(attr, yield(options), label_text, class_name)
122
- end
123
20
  end
124
21
  end
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PagesCore
4
+ module LabelledFormBuilder
5
+ def field_with_label(attr, str, label = nil, class_name = nil)
6
+ classes = ["field", class_name]
7
+ classes << "field-with-errors" if object.errors[attr].any?
8
+ tag.div(label_for(attr, label) + str, class: classes.compact.join(" "))
9
+ end
10
+
11
+ def label_and_errors(attribute, label_text)
12
+ return label_text unless object.errors[attribute].any?
13
+
14
+ error = tag.span(object.errors[attribute].first, class: "error")
15
+ safe_join([label_text, error], " ")
16
+ end
17
+
18
+ def label_for(attribute, label_text = nil)
19
+ label_text ||= object.class.human_attribute_name(attribute)
20
+ tag.label(label_and_errors(attribute, label_text),
21
+ for: [object.class.to_s.underscore, attribute].join("_"))
22
+ end
23
+
24
+ def labelled_check_box(
25
+ attr, label = nil, options = {}, checked = "1", unchecked = "0"
26
+ )
27
+ labelled_field(attr, label, options) do |opts|
28
+ check_box(attr, opts, checked, unchecked)
29
+ end
30
+ end
31
+
32
+ def labelled_country_select(
33
+ attr, label = nil, priority = {}, opts = {}, html_opts = {}
34
+ )
35
+ if priority.is_a?(Hash)
36
+ return labelled_field(attr, label, priority) do |options|
37
+ country_select(attr, options, opts, html_opts)
38
+ end
39
+ end
40
+ labelled_field(attr, label, opts) do |options|
41
+ country_select(attr, priority, options, html_opts)
42
+ end
43
+ end
44
+
45
+ def labelled_date_select(attribute, label_text = nil, options = {})
46
+ labelled_field(attribute, label_text, options) do |opts|
47
+ date_select(attribute, opts)
48
+ end
49
+ end
50
+
51
+ def labelled_datetime_select(attribute, label_text = nil, options = {})
52
+ labelled_field(attribute, label_text, options) do |opts|
53
+ datetime_select(attribute, opts)
54
+ end
55
+ end
56
+
57
+ def labelled_file_field(attribute, label_text = nil, options = {})
58
+ labelled_field(attribute, label_text, options) do |opts|
59
+ file_field(attribute, opts)
60
+ end
61
+ end
62
+
63
+ def labelled_image_file_field(attribute, label_text = nil, options = {})
64
+ labelled_field(attribute, label_text, options) do |opts|
65
+ image_file_field(attribute, opts)
66
+ end
67
+ end
68
+
69
+ def labelled_password_field(attribute, label_text = nil, options = {})
70
+ labelled_field(attribute, label_text, options, "text-field") do |opts|
71
+ password_field(attribute, opts)
72
+ end
73
+ end
74
+
75
+ def labelled_select(attribute, choices, label_text = nil, options = {})
76
+ labelled_field(attribute, label_text, options) do |opts|
77
+ select(attribute, choices, opts)
78
+ end
79
+ end
80
+
81
+ def labelled_text_area(attribute, label_text = nil, options = {})
82
+ labelled_field(attribute, label_text, options, "text-area") do |opts|
83
+ text_area(attribute, opts)
84
+ end
85
+ end
86
+
87
+ def labelled_text_field(attribute, label_text = nil, options = {})
88
+ labelled_field(attribute, label_text, options, "text-field") do |opts|
89
+ text_field(attribute, opts)
90
+ end
91
+ end
92
+
93
+ def labelled_time_select(attribute, label_text = nil, options = {})
94
+ labelled_field(attribute, label_text, options) do |opts|
95
+ time_select(attribute, opts)
96
+ end
97
+ end
98
+
99
+ protected
100
+
101
+ def labelled_field(attr, label_text = nil, options = {}, class_name = nil)
102
+ if label_text.is_a?(Hash) && options == {}
103
+ options = label_text
104
+ label_text = nil
105
+ end
106
+ field_with_label(attr, yield(options), label_text, class_name)
107
+ end
108
+ end
109
+ end
@@ -19,6 +19,8 @@ export default function Form(props) {
19
19
  props.setLocale(evt.target.value);
20
20
  };
21
21
 
22
+ const inputDir = locales[locale].dir || "ltr";
23
+
22
24
  return (
23
25
  <form>
24
26
  <div className="field embed-code">
@@ -44,7 +46,7 @@ export default function Form(props) {
44
46
  onChange={handleChangeLocale}>
45
47
  {Object.keys(locales).map(key => (
46
48
  <option key={`locale-${key}`} value={key}>
47
- {locales[key]}
49
+ {locales[key].name}
48
50
  </option>
49
51
  ))}
50
52
  </select>
@@ -59,6 +61,8 @@ export default function Form(props) {
59
61
  </span>
60
62
  <textarea
61
63
  className="alternative"
64
+ lang={locale}
65
+ dir={inputDir}
62
66
  value={alternative[locale] || ""}
63
67
  onChange={e => props.updateLocalization("alternative", e.target.value)} />
64
68
  </div>
@@ -68,6 +72,8 @@ export default function Form(props) {
68
72
  Caption
69
73
  </label>
70
74
  <textarea
75
+ lang={locale}
76
+ dir={inputDir}
71
77
  onChange={e => props.updateLocalization("caption", e.target.value)}
72
78
  value={caption[locale] || ""}
73
79
  className="caption" />
@@ -139,7 +139,9 @@ export default class PageTree extends React.Component {
139
139
  movedPage={this.movedPage}
140
140
  toggleCollapsed={this.toggleCollapsed}
141
141
  updatePage={this.updatePage}
142
- updateTree={this.updateTree} />
142
+ updateTree={this.updateTree}
143
+ locale={this.props.locale}
144
+ dir={this.props.dir} />
143
145
  );
144
146
  }
145
147
 
@@ -189,5 +191,6 @@ export default class PageTree extends React.Component {
189
191
  PageTree.propTypes = {
190
192
  pages: PropTypes.array,
191
193
  locale: PropTypes.string,
194
+ dir: PropTypes.string,
192
195
  permissions: PropTypes.array
193
196
  };
@@ -75,7 +75,7 @@ export default class PageTreeDraggable extends React.Component {
75
75
  }
76
76
 
77
77
  render() {
78
- var tree = this.props.tree;
78
+ const { tree, dir, locale } = this.props;
79
79
  var dragging = this.state.dragging;
80
80
 
81
81
  if (!tree) {
@@ -90,16 +90,17 @@ export default class PageTreeDraggable extends React.Component {
90
90
  <div className="page-tree">
91
91
  {this.getDraggingDom()}
92
92
  <PageTreeNode
93
- tree={tree}
94
- index={root}
95
- key={root.id}
96
- paddingLeft={this.props.paddingLeft}
97
- addChild={id => this.addChild(id)}
98
- onDragStart={(id, dom, e) => this.dragStart(id, dom, e)}
99
- onCollapse={nodeId => this.toggleCollapse(nodeId)}
100
- updatePage={(idx, attrs) => this.updatePage(idx, attrs)}
101
- dragging={dragging && dragging.id}
102
- />
93
+ tree={tree}
94
+ index={root}
95
+ key={root.id}
96
+ paddingLeft={this.props.paddingLeft}
97
+ addChild={id => this.addChild(id)}
98
+ onDragStart={(id, dom, e) => this.dragStart(id, dom, e)}
99
+ onCollapse={nodeId => this.toggleCollapse(nodeId)}
100
+ updatePage={(idx, attrs) => this.updatePage(idx, attrs)}
101
+ dragging={dragging && dragging.id}
102
+ dir={dir}
103
+ locale={locale} />
103
104
  </div>
104
105
  );
105
106
  }
@@ -303,4 +304,6 @@ PageTreeDraggable.propTypes = {
303
304
  paddingLeft: PropTypes.number,
304
305
  updatePage: PropTypes.func,
305
306
  updateTree: PropTypes.func,
307
+ locale: PropTypes.string,
308
+ dir: PropTypes.string
306
309
  };
@@ -125,9 +125,7 @@ export default class PageTreeNode extends React.Component {
125
125
  }
126
126
 
127
127
  childNodes() {
128
- let index = this.props.index;
129
- let tree = this.props.tree;
130
- let dragging = this.props.dragging;
128
+ const { index, tree, dragging, dir, locale } = this.props;
131
129
 
132
130
  if (index.children && index.children.length && !index.node.collapsed) {
133
131
  var childrenStyles = {};
@@ -139,21 +137,22 @@ export default class PageTreeNode extends React.Component {
139
137
  return (
140
138
  <div className="children" style={childrenStyles}>
141
139
  {index.children.map((child) => {
142
- var childIndex = tree.getIndex(child);
143
- return (
144
- <PageTreeNode
145
- tree={tree}
146
- index={childIndex}
147
- key={childIndex.id}
148
- dragging={dragging}
149
- paddingLeft={this.props.paddingLeft}
150
- addChild={this.props.addChild}
151
- onCollapse={this.props.onCollapse}
152
- onDragStart={this.props.onDragStart}
153
- updatePage={this.props.updatePage}
154
- />
155
- );
156
- })}
140
+ var childIndex = tree.getIndex(child);
141
+ return (
142
+ <PageTreeNode
143
+ tree={tree}
144
+ index={childIndex}
145
+ key={childIndex.id}
146
+ dragging={dragging}
147
+ paddingLeft={this.props.paddingLeft}
148
+ addChild={this.props.addChild}
149
+ onCollapse={this.props.onCollapse}
150
+ onDragStart={this.props.onDragStart}
151
+ updatePage={this.props.updatePage}
152
+ dir={dir}
153
+ locale={locale} />
154
+ );
155
+ })}
157
156
  </div>
158
157
  );
159
158
  }
@@ -232,11 +231,14 @@ export default class PageTreeNode extends React.Component {
232
231
  }
233
232
 
234
233
  pageName() {
235
- if (this.node().name) {
236
- return this.node().name;
237
- } else {
238
- return <i className="untitled">Untitled</i>;
239
- }
234
+ const name = this.node().name || <i className="untitled">Untitled</i>;
235
+
236
+ return(
237
+ <span dir={this.props.dir}
238
+ lang={this.props.locale}>
239
+ {name}
240
+ </span>
241
+ );
240
242
  }
241
243
 
242
244
  render() {
@@ -278,6 +280,7 @@ export default class PageTreeNode extends React.Component {
278
280
  }
279
281
 
280
282
  renderEditNode() {
283
+ const { dir, locale } = this.props;
281
284
  let self = this;
282
285
 
283
286
  let handleNameChange = function(event) {
@@ -303,6 +306,8 @@ export default class PageTreeNode extends React.Component {
303
306
  <form onSubmit={performEdit}>
304
307
  <input type="text"
305
308
  value={this.state.newName}
309
+ dir={dir}
310
+ lang={locale}
306
311
  autoFocus
307
312
  onChange={handleNameChange} />
308
313
  <button className="save" type="submit">
@@ -407,5 +412,7 @@ PageTreeNode.propTypes = {
407
412
  onDragStart: PropTypes.func,
408
413
  paddingLeft: PropTypes.number,
409
414
  tree: PropTypes.object,
410
- updatePage: PropTypes.func
415
+ updatePage: PropTypes.func,
416
+ locale: PropTypes.string,
417
+ dir: PropTypes.string,
411
418
  };
@@ -129,6 +129,20 @@ export default class RichTextArea extends React.Component {
129
129
  }
130
130
  }
131
131
 
132
+ localeOptions() {
133
+ let opts = {};
134
+
135
+ if (this.props.lang) {
136
+ opts.lang = this.props.lang;
137
+ }
138
+
139
+ if (this.props.dir) {
140
+ opts.dir = this.props.dir;
141
+ }
142
+
143
+ return opts;
144
+ }
145
+
132
146
  relativeUrl(str) {
133
147
  let url = null;
134
148
 
@@ -177,7 +191,8 @@ export default class RichTextArea extends React.Component {
177
191
  value={value}
178
192
  rows={rows}
179
193
  onChange={this.handleChange}
180
- onKeyDown={this.handleKeyPress} />
194
+ onKeyDown={this.handleKeyPress}
195
+ {...this.localeOptions()} />
181
196
  </div>
182
197
  );
183
198
  }
@@ -209,5 +224,7 @@ RichTextArea.propTypes = {
209
224
  name: PropTypes.string,
210
225
  value: PropTypes.string,
211
226
  rows: PropTypes.number,
212
- simple: PropTypes.bool
227
+ simple: PropTypes.bool,
228
+ lang: PropTypes.string,
229
+ dir: PropTypes.string
213
230
  };
@@ -6,15 +6,21 @@ module PagesCore
6
6
  extend ActiveSupport::Concern
7
7
 
8
8
  def humanized_param(slug)
9
- return id.to_s unless slug&.present?
9
+ safe_slug = safe_humanized_param(slug)
10
+ return id.to_s if safe_slug.blank?
10
11
 
11
- "#{id}-" + transliterate(slug)
12
- .downcase
13
- .gsub(/[\[{]/, "(")
14
- .gsub(/}\]/, ")")
15
- .gsub(/[^[[:alnum:]]()-]+/, "-")
16
- .gsub(/-{2,}/, "-")
17
- .gsub(/(^-|-$)/, "")
12
+ "#{id}-#{safe_slug}"
13
+ end
14
+
15
+ private
16
+
17
+ def safe_humanized_param(str)
18
+ transliterate(str.to_s).downcase
19
+ .gsub(/[\[{]/, "(")
20
+ .gsub(/}\]/, ")")
21
+ .gsub(/[^[[:alnum:]]()-]+/, "-")
22
+ .gsub(/-{2,}/, "-")
23
+ .gsub(/(^-|-$)/, "")
18
24
  end
19
25
  end
20
26
  end
@@ -24,7 +24,7 @@ module PagesCore
24
24
  end
25
25
 
26
26
  def ensure_path_segment
27
- return if deleted? || path_segment? || !name?
27
+ return if deleted? || path_segment? || generated_path_segment.blank?
28
28
 
29
29
  segment = generated_path_segment
30
30
  segment = "#{segment}-#{id}" if path_collision?(segment)
@@ -59,11 +59,8 @@ module PagesCore
59
59
  end
60
60
 
61
61
  def generated_path_segment
62
- transliterated_name.gsub(/[^[[:alnum:]]-_]+/, "-")
63
- .gsub(/-{2,}/, "-")
64
- .gsub(/(^-|-$)/, "")
65
- .mb_chars
66
- .downcase
62
+ safe_path_segment(transliterated_name).presence ||
63
+ safe_path_segment(unique_name)
67
64
  end
68
65
 
69
66
  def page_path_matches_routes?(page_path)
@@ -98,6 +95,15 @@ module PagesCore
98
95
  false
99
96
  end
100
97
 
98
+ def safe_path_segment(str)
99
+ str.to_s
100
+ .gsub(/[^[[:alnum:]]-_]+/, "-")
101
+ .gsub(/-{2,}/, "-")
102
+ .gsub(/(^-|-$)/, "")
103
+ .mb_chars
104
+ .downcase
105
+ end
106
+
101
107
  def sibling_path_segments
102
108
  siblings = if parent
103
109
  parent.children
@@ -1,4 +1,4 @@
1
1
  <%= react_component("PageImages",
2
2
  locale: @locale,
3
- locales: PagesCore.config.locales,
3
+ locales: locales_with_dir,
4
4
  records: @page.page_images.map { |pi| Admin::PageImageResource.new(pi).to_hash }) %>
@@ -16,6 +16,7 @@
16
16
  "PageTree", {
17
17
  pages: @pages.map { |p| page_json(p) },
18
18
  locale: @locale,
19
+ dir: locale_direction(@locale),
19
20
  permissions: [(:create if policy(Page).create?)] }
20
21
  ) %>
21
22
  <% end %>