diff --git a/app/event.rb b/app/event.rb
index 7c4395c0..a1d6eecf 100644
--- a/app/event.rb
+++ b/app/event.rb
@@ -13,18 +13,28 @@ post '/event/:event_id/comment' do |event_id|
content_type :json
event = Event[id: event_id]
- return 403 if event.site && event.site.is_blocking?(current_site)
- return 403 if event.actioning_site && event.actioning_site.is_blocking?(current_site)
+ if event.site && event.site.is_blocking?(current_site)
+ flash[:error] = comment_unavailable_message(event.site)
+ return {result: 'error', message: flash[:error]}.to_json
+ end
+
+ if event.actioning_site && event.actioning_site.is_blocking?(current_site)
+ flash[:error] = comment_unavailable_message(event.actioning_site)
+ return {result: 'error', message: flash[:error]}.to_json
+ end
site = event.site
message = normalize_comment_message(params[:message])
+ message_error = comment_message_error(message)
+ unavailable_message = comment_unavailable_message(site)
if(site.is_blocking?(current_site) ||
site.profile_comments_enabled == false ||
current_site.commenting_allowed? == false ||
(current_site.is_a_jerk? && event.site_id != current_site.id && !site.is_following?(current_site)) ||
- !valid_comment_message?(message))
- return {result: 'error'}.to_json
+ message_error)
+ flash[:error] = message_error || unavailable_message
+ return {result: 'error', message: flash[:error]}.to_json
end
event.add_site_comment current_site, message
@@ -36,8 +46,9 @@ post '/event/:event_id/update_profile_comment' do |event_id|
content_type :json
event = Event[id: event_id]
message = normalize_comment_message(params[:message])
+ message_error = comment_message_error(message)
return {result: 'error'}.to_json unless (current_site.id == event.profile_comment.actioning_site_id &&
- valid_comment_message?(message))
+ message_error.nil?)
event.profile_comment.update message: message
return {result: 'success'}.to_json
diff --git a/app/site.rb b/app/site.rb
index 9c7dcba0..8e5af540 100644
--- a/app/site.rb
+++ b/app/site.rb
@@ -165,21 +165,27 @@ post '/site/:username/comment' do |username|
site = Site[username: username]
message = normalize_comment_message(params[:message])
+ message_error = comment_message_error(message)
- redirect request.referer if current_site && (site.is_blocking?(current_site) || current_site.is_blocking?(site))
+ if current_site && (site.is_blocking?(current_site) || current_site.is_blocking?(site))
+ flash[:error] = comment_unavailable_message(site)
+ redirect request.referer
+ end
last_comment = site.profile_comments_dataset.order(:created_at.desc).first
if last_comment && last_comment.message == message && last_comment.created_at > 2.hours.ago
+ flash[:error] = 'You already posted that comment.'
redirect request.referer
end
if site.profile_comments_enabled == false ||
- !valid_comment_message?(message) ||
+ message_error ||
site.is_blocking?(current_site) ||
current_site.is_blocking?(site) ||
current_site.commenting_allowed? == false ||
(current_site.is_a_jerk? && site.id != current_site.id && !site.is_following?(current_site))
+ flash[:error] = message_error || comment_unavailable_message(site)
redirect request.referrer
end
diff --git a/app_helpers.rb b/app_helpers.rb
index 53a666f1..0c78d76f 100644
--- a/app_helpers.rb
+++ b/app_helpers.rb
@@ -131,21 +131,66 @@ def sanitize_comment(text)
Rinku.auto_link Sanitize.fragment(text), :all, 'target="_blank" rel="nofollow"'
end
+def flash_message_keys
+ flash.keys.select {|k| [:success, :error, :errors].include?(k) && !flash[k].to_s.empty?}
+end
+
+def flash_message_entries
+ entries = []
+
+ Array(@error).each do |message|
+ entries << {type: 'error', message: message} unless message.to_s.empty?
+ end
+
+ flash_message_keys.each do |key|
+ type = key == :success ? 'success' : 'error'
+
+ Array(flash[key]).each do |message|
+ entries << {type: type, message: message} unless message.to_s.empty?
+ end
+ end
+
+ entries
+end
+
def normalize_comment_message(message)
message.to_s.gsub(/\r\n?/, "\n").gsub(/\n{3,}/, "\n\n").strip
end
-def valid_comment_message?(message)
- return false if message.empty? || message.length > Site::MAX_COMMENT_SIZE
+def comment_message_error(message)
+ return 'Comment cannot be empty.' if message.empty?
+ return "Comments must be #{Site::MAX_COMMENT_SIZE} characters or fewer." if message.length > Site::MAX_COMMENT_SIZE
lines = message.split("\n")
- return false if lines.length > Site::MAX_COMMENT_LINES
+ return "Comments must be #{Site::MAX_COMMENT_LINES} lines or fewer." if lines.length > Site::MAX_COMMENT_LINES
- return true if lines.length < Site::MULTILINE_COMMENT_AVERAGE_LINE_THRESHOLD
+ return nil if lines.length < Site::MULTILINE_COMMENT_AVERAGE_LINE_THRESHOLD
nonblank_lines = lines.map(&:strip).reject(&:empty?)
+ return 'Comment cannot be empty.' if nonblank_lines.empty?
+
average_line_length = nonblank_lines.sum(&:length).to_f / nonblank_lines.length
- average_line_length >= Site::MIN_MULTILINE_COMMENT_AVERAGE_LINE_LENGTH
+ return nil if average_line_length >= Site::MIN_MULTILINE_COMMENT_AVERAGE_LINE_LENGTH
+
+ 'Multiline comments need a little more text on each line.'
+end
+
+def valid_comment_message?(message)
+ comment_message_error(message).nil?
+end
+
+def comment_unavailable_message(site=nil)
+ return 'Comments are disabled for this site.' if site && site.profile_comments_enabled == false
+
+ if current_site && !current_site.commenting_allowed?
+ if current_site.commenting_too_much?
+ return "To prevent spam, comments are limited to #{Site::MAX_COMMENTS_PER_DAY} per day. Please try again tomorrow."
+ end
+
+ return "To prevent spam, you cannot comment until you have updated your site #{Site::COMMENTING_ALLOWED_UPDATED_COUNT} times on separate days, and your account is one week old."
+ end
+
+ 'You cannot comment on this right now.'
end
def flash_display(opts={})
diff --git a/public/js/news/profile_comment.js b/public/js/news/profile_comment.js
index 21e535cc..89b654a2 100644
--- a/public/js/news/profile_comment.js
+++ b/public/js/news/profile_comment.js
@@ -1,31 +1,45 @@
var ProfileComment = {
+ commentContent: function(eventId) {
+ return $('#event_'+eventId+' > div.content').first()
+ },
+
displayEditor: function(eventId) {
- var commentDiv = $('#event_'+eventId+' div.content').first()
+ var commentDiv = this.commentContent(eventId)
var eventActions = $('#event_'+eventId+'_actions').first()
+ var rendered = Template.template($('#comment-edit-template').html(), {
+ eventId: eventId,
+ content: commentDiv.text()
+ })
eventActions.find('a#editLink').css('display', 'none')
- commentDiv.html(Template.template($('#comment-edit-template').html(), {eventId: eventId, content: commentDiv.text()}))
- $('#event_'+eventId+' div.title div.comment').text()
+ commentDiv.data('original-html', commentDiv.html())
+ commentDiv.addClass('is-editing')
+ commentDiv.html($.trim(rendered))
+ commentDiv.find('textarea').focus()
},
cancelEditor: function(eventId) {
var eventActions = $('#event_'+eventId+'_actions').first()
- var commentDiv = $('#event_'+eventId+' div.content').first()
+ var commentDiv = this.commentContent(eventId)
eventActions.find('a#editLink').css('display', 'inline')
- commentDiv.text(commentDiv.find('textarea').text())
+ commentDiv.removeClass('is-editing')
+ commentDiv.html(commentDiv.data('original-html'))
},
update: function(eventId, csrfToken) {
- var commentDiv = $('#event_'+eventId+' div.content').first()
- var self = this
- console.log(commentDiv.find('textarea').val())
+ var commentDiv = this.commentContent(eventId)
+ var message = commentDiv.find('textarea').val()
+
$.post('/event/'+eventId+'/update_profile_comment', {
csrf_token: csrfToken,
- message: commentDiv.find('textarea').val()
+ message: message
}, function(res) {
- commentDiv.find('textarea').text(commentDiv.find('textarea').val())
- self.cancelEditor(eventId)
+ if(res.result == 'success') {
+ $('#event_'+eventId+'_actions').first().find('a#editLink').css('display', 'inline')
+ commentDiv.removeClass('is-editing')
+ commentDiv.text(message)
+ }
})
}
}
diff --git a/sass/_project-sass/_project-Main.scss b/sass/_project-sass/_project-Main.scss
index bd294a5c..7abd8462 100644
--- a/sass/_project-sass/_project-Main.scss
+++ b/sass/_project-sass/_project-Main.scss
@@ -13,7 +13,7 @@
}
}
.content, .footer-Content {
- padding: 20px 3%;
+ padding: 20px 3%;
@media (max-device-width:480px), screen and (max-width:800px) {
padding: 20px 7%;
@@ -23,6 +23,126 @@
padding: 20px 0;
}
}
+
+.flash-Messages {
+ box-sizing: border-box;
+ left: 0;
+ padding: 0 3%;
+ pointer-events: none;
+ position: fixed;
+ right: 0;
+ top: 72px;
+ z-index: 2100;
+
+ @media (max-device-width:480px), screen and (max-width:800px) {
+ padding: 0 7%;
+ top: 62px;
+ }
+}
+
+.hp .flash-Messages {
+ top: 62px;
+}
+
+.flash-Message {
+ align-items: flex-start;
+ background: #f9fffd;
+ border: 1px solid #d6e9e6;
+ border-left: 4px solid $c-Brand-1;
+ border-radius: 4px;
+ box-shadow: 0 1px 2px rgba(44, 62, 80, .08);
+ box-sizing: border-box;
+ color: #2c3e50;
+ display: flex;
+ gap: 10px;
+ line-height: 1.4;
+ margin: 0 auto 14px auto;
+ max-width: 1200px;
+ animation: flashMessageFadeOut .25s ease 5s forwards;
+ pointer-events: auto;
+ padding: 11px 12px;
+
+ &:hover, &:focus-within {
+ animation-play-state: paused;
+ }
+}
+
+.flash-Message--error {
+ background: #fff8ed;
+ border-color: #ead7bd;
+ border-left-color: #d9820f;
+}
+
+.flash-Message--success {
+ background: #f2fbf8;
+ border-color: #cfe9df;
+ border-left-color: #18bc9c;
+}
+
+.flash-MessageIcon {
+ color: #18bc9c;
+ flex: 0 0 18px;
+ line-height: 1.4;
+ padding-top: 1px;
+ text-align: center;
+}
+
+.flash-Message--error .flash-MessageIcon {
+ color: #d9820f;
+}
+
+.flash-MessageText {
+ flex: 1 1 auto;
+ min-width: 0;
+
+ p {
+ margin: 0;
+ overflow-wrap: anywhere;
+ }
+}
+
+.flash-MessageClose {
+ background: transparent;
+ border: 0;
+ color: #7b8a8b;
+ flex: 0 0 auto;
+ font-size: 13px;
+ line-height: 1;
+ margin: -1px -3px -1px 6px;
+ padding: 4px;
+
+ &:hover, &:focus {
+ color: #2c3e50;
+ }
+}
+
+.flash-Messages.is-hidden, .flash-Message.is-hidden {
+ display: none;
+}
+
+@keyframes flashMessageFadeOut {
+ 0% {
+ opacity: 1;
+ transform: translateY(0);
+ visibility: visible;
+ }
+
+ 99% {
+ opacity: 0;
+ transform: translateY(-4px);
+ visibility: visible;
+ }
+
+ 100% {
+ opacity: 0;
+ transform: translateY(-4px);
+ visibility: hidden;
+ }
+}
+
+.flash-Messages--centered .flash-MessageText {
+ text-align: center;
+}
.single-Col{
}
@@ -1439,6 +1559,30 @@ a.tag:hover {
.news-item.comment > .title + .content {
white-space: pre-wrap;
}
+.news-item.comment > .title + .content.is-editing {
+ white-space: normal;
+}
+.news-item.comment .profile-comment-editor {
+ margin: 0;
+}
+.news-item.comment .profile-comment-editor textarea {
+ box-sizing: border-box;
+ display: block;
+ font-size: 1em;
+ line-height: 1.4;
+ margin: 0 0 8px 0;
+ min-height: 82px;
+ resize: vertical;
+ width: 100%;
+}
+.news-item.comment .profile-comment-editor-actions {
+ align-items: center;
+ display: flex;
+ gap: 10px;
+}
+.news-item.comment .profile-comment-editor-actions .btn-Action {
+ margin: 0;
+}
.news-item > .title + .actions {
margin-top: 8px;
}
diff --git a/tests/site_change_tests.rb b/tests/site_change_tests.rb
new file mode 100644
index 00000000..bbd17bc6
--- /dev/null
+++ b/tests/site_change_tests.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+require_relative './environment'
+
+describe SiteChange do
+ it 'returns up to ten changed filenames by default' do
+ site = Fabricate :site
+ site_change = SiteChange.create site: site
+ base_time = Time.now
+
+ 11.times do |i|
+ site_change.add_site_change_file site_id: site.id, filename: "page#{i}.html", created_at: base_time + i
+ end
+
+ filenames = site_change.site_change_filenames
+
+ _(filenames.length).must_equal 10
+ _(filenames).must_include 'page10.html'
+ _(filenames).wont_include 'page0.html'
+ end
+end
diff --git a/views/_flash.erb b/views/_flash.erb
index 7af68ecf..6a40738f 100644
--- a/views/_flash.erb
+++ b/views/_flash.erb
@@ -1,15 +1,19 @@
-<% if @error %>
-
-<% end %>
-
-<% if flash.keys.length > 0 %>
-
-
- <% flash.keys.select {|k| [:success, :error, :errors].include?(k)}.each do |key| %>
- <%== flash[key] %>
- <% end %>
-
+<% opts ||= {} %>
+<% messages = flash_message_entries %>
+<% if messages.any? %>
+
+ <% messages.each do |entry| %>
+
+
+
+
+
+
<%== entry[:message] %>
+
+
+
+
+
+ <% end %>
<% end %>
diff --git a/views/_news_templates.erb b/views/_news_templates.erb
index 8acc91b0..d2db9a35 100644
--- a/views/_news_templates.erb
+++ b/views/_news_templates.erb
@@ -6,8 +6,11 @@
diff --git a/views/admin.erb b/views/admin.erb
index bebdd7f6..d5f3c458 100644
--- a/views/admin.erb
+++ b/views/admin.erb
@@ -11,9 +11,6 @@
Site Reports | Site Bandwidth Usage | Site Ban History
-
- <%== flash_display %>
-
Ban Site
diff --git a/views/admin/ban_history.erb b/views/admin/ban_history.erb
index d84d9b54..d5d792d2 100644
--- a/views/admin/ban_history.erb
+++ b/views/admin/ban_history.erb
@@ -6,8 +6,6 @@
- <%== flash_display %>
-
<% if @sites.empty? %>
No sites found.
@@ -218,4 +216,4 @@
});
});
});
-
\ No newline at end of file
+
diff --git a/views/admin/email.erb b/views/admin/email.erb
index 44238c98..53603310 100644
--- a/views/admin/email.erb
+++ b/views/admin/email.erb
@@ -6,9 +6,6 @@
-
- <%== flash_display %>
-
diff --git a/views/admin/reports.erb b/views/admin/reports.erb
index 58ee8110..855f5e89 100644
--- a/views/admin/reports.erb
+++ b/views/admin/reports.erb
@@ -6,8 +6,6 @@
- <%== flash_display %>
-
<% if @reports.empty? %>
No reports found.
diff --git a/views/admin/site.erb b/views/admin/site.erb
index 2a57c2ec..8f379ddd 100644
--- a/views/admin/site.erb
+++ b/views/admin/site.erb
@@ -14,7 +14,6 @@
- <%== flash_display %>
diff --git a/views/dashboard/index.erb b/views/dashboard/index.erb
index 7ce43ad3..22175817 100644
--- a/views/dashboard/index.erb
+++ b/views/dashboard/index.erb
@@ -61,15 +61,7 @@
<% end %>
-<% if flash.keys.length > 0 %>
-
- <% flash.keys.select {|k| [:success, :error, :errors].include?(k)}.each do |key| %>
- <%== flash[key] %>
- <% end %>
-
-<% else %>
-
-<% end %>
+
<%== erb :'dashboard/files' %>
diff --git a/views/forgot_username.erb b/views/forgot_username.erb
index 7f7f966c..d40c894d 100644
--- a/views/forgot_username.erb
+++ b/views/forgot_username.erb
@@ -6,8 +6,6 @@
- <%== flash_display %>
-
If you forgot your sitename (AKA username), you can have it emailed to you here.
Keep in mind that we cannot help you locate your sitename without a valid email address, and we don't disclose whether an email address exists in our system. This is for security and privacy reasons.
diff --git a/views/layout.erb b/views/layout.erb
index 3fb82e2a..6bb0aeb8 100644
--- a/views/layout.erb
+++ b/views/layout.erb
@@ -39,6 +39,7 @@
<%== erb :'_header', layout: false %>
+ <%== flash_display %>
<%== yield %>
-
- <%== flash_display centered: true %>
-