Răsfoiți Sursa

add trix editor

Jason Gorst 1 lună în urmă
părinte
comite
f7ccd7ae04
2 a modificat fișierele cu 163 adăugiri și 9 ștergeri
  1. 4 9
      app/assets/css/trix.css
  2. 159 0
      app/components/TrixEditor.client.vue

+ 4 - 9
app/assets/css/trix.css

@@ -5,15 +5,10 @@
 }
 
 .trix-content {
-  @apply prose leading-[1.5];
-  @apply prose-h1:text-xl prose-h1:mt-0 prose-h1:mb-2;
-  @apply prose-p:mt-0 prose-p:mb-2;
-  @apply prose-a:underline;
-  @apply prose-blockquote:mt-0 prose-blockquote:mb-2;
-  @apply prose-pre:mt-0 prose-pre:mb-2;
-  @apply prose-ol:mt-0 prose-ol:mb-2;
-  @apply prose-ul:mt-0 prose-ul:mb-2;
-  @apply prose-li:my-0;
+  @apply prose leading-normal prose-h1:mt-0 prose-h1:mb-2 prose-h1:text-xl
+    prose-p:mt-0 prose-p:mb-2 prose-a:underline prose-blockquote:mt-0
+    prose-blockquote:mb-2 prose-pre:mt-0 prose-pre:mb-2 prose-ol:mt-0 prose-ol:mb-2
+    prose-ul:mt-0 prose-ul:mb-2 prose-li:my-0;
 
   & :is(div, p) + h1 {
     @apply mt-4;

+ 159 - 0
app/components/TrixEditor.client.vue

@@ -0,0 +1,159 @@
+<template>
+  <div>
+    <input
+      :id="`${id}_input`"
+      :value="initialValue"
+      type="hidden"
+    />
+
+    <trix-editor
+      :id="id"
+      ref="trixEditor"
+      :input="`${id}_input`"
+      class="trix-content"
+      v-bind="$attrs"
+      @trix-change="onTrixChange"
+      @trix-focus="onTrixFocus"
+      @trix-blur="onTrixBlur"
+      @trix-before-initialize="onTrixBeforeInitialize"
+      @trix-initialize="onTrixInitialize"
+      @trix-paste="onTrixPaste"
+      @trix-selection-change="onTrixSelectionChange"
+      @trix-file-accept.prevent="onTrixFileAccept"
+      @trix-attachment-add="onTrixAttachmentAdd"
+      @trix-attachment-remove="onTrixAttachmentRemove"
+    />
+  </div>
+</template>
+
+<script setup>
+import Trix from "trix"
+
+defineOptions({ inheritAttrs: false })
+
+const model = defineModel()
+
+const props = defineProps({
+  id: {
+    type: String,
+    required: false,
+    default: () => useId()
+  },
+
+  config: {
+    type: Object,
+    required: false,
+    default: () => {}
+  }
+})
+
+const emit = defineEmits([
+  "blur",
+  "change",
+  "focus",
+  "input",
+  "trix-attachment-add",
+  "trix-attachment-remove",
+  "trix-before-initialize",
+  "trix-blur",
+  "trix-change",
+  "trix-file-accept",
+  "trix-focus",
+  "trix-initialize",
+  "trix-paste",
+  "trix-selection-change"
+])
+
+defineExpose({
+  id: props.id,
+  modelValue: model,
+  reset() {
+    reset()
+  }
+})
+
+const trixEditor = useTemplateRef("trixEditor")
+const initialValue = ref(_isNull(model.value) ? "" : model.value)
+
+onBeforeMount(() => {
+  _merge(Trix.config, trixConfig, props.config)
+})
+
+async function reset() {
+  if (_isNull(model.value)) {
+    model.value = ""
+    await nextTick()
+  }
+
+  trixEditor.value?.editor.loadHTML(model.value)
+}
+
+function onTrixChange(event) {
+  model.value = event.target.value
+
+  emit("change", event)
+  emit("input", event)
+  emit("trix-change", event)
+}
+
+function onTrixInitialize(event) {
+  reset()
+  emit("trix-initialize", event)
+}
+
+function onTrixFocus(event) {
+  emit("focus", event)
+  emit("trix-focus", event)
+}
+
+function onTrixBlur(event) {
+  emit("blur", event)
+  emit("trix-blur", event)
+}
+
+function onTrixBeforeInitialize(event) {
+  emit("trix-before-initialize", event)
+}
+
+function onTrixPaste(event) {
+  emit("trix-paste", event)
+}
+
+function onTrixSelectionChange(event) {
+  emit("trix-selection-change", event)
+}
+
+function onTrixFileAccept(file) {
+  emit("trix-file-accept", file)
+}
+
+function onTrixAttachmentAdd(file) {
+  emit("trix-attachment-add", file)
+}
+
+function onTrixAttachmentRemove(file) {
+  emit("trix-attachment-remove", file)
+}
+</script>
+
+<style>
+@reference "~/assets/css/main.css";
+
+trix-editor {
+  /* textarea.root */
+  /* min-height: py-2 [--spacing(4)] + border [2 * 1px = 2px] + line-height [1.5rem] */
+  @apply max-w-full! min-h-[calc(--spacing(4)+2px+1.5rem)] appearance-none rounded-md border
+  border-surface-300 bg-surface-0 px-3 py-2 text-surface-700
+  shadow-[0_1px_2px_0_rgba(18,18,23,0.05)] outline-hidden transition-colors
+  duration-200 placeholder:text-surface-500 enabled:hover:border-surface-400
+  enabled:focus:border-primary disabled:bg-surface-200 disabled:text-surface-500
+  dark:border-surface-700 dark:bg-surface-950 dark:text-surface-0
+  dark:placeholder:text-surface-400 dark:enabled:hover:border-surface-600
+  dark:disabled:bg-surface-700 dark:disabled:text-surface-400
+  p-invalid:border-red-400 p-invalid:placeholder:text-red-600
+  dark:p-invalid:border-red-300 dark:p-invalid:placeholder:text-red-400
+  p-small:px-2.5 p-small:py-1.5 p-small:text-sm p-large:px-3.5 p-large:py-2.5
+  p-large:text-lg p-fluid:w-full p-filled:bg-surface-50
+  dark:p-filled:bg-surface-800;
+}
+</style>