Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions app/controllers/concerns/development_dependency_checks.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,22 @@ module DevelopmentDependencyChecks
private

def verify_sidekiq_running
return if Sidekiq::ProcessSet.new.size.positive?
# Check if Sidekiq is running with a small retry mechanism
# This helps with timing issues where Sidekiq might not be fully registered yet
sidekiq_running = false
3.times do |i|
sidekiq_running = Sidekiq::ProcessSet.new.size.positive?
break if sidekiq_running

sleep(0.1) if i < 2 # Small delay before retry
rescue StandardError => e
Rails.logger.debug { "Sidekiq health check attempt #{i + 1} failed: #{e.message}" }
sleep(0.1) if i < 2
end

return if sidekiq_running

flash[:global_notice] = "Sidekiq is not running and is needed for the app to function properly. \
Use bin/startup to start the application properly.".html_safe
Use bin/startup-local to start the application properly.".html_safe
end
end
275 changes: 240 additions & 35 deletions app/views/articles/_fullscreen_embed.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -241,11 +241,11 @@
</div>
</div>

<!-- Comments modal -->
<!-- Universal modal for comments, mod actions, and extra menu -->
<div class="comments-modal" id="comments-modal">
<div class="comments-modal-content">
<div class="comments-modal-header">
<h2 class="crayons-subtitle-1">
<h2 class="crayons-subtitle-1" id="modal-title">
Comments
<% if @comments_count > 0 %>
<span class="js-comments-count" data-comments-count="<%= @comments_count %>">
Expand All @@ -256,12 +256,12 @@
<button
class="comments-modal-close"
id="comments-modal-close"
aria-label="Close comments"
aria-label="Close modal"
type="button">
×
</button>
</div>
<div class="comments-modal-body">
<div class="comments-modal-body" id="modal-body">
<%# Load comments in iframe to avoid encoding issues %>
<iframe
src="<%= @article.path %>/comments"
Expand All @@ -274,73 +274,264 @@
</div>
</div>

<!-- Quickie boost modal -->
<% if user_signed_in? %>
<div id="quickie-wrapper" class="hidden">
<div class="crayons-modal">
<div role="dialog" aria-modal="true" aria-label="modal" class="crayons-modal__box" style="max-width:800px">
<header class="crayons-modal__box__header">
<h2 class="crayons-subtitle-2">Boost Post to the Feed</h2>
<button id="quickie-modal-close" type="button" aria-label="Close" class="c-btn c-btn--icon-alone crayons-modal__dismiss modal-close-el">
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" class="crayons-icon c-btn__icon"><path d="M12 10.586l4.95-4.95 1.414 1.414-4.95 4.95 4.95 4.95-1.414 1.414-4.95-4.95-4.95 4.95-1.414-1.414 4.95-4.95-4.95-4.95L7.05 5.636l4.95 4.95z"></path></svg>
</button>
</header><div class="crayons-modal__box__body">
<div class="h-100 w-100">
<%= render "articles/quickie_form", expanded: true %>
</div>
</div>
</div>
<div class="crayons-modal__backdrop modal-close-el"></div></div>
</div>
<script>
document.querySelectorAll(".modal-close-el").forEach((el) => {
el.addEventListener("click", () => {
document.getElementById("quickie-wrapper").classList.add("hidden");
});
});
</script>
<% end %>

<!-- Mod actions menu -->
<div class="mod-actions-menu print-hidden"></div>

<!-- Hidden extra menu content for modal -->
<div id="extra-menu-content" style="display: none;">
<div style="padding: 20px;">
<div class="crayons-dropdown">
<div class="only-mobile-menu-item">
<button class="mod-actions-menu-btn crayons-link crayons-link--block w-100 bg-transparent border-0 fw-bold">
Moderate
</button>
</div>
<div>
<button
id="copy-post-url-button"
class="flex justify-between crayons-link crayons-link--block w-100 bg-transparent border-0"
data-postUrl="<%= article_url(@article) %>">
<span class="fw-bold"><%= t("views.actions.copy.button") %></span>
<%= crayons_icon_tag(:copy, class: "mx-2 shrink-0", id: "article-copy-icon", aria_hidden: true, title: t("views.actions.copy.button")) %>
</button>
<div id="article-copy-link-announcer" aria-live="polite" class="crayons-notice crayons-notice--success my-2 p-1" aria-live="polite" hidden><%= t("views.actions.copy.text") %></div>
</div>
<div class="Desktop-only">
<a
target="_blank"
class="crayons-link crayons-link--block"
rel="noopener"
href='https://twitter.com/intent/tweet?text=<%= u t("views.actions.share.twitter.query", article: @article.title.strip, author: @article.user.twitter_username ? "@#{@article.user.twitter_username}" : @article.user.name) %><%= u " #{Settings::General.twitter_hashtag}" %><%= u " #{article_url(@article)}" %>'>
<%= t("views.actions.share.twitter.text") %>
</a>
<a
target="_blank"
class="crayons-link crayons-link--block"
rel="noopener"
href="https://www.linkedin.com/shareArticle?mini=true&url=<%= u article_url(@article) %>&title=<%= u @article.title %>&summary=<%= u @article.description %>&source=<%= u community_name %>">
<%= t("views.actions.share.linkedin.text") %>
</a>
<a
target="_blank"
class="crayons-link crayons-link--block"
rel="noopener"
href="https://www.facebook.com/sharer.php?u=<%= u article_url(@article) %>">
<%= t("views.actions.share.facebook.text") %>
</a>
<a
target="_blank"
class="crayons-link crayons-link--block"
rel="noopener"
href="https://toot.kytta.dev/?text=<%= u article_url(@article) %>">
<%= t("views.actions.share.mastodon.text") %>
</a>
</div>
<web-share-wrapper shareurl="<%= article_url(@article) %>" sharetitle="<%= @article.title %>" sharetext="<%= @article.description %>" template="web-share-button">
</web-share-wrapper>
<template id="web-share-button">
<a href="#" class="dropdown-link-row crayons-link crayons-link--block"><%= t("views.actions.share.link") %></a>
</template>
<a href="/report-abuse" class="crayons-link crayons-link--block"><%= t("views.actions.share.report") %></a>
</div>
</div>
</div>

<script>
// Fullscreen embed functionality
document.addEventListener('DOMContentLoaded', function() {
function initializeFullscreenEmbed() {
// Only run this for fullscreen embeds
if (document.querySelector('#article-body[data-type-of="fullscreen_embed"]')) {
// Wait a bit for articleReactions.js to finish processing, then override
setTimeout(() => {
const commentsToggle = document.getElementById('reaction-butt-comment');
const commentsModal = document.getElementById('comments-modal');
const commentsModalClose = document.getElementById('comments-modal-close');
const modalTitle = document.getElementById('modal-title');
const modalBody = document.getElementById('modal-body');

console.log('Fullscreen embed detected, modal elements found:', { commentsToggle, commentsModal, commentsModalClose });

if (commentsToggle && commentsModal && commentsModalClose) {
// Remove any existing onclick handlers to prevent conflicts
commentsToggle.onclick = null;
commentsToggle.removeAttribute('data-category');

// Add a custom attribute to prevent articleReactions.js from interfering
commentsToggle.setAttribute('data-fullscreen-embed', 'true');

// Show comments modal
commentsToggle.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
console.log('Opening comments modal');
if (commentsModal && commentsModalClose && modalTitle && modalBody) {
// Helper function to show modal with custom content
function showModal(title, content) {
modalTitle.innerHTML = title;
modalBody.innerHTML = content;
commentsModal.classList.add('show');
document.body.style.overflow = 'hidden';
// Focus the close button for accessibility
commentsModalClose.focus();

// Initialize interactive elements within the modal
initializeModalContent();
}

// Initialize interactive elements within the modal
function initializeModalContent() {
// Copy button functionality
const copyButton = document.getElementById('copy-post-url-button');
if (copyButton) {
copyButton.addEventListener('click', function(e) {
e.preventDefault();
const postUrl = this.getAttribute('data-postUrl');
navigator.clipboard.writeText(postUrl).then(() => {
const announcer = document.getElementById('article-copy-link-announcer');
if (announcer) {
announcer.hidden = false;
setTimeout(() => {
announcer.hidden = true;
}, 3000);
}
});
});
}

// Mod actions button within the extra menu
const modalModButtons = modalBody.querySelectorAll('.mod-actions-menu-btn');
modalModButtons.forEach(button => {
button.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
console.log('Opening mod actions modal from extra menu');

const modTitle = 'Moderation Actions';
const modContent = `<iframe
src="<%= @article.path %>/actions_panel"
frameborder="0"
style="width: 100%; height: 100%; min-height: 400px; border: none;"
title="Moderation panel actions"
loading="lazy">
</iframe>`;

showModal(modTitle, modContent);
});
});
}

// Helper function to close modal
function closeModal() {
commentsModal.classList.remove('show');
document.body.style.overflow = '';
console.log('Modal closed, classes:', commentsModal.className);
}

// Comments button functionality
if (commentsToggle && !commentsToggle.hasAttribute('data-fullscreen-embed')) {
commentsToggle.onclick = null;
commentsToggle.removeAttribute('data-category');
commentsToggle.setAttribute('data-fullscreen-embed', 'true');

commentsToggle.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
console.log('Opening comments modal');

const commentsTitle = `Comments${document.querySelector('.js-comments-count') ? ' ' + document.querySelector('.js-comments-count').outerHTML : ''}`;
const commentsContent = `<iframe
src="<%= @article.path %>/comments"
frameborder="0"
style="width: 100%; height: 100%; min-height: 400px; border: none;"
title="Article comments"
loading="lazy">
</iframe>`;

showModal(commentsTitle, commentsContent);
});
}

// Mod actions button functionality
const modButtons = document.querySelectorAll('.mod-actions-menu-btn');
modButtons.forEach(button => {
if (!button.hasAttribute('data-fullscreen-embed')) {
button.setAttribute('data-fullscreen-embed', 'true');
button.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
console.log('Opening mod actions modal');

const modTitle = 'Moderation Actions';
const modContent = `<iframe
src="<%= @article.path %>/actions_panel"
frameborder="0"
style="width: 100%; height: 100%; min-height: 400px; border: none;"
title="Moderation panel actions"
loading="lazy">
</iframe>`;

showModal(modTitle, modContent);
});
}
});

// Hide comments modal
// Extra menu (more options) functionality
const moreButton = document.getElementById('article-show-more-button');
if (moreButton && !moreButton.hasAttribute('data-fullscreen-embed')) {
moreButton.setAttribute('data-fullscreen-embed', 'true');
moreButton.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
console.log('Opening extra menu modal');

const moreTitle = 'More Options';
const moreContent = document.getElementById('extra-menu-content').innerHTML;

showModal(moreTitle, moreContent);
});
}

// Modal close functionality
commentsModalClose.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
console.log('Closing comments modal via close button');
console.log('Closing modal via close button');
closeModal();
});

// Close modal when clicking outside
commentsModal.addEventListener('click', function(e) {
if (e.target === commentsModal) {
console.log('Closing comments modal via outside click');
console.log('Closing modal via outside click');
closeModal();
}
});

// Close modal with Escape key
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape' && commentsModal.classList.contains('show')) {
console.log('Closing comments modal via Escape key');
console.log('Closing modal via Escape key');
closeModal();
}
});

// Helper function to close modal
function closeModal() {
commentsModal.classList.remove('show');
document.body.style.overflow = '';
// Return focus to the toggle button
commentsToggle.focus();
console.log('Modal closed, classes:', commentsModal.className);
}
} else {
console.error('Missing modal elements:', { commentsToggle, commentsModal, commentsModalClose });
console.error('Missing modal elements:', { commentsModal, commentsModalClose, modalTitle, modalBody });
}
}, 100); // Wait for articleReactions.js to finish
}
Expand Down Expand Up @@ -375,7 +566,21 @@
attributeFilter: ['data-side-nav-visible']
});
}
});
}

// Initialize on DOMContentLoaded
document.addEventListener('DOMContentLoaded', initializeFullscreenEmbed);

// Also initialize on InstantClick navigation
if (typeof InstantClick !== 'undefined') {
InstantClick.on('change', initializeFullscreenEmbed);
}
</script>

<%= javascript_include_tag "billboard", "localizeArticleDates", "articleReactions", defer: true %>

<% if user_signed_in? %>
<%= javascript_include_tag "articlePage", "articleModerationTools", defer: true %>
<% else %>
<%= javascript_include_tag "articlePage", defer: true %>
<% end %>
12 changes: 12 additions & 0 deletions bin/startup-local
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/usr/bin/env bash

# Set environment variables for local development
export DATABASE_URL="postgres://postgres:postgres@localhost:5432/Forem_development"
export DATABASE_URL_TEST="postgres://postgres:postgres@localhost:5432/Forem_test"
export REDIS_URL="redis://localhost:6379/"
export REDIS_SIDEKIQ_URL="redis://localhost:6379"
export REDIS_SESSIONS_URL="redis://localhost:6379"
export REDIS_RPUSH_URL="redis://localhost:6379"

# Start the application with the correct environment variables
exec bin/startup
Loading
Loading