Skip to content

Commit

Permalink
feature: add Tiptap WYSIWYG field (avo-hq#2502)
Browse files Browse the repository at this point in the history
  • Loading branch information
olivierbuffon authored Mar 9, 2024
1 parent fa8605a commit 463e9fb
Show file tree
Hide file tree
Showing 20 changed files with 998 additions and 1 deletion.
1 change: 1 addition & 0 deletions app/assets/stylesheets/avo.base.css
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
@import './css/fields/progress.css';
@import './css/fields/trix.css';
@import './css/fields/tags.css';
@import './css/fields/tiptap.css';

@import 'tailwindcss/components';

Expand Down
121 changes: 121 additions & 0 deletions app/assets/stylesheets/css/fields/tiptap.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/* EDIT STYLES */

.tiptap {
@apply m-0 p-4 outline-none min-h-44;

>*+* {
@apply mt-4;
}

ul,
ol {
@apply pl-6 list-outside;
}

ul {
@apply list-disc;
}

ol {
@apply list-decimal;
}
}

.tiptap__field {
@apply w-full mb-4 border border-gray-200 rounded;
}

.tiptap__editor {
@apply border-none;
}

.tiptap__toolbar {
@apply flex items-center justify-between px-3 py-2 border-b bg-gray-50;
}

.tiptap__toolbar-area {
@apply flex flex-wrap items-center divide-gray-200 sm:divide-x sm:rtl:divide-x-reverse;
}

.tiptap__button-group {
@apply flex flex-wrap items-center space-x-1 rtl:space-x-reverse;

&:not(:first-child):not(:last-child) {
@apply px-4;
}

&:first-child {
@apply pe-4;
}

&:last-child {
@apply ps-4;
}
}

.tiptap__button {
@apply p-1 text-gray-500 rounded cursor-pointer;

&:hover:enabled {
@apply bg-gray-100;
}

&:active:enabled {
@apply bg-gray-200;
}

&:disabled {
@apply text-gray-200 cursor-default;
}

svg {
@apply block h-6;
}
}

.tiptap__button--selected {
@apply bg-gray-900/10;
}

/* LINK STYLES */

.tiptap__link-area {
@apply opacity-100 transition-all;
}

.tiptap__link-field {
&:placeholder {
@apply italic text-gray-400;
}

&:focus::placeholder {
@apply text-transparent;
}
}

.tiptap__link-field {
@apply text-sm mr-2 py-1 border border-gray-200 rounded;
}

/* SHOW STYLES */

.tiptap__content {
@apply py-2 max-w-4xl;

>*+* {
@apply mt-4;
}

ul,
ol {
@apply pl-6 list-outside;
}

ul {
@apply list-disc;
}

ol {
@apply list-decimal;
}
}
1 change: 1 addition & 0 deletions app/assets/svgs/editor-bold.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions app/assets/svgs/editor-italic.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions app/assets/svgs/editor-link.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions app/assets/svgs/editor-list.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions app/assets/svgs/editor-ordered-list.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions app/assets/svgs/editor-strike.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions app/assets/svgs/editor-underline.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
81 changes: 81 additions & 0 deletions app/components/avo/fields/tiptap_field/edit_component.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<%= field_wrapper **field_wrapper_args do %>
<%= content_tag :div,
class: "tiptap__field",
data: {
controller: "tiptap-field",
tiptap_field_target: "controller"
} do %>

<%= content_tag "tiptap-editor",
class: "tiptap__editor",
data: {
tiptap_field_target: "editor",
**@field.get_html(:data, view: view, element: :input)
},
id: "#{tiptap_id}_editor",
input: tiptap_id,
placeholder: @field.placeholder do %>

<%= content_tag :div, class: "tiptap__toolbar" do %>
<%= content_tag :div, class: "tiptap__toolbar-area" do %>
<%= content_tag :div, class: "tiptap__button-group" do %>
<%= button_tag type: "button", class: "tiptap__button tiptap__button--bold", data: { action: "click->tiptap-field#bold" } do %>
<%= helpers.svg("editor-bold") %>
<% end %>

<%= button_tag type: "button", class: "tiptap__button tiptap__button--italic", data: { action: "click->tiptap-field#italic" } do %>
<%= helpers.svg("editor-italic") %>
<% end %>

<%= button_tag type: "button", class: "tiptap__button tiptap__button--underline", data: { action: "click->tiptap-field#underline" } do %>
<%= helpers.svg("editor-underline") %>
<% end %>

<%= button_tag type: "button", class: "tiptap__button tiptap__button--strike", data: { action: "click->tiptap-field#strike" } do %>
<%= helpers.svg("editor-strike") %>
<% end %>
<% end %>

<%= content_tag :div, class: "tiptap__button-group" do %>
<%= button_tag type: "button", class: "tiptap__button tiptap__button--ul", data: { action: "click->tiptap-field#unorderedList" } do %>
<%= helpers.svg("editor-list") %>
<% end %>

<%= button_tag type: "button", class: "tiptap__button tiptap__button--ol", data: { action: "click->tiptap-field#orderedList" } do %>
<%= helpers.svg("editor-ordered-list") %>
<% end %>
<% end %>

<%= content_tag :div, class: "tiptap__button-group" do %>
<%= button_tag type: "button", disabled: true, class: "tiptap__button tiptap__button--link", data: { action: "click->tiptap-field#toggleLinkArea" } do %>
<%= helpers.svg("editor-link") %>
<% end %>
<% end %>

<%= content_tag :div, class: "tiptap__button-group tiptap__link-area hidden" do %>
<%= text_field_tag nil, nil, class: "tiptap__link-field", placeholder: "Enter URL here ...", data: { action: "keydown->tiptap-field#preventEnter" } %>
<%= button_tag type: "button", class: "tiptap__button", data: { action: "click->tiptap-field#setLink" } do %>
<%= helpers.svg("check-circle") %>
<% end %>
<%= button_tag type: "button", class: "tiptap__button tiptap__link-button--unset", data: { action: "click->tiptap-field#unsetLink" } do %>
<%= helpers.svg("trash") %>
<% end %>
<% end %>
<% end %>
<% end %>
<% end %>

<%= @form.text_area @field.id,
value: @field.value,
class: classes("w-full hidden"),
data: {
tiptap_field_target: "input",
**@field.get_html(:data, view: view, element: :input)
},
disabled: disabled?,
id: tiptap_id,
placeholder: @field.placeholder,
style: @field.get_html(:style, view: view, element: :input)
%>
<% end %>
<% end %>
29 changes: 29 additions & 0 deletions app/components/avo/fields/tiptap_field/edit_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# frozen_string_literal: true

class Avo::Fields::TiptapField::EditComponent < Avo::Fields::EditComponent
attr_reader :resource

def initialize(**args)
@resource = args[:resource]
@resource_id = args[:resource_id]
@resource_name = args[:resource_name]

super(**args)
end

def resource_name
@resource_name || resource&.singular_route_key
end

def resource_id
@resource_id || resource&.record&.id
end

def tiptap_id
if resource_name.present?
"tiptap_#{resource_name}_#{@field.id}"
elsif form.present?
"tiptap_#{form.index}_#{@field.id}"
end
end
end
14 changes: 14 additions & 0 deletions app/components/avo/fields/tiptap_field/show_component.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<%= field_wrapper **field_wrapper_args do %>
<%
content_classes = 'tiptap__content py-2 max-w-4xl'
content_classes << ' hidden' unless @field.always_show
%>
<div data-controller="hidden-input">
<% unless @field.always_show %>
<%= link_to t('avo.show_content'), 'javascript:void(0);', class: 'font-bold inline-block', data: { action: 'click->hidden-input#showContent' } %>
<% end %>
<div class="<%= content_classes %> " data-hidden-input-target="content">
<%= sanitize @field.value.to_s %>
</div>
</div>
<% end %>
4 changes: 4 additions & 0 deletions app/components/avo/fields/tiptap_field/show_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# frozen_string_literal: true

class Avo::Fields::TiptapField::ShowComponent < Avo::Fields::ShowComponent
end
2 changes: 2 additions & 0 deletions app/javascript/js/controllers.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import TextFilterController from './controllers/text_filter_controller'
import TippyController from './controllers/tippy_controller'
import ToggleController from './controllers/toggle_controller'
import TrixFieldController from './controllers/fields/trix_field_controller'
import TiptapFieldController from './controllers/fields/tiptap_field_controller'

application.register('action', ActionController)
application.register('actions-overflow', ActionsOverflowController)
Expand Down Expand Up @@ -85,5 +86,6 @@ application.register('progress-bar-field', ProgressBarFieldController)
application.register('reload-belongs-to-field', ReloadBelongsToFieldController)
application.register('tags-field', TagsFieldController)
application.register('trix-field', TrixFieldController)
application.register('tiptap-field', TiptapFieldController)

// Custom controllers
Loading

0 comments on commit 463e9fb

Please sign in to comment.