fix: contedtable
This commit is contained in:
37
bo/src/composable/useConteditable.ts
Normal file
37
bo/src/composable/useConteditable.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { ref, type Ref } from 'vue'
|
||||||
|
import DOMPurify from 'dompurify'
|
||||||
|
import { useProductStore } from '@/stores/product'
|
||||||
|
|
||||||
|
export function useEditable() {
|
||||||
|
const isEditing = ref(false)
|
||||||
|
const productStore = useProductStore()
|
||||||
|
|
||||||
|
const removeAttribute = (editableRef: HTMLElement | null) => {
|
||||||
|
if (!editableRef) return
|
||||||
|
isEditing.value = true
|
||||||
|
Array.from(editableRef.children).forEach(item => {
|
||||||
|
item.setAttribute('contenteditable', 'true')
|
||||||
|
console.log('lllllll')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const setAttribute = async (editableRef: HTMLElement | null): Promise<string | undefined> => {
|
||||||
|
if (!editableRef) return
|
||||||
|
Array.from(editableRef.children).forEach(item => {
|
||||||
|
item.setAttribute('contenteditable', 'false')
|
||||||
|
})
|
||||||
|
isEditing.value = false
|
||||||
|
|
||||||
|
const html = editableRef.innerHTML
|
||||||
|
const cleanHtml = DOMPurify.sanitize(html)
|
||||||
|
await productStore.saveProductDescription(cleanHtml)
|
||||||
|
|
||||||
|
return cleanHtml
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
isEditing,
|
||||||
|
removeAttribute,
|
||||||
|
setAttribute
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -49,9 +49,7 @@ export const useProductStore = defineStore('product', () => {
|
|||||||
currentProduct.value = null
|
currentProduct.value = null
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const data = await useFetchJson<{ items: Product }>(`/api/v1/restricted/product-description?id=${id}`, {
|
const data = await useFetchJson<{ items: Product }>(`/api/v1/restricted/product-description?id=${id}`)
|
||||||
method: 'GET',
|
|
||||||
})
|
|
||||||
|
|
||||||
const response = (data as any).items || data
|
const response = (data as any).items || data
|
||||||
currentProduct.value = response.items?.[0] || response
|
currentProduct.value = response.items?.[0] || response
|
||||||
@@ -63,6 +61,34 @@ export const useProductStore = defineStore('product', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function saveProductDescription(description: string) {
|
||||||
|
try {
|
||||||
|
const data = await useFetchJson(
|
||||||
|
`/api/v1/restricted/product-description/save-product-description?productID=1&productShopID=1&productLangID=1`,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
description: description,
|
||||||
|
description_short: "<p>Test short</p>",
|
||||||
|
meta_description: "This is a test",
|
||||||
|
meta_title: "...",
|
||||||
|
name: "...",
|
||||||
|
available_now: "Test",
|
||||||
|
available_later: "...",
|
||||||
|
usage: "<p>test</p>"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return data
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Clear current product
|
// Clear current product
|
||||||
function clearCurrentProduct() {
|
function clearCurrentProduct() {
|
||||||
currentProduct.value = null
|
currentProduct.value = null
|
||||||
@@ -76,5 +102,6 @@ export const useProductStore = defineStore('product', () => {
|
|||||||
getProductDescription,
|
getProductDescription,
|
||||||
fetchProductById,
|
fetchProductById,
|
||||||
clearCurrentProduct,
|
clearCurrentProduct,
|
||||||
|
saveProductDescription
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,34 +1,36 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="container my-10">
|
<div class="container my-10 ">
|
||||||
<div id="textDescriptionRef" ref="textDescriptionRef" v-html="productStore.productDescription.description"></div>
|
<div class="flex items-start gap-30">
|
||||||
<button @click="create">create</button>
|
<div class="flex flex-col gap-10">
|
||||||
<button @click="save">Save</button>
|
<p class="p-60 bg-yellow-300">img</p>
|
||||||
|
<div class="flex gap-3 mb-3">
|
||||||
|
<button @click="removeAttribute(editableRef)"
|
||||||
|
class="p-2 border border-(--border-light) dark:border-(--border-dark)">create</button>
|
||||||
|
<button @click="setAttribute(editableRef)"
|
||||||
|
class="p-2 border border-(--border-light) dark:border-(--border-dark)">Save</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p class="text-[25px] font-bold">{{ productStore.productDescription.name }}</p>
|
||||||
|
<p>{{ productStore.productDescription.available_now }}</p>
|
||||||
|
<p ref="editableRef2" v-html="productStore.productDescription.usage"></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div ref="editableRef" v-html="productStore.productDescription.description"></div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
|
||||||
import { useProductStore } from '@/stores/product'
|
import { useProductStore } from '@/stores/product'
|
||||||
|
import { useEditable } from '@/composable/useConteditable'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
|
||||||
const productStore = useProductStore()
|
const productStore = useProductStore()
|
||||||
const textDescriptionRef = ref<HTMLElement | null>(null)
|
|
||||||
|
|
||||||
await productStore.getProductDescription()
|
await productStore.getProductDescription()
|
||||||
|
|
||||||
const save = () => {
|
const editableRef = ref<HTMLElement | null>(null)
|
||||||
const myDiv = document.getElementById("textDescriptionRef");
|
const { removeAttribute, setAttribute } = useEditable()
|
||||||
Array.from(myDiv.children).forEach(item => {
|
|
||||||
item.setAttribute('contenteditable', 'false')
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const create = () => {
|
|
||||||
const myDiv = document.getElementById("textDescriptionRef");
|
|
||||||
Array.from(myDiv.children).forEach(item => {
|
|
||||||
item.setAttribute('contenteditable', 'true')
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -39,4 +41,5 @@ const create = () => {
|
|||||||
gap: 70px;
|
gap: 70px;
|
||||||
margin: 20px 0 20px 0;
|
margin: 20px 0 20px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
17
package-lock.json
generated
17
package-lock.json
generated
@@ -7,6 +7,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nuxt/ui": "^4.5.1",
|
"@nuxt/ui": "^4.5.1",
|
||||||
"chart.js": "^4.5.1",
|
"chart.js": "^4.5.1",
|
||||||
|
"dompurify": "^3.3.3",
|
||||||
"vue-chartjs": "^5.3.3"
|
"vue-chartjs": "^5.3.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1960,6 +1961,13 @@
|
|||||||
"integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==",
|
"integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/trusted-types": {
|
||||||
|
"version": "2.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
|
||||||
|
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
"node_modules/@types/web-bluetooth": {
|
"node_modules/@types/web-bluetooth": {
|
||||||
"version": "0.0.21",
|
"version": "0.0.21",
|
||||||
"resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz",
|
"resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz",
|
||||||
@@ -2441,6 +2449,15 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/dompurify": {
|
||||||
|
"version": "3.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.3.tgz",
|
||||||
|
"integrity": "sha512-Oj6pzI2+RqBfFG+qOaOLbFXLQ90ARpcGG6UePL82bJLtdsa6CYJD7nmiU8MW9nQNOtCHV3lZ/Bzq1X0QYbBZCA==",
|
||||||
|
"license": "(MPL-2.0 OR Apache-2.0)",
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@types/trusted-types": "^2.0.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/dotenv": {
|
"node_modules/dotenv": {
|
||||||
"version": "17.3.1",
|
"version": "17.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz",
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nuxt/ui": "^4.5.1",
|
"@nuxt/ui": "^4.5.1",
|
||||||
"chart.js": "^4.5.1",
|
"chart.js": "^4.5.1",
|
||||||
|
"dompurify": "^3.3.3",
|
||||||
"vue-chartjs": "^5.3.3"
|
"vue-chartjs": "^5.3.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user