Repository: coddx-hq/coddx-alpha Branch: master Commit: fb0ac6595a5a Files: 46 Total size: 96.3 KB Directory structure: gitextract_gosdofly/ ├── .coddx-template ├── .editorconfig ├── .gitignore ├── .prettierrc.yml ├── .vscodeignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── TODO.md ├── docs/ │ ├── documentation.md │ ├── generate-files.md │ └── task-board.md ├── package.json ├── src/ │ ├── .editorconfig │ ├── .gitignore │ ├── .prettierrc.yml │ ├── .vscodeignore │ ├── extension.ts │ ├── test/ │ │ ├── extension.test.ts │ │ └── index.ts │ ├── view/ │ │ ├── TaskBoardLoader.ts │ │ ├── ViewLoader.ts │ │ └── app/ │ │ ├── Utils.ts │ │ ├── components/ │ │ │ ├── CodeGen/ │ │ │ │ ├── CodeEditor.css │ │ │ │ ├── CodeEditor.tsx │ │ │ │ ├── EditTemplatePanel.tsx │ │ │ │ ├── MainView.css │ │ │ │ └── MainView.tsx │ │ │ └── TaskBoard/ │ │ │ ├── ButtonBar.tsx │ │ │ ├── Helpers.tsx │ │ │ ├── SearchInput.tsx │ │ │ ├── Task.tsx │ │ │ ├── TaskBoard.css │ │ │ ├── TaskBoard.tsx │ │ │ ├── TaskColumn.tsx │ │ │ └── TaskMenu.tsx │ │ ├── config.tsx │ │ ├── index.css │ │ ├── index.tsx │ │ ├── model.ts │ │ └── tsconfig.json │ └── webpack.config.js ├── tsconfig.json ├── tslint.json ├── vsc-extension-quickstart.md └── webpack.config.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .coddx-template ================================================ // -------------->> {{fileName}}.tsx // created time: {{YYYY}}-{{MM}}-{{DD}} {{HH}}:{{mm}} import * as React from 'react'; import './{{fileName}}.css'; export default class {{fileName}} extends React.Component { render() { return (
{/* ...code goes here... */}
) } } // -------------->> {{fileName}}.css .main {} // -------------->> __test__/{{fileName}}.test.tsx import * as React from 'react'; it('{{fileName}} - render', () => { // render(<{{fileName}} />); // expect(...); }); ================================================ FILE: .editorconfig ================================================ # EditorConfig: http://EditorConfig.org root = true [*] indent_style = space indent_size = 2 end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true [*.md] trim_trailing_whitespace = false ================================================ FILE: .gitignore ================================================ out node_modules .vscode-test/ *.vsix configViewer/configViewer.js ================================================ FILE: .prettierrc.yml ================================================ --- printWidth: 120 tabWidth: 2 useTabs: false semi: true singleQuote: true trailingComma: none bracketSpacing: true jsxBracketSameLine: false arrowParens: avoid ================================================ FILE: .vscodeignore ================================================ .vscode/** .vscode-test/** out/test/** src/** .gitignore vsc-extension-quickstart.md **/tsconfig.json **/tslint.json **/*.map **/*.ts ================================================ FILE: CHANGELOG.md ================================================ # Change Log ## [0.3.0] - 2021-05-24 - Task title supports markdown now for styling, hyperlinks, simple html or even img tags. - New Task Action: move a task to the column on the right. ## [0.2.27] - 2020-03-28 - Task Board - support multiple task lists defined in user's settings.json. - Task Board - refresh button to reload file content. - Task Board - checkbox is now optional (if task title doesn't have it). - Task Board - support sub-task (task title starts with 2-space indentation). - Task Board - task menu for: toggling sub-task; inserting emojis; - Task Board - respect theme colors. ## [0.2.12] - 2020-03-21 - Task Board - search box - Task Board - autofocus when creating a new task. - Task Board - checkmark to mark a task as complete. - Task Board Doc ## [0.2.3] - 2020-03-15 - Task Board - manage tasks and save them as TODO.md - a simple plain text file. The syntax is compatible with [Github Markdown](https://bit.ly/2wBp1Mk) ## [0.1.4] - 2020-03-10 - Output can be edited before generating files. ## [0.1.3] - Initial release - Follow this format - Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file. ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2021 Duc Nguyen Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Todo Kanban Board Todo Kanban Board manages tasks and save them as [TODO.md](https://bit.ly/2JdEuET) - a simple plain text file. ## Features - The syntax is compatible with [Github Markdown](https://bit.ly/2wBp1Mk) - TODO.md file is portable and can be committed with Pull Requests (PRs) to git repositories. - Support custom file name, multiple task lists. - Checkboxes are optional (if your task titles don't have them). - Task title can also have markdown for styling, hyperlinks, simple html or even img tags. - Task menu: to insert a sub-task, emoji icons (like bug 🐞 blocked ❌ party 🎉 etc.). - See also: Documentation / Guides ## Usage: - Open Coddx Task Board: - Bring up the Command Palette (F1), type and select: Coddx: Task Board. - When interacting with the Task Board, TODO.md will be created or updated automatically. - Vice versa, TODO.md can be edited manually, Task Board will load it every time (click the Refresh icon).
## Support - For Feedbacks, Bug Reports: https://github.com/coddx-hq/coddx-alpha/issues - Documentation: Documentation / Guides - CHANGELOG ## Next milestone: - Feedbacks, suggestions or ideas are welcome! Thanks. - Check out [TODO.md](TODO.md) ================================================ FILE: TODO.md ================================================ # TODO.md Kanban Board TODO.md Kanban Board [TODO.md spec & Kanban Board](https://bit.ly/3fCwKfM) ### Todo - [ ] Publish on open-vsx.org - [ ] Get Todo\*.md files for the dropdown - [ ] Rename Git Repos - [ ] Update Readme, Docs - [ ] Update all URLs - [ ] Animate Search Input - [ ] Support Task description - [ ] Markdown links for File or URL - [ ] Press _Enter_ to add the next task ### In Progress - [ ] Task menu: add hyperlinks ? - [ ] Smart positioning for Task Menu - [ ] Change icon on Marketplace - [ ] 🐞 Fix sub-task toggling for a new task - [ ] 🐞 Bug: it flashes the default data, then loads the real data ### Done ✓ - [x] Markdown text formats 05/21/2021 - [x] New task should be at the top 05/20/2021 - [x] Dropdown option to help about adding more todo files 05/19/2021 - [x] Respect theme colors - [x] Task menu: Insert Emojis 😊 - [x] Task menu: Toggle sub-task - [x] Rename to Todo.md Kanban Board - [x] Support sub-task - [x] Optional checkbox in task title - [x] Default to the 1st Task List - [x] Add Help link - [x] Multiple task lists - [x] Add Search Box - [x] Auto save for Task Re-ordering - [x] Add Open File link - [x] Split to different Doc Topics - [x] Auto focus on New Task 2020-03-18 - [x] Check mark to mark as Done 2020-03-18 - [x] Setup coddx mailbox ================================================ FILE: docs/documentation.md ================================================ # Documentation ### Task Board - TODO.md [Task Board Doc](task-board.md) ### Generate Files [Generate Files Doc](generate-files.md) ================================================ FILE: docs/generate-files.md ================================================ # Generate Files ### Features - Work with any programming languages. - Templating syntax. - Store templates as a single file (can be committed to git, shared with others). - Auto-create sub-directories. - Built-in params (fileName, date, time, etc.). - Custom params. - Context menu to generate files/directories from anywhere. #### Usage: - Open Coddx panel: - Bring up the Command Palette (F1), type and select: Coddx: Generate Files. - Or: right-click on a directory, select: Generate files. - Verify Template to suit your needs. (See Docs for syntax) - Verify the output path (relative to Project root), a new directory will be created if not existed. - Enter the new file name to generate file(s). ### How does it work? Coddx uses Mozilla's Templating Engine called nunjucks. Nunjucks is a powerful library, supports more advanced use cases like parameters, custom filters, etc. ### Templating Syntax Example: ``` // -------------->> {{fileName}}.tsx // created time: {{YYYY}}-{{MM}}-{{DD}} {{HH}}:{{mm}} // file1 content... // -------------->> __test__/{{fileName}}.test.tsx // file2 content... (sub-directory will be created) ``` Built-in Template Variables: - {{fileName}} - will be replace with the value of "File Name" field. - {{YYYY}}, {{MM}}, {{DD}}, {{HH}}, {{mm}} - year, month, day, hours (24), minutes. Read more: Nunjucks Templating Syntax ### Custom Variables You can declare custom variables (Params field) in JSON format, for example: - { "myVar": "value" } - declare `myVar`, then use it in your template like `{{myVar}}` ### Custom filters (Coming soon) ================================================ FILE: docs/task-board.md ================================================ # Todo Kanban Board Todo Kanban Board manages tasks and save them as [TODO.md](https://bit.ly/2JdEuET) - a simple plain text file. ## Features - The syntax is compatible with [Github Markdown](https://bit.ly/2wBp1Mk) - TODO.md file is portable and can be committed with Pull Requests (PRs) to git repositories. - Support custom file name, multiple task lists. - Checkboxes are optional (if your task titles don't have them). - Task title can also have markdown for styling, hyperlinks, simple html or even img tags. - Task menu: to insert a sub-task, emoji icons (like bug 🐞 blocked ❌ party 🎉 etc.). - See also: Documentation / Guides ## TODO.md - Tasks are synced to the markdown file using [TODO.md format](https://bit.ly/2JdEuET). - Task Board is a bit strict about the format. - Please follow the typical structure like in the example so it can work properly. If it fails to open, please revert or re-generate your TODO.md file to make it work again. - For now, after making changes to TODO.md file, please click the Refresh icon (next to the board title) to reload. - [Example of a TODO.md file](https://github.com/todomd/todo.md/blob/master/TODO.md) ## Settings - Multiple TODO files: - In your workspace settings.json file, add this: `"coddx.taskBoard.fileList": "TODO.md, folder/TODO-name.md"` (comma separated, use your file names) ## Tips & Tricks - In the task title: - You can type `#bug` or `#feat` to classify your task. - You can type a name like `@john`, `@jane`. - Date format can be yyyy-mm-dd - Use the Search Box to filter for types, names, etc. ================================================ FILE: package.json ================================================ { "name": "coddx-alpha", "displayName": "TODO.md Kanban Board", "description": "Coddx - a collection of tools that help developers program efficiently. Some useful features like: Kanban Board to manage project tasks in TODO.md, generating multiple files from templates quickly.", "version": "0.2.55", "publisher": "coddx", "license": "MIT", "homepage": "https://github.com/coddx-hq", "repository": { "type": "git", "url": "https://github.com/coddx-hq/coddx-alpha.git" }, "keywords": [ "todo", "todolist", "kanban", "task", "project", "management", "generate", "generator", "template", "webdev", "snippet", "file" ], "icon": "docs/media/logo.png", "engines": { "vscode": "^1.29.0" }, "categories": [ "Extension Packs", "Programming Languages", "Snippets", "Other" ], "activationEvents": [ "onCommand:extension.viewconfig", "onCommand:extension.taskboard" ], "main": "./out/extension.js", "contributes": { "languages": [], "commands": [ { "command": "extension.viewconfig", "title": "Generate files", "category": "Coddx" }, { "command": "extension.taskboard", "title": "TODO.md Kanban Task Board", "category": "Coddx" } ], "menus": { "explorer/context": [ { "command": "extension.viewconfig" } ] } }, "scripts": { "vscode:prepublish": "npm run compile", "compile": "npm-run-all compile:*", "watch": "npm-run-all -p watch:*", "compile:extension": "rm -rf out && tsc --jsx react -p ./", "compile:views": "webpack --mode development", "watch:extension": "tsc --jsx react -watch -p ./", "watch:views": "webpack --watch --mode development", "postinstall": "node ./node_modules/vscode/bin/install", "test": "npm run compile && node ./node_modules/vscode/bin/test" }, "devDependencies": { "@types/marked": "^2.0.2", "@types/mocha": "^2.2.42", "@types/node": "^10.12.21", "@types/react": "^16.9.0", "@types/react-dom": "^16.9.0", "css-loader": "^3.0.0", "npm-run-all": "^4.1.5", "style-loader": "^0.23.1", "ts-loader": "^6.0.4", "tslint": "^5.12.1", "typescript": "^3.3.1", "vscode": "^1.1.37", "webpack": "^4.35.2", "webpack-cli": "^3.3.5" }, "dependencies": { "marked": "^2.0.4", "nunjucks": "^3.2.0", "prismjs": "^1.19.0", "react": "^16.13.0", "react-autosize-textarea": "^7.0.0", "react-beautiful-dnd": "^13.0.0", "react-dom": "^16.13.0", "react-select": "^3.1.0", "react-simple-code-editor": "^0.11.0", "styled-components": "^5.0.1" } } ================================================ FILE: src/.editorconfig ================================================ # EditorConfig: http://EditorConfig.org root = true [*] indent_style = space indent_size = 2 end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true [*.md] trim_trailing_whitespace = false ================================================ FILE: src/.gitignore ================================================ out node_modules .vscode-test/ *.vsix configViewer/configViewer.js ================================================ FILE: src/.prettierrc.yml ================================================ --- printWidth: 120 tabWidth: 2 useTabs: false semi: true singleQuote: true trailingComma: none bracketSpacing: true jsxBracketSameLine: false arrowParens: avoid ================================================ FILE: src/.vscodeignore ================================================ .vscode/** .vscode-test/** out/test/** src/** .gitignore vsc-extension-quickstart.md **/tsconfig.json **/tslint.json **/*.map **/*.ts ================================================ FILE: src/extension.ts ================================================ // The module 'vscode' contains the VS Code extensibility API // Import the module and reference it with the alias vscode in your code below import * as vscode from 'vscode'; // import { telemetry } from './view/app/Utils'; import ViewLoader from './view/ViewLoader'; import TaskBoardLoader from './view/TaskBoardLoader'; // this method is called when your extension is activated // your extension is activated the very first time the command is executed export function activate(context: vscode.ExtensionContext) { // Use the console to output diagnostic information (console.log) and errors (console.error) // This line of code will only be executed once when your extension is activated // console.log('Congratulations, your extension "vscode-react" is now active!'); // context.subscriptions.push(telemetry); // The command has been defined in the package.json file // Now provide the implementation of the command with registerCommand // The commandId parameter must match the command field in package.json let disposable = vscode.commands.registerCommand('extension.viewconfig', (uri: vscode.Uri) => { // let openDialogOptions: vscode.OpenDialogOptions = { // canSelectFiles: true, // canSelectFolders: false, // canSelectMany: false, // filters: { // Json: ["json"] // } // }; // vscode.window // .showOpenDialog(openDialogOptions) // .then(async (uri: vscode.Uri[] | undefined) => { // if (uri && uri.length > 0) { // const view = new ViewLoader(uri[0], context.extensionPath); // } else { // vscode.window.showErrorMessage("No valid file selected!"); // return; // } // }); const view = new ViewLoader(context.extensionPath, uri); // telemetry.sendTelemetryEvent('init-file-generator'); return view; }); context.subscriptions.push(disposable); let taskBoardCmd = vscode.commands.registerCommand('extension.taskboard', (uri: vscode.Uri) => { const view = new TaskBoardLoader(context.extensionPath, uri); // telemetry.sendTelemetryEvent('init-task-board'); return view; }); context.subscriptions.push(taskBoardCmd); } // this method is called when your extension is deactivated export function deactivate() { // telemetry.dispose(); } ================================================ FILE: src/test/extension.test.ts ================================================ // // Note: This example test is leveraging the Mocha test framework. // Please refer to their documentation on https://mochajs.org/ for help. // // The module 'assert' provides assertion methods from node import * as assert from 'assert'; // You can import and use all API from the 'vscode' module // as well as import your extension to test it // import * as vscode from 'vscode'; // import * as myExtension from '../extension'; // Defines a Mocha test suite to group tests of similar kind together suite("Extension Tests", function () { // Defines a Mocha unit test test("Something 1", function() { assert.equal(-1, [1, 2, 3].indexOf(5)); assert.equal(-1, [1, 2, 3].indexOf(0)); }); }); ================================================ FILE: src/test/index.ts ================================================ // // PLEASE DO NOT MODIFY / DELETE UNLESS YOU KNOW WHAT YOU ARE DOING // // This file is providing the test runner to use when running extension tests. // By default the test runner in use is Mocha based. // // You can provide your own test runner if you want to override it by exporting // a function run(testsRoot: string, clb: (error: Error, failures?: number) => void): void // that the extension host can call to run the tests. The test runner is expected to use console.log // to report the results back to the caller. When the tests are finished, return // a possible error to the callback or null if none. import * as testRunner from 'vscode/lib/testrunner'; // You can directly control Mocha options by configuring the test runner below // See https://github.com/mochajs/mocha/wiki/Using-mocha-programmatically#set-options // for more info testRunner.configure({ ui: 'tdd', // the TDD UI is being used in extension.test.ts (suite, test, etc.) useColors: true // colored output from test results }); module.exports = testRunner; ================================================ FILE: src/view/TaskBoardLoader.ts ================================================ import * as vscode from 'vscode'; import * as fs from 'fs'; import * as path from 'path'; import { IConfig, ICommand, CommandAction } from './app/model'; import { deepFind, VER } from './app/Utils'; let __panel = null; let selectedFile = ''; export default class ViewLoader { private readonly _panel: vscode.WebviewPanel | undefined; private readonly _extensionPath: string = ''; private _disposables: vscode.Disposable[] = []; constructor(extensionPath: string, uri: vscode.Uri) { // load "coddx.taskBoard.fileList" from config (settings): const configuration = vscode.workspace.getConfiguration(); const fileList: string = configuration.get('coddx.taskBoard.fileList') || 'TODO.md'; const filesArr = fileList.split(',').map(str => str.trim()); selectedFile = filesArr[0]; this._extensionPath = extensionPath; const column = vscode.window.activeTextEditor ? vscode.ViewColumn.Two // vscode.window.activeTextEditor.viewColumn vscode.ViewColumn.Two : undefined; // let config = this.getFileContent(); // if (config) { this._panel = vscode.window.createWebviewPanel('configView', 'Task Board', column || vscode.ViewColumn.Two, { enableScripts: true, localResourceRoots: [vscode.Uri.file(path.join(extensionPath, 'configViewer'))] }); // get base path (from the user's workspace path): const rootPath = deepFind(vscode, 'workspace.workspaceFolders[0].uri.fsPath', '') + '/'; // const templateFilePath = rootPath + '/TODO.md'; let basePath = ''; // relative if (uri && uri.fsPath) { // path from Context Menu basePath = uri.fsPath.replace(rootPath, ''); // get relative path } // const fileUri = vscode.Uri.file(templateFilePath); const todoStr = ''; // this.getFileContent(fileUri); this._panel.webview.html = this.getWebviewContent({ basePath, templateString: todoStr || '', fileList, selectedFile, rootPath }); __panel = this._panel; this._panel.webview.onDidReceiveMessage( (command: ICommand) => { switch (command.action) { case CommandAction.ShowMessage: vscode.window.showInformationMessage(command.content.description); return; case CommandAction.OpenFile: const rootPath2 = deepFind(vscode, 'workspace.workspaceFolders[0].uri.fsPath', '') + '/'; const filePath2 = rootPath2 + (selectedFile || 'TODO.md'); vscode.window.showTextDocument(vscode.Uri.file(filePath2)); return; case CommandAction.Save: this.saveFileContent(command.content); return; case CommandAction.Load: selectedFile = command.content.description || 'TODO.md'; const rootPath3 = deepFind(vscode, 'workspace.workspaceFolders[0].uri.fsPath', '') + '/'; const filePath3 = rootPath3 + selectedFile; const fileUri = vscode.Uri.file(filePath3); const todoStr = this.getFileContent(fileUri); this._panel.webview.html = this.getWebviewContent({ basePath, templateString: todoStr || '', fileList, selectedFile, rootPath }); // __panel.webview.postMessage({ command: 'load', content }); // Doesn't work ??? return; // case CommandAction.GetListFiles: // load config (settings) // vscode.window.showInformationMessage(`👍 Config:`, JSON.stringify(filesArr)); // WORKS! // const workspace = vscode.workspace.workspaceFolders[0]; // if (workspace) { // vscode.workspace // .findFiles(new vscode.RelativePattern(workspace, '**/*/TODO.md'), '**/node_modules/**') // .then(results => { // console.log('results: ', results); // }); // } // return; } }, undefined, this._disposables ); // } } private getWebviewContent({ basePath, templateString, fileList, selectedFile, rootPath }): string { // Local path to main script run in the webview const reactAppPathOnDisk = vscode.Uri.file(path.join(this._extensionPath, 'configViewer', 'configViewer.js')); const reactAppUri = reactAppPathOnDisk.with({ scheme: 'vscode-resource' }); // const configJson = JSON.stringify(config); const fullPath = deepFind(vscode, 'workspace.workspaceFolders[0].uri.fsPath', '') + `/${selectedFile}`; // rootPathBase64 = toBase64(rootPathBase64); return ` Task Board
`; } private getFileContent(fileUri: vscode.Uri) { //: IConfig | undefined { if (fs.existsSync(fileUri.fsPath)) { let content = fs.readFileSync(fileUri.fsPath, 'utf8'); // let config: IConfig = JSON.parse(content); // return config; return content; } return undefined; } private saveFileContent(config: IConfig) { const content = config.description; const rootPath = deepFind(vscode, 'workspace.workspaceFolders[0].uri.fsPath', '') + '/'; const filePath = rootPath + (selectedFile || 'TODO.md'); const uri = vscode.Uri.file(filePath); fs.writeFileSync(uri.fsPath, content); // vscode.window.showInformationMessage(`👍 TODO.md saved!`); } } ================================================ FILE: src/view/ViewLoader.ts ================================================ import * as vscode from 'vscode'; import * as fs from 'fs'; import * as path from 'path'; import { IConfig, ICommand, CommandAction } from './app/model'; import { deepFind, VER } from './app/Utils'; export default class ViewLoader { private readonly _panel: vscode.WebviewPanel | undefined; private readonly _extensionPath: string = ''; private _disposables: vscode.Disposable[] = []; constructor(extensionPath: string, uri: vscode.Uri) { this._extensionPath = extensionPath; const column = vscode.window.activeTextEditor ? vscode.ViewColumn.Two // vscode.window.activeTextEditor.viewColumn : undefined; // let config = this.getFileContent(); // if (config) { this._panel = vscode.window.createWebviewPanel( 'configView', 'Coddx - Generate Files', column || vscode.ViewColumn.Two, { enableScripts: true, localResourceRoots: [vscode.Uri.file(path.join(extensionPath, 'configViewer'))] } ); // get base path (from the user's workspace path): const rootPath = deepFind(vscode, 'workspace.workspaceFolders[0].uri.fsPath', '') + '/'; const templateFilePath = rootPath + '/.coddx-template'; let basePath = ''; // relative if (uri && uri.fsPath) { // path from Context Menu basePath = uri.fsPath.replace(rootPath, ''); // get relative path } // if (uri.fsPath) { // basePath = uri.fsPath; // } else if (vscode.workspace.workspaceFolders) { // basePath = vscode.workspace.workspaceFolders[0].uri.fsPath; // } // load template file: // const filePath = '/Users/duc/Documents/downloads/coddx-ext/.coddx-template'; const fileUri = vscode.Uri.file(templateFilePath); const templateString = this.getFileContent(fileUri); this._panel.webview.html = this.getWebviewContent(basePath, templateString || ''); this._panel.webview.onDidReceiveMessage( (command: ICommand) => { switch (command.action) { case CommandAction.Save: const fileUri = vscode.Uri.file(templateFilePath); this.saveFileContent(fileUri, command.content); return; case CommandAction.GenerateFiles: this.generateFiles(command.content); return; } }, undefined, this._disposables ); // } } // constructor(fileUri: vscode.Uri, extensionPath: string) { // this._extensionPath = extensionPath; // const column = vscode.window.activeTextEditor // ? vscode.ViewColumn.Two // vscode.window.activeTextEditor.viewColumn // : undefined; // let config = this.getFileContent(fileUri); // if (config) { // this._panel = vscode.window.createWebviewPanel( // "configView", // "Config View", // column || vscode.ViewColumn.Two, // { // enableScripts: true, // localResourceRoots: [ // vscode.Uri.file(path.join(extensionPath, "configViewer")) // ] // } // ); // this._panel.webview.html = this.getWebviewContent(config); // this._panel.webview.onDidReceiveMessage( // (command: ICommand) => { // switch (command.action) { // case CommandAction.Save: // this.saveFileContent(fileUri, command.content); // return; // } // }, // undefined, // this._disposables // ); // } // } private getWebviewContent(basePath: string, templateString: string): string { // Local path to main script run in the webview const reactAppPathOnDisk = vscode.Uri.file(path.join(this._extensionPath, 'configViewer', 'configViewer.js')); const reactAppUri = reactAppPathOnDisk.with({ scheme: 'vscode-resource' }); // const configJson = JSON.stringify(config); return ` Coddx - Generate Files
`; } private getFileContent(fileUri: vscode.Uri) { //: IConfig | undefined { if (fs.existsSync(fileUri.fsPath)) { let content = fs.readFileSync(fileUri.fsPath, 'utf8'); // let config: IConfig = JSON.parse(content); // return config; return content; } return undefined; } private saveFileContent(fileUri: vscode.Uri, config: IConfig) { // if (fs.existsSync(fileUri.fsPath)) {} fs.writeFileSync(fileUri.fsPath, config.description || ''); vscode.window.showInformationMessage(`👍 Template saved to .coddx-template`); } private mkDirByPathSync(targetDir: string, { baseDir = '', isRelativeToScript = false } = {}) { const sep = path.sep; const initDir = path.isAbsolute(targetDir) ? sep : ''; // const baseDir = isRelativeToScript ? __dirname : '.'; return targetDir.split(sep).reduce((parentDir, childDir) => { const curDir = path.resolve(baseDir, parentDir, childDir); try { fs.mkdirSync(curDir); } catch (err) { if (err.code === 'EEXIST') { // curDir already exists! return curDir; } // To avoid `EISDIR` error on Mac and `EACCES`-->`ENOENT` and `EPERM` on Windows. if (err.code === 'ENOENT') { // Throw the original parentDir error on curDir `ENOENT` failure. throw new Error(`EACCES: permission denied, mkdir '${parentDir}'`); } const caughtErr = ['EACCES', 'EPERM', 'EISDIR'].indexOf(err.code) > -1; if (!caughtErr || (caughtErr && curDir === path.resolve(targetDir))) { throw err; // Throw if it's just the last created dir. } } return curDir; }, initDir); } private generateFiles(config: IConfig) { const { path, files } = JSON.parse(config.description || ''); let counter = 0; const rootPath = deepFind(vscode, 'workspace.workspaceFolders[0].uri.fsPath', '') + '/'; if (!fs.existsSync(rootPath + path)) { this.mkDirByPathSync(path, { baseDir: rootPath }); } Object.keys(files).forEach(itemKey => { const item = files[itemKey]; if (item.checked === true) { const fileName = item.fileName; // itemKey.trim() const filePath = rootPath + (path + '/' + fileName).trim(); if (fileName.indexOf('/') > 0) { // create directories, e.g. __test__/mocks const dirPath = fileName.substr(0, fileName.lastIndexOf('/')); this.mkDirByPathSync(dirPath, { baseDir: rootPath + path }); } const fileUri = vscode.Uri.file(filePath); fs.writeFileSync(fileUri.fsPath, item.fileContent); counter++; } }); vscode.window.showInformationMessage(`👍 ${counter} files generated.`); } } ================================================ FILE: src/view/app/Utils.ts ================================================ import nunjucks from 'nunjucks'; import { ICommand, CommandAction } from './model'; export const VER = '0.2.55'; // TODO: get this from package.json. // const TelemetryReporter = require('vscode-extension-telemetry'); // const extensionId = 'coddx-alpha'; // const extensionVersion = '0.2.11'; // const key = ''; // export const telemetry = new TelemetryReporter(extensionId, extensionVersion, key); // TODO: this crashed! export interface FilesInterface { checked?: boolean; fileMarker?: string; fileeName?: string; fileContent?: string; } export const FILE_SEPARATOR = '--->>'; export const DefaultTemplateString = `// -------------->> {{fileName}}.tsx // created time: {{YYYY}}-{{MM}}-{{DD}} {{HH}}:{{mm}} import * as React from 'react'; import './{{fileName}}.css'; export default class {{fileName}} extends React.Component { render() { return (
{/* ...code goes here... */}
) } } // -------------->> {{fileName}}.css .main {} // -------------->> __test__/{{fileName}}.test.tsx import * as React from 'react'; it('{{fileName}} - render', () => { // render(<{{fileName}} />); // expect(...); }); `; const getBuiltInParams = () => { const fd = formatDate(new Date()); return { YYYY: fd.year, MM: fd.month, DD: fd.day, HH: fd.hours24, mm: fd.minutes, ss: fd.seconds }; }; // format to: yyy-mm-dd export function formatDate(d: Date) { let month = '' + (d.getMonth() + 1); let day = '' + d.getDate(); const year = d.getFullYear(); if (month.length < 2) month = '0' + month; if (day.length < 2) day = '0' + day; return { year, month, day, hours24: d.getHours(), minutes: d.getMinutes(), seconds: d.getSeconds(), yyyymmdd: [year, month, day].join('-') }; } export function parseJsonString(jsonString: string) { if (!jsonString || !jsonString.trim()) { return {}; } let useParams = {}; try { useParams = JSON.parse(jsonString.trim()); } catch {} return useParams; } export function jsonClone(obj: any) { if (!obj) { return obj; // null or undefined } return JSON.parse(JSON.stringify(obj)); } export function deepFind(obj: any, path: string, defaultValue: any) { const travel = regexp => String.prototype.split .call(path, regexp) .filter(Boolean) .reduce((res, key) => (res !== null && res !== undefined ? res[key] : res), obj); const result = travel(/[,[\]]+?/) || travel(/[,[\].]+?/); return result === undefined || result === obj ? defaultValue : result; } export function nunjucksRender(str: string, data: any): string { const tmpl = nunjucks.compile(str); return tmpl.render({ ...getBuiltInParams(), ...data }); } // from content string => parse to get file contents & put in "files" object {} export function getTemplateItems(content: string, params: any = {}, existingFiles: any = {}): FilesInterface { const lines = content.split('\n'); const output: FilesInterface = {}; let fileContent = ''; let lastKey = ''; lines.forEach(line => { if (line.indexOf(FILE_SEPARATOR) >= 0) { // when seeing a new file separator => set the accumulated "fileContent" string for the lastKey: if (lastKey) { output[lastKey].fileContent = nunjucksRender(fileContent, params).trim(); fileContent = ''; // reset fileContent, prepare for the next file. } const arr = line.split(FILE_SEPARATOR); const itemKey = arr[arr.length - 1].trim(); const existingItem = existingFiles[itemKey] || {}; const fileName = nunjucksRender(itemKey, params).trim(); output[itemKey] = { checked: existingItem.checked !== undefined ? existingItem.checked : true, fileMarker: line.replace(itemKey, fileName), fileName, fileContent }; lastKey = itemKey; } else { if (output[lastKey] && output[lastKey].checked === true) { fileContent += line + '\n'; } } }); if (lastKey) { output[lastKey].fileContent = nunjucksRender(fileContent, params).trim(); } return output; } export const sendCommand = (vscode: any, action: CommandAction, dataStr: string) => { let command: ICommand = { action: action, content: { name: '', description: dataStr } }; if (vscode.postMessage) { vscode.postMessage(command); } }; export function getVscodeHelper(vscode: any) { return { showMessage: (msg: string) => vscode.postMessage({ action: CommandAction.ShowMessage, content: { name: '', description: msg } }), saveList: (dataStr: string) => { let command: ICommand = { action: CommandAction.Save, content: { name: '', description: dataStr } }; if (vscode.postMessage) { vscode.postMessage(command); } } }; } // In VSCode, "btoa" function is not available (?) => use this: // A helper that returns Base64 characters and their indices. // const chars = { // ascii: function() { // return 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; // }, // indices: function() { // if (!this.cache) { // this.cache = {}; // var ascii = chars.ascii(); // for (var c = 0; c < ascii.length; c++) { // var chr = ascii[c]; // this.cache[chr] = c; // } // } // return this.cache; // } // }; // const toBase64 = function(data) { // var ascii = chars.ascii(), // len = data.length - 1, // i = -1, // b64 = ''; // while (i < len) { // var code = (data.charCodeAt(++i) << 16) | (data.charCodeAt(++i) << 8) | data.charCodeAt(++i); // b64 += ascii[(code >>> 18) & 63] + ascii[(code >>> 12) & 63] + ascii[(code >>> 6) & 63] + ascii[code & 63]; // } // var pads = data.length % 3; // if (pads > 0) { // b64 = b64.slice(0, pads - 3); // while (b64.length % 4 !== 0) { // b64 += '='; // } // } // return b64; // }; ================================================ FILE: src/view/app/components/CodeGen/CodeEditor.css ================================================ .container__content { width: 440px; max-width: 100%; padding: 10px; text-align: center; } .container_editor_area { tab-size: 4ch; max-height: 400px; overflow: auto; margin: 1.67em 0; } .container__editor { font-size: 12px; font-variant-ligatures: common-ligatures; background-color: #fafafa; border-radius: 3px; } .container__editor textarea { outline: 0; } .button { display: inline-block; padding: 0 6px; text-decoration: none; background: #000; color: #fff; } .button:hover { background: linear-gradient(45deg, #E42B66, #E2433F); } /* Syntax highlighting */ .token.comment, .token.prolog, .token.doctype, .token.cdata { color: #5a9b4b; } .token.punctuation { color: #9e9e9e; } .namespace { opacity: 0.7; } .token.property, .token.tag, .token.boolean, .token.number, .token.constant, .token.symbol, .token.deleted { color: #e91e63; } .token.selector, .token.attr-name, .token.string, .token.char, .token.builtin, .token.inserted { color: #4caf50; } .token.operator, .token.entity, .token.url, .language-css .token.string, .style .token.string { color: #795548; } .token.atrule, .token.attr-value, .token.keyword { color: #3f51b5; } .token.function { color: #f44336; } .token.regex, .token.important, .token.variable { color: #ff9800; } .token.important, .token.bold { font-weight: bold; } .token.italic { font-style: italic; } .token.entity { cursor: help; } ================================================ FILE: src/view/app/components/CodeGen/CodeEditor.tsx ================================================ import * as React from 'react'; import Editor from 'react-simple-code-editor'; import { highlight, languages } from 'prismjs/components/prism-core'; import 'prismjs/components/prism-clike'; import 'prismjs/components/prism-javascript'; import './CodeEditor.css'; const styles = { editor: { fontFamily: '"Fira code", "Fira Mono", monospace', fontSize: 12 } } interface Props { code: string, onValueChange?: (code: string) => void } export default class CodeEditor extends React.Component { state = { code: this.props.code }; componentWillReceiveProps(newProps: any) { this.setState({ code: newProps.code }); } render() { return ( <> { this.setState({ code }); if (this.props.onValueChange) { this.props.onValueChange(code); } }} highlight={code => highlight(code || '', languages.js)} padding={10} style={styles.editor} /> ); } } ================================================ FILE: src/view/app/components/CodeGen/EditTemplatePanel.tsx ================================================ import * as React from 'react'; import CodeEditor from './CodeEditor'; export default class EditTemplatePanel extends React.Component { state = { code: this.props.code }; componentWillReceiveProps(newProps: any) { this.setState({ code: newProps.code }); } render() { return (
  {/* */}

this.setState({ code })} />
); } } ================================================ FILE: src/view/app/components/CodeGen/MainView.css ================================================ body, html { margin: 0; padding: 0; } button { background-color: #0063a0; color: #fff; padding: 5px 20px; border: none; cursor: pointer; } button:hover { background-color: #0073bb; } button:disabled { color: #333; cursor: not-allowed; } .mainView { display: flex; flex-direction: column; margin: 0; padding: 10px; } .mainView .body { /* min-height: 92vh; */ padding: 10px 0; } .mainView a { text-decoration: none; } .mainView textarea { width: 97%; height: 50vh; padding: 5px; font-family: 'Courier New', Courier, monospace; font-size: 1.2em; } .mainView .fileList { margin: 10px 10px; padding: 0; } .mainView .fileList li { list-style: none; } .mainView .fileList input { margin-right: 10px; } .mainView footer { position: fixed; bottom: 0px; width: 100%; background-color: #333; display: flex; margin: 0; padding: 0; } .mainView footer input { flex: 1; height: 25px; background-color: #333; color: #eee; border-color: #555; padding: 2px 5px; font-family: 'Courier New', Courier, monospace; font-size: 1.2em; } .mainView .infoBox div { display: flex; margin-bottom: 10px; } .mainView .infoBox span { display: inline-block; width: 90px; align-self: center; } .mainView .infoBox input { flex: 1; font-family: Verdana, Geneva, Tahoma, sans-serif; /* 'Courier New', Courier, monospace */ background-color: #373837; color: #fff; padding: 2px 4px; border: 1px solid #333; } ================================================ FILE: src/view/app/components/CodeGen/MainView.tsx ================================================ import * as React from 'react'; import { IConfig, ICommand, CommandAction } from '../../model'; import { FilesInterface, nunjucksRender, getTemplateItems, DefaultTemplateString, parseJsonString } from '../../Utils'; import EditTemplatePanel from './EditTemplatePanel'; import CodeEditor from './CodeEditor'; import './MainView.css'; const saveTemplate = (vscode: any, templateString: string) => { let command: ICommand = { action: CommandAction.Save, content: { name: '', description: templateString } }; if (vscode.postMessage) { vscode.postMessage(command); } }; const generateFiles = (vscode: any, path: string, files: FilesInterface) => { let command: ICommand = { action: CommandAction.GenerateFiles, content: { name: '', description: JSON.stringify({ path, files }) } }; if (vscode.postMessage) { vscode.postMessage(command); } }; interface IConfigProps { vscode: any; initialData?: IConfig; } interface IConfigState { path: string; templateString: string; editMode: boolean; config: IConfig; output: string; name: string; action: string; files: any; params: string; } let templateStr = window && window['initialData'] ? window['initialData']['templateString'] : ''; if (!templateStr) { templateStr = DefaultTemplateString; } const initialPath = window && window['initialData'] ? window['initialData']['path'] : ''; const DefaultName = 'NewComponent'; export default class MainView extends React.Component { state = { path: '', templateString: templateStr, editMode: false, config: null, output: '', name: DefaultName, action: 'Preview Output', files: getTemplateItems(window && window['initialData'] ? window['initialData']['templateString'] : ''), params: '' }; componentDidMount() { this.setState({ path: initialPath + '/' + DefaultName }); } render() { // const templateString = window['initialData']['templateString']; const { path, templateString, output, name, params } = this.state; // const [editMode, setEditMode] = React.useState(false); // Hooks don't work in VSCode Ext ?? // console.log( // '--- ', // getTemplateItems( // templateString, // {}, // { // '{{fileName}}.css': { checked: false } // } // ) // ); return (
{this.state.editMode ? ( { this.setState({ editMode: false, templateString: newContent, output: '' }); saveTemplate(this.props.vscode, newContent); }} /> ) : (

File Name* { const name = e.target.value; this.setState({ name, output: '', path: initialPath + '/' + name }); }} />
Path* this.setState({ path: e.target.value, output: '' })} />
Params this.setState({ params: e.target.value, output: '' })} />

Choose files to generate:

    {Object.keys(this.state.files).map(itemKey => { const item = this.state.files[itemKey]; const outputName = nunjucksRender(itemKey, { fileName: name, ...parseJsonString(params) }); return (
  • ); })}

   {name.length > 0 && output && ( )}

{output &&
} this.setState({ output: code })} />
)}
); } } ================================================ FILE: src/view/app/components/TaskBoard/ButtonBar.tsx ================================================ import * as React from 'react'; import styled from 'styled-components'; import Select from 'react-select'; import { getMarkdown, SearchIcon } from './Helpers'; import SearchInput from './SearchInput'; const Container = styled.div` display: flex; justify-content: space-between; text-align: right; margin: 5px 10px; span[data-name='project-name'] { font-size: 1.2em; align-self: center; margin-top: 5px; margin-left: 0px; svg { visibility: hidden; } &:hover { svg { visibility: visible; } } } > button { margin-left: 10px; } `; // const newData = parseMarkdown(defaultDataString); export function OpenFileIcon(props: any) { return ( ); } export function RefreshIcon(props: any) { return ( ); } export default ({ vscodeHelper, fileArray, selectedFile, data, onLoadData, onSave, onRefresh, onOpenFile, onSearch, onSelectFile }) => { const fileOptions = fileArray.map(path => { return { value: path, label: path }; }); fileOptions.push({ value: 'ADD', label: '+ Add file...' }); const selectedOpt = fileOptions.find(opt => opt.value === selectedFile); const [selectValue, setSelectValue] = React.useState(selectedOpt); const [searchActive, setSearchActive] = React.useState(false); return (
{ setValue(ev.target.value); if (timeoutId) { clearTimeout(timeoutId); setTimeoutId(null); } const id = resetTimer(); setTimeoutId(id); }} style={{ ...allStyle }} /> ); } ================================================ FILE: src/view/app/components/TaskBoard/Task.tsx ================================================ import * as React from 'react'; import styled from 'styled-components'; import { Draggable } from 'react-beautiful-dnd'; import TextareaAutosize from 'react-autosize-textarea'; import { DragIcon } from './Helpers'; import TaskMenu from './TaskMenu'; import { parseInline } from 'marked'; const { memo } = React; export interface TaskInterface { id: string; content: string; done: boolean; hasCheckbox?: boolean; matched?: boolean; level?: number; } const TaskContainer = styled.div<{ isDragging: boolean }>` position: relative; border-radius: 4px; margin-left: 5px; margin-bottom: 8px; background-color: ${props => (props.isDragging ? '#eef' : '#333')}; transition: background 0.1s; `; const Handle = styled.span` display: flex; margin-right: 5px; `; const TaskDisplay = styled.div` box-sizing: border-box; width: 100%; padding: 3px 0 3px 0; margin-bottom: 2px; background-color: inherit; color: var(--vscode-tab-foreground); border: 1px solid transparent; font-family: inherit; `; const StyledTextarea = styled(TextareaAutosize)` resize: none; box-sizing: border-box; width: 100%; background-color: inherit; color: var(--vscode-tab-foreground); border: 1px solid transparent; margin-top: 1px; margin-bottom: 3px; font-family: inherit; `; // use "svg, [data-type='action-icon']" to hide the drag icon svg also. const TaskWrapper = styled.div` display: flex; align-items: center; background-color: var(--vscode-tab-inactiveBackground); padding-right: 4px; [draggable] { margin: 0; } [data-type='action-icon'] { visibility: hidden; } &:hover { [data-type='action-icon'] { visibility: visible; } } `; const ActionWrapper = styled.div` position: absolute; right: 0; padding: 2px; `; const ActionIcon = styled.span` font-size: 1em; padding: 1px 5px; margin: 0 2px; cursor: pointer; background-color: #555; color: #eee; border-radius: 4px; &:hover { background-color: #777; } `; const TickMark = styled.span` font-size: 1em; color: #ddd; margin-right: 3px; `; interface TaskProps { column: any; columnIndex: number; task: TaskInterface; index: number; onChangeTitle: (title: string) => void; onDelete: (task: TaskInterface) => void; onInProgress: (task: TaskInterface) => void; onComplete: (task: TaskInterface) => void; onChangeTask: (id: string, task: TaskInterface) => void; } export default memo( ({ column, columnIndex, task, index, onChangeTitle, onDelete, onInProgress, onComplete, onChangeTask }: TaskProps) => { // mainKey is used to force re-render StyledTextarea as it doesn't auto re-render as expected. const [mainKey, setMainKey] = React.useState('key_' + Math.random()); const [isEditing, setIsEditing] = React.useState(false); const [menuActive, setMenuActive] = React.useState(''); const inputRef: React.RefObject = React.createRef(); React.useEffect(() => { // on did mount if (window['isCreatingTask'] === true) { // after clicking on "+ New Task" button => auto focus when creating task window['isCreatingTask'] = false; setIsEditing(true); } }, []); React.useEffect(() => { if (isEditing === true) { // when editing, auto set the cursor at the end: inputRef.current.setSelectionRange(inputRef.current.value.length, inputRef.current.value.length); } }, [isEditing]); const isHidden = task.matched === false; // filtered by SearchInput's value if (isHidden) { return null; } // console.log('column.title', column.title); return ( {(provided, snapshot) => ( {column.title.indexOf('✓') >= 0 ? '✓ ' : ''} {isEditing ? ( { if (ev.keyCode === 13) { ev.preventDefault(); // in this Textarea, ignore Enter Key. Otherwise, it will be a linebreak. // inputRef.current.blur(); setIsEditing(false); } // TODO: Ctrl or Cmd Enter to add the next task? }} style={{ paddingLeft: task.level > 0 ? 10 : 0 }} onChange={(ev: any) => onChangeTitle(ev.target.value)} onFocus={() => { setIsEditing(true); setMenuActive(''); }} onClick={() => { setMenuActive(''); }} onBlur={() => { if (menuActive === '' || menuActive === 'MENU') { // e.g. if user is focusing in EMOJI menu, don't exit out of editing: setIsEditing(false); } }} > {task.content} ) : ( { if (ev.target.tagName.toUpperCase() === 'A') { // user clicked on a hyperlink tag, let it behaves normally. } else { setIsEditing(true); } }} /> )} {isEditing ? ( setMenuActive('MENU')} onClick={() => { setMenuActive(''); setIsEditing(false); }} > ☰ {menuActive && ( )} ) : ( {(!task.done || !column.isLast) && ( onInProgress(task)}> 🡢 )} {/* TODO: don't show Tick icon on the first column => need column index? */} {!task.done && ( onComplete(task)}> ✓ )} onDelete(task)}> ✕ )} )} ); } ); ================================================ FILE: src/view/app/components/TaskBoard/TaskBoard.css ================================================ ================================================ FILE: src/view/app/components/TaskBoard/TaskBoard.tsx ================================================ import * as React from 'react'; import styled from 'styled-components'; import { CommandAction } from '../../model'; import { sendCommand, getVscodeHelper } from '../../Utils'; import { DragDropContext, Droppable } from 'react-beautiful-dnd'; import { parseMarkdown, defaultDataString, getMarkdown } from './Helpers'; import { TaskInterface } from './Task'; import TaskColumn, { ColumnInterface } from './TaskColumn'; import ButtonBar from './ButtonBar'; // import '@atlaskit/css-reset'; import '../../index.css'; import './TaskBoard.css'; const { useState } = React; const Columns = styled.div` display: flex; `; const selectedFile = (window && window['initialData'] ? window['initialData']['selectedFile'] : '') || 'TODO.md'; const fileArray = (window && window['initialData'] ? window['initialData']['fileList'] : 'TODO.md') .split(',') .map(str => str.trim()); const dataString = (window && window['initialData'] ? window['initialData']['dataString'] : '') || defaultDataString; let data = parseMarkdown(dataString); export default function TaskBoard({ vscode, initialData }) { const [state, setState] = useState(data); const vscodeHelper = getVscodeHelper(vscode); const reloadFile = () => sendCommand(vscode, CommandAction.Load, selectedFile); React.useEffect(() => { reloadFile(); }, []); const updateStateAndSave = newState => { setState(newState); vscodeHelper.saveList(getMarkdown(newState)); }; // const [msg, setMsg] = useState(''); // window.addEventListener('message', event => { // setMsg(JSON.stringify(event)); // // const message = event.data; // The JSON data our extension sent // // switch (message.command) { // // case 'load': // // break; // // } // }); return (
{ data = newData; setState(newData); }} onSave={dataStr => { vscodeHelper.saveList(dataStr); }} onRefresh={() => reloadFile()} onOpenFile={() => sendCommand(vscode, CommandAction.OpenFile, '')} onSearch={searchTerm => { const searchTermStr = searchTerm.toLowerCase(); // console.log('search: ', searchTerm); const newState = { ...state }; Object.keys(newState.tasks).forEach(taskId => { const t = newState.tasks[taskId]; newState.tasks[taskId].matched = t.content.toLowerCase().indexOf(searchTermStr) >= 0; }); updateStateAndSave(newState); }} onSelectFile={selectedOpt => { sendCommand(vscode, CommandAction.Load, selectedOpt.value); }} /> { if (!destination) { return; } if (destination.droppableId === source.droppableId && destination.index === source.index) { return; } if (type === 'column') { const newColOrd = Array.from(state.columnOrder); newColOrd.splice(source.index, 1); newColOrd.splice(destination.index, 0, draggableId); const newState = { ...state, columnOrder: newColOrd }; updateStateAndSave(newState); return; } const startcol = state.columns[source.droppableId]; const endcol = state.columns[destination.droppableId]; // console.log("startcol", startcol); // if (!startcol) { // return; // } if (startcol === endcol) { const tasks = Array.from(startcol.taskIds); tasks.splice(source.index, 1); tasks.splice(destination.index, 0, draggableId); const newCol = { ...startcol, taskIds: tasks }; const newState = { ...state, columns: { ...state.columns, [newCol.id]: newCol } }; // setState(newState); updateStateAndSave(newState); return; } const startTaskIds = Array.from(startcol.taskIds); startTaskIds.splice(source.index, 1); const newStart = { ...startcol, taskIds: startTaskIds }; const endTaskIds = Array.from(endcol.taskIds); endTaskIds.splice(destination.index, 0, draggableId); const newEnd = { ...endcol, taskIds: endTaskIds }; const newState = { ...state, columns: { ...state.columns, [newStart.id]: newStart, [newEnd.id]: newEnd }, tasks: data.tasks }; // setState(newState); updateStateAndSave(newState); // loadData(vscode, getMarkdown(newState)) // reload data to make sure it's reliable. }} > {provided => ( {state.columnOrder.map((id, idx) => { const col = state.columns[id]; if (idx === Object.keys(state.columns).length - 1) { col.isLast = true; } else { col.isLast = false; } const tasks = col.taskIds.map(taskid => state.tasks[taskid]); return ( { tasks[id] = newTask; const newState = { ...state, tasks: data.tasks }; updateStateAndSave(newState); }} onDeleteTask={(task: TaskInterface, column: ColumnInterface) => { const newState = { ...state }; delete newState.tasks[task.id]; newState.columns[column.id].taskIds = newState.columns[column.id].taskIds.filter( (taskId: string) => taskId !== task.id ); updateStateAndSave(newState); }} onInProgressTask={(task: TaskInterface, column: ColumnInterface) => { const newState = { ...state }; const columnKeys = Object.keys(newState.columns); const currentColumnIdx = Object.keys(newState.columns).findIndex( (id: string) => id === column.id ); const doneColumnKey = columnKeys[columnKeys.length - 1]; const nextColumnKey = columnKeys[currentColumnIdx + 1]; if (nextColumnKey === doneColumnKey) { task.done = true; // user moved this task to the right column and reached Done Column. } // remove task from current column: newState.columns[column.id].taskIds = newState.columns[column.id].taskIds.filter( (taskId: string) => taskId !== task.id ); // append task to the next column: newState.columns[nextColumnKey].taskIds.unshift(task.id); updateStateAndSave(newState); }} onCompleteTask={(task: TaskInterface, column: ColumnInterface) => { task.done = true; const newState = { ...state }; const columnKeys = Object.keys(newState.columns); const doneColumnKey = columnKeys[columnKeys.length - 1]; // remove task from current column: newState.columns[column.id].taskIds = newState.columns[column.id].taskIds.filter( (taskId: string) => taskId !== task.id ); // append task to the top of Done column: newState.columns[doneColumnKey].taskIds.unshift(task.id); updateStateAndSave(newState); }} /> ); })} {provided.placeholder} )} {/*
{msg}
*/}
); } ================================================ FILE: src/view/app/components/TaskBoard/TaskColumn.tsx ================================================ import * as React from 'react'; import styled from 'styled-components'; import { Droppable, Draggable } from 'react-beautiful-dnd'; import Task, { TaskInterface } from './Task'; const { memo } = React; const Container = styled.div<{ isDragging: boolean }>` min-height: 150px; margin: 0px; border-radius: 2px; width: 33.3vw; display: flex; flex-direction: column; background-color: ${props => (props.isDragging ? 'lightgreen' : 'inherit')}; `; const Title = styled.div` padding: 5px; margin: 5px; > span { padding: 5px; background-color: var(--vscode-editor-selectionBackground); border-radius: 2px; color: var(--vscode-editor-selectionForeground); } `; // or use: vscode-tab-activeBackground & vscode-tab-foreground const List = styled.div<{ isDraggingOver: boolean }>` padding: 5px; transition: background 0.1s; background-color: ${props => (props.isDraggingOver ? 'var(--vscode-tab-border)' : 'inherit ')}; flex-grow: 1; `; export interface ColumnInterface { id: string; title: string; } interface ColumnProps { tasks: TaskInterface[]; columnIndex: number; column: ColumnInterface; onChangeTask: (idx: string, task: any) => void; onDeleteTask: (task: TaskInterface, column: ColumnInterface) => void; onInProgressTask: (task: TaskInterface, column: ColumnInterface) => void; onCompleteTask: (task: TaskInterface, column: ColumnInterface) => void; } export default memo( ({ column, tasks, columnIndex, onChangeTask, onDeleteTask, onInProgressTask, onCompleteTask }: ColumnProps) => ( {(provided, snapshot) => ( <span>{column.title}</span> {(provided, snapshot) => ( {tasks.map((t, i) => { if (!t || !t.id) { return null; } return ( { t.content = newTitle; onChangeTask(t.id, t); }} onDelete={(task: TaskInterface) => onDeleteTask(task, column)} onInProgress={(task: TaskInterface) => onInProgressTask(task, column)} onComplete={(task: TaskInterface) => onCompleteTask(task, column)} onChangeTask={onChangeTask} /> ); })} {provided.placeholder} )} )} ) ); ================================================ FILE: src/view/app/components/TaskBoard/TaskMenu.tsx ================================================ import * as React from 'react'; import styled from 'styled-components'; const emojis = [ { text: '(bug)', char: '🐞', names: 'bug, ladybug, defect, issue, problem, error' }, // JIRA emojis { text: ':)', char: '😊', names: 'smile, happy, glad' }, { text: ':(', char: '😟', names: 'sad, unhappy, cry' }, { text: ':P', char: '😛', names: 'tongue, fun' }, { text: ':D', char: '😄', names: 'broad smile, big open smile, amusemed, excited, happy' }, { text: ';)', char: '😉', names: 'wink' }, { text: '(y)', char: '👍', names: 'yes, thumb up, okay, agree, correct, good' }, { text: '(n)', char: '👎', names: 'no, thumb down, not okay, disagree, bad' }, { text: '(on)', char: '☑️', names: 'on, ticked, checked, yes, done' }, { text: '(off)', char: '🔲', names: 'off, unchecked, checkbox, empty box, square' }, { text: '(!)', char: '⚠️', names: 'warning, attention, dangerous, exclamation' }, { text: '(*)', char: '⭐', names: 'star, rate, favorite, favourite, award, reward' }, { text: '(/)', char: '✅', names: 'ticked, checked, yes, done, correct' }, { text: '(x)', char: '❌', names: 'crossed, deleted, blocked, wrong, incorrect, no' }, { text: '(i)', char: 'ℹ️', names: 'info, information, attention' }, { text: '(+)', char: '➕', names: 'plus, addition' }, { text: '(-)', char: '➖', names: 'minus, substraction' }, { text: '(?)', char: '❓', names: 'question mark, ask' }, { text: '<3', char: '💗', names: 'heart, pink heart, love, happy' }, { text: ' { setMenuActive(''); setIsEditing(false); }; return (
{menuActive === 'MENU' ? (
  • { // can't use onClick as it will close the menu without coming here. ev.preventDefault(); task.level = (task.level + 1) % 2; onChangeTask(task.id, task); closeMenu(); }} > Toggle Task / Sub-task
  • { // can't use onClick as it will close the menu without coming here. ev.preventDefault(); setMenuActive('EMOJI'); }} > Insert Emoji Icon
  • ) : ( setSearchText(ev.target.value)} onKeyUp={ev => { if (ev.keyCode === 27) { closeMenu(); } }} style={{ width: '87%', margin: 5, padding: 3 }} /> {emojis.map(emoji => { const txt = searchText.trim().toLowerCase(); if (txt && emoji.names.toLowerCase().indexOf(txt) < 0) { return null; } return (
  • { ev.preventDefault(); task.content += emoji.char; onChangeTask(task.id, task); closeMenu(); setMainKey('key_' + Math.random()); }} > {emoji.char}     {emoji.text}
  • ); })}
    )}
    ); } ================================================ FILE: src/view/app/config.tsx ================================================ import * as React from "react"; import { IConfig, IUser, ICommand, CommandAction } from "./model"; interface IConfigProps { vscode: any; initialData: IConfig; } interface IConfigState { config: IConfig; } export default class Config extends React.Component< IConfigProps, IConfigState > { constructor(props: any) { super(props); let initialData = this.props.initialData; let oldState = this.props.vscode.getState(); if (oldState) { this.state = oldState; } else { this.state = { config: initialData }; } } private defineState(newSate: IConfigState) { this.setState(newSate); this.props.vscode.setState(newSate); } onChangeUserActiveState(userIndex: number) { let newState = { ...this.state }; newState.config.users[userIndex].active = !newState.config.users[userIndex] .active; this.defineState(newState); } onAddRole(event: React.KeyboardEvent, userIndex: number) { if (event.keyCode === 13 && event.currentTarget.value !== "") { let newState = { ...this.state }; newState.config.users[userIndex].roles.push(event.currentTarget.value); this.defineState(newState); event.currentTarget.value = ""; } } onAddUser(event: React.KeyboardEvent) { if (event.keyCode === 13 && event.currentTarget.value !== "") { let newState = { ...this.state }; let newUser: IUser = { name: event.currentTarget.value, active: true, roles: [] }; newState.config.users.push(newUser); this.defineState(newState); event.currentTarget.value = ""; } } renderUsers(users: IUser[]) { return (

    User List :

      {users && users.length > 0 ? users.map((user, userIndex) => { let roles = user.roles && user.roles.length > 0 ? user.roles.join(",") : null; return (
    • {user.name}
      Is active :{" "} this.onChangeUserActiveState(userIndex)} />
      Roles : {roles} this.onAddRole(event, userIndex)} />
    • ); }) : null}
    this.onAddUser(event)} />
    ); } render() { return (

    Config name : {this.state.config.name}

    {" "} {this.state.config.description} {this.renderUsers(this.state.config.users)}
    this.saveConfig()} />
    ); } saveConfig() { let command: ICommand = { action: CommandAction.Save, content: this.state.config }; this.props.vscode.postMessage(command); } } ================================================ FILE: src/view/app/index.css ================================================ :root { /* --text-color: #eee; */ /* --bg-color: #1d1e1d; */ --button-bg-color: rgb(3, 0, 150); --button-text-color: #777; } html, body { background-color: var(--bg-color); /* Dracula+ Dark Theme */ } a { text-decoration: none; color: darkslategray; } li { margin-bottom: 15px; } input { margin-left: 10px; } .save { margin-top: 10px; } .__reactSelect .__select__control { background-color: var(--vscode-tab-background); border: none; margin-top: 5px; min-height: 30px; } .__reactSelect .__select__input { color: var(--button-text-color); } .__reactSelect .__select__single-value { color: var(--button-text-color); padding: 0; } .__reactSelect .__select__option { color: var(--button-text-color); } .__reactSelect .__select__indicator-separator { background-color: var(--vscode-tab-inactiveBackground); } .__reactSelect .__select__indicator { color: var(--button-text-color); } .__reactSelect .__select__option--is-selected { background-color: var(--vscode-tab-inactiveBackground); } ================================================ FILE: src/view/app/index.tsx ================================================ import * as React from 'react'; import * as ReactDOM from 'react-dom'; import './index.css'; import { IConfig } from './model'; // import Config from "./config"; import MainView from './components/CodeGen/MainView'; import TaskBoard from './components/TaskBoard/TaskBoard'; declare global { interface Window { acquireVsCodeApi(): any; initialData: IConfig; } } const vscode = window.acquireVsCodeApi(); if (window.initialData.name === 'TaskBoard') { ReactDOM.render( , document.getElementById('root') ); } else { ReactDOM.render( , document.getElementById('root') ); } ================================================ FILE: src/view/app/model.ts ================================================ export interface IConfig { name: string; description?: string; users?: IUser[]; } export interface IUser { name: string; active: boolean; roles: string[]; } export interface ICommand { action: CommandAction; content: IConfig; } export enum CommandAction { ShowMessage, Save, Load, OpenFile, GenerateFiles } ================================================ FILE: src/view/app/tsconfig.json ================================================ { "compilerOptions": { "module": "esnext", "moduleResolution": "node", "target": "es6", "outDir": "configViewer", "lib": [ "es6", "dom" ], "jsx": "react", "sourceMap": true, "rootDir": "..", "noUnusedLocals": true, "noImplicitReturns": true, "noFallthroughCasesInSwitch": true, "experimentalDecorators": true }, "exclude": [ "node_modules" ] } ================================================ FILE: src/webpack.config.js ================================================ const path = require("path"); module.exports = { entry: { configViewer: "./src/view/app/index.tsx" }, output: { path: path.resolve(__dirname, "configViewer"), filename: "[name].js" }, devtool: "eval-source-map", resolve: { extensions: [".js", ".ts", ".tsx", ".json"] }, module: { rules: [ { test: /\.(ts|tsx)$/, loader: "ts-loader", options: {} }, { test: /\.css$/, use: [ { loader: "style-loader" }, { loader: "css-loader" } ] } ] }, performance: { hints: false } }; ================================================ FILE: tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "target": "es6", "outDir": "out", "lib": [ "es6", "dom" ], "sourceMap": false, "rootDir": "src", // "strict": true /* enable all strict type-checking options */ /* Additional Checks */ // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ // "noUnusedParameters": true, /* Report errors on unused parameters. */ }, "exclude": [ "node_modules", ".vscode-test", "**/view/app/index.css", "**/view/app/index.tsx", "**/view/app/tsconfig.json", "**/view/app/config.tsx" ] } ================================================ FILE: tslint.json ================================================ { "rules": { "no-string-throw": true, "no-unused-expression": true, "no-duplicate-variable": true, "curly": true, "class-name": true, "semicolon": [ true, "always" ], "triple-equals": true }, "defaultSeverity": "warning" } ================================================ FILE: vsc-extension-quickstart.md ================================================ # Welcome to your VS Code Extension ## What's in the folder * This folder contains all of the files necessary for your extension. * `package.json` - this is the manifest file in which you declare your extension and command. * The sample plugin registers a command and defines its title and command name. With this information VS Code can show the command in the command palette. It doesn’t yet need to load the plugin. * `src/extension.ts` - this is the main file where you will provide the implementation of your command. * The file exports one function, `activate`, which is called the very first time your extension is activated (in this case by executing the command). Inside the `activate` function we call `registerCommand`. * We pass the function containing the implementation of the command as the second parameter to `registerCommand`. ## Get up and running straight away * Press `F5` to open a new window with your extension loaded. * Run your command from the command palette by pressing (`Ctrl+Shift+P` or `Cmd+Shift+P` on Mac) and typing `Hello World`. * Set breakpoints in your code inside `src/extension.ts` to debug your extension. * Find output from your extension in the debug console. ## Make changes * You can relaunch the extension from the debug toolbar after changing code in `src/extension.ts`. * You can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes. ## Explore the API * You can open the full set of our API when you open the file `node_modules/vscode/vscode.d.ts`. ## Run tests * Open the debug viewlet (`Ctrl+Shift+D` or `Cmd+Shift+D` on Mac) and from the launch configuration dropdown pick `Extension Tests`. * Press `F5` to run the tests in a new window with your extension loaded. * See the output of the test result in the debug console. * Make changes to `test/extension.test.ts` or create new test files inside the `test` folder. * By convention, the test runner will only consider files matching the name pattern `**.test.ts`. * You can create folders inside the `test` folder to structure your tests any way you want. ## Go further * Reduce the extension size and improve the startup time by [bundling your extension](https://code.visualstudio.com/api/working-with-extensions/testing-extension). * [Publish your extension](https://code.visualstudio.com/api/working-with-extensions/publishing-extension) on the VSCode extension marketplace. * Automate builds by setting up [Continuous Integration](https://code.visualstudio.com/api/working-with-extensions/continuous-integration). ================================================ FILE: webpack.config.js ================================================ const path = require("path"); module.exports = { entry: { configViewer: "./src/view/app/index.tsx" }, output: { path: path.resolve(__dirname, "configViewer"), filename: "[name].js" }, devtool: "eval-source-map", resolve: { extensions: [".js", ".ts", ".tsx", ".json"] }, module: { rules: [ { test: /\.(ts|tsx)$/, loader: "ts-loader", options: {} }, { test: /\.css$/, use: [ { loader: "style-loader" }, { loader: "css-loader" } ] } ] }, performance: { hints: false } };