Use House for MD comments
This commit is contained in:
@@ -19,6 +19,9 @@ gem "thruster", require: false
|
|||||||
# Features
|
# Features
|
||||||
gem "bcrypt", "~> 3.1.7"
|
gem "bcrypt", "~> 3.1.7"
|
||||||
gem "rqrcode"
|
gem "rqrcode"
|
||||||
|
gem "redcarpet", "~> 3.6"
|
||||||
|
gem "rouge", "~> 4.5"
|
||||||
|
gem "jbuilder"
|
||||||
|
|
||||||
# Telemetry
|
# Telemetry
|
||||||
gem "sentry-ruby"
|
gem "sentry-ruby"
|
||||||
|
|||||||
@@ -126,6 +126,9 @@ GEM
|
|||||||
irb (1.14.1)
|
irb (1.14.1)
|
||||||
rdoc (>= 4.0.0)
|
rdoc (>= 4.0.0)
|
||||||
reline (>= 0.4.2)
|
reline (>= 0.4.2)
|
||||||
|
jbuilder (2.13.0)
|
||||||
|
actionview (>= 5.0.0)
|
||||||
|
activesupport (>= 5.0.0)
|
||||||
json (2.7.2)
|
json (2.7.2)
|
||||||
language_server-protocol (3.17.0.3)
|
language_server-protocol (3.17.0.3)
|
||||||
logger (1.6.1)
|
logger (1.6.1)
|
||||||
@@ -223,10 +226,12 @@ GEM
|
|||||||
rake (13.2.1)
|
rake (13.2.1)
|
||||||
rdoc (6.7.0)
|
rdoc (6.7.0)
|
||||||
psych (>= 4.0.0)
|
psych (>= 4.0.0)
|
||||||
|
redcarpet (3.6.0)
|
||||||
regexp_parser (2.9.2)
|
regexp_parser (2.9.2)
|
||||||
reline (0.5.10)
|
reline (0.5.10)
|
||||||
io-console (~> 0.5)
|
io-console (~> 0.5)
|
||||||
rexml (3.3.8)
|
rexml (3.3.8)
|
||||||
|
rouge (4.5.1)
|
||||||
rqrcode (2.2.0)
|
rqrcode (2.2.0)
|
||||||
chunky_png (~> 1.0)
|
chunky_png (~> 1.0)
|
||||||
rqrcode_core (~> 1.0)
|
rqrcode_core (~> 1.0)
|
||||||
@@ -335,9 +340,12 @@ DEPENDENCIES
|
|||||||
debug
|
debug
|
||||||
hotwire_combobox!
|
hotwire_combobox!
|
||||||
importmap-rails
|
importmap-rails
|
||||||
|
jbuilder
|
||||||
propshaft
|
propshaft
|
||||||
puma (>= 5.0)
|
puma (>= 5.0)
|
||||||
rails (>= 8.0.0.rc1)
|
rails (>= 8.0.0.rc1)
|
||||||
|
redcarpet (~> 3.6)
|
||||||
|
rouge (~> 4.5)
|
||||||
rqrcode
|
rqrcode
|
||||||
rubocop-rails-omakase
|
rubocop-rails-omakase
|
||||||
selenium-webdriver
|
selenium-webdriver
|
||||||
|
|||||||
@@ -0,0 +1,310 @@
|
|||||||
|
/* Colors */
|
||||||
|
:root {
|
||||||
|
/* Named color values */
|
||||||
|
--house-md-black: 0% 0 0;
|
||||||
|
--house-md-white: 100% 0 0;
|
||||||
|
--house-md-gray-light: 96% 0.005 96;
|
||||||
|
--house-md-gray: 92% 0.005 96;
|
||||||
|
--house-md-gray-dark: 75% 0.005 96;
|
||||||
|
--house-md-blue: 54% 0.23 255;
|
||||||
|
--house-md-blue-light: 95% 0.03 255;
|
||||||
|
--house-md-blue-dark: 80% 0.08 255;
|
||||||
|
--house-md-orange: 70% 0.2 44;
|
||||||
|
--house-md-red: 51% 0.2 31;
|
||||||
|
--house-md-green: 65.59% 0.234 142.49;
|
||||||
|
--house-md-always-black: 0% 0 0;
|
||||||
|
|
||||||
|
/* Abstractions */
|
||||||
|
--house-color-negative: oklch(var(--house-md-red));
|
||||||
|
--house-color-positive: oklch(var(--house-md-green));
|
||||||
|
--house-color-bg: oklch(var(--house-md-white));
|
||||||
|
--house-color-ink: oklch(var(--house-md-black));
|
||||||
|
--house-color-ink-reversed: oklch(var(--house-md-white));
|
||||||
|
--house-color-link: oklch(var(--house-md-blue));
|
||||||
|
--house-color-subtle-light: oklch(var(--house-md-gray-light));
|
||||||
|
--house-color-subtle: oklch(var(--house-md-gray));
|
||||||
|
--house-color-subtle-dark: oklch(var(--house-md-gray-dark));
|
||||||
|
--house-color-selected: oklch(var(--house-md-blue-light));
|
||||||
|
--house-color-selected-dark: oklch(var(--house-md-blue-dark));
|
||||||
|
--house-color-alert: oklch(var(--house-md-orange));
|
||||||
|
--house-color-always-black: oklch(var(--house-md-always-black));
|
||||||
|
|
||||||
|
/* Redefine named color values for dark mode */
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
--house-md-black: 100% 0 0;
|
||||||
|
--house-md-white: 0% 0 0;
|
||||||
|
--house-md-gray: 25.2% 0 0;
|
||||||
|
--house-md-gray-dark: 30.12% 0 0;
|
||||||
|
--house-md-gray-darker: 44.95% 0 0;
|
||||||
|
--house-md-blue: 72.25% 0.16 248;
|
||||||
|
--house-md-blue-light: 28.11% 0.053 248;
|
||||||
|
--house-md-blue-dark: 42.25% 0.07 248;
|
||||||
|
--house-md-red: 73.8% 0.184 29.18;
|
||||||
|
--house-md-green: 75% 0.21 141.89;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Base styles */
|
||||||
|
:is(house-md-editor, house-md-toolbar) {
|
||||||
|
}
|
||||||
|
|
||||||
|
a:not([class]) {
|
||||||
|
color: var(--house-color-link);
|
||||||
|
text-decoration-skip-ink: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
:is(a, button, input, textarea) {
|
||||||
|
--outline-size: max(2px, 0.08em);
|
||||||
|
--outline-style: solid;
|
||||||
|
--house-outline-color: currentColor;
|
||||||
|
|
||||||
|
caret-color: var(--house-color-link);
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: var(--outline-size) var(--outline-style) var(--house-outline-color);
|
||||||
|
outline-offset: var(--outline-offset, var(--outline-size));
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus-visible {
|
||||||
|
outline: var(--outline-size) var(--outline-style) var(--house-outline-color);
|
||||||
|
outline-offset: var(--outline-offset, var(--outline-size));
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus:not(:focus-visible) {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
::selection {
|
||||||
|
background-color: var(--house-color-selected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Editor */
|
||||||
|
house-md {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-grow: 1;
|
||||||
|
|
||||||
|
&:invalid {
|
||||||
|
border: var(--house-color-negative) 2px solid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Toolbar */
|
||||||
|
house-md-toolbar {
|
||||||
|
background-color: inherit;
|
||||||
|
border: 1px solid currentColor;
|
||||||
|
border-radius: 0.5em;
|
||||||
|
color: currentColor;
|
||||||
|
display: inline-flex;
|
||||||
|
font-size: inherit;
|
||||||
|
padding: 0.2em;
|
||||||
|
|
||||||
|
button, label {
|
||||||
|
aspect-ratio: 4/3;
|
||||||
|
appearance: none;
|
||||||
|
background-color: transparent;
|
||||||
|
block-size: 2em;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0.5em;
|
||||||
|
color: currentColor;
|
||||||
|
cursor: pointer;
|
||||||
|
display: grid;
|
||||||
|
font-size: inherit;
|
||||||
|
inline-size: auto;
|
||||||
|
place-items: center;
|
||||||
|
transition: background-color 300ms ease;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
-webkit-touch-callout: none;
|
||||||
|
block-size: 0.9em;
|
||||||
|
fill: currentColor;
|
||||||
|
grid-area: 1/1;
|
||||||
|
inline-size: auto;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:is(:focus, :hover) {
|
||||||
|
background-color: var(--house-color-subtle);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:is(:active) {
|
||||||
|
background-color: var(--house-color-selected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Markdown Content */
|
||||||
|
.house-md-content {
|
||||||
|
caret-color: var(--color-link);
|
||||||
|
flex-grow: 1;
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
min-block-size: 50dvh;
|
||||||
|
text-align: left;
|
||||||
|
white-space: break-spaces;
|
||||||
|
padding: 1rem;
|
||||||
|
|
||||||
|
&.house-md-content-empty::before {
|
||||||
|
content: attr(placeholder);
|
||||||
|
color: var(--house-color-ink);
|
||||||
|
opacity: 0.5;
|
||||||
|
pointer-events: none;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active, &:focus{
|
||||||
|
border:none;
|
||||||
|
outline:none;
|
||||||
|
}
|
||||||
|
|
||||||
|
:is(.h1, .h2, .h3, .h4, .h5, .h6) {
|
||||||
|
display: inline-inblock;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-block: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h1 {
|
||||||
|
font-size: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h2 {
|
||||||
|
font-size: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h3 {
|
||||||
|
font-size: 1.17em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h4 {
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h5 {
|
||||||
|
font-size: 0.83em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h6 {
|
||||||
|
font-size: 0.67em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link, .img {
|
||||||
|
color: var(--house-color-link);
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hr {
|
||||||
|
border-color: var(--color-house-subtle-dark);
|
||||||
|
border-style: solid none none;
|
||||||
|
margin: 2lh auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code {
|
||||||
|
font-family: ui-monospace, monospace;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.85em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quote {
|
||||||
|
font-style: italic;
|
||||||
|
margin: 0 3ch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quote, .ol-li, .ul-li {
|
||||||
|
padding-left: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment {
|
||||||
|
color: var(--color-house-positive);
|
||||||
|
}
|
||||||
|
|
||||||
|
mark {
|
||||||
|
background-color: rgba(241, 241, 26, 0.678);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Uploads */
|
||||||
|
house-md-upload {
|
||||||
|
border-radius: 0.5em;
|
||||||
|
margin-block: 0.5ex;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&[status="failed"] {
|
||||||
|
background-color: var(--house-color-negative);
|
||||||
|
color: var(--house-color-ink-reversed);
|
||||||
|
font-weight: bold;
|
||||||
|
padding: 0.5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-close {
|
||||||
|
appearance: none;
|
||||||
|
display: none;
|
||||||
|
inline-size: 1em;
|
||||||
|
block-size: 1em;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
background-color: transparent;
|
||||||
|
position: absolute;
|
||||||
|
inset: 0.5em 0.5em auto auto;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
house-md-upload[status="failed"] & {
|
||||||
|
color: var(--house-color-ink-reversed);
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '×';
|
||||||
|
color: var(--house-color-ink-reversed);
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: bold;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-file {
|
||||||
|
font-weight: normal;
|
||||||
|
|
||||||
|
house-md-upload[status="complete"] & {
|
||||||
|
color: var(--house-color-positive);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-progress-bar {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
background-color: var(--house-color-subtle);
|
||||||
|
border-radius: 0.5em;
|
||||||
|
block-size: 1ex;
|
||||||
|
display: block;
|
||||||
|
inline-size: 100%;
|
||||||
|
line-height: inherit;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
house-md-upload[status="failed"] & ,
|
||||||
|
house-md-upload[status="complete"] &{
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-progress-bar {
|
||||||
|
border-radius: 0.5em;
|
||||||
|
background-color: var(--house-color-subtle);
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-progress-inner-element {
|
||||||
|
border-radius: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-progress-value {
|
||||||
|
border-radius: 0.5em;
|
||||||
|
background-color: var(--house-color-positive);
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-moz-progress-bar {
|
||||||
|
border-radius: 0.5em;
|
||||||
|
background-color: var(--house-color-positive);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
class ActionText::Markdown::UploadsController < ApplicationController
|
||||||
|
include ActiveStorage::SetCurrent
|
||||||
|
|
||||||
|
def create
|
||||||
|
file = params[:file]
|
||||||
|
blob = ActiveStorage::Blob.create_and_upload! io: file, filename: file.original_filename, content_type: file.content_type
|
||||||
|
@upload = Current.account.uploads_attachments.create! blob: blob
|
||||||
|
render :create, status: :created, formats: :json
|
||||||
|
end
|
||||||
|
|
||||||
|
def show
|
||||||
|
@attachment = ActiveStorage::Attachment.find_by! slug: "#{params[:slug]}.#{params[:format]}"
|
||||||
|
expires_in 1.year, public: true
|
||||||
|
redirect_to @attachment.url
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -2,3 +2,4 @@
|
|||||||
import "@hotwired/turbo-rails"
|
import "@hotwired/turbo-rails"
|
||||||
import "initializers"
|
import "initializers"
|
||||||
import "controllers"
|
import "controllers"
|
||||||
|
import "house"
|
||||||
|
|||||||
@@ -10,4 +10,6 @@ class Account < ApplicationRecord
|
|||||||
has_many :stages, through: :workflows, class_name: "Workflow::Stage"
|
has_many :stages, through: :workflows, class_name: "Workflow::Stage"
|
||||||
|
|
||||||
has_many :tags, dependent: :destroy
|
has_many :tags, dependent: :destroy
|
||||||
|
|
||||||
|
has_many_attached :uploads
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,4 +4,6 @@ class Comment < ApplicationRecord
|
|||||||
belongs_to :creator, class_name: "User", default: -> { Current.user }
|
belongs_to :creator, class_name: "User", default: -> { Current.user }
|
||||||
|
|
||||||
searchable_by :body, using: :comments_search_index
|
searchable_by :body, using: :comments_search_index
|
||||||
|
|
||||||
|
has_markdown :body
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
json.message "File uploaded successfully"
|
||||||
|
json.fileName @upload.filename.to_s
|
||||||
|
json.mimetype @upload.content_type
|
||||||
|
json.fileUrl @upload.slug_url
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<div class="comment__body txt-align-start">
|
<div class="comment__body txt-align-start">
|
||||||
<%= simple_format comment.body %>
|
<%= comment.body.to_html %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="comment__body txt-align-start margin-block-start-half">
|
<div class="comment__body txt-align-start margin-block-start-half">
|
||||||
<%= form_with model: Comment.new, url: bucket_bubble_comments_path(bubble.bucket, bubble), class: "flex flex-column gap full-width", data: { controller: "form", action: "keydown.meta+enter->form#submit" } do |form| %>
|
<%= form_with model: Comment.new, url: bucket_bubble_comments_path(bubble.bucket, bubble), class: "flex flex-column gap full-width", data: { controller: "form", action: "keydown.meta+enter->form#submit" } do |form| %>
|
||||||
<%= form.text_area :body, class: "input border border-radius", required: true, rows: 4, placeholder: new_comment_placeholder(bubble) %>
|
<%= form.markdown_area :body, class: "input border border-radius", required: true, placeholder: new_comment_placeholder(bubble) %>
|
||||||
<%= form.button class: "btn btn--reversed center" do %>
|
<%= form.button class: "btn btn--reversed center" do %>
|
||||||
<%= image_tag "check.svg", aria: { hidden: "true" }, size: 24 %>
|
<%= image_tag "check.svg", aria: { hidden: "true" }, size: 24 %>
|
||||||
<span class="for-screen-reader">Save</span>
|
<span class="for-screen-reader">Save</span>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ pin "application"
|
|||||||
pin "@hotwired/turbo-rails", to: "turbo.min.js"
|
pin "@hotwired/turbo-rails", to: "turbo.min.js"
|
||||||
pin "@hotwired/stimulus", to: "stimulus.min.js"
|
pin "@hotwired/stimulus", to: "stimulus.min.js"
|
||||||
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js"
|
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js"
|
||||||
|
pin "house", to: "house.min.js"
|
||||||
|
|
||||||
pin_all_from "app/javascript/controllers", under: "controllers"
|
pin_all_from "app/javascript/controllers", under: "controllers"
|
||||||
pin_all_from "app/javascript/helpers", under: "helpers"
|
pin_all_from "app/javascript/helpers", under: "helpers"
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
ActiveSupport.on_load :action_text_markdown do
|
||||||
|
require "markdown_renderer"
|
||||||
|
ActionText::Markdown.renderer = -> { MarkdownRenderer.build }
|
||||||
|
end
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
Rails.application.config.after_initialize do
|
||||||
|
%w[ db files ].each do |dir|
|
||||||
|
Rails.root.join("storage", dir).mkpath
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -50,6 +50,11 @@ Rails.application.routes.draw do
|
|||||||
resources :stages, module: :workflows
|
resources :stages, module: :workflows
|
||||||
end
|
end
|
||||||
|
|
||||||
|
namespace :action_text, path: nil do
|
||||||
|
get "/u/*slug" => "markdown/uploads#show", as: :markdown_upload
|
||||||
|
post "/uploads" => "markdown/uploads#create", as: :markdown_uploads
|
||||||
|
end
|
||||||
|
|
||||||
get "join/:join_code", to: "users#new", as: :join
|
get "join/:join_code", to: "users#new", as: :join
|
||||||
post "join/:join_code", to: "users#create"
|
post "join/:join_code", to: "users#create"
|
||||||
get "up", to: "rails/health#show", as: :rails_health_check
|
get "up", to: "rails/health#show", as: :rails_health_check
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ test:
|
|||||||
|
|
||||||
local:
|
local:
|
||||||
service: Disk
|
service: Disk
|
||||||
root: <%= Rails.root.join("storage") %>
|
root: <%= Rails.root.join("storage", "files") %>
|
||||||
|
|
||||||
# Use bin/rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key)
|
# Use bin/rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key)
|
||||||
# amazon:
|
# amazon:
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
class CreateActionTextMarkdowns < ActiveRecord::Migration[8.0]
|
||||||
|
def change
|
||||||
|
create_table :action_text_markdowns do |t|
|
||||||
|
t.references :record, polymorphic: true, null: false
|
||||||
|
t.string :name, null: false
|
||||||
|
t.text :content, null: false, default: ""
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
|
||||||
|
add_column :active_storage_attachments, :slug, :string
|
||||||
|
add_index :active_storage_attachments, :slug, unique: true
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
class RemoveBodyFromComments < ActiveRecord::Migration[8.0]
|
||||||
|
def change
|
||||||
|
remove_column :comments, :body, :text, null: false
|
||||||
|
end
|
||||||
|
end
|
||||||
Generated
+13
-2
@@ -10,7 +10,7 @@
|
|||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema[8.0].define(version: 2024_11_20_222444) do
|
ActiveRecord::Schema[8.0].define(version: 2024_11_26_232658) do
|
||||||
create_table "accesses", force: :cascade do |t|
|
create_table "accesses", force: :cascade do |t|
|
||||||
t.integer "bucket_id", null: false
|
t.integer "bucket_id", null: false
|
||||||
t.integer "user_id", null: false
|
t.integer "user_id", null: false
|
||||||
@@ -30,14 +30,26 @@ ActiveRecord::Schema[8.0].define(version: 2024_11_20_222444) do
|
|||||||
t.index ["name"], name: "index_accounts_on_name", unique: true
|
t.index ["name"], name: "index_accounts_on_name", unique: true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
create_table "action_text_markdowns", force: :cascade do |t|
|
||||||
|
t.string "record_type", null: false
|
||||||
|
t.integer "record_id", null: false
|
||||||
|
t.string "name", null: false
|
||||||
|
t.text "content", default: "", null: false
|
||||||
|
t.datetime "created_at", null: false
|
||||||
|
t.datetime "updated_at", null: false
|
||||||
|
t.index ["record_type", "record_id"], name: "index_action_text_markdowns_on_record"
|
||||||
|
end
|
||||||
|
|
||||||
create_table "active_storage_attachments", force: :cascade do |t|
|
create_table "active_storage_attachments", force: :cascade do |t|
|
||||||
t.string "name", null: false
|
t.string "name", null: false
|
||||||
t.string "record_type", null: false
|
t.string "record_type", null: false
|
||||||
t.bigint "record_id", null: false
|
t.bigint "record_id", null: false
|
||||||
t.bigint "blob_id", null: false
|
t.bigint "blob_id", null: false
|
||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
|
t.string "slug"
|
||||||
t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id"
|
t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id"
|
||||||
t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true
|
t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true
|
||||||
|
t.index ["slug"], name: "index_active_storage_attachments_on_slug", unique: true
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "active_storage_blobs", force: :cascade do |t|
|
create_table "active_storage_blobs", force: :cascade do |t|
|
||||||
@@ -116,7 +128,6 @@ ActiveRecord::Schema[8.0].define(version: 2024_11_20_222444) do
|
|||||||
end
|
end
|
||||||
|
|
||||||
create_table "comments", force: :cascade do |t|
|
create_table "comments", force: :cascade do |t|
|
||||||
t.text "body", null: false
|
|
||||||
t.integer "creator_id", null: false
|
t.integer "creator_id", null: false
|
||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
require "rouge/plugins/redcarpet"
|
||||||
|
|
||||||
|
class MarkdownRenderer < Redcarpet::Render::HTML
|
||||||
|
include Rouge::Plugins::Redcarpet
|
||||||
|
|
||||||
|
def self.build
|
||||||
|
renderer = MarkdownRenderer.new(ActionText::Markdown::DEFAULT_RENDERER_OPTIONS)
|
||||||
|
Redcarpet::Markdown.new(renderer, ActionText::Markdown::DEFAULT_MARKDOWN_EXTENSIONS)
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize(*args)
|
||||||
|
super
|
||||||
|
@id_counts = Hash.new(0)
|
||||||
|
end
|
||||||
|
|
||||||
|
def header(text, header_level)
|
||||||
|
unique_id(text).then do |id|
|
||||||
|
"<h#{header_level} id='#{id}'>#{text} <a href='##{id}' class='heading__link' aria-hidden='true'>#</a></h#{header_level}>"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def image(url, title, alt_text)
|
||||||
|
%(<a title="#{title}" data-action="lightbox#open:prevent" data-lightbox-target="image" data-lightbox-url-value="#{url}?disposition=attachment" href="#{url}"><img src="#{url}" alt="#{alt_text}"></a>)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
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
|
||||||
|
end
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
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}?
|
||||||
|
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
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
module ActionText
|
||||||
|
class Markdown < Record
|
||||||
|
DEFAULT_RENDERER_OPTIONS = {
|
||||||
|
filter_html: false
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFAULT_MARKDOWN_EXTENSIONS = {
|
||||||
|
autolink: true,
|
||||||
|
highlight: true,
|
||||||
|
no_intra_emphasis: true,
|
||||||
|
fenced_code_blocks: true,
|
||||||
|
lax_spacing: true,
|
||||||
|
strikethrough: true,
|
||||||
|
tables: true
|
||||||
|
}
|
||||||
|
|
||||||
|
mattr_accessor :renderer, default: Redcarpet::Markdown.new(
|
||||||
|
Redcarpet::Render::HTML.new(DEFAULT_RENDERER_OPTIONS), DEFAULT_MARKDOWN_EXTENSIONS)
|
||||||
|
|
||||||
|
belongs_to :record, polymorphic: true, touch: true
|
||||||
|
|
||||||
|
def to_html
|
||||||
|
(renderer.try(:call) || renderer).render(content).html_safe
|
||||||
|
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
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
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: action_text_markdown_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
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
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.action_text_markdown_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
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
logo_agreement_jz:
|
||||||
|
record: logo_agreement_jz (Comment)
|
||||||
|
name: body
|
||||||
|
content: I agree.
|
||||||
|
|
||||||
|
logo_agreement_kevin:
|
||||||
|
record: logo_agreement_kevin (Comment)
|
||||||
|
name: body
|
||||||
|
content: Same, let’s do it.
|
||||||
|
|
||||||
|
layout_overflowing_david:
|
||||||
|
record: layout_overflowing_david (Comment)
|
||||||
|
name: body
|
||||||
|
content: The text is overflowing the container.
|
||||||
Vendored
-3
@@ -1,13 +1,10 @@
|
|||||||
logo_agreement_jz:
|
logo_agreement_jz:
|
||||||
body: I agree.
|
|
||||||
creator: jz
|
creator: jz
|
||||||
created_at: <%= 2.days.ago %>
|
created_at: <%= 2.days.ago %>
|
||||||
|
|
||||||
logo_agreement_kevin:
|
logo_agreement_kevin:
|
||||||
body: Same, let’s do it.
|
|
||||||
creator: kevin
|
creator: kevin
|
||||||
created_at: <%= 2.hours.ago %>
|
created_at: <%= 2.hours.ago %>
|
||||||
|
|
||||||
layout_overflowing_david:
|
layout_overflowing_david:
|
||||||
body: The text is overflowing the container.
|
|
||||||
creator: david
|
creator: david
|
||||||
|
|||||||
Vendored
Vendored
Vendored
+1
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user