diff --git a/package-lock.json b/package-lock.json index 6151e571..54082abc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3317,6 +3317,10 @@ "langium": "3.3.1" } }, + "node_modules/@microsoft/chartifact-chrome-extension": { + "resolved": "packages/chrome-extension", + "link": true + }, "node_modules/@microsoft/chartifact-compiler": { "resolved": "packages/compiler", "link": true @@ -4235,6 +4239,17 @@ "@babel/types": "^7.28.2" } }, + "node_modules/@types/chrome": { + "version": "0.0.280", + "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.280.tgz", + "integrity": "sha512-AotSmZrL9bcZDDmSI1D9dE7PGbhOur5L0cKxXd7IqbVizQWCY4gcvupPUVsQ4FfDj3V2tt/iOpomT9EY0s+w1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/filesystem": "*", + "@types/har-format": "*" + } + }, "node_modules/@types/css-tree": { "version": "2.3.10", "resolved": "https://registry.npmjs.org/@types/css-tree/-/css-tree-2.3.10.tgz", @@ -4532,12 +4547,36 @@ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "license": "MIT" }, + "node_modules/@types/filesystem": { + "version": "0.0.36", + "resolved": "https://registry.npmjs.org/@types/filesystem/-/filesystem-0.0.36.tgz", + "integrity": "sha512-vPDXOZuannb9FZdxgHnqSwAG/jvdGM8Wq+6N4D/d80z+D4HWH+bItqsZaVRQykAn6WEVeEkLm2oQigyHtgb0RA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/filewriter": "*" + } + }, + "node_modules/@types/filewriter": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/@types/filewriter/-/filewriter-0.0.33.tgz", + "integrity": "sha512-xFU8ZXTw4gd358lb2jw25nxY9QAgqn2+bKKjKOYfNCzN4DKCFetK7sPtrlpg66Ywe3vWY9FNxprZawAh9wfJ3g==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/geojson": { "version": "7946.0.16", "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", "license": "MIT" }, + "node_modules/@types/har-format": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@types/har-format/-/har-format-1.2.16.tgz", + "integrity": "sha512-fluxdy7ryD3MV6h8pTfTYpy/xQzCFC7m89nOH9y94cNqJ1mDIDPut7MnRHI3F6qRmh/cT2fUjG1MLdCNb4hE9A==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/js-yaml": { "version": "4.0.9", "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", @@ -15307,6 +15346,525 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "packages/chrome-extension": { + "name": "@microsoft/chartifact-chrome-extension", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@microsoft/chartifact-compiler": "1.0.0", + "@microsoft/chartifact-sandbox": "1.0.0", + "@microsoft/chartifact-schema": "1.0.0" + }, + "devDependencies": { + "@types/chrome": "^0.0.280", + "vite": "^5.0.0" + } + }, + "packages/chrome-extension/node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "packages/chrome-extension/node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "packages/chrome-extension/node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "packages/chrome-extension/node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "packages/chrome-extension/node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "packages/chrome-extension/node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "packages/chrome-extension/node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "packages/chrome-extension/node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "packages/chrome-extension/node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/chrome-extension/node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/chrome-extension/node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/chrome-extension/node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/chrome-extension/node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/chrome-extension/node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/chrome-extension/node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/chrome-extension/node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/chrome-extension/node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/chrome-extension/node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "packages/chrome-extension/node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "packages/chrome-extension/node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "packages/chrome-extension/node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "packages/chrome-extension/node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "packages/chrome-extension/node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "packages/chrome-extension/node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "packages/chrome-extension/node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "packages/chrome-extension/node_modules/vite": { + "version": "5.4.19", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.19.tgz", + "integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, "packages/common": { "version": "1.0.0", "license": "MIT" @@ -15385,7 +15943,7 @@ }, "packages/vscode": { "name": "chartifact", - "version": "1.0.0", + "version": "1.0.2", "license": "MIT", "devDependencies": { "@vscode/vsce": "^3.6.0" diff --git a/packages/chrome-extension/.gitignore b/packages/chrome-extension/.gitignore new file mode 100644 index 00000000..2438fdad --- /dev/null +++ b/packages/chrome-extension/.gitignore @@ -0,0 +1,4 @@ +dist/ +*.zip +node_modules/ +.DS_Store \ No newline at end of file diff --git a/packages/chrome-extension/INSTALL.md b/packages/chrome-extension/INSTALL.md new file mode 100644 index 00000000..fff080eb --- /dev/null +++ b/packages/chrome-extension/INSTALL.md @@ -0,0 +1,108 @@ +# Installing the Chartifact Chrome Extension + +This guide helps you install and test the Chartifact Chrome extension for viewing Interactive Document files. + +## Quick Start + +### 1. Build the Extension + +From the repository root: + +```bash +# Install dependencies (if not already done) +npm install + +# Build the extension +npm run build --workspace=@microsoft/chartifact-chrome-extension +``` + +### 2. Load in Chrome + +1. Open Chrome and navigate to `chrome://extensions/` +2. Enable **Developer mode** (toggle in the top-right corner) +3. Click **Load unpacked** +4. Select the folder: `packages/chrome-extension/dist` +5. The extension should now appear in your extensions list + +### 3. Test the Extension + +**Option A: Use the test page** +1. Open `packages/chrome-extension/test.html` in Chrome +2. Click on one of the test file links +3. Look for the "📊 View as Interactive Document" button + +**Option B: Test with local files** +1. Download any `.idoc.json` or `.idoc.md` file +2. Open it in Chrome (drag and drop into browser) +3. The extension should detect it and show the view button + +### 4. Using the Extension + +- **Automatic Detection**: The extension automatically detects `.idoc.md` and `.idoc.json` files +- **View Button**: Look for the floating "📊 View as Interactive Document" button in the top-right +- **Extension Popup**: Click the extension icon to see status and controls +- **Secure Rendering**: All content is rendered in a sandboxed iframe for security + +## Example Files to Test + +You can test with these example files in the repository: +- `packages/examples/json/seattle-weather/6.idoc.json` +- `packages/chrome-extension/test.idoc.json` (simple test file) + +## Troubleshooting + +### Extension not detecting files +- Ensure the file has `.idoc.md` or `.idoc.json` extension +- Check that the extension is enabled in `chrome://extensions/` +- Try refreshing the page + +### View button not appearing +- Make sure you're viewing an actual idoc file (not just a page that links to one) +- Check browser console for any JavaScript errors +- Verify the extension has proper permissions + +### Viewer not loading +- Check browser console for errors +- Ensure the chartifact libraries are properly loaded +- Verify the file content is valid Interactive Document format + +### Permission issues +- The extension needs `activeTab` permission to function +- For local files, you may need to enable "Allow access to file URLs" in the extension details + +## For Developers + +### Building from Source +```bash +cd packages/chrome-extension +npm run build # Build the extension +npm run package # Create distributable ZIP +npm run dev # Watch mode for development +``` + +### Extension Structure +- `src/manifest.json` - Extension manifest +- `src/background.ts` - Service worker +- `src/content.ts` - Content script for detection and injection +- `src/popup.ts/html` - Extension popup interface +- `dist/` - Built extension (load this folder in Chrome) + +### Distribution +- Run `npm run package` to create `chartifact-chrome-extension.zip` +- This ZIP can be uploaded to Chrome Web Store or distributed manually + +## Security + +The extension follows security best practices: +- Minimal permissions (only `activeTab` and `storage`) +- Sandboxed iframe rendering +- No remote code execution +- Content Security Policy compliant + +## Support + +For issues or questions: +1. Check the browser console for error messages +2. Verify file format with the Interactive Document schema +3. Test with the provided example files first +4. Create an issue in the Chartifact repository \ No newline at end of file diff --git a/packages/chrome-extension/README.md b/packages/chrome-extension/README.md new file mode 100644 index 00000000..2c4f3c93 --- /dev/null +++ b/packages/chrome-extension/README.md @@ -0,0 +1,97 @@ +# Chartifact Chrome Extension + +A Chrome extension for viewing Interactive Document files (*.idoc.md and *.idoc.json) with rich visualizations and charts. + +## Features + +- **File Detection**: Automatically detects `.idoc.md` and `.idoc.json` files in web pages +- **Secure Rendering**: Uses sandboxed iframe rendering for security +- **Rich Visualizations**: Supports charts, graphs, and interactive elements +- **Easy Access**: Simple button to open the viewer overlay + +## Installation + +### For Development + +1. Build the extension: + ```bash + cd packages/chrome-extension + npm run build + ``` + +2. Load the extension in Chrome: + - Open Chrome and go to `chrome://extensions/` + - Enable "Developer mode" (toggle in top right) + - Click "Load unpacked" + - Select the `packages/chrome-extension/dist` folder + +### For Distribution + +1. Package the extension: + ```bash + cd packages/chrome-extension + npm run package + ``` + +2. This creates `chartifact-chrome-extension.zip` that can be uploaded to the Chrome Web Store. + +## Usage + +1. Navigate to any `.idoc.md` or `.idoc.json` file in your browser +2. The extension will automatically detect the file and show a "View as Interactive Document" button +3. Click the button to open the Chartifact viewer overlay +4. You can also use the extension popup (click the extension icon) to open the viewer + +## Supported File Types + +- **`.idoc.md`**: Interactive Document markdown files +- **`.idoc.json`**: Interactive Document JSON files + +## Technical Details + +The extension uses: +- **Manifest V3** for modern Chrome extension standards +- **Content Scripts** to detect and inject the viewer +- **Sandbox Rendering** for security using iframes +- **UMD Bundles** for the Chartifact compiler and sandbox libraries + +## Security + +The extension follows security best practices: +- Uses sandboxed iframe rendering to isolate document content +- Minimal permissions (only `activeTab` and `storage`) +- Content Security Policy compliance +- No remote code execution + +## Development + +### Build Process + +1. TypeScript compilation (`npm run tsc`) +2. Vite bundling (`npm run bundle`) +3. Resource copying (`npm run resources`) + +### File Structure + +``` +src/ +├── manifest.json # Extension manifest +├── background.ts # Service worker +├── content.ts # Content script +├── popup.ts # Popup script +└── popup.html # Popup HTML + +dist/ # Built extension +├── manifest.json +├── background.js +├── content.js +├── popup.js +├── popup.html +├── chartifact.sandbox.js +├── chartifact.compiler.js +└── icons/ +``` + +## License + +MIT License - see LICENSE file for details. \ No newline at end of file diff --git a/packages/chrome-extension/package.json b/packages/chrome-extension/package.json new file mode 100644 index 00000000..741a24ab --- /dev/null +++ b/packages/chrome-extension/package.json @@ -0,0 +1,30 @@ +{ + "name": "@microsoft/chartifact-chrome-extension", + "version": "1.0.0", + "description": "Chrome extension for viewing Interactive Document files (*.idoc.md and *.idoc.json)", + "type": "module", + "private": true, + "main": "dist/background.js", + "scripts": { + "clean": "rimraf dist", + "resources": "node scripts/resources.mjs", + "prebuild": "npm run resources", + "tsc": "tsc -p .", + "build": "npm run tsc && npm run bundle", + "bundle": "vite build", + "build:08": "npm run build", + "package": "cd dist && zip -r ../chartifact-chrome-extension.zip .", + "dev": "vite build --watch" + }, + "dependencies": { + "@microsoft/chartifact-sandbox": "1.0.0", + "@microsoft/chartifact-compiler": "1.0.0", + "@microsoft/chartifact-schema": "1.0.0" + }, + "devDependencies": { + "@types/chrome": "^0.0.280", + "vite": "^5.0.0" + }, + "author": "Dan Marshall", + "license": "MIT" +} \ No newline at end of file diff --git a/packages/chrome-extension/scripts/resources.mjs b/packages/chrome-extension/scripts/resources.mjs new file mode 100644 index 00000000..40057776 --- /dev/null +++ b/packages/chrome-extension/scripts/resources.mjs @@ -0,0 +1,84 @@ +/** + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const distDir = path.join(__dirname, '..', 'dist'); +const srcDir = path.join(__dirname, '..', 'src'); + +// Ensure dist directory exists +if (!fs.existsSync(distDir)) { + fs.mkdirSync(distDir, { recursive: true }); +} + +// Copy static files +const staticFiles = [ + 'manifest.json', + 'popup.html' +]; + +for (const file of staticFiles) { + const srcFile = path.join(srcDir, file); + const destFile = path.join(distDir, file); + + if (fs.existsSync(srcFile)) { + fs.copyFileSync(srcFile, destFile); + console.log(`Copied ${file}`); + } +} + +// Copy toolbar CSS +const toolbarCssSource = path.join(__dirname, '..', '..', 'toolbar', 'chartifact-toolbar.css'); +const toolbarCssDest = path.join(distDir, 'chartifact-toolbar.css'); +if (fs.existsSync(toolbarCssSource)) { + fs.copyFileSync(toolbarCssSource, toolbarCssDest); + console.log('Copied chartifact-toolbar.css'); +} + +// Copy UMD bundles for use in content script +const docsDir = path.join(__dirname, '..', '..', '..', 'docs', 'dist', 'v1'); +const bundleFiles = [ + 'chartifact.sandbox.umd.js', + 'chartifact.compiler.umd.js', + 'chartifact.host.umd.js' +]; + +for (const file of bundleFiles) { + const srcFile = path.join(docsDir, file); + const destFile = path.join(distDir, file.replace('.umd.js', '.js')); + + if (fs.existsSync(srcFile)) { + fs.copyFileSync(srcFile, destFile); + console.log(`Copied ${file} as ${path.basename(destFile)}`); + } else { + console.warn(`Bundle file not found: ${srcFile}`); + } +} + +// Create icons directory and placeholder icons +const iconsDir = path.join(distDir, 'icons'); +if (!fs.existsSync(iconsDir)) { + fs.mkdirSync(iconsDir, { recursive: true }); +} + +// Create simple text-based placeholder icons (in production, use actual PNG files) +const iconSizes = [16, 32, 48, 128]; +for (const size of iconSizes) { + const iconFile = path.join(iconsDir, `icon-${size}.png`); + + // Create a minimal fake PNG-like file to make Chrome happy + // In production, these should be proper PNG icon files + const header = `PNG ICON ${size}x${size}`; + const padding = Buffer.alloc(Math.max(0, 100 - header.length), 0); + const iconBuffer = Buffer.concat([Buffer.from(header), padding]); + fs.writeFileSync(iconFile, iconBuffer); +} + +console.log('Resources copied successfully'); \ No newline at end of file diff --git a/packages/chrome-extension/src/background.ts b/packages/chrome-extension/src/background.ts new file mode 100644 index 00000000..be7a5b26 --- /dev/null +++ b/packages/chrome-extension/src/background.ts @@ -0,0 +1,26 @@ +/** + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +// Background service worker for the Chrome extension +chrome.runtime.onInstalled.addListener(() => { + console.log('Chartifact Interactive Document Viewer installed'); +}); + +// Handle content script messages +chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { + if (request.type === 'IDOC_FILE_DETECTED') { + // Could show notification or update badge + console.log('Interactive Document file detected:', request.url); + sendResponse({ success: true }); + } +}); + +// Handle action button clicks +chrome.action.onClicked.addListener((tab) => { + // Open the viewer for the current tab if it's an idoc file + if (tab.url && (tab.url.includes('.idoc.md') || tab.url.includes('.idoc.json'))) { + chrome.tabs.sendMessage(tab.id!, { type: 'OPEN_VIEWER' }); + } +}); \ No newline at end of file diff --git a/packages/chrome-extension/src/content.ts b/packages/chrome-extension/src/content.ts new file mode 100644 index 00000000..f3ed99d3 --- /dev/null +++ b/packages/chrome-extension/src/content.ts @@ -0,0 +1,302 @@ +/** + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +// Content script that runs on pages with .idoc files +(function() { + 'use strict'; + + // Check if this is an Interactive Document file + function isIdocFile(): boolean { + const url = window.location.href; + return url.endsWith('.idoc.md') || url.endsWith('.idoc.json'); + } + + // Get file content from the page + function getFileContent(): string | null { + // For file:// URLs, the content is usually displayed in a
tag or plain text
+ const pre = document.querySelector('pre');
+ if (pre) {
+ return pre.textContent || '';
+ }
+
+ // Fallback to body text content
+ return document.body.textContent || '';
+ }
+
+ // Load external scripts
+ function loadScript(src: string): Promise {
+ return new Promise((resolve, reject) => {
+ const script = document.createElement('script');
+ script.src = chrome.runtime.getURL(src);
+ script.onload = () => resolve();
+ script.onerror = reject;
+ document.head.appendChild(script);
+ });
+ }
+
+ // Load CSS file
+ function loadCSS(href: string): void {
+ const link = document.createElement('link');
+ link.rel = 'stylesheet';
+ link.href = chrome.runtime.getURL(href);
+ document.head.appendChild(link);
+ }
+
+ // Inject the viewer interface using host/toolbar architecture
+ async function injectViewer(): Promise {
+ const content = getFileContent();
+ if (!content) {
+ console.warn('Could not extract file content');
+ return;
+ }
+
+ try {
+ // Load required scripts and CSS
+ await Promise.all([
+ loadScript('chartifact.sandbox.js'),
+ loadScript('chartifact.host.js'),
+ loadScript('chartifact.compiler.js')
+ ]);
+ loadCSS('chartifact-toolbar.css');
+
+ // Create overlay container with proper structure
+ const overlay = document.createElement('div');
+ overlay.id = 'chartifact-viewer-overlay';
+ overlay.style.cssText = `
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: white;
+ z-index: 10000;
+ display: flex;
+ flex-direction: column;
+ `;
+
+ // Create toolbar container
+ const toolbarContainer = document.createElement('div');
+ toolbarContainer.className = 'chartifact-toolbar';
+ toolbarContainer.style.cssText = `
+ background: #f5f5f5;
+ border-bottom: 1px solid #ddd;
+ padding: 10px 20px;
+ `;
+
+ // Create close button in toolbar
+ const closeButton = document.createElement('button');
+ closeButton.textContent = '✕ Close Viewer';
+ closeButton.style.cssText = `
+ background: #dc3545;
+ color: white;
+ border: none;
+ padding: 8px 16px;
+ cursor: pointer;
+ border-radius: 4px;
+ float: right;
+ margin-left: 10px;
+ `;
+ closeButton.onclick = () => overlay.remove();
+
+ // Create source textarea (hidden by default)
+ const textarea = document.createElement('textarea');
+ textarea.id = 'source';
+ textarea.style.cssText = `
+ width: 100%;
+ height: 200px;
+ font-family: monospace;
+ display: none;
+ resize: vertical;
+ border: 1px solid #ddd;
+ padding: 10px;
+ `;
+ textarea.value = content;
+
+ // Create main content containers
+ const mainContainer = document.createElement('div');
+ mainContainer.style.cssText = `
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+ `;
+
+ const loadingDiv = document.createElement('div');
+ loadingDiv.id = 'loading';
+ loadingDiv.style.cssText = `
+ text-align: center;
+ padding: 40px;
+ font-size: 18px;
+ `;
+ loadingDiv.textContent = 'Loading Interactive Document...';
+
+ const helpDiv = document.createElement('div');
+ helpDiv.id = 'help';
+ helpDiv.style.cssText = `
+ text-align: center;
+ padding: 40px;
+ display: none;
+ `;
+ helpDiv.textContent = 'Help information would go here';
+
+ const previewDiv = document.createElement('div');
+ previewDiv.id = 'preview';
+ previewDiv.style.cssText = `
+ flex: 1;
+ overflow: auto;
+ padding: 20px;
+ `;
+
+ // Assemble the UI
+ toolbarContainer.appendChild(closeButton);
+ mainContainer.appendChild(textarea);
+ mainContainer.appendChild(loadingDiv);
+ mainContainer.appendChild(helpDiv);
+ mainContainer.appendChild(previewDiv);
+ overlay.appendChild(toolbarContainer);
+ overlay.appendChild(mainContainer);
+ document.body.appendChild(overlay);
+
+ // Wait a moment for scripts to be available
+ await new Promise(resolve => setTimeout(resolve, 100));
+
+ // Access the global objects created by the UMD bundles
+ const Chartifact = (window as any).Chartifact;
+ if (!Chartifact || !Chartifact.host || !Chartifact.toolbar) {
+ throw new Error('Chartifact libraries not loaded properly');
+ }
+
+ // Initialize toolbar
+ const toolbar = new Chartifact.toolbar.Toolbar(toolbarContainer, {
+ textarea: textarea,
+ tweakButton: true,
+ downloadButton: true,
+ restartButton: false,
+ mode: window.location.href.endsWith('.idoc.json') ? 'json' : 'markdown',
+ filename: extractFilename(window.location.href)
+ });
+
+ // Initialize host listener
+ const host = new Chartifact.host.Listener({
+ preview: previewDiv,
+ loading: loadingDiv,
+ help: helpDiv,
+ toolbar: toolbar,
+ options: {
+ clipboard: false,
+ dragDrop: false,
+ fileUpload: false,
+ postMessage: false,
+ url: false
+ },
+ onApprove: (message: any) => {
+ // Approve all specs for viewing (no editing capabilities)
+ const { specs } = message;
+ return specs;
+ }
+ });
+
+ // Render the content
+ const url = window.location.href;
+ const filename = extractFilename(url);
+
+ if (url.endsWith('.idoc.json')) {
+ // Parse JSON and render as interactive document
+ try {
+ const interactiveDocument = JSON.parse(content);
+ host.render(filename, null, interactiveDocument, false);
+ } catch (error) {
+ host.errorHandler(error, 'Failed to parse JSON content');
+ }
+ } else {
+ // Render markdown directly
+ host.render(filename, content, null, false);
+ }
+
+ } catch (error) {
+ console.error('Failed to inject viewer:', error);
+ // Show error message
+ const errorDiv = document.createElement('div');
+ errorDiv.style.cssText = `
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background: white;
+ border: 2px solid red;
+ padding: 20px;
+ border-radius: 8px;
+ z-index: 10001;
+ max-width: 400px;
+ `;
+ errorDiv.innerHTML = `
+ Error Loading Viewer
+ ${error.message}
+
+ `;
+ document.body.appendChild(errorDiv);
+ }
+ }
+
+ // Extract filename from URL
+ function extractFilename(url: string): string {
+ const path = url.split('/').pop() || 'document';
+ return path.replace(/\?.*$/, ''); // Remove query parameters
+ }
+
+ // Add button to view the file
+ function addViewButton(): void {
+ const button = document.createElement('button');
+ button.textContent = '📊 View as Interactive Document';
+ button.style.cssText = `
+ position: fixed;
+ top: 20px;
+ right: 20px;
+ background: #007acc;
+ color: white;
+ border: none;
+ padding: 12px 20px;
+ cursor: pointer;
+ border-radius: 6px;
+ font-size: 14px;
+ font-weight: bold;
+ box-shadow: 0 2px 10px rgba(0,0,0,0.2);
+ z-index: 9999;
+ `;
+ button.onclick = injectViewer;
+ document.body.appendChild(button);
+ }
+
+ // Initialize the content script
+ function init(): void {
+ if (!isIdocFile()) {
+ return;
+ }
+
+ // Notify background script (using common message structure)
+ chrome.runtime.sendMessage({
+ type: 'IDOC_FILE_DETECTED',
+ url: window.location.href
+ });
+
+ // Add view button
+ addViewButton();
+
+ // Listen for messages from background script
+ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
+ if (request.type === 'OPEN_VIEWER') {
+ injectViewer();
+ sendResponse({ success: true });
+ }
+ });
+ }
+
+ // Run when DOM is ready
+ if (document.readyState === 'loading') {
+ document.addEventListener('DOMContentLoaded', init);
+ } else {
+ init();
+ }
+})();
\ No newline at end of file
diff --git a/packages/chrome-extension/src/manifest.json b/packages/chrome-extension/src/manifest.json
new file mode 100644
index 00000000..634e60c4
--- /dev/null
+++ b/packages/chrome-extension/src/manifest.json
@@ -0,0 +1,56 @@
+{
+ "manifest_version": 3,
+ "name": "Chartifact Interactive Document Viewer",
+ "version": "1.0.0",
+ "description": "View Interactive Document files (*.idoc.md and *.idoc.json) with rich visualizations",
+ "permissions": [
+ "activeTab",
+ "storage"
+ ],
+ "host_permissions": [
+ "file://*/*.idoc.md",
+ "file://*/*.idoc.json",
+ "https://*/*.idoc.md",
+ "https://*/*.idoc.json",
+ "http://*/*.idoc.md",
+ "http://*/*.idoc.json"
+ ],
+ "background": {
+ "service_worker": "background.js"
+ },
+ "content_scripts": [
+ {
+ "matches": [
+ "file://*/*.idoc.md",
+ "file://*/*.idoc.json",
+ "https://*/*.idoc.md",
+ "https://*/*.idoc.json",
+ "http://*/*.idoc.md",
+ "http://*/*.idoc.json"
+ ],
+ "js": ["content.js"],
+ "run_at": "document_end"
+ }
+ ],
+ "action": {
+ "default_popup": "popup.html",
+ "default_title": "Chartifact Viewer"
+ },
+ "web_accessible_resources": [
+ {
+ "resources": [
+ "chartifact.compiler.js",
+ "chartifact.sandbox.js",
+ "chartifact.host.js",
+ "chartifact-toolbar.css"
+ ],
+ "matches": [""]
+ }
+ ],
+ "icons": {
+ "16": "icons/icon-16.png",
+ "32": "icons/icon-32.png",
+ "48": "icons/icon-48.png",
+ "128": "icons/icon-128.png"
+ }
+}
\ No newline at end of file
diff --git a/packages/chrome-extension/src/popup.html b/packages/chrome-extension/src/popup.html
new file mode 100644
index 00000000..d9e90ada
--- /dev/null
+++ b/packages/chrome-extension/src/popup.html
@@ -0,0 +1,119 @@
+
+
+
+
+ Chartifact Viewer
+
+
+
+
+ 📊
+ Chartifact
+ Interactive Document Viewer
+
+
+
+
+ Checking current page...
+
+
+
+
+
+
+
+
+
+ This extension works with .idoc.md and .idoc.json files. Navigate to an Interactive Document file to view it with rich visualizations.
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/chrome-extension/src/popup.ts b/packages/chrome-extension/src/popup.ts
new file mode 100644
index 00000000..19379230
--- /dev/null
+++ b/packages/chrome-extension/src/popup.ts
@@ -0,0 +1,70 @@
+/**
+ * Copyright (c) Microsoft Corporation.
+ * Licensed under the MIT License.
+ */
+
+// Popup script for the Chrome extension
+document.addEventListener('DOMContentLoaded', async () => {
+ const statusEl = document.getElementById('status')!;
+ const openViewerBtn = document.getElementById('openViewer')! as HTMLButtonElement;
+ const refreshStatusBtn = document.getElementById('refreshStatus')! as HTMLButtonElement;
+
+ // Check if current tab has an idoc file
+ async function checkCurrentTab(): Promise<{ isIdocFile: boolean; url: string }> {
+ try {
+ const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
+ const url = tab.url || '';
+ const isIdocFile = url.endsWith('.idoc.md') || url.endsWith('.idoc.json');
+ return { isIdocFile, url };
+ } catch (error) {
+ console.error('Error checking current tab:', error);
+ return { isIdocFile: false, url: '' };
+ }
+ }
+
+ // Update the status display
+ function updateStatus(isIdocFile: boolean, url: string): void {
+ if (isIdocFile) {
+ statusEl.className = 'status detected';
+ statusEl.textContent = '✅ Interactive Document detected!';
+ openViewerBtn.disabled = false;
+ } else {
+ statusEl.className = 'status not-detected';
+ if (url.startsWith('chrome://') || url.startsWith('chrome-extension://')) {
+ statusEl.textContent = '📋 Navigate to an .idoc file to use the viewer';
+ } else {
+ statusEl.textContent = '📄 No Interactive Document on this page';
+ }
+ openViewerBtn.disabled = true;
+ }
+ }
+
+ // Open the viewer on the current tab
+ async function openViewer(): Promise {
+ try {
+ const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
+ if (tab.id) {
+ await chrome.tabs.sendMessage(tab.id, { type: 'OPEN_VIEWER' });
+ window.close(); // Close the popup
+ }
+ } catch (error) {
+ console.error('Error opening viewer:', error);
+ statusEl.className = 'status not-detected';
+ statusEl.textContent = '❌ Error opening viewer';
+ }
+ }
+
+ // Refresh the status
+ async function refreshStatus(): Promise {
+ statusEl.textContent = 'Checking...';
+ const { isIdocFile, url } = await checkCurrentTab();
+ updateStatus(isIdocFile, url);
+ }
+
+ // Event listeners
+ openViewerBtn.addEventListener('click', openViewer);
+ refreshStatusBtn.addEventListener('click', refreshStatus);
+
+ // Initial status check
+ await refreshStatus();
+});
\ No newline at end of file
diff --git a/packages/chrome-extension/test-extension.html b/packages/chrome-extension/test-extension.html
new file mode 100644
index 00000000..d994a8d8
--- /dev/null
+++ b/packages/chrome-extension/test-extension.html
@@ -0,0 +1,49 @@
+
+
+
+ Test Chrome Extension - test-simple.idoc.md
+
+
+ # Simple Test Document
+
+This is a test Interactive Document.
+
+```echarts
+{
+ "title": { "text": "Simple Chart" },
+ "xAxis": { "type": "category", "data": ["A", "B", "C"] },
+ "yAxis": { "type": "value" },
+ "series": [{ "data": [10, 20, 30], "type": "bar" }]
+}
+```
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/chrome-extension/test-simple.idoc.md b/packages/chrome-extension/test-simple.idoc.md
new file mode 100644
index 00000000..0113e1d7
--- /dev/null
+++ b/packages/chrome-extension/test-simple.idoc.md
@@ -0,0 +1,12 @@
+# Simple Test Document
+
+This is a test Interactive Document.
+
+```echarts
+{
+ "title": { "text": "Simple Chart" },
+ "xAxis": { "type": "category", "data": ["A", "B", "C"] },
+ "yAxis": { "type": "value" },
+ "series": [{ "data": [10, 20, 30], "type": "bar" }]
+}
+```
\ No newline at end of file
diff --git a/packages/chrome-extension/test.html b/packages/chrome-extension/test.html
new file mode 100644
index 00000000..b0b4ac08
--- /dev/null
+++ b/packages/chrome-extension/test.html
@@ -0,0 +1,84 @@
+
+
+
+ Interactive Document Test
+
+
+
+
+ Chartifact Chrome Extension Test Page
+ This page helps you test the Chartifact Chrome extension with sample Interactive Document files.
+
+
+
+ 📋 Instructions
+
+ - Install the Chrome extension by loading the
packages/chrome-extension/dist folder as an unpacked extension
+ - Click one of the test files below
+ - Look for the "📊 View as Interactive Document" button in the top-right corner
+ - Click the button to open the Chartifact viewer
+
+
+
+ Test Files
+
+
+ Extension Status
+ If the extension is properly installed and enabled, it will:
+
+ - Automatically detect .idoc.md and .idoc.json files
+ - Show a floating "View as Interactive Document" button
+ - Allow you to open an overlay viewer with rich visualizations
+
+
+ Troubleshooting
+
+ - Button not appearing? Make sure the extension is enabled and has permissions
+ - Viewer not loading? Check the browser console for errors
+ - Charts not rendering? Ensure the file content is valid Interactive Document format
+
+
+
+ Note: This is a development test page. In production, the extension would work with any .idoc files hosted on the web or viewed locally via file:// URLs.
+
+
+
\ No newline at end of file
diff --git a/packages/chrome-extension/test.idoc.json b/packages/chrome-extension/test.idoc.json
new file mode 100644
index 00000000..5f24734f
--- /dev/null
+++ b/packages/chrome-extension/test.idoc.json
@@ -0,0 +1,37 @@
+{
+ "$schema": "https://raw.githubusercontent.com/microsoft/chartifact/main/docs/schema/idoc_v1.json",
+ "title": "Chrome Extension Test",
+ "dataLoaders": [
+ {
+ "type": "data",
+ "data": [
+ { "category": "A", "value": 28 },
+ { "category": "B", "value": 55 },
+ { "category": "C", "value": 43 }
+ ],
+ "dataSourceName": "testData"
+ }
+ ],
+ "groups": [
+ {
+ "groupId": "main",
+ "elements": [
+ "# Chrome Extension Test",
+ "This is a test Interactive Document for the Chrome extension.",
+ {
+ "elementId": "chart1",
+ "type": "chart",
+ "dataSource": "testData",
+ "spec": {
+ "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
+ "mark": "bar",
+ "encoding": {
+ "x": { "field": "category", "type": "nominal" },
+ "y": { "field": "value", "type": "quantitative" }
+ }
+ }
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/packages/chrome-extension/tsconfig.json b/packages/chrome-extension/tsconfig.json
new file mode 100644
index 00000000..847b74d9
--- /dev/null
+++ b/packages/chrome-extension/tsconfig.json
@@ -0,0 +1,20 @@
+{
+ "extends": "../tsconfig.base.json",
+ "compilerOptions": {
+ "outDir": "./dist",
+ "module": "esnext",
+ "moduleResolution": "bundler",
+ "target": "es2020",
+ "lib": ["es2020", "dom"],
+ "types": ["chrome"],
+ "allowSyntheticDefaultImports": true,
+ "esModuleInterop": true
+ },
+ "include": [
+ "src/**/*"
+ ],
+ "exclude": [
+ "dist",
+ "node_modules"
+ ]
+}
\ No newline at end of file
diff --git a/packages/chrome-extension/vite.config.js b/packages/chrome-extension/vite.config.js
new file mode 100644
index 00000000..b0850fbc
--- /dev/null
+++ b/packages/chrome-extension/vite.config.js
@@ -0,0 +1,29 @@
+import { defineConfig } from 'vite';
+import { resolve } from 'path';
+
+export default defineConfig({
+ build: {
+ outDir: 'dist',
+ rollupOptions: {
+ input: {
+ background: resolve(__dirname, 'src/background.ts'),
+ content: resolve(__dirname, 'src/content.ts'),
+ popup: resolve(__dirname, 'src/popup.ts'),
+ },
+ output: {
+ entryFileNames: '[name].js',
+ format: 'es'
+ },
+ external: [
+ '@microsoft/chartifact-sandbox',
+ '@microsoft/chartifact-compiler'
+ ]
+ },
+ target: 'es2020',
+ minify: false, // Keep readable for debugging
+ sourcemap: false
+ },
+ define: {
+ 'process.env.NODE_ENV': '"production"'
+ }
+});
\ No newline at end of file