├── examples ├── .gitignore ├── web-office │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── config.sample.js │ ├── office_thread.js │ ├── gulpfile.js │ └── index.html ├── ping-monitor │ ├── .gitignore │ ├── ping_monitor.ods │ ├── pwa-icon-192x192.png │ ├── pwa-manifest.json │ ├── package.json │ ├── README.md │ ├── config.sample.js │ ├── office_thread.js │ ├── gulpfile.js │ └── index.html ├── convertpdf │ ├── .gitignore │ ├── package.json │ ├── README.md │ ├── config.sample.js │ ├── office_thread.js │ ├── index.html │ └── gulpfile.js ├── letter-address-tool │ ├── .gitignore │ ├── Modern_business_letter_sans_serif.ott │ ├── package.json │ ├── README.md │ ├── config.sample.js │ ├── gulpfile.js │ └── office_thread.js ├── simple-examples │ ├── zeta.js │ ├── config.sample.js │ ├── find_and_replace.js │ ├── README.md │ ├── simple.js │ ├── simple_key_handler.js │ ├── simple.html │ ├── rainbow_writer.html │ ├── find_and_replace.html │ ├── simple_key_handler.html │ └── rainbow_writer.js ├── standalone │ ├── .gitignore │ ├── package.json │ ├── README.md │ ├── config.sample.js │ ├── office_thread.js │ ├── gulpfile.js │ └── index.html ├── vuejs3-ping-tool │ ├── public │ │ ├── assets │ │ │ └── vendor │ │ │ │ └── zetajs │ │ │ │ ├── zeta.js │ │ │ │ └── zetaHelper.js │ │ ├── favicon.ico │ │ ├── calc_ping_example.ods │ │ ├── config.sample.js │ │ ├── office_thread.js │ │ └── pre_soffice.js │ ├── src │ │ ├── main.js │ │ ├── assets │ │ │ ├── main.css │ │ │ └── base.css │ │ ├── components │ │ │ └── ControlBar.vue │ │ └── App.vue │ ├── jsconfig.json │ ├── .gitignore │ ├── vite.config.js │ ├── package.json │ ├── index.html │ └── README.md └── letter-address-vuejs3 │ ├── public │ ├── assets │ │ └── vendor │ │ │ └── zetajs │ │ │ ├── zeta.js │ │ │ └── zetaHelper.js │ ├── table.ods │ ├── favicon.ico │ ├── letter.odt │ ├── config.sample.js │ ├── pre_soffice.js │ └── office_thread.js │ ├── jsconfig.json │ ├── src │ ├── main.js │ ├── assets │ │ ├── main.css │ │ └── base.css │ ├── App.vue │ └── components │ │ └── ControlBar.vue │ ├── .gitignore │ ├── vite.config.js │ ├── package.json │ ├── index.html │ └── README.md ├── source ├── .gitignore └── zetaHelper.ts ├── .npmignore ├── .gitignore ├── jest.config.json ├── screenshots.png ├── test ├── e2e │ ├── test.odt │ └── test.js └── README.md ├── types └── globals.d.ts ├── jest-puppeteer.config.js ├── tsconfig.json ├── .github └── workflows │ └── main.yml ├── CHANGELOG.md ├── LICENSE ├── package.json ├── docs ├── network.md └── uno.md └── README.md /examples/.gitignore: -------------------------------------------------------------------------------- 1 | */config.js 2 | -------------------------------------------------------------------------------- /source/.gitignore: -------------------------------------------------------------------------------- 1 | zetaHelper.js 2 | -------------------------------------------------------------------------------- /examples/web-office/.gitignore: -------------------------------------------------------------------------------- 1 | public/ 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | examples/ 2 | test/ 3 | docs/ 4 | -------------------------------------------------------------------------------- /examples/ping-monitor/.gitignore: -------------------------------------------------------------------------------- 1 | public/ 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/node_modules 2 | **/soffice.* 3 | *~ -------------------------------------------------------------------------------- /examples/convertpdf/.gitignore: -------------------------------------------------------------------------------- 1 | /out/ 2 | public/ 3 | -------------------------------------------------------------------------------- /examples/letter-address-tool/.gitignore: -------------------------------------------------------------------------------- 1 | public/ 2 | -------------------------------------------------------------------------------- /examples/simple-examples/zeta.js: -------------------------------------------------------------------------------- 1 | ../../source/zeta.js -------------------------------------------------------------------------------- /examples/standalone/.gitignore: -------------------------------------------------------------------------------- 1 | **/public 2 | 3 | -------------------------------------------------------------------------------- /jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "preset": "jest-puppeteer" 3 | } 4 | -------------------------------------------------------------------------------- /screenshots.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/allotropia/zetajs/HEAD/screenshots.png -------------------------------------------------------------------------------- /test/e2e/test.odt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/allotropia/zetajs/HEAD/test/e2e/test.odt -------------------------------------------------------------------------------- /examples/vuejs3-ping-tool/public/assets/vendor/zetajs/zeta.js: -------------------------------------------------------------------------------- 1 | ../../../../../../source/zeta.js -------------------------------------------------------------------------------- /examples/letter-address-vuejs3/public/assets/vendor/zetajs/zeta.js: -------------------------------------------------------------------------------- 1 | ../../../../../../source/zeta.js -------------------------------------------------------------------------------- /examples/vuejs3-ping-tool/public/assets/vendor/zetajs/zetaHelper.js: -------------------------------------------------------------------------------- 1 | ../../../../../../source/zetaHelper.js -------------------------------------------------------------------------------- /examples/letter-address-vuejs3/public/assets/vendor/zetajs/zetaHelper.js: -------------------------------------------------------------------------------- 1 | ../../../../../../source/zetaHelper.js -------------------------------------------------------------------------------- /examples/ping-monitor/ping_monitor.ods: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/allotropia/zetajs/HEAD/examples/ping-monitor/ping_monitor.ods -------------------------------------------------------------------------------- /examples/ping-monitor/pwa-icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/allotropia/zetajs/HEAD/examples/ping-monitor/pwa-icon-192x192.png -------------------------------------------------------------------------------- /examples/vuejs3-ping-tool/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/allotropia/zetajs/HEAD/examples/vuejs3-ping-tool/public/favicon.ico -------------------------------------------------------------------------------- /examples/letter-address-vuejs3/public/table.ods: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/allotropia/zetajs/HEAD/examples/letter-address-vuejs3/public/table.ods -------------------------------------------------------------------------------- /examples/letter-address-vuejs3/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/allotropia/zetajs/HEAD/examples/letter-address-vuejs3/public/favicon.ico -------------------------------------------------------------------------------- /examples/letter-address-vuejs3/public/letter.odt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/allotropia/zetajs/HEAD/examples/letter-address-vuejs3/public/letter.odt -------------------------------------------------------------------------------- /examples/vuejs3-ping-tool/public/calc_ping_example.ods: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/allotropia/zetajs/HEAD/examples/vuejs3-ping-tool/public/calc_ping_example.ods -------------------------------------------------------------------------------- /types/globals.d.ts: -------------------------------------------------------------------------------- 1 | declare const Module: any; 2 | declare const FinalizationRegistry: any; 3 | 4 | declare module globalThis { 5 | var zetajsStore: any 6 | } 7 | -------------------------------------------------------------------------------- /examples/vuejs3-ping-tool/src/main.js: -------------------------------------------------------------------------------- 1 | import './assets/main.css' 2 | 3 | import { createApp } from 'vue' 4 | import App from './App.vue' 5 | 6 | createApp(App).mount('#app') 7 | -------------------------------------------------------------------------------- /examples/letter-address-tool/Modern_business_letter_sans_serif.ott: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/allotropia/zetajs/HEAD/examples/letter-address-tool/Modern_business_letter_sans_serif.ott -------------------------------------------------------------------------------- /examples/vuejs3-ping-tool/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "paths": { 4 | "@/*": ["./src/*"] 5 | } 6 | }, 7 | "exclude": ["node_modules", "dist"] 8 | } 9 | -------------------------------------------------------------------------------- /examples/letter-address-vuejs3/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "paths": { 4 | "@/*": ["./src/*"] 5 | } 6 | }, 7 | "exclude": ["node_modules", "dist"] 8 | } 9 | -------------------------------------------------------------------------------- /examples/letter-address-vuejs3/src/main.js: -------------------------------------------------------------------------------- 1 | import './assets/main.css' 2 | import "bootstrap/dist/css/bootstrap.min.css" 3 | import "bootstrap" 4 | 5 | import { createApp } from 'vue' 6 | import App from './App.vue' 7 | 8 | createApp(App).mount('#app') 9 | -------------------------------------------------------------------------------- /jest-puppeteer.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | launch: { 3 | headless: true, 4 | args: ["--no-sandbox"] 5 | }, 6 | server: { 7 | command: "npm run test:convertpdf", 8 | debug: true 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /examples/ping-monitor/pwa-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ZetaJS Demo: Ping Monitor", 3 | "start_url": "../", 4 | "display": "standalone", 5 | "icons": [ 6 | { 7 | "src": "pwa-icon-192x192.png", 8 | "sizes": "192x192", 9 | "type": "image/png" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "exclude": [ 3 | "examples" 4 | ], 5 | "compilerOptions": { 6 | "target": "ES2020", 7 | "module": "ES2020", 8 | "esModuleInterop": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "strict": true, 11 | "skipLibCheck": true 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: push 3 | 4 | jobs: 5 | test: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - name: checkout repo 9 | uses: actions/checkout@v3 10 | - name: use node.js 11 | uses: actions/setup-node@v3 12 | with: 13 | node-version: '18.x' 14 | - run: npm install 15 | - run: npm test 16 | -------------------------------------------------------------------------------- /examples/vuejs3-ping-tool/src/assets/main.css: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT */ 2 | 3 | @import './base.css'; 4 | 5 | a, 6 | .green { 7 | text-decoration: none; 8 | color: hsla(160, 100%, 37%, 1); 9 | transition: 0.4s; 10 | padding: 3px; 11 | } 12 | 13 | @media (hover: hover) { 14 | a:hover { 15 | background-color: hsla(160, 100%, 37%, 0.2); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/letter-address-vuejs3/src/assets/main.css: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT */ 2 | 3 | @import './base.css'; 4 | 5 | #app { 6 | font-weight: normal; 7 | } 8 | 9 | a, 10 | .green { 11 | text-decoration: none; 12 | color: hsla(160, 100%, 37%, 1); 13 | transition: 0.4s; 14 | padding: 3px; 15 | } 16 | 17 | @media (hover: hover) { 18 | a:hover { 19 | background-color: hsla(160, 100%, 37%, 0.2); 20 | } 21 | } -------------------------------------------------------------------------------- /examples/vuejs3-ping-tool/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | .DS_Store 12 | dist 13 | dist-ssr 14 | coverage 15 | *.local 16 | 17 | /cypress/videos/ 18 | /cypress/screenshots/ 19 | 20 | # Editor directories and files 21 | .vscode/* 22 | !.vscode/extensions.json 23 | .idea 24 | *.suo 25 | *.ntvs* 26 | *.njsproj 27 | *.sln 28 | *.sw? 29 | 30 | *.tsbuildinfo 31 | 32 | # project specific 33 | public/config.js 34 | -------------------------------------------------------------------------------- /examples/letter-address-vuejs3/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | .DS_Store 12 | dist 13 | dist-ssr 14 | coverage 15 | *.local 16 | 17 | /cypress/videos/ 18 | /cypress/screenshots/ 19 | 20 | # Editor directories and files 21 | .vscode/* 22 | !.vscode/extensions.json 23 | .idea 24 | *.suo 25 | *.ntvs* 26 | *.njsproj 27 | *.sln 28 | *.sw? 29 | 30 | *.tsbuildinfo 31 | 32 | # project specific 33 | public/config.js 34 | -------------------------------------------------------------------------------- /examples/vuejs3-ping-tool/vite.config.js: -------------------------------------------------------------------------------- 1 | import { fileURLToPath, URL } from 'node:url' 2 | 3 | import { defineConfig } from 'vite' 4 | import vue from '@vitejs/plugin-vue' 5 | 6 | // https://vitejs.dev/config/ 7 | export default defineConfig({ 8 | plugins: [ 9 | vue(), 10 | ], 11 | base: './', 12 | resolve: { 13 | alias: { 14 | '@': fileURLToPath(new URL('./src', import.meta.url)) 15 | } 16 | }, 17 | 18 | server: { 19 | host: '127.0.0.1', 20 | port: 8080, 21 | strictPort: true, 22 | headers: { 23 | 'Cross-Origin-Opener-Policy': 'same-origin', 24 | 'Cross-Origin-Embedder-Policy': 'require-corp' 25 | } 26 | } 27 | }) 28 | -------------------------------------------------------------------------------- /examples/letter-address-vuejs3/vite.config.js: -------------------------------------------------------------------------------- 1 | import { fileURLToPath, URL } from 'node:url' 2 | 3 | import { defineConfig } from 'vite' 4 | import vue from '@vitejs/plugin-vue' 5 | 6 | // https://vitejs.dev/config/ 7 | export default defineConfig({ 8 | plugins: [ 9 | vue(), 10 | ], 11 | base: './', 12 | resolve: { 13 | alias: { 14 | '@': fileURLToPath(new URL('./src', import.meta.url)) 15 | } 16 | }, 17 | 18 | server: { 19 | host: '127.0.0.1', 20 | port: 8080, 21 | strictPort: true, 22 | headers: { 23 | 'Cross-Origin-Opener-Policy': 'same-origin', 24 | 'Cross-Origin-Embedder-Policy': 'require-corp' 25 | } 26 | } 27 | }) 28 | -------------------------------------------------------------------------------- /examples/letter-address-vuejs3/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-project", 3 | "version": "0.0.0", 4 | "private": true, 5 | "type": "module", 6 | "scripts": { 7 | "start": "vite", 8 | "dev": "vite", 9 | "build": "vite build", 10 | "preview": "vite preview", 11 | "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore" 12 | }, 13 | "dependencies": { 14 | "bootstrap": "^5.3.3", 15 | "vue": "^3.4.29", 16 | "vue-file-toolbar-menu": "^2.2.0" 17 | }, 18 | "devDependencies": { 19 | "@vitejs/plugin-vue": "^5.0.5", 20 | "eslint": "^8.57.0", 21 | "eslint-plugin-vue": "^9.23.0", 22 | "vite": "^5.3.1" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/vuejs3-ping-tool/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-project", 3 | "version": "0.0.0", 4 | "private": true, 5 | "type": "module", 6 | "scripts": { 7 | "start": "vite", 8 | "dev": "vite", 9 | "build": "vite build", 10 | "preview": "vite preview", 11 | "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore" 12 | }, 13 | "dependencies": { 14 | "bootstrap": "^5.3.3", 15 | "ping.js": "^0.3.0", 16 | "vue": "^3.4.29", 17 | "vue-file-toolbar-menu": "^2.2.0" 18 | }, 19 | "devDependencies": { 20 | "@vitejs/plugin-vue": "^5.0.5", 21 | "eslint": "^8.57.0", 22 | "eslint-plugin-vue": "^9.23.0", 23 | "vite": "^5.3.1" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /examples/simple-examples/config.sample.js: -------------------------------------------------------------------------------- 1 | //// IMPORTANT - Usage: 2 | //// COPY THIS FILE to config.js to use it. 3 | 4 | 5 | 6 | 7 | //////// SIMPLE OPTIONS 8 | 9 | // Serve the LOWA files from a custom URL. 10 | // E.g. for testing on localhost / 127.0.0.1. 11 | // 12 | // When serving the LOWA files from foreign origins, these HTTP headers are needed: 13 | // Cross-Origin-Resource-Policy: cross-origin 14 | // Access-Control-Allow-Origin: * 15 | // Access-Control-Allow-Methods: * 16 | // 17 | // IMPORTANT: Place soffice.{data,data.js.metadata,js,wasm} in this folder. 18 | soffice_base_url = ''; 19 | // current limitations: 20 | // - Relative URLs may not work. Use absolute URLs instead! 21 | // - For example: http://127.0.0.1:8080/lowa_build_subdir/ 22 | // - Also always append the trailing slash! 23 | -------------------------------------------------------------------------------- /examples/web-office/README.md: -------------------------------------------------------------------------------- 1 | An example of how to run a full ZetaOffice / LibreOffice in the web browser. 2 | 3 | See config.sample.js for configuration options. 4 | 5 | [online demo](https://zetaoffice.net/demos/web-office/) 6 | 7 | # Run local for development 8 | 9 | To run the example, do 10 | ``` 11 | npm install 12 | npm start 13 | ``` 14 | 15 | # Using with a web server 16 | 17 | For getting files you can put on a web server, do 18 | ``` 19 | npm install 20 | npm run dist 21 | ``` 22 | 23 | The following HTTP headers must be set in the web server configuration. 24 | ``` 25 | Cross-Origin-Opener-Policy "same-origin" 26 | Cross-Origin-Embedder-Policy "require-corp" 27 | ``` 28 | 29 | Attention: When using in production, replace the zetajs 'file:' link in `package.json` with a proper version from npmjs.com. 30 | -------------------------------------------------------------------------------- /examples/vuejs3-ping-tool/public/config.sample.js: -------------------------------------------------------------------------------- 1 | //// IMPORTANT - Usage: 2 | //// COPY THIS FILE to config.js to use it. 3 | 4 | 5 | 6 | 7 | //////// SIMPLE OPTIONS 8 | 9 | // Serve the LOWA files from a custom URL. 10 | // E.g. for testing on localhost / 127.0.0.1. 11 | // 12 | // When serving the LOWA files from foreign origins, these HTTP headers are needed: 13 | // Cross-Origin-Resource-Policy: cross-origin 14 | // Access-Control-Allow-Origin: * 15 | // Access-Control-Allow-Methods: * 16 | // 17 | // IMPORTANT: Place soffice.{data,data.js.metadata,js,wasm} in this folder. 18 | let config_soffice_base_url = ''; 19 | // current limitations: 20 | // - Relative URLs may not work. Use absolute URLs instead! 21 | // - For example: http://127.0.0.1:8080/lowa_build_subdir/ 22 | // - Also always append the trailing slash! 23 | -------------------------------------------------------------------------------- /examples/letter-address-vuejs3/public/config.sample.js: -------------------------------------------------------------------------------- 1 | //// IMPORTANT - Usage: 2 | //// COPY THIS FILE to config.js to use it. 3 | 4 | 5 | 6 | 7 | //////// SIMPLE OPTIONS 8 | 9 | // Serve the LOWA files from a custom URL. 10 | // E.g. for testing on localhost / 127.0.0.1. 11 | // 12 | // When serving the LOWA files from foreign origins, these HTTP headers are needed: 13 | // Cross-Origin-Resource-Policy: cross-origin 14 | // Access-Control-Allow-Origin: * 15 | // Access-Control-Allow-Methods: * 16 | // 17 | // IMPORTANT: Place soffice.{data,data.js.metadata,js,wasm} in this folder. 18 | let config_soffice_base_url = ''; 19 | // current limitations: 20 | // - Relative URLs may not work. Use absolute URLs instead! 21 | // - For example: http://127.0.0.1:8080/lowa_build_subdir/ 22 | // - Also always append the trailing slash! 23 | -------------------------------------------------------------------------------- /examples/web-office/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web-office", 3 | "version": "1.0.0", 4 | "description": "An example of how to run a full ZetaOffice / LibreOffice in the web browser.", 5 | "scripts": { 6 | "start": "node_modules/.bin/gulp start", 7 | "debug": "node_modules/.bin/gulp debug", 8 | "dist": "node_modules/.bin/gulp", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "author": "allotropia software GmbH", 12 | "license": "MIT", 13 | "devDependencies": { 14 | "browser-sync": "^3.0.2", 15 | "del": "^6.0.0", 16 | "gulp": "^4.0.2", 17 | "gulp-beautify": "^3.0.0", 18 | "gulp-inject-string": "^1.1.2", 19 | "merge-stream": "^2.0.0", 20 | "yargs": "^17.7.2" 21 | }, 22 | "dependencies": { 23 | "bootstrap": "^5.3.3", 24 | "zetajs": "file:../.." 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/letter-address-tool/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zetajs-example-letter", 3 | "version": "1.0.0", 4 | "description": "Example how to use zetajs to insert addresses in LibreOffice Writer", 5 | "scripts": { 6 | "start": "node_modules/.bin/gulp start", 7 | "debug": "node_modules/.bin/gulp debug", 8 | "dist": "node_modules/.bin/gulp", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "author": "allotropia software GmbH", 12 | "license": "MIT", 13 | "devDependencies": { 14 | "browser-sync": "^3.0.2", 15 | "del": "^6.0.0", 16 | "gulp": "^4.0.2", 17 | "gulp-beautify": "^3.0.0", 18 | "gulp-inject-string": "^1.1.2", 19 | "merge-stream": "^2.0.0", 20 | "yargs": "^17.7.2" 21 | }, 22 | "dependencies": { 23 | "w3-css": "^4.1.0", 24 | "zetajs": "file:../.." 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/standalone/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zetajs-example-standalone", 3 | "version": "1.0.0", 4 | "description": "Example how to use zetajs to do simple text formatting in LibreOffice Writer", 5 | "scripts": { 6 | "start": "node_modules/.bin/gulp start", 7 | "debug": "node_modules/.bin/gulp debug", 8 | "dist": "node_modules/.bin/gulp", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "author": "allotropia software GmbH", 12 | "license": "MIT", 13 | "devDependencies": { 14 | "browser-sync": "^3.0.2", 15 | "del": "^6.0.0", 16 | "gulp": "^4.0.2", 17 | "gulp-beautify": "^3.0.0", 18 | "gulp-inject-string": "^1.1.2", 19 | "merge-stream": "^2.0.0", 20 | "yargs": "^17.7.2" 21 | }, 22 | "dependencies": { 23 | "bootstrap": "^5.3.3", 24 | "zetajs": "file:../.." 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/ping-monitor/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zetajs-ping-monitor", 3 | "version": "1.0.0", 4 | "description": "Example how to use zetajs to display a chart with values being added on the fly.", 5 | "scripts": { 6 | "start": "node_modules/.bin/gulp start", 7 | "debug": "node_modules/.bin/gulp debug", 8 | "dist": "node_modules/.bin/gulp", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "author": "allotropia software GmbH", 12 | "license": "MIT", 13 | "devDependencies": { 14 | "browser-sync": "^3.0.2", 15 | "del": "^6.0.0", 16 | "gulp": "^4.0.2", 17 | "gulp-beautify": "^3.0.0", 18 | "gulp-inject-string": "^1.1.2", 19 | "merge-stream": "^2.0.0", 20 | "yargs": "^17.7.2" 21 | }, 22 | "dependencies": { 23 | "bootstrap": "^5.3.3", 24 | "ping.js": "^0.3.0", 25 | "zetajs": "file:../.." 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /examples/convertpdf/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zetajs-example-convertpdf", 3 | "version": "1.0.0", 4 | "description": "Example how to use zetajs to convert a document to PDF fully client-side", 5 | "scripts": { 6 | "start": "node_modules/.bin/gulp start", 7 | "start-nobrowser": "node_modules/.bin/gulp start --nobrowser", 8 | "debug": "node_modules/.bin/gulp debug", 9 | "dist": "node_modules/.bin/gulp", 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "author": "allotropia software GmbH", 13 | "license": "MIT", 14 | "devDependencies": { 15 | "browser-sync": "^3.0.2", 16 | "del": "^6.0.0", 17 | "gulp": "^4.0.2", 18 | "gulp-beautify": "^3.0.0", 19 | "gulp-inject-string": "^1.1.2", 20 | "merge-stream": "^2.0.0", 21 | "yargs": "^17.7.2" 22 | }, 23 | "dependencies": { 24 | "zetajs": "file:../.." 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/vuejs3-ping-tool/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 13 | 14 | 15 | ZetaJS Ping Tool (Calc Demo with Vue.js-3) 16 | 17 | 18 |
19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /examples/letter-address-tool/README.md: -------------------------------------------------------------------------------- 1 | An example of a Web form letter demo, using a stripped-down, standalone Writer document canvas 2 | without any surrounding menubars, toolbars, side panels, etc. 3 | 4 | See config.sample.js for configuration options. 5 | 6 | [online demo](https://zetaoffice.net/demos/letter-address-tool/) 7 | 8 | # Run local for development 9 | 10 | To run the example, do 11 | ``` 12 | npm install 13 | npm start 14 | ``` 15 | 16 | # Using with a web server 17 | 18 | For getting files you can put on a web server, do 19 | ``` 20 | npm install 21 | npm run dist 22 | ``` 23 | 24 | The following HTTP headers must be set in the web server configuration. 25 | ``` 26 | Cross-Origin-Opener-Policy "same-origin" 27 | Cross-Origin-Embedder-Policy "require-corp" 28 | ``` 29 | 30 | Attention: When using in production, replace the zetajs 'file:' link in `package.json` with a proper version from npmjs.com. 31 | -------------------------------------------------------------------------------- /examples/letter-address-vuejs3/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 13 | 14 | 15 | ZetaJS: Letter Address Vue.js-3 (Writer Demo with Vue.js-3) 16 | 17 | 18 |
19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /examples/ping-monitor/README.md: -------------------------------------------------------------------------------- 1 | An example of a simple Calc document displaying a single chart with data being added on the fly. 2 | All surrounding UI elements are hidden in this demo. Can be installed as PWA. 3 | 4 | See config.sample.js for configuration options. 5 | 6 | [online demo](https://zetaoffice.net/demos/ping-monitor/) 7 | 8 | # Run local for development 9 | 10 | To run the example, do 11 | ``` 12 | npm install 13 | npm start 14 | ``` 15 | 16 | # Using with a web server 17 | 18 | For getting files you can put on a web server, do 19 | ``` 20 | npm install 21 | npm run dist 22 | ``` 23 | 24 | The following HTTP headers must be set in the web server configuration. 25 | ``` 26 | Cross-Origin-Opener-Policy "same-origin" 27 | Cross-Origin-Embedder-Policy "require-corp" 28 | ``` 29 | 30 | Attention: When using in production, replace the zetajs 'file:' link in `package.json` with a proper version from npmjs.com. 31 | -------------------------------------------------------------------------------- /examples/standalone/README.md: -------------------------------------------------------------------------------- 1 | An example of a stripped-down, standalone Writer document canvas without any surrounding menubars, 2 | toolbars, side panels, etc. Includes HTML buttons to for simple formatting (bold, italic, underlined). 3 | 4 | See config.sample.js for configuration options. 5 | 6 | [online demo](https://zetaoffice.net/demos/standalone/) 7 | 8 | ## Run local for development 9 | 10 | To run the example, do 11 | ``` 12 | npm install 13 | npm start 14 | ``` 15 | 16 | ## Using with a web server 17 | 18 | For getting files you can put on a web server, do 19 | ``` 20 | npm install 21 | npm run dist 22 | ``` 23 | 24 | The following HTTP headers must be set in the web server configuration. 25 | ``` 26 | Cross-Origin-Opener-Policy "same-origin" 27 | Cross-Origin-Embedder-Policy "require-corp" 28 | ``` 29 | 30 | Attention: When using in production, replace the zetajs 'file:' link in `package.json` with a proper version from npmjs.com. 31 | -------------------------------------------------------------------------------- /examples/convertpdf/README.md: -------------------------------------------------------------------------------- 1 | An example of a local file to PDF conversion service. If the "Download" button is checked, the 2 | converted PDF document is downloaded to the local file system, in addition to being shown in an 3 | iframe on the web page. 4 | 5 | See config.sample.js for configuration options. 6 | 7 | [online demo](https://zetaoffice.net/demos/convertpdf/) 8 | 9 | ## Run local for development 10 | 11 | To run the example, do 12 | ``` 13 | npm install 14 | npm start 15 | ``` 16 | 17 | ## Using with a web server 18 | 19 | For getting files you can put on a web server, do 20 | ``` 21 | npm install 22 | npm run dist 23 | ``` 24 | 25 | The following HTTP headers must be set in the web server configuration. 26 | ``` 27 | Cross-Origin-Opener-Policy "same-origin" 28 | Cross-Origin-Embedder-Policy "require-corp" 29 | ``` 30 | 31 | Attention: When using in production, replace the zetajs 'file:' link in `package.json` with a proper version from npmjs.com. 32 | -------------------------------------------------------------------------------- /examples/vuejs3-ping-tool/README.md: -------------------------------------------------------------------------------- 1 | # A ZetaJS Ping Tool (Calc Demo using Vue.js-3) 2 | 3 | [online demo](https://zetaoffice.net/demos/vuejs3-ping-tool/) 4 | 5 | For Vue.js-3 you'll need nodejs. 6 | And either you also install npm system wide or you use: 7 | `nodeenv --node=system --with-npm nodeenv/` 8 | https://packages.debian.org/bookworm/nodejs 9 | https://packages.debian.org/bookworm/nodeenv 10 | 11 | # vuejs3 12 | 13 | ## Customize configuration 14 | 15 | See [Vite Configuration Reference](https://vitejs.dev/config/). 16 | 17 | ## Project Setup 18 | 19 | ```sh 20 | npm install 21 | ``` 22 | 23 | ### Compile and Hot-Reload for Development 24 | 25 | ```sh 26 | npm start 27 | ``` 28 | 29 | ### Compile and Minify for Production 30 | 31 | ```sh 32 | npm run build 33 | ``` 34 | 35 | The following HTTP headers must be set in the web server configuration. 36 | ``` 37 | Cross-Origin-Opener-Policy "same-origin" 38 | Cross-Origin-Embedder-Policy "require-corp" 39 | ``` 40 | 41 | ### Lint with [ESLint](https://eslint.org/) 42 | 43 | ```sh 44 | npm run lint 45 | ``` 46 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 1.2.0 4 | * Added zetaHelper TypeScript library to combine commonly used code 5 | * used for examples: convertpdf, letter-address-tool, letter-address-vuejs3, ping-monitor, vuejs3-ping-tool 6 | * disabled HTML page scrolling when the mouse cursor is above the canvas 7 | * examples: 8 | * improved example layouts and zooming 9 | * improved initialisation 10 | * simplified and unified code 11 | * examples: fixed crash on non uniformly formatted selections 12 | * using Chromium's "File System Access API" in the web-office exmaple 13 | * many small fixes 14 | 15 | ## 1.1.0 16 | * Let zetajs return unwrapped ANY representations 17 | * Technically backwards-incompatible with old code that relied on unspecified implementation details, like relying on receiving wrapped rather than unwrapped UNO ANY representations. 18 | * Converted most examples to NPM 19 | 20 | ## 1.0.3 21 | * Fix version number clash on NPM 22 | 23 | ## 1.0.2 24 | * Add SPDX-License-Identifier 25 | 26 | ## 1.0.1 27 | * Fix domain name 28 | 29 | ## 1.0.0 30 | * Initial release 31 | -------------------------------------------------------------------------------- /test/e2e/test.js: -------------------------------------------------------------------------------- 1 | describe("convert", () => { 2 | beforeAll(async () => { 3 | // Wait a bit to give time for the server to start 4 | await new Promise(r => setTimeout(r, 10000)); 5 | await page.goto("http://127.0.0.1:3000"); 6 | }, 30000); 7 | 8 | it('should convert an odt file to pdf', async () => { 9 | // Wait for zetaoffice to load 10 | await new Promise(r => { 11 | const waitFunc = async () => { 12 | const success = await page.evaluate(() => { 13 | return document.querySelector('input').disabled === false; 14 | }); 15 | success ? r() : setTimeout(waitFunc, 1000); 16 | }; 17 | waitFunc(); 18 | }); 19 | 20 | // Convert test/test.odt to pdf and make sure it opens in new tab 21 | const download = await page.$("#download"); 22 | download.click(); 23 | const input = await page.$('#input'); 24 | await input.uploadFile('test/e2e/test.odt'); 25 | const newTarget = await browser.waitForTarget(target => target.opener() === page.target()); 26 | await newTarget.page(); 27 | }, 30000); 28 | }); 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 allotropia software GmbH and contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/letter-address-vuejs3/README.md: -------------------------------------------------------------------------------- 1 | # A ZetaJS Letter Address Tool (Writer Demo using Vue.js-3) 2 | 3 | An example of a Web form letter demo, using a stripped-down, standalone Writer document canvas 4 | without any surrounding menubars, toolbars, side panels, etc. This version uses Vue.js-3. 5 | 6 | [online demo](https://zetaoffice.net/demos/letter-address-vuejs3/) 7 | 8 | For Vue.js-3 you'll need nodejs. 9 | And either you also install npm system wide or you use: 10 | `nodeenv --node=system --with-npm nodeenv/` 11 | https://packages.debian.org/bookworm/nodejs 12 | https://packages.debian.org/bookworm/nodeenv 13 | 14 | # vuejs3 15 | 16 | ## Customize configuration 17 | 18 | See [Vite Configuration Reference](https://vitejs.dev/config/). 19 | 20 | ## Project Setup 21 | 22 | ```sh 23 | npm install 24 | ``` 25 | 26 | ### Compile and Hot-Reload for Development 27 | 28 | ```sh 29 | npm start 30 | ``` 31 | 32 | ### Compile and Minify for Production 33 | 34 | ```sh 35 | npm run build 36 | ``` 37 | 38 | The following HTTP headers must be set in the web server configuration. 39 | ``` 40 | Cross-Origin-Opener-Policy "same-origin" 41 | Cross-Origin-Embedder-Policy "require-corp" 42 | ``` 43 | 44 | ### Lint with [ESLint](https://eslint.org/) 45 | 46 | ```sh 47 | npm run lint 48 | ``` 49 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zetajs", 3 | "version": "1.2.0", 4 | "description": "Access ZetaOffice in the Browser from JavaScript via UNO", 5 | "exports": { 6 | "./zeta.js": "./source/zeta.js", 7 | "./zetaHelper.js": "./source/zetaHelper.js", 8 | "./zetaHelper.ts": "./source/zetaHelper.ts" 9 | }, 10 | "directories": { 11 | "doc": "docs", 12 | "example": "examples", 13 | "test": "test" 14 | }, 15 | "files": [ 16 | "/CHANGELOG.md", 17 | "/LICENSE", 18 | "/source/zeta.js", 19 | "/source/zetaHelper.js" 20 | ], 21 | "scripts": { 22 | "build": "tsc", 23 | "prepare": "npm run build", 24 | "test": "jest test/e2e/", 25 | "test:convertpdf": "cd examples/convertpdf && npm install && npm run start-nobrowser" 26 | }, 27 | "repository": { 28 | "type": "git", 29 | "url": "git+https://github.com/allotropia/zetajs.git" 30 | }, 31 | "keywords": [ 32 | "libreoffice", 33 | "office", 34 | "zetaoffice", 35 | "allotropia", 36 | "collabora", 37 | "wasm", 38 | "lowa", 39 | "typescript" 40 | ], 41 | "author": "allotropia software GmbH", 42 | "license": "MIT", 43 | "bugs": { 44 | "url": "https://github.com/allotropia/zetajs/issues" 45 | }, 46 | "homepage": "https://github.com/allotropia/zetajs#readme", 47 | "devDependencies": { 48 | "@types/node": "^22.13.10", 49 | "concurrently": "^9.1.0", 50 | "jest": "^29.7.0", 51 | "jest-puppeteer": "^11.0.0", 52 | "puppeteer": "^23.11.1", 53 | "typescript": "^5.8.2" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /examples/vuejs3-ping-tool/src/components/ControlBar.vue: -------------------------------------------------------------------------------- 1 | 2 | 50 | 51 | 52 | 53 | 54 | 59 | 60 | 62 | -------------------------------------------------------------------------------- /test/README.md: -------------------------------------------------------------------------------- 1 | # zetajs Smoketesting 2 | 3 | *Note:* Running `smoketest.js` requires a LibreOffice configured with `--enable-dbgutil` to have 4 | the `org.libreoffice.embindtest` UNOIDL entities available. 5 | 6 | ## Running the smoketest 7 | 8 | Put this directory into a webservers webroot. 9 | 10 | The following HTTP headers must be set in the webserver configuration. 11 | ``` 12 | Cross-Origin-Opener-Policy "same-origin" 13 | Cross-Origin-Embedder-Policy "require-corp" 14 | ``` 15 | 16 | You might configure a webserver like Apache for this. Or you use emrun which comes has Emscripten and sets this headers by default. 17 | ``` 18 | $ emrun rainbow_writer.html 19 | ``` 20 | 21 | Additionally you need to provide the following files. in the webroot: 22 | ``` 23 | soffice.data 24 | soffice.data.js.metadata 25 | soffice.js 26 | soffice.wasm 27 | zeta.js 28 | ``` 29 | 30 | ### Alternative: Build LOWA with EMSCRIPTEN_EXTRA_SOFFICE_PRE_JS 31 | 32 | One way to run some of the provided example and test code is to serve those files next to `qt_soffice.html`, along with some `include.js` that looks like 33 | ``` 34 | Module.uno_scripts = [ 35 | 'zetajs/source/zeta.js', 36 | 'zetajs/test/smoketest.js']; 37 | ``` 38 | (or whatever the paths where you serve them, relative to `qt_soffice.html`; `zeta.js` always needs to come first), and to build LOWA with an `EMSCRIPTEN_EXTRA_SOFFICE_PRE_JS=/path/to/include.js` configuration option (e.g., as a line in `autogen.input`), with `/path/to` adapted accordingly. (The `test/smoketest.js` code requires a LibreOffice configured with `--enable-dbgutil` to have the `org.libreoffice.embindtest` UNOIDL entities available.) 39 | -------------------------------------------------------------------------------- /examples/convertpdf/config.sample.js: -------------------------------------------------------------------------------- 1 | //// IMPORTANT - Usage: 2 | //// COPY THIS FILE to config.js to use it. 3 | //// 4 | //// gulpfile.js will automatically include config.js if it exists. 5 | 6 | 7 | 8 | 9 | //////// SIMPLE OPTIONS 10 | 11 | // Serve the LOWA files from a custom URL. 12 | // E.g. for testing on localhost / 127.0.0.1. 13 | // 14 | // When serving the LOWA files from foreign origins, these HTTP headers are needed: 15 | // Cross-Origin-Resource-Policy: cross-origin 16 | // Access-Control-Allow-Origin: * 17 | // Access-Control-Allow-Methods: * 18 | // 19 | // '' will assume the LOWA files in the same directory as the web app. 20 | // IMPORTANT: Place soffice.{data,data.js.metadata,js,wasm} in the public/ folder. 21 | // Use with "npm run debug". (other tasks may clean the public/ folder) 22 | soffice_base_url = ''; 23 | // current limitations: 24 | // - Relative URLs may not work. Use absolute URLs instead! 25 | // - For example: http://127.0.0.1:8080/lowa_build_subdir/ 26 | // - Also always append the trailing slash! 27 | 28 | // Disable cleaning the public folder for all tasks. 29 | clean_disabled = true; 30 | 31 | // Custom webserver port. 32 | //custom_port = "8080"; 33 | 34 | // Which web browser to start with "npm run start". 35 | //custom_browser = "chromium"; 36 | 37 | 38 | 39 | 40 | //////// ADVANCED OPTIONS 41 | 42 | //// You may modify the debug target. 43 | // exports.debug = gulp.series(gulp.parallel(compileHTML, compileJS, copyVendors)); 44 | 45 | //// Pick a custom browser and launch it automatically. 46 | //custom_browser = "chromium"; // ["google chrome", "firefox"] 47 | //exports.debug = gulp.series(gulp.parallel(compileHTML, compileJS, copyVendors), 48 | // gulp.parallel(watchFiles, initBrowserSync) ); 49 | -------------------------------------------------------------------------------- /examples/ping-monitor/config.sample.js: -------------------------------------------------------------------------------- 1 | //// IMPORTANT - Usage: 2 | //// COPY THIS FILE to config.js to use it. 3 | //// 4 | //// gulpfile.js will automatically include config.js if it exists. 5 | 6 | 7 | 8 | 9 | //////// SIMPLE OPTIONS 10 | 11 | // Serve the LOWA files from a custom URL. 12 | // E.g. for testing on localhost / 127.0.0.1. 13 | // 14 | // When serving the LOWA files from foreign origins, these HTTP headers are needed: 15 | // Cross-Origin-Resource-Policy: cross-origin 16 | // Access-Control-Allow-Origin: * 17 | // Access-Control-Allow-Methods: * 18 | // 19 | // '' will assume the LOWA files in the same directory as the web app. 20 | // IMPORTANT: Place soffice.{data,data.js.metadata,js,wasm} in the public/ folder. 21 | // Use with "npm run debug". (other tasks may clean the public/ folder) 22 | soffice_base_url = ''; 23 | // current limitations: 24 | // - Relative URLs may not work. Use absolute URLs instead! 25 | // - For example: http://127.0.0.1:8080/lowa_build_subdir/ 26 | // - Also always append the trailing slash! 27 | 28 | // Disable cleaning the public folder for all tasks. 29 | clean_disabled = true; 30 | 31 | // Custom webserver port. 32 | //custom_port = "8080"; 33 | 34 | // Which web browser to start with "npm run start". 35 | //custom_browser = "chromium"; 36 | 37 | 38 | 39 | 40 | //////// ADVANCED OPTIONS 41 | 42 | //// You may modify the debug target. 43 | // exports.debug = gulp.series(gulp.parallel(compileHTML, compileJS, copyVendors)); 44 | 45 | //// Pick a custom browser and launch it automatically. 46 | //custom_browser = "chromium"; // ["google chrome", "firefox"] 47 | //exports.debug = gulp.series(gulp.parallel(compileHTML, compileJS, copyVendors), 48 | // gulp.parallel(watchFiles, initBrowserSync) ); 49 | -------------------------------------------------------------------------------- /examples/standalone/config.sample.js: -------------------------------------------------------------------------------- 1 | //// IMPORTANT - Usage: 2 | //// COPY THIS FILE to config.js to use it. 3 | //// 4 | //// gulpfile.js will automatically include config.js if it exists. 5 | 6 | 7 | 8 | 9 | //////// SIMPLE OPTIONS 10 | 11 | // Serve the LOWA files from a custom URL. 12 | // E.g. for testing on localhost / 127.0.0.1. 13 | // 14 | // When serving the LOWA files from foreign origins, these HTTP headers are needed: 15 | // Cross-Origin-Resource-Policy: cross-origin 16 | // Access-Control-Allow-Origin: * 17 | // Access-Control-Allow-Methods: * 18 | // 19 | // '' will assume the LOWA files in the same directory as the web app. 20 | // IMPORTANT: Place soffice.{data,data.js.metadata,js,wasm} in the public/ folder. 21 | // Use with "npm run debug". (other tasks may clean the public/ folder) 22 | soffice_base_url = ''; 23 | // current limitations: 24 | // - Relative URLs may not work. Use absolute URLs instead! 25 | // - For example: http://127.0.0.1:8080/lowa_build_subdir/ 26 | // - Also always append the trailing slash! 27 | 28 | // Disable cleaning the public folder for all tasks. 29 | clean_disabled = true; 30 | 31 | // Custom webserver port. 32 | //custom_port = "8080"; 33 | 34 | // Which web browser to start with "npm run start". 35 | //custom_browser = "chromium"; 36 | 37 | 38 | 39 | 40 | //////// ADVANCED OPTIONS 41 | 42 | //// You may modify the debug target. 43 | // exports.debug = gulp.series(gulp.parallel(compileHTML, compileJS, copyVendors)); 44 | 45 | //// Pick a custom browser and launch it automatically. 46 | //custom_browser = "chromium"; // ["google chrome", "firefox"] 47 | //exports.debug = gulp.series(gulp.parallel(compileHTML, compileJS, copyVendors), 48 | // gulp.parallel(watchFiles, initBrowserSync) ); 49 | -------------------------------------------------------------------------------- /examples/web-office/config.sample.js: -------------------------------------------------------------------------------- 1 | //// IMPORTANT - Usage: 2 | //// COPY THIS FILE to config.js to use it. 3 | //// 4 | //// gulpfile.js will automatically include config.js if it exists. 5 | 6 | 7 | 8 | 9 | //////// SIMPLE OPTIONS 10 | 11 | // Serve the LOWA files from a custom URL. 12 | // E.g. for testing on localhost / 127.0.0.1. 13 | // 14 | // When serving the LOWA files from foreign origins, these HTTP headers are needed: 15 | // Cross-Origin-Resource-Policy: cross-origin 16 | // Access-Control-Allow-Origin: * 17 | // Access-Control-Allow-Methods: * 18 | // 19 | // '' will assume the LOWA files in the same directory as the web app. 20 | // IMPORTANT: Place soffice.{data,data.js.metadata,js,wasm} in the public/ folder. 21 | // Use with "npm run debug". (other tasks may clean the public/ folder) 22 | soffice_base_url = ''; 23 | // current limitations: 24 | // - Relative URLs may not work. Use absolute URLs instead! 25 | // - For example: http://127.0.0.1:8080/lowa_build_subdir/ 26 | // - Also always append the trailing slash! 27 | 28 | // Disable cleaning the public folder for all tasks. 29 | clean_disabled = true; 30 | 31 | // Custom webserver port. 32 | //custom_port = "8080"; 33 | 34 | // Which web browser to start with "npm run start". 35 | //custom_browser = "chromium"; 36 | 37 | 38 | 39 | 40 | //////// ADVANCED OPTIONS 41 | 42 | //// You may modify the debug target. 43 | // exports.debug = gulp.series(gulp.parallel(compileHTML, compileJS, copyVendors)); 44 | 45 | //// Pick a custom browser and launch it automatically. 46 | //custom_browser = "chromium"; // ["google chrome", "firefox"] 47 | //exports.debug = gulp.series(gulp.parallel(compileHTML, compileJS, copyVendors), 48 | // gulp.parallel(watchFiles, initBrowserSync) ); 49 | -------------------------------------------------------------------------------- /examples/letter-address-tool/config.sample.js: -------------------------------------------------------------------------------- 1 | //// IMPORTANT - Usage: 2 | //// COPY THIS FILE to config.js to use it. 3 | //// 4 | //// gulpfile.js will automatically include config.js if it exists. 5 | 6 | 7 | 8 | 9 | //////// SIMPLE OPTIONS 10 | 11 | // Serve the LOWA files from a custom URL. 12 | // E.g. for testing on localhost / 127.0.0.1. 13 | // 14 | // When serving the LOWA files from foreign origins, these HTTP headers are needed: 15 | // Cross-Origin-Resource-Policy: cross-origin 16 | // Access-Control-Allow-Origin: * 17 | // Access-Control-Allow-Methods: * 18 | // 19 | // '' will assume the LOWA files in the same directory as the web app. 20 | // IMPORTANT: Place soffice.{data,data.js.metadata,js,wasm} in the public/ folder. 21 | // Use with "npm run debug". (other tasks may clean the public/ folder) 22 | soffice_base_url = ''; 23 | // current limitations: 24 | // - Relative URLs may not work. Use absolute URLs instead! 25 | // - For example: http://127.0.0.1:8080/lowa_build_subdir/ 26 | // - Also always append the trailing slash! 27 | 28 | // Disable cleaning the public folder for all tasks. 29 | clean_disabled = true; 30 | 31 | // Custom webserver port. 32 | //custom_port = "8080"; 33 | 34 | // Which web browser to start with "npm run start". 35 | //custom_browser = "chromium"; 36 | 37 | 38 | 39 | 40 | //////// ADVANCED OPTIONS 41 | 42 | //// You may modify the debug target. 43 | // exports.debug = gulp.series(gulp.parallel(compileHTML, compileJS, copyVendors)); 44 | 45 | //// Pick a custom browser and launch it automatically. 46 | //custom_browser = "chromium"; // ["google chrome", "firefox"] 47 | //exports.debug = gulp.series(gulp.parallel(compileHTML, compileJS, copyVendors), 48 | // gulp.parallel(watchFiles, initBrowserSync) ); 49 | -------------------------------------------------------------------------------- /examples/simple-examples/find_and_replace.js: -------------------------------------------------------------------------------- 1 | /* -*- Mode: JS; tab-width: 2; indent-tabs-mode: nil; js-indent-level: 2; fill-column: 100 -*- */ 2 | // SPDX-License-Identifier: MIT 3 | 4 | // Debugging note: 5 | // Switch the web worker in the browsers debug tab to debug this code. 6 | // It's the "em-pthread" web worker with the most memory usage, where "zetajs" is defined. 7 | 8 | "use strict"; 9 | 10 | 11 | // global variables - zetajs environment: 12 | let zetajs, css; 13 | 14 | // = global variables (some are global for easier debugging) = 15 | // common variables: 16 | let context, desktop, xModel, toolkit; 17 | // example specific: 18 | let searchDescriptor, xTextCursor; 19 | 20 | 21 | function demo() { 22 | // Replaces every occurence of "LibreOffice" with "LIBRE-OFFICE YEAH". 23 | console.log('PLUS: execute example code'); 24 | 25 | context = zetajs.getUnoComponentContext(); 26 | desktop = css.frame.Desktop.create(context); 27 | const in_path = 'file:///android/default-document/example.odt' 28 | xModel = desktop.loadComponentFromURL(in_path, '_default', 0, []); 29 | 30 | toolkit = css.awt.Toolkit.create(context); 31 | setInterval(function() {try {toolkit.getActiveTopWindow().FullScreen = true} catch {}}, 1000); 32 | 33 | searchDescriptor = xModel.createSearchDescriptor(); 34 | searchDescriptor.setSearchString('LibreOffice'); 35 | xTextCursor = xModel.findFirst(searchDescriptor); 36 | while (xTextCursor !== null) { 37 | //xTextCursor.goRight(0, false); // Appending instead of replacing. 38 | xTextCursor.setString("LIBRE-OFFICE YEAH"); 39 | xTextCursor = xModel.findNext(xTextCursor, searchDescriptor); 40 | } 41 | } 42 | 43 | 44 | Module.zetajs.then(function(pZetajs) { 45 | // initializing zetajs environment: 46 | zetajs = pZetajs; 47 | css = zetajs.uno.com.sun.star; 48 | demo(); // launching demo 49 | }); 50 | 51 | /* vim:set shiftwidth=2 softtabstop=2 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ 52 | -------------------------------------------------------------------------------- /examples/simple-examples/README.md: -------------------------------------------------------------------------------- 1 | These are small examples that showcase code snippets how to programmatically interact with 2 | documents. 3 | 4 | ## Online demos 5 | 6 | - [find_and_replace.html](https://zetaoffice.net/demos/simple-examples/find_and_replace.html) 7 | - [rainbow_writer.html](https://zetaoffice.net/demos/simple-examples/rainbow_writer.html) 8 | - [simple.html](https://zetaoffice.net/demos/simple-examples/simple.html) 9 | - [simple_key_handler.html](https://zetaoffice.net/demos/simple-examples/simple_key_handler.html) 10 | 11 | ## Running the examples 12 | 13 | Put the whole Git repo into a webservers webroot. The whole Git repo is needed, because this directory has a symlink to "../../source/zeta.js". 14 | 15 | The following HTTP headers must be set in the webserver configuration. 16 | ``` 17 | Cross-Origin-Opener-Policy "same-origin" 18 | Cross-Origin-Embedder-Policy "require-corp" 19 | ``` 20 | 21 | You might configure a webserver like Apache for this. Or you use emrun which comes has Emscripten and sets this headers by default. (replace rainbow_writer.html with the simple-example you like to run) 22 | ``` 23 | $ emrun rainbow_writer.html 24 | ``` 25 | 26 | ### Alternative: Build LOWA with EMSCRIPTEN_EXTRA_SOFFICE_PRE_JS 27 | 28 | One way to run some of the provided example and test code is to serve those files next to `qt_soffice.html`, along with some `include.js` that looks like 29 | ``` 30 | Module.uno_scripts = [ 31 | 'zetajs/source/zeta.js', 32 | 'zetajs/examples/rainbow_writer.js']; 33 | ``` 34 | (or whatever the paths where you serve them, relative to `qt_soffice.html`; `zeta.js` always needs to come first), and to build LOWA with an `EMSCRIPTEN_EXTRA_SOFFICE_PRE_JS=/path/to/include.js` configuration option (e.g., as a line in `autogen.input`), with `/path/to` adapted accordingly. (The `test/smoketest.js` code requires a LibreOffice configured with `--enable-dbgutil` to have the `org.libreoffice.embindtest` UNOIDL entities available.) 35 | -------------------------------------------------------------------------------- /examples/web-office/office_thread.js: -------------------------------------------------------------------------------- 1 | /* -*- Mode: JS; tab-width: 2; indent-tabs-mode: nil; js-indent-level: 2; fill-column: 100 -*- */ 2 | // SPDX-License-Identifier: MIT 3 | 4 | // Debugging note: 5 | // Switch the web worker in the browsers debug tab to debug this code. 6 | // It's the "em-pthread" web worker with the most memory usage, where "zetajs" is defined. 7 | 8 | 'use strict'; 9 | 10 | 11 | // global variables - zetajs environment: 12 | let zetajs, css; 13 | 14 | // = global variables (some are global for easier debugging) = 15 | // common variables: 16 | let context, desktop, xModel, ctrl; 17 | 18 | 19 | function demo() { 20 | context = zetajs.getUnoComponentContext(); 21 | const bean_overwrite = new css.beans.PropertyValue({Name: 'Overwrite', Value: true}); 22 | const bean_odt_export = new css.beans.PropertyValue({Name: 'FilterName', Value: 'writer8'}); 23 | desktop = css.frame.Desktop.create(context); 24 | 25 | zetajs.mainPort.onmessage = function (e) { 26 | switch (e.data.cmd) { 27 | case 'upload': 28 | loadFile(e.data.filename); 29 | break; 30 | case 'download': 31 | xModel.store(); 32 | zetajs.mainPort.postMessage({cmd: 'download', id: e.data.id}); 33 | break; 34 | default: 35 | throw Error('Unknown message command: ' + e.data.cmd); 36 | } 37 | } 38 | zetajs.mainPort.postMessage({cmd: 'thr_running'}); 39 | } 40 | 41 | function loadFile(filename) { 42 | const in_path = 'file:///tmp/office/' + filename; 43 | xModel = desktop.loadComponentFromURL(in_path, '_default', 0, []); 44 | ctrl = xModel.getCurrentController(); 45 | ctrl.getFrame().getContainerWindow().FullScreen = true; 46 | zetajs.mainPort.postMessage({cmd: 'ui_ready'}); 47 | } 48 | 49 | Module.zetajs.then(function(pZetajs) { 50 | // initializing zetajs environment: 51 | zetajs = pZetajs; 52 | css = zetajs.uno.com.sun.star; 53 | demo(); // launching demo 54 | }); 55 | 56 | /* vim:set shiftwidth=2 softtabstop=2 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ 57 | -------------------------------------------------------------------------------- /examples/simple-examples/simple.js: -------------------------------------------------------------------------------- 1 | /* -*- Mode: JS; tab-width: 2; indent-tabs-mode: nil; js-indent-level: 2; fill-column: 100 -*- */ 2 | // SPDX-License-Identifier: MIT 3 | 4 | // Debugging note: 5 | // Switch the web worker in the browsers debug tab to debug this code. 6 | // It's the "em-pthread" web worker with the most memory usage, where "zetajs" is defined. 7 | 8 | 'use strict'; 9 | 10 | 11 | // global variables - zetajs environment: 12 | let zetajs, css; 13 | 14 | // = global variables (some are global for easier debugging) = 15 | // common variables: 16 | let context, desktop, xModel, toolkit; 17 | // example specific: 18 | let xText, xTextCursor; 19 | 20 | 21 | function demo() { 22 | // Adapted sample code from static/README.wasm.md: 23 | console.log('PLUS: execute example code'); 24 | 25 | // load document 26 | context = zetajs.getUnoComponentContext(); 27 | desktop = css.frame.Desktop.create(context); 28 | xModel = desktop.getCurrentFrame().getController().getModel(); 29 | if (!xModel?.queryInterface(zetajs.type.interface(css.text.XTextDocument))) { 30 | xModel = desktop.loadComponentFromURL( 31 | 'file:///android/default-document/example.odt', '_default', 0, []); 32 | } 33 | toolkit = css.awt.Toolkit.create(context); 34 | setInterval(function() {try {toolkit.getActiveTopWindow().FullScreen = true} catch {}}, 1000); 35 | xText = xModel.getText(); 36 | 37 | // insert string 38 | xTextCursor = xText.createTextCursor(); 39 | xTextCursor.setString("string here!"); 40 | 41 | // colorize paragraphs 42 | const xParaEnumeration = xText.createEnumeration(); 43 | for (const xParagraph of xParaEnumeration) { 44 | const color = Math.floor(Math.random() * 0xFFFFFF); 45 | try { 46 | xParagraph.setPropertyValue("CharColor", color); 47 | } catch (e) { 48 | if (zetajs.getAnyType(zetajs.catchUnoException(e)) != 49 | 'com.sun.star.lang.IllegalArgumentException') 50 | { 51 | throw e; 52 | } 53 | } 54 | } 55 | } 56 | 57 | 58 | Module.zetajs.then(function(pZetajs) { 59 | // initializing zetajs environment: 60 | zetajs = pZetajs; 61 | css = zetajs.uno.com.sun.star; 62 | demo(); // launching demo 63 | }); 64 | 65 | /* vim:set shiftwidth=2 softtabstop=2 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ 66 | -------------------------------------------------------------------------------- /examples/vuejs3-ping-tool/src/assets/base.css: -------------------------------------------------------------------------------- 1 | /* color palette from */ 2 | :root { 3 | --vt-c-white: #ffffff; 4 | --vt-c-white-soft: #f8f8f8; 5 | --vt-c-white-mute: #f2f2f2; 6 | 7 | --vt-c-black: #181818; 8 | --vt-c-black-soft: #222222; 9 | --vt-c-black-mute: #282828; 10 | 11 | --vt-c-indigo: #2c3e50; 12 | 13 | --vt-c-divider-light-1: rgba(60, 60, 60, 0.29); 14 | --vt-c-divider-light-2: rgba(60, 60, 60, 0.12); 15 | --vt-c-divider-dark-1: rgba(84, 84, 84, 0.65); 16 | --vt-c-divider-dark-2: rgba(84, 84, 84, 0.48); 17 | 18 | --vt-c-text-light-1: var(--vt-c-indigo); 19 | --vt-c-text-light-2: rgba(60, 60, 60, 0.66); 20 | --vt-c-text-dark-1: var(--vt-c-white); 21 | --vt-c-text-dark-2: rgba(235, 235, 235, 0.64); 22 | } 23 | 24 | /* semantic color variables for this project */ 25 | :root { 26 | --color-background: var(--vt-c-white); 27 | --color-background-soft: var(--vt-c-white-soft); 28 | --color-background-mute: var(--vt-c-white-mute); 29 | 30 | --color-border: var(--vt-c-divider-light-2); 31 | --color-border-hover: var(--vt-c-divider-light-1); 32 | 33 | --color-heading: var(--vt-c-text-light-1); 34 | --color-text: var(--vt-c-text-light-1); 35 | 36 | --section-gap: 160px; 37 | } 38 | 39 | @media (prefers-color-scheme: dark) { 40 | :root { 41 | --color-background: var(--vt-c-black); 42 | --color-background-soft: var(--vt-c-black-soft); 43 | --color-background-mute: var(--vt-c-black-mute); 44 | 45 | --color-border: var(--vt-c-divider-dark-2); 46 | --color-border-hover: var(--vt-c-divider-dark-1); 47 | 48 | --color-heading: var(--vt-c-text-dark-1); 49 | --color-text: var(--vt-c-text-dark-2); 50 | } 51 | } 52 | 53 | *, 54 | *::before, 55 | *::after { 56 | box-sizing: border-box; 57 | margin: 0; 58 | font-weight: normal; 59 | } 60 | 61 | body { 62 | min-height: 100vh; 63 | color: var(--color-text); 64 | background: var(--color-background); 65 | transition: 66 | color 0.5s, 67 | background-color 0.5s; 68 | line-height: 1.6; 69 | font-family: 70 | Inter, 71 | -apple-system, 72 | BlinkMacSystemFont, 73 | 'Segoe UI', 74 | Roboto, 75 | Oxygen, 76 | Ubuntu, 77 | Cantarell, 78 | 'Fira Sans', 79 | 'Droid Sans', 80 | 'Helvetica Neue', 81 | sans-serif; 82 | font-size: 15px; 83 | text-rendering: optimizeLegibility; 84 | -webkit-font-smoothing: antialiased; 85 | -moz-osx-font-smoothing: grayscale; 86 | } 87 | -------------------------------------------------------------------------------- /examples/letter-address-vuejs3/src/assets/base.css: -------------------------------------------------------------------------------- 1 | /* color palette from */ 2 | :root { 3 | --vt-c-white: #ffffff; 4 | --vt-c-white-soft: #f8f8f8; 5 | --vt-c-white-mute: #f2f2f2; 6 | 7 | --vt-c-black: #181818; 8 | --vt-c-black-soft: #222222; 9 | --vt-c-black-mute: #282828; 10 | 11 | --vt-c-indigo: #2c3e50; 12 | 13 | --vt-c-divider-light-1: rgba(60, 60, 60, 0.29); 14 | --vt-c-divider-light-2: rgba(60, 60, 60, 0.12); 15 | --vt-c-divider-dark-1: rgba(84, 84, 84, 0.65); 16 | --vt-c-divider-dark-2: rgba(84, 84, 84, 0.48); 17 | 18 | --vt-c-text-light-1: var(--vt-c-indigo); 19 | --vt-c-text-light-2: rgba(60, 60, 60, 0.66); 20 | --vt-c-text-dark-1: var(--vt-c-white); 21 | --vt-c-text-dark-2: rgba(235, 235, 235, 0.64); 22 | } 23 | 24 | /* semantic color variables for this project */ 25 | :root { 26 | --color-background: var(--vt-c-white); 27 | --color-background-soft: var(--vt-c-white-soft); 28 | --color-background-mute: var(--vt-c-white-mute); 29 | 30 | --color-border: var(--vt-c-divider-light-2); 31 | --color-border-hover: var(--vt-c-divider-light-1); 32 | 33 | --color-heading: var(--vt-c-text-light-1); 34 | --color-text: var(--vt-c-text-light-1); 35 | 36 | --section-gap: 160px; 37 | } 38 | 39 | @media (prefers-color-scheme: dark) { 40 | :root { 41 | --color-background: var(--vt-c-black); 42 | --color-background-soft: var(--vt-c-black-soft); 43 | --color-background-mute: var(--vt-c-black-mute); 44 | 45 | --color-border: var(--vt-c-divider-dark-2); 46 | --color-border-hover: var(--vt-c-divider-dark-1); 47 | 48 | --color-heading: var(--vt-c-text-dark-1); 49 | --color-text: var(--vt-c-text-dark-2); 50 | } 51 | } 52 | 53 | *, 54 | *::before, 55 | *::after { 56 | box-sizing: border-box; 57 | margin: 0; 58 | font-weight: normal; 59 | } 60 | 61 | body { 62 | min-height: 100vh; 63 | color: var(--color-text); 64 | background: var(--color-background); 65 | transition: 66 | color 0.5s, 67 | background-color 0.5s; 68 | line-height: 1.6; 69 | font-family: 70 | Inter, 71 | -apple-system, 72 | BlinkMacSystemFont, 73 | 'Segoe UI', 74 | Roboto, 75 | Oxygen, 76 | Ubuntu, 77 | Cantarell, 78 | 'Fira Sans', 79 | 'Droid Sans', 80 | 'Helvetica Neue', 81 | sans-serif; 82 | font-size: 15px; 83 | text-rendering: optimizeLegibility; 84 | -webkit-font-smoothing: antialiased; 85 | -moz-osx-font-smoothing: grayscale; 86 | } 87 | -------------------------------------------------------------------------------- /examples/convertpdf/office_thread.js: -------------------------------------------------------------------------------- 1 | /* -*- Mode: JS; tab-width: 2; indent-tabs-mode: nil; js-indent-level: 2; fill-column: 100 -*- */ 2 | // SPDX-License-Identifier: MIT 3 | 4 | // Debugging note: 5 | // Switch the web worker in the browsers debug tab to debug this code. 6 | // It's the "em-pthread" web worker with the most memory usage, where "zetajs" is defined. 7 | 8 | // JS mode: module 9 | import { ZetaHelperThread } from './assets/vendor/zetajs/zetaHelper.js'; 10 | 11 | 12 | // global variables - zetajs environment: 13 | const zHT = new ZetaHelperThread(); 14 | const zetajs = zHT.zetajs; 15 | const css = zHT.css; 16 | 17 | // = global variables (some are global for easier debugging) = 18 | // common variables: 19 | let xModel; 20 | // example specific: 21 | let bean_hidden, bean_overwrite, bean_pdf_export, from, to; 22 | 23 | // Export variables for debugging. Available for debugging via: 24 | // globalThis.zetajsStore.threadJsContext 25 | export { zHT, xModel, bean_hidden, bean_overwrite, bean_pdf_export, from, to }; 26 | 27 | 28 | function demo() { 29 | bean_hidden = new css.beans.PropertyValue({Name: 'Hidden', Value: true}); 30 | bean_overwrite = new css.beans.PropertyValue({Name: 'Overwrite', Value: true}); 31 | bean_pdf_export = new css.beans.PropertyValue({Name: 'FilterName', Value: 'writer_pdf_Export'}); 32 | 33 | zHT.thrPort.onmessage = (e) => { 34 | switch (e.data.cmd) { 35 | case 'convert': 36 | try { 37 | // Close old document in advance. Keep document open afterwards for debugging. 38 | if (xModel !== undefined && 39 | xModel.queryInterface(zetajs.type.interface(css.util.XCloseable))) { 40 | xModel.close(false); 41 | } 42 | from = e.data.from; 43 | to = e.data.to; 44 | xModel = zHT.desktop.loadComponentFromURL('file://' + from, '_blank', 0, [bean_hidden]); 45 | xModel.storeToURL( 'file://' + to, [bean_overwrite, bean_pdf_export]); 46 | zetajs.mainPort.postMessage({cmd: 'converted', name: e.data.name, from, to}); 47 | } catch (e) { 48 | const exc = zetajs.catchUnoException(e); 49 | console.log('TODO', zetajs.getAnyType(exc), exc.Message); 50 | } 51 | break; 52 | default: 53 | throw Error('Unknown message command: ' + e.data.cmd); 54 | } 55 | } 56 | 57 | zHT.thrPort.postMessage({cmd: 'start'}); 58 | } 59 | 60 | demo(); // launching demo 61 | 62 | /* vim:set shiftwidth=2 softtabstop=2 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ 63 | -------------------------------------------------------------------------------- /examples/simple-examples/simple_key_handler.js: -------------------------------------------------------------------------------- 1 | /* -*- Mode: JS; tab-width: 2; indent-tabs-mode: nil; js-indent-level: 2; fill-column: 100 -*- */ 2 | // SPDX-License-Identifier: MIT 3 | 4 | // Debugging note: 5 | // Switch the web worker in the browsers debug tab to debug this code. 6 | // It's the "em-pthread" web worker with the most memory usage, where "zetajs" is defined. 7 | 8 | "use strict"; 9 | 10 | 11 | // global variables - zetajs environment: 12 | let zetajs, css; 13 | 14 | // = global variables (some are global for easier debugging) = 15 | // common variables: 16 | let context, desktop, xModel, toolkit; 17 | // example specific: 18 | let myXKeyHandler, evtPressed, evtReleased, xController; 19 | 20 | 21 | function demo() { 22 | console.log('PLUS: execute example code'); 23 | 24 | /* Implements com.sun.star.awt.XKeyHandler 25 | * Outputs printable characters typed into the OfficeDocument. 26 | * Browser console is used for output. 27 | */ 28 | myXKeyHandler = zetajs.unoObject( 29 | [css.awt.XKeyHandler], 30 | { 31 | keyPressed(e) { 32 | console.log('key pressed (' + e.KeyCode + "): " + e.KeyChar); 33 | evtPressed = e; 34 | return false; // false: don't consume (run other event handlers) 35 | }, 36 | keyReleased(e) { 37 | console.log('key released (' + e.KeyCode + "): " + e.KeyChar); 38 | evtReleased = e; 39 | return false; // false: don't consume (run other event handlers) 40 | } 41 | }); 42 | 43 | context = zetajs.getUnoComponentContext(); 44 | desktop = css.frame.Desktop.create(context); 45 | // Open a new writer document. 46 | // xModel is somethink like: SwXTextDocument, ScModelObj, SdXImpressDocument 47 | xModel = desktop.loadComponentFromURL('private:factory/swriter', '_default', 0, []); 48 | xController = xModel.getCurrentController(); 49 | 50 | toolkit = css.awt.Toolkit.create(context); 51 | setInterval(function() {try {toolkit.getActiveTopWindow().FullScreen = true} catch {}}, 1000); 52 | 53 | xController.addKeyHandler(myXKeyHandler); 54 | // addKeyListener != addKeyHandler (that's something very DIFFERENT) 55 | 56 | const xText = xModel.getText(); 57 | const xTextCursor = xText.createTextCursor(); 58 | xTextCursor.setString("Open browser console and type something in LibreOffice!"); 59 | } 60 | 61 | 62 | Module.zetajs.then(function(pZetajs) { 63 | // initializing zetajs environment: 64 | zetajs = pZetajs; 65 | css = zetajs.uno.com.sun.star; 66 | demo(); // launching demo 67 | }); 68 | 69 | /* vim:set shiftwidth=2 softtabstop=2 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ 70 | -------------------------------------------------------------------------------- /examples/simple-examples/simple.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 13 | ZetaJS Demo: Simple Example 14 | 18 | 19 | 20 | 21 | 25 | 26 | 27 | 28 | 29 | 37 | 38 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /examples/simple-examples/rainbow_writer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 13 | ZetaJS Demo: Rainbow Writer 14 | 18 | 19 | 20 | 21 | 25 | 26 | 27 | 28 | 29 | 37 | 38 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /examples/simple-examples/find_and_replace.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 13 | ZetaJS Demo: Find and Replace 14 | 18 | 19 | 20 | 21 | 25 | 26 | 27 | 28 | 29 | 37 | 38 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /examples/simple-examples/simple_key_handler.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 13 | ZetaJS Demo: Simple Key Handler 14 | 18 | 19 | 20 | 21 | 25 | 26 | 27 | 28 | 29 | 37 | 38 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /examples/standalone/office_thread.js: -------------------------------------------------------------------------------- 1 | /* -*- Mode: JS; tab-width: 2; indent-tabs-mode: nil; js-indent-level: 2; fill-column: 100 -*- */ 2 | // SPDX-License-Identifier: MIT 3 | 4 | // Debugging note: 5 | // Switch the web worker in the browsers debug tab to debug this code. 6 | // It's the "em-pthread" web worker with the most memory usage, where "zetajs" is defined. 7 | 8 | 'use strict'; 9 | 10 | 11 | // global variables - zetajs environment: 12 | let zetajs, css; 13 | 14 | // = global variables (some are global for easier debugging) = 15 | // common variables: 16 | let context, desktop, xModel, ctrl; 17 | 18 | 19 | function demo() { 20 | context = zetajs.getUnoComponentContext(); 21 | 22 | // Turn off toolbars: 23 | const config = css.configuration.ReadWriteAccess.create(context, 'en-US'); 24 | const uielems = config.getByHierarchicalName( 25 | '/org.openoffice.Office.UI.WriterWindowState/UIElements/States'); 26 | for (const i of uielems.getElementNames()) { 27 | const uielem = uielems.getByName(i); 28 | if (uielem.getByName('Visible')) { 29 | uielem.setPropertyValue('Visible', false); 30 | } 31 | } 32 | config.commitChanges(); 33 | 34 | desktop = css.frame.Desktop.create(context); 35 | xModel = desktop.loadComponentFromURL('private:factory/swriter', '_default', 0, []) 36 | ctrl = xModel.getCurrentController(); 37 | ctrl.getFrame().getContainerWindow().FullScreen = true; 38 | 39 | ctrl.getFrame().LayoutManager.hideElement("private:resource/menubar/menubar"); 40 | 41 | // Turn off sidebar: 42 | dispatch('.uno:Sidebar'); 43 | 44 | for (const id of 'Bold Italic Underline'.split(' ')) { 45 | const urlObj = transformUrl('.uno:' + id); 46 | const listener = zetajs.unoObject([css.frame.XStatusListener], { 47 | disposing: function(source) {}, 48 | statusChanged: function(state) { 49 | state = zetajs.fromAny(state.State); 50 | // Behave like desktop UI if a non uniformly formatted area is selected. 51 | if (typeof state !== 'boolean') state = false; // like desktop UI 52 | zetajs.mainPort.postMessage({cmd: 'setFormat', id, state}); 53 | } 54 | }); 55 | queryDispatch(urlObj).addStatusListener(listener, urlObj); 56 | } 57 | 58 | zetajs.mainPort.onmessage = function (e) { 59 | switch (e.data.cmd) { 60 | case 'toggleFormatting': 61 | dispatch('.uno:' + e.data.id); 62 | break; 63 | default: 64 | throw Error('Unknown message command: ' + e.data.cmd); 65 | } 66 | } 67 | zetajs.mainPort.postMessage({cmd: 'ui_ready'}); 68 | } 69 | 70 | function transformUrl(unoUrl) { 71 | const ioparam = {val: new css.util.URL({Complete: unoUrl})}; 72 | css.util.URLTransformer.create(context).parseStrict(ioparam); 73 | return ioparam.val; 74 | } 75 | 76 | function queryDispatch(urlObj) { 77 | return ctrl.queryDispatch(urlObj, '_self', 0); 78 | } 79 | 80 | function dispatch(unoUrl) { 81 | const urlObj = transformUrl(unoUrl); 82 | queryDispatch(urlObj).dispatch(urlObj, []); 83 | } 84 | 85 | Module.zetajs.then(function(pZetajs) { 86 | // initializing zetajs environment: 87 | zetajs = pZetajs; 88 | css = zetajs.uno.com.sun.star; 89 | demo(); // launching demo 90 | }); 91 | 92 | /* vim:set shiftwidth=2 softtabstop=2 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ 93 | -------------------------------------------------------------------------------- /examples/ping-monitor/office_thread.js: -------------------------------------------------------------------------------- 1 | /* -*- Mode: JS; tab-width: 2; indent-tabs-mode: nil; js-indent-level: 2; fill-column: 100 -*- */ 2 | // SPDX-License-Identifier: MIT 3 | 4 | // Debugging note: 5 | // Switch the web worker in the browsers debug tab to debug this code. 6 | // It's the "em-pthread" web worker with the most memory usage, where "zetajs" is defined. 7 | 8 | // JS mode: module 9 | import { ZetaHelperThread } from './assets/vendor/zetajs/zetaHelper.js'; 10 | 11 | 12 | // global variables - zetajs environment: 13 | const zHT = new ZetaHelperThread(); 14 | const zetajs = zHT.zetajs; 15 | 16 | // = global variables (some are global for easier debugging) = 17 | // common variables: 18 | let xModel, ctrl; 19 | // example specific: 20 | let activeSheet, cellRange, dataAry, oldUrl; 21 | 22 | // Export variables for debugging. Available for debugging via: 23 | // globalThis.zetajsStore.threadJsContext 24 | export { zHT, xModel, ctrl, activeSheet, cellRange, dataAry, oldUrl }; 25 | 26 | const max_values = 20; // setting, adjust as needed 27 | 28 | 29 | function demo() { 30 | zHT.configDisableToolbars(['Calc']); 31 | xModel = zHT.desktop.loadComponentFromURL('file:///tmp/ping_monitor.ods', '_default', 0, []); 32 | ctrl = xModel.getCurrentController(); 33 | 34 | // Turn off UI elements: 35 | zHT.dispatch(ctrl, 'Sidebar'); 36 | zHT.dispatch(ctrl, 'InputLineVisible'); // FormulaBar at the top 37 | zHT.dispatch(ctrl, 'ViewRowColumnHeaders'); 38 | const frame = ctrl.getFrame(); 39 | frame.LayoutManager.hideElement("private:resource/statusbar/statusbar"); 40 | frame.LayoutManager.hideElement("private:resource/menubar/menubar"); 41 | ctrl.setPropertyValue('SheetTabs', false); 42 | ctrl.setPropertyValue('HasHorizontalScrollBar', false); 43 | ctrl.setPropertyValue('HasVerticalScrollBar', false); 44 | ctrl.getFrame().getContainerWindow().FullScreen = true; 45 | 46 | activeSheet = ctrl.getActiveSheet(); 47 | cellRange = activeSheet.getCellRangeByPosition(0, 1, 0, max_values+1); 48 | dataAry = cellRange.getDataArray(); // 2 dimensional array 49 | zHT.thrPort.onmessage = (e) => { 50 | switch (e.data.cmd) { 51 | case 'ping_result': 52 | const newUrl = e.data.url; 53 | if (newUrl == oldUrl) { 54 | moveRows(dataAry); 55 | } else { 56 | clearRows(dataAry); 57 | } 58 | oldUrl = newUrl; 59 | setCell(dataAry[max_values-1], e.data.ping_value); 60 | cellRange.setDataArray(dataAry); 61 | break; 62 | default: 63 | throw Error('Unknown message command: ' + e.data.cmd); 64 | } 65 | } 66 | zHT.thrPort.postMessage({cmd: 'ui_ready'}); 67 | } 68 | 69 | 70 | function moveRows(ary) { 71 | for (let i = 0; i < max_values-1; i++) { 72 | const writeCell = ary[i]; 73 | const readCell = ary[i+1]; 74 | const ping_value = readCell[0]; 75 | setCell(writeCell, ping_value); 76 | } 77 | } 78 | 79 | function clearRows(ary) { 80 | for (let i = 0; i < max_values-1; i++) { 81 | setCell(ary[i], ''); 82 | } 83 | } 84 | 85 | function setCell(cell, value) { 86 | let num = value; // keep original value 87 | if (typeof(value) === 'number' || !isNaN(num=parseFloat(value))) { 88 | cell[0] = num; 89 | } else { 90 | cell[0] = value.toString(); 91 | } 92 | } 93 | 94 | demo(); // launching demo 95 | 96 | /* vim:set shiftwidth=2 softtabstop=2 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ 97 | -------------------------------------------------------------------------------- /examples/convertpdf/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 13 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /examples/vuejs3-ping-tool/src/App.vue: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 26 | 27 | 70 | 71 | 72 | 94 | -------------------------------------------------------------------------------- /examples/simple-examples/rainbow_writer.js: -------------------------------------------------------------------------------- 1 | /* -*- Mode: JS; tab-width: 2; indent-tabs-mode: nil; js-indent-level: 2; fill-column: 100 -*- */ 2 | // SPDX-License-Identifier: MIT 3 | 4 | // Debugging note: 5 | // Switch the web worker in the browsers debug tab to debug this code. 6 | // It's the "em-pthread" web worker with the most memory usage, where "zetajs" is defined. 7 | 8 | "use strict"; 9 | 10 | 11 | // global variables - zetajs environment: 12 | let zetajs, css; 13 | 14 | // = global variables (some are global for easier debugging) = 15 | // common variables: 16 | let context, desktop, xModel, toolkit; 17 | // example specific: 18 | let uno_bold, uno_font_monospace; 19 | 20 | // [ red, orange, yellow, green, blue, violet] 21 | const rainbow = [0xE50000, 0xF08500, 0xFFEE00, 0x008121, 0x004CFF, 0x760188]; 22 | 23 | 24 | function demo() { 25 | console.log('PLUS: execute example code'); 26 | 27 | uno_bold = css.awt.FontWeight.BOLD; 28 | uno_font_monospace = "Monospace"; 29 | 30 | context = zetajs.getUnoComponentContext(); 31 | desktop = css.frame.Desktop.create(context); 32 | //// Open a new writer document. 33 | xModel = desktop.loadComponentFromURL('private:factory/swriter', '_default', 0, []); 34 | 35 | toolkit = css.awt.Toolkit.create(context); 36 | setInterval(function() {try {toolkit.getActiveTopWindow().FullScreen = true} catch {}}, 1000); 37 | 38 | const xController = xModel.getCurrentController(); 39 | const xKeyHandler = zetajs.unoObject([css.awt.XKeyHandler], new ColorXKeyHandler(xModel)); 40 | xController.addKeyHandler(xKeyHandler); // XUserInputInterception.addKeyHandler() 41 | 42 | const xTextCursor = xModel.getText().createTextCursor(); // XTextDocument.getText() 43 | xTextCursor.setPropertyValue("CharWeight", uno_bold); // XPropertySet.setPropertyValue() 44 | xTextCursor.setString("Please type something!\n\n"); 45 | } 46 | 47 | 48 | function ColorXKeyHandler(xModel) { 49 | this.rainbow_i = 0; 50 | this.xModel = null; 51 | this.keyPressed = function(e) { return false; }; 52 | this.keyReleased = function(e) { 53 | if (e.KeyChar === "\x00") { return false; } // non symbol keys (e.g. arrow keys) 54 | 55 | const xController = this.xModel.getCurrentController(); 56 | const xTextViewCursor = xController.getViewCursor(); // XTextViewCursorSupplier.getViewCursor() 57 | const xText = this.xModel.getText(); // XTextDocument.getText() 58 | const xTextCursor = xText.createTextCursorByRange(xTextViewCursor.getStart()); 59 | xTextCursor.goLeft(1, true); 60 | 61 | // Walk the rainbow ;-) 62 | const color = rainbow[this.rainbow_i]; 63 | this.rainbow_i++; 64 | if (this.rainbow_i >= rainbow.length) { this.rainbow_i = 0; } 65 | 66 | xTextCursor.setPropertyValue("CharBackColor", color); // xPropertySet.setPropertyValue 67 | xTextCursor.setPropertyValue("CharWeight", uno_bold); 68 | xTextCursor.setPropertyValue("CharFontName", uno_font_monospace); 69 | // More properties: 70 | // https://api.libreoffice.org/docs/idl/ref/servicecom_1_1sun_1_1star_1_1style_1_1CharacterProperties.html 71 | 72 | return false; 73 | }; 74 | 75 | const xModel_types = xModel.getTypes(); // XTypeProvider.getTypes() 76 | for (let i=0; i'", quoted_url) ) 49 | .pipe(gulp.dest(distDir)) 50 | .pipe(browserSync.stream()); 51 | } 52 | 53 | 54 | // Task: Compile JS 55 | function compileJS() { 56 | return gulp.src( './office_thread.js') // 57 | .pipe(beautify.js({ indent_size: 2, max_preserve_newlines: 2})) 58 | .pipe(gulp.dest(distDir)) 59 | .pipe(browserSync.stream()); 60 | } 61 | 62 | 63 | // Task: Copy Vendors 64 | function copyVendors() { 65 | let stream = mergeStream(); 66 | 67 | stream.add( gulp.src( 'node_modules/zetajs/source/zeta.js' ).pipe( gulp.dest( distDir + 'assets/vendor/zetajs/' ) ) ); 68 | stream.add( gulp.src( 'node_modules/zetajs/source/zetaHelper.js' ).pipe( gulp.dest( distDir + 'assets/vendor/zetajs/' ) ) ); 69 | 70 | return stream; 71 | } 72 | 73 | 74 | // Init live server browser sync 75 | function initBrowserSync(done) { 76 | browserSync.init({ 77 | server: { 78 | baseDir: distDir, 79 | middleware: function (req, res, next) { 80 | // required for WASM (SharedArray support) 81 | res.setHeader('Cross-Origin-Opener-Policy', 'same-origin'); 82 | res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp'); 83 | next(); 84 | } 85 | }, 86 | listen: custom_listen, // host ip 87 | port: custom_port, 88 | notify: false, 89 | browser: custom_browser, 90 | open: !no_browser 91 | }); 92 | done(); 93 | } 94 | 95 | 96 | // Watch files 97 | function watchFiles() { 98 | gulp.watch('*.html', compileHTML); 99 | gulp.watch('*.js', compileJS); 100 | } 101 | 102 | 103 | function setDebug(done) { 104 | soffice_base_url = ''; 105 | done(); 106 | } 107 | 108 | 109 | // Export tasks 110 | const dist = gulp.series(clean, gulp.parallel(compileHTML, compileJS, copyVendors) ); 111 | 112 | exports.watch = gulp.series(dist, watchFiles); 113 | exports.start = gulp.series(dist, gulp.parallel(watchFiles, initBrowserSync) ); 114 | exports.debug = gulp.series(setDebug, gulp.parallel(compileHTML, compileJS, copyVendors) ); 115 | exports.default = dist; 116 | 117 | const fs = require('fs'); 118 | const gulpfile_optional = 'config.js'; 119 | if (fs.existsSync(gulpfile_optional)) { 120 | eval(fs.readFileSync(gulpfile_optional)+''); 121 | } 122 | -------------------------------------------------------------------------------- /examples/web-office/gulpfile.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // Load Plugins 4 | const browserSync = require("browser-sync").create(); 5 | const del = require('del'); 6 | const gulp = require('gulp'); 7 | const argv = require('yargs').argv; 8 | const beautify = require('gulp-beautify'); 9 | const inject = require('gulp-inject-string'); 10 | const mergeStream = require('merge-stream'); 11 | 12 | /** 13 | * Set the destination/production directory 14 | * This is where the project is compiled and exported for production. 15 | * This folder is auto created and managed by gulp. 16 | * Do not add/edit/save any files or folders iside this folder. They will be deleted by the gulp tasks. 17 | */ 18 | const distDir = './public/'; 19 | 20 | // Variables can be adjusted via command line arguments. Example: 21 | // npm run start -- --clean_disabled --port 8080 --browser chromium 22 | // Overwrites in config.js have priority over command line arguments. 23 | 24 | var soffice_base_url = argv.soffice_base_url; 25 | // Set "" for same server. But empty strings are falsy, so check "undefined". 26 | if (typeof soffice_base_url === "undefined") soffice_base_url = 'https://cdn.zetaoffice.net/zetaoffice_latest/'; 27 | 28 | var custom_browser = argv.browser; // else use default system browser 29 | var custom_listen = argv.listen || '127.0.0.1'; 30 | var custom_port = argv.port || 3000; 31 | var clean_disabled = argv.clean_disabled; 32 | var no_browser = argv.nobrowser; 33 | 34 | 35 | // Clean up the dist folder before running any task 36 | function clean() { 37 | if (clean_disabled) return Promise.resolve(); 38 | return del(distDir + '**/*'); 39 | } 40 | 41 | 42 | // Task: Compile HTML 43 | function compileHTML() { 44 | let css_links =''; 45 | 46 | return gulp.src(['index.html']) 47 | .pipe( inject.replace('', css_links) ) 48 | .pipe( inject.replace('', soffice_base_url) ) 49 | .pipe(gulp.dest(distDir)) 50 | .pipe(browserSync.stream()); 51 | } 52 | 53 | 54 | // Task: Compile JS 55 | function compileJS() { 56 | return gulp.src( './office_thread.js') // 57 | .pipe(beautify.js({ indent_size: 2, max_preserve_newlines: 2})) 58 | .pipe(gulp.dest(distDir)) 59 | .pipe(browserSync.stream()); 60 | } 61 | 62 | 63 | // Task: Copy Vendors 64 | function copyVendors() { 65 | let stream = mergeStream(); 66 | 67 | stream.add( gulp.src( 'node_modules/zetajs/source/zeta.js' ).pipe( gulp.dest( distDir + 'assets/vendor/zetajs/' ) ) ); 68 | stream.add( gulp.src( 'node_modules/bootstrap/dist/css/bootstrap.min.css' ).pipe( gulp.dest( distDir + 'assets/vendor/bootstrap/' ) ) ); 69 | 70 | return stream; 71 | } 72 | 73 | 74 | // Init live server browser sync 75 | function initBrowserSync(done) { 76 | browserSync.init({ 77 | server: { 78 | baseDir: distDir, 79 | middleware: function (req, res, next) { 80 | // required for WASM (SharedArray support) 81 | res.setHeader('Cross-Origin-Opener-Policy', 'same-origin'); 82 | res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp'); 83 | next(); 84 | } 85 | }, 86 | listen: custom_listen, // host ip 87 | port: custom_port, 88 | notify: false, 89 | browser: custom_browser, 90 | open: !no_browser 91 | }); 92 | done(); 93 | } 94 | 95 | 96 | // Watch files 97 | function watchFiles() { 98 | gulp.watch('*.html', compileHTML); 99 | gulp.watch('*.js', compileJS); 100 | } 101 | 102 | 103 | function setDebug(done) { 104 | soffice_base_url = ''; 105 | done(); 106 | } 107 | 108 | 109 | // Export tasks 110 | const dist = gulp.series(clean, gulp.parallel(compileHTML, compileJS, copyVendors) ); 111 | 112 | exports.watch = gulp.series(dist, watchFiles); 113 | exports.start = gulp.series(dist, gulp.parallel(watchFiles, initBrowserSync) ); 114 | exports.debug = gulp.series(setDebug, gulp.parallel(compileHTML, compileJS, copyVendors) ); 115 | exports.default = dist; 116 | 117 | const fs = require('fs'); 118 | const gulpfile_optional = 'config.js'; 119 | if (fs.existsSync(gulpfile_optional)) { 120 | eval(fs.readFileSync(gulpfile_optional)+''); 121 | } 122 | -------------------------------------------------------------------------------- /examples/standalone/gulpfile.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // Load Plugins 4 | const browserSync = require("browser-sync").create(); 5 | const del = require('del'); 6 | const gulp = require('gulp'); 7 | const argv = require('yargs').argv; 8 | const beautify = require('gulp-beautify'); 9 | const inject = require('gulp-inject-string'); 10 | const mergeStream = require('merge-stream'); 11 | 12 | /** 13 | * Set the destination/production directory 14 | * This is where the project is compiled and exported for production. 15 | * This folder is auto created and managed by gulp. 16 | * Do not add/edit/save any files or folders iside this folder. They will be deleted by the gulp tasks. 17 | */ 18 | const distDir = './public/'; 19 | 20 | // Variables can be adjusted via command line arguments. Example: 21 | // npm run start -- --clean_disabled --port 8080 --browser chromium 22 | // Overwrites in config.js have priority over command line arguments. 23 | 24 | var soffice_base_url = argv.soffice_base_url; 25 | // Set "" for same server. But empty strings are falsy, so check "undefined". 26 | if (typeof soffice_base_url === "undefined") soffice_base_url = 'https://cdn.zetaoffice.net/zetaoffice_latest/'; 27 | 28 | var custom_browser = argv.browser; // else use default system browser 29 | var custom_listen = argv.listen || '127.0.0.1'; 30 | var custom_port = argv.port || 3000; 31 | var clean_disabled = argv.clean_disabled; 32 | var no_browser = argv.nobrowser; 33 | 34 | 35 | // Clean up the dist folder before running any task 36 | function clean() { 37 | if (clean_disabled) return Promise.resolve(); 38 | return del(distDir + '**/*'); 39 | } 40 | 41 | // Task: Compile HTML 42 | function compileHTML() { 43 | let css_links =''; 44 | let js_links = ''; 45 | 46 | return gulp.src(['index.html']) 47 | .pipe( inject.replace('', css_links) ) 48 | .pipe( inject.replace('', js_links) ) 49 | .pipe( inject.replace('', soffice_base_url) ) 50 | .pipe(gulp.dest(distDir)) 51 | .pipe(browserSync.stream()); 52 | } 53 | 54 | // Task: Compile JS 55 | function compileJS() { 56 | return gulp.src( './office_thread.js') // 57 | .pipe(beautify.js({ indent_size: 2, max_preserve_newlines: 2})) 58 | .pipe(gulp.dest(distDir)) 59 | .pipe(browserSync.stream()); 60 | } 61 | 62 | // Task: Copy Vendors 63 | function copyVendors() { 64 | let stream = mergeStream(); 65 | 66 | stream.add( gulp.src( 'node_modules/zetajs/source/zeta.js' ).pipe( gulp.dest( distDir + 'assets/vendor/zetajs/' ) ) ); 67 | stream.add( gulp.src( 'node_modules/bootstrap/dist/css/bootstrap.min.css' ).pipe( gulp.dest( distDir + 'assets/vendor/bootstrap/' ) ) ); 68 | 69 | return stream; 70 | } 71 | 72 | // Init live server browser sync 73 | function initBrowserSync(done) { 74 | browserSync.init({ 75 | server: { 76 | baseDir: distDir, 77 | middleware: function (req, res, next) { 78 | // required for WASM (SharedArray support) 79 | res.setHeader('Cross-Origin-Opener-Policy', 'same-origin'); 80 | res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp'); 81 | next(); 82 | } 83 | }, 84 | listen: custom_listen, // host ip 85 | port: custom_port, 86 | notify: false, 87 | browser: custom_browser, 88 | open: !no_browser 89 | }); 90 | done(); 91 | } 92 | 93 | // Watch files 94 | function watchFiles() { 95 | gulp.watch('*.html', compileHTML); 96 | gulp.watch('*.js', compileJS); 97 | } 98 | 99 | function setDebug(done) { 100 | soffice_base_url = ''; 101 | done(); 102 | } 103 | 104 | // Export tasks 105 | const dist = gulp.series(clean, gulp.parallel(compileHTML, compileJS, copyVendors) ); 106 | 107 | exports.watch = gulp.series(dist, watchFiles); 108 | exports.start = gulp.series(dist, gulp.parallel(watchFiles, initBrowserSync) ); 109 | exports.debug = gulp.series(setDebug, gulp.parallel(compileHTML, compileJS, copyVendors) ); 110 | exports.default = dist; 111 | 112 | const fs = require('fs'); 113 | const gulpfile_optional = 'config.js'; 114 | if (fs.existsSync(gulpfile_optional)) { 115 | eval(fs.readFileSync(gulpfile_optional)+''); 116 | } 117 | -------------------------------------------------------------------------------- /examples/vuejs3-ping-tool/public/office_thread.js: -------------------------------------------------------------------------------- 1 | /* -*- Mode: JS; tab-width: 2; indent-tabs-mode: nil; js-indent-level: 2; fill-column: 100 -*- */ 2 | // SPDX-License-Identifier: MIT 3 | 4 | // Debugging note: 5 | // Switch the web worker in the browsers debug tab to debug this code. 6 | // It's the "em-pthread" web worker with the most memory usage, where "zetajs" is defined. 7 | 8 | // JS mode: module 9 | import { ZetaHelperThread } from './assets/vendor/zetajs/zetaHelper.js'; 10 | 11 | 12 | // global variables - zetajs environment: 13 | const zHT = new ZetaHelperThread(); 14 | const zetajs = zHT.zetajs; 15 | const css = zHT.css; 16 | const desktop = zHT.desktop; 17 | 18 | // = global variables = 19 | // common variables: 20 | let xModel, ctrl; 21 | // example specific: 22 | let ping_line, xComponent, charLocale, formatNumber, formatText, activeSheet, cell; 23 | 24 | // Export variables for debugging. Available for debugging via: 25 | // globalThis.zetajsStore.threadJsContext 26 | export { zHT, xModel, ctrl, ping_line, xComponent, charLocale, formatNumber, formatText, activeSheet, cell }; 27 | 28 | 29 | function demo() { 30 | zHT.configDisableToolbars(["Calc"]); 31 | 32 | xModel = desktop.loadComponentFromURL('file:///tmp/calc_ping_example.ods', '_default', 0, []); 33 | ctrl = xModel.getCurrentController(); 34 | ctrl.getFrame().getContainerWindow().FullScreen = true; 35 | 36 | xComponent = ctrl.getModel(); 37 | charLocale = xComponent.getPropertyValue('CharLocale'); 38 | formatNumber = xComponent.getNumberFormats(). 39 | queryKey('0', charLocale, false); 40 | formatText = xComponent.getNumberFormats(). 41 | queryKey('@', charLocale, false); 42 | 43 | // Turn off UI elements: 44 | zHT.dispatch(ctrl, 'Sidebar'); 45 | zHT.dispatch(ctrl, 'InputLineVisible'); // FormulaBar at the top 46 | ctrl.getFrame().LayoutManager.hideElement("private:resource/statusbar/statusbar"); 47 | ctrl.getFrame().LayoutManager.hideElement("private:resource/menubar/menubar"); 48 | 49 | for (const id of 'Bold Italic Underline'.split(' ')) { 50 | const urlObj = zHT.transformUrl(id); 51 | const listener = zetajs.unoObject([css.frame.XStatusListener], { 52 | disposing: function(source) {}, 53 | statusChanged: function(state) { 54 | state = zetajs.fromAny(state.State); 55 | // Behave like desktop UI if a non uniformly formatted area is selected. 56 | if (typeof state !== 'boolean') state = false; // like desktop UI 57 | zetajs.mainPort.postMessage({cmd: 'setFormat', id, state: state}); 58 | } 59 | }); 60 | zHT.queryDispatch(ctrl, urlObj).addStatusListener(listener, urlObj); 61 | } 62 | 63 | activeSheet = ctrl.getActiveSheet(); 64 | zHT.thrPort.onmessage = function (e) { 65 | switch (e.data.cmd) { 66 | case 'toggleFormatting': 67 | zHT.dispatch(ctrl, e.data.id); 68 | break; 69 | case 'ping_result': 70 | if (ping_line === undefined) { 71 | ping_line = 1; // start at line 1 (line 0 is the header) 72 | } else { 73 | ping_line = findEmptyRowInCol1(activeSheet); 74 | } 75 | 76 | const url = e.data.id['url']; 77 | cell = activeSheet.getCellByPosition(0, ping_line); 78 | cell.setPropertyValue('NumberFormat', formatText); // optional 79 | cell.setString((new URL(url)).hostname); 80 | 81 | cell = activeSheet.getCellByPosition(1, ping_line); 82 | let ping_value = String(e.data.id['data']); 83 | if (!isNaN(ping_value)) { 84 | cell.setPropertyValue('NumberFormat', formatNumber); // optional 85 | cell.setValue(parseFloat(ping_value)); 86 | } else { 87 | // in case e.data.id['data'] contains an error message 88 | cell.setPropertyValue('NumberFormat', formatText); // optional 89 | cell.setString(ping_value); 90 | } 91 | break; 92 | default: 93 | throw Error('Unknown message command: ' + e.data.cmd); 94 | } 95 | } 96 | zHT.thrPort.postMessage({cmd: 'ui_ready'}); 97 | } 98 | 99 | function findEmptyRowInCol1(activeSheet) { 100 | let str; 101 | let line = 0; 102 | while (str != "") { 103 | line++; 104 | str = activeSheet.getCellByPosition(0, line).getString(); 105 | } 106 | return line; 107 | } 108 | 109 | demo(); // launching demo 110 | 111 | /* vim:set shiftwidth=2 softtabstop=2 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ 112 | -------------------------------------------------------------------------------- /examples/letter-address-tool/gulpfile.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // Load Plugins 4 | const browserSync = require("browser-sync").create(); 5 | const del = require('del'); 6 | const gulp = require('gulp'); 7 | const argv = require('yargs').argv; 8 | const beautify = require('gulp-beautify'); 9 | const inject = require('gulp-inject-string'); 10 | const mergeStream = require('merge-stream'); 11 | 12 | /** 13 | * Set the destination/production directory 14 | * This is where the project is compiled and exported for production. 15 | * This folder is auto created and managed by gulp. 16 | * Do not add/edit/save any files or folders iside this folder. They will be deleted by the gulp tasks. 17 | */ 18 | const distDir = './public/'; 19 | 20 | // Variables can be adjusted via command line arguments. Example: 21 | // npm run start -- --clean_disabled --port 8080 --browser chromium 22 | // Overwrites in config.js have priority over command line arguments. 23 | 24 | var soffice_base_url = argv.soffice_base_url; 25 | 26 | var custom_browser = argv.browser; // else use default system browser 27 | var custom_listen = argv.listen || '127.0.0.1'; 28 | var custom_port = argv.port || 3000; 29 | var clean_disabled = argv.clean_disabled; 30 | var no_browser = argv.nobrowser; 31 | 32 | 33 | // Clean up the dist folder before running any task 34 | function clean() { 35 | if (clean_disabled) return Promise.resolve(); 36 | return del(distDir + '**/*'); 37 | } 38 | 39 | 40 | // Task: Compile HTML 41 | function compileHTML() { 42 | let css_links =''; 43 | 44 | // Set "" for same server. But empty strings are falsy, so check "undefined". 45 | // If undefined, it's set to null without quotes to let zetaHelper set the URL. 46 | let quoted_url = null; // null, prevents additional quoting on "npm run start" reloads 47 | if (typeof soffice_base_url !== "undefined") quoted_url = "'" + soffice_base_url + "'"; 48 | 49 | return gulp.src(['index.html']) 50 | .pipe( inject.replace('', css_links) ) 51 | .pipe( inject.replace("''", quoted_url) ) 52 | .pipe(gulp.dest(distDir)) 53 | .pipe(browserSync.stream()); 54 | } 55 | 56 | 57 | // Task: Compile JS 58 | function compileJS() { 59 | return gulp.src( './office_thread.js') // 60 | .pipe(beautify.js({ indent_size: 2, max_preserve_newlines: 2})) 61 | .pipe(gulp.dest(distDir)) 62 | .pipe(browserSync.stream()); 63 | } 64 | 65 | 66 | // Task: Copy Vendors 67 | function copyVendors() { 68 | let stream = mergeStream(); 69 | 70 | stream.add( gulp.src( 'node_modules/zetajs/source/zeta.js' ).pipe( gulp.dest( distDir + 'assets/vendor/zetajs/' ) ) ); 71 | stream.add( gulp.src( 'node_modules/zetajs/source/zetaHelper.js' ).pipe( gulp.dest( distDir + 'assets/vendor/zetajs/' ) ) ); 72 | stream.add( gulp.src( 'node_modules/w3-css/3/w3.css' ).pipe( gulp.dest( distDir + 'assets/vendor/w3/' ) ) ); 73 | 74 | stream.add( gulp.src( 'Modern_business_letter_sans_serif.ott' ).pipe( gulp.dest( distDir + 'assets/' ) ) ); 75 | 76 | return stream; 77 | } 78 | 79 | 80 | // Init live server browser sync 81 | function initBrowserSync(done) { 82 | browserSync.init({ 83 | server: { 84 | baseDir: distDir, 85 | middleware: function (req, res, next) { 86 | // required for WASM (SharedArray support) 87 | res.setHeader('Cross-Origin-Opener-Policy', 'same-origin'); 88 | res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp'); 89 | next(); 90 | } 91 | }, 92 | listen: custom_listen, // host ip 93 | port: custom_port, 94 | notify: false, 95 | browser: custom_browser, 96 | open: !no_browser 97 | }); 98 | done(); 99 | } 100 | 101 | 102 | // Watch files 103 | function watchFiles() { 104 | gulp.watch('*.html', compileHTML); 105 | gulp.watch('*.js', compileJS); 106 | } 107 | 108 | 109 | function setDebug(done) { 110 | soffice_base_url = ''; 111 | done(); 112 | } 113 | 114 | 115 | // Export tasks 116 | const dist = gulp.series(clean, gulp.parallel(compileHTML, compileJS, copyVendors) ); 117 | 118 | exports.watch = gulp.series(dist, watchFiles); 119 | exports.start = gulp.series(dist, gulp.parallel(watchFiles, initBrowserSync) ); 120 | exports.debug = gulp.series(setDebug, gulp.parallel(compileHTML, compileJS, copyVendors) ); 121 | exports.default = dist; 122 | 123 | const fs = require('fs'); 124 | const gulpfile_optional = 'config.js'; 125 | if (fs.existsSync(gulpfile_optional)) { 126 | eval(fs.readFileSync(gulpfile_optional)+''); 127 | } 128 | -------------------------------------------------------------------------------- /examples/ping-monitor/gulpfile.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // Load Plugins 4 | const browserSync = require("browser-sync").create(); 5 | const del = require('del'); 6 | const gulp = require('gulp'); 7 | const argv = require('yargs').argv; 8 | const beautify = require('gulp-beautify'); 9 | const inject = require('gulp-inject-string'); 10 | const mergeStream = require('merge-stream'); 11 | 12 | /** 13 | * Set the destination/production directory 14 | * This is where the project is compiled and exported for production. 15 | * This folder is auto created and managed by gulp. 16 | * Do not add/edit/save any files or folders iside this folder. They will be deleted by the gulp tasks. 17 | */ 18 | const distDir = './public/'; 19 | 20 | // Variables can be adjusted via command line arguments. Example: 21 | // npm run start -- --clean_disabled --port 8080 --browser chromium 22 | // Overwrites in config.js have priority over command line arguments. 23 | 24 | var soffice_base_url = argv.soffice_base_url; 25 | 26 | var custom_browser = argv.browser; // else use default system browser 27 | var custom_listen = argv.listen || '127.0.0.1'; 28 | var custom_port = argv.port || 3000; 29 | var clean_disabled = argv.clean_disabled; 30 | var no_browser = argv.nobrowser; 31 | 32 | 33 | // Clean up the dist folder before running any task 34 | function clean() { 35 | if (clean_disabled) return Promise.resolve(); 36 | return del(distDir + '**/*'); 37 | } 38 | 39 | 40 | // Task: Compile HTML 41 | function compileHTML() { 42 | let css_links =''; 43 | let js_links =''; 44 | 45 | // Set "" for same server. But empty strings are falsy, so check "undefined". 46 | // If undefined, it's set to null without quotes to let zetaHelper set the URL. 47 | let quoted_url = null; // null, prevents additional quoting on "npm run start" reloads 48 | if (typeof soffice_base_url !== "undefined") quoted_url = "'" + soffice_base_url + "'"; 49 | 50 | return gulp.src(['index.html']) 51 | .pipe( inject.replace('', css_links) ) 52 | .pipe( inject.replace('', js_links) ) 53 | .pipe( inject.replace("''", quoted_url) ) 54 | .pipe(gulp.dest(distDir)) 55 | .pipe(browserSync.stream()); 56 | } 57 | 58 | 59 | // Task: Compile JS 60 | function compileJS() { 61 | return gulp.src( './office_thread.js') // 62 | .pipe(beautify.js({ indent_size: 2, max_preserve_newlines: 2})) 63 | .pipe(gulp.dest(distDir)) 64 | .pipe(browserSync.stream()); 65 | } 66 | 67 | 68 | // Task: Copy Vendors 69 | function copyVendors() { 70 | let stream = mergeStream(); 71 | 72 | stream.add( gulp.src( 'node_modules/zetajs/source/zeta.js' ).pipe( gulp.dest( distDir + 'assets/vendor/zetajs/' ) ) ); 73 | stream.add( gulp.src( 'node_modules/zetajs/source/zetaHelper.js' ).pipe( gulp.dest( distDir + 'assets/vendor/zetajs/' ) ) ); 74 | stream.add( gulp.src( 'node_modules/ping.js/dist/ping.js' ).pipe( gulp.dest( distDir + 'assets/vendor/ping/' ) ) ); 75 | stream.add( gulp.src( 'node_modules/bootstrap/dist/css/bootstrap.min.css' ).pipe( gulp.dest( distDir + 'assets/vendor/bootstrap/' ) ) ); 76 | 77 | stream.add( gulp.src( 'pwa-icon-192x192.png' ).pipe( gulp.dest( distDir + 'assets/' ) ) ); 78 | stream.add( gulp.src( 'pwa-manifest.json' ).pipe( gulp.dest( distDir + 'assets/' ) ) ); 79 | stream.add( gulp.src( 'ping_monitor.ods' ).pipe( gulp.dest( distDir + 'assets/' ) ) ); 80 | 81 | return stream; 82 | } 83 | 84 | 85 | // Init live server browser sync 86 | function initBrowserSync(done) { 87 | browserSync.init({ 88 | server: { 89 | baseDir: distDir, 90 | middleware: function (req, res, next) { 91 | // required for WASM (SharedArray support) 92 | res.setHeader('Cross-Origin-Opener-Policy', 'same-origin'); 93 | res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp'); 94 | next(); 95 | } 96 | }, 97 | listen: custom_listen, // host ip 98 | port: custom_port, 99 | notify: false, 100 | browser: custom_browser, 101 | open: !no_browser 102 | }); 103 | done(); 104 | } 105 | 106 | 107 | // Watch files 108 | function watchFiles() { 109 | gulp.watch('*.html', compileHTML); 110 | gulp.watch('*.js', compileJS); 111 | } 112 | 113 | 114 | function setDebug(done) { 115 | soffice_base_url = ''; 116 | done(); 117 | } 118 | 119 | 120 | // Export tasks 121 | const dist = gulp.series(clean, gulp.parallel(compileHTML, compileJS, copyVendors) ); 122 | 123 | exports.watch = gulp.series(dist, watchFiles); 124 | exports.start = gulp.series(dist, gulp.parallel(watchFiles, initBrowserSync) ); 125 | exports.debug = gulp.series(setDebug, gulp.parallel(compileHTML, compileJS, copyVendors) ); 126 | exports.default = dist; 127 | 128 | const fs = require('fs'); 129 | const gulpfile_optional = 'config.js'; 130 | if (fs.existsSync(gulpfile_optional)) { 131 | eval(fs.readFileSync(gulpfile_optional)+''); 132 | } 133 | -------------------------------------------------------------------------------- /docs/uno.md: -------------------------------------------------------------------------------- 1 | # The zetajs UNO Mapping 2 | 3 | The mapping between [UNO types](http://www.openoffice.org/udk/common/man/typesystem.html) and zetajs JavaScript types is as follows: 4 | 5 | - UNO `VOID`: As a UNO interface method return type it maps to JavaScript `void`. As a value type contained in a UNO `ANY` value it maps to the JavaScript Undefined type. 6 | 7 | - UNO `BOOLEAN` maps to the JavaScript Boolean type. 8 | 9 | - UNO `BYTE` maps to the JavaScript Number type's integer values in the interval from −27 (inclusive) to 27 (exclusive). 10 | 11 | - UNO `SHORT` maps to the JavaScript Number type's integer values in the interval from −215 (inclusive) to 215 (exclusive). 12 | 13 | - UNO `UNSIGNED SHORT` maps to the JavaScript Number type's integer values in the interval from 0 (inclusive) to 216 (exclusive). 14 | 15 | - UNO `LONG` maps to the JavaScript Number type's integer values in the interval from −231 (inclusive) to 231 (exclusive). 16 | 17 | - UNO `UNSIGNED LONG` maps to the JavaScript Number type's integer values in the interval from 0 (inclusive) to 232 (exclusive). 18 | 19 | - UNO `HYPER` maps to the JavaScript BigInt type's values in the interval from −263 (inclusive) to 263 (exclusive). 20 | 21 | - UNO `UNSIGNED HYPER` maps to the JavaScript BigInt type's values in the interval from 0 (inclusive) to 264 (exclusive). 22 | 23 | - UNO `FLOAT` maps to the JavaScript Number type`s values that correspond to values of the IEEE 754 binary32 interchange format. 24 | 25 | - UNO `DOUBLE` maps to the JavaScript Number type. 26 | 27 | - UNO `CHAR` maps to the JavaScript String type's one-element values. 28 | 29 | - UNO `STRING` maps to the JavaScript String type's values that correspond to valid UTF-16 strings, up to the JavaScript length limit of 253−1 UTF16 code units. 30 | 31 | - UNO `TYPE` maps to opaque JavaScript objects, see the documentation of `zetajs.type` at [Starting Points: Using zetajs](start.md#using-zetajs). 32 | 33 | - UNO `ANY` maps to the combined set of wrapped and unwrapped representations: 34 | 35 | - Any value of UNO type `ANY` can map to and from a wrapped representation, which is an opaque JavaScript object that has a `type` property (containing a zetajs representation of a value of UNO type `TYPE`) and a `val` property (containing a zetajs representation of a value of the given UNO type). 36 | 37 | - A value of UNO type `ANY` where the contained UNO value is of any of the UNO types `VOID`, `BOOLEAN`, `LONG`, `HYPER`, `STRING`, `TYPE`, a UNO enum type, a UNO struct type, or a UNO exception type, can also map to and from an unwrapped representation, which directly maps to the JavaScript representation of the contained UNO value. 38 | 39 | - A value of UNO type `ANY` where the contained UNO value is of any of the UNO types `BYTE`, `SHORT`, `UNSIGNED SHORT`, `UNSIGNED LONG`, `UNSIGNED HYPER`, `FLOAT`, `DOUBLE`, `CHAR`, a UNO sequence type, or a UNO interface type, can also map to an unwrapped representation, which directly maps to the JavaScript representation of the contained UNO value. In the opposite direction: 40 | 41 | - JavaScript Number values with integer values in the interval 263 (inclusive) to 264 (exclusive) map to UNO `ANY` values with contained values of UNO type `UNSIGNED LONG`. 42 | 43 | - JavaScript Number values not covered by the preceding cases for `LONG` and `UNSIGNED LONG` target types map to UNO `ANY` values with contained values of UNO type `DOUBLE`. 44 | 45 | - JavaScript BigInt values that are larger than 263 − 1 map to UNO `ANY` values with contained values of UNO type `UNSIGNED HYPER`. 46 | 47 | - JavaScript Array values map to UNO `ANY` values with contained values of UNO type “sequence of `ANY`”. 48 | 49 | - JavaScript Null values and JavaScript objects representing UNO objects map to UNO `ANY` values with contained values of UNO interface type `com.sun.star.uno.XInterface`. 50 | 51 | Also see the documentation of `zetajs.Any`, `zetajs.getAnyType`, and `zetajs.fromAny` at [Starting Points: Using zetajs](start.md#using-zetajs). 52 | 53 | - UNO sequence types map to JavaScript Arrays with corresponding element value constraints, up to the JavaScript length limit of 232−1 elements. 54 | 55 | - UNO struct and exception types map to opaque JavaScript objects, where each member with name N maps to a property with property name N and with corresponding value constraints. See the documentation of the `zetajs.uno` dictionary at [Starting Points: Using zetajs](start.md#using-zetajs) for corresponding constructor functions. 56 | 57 | - UNO interface types map to the JavaScript Null type for null references, and to opaque JavaScript objects for non-null references. (A zetajs representation referencing a given UNO object has direct access to all the UNO interface methods and attribute getters and setters implemented by that object. Thus, such zetajs representations are not tied to specific UNO interface types.) 58 | -------------------------------------------------------------------------------- /examples/letter-address-tool/office_thread.js: -------------------------------------------------------------------------------- 1 | /* -*- Mode: JS; tab-width: 2; indent-tabs-mode: nil; js-indent-level: 2; fill-column: 100 -*- */ 2 | // SPDX-License-Identifier: MIT 3 | 4 | // Debugging note: 5 | // Switch the web worker in the browsers debug tab to debug this code. 6 | // It's the "em-pthread" web worker with the most memory usage, where "zetajs" is defined. 7 | 8 | // JS mode: script 9 | 'use strict'; 10 | 11 | 12 | // global variables - zetajs environment: 13 | let zetajs, css; 14 | 15 | // = global variables (some are global for easier debugging) = 16 | // common variables: 17 | let zHT, desktop, xModel, ctrl; 18 | 19 | 20 | function demo() { 21 | const bean_overwrite = new css.beans.PropertyValue({Name: 'Overwrite', Value: true}); 22 | const bean_odt_export = new css.beans.PropertyValue({Name: 'FilterName', Value: 'writer8'}); 23 | const bean_pdf_export = new css.beans.PropertyValue({Name: 'FilterName', Value: 'writer_pdf_Export'}); 24 | zHT.configDisableToolbars(["Writer"]); 25 | 26 | loadFile(); 27 | // Turn off UI elements. 28 | // Permanant settings. Don't run again on a document reload. 29 | zHT.dispatch(ctrl, 'Sidebar'); 30 | zHT.dispatch(ctrl, 'Ruler'); 31 | 32 | zHT.thrPort.onmessage = function (e) { 33 | switch (e.data.cmd) { 34 | case 'download': 35 | const format = e.data.id === 'btnOdt' ? bean_odt_export : bean_pdf_export; 36 | xModel.storeToURL( 'file:///tmp/output', [bean_overwrite, format]); 37 | zetajs.mainPort.postMessage({cmd: 'download', id: e.data.id}); 38 | break; 39 | case 'reload': 40 | xModel.close(true) 41 | loadFile(); 42 | break; 43 | case 'toggleFormat': 44 | zHT.dispatch(ctrl, e.data.id); 45 | break; 46 | case 'insert_address': 47 | const recipient = e.data.recipient; 48 | const fieldsEnum = xModel.getTextFields().createEnumeration(); 49 | let state_count=0, city_count=0, postal_code_count=0, street_count=0; 50 | while (fieldsEnum.hasMoreElements()) { 51 | const field = fieldsEnum.nextElement().getAnchor(); 52 | switch (field.getString()) { 53 | case "": // additional space is needed 54 | field.setString(recipient.title === '' ? '' : recipient.title+' '); // recipient 55 | break; 56 | case "": 57 | field.setString(recipient.name); 58 | break; 59 | case "": 60 | field.setString(recipient.street); 61 | break; 62 | case "": // additional space is needed 63 | field.setString(recipient.postal_code+' '); 64 | break; 65 | case "": 66 | field.setString(recipient.city); 67 | break; 68 | case "": 69 | field.setString(recipient.state); 70 | break; 71 | case "": 72 | field.setString("Dent, Arthur Phillip"); 73 | break; 74 | case "": 75 | field.setString("Cottingshire Radio"); 76 | break; 77 | case "": 78 | field.setString("155 Country Lane"); 79 | break; 80 | case "": // additional space is needed 81 | field.setString("2A 2A2A"+' '); 82 | break; 83 | case "": 84 | field.setString("Cottington"); 85 | break; 86 | case "": 87 | field.setString("Cottingshire County"); 88 | break; 89 | } 90 | } 91 | break; 92 | default: 93 | throw Error('Unknown message command: ' + e.data.cmd); 94 | } 95 | } 96 | } 97 | 98 | function loadFile() { 99 | const in_path = 'file:///tmp/Modern_business_letter_sans_serif.ott' 100 | xModel = desktop.loadComponentFromURL(in_path, '_default', 0, []); 101 | ctrl = xModel.getCurrentController(); 102 | const frame = ctrl.getFrame(); 103 | 104 | // Turn off UI elements (idempotent operations): 105 | frame.LayoutManager.hideElement("private:resource/statusbar/statusbar"); 106 | frame.LayoutManager.hideElement("private:resource/menubar/menubar"); 107 | 108 | frame.getContainerWindow().FullScreen = true; 109 | 110 | for (const id of 'Bold Italic Underline'.split(' ')) { 111 | const urlObj = zHT.transformUrl(id); 112 | const listener = zetajs.unoObject([css.frame.XStatusListener], { 113 | disposing: function(source) {}, 114 | statusChanged: function(state) { 115 | state = zetajs.fromAny(state.State); 116 | // Behave like desktop UI if a non uniformly formatted area is selected. 117 | if (typeof state !== 'boolean') state = false; 118 | zetajs.mainPort.postMessage({cmd: 'setFormat', id, state}); 119 | } 120 | }); 121 | zHT.queryDispatch(ctrl, urlObj).addStatusListener(listener, urlObj); 122 | } 123 | zetajs.mainPort.postMessage({cmd: 'ui_ready'}); 124 | } 125 | 126 | 127 | import('./assets/vendor/zetajs/zetaHelper.js').then(zetaHelper => { 128 | zHT = new zetaHelper.ZetaHelperThread(); 129 | zetajs = zHT.zetajs; 130 | css = zHT.css; 131 | desktop = zHT.desktop; 132 | demo(); // launching demo 133 | }); 134 | 135 | /* vim:set shiftwidth=2 softtabstop=2 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ 136 | -------------------------------------------------------------------------------- /examples/vuejs3-ping-tool/public/pre_soffice.js: -------------------------------------------------------------------------------- 1 | /* -*- Mode: JS; tab-width: 2; indent-tabs-mode: nil; js-indent-level: 2; fill-column: 100 -*- */ 2 | // SPDX-License-Identifier: MIT 3 | 4 | import { ZetaHelperMain } from './assets/vendor/zetajs/zetaHelper.js'; 5 | 6 | 7 | let thrPort; // zetajs thread communication 8 | let tbDataJs; // toolbar dataset passed from vue.js for plain JS 9 | window.PingModule = null; // Ping module passed from vue.js for plain JS 10 | 11 | const loadingInfo = document.getElementById('loadingInfo'); 12 | const canvas = document.getElementById('qtcanvas'); 13 | const pingTarget = document.getElementById("ping_target"); 14 | const btnPing = document.getElementById("btnPing"); 15 | const disabledElementsAry = [btnPing]; 16 | 17 | // IMPORTANT: 18 | // Set base URL to the soffice.* files. 19 | // Use an empty string if those files are in the same directory. 20 | let wasmPkg; 21 | try { 22 | wasmPkg = 'url:' + config_soffice_base_url; // May fail. config.js is optional. 23 | } catch {} 24 | const zHM = new ZetaHelperMain('office_thread.js', {threadJsType: 'module', wasmPkg}); 25 | 26 | 27 | // Functions stored below window.* are usually accessed from vue.js. 28 | 29 | window.jsPassCtrlBar = function(pTbDataJs) { 30 | tbDataJs = pTbDataJs; 31 | disabledElementsAry.push(tbDataJs); 32 | } 33 | 34 | window.toggleFormatting = function(id) { 35 | setToolbarActive(id, !tbDataJs.active[id]); 36 | thrPort.postMessage({cmd: 'toggleFormatting', id}); 37 | // Give focus to the LO canvas to avoid issues with 38 | // "Setting Bold is 39 | // undone when clicking into non-empty document" when the user would need to click 40 | // into the canvas to give back focus to it: 41 | canvas.focus(); 42 | } 43 | 44 | function setToolbarActive(id, value) { 45 | tbDataJs.active[id] = value; 46 | // Need to set "active" on "tbDataJs" to trigger an UI update. 47 | tbDataJs.active = tbDataJs.active; 48 | } 49 | 50 | let dbgPingData; 51 | function pingResult(url, err, data) { 52 | dbgPingData = {data, err}; 53 | const hostname = (new URL(url)).hostname; 54 | let output = data; 55 | // If /favicon.ico can't be loaded the result still represents the response time. 56 | if (err) output = hostname + ": " + output + " " + err; 57 | thrPort.postMessage({cmd: 'ping_result', id:{url, data} }); 58 | } 59 | 60 | let pingInst; 61 | const urls_ary = ["https://documentfoundation.org/", "https://ip4.me/", "https://allotropia.de/"]; 62 | let urls_ary_i = 0; 63 | function pingExamples(err, data) { 64 | let url = urls_ary[urls_ary_i]; 65 | pingResult(url, err, data); 66 | url = urls_ary[++urls_ary_i]; 67 | if (typeof url !== 'undefined') { 68 | setTimeout(function() { // make the demo look more interesting ;-) 69 | pingInst.ping(url, function(err_rec, data_rec) { 70 | pingExamples(err_rec, data_rec); 71 | }); 72 | }, 1000); // milliseconds 73 | } 74 | } 75 | 76 | function btnPingFunc() { 77 | // Using Ping callback interface. 78 | let url = pingTarget.value; 79 | if (!url.startsWith('http')) { 80 | url = 'http://' + url; 81 | } 82 | pingInst.ping(url, function(err, data) { 83 | pingResult(url, err, data); 84 | }); 85 | } 86 | btnPing.onclick = btnPingFunc; 87 | 88 | 89 | async function get_calc_ping_example_ods() { 90 | const response = await fetch("./calc_ping_example.ods"); 91 | return response.arrayBuffer(); 92 | } 93 | 94 | 95 | zHM.start(function() { 96 | // Should run after App.vue has set PingModule but before demo(). 97 | // 'Cross-Origin-Embedder-Policy': Ping seems to work with 'require-corp' without 98 | // acutally having CORP on foreign origins. 99 | // Also 'credentialless' isn't supported by Safari-18 as of 2024-09. 100 | pingInst = new window.PingModule(); 101 | 102 | thrPort = zHM.thrPort; 103 | thrPort.onmessage = function(e) { 104 | switch (e.data.cmd) { 105 | case 'setFormat': 106 | setToolbarActive(e.data.id, e.data.state); 107 | break; 108 | case 'ui_ready': 109 | // Trigger resize of the embedded window to match the canvas size. 110 | // May somewhen be obsoleted by: 111 | // https://gerrit.libreoffice.org/c/core/+/174040 112 | window.dispatchEvent(new Event('resize')); 113 | setTimeout(function() { // display Office UI properly 114 | loadingInfo.style.display = 'none'; 115 | canvas.style.visibility = null; 116 | for (const elem of disabledElementsAry) elem.disabled = false; 117 | pingTarget.addEventListener ("keyup", (evt) => { 118 | if(evt.key === 'Enter') btnPingFunc(); 119 | }); 120 | // Using Ping callback interface. 121 | pingInst.ping(urls_ary[urls_ary_i], function() { 122 | // Continue after first ping, which is often exceptionally slow. 123 | setTimeout(function() { // small delay to make the demo more interesting 124 | pingInst.ping(urls_ary[urls_ary_i], function(err, data) { 125 | pingExamples(err, data); 126 | }); 127 | }, 1000); // milliseconds 128 | }); 129 | }, 1000); // milliseconds 130 | break; 131 | default: 132 | throw Error('Unknown message command: ' + e.data.cmd); 133 | } 134 | }; 135 | 136 | get_calc_ping_example_ods().then(function(aryBuf) { 137 | zHM.FS.writeFile('/tmp/calc_ping_example.ods', new Uint8Array(aryBuf)); 138 | }); 139 | }); 140 | 141 | /* vim:set shiftwidth=2 softtabstop=2 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ 142 | -------------------------------------------------------------------------------- /examples/letter-address-vuejs3/src/App.vue: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 22 | 23 | 90 | 91 | 92 | 155 | -------------------------------------------------------------------------------- /examples/ping-monitor/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 13 | ZetaJS Demo: Ping Monitor 14 | 15 | 16 | 17 | 37 | 38 | 39 | 40 |
41 |
42 |
43 |

ZetaJS Demo: Ping Monitor

44 |
45 |
46 |
47 | 48 | 49 |
50 |
51 |
52 |
53 |
54 |
55 | 57 |
59 |

60 |

ZetaOffice is loading...

61 |
62 | 75 |
76 |
77 |
78 |
79 | 157 | 158 | 159 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # zetajs: Access ZetaOffice in the Browser from JavaScript via UNO 2 | 3 | The zetajs library provides the facilities to run an instance of ZetaOffice integrated in your 4 | web site, allowing you to control it with JavaScript code via the LibreOffice 5 | [UNO](https://wiki.documentfoundation.org/Documentation/DevGuide) technology. 6 | 7 | Use cases range from an in-browser office suite that looks and feels just like its desktop 8 | counterpart, to fine-tuned custom text editing and spreadsheet capabilities embedded in your web 9 | site, to a headless zetajs instance that does document conversion in the background. 10 | 11 | For a detailed description of zetajs, see the [Starting Points](docs/start.md) documentation. 12 | 13 | (Technically, zetajs provides a wrapper on top of the 14 | [Embind-based](https://blog.allotropia.de/2024/04/30/libreoffice-javascripted/) JavaScript scripting 15 | capabilities for LibreOffice. But it aims to provide a nicer, more idiomatic JavaScript experience, 16 | and completely hides the underlying machinery. In the future, it may even move away from that 17 | underlying Embind layer, in a backward-compatible way.) 18 | 19 | ## Using ZetaOffice 20 | 21 | Visit [zetaoffice.net](https://zetaoffice.net) to learn more about ZetaOffice, its CDN and how to host ZetaOffice yourself. 22 | 23 | ### Demo 24 | To see a demo of zetajs in use, visit [zetaoffice.net/#tryit](https://zetaoffice.net/#tryit). 25 | 26 | ![screenshots](screenshots.png) 27 | 28 | ### Examples and test code 29 | 30 | Check out our examples. Each example has instructions how to run it in its respective folder. 31 | 32 | | Example | Description | Toolkits/Libraries | Online Demo | 33 | | --- | --- | --- | --- | 34 | | [standalone](https://github.com/allotropia/zetajs/tree/main/examples/standalone) | Standalone Writer document canvas with simple formatting options. *Simple code, easy to start with* | Bootstrap |https://zetaoffice.net/demos/standalone/ | 35 | | [letter-address-vuejs3](https://github.com/allotropia/zetajs/tree/main/examples/letter-address-vuejs3) | Web form letter demo | Vue, w3.css | https://zetaoffice.net/demos/letter-address-vuejs3/ 36 | | [web-office](https://github.com/allotropia/zetajs/tree/main/examples/web-office) | Full office suite in the browser | Bootstrap | https://zetaoffice.net/demos/web-office/ 37 | | [ping-monitor](https://github.com/allotropia/zetajs/tree/main/examples/ping-monitor) | Chart with values being added on the fly | Bootstrap, ping.js | https://zetaoffice.net/demos/ping-monitor/ 38 | | [vuejs3-ping-tool](https://github.com/allotropia/zetajs/tree/main/examples/vuejs3-ping-tool) | Chart with values being added on the fly | Vue, Bootstrap, ping.js | https://zetaoffice.net/demos/vuejs3-ping-tool/ 39 | | [convertpdf](https://github.com/allotropia/zetajs/tree/main/examples/convertpdf) | local file to PDF conversion service | Plain javascript | https://zetaoffice.net/demos/convertpdf/ 40 | | [simple-examples](https://github.com/allotropia/zetajs/tree/main/examples/simple-examples) | small examples displaying various API features | Plain javascript 41 | 42 | These examples use the ZetaOffice CDN to get you started quickly. 43 | 44 | ## Why zetajs 45 | 46 | See how zetajs makes scripting ZetaOffice easy, building on the foundation of the LibreOffice UNO API: 47 | 48 | ### 1. Load a document 49 | 50 | ```javascript 51 | const css = zetajs.uno.com.sun.star; 52 | const desktop = css.frame.Desktop.create(zetajs.getUnoComponentContext()); 53 | let xModel = desktop.getCurrentFrame().getController().getModel(); 54 | if (!xModel?.queryInterface(zetajs.type.interface(css.text.XTextDocument))) { 55 | xModel = desktop.loadComponentFromURL( 56 | 'file:///android/default-document/example.odt', '_default', 0, []); 57 | } 58 | ``` 59 | 60 | ### 2. Change each paragraph in Writer into a random color 61 | 62 | ```javascript 63 | const xText = xModel.getText(); 64 | const xParaEnumeration = xText.createEnumeration(); 65 | for (const xParagraph of xParaEnumeration) { 66 | const color = Math.floor(Math.random() * 0xFFFFFF); 67 | xParagraph.setPropertyValue("CharColor", color); 68 | } 69 | ``` 70 | 71 | ## Using with an own build 72 | 73 | Please have a look into the respective config.sample.js file of each demo to use another ZetaOffice build. 74 | 75 | You may also compile a custom [LOWA build](https://git.libreoffice.org/core/+/refs/heads/master/static/README.wasm.md). There the folder `workdir/installation/LibreOffice/emscripten/` will contain the files for the web root. If you host the WASM binary on another origin then the example code you will need to set a [CORS header](https://developer.mozilla.org/docs/Web/HTTP/CORS). 76 | 77 | For the sources of the WASM binaries served by cdn.zetaoffice.net see: 78 | * https://git.libreoffice.org/core/+/refs/heads/distro/allotropia/zeta-24-2 79 | * https://github.com/allotropia/emscripten/commits/fixed-3.1.65 80 | * https://github.com/allotropia/qt5/tree/5.15.2%2Bwasm 81 | * https://github.com/allotropia/qtbase/tree/5.15.2%2Bwasm 82 | 83 | ## Contributions 84 | 85 | ### Submitting issues 86 | 87 | First off, please search existing issues first, before filing a new 88 | one (see [search on github](https://help.github.com/articles/searching-issues)). 89 | 90 | If you think you've found a security problem, feel free to contact one 91 | of the project maintainers in private, for responsible disclosure. 92 | 93 | ### Development and Code 94 | 95 | For any LibreOffice questions (code, API, features), you'll find us on 96 | the [LibreOffice IRC channel](https://web.libera.chat/?channels=libreoffice-dev) - changes 97 | to LibreOffice core then go through 98 | [TDF's development process and gerrit code review system](https://wiki.documentfoundation.org/Development/GetInvolved). 99 | 100 | For changes to the zetajs library, just raise a pull request here, and 101 | make sure you've got permission to contribute your changes under the 102 | MIT license. For that, we use the [Developer Certificate of Origin (DCO)](https://developercertificate.org/): 103 | 104 | ~~~ 105 | Developer Certificate of Origin 106 | Version 1.1 107 | 108 | Copyright (C) 2004, 2006 The Linux Foundation and its contributors. 109 | 110 | Everyone is permitted to copy and distribute verbatim copies of this 111 | license document, but changing it is not allowed. 112 | 113 | 114 | Developer's Certificate of Origin 1.1 115 | 116 | By making a contribution to this project, I certify that: 117 | 118 | (a) The contribution was created in whole or in part by me and I 119 | have the right to submit it under the open source license 120 | indicated in the file; or 121 | 122 | (b) The contribution is based upon previous work that, to the best 123 | of my knowledge, is covered under an appropriate open source 124 | license and I have the right under that license to submit that 125 | work with modifications, whether created in whole or in part 126 | by me, under the same open source license (unless I am 127 | permitted to submit under a different license), as indicated 128 | in the file; or 129 | 130 | (c) The contribution was provided directly to me by some other 131 | person who certified (a), (b) or (c) and I have not modified 132 | it. 133 | 134 | (d) I understand and agree that this project and the contribution 135 | are public and that a record of the contribution (including all 136 | personal information I submit with it, including my sign-off) is 137 | maintained indefinitely and may be redistributed consistent with 138 | this project or the open source license(s) involved. 139 | ~~~ 140 | 141 | When submitting a pull request, to make this certification please 142 | therefore add a sign-off line to your commits: 143 | 144 | ~~~ 145 | Signed-off-by: Random J Developer 146 | ~~~ 147 | 148 | Use your real name (sorry, no pseudonyms or anonymous 149 | contributions). 150 | 151 | This project is tested with BrowserStack. 152 | -------------------------------------------------------------------------------- /examples/standalone/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 13 | 14 | 15 | 31 | 32 | 33 | 34 |
35 |
36 |
37 |

Writer Document canvas

38 |

An example of a stripped-down, standalone Writer document canvas without any surrounding menubars, toolbars, side panels, etc.

39 |
40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 |
49 |
50 |
51 |
52 |
53 | 56 |
58 |

59 |

ZetaOffice is loading...

60 |
61 | 69 |
70 |
71 |
72 | 73 | 166 | 167 | 168 | -------------------------------------------------------------------------------- /examples/letter-address-vuejs3/public/pre_soffice.js: -------------------------------------------------------------------------------- 1 | /* -*- Mode: JS; tab-width: 2; indent-tabs-mode: nil; js-indent-level: 2; fill-column: 100 -*- */ 2 | // SPDX-License-Identifier: MIT 3 | 4 | import { ZetaHelperMain } from './assets/vendor/zetajs/zetaHelper.js'; 5 | 6 | 7 | 8 | let tbDataJs; // toolbar dataset passed from vue.js for plain JS 9 | let letterForeground = true; 10 | let data = []; 11 | 12 | const loadingInfo = document.getElementById('loadingInfo'); 13 | const canvas = document.getElementById('qtcanvas'); 14 | const controlbar = document.getElementById('controlbar'); 15 | const controlCell = document.getElementById('controlCell'); 16 | const addrNameCell = document.getElementById('addrNameCell'); 17 | const canvasCell = document.getElementById('canvasCell'); 18 | const btnLetter = document.getElementById('btnLetter'); 19 | const btnTable = document.getElementById('btnTable'); 20 | const lblUpload = document.getElementById('lblUpload'); 21 | const btnUpload = document.getElementById('btnUpload'); 22 | const btnReload = document.getElementById('btnReload'); 23 | const btnInsert = document.getElementById('btnInsert'); 24 | const addrName = document.getElementById('addrName'); 25 | const disabledElementsAry = 26 | [btnLetter, btnTable, btnUpload, btnReload, btnInsert, addrName]; 27 | const canvas_height = parseInt(canvas.style.height); 28 | const canvas_width = parseInt(canvas.style.width); 29 | 30 | // IMPORTANT: 31 | // Set base URL to the soffice.* files. 32 | // Use an empty string if those files are in the same directory. 33 | let wasmPkg; 34 | try { 35 | wasmPkg = 'url:' + config_soffice_base_url; // May fail. config.js is optional. 36 | } catch {} 37 | const zHM = new ZetaHelperMain('office_thread.js', {threadJsType: 'module', wasmPkg}); 38 | 39 | 40 | // Functions stored below window.* are usually accessed from vue.js. 41 | 42 | window.jsPassCtrlBar = (pTbDataJs) => { // window....: make it accessible to vue.js 43 | tbDataJs = pTbDataJs; 44 | disabledElementsAry.push(tbDataJs); 45 | } 46 | 47 | window.toggleFormatting = (id, value) => { // window....: make it accessible to vue.js 48 | setToolbarActive(id, !tbDataJs.active[id]); 49 | zHM.thrPort.postMessage({cmd: 'toggleFormat', id, value}); 50 | // Give focus to the LO canvas to avoid issues with 51 | // "Setting Bold is 52 | // undone when clicking into non-empty document" when the user would need to click 53 | // into the canvas to give back focus to it: 54 | canvas.focus(); 55 | } 56 | 57 | function setToolbarActive(id, value) { 58 | tbDataJs.active[id] = value; 59 | // Need to set "active" on "tbDataJs" to trigger an UI update. 60 | tbDataJs.active = tbDataJs.active; 61 | } 62 | 63 | window.btnSwitchTab = (tab) => { // window....: make it accessible to vue.js 64 | if (tab === 'letter') { 65 | letterForeground = true; 66 | btnLetter.classList.add('active'); 67 | btnTable.classList.remove('active'); 68 | controlbar.style.display = null; 69 | btnUpload.accept = '.odt'; 70 | lblUpload.classList.remove('btn-primary'); 71 | lblUpload.classList.add('btn-light'); 72 | btnInsert.disabled = false; 73 | addrNameCell.style.display = null; 74 | addrName.style.visibility = null; 75 | btnReload.classList.remove('mt-2'); 76 | btnReload.classList.add('ms-2'); 77 | canvasCell.classList.remove('col-lg-10'); 78 | canvasCell.classList.add('col-lg-9'); 79 | controlCell.classList.remove('col-lg-2'); 80 | controlCell.classList.add('col-lg-3'); 81 | canvas.style.height = canvas_height + 'px'; 82 | canvas.style.width = canvas_width + 'px'; 83 | } else { // table 84 | letterForeground = false; 85 | btnLetter.classList.remove('active'); 86 | btnTable.classList.add('active'); 87 | controlbar.style.display = 'none'; 88 | btnUpload.accept = '.ods'; 89 | lblUpload.classList.add('btn-primary'); 90 | lblUpload.classList.remove('btn-light'); 91 | btnInsert.disabled = true; 92 | addrNameCell.style.display = 'none'; 93 | addrName.style.visibility = 'hidden'; 94 | btnReload.classList.remove('ms-2'); 95 | btnReload.classList.add('mt-2'); 96 | canvasCell.classList.remove('col-lg-9'); 97 | canvasCell.classList.add('col-lg-10'); 98 | controlCell.classList.remove('col-lg-3'); 99 | controlCell.classList.add('col-lg-2'); 100 | canvas.style.height = canvas_height + 46 + 'px'; 101 | canvas.style.width = canvas_width + 100 + 'px'; 102 | } 103 | zHM.thrPort.postMessage({cmd: 'switch_tab', id: tab}); 104 | } 105 | 106 | window.btnDownloadFunc = (btnId) => { // window....: make it accessible to vue.js 107 | zHM.thrPort.postMessage({cmd: 'download', id: btnId}); 108 | } 109 | 110 | window.btnUploadFunc = () => { // window....: make it accessible to vue.js 111 | for (const elem of disabledElementsAry) elem.disabled = true; 112 | lblUpload.classList.add('disabled'); 113 | const filename = letterForeground ? 'letter.odt' : 'table.ods'; 114 | btnUpload.files[0].arrayBuffer().then(aryBuf => { 115 | FS.writeFile('/tmp/' + filename, new Uint8Array(aryBuf)); 116 | btnReloadFunc(); 117 | }); 118 | } 119 | 120 | window.btnReloadFunc = () => { // window....: make it accessible to vue.js 121 | for (const elem of disabledElementsAry) elem.disabled = true; 122 | lblUpload.classList.add('disabled'); 123 | loadingInfo.style.display = null; 124 | canvas.style.visibility = 'hidden'; 125 | zHM.thrPort.postMessage({cmd: 'reload', id: letterForeground}); 126 | } 127 | 128 | window.btnInsertFunc = () => { // window....: make it accessible to vue.js 129 | if (addrName.selectedIndex != -1) { 130 | const recipient = data[addrName.selectedIndex]; 131 | zHM.thrPort.postMessage({cmd: 'insertAddress', recipient}); 132 | } 133 | } 134 | 135 | 136 | async function getDataFile(file_url) { 137 | const response = await fetch(file_url); 138 | return response.arrayBuffer(); 139 | } 140 | 141 | 142 | zHM.start(() => { 143 | zHM.thrPort.onmessage = (e) => { 144 | switch (e.data.cmd) { 145 | case 'ui_ready': 146 | // Trigger resize of the embedded window to match the canvas size. 147 | // May somewhen be obsoleted by: 148 | // https://gerrit.libreoffice.org/c/core/+/174040 149 | window.dispatchEvent(new Event('resize')); 150 | setTimeout(() => { // display Office UI properly 151 | loadingInfo.style.display = 'none'; 152 | canvas.style.visibility = null; 153 | tbDataJs.font_name_list = e.data.fontsList; 154 | for (const elem of disabledElementsAry) elem.disabled = false; 155 | lblUpload.classList.remove('disabled'); 156 | btnInsert.disabled = !letterForeground; 157 | }, 1000); // milliseconds 158 | break; 159 | case 'resizeEvt': 160 | window.dispatchEvent(new Event('resize')); 161 | break; 162 | case 'addrData': 163 | data = e.data.data; 164 | addrName.innerHTML = ''; 165 | for (const recipient of data) { 166 | const option = document.createElement('option'); 167 | option.innerHTML = recipient[1]; 168 | addrName.appendChild(option); 169 | } 170 | break; 171 | case 'setFormat': 172 | setToolbarActive(e.data.id, e.data.state); 173 | break; 174 | case 'download': 175 | const bytes = zHM.FS.readFile('/tmp/output'); 176 | const format = e.data.id === 'btnOdt' ? 'odt' : 'pdf'; 177 | const blob = new Blob([bytes], {type: 'application/' + format}); 178 | const link = document.createElement('a'); 179 | link.href = URL.createObjectURL(blob); 180 | link.download = 'letter.' + format; 181 | link.style.display = 'none'; 182 | document.body.appendChild(link); 183 | link.click(); 184 | document.body.removeChild(link); 185 | URL.revokeObjectURL(link.href); 186 | break; 187 | default: 188 | throw Error('Unknown message command: ' + e.data.cmd); 189 | } 190 | }; 191 | 192 | getDataFile('./letter.odt').then((aryBuf) => { 193 | zHM.FS.writeFile('/tmp/letter.odt', new Uint8Array(aryBuf)); 194 | }); 195 | getDataFile('./table.ods').then((aryBuf) => { 196 | zHM.FS.writeFile('/tmp/table.ods', new Uint8Array(aryBuf)); 197 | }); 198 | }); 199 | 200 | /* vim:set shiftwidth=2 softtabstop=2 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ 201 | -------------------------------------------------------------------------------- /examples/letter-address-vuejs3/src/components/ControlBar.vue: -------------------------------------------------------------------------------- 1 | 2 | 221 | 222 | 223 | 224 | 225 | 230 | 231 | 233 | -------------------------------------------------------------------------------- /source/zetaHelper.ts: -------------------------------------------------------------------------------- 1 | // ATTENTION: Experimental code! Expect heavy API changes. 2 | 3 | 4 | 5 | /** 6 | * Helper for code in the browsers main thread. 7 | * @beta 8 | */ 9 | export class ZetaHelperMain { 10 | static wasmUrls: Record = { 11 | free: 'https://cdn.zetaoffice.net/zetaoffice_latest/', 12 | business: 'https://business-cdn.zetaoffice.net/zetaoffice_latest/', 13 | }; 14 | 15 | canvas: HTMLElement; 16 | Module: any; 17 | threadJs: string | null; 18 | threadJsType = 'classic'; 19 | soffice_base_url: string; 20 | /** zetajs thread communication */ 21 | thrPort!: MessagePort; 22 | /** Emscripten Unix like virtual file system */ 23 | FS!: any; 24 | 25 | 26 | /** 27 | * Use absolute URLs or URLs relative to the root HTML document (location.href). 28 | * @param threadJs - URL for JS code file running inside the office thread (web worker). 29 | * @param options.threadJsType - 'classic' or 'module' (ES2015) 30 | * see: https://developer.mozilla.org/docs/Web/API/Worker/Worker#type 31 | * @param options.wasmPkg - Which WASM binaries to use. Possible options: 32 | * 'free', 'business', 'url:YOUR_CUSTOM_URL' 33 | * @param options.blockPageScroll - Don't scroll the HTML page while the cursor is above 34 | * the canvas. (default: true) 35 | */ 36 | constructor( 37 | threadJs: string | URL | null, 38 | options: { 39 | threadJsType: string | null, 40 | wasmPkg: string | null, // default: 'free' 41 | blockPageScroll: boolean // default: true 42 | }) { 43 | // Enable usage of LOWA builds with UI. 44 | const canvas = document.getElementById('qtcanvas')!; 45 | 46 | const thisFileUrl = import.meta.url; 47 | const modUrlDir = thisFileUrl.substring(0, thisFileUrl.length - 'zetaHelper.js'.length); 48 | 49 | if (threadJs) threadJs = (new URL(threadJs, location.href)).toString(); 50 | 51 | const zetajsScript = modUrlDir + 'zeta.js'; 52 | const threadWrapScript = 'data:text/javascript;charset=UTF-8,' + 53 | 'import("' + import.meta.url + '").then(m => {m.zetaHelperWrapThread();});'; 54 | const wasmPkg = options.wasmPkg || 'free'; 55 | let soffice_base_url = ZetaHelperMain.wasmUrls[wasmPkg] || ZetaHelperMain.wasmUrls['free']; 56 | if (wasmPkg?.substring(0,4) === 'url:') soffice_base_url = wasmPkg.substring(4, wasmPkg.length); 57 | if (soffice_base_url === '') soffice_base_url = './'; 58 | soffice_base_url = (new URL(soffice_base_url, location.href)).toString(); 59 | const Module: any = { 60 | canvas, 61 | uno_scripts: [zetajsScript, threadWrapScript], 62 | locateFile: (path: string, prefix: string) => { return (prefix || soffice_base_url) + path; }, 63 | modUrlDir, 64 | }; 65 | Module.mainScriptUrlOrBlob = new Blob( 66 | ["importScripts('"+(new URL('soffice.js', soffice_base_url))+"');"], 67 | {type: 'text/javascript'}); 68 | 69 | let lastDevicePixelRatio = window.devicePixelRatio; 70 | addEventListener('resize', () => { 71 | // Workaround to inform Qt5 about changed browser zoom. 72 | setTimeout(() => { 73 | if (lastDevicePixelRatio != -1) { 74 | if (lastDevicePixelRatio != window.devicePixelRatio) { 75 | lastDevicePixelRatio = -1; 76 | this.widthPxAdd(canvas.style, +1); 77 | window.dispatchEvent(new Event('resize')); 78 | } 79 | } else { 80 | lastDevicePixelRatio = window.devicePixelRatio 81 | this.widthPxAdd(canvas.style, -1); 82 | window.dispatchEvent(new Event('resize')); 83 | } 84 | }, 100); 85 | }); 86 | 87 | // Scroll only the canvas while the mouse cursor is above it. 88 | if (options.blockPageScroll != false) 89 | canvas.addEventListener('wheel', (event) => { 90 | event.preventDefault(); 91 | }, {passive: false}); 92 | 93 | (window as any).Module = Module; // window.* is global 94 | this.canvas = canvas; 95 | this.Module = Module; 96 | this.threadJs = threadJs?.toString() || null; 97 | if (options.threadJsType === 'module') this.threadJsType = 'module'; 98 | this.soffice_base_url = soffice_base_url; 99 | } 100 | 101 | 102 | start(app_init: () => void) { 103 | const zHM = this; 104 | const soffice_js = document.createElement("script"); 105 | soffice_js.src = zHM.soffice_base_url + "soffice.js"; 106 | // "onload" runs after the loaded script has run. 107 | soffice_js.onload = () => { 108 | console.log('zetaHelper: Configuring Module'); 109 | zHM.Module.uno_main.then((pThrPort: MessagePort) => { 110 | zHM.thrPort = pThrPort; 111 | zHM.FS = (window as any).FS; 112 | zHM.thrPort.onmessage = (e: any) => { 113 | switch (e.data.cmd) { 114 | case 'ZetaHelper::thr_started': 115 | // Trigger resize of the embedded window to match the canvas size. 116 | // May somewhen be obsoleted by: 117 | // https://gerrit.libreoffice.org/c/core/+/174040 118 | window.dispatchEvent(new Event('resize')); 119 | zHM.thrPort.postMessage({ 120 | cmd: 'ZetaHelper::run_thr_script', 121 | threadJs: zHM.threadJs, 122 | threadJsType: zHM.threadJsType 123 | }); 124 | app_init(); 125 | break; 126 | default: 127 | throw Error('Unknown message command: ' + e.data.cmd); 128 | }; 129 | } 130 | }); 131 | }; 132 | console.log('zetaHelper: Loading WASM binaries for ZetaJS from: ' + zHM.soffice_base_url); 133 | // Hint: The global objects "canvas" and "Module" must exist before the next line. 134 | document.body.appendChild(soffice_js); 135 | } 136 | 137 | private widthPxAdd(obj: CSSStyleDeclaration, value: number) { 138 | if (/\A\d+px\z/.test(obj.width)) { 139 | obj.width = parseInt(obj.width) + value + 'px'; 140 | } 141 | } 142 | } 143 | 144 | 145 | 146 | 147 | /** 148 | * Initializes zetajs in the office thread (web worker). 149 | * Will be called by ZetaHelperMain. 150 | * NOT MEANT FOR DIRECT USE 151 | * @beta 152 | */ 153 | export function zetaHelperWrapThread() { 154 | const zJsModule = (globalThis as any).Module; 155 | zJsModule.zetajs.then((zetajs: any) => { 156 | const port: MessagePort = zetajs.mainPort; 157 | port.onmessage = (e) => { 158 | switch (e.data.cmd) { 159 | case 'ZetaHelper::run_thr_script': 160 | port.onmessage = null; 161 | globalThis.zetajsStore = {zetajs, zJsModule}; 162 | let threadJs = e.data.threadJs; 163 | if (threadJs) { 164 | if (e.data.threadJsType === 'module') { 165 | console.log('zetaHelper: Loading threadJs as module from: ' + threadJs); 166 | import(threadJs).then(module => { 167 | // Make exports of threadJs accessible for debugging. 168 | globalThis.zetajsStore.threadJsContext = module; 169 | }); 170 | } else { // classic 171 | console.log('zetaHelper: Loading threadJs as script from: ' + threadJs); 172 | importScripts(threadJs); 173 | } 174 | } else { 175 | console.log('zetaHelper: Office loaded. No threadJs given.'); 176 | } 177 | break; 178 | default: 179 | throw Error('Unknown message command: ' + e.data.cmd); 180 | }; 181 | } 182 | port.postMessage({ 183 | cmd: 'ZetaHelper::thr_started' 184 | }); 185 | }); 186 | } 187 | 188 | 189 | 190 | 191 | /** 192 | * Helper for inside the office thread (web worker). 193 | * @beta 194 | */ 195 | export class ZetaHelperThread { 196 | config: any; 197 | context: any; 198 | /** com.sun.star */ 199 | css: any; 200 | desktop: any; 201 | thrPort: MessagePort; 202 | zetajs: any; 203 | zJsModule: any; 204 | 205 | 206 | constructor() { 207 | this.zetajs = globalThis.zetajsStore.zetajs; 208 | 209 | this.zJsModule = globalThis.zetajsStore.zJsModule; 210 | 211 | this.thrPort = this.zetajs.mainPort; 212 | this.css = this.zetajs.uno.com.sun.star; 213 | this.context = this.zetajs.getUnoComponentContext(); 214 | this.desktop = this.css.frame.Desktop.create(this.context); 215 | this.config = this.css.configuration.ReadWriteAccess.create(this.context, 'en-US'); 216 | } 217 | 218 | 219 | /** 220 | * Turn off toolbars. 221 | * @param officeModules - ["Base", "Calc", "Draw", "Impress", "Math", "Writer"]; 222 | */ 223 | configDisableToolbars(officeModules: string[]) { 224 | for (const mod of officeModules) { 225 | const modName = "/org.openoffice.Office.UI." + mod + "WindowState/UIElements/States"; 226 | const uielems = this.config.getByHierarchicalName(modName); 227 | for (const i of uielems.getElementNames()) { 228 | if (i.startsWith("private:resource/toolbar/")) { 229 | const uielem = uielems.getByName(i); // SLOW OPERATION 230 | if (uielem.getByName('Visible')) { 231 | uielem.setPropertyValue('Visible', false); 232 | } 233 | } 234 | } 235 | } 236 | this.config.commitChanges(); 237 | } 238 | 239 | /** 240 | * @param unoUrl - string following ".uno:" (e.g. "Bold") 241 | */ 242 | transformUrl(unoUrl: string) { 243 | const ioparam = {val: new this.css.util.URL({Complete: '.uno:' + unoUrl})}; 244 | this.css.util.URLTransformer.create(this.context).parseStrict(ioparam); 245 | return ioparam.val; 246 | } 247 | 248 | queryDispatch(ctrl: any, urlObj: any) { 249 | return ctrl.queryDispatch(urlObj, '_self', 0); 250 | } 251 | 252 | dispatch(ctrl: any, unoUrl: string, params: any[] = []) { 253 | const urlObj = this.transformUrl(unoUrl); 254 | this.queryDispatch(ctrl, urlObj).dispatch(urlObj, params); 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /examples/web-office/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 13 | ZetaJS Demo: Web Office 14 | 15 | 53 | 54 | 55 | 56 |
57 |
58 |
59 |

ZetaJS Demo: Web Office

60 |
61 |
62 | 63 |
64 |
65 |
66 | 67 | 68 |
69 |
70 |
71 | 72 |
73 |
74 |
75 |
76 |

77 |

ZetaOffice is loading...

78 |
79 | 87 |
88 |
89 |
90 | 91 |
92 | 238 | 239 | 240 | -------------------------------------------------------------------------------- /examples/letter-address-vuejs3/public/office_thread.js: -------------------------------------------------------------------------------- 1 | /* -*- Mode: JS; tab-width: 2; indent-tabs-mode: nil; js-indent-level: 2; fill-column: 100 -*- */ 2 | // SPDX-License-Identifier: MIT 3 | 4 | // Debugging note: 5 | // Switch the web worker in the browsers debug tab to debug this code. 6 | // It's the "em-pthread" web worker with the most memory usage, where "zetajs" is defined. 7 | 8 | // JS mode: module 9 | import { ZetaHelperThread } from './assets/vendor/zetajs/zetaHelper.js'; 10 | 11 | 12 | // global variables - zetajs environment: 13 | const zHT = new ZetaHelperThread(); 14 | const zetajs = zHT.zetajs; 15 | const css = zHT.css; 16 | const desktop = zHT.desktop; 17 | 18 | // = global variables (some are global for easier debugging) = 19 | // common variables: 20 | let tableXModel, letterXModel, tableCtrl, letterCtrl; 21 | // example specific: 22 | let writerModuleConfigured=false, calcModuleConfigured=false; 23 | const readyList = {Fonts: false, Window: false}; 24 | let letterFrame, tableFrame, fontsList, switchVals; 25 | // switchVals needed globally in case the user switches tabs rapidly. 26 | let bean_overwrite, bean_odt_export, bean_pdf_export; 27 | 28 | // Export variables for debugging. Available for debugging via: 29 | // globalThis.zetajsStore.threadJsContext 30 | export { zHT, tableXModel, letterXModel, tableCtrl, letterCtrl, 31 | bean_overwrite, bean_odt_export, bean_pdf_export }; 32 | 33 | 34 | function demo() { 35 | bean_overwrite = new css.beans.PropertyValue({Name: 'Overwrite', Value: true}); 36 | bean_odt_export = new css.beans.PropertyValue({Name: 'FilterName', Value: 'writer8'}); 37 | bean_pdf_export = new css.beans.PropertyValue({Name: 'FilterName', Value: 'writer_pdf_Export'}); 38 | 39 | zHT.configDisableToolbars(['Writer', 'Calc']); 40 | loadFile('both'); 41 | tableToHtml(); 42 | 43 | zHT.thrPort.onmessage = (e) => { 44 | switch (e.data.cmd) { 45 | case 'switch_tab': 46 | if (e.data.id === 'letter') { 47 | switchVals = [true, false]; 48 | tableToHtml(); 49 | } else switchVals = [false, true]; // table 50 | const setVals = () => { 51 | letterFrame.getContainerWindow().FullScreen = switchVals[0]; 52 | tableFrame.getContainerWindow().FullScreen = switchVals[1]; 53 | zHT.thrPort.postMessage({cmd: 'resizeEvt'}); 54 | } 55 | setVals(); // sometimes needed twice to apply resize 56 | setTimeout(() => { setVals(); }, 500); 57 | break; 58 | case 'download': 59 | const format = e.data.id === 'btnOdt' ? bean_odt_export : bean_pdf_export; 60 | letterXModel.storeToURL( 'file:///tmp/output', [bean_overwrite, format]); 61 | zHT.thrPort.postMessage({cmd: 'download', id: e.data.id}); 62 | break; 63 | case 'reload': 64 | const letterForeground = e.data.id; 65 | if (letterForeground) letterXModel.close(true) 66 | else tableXModel.close(true); 67 | loadFile(letterForeground ? 'letter' : 'table'); 68 | break; 69 | case 'toggleFormat': 70 | const params = []; 71 | const value = e.data.value; 72 | for (let i = 0; i < value.length; i++) { 73 | params[i] = new css.beans.PropertyValue({Name: value[i][0], Value: value[i][1]}); 74 | } 75 | zHT.dispatch(letterCtrl, e.data.id, params); 76 | break; 77 | case 'insertAddress': 78 | const recipient = e.data.recipient; 79 | const fieldsEnum = letterXModel.getTextFields().createEnumeration(); 80 | let state_count=0, city_count=0, postal_code_count=0, street_count=0; 81 | while (fieldsEnum.hasMoreElements()) { 82 | const field = fieldsEnum.nextElement().getAnchor(); 83 | switch (field.getString()) { 84 | case "": // additional space is needed 85 | field.setString(recipient[0] === '' ? '' : recipient[0]+' '); // recipient 86 | break; 87 | case "": 88 | field.setString(recipient[1]); 89 | break; 90 | case "": 91 | field.setString(recipient[2]); 92 | break; 93 | case "": // additional space is needed 94 | field.setString(recipient[3]+' '); 95 | break; 96 | case "": 97 | field.setString(recipient[4]); 98 | break; 99 | case "": 100 | field.setString(recipient[5]); 101 | break; 102 | case "": 103 | field.setString("Dent, Arthur Phillip"); 104 | break; 105 | case "": 106 | field.setString("Cottingshire Radio"); 107 | break; 108 | case "": 109 | field.setString("155 Country Lane"); 110 | break; 111 | case "": // additional space is needed 112 | field.setString("2A 2A2A"+' '); 113 | break; 114 | case "": 115 | field.setString("Cottington"); 116 | break; 117 | case "": 118 | field.setString("Cottingshire County"); 119 | break; 120 | } 121 | } 122 | break; 123 | default: 124 | throw Error('Unknown message command: ' + e.data.cmd); 125 | } 126 | } 127 | } 128 | 129 | 130 | function tableToHtml() { 131 | const activeSheet = tableCtrl.getActiveSheet(); 132 | const data = []; 133 | let row_10 = 0; 134 | let local_row = 0; 135 | while (local_row >= 0) { 136 | local_row = 0; 137 | const lines_block = activeSheet. 138 | getCellRangeByPosition(0, row_10*10+1, 5, row_10*10+9+1).getDataArray(); 139 | while (local_row >= 0 && local_row < 10) { 140 | const recipient = []; 141 | for (let rowData of lines_block[local_row]) recipient.push(rowData); 142 | if (!recipient.reduce((acc,v) => acc &&= v==='', true)) { 143 | data.push(recipient); 144 | local_row += 1; 145 | } else local_row = -1; 146 | } 147 | if (local_row > 0) local_row = 0; 148 | row_10 += 1; 149 | } 150 | zHT.thrPort.postMessage({cmd: 'addrData', data}); 151 | } 152 | 153 | 154 | function loadFile(fileTab) { 155 | if (fileTab != 'letter') { // table or both 156 | tableXModel = desktop.loadComponentFromURL('file:///tmp/table.ods', '_default', 0, []); 157 | tableCtrl = tableXModel.getCurrentController(); 158 | if (!calcModuleConfigured) { 159 | calcModuleConfigured = true; 160 | // Permanant Calc module toggles. Don't run again on a document reload. 161 | zHT.dispatch(tableCtrl, 'Sidebar', []); 162 | zHT.dispatch(tableCtrl, 'InputLineVisible', []); // FormulaBar at the top 163 | } 164 | tableFrame = tableCtrl.getFrame(); 165 | // Turn off UI elements (idempotent operations): 166 | tableFrame.LayoutManager.hideElement("private:resource/statusbar/statusbar"); 167 | tableFrame.LayoutManager.hideElement("private:resource/menubar/menubar"); 168 | tableCtrl.setPropertyValue('SheetTabs', false); 169 | if (fileTab == 'table') { 170 | // Storing the getContainerWindow() result is unstable. 171 | tableFrame.getContainerWindow().setPosSize(-1000,-1000,500,500,15); 172 | tableFrame.getContainerWindow().FullScreen = true; 173 | } 174 | } 175 | 176 | if (fileTab != 'table') { // letter or both 177 | letterXModel = desktop.loadComponentFromURL('file:///tmp/letter.odt', '_default', 0, []); 178 | letterCtrl = letterXModel.getCurrentController(); 179 | if (!writerModuleConfigured) { 180 | writerModuleConfigured = true; 181 | // Permanant Writer module toggles. Don't run again on a document reload. 182 | zHT.dispatch(letterCtrl, 'Sidebar', []); 183 | zHT.dispatch(letterCtrl, 'Ruler', []); 184 | } 185 | letterFrame = letterCtrl.getFrame(); 186 | // Turn off UI elements (idempotent operations): 187 | letterFrame.LayoutManager.hideElement("private:resource/statusbar/statusbar"); 188 | letterFrame.LayoutManager.hideElement("private:resource/menubar/menubar"); 189 | // Storing the getContainerWindow() result is unstable. 190 | letterFrame.getContainerWindow().setPosSize(-1000,-1000,500,500,15); 191 | letterFrame.getContainerWindow().FullScreen = true; 192 | 193 | // Get font list for toolbar. 194 | const fontsUrlObj = zHT.transformUrl('FontNameList'); 195 | const fontsDispatcher = zHT.queryDispatch(letterCtrl, fontsUrlObj); 196 | const fontsDispatchNotifier = css.frame.XDispatch.constructor(fontsDispatcher) 197 | const fontListener = zetajs.unoObject( 198 | [css.frame.XStatusListener], 199 | { statusChanged(e) { 200 | fontsDispatchNotifier.removeStatusListener(fontListener, fontsUrlObj); 201 | fontsList = zetajs.fromAny(e.State); 202 | startupReady('Fonts'); 203 | }}); 204 | fontsDispatchNotifier.addStatusListener(fontListener, fontsUrlObj); 205 | 206 | for (const id of [ 207 | 'Bold', 'Italic', 'Underline', 208 | 'Overline', 'Strikeout', 'Shadowed', 'Color', 'CharBackColor', 209 | 'LeftPara', 'CenterPara', 'RightPara', 'JustifyPara', 'DefaultBullet', 210 | 'FontHeight', 'CharFontName' 211 | ]) { 212 | const urlObj = zHT.transformUrl(id); 213 | const listener = zetajs.unoObject([css.frame.XStatusListener], { 214 | disposing: (source) => {}, 215 | statusChanged: (rawSt) => { // rawState 216 | rawSt = zetajs.fromAny(rawSt.State); 217 | // If a non uniformly formatted area is selected, state may contain an invalid value. 218 | let state; 219 | if (id === 'FontHeight') { 220 | if (typeof rawSt.Height === 'number') state = Math.round(rawSt.Height * 10) / 10; 221 | } else if (id === 'CharFontName') { 222 | if (typeof rawSt.Name === 'string') state = rawSt.Name; 223 | } else if (['Color', 'CharBackColor'].includes(id)) { 224 | if (typeof rawSt === 'number') { 225 | if (id === 'Color' && rawSt === -1) rawSt = 0x000000; 226 | else if (id === 'CharBackColor' && rawSt === -1) rawSt = 0xFFFFFF; 227 | state = '#' + (0x1000000 + rawSt).toString(16).substring(1, 7); // int to #RRGGBB 228 | } 229 | } else if (typeof rawSt === 'boolean') state = rawSt; 230 | else state = false; // Behave like desktop UI if a non uniformly formatted area is selected. 231 | if (typeof state !== 'undefined') zetajs.mainPort.postMessage({cmd: 'setFormat', id, state}); 232 | } 233 | }); 234 | zHT.queryDispatch(letterCtrl, urlObj).addStatusListener(listener, urlObj); 235 | } 236 | } 237 | startupReady('Window'); 238 | } 239 | 240 | 241 | function startupReady(startupStep) { 242 | readyList[startupStep] = true; 243 | if (Object.values(readyList).indexOf(false) == -1) 244 | zHT.thrPort.postMessage({cmd: 'ui_ready', fontsList}); 245 | } 246 | 247 | demo(); // launching demo 248 | 249 | /* vim:set shiftwidth=2 softtabstop=2 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ 250 | --------------------------------------------------------------------------------