diff --git a/sharepoint-integration/.gitignore b/sharepoint-integration/.gitignore new file mode 100644 index 00000000..5de84738 --- /dev/null +++ b/sharepoint-integration/.gitignore @@ -0,0 +1,38 @@ +# Logs +logs +*.log +npm-debug.log* + +# Dependency directories +node_modules + +# Build generated files +dist +lib +lib-dts +lib-commonjs +lib-esm +jest-output +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-integration/.npmignore b/sharepoint-integration/.npmignore new file mode 100644 index 00000000..b2625b81 --- /dev/null +++ b/sharepoint-integration/.npmignore @@ -0,0 +1,22 @@ +# Ignore everything by default +** + +# Use negative patterns to bring back the specific things we want to publish +!/bin/** +!/dist/** +!/EULA/** +!/lib/** +!ThirdPartyNotice.txt + +# Ignore certain files in the above folders +/dist/*.stats.* +/dist/**/*.js.map +/lib/**/*.js.map +/lib/**/test/** + +# NOTE: These don't need to be specified, because NPM includes them automatically. +# +# package.json +# README (and its variants) +# CHANGELOG (and its variants) +# LICENSE / LICENCE diff --git a/sharepoint-integration/.yo-rc.json b/sharepoint-integration/.yo-rc.json new file mode 100644 index 00000000..2f35eeb3 --- /dev/null +++ b/sharepoint-integration/.yo-rc.json @@ -0,0 +1,19 @@ +{ + "@microsoft/generator-sharepoint": { + "useGulp": false, + "plusBeta": false, + "isCreatingSolution": true, + "nodeVersion": "22.23.3", + "sdksVersions": {}, + "version": "1.23.2", + "libraryName": "docx-editor-sp-fx", + "libraryId": "7a537e50-ce5c-40c6-89fe-11ffd128299d", + "environment": "spo", + "packageManager": "npm", + "solutionName": "DOCXEditorSPFx", + "solutionShortDescription": "DOCXEditorSPFx description", + "skipFeatureDeployment": true, + "isDomainIsolated": false, + "componentType": "webpart" + } +} diff --git a/sharepoint-integration/README.md b/sharepoint-integration/README.md new file mode 100644 index 00000000..aaf6ad13 --- /dev/null +++ b/sharepoint-integration/README.md @@ -0,0 +1,56 @@ +# Getting Started with Integrating Syncfusion TypeScript DOCX Editor in SharePoint + +## Description + +This repository provides a complete example of how to integrate the Syncfusion [TypeScript DOCX Editor](https://www.syncfusion.com/docx-editor-sdk/javascript-docx-editor?utm_source=github&utm_medium=listing&utm_campaign=javascript-docx-editor-github-samples) component into a SharePoint Framework (SPFx) web part using the modern Heft-based build toolchain introduced in SPFx v1.22+. SharePoint Framework enables fully client‑side solutions that run seamlessly within SharePoint Online pages. + +## Prerequisites + +- Use Node Version 22 (supported by SPFx 1.22) +- A valid Microsoft 365 tenant + +## Application Setup & Running the Project (Clone & Run) + +1. **Clone the repository to your local machine and navigate to the project folder.** + ```bash + git clone + cd + ``` + +2. **Install project dependencies:** + ```bash + npm install + ``` + +3. **Update Tenant Domain in `config/serve.json`:** + ```json + "initialPage": "https://.sharepoint.com/_layouts/15/workbench.aspx" + ``` + **Note:** Replace `` with your actual SharePoint Online tenant name. + +4. **Trust the SPFx Dev Certificate:** + ```bash + heft trust-dev-cert + ``` + Or simply trust when prompted during first run. + +5. **Start the application:** + ```bash + npm start + ``` + +6. **Allow debug scripts?** + From the browser opened in step 5, allow debug scripts by clicking `Load debug scripts`. + +7. **Viewing Syncfusion Schedule Component** + - Click the + (Add a web part in column one) icon in the browser window (SharePoint). + - Select 'App' from the options shown. + - Finally, you can see the Syncfusion Document Editor rendered in SharePoint. + +## Output Preview + +**Syncfusion TypeScript DOCX Editor** + +![TypeScript DOCX Editor in SharePoint](./typescript-docx-editor-sharepoint.png) + +*Image illustrating the Syncfusion TypeScript DOCX Editor in SharePoint* \ No newline at end of file diff --git a/sharepoint-integration/config/config.json b/sharepoint-integration/config/config.json new file mode 100644 index 00000000..b3f0e1f6 --- /dev/null +++ b/sharepoint-integration/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": { + "docx-editor-web-part": { + "components": [ + { + "entrypoint": "./lib/webparts/docxEditor/DocxEditorWebPart.js", + "manifest": "./src/webparts/docxEditor/DocxEditorWebPart.manifest.json" + } + ] + } + }, + "externals": {}, + "localizedResources": { + "DocxEditorWebPartStrings": "lib/webparts/docxEditor/loc/{locale}.js" + } +} diff --git a/sharepoint-integration/config/deploy-azure-storage.json b/sharepoint-integration/config/deploy-azure-storage.json new file mode 100644 index 00000000..033c8754 --- /dev/null +++ b/sharepoint-integration/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": "docx-editor-sp-fx", + "accessKey": "" +} diff --git a/sharepoint-integration/config/package-solution.json b/sharepoint-integration/config/package-solution.json new file mode 100644 index 00000000..cb78e17f --- /dev/null +++ b/sharepoint-integration/config/package-solution.json @@ -0,0 +1,40 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json", + "solution": { + "name": "docx-editor-sp-fx-client-side-solution", + "id": "7a537e50-ce5c-40c6-89fe-11ffd128299d", + "version": "1.0.0.0", + "includeClientSideAssets": true, + "skipFeatureDeployment": true, + "isDomainIsolated": false, + "developer": { + "name": "", + "websiteUrl": "", + "privacyUrl": "", + "termsOfUseUrl": "", + "mpnId": "Undefined-1.23.2" + }, + "metadata": { + "shortDescription": { + "default": "DOCXEditorSPFx description" + }, + "longDescription": { + "default": "DOCXEditorSPFx description" + }, + "screenshotPaths": [], + "videoUrl": "", + "categories": [] + }, + "features": [ + { + "title": "docx-editor-sp-fx Feature", + "description": "The feature that activates elements of the docx-editor-sp-fx solution.", + "id": "9a5de981-4c76-4981-9dd2-594082d52d8b", + "version": "1.0.0.0" + } + ] + }, + "paths": { + "zippedPackage": "solution/docx-editor-sp-fx.sppkg" + } +} diff --git a/sharepoint-integration/config/rig.json b/sharepoint-integration/config/rig.json new file mode 100644 index 00000000..99e34318 --- /dev/null +++ b/sharepoint-integration/config/rig.json @@ -0,0 +1,7 @@ +{ + // The "rig.json" file directs tools to look for their config files in an external package. + // Documentation for this system: https://www.npmjs.com/package/@rushstack/rig-package + "$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json", + + "rigPackageName": "@microsoft/spfx-web-build-rig" +} diff --git a/sharepoint-integration/config/sass.json b/sharepoint-integration/config/sass.json new file mode 100644 index 00000000..1cfc17b3 --- /dev/null +++ b/sharepoint-integration/config/sass.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/heft/v0/heft-sass-plugin.schema.json", + + "extends": "@microsoft/spfx-web-build-rig/profiles/default/config/sass.json" +} \ No newline at end of file diff --git a/sharepoint-integration/config/serve.json b/sharepoint-integration/config/serve.json new file mode 100644 index 00000000..ebc983ea --- /dev/null +++ b/sharepoint-integration/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://.sharepoint.com/_layouts/15/workbench.aspx" +} diff --git a/sharepoint-integration/config/typescript.json b/sharepoint-integration/config/typescript.json new file mode 100644 index 00000000..ce4cc73e --- /dev/null +++ b/sharepoint-integration/config/typescript.json @@ -0,0 +1,9 @@ +{ + "extends": "@microsoft/spfx-web-build-rig/profiles/default/config/typescript.json", + + "staticAssetsToCopy": { + "fileExtensions": [".resx", ".jpg", ".png", ".woff", ".eot", ".ttf", ".svg", ".gif"], + + "includeGlobs": ["webparts/*/loc/*.js"] + } +} diff --git a/sharepoint-integration/config/write-manifests.json b/sharepoint-integration/config/write-manifests.json new file mode 100644 index 00000000..bad35260 --- /dev/null +++ b/sharepoint-integration/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-integration/eslint.config.js b/sharepoint-integration/eslint.config.js new file mode 100644 index 00000000..859c1555 --- /dev/null +++ b/sharepoint-integration/eslint.config.js @@ -0,0 +1,14 @@ +const spfxProfile = require('@microsoft/eslint-config-spfx/lib/flat-profiles/default'); + +module.exports = [ + ...spfxProfile, + { + files: ['**/*.ts', '**/*.tsx'], + languageOptions: { + parserOptions: { + tsconfigRootDir: __dirname, + project: './tsconfig.json' + } + } + } +]; diff --git a/sharepoint-integration/package.json b/sharepoint-integration/package.json new file mode 100644 index 00000000..dc6e5cca --- /dev/null +++ b/sharepoint-integration/package.json @@ -0,0 +1,43 @@ +{ + "name": "docx-editor-sp-fx", + "version": "0.0.1", + "private": true, + "engines": { + "node": ">=22.14.0 < 23.0.0" + }, + "scripts": { + "build": "heft test --clean --production && heft package-solution --production", + "clean": "heft clean", + "eject-webpack": "heft eject-webpack", + "start": "heft start --clean" + }, + "dependencies": { + "@microsoft/sp-component-base": "1.23.2", + "@microsoft/sp-core-library": "1.23.2", + "@microsoft/sp-lodash-subset": "1.23.2", + "@microsoft/sp-office-ui-fabric-core": "1.23.2", + "@microsoft/sp-property-pane": "1.23.2", + "@microsoft/sp-webpart-base": "1.23.2", + "@syncfusion/ej2-documenteditor": "^34.2.8", + "@syncfusion/ej2-tailwind3-theme": "^34.2.8", + "gulp": "^5.0.1", + "tslib": "2.3.1" + }, + "devDependencies": { + "@fluentui/react": "^8.106.4", + "@microsoft/eslint-config-spfx": "1.23.2", + "@microsoft/eslint-plugin-spfx": "1.23.2", + "@microsoft/sp-module-interfaces": "1.23.2", + "@microsoft/spfx-heft-plugins": "1.23.2", + "@microsoft/spfx-web-build-rig": "1.23.2", + "@rushstack/heft": "1.2.17", + "@types/jest": "30.0.0", + "@types/webpack-env": "~1.15.2", + "css-loader": "~7.1.2", + "eslint": "9.37.0", + "typescript": "~5.8.0" + }, + "overrides": { + "@rushstack/heft": "1.2.17" + } +} diff --git a/sharepoint-integration/src/webparts/docxEditor/DocxEditorWebPart.manifest.json b/sharepoint-integration/src/webparts/docxEditor/DocxEditorWebPart.manifest.json new file mode 100644 index 00000000..921490b5 --- /dev/null +++ b/sharepoint-integration/src/webparts/docxEditor/DocxEditorWebPart.manifest.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json", + "id": "d6275826-8cf5-4874-89e0-40e428ca4313", + "alias": "DocxEditorWebPart", + "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": "DOCXEditor" }, + "description": { "default": "DOCXEditor description" }, + "officeFabricIconFontName": "Page", + "properties": { + "description": "DOCXEditor" + } + }] +} diff --git a/sharepoint-integration/src/webparts/docxEditor/DocxEditorWebPart.module.scss b/sharepoint-integration/src/webparts/docxEditor/DocxEditorWebPart.module.scss new file mode 100644 index 00000000..f0ba9492 --- /dev/null +++ b/sharepoint-integration/src/webparts/docxEditor/DocxEditorWebPart.module.scss @@ -0,0 +1,29 @@ +.docxEditor { + overflow: hidden; + padding: 1em; + color: "[theme:bodyText, default: #323130]"; + color: var(--bodyText); +} + +.welcome { + text-align: center; +} + +.welcomeImage { + width: 100%; + max-width: 420px; +} + +.links { + a { + text-decoration: none; + color: "[theme:link, default:#03787c]"; + color: var(--link); // note: CSS Custom Properties support is limited to modern browsers only + + &:hover { + text-decoration: underline; + color: "[theme:linkHovered, default: #014446]"; + color: var(--linkHovered); // note: CSS Custom Properties support is limited to modern browsers only + } + } +} \ No newline at end of file diff --git a/sharepoint-integration/src/webparts/docxEditor/DocxEditorWebPart.ts b/sharepoint-integration/src/webparts/docxEditor/DocxEditorWebPart.ts new file mode 100644 index 00000000..fa0a361e --- /dev/null +++ b/sharepoint-integration/src/webparts/docxEditor/DocxEditorWebPart.ts @@ -0,0 +1,130 @@ +import { +DocumentEditorContainer, +Toolbar +} from '@syncfusion/ej2-documenteditor'; +DocumentEditorContainer.Inject(Toolbar); +import { SPComponentLoader } from '@microsoft/sp-loader'; +import { Version } from '@microsoft/sp-core-library'; +import { + type IPropertyPaneConfiguration, + PropertyPaneTextField +} from '@microsoft/sp-property-pane'; +import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base'; +import type { IReadonlyTheme } from '@microsoft/sp-component-base'; +import { escape } from '@microsoft/sp-lodash-subset'; + +import styles from './DocxEditorWebPart.module.scss'; +import * as strings from 'DocxEditorWebPartStrings'; +import welcomeDark from './assets/welcome-dark.png'; +import welcomeLight from './assets/welcome-light.png'; + +export interface IDocxEditorWebPartProps { + description: string; +} + +export default class DocxEditorWebPart extends BaseClientSideWebPart { + + private _isDarkTheme: boolean = false; + private _environmentMessage: string = ''; + + public render(): void { + + this.domElement.innerHTML = ` +
+
+ `; + + const editor = new DocumentEditorContainer({ + enableToolbar: true, + height: '800px' + }); + + editor.appendTo('#document-editor-container'); +} + + protected onInit(): Promise { + + SPComponentLoader.loadCss( + 'https://cdn.syncfusion.com/ej2/34.2.8/tailwind3.css' + ); + + return this._getEnvironmentMessage().then(message => { + this._environmentMessage = message; + }); + } + + + + private _getEnvironmentMessage(): Promise { + if (!!this.context.sdks.microsoftTeams) { // running in Teams, office.com or Outlook + return this.context.sdks.microsoftTeams.teamsJs.app.getContext() + .then(context => { + let environmentMessage: string = ''; + switch (context.app.host.name) { + case 'Office': // running in Office + environmentMessage = this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentOffice : strings.AppOfficeEnvironment; + break; + case 'Outlook': // running in Outlook + environmentMessage = this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentOutlook : strings.AppOutlookEnvironment; + break; + case 'Teams': // running in Teams + case 'TeamsModern': + environmentMessage = this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentTeams : strings.AppTeamsTabEnvironment; + break; + default: + environmentMessage = strings.UnknownEnvironment; + } + + return environmentMessage; + }); + } + + return Promise.resolve(this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentSharePoint : strings.AppSharePointEnvironment); + } + + protected onThemeChanged(currentTheme: IReadonlyTheme | undefined): void { + if (!currentTheme) { + return; + } + + this._isDarkTheme = !!currentTheme.isInverted; + const { + semanticColors + } = currentTheme; + + if (semanticColors) { + this.domElement.style.setProperty('--bodyText', semanticColors.bodyText || null); + this.domElement.style.setProperty('--link', semanticColors.link || null); + this.domElement.style.setProperty('--linkHovered', semanticColors.linkHovered || null); + } + + } + + protected get dataVersion(): Version { + return Version.parse('1.0'); + } + + protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration { + return { + pages: [ + { + header: { + description: strings.PropertyPaneDescription + }, + groups: [ + { + groupName: strings.BasicGroupName, + groupFields: [ + PropertyPaneTextField('description', { + label: strings.DescriptionFieldLabel + }) + ] + } + ] + } + ] + }; + } +} diff --git a/sharepoint-integration/src/webparts/docxEditor/assets/welcome-dark.png b/sharepoint-integration/src/webparts/docxEditor/assets/welcome-dark.png new file mode 100644 index 00000000..42f0b8d2 Binary files /dev/null and b/sharepoint-integration/src/webparts/docxEditor/assets/welcome-dark.png differ diff --git a/sharepoint-integration/src/webparts/docxEditor/assets/welcome-light.png b/sharepoint-integration/src/webparts/docxEditor/assets/welcome-light.png new file mode 100644 index 00000000..69eb3b48 Binary files /dev/null and b/sharepoint-integration/src/webparts/docxEditor/assets/welcome-light.png differ diff --git a/sharepoint-integration/src/webparts/docxEditor/loc/en-us.js b/sharepoint-integration/src/webparts/docxEditor/loc/en-us.js new file mode 100644 index 00000000..3b25e74a --- /dev/null +++ b/sharepoint-integration/src/webparts/docxEditor/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-integration/src/webparts/docxEditor/loc/mystrings.d.ts b/sharepoint-integration/src/webparts/docxEditor/loc/mystrings.d.ts new file mode 100644 index 00000000..baa34f00 --- /dev/null +++ b/sharepoint-integration/src/webparts/docxEditor/loc/mystrings.d.ts @@ -0,0 +1,19 @@ +declare interface IDocxEditorWebPartStrings { + 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 'DocxEditorWebPartStrings' { + const strings: IDocxEditorWebPartStrings; + export = strings; +} diff --git a/sharepoint-integration/tsconfig.json b/sharepoint-integration/tsconfig.json new file mode 100644 index 00000000..ee582d51 --- /dev/null +++ b/sharepoint-integration/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "./node_modules/@microsoft/spfx-web-build-rig/profiles/default/tsconfig-base.json" +} diff --git a/sharepoint-integration/typescript-docx-editor-sharepoint.png b/sharepoint-integration/typescript-docx-editor-sharepoint.png new file mode 100644 index 00000000..6119c998 Binary files /dev/null and b/sharepoint-integration/typescript-docx-editor-sharepoint.png differ