StickyNote.vue 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. <script lang="ts" setup>
  2. import { computed, ref, watch } from 'vue'
  3. import { defaultStickyProps } from './constants'
  4. import type { StickyProps } from './types'
  5. import { ElInput } from 'element-plus'
  6. import Markdown from '../markdown/Markdown.vue'
  7. const props = withDefaults(defineProps<StickyProps>(), defaultStickyProps)
  8. const emit = defineEmits<{
  9. edit: [editing: boolean]
  10. 'update:modelValue': [value: string]
  11. 'markdown-click': [link: HTMLAnchorElement, e: MouseEvent]
  12. }>()
  13. const isResizing = ref(false)
  14. const input = ref<HTMLTextAreaElement | undefined>(undefined)
  15. const resHeight = computed((): number => {
  16. return props.height < props.minHeight ? props.minHeight : props.height
  17. })
  18. const resWidth = computed((): number => {
  19. return props.width < props.minWidth ? props.minWidth : props.width
  20. })
  21. const inputName = computed(() => (props.id ? `${props.id}-input` : undefined))
  22. const styles = computed((): { height: string; width: string; backgroundColor: string } => ({
  23. height: `${resHeight.value}px`,
  24. width: `${resWidth.value}px`,
  25. backgroundColor: props.backgroundColor
  26. }))
  27. const shouldShowFooter = computed((): boolean => resHeight.value > 100 && resWidth.value > 155)
  28. watch(
  29. () => props.editMode,
  30. (newMode, prevMode) => {
  31. setTimeout(() => {
  32. if (newMode && !prevMode && input.value) {
  33. if (props.defaultText === props.modelValue) {
  34. input.value.select()
  35. }
  36. input.value.focus()
  37. }
  38. }, 100)
  39. }
  40. )
  41. const onDoubleClick = () => {
  42. if (!props.readOnly) emit('edit', true)
  43. }
  44. const onInputBlur = () => {
  45. if (!isResizing.value) emit('edit', false)
  46. }
  47. const onUpdateModelValue = (value: string) => {
  48. emit('update:modelValue', value)
  49. }
  50. const onMarkdownClick = (link: HTMLAnchorElement, event: MouseEvent) => {
  51. emit('markdown-click', link, event)
  52. }
  53. const onInputScroll = (event: WheelEvent) => {
  54. // Pass through zoom events but hold regular scrolling
  55. if (!event.ctrlKey && !event.metaKey) {
  56. event.stopPropagation()
  57. }
  58. }
  59. </script>
  60. <template>
  61. <div
  62. :class="{
  63. 'sticky-note': true,
  64. [$style.sticky]: true,
  65. [$style.clickable]: !isResizing
  66. }"
  67. :style="styles"
  68. @keydown.prevent
  69. >
  70. <div v-show="!editMode" :class="$style.wrapper" @dblclick.stop="onDoubleClick">
  71. <Markdown
  72. theme="sticky"
  73. :content="modelValue"
  74. :with-multi-breaks="true"
  75. @markdown-click="onMarkdownClick"
  76. @update-content="onUpdateModelValue"
  77. />
  78. </div>
  79. <div
  80. v-show="editMode"
  81. :class="{ 'full-height': !shouldShowFooter, 'sticky-textarea': true }"
  82. @click.stop
  83. @mousedown.stop
  84. @mouseup.stop
  85. @keydown.esc="onInputBlur"
  86. @keydown.stop
  87. >
  88. <ElInput
  89. ref="input"
  90. :model-value="modelValue"
  91. :name="inputName"
  92. type="textarea"
  93. :rows="5"
  94. @blur="onInputBlur"
  95. @update:model-value="onUpdateModelValue"
  96. @wheel="onInputScroll"
  97. />
  98. </div>
  99. <div v-if="editMode && shouldShowFooter" :class="$style.footer">
  100. <span>{{ footerText }}</span>
  101. </div>
  102. </div>
  103. </template>
  104. <style lang="less" module>
  105. .sticky {
  106. position: relative;
  107. border-radius: 4px;
  108. overflow: hidden;
  109. border: 1px solid #eee;
  110. }
  111. .clickable {
  112. cursor: pointer;
  113. }
  114. .wrapper {
  115. width: 100%;
  116. height: 100%;
  117. position: absolute;
  118. padding: 0.5rem 0.75rem 0;
  119. overflow: hidden;
  120. }
  121. </style>
  122. <style lang="less">
  123. .sticky-textarea {
  124. height: 100%;
  125. box-sizing: border-box;
  126. padding: 0.5rem 0.75rem;
  127. cursor: default;
  128. .el-textarea {
  129. height: 100%;
  130. .el-textarea__inner {
  131. height: 100%;
  132. resize: unset;
  133. }
  134. }
  135. }
  136. .full-height {
  137. height: calc(100% - var(--spacing--2xs));
  138. }
  139. </style>