Merge pull request #629 from basecamp/prompts-init
Prompts for mentions and links
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
||||
GIT
|
||||
remote: https://github.com/basecamp/actiontext-lexical
|
||||
revision: 3f1ec3916f468ac2bb626091bcb97469c2c52483
|
||||
revision: ea4a511a27db4718fdd7db61d7235c3a374cb1ab
|
||||
specs:
|
||||
actiontext-lexical (0.1.0)
|
||||
rails (>= 8.0.2)
|
||||
|
||||
@@ -1,136 +1,195 @@
|
||||
lexical-editor {
|
||||
.node--selected {
|
||||
&:not(:has(img)) {
|
||||
box-shadow: 0 0 0 var(--hover-size) var(--hover-color);
|
||||
@layer components {
|
||||
lexical-editor {
|
||||
display: block;
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
|
||||
.node--selected:not(.attachment--custom) {
|
||||
&:not(:has(img)) {
|
||||
box-shadow: 0 0 0 var(--hover-size) var(--hover-color);
|
||||
}
|
||||
|
||||
&:has(img) {
|
||||
img {
|
||||
box-shadow: 0 0 0 var(--hover-size) var(--hover-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:has(img) {
|
||||
img {
|
||||
box-shadow: 0 0 0 var(--hover-size) var(--hover-color);
|
||||
&.lexical-editor--empty {
|
||||
.lexical-editor__content::before {
|
||||
content: attr(placeholder);
|
||||
color: currentColor;
|
||||
cursor: text;
|
||||
opacity: 0.66;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
white-space: pre-line;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.lexical-editor--empty {
|
||||
.lexical-editor__content::before {
|
||||
content: attr(placeholder);
|
||||
color: currentColor;
|
||||
cursor: text;
|
||||
opacity: 0.66;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
white-space: pre-line;
|
||||
.lexical-dialog-actions {
|
||||
display: flex;
|
||||
font-size: var(--text-x-small);
|
||||
flex: 1 1 0%;
|
||||
gap: var(--inline-space-half);
|
||||
margin-block-start: var(--block-space-half);
|
||||
|
||||
.btn {
|
||||
--radius: 0.3em;
|
||||
|
||||
inline-size: 100%;
|
||||
justify-content: center;
|
||||
|
||||
&:is([type="submit"]) {
|
||||
--btn-background: var(--card-color, var(--color-link));
|
||||
--btn-color: var(--color-ink-inverted);
|
||||
--outline-color: var(--card-color, var(--color-link));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.lexical-dialog-actions {
|
||||
display: flex;
|
||||
font-size: var(--text-x-small);
|
||||
flex: 1 1 0%;
|
||||
gap: var(--inline-space-half);
|
||||
margin-block-start: var(--block-space-half);
|
||||
|
||||
.btn {
|
||||
--radius: 0.3em;
|
||||
|
||||
inline-size: 100%;
|
||||
justify-content: center;
|
||||
|
||||
&:is([type="submit"]) {
|
||||
--btn-background: var(--card-color, var(--color-link));
|
||||
--btn-color: var(--color-ink-inverted);
|
||||
--outline-color: var(--card-color, var(--color-link));
|
||||
span {
|
||||
inline-size: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
inline-size: 100%;
|
||||
}
|
||||
}
|
||||
.lexical-editor__content {
|
||||
--outline-size: max(2px, 0.08em);
|
||||
|
||||
.lexical-editor__content {
|
||||
--outline-size: max(2px, 0.08em);
|
||||
margin: var(--block-space-half) 0;
|
||||
min-block-size: calc(7lh + var(--block-space));
|
||||
outline: 0;
|
||||
padding: var(--block-space-half) var(--inline-space);
|
||||
|
||||
margin: var(--block-space-half) 0;
|
||||
min-block-size: calc(7lh + var(--block-space));
|
||||
outline: 0;
|
||||
padding: var(--block-space-half) var(--inline-space);
|
||||
* {
|
||||
&:first-child {
|
||||
margin-block-start: 0;
|
||||
}
|
||||
|
||||
* {
|
||||
&:first-child {
|
||||
margin-block-start: 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-block-end: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.lexical-editor--drag-over {
|
||||
background-color: var(--color-selected);
|
||||
border-radius: 4px;
|
||||
outline: 2px dashed var(--color-selected-dark);
|
||||
}
|
||||
|
||||
lexical-toolbar {
|
||||
background-color: inherit;
|
||||
border-block-end: 1px solid var(--color-ink-light);
|
||||
color: currentColor;
|
||||
display: flex;
|
||||
font-size: inherit;
|
||||
margin: 0;
|
||||
max-inline-size: 100%;
|
||||
padding: 0.2em 0;
|
||||
position: relative;
|
||||
|
||||
dialog {
|
||||
background-color: var(--color-canvas);
|
||||
border: 1px solid var(--color-ink-lighter);
|
||||
border-radius: 0.5em;
|
||||
box-shadow: var(--shadow);
|
||||
color: var(--color-ink);
|
||||
padding: var(--block-space) calc(var(--inline-space) * 1.5);
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
|
||||
.input[type="url"] {
|
||||
min-inline-size: 30ch;
|
||||
&:last-child {
|
||||
margin-block-end: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> button {
|
||||
aspect-ratio: 4/3.5;
|
||||
appearance: none;
|
||||
background-color: transparent;
|
||||
block-size: 2em;
|
||||
border: none;
|
||||
border-radius: 0.2em;
|
||||
.lexical-editor--drag-over {
|
||||
background-color: var(--color-selected);
|
||||
border-radius: 4px;
|
||||
outline: 2px dashed var(--color-selected-dark);
|
||||
}
|
||||
|
||||
lexical-toolbar {
|
||||
background-color: inherit;
|
||||
border-block-end: 1px solid var(--color-ink-light);
|
||||
color: currentColor;
|
||||
cursor: pointer;
|
||||
display: grid;
|
||||
display: flex;
|
||||
font-size: inherit;
|
||||
inline-size: auto;
|
||||
place-items: center;
|
||||
transition: background-color 300ms ease;
|
||||
margin: 0;
|
||||
max-inline-size: 100%;
|
||||
padding: 0.2em 0;
|
||||
position: relative;
|
||||
|
||||
svg {
|
||||
-webkit-touch-callout: none;
|
||||
block-size: 0.85em;
|
||||
fill: currentColor;
|
||||
grid-area: 1/1;
|
||||
dialog {
|
||||
background-color: var(--color-canvas);
|
||||
border: 1px solid var(--color-ink-lighter);
|
||||
border-radius: 0.5em;
|
||||
box-shadow: var(--shadow);
|
||||
color: var(--color-ink);
|
||||
padding: var(--block-space) calc(var(--inline-space) * 1.5);
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
|
||||
.input[type="url"] {
|
||||
min-inline-size: 30ch;
|
||||
}
|
||||
}
|
||||
|
||||
> button {
|
||||
aspect-ratio: 4/3.5;
|
||||
appearance: none;
|
||||
background-color: transparent;
|
||||
block-size: 2em;
|
||||
border: none;
|
||||
border-radius: 0.2em;
|
||||
color: currentColor;
|
||||
cursor: pointer;
|
||||
display: grid;
|
||||
font-size: inherit;
|
||||
inline-size: auto;
|
||||
user-select: none;
|
||||
}
|
||||
place-items: center;
|
||||
transition: background-color 300ms ease;
|
||||
|
||||
&:is(:focus, :hover) {
|
||||
background-color: var(--color-ink-lighter);
|
||||
box-shadow: none;
|
||||
}
|
||||
svg {
|
||||
-webkit-touch-callout: none;
|
||||
block-size: 0.85em;
|
||||
fill: currentColor;
|
||||
grid-area: 1/1;
|
||||
inline-size: auto;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
&:is(:active) {
|
||||
&:is(:focus, :hover) {
|
||||
background-color: var(--color-ink-lighter);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
&:is(:active) {
|
||||
background-color: var(--color-selected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Prompt
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
.lexical-prompt-menu {
|
||||
--lexical-prompt-avatar-size: 24px;
|
||||
--lexical-prompt-min-width: 20ch;
|
||||
--lexical-prompt-padding: 0.5ch;
|
||||
|
||||
background-color: var(--color-canvas);
|
||||
border-radius: calc(var(--lexical-prompt-padding) * 2);
|
||||
box-shadow: var(--shadow);
|
||||
font-size: var(--text-small);
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
min-inline-size: var(--lexical-prompt-min-width);
|
||||
padding: var(--lexical-prompt-padding);
|
||||
visibility: hidden;
|
||||
z-index: var(--z-popup);
|
||||
}
|
||||
|
||||
.lexical-prompt-menu--visible {
|
||||
visibility: initial;
|
||||
}
|
||||
|
||||
.lexical-prompt-menu__item {
|
||||
align-items: center;
|
||||
border-radius: 0.5ch;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
gap: var(--lexical-prompt-padding);
|
||||
padding: var(--lexical-prompt-padding);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-selected);
|
||||
}
|
||||
|
||||
&[aria-selected] {
|
||||
box-shadow: 0 0 0 var(--hover-size) var(--color-selected-dark) inset;
|
||||
|
||||
&:hover {
|
||||
--hover-size: 0;
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
block-size: var(--lexical-prompt-avatar-size);
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
inline-size: var(--lexical-prompt-avatar-size);
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
:where(h5) { font-size: 0.83em; }
|
||||
:where(h6) { font-size: 0.67em; }
|
||||
|
||||
:where(p, ul, ol, dl, blockquote, figure, .attachment) {
|
||||
:where(p, ul, ol, dl, blockquote, figure:not(.attachment--custom), .attachment:not(.attachment--custom)) {
|
||||
margin-block: var(--block-margin);
|
||||
overflow-wrap: break-word;
|
||||
text-wrap: pretty;
|
||||
@@ -54,7 +54,7 @@
|
||||
}
|
||||
|
||||
/* Attachments */
|
||||
.attachment {
|
||||
.attachment:not(.attachment--custom) {
|
||||
block-size: auto;
|
||||
display: inline-block;
|
||||
inline-size: 100%;
|
||||
@@ -221,7 +221,7 @@
|
||||
}
|
||||
|
||||
/* Attachment junk gets wrapped in a paragraph causing unwanted space */
|
||||
p:has(action-text-attachment) {
|
||||
p:has(action-text-attachment[src]) {
|
||||
display: none;
|
||||
|
||||
+ * {
|
||||
@@ -329,5 +329,44 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Custom attachments such as mentions, etc. */
|
||||
.attachment--custom {
|
||||
display: inline;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.attachment--custom,
|
||||
action-text-attachment {
|
||||
--attachment-image-size: 1em;
|
||||
--attachment-selected-color: oklch(var(--lch-blue-dark));
|
||||
|
||||
align-items: center;
|
||||
display: inline-flex;
|
||||
gap: 0.25ch;
|
||||
position: relative;
|
||||
vertical-align: bottom;
|
||||
|
||||
lexical-editor & {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
img {
|
||||
block-size: var(--attachment-image-size);
|
||||
border-radius: 50%;
|
||||
inline-size: var(--attachment-image-size);
|
||||
}
|
||||
|
||||
&.node--selected {
|
||||
background: var(--attachment-selected-color);
|
||||
box-shadow:
|
||||
-0.25ch 0 0 var(--attachment-selected-color),
|
||||
0.5ch 0 0 var(--attachment-selected-color);
|
||||
border-radius: 99rem;
|
||||
color: var(--color-ink-inverted);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
class Prompts::CardsController < ApplicationController
|
||||
MAX_RESULTS = 10
|
||||
|
||||
def index
|
||||
@cards = if filter_param.present?
|
||||
prepending_exact_matches_by_id(published_cards.mentioning(params[:filter]))
|
||||
else
|
||||
@cards = published_cards.latest
|
||||
end
|
||||
|
||||
if stale? etag: @cards
|
||||
render layout: false
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def filter_param
|
||||
params[:filter]
|
||||
end
|
||||
|
||||
def published_cards
|
||||
Current.user.accessible_cards.published.limit(MAX_RESULTS)
|
||||
end
|
||||
|
||||
def prepending_exact_matches_by_id(cards)
|
||||
if card_by_id = Current.user.accessible_cards.find_by_id(params[:filter])
|
||||
[ card_by_id ] + cards
|
||||
else
|
||||
cards
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,11 @@
|
||||
class Prompts::Collections::UsersController < ApplicationController
|
||||
include CollectionScoped
|
||||
|
||||
def index
|
||||
@users = @collection.users
|
||||
|
||||
if stale? etag: @users
|
||||
render layout: false
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,9 @@
|
||||
module RichTextHelper
|
||||
def mentions_prompt(collection)
|
||||
content_tag "lexical-prompt", "", trigger: "@", src: prompts_collection_users_path(collection), name: "mention"
|
||||
end
|
||||
|
||||
def cards_prompt
|
||||
content_tag "lexical-prompt", "", trigger: "#", src: prompts_cards_path, name: "card", "insert-editable-text": true, "remote-filtering": true
|
||||
end
|
||||
end
|
||||
@@ -19,6 +19,10 @@ module Mentions
|
||||
|
||||
private
|
||||
def scan_mentionees
|
||||
mentionees_from_plain_text | mentionees_from_rich_text
|
||||
end
|
||||
|
||||
def mentionees_from_plain_text
|
||||
scan_mentioned_handles.filter_map do |mention|
|
||||
mentionable_users.find { |user| user.mentionable_handles.include?(mention) }
|
||||
end
|
||||
@@ -28,6 +32,14 @@ module Mentions
|
||||
mentionable_content.scan(/(?<!\w)@(\w+)/).flatten.uniq(&:downcase)
|
||||
end
|
||||
|
||||
def mentionees_from_rich_text
|
||||
mentionees_from_attachments & mentionable_users
|
||||
end
|
||||
|
||||
def mentionees_from_attachments
|
||||
rich_text_associations.flat_map { send(it.name)&.body&.attachments&.collect { it.attachable } }.compact
|
||||
end
|
||||
|
||||
def mentionable_users
|
||||
collection.users
|
||||
end
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
class User < ApplicationRecord
|
||||
include Accessor, Assignee, Mentionable, Named, Role, Transferable
|
||||
include Accessor, ActionText::Attachable, Assignee, Mentionable, Named, Role, Transferable
|
||||
include Timelined # Depends on Accessor
|
||||
|
||||
has_one_attached :avatar
|
||||
|
||||
@@ -3,6 +3,11 @@ module User::Mentionable
|
||||
|
||||
included do
|
||||
has_many :mentions, dependent: :destroy, inverse_of: :mentionee
|
||||
|
||||
# Need to set in the included block so that it overrides Action Text's
|
||||
def to_attachable_partial_path
|
||||
"users/attachable"
|
||||
end
|
||||
end
|
||||
|
||||
def mentioned_by(mentioner, at:)
|
||||
@@ -13,6 +18,10 @@ module User::Mentionable
|
||||
[ initials, first_name, first_name_with_last_name_initial ].collect(&:downcase)
|
||||
end
|
||||
|
||||
def content_type
|
||||
"application/vnd.actiontext.mention"
|
||||
end
|
||||
|
||||
private
|
||||
def first_name_with_last_name_initial
|
||||
"#{first_name}#{last_name&.first}"
|
||||
|
||||
@@ -7,10 +7,13 @@
|
||||
<div class="comment__body rich-text-content txt-align-start">
|
||||
<%= form_with model: Comment.new, url: card_comments_path(card), class: "flex flex-column gap full-width",
|
||||
data: { controller: "form local-save",
|
||||
local_save_key_value: "comment-#{card.id}",
|
||||
action: "turbo:submit-end->local-save#submit keydown.ctrl+enter->form#submit:prevent keydown.meta+enter->form#submit:prevent keydown.esc->form#cancel:stop" } do |form| %>
|
||||
local_save_key_value: "comment-#{card.id}",
|
||||
action: "turbo:submit-end->local-save#submit keydown.ctrl+enter->form#submit:prevent keydown.meta+enter->form#submit:prevent keydown.esc->form#cancel:stop" } do |form| %>
|
||||
<%= form.rich_textarea :body, required: true, placeholder: new_comment_placeholder(card),
|
||||
data: { local_save_target: "input", action: "actiontext:change->local-save#save turbo:morph-element->local-save#restoreContent" } %>
|
||||
data: { local_save_target: "input", action: "actiontext:change->local-save#save turbo:morph-element->local-save#restoreContent" } do %>
|
||||
<%= mentions_prompt(@card.collection) %>
|
||||
<%= cards_prompt %>
|
||||
<% end %>
|
||||
<%= form.button class: "comment__submit btn btn--reversed flex-item-justify-start" do %>
|
||||
<span>Post this comment</span>
|
||||
<% end %>
|
||||
|
||||
@@ -8,7 +8,10 @@
|
||||
<div class="comment__body rich-text-content txt-align-start">
|
||||
<%= form_with model: [ @card, @comment ], class: "flex flex-column gap full-width",
|
||||
data: { controller: "form", action: "keydown.ctrl+enter->form#submit:prevent keydown.meta+enter->form#submit:prevent keydown.esc->form#cancel:stop" } do |form| %>
|
||||
<%= form.rich_textarea :body, required: true, autofocus: true, placeholder: new_comment_placeholder(@card) %>
|
||||
<%= form.rich_textarea :body, required: true, autofocus: true, placeholder: new_comment_placeholder(@card) do %>
|
||||
<%= mentions_prompt(@card.collection) %>
|
||||
<%= cards_prompt %>
|
||||
<% end %>
|
||||
<div class="flex gap-half justify-start">
|
||||
<%= form.button class: "btn btn--reversed", type: :submit do %>
|
||||
<span>Save changes</span>
|
||||
@@ -18,7 +21,7 @@
|
||||
<% end %>
|
||||
|
||||
<%= tag.button type: :submit, class: "btn btn--negative flex-item-justify-end", form: dom_id(@comment, :delete_form),
|
||||
data: { turbo_confirm: "Are you sure you want to delete this comment?" } do %>
|
||||
data: { turbo_confirm: "Are you sure you want to delete this comment?" } do %>
|
||||
<%= icon_tag "trash" %>
|
||||
<span class="for-screen-reader">Delete</span>
|
||||
<% end %>
|
||||
|
||||
@@ -20,7 +20,10 @@
|
||||
<div class="card__description rich-text-content margin-block-start-half margin-block-end">
|
||||
<%= form.rich_textarea :description, class: "card-field__description",
|
||||
placeholder: "Add some notes, context, pictures, or video about this…",
|
||||
data: { action: "actiontext:change->auto-save#change focusout->auto-save#submit keydown.ctrl+enter->form#submit:prevent keydown.meta+enter->form#submit:prevent keydown.esc->form#cancel:stop" } %>
|
||||
data: { action: "actiontext:change->auto-save#change focusout->auto-save#submit keydown.ctrl+enter->form#submit:prevent keydown.meta+enter->form#submit:prevent keydown.esc->form#cancel:stop" } do %>
|
||||
<%= mentions_prompt(card.collection) %>
|
||||
<%= cards_prompt %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
@@ -10,7 +10,10 @@
|
||||
|
||||
<%= form.rich_textarea :description, class: "card-field__description rich-text-content",
|
||||
placeholder: "Add some notes, context, pictures, or video about this…",
|
||||
data: { action: "keydown.ctrl+enter->form#submit:prevent keydown.meta+enter->form#submit:prevent keydown.esc->form#cancel:stop" } %>
|
||||
data: { action: "keydown.ctrl+enter->form#submit:prevent keydown.meta+enter->form#submit:prevent keydown.esc->form#cancel:stop" } do %>
|
||||
<%= mentions_prompt(@card.collection) %>
|
||||
<%= cards_prompt %>
|
||||
<% end %>
|
||||
</div>
|
||||
<%= form.button "Save changes", type: :submit, class: "btn btn--reversed", style: "--btn-background: #{@card.color}" %>
|
||||
<%= link_to "Close editor and discard changes", collection_card_path(@card.collection, @card), data: { form_target: "cancel" }, hidden: true %>
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<lexical-prompt-item>
|
||||
<template type="menu">
|
||||
#<%= card.id %> <%= card.title %>
|
||||
</template>
|
||||
<template type="editor">
|
||||
<%= link_to "##{card.id} #{card.title}", collection_card_url(card.collection, card) %>
|
||||
</template>
|
||||
</lexical-prompt-item>
|
||||
@@ -0,0 +1 @@
|
||||
<%= render partial: "prompts/cards/card", collection: @cards %>
|
||||
@@ -0,0 +1,10 @@
|
||||
<lexical-prompt-item search="<%= "#{user.name} #{user.initials}" %>" sgid="<%= user.attachable_sgid %>">
|
||||
<template type="menu">
|
||||
<%= avatar_image_tag user %>
|
||||
<%= user.name %>
|
||||
</template>
|
||||
<template type="editor">
|
||||
<%= avatar_image_tag user %>
|
||||
<%= user.first_name %>
|
||||
</template>
|
||||
</lexical-prompt-item>
|
||||
@@ -0,0 +1 @@
|
||||
<%= render partial: "prompts/collections/users/user", collection: @users %>
|
||||
@@ -0,0 +1,2 @@
|
||||
<%= avatar_image_tag user %>
|
||||
<%= user.first_name %>
|
||||
@@ -103,6 +103,15 @@ Rails.application.routes.draw do
|
||||
resources :pins
|
||||
end
|
||||
|
||||
namespace :prompts do
|
||||
resources :cards
|
||||
resources :collections do
|
||||
scope module: :collections do
|
||||
resources :users
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
namespace :public do
|
||||
resources :collections do
|
||||
scope module: :collections do
|
||||
|
||||
Generated
+1
@@ -324,6 +324,7 @@ ActiveRecord::Schema[8.1].define(version: 2025_06_09_102553) do
|
||||
t.datetime "created_at", null: false
|
||||
t.string "title"
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["title"], name: "index_tags_on_account_id_and_title", unique: true
|
||||
end
|
||||
|
||||
create_table "users", force: :cascade do |t|
|
||||
|
||||
+17
-1
@@ -2200,7 +2200,23 @@ indexes:
|
||||
nulls_not_distinct:
|
||||
comment:
|
||||
valid: true
|
||||
tags: []
|
||||
tags:
|
||||
- !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition
|
||||
table: tags
|
||||
name: index_tags_on_account_id_and_title
|
||||
unique: true
|
||||
columns:
|
||||
- title
|
||||
lengths: {}
|
||||
orders: {}
|
||||
opclasses: {}
|
||||
where:
|
||||
type:
|
||||
using:
|
||||
include:
|
||||
nulls_not_distinct:
|
||||
comment:
|
||||
valid: true
|
||||
users:
|
||||
- !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition
|
||||
table: users
|
||||
|
||||
@@ -5,7 +5,7 @@ class MentionsTest < ActiveSupport::TestCase
|
||||
Current.session = sessions(:david)
|
||||
end
|
||||
|
||||
test "create mentions when creating messages" do
|
||||
test "create mentions from plain text mentions" do
|
||||
assert_difference -> { Mention.count }, +1 do
|
||||
perform_enqueued_jobs only: Mention::CreateJob do
|
||||
collections(:writebook).cards.create title: "Cleanup", description: "Did you finish up with the cleanup, @david?"
|
||||
@@ -13,6 +13,27 @@ class MentionsTest < ActiveSupport::TestCase
|
||||
end
|
||||
end
|
||||
|
||||
test "create mentions from rich text mentions" do
|
||||
assert_difference -> { Mention.count }, +1 do
|
||||
perform_enqueued_jobs only: Mention::CreateJob do
|
||||
attachment = ActionText::Attachment.from_attachable(users(:david))
|
||||
collections(:writebook).cards.create title: "Cleanup", description: "Did you finish up with the cleanup, #{attachment.to_html}?"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
test "can't mention users that don't have access to the collection" do
|
||||
collections(:writebook).update! all_access: false
|
||||
collections(:writebook).accesses.revoke_from(users(:david))
|
||||
|
||||
assert_no_difference -> { Mention.count }, +1 do
|
||||
perform_enqueued_jobs only: Mention::CreateJob do
|
||||
attachment = ActionText::Attachment.from_attachable(users(:david))
|
||||
collections(:writebook).cards.create title: "Cleanup", description: "Did you finish up with the cleanup, #{attachment.to_html}?"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
test "mentionees are added as watchers of the card" do
|
||||
perform_enqueued_jobs only: Mention::CreateJob do
|
||||
card = collections(:writebook).cards.create title: "Cleanup", description: "Did you finish up with the cleanup @kevin?"
|
||||
|
||||
Reference in New Issue
Block a user