mirror of
https://github.com/indentlabs/notebook.git
synced 2025-10-26 11:19:22 +00:00
Merge branch 'master' into tailwind-redesign
This commit is contained in:
commit
862ccbeda6
1
Gemfile
1
Gemfile
@ -78,6 +78,7 @@ gem 'thredded', git: 'https://github.com/indentlabs/thredded.git', branch: 'feat
|
||||
# gem 'thredded', git: 'https://github.com/sudara/thredded', branch: 'master'
|
||||
gem 'rails-ujs'
|
||||
gem 'language_filter'
|
||||
gem 'onebox', git: 'https://github.com/indentlabs/onebox.git', branch: 'notebook-engine'
|
||||
gem 'discordrb'
|
||||
|
||||
# Smarts
|
||||
|
||||
39
Gemfile.lock
39
Gemfile.lock
@ -5,6 +5,19 @@ GIT
|
||||
specs:
|
||||
birch (0.0.8)
|
||||
|
||||
GIT
|
||||
remote: https://github.com/indentlabs/onebox.git
|
||||
revision: fa5addc8d1ba6e988f54a7c4a83a770ed1d638ce
|
||||
branch: notebook-engine
|
||||
specs:
|
||||
onebox (2.2.19)
|
||||
addressable (~> 2.8.0)
|
||||
htmlentities (~> 4.3)
|
||||
multi_json (~> 1.11)
|
||||
mustache
|
||||
nokogiri (~> 1.7)
|
||||
sanitize
|
||||
|
||||
GIT
|
||||
remote: https://github.com/indentlabs/serendipitous-gem.git
|
||||
revision: 393c9b664e0cbfacfc06d44dfd43898413b539a5
|
||||
@ -1281,7 +1294,7 @@ GEM
|
||||
coffee-script-source
|
||||
execjs
|
||||
coffee-script-source (1.12.2)
|
||||
concurrent-ruby (1.1.9)
|
||||
concurrent-ruby (1.1.10)
|
||||
connection_pool (2.2.5)
|
||||
crass (1.0.6)
|
||||
csv (3.2.2)
|
||||
@ -1363,7 +1376,7 @@ GEM
|
||||
concurrent-ruby (~> 1.0)
|
||||
http (~> 4.4.0)
|
||||
jwt (~> 2.2.1)
|
||||
ibm_watson (2.1.3)
|
||||
ibm_watson (2.2.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
eventmachine (~> 1.2)
|
||||
faye-websocket (~> 0.11)
|
||||
@ -1440,18 +1453,11 @@ GEM
|
||||
netrc (0.11.0)
|
||||
newrelic_rpm (8.5.0)
|
||||
nio4r (2.5.8)
|
||||
nokogiri (1.13.3)
|
||||
nokogiri (1.13.6)
|
||||
mini_portile2 (~> 2.8.0)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.13.3-x86_64-linux)
|
||||
nokogiri (1.13.6-x86_64-linux)
|
||||
racc (~> 1.4)
|
||||
onebox (2.2.19)
|
||||
addressable (~> 2.8.0)
|
||||
htmlentities (~> 4.3)
|
||||
multi_json (~> 1.11)
|
||||
mustache
|
||||
nokogiri (~> 1.7)
|
||||
sanitize
|
||||
opus-ruby (1.0.1)
|
||||
ffi
|
||||
orm_adapter (0.5.0)
|
||||
@ -1461,7 +1467,7 @@ GEM
|
||||
mime-types
|
||||
mimemagic (~> 0.3.0)
|
||||
terrapin (~> 0.6.0)
|
||||
paranoia (2.5.2)
|
||||
paranoia (2.6.0)
|
||||
activerecord (>= 5.1, < 7.1)
|
||||
paypal-checkout-sdk (1.0.4)
|
||||
paypalhttp (~> 1.0.1)
|
||||
@ -1470,12 +1476,12 @@ GEM
|
||||
faraday (~> 0.15)
|
||||
faraday_middleware (~> 0.12)
|
||||
paypalhttp (1.0.1)
|
||||
pg (1.3.4)
|
||||
pg (1.3.5)
|
||||
pry (0.14.1)
|
||||
coderay (~> 1.1)
|
||||
method_source (~> 1.0)
|
||||
public_suffix (4.0.6)
|
||||
puma (5.6.2)
|
||||
puma (5.6.4)
|
||||
nio4r (~> 2.0)
|
||||
puma-heroku (2.0.0)
|
||||
puma (>= 5.0, < 6.0)
|
||||
@ -1605,7 +1611,7 @@ GEM
|
||||
sqlite3 (1.4.2)
|
||||
stackprof (0.2.19)
|
||||
statsd-ruby (1.5.0)
|
||||
stripe (5.45.0)
|
||||
stripe (5.55.0)
|
||||
stripe_event (2.3.2)
|
||||
activesupport (>= 3.1)
|
||||
stripe (>= 2.8, < 6)
|
||||
@ -1625,7 +1631,7 @@ GEM
|
||||
execjs (>= 0.3.0, < 3)
|
||||
unf (0.1.4)
|
||||
unf_ext
|
||||
unf_ext (0.0.8)
|
||||
unf_ext (0.0.8.1)
|
||||
uniform_notifier (1.14.2)
|
||||
warden (1.2.9)
|
||||
rack (>= 2.0.9)
|
||||
@ -1690,6 +1696,7 @@ DEPENDENCIES
|
||||
meta-tags
|
||||
mini_racer (~> 0.6.2)
|
||||
newrelic_rpm
|
||||
onebox!
|
||||
paperclip
|
||||
paranoia
|
||||
paypal-checkout-sdk
|
||||
|
||||
@ -19,6 +19,7 @@ Notebook.init = function() {
|
||||
});
|
||||
$('.slider').slider({ height: 200, indicators: false });
|
||||
$('.dropdown-trigger').dropdown({ coverTrigger: false });
|
||||
$('.dropdown-trigger-on-hover').dropdown({ coverTrigger: false, hover: true });
|
||||
$('.tooltipped').tooltip({ enterDelay: 50 });
|
||||
$('.with-character-counter').characterCounter();
|
||||
$('.materialboxed').materialbox();
|
||||
|
||||
@ -100,6 +100,7 @@ $(document).ready(function () {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
$('.js-load-page-name').each(function() {
|
||||
// Replace this element's content with the name of the page
|
||||
var tag = $(this);
|
||||
@ -130,9 +131,6 @@ $(document).ready(function () {
|
||||
tag.find('.name-container').text("Unknown " + tag.data('klass'));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@ -33,7 +33,6 @@ $(document).ready(function () {
|
||||
$('.js-move-event-up').click(function () {
|
||||
var event_container = $(this).closest('.timeline-event-container');
|
||||
var event_id = event_container.data('event-id');
|
||||
debugger;
|
||||
|
||||
$.get(
|
||||
"/plan/move/timeline_events/" + event_id + "/up"
|
||||
@ -117,6 +116,20 @@ $(document).ready(function () {
|
||||
// Update IDs to the newly-created event
|
||||
cloned_template.data('event-id', new_event_id);
|
||||
cloned_template.attr('data-event-id', new_event_id);
|
||||
|
||||
// Update labels to jump to this event's fields
|
||||
var title_field = cloned_template.find('.ref-title');
|
||||
title_field.find('input').attr('id', 'timeline-event-title-' + new_event_id);
|
||||
title_field.find('label').attr('for', 'timeline-event-title-' + new_event_id);
|
||||
|
||||
var desc_field = cloned_template.find('.ref-description');
|
||||
desc_field.find('textarea').attr('id', 'timeline-event-description-' + new_event_id);
|
||||
desc_field.find('label').attr('for', 'timeline-event-description-' + new_event_id);
|
||||
|
||||
var notes_field = cloned_template.find('.ref-notes');
|
||||
notes_field.find('textarea').attr('id', 'timeline-event-notes-' + new_event_id);
|
||||
notes_field.find('label').attr('for', 'timeline-event-notes-' + new_event_id);
|
||||
|
||||
//cloned_template.find('input[name="timeline_event[timeline_id]"]').val(timeline_id);
|
||||
cloned_template.find('.js-delete-timeline-event').attr('href', '/plan/timeline_events/' + new_event_id);
|
||||
cloned_template.find('.autosave-form').attr('action', '/plan/timeline_events/' + new_event_id);
|
||||
|
||||
@ -277,4 +277,4 @@ body {
|
||||
color: black;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,23 +2,55 @@
|
||||
|
||||
#thredded--container {
|
||||
#q /* search input */ {
|
||||
height: 37px;
|
||||
padding-left: 16px;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 600px) {
|
||||
#q /* search input */ {
|
||||
height: 19px;
|
||||
}
|
||||
}
|
||||
|
||||
.thredded--navigation-breadcrumbs {
|
||||
margin-top: 0.4em;
|
||||
padding-left: 0.1em;
|
||||
overflow: hidden;
|
||||
min-height: 5em;
|
||||
max-height: 60px;
|
||||
|
||||
li a {
|
||||
padding: 0;
|
||||
line-height: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.thredded--scoped-navigation li a {
|
||||
padding: 0;
|
||||
line-height: 2rem;
|
||||
}
|
||||
|
||||
.thredded--user-navigation {
|
||||
height: 70px;
|
||||
|
||||
margin-top: 10px;
|
||||
margin-right: 10px;
|
||||
height: 2rem;
|
||||
margin: 1rem;
|
||||
border-bottom: 0;
|
||||
|
||||
.thredded--user-navigation--item a {
|
||||
padding: 0;
|
||||
padding: 8px 4px;
|
||||
line-height: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
.thredded--user-navigation {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.thredded--post--user {
|
||||
color: black;
|
||||
|
||||
a {
|
||||
color: #347a36;
|
||||
}
|
||||
}
|
||||
|
||||
@ -91,9 +123,22 @@
|
||||
.thredded--main-header {
|
||||
nav {
|
||||
background: white;
|
||||
padding: 0 10px;
|
||||
|
||||
margin-bottom: 100px;
|
||||
margin-bottom: 4rem;
|
||||
line-height: 3rem;
|
||||
}
|
||||
|
||||
|
||||
@media only screen and (min-width: 600px) {
|
||||
nav {
|
||||
height: 3rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.thredded--topic-header {
|
||||
.thredded--topic-header--title {
|
||||
margin-bottom: 0.4em;
|
||||
}
|
||||
}
|
||||
|
||||
@ -122,6 +167,8 @@
|
||||
}
|
||||
|
||||
.thredded--post--content {
|
||||
padding-top: 0.6em !important;
|
||||
|
||||
ul {
|
||||
list-style-type: inherit !important;
|
||||
padding-left: 40px !important;
|
||||
@ -152,3 +199,7 @@
|
||||
padding-top: 5px !important;
|
||||
}
|
||||
}
|
||||
.thredded--topic-delete--wrapper {
|
||||
margin-top: 0.5rem !important;
|
||||
padding-top: 1rem !important;
|
||||
}
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
class ThreddedProxyController < ApplicationController
|
||||
before_action :authenticate_user!, only: [:view_as_plaintext, :view_as_irc_log, :save_to_document]
|
||||
|
||||
def topic
|
||||
topic = Thredded::Topic.find_by(slug: params[:slug])
|
||||
if topic.present?
|
||||
@ -10,4 +12,20 @@ class ThreddedProxyController < ApplicationController
|
||||
redirect_back(fallback_location: root_path, notice: "The link you tried to visit is invalid or no longer exists.")
|
||||
end
|
||||
end
|
||||
|
||||
def view_as_plaintext
|
||||
topic = Thredded::Topic.find_by(slug: params[:slug])
|
||||
render plain: ForumsProsifyService.prosify_text(topic)
|
||||
end
|
||||
|
||||
def view_as_irc_log
|
||||
topic = Thredded::Topic.find_by(slug: params[:slug])
|
||||
render plain: ForumsProsifyService.prosify_irc_log(topic)
|
||||
end
|
||||
|
||||
def save_to_document
|
||||
topic = Thredded::Topic.find_by(slug: params[:slug])
|
||||
document = ForumsProsifyService.save_to_document(current_user, topic)
|
||||
redirect_to document, notice: "Thread saved to document!"
|
||||
end
|
||||
end
|
||||
|
||||
@ -30,6 +30,10 @@ class UsersController < ApplicationController
|
||||
# We double up on the same returns from set_user here since we're calling set_user manually instead of a before_action
|
||||
return if @user.nil?
|
||||
return if @user.private_profile?
|
||||
|
||||
@random_image_including_private_pool_cache = ImageUpload.where(
|
||||
user_id: @user.id,
|
||||
).group_by { |image| [image.content_type, image.content_id] }
|
||||
|
||||
@content_type = content_type
|
||||
@content_list = @user.send(content_type_name).is_public.order(:name)
|
||||
|
||||
@ -107,7 +107,9 @@ class DocumentEntitiesSidebar extends React.Component {
|
||||
<i className={"material-icons left " + this.classColor(entity_type) + "-text"}>
|
||||
{ this.classIcon(entity_type) }
|
||||
</i>
|
||||
{ entity.text }
|
||||
<span className='js-load-page-name' data-klass={entity_type} data-id={entity.entity_id}>
|
||||
<span className='name-container'>{ entity.text }</span>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
|
||||
@ -34,6 +34,8 @@ class PageLookupSidebar extends React.Component {
|
||||
show_data: false,
|
||||
category_open: {}
|
||||
};
|
||||
|
||||
window.page_lookup_cache = {};
|
||||
}
|
||||
|
||||
classIcon(class_name) {
|
||||
@ -51,32 +53,45 @@ class PageLookupSidebar extends React.Component {
|
||||
category_open: {}
|
||||
});
|
||||
|
||||
// make api request
|
||||
await axios.get(
|
||||
"/api/v1/" + page_type.toLowerCase() + "/" + page_id
|
||||
+ '?application_token=4756de490e82956dc6329e6650aaec664e27ccd27e153e2f'
|
||||
+ '&authorization_token=167bb93139303904cf67f6480a29e71c9f1eaf7a28e902e1',
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
}
|
||||
).then(response => {
|
||||
// load response into list
|
||||
if ((page_type + '-' + page_id) in window.page_lookup_cache) {
|
||||
this.setState({
|
||||
page_data: response.data,
|
||||
show_data: !response.data.hasOwnProperty('error'),
|
||||
page_data: window.page_lookup_cache[page_type + '-' + page_id],
|
||||
show_data: true,
|
||||
page_type: page_type,
|
||||
page_id: page_id
|
||||
});
|
||||
|
||||
}).catch(err => {
|
||||
console.log(err);
|
||||
this.setDrawerVisible(false);
|
||||
} else {
|
||||
// make new api request
|
||||
await axios.get(
|
||||
"/api/v1/" + page_type.toLowerCase() + "/" + page_id
|
||||
+ '?application_token=4756de490e82956dc6329e6650aaec664e27ccd27e153e2f'
|
||||
+ '&authorization_token=167bb93139303904cf67f6480a29e71c9f1eaf7a28e902e1',
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
}
|
||||
).then(response => {
|
||||
// cache response
|
||||
window.page_lookup_cache[page_type + '-' + page_id] = response.data;
|
||||
|
||||
return null;
|
||||
});
|
||||
// load response into list
|
||||
this.setState({
|
||||
page_data: response.data,
|
||||
show_data: !response.data.hasOwnProperty('error'),
|
||||
page_type: page_type,
|
||||
page_id: page_id
|
||||
});
|
||||
|
||||
}).catch(err => {
|
||||
console.log(err);
|
||||
this.setDrawerVisible(false);
|
||||
|
||||
return null;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
content_page_view_path(content_type, content_id) {
|
||||
|
||||
@ -7,6 +7,7 @@ class NotifyDiscordOfThreadJob < ApplicationJob
|
||||
thread_id = args.shift
|
||||
thread = Thredded::Topic.find_by(id: thread_id)
|
||||
raise "No thread found for new ID #{thread.id.inspect}" unless thread
|
||||
return if thread.moderation_state == "blocked"
|
||||
|
||||
webhook_url = ENV.fetch('DISCORD_FORUMS_WEBHOOK', '').freeze
|
||||
|
||||
|
||||
@ -36,7 +36,10 @@ module HasImageUploads
|
||||
end
|
||||
|
||||
def header_asset_for(class_name)
|
||||
ActionController::Base.helpers.asset_path("card-headers/#{class_name.downcase.pluralize}.webp")
|
||||
# Since we use this as a fallback image on SEO content (for example, Twitter cards for shared notebook pages),
|
||||
# we need to include the full protocol + domain + path to ensure they will display the image. A relative path
|
||||
# will not work.
|
||||
"https://www.notebook.ai" + ActionController::Base.helpers.asset_url("card-headers/#{class_name.downcase.pluralize}.webp")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
class DocumentEntity < ApplicationRecord
|
||||
belongs_to :entity, polymorphic: true, optional: true
|
||||
belongs_to :document_analysis
|
||||
belongs_to :document_analysis, optional: true
|
||||
|
||||
after_create :match_notebook_page!, if: Proc.new { |de| de.entity_id.nil? }
|
||||
|
||||
|
||||
@ -18,7 +18,8 @@ class ImageUpload < ApplicationRecord
|
||||
SecureRandom.uuid,
|
||||
File.extname(filename).downcase
|
||||
].join
|
||||
}
|
||||
},
|
||||
s3_protocol: 'https'
|
||||
# has_one_attached :upload
|
||||
|
||||
validates_attachment_content_type :src, content_type: /\Aimage\/.*\Z/
|
||||
|
||||
@ -59,7 +59,30 @@ class ApiContentSerializer
|
||||
def value_for(attribute_field, content)
|
||||
case attribute_field.field_type
|
||||
when 'link'
|
||||
self.raw_model.send(attribute_field.old_column_source)
|
||||
page_links = attribute_field.attribute_values.find_by(entity_type: content.class.name, entity_id: content.id)
|
||||
if page_links.nil?
|
||||
# Fall back on old relation value
|
||||
# We're technically doing a double lookup here (by converting response
|
||||
# to link code, then looking up again later) but since this is just stopgap
|
||||
# code to standardize links in views this should be fine for now.
|
||||
if attribute_field.old_column_source.present?
|
||||
self.raw_model.send(attribute_field.old_column_source).map { |page| "#{page.page_type}-#{page.id}" }
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
||||
else
|
||||
# Use new link system
|
||||
begin
|
||||
JSON.parse(page_links.value)
|
||||
rescue
|
||||
if page_links.value == ""
|
||||
[]
|
||||
else
|
||||
"Error loading Attribute ID #{page_links.id}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
when 'tags'
|
||||
self.page_tags
|
||||
|
||||
@ -142,7 +142,7 @@ class ContentSerializer
|
||||
end
|
||||
|
||||
when 'tags'
|
||||
self.raw_model.page_tags
|
||||
self.page_tags
|
||||
|
||||
else # text_area, name, universe, etc
|
||||
#codesmell here: we shouldn't ever have multiple attribute values but for some reason we do sometimes (in collaboration?)
|
||||
|
||||
@ -12,11 +12,15 @@ class ContentFormatterService < Service
|
||||
# todo page slugs could be cool for this? I dunno. We probably don't want to use a
|
||||
# field like name that can change ([[bob]]) or have an ambiguous link.
|
||||
TOKEN_REGEX = /\[\[([^\-]+)\-([^\]]+)\]\]/
|
||||
|
||||
# For finding links to Notebook.ai pages in the form notebook.ai/plan/characters/12345
|
||||
LINK_REGEX = /https?:\/\/(?:www\.)?(?:(?:\w)+\.)?notebook\.ai\/plan\/([\w]+)\/([\d]+)/
|
||||
|
||||
# Only allow linking to content type classes
|
||||
# todo: we shouldn't have to map name here, but apparently rails is having a little difficulty
|
||||
# https://s3.amazonaws.com/raw.paste.esk.io/Llb%2F64DJHK?versionId=19Lb_TtukDbo1J_IoCpkr.d.pwpW_vmH
|
||||
VALID_LINK_CLASSES = Rails.application.config.content_types[:all].map(&:name) + %w(Timeline Document)
|
||||
VALID_LINK_CLASSES = Rails.application.config.content_type_names[:all] + %w(Timeline Document)
|
||||
|
||||
def self.show(text:, viewing_user: User.new)
|
||||
# We want to evaluate markdown first, because the markdown engine also happens
|
||||
# to strip out HTML tags. So: markdown, _then_ insert content links.
|
||||
@ -48,6 +52,21 @@ class ContentFormatterService < Service
|
||||
end
|
||||
end
|
||||
|
||||
# Build links for linking documents to the pages they reference
|
||||
def self.links_to_replace(text)
|
||||
text.scan(LINK_REGEX).map do |klass, id|
|
||||
# Sanitize klass (which is plural/lower to singular/title)
|
||||
sanitized_klass = klass.singularize.titleize
|
||||
next unless VALID_LINK_CLASSES.include?(sanitized_klass)
|
||||
|
||||
{
|
||||
content_type: sanitized_klass,
|
||||
content_id: id,
|
||||
matched_string: "https://www.notebook.ai/plan/#{klass}/#{id}"
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def self.replacement_for_token(token, viewing_user)
|
||||
return unknown_link_template(token) unless token.key?(:content_type) && token.key?(:content_id)
|
||||
begin
|
||||
@ -97,6 +116,28 @@ class ContentFormatterService < Service
|
||||
end
|
||||
end
|
||||
|
||||
def self.name_autoloaded_chip_template(klass_model, id)
|
||||
content_tag(:span, class: 'chip') do
|
||||
body = ''
|
||||
if klass_model
|
||||
body += content_tag(:span,
|
||||
class: "js-load-page-name #{klass_model.text_color}",
|
||||
data: { klass: klass_model.name, id: id }
|
||||
) do
|
||||
[
|
||||
content_tag(:i, class: 'material-icons left', style: 'position: relative; top: 3px;') do
|
||||
klass_model.icon
|
||||
end,
|
||||
content_tag(:span, class: 'name-container') do
|
||||
"<em>Loading #{klass_model.name.downcase} ##{id}...</em>".html_safe
|
||||
end
|
||||
].join.html_safe
|
||||
end
|
||||
end
|
||||
body.html_safe
|
||||
end
|
||||
end
|
||||
|
||||
def self.inline_template(class_model=nil)
|
||||
content_tag(:span, class: 'inline-link') do
|
||||
content_tag(:span, class: class_model ? "#{class_model.text_color}" : '') do
|
||||
|
||||
@ -19,9 +19,6 @@ class ForumReplacementService < Service
|
||||
# perspective changes, always surrounded by {} (e.g. {@reader} )
|
||||
PERSPECTIVE_REPLACEMENTS = {
|
||||
'@reader' => Proc.new { |trigger, user| (user.nil? ? 'anonymous reader' : user.try(:display_name)) || 'anonymous reader' }
|
||||
# 'their' => Proc.new { |_, _| ["they're", "their", "there"].sample }
|
||||
# 'there' => Proc.new { |_, _| ["they're", "their", "there"].sample }
|
||||
# "they're" => Proc.new { |_, _| ["they're", "their", "there"].sample }
|
||||
}
|
||||
|
||||
# turn on and off at will
|
||||
@ -413,9 +410,14 @@ class ForumReplacementService < Service
|
||||
|
||||
def self.replace_for(text, user)
|
||||
gremlins_phase = 0
|
||||
|
||||
replaced_text = text.dup
|
||||
|
||||
# Page tag replacements
|
||||
replaced_text = ContentFormatterService.substitute_content_links(
|
||||
replaced_text,
|
||||
user
|
||||
)
|
||||
|
||||
SPAM_WORD_REPLACEMENTS.each do |trigger, replacement|
|
||||
replaced_text.gsub!(/\b#{trigger.downcase}\b/i, wrapped(replacement, trigger, 'red'))
|
||||
end
|
||||
@ -424,9 +426,22 @@ class ForumReplacementService < Service
|
||||
replaced_text.gsub!(/{#{trigger.downcase}}/i, wrapped(replacement.call(trigger, user), trigger, 'blue'))
|
||||
end
|
||||
|
||||
if false # not implemented: [[Character-123]] or https://www.notebook.ai/plan/characters/553 etc
|
||||
REFERENCE_REPLACEMENTS.each do |trigger, replacement|
|
||||
replaced_text.gsub!(/\b#{trigger.downcase}\b/i, wrapped(replacement, trigger, 'notebook-blue'))
|
||||
if true # not implemented: [[Character-123]] or https://www.notebook.ai/plan/characters/553 etc
|
||||
replaced_text.gsub!(/>https?:\/\/(?:www\.)?(?:(?:\w)+\.)?notebook\.ai\/plan\/([\w]+)\/([\d]+)</).each do |match|
|
||||
klass = $1.singularize.titleize
|
||||
|
||||
linkable_whitelist = Rails.application.config.content_type_names[:all]
|
||||
if linkable_whitelist.include? klass
|
||||
token = {
|
||||
content_type: klass,
|
||||
content_id: $2.to_i,
|
||||
matched_string: "Invalid #{klass}"
|
||||
}
|
||||
replacement = ContentFormatterService.replacement_for_token(token, User.new)
|
||||
">#{replacement}<"
|
||||
else
|
||||
match
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
78
app/services/forums_prosify_service.rb
Normal file
78
app/services/forums_prosify_service.rb
Normal file
@ -0,0 +1,78 @@
|
||||
class ForumsProsifyService < Service
|
||||
ENDLINE = "\r\n"
|
||||
|
||||
def self.prosify_text(thredded_topic, strip_parentheticals=true)
|
||||
prose = ""
|
||||
|
||||
thredded_topic.posts.find_each do |post|
|
||||
paragraphs = post.content.split(ENDLINE)
|
||||
|
||||
paragraphs.each do |paragraph|
|
||||
prose += "\t#{paragraph}#{ENDLINE}" unless strip_parentheticals && post.content.start_with?('(') && post.content.end_with?(')')
|
||||
end
|
||||
end
|
||||
|
||||
prose
|
||||
end
|
||||
|
||||
def self.prosify_irc_log(thredded_topic, strip_parentheticals=true)
|
||||
prose = "-!- Topic: #{thredded_topic.title}#{ENDLINE}"
|
||||
user_display_name_cache = {}
|
||||
|
||||
thredded_topic.posts.find_each do |post|
|
||||
paragraphs = post.content.split(ENDLINE)
|
||||
user_display_name_cache[post.user_id] = post.user.try(:display_name) || "Anonymous" unless user_display_name_cache.key?(post.user_id)
|
||||
|
||||
paragraphs.each do |paragraph|
|
||||
next if paragraph.empty?
|
||||
next if strip_parentheticals && post.content.start_with?('(') && post.content.end_with?(')')
|
||||
|
||||
prose += "<#{user_display_name_cache[post.user_id]}> #{paragraph}#{ENDLINE}"
|
||||
end
|
||||
end
|
||||
|
||||
prose
|
||||
end
|
||||
|
||||
def self.prosify_html(thredded_topic, strip_parentheticals=true)
|
||||
prose = ""
|
||||
user_display_name_cache = {}
|
||||
|
||||
thredded_topic.posts.find_each do |post|
|
||||
user_display_name_cache[post.user_id] = post.user.try(:display_name) || "Anonymous user" unless user_display_name_cache.key?(post.user_id)
|
||||
|
||||
tooltip = "authored by #{user_display_name_cache[post.user_id]}"
|
||||
prose += "<p class='tooltipped' data-tooltip='#{tooltip}' data-position='right'>#{post.content}</p>" unless strip_parentheticals && post.content.start_with?('(') && post.content.end_with?(')')
|
||||
end
|
||||
|
||||
prose
|
||||
end
|
||||
|
||||
def self.save_to_document(user, thredded_topic, strip_parentheticals=true)
|
||||
document = user.documents.create!(
|
||||
title: "#{thredded_topic.title} (forums post)",
|
||||
body: prosify_html(thredded_topic, strip_parentheticals)
|
||||
)
|
||||
analysis = document.document_analysis.create!
|
||||
|
||||
# If there are any links to Notebook.ai pages or [[page-id]] tokens in this post, we should automatically link those pages
|
||||
tokens = ContentFormatterService.tokens_to_replace(document.body)
|
||||
tokens.each do |token|
|
||||
analysis.document_entities.find_or_create_by(
|
||||
entity_type: token[:content_type],
|
||||
entity_id: token[:content_id]
|
||||
)
|
||||
end
|
||||
|
||||
links = ContentFormatterService.links_to_replace(document.body)
|
||||
links.each do |link|
|
||||
analysis.document_entities.find_or_create_by(
|
||||
entity_type: link[:content_type],
|
||||
entity_id: link[:content_id],
|
||||
text: link[:matched_string]
|
||||
)
|
||||
end
|
||||
|
||||
document
|
||||
end
|
||||
end
|
||||
@ -3,6 +3,7 @@
|
||||
<div>
|
||||
<%= image_tag asset_path("card-headers/#{@content_type_name.downcase.pluralize}.webp"), class: 'h-32 w-full object-cover lg:h-48' %>
|
||||
</div>
|
||||
|
||||
<div class="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="-mt-12 sm:-mt-16 sm:flex sm:items-end sm:space-x-5">
|
||||
<div class="flex">
|
||||
|
||||
@ -33,13 +33,13 @@
|
||||
</div>
|
||||
<div class="card-action">
|
||||
<% if current_user.can_update?(content) %>
|
||||
<%= link_to content.edit_path, class: 'green-text right', target: content.is_a?(Document) ? '_new' : '_self' do %>
|
||||
<%= link_to content.is_a?(ContentPage) ? content.edit_path : edit_polymorphic_path(content), class: 'green-text right', target: content.is_a?(Document) ? '_new' : '_self' do %>
|
||||
<i class="material-icons left"><%= content_type.icon %></i>
|
||||
Edit
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% if current_user.can_read?(content) %>
|
||||
<%= link_to content.view_path, class: 'blue-text text-lighten-1' do %>
|
||||
<%= link_to content.is_a?(ContentPage) ? content.view_path : content, class: 'blue-text text-lighten-1' do %>
|
||||
<i class="material-icons left"><%= content_type.icon %></i>
|
||||
View
|
||||
<% end %>
|
||||
|
||||
@ -1,8 +1,12 @@
|
||||
<%
|
||||
page_description = "#{@content.name}, #{@content.description} — a fictional #{@content.class.name.downcase} on Notebook.ai"
|
||||
page_description ||= "#{@content.name} is a fictional #{@content.class.name.downcase} on Notebook.ai"
|
||||
|
||||
set_meta_tags title: @content.name,
|
||||
description: "#{%w(a e i o u).include?(content.class.name.downcase[0]) ? "An" : "A"} #{@content.class.name.downcase} on Notebook.ai",
|
||||
description: page_description,
|
||||
image_src: @content.first_public_image,
|
||||
og: { type: 'website' }
|
||||
og: { type: 'website' },
|
||||
twitter: { card: 'photo', image: @content.first_public_image }
|
||||
%>
|
||||
|
||||
<%= content_for :full_width_page_header do %>
|
||||
|
||||
@ -1,26 +1,32 @@
|
||||
<%= content_for :javascript do %>
|
||||
var tribute = new Tribute({
|
||||
values: [
|
||||
<% @linkables_cache.each do |class_name, collection| %>
|
||||
<% linkable_class = content_class_from_name(class_name) %>
|
||||
<% collection.each do |page_name, page_id| %>
|
||||
{
|
||||
key: "<%= page_name.gsub('"', "\"").gsub("\n", " ").gsub("\r", " ").strip %>",
|
||||
value: '[[<%= class_name %>-<%= page_id %>]]',
|
||||
color: '<%= linkable_class.color %>',
|
||||
icon: '<%= linkable_class.icon %>'
|
||||
},
|
||||
<% if @linkables_cache %>
|
||||
<%= content_for :javascript do %>
|
||||
var tribute = new Tribute({
|
||||
<%#
|
||||
TODO: tribute allows us to populate values async, so that might be worth exploring the tradeoffs of
|
||||
https://github.com/zurb/tribute
|
||||
%>
|
||||
values: [
|
||||
<% @linkables_cache.each do |class_name, collection| %>
|
||||
<% linkable_class = content_class_from_name(class_name) %>
|
||||
<% collection.each do |page_name, page_id| %>
|
||||
{
|
||||
key: "<%= page_name.gsub('"', "\"").gsub("\n", " ").gsub("\r", " ").strip %>",
|
||||
value: '[[<%= class_name %>-<%= page_id %>]]',
|
||||
color: '<%= linkable_class.color %>',
|
||||
icon: '<%= linkable_class.icon %>'
|
||||
},
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
],
|
||||
selectTemplate: function (item) {
|
||||
// We're overriding the default here so we don't prepend a @
|
||||
return item.original.value;
|
||||
},
|
||||
menuItemTemplate: function (item) {
|
||||
return '<i class="material-icons right ' + item.original.color + '-text">' + item.original.icon + '</i>' + item.string;
|
||||
},
|
||||
spaceSelectsMatch: false
|
||||
});
|
||||
tribute.attach(document.querySelectorAll('.js-can-mention-pages'));
|
||||
],
|
||||
selectTemplate: function (item) {
|
||||
// We're overriding the default here so we don't prepend a @
|
||||
return item.original.value;
|
||||
},
|
||||
menuItemTemplate: function (item) {
|
||||
return '<i class="material-icons right ' + item.original.color + '-text">' + item.original.icon + '</i>' + item.string;
|
||||
},
|
||||
spaceSelectsMatch: false
|
||||
});
|
||||
tribute.attach(document.querySelectorAll('.js-can-mention-pages'));
|
||||
<% end %>
|
||||
<% end %>
|
||||
@ -1,6 +1,6 @@
|
||||
<%= view_hooks.messageboards_index.messageboard.render self, messageboard: messageboard do %>
|
||||
<%= link_to messageboard.path,
|
||||
class: ['thredded--messageboard',
|
||||
class: ['thredded--messageboard hoverable',
|
||||
*('thredded--messageboard--has-unread-topics' if messageboard.unread_topics?),
|
||||
*('thredded--messageboard--has-unread-followed-topics' if messageboard.unread_followed_topics?)] do %>
|
||||
<%= render 'thredded/messageboards/messageboard/header', messageboard: messageboard %>
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
<% post, content = post_and_content if local_assigns.key?(:post_and_content) %>
|
||||
|
||||
<%
|
||||
blocked_post = user_signed_in? && post.user.present? && post.user.blocked_by?(current_user)
|
||||
<%
|
||||
post, content = post_and_content if local_assigns.key?(:post_and_content)
|
||||
blocked_post = user_signed_in? && post.user.present? && post.user.blocked_by?(current_user)
|
||||
%>
|
||||
|
||||
<%
|
||||
@ -14,6 +13,7 @@
|
||||
<div class='grey-text text-lighten-2'>
|
||||
<em class="tooltipped" data-tooltip="This message is hidden because you've blocked the user it's from.">1 message hidden.</em>
|
||||
</div>
|
||||
|
||||
<% else %>
|
||||
<%= render 'thredded/posts_common/before_first_unread_post', post: post if post.first_unread_in_page? %>
|
||||
<%= content_tag :article, id: dom_id(post), class: "thredded--post thredded--#{post.read_state}--post #{muted_post ? muted_post_classes : 'card'}" do %>
|
||||
|
||||
7
app/views/thredded/posts_common/_header.html.erb
Normal file
7
app/views/thredded/posts_common/_header.html.erb
Normal file
@ -0,0 +1,7 @@
|
||||
<header>
|
||||
<%= image_tag post.avatar_url, class: 'thredded--post--avatar' if post.user %>
|
||||
<h2 class="thredded--post--user"><%= user_link post.user %></h2>
|
||||
<a href="<%= post.permalink_path %>" rel="nofollow" class="thredded--link thredded--post--created-at right">
|
||||
<%= time_ago post.created_at %>
|
||||
</a>
|
||||
</header>
|
||||
13
app/views/thredded/posts_common/form/_content_field.html.erb
Normal file
13
app/views/thredded/posts_common/form/_content_field.html.erb
Normal file
@ -0,0 +1,13 @@
|
||||
<li>
|
||||
<%= form.label :content, content_label %>
|
||||
<%= view_hooks.post_form.content_text_area.render self, form: form, content_label: content_label do %>
|
||||
<%= render 'thredded/posts_common/form/before_content', form: form %>
|
||||
<%= form.text_area :content, {rows: 5, required: true, autofocus: !!params[:autofocus_new_post_content], class: 'js-can-mention-pages'} %>
|
||||
<%= render 'thredded/posts_common/form/after_content', form: form %>
|
||||
<% end %>
|
||||
<div>
|
||||
<%= link_to 'https://www.markdownguide.org/basic-syntax/', target: '_blank' do %>
|
||||
<span class="hoverable badge new blue white-text" data-badge-caption="formatting help"></span>
|
||||
<% end %>
|
||||
</div>
|
||||
</li>
|
||||
99
app/views/thredded/preferences/_form.html.erb
Normal file
99
app/views/thredded/preferences/_form.html.erb
Normal file
@ -0,0 +1,99 @@
|
||||
<%# @type preferences [Thredded::UserPreferencesForm] %>
|
||||
<%= form_for(preferences, method: :patch, url: preferences.update_path, html: {
|
||||
class: 'thredded--form thredded--notification-preferences-form',
|
||||
'data-thredded-user-preferences-form' => true
|
||||
}) do |f| %>
|
||||
<div class="card-panel">
|
||||
<ul class="thredded--form-list thredded--user-preferences-global">
|
||||
<li class="thredded--user-preferences--auto-follow-topics">
|
||||
<%= f.label :auto_follow_topics do %>
|
||||
<%= f.check_box :auto_follow_topics,
|
||||
'data-thredded-update-checkbox-on-change' =>
|
||||
'user_preferences_form[messageboard_auto_follow_topics]' %>
|
||||
<%= t 'thredded.preferences.form.auto_follow_topics.label' %>
|
||||
<p class="thredded--form-list--hint">
|
||||
<%= t 'thredded.preferences.form.auto_follow_topics.hint' %>
|
||||
</p>
|
||||
<% end %>
|
||||
</li>
|
||||
<li class="thredded--user-preferences--follow-topics-on-mention">
|
||||
<%= f.label :follow_topics_on_mention do %>
|
||||
<%= f.check_box :follow_topics_on_mention, 'data-thredded-bound-messageboard-pref' => 'user_preferences_form[messageboard_follow_topics_on_mention]' %>
|
||||
<%= t 'thredded.preferences.form.follow_topics_on_mention.label' %>
|
||||
<p class="thredded--form-list--hint">
|
||||
<%= t 'thredded.preferences.form.follow_topics_on_mention.hint' %>
|
||||
</p>
|
||||
<% end %>
|
||||
</li>
|
||||
<% if Thredded.notifiers.present? %>
|
||||
<li class="thredded--user-preferences--notifications-for-followed-topics">
|
||||
<label><%= t 'thredded.preferences.form.notifications_for_followed_topics.label' %></label>
|
||||
<%= f.fields_for :notifications_for_followed_topics, preferences.notifications_for_followed_topics do |fn| %>
|
||||
<%= fn.label :enabled do %>
|
||||
<%= fn.hidden_field :notifier_key %>
|
||||
<%= fn.check_box :enabled,
|
||||
'data-thredded-bound-messageboard-pref' =>
|
||||
"user_preferences_form[messageboard_notifications_for_followed_topics_attributes][#{fn.index}][enabled]" %>
|
||||
<%= fn.object.notifier_human_name %>
|
||||
<%- end %>
|
||||
<%- end %>
|
||||
</li>
|
||||
<li class="thredded--user-preferences--notifications-for-private-topics">
|
||||
<label><%= t 'thredded.preferences.form.notifications_for_private_topics.label' %></label>
|
||||
<%= f.fields_for :notifications_for_private_topics, preferences.notifications_for_private_topics do |fn| %>
|
||||
<%= fn.label :enabled do %>
|
||||
<%= fn.hidden_field :notifier_key %>
|
||||
<%= fn.check_box :enabled %>
|
||||
<%= fn.object.notifier_human_name %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</div>
|
||||
<% if preferences.messageboard %>
|
||||
<h2 class="thredded--preferences--title">
|
||||
<%= t 'thredded.preferences.messageboard_preferences_title_html', messageboard: messageboard.name %>
|
||||
</h2>
|
||||
<div class="card-panel">
|
||||
<ul class="thredded--form-list thredded--user-preferences-messageboard" data-thredded-user-preferences-form-messageboard-fields>
|
||||
<li class="thredded--user-preferences--auto-follow-topics">
|
||||
<%= f.label :messageboard_auto_follow_topics do %>
|
||||
<%= f.check_box :messageboard_auto_follow_topics %>
|
||||
<%= t 'thredded.preferences.form.messageboard_auto_follow_topics.label' %>
|
||||
<p class="thredded--form-list--hint">
|
||||
<%= t 'thredded.preferences.form.messageboard_auto_follow_topics.hint' %>
|
||||
</p>
|
||||
<% end %>
|
||||
</li>
|
||||
<li class="thredded--user-preferences--follow-topics-on-mention">
|
||||
<%= f.label :messageboard_follow_topics_on_mention do %>
|
||||
<%= f.check_box :messageboard_follow_topics_on_mention %>
|
||||
<%= t 'thredded.preferences.form.messageboard_follow_topics_on_mention.label' %>
|
||||
<p class="thredded--form-list--hint">
|
||||
<%= t 'thredded.preferences.form.messageboard_follow_topics_on_mention.hint' %>
|
||||
</p>
|
||||
<% end %>
|
||||
</li>
|
||||
<% if Thredded.notifiers.present? %>
|
||||
<li class="thredded--user-preferences--notifications-for-followed-topics">
|
||||
<label><%= t 'thredded.preferences.form.messageboard_notifications_for_followed_topics.label' %></label>
|
||||
<%= f.fields_for :messageboard_notifications_for_followed_topics,
|
||||
preferences.messageboard_notifications_for_followed_topics do |fn| %>
|
||||
<%= fn.label :enabled do %>
|
||||
<%= fn.hidden_field :notifier_key %>
|
||||
<%= fn.hidden_field :messageboard_id %>
|
||||
<%= fn.check_box :enabled %>
|
||||
<%= fn.object.notifier_human_name %>
|
||||
<% end %>
|
||||
<%- end %>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</div>
|
||||
<% end %>
|
||||
<button type="submit" class="thredded--form--submit"
|
||||
data-disable-with="<%= t 'thredded.preferences.form.update_btn_submitting' %>">
|
||||
<%= t 'thredded.preferences.form.submit_btn' %>
|
||||
</button>
|
||||
<% end %>
|
||||
@ -1,9 +1,11 @@
|
||||
|
||||
<% if Thredded.show_topic_followers %>
|
||||
<div class="thredded--topic-followers">
|
||||
<% if topic.followers.present? %>
|
||||
<%= t('thredded.topics.followed_by')%>
|
||||
<%= pluralize topic.followers.count, 'user' %>
|
||||
<%# t('thredded.topics.followed_by') %>
|
||||
<span class="tooltipped" data-tooltip="<%= pluralize topic.followers.count, 'person' %> will be notified of any updates to this thread." data-position="right">
|
||||
<i class="material-icons tiny" style="position: relative; top: 2px;">people_alt</i>
|
||||
<%= pluralize topic.followers.count, 'follower' %>
|
||||
</span>
|
||||
<%# topic.followers.each do |user| %>
|
||||
<%#= user_link(user) %>
|
||||
<%# end %>
|
||||
|
||||
68
app/views/thredded/topics/_header.html.erb
Normal file
68
app/views/thredded/topics/_header.html.erb
Normal file
@ -0,0 +1,68 @@
|
||||
<header class="thredded--topic-header">
|
||||
<%= view_hooks.topic_page.title.render self, topic: topic do %>
|
||||
<h1 class="thredded--topic-header--title">
|
||||
<i class="material-icons left blue-text">forum</i>
|
||||
<%= topic.title %>
|
||||
<div>
|
||||
<small>
|
||||
<cite class="thredded--topic-header--started-by">
|
||||
<%= t 'thredded.topics.started_by_html',
|
||||
time_ago: time_ago(topic.created_at),
|
||||
user: user_link(topic.user) %>
|
||||
</cite>
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div class="thredded--post--dropdown">
|
||||
<i class="material-icons thredded--post--dropdown--toggle">tune</i>
|
||||
|
||||
<div class="thredded--post--dropdown--actions">
|
||||
<% if topic.can_update? %>
|
||||
<%= link_to t('thredded.topics.edit'), topic.edit_path,
|
||||
class: 'thredded--post--dropdown--actions--item',
|
||||
rel: 'nofollow' %>
|
||||
<% end %>
|
||||
<% if user_signed_in? && params.key?(:id) %>
|
||||
<%= link_to main_app.documentize_topic_path(params[:id]), class: 'thredded--post--dropdown--actions--item' do %>
|
||||
Export to document
|
||||
<% end %>
|
||||
<%= link_to main_app.plaintext_topic_path(params[:id]), class: 'thredded--post--dropdown--actions--item', rel: 'nofollow' do %>
|
||||
View as plaintext
|
||||
<% end %>
|
||||
<%= link_to main_app.irc_log_topic_path(params[:id]), class: 'thredded--post--dropdown--actions--item', rel: 'nofollow' do %>
|
||||
View as chat log
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% if topic.can_destroy? %>
|
||||
<div class="thredded--topic-delete--wrapper">
|
||||
<%= button_to t('thredded.topics.delete_topic'), topic.destroy_path, method: :delete,
|
||||
form_class: 'thredded--topic-delete-form',
|
||||
class: 'thredded--button thredded--post--dropdown--actions--item',
|
||||
'data-confirm' => t('thredded.topics.delete_confirm') %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</h1>
|
||||
<% end %>
|
||||
<% if thredded_current_user %>
|
||||
<% if topic.followed? %>
|
||||
<div class="thredded--topic-header--follow-info">
|
||||
<span class="thredded--topic-header--follow-info--reason">
|
||||
<%= topic_follow_reason_text topic.follow_reason %>
|
||||
</span>
|
||||
<%= button_to topic.unfollow_path, form: {class: 'thredded--topic-header--follow-info--unfollow'} do %>
|
||||
<%= t('thredded.topics.unfollow') %>
|
||||
<%= inline_svg_tag 'thredded/follow.svg', class: 'thredded--topic-header--follow-icon' %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="thredded--topic-header--follow-info">
|
||||
<%= button_to t('thredded.topics.follow'), topic.follow_path,
|
||||
form: {class: 'thredded--topic-header--follow-info--follow'} %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<%= render partial: 'thredded/topics/followers', locals: {topic: topic} %>
|
||||
</header>
|
||||
41
app/views/thredded/topics/show.html.erb
Normal file
41
app/views/thredded/topics/show.html.erb
Normal file
@ -0,0 +1,41 @@
|
||||
<% topic = @posts.topic %>
|
||||
<% content_for :thredded_page_title, topic.title %>
|
||||
<% content_for :thredded_page_id, 'thredded--topic-show' %>
|
||||
<% content_for :thredded_breadcrumbs, render('thredded/shared/breadcrumbs') %>
|
||||
<%= thredded_page do %>
|
||||
<%= content_tag :section,
|
||||
id: dom_id(topic),
|
||||
class: ['thredded--main-section', 'thredded--topic', *topic_css_classes(topic)] do %>
|
||||
<%= render 'thredded/topics/header', topic: topic %>
|
||||
<%= view_hooks.posts_common.pagination_top.render(self, posts: @posts) do %>
|
||||
<footer class="thredded--pagination-top"><%= paginate @posts %></footer>
|
||||
<% end %>
|
||||
<%= render_posts @posts,
|
||||
partial: 'thredded/posts/post',
|
||||
content_partial: 'thredded/posts/content',
|
||||
locals: { actions: { quote: true } } %>
|
||||
<%= view_hooks.posts_common.pagination_bottom.render(self, posts: @posts) do %>
|
||||
<footer class="thredded--pagination-bottom"><%= paginate @posts %></footer>
|
||||
<% end %>
|
||||
<% if topic.locked? %>
|
||||
<p class="thredded--topic--locked-notice"><%= t 'thredded.topics.locked.message'%></p>
|
||||
<% end %>
|
||||
<% if policy(@new_post.post).create? %>
|
||||
<div class="thredded--post-form--wrapper">
|
||||
<h3 class="thredded--post-form--title"><%= t('thredded.posts.form.title_label') %></h3>
|
||||
<%= render 'thredded/posts/form',
|
||||
post: @new_post,
|
||||
button_text: t('thredded.posts.form.create_btn'),
|
||||
button_submitting_text: t('thredded.posts.form.create_btn_submitting') %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if false && topic.can_destroy? %>
|
||||
<div class="thredded--topic-delete--wrapper">
|
||||
<%= button_to t('thredded.topics.delete_topic'), topic.destroy_path, method: :delete,
|
||||
form_class: 'thredded--topic-delete-form',
|
||||
class: 'thredded--button',
|
||||
'data-confirm' => t('thredded.topics.delete_confirm') %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
@ -1,16 +1,28 @@
|
||||
<%
|
||||
badge_style = 'padding: 3px 5px; font-size: 70%; font-weight: normal;'
|
||||
badge_text = user.forums_badge_text
|
||||
|
||||
if user.favorite_page_type?
|
||||
favorite_link = link_to main_app.send("#{user.favorite_page_type.downcase.pluralize}_user_path", { id: user.id }) do
|
||||
content_tag(:i, class: "material-icons tiny #{content_class_from_name(user.favorite_page_type).try(:text_color)}", style: 'position: relative; top: 4px') do
|
||||
"#{content_class_from_name(user.favorite_page_type).try(:icon)}"
|
||||
end
|
||||
end
|
||||
|
||||
badge_text = favorite_link + badge_text
|
||||
end
|
||||
%>
|
||||
|
||||
<% if user.respond_to?(:selected_billing_plan_id) %>
|
||||
<% if user.id == 5 %>
|
||||
<span style="<%= badge_style %>; background: #2196F3; color: white"><%= badge_text.presence || 'Admin' %></span>
|
||||
<span style="<%= badge_style %>; background: #2196F3; color: white"><%= badge_text.presence + 'Admin' %></span>
|
||||
<% elsif user.selected_billing_plan_id == 2 %>
|
||||
<span style="<%= badge_style %>; background: #dedeff"><%= badge_text.presence || 'Beta Tester' %></span>
|
||||
<% elsif user.selected_billing_plan_id == 3 %>
|
||||
<span style="<%= badge_style %>; background: #ffdede"><%= badge_text.presence || 'Early Adopter' %></span>
|
||||
<% elsif user.on_premium_plan? %>
|
||||
<span style="<%= badge_style %>; background: #ffffaa"><%= badge_text.presence || 'Premium Supporter' %></span>
|
||||
<% else %>
|
||||
<%= favorite_link %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
@ -2,9 +2,13 @@
|
||||
<% if !user.thredded_anonymous? %>
|
||||
<% user_path = user_path(user) %>
|
||||
<% if user_path.blank? %>
|
||||
<span class='thredded--username'><%= user.thredded_display_name %></span>
|
||||
<span class='thredded--username'>
|
||||
<%= user.thredded_display_name %>
|
||||
</span>
|
||||
<% else %>
|
||||
<a href="<%= user_path %>"><%= user.thredded_display_name %></a>
|
||||
<a href="<%= user_path %>">
|
||||
<%= user.thredded_display_name %>
|
||||
</a>
|
||||
<%= render partial: 'thredded/users/badge', locals: { user: user } %>
|
||||
<% end %>
|
||||
<% else %>
|
||||
|
||||
@ -311,16 +311,16 @@
|
||||
<div class="hoverable clearfix card" style="border-top: 5px solid <%= Timeline.hex_color %>">
|
||||
<div class="card-content white-text">
|
||||
<span class="card-title">
|
||||
<div class="input-field">
|
||||
<div class="input-field ref-title">
|
||||
<%= f.text_field :title, class: 'materialize-textarea js-trigger-autosave-on-change' %>
|
||||
<%= f.label :title, 'Event Title' %>
|
||||
<%= f.label :title, 'Event Title', for: "timeline-event-title-[[tid]]" %>
|
||||
</div>
|
||||
</span>
|
||||
<div class="input-field">
|
||||
<div class="input-field ref-description">
|
||||
<%= f.text_area :description, class: 'materialize-textarea js-trigger-autosave-on-change' %>
|
||||
<%= f.label :description %>
|
||||
</div>
|
||||
<div class="input-field">
|
||||
<div class="input-field ref-notes">
|
||||
<%= f.text_area :notes, class: 'materialize-textarea js-trigger-autosave-on-change' %>
|
||||
<%= f.label :notes %>
|
||||
</div>
|
||||
|
||||
@ -13,8 +13,15 @@
|
||||
</span>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<%= render partial: 'content/list/list', locals: { content_list: @content_list, content_type: @content_type } %>
|
||||
<%= render partial: 'content/list/cards', locals: {
|
||||
content_list: @content_list,
|
||||
content_type: @content_type,
|
||||
how_add_another_form: false,
|
||||
show_template_editor_form: false,
|
||||
show_new_button: false
|
||||
} %>
|
||||
</div>
|
||||
<div class="clearfix" style="padding-bottom: 2em"></div>
|
||||
<div class="card-action">
|
||||
<%= link_to "Back to #{@user.name}'s profile'", @user %>
|
||||
</div>
|
||||
|
||||
@ -174,6 +174,9 @@ end
|
||||
Rails.application.config.to_prepare do
|
||||
Thredded::TopicPolicy.prepend AllowUsersToDeleteOwnTopics
|
||||
Thredded::PrivateTopicPolicy.prepend AllowAdminModeration
|
||||
|
||||
Onebox::Engine::AllowlistedGenericOnebox.allowed_domains << "notebook.ai"
|
||||
Onebox::Engine::AllowlistedGenericOnebox.allowed_domains << "papercut-server.herokuapp.com"
|
||||
end
|
||||
|
||||
Rails.application.config.to_prepare do
|
||||
|
||||
@ -1213,11 +1213,13 @@ en:
|
||||
messageboard_preferences_nav_title: Per-category Settings
|
||||
messageboard:
|
||||
create: Create a New Discussion Board
|
||||
topics_and_posts_counts: "%{topics_count} threads / %{posts_unread} unread"
|
||||
topics_and_posts_counts: "%{posts_unread} unread of %{topics_count} threads"
|
||||
last_updated_by_html: "<em>%{user} commented %{time_ago}</em>"
|
||||
index:
|
||||
page_title: Discussions
|
||||
topics:
|
||||
delete_topic: Delete full discussion
|
||||
edit: Edit topic
|
||||
follow: Follow this discussion
|
||||
form:
|
||||
create_btn: Create New Discussion
|
||||
|
||||
@ -408,8 +408,11 @@ Rails.application.routes.draw do
|
||||
# get '/forum', to: 'emergency#temporarily_disabled'
|
||||
# get '/forum/:wildcard', to: 'emergency#temporarily_disabled'
|
||||
# get '/forum/:wildcard/:another', to: 'emergency#temporarily_disabled'
|
||||
mount Thredded::Engine, at: '/forum', as: :thredded
|
||||
get '/topic/:slug', to: 'thredded_proxy#topic', as: :topic
|
||||
mount Thredded::Engine, at: '/forum', as: :thredded
|
||||
get '/topic/:slug', to: 'thredded_proxy#topic', as: :topic
|
||||
get '/plaintext/:slug', to: 'thredded_proxy#view_as_plaintext', as: :plaintext_topic
|
||||
get '/irc_log/:slug', to: 'thredded_proxy#view_as_irc_log', as: :irc_log_topic
|
||||
get '/save/:slug', to: 'thredded_proxy#save_to_document', as: :documentize_topic
|
||||
|
||||
mount StripeEvent::Engine, at: '/webhooks/stripe'
|
||||
|
||||
|
||||
@ -55,7 +55,7 @@ namespace :backfill do
|
||||
|
||||
desc "Backfill cached word counts on all documents"
|
||||
task document_word_count_caches: :environment do
|
||||
Document.where(cached_word_count: nil).where.not(body: [nil, ""]).find_each(batch_size: 500) do |document|
|
||||
Document.with_deleted.where(cached_word_count: nil).where.not(body: [nil, ""]).find_each(batch_size: 500) do |document|
|
||||
document.update_column(:cached_word_count, document.computed_word_count)
|
||||
puts document.id
|
||||
end
|
||||
|
||||
Loading…
Reference in New Issue
Block a user