diff --git a/Gemfile.lock b/Gemfile.lock index b9c3489b5d..43d4e718bd 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -237,7 +237,7 @@ GEM logger (~> 1.6) letter_opener (1.10.0) launchy (>= 2.2, < 4) - lexxy (0.1.20.beta) + lexxy (0.1.23.beta) rails (>= 8.0.2) lint_roller (1.1.0) logger (1.7.0) diff --git a/Gemfile.saas.lock b/Gemfile.saas.lock index 5cd3081555..39af96f957 100644 --- a/Gemfile.saas.lock +++ b/Gemfile.saas.lock @@ -313,7 +313,7 @@ GEM logger (~> 1.6) letter_opener (1.10.0) launchy (>= 2.2, < 4) - lexxy (0.1.20.beta) + lexxy (0.1.23.beta) rails (>= 8.0.2) lint_roller (1.1.0) logger (1.7.0) diff --git a/app/assets/stylesheets/_global.css b/app/assets/stylesheets/_global.css index 414d4c6964..13988f0271 100644 --- a/app/assets/stylesheets/_global.css +++ b/app/assets/stylesheets/_global.css @@ -219,6 +219,27 @@ --color-card-7: oklch(var(--lch-purple-medium)); --color-card-8: oklch(var(--lch-pink-medium)); + /* Colors: Highlighter */ + --highlight-1: rgb(136, 118, 38); + --highlight-2: rgb(185, 94, 6); + --highlight-3: rgb(207, 0, 0); + --highlight-4: rgb(216, 28, 170); + --highlight-5: rgb(144, 19, 254); + --highlight-6: rgb(5, 98, 185); + --highlight-7: rgb(17, 138, 15); + --highlight-8: rgb(148, 82, 22); + --highlight-9: rgb(102, 102, 102); + + --highlight-bg-1: rgba(229, 223, 6, 0.3); + --highlight-bg-2: rgba(255, 185, 87, 0.3); + --highlight-bg-3: rgba(255, 118, 118, 0.3); + --highlight-bg-4: rgba(248, 137, 216, 0.3); + --highlight-bg-5: rgba(190, 165, 255, 0.3); + --highlight-bg-6: rgba(124, 192, 252, 0.3); + --highlight-bg-7: rgba(140, 255, 129, 0.3); + --highlight-bg-8: rgba(221, 170, 123, 0.3); + --highlight-bg-9: rgba(200, 200, 200, 0.3); + /* Colors: Syntax highlighting */ --color-code-token__att: oklch(var(--lch-blue-dark)); --color-code-token__comment: oklch(var(--lch-ink-medium)); diff --git a/app/assets/stylesheets/lexxy.css b/app/assets/stylesheets/lexxy.css index e3f8f8cbdc..7b647caca5 100644 --- a/app/assets/stylesheets/lexxy.css +++ b/app/assets/stylesheets/lexxy.css @@ -1,61 +1,39 @@ @layer components { + /* Editor + /* ------------------------------------------------------------------------ */ + lexxy-editor { display: block; position: relative; overflow: visible; figure.node--selected { - &:not(:has(img)) { - outline: var(--focus-ring-size) solid var(--focus-ring-color); - outline-offset: var(--focus-ring-offset); - } - &:has(img) { img { outline: var(--focus-ring-size) solid var(--focus-ring-color); outline-offset: var(--focus-ring-offset); } } - } - /* Lexical still uses the `lexxy-editor--empty` class if you have a list - started with no other characters. Here, we won't show the placeholder if - you've clicked the List button in the toolbar. */ - &.lexxy-editor--empty { - .lexxy-editor__content:not(:has(ul, ol))::before { - content: attr(placeholder); - color: currentColor; - cursor: text; - opacity: 0.66; - pointer-events: none; - position: absolute; - white-space: pre-line; + &:not(:has(img)) { + outline: var(--focus-ring-size) solid var(--focus-ring-color); + outline-offset: var(--focus-ring-offset); } } } - .lexxy-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; - - &:is([type="submit"]) { - --btn-background: var(--card-color, var(--color-link)); - --btn-color: var(--color-ink-inverted); - --focus-ring-color: var(--card-color, var(--color-link)); - } - } - - span { - inline-size: 100%; + /* Lexical uses the `lexxy-editor--empty` class even if you have a list + * started but haven't added other characters. Here, we hide the placeholder + * when you click the List button in the toolbar. */ + .lexxy-editor--empty { + .lexxy-editor__content:not(:has(ul, ol))::before { + content: attr(placeholder); + color: currentColor; + cursor: text; + opacity: 0.66; + pointer-events: none; + position: absolute; + white-space: pre-line; } } @@ -64,14 +42,9 @@ min-block-size: calc(7lh + var(--block-space)); outline: 0; - * { - &:first-child { - margin-block-start: 0; - } - - &:last-child { - margin-block-end: 0; - } + /* Allow color highlights to show through a bit */ + ::selection { + background: oklch(var(--lch-blue-light) / 0.5); } } @@ -81,6 +54,40 @@ outline: 2px dashed var(--color-selected-dark); } + .lexxy-code-language-picker { + -webkit-appearance: none; + appearance: none; + background-color: var(--color-canvas); + background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m12 19.5c-.7 0-1.3-.3-1.7-.8l-9.8-11.1c-.7-.8-.6-1.9.2-2.6.8-.6 1.9-.6 2.5.2l8.6 9.8c0 .1.2.1.4 0l8.6-9.8c.7-.8 1.8-.9 2.6-.2s.9 1.8.2 2.6l-9.8 11.1c-.4.5-1.1.8-1.7.8z' fill='%23000'/%3E%3C/svg%3E"); + background-position: center right 0.9em; + background-repeat: no-repeat; + background-size: 0.5em; + border: 1px solid var(--color-ink-light); + border-radius: 99rem; + color: var(--color-ink); + cursor: pointer; + font-family: var(--font-base); + font-size: var(--text-x-small); + font-weight: 500; + inset-inline-end: 0; + line-height: 1.15lh; + margin: 0.75ch 0.75ch 0 0; + padding-inline: 1.5ch 1.8em; + text-align: start; + + @media (prefers-color-scheme: dark) { + background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m12 19.5c-.7 0-1.3-.3-1.7-.8l-9.8-11.1c-.7-.8-.6-1.9.2-2.6.8-.6 1.9-.6 2.5.2l8.6 9.8c0 .1.2.1.4 0l8.6-9.8c.7-.8 1.8-.9 2.6-.2s.9 1.8.2 2.6l-9.8 11.1c-.4.5-1.1.8-1.7.8z' fill='%23fff'/%3E%3C/svg%3E"); + } + + option { + background-color: var(--color-canvas); + color: var(--color-ink); + } + } + + /* Toolbar + /* ------------------------------------------------------------------------ */ + lexxy-toolbar { --lexxy-toolbar-icon-size: 1em; @@ -96,21 +103,6 @@ position: sticky; inset-block-start: 0; z-index: 1; - - 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; - z-index: 1; - - .input[type="url"] { - min-inline-size: 30ch; - } - } } .lexxy-editor__toolbar-button { @@ -163,42 +155,122 @@ z-index: 1; } - :where(.lexxy-editor__toolbar-spacer) { + .lexxy-editor__toolbar-spacer { flex: 1; } - /* Based on .input, .input--select */ - .lexxy-code-language-picker { - -webkit-appearance: none; - appearance: none; + /* Dropdowns + /* ------------------------------------------------------------------------ */ + + .lexxy-editor__toolbar-dropdown-content { + --lexxy-dropdown-padding: 0.75rem; + --lexxy-dropdown-btn-size: 2rem; + background-color: var(--color-canvas); - background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m12 19.5c-.7 0-1.3-.3-1.7-.8l-9.8-11.1c-.7-.8-.6-1.9.2-2.6.8-.6 1.9-.6 2.5.2l8.6 9.8c0 .1.2.1.4 0l8.6-9.8c.7-.8 1.8-.9 2.6-.2s.9 1.8.2 2.6l-9.8 11.1c-.4.5-1.1.8-1.7.8z' fill='%23000'/%3E%3C/svg%3E"); - background-position: center right 0.9em; - background-repeat: no-repeat; - background-size: 0.5em; border: 1px solid var(--color-ink-lighter); - border-radius: 0.3em; + border-radius: 0.5em; + box-shadow: var(--shadow); color: var(--color-ink); - font-family: var(--font-base); + font-size: var(--text-small); + padding: var(--lexxy-dropdown-padding); + position: absolute; + z-index: 1; + + button { + block-size: var(--lexxy-dropdown-btn-size); + } + } + + .lexxy-editor__toolbar-dropdown-actions { + display: flex; font-size: var(--text-x-small); + flex: 1 1 0%; + gap: var(--lexxy-dropdown-padding); + margin-block-start: var(--lexxy-dropdown-padding); + + .btn { + --radius: 99rem; + + inline-size: 100%; + } + + .btn[type="submit"] { + --btn-background: var(--card-color, var(--color-link)); + --btn-color: var(--color-ink-inverted); + --focus-ring-color: var(--card-color, var(--color-link)); + } + + span { + inline-size: 100%; + } + } + + lexxy-link-dropdown { + .input { + min-inline-size: 30ch; + } + } + + lexxy-highlight-dropdown { + --gap: 0.5ch; + + [data-button-group] { + display: flex; + flex-direction: row; + flex-wrap: wrap; + gap: var(--gap); + + + & { + margin-block-start: var(--gap); + } + } + } + + .lexxy-highlight-button { + --outline-color: oklch(var(--lch-ink-darkest) / 0.2); + + appearance: none; + background: var(--color-canvas); + border: none; + border-radius: 0.5ch; + display: grid; font-weight: 500; - inset-inline-end: 0; - line-height: inherit; - margin: 0.3em 0.3em 0 0; - padding: 0.5em 1.8em 0.5em 1.2em; - text-align: start; + inline-size: var(--lexxy-dropdown-btn-size); + place-items: center; + position: relative; + outline: none; - @media (prefers-color-scheme: dark) { - background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m12 19.5c-.7 0-1.3-.3-1.7-.8l-9.8-11.1c-.7-.8-.6-1.9.2-2.6.8-.6 1.9-.6 2.5.2l8.6 9.8c0 .1.2.1.4 0l8.6-9.8c.7-.8 1.8-.9 2.6-.2s.9 1.8.2 2.6l-9.8 11.1c-.4.5-1.1.8-1.7.8z' fill='%23fff'/%3E%3C/svg%3E"); + &:after { + content: "Aa"; } - option { - background-color: var(--color-canvas); - color: var(--color-ink); + &:hover, + &[aria-pressed="true"] { + box-shadow: 0 0 0 1px var(--color-canvas), 0 0 0 3px var(--outline-color); + } + + &[aria-pressed="true"] { + --outline-color: var(--color-link); + + &:after { + content: "✓"; + } + } + } + + .lexxy-editor__toolbar-dropdown-reset { + background: var(--color-canvas); + border: 1px solid var(--color-ink-light); + border-radius: 99rem; + inline-size: 100%; + margin-block-start: var(--lexxy-dropdown-padding); + + &[disabled] { + display: none; } } - /* Prompt + /* Prompt ,enu (@mentions, etc.) /* ------------------------------------------------------------------------ */ .lexxy-prompt-menu { diff --git a/app/assets/stylesheets/rich-text-content.css b/app/assets/stylesheets/rich-text-content.css index 2d465c935b..fdb8224fc1 100644 --- a/app/assets/stylesheets/rich-text-content.css +++ b/app/assets/stylesheets/rich-text-content.css @@ -28,6 +28,14 @@ } } + :where(ol, ul) { + padding-inline-start: 3ch; + } + + :where(li:has(li)) { + list-style: none; + } + :where(b, strong, .lexxy-content__bold) { font-weight: 700; } @@ -40,6 +48,10 @@ text-decoration: line-through; } + :where(mark, .lexxy-content__highlight) { + background-color: transparent; + color: inherit; + } :where(p, blockquote) { letter-spacing: -0.005ch; @@ -201,14 +213,17 @@ color: color-mix(in oklch, var(--color-ink) 66%, transparent); font-size: var(--text-small); - .input { + textarea { --input-border-radius: 0.3em; --input-border-size: 0; --input-padding: 0; + background-color: var(--input-background, transparent); + border: none; color: inherit; inline-size: 100%; max-inline-size: 100%; + resize: none; text-align: center; &:focus { @@ -274,7 +289,8 @@ display: flex; flex-wrap: wrap; gap: 1ch; - inline-size: auto; + inline-size: 100%; + margin-inline: 0; .attachment__caption { display: grid; diff --git a/test/system/smoke_test.rb b/test/system/smoke_test.rb index e4bc9cae98..539921b939 100644 --- a/test/system/smoke_test.rb +++ b/test/system/smoke_test.rb @@ -24,7 +24,7 @@ class SmokeTest < ApplicationSystemTestCase within("form lexxy-editor figure.attachment[data-content-type='image/jpeg']") do assert_selector "img[src*='/rails/active_storage']" - assert_selector "figcaption input[placeholder='moon.jpg']" + assert_selector "figcaption textarea[placeholder='moon.jpg']" end click_on "Post"