v203 - 5е67

This commit is contained in:
Arseny Sazhin (via WeWeb) 2025-10-21 15:18:58 +03:00
parent 8a7334f7c1
commit 580b7b3185
53 changed files with 3042 additions and 124 deletions

View File

@ -1 +1 @@
{"name":"weweb-front","version":"4.0.0","private":true,"type":"module","scripts":{"serve":"vite","build":"vite build","postbuild":"node ./postbuild.js"},"dependencies":{"@vueuse/head":"2.0.0","axios":"1.9.0","html-escaper":"3.0.3","lodash":"4.17.21","pinia":"3.0.2","short-unique-id":"5.3.2","tiny-emitter":"2.1.0","uuid":"11.1.0","vue":"3.5.13","vue-cookie-next":"1.3.0","vue-meta":"2.4.0","vue-router":"4.5.1","vuex":"4.1.0","dayjs":"1.11.0","@supabase/supabase-js":"2.50.3","qs":"^6.14.0","@vueform/multiselect":"2.6.2","vue-ellipse-progress":"^2.1.1","@vueuse/core":"^13.0.0","diff":"^5.1.0","animejs":"^3.2.2","marked":"^14.1.2","medium-zoom":"^1.0.6","prismjs":"^1.23.0","@vuepic/vue-datepicker":"3.6.8","date-fns":"4.1.0","@tiptap/extension-color":"2.8.0","@tiptap/extension-image":"2.8.0","@tiptap/extension-link":"2.8.0","@tiptap/extension-mathematics":"2.26.1","@tiptap/extension-mention":"2.8.0","@tiptap/extension-placeholder":"2.8.0","@tiptap/extension-table":"2.8.0","@tiptap/extension-table-cell":"2.8.0","@tiptap/extension-table-header":"2.8.0","@tiptap/extension-table-row":"2.8.0","@tiptap/extension-task-item":"2.8.0","@tiptap/extension-task-list":"2.8.0","@tiptap/extension-text-align":"2.8.0","@tiptap/extension-text-style":"2.8.0","@tiptap/extension-underline":"2.8.0","@tiptap/pm":"2.8.0","@tiptap/starter-kit":"2.8.0","@tiptap/suggestion":"2.8.0","@tiptap/vue-3":"2.8.0","katex":"^0.16.22","prosemirror-commands":"1.5.0","prosemirror-dropcursor":"1.5.0","prosemirror-gapcursor":"1.3.1","prosemirror-history":"1.3.0","prosemirror-keymap":"1.2.0","prosemirror-schema-list":"1.2.2","tiptap-markdown":"^0.8.10","lodash-es":"^4.17.21"},"devDependencies":{"@vitejs/plugin-vue":"5.2.4","autoprefixer":"10.4.21","handlebars":"4.7.8","sass-embedded":"1.89.0","vite":"6.3.5","vite-plugin-node-polyfills":"0.23.0"},"browserslist":["last 3 years"]}
{"name":"weweb-front","version":"4.0.0","private":true,"type":"module","scripts":{"serve":"vite","build":"vite build","postbuild":"node ./postbuild.js"},"dependencies":{"@vueuse/head":"2.0.0","axios":"1.12.2","html-escaper":"3.0.3","lodash":"4.17.21","pinia":"3.0.2","short-unique-id":"5.3.2","tiny-emitter":"2.1.0","uuid":"11.1.0","vue":"3.5.13","vue-cookie-next":"1.3.0","vue-meta":"2.4.0","vue-router":"4.5.1","vuex":"4.1.0","dayjs":"1.11.0","qs":"^6.14.0","@supabase/supabase-js":"2.50.3","@vueform/multiselect":"2.6.2","vue-ellipse-progress":"^2.1.1","@vueuse/core":"^13.0.0","diff":"^5.1.0","animejs":"^3.2.2","marked":"^14.1.2","medium-zoom":"^1.0.6","prismjs":"^1.23.0","@vuepic/vue-datepicker":"3.6.8","date-fns":"4.1.0","@tiptap/extension-color":"2.8.0","@tiptap/extension-image":"2.8.0","@tiptap/extension-link":"2.8.0","@tiptap/extension-mathematics":"2.26.1","@tiptap/extension-mention":"2.8.0","@tiptap/extension-placeholder":"2.8.0","@tiptap/extension-table":"2.8.0","@tiptap/extension-table-cell":"2.8.0","@tiptap/extension-table-header":"2.8.0","@tiptap/extension-table-row":"2.8.0","@tiptap/extension-task-item":"2.8.0","@tiptap/extension-task-list":"2.8.0","@tiptap/extension-text-align":"2.8.0","@tiptap/extension-text-style":"2.8.0","@tiptap/extension-underline":"2.8.0","@tiptap/pm":"2.8.0","@tiptap/starter-kit":"2.8.0","@tiptap/suggestion":"2.8.0","@tiptap/vue-3":"2.8.0","katex":"^0.16.22","prosemirror-commands":"1.5.0","prosemirror-dropcursor":"1.5.0","prosemirror-gapcursor":"1.3.1","prosemirror-history":"1.3.0","prosemirror-keymap":"1.2.0","prosemirror-schema-list":"1.2.2","tiptap-markdown":"^0.8.10","lodash-es":"^4.17.21"},"devDependencies":{"@vitejs/plugin-vue":"5.2.4","autoprefixer":"10.4.21","handlebars":"4.7.8","sass-embedded":"1.89.0","vite":"6.3.5","vite-plugin-node-polyfills":"0.23.0"},"browserslist":["last 3 years"]}

View File

@ -33,6 +33,12 @@
"publicData": {
"apiKey": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ewogICAgInJvbGUiOiAiYW5vbiIsCiAgICAiaXNzIjogInN1cGFiYXNlIiwKICAgICJpYXQiOiAxNzQ1NDQyMDAwLAogICAgImV4cCI6IDE5MDMyMDg0MDAKfQ.6o8FlA2X8jsM4lUKF1mqKSC-v_GX5iE0dY20b6x8bnw",
"projectUrl": "https://sb.meetgu.ru",
"environments": {
"production": {
"apiKey": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ewogICAgInJvbGUiOiAiYW5vbiIsCiAgICAiaXNzIjogInN1cGFiYXNlIiwKICAgICJpYXQiOiAxNzQ1NDQyMDAwLAogICAgImV4cCI6IDE5MDMyMDg0MDAKfQ.6o8FlA2X8jsM4lUKF1mqKSC-v_GX5iE0dY20b6x8bnw",
"projectUrl": "https://sb.meetgu.ru"
}
},
"realtimeTables": {
"shop": true,
"test": true,

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
{"cacheVersion":202,"page":{"id":"cf9f551f-e733-4934-a682-535575cb7c70","paths":{"en":"promo","default":"promo"},"cmsDataSetPath":null,"workflows":[]},"sections":{"4a2188f6-d98c-4150-9ba7-5918c72f3421":{"uid":"4a2188f6-d98c-4150-9ba7-5918c72f3421","linkId":"416695e7-e54e-4897-9f9a-befeb223f1b0","_state":{"style":{"default":{}}},"content":{"default":{"wwObjects":[],"_ww-layout_alignItems":"flex-start","_ww-layout_flexDirection":"column"}},"sectionBaseId":"99586bd3-2b15-4d6b-a025-6a50d07ca845","sectionTitle":"Section"}},"wwObjects":{},"collections":[],"variables":[],"workflows":[],"formulas":[],"libraryComponents":[]}
{"cacheVersion":203,"page":{"id":"cf9f551f-e733-4934-a682-535575cb7c70","paths":{"en":"promo","default":"promo"},"cmsDataSetPath":null,"workflows":[]},"sections":{"4a2188f6-d98c-4150-9ba7-5918c72f3421":{"uid":"4a2188f6-d98c-4150-9ba7-5918c72f3421","linkId":"416695e7-e54e-4897-9f9a-befeb223f1b0","_state":{"style":{"default":{}}},"content":{"default":{"wwObjects":[],"_ww-layout_alignItems":"flex-start","_ww-layout_flexDirection":"column"}},"sectionBaseId":"99586bd3-2b15-4d6b-a025-6a50d07ca845","sectionTitle":"Section"}},"wwObjects":{},"collections":[],"variables":[],"workflows":[],"formulas":[],"libraryComponents":[]}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
{"name":"Образовательная платформа Meetguru","short_name":"Образовательная платформа Meetguru","icons":[{"src":"images/48-favicon.png?_wwcv=202","type":"image/png","sizes":"48x48"},{"src":"images/72-favicon.png?_wwcv=202","type":"image/png","sizes":"72x72"},{"src":"images/96-favicon.png?_wwcv=202","type":"image/png","sizes":"96x96"},{"src":"images/128-favicon.png?_wwcv=202","type":"image/png","sizes":"128x128"},{"src":"images/144-favicon.png?_wwcv=202","type":"image/png","sizes":"144x144"},{"src":"images/152-favicon.png?_wwcv=202","type":"image/png","sizes":"152x152"},{"src":"images/192-favicon.png?_wwcv=202","type":"image/png","sizes":"192x192"},{"src":"images/256-favicon.png?_wwcv=202","type":"image/png","sizes":"256x256"},{"src":"images/384-favicon.png?_wwcv=202","type":"image/png","sizes":"384x384"},{"src":"images/512-favicon.png?_wwcv=202","type":"image/png","sizes":"512x512"}],"start_url":"/","display":"fullscreen","scope":"/","background_color":"#FFFFFF","theme_color":"#FFFFFF"}
{"name":"Образовательная платформа Meetguru","short_name":"Образовательная платформа Meetguru","icons":[{"src":"images/48-favicon.png?_wwcv=203","type":"image/png","sizes":"48x48"},{"src":"images/72-favicon.png?_wwcv=203","type":"image/png","sizes":"72x72"},{"src":"images/96-favicon.png?_wwcv=203","type":"image/png","sizes":"96x96"},{"src":"images/128-favicon.png?_wwcv=203","type":"image/png","sizes":"128x128"},{"src":"images/144-favicon.png?_wwcv=203","type":"image/png","sizes":"144x144"},{"src":"images/152-favicon.png?_wwcv=203","type":"image/png","sizes":"152x152"},{"src":"images/192-favicon.png?_wwcv=203","type":"image/png","sizes":"192x192"},{"src":"images/256-favicon.png?_wwcv=203","type":"image/png","sizes":"256x256"},{"src":"images/384-favicon.png?_wwcv=203","type":"image/png","sizes":"384x384"},{"src":"images/512-favicon.png?_wwcv=203","type":"image/png","sizes":"512x512"}],"start_url":"/","display":"fullscreen","scope":"/","background_color":"#FFFFFF","theme_color":"#FFFFFF"}

View File

@ -1,4 +1,4 @@
const version = 202;
const version = 203;
self.addEventListener('install', event => {
// eslint-disable-next-line no-console
console.log(`Service worker v${version} installed`);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,24 +1,45 @@
<template>
<div class="flex items-center body-2 mb-2">
<wwEditorIcon name="database" class="mr-2" />
<div class="mr-2 content-secondary">project</div>
<div class="text-ellipsis">{{ settings.publicData.projectUrl }}</div>
<div v-if="isMultiEnv">
<!-- Multi-environment summary -->
<div v-for="(env, envName) in activeEnvironments" :key="envName" class="mb-3">
<div class="label-sm content-secondary mb-1">{{ getEnvLabel(envName) }}</div>
<div class="flex items-center body-2 mb-1">
<wwEditorIcon name="database" class="mr-2" small />
<div class="text-ellipsis">{{ env.projectUrl }}</div>
</div>
<div v-if="env.branchSlug" class="flex items-center body-2 mb-1">
<wwEditorIcon name="16/branch" class="mr-2" small />
<div class="text-ellipsis">{{ env.branchSlug }}</div>
</div>
<div v-if="env.customDomain" class="flex items-center body-2 mb-1">
<wwEditorIcon name="globe" class="mr-2" small />
<div class="text-ellipsis">{{ env.customDomain }}</div>
</div>
</div>
</div>
<div v-if="settings.publicData.customDomain" class="flex items-center body-2 mb-2">
<wwEditorIcon name="globe" class="mr-2" />
<div class="mr-2 content-secondary">custom domain</div>
<div class="text-ellipsis">{{ settings.publicData.customDomain }}</div>
</div>
<div class="flex items-center body-2 mb-2">
<wwEditorIcon name="key" class="mr-2" />
<div class="mr-2 content-secondary">public</div>
<div class="text-ellipsis">{{ settings.publicData.apiKey }}</div>
</div>
<div class="flex items-center body-2">
<wwEditorIcon name="key" class="mr-2" />
<div class="mr-2 content-secondary">service_role</div>
<div class="text-ellipsis" :class="{ 'text-dark-400': !settings.privateData.apiKey }">
{{ settings.privateData?.apiKey?.replace(/./g, '*') || 'No service role key provided' }}
<div v-else>
<!-- Legacy single environment summary -->
<div class="flex items-center body-2 mb-2">
<wwEditorIcon name="database" class="mr-2" />
<div class="mr-2 content-secondary">project</div>
<div class="text-ellipsis">{{ settings.publicData.projectUrl }}</div>
</div>
<div v-if="settings.publicData.customDomain" class="flex items-center body-2 mb-2">
<wwEditorIcon name="globe" class="mr-2" />
<div class="mr-2 content-secondary">custom domain</div>
<div class="text-ellipsis">{{ settings.publicData.customDomain }}</div>
</div>
<div class="flex items-center body-2 mb-2">
<wwEditorIcon name="key" class="mr-2" />
<div class="mr-2 content-secondary">public</div>
<div class="text-ellipsis">{{ settings.publicData.apiKey }}</div>
</div>
<div class="flex items-center body-2">
<wwEditorIcon name="key" class="mr-2" />
<div class="mr-2 content-secondary">service_role</div>
<div class="text-ellipsis" :class="{ 'text-dark-400': !settings.privateData.apiKey }">
{{ settings.privateData?.apiKey?.replace(/./g, '*') || 'No service role key provided' }}
</div>
</div>
</div>
</template>
@ -28,6 +49,40 @@ export default {
props: {
settings: { type: Object, required: true },
},
computed: {
isMultiEnv() {
return !!this.settings.publicData?.environments;
},
activeEnvironments() {
if (!this.isMultiEnv) return {};
const envs = {};
const environments = this.settings.publicData.environments;
// Only show configured environments
if (environments.production?.projectUrl) {
envs.production = environments.production;
}
if (environments.staging?.projectUrl) {
envs.staging = environments.staging;
}
if (environments.editor?.projectUrl) {
envs.editor = environments.editor;
}
return envs;
}
},
methods: {
getEnvLabel(envName) {
const labels = {
production: 'Production',
staging: 'Staging',
editor: 'Editor'
};
return labels[envName] || envName;
}
}
};
</script>
@ -37,4 +92,4 @@ export default {
text-overflow: ellipsis;
white-space: nowrap;
}
</style>
</style>

View File

@ -0,0 +1,167 @@
/**
* Gets the current Supabase settings for the active environment
* Automatically detects the environment and returns the appropriate configuration
* with intelligent fallback based on what's configured:
* - If only production is configured: all environments use production
* - If production + staging: editor uses staging, staging uses staging, production uses production
* - If production + editor: editor uses editor, staging uses production, production uses production
* - If all three: each uses its own configuration
*
* @param {string} [pluginName='supabaseAuth'] - The plugin to get settings for ('supabase' or 'supabaseAuth')
* @returns {Object} - Object with projectUrl, publicApiKey, privateApiKey, and other settings
*/
export function getCurrentSupabaseSettings(pluginName = 'supabaseAuth') {
// Get the plugin settings
const plugin = wwLib.wwPlugins[pluginName];
if (!plugin?.settings) {
return {
projectUrl: null,
publicApiKey: null,
privateApiKey: null,
customDomain: null,
environment: getCurrentEnvironment()
};
}
const settings = plugin.settings;
const currentEnv = getCurrentEnvironment();
// Try new multi-environment format first
if (settings?.publicData?.environments) {
const envs = settings.publicData.environments;
// Check which environments are configured
const hasProduction = !!(envs.production?.projectUrl && envs.production?.apiKey);
const hasStaging = !!(envs.staging?.projectUrl && envs.staging?.apiKey);
const hasEditor = !!(envs.editor?.projectUrl && envs.editor?.apiKey);
// Determine which environment config to use based on what's available
let targetEnv = null;
if (currentEnv === 'production') {
// Production always uses production (if configured)
if (hasProduction) targetEnv = 'production';
} else if (currentEnv === 'staging') {
// Staging prefers staging, falls back to production
if (hasStaging) targetEnv = 'staging';
else if (hasProduction) targetEnv = 'production';
} else if (currentEnv === 'editor') {
// Editor prefers editor, then staging, then production
if (hasEditor) targetEnv = 'editor';
else if (hasStaging) targetEnv = 'staging';
else if (hasProduction) targetEnv = 'production';
}
// Return the configuration for the target environment
if (targetEnv) {
const envConfig = envs[targetEnv];
const privateEnvConfig = settings.privateData?.environments?.[targetEnv];
// Single OAuth across environments: use global tokens only
const connectionMode = settings.privateData?.connectionMode || privateEnvConfig?.connectionMode || null;
const inferredProjectRef =
envConfig.projectUrl?.replace('https://', '').replace('.supabase.co', '') || envConfig.baseProjectRef || null;
return {
projectUrl: envConfig.customDomain || envConfig.projectUrl,
projectRef: inferredProjectRef,
baseProjectRef:
envConfig.baseProjectRef ||
inferredProjectRef ||
settings?.publicData?.projectUrl?.replace('https://', '').replace('.supabase.co', '') ||
null,
branch: envConfig.branch || null,
branchSlug: envConfig.branchSlug || null,
publicApiKey: envConfig.apiKey,
privateApiKey: privateEnvConfig?.apiKey || null,
customDomain: envConfig.customDomain || null,
connectionMode: connectionMode,
accessToken: settings.privateData?.accessToken || null,
refreshToken: settings.privateData?.refreshToken || null,
databasePassword: privateEnvConfig?.databasePassword || null,
connectionString: privateEnvConfig?.connectionString || null,
environment: currentEnv,
resolvedEnvironment: targetEnv // The actual environment config being used after fallback
};
}
}
// Fallback to legacy format (acts as production for all environments)
if (settings?.publicData?.projectUrl && settings?.publicData?.apiKey) {
const url = settings.publicData.projectUrl;
const projectRef = url?.replace('https://', '').replace('.supabase.co', '') || null;
return {
projectUrl: settings.publicData.customDomain || url,
projectRef,
baseProjectRef: projectRef,
branch: null,
branchSlug: null,
publicApiKey: settings.publicData.apiKey,
privateApiKey: settings.privateData?.apiKey || null,
customDomain: settings.publicData.customDomain || null,
connectionMode: settings.privateData?.connectionMode || null,
accessToken: settings.privateData?.accessToken || null,
refreshToken: settings.privateData?.refreshToken || null,
databasePassword: settings.privateData?.databasePassword || null,
connectionString: settings.privateData?.connectionString || null,
environment: currentEnv,
resolvedEnvironment: 'production' // Legacy format is treated as production
};
}
// No configuration found
return {
projectUrl: null,
projectRef: null,
baseProjectRef: null,
branch: null,
branchSlug: null,
publicApiKey: null,
privateApiKey: null,
customDomain: null,
environment: currentEnv,
resolvedEnvironment: null
};
}
/**
* Detects the current WeWeb environment
* @returns {string} - 'production', 'staging', or 'editor'
*/
export function getCurrentEnvironment() {
// Use WeWeb's official environment detection
const env = wwLib.getEnvironment();
// Map WeWeb's environment values to our three-environment model
// 'preview' refers to apps hosted on weweb-preview.io (WeWeb's default hosting)
// These are production apps without custom domains, so we map to 'production'
if (env === 'editor') return 'editor';
if (env === 'staging') return 'staging';
// 'preview' and 'production' both map to production
return 'production';
}
export function resolveRuntimeProjectUrl(config) {
if (!config) return null;
if (config.customDomain) return config.customDomain;
if (config.branch) return `https://${config.branch}.supabase.co`;
if (config.projectUrl) return config.projectUrl;
if (config.projectRef) return `https://${config.projectRef}.supabase.co`;
return null;
}
/**
* Checks if an environment is configured
* @param {Object} settings - Plugin settings
* @param {string} env - Environment to check
* @returns {boolean}
*/
export function isEnvironmentConfigured(settings, env) {
if (!settings?.publicData?.environments?.[env]) {
return false;
}
const envConfig = settings.publicData.environments[env];
return !!(envConfig.projectUrl && envConfig.apiKey);
}

View File

@ -1,4 +1,12 @@
import { createClient } from '@supabase/supabase-js';
import { getCurrentSupabaseSettings, resolveRuntimeProjectUrl } from './helpers/environmentConfig';
const maskForLog = value => {
if (!value) return null;
const str = String(value);
if (str.length <= 8) return str;
return `${str.slice(0, 4)}...${str.slice(-4)}`;
};
export default {
privateInstance: null,
@ -7,8 +15,10 @@ export default {
Plugin API
\================================================================================================*/
async _onLoad(settings) {
const config = getCurrentSupabaseSettings('supabaseAuth');
/* wwFront:start */
await this.load(settings.publicData.customDomain || settings.publicData.projectUrl, settings.publicData.apiKey);
await this.load(config.projectUrl, config.publicApiKey);
/* wwFront:end */
},
async _initAuth() {
@ -28,10 +38,14 @@ export default {
\================================================================================================*/
async load(projectUrl, publicApiKey, privateApiKey = null) {
try {
if (!projectUrl || !publicApiKey) return;
const config = getCurrentSupabaseSettings('supabaseAuth');
const runtimeProjectUrl = resolveRuntimeProjectUrl(config) || projectUrl;
const effectivePublicKey = config?.publicApiKey || publicApiKey;
const effectivePrivateKey = config?.privateApiKey || privateApiKey;
if (!runtimeProjectUrl || !effectivePublicKey) return;
this.publicInstance = createClient(projectUrl, publicApiKey, {
this.publicInstance = createClient(runtimeProjectUrl, effectivePublicKey, {
auth: {
storageKey: wwLib.wwWebsiteData.getInfo().id,
},

View File

@ -4,22 +4,19 @@ export default {
},
editor: {
settings: [
{
label: 'Connection',
icon: 'advanced',
edit: () => import('./src/components/Configuration/ConnectionEdit.vue'),
summary: () => import('./src/components/Configuration/ConnectionSummary.vue'),
getIsValid(settings) {
return !!settings.privateData.accessToken || settings.privateData.connectionMode === 'custom';
},
onSave: 'onSave',
},
{
label: 'Configuration',
icon: 'advanced',
edit: () => import('./src/components/Configuration/SettingsEdit.vue'),
edit: () => import('./src/components/Configuration/SettingsEditMultiEnv.vue'),
summary: () => import('./src/components/Configuration/SettingsSummary.vue'),
getIsValid(settings) {
// Check if using new multi-environment format
if (settings.publicData?.environments) {
// Production environment is required
return !!(settings.publicData.environments.production?.projectUrl &&
settings.publicData.environments.production?.apiKey);
}
// Legacy format validation
return !!settings.publicData.projectUrl && !!settings.publicData.apiKey;
},
onSave: 'onSave',

View File

@ -274,7 +274,10 @@ export default {
return [
...(this.websiteVariables ? Object.values(this.websiteVariables) : []),
...(this.componentVariables ? Object.values(this.componentVariables) : []),
];
].filter(variable => {
if (variable.componentType === 'libraryComponent') return false;
return true;
});
},
wwVariableOptions() {
return this.wwVariables

View File

@ -1,24 +1,45 @@
<template>
<div class="flex items-center body-2 mb-2">
<wwEditorIcon name="database" class="mr-2" />
<div class="mr-2 content-secondary">project</div>
<div class="text-ellipsis">{{ settings.publicData.projectUrl }}</div>
<div v-if="isMultiEnv">
<!-- Multi-environment summary -->
<div v-for="(env, envName) in activeEnvironments" :key="envName" class="mb-3">
<div class="label-sm content-secondary mb-1">{{ getEnvLabel(envName) }}</div>
<div class="flex items-center body-2 mb-1">
<wwEditorIcon name="database" class="mr-2" small />
<div class="text-ellipsis">{{ env.projectUrl }}</div>
</div>
<div v-if="env.branchSlug" class="flex items-center body-2 mb-1">
<wwEditorIcon name="16/branch" class="mr-2" small />
<div class="text-ellipsis">{{ env.branchSlug }}</div>
</div>
<div v-if="env.customDomain" class="flex items-center body-2 mb-1">
<wwEditorIcon name="globe" class="mr-2" small />
<div class="text-ellipsis">{{ env.customDomain }}</div>
</div>
</div>
</div>
<div v-if="settings.publicData.customDomain" class="flex items-center body-2 mb-2">
<wwEditorIcon name="globe" class="mr-2" />
<div class="mr-2 content-secondary">custom domain</div>
<div class="text-ellipsis">{{ settings.publicData.customDomain }}</div>
</div>
<div class="flex items-center body-2 mb-2">
<wwEditorIcon name="key" class="mr-2" />
<div class="mr-2 content-secondary">public</div>
<div class="text-ellipsis">{{ settings.publicData.apiKey }}</div>
</div>
<div class="flex items-center body-2">
<wwEditorIcon name="key" class="mr-2" />
<div class="mr-2 content-secondary">service_role</div>
<div class="text-ellipsis" :class="{ 'text-dark-400': !settings.privateData.apiKey }">
{{ settings.privateData?.apiKey?.replace(/./g, '*') || 'No service role key provided' }}
<div v-else>
<!-- Legacy single environment summary -->
<div class="flex items-center body-2 mb-2">
<wwEditorIcon name="database" class="mr-2" />
<div class="mr-2 content-secondary">project</div>
<div class="text-ellipsis">{{ settings.publicData.projectUrl }}</div>
</div>
<div v-if="settings.publicData.customDomain" class="flex items-center body-2 mb-2">
<wwEditorIcon name="globe" class="mr-2" />
<div class="mr-2 content-secondary">custom domain</div>
<div class="text-ellipsis">{{ settings.publicData.customDomain }}</div>
</div>
<div class="flex items-center body-2 mb-2">
<wwEditorIcon name="key" class="mr-2" />
<div class="mr-2 content-secondary">Public API Key</div>
<div class="text-ellipsis">{{ settings.publicData.apiKey }}</div>
</div>
<div class="flex items-center body-2">
<wwEditorIcon name="key" class="mr-2" />
<div class="mr-2 content-secondary">Service Role Key</div>
<div class="text-ellipsis" :class="{ 'text-dark-400': !settings.privateData.apiKey }">
{{ settings.privateData?.apiKey?.replace(/./g, '*') || 'No service role key provided' }}
</div>
</div>
</div>
</template>
@ -28,6 +49,40 @@ export default {
props: {
settings: { type: Object, required: true },
},
computed: {
isMultiEnv() {
return !!this.settings.publicData?.environments;
},
activeEnvironments() {
if (!this.isMultiEnv) return {};
const envs = {};
const environments = this.settings.publicData.environments;
// Only show configured environments
if (environments.production?.projectUrl) {
envs.production = environments.production;
}
if (environments.staging?.projectUrl) {
envs.staging = environments.staging;
}
if (environments.editor?.projectUrl) {
envs.editor = environments.editor;
}
return envs;
}
},
methods: {
getEnvLabel(envName) {
const labels = {
production: 'Production',
staging: 'Staging',
editor: 'Editor'
};
return labels[envName] || envName;
}
}
};
</script>

View File

@ -169,7 +169,10 @@ export default {
return [
...(this.websiteVariables ? Object.values(this.websiteVariables) : []),
...(this.componentVariables ? Object.values(this.componentVariables) : []),
];
].filter(variable => {
if (variable.componentType === 'libraryComponent') return false;
return true;
});
},
wwVariableOptions() {
return this.wwVariables

View File

@ -4,7 +4,7 @@
This feature allow your collections to be updated in realtime automcatically. You must enable realtime on your
tables both in WeWeb and in Supabase in order to use this feature.
</div>
<wwEditorFormRow label="Enable realtime table">
<wwEditorFormRow label="Enable realtime table" @togglePanel="handleTogglePanel">
<template #append-label>
<a class="ww-editor-link ml-2" href="https://supabase.com/docs/guides/api#realtime-api-1" target="_blank">
Find it here
@ -24,6 +24,8 @@
</template>
<script>
import { getCurrentSupabaseSettings } from '../../helpers/environmentConfig';
export default {
props: {
plugin: { type: Object, required: true },
@ -34,6 +36,7 @@ export default {
return {
isLoading: false,
definitions: {},
panelOpen: false,
};
},
computed: {
@ -47,11 +50,88 @@ export default {
return this.settings.publicData?.realtimeTables || {};
},
},
watch: {
'plugin.doc.definitions': {
handler(definitions) {
this.applyDefinitions(definitions);
},
immediate: true,
},
panelOpen(value) {
if (value) {
this.queueDocRefresh(200);
}
},
settings: {
handler() {
this.scheduleConfigWatch();
},
deep: true,
},
},
mounted() {
this.definitions = this.plugin?.doc?.definitions || {};
if (!this.settings.publicData?.realtimeTables) this.changeRealtimeTables({});
this.applyDefinitions(this.plugin?.doc?.definitions || {});
this.scheduleConfigWatch();
this.queueDocRefresh();
},
beforeUnmount() {
if (this._refreshTimeout) {
clearTimeout(this._refreshTimeout);
this._refreshTimeout = null;
}
},
methods: {
scheduleConfigWatch() {
const cfg = getCurrentSupabaseSettings('supabase');
const snapshot = {
projectUrl: cfg?.projectUrl || null,
baseProjectRef: cfg?.baseProjectRef || null,
branch: cfg?.branch || null,
branchSlug: cfg?.branchSlug || null,
};
const serialized = JSON.stringify(snapshot);
if (this._lastConfigSnapshot === serialized) return;
const previous = this._lastConfigSnapshot ? JSON.parse(this._lastConfigSnapshot) : null;
this._lastConfigSnapshot = serialized;
if (previous) {
this.queueDocRefresh(200);
}
},
handleTogglePanel(isOpen) {
this.panelOpen = isOpen;
},
queueDocRefresh(delay = 1000) {
if (this._refreshTimeout) clearTimeout(this._refreshTimeout);
this._refreshTimeout = setTimeout(async () => {
try {
this.isLoading = true;
await this.plugin.fetchDoc();
this.applyDefinitions(this.plugin?.doc?.definitions || {});
} catch (err) {
wwLib.wwLog.error(err);
} finally {
this.isLoading = false;
}
}, 1000);
},
applyDefinitions(definitions = {}) {
const normalized = definitions || {};
this.definitions = normalized;
const available = new Set(Object.keys(normalized));
const realtimeTables = { ...this.realtimeTables };
let mutated = false;
for (const key of Object.keys(realtimeTables)) {
if (!available.has(key)) {
delete realtimeTables[key];
mutated = true;
}
}
if (mutated) {
this.changeRealtimeTables(realtimeTables);
}
},
async fetchTables() {
try {
this.isLoading = true;

View File

@ -0,0 +1,167 @@
/**
* Gets the current Supabase settings for the active environment
* Automatically detects the environment and returns the appropriate configuration
* with intelligent fallback based on what's configured:
* - If only production is configured: all environments use production
* - If production + staging: editor uses staging, staging uses staging, production uses production
* - If production + editor: editor uses editor, staging uses production, production uses production
* - If all three: each uses its own configuration
*
* @param {string} [pluginName='supabase'] - The plugin to get settings for ('supabase' or 'supabaseAuth')
* @returns {Object} - Object with projectUrl, publicApiKey, privateApiKey, and other settings
*/
export function getCurrentSupabaseSettings(pluginName = 'supabase') {
// Get the plugin settings
const plugin = wwLib.wwPlugins[pluginName];
if (!plugin?.settings) {
return {
projectUrl: null,
publicApiKey: null,
privateApiKey: null,
customDomain: null,
environment: getCurrentEnvironment()
};
}
const settings = plugin.settings;
const currentEnv = getCurrentEnvironment();
// Try new multi-environment format first
if (settings?.publicData?.environments) {
const envs = settings.publicData.environments;
// Check which environments are configured
const hasProduction = !!(envs.production?.projectUrl && envs.production?.apiKey);
const hasStaging = !!(envs.staging?.projectUrl && envs.staging?.apiKey);
const hasEditor = !!(envs.editor?.projectUrl && envs.editor?.apiKey);
// Determine which environment config to use based on what's available
let targetEnv = null;
if (currentEnv === 'production') {
// Production always uses production (if configured)
if (hasProduction) targetEnv = 'production';
} else if (currentEnv === 'staging') {
// Staging prefers staging, falls back to production
if (hasStaging) targetEnv = 'staging';
else if (hasProduction) targetEnv = 'production';
} else if (currentEnv === 'editor') {
// Editor prefers editor, then staging, then production
if (hasEditor) targetEnv = 'editor';
else if (hasStaging) targetEnv = 'staging';
else if (hasProduction) targetEnv = 'production';
}
// Return the configuration for the target environment
if (targetEnv) {
const envConfig = envs[targetEnv];
const privateEnvConfig = settings.privateData?.environments?.[targetEnv];
// Single OAuth across environments: use global tokens only
const connectionMode = settings.privateData?.connectionMode || privateEnvConfig?.connectionMode || null;
const inferredProjectRef =
envConfig.projectUrl?.replace('https://', '').replace('.supabase.co', '') || envConfig.baseProjectRef || null;
return {
projectUrl: envConfig.customDomain || envConfig.projectUrl,
projectRef: inferredProjectRef,
baseProjectRef:
envConfig.baseProjectRef ||
inferredProjectRef ||
settings?.publicData?.projectUrl?.replace('https://', '').replace('.supabase.co', '') ||
null,
branch: envConfig.branch || null,
branchSlug: envConfig.branchSlug || null,
publicApiKey: envConfig.apiKey,
privateApiKey: privateEnvConfig?.apiKey || null,
customDomain: envConfig.customDomain || null,
connectionMode: connectionMode,
accessToken: settings.privateData?.accessToken || null,
refreshToken: settings.privateData?.refreshToken || null,
databasePassword: privateEnvConfig?.databasePassword || null,
connectionString: privateEnvConfig?.connectionString || null,
environment: currentEnv,
resolvedEnvironment: targetEnv // The actual environment config being used after fallback
};
}
}
// Fallback to legacy format (acts as production for all environments)
if (settings?.publicData?.projectUrl && settings?.publicData?.apiKey) {
const url = settings.publicData.projectUrl;
const projectRef = url?.replace('https://', '').replace('.supabase.co', '') || null;
return {
projectUrl: settings.publicData.customDomain || url,
projectRef,
baseProjectRef: projectRef,
branch: null,
branchSlug: null,
publicApiKey: settings.publicData.apiKey,
privateApiKey: settings.privateData?.apiKey || null,
customDomain: settings.publicData.customDomain || null,
connectionMode: settings.privateData?.connectionMode || null,
accessToken: settings.privateData?.accessToken || null,
refreshToken: settings.privateData?.refreshToken || null,
databasePassword: settings.privateData?.databasePassword || null,
connectionString: settings.privateData?.connectionString || null,
environment: currentEnv,
resolvedEnvironment: 'production' // Legacy format is treated as production
};
}
// No configuration found
return {
projectUrl: null,
projectRef: null,
baseProjectRef: null,
branch: null,
branchSlug: null,
publicApiKey: null,
privateApiKey: null,
customDomain: null,
environment: currentEnv,
resolvedEnvironment: null
};
}
/**
* Detects the current WeWeb environment
* @returns {string} - 'production', 'staging', or 'editor'
*/
export function getCurrentEnvironment() {
// Use WeWeb's official environment detection
const env = wwLib.getEnvironment();
// Map WeWeb's environment values to our three-environment model
// 'preview' refers to apps hosted on weweb-preview.io (WeWeb's default hosting)
// These are production apps without custom domains, so we map to 'production'
if (env === 'editor') return 'editor';
if (env === 'staging') return 'staging';
// 'preview' and 'production' both map to production
return 'production';
}
export function resolveRuntimeProjectUrl(config) {
if (!config) return null;
if (config.customDomain) return config.customDomain;
if (config.branch) return `https://${config.branch}.supabase.co`;
if (config.projectUrl) return config.projectUrl;
if (config.projectRef) return `https://${config.projectRef}.supabase.co`;
return null;
}
/**
* Checks if an environment is configured
* @param {Object} settings - Plugin settings
* @param {string} env - Environment to check
* @returns {boolean}
*/
export function isEnvironmentConfigured(settings, env) {
if (!settings?.publicData?.environments?.[env]) {
return false;
}
const envConfig = settings.publicData.environments[env];
return !!(envConfig.projectUrl && envConfig.apiKey);
}

View File

@ -1,6 +1,7 @@
import { createClient } from '@supabase/supabase-js';
import { generateFilter } from './helpers/filters';
import { getCurrentSupabaseSettings, resolveRuntimeProjectUrl } from './helpers/environmentConfig';
import {
buildQueryString,
@ -9,6 +10,13 @@ import {
executeStreamingInvocation,
} from './helpers/edgeFunction.js';
const maskForLog = value => {
if (!value) return null;
const str = String(value);
if (str.length <= 8) return str;
return `${str.slice(0, 4)}...${str.slice(-4)}`;
};
export default {
instance: null,
channels: {},
@ -16,9 +24,90 @@ export default {
Plugin API
\================================================================================================*/
async _onLoad(settings) {
await this.load(settings.publicData.customDomain || settings.publicData.projectUrl, settings.publicData.apiKey);
// Get configuration for current environment
let config = getCurrentSupabaseSettings('supabase');
let runtimeProjectUrl = resolveRuntimeProjectUrl(config);
await this.load(runtimeProjectUrl, config.publicApiKey);
this.subscribeTables(settings.publicData.realtimeTables || {});
},
async fetchProjectInfo(configOverride = null) {
const config = configOverride || getCurrentSupabaseSettings('supabase');
const projectUrl = config?.projectUrl;
const accessToken = config?.accessToken;
if (!accessToken || !projectUrl) return;
const params = this.getBranchQueryParams(config);
const { data: schemaData } = await this.requestAPI({ method: 'GET', path: '/schema', params });
const { data: edgeData } = await this.requestAPI({ method: 'GET', path: '/edge', params });
this.projectInfo = schemaData?.data;
this.projectInfo.edge = edgeData?.data;
wwLib.$emit('wwTopBar:supabase:refresh');
return this.projectInfo;
},
getBranchQueryParams(config = getCurrentSupabaseSettings('supabase')) {
if (!config) return undefined;
const params = {};
if (config.branchSlug) params.branch = config.branchSlug;
if (config.baseProjectRef) params.baseProjectRef = config.baseProjectRef;
return Object.keys(params).length ? params : undefined;
},
async onSave(settings) {
await this.syncSettings(settings);
// Get config for current environment
const config = getCurrentSupabaseSettings('supabase');
if (!config.projectUrl) return;
const runtimeProjectUrl = resolveRuntimeProjectUrl(config);
if (config.accessToken && config.projectUrl) {
await this.install();
await this.fetchProjectInfo(config);
}
if (wwLib.wwPlugins.supabaseAuth) {
// supabaseAuth will call syncInstance
const authConfig = getCurrentSupabaseSettings('supabaseAuth');
const authRuntimeUrl = resolveRuntimeProjectUrl(authConfig);
await wwLib.wwPlugins.supabaseAuth.load(
authRuntimeUrl,
authConfig.publicApiKey,
authConfig.privateApiKey
);
} else {
await this.load(runtimeProjectUrl, config.publicApiKey);
this.subscribeTables(settings.publicData.realtimeTables || {});
}
},
async requestAPI({ method, path, data, params }, retry = true) {
try {
return await wwAxios({
method,
url: `${wwLib.wwApiRequests._getPluginsUrl()}/designs/${
wwLib.$store.getters['websiteData/getDesignInfo'].id
}/supabase${path}`,
data,
params,
});
} catch (error) {
const isOauthToken = wwLib.wwPlugins.supabase.settings.privateData.accessToken?.startsWith('sbp_oauth');
if (retry && [401, 403].includes(error.response?.status) && isOauthToken) {
const { data } = await wwAxios.post(
`${wwLib.wwApiRequests._getPluginsUrl()}/designs/${
wwLib.$store.getters['websiteData/getDesignInfo'].id
}/supabase/refresh`
);
return await this.requestAPI({ method, path, data, params }, false);
}
wwLib.wwNotification.open({ text: 'Error while requesting the supabase project.', color: 'red' });
throw error;
}
},
/* wwEditor:end */
/* Called by supabase auth plugin
* Allow supabase to use the supabase auth instance when available
*/
@ -34,6 +123,15 @@ export default {
async _fetchCollection(collection) {
if (collection.mode === 'dynamic') {
try {
// Ensure we have an instance for the current environment
if (!this.instance) {
const config = getCurrentSupabaseSettings('supabase');
const runtimeProjectUrl = resolveRuntimeProjectUrl(config);
if (runtimeProjectUrl && config.publicApiKey) {
await this.load(runtimeProjectUrl, config.publicApiKey);
}
}
const fields =
collection.config.fieldsMode === 'guided'
? (collection.config.dataFields || []).join(', ')

View File

@ -4,22 +4,19 @@ export default {
},
editor: {
settings: [
{
label: 'Connection',
icon: 'advanced',
edit: () => import('./src/components/Configuration/ConnectionEdit.vue'),
summary: () => import('./src/components/Configuration/ConnectionSummary.vue'),
getIsValid(settings) {
return !!settings.privateData.accessToken || settings.privateData.connectionMode === 'custom';
},
onSave: 'onSave',
},
{
label: 'Configuration',
icon: 'advanced',
edit: () => import('./src/components/Configuration/SettingsEdit.vue'),
edit: () => import('./src/components/Configuration/SettingsEditMultiEnv.vue'),
summary: () => import('./src/components/Configuration/SettingsSummary.vue'),
getIsValid(settings) {
// Check if using new multi-environment format
if (settings.publicData?.environments) {
// Production environment is required
return !!(settings.publicData.environments.production?.projectUrl &&
settings.publicData.environments.production?.apiKey);
}
// Legacy format validation
return !!settings.publicData.projectUrl && !!settings.publicData.apiKey;
},
onSave: 'onSave',

View File

@ -3,9 +3,9 @@ import { getInheritedConfiguration } from '@/_common/helpers/configuration/confi
/* wwFront:start */
import plugin832d6f7a42c343f1a3ce9a678272f811 from '@/components/plugins/plugin-832d6f7a-42c3-43f1-a3ce-9a678272f811/ww-config.js';
import plugin1fa0dd685069436c9a7d3b54c340f1fa from '@/components/plugins/plugin-1fa0dd68-5069-436c-9a7d-3b54c340f1fa/ww-config.js';
import pluginf9ef41c31c534857855bf2f6a40b7186 from '@/components/plugins/plugin-f9ef41c3-1c53-4857-855b-f2f6a40b7186/ww-config.js';
import plugin2bd1c68831c5443eae2559aa5b6431fb from '@/components/plugins/plugin-2bd1c688-31c5-443e-ae25-59aa5b6431fb/ww-config.js';
import pluginf9ef41c31c534857855bf2f6a40b7186 from '@/components/plugins/plugin-f9ef41c3-1c53-4857-855b-f2f6a40b7186/ww-config.js';
import plugin1fa0dd685069436c9a7d3b54c340f1fa from '@/components/plugins/plugin-1fa0dd68-5069-436c-9a7d-3b54c340f1fa/ww-config.js';
import section99586bd32b154d6ba0256a50d07ca845 from '@/components/sections/section-99586bd3-2b15-4d6b-a025-6a50d07ca845/ww-config.js';
import wwobjectaeb78b9a6fb64c49931dfaedcfad67ba from '@/components/elements/element-aeb78b9a-6fb6-4c49-931d-faedcfad67ba/ww-config.js';
import wwobjectfd8c482f532c4aeba7ae6904a6b62a1b from '@/components/elements/element-fd8c482f-532c-4aeb-a7ae-6904a6b62a1b/ww-config.js';
@ -44,9 +44,9 @@ export const useComponentBasesStore = defineStore('componentBases', () => {
/* wwFront:start */
// eslint-disable-next-line no-undef
configurations = {'plugin-832d6f7a-42c3-43f1-a3ce-9a678272f811': getInheritedConfiguration({ ...plugin832d6f7a42c343f1a3ce9a678272f811, name: 'plugin-832d6f7a-42c3-43f1-a3ce-9a678272f811' }),
'plugin-1fa0dd68-5069-436c-9a7d-3b54c340f1fa': getInheritedConfiguration({ ...plugin1fa0dd685069436c9a7d3b54c340f1fa, name: 'plugin-1fa0dd68-5069-436c-9a7d-3b54c340f1fa' }),
'plugin-f9ef41c3-1c53-4857-855b-f2f6a40b7186': getInheritedConfiguration({ ...pluginf9ef41c31c534857855bf2f6a40b7186, name: 'plugin-f9ef41c3-1c53-4857-855b-f2f6a40b7186' }),
'plugin-2bd1c688-31c5-443e-ae25-59aa5b6431fb': getInheritedConfiguration({ ...plugin2bd1c68831c5443eae2559aa5b6431fb, name: 'plugin-2bd1c688-31c5-443e-ae25-59aa5b6431fb' }),
'plugin-f9ef41c3-1c53-4857-855b-f2f6a40b7186': getInheritedConfiguration({ ...pluginf9ef41c31c534857855bf2f6a40b7186, name: 'plugin-f9ef41c3-1c53-4857-855b-f2f6a40b7186' }),
'plugin-1fa0dd68-5069-436c-9a7d-3b54c340f1fa': getInheritedConfiguration({ ...plugin1fa0dd685069436c9a7d3b54c340f1fa, name: 'plugin-1fa0dd68-5069-436c-9a7d-3b54c340f1fa' }),
'section-99586bd3-2b15-4d6b-a025-6a50d07ca845': getInheritedConfiguration({ ...section99586bd32b154d6ba0256a50d07ca845, name: 'section-99586bd3-2b15-4d6b-a025-6a50d07ca845' }),
'wwobject-aeb78b9a-6fb6-4c49-931d-faedcfad67ba': getInheritedConfiguration({ ...wwobjectaeb78b9a6fb64c49931dfaedcfad67ba, name: 'wwobject-aeb78b9a-6fb6-4c49-931d-faedcfad67ba' }),
'wwobject-fd8c482f-532c-4aeb-a7ae-6904a6b62a1b': getInheritedConfiguration({ ...wwobjectfd8c482f532c4aeba7ae6904a6b62a1b, name: 'wwobject-fd8c482f-532c-4aeb-a7ae-6904a6b62a1b' }),

View File

@ -5,9 +5,9 @@ import { useIconsStore } from '@/pinia/icons';
/* wwFront:start */
// eslint-disable-next-line no-undef
import plugin_832d6f7a_42c3_43f1_a3ce_9a678272f811 from '@/components/plugins/plugin-832d6f7a-42c3-43f1-a3ce-9a678272f811/src/wwPlugin.js';
import plugin_1fa0dd68_5069_436c_9a7d_3b54c340f1fa from '@/components/plugins/plugin-1fa0dd68-5069-436c-9a7d-3b54c340f1fa/src/wwPlugin.js';
import plugin_f9ef41c3_1c53_4857_855b_f2f6a40b7186 from '@/components/plugins/plugin-f9ef41c3-1c53-4857-855b-f2f6a40b7186/src/wwPlugin.js';
import plugin_2bd1c688_31c5_443e_ae25_59aa5b6431fb from '@/components/plugins/plugin-2bd1c688-31c5-443e-ae25-59aa5b6431fb/src/wwPlugin.js';
import plugin_f9ef41c3_1c53_4857_855b_f2f6a40b7186 from '@/components/plugins/plugin-f9ef41c3-1c53-4857-855b-f2f6a40b7186/src/wwPlugin.js';
import plugin_1fa0dd68_5069_436c_9a7d_3b54c340f1fa from '@/components/plugins/plugin-1fa0dd68-5069-436c-9a7d-3b54c340f1fa/src/wwPlugin.js';
/* wwFront:end */
import { computed, reactive } from 'vue';
@ -50,9 +50,9 @@ export default {
/* wwFront:start */
// eslint-disable-next-line no-undef
wwLib.wwPluginHelper.registerPlugin('plugin-832d6f7a-42c3-43f1-a3ce-9a678272f811', plugin_832d6f7a_42c3_43f1_a3ce_9a678272f811);
wwLib.wwPluginHelper.registerPlugin('plugin-1fa0dd68-5069-436c-9a7d-3b54c340f1fa', plugin_1fa0dd68_5069_436c_9a7d_3b54c340f1fa);
wwLib.wwPluginHelper.registerPlugin('plugin-f9ef41c3-1c53-4857-855b-f2f6a40b7186', plugin_f9ef41c3_1c53_4857_855b_f2f6a40b7186);
wwLib.wwPluginHelper.registerPlugin('plugin-2bd1c688-31c5-443e-ae25-59aa5b6431fb', plugin_2bd1c688_31c5_443e_ae25_59aa5b6431fb);
wwLib.wwPluginHelper.registerPlugin('plugin-f9ef41c3-1c53-4857-855b-f2f6a40b7186', plugin_f9ef41c3_1c53_4857_855b_f2f6a40b7186);
wwLib.wwPluginHelper.registerPlugin('plugin-1fa0dd68-5069-436c-9a7d-3b54c340f1fa', plugin_1fa0dd68_5069_436c_9a7d_3b54c340f1fa);
/* wwFront:end */

View File

@ -12,20 +12,20 @@
<link rel="icon" type="image/x-icon" href="favicon.ico?_wwcv={{cacheVersion}}" />
<link rel="manifest" href="manifest.json?_wwcv=202" />
<link rel="manifest" href="manifest.json?_wwcv=203" />
<meta name="theme-color" content="" />
<link rel="apple-touch-icon" sizes="48x48" href="images/48-favicon.png?_wwcv=202">
<link rel="apple-touch-icon" sizes="72x72" href="images/72-favicon.png?_wwcv=202">
<link rel="apple-touch-icon" sizes="96x96" href="images/96-favicon.png?_wwcv=202">
<link rel="apple-touch-icon" sizes="128x128" href="images/128-favicon.png?_wwcv=202">
<link rel="apple-touch-icon" sizes="144x144" href="images/144-favicon.png?_wwcv=202">
<link rel="apple-touch-icon" sizes="152x152" href="images/152-favicon.png?_wwcv=202">
<link rel="apple-touch-icon" sizes="192x192" href="images/192-favicon.png?_wwcv=202">
<link rel="apple-touch-icon" sizes="256x256" href="images/256-favicon.png?_wwcv=202">
<link rel="apple-touch-icon" sizes="384x384" href="images/384-favicon.png?_wwcv=202">
<link rel="apple-touch-icon" sizes="512x512" href="images/512-favicon.png?_wwcv=202">
<link rel="apple-touch-icon" sizes="48x48" href="images/48-favicon.png?_wwcv=203">
<link rel="apple-touch-icon" sizes="72x72" href="images/72-favicon.png?_wwcv=203">
<link rel="apple-touch-icon" sizes="96x96" href="images/96-favicon.png?_wwcv=203">
<link rel="apple-touch-icon" sizes="128x128" href="images/128-favicon.png?_wwcv=203">
<link rel="apple-touch-icon" sizes="144x144" href="images/144-favicon.png?_wwcv=203">
<link rel="apple-touch-icon" sizes="152x152" href="images/152-favicon.png?_wwcv=203">
<link rel="apple-touch-icon" sizes="192x192" href="images/192-favicon.png?_wwcv=203">
<link rel="apple-touch-icon" sizes="256x256" href="images/256-favicon.png?_wwcv=203">
<link rel="apple-touch-icon" sizes="384x384" href="images/384-favicon.png?_wwcv=203">
<link rel="apple-touch-icon" sizes="512x512" href="images/512-favicon.png?_wwcv=203">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&display=swap" rel="preload" as="style" onload="this.onload=null;this.rel='stylesheet'">
<link href="/fonts/Phosphor/font.css?_wwcv=202" rel="preload" as="style" onload="this.onload=null;this.rel='stylesheet'">
<link href="/fonts/Phosphor/font.css?_wwcv=203" rel="preload" as="style" onload="this.onload=null;this.rel='stylesheet'">
<link href="https://fonts.googleapis.com/css2?family=Raleway:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap" rel="preload" as="style" onload="this.onload=null;this.rel='stylesheet'">
<style>:root{ --ww-default-font-family: 'Raleway', sans-serif }</style>
<style>

File diff suppressed because one or more lines are too long