feat: 完善模型用量趋势与全局手账卡片并补齐阶段验收
This commit is contained in:
@@ -4,16 +4,20 @@ import { t } from '@/i18n'
|
||||
import { getCommunityThemePreviewCss, mockCommunityThemes } from '@/services/themePackageService'
|
||||
import tokensCss from '@/styles/tokens.css?raw'
|
||||
|
||||
const props = defineProps<{ themeId: string }>()
|
||||
const props = defineProps<{ themeId: string; name?: string; css?: string }>()
|
||||
const emit = defineEmits<{ (event: 'close'): void }>()
|
||||
const theme = computed(() => mockCommunityThemes.find(item => item.theme_id === props.themeId))
|
||||
const theme = computed(() => props.name ? { name: props.name } : mockCommunityThemes.find(item => item.theme_id === props.themeId))
|
||||
const previewDocument = computed(() => {
|
||||
// Only bundled community CSS enters this script-free, isolated document.
|
||||
// Both imported and bundled CSS are previewed in a script-free isolated document.
|
||||
// Previewing never installs a theme or changes application styles/storage.
|
||||
const doc = document.implementation.createHTMLDocument(theme.value?.name ?? '')
|
||||
doc.documentElement.dataset.theme = props.themeId
|
||||
const policy = doc.createElement('meta')
|
||||
policy.httpEquiv = 'Content-Security-Policy'
|
||||
policy.content = "default-src 'none'; style-src 'unsafe-inline'; img-src data:; font-src data:; base-uri 'none'; form-action 'none'"
|
||||
doc.head.append(policy)
|
||||
const style = doc.createElement('style')
|
||||
style.textContent = `${tokensCss}\n${getCommunityThemePreviewCss(props.themeId)}\nbody { margin:0; padding:24px; background:var(--color-background-primary); color:var(--color-text-primary); font:16px/1.6 system-ui; } article { padding:20px; border:1px solid var(--color-border-default); border-radius:8px; background:var(--color-surface-primary); } p { color:var(--color-text-secondary); } button { padding:8px 16px; border:0; border-radius:6px; background:var(--color-accent-primary); color:white; }`
|
||||
style.textContent = `${tokensCss}\n${props.css ?? getCommunityThemePreviewCss(props.themeId)}\nbody { margin:0; padding:24px; background:var(--color-background-primary); color:var(--color-text-primary); font:16px/1.6 system-ui; } article { padding:20px; border:1px solid var(--color-border-default); border-radius:8px; background:var(--color-surface-primary); } p { color:var(--color-text-secondary); } button { padding:8px 16px; border:0; border-radius:6px; background:var(--color-accent-primary); color:white; }`
|
||||
doc.head.append(style)
|
||||
const article = doc.createElement('article')
|
||||
article.className = 'panel'
|
||||
|
||||
@@ -21,6 +21,12 @@ it('downloads a URL for inspection without automatically installing it', async (
|
||||
await vi.waitFor(() => expect(useThemeStore().pendingInspection?.compatible).toBe(true))
|
||||
expect(useThemeStore().isThemeInstalled('paper-moments')).toBe(false)
|
||||
expect(wrapper.get('.inspection-result').text()).toContain('纸间时光')
|
||||
await wrapper.findAll('button').find(button => button.text() === '预览主题效果')!.trigger('click')
|
||||
const preview = wrapper.get('iframe')
|
||||
expect(preview.attributes('sandbox')).toBe('')
|
||||
expect(preview.attributes('srcdoc')).toContain('Content-Security-Policy')
|
||||
expect(preview.attributes('srcdoc')).toContain('data-theme="paper-moments"')
|
||||
expect(useThemeStore().isThemeInstalled('paper-moments')).toBe(false)
|
||||
})
|
||||
|
||||
it('ignores a URL response after the dialog is cancelled', async () => {
|
||||
|
||||
@@ -9,6 +9,7 @@ import CommunityThemePreview from './CommunityThemePreview.vue'
|
||||
import paperMomentsUrl from '@/assets/themes/paper-moments.theme?url'
|
||||
|
||||
const themeStore = useThemeStore()
|
||||
const previewImport = ref(false)
|
||||
|
||||
const activeTab = ref<'installed' | 'community'>('installed')
|
||||
const showImportDialog = ref(false)
|
||||
@@ -22,6 +23,7 @@ let importGeneration = 0
|
||||
let downloadController: AbortController | undefined
|
||||
|
||||
function resetImport() {
|
||||
previewImport.value = false
|
||||
importGeneration++
|
||||
downloadController?.abort()
|
||||
importing.value = false
|
||||
@@ -253,6 +255,7 @@ onMounted(() => {
|
||||
<summary>将要安装的 CSS({{ themeStore.pendingInspection.css.length }} 字符)</summary>
|
||||
<pre>{{ themeStore.pendingInspection.css }}</pre>
|
||||
</details>
|
||||
<button type="button" class="button-secondary" @click="previewImport = true">预览主题效果</button>
|
||||
</div>
|
||||
|
||||
<div v-else class="upload-area">
|
||||
@@ -280,6 +283,7 @@ onMounted(() => {
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<CommunityThemePreview v-if="previewImport && themeStore.pendingInspection?.compatible" :theme-id="themeStore.pendingInspection.manifest.theme_id" :name="themeStore.pendingInspection.manifest.name" :css="themeStore.pendingInspection.css" @close="previewImport = false" />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
Reference in New Issue
Block a user