Merge branch 'master' into image-opts

This commit is contained in:
Andrew Brown 2021-12-28 09:21:46 -08:00 committed by GitHub
commit 785d7da289
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
50 changed files with 564 additions and 211 deletions

View File

@ -74,8 +74,11 @@ gem 'barnes'
# Forum
gem 'thredded', git: 'https://github.com/indentlabs/thredded.git', branch: 'feature/report-posts'
# gem 'thredded', git: 'https://github.com/thredded/thredded', branch: 'master'
# gem 'thredded', git: 'https://github.com/sudara/thredded', branch: 'master'
gem 'rails-ujs'
gem 'language_filter'
gem 'discordrb'
# Smarts
gem 'word_count_analyzer'

View File

@ -13,13 +13,13 @@ GIT
GIT
remote: https://github.com/indentlabs/thredded.git
revision: b9a3976e310bb4eeb3f9a4b30d6c176d15a28ce1
revision: 6ed40273e4696fc4f32013e4f2a101ff8658b960
branch: feature/report-posts
specs:
thredded (0.16.16)
active_record_union (>= 1.3.0)
autoprefixer-rails
db_text_search (~> 0.3.0)
db_text_search (~> 0.3.2)
friendly_id
html-pipeline
htmlentities
@ -28,10 +28,10 @@ GIT
kramdown (>= 2.0.0)
kramdown-parser-gfm
nokogiri
onebox (~> 1.8, >= 1.8.99)
onebox (>= 1.8.99)
pundit (>= 1.1.0)
rails (>= 4.2.10, != 6.0.0.rc2)
rb-gravatar
rails_gravatar
rinku
sanitize
sassc-rails (>= 2.0.0)
@ -41,38 +41,40 @@ GIT
GEM
remote: https://rubygems.org/
specs:
actioncable (6.0.3.4)
actionpack (= 6.0.3.4)
actioncable (6.1.4.1)
actionpack (= 6.1.4.1)
activesupport (= 6.1.4.1)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
actionmailbox (6.0.3.4)
actionpack (= 6.0.3.4)
activejob (= 6.0.3.4)
activerecord (= 6.0.3.4)
activestorage (= 6.0.3.4)
activesupport (= 6.0.3.4)
actionmailbox (6.1.4.1)
actionpack (= 6.1.4.1)
activejob (= 6.1.4.1)
activerecord (= 6.1.4.1)
activestorage (= 6.1.4.1)
activesupport (= 6.1.4.1)
mail (>= 2.7.1)
actionmailer (6.0.3.4)
actionpack (= 6.0.3.4)
actionview (= 6.0.3.4)
activejob (= 6.0.3.4)
actionmailer (6.1.4.1)
actionpack (= 6.1.4.1)
actionview (= 6.1.4.1)
activejob (= 6.1.4.1)
activesupport (= 6.1.4.1)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0)
actionpack (6.0.3.4)
actionview (= 6.0.3.4)
activesupport (= 6.0.3.4)
rack (~> 2.0, >= 2.0.8)
actionpack (6.1.4.1)
actionview (= 6.1.4.1)
activesupport (= 6.1.4.1)
rack (~> 2.0, >= 2.0.9)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.2.0)
actiontext (6.0.3.4)
actionpack (= 6.0.3.4)
activerecord (= 6.0.3.4)
activestorage (= 6.0.3.4)
activesupport (= 6.0.3.4)
actiontext (6.1.4.1)
actionpack (= 6.1.4.1)
activerecord (= 6.1.4.1)
activestorage (= 6.1.4.1)
activesupport (= 6.1.4.1)
nokogiri (>= 1.8.5)
actionview (6.0.3.4)
activesupport (= 6.0.3.4)
actionview (6.1.4.1)
activesupport (= 6.1.4.1)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
@ -81,39 +83,41 @@ GEM
activerecord (>= 4.0)
active_storage_validations (0.9.5)
rails (>= 5.2.0)
activejob (6.0.3.4)
activesupport (= 6.0.3.4)
activejob (6.1.4.1)
activesupport (= 6.1.4.1)
globalid (>= 0.3.6)
activemodel (6.0.3.4)
activesupport (= 6.0.3.4)
activemodel (6.1.4.1)
activesupport (= 6.1.4.1)
activemodel-serializers-xml (1.0.2)
activemodel (> 5.x)
activesupport (> 5.x)
builder (~> 3.1)
activerecord (6.0.3.4)
activemodel (= 6.0.3.4)
activesupport (= 6.0.3.4)
activestorage (6.0.3.4)
actionpack (= 6.0.3.4)
activejob (= 6.0.3.4)
activerecord (= 6.0.3.4)
marcel (~> 0.3.1)
activesupport (6.0.3.4)
activerecord (6.1.4.1)
activemodel (= 6.1.4.1)
activesupport (= 6.1.4.1)
activestorage (6.1.4.1)
actionpack (= 6.1.4.1)
activejob (= 6.1.4.1)
activerecord (= 6.1.4.1)
activesupport (= 6.1.4.1)
marcel (~> 1.0.0)
mini_mime (>= 1.1.0)
activesupport (6.1.4.1)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2)
minitest (~> 5.1)
tzinfo (~> 1.1)
zeitwerk (~> 2.2, >= 2.2.2)
i18n (>= 1.6, < 2)
minitest (>= 5.1)
tzinfo (~> 2.0)
zeitwerk (~> 2.3)
acts_as_list (1.0.4)
activerecord (>= 4.2)
addressable (2.7.0)
addressable (2.8.0)
public_suffix (>= 2.0.2, < 5.0)
authority (3.3.0)
activesupport (>= 3.0.0)
autoprefixer-rails (9.8.4)
execjs
autoprefixer-rails (10.3.3.0)
execjs (~> 2)
aws-eventstream (1.2.0)
aws-partitions (1.503.0)
aws-partitions (1.543.0)
aws-sdk (3.1.0)
aws-sdk-resources (~> 3)
aws-sdk-accessanalyzer (1.23.0)
@ -311,9 +315,9 @@ GEM
aws-sdk-connectparticipant (1.14.0)
aws-sdk-core (~> 3, >= 3.120.0)
aws-sigv4 (~> 1.1)
aws-sdk-core (3.121.0)
aws-sdk-core (3.125.0)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.239.0)
aws-partitions (~> 1, >= 1.525.0)
aws-sigv4 (~> 1.1)
jmespath (~> 1.0)
aws-sdk-costandusagereportservice (1.34.0)
@ -574,8 +578,8 @@ GEM
aws-sdk-kinesisvideosignalingchannels (1.13.0)
aws-sdk-core (~> 3, >= 3.120.0)
aws-sigv4 (~> 1.1)
aws-sdk-kms (1.48.0)
aws-sdk-core (~> 3, >= 3.120.0)
aws-sdk-kms (1.53.0)
aws-sdk-core (~> 3, >= 3.125.0)
aws-sigv4 (~> 1.1)
aws-sdk-lakeformation (1.17.0)
aws-sdk-core (~> 3, >= 3.120.0)
@ -1082,8 +1086,8 @@ GEM
aws-sdk-route53resolver (1.30.0)
aws-sdk-core (~> 3, >= 3.120.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.103.0)
aws-sdk-core (~> 3, >= 3.120.0)
aws-sdk-s3 (1.110.0)
aws-sdk-core (~> 3, >= 3.125.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.4)
aws-sdk-s3control (1.40.0)
@ -1261,7 +1265,7 @@ GEM
bundler (>= 1.2.0, < 3)
thor (~> 1.0)
chartkick (4.0.5)
childprocess (3.0.0)
childprocess (4.1.0)
climate_control (0.2.0)
cocoon (1.2.15)
codeclimate-test-reporter (1.0.9)
@ -1277,34 +1281,43 @@ GEM
concurrent-ruby (1.1.9)
connection_pool (2.2.5)
crass (1.0.6)
csv (3.2.0)
csv (3.2.2)
d3-rails (5.9.2)
railties (>= 3.1)
database_cleaner (2.0.1)
database_cleaner-active_record (~> 2.0.0)
database_cleaner-active_record (2.0.0)
database_cleaner-active_record (2.0.1)
activerecord (>= 5.a)
database_cleaner-core (~> 2.0.0)
database_cleaner-core (2.0.1)
dateslices (0.0.4)
rails (> 4)
db_text_search (0.3.1)
db_text_search (0.3.2)
activerecord (>= 4.1.15, < 7.0)
debug_inspector (1.0.0)
debug_inspector (1.1.0)
devise (4.8.0)
bcrypt (~> 3.0)
orm_adapter (~> 0.1)
railties (>= 4.1.0)
responders
warden (~> 1.2.3)
discordrb (3.4.0)
discordrb-webhooks (~> 3.3.0)
ffi (>= 1.9.24)
opus-ruby
rest-client (>= 2.0.0)
websocket-client-simple (>= 0.3.0)
discordrb-webhooks (3.3.0)
rest-client (>= 2.1.0.rc1)
docile (1.1.5)
domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0)
engtagger (0.2.1)
erubi (1.10.0)
event_emitter (0.2.6)
eventmachine (1.2.7)
execjs (2.7.0)
faraday (0.17.3)
execjs (2.8.1)
faraday (0.17.4)
multipart-post (>= 1.2, < 3)
faraday_middleware (0.14.0)
faraday (>= 0.7.4, < 1.0)
@ -1319,7 +1332,7 @@ GEM
flamegraph (0.9.5)
font-awesome-rails (4.7.0.7)
railties (>= 3.2, < 7)
friendly_id (5.3.0)
friendly_id (5.4.2)
activerecord (>= 4.0.0)
globalid (0.5.2)
activesupport (>= 5.0)
@ -1335,6 +1348,7 @@ GEM
http-cookie (~> 1.0)
http-form_data (~> 2.2)
http-parser (~> 1.2.0)
http-accept (1.7.0)
http-cookie (1.0.4)
domain_name (~> 0.5)
http-form_data (2.3.0)
@ -1356,7 +1370,7 @@ GEM
image_processing (1.12.1)
mini_magick (>= 4.9.5, < 5)
ruby-vips (>= 2.0.17, < 3)
inline_svg (1.7.1)
inline_svg (1.7.2)
activesupport (>= 3.0)
nokogiri (>= 1.6)
jmespath (1.4.0)
@ -1366,7 +1380,7 @@ GEM
thor (>= 0.14, < 2.0)
jquery-ui-rails (6.0.1)
railties (>= 3.2.16)
json (2.3.0)
json (2.5.1)
jwt (2.2.3)
kaminari (1.2.1)
activesupport (>= 4.1.0)
@ -1394,8 +1408,7 @@ GEM
nokogiri (>= 1.5.9)
mail (2.7.1)
mini_mime (>= 0.1.1)
marcel (0.3.3)
mimemagic (~> 0.3.2)
marcel (1.0.2)
material_icons (2.2.1)
railties (>= 3.2)
medium-editor-rails (2.3.1)
@ -1406,12 +1419,12 @@ GEM
method_source (1.0.0)
mime-types (3.3.1)
mime-types-data (~> 3.2015)
mime-types-data (3.2020.0512)
mime-types-data (3.2021.0704)
mimemagic (0.3.10)
nokogiri (~> 1)
rake
mini_magick (4.11.0)
mini_mime (1.1.0)
mini_mime (1.1.2)
mini_portile2 (2.6.1)
mini_racer (0.4.0)
libv8-node (~> 15.14.0.0)
@ -1420,20 +1433,21 @@ GEM
multipart-post (2.1.1)
mustache (1.1.1)
nested_form (0.3.2)
netrc (0.11.0)
newrelic_rpm (8.0.0)
nio4r (2.5.8)
nokogiri (1.12.5)
mini_portile2 (~> 2.6.1)
racc (~> 1.4)
nokogumbo (2.0.2)
nokogiri (~> 1.8, >= 1.8.4)
onebox (1.9.29)
addressable (~> 2.7.0)
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)
paperclip (6.1.0)
activemodel (>= 4.2.0)
@ -1455,13 +1469,13 @@ GEM
coderay (~> 1.1)
method_source (~> 1.0)
public_suffix (4.0.6)
puma (5.5.0)
puma (5.5.1)
nio4r (~> 2.0)
puma-heroku (2.0.0)
puma (>= 5.0, < 6.0)
pundit (2.1.0)
pundit (2.1.1)
activesupport (>= 3.0.0)
racc (1.5.2)
racc (1.6.0)
rack (2.2.3)
rack-mini-profiler (2.3.3)
rack (>= 1.2.0)
@ -1472,20 +1486,20 @@ GEM
rack
rack-test (1.1.0)
rack (>= 1.0, < 3)
rails (6.0.3.4)
actioncable (= 6.0.3.4)
actionmailbox (= 6.0.3.4)
actionmailer (= 6.0.3.4)
actionpack (= 6.0.3.4)
actiontext (= 6.0.3.4)
actionview (= 6.0.3.4)
activejob (= 6.0.3.4)
activemodel (= 6.0.3.4)
activerecord (= 6.0.3.4)
activestorage (= 6.0.3.4)
activesupport (= 6.0.3.4)
bundler (>= 1.3.0)
railties (= 6.0.3.4)
rails (6.1.4.1)
actioncable (= 6.1.4.1)
actionmailbox (= 6.1.4.1)
actionmailer (= 6.1.4.1)
actionpack (= 6.1.4.1)
actiontext (= 6.1.4.1)
actionview (= 6.1.4.1)
activejob (= 6.1.4.1)
activemodel (= 6.1.4.1)
activerecord (= 6.1.4.1)
activestorage (= 6.1.4.1)
activesupport (= 6.1.4.1)
bundler (>= 1.15.0)
railties (= 6.1.4.1)
sprockets-rails (>= 2.0.0)
rails-dom-testing (2.0.3)
activesupport (>= 4.2.0)
@ -1508,15 +1522,16 @@ GEM
rails (>= 5.0, < 7)
remotipart (~> 1.3)
sassc-rails (>= 1.3, < 3)
railties (6.0.3.4)
actionpack (= 6.0.3.4)
activesupport (= 6.0.3.4)
rails_gravatar (1.0.4)
actionview
railties (6.1.4.1)
actionpack (= 6.1.4.1)
activesupport (= 6.1.4.1)
method_source
rake (>= 0.8.7)
thor (>= 0.20.3, < 2.0)
rake (>= 0.13)
thor (~> 1.0)
rake (13.0.6)
rb-fsevent (0.11.0)
rb-gravatar (1.0.5)
rb-inotify (0.10.1)
ffi (~> 1.0)
react-rails (2.6.1)
@ -1526,21 +1541,25 @@ GEM
railties (>= 3.2)
tilt
redcarpet (3.5.1)
redis (4.4.0)
redis (4.5.1)
remotipart (1.4.4)
responders (3.0.1)
actionpack (>= 5.0)
railties (>= 5.0)
rest-client (2.1.0)
http-accept (>= 1.7.0, < 2.0)
http-cookie (>= 1.0.2, < 2.0)
mime-types (>= 1.16, < 4.0)
netrc (~> 0.8)
rexml (3.2.5)
rinku (2.0.6)
rmagick (4.2.2)
rmagick (4.2.4)
ruby-vips (2.0.17)
ffi (~> 1.9)
rubyzip (2.0.0)
sanitize (5.2.1)
rubyzip (2.3.2)
sanitize (6.0.0)
crass (~> 1.0.2)
nokogiri (>= 1.8.0)
nokogumbo (~> 2.0)
nokogiri (>= 1.12.0)
sass-rails (6.0.0)
sassc-rails (~> 2.1, >= 2.1.1)
sassc (2.4.0)
@ -1551,8 +1570,9 @@ GEM
sprockets (> 3.0)
sprockets-rails
tilt
selenium-webdriver (3.142.7)
childprocess (>= 0.5, < 4.0)
selenium-webdriver (4.1.0)
childprocess (>= 0.5, < 5.0)
rexml (~> 3.2, >= 3.2.5)
rubyzip (>= 1.2.2)
semantic_range (3.0.0)
sidekiq (6.2.2)
@ -1579,7 +1599,7 @@ GEM
sqlite3 (1.4.2)
stackprof (0.2.17)
statsd-ruby (1.5.0)
stripe (5.38.0)
stripe (5.42.0)
stripe_event (2.3.1)
activesupport (>= 3.1)
stripe (>= 2.8, < 6)
@ -1587,15 +1607,14 @@ GEM
terrapin (0.6.0)
climate_control (>= 0.0.3, < 1.0)
text-hyphen (1.4.1)
textstat (0.1.6)
textstat (0.1.7)
text-hyphen (~> 1.4, >= 1.4.1)
thor (1.1.0)
thread_safe (0.3.6)
tilt (2.0.10)
timeago_js (3.0.2.2)
tribute (3.6.0.0)
tzinfo (1.2.9)
thread_safe (~> 0.1)
tzinfo (2.0.4)
concurrent-ruby (~> 1.0)
uglifier (4.2.0)
execjs (>= 0.3.0, < 3)
unf (0.1.4)
@ -1614,12 +1633,16 @@ GEM
rack-proxy (>= 0.6.1)
railties (>= 5.2)
semantic_range (>= 2.3.0)
websocket (1.2.9)
websocket-client-simple (0.3.0)
event_emitter
websocket
websocket-driver (0.7.5)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)
word_count_analyzer (1.0.1)
engtagger
zeitwerk (2.4.2)
zeitwerk (2.5.1)
PLATFORMS
ruby
@ -1644,6 +1667,7 @@ DEPENDENCIES
database_cleaner
dateslices
devise
discordrb
engtagger
filesize
flamegraph

View File

@ -59,6 +59,13 @@ class AdminController < ApplicationController
def churn
end
def notifications
@clicked_notifications = Notification.where.not(viewed_at: nil)
@notifications = Notification.all.order(:reference_code)
@codes = Notification.distinct.order('reference_code').pluck(:reference_code)
end
def hate
@posts = Thredded::PrivatePost.order('id DESC').limit(params.fetch(:limit, 500)).includes(:postable)
@list = params[:matchlist]

View File

@ -94,6 +94,13 @@ class ApplicationController < ActionController::Base
universe_id: @universe_scope.try(:id)
)
# Due to the way we loop over @current_user_content (page, list) later, we want to make sure that we
# at least have an empty list for all activated content types -- otherwise we may skip over the contributor
# content injection for a type that a user doesn't have ANY pages for.
@activated_content_types.each do |content_type|
@current_user_content[content_type] ||= []
end
# Likewise, we should also always cache Timelines & Documents
if @universe_scope
@current_user_content['Timeline'] = current_user.timelines.where(universe_id: @universe_scope.try(:id)).to_a
@ -196,7 +203,7 @@ class ApplicationController < ActionController::Base
# If we're scoped to a universe, also scope contributor content pulled to that
# universe. If we're not, leave it as all contributor content.
if @universe_scope && pages_to_add.klass.respond_to?(:universe)
if @universe_scope && pages_to_add.klass.name != 'Universe'
pages_to_add = pages_to_add.where(universe: @universe_scope)
end

View File

@ -55,6 +55,11 @@ class ContentController < ApplicationController
@questioned_content = @content.sample
@attribute_field_to_question = SerendipitousService.question_for(@questioned_content)
@random_image_including_private_pool_cache = ImageUpload.where(
content_type: @content_type_class.name,
content_id: @content.pluck(:id)
).group_by { |image| [image.content_type, image.content_id] }
# Uh, do we ever actually make JSON requests to logged-in user pages?
respond_to do |format|
format.html { render 'content/index' }
@ -147,6 +152,10 @@ class ContentController < ApplicationController
return redirect_to @content, notice: t(:no_do_permission)
end
@random_image_including_private_pool_cache = ImageUpload.where(
user_id: current_user.id,
).group_by { |image| [image.content_type, image.content_id] }
respond_to do |format|
format.html { render 'content/edit', locals: { content: @content } }
format.json { render json: @content }

View File

@ -13,7 +13,8 @@ class ContributorsController < ApplicationController
icon: Universe.icon,
icon_color: Universe.color,
happened_at: DateTime.current,
passthrough_link: Rails.application.routes.url_helpers.universe_path(relevant_universe)
passthrough_link: Rails.application.routes.url_helpers.universe_path(relevant_universe),
reference_code: 'contributor-removed'
) if user.present?
# Create a notification letting the universe owner know
@ -22,7 +23,8 @@ class ContributorsController < ApplicationController
icon: Universe.icon,
icon_color: Universe.color,
happened_at: DateTime.current,
passthrough_link: Rails.application.routes.url_helpers.universe_path(relevant_universe)
passthrough_link: Rails.application.routes.url_helpers.universe_path(relevant_universe),
reference_code: 'contributor-left'
) if user.present?
#todo send "you've been removed as a contributor" email

View File

@ -18,12 +18,18 @@ class DataController < ApplicationController
@created_content = {}
@total_created_non_universe_content = 0
@words_written = 0
Rails.application.config.content_types[:all].each do |klass|
@created_content[klass.name] = klass.where(user_id: current_user.id)
.where('created_at > ?', comparable_year.beginning_of_year)
.where('created_at < ?', comparable_year.end_of_year)
.order('created_at ASC')
@words_written += WordCountUpdate.where(
entity_type: klass.name,
entity_id: @created_content[klass.name].map(&:id)
).sum(:word_count)
if klass.name != 'Universe'
@total_created_non_universe_content += @created_content[klass.name].count
end
@ -34,6 +40,8 @@ class DataController < ApplicationController
.where('created_at < ?', comparable_year.end_of_year)
.order('created_at ASC')
@words_written += @created_content['Document'].sum(:cached_word_count)
earliest_page_date = DateTime.current
@earliest_page = nil
@created_content.each do |content_type, list|
@ -80,7 +88,6 @@ class DataController < ApplicationController
@published_collections = PageCollection.where(id: @created_content['PageCollectionSubmission'].pluck(:page_collection_id) - @created_content['PageCollection'].pluck(:id))
@publish_rate = @created_content['PageCollectionSubmission'].select { |s| s.accepted_at.present? && !@created_content['PageCollection'].pluck(:id).include?(s.page_collection_id) }.count.to_f / @created_content['PageCollectionSubmission'].count
end
def archive

View File

@ -68,7 +68,8 @@ class PageCollectionSubmissionsController < ApplicationController
icon: PageCollection.icon,
icon_color: PageCollection.color,
happened_at: DateTime.current,
passthrough_link: Rails.application.routes.url_helpers.page_collection_path(@page_collection_submission.page_collection)
passthrough_link: Rails.application.routes.url_helpers.page_collection_path(@page_collection_submission.page_collection),
reference_code: 'approved-in-collection'
)
redirect_to(page_collection_pending_submissions_path(@page_collection_submission.page_collection), notice: "Submission approved!")

View File

@ -88,7 +88,8 @@ class RegistrationsController < Devise::RegistrationsController
icon: Universe.icon,
icon_color: Universe.color,
happened_at: DateTime.current,
passthrough_link: Rails.application.routes.url_helpers.universe_path(contributorship.universe)
passthrough_link: Rails.application.routes.url_helpers.universe_path(contributorship.universe),
reference_code: 'contributor-added'
)
end
end

View File

@ -224,7 +224,8 @@ class SubscriptionsController < ApplicationController
icon: 'star',
icon_color: 'text-darken-3 yellow',
happened_at: DateTime.current,
passthrough_link: Rails.application.routes.url_helpers.customization_content_types_path
passthrough_link: Rails.application.routes.url_helpers.customization_content_types_path,
reference_code: 'premium-activation'
)
redirect_back(fallback_location: subscription_path, notice: "Promo code successfully activated!")

View File

@ -34,7 +34,8 @@ class UserFollowingsController < ApplicationController
icon: User.icon,
icon_color: User.color,
happened_at: DateTime.current,
passthrough_link: Rails.application.routes.url_helpers.user_path(@user_following.user)
passthrough_link: Rails.application.routes.url_helpers.user_path(@user_following.user),
reference_code: 'followed-by-user'
)
redirect_to @user_following.followed_user, notice: "You're now following #{@user_following.followed_user.name.presence || 'this user'}"

View File

@ -0,0 +1,30 @@
class CacheAttributeWordCountJob < ApplicationJob
queue_as :cache
def perform(*args)
attribute_id = args.shift
attribute = Attribute.find_by(id: attribute_id)
return if attribute.nil?
return if attribute.value.nil? || attribute.value.blank?
word_count = WordCountAnalyzer::Counter.new(
ellipsis: 'no_special_treatment',
hyperlink: 'count_as_one',
contraction: 'count_as_one',
hyphenated_word: 'count_as_one',
date: 'no_special_treatment',
number: 'count',
numbered_list: 'ignore',
xhtml: 'remove',
forward_slash: 'count_as_multiple_except_dates',
backslash: 'count_as_one',
dotted_line: 'ignore',
dashed_line: 'ignore',
underscore: 'ignore',
stray_punctuation: 'ignore'
).count(attribute.value)
attribute.update!(word_count_cache: word_count)
end
end

View File

@ -0,0 +1,20 @@
class CacheSumAttributeWordCountJob < ApplicationJob
queue_as :cache
def perform(*args)
entity_type = args.shift
entity_id = args.shift
entity = entity_type.constantize.find_by(id: entity_id)
return if entity.nil?
sum_attribute_word_count = Attribute.where(entity_type: entity_type, entity_id: entity_id).sum(:word_count_cache)
update = entity.word_count_updates.find_or_initialize_by(
for_date: DateTime.current,
)
update.word_count = sum_attribute_word_count
update.user_id ||= entity.user_id
update.save!
end
end

View File

@ -20,7 +20,8 @@ class ContentPageShareNotificationJob < ApplicationJob
passthrough_link: Rails.application.routes.url_helpers.user_content_page_share_path(
id: comment.content_page_share.id,
user_id: comment.content_page_share.user_id
)
),
reference_code: 'comment-on-shared-page'
)
end
end

View File

@ -42,7 +42,8 @@ class DocumentAnalysisJob < ApplicationJob
icon: Document.icon,
icon_color: Document.color,
happened_at: DateTime.current,
passthrough_link: Rails.application.routes.url_helpers.analysis_document_url(analysis.document)
passthrough_link: Rails.application.routes.url_helpers.analysis_document_url(analysis.document),
reference_code: 'analysis-ready'
)
end
end

View File

@ -0,0 +1,26 @@
class NotifyDiscordOfThreadJob < ApplicationJob
require 'discordrb/webhooks'
queue_as :low_priority
def perform(*args)
thread_id = args.shift
thread = Thredded::Topic.find_by(id: thread_id)
raise "No thread found for new ID #{thread.id.inspect}" unless thread
webhook_url = ENV.fetch('DISCORD_FORUMS_WEBHOOK', '').freeze
client = Discordrb::Webhooks::Client.new(url: webhook_url)
client.execute do |builder|
builder.content = "\"#{thread.title}\" started by #{thread.user.display_name} in *#{thread.messageboard.name}*"
builder.add_embed do |embed|
embed.title = thread.title
embed.description = thread.first_post.content.truncate(140)
embed.timestamp = Time.now
embed.url = "https://www.notebook.ai/forum/#{thread.messageboard.slug}/#{thread.slug}"
embed.colour = 2201331
end
end
end
end

View File

@ -4,13 +4,21 @@ class SaveDocumentRevisionJob < ApplicationJob
def perform(*args)
document_id = args.shift
document = Document.find(document_id)
return unless document.present?
document = Document.find_by(id: document_id)
return unless document
# Update cached word count for the document regardless of how often this is called
new_word_count = document.computed_word_count
document.update(cached_word_count: new_word_count)
# Save a WordCountUpdate for this document for today
update = document.word_count_updates.find_or_initialize_by(
for_date: DateTime.current,
)
update.word_count = new_word_count
update.user_id ||= document.user_id
update.save!
# Make sure we're only storing revisions at least every 5 min
latest_revision = document.document_revisions.order('created_at DESC').limit(1).first
if latest_revision.present? && latest_revision.created_at > 5.minutes.ago

View File

@ -4,6 +4,6 @@ module HasPageTags
extend ActiveSupport::Concern
included do
has_many :page_tags, as: :page
has_many :page_tags, as: :page, dependent: :destroy
end
end

View File

@ -18,6 +18,11 @@ module IsContentPage
has_many :timeline_events, through: :timeline_event_entities
has_many :timelines, -> { distinct }, through: :timeline_events
has_many :word_count_updates, as: :entity, dependent: :destroy
def latest_word_count_cache
word_count_updates.order('for_date DESC').limit(1).first.try(:word_count) || 0
end
scope :unarchived, -> { where(archived_at: nil) }
def archive!
update!(archived_at: DateTime.now)

View File

@ -26,6 +26,12 @@ class Document < ApplicationRecord
attr_accessor :tagged_text
# Duplicated from is_content_page since we don't include that here yet
has_many :word_count_updates, as: :entity, dependent: :destroy
def latest_word_count_cache
word_count_updates.order('for_date DESC').limit(1).first.try(:word_count) || 0
end
KEYS_TO_TRIGGER_REVISION_ON_CHANGE = %w(title body synopsis notes_text)
def self.color

View File

@ -35,7 +35,8 @@ class PageCollectionSubmission < ApplicationRecord
icon: PageCollection.icon,
icon_color: PageCollection.color,
happened_at: DateTime.current,
passthrough_link: Rails.application.routes.url_helpers.page_collection_path(page_collection)
passthrough_link: Rails.application.routes.url_helpers.page_collection_path(page_collection),
reference_code: 'collection-submission-accepted'
)
end
@ -79,7 +80,8 @@ class PageCollectionSubmission < ApplicationRecord
icon: PageCollection.icon,
icon_color: PageCollection.color,
happened_at: DateTime.current,
passthrough_link: Rails.application.routes.url_helpers.page_collection_pending_submissions_path(page_collection)
passthrough_link: Rails.application.routes.url_helpers.page_collection_pending_submissions_path(page_collection),
reference_code: 'collection-has-submission'
)
end
end

View File

@ -19,6 +19,16 @@ class Attribute < ApplicationRecord
end
end
after_commit do
if saved_changes.key?('value')
# Cache the updated word count on this attribute
CacheAttributeWordCountJob.perform_later(self.id)
# Cache the updated word count on the page this attribute belongs to
CacheSumAttributeWordCountJob.perform_later(self.entity_type, self.entity_id)
end
end
after_save do
entity.touch
end

View File

@ -113,13 +113,16 @@ class ContentSerializer
def value_for(attribute_field, content)
case attribute_field.field_type
when 'link'
page_links = attribute_field.attribute_values.find_by(entity_type: content.class.name, entity_id: content.id)
page_links = self.attribute_values.detect do |value|
value.entity_type == content.class.name && value.entity_id == content.id && value.attribute_field_id == attribute_field.id
end
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?
# raise "wee"
self.raw_model.send(attribute_field.old_column_source).map { |page| "#{page.page_type}-#{page.id}" }
else
[]

View File

@ -122,6 +122,9 @@ class User < ApplicationRecord
def contributable_universe_ids
# TODO: email confirmation needs to happen for data safety / privacy (only verified emails)
@contributable_universe_ids ||= Contributor.where('email = ? OR user_id = ?', self.email, self.id).pluck(:universe_id)
@contributable_universe_ids += Contributor.where(universe_id: my_universe_ids).pluck(:universe_id)
@contributable_universe_ids.uniq
end
# TODO: rename this to #{content_type}_shared_with_me
@ -290,7 +293,7 @@ class User < ApplicationRecord
params.delete(:username)
end
result = update_attributes(params, *options)
result = update(params, *options)
clean_up_passwords
result
end

View File

@ -0,0 +1,4 @@
class WordCountUpdate < ApplicationRecord
belongs_to :user
belongs_to :entity, polymorphic: true
end

View File

@ -25,7 +25,8 @@ class ContributorService < Service
icon: Universe.icon,
icon_color: Universe.color,
happened_at: DateTime.current,
passthrough_link: Rails.application.routes.url_helpers.universe_path(universe)
passthrough_link: Rails.application.routes.url_helpers.universe_path(universe),
reference_code: 'invitation-to-contribute'
) if related_user.present?
end
end

View File

@ -46,7 +46,8 @@ class SubscriptionService < Service
icon: 'star',
icon_color: 'text-darken-3 yellow',
happened_at: DateTime.current,
passthrough_link: Rails.application.routes.url_helpers.customization_content_types_path
passthrough_link: Rails.application.routes.url_helpers.customization_content_types_path,
reference_code: 'premium-activation'
) if user.reload.on_premium_plan?
report_subscription_change_to_slack(user, plan_id)

View File

@ -0,0 +1,28 @@
<h1>
Notification open rates
</h1>
<div>
<%= number_with_delimiter @clicked_notifications.count %> clicked
/
<%= number_with_delimiter @notifications.count %> total
(<%= @clicked_notifications.count / @notifications.count.to_f * 100 %>%)
</div>
<% @codes.each do |code| %>
<div class="card">
<div class="card-content">
<div class="card-title">Reference code: <%= code.inspect %></div>
<%
clicked_notifs = Notification.where(reference_code: code).where.not(viewed_at: nil).count
total_notifs = Notification.where(reference_code: code).count
%>
<ul class="browser-default">
<li><%= number_with_delimiter clicked_notifs %> clicked</li>
<li><%= number_with_delimiter total_notifs %> total</li>
<li><%= clicked_notifs / total_notifs.to_f * 100 %>% clicked</li>
</ul>
</div>
</div>
<% end %>

View File

@ -18,7 +18,7 @@
prompt="Please select at least one page type for this field to link to"
class="autosave-closest-form-on-change"
>
<%=
<%=
options_for_select(
Rails.application.config.content_types[:all].map { |klass| [klass.name.pluralize, klass.name] },
field.migrated_from_legacy? \

View File

@ -8,7 +8,7 @@
end
%>
<% if content.persisted? && user_signed_in? && content.user == current_user %>
<% if content.persisted? && user_signed_in? && content.user_id == current_user.try(:id) %>
<i class="material-icons right yellow-text favorite-button tooltipped"
data-tooltip="<%= action %> this page"
data-content-id="<%= content.id %>"

View File

@ -30,8 +30,13 @@
)
%>
<% @linkables_raw.fetch(page_type.name, []).each do |linkable| %>
<%
linkable_image = @random_image_including_private_pool_cache.fetch([linkable.page_type, linkable.id], [])
.sample
.try(:src, :thumb) || asset_path("card-headers/#{linkable.page_type.downcase.pluralize}.jpg")
%>
<option value="<%= page_type %>-<%= linkable.id %>"
data-icon="<%= asset_path linkable.random_image_including_private(format: :thumb) %>"
data-icon="<%= asset_path linkable_image %>"
<%= 'selected' if field[:value].include?("#{page_type}-#{linkable.id}") %>
>
<%= linkable.name %>

View File

@ -60,7 +60,7 @@
},
data: [
<% page.page_tags.pluck(:tag).each do |tag| %>
{tag: '<%= tag %>'},
{tag: "<%= tag.gsub('"', '\"').html_safe %>"},
<% end %>
],
onChipAdd: update_hidden_page_tag_value,

View File

@ -1,10 +1,15 @@
<div class="js-content-cards-list">
<% content_list.each.with_index do |content, i| %>
<%
content_image = @random_image_including_private_pool_cache.fetch([content.page_type, content.id], [])
.sample
.try(:src, :medium) || asset_path("card-headers/#{content.page_type.downcase.pluralize}.jpg")
%>
<div class="col s12 m6 l4 js-content-card-container">
<div class="hoverable card sticky-action" style="margin-bottom: 2px">
<div class="card-image waves-effect waves-block waves-light">
<%= render partial: 'content/display/favorite_control', locals: { content: content } %>
<div class="activator" style="height: 265px; background: url('<%= content.random_image_including_private(format: :medium) %>'); background-size: cover;"></div>
<div class="activator" style="height: 265px; background: url('<%= content_image %>'); background-size: cover;"></div>
<span class="card-title js-content-name activator">
<div class="bordered-text">

View File

@ -181,6 +181,20 @@
</div>
<% end %>
<% if @words_written > 0 %>
<div class="spacer" style="height: 100px"></div>
<div class="row">
<div class="col s12">
<div class="card-panel center blue white-text">
<h2>All in all, you wrote</h2>
<h1 style="font-size: 10rem"><%= number_with_delimiter @words_written %></h1>
<h2>words in Notebook.ai this year!</h2>
<div>Good job!</div>
</div>
</div>
</div>
<% end %>
<% if @created_content['Thredded::Topic'].any? || @created_content['Thredded::Post'].any? || @created_content['ContentPageShare'].any? %>
<div class="spacer" style="height: 100px;"></div>
<div class="row">

View File

@ -10,7 +10,7 @@
<%= javascript_include_tag 'thredded',
async: !Rails.application.config.assets.debug,
defer: true %>
<%== Gravatar.prefetch_dns %>
<%== RailsGravatar.prefetch_dns_tag %>
</head>
<body data-in-app="true" class="<%= 'has-fixed-sidenav' if user_signed_in? %> <%= 'dark' if user_signed_in? && current_user.dark_mode_enabled? %>">
<%= render 'layouts/sidenav' if user_signed_in? %>

View File

@ -212,23 +212,6 @@
</div>
<% end %>
<br />
<div>
<%= link_to new_creature_path, class: "white-text", style: 'width: 100%' do %>
<div class="hoverable card-panel <%= Creature.color %>" style="margin-bottom: 4px">
<div class="valign-wrapper">
<i class="material-icons grey-bordered-text" class="left" style="font-size: 3em;"><%= Creature.icon %></i>
<span style="font-size: 1.1em; margin-left: 1em">
New Creature<br />
<small>
Free for all users in October
</small>
</span>
</div>
</div>
<% end %>
</div>
</div>
</div>

View File

@ -6,7 +6,7 @@
<%= image_tag 'logos/both-small.webp', class: 'center' %>
</div>
<h1 style="font-size: 2em">Notebook.ai + Writer's Craft Super Stack</h1>
<h1 style="font-size: 2em">Notebook.ai + Writer's Craft 3.0</h1>
<p>
We're extremely excited to be a part of a super cool project put together by our friends at Infostack.io: a gigantic bundle of high-quality writing tools
for a low, super-accessible price. If you've already purchased the stack for yourself (or a friend&mdash;they make great gifts!),
@ -15,57 +15,12 @@
<blockquote class="card-panel">
"I built Notebook.ai because I want to see powerful writing tools in the hands of more people. I'm excited to include 6 months of free Premium alongside a ton of other hugely-discounted writing resources just for you."
<br /><strong>- Andrew Brown, Notebook.ai creator</strong>
<br />
<strong>- Andrew Brown, Notebook.ai creator</strong>
</blockquote>
<p>
If you <em>haven't</em> bought the Writer's Craft Super Stack yet, here's a quick overview of some of my personal favorites inside:
</p>
<div class="row">
<div class="col s12 m6 l3">
<div class="hoverable card-panel">
A 16-day worldbuilding workshop from fantasy author Stephanie Bwabwa <span class="grey-text">normally $97</span>
</div>
</div>
<div class="col s12 m6 l3">
<div class="hoverable card-panel">
An online 48-lesson course for writing romance novels from Writing Academy <span class="grey-text">normally $299</span>
</div>
</div>
<div class="col s12 m6 l3">
<div class="hoverable card-panel">
An online class on writing taught by NYT best-selling author Dave Wolverton <span class="grey-text">normally $199</span>
</div>
</div>
<div class="col s12 m6 l3">
<div class="hoverable card-panel">
Three ebooks on worldbuilding from fantasy author Kristen Kieffer <span class="grey-text">normally $65</span>
</div>
</div>
<div class="col s12 m6 l3">
<div class="hoverable card-panel">
A full manuscript review of your novel from professional editor Andi Cumbo-Floyd <span class="grey-text">normally $500</span>
</div>
</div>
<div class="col s12 m6 l3">
<div class="hoverable card-panel">
$100 off a professional cover design for your book from the designers at 100 Covers
</div>
</div>
<div class="col s12 m6 l3">
<div class="hoverable card-panel">
An introductory course on how to write children's books from Darcy Pattison <span class="grey-text">normally $29</span>
</div>
</div>
<div class="col s12 m6 l3">
<div class="hoverable card-panel">
The Ultimate Novel Planning Workbook ebook (so you can print it over and over!) from Lana Pecherczyk <span class="grey-text">normally $11</span>
</div>
</div>
</div>
<div class="center">
<%= link_to 'Buy the stack ($50)', 'https://drusepth--infostack.thrivecart.com/wc', class: 'hoverable btn btn-large white-text blue' %>
<%= link_to 'Buy the stack ($49)', 'https://drusepth--infostack.thrivecart.com/wc3 ', class: 'hoverable btn btn-large white-text blue' %>
</div>
<div style="font-size: 1.5em; padding-top: 100px; font-weight: bold" id="how-to-redeem">Redeeming your Notebook.ai Premium</div>
@ -101,7 +56,7 @@
<div style="font-size: 1.3em;">Step 2: Redeem your code</div>
<% if user_signed_in? %>
<p>
Now that you're signed into a Notebook.ai account, you can paste the code you received from the Writer's Craft Super Stack into the box below.
Now that you're signed into a Notebook.ai account, you can paste the code you received from the Writer's Craft 3.0 into the box below.
Your account will be immediately upgraded to Premium (<span class="grey-text">normally $9/month</span>) for 6 months.
</p>
<div>
@ -120,7 +75,7 @@
<% else %>
<p>
Once you've signed into a Notebook.ai account, you can paste the code you received from the Writer's Craft Super Stack into the box below.
Once you've signed into a Notebook.ai account, you can paste the code you received from the Writer's Craft 3.0 into the box below.
Your account will be immediately upgraded to Premium (<span class="grey-text">normally $9/month</span>) for 6 months.
</p>
<div>

View File

@ -278,6 +278,7 @@ Rails.application.routes.draw do
get '/churn', to: 'admin#churn'
get '/hatewatch/:matchlist', to: 'admin#hate'
get '/spamwatch', to: 'admin#spam'
get '/notifications', to: 'admin#notifications'
post '/perform_unsubscribe', to: 'admin#perform_unsubscribe', as: :perform_unsubscribe
end
mount RailsAdmin::Engine => '/admin', as: 'rails_admin'

View File

@ -0,0 +1,18 @@
# This migration comes from active_storage (originally 20190112182829)
class AddServiceNameToActiveStorageBlobs < ActiveRecord::Migration[6.0]
def up
unless column_exists?(:active_storage_blobs, :service_name)
add_column :active_storage_blobs, :service_name, :string
if configured_service = ActiveStorage::Blob.service.name
ActiveStorage::Blob.unscoped.update_all(service_name: configured_service)
end
change_column :active_storage_blobs, :service_name, :string, null: false
end
end
def down
remove_column :active_storage_blobs, :service_name
end
end

View File

@ -0,0 +1,12 @@
# This migration comes from active_storage (originally 20191206030411)
class CreateActiveStorageVariantRecords < ActiveRecord::Migration[6.0]
def change
create_table :active_storage_variant_records do |t|
t.belongs_to :blob, null: false, index: false
t.string :variation_digest, null: false
t.index %i[ blob_id variation_digest ], name: "index_active_storage_variant_records_uniqueness", unique: true
t.foreign_key :active_storage_blobs, column: :blob_id
end
end
end

View File

@ -0,0 +1,5 @@
class AddWordCountCacheToAttributes < ActiveRecord::Migration[6.0]
def change
add_column :attributes, :word_count_cache, :integer
end
end

View File

@ -0,0 +1,12 @@
class CreateWordCountUpdates < ActiveRecord::Migration[6.0]
def change
create_table :word_count_updates do |t|
t.references :user, null: false, foreign_key: true
t.references :entity, polymorphic: true, null: false
t.integer :word_count
t.date :for_date
t.timestamps
end
end
end

View File

@ -0,0 +1,17 @@
class AddServiceNameMigrationAgain < ActiveRecord::Migration[6.1]
def up
unless column_exists?(:active_storage_blobs, :service_name)
add_column :active_storage_blobs, :service_name, :string
if configured_service = ActiveStorage::Blob.service.name
ActiveStorage::Blob.unscoped.update_all(service_name: configured_service)
end
change_column :active_storage_blobs, :service_name, :string, null: false
end
end
def down
remove_column :active_storage_blobs, :service_name
end
end

View File

@ -2,15 +2,15 @@
# of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition.
#
# This file is the source Rails uses to define your schema when running `rails
# db:schema:load`. When creating a new database, `rails db:schema:load` tends to
# This file is the source Rails uses to define your schema when running `bin/rails
# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
# be faster and is potentially less error prone than running all of your
# migrations from scratch. Old migrations may fail to apply correctly if those
# migrations use external dependencies or application code.
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2021_09_15_030031) do
ActiveRecord::Schema.define(version: 2021_10_27_065954) do
create_table "active_storage_attachments", force: :cascade do |t|
t.string "name", null: false
@ -27,12 +27,19 @@ ActiveRecord::Schema.define(version: 2021_09_15_030031) do
t.string "filename", null: false
t.string "content_type"
t.text "metadata"
t.bigint "byte_size", null: false
t.integer "byte_size", null: false
t.string "checksum", null: false
t.datetime "created_at", null: false
t.string "service_name", null: false
t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true
end
create_table "active_storage_variant_records", force: :cascade do |t|
t.bigint "blob_id", null: false
t.string "variation_digest", null: false
t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true
end
create_table "api_keys", force: :cascade do |t|
t.integer "user_id"
t.string "key"
@ -165,6 +172,7 @@ ActiveRecord::Schema.define(version: 2021_09_15_030031) do
t.datetime "created_at"
t.datetime "updated_at"
t.datetime "deleted_at"
t.integer "word_count_cache"
t.index ["attribute_field_id", "deleted_at", "entity_id", "entity_type"], name: "attributes_afi_deleted_at_entity_id_entity_type"
t.index ["attribute_field_id", "deleted_at"], name: "index_attributes_on_attribute_field_id_and_deleted_at"
t.index ["attribute_field_id", "user_id", "entity_type", "entity_id", "deleted_at"], name: "attributes_afi_ui_et_ei_da"
@ -3636,7 +3644,20 @@ ActiveRecord::Schema.define(version: 2021_09_15_030031) do
t.integer "habitat_id"
end
create_table "word_count_updates", force: :cascade do |t|
t.integer "user_id", null: false
t.string "entity_type", null: false
t.integer "entity_id", null: false
t.integer "word_count"
t.date "for_date"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["entity_type", "entity_id"], name: "index_word_count_updates_on_entity_type_and_entity_id"
t.index ["user_id"], name: "index_word_count_updates_on_user_id"
end
add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"
add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id"
add_foreign_key "api_keys", "users"
add_foreign_key "api_requests", "application_integrations"
add_foreign_key "api_requests", "integration_authorizations"
@ -4047,4 +4068,5 @@ ActiveRecord::Schema.define(version: 2021_09_15_030031) do
add_foreign_key "vehicles", "users"
add_foreign_key "votes", "users"
add_foreign_key "votes", "votables"
add_foreign_key "word_count_updates", "users"
end

View File

@ -2,11 +2,11 @@
User.find_each do |user|
user.notifications.create(
message_html: "<div class='text-darken-4 black-text'>New in Notebook.ai:</div>Document tags, folders, and more!",
icon: Document.icon,
icon_color: Document.color,
message_html: "<div class='text-darken-4 black-text'>2021 is winding to a close...</div>Click here to see your year in review!",
icon: 'event_available',
icon_color: 'blue',
happened_at: DateTime.current,
passthrough_link: "https://medium.com/indent-labs/write-and-organize-your-stories-in-notebook-ai-1d7821af6f07",
reference_code: "documents-folders-and-tags"
passthrough_link: "https://www.notebook.ai/my/data/yearly/2021",
reference_code: "2021-year-in-review"
)
end

View File

@ -8,6 +8,7 @@ module Extensions
included do
after_create :create_content_page_share
after_create :notify_discord
has_many :content_page_shares, as: :content
acts_as_paranoid
@ -25,6 +26,10 @@ module Extensions
end
end
def notify_discord
NotifyDiscordOfThreadJob.set(wait: 1.minute).perform_later(self.id)
end
def create_content_page_share
ContentPageShare.create(
user_id: self.user_id,

View File

@ -1,4 +1,58 @@
namespace :backfill do
desc "Backfill cached word counts on all attributes"
task attribute_word_count_caches: :environment do
Attribute.where(word_count_cache: nil).where.not(value: ["", " ", ".", nil]).find_each do |attribute|
word_count = WordCountAnalyzer::Counter.new(
ellipsis: 'no_special_treatment',
hyperlink: 'count_as_one',
contraction: 'count_as_one',
hyphenated_word: 'count_as_one',
date: 'no_special_treatment',
number: 'count',
numbered_list: 'ignore',
xhtml: 'remove',
forward_slash: 'count_as_multiple_except_dates',
backslash: 'count_as_one',
dotted_line: 'ignore',
dashed_line: 'ignore',
underscore: 'ignore',
stray_punctuation: 'ignore'
).count(attribute.value)
attribute.update_column(:word_count_cache, word_count)
end
end
task most_used_attribute_word_counts: :environment do
word_counts = {}
Attribute.where(word_count_cache: nil).group(:value).order('count_id DESC').limit(500).count(:id).each do |value, count|
word_count = WordCountAnalyzer::Counter.new(
ellipsis: 'no_special_treatment',
hyperlink: 'count_as_one',
contraction: 'count_as_one',
hyphenated_word: 'count_as_one',
date: 'no_special_treatment',
number: 'count',
numbered_list: 'ignore',
xhtml: 'remove',
forward_slash: 'count_as_multiple_except_dates',
backslash: 'count_as_one',
dotted_line: 'ignore',
dashed_line: 'ignore',
underscore: 'ignore',
stray_punctuation: 'ignore'
).count(value)
word_counts[word_count] ||= []
word_counts[word_count].push value
puts "#{value} x #{count}: #{word_count} words"
end
word_counts.each do |count, values|
Attribute.where(word_count_cache: nil, value: values).update_all(word_count_cache: count)
end
end
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|

View File

@ -1,4 +1,15 @@
namespace :one_off do
desc "Clean up orphaned page tags"
task clean_orphaned_page_tags: :environment do
PageTag.find_each do |page_tag|
referenced_page = page_tag.page
if referenced_page.nil?
page_tag.destroy
end
end
end
desc "Alert users who've saved at least one tree"
task trees_notification: :environment do
reference_code = 'green-trees'

15
test/fixtures/word_count_updates.yml vendored Normal file
View File

@ -0,0 +1,15 @@
# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
one:
user: one
entity: one
entity_type: Entity
word_count: 1
for_date: 2021-10-07
two:
user: two
entity: two
entity_type: Entity
word_count: 1
for_date: 2021-10-07

View File

@ -0,0 +1,7 @@
require 'test_helper'
class WordCountUpdateTest < ActiveSupport::TestCase
# test "the truth" do
# assert true
# end
end