Change flash to be a popup that's uniform across the site design

This commit is contained in:
Kyle Drake
2026-05-12 23:29:07 -05:00
parent 13674d1a97
commit 16e291c67b
26 changed files with 293 additions and 151 deletions
+16 -5
View File
@@ -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
+8 -2
View File
@@ -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
+50 -5
View File
@@ -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={})
+25 -11
View File
@@ -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)
}
})
}
}
+145 -1
View File
@@ -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;
}
+20
View File
@@ -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
+17 -13
View File
@@ -1,15 +1,19 @@
<% if @error %>
<div class="alert alert-block alert-error">
<p><%== @error %></p>
</div>
<% end %>
<% if flash.keys.length > 0 %>
<div class="alert alert-block alert-<%= flash.keys.first %> <%= opts[:centered] ? 'txt-Center' : '' %>">
<p>
<% flash.keys.select {|k| [:success, :error, :errors].include?(k)}.each do |key| %>
<%== flash[key] %>
<% end %>
</p>
<% opts ||= {} %>
<% messages = flash_message_entries %>
<% if messages.any? %>
<div class="flash-Messages <%= opts[:centered] ? 'flash-Messages--centered' : '' %>">
<% messages.each do |entry| %>
<div class="flash-Message flash-Message--<%= entry[:type] %>" role="<%= entry[:type] == 'error' ? 'alert' : 'status' %>">
<div class="flash-MessageIcon" aria-hidden="true">
<i class="fa fa-<%= entry[:type] == 'error' ? 'exclamation-circle' : 'check-circle' %>"></i>
</div>
<div class="flash-MessageText">
<p><%== entry[:message] %></p>
</div>
<button class="flash-MessageClose" type="button" aria-label="Dismiss message" onclick="var message=this.parentNode; message.className += ' is-hidden'; var group=message.parentNode; if(!group.querySelector('.flash-Message:not(.is-hidden)')) group.className += ' is-hidden'; return false">
<i class="fa fa-times" aria-hidden="true"></i>
</button>
</div>
<% end %>
</div>
<% end %>
+6 -3
View File
@@ -6,8 +6,11 @@
</div>
<div id="comment-edit-template" style="display: none">
<form onsubmit="ProfileComment.update({{ eventId }}, '<%= csrf_token %>'); return false">
<textarea rows="5" style="width: 100%" maxlength="<%= Site::MAX_COMMENT_SIZE %>">{{ content }}</textarea>
<button class="btn-Action">Save</button> <a href="#" onclick="ProfileComment.cancelEditor({{ eventId }}); return false">Cancel</a>
<form class="profile-comment-editor" onsubmit="ProfileComment.update({{ eventId }}, '<%= csrf_token %>'); return false">
<textarea rows="3" maxlength="<%= Site::MAX_COMMENT_SIZE %>">{{ content }}</textarea>
<div class="profile-comment-editor-actions">
<button class="btn-Action">Save</button>
<a href="#" onclick="ProfileComment.cancelEditor({{ eventId }}); return false">Cancel</a>
</div>
</form>
</div>
-3
View File
@@ -11,9 +11,6 @@
<a href="/admin/reports">Site Reports</a> | <a href="/admin/usage">Site Bandwidth Usage</a> | <a href="/admin/ban_history">Site Ban History</a>
</div>
</div>
<%== flash_display %>
<div class="row">
<div class="col col-50">
<h2>Ban Site</h2>
+1 -3
View File
@@ -6,8 +6,6 @@
<div class="content single-Col misc-page">
<article>
<%== flash_display %>
<% if @sites.empty? %>
<div class="empty-state">
<p><em>No sites found.</em></p>
@@ -218,4 +216,4 @@
});
});
});
</script>
</script>
-3
View File
@@ -6,9 +6,6 @@
</div>
<div class="content misc-page single-Col txt-Center" style="padding-top: 20px;">
<%== flash_display %>
<div class="row">
<div class="col col-100">
<p>
-2
View File
@@ -6,8 +6,6 @@
<div class="content single-Col misc-page">
<article>
<%== flash_display %>
<% if @reports.empty? %>
<div style="text-align: center; padding: 40px;">
<p><em>No reports found.</em></p>
-1
View File
@@ -14,7 +14,6 @@
<div class="content single-Col misc-page">
<article>
<%== flash_display %>
<div class="row">
<div class="col col-50">
+1 -9
View File
@@ -61,15 +61,7 @@
</div>
<% end %>
<% if flash.keys.length > 0 %>
<div id="alertDialogue" class="alert alert-block alert-<%= flash.keys.first %>" style="display: block; max-height: 200px; overflow-y: auto;">
<% flash.keys.select {|k| [:success, :error, :errors].include?(k)}.each do |key| %>
<%== flash[key] %>
<% end %>
</div>
<% else %>
<div id="alertDialogue" class="alert alert-block alert-<%= flash.keys.first || 'success' %>" style="display: none; max-height: 200px; overflow-y: auto;"></div>
<% end %>
<div id="alertDialogue" class="alert alert-block alert-success" style="display: none; max-height: 200px; overflow-y: auto;"></div>
<div id="filesDisplay" class="files">
<%== erb :'dashboard/files' %>
-2
View File
@@ -6,8 +6,6 @@
</div>
<div class="content txt-Center single-Col misc-page">
<%== flash_display %>
<p>If you forgot your sitename (AKA username), you can have it emailed to you here.</p>
<p>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.</p>
+1
View File
@@ -39,6 +39,7 @@
<body class="interior">
<div class="page">
<%== erb :'_header', layout: false %>
<%== flash_display %>
<%== yield %>
</div>
<footer class="footer-Base">
-3
View File
@@ -5,9 +5,6 @@
</div>
<div class="content single-Col misc-page">
<%== flash_display centered: true %>
<form method="post" action="/send_password_reset" class="content">
<fieldset>
<input name="csrf_token" type="hidden" value="<%= csrf_token %>">
-13
View File
@@ -8,19 +8,6 @@
<div class="content single-Col misc-page txt-Center">
<article>
<section>
<div class="txt-Center">
<% if flash[:success] %>
<div class="alert alert-block alert-success" style="margin-top: 20px">
<%== flash[:success] %>
</div>
<% end %>
<% if flash[:error] %>
<div class="alert alert-block alert-error" style="margin-top: 20px">
<%== flash[:error] %>
</div>
<% end %>
</div>
<div class="tabbable" style="margin-top: 20px"> <!-- Only required for left/right tabs -->
<ul class="nav nav-tabs">
+1 -15
View File
@@ -8,20 +8,6 @@
<div class="content single-Col misc-page txt-Center">
<article>
<section>
<div class="txt-Center">
<% if flash[:success] %>
<div class="alert alert-block alert-success" style="margin-top: 20px">
<%== flash[:success] %>
</div>
<% end %>
<% if flash[:error] %>
<div class="alert alert-block alert-error" style="margin-top: 20px">
<%== flash[:error] %>
</div>
<% end %>
</div>
<h2>Generated Invoices</h2>
<% if @invoices.empty? && !current_site.paypal_profile_id %>
@@ -62,4 +48,4 @@
<button type="submit" class="btn-Action">Permanently Delete Site</button>
</div>
</form>
</div>
</div>
-13
View File
@@ -8,19 +8,6 @@
<div class="content single-Col misc-page txt-Center">
<article>
<section>
<div class="txt-Center">
<% if flash[:success] %>
<div class="alert alert-block alert-success" style="margin-top: 20px">
<%== flash[:success] %>
</div>
<% end %>
<% if flash[:error] %>
<div class="alert alert-block alert-error" style="margin-top: 20px">
<%== flash[:error] %>
</div>
<% end %>
</div>
<div class="tabbable" style="margin-top: 20px"> <!-- Only required for left/right tabs -->
<ul class="nav nav-tabs">
<li class="active"><a href="#profile" data-toggle="tab">Profile</a></li>
-2
View File
@@ -6,8 +6,6 @@
</div>
<div class="content txt-Center single-Col misc-page">
<%== flash_display %>
<form method="POST" action="/signin" class="content">
<input name="csrf_token" type="hidden" value="<%= csrf_token %>">
<fieldset>
-2
View File
@@ -6,8 +6,6 @@
</div>
<div class="content txt-Center single-Col misc-page">
<%== flash_display %>
<h3>Your site was deleted.</h3>
<p>Would you like to restore your site?<br><br><strong><%= @site.username %>.neocities.org</strong></p>
+1 -10
View File
@@ -1,13 +1,4 @@
<div class="header-Outro with-site-image">
<% if current_site && flash.keys.length > 0 %>
<div class="row content">
<div class="alert txt-Center">
<% flash.keys.each do |key| %>
<%= flash[key] %>
<% end %>
</div>
</div>
<% end %>
<div class="row content site-info-row">
<div class="col col-50 signup-Area site-display-preview-wrapper large">
<div class="signup-Form site-display-preview">
@@ -116,4 +107,4 @@
</div>
</form>
</div>
<% end %>
<% end %>
-13
View File
@@ -32,19 +32,6 @@
<div class="row">
<div class="col col-100 txt-Center" style="margin-top: 10px;">
<% if flash[:success] %>
<div class="alert alert-block alert-success" style="margin-top: 20px">
<%== flash[:success] %>
</div>
<% end %>
<% if flash[:error] %>
<div class="alert alert-block alert-error" style="margin-top: 20px">
<%== flash[:error] %>
</div>
<% end %>
<form method="POST" action="/site/<%= current_site.username %>/confirm_email" class="content">
<%== csrf_token_input_html %>
<fieldset>
-12
View File
@@ -9,18 +9,6 @@
<div class="row">
<div class="col col-100 txt-Center" style="margin-top: 10px;">
<% if flash[:success] %>
<div class="alert alert-block alert-success" style="margin-top: 20px">
<%== flash[:success] %>
</div>
<% end %>
<% if flash[:error] %>
<div class="alert alert-block alert-error" style="margin-top: 20px">
<%== flash[:error] %>
</div>
<% end %>
<form method="POST" action="/site/<%= current_site.username %>/confirm_phone" class="content">
<%== csrf_token_input_html %>
+1 -5
View File
@@ -1,8 +1,4 @@
<section class="section plans welcome">
<% if current_site %>
<%== flash_display centered: true %>
<% end %>
<% if request.path == '/welcome' %>
<h2>Welcome to Neocities, <%= current_site.username %>!</h2>
<% elsif parent_site && parent_site.supporter? %>
@@ -130,4 +126,4 @@
placement: 'top'
})
})
</script>
</script>