Add Algolia to comments, modify button styles, add comments tab to reading list area (#32)

* Add Algolia to search and add Only My Posts filter

* Actually fix Algolia index possible issue

* Fix search issue

* Make minor adjustments to sponsorship sidebar

* Make submission rules headsup html allowed and remove devise trackable

* Remove devise_trackable from application_controller

* Adjust login CTA for /new

* Adjust string in test to reflect changes

* Quick fix for internal navigatioon draft caching issue

* Add ID to internal/articles

* Fix auth with Twitter in two places

* Added comments to algolia and modified design

* Update sidebar styles and make other small adjustments

* Clean up tag styling and other small improvements

* Finalize design adjustments

* Add indexing condition for comments

* Fix Algolia typo

* Fix Algolia indexing on comment
This commit is contained in:
Ben Halpern 2018-03-04 22:25:08 -05:00 committed by GitHub
parent a27852cbc3
commit 2771a2e866
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
47 changed files with 1134 additions and 713 deletions

View file

@ -40,6 +40,7 @@ gem "fog", "~> 1.41"
gem "front_matter_parser", "~> 0.1"
gem "gibbon", "~> 2.2"
gem "google-api-client", "~> 0.19"
gem "html_truncator", "~>0.2"
gem "httparty", "~> 0.16"
gem "inline_svg", "~> 0.12"
gem "jbuilder", "~> 2.7"
@ -87,6 +88,7 @@ gem "uglifier", "~> 3.2"
gem "validate_url", "~> 1.0"
gem "webpacker", "~> 3.2"
group :development do
gem "brakeman", "~> 3.7", require: false
gem "bullet", "~> 5.7"
@ -106,7 +108,6 @@ group :development, :test do
gem "faker", "~> 1.8"
gem "memory_profiler", "~> 0.9"
gem "parallel_tests", "~> 2.21"
gem "rack-mini-profiler", "~> 0.10"
gem "rspec-rails", "~> 3.7"
gem "rspec-retry", "~> 0.5"
gem "rubocop", "~> 0.52", require: false

View file

@ -465,6 +465,8 @@ GEM
hashdiff (0.3.7)
hashie (3.5.7)
heapy (0.1.3)
html_truncator (0.4.2)
nokogiri (~> 1.5)
http (3.0.0)
addressable (~> 2.3)
http-cookie (~> 1.0)
@ -605,8 +607,6 @@ GEM
rack (2.0.4)
rack-host-redirect (1.3.0)
rack
rack-mini-profiler (0.10.7)
rack (>= 1.2.0)
rack-protection (2.0.1)
rack
rack-proxy (0.6.3)
@ -925,6 +925,7 @@ DEPENDENCIES
guard (~> 2.14)
guard-livereload (~> 2.5)
guard-rspec (~> 4.7)
html_truncator (~> 0.2)
httparty (~> 0.16)
inline_svg (~> 0.12)
jbuilder (~> 2.7)
@ -947,7 +948,6 @@ DEPENDENCIES
puma_worker_killer (~> 0.1)
pundit (~> 1.1)
rack-host-redirect (~> 1.3)
rack-mini-profiler (~> 0.10)
rack-timeout (~> 0.4)
rack_session_access (~> 0.1)
rails (~> 5.1)

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.5 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 22 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.1 KiB

View file

@ -1,10 +1,14 @@
function initializeBaseTracking() {
var wait = 0;
var addedGA = false;
var waitingOnGA = setInterval(function(){
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
if (!addedGA) {
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
}
addedGA = true;
wait++;
if(window.ga && ga.create){
ga('create', '<%= ENV["GA_TRACKING_ID"] %>', 'auto');

View file

@ -19,7 +19,7 @@ function initializeUserSidebar(user) {
renderedTagsCount++
tagHTML = tagHTML + '<div class="sidebar-nav-element" id="sidebar-element-'+t.name+'">\
<a class="sidebar-nav-link" href="/t/'+t.name+'">\
<span class="sidebar-nav-tag-text" style="color:'+t.text_color_hex+';background:'+t.bg_color_hex+';">#'+t.name+'</span>\
<span class="sidebar-nav-tag-text">#'+t.name+'</span>\
</a>\
</div>';
if (document.getElementById("default-sidebar-element-"+t.name)){

View file

@ -102,14 +102,14 @@ function initializeCommentsPage() {
showModal('reply-to-comment');
return;
}
var actionNode = this.parentNode;
var actionNode = event.target.parentNode;
var parentId = actionNode.dataset.commentId;
document.getElementById("button-for-comment-"+actionNode.dataset.commentId).style.zIndex = "5";
var waitingOnCSRF = setInterval(function () {
var metaTag = document.querySelector("meta[name='csrf-token']");
if (metaTag) {
clearInterval(waitingOnCSRF);
actionNode.innerHTML = formHTML(commentableId, commentableType, parentId);
actionNode.innerHTML = buildCommentFormHTML(commentableId, commentableType, parentId);
setTimeout(function () {
actionNode.getElementsByTagName('textarea')[0].focus();
}, 30);
@ -147,12 +147,6 @@ function toggleCodeOfConduct() {
}
}
function cancel(event, el) {
event.preventDefault();
replaceActionButts(el.parentNode.parentNode.parentNode.parentNode);
initializeCommentsPage();
}
function replaceActionButts(el) {
var loggedInActionButts = "";
var wrapper = el.getElementsByClassName("actions")[0]
@ -172,42 +166,6 @@ function addRelevantButtonsToArticle(responseObj) {
}
}
function formHTML(commentableId, commentableType, parentId) {
var authToken = document.querySelector("meta[name='csrf-token']").getAttribute('content');
var user = userData();
var codeOfConductHTML = ""
if (user && !user.codeOfConduct && user.commentCount < 1){
codeOfConductHTML = '<div class="code-of-conduct sub-comment-code-of-conduct" style="display:block" id="toggle-code-of-conduct-checkbox">\
<input class="checkbox" type="checkbox" name="checked_code_of_conduct" required />\
<label for="checked_code_of_conduct">I\'ve read the <a href="https://dev.to/code-of-conduct">code of conduct</a></label>\
</div>'
}
var randomIdNumber = Math.floor(Math.random() * 1991)
return '<form class="new_comment" onsubmit="handleCommentSubmit.bind(this)(event)" id="new-comment-' + parentId + '" action="/comments" accept-charset="UTF-8" method="post" data-comment-id="'+parentId+'">\
<input name="utf8" type="hidden" value="&#x2713;" />\
<input type="hidden" name="authenticity_token" value="' + authToken + '">\
<input value="' + commentableId + '" type="hidden" name="comment[commentable_id]" id="comment_commentable_id" />\
<input value="' + commentableType + '" type="hidden" name="comment[commentable_type]" id="comment_commentable_type" />\
<input value="' + parentId + '" type="hidden" name="comment[parent_id]" id="comment_parent_id" />\
<textarea id="textarea-for-' + parentId + '" class="embiggened" name="comment[body_markdown]" id="comment_body_markdown" required></textarea>\
'+codeOfConductHTML+'\
<a href="/p/editor_guide" class="markdown-guide" target="_blank" title="Markdown Guide">\
<img class="icon-image" src="<%= asset_path('info.svg') %>" />\
</a>\
<div class="editor-image-upload">\
<input type="file" id="image-upload-' + randomIdNumber + '" name="file" accept="image/*" style="display:none">\
<button title="Upload Image" class="image-upload-button" id="image-upload-button-' + randomIdNumber + '" onclick="handleImageUpload(event,'+ randomIdNumber + ')">\
<img class="icon-image" src="<%= asset_path("image-upload.svg") %>" />\
</button>\
<label class="image-upload-file-label" id="image-upload-file-label-' + randomIdNumber + '"></label>\
<input type="submit" id="image-upload-submit-' + randomIdNumber + '" value="Upload" style="display:none">\
<input class="uploaded-image" id="uploaded-image-' + randomIdNumber + '" />\
</div>\
<a href="#" class="cancel" onclick="cancel(event,this)">CANCEL</a>\
<input type="submit" name="commit" value="SUBMIT" />\
</form>';
}
function handleCommentSubmit(event) {
event.preventDefault();
var form = event.target;

View file

@ -1,8 +1,14 @@
function initializeReadingListPage(){
if ( getCurrentPage("reading_list_items-index") && checkUserLoggedIn() ) {
client = algoliasearch('<%= ENV["ALGOLIASEARCH_APPLICATION_ID"] %>', '<%= ENV["ALGOLIASEARCH_SEARCH_ONLY_KEY"] %>');
articlesIndex = client.initIndex('ordered_articles_<%= Rails.env %>');
commentsIndex = client.initIndex('ordered_comments_<%= Rails.env %>');
var params = getQueryParams(document.location.search);
var tag = filterXSS(params.t || "");
getArticles(tag);
document.getElementById('substories').dataset.tabs = '{"v": "'+(params.v || '')+'", "t": "'+(params.t || '')+'" }'
postCommentFilter();
if (params.v === "comments") {
getArticles("***onlytags***");
}
}
}
@ -11,12 +17,10 @@ function initializeReadingListPage(){
function getArticles(tag){
var user = userData();
var substoriesDiv = document.getElementById("substories");
var client = algoliasearch('<%= ENV["ALGOLIASEARCH_APPLICATION_ID"] %>', '<%= ENV["ALGOLIASEARCH_SEARCH_ONLY_KEY"] %>');
var index = client.initIndex('ordered_articles_<%= Rails.env %>');
var algoliaIndexes = user.reading_list_ids.map(function(id){return "articles-"+id});
var algoliaIds = user.reading_list_ids.map(function(id){return "articles-"+id});
var resultDivs = []
var tags = {}
index.getObjects(algoliaIndexes, function(err, content) {
articlesIndex.getObjects(algoliaIds, function(err, content) {
content.results.forEach(function(story){
if (story) {
if (tag == "" || story.tag_list.indexOf(tag) > -1){
@ -31,16 +35,44 @@ function getArticles(tag){
})
}
})
substoriesDiv.innerHTML = resultDivs.join("");
renderTags(tags)
if ( resultDivs.length == 0) {
var message = "<div style='text-align:left;margin-top:20px;'>This is where to find your bookmarked posts, but it looks like you have not bookmarked anything.</div>"
substoriesDiv.innerHTML = message;
if (tag != "***onlytags***") {
substoriesDiv.innerHTML = resultDivs.join("");
if ( resultDivs.length == 0) {
var message = "<div style='text-align:left;margin-top:20px;'>This is where to find your bookmarked posts, but it looks like you have not bookmarked anything.</div>"
substoriesDiv.innerHTML = message;
}
}
renderTags(tags)
initializeReadingListIcons();
});
}
function getComments(){
var user = userData();
var algoliaIds = user.reading_list_ids.map(function(id){return "commentable_Article_"+id});
var substories = document.getElementById("substories");
var tabsObj = JSON.parse(substories.dataset.tabs);
commentsIndex.search("*", {
hitsPerPage: 100,
attributesToHighlight: [],
tagFilters: [algoliaIds,tabsObj["t"]],
})
.then(function searchDone(content) {
var substoriesDiv = document.getElementById("substories");
var resultDivs = []
content.hits.forEach(function(comment){
var aboveDiv = ""
aboveDiv = aboveDiv + '<div class="comment-view-parent comment-view-commentable"><a href="'+comment.commentable.path+'">Re: '+comment.commentable.title+'</a></div>'
if (comment.parent_path) {
aboveDiv = aboveDiv + '<div class="comment-view-parent"><a href="'+comment.root_path+'">VIEW COMMENT TREE</a></div>'
}
resultDivs.push(aboveDiv+buildCommentHTML(comment));
});
substoriesDiv.innerHTML = '<div class="comment-trees"><h2>Recent Comment Activity</h2>'+resultDivs.join("<br/>")+'</div>';
initializeCommentDropdown();
});
}
function renderTags(tags){
var sortedTags = [];
for (var tag in tags) {
@ -51,7 +83,7 @@ function renderTags(tags){
});
var tagDivs = []
sortedTags.forEach(function(tag){
var html = '<button class="query-filter-button" data-tag="'+tag[0]+'">#'+tag[0]+' ('+tag[1]+')</button>'
var html = '<button class="query-filter-button query-tag-filter-button" data-tag="'+tag[0]+'">#'+tag[0]+' ('+tag[1]+')</button>'
tagDivs.push(html)
})
document.getElementById("tag-filters").innerHTML = tagDivs.join("");
@ -74,10 +106,9 @@ function getQueryParams(qs) {
return params;
}
function tagFilters(tag){
var filterButts = document.getElementsByClassName("query-filter-button");
var substories = document.getElementById('substories');
var filterButts = document.getElementsByClassName("query-tag-filter-button");
for(var i = 0; i < filterButts.length; i++) {
filterButts[i].classList.remove("selected");
if (tag == filterButts[i].dataset.tag){
@ -86,19 +117,70 @@ function tagFilters(tag){
filterButts[i].onclick = function(e){
var tag = e.target.dataset.tag;
window.scrollTo(0, 0);
window.history.replaceState(null, null, "/readinglist?t="+tag);
var className = e.target.className;
var tabsObj = JSON.parse(substories.dataset.tabs);
for(var i = 0; i < filterButts.length; i++) {
filterButts[i].classList.remove("selected");
}
if (className.indexOf("selected") == -1 ) {
e.target.classList.add("selected");
window.history.replaceState(null, null, "/readinglist?t="+tag);
getArticles(tag);
assignTab(tabsObj, "t", tag)
getStories();
} else{
window.history.replaceState(null, null, "/readinglist");
getArticles("");
assignTab(tabsObj, "t", "")
getStories();
}
}
}
}
function postCommentFilter(){
var substories = document.getElementById('substories');
var tabsObj = JSON.parse(substories.dataset.tabs);
getStories()
var filterButts = document.getElementsByClassName("query-type-filter-button");
for(var i = 0; i < filterButts.length; i++) {
filterButts[i].classList.remove("selected");
if ((tabsObj["v"] || "posts") === filterButts[i].dataset.type){
filterButts[i].classList.add("selected");
}
filterButts[i].onclick = function(e){
for(var i = 0; i < filterButts.length; i++) {
filterButts[i].classList.remove("selected");
}
var tabsObj = JSON.parse(substories.dataset.tabs);
assignTab(tabsObj, "v", e.target.dataset.type);
e.target.classList.add("selected");
getStories()
}
}
}
function getStories(){
var substories = document.getElementById('substories');
var tabsObj = JSON.parse(substories.dataset.tabs);
if (tabsObj["v"] == "comments") {
getComments(tabsObj["t"] || "");
} else {
getArticles(tabsObj["t"] || "");
}
}
function assignTab(obj, tab, value) {
obj[tab] = value
substories.dataset.tabs = JSON.stringify(obj);
window.history.replaceState(null, null, "/readinglist"+urlParams(obj));
return obj
}
function urlParams(obj){
if (obj["v"].length > 0 && obj["t"].length > 0) {
return "?v="+obj["v"]+"&t="+obj["t"]
} else if (obj["v"].length > 0) {
return "?v="+obj["v"]
} else if (obj["t"].length > 0) {
return "?t="+obj["t"]
} else {
return ""
}
}

View file

@ -1,4 +1,4 @@
var CACHE_VERSION = 'v2.4.4';
var CACHE_VERSION = 'v2.4.5';
var CACHE_NAME = CACHE_VERSION + ':sw-cache::';
var REQUESTS_LIMIT = 70;
@ -11,6 +11,12 @@ function onInstall(event) {
'/offline.html',
'<%= asset_path "devword.png" %>',
'<%= asset_path "wires.png" %>',
'<%= asset_path "comments-bubble.svg" %>',
'<%= asset_path "reactions-stack.svg" %>',
'<%= asset_path "readinglist-button.svg" %>',
'<%= asset_path "emoji/emoji-one-heart.png" %>',
'<%= asset_path "emoji/emoji-one-unicorn.png" %>',
'<%= asset_path "emoji/emoji-one-bookmark.png" %>',
]).then(function () {
console.log("WORKER: Install completed");
});

View file

@ -24,7 +24,7 @@ function buildArticleHTML(article) {
}
var commentsCountHTML = ""
if ((article.comments_count || '0') > 0 && article.class_name != "User") {
commentsCountHTML = '<div class="article-engagement-count comments-count"><a href="'+article.path+'"><img src="<%= asset_path("chat.svg") %>" alt="chat" /><span class="engagement-count-number">'+(article.comments_count || '0')+'</span></a></div>'
commentsCountHTML = '<div class="article-engagement-count comments-count"><a href="'+article.path+'"><img src="<%= asset_path("comments-bubble.svg") %>" alt="chat" /><span class="engagement-count-number">'+(article.comments_count || '0')+'</span></a></div>'
}
var flareTag = ""
if (container){
@ -42,7 +42,7 @@ function buildArticleHTML(article) {
var rc = article.positive_reactions_count || article.reactions_count
var reactionsCountHTML = ''
if ((rc || '0') > 0 && article.class_name != "User") {
var reactionsCountHTML = '<div class="article-engagement-count reactions-count"><a href="'+article.path+'"><img src="<%= asset_path("favorite-heart-outline-button.svg") %>" alt="heart" /><span id="engagement-count-number-'+article.id+'" class="engagement-count-number">'+(rc || '0')+'</span></a></div>'
var reactionsCountHTML = '<div class="article-engagement-count reactions-count"><a href="'+article.path+'"><img src="<%= asset_path("reactions-stack.svg") %>" alt="heart" /><span id="engagement-count-number-'+article.id+'" class="engagement-count-number">'+(rc || '0')+'</span></a></div>'
}
if ( article.organization && article.tag_list.indexOf("hiring") > -1 && !document.getElementById("organization-index-page") ){
var picUrl = article.organization.profile_image_90
@ -96,8 +96,8 @@ function buildArticleHTML(article) {
<div class="tags">'+tagString+'</div>\
'+commentsCountHTML+reactionsCountHTML+'\
<button class="article-engagement-count bookmark-engage" data-reactable-id="'+article.id+'">\
<img src="<%= asset_path("bookmark.svg") %>" alt="bookmark" class="bm-initial" />\
<img src="<%= asset_path("emoji/emoji-one-bookmark.png") %>" alt="bookmark" class="bm-success" />\
<span class="bm-success"><img src="<%= asset_path("readinglist-button.svg") %>" alt="bookmark" /> SAVED</span>\
<span class="bm-initial">SAVE</span>\
</button></div>';
}
}

View file

@ -0,0 +1,41 @@
function buildCommentFormHTML(commentableId, commentableType, parentId) {
var authToken = document.querySelector("meta[name='csrf-token']").getAttribute('content');
var user = userData();
var codeOfConductHTML = ""
if (user && !user.codeOfConduct && user.commentCount < 1){
codeOfConductHTML = '<div class="code-of-conduct sub-comment-code-of-conduct" style="display:block" id="toggle-code-of-conduct-checkbox">\
<input class="checkbox" type="checkbox" name="checked_code_of_conduct" required />\
<label for="checked_code_of_conduct">I\'ve read the <a href="https://dev.to/code-of-conduct">code of conduct</a></label>\
</div>'
}
var randomIdNumber = Math.floor(Math.random() * 1991)
return '<form class="new_comment" onsubmit="handleCommentSubmit.bind(this)(event)" id="new-comment-' + parentId + '" action="/comments" accept-charset="UTF-8" method="post" data-comment-id="'+parentId+'">\
<input name="utf8" type="hidden" value="&#x2713;" />\
<input type="hidden" name="authenticity_token" value="' + authToken + '">\
<input value="' + commentableId + '" type="hidden" name="comment[commentable_id]" id="comment_commentable_id" />\
<input value="' + commentableType + '" type="hidden" name="comment[commentable_type]" id="comment_commentable_type" />\
<input value="' + parentId + '" type="hidden" name="comment[parent_id]" id="comment_parent_id" />\
<textarea id="textarea-for-' + parentId + '" class="embiggened" name="comment[body_markdown]" id="comment_body_markdown" required></textarea>\
'+codeOfConductHTML+'\
<a href="/p/editor_guide" class="markdown-guide" target="_blank" title="Markdown Guide">\
<img class="icon-image" src="<%= asset_path('info.svg') %>" />\
</a>\
<div class="editor-image-upload">\
<input type="file" id="image-upload-' + randomIdNumber + '" name="file" accept="image/*" style="display:none">\
<button title="Upload Image" class="image-upload-button" id="image-upload-button-' + randomIdNumber + '" onclick="handleImageUpload(event,'+ randomIdNumber + ')">\
<img class="icon-image" src="<%= asset_path("image-upload.svg") %>" />\
</button>\
<label class="image-upload-file-label" id="image-upload-file-label-' + randomIdNumber + '"></label>\
<input type="submit" id="image-upload-submit-' + randomIdNumber + '" value="Upload" style="display:none">\
<input class="uploaded-image" id="uploaded-image-' + randomIdNumber + '" />\
</div>\
<a href="#" class="cancel" onclick="cancel(event,this)">CANCEL</a>\
<input type="submit" name="commit" value="SUBMIT" />\
</form>';
}
function cancel(event, el) {
event.preventDefault();
replaceActionButts(el.parentNode.parentNode.parentNode.parentNode);
initializeCommentsPage();
}

View file

@ -2,10 +2,12 @@ function buildCommentHTML(comment) {
var depthClass = ""
var githubIcon = ""
var twitterIcon = ""
if (comment.depth == 0 ){
var customClass = ""
if (!comment.newly_created) {
depthClass="child flat-node"
} else if (comment.depth == 0 ){
depthClass = "root";
}
else if (comment.depth < 3) {
} else if (comment.depth < 3) {
depthClass="child"
} else {
depthClass="child flat-node"
@ -16,12 +18,15 @@ function buildCommentHTML(comment) {
if (comment.user.github_username && comment.user.github_username.length > 0) {
githubIcon = '<a href="http://github.com/'+comment.user.github_username+'"><%= image_tag("github-logo.svg", class:"icon-img") %></a>'
}
if (comment.newly_created) {
customClass = "comment-created-via-fetch"
}
return '<style>'+comment.css+'</style>\
<div class="comment-hash-marker" id="'+comment.id_code+'"></div>\
<div id="comment-node-'+comment.id+'" class="single-comment-node '+depthClass+' comment-deep-'+comment.depth+'" "\
data-comment-id="'+comment.id+'" data-comment-author-id="'+comment.user.id+'" data-current-user-comment="true">\
<div class="inner-comment comment-created-via-fetch">\
data-comment-id="'+comment.id+'" data-comment-author-id="'+comment.user.id+'" data-current-user-comment="'+comment.newly_created+'">\
<div class="inner-comment '+customClass+'">\
<div class="details">\
<a href="/'+comment.user.username+'">\
<img class="profile-pic" src="'+comment.user.profile_pic+'">\
@ -46,18 +51,44 @@ function buildCommentHTML(comment) {
</div>\
<div class="body">\
'+comment.body_html+'\
<button class="reaction-button reacted" id="button-for-comment-'+comment.id+'" data-comment-id="'+comment.id+'">\
<img src="<%= asset_path("emoji/emoji-one-heart.png") %>" alt="Favorite heart outline button">\
<img class="voted-heart" src="<%= asset_path("emoji/emoji-one-heart.png") %>">\
<span class="reactions-count" id="reactions-count-'+comment.id+'">1</span></button>\
'+reactions(comment)+'\
</div>\
<div class="actions" data-comment-id="'+comment.id+'" data-path="'+comment.url+'">\
<span class="current-user-actions" style="display: inline-block;">\
'+actions(comment)+'\
</div>\
</div>'
}
function actions(comment) {
if (comment.newly_created) {
return '<div class="actions" data-comment-id="'+comment.id+'" data-path="'+comment.url+'">\
<span class="current-user-actions" style="display: '+ (comment.newly_created ? 'inline-block' : 'none') +';">\
<a data-no-instant="" href="'+comment.url+'/delete_confirm" class="edit-butt" rel="nofollow">DELETE</a>\
<a href="'+comment.url+'/edit" class="edit-butt" rel="nofollow">EDIT</a>\
</span>\
<a href="#" class="toggle-reply-form" rel="nofollow">REPLY</a>\
</div>\
</div>\
</div>'
</div>';
} else {
return '<div class="actions" data-comment-id="'+comment.id+'" data-path="'+comment.url+'" data-commentable-id="'+comment.commentable.id+'">\
<a href="'+comment.url+'" class="toggle-reply-form" rel="nofollow">VIEW/REPLY</a>\
</div>';
}
}
function reactions(comment) {
if (comment.newly_created) {
return '<button class="reaction-button reacted" id="button-for-comment-'+comment.id+'" data-comment-id="'+comment.id+'">\
<img src="<%= asset_path("favorite-heart-outline-button.svg") %>" alt="Favorite heart outline button">\
<img class="voted-heart" src="<%= asset_path("emoji/emoji-one-heart.png") %>">\
<span class="reactions-count" id="reactions-count-'+comment.id+'">1</span></button>';
} else {
if (comment.heart_ids.indexOf(userData().id) > -1) {
var reactedClass = "reacted";
} else {
var reactedClass = ""
}
return '<button style="background:white" class="reaction-button '+ reactedClass +'" id="button-for-comment-'+comment.id+'" data-comment-id="'+comment.id+'">\
<img src="<%= asset_path("favorite-heart-outline-button.svg") %>" alt="Favorite heart outline button">\
<img class="voted-heart" src="<%= asset_path("emoji/emoji-one-heart.png") %>">\
<span class="reactions-count" id="reactions-count-'+comment.id+'">'+comment.positive_reactions_count+'</span></button>';
}
}

View file

@ -180,7 +180,7 @@
float:left;
width:calc(100% - 75px);
padding:calc(1vw + 7px) 9px;
padding-bottom:94px;
padding-bottom:110px;
h3{
margin:0px;
font-size:21px;
@ -206,7 +206,7 @@
@media screen and ( min-width: 430px ){
width:calc(100% - 88px);
padding-right:0px;
padding-bottom:104px;
padding-bottom:124px;
h3{
margin:0px;
font-size:27px;
@ -236,12 +236,12 @@
display:inline-block;
left:62px;
position:absolute;
bottom:65px;
bottom:80px;
a{
color: #666666;
}
@media screen and ( min-width: 430px ){
bottom:74px;
bottom:90px;
left:72px;
font-size:17px;
}
@ -262,7 +262,7 @@
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
bottom:48px;
bottom:63px;
color: $black;
a{
display:inline-block;
@ -275,7 +275,7 @@
}
@media screen and ( min-width: 430px ){
left:72px;
bottom:58px;
bottom:72px;
font-size:16px;
}
@ -334,7 +334,7 @@
z-index:5;
border-top:1px solid darken($lightest-gray,4%);
border-bottom:1px solid darken($lightest-gray,4%);
padding-bottom:90px;
padding-bottom:105px;
border-radius:3px;
h3{
font-size:calc(1.7vw + 22px);
@ -352,7 +352,7 @@
display:inline-block;
}
@media screen and ( min-width: 430px ){
padding-bottom:106px;
padding-bottom:120px;
}
}
&:hover{
@ -364,12 +364,12 @@
}
.featured-profile-button{
float:left;
margin-top:-88px;
margin-top:-104px;
z-index:10;
left:12px;
position:absolute;
@media screen and ( min-width: 430px ){
margin-top:-108px;
margin-top:-124px;
}
}
img.featured-profile-pic{
@ -386,7 +386,7 @@
}
.featured-user-name{
font-weight:bold;
margin-top:-87px;
margin-top:-101px;
position:absolute;
z-index:10;
left:62px;
@ -401,12 +401,12 @@
@media screen and ( min-width: 430px ){
left:72px;
font-size:18px;
margin-top:-106px;
margin-top:-120px;
}
}
.featured-tags{
font-size:15px;
margin-top:-66px !important;
margin-top:-78px !important;
bottom:auto !important;
position:absolute;
max-width:76%;
@ -426,63 +426,78 @@
font-size:16px !important;
left:72px;
bottom:auto;
margin-top:-82px !important;
margin-top:-94px !important;
}
}
.article-engagement-count{
font-size:13px;
font-weight:bold;
position:absolute;
bottom:10px;
bottom:12px;
font-weight:400;
padding:3px 6px;
background: transparent;
border:0;
-webkit-appearance:none;
font-family: $helvetica-condensed;
color: $medium-gray;
a{
&:hover{
opacity:1;
}
color: $medium-gray;
}
img{
width:16px;
height:18px;
border:0px;
vertical-align:-4px;
margin-right:7px;
}
&.comments-count {
left:73px;
left:93px;
}
&.reactions-count {
left:10px;
img{
margin-bottom:1px;
opacity:0.8;
}
left:20px;
}
&.bookmark-engage {
right: 7px;
right: 12px;
border: 2px solid transparent;
border-radius: 3px;
text-align:center;
bottom:8px;
img{
width:28px;
margin-right: auto;
&.bm-success{
display:none;
}
bottom:12px;
// border: 2px solid darken($purple, 30%);
background: darken($purple, 26%);
color: white;
letter-spacing: 1px;
&:hover{
background: darken($purple, 30%);
}
img{
height:18px;
margin-right: 0px;
vertical-align:-3px;
}
.bm-success{
display:none;
}
&.selected{
img.bm-initial{
color: darken($purple, 33%);
background: transparent;
padding-right: 0px;
.bm-initial{
display:none;
}
img.bm-success{
.bm-success{
display:block;
}
}
}
&.featured-engagement-count{
margin-top:-35px;
margin-top:-38px;
bottom: auto;
z-index:10;
&.bookmark-engage {
margin-top:-46px;
margin-top:-39px;
}
}
.engagement-count-number{
@ -493,7 +508,8 @@
@media screen and ( min-width: 430px ){
font-size:15px;
img{
width:18px;
height:20px;
min-width:26px;
vertical-align:-5px;
}
&.featured-engagement{
@ -628,13 +644,16 @@
}
.sidebar-profile-pic{
display:inline-block;
width:66px;
height:66px;
width:50px;
height:50px;
vertical-align:top;
overflow:hidden;
background: $tan;
border-radius: 100%;
margin: 8px 5px 8px 12px;
img{
width:100%;
border-radius: 100%;
}
}
.sidebar-profile-content{
@ -666,7 +685,7 @@
}
.sidebar-nav-header{
padding:15px 10px;
font-size: 1.1em;
font-size: 1.3em;
font-family: $helvetica-condensed;
font-stretch:condensed;
font-weight: 800;
@ -682,6 +701,7 @@
padding:10px;
font-weight:500;
font-size:0.9em;
background: darken($light-gray, 4%);
a{
color:$black;
}
@ -695,31 +715,39 @@
.sidebar-nav-element{
color:$black;
display:block;
font-family: "Lucida Console", Monaco, monospace, sans-serif;
// font-family: "Lucida Console", Monaco, monospace, sans-serif;
position:relative;
&.sidebar-nav-readinglist{
background: $purple;
font-family: $helvetica-condensed;
font-stretch:condensed;
&:hover{
background:darken($purple, 3%);
}
font-size:1.1em;
}
&:hover{
background:$light-gray;
}
@media screen and ( min-width: 950px ){
.sidebar-nav-link-follow{
display: none;
}
&:hover{
.sidebar-nav-link-follow{
display: block;
}
}
}
img {
width: 30px;
height: 30px;
border: 0px;
vertical-align: -7px;
vertical-align: -8px;
margin-right: 4px;
}
a.sidebar-nav-link{
color:$black;
padding:10px;
padding:5px;
display:inline-block;
width:100%;
width:calc(100% - 10px);
font-weight: 500;
.sidebar-nav-tag-text{
border-radius: 3px;
padding: 3px 8px;
@ -728,15 +756,13 @@
a.sidebar-nav-link-follow{
position:absolute;
color:$black;
right:9px;
top:5px;
width:31px;
height:31px;
right:5px;
top:2px;
text-align:center;
border-radius:100px;
border-radius:3px;
font-weight:900;
line-height:28px;
font-size:1.3em;
font-size:0.8em;
padding: 2px 8px;
}
}
}
@ -761,6 +787,9 @@
&.sponsorship-widget{
display: none;
text-align:center;
header{
font-size:1.1em;
}
}
&.showing{
display: block;
@ -808,12 +837,15 @@
}
}
}
hr{
opacity:0.2;
}
header{
width:100%;
margin:auto;
padding-bottom:5px;
font-weight:800;
font-size: 1.1em;
font-size: 1.3em;
padding-bottom:10px;
font-family: $helvetica-condensed;
font-stretch:condensed;
@ -832,8 +864,8 @@
.widget-body{
margin-top:9px;
&.body-scrollable{
height: calc(100vh - 500px);
max-height: 385px;
height: calc(100vh - 300px);
max-height: 500px;
overflow-y: scroll;
}
&.signup-cta-widget-body{
@ -877,7 +909,7 @@
}
.sponsor-footer{
font-size:0.77em;
margin-top:110px;
margin-top:106px;
margin:auto;
margin-bottom:18px;
width:96%;
@ -931,14 +963,22 @@
border: 0px;
display:block;
padding: 6px 8px;
width: calc(100% - 16px);
width: 100%;
text-align:left;
border-radius: 0px;
&:hover{
background: lighten($purple, 7%);
background: lighten($yellow, 15%);
}
&.selected{
background: $purple;
background: lighten($yellow, 2%);
}
&.query-type-filter-button{
&:hover{
background: $light-green;
}
&.selected{
background: darken($light-green,10%);
}
}
}
p{
@ -1018,16 +1058,18 @@
// background:red;
}
.discuss-list-comment-count{
background:$bold-blue;
color:white;
font-size:0.7em;
padding:2px 6px;
vertical-align:1px;
border-radius:5px;
opacity:0.95;
font-size: 0.7em;
color: $medium-gray;
margin-top: 2px;
img {
width: 20px;
vertical-align: -3px;
border: 0px;
}
&.discuss-list-comment-count-brand-new {
background:$yellow;
color:$black;
font-size:1.1em;
}
}
&::before {

View file

@ -103,6 +103,7 @@ a.header-link{
padding-top:10px;
margin:auto;
margin-bottom:100px;
text-align: left;
&.comments-dedicated-page-container{
min-height:calc(97vh - 200px);
}
@ -301,7 +302,7 @@ a.header-link{
.comment-hash-marker{
margin-top:-45px;
margin-bottom:45px;
border:1px solid white;
border:1px solid transparent;
z-index:-10;
position:relative;
&:target + .single-comment-node{
@ -313,426 +314,462 @@ a.header-link{
.root-comment{
margin-top:-80px;
}
.single-comment-node{
padding:2px 0px 2px $indent-distance;
&.root{
padding:10px 0px 2px 0;
}
margin-top:42px;
font-size:17px;
}
.comment-view-parent{
margin-bottom: 7px;
text-align:left;
font-family: $helvetica-condensed;
font-size:0.8em;
background: $purple;
display: inline-block;
padding: 2px 4px;
border-radius: 3px;
margin-right: 5px;
vertical-align:top;
&.comment-view-commentable{
background: transparent;
max-width: calc(100% - 100px);
white-space: nowrap;
overflow-x: hidden;
text-overflow: ellipsis;
}
}
}
.single-comment-node{
padding:2px 0px 2px $indent-distance;
text-align: left;
&.root{
padding:10px 0px 2px 0;
}
margin-top:42px;
font-size:17px;
position:relative;
@media screen and ( min-width: 390px ){
font-size:20px;
}
&.flat-node{
padding:0px;
margin-top:-3px;
margin-bottom:-3px;
&.root{
padding:2px 0px;
margin-top:0px;
}
.inner-comment{
border-top-left-radius:0px;
border-top-right-radius:0px;
}
button{
margin-bottom:3px;
z-index:7;
position:relative;
@media screen and ( min-width: 390px ){
font-size:20px;
}
}
.comment-deep-3{
padding:2px 0px 2px calc(0.9vw + 7px);
button{
margin-bottom:3px;
z-index:8;
position:relative;
}
}
.permalink{
width:40px;
position:absolute;
right:2px;
margin-top:calc(0.4vw + 12px);
text-align:center;
opacity:0.9;
img{
width:14px;
}
&:hover{
opacity:1;
}
}
.body{
padding:1px;
padding-left:1.5%;
padding-right:1.5%;
padding-bottom:5px;
margin:0;
width:96%;
color:$black;
font-size:0.95em;
line-height:1.35em;
overflow:hidden;
margin-bottom:-30px;
word-wrap: break-word;
font-family: $helvetica;
position:relative;
h1,h2,h3,h4,h5,h6{
padding:0px;
margin:4px 0px calc(1.1vw + 10px);
font-size:1em;
font-weight:600;
}
h1{
font-size:1.3em;
}
p{
padding:0px;
margin:4px 0px calc(1.1vw + 10px);
}
ul,ol{
padding-left:6px;
margin:0.8em 0.7em 0.8em 1em;
}
ul{
list-style-type: square;
}
li{
margin:3px 0px calc(0.5vw + 5px);
}
blockquote{
border-left:calc(0.2vw + 2px) solid $dark-gray;
padding:0.1% 3% 0.1% 2%;
margin:1.6em 1vw;
font-size:0.92em;
line-height:1.4em;
p{
padding:0;
margin:0.95em 0 0.95em;
}
&.flat-node{
padding:0px;
margin-top:-3px;
margin-bottom:-3px;
&.root{
padding:2px 0px;
margin-top:0px;
}
.inner-comment{
border-top-left-radius:0px;
border-top-right-radius:0px;
}
button{
margin-bottom:3px;
z-index:7;
position:relative;
&.twitter-tweet{
background:white;
max-width:480px;
min-height:135px;
border:1px solid #e9eef2;
font-size:17px;
line-height:20px;
border-radius:5px;
margin-top:10px;
padding:10px;
}
}
.edited-notification{
color:lighten($medium-gray,20%);
font-size:0.7em;
padding:3px 0px 0px;
position:relative;
z-index:1;
}
code{
margin:auto;
white-space: nowrap;
background:$lightest-gray;
padding:1px 5px 0px;
border-radius:2px;
color:#333842;
font-size:0.8em;
display:inline-block;
vertical-align:0.1em;
max-width:100%;
line-height:1.4em;
}
pre{
width:88%;
margin-left:-3%;
margin-left:0px;
padding-left:4%;
padding-right:7%;
padding-top:6%;
padding-bottom:6%;
overflow-wrap: normal;
code{
background:#29292e;
color:#eff0f9;
white-space: pre;
font-size:1em;
}
}
img{
max-width:100%;
}
.table-wrapper-paragraph{
width:100%;
margin-left:0%;
overflow-x:auto;
}
}
.icon-img{
height:16px;
width:16px;
margin-right:0px;
opacity:0.7;
}
.details{
padding:0px;
border-top-left-radius:3px;
border-top-left-radius:3px;
color:$dark-gray;
position:relative;
// background:$light-gray;
.comment-date{
border: none;
position: absolute;
top: calc(16px - 0.25vw);
right: calc(35px + 0.2vw);
font-size:12px;
text-align:right;
a{
color:$medium-gray;
}
}
.dropbtn {
border: none;
cursor: pointer;
position: absolute;
top: calc(0.3vw + 11px);
right: 10px;
}
.dropdown {
position: absolute;
top: 40px;
right: 10px;
display: inline-block;
white-space: nowrap;
.dropdown-content {
display: none;
position: absolute;
right: 0;
border: 1px solid $light-medium-gray;
border-radius: 3px;
background: white;
z-index:20;
box-shadow: $shadow;
&.showing{
display: block;
}
}
.comment-deep-3{
padding:2px 0px 2px calc(0.9vw + 7px);
button{
margin-bottom:3px;
z-index:8;
position:relative;
}
.dropdown-content a {
color: black;
padding: 12px 16px;
width: 93px;
height: 14px;
font-weight: bold;
display: block;
font-size: 14px;
}
.permalink{
width:40px;
position:absolute;
right:2px;
margin-top:calc(0.4vw + 12px);
text-align:center;
opacity:0.9;
img{
width:14px;
}
&:hover{
opacity:1;
}
.dropdown-content a:hover { background-color: #f1f1f1 }
.dropdown-icon {
position: absolute;
left: 10px;
top: 7px;
padding: 4px;
max-width: 15px;
max-height: 15px;
}
.body{
padding:1px;
padding-left:1.5%;
padding-right:1.5%;
padding-bottom:5px;
margin:0;
width:96%;
}
img.profile-pic{
height:33px;
width:33px;
border-radius:50px;
margin:1.5% 0.2% 1.5% 1.5%;
}
.comment-username{
vertical-align:calc(0.62vw + 13px);
}
.comment-username-inner{
vertical-align:middle;
display:inline-block;
white-space: nowrap;
overflow-x: hidden;
text-overflow: ellipsis;
max-width:calc( 60% - 20px);
}
.icon-img{
vertical-align:calc(0.7vw + 9px);
margin-left:2px;
}
.op-marker{
display:none;
@media screen and ( min-width: 580px ){
display:inline-block;
vertical-align:calc(0.7vw + 12px);
background:$purple;
color:$black;
font-size:0.95em;
line-height:1.35em;
overflow:hidden;
margin-bottom:-30px;
word-wrap: break-word;
font-family: $helvetica;
position:relative;
h1,h2,h3,h4,h5,h6{
padding:0px;
margin:4px 0px calc(1.1vw + 10px);
font-size:1em;
font-weight:600;
}
h1{
font-size:1.3em;
}
p{
padding:0px;
margin:4px 0px calc(1.1vw + 10px);
}
ul,ol{
padding-left:6px;
margin:0.8em 0.7em 0.8em 1em;
}
ul{
list-style-type: square;
}
li{
margin:3px 0px calc(0.5vw + 5px);
}
blockquote{
border-left:calc(0.2vw + 2px) solid $dark-gray;
padding:0.1% 3% 0.1% 2%;
margin:1.6em 1vw;
font-size:0.92em;
line-height:1.4em;
p{
padding:0;
margin:0.95em 0 0.95em;
}
&.twitter-tweet{
background:white;
max-width:480px;
min-height:135px;
border:1px solid #e9eef2;
font-size:17px;
line-height:20px;
border-radius:5px;
margin-top:10px;
padding:10px;
}
}
.edited-notification{
color:lighten($medium-gray,20%);
font-size:0.7em;
padding:3px 0px 0px;
position:relative;
z-index:1;
}
code{
margin:auto;
white-space: nowrap;
background:$lightest-gray;
padding:1px 5px 0px;
border-radius:2px;
color:#333842;
font-size:0.8em;
display:inline-block;
vertical-align:0.1em;
max-width:100%;
line-height:1.4em;
}
pre{
width:88%;
margin-left:-3%;
margin-left:0px;
padding-left:4%;
padding-right:7%;
padding-top:6%;
padding-bottom:6%;
overflow-wrap: normal;
code{
background:#29292e;
color:#eff0f9;
white-space: pre;
font-size:1em;
}
}
img{
max-width:100%;
}
.table-wrapper-paragraph{
width:100%;
margin-left:0%;
overflow-x:auto;
}
}
.icon-img{
height:16px;
width:16px;
margin-right:0px;
opacity:0.7;
}
.details{
padding:0px;
border-top-left-radius:3px;
border-top-left-radius:3px;
color:$dark-gray;
position:relative;
// background:$light-gray;
.comment-date{
border: none;
position: absolute;
top: calc(16px - 0.25vw);
right: calc(35px + 0.2vw);
font-size:12px;
text-align:right;
a{
color:$medium-gray;
}
}
.dropbtn {
border: none;
cursor: pointer;
position: absolute;
top: calc(0.3vw + 11px);
right: 10px;
}
.dropdown {
position: absolute;
top: 40px;
right: 10px;
display: inline-block;
white-space: nowrap;
.dropdown-content {
display: none;
position: absolute;
right: 0;
border: 1px solid $light-medium-gray;
border-radius: 3px;
background: white;
z-index:20;
box-shadow: $shadow;
&.showing{
display: block;
}
}
.dropdown-content a {
color: black;
padding: 12px 16px;
width: 93px;
height: 14px;
font-weight: bold;
display: block;
font-size: 14px;
}
.dropdown-content a:hover { background-color: #f1f1f1 }
.dropdown-icon {
position: absolute;
left: 10px;
top: 7px;
padding: 4px;
max-width: 15px;
max-height: 15px;
}
}
img.profile-pic{
height:33px;
width:33px;
border-radius:50px;
margin:1.5% 0.2% 1.5% 1.5%;
}
.comment-username{
vertical-align:calc(0.62vw + 13px);
}
.comment-username-inner{
vertical-align:middle;
display:inline-block;
white-space: nowrap;
overflow-x: hidden;
text-overflow: ellipsis;
max-width:calc( 60% - 20px);
}
.icon-img{
vertical-align:calc(0.7vw + 9px);
margin-left:2px;
}
.op-marker{
display:none;
@media screen and ( min-width: 580px ){
display:inline-block;
vertical-align:calc(0.7vw + 12px);
background:$purple;
color:$black;
padding:2px 6px 2px;
margin-left:0.3vw;
font-family: $helvetica-condensed;
font-stretch:condensed;
font-size: 0.78em;
}
}
a{
color:lighten($dark-gray,3%);
}
}
.reaction-button,.dropbtn{
border:0px;
background:transparent;
vertical-align:2px;
cursor:pointer;
border-radius:3px;
height:32px;
display:block;
padding:0px;
margin-left:2px;
position:relative;
margin-top:calc(-0.6vw - 4px);
z-index:8;
position:relative;
.reactions-count {
font-size:calc(0.9em + 0.1vw);
background: $light-gray;
padding:1px 5px;
border-radius:3px;
border: 1px solid darken($light-gray, 10%);
margin-left:1px;
position:absolute;
top:8px;
left: 24px;
}
img{
height:20px;
width:20px;
opacity:0.62;
left:0px;
}
&:hover{
img{
opacity:1;
}
}
.voted-heart{
display:none;
vertical-align:0px;
margin-left:0px;
height: 25px;
width: 22px;
opacity:0.9;
animation: fadein 0.22s ease ;
&:hover{
opacity:1;
}
}
@media screen and ( min-width: 390px ){
vertical-align:-7px;
}
&.reacted{
color:$green;
img{
display:none;
}
.voted-heart{
display:inline-block;
}
.reactions-count {
color:$black;
font-weight:500;
}
}
}
img.icon-image{
height:20px;
}
.editor-image-upload{
width:calc(100% - 150px);
text-align:left;
.uploaded-image {
width: calc( 93% - 54px);
}
.image-upload-button {
padding: 5px 2px 0px;
margin-left:3px;
}
@media screen and ( min-width: 500px ){
.uploaded-image {
width: calc( 94% - 62px);
}
}
}
.actions{
font-size:0.8em;
padding:0.3em 0.6em 0.3em 0.4em;
text-align:right;
position:relative;
min-height:1.2em;
z-index:5;
padding:2px 6px 2px;
margin-left:0.3vw;
font-family: $helvetica-condensed;
font-stretch:condensed;
width: calc(100% - 74px);
margin-left: 58px;
.current-user-actions{
.hidden{
display:none;
}
a{
margin-right:10px;
display:inline-block;
color:$medium-gray;
}
font-size: 0.78em;
}
}
a{
color:lighten($dark-gray,3%);
}
}
.reaction-button,.dropbtn{
border:0px;
background:transparent;
vertical-align:2px;
cursor:pointer;
border-radius:3px;
height:32px;
display:block;
padding:0px;
margin-left:2px;
position:relative;
margin-top:calc(-0.6vw - 4px);
z-index:8;
position:relative;
.reactions-count {
font-size:calc(0.9em + 0.1vw);
background: $light-gray;
padding:1px 5px;
border-radius:3px;
border: 1px solid darken($light-gray, 10%);
margin-left:1px;
position:absolute;
top:8px;
left: 24px;
}
img{
height:20px;
width:20px;
opacity:0.62;
left:0px;
}
&:hover{
img{
opacity:1;
}
}
.voted-heart{
display:none;
vertical-align:0px;
margin-left:0px;
height: 25px;
width: 22px;
opacity:0.9;
animation: fadein 0.22s ease ;
&:hover{
opacity:1;
}
}
@media screen and ( min-width: 390px ){
vertical-align:-7px;
}
&.reacted{
color:$green;
img{
display:none;
}
.voted-heart{
display:inline-block;
}
.reactions-count {
color:$black;
font-weight:500;
}
}
}
img.icon-image{
height:20px;
}
.editor-image-upload{
width:calc(100% - 150px);
text-align:left;
.uploaded-image {
width: calc( 93% - 54px);
}
.image-upload-button {
padding: 5px 2px 0px;
margin-left:3px;
}
@media screen and ( min-width: 500px ){
.uploaded-image {
width: calc( 94% - 62px);
}
}
}
.actions{
font-size:0.8em;
padding:0.3em 0.6em 0.3em 0.4em;
text-align:right;
position:relative;
min-height:1.2em;
z-index:5;
font-family: $helvetica-condensed;
font-stretch:condensed;
width: calc(100% - 74px);
margin-left: 58px;
.current-user-actions{
.hidden{
display:none;
}
a{
margin-right:10px;
display:inline-block;
color:$medium-gray;
}
}
form{
margin-bottom:-5px;
margin-top:-7px;
z-index:29;
position:relative;
width: calc(100% + 60px);
margin-left: -58px;
&.submitting{
input[type="submit"]{
background:$bold-blue;
}
form{
margin-bottom:-5px;
margin-top:-7px;
z-index:29;
position:relative;
width: calc(100% + 60px);
margin-left: -58px;
&.submitting{
input[type="submit"]{
background:$bold-blue;
}
textarea{
color:lighten($dark-gray,25%);
}
}
input[type="submit"]{
padding:5px 8px;
margin-bottom:5px;
}
textarea{
color:lighten($dark-gray,25%);
}
}
input[type="submit"]{
padding:5px 8px;
margin-bottom:5px;
}
.cancel{
margin-right:5px;
color:$red;
opacity:0.5;
vertical-align:-1px;
font-size:0.9em;
&:hover{
opacity:0.8;
}
}
.cancel{
margin-right:5px;
color:$red;
opacity:0.5;
vertical-align:-1px;
font-size:0.9em;
&:hover{
opacity:0.8;
}
}
.inner-comment{
padding:0px;
border:1px solid $light-medium-gray;
background:white;
&.comment-created-via-fetch{
animation: comment-create 1.2s;
.body{
background-color: transparent;
}
}
}
}
.inner-comment{
padding:0px;
border:1px solid $light-medium-gray;
background:white;
&.comment-created-via-fetch{
animation: comment-create 1.2s;
.body{
background-color: transparent;
}
}
}
.comment-read-more{
background: $green !important;
padding: 3px 6px;
display: block;
width: 250px;
text-align: center;
margin: auto;
border-radius: 3px;
margin-bottom: 20px;
font-weight: bold;
&:hover{
opacity:1;
background: lighten($green, 5%) !important;
}
}
}
@keyframes fadein{

View file

@ -5,6 +5,8 @@ footer{
margin-bottom:-60px;
padding:80px 0px 100px;
box-shadow: 0 -1px 4px 0 rgba(0, 0, 0, 0.15);
z-index: 10;
position: relative;
.container{
background: darken($tan,1%);
width:92%;

View file

@ -150,7 +150,6 @@
background: transparent;
border:0px;
margin-top:5px;
font-size:22px;
margin-left:-5px;
margin-right:3px;
height:30px;
@ -161,10 +160,34 @@
.reacted-emoji{
display:none;
}
&.readinglist-button{
background: darken($purple, 26%);
color: white;
font-family: $helvetica-condensed;
height: auto;
width: auto;
padding: 3px 7px;
display: inline-block;
vertical-align: 5px;
font-size: 17px;
border-radius: 3px;
float:right;
img{
opacity:1;
}
&.reacted{
vertical-align:0px;
padding: 3px 7px 1px;
}
}
&.reacted {
background: transparent;
.reacted-emoji{
display:inline;
}
.reaction-button-text{
display: none;
}
img{
display:none;
}

View file

@ -129,12 +129,21 @@
-webkit-appearance: none;
}
}
.nav-profile-image{
.nav-profile-image-wrapper{
height: 33px;
width: 33px;
background: $dark-gray;
border-radius: 3px;
margin-top:-5px;
border-radius: 100%;
border: 0;
overflow: hidden;
img{
height: 35px;
width: 35px;
border-radius: 100%;
margin-top:-1px;
margin-left:-1px;
}
}
.bars{
height:22px;

View file

@ -77,6 +77,7 @@ class CommentsController < ApplicationController
body_html: @comment.processed_html,
id: @comment.id,
id_code: @comment.id_code_generated,
newly_created: true,
user: {
id: current_user.id,
username: current_user.username,

View file

@ -70,7 +70,10 @@ class Internal::ArticlesController < Internal::ApplicationController
limited_columns_internal_select.
order("featured_number DESC")
end
end
def show
@article = Article.find(params[:id])
end
def update

View file

@ -5,6 +5,8 @@ class Internal::TagsController < Internal::ApplicationController
def index
if params[:state] == "supported"
@tags = Tag.where(supported: true).order("taggings_count DESC").limit(120)
elsif params[:state] == "unsupported"
@tags = Tag.where(supported: false).order("taggings_count DESC").limit(120)
else
@tags = Tag.order("taggings_count DESC").limit(120)
end
@ -12,7 +14,7 @@ class Internal::TagsController < Internal::ApplicationController
def update
@tag = Tag.find(params[:id])
@tag.update(tag_params)
@tag.update!(tag_params)
redirect_to(action: :index)
end

View file

@ -197,6 +197,13 @@ class StoriesController < ApplicationController
end
end
def redirect_if_show_view_param
if params[:view] == "moderate"
redirect_to "/internal/articles/#{@article.id}"
end
end
def handle_article_show
@article_show = true
@comment = Comment.new
@ -210,6 +217,7 @@ class StoriesController < ApplicationController
unless user_signed_in?
response.headers["Surrogate-Control"] = "max-age=10000, stale-while-revalidate=30, stale-if-error=86400"
end
redirect_if_show_view_param; return if performed?
render template: "articles/show"
end

View file

@ -73,7 +73,6 @@ class TagDashboard < Administrate::BaseDashboard
:supported,
:alias_for,
:wiki_body_markdown,
:wiki_body_html,
:rules_markdown,
:short_summary,
:requires_approval,

View file

@ -1,6 +1,7 @@
class CacheBuster
def bust(path)
return unless Rails.env.production?
request = HTTParty.post("https://api.fastly.com/purge/https://dev.to#{path}",
:headers => { 'Fastly-Key' => 'f15066a3abedf47238b08e437684c84f' } )
request = HTTParty.post("https://api.fastly.com/purge/https://dev.to#{path}?i=i",

View file

@ -143,6 +143,15 @@ class Article < ApplicationRecord
end
end
def self.active_discuss_threads(tag)
where(published:true).
where("published_at > ?", 7.days.ago).
tagged_with(["discuss", tag]).
order("last_comment_at DESC").
limit(8).
pluck(:path, :title, :comments_count, :created_at)
end
def body_text
ActionView::Base.full_sanitizer.sanitize(processed_html)[0..7000]
end
@ -352,6 +361,10 @@ class Article < ApplicationRecord
self.crossposted_at = Time.now if published && crossposted_at.blank? && published_from_feed
end
def set_last_comment_at
self.last_comment_at = Time.now if published && last_comment_at.blank?
end
def title_to_slug
title.to_s.downcase.tr(" ", "-").gsub(/[^\w-]/, "").tr("_", "") + "-" + rand(100000).to_s(26)
end

View file

@ -1,5 +1,6 @@
class Comment < ApplicationRecord
has_ancestry
include AlgoliaSearch
belongs_to :commentable, polymorphic: true
counter_culture :commentable
belongs_to :user
@ -19,7 +20,7 @@ class Comment < ApplicationRecord
after_create :after_create_checks
after_save :calculate_score
after_save :bust_cache
before_destroy :bust_cache
before_destroy :before_destroy_actions
after_create :send_email_notification
after_create :create_first_reaction
after_create :send_to_moderator
@ -30,6 +31,84 @@ class Comment < ApplicationRecord
include StreamRails::Activity
as_activity
algoliasearch per_environment: true, enqueue: :trigger_delayed_index do
add_index "ordered_comments",
id: :index_id,
per_environment: true,
enqueue: :trigger_delayed_index do
attributes :id, :user_id, :commentable_id, :commentable_type, :id_code_generated, :path,
:id_code, :readable_publish_date, :parent_id, :positive_reactions_count
attribute :body_html do
HTML_Truncator.truncate(processed_html,
500, :ellipsis => '<a class="comment-read-more" href="'+path+'">... Read Entire Comment</a>')
end
attribute :url do
path
end
attribute :css do
custom_css
end
attribute :tag_list do
commentable.tag_list
end
attribute :root_path do
root&.path
end
attribute :parent_path do
parent&.path
end
attribute :heart_ids do
reactions.where(category:"like").pluck(:user_id)
end
attribute :user do
{ username: user.username,
name: user.name,
id: user.id,
profile_pic: ProfileImage.new(user).get(90),
profile_image_90: ProfileImage.new(user).get(90),
github_username: user.github_username,
twitter_username: user.twitter_username
}
end
attribute :commentable do
{ path: commentable&.path,
title: commentable&.title,
tag_list: commentable&.tag_list,
id: commentable&.id
}
end
tags do
[commentable.tag_list,
"user_#{user_id}",
"commentable_#{commentable_type}_#{commentable_id}"].flatten.compact
end
ranking ["desc(created_at)"]
end
end
def self.trigger_delayed_index(record, remove)
if remove
record.delay.remove_from_index! if (record && record.persisted?)
else
if record.deleted == false
record.delay.index!
else
record.remove_algolia_index
end
end
end
def remove_algolia_index
remove_from_index!
index = Algolia::Index.new("ordered_comments_#{Rails.env}")
index.delete_object("comments-#{id}")
end
def index_id
"comments-#{id}"
end
def self.rooted_on(commentable_id, commentable_type)
includes(:user, :commentable).
where(commentable_id: commentable_id,
@ -214,6 +293,12 @@ class Comment < ApplicationRecord
end
handle_asynchronously :create_first_reaction
def before_destroy_actions
bust_cache
remove_algolia_index
reactions.destroy_all
end
def bust_cache
expire_root_fragment
CacheBuster.new.bust("#{commentable.path}") if commentable
@ -224,6 +309,7 @@ class Comment < ApplicationRecord
def async_bust
expire_root_fragment
commentable.touch
commentable.touch(:last_comment_at)
CacheBuster.new.bust_comment(self)
commentable.index!
end

View file

@ -22,6 +22,7 @@ class Tag < ActsAsTaggableOn::Tag
def evaluate_markdown
self.rules_html = MarkdownParser.new(rules_markdown).evaluate_markdown
self.wiki_body_html = MarkdownParser.new(wiki_body_markdown).evaluate_markdown
end
def calculate_hotness_score

View file

@ -5,14 +5,14 @@
<div class="breadcrumb"><a href="<%= @user.path %>">View All Activity</a></div>
<h2>
<% if @user.comments_count > 250 %>
<img class="emoji-img" src="<%= asset_path("chat.svg") %>"/> Last 250 Comments
<img class="emoji-img" src="<%= asset_path("comments-bubble.svg") %>"/> Last 250 Comments
<% else %>
<img class="emoji-img" src="<%= asset_path("chat.svg") %>"/> All <%= @user.comments_count %> Comments
<img class="emoji-img" src="<%= asset_path("comments-bubble.svg") %>"/> All <%= @user.comments_count %> Comments
<% end %>
</h2>
<% else %>
<h2>
<img class="emoji-img" src="<%= asset_path("chat.svg") %>"/> Recent Comments
<img class="emoji-img" src="<%= asset_path("comments-bubble.svg") %>"/> Recent Comments
</h2>
<% end %>
<% end %>

View file

@ -207,7 +207,23 @@
<header>
&lt;SUBMISSION GUIDELINES&gt;
</header>
<%= @tag_model.rules_html.html_safe %>
<div class="widget-body">
<%= @tag_model.rules_html.html_safe %>
</div>
<p><center><a class="cta cta-button" href="/new/<%= @tag %>"
style="background:<%= @tag_model.bg_color_hex %>;color:<%= @tag_model.text_color_hex %>;">
WRITE A POST
</a></center></p>
</div>
<% end %>
<% if @tag_model && @tag_model.wiki_body_html.present? %>
<div class="widget">
<header>
&lt;ABOUT <%= @tag.upcase %>&gt;
</header>
<div class="widget-body">
<%= @tag_model.wiki_body_html.html_safe %>
</div>
</div>
<% end %>
<% elsif @podcast_index && @podcasts %>

View file

@ -41,16 +41,32 @@
</div>
<% end %>
<% elsif @user && @user.class.name == "Organization" %>
<% elsif @tag || @query %>
<% elsif @tag %>
<% if Article.active_discuss_threads(@tag).any? %>
<div class="widget">
<header>
<#DISCUSS>
</header>
<div class="widget-body">
<div class="widget-link-list">
<% Article.active_discuss_threads(@tag).
each do |plucked_article| %>
<%= render "articles/widget_list_item", plucked_article: plucked_article, show_comment_count: true %>
<% end %>
</div>
</div>
</div>
<% end %>
<% elsif @query %>
<% elsif @podcast_index %>
<% else %>
<% unless user_signed_in? %>
<div class="widget signin-cta-widget">
<header>
&lt;SIGN UP&gt;
DEV.TO(GETHER)
</header>
<div class="widget-body">
<p style="margin-bottom: 12px;">Join thousands of developers leveling up their game together.</p>
<p style="margin-bottom: 12px;">Join thousands of software developers leveling up their code and careers.</p>
<center style="margin-bottom: 12px;">
<a href="/users/auth/twitter?state=navbar_basic" class="cta cta-button" data-no-instant>
TWITTER
@ -102,12 +118,7 @@
limit(20).
pluck(:path, :title, :comments_count, :created_at).
each do |plucked_article| %>
<div class="widget-link-list__item">
<a href="<%= plucked_article[0] %>">
<%= plucked_article[1] %> <span class="discuss-list-comment-count <%= " discuss-list-comment-count-brand-new " if plucked_article[2] == 0 %>"><%= plucked_article[2] == 0 ? "fresh" : plucked_article[2] %>
</span>
</a>
</div>
<%= render "articles/widget_list_item", plucked_article: plucked_article, show_comment_count: false %>
<% end %>
</div>
<p><center><a class="cta cta-button" href="/new/shecoded">TELL YOUR STORY</a></center></p>
@ -122,16 +133,13 @@
<div class="widget-link-list">
<% Article.
where(published:true).
where("published_at > ?", 2.days.ago).
tagged_with("discuss").
order("created_at DESC").
order("last_comment_at DESC").
limit(8).
pluck(:path, :title, :comments_count, :created_at).
each do |plucked_article| %>
<div class="widget-link-list__item">
<a href="<%= plucked_article[0] %>">
<%= plucked_article[1] %> <span class="discuss-list-comment-count <%= "discuss-list-comment-count-brand-new" if plucked_article[2] == 0 %>"><%= plucked_article[2] == 0 ? "fresh" : plucked_article[2] %></span>
</a>
</div>
<%= render "articles/widget_list_item", plucked_article: plucked_article, show_comment_count: true %>
<% end %>
</div>
<p><center><a class="cta cta-button" href="/new/discuss">START A DISCUSSION</a></center></p>
@ -147,15 +155,11 @@
<% Article.
where(published:true).
tagged_with("explainlikeimfive").
order("created_at DESC").
limit(8).
order("last_comment_at DESC").
limit(3).
pluck(:path,:title,:comments_count, :created_at).
each do |plucked_article| %>
<div class="widget-link-list__item">
<a href="<%= plucked_article[0] %>">
<%= plucked_article[1] %> <span class="discuss-list-comment-count <%= "discuss-list-comment-count-brand-new" if plucked_article[2] == 0 %>"><%= plucked_article[2] == 0 ? "fresh" : plucked_article[2] %></span>
</a>
</div>
<%= render "articles/widget_list_item", plucked_article: plucked_article, show_comment_count: true %>
<% end %>
</div>
<p><center><a class="cta cta-button" href="/new/explainlikeimfive">ASK FOR AN EXPLANATION</a></center></p>

View file

@ -17,7 +17,7 @@
<div class="widget sidebar-nav-block">
<div class="sidebar-nav-element sidebar-nav-readinglist">
<a class="sidebar-nav-link" href="/readinglist">
<img src="<%=asset_path("emoji/emoji-one-bookmark.png")%>" /> MY READING LIST
<img src="<%=asset_path("readinglist-button.svg")%>" /> MY READING LIST
<span id="reading-list-count"></span>
</a>
</div>
@ -25,7 +25,11 @@
<% end %>
<div class="widget sidebar-nav-block sidebar-tag-block">
<div class="sidebar-nav-header sidebar-nav-header-middle">
&lt;<%= user_signed_in? ? "MY" : "DEV" %> TAGS&gt;
<% if user_signed_in? %>
&lt;MY TAGS&gt;
<% else %>
&lt;DESIGN YOUR EXPERIENCE&gt;
<% end %>
</div>
<div class="sidebar-nav-block-inner">
<div id="sidebar-nav-followed-tags" class="sidebar-nav-followed-tags"></div>
@ -33,17 +37,17 @@
<% if user_signed_in? %>
<div class="sidebar-nav-subheader tag-separator" id="tag-separator">Other Popular Tags</div>
<% end %>
<% Tag.where(supported:true).order("hotness_score DESC").limit(20).each do |tag| %>
<div class="sidebar-nav-element" id="default-sidebar-element-<%= tag.name %>">
<a class="sidebar-nav-link" href="/t/<%= tag.name %>">
<span class="sidebar-nav-tag-text" style="color:<%= tag.text_color_hex %>;background:<%= tag.bg_color_hex %>;" >
#<%= tag.name %>
<% Tag.where(supported:true).order("hotness_score DESC").limit(30).pluck(:id, :name).each do |tag_array| %>
<div class="sidebar-nav-element" id="default-sidebar-element-<%= tag_array.second %>">
<a class="sidebar-nav-link" href="/t/<%= tag_array.second %>">
<span class="sidebar-nav-tag-text" >
#<%= tag_array.second %>
</span>
</a>
<a class="follow-action-button sidebar-nav-link-follow cta"
href="#" id="sidebar-nav-link-follow-<%= tag.name %>"
data-info='{"id":<%= tag.id%>,"className":"Tag", "style":"small"}'>
+
href="#" id="sidebar-nav-link-follow-<%= tag_array.second %>"
data-info='{"id":<%= tag_array.first %>,"className":"Tag"}'>
+ FOLLOW
</a>
</div>
<% end %>

View file

@ -32,13 +32,13 @@
<% if story.comments_count > 0 %>
<div class="article-engagement-count comments-count">
<a href="<%=story.path%>">
<img src="<%= asset_path("chat.svg") %>" alt="chat" /><span class="engagement-count-number"><%= story.comments_count %></span>
<img src="<%= asset_path("comments-bubble.svg") %>" alt="chat" /><span class="engagement-count-number"><%= story.comments_count %></span>
</a>
</div>
<% end %>
<div class="article-engagement-count reactions-count" data-reaction-count data-reactable-id="<%= story.id %>">
<a href="<%=story.path%>">
<img src="<%= asset_path("favorite-heart-outline-button.svg") %>" alt="heart" /><span id="engagement-count-number-<%= story.id %>" class="engagement-count-number">
<img src="<%= asset_path("reactions-stack.svg") %>" alt="Reactions" /><span id="engagement-count-number-<%= story.id %>" class="engagement-count-number">
<%= story.positive_reactions_count %>
</span>
</a>
@ -46,9 +46,9 @@
<button
class="article-engagement-count bookmark-engage"
data-reactable-id="<%= story.id %>"
aria-label="Add to reading list"
title="Add to reading list">
<img src="<%= asset_path("bookmark.svg") %>" alt="bookmark" class="bm-initial" />
<img src="<%= asset_path("emoji/emoji-one-bookmark.png") %>" alt="bookmark" class="bm-success" />
aria-label="Save to reading list"
title="Save to reading list">
<span class="bm-success"><img src="<%= asset_path("readinglist-button.svg") %>" alt="bookmark" /> SAVED</span>
<span class="bm-initial">SAVE</span>
</button>
</div>

View file

@ -0,0 +1,14 @@
<div class="widget-link-list__item">
<a href="<%= plucked_article[0] %>">
<%= plucked_article[1] %>
</a>
<% if show_comment_count %>
<div class="discuss-list-comment-count">
<% if plucked_article[2] > 0 %>
<img src="<%= asset_path("comments-bubble.svg") %>" alt="Comments" /> <%= plucked_article[2] %>
<% else %>
<span class="discuss-list-comment-count discuss-list-comment-count-brand-new">fresh</span>
<% end %>
</div>
<% end %>
</div>

View file

@ -231,14 +231,14 @@
</div>
<% if @featured_story.comments_count > 0 %>
<div class="article-engagement-count comments-count featured-engagement-count">
<a href="<%=@featured_story.path%>"><img src="<%= asset_path("chat.svg") %>" alt="chat" />
<a href="<%=@featured_story.path%>"><img src="<%= asset_path("comments-bubble.svg") %>" alt="chat" />
<span class="engagement-count-number"><%= @featured_story.comments_count %></span>
</a>
</div>
<% end %>
<% if @featured_story.positive_reactions_count > 0 %>
<div class="article-engagement-count reactions-count featured-engagement-count" data-reaction-count data-reactable-id="<%= @featured_story.id %>">
<a href="<%=@featured_story.path%>"><img src="<%= asset_path("favorite-heart-outline-button.svg") %>" alt="heart" /><span id="engagement-count-number-<%= @featured_story.id %>" class="engagement-count-number">
<a href="<%=@featured_story.path%>"><img src="<%= asset_path("reactions-stack.svg") %>" alt="heart" /><span id="engagement-count-number-<%= @featured_story.id %>" class="engagement-count-number">
<%= @featured_story.positive_reactions_count %>
</span>
</a>
@ -249,8 +249,8 @@
data-reactable-id="<%= @featured_story.id %>"
aria-label="Add to reading list"
title="Add to reading list" >
<img src="<%= asset_path("bookmark.svg") %>" alt="bookmark" class="bm-initial" />
<img src="<%= asset_path("emoji/emoji-one-bookmark.png") %>" alt="bookmark" class="bm-success" />
<span class="bm-success"><img src="<%= asset_path("readinglist-button.svg") %>" alt="bookmark" /> SAVED</span>
<span class="bm-initial">SAVE</span>
</button>
</div>
</a>

View file

@ -1,71 +1,69 @@
<% articles.each do |article| %>
<% featured = article.featured ? "featured-bg": "" %>
<% featured = article.approved ? "approved-bg": featured %>
<div class="row main-group <%= featured %>" style="<%= "background:red" if (!article.published? && article.published_from_feed? && !article.user&.feed_admin_publish_permission?) %>">
<% if article.published_from_feed? && !article.published? %>
<p style="padding:5px;background:#f7ff1e;color:black;display:inline;font-weight:bold">
RSS Import <%= article.created_at.strftime("%b %d, %Y") %>
</p>
<p style="font-size:0.8em;padding:5px;">
Originally Published <%= article.published_at&.strftime("%b %d, %Y") %>
</p>
<% elsif article.crossposted_at? %>
<p>
Crossposted <%= article.crossposted_at.strftime("%b %d, %Y") %> & Published <%= article.published_at&.strftime("%b %d, %Y") %>
</p>
<% else %>
<p>
Published <%= article.published_at&.strftime("%b %d, %Y") %>
</p>
<% end %>
<% if article.main_image.present? %>
<div style="max-height:450px;overflow:hidden;">
<img src="<%= cloud_cover_url(article.main_image) %>" style="width:100%;" alt="cover image"/><br/>
</div>
<% end %>
<b><a href="<%= article.path %>" target="_blank"><%= article.title %></a></b> <a href="<%= article.path %>/edit" target="_blank" style="background:#00da8b;color:white;padding:1px 6px">EDIT</a>
<% article.tag_list.each do |tag| %>
<a href='/t/<%= tag %>'>#<%= tag %></a>
<% end %>
<a href="<%= article.user&.path %>">@<%= article.user&.username %></a> ❤️ <%= article.positive_reactions_count %> | 💬 <%= article.comments_count %> | ID: <%= article.id %>
<br/><br/>
<div class="inner-row">
<form action="/internal/articles/<%= article.id %>" accept-charset="UTF-8" method="post">
<input name="utf8" type="hidden" value="✓">
<input type="hidden" name="authenticity_token" value="<%= form_authenticity_token %>" />
<input type="hidden" name="_method" value="patch" />
Featured Number: <input name="article[featured_number]" value="<%= article.featured_number %>" id="featured_number_<%= article.id %>" style="max-width:140px"><button onclick="timeNow(<%= article.id %>);return false;">☝️</button><button onclick="sink(<%= article.id %>);return false;">👇 SPAM!</button>
&nbsp Featured: <input name="article[featured]" type="checkbox" <%= "checked" if article.featured %>>
&nbsp Approved: <input name="article[approved]" type="checkbox" <%= "checked" if article.approved %>>
&nbsp Live Now: <input name="article[live_now]" type="checkbox" <%= "checked" if article.live_now %>>
&nbsp Image BG color: <input name="article[main_image_background_hex_color]" value="<%= article.main_image_background_hex_color %>" id="featured_number_<%= article.id %>"><br/><br/>
Social Image: <input name="article[social_image]" value="<%= article.social_image %>" id="featured_number_<%= article.id %>">
&nbsp &nbsp <button class="btn btn-primary">SUBMIT</button>
</form>
</div>
<div class="inner-row" style="padding:10px;margin-top:20px;font-weight:800;">
<%= form_tag "/internal/buffer_updates" do %>
<input type="hidden" name="social_channel" value="twitter" />
<input type="hidden" name="article_id" value="<%= article.id %>" />
<p> Twitter </p>
<textarea cols="37" rows="6" wrap="hard" name="tweet" maxlength="230" /><%= article.title %></textarea>
<% if article.last_buffered %>
<button class="btn btn-danger" style="font-size:1em;">🙈 TWEETED <%= time_ago_in_words(article.last_buffered) %> ago</button>
<% else %>
<button class="btn btn-info" style="font-size:1em;">😎 TWEET 🚀🚀🚀🚀🚀🚀</button>
<% end %>
<% end %>
<%= form_tag "/internal/buffer_updates" do %>
<input type="hidden" name="social_channel" value="facebook" />
<input type="hidden" name="article_id" value="<%= article.id %>" />
<p> Facebook & LinkedIn </p>
<textarea cols="37" rows="6" wrap="hard" name="fb_post" required/></textarea>
<% if article.facebook_last_buffered %>
<button class="btn btn-danger" style="font-size:1em;">🙈 POSTED <%= time_ago_in_words(article.facebook_last_buffered) %> ago</button>
<% else %>
<button class="btn btn-info" style="font-size:1em;">😎 POST 🚀🚀🚀🚀🚀🚀</button>
<% end %>
<% featured = article.featured ? "featured-bg": "" %>
<% featured = article.approved ? "approved-bg": featured %>
<div class="row main-group <%= featured %>" style="<%= "background:red" if (!article.published? && article.published_from_feed? && !article.user&.feed_admin_publish_permission?) %>">
<% if article.published_from_feed? && !article.published? %>
<p style="padding:5px;background:#f7ff1e;color:black;display:inline;font-weight:bold">
RSS Import <%= article.created_at.strftime("%b %d, %Y") %>
</p>
<p style="font-size:0.8em;padding:5px;">
Originally Published <%= article.published_at&.strftime("%b %d, %Y") %>
</p>
<% elsif article.crossposted_at? %>
<p>
Crossposted <%= article.crossposted_at.strftime("%b %d, %Y") %> & Published <%= article.published_at&.strftime("%b %d, %Y") %>
</p>
<% else %>
<p>
Published <%= article.published_at&.strftime("%b %d, %Y") %>
</p>
<% end %>
<% if article.main_image.present? %>
<div style="max-height:450px;overflow:hidden;">
<img src="<%= cloud_cover_url(article.main_image) %>" style="width:100%;" alt="cover image"/><br/>
</div>
<% end %>
<b><a href="<%= article.path %>" target="_blank"><%= article.title %></a></b> <a href="<%= article.path %>/edit" target="_blank" style="background:#00da8b;color:white;padding:1px 6px">EDIT</a>
<% article.tag_list.each do |tag| %>
<a href='/t/<%= tag %>'>#<%= tag %></a>
<% end %>
<a href="<%= article.user&.path %>">@<%= article.user&.username %></a> ❤️ <%= article.positive_reactions_count %> | 💬 <%= article.comments_count %> | ID: <a href="/internal/articles/<%= article.id %>"><%= article.id %></a>
<br/><br/>
<div class="inner-row">
<form action="/internal/articles/<%= article.id %>" accept-charset="UTF-8" method="post">
<input name="utf8" type="hidden" value="✓">
<input type="hidden" name="authenticity_token" value="<%= form_authenticity_token %>" />
<input type="hidden" name="_method" value="patch" />
Featured Number: <input name="article[featured_number]" value="<%= article.featured_number %>" id="featured_number_<%= article.id %>" style="max-width:140px"><button onclick="timeNow(<%= article.id %>);return false;">☝️</button><button onclick="sink(<%= article.id %>);return false;">👇 SPAM!</button>
&nbsp Featured: <input name="article[featured]" type="checkbox" <%= "checked" if article.featured %>>
&nbsp Approved: <input name="article[approved]" type="checkbox" <%= "checked" if article.approved %>>
&nbsp Live Now: <input name="article[live_now]" type="checkbox" <%= "checked" if article.live_now %>>
&nbsp Image BG color: <input name="article[main_image_background_hex_color]" value="<%= article.main_image_background_hex_color %>" id="featured_number_<%= article.id %>"><br/><br/>
Social Image: <input name="article[social_image]" value="<%= article.social_image %>" id="featured_number_<%= article.id %>">
&nbsp &nbsp <button class="btn btn-primary">SUBMIT</button>
</form>
</div>
<div class="inner-row" style="padding:10px;margin-top:20px;font-weight:800;">
<%= form_tag "/internal/buffer_updates" do %>
<input type="hidden" name="social_channel" value="twitter" />
<input type="hidden" name="article_id" value="<%= article.id %>" />
<p> Twitter </p>
<textarea cols="37" rows="6" wrap="hard" name="tweet" maxlength="230" /><%= article.title %></textarea>
<% if article.last_buffered %>
<button class="btn btn-danger" style="font-size:1em;">🙈 TWEETED <%= time_ago_in_words(article.last_buffered) %> ago</button>
<% else %>
<button class="btn btn-info" style="font-size:1em;">😎 TWEET 🚀🚀🚀🚀🚀🚀</button>
<% end %>
</div>
</div>
<% end %>
<% end %>
<%= form_tag "/internal/buffer_updates" do %>
<input type="hidden" name="social_channel" value="facebook" />
<input type="hidden" name="article_id" value="<%= article.id %>" />
<p> Facebook & LinkedIn </p>
<textarea cols="37" rows="6" wrap="hard" name="fb_post" required/></textarea>
<% if article.facebook_last_buffered %>
<button class="btn btn-danger" style="font-size:1em;">🙈 POSTED <%= time_ago_in_words(article.facebook_last_buffered) %> ago</button>
<% else %>
<button class="btn btn-info" style="font-size:1em;">😎 POST 🚀🚀🚀🚀🚀🚀</button>
<% end %>
<% end %>
</div>
</div>

View file

@ -37,7 +37,9 @@
<% if @featured_articles && @featured_articles.any? %>
<h1>Manually Featured Articles</h1>
<%= render "individual_article", articles: @featured_articles %>
<% @featured_articles.each do |article| %>
<%= render "individual_article", article: article %>
<% end %>
<% end %>
@ -46,7 +48,9 @@
<%= paginate @articles %>
</div>
<%= render "individual_article", articles: @articles %>
<% @articles.each do |article| %>
<%= render "individual_article", article: article %>
<% end %>
<div class="row" style="font-size:1.5em;border:0px">
<%= paginate @articles %>

View file

@ -0,0 +1 @@
<%= render "individual_article", article: @article %>

View file

@ -1,15 +1,24 @@
<h1>All Tags</h1>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jscolor/2.0.4/jscolor.min.js"></script>
<style>
textarea{
height: 140px;
}
</style>
<h1>Tags</h1>
<h2>
<% if params[:state] == "supported" %>
<a href="/internal/tags">View All</a>
<% else %>
<a href="/internal/tags?state=supported">View Supported only</a>
<% end %>
<a href="/internal/tags"
style="<%= 'background: yellow' if params[:state].blank? %>">All</a> |
<a href="/internal/tags?state=supported"
style="<%= 'background: yellow' if params[:state] == "supported" %>">Supported</a> |
<a href="/internal/tags?state=unsupported"
style="<%= 'background: yellow' if params[:state] == "unsupported" %>">Unsupported</a> |
</h2>
<hr/><hr/>
<% @tags.each do |tag| %>
<div>
<h2><%= tag.name %></h2>
<h1><a href="/t/<%= tag.name %>" style="background:<%= tag.bg_color_hex %>;color:<%= tag.text_color_hex %>;">#<%= tag.name %></a></h1>
<p>count: <%= tag.taggings_count %></p>
<br/>
<%= form_for [:internal, tag] do |f| %>
@ -27,15 +36,21 @@
<div>
<div>
<%= f.label :rules_markdown %>
<br/>
<%= f.text_area :rules_markdown %>
<div>
<div>
<%= f.label :wiki_body_markdown %>
<br/>
<%= f.text_area :wiki_body_markdown %>
<div>
</div>
<%= f.label :bg_color_hex %>
<%= f.text_field :bg_color_hex %>
<%= f.text_field :bg_color_hex, class: "jscolor {hash:true}", required: true %>
<div>
</div>
<%= f.label :text_color_hex %>
<%= f.text_field :text_color_hex %>
<%= f.text_field :text_color_hex, class: "jscolor {hash:true}", required: true %>
<div>
</div>
<div>

View file

@ -34,6 +34,7 @@
<% Rails.application.config.assets.compile = true %>
<%= Rails.application.assets['scaffolds.css'].to_s.html_safe %>
<%= Rails.application.assets['articles.css'].to_s.html_safe %>
<%= Rails.application.assets['comments.css'].to_s.html_safe %>
<%= Rails.application.assets['notifications.css'].to_s.html_safe %>
<%= Rails.application.assets['top-bar.css'].to_s.html_safe %>
<%= Rails.application.assets['user-profile-header.css'].to_s.html_safe %>

View file

@ -16,7 +16,7 @@
<div class="navbar-menu-wrapper" id="navbar-menu-wrapper">
<button class="navigation-butt" id="navigation-butt" aria-label="Navigation">
<% if user_signed_in? %>
<img class="nav-profile-image" id="nav-profile-image"/>
<div class="nav-profile-image-wrapper"><img class="nav-profile-image" id="nav-profile-image"/>
<% else %>
<%= inline_svg("menu.svg", class: "bars", size: '20% * 20%') %>
<% end %>

View file

@ -31,9 +31,9 @@
<%= image_tag "favorite-heart-outline-button.svg" %>
<img class="reacted-emoji" src="<%= asset_path("emoji/emoji-one-heart.png") %>"/>
</button>
<button class="reaction-button <%= Reaction.cached_any_reactions_for?(activity['object'], current_user, "readinglist") ? "reacted" : "" %>" data-reactable-id="<%=activity['object'].id%>" data-category="readinglist" data-reactable-type="Article">
<%= image_tag "bookmark.svg" %>
<img class="reacted-emoji" src="<%= asset_path("emoji/emoji-one-bookmark.png") %>"/>
<button class="reaction-button readinglist-button <%= Reaction.cached_any_reactions_for?(activity['object'], current_user, "readinglist") ? "reacted" : "" %>" data-reactable-id="<%=activity['object'].id%>" data-category="readinglist" data-reactable-type="Article">
<span class="reaction-button-text">SAVE</span>
<img class="reacted-emoji reaction-button-reacted-emoji" src="<%= asset_path("readinglist-button.svg") %>"/>
</button>
</div>
<% end %>

View file

@ -19,27 +19,27 @@
<div class="user-profile-header tag-header readinglist-header">
<div class="tag-or-query-header-container">
<h1 class="notifications-header"><img src="<%=asset_path("emoji/emoji-one-bookmark.png")%>" /> My Reading List</h1>
<h1 class="notifications-header"><img src="<%=asset_path("readinglist-button.svg")%>" /> My Reading List</h1>
</div>
</div>
<div class="home" id="notifications-container">
<div class="home">
<div class="side-bar" style="margin-top:12px" id="sidebar">
<div class="fixed-sidebar">
<div class="sidebar-nav">
<div class="widget sidebar-nav-block">
<div class="sidebar-nav-element sidebar-nav-readinglist">
<a class="sidebar-nav-link" href="/readinglist">
<img src="<%=asset_path("emoji/emoji-one-bookmark.png")%>" /> MY READING LIST
<img src="<%=asset_path("readinglist-button.svg")%>" /> MY READING LIST
<span id="reading-list-count"></span>
</a>
</div>
</div>
</div>
<div class="widget" id="tag-filter-widget">
<header>
&lt;FILTER BY TAG&gt;
</header>
<button class="query-filter-button query-type-filter-button" data-type="posts">Posts</button>
<button class="query-filter-button query-type-filter-button" data-type="comments">Comments</button>
<hr/>
<div class="widget-body body-scrollable" id="tag-filters" style="margin-bottom:16px;">
</div>
</div>
@ -47,7 +47,9 @@
</div>
<div class="articles-list" id="articles-list">
<% if user_signed_in? %>
<div class="substories" id="substories">
<div class="comments-container" id="comments-container">
<div class="substories" id="substories" data-tabs="{}">
</div>
</div>
<% else %>
<script>

View file

@ -4,9 +4,6 @@ Rails.application.configure do
config.flipflop.dashboard_access_filter = nil
# Settings specified here will take precedence over those in config/application.rb.
# Make sure Rack MiniProfiler starts hidden
Rack::MiniProfiler.config.start_hidden = true
# In the development environment your application's code is reloaded on
# every request. This slows down response time but is perfect for development
# since you don't have to restart the web server when you make code changes.

View file

@ -19,7 +19,7 @@ Rails.application.routes.draw do
namespace :internal do
resources :comments
resources :articles, except: [:show]
resources :articles
resources :tags
resources :welcome, only: [:index, :create]
resources :broadcasts
@ -207,6 +207,7 @@ Rails.application.routes.draw do
get "/:username/comment/:id_code/edit" => 'comments#edit'
get "/:username/comment/:id_code/delete_confirm" => 'comments#delete_confirm'
get "/:username/:slug/:view" => 'stories#show', constraints: { view: /moderate/}
get "/:username/:slug/edit" => 'articles#edit'
get "/:username/:slug/delete_confirm" => 'articles#delete_confirm'
get "/:username/:view" => 'stories#index', constraints: { view: /comments|moderate|admin/}

View file

@ -0,0 +1,5 @@
class AddLastCommentAtToArticles < ActiveRecord::Migration[5.1]
def change
add_column :articles, :last_comment_at, :datetime, default: "2017-01-01 05:00:00"
end
end

View file

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20180213165354) do
ActiveRecord::Schema.define(version: 20180304151124) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -105,6 +105,7 @@ ActiveRecord::Schema.define(version: 20180213165354) do
t.string "video_code"
t.string "video_source_url"
t.string "video_thumbnail_url"
t.datetime "last_comment_at", default: "2017-01-01 05:00:00"
t.index ["featured_number"], name: "index_articles_on_featured_number"
t.index ["hotness_score"], name: "index_articles_on_hotness_score"
t.index ["published_at"], name: "index_articles_on_published_at"

View file

@ -149,6 +149,10 @@ RSpec.describe Comment, type: :model do
expect(child_comment.parent_or_root_article).to eq(comment)
end
it "properly indexes" do
comment.index!
end
describe "#parent_user" do
it "returns the root article's user if no parent comment" do
expect(comment.parent_user).to eq(user)
@ -169,6 +173,7 @@ RSpec.describe Comment, type: :model do
expect(comment.processed_html).to include CGI.unescapeHTML(text)
end
end
describe "#custom_css" do
it "returns nothing when no ltag was used" do