diff --git a/packages/docs/src/.vitepress/config.ts b/packages/docs/src/.vitepress/config.ts
index 14bf0dc..5cb5a60 100644
--- a/packages/docs/src/.vitepress/config.ts
+++ b/packages/docs/src/.vitepress/config.ts
@@ -259,5 +259,9 @@ export default withMermaid({
target: 'esnext',
},
},
+ resolve: {
+ // Configure to use browser version of @xocash/stack
+ conditions: ['browser', 'import', 'module', 'default'],
+ },
},
});
diff --git a/packages/docs/src/.vitepress/theme/components/CodeSandbox.vue b/packages/docs/src/.vitepress/theme/components/CodeSandbox.vue
index 60fc047..2fe7027 100644
--- a/packages/docs/src/.vitepress/theme/components/CodeSandbox.vue
+++ b/packages/docs/src/.vitepress/theme/components/CodeSandbox.vue
@@ -2,7 +2,7 @@
-
+
@@ -51,6 +51,8 @@ import iframeTemplateHtml from './CodeSandboxTemplate.html?raw';
import CodeEditor from './CodeEditor.vue';
import { ImportedLib, useXOCashLibs } from '../utils/useXOCashLibs';
+import { StorageLocalStorage, BaseStore } from '@xocash/stack';
+
const { isDark } = useData();
const xocashLibs = useXOCashLibs();
@@ -62,6 +64,7 @@ export type CodeSandboxProps = {
outputHeight?: string;
hideRunButton?: boolean;
libs?: Record
;
+ saveable?: boolean;
}
// Define props for the component
@@ -73,6 +76,7 @@ const props = withDefaults(defineProps(), {
outputHeight: 'auto',
hideRunButton: false,
libs: (): Record => ({}),
+ saveable: false,
});
// Define types for console logs
@@ -97,6 +101,10 @@ const showLogs = ref(false);
const sandboxIframe = ref(null);
const editorRef = ref | null>(null);
+const sandboxStorage = ref(null);
+const sandboxStore = ref(null);
+let saveTimeout: NodeJS.Timeout | null = null;
+
// Computed styles based on props
const sandboxStyle = computed(() => ({
height: props.fullHeight ? '100%' : props.height,
@@ -313,25 +321,26 @@ const handleMessage = (event: MessageEvent) => {
};
// Lifecycle hooks
-onMounted(() => {
- // Check if there's code in the URL hash fragment
- if (window.location.hash && window.location.hash.startsWith('#code=')) {
- const codeParam = window.location.hash.substring(6); // Remove '#code='
+onMounted(async () => {
+ sandboxStorage.value = await StorageLocalStorage.createOrOpen('xoCodeSandbox_V0.0.1');
+ sandboxStore.value = await sandboxStorage.value.createOrGetStore('codeSandbox');
- // If code exists in URL hash, use it
- try {
- // Always assume Base64 encoding
- code.value = decodeURIComponent(escape(atob(decodeURIComponent(codeParam))));
- } catch (error) {
- console.error('Failed to decode URL hash parameter:', error);
- logs.value.push({
- type: 'error',
- content: 'Failed to decode URL hash parameter: ' + error
- });
- // Fallback to slot content if URL decoding fails
- loadCodeFromSlot();
+ const fromCodeParam = (): string | undefined => {
+ if (window.location.hash && window.location.hash.startsWith('#code=')) {
+ const codeParam = window.location.hash.substring(6); // Remove '#code='
+
+ try {
+ // Always assume Base64 encoding
+ return decodeURIComponent(escape(atob(decodeURIComponent(codeParam))));
+ } catch (error) {
+ console.error('Failed to decode URL hash parameter:', error);
+ logs.value.push({
+ type: 'error',
+ content: 'Failed to decode URL hash parameter: ' + error
+ });
+ }
}
- } else {
+
// Otherwise check query parameters (for backward compatibility)
const urlParams = new URLSearchParams(window.location.search);
const queryCodeParam = urlParams.get('code');
@@ -339,19 +348,68 @@ onMounted(() => {
if (queryCodeParam) {
try {
// Try to decode from Base64
- code.value = decodeURIComponent(escape(atob(decodeURIComponent(queryCodeParam))));
+ return decodeURIComponent(escape(atob(decodeURIComponent(queryCodeParam))));
} catch (error) {
console.error('Failed to decode URL query parameter:', error);
- // Fallback to slot content
- loadCodeFromSlot();
}
- } else {
- // No code in hash or query, load from slot
- loadCodeFromSlot();
}
+ return undefined;
}
+ const fromCodeSlot = (): string | undefined => {
+ // Try to read the code from the code slot
+ if (codeRef.value) {
+ return codeRef.value.innerText.trim();
+ }
+ return undefined;
+ }
+
+ const fromSessionStorage = (): string | undefined => {
+ if (!props.saveable) {
+ return undefined;
+ }
+
+ // Try to read the code from session storage
+ const sessionStorageKey = 'xoCodeSandbox_V0.0.1';
+ const sessionStorage = window.sessionStorage.getItem(sessionStorageKey);
+ if (!sessionStorage) {
+ return undefined;
+ }
+
+ // Parse the session storage data
+ const sessionStorageData = JSON.parse(sessionStorage);
+ if (sessionStorageData) {
+ return sessionStorageData.code;
+ }
+
+ return undefined;
+ }
+
+ const fromLocalStorage = async (): Promise => {
+ if (!props.saveable || !sandboxStore.value) {
+ return undefined;
+ }
+
+ // Try to read the code from local storage and update the sandbox code
+ const sandboxTemp = await sandboxStore.value.get<{ code: string }>('sandbox_temp');
+ if (sandboxTemp) {
+ return sandboxTemp.code;
+ }
+
+ return undefined;
+ }
+
+ // Try to load the code from the various sources
+ // 1. URL hash
+ // 2. Saveable ? Session storage
+ // 3. Saveable ? Local storage
+ // 4. Code slot
+ const importedCode = await Promise.all([fromCodeParam(), fromSessionStorage(), fromLocalStorage(), fromCodeSlot()]);
+ const codeToLoad = importedCode.find(code => code !== undefined);
+
+ code.value = codeToLoad || '';
+
// Get the selected version from the url if it exists
const urlParams = new URLSearchParams(window.location.search);
const versionParam = urlParams.get('version');
@@ -364,19 +422,55 @@ onMounted(() => {
window.addEventListener('message', handleMessage);
});
-// Function to load code from slot
-const loadCodeFromSlot = () => {
- if (codeRef.value) {
- const template = codeRef.value;
- if (template) {
- code.value = template.innerText.trim();
- }
+/**
+ * Saves the sandbox code to both local and session storage
+ * This function is debounced to avoid excessive saves during typing
+ */
+const saveSandbox = async () => {
+ if (!props.saveable) {
+ return;
}
-};
+
+ // Clear any existing timeout to debounce the save operation
+ if (saveTimeout) {
+ clearTimeout(saveTimeout);
+ }
+
+ // Define our different methods of saving the sandbox
+ const toLocalStorage = async () => {
+ // Save the code to the local storage
+ if (!sandboxStore.value) {
+ return;
+ }
+
+ await sandboxStore.value.set('sandbox_temp', {
+ code: code.value
+ });
+ }
+
+ const toSessionStorage = async () => {
+ const sessionStorageKey = 'xoCodeSandbox_V0.0.1';
+ window.sessionStorage.setItem(sessionStorageKey, JSON.stringify({
+ code: code.value,
+ }));
+ }
+
+ // Set a new timeout to save after 500ms of inactivity
+ saveTimeout = setTimeout(async () => {
+ await Promise.all([toLocalStorage(), toSessionStorage()]);
+ saveTimeout = null;
+ }, 500);
+}
onBeforeUnmount(() => {
// Clean up event listener
window.removeEventListener('message', handleMessage);
+
+ // Clean up any pending save timeout
+ if (saveTimeout) {
+ clearTimeout(saveTimeout);
+ saveTimeout = null;
+ }
});
// Expose our functions so that they can be called from parent components.
diff --git a/packages/docs/src/.vitepress/theme/components/Playground.vue b/packages/docs/src/.vitepress/theme/components/Playground.vue
index d7661ee..0d48606 100644
--- a/packages/docs/src/.vitepress/theme/components/Playground.vue
+++ b/packages/docs/src/.vitepress/theme/components/Playground.vue
@@ -23,6 +23,7 @@
hideRunButton
:libs="xocashLibs"
:isDark="isDark"
+ :saveable="true"
>