mirror of
https://github.com/indentlabs/notebook.git
synced 2025-10-26 11:19:22 +00:00
Merge branch 'master' into thredded-report
This commit is contained in:
commit
161c8a7485
9
Gemfile
9
Gemfile
@ -36,6 +36,11 @@ gem 'paranoia'
|
||||
gem 'coffee-rails'
|
||||
gem 'rails-jquery-autocomplete'
|
||||
|
||||
# Form enhancements
|
||||
gem 'redcarpet' #markdown formatting
|
||||
gem 'acts_as_list' #sortables
|
||||
gem 'tribute' # @mentions
|
||||
|
||||
# SEO
|
||||
gem 'meta-tags'
|
||||
|
||||
@ -50,10 +55,6 @@ gem 'medium-editor-rails'
|
||||
gem 'chartkick'
|
||||
gem 'slack-notifier'
|
||||
|
||||
# Form enhancements
|
||||
gem 'redcarpet' #markdown formatting
|
||||
gem 'acts_as_list' #sortables
|
||||
|
||||
# Analytics
|
||||
gem 'mixpanel-ruby'
|
||||
gem 'barnes'
|
||||
|
||||
127
Gemfile.lock
127
Gemfile.lock
@ -34,45 +34,45 @@ GIT
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
actioncable (5.2.2)
|
||||
actionpack (= 5.2.2)
|
||||
actioncable (5.2.2.1)
|
||||
actionpack (= 5.2.2.1)
|
||||
nio4r (~> 2.0)
|
||||
websocket-driver (>= 0.6.1)
|
||||
actionmailer (5.2.2)
|
||||
actionpack (= 5.2.2)
|
||||
actionview (= 5.2.2)
|
||||
activejob (= 5.2.2)
|
||||
actionmailer (5.2.2.1)
|
||||
actionpack (= 5.2.2.1)
|
||||
actionview (= 5.2.2.1)
|
||||
activejob (= 5.2.2.1)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
actionpack (5.2.2)
|
||||
actionview (= 5.2.2)
|
||||
activesupport (= 5.2.2)
|
||||
actionpack (5.2.2.1)
|
||||
actionview (= 5.2.2.1)
|
||||
activesupport (= 5.2.2.1)
|
||||
rack (~> 2.0)
|
||||
rack-test (>= 0.6.3)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
||||
actionview (5.2.2)
|
||||
activesupport (= 5.2.2)
|
||||
actionview (5.2.2.1)
|
||||
activesupport (= 5.2.2.1)
|
||||
builder (~> 3.1)
|
||||
erubi (~> 1.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.0.3)
|
||||
active_record_union (1.3.0)
|
||||
activerecord (>= 4.0)
|
||||
activejob (5.2.2)
|
||||
activesupport (= 5.2.2)
|
||||
activejob (5.2.2.1)
|
||||
activesupport (= 5.2.2.1)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (5.2.2)
|
||||
activesupport (= 5.2.2)
|
||||
activerecord (5.2.2)
|
||||
activemodel (= 5.2.2)
|
||||
activesupport (= 5.2.2)
|
||||
activemodel (5.2.2.1)
|
||||
activesupport (= 5.2.2.1)
|
||||
activerecord (5.2.2.1)
|
||||
activemodel (= 5.2.2.1)
|
||||
activesupport (= 5.2.2.1)
|
||||
arel (>= 9.0)
|
||||
activestorage (5.2.2)
|
||||
actionpack (= 5.2.2)
|
||||
activerecord (= 5.2.2)
|
||||
activestorage (5.2.2.1)
|
||||
actionpack (= 5.2.2.1)
|
||||
activerecord (= 5.2.2.1)
|
||||
marcel (~> 0.3.1)
|
||||
activesupport (5.2.2)
|
||||
activesupport (5.2.2.1)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 0.7, < 2)
|
||||
minitest (~> 5.1)
|
||||
@ -85,7 +85,7 @@ GEM
|
||||
ast (2.4.0)
|
||||
authority (3.3.0)
|
||||
activesupport (>= 3.0.0)
|
||||
autoprefixer-rails (9.4.8)
|
||||
autoprefixer-rails (9.4.10.2)
|
||||
execjs
|
||||
aws-eventstream (1.0.1)
|
||||
aws-partitions (1.131.0)
|
||||
@ -150,7 +150,7 @@ GEM
|
||||
coffee-script-source
|
||||
execjs
|
||||
coffee-script-source (1.12.2)
|
||||
concurrent-ruby (1.1.4)
|
||||
concurrent-ruby (1.1.5)
|
||||
connection_pool (2.2.2)
|
||||
crack (0.4.3)
|
||||
safe_yaml (~> 1.0.0)
|
||||
@ -184,7 +184,7 @@ GEM
|
||||
db_text_search (0.3.0)
|
||||
activerecord (>= 4.1.15, < 6.0)
|
||||
debug_inspector (0.0.3)
|
||||
devise (4.5.0)
|
||||
devise (4.6.1)
|
||||
bcrypt (~> 3.0)
|
||||
orm_adapter (~> 0.1)
|
||||
railties (>= 4.1.0, < 6.0)
|
||||
@ -201,7 +201,7 @@ GEM
|
||||
railties (>= 3.0.0)
|
||||
faraday (0.15.4)
|
||||
multipart-post (>= 1.2, < 3)
|
||||
ffi (1.9.25)
|
||||
ffi (1.10.0)
|
||||
filesize (0.2.0)
|
||||
flamegraph (0.9.5)
|
||||
font-awesome-rails (4.7.0.4)
|
||||
@ -236,7 +236,7 @@ GEM
|
||||
activesupport (>= 2)
|
||||
nokogiri (>= 1.4)
|
||||
htmlentities (4.3.4)
|
||||
i18n (1.5.2)
|
||||
i18n (1.6.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
inline_svg (1.3.1)
|
||||
activesupport (>= 3.0)
|
||||
@ -314,7 +314,7 @@ GEM
|
||||
notiffany (0.1.1)
|
||||
nenv (~> 0.1)
|
||||
shellany (~> 0.0)
|
||||
onebox (1.8.78)
|
||||
onebox (1.8.82)
|
||||
htmlentities (~> 4.3)
|
||||
moneta (~> 1.0)
|
||||
multi_json (~> 1.11)
|
||||
@ -328,16 +328,17 @@ GEM
|
||||
mime-types
|
||||
mimemagic (~> 0.3.0)
|
||||
terrapin (~> 0.6.0)
|
||||
parallel (1.12.1)
|
||||
parallel (1.14.0)
|
||||
paranoia (2.4.1)
|
||||
activerecord (>= 4.0, < 5.3)
|
||||
parser (2.5.3.0)
|
||||
parser (2.6.0.0)
|
||||
ast (~> 2.4.0)
|
||||
pg (0.21.0)
|
||||
powerpack (0.1.2)
|
||||
pry (0.12.2)
|
||||
coderay (~> 1.1.0)
|
||||
method_source (~> 0.9.0)
|
||||
psych (3.1.0)
|
||||
public_suffix (3.0.3)
|
||||
puma (3.12.0)
|
||||
puma-heroku (1.0.0)
|
||||
@ -354,18 +355,18 @@ GEM
|
||||
rack
|
||||
rack-test (1.1.0)
|
||||
rack (>= 1.0, < 3)
|
||||
rails (5.2.2)
|
||||
actioncable (= 5.2.2)
|
||||
actionmailer (= 5.2.2)
|
||||
actionpack (= 5.2.2)
|
||||
actionview (= 5.2.2)
|
||||
activejob (= 5.2.2)
|
||||
activemodel (= 5.2.2)
|
||||
activerecord (= 5.2.2)
|
||||
activestorage (= 5.2.2)
|
||||
activesupport (= 5.2.2)
|
||||
rails (5.2.2.1)
|
||||
actioncable (= 5.2.2.1)
|
||||
actionmailer (= 5.2.2.1)
|
||||
actionpack (= 5.2.2.1)
|
||||
actionview (= 5.2.2.1)
|
||||
activejob (= 5.2.2.1)
|
||||
activemodel (= 5.2.2.1)
|
||||
activerecord (= 5.2.2.1)
|
||||
activestorage (= 5.2.2.1)
|
||||
activesupport (= 5.2.2.1)
|
||||
bundler (>= 1.3.0)
|
||||
railties (= 5.2.2)
|
||||
railties (= 5.2.2.1)
|
||||
sprockets-rails (>= 2.0.0)
|
||||
rails-controller-testing (1.0.4)
|
||||
actionpack (>= 5.0.1.x)
|
||||
@ -399,9 +400,9 @@ GEM
|
||||
sass-rails (>= 4.0, < 6)
|
||||
rails_serve_static_assets (0.0.5)
|
||||
rails_stdout_logging (0.0.5)
|
||||
railties (5.2.2)
|
||||
actionpack (= 5.2.2)
|
||||
activesupport (= 5.2.2)
|
||||
railties (5.2.2.1)
|
||||
actionpack (= 5.2.2.1)
|
||||
activesupport (= 5.2.2.1)
|
||||
method_source
|
||||
rake (>= 0.8.7)
|
||||
thor (>= 0.19.0, < 2.0)
|
||||
@ -415,9 +416,9 @@ GEM
|
||||
redis (4.1.0)
|
||||
regexp_parser (1.3.0)
|
||||
remotipart (1.4.2)
|
||||
responders (2.4.0)
|
||||
actionpack (>= 4.2.0, < 5.3)
|
||||
railties (>= 4.2.0, < 5.3)
|
||||
responders (2.4.1)
|
||||
actionpack (>= 4.2.0, < 6.0)
|
||||
railties (>= 4.2.0, < 6.0)
|
||||
rinku (2.0.5)
|
||||
rmagick (2.16.0)
|
||||
rspec (3.8.0)
|
||||
@ -444,11 +445,12 @@ GEM
|
||||
rspec-mocks (~> 3.8.0)
|
||||
rspec-support (~> 3.8.0)
|
||||
rspec-support (3.8.0)
|
||||
rubocop (0.62.0)
|
||||
rubocop (0.65.0)
|
||||
jaro_winkler (~> 1.5.1)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 2.5, != 2.5.1.1)
|
||||
powerpack (~> 0.1)
|
||||
psych (>= 3.1.0)
|
||||
rainbow (>= 2.2.2, < 4.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (~> 1.4.0)
|
||||
@ -472,8 +474,8 @@ GEM
|
||||
sprockets (>= 2.8, < 4.0)
|
||||
sprockets-rails (>= 2.0, < 4.0)
|
||||
tilt (>= 1.1, < 3)
|
||||
sassc (2.0.0)
|
||||
ffi (~> 1.9.6)
|
||||
sassc (2.0.1)
|
||||
ffi (~> 1.9)
|
||||
rake
|
||||
sassc-rails (2.1.0)
|
||||
railties (>= 4.0.0)
|
||||
@ -523,8 +525,30 @@ GEM
|
||||
climate_control (>= 0.0.3, < 1.0)
|
||||
thor (0.20.3)
|
||||
thread_safe (0.3.6)
|
||||
thredded (0.16.9)
|
||||
active_record_union (>= 1.3.0)
|
||||
autoprefixer-rails
|
||||
db_text_search (~> 0.3.0)
|
||||
friendly_id
|
||||
html-pipeline
|
||||
htmlentities
|
||||
inline_svg
|
||||
kaminari
|
||||
kramdown (>= 2.0.0)
|
||||
kramdown-parser-gfm
|
||||
nokogiri
|
||||
onebox (~> 1.8, >= 1.8.48)
|
||||
pundit (>= 1.1.0)
|
||||
rails (>= 4.2.10)
|
||||
rb-gravatar
|
||||
rinku
|
||||
sanitize
|
||||
sassc-rails (>= 2.0.0)
|
||||
sprockets-es6
|
||||
timeago_js
|
||||
tilt (2.0.9)
|
||||
timeago_js (3.0.2)
|
||||
tribute (3.6.0.0)
|
||||
tzinfo (1.2.5)
|
||||
thread_safe (~> 0.1)
|
||||
tzinfo-data (1.2018.9)
|
||||
@ -618,7 +642,8 @@ DEPENDENCIES
|
||||
stackprof
|
||||
stripe
|
||||
stripe_event
|
||||
thredded!
|
||||
thredded
|
||||
tribute
|
||||
tzinfo-data
|
||||
uglifier (>= 1.3.0)
|
||||
web-console
|
||||
|
||||
@ -80,7 +80,7 @@ You should now see a copy of the site running locally at http://localhost:3000/!
|
||||
|
||||
== Deployment to notebook.ai
|
||||
|
||||
Deployment to the live stage will only be done by approved developers, and consists of a deployment of
|
||||
Deployment to the live production servers at www.Notebook.ai will only be done by approved developers, and consists of a deployment of
|
||||
|
||||
- deploy github to staging (done only by approved developers)
|
||||
|
||||
|
||||
BIN
app/assets/images/card-headers/documents.jpg
Normal file
BIN
app/assets/images/card-headers/documents.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 184 KiB |
@ -13,4 +13,5 @@
|
||||
//= require autocomplete-rails
|
||||
//= require cocoon
|
||||
//= require medium-editor
|
||||
//= require tribute
|
||||
//= require_tree .
|
||||
|
||||
17
app/assets/javascripts/content_filtering.js
Normal file
17
app/assets/javascripts/content_filtering.js
Normal file
@ -0,0 +1,17 @@
|
||||
$(document).ready(function () {
|
||||
// Override the :contains() selector to make it case-insensitive
|
||||
$.expr[":"].contains = $.expr.createPseudo(function(arg) {
|
||||
return function( elem ) {
|
||||
return $(elem).text().toUpperCase().indexOf(arg.toUpperCase()) >= 0;
|
||||
};
|
||||
});
|
||||
|
||||
$('#js-content-name-filter').keyup(function (e) {
|
||||
var search_query = $(this).val();
|
||||
var content_list = $('.js-content-cards-list > .js-content-card-container');
|
||||
|
||||
// Show everything, then hide what doesn't match
|
||||
content_list.hide();
|
||||
content_list.find(".js-content-name:contains(" + search_query + ")").closest('.js-content-card-container').show();
|
||||
});
|
||||
});
|
||||
@ -1,76 +0,0 @@
|
||||
$(document).ready(function () {
|
||||
function add_link_bar(link_bar_container) {
|
||||
$('.content-field-link-bar-container').html('');
|
||||
$(link_bar_container).html($('#content-field-link-bar-template').html())
|
||||
$(link_bar_container).find('.content-field-link-bar').show();
|
||||
$(link_bar_container).find('.dropdown-trigger').dropdown({coverTrigger: false});
|
||||
$(link_bar_container).find('.tooltipped').tooltip({ enterDelay: 50 });
|
||||
add_link_bar_click_handlers();
|
||||
}
|
||||
|
||||
$('.content-field').find('textarea').focus(function (focus_event) {
|
||||
var focused_text_field = $(focus_event.target);
|
||||
var parent_content_field = focused_text_field.closest('.content-field');
|
||||
|
||||
$('.show-when-focused').hide();
|
||||
parent_content_field.find('.show-when-focused').show();
|
||||
|
||||
$('.content-field').removeClass('focused');
|
||||
parent_content_field.addClass('focused');
|
||||
$('.content-field-link-bar').hide();
|
||||
parent_content_field.find('.content-field-link-bar').show();
|
||||
|
||||
add_link_bar(parent_content_field.find('.content-field-link-bar-container'));
|
||||
});
|
||||
|
||||
// um sir excuse me why does this need to be in a setTimeout? It doesn't work
|
||||
// unless it is... for whatever reason. Here be dragons.
|
||||
setTimeout(function () {
|
||||
$('.content-field').find('.chips input').focus(function (focus_event) {
|
||||
var focused_text_field = $(focus_event.target);
|
||||
var parent_content_field = focused_text_field.closest('.content-field');
|
||||
|
||||
$('.show-when-focused').hide();
|
||||
parent_content_field.find('.show-when-focused').show();
|
||||
|
||||
$('.content-field').removeClass('focused');
|
||||
parent_content_field.addClass('focused');
|
||||
$('.content-field-link-bar').hide();
|
||||
parent_content_field.find('.content-field-link-bar').show();
|
||||
});
|
||||
}, 100);
|
||||
|
||||
function add_link_bar_click_handlers() {
|
||||
$('.js-content-link-option').click(function (click_event) {
|
||||
var selected_option = $(click_event.target);
|
||||
var textarea_to_modify = selected_option
|
||||
.closest('.content-field')
|
||||
.find('textarea');
|
||||
var current_textarea_value = textarea_to_modify.val();
|
||||
|
||||
var token_to_insert = [
|
||||
'[[',
|
||||
selected_option.data('content-type'),
|
||||
'-',
|
||||
selected_option.data('content-id'),
|
||||
']]'
|
||||
].join('')
|
||||
|
||||
// If the final character of the textarea is a newline or a space
|
||||
// character, then we want to just append our token. Otherwise, we want
|
||||
// to prepend our token with a space so it is properly separated.
|
||||
var final_letter_presently = current_textarea_value.slice(-1);
|
||||
if (final_letter_presently != ' ' && final_letter_presently != "\n") {
|
||||
// Prepend a space
|
||||
token_to_insert = ' ' + token_to_insert;
|
||||
}
|
||||
|
||||
// Make the change!
|
||||
textarea_to_modify.val(current_textarea_value + token_to_insert);
|
||||
|
||||
// Don't jump to the top of the page after clicking!
|
||||
click_event.stopPropagation();
|
||||
return false;
|
||||
});
|
||||
}
|
||||
});
|
||||
31
app/assets/javascripts/field_focusing.js
Normal file
31
app/assets/javascripts/field_focusing.js
Normal file
@ -0,0 +1,31 @@
|
||||
$(document).ready(function () {
|
||||
$('.content-field').find('textarea').focus(function (focus_event) {
|
||||
var focused_text_field = $(focus_event.target);
|
||||
var parent_content_field = focused_text_field.closest('.content-field');
|
||||
|
||||
$('.show-when-focused').hide();
|
||||
parent_content_field.find('.show-when-focused').show();
|
||||
|
||||
$('.content-field').removeClass('focused');
|
||||
parent_content_field.addClass('focused');
|
||||
$('.content-field-link-bar').hide();
|
||||
parent_content_field.find('.content-field-link-bar').show();
|
||||
});
|
||||
|
||||
// um sir excuse me why does this need to be in a setTimeout? It doesn't work
|
||||
// unless it is... for whatever reason. Here be dragons.
|
||||
setTimeout(function () {
|
||||
$('.content-field').find('.chips input').focus(function (focus_event) {
|
||||
var focused_text_field = $(focus_event.target);
|
||||
var parent_content_field = focused_text_field.closest('.content-field');
|
||||
|
||||
$('.show-when-focused').hide();
|
||||
parent_content_field.find('.show-when-focused').show();
|
||||
|
||||
$('.content-field').removeClass('focused');
|
||||
parent_content_field.addClass('focused');
|
||||
$('.content-field-link-bar').hide();
|
||||
parent_content_field.find('.content-field-link-bar').show();
|
||||
});
|
||||
}, 100);
|
||||
});
|
||||
@ -13,5 +13,6 @@
|
||||
*= require font-awesome
|
||||
*= require medium-editor/medium-editor
|
||||
*= require medium-editor/themes/beagle
|
||||
*= require tribute
|
||||
*= require_tree .
|
||||
*/
|
||||
|
||||
5
app/assets/stylesheets/buttons.scss
Normal file
5
app/assets/stylesheets/buttons.scss
Normal file
@ -0,0 +1,5 @@
|
||||
.btn {
|
||||
.material-icons {
|
||||
vertical-align: top;
|
||||
}
|
||||
}
|
||||
@ -1,3 +0,0 @@
|
||||
.content-field-link-bar, .dropdown-content {
|
||||
display: none;
|
||||
}
|
||||
6
app/assets/stylesheets/content_linking.scss.erb
Normal file
6
app/assets/stylesheets/content_linking.scss.erb
Normal file
@ -0,0 +1,6 @@
|
||||
<% Rails.application.config.content_types[:all].each do |content_type| %>
|
||||
.<%= content_type.name.downcase %>-link {
|
||||
color: <%= content_type.hex_color %>;
|
||||
border-bottom: 1px dashed <%= content_type.hex_color %>;
|
||||
}
|
||||
<% end %>
|
||||
@ -25,3 +25,12 @@
|
||||
right: 7px;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.centered-card-content {
|
||||
margin: 0;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -80%);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@ -7,3 +7,8 @@
|
||||
.slider .slides li.active {
|
||||
z-index: 1 !important;
|
||||
}
|
||||
|
||||
.slider {
|
||||
height: 140px;
|
||||
overflow: hidden;
|
||||
}
|
||||
@ -1,4 +1,8 @@
|
||||
class DocumentAuthorizer < ApplicationAuthorizer
|
||||
def self.creatable_by?(user)
|
||||
true
|
||||
end
|
||||
|
||||
def readable_by?(user)
|
||||
[
|
||||
resource.user_id == user.id
|
||||
|
||||
@ -69,4 +69,27 @@ class ApplicationController < ActionController::Base
|
||||
0
|
||||
end
|
||||
end
|
||||
|
||||
def cache_linkable_content_for_each_content_type
|
||||
linkable_classes = Rails.application.config.content_types[:all].map(&:name) & current_user.user_content_type_activators.pluck(:content_type)
|
||||
linkable_classes -= ["Universe"]
|
||||
|
||||
@linkables_cache = {}
|
||||
linkable_classes.each do |class_name|
|
||||
# class_name = "Character"
|
||||
|
||||
@linkables_cache[class_name] = current_user.send("linkable_#{class_name.downcase.pluralize}")
|
||||
.in_universe(@universe_scope)
|
||||
|
||||
if @content.present? && @content.persisted?
|
||||
@linkables_cache[class_name] = @linkables_cache[class_name]
|
||||
.in_universe(@content.universe)
|
||||
.reject { |content| content.class.name == class_name && content.id == @content.id }
|
||||
end
|
||||
|
||||
@linkables_cache[class_name] = @linkables_cache[class_name].sort_by(&:name)
|
||||
.map { |c| [c.name, c.id] }
|
||||
.compact
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -6,7 +6,7 @@ class ContentController < ApplicationController
|
||||
# old content to the new attributes styling.
|
||||
before_action :migrate_old_style_field_values, only: [:show, :edit]
|
||||
|
||||
before_action :populate_linkable_content_for_each_content_type, only: [:new, :edit]
|
||||
before_action :cache_linkable_content_for_each_content_type, only: [:new, :edit]
|
||||
|
||||
before_action :set_attributes_content_type, only: [:attributes]
|
||||
|
||||
@ -23,11 +23,11 @@ class ContentController < ApplicationController
|
||||
@content_type_class.attribute_categories(current_user)
|
||||
|
||||
if @universe_scope.present? && @content_type_class != Universe
|
||||
@content = @universe_scope.send(pluralized_content_name)
|
||||
@content = @universe_scope.send(pluralized_content_name).includes(:page_tags, :image_uploads)
|
||||
else
|
||||
@content = (
|
||||
current_user.send(pluralized_content_name).includes(:page_tags) +
|
||||
current_user.send("contributable_#{pluralized_content_name}").includes(:page_tags)
|
||||
current_user.send(pluralized_content_name).includes(:page_tags, :image_uploads) +
|
||||
current_user.send("contributable_#{pluralized_content_name}").includes(:page_tags, :image_uploads)
|
||||
)
|
||||
|
||||
unless @content_type_class == Universe
|
||||
@ -42,12 +42,13 @@ class ContentController < ApplicationController
|
||||
@page_tags = PageTag.where(
|
||||
page_type: @content_type_class.name,
|
||||
page_id: @content.pluck(:id)
|
||||
)
|
||||
).order(:tag)
|
||||
if params.key?(:slug)
|
||||
filtered_page_tags = @page_tags.where(slug: params[:slug])
|
||||
@content.select! { |content| filtered_page_tags.pluck(:page_id).include?(content.id) }
|
||||
@filtered_page_tags = @page_tags.where(slug: params[:slug])
|
||||
@content.select! { |content| @filtered_page_tags.pluck(:page_id).include?(content.id) }
|
||||
end
|
||||
|
||||
@page_tags = @page_tags.uniq(&:tag)
|
||||
@content = @content.sort_by(&:name)
|
||||
|
||||
respond_to do |format|
|
||||
@ -182,7 +183,7 @@ class ContentController < ApplicationController
|
||||
upload_files params['image_uploads'], content_type.name, @content.id
|
||||
end
|
||||
|
||||
update_page_tags
|
||||
update_page_tags if @content.respond_to?(:page_tags)
|
||||
|
||||
successful_response(content_creation_redirect_url, t(:create_success, model_name: @content.try(:name).presence || humanized_model_name))
|
||||
else
|
||||
@ -214,7 +215,7 @@ class ContentController < ApplicationController
|
||||
end
|
||||
end
|
||||
|
||||
update_page_tags
|
||||
update_page_tags if @content.respond_to?(:page_tags)
|
||||
|
||||
if @content.user == current_user
|
||||
# todo this needs some extra validation probably to ensure each attribute is one associated with this page
|
||||
@ -404,29 +405,6 @@ class ContentController < ApplicationController
|
||||
TemporaryFieldMigrationService.migrate_fields_for_content(content, current_user) if content.present?
|
||||
end
|
||||
|
||||
def populate_linkable_content_for_each_content_type
|
||||
linkable_classes = Rails.application.config.content_types[:all].map(&:name) & current_user.user_content_type_activators.pluck(:content_type)
|
||||
linkable_classes -= ["Universe"]
|
||||
|
||||
@linkables_cache = {}
|
||||
linkable_classes.each do |class_name|
|
||||
# class_name = "Character"
|
||||
|
||||
@linkables_cache[class_name] = current_user.send("linkable_#{class_name.downcase.pluralize}")
|
||||
.in_universe(@universe_scope)
|
||||
|
||||
if @content.present? && @content.persisted?
|
||||
@linkables_cache[class_name] = @linkables_cache[class_name]
|
||||
.in_universe(@content.universe)
|
||||
.reject { |content| content.class.name == class_name && content.id == @content.id }
|
||||
end
|
||||
|
||||
@linkables_cache[class_name] = @linkables_cache[class_name].sort_by(&:name)
|
||||
.map { |c| [c.name, c.id] }
|
||||
.compact
|
||||
end
|
||||
end
|
||||
|
||||
def valid_content_types
|
||||
Rails.application.config.content_types[:all]
|
||||
end
|
||||
@ -522,7 +500,7 @@ class ContentController < ApplicationController
|
||||
@navbar_actions << {
|
||||
label: "New #{content_type.name.downcase}",
|
||||
href: main_app.new_polymorphic_path(content_type)
|
||||
}
|
||||
} if user_signed_in? && current_user.can_create?(content_type)
|
||||
|
||||
discussions_link = ForumsLinkbuilderService.worldbuilding_url(content_type)
|
||||
if discussions_link.present?
|
||||
@ -550,11 +528,11 @@ class ContentController < ApplicationController
|
||||
href: main_app.polymorphic_path(content_type)
|
||||
}
|
||||
end
|
||||
#
|
||||
# @navbar_actions << {
|
||||
# label: "New #{content_type.name.downcase}",
|
||||
# href: main_app.new_polymorphic_path(content_type)
|
||||
# }
|
||||
|
||||
@navbar_actions << {
|
||||
label: "New #{content_type.name.downcase}",
|
||||
href: main_app.new_polymorphic_path(content_type)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@ -6,6 +6,8 @@ class DocumentsController < ApplicationController
|
||||
before_action :set_navbar_actions, except: [:edit]
|
||||
before_action :set_footer_visibility, only: [:edit]
|
||||
|
||||
before_action :cache_linkable_content_for_each_content_type, only: [:edit]
|
||||
|
||||
layout 'editor', only: [:edit]
|
||||
|
||||
def index
|
||||
|
||||
@ -5,8 +5,8 @@ class UsersController < ApplicationController
|
||||
|
||||
def show
|
||||
@sidenav_expansion = 'my account'
|
||||
|
||||
@user = User.find_by(id: params[:id])
|
||||
|
||||
@user = User.find_by(user_params)
|
||||
return redirect_to(root_path, notice: 'That user does not exist.') if @user.nil?
|
||||
|
||||
@content = @user.public_content.select { |type, list| list.any? }
|
||||
@ -70,4 +70,10 @@ class UsersController < ApplicationController
|
||||
|
||||
notifier.ping ":bomb: :bomb: :bomb: #{user.email.split('@').first}@ (##{user.id}) just deleted their account."
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def user_params
|
||||
params.permit(:id, :username)
|
||||
end
|
||||
end
|
||||
|
||||
@ -4,17 +4,10 @@ module HasImageUploads
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
has_many :image_uploads
|
||||
has_many :image_uploads, as: :content
|
||||
# todo: dependent: :destroy
|
||||
# todo: destroy from s3 on destroy
|
||||
|
||||
def image_uploads
|
||||
ImageUpload.where(
|
||||
content_type: self.class.name,
|
||||
content_id: self.id
|
||||
)
|
||||
end
|
||||
|
||||
def public_image_uploads
|
||||
self.image_uploads.where(privacy: 'public')
|
||||
end
|
||||
|
||||
@ -25,4 +25,15 @@ class Document < ApplicationRecord
|
||||
def universe_field_value
|
||||
#todo when documents belong to a universe
|
||||
end
|
||||
|
||||
def reading_estimate
|
||||
words = (self.body || "").split(/\s+/).count
|
||||
minutes = 1 + (words / 200).to_i
|
||||
|
||||
"~#{minutes} minute read"
|
||||
end
|
||||
|
||||
def description
|
||||
self.body
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
class ImageUpload < ApplicationRecord
|
||||
belongs_to :user
|
||||
belongs_to :content, polymorphic: true
|
||||
|
||||
has_attached_file :src,
|
||||
path: 'content/uploads/:style/:filename',
|
||||
|
||||
@ -11,7 +11,12 @@ class User < ApplicationRecord
|
||||
include HasContent
|
||||
include Authority::UserAbilities
|
||||
|
||||
validates_uniqueness_of :username, allow_nil: true, allow_blank: true
|
||||
validates :username,
|
||||
uniqueness: true,
|
||||
allow_nil: true,
|
||||
allow_blank: true,
|
||||
length: { maximum: 40 },
|
||||
format: /\A[A-Za-z0-9\-_\$\+\!\*]+\z/
|
||||
|
||||
has_many :subscriptions, dependent: :destroy
|
||||
has_many :billing_plans, through: :subscriptions
|
||||
@ -189,6 +194,14 @@ class User < ApplicationRecord
|
||||
found_key.user
|
||||
end
|
||||
|
||||
def profile_url
|
||||
if self.username.present?
|
||||
Rails.application.routes.url_helpers.profile_by_username_path(username: self.username)
|
||||
else
|
||||
Rails.application.routes.url_helpers.user_path(id: self.id)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Attributes that are non-public, and should be blacklisted from any public
|
||||
|
||||
@ -20,7 +20,7 @@ class ContentFormatterService < Service
|
||||
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.
|
||||
formatted_text = markdown.render(text).html_safe
|
||||
formatted_text = markdown.render(text || '').html_safe
|
||||
substitute_content_links(formatted_text, viewing_user).html_safe
|
||||
end
|
||||
|
||||
@ -67,11 +67,11 @@ class ContentFormatterService < Service
|
||||
end
|
||||
|
||||
def self.link_template(content_model)
|
||||
chip_template(content_model.class) { link_to(content_model.name, link_for(content_model), class: "content_link #{content_model.class}-link") }
|
||||
inline_template(content_model.class) { link_to(content_model.name, link_for(content_model), class: "content_link #{content_model.class.name.downcase}-link") }
|
||||
end
|
||||
|
||||
def self.private_link_template(content_model)
|
||||
chip_template(content_model.class) { link_to(content_model.name, link_for(content_model), class: 'grey-text content_link disabled') }
|
||||
inline_template(content_model.class) { link_to(content_model.name, link_for(content_model), class: 'grey-text content_link disabled') }
|
||||
end
|
||||
|
||||
def self.unknown_link_template(attempted_key)
|
||||
@ -95,12 +95,20 @@ class ContentFormatterService < Service
|
||||
end
|
||||
end
|
||||
|
||||
def self.inline_template(class_model=nil)
|
||||
content_tag(:span, class: 'inline-link') do
|
||||
content_tag(:span, class: class_model ? "#{class_model.color}-text" : '') do
|
||||
yield
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# This is a hack until I figure out how to include polymorphic paths in a service model.
|
||||
#todo remove this
|
||||
def self.link_for(content_model)
|
||||
[
|
||||
Rails.env.production? ? 'https://' : 'http://',
|
||||
'www.notebook.ai', # Rails.application.routes.default_url_options[:host],
|
||||
Rails.env.production? ? 'www.notebook.ai' : 'localhost:3000', # Rails.application.routes.default_url_options[:host]?
|
||||
'/plan/',
|
||||
content_model.class.name.downcase.pluralize,
|
||||
'/',
|
||||
|
||||
@ -16,13 +16,13 @@ class PageTagService < Service
|
||||
when Creature.name
|
||||
["Domesticated", "Wild", "Humanoid"]
|
||||
when Deity.name
|
||||
["Good", "Evil"]
|
||||
["Good", "Evil", "Neutral"]
|
||||
when Flora.name
|
||||
["Floral", "Weed"]
|
||||
["Floral", "Weed", "Tree", "Bush"]
|
||||
when Government.name
|
||||
["Republic", "Democracy", "Monarchy", "Dictatorship"]
|
||||
when Group.name
|
||||
["Good", "Evil"]
|
||||
["Good", "Evil", "Secret"]
|
||||
when Item.name
|
||||
["Weapon", "Armor", "Artifact", "Relic"]
|
||||
when Job.name
|
||||
|
||||
@ -13,7 +13,9 @@ class SubscriptionService < Service
|
||||
# Add any one-time referral bonuses
|
||||
add_any_referral_bonuses(user, plan_id)
|
||||
|
||||
user.update(selected_billing_plan_id: related_plan.id)
|
||||
# We intentionally skip callbacks on this to ensure the billing plan changes even on invalid users
|
||||
user.update_column(:selected_billing_plan_id, related_plan.id)
|
||||
|
||||
user.subscriptions.create(
|
||||
billing_plan: related_plan,
|
||||
start_date: DateTime.now,
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<% if alert %>
|
||||
<%= content_for :javascript do %>
|
||||
M.toast({
|
||||
html: "<%= alert.gsub('"', '\"').html_safe %>",
|
||||
html: "<%= alert.gsub('"', '\"').gsub("\n", ' ').html_safe %>",
|
||||
classes: 'rounded'
|
||||
})
|
||||
<% end %>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<% if notice %>
|
||||
<%= content_for :javascript do %>
|
||||
M.toast({
|
||||
html: "<%= notice.gsub('"', '\"').html_safe %>",
|
||||
html: "<%= notice.gsub('"', '\"').gsub("\n", ' ').html_safe %>",
|
||||
classes: 'rounded'
|
||||
})
|
||||
<% end %>
|
||||
|
||||
@ -18,45 +18,6 @@
|
||||
Tip: You don't need to save before changing categories (on the right). Just remember to save when you're done!
|
||||
</div>
|
||||
|
||||
<div id="content-field-link-bar-template">
|
||||
<div class="content-field-link-bar">
|
||||
<div class="btn-group" role="group">
|
||||
<span class="grey-text" style="padding-top: 6px; padding-right: 4px;">
|
||||
Link a page (<a href="#" class="tooltipped" tabindex="-1" data-tooltip="These links will insert a small snippet that<br />automatically turns into a link to that page after saving." data-position="bottom" data-html="true">?</a>):
|
||||
</span>
|
||||
<% @linkables_cache.keys.each do |class_name| %>
|
||||
<% relation_class = class_name.constantize %>
|
||||
<a class="btn btn-small <%= relation_class.color %> dropdown-trigger <%= 'disabled' unless @linkables_cache.fetch(class_name, []).any? %> tooltipped"
|
||||
href="#" style="padding: 0 9px;"
|
||||
data-target='active_link_bar_<%= class_name %>'
|
||||
data-tooltip="<%= class_name.pluralize %>"
|
||||
data-position="bottom"
|
||||
tabindex="-1">
|
||||
<i class="material-icons">
|
||||
<%= relation_class.icon %>
|
||||
</i>
|
||||
</a>
|
||||
|
||||
<ul id='active_link_bar_<%= class_name %>' class='dropdown-content'>
|
||||
<%
|
||||
@linkables_cache[class_name].each do |linkable|
|
||||
%>
|
||||
<li>
|
||||
<a href="#" data-content-type="<%= class_name %>" data-content-id="<%= linkable.second %>" class="js-content-link-option">
|
||||
<i class="material-icons <%= relation_class.color %>-text"><%= relation_class.icon %></i>
|
||||
<%= linkable.first %>
|
||||
</a>
|
||||
</li>
|
||||
<%
|
||||
end
|
||||
%>
|
||||
</ul>
|
||||
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%= content_for :javascript do %>
|
||||
// todo hack this might be fixed with the latest materialize -- we should check
|
||||
$('.field_with_errors').find('textarea, input').addClass('invalid');
|
||||
|
||||
@ -29,8 +29,7 @@
|
||||
asset_path("card-headers/#{content_type_pluralized}.jpg")
|
||||
end
|
||||
%>
|
||||
<div class="activator" style="height: 265px; background: url('<%= category_image %>'); background-size: cover;">
|
||||
</div>
|
||||
<div class="activator" style="height: 265px; background: url('<%= category_image %>'); background-size: cover;"></div>
|
||||
<span class="card-title bordered-text" style="font-size: 200%">
|
||||
<% if defined?(card_title) && !card_title.blank? %>
|
||||
<%= card_title %>
|
||||
|
||||
@ -1,50 +1,96 @@
|
||||
<!-- <div class="row">
|
||||
<div class="col s12 right">
|
||||
<div class="row">
|
||||
<div class="col s12">
|
||||
<div class="filter-bar">
|
||||
<div class="btn-group card">
|
||||
<!-- <div class="btn-group card">
|
||||
<a href="#" class="btn white black-text"><i class="material-icons">view_module</i></a>
|
||||
<a href="#" class="btn white black-text"><i class="material-icons">view_headline</i></a>
|
||||
<a href="#" class="btn white black-text"><i class="material-icons">view_stream</i></a>
|
||||
<a href="#" class="btn white black-text"><i class="material-icons">grid_on</i></a>
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<div class="btn-group card">
|
||||
<a class='dropdown-trigger btn white black-text' href='#' data-target='universe-filter-dropdown'>
|
||||
<i class="material-icons <%= Universe.color %>-text"><%= Universe.icon %></i>
|
||||
</a>
|
||||
<ul id='universe-filter-dropdown' class='dropdown-content'>
|
||||
<li><a href="#!">All Universes</a></li>
|
||||
<li class="divider" tabindex="-1"></li>
|
||||
<% @current_user_content.fetch('Universe', []).each do |universe| %>
|
||||
<% if content_type.new.respond_to?(:universe) %>
|
||||
<div class="btn-group card">
|
||||
<a
|
||||
class='dropdown-trigger btn white black-text tooltipped <%= @universe_scope.present? ? Universe.color : 'white' %> lighten-5'
|
||||
href='#'
|
||||
data-position="bottom"
|
||||
data-delay="500"
|
||||
data-tooltip="Filter by universe"
|
||||
data-target='universe-filter-dropdown'>
|
||||
<i class="material-icons <%= Universe.color %>-text"><%= Universe.icon %></i>
|
||||
<%= @universe_scope.try(:name) if @universe_scope %>
|
||||
</a>
|
||||
<ul id='universe-filter-dropdown' class='dropdown-content'>
|
||||
<li>
|
||||
<a href="#!" data-universe-id="<%= universe.id %>">
|
||||
<a href="?universe=all" class="<%= Universe.color %>-text">
|
||||
<i class="material-icons <%= Universe.color %>-text"><%= Universe.icon %></i>
|
||||
<%= universe.name %>
|
||||
All Universes
|
||||
</a>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</div>
|
||||
<li class="divider" tabindex="-1"></li>
|
||||
<% @current_user_content.fetch('Universe', []).each do |universe| %>
|
||||
<% next unless universe.becomes(Universe).send(content_type.name.downcase.pluralize).any? rescue false %>
|
||||
<li>
|
||||
<a href="?universe=<%= universe.id %>" data-universe-id="<%= universe.id %>" class="<%= Universe.color %>-text">
|
||||
<i class="material-icons <%= Universe.color %>-text"><%= Universe.icon %></i>
|
||||
<span class="grey-text">in</span>
|
||||
<%= universe.name %>
|
||||
</a>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<a class='btn grey lighten-2 black-text tooltipped'
|
||||
href='#'
|
||||
data-position="bottom"
|
||||
data-delay="500"
|
||||
data-tooltip="Tagging pages is a new feature coming soon.">
|
||||
<i class="material-icons red-text">loyalty</i>
|
||||
</a>
|
||||
<% if @page_tags.present? && @page_tags.any? %>
|
||||
<div class="btn-group card">
|
||||
<a
|
||||
class='dropdown-trigger btn <%= @filtered_page_tags.nil? ? 'white' : content_type.color %> lighten-4 black-text tooltipped'
|
||||
href='#'
|
||||
data-target='tag-filter-dropdown'
|
||||
data-position="bottom"
|
||||
data-delay="500"
|
||||
data-tooltip="Filter by tag">
|
||||
<i class="material-icons <%= content_type.color %>-text">loyalty</i>
|
||||
<%= @filtered_page_tags.try(:first).try(:tag) %>
|
||||
</a>
|
||||
<ul id='tag-filter-dropdown' class='dropdown-content'>
|
||||
<li>
|
||||
<%= link_to polymorphic_path(content_type), class: "#{content_type.color}-text" do %>
|
||||
<i class="material-icons">loyalty</i>
|
||||
All <%= content_type.name.downcase.pluralize %>
|
||||
<% end %>
|
||||
</li>
|
||||
<li class="divider" tabindex="-1"></li>
|
||||
<% @page_tags.each do |page_tag| %>
|
||||
<li>
|
||||
<%=
|
||||
link_to send(
|
||||
"page_tag_#{content_type.name.downcase.pluralize}_path",
|
||||
slug: PageTagService.slug_for(page_tag.tag)
|
||||
), class: "#{content_type.color}-text" do
|
||||
%>
|
||||
<i class="material-icons">loyalty</i>
|
||||
<small class="grey-text">tagged</small>
|
||||
<%= page_tag.tag %>
|
||||
<% end %>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<a class='btn grey lighten-2 black-text tooltipped'
|
||||
<!-- <a class='btn grey lighten-2 black-text tooltipped'
|
||||
href='#'
|
||||
data-position="bottom"
|
||||
data-delay="500"
|
||||
data-tooltip="Favoriting pages is a new feature coming soon.">
|
||||
<i class="material-icons amber-text">star_outline</i>
|
||||
</a>
|
||||
</a> -->
|
||||
|
||||
|
||||
|
||||
<div class="btn-group card">
|
||||
<!-- <div class="btn-group card">
|
||||
<a class='btn white grey-text tooltipped'
|
||||
href='#'
|
||||
data-position="bottom"
|
||||
@ -68,15 +114,15 @@
|
||||
data-tooltip="Most-recent order">
|
||||
<i class="material-icons black-text">access_time</i>
|
||||
</a>
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
|
||||
|
||||
<div class="input-field inline" style="position: relative; top: 5px;">
|
||||
<input id="email_inline" type="email" class="validate">
|
||||
<label for="email_inline">Filter by name...</label>
|
||||
<div class="input-field inline" style="position: relative; top: 5px; min-width: 320px">
|
||||
<input id="js-content-name-filter" type="text">
|
||||
<label for="js-content-name-filter">Filter <%= content_type.name.downcase.pluralize %> by name...</label>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
|
||||
@ -28,6 +28,31 @@
|
||||
height: 200,
|
||||
indicators: false
|
||||
});
|
||||
|
||||
var tribute = new Tribute({
|
||||
values: [
|
||||
<% @linkables_cache.each do |class_name, collection| %>
|
||||
<% linkable_class = class_name.constantize %>
|
||||
<% 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 %>
|
||||
],
|
||||
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'));
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@ -36,11 +36,13 @@
|
||||
<%=
|
||||
text_area_tag "#{content_name}[custom_attribute_values][][value]",
|
||||
value,
|
||||
class: "materialize-textarea #{defined?(autocomplete) && autocomplete ? ('autocomplete ' + 'js-autocomplete-' + field[:id].to_s) : ''}",
|
||||
class: "js-can-mention-pages materialize-textarea #{defined?(autocomplete) && autocomplete ? ('autocomplete ' + 'js-autocomplete-' + field[:id].to_s) : ''}",
|
||||
placeholder: placeholder
|
||||
%>
|
||||
|
||||
<div class="content-field-link-bar-container"></div>
|
||||
<p class="grey-text show-when-focused">
|
||||
Tip: You can press @ to insert a link to your other pages.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@ -6,33 +6,10 @@
|
||||
<%= render partial: 'content/components/parallax_header', locals: { content_type: content_type, content_class: @content_type_class } %>
|
||||
<% end %>
|
||||
|
||||
|
||||
<% if @page_tags.any? %>
|
||||
<div class="tags-container">
|
||||
<span class="left grey-text">View by tag: </span>
|
||||
<%= link_to polymorphic_path(@content_type_class) do %>
|
||||
<span class="new badge <%= params.key?(:slug) ? 'grey' : @content_type_class.color %> left" data-badge-caption="All <%= content_type.pluralize %>"></span>
|
||||
<% end %>
|
||||
<% @page_tags.uniq(&:tag).each do |tag| %>
|
||||
<%=
|
||||
link_to send(
|
||||
"page_tag_#{content_type.pluralize}_path",
|
||||
slug: PageTagService.slug_for(tag.tag)
|
||||
) do
|
||||
%>
|
||||
<span class="new badge <%= params[:slug] == tag.slug ? @content_type_class.color : 'grey' %> left" data-badge-caption="<%= tag.tag %>"></span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
<div style="clear: both"></div>
|
||||
<% end %>
|
||||
|
||||
<% if @content.any? %>
|
||||
|
||||
<%= render partial: 'content/components/list_filter_bar', locals: { } %>
|
||||
<div class="card">
|
||||
<%= render partial: 'content/list/list', locals: { content_list: @content, content_type: @content_type_class, show_add_another_form: true } %>
|
||||
</div>
|
||||
<%= render partial: 'content/components/list_filter_bar', locals: { content_type: @content_type_class } %>
|
||||
<%= render partial: 'content/list/cards', locals: { content_list: @content, content_type: @content_type_class, show_add_another_form: true } %>
|
||||
|
||||
<% elsif @content.empty? %>
|
||||
|
||||
|
||||
109
app/views/content/list/_cards.html.erb
Normal file
109
app/views/content/list/_cards.html.erb
Normal file
@ -0,0 +1,109 @@
|
||||
<div class="row js-content-cards-list">
|
||||
<% content_list.each.with_index do |content, i| %>
|
||||
<div class="col s12 m6 l4 js-content-card-container">
|
||||
<div class="card sticky-action">
|
||||
<div class="card-image waves-effect waves-block waves-light">
|
||||
<% content_image = asset_path("card-headers/#{content_type.name.downcase.pluralize}.jpg") %>
|
||||
<% if content.respond_to?(:image_uploads) %>
|
||||
<% images = content.image_uploads %>
|
||||
<% if images.any? %>
|
||||
<% content_image = images.sample.src(:medium) %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<div class="activator" style="height: 265px; background: url('<%= content_image %>'); background-size: cover;"></div>
|
||||
</div>
|
||||
<div class="card-content fixed-card-content" style="overflow: hidden">
|
||||
<span class="card-title js-content-name activator grey-text text-darken-4">
|
||||
<%= content.name.presence || 'Untitled' %>
|
||||
</span>
|
||||
<p>
|
||||
<% if content.is_a?(Document) %>
|
||||
<%= content.reading_estimate %>
|
||||
<% else %>
|
||||
<%= sanitize ContentFormatterService.show(text: truncate(content.description, length: 140), viewing_user: current_user) %>
|
||||
<% end %>
|
||||
</p>
|
||||
</div>
|
||||
<div class="card-action nice-icon-links">
|
||||
<%= link_to edit_polymorphic_path(content), class: 'green-text right', target: content.is_a?(Document) ? '_new' : '_self' do %>
|
||||
<i class="material-icons"><%= content_type.icon %></i> Edit
|
||||
<% end %>
|
||||
<%= link_to polymorphic_path(content), class: 'blue-text text-lighten-1' do %>
|
||||
<i class="material-icons"><%= content_type.icon %></i> View
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="card-reveal">
|
||||
<span class="card-title grey-text text-darken-4">
|
||||
<%= content.name.presence || 'Document preview' %>
|
||||
<i class="material-icons right">close</i>
|
||||
</span>
|
||||
<% if content.is_a?(Document) || (content.description.try(:length) || 0 > 140) %>
|
||||
<p>
|
||||
<%= sanitize ContentFormatterService.show(text: truncate(content.description, length: 420, escape: false), viewing_user: current_user) %>
|
||||
</p>
|
||||
<% end %>
|
||||
<% if content.respond_to?(:page_tags) %>
|
||||
<p class="tags-container">
|
||||
<% content.page_tags.each do |tag| %>
|
||||
<% if user_signed_in? && content.user == current_user %>
|
||||
<%=
|
||||
link_to send(
|
||||
"page_tag_#{content.class.name.downcase.pluralize}_path",
|
||||
slug: PageTagService.slug_for(tag.tag)
|
||||
) do
|
||||
%>
|
||||
<span class="new badge <%= params[:slug] == tag.slug ? content_type.color : 'grey' %> left" data-badge-caption="<%= tag.tag %>"></span>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<span class="new badge <%= params[:slug] == tag.slug ? content_type.color : 'grey' %> left" data-badge-caption="<%= tag.tag %>"></span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</p>
|
||||
<% end %>
|
||||
<div class="clearfix"></div>
|
||||
<p class="grey-text clearfix">
|
||||
<%= content.created_at == content.updated_at ? 'created' : 'last updated' %>
|
||||
<%= time_ago_in_words content.updated_at %>
|
||||
ago
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<% if current_user.can_create?(content_type) %>
|
||||
<div class="col s12 m6 l4">
|
||||
<%# TODO: We really need to fix up the new document path so we don't have weird overrides like this %>
|
||||
<%= link_to content_type == Document ? edit_document_path(:new) : new_polymorphic_path(content_type), class: 'white-text' do %>
|
||||
<div class="card <%= content_type.color %> lighten-1" style="height: 440px;">
|
||||
<div class="card-content fixed-card-content centered-card-content">
|
||||
<strong class="card-title">
|
||||
New<br />
|
||||
<%= content_type.name %>
|
||||
</strong>
|
||||
<p>
|
||||
<i class="material-icons large">add</i>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="col s12 m6 l4">
|
||||
<div class="card <%= content_type.color %> lighten-3" style="height: 440px;">
|
||||
<div class="card-content fixed-card-content">
|
||||
<p class="center grey-text text-lighten-1" style="margin-top: 50px; margin-bottom: 20px;">
|
||||
<i class="material-icons large"><%= content_type.icon %></i>
|
||||
</p>
|
||||
<p>
|
||||
An active
|
||||
<%= link_to 'Notebook.ai Premium', subscription_path, class: 'blue-text text-darken-2' %> subscription
|
||||
is required to create additional
|
||||
<%= content_type.name.downcase %> pages, but pages you've already created will always
|
||||
be available here.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
@ -35,6 +35,7 @@
|
||||
You're on a Premium plan! <strong>All</strong> pages are available to you!
|
||||
<% else %>
|
||||
You're on a free Starter plan. You can create unlimited Characters, Locations, and Items. You can also create up to 5 Universes.
|
||||
<%= link_to 'Click here to browse the available Premium plans.', subscription_path %>
|
||||
<% end %>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %>
|
||||
<%= devise_error_messages! %>
|
||||
<div class="red lighten-4">
|
||||
<%= devise_error_messages! %>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col s12">
|
||||
|
||||
@ -16,6 +16,8 @@
|
||||
<div class="field">
|
||||
<%= f.label 'Username (users can @mention you with your username on the forums)' %><br />
|
||||
<%= f.text_field :username %>
|
||||
<small class="help-text">Your Notebook.ai profile will be available at https://www.notebook.ai/@username.</small><br />
|
||||
<small class="help-text">Up to 40 numbers, letters, and/or the following symbols are allowed: <strong>- _ $ + ! *</strong></small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
|
||||
<div class="row">
|
||||
<div class="col s12 m8 l8 offset-l1">
|
||||
<div id="editor" class="editable"><%= @document.body.try(:html_safe) %></div>
|
||||
<div id="editor" class="editable js-can-mention-pages"><%= @document.body.try(:html_safe) %></div>
|
||||
</div>
|
||||
<div class="col s12 m4 l2 smart-sidebar">
|
||||
<%= render partial: 'documents/components/smart_sidebar', locals: { document: @document } %>
|
||||
@ -27,7 +27,6 @@
|
||||
|
||||
<div style="clear: both"></div>
|
||||
<%= content_for :javascript do %>
|
||||
|
||||
var editor = new MediumEditor('#editor', {
|
||||
targetBlank: true,
|
||||
autoLink: true,
|
||||
@ -169,4 +168,31 @@
|
||||
}
|
||||
});
|
||||
|
||||
$(document).ready(function(){
|
||||
// Page @mentions
|
||||
var tribute = new Tribute({
|
||||
values: [
|
||||
<% @linkables_cache.each do |class_name, collection| %>
|
||||
<% linkable_class = class_name.constantize %>
|
||||
<% 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 %>
|
||||
],
|
||||
selectTemplate: function (item) {
|
||||
// We're overriding the default here so we don't prepend a @
|
||||
return '<span class="' + item.original.color + '-text" contenteditable="false">' + item.original.value + '</span>';
|
||||
},
|
||||
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 %>
|
||||
|
||||
@ -1,49 +1,6 @@
|
||||
<% if @documents.any? %>
|
||||
<%= render partial: 'content/components/list_filter_bar', locals: { } %>
|
||||
|
||||
<div class="row">
|
||||
<% @documents.each.with_index do |document, i| %>
|
||||
<% if i % 3 == 0 %>
|
||||
<div style="clear: both;" class="hide-on-small-only"></div>
|
||||
<% end %>
|
||||
<div class="col s12 m4 l4">
|
||||
<div class="card sticky-action">
|
||||
<div class="card-image waves-effect waves-block waves-light">
|
||||
<%= image_tag "card-headers/document-mini.jpg", class: 'activator' %>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<span class="card-title activator grey-text text-darken-4">
|
||||
<%= document.title.presence || 'Untitled document' %>
|
||||
</span>
|
||||
<p class="grey-text">
|
||||
~<%= pluralize 1 + ((document.body || "").split(/\s+/).count / 200).to_i, 'minute' %> read
|
||||
<br />
|
||||
<%= document.created_at == document.updated_at ? 'created' : 'updated' %>
|
||||
<%= time_ago_in_words document.updated_at %>
|
||||
ago
|
||||
</p>
|
||||
</div>
|
||||
<div class="card-action nice-icon-links">
|
||||
<%= link_to edit_document_path(document), class: 'green-text right', target: '_new' do %>
|
||||
<i class="material-icons"><%= Document.icon %></i> Edit
|
||||
<% end %>
|
||||
<%= link_to document_path(document), class: 'blue-text text-lighten-1' do %>
|
||||
<i class="material-icons"><%= Document.icon %></i> View
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="card-reveal">
|
||||
<span class="card-title grey-text text-darken-4">
|
||||
<%= document.title.presence || 'Document preview' %>
|
||||
<i class="material-icons right">close</i>
|
||||
</span>
|
||||
<p>
|
||||
<%= sanitize truncate(document.body || '<em>This document is blank.</em>', escape: false, length: 420) %>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<%= render partial: 'content/components/list_filter_bar', locals: { content_type: Document } %>
|
||||
<%= render partial: 'content/list/cards', locals: { content_list: @documents, content_type: Document } %>
|
||||
<% end %>
|
||||
|
||||
<% if @documents.empty? %>
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
|
||||
<div class="row">
|
||||
<div class="col s12 m8 l8 offset-l1">
|
||||
<div id="editor" class="editable"><%= @document.body.try(:html_safe) || 'This document is blank.' %></div>
|
||||
<div id="editor"><%= ContentFormatterService.substitute_content_links(@document.body.try(:html_safe) || "", current_user).html_safe %></div>
|
||||
</div>
|
||||
<div class="col l2 m4 smart-sidebar">
|
||||
<%= render partial: 'documents/components/smart_sidebar', locals: { document: @document } %>
|
||||
|
||||
@ -135,7 +135,7 @@
|
||||
<div class="collapsible-body">
|
||||
<ul>
|
||||
<li>
|
||||
<%= link_to current_user, class: 'waves-effect' do %>
|
||||
<%= link_to current_user.profile_url, class: 'waves-effect' do %>
|
||||
<i class="material-icons left green-text">person</i>
|
||||
Profile
|
||||
<% end %>
|
||||
|
||||
@ -40,7 +40,7 @@
|
||||
</div>
|
||||
|
||||
<div class="col s12 m3">
|
||||
<%= link_to current_user do %>
|
||||
<%= link_to current_user.profile_url do %>
|
||||
<div class="hoverable card green">
|
||||
<div class="card-content white-text">
|
||||
<span class="card-title">
|
||||
|
||||
@ -277,7 +277,7 @@
|
||||
Of these, <strong><%= pluralize converted_referrals.count, 'user' %></strong> <%= converted_referrals.count == 1 ? 'has' : 'have' %> earned you both a Premium bonus!
|
||||
</li>
|
||||
<% else %>
|
||||
<li>None have signed up for Premium yet, however.</li>
|
||||
<li>None have signed up for Premium yet. You receive a permanent 100MB boost to your upload storage whenever someone you refers upgrades to Premium. And they get a 100MB boost, too!</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
20
app/views/thredded/posts/_post.html.erb
Normal file
20
app/views/thredded/posts/_post.html.erb
Normal file
@ -0,0 +1,20 @@
|
||||
<% post, content = post_and_content if local_assigns.key?(:post_and_content) %>
|
||||
|
||||
<%
|
||||
muted_post = post.present? && post.to_model.content.split("\n").reject(&:empty?).all? { |paragraph| paragraph.strip.start_with?('(') && paragraph.strip.end_with?(')') }
|
||||
muted_post_classes = 'grey lighten-4 grey-text text-darken-2'
|
||||
%>
|
||||
|
||||
<%= 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 %>
|
||||
<%= render 'thredded/posts_common/actions', post: post, actions: local_assigns[:actions] %>
|
||||
<%= render 'thredded/posts_common/header', post: post %>
|
||||
<%= content || render('thredded/posts/content', post: post) %>
|
||||
<% if post.pending_moderation? && !Thredded.content_visible_while_pending_moderation %>
|
||||
<p class="thredded--alert thredded--alert-warning"><%= t 'thredded.posts.pending_moderation_notice' %></p>
|
||||
<% elsif post.blocked? && post.can_moderate? %>
|
||||
<p class="thredded--alert thredded--alert-danger">
|
||||
<%= render 'thredded/shared/content_moderation_blocked_state', moderation_record: post.last_moderation_record %>
|
||||
</p>
|
||||
<% end %>
|
||||
<% end %>
|
||||
@ -28,7 +28,7 @@
|
||||
Username
|
||||
</div>
|
||||
<div>
|
||||
<%= link_to "@#{@user.username}", thredded_path %>
|
||||
<%= link_to "@#{@user.username}", profile_by_username_path(username: @user.username) %>
|
||||
</div>
|
||||
<br />
|
||||
<% end %>
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
<h4 class="flow-text">
|
||||
<%= @user.name %>
|
||||
|
||||
<%= render partial: 'thredded/users/badge', locals: { user: @user } %>
|
||||
</h4>
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
</div>
|
||||
<% @tabs.each do |tab| %>
|
||||
<% content_type_class = @user.send(tab).build.class %>
|
||||
<%= link_to send("#{tab}_user_path"), class: "collection-item #{content_type_class.color}-text" do %>
|
||||
<%= link_to send("#{tab}_user_path", { id: @user.id }), class: "collection-item #{content_type_class.color}-text" do %>
|
||||
<%= pluralize @content[tab].length, tab.to_s.singularize %>
|
||||
<span class="secondary-content">
|
||||
<i class="material-icons <%= content_type_class.color %>-text"><%= content_type_class.icon %></i>
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
image_src: @user.image_url(120)
|
||||
|
||||
content_jsonld = {
|
||||
'@id': user_url,
|
||||
'@id': user_url(id: @user.id),
|
||||
'@type': 'http://schema.org/Person',
|
||||
'http://schema.org/name': @user.name,
|
||||
'http://schema.org/description': "#{@user.name}’s profile on notebook.ai",
|
||||
|
||||
@ -13,6 +13,8 @@ Rails.application.routes.draw do
|
||||
# todo page tags here
|
||||
end
|
||||
end
|
||||
get '/@:username', to: 'users#show', as: :profile_by_username
|
||||
|
||||
scope '/my' do
|
||||
get '/content', to: 'main#dashboard', as: :dashboard
|
||||
get '/content/recent', to: 'main#recent_content', as: :recent_content
|
||||
|
||||
@ -48,6 +48,36 @@ RSpec.describe SubscriptionsController, type: :controller do
|
||||
@user.update(stripe_customer_id: 'stripe-id')
|
||||
sign_in @user
|
||||
|
||||
@free_plan = BillingPlan.create(
|
||||
name: 'Starter',
|
||||
stripe_plan_id: 'starter',
|
||||
monthly_cents: 0, # $0.00/mo
|
||||
available: true,
|
||||
|
||||
# Content creation and other permissions:
|
||||
universe_limit: 5,
|
||||
allows_core_content: true,
|
||||
allows_extended_content: false,
|
||||
allows_collective_content: false,
|
||||
allows_collaboration: false,
|
||||
bonus_bandwidth_kb: 123155
|
||||
)
|
||||
|
||||
@beta_plan = BillingPlan.create(
|
||||
name: 'Early Adopters',
|
||||
stripe_plan_id: 'early-adopters',
|
||||
monthly_cents: 0, # $0.00/mo
|
||||
available: true,
|
||||
|
||||
# Content creation and other permissions:
|
||||
universe_limit: 5,
|
||||
allows_core_content: true,
|
||||
allows_extended_content: false,
|
||||
allows_collective_content: false,
|
||||
allows_collaboration: false,
|
||||
bonus_bandwidth_kb: 123155
|
||||
)
|
||||
|
||||
@premium_plan = BillingPlan.create(
|
||||
name: 'Premium',
|
||||
stripe_plan_id: 'premium',
|
||||
@ -73,21 +103,6 @@ RSpec.describe SubscriptionsController, type: :controller do
|
||||
allows_collaboration: true,
|
||||
bonus_bandwidth_kb: 123155
|
||||
)
|
||||
|
||||
@free_plan = BillingPlan.create(
|
||||
name: 'Starter',
|
||||
stripe_plan_id: 'starter',
|
||||
monthly_cents: 0, # $0.00/mo
|
||||
available: true,
|
||||
|
||||
# Content creation and other permissions:
|
||||
universe_limit: 5,
|
||||
allows_core_content: true,
|
||||
allows_extended_content: false,
|
||||
allows_collective_content: false,
|
||||
allows_collaboration: false,
|
||||
bonus_bandwidth_kb: 123155
|
||||
)
|
||||
end
|
||||
|
||||
describe "User with no plan (fallback to Starter) tries to upgrade" do
|
||||
@ -101,7 +116,6 @@ RSpec.describe SubscriptionsController, type: :controller do
|
||||
describe "User on Starter" do
|
||||
before do
|
||||
# Create a Starter subscription for the user
|
||||
@user.active_subscriptions.create(billing_plan: @free_plan, start_date: Time.now - 5.days, end_date: Time.now + 5.days)
|
||||
@user.update(selected_billing_plan_id: @free_plan.id)
|
||||
end
|
||||
|
||||
@ -169,12 +183,10 @@ RSpec.describe SubscriptionsController, type: :controller do
|
||||
describe "User on Premium" do
|
||||
before do
|
||||
# Create a premium subscription for the user
|
||||
@user.active_subscriptions.create(billing_plan: @premium_plan, start_date: Time.now - 5.days, end_date: Time.now + 5.days)
|
||||
expect(@user.active_subscriptions.map(&:billing_plan_id)).to eq([@premium_plan.id])
|
||||
@user.update(selected_billing_plan_id: BillingPlan.find_by(stripe_plan_id: 'premium').id)
|
||||
end
|
||||
|
||||
it "allows downgrading to Starter" do
|
||||
|
||||
# Downgrade to Starter
|
||||
post :change, params: { stripe_plan_id: 'starter' }
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user