diff --git a/app/assets/stylesheets/article-show.scss b/app/assets/stylesheets/article-show.scss
index b5f20a03b..d1f33fa9b 100644
--- a/app/assets/stylesheets/article-show.scss
+++ b/app/assets/stylesheets/article-show.scss
@@ -247,13 +247,13 @@ header{
.article-collection-wrapper{
text-align:center;
font-family: $helvetica;
- font-size:15px;
+ font-size:16px;
p{
- border-bottom:2px solid $black;
- width:120px;
+ max-width:90%;
margin:35px auto 15px;
padding:2px 0px;
font-weight:bold;
+ font-style: oblique;
}
.article-collection{
display:inline-block;
@@ -265,19 +265,19 @@ header{
margin-bottom: 1.1vw;
a{
color:$black;
- padding:calc(0.8vw + 10px);
- background:$lightest-gray;
+ padding:calc(0.3vw + 14px);
+ background:darken($lightest-gray, 12%);
display:inline-block;
position:relative;
z-index:4;
max-width:260px;
margin:0.2vw;
- border-radius:5px;
+ border-radius:100px;
&:first-child{
// margin-left:-5px;
}
&:hover{
- background:darken($lightest-gray,3%);
+ background:darken($lightest-gray,18%);
}
&.current-article{
background: $black;
diff --git a/app/assets/stylesheets/preact/article-form.scss b/app/assets/stylesheets/preact/article-form.scss
index 049416338..f7b3e6cc9 100644
--- a/app/assets/stylesheets/preact/article-form.scss
+++ b/app/assets/stylesheets/preact/article-form.scss
@@ -371,6 +371,10 @@
@media screen and ( min-width: 600px ){
width: calc(100% - 40px);
}
+ button {
+ margin-left: 5px;
+ background: $bold-blue;
+ }
}
.articleform__exitbutton{
position:absolute;
@@ -392,6 +396,15 @@
border-radius: 3px;
font-weight: bold;
}
+ .articleform__donebutton{
+ display: block;
+ font-size: 22px;
+ background: $green;
+ color: $black;
+ padding: 10px 40px;
+ margin: 20px auto;
+
+ }
img{
display: block;
margin: 10px auto;
diff --git a/app/controllers/api/v0/articles_controller.rb b/app/controllers/api/v0/articles_controller.rb
index 804c5a7e1..352fb0b78 100644
--- a/app/controllers/api/v0/articles_controller.rb
+++ b/app/controllers/api/v0/articles_controller.rb
@@ -68,9 +68,12 @@ module Api
else
params["article"]["organization_id"] = nil
end
+ if params["article"]["series"].present?
+ params["article"]["collection_id"] = Collection.find_series(params["article"]["series"], current_user)&.id
+ end
params.require(:article).permit(
:title, :body_markdown, :user_id, :main_image, :published, :description,
- :tag_list, :organization_id, :canonical_url
+ :tag_list, :organization_id, :canonical_url, :series, :collection_id
)
end
end
diff --git a/app/controllers/articles_controller.rb b/app/controllers/articles_controller.rb
index 6728259c8..139a3340d 100644
--- a/app/controllers/articles_controller.rb
+++ b/app/controllers/articles_controller.rb
@@ -45,19 +45,19 @@ class ArticlesController < ApplicationController
@article = if @tag.present? && @user&.editor_version == "v2"
authorize Article
Article.new(body_markdown: "", cached_tag_list: @tag.name,
- processed_html: "")
+ processed_html: "", user_id: current_user&.id)
elsif @tag&.submission_template.present? && @user
authorize Article
Article.new(body_markdown: @tag.submission_template_customized(@user.name),
- processed_html: "")
+ processed_html: "", user_id: current_user&.id)
else
skip_authorization
if @user&.editor_version == "v2"
- Article.new
+ Article.new(user_id: current_user&.id)
else
Article.new(
body_markdown: "---\ntitle: \npublished: false\ndescription: \ntags: \n---\n\n",
- processed_html: "",
+ processed_html: "", user_id: current_user&.id
)
end
end
diff --git a/app/javascript/article-form/articleForm.jsx b/app/javascript/article-form/articleForm.jsx
index ab677cc93..bad332bf5 100644
--- a/app/javascript/article-form/articleForm.jsx
+++ b/app/javascript/article-form/articleForm.jsx
@@ -33,6 +33,8 @@ export default class ArticleForm extends Component {
tagList: article.cached_tag_list || '',
description: '',
canonicalUrl: article.canonical_url || '',
+ series: article.series || '',
+ allSeries: article.all_series || [],
bodyMarkdown: article.body_markdown || '',
published: article.published || false,
previewShowing: false,
@@ -118,6 +120,7 @@ export default class ArticleForm extends Component {
};
handleConfigChange = e => {
+ e.preventDefault();
let newState = {}
newState[e.target.name] = e.target.value;
this.setState(newState)
diff --git a/app/javascript/article-form/elements/imageManagement.jsx b/app/javascript/article-form/elements/imageManagement.jsx
index 8c4ee28ce..116b8a342 100644
--- a/app/javascript/article-form/elements/imageManagement.jsx
+++ b/app/javascript/article-form/elements/imageManagement.jsx
@@ -72,6 +72,7 @@ export default class ImageManagement extends Component {
{mainImageArea}
Body Images
{inertionImageArea}
+
}
diff --git a/app/javascript/article-form/elements/moreConfig.jsx b/app/javascript/article-form/elements/moreConfig.jsx
index b47385d41..1c321e24c 100644
--- a/app/javascript/article-form/elements/moreConfig.jsx
+++ b/app/javascript/article-form/elements/moreConfig.jsx
@@ -11,9 +11,21 @@ export default class MoreConfig extends Component {
// this.state = {insertionImageUrl: null}
}
+ handleSeriesButtonClick = e => {
+ e.preventDefault();
+ this.props.onConfigChange(e);
+ }
+
render() {
const { onExit, passedData, onSaveDraft } = this.props;
let publishedField = '';
+ let seriesTip = Will this post be part of a series? Give the series a unique name. (Series visible once it has multiple posts)
+ if (passedData.allSeries.length > 0) {
+ const seriesNames = passedData.allSeries.map( name => {
+ return
+ })
+ seriesTip = Existing series: {seriesNames}
+ }
if (passedData.published) {
publishedField =
Danger Zone
@@ -35,6 +47,12 @@ export default class MoreConfig extends Component {
Change meta tag canonical_url if this post was first published elsewhere (like your own blog)
+
+
+
+
+ {seriesTip}
+
{publishedField}
}
diff --git a/app/models/article.rb b/app/models/article.rb
index 2002e8f03..bd414c445 100644
--- a/app/models/article.rb
+++ b/app/models/article.rb
@@ -6,7 +6,7 @@ class Article < ApplicationRecord
acts_as_taggable_on :tags
- attr_accessor :publish_under_org
+ attr_accessor :publish_under_org, :series
belongs_to :user
belongs_to :job_opportunity, optional: true
@@ -31,6 +31,7 @@ class Article < ApplicationRecord
validates :body_markdown, uniqueness: { scope: %i[user_id title] }
validate :validate_tag
validate :validate_video
+ validate :validate_collection_permission
validates :video_state, inclusion: { in: %w(PROGRESSING COMPLETED) }, allow_nil: true
validates :cached_tag_list, length: { maximum: 86 }
validates :main_image, url: { allow_blank: true, schemes: ["https", "http"] }
@@ -359,6 +360,16 @@ class Article < ApplicationRecord
end
handle_asynchronously :async_score_calc
+ def series
+ # name of series article is part of
+ collection&.slug
+ end
+
+ def all_series
+ # all series names
+ user&.collections&.pluck(:slug)
+ end
+
private
# def send_to_moderator
@@ -387,6 +398,7 @@ class Article < ApplicationRecord
self.main_image = front_matter["cover_image"] if front_matter["cover_image"].present?
self.canonical_url = front_matter["canonical_url"] if front_matter["canonical_url"].present?
self.description = front_matter["description"] || token_msg
+ self.collection_id = Collection.find_series(front_matter["series"], user).id if front_matter["series"].present?
if front_matter["automatically_renew"].present? && tag_list.include?("hiring")
self.automatically_renew = front_matter["automatically_renew"]
end
@@ -418,6 +430,12 @@ class Article < ApplicationRecord
end
end
+ def validate_collection_permission
+ if collection && collection.user_id != user_id
+ errors.add(:collection_id, "must be one you have permission to post to")
+ end
+ end
+
def create_slug
if slug.blank? && title.present? && !published
self.slug = title_to_slug + "-temp-slug-#{rand(10_000_000)}"
diff --git a/app/models/collection.rb b/app/models/collection.rb
index 34b5b5f80..6751fa471 100644
--- a/app/models/collection.rb
+++ b/app/models/collection.rb
@@ -2,4 +2,12 @@ class Collection < ApplicationRecord
has_many :articles
belongs_to :user, optional: true
belongs_to :organization, optional: true
+
+ validates :user_id, presence: true
+ validates :slug, uniqueness: { scope: :user_id }
+
+ def self.find_series(slug, user)
+ series = Collection.where(slug: slug, user: user).first
+ series || Collection.create(slug: slug, user: user)
+ end
end
diff --git a/app/services/article_creation_service.rb b/app/services/article_creation_service.rb
index ec0d3ed0e..7010c32f8 100644
--- a/app/services/article_creation_service.rb
+++ b/app/services/article_creation_service.rb
@@ -15,6 +15,7 @@ class ArticleCreationService
if user.organization_id.present? && article_params[:publish_under_org].to_i == 1
article.organization_id = user.organization_id
end
+ article.collection_id = Collection.find_series(article_params[:series], user).id if article_params[:series].present?
create_job_opportunity(article)
if article.save
if article.published
diff --git a/app/views/articles/_collection.html.erb b/app/views/articles/_collection.html.erb
index 6488fd19b..0f2c02cb1 100644
--- a/app/views/articles/_collection.html.erb
+++ b/app/views/articles/_collection.html.erb
@@ -1,10 +1,13 @@
-
-
-<% if @article.collection.present? && @article.collection.articles.size > 1 %>
+<% @collection = @article.collection %>
+<% if @collection.present? && @collection.articles.where(published: true).size > 1 %>
-
Part of a series
+ <% if @collection.slug.present? %>
+
Part of "<%= @collection.slug %>" series
+ <% else %>
+
Part of a series
+ <% end %>
- <% @article.collection.articles.order("collection_position ASC").each_with_index do |article,i| %>
+ <% @collection.articles.where(published: true).order("published_at ASC").each_with_index do |article,i| %>
"
data-organization="<%= @organization&.to_json(only: [:name, :bg_color_hex, :text_color_hex], methods: [:profile_image_90]) %>"
>
diff --git a/app/views/articles/show.html.erb b/app/views/articles/show.html.erb
index fe17df223..fc92ebba9 100644
--- a/app/views/articles/show.html.erb
+++ b/app/views/articles/show.html.erb
@@ -164,7 +164,9 @@
<%= @article.processed_html.html_safe %>
- <%= render "articles/collection", position: "bottom" %>
+ <% if @article.body_markdown && @article.body_markdown.size > 900 %>
+ <%= render "articles/collection", position: "bottom" %>
+ <% end %>
<%= render 'articles/actions' %>
<% if @article.body_markdown && @article.body_markdown.size > 900 %>
diff --git a/lib/tasks/fetch.rake b/lib/tasks/fetch.rake
index 291dca77e..7605f39b6 100644
--- a/lib/tasks/fetch.rake
+++ b/lib/tasks/fetch.rake
@@ -96,7 +96,8 @@ task remove_old_html_variant_data: :environment do
HtmlVariantTrial.where("created_at < ?", 1.week.ago).destroy_all
HtmlVariantSuccess.where("created_at < ?", 1.week.ago).destroy_all
HtmlVariant.find_each do |html_variant|
- return if html_variant.html_variant_successes.size < 12
+ if html_variant.html_variant_successes.size > 3
html_variant.calculate_success_rate!
+ end
end
end
diff --git a/spec/models/article_spec.rb b/spec/models/article_spec.rb
index 5407331f7..3da93cab3 100644
--- a/spec/models/article_spec.rb
+++ b/spec/models/article_spec.rb
@@ -473,5 +473,17 @@ RSpec.describe Article, type: :model do
last_year = 1.year.ago.year % 100
expect(article.readable_publish_date.include?("'#{last_year}")).to eq(true)
end
+
+ it "is valid as part of a collection" do
+ collection = Collection.create(user_id: article.user.id, slug: "yoyoyo")
+ article.collection_id = collection.id
+ expect(article).to be_valid
+ end
+
+ it "is not valid as part of a collection that does not belong to user" do
+ collection = Collection.create(user_id: 32443, slug: "yoyoyo")
+ article.collection_id = collection.id
+ expect(article).not_to be_valid
+ end
end
# rubocop:enable RSpec/ExampleLength, RSpec/MultipleExpectations
diff --git a/spec/requests/articles_api_spec.rb b/spec/requests/articles_api_spec.rb
index bac52abcf..66f36d4a3 100644
--- a/spec/requests/articles_api_spec.rb
+++ b/spec/requests/articles_api_spec.rb
@@ -67,4 +67,70 @@ RSpec.describe "ArticlesApi", type: :request do
expect(JSON.parse(response.body)["title"]).to eq(article.title)
end
end
+
+ describe "POST /api/articles w/ current_user" do
+ before do
+ sign_in user1
+ end
+ it "creates ordinary article with proper params" do
+ new_title = "NEW TITLE #{rand(100)}"
+ post "/api/articles", params: {
+ article: { title: new_title, body_markdown: "Yo ho ho#{rand(100)}", tag_list: "yo" }
+ }
+ expect(Article.last.user_id).to eq(user1.id)
+ end
+ # rubocop:disable RSpec/ExampleLength
+ it "creates article with front matter params" do
+ post "/api/articles", params: {
+ article: {
+ body_markdown: "---\ntitle: hey hey hahuu\npublished: false\n---\nYo ho ho#{rand(100)}",
+ tag_list: "yo"
+ }
+ }
+ expect(Article.last.title).to eq("hey hey hahuu")
+ end
+ it "creates article w/ series param" do
+ new_title = "NEW TITLE #{rand(100)}"
+ post "/api/articles", params: {
+ article: { title: new_title,
+ body_markdown: "Yo ho ho#{rand(100)}",
+ tag_list: "yo",
+ series: "helloyo",
+ }
+ }
+ expect(Article.last.collection).to eq(Collection.find_by_slug("helloyo"))
+ expect(Article.last.collection.user_id).to eq(Article.last.user_id)
+ end
+ it "creates article with front matter params" do
+ post "/api/articles", params: {
+ article: {
+ body_markdown: "---\ntitle: hey hey hahuu\npublished: false\nseries: helloyo\n---\nYo ho ho#{rand(100)}",
+ tag_list: "yo"
+ }
+ }
+ expect(Article.last.collection).to eq(Collection.find_by_slug("helloyo"))
+ expect(Article.last.collection.user_id).to eq(Article.last.user_id)
+ end
+ end
+
+ describe "PUT /api/articles/:id w/ current_user" do
+ before do
+ sign_in user1
+ @article = create(:article, user: user1)
+ end
+ it "updates ordinary article with proper params" do
+ new_title = "NEW TITLE #{rand(100)}"
+ put "/api/articles/#{@article.id}", params: {
+ article: { title: new_title, body_markdown: "Yo ho ho#{rand(100)}", tag_list: "yo" }
+ }
+ expect(Article.last.title).to eq(new_title)
+ end
+ it "updates ordinary article with proper params" do
+ new_title = "NEW TITLE #{rand(100)}"
+ put "/api/articles/#{@article.id}", params: {
+ article: { title: new_title, body_markdown: "Yo ho ho#{rand(100)}", tag_list: "yo" }
+ }
+ expect(Article.last.title).to eq(new_title)
+ end
+ end
end
diff --git a/spec/requests/articles_create_spec.rb b/spec/requests/articles_create_spec.rb
index 4df62ffe8..8ff304649 100644
--- a/spec/requests/articles_create_spec.rb
+++ b/spec/requests/articles_create_spec.rb
@@ -50,5 +50,16 @@ RSpec.describe "ArticlesCreate", type: :request do
}
expect(Article.last.job_opportunity.remoteness).to eq("on_premise")
end
+
+ it "creates series when series is created with frontmatter" do
+ new_title = "NEW TITLE #{rand(100)}"
+ post "/articles", params: {
+ article: {
+ title: new_title,
+ body_markdown: "---\ntitle: hey hey hahuu\npublished: false\nseries: helloyo\n---\nYo ho ho#{rand(100)}",
+ }
+ }
+ expect(Collection.last.slug).to eq("helloyo")
+ end
# rubocop:enable RSpec/ExampleLength
end