From 1e63ed19c83672f9e7aab1794d4a9c6ab711b263 Mon Sep 17 00:00:00 2001 From: Frederic Salmen <4583669+salmenf@users.noreply.github.com> Date: Fri, 13 Dec 2024 22:03:29 +0100 Subject: [PATCH] Improve and fix local packages, creating new local packages --- .../core/model/schemas/packageschema/index.ts | 6 +- .../schemas/resourceschema/editingstyles.css | 7 + @webwriter/core/model/stores/packagestore.ts | 176 +++++++++++++++--- @webwriter/core/model/stores/uistore.ts | 1 + @webwriter/core/model/templates/index.ts | 2 +- @webwriter/core/view/editor/palette.ts | 39 +++- @webwriter/core/view/editor/toolbox.ts | 13 +- @webwriter/core/view/forms/saveform.ts | 1 - @webwriter/core/view/index.ts | 4 +- .../core/viewmodel/services/bundleservice.ts | 42 ++++- .../core/viewmodel/settingscontroller.ts | 12 +- 11 files changed, 260 insertions(+), 43 deletions(-) diff --git a/@webwriter/core/model/schemas/packageschema/index.ts b/@webwriter/core/model/schemas/packageschema/index.ts index a30ee476..db065a76 100644 --- a/@webwriter/core/model/schemas/packageschema/index.ts +++ b/@webwriter/core/model/schemas/packageschema/index.ts @@ -306,7 +306,8 @@ export class Package { exports: PackageExports.optional(), imports: z.record(z.string().startsWith("#"), z.record(z.string())).optional(), editingConfig: EditingConfig.optional(), - lastLoaded: z.number().optional() + lastLoaded: z.number().optional(), + trusted: z.boolean().optional() }) static objectSchema = this.coreObjectSchema @@ -327,13 +328,14 @@ export class Package { static coreKeys = Object.keys(this.coreObjectSchema.shape) as unknown as keyof typeof this.coreObjectSchema.shape - constructor(pkg: Package | z.input & Record, editingState?: Partial>) { + constructor(pkg: Package | z.input & Record, editingState?: Partial>) { return pkg instanceof Package ? Object.assign(pkg, editingState) : Object.assign(Package.schema.parse(pkg), editingState) } watching?: boolean = false + trusted?: boolean = false localPath?: string lastLoaded?: number installed?: boolean diff --git a/@webwriter/core/model/schemas/resourceschema/editingstyles.css b/@webwriter/core/model/schemas/resourceschema/editingstyles.css index f55a5a22..28757854 100644 --- a/@webwriter/core/model/schemas/resourceschema/editingstyles.css +++ b/@webwriter/core/model/schemas/resourceschema/editingstyles.css @@ -478,6 +478,13 @@ abbr { color: goldenrod !important; } +:not(:defined) { + display: block; + min-height: 1.75rem; + cursor: pointer; + caret-color: transparent !important; +} + :is(:empty, :has(.ProseMirror-trailingBreak)) { position: relative; } diff --git a/@webwriter/core/model/stores/packagestore.ts b/@webwriter/core/model/stores/packagestore.ts index 149078d9..d9e9b544 100644 --- a/@webwriter/core/model/stores/packagestore.ts +++ b/@webwriter/core/model/stores/packagestore.ts @@ -80,6 +80,10 @@ export class PackageStore { importMap: ImportMap + static allowedOrgs = [ + "@webwriter" + ] + set installedPackages(value: string[]) { let valueUnique = Array.from(new Set(value)) valueUnique = valueUnique.filter(a => !valueUnique.some(b => b !== a && b.startsWith("@" + a.split("@")[1]))) @@ -436,6 +440,45 @@ export class PackageStore { this.installedPackages = ids } + async checkForMissingMembers(pkgs: Package[]) { + const ids = pkgs.map(pkg => pkg.id) + /* + const expectedIds = pkgs.flatMap(pkg => + Object.keys(pkg.exports) + .filter(k => k.startsWith("./widgets/") || k.startsWith("./snippets/") || k.startsWith("./themes/")) + .flatMap(k => k.endsWith(".*")? [pkg.id + k.slice(1, -1) + "js", pkg.id + k.slice(1, -1) + "css"]: [pkg.id + k.slice(1)]) + ) + const availableIds = Object.keys(this.importMap.imports).filter(id => ids.some(toCheck => id.startsWith(toCheck))) + const missingIds = expectedIds.filter(id => !availableIds.includes(id)) + for(const id of missingIds) { + const pkgId = id.split("/").slice(0, 2).join("/") + const type = id.split("/")[2].slice(0, -1) + const issue = new BundleIssue(`Missing ${type} '${id}'. Did you create an importable bundle, for example with 'npx @webwriter/dev'? https://webwriter.app/docs/quickstart/`) + this.appendPackageIssues(pkgId, issue) + } + console.log(this.importMap) + return */ + const idsToCheck = Object.keys(this.importMap.imports).filter(id => ids.some(toCheck => id.startsWith(toCheck))) + return Promise.all(idsToCheck.map(async id => { + const url = this.importMap.resolve(id) + try { + const result = await fetch(url) + if(!result.ok) throw Error("Failed to fetch") + } catch(err) { + const pkgId = id.split("/").slice(0, 2).join("/") + if(id.split("/")[2] === "widgets" && url.endsWith(".css")) { + return + } + else { + const type = id.split("/")[2].slice(0, -1) + const issue = new BundleIssue(`Missing ${type} '${id}'. Did you create an importable bundle, for example with 'npx @webwriter/build dev'? https://webwriter.app/docs/quickstart/`) + this.appendPackageIssues(pkgId, issue) + console.error(issue) + } + } + })) + } + pmQueue = cargoQueue(async (tasks: PmQueueTask[]) => { const toAdd = tasks.filter(t => t.command === "add" && !t.name && !t.handle).flatMap(t => t.parameters) const toAddLocal = tasks.filter(t => t.command === "add" && t.handle).flatMap(t => ({handle: t.handle!, name: t.name})) @@ -494,6 +537,10 @@ export class PackageStore { this.updateImportMap([...this.installedPackages, ...pkgs.map(pkg => pkg.id)]) } catch(err) { + if(err instanceof Error && err.name === "NotFoundError") { + console.error("No package.json found in selected directory", {cause: err}) + return + } console.error(err) } finally { @@ -782,6 +829,7 @@ export class PackageStore { this.bundleID = PackageStore.computeBundleID(this.installedPackages, false, final.some(pkg => pkg.localPath)? this.lastLoaded: undefined); (this.onBundleChange ?? (() => null))(final.filter(pkg => pkg.installed)) this.packages = Object.fromEntries(final.map(pkg => [pkg.id, pkg])) + await this.checkForMissingMembers(this.installed) } this.searchIndex.removeAll() this.searchIndex.addAll(final) @@ -822,8 +870,9 @@ export class PackageStore { } const members = await Promise.all(rawPkgs.map(async pkg => this.readPackageMembers(pkg))) return rawPkgs.map((pkg, i) => { + const trusted = PackageStore.allowedOrgs.some(org => pkg.name.startsWith(`${org}/`)) try { - return new Package(pkg, {members: members[i]}) + return new Package(pkg, {members: members[i], trusted}) } catch(err) { const parseIssues = JSON.parse((err as any)?.message) @@ -840,7 +889,7 @@ export class PackageStore { return issue }) this.appendPackageIssues(`${pkg.name}@${pkg.version}`, ...errors) - return new Package({name: pkg.name, version: pkg.version}) + return new Package({name: pkg.name, version: pkg.version}, {trusted}) } }).filter(pkg => pkg) } @@ -911,7 +960,7 @@ export class PackageStore { } } catch(cause) { - // throw new ReadWriteIssue(`Could not read file ${fullPath}`, {cause}) + throw new ReadWriteIssue(`Could not read file ${fullPath}`, {cause}) } } members[name] = {name, legacy: !rawName.endsWith(".*"), ...memberSettings, ...(source? {source}: undefined)} @@ -1171,8 +1220,16 @@ export class PackageStore { .filter(k => k.startsWith("./widgets/") || k.startsWith("./snippets/") || k.startsWith("./themes/")) .map(k => typeof (exports as any)[k] !== "string"? (exports as any)[k]?.default as string: (exports as any)[k] as string) .flatMap(k => !k.endsWith(".*")? [k]: [k.slice(0, -2) + ".js", k.slice(0, -2) + ".css"]) - const exportedFiles = await Promise.all(exportPaths.map(path => this.resolveRelativeLocalPath(path, handle))) - if(exportedFiles.some(file => file.lastModified >= this.lastLoaded)) { + const exportedFiles = (await Promise.all(exportPaths.map(async path => { + try { + return await this.resolveRelativeLocalPath(path, handle) + } + catch { + return null + } + + }))).filter(file => file) + if(exportedFiles.some(file => file!.lastModified >= this.lastLoaded)) { return this.load() } @@ -1181,9 +1238,7 @@ export class PackageStore { } /** Write a given package to a directory, creating files as neccessary. If `force` is false, abort if existing files are found. */ - async writeLocal(path: string, pkg: Package, {extraFiles = {} as Record, mergePackage=false, overwrite=false, preset="none", generateLicense=false}) { - const resolvedPath = await this.Path.resolve(path) - + async writeLocal(pathOrHandle: string | FileSystemDirectoryHandle, pkg: Package, {extraFiles = {} as Record, mergePackage=false, overwrite=false, preset="none", generateLicense=false}) { let allExtraFiles = {...extraFiles} if(preset && preset in presets) { allExtraFiles = {...allExtraFiles, ...(presets as any)[String(preset)](pkg)} @@ -1191,27 +1246,46 @@ export class PackageStore { if(generateLicense && String(pkg.license) in licenses) { allExtraFiles = {...allExtraFiles, ...(licenses as any)[String(pkg.license)](pkg)} } - await Promise.all(Object.keys(allExtraFiles).map(async fileName => { - const extraPath = await this.Path.join(resolvedPath, fileName) - const extraPathDir = await this.Path.dirname(extraPath) - const extraExists = await this.FS.exists(extraPath) - const extraDirExists = await this.FS.exists(extraPathDir) - if(extraExists && !overwrite) { - throw Error("Existing extra file found under " + extraPath) + + if(pathOrHandle instanceof FileSystemDirectoryHandle) { + const root = pathOrHandle + await Promise.all(Object.keys(allExtraFiles).map(async path => { + return writeFile(root, path, allExtraFiles[path], true, overwrite) + })) + const existingPkgFile = await readFile(root, "package.json") + if(existingPkgFile && !mergePackage) { + throw Error(`Existing package.json file found in '${root.name}'`) } - if(!extraDirExists) { - await this.FS.mkdir(extraPathDir) + const existingPkg = existingPkgFile? new Package(JSON.parse(await existingPkgFile.text())): null + const newPkg = existingPkg? existingPkg.extend(pkg): pkg + await writeFile(root, "package.json", JSON.stringify(newPkg), true, true) + await this.add(root, newPkg.id) + } + else { + const resolvedPath = await this.Path.resolve(pathOrHandle) + await Promise.all(Object.keys(allExtraFiles).map(async fileName => { + const extraPath = await this.Path.join(resolvedPath, fileName) + const extraPathDir = await this.Path.dirname(extraPath) + const extraExists = await this.FS.exists(extraPath) + const extraDirExists = await this.FS.exists(extraPathDir) + if(extraExists && !overwrite) { + throw Error("Existing extra file found under " + extraPath) + } + if(!extraDirExists) { + await this.FS.mkdir(extraPathDir) + } + return this.FS.writeFile(extraPath, allExtraFiles[fileName]) + })) + const pkgJsonPath = await this.Path.join(resolvedPath, "package.json") + const exists = await this.FS.exists(pkgJsonPath) + if(exists && !mergePackage) { + throw Error("Existing package.json file found under " + pkgJsonPath) } - return this.FS.writeFile(extraPath, allExtraFiles[fileName]) - })) - const pkgJsonPath = await this.Path.join(resolvedPath, "package.json") - const exists = await this.FS.exists(pkgJsonPath) - if(exists && !mergePackage) { - throw Error("Existing package.json file found under " + pkgJsonPath) + const existingPkg = exists? new Package(JSON.parse(await this.FS.readFile(pkgJsonPath) as string)): null + const newPkg = existingPkg? existingPkg.extend(pkg): pkg + await this.FS.writeFile(pkgJsonPath, JSON.stringify(newPkg, undefined, 2)) } - const existingPkg = exists? new Package(JSON.parse(await this.FS.readFile(pkgJsonPath) as string)): null - const newPkg = existingPkg? existingPkg.extend(pkg): pkg - await this.FS.writeFile(pkgJsonPath, JSON.stringify(newPkg, undefined, 2)) + } /** Uses the provided system shell to open the app directory. */ @@ -1243,4 +1317,54 @@ export class PackageStore { await fetch(url, {method: "DELETE"}) return this.load() } +} + +async function writeFile(root: FileSystemDirectoryHandle, path: string, content: string | Blob | BufferSource, ensurePath=false, overwrite=false) { + const pathParts = path.split("/") + let directory = root + for(const [i, part] of pathParts.entries()) { + if(i === pathParts.length - 1) { + let fileHandle + try { + fileHandle = await directory.getFileHandle(part) + } catch {} + if(fileHandle && !overwrite) { + throw Error("Found existing file, and 'overwrite' is false") + } + else { + fileHandle = await directory.getFileHandle(part, {create: true}) + const writable = await fileHandle.createWritable() + await writable.write(content) + await writable.close() + } + } + else { + directory = await directory.getDirectoryHandle(part, {create: ensurePath}) + } + } +} + +async function readFile(root: FileSystemDirectoryHandle, path: string) { + const pathParts = path.split("/") + let directory = root + for(const [i, part] of pathParts.entries()) { + if(i === pathParts.length - 1) { + try { + const fileHandle = await directory.getFileHandle(part) + return fileHandle.getFile() + } + catch(err) { + return null + } + } + else { + try { + directory = await directory.getDirectoryHandle(part) + } + catch(err) { + return null + } + } + } + return null } \ No newline at end of file diff --git a/@webwriter/core/model/stores/uistore.ts b/@webwriter/core/model/stores/uistore.ts index 914569b1..44207cef 100644 --- a/@webwriter/core/model/stores/uistore.ts +++ b/@webwriter/core/model/stores/uistore.ts @@ -30,6 +30,7 @@ export class UIStore { // showWidgetPreview: boolean = false // TODO: Causes multiple issues showUnstable = false + showUnknown = false keymap: Record = {} diff --git a/@webwriter/core/model/templates/index.ts b/@webwriter/core/model/templates/index.ts index 28b19e49..0facdadd 100644 --- a/@webwriter/core/model/templates/index.ts +++ b/@webwriter/core/model/templates/index.ts @@ -20,7 +20,7 @@ const interpolateTemplate = (template: string, pkg: Package) => { const defaultElementName = `${scope}-${name}` const replacementMap: Record = { - "____classname____": [scope ?? "", name].map(capitalizeWord).join("") || "MyWidget", + "____classname____": [scope ?? "", ...name.split("-")].map(capitalizeWord).join("") || "MyWidget", "____year____": String(new Date().getFullYear()) } for (const [key, value] of Object.entries(pkg.toJSON())) { diff --git a/@webwriter/core/view/editor/palette.ts b/@webwriter/core/view/editor/palette.ts index b60d794e..38b1e262 100644 --- a/@webwriter/core/view/editor/palette.ts +++ b/@webwriter/core/view/editor/palette.ts @@ -1,5 +1,5 @@ import { msg } from "@lit/localize" -import { LitElement, Part, PropertyValueMap, css, html, noChange } from "lit" +import { LitElement, Part, PropertyValueMap, PropertyValues, css, html, noChange } from "lit" import { customElement, property, query } from "lit/decorators.js" import { classMap } from "lit/directives/class-map.js" import { ifDefined } from "lit/directives/if-defined.js" @@ -7,7 +7,7 @@ import { EditorState, Command as PmCommand } from "prosemirror-state" import {Directive, PartInfo, directive, ElementPart} from "lit/directive.js" import { MemberSettings, Package, SemVer, watch } from "../../model" -import { unscopePackageName, prettifyPackageName, camelCaseToSpacedCase, filterObject } from "../../utility" +import { unscopePackageName, prettifyPackageName, camelCaseToSpacedCase, filterObject, sameMembers } from "../../utility" import { SlDropdown, SlInput, SlMenu, SlPopup, SlProgressBar } from "@shoelace-style/shoelace" import { Command } from "../../viewmodel" import { App, PackageForm } from ".." @@ -1032,14 +1032,23 @@ export class Palette extends LitElement { mergePackage: true } this.packageFormMode = undefined - if(packageForm.changed) { + if(packageForm.changed && WEBWRITER_ENVIRONMENT.backend === "tauri") { await this.app.store.packages.writeLocal(pkg.localPath!, pkg, options) } + else if(packageForm.changed && WEBWRITER_ENVIRONMENT.backend !== "tauri") { + const unlocalVersion = new SemVer(pkg.version) + unlocalVersion.prerelease = unlocalVersion.prerelease.filter(v => v !== "local") + await this.app.store.packages.writeLocal(packageForm.directoryHandle!, pkg.extend({version: unlocalVersion}), options) + this.packageForm.reset() + return + } + if(WEBWRITER_ENVIRONMENT.backend === "tauri") { await this.app.store.packages.add(`file://${pkg.localPath!}`, pkg.name) } else { - await this.app.store.packages.add(packageForm.directoryHandle!, pkg.id) + let directoryHandle = packageForm.directoryHandle + await this.app.store.packages.add(directoryHandle!, pkg.id) } if(packageForm.editingState.watching) { this.emitWatchWidget(pkg.id) @@ -1172,6 +1181,28 @@ export class Palette extends LitElement { }) } + protected updated(changed: PropertyValues) { + if(changed.has("packages")) { + const prevIds = changed.get("packages")?.map((pkg: Package) => pkg.id + (pkg.installed? "!installed": "")) ?? [] + const ids = this.packages.map(pkg => pkg.id + (pkg.installed? "!installed": "")) + const isAdd = ids.filter(id => id.endsWith("!installed")).length > prevIds.filter((id: string) => id.endsWith("!installed")).length + const firstChangedId = ids.find((id, i) => prevIds[i] !== id)?.split("!")[0] + if(firstChangedId && isAdd) { + const el = this.shadowRoot!.querySelector("#" + CSS.escape(firstChangedId))! as HTMLElement + el.scrollIntoView({behavior: "smooth", block: "center", inline: "center"}) + return + const elTop = el.getBoundingClientRect().top + const elLeft = el.getBoundingClientRect().left + const heightOffset = this.shadowRoot!.getElementById("package-toolbar")!.getBoundingClientRect().height + let top = elTop + heightOffset + const widthOffset = this.shadowRoot!.getElementById("package-toolbar")!.getBoundingClientRect().width + let left = el.getBoundingClientRect().left + widthOffset + console.log({elTop, elLeft, top, left}) + this.scrollTo({left, top, behavior: "smooth"}) + } + } + } + render() { return html` ${this.PackageToolbar()} diff --git a/@webwriter/core/view/editor/toolbox.ts b/@webwriter/core/view/editor/toolbox.ts index f7dc4b7a..5304f7bf 100644 --- a/@webwriter/core/view/editor/toolbox.ts +++ b/@webwriter/core/view/editor/toolbox.ts @@ -1317,7 +1317,18 @@ export class Toolbox extends LitElement { : html`${content}` } else { - return html`` + const content = html` this.emitClickBreadcrumb(el)} + @hover=${() => this.emitHoverBreadcrumb(el)} + > + ${!isLast? null: prettifyPackageName(elementName, "all", true)} + ` + return !menuItem + ? html`${content}${separator}` + : html`${content}` } } diff --git a/@webwriter/core/view/forms/saveform.ts b/@webwriter/core/view/forms/saveform.ts index 84ef46e2..9c6ecbeb 100644 --- a/@webwriter/core/view/forms/saveform.ts +++ b/@webwriter/core/view/forms/saveform.ts @@ -265,7 +265,6 @@ export class SaveForm extends LitElement { combobox: Combobox; Tree() { - console.log("Tree") return html`
${msg("Files of ")}${this.client.account.id ?? this.client.account.email} diff --git a/@webwriter/core/view/index.ts b/@webwriter/core/view/index.ts index c27b70a1..666f823f 100644 --- a/@webwriter/core/view/index.ts +++ b/@webwriter/core/view/index.ts @@ -258,6 +258,7 @@ export class App extends ViewModelMixin(LitElement) { if (this.initializing) { return null; } + const {showUnknown, showUnstable} = this.store.ui const { changed, set, @@ -270,6 +271,7 @@ export class App extends ViewModelMixin(LitElement) { inMemory, } = this.store.document; const { packagesList, bundleJS, bundleCSS, bundleID } = this.store.packages; + const filteredPackages = packagesList.filter(pkg => pkg.localPath || (!pkg.version.lt("1.0.0") || showUnstable) && (pkg.trusted || showUnknown) || pkg.version.prerelease.includes("snippet")) const { locale } = this.store.ui; const { open } = this.environment?.api?.Shell ?? window.open; const { @@ -319,7 +321,7 @@ export class App extends ViewModelMixin(LitElement) { .codeState=${codeState} @update=${(e: any) => set(e.detail.editorState)} @ww-open=${(e: any) => open(e.detail.url)} - .packages=${packagesList} + .packages=${filteredPackages} ?loadingPackages=${false} ?controlsVisible=${!this.foldOpen} lang=${locale} diff --git a/@webwriter/core/viewmodel/services/bundleservice.ts b/@webwriter/core/viewmodel/services/bundleservice.ts index ed14f6b6..92abc0db 100644 --- a/@webwriter/core/viewmodel/services/bundleservice.ts +++ b/@webwriter/core/viewmodel/services/bundleservice.ts @@ -442,11 +442,21 @@ async function getAsset(id: string) { let file: File for(const [i, part] of pathParts.entries()) { if(i === pathParts.length - 1) { - const fileHandle = await directory.getFileHandle(part) - file = await fileHandle.getFile() + try { + const fileHandle = await directory.getFileHandle(part) + file = await fileHandle.getFile() + } + catch(err) { + return new Response(null, {status: 404, statusText: "404: Local file not found"}) + } } else { - directory = await directory.getDirectoryHandle(part) + try { + directory = await directory.getDirectoryHandle(part) + } + catch(err) { + return new Response(null, {status: 404, statusText: "404: Local directory not found"}) + } } } return new Response(file!, {headers: {"Access-Control-Allow-Origin": "*"}}) @@ -568,7 +578,7 @@ async function getImportmap(ids: string[] | Record[]) { .filter(pkg => (new SemVer(pkg.version).prerelease.includes("local"))) .flatMap(pkg => Object.keys(pkg.exports) .filter(k => k.startsWith("./") && k.endsWith(".*")) - .map(k => pkg.name + "@" + pkg.version + k.slice(1, -2) + ".js") + .map(k => pkg.name + "@0.0.0-local" + k.slice(1, -2) + ".js") ) )) } @@ -597,6 +607,7 @@ async function getImportmap(ids: string[] | Record[]) { const name = key.split("/").slice(0, 2).join("/") map.set(!name.slice(1).includes("@")? key.replace(name, name + "@" + resolutions[name]): key, value) } + console.log(localIds) if(localIds.length) { const localGenerator = new Generator({cache: false, inputMap: map, customProviders: {filesystem}, defaultProvider: "filesystem", resolutions}) let allLinkedLocal = false @@ -607,10 +618,15 @@ async function getImportmap(ids: string[] | Record[]) { } catch(err: any) { const regexMatch = / imported from /g.exec(err.message) + const notFoundMatch = /Unable to analyze (https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&//=]*)) imported from/g.exec(err.message) if(err.code === "MODULE_NOT_FOUND" && regexMatch) { console.warn(`Excluding faulty package ${regexMatch[1]}: ${err.message}`) localIds = localIds.filter(id => id !== regexMatch[1]) } + else if(err.cause.name === "NotFoundError" && notFoundMatch) { + const faultyPkgID = (new URL(notFoundMatch[1])).pathname.split("/").slice(3, 5).join("/") + localIds = localIds.filter(id => !id.startsWith(faultyPkgID)) + } else { console.error(err) return new Response(null, {status: 500}) @@ -751,7 +767,9 @@ async function respond(action: Action) { } else if(action.collection === "importmaps") { const mapResponse = await getImportmap(action.args.pkg === "true"? pkgs: action.ids) - cache.put(url, mapResponse.clone()) + if(!versionedIds.some(id => id.split("/")[0].split("@")[1].endsWith("-local"))) { + cache.put(url, mapResponse.clone()) + } return mapResponse } else if(action.collection === "bundles") { @@ -892,4 +910,16 @@ async function search(text: string, params?: {size?: number, quality?: number, p } } while(from < total) return {objects, total, time} -} \ No newline at end of file +} + +/* +if(path.endsWith(".css")) { + return null +} +else if(path.endsWith(".html")) { + throw new Error(`Exported snippet not found at '${path}'. Check that your package.json is correct. https://webwriter.app/docs/quickstart/`) +} +else if(path.endsWith(".js")) { + throw new Error(`Exported widget bundle not found at '${path}'. Check that your package.json is correct and that you bundled your widget, for example with '@webwriter/build'. https://webwriter.app/docs/quickstart/`) +} +*/ \ No newline at end of file diff --git a/@webwriter/core/viewmodel/settingscontroller.ts b/@webwriter/core/viewmodel/settingscontroller.ts index e5f73379..0e070dce 100644 --- a/@webwriter/core/viewmodel/settingscontroller.ts +++ b/@webwriter/core/viewmodel/settingscontroller.ts @@ -174,11 +174,21 @@ export class SettingsController implements ReactiveController { .boolean() .describe( msg( - "Advanced: Show with versions like 0.x.x in the package manager" + "Advanced: Show packages with versions like 0.x.x in the package manager" ) ), label: msg("Show unstable packages"), }, + showUnknown: { + schema: z + .boolean() + .describe( + msg( + "Advanced: Show packages from unknown sources in the package manager (Warning: May be unsafe)" + ) + ), + label: msg("Show unknown packages"), + }, /*showTextPlaceholder: { schema: z .boolean()