Skip to content
This repository was archived by the owner on May 1, 2025. It is now read-only.

Commit 90908e7

Browse files
authored
feat: allow each guide to configure file filter (#101)
1 parent 619c558 commit 90908e7

File tree

8 files changed

+79
-10
lines changed

8 files changed

+79
-10
lines changed

components/PanelEditor.vue

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@ const play = usePlaygroundStore()
66
const ui = useUiState()
77
const guide = useGuideStore()
88
9-
const files = computed(() => Array.from(play.files.values()).filter(file => !isFileIgnored(file.filepath)))
9+
const files = computed(() => Array
10+
.from(play.files.values())
11+
.filter(file => !isFileIgnored(file.filepath, guide.ignoredFiles)),
12+
)
13+
1014
const directory = computed(() => filesToVirtualFsTree(files.value))
1115
1216
const input = ref<string>('')

components/TheNav.vue

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,19 @@ const repo = 'https://github.com/nuxt/learn.nuxt.com'
99
const buildTime = new Date(runtime.public.buildTime)
1010
const timeAgo = useTimeAgo(buildTime)
1111
12+
function downloadCurrentGuide() {
13+
if (!play.webcontainer)
14+
throw new Error('No webcontainer found')
15+
16+
if (play.status !== 'ready')
17+
throw new Error('Playground is not ready')
18+
19+
if (!guide.features.download)
20+
throw new Error(`Download feature is disabled for guide ${guide.currentGuide}`)
21+
22+
downloadZip(play.webcontainer, guide.ignoredFiles)
23+
}
24+
1225
addCommands(
1326
{
1427
id: 'download-zip',
@@ -17,7 +30,7 @@ addCommands(
1730
return play.status === 'ready' && guide.features.download !== false
1831
},
1932
handler: () => {
20-
downloadZip(play.webcontainer!)
33+
downloadCurrentGuide()
2134
},
2235
icon: 'i-ph-download-duotone',
2336
},
@@ -62,11 +75,11 @@ addCommands(
6275
<div i-ph-magnifying-glass-duotone text-2xl />
6376
</button>
6477
<button
65-
v-if="play.status === 'ready' && guide.features.download !== false"
78+
v-if="play.status === 'ready' && !!guide.features.download"
6679
rounded p2
6780
hover="bg-active"
6881
title="Download as ZIP"
69-
@click="downloadZip(play.webcontainer!)"
82+
@click="downloadCurrentGuide()"
7083
>
7184
<div i-ph-download-duotone text-2xl />
7285
</button>

composables/download.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
import type { WebContainer } from '@webcontainer/api'
2+
import type { StringOrRegExp } from '~/types/guides'
23

3-
export async function downloadZip(wc: WebContainer) {
4+
export async function downloadZip(wc: WebContainer, ignoredFiles?: StringOrRegExp[]) {
45
if (!import.meta.client)
56
return
67

78
const { default: JSZip } = await import('jszip')
89
const zip = new JSZip()
910

1011
type Zip = typeof zip
11-
1212
const crawlFiles = async (dir: string, zip: Zip) => {
1313
const files = await wc.fs.readdir(dir, { withFileTypes: true })
1414

1515
await Promise.all(
1616
files.map(async (file) => {
17-
if (isFileIgnored(file.name))
17+
if (isFileIgnored(file.name, ignoredFiles))
1818
return
1919

2020
if (file.isFile()) {

composables/files-ignore.ts

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,41 @@
1-
const INGORE_FILES: (string | RegExp)[] = [
1+
import type { GuideIgnoredFiles, GuideMeta, StringOrRegExp } from '~/types/guides'
2+
3+
const DEFAULT_IGNORED_FILES: StringOrRegExp[] = [
24
'pnpm-lock.yaml',
35
'pnpm-workspace.yaml',
46
'node_modules',
57
/tsconfig\.json$/,
68
/^\./,
79
]
810

9-
export function isFileIgnored(filepath: string) {
10-
return INGORE_FILES.some(pattern => typeof pattern === 'string'
11+
function normalizeGuideIgnoredFiles(input: GuideMeta['ignoredFiles']): GuideIgnoredFiles {
12+
if (!input)
13+
return { overwrite: false, patterns: [] }
14+
15+
if (Array.isArray(input))
16+
return { overwrite: false, patterns: input }
17+
18+
if (typeof input === 'object') {
19+
return {
20+
overwrite: input.overwrite || false,
21+
patterns: input.patterns || [],
22+
}
23+
}
24+
25+
throw new Error('Invalid ignoredFiles', input)
26+
}
27+
28+
export function transformGuideIgnoredFiles(input: GuideMeta['ignoredFiles']): StringOrRegExp[] {
29+
const { overwrite, patterns } = normalizeGuideIgnoredFiles(input)
30+
31+
return overwrite
32+
? patterns
33+
: DEFAULT_IGNORED_FILES.concat(patterns)
34+
}
35+
36+
export function isFileIgnored(filepath: string, ignoredFiles?: GuideMeta['ignoredFiles']) {
37+
const patterns = transformGuideIgnoredFiles(ignoredFiles)
38+
return patterns.some(pattern => typeof pattern === 'string'
1139
? filepath === pattern
1240
: pattern.test(filepath),
1341
)

content/2.concepts/2.app-vue/.template/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ export const meta: GuideMeta = {
66
fileTree: true,
77
navigation: false,
88
},
9+
ignoredFiles: ['package.json', 'server'],
910
}

content/2.concepts/3.routing/.template/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ export const meta: GuideMeta = {
55
features: {
66
fileTree: true,
77
},
8+
ignoredFiles: ['server'],
89
}

stores/guide.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const defaultFeatures = Object.freeze(<PlaygroundFeatures>{
55
fileTree: false,
66
terminal: false,
77
navigation: true,
8+
download: true,
89
})
910

1011
export const useGuideStore = defineStore('guide', () => {
@@ -17,6 +18,8 @@ export const useGuideStore = defineStore('guide', () => {
1718
const showingSolution = ref(false)
1819
const embeddedDocs = ref('')
1920

21+
const ignoredFiles = computed(() => transformGuideIgnoredFiles(currentGuide.value?.ignoredFiles))
22+
2023
watch(features, () => {
2124
if (features.value.fileTree === true) {
2225
if (ui.panelFileTree <= 0)
@@ -73,6 +76,7 @@ export const useGuideStore = defineStore('guide', () => {
7376
showingSolution,
7477
embeddedDocs,
7578
openEmbeddedDocs,
79+
ignoredFiles,
7680
}
7781
})
7882

types/guides.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
export type StringOrRegExp = string | RegExp
2+
3+
export interface GuideIgnoredFiles { overwrite: boolean, patterns: StringOrRegExp[] }
4+
15
export interface GuideMeta {
26
features?: PlaygroundFeatures
37
startingFile?: string
@@ -10,6 +14,20 @@ export interface GuideMeta {
1014
files?: Record<string, string>
1115
// TODO:
1216
solutions?: Record<string, string>
17+
18+
/**
19+
* Ignored file patterns.
20+
* Can be a list of strings or regex.
21+
*
22+
* @example
23+
* // add to default patterns
24+
* ignoredFiles: ['pnpm-lock.yaml']
25+
*
26+
* @example
27+
* // overwrite default patterns
28+
* ignoredFiles: { overwrite: true, patterns: ['pnpm-lock.yaml'] }
29+
*/
30+
ignoredFiles?: StringOrRegExp[] | GuideIgnoredFiles
1331
}
1432

1533
export interface PlaygroundFeatures {

0 commit comments

Comments
 (0)