From caaebde2402b61e61234dd5e1bd5c904c74c4533 Mon Sep 17 00:00:00 2001 From: Sergey Konovalov Date: Fri, 27 Jun 2025 10:37:36 +0300 Subject: [PATCH 01/11] [develop] Add note to clarify that ARM is unsupported --- develop/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/develop/README.md b/develop/README.md index 8de92cd..83e51d0 100644 --- a/develop/README.md +++ b/develop/README.md @@ -6,6 +6,9 @@ but don't want to compile pretty compilcated core product to make those changes. ## System requirements +**Note**: ARM-based architectures are currently **NOT** supported; +attempting to run the images on ARM devices may result in startup failures or other runtime issues. + ### Windows You need the latest From dcc9f8e66932457f8c36ffed935cfdd15fad718d Mon Sep 17 00:00:00 2001 From: Nikita Khromov Date: Mon, 30 Jun 2025 17:58:52 +0700 Subject: [PATCH 02/11] [macros][libs] Added forms to generation --- scripts/sdkjs_common/generate_builder_intarface.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/sdkjs_common/generate_builder_intarface.py b/scripts/sdkjs_common/generate_builder_intarface.py index 3bf1e42..49ed648 100644 --- a/scripts/sdkjs_common/generate_builder_intarface.py +++ b/scripts/sdkjs_common/generate_builder_intarface.py @@ -13,7 +13,7 @@ def writeFile(path, content): if (os.path.isfile(path)): os.remove(path) - with open(path, "w") as file: + with open(path, "w", encoding='utf-8') as file: file.write(content) return @@ -208,7 +208,7 @@ if __name__ == "__main__": type=str, help="Destination directory for the generated documentation", nargs='?', # Indicates the argument is optional - default="../../../onlyoffice.github.io\sdkjs-plugins\content\macros\libs/" # Default value + default="../../../web-apps/vendor/monaco/libs/" # Default value ) args = parser.parse_args() @@ -217,7 +217,7 @@ if __name__ == "__main__": if True == os.path.isdir(args.destination): shutil.rmtree(args.destination, ignore_errors=True) os.mkdir(args.destination) - convert_to_interface(["word/apiBuilder.js"], "word") + convert_to_interface(["word/apiBuilder.js", "../sdkjs-forms/apiBuilder.js"], "word") convert_to_interface(["word/apiBuilder.js", "slide/apiBuilder.js"], "slide") convert_to_interface(["word/apiBuilder.js", "slide/apiBuilder.js", "cell/apiBuilder.js"], "cell") os.chdir(old_cur) From 1d721e3e3ef853c8f4e9f9b459d772b605585d69 Mon Sep 17 00:00:00 2001 From: Nikita Khromov Date: Mon, 30 Jun 2025 18:32:39 +0700 Subject: [PATCH 03/11] [macros][libs] Remove extra methods info --- scripts/sdkjs_common/generate_builder_intarface.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/scripts/sdkjs_common/generate_builder_intarface.py b/scripts/sdkjs_common/generate_builder_intarface.py index 49ed648..ede609d 100644 --- a/scripts/sdkjs_common/generate_builder_intarface.py +++ b/scripts/sdkjs_common/generate_builder_intarface.py @@ -160,6 +160,12 @@ class EditorApi(object): editors_support = decoration[index_type_editors:index_type_editors_end] if -1 == editors_support.find(self.type): return + + decoration = "\n".join( + line for line in decoration.splitlines() + if "@typeofeditors" not in line and "@see" not in line + ) + # optimizations for first file if 0 == self.numfile: self.records.append(decoration + "\n" + code + "\n") From 31f679a050673fda88da5660991564264d332ae8 Mon Sep 17 00:00:00 2001 From: maxkadushkin Date: Mon, 30 Jun 2025 17:10:30 +0300 Subject: [PATCH 04/11] [js] added some logs --- scripts/build_js.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/scripts/build_js.py b/scripts/build_js.py index aab8864..e643d73 100644 --- a/scripts/build_js.py +++ b/scripts/build_js.py @@ -46,13 +46,21 @@ def make(): # builder if not isOnlyMobile: + print("LOG: start merge web-apps langs"); base.cmd_in_dir(base_dir + "/../web-apps/translation", "python", ["merge_and_check.py"]) + print("LOG: start build_interface"); build_interface(base_dir + "/../web-apps/build") + print("LOG: start build_sdk_builder"); build_sdk_builder(base_dir + "/../sdkjs/build") + print("LOG: create_dir builder"); base.create_dir(out_dir + "/builder") + print("LOG: copy_dir web-apps"); base.copy_dir(base_dir + "/../web-apps/deploy/web-apps", out_dir + "/builder/web-apps") + print("LOG: copy_dir sdkjs"); base.copy_dir(base_dir + "/../sdkjs/deploy/sdkjs", out_dir + "/builder/sdkjs") + print("LOG: correct_sdkjs_licence"); correct_sdkjs_licence(out_dir + "/builder/sdkjs") + print("LOG: correct_sdkjs_licence finished"); # desktop if config.check_option("module", "desktop") and not isOnlyMobile: From c4551af2533c84d09f72458cc6b89699838a0592 Mon Sep 17 00:00:00 2001 From: Oleg Korshul Date: Thu, 3 Jul 2025 11:07:08 +0300 Subject: [PATCH 05/11] Add temporary logs --- scripts/build_js.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/build_js.py b/scripts/build_js.py index e643d73..2961b7f 100644 --- a/scripts/build_js.py +++ b/scripts/build_js.py @@ -129,8 +129,11 @@ def _run_grunt(directory, params=[]): return base.cmd_in_dir(directory, "grunt", params) def build_interface(directory): + print("LOG: interface 1 step"); _run_npm(directory) + print("LOG: interface 2 step"); _run_grunt(directory, ["--force"] + base.web_apps_addons_param()) + print("LOG: interface 3 step"); return def get_build_param(minimize=True): From 2e179644b3f7faf61bdeff60e40a57f8d261a868 Mon Sep 17 00:00:00 2001 From: Oleg Korshul Date: Thu, 3 Jul 2025 11:50:44 +0300 Subject: [PATCH 06/11] Add verbose for debug npm/grunt --- scripts/build_js.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/build_js.py b/scripts/build_js.py index 2961b7f..7e548ab 100644 --- a/scripts/build_js.py +++ b/scripts/build_js.py @@ -116,8 +116,8 @@ def make(): return # JS build -def _run_npm(directory): - return base.cmd_in_dir(directory, "npm", ["install"]) +def _run_npm(directory, params=[]): + return base.cmd_in_dir(directory, "npm", ["install"] + params) def _run_npm_ci(directory): return base.cmd_in_dir(directory, "npm", ["ci"]) @@ -130,9 +130,9 @@ def _run_grunt(directory, params=[]): def build_interface(directory): print("LOG: interface 1 step"); - _run_npm(directory) + _run_npm(directory, ["--verbose"]) print("LOG: interface 2 step"); - _run_grunt(directory, ["--force"] + base.web_apps_addons_param()) + _run_grunt(directory, ["--force", "--verbose"] + base.web_apps_addons_param()) print("LOG: interface 3 step"); return From de1d4375768ccf787d1dc75aa0f6e6cd3d140290 Mon Sep 17 00:00:00 2001 From: Oleg Korshul Date: Thu, 3 Jul 2025 13:10:37 +0300 Subject: [PATCH 07/11] Remove temporary logs. Add verbose to install modules if failed --- scripts/build_js.py | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/scripts/build_js.py b/scripts/build_js.py index 7e548ab..5405e73 100644 --- a/scripts/build_js.py +++ b/scripts/build_js.py @@ -46,21 +46,13 @@ def make(): # builder if not isOnlyMobile: - print("LOG: start merge web-apps langs"); base.cmd_in_dir(base_dir + "/../web-apps/translation", "python", ["merge_and_check.py"]) - print("LOG: start build_interface"); build_interface(base_dir + "/../web-apps/build") - print("LOG: start build_sdk_builder"); build_sdk_builder(base_dir + "/../sdkjs/build") - print("LOG: create_dir builder"); base.create_dir(out_dir + "/builder") - print("LOG: copy_dir web-apps"); base.copy_dir(base_dir + "/../web-apps/deploy/web-apps", out_dir + "/builder/web-apps") - print("LOG: copy_dir sdkjs"); base.copy_dir(base_dir + "/../sdkjs/deploy/sdkjs", out_dir + "/builder/sdkjs") - print("LOG: correct_sdkjs_licence"); correct_sdkjs_licence(out_dir + "/builder/sdkjs") - print("LOG: correct_sdkjs_licence finished"); # desktop if config.check_option("module", "desktop") and not isOnlyMobile: @@ -116,8 +108,11 @@ def make(): return # JS build -def _run_npm(directory, params=[]): - return base.cmd_in_dir(directory, "npm", ["install"] + params) +def _run_npm(directory): + retValue = base.cmd_in_dir(directory, "npm", ["install"], True) + if (0 != retValue): + retValue = base.cmd_in_dir(directory, "npm", ["install", "--verbose"]) + return retValue def _run_npm_ci(directory): return base.cmd_in_dir(directory, "npm", ["ci"]) @@ -129,11 +124,8 @@ def _run_grunt(directory, params=[]): return base.cmd_in_dir(directory, "grunt", params) def build_interface(directory): - print("LOG: interface 1 step"); - _run_npm(directory, ["--verbose"]) - print("LOG: interface 2 step"); + _run_npm(directory) _run_grunt(directory, ["--force", "--verbose"] + base.web_apps_addons_param()) - print("LOG: interface 3 step"); return def get_build_param(minimize=True): From 71a2981ae8ded01c8b204ec407e83685d81353db Mon Sep 17 00:00:00 2001 From: Igor Demin Date: Mon, 7 Jul 2025 13:12:43 +0500 Subject: [PATCH 08/11] Up version to 9.0.3 --- version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version b/version index c9277c5..8e055f7 100644 --- a/version +++ b/version @@ -1 +1 @@ -9.0.0 \ No newline at end of file +9.0.3 \ No newline at end of file From d2c79bb78da4c038097ca0dc43ac8fa29e2e0d2e Mon Sep 17 00:00:00 2001 From: Nikita Khromov Date: Mon, 7 Jul 2025 19:05:10 +0700 Subject: [PATCH 09/11] [jsdoc] Refactoring --- scripts/sdkjs_common/jsdoc/README.md | 108 ++- scripts/sdkjs_common/jsdoc/Readme.txt | 0 .../jsdoc/config/builder/slide.json | 16 - .../jsdoc/generate_docs_md_site.py | 586 ---------------- .../jsdoc/generate_docs_plugins_md_site.py | 634 ------------------ .../builder => office-api/config}/cell.json | 2 +- .../config}/correct_doclets.js | 0 .../builder => office-api/config}/forms.json | 2 +- .../forms.json => office-api/config/pdf.json} | 2 +- .../plugins => office-api/config}/slide.json | 2 +- .../builder => office-api/config}/word.json | 2 +- .../{ => office-api}/generate_docs_json.py | 10 +- .../{ => office-api}/generate_docs_md.py | 28 +- .../generate_jsonl_dataset.py | 4 +- .../config}/correct_doclets.js | 0 .../jsdoc/plugins/config/events/cell.json | 16 + .../jsdoc/plugins/config/events/common.json | 16 + .../jsdoc/plugins/config/events/forms.json | 16 + .../jsdoc/plugins/config/events/slide.json | 16 + .../jsdoc/plugins/config/events/word.json | 16 + .../config/methods}/cell.json | 4 +- .../config/methods}/common.json | 4 +- .../jsdoc/plugins/config/methods/forms.json | 16 + .../jsdoc/plugins/config/methods/slide.json | 16 + .../config/methods}/word.json | 4 +- .../generate_docs_events_json.py} | 16 +- .../jsdoc/plugins/generate_docs_events_md.py | 378 +++++++++++ .../plugins/generate_docs_methods_json.py | 111 +++ .../generate_docs_methods_md.py} | 109 +-- 29 files changed, 787 insertions(+), 1347 deletions(-) create mode 100644 scripts/sdkjs_common/jsdoc/Readme.txt delete mode 100644 scripts/sdkjs_common/jsdoc/config/builder/slide.json delete mode 100644 scripts/sdkjs_common/jsdoc/generate_docs_md_site.py delete mode 100644 scripts/sdkjs_common/jsdoc/generate_docs_plugins_md_site.py rename scripts/sdkjs_common/jsdoc/{config/builder => office-api/config}/cell.json (59%) rename scripts/sdkjs_common/jsdoc/{config/builder => office-api/config}/correct_doclets.js (100%) rename scripts/sdkjs_common/jsdoc/{config/builder => office-api/config}/forms.json (67%) rename scripts/sdkjs_common/jsdoc/{config/plugins/forms.json => office-api/config/pdf.json} (78%) rename scripts/sdkjs_common/jsdoc/{config/plugins => office-api/config}/slide.json (67%) rename scripts/sdkjs_common/jsdoc/{config/builder => office-api/config}/word.json (78%) rename scripts/sdkjs_common/jsdoc/{ => office-api}/generate_docs_json.py (96%) rename scripts/sdkjs_common/jsdoc/{ => office-api}/generate_docs_md.py (95%) rename scripts/sdkjs_common/jsdoc/{ => office-api}/generate_jsonl_dataset.py (98%) rename scripts/sdkjs_common/jsdoc/{config/plugins => plugins/config}/correct_doclets.js (100%) create mode 100644 scripts/sdkjs_common/jsdoc/plugins/config/events/cell.json create mode 100644 scripts/sdkjs_common/jsdoc/plugins/config/events/common.json create mode 100644 scripts/sdkjs_common/jsdoc/plugins/config/events/forms.json create mode 100644 scripts/sdkjs_common/jsdoc/plugins/config/events/slide.json create mode 100644 scripts/sdkjs_common/jsdoc/plugins/config/events/word.json rename scripts/sdkjs_common/jsdoc/{config/plugins => plugins/config/methods}/cell.json (63%) rename scripts/sdkjs_common/jsdoc/{config/plugins => plugins/config/methods}/common.json (51%) create mode 100644 scripts/sdkjs_common/jsdoc/plugins/config/methods/forms.json create mode 100644 scripts/sdkjs_common/jsdoc/plugins/config/methods/slide.json rename scripts/sdkjs_common/jsdoc/{config/plugins => plugins/config/methods}/word.json (54%) rename scripts/sdkjs_common/jsdoc/{generate_docs_plugins_json.py => plugins/generate_docs_events_json.py} (93%) create mode 100644 scripts/sdkjs_common/jsdoc/plugins/generate_docs_events_md.py create mode 100644 scripts/sdkjs_common/jsdoc/plugins/generate_docs_methods_json.py rename scripts/sdkjs_common/jsdoc/{generate_docs_plugins_md.py => plugins/generate_docs_methods_md.py} (89%) diff --git a/scripts/sdkjs_common/jsdoc/README.md b/scripts/sdkjs_common/jsdoc/README.md index ba77eaf..a0dd1ae 100644 --- a/scripts/sdkjs_common/jsdoc/README.md +++ b/scripts/sdkjs_common/jsdoc/README.md @@ -1,7 +1,14 @@ - # Documentation Generation Guide -This guide explains how to generate documentation for Onlyoffice Builder/Plugins API using the provided Python scripts: `generate_docs_json.py`, `generate_docs_plugins_json.py`, `generate_docs_md.py`. These scripts are used to create JSON and Markdown documentation for the `apiBuilder.js` files from the word, cell, and slide editors. +This guide explains how to generate documentation for Onlyoffice Builder and +Plugins (Methods/Events) API using the following Python scripts: + +- `office-api/generate_docs_json.py` +- `office-api/generate_docs_md.py` +- `plugins/generate_docs_methods_json.py` +- `plugins/generate_docs_methods_md.py` +- `plugins/generate_docs_events_json.py` +- `plugins/generate_docs_events_md.py` ## Requirements @@ -20,61 +27,112 @@ npm install ## Scripts Overview -### `generate_docs_json.py` +### `office-api/generate_docs_json.py` This script generates JSON documentation based on the `apiBuilder.js` files. - **Usage**: + ```bash python generate_docs_json.py output_path ``` - **Parameters**: - - `output_path` (optional): The directory where the JSON documentation will be saved. If not specified, the default path is `../../../../office-js-api-declarations/office-js-api`. + - `output_path` (optional): The directory where the JSON documentation will be + saved. If not specified, the default path is + `../../../../office-js-api-declarations/office-js-api`. -### `generate_docs_plugins_json.py` - -This script generates JSON documentation based on the `api_plugins.js` files. - -- **Usage**: - ```bash - python generate_docs_plugins_json.py output_path - ``` - -- **Parameters**: - - `output_path` (optional): The directory where the JSON documentation will be saved. If not specified, the default path is `../../../../office-js-api-declarations/office-js-api-plugins`. - -### `generate_docs_md.py` +### `office-api/generate_docs_md.py` This script generates Markdown documentation from the `apiBuilder.js` files. - **Usage**: + ```bash python generate_docs_md.py output_path ``` - **Parameters**: - - `output_path` (optional): The directory where the Markdown documentation will be saved. If not specified, the default path is `../../../../office-js-api/`. + - `output_path` (optional): The directory where the Markdown documentation will + be saved. If not specified, the default path is + `../../../../office-js-api/`. + +### `plugins/generate_docs_methods_json.py` + +This script generates JSON documentation based on the `api_plugins.js` files. + +- **Usage**: + + ```bash + python generate_docs_methods_json.py output_path + ``` + +- **Parameters**: + - `output_path` (optional): The directory where the JSON documentation will be + saved. If not specified, the default path is + `../../../../office-js-api-declarations/office-js-api-plugins`. + +### `plugins/generate_docs_events_json.py` + +This script generates JSON documentation based on the `plugin-events.js` files. + +- **Usage**: + + ```bash + python generate_docs_events_json.py output_path + ``` + +- **Parameters**: + - `output_path` (optional): The directory where the JSON documentation will be + saved. If not specified, the default path is + `../../../../office-js-api-declarations/office-js-api-plugins`. + +### `plugins/generate_docs_methods_md.py` + +This script generates Markdown documentation from the `api_plugins.js` files. + +- **Usage**: + + ```bash + python generate_docs_methods_md.py output_path + ``` + +- **Parameters**: + - `output_path` (optional): The directory where the Markdown documentation will + be saved. If not specified, the default path is + `../../../../office-js-api/`. + +### `plugins/generate_docs_events_md.py` + +This script generates Markdown documentation from the `plugin-events.js` files. + +- **Usage**: + + ```bash + python generate_docs_events_md.py output_path + ``` + +- **Parameters**: + - `output_path` (optional): The directory where the Markdown documentation will + be saved. If not specified, the default path is + `../../../../office-js-api/`. ## Example To generate JSON documentation with the default output path: + ```bash python generate_docs_json.py /path/to/save/json ``` -To generate JSON documentation with the default output path: -```bash -python generate_docs_plugins_json.py /path/to/save/json -``` - To generate Markdown documentation and specify a custom output path: + ```bash python generate_docs_md.py /path/to/save/markdown ``` ## Notes -- Make sure to have all necessary permissions to run these scripts and write to the specified directories. +- Make sure to have all necessary permissions to run these scripts and write to + the specified directories. - The output directories will be created if they do not exist. - diff --git a/scripts/sdkjs_common/jsdoc/Readme.txt b/scripts/sdkjs_common/jsdoc/Readme.txt new file mode 100644 index 0000000..e69de29 diff --git a/scripts/sdkjs_common/jsdoc/config/builder/slide.json b/scripts/sdkjs_common/jsdoc/config/builder/slide.json deleted file mode 100644 index 96b5dbf..0000000 --- a/scripts/sdkjs_common/jsdoc/config/builder/slide.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "source": { - "include": ["../../../../sdkjs/word/apiBuilder.js", "../../../../sdkjs/slide/apiBuilder.js"] - }, - "plugins": ["./correct_doclets.js"], - "opts": { - "destination": "./out", - "recurse": true, - "encoding": "utf8" - }, - "templates": { - "json": { - "pretty": true - } - } -} diff --git a/scripts/sdkjs_common/jsdoc/generate_docs_md_site.py b/scripts/sdkjs_common/jsdoc/generate_docs_md_site.py deleted file mode 100644 index ddb1b23..0000000 --- a/scripts/sdkjs_common/jsdoc/generate_docs_md_site.py +++ /dev/null @@ -1,586 +0,0 @@ -import os -import json -import re -import shutil -import argparse -import generate_docs_json - -# Configuration files -editors = { - "word": "text-document-api", - "cell": "spreadsheet-api", - "slide": "presentation-api", - "forms": "form-api" -} - -missing_examples = [] -used_enumerations = set() - -cur_editor_name = None - -_CODE_BLOCK_RE = re.compile(r'(```.*?```)', re.DOTALL) -_QSTRING_RE = re.compile(r'(["\'])(.*?)(? 1 else None - - if ref.startswith('/'): - # Handle reserved links using mapping - if ref in reserved_links: - url = reserved_links[ref] - display_text = label if label else ref - return f"[{display_text}]({url})" - else: - # If the link is not in the mapping, return the original construction - return match.group(0) - elif ref.startswith("global#"): - # Handle links to typedef (similar logic as before) - typedef_name = ref.split("#")[1] - used_enumerations.add(typedef_name) - display_text = label if label else typedef_name - return f"[{display_text}]({root}Enumeration/{typedef_name}.md)" - else: - # Handle links to class methods like ClassName#MethodName - try: - class_name, method_name = ref.split("#") - except ValueError: - return match.group(0) - display_text = label if label else ref # Keep the full notation, e.g., "Api#CreateSlide" - return f"[{display_text}]({root}{class_name}/Methods/{method_name}.md)" - - return re.sub(r'{@link\s+([^}]+)}', replace_link, text) - -def correct_description(string, root=''): - """ - Cleans or transforms specific tags in the doclet description: - - => ** (bold text) - - ... => πŸ’‘ ... - - {@link ...} is replaced with a Markdown link - - If the description is missing, returns a default value. - - All '\r' characters are replaced with '\n'. - """ - if string is None: - return 'No description provided.' - - # Line breaks - string = string.replace('\r', '\\\n') - - # Replace tags with Markdown bold formatting - string = re.sub(r'', '-**', string) - string = re.sub(r'', '**', string) - - # Replace tags with an icon and text - string = re.sub(r'(.*?)', r'πŸ’‘ \1', string, flags=re.DOTALL) - - # Process {@link ...} constructions - string = process_link_tags(string, root) - - return string - -def correct_default_value(value, enumerations, classes): - if value is None or value == '': - return '' - - if isinstance(value, bool): - value = "true" if value else "false" - else: - value = str(value) - - return generate_data_types_markdown([value], enumerations, classes) - -def remove_line_breaks(string): - return re.sub(r'[\r\n]+', ' ', string) - -# Convert Array. => T[] (including nested arrays). -def convert_jsdoc_array_to_ts(type_str: str) -> str: - """ - Recursively replaces 'Array.' with 'T[]', - handling nested arrays like 'Array.>' => 'string[][]'. - """ - pattern = re.compile(r'Array\.<([^>]+)>') - - while True: - match = pattern.search(type_str) - if not match: - break - - inner_type = match.group(1).strip() - # Recursively convert inner parts - inner_type = convert_jsdoc_array_to_ts(inner_type) - - # Replace the outer Array.<...> with ...[] - type_str = ( - type_str[:match.start()] - + f"{inner_type}[]" - + type_str[match.end():] - ) - - return type_str - -def escape_text_outside_code_blocks(markdown: str) -> str: - """ - Splits content by fenced code blocks, escapes MDX-unsafe characters - (<, >, {, }) only in the text outside those code blocks. - """ - # A regex to capture fenced code blocks with ``` - parts = re.split(r'(```.*?```)', markdown, flags=re.DOTALL) - - # Even indices (0, 2, 4, ...) are outside code blocks, - # odd indices (1, 3, 5, ...) are actual code blocks. - for i in range(0, len(parts), 2): - text = (parts[i] - .replace('<', '<') - .replace('>', '>') - .replace('{', '{') - .replace('}', '}')) - parts[i] = escape_brackets_in_quotes(text) - - return "".join(parts) - -def escape_brackets_in_quotes(text: str) -> str: - return re.sub( - r"(['\"])(.*?)(? str: - """ - Given a TypeScript-like type (e.g. "Drawing[][]"), return the - 'base' portion by stripping trailing "[]". For "Drawing[][]", - returns "Drawing". For "Array.", you'd convert it first - to "Drawing[]" then return "Drawing". - """ - while ts_type.endswith('[]'): - ts_type = ts_type[:-2] - return ts_type - -def generate_data_types_markdown(types, enumerations, classes, root='../../'): - """ - 1) Converts each type from JSDoc (e.g., Array.) to T[]. - 2) Processes union types by splitting them using '|'. - 3) Supports multidimensional arrays, e.g., (string|ApiRange|number)[]. - 4) If the base type matches the name of an enumeration or class, generates a link. - 5) The final types are joined using " | ". - """ - # Convert each type from JSDoc format to TypeScript format (e.g., T[]) - converted = [convert_jsdoc_array_to_ts(t) for t in types] - - # Set of primitive types - primitive_types = {"string", "number", "boolean", "null", "undefined", "any", "object", "false", "true", "json", "function", "{}"} - - def is_primitive(type): - if (type.lower() in primitive_types or - (type.startswith('"') and type.endswith('"')) or - (type.startswith("'") and type.endswith("'")) or - type.replace('.', '', 1).isdigit() or - (type.startswith('-') and type[1:].replace('.', '', 1).isdigit())): - return True - return False - - def link_if_known(ts_type): - ts_type = ts_type.strip() - # Count the number of array dimensions, e.g., "[][]" has 2 dimensions - array_dims = 0 - while ts_type.endswith("[]"): - array_dims += 1 - ts_type = ts_type[:-2].strip() - - # Process generic types, e.g., Object. - if ".<" in ts_type and ts_type.endswith(">"): - import re - m = re.match(r'^(.*?)\.<(.*)>$', ts_type) - if m: - base_part = m.group(1).strip() - generic_args_str = m.group(2).strip() - # Process the base part of the type - found = False - for enum in enumerations: - if enum['name'] == base_part: - used_enumerations.add(base_part) - base_result = f"[{base_part}]({root}Enumeration/{base_part}.md)" - found = True - break - if not found: - if base_part in classes: - base_result = f"[{base_part}]({root}{base_part}/{base_part}.md)" - elif is_primitive(base_part): - base_result = base_part - elif cur_editor_name == "forms": - base_result = f"[{base_part}]({root}../text-document-api/{base_part}/{base_part}.md)" - else: - print(f"Unknown type encountered: {base_part}") - base_result = base_part - # Split the generic parameters by commas and process each recursively - generic_args = [link_if_known(x) for x in generic_args_str.split(",")] - result = base_result + ".<" + ", ".join(generic_args) + ">" - result += "[]" * array_dims - return result - - # Process union types: if the type is enclosed in parentheses - if ts_type.startswith("(") and ts_type.endswith(")"): - inner = ts_type[1:-1].strip() - subtypes = [sub.strip() for sub in inner.split("|")] - if len(subtypes) == 1: - result = link_if_known(subtypes[0]) - else: - processed = [link_if_known(subtype) for subtype in subtypes] - result = "(" + " | ".join(processed) + ")" - result += "[]" * array_dims - return result - - # If not a generic or union type – process the base type - else: - base = ts_type - found = False - for enum in enumerations: - if enum['name'] == base: - used_enumerations.add(base) - result = f"[{base}]({root}Enumeration/{base}.md)" - found = True - break - if not found: - if base in classes: - result = f"[{base}]({root}{base}/{base}.md)" - elif is_primitive(base): - result = base - elif cur_editor_name == "forms": - result = f"[{base}]({root}../text-document-api/{base}/{base}.md)" - else: - print(f"Unknown type encountered: {base}") - result = base - result += "[]" * array_dims - return result - - # Apply link_if_known to each converted type - linked = [link_if_known(ts_t) for ts_t in converted] - - # Join results using " | " - param_types_md = r' | '.join(linked) - param_types_md = param_types_md.replace("|", r"\|") - - # Escape remaining angle brackets for generics - def replace_leftover_generics(match): - element = match.group(1).strip() - return f"<{element}>" - - param_types_md = re.sub(r'<([^<>]+)>', replace_leftover_generics, param_types_md) - - return param_types_md - - -def generate_class_markdown(class_name, methods, properties, enumerations, classes): - content = f"# {class_name}\n\nRepresents the {class_name} class.\n\n" - - content += generate_properties_markdown(properties, enumerations, classes) - - content += "\n## Methods\n\n" - content += "| Method | Returns | Description |\n" - content += "| ------ | ------- | ----------- |\n" - - for method in sorted(methods, key=lambda m: m['name']): - method_name = method['name'] - - # Get the type of return values - returns = method.get('returns', []) - if returns: - return_type_list = returns[0].get('type', {}).get('names', []) - returns_markdown = generate_data_types_markdown(return_type_list, enumerations, classes, '../') - else: - returns_markdown = "None" - - # Processing the method description - description = remove_line_breaks(correct_description(method.get('description', 'No description provided.'), '../')) - - # Form a link to the method document - method_link = f"[{method_name}](./Methods/{method_name}.md)" - - content += f"| {method_link} | {returns_markdown} | {description} |\n" - - return escape_text_outside_code_blocks(content) - -def generate_method_markdown(method, enumerations, classes, example_editor_name): - method_name = method['name'] - description = method.get('description', 'No description provided.') - description = correct_description(description, '../../') - params = method.get('params', []) - returns = method.get('returns', []) - example = method.get('example', '') - memberof = method.get('memberof', '') - - content = f"# {method_name}\n\n{description}\n\n" - - # Syntax - param_list = ', '.join([param['name'] for param in params if '.' not in param['name']]) if params else '' - content += f"## Syntax\n\n```javascript\nexpression.{method_name}({param_list});\n```\n\n" - if memberof: - content += f"`expression` - A variable that represents a [{memberof}](../{memberof}.md) class.\n\n" - - # Parameters - content += "## Parameters\n\n" - if params: - content += "| **Name** | **Required/Optional** | **Data type** | **Default** | **Description** |\n" - content += "| ------------- | ------------- | ------------- | ------------- | ------------- |\n" - for param in params: - param_name = param.get('name', 'Unnamed') - param_types = param.get('type', {}).get('names', []) if param.get('type') else [] - param_types_md = generate_data_types_markdown(param_types, enumerations, classes) - param_desc = remove_line_breaks(correct_description(param.get('description', 'No description provided.'), '../../')) - param_required = "Required" if not param.get('optional') else "Optional" - param_default = correct_default_value(param.get('defaultvalue', ''), enumerations, classes) - - content += f"| {param_name} | {param_required} | {param_types_md} | {param_default} | {param_desc} |\n" - else: - content += "This method doesn't have any parameters.\n" - - # Returns - content += "\n## Returns\n\n" - if returns: - return_type_list = returns[0].get('type', {}).get('names', []) - return_type_md = generate_data_types_markdown(return_type_list, enumerations, classes) - content += return_type_md - else: - content += "This method doesn't return any data." - - # Example - if example: - # Separate comment and code, remove JS comments - if '```js' in example: - comment, code = example.split('```js', 1) - comment = remove_js_comments(comment) - content += f"\n\n## Example\n\n{comment}\n\n```javascript {example_editor_name}\n{code.strip()}\n" - else: - # If there's no triple-backtick structure, just show it as code - cleaned_example = remove_js_comments(example) - content += f"\n\n## Example\n\n```javascript {example_editor_name}\n{cleaned_example}\n```\n" - - return escape_text_outside_code_blocks(content) - -def generate_properties_markdown(properties, enumerations, classes, root='../'): - if properties is None: - return '' - - content = "## Properties\n\n" - content += "| Name | Type | Description |\n" - content += "| ---- | ---- | ----------- |\n" - - for prop in sorted(properties, key=lambda m: m['name']): - prop_name = prop['name'] - prop_description = prop.get('description', 'No description provided.') - prop_description = remove_line_breaks(correct_description(prop_description, root)) - prop_types = prop['type']['names'] if prop.get('type') else [] - param_types_md = generate_data_types_markdown(prop_types, enumerations, classes, root) - content += f"| {prop_name} | {param_types_md} | {prop_description} |\n" - - # Escape outside code blocks - return escape_text_outside_code_blocks(content) - -def generate_enumeration_markdown(enumeration, enumerations, classes, example_editor_name): - enum_name = enumeration['name'] - - if enum_name not in used_enumerations: - return None - - description = enumeration.get('description', 'No description provided.') - description = correct_description(description, '../') - example = enumeration.get('example', '') - - content = f"# {enum_name}\n\n{description}\n\n" - - ptype = enumeration['type']['parsedType'] - if ptype['type'] == 'TypeUnion': - enum_empty = True # is empty enum - - content += "## Type\n\nEnumeration\n\n" - content += "## Values\n\n" - # Each top-level name in the union - for raw_t in enumeration['type']['names']: - ts_t = convert_jsdoc_array_to_ts(raw_t) - - # Attempt linking: we compare the raw type to enumerations/classes - if any(enum['name'] == raw_t for enum in enumerations): - used_enumerations.add(raw_t) - content += f"- [{ts_t}](../Enumeration/{raw_t}.md)\n" - enum_empty = False - elif raw_t in classes: - content += f"- [{ts_t}](../{raw_t}/{raw_t}.md)\n" - enum_empty = False - elif ts_t.find('Api') == -1: - content += f"- {ts_t}\n" - enum_empty = False - - if enum_empty == True: - return None - elif enumeration['properties'] is not None: - content += "## Type\n\nObject\n\n" - content += generate_properties_markdown(enumeration['properties'], enumerations, classes) - else: - content += "## Type\n\n" - # If it's not a union and has no properties, simply print the type(s). - types = enumeration['type']['names'] - t_md = generate_data_types_markdown(types, enumerations, classes) - content += t_md + "\n\n" - - # Example - if example: - if '```js' in example: - comment, code = example.split('```js', 1) - comment = remove_js_comments(comment) - content += f"\n\n## Example\n\n{comment}\n\n```javascript {example_editor_name}\n{code.strip()}\n" - else: - # If there's no triple-backtick structure - cleaned_example = remove_js_comments(example) - content += f"\n\n## Example\n\n```javascript {example_editor_name}\n{cleaned_example}\n```\n" - - return escape_text_outside_code_blocks(content) - -def process_doclets(data, output_dir, editor_name): - global cur_editor_name - cur_editor_name = editor_name - - classes = {} - classes_props = {} - enumerations = [] - editor_dir = os.path.join(output_dir, editors[editor_name]) - example_editor_name = 'editor-' - - if editor_name == 'word': - example_editor_name += 'docx' - elif editor_name == 'forms': - example_editor_name += 'pdf' - elif editor_name == 'slide': - example_editor_name += 'pptx' - elif editor_name == 'cell': - example_editor_name += 'xlsx' - - for doclet in data: - if doclet['kind'] == 'class': - class_name = doclet['name'] - if class_name: - if class_name not in classes: - classes[class_name] = [] - classes_props[class_name] = doclet.get('properties', None) - elif doclet['kind'] == 'function': - class_name = doclet.get('memberof') - if class_name: - if class_name not in classes: - classes[class_name] = [] - classes[class_name].append(doclet) - elif doclet['kind'] == 'typedef': - enumerations.append(doclet) - - # Process classes - for class_name, methods in classes.items(): - if (len(methods) == 0): - continue - - class_dir = os.path.join(editor_dir, class_name) - methods_dir = os.path.join(class_dir, 'Methods') - os.makedirs(methods_dir, exist_ok=True) - - # Write class file - class_content = generate_class_markdown( - class_name, - methods, - classes_props[class_name], - enumerations, - classes - ) - write_markdown_file(os.path.join(class_dir, f"{class_name}.md"), class_content) - - # Write method files - for method in methods: - method_file_path = os.path.join(methods_dir, f"{method['name']}.md") - method_content = generate_method_markdown(method, enumerations, classes, example_editor_name) - write_markdown_file(method_file_path, method_content) - - if not method.get('example', ''): - missing_examples.append(os.path.relpath(method_file_path, output_dir)) - - # Process enumerations - enum_dir = os.path.join(editor_dir, 'Enumeration') - os.makedirs(enum_dir, exist_ok=True) - - # idle run - prev_used_count = -1 - while len(used_enumerations) != prev_used_count: - prev_used_count = len(used_enumerations) - for enum in [e for e in enumerations if e['name'] in used_enumerations]: - enum_content = generate_enumeration_markdown(enum, enumerations, classes, example_editor_name) - - for enum in enumerations: - enum_file_path = os.path.join(enum_dir, f"{enum['name']}.md") - enum_content = generate_enumeration_markdown(enum, enumerations, classes, example_editor_name) - if enum_content is None: - continue - - write_markdown_file(enum_file_path, enum_content) - if not enum.get('example', ''): - missing_examples.append(os.path.relpath(enum_file_path, output_dir)) - -def generate(output_dir): - print('Generating Markdown documentation...') - - generate_docs_json.generate(output_dir + 'tmp_json', md=True) - for editor_name, folder_name in editors.items(): - input_file = os.path.join(output_dir + '/tmp_json', editor_name + ".json") - - editor_folder_path = os.path.join(output_dir, folder_name) - for folder_name in os.listdir(editor_folder_path): - folder_path_to_del = os.path.join(editor_folder_path, folder_name) - if os.path.isdir(folder_path_to_del): - shutil.rmtree(folder_path_to_del, ignore_errors=True) - - data = load_json(input_file) - used_enumerations.clear() - process_doclets(data, output_dir, editor_name) - - shutil.rmtree(output_dir + 'tmp_json') - print('Done') - -if __name__ == "__main__": - parser = argparse.ArgumentParser(description="Generate documentation") - parser.add_argument( - "destination", - type=str, - help="Destination directory for the generated documentation", - nargs='?', # Indicates the argument is optional - default="../../../../api.onlyoffice.com/site/docs/office-api/usage-api/" # Default value - ) - args = parser.parse_args() - generate(args.destination) - print("START_MISSING_EXAMPLES") - print(",".join(missing_examples)) - print("END_MISSING_EXAMPLES") diff --git a/scripts/sdkjs_common/jsdoc/generate_docs_plugins_md_site.py b/scripts/sdkjs_common/jsdoc/generate_docs_plugins_md_site.py deleted file mode 100644 index 4de086a..0000000 --- a/scripts/sdkjs_common/jsdoc/generate_docs_plugins_md_site.py +++ /dev/null @@ -1,634 +0,0 @@ -import os -import json -import re -import shutil -import argparse -import generate_docs_plugins_json - -# Configuration files -editors = { - "word": "text-document-api", - "cell": "spreadsheet-api", - "slide": "presentation-api", - "forms": "form-api" -} - -missing_examples = [] -used_enumerations = set() - -cur_editor_name = None - -_CODE_BLOCK_RE = re.compile(r'(```.*?```)', re.DOTALL) -_QSTRING_RE = re.compile(r'(["\'])(.*?)(? 1 else None - - if ref.startswith('/'): - # Handle reserved links using mapping - if ref in reserved_links: - url = reserved_links[ref] - display_text = label if label else ref - return f"[{display_text}]({url})" - else: - # If the link is not in the mapping, return the original construction - return match.group(0) - elif ref.startswith("global#"): - # Handle links to typedef (similar logic as before) - typedef_name = ref.split("#")[1] - used_enumerations.add(typedef_name) - display_text = label if label else typedef_name - return f"[{display_text}]({root}Enumeration/{typedef_name}.md)" - else: - # Handle links to class methods like ClassName#MethodName - try: - class_name, method_name = ref.split("#") - except ValueError: - return match.group(0) - display_text = label if label else ref # Keep the full notation, e.g., "Api#CreateSlide" - return f"[{display_text}]({root}{class_name}/Methods/{method_name}.md)" - - return re.sub(r'{@link\s+([^}]+)}', replace_link, text) - -def correct_description(string, root=''): - """ - Cleans or transforms specific tags in the doclet description: - - => ** (bold text) - - ... => πŸ’‘ ... - - {@link ...} is replaced with a Markdown link - - If the description is missing, returns a default value. - - All '\r' characters are replaced with '\n'. - """ - if string is None: - return 'No description provided.' - - # Line breaks - string = string.replace('\r', '\\\n') - - # Replace tags with Markdown bold formatting - string = re.sub(r'', '-**', string) - string = re.sub(r'', '**', string) - - # Replace tags with an icon and text - string = re.sub(r'(.*?)', r'πŸ’‘ \1', string, flags=re.DOTALL) - - # Process {@link ...} constructions - string = process_link_tags(string, root) - - return string - -def correct_default_value(value, enumerations, classes): - if value is None or value == '': - return '' - - if value == True: - value = "true" - elif value == False: - value = "false" - else: - value = str(value) - - return generate_data_types_markdown([value], enumerations, classes) - -def remove_line_breaks(string): - return re.sub(r'[\r\n]+', ' ', string) - -# Convert Array. => T[] (including nested arrays). -def convert_jsdoc_array_to_ts(type_str: str) -> str: - """ - Recursively replaces 'Array.' with 'T[]', - handling nested arrays like 'Array.>' => 'string[][]'. - """ - pattern = re.compile(r'Array\.<([^>]+)>') - - while True: - match = pattern.search(type_str) - if not match: - break - - inner_type = match.group(1).strip() - # Recursively convert inner parts - inner_type = convert_jsdoc_array_to_ts(inner_type) - - # Replace the outer Array.<...> with ...[] - type_str = ( - type_str[:match.start()] - + f"{inner_type}[]" - + type_str[match.end():] - ) - - return type_str - -def escape_text_outside_code_blocks(markdown: str) -> str: - """ - Splits content by fenced code blocks, escapes MDX-unsafe characters - (<, >, {, }) only in the text outside those code blocks. - """ - # A regex to capture fenced code blocks with ``` - parts = re.split(r'(```.*?```)', markdown, flags=re.DOTALL) - - # Even indices (0, 2, 4, ...) are outside code blocks, - # odd indices (1, 3, 5, ...) are actual code blocks. - for i in range(0, len(parts), 2): - text = (parts[i] - .replace('<', '<') - .replace('>', '>') - .replace('{', '{') - .replace('}', '}')) - parts[i] = escape_brackets_in_quotes(text) - - return "".join(parts) - -def escape_brackets_in_quotes(text: str) -> str: - return re.sub( - r"(['\"])(.*?)(? str: - """ - Given a TypeScript-like type (e.g. "Drawing[][]"), return the - 'base' portion by stripping trailing "[]". For "Drawing[][]", - returns "Drawing". For "Array.", you'd convert it first - to "Drawing[]" then return "Drawing". - """ - while ts_type.endswith('[]'): - ts_type = ts_type[:-2] - return ts_type - -def generate_data_types_markdown(types, enumerations, classes, root='../../'): - """ - 1) Converts each type from JSDoc (e.g., Array.) to T[]. - 2) Processes union types by splitting them using '|'. - 3) Supports multidimensional arrays, e.g., (string|ApiRange|number)[]. - 4) If the base type matches the name of an enumeration or class, generates a link. - 5) The final types are joined using " | ". - """ - # Convert each type from JSDoc format to TypeScript format (e.g., T[]) - converted = [convert_jsdoc_array_to_ts(t) for t in types] - - # Set of primitive types - primitive_types = {"string", "number", "boolean", "null", "undefined", "any", "object", "false", "true", "json", "function", "{}"} - - def is_primitive(type): - if (type.lower() in primitive_types or - (type.startswith('"') and type.endswith('"')) or - (type.startswith("'") and type.endswith("'")) or - type.replace('.', '', 1).isdigit() or - (type.startswith('-') and type[1:].replace('.', '', 1).isdigit())): - return True - return False - - def link_if_known(ts_type): - ts_type = ts_type.strip() - # Count the number of array dimensions, e.g., "[][]" has 2 dimensions - array_dims = 0 - while ts_type.endswith("[]"): - array_dims += 1 - ts_type = ts_type[:-2].strip() - - # Process generic types, e.g., Object. - if ".<" in ts_type and ts_type.endswith(">"): - import re - m = re.match(r'^(.*?)\.<(.*)>$', ts_type) - if m: - base_part = m.group(1).strip() - generic_args_str = m.group(2).strip() - # Process the base part of the type - found = False - for enum in enumerations: - if enum['name'] == base_part: - used_enumerations.add(base_part) - base_result = f"[{base_part}]({root}Enumeration/{base_part}.md)" - found = True - break - if not found: - if base_part in classes: - base_result = f"[{base_part}]({root}{base_part}/{base_part}.md)" - elif is_primitive(base_part): - base_result = base_part - elif cur_editor_name == "forms": - base_result = f"[{base_part}]({root}../text-document-api/{base_part}/{base_part}.md)" - else: - print(f"Unknown type encountered: {base_part}") - base_result = base_part - # Split the generic parameters by commas and process each recursively - generic_args = [link_if_known(x) for x in generic_args_str.split(",")] - result = base_result + ".<" + ", ".join(generic_args) + ">" - result += "[]" * array_dims - return result - - # Process union types: if the type is enclosed in parentheses - if ts_type.startswith("(") and ts_type.endswith(")"): - inner = ts_type[1:-1].strip() - subtypes = [sub.strip() for sub in inner.split("|")] - if len(subtypes) == 1: - result = link_if_known(subtypes[0]) - else: - processed = [link_if_known(subtype) for subtype in subtypes] - result = "(" + " | ".join(processed) + ")" - result += "[]" * array_dims - return result - - # If not a generic or union type – process the base type - else: - base = ts_type - found = False - for enum in enumerations: - if enum['name'] == base: - used_enumerations.add(base) - result = f"[{base}]({root}Enumeration/{base}.md)" - found = True - break - if not found: - if base in classes: - result = f"[{base}]({root}{base}/{base}.md)" - elif is_primitive(base): - result = base - elif cur_editor_name == "forms": - result = f"[{base}]({root}../text-document-api/{base}/{base}.md)" - else: - print(f"Unknown type encountered: {base}") - result = base - result += "[]" * array_dims - return result - - # Apply link_if_known to each converted type - linked = [link_if_known(ts_t) for ts_t in converted] - - # Join results using " | " - param_types_md = r' | '.join(linked) - param_types_md = param_types_md.replace("|", r"\|") - - # Escape remaining angle brackets for generics - def replace_leftover_generics(match): - element = match.group(1).strip() - return f"<{element}>" - - param_types_md = re.sub(r'<([^<>]+)>', replace_leftover_generics, param_types_md) - - return param_types_md - -def generate_class_markdown(class_name, methods, properties, enumerations, classes): - content = f"# {class_name}\n\nRepresents the {class_name} class.\n\n" - - content += generate_properties_markdown(properties, enumerations, classes) - - content += "\n## Methods\n\n" - content += "| Method | Returns | Description |\n" - content += "| ------ | ------- | ----------- |\n" - - for method in sorted(methods, key=lambda m: m['name']): - method_name = method['name'] - - # Get the type of return values - returns = method.get('returns', []) - if returns: - return_type_list = returns[0].get('type', {}).get('names', []) - returns_markdown = generate_data_types_markdown(return_type_list, enumerations, classes, '../') - else: - returns_markdown = "None" - - # Processing the method description - description = remove_line_breaks(correct_description(method.get('description', 'No description provided.'), '../')) - - # Form a link to the method document - method_link = f"[{method_name}](./Methods/{method_name}.md)" - - content += f"| {method_link} | {returns_markdown} | {description} |\n" - - return escape_text_outside_code_blocks(content) - -def generate_method_markdown(method, enumerations, classes): - """ - Generates Markdown for a method doclet, relying only on `method['examples']` - (array of strings). Ignores any single `method['example']` field. - """ - - method_name = method['name'] - description = method.get('description', 'No description provided.') - description = correct_description(description, '../../') - params = method.get('params', []) - returns = method.get('returns', []) - memberof = method.get('memberof', '') - - # Use the 'examples' array only - examples = method.get('examples', []) - - content = f"# {method_name}\n\n{description}\n\n" - - # Syntax - param_list = ', '.join([param['name'] for param in params if '.' not in param['name']]) if params else '' - content += f"## Syntax\n\n```javascript\nexpression.{method_name}({param_list});\n```\n\n" - if memberof: - content += f"`expression` - A variable that represents a [{memberof}](../{memberof}.md) class.\n\n" - - # Parameters - content += "## Parameters\n\n" - if params: - content += "| **Name** | **Required/Optional** | **Data type** | **Default** | **Description** |\n" - content += "| ------------- | ------------- | ------------- | ------------- | ------------- |\n" - for param in params: - param_name = param.get('name', 'Unnamed') - param_types = param.get('type', {}).get('names', []) if param.get('type') else [] - param_types_md = generate_data_types_markdown(param_types, enumerations, classes) - param_desc = remove_line_breaks(correct_description(param.get('description', 'No description provided.'), '../../')) - param_required = "Required" if not param.get('optional') else "Optional" - param_default = correct_default_value(param.get('defaultvalue', ''), enumerations, classes) - - content += f"| {param_name} | {param_required} | {param_types_md} | {param_default} | {param_desc} |\n" - else: - content += "This method doesn't have any parameters.\n" - - # Returns - content += "\n## Returns\n\n" - if returns: - return_type_list = returns[0].get('type', {}).get('names', []) - return_type_md = generate_data_types_markdown(return_type_list, enumerations, classes) - content += return_type_md - else: - content += "This method doesn't return any data." - - # Process examples array - if examples: - if len(examples) > 1: - content += "\n\n## Examples\n\n" - else: - content += "\n\n## Example\n\n" - - for i, ex_line in enumerate(examples, start=1): - # Remove JS comments - cleaned_example = remove_js_comments(ex_line).strip() - - # Attempt splitting if the user used ```js - if '```js' in cleaned_example: - comment, code = cleaned_example.split('```js', 1) - comment = comment.strip() - code = code.strip() - if len(examples) > 1: - content += f"**Example {i}:**\n\n{comment}\n\n" - - content += f"```javascript\n{code}\n```\n" - else: - if len(examples) > 1: - content += f"**Example {i}:**\n\n{comment}\n\n" - # No special fences, just show as code - content += f"```javascript\n{cleaned_example}\n```\n" - - return escape_text_outside_code_blocks(content) - -def generate_properties_markdown(properties, enumerations, classes, root='../'): - if properties is None: - return '' - - content = "## Properties\n\n" - content += "| Name | Type | Description |\n" - content += "| ---- | ---- | ----------- |\n" - - for prop in sorted(properties, key=lambda m: m['name']): - prop_name = prop['name'] - prop_description = prop.get('description', 'No description provided.') - prop_description = remove_line_breaks(correct_description(prop_description)) - prop_types = prop['type']['names'] if prop.get('type') else [] - param_types_md = generate_data_types_markdown(prop_types, enumerations, classes, root) - content += f"| {prop_name} | {param_types_md} | {prop_description} |\n" - - # Escape outside code blocks - return escape_text_outside_code_blocks(content) - -def generate_enumeration_markdown(enumeration, enumerations, classes): - """ - Generates Markdown documentation for a 'typedef' doclet. - This version only works with `enumeration['examples']` (an array of strings), - ignoring any single `enumeration['examples']` field. - """ - enum_name = enumeration['name'] - - if enum_name not in used_enumerations: - return None - - description = enumeration.get('description', 'No description provided.') - description = correct_description(description, '../') - - # Only use the 'examples' array - examples = enumeration.get('examples', []) - - content = f"# {enum_name}\n\n{description}\n\n" - - parsed_type = enumeration['type'].get('parsedType') - if not parsed_type: - # If parsedType is missing, just list 'type.names' if available - type_names = enumeration['type'].get('names', []) - if type_names: - content += "## Type\n\n" - t_md = generate_data_types_markdown(type_names, enumerations, classes) - content += t_md + "\n\n" - else: - ptype = parsed_type['type'] - - # 1) Handle TypeUnion - if ptype == 'TypeUnion': - content += "## Type\n\nEnumeration\n\n" - content += "## Values\n\n" - for raw_t in enumeration['type']['names']: - # Attempt linking - if any(enum['name'] == raw_t for enum in enumerations): - used_enumerations.add(raw_t) - content += f"- [{raw_t}](../Enumeration/{raw_t}.md)\n" - elif raw_t in classes: - content += f"- [{raw_t}](../{raw_t}/{raw_t}.md)\n" - else: - content += f"- {raw_t}\n" - - # 2) Handle TypeApplication (e.g. Object.) - elif ptype == 'TypeApplication': - content += "## Type\n\nObject\n\n" - type_names = enumeration['type'].get('names', []) - if type_names: - t_md = generate_data_types_markdown(type_names, enumerations, classes) - content += f"**Type:** {t_md}\n\n" - - # 3) If properties are present, treat it like an object - if enumeration.get('properties') is not None: - content += generate_properties_markdown(enumeration['properties'], enumerations, classes) - - # 4) If it's neither TypeUnion nor TypeApplication, just output the type names - if ptype not in ('TypeUnion', 'TypeApplication'): - type_names = enumeration['type'].get('names', []) - if type_names: - content += "## Type\n\n" - t_md = generate_data_types_markdown(type_names, enumerations, classes) - content += t_md + "\n\n" - - # Process examples array - if examples: - if len(examples) > 1: - content += "\n\n## Examples\n\n" - else: - content += "\n\n## Example\n\n" - - for i, ex_line in enumerate(examples, start=1): - # Remove JS comments - cleaned_example = remove_js_comments(ex_line).strip() - - # Attempt splitting if the user used ```js - if '```js' in cleaned_example: - comment, code = cleaned_example.split('```js', 1) - comment = comment.strip() - code = code.strip() - if len(examples) > 1: - content += f"**Example {i}:**\n\n{comment}\n\n" - - content += f"```javascript\n{code}\n```\n" - else: - if len(examples) > 1: - content += f"**Example {i}:**\n\n{comment}\n\n" - # No special fences, just show as code - content += f"```javascript\n{cleaned_example}\n```\n" - - return escape_text_outside_code_blocks(content) - -def process_doclets(data, output_dir, editor_name): - global cur_editor_name - cur_editor_name = editor_name - - classes = {} - classes_props = {} - enumerations = [] - editor_dir = os.path.join(output_dir, editors[editor_name]) - - for doclet in data: - if doclet['kind'] == 'class': - class_name = doclet['name'] - if class_name: - if class_name not in classes: - classes[class_name] = [] - classes_props[class_name] = doclet.get('properties', None) - elif doclet['kind'] == 'function': - class_name = doclet.get('memberof') - if class_name: - if class_name not in classes: - classes[class_name] = [] - classes[class_name].append(doclet) - elif doclet['kind'] == 'typedef': - enumerations.append(doclet) - - # Process classes - for class_name, methods in classes.items(): - if (len(methods) == 0): - continue - - class_dir = os.path.join(editor_dir, class_name) - methods_dir = os.path.join(class_dir, 'Methods') - os.makedirs(methods_dir, exist_ok=True) - - # Write class file - class_content = generate_class_markdown( - class_name, - methods, - classes_props[class_name], - enumerations, - classes - ) - write_markdown_file(os.path.join(class_dir, f"{class_name}.md"), class_content) - - # Write method files - for method in methods: - method_file_path = os.path.join(methods_dir, f"{method['name']}.md") - method_content = generate_method_markdown(method, enumerations, classes) - write_markdown_file(method_file_path, method_content) - - if not method.get('examples', ''): - missing_examples.append(os.path.relpath(method_file_path, output_dir)) - - # Process enumerations - enum_dir = os.path.join(editor_dir, 'Enumeration') - os.makedirs(enum_dir, exist_ok=True) - - # idle run - prev_used_count = -1 - while len(used_enumerations) != prev_used_count: - prev_used_count = len(used_enumerations) - for enum in [e for e in enumerations if e['name'] in used_enumerations]: - enum_content = generate_enumeration_markdown(enum, enumerations, classes) - - for enum in enumerations: - enum_file_path = os.path.join(enum_dir, f"{enum['name']}.md") - enum_content = generate_enumeration_markdown(enum, enumerations, classes) - if enum_content is None: - continue - - write_markdown_file(enum_file_path, enum_content) - if not enum.get('examples', ''): - missing_examples.append(os.path.relpath(enum_file_path, output_dir)) - -def generate(output_dir): - print('Generating Markdown documentation...') - - if output_dir[-1] == '/': - output_dir = output_dir[:-1] - - generate_docs_plugins_json.generate(output_dir + '/tmp_json', md=True) - for editor_name, folder_name in editors.items(): - input_file = os.path.join(output_dir + '/tmp_json', editor_name + ".json") - - editor_folder_path = os.path.join(output_dir, folder_name) - for folder_name in os.listdir(editor_folder_path): - folder_path_to_del = os.path.join(editor_folder_path, folder_name) - if os.path.isdir(folder_path_to_del): - shutil.rmtree(folder_path_to_del, ignore_errors=True) - - data = load_json(input_file) - used_enumerations.clear() - process_doclets(data, output_dir, editor_name) - - shutil.rmtree(output_dir + '/tmp_json') - print('Done') - -if __name__ == "__main__": - parser = argparse.ArgumentParser(description="Generate documentation") - parser.add_argument( - "destination", - type=str, - help="Destination directory for the generated documentation", - nargs='?', # Indicates the argument is optional - default="../../../../api.onlyoffice.com/site/docs/plugin-and-macros/interacting-with-editors/methods/" # Default value - ) - args = parser.parse_args() - generate(args.destination) - print("START_MISSING_EXAMPLES") - print(",".join(missing_examples)) - print("END_MISSING_EXAMPLES") diff --git a/scripts/sdkjs_common/jsdoc/config/builder/cell.json b/scripts/sdkjs_common/jsdoc/office-api/config/cell.json similarity index 59% rename from scripts/sdkjs_common/jsdoc/config/builder/cell.json rename to scripts/sdkjs_common/jsdoc/office-api/config/cell.json index 2403a82..5b86ac8 100644 --- a/scripts/sdkjs_common/jsdoc/config/builder/cell.json +++ b/scripts/sdkjs_common/jsdoc/office-api/config/cell.json @@ -1,6 +1,6 @@ { "source": { - "include": ["../../../../sdkjs/word/apiBuilder.js", "../../../../sdkjs/slide/apiBuilder.js", "../../../../sdkjs/cell/apiBuilder.js"] + "include": ["../../../../../sdkjs/word/apiBuilder.js", "../../../../../sdkjs/slide/apiBuilder.js", "../../../../../sdkjs/cell/apiBuilder.js"] }, "plugins": ["./correct_doclets.js"], "opts": { diff --git a/scripts/sdkjs_common/jsdoc/config/builder/correct_doclets.js b/scripts/sdkjs_common/jsdoc/office-api/config/correct_doclets.js similarity index 100% rename from scripts/sdkjs_common/jsdoc/config/builder/correct_doclets.js rename to scripts/sdkjs_common/jsdoc/office-api/config/correct_doclets.js diff --git a/scripts/sdkjs_common/jsdoc/config/builder/forms.json b/scripts/sdkjs_common/jsdoc/office-api/config/forms.json similarity index 67% rename from scripts/sdkjs_common/jsdoc/config/builder/forms.json rename to scripts/sdkjs_common/jsdoc/office-api/config/forms.json index d39d531..bfbda89 100644 --- a/scripts/sdkjs_common/jsdoc/config/builder/forms.json +++ b/scripts/sdkjs_common/jsdoc/office-api/config/forms.json @@ -1,6 +1,6 @@ { "source": { - "include": ["../../../../sdkjs/word/apiBuilder.js", "../../../../sdkjs-forms/apiBuilder.js"] + "include": ["../../../../../sdkjs/word/apiBuilder.js", "../../../../../sdkjs-forms/apiBuilder.js"] }, "plugins": ["./correct_doclets.js"], "opts": { diff --git a/scripts/sdkjs_common/jsdoc/config/plugins/forms.json b/scripts/sdkjs_common/jsdoc/office-api/config/pdf.json similarity index 78% rename from scripts/sdkjs_common/jsdoc/config/plugins/forms.json rename to scripts/sdkjs_common/jsdoc/office-api/config/pdf.json index 65cb6a2..30ec3a8 100644 --- a/scripts/sdkjs_common/jsdoc/config/plugins/forms.json +++ b/scripts/sdkjs_common/jsdoc/office-api/config/pdf.json @@ -1,6 +1,6 @@ { "source": { - "include": ["../../../../sdkjs-forms/apiPlugins.js"] + "include": ["../../../../../sdkjs/pdf/apiBuilder.js"] }, "plugins": ["./correct_doclets.js"], "opts": { diff --git a/scripts/sdkjs_common/jsdoc/config/plugins/slide.json b/scripts/sdkjs_common/jsdoc/office-api/config/slide.json similarity index 67% rename from scripts/sdkjs_common/jsdoc/config/plugins/slide.json rename to scripts/sdkjs_common/jsdoc/office-api/config/slide.json index d015171..8a126b7 100644 --- a/scripts/sdkjs_common/jsdoc/config/plugins/slide.json +++ b/scripts/sdkjs_common/jsdoc/office-api/config/slide.json @@ -1,6 +1,6 @@ { "source": { - "include": ["../../../../sdkjs/slide/api_plugins.js"] + "include": ["../../../../../sdkjs/word/apiBuilder.js", "../../../../../sdkjs/slide/apiBuilder.js"] }, "plugins": ["./correct_doclets.js"], "opts": { diff --git a/scripts/sdkjs_common/jsdoc/config/builder/word.json b/scripts/sdkjs_common/jsdoc/office-api/config/word.json similarity index 78% rename from scripts/sdkjs_common/jsdoc/config/builder/word.json rename to scripts/sdkjs_common/jsdoc/office-api/config/word.json index 3b90c0a..8deb05a 100644 --- a/scripts/sdkjs_common/jsdoc/config/builder/word.json +++ b/scripts/sdkjs_common/jsdoc/office-api/config/word.json @@ -1,6 +1,6 @@ { "source": { - "include": ["../../../../sdkjs/word/apiBuilder.js"] + "include": ["../../../../../sdkjs/word/apiBuilder.js"] }, "plugins": ["./correct_doclets.js"], "opts": { diff --git a/scripts/sdkjs_common/jsdoc/generate_docs_json.py b/scripts/sdkjs_common/jsdoc/office-api/generate_docs_json.py similarity index 96% rename from scripts/sdkjs_common/jsdoc/generate_docs_json.py rename to scripts/sdkjs_common/jsdoc/office-api/generate_docs_json.py index 0ccb48a..b175a44 100644 --- a/scripts/sdkjs_common/jsdoc/generate_docs_json.py +++ b/scripts/sdkjs_common/jsdoc/office-api/generate_docs_json.py @@ -5,14 +5,14 @@ import argparse import re import platform -root = '../../../..' +root = '../../../../..' # Configuration files configs = [ - "./config/builder/word.json", - "./config/builder/cell.json", - "./config/builder/slide.json", - "./config/builder/forms.json" + "./config/word.json", + "./config/cell.json", + "./config/slide.json", + "./config/forms.json" ] editors_maps = { diff --git a/scripts/sdkjs_common/jsdoc/generate_docs_md.py b/scripts/sdkjs_common/jsdoc/office-api/generate_docs_md.py similarity index 95% rename from scripts/sdkjs_common/jsdoc/generate_docs_md.py rename to scripts/sdkjs_common/jsdoc/office-api/generate_docs_md.py index c9e8cdd..6693fff 100644 --- a/scripts/sdkjs_common/jsdoc/generate_docs_md.py +++ b/scripts/sdkjs_common/jsdoc/office-api/generate_docs_md.py @@ -39,8 +39,8 @@ def process_link_tags(text, root=''): For a method, if an alias is not specified, the name is left in the format 'Class#Method'. """ reserved_links = { - '/docbuilder/global#ShapeType': f"{'../../../' if root == '' else '../../' if root == '../' else root}text-document-api/Enumeration/ShapeType.md", - '/plugin/config': 'https://api.onlyoffice.com/docs/plugin-and-macros/structure/manifest/', + '/docbuilder/global#ShapeType': f"{'../../../../../../' if root == '' else '../../../../../' if root == '../' else root}docs/office-api/usage-api/text-document-api/Enumeration/ShapeType.md", + '/plugin/config': 'https://api.onlyoffice.com/docs/plugin-and-macros/structure/configuration/', '/docbuilder/basic': 'https://api.onlyoffice.com/docs/office-api/usage-api/text-document-api/' } @@ -76,7 +76,7 @@ def process_link_tags(text, root=''): return re.sub(r'{@link\s+([^}]+)}', replace_link, text) -def correct_description(string, root=''): +def correct_description(string, root='', isInTable=False): """ Cleans or transforms specific tags in the doclet description: - => ** (bold text) @@ -88,8 +88,9 @@ def correct_description(string, root=''): if string is None: return 'No description provided.' - # Line breaks - string = string.replace('\r', '\\\n') + if False == isInTable: + # Line breaks + string = string.replace('\r', '\\\n') # Replace tags with Markdown bold formatting string = re.sub(r'', '-**', string) @@ -195,7 +196,7 @@ def generate_data_types_markdown(types, enumerations, classes, root='../../'): converted = [convert_jsdoc_array_to_ts(t) for t in types] # Set of primitive types - primitive_types = {"string", "number", "boolean", "null", "undefined", "any", "object", "false", "true", "json", "function", "{}"} + primitive_types = {"string", "number", "boolean", "null", "undefined", "any", "object", "false", "true", "json", "function", "date", "{}"} def is_primitive(type): if (type.lower() in primitive_types or @@ -318,7 +319,7 @@ def generate_class_markdown(class_name, methods, properties, enumerations, class returns_markdown = "None" # Processing the method description - description = remove_line_breaks(correct_description(method.get('description', 'No description provided.'), '../')) + description = remove_line_breaks(correct_description(method.get('description', 'No description provided.'), '../', True)) # Form a link to the method document method_link = f"[{method_name}](./Methods/{method_name}.md)" @@ -353,7 +354,7 @@ def generate_method_markdown(method, enumerations, classes, example_editor_name) param_name = param.get('name', 'Unnamed') param_types = param.get('type', {}).get('names', []) if param.get('type') else [] param_types_md = generate_data_types_markdown(param_types, enumerations, classes) - param_desc = remove_line_breaks(correct_description(param.get('description', 'No description provided.'), '../../')) + param_desc = remove_line_breaks(correct_description(param.get('description', 'No description provided.'), '../../', True)) param_required = "Required" if not param.get('optional') else "Optional" param_default = correct_default_value(param.get('defaultvalue', ''), enumerations, classes) @@ -395,7 +396,7 @@ def generate_properties_markdown(properties, enumerations, classes, root='../'): for prop in sorted(properties, key=lambda m: m['name']): prop_name = prop['name'] prop_description = prop.get('description', 'No description provided.') - prop_description = remove_line_breaks(correct_description(prop_description, root)) + prop_description = remove_line_breaks(correct_description(prop_description, root, True)) prop_types = prop['type']['names'] if prop.get('type') else [] param_types_md = generate_data_types_markdown(prop_types, enumerations, classes, root) content += f"| {prop_name} | {param_types_md} | {prop_description} |\n" @@ -553,8 +554,11 @@ def generate(output_dir): for editor_name, folder_name in editors.items(): input_file = os.path.join(output_dir + '/tmp_json', editor_name + ".json") - shutil.rmtree(output_dir + f'/{folder_name}', ignore_errors=True) - os.makedirs(output_dir + f'/{folder_name}') + editor_folder_path = os.path.join(output_dir, folder_name) + for folder_name in os.listdir(editor_folder_path): + folder_path_to_del = os.path.join(editor_folder_path, folder_name) + if os.path.isdir(folder_path_to_del): + shutil.rmtree(folder_path_to_del, ignore_errors=True) data = load_json(input_file) used_enumerations.clear() @@ -570,7 +574,7 @@ if __name__ == "__main__": type=str, help="Destination directory for the generated documentation", nargs='?', # Indicates the argument is optional - default="../../../../office-js-api/" # Default value + default="../../../../../api.onlyoffice.com/site/docs/office-api/usage-api/" # Default value ) args = parser.parse_args() generate(args.destination) diff --git a/scripts/sdkjs_common/jsdoc/generate_jsonl_dataset.py b/scripts/sdkjs_common/jsdoc/office-api/generate_jsonl_dataset.py similarity index 98% rename from scripts/sdkjs_common/jsdoc/generate_jsonl_dataset.py rename to scripts/sdkjs_common/jsdoc/office-api/generate_jsonl_dataset.py index cba9b68..5187d0d 100644 --- a/scripts/sdkjs_common/jsdoc/generate_jsonl_dataset.py +++ b/scripts/sdkjs_common/jsdoc/office-api/generate_jsonl_dataset.py @@ -21,7 +21,7 @@ editors_names = { "forms": "Forms" } -root = '../../../..' +root = '../../../../..' missing_examples = [] def load_json(file_path): @@ -228,7 +228,7 @@ if __name__ == "__main__": type=str, help="Destination directory for the generated documentation", nargs='?', # Indicates the argument is optional - default="../../../../office-js-api/dataset" # Default value + default="../../../../../office-js-api/dataset" # Default value ) parser.add_argument( "model", diff --git a/scripts/sdkjs_common/jsdoc/config/plugins/correct_doclets.js b/scripts/sdkjs_common/jsdoc/plugins/config/correct_doclets.js similarity index 100% rename from scripts/sdkjs_common/jsdoc/config/plugins/correct_doclets.js rename to scripts/sdkjs_common/jsdoc/plugins/config/correct_doclets.js diff --git a/scripts/sdkjs_common/jsdoc/plugins/config/events/cell.json b/scripts/sdkjs_common/jsdoc/plugins/config/events/cell.json new file mode 100644 index 0000000..1194d1b --- /dev/null +++ b/scripts/sdkjs_common/jsdoc/plugins/config/events/cell.json @@ -0,0 +1,16 @@ +{ + "source": { + "include": ["../../../../../sdkjs/cell/plugin-events.js"] + }, + "plugins": ["../correct_doclets.js"], + "opts": { + "destination": "./out", + "recurse": true, + "encoding": "utf8" + }, + "templates": { + "json": { + "pretty": true + } + } +} diff --git a/scripts/sdkjs_common/jsdoc/plugins/config/events/common.json b/scripts/sdkjs_common/jsdoc/plugins/config/events/common.json new file mode 100644 index 0000000..364c80f --- /dev/null +++ b/scripts/sdkjs_common/jsdoc/plugins/config/events/common.json @@ -0,0 +1,16 @@ +{ + "source": { + "include": ["../../../../../sdkjs/common/base-plugin-events.js"] + }, + "plugins": ["../correct_doclets.js"], + "opts": { + "destination": "./out", + "recurse": true, + "encoding": "utf8" + }, + "templates": { + "json": { + "pretty": true + } + } +} \ No newline at end of file diff --git a/scripts/sdkjs_common/jsdoc/plugins/config/events/forms.json b/scripts/sdkjs_common/jsdoc/plugins/config/events/forms.json new file mode 100644 index 0000000..cafd6c9 --- /dev/null +++ b/scripts/sdkjs_common/jsdoc/plugins/config/events/forms.json @@ -0,0 +1,16 @@ +{ + "source": { + "include": ["../../../../../sdkjs-forms/plugin-events.js"] + }, + "plugins": ["../correct_doclets.js"], + "opts": { + "destination": "./out", + "recurse": true, + "encoding": "utf8" + }, + "templates": { + "json": { + "pretty": true + } + } +} \ No newline at end of file diff --git a/scripts/sdkjs_common/jsdoc/plugins/config/events/slide.json b/scripts/sdkjs_common/jsdoc/plugins/config/events/slide.json new file mode 100644 index 0000000..c11c90e --- /dev/null +++ b/scripts/sdkjs_common/jsdoc/plugins/config/events/slide.json @@ -0,0 +1,16 @@ +{ + "source": { + "include": ["../../../../../sdkjs/slide/plugin-events.js"] + }, + "plugins": ["../correct_doclets.js"], + "opts": { + "destination": "./out", + "recurse": true, + "encoding": "utf8" + }, + "templates": { + "json": { + "pretty": true + } + } +} diff --git a/scripts/sdkjs_common/jsdoc/plugins/config/events/word.json b/scripts/sdkjs_common/jsdoc/plugins/config/events/word.json new file mode 100644 index 0000000..23ff50a --- /dev/null +++ b/scripts/sdkjs_common/jsdoc/plugins/config/events/word.json @@ -0,0 +1,16 @@ +{ + "source": { + "include": ["../../../../../sdkjs/word/plugin-events.js"] + }, + "plugins": ["../correct_doclets.js"], + "opts": { + "destination": "./out", + "recurse": true, + "encoding": "utf8" + }, + "templates": { + "json": { + "pretty": true + } + } +} \ No newline at end of file diff --git a/scripts/sdkjs_common/jsdoc/config/plugins/cell.json b/scripts/sdkjs_common/jsdoc/plugins/config/methods/cell.json similarity index 63% rename from scripts/sdkjs_common/jsdoc/config/plugins/cell.json rename to scripts/sdkjs_common/jsdoc/plugins/config/methods/cell.json index b49b71e..bf9ac65 100644 --- a/scripts/sdkjs_common/jsdoc/config/plugins/cell.json +++ b/scripts/sdkjs_common/jsdoc/plugins/config/methods/cell.json @@ -1,8 +1,8 @@ { "source": { - "include": ["../../../../sdkjs/cell/api_plugins.js"] + "include": ["../../../../../sdkjs/cell/api_plugins.js"] }, - "plugins": ["./correct_doclets.js"], + "plugins": ["../correct_doclets.js"], "opts": { "destination": "./out", "recurse": true, diff --git a/scripts/sdkjs_common/jsdoc/config/plugins/common.json b/scripts/sdkjs_common/jsdoc/plugins/config/methods/common.json similarity index 51% rename from scripts/sdkjs_common/jsdoc/config/plugins/common.json rename to scripts/sdkjs_common/jsdoc/plugins/config/methods/common.json index 4bf510c..55b21ad 100644 --- a/scripts/sdkjs_common/jsdoc/config/plugins/common.json +++ b/scripts/sdkjs_common/jsdoc/plugins/config/methods/common.json @@ -1,8 +1,8 @@ { "source": { - "include": ["../../../../sdkjs/common/plugins/plugin_base_api.js" ,"../../../../sdkjs/common/apiBase_plugins.js"] + "include": ["../../../../../sdkjs/common/plugins/plugin_base_api.js" ,"../../../../../sdkjs/common/apiBase_plugins.js"] }, - "plugins": ["./correct_doclets.js"], + "plugins": ["../correct_doclets.js"], "opts": { "destination": "./out", "recurse": true, diff --git a/scripts/sdkjs_common/jsdoc/plugins/config/methods/forms.json b/scripts/sdkjs_common/jsdoc/plugins/config/methods/forms.json new file mode 100644 index 0000000..82bf9bd --- /dev/null +++ b/scripts/sdkjs_common/jsdoc/plugins/config/methods/forms.json @@ -0,0 +1,16 @@ +{ + "source": { + "include": ["../../../../../sdkjs-forms/apiPlugins.js"] + }, + "plugins": ["../correct_doclets.js"], + "opts": { + "destination": "./out", + "recurse": true, + "encoding": "utf8" + }, + "templates": { + "json": { + "pretty": true + } + } +} \ No newline at end of file diff --git a/scripts/sdkjs_common/jsdoc/plugins/config/methods/slide.json b/scripts/sdkjs_common/jsdoc/plugins/config/methods/slide.json new file mode 100644 index 0000000..4c79b00 --- /dev/null +++ b/scripts/sdkjs_common/jsdoc/plugins/config/methods/slide.json @@ -0,0 +1,16 @@ +{ + "source": { + "include": ["../../../../../sdkjs/slide/api_plugins.js"] + }, + "plugins": ["../correct_doclets.js"], + "opts": { + "destination": "./out", + "recurse": true, + "encoding": "utf8" + }, + "templates": { + "json": { + "pretty": true + } + } +} diff --git a/scripts/sdkjs_common/jsdoc/config/plugins/word.json b/scripts/sdkjs_common/jsdoc/plugins/config/methods/word.json similarity index 54% rename from scripts/sdkjs_common/jsdoc/config/plugins/word.json rename to scripts/sdkjs_common/jsdoc/plugins/config/methods/word.json index b06743e..45d7f3e 100644 --- a/scripts/sdkjs_common/jsdoc/config/plugins/word.json +++ b/scripts/sdkjs_common/jsdoc/plugins/config/methods/word.json @@ -1,8 +1,8 @@ { "source": { - "include": ["../../../../sdkjs/word/api_plugins.js", "../../../../sdkjs-forms/apiPlugins.js"] + "include": ["../../../../../sdkjs/word/api_plugins.js", "../../../../../sdkjs-forms/apiPlugins.js"] }, - "plugins": ["./correct_doclets.js"], + "plugins": ["../correct_doclets.js"], "opts": { "destination": "./out", "recurse": true, diff --git a/scripts/sdkjs_common/jsdoc/generate_docs_plugins_json.py b/scripts/sdkjs_common/jsdoc/plugins/generate_docs_events_json.py similarity index 93% rename from scripts/sdkjs_common/jsdoc/generate_docs_plugins_json.py rename to scripts/sdkjs_common/jsdoc/plugins/generate_docs_events_json.py index 12f5d57..850aa14 100644 --- a/scripts/sdkjs_common/jsdoc/generate_docs_plugins_json.py +++ b/scripts/sdkjs_common/jsdoc/plugins/generate_docs_events_json.py @@ -6,14 +6,14 @@ import re # Configuration files configs = [ - "./config/plugins/common.json", - "./config/plugins/word.json", - "./config/plugins/cell.json", - "./config/plugins/slide.json", - "./config/plugins/forms.json" + "./config/events/common.json", + "./config/events/word.json", + "./config/events/cell.json", + "./config/events/slide.json", + "./config/events/forms.json" ] -root = '../../../..' +root = '../../../../..' def generate(output_dir, md=False): if not os.path.exists(output_dir): @@ -49,7 +49,7 @@ def generate(output_dir, md=False): # Modify JSON data for idx, doclet in enumerate(data): - if idx == start_common_doclet_idx: + if idx >= start_common_doclet_idx: example_folder_name = 'common' elif editor_name == 'forms': example_folder_name = 'word' @@ -84,7 +84,7 @@ def generate(output_dir, md=False): with open(output_file, 'w', encoding='utf-8') as f: json.dump(data, f, ensure_ascii=False, indent=4) - print("Documentation generation for builder completed.") + print("Documentation generation for plugin events completed.") def remove_builder_lines(text): lines = text.splitlines() # Split text into lines diff --git a/scripts/sdkjs_common/jsdoc/plugins/generate_docs_events_md.py b/scripts/sdkjs_common/jsdoc/plugins/generate_docs_events_md.py new file mode 100644 index 0000000..a23e919 --- /dev/null +++ b/scripts/sdkjs_common/jsdoc/plugins/generate_docs_events_md.py @@ -0,0 +1,378 @@ +#!/usr/bin/env python3 +import os +import json +import re +import shutil +import argparse +import generate_docs_events_json + +# Папки для ΠΊΠ°ΠΆΠ΄ΠΎΠ³ΠΎ editor_name +editors = { + "word": "text-document-api", + "cell": "spreadsheet-api", + "slide": "presentation-api", + "forms": "form-api" +} + +missing_examples = [] +used_enumerations = set() + + +def load_json(path): + with open(path, 'r', encoding='utf-8') as f: + return json.load(f) + + +def write_markdown_file(path, content): + os.makedirs(os.path.dirname(path), exist_ok=True) + with open(path, 'w', encoding='utf-8') as f: + f.write(content) + + +def remove_js_comments(text): + text = re.sub(r'^\s*//.*$', '', text, flags=re.MULTILINE) + text = re.sub(r'/\*.*?\*/', '', text, flags=re.DOTALL) + return text.strip() + + +def correct_description(string, root='', isInTable=False): + """ + Cleans or transforms specific tags in the doclet description: + - => ** (bold text) + - ... => πŸ’‘ ... + - {@link ...} is replaced with a Markdown link + - If the description is missing, returns a default value. + - All '\r' characters are replaced with '\n'. + """ + if string is None: + return 'No description provided.' + + if False == isInTable: + # Line breaks + string = string.replace('\r', '\\\n') + + # Replace tags with Markdown bold formatting + string = re.sub(r'', '-**', string) + string = re.sub(r'', '**', string) + + # Replace tags with an icon and text + string = re.sub(r'(.*?)', r'πŸ’‘ \1', string, flags=re.DOTALL) + + # Process {@link ...} constructions + string = process_link_tags(string, root) + + return string + +def process_link_tags(text, root=''): + """ + Finds patterns like {@link ...} and replaces them with Markdown links. + If the prefix 'global#' is found, a link to a typedef is generated, + otherwise, a link to a class method is created. + For a method, if an alias is not specified, the name is left in the format 'Class#Method'. + """ + reserved_links = { + '/docbuilder/global#ShapeType': f"{'../../../../../../' if root == '' else '../../../../../' if root == '../' else root}docs/office-api/usage-api/text-document-api/Enumeration/ShapeType.md", + '/plugin/config': 'https://api.onlyoffice.com/docs/plugin-and-macros/structure/configuration/', + '/docbuilder/basic': 'https://api.onlyoffice.com/docs/office-api/usage-api/text-document-api/' + } + + def replace_link(match): + content = match.group(1).strip() # Example: "/docbuilder/global#ShapeType shape type" or "global#ErrorValue ErrorValue" + parts = content.split() + ref = parts[0] + label = parts[1] if len(parts) > 1 else None + + if ref.startswith('/'): + # Handle reserved links using mapping + if ref in reserved_links: + url = reserved_links[ref] + display_text = label if label else ref + return f"[{display_text}]({url})" + elif ref.startswith('/docs/plugins/'): + url = f'../../{ref.split('/docs/plugins/')[1]}.md' + display_text = label if label else ref + return f"[{display_text}]({url})" + else: + # If the link is not in the mapping, return the original construction + return match.group(0) + elif ref.startswith("global#"): + # Handle links to typedef (similar logic as before) + typedef_name = ref.split("#")[1] + used_enumerations.add(typedef_name) + display_text = label if label else typedef_name + return f"[{display_text}]({root}Enumeration/Event_{typedef_name}.md)" + else: + # Handle links to class methods like ClassName#MethodName + try: + class_name, method_name = ref.split("#") + except ValueError: + return match.group(0) + display_text = label if label else ref # Keep the full notation, e.g., "Api#CreateSlide" + return f"[{display_text}]({root}{class_name}/Methods/{method_name}.md)" + + return re.sub(r'{@link\s+([^}]+)}', replace_link, text) + +def remove_line_breaks(s): + return re.sub(r'[\r\n]+', ' ', s) + + +def convert_jsdoc_array_to_ts(type_str): + p = re.compile(r'Array\.<([^>]+)>') + while True: + m = p.search(type_str) + if not m: + break + inner = convert_jsdoc_array_to_ts(m.group(1).strip()) + type_str = type_str[:m.start()] + inner + '[]' + type_str[m.end():] + return type_str + + +def generate_data_types_markdown(types, enumerations, root=''): + converted = [convert_jsdoc_array_to_ts(t) for t in types] + primitives = {"string", "number", "boolean", "null", "undefined", "any", "object", "false", "true", "json", "function", "date", "{}"} + result = [] + enum_names = {e['name'] for e in enumerations} + for t in converted: + base = t.rstrip('[]') + dims = t[len(base):] + if base in enum_names: + used_enumerations.add(base) + link = f"[Event_{base}]({root}../Enumeration/Event_{base}.md)" + elif base in primitives or re.match(r"^['\"].*['\"]$", base) or re.match(r"^-?\d+(\.\d+)?$", base): + link = base + else: + link = base + result.append(link + dims) + return " | ".join(result) + + +def escape_text_outside_code_blocks(md): + parts = re.split(r'(```.*?```)', md, flags=re.DOTALL) + for i in range(0, len(parts), 2): + parts[i] = parts[i].replace('<', '<').replace('>', '>') + return "".join(parts) + + +def generate_event_markdown(event, enumerations): + name = event['name'] + desc = correct_description(event.get('description', '')) + params = event.get('params', []) + + md = f"# {name}\n\n{desc}\n\n" + + # Parameters + md += "## Parameters\n\n" + if params: + md += "| **Name** | **Data type** | **Description** |\n" + md += "| --------- | ------------- | ----------- |\n" + for p in params: + t_md = generate_data_types_markdown( + p.get('type', {}).get('names', []), + enumerations + ) + d = remove_line_breaks(correct_description(p.get('description', ''), isInTable=True)) + md += f"| {p['name']} | {t_md} | {d} |\n" + md += "\n" + else: + md += "This event has no parameters.\n\n" + + for ex in event.get('examples', []): + code = remove_js_comments(ex).strip() + md += f"```javascript\n{code}\n```\n\n" + + return escape_text_outside_code_blocks(md) + + +def generate_enumeration_markdown(enumeration, enumerations): + """ + Generates Markdown documentation for a 'typedef' doclet. + This version only works with `enumeration['examples']` (an array of strings), + ignoring any single `enumeration['examples']` field. + """ + enum_name = enumeration['name'] + + if enum_name not in used_enumerations: + return None + + description = enumeration.get('description', 'No description provided.') + description = correct_description(description, '../') + + # Only use the 'examples' array + examples = enumeration.get('examples', []) + + content = f"# Event_{enum_name}\n\n{description}\n\n" + + parsed_type = enumeration['type'].get('parsedType') + if not parsed_type: + # If parsedType is missing, just list 'type.names' if available + type_names = enumeration['type'].get('names', []) + if type_names: + content += "## Type\n\n" + t_md = generate_data_types_markdown(type_names, enumerations) + content += t_md + "\n\n" + else: + ptype = parsed_type['type'] + + # 1) Handle TypeUnion + if ptype == 'TypeUnion': + content += "## Type\n\nEnumeration\n\n" + content += "## Values\n\n" + for raw_t in enumeration['type']['names']: + # Attempt linking + if any(enum['name'] == raw_t for enum in enumerations): + used_enumerations.add(raw_t) + content += f"- [{raw_t}](../Enumeration/Event_{raw_t}.md)\n" + else: + content += f"- {raw_t}\n" + + # 2) Handle TypeApplication (e.g. Object.) + elif ptype == 'TypeApplication': + content += "## Type\n\nObject\n\n" + type_names = enumeration['type'].get('names', []) + if type_names: + t_md = generate_data_types_markdown(type_names, enumerations) + content += f"**Type:** {t_md}\n\n" + + # 3) If properties are present, treat it like an object + if enumeration.get('properties') is not None: + content += generate_properties_markdown(enumeration['properties'], enumerations) + + # 4) If it's neither TypeUnion nor TypeApplication, just output the type names + if ptype not in ('TypeUnion', 'TypeApplication'): + type_names = enumeration['type'].get('names', []) + if type_names: + content += "## Type\n\n" + t_md = generate_data_types_markdown(type_names, enumerations) + content += t_md + "\n\n" + + # Process examples array + if examples: + if len(examples) > 1: + content += "\n\n## Examples\n\n" + else: + content += "\n\n## Example\n\n" + + for i, ex_line in enumerate(examples, start=1): + # Remove JS comments + cleaned_example = remove_js_comments(ex_line).strip() + + # Attempt splitting if the user used ```js + if '```js' in cleaned_example: + comment, code = cleaned_example.split('```js', 1) + comment = comment.strip() + code = code.strip() + if len(examples) > 1: + content += f"**Example {i}:**\n\n{comment}\n\n" + + content += f"```javascript\n{code}\n```\n" + else: + if len(examples) > 1: + content += f"**Example {i}:**\n\n{comment}\n\n" + # No special fences, just show as code + content += f"```javascript\n{cleaned_example}\n```\n" + + return escape_text_outside_code_blocks(content) + +def generate_properties_markdown(properties, enumerations): + if properties is None: + return '' + + content = "## Properties\n\n" + content += "| Name | Type | Description |\n" + content += "| ---- | ---- | ----------- |\n" + + for prop in sorted(properties, key=lambda m: m['name']): + prop_name = prop['name'] + prop_description = prop.get('description', 'No description provided.') + prop_description = remove_line_breaks(correct_description(prop_description, isInTable=True)) + prop_types = prop['type']['names'] if prop.get('type') else [] + param_types_md = generate_data_types_markdown(prop_types, enumerations) + content += f"| {prop_name} | {param_types_md} | {prop_description} |\n" + + # Escape outside code blocks + return escape_text_outside_code_blocks(content) + +def clean_editor_dir(editor_dir): + for root, dirs, files in os.walk(editor_dir, topdown=False): + for file in files: + if not file.endswith(('.json')): + os.remove(os.path.join(root, file)) + for dir in dirs: + dir_path = os.path.join(root, dir) + # remove empty folder + if not os.listdir(dir_path): + os.rmdir(dir_path) + +def clean_enum_files(editor_dir: str): + for root, _, files in os.walk(editor_dir, topdown=False): + for file in files: + if True == file.startswith('Event_') and False == file.endswith('.json'): + os.remove(os.path.join(root, file)) + +def process_events(data, editor_dir): + enumerations = [] + events = [] + + for doclet in data: + kind = doclet.get('kind') + if kind == 'typedef': + enumerations.append(doclet) + elif kind == 'event': + events.append(doclet) + + events_dir = f'{editor_dir}/Events' + clean_editor_dir(events_dir) + os.makedirs(events_dir, exist_ok=True) + used_enumerations.clear() + + # пишСм события + for ev in events: + path = os.path.join(events_dir, f"{ev['name']}.md") + write_markdown_file(path, generate_event_markdown(ev, enumerations)) + if not ev.get('examples'): + missing_examples.append(os.path.relpath(path, events_dir)) + + # пишСм пСрСчислСния, ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌΡ‹Π΅ событиями + enum_dir = os.path.join(editor_dir, 'Enumeration') + clean_enum_files(enum_dir) + + os.makedirs(enum_dir, exist_ok=True) + prev = -1 + while len(used_enumerations) != prev: + prev = len(used_enumerations) + for e in enumerations: + if e['name'] in used_enumerations: + generate_enumeration_markdown(e, enumerations) + for e in enumerations: + if e['name'] in used_enumerations: + path = os.path.join(enum_dir, f"Event_{e['name']}.md") + write_markdown_file(path, generate_enumeration_markdown(e, enumerations)) + if not e.get('examples'): + missing_examples.append(os.path.relpath(path, editor_dir)) + + +def generate_events(output_dir): + if output_dir.endswith('/'): + output_dir = output_dir[:-1] + tmp = os.path.join(output_dir, 'tmp_json') + + generate_docs_events_json.generate(tmp, md=True) + + for editor_name, folder in editors.items(): + data = load_json(os.path.join(tmp, f"{editor_name}.json")) + process_events(data, os.path.join(output_dir, folder)) + + shutil.rmtree(tmp) + print("Done. Missing examples:", missing_examples) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Generate events documentation") + parser.add_argument( + "destination", + nargs="?", + default="../../../../../api.onlyoffice.com/site/docs/plugin-and-macros/interacting-with-editors/", + help="Output directory" + ) + args = parser.parse_args() + generate_events(args.destination) \ No newline at end of file diff --git a/scripts/sdkjs_common/jsdoc/plugins/generate_docs_methods_json.py b/scripts/sdkjs_common/jsdoc/plugins/generate_docs_methods_json.py new file mode 100644 index 0000000..7f55b3c --- /dev/null +++ b/scripts/sdkjs_common/jsdoc/plugins/generate_docs_methods_json.py @@ -0,0 +1,111 @@ +import os +import subprocess +import json +import argparse +import re + +# Configuration files +configs = [ + "./config/methods/common.json", + "./config/methods/word.json", + "./config/methods/cell.json", + "./config/methods/slide.json", + "./config/methods/forms.json" +] + +root = '../../../../..' + +def generate(output_dir, md=False): + if not os.path.exists(output_dir): + os.makedirs(output_dir) + + # Generate JSON documentation + for config in configs: + editor_name = config.split('/')[-1].replace('.json', '') + output_file = os.path.join(output_dir, editor_name + ".json") + command = f"npx jsdoc -c {config} -X > {output_file}" + print(f"Generating {editor_name}.json: {command}") + subprocess.run(command, shell=True) + + common_doclets_file = os.path.join(output_dir, 'common.json') + with open(common_doclets_file, 'r', encoding='utf-8') as f: + common_doclets_json = json.dumps(json.load(f)) + os.remove(common_doclets_file) + + # Append examples to JSON documentation + for config in configs: + if (config.find('common') != -1): + continue + + editor_name = config.split('/')[-1].replace('.json', '') + example_folder_name = editor_name # name of folder with examples + output_file = os.path.join(output_dir, editor_name + ".json") + + # Read the JSON file + with open(output_file, 'r', encoding='utf-8') as f: + data = json.load(f) + start_common_doclet_idx = len(data) + data += json.loads(common_doclets_json) + + # Modify JSON data + for idx, doclet in enumerate(data): + if idx >= start_common_doclet_idx: + example_folder_name = 'common' + elif editor_name == 'forms': + example_folder_name = 'word' + + if 'see' in doclet: + if doclet['see'] is not None: + doclet['see'][0] = doclet['see'][0].replace('{Editor}', example_folder_name.title()) + file_path = f'{root}/' + doclet['see'][0] + + if os.path.exists(file_path): + with open(file_path, 'r', encoding='utf-8') as see_file: + example_content = see_file.read() + + # Extract the first line as a comment if it exists + lines = example_content.split('\n') + if lines[0].startswith('//'): + comment = lines[0] + '\n' + code_content = '\n'.join(lines[1:]) + else: + comment = '' + code_content = example_content + + doclet['examples'] = [remove_js_comments(comment) + code_content] + + if md == False: + document_type = editor_name + if "forms" == document_type: + document_type = "pdf" + doclet['description'] = doclet['description'] + f'\n\n## Try it\n\n ```js document-builder={{"documentType": "{document_type}"}}\n{code_content}\n```' + + # Write the modified JSON file back + with open(output_file, 'w', encoding='utf-8') as f: + json.dump(data, f, ensure_ascii=False, indent=4) + + print("Documentation generation for plugin methods completed.") + +def remove_builder_lines(text): + lines = text.splitlines() # Split text into lines + filtered_lines = [line for line in lines if not line.strip().startswith("builder.")] + return "\n".join(filtered_lines) + +def remove_js_comments(text): + # Remove single-line comments, leaving text after // + text = re.sub(r'^\s*//\s?', '', text, flags=re.MULTILINE) + # Remove multi-line comments, leaving text after /* + text = re.sub(r'/\*\s*|\s*\*/', '', text, flags=re.DOTALL) + return text.strip() + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Generate documentation") + parser.add_argument( + "destination", + type=str, + help="Destination directory for the generated documentation", + nargs='?', # Indicates the argument is optional + default=f"{root}/office-js-api-declarations/office-js-api-plugins" + ) + args = parser.parse_args() + generate(args.destination) diff --git a/scripts/sdkjs_common/jsdoc/generate_docs_plugins_md.py b/scripts/sdkjs_common/jsdoc/plugins/generate_docs_methods_md.py similarity index 89% rename from scripts/sdkjs_common/jsdoc/generate_docs_plugins_md.py rename to scripts/sdkjs_common/jsdoc/plugins/generate_docs_methods_md.py index 4145fc9..51d9f35 100644 --- a/scripts/sdkjs_common/jsdoc/generate_docs_plugins_md.py +++ b/scripts/sdkjs_common/jsdoc/plugins/generate_docs_methods_md.py @@ -3,7 +3,7 @@ import json import re import shutil import argparse -import generate_docs_plugins_json +import generate_docs_methods_json # Configuration files editors = { @@ -18,10 +18,6 @@ used_enumerations = set() cur_editor_name = None -_CODE_BLOCK_RE = re.compile(r'(```.*?```)', re.DOTALL) -_QSTRING_RE = re.compile(r'(["\'])(.*?)(? => ** (bold text) @@ -92,8 +91,9 @@ def correct_description(string, root=''): if string is None: return 'No description provided.' - # Line breaks - string = string.replace('\r', '\\\n') + if False == isInTable: + # Line breaks + string = string.replace('\r', '\\\n') # Replace tags with Markdown bold formatting string = re.sub(r'', '-**', string) @@ -189,7 +189,7 @@ def get_base_type(ts_type: str) -> str: ts_type = ts_type[:-2] return ts_type -def generate_data_types_markdown(types, enumerations, classes, root='../../'): +def generate_data_types_markdown(types, enumerations, classes, root='../'): """ 1) Converts each type from JSDoc (e.g., Array.) to T[]. 2) Processes union types by splitting them using '|'. @@ -201,7 +201,7 @@ def generate_data_types_markdown(types, enumerations, classes, root='../../'): converted = [convert_jsdoc_array_to_ts(t) for t in types] # Set of primitive types - primitive_types = {"string", "number", "boolean", "null", "undefined", "any", "object", "false", "true", "json", "function", "{}"} + primitive_types = {"string", "number", "boolean", "null", "undefined", "any", "object", "false", "true", "json", "function", "date", "{}"} def is_primitive(type): if (type.lower() in primitive_types or @@ -323,10 +323,10 @@ def generate_class_markdown(class_name, methods, properties, enumerations, class returns_markdown = "None" # Processing the method description - description = remove_line_breaks(correct_description(method.get('description', 'No description provided.'), '../')) + description = remove_line_breaks(correct_description(method.get('description', 'No description provided.'), '../', True)) # Form a link to the method document - method_link = f"[{method_name}](./Methods/{method_name}.md)" + method_link = f"[{method_name}](./{method_name}.md)" content += f"| {method_link} | {returns_markdown} | {description} |\n" @@ -340,7 +340,7 @@ def generate_method_markdown(method, enumerations, classes): method_name = method['name'] description = method.get('description', 'No description provided.') - description = correct_description(description, '../../') + description = correct_description(description, '../') params = method.get('params', []) returns = method.get('returns', []) memberof = method.get('memberof', '') @@ -354,7 +354,7 @@ def generate_method_markdown(method, enumerations, classes): param_list = ', '.join([param['name'] for param in params if '.' not in param['name']]) if params else '' content += f"## Syntax\n\n```javascript\nexpression.{method_name}({param_list});\n```\n\n" if memberof: - content += f"`expression` - A variable that represents a [{memberof}](../{memberof}.md) class.\n\n" + content += f"`expression` - A variable that represents a [{memberof}](Methods.md) class.\n\n" # Parameters content += "## Parameters\n\n" @@ -365,7 +365,7 @@ def generate_method_markdown(method, enumerations, classes): param_name = param.get('name', 'Unnamed') param_types = param.get('type', {}).get('names', []) if param.get('type') else [] param_types_md = generate_data_types_markdown(param_types, enumerations, classes) - param_desc = remove_line_breaks(correct_description(param.get('description', 'No description provided.'), '../../')) + param_desc = remove_line_breaks(correct_description(param.get('description', 'No description provided.'), '../', True)) param_required = "Required" if not param.get('optional') else "Optional" param_default = correct_default_value(param.get('defaultvalue', ''), enumerations, classes) @@ -421,7 +421,7 @@ def generate_properties_markdown(properties, enumerations, classes, root='../'): for prop in sorted(properties, key=lambda m: m['name']): prop_name = prop['name'] prop_description = prop.get('description', 'No description provided.') - prop_description = remove_line_breaks(correct_description(prop_description)) + prop_description = remove_line_breaks(correct_description(prop_description, isInTable=True)) prop_types = prop['type']['names'] if prop.get('type') else [] param_types_md = generate_data_types_markdown(prop_types, enumerations, classes, root) content += f"| {prop_name} | {param_types_md} | {prop_description} |\n" @@ -521,6 +521,23 @@ def generate_enumeration_markdown(enumeration, enumerations, classes): return escape_text_outside_code_blocks(content) +def clean_methods_dir(methods_dir): + for root, dirs, files in os.walk(methods_dir, topdown=False): + for file in files: + if not file.endswith(('.json')): + os.remove(os.path.join(root, file)) + for dir in dirs: + dir_path = os.path.join(root, dir) + # remove empty folder + if not os.listdir(dir_path): + os.rmdir(dir_path) + +def clean_enum_files(editor_dir: str): + for root, _, files in os.walk(editor_dir, topdown=False): + for file in files: + if False == file.startswith('Event_') and False == file.endswith('.json'): + os.remove(os.path.join(root, file)) + def process_doclets(data, output_dir, editor_name): global cur_editor_name cur_editor_name = editor_name @@ -529,6 +546,10 @@ def process_doclets(data, output_dir, editor_name): classes_props = {} enumerations = [] editor_dir = os.path.join(output_dir, editors[editor_name]) + methods_dir = os.path.join(output_dir, editors[editor_name], 'Methods') + + clean_methods_dir(methods_dir) + os.makedirs(methods_dir, exist_ok=True) for doclet in data: if doclet['kind'] == 'class': @@ -546,36 +567,31 @@ def process_doclets(data, output_dir, editor_name): elif doclet['kind'] == 'typedef': enumerations.append(doclet) - # Process classes - for class_name, methods in classes.items(): - if (len(methods) == 0): - continue + # Process api methods + class_name = 'Api' + methods = classes[class_name] + # Write class file + class_content = generate_class_markdown( + class_name, + methods, + classes_props[class_name], + enumerations, + classes + ) + write_markdown_file(os.path.join(methods_dir, f"Methods.md"), class_content) - class_dir = os.path.join(editor_dir, class_name) - methods_dir = os.path.join(class_dir, 'Methods') - os.makedirs(methods_dir, exist_ok=True) + # Write method files + for method in methods: + method_file_path = os.path.join(methods_dir, f"{method['name']}.md") + method_content = generate_method_markdown(method, enumerations, classes) + write_markdown_file(method_file_path, method_content) - # Write class file - class_content = generate_class_markdown( - class_name, - methods, - classes_props[class_name], - enumerations, - classes - ) - write_markdown_file(os.path.join(class_dir, f"{class_name}.md"), class_content) - - # Write method files - for method in methods: - method_file_path = os.path.join(methods_dir, f"{method['name']}.md") - method_content = generate_method_markdown(method, enumerations, classes) - write_markdown_file(method_file_path, method_content) - - if not method.get('examples', ''): - missing_examples.append(os.path.relpath(method_file_path, output_dir)) + if not method.get('examples', ''): + missing_examples.append(os.path.relpath(method_file_path, output_dir)) # Process enumerations enum_dir = os.path.join(editor_dir, 'Enumeration') + clean_enum_files(enum_dir) os.makedirs(enum_dir, exist_ok=True) # idle run @@ -601,13 +617,10 @@ def generate(output_dir): if output_dir[-1] == '/': output_dir = output_dir[:-1] - generate_docs_plugins_json.generate(output_dir + '/tmp_json', md=True) + generate_docs_methods_json.generate(output_dir + '/tmp_json', md=True) for editor_name, folder_name in editors.items(): input_file = os.path.join(output_dir + '/tmp_json', editor_name + ".json") - shutil.rmtree(output_dir + f'/{folder_name}', ignore_errors=True) - os.makedirs(output_dir + f'/{folder_name}') - data = load_json(input_file) used_enumerations.clear() process_doclets(data, output_dir, editor_name) @@ -622,7 +635,7 @@ if __name__ == "__main__": type=str, help="Destination directory for the generated documentation", nargs='?', # Indicates the argument is optional - default="../../../../office-js-api/plugins/" # Default value + default="../../../../../api.onlyoffice.com/site/docs/plugin-and-macros/interacting-with-editors/" # Default value ) args = parser.parse_args() generate(args.destination) From 4d6b9f9463d7f184dc74e38fbb2ff797b3b3bdbd Mon Sep 17 00:00:00 2001 From: Oleg Korshul Date: Mon, 7 Jul 2025 22:50:04 +0700 Subject: [PATCH 10/11] Fix md --- README.md | 4 ++-- develop/README.md | 3 ++- scripts/sdkjs_common/jsdoc/README.md | 32 +++++++++++++-------------- scripts/sdkjs_common/jsdoc/Readme.txt | 0 4 files changed, 20 insertions(+), 19 deletions(-) delete mode 100644 scripts/sdkjs_common/jsdoc/Readme.txt diff --git a/README.md b/README.md index 414e62b..a96a939 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,8 @@ necessary for the compilation process, all the dependencies required for the correct work, as well as to get the latest version of **ONLYOFFICE products** source code and build all their components. -**Important!** We can only guarantee the correct work of the products built from -the `master` branch. +**Important!** We can only guarantee the correct work of the products built +from the `master` branch. ## How to use - Linux diff --git a/develop/README.md b/develop/README.md index 83e51d0..17860f0 100644 --- a/develop/README.md +++ b/develop/README.md @@ -7,7 +7,8 @@ but don't want to compile pretty compilcated core product to make those changes. ## System requirements **Note**: ARM-based architectures are currently **NOT** supported; -attempting to run the images on ARM devices may result in startup failures or other runtime issues. +attempting to run the images on ARM devices may result in startup failures +or other runtime issues. ### Windows diff --git a/scripts/sdkjs_common/jsdoc/README.md b/scripts/sdkjs_common/jsdoc/README.md index a0dd1ae..9432882 100644 --- a/scripts/sdkjs_common/jsdoc/README.md +++ b/scripts/sdkjs_common/jsdoc/README.md @@ -1,7 +1,7 @@ # Documentation Generation Guide -This guide explains how to generate documentation for Onlyoffice Builder and -Plugins (Methods/Events) API using the following Python scripts: +This guide explains how to generate documentation for Onlyoffice Builder +and Plugins (Methods/Events) API using the following Python scripts: - `office-api/generate_docs_json.py` - `office-api/generate_docs_md.py` @@ -38,8 +38,8 @@ This script generates JSON documentation based on the `apiBuilder.js` files. ``` - **Parameters**: - - `output_path` (optional): The directory where the JSON documentation will be - saved. If not specified, the default path is + - `output_path` (optional): The directory where the JSON documentation + will be saved. If not specified, the default path is `../../../../office-js-api-declarations/office-js-api`. ### `office-api/generate_docs_md.py` @@ -53,8 +53,8 @@ This script generates Markdown documentation from the `apiBuilder.js` files. ``` - **Parameters**: - - `output_path` (optional): The directory where the Markdown documentation will - be saved. If not specified, the default path is + - `output_path` (optional): The directory where the Markdown documentation + will be saved. If not specified, the default path is `../../../../office-js-api/`. ### `plugins/generate_docs_methods_json.py` @@ -68,8 +68,8 @@ This script generates JSON documentation based on the `api_plugins.js` files. ``` - **Parameters**: - - `output_path` (optional): The directory where the JSON documentation will be - saved. If not specified, the default path is + - `output_path` (optional): The directory where the JSON documentation + will be saved. If not specified, the default path is `../../../../office-js-api-declarations/office-js-api-plugins`. ### `plugins/generate_docs_events_json.py` @@ -83,8 +83,8 @@ This script generates JSON documentation based on the `plugin-events.js` files. ``` - **Parameters**: - - `output_path` (optional): The directory where the JSON documentation will be - saved. If not specified, the default path is + - `output_path` (optional): The directory where the JSON documentation + will be saved. If not specified, the default path is `../../../../office-js-api-declarations/office-js-api-plugins`. ### `plugins/generate_docs_methods_md.py` @@ -98,8 +98,8 @@ This script generates Markdown documentation from the `api_plugins.js` files. ``` - **Parameters**: - - `output_path` (optional): The directory where the Markdown documentation will - be saved. If not specified, the default path is + - `output_path` (optional): The directory where the Markdown documentation + will be saved. If not specified, the default path is `../../../../office-js-api/`. ### `plugins/generate_docs_events_md.py` @@ -113,8 +113,8 @@ This script generates Markdown documentation from the `plugin-events.js` files. ``` - **Parameters**: - - `output_path` (optional): The directory where the Markdown documentation will - be saved. If not specified, the default path is + - `output_path` (optional): The directory where the Markdown documentation + will be saved. If not specified, the default path is `../../../../office-js-api/`. ## Example @@ -133,6 +133,6 @@ python generate_docs_md.py /path/to/save/markdown ## Notes -- Make sure to have all necessary permissions to run these scripts and write to - the specified directories. +- Make sure to have all necessary permissions to run these scripts and write + to the specified directories. - The output directories will be created if they do not exist. diff --git a/scripts/sdkjs_common/jsdoc/Readme.txt b/scripts/sdkjs_common/jsdoc/Readme.txt deleted file mode 100644 index e69de29..0000000 From 9b5b3eb77c213793ef1da7df1fb5126338d3d881 Mon Sep 17 00:00:00 2001 From: Nikita Khromov Date: Tue, 8 Jul 2025 15:51:43 +0700 Subject: [PATCH 11/11] [jsdoc] Fix replace
tag --- scripts/sdkjs_common/jsdoc/office-api/generate_docs_md.py | 6 ++++-- .../sdkjs_common/jsdoc/plugins/generate_docs_events_md.py | 6 ++++-- .../sdkjs_common/jsdoc/plugins/generate_docs_methods_md.py | 6 ++++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/scripts/sdkjs_common/jsdoc/office-api/generate_docs_md.py b/scripts/sdkjs_common/jsdoc/office-api/generate_docs_md.py index 6693fff..cafc8d2 100644 --- a/scripts/sdkjs_common/jsdoc/office-api/generate_docs_md.py +++ b/scripts/sdkjs_common/jsdoc/office-api/generate_docs_md.py @@ -91,9 +91,11 @@ def correct_description(string, root='', isInTable=False): if False == isInTable: # Line breaks string = string.replace('\r', '\\\n') + # Replace tags with Markdown bold formatting + string = re.sub(r'', '-**', string) + else: + string = re.sub(r'', '**', string) - # Replace tags with Markdown bold formatting - string = re.sub(r'', '-**', string) string = re.sub(r'', '**', string) # Replace tags with an icon and text diff --git a/scripts/sdkjs_common/jsdoc/plugins/generate_docs_events_md.py b/scripts/sdkjs_common/jsdoc/plugins/generate_docs_events_md.py index a23e919..350e676 100644 --- a/scripts/sdkjs_common/jsdoc/plugins/generate_docs_events_md.py +++ b/scripts/sdkjs_common/jsdoc/plugins/generate_docs_events_md.py @@ -50,9 +50,11 @@ def correct_description(string, root='', isInTable=False): if False == isInTable: # Line breaks string = string.replace('\r', '\\\n') + # Replace tags with Markdown bold formatting + string = re.sub(r'', '-**', string) + else: + string = re.sub(r'', '**', string) - # Replace tags with Markdown bold formatting - string = re.sub(r'', '-**', string) string = re.sub(r'', '**', string) # Replace tags with an icon and text diff --git a/scripts/sdkjs_common/jsdoc/plugins/generate_docs_methods_md.py b/scripts/sdkjs_common/jsdoc/plugins/generate_docs_methods_md.py index 51d9f35..65ab6c2 100644 --- a/scripts/sdkjs_common/jsdoc/plugins/generate_docs_methods_md.py +++ b/scripts/sdkjs_common/jsdoc/plugins/generate_docs_methods_md.py @@ -94,9 +94,11 @@ def correct_description(string, root='', isInTable=False): if False == isInTable: # Line breaks string = string.replace('\r', '\\\n') + # Replace tags with Markdown bold formatting + string = re.sub(r'', '-**', string) + else: + string = re.sub(r'', '**', string) - # Replace tags with Markdown bold formatting - string = re.sub(r'', '-**', string) string = re.sub(r'', '**', string) # Replace tags with an icon and text