mirror of
https://github.com/land0r/obsidian-flowchart-plugin.git
synced 2026-07-22 05:32:04 +00:00
feat: Update - prettier and linter
This commit is contained in:
parent
43b7732ce9
commit
f001f6263c
7 changed files with 159 additions and 171 deletions
7
.prettierrc
Normal file
7
.prettierrc
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "es5",
|
||||
"tabWidth": 2,
|
||||
"printWidth": 80
|
||||
}
|
||||
62
README.md
62
README.md
|
|
@ -4,27 +4,27 @@ This is a custom plugin for rendering flowcharts in Obsidian using **flowchart.j
|
|||
|
||||
## Features
|
||||
|
||||
- Renders flowcharts using `flowchart.js` syntax within Obsidian notes.
|
||||
- Supports customization of flowchart styles, such as line width, font size, colors, and element styling, directly from the plugin settings.
|
||||
- Adjustable settings for each part of the flowchart, including start and end symbols, decision conditions, operations, and more.
|
||||
- Provides an easy way to create interactive and visually appealing diagrams in your notes.
|
||||
- Renders flowcharts using `flowchart.js` syntax within Obsidian notes.
|
||||
- Supports customization of flowchart styles, such as line width, font size, colors, and element styling, directly from the plugin settings.
|
||||
- Adjustable settings for each part of the flowchart, including start and end symbols, decision conditions, operations, and more.
|
||||
- Provides an easy way to create interactive and visually appealing diagrams in your notes.
|
||||
|
||||
## How to Use
|
||||
|
||||
1. Open a note in Obsidian and add a `flowchart` code block, like this:
|
||||
|
||||
````markdown
|
||||
```flowchart
|
||||
st=>start: Start
|
||||
op=>operation: My Operation
|
||||
cond=>condition: Yes or No?
|
||||
e=>end: End
|
||||
````markdown
|
||||
```flowchart
|
||||
st=>start: Start
|
||||
op=>operation: My Operation
|
||||
cond=>condition: Yes or No?
|
||||
e=>end: End
|
||||
|
||||
st->op->cond
|
||||
cond(yes)->e
|
||||
cond(no)->op
|
||||
```
|
||||
````
|
||||
st->op->cond
|
||||
cond(yes)->e
|
||||
cond(no)->op
|
||||
```
|
||||
````
|
||||
|
||||
2. The plugin will render the flowchart according to the configuration specified in the plugin settings.
|
||||
|
||||
|
|
@ -32,13 +32,13 @@ This is a custom plugin for rendering flowcharts in Obsidian using **flowchart.j
|
|||
|
||||
The plugin includes various settings that allow you to customize the appearance of the flowchart:
|
||||
|
||||
- **Line Width**: Controls the width of lines in the flowchart.
|
||||
- **Font Size**: Sets the font size for text within the flowchart.
|
||||
- **Font Color**: Changes the color of the font.
|
||||
- **Line Color**: Sets the color of the lines connecting elements.
|
||||
- **Element Color**: Adjusts the color of the shapes around text.
|
||||
- **Fill Color**: Sets the background fill color for elements.
|
||||
- **Yes/No Text**: Customizes the labels for decision paths.
|
||||
- **Line Width**: Controls the width of lines in the flowchart.
|
||||
- **Font Size**: Sets the font size for text within the flowchart.
|
||||
- **Font Color**: Changes the color of the font.
|
||||
- **Line Color**: Sets the color of the lines connecting elements.
|
||||
- **Element Color**: Adjusts the color of the shapes around text.
|
||||
- **Fill Color**: Sets the background fill color for elements.
|
||||
- **Yes/No Text**: Customizes the labels for decision paths.
|
||||
|
||||
You can access these settings under **Settings** > **Community Plugins** > **Flowchart Plugin**.
|
||||
|
||||
|
|
@ -48,17 +48,17 @@ You can access these settings under **Settings** > **Community Plugins** > **Flo
|
|||
|
||||
If you're new to Obsidian plugin development:
|
||||
|
||||
- Clone this repository or use it as a template.
|
||||
- Ensure NodeJS is installed (minimum v16).
|
||||
- Run `npm install` to install dependencies.
|
||||
- Use `npm run dev` to compile the plugin and start in watch mode.
|
||||
- Clone this repository or use it as a template.
|
||||
- Ensure NodeJS is installed (minimum v16).
|
||||
- Run `npm install` to install dependencies.
|
||||
- Use `npm run dev` to compile the plugin and start in watch mode.
|
||||
|
||||
### Building the Plugin
|
||||
|
||||
To build the plugin:
|
||||
|
||||
- Run `npm run build` to compile the TypeScript code.
|
||||
- Files will be output to the `dist` folder for use in Obsidian.
|
||||
- Run `npm run build` to compile the TypeScript code.
|
||||
- Files will be output to the `dist` folder for use in Obsidian.
|
||||
|
||||
## Manually Installing the Plugin
|
||||
|
||||
|
|
@ -78,9 +78,9 @@ If you'd like to install the plugin manually:
|
|||
|
||||
If you encounter issues with rendering or plugin behavior:
|
||||
|
||||
- Ensure you are using a compatible version of Obsidian.
|
||||
- Check the developer console (`Cmd + Option + I` on macOS or `Ctrl + Shift + I` on Windows/Linux) for error messages.
|
||||
- Adjust settings to verify if certain configuration options are causing issues.
|
||||
- Ensure you are using a compatible version of Obsidian.
|
||||
- Check the developer console (`Cmd + Option + I` on macOS or `Ctrl + Shift + I` on Windows/Linux) for error messages.
|
||||
- Adjust settings to verify if certain configuration options are causing issues.
|
||||
|
||||
## Contributing
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import esbuild from "esbuild";
|
||||
import process from "process";
|
||||
import builtins from "builtin-modules";
|
||||
import esbuild from 'esbuild';
|
||||
import process from 'process';
|
||||
import builtins from 'builtin-modules';
|
||||
|
||||
const banner = `/*
|
||||
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
|
||||
|
|
@ -8,36 +8,36 @@ if you want to view the source, please visit the github repository of this plugi
|
|||
*/
|
||||
`;
|
||||
|
||||
const prod = process.argv[2] === "production";
|
||||
const prod = process.argv[2] === 'production';
|
||||
|
||||
const context = await esbuild.context({
|
||||
banner: {
|
||||
js: banner,
|
||||
},
|
||||
entryPoints: ["main.ts"],
|
||||
entryPoints: ['main.ts'],
|
||||
bundle: true,
|
||||
external: [
|
||||
"obsidian",
|
||||
"electron",
|
||||
"@codemirror/autocomplete",
|
||||
"@codemirror/collab",
|
||||
"@codemirror/commands",
|
||||
"@codemirror/language",
|
||||
"@codemirror/lint",
|
||||
"@codemirror/search",
|
||||
"@codemirror/state",
|
||||
"@codemirror/view",
|
||||
"@lezer/common",
|
||||
"@lezer/highlight",
|
||||
"@lezer/lr",
|
||||
'obsidian',
|
||||
'electron',
|
||||
'@codemirror/autocomplete',
|
||||
'@codemirror/collab',
|
||||
'@codemirror/commands',
|
||||
'@codemirror/language',
|
||||
'@codemirror/lint',
|
||||
'@codemirror/search',
|
||||
'@codemirror/state',
|
||||
'@codemirror/view',
|
||||
'@lezer/common',
|
||||
'@lezer/highlight',
|
||||
'@lezer/lr',
|
||||
...builtins,
|
||||
],
|
||||
format: "cjs",
|
||||
target: "es2018",
|
||||
logLevel: "info",
|
||||
sourcemap: prod ? false : "inline",
|
||||
format: 'cjs',
|
||||
target: 'es2018',
|
||||
logLevel: 'info',
|
||||
sourcemap: prod ? false : 'inline',
|
||||
treeShaking: true,
|
||||
outfile: "main.js",
|
||||
outfile: 'main.js',
|
||||
minify: prod,
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,25 +1,25 @@
|
|||
const typescriptEslintParser = require("@typescript-eslint/parser");
|
||||
const typescriptEslintPlugin = require("@typescript-eslint/eslint-plugin");
|
||||
const typescriptEslintParser = require('@typescript-eslint/parser');
|
||||
const typescriptEslintPlugin = require('@typescript-eslint/eslint-plugin');
|
||||
|
||||
module.exports = [
|
||||
{
|
||||
ignores: ["node_modules", "main.js"], // Ignoring the default generated files and directories
|
||||
ignores: ['node_modules', 'main.js'], // Ignoring the default generated files and directories
|
||||
},
|
||||
{
|
||||
files: ["**/*.ts"], // Apply the configuration to TypeScript files
|
||||
files: ['**/*.ts'], // Apply the configuration to TypeScript files
|
||||
languageOptions: {
|
||||
parser: typescriptEslintParser,
|
||||
ecmaVersion: "latest", // Always using the latest ECMAScript version
|
||||
sourceType: "module", // Modern JavaScript uses module import/export syntax
|
||||
ecmaVersion: 'latest', // Always using the latest ECMAScript version
|
||||
sourceType: 'module', // Modern JavaScript uses module import/export syntax
|
||||
},
|
||||
plugins: {
|
||||
"@typescript-eslint": typescriptEslintPlugin, // Using TypeScript-specific plugin
|
||||
'@typescript-eslint': typescriptEslintPlugin, // Using TypeScript-specific plugin
|
||||
},
|
||||
rules: {
|
||||
"@typescript-eslint/no-unused-vars": ["error", { args: "none" }], // Replacing 'no-unused-vars' with TS-specific version
|
||||
"@typescript-eslint/ban-ts-comment": "off", // Allowing use of ts comments if needed
|
||||
"@typescript-eslint/no-empty-function": "off", // Allowing empty functions for flexibility
|
||||
"no-prototype-builtins": "off", // Allowing direct calls to object's prototype functions
|
||||
'@typescript-eslint/no-unused-vars': ['error', { args: 'none' }], // Replacing 'no-unused-vars' with TS-specific version
|
||||
'@typescript-eslint/ban-ts-comment': 'off', // Allowing use of ts comments if needed
|
||||
'@typescript-eslint/no-empty-function': 'off', // Allowing empty functions for flexibility
|
||||
'no-prototype-builtins': 'off', // Allowing direct calls to object's prototype functions
|
||||
},
|
||||
},
|
||||
];
|
||||
|
|
|
|||
180
main.ts
180
main.ts
|
|
@ -1,5 +1,5 @@
|
|||
import { App, Plugin, PluginSettingTab, Setting } from "obsidian";
|
||||
import * as flowchart from "flowchart.js";
|
||||
import { App, Plugin, PluginSettingTab, Setting } from 'obsidian';
|
||||
import * as flowchart from 'flowchart.js';
|
||||
|
||||
interface FlowchartPluginSettings {
|
||||
config: Record<string, any>;
|
||||
|
|
@ -9,28 +9,28 @@ const DEFAULT_SETTINGS: FlowchartPluginSettings = {
|
|||
config: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
"line-width": 2,
|
||||
"line-length": 50,
|
||||
"text-margin": 10,
|
||||
"font-size": 14,
|
||||
"font-color": "black",
|
||||
"line-color": "black",
|
||||
"element-color": "black",
|
||||
fill: "white",
|
||||
"yes-text": "yes",
|
||||
"no-text": "no",
|
||||
"arrow-end": "block",
|
||||
'line-width': 2,
|
||||
'line-length': 50,
|
||||
'text-margin': 10,
|
||||
'font-size': 14,
|
||||
'font-color': 'black',
|
||||
'line-color': 'black',
|
||||
'element-color': 'black',
|
||||
fill: 'white',
|
||||
'yes-text': 'yes',
|
||||
'no-text': 'no',
|
||||
'arrow-end': 'block',
|
||||
scale: 1,
|
||||
symbols: {
|
||||
start: {
|
||||
"font-color": "black",
|
||||
"element-color": "black",
|
||||
fill: "white",
|
||||
'font-color': 'black',
|
||||
'element-color': 'black',
|
||||
fill: 'white',
|
||||
},
|
||||
end: {
|
||||
"font-color": "black",
|
||||
"element-color": "black",
|
||||
fill: "white",
|
||||
'font-color': 'black',
|
||||
'element-color': 'black',
|
||||
fill: 'white',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -42,15 +42,12 @@ export default class FlowchartPlugin extends Plugin {
|
|||
async onload() {
|
||||
await this.loadSettings();
|
||||
|
||||
console.log("Flowchart plugin loaded");
|
||||
console.log('Flowchart plugin loaded');
|
||||
|
||||
// Register the markdown post processor for 'flowchart' code blocks
|
||||
this.registerMarkdownCodeBlockProcessor(
|
||||
"flowchart",
|
||||
(source, el, ctx) => {
|
||||
this.renderFlowchart(source, el);
|
||||
},
|
||||
);
|
||||
this.registerMarkdownCodeBlockProcessor('flowchart', (source, el, ctx) => {
|
||||
this.renderFlowchart(source, el);
|
||||
});
|
||||
|
||||
this.addSettingTab(new FlowchartSettingTab(this.app, this));
|
||||
}
|
||||
|
|
@ -60,17 +57,17 @@ export default class FlowchartPlugin extends Plugin {
|
|||
requestAnimationFrame(() => {
|
||||
try {
|
||||
const diagram = flowchart.parse(source);
|
||||
const container = el.createEl("div", {
|
||||
cls: "obsidian-flowchart-container",
|
||||
const container = el.createEl('div', {
|
||||
cls: 'obsidian-flowchart-container',
|
||||
});
|
||||
diagram.drawSVG(container, this.settings.config);
|
||||
|
||||
// Apply a fix for deprecated xlink attributes
|
||||
this.fixXlinkAttributes(container);
|
||||
} catch (error) {
|
||||
console.error("Error rendering flowchart: ", error);
|
||||
el.createEl("div", {
|
||||
text: "Error rendering flowchart. Check your markup.",
|
||||
console.error('Error rendering flowchart: ', error);
|
||||
el.createEl('div', {
|
||||
text: 'Error rendering flowchart. Check your markup.',
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
@ -78,26 +75,22 @@ export default class FlowchartPlugin extends Plugin {
|
|||
|
||||
fixXlinkAttributes(el: HTMLElement) {
|
||||
// Find elements with deprecated xlink attributes and replace them
|
||||
const elements = el.querySelectorAll("[*|href]");
|
||||
const elements = el.querySelectorAll('[*|href]');
|
||||
elements.forEach((element) => {
|
||||
const xlinkHref = element.getAttribute("xlink:href");
|
||||
const xlinkHref = element.getAttribute('xlink:href');
|
||||
if (xlinkHref) {
|
||||
element.setAttribute("href", xlinkHref);
|
||||
element.removeAttribute("xlink:href");
|
||||
element.setAttribute('href', xlinkHref);
|
||||
element.removeAttribute('xlink:href');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onunload() {
|
||||
console.log("Flowchart plugin unloaded");
|
||||
console.log('Flowchart plugin unloaded');
|
||||
}
|
||||
|
||||
async loadSettings() {
|
||||
this.settings = Object.assign(
|
||||
{},
|
||||
DEFAULT_SETTINGS,
|
||||
await this.loadData(),
|
||||
);
|
||||
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
|
||||
}
|
||||
|
||||
async saveSettings() {
|
||||
|
|
@ -118,127 +111,114 @@ class FlowchartSettingTab extends PluginSettingTab {
|
|||
|
||||
containerEl.empty();
|
||||
|
||||
containerEl.createEl("h2", { text: "Flowchart Plugin Settings" });
|
||||
containerEl.createEl('h2', { text: 'Flowchart Plugin Settings' });
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Line Width")
|
||||
.setDesc("Set the line width for the flowchart.")
|
||||
.setName('Line Width')
|
||||
.setDesc('Set the line width for the flowchart.')
|
||||
.addText((text) =>
|
||||
text
|
||||
.setValue(
|
||||
this.plugin.settings.config["line-width"].toString(),
|
||||
)
|
||||
.setValue(this.plugin.settings.config['line-width'].toString())
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.config["line-width"] =
|
||||
parseInt(value) || 2;
|
||||
this.plugin.settings.config['line-width'] = parseInt(value) || 2;
|
||||
await this.plugin.saveSettings();
|
||||
}),
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Line Length")
|
||||
.setDesc("Set the line length for the flowchart.")
|
||||
.setName('Line Length')
|
||||
.setDesc('Set the line length for the flowchart.')
|
||||
.addText((text) =>
|
||||
text
|
||||
.setValue(
|
||||
this.plugin.settings.config["line-length"].toString(),
|
||||
)
|
||||
.setValue(this.plugin.settings.config['line-length'].toString())
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.config["line-length"] =
|
||||
parseInt(value) || 2;
|
||||
this.plugin.settings.config['line-length'] = parseInt(value) || 2;
|
||||
await this.plugin.saveSettings();
|
||||
}),
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Yes Text")
|
||||
.setDesc("Text for Yes responses in the flowchart.")
|
||||
.setName('Yes Text')
|
||||
.setDesc('Text for Yes responses in the flowchart.')
|
||||
.addText((text) =>
|
||||
text
|
||||
.setValue(this.plugin.settings.config["yes-text"])
|
||||
.setValue(this.plugin.settings.config['yes-text'])
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.config["yes-text"] =
|
||||
value || "yes";
|
||||
this.plugin.settings.config['yes-text'] = value || 'yes';
|
||||
await this.plugin.saveSettings();
|
||||
}),
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("No Text")
|
||||
.setDesc("Text for No responses in the flowchart.")
|
||||
.setName('No Text')
|
||||
.setDesc('Text for No responses in the flowchart.')
|
||||
.addText((text) =>
|
||||
text
|
||||
.setValue(this.plugin.settings.config["no-text"])
|
||||
.setValue(this.plugin.settings.config['no-text'])
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.config["no-text"] = value || "no";
|
||||
this.plugin.settings.config['no-text'] = value || 'no';
|
||||
await this.plugin.saveSettings();
|
||||
}),
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Font Size")
|
||||
.setDesc("Set the font size for the flowchart.")
|
||||
.setName('Font Size')
|
||||
.setDesc('Set the font size for the flowchart.')
|
||||
.addText((text) =>
|
||||
text
|
||||
.setValue(
|
||||
this.plugin.settings.config["font-size"].toString(),
|
||||
)
|
||||
.setValue(this.plugin.settings.config['font-size'].toString())
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.config["font-size"] =
|
||||
parseInt(value) || 14;
|
||||
this.plugin.settings.config['font-size'] = parseInt(value) || 14;
|
||||
await this.plugin.saveSettings();
|
||||
}),
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Font Color")
|
||||
.setDesc("Set the font color for the flowchart.")
|
||||
.setName('Font Color')
|
||||
.setDesc('Set the font color for the flowchart.')
|
||||
.addColorPicker((picker) =>
|
||||
picker
|
||||
.setValue(this.plugin.settings.config["font-color"])
|
||||
.setValue(this.plugin.settings.config['font-color'])
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.config["font-color"] =
|
||||
value || "black";
|
||||
this.plugin.settings.config['font-color'] = value || 'black';
|
||||
await this.plugin.saveSettings();
|
||||
}),
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Line Color")
|
||||
.setDesc("Set the line color for the flowchart.")
|
||||
.setName('Line Color')
|
||||
.setDesc('Set the line color for the flowchart.')
|
||||
.addColorPicker((picker) =>
|
||||
picker
|
||||
.setValue(this.plugin.settings.config["line-color"])
|
||||
.setValue(this.plugin.settings.config['line-color'])
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.config["line-color"] =
|
||||
value || "black";
|
||||
this.plugin.settings.config['line-color'] = value || 'black';
|
||||
await this.plugin.saveSettings();
|
||||
}),
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Element Color")
|
||||
.setDesc("Set the element color for the flowchart.")
|
||||
.setName('Element Color')
|
||||
.setDesc('Set the element color for the flowchart.')
|
||||
.addColorPicker((picker) =>
|
||||
picker
|
||||
.setValue(this.plugin.settings.config["element-color"])
|
||||
.setValue(this.plugin.settings.config['element-color'])
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.config["element-color"] =
|
||||
value || "black";
|
||||
this.plugin.settings.config['element-color'] = value || 'black';
|
||||
await this.plugin.saveSettings();
|
||||
}),
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Fill Color")
|
||||
.setDesc("Set the background fill color for the flowchart.")
|
||||
.setName('Fill Color')
|
||||
.setDesc('Set the background fill color for the flowchart.')
|
||||
.addColorPicker((picker) =>
|
||||
picker
|
||||
.setValue(this.plugin.settings.config["fill"])
|
||||
.setValue(this.plugin.settings.config['fill'])
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.config["fill"] = value || "white";
|
||||
this.plugin.settings.config['fill'] = value || 'white';
|
||||
await this.plugin.saveSettings();
|
||||
}),
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@
|
|||
"dev": "node esbuild.config.mjs",
|
||||
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
|
||||
"version": "node version-bump.mjs && git add manifest.json versions.json",
|
||||
"format": "prettier --write ."
|
||||
"format": "prettier --write .",
|
||||
"lint": "eslint '**/*.ts'"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "land0r",
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
import { readFileSync, writeFileSync } from "fs";
|
||||
import { readFileSync, writeFileSync } from 'fs';
|
||||
|
||||
const targetVersion = process.env.npm_package_version;
|
||||
|
||||
// read minAppVersion from manifest.json and bump version to target version
|
||||
let manifest = JSON.parse(readFileSync("manifest.json", "utf8"));
|
||||
let manifest = JSON.parse(readFileSync('manifest.json', 'utf8'));
|
||||
const { minAppVersion } = manifest;
|
||||
manifest.version = targetVersion;
|
||||
writeFileSync("manifest.json", JSON.stringify(manifest, null, "\t"));
|
||||
writeFileSync('manifest.json', JSON.stringify(manifest, null, '\t'));
|
||||
|
||||
// update versions.json with target version and minAppVersion from manifest.json
|
||||
let versions = JSON.parse(readFileSync("versions.json", "utf8"));
|
||||
let versions = JSON.parse(readFileSync('versions.json', 'utf8'));
|
||||
versions[targetVersion] = minAppVersion;
|
||||
writeFileSync("versions.json", JSON.stringify(versions, null, "\t"));
|
||||
writeFileSync('versions.json', JSON.stringify(versions, null, '\t'));
|
||||
|
|
|
|||
Loading…
Reference in a new issue