diff --git a/package-lock.json b/package-lock.json index c8fae637..2f7bfb64 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7990,6 +7990,10 @@ "resolved": "plugins/renamer", "link": true }, + "node_modules/repro": { + "resolved": "plugins/repro", + "link": true + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -11506,6 +11510,222 @@ "vite-plugin-mkcert": "^1" } }, + "plugins/repro": { + "version": "0.0.0", + "dependencies": { + "classnames": "^2.5.1", + "framer-plugin": "^2.0.4", + "react": "^18", + "react-dom": "^18", + "vite-plugin-mkcert": "^1" + }, + "devDependencies": { + "@types/react": "^18", + "@types/react-dom": "^18", + "@typescript-eslint/eslint-plugin": "^7", + "@typescript-eslint/parser": "^7", + "@vitejs/plugin-react-swc": "^3", + "eslint": "^8", + "eslint-plugin-react-hooks": "^4", + "eslint-plugin-react-refresh": "^0", + "typescript": "^5.3.3", + "vite": "^5", + "vite-plugin-framer": "^1.0.1" + } + }, + "plugins/repro/node_modules/@typescript-eslint/eslint-plugin": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz", + "integrity": "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/type-utils": "7.18.0", + "@typescript-eslint/utils": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "plugins/repro/node_modules/@typescript-eslint/parser": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz", + "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "plugins/repro/node_modules/@typescript-eslint/scope-manager": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz", + "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "plugins/repro/node_modules/@typescript-eslint/type-utils": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz", + "integrity": "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/utils": "7.18.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "plugins/repro/node_modules/@typescript-eslint/types": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", + "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "plugins/repro/node_modules/@typescript-eslint/typescript-estree": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz", + "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "plugins/repro/node_modules/@typescript-eslint/utils": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz", + "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + } + }, + "plugins/repro/node_modules/@typescript-eslint/visitor-keys": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz", + "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "plugins/rss-feeds": { "version": "0.0.0", "dependencies": { diff --git a/plugins/repro/.eslintrc.cjs b/plugins/repro/.eslintrc.cjs new file mode 100644 index 00000000..9358989c --- /dev/null +++ b/plugins/repro/.eslintrc.cjs @@ -0,0 +1,11 @@ +module.exports = { + root: true, + env: { browser: true, es2020: true }, + extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended", "plugin:react-hooks/recommended"], + ignorePatterns: ["dist", ".eslintrc.cjs"], + parser: "@typescript-eslint/parser", + plugins: ["react-refresh"], + rules: { + "react-refresh/only-export-components": ["warn", { allowConstantExport: true }], + }, +} diff --git a/plugins/repro/.gitignore b/plugins/repro/.gitignore new file mode 100644 index 00000000..94469931 --- /dev/null +++ b/plugins/repro/.gitignore @@ -0,0 +1,24 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js +.yarn + +# misc +.DS_Store +*.pem + +# files +my-plugin +dev-plugin +dist + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local diff --git a/plugins/repro/README.md b/plugins/repro/README.md new file mode 100644 index 00000000..907341a5 --- /dev/null +++ b/plugins/repro/README.md @@ -0,0 +1,7 @@ +# RSS Feeds + +Synchronize RSS feeds with Framer CMS + +**By**: @niekert @huntercaron + +![RSS Feeds](../../assets/rss.png) diff --git a/plugins/repro/framer.json b/plugins/repro/framer.json new file mode 100644 index 00000000..a320668d --- /dev/null +++ b/plugins/repro/framer.json @@ -0,0 +1,6 @@ +{ + "id": "xxd64a", + "name": "Repro", + "modes": ["syncManagedCollection", "configureManagedCollection"], + "icon": "/rss.png" +} diff --git a/plugins/repro/index.html b/plugins/repro/index.html new file mode 100644 index 00000000..3088b3dd --- /dev/null +++ b/plugins/repro/index.html @@ -0,0 +1,13 @@ + + + + + + + RSS Feeds + + +
+ + + diff --git a/plugins/repro/package.json b/plugins/repro/package.json new file mode 100644 index 00000000..d98f698c --- /dev/null +++ b/plugins/repro/package.json @@ -0,0 +1,34 @@ +{ + "name": "repro", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build --base=${PREFIX_BASE_PATH:+/$npm_package_name}/", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "pack": "npx framer-plugin-tools@latest pack", + "preview": "vite preview", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "classnames": "^2.5.1", + "framer-plugin": "^2.0.4", + "react": "^18", + "react-dom": "^18", + "vite-plugin-mkcert": "^1" + }, + "devDependencies": { + "@types/react": "^18", + "@types/react-dom": "^18", + "@typescript-eslint/eslint-plugin": "^7", + "@typescript-eslint/parser": "^7", + "@vitejs/plugin-react-swc": "^3", + "eslint": "^8", + "eslint-plugin-react-hooks": "^4", + "eslint-plugin-react-refresh": "^0", + "typescript": "^5.3.3", + "vite": "^5", + "vite-plugin-framer": "^1.0.1" + } +} diff --git a/plugins/repro/public/rss.png b/plugins/repro/public/rss.png new file mode 100644 index 00000000..4c2f71de Binary files /dev/null and b/plugins/repro/public/rss.png differ diff --git a/plugins/repro/src/App.css b/plugins/repro/src/App.css new file mode 100644 index 00000000..36d7dc6b --- /dev/null +++ b/plugins/repro/src/App.css @@ -0,0 +1,42 @@ +/* Your Plugin CSS */ + +main { + display: flex; + flex-direction: column; + align-items: start; + padding: 0 15px 15px 15px; + height: 100%; + gap: 15px; +} + +.label { + display: block; +} + +.field { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; +} + +.field select { + width: 165px; + padding: 0 24px 0 8px; +} + +.illustration { + border-radius: 8px; + width: 100%; + height: 150px; + background-color: rgba(0, 153, 255, 0.1); + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} + +.illustration svg { + width: 60px; + height: 60px; +} diff --git a/plugins/repro/src/App.tsx b/plugins/repro/src/App.tsx new file mode 100644 index 00000000..da1d68a3 --- /dev/null +++ b/plugins/repro/src/App.tsx @@ -0,0 +1,85 @@ +import { framer, ManagedCollection } from "framer-plugin" + +import { useState } from "react" +import "./App.css" +import { simpleHash, slugify } from "./utils" + +const contentFieldId = "content" + +const html = ` +

example md file

+
import { z } from 'zod'
+
+
+` +export async function importData(collection: ManagedCollection) { + try { + await collection.setFields([ + { + type: "formattedText", + name: "Content", + id: contentFieldId, + }, + ]) + + const unseenItemIds = new Set(await collection.getItemIds()) + + // Remove all the items that weren't in the new feed + const itemsToDelete = Array.from(unseenItemIds) + await collection.removeItems(itemsToDelete) + await collection.addItems([ + { + id: simpleHash("x"), + slug: slugify("x"), + + fieldData: { + [contentFieldId]: html, + }, + }, + ]) + await framer.notify("Import successful!") + } catch (error: any) { + console.error("Error importing data:", error) + await framer.notify(error.message, { variant: "error" }) + return { error } + } +} + +interface Props { + collection: ManagedCollection +} + +interface Props { + collection: ManagedCollection +} + +export function App({ collection }: Props) { + const [isSyncing, setIsSyncing] = useState(false) + const [importError, setImportError] = useState(null) + + const handleImport = async () => { + setIsSyncing(true) + setImportError(null) + try { + const result = await importData(collection) + if (result && result.error) { + setImportError(result.error.message) + } + } finally { + setIsSyncing(false) + } + } + if (importError) { + return
{importError}
+ } + + return ( +
+

Issue repro.

+ + +
+ ) +} diff --git a/plugins/repro/src/icons.tsx b/plugins/repro/src/icons.tsx new file mode 100644 index 00000000..6baedb39 --- /dev/null +++ b/plugins/repro/src/icons.tsx @@ -0,0 +1,20 @@ +export function RSSIcon() { + return ( + + + + + + + + + + + + ) +} diff --git a/plugins/repro/src/main.tsx b/plugins/repro/src/main.tsx new file mode 100644 index 00000000..3140a0e0 --- /dev/null +++ b/plugins/repro/src/main.tsx @@ -0,0 +1,30 @@ +import "framer-plugin/framer.css" + +import { framer } from "framer-plugin" +import React from "react" +import ReactDOM from "react-dom/client" +import { App, importData } from "./App.tsx" + +async function runPlugin() { + const mode = framer.mode + const collection = await framer.getManagedCollection() + + const root = document.getElementById("root") + if (!root) { + throw new Error("Root element not found") + } + + framer.showUI({ + position: "top right", + width: 280, + height: 305, + }) + + ReactDOM.createRoot(root).render( + + + + ) +} + +runPlugin() diff --git a/plugins/repro/src/utils.ts b/plugins/repro/src/utils.ts new file mode 100644 index 00000000..d7d708d6 --- /dev/null +++ b/plugins/repro/src/utils.ts @@ -0,0 +1,17 @@ +const nonSlugCharactersRegExp = /[^\p{Letter}\p{Number}()]+/gu +// Match leading/trailing dashes, for trimming purposes. +const trimSlugRegExp = /^-+|-+$/gu + +export function slugify(value: string): string { + return value.toLowerCase().replace(nonSlugCharactersRegExp, "-").replace(trimSlugRegExp, "") +} + +export function simpleHash(input: string) { + let hash = 0 + for (let i = 0; i < input.length; i++) { + const char = input.charCodeAt(i) + hash = (hash << 5) - hash + char + hash = hash & hash // Convert to 32bit integer + } + return Math.abs(hash).toString(16).padStart(8, "0") +} diff --git a/plugins/repro/src/vite-env.d.ts b/plugins/repro/src/vite-env.d.ts new file mode 100644 index 00000000..11f02fe2 --- /dev/null +++ b/plugins/repro/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/plugins/repro/tsconfig.json b/plugins/repro/tsconfig.json new file mode 100644 index 00000000..4a46a4b4 --- /dev/null +++ b/plugins/repro/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"] +} diff --git a/plugins/repro/vite.config.ts b/plugins/repro/vite.config.ts new file mode 100644 index 00000000..d2c2c53a --- /dev/null +++ b/plugins/repro/vite.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from "vite" +import react from "@vitejs/plugin-react-swc" +import mkcert from "vite-plugin-mkcert" +import framer from "vite-plugin-framer" + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react(), mkcert(), framer()], +})