Merge pull request #1498 from basecamp/rename-boards
Rename collections => boards
This commit is contained in:
|
Before Width: | Height: | Size: 290 B After Width: | Height: | Size: 290 B |
|
Before Width: | Height: | Size: 286 B After Width: | Height: | Size: 286 B |
@@ -45,7 +45,7 @@
|
||||
|
||||
.bubble__number {
|
||||
display: grid;
|
||||
font-size: clamp(10px, 50cqi, var(--bubble-number-max)); /* FF bug: https://app.fizzy.do/5986089/collections/2/cards/1373 */
|
||||
font-size: clamp(10px, 50cqi, var(--bubble-number-max)); /* FF bug: https://app.fizzy.do/5986089/boards/2/cards/1373 */
|
||||
font-weight: 900;
|
||||
inset: 0;
|
||||
place-content: center;
|
||||
|
||||
@@ -492,7 +492,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.card__collection {
|
||||
.card__board {
|
||||
background-color: transparent;
|
||||
color: var(--card-content-color);
|
||||
}
|
||||
@@ -519,10 +519,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* Collection tools
|
||||
/* Board tools
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
.collection-tools.card {
|
||||
.board-tools.card {
|
||||
--border-color: var(--color-selected-dark);
|
||||
--border-size: 1px;
|
||||
--card-padding-block: var(--block-space);
|
||||
@@ -560,7 +560,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.collection-tools__watching {
|
||||
.board-tools__watching {
|
||||
--btn-size: 32px;
|
||||
--gap: 0.5ch;
|
||||
|
||||
@@ -572,7 +572,7 @@
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.collection-tools__watching-dialog {
|
||||
.board-tools__watching-dialog {
|
||||
--panel-padding: 2ch;
|
||||
--panel-size: 100%;
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.collection-picker__button {
|
||||
.board-picker__button {
|
||||
--btn-border-size: 0;
|
||||
|
||||
font-size: 0.5em;
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.card__collection {
|
||||
.card__board {
|
||||
background-color: var(--card-color);
|
||||
border-radius: var(--border-radius) 0 var(--border-radius) 0;
|
||||
color: var(--color-ink-inverted);
|
||||
@@ -66,7 +66,7 @@
|
||||
padding-block: 0.25lh;
|
||||
padding-inline: var(--card-padding-inline) 1ch;
|
||||
|
||||
&:has(.collection-picker__button) {
|
||||
&:has(.board-picker__button) {
|
||||
cursor: pointer;
|
||||
transition: background-color 100ms ease-out;
|
||||
|
||||
@@ -92,7 +92,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.card__collection-name {
|
||||
.card__board-name {
|
||||
border-inline-start: 1px solid color-mix(in hsl, transparent 75%, currentColor);
|
||||
color: currentColor;
|
||||
display: flex;
|
||||
@@ -404,7 +404,7 @@
|
||||
padding-block-end: 0;
|
||||
}
|
||||
|
||||
.card__collection {
|
||||
.card__board {
|
||||
font-size: var(--text-xx-small);
|
||||
padding-block: 0.5ch;
|
||||
padding-inline-start: var(--inline-space-double);
|
||||
@@ -487,7 +487,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.card__collection-public-description {
|
||||
.card__board-public-description {
|
||||
max-inline-size: 66ch;
|
||||
|
||||
> *:first-child { margin-block-start: 0; }
|
||||
|
||||
@@ -270,7 +270,7 @@
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.card__collection {
|
||||
.card__board {
|
||||
background-color: transparent;
|
||||
color: color-mix(in srgb, var(--card-color) 40%, var(--color-ink));
|
||||
font-size: 0.7em;
|
||||
@@ -278,7 +278,7 @@
|
||||
translate: 0 -0.3em;
|
||||
}
|
||||
|
||||
.card__collection-name {
|
||||
.card__board-name {
|
||||
border-inline-start: 1px solid color-mix(in srgb, var(--color-ink) 33%, var(--color-canvas));
|
||||
color: color-mix(in srgb, var(--card-color) 40%, var(--color-ink));
|
||||
margin: 0 0 0 calc(var(--inline-space) * 0.75);
|
||||
|
||||
@@ -42,8 +42,8 @@
|
||||
.icon--clipboard { --svg: url("clipboard.svg "); }
|
||||
.icon--close { --svg: url("close.svg "); }
|
||||
.icon--close-circle { --svg: url("close-circle.svg "); }
|
||||
.icon--collection { --svg: url("collection.svg "); }
|
||||
.icon--collection-add { --svg: url("collection-add.svg "); }
|
||||
.icon--board { --svg: url("board.svg "); }
|
||||
.icon--board-add { --svg: url("board-add.svg "); }
|
||||
.icon--column-left { --svg: url("column-left.svg "); }
|
||||
.icon--column-right { --svg: url("column-right.svg "); }
|
||||
.icon--comment { --svg: url("comment.svg "); }
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
}
|
||||
|
||||
.card {
|
||||
.card__collection {
|
||||
.card__board {
|
||||
background: var(--color-canvas);
|
||||
border-block-end: var(--border-light);
|
||||
border-inline-end: var(--border-light);
|
||||
@@ -118,7 +118,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* Collections
|
||||
/* Boards
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
.filters,
|
||||
@@ -181,7 +181,7 @@
|
||||
.card__meta-avatars--assignees > div > div:last-child,
|
||||
.steps > .step:last-child,
|
||||
div:has(> .tag-picker__button),
|
||||
div:has(> .collection-picker__button),
|
||||
div:has(> .board-picker__button),
|
||||
.delete-card,
|
||||
.header--card .header__title {
|
||||
display: none;
|
||||
|
||||
@@ -314,7 +314,7 @@
|
||||
padding-block-end: 0;
|
||||
}
|
||||
|
||||
.card__collection {
|
||||
.card__board {
|
||||
padding-block: 0.25ch;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,4 +7,6 @@ class ApplicationController < ActionController::Base
|
||||
|
||||
stale_when_importmap_changes
|
||||
allow_browser versions: :modern
|
||||
|
||||
etag { "v1" } # 2025-11-05 @todo: To invalidate HTTP cache after big renaming. To remove after a few days.
|
||||
end
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
class Boards::Columns::ClosedsController < ApplicationController
|
||||
include BoardScoped
|
||||
|
||||
def show
|
||||
set_page_and_extract_portion_from @board.cards.closed.recently_closed_first
|
||||
fresh_when etag: @page.records
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,8 @@
|
||||
class Boards::Columns::NotNowsController < ApplicationController
|
||||
include BoardScoped
|
||||
|
||||
def show
|
||||
set_page_and_extract_portion_from @board.cards.postponed.reverse_chronologically.with_golden_first
|
||||
fresh_when etag: @page.records
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,8 @@
|
||||
class Boards::Columns::StreamsController < ApplicationController
|
||||
include BoardScoped
|
||||
|
||||
def show
|
||||
set_page_and_extract_portion_from @board.cards.awaiting_triage.latest.with_golden_first
|
||||
fresh_when etag: @page.records
|
||||
end
|
||||
end
|
||||
+4
-4
@@ -1,5 +1,5 @@
|
||||
class Collections::ColumnsController < ApplicationController
|
||||
include CollectionScoped
|
||||
class Boards::ColumnsController < ApplicationController
|
||||
include BoardScoped
|
||||
|
||||
before_action :set_column, only: %i[ show update destroy ]
|
||||
|
||||
@@ -9,7 +9,7 @@ class Collections::ColumnsController < ApplicationController
|
||||
end
|
||||
|
||||
def create
|
||||
@column = @collection.columns.create!(column_params)
|
||||
@column = @board.columns.create!(column_params)
|
||||
end
|
||||
|
||||
def update
|
||||
@@ -22,7 +22,7 @@ class Collections::ColumnsController < ApplicationController
|
||||
|
||||
private
|
||||
def set_column
|
||||
@column = @collection.columns.find(params[:id])
|
||||
@column = @board.columns.find(params[:id])
|
||||
end
|
||||
|
||||
def column_params
|
||||
@@ -0,0 +1,12 @@
|
||||
class Boards::EntropiesController < ApplicationController
|
||||
include BoardScoped
|
||||
|
||||
def update
|
||||
@board.entropy.update!(entropy_params)
|
||||
end
|
||||
|
||||
private
|
||||
def entropy_params
|
||||
params.expect(board: [ :auto_postpone_period ])
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,7 @@
|
||||
class Boards::InvolvementsController < ApplicationController
|
||||
include BoardScoped
|
||||
|
||||
def update
|
||||
@board.access_for(Current.user).update!(involvement: params[:involvement])
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,12 @@
|
||||
class Boards::PublicationsController < ApplicationController
|
||||
include BoardScoped
|
||||
|
||||
def create
|
||||
@board.publish
|
||||
end
|
||||
|
||||
def destroy
|
||||
@board.unpublish
|
||||
@board.reload
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,94 @@
|
||||
class BoardsController < ApplicationController
|
||||
include FilterScoped
|
||||
|
||||
before_action :set_board, except: %i[ new create ]
|
||||
before_action :ensure_permission_to_change_accesses, only: %i[ update ]
|
||||
|
||||
def show
|
||||
if @filter.used?(ignore_boards: true)
|
||||
show_filtered_cards
|
||||
else
|
||||
show_columns
|
||||
end
|
||||
end
|
||||
|
||||
def new
|
||||
@board = Board.new
|
||||
end
|
||||
|
||||
def create
|
||||
@board = Board.create! board_params.with_defaults(all_access: true)
|
||||
redirect_to board_path(@board)
|
||||
end
|
||||
|
||||
def edit
|
||||
selected_user_ids = @board.users.pluck :id
|
||||
@selected_users, @unselected_users = \
|
||||
User.active.alphabetically.partition { |user| selected_user_ids.include? user.id }
|
||||
end
|
||||
|
||||
def update
|
||||
@board.update! board_params
|
||||
@board.accesses.revise granted: grantees, revoked: revokees if grantees_changed?
|
||||
|
||||
if @board.accessible_to?(Current.user)
|
||||
redirect_to edit_board_path(@board), notice: "Saved"
|
||||
else
|
||||
redirect_to root_path, notice: "Saved (you were removed from the board)"
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
@board.destroy
|
||||
redirect_to root_path
|
||||
end
|
||||
|
||||
private
|
||||
def set_board
|
||||
@board = Current.user.boards.find params[:id]
|
||||
end
|
||||
|
||||
def ensure_permission_to_change_accesses
|
||||
if trying_to_change_accesses? && !Current.user.can_administer_board?(@board)
|
||||
head :forbidden
|
||||
end
|
||||
end
|
||||
|
||||
def trying_to_change_accesses?
|
||||
all_access_changed? || grantees_changed?
|
||||
end
|
||||
|
||||
def all_access_changed?
|
||||
params[:board]&.key?(:all_access)
|
||||
end
|
||||
|
||||
def grantees_changed?
|
||||
params.key?(:user_ids)
|
||||
end
|
||||
|
||||
def show_filtered_cards
|
||||
@filter.board_ids = [ @board.id ]
|
||||
set_page_and_extract_portion_from @filter.cards
|
||||
end
|
||||
|
||||
def show_columns
|
||||
set_page_and_extract_portion_from @board.cards.awaiting_triage.latest.with_golden_first
|
||||
fresh_when etag: [ @board, @page.records, @user_filtering ]
|
||||
end
|
||||
|
||||
def board_params
|
||||
params.expect(board: [ :name, :all_access, :auto_postpone_period, :public_description ])
|
||||
end
|
||||
|
||||
def grantees
|
||||
User.active.where id: grantee_ids
|
||||
end
|
||||
|
||||
def revokees
|
||||
@board.users.where.not id: grantee_ids
|
||||
end
|
||||
|
||||
def grantee_ids
|
||||
params.fetch :user_ids, []
|
||||
end
|
||||
end
|
||||
@@ -2,11 +2,11 @@ class Cards::AssignmentsController < ApplicationController
|
||||
include CardScoped
|
||||
|
||||
def new
|
||||
@users = @collection.users.active.alphabetically
|
||||
@users = @board.users.active.alphabetically
|
||||
fresh_when @users
|
||||
end
|
||||
|
||||
def create
|
||||
@card.toggle_assignment @collection.users.active.find(params[:assignee_id])
|
||||
@card.toggle_assignment @board.users.active.find(params[:assignee_id])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
class Cards::BoardsController < ApplicationController
|
||||
include BoardScoped
|
||||
|
||||
skip_before_action :set_board, only: %i[ edit ]
|
||||
before_action :set_card
|
||||
|
||||
def edit
|
||||
@boards = Current.user.boards.ordered_by_recently_accessed
|
||||
fresh_when @boards
|
||||
end
|
||||
|
||||
def update
|
||||
@card.move_to(@board)
|
||||
redirect_to @card
|
||||
end
|
||||
|
||||
private
|
||||
def set_card
|
||||
@card = Current.user.accessible_cards.find(params[:card_id])
|
||||
end
|
||||
end
|
||||
@@ -1,21 +0,0 @@
|
||||
class Cards::CollectionsController < ApplicationController
|
||||
include CollectionScoped
|
||||
|
||||
skip_before_action :set_collection, only: %i[ edit ]
|
||||
before_action :set_card
|
||||
|
||||
def edit
|
||||
@collections = Current.user.collections.ordered_by_recently_accessed
|
||||
fresh_when @collections
|
||||
end
|
||||
|
||||
def update
|
||||
@card.move_to(@collection)
|
||||
redirect_to @card
|
||||
end
|
||||
|
||||
private
|
||||
def set_card
|
||||
@card = Current.user.accessible_cards.find(params[:card_id])
|
||||
end
|
||||
end
|
||||
@@ -1,7 +1,7 @@
|
||||
class Cards::ColumnsController < ApplicationController
|
||||
def edit
|
||||
@card = Current.user.accessible_cards.find(params[:card_id])
|
||||
@columns = @card.collection.columns
|
||||
@columns = @card.board.columns
|
||||
|
||||
fresh_when etag: [ @card, @columns ]
|
||||
end
|
||||
|
||||
@@ -5,9 +5,9 @@ class Cards::PublishesController < ApplicationController
|
||||
@card.publish
|
||||
|
||||
if add_another_param?
|
||||
redirect_to @collection.cards.create!, notice: "Card added"
|
||||
redirect_to @board.cards.create!, notice: "Card added"
|
||||
else
|
||||
redirect_to @card.collection
|
||||
redirect_to @card.board
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -5,11 +5,11 @@ class Cards::ReadingsController < ApplicationController
|
||||
|
||||
def create
|
||||
@notifications = @card.read_by(Current.user)
|
||||
record_collection_access
|
||||
record_board_access
|
||||
end
|
||||
|
||||
private
|
||||
def record_collection_access
|
||||
@card.collection.accessed_by(Current.user)
|
||||
def record_board_access
|
||||
@card.board.accessed_by(Current.user)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,7 +2,7 @@ class Cards::TriagesController < ApplicationController
|
||||
include CardScoped
|
||||
|
||||
def create
|
||||
column = @card.collection.columns.find(params[:column_id])
|
||||
column = @card.board.columns.find(params[:column_id])
|
||||
@card.triage_into(column)
|
||||
|
||||
redirect_to @card
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
class CardsController < ApplicationController
|
||||
include FilterScoped
|
||||
|
||||
before_action :set_collection, only: %i[ create ]
|
||||
before_action :set_board, only: %i[ create ]
|
||||
before_action :set_card, only: %i[ show edit update destroy ]
|
||||
before_action :ensure_permission_to_administer_card, only: %i[ destroy ]
|
||||
|
||||
def index
|
||||
set_page_and_extract_portion_from @filter.cards
|
||||
end
|
||||
|
||||
def create
|
||||
card = @collection.cards.find_or_create_by!(creator: Current.user, status: "drafted")
|
||||
card = @board.cards.find_or_create_by!(creator: Current.user, status: "drafted")
|
||||
redirect_to card
|
||||
end
|
||||
|
||||
@@ -30,23 +31,27 @@ class CardsController < ApplicationController
|
||||
|
||||
def destroy
|
||||
@card.destroy!
|
||||
redirect_to @card.collection, notice: "Card deleted"
|
||||
redirect_to @card.board, notice: "Card deleted"
|
||||
end
|
||||
|
||||
private
|
||||
def set_collection
|
||||
@collection = Current.user.collections.find params[:collection_id]
|
||||
def set_board
|
||||
@board = Current.user.boards.find params[:board_id]
|
||||
end
|
||||
|
||||
def set_card
|
||||
@card = Current.user.accessible_cards.find params[:id]
|
||||
end
|
||||
|
||||
def ensure_permission_to_administer_card
|
||||
head :forbidden unless Current.user.can_administer_card?(@card)
|
||||
end
|
||||
|
||||
def suppressing_broadcasts_unless_published(card, &block)
|
||||
if card.published?
|
||||
yield
|
||||
else
|
||||
Collection.suppressing_turbo_broadcasts(&block)
|
||||
Board.suppressing_turbo_broadcasts(&block)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
class Collections::Columns::ClosedsController < ApplicationController
|
||||
include CollectionScoped
|
||||
|
||||
def show
|
||||
set_page_and_extract_portion_from @collection.cards.closed.recently_closed_first
|
||||
fresh_when etag: @page.records
|
||||
end
|
||||
end
|
||||
@@ -1,8 +0,0 @@
|
||||
class Collections::Columns::NotNowsController < ApplicationController
|
||||
include CollectionScoped
|
||||
|
||||
def show
|
||||
set_page_and_extract_portion_from @collection.cards.postponed.reverse_chronologically.with_golden_first
|
||||
fresh_when etag: @page.records
|
||||
end
|
||||
end
|
||||
@@ -1,8 +0,0 @@
|
||||
class Collections::Columns::StreamsController < ApplicationController
|
||||
include CollectionScoped
|
||||
|
||||
def show
|
||||
set_page_and_extract_portion_from @collection.cards.awaiting_triage.latest.with_golden_first
|
||||
fresh_when etag: @page.records
|
||||
end
|
||||
end
|
||||
@@ -1,12 +0,0 @@
|
||||
class Collections::EntropiesController < ApplicationController
|
||||
include CollectionScoped
|
||||
|
||||
def update
|
||||
@collection.entropy.update!(entropy_params)
|
||||
end
|
||||
|
||||
private
|
||||
def entropy_params
|
||||
params.expect(collection: [ :auto_postpone_period ])
|
||||
end
|
||||
end
|
||||
@@ -1,7 +0,0 @@
|
||||
class Collections::InvolvementsController < ApplicationController
|
||||
include CollectionScoped
|
||||
|
||||
def update
|
||||
@collection.access_for(Current.user).update!(involvement: params[:involvement])
|
||||
end
|
||||
end
|
||||
@@ -1,12 +0,0 @@
|
||||
class Collections::PublicationsController < ApplicationController
|
||||
include CollectionScoped
|
||||
|
||||
def create
|
||||
@collection.publish
|
||||
end
|
||||
|
||||
def destroy
|
||||
@collection.unpublish
|
||||
@collection.reload
|
||||
end
|
||||
end
|
||||
@@ -1,79 +0,0 @@
|
||||
class CollectionsController < ApplicationController
|
||||
include FilterScoped
|
||||
|
||||
before_action :set_collection, except: %i[ new create ]
|
||||
|
||||
def show
|
||||
if @filter.used?(ignore_collections: true)
|
||||
show_filtered_cards
|
||||
else
|
||||
show_columns
|
||||
end
|
||||
end
|
||||
|
||||
def new
|
||||
@collection = Collection.new
|
||||
end
|
||||
|
||||
def create
|
||||
@collection = Collection.create! collection_params.with_defaults(all_access: true)
|
||||
redirect_to collection_path(@collection)
|
||||
end
|
||||
|
||||
def edit
|
||||
selected_user_ids = @collection.users.pluck :id
|
||||
@selected_users, @unselected_users = \
|
||||
User.active.alphabetically.partition { |user| selected_user_ids.include? user.id }
|
||||
end
|
||||
|
||||
def update
|
||||
@collection.update! collection_params
|
||||
@collection.accesses.revise granted: grantees, revoked: revokees if grantees_changed?
|
||||
|
||||
if @collection.accessible_to?(Current.user)
|
||||
redirect_to edit_collection_path(@collection), notice: "Saved"
|
||||
else
|
||||
redirect_to root_path, notice: "Saved (you were removed from the collection)"
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
@collection.destroy
|
||||
redirect_to root_path
|
||||
end
|
||||
|
||||
private
|
||||
def set_collection
|
||||
@collection = Current.user.collections.find params[:id]
|
||||
end
|
||||
|
||||
def show_filtered_cards
|
||||
@filter.collection_ids = [ @collection.id ]
|
||||
set_page_and_extract_portion_from @filter.cards
|
||||
end
|
||||
|
||||
def show_columns
|
||||
set_page_and_extract_portion_from @collection.cards.awaiting_triage.latest.with_golden_first
|
||||
fresh_when etag: [ @collection, @page.records, @user_filtering ]
|
||||
end
|
||||
|
||||
def collection_params
|
||||
params.expect(collection: [ :name, :all_access, :auto_postpone_period, :public_description ])
|
||||
end
|
||||
|
||||
def grantees
|
||||
User.active.where id: grantee_ids
|
||||
end
|
||||
|
||||
def revokees
|
||||
@collection.users.where.not id: grantee_ids
|
||||
end
|
||||
|
||||
def grantee_ids
|
||||
params.fetch :user_ids, []
|
||||
end
|
||||
|
||||
def grantees_changed?
|
||||
params.key?(:user_ids)
|
||||
end
|
||||
end
|
||||
@@ -2,7 +2,7 @@ class Columns::Cards::Drops::ColumnsController < ApplicationController
|
||||
include CardScoped
|
||||
|
||||
def create
|
||||
@column = @card.collection.columns.find(params[:column_id])
|
||||
@column = @card.board.columns.find(params[:column_id])
|
||||
@card.triage_into(@column)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,6 +3,6 @@ class Columns::Cards::Drops::StreamsController < ApplicationController
|
||||
|
||||
def create
|
||||
@card.send_back_to_triage
|
||||
set_page_and_extract_portion_from @collection.cards.awaiting_triage.latest.with_golden_first
|
||||
set_page_and_extract_portion_from @board.cards.awaiting_triage.latest.with_golden_first
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
module BoardScoped
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
before_action :set_board
|
||||
end
|
||||
|
||||
private
|
||||
def set_board
|
||||
@board = Current.user.boards.find(params[:board_id])
|
||||
end
|
||||
end
|
||||
@@ -2,7 +2,7 @@ module CardScoped
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
before_action :set_card, :set_collection
|
||||
before_action :set_card, :set_board
|
||||
end
|
||||
|
||||
private
|
||||
@@ -10,8 +10,8 @@ module CardScoped
|
||||
@card = Current.user.accessible_cards.find(params[:card_id])
|
||||
end
|
||||
|
||||
def set_collection
|
||||
@collection = @card.collection
|
||||
def set_board
|
||||
@board = @card.board
|
||||
end
|
||||
|
||||
def render_card_replacement
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
module CollectionScoped
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
before_action :set_collection
|
||||
end
|
||||
|
||||
private
|
||||
def set_collection
|
||||
@collection = Current.user.collections.find(params[:collection_id])
|
||||
end
|
||||
end
|
||||
@@ -1,7 +1,7 @@
|
||||
class LandingsController < ApplicationController
|
||||
def show
|
||||
if Current.user.collections.one?
|
||||
redirect_to collection_path(Current.user.collections.first)
|
||||
if Current.user.boards.one?
|
||||
redirect_to board_path(Current.user.boards.first)
|
||||
else
|
||||
redirect_to events_path
|
||||
end
|
||||
|
||||
@@ -2,7 +2,7 @@ class Notifications::SettingsController < ApplicationController
|
||||
before_action :set_settings
|
||||
|
||||
def show
|
||||
@collections = Current.user.collections.alphabetically
|
||||
@boards = Current.user.boards.alphabetically
|
||||
end
|
||||
|
||||
def update
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
class Prompts::Boards::UsersController < ApplicationController
|
||||
include BoardScoped
|
||||
|
||||
def index
|
||||
@users = @board.users.alphabetically
|
||||
|
||||
if stale? etag: @users
|
||||
render layout: false
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,11 +0,0 @@
|
||||
class Prompts::Collections::UsersController < ApplicationController
|
||||
include CollectionScoped
|
||||
|
||||
def index
|
||||
@users = @collection.users.alphabetically
|
||||
|
||||
if stale? etag: @users
|
||||
render layout: false
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,17 +1,17 @@
|
||||
class Public::BaseController < ApplicationController
|
||||
allow_unauthenticated_access
|
||||
|
||||
before_action :set_collection, :set_card, :set_public_cache_expiration
|
||||
before_action :set_board, :set_card, :set_public_cache_expiration
|
||||
|
||||
layout "public"
|
||||
|
||||
private
|
||||
def set_collection
|
||||
@collection = Collection.find_by_published_key(params[:collection_id] || params[:id])
|
||||
def set_board
|
||||
@board = Board.find_by_published_key(params[:board_id] || params[:id])
|
||||
end
|
||||
|
||||
def set_card
|
||||
@card = @collection.cards.find(params[:id]) if params[:collection_id] && params[:id]
|
||||
@card = @board.cards.find(params[:id]) if params[:board_id] && params[:id]
|
||||
end
|
||||
|
||||
def set_public_cache_expiration
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
class Public::Boards::Columns::ClosedsController < Public::BaseController
|
||||
def show
|
||||
set_page_and_extract_portion_from @board.cards.closed.recently_closed_first
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,5 @@
|
||||
class Public::Boards::Columns::NotNowsController < Public::BaseController
|
||||
def show
|
||||
set_page_and_extract_portion_from @board.cards.postponed.reverse_chronologically.with_golden_first
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,5 @@
|
||||
class Public::Boards::Columns::StreamsController < Public::BaseController
|
||||
def show
|
||||
set_page_and_extract_portion_from @board.cards.awaiting_triage.latest.with_golden_first
|
||||
end
|
||||
end
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
class Public::Collections::ColumnsController < Public::BaseController
|
||||
class Public::Boards::ColumnsController < Public::BaseController
|
||||
before_action :set_column
|
||||
|
||||
def show
|
||||
@@ -11,6 +11,6 @@ class Public::Collections::ColumnsController < Public::BaseController
|
||||
end
|
||||
|
||||
def set_column
|
||||
@column = @collection.columns.find(params[:id])
|
||||
@column = @board.columns.find(params[:id])
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,5 @@
|
||||
class Public::BoardsController < Public::BaseController
|
||||
def show
|
||||
set_page_and_extract_portion_from @board.cards.awaiting_triage.latest.with_golden_first
|
||||
end
|
||||
end
|
||||
@@ -1,5 +0,0 @@
|
||||
class Public::Collections::Columns::ClosedsController < Public::BaseController
|
||||
def show
|
||||
set_page_and_extract_portion_from @collection.cards.closed.recently_closed_first
|
||||
end
|
||||
end
|
||||
@@ -1,5 +0,0 @@
|
||||
class Public::Collections::Columns::NotNowsController < Public::BaseController
|
||||
def show
|
||||
set_page_and_extract_portion_from @collection.cards.postponed.reverse_chronologically.with_golden_first
|
||||
end
|
||||
end
|
||||
@@ -1,5 +0,0 @@
|
||||
class Public::Collections::Columns::StreamsController < Public::BaseController
|
||||
def show
|
||||
set_page_and_extract_portion_from @collection.cards.awaiting_triage.latest.with_golden_first
|
||||
end
|
||||
end
|
||||
@@ -1,5 +0,0 @@
|
||||
class Public::CollectionsController < Public::BaseController
|
||||
def show
|
||||
set_page_and_extract_portion_from @collection.cards.awaiting_triage.latest.with_golden_first
|
||||
end
|
||||
end
|
||||
@@ -1,22 +1,22 @@
|
||||
class WebhooksController < ApplicationController
|
||||
include CollectionScoped
|
||||
include BoardScoped
|
||||
|
||||
before_action :ensure_admin
|
||||
before_action :set_webhook, except: %i[ index new create ]
|
||||
|
||||
def index
|
||||
set_page_and_extract_portion_from @collection.webhooks.ordered
|
||||
set_page_and_extract_portion_from @board.webhooks.ordered
|
||||
end
|
||||
|
||||
def show
|
||||
end
|
||||
|
||||
def new
|
||||
@webhook = @collection.webhooks.new
|
||||
@webhook = @board.webhooks.new
|
||||
end
|
||||
|
||||
def create
|
||||
webhook = @collection.webhooks.create!(webhook_params)
|
||||
webhook = @board.webhooks.create!(webhook_params)
|
||||
redirect_to webhook
|
||||
end
|
||||
|
||||
@@ -30,17 +30,17 @@ class WebhooksController < ApplicationController
|
||||
|
||||
def destroy
|
||||
@webhook.destroy!
|
||||
redirect_to collection_webhooks_path
|
||||
redirect_to board_webhooks_path
|
||||
end
|
||||
|
||||
private
|
||||
def set_webhook
|
||||
@webhook = @collection.webhooks.find(params[:id])
|
||||
@webhook = @board.webhooks.find(params[:id])
|
||||
end
|
||||
|
||||
def webhook_params
|
||||
params
|
||||
.expect(webhook: [ :name, :url, subscribed_actions: [] ])
|
||||
.merge(collection_id: @collection.id)
|
||||
.merge(board_id: @board.id)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
module AccessesHelper
|
||||
MAX_DISPLAYED_WATCHERS = 8
|
||||
|
||||
def access_menu_tag(collection, **options, &)
|
||||
tag.menu class: [ options[:class], { "toggler--toggled": collection.all_access? } ], data: {
|
||||
def access_menu_tag(board, **options, &)
|
||||
tag.menu class: [ options[:class], { "toggler--toggled": board.all_access? } ], data: {
|
||||
controller: "filter toggle-class navigable-list",
|
||||
action: "keydown->navigable-list#navigate filter:changed->navigable-list#reset",
|
||||
navigable_list_focus_on_selection_value: true,
|
||||
@@ -10,36 +10,36 @@ module AccessesHelper
|
||||
toggle_class_toggle_class: "toggler--toggled" }, &
|
||||
end
|
||||
|
||||
def access_toggles_for(users, selected:)
|
||||
render partial: "collections/access_toggle",
|
||||
def access_toggles_for(users, selected:, disabled: false)
|
||||
render partial: "boards/access_toggle",
|
||||
collection: users, as: :user,
|
||||
locals: { selected: selected },
|
||||
locals: { selected: selected, disabled: disabled },
|
||||
cached: ->(user) { [ user, selected ] }
|
||||
end
|
||||
|
||||
def access_involvement_advance_button(collection, user, show_watchers: true, icon_only: false)
|
||||
access = collection.access_for(user)
|
||||
def access_involvement_advance_button(board, user, show_watchers: true, icon_only: false)
|
||||
access = board.access_for(user)
|
||||
|
||||
turbo_frame_tag dom_id(collection, :involvement_button) do
|
||||
concat collection_watchers_list(collection) if show_watchers
|
||||
concat involvement_button(collection, access, show_watchers, icon_only)
|
||||
turbo_frame_tag dom_id(board, :involvement_button) do
|
||||
concat board_watchers_list(board) if show_watchers
|
||||
concat involvement_button(board, access, show_watchers, icon_only)
|
||||
end
|
||||
end
|
||||
|
||||
def collection_watchers_list(collection)
|
||||
watchers = collection.watchers
|
||||
def board_watchers_list(board)
|
||||
watchers = board.watchers
|
||||
|
||||
displayed_watchers = watchers.limit(MAX_DISPLAYED_WATCHERS)
|
||||
overflow_count = watchers.count - MAX_DISPLAYED_WATCHERS
|
||||
|
||||
safe_join([
|
||||
tag.strong(displayed_watchers.any? ? "Watching for new cards" : "No one is watching for new cards", class: "txt-uppercase"),
|
||||
tag.div(class: "collection-tools__watching") do
|
||||
tag.div(class: "board-tools__watching") do
|
||||
safe_join([
|
||||
safe_join(displayed_watchers.map { |watcher| avatar_tag(watcher) }),
|
||||
(tag.div(data: { controller: "dialog", action: "keydown.esc->dialog#close click@document->dialog#closeOnClickOutside" }) do
|
||||
concat tag.button("+#{overflow_count}", class: "overflow-count btn btn--circle borderless", data: { action: "dialog#open" }, aria: { label: "Show #{overflow_count} more watchers" })
|
||||
concat(tag.dialog(class: "collection-tools__watching-dialog dialog panel", data: { dialog_target: "dialog" }, aria: { hidden: "true" }) do
|
||||
concat(tag.dialog(class: "board-tools__watching-dialog dialog panel", data: { dialog_target: "dialog" }, aria: { hidden: "true" }) do
|
||||
safe_join(watchers.map { |watcher| avatar_tag(watcher) })
|
||||
end)
|
||||
end if overflow_count > 0)
|
||||
@@ -48,11 +48,11 @@ module AccessesHelper
|
||||
])
|
||||
end
|
||||
|
||||
def involvement_button(collection, access, show_watchers, icon_only)
|
||||
involvement_label_id = dom_id(collection, :involvement_label)
|
||||
def involvement_button(board, access, show_watchers, icon_only)
|
||||
involvement_label_id = dom_id(board, :involvement_label)
|
||||
|
||||
button_to(
|
||||
collection_involvement_path(collection),
|
||||
board_involvement_path(board),
|
||||
method: :put,
|
||||
aria: { labelledby: involvement_label_id },
|
||||
class: class_names("btn", { "btn--reversed": access.watching? && icon_only }),
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
module BoardsHelper
|
||||
def link_back_to_board(board)
|
||||
link_to board, class: "btn borderless txt-medium",
|
||||
data: { controller: "hotkey", action: "keydown.esc@document->hotkey#click click->turbo-navigation#backIfSamePath" } do
|
||||
tag.span ("←" + tag.strong(board.name, class: "overflow-ellipsis")).html_safe
|
||||
end
|
||||
end
|
||||
|
||||
def link_to_edit_board(board)
|
||||
link_to edit_board_path(board), class: "btn", data: { controller: "tooltip" } do
|
||||
icon_tag("settings") + tag.span("Settings for #{board.name}", class: "for-screen-reader")
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -27,14 +27,14 @@ module CardsHelper
|
||||
title = [
|
||||
card.title,
|
||||
"added by #{card.creator.name}",
|
||||
"in #{card.collection.name}"
|
||||
"in #{card.board.name}"
|
||||
]
|
||||
title << "assigned to #{card.assignees.map(&:name).to_sentence}" if card.assignees.any?
|
||||
title.join(" ")
|
||||
end
|
||||
|
||||
def card_social_tags(card)
|
||||
tag.meta(property: "og:title", content: "#{card.title} | #{card.collection.name}") +
|
||||
tag.meta(property: "og:title", content: "#{card.title} | #{card.board.name}") +
|
||||
tag.meta(property: "og:description", content: format_excerpt(@card&.description, length: 200)) +
|
||||
tag.meta(property: "og:image", content: @card.image.attached? ? "#{request.base_url}#{url_for(@card.image)}" : "#{request.base_url}/app-icon.png") +
|
||||
tag.meta(property: "og:url", content: card_url(@card))
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
module CollectionsHelper
|
||||
def link_back_to_collection(collection)
|
||||
link_to collection, class: "btn borderless txt-medium",
|
||||
data: { controller: "hotkey", action: "keydown.esc@document->hotkey#click click->turbo-navigation#backIfSamePath" } do
|
||||
tag.span ("←" + tag.strong(collection.name, class: "overflow-ellipsis")).html_safe
|
||||
end
|
||||
end
|
||||
|
||||
def link_to_edit_collection(collection)
|
||||
link_to edit_collection_path(collection), class: "btn", data: { controller: "tooltip" } do
|
||||
icon_tag("settings") + tag.span("Settings for #{collection.name}", class: "for-screen-reader")
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -102,8 +102,8 @@ module EventsHelper
|
||||
"#{event_creator_name(event)} resumed #{event_card_title(card)}"
|
||||
when "card_title_changed"
|
||||
"#{event_creator_name(event)} renamed #{event_card_title(card)} (was: “#{h event.particulars.dig('particulars', 'old_title')}”)"
|
||||
when "card_collection_changed"
|
||||
"#{event_creator_name(event)} moved #{event_card_title(card)} to “#{h event.particulars.dig('particulars', 'new_collection')}”"
|
||||
when "card_board_changed", "card_collection_changed"
|
||||
"#{event_creator_name(event)} moved #{event_card_title(card)} to “#{h (event.particulars.dig('particulars', 'new_board') || event.particulars.dig('particulars', 'new_collection'))}”"
|
||||
when "card_triaged"
|
||||
"#{event_creator_name(event)} moved #{event_card_title(card)} to “#{h event.particulars.dig('particulars', 'column')}”"
|
||||
when "card_sent_back_to_triage"
|
||||
@@ -129,7 +129,7 @@ module EventsHelper
|
||||
"comment"
|
||||
when "card_title_changed"
|
||||
"rename"
|
||||
when "card_collection_changed"
|
||||
when "card_board_changed"
|
||||
"move"
|
||||
else
|
||||
"person"
|
||||
|
||||
@@ -11,8 +11,8 @@ module FiltersHelper
|
||||
hidden_field_tag name, value, id: nil
|
||||
end
|
||||
|
||||
def filter_selected_collections_title(user_filtering)
|
||||
user_filtering.selected_collection_titles.collect { tag.strong it }.to_sentence.html_safe
|
||||
def filter_selected_boards_title(user_filtering)
|
||||
user_filtering.selected_board_titles.collect { tag.strong it }.to_sentence.html_safe
|
||||
end
|
||||
|
||||
def filter_place_menu_item(path, label, icon, new_window: false, current: false)
|
||||
|
||||
@@ -3,7 +3,7 @@ module My::MenuHelper
|
||||
text_field_tag :search, nil,
|
||||
type: "search",
|
||||
role: "combobox",
|
||||
placeholder: "Type to jump to a collection, person, place, or tag…",
|
||||
placeholder: "Type to jump to a board, person, place, or tag…",
|
||||
class: "input input--transparent txt-small",
|
||||
autofocus: true,
|
||||
autocorrect: "off",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module RichTextHelper
|
||||
def mentions_prompt(collection)
|
||||
content_tag "lexxy-prompt", "", trigger: "@", src: prompts_collection_users_path(collection), name: "mention"
|
||||
def mentions_prompt(board)
|
||||
content_tag "lexxy-prompt", "", trigger: "@", src: prompts_board_users_path(board), name: "mention"
|
||||
end
|
||||
|
||||
def global_mentions_prompt
|
||||
@@ -19,7 +19,7 @@ module RichTextHelper
|
||||
content_tag "lexxy-code-language-picker"
|
||||
end
|
||||
|
||||
def general_prompts(collection)
|
||||
safe_join([ mentions_prompt(collection), cards_prompt, code_language_picker ])
|
||||
def general_prompts(board)
|
||||
safe_join([ mentions_prompt(board), cards_prompt, code_language_picker ])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,7 +2,7 @@ module WebhooksHelper
|
||||
ACTION_LABELS = {
|
||||
card_published: "Card added",
|
||||
card_title_changed: "Card title changed",
|
||||
card_collection_changed: "Card collection changed",
|
||||
card_board_changed: "Card board changed",
|
||||
comment_created: "Comment added",
|
||||
card_assigned: "Card assigned",
|
||||
card_unassigned: "Card unassigned",
|
||||
@@ -22,9 +22,9 @@ module WebhooksHelper
|
||||
ACTION_LABELS[action] || action.to_s.humanize
|
||||
end
|
||||
|
||||
def link_to_webhooks(collection, &)
|
||||
link_to collection_webhooks_path(collection_id: collection),
|
||||
class: [ "btn", { "btn--reversed": collection.webhooks.any? } ],
|
||||
def link_to_webhooks(board, &)
|
||||
link_to board_webhooks_path(board_id: board),
|
||||
class: [ "btn", { "btn--reversed": board.webhooks.any? } ],
|
||||
data: { controller: "tooltip" } do
|
||||
icon_tag("world") + tag.span("Webhooks", class: "for-screen-reader")
|
||||
end
|
||||
|
||||
@@ -5,7 +5,7 @@ export default class extends Controller {
|
||||
static classes = [ "collapsed", "noTransitions" ]
|
||||
static targets = [ "column", "button" ]
|
||||
static values = {
|
||||
collection: String
|
||||
board: String
|
||||
}
|
||||
|
||||
initialize() {
|
||||
@@ -104,6 +104,6 @@ export default class extends Controller {
|
||||
}
|
||||
|
||||
#localStorageKeyFor(column) {
|
||||
return `expand-${this.collectionValue}-${column.getAttribute("id")}`
|
||||
return `expand-${this.boardValue}-${column.getAttribute("id")}`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ export default class extends Controller {
|
||||
|
||||
checkNone() {
|
||||
this.checkboxTargets.forEach(checkbox => {
|
||||
if (checkbox.dataset.collectionsFormTarget === "meCheckbox") return
|
||||
if (checkbox.dataset.boardsFormTarget === "meCheckbox") return
|
||||
checkbox.checked = false
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
class Board::CleanInaccessibleDataJob < ApplicationJob
|
||||
def perform(user, board)
|
||||
board.clean_inaccessible_data_for(user)
|
||||
end
|
||||
end
|
||||
@@ -1,5 +0,0 @@
|
||||
class Collection::CleanInaccessibleDataJob < ApplicationJob
|
||||
def perform(user, collection)
|
||||
collection.clean_inaccessible_data_for(user)
|
||||
end
|
||||
end
|
||||
@@ -1,5 +1,5 @@
|
||||
class Access < ApplicationRecord
|
||||
belongs_to :collection, touch: true
|
||||
belongs_to :board, touch: true
|
||||
belongs_to :user, touch: true
|
||||
|
||||
enum :involvement, %i[ access_only watching ].index_by(&:itself), default: :access_only
|
||||
@@ -18,6 +18,6 @@ class Access < ApplicationRecord
|
||||
end
|
||||
|
||||
def clean_inaccessible_data_later
|
||||
Collection::CleanInaccessibleDataJob.perform_later(user, collection)
|
||||
Board::CleanInaccessibleDataJob.perform_later(user, board)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -26,9 +26,9 @@ class Account::Seeder
|
||||
|
||||
def populate
|
||||
# ---------------
|
||||
# Playground Collection
|
||||
# Playground Board
|
||||
# ---------------
|
||||
playground = Collection.create! name: "Playground", creator: creator, all_access: true
|
||||
playground = Board.create! name: "Playground", creator: creator, all_access: true
|
||||
|
||||
# Cards
|
||||
playground.cards.create! creator: creator, title: "Watch this: Fizzy orientation video", status: "published", description: <<~HTML
|
||||
@@ -105,7 +105,7 @@ class Account::Seeder
|
||||
|
||||
def delete_everything
|
||||
Current.set session: session do
|
||||
Collection.destroy_all
|
||||
Board.destroy_all
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
class Collection < ApplicationRecord
|
||||
include AutoClosing, Accessible, Broadcastable, Cards, Entropic, Filterable, Publishable, Triageable
|
||||
class Board < ApplicationRecord
|
||||
include AutoPostponing, Accessible, Broadcastable, Cards, Entropic, Filterable, Publishable, Triageable
|
||||
|
||||
belongs_to :creator, class_name: "User", default: -> { Current.user }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
module Collection::Accessible
|
||||
module Board::Accessible
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
@@ -11,7 +11,7 @@ module Collection::Accessible
|
||||
end
|
||||
|
||||
def grant_to(users)
|
||||
Access.insert_all Array(users).collect { |user| { collection_id: proxy_association.owner.id, user_id: user.id } }
|
||||
Access.insert_all Array(users).collect { |user| { board_id: proxy_association.owner.id, user_id: user.id } }
|
||||
end
|
||||
|
||||
def revoke_from(users)
|
||||
@@ -69,7 +69,7 @@ module Collection::Accessible
|
||||
.joins("LEFT JOIN cards ON mentions.source_id = cards.id AND mentions.source_type = 'Card'")
|
||||
.joins("LEFT JOIN comments ON mentions.source_id = comments.id AND mentions.source_type = 'Comment'")
|
||||
.joins("LEFT JOIN cards AS comment_cards ON comments.card_id = comment_cards.id")
|
||||
.where("(mentions.source_type = 'Card' AND cards.collection_id = ?) OR (mentions.source_type = 'Comment' AND comment_cards.collection_id = ?)", id, id)
|
||||
.where("(mentions.source_type = 'Card' AND cards.board_id = ?) OR (mentions.source_type = 'Comment' AND comment_cards.board_id = ?)", id, id)
|
||||
end
|
||||
|
||||
def notifications_for_user(user)
|
||||
@@ -85,8 +85,8 @@ module Collection::Accessible
|
||||
.joins("LEFT JOIN cards AS event_cards ON events.eventable_id = event_cards.id AND events.eventable_type = 'Card'")
|
||||
.joins("LEFT JOIN comments AS event_comments ON events.eventable_id = event_comments.id AND events.eventable_type = 'Comment'")
|
||||
.joins("LEFT JOIN cards AS event_comment_cards ON event_comments.card_id = event_comment_cards.id")
|
||||
.where("(notifications.source_type = 'Event' AND events.eventable_type = 'Card' AND event_cards.collection_id = ?) OR
|
||||
(notifications.source_type = 'Event' AND events.eventable_type = 'Comment' AND event_comment_cards.collection_id = ?)",
|
||||
.where("(notifications.source_type = 'Event' AND events.eventable_type = 'Card' AND event_cards.board_id = ?) OR
|
||||
(notifications.source_type = 'Event' AND events.eventable_type = 'Comment' AND event_comment_cards.board_id = ?)",
|
||||
id, id)
|
||||
end
|
||||
end
|
||||
@@ -1,4 +1,4 @@
|
||||
module Collection::AutoClosing
|
||||
module Board::AutoPostponing
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
@@ -6,9 +6,9 @@ module Collection::AutoClosing
|
||||
end
|
||||
|
||||
private
|
||||
DEFAULT_auto_postpone_period = 30.days
|
||||
DEFAULT_AUTO_POSTPONE_PERIOD = 30.days
|
||||
|
||||
def set_default_auto_postpone_period
|
||||
self.auto_postpone_period ||= DEFAULT_auto_postpone_period unless attribute_present?(:auto_postpone_period)
|
||||
self.auto_postpone_period ||= DEFAULT_AUTO_POSTPONE_PERIOD unless attribute_present?(:auto_postpone_period)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,8 @@
|
||||
module Board::Broadcastable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
broadcasts_refreshes
|
||||
broadcasts_refreshes_to ->(_) { :all_boards }
|
||||
end
|
||||
end
|
||||
@@ -1,4 +1,4 @@
|
||||
module Collection::Cards
|
||||
module Board::Cards
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
@@ -1,4 +1,4 @@
|
||||
module Collection::Entropic
|
||||
module Board::Entropic
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
@@ -0,0 +1,5 @@
|
||||
class Board::Publication < ApplicationRecord
|
||||
belongs_to :board
|
||||
|
||||
has_secure_token :key
|
||||
end
|
||||
@@ -1,14 +1,14 @@
|
||||
module Collection::Publishable
|
||||
module Board::Publishable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
has_one :publication, class_name: "Collection::Publication", dependent: :destroy
|
||||
has_one :publication, class_name: "Board::Publication", dependent: :destroy
|
||||
scope :published, -> { joins(:publication) }
|
||||
end
|
||||
|
||||
class_methods do
|
||||
def find_by_published_key(key)
|
||||
Collection::Publication.find_by!(key: key).collection
|
||||
Board::Publication.find_by!(key: key).board
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
module Collection::Triageable
|
||||
module Board::Triageable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
+13
-13
@@ -3,7 +3,7 @@ class Card < ApplicationRecord
|
||||
Eventable, Golden, Mentions, Multistep, Pinnable, Postponable, Promptable,
|
||||
Readable, Searchable, Stallable, Statuses, Taggable, Triageable, Watchable
|
||||
|
||||
belongs_to :collection, touch: true
|
||||
belongs_to :board, touch: true
|
||||
belongs_to :creator, class_name: "User", default: -> { Current.user }
|
||||
|
||||
has_many :comments, dependent: :destroy
|
||||
@@ -12,7 +12,7 @@ class Card < ApplicationRecord
|
||||
has_rich_text :description
|
||||
|
||||
before_save :set_default_title, if: :published?
|
||||
after_update :handle_collection_change, if: :saved_change_to_collection_id?
|
||||
after_update :handle_board_change, if: :saved_change_to_board_id?
|
||||
|
||||
scope :reverse_chronologically, -> { order created_at: :desc, id: :desc }
|
||||
scope :chronologically, -> { order created_at: :asc, id: :asc }
|
||||
@@ -39,16 +39,16 @@ class Card < ApplicationRecord
|
||||
end
|
||||
end
|
||||
|
||||
delegate :accessible_to?, to: :collection
|
||||
delegate :accessible_to?, to: :board
|
||||
|
||||
def card
|
||||
self
|
||||
end
|
||||
|
||||
def move_to(new_collection)
|
||||
def move_to(new_board)
|
||||
transaction do
|
||||
card.update!(collection: new_collection)
|
||||
card.events.update_all(collection_id: new_collection.id)
|
||||
card.update!(board: new_board)
|
||||
card.events.update_all(board_id: new_board.id)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -61,23 +61,23 @@ class Card < ApplicationRecord
|
||||
self.title = "Untitled" if title.blank?
|
||||
end
|
||||
|
||||
def handle_collection_change
|
||||
old_collection = Collection.find_by(id: collection_id_before_last_save)
|
||||
def handle_board_change
|
||||
old_board = Board.find_by(id: board_id_before_last_save)
|
||||
|
||||
transaction do
|
||||
update! column: nil
|
||||
track_collection_change_event(old_collection.name)
|
||||
grant_access_to_assignees unless collection.all_access?
|
||||
track_board_change_event(old_board.name)
|
||||
grant_access_to_assignees unless board.all_access?
|
||||
end
|
||||
|
||||
remove_inaccessible_notifications_later
|
||||
end
|
||||
|
||||
def track_collection_change_event(old_collection_name)
|
||||
track_event "collection_changed", particulars: { old_collection: old_collection_name, new_collection: collection.name }
|
||||
def track_board_change_event(old_board_name)
|
||||
track_event "board_changed", particulars: { old_board: old_board_name, new_board: board.name }
|
||||
end
|
||||
|
||||
def grant_access_to_assignees
|
||||
collection.accesses.grant_to(assignees)
|
||||
board.accesses.grant_to(assignees)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,19 +4,19 @@ module Card::Entropic
|
||||
included do
|
||||
scope :due_to_be_postponed, -> do
|
||||
active
|
||||
.left_outer_joins(collection: :entropy)
|
||||
.left_outer_joins(board: :entropy)
|
||||
.where("last_active_at <= DATETIME('now', '-' || COALESCE(entropies.auto_postpone_period, ?) || ' seconds')",
|
||||
Account.sole.entropy.auto_postpone_period)
|
||||
end
|
||||
|
||||
scope :postponing_soon, -> do
|
||||
active
|
||||
.left_outer_joins(collection: :entropy)
|
||||
.left_outer_joins(board: :entropy)
|
||||
.where("last_active_at > DATETIME('now', '-' || COALESCE(entropies.auto_postpone_period, ?) || ' seconds')", Account.sole.entropy.auto_postpone_period)
|
||||
.where("last_active_at <= DATETIME('now', '-' || CAST(COALESCE(entropies.auto_postpone_period, ?) * 0.75 AS INTEGER) || ' seconds')", Account.sole.entropy.auto_postpone_period)
|
||||
end
|
||||
|
||||
delegate :auto_postpone_period, to: :collection
|
||||
delegate :auto_postpone_period, to: :board
|
||||
end
|
||||
|
||||
class_methods do
|
||||
|
||||
@@ -28,8 +28,8 @@ class Card::Eventable::SystemCommenter
|
||||
"<strong>Closed</strong> as “Not Now” due to inactivity"
|
||||
when "card_title_changed"
|
||||
"#{event.creator.name} <strong>changed the title</strong> from “#{event.particulars.dig('particulars', 'old_title')}” to “#{event.particulars.dig('particulars', 'new_title')}”."
|
||||
when "card_collection_changed"
|
||||
"#{event.creator.name} <strong>moved</strong> this from “#{event.particulars.dig('particulars', 'old_collection')}” to “#{event.particulars.dig('particulars', 'new_collection')}”."
|
||||
when "card_board_changed"
|
||||
"#{event.creator.name} <strong>moved</strong> this from “#{event.particulars.dig('particulars', 'old_board')}” to “#{event.particulars.dig('particulars', 'new_board')}”."
|
||||
when "card_triaged"
|
||||
"#{event.creator.name} <strong>moved</strong> this to “#{event.particulars.dig('particulars', 'column')}”"
|
||||
when "card_sent_back_to_triage"
|
||||
|
||||
@@ -21,8 +21,8 @@ module Card::Promptable
|
||||
* Assigned to: #{assignees.map(&:name).join(", ")}
|
||||
* Column: #{column_prompt_label}
|
||||
* Created at: #{created_at}}
|
||||
* Collection id: #{collection_id}
|
||||
* Collection name: #{collection.name}
|
||||
* Board id: #{board_id}
|
||||
* Board name: #{board.name}
|
||||
* Number of comments: #{comments.count}
|
||||
* Path: #{card_path(self, script_name: Account.sole.slug)}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ module Card::Triageable
|
||||
end
|
||||
|
||||
def triage_into(column)
|
||||
raise "The column must belong to the card collection" unless collection == column.collection
|
||||
raise "The column must belong to the card board" unless board == column.board
|
||||
|
||||
transaction do
|
||||
resume
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
module Collection::Broadcastable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
broadcasts_refreshes
|
||||
broadcasts_refreshes_to ->(_) { :all_collections }
|
||||
end
|
||||
end
|
||||
@@ -1,5 +0,0 @@
|
||||
class Collection::Publication < ApplicationRecord
|
||||
belongs_to :collection
|
||||
|
||||
has_secure_token :key
|
||||
end
|
||||
@@ -1,10 +1,10 @@
|
||||
class Column < ApplicationRecord
|
||||
include Positioned
|
||||
|
||||
belongs_to :collection, touch: true
|
||||
belongs_to :board, touch: true
|
||||
has_many :cards, dependent: :nullify
|
||||
|
||||
before_validation -> { self.color ||= Card::DEFAULT_COLOR }
|
||||
after_save_commit -> { cards.touch_all }, if: -> { saved_change_to_name? || saved_change_to_color? }
|
||||
after_destroy_commit -> { collection.cards.touch_all }
|
||||
after_destroy_commit -> { board.cards.touch_all }
|
||||
end
|
||||
|
||||
@@ -16,11 +16,11 @@ module Column::Positioned
|
||||
end
|
||||
|
||||
def left_column
|
||||
collection.columns.where("position < ?", position).sorted.last
|
||||
board.columns.where("position < ?", position).sorted.last
|
||||
end
|
||||
|
||||
def right_column
|
||||
collection.columns.where("position > ?", position).sorted.first
|
||||
board.columns.where("position > ?", position).sorted.first
|
||||
end
|
||||
|
||||
def leftmost?
|
||||
@@ -33,7 +33,7 @@ module Column::Positioned
|
||||
|
||||
private
|
||||
def set_position
|
||||
max_position = collection.columns.maximum(:position) || 0
|
||||
max_position = board.columns.maximum(:position) || 0
|
||||
self.position = max_position + 1
|
||||
end
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ class Comment < ApplicationRecord
|
||||
|
||||
after_create_commit :watch_card_by_creator
|
||||
|
||||
delegate :collection, :watch_by, to: :card
|
||||
delegate :board, :watch_by, to: :card
|
||||
|
||||
def to_partial_path
|
||||
"cards/#{super}"
|
||||
|
||||
@@ -17,6 +17,6 @@ module Comment::Eventable
|
||||
end
|
||||
|
||||
def track_creation
|
||||
track_event("created", collection: card.collection, creator: creator)
|
||||
track_event("created", board: card.board, creator: creator)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -41,7 +41,7 @@ module Mentions
|
||||
end
|
||||
|
||||
def mentionable_users
|
||||
collection.users
|
||||
board.users
|
||||
end
|
||||
|
||||
def rich_text_associations
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
class Event < ApplicationRecord
|
||||
include Notifiable, Particulars, Promptable
|
||||
|
||||
belongs_to :collection
|
||||
belongs_to :board
|
||||
belongs_to :creator, class_name: "User"
|
||||
belongs_to :eventable, polymorphic: true
|
||||
|
||||
|
||||
@@ -5,9 +5,9 @@ module Eventable
|
||||
has_many :events, as: :eventable, dependent: :destroy
|
||||
end
|
||||
|
||||
def track_event(action, creator: Current.user, collection: self.collection, **particulars)
|
||||
def track_event(action, creator: Current.user, board: self.board, **particulars)
|
||||
if should_track_event?
|
||||
collection.events.create!(action: "#{eventable_prefix}_#{action}", creator:, collection:, eventable: self, particulars:)
|
||||
board.events.create!(action: "#{eventable_prefix}_#{action}", creator:, board:, eventable: self, particulars:)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ class Filter < ApplicationRecord
|
||||
result = result.unassigned if assignment_status.unassigned?
|
||||
result = result.assigned_to(assignees.ids) if assignees.present?
|
||||
result = result.where(creator_id: creators.ids) if creators.present?
|
||||
result = result.where(collection: collections.ids) if collections.present?
|
||||
result = result.where(board: boards.ids) if boards.present?
|
||||
result = result.tagged_with(tags.ids) if tags.present?
|
||||
result = result.where("cards.created_at": creation_window) if creation_window
|
||||
result = result.closed_at_window(closure_window) if closure_window
|
||||
@@ -44,16 +44,16 @@ class Filter < ApplicationRecord
|
||||
self.class.normalize_params(as_params).blank?
|
||||
end
|
||||
|
||||
def single_collection
|
||||
collections.first if collections.one?
|
||||
def single_board
|
||||
boards.first if boards.one?
|
||||
end
|
||||
|
||||
def single_workflow
|
||||
collections.first.workflow if collections.pluck(:workflow_id).uniq.one?
|
||||
boards.first.workflow if boards.pluck(:workflow_id).uniq.one?
|
||||
end
|
||||
|
||||
def cacheable?
|
||||
collections.exists?
|
||||
boards.exists?
|
||||
end
|
||||
|
||||
def cache_key
|
||||
|
||||
@@ -11,7 +11,7 @@ module Filter::Params
|
||||
assignee_ids: [],
|
||||
creator_ids: [],
|
||||
closer_ids: [],
|
||||
collection_ids: [],
|
||||
board_ids: [],
|
||||
tag_ids: [],
|
||||
terms: []
|
||||
]
|
||||
@@ -40,9 +40,9 @@ module Filter::Params
|
||||
before_save { self.params_digest = self.class.digest_params(as_params) }
|
||||
end
|
||||
|
||||
def used?(ignore_collections: false)
|
||||
def used?(ignore_boards: false)
|
||||
tags.any? || assignees.any? || creators.any? || closers.any? ||
|
||||
terms.any? || card_ids&.any? || (!ignore_collections && collections.present?) ||
|
||||
terms.any? || card_ids&.any? || (!ignore_boards && boards.present?) ||
|
||||
assignment_status.unassigned? || !indexed_by.all? || !sorted_by.latest?
|
||||
end
|
||||
|
||||
@@ -57,7 +57,7 @@ module Filter::Params
|
||||
params[:assignment_status] = assignment_status
|
||||
params[:terms] = terms
|
||||
params[:tag_ids] = tags.ids
|
||||
params[:collection_ids] = collections.ids
|
||||
params[:board_ids] = boards.ids
|
||||
params[:card_ids] = card_ids
|
||||
params[:assignee_ids] = assignees.ids
|
||||
params[:creator_ids] = creators.ids
|
||||
|
||||
@@ -3,7 +3,7 @@ module Filter::Resources
|
||||
|
||||
included do
|
||||
has_and_belongs_to_many :tags
|
||||
has_and_belongs_to_many :collections
|
||||
has_and_belongs_to_many :boards
|
||||
has_and_belongs_to_many :assignees, class_name: "User", join_table: "assignees_filters", association_foreign_key: "assignee_id"
|
||||
has_and_belongs_to_many :creators, class_name: "User", join_table: "creators_filters", association_foreign_key: "creator_id"
|
||||
has_and_belongs_to_many :closers, class_name: "User", join_table: "closers_filters", association_foreign_key: "closer_id"
|
||||
@@ -17,19 +17,19 @@ module Filter::Resources
|
||||
destroy!
|
||||
end
|
||||
|
||||
def collections
|
||||
creator.collections.where id: super.ids
|
||||
def boards
|
||||
creator.boards.where id: super.ids
|
||||
end
|
||||
|
||||
def collection_titles
|
||||
if collections.none?
|
||||
Collection.one? ? [ Collection.first.name ] : [ "all boards" ]
|
||||
def board_titles
|
||||
if boards.none?
|
||||
Board.one? ? [ Board.first.name ] : [ "all boards" ]
|
||||
else
|
||||
collections.map(&:name)
|
||||
boards.map(&:name)
|
||||
end
|
||||
end
|
||||
|
||||
def collections_label
|
||||
collection_titles.to_sentence
|
||||
def boards_label
|
||||
board_titles.to_sentence
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
class Notifier::CardEventNotifier < Notifier
|
||||
delegate :creator, to: :source
|
||||
delegate :collection, to: :card
|
||||
delegate :board, to: :card
|
||||
|
||||
private
|
||||
def recipients
|
||||
@@ -8,11 +8,11 @@ class Notifier::CardEventNotifier < Notifier
|
||||
when "card_assigned"
|
||||
source.assignees.excluding(creator)
|
||||
when "card_published"
|
||||
collection.watchers.without(creator, *card.mentionees)
|
||||
board.watchers.without(creator, *card.mentionees)
|
||||
when "comment_created"
|
||||
card.watchers.without(creator, *source.eventable.mentionees)
|
||||
else
|
||||
collection.watchers.without(creator)
|
||||
board.watchers.without(creator)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ class Search
|
||||
"highlight(cards_search_index, 0, '#{HIGHLIGHT_OPENING_MARK}', '#{HIGHLIGHT_CLOSING_MARK}') AS card_title",
|
||||
"snippet(cards_search_index, 1, '#{HIGHLIGHT_OPENING_MARK}', '#{HIGHLIGHT_CLOSING_MARK}', '...', 20) AS card_description",
|
||||
"null as comment_body",
|
||||
"collections.name as collection_name",
|
||||
"boards.name as board_name",
|
||||
"cards.creator_id",
|
||||
"cards.created_at",
|
||||
"bm25(cards_search_index, 10.0, 2.0) AS score"
|
||||
@@ -34,7 +34,7 @@ class Search
|
||||
"cards.title AS card_title",
|
||||
"null AS card_description",
|
||||
"snippet(comments_search_index, 0, '#{HIGHLIGHT_OPENING_MARK}', '#{HIGHLIGHT_CLOSING_MARK}', '...', 20) AS comment_body",
|
||||
"collections.name as collection_name",
|
||||
"boards.name as board_name",
|
||||
"comments.creator_id",
|
||||
"comments.created_at",
|
||||
"bm25(comments_search_index, 1.0) AS score"
|
||||
|
||||
@@ -3,15 +3,15 @@ module User::Accessor
|
||||
|
||||
included do
|
||||
has_many :accesses, dependent: :destroy
|
||||
has_many :collections, through: :accesses
|
||||
has_many :accessible_cards, through: :collections, source: :cards
|
||||
has_many :boards, through: :accesses
|
||||
has_many :accessible_cards, through: :boards, source: :cards
|
||||
has_many :accessible_comments, through: :accessible_cards, source: :comments
|
||||
|
||||
after_create_commit :grant_access_to_collections, unless: :system?
|
||||
after_create_commit :grant_access_to_boards, unless: :system?
|
||||
end
|
||||
|
||||
private
|
||||
def grant_access_to_collections
|
||||
Access.insert_all Collection.all_access.pluck(:id).collect { |collection_id| { collection_id: collection_id, user_id: id } }
|
||||
def grant_access_to_boards
|
||||
Access.insert_all Board.all_access.pluck(:id).collect { |board_id| { board_id: board_id, user_id: id } }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -41,6 +41,7 @@ class User::DayTimeline
|
||||
card_closed
|
||||
card_reopened
|
||||
card_collection_changed
|
||||
card_board_changed
|
||||
card_postponed
|
||||
card_triaged
|
||||
card_sent_back_to_triage
|
||||
@@ -57,12 +58,12 @@ class User::DayTimeline
|
||||
|
||||
def timelineable_events
|
||||
Event
|
||||
.where(collection: collections)
|
||||
.where(board: boards)
|
||||
.where(action: TIMELINEABLE_ACTIONS)
|
||||
end
|
||||
|
||||
def collections
|
||||
filter.collections.presence || user.collections
|
||||
def boards
|
||||
filter.boards.presence || user.boards
|
||||
end
|
||||
|
||||
def latest_event_before
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user