diff --git a/SharePoint/SharePoint/.eslintrc.js b/SharePoint/SharePoint/.eslintrc.js new file mode 100644 index 0000000..562b8f6 --- /dev/null +++ b/SharePoint/SharePoint/.eslintrc.js @@ -0,0 +1,319 @@ +require('@rushstack/eslint-config/patch/modern-module-resolution'); +module.exports = { + extends: ['@microsoft/eslint-config-spfx/lib/profiles/default'], + parserOptions: { tsconfigRootDir: __dirname }, + overrides: [ + { + files: ['*.ts', '*.tsx'], + parser: '@typescript-eslint/parser', + 'parserOptions': { + 'project': './tsconfig.json', + 'ecmaVersion': 2018, + 'sourceType': 'module' + }, + rules: { + // Prevent usage of the JavaScript null value, while allowing code to access existing APIs that may require null. https://www.npmjs.com/package/@rushstack/eslint-plugin + '@rushstack/no-new-null': 1, + // Require Jest module mocking APIs to be called before any other statements in their code block. https://www.npmjs.com/package/@rushstack/eslint-plugin + '@rushstack/hoist-jest-mock': 1, + // Require regular expressions to be constructed from string constants rather than dynamically building strings at runtime. https://www.npmjs.com/package/@rushstack/eslint-plugin-security + '@rushstack/security/no-unsafe-regexp': 1, + // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json + '@typescript-eslint/adjacent-overload-signatures': 1, + // RATIONALE: Code is more readable when the type of every variable is immediately obvious. + // Even if the compiler may be able to infer a type, this inference will be unavailable + // to a person who is reviewing a GitHub diff. This rule makes writing code harder, + // but writing code is a much less important activity than reading it. + // + // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json + '@typescript-eslint/explicit-function-return-type': [ + 1, + { + 'allowExpressions': true, + 'allowTypedFunctionExpressions': true, + 'allowHigherOrderFunctions': false + } + ], + // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json + // Rationale to disable: although this is a recommended rule, it is up to dev to select coding style. + // Set to 1 (warning) or 2 (error) to enable. + '@typescript-eslint/explicit-member-accessibility': 0, + // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json + '@typescript-eslint/no-array-constructor': 1, + // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json + // + // RATIONALE: The "any" keyword disables static type checking, the main benefit of using TypeScript. + // This rule should be suppressed only in very special cases such as JSON.stringify() + // where the type really can be anything. Even if the type is flexible, another type + // may be more appropriate such as "unknown", "{}", or "Record". + '@typescript-eslint/no-explicit-any': 1, + // RATIONALE: The #1 rule of promises is that every promise chain must be terminated by a catch() + // handler. Thus wherever a Promise arises, the code must either append a catch handler, + // or else return the object to a caller (who assumes this responsibility). Unterminated + // promise chains are a serious issue. Besides causing errors to be silently ignored, + // they can also cause a NodeJS process to terminate unexpectedly. + '@typescript-eslint/no-floating-promises': 2, + // RATIONALE: Catches a common coding mistake. + '@typescript-eslint/no-for-in-array': 2, + // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json + '@typescript-eslint/no-misused-new': 2, + // RATIONALE: The "namespace" keyword is not recommended for organizing code because JavaScript lacks + // a "using" statement to traverse namespaces. Nested namespaces prevent certain bundler + // optimizations. If you are declaring loose functions/variables, it's better to make them + // static members of a class, since classes support property getters and their private + // members are accessible by unit tests. Also, the exercise of choosing a meaningful + // class name tends to produce more discoverable APIs: for example, search+replacing + // the function "reverse()" is likely to return many false matches, whereas if we always + // write "Text.reverse()" is more unique. For large scale organization, it's recommended + // to decompose your code into separate NPM packages, which ensures that component + // dependencies are tracked more conscientiously. + // + // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json + '@typescript-eslint/no-namespace': [ + 1, + { + 'allowDeclarations': false, + 'allowDefinitionFiles': false + } + ], + // RATIONALE: Parameter properties provide a shorthand such as "constructor(public title: string)" + // that avoids the effort of declaring "title" as a field. This TypeScript feature makes + // code easier to write, but arguably sacrifices readability: In the notes for + // "@typescript-eslint/member-ordering" we pointed out that fields are central to + // a class's design, so we wouldn't want to bury them in a constructor signature + // just to save some typing. + // + // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json + // Set to 1 (warning) or 2 (error) to enable the rule + '@typescript-eslint/parameter-properties': 0, + // RATIONALE: When left in shipping code, unused variables often indicate a mistake. Dead code + // may impact performance. + // + // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json + '@typescript-eslint/no-unused-vars': [ + 1, + { + 'vars': 'all', + // Unused function arguments often indicate a mistake in JavaScript code. However in TypeScript code, + // the compiler catches most of those mistakes, and unused arguments are fairly common for type signatures + // that are overriding a base class method or implementing an interface. + 'args': 'none' + } + ], + // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json + '@typescript-eslint/no-use-before-define': [ + 2, + { + 'functions': false, + 'classes': true, + 'variables': true, + 'enums': true, + 'typedefs': true + } + ], + // Disallows require statements except in import statements. + // In other words, the use of forms such as var foo = require("foo") are banned. Instead use ES6 style imports or import foo = require("foo") imports. + '@typescript-eslint/no-var-requires': 'error', + // RATIONALE: The "module" keyword is deprecated except when describing legacy libraries. + // + // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json + '@typescript-eslint/prefer-namespace-keyword': 1, + // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json + // Rationale to disable: it's up to developer to decide if he wants to add type annotations + // Set to 1 (warning) or 2 (error) to enable the rule + '@typescript-eslint/no-inferrable-types': 0, + // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json + // Rationale to disable: declaration of empty interfaces may be helpful for generic types scenarios + '@typescript-eslint/no-empty-interface': 0, + // RATIONALE: This rule warns if setters are defined without getters, which is probably a mistake. + 'accessor-pairs': 1, + // RATIONALE: In TypeScript, if you write x["y"] instead of x.y, it disables type checking. + 'dot-notation': [ + 1, + { + 'allowPattern': '^_' + } + ], + // RATIONALE: Catches code that is likely to be incorrect + 'eqeqeq': 1, + // STANDARDIZED BY: eslint\conf\eslint-recommended.js + 'for-direction': 1, + // RATIONALE: Catches a common coding mistake. + 'guard-for-in': 2, + // RATIONALE: If you have more than 2,000 lines in a single source file, it's probably time + // to split up your code. + 'max-lines': ['warn', { max: 2000 }], + // STANDARDIZED BY: eslint\conf\eslint-recommended.js + 'no-async-promise-executor': 2, + // RATIONALE: Deprecated language feature. + 'no-caller': 2, + // STANDARDIZED BY: eslint\conf\eslint-recommended.js + 'no-compare-neg-zero': 2, + // STANDARDIZED BY: eslint\conf\eslint-recommended.js + 'no-cond-assign': 2, + // STANDARDIZED BY: eslint\conf\eslint-recommended.js + 'no-constant-condition': 1, + // STANDARDIZED BY: eslint\conf\eslint-recommended.js + 'no-control-regex': 2, + // STANDARDIZED BY: eslint\conf\eslint-recommended.js + 'no-debugger': 1, + // STANDARDIZED BY: eslint\conf\eslint-recommended.js + 'no-delete-var': 2, + // RATIONALE: Catches code that is likely to be incorrect + // STANDARDIZED BY: eslint\conf\eslint-recommended.js + 'no-duplicate-case': 2, + // STANDARDIZED BY: eslint\conf\eslint-recommended.js + 'no-empty': 1, + // STANDARDIZED BY: eslint\conf\eslint-recommended.js + 'no-empty-character-class': 2, + // STANDARDIZED BY: eslint\conf\eslint-recommended.js + 'no-empty-pattern': 1, + // RATIONALE: Eval is a security concern and a performance concern. + 'no-eval': 1, + // RATIONALE: Catches code that is likely to be incorrect + // STANDARDIZED BY: eslint\conf\eslint-recommended.js + 'no-ex-assign': 2, + // RATIONALE: System types are global and should not be tampered with in a scalable code base. + // If two different libraries (or two versions of the same library) both try to modify + // a type, only one of them can win. Polyfills are acceptable because they implement + // a standardized interoperable contract, but polyfills are generally coded in plain + // JavaScript. + 'no-extend-native': 1, + // Disallow unnecessary labels + 'no-extra-label': 1, + // STANDARDIZED BY: eslint\conf\eslint-recommended.js + 'no-fallthrough': 2, + // STANDARDIZED BY: eslint\conf\eslint-recommended.js + 'no-func-assign': 1, + // RATIONALE: Catches a common coding mistake. + 'no-implied-eval': 2, + // STANDARDIZED BY: eslint\conf\eslint-recommended.js + 'no-invalid-regexp': 2, + // RATIONALE: Catches a common coding mistake. + 'no-label-var': 2, + // RATIONALE: Eliminates redundant code. + 'no-lone-blocks': 1, + // STANDARDIZED BY: eslint\conf\eslint-recommended.js + 'no-misleading-character-class': 2, + // RATIONALE: Catches a common coding mistake. + 'no-multi-str': 2, + // RATIONALE: It's generally a bad practice to call "new Thing()" without assigning the result to + // a variable. Either it's part of an awkward expression like "(new Thing()).doSomething()", + // or else implies that the constructor is doing nontrivial computations, which is often + // a poor class design. + 'no-new': 1, + // RATIONALE: Obsolete language feature that is deprecated. + 'no-new-func': 2, + // RATIONALE: Obsolete language feature that is deprecated. + 'no-new-object': 2, + // RATIONALE: Obsolete notation. + 'no-new-wrappers': 1, + // RATIONALE: Catches code that is likely to be incorrect + // STANDARDIZED BY: eslint\conf\eslint-recommended.js + 'no-octal': 2, + // RATIONALE: Catches code that is likely to be incorrect + 'no-octal-escape': 2, + // RATIONALE: Catches code that is likely to be incorrect + // STANDARDIZED BY: eslint\conf\eslint-recommended.js + 'no-regex-spaces': 2, + // RATIONALE: Catches a common coding mistake. + 'no-return-assign': 2, + // RATIONALE: Security risk. + 'no-script-url': 1, + // STANDARDIZED BY: eslint\conf\eslint-recommended.js + 'no-self-assign': 2, + // RATIONALE: Catches a common coding mistake. + 'no-self-compare': 2, + // RATIONALE: This avoids statements such as "while (a = next(), a && a.length);" that use + // commas to create compound expressions. In general code is more readable if each + // step is split onto a separate line. This also makes it easier to set breakpoints + // in the debugger. + 'no-sequences': 1, + // RATIONALE: Catches code that is likely to be incorrect + // STANDARDIZED BY: eslint\conf\eslint-recommended.js + 'no-shadow-restricted-names': 2, + // RATIONALE: Obsolete language feature that is deprecated. + // STANDARDIZED BY: eslint\conf\eslint-recommended.js + 'no-sparse-arrays': 2, + // RATIONALE: Although in theory JavaScript allows any possible data type to be thrown as an exception, + // such flexibility adds pointless complexity, by requiring every catch block to test + // the type of the object that it receives. Whereas if catch blocks can always assume + // that their object implements the "Error" contract, then the code is simpler, and + // we generally get useful additional information like a call stack. + 'no-throw-literal': 2, + // RATIONALE: Catches a common coding mistake. + 'no-unmodified-loop-condition': 1, + // STANDARDIZED BY: eslint\conf\eslint-recommended.js + 'no-unsafe-finally': 2, + // RATIONALE: Catches a common coding mistake. + 'no-unused-expressions': 1, + // STANDARDIZED BY: eslint\conf\eslint-recommended.js + 'no-unused-labels': 1, + // STANDARDIZED BY: eslint\conf\eslint-recommended.js + 'no-useless-catch': 1, + // RATIONALE: Avoids a potential performance problem. + 'no-useless-concat': 1, + // RATIONALE: The "var" keyword is deprecated because of its confusing "hoisting" behavior. + // Always use "let" or "const" instead. + // + // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json + 'no-var': 2, + // RATIONALE: Generally not needed in modern code. + 'no-void': 1, + // RATIONALE: Obsolete language feature that is deprecated. + // STANDARDIZED BY: eslint\conf\eslint-recommended.js + 'no-with': 2, + // RATIONALE: Makes logic easier to understand, since constants always have a known value + // @typescript-eslint\eslint-plugin\dist\configs\eslint-recommended.js + 'prefer-const': 1, + // RATIONALE: Catches a common coding mistake where "resolve" and "reject" are confused. + 'promise/param-names': 2, + // RATIONALE: Catches code that is likely to be incorrect + // STANDARDIZED BY: eslint\conf\eslint-recommended.js + 'require-atomic-updates': 2, + // STANDARDIZED BY: eslint\conf\eslint-recommended.js + 'require-yield': 1, + // "Use strict" is redundant when using the TypeScript compiler. + 'strict': [ + 2, + 'never' + ], + // RATIONALE: Catches code that is likely to be incorrect + // STANDARDIZED BY: eslint\conf\eslint-recommended.js + 'use-isnan': 2, + // STANDARDIZED BY: eslint\conf\eslint-recommended.js + // Set to 1 (warning) or 2 (error) to enable. + // Rationale to disable: !!{} + 'no-extra-boolean-cast': 0, + // ==================================================================== + // @microsoft/eslint-plugin-spfx + // ==================================================================== + '@microsoft/spfx/import-requires-chunk-name': 1, + '@microsoft/spfx/no-require-ensure': 2, + '@microsoft/spfx/pair-react-dom-render-unmount': 1 + } + }, + { + // For unit tests, we can be a little bit less strict. The settings below revise the + // defaults specified in the extended configurations, as well as above. + files: [ + // Test files + '*.test.ts', + '*.test.tsx', + '*.spec.ts', + '*.spec.tsx', + + // Facebook convention + '**/__mocks__/*.ts', + '**/__mocks__/*.tsx', + '**/__tests__/*.ts', + '**/__tests__/*.tsx', + + // Microsoft convention + '**/test/*.ts', + '**/test/*.tsx' + ], + rules: {} + } + ] +}; \ No newline at end of file diff --git a/SharePoint/SharePoint/.gitignore b/SharePoint/SharePoint/.gitignore new file mode 100644 index 0000000..51ca7b9 --- /dev/null +++ b/SharePoint/SharePoint/.gitignore @@ -0,0 +1,34 @@ +# Logs +logs +*.log +npm-debug.log* + +# Dependency directories +node_modules + +# Build generated files +dist +lib +release +solution +temp +*.sppkg +.heft + +# Coverage directory used by tools like istanbul +coverage + +# OSX +.DS_Store + +# Visual Studio files +.ntvs_analysis.dat +.vs +bin +obj + +# Resx Generated Code +*.resx.ts + +# Styles Generated Code +*.scss.ts diff --git a/SharePoint/SharePoint/.npmignore b/SharePoint/SharePoint/.npmignore new file mode 100644 index 0000000..ae0b487 --- /dev/null +++ b/SharePoint/SharePoint/.npmignore @@ -0,0 +1,16 @@ +!dist +config + +gulpfile.js + +release +src +temp + +tsconfig.json +tslint.json + +*.log + +.yo-rc.json +.vscode diff --git a/SharePoint/SharePoint/.yo-rc.json b/SharePoint/SharePoint/.yo-rc.json new file mode 100644 index 0000000..5888527 --- /dev/null +++ b/SharePoint/SharePoint/.yo-rc.json @@ -0,0 +1,21 @@ +{ + "@microsoft/generator-sharepoint": { + "plusBeta": false, + "isCreatingSolution": true, + "nodeVersion": "20.18.2", + "sdksVersions": { + "@microsoft/microsoft-graph-client": "3.0.2", + "@microsoft/teams-js": "2.24.0" + }, + "version": "1.20.0", + "libraryName": "pdfviewer-spfx", + "libraryId": "756ebdc3-ddeb-495b-b837-e56bc0ee5c70", + "environment": "spo", + "packageManager": "npm", + "solutionName": "pdfviewer-spfx", + "solutionShortDescription": "pdfviewer-spfx description", + "skipFeatureDeployment": true, + "isDomainIsolated": false, + "componentType": "webpart" + } +} diff --git a/SharePoint/SharePoint/README.md b/SharePoint/SharePoint/README.md new file mode 100644 index 0000000..424b359 --- /dev/null +++ b/SharePoint/SharePoint/README.md @@ -0,0 +1,98 @@ +# SharePoint PDF Viewer Web Part + +A SharePoint Framework (SPFx) web part that integrates **Syncfusion EJ2 PDF Viewer** for viewing PDF documents stored in SharePoint. This application provides a view-only mode with a user-friendly interface for document selection and navigation. + +## 📦 Installation + +### Prerequisites +- A SharePoint development environment and a Microsoft 365 tenant (for testing/deployment). +- Node.js compatible with your SPFx version (check SPFx docs). This application requires 20.18.2 +- Gulp: `npm install -g gulp-cli`. + +### Setup Steps + +1. **Clone/Extract the project** + ```bash + cd typescript-pdf-viewer-examples/SharePoint + ``` + +2. **Install dependencies** + ```bash + npm install + ``` + +3. **Configure PDF Source Location** + Edit `src/webparts/pdfviewer/PdfviewerWebPart.ts`: + - Replace `YOUR-SHAREPOINT-SITE` with your SharePoint site URL + - Replace `{your-site-name}` with your site name + - Replace `{documents-containing-path}` with the path to your PDF documents folder + + ```typescript + const url = `${targetSite}/_api/web/GetFolderByServerRelativeUrl('/sites/{your-site-name}/{documents-containing-path}')/Files`; + ``` + +4. **Configure Resource URL** (Optional) + Update the `resourceUrl` in the PDF Viewer initialization: + ```typescript + resourceUrl: '${YOUR-LOCATION-FOR-RESOURCE}/ej2-pdfviewer-lib' + ``` + +## Run the application + +### Development Build + +```bash +gulp serve +``` + +The sample will be hosted in `https://{tenantDomain}/_layouts/workbench.aspx`. + +## 📖 Usage + +### Adding the Web Part to a Page + +1. Navigate to a SharePoint page (modern or full page) +2. Click **Edit** to enter edit mode +3. Click **+ Add a new web part** +4. Search for **"pdfviewer"** (labeled as "Advanced" category) +5. Click to add the web part +6. The web part will load available PDF documents from the configured location +7. Use the dropdown menu to select a PDF document +8. The selected document will load in the viewer + +### Web Part Configuration + +**Property Pane Settings** +- Navigate to the web part menu → **Edit web part** +- **Basic Settings**: + - **Description**: Add a description for the web part (for reference) + +### Toolbar Options (View-Only Mode) + +The toolbar displays the following tools: +- **Open Option**: Load a different document +- **Page Navigation Tool**: Jump to specific pages +- **Magnification Tool**: Zoom controls +- **Pan Tool**: Navigate large pages +- **Print Option**: Print the document + +## View-Only Mode + +### Form Fields +All form fields are automatically set to **read-only** when the document loads: +```typescript +viewer.formDesignerModule.updateFormField(viewer.formFieldCollections[x], { + isReadOnly: true, +}); +``` + +### Disabled Features +- Annotations and sticky notes +- Page organizer +- Context menu interactions +- Direct form field editing + +## 🔗 Resources + +- [Syncfusion Javascript PDF Viewer](https://www.syncfusion.com/pdf-viewer-sdk/javascript-pdf-viewer) +- [Documentation](https://help.syncfusion.com/document-processing/pdf/pdf-viewer/react/depoyment-integration/share-point) \ No newline at end of file diff --git a/SharePoint/SharePoint/config/config.json b/SharePoint/SharePoint/config/config.json new file mode 100644 index 0000000..3d7c825 --- /dev/null +++ b/SharePoint/SharePoint/config/config.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json", + "version": "2.0", + "bundles": { + "pdfviewer-web-part": { + "components": [ + { + "entrypoint": "./lib/webparts/pdfviewer/PdfviewerWebPart.js", + "manifest": "./src/webparts/pdfviewer/PdfviewerWebPart.manifest.json" + } + ] + } + }, + "externals": {}, + "localizedResources": { + "PdfviewerWebPartStrings": "lib/webparts/pdfviewer/loc/{locale}.js" + } +} diff --git a/SharePoint/SharePoint/config/deploy-azure-storage.json b/SharePoint/SharePoint/config/deploy-azure-storage.json new file mode 100644 index 0000000..d745a6c --- /dev/null +++ b/SharePoint/SharePoint/config/deploy-azure-storage.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json", + "workingDir": "./release/assets/", + "account": "", + "container": "pdfviewer-spfx", + "accessKey": "" +} \ No newline at end of file diff --git a/SharePoint/SharePoint/config/package-solution.json b/SharePoint/SharePoint/config/package-solution.json new file mode 100644 index 0000000..2b69200 --- /dev/null +++ b/SharePoint/SharePoint/config/package-solution.json @@ -0,0 +1,40 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json", + "solution": { + "name": "pdfviewer-spfx-client-side-solution", + "id": "756ebdc3-ddeb-495b-b837-e56bc0ee5c70", + "version": "1.0.0.0", + "includeClientSideAssets": true, + "skipFeatureDeployment": true, + "isDomainIsolated": false, + "developer": { + "name": "", + "websiteUrl": "", + "privacyUrl": "", + "termsOfUseUrl": "", + "mpnId": "Undefined-1.20.0" + }, + "metadata": { + "shortDescription": { + "default": "pdfviewer-spfx description" + }, + "longDescription": { + "default": "pdfviewer-spfx description" + }, + "screenshotPaths": [], + "videoUrl": "", + "categories": [] + }, + "features": [ + { + "title": "pdfviewer-spfx Feature", + "description": "The feature that activates elements of the pdfviewer-spfx solution.", + "id": "c3b9ea15-4bf5-4e7d-abe4-2b039c7de9d0", + "version": "1.0.0.0" + } + ] + }, + "paths": { + "zippedPackage": "solution/pdfviewer-spfx.sppkg" + } +} diff --git a/SharePoint/SharePoint/config/sass.json b/SharePoint/SharePoint/config/sass.json new file mode 100644 index 0000000..5e78c98 --- /dev/null +++ b/SharePoint/SharePoint/config/sass.json @@ -0,0 +1,3 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/core-build/sass.schema.json" +} \ No newline at end of file diff --git a/SharePoint/SharePoint/config/serve.json b/SharePoint/SharePoint/config/serve.json new file mode 100644 index 0000000..a4c03e2 --- /dev/null +++ b/SharePoint/SharePoint/config/serve.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/spfx-serve.schema.json", + "port": 4321, + "https": true, + "initialPage": "https://{tenantDomain}/_layouts/workbench.aspx" +} diff --git a/SharePoint/SharePoint/config/write-manifests.json b/SharePoint/SharePoint/config/write-manifests.json new file mode 100644 index 0000000..bad3526 --- /dev/null +++ b/SharePoint/SharePoint/config/write-manifests.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json", + "cdnBasePath": "" +} \ No newline at end of file diff --git a/SharePoint/SharePoint/gulpfile.js b/SharePoint/SharePoint/gulpfile.js new file mode 100644 index 0000000..be29187 --- /dev/null +++ b/SharePoint/SharePoint/gulpfile.js @@ -0,0 +1,16 @@ +'use strict'; + +const build = require('@microsoft/sp-build-web'); + +build.addSuppression(`Warning - [sass] The local CSS class 'ms-Grid' is not camelCase and will not be type-safe.`); + +var getTasks = build.rig.getTasks; +build.rig.getTasks = function () { + var result = getTasks.call(build.rig); + + result.set('serve', result.get('serve-deprecated')); + + return result; +}; + +build.initialize(require('gulp')); diff --git a/SharePoint/SharePoint/package.json b/SharePoint/SharePoint/package.json new file mode 100644 index 0000000..a237da0 --- /dev/null +++ b/SharePoint/SharePoint/package.json @@ -0,0 +1,38 @@ +{ + "name": "pdfviewer-spfx", + "version": "0.0.1", + "private": true, + "engines": { + "node": ">=18.17.1 <19.0.0" + }, + "main": "lib/index.js", + "scripts": { + "build": "gulp bundle", + "clean": "gulp clean", + "test": "gulp test" + }, + "dependencies": { + "@microsoft/sp-component-base": "1.20.0", + "@microsoft/sp-core-library": "1.20.0", + "@microsoft/sp-lodash-subset": "1.20.0", + "@microsoft/sp-office-ui-fabric-core": "1.20.0", + "@microsoft/sp-property-pane": "1.20.0", + "@microsoft/sp-webpart-base": "1.20.0", + "@syncfusion/ej2-pdfviewer": "^34.2.5", + "tslib": "2.3.1" + }, + "devDependencies": { + "@fluentui/react": "^8.106.4", + "@microsoft/eslint-config-spfx": "1.20.2", + "@microsoft/eslint-plugin-spfx": "1.20.2", + "@microsoft/rush-stack-compiler-4.7": "0.1.0", + "@microsoft/sp-build-web": "1.20.2", + "@microsoft/sp-module-interfaces": "1.20.2", + "@rushstack/eslint-config": "4.0.1", + "@types/webpack-env": "~1.15.2", + "ajv": "^6.12.5", + "eslint": "8.57.0", + "gulp": "4.0.2", + "typescript": "4.7.4" + } +} diff --git a/SharePoint/SharePoint/src/index.ts b/SharePoint/SharePoint/src/index.ts new file mode 100644 index 0000000..fb81db1 --- /dev/null +++ b/SharePoint/SharePoint/src/index.ts @@ -0,0 +1 @@ +// A file is required to be in the root of the /src directory by the TypeScript compiler diff --git a/SharePoint/SharePoint/src/webparts/pdfviewer/PdfviewerWebPart.manifest.json b/SharePoint/SharePoint/src/webparts/pdfviewer/PdfviewerWebPart.manifest.json new file mode 100644 index 0000000..3924f84 --- /dev/null +++ b/SharePoint/SharePoint/src/webparts/pdfviewer/PdfviewerWebPart.manifest.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json", + "id": "87887a8b-fa43-424d-9383-5e5a2a782bfd", + "alias": "PdfviewerWebPart", + "componentType": "WebPart", + + // The "*" signifies that the version should be taken from the package.json + "version": "*", + "manifestVersion": 2, + + // If true, the component can only be installed on sites where Custom Script is allowed. + // Components that allow authors to embed arbitrary script code should set this to true. + // https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f + "requiresCustomScript": false, + "supportedHosts": ["SharePointWebPart", "TeamsPersonalApp", "TeamsTab", "SharePointFullPage"], + "supportsThemeVariants": true, + + "preconfiguredEntries": [{ + "groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Advanced + "group": { "default": "Advanced" }, + "title": { "default": "pdfviewer" }, + "description": { "default": "pdfviewer description" }, + "officeFabricIconFontName": "Page", + "properties": { + "description": "pdfviewer" + } + }] +} diff --git a/SharePoint/SharePoint/src/webparts/pdfviewer/PdfviewerWebPart.module.scss b/SharePoint/SharePoint/src/webparts/pdfviewer/PdfviewerWebPart.module.scss new file mode 100644 index 0000000..3800d1a --- /dev/null +++ b/SharePoint/SharePoint/src/webparts/pdfviewer/PdfviewerWebPart.module.scss @@ -0,0 +1,76 @@ + +.pdfViewerContainer { + display: flex; + flex-direction: column; + height: 100%; + + .documentSelector { + padding: 15px; + background: #f5f5f5; + border-bottom: 1px solid #ddd; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + + label { + display: block; + margin-bottom: 8px; + font-weight: 600; + color: #333; + font-size: 14px; + } + + select { + width: 100%; + padding: 10px; + border: 1px solid #ccc; + border-radius: 4px; + font-size: 14px; + background-color: white; + cursor: pointer; + transition: border-color 0.3s; + + &:hover { + border-color: #999; + } + + &:focus { + outline: none; + border-color: #0078d4; + box-shadow: 0 0 0 1px #0078d4; + } + } + } + + .statusMessage { + font-size: 12px; + padding: 0 15px; + margin-top: 5px; + + &.loading { + color: #0078d4; + } + + &.error { + color: #d13438; + } + } + + .pdfViewerWrapper { + flex: 1; + overflow: hidden; + } +} + +.emptyState { + display: flex; + align-items: center; + justify-content: center; + height: 100%; + color: #666; + font-size: 14px; + text-align: center; + padding: 20px; + + p { + margin: 0; + } +} diff --git a/SharePoint/SharePoint/src/webparts/pdfviewer/PdfviewerWebPart.ts b/SharePoint/SharePoint/src/webparts/pdfviewer/PdfviewerWebPart.ts new file mode 100644 index 0000000..af75ea9 --- /dev/null +++ b/SharePoint/SharePoint/src/webparts/pdfviewer/PdfviewerWebPart.ts @@ -0,0 +1,195 @@ + +import { Version } from '@microsoft/sp-core-library'; +import { + IPropertyPaneConfiguration, + PropertyPaneTextField +} from '@microsoft/sp-property-pane'; +import { SPHttpClient } from '@microsoft/sp-http'; +import { + BaseClientSideWebPart +} from '@microsoft/sp-webpart-base'; + +import { PdfViewer, Toolbar, Magnification, Navigation, + Annotation, LinkAnnotation, ThumbnailView, + BookmarkView, TextSelection, TextSearch, + FormFields, FormDesigner } from '@syncfusion/ej2-pdfviewer'; + +PdfViewer.Inject(Toolbar, Magnification, Navigation, + Annotation, LinkAnnotation, ThumbnailView, + BookmarkView, TextSelection, TextSearch, + FormFields, FormDesigner); + +export interface IPdfViewerWebPartProps { + description: string; +} + +interface IPdfFile { + name: string; + url: string; +} + +export default class PdfViewerWebPart extends BaseClientSideWebPart { + + private pdfViewer: PdfViewer | undefined; + private pdfFiles: IPdfFile[] = []; + + public render(): void { + + this.domElement.innerHTML = ` + +
+
+ + +
+
Loading PDF documents from SiteAssets/pdfs...
+ +
+
+ `; + + const siteUrl = this.context.pageContext.web.absoluteUrl; + console.log("Site URL: ", siteUrl); + + // Fetch PDF documents + this.fetchPdfDocuments(siteUrl); + + // Initialize PDF Viewer + this.initializePdfViewer(); + } + + private async fetchPdfDocuments(siteUrl: string): Promise { + try { + const targetSite = "YOUR-SHAREPOINT-SITE"; + + const url = `${targetSite}/_api/web/GetFolderByServerRelativeUrl('/sites/{your-site-name}/{documents-containing-path}')/Files`; + + const response = await this.context.spHttpClient.get( + url, + SPHttpClient.configurations.v1 + ); + + if (!response.ok) { + throw new Error(`Failed to fetch PDFs: ${response.statusText}`); + } + + const data = await response.json(); + + console.log(data); + + this.pdfFiles = data.value + .filter((item: any) => + item.Name.toLowerCase().endsWith('.pdf')) + .map((item: any) => ({ + name: item.Name, + url: `${siteUrl}${item.ServerRelativeUrl}` + })); + + console.log('PDF Files found:', this.pdfFiles); + this.populateDropdown(); + + } catch (error) { + console.error('Error fetching PDF documents:', error); + const errorDiv = document.getElementById('errorMessage'); + if (errorDiv) { + errorDiv.style.display = 'block'; + errorDiv.textContent = `Error loading documents: ${error instanceof Error ? error.message : 'Unknown error'}`; + } + } + } + + private populateDropdown(): void { + const dropdown = document.getElementById('pdfDropdown') as HTMLSelectElement; + const loadingMessage = document.getElementById('loadingMessage'); + + if (!dropdown) return; + + dropdown.innerHTML = ''; + + this.pdfFiles.forEach((file) => { + const option = document.createElement('option'); + option.value = file.url; + option.textContent = file.name; + dropdown.appendChild(option); + }); + + if (loadingMessage) { + loadingMessage.style.display = 'none'; + } + + // Add change event listener + dropdown.addEventListener('change', (e: Event) => { + const selectedUrl = (e.target as HTMLSelectElement).value; + if (selectedUrl) { + this.loadPdfDocument(selectedUrl); + } + }); + } + + private initializePdfViewer(): void { + this.pdfViewer = new PdfViewer({ + documentPath: 'https://cdn.syncfusion.com/content/pdf/gis-succinctly.pdf', // Will be set when user selects a document + resourceUrl: '${YOUR-LOCATION-FOR-RESOURCE}/ej2-pdfviewer-lib', + toolbarSettings: { + showTooltip: true, + toolbarItems: ['OpenOption', 'PageNavigationTool', 'MagnificationTool', 'PanTool', 'PrintOption',] + }, + enableAnnotationToolbar: false, + enableDownload: true, + enableStickyNotesAnnotation: false, + enablePageOrganizer: false, + annotationSettings: { + isLock: true, + }, + contextMenuOption: 'None', + documentLoad: () => { + const viewer = (document.getElementById('PdfViewer') as any).ej2_instances[0]; + const formField = viewer.retrieveFormFields(); + for (let x = 0; x < formField.length; x++) { + viewer.formDesignerModule.updateFormField(viewer.formFieldCollections[x], { + isReadOnly: true, + }); + } + } + }); + + this.pdfViewer.appendTo('#PdfViewer'); + } + + private loadPdfDocument(url: string): void { + if (this.pdfViewer) { + this.pdfViewer.load(url, "null"); + console.log('Loading PDF:', url); + } + } + + protected get dataVersion(): Version { + return Version.parse('1.0'); + } + + protected getPropertyPaneConfiguration(): + IPropertyPaneConfiguration { + + return { + pages: [ + { + header: { + description: 'PDF Viewer Settings' + }, + groups: [ + { + groupName: 'Basic Settings', + groupFields: [ + PropertyPaneTextField('description', { + label: 'Description' + }) + ] + } + ] + } + ] + }; + } +} diff --git a/SharePoint/SharePoint/src/webparts/pdfviewer/assets/welcome-dark.png b/SharePoint/SharePoint/src/webparts/pdfviewer/assets/welcome-dark.png new file mode 100644 index 0000000..42f0b8d Binary files /dev/null and b/SharePoint/SharePoint/src/webparts/pdfviewer/assets/welcome-dark.png differ diff --git a/SharePoint/SharePoint/src/webparts/pdfviewer/assets/welcome-light.png b/SharePoint/SharePoint/src/webparts/pdfviewer/assets/welcome-light.png new file mode 100644 index 0000000..69eb3b4 Binary files /dev/null and b/SharePoint/SharePoint/src/webparts/pdfviewer/assets/welcome-light.png differ diff --git a/SharePoint/SharePoint/src/webparts/pdfviewer/loc/en-us.js b/SharePoint/SharePoint/src/webparts/pdfviewer/loc/en-us.js new file mode 100644 index 0000000..3b25e74 --- /dev/null +++ b/SharePoint/SharePoint/src/webparts/pdfviewer/loc/en-us.js @@ -0,0 +1,16 @@ +define([], function() { + return { + "PropertyPaneDescription": "Description", + "BasicGroupName": "Group Name", + "DescriptionFieldLabel": "Description Field", + "AppLocalEnvironmentSharePoint": "The app is running on your local environment as SharePoint web part", + "AppLocalEnvironmentTeams": "The app is running on your local environment as Microsoft Teams app", + "AppLocalEnvironmentOffice": "The app is running on your local environment in office.com", + "AppLocalEnvironmentOutlook": "The app is running on your local environment in Outlook", + "AppSharePointEnvironment": "The app is running on SharePoint page", + "AppTeamsTabEnvironment": "The app is running in Microsoft Teams", + "AppOfficeEnvironment": "The app is running in office.com", + "AppOutlookEnvironment": "The app is running in Outlook", + "UnknownEnvironment": "The app is running in an unknown environment" + } +}); \ No newline at end of file diff --git a/SharePoint/SharePoint/src/webparts/pdfviewer/loc/mystrings.d.ts b/SharePoint/SharePoint/src/webparts/pdfviewer/loc/mystrings.d.ts new file mode 100644 index 0000000..3807635 --- /dev/null +++ b/SharePoint/SharePoint/src/webparts/pdfviewer/loc/mystrings.d.ts @@ -0,0 +1,19 @@ +declare interface IPdfviewerWebPartStrings { + PropertyPaneDescription: string; + BasicGroupName: string; + DescriptionFieldLabel: string; + AppLocalEnvironmentSharePoint: string; + AppLocalEnvironmentTeams: string; + AppLocalEnvironmentOffice: string; + AppLocalEnvironmentOutlook: string; + AppSharePointEnvironment: string; + AppTeamsTabEnvironment: string; + AppOfficeEnvironment: string; + AppOutlookEnvironment: string; + UnknownEnvironment: string; +} + +declare module 'PdfviewerWebPartStrings' { + const strings: IPdfviewerWebPartStrings; + export = strings; +} diff --git a/SharePoint/SharePoint/teams/87887a8b-fa43-424d-9383-5e5a2a782bfd_color.png b/SharePoint/SharePoint/teams/87887a8b-fa43-424d-9383-5e5a2a782bfd_color.png new file mode 100644 index 0000000..0e1f764 Binary files /dev/null and b/SharePoint/SharePoint/teams/87887a8b-fa43-424d-9383-5e5a2a782bfd_color.png differ diff --git a/SharePoint/SharePoint/teams/87887a8b-fa43-424d-9383-5e5a2a782bfd_outline.png b/SharePoint/SharePoint/teams/87887a8b-fa43-424d-9383-5e5a2a782bfd_outline.png new file mode 100644 index 0000000..e8cb4b6 Binary files /dev/null and b/SharePoint/SharePoint/teams/87887a8b-fa43-424d-9383-5e5a2a782bfd_outline.png differ diff --git a/SharePoint/SharePoint/tsconfig.json b/SharePoint/SharePoint/tsconfig.json new file mode 100644 index 0000000..c4cd392 --- /dev/null +++ b/SharePoint/SharePoint/tsconfig.json @@ -0,0 +1,35 @@ +{ + "extends": "./node_modules/@microsoft/rush-stack-compiler-4.7/includes/tsconfig-web.json", + "compilerOptions": { + "target": "es5", + "forceConsistentCasingInFileNames": true, + "module": "esnext", + "moduleResolution": "node", + "jsx": "react", + "declaration": true, + "sourceMap": true, + "experimentalDecorators": true, + "skipLibCheck": true, + "outDir": "lib", + "inlineSources": false, + "noImplicitAny": true, + + "typeRoots": [ + "./node_modules/@types", + "./node_modules/@microsoft" + ], + "types": [ + "webpack-env" + ], + "lib": [ + "es5", + "dom", + "es2015.collection", + "es2015.promise" + ] + }, + "include": [ + "src/**/*.ts", + "src/**/*.tsx" + ] +}