This commit is contained in:
Kira 2019-02-28 11:24:04 -08:00
parent 8a98fe543c
commit bc1218044c
19 changed files with 603 additions and 2 deletions

View File

@ -0,0 +1,116 @@
class BlipsController < ApplicationController
class BlipTooOld < Exception ; end
respond_to :html, :xml, :json
before_action :member_only, only: [:create, :new, :update, :edit, :hide]
before_action :moderator_only, only: [:unhide, :destroy]
rescue_from BlipTooOld, with: :blip_too_old
def index
@blips = Blip.search(search_params).paginate(params[:page], limit: params[:limit])
respond_with(@blips)
end
def show
@blip = Blip.find(params[:id])
check_privilege(@blip)
@parent = @blip.response_to
@children = Blip.where('response_to = ?', @blip.id).paginate(params[:page])
respond_with(@blip)
end
def edit
@blip = Blip.find(params[:id])
check_edit_privilege(@blip)
respond_with(@blip)
end
def update
@blip = Blip.find(params[:id])
check_edit_privilege(@blip)
Blip.transaction do
@blip.update(blip_params(:update))
ModAction.log("Edited blip ##{@blip.id}", :edited_blip)
end
flash[:notice] = 'Blip updated'
respond_with(@blip)
end
def hide
@blip = Blip.find(params[:id])
check_hide_privilege(@blip)
Blip.transaction do
@blip.update(is_hidden: true)
ModAction.log("Hid blip ##{@blip.id}", :hide_blip)
end
respond_with(@blip)
end
def unhide
@blip = Blip.find(params[:id])
Blip.transaction do
@blip.update(is_hidden: false)
ModAction.log("Unhide blip ##{@blip.id}", :unhide_blip)
end
respond_with(@blip)
end
def destroy
@blip = Blip.find(params[:id])
ModAction.log("Deleted Blip ##{@blip.id}", :delete_blip)
@blip.destroy
flash[:notice] = 'Blip deleted'
respond_with(@blip) do |format|
format.html do
redirect_back(fallback_location: blip_path(id: @blip.response_to))
end
end
end
def new
@blip = Blip.new
end
def create
@blip = Blip.create(blip_params(:create))
flash[:notice] = @blip.valid? ? "Blip posted" : @blip.errors.full_messages.join("; ")
respond_with(@blip) do |format|
format.html do
redirect_back(fallback_location: blips_path)
end
end
end
private
def search_params
params.fetch(:search, {}).permit!
end
def blip_params(mode)
allowed = [:body]
allowed << :response_to if mode == :create
params.require(:blip).permit(allowed)
end
def blip_too_old
redirect_back(fallback_location: blips_path, flash: {notice: 'You cannot edit blips more than 5 minutes old'})
end
def check_privilege(blip)
raise User::PrivilegeError unless blip.visible_to?(CurrentUser.user)
end
def check_hide_privilege(blip)
raise User::PrivilegeError unless blip.can_hide?(CurrentUser.user)
end
def check_edit_privilege(blip)
return if CurrentUser.is_moderator?
raise User::PrivilegeError if blip.creator_id != CurrentUser.id
raise BlipTooOld if blip.created_at < 5.minutes.ago
end
end

View File

@ -45,5 +45,7 @@ export { default as Utility } from '../src/javascripts/utility.js';
export { default as Ugoira } from '../src/javascripts/ugoira.js';
import Takedown from '../src/javascripts/takedowns.js';
window.Takedown = Takedown;
import Blip from '../src/javascripts/blips.js';
window.Blip = Blip;

View File

@ -0,0 +1,46 @@
import Utility from './utility.js';
let Blip = {};
Blip.atme = function (id) {
$.ajax({
url: `/blips/${id}.json`,
type: 'GET',
dataType: 'json',
accept: 'text/javascript',
data: {
id: id
}
}).done(function (data) {
$('#blip_body_for_')[0].value += '@' + data.creator_name.replace(/ /g, "_") + ': ';
$("#blip_body_for_")[0].focus();
$('#blip_response_to')[0].value = data.id;
}).fail(function (data) {
Utility.error(data.responseText);
});
};
Blip.quote = function (id) {
$.ajax({
url: `/blips/${id}.json`,
type: 'GET',
dataType: 'json',
accept: 'text/javascript',
data: {
id: id
}
}).done(function (data) {
const stripped_body = data.body.replace(/\[quote\](?:.|\n|\r)+?\[\/quote\][\n\r]*/gm, "");
$('#blip_body_for_')[0].value += `[quote]"${data.creator_name}":/user/show/${data.creator_id} said:
${stripped_body}
[/quote]
`;
$("#blip_body_for_")[0].focus();
$('#blip_response_to')[0].value = data.id;
}).fail(function (data) {
Utility.error(data.responseText);
});
};
export default Blip;

View File

@ -0,0 +1,43 @@
@import "../base/000_vars.scss";
div#c-blips {
article.blip {
margin-bottom: 2em;
word-wrap: break-word;
padding: 5px;
background: $menu_color;
div.author {
width: 12em;
float: left;
overflow: hidden;
margin-right: 1em;
h1 {
display: block;
font-size: $h3_size;
}
}
div.content {
margin-left: 13em;
min-width: 17em;
menu {
li {
margin-right: 1em;
}
}
}
}
}
form.edit_blip div.input.boolean {
display: inline-block;
label {
font-weight: normal;
vertical-align: initial;
}
}

101
app/models/blip.rb Normal file
View File

@ -0,0 +1,101 @@
class Blip < ApplicationRecord
belongs_to_creator
validates_presence_of :body
belongs_to :parent, class_name: "Blip", foreign_key: "response_to", optional: true
has_many :responses, class_name: "Blip", foreign_key: "response_to"
validates_length_of :body, within: 5..1000
validate :validate_parent_exists, :on => :create
validate :validate_creator_is_not_limited, :on => :create
def response?
parent.present?
end
def has_responses?
responses.any?
end
def validate_creator_is_not_limited
unless creator.can_comment?
errors.add(:base, "You may not create blips until your account is three days old")
end
end
def validate_parent_exists
if response_to.present?
errors.add(:response_to, "must exist") unless Blip.exists?(response_to)
end
end
module ApiMethods
def hidden_attributes
super + [:body_index]
end
def method_attributes
super + [:creator_name]
end
def creator_name
User.id_to_name(creator_id)
end
end
module PermissionsMethods
def can_hide?(user)
user.is_moderator? || user.id == creator_id
end
def can_edit?(user)
(creator_id == user.id && created_at > 5.minutes.ago) || user.is_moderator?
end
def visible_to?(user)
return true unless is_hidden
user.is_moderator? || user.id == creator_id
end
end
module SearchMethods
def for_creator(user_id)
user_id.present? ? where("creator_id = ?", user_id) : none
end
def for_creator_name(user_name)
for_creator(User.name_to_id(user_name))
end
def search(params)
q = super
q = q.includes(:creator).includes(:responses).includes(:parent)
q = q.attribute_matches(:body, params[:body_matches], index_column: :body_index)
if params[:response_to].present?
q = q.where('response_to = ?', params[:response_to].to_i)
end
if params[:creator_name].present?
q = q.for_creator_name(params[:creator_name])
end
if params[:creator_id].present?
q = q.for_creator(params[:creator_id].to_i)
end
case params[:order]
when "updated_at", "updated_at_desc"
q = q.order("blips.updated_at DESC")
else
q = q.order('id DESC')
end
q
end
end
include PermissionsMethods
extend SearchMethods
include ApiMethods
end

View File

@ -0,0 +1,11 @@
<div class='section' id='reply'>
<%= error_messages_for :blip %>
<%= simple_form_for(blip, :html => {:style => ("display: none;" if local_assigns[:hidden]), :class => "edit_blip"}) do |f| %>
<% if blip.new_record? %>
<%= f.hidden_field :response_to %>
<% end %>
<%= dtext_field "blip", "body", :classes => "autocomplete-mentions", :value => blip.body, :input_id => "blip_body_for_#{blip.id}", :preview_id => "dtext-preview-for-#{blip.id}" %>
<%= f.button :submit, "Submit" %>
<%= dtext_preview_button "blip", "body", :input_id => "blip_body_for_#{blip.id}", :preview_id => "dtext-preview-for-#{blip.id}" %>
<% end %>
</div>

View File

@ -0,0 +1,31 @@
<% unless params[:show] %>
<div id='searchform_hide'>
<%= link_to_function "Show Search Options", "$j('#searchform').fadeIn('fast'); $('searchform_hide').hide();" %>
</div>
<% end %>
<div class='section' id='searchform' style='width:400px;<% unless params[:show] %>display:none;<% end %>'>
<% unless params[:show] %>
<%= link_to_function "Hide Search Options", "$j('#searchform').fadeOut('fast'); $('searchform_hide').show();" %>
<% end %>
<% form_tag({action: "index"}, method: :get) do %>
<table class='nomargin'>
<tr><td><label for="body">Body</label></td><td><%= text_field_tag "body", params[:body], style: "width:195px;" %></td></tr>
<tr><td><label for="name">Username</label></td><td><%= text_field_tag "name", params[:name], style: "width:195px;" %></td></tr>
<tr><td><label for="response_to">Response to</label></td><td><%= text_field_tag "response_to", params[:response_to], style: "width:195px;" %></td></tr>
<tr><td><label for="status">Status</label></td><td>
<%= select_tag "status", options_for_select([
["Any", "any"],
["Visible", "active"],
["Hidden", "hidden"],
], params[:status] || "any"), style: "width:200px;" %>
</td></tr>
<tr><td colspan="2"><%= submit_tag "Search", name: nil%></td></tr>
</table>
<% if params[:show] %>
<input type='hidden' name='show' value='1'/>
<% end %>
<% end %>
</div>
<% if params[:body] || params[:name] || params[:response_to] %>
<script type='text/javascript'>$('searchform_hide').hide(); $('searchform').show();</script>
<% end %>

View File

@ -0,0 +1,11 @@
<% content_for(:secondary_links) do %>
<menu>
<%= subnav_link_to "List", blips_path %>
<%= subnav_link_to "New", new_blip_path %>
<!-- <li><%#= link_to "Help", controller: "help", action: "blips" %></li>-->
<% unless @blip&.response_to.nil? %>
<li>|</li>
<%= subnav_link_to "Parent", blip_path(@blip.response_to) %>
<% end %>
</menu>
<% end %>

View File

@ -0,0 +1,7 @@
<div id="c-blips">
<div id="a-edit">
<%= render "form", blip: @blip %>
</div>
</div>
<%= render partial: "secondary_links" %>

View File

@ -0,0 +1,29 @@
<%#= render partial: "search" %>
<div id="c-blips">
<div id="a-index">
<div id="blip-list">
<% if @blips.empty? %>
<h4>No blips</h4>
<% else %>
<% @blips.each do |blip| %>
<% if blip.visible_to?(CurrentUser.user) %>
<div class="response-list">
<%= render partial: "blips/partials/show/blip", locals: {blip: blip} %>
</div>
<% end %>
<% end %>
<% end %>
<div id='preview' class='section' style="width:600px; margin:1em 0; display:none;"></div>
<%= render "form", blip: Blip.new %>
<div id="paginator">
<%= numbered_paginator(@blips) %>
</div>
</div>
</div>
</div>
<%= render partial: "secondary_links" %>

View File

@ -0,0 +1,9 @@
<div id="c-blips">
<div id="a-new">
<h2>New blip</h2>
<%= render partial: "form" %>
</div>
</div>
<%= render partial: "secondary_links" %>

View File

@ -0,0 +1,10 @@
<div class="section" style="float: left; margin: 10px; padding: 5px; width: 97%;">
<% if blips.empty? %>
<p>No blips</p>
<% else %>
<% blips.each do |blip| %>
<% next unless blip.can_view?(current_user) %>
<div class="response-list"><%= render partial: "blip/blip", locals: {blip: blip} %></div>
<% end %>
<% end %>
</div>

View File

@ -0,0 +1,61 @@
<% if blip.visible_to?(CurrentUser.user) %>
<article class="blip">
<div class="author">
<h1>
<%= link_to_user blip.creator %>
<% if blip.is_hidden %>
(hidden)
<% end %>
<%#= avatar_for(blip.creator) %>
</h1>
<p>
<span class="date" title="<%= blip.created_at.strftime("%b %d, %Y %I:%M %p") %>"><%= link_to time_ago_in_words(blip.created_at) + " ago", blip_path(blip) %></span>
</p>
</div>
<div class="content">
<% if blip.response? %>
<h6><%= link_to "In response to blip ##{blip.response_to}", blip_path(id: blip.response_to) %> </h6>
<% end %>
<div class="body prose">
<%= format_text(blip.body) %>
</div>
<menu>
<li><%= tag.a "Respond", href: '#', onclick: "Blip.quote(#{blip.id})" %></li>
<% if blip.can_edit?(CurrentUser.user) %>
<li><%= link_to "Edit", edit_blip_path(blip) %></li>
<% end %>
<li><%= tag.a "@", href: '#', onclick: "Blip.atme(#{blip.id})" %></li>
<% if !blip.is_hidden %>
<% if blip.can_hide?(CurrentUser.user) %>
<li><%= link_to "Hide", hide_blip_path(blip), data: {confirm: "Are you sure you want to hide this blip?"}, method: :post %></li>
<% else %>
<!-- | <%#= link_to "Report", {controller: "ticket", action: "new", type: "blip", disp_id: blip.id}, class: "" %>-->
<% end %>
<% elsif CurrentUser.is_moderator? %>
<li><%= link_to "Unhide", unhide_blip_path(blip), data: {confirm: "Are you sure you want to unhide this blip?"}, method: :post %></li>
<% end %>
<% if CurrentUser.is_moderator? %>
<li><%= link_to "Delete", blip_path(blip), data: {confirm: "Are you sure you want to delete this blip?"}, method: :delete %></li>
<!-- | <%#= link_to "View Edits", controller: "edit_history", action: "show", id: blip.id, type: "Blip" %>-->
<% end %>
<% if blip.has_responses? %>
<li><%= link_to "View Responses", blip_path(blip) %></li>
<% end %>
<% if CurrentUser.is_moderator? %>
<li>|</li>
<li>
<strong>IP</strong>
<span><%= link_to_ip blip.creator_ip_addr %></span>
</li>
<% end %>
</menu>
</div>
<div class="clearfix"></div>
</article>
<% end %>

View File

@ -0,0 +1,30 @@
<div id="c-blips">
<div id="a-show">
<div id="blip" class="response-list">
<% unless params[:page].to_i > 1 %>
<%= render partial: "blips/partials/show/blip", locals: {blip: @blip} %>
<% end %>
<h4>Responses</h4>
<% unless @children.nil? %>
<% @children.each do |c| %>
<% next unless c.visible_to?(CurrentUser.user) %>
<%= render partial: "blips/partials/show/blip", locals: {blip: c} %>
<% end %>
<% end %>
</div>
<% if CurrentUser.is_anonymous? %>
<h5 id="respond-link"><%= link_to "Login to respond &raquo;", controller: "user", action: "login", url: request.request_uri %></h5>
<% else %>
<%= render "form", blip: @blip.responses.new %>
<% end %>
<% unless @children.nil? %>
<div id="paginator">
<%= numbered_paginator(@children) %>
</div>
<% end %>
</div>
</div>
<%= render partial: "secondary_links" %>

View File

@ -9,6 +9,7 @@
<%= nav_link_to("Notes", notes_path) %>
<%= nav_link_to("Artists", artists_path) %>
<%= nav_link_to("Tags", tags_path) %>
<%= nav_link_to("Blips", blips_path) %>
<% if CurrentUser.is_builder? %>
<%= nav_link_to("Aliases", tag_aliases_path) %>
<%= nav_link_to("Implications", tag_implications_path) %>

View File

@ -94,6 +94,11 @@
<li><%= link_to("Listing", wiki_pages_path) %></li>
<li><%= link_to("Changes", wiki_page_versions_path) %></li>
</ul>
<ul>
<li><h1>Blips</h1></li>
<li><%= link_to("Help", wiki_pages_path(title: "help:blips")) %></li>
<li><%= link_to("Listing", blips_path) %></li>
</ul>
<ul>
<li><h1>Artist commentary</h1></li>
<li><%= link_to("Help", wiki_pages_path(:title => "help:artist_commentary")) %></li>

View File

@ -346,6 +346,12 @@ Rails.application.routes.draw do
get :diff
end
end
resources :blips do
member do
post :hide
post :unhide
end
end
# aliases
resources :wpages, :controller => "wiki_pages"

View File

@ -0,0 +1,16 @@
class CreateBlips < ActiveRecord::Migration[5.2]
def change
create_table :blips do |t|
t.column :creator_ip_addr, "inet", null: false
t.column :creator_id, :integer, null: false
t.column :body, :string, null: false
t.column :response_to, :integer
t.boolean :is_hidden, default: false
t.column :body_index, "tsvector", :null => false
t.timestamps
end
execute "CREATE INDEX index_blips_on_body_index ON blips USING GIN (body_index)"
execute "CREATE TRIGGER trigger_blips_on_update BEFORE INSERT OR UPDATE ON blips FOR EACH ROW EXECUTE PROCEDURE tsvector_update_trigger('body_index', 'pg_catalog.english', 'body')"
end
end

View File

@ -514,6 +514,42 @@ CREATE SEQUENCE public.bans_id_seq
ALTER SEQUENCE public.bans_id_seq OWNED BY public.bans.id;
--
-- Name: blips; Type: TABLE; Schema: public; Owner: -
--
CREATE TABLE public.blips (
id bigint NOT NULL,
creator_ip_addr inet NOT NULL,
creator_id integer NOT NULL,
body character varying NOT NULL,
response_to integer,
is_hidden boolean DEFAULT false,
body_index tsvector NOT NULL,
created_at timestamp without time zone NOT NULL,
updated_at timestamp without time zone NOT NULL
);
--
-- Name: blips_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--
CREATE SEQUENCE public.blips_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
--
-- Name: blips_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--
ALTER SEQUENCE public.blips_id_seq OWNED BY public.blips.id;
--
-- Name: bulk_update_requests; Type: TABLE; Schema: public; Owner: -
--
@ -2266,6 +2302,13 @@ ALTER TABLE ONLY public.artists ALTER COLUMN id SET DEFAULT nextval('public.arti
ALTER TABLE ONLY public.bans ALTER COLUMN id SET DEFAULT nextval('public.bans_id_seq'::regclass);
--
-- Name: blips id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.blips ALTER COLUMN id SET DEFAULT nextval('public.blips_id_seq'::regclass);
--
-- Name: bulk_update_requests id; Type: DEFAULT; Schema: public; Owner: -
--
@ -2663,6 +2706,14 @@ ALTER TABLE ONLY public.bans
ADD CONSTRAINT bans_pkey PRIMARY KEY (id);
--
-- Name: blips blips_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.blips
ADD CONSTRAINT blips_pkey PRIMARY KEY (id);
--
-- Name: bulk_update_requests bulk_update_requests_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
@ -3197,6 +3248,13 @@ CREATE INDEX index_bans_on_expires_at ON public.bans USING btree (expires_at);
CREATE INDEX index_bans_on_user_id ON public.bans USING btree (user_id);
--
-- Name: index_blips_on_body_index; Type: INDEX; Schema: public; Owner: -
--
CREATE INDEX index_blips_on_body_index ON public.blips USING gin (body_index);
--
-- Name: index_bulk_update_requests_on_forum_post_id; Type: INDEX; Schema: public; Owner: -
--
@ -4051,6 +4109,13 @@ CREATE INDEX index_wiki_pages_on_title_pattern ON public.wiki_pages USING btree
CREATE INDEX index_wiki_pages_on_updated_at ON public.wiki_pages USING btree (updated_at);
--
-- Name: blips trigger_blips_on_update; Type: TRIGGER; Schema: public; Owner: -
--
CREATE TRIGGER trigger_blips_on_update BEFORE INSERT OR UPDATE ON public.blips FOR EACH ROW EXECUTE PROCEDURE tsvector_update_trigger('body_index', 'pg_catalog.english', 'body');
--
-- Name: comments trigger_comments_on_update; Type: TRIGGER; Schema: public; Owner: -
--
@ -4302,6 +4367,7 @@ INSERT INTO "schema_migrations" (version) VALUES
('20190214090126'),
('20190220025517'),
('20190220041928'),
('20190222082952');
('20190222082952'),
('20190228144206');