Replace House with lexical-powered actiontext

This commit is contained in:
Jorge Manrubia
2025-05-23 11:41:08 +02:00
parent 99797ca856
commit 10b2aad647
33 changed files with 737 additions and 382 deletions
+3 -1
View File
@@ -24,9 +24,10 @@ gem "thruster", require: false
gem "bcrypt", "~> 3.1.7"
gem "geared_pagination", "~> 1.2"
gem "rqrcode"
gem "redcarpet"
gem "rouge"
gem "jbuilder"
gem "actiontext-lexical", path: "/Users/jorge/Work/jorgemanrubia/actiontext-lexical"
gem "image_processing", "~> 1.14"
# Telemetry and logging
gem "sentry-ruby"
@@ -51,3 +52,4 @@ group :test do
gem "webmock"
gem "vcr"
end
+46 -11
View File
@@ -1,6 +1,6 @@
GIT
remote: https://github.com/basecamp/active_record-tenanted
revision: 612f5e8b7f77b7076a7eb21521920630161461aa
revision: 173048c2af29e9a723811e552bc59e3154e82e2a
specs:
active_record-tenanted (0.1.0)
activerecord (>= 8.1.alpha)
@@ -128,6 +128,16 @@ GIT
thor (~> 1.0, >= 1.2.2)
zeitwerk (~> 2.6)
PATH
remote: /Users/jorge/Work/jorgemanrubia/actiontext-lexical
specs:
actiontext-lexical (0.1.0)
capybara
cuprite
image_processing
rails (>= 8.0.2)
selenium-webdriver
GEM
remote: https://rubygems.org/
specs:
@@ -163,13 +173,17 @@ GEM
bigdecimal
rexml
crass (1.0.6)
cuprite (0.17)
capybara (~> 3.0)
ferrum (~> 0.17.0)
date (3.4.1)
debug (1.10.0)
irb (~> 1.10)
reline (>= 0.3.8)
dotenv (3.1.8)
drb (2.2.1)
drb (2.2.3)
ed25519 (1.4.0)
erb (5.0.1)
erubi (1.13.1)
et-orbi (1.2.11)
tzinfo
@@ -184,6 +198,16 @@ GEM
net-http (>= 0.5.0)
faraday-retry (2.3.1)
faraday (~> 2.0)
ferrum (0.17.1)
addressable (~> 2.5)
base64 (~> 0.2)
concurrent-ruby (~> 1.1)
webrick (~> 1.7)
websocket-driver (~> 0.7)
ffi (1.17.2)
ffi (1.17.2-arm64-darwin)
ffi (1.17.2-x86_64-darwin)
ffi (1.17.2-x86_64-linux-gnu)
fugit (1.11.1)
et-orbi (~> 1, >= 1.2.11)
raabro (~> 1.4)
@@ -195,6 +219,9 @@ GEM
hashdiff (1.1.2)
i18n (1.14.7)
concurrent-ruby (~> 1.0)
image_processing (1.14.0)
mini_magick (>= 4.9.5, < 6)
ruby-vips (>= 2.0.17, < 3)
importmap-rails (2.1.0)
actionpack (>= 6.0.0)
activesupport (>= 6.0.0)
@@ -222,7 +249,7 @@ GEM
language_server-protocol (3.17.0.4)
lint_roller (1.1.0)
logger (1.7.0)
loofah (2.24.0)
loofah (2.24.1)
crass (~> 1.0.2)
nokogiri (>= 1.12.0)
mail (2.8.1)
@@ -232,8 +259,11 @@ GEM
net-smtp
marcel (1.0.4)
matrix (0.4.2)
mini_magick (5.2.0)
benchmark
logger
mini_mime (1.1.5)
mini_portile2 (2.8.8)
mini_portile2 (2.8.9)
minitest (5.25.5)
msgpack (1.8.0)
multipart-post (2.4.1)
@@ -277,7 +307,7 @@ GEM
activesupport (>= 7.0.0)
rack
railties (>= 7.0.0)
psych (5.2.3)
psych (5.2.6)
date
stringio
public_suffix (6.0.1)
@@ -285,7 +315,7 @@ GEM
nio4r (~> 2.0)
raabro (1.4.0)
racc (1.8.1)
rack (3.1.14)
rack (3.1.15)
rack-contrib (2.5.0)
rack (< 4)
rack-session (2.1.1)
@@ -295,7 +325,7 @@ GEM
rack (>= 1.3)
rackup (2.2.1)
rack (>= 3)
rails-dom-testing (2.2.0)
rails-dom-testing (2.3.0)
activesupport (>= 5.0.0)
minitest
nokogiri (>= 1.6)
@@ -305,9 +335,9 @@ GEM
rainbow (3.1.1)
rake (13.2.1)
rb_sys (0.9.106)
rdoc (6.13.1)
rdoc (6.14.0)
erb
psych (>= 4.0.0)
redcarpet (3.6.1)
regexp_parser (2.10.0)
reline (0.6.1)
io-console (~> 0.5)
@@ -346,6 +376,9 @@ GEM
rubocop-performance (>= 1.24)
rubocop-rails (>= 2.30)
ruby-progressbar (1.13.0)
ruby-vips (2.2.3)
ffi (~> 1.12)
logger
rubyzip (2.4.1)
securerandom (0.4.1)
selenium-webdriver (4.31.0)
@@ -423,6 +456,7 @@ GEM
addressable (>= 2.8.0)
crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0)
webrick (1.9.1)
websocket (1.2.11)
websocket-driver (0.7.7)
base64
@@ -430,7 +464,7 @@ GEM
websocket-extensions (0.1.5)
xpath (3.2.0)
nokogiri (~> 1.8)
zeitwerk (2.7.2)
zeitwerk (2.7.3)
PLATFORMS
arm64-darwin
@@ -440,6 +474,7 @@ PLATFORMS
x86_64-mingw32
DEPENDENCIES
actiontext-lexical!
active_record-tenanted!
bcrypt (~> 3.1.7)
bootsnap
@@ -448,6 +483,7 @@ DEPENDENCIES
capybara
debug
geared_pagination (~> 1.2)
image_processing (~> 1.14)
importmap-rails
jbuilder
kamal
@@ -455,7 +491,6 @@ DEPENDENCIES
puma (>= 5.0)
rails!
rails_structured_logging!
redcarpet
rouge
rqrcode
rubocop-rails-omakase
+440
View File
@@ -0,0 +1,440 @@
/*
* Default Trix editor styles. See Action Text overwrites below.
*/
trix-editor {
border: 1px solid #bbb;
border-radius: 3px;
margin: 0;
padding: 0.4em 0.6em;
min-height: 5em;
outline: none; }
trix-toolbar * {
box-sizing: border-box; }
trix-toolbar .trix-button-row {
display: flex;
flex-wrap: nowrap;
justify-content: space-between;
overflow-x: auto; }
trix-toolbar .trix-button-group {
display: flex;
margin-bottom: 10px;
border: 1px solid #bbb;
border-top-color: #ccc;
border-bottom-color: #888;
border-radius: 3px; }
trix-toolbar .trix-button-group:not(:first-child) {
margin-left: 1.5vw; }
@media (max-width: 768px) {
trix-toolbar .trix-button-group:not(:first-child) {
margin-left: 0; } }
trix-toolbar .trix-button-group-spacer {
flex-grow: 1; }
@media (max-width: 768px) {
trix-toolbar .trix-button-group-spacer {
display: none; } }
trix-toolbar .trix-button {
position: relative;
float: left;
color: rgba(0, 0, 0, 0.6);
font-size: 0.75em;
font-weight: 600;
white-space: nowrap;
padding: 0 0.5em;
margin: 0;
outline: none;
border: none;
border-bottom: 1px solid #ddd;
border-radius: 0;
background: transparent; }
trix-toolbar .trix-button:not(:first-child) {
border-left: 1px solid #ccc; }
trix-toolbar .trix-button.trix-active {
background: #cbeefa;
color: black; }
trix-toolbar .trix-button:not(:disabled) {
cursor: pointer; }
trix-toolbar .trix-button:disabled {
color: rgba(0, 0, 0, 0.125); }
@media (max-width: 768px) {
trix-toolbar .trix-button {
letter-spacing: -0.01em;
padding: 0 0.3em; } }
trix-toolbar .trix-button--icon {
font-size: inherit;
width: 2.6em;
height: 1.6em;
max-width: calc(0.8em + 4vw);
text-indent: -9999px; }
@media (max-width: 768px) {
trix-toolbar .trix-button--icon {
height: 2em;
max-width: calc(0.8em + 3.5vw); } }
trix-toolbar .trix-button--icon::before {
display: inline-block;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
opacity: 0.6;
content: "";
background-position: center;
background-repeat: no-repeat;
background-size: contain; }
@media (max-width: 768px) {
trix-toolbar .trix-button--icon::before {
right: 6%;
left: 6%; } }
trix-toolbar .trix-button--icon.trix-active::before {
opacity: 1; }
trix-toolbar .trix-button--icon:disabled::before {
opacity: 0.125; }
trix-toolbar .trix-button--icon-attach::before {
background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M10.5%2018V7.5c0-2.25%203-2.25%203%200V18c0%204.125-6%204.125-6%200V7.5c0-6.375%209-6.375%209%200V18%22%20stroke%3D%22%23000%22%20stroke-width%3D%222%22%20stroke-miterlimit%3D%2210%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%2F%3E%3C%2Fsvg%3E");
top: 8%;
bottom: 4%; }
trix-toolbar .trix-button--icon-bold::before {
background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M6.522%2019.242a.5.5%200%200%201-.5-.5V5.35a.5.5%200%200%201%20.5-.5h5.783c1.347%200%202.46.345%203.24.982.783.64%201.216%201.562%201.216%202.683%200%201.13-.587%202.129-1.476%202.71a.35.35%200%200%200%20.049.613c1.259.56%202.101%201.742%202.101%203.22%200%201.282-.483%202.334-1.363%203.063-.876.726-2.132%201.12-3.66%201.12h-5.89ZM9.27%207.347v3.362h1.97c.766%200%201.347-.17%201.733-.464.38-.291.587-.716.587-1.27%200-.53-.183-.928-.513-1.198-.334-.273-.838-.43-1.505-.43H9.27Zm0%205.606v3.791h2.389c.832%200%201.448-.177%201.853-.497.399-.315.614-.786.614-1.423%200-.62-.22-1.077-.63-1.385-.418-.313-1.053-.486-1.905-.486H9.27Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); }
trix-toolbar .trix-button--icon-italic::before {
background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M9%205h6.5v2h-2.23l-2.31%2010H13v2H6v-2h2.461l2.306-10H9V5Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); }
trix-toolbar .trix-button--icon-link::before {
background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M18.948%205.258a4.337%204.337%200%200%200-6.108%200L11.217%206.87a.993.993%200%200%200%200%201.41c.392.39%201.027.39%201.418%200l1.623-1.613a2.323%202.323%200%200%201%203.271%200%202.29%202.29%200%200%201%200%203.251l-2.393%202.38a3.021%203.021%200%200%201-4.255%200l-.05-.049a1.007%201.007%200%200%200-1.418%200%20.993.993%200%200%200%200%201.41l.05.049a5.036%205.036%200%200%200%207.091%200l2.394-2.38a4.275%204.275%200%200%200%200-6.072Zm-13.683%2013.6a4.337%204.337%200%200%200%206.108%200l1.262-1.255a.993.993%200%200%200%200-1.41%201.007%201.007%200%200%200-1.418%200L9.954%2017.45a2.323%202.323%200%200%201-3.27%200%202.29%202.29%200%200%201%200-3.251l2.344-2.331a2.579%202.579%200%200%201%203.631%200c.392.39%201.027.39%201.419%200a.993.993%200%200%200%200-1.41%204.593%204.593%200%200%200-6.468%200l-2.345%202.33a4.275%204.275%200%200%200%200%206.072Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); }
trix-toolbar .trix-button--icon-strike::before {
background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M6%2014.986c.088%202.647%202.246%204.258%205.635%204.258%203.496%200%205.713-1.728%205.713-4.463%200-.275-.02-.536-.062-.781h-3.461c.398.293.573.654.573%201.123%200%201.035-1.074%201.787-2.646%201.787-1.563%200-2.773-.762-2.91-1.924H6ZM6.432%2010h3.763c-.632-.314-.914-.715-.914-1.273%200-1.045.977-1.739%202.432-1.739%201.475%200%202.52.723%202.617%201.914h2.764c-.05-2.548-2.11-4.238-5.39-4.238-3.145%200-5.392%201.719-5.392%204.316%200%20.363.04.703.12%201.02ZM4%2011a1%201%200%201%200%200%202h15a1%201%200%201%200%200-2H4Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); }
trix-toolbar .trix-button--icon-quote::before {
background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M4.581%208.471c.44-.5%201.056-.834%201.758-.995C8.074%207.17%209.201%207.822%2010%208.752c1.354%201.578%201.33%203.555.394%205.277-.941%201.731-2.788%203.163-4.988%203.56a.622.622%200%200%201-.653-.317c-.113-.205-.121-.49.16-.764.294-.286.567-.566.791-.835.222-.266.413-.54.524-.815.113-.28.156-.597.026-.908-.128-.303-.39-.524-.72-.69a3.02%203.02%200%200%201-1.674-2.7c0-.905.283-1.59.72-2.088Zm9.419%200c.44-.5%201.055-.834%201.758-.995%201.734-.306%202.862.346%203.66%201.276%201.355%201.578%201.33%203.555.395%205.277-.941%201.731-2.789%203.163-4.988%203.56a.622.622%200%200%201-.653-.317c-.113-.205-.122-.49.16-.764.294-.286.567-.566.791-.835.222-.266.412-.54.523-.815.114-.28.157-.597.026-.908-.127-.303-.39-.524-.72-.69a3.02%203.02%200%200%201-1.672-2.701c0-.905.283-1.59.72-2.088Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); }
trix-toolbar .trix-button--icon-heading-1::before {
background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M21.5%207.5v-3h-12v3H14v13h3v-13h4.5ZM9%2013.5h3.5v-3h-10v3H6v7h3v-7Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); }
trix-toolbar .trix-button--icon-code::before {
background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M3.293%2011.293a1%201%200%200%200%200%201.414l4%204a1%201%200%201%200%201.414-1.414L5.414%2012l3.293-3.293a1%201%200%200%200-1.414-1.414l-4%204Zm13.414%205.414%204-4a1%201%200%200%200%200-1.414l-4-4a1%201%200%201%200-1.414%201.414L18.586%2012l-3.293%203.293a1%201%200%200%200%201.414%201.414Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); }
trix-toolbar .trix-button--icon-bullet-list::before {
background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M5%207.5a1.5%201.5%200%201%200%200-3%201.5%201.5%200%200%200%200%203ZM8%206a1%201%200%200%201%201-1h11a1%201%200%201%201%200%202H9a1%201%200%200%201-1-1Zm1%205a1%201%200%201%200%200%202h11a1%201%200%201%200%200-2H9Zm0%206a1%201%200%201%200%200%202h11a1%201%200%201%200%200-2H9Zm-2.5-5a1.5%201.5%200%201%201-3%200%201.5%201.5%200%200%201%203%200ZM5%2019.5a1.5%201.5%200%201%200%200-3%201.5%201.5%200%200%200%200%203Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); }
trix-toolbar .trix-button--icon-number-list::before {
background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M3%204h2v4H4V5H3V4Zm5%202a1%201%200%200%201%201-1h11a1%201%200%201%201%200%202H9a1%201%200%200%201-1-1Zm1%205a1%201%200%201%200%200%202h11a1%201%200%201%200%200-2H9Zm0%206a1%201%200%201%200%200%202h11a1%201%200%201%200%200-2H9Zm-3.5-7H6v1l-1.5%202H6v1H3v-1l1.667-2H3v-1h2.5ZM3%2017v-1h3v4H3v-1h2v-.5H4v-1h1V17H3Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); }
trix-toolbar .trix-button--icon-undo::before {
background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M3%2014a1%201%200%200%200%201%201h6a1%201%200%201%200%200-2H6.257c2.247-2.764%205.151-3.668%207.579-3.264%202.589.432%204.739%202.356%205.174%205.405a1%201%200%200%200%201.98-.283c-.564-3.95-3.415-6.526-6.825-7.095C11.084%207.25%207.63%208.377%205%2011.39V8a1%201%200%200%200-2%200v6Zm2-1Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); }
trix-toolbar .trix-button--icon-redo::before {
background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M21%2014a1%201%200%200%201-1%201h-6a1%201%200%201%201%200-2h3.743c-2.247-2.764-5.151-3.668-7.579-3.264-2.589.432-4.739%202.356-5.174%205.405a1%201%200%200%201-1.98-.283c.564-3.95%203.415-6.526%206.826-7.095%203.08-.513%206.534.614%209.164%203.626V8a1%201%200%201%201%202%200v6Zm-2-1Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); }
trix-toolbar .trix-button--icon-decrease-nesting-level::before {
background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M5%206a1%201%200%200%201%201-1h12a1%201%200%201%201%200%202H6a1%201%200%200%201-1-1Zm4%205a1%201%200%201%200%200%202h9a1%201%200%201%200%200-2H9Zm-3%206a1%201%200%201%200%200%202h12a1%201%200%201%200%200-2H6Zm-3.707-5.707a1%201%200%200%200%200%201.414l2%202a1%201%200%201%200%201.414-1.414L4.414%2012l1.293-1.293a1%201%200%200%200-1.414-1.414l-2%202Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); }
trix-toolbar .trix-button--icon-increase-nesting-level::before {
background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M5%206a1%201%200%200%201%201-1h12a1%201%200%201%201%200%202H6a1%201%200%200%201-1-1Zm4%205a1%201%200%201%200%200%202h9a1%201%200%201%200%200-2H9Zm-3%206a1%201%200%201%200%200%202h12a1%201%200%201%200%200-2H6Zm-2.293-2.293%202-2a1%201%200%200%200%200-1.414l-2-2a1%201%200%201%200-1.414%201.414L3.586%2012l-1.293%201.293a1%201%200%201%200%201.414%201.414Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); }
trix-toolbar .trix-dialogs {
position: relative; }
trix-toolbar .trix-dialog {
position: absolute;
top: 0;
left: 0;
right: 0;
font-size: 0.75em;
padding: 15px 10px;
background: #fff;
box-shadow: 0 0.3em 1em #ccc;
border-top: 2px solid #888;
border-radius: 5px;
z-index: 5; }
trix-toolbar .trix-input--dialog {
font-size: inherit;
font-weight: normal;
padding: 0.5em 0.8em;
margin: 0 10px 0 0;
border-radius: 3px;
border: 1px solid #bbb;
background-color: #fff;
box-shadow: none;
outline: none;
-webkit-appearance: none;
-moz-appearance: none; }
trix-toolbar .trix-input--dialog.validate:invalid {
box-shadow: #F00 0px 0px 1.5px 1px; }
trix-toolbar .trix-button--dialog {
font-size: inherit;
padding: 0.5em;
border-bottom: none; }
trix-toolbar .trix-dialog--link {
max-width: 600px; }
trix-toolbar .trix-dialog__link-fields {
display: flex;
align-items: baseline; }
trix-toolbar .trix-dialog__link-fields .trix-input {
flex: 1; }
trix-toolbar .trix-dialog__link-fields .trix-button-group {
flex: 0 0 content;
margin: 0; }
trix-editor [data-trix-mutable]:not(.attachment__caption-editor) {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none; }
trix-editor [data-trix-mutable]::-moz-selection,
trix-editor [data-trix-cursor-target]::-moz-selection, trix-editor [data-trix-mutable] ::-moz-selection {
background: none; }
trix-editor [data-trix-mutable]::selection,
trix-editor [data-trix-cursor-target]::selection, trix-editor [data-trix-mutable] ::selection {
background: none; }
trix-editor .attachment__caption-editor:focus[data-trix-mutable]::-moz-selection {
background: highlight; }
trix-editor .attachment__caption-editor:focus[data-trix-mutable]::selection {
background: highlight; }
trix-editor [data-trix-mutable].attachment.attachment--file {
box-shadow: 0 0 0 2px highlight;
border-color: transparent; }
trix-editor [data-trix-mutable].attachment img {
box-shadow: 0 0 0 2px highlight; }
trix-editor .attachment {
position: relative; }
trix-editor .attachment:hover {
cursor: default; }
trix-editor .attachment--preview .attachment__caption:hover {
cursor: text; }
trix-editor .attachment__progress {
position: absolute;
z-index: 1;
height: 20px;
top: calc(50% - 10px);
left: 5%;
width: 90%;
opacity: 0.9;
transition: opacity 200ms ease-in; }
trix-editor .attachment__progress[value="100"] {
opacity: 0; }
trix-editor .attachment__caption-editor {
display: inline-block;
width: 100%;
margin: 0;
padding: 0;
font-size: inherit;
font-family: inherit;
line-height: inherit;
color: inherit;
text-align: center;
vertical-align: top;
border: none;
outline: none;
-webkit-appearance: none;
-moz-appearance: none; }
trix-editor .attachment__toolbar {
position: absolute;
z-index: 1;
top: -0.9em;
left: 0;
width: 100%;
text-align: center; }
trix-editor .trix-button-group {
display: inline-flex; }
trix-editor .trix-button {
position: relative;
float: left;
color: #666;
white-space: nowrap;
font-size: 80%;
padding: 0 0.8em;
margin: 0;
outline: none;
border: none;
border-radius: 0;
background: transparent; }
trix-editor .trix-button:not(:first-child) {
border-left: 1px solid #ccc; }
trix-editor .trix-button.trix-active {
background: #cbeefa; }
trix-editor .trix-button:not(:disabled) {
cursor: pointer; }
trix-editor .trix-button--remove {
text-indent: -9999px;
display: inline-block;
padding: 0;
outline: none;
width: 1.8em;
height: 1.8em;
line-height: 1.8em;
border-radius: 50%;
background-color: #fff;
border: 2px solid highlight;
box-shadow: 1px 1px 6px rgba(0, 0, 0, 0.25); }
trix-editor .trix-button--remove::before {
display: inline-block;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
opacity: 0.7;
content: "";
background-image: url("data:image/svg+xml,%3Csvg%20height%3D%2224%22%20width%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M19%206.41%2017.59%205%2012%2010.59%206.41%205%205%206.41%2010.59%2012%205%2017.59%206.41%2019%2012%2013.41%2017.59%2019%2019%2017.59%2013.41%2012z%22%2F%3E%3Cpath%20d%3D%22M0%200h24v24H0z%22%20fill%3D%22none%22%2F%3E%3C%2Fsvg%3E");
background-position: center;
background-repeat: no-repeat;
background-size: 90%; }
trix-editor .trix-button--remove:hover {
border-color: #333; }
trix-editor .trix-button--remove:hover::before {
opacity: 1; }
trix-editor .attachment__metadata-container {
position: relative; }
trix-editor .attachment__metadata {
position: absolute;
left: 50%;
top: 2em;
transform: translate(-50%, 0);
max-width: 90%;
padding: 0.1em 0.6em;
font-size: 0.8em;
color: #fff;
background-color: rgba(0, 0, 0, 0.7);
border-radius: 3px; }
trix-editor .attachment__metadata .attachment__name {
display: inline-block;
max-width: 100%;
vertical-align: bottom;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap; }
trix-editor .attachment__metadata .attachment__size {
margin-left: 0.2em;
white-space: nowrap; }
.trix-content {
line-height: 1.5;
overflow-wrap: break-word;
word-break: break-word; }
.trix-content * {
box-sizing: border-box;
margin: 0;
padding: 0; }
.trix-content h1 {
font-size: 1.2em;
line-height: 1.2; }
.trix-content blockquote {
border: 0 solid #ccc;
border-left-width: 0.3em;
margin-left: 0.3em;
padding-left: 0.6em; }
.trix-content [dir=rtl] blockquote,
.trix-content blockquote[dir=rtl] {
border-width: 0;
border-right-width: 0.3em;
margin-right: 0.3em;
padding-right: 0.6em; }
.trix-content li {
margin-left: 1em; }
.trix-content [dir=rtl] li {
margin-right: 1em; }
.trix-content pre {
display: inline-block;
width: 100%;
vertical-align: top;
font-family: monospace;
font-size: 0.9em;
padding: 0.5em;
white-space: pre;
background-color: #eee;
overflow-x: auto; }
.trix-content img {
max-width: 100%;
height: auto; }
.trix-content .attachment {
display: inline-block;
position: relative;
max-width: 100%; }
.trix-content .attachment a {
color: inherit;
text-decoration: none; }
.trix-content .attachment a:hover, .trix-content .attachment a:visited:hover {
color: inherit; }
.trix-content .attachment__caption {
text-align: center; }
.trix-content .attachment__caption .attachment__name + .attachment__size::before {
content: ' \2022 '; }
.trix-content .attachment--preview {
width: 100%;
text-align: center; }
.trix-content .attachment--preview .attachment__caption {
color: #666;
font-size: 0.9em;
line-height: 1.2; }
.trix-content .attachment--file {
color: #333;
line-height: 1;
margin: 0 2px 2px 2px;
padding: 0.4em 1em;
border: 1px solid #bbb;
border-radius: 5px; }
.trix-content .attachment-gallery {
display: flex;
flex-wrap: wrap;
position: relative; }
.trix-content .attachment-gallery .attachment {
flex: 1 0 33%;
padding: 0 0.5em;
max-width: 33%; }
.trix-content .attachment-gallery.attachment-gallery--2 .attachment, .trix-content .attachment-gallery.attachment-gallery--4 .attachment {
flex-basis: 50%;
max-width: 50%; }
/*
* We need to override trix.csss image gallery styles to accommodate the
* <action-text-attachment> element we wrap around attachments. Otherwise,
* images in galleries will be squished by the max-width: 33%; rule.
*/
.trix-content .attachment-gallery > action-text-attachment,
.trix-content .attachment-gallery > .attachment {
flex: 1 0 33%;
padding: 0 0.5em;
max-width: 33%;
}
.trix-content .attachment-gallery.attachment-gallery--2 > action-text-attachment,
.trix-content .attachment-gallery.attachment-gallery--2 > .attachment, .trix-content .attachment-gallery.attachment-gallery--4 > action-text-attachment,
.trix-content .attachment-gallery.attachment-gallery--4 > .attachment {
flex-basis: 50%;
max-width: 50%;
}
.trix-content action-text-attachment .attachment {
padding: 0 !important;
max-width: 100% !important;
}
+1 -1
View File
@@ -58,7 +58,7 @@ module NotificationsHelper
def comment_notification_body(event)
comment = event.eventable
"#{strip_tags(comment.body_html).blank? ? "#{event.creator.name} replied" : "#{event.creator.name}:" } #{strip_tags(comment.body_html).truncate(200)}"
"#{strip_tags(comment.body.to_s).blank? ? "#{event.creator.name} replied" : "#{event.creator.name}:" } #{strip_tags(comment.body.to_s).truncate(200)}"
end
def card_notification_title(card)
+29
View File
@@ -0,0 +1,29 @@
module RichTextareaHelper
def rich_textarea_toolbar(id = "editor-toolbar")
content_tag("lexical-toolbar", id: id) do
safe_join([
# Inline formatting
rich_textarea_toolbar_button("Bold", "bold"),
rich_textarea_toolbar_button("Italic", "italic"),
rich_textarea_toolbar_button("Link", "link"),
# Block-level formatting
rich_textarea_toolbar_button("Heading", "formatElement", data: { payload: "h2" }),
rich_textarea_toolbar_button("Subheading", "formatElement", data: { payload: "h3" }),
rich_textarea_toolbar_button("Code Block", "insertCodeBlock"),
# Lists
rich_textarea_toolbar_button("• Bullet List", "insertUnorderedList"),
rich_textarea_toolbar_button("1. Numbered List", "insertOrderedList"),
# Attachments
rich_textarea_toolbar_button("Upload Attachment", "uploadAttachments")
])
end
end
def rich_textarea_toolbar_button(label, command, data: {}, **properties)
data[:command] = command
content_tag(:button, label, type: "button", data: data, **properties)
end
end
+3
View File
@@ -3,3 +3,6 @@ import "@hotwired/turbo-rails"
import "initializers"
import "controllers"
import "house"
import "actiontext-lexical"
import "@rails/actiontext"
@@ -30,9 +30,11 @@ export default class extends Controller {
async restoreContent() {
await nextFrame()
const savedContent = localStorage.getItem(this.keyValue)
let savedContent = localStorage.getItem(this.keyValue)
if (savedContent) {
savedContent = `<div>${savedContent}</div>` // temporary for old markdown saves
console.debug("Es", savedContent);
this.inputTarget.value = savedContent
this.#triggerChangeEvent(savedContent)
}
+1 -1
View File
@@ -9,7 +9,7 @@ class Card < ApplicationRecord
has_many :comments, dependent: :destroy
has_one_attached :image, dependent: :purge_later
has_markdown :description
has_rich_text :description
before_save :set_default_title, if: :published?
+5 -1
View File
@@ -5,7 +5,7 @@ class Comment < ApplicationRecord
belongs_to :creator, class_name: "User", default: -> { Current.user }
has_many :reactions, dependent: :delete_all
has_markdown :body
has_rich_text :body
searchable_by :body_plain_text, using: :comments_search_index, as: :body
scope :chronologically, -> { order created_at: :asc, id: :desc }
@@ -19,6 +19,10 @@ class Comment < ApplicationRecord
end
private
def body_plain_text
body.to_plain_text
end
def watch_card_by_creator
card.watch_by creator
end
+4 -4
View File
@@ -14,7 +14,7 @@ module Mentions
end
def mentionable_content
markdown_associations.collect { send(it.name)&.to_plain_text }.compact.join(" ")
rich_text_associations.collect { send(it.name)&.to_plain_text }.compact.join(" ")
end
private
@@ -32,12 +32,12 @@ module Mentions
collection.users
end
def markdown_associations
self.class.reflect_on_all_associations(:has_one).filter { it.klass == ActionText::Markdown }
def rich_text_associations
self.class.reflect_on_all_associations(:has_one).filter { it.klass == ActionText::RichText }
end
def mentionable_content_changed?
markdown_associations.any? { send(it.name).content_previously_changed? }
rich_text_associations.any? { send(it.name).body_previously_changed? }
end
def create_mentions_later
@@ -0,0 +1,14 @@
<figure class="attachment attachment--<%= blob.representable? ? "preview" : "file" %> attachment--<%= blob.filename.extension %>">
<% if blob.representable? %>
<%= image_tag blob.representation(resize_to_limit: local_assigns[:in_gallery] ? [ 800, 600 ] : [ 1024, 768 ]) %>
<% end %>
<figcaption class="attachment__caption">
<% if caption = blob.try(:caption) %>
<%= caption %>
<% else %>
<span class="attachment__name"><%= blob.filename %></span>
<span class="attachment__size"><%= number_to_human_size blob.byte_size %></span>
<% end %>
</figcaption>
</figure>
+1 -1
View File
@@ -29,7 +29,7 @@
</div>
<div class="comment__body markdown-content txt-align-start">
<%= sanitize comment.body_html %>
<%= comment.body %>
</div>
<%= render "cards/comments/reactions/reactions", comment: comment %>
+2 -1
View File
@@ -9,7 +9,8 @@
data: { controller: "form paste 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 paste->paste#pasteFiles" } do |form| %>
<%= form.markdown_area :body, class: "input comment__input", required: true, placeholder: new_comment_placeholder(card),
<%= rich_textarea_toolbar "editor-toolbar" %>
<%= form.rich_textarea :body, class: "input comment__input", toolbar: "editor-toolbar", required: true, placeholder: new_comment_placeholder(card),
data: { local_save_target: "input", action: "house-md:change->local-save#save turbo:morph-element->local-save#restoreContent" } %>
<%= form.button class: "comment__submit btn btn--reversed flex-item-justify-start" do %>
<span>Post this comment</span>
+2 -1
View File
@@ -8,7 +8,8 @@
<div class="comment__body markdown-content txt-align-start">
<%= form_with model: [ @card, @comment ], class: "flex flex-column gap full-width",
data: { controller: "form paste", action: "keydown.ctrl+enter->form#submit:prevent keydown.meta+enter->form#submit:prevent keydown.esc->form#cancel:stop paste->paste#pasteFiles" } do |form| %>
<%= form.markdown_area :body, class: "input comment__input", required: true, autofocus: true, placeholder: new_comment_placeholder(@card) %>
<%= rich_textarea_toolbar "editor-toolbar" %>
<%= form.rich_textarea :body, class: "input comment__input", toolbar: "editor-toolbar", required: true, autofocus: true, placeholder: new_comment_placeholder(@card) %>
<div class="flex gap-half justify-start">
<%= form.button class: "btn btn--reversed", type: :submit do %>
<span>Save changes</span>
+4 -2
View File
@@ -5,7 +5,7 @@
<%= link_to card.title, edit_collection_card_path(card.collection, card), class: "card__title-link" %>
</h1>
<div class="card__description markdown-content margin-block-half">
<%= sanitize card.description_html %>
<%= card.description %>
</div>
</div>
<% end %>
@@ -18,7 +18,9 @@
data: { action: "auto-save#change blur->auto-save#submit keydown.enter->auto-save#submit:prevent keydown.ctrl+enter->auto-save#submit:prevent" } %>
</h1>
<div class="card__description markdown-content margin-block-start-half margin-block-end">
<%= form.markdown_area :description, class: "input input--textarea full-width borderless txt-align-start card-field__description",
<%= rich_textarea_toolbar "editor-toolbar" %>
<%= form.rich_textarea :description, class: "input input--textarea full-width borderless txt-align-start card-field__description",
toolbar: "the-toolbar",
placeholder: "Add some notes, context, pictures, or video about this…",
data: { action: "house-md: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 paste->paste#pasteFiles" } %>
</div>
+7 -4
View File
@@ -8,10 +8,13 @@
data: { action: "keydown.enter->form#submit:prevent keydown.ctrl+enter->form#submit:prevent keydown.meta+enter->form#submit:prevent keydown.esc->form#cancel focus->form#select" } %>
<% end %>
</h1>
<div class="card__description markdown-content margin-block-start-half margin-block-end">
<%= form.markdown_area :description, class: "input input--textarea full-width borderless txt-align-start card-field__description",
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 paste->paste#pasteFiles" } %>
<%= rich_textarea_toolbar "editor-toolbar" %>
<%= form.rich_textarea :description, class: "input input--textarea full-width borderless txt-align-start card-field__description",
toolbar: "editor-toolbar",
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 paste->paste#pasteFiles" } %>
</div>
<%= form.button "Save changes", type: :submit, class: "btn" %>
<%= link_to "Close editor and discard changes", collection_card_path(@card.collection, @card), data: { form_target: "cancel" }, hidden: true %>
+1 -1
View File
@@ -1,5 +1,5 @@
<%= render "events/event/layout", card: event.eventable.card, event: event do %>
<span class="txt-break overflow-line-clamp txt-tight-lines">
<%= strip_tags(event&.eventable.body_html).truncate(200) -%>
<%= strip_tags(event&.eventable.body.to_s).truncate(200) -%>
</span>
<% end %>
@@ -0,0 +1,3 @@
<div class="action-text-content">
<%= yield -%>
</div>
+3
View File
@@ -11,3 +11,6 @@ pin_all_from "app/javascript/controllers", under: "controllers"
pin_all_from "app/javascript/helpers", under: "helpers"
pin_all_from "app/javascript/initializers", under: "initializers"
pin "marked" # @15.0.11
pin "actiontext-lexical"
pin "@rails/activestorage", to: "activestorage.esm.js"
pin "@rails/actiontext", to: "actiontext.esm.js"
-7
View File
@@ -1,7 +0,0 @@
ActiveSupport.on_load :action_text_markdown do
require "markdown_renderer"
require "redcarpet/render_strip"
ActionText::Markdown.html_renderer = ->(content) { MarkdownRenderer.build.render(content) }
ActionText::Markdown.plain_text_renderer = Redcarpet::Markdown.new(Redcarpet::Render::StripDown)
end
@@ -0,0 +1,26 @@
# This migration comes from action_text (originally 20180528164100)
class CreateActionTextTables < ActiveRecord::Migration[6.0]
def change
# Use Active Record's configured type for primary and foreign keys
primary_key_type, foreign_key_type = primary_and_foreign_key_types
create_table :action_text_rich_texts, id: primary_key_type do |t|
t.string :name, null: false
t.text :body, size: :long
t.references :record, null: false, polymorphic: true, index: false, type: foreign_key_type
t.timestamps
t.index [ :record_type, :record_id, :name ], name: "index_action_text_rich_texts_uniqueness", unique: true
end
end
private
def primary_and_foreign_key_types
config = Rails.configuration.generators
setting = config.options[config.orm][:primary_key_type]
primary_key_type = setting || :primary_key
foreign_key_type = setting || :bigint
[ primary_key_type, foreign_key_type ]
end
end
Generated
+11 -1
View File
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[8.1].define(version: 2025_05_15_125505) do
ActiveRecord::Schema[8.1].define(version: 2025_05_23_084734) do
create_table "accesses", force: :cascade do |t|
t.integer "collection_id", null: false
t.datetime "created_at", null: false
@@ -39,6 +39,16 @@ ActiveRecord::Schema[8.1].define(version: 2025_05_15_125505) do
t.index ["record_type", "record_id"], name: "index_action_text_markdowns_on_record"
end
create_table "action_text_rich_texts", force: :cascade do |t|
t.text "body"
t.datetime "created_at", null: false
t.string "name", null: false
t.bigint "record_id", null: false
t.string "record_type", null: false
t.datetime "updated_at", null: false
t.index ["record_type", "record_id", "name"], name: "index_action_text_rich_texts_uniqueness", unique: true
end
create_table "active_storage_attachments", force: :cascade do |t|
t.bigint "blob_id", null: false
t.datetime "created_at", null: false
+116 -78
View File
@@ -1,7 +1,7 @@
--- !ruby/object:ActiveRecord::ConnectionAdapters::SchemaCache
columns:
accesses:
- &22 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
- &23 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: collection_id
cast_type: &1 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3Adapter::SQLite3Integer
@@ -80,7 +80,7 @@ columns:
default_function:
collation:
comment:
- &24 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
- &25 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: user_id
cast_type: *1
@@ -118,13 +118,13 @@ columns:
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: content
cast_type: &14 !ruby/object:ActiveRecord::Type::Text
cast_type: &11 !ruby/object:ActiveRecord::Type::Text
true: t
false: f
precision:
scale:
limit:
sql_type_metadata: &15 !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata
sql_type_metadata: &12 !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata
sql_type: TEXT
type: :text
limit:
@@ -159,17 +159,30 @@ columns:
collation:
comment:
- *9
active_storage_attachments:
action_text_rich_texts:
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: body
cast_type: *11
sql_type_metadata: *12
'null': true
default:
default_function:
collation:
comment:
- *5
- *6
- *10
- &16 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: blob_id
cast_type: &11 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3Adapter::SQLite3Integer
name: record_id
cast_type: &14 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3Adapter::SQLite3Integer
precision:
scale:
limit:
max: 9223372036854775808
min: -9223372036854775808
sql_type_metadata: &12 !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata
sql_type_metadata: &15 !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata
sql_type: bigint
type: :integer
limit:
@@ -180,19 +193,23 @@ columns:
default_function:
collation:
comment:
- *5
- *6
- *10
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
- *13
- *9
active_storage_attachments:
- &17 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: record_id
cast_type: *11
sql_type_metadata: *12
name: blob_id
cast_type: *14
sql_type_metadata: *15
'null': false
default:
default_function:
collation:
comment:
- *5
- *6
- *10
- *16
- *13
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
@@ -208,8 +225,8 @@ columns:
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: byte_size
cast_type: *11
sql_type_metadata: *12
cast_type: *14
sql_type_metadata: *15
'null': false
default:
default_function:
@@ -247,7 +264,7 @@ columns:
collation:
comment:
- *6
- &17 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
- &18 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: key
cast_type: *7
@@ -260,8 +277,8 @@ columns:
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: metadata
cast_type: *14
sql_type_metadata: *15
cast_type: *11
sql_type_metadata: *12
'null': true
default:
default_function:
@@ -278,7 +295,7 @@ columns:
collation:
comment:
active_storage_variant_records:
- *16
- *17
- *6
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
@@ -292,7 +309,7 @@ columns:
comment:
ar_internal_metadata:
- *5
- *17
- *18
- *9
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
@@ -305,7 +322,7 @@ columns:
collation:
comment:
assignees_filters:
- &19 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
- &20 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: assignee_id
cast_type: *1
@@ -315,7 +332,7 @@ columns:
default_function:
collation:
comment:
- &18 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
- &19 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: filter_id
cast_type: *1
@@ -326,7 +343,7 @@ columns:
collation:
comment:
assigners_filters:
- &20 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
- &21 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: assigner_id
cast_type: *1
@@ -336,11 +353,11 @@ columns:
default_function:
collation:
comment:
- *18
assignments:
- *19
assignments:
- *20
- &21 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
- *21
- &22 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: card_id
cast_type: *1
@@ -368,14 +385,14 @@ columns:
- *6
- *9
card_goldnesses:
- *21
- *22
- *5
- *6
- *9
cards:
- *22
- *23
- *5
- &23 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
- &24 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: creator_id
cast_type: *1
@@ -428,14 +445,14 @@ columns:
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: status
cast_type: *14
sql_type_metadata: *15
cast_type: *11
sql_type_metadata: *12
'null': false
default: creating
default_function:
collation:
comment:
- &30 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
- &31 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: title
cast_type: *7
@@ -461,7 +478,7 @@ columns:
comment:
- *9
closures:
- *21
- *22
- *5
- *6
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
@@ -489,11 +506,11 @@ columns:
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: all_access
cast_type: &31 !ruby/object:ActiveModel::Type::Boolean
cast_type: &32 !ruby/object:ActiveModel::Type::Boolean
precision:
scale:
limit:
sql_type_metadata: &32 !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata
sql_type_metadata: &33 !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata
sql_type: boolean
type: :boolean
limit:
@@ -507,15 +524,15 @@ columns:
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: auto_close_period
cast_type: *11
sql_type_metadata: *12
cast_type: *14
sql_type_metadata: *15
'null': true
default:
default_function:
collation:
comment:
- *5
- *23
- *24
- *6
- *10
- *9
@@ -530,18 +547,18 @@ columns:
collation:
comment:
collections_filters:
- *22
- *18
- *23
- *19
commands:
- *5
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: data
cast_type: &25 !ruby/object:ActiveRecord::Type::Json
cast_type: &26 !ruby/object:ActiveRecord::Type::Json
precision:
scale:
limit:
sql_type_metadata: &26 !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata
sql_type_metadata: &27 !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata
sql_type: json
type: :json
limit:
@@ -556,8 +573,8 @@ columns:
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: line
cast_type: *14
sql_type_metadata: *15
cast_type: *11
sql_type_metadata: *12
'null': true
default:
default_function:
@@ -584,16 +601,16 @@ columns:
collation:
comment:
- *9
- *24
- *25
comments:
- *21
- *22
- *5
- *23
- *24
- *6
- *9
creators_filters:
- *23
- *18
- *24
- *19
events:
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
@@ -605,9 +622,9 @@ columns:
default_function:
collation:
comment:
- *22
- *5
- *23
- *5
- *24
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: eventable_id
@@ -632,8 +649,8 @@ columns:
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: particulars
cast_type: *25
sql_type_metadata: *26
cast_type: *26
sql_type_metadata: *27
'null': true
default: "{}"
default_function:
@@ -642,12 +659,12 @@ columns:
- *9
filters:
- *5
- *23
- *24
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: fields
cast_type: *25
sql_type_metadata: *26
cast_type: *26
sql_type_metadata: *27
'null': false
default: "{}"
default_function:
@@ -666,7 +683,7 @@ columns:
comment:
- *9
filters_stages:
- *18
- *19
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: stage_id
@@ -678,8 +695,8 @@ columns:
collation:
comment:
filters_tags:
- *18
- &29 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
- *19
- &30 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: tag_id
cast_type: *1
@@ -712,7 +729,7 @@ columns:
default_function:
collation:
comment:
- &27 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
- &28 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: source_id
cast_type: *1
@@ -722,7 +739,7 @@ columns:
default_function:
collation:
comment:
- &28 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
- &29 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: source_type
cast_type: *7
@@ -756,16 +773,16 @@ columns:
default_function:
collation:
comment:
- *27
- *28
- *29
- *9
- *24
- *25
pins:
- *21
- *22
- *5
- *6
- *9
- *24
- *25
reactions:
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
@@ -882,24 +899,24 @@ columns:
default_function:
collation:
comment:
- *24
- *25
taggings:
- *21
- *22
- *5
- *6
- *29
- *30
- *9
tags:
- *5
- *6
- *30
- *31
- *9
users:
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: active
cast_type: *31
sql_type_metadata: *32
cast_type: *32
sql_type_metadata: *33
'null': false
default: true
default_function:
@@ -940,16 +957,16 @@ columns:
comment:
- *9
watches:
- *21
- *22
- *5
- *6
- *9
- *24
- *25
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: watching
cast_type: *31
sql_type_metadata: *32
cast_type: *32
sql_type_metadata: *33
'null': false
default: true
default_function:
@@ -989,6 +1006,7 @@ primary_keys:
accesses: id
accounts: id
action_text_markdowns: id
action_text_rich_texts: id
active_storage_attachments: id
active_storage_blobs: id
active_storage_variant_records: id
@@ -1027,6 +1045,7 @@ data_sources:
accesses: true
accounts: true
action_text_markdowns: true
action_text_rich_texts: true
active_storage_attachments: true
active_storage_blobs: true
active_storage_variant_records: true
@@ -1131,6 +1150,25 @@ indexes:
nulls_not_distinct:
comment:
valid: true
action_text_rich_texts:
- !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition
table: action_text_rich_texts
name: index_action_text_rich_texts_uniqueness
unique: true
columns:
- record_type
- record_id
- name
lengths: {}
orders: {}
opclasses: {}
where:
type:
using:
include:
nulls_not_distinct:
comment:
valid: true
active_storage_attachments:
- !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition
table: active_storage_attachments
@@ -2161,4 +2199,4 @@ indexes:
comment:
valid: true
workflows: []
version: 20250515125505
version: 20250523084734
-81
View File
@@ -1,81 +0,0 @@
require "rouge/plugins/redcarpet"
class MarkdownRenderer < Redcarpet::Render::HTML
include Rouge::Plugins::Redcarpet
def self.build
Redcarpet::Markdown.new MarkdownRenderer.new(filter_html: false),
autolink: true, highlight: true, no_intra_emphasis: true,
fenced_code_blocks: true, lax_spacing: true, strikethrough: true, tables: true
end
def initialize(...)
super
@id_counts = Hash.new 0
end
def header(text, header_level)
unique_id(text).then do |id|
<<~HTML.chomp
<h#{header_level} id="#{id}">
#{text} <a href="##{id}" class="heading__link" aria-hidden="true">#</a>
</h#{header_level}>
HTML
end
end
def image(url, title, alt_text)
<<~HTML.chomp
<a title="#{title}" href="#{url}" data-action="lightbox#open:prevent" data-lightbox-target="image" data-lightbox-url-value="#{url}?disposition=attachment">
<img src="#{url}" alt="#{alt_text}">
</a>
HTML
end
def code_block(code, language)
block_code(code, language) # call Rouge Redcarpet plugin
end
def link(url, title, content)
return video_tag(url, title) if video_url?(url)
attributes = { href: url }
attributes[:title] = title if title
attributes["data-turbo-frame"] = "_top"
"<a #{html_attributes(attributes)}>#{content}</a>"
end
def autolink(url, link_type)
attributes = { href: url }
attributes["data-turbo-frame"] = "_top"
"<a #{html_attributes(attributes)}>#{url}</a>"
end
private
attr_reader :id_counts
def html_attributes(attributes)
attributes.map { |key, value| %Q(#{key}="#{value}") }.join(" ")
end
def unique_id(text)
text.parameterize.then do |base_id|
id_counts[base_id] += 1
id_counts[base_id] > 1 ? "#{base_id}-#{id_counts[base_id]}" : base_id
end
end
def video_url?(url)
File.extname(url).downcase.match?(/\.(mp4|webm|ogg|mov|avi|mkv)/)
end
def video_tag(url, title)
<<~HTML.chomp
<video controls title="#{title}" width="1024">
<source src="#{url}" type="video/#{File.extname(url).delete('.')}">
</video>
HTML
end
end
-47
View File
@@ -1,47 +0,0 @@
module ActionText
module HasMarkdown
extend ActiveSupport::Concern
class_methods do
def has_markdown(name, strict_loading: strict_loading_by_default)
class_eval <<-CODE, __FILE__, __LINE__ + 1
def #{name}
markdown_#{name} || build_markdown_#{name}
end
def #{name}_html
#{name}.to_html
end
def #{name}_plain_text
#{name}.to_plain_text
end
def #{name}?
markdown_#{name}.present?
end
def #{name}=(content)
self.#{name}.content = content
end
CODE
has_one :"markdown_#{name}", -> { where(name: name) },
class_name: "ActionText::Markdown", as: :record, inverse_of: :record, autosave: true, dependent: :destroy,
strict_loading: strict_loading
scope :"with_markdown_#{name}", -> { includes("markdown_#{name}") }
end
end
def safe_markdown_attribute(name)
if self.class.reflect_on_association("markdown_#{name}")&.klass == ActionText::Markdown
public_send(name)
end
end
end
end
ActiveSupport.on_load :active_record do
include ActionText::HasMarkdown
end
-44
View File
@@ -1,44 +0,0 @@
module ActionText
class Markdown < Record
mattr_accessor :html_renderer
mattr_accessor :plain_text_renderer
belongs_to :record, polymorphic: true, touch: true
def to_html
to_unsafe_html.html_safe
end
def to_unsafe_html
if html_renderer.respond_to? :call
html_renderer.call content
else
html_renderer.render content
end
end
def to_plain_text
if plain_text_renderer.respond_to? :call
plain_text_renderer.call content
else
plain_text_renderer.render content
end
end
end
end
module ActionText::Markdown::Uploads
extend ActiveSupport::Concern
included do
has_many_attached :uploads, dependent: :destroy
end
end
ActiveSupport.on_load :active_storage_attachment do
class ActionText::Markdown
include ActionText::Markdown::Uploads
end
end
ActiveSupport.run_load_hooks :action_text_markdown, ActionText::Markdown
-36
View File
@@ -1,36 +0,0 @@
module ActionText
module TagHelper
def markdown_area(record, name, value: nil, data: {}, **options)
field_name = "#{record.class.model_name.param_key}[#{name}]"
value = record.safe_markdown_attribute(name).content.to_s if value.nil?
data = data.reverse_merge! uploads_url: uploads_url(format: "json")
tag.house_md value, name: field_name, data: data, **options
end
def house_toolbar(**options, &block)
tag.house_md_toolbar(**options, &block)
end
def house_toolbar_button(action, **options, &block)
tag.button title: action.to_s.humanize, data: { "house-md-action": action }, **options, &block
end
def house_toolbar_file_upload_button(name: "upload", title: "Upload File", **options, &block)
tag.label title: title, **options do
safe_join [
file_field_tag(name, data: { "house-md-toolbar-file-picker": true }, style: "display: none;"),
capture(&block)
]
end
end
end
end
module ActionView::Helpers
class FormBuilder
def markdown_area(method, **options)
@template.markdown_area(@object, method, **options)
end
end
end
-32
View File
@@ -1,32 +0,0 @@
module ActiveStorage::Sluggable
extend ActiveSupport::Concern
included do
before_create :set_slug
end
def slug_url(host: ActiveStorage::Current.url_options[:host], port: ActiveStorage::Current.url_options[:port])
Rails.application.routes.url_helpers.upload_url(slug, host: host, port: port)
end
private
def set_slug
self.slug = "#{slug_basename}-#{SecureRandom.alphanumeric(6)}.#{slug_extension}"
end
def slug_basename
File.basename(slug_filename, ".*").parameterize
end
def slug_extension
File.extname(slug_filename).delete(".").parameterize
end
def slug_filename
slug.presence || filename.to_s
end
end
ActiveSupport.on_load :active_storage_attachment do
ActiveStorage::Attachment.include ActiveStorage::Sluggable
end
+1 -19
View File
@@ -214,9 +214,6 @@ class EnactTenanting
assert_count Tagging, Tagging.count
assert_count Watch, Watch.count
# we're only copying the markdown records that are still accessible.
assert_count ActionText::Markdown, Comment.count
assert_count Event, Event.count
assert_count Notification, Notification.count
@@ -228,22 +225,7 @@ class EnactTenanting
end
def update_action_text_urls
update_counter["ActionText::Markdown"] = 0
TENANTS.each do |tenant|
tenanted_domain = sprintf(TENANTED_DOMAIN, tenant: tenant)
ApplicationRecord.with_tenant(tenant) do
ActionText::Markdown.all.each do |markdown|
content = markdown.content
next unless content =~ %r{#{CURRENT_DOMAIN}/u/}
content.gsub!(%r{#{CURRENT_DOMAIN}/u/}, "#{tenanted_domain}/u/")
markdown.update_column :content, content
update_counter["ActionText::Markdown"] += 1
end
end
end
raise "No more markdown"
end
def hardlink_active_storage
+3 -3
View File
@@ -100,9 +100,9 @@ ApplicationRecord.with_each_tenant do |tenant|
end
# Update action_text_markdowns table (polymorphic relationship)
ActionText::Markdown.where(record_type: "Card").find_each do |markdown|
if id_mapping[markdown.record_id]
markdown.update_column(:record_id, id_mapping[markdown.record_id])
ActionText::RichText.where(record_type: "Card").find_each do |rich_text|
if id_mapping[rich_text.record_id]
rich_text.update_column(:record_id, id_mapping[rich_text.record_id])
end
end
+3 -3
View File
@@ -73,9 +73,9 @@ ApplicationRecord.with_each_tenant do |tenant|
end
# Update action_text_markdowns table (polymorphic relationship)
ActionText::Markdown.where(record_type: "Collection").find_each do |markdown|
if id_mapping[markdown.record_id]
markdown.update_column(:record_id, id_mapping[markdown.record_id])
ActionText::RichText.where(record_type: "Collection").find_each do |rich_text|
if id_mapping[rich_text.record_id]
rich_text.update_column(:record_id, id_mapping[rich_text.record_id])
end
end
+1 -1
View File
@@ -46,7 +46,7 @@ class CardsControllerTest < ActionDispatch::IntegrationTest
assert_equal "moon.jpg", card.image.filename.to_s
assert_equal [ tags(:mobile) ], card.tags
assert_equal "Something more in-depth", card.description_plain_text.strip
assert_equal "Something more in-depth", card.description.to_plain_text.strip
end
test "users can only see cards in collections they have access to" do
+4
View File
@@ -0,0 +1,4 @@
# one:
# record: name_of_fixture (ClassOfFixture)
# name: content
# body: <p>In a <i>million</i> stars!</p>