Use House for MD comments
This commit is contained in:
@@ -19,6 +19,9 @@ gem "thruster", require: false
|
||||
# Features
|
||||
gem "bcrypt", "~> 3.1.7"
|
||||
gem "rqrcode"
|
||||
gem "redcarpet", "~> 3.6"
|
||||
gem "rouge", "~> 4.5"
|
||||
gem "jbuilder"
|
||||
|
||||
# Telemetry
|
||||
gem "sentry-ruby"
|
||||
|
||||
@@ -126,6 +126,9 @@ GEM
|
||||
irb (1.14.1)
|
||||
rdoc (>= 4.0.0)
|
||||
reline (>= 0.4.2)
|
||||
jbuilder (2.13.0)
|
||||
actionview (>= 5.0.0)
|
||||
activesupport (>= 5.0.0)
|
||||
json (2.7.2)
|
||||
language_server-protocol (3.17.0.3)
|
||||
logger (1.6.1)
|
||||
@@ -223,10 +226,12 @@ GEM
|
||||
rake (13.2.1)
|
||||
rdoc (6.7.0)
|
||||
psych (>= 4.0.0)
|
||||
redcarpet (3.6.0)
|
||||
regexp_parser (2.9.2)
|
||||
reline (0.5.10)
|
||||
io-console (~> 0.5)
|
||||
rexml (3.3.8)
|
||||
rouge (4.5.1)
|
||||
rqrcode (2.2.0)
|
||||
chunky_png (~> 1.0)
|
||||
rqrcode_core (~> 1.0)
|
||||
@@ -335,9 +340,12 @@ DEPENDENCIES
|
||||
debug
|
||||
hotwire_combobox!
|
||||
importmap-rails
|
||||
jbuilder
|
||||
propshaft
|
||||
puma (>= 5.0)
|
||||
rails (>= 8.0.0.rc1)
|
||||
redcarpet (~> 3.6)
|
||||
rouge (~> 4.5)
|
||||
rqrcode
|
||||
rubocop-rails-omakase
|
||||
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 "initializers"
|
||||
import "controllers"
|
||||
import "house"
|
||||
|
||||
@@ -10,4 +10,6 @@ class Account < ApplicationRecord
|
||||
has_many :stages, through: :workflows, class_name: "Workflow::Stage"
|
||||
|
||||
has_many :tags, dependent: :destroy
|
||||
|
||||
has_many_attached :uploads
|
||||
end
|
||||
|
||||
@@ -4,4 +4,6 @@ class Comment < ApplicationRecord
|
||||
belongs_to :creator, class_name: "User", default: -> { Current.user }
|
||||
|
||||
searchable_by :body, using: :comments_search_index
|
||||
|
||||
has_markdown :body
|
||||
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 %>
|
||||
</div>
|
||||
<div class="comment__body txt-align-start">
|
||||
<%= simple_format comment.body %>
|
||||
<%= comment.body.to_html %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
</div>
|
||||
<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.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 %>
|
||||
<%= image_tag "check.svg", aria: { hidden: "true" }, size: 24 %>
|
||||
<span class="for-screen-reader">Save</span>
|
||||
|
||||
@@ -4,6 +4,7 @@ pin "application"
|
||||
pin "@hotwired/turbo-rails", to: "turbo.min.js"
|
||||
pin "@hotwired/stimulus", to: "stimulus.min.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/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
|
||||
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
|
||||
post "join/:join_code", to: "users#create"
|
||||
get "up", to: "rails/health#show", as: :rails_health_check
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ test:
|
||||
|
||||
local:
|
||||
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)
|
||||
# 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.
|
||||
|
||||
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|
|
||||
t.integer "bucket_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
|
||||
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|
|
||||
t.string "name", null: false
|
||||
t.string "record_type", null: false
|
||||
t.bigint "record_id", null: false
|
||||
t.bigint "blob_id", 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 ["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
|
||||
|
||||
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
|
||||
|
||||
create_table "comments", force: :cascade do |t|
|
||||
t.text "body", null: false
|
||||
t.integer "creator_id", null: false
|
||||
t.datetime "created_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:
|
||||
body: I agree.
|
||||
creator: jz
|
||||
created_at: <%= 2.days.ago %>
|
||||
|
||||
logo_agreement_kevin:
|
||||
body: Same, let’s do it.
|
||||
creator: kevin
|
||||
created_at: <%= 2.hours.ago %>
|
||||
|
||||
layout_overflowing_david:
|
||||
body: The text is overflowing the container.
|
||||
creator: david
|
||||
|
||||
Vendored
Vendored
Vendored
+1
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user