feat: improve editor

zio/stable
Anthony Fu 2022-11-30 12:50:29 +08:00
parent b784264d96
commit ccf6a17f72
12 changed files with 68 additions and 24 deletions

View File

@ -9,6 +9,7 @@ const {
draftKey, draftKey,
initial = getDefaultDraft() as never /* Bug of vue-core */, initial = getDefaultDraft() as never /* Bug of vue-core */,
expanded: _expanded = false, expanded: _expanded = false,
placeholder,
} = defineProps<{ } = defineProps<{
draftKey: string draftKey: string
initial?: () => Draft initial?: () => Draft
@ -18,6 +19,7 @@ const {
expanded?: boolean expanded?: boolean
}>() }>()
const { t } = useI18n()
// eslint-disable-next-line prefer-const // eslint-disable-next-line prefer-const
let { draft, isEmpty } = $(useDraft(draftKey, initial)) let { draft, isEmpty } = $(useDraft(draftKey, initial))
@ -30,7 +32,7 @@ const { editor } = useTiptap({
get: () => draft.params.status, get: () => draft.params.status,
set: newVal => draft.params.status = newVal, 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, autofocus: shouldExpanded,
onSubmit: publish, onSubmit: publish,
onFocus() { isExpanded = true }, onFocus() { isExpanded = true },

View File

@ -20,7 +20,7 @@ const selectedLanguage = computed({
<template> <template>
<NodeViewWrapper> <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> <select v-model="selectedLanguage" contenteditable="false" absolute top-1 right-1 rounded px2 op0 hover:op100 focus:op100 transition>
<option :value="null"> <option :value="null">
plain plain

View File

@ -125,12 +125,26 @@ function treeToText(input: Node): string {
pre = '\n' pre = '\n'
if (input.nodeName === 'code') { if (input.nodeName === 'code') {
if (input.parentNode?.nodeName === 'pre') {
const clz = input.attrs.find(attr => attr.name === 'class') const clz = input.attrs.find(attr => attr.name === 'class')
const lang = clz?.value.replace('language-', '') const lang = clz?.value.replace('language-', '')
pre = `\`\`\`${lang || ''}\n` pre = `\`\`\`${lang || ''}\n`
post = '\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) if ('childNodes' in input)
body = input.childNodes.map(n => treeToText(n)).join('') body = input.childNodes.map(n => treeToText(n)).join('')

View File

@ -8,7 +8,6 @@ export interface Draft {
status?: Exclude<CreateStatusParams['status'], null> status?: Exclude<CreateStatusParams['status'], null>
} }
attachments: Attachment[] attachments: Attachment[]
placeholder: string
} }
export type DraftMap = Record<string, Draft> 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 { export function getDefaultDraft(options: Partial<Draft['params'] & Omit<Draft, 'params'>> = {}): Draft {
const { t } = useI18n()
const { const {
status = '', status = '',
inReplyToId, inReplyToId,
visibility = 'public', visibility = 'public',
placeholder = t('placeholder.default_1'),
attachments = [], attachments = [],
} = options } = options
return { return {
params: { params: {
status, status,
@ -39,7 +37,6 @@ export function getDefaultDraft(options: Partial<Draft['params'] & Omit<Draft, '
visibility, visibility,
}, },
attachments, attachments,
placeholder,
} }
} }
@ -53,12 +50,10 @@ export function getDraftFromStatus(status: Status, text?: null | string): Draft
} }
export function getReplyDraft(status: Status) { export function getReplyDraft(status: Status) {
const { t } = useI18n()
return { return {
key: `reply-${status.id}`, key: `reply-${status.id}`,
draft: () => getDefaultDraft({ draft: () => getDefaultDraft({
inReplyToId: status!.id, inReplyToId: status!.id,
placeholder: t('placeholder.reply_to_account', [status?.account ? getDisplayName(status.account) : t('placeholder.the_thread')]),
visibility: status.visibility, visibility: status.visibility,
}), }),
} }
@ -76,7 +71,7 @@ export const isEmptyDraft = (draft: Draft | null | undefined) => {
export function useDraft( export function useDraft(
draftKey: string, draftKey: string,
initial: () => Draft = () => getDefaultDraft(), initial: () => Draft = () => getDefaultDraft({}),
) { ) {
const draft = computed({ const draft = computed({
get() { get() {

View File

@ -98,6 +98,7 @@
"placeholder": { "placeholder": {
"default_1": "What is on your mind?", "default_1": "What is on your mind?",
"reply_to_account": "Reply to {0}", "reply_to_account": "Reply to {0}",
"replying": "Replying",
"the_thread": "the thread" "the_thread": "the thread"
}, },
"state": { "state": {

View File

@ -67,9 +67,15 @@ body {
p { p {
--at-apply: my-2; --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 { .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 { .shiki {
background: transparent !important; background: transparent !important;
@ -78,15 +84,7 @@ body {
} }
.content-editor { .content-editor {
--at-apply: 'outline-none flex-1'; --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;
}
}
} }
.content-editor.content-rich { .content-editor.content-rich {

View File

@ -2,13 +2,17 @@
--c-primary: #EA9E44; --c-primary: #EA9E44;
--c-primary-active: #C16929; --c-primary-active: #C16929;
--c-border: #88888820; --c-border: #88888820;
--c-bg-base: #fff; --c-bg-base: #fff;
--c-bg-active: #f6f6f6; --c-bg-active: #f6f6f6;
--c-bg-code: #00000006; --c-bg-code: #00000006;
--c-bg-selection: #8885; --c-bg-selection: #8885;
--c-text-base: #232323; --c-text-base: #232323;
--c-text-code: #7ca72f;
--c-text-secondary: #686868; --c-text-secondary: #686868;
--c-text-secondary-light: #919191; --c-text-secondary-light: #919191;
--c-bg-btn-disabled: #a1a1a1; --c-bg-btn-disabled: #a1a1a1;
--c-text-btn-disabled: #fff; --c-text-btn-disabled: #fff;
--c-text-btn: #232323; --c-text-btn: #232323;
@ -18,9 +22,12 @@
--c-bg-base: #111; --c-bg-base: #111;
--c-bg-active: #191919; --c-bg-active: #191919;
--c-bg-code: #ffffff06; --c-bg-code: #ffffff06;
--c-text-base: #fff; --c-text-base: #fff;
--c-text-code: #90be3d;
--c-text-secondary: #888; --c-text-secondary: #888;
--c-text-secondary-light: #686868; --c-text-secondary-light: #686868;
--c-bg-btn-disabled: #2a2a2a; --c-bg-btn-disabled: #2a2a2a;
--c-text-btn-disabled: #919191; --c-text-btn-disabled: #919191;
} }

View File

@ -22,7 +22,7 @@ exports[`content-rich > custom emoji 1`] = `
"Daniel Roe "Daniel Roe
<img <img
src=\\"https://media.mas.to/masto-public/cache/custom_emojis/images/000/288/667/original/c96ba3cb0e0e1eac.png\\" src=\\"https://media.mas.to/masto-public/cache/custom_emojis/images/000/288/667/original/c96ba3cb0e0e1eac.png\\"
alt=\\"nuxt\\" alt=\\":nuxt:\\"
class=\\"custom-emoji\\" class=\\"custom-emoji\\"
/> />
" "

View File

@ -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*"')
})
})

View File

@ -29,3 +29,5 @@ export interface GroupedNotifications {
type: string type: string
items: Notification[] items: Notification[]
} }
export type TranslateFn = ReturnType<typeof useI18n>['t']

View File

@ -22,6 +22,7 @@ export default defineConfig({
// text colors // text colors
'text-base': 'text-$c-text-base', 'text-base': 'text-$c-text-base',
'text-code': 'text-$c-text-code',
'text-inverted': 'text-$c-bg-base', 'text-inverted': 'text-$c-bg-base',
'text-secondary': 'text-$c-text-secondary', 'text-secondary': 'text-$c-text-secondary',
'text-secondary-light': 'text-$c-text-secondary-light', 'text-secondary-light': 'text-$c-text-secondary-light',