pages_core 3.10.2 → 3.11.0

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.
@@ -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 %>