Browse Source

make markdown editor more advanced

imwald
Silberengel 2 weeks ago
parent
commit
551e103618
  1. 111
      src/components/AdvancedEventLab/AdvancedEventLabMarkupToolbar.tsx
  2. 18
      src/components/Image/index.tsx
  3. 68
      src/components/Note/MarkdownArticle/MarkdownArticle.tsx
  4. 20
      src/components/PaytoLink/index.tsx
  5. 13
      src/i18n/locales/de.ts
  6. 13
      src/i18n/locales/en.ts

111
src/components/AdvancedEventLab/AdvancedEventLabMarkupToolbar.tsx

@ -176,8 +176,31 @@ export function AdvancedEventLabMarkupToolbar({
) )
})} })}
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<DropdownMenuItem onClick={() => run((v) => labInsertRaw(v, sliceRef, '\n---\n'))}> <DropdownMenuItem
{t('Advanced lab tb horizontalRule')} onClick={() =>
run((v) =>
labInsertRaw(
v,
sliceRef,
`\n${t('Advanced lab tb heading placeholder')}\n===\n`
)
)
}
>
{t('Advanced lab tb setextH1')}
</DropdownMenuItem>
<DropdownMenuItem
onClick={() =>
run((v) =>
labInsertRaw(
v,
sliceRef,
`\n${t('Advanced lab tb heading placeholder')}\n---\n`
)
)
}
>
{t('Advanced lab tb setextH2')}
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
@ -197,6 +220,12 @@ export function AdvancedEventLabMarkupToolbar({
<DropdownMenuItem onClick={() => run((v) => labWrapOrSnippet(v, sliceRef, '*', 'italic'))}> <DropdownMenuItem onClick={() => run((v) => labWrapOrSnippet(v, sliceRef, '*', 'italic'))}>
{t('Advanced lab tb italic')} {t('Advanced lab tb italic')}
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem onClick={() => run((v) => labWrapOrSnippet(v, sliceRef, '__', 'bold'))}>
{t('Advanced lab tb boldUnderscore')}
</DropdownMenuItem>
<DropdownMenuItem onClick={() => run((v) => labWrapOrSnippet(v, sliceRef, '_', 'italic'))}>
{t('Advanced lab tb italicUnderscore')}
</DropdownMenuItem>
<DropdownMenuItem onClick={() => run((v) => labWrapOrSnippet(v, sliceRef, '~~', 'strikethrough'))}> <DropdownMenuItem onClick={() => run((v) => labWrapOrSnippet(v, sliceRef, '~~', 'strikethrough'))}>
{t('Advanced lab tb strike')} {t('Advanced lab tb strike')}
</DropdownMenuItem> </DropdownMenuItem>
@ -224,6 +253,44 @@ export function AdvancedEventLabMarkupToolbar({
<ImageIcon className="h-3.5 w-3.5 mr-2 inline" /> <ImageIcon className="h-3.5 w-3.5 mr-2 inline" />
{t('Advanced lab tb image')} {t('Advanced lab tb image')}
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
onClick={() =>
run((v) =>
labInsertSnippet(
v,
sliceRef,
'[',
'link text',
'](https://example.com "Link title")'
)
)
}
>
<Link2 className="h-3.5 w-3.5 mr-2 inline" />
{t('Advanced lab tb linkTitled')}
</DropdownMenuItem>
<DropdownMenuItem
onClick={() =>
run((v) =>
labInsertSnippet(
v,
sliceRef,
'![',
'alt text',
'](https://example.com/image.png "Image title")'
)
)
}
>
<ImageIcon className="h-3.5 w-3.5 mr-2 inline" />
{t('Advanced lab tb imageTitled')}
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={() => run((v) => labInsertRaw(v, sliceRef, ' \n'))}>
<Pilcrow className="h-3.5 w-3.5 mr-2 inline" />
{t('Advanced lab tb hardBreak')}
</DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
@ -240,10 +307,28 @@ export function AdvancedEventLabMarkupToolbar({
<List className="h-3.5 w-3.5 mr-2 inline" /> <List className="h-3.5 w-3.5 mr-2 inline" />
{t('Advanced lab tb bulletList')} {t('Advanced lab tb bulletList')}
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem onClick={() => run((v) => labInsertRaw(v, sliceRef, '\n* item one\n* item two\n'))}>
<List className="h-3.5 w-3.5 mr-2 inline" />
{t('Advanced lab tb bulletListStar')}
</DropdownMenuItem>
<DropdownMenuItem onClick={() => run((v) => labInsertRaw(v, sliceRef, '\n1. first\n2. second\n'))}> <DropdownMenuItem onClick={() => run((v) => labInsertRaw(v, sliceRef, '\n1. first\n2. second\n'))}>
<ListOrdered className="h-3.5 w-3.5 mr-2 inline" /> <ListOrdered className="h-3.5 w-3.5 mr-2 inline" />
{t('Advanced lab tb orderedList')} {t('Advanced lab tb orderedList')}
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem
onClick={() =>
run((v) =>
labInsertRaw(
v,
sliceRef,
'\n4. item starting at four\n5. next item\n'
)
)
}
>
<ListOrdered className="h-3.5 w-3.5 mr-2 inline" />
{t('Advanced lab tb orderedListStart')}
</DropdownMenuItem>
<DropdownMenuItem <DropdownMenuItem
onClick={() => onClick={() =>
run((v) => run((v) =>
@ -454,16 +539,32 @@ export function AdvancedEventLabMarkupToolbar({
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button <Button
type="button" type="button"
variant="ghost" variant="ghost"
size="sm" size="sm"
className="h-8 text-xs shrink-0" className="h-8 gap-1 text-xs shrink-0"
title={t('Advanced lab tb hrTitle')} title={t('Advanced lab tb horizontalRules')}
onClick={() => run((v) => labInsertRaw(v, sliceRef, '\n---\n'))}
> >
<Minus className="h-3.5 w-3.5" /> <Minus className="h-3.5 w-3.5" />
<ChevronDown className="h-3 w-3 opacity-60" />
</Button> </Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="z-[280] w-48">
<DropdownMenuLabel>{t('Advanced lab tb horizontalRules')}</DropdownMenuLabel>
<DropdownMenuItem onClick={() => run((v) => labInsertRaw(v, sliceRef, '\n---\n'))}>
{t('Advanced lab tb hrDashes')}
</DropdownMenuItem>
<DropdownMenuItem onClick={() => run((v) => labInsertRaw(v, sliceRef, '\n***\n'))}>
{t('Advanced lab tb hrAsterisks')}
</DropdownMenuItem>
<DropdownMenuItem onClick={() => run((v) => labInsertRaw(v, sliceRef, '\n___\n'))}>
{t('Advanced lab tb hrUnderscores')}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div> </div>
) )
} }

18
src/components/Image/index.tsx

@ -50,6 +50,8 @@ export default function Image({
holdUntilClick = false, holdUntilClick = false,
fetchPriority, fetchPriority,
onClick, onClick,
/** Native tooltip on hover (e.g. Markdown `![alt](url "title")`). When set, overrides alt-as-title on `<img>`. */
tooltipTitle,
...props ...props
}: HTMLAttributes<HTMLSpanElement> & { }: HTMLAttributes<HTMLSpanElement> & {
classNames?: { classNames?: {
@ -58,6 +60,8 @@ export default function Image({
} }
image: TImetaInfo image: TImetaInfo
alt?: string alt?: string
/** Shown as the `<img title>` tooltip when non-empty. */
tooltipTitle?: string
hideIfError?: boolean hideIfError?: boolean
errorPlaceholder?: React.ReactNode errorPlaceholder?: React.ReactNode
/** Passed to the inner `<img>` (e.g. profile banner vs avatar load order). */ /** Passed to the inner `<img>` (e.g. profile banner vs avatar load order). */
@ -93,6 +97,10 @@ export default function Image({
const loadSettledRef = useRef(false) const loadSettledRef = useRef(false)
const finalAlt = imetaAlt || alt const finalAlt = imetaAlt || alt
const imgTitle =
tooltipTitle != null && String(tooltipTitle).trim() !== ''
? String(tooltipTitle).trim()
: finalAlt || undefined
const openLinkHref = const openLinkHref =
(isSafeMediaUrl(url) && url.trim()) || (isSafeMediaUrl(imageUrl) && imageUrl.trim()) || '' (isSafeMediaUrl(url) && url.trim()) || (isSafeMediaUrl(imageUrl) && imageUrl.trim()) || ''
@ -216,9 +224,15 @@ export default function Image({
onClick?.(e) onClick?.(e)
} }
const titled = tooltipTitle != null && String(tooltipTitle).trim() !== ''
return ( return (
<span <span
className={cn('relative overflow-hidden block w-full', classNames.wrapper)} className={cn(
'relative overflow-hidden block w-full',
classNames.wrapper,
titled && 'cursor-help rounded-lg ring-1 ring-inset ring-dotted ring-muted-foreground/45'
)}
style={mergedWrapperStyle} style={mergedWrapperStyle}
onClick={handleWrapperClick} onClick={handleWrapperClick}
{...props} {...props}
@ -258,7 +272,7 @@ export default function Image({
ref={imgRef} ref={imgRef}
src={imageUrl} src={imageUrl}
alt={finalAlt} alt={finalAlt}
title={finalAlt || undefined} title={imgTitle}
referrerPolicy="no-referrer" referrerPolicy="no-referrer"
decoding={effectiveHoldUntilClick ? 'async' : 'sync'} decoding={effectiveHoldUntilClick ? 'async' : 'sync'}
// `lazy` often never starts the request inside nested feed scrollers; always-load should fetch eagerly. // `lazy` often never starts the request inside nested feed scrollers; always-load should fetch eagerly.

68
src/components/Note/MarkdownArticle/MarkdownArticle.tsx

@ -23,6 +23,7 @@ import {
} from '@/lib/url' } from '@/lib/url'
import { getHttpUrlFromITags, getImetaInfosFromEvent } from '@/lib/event' import { getHttpUrlFromITags, getImetaInfosFromEvent } from '@/lib/event'
import { canonicalizeRssArticleUrl } from '@/lib/rss-article' import { canonicalizeRssArticleUrl } from '@/lib/rss-article'
import { cn } from '@/lib/utils'
import { Event, kinds } from 'nostr-tools' import { Event, kinds } from 'nostr-tools'
import Emoji from '@/components/Emoji' import Emoji from '@/components/Emoji'
import { import {
@ -613,16 +614,6 @@ function normalizeBackticks(content: string): string {
* Note: Only converts if the text line has at least 2 characters to avoid * Note: Only converts if the text line has at least 2 characters to avoid
* creating headers from fragments like "D\n------" which would become "## D" * creating headers from fragments like "D\n------" which would become "## D"
*/ */
/**
* Normalize excessive newlines - reduce 3+ consecutive newlines (with optional whitespace) to exactly 2
*/
function normalizeNewlines(content: string): string {
// Match sequences of 3 or more newlines with optional whitespace between them
// Pattern: newline, optional whitespace, newline, optional whitespace, one or more newlines
// Replace with exactly 2 newlines
return content.replace(/\n\s*\n\s*\n+/g, '\n\n')
}
/** /**
* Normalize single newlines within bold/italic spans to spaces * Normalize single newlines within bold/italic spans to spaces
* This allows bold/italic formatting to work across single line breaks * This allows bold/italic formatting to work across single line breaks
@ -3237,6 +3228,14 @@ function parseMarkdownContentMarked(
} }
} }
/** CommonMark / GFM optional title in `()` for links and images — surfaced as native `title` + hover cue. */
const markdownTokenTitle = (token: { title?: string | null }): string | undefined => {
const raw = token.title
if (raw == null) return undefined
const s = String(raw).trim()
return s.length > 0 ? s : undefined
}
const renderInlineTokens = (tokens: any[], keyPrefix: string): React.ReactNode[] => { const renderInlineTokens = (tokens: any[], keyPrefix: string): React.ReactNode[] => {
const out: React.ReactNode[] = [] const out: React.ReactNode[] = []
for (let i = 0; i < tokens.length; i++) { for (let i = 0; i < tokens.length; i++) {
@ -3294,6 +3293,11 @@ function parseMarkdownContentMarked(
} }
case 'link': { case 'link': {
const href = String(token.href ?? '') const href = String(token.href ?? '')
const linkTip = markdownTokenTitle(token)
const linkVisual = cn(
'text-green-600 dark:text-green-400 hover:text-green-700 dark:hover:text-green-300 hover:underline break-words',
linkTip && 'cursor-help underline decoration-dotted decoration-current/70 underline-offset-2'
)
const children = stripNestedAnchorsFromNodes( const children = stripNestedAnchorsFromNodes(
renderInlineTokens(token.tokens ?? [{ type: 'text', text: token.text ?? href }], `${key}-link`), renderInlineTokens(token.tokens ?? [{ type: 'text', text: token.text ?? href }], `${key}-link`),
`${key}-link-sanitized` `${key}-link-sanitized`
@ -3303,7 +3307,8 @@ function parseMarkdownContentMarked(
<PaytoLink <PaytoLink
key={`${key}-payto`} key={`${key}-payto`}
paytoUri={href} paytoUri={href}
className="text-green-600 dark:text-green-400 hover:text-green-700 dark:hover:text-green-300 hover:underline break-words" className={linkVisual}
linkTitle={linkTip}
> >
{children} {children}
</PaytoLink> </PaytoLink>
@ -3315,7 +3320,8 @@ function parseMarkdownContentMarked(
href={href} href={href}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="text-green-600 dark:text-green-400 hover:text-green-700 dark:hover:text-green-300 hover:underline break-words" title={linkTip}
className={linkVisual}
> >
{children} {children}
</a> </a>
@ -3331,6 +3337,7 @@ function parseMarkdownContentMarked(
const cleaned = cleanUrl(src) const cleaned = cleanUrl(src)
if (!cleaned) break if (!cleaned) break
const label = String(token.text ?? '') const label = String(token.text ?? '')
const imageTip = markdownTokenTitle(token)
if (isVideo(cleaned) || isAudio(cleaned) || isHlsPlaylistUrl(cleaned)) { if (isVideo(cleaned) || isAudio(cleaned) || isHlsPlaylistUrl(cleaned)) {
const poster = videoPosterMap?.get(cleaned) const poster = videoPosterMap?.get(cleaned)
out.push( out.push(
@ -3366,6 +3373,7 @@ function parseMarkdownContentMarked(
key={`${key}-img-inline`} key={`${key}-img-inline`}
image={{ ...baseImeta, url: src }} image={{ ...baseImeta, url: src }}
alt={label || 'image'} alt={label || 'image'}
tooltipTitle={imageTip}
className="w-full rounded-lg cursor-zoom-in" className="w-full rounded-lg cursor-zoom-in"
classNames={{ classNames={{
wrapper: 'not-prose my-2 block max-w-[400px] mx-auto rounded-lg w-full', wrapper: 'not-prose my-2 block max-w-[400px] mx-auto rounded-lg w-full',
@ -4115,6 +4123,7 @@ function parseMarkdownContentMarked(
key={`${key}-img-block`} key={`${key}-img-block`}
image={imetaInfoForStandaloneImageUrl(cleaned)} image={imetaInfoForStandaloneImageUrl(cleaned)}
alt={imageToken.text || 'image'} alt={imageToken.text || 'image'}
tooltipTitle={markdownTokenTitle(imageToken)}
className="w-full rounded-lg cursor-zoom-in my-0" className="w-full rounded-lg cursor-zoom-in my-0"
classNames={{ wrapper: 'my-2 block max-w-[400px] mx-auto' }} classNames={{ wrapper: 'my-2 block max-w-[400px] mx-auto' }}
holdUntilClick={lazyMedia} holdUntilClick={lazyMedia}
@ -4133,12 +4142,31 @@ function parseMarkdownContentMarked(
const renderBlockTokens = (tokens: any[], keyPrefix: string): React.ReactNode[] => { const renderBlockTokens = (tokens: any[], keyPrefix: string): React.ReactNode[] => {
const nodes: React.ReactNode[] = [] const nodes: React.ReactNode[] = []
/** Marked emits `space` for inter-block newlines; ≥2 are required between blocks—extras are user intent. */
const spaceTokenExtraGapEm = (t: { raw?: string }): number => {
const raw = String(t.raw ?? '')
const newlineCount = raw.match(/\n/g)?.length ?? 0
const extraLines = Math.max(0, newlineCount - 2)
return Math.min(extraLines, 12) * 1.25
}
for (let i = 0; i < tokens.length; i++) { for (let i = 0; i < tokens.length; i++) {
const token = tokens[i] const token = tokens[i]
const key = `${keyPrefix}-${i}` const key = `${keyPrefix}-${i}`
switch (token.type) { switch (token.type) {
case 'space': case 'space': {
const gapEm = spaceTokenExtraGapEm(token)
if (gapEm > 0) {
nodes.push(
<div
key={`${key}-space`}
className="not-prose pointer-events-none select-none"
aria-hidden
style={{ minHeight: `${gapEm}em` }}
/>
)
}
break break
}
case 'paragraph': case 'paragraph':
nodes.push(renderParagraph(token, key)) nodes.push(renderParagraph(token, key))
break break
@ -4268,7 +4296,9 @@ function parseMarkdownContentMarked(
const listBody = React.createElement( const listBody = React.createElement(
ListTag, ListTag,
{ className: listClass }, token.ordered
? { className: listClass, start: startNum }
: { className: listClass },
items.map((item: any, itemIdx: number) => ( items.map((item: any, itemIdx: number) => (
<li <li
key={`${key}-li-${itemIdx}`} key={`${key}-li-${itemIdx}`}
@ -5451,8 +5481,7 @@ export default function MarkdownArticle({
const preprocessedContent = useMemo(() => { const preprocessedContent = useMemo(() => {
// First unescape JSON-encoded escape sequences // First unescape JSON-encoded escape sequences
let processed = unescapeJsonContent(event.content) let processed = unescapeJsonContent(event.content)
// Normalize excessive newlines (reduce 3+ to 2) // Keep multi-newline runs intact so Marked `space` tokens can reproduce intentional vertical gaps.
processed = normalizeNewlines(processed)
// Normalize single newlines within bold/italic spans to spaces // Normalize single newlines within bold/italic spans to spaces
processed = normalizeInlineFormattingNewlines(processed) processed = normalizeInlineFormattingNewlines(processed)
// Normalize Setext-style headers (H1 with ===, H2 with ---) // Normalize Setext-style headers (H1 with ===, H2 with ---)
@ -5615,6 +5644,13 @@ export default function MarkdownArticle({
return ( return (
<> <>
<style>{` <style>{`
/* Padding (not margin) so separation does not collapse with the prior list's margin */
.prose > div.break-words > ul + ul,
.prose > div.break-words > ul + ol,
.prose > div.break-words > ol + ul,
.prose > div.break-words > ol + ol {
padding-top: 0.75rem;
}
.prose ol[class*="list-decimal"] { .prose ol[class*="list-decimal"] {
list-style-type: decimal !important; list-style-type: decimal !important;
} }

20
src/components/PaytoLink/index.tsx

@ -23,7 +23,9 @@ export default function PaytoLink({
pubkey, pubkey,
onOpenZap, onOpenZap,
className, className,
children children,
/** When set (e.g. Markdown link title), used as the native `title` tooltip instead of the default payto hint. */
linkTitle
}: { }: {
paytoUri?: string paytoUri?: string
type?: string type?: string
@ -33,6 +35,7 @@ export default function PaytoLink({
onOpenZap?: (pubkey: string) => void onOpenZap?: (pubkey: string) => void
className?: string className?: string
children?: React.ReactNode children?: React.ReactNode
linkTitle?: string
}) { }) {
const { t } = useTranslation() const { t } = useTranslation()
const [dialogOpen, setDialogOpen] = useState(false) const [dialogOpen, setDialogOpen] = useState(false)
@ -78,6 +81,7 @@ export default function PaytoLink({
const iconChar = getPaytoIconChar(type) const iconChar = getPaytoIconChar(type)
const profileUrl = getPaytoProfileUrl(type, authority) const profileUrl = getPaytoProfileUrl(type, authority)
const content = children ?? <span className="break-all">{authority}</span> const content = children ?? <span className="break-all">{authority}</span>
const overrideTip = linkTitle?.trim()
const iconEl = ( const iconEl = (
<span className="shrink-0 flex items-center justify-center w-4 h-4 text-[1rem] leading-none" aria-hidden> <span className="shrink-0 flex items-center justify-center w-4 h-4 text-[1rem] leading-none" aria-hidden>
@ -106,7 +110,10 @@ export default function PaytoLink({
'text-primary hover:underline cursor-pointer text-left break-words inline-flex items-center gap-1.5', 'text-primary hover:underline cursor-pointer text-left break-words inline-flex items-center gap-1.5',
className className
)} )}
title={categoryLabel ? `${displayLabel} (${categoryLabel}): ${t('Open on website')}` : `${displayLabel}: ${t('Open on website')}`} title={
overrideTip ||
(categoryLabel ? `${displayLabel} (${categoryLabel}): ${t('Open on website')}` : `${displayLabel}: ${t('Open on website')}`)
}
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
> >
{iconEl} {iconEl}
@ -124,7 +131,14 @@ export default function PaytoLink({
'text-primary hover:underline cursor-pointer text-left break-words inline-flex items-center gap-1.5', 'text-primary hover:underline cursor-pointer text-left break-words inline-flex items-center gap-1.5',
className className
)} )}
title={known && categoryLabel ? `${displayLabel} (${categoryLabel}): ${t('Click to open payment options')}` : known ? `${displayLabel}: ${t('Click to open payment options')}` : t('Click to copy address')} title={
overrideTip ||
(known && categoryLabel
? `${displayLabel} (${categoryLabel}): ${t('Click to open payment options')}`
: known
? `${displayLabel}: ${t('Click to open payment options')}`
: t('Click to copy address'))
}
> >
{iconEl} {iconEl}
{content} {content}

13
src/i18n/locales/de.ts

@ -989,17 +989,30 @@ export default {
'Advanced lab tb h4': 'Überschrift 4 (####)', 'Advanced lab tb h4': 'Überschrift 4 (####)',
'Advanced lab tb h5': 'Überschrift 5 (#####)', 'Advanced lab tb h5': 'Überschrift 5 (#####)',
'Advanced lab tb h6': 'Überschrift 6 (######)', 'Advanced lab tb h6': 'Überschrift 6 (######)',
'Advanced lab tb setextH1': 'Setext-Überschrift 1 (Titel + ===)',
'Advanced lab tb setextH2': 'Setext-Überschrift 2 (Titel + ---)',
'Advanced lab tb horizontalRule': 'Horizontale Linie (---)', 'Advanced lab tb horizontalRule': 'Horizontale Linie (---)',
'Advanced lab tb horizontalRules': 'Horizontale Linien',
'Advanced lab tb hrDashes': 'Bindestriche (---)',
'Advanced lab tb hrAsterisks': 'Sterne (***)',
'Advanced lab tb hrUnderscores': 'Unterstriche (___)',
'Advanced lab tb inline': 'Inline', 'Advanced lab tb inline': 'Inline',
'Advanced lab tb bold': 'Fett (** **)', 'Advanced lab tb bold': 'Fett (** **)',
'Advanced lab tb boldUnderscore': 'Fett (__ __)',
'Advanced lab tb italic': 'Kursiv (* *)', 'Advanced lab tb italic': 'Kursiv (* *)',
'Advanced lab tb italicUnderscore': 'Kursiv (_ _)',
'Advanced lab tb strike': 'Durchgestrichen (~~ ~~)', 'Advanced lab tb strike': 'Durchgestrichen (~~ ~~)',
'Advanced lab tb inlineCode': 'Inline-Code (` `)', 'Advanced lab tb inlineCode': 'Inline-Code (` `)',
'Advanced lab tb link': 'Link [Text](URL)', 'Advanced lab tb link': 'Link [Text](URL)',
'Advanced lab tb linkTitled': 'Link mit Titel [Text](URL "Titel")',
'Advanced lab tb image': 'Bild ![alt](URL)', 'Advanced lab tb image': 'Bild ![alt](URL)',
'Advanced lab tb imageTitled': 'Bild mit Titel ![alt](URL "Titel")',
'Advanced lab tb hardBreak': 'Harter Zeilenumbruch (zwei Leerzeichen + Zeilenumbruch)',
'Advanced lab tb lists': 'Listen', 'Advanced lab tb lists': 'Listen',
'Advanced lab tb bulletList': 'Aufzählung (-)', 'Advanced lab tb bulletList': 'Aufzählung (-)',
'Advanced lab tb bulletListStar': 'Aufzählung (*)',
'Advanced lab tb orderedList': 'Nummerierte Liste (1.)', 'Advanced lab tb orderedList': 'Nummerierte Liste (1.)',
'Advanced lab tb orderedListStart': 'Nummerierte Liste (Startnummer, z. B. 4.)',
'Advanced lab tb taskItem': 'Aufgabe (- [ ])', 'Advanced lab tb taskItem': 'Aufgabe (- [ ])',
'Advanced lab tb blocks': 'Blöcke', 'Advanced lab tb blocks': 'Blöcke',
'Advanced lab tb blockquote': 'Zitat (>)', 'Advanced lab tb blockquote': 'Zitat (>)',

13
src/i18n/locales/en.ts

@ -990,17 +990,30 @@ export default {
'Advanced lab tb h4': 'Heading 4 (####)', 'Advanced lab tb h4': 'Heading 4 (####)',
'Advanced lab tb h5': 'Heading 5 (#####)', 'Advanced lab tb h5': 'Heading 5 (#####)',
'Advanced lab tb h6': 'Heading 6 (######)', 'Advanced lab tb h6': 'Heading 6 (######)',
'Advanced lab tb setextH1': 'Setext heading 1 (title + ===)',
'Advanced lab tb setextH2': 'Setext heading 2 (title + ---)',
'Advanced lab tb horizontalRule': 'Horizontal rule (---)', 'Advanced lab tb horizontalRule': 'Horizontal rule (---)',
'Advanced lab tb horizontalRules': 'Horizontal rules',
'Advanced lab tb hrDashes': 'Dashes (---)',
'Advanced lab tb hrAsterisks': 'Asterisks (***)',
'Advanced lab tb hrUnderscores': 'Underscores (___)',
'Advanced lab tb inline': 'Inline', 'Advanced lab tb inline': 'Inline',
'Advanced lab tb bold': 'Bold (** **)', 'Advanced lab tb bold': 'Bold (** **)',
'Advanced lab tb boldUnderscore': 'Bold (__ __)',
'Advanced lab tb italic': 'Italic (* *)', 'Advanced lab tb italic': 'Italic (* *)',
'Advanced lab tb italicUnderscore': 'Italic (_ _)',
'Advanced lab tb strike': 'Strikethrough (~~ ~~)', 'Advanced lab tb strike': 'Strikethrough (~~ ~~)',
'Advanced lab tb inlineCode': 'Inline code (` `)', 'Advanced lab tb inlineCode': 'Inline code (` `)',
'Advanced lab tb link': 'Link [text](url)', 'Advanced lab tb link': 'Link [text](url)',
'Advanced lab tb linkTitled': 'Link with title [text](url "title")',
'Advanced lab tb image': 'Image ![alt](url)', 'Advanced lab tb image': 'Image ![alt](url)',
'Advanced lab tb imageTitled': 'Image with title ![alt](url "title")',
'Advanced lab tb hardBreak': 'Hard line break (two spaces + newline)',
'Advanced lab tb lists': 'Lists', 'Advanced lab tb lists': 'Lists',
'Advanced lab tb bulletList': 'Bullet list (-)', 'Advanced lab tb bulletList': 'Bullet list (-)',
'Advanced lab tb bulletListStar': 'Bullet list (*)',
'Advanced lab tb orderedList': 'Numbered list (1.)', 'Advanced lab tb orderedList': 'Numbered list (1.)',
'Advanced lab tb orderedListStart': 'Numbered list (custom start, e.g. 4.)',
'Advanced lab tb taskItem': 'Task item (- [ ])', 'Advanced lab tb taskItem': 'Task item (- [ ])',
'Advanced lab tb blocks': 'Blocks', 'Advanced lab tb blocks': 'Blocks',
'Advanced lab tb blockquote': 'Blockquote (>)', 'Advanced lab tb blockquote': 'Blockquote (>)',

Loading…
Cancel
Save