Skip to main content
Glama
EditedContentContext.tsx10.2 kB
import { editDictionaryByKeyPath, getContentNodeByKeyPath, renameContentNodeByKeyPath, } from '@intlayer/core'; import { MessageKey } from '@intlayer/editor'; import { type ContentNode, type Dictionary, type KeyPath, type LocalDictionaryId, NodeType, } from '@intlayer/types'; import { type Component, createContext, type ParentProps, useContext, } from 'solid-js'; import { type DictionaryContent, useDictionariesRecord, } from './DictionariesRecordContext'; import { useCrossFrameMessageListener } from './useCrossFrameMessageListener'; import { useCrossFrameState } from './useCrossFrameState'; type EditedContentStateContextType = { editedContent: Record<Dictionary['key'], Dictionary> | undefined; }; const EditedContentStateContext = createContext< EditedContentStateContextType | undefined >(undefined); export const usePostEditedContentState = <S,>( onEventTriggered?: (data: S) => void ) => useCrossFrameMessageListener( `${MessageKey.INTLAYER_EDITED_CONTENT_CHANGED}/post`, onEventTriggered ); export const useGetEditedContentState = <S,>( onEventTriggered?: (data: S) => void ) => useCrossFrameMessageListener( `${MessageKey.INTLAYER_EDITED_CONTENT_CHANGED}/get`, onEventTriggered ); type EditedContentActionsContextType = { setEditedContentState: ( editedContent: Record<Dictionary['key'], Dictionary> ) => void; setEditedDictionary: ( newValue: Dictionary | ((prev?: Dictionary) => Dictionary) ) => void; setEditedContent: ( dictionaryKey: Dictionary['key'], newValue: Dictionary['content'] ) => void; addEditedContent: ( dictionaryKey: Dictionary['key'], newValue: ContentNode<any>, keyPath?: KeyPath[], overwrite?: boolean ) => void; renameEditedContent: ( dictionaryKey: Dictionary['key'], newKey: KeyPath['key'], keyPath?: KeyPath[] ) => void; removeEditedContent: ( dictionaryKey: Dictionary['key'], keyPath: KeyPath[] ) => void; restoreEditedContent: (dictionaryKey: Dictionary['key']) => void; clearEditedDictionaryContent: (dictionaryKey: Dictionary['key']) => void; clearEditedContent: () => void; getEditedContentValue: ( localDictionaryIdOrKey: LocalDictionaryId | Dictionary['key'] | string, keyPath: KeyPath[] ) => ContentNode | undefined; }; const EditedContentActionsContext = createContext< EditedContentActionsContextType | undefined >(undefined); export const EditedContentProvider: Component<ParentProps> = (props) => { const { localeDictionaries } = useDictionariesRecord(); const [editedContent, setEditedContentState] = useCrossFrameState<DictionaryContent>( MessageKey.INTLAYER_EDITED_CONTENT_CHANGED ); const setEditedDictionary = ( newValue: Dictionary | ((prev?: Dictionary) => Dictionary) ) => { if (typeof newValue === 'function') { setEditedContentState((prev) => { const currentDict = prev?.[Object.keys(prev || {})[0]]; const updatedDictionary = ( newValue as (prev?: Dictionary) => Dictionary )(currentDict); return { ...prev, [updatedDictionary.key]: updatedDictionary, }; }); } else { const updatedDictionary = newValue as Dictionary; setEditedContentState((prev) => ({ ...prev, [updatedDictionary.key]: updatedDictionary, })); } }; const setEditedContent = ( dictionaryKey: Dictionary['key'], newValue: Dictionary['content'] ) => { setEditedContentState((prev) => ({ ...prev, [dictionaryKey]: { ...prev?.[dictionaryKey], content: newValue, }, })); }; const addEditedContent = ( dictionaryKey: Dictionary['key'], newValue: ContentNode, keyPath: KeyPath[] = [], overwrite: boolean = true ) => { setEditedContentState((prev) => { // Get the starting content: edited version if available, otherwise a deep copy of the original const originalContent = localeDictionaries[dictionaryKey]?.content; const currentContent = structuredClone( prev?.[dictionaryKey]?.content ?? originalContent ); let newKeyPath = keyPath; if (!overwrite) { // Find a unique key based on the keyPath provided let index = 0; const otherKeyPath = keyPath.slice(0, -1); const lastKeyPath: KeyPath = keyPath[keyPath.length - 1]; let finalKey = lastKeyPath.key; // Loop until we find a key that does not exist while ( typeof getContentNodeByKeyPath(currentContent, newKeyPath) !== 'undefined' ) { index++; finalKey = index === 0 ? lastKeyPath.key : `${lastKeyPath.key} (${index})`; newKeyPath = [ ...otherKeyPath, { ...lastKeyPath, key: finalKey } as KeyPath, ]; } } const updatedContent = editDictionaryByKeyPath( currentContent, newKeyPath, newValue ); return { ...prev, [dictionaryKey]: { ...prev?.[dictionaryKey], content: updatedContent as Dictionary['content'], }, }; }); }; const renameEditedContent = ( dictionaryKey: Dictionary['key'], newKey: KeyPath['key'], keyPath: KeyPath[] = [] ) => { setEditedContentState((prev) => { // Retrieve the base content: use edited version if available, otherwise deep copy of original const originalContent = localeDictionaries[dictionaryKey]?.content; const currentContent = structuredClone( prev?.[dictionaryKey]?.content ?? originalContent ); const contentWithNewField = renameContentNodeByKeyPath( currentContent, newKey, keyPath ); return { ...prev, [dictionaryKey]: { ...prev?.[dictionaryKey], content: contentWithNewField as Dictionary['content'], }, }; }); }; const removeEditedContent = ( dictionaryKey: Dictionary['key'], keyPath: KeyPath[] ) => { setEditedContentState((prev) => { // Get the starting content: edited version if available, otherwise a deep copy of the original const originalContent = localeDictionaries[dictionaryKey]?.content; const currentContent = structuredClone( prev?.[dictionaryKey]?.content ?? originalContent ); // Deep clone to avoid mutation const contentWithoutField = structuredClone(currentContent); // Navigate to the parent of the target and delete the specified field const parentKeyPath = keyPath.slice(0, -1); const targetKey = keyPath[keyPath.length - 1]?.key; if (targetKey) { const parentNode = getContentNodeByKeyPath( contentWithoutField, parentKeyPath.filter((key) => key.type !== NodeType.Translation) ); if ( parentNode && typeof parentNode === 'object' && targetKey in parentNode ) { delete (parentNode as any)[targetKey]; } } return { ...prev, [dictionaryKey]: { ...prev?.[dictionaryKey], content: contentWithoutField as Dictionary['content'], }, }; }); }; const restoreEditedContent = (dictionaryKey: Dictionary['key']) => { setEditedContentState((prev) => { const newEditedContent = { ...prev }; delete newEditedContent[dictionaryKey]; return newEditedContent; }); }; const clearEditedDictionaryContent = (dictionaryKey: Dictionary['key']) => { setEditedContentState((prev) => ({ ...prev, [dictionaryKey]: { ...prev?.[dictionaryKey], content: {}, }, })); }; const clearEditedContent = () => { setEditedContentState({}); }; const getEditedContentValue = ( localDictionaryIdOrKey: LocalDictionaryId | Dictionary['key'] | string, keyPath: KeyPath[] ): ContentNode | undefined => { const editedContentValue = editedContent(); if (!editedContentValue) return undefined; const filteredKeyPath = keyPath.filter( (key) => key.type !== NodeType.Translation ); const isDictionaryId = localDictionaryIdOrKey.includes(':local:') || localDictionaryIdOrKey.includes(':remote:'); if (isDictionaryId) { const currentContent = editedContentValue?.[localDictionaryIdOrKey as LocalDictionaryId] ?.content ?? {}; const contentNode = getContentNodeByKeyPath( currentContent, filteredKeyPath ); return contentNode; } const filteredDictionariesLocalId = Object.keys(editedContentValue).filter( (key) => key.startsWith(`${localDictionaryIdOrKey}:`) ); for (const localDictionaryId of filteredDictionariesLocalId) { const currentContent = editedContentValue?.[localDictionaryId as LocalDictionaryId]?.content ?? {}; const contentNode = getContentNodeByKeyPath( currentContent, filteredKeyPath ); if (contentNode) return contentNode; } return undefined; }; const stateValue = { editedContent: editedContent(), }; const actionValue = { setEditedContentState, setEditedDictionary, setEditedContent, addEditedContent, renameEditedContent, removeEditedContent, restoreEditedContent, clearEditedDictionaryContent, clearEditedContent, getEditedContentValue, }; return ( <EditedContentStateContext.Provider value={stateValue}> <EditedContentActionsContext.Provider value={actionValue}> {props.children} </EditedContentActionsContext.Provider> </EditedContentStateContext.Provider> ); }; export const useEditedContentActions = () => useContext(EditedContentActionsContext); export const useEditedContent = () => { const actionsContext = useEditedContentActions(); const statesContext = useContext(EditedContentStateContext); if (!statesContext) { throw new Error( 'useEditedContent must be used within a EditedContentProvider' ); } return { ...statesContext, ...actionsContext }; };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/aymericzip/intlayer'

If you have feedback or need assistance with the MCP directory API, please join our Discord server