pageflow 15.0.0.beta2 → 15.0.0.beta3
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of pageflow might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +105 -2
- data/README.md +5 -1
- data/app/assets/javascripts/pageflow/dist/react-client.js +176 -56
- data/app/assets/javascripts/pageflow/dist/react-server.js +176 -56
- data/app/assets/javascripts/pageflow/editor/api/widget_type.js +0 -1
- data/app/assets/javascripts/pageflow/editor/views/widget_types/media_loading_spinner.js +18 -0
- data/app/assets/javascripts/pageflow/editor/views/widget_types/title_loading_spinner.js +3 -0
- data/app/assets/javascripts/pageflow/ui/views/tabs_view.js +2 -1
- data/app/assets/stylesheets/pageflow/themes/default/base.scss +1 -1
- data/app/assets/stylesheets/pageflow/themes/default/loading_spinner.scss +37 -1
- data/app/assets/stylesheets/pageflow/themes/default/loading_spinner/media.scss +56 -0
- data/app/assets/stylesheets/pageflow/themes/default/loading_spinner/title.scss +10 -50
- data/app/assets/stylesheets/pageflow/themes/default/logo/variant/watermark.scss +10 -1
- data/app/assets/stylesheets/pageflow/themes/default/player_controls/classic/info_box.scss +5 -0
- data/app/assets/stylesheets/pageflow/themes/default/player_controls/slim/info_box.scss +4 -0
- data/app/controllers/pageflow/editor/files_controller.rb +6 -3
- data/app/helpers/pageflow/meta_tags_helper.rb +3 -3
- data/app/helpers/pageflow/social_share_links_helper.rb +1 -1
- data/app/jobs/pageflow/entry_export_import/upload_and_publish_file_job.rb +41 -0
- data/app/models/concerns/pageflow/reusable_file.rb +6 -0
- data/app/models/concerns/pageflow/uploadable_file.rb +5 -0
- data/app/models/pageflow/draft_entry.rb +26 -2
- data/app/models/pageflow/membership.rb +3 -4
- data/app/models/pageflow/revision.rb +20 -4
- data/app/models/pageflow/widget.rb +12 -6
- data/config/locales/de.yml +24 -0
- data/config/locales/en.yml +24 -0
- data/db/migrate/20140418225525_setup_schema.rb +18 -18
- data/db/migrate/20190820152900_drop_accounts_themes.rb +8 -0
- data/lib/pageflow/built_in_page_type.rb +3 -0
- data/lib/pageflow/built_in_widget_type.rb +7 -0
- data/lib/pageflow/built_in_widget_types_plugin.rb +6 -0
- data/lib/pageflow/entry_export_import.rb +43 -0
- data/lib/pageflow/entry_export_import/attachment_files.rb +65 -0
- data/lib/pageflow/entry_export_import/entry_serialization.rb +68 -0
- data/lib/pageflow/entry_export_import/file_mappings.rb +32 -0
- data/lib/pageflow/entry_export_import/page_type_versions.rb +29 -0
- data/lib/pageflow/entry_export_import/revision_serialization.rb +58 -0
- data/lib/pageflow/entry_export_import/revision_serialization/import.rb +158 -0
- data/lib/pageflow/entry_export_import/zip_archive.rb +36 -0
- data/lib/pageflow/file_type.rb +17 -4
- data/lib/pageflow/page_type.rb +20 -0
- data/lib/pageflow/page_types.rb +8 -0
- data/lib/pageflow/react/page_type.rb +10 -0
- data/lib/pageflow/version.rb +1 -1
- data/lib/pageflow/widget_types.rb +9 -1
- data/lib/tasks/entry_export_import.rake +27 -0
- data/spec/factories/revisions.rb +6 -0
- data/spec/factories/test_multi_attachment_files.rb +16 -0
- data/spec/factories/test_revision_components.rb +7 -0
- metadata +46 -3
@@ -0,0 +1,56 @@
|
|
1
|
+
$media-loading-spinner-duration: 7s;
|
2
|
+
|
3
|
+
.media_loading_spinner {
|
4
|
+
&:before {
|
5
|
+
content: "";
|
6
|
+
display: block;
|
7
|
+
position: absolute;
|
8
|
+
top: 0;
|
9
|
+
left: 0;
|
10
|
+
bottom: 0;
|
11
|
+
right: 0;
|
12
|
+
z-index: 1;
|
13
|
+
background-color: rgba(0, 0, 0, 0.15);
|
14
|
+
}
|
15
|
+
|
16
|
+
&-image {
|
17
|
+
background-size: cover;
|
18
|
+
height: 100%;
|
19
|
+
}
|
20
|
+
|
21
|
+
&-logo {
|
22
|
+
@extend %pageflow_widget_margin_right !optional;
|
23
|
+
position: relative;
|
24
|
+
|
25
|
+
&:after {
|
26
|
+
@extend %background_logo;
|
27
|
+
content: "";
|
28
|
+
display: block;
|
29
|
+
z-index: 1;
|
30
|
+
opacity: 0;
|
31
|
+
}
|
32
|
+
}
|
33
|
+
|
34
|
+
&-logo-invert {
|
35
|
+
&:after{
|
36
|
+
background-image: image-url("pageflow/themes/#{$theme-name}/logo_header_invert.#{$logo-image-file-extension}");
|
37
|
+
}
|
38
|
+
}
|
39
|
+
|
40
|
+
&-fade {
|
41
|
+
@include animation(fade_out 1s ease 1);
|
42
|
+
@include animation-delay($media-loading-spinner-duration + 0.5s);
|
43
|
+
@include animation-fill-mode(forwards);
|
44
|
+
|
45
|
+
.media_loading_spinner-logo:after {
|
46
|
+
@include animation(fade_in 1s ease-out 1);
|
47
|
+
@include animation-fill-mode(forwards);
|
48
|
+
}
|
49
|
+
}
|
50
|
+
|
51
|
+
&-invert {
|
52
|
+
color: #000 !important;
|
53
|
+
}
|
54
|
+
|
55
|
+
}
|
56
|
+
|
@@ -8,25 +8,13 @@ $title-loading-spinner-subtitle-typography: () !default;
|
|
8
8
|
|
9
9
|
$title-loading-spinner-phone-subtitle-typography: () !default;
|
10
10
|
|
11
|
-
.
|
12
|
-
$duration: 7s;
|
11
|
+
.media_loading_spinner {
|
13
12
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
left: 0;
|
20
|
-
bottom: 0;
|
21
|
-
right: 0;
|
22
|
-
z-index: 1;
|
23
|
-
background-color: rgba(0, 0, 0, 0.15);
|
24
|
-
}
|
25
|
-
|
26
|
-
&-image {
|
27
|
-
background-size: cover;
|
28
|
-
filter: blur(50px);
|
29
|
-
height: 100%;
|
13
|
+
&-fade {
|
14
|
+
.media_loading_spinner-titles {
|
15
|
+
@include animation(title_fade $media-loading-spinner-duration ease-out 1);
|
16
|
+
@include animation-fill-mode(forwards);
|
17
|
+
}
|
30
18
|
}
|
31
19
|
|
32
20
|
&-titles {
|
@@ -53,7 +41,7 @@ $title-loading-spinner-phone-subtitle-typography: () !default;
|
|
53
41
|
content: "";
|
54
42
|
display: block;
|
55
43
|
width: 100px;
|
56
|
-
border-bottom: solid 2px
|
44
|
+
border-bottom: solid 2px;
|
57
45
|
margin: 20px auto;
|
58
46
|
}
|
59
47
|
|
@@ -96,36 +84,7 @@ $title-loading-spinner-phone-subtitle-typography: () !default;
|
|
96
84
|
);
|
97
85
|
}
|
98
86
|
}
|
99
|
-
|
100
|
-
&-logo {
|
101
|
-
@extend %pageflow_widget_margin_right !optional;
|
102
|
-
position: relative;
|
103
|
-
|
104
|
-
&:after {
|
105
|
-
@extend %background_logo;
|
106
|
-
content: "";
|
107
|
-
display: block;
|
108
|
-
z-index: 1;
|
109
|
-
opacity: 0;
|
110
|
-
}
|
111
|
-
}
|
112
|
-
|
113
|
-
&-fade {
|
114
|
-
@include animation(fade_out 1s ease 1);
|
115
|
-
@include animation-delay($duration + 0.5s);
|
116
|
-
@include animation-fill-mode(forwards);
|
117
|
-
|
118
|
-
.title_loading_spinner-titles {
|
119
|
-
@include animation(title_fade $duration ease-out 1);
|
120
|
-
@include animation-fill-mode(forwards);
|
121
|
-
}
|
122
|
-
|
123
|
-
.title_loading_spinner-logo:after {
|
124
|
-
@include animation(fade_in 1s ease-out 1);
|
125
|
-
@include animation-fill-mode(forwards);
|
126
|
-
}
|
127
|
-
}
|
128
|
-
|
87
|
+
|
129
88
|
@include keyframes(title_fade) {
|
130
89
|
0% {
|
131
90
|
@include transform(translate(-50%, -30%));
|
@@ -155,4 +114,5 @@ $title-loading-spinner-phone-subtitle-typography: () !default;
|
|
155
114
|
opacity: 0;
|
156
115
|
}
|
157
116
|
}
|
158
|
-
|
117
|
+
|
118
|
+
}
|
@@ -15,6 +15,9 @@ $logo-watermark-variant-fade-in-with-header: false !default;
|
|
15
15
|
/// Set opacity value of watermark logos
|
16
16
|
$logo-watermark-variant-opacity: 0.3 !default;
|
17
17
|
|
18
|
+
/// Height of the logo in phone layout.
|
19
|
+
$logo-watermark-mobile-height: null !default;
|
20
|
+
|
18
21
|
@mixin logo-variant-watermark(
|
19
22
|
$top,
|
20
23
|
$width,
|
@@ -32,7 +35,13 @@ $logo-watermark-variant-opacity: 0.3 !default;
|
|
32
35
|
height: $phone-height;
|
33
36
|
top: 21px;
|
34
37
|
}
|
35
|
-
|
38
|
+
@if $logo-watermark-mobile-height {
|
39
|
+
@include mobile {
|
40
|
+
width: $logo-watermark-mobile-height * $width / $height;
|
41
|
+
height: $logo-watermark-mobile-height;
|
42
|
+
top: 21px;
|
43
|
+
}
|
44
|
+
}
|
36
45
|
@include logo-alignment;
|
37
46
|
}
|
38
47
|
|
@@ -73,6 +73,10 @@ $slim-player-controls-info-box-header-typography: () !default;
|
|
73
73
|
@include transition(opacity 0.2s linear, visibility 0.2s linear, transform 0.2s ease);
|
74
74
|
pointer-events: none;
|
75
75
|
|
76
|
+
a {
|
77
|
+
pointer-events: all;
|
78
|
+
}
|
79
|
+
|
76
80
|
position: absolute;
|
77
81
|
left: 50%;
|
78
82
|
@include transform(translate3d(-50%, 0, 0));
|
@@ -19,11 +19,11 @@ module Pageflow
|
|
19
19
|
authorize!(:edit, entry.to_model)
|
20
20
|
verify_edit_lock!(entry)
|
21
21
|
|
22
|
-
@file = entry.create_file!(file_type
|
22
|
+
@file = entry.create_file!(file_type, create_params)
|
23
23
|
@file.publish! if params[:no_upload]
|
24
24
|
|
25
25
|
respond_with(:editor, @file)
|
26
|
-
rescue ActiveRecord::RecordInvalid => e
|
26
|
+
rescue ActiveRecord::RecordInvalid, DraftEntry::InvalidForeignKeyCustomAttributeError => e
|
27
27
|
debug_log_with_backtrace(e)
|
28
28
|
head :unprocessable_entity
|
29
29
|
end
|
@@ -118,7 +118,10 @@ module Pageflow
|
|
118
118
|
end
|
119
119
|
|
120
120
|
def file_custom_params
|
121
|
-
file_params.permit(file_type
|
121
|
+
file_params.permit(file_type
|
122
|
+
.custom_attributes
|
123
|
+
.select { |_, options| options[:permitted_create_param] }
|
124
|
+
.keys)
|
122
125
|
end
|
123
126
|
|
124
127
|
def file_params
|
@@ -7,9 +7,9 @@ module Pageflow
|
|
7
7
|
|
8
8
|
def meta_tags_data_for_entry(entry)
|
9
9
|
{
|
10
|
-
keywords: entry.keywords
|
11
|
-
author: entry.author
|
12
|
-
publisher: entry.publisher
|
10
|
+
keywords: entry.keywords,
|
11
|
+
author: entry.author,
|
12
|
+
publisher: entry.publisher
|
13
13
|
}
|
14
14
|
end
|
15
15
|
end
|
@@ -8,7 +8,7 @@ module Pageflow
|
|
8
8
|
google: 'https://plus.google.com/share?url=%{url}',
|
9
9
|
linked_in: 'https://www.linkedin.com/shareArticle?mini=true&url=%{url}',
|
10
10
|
telegram: 'tg://msg?text=%{url}',
|
11
|
-
twitter: '
|
11
|
+
twitter: 'https://twitter.com/intent/tweet?url=%{url}',
|
12
12
|
whats_app: 'WhatsApp://send?text=%{url}'
|
13
13
|
}.freeze
|
14
14
|
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Pageflow
|
2
|
+
module EntryExportImport
|
3
|
+
# Uploads a file attachment associated with an ReusableFile
|
4
|
+
# and publishes the file afterwards
|
5
|
+
class UploadAndPublishFileJob < ApplicationJob
|
6
|
+
queue_as :file_upload
|
7
|
+
|
8
|
+
def perform(reusable_file, exported_id, archive_file_name)
|
9
|
+
archive = ZipArchive.new(archive_file_name)
|
10
|
+
|
11
|
+
reusable_file.attachments_for_export.each do |attachment|
|
12
|
+
archive_path = AttachmentFiles.archive_path(reusable_file,
|
13
|
+
attachment,
|
14
|
+
exported_id: exported_id)
|
15
|
+
|
16
|
+
archive.extract_to_tempfile(archive_path) do |tempfile|
|
17
|
+
# Paperclip skips post processing anyway since the name of
|
18
|
+
# the tempfile does not pass the validation defined in
|
19
|
+
# UploadableFile. To be explicit, we disable post
|
20
|
+
# processing manually as well.
|
21
|
+
attachment.post_processing = false
|
22
|
+
attachment.assign(tempfile)
|
23
|
+
|
24
|
+
# Calling `attachment.assign` changes the
|
25
|
+
# `<attachment-name>_file_name` attribute based on the
|
26
|
+
# name of the tempfile. To prevent `flush_writes` from
|
27
|
+
# using these new values when constructing the
|
28
|
+
# destionation path, we need to undo the attribute
|
29
|
+
# changes. Restoring the attributes does not reset the
|
30
|
+
# list of files queued for write in the attachment.
|
31
|
+
reusable_file.restore_attributes
|
32
|
+
|
33
|
+
attachment.flush_writes
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
reusable_file.publish!
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -83,6 +83,12 @@ module Pageflow
|
|
83
83
|
'unused'
|
84
84
|
end
|
85
85
|
|
86
|
+
# Overwritten with the list of attachments of the file type
|
87
|
+
# that should get included in export archives.
|
88
|
+
def attachments_for_export
|
89
|
+
[]
|
90
|
+
end
|
91
|
+
|
86
92
|
# Overwritten in UploadableFile based on initial state_machine-state.
|
87
93
|
# Defaults to false for files that only use the ReusableFile module
|
88
94
|
def can_upload?
|
@@ -2,6 +2,8 @@ module Pageflow
|
|
2
2
|
class DraftEntry
|
3
3
|
include ActiveModel::Conversion
|
4
4
|
|
5
|
+
class InvalidForeignKeyCustomAttributeError < StandardError; end
|
6
|
+
|
5
7
|
attr_reader :entry, :draft
|
6
8
|
|
7
9
|
delegate(:id, :slug,
|
@@ -38,8 +40,10 @@ module Pageflow
|
|
38
40
|
entry.title
|
39
41
|
end
|
40
42
|
|
41
|
-
def create_file!(
|
42
|
-
|
43
|
+
def create_file!(file_type, attributes)
|
44
|
+
check_foreign_key_custom_attributes(file_type.custom_attributes, attributes)
|
45
|
+
|
46
|
+
file = file_type.model.create!(attributes.except(:configuration)) do |f|
|
43
47
|
f.entry = entry
|
44
48
|
end
|
45
49
|
|
@@ -109,5 +113,25 @@ module Pageflow
|
|
109
113
|
def resolve_widgets(options = {})
|
110
114
|
widgets.resolve(Pageflow.config_for(entry), options)
|
111
115
|
end
|
116
|
+
|
117
|
+
private
|
118
|
+
|
119
|
+
def check_foreign_key_custom_attributes(custom_attributes, attributes)
|
120
|
+
custom_attributes
|
121
|
+
.each do |attribute_name, options|
|
122
|
+
file_type = options[:model]
|
123
|
+
file_id = attributes[attribute_name]
|
124
|
+
|
125
|
+
next if !file_type || file_is_used(file_type, file_id)
|
126
|
+
|
127
|
+
raise(InvalidForeignKeyCustomAttributeError,
|
128
|
+
"Custom attribute #{attribute_name} references #{file_type} #{file_id} " \
|
129
|
+
'which is not used in this revsion')
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def file_is_used(file_type, file_id)
|
134
|
+
draft.file_usages.where(file_type: file_type, file_id: file_id).exists?
|
135
|
+
end
|
112
136
|
end
|
113
137
|
end
|
@@ -11,7 +11,7 @@ module Pageflow
|
|
11
11
|
foreign_key: 'entity_id',
|
12
12
|
optional: true
|
13
13
|
|
14
|
-
validates :
|
14
|
+
validates :entity, :role, presence: true
|
15
15
|
validates :user_id, uniqueness: {scope: [:entity_type, :entity_id]}
|
16
16
|
validate :account_membership_exists, if: :on_entry?
|
17
17
|
validates :role,
|
@@ -38,9 +38,8 @@ module Pageflow
|
|
38
38
|
private
|
39
39
|
|
40
40
|
def account_membership_exists
|
41
|
-
|
42
|
-
|
43
|
-
end
|
41
|
+
errors[:base] << 'Entry Membership misses presupposed Membership on account of entry' if
|
42
|
+
user.present? && !user.accounts.include?(entity.account)
|
44
43
|
end
|
45
44
|
|
46
45
|
def on_entry?
|
@@ -103,6 +103,18 @@ module Pageflow
|
|
103
103
|
self[:share_providers] || entry.theming.default_share_providers
|
104
104
|
end
|
105
105
|
|
106
|
+
def author
|
107
|
+
read_attribute(:author) || Pageflow.config.default_author_meta_tag
|
108
|
+
end
|
109
|
+
|
110
|
+
def publisher
|
111
|
+
read_attribute(:publisher) || Pageflow.config.default_publisher_meta_tag
|
112
|
+
end
|
113
|
+
|
114
|
+
def keywords
|
115
|
+
read_attribute(:keywords) || Pageflow.config.default_keywords_meta_tag
|
116
|
+
end
|
117
|
+
|
106
118
|
def active_share_providers
|
107
119
|
share_providers.select { |_k, v| v }.keys
|
108
120
|
end
|
@@ -161,16 +173,20 @@ module Pageflow
|
|
161
173
|
file_usage.copy_to(revision)
|
162
174
|
end
|
163
175
|
|
164
|
-
|
165
|
-
|
166
|
-
record.copy_to(revision)
|
167
|
-
end
|
176
|
+
find_revision_components.each do |revision_component|
|
177
|
+
revision_component.copy_to(revision)
|
168
178
|
end
|
169
179
|
|
170
180
|
revision.save!
|
171
181
|
revision
|
172
182
|
end
|
173
183
|
|
184
|
+
def find_revision_components
|
185
|
+
Pageflow.config.revision_components.flat_map do |model|
|
186
|
+
model.all_for_revision(self).to_a
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
174
190
|
def self.depublish_all
|
175
191
|
published.update_all(:published_until => Time.now)
|
176
192
|
end
|
@@ -61,10 +61,14 @@ module Pageflow
|
|
61
61
|
private
|
62
62
|
|
63
63
|
def all
|
64
|
-
placeholders_by_role
|
65
|
-
|
66
|
-
.
|
67
|
-
|
64
|
+
initial_widgets = placeholders_by_role.merge(defaults_by_role)
|
65
|
+
initial_widgets.merge(from_db_by_role) { |_role_key, old_val, new_val|
|
66
|
+
if old_val.configuration.present?
|
67
|
+
new_val.configuration = {} if new_val.configuration.nil?
|
68
|
+
new_val.configuration = old_val.configuration.merge(new_val.configuration)
|
69
|
+
end
|
70
|
+
new_val
|
71
|
+
}.values
|
68
72
|
end
|
69
73
|
|
70
74
|
def from_db_by_role
|
@@ -73,13 +77,15 @@ module Pageflow
|
|
73
77
|
|
74
78
|
def defaults_by_role
|
75
79
|
config.widget_types.defaults_by_role.each_with_object({}) do |(role, widget_type), result|
|
76
|
-
result[role] = Widget.new(role: role, type_name: widget_type.name,
|
80
|
+
result[role] = Widget.new(role: role, type_name: widget_type.name,
|
81
|
+
subject: nil,
|
82
|
+
configuration:
|
83
|
+
config.widget_types.default_configuration(role))
|
77
84
|
end
|
78
85
|
end
|
79
86
|
|
80
87
|
def placeholders_by_role
|
81
88
|
return {} unless options[:include_placeholders]
|
82
|
-
|
83
89
|
config.widget_types.roles.each_with_object({}) do |role, result|
|
84
90
|
result[role] = Widget.new(role: role, type_name: nil, subject: nil)
|
85
91
|
end
|