From 28fc89307e373ea540594c0fabbeec32be46716f Mon Sep 17 00:00:00 2001 From: Wes Copeland Date: Mon, 20 Jan 2025 10:10:33 -0500 Subject: [PATCH 1/4] fix(forums): normalize newlines from synthetic events (#3083) --- .../utils/preProcessShortcodesInBody.test.ts | 67 +++++++++++++++++++ .../utils/preProcessShortcodesInBody.ts | 6 +- 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/resources/js/features/forums/utils/preProcessShortcodesInBody.test.ts b/resources/js/features/forums/utils/preProcessShortcodesInBody.test.ts index 06de3421b6..17f526cca7 100644 --- a/resources/js/features/forums/utils/preProcessShortcodesInBody.test.ts +++ b/resources/js/features/forums/utils/preProcessShortcodesInBody.test.ts @@ -83,4 +83,71 @@ describe('Util: preProcessShortcodesInBody', () => { // ASSERT expect(result).toEqual(input); }); + + it('normalizes escaped newlines to actual newlines', () => { + // ARRANGE + const input = 'first line↵\nsecond line'; + + // ACT + const result = preProcessShortcodesInBody(input); + + // ASSERT + expect(result).toEqual('first line\nsecond line'); + }); + + it('handles mixed escaped and actual newlines', () => { + // ARRANGE + const input = 'first line↵\nsecond line\nthird line↵\nfourth line'; + + // ACT + const result = preProcessShortcodesInBody(input); + + // ASSERT + expect(result).toEqual('first line\nsecond line\nthird line\nfourth line'); + }); + + it('normalizes line endings when content includes shortcodes', () => { + // ARRANGE + const input = + 'Check out https://retroachievements.org/user/ScottAdams↵\nAnd also↵\nhttps://retroachievements.org/game/1234'; + + // ACT + const result = preProcessShortcodesInBody(input); + + // ASSERT + expect(result).toEqual('Check out [user=ScottAdams]\nAnd also\n[game=1234]'); + }); + + it('handles carriage returns and different line ending styles', () => { + // ARRANGE + const input = 'line1\r\nline2\rline3\nline4'; + + // ACT + const result = preProcessShortcodesInBody(input); + + // ASSERT + expect(result).toEqual('line1\nline2\nline3\nline4'); + }); + + it('preserves whitespace while normalizing line endings', () => { + // ARRANGE + const input = ' indented↵\n still indented ↵\n\n more space '; + + // ACT + const result = preProcessShortcodesInBody(input); + + // ASSERT + expect(result).toEqual(' indented\n still indented \n\n more space '); + }); + + it('preserves whitespace while normalizing encoded line endings', () => { + // ARRANGE + const input = ' indented\u21B5\n still indented \u21B5\n\n more space '; + + // ACT + const result = preProcessShortcodesInBody(input); + + // ASSERT + expect(result).toEqual(' indented\n still indented \n\n more space '); + }); }); diff --git a/resources/js/features/forums/utils/preProcessShortcodesInBody.ts b/resources/js/features/forums/utils/preProcessShortcodesInBody.ts index e0524cc73a..fa79ebc7af 100644 --- a/resources/js/features/forums/utils/preProcessShortcodesInBody.ts +++ b/resources/js/features/forums/utils/preProcessShortcodesInBody.ts @@ -30,7 +30,11 @@ const createPatterns = (type: string) => [ ]; export function preProcessShortcodesInBody(body: string): string { - let result = body; + // First, normalize any escaped newlines back to actual newlines. + let result = body.replace(/\u21B5\n/g, '\n'); + + // Then, normalize any remaining line endings. + result = result.replace(/\r\n|\r|\n/g, '\n'); for (const { type, shortcode } of shortcodeTypes) { const patterns = createPatterns(type); From a63e64ffaba591afbaf114537384793df2f0c0e8 Mon Sep 17 00:00:00 2001 From: Jamiras <32680403+Jamiras@users.noreply.github.com> Date: Mon, 20 Jan 2025 08:22:11 -0700 Subject: [PATCH 2/4] hide future event achievement selection (#3085) --- app/Support/Shortcode/Shortcode.php | 13 ++++++++++ .../achievements-list-item.blade.php | 24 ++++++++++++------- .../pages-legacy/achievementInfo.blade.php | 14 +++++++++-- 3 files changed, 40 insertions(+), 11 deletions(-) diff --git a/app/Support/Shortcode/Shortcode.php b/app/Support/Shortcode/Shortcode.php index be493f2a6a..41473ee976 100644 --- a/app/Support/Shortcode/Shortcode.php +++ b/app/Support/Shortcode/Shortcode.php @@ -4,8 +4,11 @@ namespace App\Support\Shortcode; +use App\Models\Achievement; +use App\Models\System; use App\Models\Ticket; use App\Models\User; +use Carbon\Carbon; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\DB; use Thunder\Shortcode\Event\FilterShortcodesEvent; @@ -340,6 +343,16 @@ private function embedAchievement(int $id): string return ''; } + if ($data['ConsoleID'] === System::Events) { + $achievement = Achievement::find($id); + if ($achievement->eventData?->source_achievement_id + && $achievement->eventData->active_from > Carbon::now()) { + $data['Title'] = $data['AchievementTitle'] = 'Upcoming Challenge'; + $data['Description'] = '?????'; + $data['BadgeName'] = '00000'; + } + } + return achievementAvatar($data, iconSize: 24); } diff --git a/resources/views/components/game/achievements-list/achievements-list-item.blade.php b/resources/views/components/game/achievements-list/achievements-list-item.blade.php index 94a514b88a..730f1b4e13 100644 --- a/resources/views/components/game/achievements-list/achievements-list-item.blade.php +++ b/resources/views/components/game/achievements-list/achievements-list-item.blade.php @@ -26,15 +26,6 @@ $imgClass = $isUnlockedOnHardcore ? 'goldimagebig' : 'badgeimg'; $imgClass .= ' w-[54px] h-[54px] sm:w-16 sm:h-16'; -$renderedAchievementAvatar = achievementAvatar( - $achievement, - label: false, - icon: $achBadgeName, - iconSize: 64, - iconClass: $imgClass, - tooltip: false -); - $unlockDate = ''; if (isset($achievement['DateEarned'])) { $unlockDate = Carbon::parse($achievement['DateEarned'])->format('F j Y, g:ia'); @@ -53,8 +44,23 @@ if ($activeUntil > Carbon::now()) { $isActive = true; } + } elseif ($achievement['SourceAchievementId']) { + // future event has been picked. don't show it until it's active + $achBadgeName = '00000'; + $achievement['Title'] = 'Upcoming Challenge'; + $achievement['Description'] = '?????'; + $achievement['SourceGameId'] = null; } } + +$renderedAchievementAvatar = achievementAvatar( + $achievement, + label: false, + icon: $achBadgeName, + iconSize: 64, + iconClass: $imgClass, + tooltip: false +); ?>
  • diff --git a/resources/views/pages-legacy/achievementInfo.blade.php b/resources/views/pages-legacy/achievementInfo.blade.php index a2b7190776..1c949ad855 100644 --- a/resources/views/pages-legacy/achievementInfo.blade.php +++ b/resources/views/pages-legacy/achievementInfo.blade.php @@ -15,6 +15,7 @@ use App\Platform\Enums\AchievementType; use App\Platform\Services\TriggerDecoderService; use App\Support\Shortcode\Shortcode; +use Carbon\Carbon; use Illuminate\Support\Facades\Blade; authenticateFromCookie($user, $permissions, $userDetails); @@ -132,9 +133,18 @@ $eventAchievement = EventAchievement::where('achievement_id', '=', $achievementID) ->with('sourceAchievement')->first(); - // update the ID of the dataOut so the link goes to the source achievement if ($eventAchievement?->sourceAchievement) { - $dataOut['ID'] = $eventAchievement->sourceAchievement->ID; + if ($eventAchievement->active_from > Carbon::now()) { + // future event has been picked. don't show it until it's active + $eventAchievement = null; + $badgeName = $dataOut['BadgeName'] = '00000'; + $achievementTitleRaw = $achievementTitle = $dataOut['Title'] = 'Upcoming Challenge'; + $achievementDescriptionRaw = $dataOut['Description'] = '?????'; + $dataOut['SourceGameId'] = null; + } else { + // update the ID of the dataOut so the link goes to the source achievement + $dataOut['ID'] = $eventAchievement->sourceAchievement->ID; + } } } From 377e1b7fba1a39725ca06461534994450e7062bf Mon Sep 17 00:00:00 2001 From: Jamiras <32680403+Jamiras@users.noreply.github.com> Date: Mon, 20 Jan 2025 08:30:29 -0700 Subject: [PATCH 3/4] show award tiers on event page (#3082) --- .../views/pages-legacy/gameInfo.blade.php | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/resources/views/pages-legacy/gameInfo.blade.php b/resources/views/pages-legacy/gameInfo.blade.php index f72a6d89a9..a91ce33b34 100644 --- a/resources/views/pages-legacy/gameInfo.blade.php +++ b/resources/views/pages-legacy/gameInfo.blade.php @@ -1,6 +1,7 @@ system->active && !$isEventGame) @endif + + @if (count($gameModel->event?->awards ?? []) > 0) + where('AwardData', $gameModel->event->id) + ->groupBy('AwardDataExtra') + ->select(['AwardDataExtra', DB::raw('count(*) AS total')]) + ->get(); + ?> +
    +

    Award Tiers

    + + @foreach ($gameModel->event->awards->sortBy('achievements_required') as $award) + + + + + @endforeach +
    +
    + {{ $award->label }} +
    +

    {{ $award->label }}

    +

    {{ $award->achievements_required }} {{ Str::plural('achievement', $award->achievements_required) }}

    +
    +
    +
    + {{ number_format($badgeCounts->where('AwardDataExtra', $award->tier_index)->first()?->total ?? 0) }} +
    +
    + @endif @endif From bc9db6eae75f2b1164b430028ad4df80b76c5720 Mon Sep 17 00:00:00 2001 From: Jamiras <32680403+Jamiras@users.noreply.github.com> Date: Mon, 20 Jan 2025 08:45:08 -0700 Subject: [PATCH 4/4] shortcode support for [quote] (#3074) --- app/Support/Shortcode/Shortcode.php | 28 +++++++++- lang/en_US.json | 1 + resources/css/devbox.css | 11 ++++ .../ShortcodeCode/ShortcodeCode.test.tsx | 31 +++++++++++ .../ShortcodeCode/ShortcodeCode.tsx | 26 +++++---- .../ShortcodeQuote/ShortcodeQuote.test.tsx | 55 +++++++++++++++++++ .../ShortcodeQuote/ShortcodeQuote.tsx | 26 +++++++++ .../ShortcodeRenderer/ShortcodeQuote/index.ts | 1 + .../ShortcodeRenderer.test.tsx | 14 +++++ .../ShortcodeRenderer/ShortcodeRenderer.tsx | 7 +++ .../forums/hooks/useShortcodesList.ts | 2 + .../textarea-rich-text-controls.blade.php | 8 ++- tests/Feature/Community/ShortcodeTest.php | 5 ++ 13 files changed, 199 insertions(+), 16 deletions(-) create mode 100644 resources/js/features/forums/components/ShortcodeRenderer/ShortcodeQuote/ShortcodeQuote.test.tsx create mode 100644 resources/js/features/forums/components/ShortcodeRenderer/ShortcodeQuote/ShortcodeQuote.tsx create mode 100644 resources/js/features/forums/components/ShortcodeRenderer/ShortcodeQuote/index.ts diff --git a/app/Support/Shortcode/Shortcode.php b/app/Support/Shortcode/Shortcode.php index 41473ee976..d5e7351729 100644 --- a/app/Support/Shortcode/Shortcode.php +++ b/app/Support/Shortcode/Shortcode.php @@ -35,6 +35,7 @@ public function __construct() ->add('code', fn (ShortcodeInterface $s) => $this->renderCode($s)) ->add('url', fn (ShortcodeInterface $s) => $this->renderUrlLink($s)) ->add('link', fn (ShortcodeInterface $s) => $this->renderLink($s)) + ->add('quote', fn (ShortcodeInterface $s) => $this->renderQuote($s)) ->add('spoiler', fn (ShortcodeInterface $s) => $this->renderSpoiler($s)) ->add('ach', fn (ShortcodeInterface $s) => $this->embedAchievement((int) ($s->getBbCode() ?: $s->getContent()))) ->add('game', fn (ShortcodeInterface $s) => $this->embedGame((int) ($s->getBbCode() ?: $s->getContent()))) @@ -151,11 +152,11 @@ public static function stripAndClamp( '~\[ticket(=)?(\d+)]~i' => 'Ticket $2', // Fragments: opening tags without closing tags. - '~\[(b|i|u|s|img|code|url|link|spoiler|ach|game|ticket|user)\b[^\]]*?\]~i' => '', - '~\[(b|i|u|s|img|code|url|link|spoiler|ach|game|ticket|user)\b[^\]]*?$~i' => '...', + '~\[(b|i|u|s|img|code|url|link|quote|spoiler|ach|game|ticket|user)\b[^\]]*?\]~i' => '', + '~\[(b|i|u|s|img|code|url|link|quote|spoiler|ach|game|ticket|user)\b[^\]]*?$~i' => '...', // Fragments: closing tags without opening tags. - '~\[/?(b|i|u|s|img|code|url|link|spoiler|ach|game|ticket|user)\]~i' => '', + '~\[/?(b|i|u|s|img|code|url|link|quote|spoiler|ach|game|ticket|user)\]~i' => '', ]; foreach ($stripPatterns as $stripPattern => $replacement) { @@ -318,6 +319,27 @@ private function renderCode(ShortcodeInterface $shortcode): string return '
    ' . str_replace('
    ', '', $shortcode->getContent() ?? '') . '
    '; } + private function renderQuote(ShortcodeInterface $shortcode): string + { + $content = trim($shortcode->getContent() ?? ''); + + // $content will contain a leading and trailing
    if the [quote] tag is on a separate line. + // + // [quote] + // This is a quote. + // [/quote] + // + // We don't want that extra whitespace in the output, so strip them. Leave any intermediary
    s. + if (str_starts_with($content, '
    ')) { + $content = substr($content, 4); + } + if (str_ends_with($content, '
    ')) { + $content = substr($content, 0, -4); + } + + return '' . $content . ''; + } + private function renderSpoiler(ShortcodeInterface $shortcode): string { $content = $shortcode->getContent() ?? ''; diff --git a/lang/en_US.json b/lang/en_US.json index 5f35a8f9cd..6a9885b319 100644 --- a/lang/en_US.json +++ b/lang/en_US.json @@ -599,6 +599,7 @@ "Underline": "Underline", "Strikethrough": "Strikethrough", "Code": "Code", + "Quote": "Quote", "Spoiler": "Spoiler", "Image": "Image", "Link": "Link", diff --git a/resources/css/devbox.css b/resources/css/devbox.css index 94d0f8927f..cf36586b45 100644 --- a/resources/css/devbox.css +++ b/resources/css/devbox.css @@ -24,6 +24,17 @@ box-sizing: border-box; } +@layer components { + .quotedtext { + border-left: 3px solid var(--text-color-muted); + padding: 4px 6px 5px 8px; + margin: 3px 0px; + background: var(--box-bg-color); + width: 100%; + display: inline-block; + } +} + .devbox > .spoiler { display: none; border-style: dashed; diff --git a/resources/js/features/forums/components/ShortcodeRenderer/ShortcodeCode/ShortcodeCode.test.tsx b/resources/js/features/forums/components/ShortcodeRenderer/ShortcodeCode/ShortcodeCode.test.tsx index 3d86e1a27a..2c2b92d84a 100644 --- a/resources/js/features/forums/components/ShortcodeRenderer/ShortcodeCode/ShortcodeCode.test.tsx +++ b/resources/js/features/forums/components/ShortcodeRenderer/ShortcodeCode/ShortcodeCode.test.tsx @@ -44,4 +44,35 @@ describe('Component: ShortcodeCode', () => { expect(spanEl).toHaveClass('font-mono'); expect(spanEl).toHaveClass('codetags'); }); + + it('removes leading line breaks in the output', () => { + // ARRANGE + render( + +
    + test content +
    , + ); + + // ASSERT + const spanEl = screen.getByText(/test/i); + expect(spanEl).toBeVisible(); + expect(spanEl.innerHTML).toEqual('test content'); + }); + + it('retains inner line breaks in the output', () => { + // ARRANGE + render( + + test +
    + content +
    , + ); + + // ASSERT + const spanEl = screen.getByText(/test/i); + expect(spanEl).toBeVisible(); + expect(spanEl.innerHTML).toEqual('test
    content'); + }); }); diff --git a/resources/js/features/forums/components/ShortcodeRenderer/ShortcodeCode/ShortcodeCode.tsx b/resources/js/features/forums/components/ShortcodeRenderer/ShortcodeCode/ShortcodeCode.tsx index 55856bed36..a5b4b95e81 100644 --- a/resources/js/features/forums/components/ShortcodeRenderer/ShortcodeCode/ShortcodeCode.tsx +++ b/resources/js/features/forums/components/ShortcodeRenderer/ShortcodeCode/ShortcodeCode.tsx @@ -5,18 +5,22 @@ interface ShortcodeCodeProps { } export const ShortcodeCode: FC = ({ children }) => { - if (Array.isArray(children)) { - // Remove any leading newlines. - const processedChildren = children.filter((child, index) => { - if (index === 0 && typeof child === 'object' && 'type' in child && child.type === 'br') { - return false; - } + // Remove leading
    s and empty strings until we find content. + let isLeadingWhitespace = true; + const processedChildren = (Array.isArray(children) ? children : [children]).filter((node) => { + if (isLeadingWhitespace) { + const isObjectNode = typeof node === 'object' && node !== null; + const isEmptyObject = isObjectNode && !Object.keys(node as object).length; + const isBRElement = isObjectNode && 'type' in node && node.type === 'br'; + const isEmptyString = typeof node === 'string' && node.trim() === ''; - return true; - }); + isLeadingWhitespace = isEmptyObject || isBRElement || isEmptyString; + } - return {processedChildren}; - } + // If it isn't leading whitespace, it can stay. + // Otherwise, it's filtered out. + return !isLeadingWhitespace; + }); - return {children}; + return {processedChildren}; }; diff --git a/resources/js/features/forums/components/ShortcodeRenderer/ShortcodeQuote/ShortcodeQuote.test.tsx b/resources/js/features/forums/components/ShortcodeRenderer/ShortcodeQuote/ShortcodeQuote.test.tsx new file mode 100644 index 0000000000..a7a07d25c5 --- /dev/null +++ b/resources/js/features/forums/components/ShortcodeRenderer/ShortcodeQuote/ShortcodeQuote.test.tsx @@ -0,0 +1,55 @@ +import { render, screen } from '@/test'; + +import { ShortcodeQuote } from './ShortcodeQuote'; + +describe('Component: ShortcodeQuote', () => { + it('renders without crashing', () => { + // ARRANGE + const { container } = render(Test content); + + // ASSERT + expect(container).toBeTruthy(); + }); + + it('given a simple string child, renders it inside a span with the quotedtext class', () => { + // ARRANGE + render(test content); + + // ASSERT + const spanEl = screen.getByText(/test content/i); + + expect(spanEl).toBeVisible(); + expect(spanEl).toHaveClass('quotedtext'); + }); + + it('removes leading line breaks in the output', () => { + // ARRANGE + render( + +
    + test content +
    , + ); + + // ASSERT + const spanEl = screen.getByText(/test/i); + expect(spanEl).toBeVisible(); + expect(spanEl.innerHTML).toEqual('test content'); + }); + + it('retains inner line breaks in the output', () => { + // ARRANGE + render( + + test +
    + content +
    , + ); + + // ASSERT + const spanEl = screen.getByText(/test/i); + expect(spanEl).toBeVisible(); + expect(spanEl.innerHTML).toEqual('test
    content'); + }); +}); diff --git a/resources/js/features/forums/components/ShortcodeRenderer/ShortcodeQuote/ShortcodeQuote.tsx b/resources/js/features/forums/components/ShortcodeRenderer/ShortcodeQuote/ShortcodeQuote.tsx new file mode 100644 index 0000000000..ee70830f1c --- /dev/null +++ b/resources/js/features/forums/components/ShortcodeRenderer/ShortcodeQuote/ShortcodeQuote.tsx @@ -0,0 +1,26 @@ +import type { FC, ReactNode } from 'react'; + +interface ShortcodeQuoteProps { + children: ReactNode; +} + +export const ShortcodeQuote: FC = ({ children }) => { + // Remove leading
    s and empty strings until we find content. + let isLeadingWhitespace = true; + const processedChildren = (Array.isArray(children) ? children : [children]).filter((node) => { + if (isLeadingWhitespace) { + const isObjectNode = typeof node === 'object' && node !== null; + const isEmptyObject = isObjectNode && !Object.keys(node as object).length; + const isBRElement = isObjectNode && 'type' in node && node.type === 'br'; + const isEmptyString = typeof node === 'string' && node.trim() === ''; + + isLeadingWhitespace = isEmptyObject || isBRElement || isEmptyString; + } + + // If it isn't leading whitespace, it can stay. + // Otherwise, it's filtered out. + return !isLeadingWhitespace; + }); + + return {processedChildren}; +}; diff --git a/resources/js/features/forums/components/ShortcodeRenderer/ShortcodeQuote/index.ts b/resources/js/features/forums/components/ShortcodeRenderer/ShortcodeQuote/index.ts new file mode 100644 index 0000000000..b4431fac36 --- /dev/null +++ b/resources/js/features/forums/components/ShortcodeRenderer/ShortcodeQuote/index.ts @@ -0,0 +1 @@ +export * from './ShortcodeQuote'; diff --git a/resources/js/features/forums/components/ShortcodeRenderer/ShortcodeRenderer.test.tsx b/resources/js/features/forums/components/ShortcodeRenderer/ShortcodeRenderer.test.tsx index d98d845f1c..b4ebba8e04 100644 --- a/resources/js/features/forums/components/ShortcodeRenderer/ShortcodeRenderer.test.tsx +++ b/resources/js/features/forums/components/ShortcodeRenderer/ShortcodeRenderer.test.tsx @@ -58,6 +58,20 @@ describe('Component: ShortcodeRenderer', () => { expect(textEl).toHaveClass('codetags'); }); + it('given a body with a quote tag, renders the quote shortcode component', () => { + // ARRANGE + const body = '[quote]this is some stuff inside quote tags[/quote]'; + + render(); + + // ASSERT + const textEl = screen.getByText(/this is some stuff/i); + + expect(textEl).toBeVisible(); + expect(textEl.nodeName).toEqual('SPAN'); + expect(textEl).toHaveClass('quotedtext'); + }); + it('given a body with a spoiler tag, renders the spoiler shortcode component', () => { // ARRANGE const body = '[spoiler]this is a spoiler![/spoiler]'; diff --git a/resources/js/features/forums/components/ShortcodeRenderer/ShortcodeRenderer.tsx b/resources/js/features/forums/components/ShortcodeRenderer/ShortcodeRenderer.tsx index 6be2c96307..588422d20a 100644 --- a/resources/js/features/forums/components/ShortcodeRenderer/ShortcodeRenderer.tsx +++ b/resources/js/features/forums/components/ShortcodeRenderer/ShortcodeRenderer.tsx @@ -9,6 +9,7 @@ import { ShortcodeAch } from './ShortcodeAch'; import { ShortcodeCode } from './ShortcodeCode'; import { ShortcodeGame } from './ShortcodeGame'; import { ShortcodeImg } from './ShortcodeImg'; +import { ShortcodeQuote } from './ShortcodeQuote'; import { ShortcodeSpoiler } from './ShortcodeSpoiler'; import { ShortcodeTicket } from './ShortcodeTicket'; import { ShortcodeUrl } from './ShortcodeUrl'; @@ -41,6 +42,11 @@ const retroachievementsPreset = presetReact.extend((tags) => ({ tag: ShortcodeCode, }), + quote: (node) => ({ + ...node, + tag: ShortcodeQuote, + }), + spoiler: (node) => ({ ...node, tag: ShortcodeSpoiler, @@ -111,6 +117,7 @@ export const ShortcodeRenderer: FC = ({ body }) => { 'u', 's', 'code', + 'quote', 'spoiler', 'img', 'url', diff --git a/resources/js/features/forums/hooks/useShortcodesList.ts b/resources/js/features/forums/hooks/useShortcodesList.ts index e9fedd157e..5fbf7fd95c 100644 --- a/resources/js/features/forums/hooks/useShortcodesList.ts +++ b/resources/js/features/forums/hooks/useShortcodesList.ts @@ -8,6 +8,7 @@ import { LuEyeOff, LuItalic, LuLink, + LuQuote, LuStrikethrough, LuUnderline, } from 'react-icons/lu'; @@ -23,6 +24,7 @@ export function useShortcodesList() { { icon: LuUnderline, t_label: t('Underline'), start: '[u]', end: '[/u]' }, { icon: LuStrikethrough, t_label: t('Strikethrough'), start: '[s]', end: '[/s]' }, { icon: LuCode2, t_label: t('Code'), start: '[code]', end: '[/code]' }, + { icon: LuQuote, t_label: t('Quote'), start: '[quote]', end: '[/quote]' }, { icon: LuEyeOff, t_label: t('Spoiler'), start: '[spoiler]', end: '[/spoiler]' }, { icon: BsImageFill, t_label: t('Image'), start: '[img=', end: ']' }, { icon: LuLink, t_label: t('Link'), start: '[url=', end: ']' }, diff --git a/resources/views/components/base/form/textarea-rich-text-controls.blade.php b/resources/views/components/base/form/textarea-rich-text-controls.blade.php index ff27ab5456..82302bb31a 100644 --- a/resources/views/components/base/form/textarea-rich-text-controls.blade.php +++ b/resources/views/components/base/form/textarea-rich-text-controls.blade.php @@ -30,9 +30,13 @@ {{-- x-tooltip="tooltip" --}} title="{{ __('Code') }}"> - +