Compare commits
1 Commits
main
...
feat/TipTa
| Author | SHA1 | Date |
|---|---|---|
|
|
81896dbbe7 |
|
|
@ -142,7 +142,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {computed, nextTick, onBeforeUnmount, onMounted, ref, watch} from 'vue'
|
import {computed, nextTick, onBeforeUnmount, onMounted, ref, watch, watchEffect} from 'vue'
|
||||||
import {useI18n} from 'vue-i18n'
|
import {useI18n} from 'vue-i18n'
|
||||||
import {eventToHotkeyString} from '@github/hotkey'
|
import {eventToHotkeyString} from '@github/hotkey'
|
||||||
|
|
||||||
|
|
@ -168,8 +168,6 @@ import {TaskList} from '@tiptap/extension-list'
|
||||||
import {TaskItemWithId} from './taskItemWithId'
|
import {TaskItemWithId} from './taskItemWithId'
|
||||||
import HardBreak from '@tiptap/extension-hard-break'
|
import HardBreak from '@tiptap/extension-hard-break'
|
||||||
|
|
||||||
import {Node} from '@tiptap/pm/model'
|
|
||||||
|
|
||||||
import Commands from './commands'
|
import Commands from './commands'
|
||||||
import suggestionSetup from './suggestion'
|
import suggestionSetup from './suggestion'
|
||||||
import mentionSuggestionSetup from './mention/mentionSuggestion'
|
import mentionSuggestionSetup from './mention/mentionSuggestion'
|
||||||
|
|
@ -191,7 +189,6 @@ import {setLinkInEditor} from '@/components/input/editor/setLinkInEditor'
|
||||||
import {saveEditorDraft, loadEditorDraft, clearEditorDraft} from '@/helpers/editorDraftStorage'
|
import {saveEditorDraft, loadEditorDraft, clearEditorDraft} from '@/helpers/editorDraftStorage'
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
modelValue: string,
|
|
||||||
uploadCallback?: UploadCallback,
|
uploadCallback?: UploadCallback,
|
||||||
isEditEnabled?: boolean,
|
isEditEnabled?: boolean,
|
||||||
bottomActions?: BottomAction[],
|
bottomActions?: BottomAction[],
|
||||||
|
|
@ -215,7 +212,9 @@ const props = withDefaults(defineProps<{
|
||||||
storageKey: '',
|
storageKey: '',
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue', 'save'])
|
const emit = defineEmits(['save'])
|
||||||
|
|
||||||
|
const modelValue = defineModel<string>({ default: '' })
|
||||||
|
|
||||||
const tiptapInstanceRef = ref<HTMLInputElement | null>(null)
|
const tiptapInstanceRef = ref<HTMLInputElement | null>(null)
|
||||||
|
|
||||||
|
|
@ -286,7 +285,7 @@ const CustomImage = Image.extend({
|
||||||
|
|
||||||
nextTick(async () => {
|
nextTick(async () => {
|
||||||
|
|
||||||
const img = document.getElementById(id)
|
const img = document.getElementById(id) as HTMLImageElement | null
|
||||||
|
|
||||||
if (!img || !(img instanceof HTMLImageElement)) return
|
if (!img || !(img instanceof HTMLImageElement)) return
|
||||||
|
|
||||||
|
|
@ -334,7 +333,7 @@ const UPLOAD_PLACEHOLDER_ELEMENT = '<p>UPLOAD_PLACEHOLDER</p>'
|
||||||
let lastSavedState = ''
|
let lastSavedState = ''
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.modelValue,
|
modelValue,
|
||||||
(newValue) => {
|
(newValue) => {
|
||||||
if (!contentHasChanged.value) {
|
if (!contentHasChanged.value) {
|
||||||
lastSavedState = newValue
|
lastSavedState = newValue
|
||||||
|
|
@ -344,7 +343,7 @@ watch(
|
||||||
)
|
)
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => internalMode.value,
|
internalMode,
|
||||||
mode => {
|
mode => {
|
||||||
if (mode === 'preview') {
|
if (mode === 'preview') {
|
||||||
contentHasChanged.value = false
|
contentHasChanged.value = false
|
||||||
|
|
@ -371,11 +370,10 @@ const PasteHandler = Extension.create({
|
||||||
handlePaste: (view, event) => {
|
handlePaste: (view, event) => {
|
||||||
|
|
||||||
// Handle images pasted from clipboard
|
// Handle images pasted from clipboard
|
||||||
if (typeof props.uploadCallback !== 'undefined' && event.clipboardData?.items && event.clipboardData.items.length > 0) {
|
if (typeof props.uploadCallback !== 'undefined' && event.clipboardData?.items?.length) {
|
||||||
|
|
||||||
for (let i = 0; i < event.clipboardData.items.length; i++) {
|
for (const item of event.clipboardData.items) {
|
||||||
const item = event.clipboardData.items[i]
|
if (item.kind === 'file' && item.type.startsWith('image/')) {
|
||||||
if (item && item.kind === 'file' && item.type.startsWith('image/')) {
|
|
||||||
const file = item.getAsFile()
|
const file = item.getAsFile()
|
||||||
if (file) {
|
if (file) {
|
||||||
uploadAndInsertFiles([file])
|
uploadAndInsertFiles([file])
|
||||||
|
|
@ -438,25 +436,19 @@ const extensions : Extensions = [
|
||||||
}),
|
}),
|
||||||
|
|
||||||
Placeholder.configure({
|
Placeholder.configure({
|
||||||
placeholder: ({editor}) => {
|
placeholder({editor}) {
|
||||||
if (!isEditing.value) {
|
if (!isEditing.value || editor.getText() !== '' && !editor.isFocused) {
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
|
|
||||||
if (editor.getText() !== '' && !editor.isFocused) {
|
return props.placeholder || t('input.editor.placeholder')
|
||||||
return ''
|
|
||||||
}
|
|
||||||
|
|
||||||
return props.placeholder !== ''
|
|
||||||
? props.placeholder
|
|
||||||
: t('input.editor.placeholder')
|
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
Typography,
|
Typography,
|
||||||
Underline,
|
Underline,
|
||||||
NonInclusiveLink.configure({
|
NonInclusiveLink.configure({
|
||||||
openOnClick: false,
|
openOnClick: false,
|
||||||
validate: (href: string) => (new RegExp(
|
validate: (href) => (new RegExp(
|
||||||
`^(https?|${additionalLinkProtocols.join('|')}):\\/\\/`,
|
`^(https?|${additionalLinkProtocols.join('|')}):\\/\\/`,
|
||||||
'i',
|
'i',
|
||||||
)).test(href),
|
)).test(href),
|
||||||
|
|
@ -475,7 +467,7 @@ const extensions : Extensions = [
|
||||||
TaskList,
|
TaskList,
|
||||||
TaskItemWithId.configure({
|
TaskItemWithId.configure({
|
||||||
nested: true,
|
nested: true,
|
||||||
onReadOnlyChecked: (node: Node, checked: boolean): boolean => {
|
onReadOnlyChecked(node, checked) {
|
||||||
if (!props.isEditEnabled) {
|
if (!props.isEditEnabled) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
@ -575,24 +567,16 @@ const editor = useEditor({
|
||||||
// eslint-disable-next-line vue/no-ref-object-reactivity-loss
|
// eslint-disable-next-line vue/no-ref-object-reactivity-loss
|
||||||
editable: isEditing.value,
|
editable: isEditing.value,
|
||||||
extensions: extensions,
|
extensions: extensions,
|
||||||
onUpdate: () => {
|
onUpdate: bubbleNow,
|
||||||
bubbleNow()
|
|
||||||
},
|
|
||||||
parseOptions: {
|
parseOptions: {
|
||||||
preserveWhitespace: true,
|
preserveWhitespace: true,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(
|
watchEffect(() => editor.value?.setEditable(isEditing.value))
|
||||||
() => isEditing.value,
|
|
||||||
() => {
|
|
||||||
editor.value?.setEditable(isEditing.value)
|
|
||||||
},
|
|
||||||
{immediate: true},
|
|
||||||
)
|
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.modelValue,
|
modelValue,
|
||||||
value => {
|
value => {
|
||||||
if (!editor?.value) return
|
if (!editor?.value) return
|
||||||
|
|
||||||
|
|
@ -606,20 +590,20 @@ watch(
|
||||||
)
|
)
|
||||||
|
|
||||||
function bubbleNow() {
|
function bubbleNow() {
|
||||||
if (editor.value?.getHTML() === props.modelValue ||
|
const editorVal = editor.value!.getHTML()
|
||||||
(editor.value?.getHTML() === '<p></p>') && props.modelValue === '') {
|
if (editorVal === modelValue.value ||
|
||||||
|
(editorVal === '<p></p>') && modelValue.value === '') {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
contentHasChanged.value = true
|
contentHasChanged.value = true
|
||||||
const newContent = editor.value?.getHTML()
|
|
||||||
|
|
||||||
// Save to localStorage if storageKey is provided
|
// Save to localStorage if storageKey is provided
|
||||||
if (props.storageKey) {
|
if (props.storageKey) {
|
||||||
saveEditorDraft(props.storageKey, newContent || '')
|
saveEditorDraft(props.storageKey, editorVal)
|
||||||
}
|
}
|
||||||
|
|
||||||
emit('update:modelValue', newContent)
|
modelValue.value = editorVal
|
||||||
}
|
}
|
||||||
|
|
||||||
function bubbleSave() {
|
function bubbleSave() {
|
||||||
|
|
@ -760,18 +744,18 @@ onMounted(async () => {
|
||||||
// Load draft from localStorage if available
|
// Load draft from localStorage if available
|
||||||
if (props.storageKey) {
|
if (props.storageKey) {
|
||||||
const draft = loadEditorDraft(props.storageKey)
|
const draft = loadEditorDraft(props.storageKey)
|
||||||
if (draft && isEditorContentEmpty(props.modelValue)) {
|
if (draft && isEditorContentEmpty(modelValue.value)) {
|
||||||
// Only load draft if current content is empty
|
// Only load draft if current content is empty
|
||||||
// Set content and force edit mode for immediate editing
|
// Set content and force edit mode for immediate editing
|
||||||
editor.value?.commands.setContent(draft, {emitUpdate: false})
|
editor.value?.commands.setContent(draft, {emitUpdate: false})
|
||||||
internalMode.value = 'edit'
|
internalMode.value = 'edit'
|
||||||
// Emit the model update so parent sees the restored content
|
// Update the model so parent sees the restored content
|
||||||
emit('update:modelValue', draft)
|
modelValue.value = draft
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setModeAndValue(props.modelValue)
|
setModeAndValue(modelValue.value)
|
||||||
})
|
})
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue