This commit is contained in:
2025-11-07 13:34:32 -08:00
commit 1e8c5a972b
436 changed files with 11000 additions and 0 deletions

View File

@@ -0,0 +1,15 @@
module ApplicationHelper
def hide_from_user_style_tag
tag.style(<<~CSS.html_safe)
[data-hide-from-user-id="#{Current.user.id}"] {
display: none!important;
}
CSS
end
def custom_styles_tag
if custom_styles = Current.account&.custom_styles
tag.style(custom_styles.to_s.html_safe, data: { turbo_track: "reload" })
end
end
end

View File

@@ -0,0 +1,38 @@
module ArrangementHelper
def arrangement_tag(book, **, &)
tag.div data: {
controller: "arrangement reading-progress",
arrangement_cursor_class: "arrangement-cursor",
arrangement_selected_class: "arrangement-selected",
arrangement_placeholder_class: "arrangement-placeholder",
arrangement_adding_mode_class: "arrangement--adding",
arrangement_move_mode_class: "arrangement-move-mode",
arrangement_url_value: book_leaves_moves_url(book),
reading_progress_book_id_value: book.id,
reading_progress_last_read_class: "toc__leaf--last-read"
}, **, &
end
def arrangement_actions
actions = {
"click": "click",
"dragstart": "dragStart",
"dragover": "dragOver:prevent",
"dragend": "dragEnd",
"drop": "drop",
"keydown.up": "moveBefore",
"keydown.right": "moveAfter",
"keydown.down": "moveAfter",
"keydown.left": "moveBefore",
"keydown.shift+up": "moveBefore",
"keydown.shift+right": "moveAfter",
"keydown.shift+down": "moveAfter",
"keydown.shift+left": "moveBefore",
"keydown.space": "toggleMoveMode",
"keydown.enter": "applyMoveMode",
"keydown.esc": "cancelMoveMode"
}
actions.map { |action, target| "#{action}->arrangement##{target}" }.join(" ")
end
end

View File

@@ -0,0 +1,6 @@
module Books::EditingHelper
def editing_mode_toggle_switch(leaf, checked:)
target_url = checked ? leafable_slug_path(leaf) : edit_leafable_path(leaf)
render "books/edit_mode", target_url: target_url, checked: checked
end
end

View File

@@ -0,0 +1,67 @@
module BooksHelper
def book_toc_tag(book, &)
tag.ol class: "toc", tabindex: 0,
data: {
controller: "arrangement",
action: arrangement_actions,
arrangement_cursor_class: "arrangement-cursor",
arrangement_selected_class: "arrangement-selected",
arrangement_placeholder_class: "arrangement-placeholder",
arrangement_move_mode_class: "arrangement-move-mode",
arrangement_url_value: book_leaves_moves_url(book)
}, &
end
def book_part_create_button(book, kind, **, &)
url = url_for [ book, kind.new ]
button_to url, class: "btn btn--plain txt-medium fill-transparent disable-when-arranging disable-when-deleting", draggable: true,
data: {
action: "dragstart->arrangement#dragStartCreate dragend->arrangement#dragEndCreate",
arrangement_url_param: url
}, **, &
end
def link_to_first_leafable(leaves)
if first_leaf = leaves.first
link_to leafable_slug_path(first_leaf), data: hotkey_data_attributes("right"), class: "disable-when-arranging", hidden: true do
tag.span(class: "btn") do
image_tag("arrow-right.svg", aria: { hidden: true }, size: 24) + tag.span("Start reading", class: "for-screen-reader")
end + tag.span(first_leaf.title, class: "overflow-ellipsis")
end
end
end
def link_to_previous_leafable(leaf, hotkey: true, for_edit: false)
if previous_leaf = leaf.previous
path = for_edit ? edit_leafable_path(previous_leaf) : leafable_slug_path(previous_leaf)
link_to path, data: hotkey_data_attributes("left", enabled: hotkey), class: "btn" do
image_tag("arrow-left.svg", aria: { hidden: true }, size: 24) + tag.span("Previous: #{ previous_leaf.title }", class: "for-screen-reader")
end
else
link_to book_slug_path(leaf.book), data: hotkey_data_attributes("left", enabled: hotkey), class: "btn" do
image_tag("arrow-left.svg", aria: { hidden: true }, size: 24) + tag.span("Table of contents: #{ leaf.book.title }", class: "for-screen-reader")
end
end
end
def link_to_next_leafable(leaf, hotkey: true, for_edit: false)
if next_leaf = leaf.next
path = for_edit ? edit_leafable_path(next_leaf) : leafable_slug_path(next_leaf)
link_to path, data: hotkey_data_attributes("right", enabled: hotkey), class: "btn txt-medium min-width" do
tag.span("Next: #{next_leaf.title }", class: "overflow-ellipsis") + image_tag("arrow-right.svg", aria: { hidden: true }, size: 24)
end
else
link_to book_slug_path(leaf.book), data: hotkey_data_attributes("right", enabled: hotkey), class: "btn txt-medium" do
tag.span("Table of contents: #{leaf.book.title }", class: "overflow-ellipsis") + image_tag("arrow-reverse.svg", aria: { hidden: true }, size: 24)
end
end
end
private
def hotkey_data_attributes(key, enabled: true)
if enabled
{ controller: "hotkey", action: "keydown.#{key}@document->hotkey#click touch:swipe-#{key}@window->hotkey#click" }
end
end
end

View File

@@ -0,0 +1,8 @@
module FormsHelper
def auto_submit_form_with(**attributes, &)
data = attributes.delete(:data) || {}
data[:controller] = "auto-submit #{data[:controller]}".strip
form_with **attributes, data: data, &
end
end

View File

@@ -0,0 +1,22 @@
module InvitationsHelper
def button_to_copy_to_clipboard(url, &)
tag.button class: "btn", data: {
controller: "copy-to-clipboard", action: "copy-to-clipboard#copy",
copy_to_clipboard_success_class: "btn--success", copy_to_clipboard_content_value: url
}, &
end
def web_share_button(url, title, text, &)
tag.button class: "btn", hidden: true, data: {
controller: "web-share", action: "web-share#share",
web_share_url_value: url,
web_share_text_value: text,
web_share_title_value: title
}, &
end
def qr_code_image(url)
qr_code_link = QrCodeLink.new(url)
image_tag qr_code_path(qr_code_link.signed), class: "qr-code center", alt: "QR Code"
end
end

View File

@@ -0,0 +1,29 @@
module LeavesHelper
def leaf_item_tag(leaf, **, &)
tag.li class: "arrangement__item toc__leaf toc__leaf--#{leaf.leafable_name}",
id: dom_id(leaf),
data: {
id: leaf.id,
arrangement_target: "item"
}, **, &
end
def leaf_nav_tag(leaf, **, &)
tag.nav data: {
controller: "reading-tracker",
reading_tracker_book_id_value: leaf.book_id,
reading_tracker_leaf_id_value: leaf.id
}, **, &
end
def leafable_edit_form(leafable, **, &)
form_with model: leafable, url: leafable_path(leafable.leaf), method: :put, format: :html,
data: {
controller: "autosave",
action: "autosave#submit:prevent input@document->autosave#change house-md:change->autosave#change",
autosave_clean_class: "clean",
autosave_dirty_class: "dirty",
autosave_saving_class: "saving"
}, **, &
end
end

View File

@@ -0,0 +1,14 @@
module PagesHelper
def word_count(content)
return if content.blank?
pluralize number_with_delimiter(content.split.size), "word"
end
def page_title(leaf, book)
[ leaf.title, book.title, book.author ].reject(&:blank?).to_sentence(two_words_connector: " · ", words_connector: " · ", last_word_connector: " · ")
end
def sanitize_content(content)
sanitize content, scrubber: HtmlScrubber.new
end
end

View File

@@ -0,0 +1,2 @@
module PicturesHelper
end

View File

@@ -0,0 +1,2 @@
module SectionsHelper
end

View File

@@ -0,0 +1,36 @@
module TranslationsHelper
TRANSLATIONS = {
book_author: { "🇺🇸": "Author", "🇪🇸": "Autor", "🇫🇷": "Auteur", "🇮🇳": "लेखक", "🇩🇪": "Autor", "🇧🇷": "Autor" },
book_subtitle: { "🇺🇸": "Subtitle", "🇪🇸": "Subtítulo", "🇫🇷": "Sous-titre", "🇮🇳": "उपशीर्षक", "🇩🇪": "Untertitel", "🇧🇷": "Subtítulo" },
book_title: { "🇺🇸": "Book title", "🇪🇸": "Título del libro", "🇫🇷": "Titre du livre", "🇮🇳": "पुस्तक का शीर्षक", "🇩🇪": "Buchtitel", "🇧🇷": "Título do livro" },
custom_styles: { "🇺🇸": "Add custom CSS styles. Use Caution: you could break things.", "🇪🇸": "Agrega estilos CSS personalizados. Usa precaución: podrías romper cosas.", "🇫🇷": "Ajoutez des styles CSS personnalisés. Utilisez avec précaution : vous pourriez casser des choses.", "🇮🇳": "कस्टम CSS स्टाइल जोड़ें। सावधानी बरतें: आप चीज़ों को तोड़ सकते हैं।", "🇩🇪": "Fügen Sie benutzerdefinierte CSS-Stile hinzu. Vorsicht: Sie könnten Dinge kaputt machen.", "🇧🇷": "Adicione estilos CSS personalizados. Use com cuidado: você pode quebrar coisas." },
email_address: { "🇺🇸": "Enter your email address", "🇪🇸": "Introduce tu correo electrónico", "🇫🇷": "Entrez votre adresse courriel", "🇮🇳": "अपना ईमेल पता दर्ज करें", "🇩🇪": "Geben Sie Ihre E-Mail-Adresse ein", "🇧🇷": "Insira seu endereço de email" },
password: { "🇺🇸": "Enter your password", "🇪🇸": "Introduce tu contraseña", "🇫🇷": "Saisissez votre mot de passe", "🇮🇳": "अपना पासवर्ड दर्ज करें", "🇩🇪": "Geben Sie Ihr Passwort ein", "🇧🇷": "Insira sua senha" },
picture_caption: { "🇺🇸": "Picture caption", "🇪🇸": "Subtítulo de la imagen", "🇫🇷": "Légende de l'image", "🇮🇳": "चित्र का कैप्शन", "🇩🇪": "Bildunterschrift", "🇧🇷": "Legenda da imagem" },
transfer_session: { "🇺🇸": "Share to get them back into their account", "🇪🇸": "Comparte para que vuelvan a acceder a su cuenta", "🇫🇷": "Partagez pour les reconnecter à leur compte", "🇮🇳": "उन्हें उनके खाते में वापस लाने के लिए साझा करें", "🇩🇪": "Teilen, um ihnen den Zugang zu ihrem Konto zu ermöglichen", "🇧🇷": "Compartilhe para que eles voltem a acessar sua conta" },
transfer_session_self: { "🇺🇸": "Link to automatically log in on another device", "🇪🇸": "Enlace para iniciar sesión automáticamente en otro dispositivo", "🇫🇷": "Lien pour se connecter automatiquement sur un autre appareil", "🇮🇳": "किसी अन्य डिवाइस पर स्वचालित रूप से लॉग इन करने के लिए लिंक", "🇩🇪": "Link, um sich automatisch auf einem anderen Gerät anzumelden", "🇧🇷": "Link para fazer login automaticamente em outro dispositivo" },
user_name: { "🇺🇸": "Enter your name", "🇪🇸": "Introduce tu nombre", "🇫🇷": "Entrez votre nom", "🇮🇳": "अपना नाम दर्ज करें", "🇩🇪": "Geben Sie Ihren Namen ein", "🇧🇷": "Insira seu nome" },
update_password: { "🇺🇸": "Change password", "🇪🇸": "Cambiar contraseña", "🇫🇷": "Changer le mot de passe", "🇮🇳": "पासवर्ड बदलें", "🇩🇪": "Passwort ändern", "🇧🇷": "Alterar senha" }
}
def translations_for(translation_key)
tag.dl(class: "language-list") do
TRANSLATIONS[translation_key].map do |language, translation|
concat tag.dt(language)
concat tag.dd(translation, class: "margin-none")
end
end
end
def translation_button(translation_key)
tag.div(class: "position-relative", data: { controller: "popover", action: "keydown.esc->popover#close click@document->popover#closeOnClickOutside", popover_orientation_top_class: "popover-orientation-top" }) do
tag.button(type: "button", class: "btn", tabindex: -1, data: { action: "popover#toggle" }) do
concat image_tag("globe.svg", size: 20, role: "presentation", class: "color-icon")
concat tag.span("Translate", class: "for-screen-reader")
end +
tag.dialog(class: "lanuage-list-menu popover shadow", data: { popover_target: "menu" }) do
translations_for(translation_key)
end
end
end
end

View File

@@ -0,0 +1,7 @@
module TurboStreamActionsHelper
def scroll_into_view(id, animation: nil)
turbo_stream_action_tag :scroll_into_view, target: id, animation: animation
end
end
Turbo::Streams::TagBuilder.prepend TurboStreamActionsHelper

View File

@@ -0,0 +1,5 @@
module VersionHelper
def version_badge
tag.span(Rails.application.config.app_version, class: "product__version-badge")
end
end