diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 5008ddfcf..000000000 Binary files a/.DS_Store and /dev/null differ diff --git a/Dockerfile b/Dockerfile index 64436b2bd..e0712a39a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -47,7 +47,7 @@ FROM base # Install packages needed for deployment RUN apt-get update -qq && \ - apt-get install --no-install-recommends -y curl libsqlite3-0 libvips build-essential ffmpeg groff libreoffice-writer libreoffice-impress libreoffice-calc mupdf-tools && \ + apt-get install --no-install-recommends -y curl libsqlite3-0 libvips && \ rm -rf /var/lib/apt/lists /var/cache/apt/archives # Copy built artifacts: gems, application diff --git a/Gemfile b/Gemfile index 8c1219952..1d8d39cd7 100644 --- a/Gemfile +++ b/Gemfile @@ -27,8 +27,6 @@ gem "rqrcode" gem "redcarpet" gem "rouge" gem "jbuilder" -gem "actiontext-lexical", bc: "actiontext-lexical" -gem "image_processing", "~> 1.14" gem "platform_agent" # Telemetry and logging diff --git a/Gemfile.lock b/Gemfile.lock index 0ed3a138e..5b9940aad 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,10 +1,3 @@ -GIT - remote: https://github.com/basecamp/actiontext-lexical - revision: 777b2a4a933db9cb4cd515c4569dfcaf8cae45b5 - specs: - actiontext-lexical (0.1.0) - rails (>= 8.0.2) - GIT remote: https://github.com/basecamp/active_record-tenanted revision: 523947a0f0f49f66a8a62eb93e9ba1a345b3076b @@ -195,10 +188,6 @@ GEM net-http (>= 0.5.0) faraday-retry (2.3.1) faraday (~> 2.0) - 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) @@ -210,9 +199,6 @@ 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) @@ -250,9 +236,6 @@ 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.9) minitest (5.25.5) @@ -371,9 +354,6 @@ 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.32.0) @@ -468,7 +448,6 @@ PLATFORMS x86_64-mingw32 DEPENDENCIES - actiontext-lexical! active_record-tenanted! bcrypt (~> 3.1.7) bootsnap @@ -477,7 +456,6 @@ DEPENDENCIES capybara debug geared_pagination (~> 1.2) - image_processing (~> 1.14) importmap-rails jbuilder kamal diff --git a/app/assets/stylesheets/_global.css b/app/assets/stylesheets/_global.css index 5b2a966af..aad4508fc 100644 --- a/app/assets/stylesheets/_global.css +++ b/app/assets/stylesheets/_global.css @@ -177,17 +177,6 @@ --color-card-7: oklch(var(--lch-purple-medium)); --color-card-8: oklch(var(--lch-pink-medium)); - /* Colors: Syntax highlighting */ - --color-code-token__att: oklch(var(--lch-blue-dark)); - --color-code-token__comment: oklch(var(--lch-ink-medium)); - --color-code-token__function: oklch(var(--lch-purple-dark)); - --color-code-token__operator: oklch(var(--lch-red-dark)); - --color-code-token__property: oklch(var(--lch-purple-dark)); - --color-code-token__punctuation: oklch(var(--lch-ink-dark)); - --color-code-token__selector: oklch(var(--lch-green-dark)); - --color-code-token__variable: oklch(var(--lch-red-dark)); - - @media (prefers-color-scheme: dark) { --lch-canvas: 20% 0.0195 232.58; --lch-ink-inverted: var(--lch-black); diff --git a/app/assets/stylesheets/actiontext-lexical.css b/app/assets/stylesheets/actiontext-lexical.css deleted file mode 100644 index c0ab455a5..000000000 --- a/app/assets/stylesheets/actiontext-lexical.css +++ /dev/null @@ -1,113 +0,0 @@ -lexical-editor { - .node--selected { - &:not(:has(img)) { - box-shadow: 0 0 0 var(--hover-size) var(--hover-color); - } - - &:has(img) { - img { - box-shadow: 0 0 0 var(--hover-size) var(--hover-color); - } - } - } -} - -.lexical-dialog-actions { - display: flex; - font-size: var(--text-x-small); - flex: 1 1 0%; - gap: var(--inline-space-half); - margin-block-start: var(--block-space-half); - - .btn { - --radius: 0.3em; - - inline-size: 100%; - justify-content: center; - } - - span { - inline-size: 100%; - } -} - -.lexical-editor__content { - --outline-size: max(2px, 0.08em); - - margin: var(--block-space-half) 0; - min-block-size: calc(7lh + var(--block-space)); - outline: 0; - padding: var(--block-space-half) var(--inline-space); - - * { - &:first-child { - margin-block-start: 0; - } - - &:last-child { - margin-block-end: 0; - } - } -} - -.lexical-editor--drag-over { - background-color: var(--color-selected); - border-radius: 4px; - outline: 2px dashed var(--color-selected-dark); -} - -lexical-toolbar { - background-color: inherit; - border-block-end: 1px solid var(--color-ink-light); - color: currentColor; - display: flex; - font-size: inherit; - margin: 0; - max-inline-size: 100%; - padding: 0.2em 0; - position: relative; - - dialog { - background-color: var(--color-canvas); - border: 1px solid var(--color-ink-lighter); - border-radius: 0.5em; - box-shadow: var(--shadow); - color: var(--color-ink); - padding: var(--block-space) calc(var(--inline-space) * 1.5); - position: absolute; - } - - > button { - aspect-ratio: 4/3.5; - appearance: none; - background-color: transparent; - block-size: 2em; - border: none; - border-radius: 0.2em; - 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.85em; - fill: currentColor; - grid-area: 1/1; - inline-size: auto; - user-select: none; - } - - &:is(:focus, :hover) { - background-color: var(--color-ink-lighter); - box-shadow: none; - } - - &:is(:active) { - background-color: var(--color-selected); - } - } -} diff --git a/app/assets/stylesheets/cards.css b/app/assets/stylesheets/cards.css index d13b60498..4e3c237f7 100644 --- a/app/assets/stylesheets/cards.css +++ b/app/assets/stylesheets/cards.css @@ -150,13 +150,6 @@ } } - .card-field__description { - lexical-toolbar { - border-block-start: 1px solid var(--color-ink-light); - margin-block-start: var(--block-margin) !important; - } - } - .card__content { flex: 2 1 auto; max-inline-size: 100%; diff --git a/app/assets/stylesheets/comments.css b/app/assets/stylesheets/comments.css index 05d3518bc..2a5e47960 100644 --- a/app/assets/stylesheets/comments.css +++ b/app/assets/stylesheets/comments.css @@ -1,7 +1,7 @@ @layer components { .comments { --avatar-size: 2.33em; - --comment-padding-block: var(--block-space-half); + --comment-padding-block: var(--block-space); --comment-padding-inline: var(--inline-space-double); --comment-max: 70ch; --reaction-size: 2rem; @@ -47,7 +47,7 @@ } .comment__avatar { - margin: calc(var(--comment-padding-block) * 0.75) calc(var(--comment-padding-inline) * -0.75); + margin: calc(var(--comment-padding-block) * 0.5) calc(var(--comment-padding-inline) * -0.75); z-index: 0; .comment--mine_ & { @@ -64,7 +64,7 @@ text-align: center; } - * { + p { &:first-child { margin-block-start: 0; } @@ -83,8 +83,6 @@ border-radius: 0.2em; padding: var(--comment-padding-block) - var(--comment-padding-inline) - calc(var(--comment-padding-block) * 1.5) var(--comment-padding-inline); word-wrap: break-word; } @@ -115,7 +113,7 @@ line-height: 1.2; max-inline-size: var(--comment-max); text-align: center; - + &::before { /* Make up space for lack of avatar */ @@ -135,4 +133,17 @@ text-align: center !important; } } + + .comment__input { + --input-border-size: 0; + --input-padding: 0; + + min-block-size: calc(9lh + (2 * var(--comment-padding-block))); + min-inline-size: 100%; + + @supports (field-sizing: content) { + field-sizing: content; + min-block-size: calc(9lh + (2 * var(--comment-padding-block))); + } + } } diff --git a/app/assets/stylesheets/markdown-content.css b/app/assets/stylesheets/markdown-content.css new file mode 100644 index 000000000..81589501b --- /dev/null +++ b/app/assets/stylesheets/markdown-content.css @@ -0,0 +1,110 @@ +@layer components { + .markdown-content { + :where(h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6) { + font-weight: 800; + hyphens: auto; + letter-spacing: -0.02ch; + line-height: 1.1; + margin-block: 0; + overflow-wrap: break-word; + text-wrap: balance; + } + + :where(h1, h2, h3, h4, h5, h6) { display: block; } + :where(h1, h2, h3, h4, h5, h6) { margin-block: 0.65em; } + + :is(h1, .h1) { font-size: 2em; } + :is(h2, .h2) { font-size: 1.5em; } + :is(h3, .h3) { font-size: 1.17em; } + :is(h4, .h4) { font-size: 1em; } + :is(h5, .h5) { font-size: 0.83em; } + :is(h6, .h6) { font-size: 0.67em; } + + :where(p, ul, ol, dl, blockquote, pre, figure, table, hr) { + margin-block: 0.65lh; + overflow-wrap: break-word; + text-wrap: pretty; + } + + :where(hr) { + border-color: var(--color-ink-medium); + border-style: var(--border-style, solid) none none; + margin: 2lh auto; + } + + :where(strong) { + font-weight: 700; + } + + :is(pre, code, .pre, .code) { + background-color: var(--color-ink-lightest); + border: 1px solid var(--color-ink-lighter); + border-radius: 0.3em; + font-family: var(--font-mono); + font-size: 0.85em; + } + + :is(pre) { + overflow-x: auto; + /* text-wrap: nowrap; */ + } + + :where(code) { + padding: 0.1em 0.3em; + } + + :is(pre, .pre) { + border-radius: 0.5em; + padding: 0.5lh 2ch; + + :is(code, .code) { + background-color: transparent; + border: 0; + font-size: 1em; + padding: 0; + } + } + + :where(p) { + hyphens: auto; + letter-spacing: -0.005ch; + } + + :is(blockquote, .quote) { + font-style: italic; + margin: 0 3ch; + + :is(p) { + hyphens: none; + } + } + + :where(table, th, td) { + border: 1px solid var(--color-ink-medium); + } + + :where(table) { + border-collapse: collapse; + margin: 1lh 0; + } + + :where(th, td) { + padding: 0.2lh 1ch; + text-align: start; + } + + :where(th) { + border-block-end-width: 3px; + font-weight: 700; + } + + :where(img, video, embed, object) { + max-block-size: 32rem; + + /* Links should hug media contained within */ + a:has(&) { + display: inline-block; + } + } + } +} diff --git a/app/assets/stylesheets/rich-text-content.css b/app/assets/stylesheets/rich-text-content.css deleted file mode 100644 index 52254f84d..000000000 --- a/app/assets/stylesheets/rich-text-content.css +++ /dev/null @@ -1,331 +0,0 @@ -@layer components { - .rich-text-content { - --block-margin: 0.65lh; - - :where(h1, h2, h3, h4, h5, h6) { - display: block; - font-weight: 800; - hyphens: auto; - letter-spacing: -0.02ch; - line-height: 1.1; - margin-block: var(--block-margin); - overflow-wrap: break-word; - text-wrap: balance; - } - - :where(h1) { font-size: 2em; } - :where(h2) { font-size: 1.5em; } - :where(h3) { font-size: 1.17em; } - :where(h4) { font-size: 1em; } - :where(h5) { font-size: 0.83em; } - :where(h6) { font-size: 0.67em; } - - :where(p, ul, ol, dl, blockquote, figure, .attachment) { - margin-block: var(--block-margin); - overflow-wrap: break-word; - text-wrap: pretty; - } - - :where(b, strong) { - font-weight: 700; - } - - :where(p, blockquote) { - hyphens: auto; - letter-spacing: -0.005ch; - } - - :where(blockquote) { - border-inline-start: 0.25em solid var(--color-ink-lighter); - font-style: italic; - margin: var(--block-margin) 0; - padding: 0.5lh 2ch; - } - - :where(img, video, embed, object) { - margin-inline: auto; - max-block-size: 32rem; - - /* Links should hug media contained within */ - a:has(&) { - display: inline-block; - } - } - - /* Attachments */ - .attachment { - block-size: auto; - display: inline-block; - inline-size: 100%; - position: relative; - margin-inline: 0; - max-inline-size: 100%; - - &:is(figure) img { - block-size: auto; - display: block; - margin-inline: auto; - max-inline-size: 100%; - user-select: none; - } - - :where(progress) { - inline-size: 100%; - margin: auto; - } - } - - .attachment__caption { - color: var(--color-ink-dark); - display: flex; - font-size: var(--text-small); - flex-wrap: wrap; - line-height: 1.3; - column-gap: 0.5ch; - justify-content: center; - margin: var(--block-space-half) var(--inline-space); - text-align: center; - - .input { - inline-size: 100%; - max-inline-size: 100%; - text-align: center; - - @supports (field-sizing: content) { - field-sizing: content; - inline-size: auto; - min-inline-size: 20ch; - } - } - } - - .attachment__figure { - --hover-size: 0; - } - - .attachment--file { - align-items: center; - display: inline-flex; - - .attachment__caption { - flex-direction: column; - flex-wrap: nowrap; - justify-content: flex-start; - text-align: start; - - strong { - color: var(--color-ink); - } - } - } - - .attachment__icon { - aspect-ratio: 4/5; - background-color: var(--color-canvas); - block-size: calc(2.5lh + 0.5ch); - border: 2px solid var(--color-ink-medium); - border-radius: 0.3em; - color: var(--color-ink-dark); - display: inline-grid; - font-size: var(--text-small); - font-weight: 800; - inline-size: auto; - overflow: clip; - padding-block-start: 0.4lh; - place-content: center; - position: relative; - text-transform: uppercase; - - &::before { - background-color: var(--color-ink-medium); - block-size: 0.5lh; - content: ""; - display: block; - inline-size: 100%; - inset: 0 0 auto; - position: absolute; - } - - .attachment--psd &, - .attachment--key &, - .attachment--sketch &, - .attachment--ai &, - .attachment--eps &, - .attachment--indd &, - .attachment--svg &, - .attachment--ppt &, - .attachment--pptx & { - color: oklch(var(--lch-red-dark)); - background-color: oklch(var(--lch-red-lightest)); - border-color: oklch(var(--lch-red-medium)); - - &::before { - background-color: oklch(var(--lch-red-medium)); - } - } - - .attachment--css &, - .attachment--php &, - .attachment--json &, - .attachment--htm &, - .attachment--html &, - .attachment--rb &, - .attachment--erb &, - .attachment--ts &, - .attachment--js & { - color: oklch(var(--lch-purple-dark)); - background-color: oklch(var(--lch-purple-lightest)); - border-color: oklch(var(--lch-purple-medium)); - - &::before { - background-color: oklch(var(--lch-purple-medium)); - } - } - - .attachment--txt &, - .attachment--pages &, - .attachment--rtf &, - .attachment--md &, - .attachment--doc &, - .attachment--docx & { - color: oklch(var(--lch-blue-dark)); - background-color: oklch(var(--lch-blue-lightest)); - border-color: oklch(var(--lch-blue-medium)); - - &::before { - background-color: oklch(var(--lch-blue-medium)); - } - } - - .attachment--csv &, - .attachment--numbers &, - .attachment--xls &, - .attachment--xlsx & { - color: oklch(var(--lch-green-dark)); - background-color: oklch(var(--lch-green-lightest)); - border-color: oklch(var(--lch-green-medium)); - - &::before { - background-color: oklch(var(--lch-green-medium)); - } - } - } - - .attachment__link { - --hover-size: 0; - - color: var(--color-link); - text-decoration: underline; - } - - /* Attachment junk gets wrapped in a paragraph causing unwanted space */ - p:has(action-text-attachment) { - display: none; - - + * { - margin-block-start: 0; - } - } - - /* Avoid extra space due to empty paragraphs */ - p:empty { - display: none; - } - - /* Code */ - :where(code, pre) { - background-color: var(--color-canvas); - border: 1px solid var(--color-ink-lighter); - border-radius: 0.3em; - color: var(--color-ink); - font-family: var(--font-mono); - font-size: 0.85em; - font-weight: 500; - margin-block: var(--block-margin); - padding: 0.1em 0.3em; - - &[data-language] { - border-radius: 0.5em; - display: block; - hyphens: none; - overflow-x: auto; - padding: 0.5lh 2ch; - tab-size: 2; - text-wrap: nowrap; - white-space: pre; - word-break: break-word; - - /* Keywords and attributes */ - .code-token__attr, - .token.atrule, - .token.attr, - .token.keyword { - color: var(--color-code-token__att); - } - - /* Constants, booleans, numbers, properties, tags */ - .code-token__property, - .token.boolean, - .token.constant, - .token.deleted, - .token.number, - .token.property, - .token.symbol, - .token.tag { - color: var(--color-code-token__property); - } - - /* Strings, selectors, and built-in constructs */ - .code-token__selector, - .token.builtin, - .token.char, - .token.inserted, - .token.selector, - .token.string { - color: var(--color-code-token__selector); - } - - /* Comments and meta information */ - .code-token__comment, - .token.cdata, - .token.comment, - .token.doctype, - .token.prolog { - color: var(--color-code-token__comment); - font-style: italic; - } - - /* Operators and symbolic entities */ - .code-token__operator, - .token.entity, - .token.operator, - .token.url { - color: var(--color-code-token__operator); - } - - /* Functions and class names */ - .code-token__function, - .token.class, - .token.class-name, - .token.function { - color: var(--color-code-token__function); - } - - /* Variables, regex, namespaces, important */ - .code-token__variable, - .token.important, - .token.namespace, - .token.regex, - .token.variable { - color: var(--color-code-token__variable); - } - - /* Punctuation */ - .code-token__punctuation, - .token.punctuation { - color: var(--color-code-token__punctuation); - } - } - } - } -} diff --git a/app/helpers/excerpt_helper.rb b/app/helpers/excerpt_helper.rb deleted file mode 100644 index 9ae0d3274..000000000 --- a/app/helpers/excerpt_helper.rb +++ /dev/null @@ -1,12 +0,0 @@ -module ExcerptHelper - def format_excerpt(content, length: 200) - return "" if content.blank? - - text = content.to_plain_text - text = text.gsub(/^>\s*(.*)$/m, '> \1') - text = text.gsub(/^[-*]\s*(.*)$/m, '• \1') - text = text.gsub(/^\d+\.\s*(.*)$/m) { |m| m } - text = text.gsub(/\s+/, " ").strip - text.truncate(length) - end -end diff --git a/app/helpers/notifications_helper.rb b/app/helpers/notifications_helper.rb index 8dab7c110..57fd63eff 100644 --- a/app/helpers/notifications_helper.rb +++ b/app/helpers/notifications_helper.rb @@ -58,7 +58,7 @@ module NotificationsHelper def comment_notification_body(event) comment = event.eventable - "#{strip_tags(comment.body.to_s).blank? ? "#{event.creator.name} replied" : "#{event.creator.name}:" } #{strip_tags(comment.body.to_s).truncate(200)}" + "#{strip_tags(comment.body_html).blank? ? "#{event.creator.name} replied" : "#{event.creator.name}:" } #{strip_tags(comment.body_html).truncate(200)}" end def card_notification_title(card) diff --git a/app/javascript/application.js b/app/javascript/application.js index 36d861cc7..c2917aa3e 100644 --- a/app/javascript/application.js +++ b/app/javascript/application.js @@ -2,6 +2,4 @@ import "@hotwired/turbo-rails" import "initializers" import "controllers" - -import "actiontext-lexical" -import "@rails/actiontext" +import "house" diff --git a/app/javascript/controllers/local_save_controller.js b/app/javascript/controllers/local_save_controller.js index 5f9fc83a3..5d9b89a72 100644 --- a/app/javascript/controllers/local_save_controller.js +++ b/app/javascript/controllers/local_save_controller.js @@ -30,10 +30,9 @@ export default class extends Controller { async restoreContent() { await nextFrame() - let savedContent = localStorage.getItem(this.keyValue) + const savedContent = localStorage.getItem(this.keyValue) if (savedContent) { - savedContent = `
<%= link_to card.title, edit_collection_card_path(card.collection, card), class: "card__title-link" %>
-<%= form.text_area :title, placeholder: "Name it…", class: "input input--textarea full-width borderless txt-align-start", autofocus: card.title.blank?, data: { action: "auto-save#change blur->auto-save#submit keydown.enter->auto-save#submit:prevent keydown.ctrl+enter->auto-save#submit:prevent" } %>
-<%= form.label :title, class: "flex flex-column align-center" do %> <%= form.text_area :title, class: "input input--textarea full-width borderless txt-align-start card-field__title", @@ -8,10 +8,10 @@ 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 %>
- - <%= form.rich_textarea :description, class: "card-field__description rich-text-content", - 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" } %> +~~$1~~")}(t),t=function(t){return t.replaceAll(/==(.*?)==/gm,"==$1==")}(t),t}(e),e=function(t){return t=function(t){return t=t.replaceAll(/^# (.*)$/gm,'# $1'),t=t.replaceAll(/^## (.*)$/gm,'## $1'),t=t.replaceAll(/^### (.*)$/gm,'### $1'),t=t.replaceAll(/^#### (.*)$/gm,'#### $1'),t=t.replaceAll(/^##### (.*)$/gm,'##### $1'),t=t.replaceAll(/^###### (.*)$/gm,'###### $1'),t}(t),t=function(t){return t.replaceAll(/^(\d+\.\s.*)$/gm,'$1')}(t),t=function(t){return t.replaceAll(/^(-\s.*)$/gm,'$1')}(t),t=function(t){return t.replaceAll(/^>(.*)$/gm,'>$1')}(t),t}(e),e=function(t){return function(t){return t.replaceAll(/\n---\n/gm,'\n---\n')}(t)}(e),e=function(t){return t.replaceAll(/(?[$1]($2)')}(e),e=function(t){return t.replaceAll(/!\[(.*?)\]\((.*?)\)/gm,'$&')}(e),e=function(t){return t=t.replaceAll(/^```(.*?)```$/gms,'```$1```'),t=t.replaceAll(/([^`])`([^`]+)`([^`])/gm,'$1`$2`$3'),t}(e),e=function(t){return t.replaceAll(/<!--(.*?)-->/gm,'<!--$1-->')}(e),e}class a{constructor(t,e){this.content=t,this.element=e,this.selection=new r(this),this.history=new o}select(t,e){this.selection.select({start:t,end:e})}insertText(t){this.replaceText(t,this.currentSelection.start,this.currentSelection.end)}replaceText(t,e,n){const s=this.content.slice(0,e),i=`${s}${t}${this.content.slice(n)}`,r={start:s.length+t.length,end:s.length+t.length};this.#h(),this.#d(i,r)}insertParagraph(t,e){this.select(t,e),this.selection.isBulletList?this.#u():this.selection.isNumberList?this.#m():this.replaceText("\n",t,e)}insertLink(t,e,n=""){const{start:s,end:i}=this.currentSelection,r=` ${n}[${t}](${e}) `;this.replaceText(r,s,i),this.selection.select({start:s+r.length,end:s+r.length})}insertFile(t,e,n){n.startsWith("image/")?this.insertImage(t,e):this.insertLink(t,e)}insertImage(t,e){this.insertLink(t,e,"!")}deleteText(t,e){this.replaceText("",Math.max(t,0),e)}deleteCurrentLine(){const{start:t,end:e}=this.currentLine;this.deleteText(t,e)}toggleBold(){this.#p(this.selection.isBold,"**")}toggleItalic(){this.#p(this.selection.isItalic,"_")}toggleStrikethrough(){this.#p(this.selection.isStrikethrough,"~~")}toggleCode(){const[t,e]=this.selection.isMultiline?["```\n","\n```"]:["`","`"];this.#p(this.selection.isCode,t,e)}toggleLink(){this.selection.isLink?this.#g():this.#v()}toggleQuote(){this.selection.isQuote?this.#f("> "):this.#b("> ")}toggleBulletList(){this.selection.isBulletList?this.#f("- "):this.#b("- ")}toggleNumberList(){this.selection.isNumberList?this.#f(/\d+\. /):this.#b(`${this.#L+1}. `)}undo(){const{content:e,start:n,end:s}=this.history.undo(this.#x);t(e)&&this.#d(e,{start:n,end:s})}redo(){const{content:e,start:n,end:s}=this.history.redo(this.#x);t(e)&&this.#d(e,{start:n,end:s})}render(){this.contentElement.innerHTML=l(this.content)+"