feat: improve editor
parent
b784264d96
commit
ccf6a17f72
|
@ -9,6 +9,7 @@ const {
|
|||
draftKey,
|
||||
initial = getDefaultDraft() as never /* Bug of vue-core */,
|
||||
expanded: _expanded = false,
|
||||
placeholder,
|
||||
} = defineProps<{
|
||||
draftKey: string
|
||||
initial?: () => Draft
|
||||
|
@ -18,6 +19,7 @@ const {
|
|||
expanded?: boolean
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
// eslint-disable-next-line prefer-const
|
||||
let { draft, isEmpty } = $(useDraft(draftKey, initial))
|
||||
|
||||
|
@ -30,7 +32,7 @@ const { editor } = useTiptap({
|
|||
get: () => draft.params.status,
|
||||
set: newVal => draft.params.status = newVal,
|
||||
}),
|
||||
placeholder: computed(() => draft.placeholder),
|
||||
placeholder: computed(() => placeholder || draft.params.inReplyToId ? t('placeholder.replying') : t('placeholder.default_1')),
|
||||
autofocus: shouldExpanded,
|
||||
onSubmit: publish,
|
||||
onFocus() { isExpanded = true },
|
||||
|
|
|
@ -20,7 +20,7 @@ const selectedLanguage = computed({
|
|||
|
||||
<template>
|
||||
<NodeViewWrapper>
|
||||
<div relative my2>
|
||||
<div relative my2 class="code-block">
|
||||
<select v-model="selectedLanguage" contenteditable="false" absolute top-1 right-1 rounded px2 op0 hover:op100 focus:op100 transition>
|
||||
<option :value="null">
|
||||
plain
|
||||
|
|
|
@ -125,12 +125,26 @@ function treeToText(input: Node): string {
|
|||
pre = '\n'
|
||||
|
||||
if (input.nodeName === 'code') {
|
||||
if (input.parentNode?.nodeName === 'pre') {
|
||||
const clz = input.attrs.find(attr => attr.name === 'class')
|
||||
const lang = clz?.value.replace('language-', '')
|
||||
|
||||
pre = `\`\`\`${lang || ''}\n`
|
||||
post = '\n```'
|
||||
}
|
||||
else {
|
||||
pre = '`'
|
||||
post = '`'
|
||||
}
|
||||
}
|
||||
else if (input.nodeName === 'b' || input.nodeName === 'strong') {
|
||||
pre = '**'
|
||||
post = '**'
|
||||
}
|
||||
else if (input.nodeName === 'i' || input.nodeName === 'em') {
|
||||
pre = '*'
|
||||
post = '*'
|
||||
}
|
||||
|
||||
if ('childNodes' in input)
|
||||
body = input.childNodes.map(n => treeToText(n)).join('')
|
||||
|
|
|
@ -8,7 +8,6 @@ export interface Draft {
|
|||
status?: Exclude<CreateStatusParams['status'], null>
|
||||
}
|
||||
attachments: Attachment[]
|
||||
placeholder: string
|
||||
}
|
||||
export type DraftMap = Record<string, Draft>
|
||||
|
||||
|
@ -24,14 +23,13 @@ export const currentUserDrafts = computed(() => {
|
|||
})
|
||||
|
||||
export function getDefaultDraft(options: Partial<Draft['params'] & Omit<Draft, 'params'>> = {}): Draft {
|
||||
const { t } = useI18n()
|
||||
const {
|
||||
status = '',
|
||||
inReplyToId,
|
||||
visibility = 'public',
|
||||
placeholder = t('placeholder.default_1'),
|
||||
attachments = [],
|
||||
} = options
|
||||
|
||||
return {
|
||||
params: {
|
||||
status,
|
||||
|
@ -39,7 +37,6 @@ export function getDefaultDraft(options: Partial<Draft['params'] & Omit<Draft, '
|
|||
visibility,
|
||||
},
|
||||
attachments,
|
||||
placeholder,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -53,12 +50,10 @@ export function getDraftFromStatus(status: Status, text?: null | string): Draft
|
|||
}
|
||||
|
||||
export function getReplyDraft(status: Status) {
|
||||
const { t } = useI18n()
|
||||
return {
|
||||
key: `reply-${status.id}`,
|
||||
draft: () => getDefaultDraft({
|
||||
inReplyToId: status!.id,
|
||||
placeholder: t('placeholder.reply_to_account', [status?.account ? getDisplayName(status.account) : t('placeholder.the_thread')]),
|
||||
visibility: status.visibility,
|
||||
}),
|
||||
}
|
||||
|
@ -76,7 +71,7 @@ export const isEmptyDraft = (draft: Draft | null | undefined) => {
|
|||
|
||||
export function useDraft(
|
||||
draftKey: string,
|
||||
initial: () => Draft = () => getDefaultDraft(),
|
||||
initial: () => Draft = () => getDefaultDraft({}),
|
||||
) {
|
||||
const draft = computed({
|
||||
get() {
|
||||
|
|
|
@ -98,6 +98,7 @@
|
|||
"placeholder": {
|
||||
"default_1": "What is on your mind?",
|
||||
"reply_to_account": "Reply to {0}",
|
||||
"replying": "Replying",
|
||||
"the_thread": "the thread"
|
||||
},
|
||||
"state": {
|
||||
|
|
|
@ -67,9 +67,15 @@ body {
|
|||
p {
|
||||
--at-apply: my-2;
|
||||
}
|
||||
code {
|
||||
--at-apply: bg-code text-code px1 py0.5 rounded text-sm;
|
||||
}
|
||||
pre code {
|
||||
--at-apply: text-base bg-transparent px0 py0 rounded-none;
|
||||
}
|
||||
|
||||
.code-block {
|
||||
--at-apply: bg-code text-0.9rem p3 mt-2 rounded overflow-auto leading-1.6em;
|
||||
--at-apply: font-mono bg-code text-base p3 mt-2 rounded overflow-auto leading-1.6em;
|
||||
|
||||
.shiki {
|
||||
background: transparent !important;
|
||||
|
@ -78,15 +84,7 @@ body {
|
|||
}
|
||||
|
||||
.content-editor {
|
||||
--at-apply: 'outline-none flex-1';
|
||||
|
||||
pre {
|
||||
--at-apply: font-mono bg-code rounded px3 py2;
|
||||
|
||||
code {
|
||||
--at-apply: bg-transparent text-0.8rem p0;
|
||||
}
|
||||
}
|
||||
--at-apply: outline-none flex-1;
|
||||
}
|
||||
|
||||
.content-editor.content-rich {
|
||||
|
|
|
@ -2,13 +2,17 @@
|
|||
--c-primary: #EA9E44;
|
||||
--c-primary-active: #C16929;
|
||||
--c-border: #88888820;
|
||||
|
||||
--c-bg-base: #fff;
|
||||
--c-bg-active: #f6f6f6;
|
||||
--c-bg-code: #00000006;
|
||||
--c-bg-selection: #8885;
|
||||
|
||||
--c-text-base: #232323;
|
||||
--c-text-code: #7ca72f;
|
||||
--c-text-secondary: #686868;
|
||||
--c-text-secondary-light: #919191;
|
||||
|
||||
--c-bg-btn-disabled: #a1a1a1;
|
||||
--c-text-btn-disabled: #fff;
|
||||
--c-text-btn: #232323;
|
||||
|
@ -18,9 +22,12 @@
|
|||
--c-bg-base: #111;
|
||||
--c-bg-active: #191919;
|
||||
--c-bg-code: #ffffff06;
|
||||
|
||||
--c-text-base: #fff;
|
||||
--c-text-code: #90be3d;
|
||||
--c-text-secondary: #888;
|
||||
--c-text-secondary-light: #686868;
|
||||
|
||||
--c-bg-btn-disabled: #2a2a2a;
|
||||
--c-text-btn-disabled: #919191;
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ exports[`content-rich > custom emoji 1`] = `
|
|||
"Daniel Roe
|
||||
<img
|
||||
src=\\"https://media.mas.to/masto-public/cache/custom_emojis/images/000/288/667/original/c96ba3cb0e0e1eac.png\\"
|
||||
alt=\\"nuxt\\"
|
||||
alt=\\":nuxt:\\"
|
||||
class=\\"custom-emoji\\"
|
||||
/>
|
||||
"
|
|
@ -0,0 +1,24 @@
|
|||
import { describe, expect, it } from 'vitest'
|
||||
import { htmlToText } from '../composables/content'
|
||||
|
||||
describe('html-to-text', () => {
|
||||
it('inline code', () => {
|
||||
expect(htmlToText('<p>text <code>code</code> inline</p>'))
|
||||
.toMatchInlineSnapshot('"text `code` inline"')
|
||||
})
|
||||
|
||||
it('code block', () => {
|
||||
expect(htmlToText('<p>text </p><pre><code class="language-js">code</code></pre>'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"text
|
||||
\`\`\`js
|
||||
code
|
||||
\`\`\`"
|
||||
`)
|
||||
})
|
||||
|
||||
it('bold & italic', () => {
|
||||
expect(htmlToText('<p>text <b>bold</b> <em>italic</em></p>'))
|
||||
.toMatchInlineSnapshot('"text **bold** *italic*"')
|
||||
})
|
||||
})
|
|
@ -29,3 +29,5 @@ export interface GroupedNotifications {
|
|||
type: string
|
||||
items: Notification[]
|
||||
}
|
||||
|
||||
export type TranslateFn = ReturnType<typeof useI18n>['t']
|
||||
|
|
|
@ -22,6 +22,7 @@ export default defineConfig({
|
|||
|
||||
// text colors
|
||||
'text-base': 'text-$c-text-base',
|
||||
'text-code': 'text-$c-text-code',
|
||||
'text-inverted': 'text-$c-bg-base',
|
||||
'text-secondary': 'text-$c-text-secondary',
|
||||
'text-secondary-light': 'text-$c-text-secondary-light',
|
||||
|
|
Loading…
Reference in New Issue