mirror of
https://github.com/ragestudio/comty.git
synced 2025-06-09 10:34:17 +00:00
Fix context menu implementation and add clipboard utils
This commit is contained in:
parent
b54f3192b3
commit
e6fa958350
@ -1,3 +1,6 @@
|
|||||||
|
import copyToClipboard from "@utils/copyToClipboard"
|
||||||
|
import pasteFromClipboard from "@utils/pasteFromClipboard"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
"default-context": (items) => {
|
"default-context": (items) => {
|
||||||
const text = window.getSelection().toString()
|
const text = window.getSelection().toString()
|
||||||
@ -10,7 +13,7 @@ export default {
|
|||||||
copyToClipboard(text)
|
copyToClipboard(text)
|
||||||
|
|
||||||
ctx.close()
|
ctx.close()
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -18,10 +21,9 @@ export default {
|
|||||||
label: "Paste",
|
label: "Paste",
|
||||||
icon: "FiClipboard",
|
icon: "FiClipboard",
|
||||||
action: (clickedItem, ctx) => {
|
action: (clickedItem, ctx) => {
|
||||||
app.message.error("This action is not supported by your browser")
|
pasteFromClipboard(clickedItem)
|
||||||
|
|
||||||
ctx.close()
|
ctx.close()
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
items.push({
|
items.push({
|
||||||
@ -33,9 +35,9 @@ export default {
|
|||||||
})
|
})
|
||||||
|
|
||||||
ctx.close()
|
ctx.close()
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
return items
|
return items
|
||||||
}
|
},
|
||||||
}
|
}
|
@ -3,13 +3,17 @@ import download from "@utils/download"
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
"post-card": (items, parent, element, control) => {
|
"post-card": (items, parent, element, control) => {
|
||||||
|
if (!parent.id) {
|
||||||
|
parent = parent.parentNode
|
||||||
|
}
|
||||||
|
|
||||||
items.push({
|
items.push({
|
||||||
label: "Copy ID",
|
label: "Copy ID",
|
||||||
icon: "FiCopy",
|
icon: "FiCopy",
|
||||||
action: () => {
|
action: () => {
|
||||||
copyToClipboard(parent.id)
|
copyToClipboard(parent.id)
|
||||||
control.close()
|
control.close()
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
items.push({
|
items.push({
|
||||||
@ -18,7 +22,7 @@ export default {
|
|||||||
action: () => {
|
action: () => {
|
||||||
copyToClipboard(`${window.location.origin}/post/${parent.id}`)
|
copyToClipboard(`${window.location.origin}/post/${parent.id}`)
|
||||||
control.close()
|
control.close()
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
let media = null
|
let media = null
|
||||||
@ -43,7 +47,7 @@ export default {
|
|||||||
action: () => {
|
action: () => {
|
||||||
copyToClipboard(media.src)
|
copyToClipboard(media.src)
|
||||||
control.close()
|
control.close()
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
items.push({
|
items.push({
|
||||||
@ -52,7 +56,7 @@ export default {
|
|||||||
action: () => {
|
action: () => {
|
||||||
window.open(media.src, "_blank")
|
window.open(media.src, "_blank")
|
||||||
control.close()
|
control.close()
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
items.push({
|
items.push({
|
||||||
@ -61,10 +65,10 @@ export default {
|
|||||||
action: () => {
|
action: () => {
|
||||||
download(media.src)
|
download(media.src)
|
||||||
control.close()
|
control.close()
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return items
|
return items
|
||||||
}
|
},
|
||||||
}
|
}
|
@ -9,13 +9,12 @@ const ContextMenu = (props) => {
|
|||||||
const [visible, setVisible] = React.useState(true)
|
const [visible, setVisible] = React.useState(true)
|
||||||
const { items = [], cords, clickedComponent, ctx } = props
|
const { items = [], cords, clickedComponent, ctx } = props
|
||||||
|
|
||||||
async function onClose() {
|
|
||||||
setVisible(false)
|
|
||||||
props.unregisterOnClose(onClose)
|
|
||||||
}
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
props.registerOnClose(onClose)
|
if (props.fireWhenClosing) {
|
||||||
|
props.fireWhenClosing(() => {
|
||||||
|
setVisible(false)
|
||||||
|
})
|
||||||
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const handleItemClick = async (item) => {
|
const handleItemClick = async (item) => {
|
||||||
|
@ -1,21 +1,20 @@
|
|||||||
import React from "react"
|
import React from "react"
|
||||||
|
|
||||||
import { Core, EventBus } from "@ragestudio/vessel"
|
import { Core, EventBus } from "@ragestudio/vessel"
|
||||||
|
|
||||||
import ContextMenu from "./components/contextMenu"
|
import ContextMenu from "./components/contextMenu"
|
||||||
|
import DefaultContext from "@config/context-menu/default"
|
||||||
import DefaultContenxt from "@config/context-menu/default"
|
|
||||||
import PostCardContext from "@config/context-menu/post"
|
import PostCardContext from "@config/context-menu/post"
|
||||||
|
|
||||||
export default class ContextMenuCore extends Core {
|
export default class ContextMenuCore extends Core {
|
||||||
static namespace = "contextMenu"
|
static namespace = "contextMenu"
|
||||||
|
|
||||||
contexts = {
|
contexts = {
|
||||||
...DefaultContenxt,
|
...DefaultContext,
|
||||||
...PostCardContext,
|
...PostCardContext,
|
||||||
}
|
}
|
||||||
|
|
||||||
eventBus = new EventBus()
|
eventBus = new EventBus()
|
||||||
|
isMenuOpen = false
|
||||||
|
fireWhenClosing = null
|
||||||
|
|
||||||
async onInitialize() {
|
async onInitialize() {
|
||||||
if (app.isMobile) {
|
if (app.isMobile) {
|
||||||
@ -23,136 +22,190 @@ export default class ContextMenuCore extends Core {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener("contextmenu", this.handleEvent.bind(this))
|
document.addEventListener("contextmenu", this.handleEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleEvent(event) {
|
handleEvent = async (event) => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|
||||||
// get the cords of the mouse
|
// obtain cord of mouse
|
||||||
const x = event.clientX
|
const x = event.clientX
|
||||||
const y = event.clientY
|
const y = event.clientY
|
||||||
|
|
||||||
// get the component that was clicked
|
// get clicked component
|
||||||
const component = document.elementFromPoint(x, y)
|
const component = document.elementFromPoint(x, y)
|
||||||
|
|
||||||
// check if is clicking inside a context menu or a children inside a context menu
|
// check if right-clicked inside a context menu
|
||||||
if (component.classList.contains("contextMenu") || component.closest(".contextMenu")) {
|
if (
|
||||||
|
component.classList.contains("contextMenu") ||
|
||||||
|
component.closest(".contextMenu")
|
||||||
|
) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// gen items
|
||||||
const items = await this.generateItems(component)
|
const items = await this.generateItems(component)
|
||||||
|
|
||||||
if (!items) {
|
// if no items, abort
|
||||||
this.console.warn("No context menu items found, aborting")
|
if (!items || items.length === 0) {
|
||||||
|
this.console.error("No context menu items found, aborting")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// render menu
|
||||||
this.show({
|
this.show({
|
||||||
registerOnClose: (cb) => { this.eventBus.on("close", cb) },
|
cords: { x, y },
|
||||||
unregisterOnClose: (cb) => { this.eventBus.off("close", cb) },
|
|
||||||
cords: {
|
|
||||||
x,
|
|
||||||
y,
|
|
||||||
},
|
|
||||||
clickedComponent: component,
|
clickedComponent: component,
|
||||||
items: items,
|
items: items,
|
||||||
|
fireWhenClosing: (fn) => {
|
||||||
|
this.fireWhenClosing = fn
|
||||||
|
},
|
||||||
ctx: {
|
ctx: {
|
||||||
close: this.onClose,
|
close: this.close,
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
registerContext = async (element, context) => {
|
registerContext = (element, context) => {
|
||||||
this.contexts[element] = context
|
this.contexts[element] = context
|
||||||
}
|
}
|
||||||
|
|
||||||
generateItems = async (element) => {
|
generateItems = async (element) => {
|
||||||
let items = []
|
let contextNames = []
|
||||||
|
let finalItems = []
|
||||||
|
|
||||||
// find the closest context with attribute (context-menu)
|
// search parent element with context-menu attribute
|
||||||
// if not found, use default context
|
|
||||||
const parentElement = element.closest("[context-menu]")
|
const parentElement = element.closest("[context-menu]")
|
||||||
|
|
||||||
let contexts = []
|
// if parent element exists, get context names from attribute
|
||||||
|
|
||||||
if (parentElement) {
|
if (parentElement) {
|
||||||
contexts = parentElement.getAttribute("context-menu") ?? []
|
const contextAttr = parentElement.getAttribute("context-menu") || ""
|
||||||
|
contextNames = contextAttr
|
||||||
|
.split(",")
|
||||||
|
.map((context) => context.trim())
|
||||||
|
|
||||||
if (typeof contexts === "string") {
|
// if context includes "ignore", no show context menu
|
||||||
contexts = contexts.split(",").map((context) => context.trim())
|
if (contextNames.includes("ignore")) {
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if context includes ignore, return null
|
|
||||||
if (contexts.includes("ignore")) {
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// check if context includes no-default, if not, push default context and remove no-default
|
// if context includes "no-default", no add default context
|
||||||
if (contexts.includes("no-default")) {
|
if (!contextNames.includes("no-default")) {
|
||||||
contexts = contexts.filter((context) => context !== "no-default")
|
contextNames.push("default-context")
|
||||||
} else {
|
} else {
|
||||||
contexts.push("default-context")
|
// remove "no-default" from context names
|
||||||
}
|
contextNames = contextNames.filter(
|
||||||
|
(context) => context !== "no-default",
|
||||||
for await (const [index, context] of contexts.entries()) {
|
|
||||||
let contextObject = this.contexts[context]
|
|
||||||
|
|
||||||
if (!contextObject) {
|
|
||||||
this.console.warn(`Context ${context} not found`)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof contextObject === "function") {
|
|
||||||
contextObject = await contextObject(items, parentElement, element, {
|
|
||||||
close: this.onClose,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// push divider
|
|
||||||
if (contexts.length > 0 && index !== contexts.length - 1) {
|
|
||||||
items.push({
|
|
||||||
type: "separator"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// fullfill each item with a correspondent index if missing declared
|
|
||||||
items = items.map((item, index) => {
|
|
||||||
if (!item.index) {
|
|
||||||
item.index = index
|
|
||||||
}
|
|
||||||
|
|
||||||
return item
|
|
||||||
})
|
|
||||||
|
|
||||||
// short items (if has declared index)
|
|
||||||
items = items.sort((a, b) => a.index - b.index)
|
|
||||||
|
|
||||||
// remove undefined items
|
|
||||||
items = items.filter((item) => item !== undefined)
|
|
||||||
|
|
||||||
return items
|
|
||||||
}
|
|
||||||
|
|
||||||
show = async (payload) => {
|
|
||||||
app.cores.window_mng.render(
|
|
||||||
"context-menu-portal",
|
|
||||||
React.createElement(ContextMenu, payload),
|
|
||||||
{
|
|
||||||
onClose: this.onClose,
|
|
||||||
createOrUpdate: true,
|
|
||||||
closeOnClickOutside: true,
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
onClose = async (delay = 200) => {
|
// process each context sequentially
|
||||||
this.eventBus.emit("close", delay)
|
for (let i = 0; i < contextNames.length; i++) {
|
||||||
|
const contextName = contextNames[i]
|
||||||
|
|
||||||
await new Promise((resolve) => {
|
// obtain contexted items
|
||||||
setTimeout(resolve, delay)
|
const contextItems = await this.getContextItems(
|
||||||
|
contextName,
|
||||||
|
parentElement,
|
||||||
|
element,
|
||||||
|
)
|
||||||
|
|
||||||
|
// if any contexted items exist, add them to the final items
|
||||||
|
if (contextItems && contextItems.length > 0) {
|
||||||
|
finalItems = finalItems.concat(contextItems)
|
||||||
|
|
||||||
|
// if is not the last context, add a separator
|
||||||
|
if (i < contextNames.length - 1) {
|
||||||
|
finalItems.push({
|
||||||
|
type: "separator",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// assign indices
|
||||||
|
finalItems = finalItems.map((item, index) => {
|
||||||
|
if (!item.index) {
|
||||||
|
item.index = index
|
||||||
|
}
|
||||||
|
return item
|
||||||
|
})
|
||||||
|
|
||||||
|
// sort items by index
|
||||||
|
finalItems.sort((a, b) => a.index - b.index)
|
||||||
|
|
||||||
|
// remove undefined items
|
||||||
|
finalItems = finalItems.filter((item) => item !== undefined)
|
||||||
|
|
||||||
|
return finalItems
|
||||||
|
}
|
||||||
|
|
||||||
|
getContextItems = async (contextName, parentElement, element) => {
|
||||||
|
const contextObject = this.contexts[contextName]
|
||||||
|
|
||||||
|
if (!contextObject) {
|
||||||
|
this.console.warn(`Context ${contextName} not found`)
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
// if is a function, execute it to get the elements
|
||||||
|
if (typeof contextObject === "function") {
|
||||||
|
try {
|
||||||
|
const newItems = []
|
||||||
|
|
||||||
|
// call the function
|
||||||
|
const result = await contextObject(
|
||||||
|
newItems,
|
||||||
|
parentElement,
|
||||||
|
element,
|
||||||
|
{ close: this.close },
|
||||||
|
)
|
||||||
|
|
||||||
|
return result || newItems
|
||||||
|
} catch (error) {
|
||||||
|
this.console.error(
|
||||||
|
`Error processing context [${contextName}] >`,
|
||||||
|
error,
|
||||||
|
)
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if it is an object (array), return it directly
|
||||||
|
return Array.isArray(contextObject) ? contextObject : []
|
||||||
|
}
|
||||||
|
|
||||||
|
show = async (props) => {
|
||||||
|
app.cores.window_mng.render(
|
||||||
|
"context-menu-portal",
|
||||||
|
React.createElement(ContextMenu, props),
|
||||||
|
{
|
||||||
|
useFrame: false,
|
||||||
|
createOrUpdate: true,
|
||||||
|
closeOnClickOutside: true, // sets default click outside behavior
|
||||||
|
onClose: this.onClose, // triggered when the menu is closing
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
this.isMenuOpen = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// triggered when the menu is closing
|
||||||
|
onClose = async (delay = 200) => {
|
||||||
|
if (typeof this.fireWhenClosing === "function") {
|
||||||
|
await this.fireWhenClosing()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (delay > 0) {
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, delay))
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isMenuOpen = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// close the menu
|
||||||
|
close = async () => {
|
||||||
|
app.cores.window_mng.close("context-menu-portal")
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user